3. Events smtp:fail:protocol and smtp:fail:syntax
- 4. JSON and LDAP lookup support, PAM, RADIUS, perl, SPF, DKIM, DMARC and ARC
- support, all the router and authenticator drivers, and all the transport
+ 4. Support for Sieve filters can be omitted at build time
+
+ 5. JSON and LDAP lookup support, Sieve, PAM, RADIUS, perl, SPF, DKIM, DMARC and
+ ARC support, all the router and authenticator drivers, and all the transport
drivers except smtp, can now be built as loadable modules
Version 4.98
header.o host.o host_address.o ip.o log.o lss.o match.o md5.o moan.o \
os.o parse.o priv.o proxy.o queue.o \
rda.o readconf.o receive.o retry.o rewrite.o rfc2047.o regex_cache.o \
- route.o search.o sieve.o smtp_in.o smtp_out.o spool_in.o spool_out.o \
+ route.o search.o smtp_in.o smtp_out.o spool_in.o spool_out.o \
std-crypto.o store.o string.o tls.o tod.o transport.o tree.o verify.o \
xtextencode.o environment.o macro.o \
$(OBJ_LOOKUPS) $(OBJ_ROUTERS) $(OBJ_AUTHS) \
rfc2047.o: $(HDRS) rfc2047.c
route.o: $(HDRS) route.c
search.o: $(HDRS) search.c
-sieve.o: $(HDRS) sieve.c
smtp_in.o: $(HDRS) smtp_in.c
smtp_out.o: $(HDRS) smtp_out.c
spool_in.o: $(HDRS) spool_in.c
routers ROUTER ACCEPT DNSLOOKUP IPLITERAL IPLOOKUP MANUALROUTE QUERYPROGRAM REDIRECT
transports TRANSPORT APPENDFILE AUTOREPLY LMTP PIPE QUEUEFILE SMTP
auths AUTH CRAM_MD5 CYRUS_SASL DOVECOT EXTERNAL GSASL HEIMDAL_GSSAPI PLAINTEXT SPA TLS
- miscmods SUPPORT ARC _DKIM DMARC PAM PERL RADIUS SPF
+ miscmods SUPPORT ARC _DKIM DMARC PAM PERL RADIUS _SIEVE_FILTER SPF
END
# See if there is a definition of EXIM_PERL in what we have built so far.
pam.c pam_api.h \
perl.c perl_api.h \
radius.c radius_api.h \
+ sieve_filter.c sieve_filter_api.h \
spf.c spf.h spf_api.h
do
ln -s ../../src/$d/$f `basename $f`
globals.c hash.c header.c host.c host_address.c ip.c log.c lss.c match.c md5.c moan.c \
parse.c priv.c proxy.c queue.c rda.c readconf.c receive.c retry.c rewrite.c \
regex_cache.c rfc2047.c route.c search.c setenv.c environment.c \
- sieve.c smtp_in.c smtp_out.c spool_in.c spool_out.c std-crypto.c store.c \
+ smtp_in.c smtp_out.c spool_in.c spool_out.c std-crypto.c store.c \
string.c tls.c tlscert-gnu.c tlscert-openssl.c tls-cipher-stdname.c \
tls-gnu.c tls-openssl.c \
tod.c transport.c tree.c verify.c version.c xtextencode.c \
# EXIM_MONITOR=eximon.bin
+#------------------------------------------------------------------------------
+# Compiling with support for Sieve filters is the default. To disable this
+# uncomment the line below.
+
+# DISABLE_SIEVE_FILTER=yes
+
+# Alternatively, to build the support as a dynamically-loaded module uncomment
+# this line.
+
+# SUPPORT_SIEVE_FILTER=2
+
#------------------------------------------------------------------------------
# Compiling Exim with content scanning support: If you want to compile Exim
# with support for message body content scanning, set WITH_CONTENT_SCAN to
#define DISABLE_PIPE_CONNECT
#define DISABLE_PRDR
#define DISABLE_QUEUE_RAMP
+#define DISABLE_SIEVE_FILTER
#define DISABLE_TLS
#define DISABLE_TLS_RESUME
#define DISABLE_WELLKNOWN
#define SUPPORT_ARC
#define SUPPORT_DKIM
#define SUPPORT_PERL
+#define SUPPORT_SIEVE_FILTER
#define SUPPORT_RADIUS
#define SYSLOG_LOG_PID
#if defined(EXIM_PERL) && (!defined(SUPPORT_PERL) || SUPPORT_PERL!=2)
extern misc_module_info perl_module_info;
#endif
+#if !defined(DISABLE_SIEVE_FILTER) && (!defined(SUPPORT_SIEVE_FILTER) || SUPPORT_SIEVE_FILTER!=2)
+extern misc_module_info sieve_filter_module_info;
+#endif
void
init_misc_mod_list(void)
#if defined(EXIM_PERL) && (!defined(SUPPORT_PERL) || SUPPORT_PERL!=2)
misc_mod_add(&perl_module_info);
#endif
+#if !defined(DISABLE_SIEVE_FILTER) && (!defined(SUPPORT_SIEVE_FILTER) || SUPPORT_SIEVE_FILTER!=2)
+ misc_mod_add(&sieve_filter_module_info);
+#endif
}
#ifdef WITH_CONTENT_SCAN
g = string_cat(g, US" Content_Scanning");
#endif
+#ifndef DISABLE_SIEVE_FILTER
+ g = string_cat(g, US" Sieve_filter");
+#endif
#ifdef SUPPORT_CRYPTEQ
g = string_cat(g, US" crypteq");
#endif
);
return;
case CMDINFO_SIEVE:
- for (const uschar ** pp = exim_sieve_extension_list; *pp; ++pp)
- fprintf(stream, "%s\n", *pp);
+ {
+ const misc_module_info * mi;
+ typedef void (*fn_t)(FILE *);
+ if ((mi = misc_mod_find(US"sieve_filter", NULL)))
+ (((fn_t *) mi->functions)[SIEVE_EXTENSIONS]) (stream);
+ else
+ fprintf(stream, "Sieve filtering not available\n");
+ }
return;
case CMDINFO_DSCP:
dscp_list_to_stream(stream);
#ifdef EXIM_PERL
# include "miscmods/perl_api.h"
#endif
+#ifndef DISABLE_SIEVE
+# include "miscmods/sieve_filter_api.h"
+#endif
/* The following stuff must follow the inclusion of config.h because it
requires various things that are set therein. */
f.enable_dollar_recipients = FALSE;
f.system_filtering = FALSE;
}
-else
+else if (filter_type == FILTER_SIEVE)
{
- yield = filter_type == FILTER_SIEVE
- ? sieve_interpret(filebuf, RDO_REWRITE, NULL, &generated, &error)
- : filter_interpret(filebuf, RDO_REWRITE, &generated, &error);
+ const misc_module_info * mi;
+ uschar * errstr = NULL;
+ typedef int (*fn_t)(const uschar *, int, const sieve_block *,
+ address_item **, uschar **);
+ if (!(mi = misc_mod_find(US"sieve_filter", &errstr)))
+ {
+ printf("exim: Sieve filtering not available: %s\n", errstr ? errstr : US"?");
+ return FALSE;
+ }
+ yield = (((fn_t *) mi->functions)[SIEVE_INTERPRET])
+ (filebuf, RDO_REWRITE, NULL, &generated, &error);
}
+else
+ yield = filter_interpret(filebuf, RDO_REWRITE, &generated, &error);
return yield != FF_ERROR;
}
extern void sha1_end(hctx *, const uschar *, int, uschar *);
extern void sha1_mid(hctx *, const uschar *);
extern void sha1_start(hctx *);
-extern int sieve_interpret(const uschar *, int, const sieve_block *,
- address_item **, uschar **);
extern void sigalrm_handler(int);
extern void single_queue_run(qrunner *, const uschar *, const uschar *);
extern int smtp_boundsock(smtp_connect_args *);
extern gid_t exim_gid; /* To be used with exim_uid */
extern BOOL exim_gid_set; /* TRUE if exim_gid set */
extern uschar *exim_path; /* Path to exec exim */
-extern const uschar *exim_sieve_extension_list[]; /* list of sieve extensions */
extern uid_t exim_uid; /* Non-root uid for exim */
extern BOOL exim_uid_set; /* TRUE if exim_uid set */
extern int expand_level; /* Nesting depth; indent for debug */
# Note that the sources from pdkim/ are linked into the build.../miscmods/ dir
# by scripts/Makelinks.
-arc.o arc.so: $(HDRS) pdkim.h arc.c
-dkim.o dkim.so: $(HDRS) dkim.h dkim.c dkim_transport.c \
- crypt_ver.h pdkim.h pdkim_hash.h pdkim.c \
- signing.h signing.c
-dmarc.o dmarc.so: $(HDRS) pdkim.h dmarc.h dmarc.c
-dummy.o: dummy.c
-pam.o pam.so: $(HDRS) pam.c
-perl.o perl.so: $(HDRS) perl.c
-radius.o radius.so: $(HDRS) radius.c
-spf.o spf.so: $(HDRS) spf.h spf.c
+arc.o arc.so: $(HDRS) pdkim.h arc.c
+dkim.o dkim.so: $(HDRS) dkim.h dkim.c dkim_transport.c \
+ crypt_ver.h pdkim.h pdkim_hash.h pdkim.c \
+ signing.h signing.c
+dmarc.o dmarc.so: $(HDRS) pdkim.h dmarc.h dmarc.c
+dummy.o: dummy.c
+pam.o pam.so: $(HDRS) pam.c
+perl.o perl.so: $(HDRS) perl.c
+radius.o radius.so: $(HDRS) radius.c
+sieve_filter.o sieve_filter.so: $(HDRS) sieve_filter.c
+spf.o spf.so: $(HDRS) spf.h spf.c
dkim.o:
@echo "$(CC) dkim.c dkim_transport.c pdkim.c signing.c"
--- /dev/null
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/*
+Copyright (c) The Exim Maintainers 2016 - 2024
+Copyright (c) Michael Haardt 2003 - 2015
+See the file NOTICE for conditions of use and distribution.
+SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+/* This code was contributed by Michael Haardt. */
+
+
+/* Sieve mail filter. */
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../exim.h"
+
+#if HAVE_ICONV
+# include <iconv.h>
+#endif
+
+/* Define this for RFC compliant \r\n end-of-line terminators. */
+/* Undefine it for UNIX-style \n end-of-line terminators (default). */
+#undef RFC_EOL
+
+/* Define this for development of the Sieve extension "encoded-character". */
+#define ENCODED_CHARACTER
+
+/* Define this for development of the Sieve extension "envelope-auth". */
+#undef ENVELOPE_AUTH
+
+/* Define this for development of the Sieve extension "enotify". */
+#define ENOTIFY
+
+/* Define this for the Sieve extension "subaddress". */
+#define SUBADDRESS
+
+/* Define this for the Sieve extension "vacation". */
+#define VACATION
+
+/* Must be >= 1 */
+#define VACATION_MIN_DAYS 1
+/* Must be >= VACATION_MIN_DAYS, must be > 7, should be > 30 */
+#define VACATION_MAX_DAYS 31
+
+/* Keep this at 75 to accept only RFC compliant MIME words. */
+/* Increase it if you want to match headers from buggy MUAs. */
+#define MIMEWORD_LENGTH 75
+
+struct Sieve {
+ const uschar *filter;
+ const uschar *pc;
+ int line;
+ const uschar *errmsg;
+ int keep;
+ int require_envelope;
+ int require_fileinto;
+#ifdef ENCODED_CHARACTER
+ BOOL require_encoded_character;
+#endif
+#ifdef ENVELOPE_AUTH
+ int require_envelope_auth;
+#endif
+#ifdef ENOTIFY
+ int require_enotify;
+ struct Notification *notified;
+#endif
+ const uschar *enotify_mailto_owner;
+#ifdef SUBADDRESS
+ int require_subaddress;
+#endif
+#ifdef VACATION
+ BOOL require_vacation;
+ BOOL vacation_ran;
+#endif
+ const uschar *inbox;
+ const uschar *vacation_directory;
+ const uschar *subaddress;
+ const uschar *useraddress;
+ BOOL require_copy;
+ BOOL require_iascii_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 };
+#else
+enum AddressPart { ADDRPART_LOCALPART, ADDRPART_DOMAIN, ADDRPART_ALL };
+#endif
+enum RelOp { LT, LE, EQ, GE, GT, NE };
+
+struct Notification {
+ gstring method;
+ gstring importance;
+ gstring message;
+ struct Notification *next;
+};
+
+/* This should be a complete list of supported extensions, so that an external
+ManageSieve (RFC 5804) program can interrogate the current Exim binary for the
+list of extensions and provide correct information to a client.
+
+We'll emit the list in the order given here; keep it alphabetically sorted, so
+that callers don't get surprised.
+
+List *MUST* end with a NULL. Which at least makes ifdef-vs-comma easier. */
+
+static const uschar *exim_sieve_extension_list[] = {
+ CUS"comparator-i;ascii-numeric",
+ CUS"copy",
+#ifdef ENCODED_CHARACTER
+ CUS"encoded-character",
+#endif
+#ifdef ENOTIFY
+ CUS"enotify",
+#endif
+ CUS"envelope",
+#ifdef ENVELOPE_AUTH
+ CUS"envelope-auth",
+#endif
+ CUS"fileinto",
+#ifdef SUBADDRESS
+ CUS"subaddress",
+#endif
+#ifdef VACATION
+ CUS"vacation",
+#endif
+ NULL
+};
+
+static int eq_asciicase(const gstring * needle, const gstring * haystack, BOOL match_prefix);
+static int parse_test(struct Sieve *filter, int *cond, int exec);
+static int parse_commands(struct Sieve *filter, int exec, address_item **generated);
+
+static uschar str_from_c[] = "From";
+static const gstring str_from = { .s = str_from_c, .ptr = 4, .size = 5 };
+static uschar str_to_c[] = "To";
+static const gstring str_to = { .s = str_to_c, .ptr = 2, .size = 3 };
+static uschar str_cc_c[] = "Cc";
+static const gstring str_cc = { .s = str_cc_c, .ptr = 2, .size = 3 };
+static uschar str_bcc_c[] = "Bcc";
+static const gstring str_bcc = { .s = str_bcc_c, .ptr = 3, .size = 4 };
+#ifdef ENVELOPE_AUTH
+static uschar str_auth_c[] = "auth";
+static const gstring str_auth = { .s = str_auth_c, .ptr = 4, .size = 5 };
+#endif
+static uschar str_sender_c[] = "Sender";
+static const gstring str_sender = { .s = str_sender_c, .ptr = 6, .size = 7 };
+static uschar str_resent_from_c[] = "Resent-From";
+static const gstring str_resent_from = { .s = str_resent_from_c, .ptr = 11, .size = 12 };
+static uschar str_resent_to_c[] = "Resent-To";
+static const gstring str_resent_to = { .s = str_resent_to_c, .ptr = 9, .size = 10 };
+static uschar str_fileinto_c[] = "fileinto";
+static const gstring str_fileinto = { .s = str_fileinto_c, .ptr = 8, .size = 9 };
+static uschar str_envelope_c[] = "envelope";
+static const gstring str_envelope = { .s = str_envelope_c, .ptr = 8, .size = 9 };
+#ifdef ENCODED_CHARACTER
+static uschar str_encoded_character_c[] = "encoded-character";
+static const gstring str_encoded_character = { .s = str_encoded_character_c, .ptr = 17, .size = 18 };
+#endif
+#ifdef ENVELOPE_AUTH
+static uschar str_envelope_auth_c[] = "envelope-auth";
+static const gstring str_envelope_auth = { .s = str_envelope_auth_c, .ptr = 13, .size = 14 };
+#endif
+#ifdef ENOTIFY
+static uschar str_enotify_c[] = "enotify";
+static const gstring str_enotify = { .s = str_enotify_c, .ptr = 7, .size = 8 };
+static uschar str_online_c[] = "online";
+static const gstring str_online = { .s = str_online_c, .ptr = 6, .size = 7 };
+static uschar str_maybe_c[] = "maybe";
+static const gstring str_maybe = { .s = str_maybe_c, .ptr = 5, .size = 6 };
+static uschar str_auto_submitted_c[] = "Auto-Submitted";
+static const gstring str_auto_submitted = { .s = str_auto_submitted_c, .ptr = 14, .size = 15 };
+#endif
+#ifdef SUBADDRESS
+static uschar str_subaddress_c[] = "subaddress";
+static const gstring str_subaddress = { .s = str_subaddress_c, .ptr = 10, .size = 11 };
+#endif
+#ifdef VACATION
+static uschar str_vacation_c[] = "vacation";
+static const gstring str_vacation = { .s = str_vacation_c, .ptr = 8, .size = 9 };
+static uschar str_subject_c[] = "Subject";
+static const gstring str_subject = { .s = str_subject_c, .ptr = 7, .size = 8 };
+#endif
+static uschar str_copy_c[] = "copy";
+static const gstring str_copy = { .s = str_copy_c, .ptr = 4, .size = 5 };
+static uschar str_iascii_casemap_c[] = "i;ascii-casemap";
+static const gstring str_iascii_casemap = { .s = str_iascii_casemap_c, .ptr = 15, .size = 16 };
+static uschar str_enascii_casemap_c[] = "en;ascii-casemap";
+static const gstring str_enascii_casemap = { .s = str_enascii_casemap_c, .ptr = 16, .size = 17 };
+static uschar str_ioctet_c[] = "i;octet";
+static const gstring str_ioctet = { .s = str_ioctet_c, .ptr = 7, .size = 8 };
+static uschar str_iascii_numeric_c[] = "i;ascii-numeric";
+static const gstring str_iascii_numeric = { .s = str_iascii_numeric_c, .ptr = 15, .size = 16 };
+static uschar str_comparator_iascii_casemap_c[] = "comparator-i;ascii-casemap";
+static const gstring str_comparator_iascii_casemap = { .s = str_comparator_iascii_casemap_c, .ptr = 26, .size = 27 };
+static uschar str_comparator_enascii_casemap_c[] = "comparator-en;ascii-casemap";
+static const gstring str_comparator_enascii_casemap = { .s = str_comparator_enascii_casemap_c, .ptr = 27, .size = 28 };
+static uschar str_comparator_ioctet_c[] = "comparator-i;octet";
+static const gstring str_comparator_ioctet = { .s = str_comparator_ioctet_c, .ptr = 18, .size = 19 };
+static uschar str_comparator_iascii_numeric_c[] = "comparator-i;ascii-numeric";
+static const gstring str_comparator_iascii_numeric = { .s = str_comparator_iascii_numeric_c, .ptr = 26, .size = 27 };
+
+
+/*************************************************
+* Encode to quoted-printable *
+*************************************************/
+
+/*
+Arguments:
+ src UTF-8 string
+
+Returns
+ dst, allocated, a US-ASCII string
+*/
+
+static gstring *
+quoted_printable_encode(const gstring * src)
+{
+gstring * dst = NULL;
+uschar ch;
+size_t line = 0;
+
+for (const uschar * start = src->s, * end = start + src->ptr;
+ start < end; ++start)
+ {
+ ch = *start;
+ if (line >= 73) /* line length limit */
+ {
+ dst = string_catn(dst, US"=\n", 2); /* line split */
+ line = 0;
+ }
+ if ( (ch >= '!' && ch <= '<')
+ || (ch >= '>' && ch <= '~')
+ || ( (ch == '\t' || ch == ' ')
+ && start+2 < end && (start[1] != '\r' || start[2] != '\n') /* CRLF */
+ )
+ )
+ {
+ dst = string_catn(dst, start, 1); /* copy char */
+ ++line;
+ }
+ else if (ch == '\r' && start+1 < end && start[1] == '\n') /* CRLF */
+ {
+ dst = string_catn(dst, US"\n", 1); /* NL */
+ line = 0;
+ ++start; /* consume extra input char */
+ }
+ else
+ {
+ dst = string_fmt_append(dst, "=%02X", ch);
+ line += 3;
+ }
+ }
+
+(void) string_from_gstring(dst);
+gstring_release_unused(dst);
+return dst;
+}
+
+
+/*************************************************
+* Check mail address for correct syntax *
+*************************************************/
+
+/*
+Check mail address for being syntactically correct.
+
+Arguments:
+ filter points to the Sieve filter including its state
+ address String containing one address
+
+Returns
+ 1 Mail address is syntactically OK
+ -1 syntax error
+*/
+
+static int
+check_mail_address(struct Sieve * filter, const gstring * address)
+{
+int start, end, domain;
+uschar * error, * ss;
+
+if (address->ptr > 0)
+ {
+ ss = parse_extract_address(address->s, &error, &start, &end, &domain,
+ FALSE);
+ if (!ss)
+ {
+ filter->errmsg = string_sprintf("malformed address \"%s\" (%s)",
+ address->s, error);
+ return -1;
+ }
+ else
+ return 1;
+ }
+else
+ {
+ filter->errmsg = CUS "empty address";
+ return -1;
+ }
+}
+
+
+/*************************************************
+* Decode URI encoded string *
+*************************************************/
+
+/*
+Arguments:
+ str URI encoded string
+
+Returns
+ str is modified in place
+ TRUE Decoding successful
+ FALSE Encoding error
+*/
+
+#ifdef ENOTIFY
+static BOOL
+uri_decode(gstring * str)
+{
+uschar *s, *t, *e;
+
+if (str->ptr == 0) return TRUE;
+for (t = s = str->s, e = s + str->ptr; s < e; )
+ if (*s == '%')
+ {
+ if (s+2 < e && isxdigit(s[1]) && isxdigit(s[2]))
+ {
+ *t++ = ((isdigit(s[1]) ? s[1]-'0' : tolower(s[1])-'a'+10)<<4)
+ | (isdigit(s[2]) ? s[2]-'0' : tolower(s[2])-'a'+10);
+ s += 3;
+ }
+ else return FALSE;
+ }
+ else
+ *t++ = *s++;
+
+*t = '\0';
+str->ptr = t - str->s;
+return TRUE;
+}
+
+
+/*************************************************
+* Parse mailto URI *
+*************************************************/
+
+/*
+Parse mailto-URI.
+
+ mailtoURI = "mailto:" [ to ] [ headers ]
+ to = [ addr-spec *("%2C" addr-spec ) ]
+ headers = "?" header *( "&" header )
+ header = hname " = " hvalue
+ hname = *urlc
+ hvalue = *urlc
+
+Arguments:
+ filter points to the Sieve filter including its state
+ uri URI, excluding scheme
+ recipient list of recipients; prepnded to
+ body
+
+Returns
+ 1 URI is syntactically OK
+ 0 Unknown URI scheme
+ -1 syntax error
+*/
+
+static int
+parse_mailto_uri(struct Sieve * filter, const uschar * uri,
+ string_item ** recipient, gstring * header, gstring * subject,
+ gstring * body)
+{
+const uschar * start;
+
+if (Ustrncmp(uri, "mailto:", 7))
+ {
+ filter->errmsg = US "Unknown URI scheme";
+ return 0;
+ }
+
+uri += 7;
+if (*uri && *uri != '?')
+ for (;;)
+ {
+ /* match to */
+ for (start = uri; *uri && *uri != '?' && (*uri != '%' || uri[1] != '2' || tolower(uri[2]) != 'c'); ++uri);
+ if (uri > start)
+ {
+ gstring * to = string_catn(NULL, start, uri - start);
+ string_item * new;
+
+ if (!uri_decode(to))
+ {
+ filter->errmsg = US"Invalid URI encoding";
+ return -1;
+ }
+ new = store_get(sizeof(string_item), GET_UNTAINTED);
+ new->text = string_from_gstring(to);
+ new->next = *recipient;
+ *recipient = new;
+ }
+ else
+ {
+ filter->errmsg = US"Missing addr-spec in URI";
+ return -1;
+ }
+ if (*uri == '%') uri += 3;
+ else break;
+ }
+if (*uri == '?')
+ for (uri++; ;)
+ {
+ gstring * hname = string_get(0), * hvalue = NULL;
+
+ /* match hname */
+ for (start = uri; *uri && (isalnum(*uri) || strchr("$-_.+!*'(), %", *uri)); ++uri) ;
+ if (uri > start)
+ {
+ hname = string_catn(hname, start, uri-start);
+
+ if (!uri_decode(hname))
+ {
+ filter->errmsg = US"Invalid URI encoding";
+ return -1;
+ }
+ }
+ /* match = */
+ if (*uri++ != '=')
+ {
+ filter->errmsg = US"Missing equal after hname";
+ return -1;
+ }
+
+ /* match hvalue */
+ for (start = uri; *uri && (isalnum(*uri) || strchr("$-_.+!*'(), %", *uri)); ++uri) ;
+ if (uri > start)
+ {
+ hvalue = string_catn(NULL, start, uri-start); /*XXX this used to say "hname =" */
+
+ if (!uri_decode(hvalue))
+ {
+ filter->errmsg = US"Invalid URI encoding";
+ return -1;
+ }
+ }
+ if (hname->ptr == 2 && strcmpic(hname->s, US"to") == 0)
+ {
+ string_item * new = store_get(sizeof(string_item), GET_UNTAINTED);
+ new->text = string_from_gstring(hvalue);
+ new->next = *recipient;
+ *recipient = new;
+ }
+ else if (hname->ptr == 4 && strcmpic(hname->s, US"body") == 0)
+ *body = *hvalue;
+ else if (hname->ptr == 7 && strcmpic(hname->s, US"subject") == 0)
+ *subject = *hvalue;
+ else
+ {
+ static gstring ignore[] =
+ {
+ {.s = US"date", .ptr = 4, .size = 5},
+ {.s = US"from", .ptr = 4, .size = 5},
+ {.s = US"message-id", .ptr = 10, .size = 11},
+ {.s = US"received", .ptr = 8, .size = 9},
+ {.s = US"auto-submitted", .ptr = 14, .size = 15}
+ };
+ static gstring * end = ignore + nelem(ignore);
+ gstring * i;
+
+ for (i = ignore; i < end && !eq_asciicase(hname, i, FALSE); ++i);
+ if (i == end)
+ {
+ hname = string_fmt_append(NULL, "%Y%Y: %Y\n", header, hname, hvalue);
+ (void) string_from_gstring(hname);
+ /*XXX we seem to do nothing with this new hname? */
+ }
+ }
+ if (*uri == '&') ++uri;
+ else break;
+ }
+if (*uri)
+ {
+ filter->errmsg = US"Syntactically invalid URI";
+ return -1;
+ }
+return 1;
+}
+#endif
+
+
+/*************************************************
+* Octet-wise string comparison *
+*************************************************/
+
+/*
+Arguments:
+ needle UTF-8 string to search ...
+ haystack ... inside the haystack
+ match_prefix TRUE to compare if needle is a prefix of haystack
+
+Returns: 0 needle not found in haystack
+ 1 needle found
+*/
+
+static int
+eq_octet(const gstring *needle, const gstring *haystack, BOOL match_prefix)
+{
+size_t nl, hl;
+const uschar *n, *h;
+
+nl = needle->ptr;
+n = needle->s;
+hl = haystack->ptr;
+h = haystack->s;
+while (nl>0 && hl>0)
+ {
+#if !HAVE_ICONV
+ if (*n & 0x80) return 0;
+ if (*h & 0x80) return 0;
+#endif
+ if (*n != *h) return 0;
+ ++n;
+ ++h;
+ --nl;
+ --hl;
+ }
+return (match_prefix ? nl == 0 : nl == 0 && hl == 0);
+}
+
+
+/*************************************************
+* ASCII case-insensitive string comparison *
+*************************************************/
+
+/*
+Arguments:
+ needle UTF-8 string to search ...
+ haystack ... inside the haystack
+ match_prefix TRUE to compare if needle is a prefix of haystack
+
+Returns: 0 needle not found in haystack
+ 1 needle found
+*/
+
+static int
+eq_asciicase(const gstring *needle, const gstring *haystack, BOOL match_prefix)
+{
+size_t nl, hl;
+const uschar *n, *h;
+uschar nc, hc;
+
+nl = needle->ptr;
+n = needle->s;
+hl = haystack->ptr;
+h = haystack->s;
+while (nl > 0 && hl > 0)
+ {
+ nc = *n;
+ hc = *h;
+#if !HAVE_ICONV
+ if (nc & 0x80) return 0;
+ if (hc & 0x80) return 0;
+#endif
+ /* tolower depends on the locale and only ASCII case must be insensitive */
+ if ((nc >= 'A' && nc <= 'Z' ? nc | 0x20 : nc) != (hc >= 'A' && hc <= 'Z' ? hc | 0x20 : hc)) return 0;
+ ++n;
+ ++h;
+ --nl;
+ --hl;
+ }
+return (match_prefix ? nl == 0 : nl == 0 && hl == 0);
+}
+
+
+/*************************************************
+* Glob pattern search *
+*************************************************/
+
+/*
+Arguments:
+ needle pattern to search ...
+ haystack ... inside the haystack
+ ascii_caseless ignore ASCII case
+ match_octet match octets, not UTF-8 multi-octet characters
+
+Returns: 0 needle not found in haystack
+ 1 needle found
+ -1 pattern error
+*/
+
+static int
+eq_glob(const gstring *needle,
+ const gstring *haystack, BOOL ascii_caseless, BOOL match_octet)
+{
+const uschar *n, *h, *nend, *hend;
+int may_advance = 0;
+
+n = needle->s;
+h = haystack->s;
+nend = n+needle->ptr;
+hend = h+haystack->ptr;
+while (n < nend)
+ if (*n == '*')
+ {
+ ++n;
+ may_advance = 1;
+ }
+ else
+ {
+ const uschar *npart, *hpart;
+
+ /* 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 (hpart == hend) return 0;
+ if (match_octet)
+ ++hpart;
+ else
+ {
+ /* Match one UTF8 encoded character */
+ if ((*hpart&0xc0) == 0xc0)
+ {
+ ++hpart;
+ while (hpart<hend && ((*hpart&0xc0) == 0x80)) ++hpart;
+ }
+ else
+ ++hpart;
+ }
+ ++npart;
+ break;
+ }
+ case '\\':
+ {
+ ++npart;
+ if (npart == nend) return -1;
+ /* FALLTHROUGH */
+ }
+ default:
+ {
+ 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
+ )
+ {
+ if (may_advance)
+ /* string match after a star failed, advance and try again */
+ {
+ ++h;
+ goto match_part;
+ }
+ else return 0;
+ }
+ else
+ {
+ ++npart;
+ ++hpart;
+ };
+ }
+ }
+ /* 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 */
+ {
+ ++h;
+ goto match_part;
+ }
+ h = hpart;
+ n = npart;
+ may_advance = 0;
+ }
+return (h == hend ? 1 : may_advance);
+}
+
+
+/*************************************************
+* ASCII numeric comparison *
+*************************************************/
+
+/*
+Arguments:
+ a first numeric string
+ b second numeric string
+ relop relational operator
+
+Returns: 0 not (a relop b)
+ 1 a relop b
+*/
+
+static int
+eq_asciinumeric(const gstring *a, const gstring *b, enum RelOp relop)
+{
+size_t al, bl;
+const uschar *as, *aend, *bs, *bend;
+int cmp;
+
+as = a->s;
+aend = a->s+a->ptr;
+bs = b->s;
+bend = b->s+b->ptr;
+
+while (*as>= '0' && *as<= '9' && as<aend) ++as;
+al = as-a->s;
+while (*bs>= '0' && *bs<= '9' && bs<bend) ++bs;
+bl = bs-b->s;
+
+if (al && bl == 0) cmp = -1;
+else if (al == 0 && bl == 0) cmp = 0;
+else if (al == 0 && bl) cmp = 1;
+else
+ {
+ cmp = al-bl;
+ if (cmp == 0) cmp = memcmp(a->s, b->s, al);
+ }
+switch (relop)
+ {
+ case LT: return cmp < 0;
+ case LE: return cmp <= 0;
+ case EQ: return cmp == 0;
+ case GE: return cmp >= 0;
+ case GT: return cmp > 0;
+ case NE: return cmp != 0;
+ }
+ /*NOTREACHED*/
+ return -1;
+}
+
+
+/*************************************************
+* Compare strings *
+*************************************************/
+
+/*
+Arguments:
+ filter points to the Sieve filter including its state
+ needle UTF-8 pattern or string to search ...
+ haystack ... inside the haystack
+ co comparator to use
+ mt match type to use
+
+Returns: 0 needle not found in haystack
+ 1 needle found
+ -1 comparator does not offer matchtype
+*/
+
+static int
+compare(struct Sieve * filter, const gstring * needle, const gstring * haystack,
+ enum Comparator co, enum MatchType mt)
+{
+int r = 0;
+
+if ( (filter_test != FTEST_NONE && debug_selector != 0)
+ || (debug_selector & D_filter) != 0)
+ {
+ debug_printf_indent("String comparison (match ");
+ switch (mt)
+ {
+ case MATCH_IS: debug_printf_indent(":is"); break;
+ case MATCH_CONTAINS: debug_printf_indent(":contains"); break;
+ case MATCH_MATCHES: debug_printf_indent(":matches"); break;
+ }
+ debug_printf_indent(", comparison \"");
+ switch (co)
+ {
+ case COMP_OCTET: debug_printf_indent("i;octet"); break;
+ case COMP_EN_ASCII_CASEMAP: debug_printf_indent("en;ascii-casemap"); break;
+ case COMP_ASCII_NUMERIC: debug_printf_indent("i;ascii-numeric"); break;
+ }
+ debug_printf_indent("\"):\n");
+ debug_printf_indent(" Search = %s (%d chars)\n", needle->s, needle->ptr);
+ debug_printf_indent(" Inside = %s (%d chars)\n", haystack->s, haystack->ptr);
+ }
+switch (mt)
+ {
+ case MATCH_IS:
+ switch (co)
+ {
+ case COMP_OCTET:
+ if (eq_octet(needle, haystack, FALSE)) r = 1;
+ break;
+ case COMP_EN_ASCII_CASEMAP:
+ if (eq_asciicase(needle, haystack, FALSE)) r = 1;
+ break;
+ case COMP_ASCII_NUMERIC:
+ if (!filter->require_iascii_numeric)
+ {
+ filter->errmsg = CUS "missing previous require \"comparator-i;ascii-numeric\";";
+ return -1;
+ }
+ if (eq_asciinumeric(needle, haystack, EQ)) r = 1;
+ break;
+ }
+ break;
+
+ case MATCH_CONTAINS:
+ {
+ gstring h;
+
+ switch (co)
+ {
+ case COMP_OCTET:
+ for (h = *haystack; h.ptr; ++h.s, --h.ptr)
+ if (eq_octet(needle, &h, TRUE)) { r = 1; break; }
+ break;
+ case COMP_EN_ASCII_CASEMAP:
+ for (h = *haystack; h.ptr; ++h.s, --h.ptr)
+ if (eq_asciicase(needle, &h, TRUE)) { r = 1; break; }
+ break;
+ default:
+ filter->errmsg = CUS "comparator does not offer specified matchtype";
+ return -1;
+ }
+ break;
+ }
+
+ case MATCH_MATCHES:
+ switch (co)
+ {
+ case COMP_OCTET:
+ if ((r = eq_glob(needle, haystack, FALSE, TRUE)) == -1)
+ {
+ filter->errmsg = CUS "syntactically invalid pattern";
+ return -1;
+ }
+ break;
+ case COMP_EN_ASCII_CASEMAP:
+ if ((r = eq_glob(needle, haystack, TRUE, TRUE)) == -1)
+ {
+ filter->errmsg = CUS "syntactically invalid pattern";
+ return -1;
+ }
+ break;
+ default:
+ filter->errmsg = CUS "comparator does not offer specified matchtype";
+ return -1;
+ }
+ break;
+ }
+if ((filter_test != FTEST_NONE && debug_selector != 0) ||
+ (debug_selector & D_filter) != 0)
+ debug_printf_indent(" Result %s\n", r?"true":"false");
+return r;
+}
+
+
+/*************************************************
+* Check header field syntax *
+*************************************************/
+
+/*
+RFC 2822, section 3.6.8 says:
+
+ field-name = 1*ftext
+
+ ftext = %d33-57 / ; Any character except
+ %d59-126 ; controls, SP, and
+ ; ":".
+
+That forbids 8-bit header fields. This implementation accepts them, since
+all of Exim is 8-bit clean, so it adds %d128-%d255.
+
+Arguments:
+ header header field to quote for suitable use in Exim expansions
+
+Returns: 0 string is not a valid header field
+ 1 string is a value header field
+*/
+
+static int
+is_header(const gstring *header)
+{
+size_t l;
+const uschar *h;
+
+l = header->ptr;
+h = header->s;
+if (l == 0) return 0;
+while (l)
+ {
+ if (*h < 33 || *h == ':' || *h == 127)
+ return 0;
+ ++h;
+ --l;
+ }
+return 1;
+}
+
+
+/*************************************************
+* Quote special characters string *
+*************************************************/
+
+/*
+Arguments:
+ header header field to quote for suitable use in Exim expansions
+ or as debug output
+
+Returns: quoted string
+*/
+
+static const uschar *
+quote(const gstring * header)
+{
+gstring * quoted = NULL;
+size_t l;
+const uschar * h;
+
+for (l = header->ptr, h = header->s; l; ++h, --l)
+ switch (*h)
+ {
+ case '\0':
+ quoted = string_catn(quoted, CUS "\\0", 2);
+ break;
+ case '$':
+ case '{':
+ case '}':
+ quoted = string_catn(quoted, CUS "\\", 1);
+ default:
+ quoted = string_catn(quoted, h, 1);
+ }
+
+return string_from_gstring(quoted);
+}
+
+
+/*************************************************
+* Add address to list of generated addresses *
+*************************************************/
+
+/*
+According to RFC 5228, duplicate delivery to the same address must
+not happen, so the list is first searched for the address.
+
+Arguments:
+ generated list of generated addresses
+ addr new address to add
+ file address denotes a file
+
+Returns: nothing
+*/
+
+static void
+add_addr(address_item ** generated, const uschar * addr, int file, int maxage,
+ int maxmessages, int maxstorage)
+{
+address_item * new_addr;
+
+for (new_addr = *generated; new_addr; new_addr = new_addr->next)
+ if ( Ustrcmp(new_addr->address, addr) == 0
+ && ( !file
+ || testflag(new_addr, af_pfr)
+ || testflag(new_addr, af_file)
+ )
+ )
+ {
+ if ( filter_test != FTEST_NONE && debug_selector != 0
+ || (debug_selector & D_filter) != 0)
+ debug_printf_indent("Repeated %s `%s' ignored.\n",
+ file ? "fileinto" : "redirect", addr);
+
+ return;
+ }
+
+if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
+ debug_printf_indent("%s `%s'\n", file ? "fileinto" : "redirect", addr);
+
+new_addr = deliver_make_addr(addr, TRUE);
+if (file)
+ {
+ setflag(new_addr, af_pfr);
+ setflag(new_addr, af_file);
+ new_addr->mode = 0;
+ }
+new_addr->prop.errors_address = NULL;
+new_addr->next = *generated;
+*generated = new_addr;
+}
+
+
+/*************************************************
+* Return decoded header field *
+*************************************************/
+
+/*
+Unfold the header field as described in RFC 2822 and remove all
+leading and trailing white space, then perform MIME decoding and
+translate the header field to UTF-8.
+
+Arguments:
+ value returned value of the field
+ header name of the header field
+
+Returns: nothing The expanded string is empty
+ in case there is no such header
+*/
+
+static void
+expand_header(gstring * value, const gstring * header)
+{
+uschar *s, *r, *t;
+uschar *errmsg;
+
+value->ptr = 0;
+value->s = (uschar*)0;
+
+t = r = s = expand_string(string_sprintf("$rheader_%s", quote(header)));
+if (!t) return;
+while (*r == ' ' || *r == '\t') ++r;
+while (*r)
+ if (*r == '\n')
+ ++r;
+ else
+ *t++ = *r++;
+
+while (t>s && (*(t-1) == ' ' || *(t-1) == '\t')) --t;
+*t = '\0';
+value->s = rfc2047_decode(s, check_rfc2047_length, US"utf-8", '\0', &value->ptr, &errmsg);
+}
+
+
+/*************************************************
+* Parse remaining hash comment *
+*************************************************/
+
+/*
+Token definition:
+ Comment up to terminating CRLF
+
+Arguments:
+ filter points to the Sieve filter including its state
+
+Returns: 1 success
+ -1 syntax error
+*/
+
+static int
+parse_hashcomment(struct Sieve * filter)
+{
+++filter->pc;
+while (*filter->pc)
+ {
+#ifdef RFC_EOL
+ if (*filter->pc == '\r' && (filter->pc)[1] == '\n')
+#else
+ if (*filter->pc == '\n')
+#endif
+ {
+#ifdef RFC_EOL
+ filter->pc += 2;
+#else
+ ++filter->pc;
+#endif
+ ++filter->line;
+ return 1;
+ }
+ else ++filter->pc;
+ }
+filter->errmsg = CUS "missing end of comment";
+return -1;
+}
+
+
+/*************************************************
+* Parse remaining C-style comment *
+*************************************************/
+
+/*
+Token definition:
+ Everything up to star slash
+
+Arguments:
+ filter points to the Sieve filter including its state
+
+Returns: 1 success
+ -1 syntax error
+*/
+
+static int
+parse_comment(struct Sieve *filter)
+{
+filter->pc += 2;
+while (*filter->pc)
+ if (*filter->pc == '*' && (filter->pc)[1] == '/')
+ {
+ filter->pc += 2;
+ return 1;
+ }
+ else
+ ++filter->pc;
+
+filter->errmsg = CUS "missing end of comment";
+return -1;
+}
+
+
+/*************************************************
+* Parse optional white space *
+*************************************************/
+
+/*
+Token definition:
+ Spaces, tabs, CRLFs, hash comments or C-style comments
+
+Arguments:
+ filter points to the Sieve filter including its state
+
+Returns: 1 success
+ -1 syntax error
+*/
+
+static int
+parse_white(struct Sieve *filter)
+{
+while (*filter->pc)
+ {
+ if (*filter->pc == ' ' || *filter->pc == '\t') ++filter->pc;
+#ifdef RFC_EOL
+ else if (*filter->pc == '\r' && (filter->pc)[1] == '\n')
+#else
+ else if (*filter->pc == '\n')
+#endif
+ {
+#ifdef RFC_EOL
+ filter->pc += 2;
+#else
+ ++filter->pc;
+#endif
+ ++filter->line;
+ }
+ else if (*filter->pc == '#')
+ {
+ if (parse_hashcomment(filter) == -1) return -1;
+ }
+ else if (*filter->pc == '/' && (filter->pc)[1] == '*')
+ {
+ if (parse_comment(filter) == -1) return -1;
+ }
+ else break;
+ }
+return 1;
+}
+
+
+#ifdef ENCODED_CHARACTER
+/*************************************************
+* Decode hex-encoded-character string *
+*************************************************/
+
+/*
+Encoding definition:
+ blank = SP / TAB / CRLF
+ hex-pair-seq = *blank hex-pair *(1*blank hex-pair) *blank
+ hex-pair = 1*2HEXDIG
+
+Arguments:
+ src points to a hex-pair-seq
+ end points to its end
+ dst points to the destination of the decoded octets,
+ optionally to (uschar*)0 for checking only
+
+Returns: >= 0 number of decoded octets
+ -1 syntax error
+*/
+
+static int
+hex_decode(uschar *src, uschar *end, uschar *dst)
+{
+int decoded = 0;
+
+while (*src == ' ' || *src == '\t' || *src == '\n') ++src;
+do
+ {
+ int x, d, n;
+
+ for (x = 0, d = 0;
+ d<2 && src<end && isxdigit(n = tolower(*src));
+ x = (x<<4)|(n>= '0' && n<= '9' ? n-'0' : 10+(n-'a')) , ++d, ++src) ;
+ if (d == 0) return -1;
+ if (dst) *dst++ = x;
+ ++decoded;
+ if (src == end) return decoded;
+ if (*src == ' ' || *src == '\t' || *src == '\n')
+ while (*src == ' ' || *src == '\t' || *src == '\n') ++src;
+ else
+ return -1;
+ }
+while (src < end);
+return decoded;
+}
+
+
+/*************************************************
+* Decode unicode-encoded-character string *
+*************************************************/
+
+/*
+Encoding definition:
+ blank = SP / TAB / CRLF
+ unicode-hex-seq = *blank unicode-hex *(blank unicode-hex) *blank
+ unicode-hex = 1*HEXDIG
+
+ It is an error for a script to use a hexadecimal value that isn't in
+ either the range 0 to D7FF or the range E000 to 10FFFF.
+
+ At this time, strings are already scanned, thus the CRLF is converted
+ to the internally used \n (should RFC_EOL have been used).
+
+Arguments:
+ src points to a unicode-hex-seq
+ end points to its end
+ dst points to the destination of the decoded octets,
+ optionally to (uschar*)0 for checking only
+
+Returns: >= 0 number of decoded octets
+ -1 syntax error
+ -2 semantic error (character range violation)
+*/
+
+static int
+unicode_decode(uschar *src, uschar *end, uschar *dst)
+{
+int decoded = 0;
+
+while (*src == ' ' || *src == '\t' || *src == '\n') ++src;
+do
+ {
+ uschar *hex_seq;
+ int c, d, n;
+
+ unicode_hex:
+ for (hex_seq = src; src < end && *src == '0'; ) src++;
+ for (c = 0, d = 0;
+ d < 7 && src < end && isxdigit(n = tolower(*src));
+ c = (c<<4)|(n>= '0' && n<= '9' ? n-'0' : 10+(n-'a')), ++d, ++src) ;
+ if (src == hex_seq) return -1;
+ if (d == 7 || (!((c >= 0 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0x10ffff)))) return -2;
+ if (c<128)
+ {
+ if (dst) *dst++ = c;
+ ++decoded;
+ }
+ else if (c>= 0x80 && c<= 0x7ff)
+ {
+ if (dst)
+ {
+ *dst++ = 192+(c>>6);
+ *dst++ = 128+(c&0x3f);
+ }
+ decoded += 2;
+ }
+ else if (c>= 0x800 && c<= 0xffff)
+ {
+ if (dst)
+ {
+ *dst++ = 224+(c>>12);
+ *dst++ = 128+((c>>6)&0x3f);
+ *dst++ = 128+(c&0x3f);
+ }
+ decoded += 3;
+ }
+ else if (c>= 0x10000 && c<= 0x1fffff)
+ {
+ if (dst)
+ {
+ *dst++ = 240+(c>>18);
+ *dst++ = 128+((c>>10)&0x3f);
+ *dst++ = 128+((c>>6)&0x3f);
+ *dst++ = 128+(c&0x3f);
+ }
+ decoded += 4;
+ }
+ if (*src == ' ' || *src == '\t' || *src == '\n')
+ {
+ while (*src == ' ' || *src == '\t' || *src == '\n') ++src;
+ if (src == end) return decoded;
+ goto unicode_hex;
+ }
+ }
+while (src < end);
+return decoded;
+}
+
+
+/*************************************************
+* Decode encoded-character string *
+*************************************************/
+
+/*
+Encoding definition:
+ encoded-arb-octets = "${hex:" hex-pair-seq "}"
+ encoded-unicode-char = "${unicode:" unicode-hex-seq "}"
+
+Arguments:
+ encoded points to an encoded string, returns decoded string
+ filter points to the Sieve filter including its state
+
+Returns: 1 success
+ -1 syntax error
+*/
+
+static int
+string_decode(struct Sieve *filter, gstring *data)
+{
+uschar *src, *dst, *end;
+
+src = data->s;
+dst = src;
+end = data->s+data->ptr;
+while (src < end)
+ {
+ uschar *brace;
+
+ if (
+ strncmpic(src, US "${hex:", 6) == 0
+ && (brace = Ustrchr(src+6, '}')) != (uschar*)0
+ && (hex_decode(src+6, brace, (uschar*)0))>= 0
+ )
+ {
+ dst += hex_decode(src+6, brace, dst);
+ src = brace+1;
+ }
+ else if (
+ strncmpic(src, US "${unicode:", 10) == 0
+ && (brace = Ustrchr(src+10, '}')) != (uschar*)0
+ )
+ {
+ switch (unicode_decode(src+10, brace, (uschar*)0))
+ {
+ case -2:
+ {
+ filter->errmsg = CUS "unicode character out of range";
+ return -1;
+ }
+ case -1:
+ {
+ *dst++ = *src++;
+ break;
+ }
+ default:
+ {
+ dst += unicode_decode(src+10, brace, dst);
+ src = brace+1;
+ }
+ }
+ }
+ else *dst++ = *src++;
+ }
+ data->ptr = dst-data->s;
+ *dst = '\0';
+return 1;
+}
+#endif
+
+
+/*************************************************
+* Parse an optional string *
+*************************************************/
+
+/*
+Token definition:
+ quoted-string = DQUOTE *CHAR DQUOTE
+ ;; in general, \ CHAR inside a string maps to CHAR
+ ;; so \" maps to " and \\ maps to \
+ ;; note that newlines and other characters are all allowed
+ ;; in strings
+
+ multi-line = "text:" *(SP / HTAB) (hash-comment / CRLF)
+ *(multi-line-literal / multi-line-dotstuff)
+ "." CRLF
+ multi-line-literal = [CHAR-NOT-DOT *CHAR-NOT-CRLF] CRLF
+ multi-line-dotstuff = "." 1*CHAR-NOT-CRLF CRLF
+ ;; A line containing only "." ends the multi-line.
+ ;; Remove a leading '.' if followed by another '.'.
+ string = quoted-string / multi-line
+
+Arguments:
+ filter points to the Sieve filter including its state
+ id specifies identifier to match
+
+Returns: 1 success
+ -1 syntax error
+ 0 identifier not matched
+*/
+
+static int
+parse_string(struct Sieve *filter, gstring *data)
+{
+gstring * g = NULL;
+
+data->ptr = 0;
+data->s = NULL;
+
+if (*filter->pc == '"') /* quoted string */
+ {
+ ++filter->pc;
+ while (*filter->pc)
+ {
+ if (*filter->pc == '"') /* end of string */
+ {
+ ++filter->pc;
+
+ if (g)
+ data->ptr = len_string_from_gstring(g, &data->s);
+ else
+ data->s = US"\0";
+ /* that way, there will be at least one character allocated */
+
+#ifdef ENCODED_CHARACTER
+ if ( filter->require_encoded_character
+ && string_decode(filter, data) == -1)
+ return -1;
+#endif
+ return 1;
+ }
+ else if (*filter->pc == '\\' && (filter->pc)[1]) /* quoted character */
+ {
+ g = string_catn(g, filter->pc+1, 1);
+ filter->pc += 2;
+ }
+ else /* regular character */
+ {
+#ifdef RFC_EOL
+ if (*filter->pc == '\r' && (filter->pc)[1] == '\n') ++filter->line;
+#else
+ if (*filter->pc == '\n')
+ {
+ g = string_catn(g, US"\r", 1);
+ ++filter->line;
+ }
+#endif
+ g = string_catn(g, filter->pc, 1);
+ filter->pc++;
+ }
+ }
+ filter->errmsg = CUS "missing end of string";
+ return -1;
+ }
+else if (Ustrncmp(filter->pc, CUS "text:", 5) == 0) /* multiline string */
+ {
+ filter->pc += 5;
+ /* skip optional white space followed by hashed comment or CRLF */
+ while (*filter->pc == ' ' || *filter->pc == '\t') ++filter->pc;
+ if (*filter->pc == '#')
+ {
+ if (parse_hashcomment(filter) == -1) return -1;
+ }
+#ifdef RFC_EOL
+ else if (*filter->pc == '\r' && (filter->pc)[1] == '\n')
+#else
+ else if (*filter->pc == '\n')
+#endif
+ {
+#ifdef RFC_EOL
+ filter->pc += 2;
+#else
+ ++filter->pc;
+#endif
+ ++filter->line;
+ }
+ else
+ {
+ filter->errmsg = CUS "syntax error";
+ return -1;
+ }
+ while (*filter->pc)
+ {
+#ifdef RFC_EOL
+ if (*filter->pc == '\r' && (filter->pc)[1] == '\n') /* end of line */
+#else
+ if (*filter->pc == '\n') /* end of line */
+#endif
+ {
+ g = string_catn(g, CUS "\r\n", 2);
+#ifdef RFC_EOL
+ filter->pc += 2;
+#else
+ ++filter->pc;
+#endif
+ ++filter->line;
+#ifdef RFC_EOL
+ if (*filter->pc == '.' && (filter->pc)[1] == '\r' && (filter->pc)[2] == '\n') /* end of string */
+#else
+ if (*filter->pc == '.' && (filter->pc)[1] == '\n') /* end of string */
+#endif
+ {
+ if (g)
+ data->ptr = len_string_from_gstring(g, &data->s);
+ else
+ data->s = US"\0";
+ /* that way, there will be at least one character allocated */
+
+#ifdef RFC_EOL
+ filter->pc += 3;
+#else
+ filter->pc += 2;
+#endif
+ ++filter->line;
+#ifdef ENCODED_CHARACTER
+ if ( filter->require_encoded_character
+ && string_decode(filter, data) == -1)
+ return -1;
+#endif
+ return 1;
+ }
+ else if (*filter->pc == '.' && (filter->pc)[1] == '.') /* remove dot stuffing */
+ {
+ g = string_catn(g, CUS ".", 1);
+ filter->pc += 2;
+ }
+ }
+ else /* regular character */
+ {
+ g = string_catn(g, filter->pc, 1);
+ filter->pc++;
+ }
+ }
+ filter->errmsg = CUS "missing end of multi line string";
+ return -1;
+ }
+else return 0;
+}
+
+
+/*************************************************
+* Parse a specific identifier *
+*************************************************/
+
+/*
+Token definition:
+ identifier = (ALPHA / "_") *(ALPHA DIGIT "_")
+
+Arguments:
+ filter points to the Sieve filter including its state
+ id specifies identifier to match
+
+Returns: 1 success
+ 0 identifier not matched
+*/
+
+static int
+parse_identifier(struct Sieve *filter, const uschar *id)
+{
+size_t idlen = Ustrlen(id);
+
+if (strncmpic(US filter->pc, US id, idlen) == 0)
+ {
+ uschar next = filter->pc[idlen];
+
+ if ((next>= 'A' && next<= 'Z') || (next>= 'a' && next<= 'z') || next == '_' || (next>= '0' && next<= '9')) return 0;
+ filter->pc += idlen;
+ return 1;
+ }
+else return 0;
+}
+
+
+/*************************************************
+* Parse a number *
+*************************************************/
+
+/*
+Token definition:
+ number = 1*DIGIT [QUANTIFIER]
+ QUANTIFIER = "K" / "M" / "G"
+
+Arguments:
+ filter points to the Sieve filter including its state
+ data returns value
+
+Returns: 1 success
+ -1 no string list found
+*/
+
+static int
+parse_number(struct Sieve *filter, unsigned long *data)
+{
+unsigned long d, u;
+
+if (*filter->pc>= '0' && *filter->pc<= '9')
+ {
+ uschar *e;
+
+ errno = 0;
+ d = Ustrtoul(filter->pc, &e, 10);
+ if (errno == ERANGE)
+ {
+ filter->errmsg = CUstrerror(ERANGE);
+ return -1;
+ }
+ filter->pc = e;
+ u = 1;
+ if (*filter->pc == 'K') { u = 1024; ++filter->pc; }
+ else if (*filter->pc == 'M') { u = 1024*1024; ++filter->pc; }
+ else if (*filter->pc == 'G') { u = 1024*1024*1024; ++filter->pc; }
+ if (d>(ULONG_MAX/u))
+ {
+ filter->errmsg = CUstrerror(ERANGE);
+ return -1;
+ }
+ d *= u;
+ *data = d;
+ return 1;
+ }
+else
+ {
+ filter->errmsg = CUS "missing number";
+ return -1;
+ }
+}
+
+
+/*************************************************
+* Parse a string list *
+*************************************************/
+
+/*
+Grammar:
+ string-list = "[" string *(", " string) "]" / string
+
+Arguments:
+ filter points to the Sieve filter including its state
+ data returns string list
+
+Returns: 1 success
+ -1 no string list found
+*/
+
+static int
+parse_stringlist(struct Sieve *filter, gstring **data)
+{
+const uschar *orig = filter->pc;
+int dataCapacity = 0;
+int dataLength = 0;
+gstring *d = NULL;
+int m;
+
+if (*filter->pc == '[') /* string list */
+ {
+ ++filter->pc;
+ for (;;)
+ {
+ if (parse_white(filter) == -1) goto error;
+ if (dataLength+1 >= dataCapacity) /* increase buffer */
+ {
+ gstring *new;
+
+ dataCapacity = dataCapacity ? dataCapacity * 2 : 4;
+ new = store_get(sizeof(gstring) * dataCapacity, GET_UNTAINTED);
+
+ if (d) memcpy(new, d, sizeof(gstring)*dataLength);
+ d = new;
+ }
+
+ m = parse_string(filter, &d[dataLength]);
+ if (m == 0)
+ {
+ if (dataLength == 0) break;
+ else
+ {
+ filter->errmsg = CUS "missing string";
+ goto error;
+ }
+ }
+ else if (m == -1) goto error;
+ else ++dataLength;
+ if (parse_white(filter) == -1) goto error;
+ if (*filter->pc == ',') ++filter->pc;
+ else break;
+ }
+ if (*filter->pc == ']')
+ {
+ d[dataLength].s = (uschar*)0;
+ d[dataLength].ptr = -1;
+ ++filter->pc;
+ *data = d;
+ return 1;
+ }
+ else
+ {
+ filter->errmsg = CUS "missing closing bracket";
+ goto error;
+ }
+ }
+else /* single string */
+ {
+ if (!(d = store_get(sizeof(gstring)*2, GET_UNTAINTED)))
+ return -1;
+
+ m = parse_string(filter, &d[0]);
+ if (m == -1)
+ return -1;
+
+ else if (m == 0)
+ {
+ filter->pc = orig;
+ return 0;
+ }
+ else
+ {
+ d[1].s = (uschar*)0;
+ d[1].ptr = -1;
+ *data = d;
+ return 1;
+ }
+ }
+error:
+filter->errmsg = CUS "missing string list";
+return -1;
+}
+
+
+/*************************************************
+* Parse an optional address part specifier *
+*************************************************/
+
+/*
+Grammar:
+ address-part = ":localpart" / ":domain" / ":all"
+ address-part = / ":user" / ":detail"
+
+Arguments:
+ filter points to the Sieve filter including its state
+ a returns address part specified
+
+Returns: 1 success
+ 0 no comparator found
+ -1 syntax error
+*/
+
+static int
+parse_addresspart(struct Sieve *filter, enum AddressPart *a)
+{
+#ifdef SUBADDRESS
+if (parse_identifier(filter, CUS ":user") == 1)
+ {
+ if (!filter->require_subaddress)
+ {
+ filter->errmsg = CUS "missing previous require \"subaddress\";";
+ return -1;
+ }
+ *a = ADDRPART_USER;
+ return 1;
+ }
+else if (parse_identifier(filter, CUS ":detail") == 1)
+ {
+ if (!filter->require_subaddress)
+ {
+ filter->errmsg = CUS "missing previous require \"subaddress\";";
+ return -1;
+ }
+ *a = ADDRPART_DETAIL;
+ return 1;
+ }
+else
+#endif
+if (parse_identifier(filter, CUS ":localpart") == 1)
+ {
+ *a = ADDRPART_LOCALPART;
+ return 1;
+ }
+else if (parse_identifier(filter, CUS ":domain") == 1)
+ {
+ *a = ADDRPART_DOMAIN;
+ return 1;
+ }
+else if (parse_identifier(filter, CUS ":all") == 1)
+ {
+ *a = ADDRPART_ALL;
+ return 1;
+ }
+else return 0;
+}
+
+
+/*************************************************
+* Parse an optional comparator *
+*************************************************/
+
+/*
+Grammar:
+ comparator = ":comparator" <comparator-name: string>
+
+Arguments:
+ filter points to the Sieve filter including its state
+ c returns comparator
+
+Returns: 1 success
+ 0 no comparator found
+ -1 incomplete comparator found
+*/
+
+static int
+parse_comparator(struct Sieve *filter, enum Comparator *c)
+{
+gstring comparator_name;
+
+if (parse_identifier(filter, CUS ":comparator") == 0) return 0;
+if (parse_white(filter) == -1) return -1;
+switch (parse_string(filter, &comparator_name))
+ {
+ case -1: return -1;
+ case 0:
+ {
+ filter->errmsg = CUS "missing comparator";
+ return -1;
+ }
+ default:
+ {
+ int match;
+
+ if (eq_asciicase(&comparator_name, &str_ioctet, FALSE))
+ {
+ *c = COMP_OCTET;
+ match = 1;
+ }
+ else if (eq_asciicase(&comparator_name, &str_iascii_casemap, FALSE))
+ {
+ *c = COMP_EN_ASCII_CASEMAP;
+ match = 1;
+ }
+ else if (eq_asciicase(&comparator_name, &str_enascii_casemap, FALSE))
+ {
+ *c = COMP_EN_ASCII_CASEMAP;
+ match = 1;
+ }
+ else if (eq_asciicase(&comparator_name, &str_iascii_numeric, FALSE))
+ {
+ *c = COMP_ASCII_NUMERIC;
+ match = 1;
+ }
+ else
+ {
+ filter->errmsg = CUS "invalid comparator";
+ match = -1;
+ }
+ return match;
+ }
+ }
+}
+
+
+/*************************************************
+* Parse an optional match type *
+*************************************************/
+
+/*
+Grammar:
+ match-type = ":is" / ":contains" / ":matches"
+
+Arguments:
+ filter points to the Sieve filter including its state
+ m returns match type
+
+Returns: 1 success
+ 0 no match type found
+*/
+
+static int
+parse_matchtype(struct Sieve *filter, enum MatchType *m)
+{
+if (parse_identifier(filter, CUS ":is") == 1)
+ { *m = MATCH_IS; return 1; }
+else if (parse_identifier(filter, CUS ":contains") == 1)
+ { *m = MATCH_CONTAINS; return 1; }
+else if (parse_identifier(filter, CUS ":matches") == 1)
+ { *m = MATCH_MATCHES; return 1; }
+else return 0;
+}
+
+
+/*************************************************
+* Parse and interpret an optional test list *
+*************************************************/
+
+/*
+Grammar:
+ test-list = "(" test *("," test) ")"
+
+Arguments:
+ filter points to the Sieve filter including its state
+ n total number of tests
+ num_true number of passed tests
+ exec Execute parsed statements
+
+Returns: 1 success
+ 0 no test list found
+ -1 syntax or execution error
+*/
+
+static int
+parse_testlist(struct Sieve *filter, int *n, int *num_true, int exec)
+{
+if (parse_white(filter) == -1) return -1;
+if (*filter->pc == '(')
+ {
+ ++filter->pc;
+ *n = 0;
+ *num_true = 0;
+ for (;;)
+ {
+ int cond;
+
+ switch (parse_test(filter, &cond, exec))
+ {
+ case -1: return -1;
+ case 0: filter->errmsg = CUS "missing test"; return -1;
+ default: ++*n; if (cond) ++*num_true; break;
+ }
+ if (parse_white(filter) == -1) return -1;
+ if (*filter->pc == ',') ++filter->pc;
+ else break;
+ }
+ if (*filter->pc == ')')
+ {
+ ++filter->pc;
+ return 1;
+ }
+ else
+ {
+ filter->errmsg = CUS "missing closing paren";
+ return -1;
+ }
+ }
+else return 0;
+}
+
+
+/*************************************************
+* Parse and interpret an optional test *
+*************************************************/
+
+/*
+Arguments:
+ filter points to the Sieve filter including its state
+ cond returned condition status
+ exec Execute parsed statements
+
+Returns: 1 success
+ 0 no test found
+ -1 syntax or execution error
+*/
+
+static int
+parse_test(struct Sieve *filter, int *cond, int exec)
+{
+if (parse_white(filter) == -1) return -1;
+if (parse_identifier(filter, CUS "address"))
+ {
+ /*
+ address-test = "address" { [address-part] [comparator] [match-type] }
+ <header-list: string-list> <key-list: string-list>
+
+ header-list From, To, Cc, Bcc, Sender, Resent-From, Resent-To
+ */
+
+ enum AddressPart addressPart = ADDRPART_ALL;
+ enum Comparator comparator = COMP_EN_ASCII_CASEMAP;
+ enum MatchType matchType = MATCH_IS;
+ gstring *hdr, *key;
+ int m;
+ int ap = 0, co = 0, mt = 0;
+
+ for (;;)
+ {
+ if (parse_white(filter) == -1) return -1;
+ if ((m = parse_addresspart(filter, &addressPart)) != 0)
+ {
+ if (m == -1) return -1;
+ if (ap)
+ {
+ filter->errmsg = CUS "address part already specified";
+ return -1;
+ }
+ else ap = 1;
+ }
+ else if ((m = parse_comparator(filter, &comparator)) != 0)
+ {
+ if (m == -1) return -1;
+ if (co)
+ {
+ filter->errmsg = CUS "comparator already specified";
+ return -1;
+ }
+ else co = 1;
+ }
+ else if ((m = parse_matchtype(filter, &matchType)) != 0)
+ {
+ if (m == -1) return -1;
+ if (mt)
+ {
+ filter->errmsg = CUS "match type already specified";
+ return -1;
+ }
+ else mt = 1;
+ }
+ else break;
+ }
+ if (parse_white(filter) == -1)
+ return -1;
+ if ((m = parse_stringlist(filter, &hdr)) != 1)
+ {
+ if (m == 0) filter->errmsg = CUS "header string list expected";
+ return -1;
+ }
+ if (parse_white(filter) == -1)
+ return -1;
+ if ((m = parse_stringlist(filter, &key)) != 1)
+ {
+ if (m == 0) filter->errmsg = CUS "key string list expected";
+ return -1;
+ }
+ *cond = 0;
+ for (gstring * h = hdr; h->ptr != -1 && !*cond; ++h)
+ {
+ uschar * header_value = NULL, * extracted_addr, * end_addr;
+
+ if ( !eq_asciicase(h, &str_from, FALSE)
+ && !eq_asciicase(h, &str_to, FALSE)
+ && !eq_asciicase(h, &str_cc, FALSE)
+ && !eq_asciicase(h, &str_bcc, FALSE)
+ && !eq_asciicase(h, &str_sender, FALSE)
+ && !eq_asciicase(h, &str_resent_from, FALSE)
+ && !eq_asciicase(h, &str_resent_to, FALSE)
+ )
+ {
+ filter->errmsg = CUS "invalid header field";
+ return -1;
+ }
+ if (exec)
+ {
+ /* We are only interested in addresses below, so no MIME decoding */
+ if (!(header_value = expand_string(string_sprintf("$rheader_%s", quote(h)))))
+ {
+ filter->errmsg = CUS "header string expansion failed";
+ return -1;
+ }
+ f.parse_allow_group = TRUE;
+ while (*header_value && !*cond)
+ {
+ uschar *error;
+ int start, end, domain;
+ int saveend;
+ uschar *part = NULL;
+
+ end_addr = parse_find_address_end(header_value, FALSE);
+ saveend = *end_addr;
+ *end_addr = 0;
+ extracted_addr = parse_extract_address(header_value, &error, &start, &end, &domain, FALSE);
+
+ if (extracted_addr) switch (addressPart)
+ {
+ case ADDRPART_ALL: part = extracted_addr; break;
+#ifdef SUBADDRESS
+ case ADDRPART_USER:
+#endif
+ 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; break;
+#endif
+ }
+
+ *end_addr = saveend;
+ if (part && extracted_addr)
+ {
+ gstring partStr = {.s = part, .ptr = Ustrlen(part), .size = Ustrlen(part)+1};
+ for (gstring * k = key; k->ptr != - 1; ++k)
+ {
+ *cond = compare(filter, k, &partStr, comparator, matchType);
+ if (*cond == -1) return -1;
+ if (*cond) break;
+ }
+ }
+
+ if (saveend == 0) break;
+ header_value = end_addr + 1;
+ }
+ f.parse_allow_group = FALSE;
+ f.parse_found_group = FALSE;
+ }
+ }
+ return 1;
+ }
+else if (parse_identifier(filter, CUS "allof"))
+ {
+ /*
+ allof-test = "allof" <tests: test-list>
+ */
+
+ int n, num_true;
+
+ switch (parse_testlist(filter, &n, &num_true, exec))
+ {
+ case -1: return -1;
+ case 0: filter->errmsg = CUS "missing test list"; return -1;
+ default: *cond = (n == num_true); return 1;
+ }
+ }
+else if (parse_identifier(filter, CUS "anyof"))
+ {
+ /*
+ anyof-test = "anyof" <tests: test-list>
+ */
+
+ int n, num_true;
+
+ switch (parse_testlist(filter, &n, &num_true, exec))
+ {
+ case -1: return -1;
+ case 0: filter->errmsg = CUS "missing test list"; return -1;
+ default: *cond = (num_true>0); return 1;
+ }
+ }
+else if (parse_identifier(filter, CUS "exists"))
+ {
+ /*
+ exists-test = "exists" <header-names: string-list>
+ */
+
+ gstring *hdr;
+ int m;
+
+ if (parse_white(filter) == -1)
+ return -1;
+ if ((m = parse_stringlist(filter, &hdr)) != 1)
+ {
+ if (m == 0) filter->errmsg = CUS "header string list expected";
+ return -1;
+ }
+ if (exec)
+ {
+ *cond = 1;
+ for (gstring * h = hdr; h->ptr != -1 && *cond; ++h)
+ {
+ uschar *header_def;
+
+ header_def = expand_string(string_sprintf("${if def:header_%s {true}{false}}", quote(h)));
+ if (!header_def)
+ {
+ filter->errmsg = CUS "header string expansion failed";
+ return -1;
+ }
+ if (Ustrcmp(header_def,"false") == 0) *cond = 0;
+ }
+ }
+ return 1;
+ }
+else if (parse_identifier(filter, CUS "false"))
+ {
+ /*
+ false-test = "false"
+ */
+
+ *cond = 0;
+ return 1;
+ }
+else if (parse_identifier(filter, CUS "header"))
+ {
+ /*
+ header-test = "header" { [comparator] [match-type] }
+ <header-names: string-list> <key-list: string-list>
+ */
+
+ enum Comparator comparator = COMP_EN_ASCII_CASEMAP;
+ enum MatchType matchType = MATCH_IS;
+ gstring *hdr, *key;
+ int m;
+ int co = 0, mt = 0;
+
+ for (;;)
+ {
+ if (parse_white(filter) == -1)
+ return -1;
+ if ((m = parse_comparator(filter, &comparator)) != 0)
+ {
+ if (m == -1) return -1;
+ if (co)
+ {
+ filter->errmsg = CUS "comparator already specified";
+ return -1;
+ }
+ else co = 1;
+ }
+ else if ((m = parse_matchtype(filter, &matchType)) != 0)
+ {
+ if (m == -1) return -1;
+ if (mt)
+ {
+ filter->errmsg = CUS "match type already specified";
+ return -1;
+ }
+ else mt = 1;
+ }
+ else break;
+ }
+ if (parse_white(filter) == -1)
+ return -1;
+ if ((m = parse_stringlist(filter, &hdr)) != 1)
+ {
+ if (m == 0) filter->errmsg = CUS "header string list expected";
+ return -1;
+ }
+ if (parse_white(filter) == -1)
+ return -1;
+ if ((m = parse_stringlist(filter, &key)) != 1)
+ {
+ if (m == 0) filter->errmsg = CUS "key string list expected";
+ return -1;
+ }
+ *cond = 0;
+ for (gstring * h = hdr; h->ptr != -1 && !*cond; ++h)
+ {
+ if (!is_header(h))
+ {
+ filter->errmsg = CUS "invalid header field";
+ return -1;
+ }
+ if (exec)
+ {
+ gstring header_value;
+ uschar *header_def;
+
+ expand_header(&header_value, h);
+ header_def = expand_string(string_sprintf("${if def:header_%s {true}{false}}", quote(h)));
+ if (!header_value.s || !header_def)
+ {
+ filter->errmsg = CUS "header string expansion failed";
+ return -1;
+ }
+ for (gstring * k = key; k->ptr != -1; ++k)
+ if (Ustrcmp(header_def,"true") == 0)
+ {
+ *cond = compare(filter, k, &header_value, comparator, matchType);
+ if (*cond == -1) return -1;
+ if (*cond) break;
+ }
+ }
+ }
+ return 1;
+ }
+else if (parse_identifier(filter, CUS "not"))
+ {
+ if (parse_white(filter) == -1) return -1;
+ switch (parse_test(filter, cond, exec))
+ {
+ case -1: return -1;
+ case 0: filter->errmsg = CUS "missing test"; return -1;
+ default: *cond = !*cond; return 1;
+ }
+ }
+else if (parse_identifier(filter, CUS "size"))
+ {
+ /*
+ relop = ":over" / ":under"
+ size-test = "size" relop <limit: number>
+ */
+
+ unsigned long limit;
+ int overNotUnder;
+
+ if (parse_white(filter) == -1) return -1;
+ if (parse_identifier(filter, CUS ":over")) overNotUnder = 1;
+ else if (parse_identifier(filter, CUS ":under")) overNotUnder = 0;
+ else
+ {
+ filter->errmsg = CUS "missing :over or :under";
+ return -1;
+ }
+ if (parse_white(filter) == -1) return -1;
+ if (parse_number(filter, &limit) == -1) return -1;
+ *cond = (overNotUnder ? (message_size>limit) : (message_size<limit));
+ return 1;
+ }
+else if (parse_identifier(filter, CUS "true"))
+ {
+ *cond = 1;
+ return 1;
+ }
+else if (parse_identifier(filter, CUS "envelope"))
+ {
+ /*
+ envelope-test = "envelope" { [comparator] [address-part] [match-type] }
+ <envelope-part: string-list> <key-list: string-list>
+
+ envelope-part is case insensitive "from" or "to"
+#ifdef ENVELOPE_AUTH
+ envelope-part = / "auth"
+#endif
+ */
+
+ enum Comparator comparator = COMP_EN_ASCII_CASEMAP;
+ enum AddressPart addressPart = ADDRPART_ALL;
+ enum MatchType matchType = MATCH_IS;
+ gstring *env, *key;
+ int m;
+ int co = 0, ap = 0, mt = 0;
+
+ if (!filter->require_envelope)
+ {
+ filter->errmsg = CUS "missing previous require \"envelope\";";
+ return -1;
+ }
+ for (;;)
+ {
+ if (parse_white(filter) == -1) return -1;
+ if ((m = parse_comparator(filter, &comparator)) != 0)
+ {
+ if (m == -1) return -1;
+ if (co)
+ {
+ filter->errmsg = CUS "comparator already specified";
+ return -1;
+ }
+ else co = 1;
+ }
+ else if ((m = parse_addresspart(filter, &addressPart)) != 0)
+ {
+ if (m == -1) return -1;
+ if (ap)
+ {
+ filter->errmsg = CUS "address part already specified";
+ return -1;
+ }
+ else ap = 1;
+ }
+ else if ((m = parse_matchtype(filter, &matchType)) != 0)
+ {
+ if (m == -1) return -1;
+ if (mt)
+ {
+ filter->errmsg = CUS "match type already specified";
+ return -1;
+ }
+ else mt = 1;
+ }
+ else break;
+ }
+ if (parse_white(filter) == -1)
+ return -1;
+ if ((m = parse_stringlist(filter, &env)) != 1)
+ {
+ if (m == 0) filter->errmsg = CUS "envelope string list expected";
+ return -1;
+ }
+ if (parse_white(filter) == -1)
+ return -1;
+ if ((m = parse_stringlist(filter, &key)) != 1)
+ {
+ if (m == 0) filter->errmsg = CUS "key string list expected";
+ return -1;
+ }
+ *cond = 0;
+ for (gstring * e = env; e->ptr != -1 && !*cond; ++e)
+ {
+ const uschar *envelopeExpr = CUS 0;
+ uschar *envelope = US 0;
+
+ if (eq_asciicase(e, &str_from, FALSE))
+ {
+ switch (addressPart)
+ {
+ case ADDRPART_ALL: envelopeExpr = CUS "$sender_address"; break;
+#ifdef SUBADDRESS
+ case ADDRPART_USER:
+#endif
+ 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;
+#endif
+ }
+ }
+ else if (eq_asciicase(e, &str_to, FALSE))
+ {
+ switch (addressPart)
+ {
+ case ADDRPART_ALL: envelopeExpr = CUS "$local_part_prefix$local_part$local_part_suffix@$domain"; break;
+#ifdef SUBADDRESS
+ 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;
+ }
+ }
+#ifdef ENVELOPE_AUTH
+ else if (eq_asciicase(e, &str_auth, FALSE))
+ {
+ switch (addressPart)
+ {
+ case ADDRPART_ALL: envelopeExpr = CUS "$authenticated_sender"; break;
+#ifdef SUBADDRESS
+ case ADDRPART_USER:
+#endif
+ case ADDRPART_LOCALPART: envelopeExpr = CUS "${local_part:$authenticated_sender}"; break;
+ case ADDRPART_DOMAIN: envelopeExpr = CUS "${domain:$authenticated_sender}"; break;
+#ifdef SUBADDRESS
+ case ADDRPART_DETAIL: envelopeExpr = CUS 0; break;
+#endif
+ }
+ }
+#endif
+ else
+ {
+ filter->errmsg = CUS "invalid envelope string";
+ return -1;
+ }
+ if (exec && envelopeExpr)
+ {
+ if (!(envelope = expand_string(US envelopeExpr)))
+ {
+ filter->errmsg = CUS "header string expansion failed";
+ return -1;
+ }
+ for (gstring * k = key; k->ptr != -1; ++k)
+ {
+ gstring envelopeStr = {.s = envelope, .ptr = Ustrlen(envelope), .size = Ustrlen(envelope)+1};
+
+ *cond = compare(filter, k, &envelopeStr, comparator, matchType);
+ if (*cond == -1) return -1;
+ if (*cond) break;
+ }
+ }
+ }
+ return 1;
+ }
+#ifdef ENOTIFY
+else if (parse_identifier(filter, CUS "valid_notify_method"))
+ {
+ /*
+ valid_notify_method = "valid_notify_method"
+ <notification-uris: string-list>
+ */
+
+ gstring *uris;
+ int m;
+
+ if (!filter->require_enotify)
+ {
+ filter->errmsg = CUS "missing previous require \"enotify\";";
+ return -1;
+ }
+ if (parse_white(filter) == -1)
+ return -1;
+ if ((m = parse_stringlist(filter, &uris)) != 1)
+ {
+ if (m == 0) filter->errmsg = CUS "URI string list expected";
+ return -1;
+ }
+ if (exec)
+ {
+ *cond = 1;
+ for (gstring * u = uris; u->ptr != -1 && *cond; ++u)
+ {
+ string_item * recipient = NULL;
+ gstring header = { .s = NULL, .ptr = -1 };
+ gstring subject = { .s = NULL, .ptr = -1 };
+ gstring body = { .s = NULL, .ptr = -1 };
+
+ if (parse_mailto_uri(filter, u->s, &recipient, &header, &subject, &body) != 1)
+ *cond = 0;
+ }
+ }
+ return 1;
+ }
+else if (parse_identifier(filter, CUS "notify_method_capability"))
+ {
+ /*
+ notify_method_capability = "notify_method_capability" [COMPARATOR] [MATCH-TYPE]
+ <notification-uri: string>
+ <notification-capability: string>
+ <key-list: string-list>
+ */
+
+ int m;
+ int co = 0, mt = 0;
+
+ enum Comparator comparator = COMP_EN_ASCII_CASEMAP;
+ enum MatchType matchType = MATCH_IS;
+ gstring uri, capa, *keys;
+
+ if (!filter->require_enotify)
+ {
+ filter->errmsg = CUS "missing previous require \"enotify\";";
+ return -1;
+ }
+ for (;;)
+ {
+ if (parse_white(filter) == -1) return -1;
+ if ((m = parse_comparator(filter, &comparator)) != 0)
+ {
+ if (m == -1) return -1;
+ if (co)
+ {
+ filter->errmsg = CUS "comparator already specified";
+ return -1;
+ }
+ else co = 1;
+ }
+ else if ((m = parse_matchtype(filter, &matchType)) != 0)
+ {
+ if (m == -1) return -1;
+ if (mt)
+ {
+ filter->errmsg = CUS "match type already specified";
+ return -1;
+ }
+ else mt = 1;
+ }
+ else break;
+ }
+ if ((m = parse_string(filter, &uri)) != 1)
+ {
+ if (m == 0) filter->errmsg = CUS "missing notification URI string";
+ return -1;
+ }
+ if (parse_white(filter) == -1)
+ return -1;
+ if ((m = parse_string(filter, &capa)) != 1)
+ {
+ if (m == 0) filter->errmsg = CUS "missing notification capability string";
+ return -1;
+ }
+ if (parse_white(filter) == -1)
+ return -1;
+ if ((m = parse_stringlist(filter, &keys)) != 1)
+ {
+ if (m == 0) filter->errmsg = CUS "missing key string list";
+ return -1;
+ }
+ if (exec)
+ {
+ string_item * recipient = NULL;
+ gstring header = { .s = NULL, .ptr = -1 };
+ gstring subject = { .s = NULL, .ptr = -1 };
+ gstring body = { .s = NULL, .ptr = -1 };
+
+ *cond = 0;
+ if (parse_mailto_uri(filter, uri.s, &recipient, &header, &subject, &body) == 1)
+ if (eq_asciicase(&capa, &str_online, FALSE) == 1)
+ for (gstring * k = keys; k->ptr != -1; ++k)
+ {
+ *cond = compare(filter, k, &str_maybe, comparator, matchType);
+ if (*cond == -1) return -1;
+ if (*cond) break;
+ }
+ }
+ return 1;
+ }
+#endif
+else return 0;
+}
+
+
+/*************************************************
+* Parse and interpret an optional block *
+*************************************************/
+
+/*
+Arguments:
+ filter points to the Sieve filter including its state
+ exec Execute parsed statements
+ generated where to hang newly-generated addresses
+
+Returns: 2 success by stop
+ 1 other success
+ 0 no block command found
+ -1 syntax or execution error
+*/
+
+static int
+parse_block(struct Sieve * filter, int exec, address_item ** generated)
+{
+int r;
+
+if (parse_white(filter) == -1)
+ return -1;
+if (*filter->pc == '{')
+ {
+ ++filter->pc;
+ if ((r = parse_commands(filter, exec, generated)) == -1 || r == 2) return r;
+ if (*filter->pc == '}')
+ {
+ ++filter->pc;
+ return 1;
+ }
+ filter->errmsg = CUS "expecting command or closing brace";
+ return -1;
+ }
+return 0;
+}
+
+
+/*************************************************
+* Match a semicolon *
+*************************************************/
+
+/*
+Arguments:
+ filter points to the Sieve filter including its state
+
+Returns: 1 success
+ -1 syntax error
+*/
+
+static int
+parse_semicolon(struct Sieve *filter)
+{
+if (parse_white(filter) == -1)
+ return -1;
+if (*filter->pc == ';')
+ {
+ ++filter->pc;
+ return 1;
+ }
+filter->errmsg = CUS "missing semicolon";
+return -1;
+}
+
+
+/*************************************************
+* Parse and interpret a Sieve command *
+*************************************************/
+
+/*
+Arguments:
+ filter points to the Sieve filter including its state
+ exec Execute parsed statements
+ generated where to hang newly-generated addresses
+
+Returns: 2 success by stop
+ 1 other success
+ -1 syntax or execution error
+*/
+static int
+parse_commands(struct Sieve *filter, int exec, address_item **generated)
+{
+while (*filter->pc)
+ {
+ if (parse_white(filter) == -1)
+ return -1;
+ if (parse_identifier(filter, CUS "if"))
+ {
+ /*
+ if-command = "if" test block *( "elsif" test block ) [ else block ]
+ */
+
+ int cond, m, unsuccessful;
+
+ /* test block */
+ if (parse_white(filter) == -1)
+ return -1;
+ if ((m = parse_test(filter, &cond, exec)) == -1)
+ return -1;
+ if (m == 0)
+ {
+ filter->errmsg = CUS "missing test";
+ return -1;
+ }
+ if ((filter_test != FTEST_NONE && debug_selector != 0) ||
+ (debug_selector & D_filter) != 0)
+ {
+ if (exec) debug_printf_indent("if %s\n", cond?"true":"false");
+ }
+ m = parse_block(filter, exec ? cond : 0, generated);
+ if (m == -1 || m == 2)
+ return m;
+ if (m == 0)
+ {
+ filter->errmsg = CUS "missing block";
+ return -1;
+ }
+ unsuccessful = !cond;
+ for (;;) /* elsif test block */
+ {
+ if (parse_white(filter) == -1)
+ return -1;
+ if (parse_identifier(filter, CUS "elsif"))
+ {
+ if (parse_white(filter) == -1)
+ return -1;
+ m = parse_test(filter, &cond, exec && unsuccessful);
+ if (m == -1 || m == 2)
+ return m;
+ if (m == 0)
+ {
+ filter->errmsg = CUS "missing test";
+ return -1;
+ }
+ if ((filter_test != FTEST_NONE && debug_selector != 0) ||
+ (debug_selector & D_filter) != 0)
+ {
+ if (exec) debug_printf_indent("elsif %s\n", cond?"true":"false");
+ }
+ m = parse_block(filter, exec && unsuccessful ? cond : 0, generated);
+ if (m == -1 || m == 2)
+ return m;
+ if (m == 0)
+ {
+ filter->errmsg = CUS "missing block";
+ return -1;
+ }
+ if (exec && unsuccessful && cond)
+ unsuccessful = 0;
+ }
+ else break;
+ }
+ /* else block */
+ if (parse_white(filter) == -1)
+ return -1;
+ if (parse_identifier(filter, CUS "else"))
+ {
+ m = parse_block(filter, exec && unsuccessful, generated);
+ if (m == -1 || m == 2)
+ return m;
+ if (m == 0)
+ {
+ filter->errmsg = CUS "missing block";
+ return -1;
+ }
+ }
+ }
+ else if (parse_identifier(filter, CUS "stop"))
+ {
+ /*
+ stop-command = "stop" { stop-options } ";"
+ stop-options =
+ */
+
+ if (parse_semicolon(filter) == -1)
+ return -1;
+ if (exec)
+ {
+ filter->pc += Ustrlen(filter->pc);
+ return 2;
+ }
+ }
+ else if (parse_identifier(filter, CUS "keep"))
+ {
+ /*
+ keep-command = "keep" { keep-options } ";"
+ keep-options =
+ */
+
+ if (parse_semicolon(filter) == -1)
+ return -1;
+ if (exec)
+ {
+ add_addr(generated, filter->inbox, 1, 0, 0, 0);
+ filter->keep = 0;
+ }
+ }
+ else if (parse_identifier(filter, CUS "discard"))
+ {
+ /*
+ discard-command = "discard" { discard-options } ";"
+ discard-options =
+ */
+
+ if (parse_semicolon(filter) == -1)
+ return -1;
+ if (exec) filter->keep = 0;
+ }
+ else if (parse_identifier(filter, CUS "redirect"))
+ {
+ /*
+ redirect-command = "redirect" redirect-options "string" ";"
+ redirect-options =
+ redirect-options = ) ":copy"
+ */
+
+ gstring recipient;
+ int m;
+ int copy = 0;
+
+ for (;;)
+ {
+ if (parse_white(filter) == -1)
+ return -1;
+ if (parse_identifier(filter, CUS ":copy") == 1)
+ {
+ if (!filter->require_copy)
+ {
+ filter->errmsg = CUS "missing previous require \"copy\";";
+ return -1;
+ }
+ copy = 1;
+ }
+ else break;
+ }
+ if (parse_white(filter) == -1)
+ return -1;
+ if ((m = parse_string(filter, &recipient)) != 1)
+ {
+ if (m == 0)
+ filter->errmsg = CUS "missing redirect recipient string";
+ return -1;
+ }
+ if (strchr(CCS recipient.s, '@') == NULL)
+ {
+ filter->errmsg = CUS "unqualified recipient address";
+ return -1;
+ }
+ if (exec)
+ {
+ add_addr(generated, recipient.s, 0, 0, 0, 0);
+ if (!copy) filter->keep = 0;
+ }
+ if (parse_semicolon(filter) == -1) return -1;
+ }
+ else if (parse_identifier(filter, CUS "fileinto"))
+ {
+ /*
+ fileinto-command = "fileinto" { fileinto-options } string ";"
+ fileinto-options =
+ fileinto-options = ) [ ":copy" ]
+ */
+
+ gstring folder;
+ uschar *s;
+ int m;
+ unsigned long maxage, maxmessages, maxstorage;
+ int copy = 0;
+
+ maxage = maxmessages = maxstorage = 0;
+ if (!filter->require_fileinto)
+ {
+ filter->errmsg = CUS "missing previous require \"fileinto\";";
+ return -1;
+ }
+ for (;;)
+ {
+ if (parse_white(filter) == -1)
+ return -1;
+ if (parse_identifier(filter, CUS ":copy") == 1)
+ {
+ if (!filter->require_copy)
+ {
+ filter->errmsg = CUS "missing previous require \"copy\";";
+ return -1;
+ }
+ copy = 1;
+ }
+ else break;
+ }
+ if (parse_white(filter) == -1)
+ return -1;
+ if ((m = parse_string(filter, &folder)) != 1)
+ {
+ if (m == 0) filter->errmsg = CUS "missing fileinto folder string";
+ return -1;
+ }
+ m = 0; s = folder.s;
+ if (folder.ptr == 0)
+ m = 1;
+ if (Ustrcmp(s,"..") == 0 || Ustrncmp(s,"../", 3) == 0)
+ m = 1;
+ else while (*s)
+ {
+ if (Ustrcmp(s,"/..") == 0 || Ustrncmp(s,"/../", 4) == 0) { m = 1; break; }
+ ++s;
+ }
+ if (m)
+ {
+ filter->errmsg = CUS "invalid folder";
+ return -1;
+ }
+ if (exec)
+ {
+ add_addr(generated, folder.s, 1, maxage, maxmessages, maxstorage);
+ if (!copy) filter->keep = 0;
+ }
+ if (parse_semicolon(filter) == -1)
+ return -1;
+ }
+#ifdef ENOTIFY
+ else if (parse_identifier(filter, CUS "notify"))
+ {
+ /*
+ notify-command = "notify" { notify-options } <method: string> ";"
+ notify-options = [":from" string]
+ [":importance" <"1" / "2" / "3">]
+ [":options" 1*(string-list / number)]
+ [":message" string]
+ */
+
+ int m;
+ gstring from = { .s = NULL, .ptr = -1 };
+ gstring importance = { .s = NULL, .ptr = -1 };
+ gstring message = { .s = NULL, .ptr = -1 };
+ gstring method;
+ struct Notification *already;
+ string_item * recipient = NULL;
+ gstring header = { .s = NULL, .ptr = -1 };
+ gstring subject = { .s = NULL, .ptr = -1 };
+ gstring body = { .s = NULL, .ptr = -1 };
+ uschar *envelope_from;
+ gstring auto_submitted_value;
+ uschar *auto_submitted_def;
+
+ if (!filter->require_enotify)
+ {
+ filter->errmsg = CUS "missing previous require \"enotify\";";
+ return -1;
+ }
+ envelope_from = sender_address && sender_address[0]
+ ? expand_string(US"$local_part_prefix$local_part$local_part_suffix@$domain") : US "";
+ if (!envelope_from)
+ {
+ filter->errmsg = CUS "expansion failure for envelope from";
+ return -1;
+ }
+ for (;;)
+ {
+ if (parse_white(filter) == -1)
+ return -1;
+ if (parse_identifier(filter, CUS ":from") == 1)
+ {
+ 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;
+ }
+ }
+ else if (parse_identifier(filter, CUS ":importance") == 1)
+ {
+ if (parse_white(filter) == -1)
+ return -1;
+ if ((m = parse_string(filter, &importance)) != 1)
+ {
+ if (m == 0)
+ filter->errmsg = CUS "importance string expected";
+ return -1;
+ }
+ if (importance.ptr != 1 || importance.s[0] < '1' || importance.s[0] > '3')
+ {
+ filter->errmsg = CUS "invalid importance";
+ return -1;
+ }
+ }
+ else if (parse_identifier(filter, CUS ":options") == 1)
+ {
+ if (parse_white(filter) == -1)
+ return -1;
+ }
+ else if (parse_identifier(filter, CUS ":message") == 1)
+ {
+ if (parse_white(filter) == -1)
+ return -1;
+ if ((m = parse_string(filter, &message)) != 1)
+ {
+ if (m == 0)
+ filter->errmsg = CUS "message string expected";
+ return -1;
+ }
+ }
+ else break;
+ }
+ if (parse_white(filter) == -1)
+ return -1;
+ if ((m = parse_string(filter, &method)) != 1)
+ {
+ if (m == 0)
+ filter->errmsg = CUS "missing method string";
+ return -1;
+ }
+ if (parse_semicolon(filter) == -1)
+ return -1;
+ if (parse_mailto_uri(filter, method.s, &recipient, &header, &subject, &body) != 1)
+ return -1;
+ if (exec)
+ {
+ if (message.ptr == -1)
+ message = subject;
+ if (message.ptr == -1)
+ expand_header(&message, &str_subject);
+ expand_header(&auto_submitted_value, &str_auto_submitted);
+ auto_submitted_def = expand_string(US"${if def:header_auto-submitted {true}{false}}");
+ if (!auto_submitted_value.s || !auto_submitted_def)
+ {
+ filter->errmsg = CUS "header string expansion failed";
+ return -1;
+ }
+ if (Ustrcmp(auto_submitted_def,"true") != 0 || Ustrcmp(auto_submitted_value.s,"no") == 0)
+ {
+ for (already = filter->notified; already; already = already->next)
+ {
+ if ( already->method.ptr == method.ptr
+ && (method.ptr == -1 || Ustrcmp(already->method.s, method.s) == 0)
+ && already->importance.ptr == importance.ptr
+ && (importance.ptr == -1 || Ustrcmp(already->importance.s, importance.s) == 0)
+ && already->message.ptr == message.ptr
+ && (message.ptr == -1 || Ustrcmp(already->message.s, message.s) == 0))
+ break;
+ }
+ if (!already)
+ /* New notification, process it */
+ {
+ struct Notification * sent = store_get(sizeof(struct Notification), GET_UNTAINTED);
+ sent->method = method;
+ sent->importance = importance;
+ sent->message = message;
+ sent->next = filter->notified;
+ filter->notified = sent;
+ #ifndef COMPILE_SYNTAX_CHECKER
+ if (filter_test == FTEST_NONE)
+ {
+ int pid, fd;
+
+ if ((pid = child_open_exim2(&fd, envelope_from, envelope_from,
+ US"sieve-notify")) >= 1)
+ {
+ FILE * f = fdopen(fd, "wb");
+
+ fprintf(f,"From: %s\n", from.ptr == -1
+ ? expand_string(US"$local_part_prefix$local_part$local_part_suffix@$domain")
+ : from.s);
+ for (string_item * p = recipient; p; p = p->next)
+ fprintf(f, "To: %s\n", p->text);
+ fprintf(f, "Auto-Submitted: auto-notified; %s\n", filter->enotify_mailto_owner);
+ if (header.ptr > 0) fprintf(f, "%s", header.s);
+ if (message.ptr == -1)
+ {
+ message.s = US"Notification";
+ message.ptr = Ustrlen(message.s);
+ }
+ if (message.ptr != -1)
+ fprintf(f, "Subject: %s\n", parse_quote_2047(message.s,
+ message.ptr, US"utf-8", TRUE));
+ fprintf(f,"\n");
+ if (body.ptr > 0) fprintf(f, "%s\n", body.s);
+ fflush(f);
+ (void)fclose(f);
+ (void)child_close(pid, 0);
+ }
+ }
+ if ((filter_test != FTEST_NONE && debug_selector != 0) || debug_selector & D_filter)
+ debug_printf_indent("Notification to `%s': '%s'.\n", method.s, message.ptr != -1 ? message.s : CUS "");
+#endif
+ }
+ else
+ if ((filter_test != FTEST_NONE && debug_selector != 0) || debug_selector & D_filter)
+ debug_printf_indent("Repeated notification to `%s' ignored.\n", method.s);
+ }
+ else
+ if ((filter_test != FTEST_NONE && debug_selector != 0) || debug_selector & D_filter)
+ debug_printf_indent("Ignoring notification, triggering message contains Auto-submitted: field.\n");
+ }
+ }
+#endif
+#ifdef VACATION
+ else if (parse_identifier(filter, CUS "vacation"))
+ {
+ /*
+ vacation-command = "vacation" { vacation-options } <reason: string> ";"
+ vacation-options = [":days" number]
+ [":subject" string]
+ [":from" string]
+ [":addresses" string-list]
+ [":mime"]
+ [":handle" string]
+ */
+
+ int m;
+ unsigned long days;
+ gstring subject;
+ gstring from;
+ gstring *addresses;
+ int reason_is_mime;
+ string_item *aliases;
+ gstring handle;
+ gstring reason;
+
+ if (!filter->require_vacation)
+ {
+ filter->errmsg = CUS "missing previous require \"vacation\";";
+ return -1;
+ }
+ if (exec)
+ {
+ if (filter->vacation_ran)
+ {
+ filter->errmsg = CUS "trying to execute vacation more than once";
+ return -1;
+ }
+ filter->vacation_ran = TRUE;
+ }
+ days = VACATION_MIN_DAYS>7 ? VACATION_MIN_DAYS : 7;
+ subject.s = (uschar*)0;
+ subject.ptr = -1;
+ from.s = (uschar*)0;
+ from.ptr = -1;
+ addresses = (gstring*)0;
+ aliases = NULL;
+ reason_is_mime = 0;
+ handle.s = (uschar*)0;
+ handle.ptr = -1;
+ for (;;)
+ {
+ if (parse_white(filter) == -1)
+ return -1;
+ if (parse_identifier(filter, CUS ":days") == 1)
+ {
+ if (parse_white(filter) == -1)
+ return -1;
+ if (parse_number(filter, &days) == -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)
+ {
+ 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 (check_mail_address(filter, &from) != 1)
+ return -1;
+ }
+ else if (parse_identifier(filter, CUS ":addresses") == 1)
+ {
+ if (parse_white(filter) == -1)
+ return -1;
+ if ((m = parse_stringlist(filter, &addresses)) != 1)
+ {
+ if (m == 0)
+ filter->errmsg = CUS "addresses string list expected";
+ return -1;
+ }
+ for (gstring * a = addresses; a->ptr != -1; ++a)
+ {
+ string_item * new = store_get(sizeof(string_item), GET_UNTAINTED);
+
+ new->text = store_get(a->ptr+1, a->s);
+ if (a->ptr) memcpy(new->text, a->s, a->ptr);
+ new->text[a->ptr] = '\0';
+ new->next = aliases;
+ aliases = new;
+ }
+ }
+ 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, &from)) != 1)
+ {
+ if (m == 0)
+ filter->errmsg = CUS "handle string expected";
+ return -1;
+ }
+ }
+ else break;
+ }
+ if (parse_white(filter) == -1)
+ return -1;
+ if ((m = parse_string(filter, &reason)) != 1)
+ {
+ if (m == 0)
+ filter->errmsg = CUS "missing reason string";
+ return -1;
+ }
+ if (reason_is_mime)
+ {
+ uschar *s, *end;
+
+ for (s = reason.s, end = reason.s + reason.ptr;
+ 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)
+ {
+ address_item *addr;
+ md5 base;
+ uschar digest[16];
+ uschar hexdigest[33];
+ gstring * once;
+
+ if (filter_personal(aliases, TRUE))
+ {
+ if (filter_test == FTEST_NONE)
+ {
+ /* ensure oncelog directory exists; failure will be detected later */
+
+ (void)directory_make(NULL, filter->vacation_directory, 0700, FALSE);
+ }
+ /* build oncelog filename */
+
+ md5_start(&base);
+
+ if (handle.ptr == -1)
+ {
+ gstring * key = NULL;
+ if (subject.ptr != -1)
+ key = string_catn(key, subject.s, subject.ptr);
+ if (from.ptr != -1)
+ key = string_catn(key, from.s, from.ptr);
+ key = string_catn(key, reason_is_mime?US"1":US"0", 1);
+ key = string_catn(key, reason.s, reason.ptr);
+ md5_end(&base, key->s, key->ptr, digest);
+ }
+ else
+ md5_end(&base, handle.s, handle.ptr, digest);
+
+ for (int i = 0; i < 16; i++)
+ sprintf(CS (hexdigest+2*i), "%02X", digest[i]);
+
+ if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
+ debug_printf_indent("Sieve: mail was personal, vacation file basename: %s\n", hexdigest);
+
+ if (filter_test == FTEST_NONE)
+ {
+ once = string_cat (NULL, filter->vacation_directory);
+ once = string_catn(once, US"/", 1);
+ once = string_catn(once, hexdigest, 33);
+
+ /* process subject */
+
+ if (subject.ptr == -1)
+ {
+ uschar * subject_def;
+
+ subject_def = expand_string(US"${if def:header_subject {true}{false}}");
+ if (subject_def && Ustrcmp(subject_def,"true") == 0)
+ {
+ gstring * g = string_catn(NULL, US"Auto: ", 6);
+
+ expand_header(&subject, &str_subject);
+ g = string_catn(g, subject.s, subject.ptr);
+ subject.ptr = len_string_from_gstring(g, &subject.s);
+ }
+ else
+ {
+ subject.s = US"Automated reply";
+ subject.ptr = Ustrlen(subject.s);
+ }
+ }
+
+ /* add address to list of generated addresses */
+
+ addr = deliver_make_addr(string_sprintf(">%.256s", sender_address), FALSE);
+ setflag(addr, af_pfr);
+ addr->prop.ignore_error = TRUE;
+ addr->next = *generated;
+ *generated = addr;
+ addr->reply = store_get(sizeof(reply_item), GET_UNTAINTED);
+ memset(addr->reply, 0, sizeof(reply_item)); /* XXX */
+ addr->reply->to = string_copy(sender_address);
+ if (from.ptr == -1)
+ addr->reply->from = expand_string(US"$local_part@$domain");
+ else
+ addr->reply->from = from.s;
+ /* deconst cast safe as we pass in a non-const item */
+ addr->reply->subject = US parse_quote_2047(subject.s, subject.ptr, US"utf-8", TRUE);
+ addr->reply->oncelog = string_from_gstring(once);
+ addr->reply->once_repeat = days*86400;
+
+ /* build body and MIME headers */
+
+ if (reason_is_mime)
+ {
+ uschar *mime_body, *reason_end;
+ static const uschar nlnl[] = "\r\n\r\n";
+
+ for
+ (
+ mime_body = reason.s, reason_end = reason.s + reason.ptr;
+ mime_body < (reason_end-(sizeof(nlnl)-1)) && memcmp(mime_body, nlnl, (sizeof(nlnl)-1));
+ ) mime_body++;
+
+ addr->reply->headers = string_copyn(reason.s, mime_body-reason.s);
+
+ if (mime_body+(sizeof(nlnl)-1)<reason_end)
+ mime_body += (sizeof(nlnl)-1);
+ else mime_body = reason_end-1;
+ addr->reply->text = string_copyn(mime_body, reason_end-mime_body);
+ }
+ else
+ {
+ 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)->s;
+ }
+ }
+ }
+ else if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
+ debug_printf_indent("Sieve: mail was not personal, vacation would ignore it\n");
+ }
+ }
+ else break;
+#endif
+ }
+return 1;
+}
+
+
+/*************************************************
+* Parse and interpret a sieve filter *
+*************************************************/
+
+/*
+Arguments:
+ filter points to the Sieve filter including its state
+ exec Execute parsed statements
+ generated where to hang newly-generated addresses
+
+Returns: 1 success
+ -1 syntax or execution error
+*/
+
+static int
+parse_start(struct Sieve *filter, int exec, address_item **generated)
+{
+filter->pc = filter->filter;
+filter->line = 1;
+filter->keep = 1;
+filter->require_envelope = 0;
+filter->require_fileinto = 0;
+#ifdef ENCODED_CHARACTER
+filter->require_encoded_character = FALSE;
+#endif
+#ifdef ENVELOPE_AUTH
+filter->require_envelope_auth = 0;
+#endif
+#ifdef ENOTIFY
+filter->require_enotify = 0;
+filter->notified = (struct Notification*)0;
+#endif
+#ifdef SUBADDRESS
+filter->require_subaddress = FALSE;
+#endif
+#ifdef VACATION
+filter->require_vacation = FALSE;
+filter->vacation_ran = 0; /*XXX missing init? */
+#endif
+filter->require_copy = FALSE;
+filter->require_iascii_numeric = FALSE;
+
+if (parse_white(filter) == -1) return -1;
+
+if (exec && filter->vacation_directory && filter_test == FTEST_NONE)
+ {
+ DIR *oncelogdir;
+ struct dirent *oncelog;
+ struct stat properties;
+ time_t now;
+
+ /* clean up old vacation log databases */
+
+ if ( !(oncelogdir = exim_opendir(filter->vacation_directory))
+ && errno != ENOENT)
+ {
+ filter->errmsg = CUS "unable to open vacation directory";
+ return -1;
+ }
+
+ if (oncelogdir)
+ {
+ time(&now);
+
+ while ((oncelog = readdir(oncelogdir)))
+ if (strlen(oncelog->d_name) == 32)
+ {
+ uschar *s = string_sprintf("%s/%s", filter->vacation_directory, oncelog->d_name);
+ if (Ustat(s, &properties) == 0 && properties.st_mtime+VACATION_MAX_DAYS*86400 < now)
+ Uunlink(s);
+ }
+ closedir(oncelogdir);
+ }
+ }
+
+while (parse_identifier(filter, CUS "require"))
+ {
+ /*
+ require-command = "require" <capabilities: string-list>
+ */
+
+ gstring *cap;
+ int m;
+
+ if (parse_white(filter) == -1) return -1;
+ if ((m = parse_stringlist(filter, &cap)) != 1)
+ {
+ if (m == 0) filter->errmsg = CUS "capability string list expected";
+ return -1;
+ }
+ for (gstring * check = cap; check->s; ++check)
+ {
+ if (eq_octet(check, &str_envelope, FALSE)) filter->require_envelope = 1;
+ else if (eq_octet(check, &str_fileinto, FALSE)) filter->require_fileinto = 1;
+#ifdef ENCODED_CHARACTER
+ else if (eq_octet(check, &str_encoded_character, FALSE)) filter->require_encoded_character = TRUE;
+#endif
+#ifdef ENVELOPE_AUTH
+ else if (eq_octet(check, &str_envelope_auth, FALSE)) filter->require_envelope_auth = 1;
+#endif
+#ifdef ENOTIFY
+ else if (eq_octet(check, &str_enotify, FALSE))
+ {
+ if (!filter->enotify_mailto_owner)
+ {
+ filter->errmsg = CUS "enotify disabled";
+ return -1;
+ }
+ filter->require_enotify = 1;
+ }
+#endif
+#ifdef SUBADDRESS
+ else if (eq_octet(check, &str_subaddress, FALSE)) filter->require_subaddress = TRUE;
+#endif
+#ifdef VACATION
+ else if (eq_octet(check, &str_vacation, FALSE))
+ {
+ if (filter_test == FTEST_NONE && !filter->vacation_directory)
+ {
+ filter->errmsg = CUS "vacation disabled";
+ return -1;
+ }
+ filter->require_vacation = TRUE;
+ }
+#endif
+ else if (eq_octet(check, &str_copy, FALSE)) filter->require_copy = TRUE;
+ else if (eq_octet(check, &str_comparator_ioctet, FALSE)) ;
+ else if (eq_octet(check, &str_comparator_iascii_casemap, FALSE)) ;
+ else if (eq_octet(check, &str_comparator_enascii_casemap, FALSE)) ;
+ else if (eq_octet(check, &str_comparator_iascii_numeric, FALSE)) filter->require_iascii_numeric = TRUE;
+ else
+ {
+ filter->errmsg = CUS "unknown capability";
+ return -1;
+ }
+ }
+ if (parse_semicolon(filter) == -1) return -1;
+ }
+ if (parse_commands(filter, exec, generated) == -1) return -1;
+ if (*filter->pc)
+ {
+ filter->errmsg = CUS "syntax error";
+ return -1;
+ }
+ return 1;
+}
+
+
+/*************************************************
+* Interpret a sieve filter file *
+*************************************************/
+
+/*
+Arguments:
+ filter points to the entire file, read into store as a single string
+ options controls whether various special things are allowed, and requests
+ special actions (not currently used)
+ sieve
+ vacation_directory where to store vacation "once" files
+ enotify_mailto_owner owner of mailto notifications
+ useraddress string expression for :user part of address
+ subaddress string expression for :subaddress part of address
+ inbox string expression for "keep"
+ generated where to hang newly-generated addresses
+ error where to pass back an error text
+
+Returns: FF_DELIVERED success, a significant action was taken
+ FF_NOTDELIVERED success, no significant action
+ FF_DEFER defer requested
+ FF_FAIL fail requested
+ FF_FREEZE freeze requested
+ FF_ERROR there was a problem
+*/
+
+int
+sieve_interpret(const uschar * filter, int options, const sieve_block * sb,
+ address_item ** generated, uschar ** error)
+{
+struct Sieve sieve;
+int r;
+uschar * msg;
+
+DEBUG(D_route) debug_printf_indent("Sieve: start of processing\n");
+expand_level++;
+sieve.filter = filter;
+
+GET_OPTION("sieve_vacation_directory");
+if (!sb || !sb->vacation_dir)
+ sieve.vacation_directory = NULL;
+else if (!(sieve.vacation_directory = expand_cstring(sb->vacation_dir)))
+ {
+ *error = string_sprintf("failed to expand \"%s\" "
+ "(sieve_vacation_directory): %s", sb->vacation_dir, expand_string_message);
+ return FF_ERROR;
+ }
+
+GET_OPTION("sieve_vacation_directory");
+if (!sb || !sb->inbox)
+ sieve.inbox = US"inbox";
+else if (!(sieve.inbox = expand_cstring(sb->inbox)))
+ {
+ *error = string_sprintf("failed to expand \"%s\" "
+ "(sieve_inbox): %s", sb->inbox, expand_string_message);
+ return FF_ERROR;
+ }
+
+GET_OPTION("sieve_enotify_mailto_owner");
+if (!sb || !sb->enotify_mailto_owner)
+ sieve.enotify_mailto_owner = NULL;
+else if (!(sieve.enotify_mailto_owner = expand_cstring(sb->enotify_mailto_owner)))
+ {
+ *error = string_sprintf("failed to expand \"%s\" "
+ "(sieve_enotify_mailto_owner): %s", sb->enotify_mailto_owner,
+ expand_string_message);
+ return FF_ERROR;
+ }
+
+GET_OPTION("sieve_useraddress");
+sieve.useraddress = sb && sb->useraddress
+ ? sb->useraddress : CUS "$local_part_prefix$local_part$local_part_suffix";
+GET_OPTION("sieve_subaddress");
+sieve.subaddress = sb ? sb->subaddress : NULL;
+
+#ifdef COMPILE_SYNTAX_CHECKER
+if (parse_start(&sieve, 0, generated) == 1)
+#else
+if (parse_start(&sieve, 1, generated) == 1)
+#endif
+ if (sieve.keep)
+ {
+ add_addr(generated, sieve.inbox, 1, 0, 0, 0);
+ msg = US"Implicit keep";
+ r = FF_DELIVERED;
+ }
+ else
+ {
+ msg = US"No implicit keep";
+ r = FF_DELIVERED;
+ }
+else
+ {
+ msg = string_sprintf("Sieve error: %s in line %d", sieve.errmsg, sieve.line);
+#ifdef COMPILE_SYNTAX_CHECKER
+ r = FF_ERROR;
+ *error = msg;
+#else
+ add_addr(generated, sieve.inbox, 1, 0, 0, 0);
+ r = FF_DELIVERED;
+#endif
+ }
+
+#ifndef COMPILE_SYNTAX_CHECKER
+if (filter_test != FTEST_NONE) printf("%s\n", (const char*) msg);
+ else debug_printf_indent("%s\n", msg);
+#endif
+
+expand_level--;
+DEBUG(D_route) debug_printf_indent("Sieve: end of processing\n");
+return r;
+}
+
+
+/* Module API: print list of supported sieve extensions to given stream */
+static void
+sieve_extensions(FILE * fp)
+{
+for (const uschar ** pp = exim_sieve_extension_list; *pp; ++pp)
+ fprintf(fp, "%s\n", *pp);
+}
+
+
+/******************************************************************************/
+/* Module API */
+
+static void * sieve_functions[] = {
+ [SIEVE_INTERPRET] = sieve_interpret,
+ [SIEVE_EXTENSIONS] = sieve_extensions,
+};
+
+misc_module_info sieve_filter_module_info =
+{
+ .name = US"sieve_filter",
+# ifdef DYNLOOKUP
+ .dyn_magic = MISC_MODULE_MAGIC,
+# endif
+
+ .functions = sieve_functions,
+ .functions_count = nelem(sieve_functions),
+};
+
+/* End of sieve_filter.c */
--- /dev/null
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) The Exim Maintainers 2024 */
+/* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* API definitions for the sieve_filter module */
+
+
+/* Function table entry numbers */
+
+#define SIEVE_INTERPRET 0
+#define SIEVE_EXTENSIONS 1
*error = US"Exim filtering not enabled";
return FF_ERROR;
}
+/*XXX*/
frc = filter_interpret(data, options, generated, error);
}
else
{
+ const misc_module_info * mi;
+ typedef int (*fn_t)(const uschar *, int, const sieve_block *,
+ address_item **, uschar **);
+
if (options & RDO_SIEVE_FILTER)
{
*error = US"Sieve filtering not enabled";
return FF_ERROR;
}
- frc = sieve_interpret(data, options, sieve, generated, error);
+ if (!(mi = misc_mod_find(US"sieve_filter", NULL)))
+ {
+ *error = US"Sieve filtering not available";
+ return FF_ERROR;
+ }
+ frc = (((fn_t *) mi->functions)[SIEVE_INTERPRET])
+ (data, options, sieve, generated, error);
}
expand_forbid = old_expand_forbid;
+++ /dev/null
-/*************************************************
-* Exim - an Internet mail transport agent *
-*************************************************/
-
-/*
- * Copyright (c) The Exim Maintainers 2016 - 2023
- * Copyright (c) Michael Haardt 2003 - 2015
- * See the file NOTICE for conditions of use and distribution.
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-/* This code was contributed by Michael Haardt. */
-
-
-/* Sieve mail filter. */
-
-#include <ctype.h>
-#include <errno.h>
-#include <limits.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "exim.h"
-
-#if HAVE_ICONV
-# include <iconv.h>
-#endif
-
-/* Define this for RFC compliant \r\n end-of-line terminators. */
-/* Undefine it for UNIX-style \n end-of-line terminators (default). */
-#undef RFC_EOL
-
-/* Define this for development of the Sieve extension "encoded-character". */
-#define ENCODED_CHARACTER
-
-/* Define this for development of the Sieve extension "envelope-auth". */
-#undef ENVELOPE_AUTH
-
-/* Define this for development of the Sieve extension "enotify". */
-#define ENOTIFY
-
-/* Define this for the Sieve extension "subaddress". */
-#define SUBADDRESS
-
-/* Define this for the Sieve extension "vacation". */
-#define VACATION
-
-/* Must be >= 1 */
-#define VACATION_MIN_DAYS 1
-/* Must be >= VACATION_MIN_DAYS, must be > 7, should be > 30 */
-#define VACATION_MAX_DAYS 31
-
-/* Keep this at 75 to accept only RFC compliant MIME words. */
-/* Increase it if you want to match headers from buggy MUAs. */
-#define MIMEWORD_LENGTH 75
-
-struct Sieve {
- const uschar *filter;
- const uschar *pc;
- int line;
- const uschar *errmsg;
- int keep;
- int require_envelope;
- int require_fileinto;
-#ifdef ENCODED_CHARACTER
- BOOL require_encoded_character;
-#endif
-#ifdef ENVELOPE_AUTH
- int require_envelope_auth;
-#endif
-#ifdef ENOTIFY
- int require_enotify;
- struct Notification *notified;
-#endif
- const uschar *enotify_mailto_owner;
-#ifdef SUBADDRESS
- int require_subaddress;
-#endif
-#ifdef VACATION
- BOOL require_vacation;
- BOOL vacation_ran;
-#endif
- const uschar *inbox;
- const uschar *vacation_directory;
- const uschar *subaddress;
- const uschar *useraddress;
- BOOL require_copy;
- BOOL require_iascii_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 };
-#else
-enum AddressPart { ADDRPART_LOCALPART, ADDRPART_DOMAIN, ADDRPART_ALL };
-#endif
-enum RelOp { LT, LE, EQ, GE, GT, NE };
-
-struct Notification {
- gstring method;
- gstring importance;
- gstring message;
- struct Notification *next;
-};
-
-/* This should be a complete list of supported extensions, so that an external
-ManageSieve (RFC 5804) program can interrogate the current Exim binary for the
-list of extensions and provide correct information to a client.
-
-We'll emit the list in the order given here; keep it alphabetically sorted, so
-that callers don't get surprised.
-
-List *MUST* end with a NULL. Which at least makes ifdef-vs-comma easier. */
-
-const uschar *exim_sieve_extension_list[] = {
- CUS"comparator-i;ascii-numeric",
- CUS"copy",
-#ifdef ENCODED_CHARACTER
- CUS"encoded-character",
-#endif
-#ifdef ENOTIFY
- CUS"enotify",
-#endif
- CUS"envelope",
-#ifdef ENVELOPE_AUTH
- CUS"envelope-auth",
-#endif
- CUS"fileinto",
-#ifdef SUBADDRESS
- CUS"subaddress",
-#endif
-#ifdef VACATION
- CUS"vacation",
-#endif
- NULL
-};
-
-static int eq_asciicase(const gstring * needle, const gstring * haystack, BOOL match_prefix);
-static int parse_test(struct Sieve *filter, int *cond, int exec);
-static int parse_commands(struct Sieve *filter, int exec, address_item **generated);
-
-static uschar str_from_c[] = "From";
-static const gstring str_from = { .s = str_from_c, .ptr = 4, .size = 5 };
-static uschar str_to_c[] = "To";
-static const gstring str_to = { .s = str_to_c, .ptr = 2, .size = 3 };
-static uschar str_cc_c[] = "Cc";
-static const gstring str_cc = { .s = str_cc_c, .ptr = 2, .size = 3 };
-static uschar str_bcc_c[] = "Bcc";
-static const gstring str_bcc = { .s = str_bcc_c, .ptr = 3, .size = 4 };
-#ifdef ENVELOPE_AUTH
-static uschar str_auth_c[] = "auth";
-static const gstring str_auth = { .s = str_auth_c, .ptr = 4, .size = 5 };
-#endif
-static uschar str_sender_c[] = "Sender";
-static const gstring str_sender = { .s = str_sender_c, .ptr = 6, .size = 7 };
-static uschar str_resent_from_c[] = "Resent-From";
-static const gstring str_resent_from = { .s = str_resent_from_c, .ptr = 11, .size = 12 };
-static uschar str_resent_to_c[] = "Resent-To";
-static const gstring str_resent_to = { .s = str_resent_to_c, .ptr = 9, .size = 10 };
-static uschar str_fileinto_c[] = "fileinto";
-static const gstring str_fileinto = { .s = str_fileinto_c, .ptr = 8, .size = 9 };
-static uschar str_envelope_c[] = "envelope";
-static const gstring str_envelope = { .s = str_envelope_c, .ptr = 8, .size = 9 };
-#ifdef ENCODED_CHARACTER
-static uschar str_encoded_character_c[] = "encoded-character";
-static const gstring str_encoded_character = { .s = str_encoded_character_c, .ptr = 17, .size = 18 };
-#endif
-#ifdef ENVELOPE_AUTH
-static uschar str_envelope_auth_c[] = "envelope-auth";
-static const gstring str_envelope_auth = { .s = str_envelope_auth_c, .ptr = 13, .size = 14 };
-#endif
-#ifdef ENOTIFY
-static uschar str_enotify_c[] = "enotify";
-static const gstring str_enotify = { .s = str_enotify_c, .ptr = 7, .size = 8 };
-static uschar str_online_c[] = "online";
-static const gstring str_online = { .s = str_online_c, .ptr = 6, .size = 7 };
-static uschar str_maybe_c[] = "maybe";
-static const gstring str_maybe = { .s = str_maybe_c, .ptr = 5, .size = 6 };
-static uschar str_auto_submitted_c[] = "Auto-Submitted";
-static const gstring str_auto_submitted = { .s = str_auto_submitted_c, .ptr = 14, .size = 15 };
-#endif
-#ifdef SUBADDRESS
-static uschar str_subaddress_c[] = "subaddress";
-static const gstring str_subaddress = { .s = str_subaddress_c, .ptr = 10, .size = 11 };
-#endif
-#ifdef VACATION
-static uschar str_vacation_c[] = "vacation";
-static const gstring str_vacation = { .s = str_vacation_c, .ptr = 8, .size = 9 };
-static uschar str_subject_c[] = "Subject";
-static const gstring str_subject = { .s = str_subject_c, .ptr = 7, .size = 8 };
-#endif
-static uschar str_copy_c[] = "copy";
-static const gstring str_copy = { .s = str_copy_c, .ptr = 4, .size = 5 };
-static uschar str_iascii_casemap_c[] = "i;ascii-casemap";
-static const gstring str_iascii_casemap = { .s = str_iascii_casemap_c, .ptr = 15, .size = 16 };
-static uschar str_enascii_casemap_c[] = "en;ascii-casemap";
-static const gstring str_enascii_casemap = { .s = str_enascii_casemap_c, .ptr = 16, .size = 17 };
-static uschar str_ioctet_c[] = "i;octet";
-static const gstring str_ioctet = { .s = str_ioctet_c, .ptr = 7, .size = 8 };
-static uschar str_iascii_numeric_c[] = "i;ascii-numeric";
-static const gstring str_iascii_numeric = { .s = str_iascii_numeric_c, .ptr = 15, .size = 16 };
-static uschar str_comparator_iascii_casemap_c[] = "comparator-i;ascii-casemap";
-static const gstring str_comparator_iascii_casemap = { .s = str_comparator_iascii_casemap_c, .ptr = 26, .size = 27 };
-static uschar str_comparator_enascii_casemap_c[] = "comparator-en;ascii-casemap";
-static const gstring str_comparator_enascii_casemap = { .s = str_comparator_enascii_casemap_c, .ptr = 27, .size = 28 };
-static uschar str_comparator_ioctet_c[] = "comparator-i;octet";
-static const gstring str_comparator_ioctet = { .s = str_comparator_ioctet_c, .ptr = 18, .size = 19 };
-static uschar str_comparator_iascii_numeric_c[] = "comparator-i;ascii-numeric";
-static const gstring str_comparator_iascii_numeric = { .s = str_comparator_iascii_numeric_c, .ptr = 26, .size = 27 };
-
-
-/*************************************************
-* Encode to quoted-printable *
-*************************************************/
-
-/*
-Arguments:
- src UTF-8 string
-
-Returns
- dst, allocated, a US-ASCII string
-*/
-
-static gstring *
-quoted_printable_encode(const gstring * src)
-{
-gstring * dst = NULL;
-uschar ch;
-size_t line = 0;
-
-for (const uschar * start = src->s, * end = start + src->ptr;
- start < end; ++start)
- {
- ch = *start;
- if (line >= 73) /* line length limit */
- {
- dst = string_catn(dst, US"=\n", 2); /* line split */
- line = 0;
- }
- if ( (ch >= '!' && ch <= '<')
- || (ch >= '>' && ch <= '~')
- || ( (ch == '\t' || ch == ' ')
- && start+2 < end && (start[1] != '\r' || start[2] != '\n') /* CRLF */
- )
- )
- {
- dst = string_catn(dst, start, 1); /* copy char */
- ++line;
- }
- else if (ch == '\r' && start+1 < end && start[1] == '\n') /* CRLF */
- {
- dst = string_catn(dst, US"\n", 1); /* NL */
- line = 0;
- ++start; /* consume extra input char */
- }
- else
- {
- dst = string_fmt_append(dst, "=%02X", ch);
- line += 3;
- }
- }
-
-(void) string_from_gstring(dst);
-gstring_release_unused(dst);
-return dst;
-}
-
-
-/*************************************************
-* Check mail address for correct syntax *
-*************************************************/
-
-/*
-Check mail address for being syntactically correct.
-
-Arguments:
- filter points to the Sieve filter including its state
- address String containing one address
-
-Returns
- 1 Mail address is syntactically OK
- -1 syntax error
-*/
-
-int
-check_mail_address(struct Sieve * filter, const gstring * address)
-{
-int start, end, domain;
-uschar * error, * ss;
-
-if (address->ptr > 0)
- {
- ss = parse_extract_address(address->s, &error, &start, &end, &domain,
- FALSE);
- if (!ss)
- {
- filter->errmsg = string_sprintf("malformed address \"%s\" (%s)",
- address->s, error);
- return -1;
- }
- else
- return 1;
- }
-else
- {
- filter->errmsg = CUS "empty address";
- return -1;
- }
-}
-
-
-/*************************************************
-* Decode URI encoded string *
-*************************************************/
-
-/*
-Arguments:
- str URI encoded string
-
-Returns
- str is modified in place
- TRUE Decoding successful
- FALSE Encoding error
-*/
-
-#ifdef ENOTIFY
-static BOOL
-uri_decode(gstring * str)
-{
-uschar *s, *t, *e;
-
-if (str->ptr == 0) return TRUE;
-for (t = s = str->s, e = s + str->ptr; s < e; )
- if (*s == '%')
- {
- if (s+2 < e && isxdigit(s[1]) && isxdigit(s[2]))
- {
- *t++ = ((isdigit(s[1]) ? s[1]-'0' : tolower(s[1])-'a'+10)<<4)
- | (isdigit(s[2]) ? s[2]-'0' : tolower(s[2])-'a'+10);
- s += 3;
- }
- else return FALSE;
- }
- else
- *t++ = *s++;
-
-*t = '\0';
-str->ptr = t - str->s;
-return TRUE;
-}
-
-
-/*************************************************
-* Parse mailto URI *
-*************************************************/
-
-/*
-Parse mailto-URI.
-
- mailtoURI = "mailto:" [ to ] [ headers ]
- to = [ addr-spec *("%2C" addr-spec ) ]
- headers = "?" header *( "&" header )
- header = hname " = " hvalue
- hname = *urlc
- hvalue = *urlc
-
-Arguments:
- filter points to the Sieve filter including its state
- uri URI, excluding scheme
- recipient list of recipients; prepnded to
- body
-
-Returns
- 1 URI is syntactically OK
- 0 Unknown URI scheme
- -1 syntax error
-*/
-
-static int
-parse_mailto_uri(struct Sieve * filter, const uschar * uri,
- string_item ** recipient, gstring * header, gstring * subject,
- gstring * body)
-{
-const uschar * start;
-
-if (Ustrncmp(uri, "mailto:", 7))
- {
- filter->errmsg = US "Unknown URI scheme";
- return 0;
- }
-
-uri += 7;
-if (*uri && *uri != '?')
- for (;;)
- {
- /* match to */
- for (start = uri; *uri && *uri != '?' && (*uri != '%' || uri[1] != '2' || tolower(uri[2]) != 'c'); ++uri);
- if (uri > start)
- {
- gstring * to = string_catn(NULL, start, uri - start);
- string_item * new;
-
- if (!uri_decode(to))
- {
- filter->errmsg = US"Invalid URI encoding";
- return -1;
- }
- new = store_get(sizeof(string_item), GET_UNTAINTED);
- new->text = string_from_gstring(to);
- new->next = *recipient;
- *recipient = new;
- }
- else
- {
- filter->errmsg = US"Missing addr-spec in URI";
- return -1;
- }
- if (*uri == '%') uri += 3;
- else break;
- }
-if (*uri == '?')
- for (uri++; ;)
- {
- gstring * hname = string_get(0), * hvalue = NULL;
-
- /* match hname */
- for (start = uri; *uri && (isalnum(*uri) || strchr("$-_.+!*'(), %", *uri)); ++uri) ;
- if (uri > start)
- {
- hname = string_catn(hname, start, uri-start);
-
- if (!uri_decode(hname))
- {
- filter->errmsg = US"Invalid URI encoding";
- return -1;
- }
- }
- /* match = */
- if (*uri++ != '=')
- {
- filter->errmsg = US"Missing equal after hname";
- return -1;
- }
-
- /* match hvalue */
- for (start = uri; *uri && (isalnum(*uri) || strchr("$-_.+!*'(), %", *uri)); ++uri) ;
- if (uri > start)
- {
- hvalue = string_catn(NULL, start, uri-start); /*XXX this used to say "hname =" */
-
- if (!uri_decode(hvalue))
- {
- filter->errmsg = US"Invalid URI encoding";
- return -1;
- }
- }
- if (hname->ptr == 2 && strcmpic(hname->s, US"to") == 0)
- {
- string_item * new = store_get(sizeof(string_item), GET_UNTAINTED);
- new->text = string_from_gstring(hvalue);
- new->next = *recipient;
- *recipient = new;
- }
- else if (hname->ptr == 4 && strcmpic(hname->s, US"body") == 0)
- *body = *hvalue;
- else if (hname->ptr == 7 && strcmpic(hname->s, US"subject") == 0)
- *subject = *hvalue;
- else
- {
- static gstring ignore[] =
- {
- {.s = US"date", .ptr = 4, .size = 5},
- {.s = US"from", .ptr = 4, .size = 5},
- {.s = US"message-id", .ptr = 10, .size = 11},
- {.s = US"received", .ptr = 8, .size = 9},
- {.s = US"auto-submitted", .ptr = 14, .size = 15}
- };
- static gstring * end = ignore + nelem(ignore);
- gstring * i;
-
- for (i = ignore; i < end && !eq_asciicase(hname, i, FALSE); ++i);
- if (i == end)
- {
- hname = string_fmt_append(NULL, "%Y%Y: %Y\n", header, hname, hvalue);
- (void) string_from_gstring(hname);
- /*XXX we seem to do nothing with this new hname? */
- }
- }
- if (*uri == '&') ++uri;
- else break;
- }
-if (*uri)
- {
- filter->errmsg = US"Syntactically invalid URI";
- return -1;
- }
-return 1;
-}
-#endif
-
-
-/*************************************************
-* Octet-wise string comparison *
-*************************************************/
-
-/*
-Arguments:
- needle UTF-8 string to search ...
- haystack ... inside the haystack
- match_prefix TRUE to compare if needle is a prefix of haystack
-
-Returns: 0 needle not found in haystack
- 1 needle found
-*/
-
-static int
-eq_octet(const gstring *needle, const gstring *haystack, BOOL match_prefix)
-{
-size_t nl, hl;
-const uschar *n, *h;
-
-nl = needle->ptr;
-n = needle->s;
-hl = haystack->ptr;
-h = haystack->s;
-while (nl>0 && hl>0)
- {
-#if !HAVE_ICONV
- if (*n & 0x80) return 0;
- if (*h & 0x80) return 0;
-#endif
- if (*n != *h) return 0;
- ++n;
- ++h;
- --nl;
- --hl;
- }
-return (match_prefix ? nl == 0 : nl == 0 && hl == 0);
-}
-
-
-/*************************************************
-* ASCII case-insensitive string comparison *
-*************************************************/
-
-/*
-Arguments:
- needle UTF-8 string to search ...
- haystack ... inside the haystack
- match_prefix TRUE to compare if needle is a prefix of haystack
-
-Returns: 0 needle not found in haystack
- 1 needle found
-*/
-
-static int
-eq_asciicase(const gstring *needle, const gstring *haystack, BOOL match_prefix)
-{
-size_t nl, hl;
-const uschar *n, *h;
-uschar nc, hc;
-
-nl = needle->ptr;
-n = needle->s;
-hl = haystack->ptr;
-h = haystack->s;
-while (nl > 0 && hl > 0)
- {
- nc = *n;
- hc = *h;
-#if !HAVE_ICONV
- if (nc & 0x80) return 0;
- if (hc & 0x80) return 0;
-#endif
- /* tolower depends on the locale and only ASCII case must be insensitive */
- if ((nc >= 'A' && nc <= 'Z' ? nc | 0x20 : nc) != (hc >= 'A' && hc <= 'Z' ? hc | 0x20 : hc)) return 0;
- ++n;
- ++h;
- --nl;
- --hl;
- }
-return (match_prefix ? nl == 0 : nl == 0 && hl == 0);
-}
-
-
-/*************************************************
-* Glob pattern search *
-*************************************************/
-
-/*
-Arguments:
- needle pattern to search ...
- haystack ... inside the haystack
- ascii_caseless ignore ASCII case
- match_octet match octets, not UTF-8 multi-octet characters
-
-Returns: 0 needle not found in haystack
- 1 needle found
- -1 pattern error
-*/
-
-static int
-eq_glob(const gstring *needle,
- const gstring *haystack, BOOL ascii_caseless, BOOL match_octet)
-{
-const uschar *n, *h, *nend, *hend;
-int may_advance = 0;
-
-n = needle->s;
-h = haystack->s;
-nend = n+needle->ptr;
-hend = h+haystack->ptr;
-while (n < nend)
- if (*n == '*')
- {
- ++n;
- may_advance = 1;
- }
- else
- {
- const uschar *npart, *hpart;
-
- /* 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 (hpart == hend) return 0;
- if (match_octet)
- ++hpart;
- else
- {
- /* Match one UTF8 encoded character */
- if ((*hpart&0xc0) == 0xc0)
- {
- ++hpart;
- while (hpart<hend && ((*hpart&0xc0) == 0x80)) ++hpart;
- }
- else
- ++hpart;
- }
- ++npart;
- break;
- }
- case '\\':
- {
- ++npart;
- if (npart == nend) return -1;
- /* FALLTHROUGH */
- }
- default:
- {
- 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
- )
- {
- if (may_advance)
- /* string match after a star failed, advance and try again */
- {
- ++h;
- goto match_part;
- }
- else return 0;
- }
- else
- {
- ++npart;
- ++hpart;
- };
- }
- }
- /* 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 */
- {
- ++h;
- goto match_part;
- }
- h = hpart;
- n = npart;
- may_advance = 0;
- }
-return (h == hend ? 1 : may_advance);
-}
-
-
-/*************************************************
-* ASCII numeric comparison *
-*************************************************/
-
-/*
-Arguments:
- a first numeric string
- b second numeric string
- relop relational operator
-
-Returns: 0 not (a relop b)
- 1 a relop b
-*/
-
-static int
-eq_asciinumeric(const gstring *a, const gstring *b, enum RelOp relop)
-{
-size_t al, bl;
-const uschar *as, *aend, *bs, *bend;
-int cmp;
-
-as = a->s;
-aend = a->s+a->ptr;
-bs = b->s;
-bend = b->s+b->ptr;
-
-while (*as>= '0' && *as<= '9' && as<aend) ++as;
-al = as-a->s;
-while (*bs>= '0' && *bs<= '9' && bs<bend) ++bs;
-bl = bs-b->s;
-
-if (al && bl == 0) cmp = -1;
-else if (al == 0 && bl == 0) cmp = 0;
-else if (al == 0 && bl) cmp = 1;
-else
- {
- cmp = al-bl;
- if (cmp == 0) cmp = memcmp(a->s, b->s, al);
- }
-switch (relop)
- {
- case LT: return cmp < 0;
- case LE: return cmp <= 0;
- case EQ: return cmp == 0;
- case GE: return cmp >= 0;
- case GT: return cmp > 0;
- case NE: return cmp != 0;
- }
- /*NOTREACHED*/
- return -1;
-}
-
-
-/*************************************************
-* Compare strings *
-*************************************************/
-
-/*
-Arguments:
- filter points to the Sieve filter including its state
- needle UTF-8 pattern or string to search ...
- haystack ... inside the haystack
- co comparator to use
- mt match type to use
-
-Returns: 0 needle not found in haystack
- 1 needle found
- -1 comparator does not offer matchtype
-*/
-
-static int
-compare(struct Sieve * filter, const gstring * needle, const gstring * haystack,
- enum Comparator co, enum MatchType mt)
-{
-int r = 0;
-
-if ( (filter_test != FTEST_NONE && debug_selector != 0)
- || (debug_selector & D_filter) != 0)
- {
- debug_printf_indent("String comparison (match ");
- switch (mt)
- {
- case MATCH_IS: debug_printf_indent(":is"); break;
- case MATCH_CONTAINS: debug_printf_indent(":contains"); break;
- case MATCH_MATCHES: debug_printf_indent(":matches"); break;
- }
- debug_printf_indent(", comparison \"");
- switch (co)
- {
- case COMP_OCTET: debug_printf_indent("i;octet"); break;
- case COMP_EN_ASCII_CASEMAP: debug_printf_indent("en;ascii-casemap"); break;
- case COMP_ASCII_NUMERIC: debug_printf_indent("i;ascii-numeric"); break;
- }
- debug_printf_indent("\"):\n");
- debug_printf_indent(" Search = %s (%d chars)\n", needle->s, needle->ptr);
- debug_printf_indent(" Inside = %s (%d chars)\n", haystack->s, haystack->ptr);
- }
-switch (mt)
- {
- case MATCH_IS:
- switch (co)
- {
- case COMP_OCTET:
- if (eq_octet(needle, haystack, FALSE)) r = 1;
- break;
- case COMP_EN_ASCII_CASEMAP:
- if (eq_asciicase(needle, haystack, FALSE)) r = 1;
- break;
- case COMP_ASCII_NUMERIC:
- if (!filter->require_iascii_numeric)
- {
- filter->errmsg = CUS "missing previous require \"comparator-i;ascii-numeric\";";
- return -1;
- }
- if (eq_asciinumeric(needle, haystack, EQ)) r = 1;
- break;
- }
- break;
-
- case MATCH_CONTAINS:
- {
- gstring h;
-
- switch (co)
- {
- case COMP_OCTET:
- for (h = *haystack; h.ptr; ++h.s, --h.ptr)
- if (eq_octet(needle, &h, TRUE)) { r = 1; break; }
- break;
- case COMP_EN_ASCII_CASEMAP:
- for (h = *haystack; h.ptr; ++h.s, --h.ptr)
- if (eq_asciicase(needle, &h, TRUE)) { r = 1; break; }
- break;
- default:
- filter->errmsg = CUS "comparator does not offer specified matchtype";
- return -1;
- }
- break;
- }
-
- case MATCH_MATCHES:
- switch (co)
- {
- case COMP_OCTET:
- if ((r = eq_glob(needle, haystack, FALSE, TRUE)) == -1)
- {
- filter->errmsg = CUS "syntactically invalid pattern";
- return -1;
- }
- break;
- case COMP_EN_ASCII_CASEMAP:
- if ((r = eq_glob(needle, haystack, TRUE, TRUE)) == -1)
- {
- filter->errmsg = CUS "syntactically invalid pattern";
- return -1;
- }
- break;
- default:
- filter->errmsg = CUS "comparator does not offer specified matchtype";
- return -1;
- }
- break;
- }
-if ((filter_test != FTEST_NONE && debug_selector != 0) ||
- (debug_selector & D_filter) != 0)
- debug_printf_indent(" Result %s\n", r?"true":"false");
-return r;
-}
-
-
-/*************************************************
-* Check header field syntax *
-*************************************************/
-
-/*
-RFC 2822, section 3.6.8 says:
-
- field-name = 1*ftext
-
- ftext = %d33-57 / ; Any character except
- %d59-126 ; controls, SP, and
- ; ":".
-
-That forbids 8-bit header fields. This implementation accepts them, since
-all of Exim is 8-bit clean, so it adds %d128-%d255.
-
-Arguments:
- header header field to quote for suitable use in Exim expansions
-
-Returns: 0 string is not a valid header field
- 1 string is a value header field
-*/
-
-static int
-is_header(const gstring *header)
-{
-size_t l;
-const uschar *h;
-
-l = header->ptr;
-h = header->s;
-if (l == 0) return 0;
-while (l)
- {
- if (*h < 33 || *h == ':' || *h == 127)
- return 0;
- ++h;
- --l;
- }
-return 1;
-}
-
-
-/*************************************************
-* Quote special characters string *
-*************************************************/
-
-/*
-Arguments:
- header header field to quote for suitable use in Exim expansions
- or as debug output
-
-Returns: quoted string
-*/
-
-static const uschar *
-quote(const gstring * header)
-{
-gstring * quoted = NULL;
-size_t l;
-const uschar * h;
-
-for (l = header->ptr, h = header->s; l; ++h, --l)
- switch (*h)
- {
- case '\0':
- quoted = string_catn(quoted, CUS "\\0", 2);
- break;
- case '$':
- case '{':
- case '}':
- quoted = string_catn(quoted, CUS "\\", 1);
- default:
- quoted = string_catn(quoted, h, 1);
- }
-
-return string_from_gstring(quoted);
-}
-
-
-/*************************************************
-* Add address to list of generated addresses *
-*************************************************/
-
-/*
-According to RFC 5228, duplicate delivery to the same address must
-not happen, so the list is first searched for the address.
-
-Arguments:
- generated list of generated addresses
- addr new address to add
- file address denotes a file
-
-Returns: nothing
-*/
-
-static void
-add_addr(address_item ** generated, const uschar * addr, int file, int maxage,
- int maxmessages, int maxstorage)
-{
-address_item * new_addr;
-
-for (new_addr = *generated; new_addr; new_addr = new_addr->next)
- if ( Ustrcmp(new_addr->address, addr) == 0
- && ( !file
- || testflag(new_addr, af_pfr)
- || testflag(new_addr, af_file)
- )
- )
- {
- if ( filter_test != FTEST_NONE && debug_selector != 0
- || (debug_selector & D_filter) != 0)
- debug_printf_indent("Repeated %s `%s' ignored.\n",
- file ? "fileinto" : "redirect", addr);
-
- return;
- }
-
-if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
- debug_printf_indent("%s `%s'\n", file ? "fileinto" : "redirect", addr);
-
-new_addr = deliver_make_addr(addr, TRUE);
-if (file)
- {
- setflag(new_addr, af_pfr);
- setflag(new_addr, af_file);
- new_addr->mode = 0;
- }
-new_addr->prop.errors_address = NULL;
-new_addr->next = *generated;
-*generated = new_addr;
-}
-
-
-/*************************************************
-* Return decoded header field *
-*************************************************/
-
-/*
-Unfold the header field as described in RFC 2822 and remove all
-leading and trailing white space, then perform MIME decoding and
-translate the header field to UTF-8.
-
-Arguments:
- value returned value of the field
- header name of the header field
-
-Returns: nothing The expanded string is empty
- in case there is no such header
-*/
-
-static void
-expand_header(gstring * value, const gstring * header)
-{
-uschar *s, *r, *t;
-uschar *errmsg;
-
-value->ptr = 0;
-value->s = (uschar*)0;
-
-t = r = s = expand_string(string_sprintf("$rheader_%s", quote(header)));
-if (!t) return;
-while (*r == ' ' || *r == '\t') ++r;
-while (*r)
- if (*r == '\n')
- ++r;
- else
- *t++ = *r++;
-
-while (t>s && (*(t-1) == ' ' || *(t-1) == '\t')) --t;
-*t = '\0';
-value->s = rfc2047_decode(s, check_rfc2047_length, US"utf-8", '\0', &value->ptr, &errmsg);
-}
-
-
-/*************************************************
-* Parse remaining hash comment *
-*************************************************/
-
-/*
-Token definition:
- Comment up to terminating CRLF
-
-Arguments:
- filter points to the Sieve filter including its state
-
-Returns: 1 success
- -1 syntax error
-*/
-
-static int
-parse_hashcomment(struct Sieve * filter)
-{
-++filter->pc;
-while (*filter->pc)
- {
-#ifdef RFC_EOL
- if (*filter->pc == '\r' && (filter->pc)[1] == '\n')
-#else
- if (*filter->pc == '\n')
-#endif
- {
-#ifdef RFC_EOL
- filter->pc += 2;
-#else
- ++filter->pc;
-#endif
- ++filter->line;
- return 1;
- }
- else ++filter->pc;
- }
-filter->errmsg = CUS "missing end of comment";
-return -1;
-}
-
-
-/*************************************************
-* Parse remaining C-style comment *
-*************************************************/
-
-/*
-Token definition:
- Everything up to star slash
-
-Arguments:
- filter points to the Sieve filter including its state
-
-Returns: 1 success
- -1 syntax error
-*/
-
-static int
-parse_comment(struct Sieve *filter)
-{
-filter->pc += 2;
-while (*filter->pc)
- if (*filter->pc == '*' && (filter->pc)[1] == '/')
- {
- filter->pc += 2;
- return 1;
- }
- else
- ++filter->pc;
-
-filter->errmsg = CUS "missing end of comment";
-return -1;
-}
-
-
-/*************************************************
-* Parse optional white space *
-*************************************************/
-
-/*
-Token definition:
- Spaces, tabs, CRLFs, hash comments or C-style comments
-
-Arguments:
- filter points to the Sieve filter including its state
-
-Returns: 1 success
- -1 syntax error
-*/
-
-static int
-parse_white(struct Sieve *filter)
-{
-while (*filter->pc)
- {
- if (*filter->pc == ' ' || *filter->pc == '\t') ++filter->pc;
-#ifdef RFC_EOL
- else if (*filter->pc == '\r' && (filter->pc)[1] == '\n')
-#else
- else if (*filter->pc == '\n')
-#endif
- {
-#ifdef RFC_EOL
- filter->pc += 2;
-#else
- ++filter->pc;
-#endif
- ++filter->line;
- }
- else if (*filter->pc == '#')
- {
- if (parse_hashcomment(filter) == -1) return -1;
- }
- else if (*filter->pc == '/' && (filter->pc)[1] == '*')
- {
- if (parse_comment(filter) == -1) return -1;
- }
- else break;
- }
-return 1;
-}
-
-
-#ifdef ENCODED_CHARACTER
-/*************************************************
-* Decode hex-encoded-character string *
-*************************************************/
-
-/*
-Encoding definition:
- blank = SP / TAB / CRLF
- hex-pair-seq = *blank hex-pair *(1*blank hex-pair) *blank
- hex-pair = 1*2HEXDIG
-
-Arguments:
- src points to a hex-pair-seq
- end points to its end
- dst points to the destination of the decoded octets,
- optionally to (uschar*)0 for checking only
-
-Returns: >= 0 number of decoded octets
- -1 syntax error
-*/
-
-static int
-hex_decode(uschar *src, uschar *end, uschar *dst)
-{
-int decoded = 0;
-
-while (*src == ' ' || *src == '\t' || *src == '\n') ++src;
-do
- {
- int x, d, n;
-
- for (x = 0, d = 0;
- d<2 && src<end && isxdigit(n = tolower(*src));
- x = (x<<4)|(n>= '0' && n<= '9' ? n-'0' : 10+(n-'a')) , ++d, ++src) ;
- if (d == 0) return -1;
- if (dst) *dst++ = x;
- ++decoded;
- if (src == end) return decoded;
- if (*src == ' ' || *src == '\t' || *src == '\n')
- while (*src == ' ' || *src == '\t' || *src == '\n') ++src;
- else
- return -1;
- }
-while (src < end);
-return decoded;
-}
-
-
-/*************************************************
-* Decode unicode-encoded-character string *
-*************************************************/
-
-/*
-Encoding definition:
- blank = SP / TAB / CRLF
- unicode-hex-seq = *blank unicode-hex *(blank unicode-hex) *blank
- unicode-hex = 1*HEXDIG
-
- It is an error for a script to use a hexadecimal value that isn't in
- either the range 0 to D7FF or the range E000 to 10FFFF.
-
- At this time, strings are already scanned, thus the CRLF is converted
- to the internally used \n (should RFC_EOL have been used).
-
-Arguments:
- src points to a unicode-hex-seq
- end points to its end
- dst points to the destination of the decoded octets,
- optionally to (uschar*)0 for checking only
-
-Returns: >= 0 number of decoded octets
- -1 syntax error
- -2 semantic error (character range violation)
-*/
-
-static int
-unicode_decode(uschar *src, uschar *end, uschar *dst)
-{
-int decoded = 0;
-
-while (*src == ' ' || *src == '\t' || *src == '\n') ++src;
-do
- {
- uschar *hex_seq;
- int c, d, n;
-
- unicode_hex:
- for (hex_seq = src; src < end && *src == '0'; ) src++;
- for (c = 0, d = 0;
- d < 7 && src < end && isxdigit(n = tolower(*src));
- c = (c<<4)|(n>= '0' && n<= '9' ? n-'0' : 10+(n-'a')), ++d, ++src) ;
- if (src == hex_seq) return -1;
- if (d == 7 || (!((c >= 0 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0x10ffff)))) return -2;
- if (c<128)
- {
- if (dst) *dst++ = c;
- ++decoded;
- }
- else if (c>= 0x80 && c<= 0x7ff)
- {
- if (dst)
- {
- *dst++ = 192+(c>>6);
- *dst++ = 128+(c&0x3f);
- }
- decoded += 2;
- }
- else if (c>= 0x800 && c<= 0xffff)
- {
- if (dst)
- {
- *dst++ = 224+(c>>12);
- *dst++ = 128+((c>>6)&0x3f);
- *dst++ = 128+(c&0x3f);
- }
- decoded += 3;
- }
- else if (c>= 0x10000 && c<= 0x1fffff)
- {
- if (dst)
- {
- *dst++ = 240+(c>>18);
- *dst++ = 128+((c>>10)&0x3f);
- *dst++ = 128+((c>>6)&0x3f);
- *dst++ = 128+(c&0x3f);
- }
- decoded += 4;
- }
- if (*src == ' ' || *src == '\t' || *src == '\n')
- {
- while (*src == ' ' || *src == '\t' || *src == '\n') ++src;
- if (src == end) return decoded;
- goto unicode_hex;
- }
- }
-while (src < end);
-return decoded;
-}
-
-
-/*************************************************
-* Decode encoded-character string *
-*************************************************/
-
-/*
-Encoding definition:
- encoded-arb-octets = "${hex:" hex-pair-seq "}"
- encoded-unicode-char = "${unicode:" unicode-hex-seq "}"
-
-Arguments:
- encoded points to an encoded string, returns decoded string
- filter points to the Sieve filter including its state
-
-Returns: 1 success
- -1 syntax error
-*/
-
-static int
-string_decode(struct Sieve *filter, gstring *data)
-{
-uschar *src, *dst, *end;
-
-src = data->s;
-dst = src;
-end = data->s+data->ptr;
-while (src < end)
- {
- uschar *brace;
-
- if (
- strncmpic(src, US "${hex:", 6) == 0
- && (brace = Ustrchr(src+6, '}')) != (uschar*)0
- && (hex_decode(src+6, brace, (uschar*)0))>= 0
- )
- {
- dst += hex_decode(src+6, brace, dst);
- src = brace+1;
- }
- else if (
- strncmpic(src, US "${unicode:", 10) == 0
- && (brace = Ustrchr(src+10, '}')) != (uschar*)0
- )
- {
- switch (unicode_decode(src+10, brace, (uschar*)0))
- {
- case -2:
- {
- filter->errmsg = CUS "unicode character out of range";
- return -1;
- }
- case -1:
- {
- *dst++ = *src++;
- break;
- }
- default:
- {
- dst += unicode_decode(src+10, brace, dst);
- src = brace+1;
- }
- }
- }
- else *dst++ = *src++;
- }
- data->ptr = dst-data->s;
- *dst = '\0';
-return 1;
-}
-#endif
-
-
-/*************************************************
-* Parse an optional string *
-*************************************************/
-
-/*
-Token definition:
- quoted-string = DQUOTE *CHAR DQUOTE
- ;; in general, \ CHAR inside a string maps to CHAR
- ;; so \" maps to " and \\ maps to \
- ;; note that newlines and other characters are all allowed
- ;; in strings
-
- multi-line = "text:" *(SP / HTAB) (hash-comment / CRLF)
- *(multi-line-literal / multi-line-dotstuff)
- "." CRLF
- multi-line-literal = [CHAR-NOT-DOT *CHAR-NOT-CRLF] CRLF
- multi-line-dotstuff = "." 1*CHAR-NOT-CRLF CRLF
- ;; A line containing only "." ends the multi-line.
- ;; Remove a leading '.' if followed by another '.'.
- string = quoted-string / multi-line
-
-Arguments:
- filter points to the Sieve filter including its state
- id specifies identifier to match
-
-Returns: 1 success
- -1 syntax error
- 0 identifier not matched
-*/
-
-static int
-parse_string(struct Sieve *filter, gstring *data)
-{
-gstring * g = NULL;
-
-data->ptr = 0;
-data->s = NULL;
-
-if (*filter->pc == '"') /* quoted string */
- {
- ++filter->pc;
- while (*filter->pc)
- {
- if (*filter->pc == '"') /* end of string */
- {
- ++filter->pc;
-
- if (g)
- data->ptr = len_string_from_gstring(g, &data->s);
- else
- data->s = US"\0";
- /* that way, there will be at least one character allocated */
-
-#ifdef ENCODED_CHARACTER
- if ( filter->require_encoded_character
- && string_decode(filter, data) == -1)
- return -1;
-#endif
- return 1;
- }
- else if (*filter->pc == '\\' && (filter->pc)[1]) /* quoted character */
- {
- g = string_catn(g, filter->pc+1, 1);
- filter->pc += 2;
- }
- else /* regular character */
- {
-#ifdef RFC_EOL
- if (*filter->pc == '\r' && (filter->pc)[1] == '\n') ++filter->line;
-#else
- if (*filter->pc == '\n')
- {
- g = string_catn(g, US"\r", 1);
- ++filter->line;
- }
-#endif
- g = string_catn(g, filter->pc, 1);
- filter->pc++;
- }
- }
- filter->errmsg = CUS "missing end of string";
- return -1;
- }
-else if (Ustrncmp(filter->pc, CUS "text:", 5) == 0) /* multiline string */
- {
- filter->pc += 5;
- /* skip optional white space followed by hashed comment or CRLF */
- while (*filter->pc == ' ' || *filter->pc == '\t') ++filter->pc;
- if (*filter->pc == '#')
- {
- if (parse_hashcomment(filter) == -1) return -1;
- }
-#ifdef RFC_EOL
- else if (*filter->pc == '\r' && (filter->pc)[1] == '\n')
-#else
- else if (*filter->pc == '\n')
-#endif
- {
-#ifdef RFC_EOL
- filter->pc += 2;
-#else
- ++filter->pc;
-#endif
- ++filter->line;
- }
- else
- {
- filter->errmsg = CUS "syntax error";
- return -1;
- }
- while (*filter->pc)
- {
-#ifdef RFC_EOL
- if (*filter->pc == '\r' && (filter->pc)[1] == '\n') /* end of line */
-#else
- if (*filter->pc == '\n') /* end of line */
-#endif
- {
- g = string_catn(g, CUS "\r\n", 2);
-#ifdef RFC_EOL
- filter->pc += 2;
-#else
- ++filter->pc;
-#endif
- ++filter->line;
-#ifdef RFC_EOL
- if (*filter->pc == '.' && (filter->pc)[1] == '\r' && (filter->pc)[2] == '\n') /* end of string */
-#else
- if (*filter->pc == '.' && (filter->pc)[1] == '\n') /* end of string */
-#endif
- {
- if (g)
- data->ptr = len_string_from_gstring(g, &data->s);
- else
- data->s = US"\0";
- /* that way, there will be at least one character allocated */
-
-#ifdef RFC_EOL
- filter->pc += 3;
-#else
- filter->pc += 2;
-#endif
- ++filter->line;
-#ifdef ENCODED_CHARACTER
- if ( filter->require_encoded_character
- && string_decode(filter, data) == -1)
- return -1;
-#endif
- return 1;
- }
- else if (*filter->pc == '.' && (filter->pc)[1] == '.') /* remove dot stuffing */
- {
- g = string_catn(g, CUS ".", 1);
- filter->pc += 2;
- }
- }
- else /* regular character */
- {
- g = string_catn(g, filter->pc, 1);
- filter->pc++;
- }
- }
- filter->errmsg = CUS "missing end of multi line string";
- return -1;
- }
-else return 0;
-}
-
-
-/*************************************************
-* Parse a specific identifier *
-*************************************************/
-
-/*
-Token definition:
- identifier = (ALPHA / "_") *(ALPHA DIGIT "_")
-
-Arguments:
- filter points to the Sieve filter including its state
- id specifies identifier to match
-
-Returns: 1 success
- 0 identifier not matched
-*/
-
-static int
-parse_identifier(struct Sieve *filter, const uschar *id)
-{
-size_t idlen = Ustrlen(id);
-
-if (strncmpic(US filter->pc, US id, idlen) == 0)
- {
- uschar next = filter->pc[idlen];
-
- if ((next>= 'A' && next<= 'Z') || (next>= 'a' && next<= 'z') || next == '_' || (next>= '0' && next<= '9')) return 0;
- filter->pc += idlen;
- return 1;
- }
-else return 0;
-}
-
-
-/*************************************************
-* Parse a number *
-*************************************************/
-
-/*
-Token definition:
- number = 1*DIGIT [QUANTIFIER]
- QUANTIFIER = "K" / "M" / "G"
-
-Arguments:
- filter points to the Sieve filter including its state
- data returns value
-
-Returns: 1 success
- -1 no string list found
-*/
-
-static int
-parse_number(struct Sieve *filter, unsigned long *data)
-{
-unsigned long d, u;
-
-if (*filter->pc>= '0' && *filter->pc<= '9')
- {
- uschar *e;
-
- errno = 0;
- d = Ustrtoul(filter->pc, &e, 10);
- if (errno == ERANGE)
- {
- filter->errmsg = CUstrerror(ERANGE);
- return -1;
- }
- filter->pc = e;
- u = 1;
- if (*filter->pc == 'K') { u = 1024; ++filter->pc; }
- else if (*filter->pc == 'M') { u = 1024*1024; ++filter->pc; }
- else if (*filter->pc == 'G') { u = 1024*1024*1024; ++filter->pc; }
- if (d>(ULONG_MAX/u))
- {
- filter->errmsg = CUstrerror(ERANGE);
- return -1;
- }
- d *= u;
- *data = d;
- return 1;
- }
-else
- {
- filter->errmsg = CUS "missing number";
- return -1;
- }
-}
-
-
-/*************************************************
-* Parse a string list *
-*************************************************/
-
-/*
-Grammar:
- string-list = "[" string *(", " string) "]" / string
-
-Arguments:
- filter points to the Sieve filter including its state
- data returns string list
-
-Returns: 1 success
- -1 no string list found
-*/
-
-static int
-parse_stringlist(struct Sieve *filter, gstring **data)
-{
-const uschar *orig = filter->pc;
-int dataCapacity = 0;
-int dataLength = 0;
-gstring *d = NULL;
-int m;
-
-if (*filter->pc == '[') /* string list */
- {
- ++filter->pc;
- for (;;)
- {
- if (parse_white(filter) == -1) goto error;
- if (dataLength+1 >= dataCapacity) /* increase buffer */
- {
- gstring *new;
-
- dataCapacity = dataCapacity ? dataCapacity * 2 : 4;
- new = store_get(sizeof(gstring) * dataCapacity, GET_UNTAINTED);
-
- if (d) memcpy(new, d, sizeof(gstring)*dataLength);
- d = new;
- }
-
- m = parse_string(filter, &d[dataLength]);
- if (m == 0)
- {
- if (dataLength == 0) break;
- else
- {
- filter->errmsg = CUS "missing string";
- goto error;
- }
- }
- else if (m == -1) goto error;
- else ++dataLength;
- if (parse_white(filter) == -1) goto error;
- if (*filter->pc == ',') ++filter->pc;
- else break;
- }
- if (*filter->pc == ']')
- {
- d[dataLength].s = (uschar*)0;
- d[dataLength].ptr = -1;
- ++filter->pc;
- *data = d;
- return 1;
- }
- else
- {
- filter->errmsg = CUS "missing closing bracket";
- goto error;
- }
- }
-else /* single string */
- {
- if (!(d = store_get(sizeof(gstring)*2, GET_UNTAINTED)))
- return -1;
-
- m = parse_string(filter, &d[0]);
- if (m == -1)
- return -1;
-
- else if (m == 0)
- {
- filter->pc = orig;
- return 0;
- }
- else
- {
- d[1].s = (uschar*)0;
- d[1].ptr = -1;
- *data = d;
- return 1;
- }
- }
-error:
-filter->errmsg = CUS "missing string list";
-return -1;
-}
-
-
-/*************************************************
-* Parse an optional address part specifier *
-*************************************************/
-
-/*
-Grammar:
- address-part = ":localpart" / ":domain" / ":all"
- address-part = / ":user" / ":detail"
-
-Arguments:
- filter points to the Sieve filter including its state
- a returns address part specified
-
-Returns: 1 success
- 0 no comparator found
- -1 syntax error
-*/
-
-static int
-parse_addresspart(struct Sieve *filter, enum AddressPart *a)
-{
-#ifdef SUBADDRESS
-if (parse_identifier(filter, CUS ":user") == 1)
- {
- if (!filter->require_subaddress)
- {
- filter->errmsg = CUS "missing previous require \"subaddress\";";
- return -1;
- }
- *a = ADDRPART_USER;
- return 1;
- }
-else if (parse_identifier(filter, CUS ":detail") == 1)
- {
- if (!filter->require_subaddress)
- {
- filter->errmsg = CUS "missing previous require \"subaddress\";";
- return -1;
- }
- *a = ADDRPART_DETAIL;
- return 1;
- }
-else
-#endif
-if (parse_identifier(filter, CUS ":localpart") == 1)
- {
- *a = ADDRPART_LOCALPART;
- return 1;
- }
-else if (parse_identifier(filter, CUS ":domain") == 1)
- {
- *a = ADDRPART_DOMAIN;
- return 1;
- }
-else if (parse_identifier(filter, CUS ":all") == 1)
- {
- *a = ADDRPART_ALL;
- return 1;
- }
-else return 0;
-}
-
-
-/*************************************************
-* Parse an optional comparator *
-*************************************************/
-
-/*
-Grammar:
- comparator = ":comparator" <comparator-name: string>
-
-Arguments:
- filter points to the Sieve filter including its state
- c returns comparator
-
-Returns: 1 success
- 0 no comparator found
- -1 incomplete comparator found
-*/
-
-static int
-parse_comparator(struct Sieve *filter, enum Comparator *c)
-{
-gstring comparator_name;
-
-if (parse_identifier(filter, CUS ":comparator") == 0) return 0;
-if (parse_white(filter) == -1) return -1;
-switch (parse_string(filter, &comparator_name))
- {
- case -1: return -1;
- case 0:
- {
- filter->errmsg = CUS "missing comparator";
- return -1;
- }
- default:
- {
- int match;
-
- if (eq_asciicase(&comparator_name, &str_ioctet, FALSE))
- {
- *c = COMP_OCTET;
- match = 1;
- }
- else if (eq_asciicase(&comparator_name, &str_iascii_casemap, FALSE))
- {
- *c = COMP_EN_ASCII_CASEMAP;
- match = 1;
- }
- else if (eq_asciicase(&comparator_name, &str_enascii_casemap, FALSE))
- {
- *c = COMP_EN_ASCII_CASEMAP;
- match = 1;
- }
- else if (eq_asciicase(&comparator_name, &str_iascii_numeric, FALSE))
- {
- *c = COMP_ASCII_NUMERIC;
- match = 1;
- }
- else
- {
- filter->errmsg = CUS "invalid comparator";
- match = -1;
- }
- return match;
- }
- }
-}
-
-
-/*************************************************
-* Parse an optional match type *
-*************************************************/
-
-/*
-Grammar:
- match-type = ":is" / ":contains" / ":matches"
-
-Arguments:
- filter points to the Sieve filter including its state
- m returns match type
-
-Returns: 1 success
- 0 no match type found
-*/
-
-static int
-parse_matchtype(struct Sieve *filter, enum MatchType *m)
-{
-if (parse_identifier(filter, CUS ":is") == 1)
-{
- *m = MATCH_IS;
- return 1;
-}
-else if (parse_identifier(filter, CUS ":contains") == 1)
-{
- *m = MATCH_CONTAINS;
- return 1;
-}
-else if (parse_identifier(filter, CUS ":matches") == 1)
-{
- *m = MATCH_MATCHES;
- return 1;
-}
-else return 0;
-}
-
-
-/*************************************************
-* Parse and interpret an optional test list *
-*************************************************/
-
-/*
-Grammar:
- test-list = "(" test *("," test) ")"
-
-Arguments:
- filter points to the Sieve filter including its state
- n total number of tests
- num_true number of passed tests
- exec Execute parsed statements
-
-Returns: 1 success
- 0 no test list found
- -1 syntax or execution error
-*/
-
-static int
-parse_testlist(struct Sieve *filter, int *n, int *num_true, int exec)
-{
-if (parse_white(filter) == -1) return -1;
-if (*filter->pc == '(')
- {
- ++filter->pc;
- *n = 0;
- *num_true = 0;
- for (;;)
- {
- int cond;
-
- switch (parse_test(filter, &cond, exec))
- {
- case -1: return -1;
- case 0: filter->errmsg = CUS "missing test"; return -1;
- default: ++*n; if (cond) ++*num_true; break;
- }
- if (parse_white(filter) == -1) return -1;
- if (*filter->pc == ',') ++filter->pc;
- else break;
- }
- if (*filter->pc == ')')
- {
- ++filter->pc;
- return 1;
- }
- else
- {
- filter->errmsg = CUS "missing closing paren";
- return -1;
- }
- }
-else return 0;
-}
-
-
-/*************************************************
-* Parse and interpret an optional test *
-*************************************************/
-
-/*
-Arguments:
- filter points to the Sieve filter including its state
- cond returned condition status
- exec Execute parsed statements
-
-Returns: 1 success
- 0 no test found
- -1 syntax or execution error
-*/
-
-static int
-parse_test(struct Sieve *filter, int *cond, int exec)
-{
-if (parse_white(filter) == -1) return -1;
-if (parse_identifier(filter, CUS "address"))
- {
- /*
- address-test = "address" { [address-part] [comparator] [match-type] }
- <header-list: string-list> <key-list: string-list>
-
- header-list From, To, Cc, Bcc, Sender, Resent-From, Resent-To
- */
-
- enum AddressPart addressPart = ADDRPART_ALL;
- enum Comparator comparator = COMP_EN_ASCII_CASEMAP;
- enum MatchType matchType = MATCH_IS;
- gstring *hdr, *key;
- int m;
- int ap = 0, co = 0, mt = 0;
-
- for (;;)
- {
- if (parse_white(filter) == -1) return -1;
- if ((m = parse_addresspart(filter, &addressPart)) != 0)
- {
- if (m == -1) return -1;
- if (ap)
- {
- filter->errmsg = CUS "address part already specified";
- return -1;
- }
- else ap = 1;
- }
- else if ((m = parse_comparator(filter, &comparator)) != 0)
- {
- if (m == -1) return -1;
- if (co)
- {
- filter->errmsg = CUS "comparator already specified";
- return -1;
- }
- else co = 1;
- }
- else if ((m = parse_matchtype(filter, &matchType)) != 0)
- {
- if (m == -1) return -1;
- if (mt)
- {
- filter->errmsg = CUS "match type already specified";
- return -1;
- }
- else mt = 1;
- }
- else break;
- }
- if (parse_white(filter) == -1)
- return -1;
- if ((m = parse_stringlist(filter, &hdr)) != 1)
- {
- if (m == 0) filter->errmsg = CUS "header string list expected";
- return -1;
- }
- if (parse_white(filter) == -1)
- return -1;
- if ((m = parse_stringlist(filter, &key)) != 1)
- {
- if (m == 0) filter->errmsg = CUS "key string list expected";
- return -1;
- }
- *cond = 0;
- for (gstring * h = hdr; h->ptr != -1 && !*cond; ++h)
- {
- uschar * header_value = NULL, * extracted_addr, * end_addr;
-
- if ( !eq_asciicase(h, &str_from, FALSE)
- && !eq_asciicase(h, &str_to, FALSE)
- && !eq_asciicase(h, &str_cc, FALSE)
- && !eq_asciicase(h, &str_bcc, FALSE)
- && !eq_asciicase(h, &str_sender, FALSE)
- && !eq_asciicase(h, &str_resent_from, FALSE)
- && !eq_asciicase(h, &str_resent_to, FALSE)
- )
- {
- filter->errmsg = CUS "invalid header field";
- return -1;
- }
- if (exec)
- {
- /* We are only interested in addresses below, so no MIME decoding */
- if (!(header_value = expand_string(string_sprintf("$rheader_%s", quote(h)))))
- {
- filter->errmsg = CUS "header string expansion failed";
- return -1;
- }
- f.parse_allow_group = TRUE;
- while (*header_value && !*cond)
- {
- uschar *error;
- int start, end, domain;
- int saveend;
- uschar *part = NULL;
-
- end_addr = parse_find_address_end(header_value, FALSE);
- saveend = *end_addr;
- *end_addr = 0;
- extracted_addr = parse_extract_address(header_value, &error, &start, &end, &domain, FALSE);
-
- if (extracted_addr) switch (addressPart)
- {
- case ADDRPART_ALL: part = extracted_addr; break;
-#ifdef SUBADDRESS
- case ADDRPART_USER:
-#endif
- 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; break;
-#endif
- }
-
- *end_addr = saveend;
- if (part && extracted_addr)
- {
- gstring partStr = {.s = part, .ptr = Ustrlen(part), .size = Ustrlen(part)+1};
- for (gstring * k = key; k->ptr != - 1; ++k)
- {
- *cond = compare(filter, k, &partStr, comparator, matchType);
- if (*cond == -1) return -1;
- if (*cond) break;
- }
- }
-
- if (saveend == 0) break;
- header_value = end_addr + 1;
- }
- f.parse_allow_group = FALSE;
- f.parse_found_group = FALSE;
- }
- }
- return 1;
- }
-else if (parse_identifier(filter, CUS "allof"))
- {
- /*
- allof-test = "allof" <tests: test-list>
- */
-
- int n, num_true;
-
- switch (parse_testlist(filter, &n, &num_true, exec))
- {
- case -1: return -1;
- case 0: filter->errmsg = CUS "missing test list"; return -1;
- default: *cond = (n == num_true); return 1;
- }
- }
-else if (parse_identifier(filter, CUS "anyof"))
- {
- /*
- anyof-test = "anyof" <tests: test-list>
- */
-
- int n, num_true;
-
- switch (parse_testlist(filter, &n, &num_true, exec))
- {
- case -1: return -1;
- case 0: filter->errmsg = CUS "missing test list"; return -1;
- default: *cond = (num_true>0); return 1;
- }
- }
-else if (parse_identifier(filter, CUS "exists"))
- {
- /*
- exists-test = "exists" <header-names: string-list>
- */
-
- gstring *hdr;
- int m;
-
- if (parse_white(filter) == -1)
- return -1;
- if ((m = parse_stringlist(filter, &hdr)) != 1)
- {
- if (m == 0) filter->errmsg = CUS "header string list expected";
- return -1;
- }
- if (exec)
- {
- *cond = 1;
- for (gstring * h = hdr; h->ptr != -1 && *cond; ++h)
- {
- uschar *header_def;
-
- header_def = expand_string(string_sprintf("${if def:header_%s {true}{false}}", quote(h)));
- if (!header_def)
- {
- filter->errmsg = CUS "header string expansion failed";
- return -1;
- }
- if (Ustrcmp(header_def,"false") == 0) *cond = 0;
- }
- }
- return 1;
- }
-else if (parse_identifier(filter, CUS "false"))
- {
- /*
- false-test = "false"
- */
-
- *cond = 0;
- return 1;
- }
-else if (parse_identifier(filter, CUS "header"))
- {
- /*
- header-test = "header" { [comparator] [match-type] }
- <header-names: string-list> <key-list: string-list>
- */
-
- enum Comparator comparator = COMP_EN_ASCII_CASEMAP;
- enum MatchType matchType = MATCH_IS;
- gstring *hdr, *key;
- int m;
- int co = 0, mt = 0;
-
- for (;;)
- {
- if (parse_white(filter) == -1)
- return -1;
- if ((m = parse_comparator(filter, &comparator)) != 0)
- {
- if (m == -1) return -1;
- if (co)
- {
- filter->errmsg = CUS "comparator already specified";
- return -1;
- }
- else co = 1;
- }
- else if ((m = parse_matchtype(filter, &matchType)) != 0)
- {
- if (m == -1) return -1;
- if (mt)
- {
- filter->errmsg = CUS "match type already specified";
- return -1;
- }
- else mt = 1;
- }
- else break;
- }
- if (parse_white(filter) == -1)
- return -1;
- if ((m = parse_stringlist(filter, &hdr)) != 1)
- {
- if (m == 0) filter->errmsg = CUS "header string list expected";
- return -1;
- }
- if (parse_white(filter) == -1)
- return -1;
- if ((m = parse_stringlist(filter, &key)) != 1)
- {
- if (m == 0) filter->errmsg = CUS "key string list expected";
- return -1;
- }
- *cond = 0;
- for (gstring * h = hdr; h->ptr != -1 && !*cond; ++h)
- {
- if (!is_header(h))
- {
- filter->errmsg = CUS "invalid header field";
- return -1;
- }
- if (exec)
- {
- gstring header_value;
- uschar *header_def;
-
- expand_header(&header_value, h);
- header_def = expand_string(string_sprintf("${if def:header_%s {true}{false}}", quote(h)));
- if (!header_value.s || !header_def)
- {
- filter->errmsg = CUS "header string expansion failed";
- return -1;
- }
- for (gstring * k = key; k->ptr != -1; ++k)
- if (Ustrcmp(header_def,"true") == 0)
- {
- *cond = compare(filter, k, &header_value, comparator, matchType);
- if (*cond == -1) return -1;
- if (*cond) break;
- }
- }
- }
- return 1;
- }
-else if (parse_identifier(filter, CUS "not"))
- {
- if (parse_white(filter) == -1) return -1;
- switch (parse_test(filter, cond, exec))
- {
- case -1: return -1;
- case 0: filter->errmsg = CUS "missing test"; return -1;
- default: *cond = !*cond; return 1;
- }
- }
-else if (parse_identifier(filter, CUS "size"))
- {
- /*
- relop = ":over" / ":under"
- size-test = "size" relop <limit: number>
- */
-
- unsigned long limit;
- int overNotUnder;
-
- if (parse_white(filter) == -1) return -1;
- if (parse_identifier(filter, CUS ":over")) overNotUnder = 1;
- else if (parse_identifier(filter, CUS ":under")) overNotUnder = 0;
- else
- {
- filter->errmsg = CUS "missing :over or :under";
- return -1;
- }
- if (parse_white(filter) == -1) return -1;
- if (parse_number(filter, &limit) == -1) return -1;
- *cond = (overNotUnder ? (message_size>limit) : (message_size<limit));
- return 1;
- }
-else if (parse_identifier(filter, CUS "true"))
- {
- *cond = 1;
- return 1;
- }
-else if (parse_identifier(filter, CUS "envelope"))
- {
- /*
- envelope-test = "envelope" { [comparator] [address-part] [match-type] }
- <envelope-part: string-list> <key-list: string-list>
-
- envelope-part is case insensitive "from" or "to"
-#ifdef ENVELOPE_AUTH
- envelope-part = / "auth"
-#endif
- */
-
- enum Comparator comparator = COMP_EN_ASCII_CASEMAP;
- enum AddressPart addressPart = ADDRPART_ALL;
- enum MatchType matchType = MATCH_IS;
- gstring *env, *key;
- int m;
- int co = 0, ap = 0, mt = 0;
-
- if (!filter->require_envelope)
- {
- filter->errmsg = CUS "missing previous require \"envelope\";";
- return -1;
- }
- for (;;)
- {
- if (parse_white(filter) == -1) return -1;
- if ((m = parse_comparator(filter, &comparator)) != 0)
- {
- if (m == -1) return -1;
- if (co)
- {
- filter->errmsg = CUS "comparator already specified";
- return -1;
- }
- else co = 1;
- }
- else if ((m = parse_addresspart(filter, &addressPart)) != 0)
- {
- if (m == -1) return -1;
- if (ap)
- {
- filter->errmsg = CUS "address part already specified";
- return -1;
- }
- else ap = 1;
- }
- else if ((m = parse_matchtype(filter, &matchType)) != 0)
- {
- if (m == -1) return -1;
- if (mt)
- {
- filter->errmsg = CUS "match type already specified";
- return -1;
- }
- else mt = 1;
- }
- else break;
- }
- if (parse_white(filter) == -1)
- return -1;
- if ((m = parse_stringlist(filter, &env)) != 1)
- {
- if (m == 0) filter->errmsg = CUS "envelope string list expected";
- return -1;
- }
- if (parse_white(filter) == -1)
- return -1;
- if ((m = parse_stringlist(filter, &key)) != 1)
- {
- if (m == 0) filter->errmsg = CUS "key string list expected";
- return -1;
- }
- *cond = 0;
- for (gstring * e = env; e->ptr != -1 && !*cond; ++e)
- {
- const uschar *envelopeExpr = CUS 0;
- uschar *envelope = US 0;
-
- if (eq_asciicase(e, &str_from, FALSE))
- {
- switch (addressPart)
- {
- case ADDRPART_ALL: envelopeExpr = CUS "$sender_address"; break;
-#ifdef SUBADDRESS
- case ADDRPART_USER:
-#endif
- 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;
-#endif
- }
- }
- else if (eq_asciicase(e, &str_to, FALSE))
- {
- switch (addressPart)
- {
- case ADDRPART_ALL: envelopeExpr = CUS "$local_part_prefix$local_part$local_part_suffix@$domain"; break;
-#ifdef SUBADDRESS
- 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;
- }
- }
-#ifdef ENVELOPE_AUTH
- else if (eq_asciicase(e, &str_auth, FALSE))
- {
- switch (addressPart)
- {
- case ADDRPART_ALL: envelopeExpr = CUS "$authenticated_sender"; break;
-#ifdef SUBADDRESS
- case ADDRPART_USER:
-#endif
- case ADDRPART_LOCALPART: envelopeExpr = CUS "${local_part:$authenticated_sender}"; break;
- case ADDRPART_DOMAIN: envelopeExpr = CUS "${domain:$authenticated_sender}"; break;
-#ifdef SUBADDRESS
- case ADDRPART_DETAIL: envelopeExpr = CUS 0; break;
-#endif
- }
- }
-#endif
- else
- {
- filter->errmsg = CUS "invalid envelope string";
- return -1;
- }
- if (exec && envelopeExpr)
- {
- if (!(envelope = expand_string(US envelopeExpr)))
- {
- filter->errmsg = CUS "header string expansion failed";
- return -1;
- }
- for (gstring * k = key; k->ptr != -1; ++k)
- {
- gstring envelopeStr = {.s = envelope, .ptr = Ustrlen(envelope), .size = Ustrlen(envelope)+1};
-
- *cond = compare(filter, k, &envelopeStr, comparator, matchType);
- if (*cond == -1) return -1;
- if (*cond) break;
- }
- }
- }
- return 1;
- }
-#ifdef ENOTIFY
-else if (parse_identifier(filter, CUS "valid_notify_method"))
- {
- /*
- valid_notify_method = "valid_notify_method"
- <notification-uris: string-list>
- */
-
- gstring *uris;
- int m;
-
- if (!filter->require_enotify)
- {
- filter->errmsg = CUS "missing previous require \"enotify\";";
- return -1;
- }
- if (parse_white(filter) == -1)
- return -1;
- if ((m = parse_stringlist(filter, &uris)) != 1)
- {
- if (m == 0) filter->errmsg = CUS "URI string list expected";
- return -1;
- }
- if (exec)
- {
- *cond = 1;
- for (gstring * u = uris; u->ptr != -1 && *cond; ++u)
- {
- string_item * recipient = NULL;
- gstring header = { .s = NULL, .ptr = -1 };
- gstring subject = { .s = NULL, .ptr = -1 };
- gstring body = { .s = NULL, .ptr = -1 };
-
- if (parse_mailto_uri(filter, u->s, &recipient, &header, &subject, &body) != 1)
- *cond = 0;
- }
- }
- return 1;
- }
-else if (parse_identifier(filter, CUS "notify_method_capability"))
- {
- /*
- notify_method_capability = "notify_method_capability" [COMPARATOR] [MATCH-TYPE]
- <notification-uri: string>
- <notification-capability: string>
- <key-list: string-list>
- */
-
- int m;
- int co = 0, mt = 0;
-
- enum Comparator comparator = COMP_EN_ASCII_CASEMAP;
- enum MatchType matchType = MATCH_IS;
- gstring uri, capa, *keys;
-
- if (!filter->require_enotify)
- {
- filter->errmsg = CUS "missing previous require \"enotify\";";
- return -1;
- }
- for (;;)
- {
- if (parse_white(filter) == -1) return -1;
- if ((m = parse_comparator(filter, &comparator)) != 0)
- {
- if (m == -1) return -1;
- if (co)
- {
- filter->errmsg = CUS "comparator already specified";
- return -1;
- }
- else co = 1;
- }
- else if ((m = parse_matchtype(filter, &matchType)) != 0)
- {
- if (m == -1) return -1;
- if (mt)
- {
- filter->errmsg = CUS "match type already specified";
- return -1;
- }
- else mt = 1;
- }
- else break;
- }
- if ((m = parse_string(filter, &uri)) != 1)
- {
- if (m == 0) filter->errmsg = CUS "missing notification URI string";
- return -1;
- }
- if (parse_white(filter) == -1)
- return -1;
- if ((m = parse_string(filter, &capa)) != 1)
- {
- if (m == 0) filter->errmsg = CUS "missing notification capability string";
- return -1;
- }
- if (parse_white(filter) == -1)
- return -1;
- if ((m = parse_stringlist(filter, &keys)) != 1)
- {
- if (m == 0) filter->errmsg = CUS "missing key string list";
- return -1;
- }
- if (exec)
- {
- string_item * recipient = NULL;
- gstring header = { .s = NULL, .ptr = -1 };
- gstring subject = { .s = NULL, .ptr = -1 };
- gstring body = { .s = NULL, .ptr = -1 };
-
- *cond = 0;
- if (parse_mailto_uri(filter, uri.s, &recipient, &header, &subject, &body) == 1)
- if (eq_asciicase(&capa, &str_online, FALSE) == 1)
- for (gstring * k = keys; k->ptr != -1; ++k)
- {
- *cond = compare(filter, k, &str_maybe, comparator, matchType);
- if (*cond == -1) return -1;
- if (*cond) break;
- }
- }
- return 1;
- }
-#endif
-else return 0;
-}
-
-
-/*************************************************
-* Parse and interpret an optional block *
-*************************************************/
-
-/*
-Arguments:
- filter points to the Sieve filter including its state
- exec Execute parsed statements
- generated where to hang newly-generated addresses
-
-Returns: 2 success by stop
- 1 other success
- 0 no block command found
- -1 syntax or execution error
-*/
-
-static int
-parse_block(struct Sieve * filter, int exec, address_item ** generated)
-{
-int r;
-
-if (parse_white(filter) == -1)
- return -1;
-if (*filter->pc == '{')
- {
- ++filter->pc;
- if ((r = parse_commands(filter, exec, generated)) == -1 || r == 2) return r;
- if (*filter->pc == '}')
- {
- ++filter->pc;
- return 1;
- }
- filter->errmsg = CUS "expecting command or closing brace";
- return -1;
- }
-return 0;
-}
-
-
-/*************************************************
-* Match a semicolon *
-*************************************************/
-
-/*
-Arguments:
- filter points to the Sieve filter including its state
-
-Returns: 1 success
- -1 syntax error
-*/
-
-static int
-parse_semicolon(struct Sieve *filter)
-{
-if (parse_white(filter) == -1)
- return -1;
-if (*filter->pc == ';')
- {
- ++filter->pc;
- return 1;
- }
-filter->errmsg = CUS "missing semicolon";
-return -1;
-}
-
-
-/*************************************************
-* Parse and interpret a Sieve command *
-*************************************************/
-
-/*
-Arguments:
- filter points to the Sieve filter including its state
- exec Execute parsed statements
- generated where to hang newly-generated addresses
-
-Returns: 2 success by stop
- 1 other success
- -1 syntax or execution error
-*/
-static int
-parse_commands(struct Sieve *filter, int exec, address_item **generated)
-{
-while (*filter->pc)
- {
- if (parse_white(filter) == -1)
- return -1;
- if (parse_identifier(filter, CUS "if"))
- {
- /*
- if-command = "if" test block *( "elsif" test block ) [ else block ]
- */
-
- int cond, m, unsuccessful;
-
- /* test block */
- if (parse_white(filter) == -1)
- return -1;
- if ((m = parse_test(filter, &cond, exec)) == -1)
- return -1;
- if (m == 0)
- {
- filter->errmsg = CUS "missing test";
- return -1;
- }
- if ((filter_test != FTEST_NONE && debug_selector != 0) ||
- (debug_selector & D_filter) != 0)
- {
- if (exec) debug_printf_indent("if %s\n", cond?"true":"false");
- }
- m = parse_block(filter, exec ? cond : 0, generated);
- if (m == -1 || m == 2)
- return m;
- if (m == 0)
- {
- filter->errmsg = CUS "missing block";
- return -1;
- }
- unsuccessful = !cond;
- for (;;) /* elsif test block */
- {
- if (parse_white(filter) == -1)
- return -1;
- if (parse_identifier(filter, CUS "elsif"))
- {
- if (parse_white(filter) == -1)
- return -1;
- m = parse_test(filter, &cond, exec && unsuccessful);
- if (m == -1 || m == 2)
- return m;
- if (m == 0)
- {
- filter->errmsg = CUS "missing test";
- return -1;
- }
- if ((filter_test != FTEST_NONE && debug_selector != 0) ||
- (debug_selector & D_filter) != 0)
- {
- if (exec) debug_printf_indent("elsif %s\n", cond?"true":"false");
- }
- m = parse_block(filter, exec && unsuccessful ? cond : 0, generated);
- if (m == -1 || m == 2)
- return m;
- if (m == 0)
- {
- filter->errmsg = CUS "missing block";
- return -1;
- }
- if (exec && unsuccessful && cond)
- unsuccessful = 0;
- }
- else break;
- }
- /* else block */
- if (parse_white(filter) == -1)
- return -1;
- if (parse_identifier(filter, CUS "else"))
- {
- m = parse_block(filter, exec && unsuccessful, generated);
- if (m == -1 || m == 2)
- return m;
- if (m == 0)
- {
- filter->errmsg = CUS "missing block";
- return -1;
- }
- }
- }
- else if (parse_identifier(filter, CUS "stop"))
- {
- /*
- stop-command = "stop" { stop-options } ";"
- stop-options =
- */
-
- if (parse_semicolon(filter) == -1)
- return -1;
- if (exec)
- {
- filter->pc += Ustrlen(filter->pc);
- return 2;
- }
- }
- else if (parse_identifier(filter, CUS "keep"))
- {
- /*
- keep-command = "keep" { keep-options } ";"
- keep-options =
- */
-
- if (parse_semicolon(filter) == -1)
- return -1;
- if (exec)
- {
- add_addr(generated, filter->inbox, 1, 0, 0, 0);
- filter->keep = 0;
- }
- }
- else if (parse_identifier(filter, CUS "discard"))
- {
- /*
- discard-command = "discard" { discard-options } ";"
- discard-options =
- */
-
- if (parse_semicolon(filter) == -1)
- return -1;
- if (exec) filter->keep = 0;
- }
- else if (parse_identifier(filter, CUS "redirect"))
- {
- /*
- redirect-command = "redirect" redirect-options "string" ";"
- redirect-options =
- redirect-options = ) ":copy"
- */
-
- gstring recipient;
- int m;
- int copy = 0;
-
- for (;;)
- {
- if (parse_white(filter) == -1)
- return -1;
- if (parse_identifier(filter, CUS ":copy") == 1)
- {
- if (!filter->require_copy)
- {
- filter->errmsg = CUS "missing previous require \"copy\";";
- return -1;
- }
- copy = 1;
- }
- else break;
- }
- if (parse_white(filter) == -1)
- return -1;
- if ((m = parse_string(filter, &recipient)) != 1)
- {
- if (m == 0)
- filter->errmsg = CUS "missing redirect recipient string";
- return -1;
- }
- if (strchr(CCS recipient.s, '@') == NULL)
- {
- filter->errmsg = CUS "unqualified recipient address";
- return -1;
- }
- if (exec)
- {
- add_addr(generated, recipient.s, 0, 0, 0, 0);
- if (!copy) filter->keep = 0;
- }
- if (parse_semicolon(filter) == -1) return -1;
- }
- else if (parse_identifier(filter, CUS "fileinto"))
- {
- /*
- fileinto-command = "fileinto" { fileinto-options } string ";"
- fileinto-options =
- fileinto-options = ) [ ":copy" ]
- */
-
- gstring folder;
- uschar *s;
- int m;
- unsigned long maxage, maxmessages, maxstorage;
- int copy = 0;
-
- maxage = maxmessages = maxstorage = 0;
- if (!filter->require_fileinto)
- {
- filter->errmsg = CUS "missing previous require \"fileinto\";";
- return -1;
- }
- for (;;)
- {
- if (parse_white(filter) == -1)
- return -1;
- if (parse_identifier(filter, CUS ":copy") == 1)
- {
- if (!filter->require_copy)
- {
- filter->errmsg = CUS "missing previous require \"copy\";";
- return -1;
- }
- copy = 1;
- }
- else break;
- }
- if (parse_white(filter) == -1)
- return -1;
- if ((m = parse_string(filter, &folder)) != 1)
- {
- if (m == 0) filter->errmsg = CUS "missing fileinto folder string";
- return -1;
- }
- m = 0; s = folder.s;
- if (folder.ptr == 0)
- m = 1;
- if (Ustrcmp(s,"..") == 0 || Ustrncmp(s,"../", 3) == 0)
- m = 1;
- else while (*s)
- {
- if (Ustrcmp(s,"/..") == 0 || Ustrncmp(s,"/../", 4) == 0) { m = 1; break; }
- ++s;
- }
- if (m)
- {
- filter->errmsg = CUS "invalid folder";
- return -1;
- }
- if (exec)
- {
- add_addr(generated, folder.s, 1, maxage, maxmessages, maxstorage);
- if (!copy) filter->keep = 0;
- }
- if (parse_semicolon(filter) == -1)
- return -1;
- }
-#ifdef ENOTIFY
- else if (parse_identifier(filter, CUS "notify"))
- {
- /*
- notify-command = "notify" { notify-options } <method: string> ";"
- notify-options = [":from" string]
- [":importance" <"1" / "2" / "3">]
- [":options" 1*(string-list / number)]
- [":message" string]
- */
-
- int m;
- gstring from = { .s = NULL, .ptr = -1 };
- gstring importance = { .s = NULL, .ptr = -1 };
- gstring message = { .s = NULL, .ptr = -1 };
- gstring method;
- struct Notification *already;
- string_item * recipient = NULL;
- gstring header = { .s = NULL, .ptr = -1 };
- gstring subject = { .s = NULL, .ptr = -1 };
- gstring body = { .s = NULL, .ptr = -1 };
- uschar *envelope_from;
- gstring auto_submitted_value;
- uschar *auto_submitted_def;
-
- if (!filter->require_enotify)
- {
- filter->errmsg = CUS "missing previous require \"enotify\";";
- return -1;
- }
- envelope_from = sender_address && sender_address[0]
- ? expand_string(US"$local_part_prefix$local_part$local_part_suffix@$domain") : US "";
- if (!envelope_from)
- {
- filter->errmsg = CUS "expansion failure for envelope from";
- return -1;
- }
- for (;;)
- {
- if (parse_white(filter) == -1)
- return -1;
- if (parse_identifier(filter, CUS ":from") == 1)
- {
- 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;
- }
- }
- else if (parse_identifier(filter, CUS ":importance") == 1)
- {
- if (parse_white(filter) == -1)
- return -1;
- if ((m = parse_string(filter, &importance)) != 1)
- {
- if (m == 0)
- filter->errmsg = CUS "importance string expected";
- return -1;
- }
- if (importance.ptr != 1 || importance.s[0] < '1' || importance.s[0] > '3')
- {
- filter->errmsg = CUS "invalid importance";
- return -1;
- }
- }
- else if (parse_identifier(filter, CUS ":options") == 1)
- {
- if (parse_white(filter) == -1)
- return -1;
- }
- else if (parse_identifier(filter, CUS ":message") == 1)
- {
- if (parse_white(filter) == -1)
- return -1;
- if ((m = parse_string(filter, &message)) != 1)
- {
- if (m == 0)
- filter->errmsg = CUS "message string expected";
- return -1;
- }
- }
- else break;
- }
- if (parse_white(filter) == -1)
- return -1;
- if ((m = parse_string(filter, &method)) != 1)
- {
- if (m == 0)
- filter->errmsg = CUS "missing method string";
- return -1;
- }
- if (parse_semicolon(filter) == -1)
- return -1;
- if (parse_mailto_uri(filter, method.s, &recipient, &header, &subject, &body) != 1)
- return -1;
- if (exec)
- {
- if (message.ptr == -1)
- message = subject;
- if (message.ptr == -1)
- expand_header(&message, &str_subject);
- expand_header(&auto_submitted_value, &str_auto_submitted);
- auto_submitted_def = expand_string(US"${if def:header_auto-submitted {true}{false}}");
- if (!auto_submitted_value.s || !auto_submitted_def)
- {
- filter->errmsg = CUS "header string expansion failed";
- return -1;
- }
- if (Ustrcmp(auto_submitted_def,"true") != 0 || Ustrcmp(auto_submitted_value.s,"no") == 0)
- {
- for (already = filter->notified; already; already = already->next)
- {
- if ( already->method.ptr == method.ptr
- && (method.ptr == -1 || Ustrcmp(already->method.s, method.s) == 0)
- && already->importance.ptr == importance.ptr
- && (importance.ptr == -1 || Ustrcmp(already->importance.s, importance.s) == 0)
- && already->message.ptr == message.ptr
- && (message.ptr == -1 || Ustrcmp(already->message.s, message.s) == 0))
- break;
- }
- if (!already)
- /* New notification, process it */
- {
- struct Notification * sent = store_get(sizeof(struct Notification), GET_UNTAINTED);
- sent->method = method;
- sent->importance = importance;
- sent->message = message;
- sent->next = filter->notified;
- filter->notified = sent;
- #ifndef COMPILE_SYNTAX_CHECKER
- if (filter_test == FTEST_NONE)
- {
- int pid, fd;
-
- if ((pid = child_open_exim2(&fd, envelope_from, envelope_from,
- US"sieve-notify")) >= 1)
- {
- FILE * f = fdopen(fd, "wb");
-
- fprintf(f,"From: %s\n", from.ptr == -1
- ? expand_string(US"$local_part_prefix$local_part$local_part_suffix@$domain")
- : from.s);
- for (string_item * p = recipient; p; p = p->next)
- fprintf(f, "To: %s\n", p->text);
- fprintf(f, "Auto-Submitted: auto-notified; %s\n", filter->enotify_mailto_owner);
- if (header.ptr > 0) fprintf(f, "%s", header.s);
- if (message.ptr == -1)
- {
- message.s = US"Notification";
- message.ptr = Ustrlen(message.s);
- }
- if (message.ptr != -1)
- fprintf(f, "Subject: %s\n", parse_quote_2047(message.s,
- message.ptr, US"utf-8", TRUE));
- fprintf(f,"\n");
- if (body.ptr > 0) fprintf(f, "%s\n", body.s);
- fflush(f);
- (void)fclose(f);
- (void)child_close(pid, 0);
- }
- }
- if ((filter_test != FTEST_NONE && debug_selector != 0) || debug_selector & D_filter)
- debug_printf_indent("Notification to `%s': '%s'.\n", method.s, message.ptr != -1 ? message.s : CUS "");
-#endif
- }
- else
- if ((filter_test != FTEST_NONE && debug_selector != 0) || debug_selector & D_filter)
- debug_printf_indent("Repeated notification to `%s' ignored.\n", method.s);
- }
- else
- if ((filter_test != FTEST_NONE && debug_selector != 0) || debug_selector & D_filter)
- debug_printf_indent("Ignoring notification, triggering message contains Auto-submitted: field.\n");
- }
- }
-#endif
-#ifdef VACATION
- else if (parse_identifier(filter, CUS "vacation"))
- {
- /*
- vacation-command = "vacation" { vacation-options } <reason: string> ";"
- vacation-options = [":days" number]
- [":subject" string]
- [":from" string]
- [":addresses" string-list]
- [":mime"]
- [":handle" string]
- */
-
- int m;
- unsigned long days;
- gstring subject;
- gstring from;
- gstring *addresses;
- int reason_is_mime;
- string_item *aliases;
- gstring handle;
- gstring reason;
-
- if (!filter->require_vacation)
- {
- filter->errmsg = CUS "missing previous require \"vacation\";";
- return -1;
- }
- if (exec)
- {
- if (filter->vacation_ran)
- {
- filter->errmsg = CUS "trying to execute vacation more than once";
- return -1;
- }
- filter->vacation_ran = TRUE;
- }
- days = VACATION_MIN_DAYS>7 ? VACATION_MIN_DAYS : 7;
- subject.s = (uschar*)0;
- subject.ptr = -1;
- from.s = (uschar*)0;
- from.ptr = -1;
- addresses = (gstring*)0;
- aliases = NULL;
- reason_is_mime = 0;
- handle.s = (uschar*)0;
- handle.ptr = -1;
- for (;;)
- {
- if (parse_white(filter) == -1)
- return -1;
- if (parse_identifier(filter, CUS ":days") == 1)
- {
- if (parse_white(filter) == -1)
- return -1;
- if (parse_number(filter, &days) == -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)
- {
- 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 (check_mail_address(filter, &from) != 1)
- return -1;
- }
- else if (parse_identifier(filter, CUS ":addresses") == 1)
- {
- if (parse_white(filter) == -1)
- return -1;
- if ((m = parse_stringlist(filter, &addresses)) != 1)
- {
- if (m == 0)
- filter->errmsg = CUS "addresses string list expected";
- return -1;
- }
- for (gstring * a = addresses; a->ptr != -1; ++a)
- {
- string_item * new = store_get(sizeof(string_item), GET_UNTAINTED);
-
- new->text = store_get(a->ptr+1, a->s);
- if (a->ptr) memcpy(new->text, a->s, a->ptr);
- new->text[a->ptr] = '\0';
- new->next = aliases;
- aliases = new;
- }
- }
- 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, &from)) != 1)
- {
- if (m == 0)
- filter->errmsg = CUS "handle string expected";
- return -1;
- }
- }
- else break;
- }
- if (parse_white(filter) == -1)
- return -1;
- if ((m = parse_string(filter, &reason)) != 1)
- {
- if (m == 0)
- filter->errmsg = CUS "missing reason string";
- return -1;
- }
- if (reason_is_mime)
- {
- uschar *s, *end;
-
- for (s = reason.s, end = reason.s + reason.ptr;
- 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)
- {
- address_item *addr;
- md5 base;
- uschar digest[16];
- uschar hexdigest[33];
- gstring * once;
-
- if (filter_personal(aliases, TRUE))
- {
- if (filter_test == FTEST_NONE)
- {
- /* ensure oncelog directory exists; failure will be detected later */
-
- (void)directory_make(NULL, filter->vacation_directory, 0700, FALSE);
- }
- /* build oncelog filename */
-
- md5_start(&base);
-
- if (handle.ptr == -1)
- {
- gstring * key = NULL;
- if (subject.ptr != -1)
- key = string_catn(key, subject.s, subject.ptr);
- if (from.ptr != -1)
- key = string_catn(key, from.s, from.ptr);
- key = string_catn(key, reason_is_mime?US"1":US"0", 1);
- key = string_catn(key, reason.s, reason.ptr);
- md5_end(&base, key->s, key->ptr, digest);
- }
- else
- md5_end(&base, handle.s, handle.ptr, digest);
-
- for (int i = 0; i < 16; i++)
- sprintf(CS (hexdigest+2*i), "%02X", digest[i]);
-
- if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
- debug_printf_indent("Sieve: mail was personal, vacation file basename: %s\n", hexdigest);
-
- if (filter_test == FTEST_NONE)
- {
- once = string_cat (NULL, filter->vacation_directory);
- once = string_catn(once, US"/", 1);
- once = string_catn(once, hexdigest, 33);
-
- /* process subject */
-
- if (subject.ptr == -1)
- {
- uschar * subject_def;
-
- subject_def = expand_string(US"${if def:header_subject {true}{false}}");
- if (subject_def && Ustrcmp(subject_def,"true") == 0)
- {
- gstring * g = string_catn(NULL, US"Auto: ", 6);
-
- expand_header(&subject, &str_subject);
- g = string_catn(g, subject.s, subject.ptr);
- subject.ptr = len_string_from_gstring(g, &subject.s);
- }
- else
- {
- subject.s = US"Automated reply";
- subject.ptr = Ustrlen(subject.s);
- }
- }
-
- /* add address to list of generated addresses */
-
- addr = deliver_make_addr(string_sprintf(">%.256s", sender_address), FALSE);
- setflag(addr, af_pfr);
- addr->prop.ignore_error = TRUE;
- addr->next = *generated;
- *generated = addr;
- addr->reply = store_get(sizeof(reply_item), GET_UNTAINTED);
- memset(addr->reply, 0, sizeof(reply_item)); /* XXX */
- addr->reply->to = string_copy(sender_address);
- if (from.ptr == -1)
- addr->reply->from = expand_string(US"$local_part@$domain");
- else
- addr->reply->from = from.s;
- /* deconst cast safe as we pass in a non-const item */
- addr->reply->subject = US parse_quote_2047(subject.s, subject.ptr, US"utf-8", TRUE);
- addr->reply->oncelog = string_from_gstring(once);
- addr->reply->once_repeat = days*86400;
-
- /* build body and MIME headers */
-
- if (reason_is_mime)
- {
- uschar *mime_body, *reason_end;
- static const uschar nlnl[] = "\r\n\r\n";
-
- for
- (
- mime_body = reason.s, reason_end = reason.s + reason.ptr;
- mime_body < (reason_end-(sizeof(nlnl)-1)) && memcmp(mime_body, nlnl, (sizeof(nlnl)-1));
- ) mime_body++;
-
- addr->reply->headers = string_copyn(reason.s, mime_body-reason.s);
-
- if (mime_body+(sizeof(nlnl)-1)<reason_end)
- mime_body += (sizeof(nlnl)-1);
- else mime_body = reason_end-1;
- addr->reply->text = string_copyn(mime_body, reason_end-mime_body);
- }
- else
- {
- 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)->s;
- }
- }
- }
- else if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
- debug_printf_indent("Sieve: mail was not personal, vacation would ignore it\n");
- }
- }
- else break;
-#endif
- }
-return 1;
-}
-
-
-/*************************************************
-* Parse and interpret a sieve filter *
-*************************************************/
-
-/*
-Arguments:
- filter points to the Sieve filter including its state
- exec Execute parsed statements
- generated where to hang newly-generated addresses
-
-Returns: 1 success
- -1 syntax or execution error
-*/
-
-static int
-parse_start(struct Sieve *filter, int exec, address_item **generated)
-{
-filter->pc = filter->filter;
-filter->line = 1;
-filter->keep = 1;
-filter->require_envelope = 0;
-filter->require_fileinto = 0;
-#ifdef ENCODED_CHARACTER
-filter->require_encoded_character = FALSE;
-#endif
-#ifdef ENVELOPE_AUTH
-filter->require_envelope_auth = 0;
-#endif
-#ifdef ENOTIFY
-filter->require_enotify = 0;
-filter->notified = (struct Notification*)0;
-#endif
-#ifdef SUBADDRESS
-filter->require_subaddress = FALSE;
-#endif
-#ifdef VACATION
-filter->require_vacation = FALSE;
-filter->vacation_ran = 0; /*XXX missing init? */
-#endif
-filter->require_copy = FALSE;
-filter->require_iascii_numeric = FALSE;
-
-if (parse_white(filter) == -1) return -1;
-
-if (exec && filter->vacation_directory && filter_test == FTEST_NONE)
- {
- DIR *oncelogdir;
- struct dirent *oncelog;
- struct stat properties;
- time_t now;
-
- /* clean up old vacation log databases */
-
- if ( !(oncelogdir = exim_opendir(filter->vacation_directory))
- && errno != ENOENT)
- {
- filter->errmsg = CUS "unable to open vacation directory";
- return -1;
- }
-
- if (oncelogdir)
- {
- time(&now);
-
- while ((oncelog = readdir(oncelogdir)))
- if (strlen(oncelog->d_name) == 32)
- {
- uschar *s = string_sprintf("%s/%s", filter->vacation_directory, oncelog->d_name);
- if (Ustat(s, &properties) == 0 && properties.st_mtime+VACATION_MAX_DAYS*86400 < now)
- Uunlink(s);
- }
- closedir(oncelogdir);
- }
- }
-
-while (parse_identifier(filter, CUS "require"))
- {
- /*
- require-command = "require" <capabilities: string-list>
- */
-
- gstring *cap;
- int m;
-
- if (parse_white(filter) == -1) return -1;
- if ((m = parse_stringlist(filter, &cap)) != 1)
- {
- if (m == 0) filter->errmsg = CUS "capability string list expected";
- return -1;
- }
- for (gstring * check = cap; check->s; ++check)
- {
- if (eq_octet(check, &str_envelope, FALSE)) filter->require_envelope = 1;
- else if (eq_octet(check, &str_fileinto, FALSE)) filter->require_fileinto = 1;
-#ifdef ENCODED_CHARACTER
- else if (eq_octet(check, &str_encoded_character, FALSE)) filter->require_encoded_character = TRUE;
-#endif
-#ifdef ENVELOPE_AUTH
- else if (eq_octet(check, &str_envelope_auth, FALSE)) filter->require_envelope_auth = 1;
-#endif
-#ifdef ENOTIFY
- else if (eq_octet(check, &str_enotify, FALSE))
- {
- if (!filter->enotify_mailto_owner)
- {
- filter->errmsg = CUS "enotify disabled";
- return -1;
- }
- filter->require_enotify = 1;
- }
-#endif
-#ifdef SUBADDRESS
- else if (eq_octet(check, &str_subaddress, FALSE)) filter->require_subaddress = TRUE;
-#endif
-#ifdef VACATION
- else if (eq_octet(check, &str_vacation, FALSE))
- {
- if (filter_test == FTEST_NONE && !filter->vacation_directory)
- {
- filter->errmsg = CUS "vacation disabled";
- return -1;
- }
- filter->require_vacation = TRUE;
- }
-#endif
- else if (eq_octet(check, &str_copy, FALSE)) filter->require_copy = TRUE;
- else if (eq_octet(check, &str_comparator_ioctet, FALSE)) ;
- else if (eq_octet(check, &str_comparator_iascii_casemap, FALSE)) ;
- else if (eq_octet(check, &str_comparator_enascii_casemap, FALSE)) ;
- else if (eq_octet(check, &str_comparator_iascii_numeric, FALSE)) filter->require_iascii_numeric = TRUE;
- else
- {
- filter->errmsg = CUS "unknown capability";
- return -1;
- }
- }
- if (parse_semicolon(filter) == -1) return -1;
- }
- if (parse_commands(filter, exec, generated) == -1) return -1;
- if (*filter->pc)
- {
- filter->errmsg = CUS "syntax error";
- return -1;
- }
- return 1;
-}
-
-
-/*************************************************
-* Interpret a sieve filter file *
-*************************************************/
-
-/*
-Arguments:
- filter points to the entire file, read into store as a single string
- options controls whether various special things are allowed, and requests
- special actions (not currently used)
- sieve
- vacation_directory where to store vacation "once" files
- enotify_mailto_owner owner of mailto notifications
- useraddress string expression for :user part of address
- subaddress string expression for :subaddress part of address
- inbox string expression for "keep"
- generated where to hang newly-generated addresses
- error where to pass back an error text
-
-Returns: FF_DELIVERED success, a significant action was taken
- FF_NOTDELIVERED success, no significant action
- FF_DEFER defer requested
- FF_FAIL fail requested
- FF_FREEZE freeze requested
- FF_ERROR there was a problem
-*/
-
-int
-sieve_interpret(const uschar * filter, int options, const sieve_block * sb,
- address_item ** generated, uschar ** error)
-{
-struct Sieve sieve;
-int r;
-uschar * msg;
-
-DEBUG(D_route) debug_printf_indent("Sieve: start of processing\n");
-expand_level++;
-sieve.filter = filter;
-
-GET_OPTION("sieve_vacation_directory");
-if (!sb || !sb->vacation_dir)
- sieve.vacation_directory = NULL;
-else if (!(sieve.vacation_directory = expand_cstring(sb->vacation_dir)))
- {
- *error = string_sprintf("failed to expand \"%s\" "
- "(sieve_vacation_directory): %s", sb->vacation_dir, expand_string_message);
- return FF_ERROR;
- }
-
-GET_OPTION("sieve_vacation_directory");
-if (!sb || !sb->inbox)
- sieve.inbox = US"inbox";
-else if (!(sieve.inbox = expand_cstring(sb->inbox)))
- {
- *error = string_sprintf("failed to expand \"%s\" "
- "(sieve_inbox): %s", sb->inbox, expand_string_message);
- return FF_ERROR;
- }
-
-GET_OPTION("sieve_enotify_mailto_owner");
-if (!sb || !sb->enotify_mailto_owner)
- sieve.enotify_mailto_owner = NULL;
-else if (!(sieve.enotify_mailto_owner = expand_cstring(sb->enotify_mailto_owner)))
- {
- *error = string_sprintf("failed to expand \"%s\" "
- "(sieve_enotify_mailto_owner): %s", sb->enotify_mailto_owner,
- expand_string_message);
- return FF_ERROR;
- }
-
-GET_OPTION("sieve_useraddress");
-sieve.useraddress = sb && sb->useraddress
- ? sb->useraddress : CUS "$local_part_prefix$local_part$local_part_suffix";
-GET_OPTION("sieve_subaddress");
-sieve.subaddress = sb ? sb->subaddress : NULL;
-
-#ifdef COMPILE_SYNTAX_CHECKER
-if (parse_start(&sieve, 0, generated) == 1)
-#else
-if (parse_start(&sieve, 1, generated) == 1)
-#endif
- if (sieve.keep)
- {
- add_addr(generated, sieve.inbox, 1, 0, 0, 0);
- msg = US"Implicit keep";
- r = FF_DELIVERED;
- }
- else
- {
- msg = US"No implicit keep";
- r = FF_DELIVERED;
- }
-else
- {
- msg = string_sprintf("Sieve error: %s in line %d", sieve.errmsg, sieve.line);
-#ifdef COMPILE_SYNTAX_CHECKER
- r = FF_ERROR;
- *error = msg;
-#else
- add_addr(generated, sieve.inbox, 1, 0, 0, 0);
- r = FF_DELIVERED;
-#endif
- }
-
-#ifndef COMPILE_SYNTAX_CHECKER
-if (filter_test != FTEST_NONE) printf("%s\n", (const char*) msg);
- else debug_printf_indent("%s\n", msg);
-#endif
-
-expand_level--;
-DEBUG(D_route) debug_printf_indent("Sieve: end of processing\n");
-return r;
-}
+++ /dev/null
-From offerqn@bpk.example.com Wed Feb 05 10:47:05 2003
-Delivered-To: michael@nostromo.somenet-ag.example
-From: () c, "A.N. Other" <offerqn@bpk.example.com>, spam@aaaa.example
-To: marian@abcdefgh.example
-Subject: =?iso-8859-1?q?abcdef?=
- =?iso-8859-1?q?ghi?=
-X-Priority: 3
-X-MSMail-Priority: Normal
-X-BasAga: 8sDTRgF1RyrcCxLg0m2c
-X-AgaUtu: 2Vi3TgrEIwL3KWqSoI7
-X-IdiTegUtuUtu: EY4XogFnkpH1P06d
-X-RosHrIdiNa: Vu8pFqH5hFK05kD5opU3
-X-BasAgaNa: T1LeD56uyN
-X-Mailer: MMailer v3.0
-X-Special1: ?a=?ISO-8859-1?Q?=00?=cx*
-X-Special2: 1a*cx2
-Date: Wed, 5 Feb 2003 0:37:30 +-0800
-Mime-Version: 1.0
-Content-Type: text/html; charset="Windows-1251"
-X-Warning: 204.29.65.132 is listed at list.dsbl.org
-X-Warning: 204.29.65.132 is listed at unconfirmed.dsbl.org
-X-Warning: 204.29.65.132 is listed at singlestage.dnsbl.somenet.example
-X-Warning: bpk.example.com is listed at postmaster.rfc-ignorant.org
-Delivered-To: irc@01019somenet.example
-Delivered-To: irc@irc.somenet.example
-Delivered-To: some.one@somenet-ag.example
-
-<HTML>
-<HEAD><TITLE></TITLE><STYLE type="text/css"><!--
-
+++ /dev/null
-From offerqn@bpk.example.com Wed Feb 05 10:47:05 2003
-Delivered-To: michael@nostromo.somenet-ag.example
-From: () c, "A.N. Other" <offerqn@bpk.example.com>, spam@aaaa.example
-BCC: <arthur_dellea@hotmail.com>,<pgodman@netscape.net>,<artbridgea@hotmail.com>,<pets@hiwaay.net>,<isared@yahoo.com>,<arswit@hotmail.com>,<pgy@correoweb.com>,<art.stevens@hotmail.com>,<irvi_g_pete@yahoo.com>,<art4love@hotmail.com>,<artamp@hotmail.com>,<arthur364@hotmail.com>,<pberger47@attbi.com>,<arosewall@hotmail.com>,<arth16@hotmail.com>
-Subject: =?iso-8859-1?q?abcdef?=
- =?iso-8859-1?q?ghi?=
-X-Priority: 3
-X-MSMail-Priority: Normal
-X-BasAga: 8sDTRgF1RyrcCxLg0m2c
-X-AgaUtu: 2Vi3TgrEIwL3KWqSoI7
-X-IdiTegUtuUtu: EY4XogFnkpH1P06d
-X-RosHrIdiNa: Vu8pFqH5hFK05kD5opU3
-X-BasAgaNa: T1LeD56uyN
-X-Mailer: MMailer v3.0
-Date: Wed, 5 Feb 2003 0:37:30 +-0800
-Mime-Version: 1.0
-Content-Type: text/html; charset="Windows-1251"
-X-Warning: 204.29.65.132 is listed at list.dsbl.org
-X-Warning: 204.29.65.132 is listed at unconfirmed.dsbl.org
-X-Warning: 204.29.65.132 is listed at singlestage.dnsbl.somenet.example
-X-Warning: bpk.example.com is listed at postmaster.rfc-ignorant.org
-Delivered-To: irc@01019somenet.example
-Delivered-To: irc@irc.somenet.example
-Delivered-To: some.one@somenet-ag.example
-
-<HTML>
-<HEAD><TITLE></TITLE><STYLE type="text/css"><!--
+++ /dev/null
-From offerqn@bpk.example.com Wed Feb 05 10:47:05 2003
-Subject: =?iso-8859-1?q?abcdef?=
- =?iso-8859-1?q?ghi?=
-X-0: =?ISO-8859-1?Q?=00?=
-X-1: =?ISO-8859-1?Q?=31?=
-X-1b: =?ISO-8859-1?Q?=31=
-X-Wrapped: eins
- zwei
- drei
-X-NoMimeWrap: =?iso-8859-1?q?abc
- def
- ghi?=
-X-Mixed: =?iso-8859-1?q?abc?=
- def
-X-B64: =?iso-8859-1?b?VGhpcyBpcyBCQVNFNjQ=?=
-X-B64-Broken: =?iso-8859-1?b?VGhpcyBpcyBCQVNFNjQ?=
-X-Q75total: =?ISO-8859-1?Q?0123456789012345678901234567890123456789012345678901234567?=
-X-Q76total: =?ISO-8859-1?Q?01234567890123456789012345678901234567890123456789012345678?=
-
-empty body
+++ /dev/null
-# Sieve filter
-
-discard;
-
+++ /dev/null
-# Sieve filter
-#
-
-require "fileinto";
-
-if header :contains "from" "coyote" {
- discard;
-} elsif header :contains "from" "spot_this" {
- fileinto "myfolder";
-} elsif header :contains "from" "redirect" {
- redirect "fred@some_other_dom.ain";
-}
--- /dev/null
+From offerqn@bpk.example.com Wed Feb 05 10:47:05 2003
+Delivered-To: michael@nostromo.somenet-ag.example
+From: () c, "A.N. Other" <offerqn@bpk.example.com>, spam@aaaa.example
+To: marian@abcdefgh.example
+Subject: =?iso-8859-1?q?abcdef?=
+ =?iso-8859-1?q?ghi?=
+X-Priority: 3
+X-MSMail-Priority: Normal
+X-BasAga: 8sDTRgF1RyrcCxLg0m2c
+X-AgaUtu: 2Vi3TgrEIwL3KWqSoI7
+X-IdiTegUtuUtu: EY4XogFnkpH1P06d
+X-RosHrIdiNa: Vu8pFqH5hFK05kD5opU3
+X-BasAgaNa: T1LeD56uyN
+X-Mailer: MMailer v3.0
+X-Special1: ?a=?ISO-8859-1?Q?=00?=cx*
+X-Special2: 1a*cx2
+Date: Wed, 5 Feb 2003 0:37:30 +-0800
+Mime-Version: 1.0
+Content-Type: text/html; charset="Windows-1251"
+X-Warning: 204.29.65.132 is listed at list.dsbl.org
+X-Warning: 204.29.65.132 is listed at unconfirmed.dsbl.org
+X-Warning: 204.29.65.132 is listed at singlestage.dnsbl.somenet.example
+X-Warning: bpk.example.com is listed at postmaster.rfc-ignorant.org
+Delivered-To: irc@01019somenet.example
+Delivered-To: irc@irc.somenet.example
+Delivered-To: some.one@somenet-ag.example
+
+<HTML>
+<HEAD><TITLE></TITLE><STYLE type="text/css"><!--
+
--- /dev/null
+From offerqn@bpk.example.com Wed Feb 05 10:47:05 2003
+Delivered-To: michael@nostromo.somenet-ag.example
+From: () c, "A.N. Other" <offerqn@bpk.example.com>, spam@aaaa.example
+BCC: <arthur_dellea@hotmail.com>,<pgodman@netscape.net>,<artbridgea@hotmail.com>,<pets@hiwaay.net>,<isared@yahoo.com>,<arswit@hotmail.com>,<pgy@correoweb.com>,<art.stevens@hotmail.com>,<irvi_g_pete@yahoo.com>,<art4love@hotmail.com>,<artamp@hotmail.com>,<arthur364@hotmail.com>,<pberger47@attbi.com>,<arosewall@hotmail.com>,<arth16@hotmail.com>
+Subject: =?iso-8859-1?q?abcdef?=
+ =?iso-8859-1?q?ghi?=
+X-Priority: 3
+X-MSMail-Priority: Normal
+X-BasAga: 8sDTRgF1RyrcCxLg0m2c
+X-AgaUtu: 2Vi3TgrEIwL3KWqSoI7
+X-IdiTegUtuUtu: EY4XogFnkpH1P06d
+X-RosHrIdiNa: Vu8pFqH5hFK05kD5opU3
+X-BasAgaNa: T1LeD56uyN
+X-Mailer: MMailer v3.0
+Date: Wed, 5 Feb 2003 0:37:30 +-0800
+Mime-Version: 1.0
+Content-Type: text/html; charset="Windows-1251"
+X-Warning: 204.29.65.132 is listed at list.dsbl.org
+X-Warning: 204.29.65.132 is listed at unconfirmed.dsbl.org
+X-Warning: 204.29.65.132 is listed at singlestage.dnsbl.somenet.example
+X-Warning: bpk.example.com is listed at postmaster.rfc-ignorant.org
+Delivered-To: irc@01019somenet.example
+Delivered-To: irc@irc.somenet.example
+Delivered-To: some.one@somenet-ag.example
+
+<HTML>
+<HEAD><TITLE></TITLE><STYLE type="text/css"><!--
--- /dev/null
+From offerqn@bpk.example.com Wed Feb 05 10:47:05 2003
+Subject: =?iso-8859-1?q?abcdef?=
+ =?iso-8859-1?q?ghi?=
+X-0: =?ISO-8859-1?Q?=00?=
+X-1: =?ISO-8859-1?Q?=31?=
+X-1b: =?ISO-8859-1?Q?=31=
+X-Wrapped: eins
+ zwei
+ drei
+X-NoMimeWrap: =?iso-8859-1?q?abc
+ def
+ ghi?=
+X-Mixed: =?iso-8859-1?q?abc?=
+ def
+X-B64: =?iso-8859-1?b?VGhpcyBpcyBCQVNFNjQ=?=
+X-B64-Broken: =?iso-8859-1?b?VGhpcyBpcyBCQVNFNjQ?=
+X-Q75total: =?ISO-8859-1?Q?0123456789012345678901234567890123456789012345678901234567?=
+X-Q76total: =?ISO-8859-1?Q?01234567890123456789012345678901234567890123456789012345678?=
+
+empty body
--- /dev/null
+# Sieve filter
+
+discard;
+
--- /dev/null
+# Sieve filter
+#
+
+require "fileinto";
+
+if header :contains "from" "coyote" {
+ discard;
+} elsif header :contains "from" "spot_this" {
+ fileinto "myfolder";
+} elsif header :contains "from" "redirect" {
+ redirect "fred@some_other_dom.ain";
+}
+++ /dev/null
-# Exim test configuration 0427
-
-.include DIR/aux-var/std_conf_prefix
-
-
-# ----- Main settings -----
-
-primary_hostname = mail.test.ex
-qualify_domain = test.ex
-
-# ----- Routers -----
-
-begin routers
-
-
-# ----- Transports -----
-
-begin transports
-
-
-# End
+++ /dev/null
-# Exim test configuration 0428
-
-.include DIR/aux-var/std_conf_prefix
-
-
-# ----- Main settings -----
-
-primary_hostname = mail.test.ex
-qualify_domain = test.ex
-trusted_users = CALLER
-
-# ----- Routers -----
-
-begin routers
-
-rb:
- driver = accept
- senders = :
- transport = t2
-
-r0:
- driver = redirect
- local_parts = redirected
- allow_filter
- user = CALLER
- file_transport = t1
- data = "#Sieve filter\n keep;"
-
-r1:
- driver = redirect
- local_parts = ^restrict-
- allow_filter
- forbid_file
- skip_syntax_errors
- data = "#Sieve filter\n$h_filter:"
- user = CALLER
- file_transport = t1
-
-r2_8:
- driver = redirect
- local_parts = userx8
- allow_filter
- data = #Sieve filter\n \
- require["fileinto","comparator-i;ascii-numeric"]; \
- if header :comparator "i;ascii-numeric" "X-Sieve" "99" { \
- fileinto "inbox.JUNK"; \
- stop; \
- }
- user = CALLER
- file_transport = t1
- reply_transport = t3
-
-r2_9:
- driver = redirect
- local_parts = userx9
- allow_filter
- data = #Sieve filter\n \
- require["fileinto","comparator-i;ascii-numeric"]; \
- if header :comparator "i;ascii-numeric" "X-Sieve" "98" { \
- fileinto "inbox.JUNK"; \
- stop; \
- }
- user = CALLER
- file_transport = t1
- reply_transport = t3
-
-r2_10:
- driver = redirect
- local_parts = userx10
- allow_filter
- data = #Sieve filter\n \
- require["fileinto","comparator-i;ascii-numeric"]; \
- if header :comparator "i;ascii-numeric" "X-Sieve" "99" { \
- fileinto "inbox.JUNK"; \
- stop; \
- }
- user = CALLER
- file_transport = t1
- reply_transport = t3
-
-r2_11:
- driver = redirect
- local_parts = userx11
- allow_filter
- data = #Sieve filter\n \
- require["fileinto","comparator-i;ascii-numeric"]; \
- if header :comparator "i;ascii-numeric" "X-Sieve" "-99" { \
- fileinto "inbox.JUNK"; \
- stop; \
- }
- user = CALLER
- file_transport = t1
- reply_transport = t3
-
-r2_12:
- driver = redirect
- local_parts = userx12
- allow_filter
- data = #Sieve filter\n \
- require["fileinto","comparator-i;ascii-numeric"]; \
- if header :comparator "i;ascii-numeric" "X-Sieve" "-98" { \
- fileinto "inbox.JUNK"; \
- stop; \
- }
- user = CALLER
- file_transport = t1
- reply_transport = t3
-
-r2_13:
- driver = redirect
- local_parts = userx13 : someone13
- allow_filter
- data = #Sieve filter\n \
- require ["vacation"]; \
- vacation "I am gone. Not here.";
- user = CALLER
- file_transport = t1
- reply_transport = t3
- sieve_vacation_directory = DIR/test-vacation-directory
-
-r2_14:
- driver = redirect
- local_parts = userx14
- local_part_suffix = -*
- local_part_suffix_optional
- allow_filter
- data = #Sieve filter\n \
- require ["envelope","fileinto"]; \
- if envelope :matches :localpart "to" "*-suffix" { \
- fileinto "userx-sawsuffix"; \
- stop; \
- }
- user = CALLER
- file_transport = t1
- reply_transport = t3
-
-r2_15:
- driver = redirect
- local_parts = userx_inbox
- allow_filter
- data = "#Sieve filter\nkeep;\n"
- user = CALLER
- sieve_inbox = inbox.changed
- file_transport = t1
-
-r2:
- driver = redirect
- allow_filter
- skip_syntax_errors
- data = "#Sieve filter\n$h_filter:"
- user = CALLER
- file_transport = t1
- reply_transport = t3
-
-
-# ----- Transports -----
-
-begin transports
-
-t1:
- driver = appendfile
- file = ${if eq{$address_file}{inbox} \
- {DIR/test-mail/$local_part} \
- {${if eq{${substr_0_1:$address_file}}{/} \
- {$address_file} \
- {DIR/test-mail/$address_file} \
- }} \
- }
- create_file = DIR/test-mail
- delivery_date_add
- envelope_to_add
- return_path_add
- user = CALLER
-
-t2:
- driver = appendfile
- file = DIR/test-mail/$local_part
- create_file = DIR/test-mail
- delivery_date_add
- envelope_to_add
- return_path_add
- user = CALLER
-
-t3:
- driver = autoreply
-
-# End
+++ /dev/null
-# Exim test configuration 0950
-
-SERVER=
-
-.include DIR/aux-var/std_conf_prefix
-
-primary_hostname = myhost.test.ex
-log_selector = +received_recipients +smtp_connection +millisec
-
-
-# ----- Main settings -----
-
-acl_smtp_rcpt = accept
-
-# ----- Routers -----
-
-begin routers
-
-discard:
- driver = redirect
- domains = !test.ex
- data = :blackhole:
-
-client:
- driver = redirect
- file = DIR/aux-fixed/TESTNUM.sieve
- allow_filter
- user = CALLER
- file_transport = local_file
- errors_to = ""
-
-# ----- Transports -----
-
-begin transports
-
-local_file:
- driver = appendfile
- file = DIR/test-mail/${if eq{$address_file}{inbox} {$local_part} {$address_file}}
- create_file = DIR/test-mail
- delivery_date_add
- envelope_to_add
- return_path_add
-
-send_to_server:
- driver = smtp
- allow_localhost
- hosts = 127.0.0.1
- port = PORT_D
-
-# ----- Retry -----
-
-
-begin retry
-
-* * F,5d,10s
-
-
-# End
--- /dev/null
+# Exim test configuration 4160
+
+.include DIR/aux-var/std_conf_prefix
+
+
+# ----- Main settings -----
+
+primary_hostname = mail.test.ex
+qualify_domain = test.ex
+
+# ----- Routers -----
+
+begin routers
+
+
+# ----- Transports -----
+
+begin transports
+
+
+# End
--- /dev/null
+# Exim test configuration 4161
+
+.include DIR/aux-var/std_conf_prefix
+
+
+# ----- Main settings -----
+
+primary_hostname = mail.test.ex
+qualify_domain = test.ex
+trusted_users = CALLER
+
+# ----- Routers -----
+
+begin routers
+
+rb:
+ driver = accept
+ senders = :
+ transport = t2
+
+r0:
+ driver = redirect
+ local_parts = redirected
+ allow_filter
+ user = CALLER
+ file_transport = t1
+ data = "#Sieve filter\n keep;"
+
+r1:
+ driver = redirect
+ local_parts = ^restrict-
+ allow_filter
+ forbid_file
+ skip_syntax_errors
+ data = "#Sieve filter\n$h_filter:"
+ user = CALLER
+ file_transport = t1
+
+r2_8:
+ driver = redirect
+ local_parts = userx8
+ allow_filter
+ data = #Sieve filter\n \
+ require["fileinto","comparator-i;ascii-numeric"]; \
+ if header :comparator "i;ascii-numeric" "X-Sieve" "99" { \
+ fileinto "inbox.JUNK"; \
+ stop; \
+ }
+ user = CALLER
+ file_transport = t1
+ reply_transport = t3
+
+r2_9:
+ driver = redirect
+ local_parts = userx9
+ allow_filter
+ data = #Sieve filter\n \
+ require["fileinto","comparator-i;ascii-numeric"]; \
+ if header :comparator "i;ascii-numeric" "X-Sieve" "98" { \
+ fileinto "inbox.JUNK"; \
+ stop; \
+ }
+ user = CALLER
+ file_transport = t1
+ reply_transport = t3
+
+r2_10:
+ driver = redirect
+ local_parts = userx10
+ allow_filter
+ data = #Sieve filter\n \
+ require["fileinto","comparator-i;ascii-numeric"]; \
+ if header :comparator "i;ascii-numeric" "X-Sieve" "99" { \
+ fileinto "inbox.JUNK"; \
+ stop; \
+ }
+ user = CALLER
+ file_transport = t1
+ reply_transport = t3
+
+r2_11:
+ driver = redirect
+ local_parts = userx11
+ allow_filter
+ data = #Sieve filter\n \
+ require["fileinto","comparator-i;ascii-numeric"]; \
+ if header :comparator "i;ascii-numeric" "X-Sieve" "-99" { \
+ fileinto "inbox.JUNK"; \
+ stop; \
+ }
+ user = CALLER
+ file_transport = t1
+ reply_transport = t3
+
+r2_12:
+ driver = redirect
+ local_parts = userx12
+ allow_filter
+ data = #Sieve filter\n \
+ require["fileinto","comparator-i;ascii-numeric"]; \
+ if header :comparator "i;ascii-numeric" "X-Sieve" "-98" { \
+ fileinto "inbox.JUNK"; \
+ stop; \
+ }
+ user = CALLER
+ file_transport = t1
+ reply_transport = t3
+
+r2_13:
+ driver = redirect
+ local_parts = userx13 : someone13
+ allow_filter
+ data = #Sieve filter\n \
+ require ["vacation"]; \
+ vacation "I am gone. Not here.";
+ user = CALLER
+ file_transport = t1
+ reply_transport = t3
+ sieve_vacation_directory = DIR/test-vacation-directory
+
+r2_14:
+ driver = redirect
+ local_parts = userx14
+ local_part_suffix = -*
+ local_part_suffix_optional
+ allow_filter
+ data = #Sieve filter\n \
+ require ["envelope","fileinto"]; \
+ if envelope :matches :localpart "to" "*-suffix" { \
+ fileinto "userx-sawsuffix"; \
+ stop; \
+ }
+ user = CALLER
+ file_transport = t1
+ reply_transport = t3
+
+r2_15:
+ driver = redirect
+ local_parts = userx_inbox
+ allow_filter
+ data = "#Sieve filter\nkeep;\n"
+ user = CALLER
+ sieve_inbox = inbox.changed
+ file_transport = t1
+
+r2:
+ driver = redirect
+ allow_filter
+ skip_syntax_errors
+ data = "#Sieve filter\n$h_filter:"
+ user = CALLER
+ file_transport = t1
+ reply_transport = t3
+
+
+# ----- Transports -----
+
+begin transports
+
+t1:
+ driver = appendfile
+ file = ${if eq{$address_file}{inbox} \
+ {DIR/test-mail/$local_part} \
+ {${if eq{${substr_0_1:$address_file}}{/} \
+ {$address_file} \
+ {DIR/test-mail/$address_file} \
+ }} \
+ }
+ create_file = DIR/test-mail
+ delivery_date_add
+ envelope_to_add
+ return_path_add
+ user = CALLER
+
+t2:
+ driver = appendfile
+ file = DIR/test-mail/$local_part
+ create_file = DIR/test-mail
+ delivery_date_add
+ envelope_to_add
+ return_path_add
+ user = CALLER
+
+t3:
+ driver = autoreply
+
+# End
--- /dev/null
+# Exim test configuration 4162
+
+ALLOW=allow_filter
+FORBID=
+
+.include DIR/aux-var/std_conf_prefix
+
+primary_hostname = myhost.test.ex
+
+# ----- Main settings -----
+
+log_selector = +queue_time_overall
+
+# ----- Routers -----
+
+begin routers
+
+r1:
+ driver = redirect
+ user = EXIMUSER
+ ALLOW
+ FORBID
+ file = ${lookup {TESTNUM.$local_part} dsearch,ret=full {DIR/aux-fixed} {$value}fail}
+
+
+# ----- Retry -----
+
+begin retry
+
+* * F,1d,1h
+
+# End
--- /dev/null
+# Exim test configuration 4163
+
+SERVER=
+
+.include DIR/aux-var/std_conf_prefix
+
+primary_hostname = myhost.test.ex
+log_selector = +received_recipients +smtp_connection +millisec
+
+
+# ----- Main settings -----
+
+acl_smtp_rcpt = accept
+
+# ----- Routers -----
+
+begin routers
+
+discard:
+ driver = redirect
+ domains = !test.ex
+ data = :blackhole:
+
+client:
+ driver = redirect
+ file = DIR/aux-fixed/TESTNUM.sieve
+ allow_filter
+ user = CALLER
+ file_transport = local_file
+ errors_to = ""
+
+# ----- Transports -----
+
+begin transports
+
+local_file:
+ driver = appendfile
+ file = DIR/test-mail/${if eq{$address_file}{inbox} {$local_part} {$address_file}}
+ create_file = DIR/test-mail
+ delivery_date_add
+ envelope_to_add
+ return_path_add
+
+send_to_server:
+ driver = smtp
+ allow_localhost
+ hosts = 127.0.0.1
+ port = PORT_D
+
+# ----- Retry -----
+
+
+begin retry
+
+* * F,5d,10s
+
+
+# End
+++ /dev/null
-1999-03-02 09:44:33 10HmaX-000000005vi-0000 <= CALLER@test.ex U=CALLER P=local S=sss
-1999-03-02 09:44:33 10HmaX-000000005vi-0000 => TESTSUITE/test-mail/userx <userx@test.ex> R=r2 T=t1
-1999-03-02 09:44:33 10HmaX-000000005vi-0000 Completed
-1999-03-02 09:44:33 10HmaY-000000005vi-0000 <= CALLER@test.ex U=CALLER P=local S=sss
-1999-03-02 09:44:33 10HmaY-000000005vi-0000 => discarded <userx@test.ex> R=r2
-1999-03-02 09:44:33 10HmaY-000000005vi-0000 Completed
-1999-03-02 09:44:33 10HmaZ-000000005vi-0000 <= CALLER@test.ex U=CALLER P=local S=sss
-1999-03-02 09:44:33 10HmaZ-000000005vi-0000 => TESTSUITE/test-mail/userx-extra <userx@test.ex> R=r2 T=t1
-1999-03-02 09:44:33 10HmaZ-000000005vi-0000 Completed
-1999-03-02 09:44:33 10HmbA-000000005vi-0000 <= CALLER@test.ex U=CALLER P=local S=sss
-1999-03-02 09:44:33 10HmbA-000000005vi-0000 => TESTSUITE/test-mail/redirected (redirected@test.ex) <userx@test.ex> R=r0 T=t1
-1999-03-02 09:44:33 10HmbA-000000005vi-0000 Completed
-1999-03-02 09:44:33 10HmbB-000000005vi-0000 <= CALLER@test.ex U=CALLER P=local S=sss
-1999-03-02 09:44:33 10HmbB-000000005vi-0000 ** save userx-extra <restrict-userx@test.ex> R=r1 T=t1: delivery to file forbidden
-1999-03-02 09:44:33 10HmbC-000000005vi-0000 <= <> R=10HmbB-000000005vi-0000 U=EXIMUSER P=local S=sss
-1999-03-02 09:44:33 10HmbC-000000005vi-0000 => CALLER <CALLER@test.ex> R=rb T=t2
-1999-03-02 09:44:33 10HmbC-000000005vi-0000 Completed
-1999-03-02 09:44:33 10HmbB-000000005vi-0000 Completed
-1999-03-02 09:44:33 10HmbD-000000005vi-0000 <= CALLER@test.ex U=CALLER P=local S=sss
-1999-03-02 09:44:33 10HmbD-000000005vi-0000 ** save inbox <restrict-userx@test.ex> R=r1 T=t1: delivery to file forbidden
-1999-03-02 09:44:33 10HmbE-000000005vi-0000 <= <> R=10HmbD-000000005vi-0000 U=EXIMUSER P=local S=sss
-1999-03-02 09:44:33 10HmbE-000000005vi-0000 => CALLER <CALLER@test.ex> R=rb T=t2
-1999-03-02 09:44:33 10HmbE-000000005vi-0000 Completed
-1999-03-02 09:44:33 10HmbD-000000005vi-0000 Completed
-1999-03-02 09:44:33 10HmbF-000000005vi-0000 <= CALLER@test.ex U=CALLER P=local S=sss
-1999-03-02 09:44:33 10HmbF-000000005vi-0000 => TESTSUITE/test-mail/userx <userx@test.ex> R=r2 T=t1
-1999-03-02 09:44:33 10HmbF-000000005vi-0000 Completed
-1999-03-02 09:44:33 10HmbG-000000005vi-0000 <= CALLER@test.ex U=CALLER P=local S=sss
-1999-03-02 09:44:33 10HmbG-000000005vi-0000 => TESTSUITE/test-mail/inbox.JUNK <userx8@test.ex> R=r2_8 T=t1
-1999-03-02 09:44:33 10HmbG-000000005vi-0000 Completed
-1999-03-02 09:44:33 10HmbH-000000005vi-0000 <= CALLER@test.ex U=CALLER P=local S=sss
-1999-03-02 09:44:33 10HmbH-000000005vi-0000 => TESTSUITE/test-mail/userx9 <userx9@test.ex> R=r2_9 T=t1
-1999-03-02 09:44:33 10HmbH-000000005vi-0000 Completed
-1999-03-02 09:44:33 10HmbI-000000005vi-0000 <= CALLER@test.ex U=CALLER P=local S=sss
-1999-03-02 09:44:33 10HmbI-000000005vi-0000 => TESTSUITE/test-mail/inbox.JUNK <userx10@test.ex> R=r2_10 T=t1
-1999-03-02 09:44:33 10HmbI-000000005vi-0000 Completed
-1999-03-02 09:44:33 10HmbJ-000000005vi-0000 <= CALLER@test.ex U=CALLER P=local S=sss
-1999-03-02 09:44:33 10HmbJ-000000005vi-0000 => TESTSUITE/test-mail/inbox.JUNK <userx11@test.ex> R=r2_11 T=t1
-1999-03-02 09:44:33 10HmbJ-000000005vi-0000 Completed
-1999-03-02 09:44:33 10HmbK-000000005vi-0000 <= CALLER@test.ex U=CALLER P=local S=sss
-1999-03-02 09:44:33 10HmbK-000000005vi-0000 => TESTSUITE/test-mail/inbox.JUNK <userx12@test.ex> R=r2_12 T=t1
-1999-03-02 09:44:33 10HmbK-000000005vi-0000 Completed
-1999-03-02 09:44:33 10HmbL-000000005vi-0000 <= someone@test.ex U=CALLER P=local S=sss
-1999-03-02 09:44:33 10HmbL-000000005vi-0000 => TESTSUITE/test-mail/userx13 <userx13@test.ex> R=r2_13 T=t1
-1999-03-02 09:44:33 10HmbM-000000005vi-0000 <= <> R=10HmbL-000000005vi-0000 U=CALLER P=local S=sss
-1999-03-02 09:44:33 10HmbM-000000005vi-0000 => someone <someone@test.ex> R=rb T=t2
-1999-03-02 09:44:33 10HmbM-000000005vi-0000 Completed
-1999-03-02 09:44:33 10HmbL-000000005vi-0000 => >someone@test.ex <userx13@test.ex> R=r2_13 T=t3
-1999-03-02 09:44:33 10HmbL-000000005vi-0000 Completed
-1999-03-02 09:44:33 10HmbN-000000005vi-0000 <= someone@test.ex U=CALLER P=local S=sss
-1999-03-02 09:44:33 10HmbN-000000005vi-0000 => TESTSUITE/test-mail/userx14 <userx14-suffix2@test.ex> R=r2_14 T=t1
-1999-03-02 09:44:33 10HmbN-000000005vi-0000 => TESTSUITE/test-mail/userx-sawsuffix <userx14-suffix@test.ex> R=r2_14 T=t1
-1999-03-02 09:44:33 10HmbN-000000005vi-0000 Completed
-1999-03-02 09:44:33 10HmbO-000000005vi-0000 <= CALLER@test.ex U=CALLER P=local S=sss
-1999-03-02 09:44:33 10HmbO-000000005vi-0000 => TESTSUITE/test-mail/inbox.changed <userx_inbox@test.ex> R=r2_15 T=t1
-1999-03-02 09:44:33 10HmbO-000000005vi-0000 Completed
1999-03-02 09:44:33 10HmaX-000000005vi-0000 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
1999-03-02 09:44:33 10HmaX-000000005vi-0000 => discarded <exim-filter@test.ex> R=r1
-1999-03-02 09:44:33 10HmaX-000000005vi-0000 => discarded <sieve-filter@test.ex> R=r1
1999-03-02 09:44:33 10HmaX-000000005vi-0000 Completed QT=qqs
1999-03-02 09:44:33 10HmaY-000000005vi-0000 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
1999-03-02 09:44:33 10HmaY-000000005vi-0000 => discarded <exim-filter@test.ex> R=r1
-1999-03-02 09:44:33 10HmaY-000000005vi-0000 == sieve-filter@test.ex R=r1 defer (-17): error in filter file: Sieve filtering not enabled
+1999-03-02 09:44:33 10HmaY-000000005vi-0000 Completed QT=qqs
1999-03-02 09:44:33 10HmaZ-000000005vi-0000 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
1999-03-02 09:44:33 10HmaZ-000000005vi-0000 == exim-filter@test.ex R=r1 defer (-17): error in filter file: Exim filtering not enabled
-1999-03-02 09:44:33 10HmaZ-000000005vi-0000 => discarded <sieve-filter@test.ex> R=r1
1999-03-02 09:44:33 10HmbA-000000005vi-0000 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
1999-03-02 09:44:33 10HmbA-000000005vi-0000 == exim-filter@test.ex R=r1 defer (-17): error in filter file: filtering not enabled
-1999-03-02 09:44:33 10HmbA-000000005vi-0000 == sieve-filter@test.ex R=r1 defer (-17): error in filter file: filtering not enabled
+++ /dev/null
-
-******** SERVER ********
-2017-07-30 18:51:05.712 exim x.yz daemon started: pid=p1234, no queue runs, listening for SMTP on port PORT_D
-2017-07-30 18:51:05.712 SMTP connection from [127.0.0.1] (TCP/IP connection count = 1)
-2017-07-30 18:51:05.712 10HmaX-000000005vi-0000 <= implcit@test.ex H=(tester) [127.0.0.1] P=smtp S=sss for CALLER@test.ex
-2017-07-30 18:51:05.712 10HmaX-000000005vi-0000 => TESTSUITE/test-mail/CALLER <CALLER@test.ex> R=client T=local_file
-2017-07-30 18:51:05.712 10HmaX-000000005vi-0000 Completed
-2017-07-30 18:51:05.712 10HmaY-000000005vi-0000 <= discard@test.ex H=(tester) [127.0.0.1] P=smtp S=sss for CALLER@test.ex
-2017-07-30 18:51:05.712 10HmaY-000000005vi-0000 => discarded <CALLER@test.ex> R=client
-2017-07-30 18:51:05.712 10HmaY-000000005vi-0000 Completed
-2017-07-30 18:51:05.712 10HmaZ-000000005vi-0000 <= identified@test.ex H=(tester) [127.0.0.1] P=smtp S=sss for CALLER@test.ex
-2017-07-30 18:51:05.712 10HmaZ-000000005vi-0000 => TESTSUITE/test-mail/myfolder <CALLER@test.ex> R=client T=local_file
-2017-07-30 18:51:05.712 10HmaZ-000000005vi-0000 Completed
-2017-07-30 18:51:05.712 10HmbA-000000005vi-0000 <= redirect@test.ex H=(tester) [127.0.0.1] P=smtp S=sss for CALLER@test.ex
-2017-07-30 18:51:05.712 SMTP connection from (tester) [127.0.0.1] D=q.qqqs closed by QUIT
-2017-07-30 18:51:05.712 10HmbA-000000005vi-0000 => :blackhole: <fred@some_other_dom.ain> R=discard
-2017-07-30 18:51:05.712 10HmbA-000000005vi-0000 Completed
--- /dev/null
+1999-03-02 09:44:33 10HmaX-000000005vi-0000 <= CALLER@test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaX-000000005vi-0000 => TESTSUITE/test-mail/userx <userx@test.ex> R=r2 T=t1
+1999-03-02 09:44:33 10HmaX-000000005vi-0000 Completed
+1999-03-02 09:44:33 10HmaY-000000005vi-0000 <= CALLER@test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaY-000000005vi-0000 => discarded <userx@test.ex> R=r2
+1999-03-02 09:44:33 10HmaY-000000005vi-0000 Completed
+1999-03-02 09:44:33 10HmaZ-000000005vi-0000 <= CALLER@test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaZ-000000005vi-0000 => TESTSUITE/test-mail/userx-extra <userx@test.ex> R=r2 T=t1
+1999-03-02 09:44:33 10HmaZ-000000005vi-0000 Completed
+1999-03-02 09:44:33 10HmbA-000000005vi-0000 <= CALLER@test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbA-000000005vi-0000 => TESTSUITE/test-mail/redirected (redirected@test.ex) <userx@test.ex> R=r0 T=t1
+1999-03-02 09:44:33 10HmbA-000000005vi-0000 Completed
+1999-03-02 09:44:33 10HmbB-000000005vi-0000 <= CALLER@test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbB-000000005vi-0000 ** save userx-extra <restrict-userx@test.ex> R=r1 T=t1: delivery to file forbidden
+1999-03-02 09:44:33 10HmbC-000000005vi-0000 <= <> R=10HmbB-000000005vi-0000 U=EXIMUSER P=local S=sss
+1999-03-02 09:44:33 10HmbC-000000005vi-0000 => CALLER <CALLER@test.ex> R=rb T=t2
+1999-03-02 09:44:33 10HmbC-000000005vi-0000 Completed
+1999-03-02 09:44:33 10HmbB-000000005vi-0000 Completed
+1999-03-02 09:44:33 10HmbD-000000005vi-0000 <= CALLER@test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbD-000000005vi-0000 ** save inbox <restrict-userx@test.ex> R=r1 T=t1: delivery to file forbidden
+1999-03-02 09:44:33 10HmbE-000000005vi-0000 <= <> R=10HmbD-000000005vi-0000 U=EXIMUSER P=local S=sss
+1999-03-02 09:44:33 10HmbE-000000005vi-0000 => CALLER <CALLER@test.ex> R=rb T=t2
+1999-03-02 09:44:33 10HmbE-000000005vi-0000 Completed
+1999-03-02 09:44:33 10HmbD-000000005vi-0000 Completed
+1999-03-02 09:44:33 10HmbF-000000005vi-0000 <= CALLER@test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbF-000000005vi-0000 => TESTSUITE/test-mail/userx <userx@test.ex> R=r2 T=t1
+1999-03-02 09:44:33 10HmbF-000000005vi-0000 Completed
+1999-03-02 09:44:33 10HmbG-000000005vi-0000 <= CALLER@test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbG-000000005vi-0000 => TESTSUITE/test-mail/inbox.JUNK <userx8@test.ex> R=r2_8 T=t1
+1999-03-02 09:44:33 10HmbG-000000005vi-0000 Completed
+1999-03-02 09:44:33 10HmbH-000000005vi-0000 <= CALLER@test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbH-000000005vi-0000 => TESTSUITE/test-mail/userx9 <userx9@test.ex> R=r2_9 T=t1
+1999-03-02 09:44:33 10HmbH-000000005vi-0000 Completed
+1999-03-02 09:44:33 10HmbI-000000005vi-0000 <= CALLER@test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbI-000000005vi-0000 => TESTSUITE/test-mail/inbox.JUNK <userx10@test.ex> R=r2_10 T=t1
+1999-03-02 09:44:33 10HmbI-000000005vi-0000 Completed
+1999-03-02 09:44:33 10HmbJ-000000005vi-0000 <= CALLER@test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbJ-000000005vi-0000 => TESTSUITE/test-mail/inbox.JUNK <userx11@test.ex> R=r2_11 T=t1
+1999-03-02 09:44:33 10HmbJ-000000005vi-0000 Completed
+1999-03-02 09:44:33 10HmbK-000000005vi-0000 <= CALLER@test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbK-000000005vi-0000 => TESTSUITE/test-mail/inbox.JUNK <userx12@test.ex> R=r2_12 T=t1
+1999-03-02 09:44:33 10HmbK-000000005vi-0000 Completed
+1999-03-02 09:44:33 10HmbL-000000005vi-0000 <= someone@test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbL-000000005vi-0000 => TESTSUITE/test-mail/userx13 <userx13@test.ex> R=r2_13 T=t1
+1999-03-02 09:44:33 10HmbM-000000005vi-0000 <= <> R=10HmbL-000000005vi-0000 U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbM-000000005vi-0000 => someone <someone@test.ex> R=rb T=t2
+1999-03-02 09:44:33 10HmbM-000000005vi-0000 Completed
+1999-03-02 09:44:33 10HmbL-000000005vi-0000 => >someone@test.ex <userx13@test.ex> R=r2_13 T=t3
+1999-03-02 09:44:33 10HmbL-000000005vi-0000 Completed
+1999-03-02 09:44:33 10HmbN-000000005vi-0000 <= someone@test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbN-000000005vi-0000 => TESTSUITE/test-mail/userx14 <userx14-suffix2@test.ex> R=r2_14 T=t1
+1999-03-02 09:44:33 10HmbN-000000005vi-0000 => TESTSUITE/test-mail/userx-sawsuffix <userx14-suffix@test.ex> R=r2_14 T=t1
+1999-03-02 09:44:33 10HmbN-000000005vi-0000 Completed
+1999-03-02 09:44:33 10HmbO-000000005vi-0000 <= CALLER@test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbO-000000005vi-0000 => TESTSUITE/test-mail/inbox.changed <userx_inbox@test.ex> R=r2_15 T=t1
+1999-03-02 09:44:33 10HmbO-000000005vi-0000 Completed
--- /dev/null
+1999-03-02 09:44:33 10HmaX-000000005vi-0000 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaX-000000005vi-0000 => discarded <sieve-filter@test.ex> R=r1
+1999-03-02 09:44:33 10HmaX-000000005vi-0000 Completed QT=qqs
+1999-03-02 09:44:33 10HmaY-000000005vi-0000 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaY-000000005vi-0000 == sieve-filter@test.ex R=r1 defer (-17): error in filter file: Sieve filtering not enabled
+1999-03-02 09:44:33 10HmaZ-000000005vi-0000 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaZ-000000005vi-0000 => discarded <sieve-filter@test.ex> R=r1
+1999-03-02 09:44:33 10HmaZ-000000005vi-0000 Completed QT=qqs
+1999-03-02 09:44:33 10HmbA-000000005vi-0000 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbA-000000005vi-0000 == sieve-filter@test.ex R=r1 defer (-17): error in filter file: filtering not enabled
--- /dev/null
+
+******** SERVER ********
+2017-07-30 18:51:05.712 exim x.yz daemon started: pid=p1234, no queue runs, listening for SMTP on port PORT_D
+2017-07-30 18:51:05.712 SMTP connection from [127.0.0.1] (TCP/IP connection count = 1)
+2017-07-30 18:51:05.712 10HmaX-000000005vi-0000 <= implcit@test.ex H=(tester) [127.0.0.1] P=smtp S=sss for CALLER@test.ex
+2017-07-30 18:51:05.712 10HmaX-000000005vi-0000 => TESTSUITE/test-mail/CALLER <CALLER@test.ex> R=client T=local_file
+2017-07-30 18:51:05.712 10HmaX-000000005vi-0000 Completed
+2017-07-30 18:51:05.712 10HmaY-000000005vi-0000 <= discard@test.ex H=(tester) [127.0.0.1] P=smtp S=sss for CALLER@test.ex
+2017-07-30 18:51:05.712 10HmaY-000000005vi-0000 => discarded <CALLER@test.ex> R=client
+2017-07-30 18:51:05.712 10HmaY-000000005vi-0000 Completed
+2017-07-30 18:51:05.712 10HmaZ-000000005vi-0000 <= identified@test.ex H=(tester) [127.0.0.1] P=smtp S=sss for CALLER@test.ex
+2017-07-30 18:51:05.712 10HmaZ-000000005vi-0000 => TESTSUITE/test-mail/myfolder <CALLER@test.ex> R=client T=local_file
+2017-07-30 18:51:05.712 10HmaZ-000000005vi-0000 Completed
+2017-07-30 18:51:05.712 10HmbA-000000005vi-0000 <= redirect@test.ex H=(tester) [127.0.0.1] P=smtp S=sss for CALLER@test.ex
+2017-07-30 18:51:05.712 SMTP connection from (tester) [127.0.0.1] D=q.qqqs closed by QUIT
+2017-07-30 18:51:05.712 10HmbA-000000005vi-0000 => :blackhole: <fred@some_other_dom.ain> R=discard
+2017-07-30 18:51:05.712 10HmbA-000000005vi-0000 Completed
+++ /dev/null
-From MAILER-DAEMON Tue Mar 02 09:44:33 1999
-Return-path: <>
-Envelope-to: CALLER@test.ex
-Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
-Received: from EXIMUSER by mail.test.ex with local (Exim x.yz)
- id 10HmbC-000000005vi-0000
- for CALLER@test.ex;
- Tue, 2 Mar 1999 09:44:33 +0000
-X-Failed-Recipients: restrict-userx@test.ex
-Auto-Submitted: auto-replied
-From: Mail Delivery System <Mailer-Daemon@test.ex>
-To: CALLER@test.ex
-References: <E10HmbB-000000005vi-0000@mail.test.ex>
-Content-Type: multipart/report; report-type=delivery-status; boundary=NNNNNNNNNN-eximdsn-MMMMMMMMMM
-MIME-Version: 1.0
-Subject: Mail delivery failed: returning message to sender
-Message-Id: <E10HmbC-000000005vi-0000@mail.test.ex>
-Date: Tue, 2 Mar 1999 09:44:33 +0000
-
---NNNNNNNNNN-eximdsn-MMMMMMMMMM
-Content-type: text/plain; charset=us-ascii
-
-This message was created automatically by mail delivery software.
-
-A message that you sent could not be delivered to one or more of its
-recipients. This is a permanent error. The following address(es) failed:
-
- save to userx-extra
- generated by restrict-userx@test.ex
-
---NNNNNNNNNN-eximdsn-MMMMMMMMMM
-Content-type: message/delivery-status
-
-Reporting-MTA: dns; mail.test.ex
-
-Action: failed
-Final-Recipient: rfc822;restrict-userx@test.ex
-Status: 5.0.0
-
---NNNNNNNNNN-eximdsn-MMMMMMMMMM
-Content-type: message/rfc822
-
-Return-path: <CALLER@test.ex>
-Received: from CALLER by mail.test.ex with local (Exim x.yz)
- (envelope-from <CALLER@test.ex>)
- id 10HmbB-000000005vi-0000
- for restrict-userx@test.ex;
- Tue, 2 Mar 1999 09:44:33 +0000
-Filter: require "fileinto"; fileinto "userx-extra";
-Message-Id: <E10HmbB-000000005vi-0000@mail.test.ex>
-From: CALLER_NAME <CALLER@test.ex>
-Date: Tue, 2 Mar 1999 09:44:33 +0000
-
-Test 5
-
---NNNNNNNNNN-eximdsn-MMMMMMMMMM--
-
-From MAILER-DAEMON Tue Mar 02 09:44:33 1999
-Return-path: <>
-Envelope-to: CALLER@test.ex
-Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
-Received: from EXIMUSER by mail.test.ex with local (Exim x.yz)
- id 10HmbE-000000005vi-0000
- for CALLER@test.ex;
- Tue, 2 Mar 1999 09:44:33 +0000
-X-Failed-Recipients: restrict-userx@test.ex
-Auto-Submitted: auto-replied
-From: Mail Delivery System <Mailer-Daemon@test.ex>
-To: CALLER@test.ex
-References: <E10HmbD-000000005vi-0000@mail.test.ex>
-Content-Type: multipart/report; report-type=delivery-status; boundary=NNNNNNNNNN-eximdsn-MMMMMMMMMM
-MIME-Version: 1.0
-Subject: Mail delivery failed: returning message to sender
-Message-Id: <E10HmbE-000000005vi-0000@mail.test.ex>
-Date: Tue, 2 Mar 1999 09:44:33 +0000
-
---NNNNNNNNNN-eximdsn-MMMMMMMMMM
-Content-type: text/plain; charset=us-ascii
-
-This message was created automatically by mail delivery software.
-
-A message that you sent could not be delivered to one or more of its
-recipients. This is a permanent error. The following address(es) failed:
-
- save to inbox
- generated by restrict-userx@test.ex
-
---NNNNNNNNNN-eximdsn-MMMMMMMMMM
-Content-type: message/delivery-status
-
-Reporting-MTA: dns; mail.test.ex
-
-Action: failed
-Final-Recipient: rfc822;restrict-userx@test.ex
-Status: 5.0.0
-
---NNNNNNNNNN-eximdsn-MMMMMMMMMM
-Content-type: message/rfc822
-
-Return-path: <CALLER@test.ex>
-Received: from CALLER by mail.test.ex with local (Exim x.yz)
- (envelope-from <CALLER@test.ex>)
- id 10HmbD-000000005vi-0000
- for restrict-userx@test.ex;
- Tue, 2 Mar 1999 09:44:33 +0000
-Filter: fileinto "userx-extra";
-Message-Id: <E10HmbD-000000005vi-0000@mail.test.ex>
-From: CALLER_NAME <CALLER@test.ex>
-Date: Tue, 2 Mar 1999 09:44:33 +0000
-
-Test 6
-
---NNNNNNNNNN-eximdsn-MMMMMMMMMM--
-
+++ /dev/null
-From CALLER@test.ex Tue Mar 02 09:44:33 1999
-Return-path: <CALLER@test.ex>
-Envelope-to: userx8@test.ex
-Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
-Received: from CALLER by mail.test.ex with local (Exim x.yz)
- (envelope-from <CALLER@test.ex>)
- id 10HmbG-000000005vi-0000
- for userx8@test.ex;
- Tue, 2 Mar 1999 09:44:33 +0000
-X-Sieve: 99
-Message-Id: <E10HmbG-000000005vi-0000@mail.test.ex>
-From: CALLER_NAME <CALLER@test.ex>
-Date: Tue, 2 Mar 1999 09:44:33 +0000
-
- require["fileinto","comparator-i;ascii-numeric"];
- if header :comparator "i;ascii-numeric" "X-Sieve" "99" {
- fileinto "inbox.JUNK";
- stop;
- }
-Test 8
-
-From CALLER@test.ex Tue Mar 02 09:44:33 1999
-Return-path: <CALLER@test.ex>
-Envelope-to: userx10@test.ex
-Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
-Received: from CALLER by mail.test.ex with local (Exim x.yz)
- (envelope-from <CALLER@test.ex>)
- id 10HmbI-000000005vi-0000
- for userx10@test.ex;
- Tue, 2 Mar 1999 09:44:33 +0000
-X-Sieve: 99-
-Message-Id: <E10HmbI-000000005vi-0000@mail.test.ex>
-From: CALLER_NAME <CALLER@test.ex>
-Date: Tue, 2 Mar 1999 09:44:33 +0000
-
- require["fileinto","comparator-i;ascii-numeric"];
- if header :comparator "i;ascii-numeric" "X-Sieve" "99" {
- fileinto "inbox.JUNK";
- stop;
- }
-Test 10
-
-From CALLER@test.ex Tue Mar 02 09:44:33 1999
-Return-path: <CALLER@test.ex>
-Envelope-to: userx11@test.ex
-Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
-Received: from CALLER by mail.test.ex with local (Exim x.yz)
- (envelope-from <CALLER@test.ex>)
- id 10HmbJ-000000005vi-0000
- for userx11@test.ex;
- Tue, 2 Mar 1999 09:44:33 +0000
-X-Sieve: -99
-Message-Id: <E10HmbJ-000000005vi-0000@mail.test.ex>
-From: CALLER_NAME <CALLER@test.ex>
-Date: Tue, 2 Mar 1999 09:44:33 +0000
-
- require["fileinto","comparator-i;ascii-numeric"];
- if header :comparator "i;ascii-numeric" "X-Sieve" "-99" {
- fileinto "inbox.JUNK";
- stop;
- }
-Test 11
-
-From CALLER@test.ex Tue Mar 02 09:44:33 1999
-Return-path: <CALLER@test.ex>
-Envelope-to: userx12@test.ex
-Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
-Received: from CALLER by mail.test.ex with local (Exim x.yz)
- (envelope-from <CALLER@test.ex>)
- id 10HmbK-000000005vi-0000
- for userx12@test.ex;
- Tue, 2 Mar 1999 09:44:33 +0000
-X-Sieve: -99
-Message-Id: <E10HmbK-000000005vi-0000@mail.test.ex>
-From: CALLER_NAME <CALLER@test.ex>
-Date: Tue, 2 Mar 1999 09:44:33 +0000
-
- require["fileinto","comparator-i;ascii-numeric"];
- if header :comparator "i;ascii-numeric" "X-Sieve" "-98" {
- fileinto "inbox.JUNK";
- stop;
- }
-Test 12
-
+++ /dev/null
-From CALLER@test.ex Tue Mar 02 09:44:33 1999
-Return-path: <CALLER@test.ex>
-Envelope-to: userx_inbox@test.ex
-Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
-Received: from CALLER by mail.test.ex with local (Exim x.yz)
- (envelope-from <CALLER@test.ex>)
- id 10HmbO-000000005vi-0000
- for userx_inbox@test.ex;
- Tue, 2 Mar 1999 09:44:33 +0000
-Message-Id: <E10HmbO-000000005vi-0000@mail.test.ex>
-From: CALLER_NAME <CALLER@test.ex>
-Date: Tue, 2 Mar 1999 09:44:33 +0000
-
-Test 15
-
+++ /dev/null
-From CALLER@test.ex Tue Mar 02 09:44:33 1999
-Return-path: <CALLER@test.ex>
-Envelope-to: userx@test.ex
-Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
-Received: from CALLER by mail.test.ex with local (Exim x.yz)
- (envelope-from <CALLER@test.ex>)
- id 10HmbA-000000005vi-0000
- for userx@test.ex;
- Tue, 2 Mar 1999 09:44:33 +0000
-Filter: redirect "redirected@test.ex";
-Message-Id: <E10HmbA-000000005vi-0000@mail.test.ex>
-From: CALLER_NAME <CALLER@test.ex>
-Date: Tue, 2 Mar 1999 09:44:33 +0000
-
-Test 4
-
+++ /dev/null
-From MAILER-DAEMON Tue Mar 02 09:44:33 1999
-Return-path: <>
-Envelope-to: someone@test.ex
-Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
-Received: from CALLER by mail.test.ex with local (Exim x.yz)
- id 10HmbM-000000005vi-0000
- for someone@test.ex;
- Tue, 2 Mar 1999 09:44:33 +0000
-From: userx13@test.ex
-To: someone@test.ex
-Subject: Automated reply
-In-Reply-To: <E10HmbL-000000005vi-0000@mail.test.ex>
-References: <E10HmbL-000000005vi-0000@mail.test.ex>
-Auto-Submitted: auto-replied
-MIME-Version: 1.0
-Content-Type: text/plain;
- charset="utf-8"
-Content-Transfer-Encoding: quoted-printable
-Message-Id: <E10HmbM-000000005vi-0000@mail.test.ex>
-Date: Tue, 2 Mar 1999 09:44:33 +0000
-
-I am gone. Not here.
-
+++ /dev/null
-From CALLER@test.ex Tue Mar 02 09:44:33 1999
-Return-path: <CALLER@test.ex>
-Envelope-to: userx@test.ex
-Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
-Received: from CALLER by mail.test.ex with local (Exim x.yz)
- (envelope-from <CALLER@test.ex>)
- id 10HmaX-000000005vi-0000
- for userx@test.ex;
- Tue, 2 Mar 1999 09:44:33 +0000
-Message-Id: <E10HmaX-000000005vi-0000@mail.test.ex>
-From: CALLER_NAME <CALLER@test.ex>
-Date: Tue, 2 Mar 1999 09:44:33 +0000
-
-Test 1
-
-From CALLER@test.ex Tue Mar 02 09:44:33 1999
-Return-path: <CALLER@test.ex>
-Envelope-to: userx@test.ex
-Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
-Received: from CALLER by mail.test.ex with local (Exim x.yz)
- (envelope-from <CALLER@test.ex>)
- id 10HmbF-000000005vi-0000
- for userx@test.ex;
- Tue, 2 Mar 1999 09:44:33 +0000
-Filter: if true { stop; fileinto "inbox.never"; }
-Message-Id: <E10HmbF-000000005vi-0000@mail.test.ex>
-From: CALLER_NAME <CALLER@test.ex>
-Date: Tue, 2 Mar 1999 09:44:33 +0000
-
-Test 7
-
+++ /dev/null
-From CALLER@test.ex Tue Mar 02 09:44:33 1999
-Return-path: <CALLER@test.ex>
-Envelope-to: userx@test.ex
-Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
-Received: from CALLER by mail.test.ex with local (Exim x.yz)
- (envelope-from <CALLER@test.ex>)
- id 10HmaZ-000000005vi-0000
- for userx@test.ex;
- Tue, 2 Mar 1999 09:44:33 +0000
-Filter: require "fileinto"; fileinto "userx-extra";
-Message-Id: <E10HmaZ-000000005vi-0000@mail.test.ex>
-From: CALLER_NAME <CALLER@test.ex>
-Date: Tue, 2 Mar 1999 09:44:33 +0000
-
-Test 3
-
+++ /dev/null
-From someone@test.ex Tue Mar 02 09:44:33 1999
-Return-path: <someone@test.ex>
-Envelope-to: userx14-suffix@test.ex
-Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
-Received: from CALLER by mail.test.ex with local (Exim x.yz)
- (envelope-from <someone@test.ex>)
- id 10HmbN-000000005vi-0000;
- Tue, 2 Mar 1999 09:44:33 +0000
-Message-Id: <E10HmbN-000000005vi-0000@mail.test.ex>
-From: someone@test.ex
-Date: Tue, 2 Mar 1999 09:44:33 +0000
-
- require ["envelope","fileinto"];
- if envelope :matches :localpart "to" "*-suffix" {
- fileinto "userx-sawsuffix";
- stop;
- }
-Test 14
-
+++ /dev/null
-From someone@test.ex Tue Mar 02 09:44:33 1999
-Return-path: <someone@test.ex>
-Envelope-to: userx13@test.ex
-Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
-Received: from CALLER by mail.test.ex with local (Exim x.yz)
- (envelope-from <someone@test.ex>)
- id 10HmbL-000000005vi-0000
- for userx13@test.ex;
- Tue, 2 Mar 1999 09:44:33 +0000
-To: userx13@test.ex
-Message-Id: <E10HmbL-000000005vi-0000@mail.test.ex>
-From: someone@test.ex
-Date: Tue, 2 Mar 1999 09:44:33 +0000
-
- require ["vacation"];
- vacation "I am gone. Not here.";
-Test 13
-
+++ /dev/null
-From someone@test.ex Tue Mar 02 09:44:33 1999
-Return-path: <someone@test.ex>
-Envelope-to: userx14-suffix2@test.ex
-Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
-Received: from CALLER by mail.test.ex with local (Exim x.yz)
- (envelope-from <someone@test.ex>)
- id 10HmbN-000000005vi-0000;
- Tue, 2 Mar 1999 09:44:33 +0000
-Message-Id: <E10HmbN-000000005vi-0000@mail.test.ex>
-From: someone@test.ex
-Date: Tue, 2 Mar 1999 09:44:33 +0000
-
- require ["envelope","fileinto"];
- if envelope :matches :localpart "to" "*-suffix" {
- fileinto "userx-sawsuffix";
- stop;
- }
-Test 14
-
+++ /dev/null
-From CALLER@test.ex Tue Mar 02 09:44:33 1999
-Return-path: <CALLER@test.ex>
-Envelope-to: userx9@test.ex
-Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
-Received: from CALLER by mail.test.ex with local (Exim x.yz)
- (envelope-from <CALLER@test.ex>)
- id 10HmbH-000000005vi-0000
- for userx9@test.ex;
- Tue, 2 Mar 1999 09:44:33 +0000
-X-Sieve: 99
-Message-Id: <E10HmbH-000000005vi-0000@mail.test.ex>
-From: CALLER_NAME <CALLER@test.ex>
-Date: Tue, 2 Mar 1999 09:44:33 +0000
-
- require["fileinto","comparator-i;ascii-numeric"];
- if header :comparator "i;ascii-numeric" "X-Sieve" "98" {
- fileinto "inbox.JUNK";
- stop;
- }
-Test 9
-
+++ /dev/null
-From MAILER-DAEMON Tue Mar 02 09:44:33 1999
-Return-path: <>
-Envelope-to: CALLER@test.ex
-Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
-Received: from [127.0.0.1] (helo=tester)
- by myhost.test.ex with smtp (Exim x.yz)
- (envelope-from <implcit@test.ex>)
- id 10HmaX-000000005vi-0000
- for CALLER@test.ex;
- Tue, 2 Mar 1999 09:44:33 +0000
-From: <good@test.ex>
-Subject: this should be accepted and filed
-
-a single body line
-
+++ /dev/null
-From MAILER-DAEMON Tue Mar 02 09:44:33 1999
-Return-path: <>
-Envelope-to: CALLER@test.ex
-Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
-Received: from [127.0.0.1] (helo=tester)
- by myhost.test.ex with smtp (Exim x.yz)
- (envelope-from <identified@test.ex>)
- id 10HmaZ-000000005vi-0000
- for CALLER@test.ex;
- Tue, 2 Mar 1999 09:44:33 +0000
-From: <spot_this@test.ex>
-Subject: this should be delivered to a speicifc place by the filter
-
-
--- /dev/null
+From MAILER-DAEMON Tue Mar 02 09:44:33 1999
+Return-path: <>
+Envelope-to: CALLER@test.ex
+Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
+Received: from EXIMUSER by mail.test.ex with local (Exim x.yz)
+ id 10HmbC-000000005vi-0000
+ for CALLER@test.ex;
+ Tue, 2 Mar 1999 09:44:33 +0000
+X-Failed-Recipients: restrict-userx@test.ex
+Auto-Submitted: auto-replied
+From: Mail Delivery System <Mailer-Daemon@test.ex>
+To: CALLER@test.ex
+References: <E10HmbB-000000005vi-0000@mail.test.ex>
+Content-Type: multipart/report; report-type=delivery-status; boundary=NNNNNNNNNN-eximdsn-MMMMMMMMMM
+MIME-Version: 1.0
+Subject: Mail delivery failed: returning message to sender
+Message-Id: <E10HmbC-000000005vi-0000@mail.test.ex>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+--NNNNNNNNNN-eximdsn-MMMMMMMMMM
+Content-type: text/plain; charset=us-ascii
+
+This message was created automatically by mail delivery software.
+
+A message that you sent could not be delivered to one or more of its
+recipients. This is a permanent error. The following address(es) failed:
+
+ save to userx-extra
+ generated by restrict-userx@test.ex
+
+--NNNNNNNNNN-eximdsn-MMMMMMMMMM
+Content-type: message/delivery-status
+
+Reporting-MTA: dns; mail.test.ex
+
+Action: failed
+Final-Recipient: rfc822;restrict-userx@test.ex
+Status: 5.0.0
+
+--NNNNNNNNNN-eximdsn-MMMMMMMMMM
+Content-type: message/rfc822
+
+Return-path: <CALLER@test.ex>
+Received: from CALLER by mail.test.ex with local (Exim x.yz)
+ (envelope-from <CALLER@test.ex>)
+ id 10HmbB-000000005vi-0000
+ for restrict-userx@test.ex;
+ Tue, 2 Mar 1999 09:44:33 +0000
+Filter: require "fileinto"; fileinto "userx-extra";
+Message-Id: <E10HmbB-000000005vi-0000@mail.test.ex>
+From: CALLER_NAME <CALLER@test.ex>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+Test 5
+
+--NNNNNNNNNN-eximdsn-MMMMMMMMMM--
+
+From MAILER-DAEMON Tue Mar 02 09:44:33 1999
+Return-path: <>
+Envelope-to: CALLER@test.ex
+Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
+Received: from EXIMUSER by mail.test.ex with local (Exim x.yz)
+ id 10HmbE-000000005vi-0000
+ for CALLER@test.ex;
+ Tue, 2 Mar 1999 09:44:33 +0000
+X-Failed-Recipients: restrict-userx@test.ex
+Auto-Submitted: auto-replied
+From: Mail Delivery System <Mailer-Daemon@test.ex>
+To: CALLER@test.ex
+References: <E10HmbD-000000005vi-0000@mail.test.ex>
+Content-Type: multipart/report; report-type=delivery-status; boundary=NNNNNNNNNN-eximdsn-MMMMMMMMMM
+MIME-Version: 1.0
+Subject: Mail delivery failed: returning message to sender
+Message-Id: <E10HmbE-000000005vi-0000@mail.test.ex>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+--NNNNNNNNNN-eximdsn-MMMMMMMMMM
+Content-type: text/plain; charset=us-ascii
+
+This message was created automatically by mail delivery software.
+
+A message that you sent could not be delivered to one or more of its
+recipients. This is a permanent error. The following address(es) failed:
+
+ save to inbox
+ generated by restrict-userx@test.ex
+
+--NNNNNNNNNN-eximdsn-MMMMMMMMMM
+Content-type: message/delivery-status
+
+Reporting-MTA: dns; mail.test.ex
+
+Action: failed
+Final-Recipient: rfc822;restrict-userx@test.ex
+Status: 5.0.0
+
+--NNNNNNNNNN-eximdsn-MMMMMMMMMM
+Content-type: message/rfc822
+
+Return-path: <CALLER@test.ex>
+Received: from CALLER by mail.test.ex with local (Exim x.yz)
+ (envelope-from <CALLER@test.ex>)
+ id 10HmbD-000000005vi-0000
+ for restrict-userx@test.ex;
+ Tue, 2 Mar 1999 09:44:33 +0000
+Filter: fileinto "userx-extra";
+Message-Id: <E10HmbD-000000005vi-0000@mail.test.ex>
+From: CALLER_NAME <CALLER@test.ex>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+Test 6
+
+--NNNNNNNNNN-eximdsn-MMMMMMMMMM--
+
--- /dev/null
+From CALLER@test.ex Tue Mar 02 09:44:33 1999
+Return-path: <CALLER@test.ex>
+Envelope-to: userx8@test.ex
+Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
+Received: from CALLER by mail.test.ex with local (Exim x.yz)
+ (envelope-from <CALLER@test.ex>)
+ id 10HmbG-000000005vi-0000
+ for userx8@test.ex;
+ Tue, 2 Mar 1999 09:44:33 +0000
+X-Sieve: 99
+Message-Id: <E10HmbG-000000005vi-0000@mail.test.ex>
+From: CALLER_NAME <CALLER@test.ex>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+ require["fileinto","comparator-i;ascii-numeric"];
+ if header :comparator "i;ascii-numeric" "X-Sieve" "99" {
+ fileinto "inbox.JUNK";
+ stop;
+ }
+Test 8
+
+From CALLER@test.ex Tue Mar 02 09:44:33 1999
+Return-path: <CALLER@test.ex>
+Envelope-to: userx10@test.ex
+Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
+Received: from CALLER by mail.test.ex with local (Exim x.yz)
+ (envelope-from <CALLER@test.ex>)
+ id 10HmbI-000000005vi-0000
+ for userx10@test.ex;
+ Tue, 2 Mar 1999 09:44:33 +0000
+X-Sieve: 99-
+Message-Id: <E10HmbI-000000005vi-0000@mail.test.ex>
+From: CALLER_NAME <CALLER@test.ex>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+ require["fileinto","comparator-i;ascii-numeric"];
+ if header :comparator "i;ascii-numeric" "X-Sieve" "99" {
+ fileinto "inbox.JUNK";
+ stop;
+ }
+Test 10
+
+From CALLER@test.ex Tue Mar 02 09:44:33 1999
+Return-path: <CALLER@test.ex>
+Envelope-to: userx11@test.ex
+Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
+Received: from CALLER by mail.test.ex with local (Exim x.yz)
+ (envelope-from <CALLER@test.ex>)
+ id 10HmbJ-000000005vi-0000
+ for userx11@test.ex;
+ Tue, 2 Mar 1999 09:44:33 +0000
+X-Sieve: -99
+Message-Id: <E10HmbJ-000000005vi-0000@mail.test.ex>
+From: CALLER_NAME <CALLER@test.ex>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+ require["fileinto","comparator-i;ascii-numeric"];
+ if header :comparator "i;ascii-numeric" "X-Sieve" "-99" {
+ fileinto "inbox.JUNK";
+ stop;
+ }
+Test 11
+
+From CALLER@test.ex Tue Mar 02 09:44:33 1999
+Return-path: <CALLER@test.ex>
+Envelope-to: userx12@test.ex
+Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
+Received: from CALLER by mail.test.ex with local (Exim x.yz)
+ (envelope-from <CALLER@test.ex>)
+ id 10HmbK-000000005vi-0000
+ for userx12@test.ex;
+ Tue, 2 Mar 1999 09:44:33 +0000
+X-Sieve: -99
+Message-Id: <E10HmbK-000000005vi-0000@mail.test.ex>
+From: CALLER_NAME <CALLER@test.ex>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+ require["fileinto","comparator-i;ascii-numeric"];
+ if header :comparator "i;ascii-numeric" "X-Sieve" "-98" {
+ fileinto "inbox.JUNK";
+ stop;
+ }
+Test 12
+
--- /dev/null
+From CALLER@test.ex Tue Mar 02 09:44:33 1999
+Return-path: <CALLER@test.ex>
+Envelope-to: userx_inbox@test.ex
+Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
+Received: from CALLER by mail.test.ex with local (Exim x.yz)
+ (envelope-from <CALLER@test.ex>)
+ id 10HmbO-000000005vi-0000
+ for userx_inbox@test.ex;
+ Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmbO-000000005vi-0000@mail.test.ex>
+From: CALLER_NAME <CALLER@test.ex>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+Test 15
+
--- /dev/null
+From CALLER@test.ex Tue Mar 02 09:44:33 1999
+Return-path: <CALLER@test.ex>
+Envelope-to: userx@test.ex
+Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
+Received: from CALLER by mail.test.ex with local (Exim x.yz)
+ (envelope-from <CALLER@test.ex>)
+ id 10HmbA-000000005vi-0000
+ for userx@test.ex;
+ Tue, 2 Mar 1999 09:44:33 +0000
+Filter: redirect "redirected@test.ex";
+Message-Id: <E10HmbA-000000005vi-0000@mail.test.ex>
+From: CALLER_NAME <CALLER@test.ex>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+Test 4
+
--- /dev/null
+From MAILER-DAEMON Tue Mar 02 09:44:33 1999
+Return-path: <>
+Envelope-to: someone@test.ex
+Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
+Received: from CALLER by mail.test.ex with local (Exim x.yz)
+ id 10HmbM-000000005vi-0000
+ for someone@test.ex;
+ Tue, 2 Mar 1999 09:44:33 +0000
+From: userx13@test.ex
+To: someone@test.ex
+Subject: Automated reply
+In-Reply-To: <E10HmbL-000000005vi-0000@mail.test.ex>
+References: <E10HmbL-000000005vi-0000@mail.test.ex>
+Auto-Submitted: auto-replied
+MIME-Version: 1.0
+Content-Type: text/plain;
+ charset="utf-8"
+Content-Transfer-Encoding: quoted-printable
+Message-Id: <E10HmbM-000000005vi-0000@mail.test.ex>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+I am gone. Not here.
+
--- /dev/null
+From CALLER@test.ex Tue Mar 02 09:44:33 1999
+Return-path: <CALLER@test.ex>
+Envelope-to: userx@test.ex
+Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
+Received: from CALLER by mail.test.ex with local (Exim x.yz)
+ (envelope-from <CALLER@test.ex>)
+ id 10HmaX-000000005vi-0000
+ for userx@test.ex;
+ Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmaX-000000005vi-0000@mail.test.ex>
+From: CALLER_NAME <CALLER@test.ex>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+Test 1
+
+From CALLER@test.ex Tue Mar 02 09:44:33 1999
+Return-path: <CALLER@test.ex>
+Envelope-to: userx@test.ex
+Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
+Received: from CALLER by mail.test.ex with local (Exim x.yz)
+ (envelope-from <CALLER@test.ex>)
+ id 10HmbF-000000005vi-0000
+ for userx@test.ex;
+ Tue, 2 Mar 1999 09:44:33 +0000
+Filter: if true { stop; fileinto "inbox.never"; }
+Message-Id: <E10HmbF-000000005vi-0000@mail.test.ex>
+From: CALLER_NAME <CALLER@test.ex>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+Test 7
+
--- /dev/null
+From CALLER@test.ex Tue Mar 02 09:44:33 1999
+Return-path: <CALLER@test.ex>
+Envelope-to: userx@test.ex
+Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
+Received: from CALLER by mail.test.ex with local (Exim x.yz)
+ (envelope-from <CALLER@test.ex>)
+ id 10HmaZ-000000005vi-0000
+ for userx@test.ex;
+ Tue, 2 Mar 1999 09:44:33 +0000
+Filter: require "fileinto"; fileinto "userx-extra";
+Message-Id: <E10HmaZ-000000005vi-0000@mail.test.ex>
+From: CALLER_NAME <CALLER@test.ex>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+Test 3
+
--- /dev/null
+From someone@test.ex Tue Mar 02 09:44:33 1999
+Return-path: <someone@test.ex>
+Envelope-to: userx14-suffix@test.ex
+Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
+Received: from CALLER by mail.test.ex with local (Exim x.yz)
+ (envelope-from <someone@test.ex>)
+ id 10HmbN-000000005vi-0000;
+ Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmbN-000000005vi-0000@mail.test.ex>
+From: someone@test.ex
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+ require ["envelope","fileinto"];
+ if envelope :matches :localpart "to" "*-suffix" {
+ fileinto "userx-sawsuffix";
+ stop;
+ }
+Test 14
+
--- /dev/null
+From someone@test.ex Tue Mar 02 09:44:33 1999
+Return-path: <someone@test.ex>
+Envelope-to: userx13@test.ex
+Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
+Received: from CALLER by mail.test.ex with local (Exim x.yz)
+ (envelope-from <someone@test.ex>)
+ id 10HmbL-000000005vi-0000
+ for userx13@test.ex;
+ Tue, 2 Mar 1999 09:44:33 +0000
+To: userx13@test.ex
+Message-Id: <E10HmbL-000000005vi-0000@mail.test.ex>
+From: someone@test.ex
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+ require ["vacation"];
+ vacation "I am gone. Not here.";
+Test 13
+
--- /dev/null
+From someone@test.ex Tue Mar 02 09:44:33 1999
+Return-path: <someone@test.ex>
+Envelope-to: userx14-suffix2@test.ex
+Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
+Received: from CALLER by mail.test.ex with local (Exim x.yz)
+ (envelope-from <someone@test.ex>)
+ id 10HmbN-000000005vi-0000;
+ Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmbN-000000005vi-0000@mail.test.ex>
+From: someone@test.ex
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+ require ["envelope","fileinto"];
+ if envelope :matches :localpart "to" "*-suffix" {
+ fileinto "userx-sawsuffix";
+ stop;
+ }
+Test 14
+
--- /dev/null
+From CALLER@test.ex Tue Mar 02 09:44:33 1999
+Return-path: <CALLER@test.ex>
+Envelope-to: userx9@test.ex
+Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
+Received: from CALLER by mail.test.ex with local (Exim x.yz)
+ (envelope-from <CALLER@test.ex>)
+ id 10HmbH-000000005vi-0000
+ for userx9@test.ex;
+ Tue, 2 Mar 1999 09:44:33 +0000
+X-Sieve: 99
+Message-Id: <E10HmbH-000000005vi-0000@mail.test.ex>
+From: CALLER_NAME <CALLER@test.ex>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+ require["fileinto","comparator-i;ascii-numeric"];
+ if header :comparator "i;ascii-numeric" "X-Sieve" "98" {
+ fileinto "inbox.JUNK";
+ stop;
+ }
+Test 9
+
--- /dev/null
+From MAILER-DAEMON Tue Mar 02 09:44:33 1999
+Return-path: <>
+Envelope-to: CALLER@test.ex
+Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
+Received: from [127.0.0.1] (helo=tester)
+ by myhost.test.ex with smtp (Exim x.yz)
+ (envelope-from <implcit@test.ex>)
+ id 10HmaX-000000005vi-0000
+ for CALLER@test.ex;
+ Tue, 2 Mar 1999 09:44:33 +0000
+From: <good@test.ex>
+Subject: this should be accepted and filed
+
+a single body line
+
--- /dev/null
+From MAILER-DAEMON Tue Mar 02 09:44:33 1999
+Return-path: <>
+Envelope-to: CALLER@test.ex
+Delivery-date: Tue, 2 Mar 1999 09:44:33 +0000
+Received: from [127.0.0.1] (helo=tester)
+ by myhost.test.ex with smtp (Exim x.yz)
+ (envelope-from <identified@test.ex>)
+ id 10HmaZ-000000005vi-0000
+ for CALLER@test.ex;
+ Tue, 2 Mar 1999 09:44:33 +0000
+From: <spot_this@test.ex>
+Subject: this should be delivered to a speicifc place by the filter
+
+
+++ /dev/null
-# Sieve tests using -bf
-rmfiltertest
-catwrite test-data
-# Sieve filter
-if address ["From","To"] "marian@abcdefgh.example"
- { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if address :is "From" "marian@abcdefgh.example"
- { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if address :is "To" "marian@abcdefgh.example"
- { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if address :contains "To" "abcdefgh"
- { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if address :matches "To" "*abc?efgh*"
- { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if true { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if false { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if allof (false,false) { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if allof (true,false) { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if allof (false,true) { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if allof (true,true) { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if anyof (false,false) { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if anyof (true,false) { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if anyof (false,true) { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if anyof (true,true) { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if not anyof (false,false) { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if not anyof (true,false) { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if not anyof (false,true) { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if not anyof (true,true) { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if header "to" "MARIAN@abcdefgh.example" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if header :comparator "i;octet" "to" "MARIAN@abcdefgh.example" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if header :comparator "i;ascii-casemap" "to" "MARIAN@abcdefgh.example" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-require "envelope";
-if envelope "from" "marian@somenet.example" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-require "envelope";
-if envelope "from" "offerqn@bpk.example.com" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if exists [ "X-NotHere", "X-Notheretoo" ] { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if exists [ "X-NotHere", "Delivered-To" ] { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if exists [ "From", "Delivered-To" ] { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if header :contains "X-NotHere" "" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if header "Delivered-To" "" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if header :contains "Delivered-To" "" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if header :contains "Delivered-To" "rc@irc.somenet.example" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if header "Delivered-To" ["irc@irc.somenet.example"] { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if header "Delivered-To" ["irc@irc.somenet.example","irc@01019somenet.example","some.one"] { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if header "Mime-Version" "1.0" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if false { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if true { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if true { discard; } else { keep; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if false { keep; } else { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if false { keep; } elsif true { discard; } else { keep; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if true { discard; } elsif true { keep; } else { keep; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if false { keep; }
- else
- { if true { discard; } else { keep; } }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if header :matches "to" "MA*AN@abc*fg?.example" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if not header :matches "to" "MA?AN@abc*fg?.example" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if header :matches "to" "*marian@abcdefgh.example" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if not header :matches "to" "?marian@abcdefgh.example" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if header :matches "to" "marian@abcdefgh.example*" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if not header :matches "to" "marian@abcdefgh.example?" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if header :matches "x-special1" "\\?*\\*" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if header :matches "x-special1" "*\0*" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if not header :matches "x-special1" "*\0*q" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-require "encoded-character";
-if not header :matches "x-special1" "*${hex:00}*" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if not header :matches "x-special2" "\\?*\\*" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if not header :matches "x-special2" "*\0*" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if size :over 400 {
- discard;
- }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if size :over 4K {
- discard;
- }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if size :under 4K { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if size :under 400 { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if false { keep; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-# Syntax checks
-catwrite test-data
-# no filter line here
-if unknowntest { keep; }
-****
-1
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if test keep; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if test { keep;
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if true { unknownaction; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if true { fileinto abcdefgh; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-require "fileinto";
-if true { fileinto "abcdefgh"; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if header ["to"] [ "marian@abcdefgh.example", "achnee"] { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if header "to" [ "egal", "achnee" { keep; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if header "to","from"] "egal" { keep; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if header ["to","from" "egal" { keep; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if header ["to",,"from"] "egal" { keep; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if header ["to",] "egal" { keep; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if header [,"to"] "egal" { keep; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if unknowntest { keep; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if header :matches "to" "egal" { keep; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-if header :unknown "to" "egal" { keep; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message
-****
-catwrite test-data
-# Sieve filter
-#
-require ["fileinto", "envelope"];
-
-if header :matches "X-Warning" "* is listed at list.dsbl.org*"
- {
- keep; # keep in "In" folder
- }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message2
-****
-catwrite test-data
-# Sieve filter
-if header "x-1" "1" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message3
-****
-catwrite test-data
-# Sieve filter
-if not header "x-1" "=?ISO-8859-1?Q?=31?=" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message3
-****
-catwrite test-data
-# Sieve filter
-if header "x-1b" "=?ISO-8859-1?Q?=31=" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message3
-****
-catwrite test-data
-# Sieve filter
-if header "x-b64" "This is BASE64" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message3
-****
-catwrite test-data
-# Sieve filter
-if header "x-b64-broken" "=?iso-8859-1?b?VGhpcyBpcyBCQVNFNjQ?=" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message3
-****
-catwrite test-data
-# Sieve filter
-if header "x-q75total" "0123456789012345678901234567890123456789012345678901234567" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message3
-****
-catwrite test-data
-# Sieve filter
-if header "x-q76total" "=?ISO-8859-1?Q?01234567890123456789012345678901234567890123456789012345678?=" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message3
-****
-catwrite test-data
-# Sieve filter
-if header "X-Wrapped" "eins zwei drei" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message3
-****
-catwrite test-data
-# Sieve filter
-if header "x-nomimewrap" "=?iso-8859-1?q?abc def ghi?=" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message3
-****
-catwrite test-data
-# Sieve filter
-if header "subject" "abcdefghi" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message3
-****
-catwrite test-data
-# Sieve filter
-if header "x-mixed" "abc def" { discard; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message3
-****
-catwrite test-data
-#Sieve filter
-if true { stop; fileinto "inbox.never"; }
-****
-exim -bf test-data <aux-fixed/TESTNUM.message3
-****
+++ /dev/null
-# Sieve tests with actual delivery
-exim -odi userx
-Test 1
-****
-exim -odi userx
-Filter: discard;
-Test 2
-****
-exim -odi userx
-Filter: require "fileinto"; fileinto "userx-extra";
-Test 3
-****
-exim -odi userx
-Filter: redirect "redirected@test.ex";
-Test 4
-****
-exim -odi restrict-userx
-Filter: require "fileinto"; fileinto "userx-extra";
-Test 5
-****
-# Syntax error in Sieve filter (missing "require")
-exim -odi restrict-userx
-Filter: fileinto "userx-extra";
-Test 6
-****
-# Test stop inside a block
-exim -odi userx
-Filter: if true { stop; fileinto "inbox.never"; }
-Test 7
-****
-# This should fileinto inbox.JUNK (99 equal 99):
-exim -odi userx8
-X-Sieve: 99
-
- require["fileinto","comparator-i;ascii-numeric"];
- if header :comparator "i;ascii-numeric" "X-Sieve" "99" {
- fileinto "inbox.JUNK";
- stop;
- }
-Test 8
-****
-# This should not fileinto inbox.JUNK (98 not equal 99):
-exim -odi userx9
-X-Sieve: 99
-
- require["fileinto","comparator-i;ascii-numeric"];
- if header :comparator "i;ascii-numeric" "X-Sieve" "98" {
- fileinto "inbox.JUNK";
- stop;
- }
-Test 9
-****
-# This should fileinto inbox.JUNK (99-suffix equal 99):
-exim -odi userx10
-X-Sieve: 99-
-
- require["fileinto","comparator-i;ascii-numeric"];
- if header :comparator "i;ascii-numeric" "X-Sieve" "99" {
- fileinto "inbox.JUNK";
- stop;
- }
-Test 10
-****
-# This should fileinto inbox.JUNK (non-numeric equal non-numeric):
-exim -odi userx11
-X-Sieve: -99
-
- require["fileinto","comparator-i;ascii-numeric"];
- if header :comparator "i;ascii-numeric" "X-Sieve" "-99" {
- fileinto "inbox.JUNK";
- stop;
- }
-Test 11
-****
-# This should fileinto inbox.JUNK (non-numeric equal non-numeric):
-exim -odi userx12
-X-Sieve: -99
-
- require["fileinto","comparator-i;ascii-numeric"];
- if header :comparator "i;ascii-numeric" "X-Sieve" "-98" {
- fileinto "inbox.JUNK";
- stop;
- }
-Test 12
-****
-# This is a simple test of "vacation"
-exim -odi -f someone@test.ex userx13
-To: userx13@test.ex
-
- require ["vacation"];
- vacation "I am gone. Not here.";
-Test 13
-****
-# Test use of suffix
-exim -odi -f someone@test.ex userx14-suffix userx14-suffix2
-
- require ["envelope","fileinto"];
- if envelope :matches :localpart "to" "*-suffix" {
- fileinto "userx-sawsuffix";
- stop;
- }
-Test 14
-****
-exim -odi userx_inbox
-Test 15
-****
-# forbid_sieve_filter and forbid_exim_filter
-exim -odi sieve-filter@test.ex exim-filter@test.ex
+# forbid_exim_filter
+exim -odi exim-filter@test.ex
****
-exim -DFORBID=forbid_sieve_filter -odi sieve-filter@test.ex exim-filter@test.ex
+exim -DFORBID=forbid_sieve_filter -odi exim-filter@test.ex
****
-exim -DFORBID=forbid_exim_filter -odi sieve-filter@test.ex exim-filter@test.ex
+exim -DFORBID=forbid_exim_filter -odi exim-filter@test.ex
****
-exim -DALLOW= -odi sieve-filter@test.ex exim-filter@test.ex
+exim -DALLOW= -odi exim-filter@test.ex
****
no_msglog_check
+++ /dev/null
-# Sieve filter: basic classify reject/deliver
-#
-exim -bd -DSERVER=server -oX PORT_D
-****
-#
-#
-client 127.0.0.1 PORT_D
-??? 220
-HELO tester
-??? 250
-MAIL FROM:<implcit@test.ex>
-??? 250
-RCPT TO:<CALLER@test.ex>
-??? 250
-DATA
-??? 354
-From: <good@test.ex>
-Subject: this should be accepted and filed
-
-a single body line
-.
-??? 250
-RSET
-??? 250
-+++ 1
-MAIL FROM:<discard@test.ex>
-??? 250
-RCPT TO:<CALLER@test.ex>
-??? 250
-DATA
-??? 354
-From: <coyote@test.ex>
-Subject: this should be discarded by the filter
-.
-??? 250
-RSET
-??? 250
-+++ 1
-MAIL FROM:<identified@test.ex>
-??? 250
-RCPT TO:<CALLER@test.ex>
-??? 250
-DATA
-??? 354
-From: <spot_this@test.ex>
-Subject: this should be delivered to a speicifc place by the filter
-.
-??? 250
-RSET
-??? 250
-+++ 1
-MAIL FROM:<redirect@test.ex>
-??? 250
-RCPT TO:<CALLER@test.ex>
-??? 250
-DATA
-??? 354
-From: <redirect@test.ex>
-Subject: this should be redirected by the filter to a different address
-.
-??? 250
-QUIT
-??? 221
-****
-#
-millisleep 500
-killdaemon
--- /dev/null
+# Sieve tests using -bf
+rmfiltertest
+catwrite test-data
+# Sieve filter
+if address ["From","To"] "marian@abcdefgh.example"
+ { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if address :is "From" "marian@abcdefgh.example"
+ { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if address :is "To" "marian@abcdefgh.example"
+ { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if address :contains "To" "abcdefgh"
+ { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if address :matches "To" "*abc?efgh*"
+ { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if true { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if false { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if allof (false,false) { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if allof (true,false) { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if allof (false,true) { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if allof (true,true) { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if anyof (false,false) { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if anyof (true,false) { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if anyof (false,true) { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if anyof (true,true) { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if not anyof (false,false) { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if not anyof (true,false) { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if not anyof (false,true) { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if not anyof (true,true) { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if header "to" "MARIAN@abcdefgh.example" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if header :comparator "i;octet" "to" "MARIAN@abcdefgh.example" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if header :comparator "i;ascii-casemap" "to" "MARIAN@abcdefgh.example" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+require "envelope";
+if envelope "from" "marian@somenet.example" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+require "envelope";
+if envelope "from" "offerqn@bpk.example.com" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if exists [ "X-NotHere", "X-Notheretoo" ] { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if exists [ "X-NotHere", "Delivered-To" ] { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if exists [ "From", "Delivered-To" ] { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if header :contains "X-NotHere" "" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if header "Delivered-To" "" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if header :contains "Delivered-To" "" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if header :contains "Delivered-To" "rc@irc.somenet.example" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if header "Delivered-To" ["irc@irc.somenet.example"] { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if header "Delivered-To" ["irc@irc.somenet.example","irc@01019somenet.example","some.one"] { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if header "Mime-Version" "1.0" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if false { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if true { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if true { discard; } else { keep; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if false { keep; } else { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if false { keep; } elsif true { discard; } else { keep; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if true { discard; } elsif true { keep; } else { keep; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if false { keep; }
+ else
+ { if true { discard; } else { keep; } }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if header :matches "to" "MA*AN@abc*fg?.example" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if not header :matches "to" "MA?AN@abc*fg?.example" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if header :matches "to" "*marian@abcdefgh.example" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if not header :matches "to" "?marian@abcdefgh.example" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if header :matches "to" "marian@abcdefgh.example*" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if not header :matches "to" "marian@abcdefgh.example?" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if header :matches "x-special1" "\\?*\\*" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if header :matches "x-special1" "*\0*" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if not header :matches "x-special1" "*\0*q" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+require "encoded-character";
+if not header :matches "x-special1" "*${hex:00}*" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if not header :matches "x-special2" "\\?*\\*" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if not header :matches "x-special2" "*\0*" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if size :over 400 {
+ discard;
+ }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if size :over 4K {
+ discard;
+ }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if size :under 4K { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if size :under 400 { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if false { keep; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+# Syntax checks
+catwrite test-data
+# no filter line here
+if unknowntest { keep; }
+****
+1
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if test keep; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if test { keep;
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if true { unknownaction; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if true { fileinto abcdefgh; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+require "fileinto";
+if true { fileinto "abcdefgh"; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if header ["to"] [ "marian@abcdefgh.example", "achnee"] { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if header "to" [ "egal", "achnee" { keep; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if header "to","from"] "egal" { keep; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if header ["to","from" "egal" { keep; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if header ["to",,"from"] "egal" { keep; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if header ["to",] "egal" { keep; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if header [,"to"] "egal" { keep; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if unknowntest { keep; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if header :matches "to" "egal" { keep; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+if header :unknown "to" "egal" { keep; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message
+****
+catwrite test-data
+# Sieve filter
+#
+require ["fileinto", "envelope"];
+
+if header :matches "X-Warning" "* is listed at list.dsbl.org*"
+ {
+ keep; # keep in "In" folder
+ }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message2
+****
+catwrite test-data
+# Sieve filter
+if header "x-1" "1" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message3
+****
+catwrite test-data
+# Sieve filter
+if not header "x-1" "=?ISO-8859-1?Q?=31?=" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message3
+****
+catwrite test-data
+# Sieve filter
+if header "x-1b" "=?ISO-8859-1?Q?=31=" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message3
+****
+catwrite test-data
+# Sieve filter
+if header "x-b64" "This is BASE64" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message3
+****
+catwrite test-data
+# Sieve filter
+if header "x-b64-broken" "=?iso-8859-1?b?VGhpcyBpcyBCQVNFNjQ?=" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message3
+****
+catwrite test-data
+# Sieve filter
+if header "x-q75total" "0123456789012345678901234567890123456789012345678901234567" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message3
+****
+catwrite test-data
+# Sieve filter
+if header "x-q76total" "=?ISO-8859-1?Q?01234567890123456789012345678901234567890123456789012345678?=" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message3
+****
+catwrite test-data
+# Sieve filter
+if header "X-Wrapped" "eins zwei drei" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message3
+****
+catwrite test-data
+# Sieve filter
+if header "x-nomimewrap" "=?iso-8859-1?q?abc def ghi?=" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message3
+****
+catwrite test-data
+# Sieve filter
+if header "subject" "abcdefghi" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message3
+****
+catwrite test-data
+# Sieve filter
+if header "x-mixed" "abc def" { discard; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message3
+****
+catwrite test-data
+#Sieve filter
+if true { stop; fileinto "inbox.never"; }
+****
+exim -bf test-data <aux-fixed/TESTNUM.message3
+****
--- /dev/null
+# Sieve tests with actual delivery
+exim -odi userx
+Test 1
+****
+exim -odi userx
+Filter: discard;
+Test 2
+****
+exim -odi userx
+Filter: require "fileinto"; fileinto "userx-extra";
+Test 3
+****
+exim -odi userx
+Filter: redirect "redirected@test.ex";
+Test 4
+****
+exim -odi restrict-userx
+Filter: require "fileinto"; fileinto "userx-extra";
+Test 5
+****
+# Syntax error in Sieve filter (missing "require")
+exim -odi restrict-userx
+Filter: fileinto "userx-extra";
+Test 6
+****
+# Test stop inside a block
+exim -odi userx
+Filter: if true { stop; fileinto "inbox.never"; }
+Test 7
+****
+# This should fileinto inbox.JUNK (99 equal 99):
+exim -odi userx8
+X-Sieve: 99
+
+ require["fileinto","comparator-i;ascii-numeric"];
+ if header :comparator "i;ascii-numeric" "X-Sieve" "99" {
+ fileinto "inbox.JUNK";
+ stop;
+ }
+Test 8
+****
+# This should not fileinto inbox.JUNK (98 not equal 99):
+exim -odi userx9
+X-Sieve: 99
+
+ require["fileinto","comparator-i;ascii-numeric"];
+ if header :comparator "i;ascii-numeric" "X-Sieve" "98" {
+ fileinto "inbox.JUNK";
+ stop;
+ }
+Test 9
+****
+# This should fileinto inbox.JUNK (99-suffix equal 99):
+exim -odi userx10
+X-Sieve: 99-
+
+ require["fileinto","comparator-i;ascii-numeric"];
+ if header :comparator "i;ascii-numeric" "X-Sieve" "99" {
+ fileinto "inbox.JUNK";
+ stop;
+ }
+Test 10
+****
+# This should fileinto inbox.JUNK (non-numeric equal non-numeric):
+exim -odi userx11
+X-Sieve: -99
+
+ require["fileinto","comparator-i;ascii-numeric"];
+ if header :comparator "i;ascii-numeric" "X-Sieve" "-99" {
+ fileinto "inbox.JUNK";
+ stop;
+ }
+Test 11
+****
+# This should fileinto inbox.JUNK (non-numeric equal non-numeric):
+exim -odi userx12
+X-Sieve: -99
+
+ require["fileinto","comparator-i;ascii-numeric"];
+ if header :comparator "i;ascii-numeric" "X-Sieve" "-98" {
+ fileinto "inbox.JUNK";
+ stop;
+ }
+Test 12
+****
+# This is a simple test of "vacation"
+exim -odi -f someone@test.ex userx13
+To: userx13@test.ex
+
+ require ["vacation"];
+ vacation "I am gone. Not here.";
+Test 13
+****
+# Test use of suffix
+exim -odi -f someone@test.ex userx14-suffix userx14-suffix2
+
+ require ["envelope","fileinto"];
+ if envelope :matches :localpart "to" "*-suffix" {
+ fileinto "userx-sawsuffix";
+ stop;
+ }
+Test 14
+****
+exim -odi userx_inbox
+Test 15
+****
--- /dev/null
+# forbid_sieve_filter
+exim -odi sieve-filter@test.ex
+****
+exim -DFORBID=forbid_sieve_filter -odi sieve-filter@test.ex
+****
+exim -DFORBID=forbid_exim_filter -odi sieve-filter@test.ex
+****
+exim -DALLOW= -odi sieve-filter@test.ex
+****
+no_msglog_check
--- /dev/null
+# Sieve filter: basic classify reject/deliver
+#
+exim -bd -DSERVER=server -oX PORT_D
+****
+#
+#
+client 127.0.0.1 PORT_D
+??? 220
+HELO tester
+??? 250
+MAIL FROM:<implcit@test.ex>
+??? 250
+RCPT TO:<CALLER@test.ex>
+??? 250
+DATA
+??? 354
+From: <good@test.ex>
+Subject: this should be accepted and filed
+
+a single body line
+.
+??? 250
+RSET
+??? 250
++++ 1
+MAIL FROM:<discard@test.ex>
+??? 250
+RCPT TO:<CALLER@test.ex>
+??? 250
+DATA
+??? 354
+From: <coyote@test.ex>
+Subject: this should be discarded by the filter
+.
+??? 250
+RSET
+??? 250
++++ 1
+MAIL FROM:<identified@test.ex>
+??? 250
+RCPT TO:<CALLER@test.ex>
+??? 250
+DATA
+??? 354
+From: <spot_this@test.ex>
+Subject: this should be delivered to a speicifc place by the filter
+.
+??? 250
+RSET
+??? 250
++++ 1
+MAIL FROM:<redirect@test.ex>
+??? 250
+RCPT TO:<CALLER@test.ex>
+??? 250
+DATA
+??? 354
+From: <redirect@test.ex>
+Subject: this should be redirected by the filter to a different address
+.
+??? 250
+QUIT
+??? 221
+****
+#
+millisleep 500
+killdaemon
--- /dev/null
+support Sieve_filter
+++ /dev/null
-==========
-# Sieve filter
-if address ["From","To"] "marian@abcdefgh.example"
- { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if address :is "From" "marian@abcdefgh.example"
- { discard; }
-==========
-Implicit keep
-==========
-# Sieve filter
-if address :is "To" "marian@abcdefgh.example"
- { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if address :contains "To" "abcdefgh"
- { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if address :matches "To" "*abc?efgh*"
- { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if true { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if false { discard; }
-==========
-Implicit keep
-==========
-# Sieve filter
-if allof (false,false) { discard; }
-==========
-Implicit keep
-==========
-# Sieve filter
-if allof (true,false) { discard; }
-==========
-Implicit keep
-==========
-# Sieve filter
-if allof (false,true) { discard; }
-==========
-Implicit keep
-==========
-# Sieve filter
-if allof (true,true) { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if anyof (false,false) { discard; }
-==========
-Implicit keep
-==========
-# Sieve filter
-if anyof (true,false) { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if anyof (false,true) { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if anyof (true,true) { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if not anyof (false,false) { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if not anyof (true,false) { discard; }
-==========
-Implicit keep
-==========
-# Sieve filter
-if not anyof (false,true) { discard; }
-==========
-Implicit keep
-==========
-# Sieve filter
-if not anyof (true,true) { discard; }
-==========
-Implicit keep
-==========
-# Sieve filter
-if header "to" "MARIAN@abcdefgh.example" { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if header :comparator "i;octet" "to" "MARIAN@abcdefgh.example" { discard; }
-==========
-Implicit keep
-==========
-# Sieve filter
-if header :comparator "i;ascii-casemap" "to" "MARIAN@abcdefgh.example" { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-require "envelope";
-if envelope "from" "marian@somenet.example" { discard; }
-==========
-Implicit keep
-==========
-# Sieve filter
-require "envelope";
-if envelope "from" "offerqn@bpk.example.com" { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if exists [ "X-NotHere", "X-Notheretoo" ] { discard; }
-==========
-Implicit keep
-==========
-# Sieve filter
-if exists [ "X-NotHere", "Delivered-To" ] { discard; }
-==========
-Implicit keep
-==========
-# Sieve filter
-if exists [ "From", "Delivered-To" ] { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if header :contains "X-NotHere" "" { discard; }
-==========
-Implicit keep
-==========
-# Sieve filter
-if header "Delivered-To" "" { discard; }
-==========
-Implicit keep
-==========
-# Sieve filter
-if header :contains "Delivered-To" "" { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if header :contains "Delivered-To" "rc@irc.somenet.example" { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if header "Delivered-To" ["irc@irc.somenet.example"] { discard; }
-==========
-Implicit keep
-==========
-# Sieve filter
-if header "Delivered-To" ["irc@irc.somenet.example","irc@01019somenet.example","some.one"] { discard; }
-==========
-Implicit keep
-==========
-# Sieve filter
-if header "Mime-Version" "1.0" { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if false { discard; }
-==========
-Implicit keep
-==========
-# Sieve filter
-if true { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if true { discard; } else { keep; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if false { keep; } else { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if false { keep; } elsif true { discard; } else { keep; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if true { discard; } elsif true { keep; } else { keep; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if false { keep; }
- else
- { if true { discard; } else { keep; } }
-==========
-No implicit keep
-==========
-# Sieve filter
-if header :matches "to" "MA*AN@abc*fg?.example" { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if not header :matches "to" "MA?AN@abc*fg?.example" { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if header :matches "to" "*marian@abcdefgh.example" { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if not header :matches "to" "?marian@abcdefgh.example" { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if header :matches "to" "marian@abcdefgh.example*" { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if not header :matches "to" "marian@abcdefgh.example?" { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if header :matches "x-special1" "\\?*\\*" { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if header :matches "x-special1" "*\0*" { discard; }
-==========
-Implicit keep
-==========
-# Sieve filter
-if not header :matches "x-special1" "*\0*q" { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-require "encoded-character";
-if not header :matches "x-special1" "*${hex:00}*" { discard; }
-==========
-Implicit keep
-==========
-# Sieve filter
-if not header :matches "x-special2" "\\?*\\*" { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if not header :matches "x-special2" "*\0*" { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if size :over 400 {
- discard;
- }
-==========
-No implicit keep
-==========
-# Sieve filter
-if size :over 4K {
- discard;
- }
-==========
-Implicit keep
-==========
-# Sieve filter
-if size :under 4K { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if size :under 400 { discard; }
-==========
-Implicit keep
-==========
-# Sieve filter
-if false { keep; }
-==========
-Implicit keep
-==========
-# no filter line here
-if unknowntest { keep; }
-==========
-Testing forward file file "test-data"
-
-exim: error in forward file: missing or malformed local part (expected word or "<") in "if unknowntest { keep; }"
-==========
-# Sieve filter
-if test keep; }
-==========
-Sieve error: missing test in line 2
-==========
-# Sieve filter
-if test { keep;
-==========
-Sieve error: missing test in line 2
-==========
-# Sieve filter
-if true { unknownaction; }
-==========
-Sieve error: expecting command or closing brace in line 2
-==========
-# Sieve filter
-if true { fileinto abcdefgh; }
-==========
-Sieve error: missing previous require "fileinto"; in line 2
-==========
-# Sieve filter
-require "fileinto";
-if true { fileinto "abcdefgh"; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if header ["to"] [ "marian@abcdefgh.example", "achnee"] { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if header "to" [ "egal", "achnee" { keep; }
-==========
-Sieve error: missing string list in line 2
-==========
-# Sieve filter
-if header "to","from"] "egal" { keep; }
-==========
-Sieve error: key string list expected in line 2
-==========
-# Sieve filter
-if header ["to","from" "egal" { keep; }
-==========
-Sieve error: missing string list in line 2
-==========
-# Sieve filter
-if header ["to",,"from"] "egal" { keep; }
-==========
-Sieve error: missing string list in line 2
-==========
-# Sieve filter
-if header ["to",] "egal" { keep; }
-==========
-Sieve error: missing string list in line 2
-==========
-# Sieve filter
-if header [,"to"] "egal" { keep; }
-==========
-Sieve error: missing string list in line 2
-==========
-# Sieve filter
-if unknowntest { keep; }
-==========
-Sieve error: missing test in line 2
-==========
-# Sieve filter
-if header :matches "to" "egal" { keep; }
-==========
-Implicit keep
-==========
-# Sieve filter
-if header :unknown "to" "egal" { keep; }
-==========
-Sieve error: header string list expected in line 2
-==========
-# Sieve filter
-#
-require ["fileinto", "envelope"];
-
-if header :matches "X-Warning" "* is listed at list.dsbl.org*"
- {
- keep; # keep in "In" folder
- }
-==========
-No implicit keep
-==========
-# Sieve filter
-if header "x-1" "1" { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if not header "x-1" "=?ISO-8859-1?Q?=31?=" { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if header "x-1b" "=?ISO-8859-1?Q?=31=" { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if header "x-b64" "This is BASE64" { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if header "x-b64-broken" "=?iso-8859-1?b?VGhpcyBpcyBCQVNFNjQ?=" { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if header "x-q75total" "0123456789012345678901234567890123456789012345678901234567" { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if header "x-q76total" "=?ISO-8859-1?Q?01234567890123456789012345678901234567890123456789012345678?=" { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if header "X-Wrapped" "eins zwei drei" { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if header "x-nomimewrap" "=?iso-8859-1?q?abc def ghi?=" { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if header "subject" "abcdefghi" { discard; }
-==========
-No implicit keep
-==========
-# Sieve filter
-if header "x-mixed" "abc def" { discard; }
-==========
-No implicit keep
-==========
-#Sieve filter
-if true { stop; fileinto "inbox.never"; }
-==========
-Implicit keep
+++ /dev/null
-Connecting to 127.0.0.1 port 1225 ... connected
-??? 220
-<<< 220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
->>> HELO tester
-??? 250
-<<< 250 myhost.test.ex Hello tester [127.0.0.1]
->>> MAIL FROM:<implcit@test.ex>
-??? 250
-<<< 250 OK
->>> RCPT TO:<CALLER@test.ex>
-??? 250
-<<< 250 Accepted
->>> DATA
-??? 354
-<<< 354 Enter message, ending with "." on a line by itself
->>> From: <good@test.ex>
->>> Subject: this should be accepted and filed
->>>
->>> a single body line
->>> .
-??? 250
-<<< 250 OK id=10HmaX-000000005vi-0000
->>> RSET
-??? 250
-<<< 250 Reset OK
-+++ 1
->>> MAIL FROM:<discard@test.ex>
-??? 250
-<<< 250 OK
->>> RCPT TO:<CALLER@test.ex>
-??? 250
-<<< 250 Accepted
->>> DATA
-??? 354
-<<< 354 Enter message, ending with "." on a line by itself
->>> From: <coyote@test.ex>
->>> Subject: this should be discarded by the filter
->>> .
-??? 250
-<<< 250 OK id=10HmaY-000000005vi-0000
->>> RSET
-??? 250
-<<< 250 Reset OK
-+++ 1
->>> MAIL FROM:<identified@test.ex>
-??? 250
-<<< 250 OK
->>> RCPT TO:<CALLER@test.ex>
-??? 250
-<<< 250 Accepted
->>> DATA
-??? 354
-<<< 354 Enter message, ending with "." on a line by itself
->>> From: <spot_this@test.ex>
->>> Subject: this should be delivered to a speicifc place by the filter
->>> .
-??? 250
-<<< 250 OK id=10HmaZ-000000005vi-0000
->>> RSET
-??? 250
-<<< 250 Reset OK
-+++ 1
->>> MAIL FROM:<redirect@test.ex>
-??? 250
-<<< 250 OK
->>> RCPT TO:<CALLER@test.ex>
-??? 250
-<<< 250 Accepted
->>> DATA
-??? 354
-<<< 354 Enter message, ending with "." on a line by itself
->>> From: <redirect@test.ex>
->>> Subject: this should be redirected by the filter to a different address
->>> .
-??? 250
-<<< 250 OK id=10HmbA-000000005vi-0000
->>> QUIT
-??? 221
-<<< 221 myhost.test.ex closing connection
-End of script
--- /dev/null
+==========
+# Sieve filter
+if address ["From","To"] "marian@abcdefgh.example"
+ { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if address :is "From" "marian@abcdefgh.example"
+ { discard; }
+==========
+Implicit keep
+==========
+# Sieve filter
+if address :is "To" "marian@abcdefgh.example"
+ { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if address :contains "To" "abcdefgh"
+ { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if address :matches "To" "*abc?efgh*"
+ { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if true { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if false { discard; }
+==========
+Implicit keep
+==========
+# Sieve filter
+if allof (false,false) { discard; }
+==========
+Implicit keep
+==========
+# Sieve filter
+if allof (true,false) { discard; }
+==========
+Implicit keep
+==========
+# Sieve filter
+if allof (false,true) { discard; }
+==========
+Implicit keep
+==========
+# Sieve filter
+if allof (true,true) { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if anyof (false,false) { discard; }
+==========
+Implicit keep
+==========
+# Sieve filter
+if anyof (true,false) { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if anyof (false,true) { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if anyof (true,true) { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if not anyof (false,false) { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if not anyof (true,false) { discard; }
+==========
+Implicit keep
+==========
+# Sieve filter
+if not anyof (false,true) { discard; }
+==========
+Implicit keep
+==========
+# Sieve filter
+if not anyof (true,true) { discard; }
+==========
+Implicit keep
+==========
+# Sieve filter
+if header "to" "MARIAN@abcdefgh.example" { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if header :comparator "i;octet" "to" "MARIAN@abcdefgh.example" { discard; }
+==========
+Implicit keep
+==========
+# Sieve filter
+if header :comparator "i;ascii-casemap" "to" "MARIAN@abcdefgh.example" { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+require "envelope";
+if envelope "from" "marian@somenet.example" { discard; }
+==========
+Implicit keep
+==========
+# Sieve filter
+require "envelope";
+if envelope "from" "offerqn@bpk.example.com" { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if exists [ "X-NotHere", "X-Notheretoo" ] { discard; }
+==========
+Implicit keep
+==========
+# Sieve filter
+if exists [ "X-NotHere", "Delivered-To" ] { discard; }
+==========
+Implicit keep
+==========
+# Sieve filter
+if exists [ "From", "Delivered-To" ] { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if header :contains "X-NotHere" "" { discard; }
+==========
+Implicit keep
+==========
+# Sieve filter
+if header "Delivered-To" "" { discard; }
+==========
+Implicit keep
+==========
+# Sieve filter
+if header :contains "Delivered-To" "" { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if header :contains "Delivered-To" "rc@irc.somenet.example" { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if header "Delivered-To" ["irc@irc.somenet.example"] { discard; }
+==========
+Implicit keep
+==========
+# Sieve filter
+if header "Delivered-To" ["irc@irc.somenet.example","irc@01019somenet.example","some.one"] { discard; }
+==========
+Implicit keep
+==========
+# Sieve filter
+if header "Mime-Version" "1.0" { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if false { discard; }
+==========
+Implicit keep
+==========
+# Sieve filter
+if true { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if true { discard; } else { keep; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if false { keep; } else { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if false { keep; } elsif true { discard; } else { keep; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if true { discard; } elsif true { keep; } else { keep; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if false { keep; }
+ else
+ { if true { discard; } else { keep; } }
+==========
+No implicit keep
+==========
+# Sieve filter
+if header :matches "to" "MA*AN@abc*fg?.example" { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if not header :matches "to" "MA?AN@abc*fg?.example" { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if header :matches "to" "*marian@abcdefgh.example" { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if not header :matches "to" "?marian@abcdefgh.example" { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if header :matches "to" "marian@abcdefgh.example*" { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if not header :matches "to" "marian@abcdefgh.example?" { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if header :matches "x-special1" "\\?*\\*" { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if header :matches "x-special1" "*\0*" { discard; }
+==========
+Implicit keep
+==========
+# Sieve filter
+if not header :matches "x-special1" "*\0*q" { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+require "encoded-character";
+if not header :matches "x-special1" "*${hex:00}*" { discard; }
+==========
+Implicit keep
+==========
+# Sieve filter
+if not header :matches "x-special2" "\\?*\\*" { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if not header :matches "x-special2" "*\0*" { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if size :over 400 {
+ discard;
+ }
+==========
+No implicit keep
+==========
+# Sieve filter
+if size :over 4K {
+ discard;
+ }
+==========
+Implicit keep
+==========
+# Sieve filter
+if size :under 4K { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if size :under 400 { discard; }
+==========
+Implicit keep
+==========
+# Sieve filter
+if false { keep; }
+==========
+Implicit keep
+==========
+# no filter line here
+if unknowntest { keep; }
+==========
+Testing forward file file "test-data"
+
+exim: error in forward file: missing or malformed local part (expected word or "<") in "if unknowntest { keep; }"
+==========
+# Sieve filter
+if test keep; }
+==========
+Sieve error: missing test in line 2
+==========
+# Sieve filter
+if test { keep;
+==========
+Sieve error: missing test in line 2
+==========
+# Sieve filter
+if true { unknownaction; }
+==========
+Sieve error: expecting command or closing brace in line 2
+==========
+# Sieve filter
+if true { fileinto abcdefgh; }
+==========
+Sieve error: missing previous require "fileinto"; in line 2
+==========
+# Sieve filter
+require "fileinto";
+if true { fileinto "abcdefgh"; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if header ["to"] [ "marian@abcdefgh.example", "achnee"] { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if header "to" [ "egal", "achnee" { keep; }
+==========
+Sieve error: missing string list in line 2
+==========
+# Sieve filter
+if header "to","from"] "egal" { keep; }
+==========
+Sieve error: key string list expected in line 2
+==========
+# Sieve filter
+if header ["to","from" "egal" { keep; }
+==========
+Sieve error: missing string list in line 2
+==========
+# Sieve filter
+if header ["to",,"from"] "egal" { keep; }
+==========
+Sieve error: missing string list in line 2
+==========
+# Sieve filter
+if header ["to",] "egal" { keep; }
+==========
+Sieve error: missing string list in line 2
+==========
+# Sieve filter
+if header [,"to"] "egal" { keep; }
+==========
+Sieve error: missing string list in line 2
+==========
+# Sieve filter
+if unknowntest { keep; }
+==========
+Sieve error: missing test in line 2
+==========
+# Sieve filter
+if header :matches "to" "egal" { keep; }
+==========
+Implicit keep
+==========
+# Sieve filter
+if header :unknown "to" "egal" { keep; }
+==========
+Sieve error: header string list expected in line 2
+==========
+# Sieve filter
+#
+require ["fileinto", "envelope"];
+
+if header :matches "X-Warning" "* is listed at list.dsbl.org*"
+ {
+ keep; # keep in "In" folder
+ }
+==========
+No implicit keep
+==========
+# Sieve filter
+if header "x-1" "1" { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if not header "x-1" "=?ISO-8859-1?Q?=31?=" { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if header "x-1b" "=?ISO-8859-1?Q?=31=" { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if header "x-b64" "This is BASE64" { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if header "x-b64-broken" "=?iso-8859-1?b?VGhpcyBpcyBCQVNFNjQ?=" { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if header "x-q75total" "0123456789012345678901234567890123456789012345678901234567" { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if header "x-q76total" "=?ISO-8859-1?Q?01234567890123456789012345678901234567890123456789012345678?=" { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if header "X-Wrapped" "eins zwei drei" { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if header "x-nomimewrap" "=?iso-8859-1?q?abc def ghi?=" { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if header "subject" "abcdefghi" { discard; }
+==========
+No implicit keep
+==========
+# Sieve filter
+if header "x-mixed" "abc def" { discard; }
+==========
+No implicit keep
+==========
+#Sieve filter
+if true { stop; fileinto "inbox.never"; }
+==========
+Implicit keep
--- /dev/null
+Connecting to 127.0.0.1 port 1225 ... connected
+??? 220
+<<< 220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+>>> HELO tester
+??? 250
+<<< 250 myhost.test.ex Hello tester [127.0.0.1]
+>>> MAIL FROM:<implcit@test.ex>
+??? 250
+<<< 250 OK
+>>> RCPT TO:<CALLER@test.ex>
+??? 250
+<<< 250 Accepted
+>>> DATA
+??? 354
+<<< 354 Enter message, ending with "." on a line by itself
+>>> From: <good@test.ex>
+>>> Subject: this should be accepted and filed
+>>>
+>>> a single body line
+>>> .
+??? 250
+<<< 250 OK id=10HmaX-000000005vi-0000
+>>> RSET
+??? 250
+<<< 250 Reset OK
++++ 1
+>>> MAIL FROM:<discard@test.ex>
+??? 250
+<<< 250 OK
+>>> RCPT TO:<CALLER@test.ex>
+??? 250
+<<< 250 Accepted
+>>> DATA
+??? 354
+<<< 354 Enter message, ending with "." on a line by itself
+>>> From: <coyote@test.ex>
+>>> Subject: this should be discarded by the filter
+>>> .
+??? 250
+<<< 250 OK id=10HmaY-000000005vi-0000
+>>> RSET
+??? 250
+<<< 250 Reset OK
++++ 1
+>>> MAIL FROM:<identified@test.ex>
+??? 250
+<<< 250 OK
+>>> RCPT TO:<CALLER@test.ex>
+??? 250
+<<< 250 Accepted
+>>> DATA
+??? 354
+<<< 354 Enter message, ending with "." on a line by itself
+>>> From: <spot_this@test.ex>
+>>> Subject: this should be delivered to a speicifc place by the filter
+>>> .
+??? 250
+<<< 250 OK id=10HmaZ-000000005vi-0000
+>>> RSET
+??? 250
+<<< 250 Reset OK
++++ 1
+>>> MAIL FROM:<redirect@test.ex>
+??? 250
+<<< 250 OK
+>>> RCPT TO:<CALLER@test.ex>
+??? 250
+<<< 250 Accepted
+>>> DATA
+??? 354
+<<< 354 Enter message, ending with "." on a line by itself
+>>> From: <redirect@test.ex>
+>>> Subject: this should be redirected by the filter to a different address
+>>> .
+??? 250
+<<< 250 OK id=10HmbA-000000005vi-0000
+>>> QUIT
+??? 221
+<<< 221 myhost.test.ex closing connection
+End of script