sieve dynamic module
authorJeremy Harris <jgh146exb@wizmail.org>
Tue, 10 Sep 2024 11:32:03 +0000 (12:32 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Tue, 10 Sep 2024 11:34:23 +0000 (12:34 +0100)
79 files changed:
doc/doc-txt/NewStuff
src/OS/Makefile-Base
src/scripts/Configure-Makefile
src/scripts/MakeLinks
src/src/EDITME
src/src/config.h.defaults
src/src/drtables.c
src/src/exim.c
src/src/exim.h
src/src/filtertest.c
src/src/functions.h
src/src/globals.h
src/src/miscmods/Makefile
src/src/miscmods/sieve_filter.c [new file with mode: 0644]
src/src/miscmods/sieve_filter_api.h [new file with mode: 0644]
src/src/rda.c
src/src/sieve.c [deleted file]
test/aux-fixed/0427.message [deleted file]
test/aux-fixed/0427.message2 [deleted file]
test/aux-fixed/0427.message3 [deleted file]
test/aux-fixed/0506.sieve-filter [deleted file]
test/aux-fixed/0950.sieve [deleted file]
test/aux-fixed/4160.message [new file with mode: 0644]
test/aux-fixed/4160.message2 [new file with mode: 0644]
test/aux-fixed/4160.message3 [new file with mode: 0644]
test/aux-fixed/4162.sieve-filter [new file with mode: 0644]
test/aux-fixed/4163.sieve [new file with mode: 0644]
test/confs/0427 [deleted file]
test/confs/0428 [deleted file]
test/confs/0950 [deleted file]
test/confs/4160 [new file with mode: 0644]
test/confs/4161 [new file with mode: 0644]
test/confs/4162 [new file with mode: 0644]
test/confs/4163 [new file with mode: 0644]
test/log/0428 [deleted file]
test/log/0506
test/log/0950 [deleted file]
test/log/4161 [new file with mode: 0644]
test/log/4162 [new file with mode: 0644]
test/log/4163 [new file with mode: 0644]
test/mail/0428.CALLER [deleted file]
test/mail/0428.inbox.JUNK [deleted file]
test/mail/0428.inbox.changed [deleted file]
test/mail/0428.redirected [deleted file]
test/mail/0428.someone [deleted file]
test/mail/0428.userx [deleted file]
test/mail/0428.userx-extra [deleted file]
test/mail/0428.userx-sawsuffix [deleted file]
test/mail/0428.userx13 [deleted file]
test/mail/0428.userx14 [deleted file]
test/mail/0428.userx9 [deleted file]
test/mail/0950.CALLER [deleted file]
test/mail/0950.myfolder [deleted file]
test/mail/4161.CALLER [new file with mode: 0644]
test/mail/4161.inbox.JUNK [new file with mode: 0644]
test/mail/4161.inbox.changed [new file with mode: 0644]
test/mail/4161.redirected [new file with mode: 0644]
test/mail/4161.someone [new file with mode: 0644]
test/mail/4161.userx [new file with mode: 0644]
test/mail/4161.userx-extra [new file with mode: 0644]
test/mail/4161.userx-sawsuffix [new file with mode: 0644]
test/mail/4161.userx13 [new file with mode: 0644]
test/mail/4161.userx14 [new file with mode: 0644]
test/mail/4161.userx9 [new file with mode: 0644]
test/mail/4163.CALLER [new file with mode: 0644]
test/mail/4163.myfolder [new file with mode: 0644]
test/scripts/0000-Basic/0427 [deleted file]
test/scripts/0000-Basic/0428 [deleted file]
test/scripts/0000-Basic/0506
test/scripts/0000-Basic/0950 [deleted file]
test/scripts/4160-sieve-filter/4160 [new file with mode: 0644]
test/scripts/4160-sieve-filter/4161 [new file with mode: 0644]
test/scripts/4160-sieve-filter/4162 [new file with mode: 0644]
test/scripts/4160-sieve-filter/4163 [new file with mode: 0644]
test/scripts/4160-sieve-filter/REQUIRES [new file with mode: 0644]
test/stdout/0427 [deleted file]
test/stdout/0950 [deleted file]
test/stdout/4160 [new file with mode: 0644]
test/stdout/4163 [new file with mode: 0644]

index 01326b488276f60ea50ed6ee7e55fa94dec2d418..bca7d6f877600fabd0daf78efc46b0ad16609b57 100644 (file)
@@ -14,8 +14,10 @@ Version 4.98
 
  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
index d8df296723d9d99a9644e16c146288ad9c84b278..7793e5da222a9ae7cee45a1e3ae5faab0ca6e789 100644 (file)
@@ -518,7 +518,7 @@ OBJ_EXIM = acl.o base64.o child.o crypt16.o daemon.o dbfn.o debug.o deliver.o \
         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) \
