commit-mailutils
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[SCM] GNU Mailutils branch, master, updated. release-3.0-30-ge90f14e


From: Sergey Poznyakoff
Subject: [SCM] GNU Mailutils branch, master, updated. release-3.0-30-ge90f14e
Date: Wed, 7 Dec 2016 12:46:21 +0000 (UTC)

This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "GNU Mailutils".

http://git.savannah.gnu.org/cgit/mailutils.git/commit/?id=e90f14ed1f447219d8ebf20f63042049c898301c

The branch, master has been updated
       via  e90f14ed1f447219d8ebf20f63042049c898301c (commit)
       via  799519539556b38fc9f84d44927861cb9239d14c (commit)
      from  cab2fd5c0479d16a3dcd746c98de8be771a8963d (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit e90f14ed1f447219d8ebf20f63042049c898301c
Author: Sergey Poznyakoff <address@hidden>
Date:   Wed Dec 7 13:21:21 2016 +0200

    Add missing file
    
    * include/mailutils/glob.h: File was missing in df608ed0
    * include/mailutils/Makefile.am: Add glob.h

commit 799519539556b38fc9f84d44927861cb9239d14c
Author: Sergey Poznyakoff <address@hidden>
Date:   Wed Dec 7 09:46:57 2016 +0200

    Implement the "variables" Sieve extension (RFC 5229)
    
    * include/mailutils/sieve.h (mu_sieve_string): New fields
    "constant" and "changed".
    (mu_sieve_match_part_tags): New extern.
    (mu_sieve_relational_count)
    (mu_sieve_require_variables)
    (mu_sieve_has_variables)
    (mu_sieve_string_get): New functions.
    * libmu_sieve/variables.c: New file.
    * libmu_sieve/Makefile.am: Add variables.c
    * libmu_sieve/comparator.c: Use mu_sieve_string_get to obtain
    the actual value of the string.
    * libmu_sieve/require.c: Support the "variables" extension.
    * libmu_sieve/sieve-priv.h (mu_sieve_machine): New fields
    vartab, match_string, match_buf, match_count, match_max.
    (mu_i_sv_copy_variables)
    (mu_i_sv_expand_variables): New protos.
    * libmu_sieve/sieve.l (line_add): zero length means add entire
    asciiz string.
    * libmu_sieve/sieve.y (mu_sieve_machine_reset): Reset the new
    fields.
    (mu_sieve_machine_clone): Copy variables and initialize new
    fields.
    (string_rescan): New function.
    (sieve_parse): Rescan string to determine their properties.
    * libmu_sieve/strexp.c (update_len): Allow for NULL replacement
    values.
    * libmu_sieve/string.c (mu_sieve_string_get): New function.
    (mu_sieve_string): Use it.
    * libmu_sieve/tests.c (do_count): Rename to mu_sieve_relational_count,
    make global. All uses changed.
    (match_part_tags): Rename to mu_sieve_match_part_tags, make global.
    All uses changed.
    
    * sieve/tests/variables.at: New file.
    * sieve/tests/Makefile.am: Add new testcases.
    * sieve/tests/testsuite.at: Likewise.

-----------------------------------------------------------------------

Summary of changes:
 include/mailutils/Makefile.am             |    1 +
 lib/tcpwrap.h => include/mailutils/glob.h |   32 ++-
 include/mailutils/sieve.h                 |   24 +-
 libmu_sieve/Makefile.am                   |    3 +-
 libmu_sieve/comparator.c                  |  105 +++++--
 libmu_sieve/require.c                     |    7 +-
 libmu_sieve/runtime.c                     |    5 +-
 libmu_sieve/sieve-priv.h                  |   14 +-
 libmu_sieve/sieve.l                       |    2 +
 libmu_sieve/sieve.y                       |   45 ++-
 libmu_sieve/strexp.c                      |   12 +-
 libmu_sieve/string.c                      |   53 +++-
 libmu_sieve/tests.c                       |   23 +-
 libmu_sieve/variables.c                   |  431 +++++++++++++++++++++++++++++
 sieve/tests/Makefile.am                   |    1 +
 sieve/tests/testsuite.at                  |    1 +
 sieve/tests/variables.at                  |   99 +++++++
 17 files changed, 787 insertions(+), 71 deletions(-)
 copy lib/tcpwrap.h => include/mailutils/glob.h (54%)
 create mode 100644 libmu_sieve/variables.c
 create mode 100644 sieve/tests/variables.at

diff --git a/include/mailutils/Makefile.am b/include/mailutils/Makefile.am
index d1e0019..bc78342 100644
--- a/include/mailutils/Makefile.am
+++ b/include/mailutils/Makefile.am
@@ -48,6 +48,7 @@ pkginclude_HEADERS = \
  error.h\
  filter.h\
  folder.h\
+ glob.h\
  gsasl.h\
  guile.h\
  header.h\
diff --git a/lib/tcpwrap.h b/include/mailutils/glob.h
similarity index 54%
copy from lib/tcpwrap.h
copy to include/mailutils/glob.h
index 23efca1..57992f6 100644
--- a/lib/tcpwrap.h
+++ b/include/mailutils/glob.h
@@ -1,6 +1,5 @@
 /* GNU Mailutils -- a suite of utilities for electronic mail
-   Copyright (C) 1999, 2001-2008, 2010-2012, 2014-2016 Free Software
-   Foundation, Inc.
+   Copyright (C) 2016 Free Software Foundation, Inc.
 
    GNU Mailutils is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -15,19 +14,22 @@
    You should have received a copy of the GNU General Public License
    along with GNU Mailutils.  If not, see <http://www.gnu.org/licenses/>. */
 
+#ifndef _MAILUTILS_GLOB_H
+#define _MAILUTILS_GLOB_H
+
 #include <mailutils/types.h>
+#include <regex.h>
+
+/* Produce case-insensitive regex */
+#define MU_GLOBF_ICASE    0x01
+/* Treat each wildcard as regexp parenthesized group */
+#define MU_GLOBF_SUB      0x02
+/* When used with MU_GLOBF_SUB - collapse contiguous runs of * to single
+   asterisk */
+#define MU_GLOBF_COLLAPSE 0x04
+
+int mu_glob_to_regex_opool (char const *pattern, mu_opool_t pool, int flags);
+int mu_glob_to_regex (char **rxstr, char const *pattern, int flags);
+int mu_glob_compile (regex_t *rx, char const *pattern, int flags);
 
-extern int mu_tcp_wrapper_enable;
-const char *mu_tcp_wrapper_daemon;
-extern int mu_tcpwrapper_access (int fd);
-extern void mu_tcpwrapper_cfg_init (void);
-extern int mu_tcp_wrapper_prefork (int fd, 
-                                  struct sockaddr *sa, int salen,
-                                  struct mu_srv_config *pconf,
-                                  void *data);
-
-#ifdef WITH_LIBWRAP
-# define TCP_WRAPPERS_CONFIG { "tcp-wrappers", mu_cfg_section },
-#else
-# define TCP_WRAPPERS_CONFIG
 #endif
diff --git a/include/mailutils/sieve.h b/include/mailutils/sieve.h
index 6316332..8dfa381 100644
--- a/include/mailutils/sieve.h
+++ b/include/mailutils/sieve.h
@@ -35,9 +35,11 @@ typedef struct mu_sieve_machine *mu_sieve_machine_t;
 
 typedef struct mu_sieve_string
 {
-  char *orig;
-  char *exp;
-  void *rx;
+  unsigned constant:1;       /* String is constant */
+  unsigned changed:1;        /* String value has changed */
+  char *orig;                /* String original value */
+  char *exp;                 /* Actual string value after expansion */
+  void *rx;                  /* Pointer to the corresponding regular expr */
 } mu_sieve_string_t;
   
 typedef int (*mu_sieve_handler_t) (mu_sieve_machine_t mach);
@@ -141,7 +143,9 @@ extern mu_debug_handle_t mu_sieve_debug_handle;
 extern mu_list_t mu_sieve_include_path;
 extern mu_list_t mu_sieve_library_path;
 extern mu_list_t mu_sieve_library_path_prefix;
-  
+
+extern mu_sieve_tag_def_t mu_sieve_match_part_tags[];
+
 /* Memory allocation functions */
 typedef void (*mu_sieve_reclaim_t) (void *);
 void mu_sieve_register_memory (mu_sieve_machine_t mach, void *ptr,
@@ -197,6 +201,11 @@ void mu_sieve_register_comparator (mu_sieve_machine_t 
mach, const char *name,
                                   mu_sieve_comparator_t eq);
 
 int mu_sieve_require_relational (mu_sieve_machine_t mach, const char *name);
+int mu_sieve_relational_count (mu_sieve_machine_t mach, size_t count,
+                              int retval);
+  
+int mu_sieve_require_variables (mu_sieve_machine_t mach);
+int mu_sieve_has_variables (mu_sieve_machine_t mach);
 
 void *mu_sieve_load_ext (mu_sieve_machine_t mach, const char *name);
 void mu_sieve_unload_ext (void *handle);
@@ -234,9 +243,10 @@ void mu_sieve_get_arg (mu_sieve_machine_t mach, size_t 
index,
 char *mu_sieve_string (mu_sieve_machine_t mach,
                       mu_sieve_slice_t slice,
                       size_t i);
-struct mu_sieve_string *mu_sieve_string_raw (mu_sieve_machine_t mach,
-                                            mu_sieve_slice_t slice,
-                                            size_t i);
+mu_sieve_string_t *mu_sieve_string_raw (mu_sieve_machine_t mach,
+                                       mu_sieve_slice_t slice,
+                                       size_t i);
+char *mu_sieve_string_get (mu_sieve_machine_t mach, mu_sieve_string_t *string);
 
 /* Operations on value lists */ 
 int mu_sieve_vlist_do (mu_sieve_machine_t mach,
diff --git a/libmu_sieve/Makefile.am b/libmu_sieve/Makefile.am
index d0a77a9..a0ce50b 100644
--- a/libmu_sieve/Makefile.am
+++ b/libmu_sieve/Makefile.am
@@ -44,7 +44,8 @@ libmu_sieve_la_SOURCES = \
  strexp.c\
  string.c\
  tests.c\
- util.c
+ util.c\
+ variables.c
 libmu_sieve_la_LIBADD = ${MU_LIB_MAILUTILS} @LTDL_LIB@
 libmu_sieve_la_LDFLAGS = -version-info @VI_CURRENT@:@VI_REVISION@:@VI_AGE@
 
diff --git a/libmu_sieve/comparator.c b/libmu_sieve/comparator.c
index 5e10c83..e0e12b3 100644
--- a/libmu_sieve/comparator.c
+++ b/libmu_sieve/comparator.c
@@ -79,11 +79,20 @@ compile_pattern (mu_sieve_machine_t mach, mu_sieve_string_t 
*pattern, int flags)
 {
   int rc;
   regex_t *preg;
+  char *str;
 
+  str = mu_sieve_string_get (mach, pattern);
+  
   if (pattern->rx)
-    return;
-  preg = mu_sieve_malloc (mach, sizeof (*preg));
-  rc = regcomp (preg, pattern->orig, REG_EXTENDED | flags);
+    {
+      if (!pattern->changed)
+       return;
+      preg = pattern->rx;
+      regfree (preg);
+    }
+  else
+    preg = mu_sieve_malloc (mach, sizeof (*preg));
+  rc = regcomp (preg, str, REG_EXTENDED | flags);
   if (rc)
     {
       size_t size = regerror (rc, preg, NULL, 0);
@@ -107,11 +116,23 @@ compile_wildcard (mu_sieve_machine_t mach, 
mu_sieve_string_t *pattern,
 {
   int rc;
   regex_t *preg;
+  char *str;
 
+  str = mu_sieve_string_get (mach, pattern);
+  
   if (pattern->rx)
-    return;
-  preg = mu_sieve_malloc (mach, sizeof (*preg));
-  rc = mu_glob_compile (preg, pattern->orig, flags);
+    {
+      if (!pattern->changed)
+       return;
+      preg = pattern->rx;
+      regfree (preg);
+    }
+  else
+    preg = mu_sieve_malloc (mach, sizeof (*preg));
+
+  if (mu_sieve_has_variables (mach))
+    flags |= MU_GLOBF_SUB;
+  rc = mu_glob_compile (preg, str, flags);
   if (rc)
     {
       mu_sieve_error (mach, _("can't compile pattern"));
@@ -184,7 +205,7 @@ mu_sieve_match_part_checker (mu_sieve_machine_t mach)
       if (strcmp (match->tag, "count") == 0)
        {
          mu_sieve_value_t *val;
-         char *str;
+         mu_sieve_string_t *argstr;
          
          if (compname && strcmp (compname, "i;ascii-numeric"))
            {
@@ -213,14 +234,17 @@ mu_sieve_match_part_checker (mu_sieve_machine_t mach)
              mu_i_sv_error (mach);
              return 1;
            }
-         str = mu_sieve_string_raw (mach, &val->v.list, 0)->orig;
-         str = mu_str_skip_class (str, MU_CTYPE_DIGIT);
-         if (*str)
+         argstr = mu_sieve_string_raw (mach, &val->v.list, 0);
+         if (argstr->constant)
            {
-             mu_diag_at_locus (MU_LOG_ERROR, &mach->locus, 
-                          _("second argument cannot be converted to number"));
-             mu_i_sv_error (mach);
-             return 1;
+             char *p = mu_str_skip_class (argstr->orig, MU_CTYPE_DIGIT);
+             if (*p)
+               {
+                 mu_diag_at_locus (MU_LOG_ERROR, &mach->locus, 
+                                   _("second argument cannot be converted to 
number"));
+                 mu_i_sv_error (mach);
+                 return 1;
+               }
            }
        }
       else
@@ -258,7 +282,30 @@ mu_sieve_match_part_checker (mu_sieve_machine_t mach)
   
   return 0;
 }
+
+static int
+regmatch (mu_sieve_machine_t mach, mu_sieve_string_t *pattern, char const 
*text)
+{
+  regex_t *reg = pattern->rx;
+  regmatch_t *match_buf = NULL;
+  size_t match_count = 0; 
 
+  if (mu_sieve_has_variables (mach))
+    {
+      match_count = reg->re_nsub + 1;
+      while (mach->match_max < match_count)
+       mu_i_sv_2nrealloc (mach, (void **) &mach->match_buf,
+                          &mach->match_max,
+                          sizeof (mach->match_buf[0]));
+      mach->match_count = match_count;
+      mu_sieve_free (mach, mach->match_string);
+      mach->match_string = mu_sieve_strdup (mach, text);
+
+      match_buf = mach->match_buf;
+    }
+      
+  return regexec (reg, text, match_count, match_buf, 0) == 0;
+}
 /* Particular comparators */
 
 /* :comparator i;octet */
@@ -267,14 +314,14 @@ static int
 i_octet_is (mu_sieve_machine_t mach, mu_sieve_string_t *pattern,
            const char *text)
 {
-  return strcmp (pattern->orig, text) == 0;
+  return strcmp (mu_sieve_string_get (mach, pattern), text) == 0;
 }
 
 static int
 i_octet_contains (mu_sieve_machine_t mach, mu_sieve_string_t *pattern,
                  const char *text)
 {
-  return strstr (text, pattern->orig) != NULL;
+  return strstr (text, mu_sieve_string_get (mach, pattern)) != NULL;
 }
 
 static int 
@@ -282,7 +329,7 @@ i_octet_matches (mu_sieve_machine_t mach, mu_sieve_string_t 
*pattern,
                 const char *text)
 {
   compile_wildcard (mach, pattern, 0);
-  return regexec ((regex_t *)pattern->rx, text, 0, NULL, 0) == 0;
+  return regmatch (mach, pattern, text);
 }
 
 static int
@@ -290,14 +337,14 @@ i_octet_regex (mu_sieve_machine_t mach, mu_sieve_string_t 
*pattern,
               const char *text)
 {
   compile_pattern (mach, pattern, 0);
-  return regexec ((regex_t *)pattern->rx, text, 0, NULL, 0) == 0;
+  return regmatch (mach, pattern, text);
 }
 
 static int
 i_octet_eq (mu_sieve_machine_t mach,
            mu_sieve_string_t *pattern, const char *text)
 {
-  return strcmp (text, pattern->orig);
+  return strcmp (text, mu_sieve_string_get (mach, pattern));
 }
 
 /* :comparator i;ascii-casemap */
@@ -305,14 +352,14 @@ static int
 i_ascii_casemap_is (mu_sieve_machine_t mach,
                    mu_sieve_string_t *pattern, const char *text)
 {
-  return mu_c_strcasecmp (pattern->orig, text) == 0;
+  return mu_c_strcasecmp (mu_sieve_string_get (mach, pattern), text) == 0;
 }
 
 static int
 i_ascii_casemap_contains (mu_sieve_machine_t mach,
                          mu_sieve_string_t *pattern, const char *text)
 {
-  return mu_c_strcasestr (text, pattern->orig) != NULL;
+  return mu_c_strcasestr (text, mu_sieve_string_get (mach, pattern)) != NULL;
 }
 
 static int
@@ -320,7 +367,7 @@ i_ascii_casemap_matches (mu_sieve_machine_t mach,
                         mu_sieve_string_t *pattern, const char *text)
 {
   compile_wildcard (mach, pattern, MU_GLOBF_ICASE);
-  return regexec ((regex_t *)pattern->rx, text, 0, NULL, 0) == 0;
+  return regmatch (mach, pattern, text);
 }
 
 static int
@@ -328,14 +375,14 @@ i_ascii_casemap_regex (mu_sieve_machine_t mach,
                       mu_sieve_string_t *pattern, const char *text)
 {
   compile_pattern (mach, pattern, REG_ICASE);
-  return regexec ((regex_t *) pattern->rx, text, 0, NULL, 0) == 0;
+  return regmatch (mach, pattern, text);
 }
 
 static int
 i_ascii_casemap_eq (mu_sieve_machine_t mach,
                    mu_sieve_string_t *pattern, const char *text)
 {
-  return mu_c_strcasecmp (text, pattern->orig);
+  return mu_c_strcasecmp (text, mu_sieve_string_get (mach, pattern));
 }
 
 /* :comparator i;ascii-numeric */
@@ -343,11 +390,12 @@ static int
 i_ascii_numeric_is (mu_sieve_machine_t mach,
                    mu_sieve_string_t *pattern, const char *text)
 {
-  if (mu_isdigit (*pattern->orig))
+  char *str = mu_sieve_string_get (mach, pattern);
+  if (mu_isdigit (*str))
     {
       if (mu_isdigit (*text))
        //FIXME: Error checking
-       return strtol (pattern->orig, NULL, 10) == strtol (text, NULL, 10);
+       return strtol (str, NULL, 10) == strtol (text, NULL, 10);
       else 
        return 0;
     }
@@ -361,11 +409,12 @@ static int
 i_ascii_numeric_eq (mu_sieve_machine_t mach,
                    mu_sieve_string_t *pattern, const char *text)
 {
-  if (mu_isdigit (*pattern->orig))
+  char *str = mu_sieve_string_get (mach, pattern);
+  if (mu_isdigit (*str))
     {
       if (mu_isdigit (*text))
        {
-         size_t a = strtoul (pattern->orig, NULL, 10);
+         size_t a = strtoul (str, NULL, 10);
          size_t b = strtoul (text, NULL, 10);
          if (b > a)
            return 1;
diff --git a/libmu_sieve/require.c b/libmu_sieve/require.c
index 372bd07..f0ff27c 100644
--- a/libmu_sieve/require.c
+++ b/libmu_sieve/require.c
@@ -37,7 +37,9 @@ mu_sieve_require (mu_sieve_machine_t mach, mu_sieve_slice_t 
list)
       char *name = str->orig;
       int rc;
       
-      if (strcmp (name, "relational") == 0) /* RFC 3431 */
+      if (strcmp (name, "variables") == 0) /* RFC 5229 */
+       rc = mu_sieve_require_variables (mach);
+      else if (strcmp (name, "relational") == 0) /* RFC 3431 */
        rc = mu_sieve_require_relational (mach, name);
       else if (strcmp (name, "encoded-character") == 0) /* RFC 5228, 2.4.2.4 */
        rc = mu_sieve_require_encoded_character (mach, name);
@@ -52,8 +54,7 @@ mu_sieve_require (mu_sieve_machine_t mach, mu_sieve_slice_t 
list)
 
       if (rc)
        {
-         mu_diag_at_locus (MU_LOG_ERROR, &mach->locus,
-                           _("can't require %s is not available"),
+         mu_diag_at_locus (MU_LOG_ERROR, &mach->locus, _("can't require %s"),
                            name);
          mu_i_sv_error (mach);
        }
diff --git a/libmu_sieve/runtime.c b/libmu_sieve/runtime.c
index 8317f81..264fa5f 100644
--- a/libmu_sieve/runtime.c
+++ b/libmu_sieve/runtime.c
@@ -228,7 +228,10 @@ sieve_run (mu_sieve_machine_t mach)
   if (rc == 0)
     {
       mach->action_count = 0;
-  
+
+      if (mu_sieve_has_variables (mach))
+       mu_assoc_clear (mach->vartab);
+      
       for (mach->pc = 1; mach->prog[mach->pc].handler; )
        (*mach->prog[mach->pc++].instr) (mach);
 
diff --git a/libmu_sieve/sieve-priv.h b/libmu_sieve/sieve-priv.h
index a960b89..f5fb38a 100644
--- a/libmu_sieve/sieve-priv.h
+++ b/libmu_sieve/sieve-priv.h
@@ -17,6 +17,7 @@
    <http://www.gnu.org/licenses/>. */
 
 #include <mailutils/sieve.h>
+#include <mailutils/assoc.h>
 #include <setjmp.h>
 #include <string.h>
 #include <regex.h>
@@ -90,13 +91,20 @@ struct mu_sieve_machine
   size_t pc;                 /* Current program counter */
   long reg;                  /* Numeric register */
 
+  /* Support for variables (RFC 5229) */
+  mu_assoc_t vartab;         /* Table of variables */
+  char *match_string;        /* The string used in the most recent match */
+  regmatch_t *match_buf;     /* Offsets of parenthesized groups */
+  size_t match_count;        /* Actual number of elements used in match_buf */
+  size_t match_max;          /* Total number of elements available in 
match_buf */
+
   /* Call environment */
   const char *identifier;    /* Name of action or test being executed */
   size_t argstart;           /* Index of the first argument in valspace */
   size_t argcount;           /* Number of positional arguments */
   size_t tagcount;           /* Number of tagged arguments */
   mu_sieve_comparator_t comparator; /* Comparator (for tests) */
-  
+
   int dry_run;               /* Dry-run mode */
   jmp_buf errbuf;            /* Target location for non-local exits */
   
@@ -233,4 +241,8 @@ size_t mu_i_sv_id_num (mu_sieve_machine_t mach, char const 
*name);
 char *mu_i_sv_id_str (mu_sieve_machine_t mach, size_t n);
 void mu_i_sv_free_idspace (mu_sieve_machine_t mach);
 
+void mu_i_sv_copy_variables (mu_sieve_machine_t child,
+                            mu_sieve_machine_t parent);
+int mu_i_sv_expand_variables (char const *input, size_t len,
+                             char **exp, void *data);
 
diff --git a/libmu_sieve/sieve.l b/libmu_sieve/sieve.l
index 8f51b4c..b18cd4a 100644
--- a/libmu_sieve/sieve.l
+++ b/libmu_sieve/sieve.l
@@ -529,6 +529,8 @@ multiline_strip_tabs (char *text)
 static void
 line_add (char *text, size_t len)
 {
+  if (len == 0)
+    len = strlen (text);
   mu_opool_append (mu_sieve_machine->string_pool, text, len);
 }
 
diff --git a/libmu_sieve/sieve.y b/libmu_sieve/sieve.y
index 46a8fba..0a612f1 100644
--- a/libmu_sieve/sieve.y
+++ b/libmu_sieve/sieve.y
@@ -1067,6 +1067,12 @@ mu_sieve_machine_reset (mu_sieve_machine_t mach)
   mach->progsize = 0;
   mach->prog = NULL;
 
+  mu_assoc_destroy (&mach->vartab);
+  mach->match_string = NULL;
+  mach->match_buf = NULL;
+  mach->match_count = 0;
+  mach->match_max = 0;
+  
   mach->state = mu_sieve_state_init;
 
   return 0;
@@ -1174,6 +1180,16 @@ mu_sieve_machine_clone (mu_sieve_machine_t const parent,
                                     sizeof child->prog[0]);
       memcpy (child->prog, parent->prog,
              parent->progsize * sizeof (child->prog[0]));
+
+      /* Copy variables */
+      if (mu_sieve_has_variables (parent))
+       {
+         mu_i_sv_copy_variables (child, parent);
+         child->match_string = NULL;
+         child->match_buf = NULL;
+         child->match_count = 0;
+         child->match_max = 0;
+       }
       
       /* Copy user-defined settings */
       
@@ -1468,6 +1484,30 @@ with_machine (mu_sieve_machine_t mach, char const *name,
   return rc;
 }
 
+/* Rescan all registered strings to determine their properties */
+static void
+string_rescan (mu_sieve_machine_t mach)
+{
+  size_t i;
+  int hasvar = mu_sieve_has_variables (mach);
+  
+  for (i = 0; i < mach->stringcount; i++)
+    {
+      mach->stringspace[i].changed = 0;
+      if (hasvar)
+       {
+         mach->stringspace[i].constant = 0;
+         mu_sieve_string_get (mach, &mach->stringspace[i]);
+         mu_sieve_free (mach, mach->stringspace[i].exp);
+         mach->stringspace[i].exp = NULL;
+         mach->stringspace[i].constant = !mach->stringspace[i].changed;
+         mach->stringspace[i].changed = 0;
+       }
+      else
+       mach->stringspace[i].constant = 1;
+    }
+}
+
 static int
 sieve_parse (void)
 {
@@ -1509,7 +1549,10 @@ sieve_parse (void)
       if (mu_sieve_machine->state == mu_sieve_state_error)
        rc = MU_ERR_PARSE;
       else
-       mu_sieve_machine->state = mu_sieve_state_compiled;
+       {
+         string_rescan (mu_sieve_machine);
+         mu_sieve_machine->state = mu_sieve_state_compiled;
+       }
     }
 
   tree_free (&sieve_tree);
diff --git a/libmu_sieve/strexp.c b/libmu_sieve/strexp.c
index b4f31e3..d629e4a 100644
--- a/libmu_sieve/strexp.c
+++ b/libmu_sieve/strexp.c
@@ -155,7 +155,8 @@ update_len (void *item, void *data)
       break;
 
     case segm_repl:
-      st->len += strlen (segm->repl);
+      if (segm->repl)
+       st->len += strlen (segm->repl);
       break;
     }
   return 0;
@@ -176,8 +177,13 @@ append_segm (void *item, void *data)
       break;
 
     case segm_repl:
-      len = strlen (segm->repl);
-      memcpy (buf->endptr, segm->repl, len);
+      if (segm->repl)
+       {
+         len = strlen (segm->repl);
+         memcpy (buf->endptr, segm->repl, len);
+       }
+      else
+       len = 0;
     }
   
   buf->endptr += len;
diff --git a/libmu_sieve/string.c b/libmu_sieve/string.c
index 254f3ad..6712bb6 100644
--- a/libmu_sieve/string.c
+++ b/libmu_sieve/string.c
@@ -44,7 +44,7 @@ mu_i_sv_string_create (mu_sieve_machine_t mach, char *str)
   return n;
 }
 
-struct mu_sieve_string *
+mu_sieve_string_t *
 mu_sieve_string_raw (mu_sieve_machine_t mach, mu_sieve_slice_t slice,
                     size_t i)
 {
@@ -54,8 +54,57 @@ mu_sieve_string_raw (mu_sieve_machine_t mach, 
mu_sieve_slice_t slice,
 }
 
 char *
+mu_sieve_string_get (mu_sieve_machine_t mach, mu_sieve_string_t *string)
+{
+  char *exp;
+  int rc;
+  
+  if (string->constant)
+    return string->orig;
+
+  rc = mu_i_sv_string_expand (string->orig, mu_i_sv_expand_variables, mach,
+                             &exp);
+  switch (rc)
+    {
+    case 0:
+      if (string->exp == NULL)
+       {
+         string->changed = strcmp (string->orig, exp) != 0;
+         string->exp = mu_sieve_strdup (mach, exp);
+         free (exp);
+       }
+      else if (strcmp (exp, string->exp) == 0)
+       {
+         string->changed = 0;
+         free (exp);
+       }
+      else
+       {
+         string->changed = 1;
+         mu_sieve_free (mach, string->exp);
+         string->exp = mu_sieve_strdup (mach, exp);
+         free (exp);
+       }
+      break;
+         
+    case MU_ERR_CANCELED:
+      string->changed = 0;
+      return string->orig;
+
+    default:
+      mu_sieve_error (mach, "error expanding variables: %s",
+                     mu_strerror (rc));
+      mu_sieve_abort (mach);
+    }
+
+  return string->exp;
+}
+
+char *
 mu_sieve_string (mu_sieve_machine_t mach, mu_sieve_slice_t slice,
                 size_t i)
 {
-  return mu_sieve_string_raw (mach, slice, i)->orig;
+  return mu_sieve_string_get (mach, mu_sieve_string_raw (mach, slice, i));
 }
+
+      
diff --git a/libmu_sieve/tests.c b/libmu_sieve/tests.c
index 647484b..e7b6406 100644
--- a/libmu_sieve/tests.c
+++ b/libmu_sieve/tests.c
@@ -55,21 +55,26 @@ struct address_closure
   mu_address_t addr;       /* Obtained address */
 };  
 
-static int
-do_count (mu_sieve_machine_t mach, size_t count, int retval)
+int
+mu_sieve_relational_count (mu_sieve_machine_t mach, size_t count, int retval)
 {
   char *relcmp;
   
   if (mu_sieve_get_tag (mach, "count", SVT_STRING, &relcmp))
     {
       size_t limit;
-      char *str;
+      char *str, *p;
       struct mu_sieve_slice slice;
       mu_sieve_relcmpn_t stest;
       
       mu_sieve_get_arg (mach, 1, SVT_STRING_LIST, &slice);
       str = mu_sieve_string (mach, &slice, 0);
-      limit = strtoul (str, &str, 10);
+      limit = strtoul (str, &p, 10);
+      if (*p)
+       {
+         mu_sieve_error (mach, _("%s: not an integer"), str);
+         mu_sieve_abort (mach);
+       }
 
       mu_sieve_str_to_relcmp (relcmp, NULL, &stest);
       return stest (count, limit);
@@ -122,7 +127,7 @@ sieve_test_address (mu_sieve_machine_t mach)
                               &count);
   mu_address_destroy (&clos.addr);
   
-  return do_count (mach, count, rc);
+  return mu_sieve_relational_count (mach, count, rc);
 }
 
 struct header_closure
@@ -196,7 +201,7 @@ sieve_test_header (mu_sieve_machine_t mach)
                              &count))
     return 1;
 
-  return do_count (mach, count + mcount, 0);
+  return mu_sieve_relational_count (mach, count + mcount, 0);
 }
 
 int
@@ -246,7 +251,7 @@ sieve_test_envelope (mu_sieve_machine_t mach)
   rc = mu_sieve_vlist_compare (mach, h, v, comp, test, retrieve_envelope, 
&clos,
                               &count);
   mu_address_destroy (&clos.addr);
-  return do_count (mach, count, rc);
+  return mu_sieve_relational_count (mach, count, rc);
 }
 
 int
@@ -301,7 +306,7 @@ static mu_sieve_tag_def_t address_part_tags[] = {
   { NULL }
 };
 
-static mu_sieve_tag_def_t match_part_tags[] = {
+mu_sieve_tag_def_t mu_sieve_match_part_tags[] = {
   { "is", SVT_VOID },
   { "contains", SVT_VOID },
   { "matches", SVT_VOID },
@@ -327,7 +332,7 @@ static mu_sieve_tag_def_t mime_tags[] = {
   { address_part_tags, NULL }
 
 #define MATCH_PART_GROUP \
-  { match_part_tags, mu_sieve_match_part_checker }
+  { mu_sieve_match_part_tags, mu_sieve_match_part_checker }
 
 #define SIZE_GROUP { size_tags, NULL }
 
diff --git a/libmu_sieve/variables.c b/libmu_sieve/variables.c
new file mode 100644
index 0000000..5c6ddac
--- /dev/null
+++ b/libmu_sieve/variables.c
@@ -0,0 +1,431 @@
+/* Sieve variables extension (RFC 5229) 
+   Copyright (C) 2016 Free Software Foundation, Inc.
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 3 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General
+   Public License along with this library.  If not, see
+   <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif  
+#include <sieve-priv.h>
+#include <ctype.h>
+
+struct sieve_variable
+{
+  char *value;
+};
+
+
+/* FIXME: UTF support */
+char *
+mod_lower (mu_sieve_machine_t mach, char const *value)
+{
+  char *newval = mu_sieve_malloc (mach, strlen (value) + 1);
+  char *p;
+
+  for (p = newval; *value; p++, value++)
+    *p = tolower (*value);
+  *p = 0;
+  return newval;
+}
+
+char *
+mod_upper (mu_sieve_machine_t mach, char const *value)
+{
+  char *newval = mu_sieve_malloc (mach, strlen (value) + 1);
+  char *p;
+
+  for (p = newval; *value; p++, value++)
+    *p = toupper (*value);
+  *p = 0;
+  return newval;
+}
+
+char *
+mod_lowerfirst (mu_sieve_machine_t mach, char const *value)
+{
+  char *newval = mu_sieve_strdup (mach, value);
+  *newval = tolower (*newval);
+  return newval;  
+}
+
+char *
+mod_upperfirst (mu_sieve_machine_t mach, char const *value)
+{
+  char *newval = mu_sieve_strdup (mach, value);
+  *newval = toupper (*newval);
+  return newval;  
+}
+
+char *
+mod_quotewildcard (mu_sieve_machine_t mach, char const *value)
+{
+  size_t len;
+  char *newval;
+  char const *p;
+  char *q;
+  
+  len = 0;
+  for (p = value; *p; p++)
+    {
+      switch (*p)
+       {
+       case '*':
+       case '?':
+       case '\\':
+         len += 2;
+         break;
+       default:
+         len++;
+       }
+    }
+
+  newval = mu_sieve_malloc (mach, len + 1);
+  for (p = value, q = newval; *p;)
+    {
+      switch (*p)
+       {
+       case '*':
+       case '?':
+       case '\\':
+         *q++ = '\\';
+       }
+      *q++ = *p++;
+    }
+  *q = 0;
+  return newval;
+}
+
+char *
+mod_length (mu_sieve_machine_t mach, char const *value)
+{
+  char *newval, *p;
+  int rc = mu_asprintf (&newval, "%zu", strlen (value));
+  if (rc)
+    {
+      mu_diag_funcall (MU_DIAG_ERROR, "mu_asprintf", NULL, rc);
+      mu_sieve_abort (mach);
+    }
+  p = mu_sieve_strdup (mach, newval);
+  free (newval);
+  return p;
+}
+
+static mu_sieve_tag_def_t set_tags[] = {
+  { "lower", SVT_VOID },
+  { "upper", SVT_VOID },
+  { "lowerfirst", SVT_VOID },
+  { "upperfirst", SVT_VOID },
+  { "quotewildcard", SVT_VOID },
+  { "length", SVT_VOID },
+  { NULL }
+};
+
+struct modprec
+{
+  char *name;
+  unsigned prec;
+  char *(*modify) (mu_sieve_machine_t mach, char const *value);
+};
+
+static struct modprec modprec[] = {
+  { "lower",         40, mod_lower },
+  { "upper",         40, mod_upper },
+  { "lowerfirst",    30, mod_lowerfirst },
+  { "upperfirst",    30, mod_upperfirst },
+  { "quotewildcard", 20, mod_quotewildcard },
+  { "length",        10, mod_length },
+};
+
+static struct modprec *
+findprec (char const *name)
+{
+  int i;
+  
+  for (i = 0; i < MU_ARRAY_SIZE (modprec); i++)
+    if (strcmp (modprec[i].name, name) == 0)
+      return &modprec[i];
+  mu_error ("%s:%d: INTERNAL ERROR", __FILE__, __LINE__);
+  abort ();
+}
+
+static int
+sieve_action_set (mu_sieve_machine_t mach)
+{
+  size_t i;
+  char *name;
+  char *value;
+  struct sieve_variable *vptr;
+  int rc;
+  
+  mu_sieve_get_arg (mach, 0, SVT_STRING, &name);
+  mu_sieve_get_arg (mach, 1, SVT_STRING, &value);
+
+  value = mu_sieve_strdup (mach, value);
+  for (i = 0; i < mach->tagcount; i++)
+    {
+      mu_sieve_value_t *p = mu_sieve_get_tag_n (mach, i);
+      char *str = findprec (p->tag)->modify (mach, value);
+      mu_sieve_free (mach, value);
+      value = str;
+    }
+
+  rc = mu_assoc_ref_install (mach->vartab, name, (void **)&vptr);
+  switch (rc)
+    {
+    case 0:
+      break;
+
+    case MU_ERR_EXISTS:
+      mu_sieve_free (mach, vptr->value);
+      break;
+
+    default:
+      mu_sieve_error (mach, "mu_assoc_ref_install: %s", mu_strerror (rc));
+      mu_sieve_abort (mach);
+    }
+  vptr->value = value;
+  return 0;
+}  
+
+static int
+set_tag_checker (mu_sieve_machine_t mach)
+{
+  int i, j;
+  
+  /* Sort tags by decreasing priority value (RFC 5229, 4.1) */
+  for (i = 1; i < mach->tagcount; i++)
+    {
+      mu_sieve_value_t tmp = *mu_sieve_get_tag_n (mach, i);
+      int tmp_prec = findprec (tmp.tag)->prec;
+
+      for (j = i - 1; j >= 0; j--)
+       {
+         mu_sieve_value_t *t = mu_sieve_get_tag_n (mach, j);
+         int prec = findprec (t->tag)->prec;
+         if (prec < tmp_prec)
+           *mu_sieve_get_tag_n (mach, j + 1) = *t;
+         else if (prec == tmp_prec)
+           {
+             mu_diag_at_locus (MU_LOG_ERROR, &mach->locus,
+                               _("%s and %s can't be used together"),
+                               tmp.tag, t->tag);
+             mu_i_sv_error (mach);
+             return 1;
+           }         
+         else
+           break;
+       }
+      *mu_sieve_get_tag_n (mach, j + 1) = tmp;
+    }
+  return 0;
+}
+
+static mu_sieve_tag_group_t set_tag_groups[] = {
+  { set_tags, set_tag_checker },
+  { NULL }
+};
+
+static mu_sieve_data_type set_args[] = {
+  SVT_STRING,
+  SVT_STRING,
+  SVT_VOID
+};
+
+/* RFC 5229, 5. Test string */
+int
+sieve_test_string (mu_sieve_machine_t mach)
+{
+  mu_sieve_value_t *source, *key_list;
+  mu_sieve_comparator_t comp = mu_sieve_get_comparator (mach);
+  mu_sieve_relcmp_t test = mu_sieve_get_relcmp (mach);
+  size_t count = 0;
+  int rc = 0;
+  size_t i;
+  
+  source = mu_sieve_get_arg_untyped (mach, 0);
+  key_list = mu_sieve_get_arg_untyped (mach, 1);
+
+  for (i = 0; i < source->v.list.count; i++)
+    {
+      char *item = mu_sieve_string (mach, &source->v.list, i);
+      size_t k;
+
+      /* The "relational" extension [RELATIONAL] adds a match type called
+        ":count".  The count of a single string is 0 if it is the empty
+        string, or 1 otherwise.  The count of a string list is the sum of the
+        counts of the member strings.
+      */
+      if (item[0])
+       count++;
+      for (k = 0; k < key_list->v.list.count; k++)
+       {
+         mu_sieve_string_t *s =
+           mu_sieve_string_raw (mach, &key_list->v.list, k);
+         rc = test (comp (mach, s, item), 0);
+         if (rc)
+           return rc;
+       }
+    }
+
+  return mu_sieve_relational_count (mach, count, 0);
+}
+
+mu_sieve_data_type string_args[] = {
+  SVT_STRING_LIST,
+  SVT_STRING_LIST,
+  SVT_VOID
+};
+
+mu_sieve_tag_group_t string_tag_groups[] = {
+  { mu_sieve_match_part_tags, mu_sieve_match_part_checker },
+  { NULL }
+};
+
+int
+mu_i_sv_expand_variables (char const *input, size_t len,
+                         char **exp, void *data)
+{
+  mu_sieve_machine_t mach = data;
+  if (mu_isdigit (*input))
+    {
+      char *p;
+      size_t idx = 0;
+      
+      while (len)
+       {
+         if (mu_isdigit (*input))
+           {
+             int d = *input - '0';
+             idx = idx * 10 + d;
+             input++;
+             len--;
+           }
+         else
+           return 1;
+       }
+
+      if (idx > mach->match_count)
+       {
+         *exp = NULL;
+         return 0;
+       }
+      
+      len = mach->match_buf[idx].rm_eo - mach->match_buf[idx].rm_so;
+      p = malloc (len + 1);
+      if (!p)
+       {
+         mu_sieve_error (mach, "%s", mu_strerror (errno));
+         mu_sieve_abort (mach);
+       }
+      memcpy (p, mach->match_string + mach->match_buf[idx].rm_so, len);
+      p[len] = 0;
+      *exp = p;
+    }
+  else if (mu_isalpha (*input))
+    {
+      size_t i;
+      char *name;
+      struct sieve_variable *var;
+
+      for (i = 0; i < len; i++)
+       if (!(mu_isalnum (input[i]) || input[i] == '_'))
+         return 1;
+
+      name = malloc (len + 1);
+      if (!name)
+       {
+         mu_sieve_error (mach, "%s", mu_strerror (errno));
+         mu_sieve_abort (mach);
+       }
+      memcpy (name, input, len);
+      name[len] = 0;
+
+      var = mu_assoc_ref (mach->vartab, name);
+
+      free (name);
+      
+      if (var)
+       {
+         *exp = strdup (var->value);
+         if (!*exp)
+           {
+             mu_sieve_error (mach, "%s", mu_strerror (errno));
+             mu_sieve_abort (mach);
+           }
+       }
+      else
+       *exp = NULL;
+    }
+  else
+    return 1;
+  return 0;
+}
+
+int
+mu_sieve_require_variables (mu_sieve_machine_t mach)
+{
+  int rc;
+  
+  if (mach->vartab)
+    return 0;
+
+  rc = mu_assoc_create (&mach->vartab, sizeof (struct sieve_variable),
+                       MU_ASSOC_ICASE);
+  if (rc)
+    mu_sieve_error (mach, "mu_assoc_create: %s", mu_strerror (rc));
+
+  if (rc == 0)
+    {
+      mu_sieve_register_action (mach, "set", sieve_action_set, 
+                               set_args, set_tag_groups, 1);
+      mu_sieve_register_test (mach, "string", sieve_test_string,
+                             string_args, string_tag_groups, 1);
+    }
+  return rc;
+}
+  
+int
+mu_sieve_has_variables (mu_sieve_machine_t mach)
+{
+  return mach->vartab != NULL;
+}
+
+void
+mu_i_sv_copy_variables (mu_sieve_machine_t child, mu_sieve_machine_t parent)
+{
+  mu_iterator_t itr;
+  
+  mu_sieve_require_variables (child);
+         
+  mu_assoc_get_iterator (parent->vartab, &itr);
+
+  for (mu_iterator_first (itr); !mu_iterator_is_done (itr);
+       mu_iterator_next (itr))
+    {
+      const char *name;
+      struct sieve_variable const *val;
+      struct sieve_variable newval;
+
+      mu_iterator_current_kv (itr, (const void **)&name, (void**)&val);
+      newval.value = mu_sieve_strdup (child, val->value);
+      mu_assoc_install (child->vartab, name, &newval);
+    }
+
+  mu_iterator_destroy (&itr);    
+}
+
+ 
+  
+
diff --git a/sieve/tests/Makefile.am b/sieve/tests/Makefile.am
index dbf983e..36a8be4 100644
--- a/sieve/tests/Makefile.am
+++ b/sieve/tests/Makefile.am
@@ -69,6 +69,7 @@ TESTSUITE_AT = \
   true.at\
   testsuite.at\
   vacation.at\
+  variables.at\
   version.at
 
 TESTSUITE = $(srcdir)/testsuite
diff --git a/sieve/tests/testsuite.at b/sieve/tests/testsuite.at
index 87b6ff1..748e06f 100644
--- a/sieve/tests/testsuite.at
+++ b/sieve/tests/testsuite.at
@@ -149,3 +149,4 @@ m4_include([addheader.at])
 m4_include([delheader.at])
 m4_include([vacation.at])
 
+m4_include([variables.at])
diff --git a/sieve/tests/variables.at b/sieve/tests/variables.at
new file mode 100644
index 0000000..54e8428
--- /dev/null
+++ b/sieve/tests/variables.at
@@ -0,0 +1,99 @@
+# This file is part of GNU Mailutils. -*- Autotest -*-
+# Copyright (C) 2016 Free Software Foundation, Inc.
+#
+# GNU Mailutils is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 3, or (at
+# your option) any later version.
+#
+# GNU Mailutils is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Mailutils.  If not, see <http://www.gnu.org/licenses/>.
+
+AT_BANNER([Variables extension])
+
+MUT_TESTCASE([match variable],[variables match match-variable],
+[require [["variables", "fileinto"]];
+if address :matches [[ "To", "Cc" ] [ "address@hidden", "address@hidden" ]]
+{
+   fileinto "INBOX.${3}.${1}";
+}],
+[],[0],[],
+[FILEINTO on msg uid 1: delivering into INBOX.acme.example.roadrunner
+FILEINTO on msg uid 2: delivering into INBOX.landru.example.rube
+IMPLICIT KEEP on msg uid 3
+])
+
+MUT_TESTCASE([set action],[variables action set],
+[require [["variables", "fileinto"]];
+set "name" "value";
+fileinto "INBOX.${name}";
+],
+[],[0],[],
+[FILEINTO on msg uid 1: delivering into INBOX.value
+FILEINTO on msg uid 2: delivering into INBOX.value
+FILEINTO on msg uid 3: delivering into INBOX.value
+])
+
+MUT_TESTCASE([variables with encoded characters],[variables encoded-character],
+[require [["encoded-character", "variables", "fileinto"]];
+set "myvar" "INBOX";
+fileinto "${${hex: 6D 79 76}ar}";
+],
+[],[0],[],
+[FILEINTO on msg uid 1: delivering into INBOX
+FILEINTO on msg uid 2: delivering into INBOX
+FILEINTO on msg uid 3: delivering into INBOX
+])
+
+MUT_TESTCASE([set modifiers],[variables action set],
+[require [["variables", "fileinto"]];
+set "name" :upperfirst :lower "VALUE";
+fileinto "INBOX.${name}";
+],
+[],[0],[],
+[FILEINTO on msg uid 1: delivering into INBOX.Value
+FILEINTO on msg uid 2: delivering into INBOX.Value
+FILEINTO on msg uid 3: delivering into INBOX.Value
+])
+
+MUT_TESTCASE([:quotewildcard modifier],[variables action set],
+[require [["variables", "fileinto"]];
+set "name" :quotewildcard ".a*strange?name\\!";
+fileinto "INBOX${name}";
+],
+[],[0],[],
+[FILEINTO on msg uid 1: delivering into INBOX.a\*strange\?name\\!
+FILEINTO on msg uid 2: delivering into INBOX.a\*strange\?name\\!
+FILEINTO on msg uid 3: delivering into INBOX.a\*strange\?name\\!
+])
+
+MUT_TESTCASE([:length modifier],[variables action set],
+[require [["variables", "fileinto"]];
+set "name" :length "value";
+fileinto "INBOX.${name}";
+],
+[],[0],[],
+[FILEINTO on msg uid 1: delivering into INBOX.5
+FILEINTO on msg uid 2: delivering into INBOX.5
+FILEINTO on msg uid 3: delivering into INBOX.5
+])
+
+MUT_TESTCASE([string test],[variables test string],
+[require "variables";
+if address "To" :matches "address@hidden"
+{
+  if string "${2}" [[ "landru", "ACME" ]]
+  {
+    discard;
+  }
+}],
+[],[0],[],
+[DISCARD on msg uid 1: marking as deleted
+DISCARD on msg uid 2: marking as deleted
+IMPLICIT KEEP on msg uid 3
+])


hooks/post-receive
-- 
GNU Mailutils



reply via email to

[Prev in Thread] Current Thread [Next in Thread]