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
 
 
  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
     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 \
         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) \
         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
 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
 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
  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.
 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 \
        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`
        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 \
   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 \
   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
 
 
 # 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
 #------------------------------------------------------------------------------
 # 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_PIPE_CONNECT
 #define DISABLE_PRDR
 #define DISABLE_QUEUE_RAMP
+#define DISABLE_SIEVE_FILTER
 #define DISABLE_TLS
 #define DISABLE_TLS_RESUME
 #define DISABLE_WELLKNOWN
 #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_ARC
 #define SUPPORT_DKIM
 #define SUPPORT_PERL
+#define SUPPORT_SIEVE_FILTER
 #define SUPPORT_RADIUS
 
 #define SYSLOG_LOG_PID
 #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(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)
 
 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(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
 #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
 #ifdef SUPPORT_CRYPTEQ
   g = string_cat(g, US" crypteq");
 #endif
@@ -1475,8 +1478,14 @@ switch(request)
 );
     return;
   case CMDINFO_SIEVE:
 );
     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);
     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
 #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. */
 
 /* 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;
   }
   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;
 }
 
 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 void    sha1_end(hctx *, const uschar *, int, uschar *);
 extern void    sha1_mid(hctx *, const uschar *);
 extern void    sha1_start(hctx *);
-extern int     sieve_interpret(const uschar *, int, const sieve_block *,
-                address_item **, uschar **);
 extern void    sigalrm_handler(int);
 extern void    single_queue_run(qrunner *, const uschar *, const uschar *);
 extern int     smtp_boundsock(smtp_connect_args *);
 extern 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 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 */
 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.
 
 # 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"
 
 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;
       }
       *error = US"Exim filtering not enabled";
       return FF_ERROR;
       }
+/*XXX*/
     frc = filter_interpret(data, options, generated, error);
     }
   else
     {
     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;
       }
     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;
     }
 
   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 <= 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 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 <= 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 <= 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
 ****
 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