@@ -866,7 +866,6 @@ regex_cache.o:   $(HDRS) regex_cache.c
 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
index df6323f3b592014ad18c3ed958b50cf48d9010d6..fd5afaf1093ddbe01d6b69410939a87ee4492838 100755 (executable)
@@ -315,7 +315,7 @@ done <<-END
  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.
index d7441ef0ded7cd2e42969816eb09b420e38e01f5..6327bc8191746ff73ddda4e896085266154f3900 100755 (executable)
@@ -105,6 +105,7 @@ for f in dummy.c \
        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`
@@ -136,7 +137,7 @@ for f in blob.h dbfunctions.h exim.h functions.h globals.h \
   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 \
index 757e94746f389d7b208148040546a1b648556c4c..85effd13fcce0540b3da0b1ab12662262ef67afe 100644 (file)
@@ -551,6 +551,17 @@ SUPPORT_DANE=yes
 # 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
index e6ded1e8fc0107efedc1fabba02f67a540954844..404ac0c1ca4d86560ba76aeb04b3f029cb112ad3 100644 (file)
@@ -57,6 +57,7 @@ Do not put spaces between # and the 'define'.
 #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
@@ -171,6 +172,7 @@ Do not put spaces between # and the 'define'.
 #define SUPPORT_ARC
 #define SUPPORT_DKIM
 #define SUPPORT_PERL
+#define SUPPORT_SIEVE_FILTER
 #define SUPPORT_RADIUS
 
 #define SYSLOG_LOG_PID
index 8bde476668243b7aedbdcf7477a0de75e3795314..49d3ae129f6ba543746924242b80bc594d2b49a6 100644 (file)
@@ -762,6 +762,9 @@ extern misc_module_info pam_module_info;
 #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)
@@ -792,6 +795,9 @@ onetime = TRUE;
 #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
 }
 
 
index 3d1b7f1d9f8a2e292c86630875f093619c2ee0d4..274187cfcc116d055145d80d1805287d2275f463 100644 (file)
@@ -1221,6 +1221,9 @@ g = string_cat(g, US"Support for:");
 #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
@@ -1475,8 +1478,14 @@ switch(request)
 );
     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);
index 771c00df84f9f84113a0d22e6fad18ea9a4f385d..bae5ec390daec1b86218a405bc4bebc3608383fa 100644 (file)
@@ -567,6 +567,9 @@ config.h, mytypes.h, and store.h, so we don't need to mention them explicitly.
 #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. */
index a58fe5e821bc50550160338637c8a7595147eb69..eb5f5e54bf12776a7427a2830470bf95e35f231f 100644 (file)
@@ -270,12 +270,22 @@ if (is_system)
   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;
 }
index 9b645a85faa0c541e8a685e8a9d9a89a902aa7f1..60b07d2d7f9604505c90c6435a0832274dcab67b 100644 (file)
@@ -481,8 +481,6 @@ extern void    set_process_info(const char *, ...) PRINTF_FUNCTION(1,2);
 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 *);
index a172d9abc8aa35f10dc567fd76b2a88570c729ee..e9be97cd89df131d52613d26b3e54ac83d84fa17 100644 (file)
@@ -586,7 +586,6 @@ extern const uschar *event_name;       /* event classification */
 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 */
index c1489d6e8a3e5073b55bb2afbf74c4b5e6d2115a..3c255dbb6ff5e236f5741e9342d94afdce987be5 100644 (file)
@@ -31,16 +31,17 @@ miscmods.a: $(OBJ)
 
 # 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"
diff --git a/src/src/miscmods/sieve_filter.c b/src/src/miscmods/sieve_filter.c
new file mode 100644 (file)
index 0000000..56f20bf
--- /dev/null
@@ -0,0 +1,3639 @@
+/*************************************************
+*     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 */
diff --git a/src/src/miscmods/sieve_filter_api.h b/src/src/miscmods/sieve_filter_api.h
new file mode 100644 (file)
index 0000000..42294c5
--- /dev/null
@@ -0,0 +1,15 @@
+/*************************************************
+*     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
index 8289ab08474b3340795599c9b4c466d528ca3ca4..3e3a0cf831d9d7a1bab0929aea46a0c0451be51a 100644 (file)
@@ -388,16 +388,27 @@ if (*filtertype != FILTER_FORWARD)
       *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;
diff --git a/src/src/sieve.c b/src/src/sieve.c
deleted file mode 100644 (file)
index dbe64cf..0000000
+++ /dev/null
@@ -1,3617 +0,0 @@
-/*************************************************
-*     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;
-}
diff --git a/test/aux-fixed/0427.message b/test/aux-fixed/0427.message
deleted file mode 100644 (file)
index fce284d..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-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"><!--
-
diff --git a/test/aux-fixed/0427.message2 b/test/aux-fixed/0427.message2
deleted file mode 100644 (file)
index fcbda4e..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-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"><!--
diff --git a/test/aux-fixed/0427.message3 b/test/aux-fixed/0427.message3
deleted file mode 100644 (file)
index ade4824..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-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
diff --git a/test/aux-fixed/0506.sieve-filter b/test/aux-fixed/0506.sieve-filter
deleted file mode 100644 (file)
index 7a69e42..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-# Sieve filter
-
-discard;
-
diff --git a/test/aux-fixed/0950.sieve b/test/aux-fixed/0950.sieve
deleted file mode 100644 (file)
index 2f7b08d..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-# 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";
-}
diff --git a/test/aux-fixed/4160.message b/test/aux-fixed/4160.message
new file mode 100644 (file)
index 0000000..fce284d
--- /dev/null
@@ -0,0 +1,30 @@
+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"><!--
+
diff --git a/test/aux-fixed/4160.message2 b/test/aux-fixed/4160.message2
new file mode 100644 (file)
index 0000000..fcbda4e
--- /dev/null
@@ -0,0 +1,27 @@
+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"><!--
diff --git a/test/aux-fixed/4160.message3 b/test/aux-fixed/4160.message3
new file mode 100644 (file)
index 0000000..ade4824
--- /dev/null
@@ -0,0 +1,20 @@
+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
diff --git a/test/aux-fixed/4162.sieve-filter b/test/aux-fixed/4162.sieve-filter
new file mode 100644 (file)
index 0000000..7a69e42
--- /dev/null
@@ -0,0 +1,4 @@
+# Sieve filter
+
+discard;
+
diff --git a/test/aux-fixed/4163.sieve b/test/aux-fixed/4163.sieve
new file mode 100644 (file)
index 0000000..2f7b08d
--- /dev/null
@@ -0,0 +1,12 @@
+# 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";
+}
diff --git a/test/confs/0427 b/test/confs/0427
deleted file mode 100644 (file)
index cdbc8ae..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-# 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
diff --git a/test/confs/0428 b/test/confs/0428
deleted file mode 100644 (file)
index 0f6614b..0000000
+++ /dev/null
@@ -1,187 +0,0 @@
-# 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
diff --git a/test/confs/0950 b/test/confs/0950
deleted file mode 100644 (file)
index f75e2c4..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-# 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
diff --git a/test/confs/4160 b/test/confs/4160
new file mode 100644 (file)
index 0000000..019476a
--- /dev/null
@@ -0,0 +1,21 @@
+# 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
diff --git a/test/confs/4161 b/test/confs/4161
new file mode 100644 (file)
index 0000000..f24502a
--- /dev/null
@@ -0,0 +1,187 @@
+# 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
diff --git a/test/confs/4162 b/test/confs/4162
new file mode 100644 (file)
index 0000000..76045b7
--- /dev/null
@@ -0,0 +1,32 @@
+# 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
diff --git a/test/confs/4163 b/test/confs/4163
new file mode 100644 (file)
index 0000000..3b4f6df
--- /dev/null
@@ -0,0 +1,58 @@
+# 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
diff --git a/test/log/0428 b/test/log/0428
deleted file mode 100644 (file)
index 7d20a69..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-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
index db961727b646e7b85bac642679640656617a15c4..1e94ac0f83b1bf19c937bd7b1b5b0ea49a757dff 100644 (file)
@@ -1,13 +1,10 @@
 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
diff --git a/test/log/0950 b/test/log/0950
deleted file mode 100644 (file)
index 0914c99..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-
-******** 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
diff --git a/test/log/4161 b/test/log/4161
new file mode 100644 (file)
index 0000000..7d20a69
--- /dev/null
@@ -0,0 +1,56 @@
+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
diff --git a/test/log/4162 b/test/log/4162
new file mode 100644 (file)
index 0000000..5d11b2a
--- /dev/null
@@ -0,0 +1,10 @@
+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
diff --git a/test/log/4163 b/test/log/4163
new file mode 100644 (file)
index 0000000..0914c99
--- /dev/null
@@ -0,0 +1,17 @@
+
+******** 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
diff --git a/test/mail/0428.CALLER b/test/mail/0428.CALLER
deleted file mode 100644 (file)
index 50d4a66..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-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--
-
diff --git a/test/mail/0428.inbox.JUNK b/test/mail/0428.inbox.JUNK
deleted file mode 100644 (file)
index 1c3ecba..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-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
-
diff --git a/test/mail/0428.inbox.changed b/test/mail/0428.inbox.changed
deleted file mode 100644 (file)
index 262d1a5..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-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
-
diff --git a/test/mail/0428.redirected b/test/mail/0428.redirected
deleted file mode 100644 (file)
index dee3dbb..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-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
-
diff --git a/test/mail/0428.someone b/test/mail/0428.someone
deleted file mode 100644 (file)
index 05bb040..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-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.
-
diff --git a/test/mail/0428.userx b/test/mail/0428.userx
deleted file mode 100644 (file)
index c6882cd..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-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
-
diff --git a/test/mail/0428.userx-extra b/test/mail/0428.userx-extra
deleted file mode 100644 (file)
index 0d4a8b3..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-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
-
diff --git a/test/mail/0428.userx-sawsuffix b/test/mail/0428.userx-sawsuffix
deleted file mode 100644 (file)
index 043f0ab..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-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
-
diff --git a/test/mail/0428.userx13 b/test/mail/0428.userx13
deleted file mode 100644 (file)
index 859a1ae..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-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
-
diff --git a/test/mail/0428.userx14 b/test/mail/0428.userx14
deleted file mode 100644 (file)
index 3f18423..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-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
-
diff --git a/test/mail/0428.userx9 b/test/mail/0428.userx9
deleted file mode 100644 (file)
index e78bb73..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-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
-
diff --git a/test/mail/0950.CALLER b/test/mail/0950.CALLER
deleted file mode 100644 (file)
index cf7c1bc..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-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
-
diff --git a/test/mail/0950.myfolder b/test/mail/0950.myfolder
deleted file mode 100644 (file)
index 29352c4..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-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
-
-
diff --git a/test/mail/4161.CALLER b/test/mail/4161.CALLER
new file mode 100644 (file)
index 0000000..50d4a66
--- /dev/null
@@ -0,0 +1,114 @@
+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--
+
diff --git a/test/mail/4161.inbox.JUNK b/test/mail/4161.inbox.JUNK
new file mode 100644 (file)
index 0000000..1c3ecba
--- /dev/null
@@ -0,0 +1,84 @@
+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
+
diff --git a/test/mail/4161.inbox.changed b/test/mail/4161.inbox.changed
new file mode 100644 (file)
index 0000000..262d1a5
--- /dev/null
@@ -0,0 +1,15 @@
+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
+
diff --git a/test/mail/4161.redirected b/test/mail/4161.redirected
new file mode 100644 (file)
index 0000000..dee3dbb
--- /dev/null
@@ -0,0 +1,16 @@
+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
+
diff --git a/test/mail/4161.someone b/test/mail/4161.someone
new file mode 100644 (file)
index 0000000..05bb040
--- /dev/null
@@ -0,0 +1,23 @@
+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.
+
diff --git a/test/mail/4161.userx b/test/mail/4161.userx
new file mode 100644 (file)
index 0000000..c6882cd
--- /dev/null
@@ -0,0 +1,31 @@
+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
+
diff --git a/test/mail/4161.userx-extra b/test/mail/4161.userx-extra
new file mode 100644 (file)
index 0000000..0d4a8b3
--- /dev/null
@@ -0,0 +1,16 @@
+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
+
diff --git a/test/mail/4161.userx-sawsuffix b/test/mail/4161.userx-sawsuffix
new file mode 100644 (file)
index 0000000..043f0ab
--- /dev/null
@@ -0,0 +1,19 @@
+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
+
diff --git a/test/mail/4161.userx13 b/test/mail/4161.userx13
new file mode 100644 (file)
index 0000000..859a1ae
--- /dev/null
@@ -0,0 +1,18 @@
+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
+
diff --git a/test/mail/4161.userx14 b/test/mail/4161.userx14
new file mode 100644 (file)
index 0000000..3f18423
--- /dev/null
@@ -0,0 +1,19 @@
+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
+
diff --git a/test/mail/4161.userx9 b/test/mail/4161.userx9
new file mode 100644 (file)
index 0000000..e78bb73
--- /dev/null
@@ -0,0 +1,21 @@
+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
+
diff --git a/test/mail/4163.CALLER b/test/mail/4163.CALLER
new file mode 100644 (file)
index 0000000..cf7c1bc
--- /dev/null
@@ -0,0 +1,15 @@
+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
+
diff --git a/test/mail/4163.myfolder b/test/mail/4163.myfolder
new file mode 100644 (file)
index 0000000..29352c4
--- /dev/null
@@ -0,0 +1,14 @@
+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
+
+
diff --git a/test/scripts/0000-Basic/0427 b/test/scripts/0000-Basic/0427
deleted file mode 100644 (file)
index 8613b2b..0000000
+++ /dev/null
@@ -1,547 +0,0 @@
-# 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
-****
diff --git a/test/scripts/0000-Basic/0428 b/test/scripts/0000-Basic/0428
deleted file mode 100644 (file)
index f5aaa41..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-# 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
-****
index ed1d7bdffa339eefe346218561469f77b4f78787..4f388bb8c410c28f8f8584fe298ceb781a9537db 100644 (file)
@@ -1,10 +1,10 @@
-# 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
diff --git a/test/scripts/0000-Basic/0950 b/test/scripts/0000-Basic/0950
deleted file mode 100644 (file)
index fb7d005..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-# 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
diff --git a/test/scripts/4160-sieve-filter/4160 b/test/scripts/4160-sieve-filter/4160
new file mode 100644 (file)
index 0000000..8613b2b
--- /dev/null
@@ -0,0 +1,547 @@
+# 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
+****
diff --git a/test/scripts/4160-sieve-filter/4161 b/test/scripts/4160-sieve-filter/4161
new file mode 100644 (file)
index 0000000..f5aaa41
--- /dev/null
@@ -0,0 +1,106 @@
+# 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
+****
diff --git a/test/scripts/4160-sieve-filter/4162 b/test/scripts/4160-sieve-filter/4162
new file mode 100644 (file)
index 0000000..156a73b
--- /dev/null
@@ -0,0 +1,10 @@
+# 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
diff --git a/test/scripts/4160-sieve-filter/4163 b/test/scripts/4160-sieve-filter/4163
new file mode 100644 (file)
index 0000000..fb7d005
--- /dev/null
@@ -0,0 +1,67 @@
+# 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
diff --git a/test/scripts/4160-sieve-filter/REQUIRES b/test/scripts/4160-sieve-filter/REQUIRES
new file mode 100644 (file)
index 0000000..dd2bec1
--- /dev/null
@@ -0,0 +1 @@
+support Sieve_filter
diff --git a/test/stdout/0427 b/test/stdout/0427
deleted file mode 100644 (file)
index 7e70360..0000000
+++ /dev/null
@@ -1,458 +0,0 @@
-==========
-# 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
diff --git a/test/stdout/0950 b/test/stdout/0950
deleted file mode 100644 (file)
index 8a9ae23..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-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
diff --git a/test/stdout/4160 b/test/stdout/4160
new file mode 100644 (file)
index 0000000..7e70360
--- /dev/null
@@ -0,0 +1,458 @@
+==========
+# 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
diff --git a/test/stdout/4163 b/test/stdout/4163
new file mode 100644 (file)
index 0000000..8a9ae23
--- /dev/null
@@ -0,0 +1,80 @@
+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