This patch contains the pigeonhole sieve implementation from
https://github.com/dovecot/pigeonhole. It is automatically generated from the
package's git tree on build.
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/AUTHORS
@@ -0,0 +1,8 @@
+Stephan Bosch <stephan@rename-it.nl>
+
+This package is built for and partly based on the Dovecot Secure IMAP server
+written by:
+
+Timo Sirainen <tss@iki.fi>.
+
+Grepping 'patch by' from ChangeLog shows up more people.
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/COPYING
@@ -0,0 +1,4 @@
+See AUTHORS file for list of copyright holders.
+
+Everything is licenced under LGPLv2.1 (see COPYING.LGPL) unless otherwise
+mentioned at the beginning of the file.
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/COPYING.LGPL
@@ -0,0 +1,502 @@
+                  GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+                  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                            NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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 2.1 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, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/INSTALL
@@ -0,0 +1,874 @@
+Compiling
+=========
+
+If you downloaded the sources using Mercurial, you will need to execute
+./autogen.sh first to build the automake structure in your source tree. This
+process requires autotools and libtool to be installed.
+
+If you installed Dovecot from sources, Pigeonhole's configure script should be
+able to find the installed dovecot-config automatically:
+
+./configure
+make
+sudo make install
+
+If your system uses a $prefix different than the default /usr/local, the
+configure script can still find the installed dovecot-config automatically when
+supplied with the proper --prefix argument:
+
+./configure --prefix=/usr
+make
+sudo make install
+
+If this doesn't work, you can use --with-dovecot=<path> configure option, where
+the path points to a directory containing dovecot-config file. This can point to
+an installed file:
+
+./configure --with-dovecot=/usr/local/lib/dovecot
+make
+sudo make install
+
+or to a Dovecot source directory that is already compiled:
+
+./configure --with-dovecot=../dovecot-2.1.0
+make
+sudo make install
+
+The following additional parameters may be of interest for the configuration of
+the Pigeonhole build:
+
+ --with-managesieve=yes
+   Controls whether Pigeonhole ManageSieve is compiled and installed, which is
+   the default.
+
+ --with-unfinished-features=no
+   Controls whether unfinished features and extensions are built. Enabling this
+   will enable the compilation of code that is considered unfinished and highly
+   experimental and may therefore introduce bugs and unexpected behavior.
+   In fact, it may not compile at all. Enable this only when you are eager to
+   test some of the new development functionality.
+
+ --with-ldap=no
+   Controls wether Sieve LDAP support is built. This allows retrieving Sieve
+   scripts from an LDAP database. When set to `yes', support is built in. When
+   set to `plugin', LDAP support is compiled into a Sieve plugin called
+   `sieve_storage_ldap'.
+
+Configuration
+=============
+
+The Pigeonhole package provides the following items:
+
+  - The Sieve interpreter plugin for Dovecot's Local Delivery Agent (LDA): This
+    facilitates the actual Sieve filtering upon delivery.
+
+  - The ManageSieve Service: This implements the ManageSieve protocol through
+    which users can remotely manage Sieve scripts on the server.
+
+The functionality of these items is described in more detail in the README file.
+In this file and in this section their configuration is described. Example
+configuration files are provided in the doc/example-config directory of this
+package.
+
+Sieve Interpreter - Script Locations
+------------------------------------
+
+The Sieve interpreter can retrieve Sieve scripts from several types of
+locations. The default `file' location type is a local filesystem path pointing
+to a Sieve script file or a directory containing multiple Sieve script files.
+More complex setups can use other location types such as `ldap' or `dict' to
+fetch Sieve scripts from remote databases.
+
+All settings that specify the location of one ore more Sieve scripts accept the
+following syntax:
+
+location = [<type>:]path[;<option>[=<value>][;...]]
+
+The following script location types are implemented by default:
+
+  file  - The location path is a file system path pointing to the script file
+          or a directory containing script files with names structured as
+          `<script-name>.sieve'. Read doc/locations/file.txt for more
+          information and examples.
+
+  dict  - The location path is a Dovecot dict uri. Read doc/locations/dict.txt
+          for more information and examples.
+
+  ldap  - LDAP database lookup. The location path is a configuration file with
+          LDAP options. Read doc/locations/ldap.txt for more information
+          and examples.
+
+If the type prefix is omitted, the script location type is 'file' and the 
+location is interpreted as a local filesystem path pointing to a Sieve script
+file or directory.
+
+The following options are defined for all location types:
+
+  name=<script-name>
+    Set the name of the Sieve script that this location points to. If the name
+    of the Sieve script is not contained in the location path and the
+    location of a single script is specified, this option is required
+    (e.g. for dict locations that must point to a particular script).
+    If the name of the script is contained in the location path, the value of
+    the name option overrides the name retrieved from the location. If the Sieve
+    interpreter explicitly queries for a specific name (e.g. to let the Sieve
+    "include" extension retrieve a script from the sieve_global= location),
+    this option has no effect.
+
+  bindir=<dirpath>
+    Points to the directory where the compiled binaries for this script location
+    are stored. This directory is created automatically if possible. If this
+    option is omitted, the behavior depends on the location type. For `file'
+    type locations, the binary is then stored in the same directory as where the
+    script file was found if possible. For `dict' type locations, the binary is
+    not stored at all in that case. Don't specify the same directory for
+    different script locations, as this will result in undefined behavior.
+    Multiple mail users can share a single script directory if the script
+    location is the same and all users share the same system credentials (uid,
+    gid).
+
+Sieve Interpreter - Basic Configuration
+---------------------------------------
+
+To use Sieve, you will first need to make sure you are using Dovecot LDA
+or Dovecot LMTP for delivering incoming mail to users' mailboxes. Then, you need
+to enable the Sieve interpreter plugin for LDA/LMTP in your dovecot.conf:
+
+protocol lda {
+..
+  mail_plugins = sieve # ... other plugins like quota
+}
+
+protocol lmtp {
+..
+  mail_plugins = sieve # ... other plugins like quota
+}
+
+The Sieve interpreter recognizes the following configuration options in the
+plugin section of the config file (default values are shown if applicable):
+
+ sieve = file:~/sieve;active=~/.dovecot.sieve
+   The location of the user's main Sieve script or script storage. The LDA
+   Sieve plugin uses this to find the active script for Sieve filtering at
+   delivery. The "include" extension uses this location for retrieving
+   :personal" scripts.
+
+   This location is also where the ManageSieve service will store the user's
+   scripts, if supported by the location type. For the 'file' location type, the
+   location will then be the path to the storage directory for all the user's
+   personal Sieve scripts. ManageSieve maintains a symbolic link pointing to
+   the currently active script (the script executed at delivery). The location
+   of this symbolic link can be configured using the `;active=<path>' option.
+
+ sieve_default =
+   The location of the default personal sieve script file, which gets executed
+   ONLY if user's private Sieve script does not exist, e.g.
+   /var/lib/dovecot/default.sieve. This is usually a global script, so be sure
+   to pre-compile this script manually using the sievec command line tool, as
+   explained in the README file. This setting used to be called
+   `sieve_global_path', but that name is now deprecated.
+
+ sieve_default_name = 
+   The name by which the default Sieve script is visible to ManageSieve
+   clients. Normally, it is not visible at all. See "Visible Default Script"
+   section below for more information.
+
+ sieve_global =
+   Location for :global include scripts for the Sieve include extension. This
+   setting used to be called `sieve_global_dir', but that name is now
+   deprecated.
+
+ sieve_discard =
+   The location of a Sieve script that is run for any message that is about to
+   be discarded; i.e., it is not delivered anywhere by the normal Sieve
+   execution. This only happens when the "implicit keep" is canceled, by e.g.
+   the "discard" action, and no actions that deliver the message are executed.
+   This "discard script" can prevent discarding the message, by executing
+   alternative actions. If the discard script does nothing, the message is still
+   discarded as it would be when no discard script is configured.
+
+ sieve_extensions =
+   Which Sieve language extensions are available to users. By default, all
+   supported extensions are available, except for deprecated extensions or those
+   that are still under development. Some system administrators may want to
+   disable certain Sieve extensions or enable those that are not available by
+   default. This setting can use '+' and '-' to specify differences relative to
+   the default. For example `sieve_extensions = +imapflags' will enable the
+   deprecated imapflags extension in addition to all extensions were already
+   enabled by default.
+
+ sieve_global_extensions =
+   Which Sieve language extensions are ONLY avalable in global scripts. This can
+   be used to restrict the use of certain Sieve extensions to administrator
+   control, for instance when these extensions can cause security concerns. This
+   setting has higher precedence than the `sieve_extensions' setting (above),
+   meaning that the extensions enabled with this setting are never available to
+   the user's personal script no matter what is specified for the
+   `sieve_extensions' setting. The syntax of this setting is similar to
+   the `sieve_extensions' setting, with the difference that extensions are
+   enabled or disabled for exclusive use in global scripts. Currently, no
+   extensions are marked as such by default.
+
+ sieve_implicit_extensions =
+   Which Sieve language extensions are implicitly available to users. The
+   extensions listed in this setting do not need to be enabled explicitly using
+   the Sieve "require" command. This behavior directly violates the Sieve
+   standard, but can be necessary for compatibility with some existing
+   implementations of Sieve (notably jSieve). Do not use this setting unless you
+   really need to! The syntax and semantics of this setting are otherwise
+   identical to the `sieve_extensions' setting
+
+ sieve_plugins =
+   The Pigeonhole Sieve interpreter can have plugins of its own. Using this
+   setting, the used plugins can be specified. Check the Dovecot wiki
+   (wiki2.dovecot.org) or the pigeonhole website (http://pigeonhole.dovecot.org)
+   for available plugins. The `sieve_extprograms' plugin is included in this
+   release. LDAP support may also be compiled as a plugin called
+   `sieve_storage_ldap'.
+
+ sieve_user_email =
+   The primary e-mail address for the user. This is used as a default when no
+   other appropriate address is available for sending messages. If this setting
+   is not configured, either the postmaster or null "<>" address is used as a
+   sender, depending on the action involved. This setting is important when
+   there is no message envelope to extract addresses from, such as when the
+   script is executed in IMAP.
+
+ sieve_user_log =
+   The path to the file where the user log file is written. If not configured, a
+   default location is used. If the main user's personal Sieve (as configured
+   with sieve=) is a file, the logfile is set to <filename>.log by default. If
+   it is not a file, the default user log file is ~/.dovecot.sieve.log.
+
+ recipient_delimiter = +
+   The separator that is expected between the :user and :detail address parts
+   introduced by the subaddress extension. This may also be a sequence of
+   characters (e.g. '--'). The current implementation looks for the separator
+   from the left of the localpart and uses the first one encountered. The :user
+   part is left of the separator and the :detail part is right. This setting is
+   also used by Dovecot's LMTP service.
+
+ sieve_redirect_envelope_from = sender
+   Specifies what envelope sender address is used for redirected messages.
+   Normally, the Sieve "redirect" command copies the sender address for the
+   redirected message from the  processed message. So, the redirected message
+   appears to originate from the original sender. The following values are
+   supported for this setting:
+   
+     "sender"         - The sender address is used (default).
+     "recipient"      - The final recipient address is used.
+     "orig_recipient" - The original recipient is used.
+     "user_email"     - The user's primary address is used. This is
+                        configured with the "sieve_user_email" setting. If
+                        that setting is unconfigured, "user_mail" is equal to
+                        "sender" (the default).
+     "postmaster"     - The postmaster_address configured for the LDA.
+     "<user@domain>"  - Redirected messages are always sent from user@domain.
+                        The angle brackets are mandatory. The null "<>" address
+                        is also supported.
+
+   When the envelope sender of the processed message is the null address "<>",
+   the envelope sender of the redirected message is also always "<>",
+   irrespective of what is configured for this setting. 
+
+ sieve_redirect_duplicate_period = 12h
+   In an effort to halt potential mail loops, the Sieve redirect action records
+   identifying information for messages it has forwarded. If a duplicate message
+   is seen, it is not redirected and the message is discarded; i.e., the
+   implicit keep is canceled. This setting configures the period during which
+   the identifying information is recorded. If an account forwards many
+   messages, it may be necessary to lower this setting to prevent the
+   ~/.dovecot.lda-dupes database file (in which these are recorded) from growing
+   to an impractical size.
+
+For example:
+
+plugin {
+...
+  # The include extension fetches the :personal scripts from this
+  # directory. When ManageSieve is used, this is also where scripts
+  # are uploaded.
+  sieve = file:~/sieve
+
+  # If the user has no personal active script (i.e. if the location
+  # indicated in sieve= settings does have and active script or does not exist),
+  # use this one:
+  sieve_default = /var/lib/dovecot/sieve/default.sieve
+
+  # The include extension fetches the :global scripts from this
+  # directory.
+  sieve_global = /var/lib/dovecot/sieve/global/
+}
+
+Sieve Interpreter - Configurable Limits
+---------------------------------------
+
+ sieve_max_script_size = 1M
+   The maximum size of a Sieve script. The compiler will refuse to compile any
+   script larger than this limit. If set to 0, no limit on the script size is
+   enforced.
+
+ sieve_max_actions = 32
+   The maximum number of actions that can be performed during a single script
+   execution. If set to 0, no limit on the total number of actions is enforced.
+
+ sieve_max_redirects = 4
+   The maximum number of redirect actions that can be performed during a single
+   script execution. If set to 0, no redirect actions are allowed.
+
+Sieve Interpreter - Per-user Sieve Script Location
+--------------------------------------------------
+
+By default, the Pigeonhole LDA Sieve plugin looks for the user's Sieve script
+file in the user's home directory (~/.dovecot.sieve). This requires that the
+home directory is set for the user.
+
+If you want to store the script elsewhere, you can override the default using
+the sieve= setting, which specifies the location of the user's script file. This
+can be done in two ways:
+
+ 1. Define the sieve setting in the plugin section of dovecot.conf.
+ 2. Return sieve extra field from userdb extra fields.
+
+For example, to use a Sieve script file named <username>.sieve in
+/var/sieve-scripts, use:
+
+plugin {
+...
+
+ sieve = /var/sieve-scripts/%u.sieve
+}
+
+You may use templates like %u, as shown in the example. Refer to
+http://wiki.dovecot.org/Variables for more information.
+
+A relative path (or just a filename) will be interpreted to point under the
+user's home directory.
+
+Sieve Interpreter - Executing Multiple Scripts Sequentially
+-----------------------------------------------------------
+
+Pigeonhole's Sieve interpreter allows executing multiple Sieve scripts
+sequentially. The extra scripts can be executed before and after the user's
+private script. For example, this allows executing global Sieve policies before
+the user's script. The following settings in the plugin section of the Dovecot
+config file control the execution sequence:
+
+ sieve_before =
+ sieve_before2 =
+ sieve_before3 = (etc..)
+   Location Sieve of scripts that need to be executed before the user's personal
+   script. If a 'file' location path points to a directory, all the Sieve
+   scripts contained therein (with the proper `.sieve' extension) are executed.
+   The order of execution within that directory is determined by the file names,
+   using a normal 8bit per-character comparison.
+
+   Multiple script locations can be specified by appending an increasing number
+   to the setting name. The Sieve scripts found from these locations are added
+   to the script execution sequence in the specified order. Reading the numbered
+   sieve_before settings stops at the first missing setting, so no numbers may
+   be skipped.
+
+ sieve_after =
+ sieve_after2 =
+ sieve_after3 = (etc..)
+   Identical to sieve_before, but the specified scripts are executed after the
+   user's script (only when keep is still in effect, as explained below).
+
+The script execution ends when the currently executing script in the sequence
+does not yield a "keep" result: when the script terminates, the next script is
+only executed if an implicit or explicit "keep" is in effect. Thus, to end all
+script execution, a script must not execute keep and it must cancel the implicit
+keep, e.g. by executing "discard; stop;". This means that the command "keep;"
+has different semantics when used in a sequence of scripts. For normal Sieve
+execution, "keep;" is equivalent to "fileinto "INBOX";", because both cause the
+message to be stored in INBOX. However, in sequential script execution, it only
+controls whether the next script is executed. Storing the message into INBOX
+(the default folder) is not done until the last script in the sequence executes
+(implicit) keep. To force storing the message into INBOX earlier in the
+sequence, the fileinto command can be used (with ":copy" or together with
+"keep;").
+
+Apart from the keep action, all actions triggered in a script in the sequence
+are executed before continuing to the next script. This means that when a script
+in the sequence encounters an error, actions from earlier executed scripts are
+not affected. The sequence is broken however, meaning that the script execution
+of the offending script is aborted and no further scripts are executed. An
+implicit keep is executed instead if necessary, meaning that the interpreter
+makes sure that the message is at least stored in the default folder (INBOX).
+
+Just as for executing a single script the normal way, the Pigeonhole LDA Sieve
+plugin takes care never to duplicate deliveries, forwards or responses. When
+vacation actions are executed multiple times in different scripts, the usual
+error is not triggered: the subsequent duplicate vacation actions are simply
+discarded.
+
+For example:
+
+plugin {
+...
+   # Global scripts executed before the user's personal script.
+   #   E.g. handling messages marked as dangerous
+   sieve_before = /var/lib/dovecot/sieve/discard-virusses.sieve
+
+   # Domain-level scripts retrieved from LDAP
+   sieve_before2 = ldap:/etc/dovecot/sieve-ldap.conf;name=ldap-domain
+
+   # User-specific scripts executed before the user's personal script.
+   #   E.g. a vacation script managed through a non-ManageSieve GUI.
+   sieve_before3 = /var/vmail/%d/%n/sieve-before
+
+   # User-specific scripts executed after the user's personal script.
+   # (if keep is still in effect)
+   #   E.g. user-specific default mail filing rules
+   sieve_after = /var/vmail/%d/%n/sieve-after
+
+   # Global scripts executed after the user's personal script
+   # (if keep is still in effect)
+   #   E.g. default mail filing rules.
+   sieve_after2 = /var/lib/dovecot/sieve/after.d/
+}
+
+IMPORTANT: The scripts specified by sieve_before and sieve_after are often
+located in global locations to which the Sieve interpreter has no write access
+to store the compiled binaries. In that case, be sure to manually pre-compile
+those scripts using the sievec tool, as explained in the README file.
+
+Sieve Interpreter - Visible Default Script
+------------------------------------------
+
+The `sieve_default=' setting specifies the location of a default script that
+is executed when the user has no active personal script. Normally, this
+default script is invisible to the user; i.e., it is not listed in ManageSieve.
+To give the user the ability to see and read the default script, it is possible
+to make it visible under a specific configurable name using the
+`sieve_default_name' setting.
+
+ManageSieve will magically list the default script under that name, even though
+it does not actually exist in the user's normal script storage location. This
+way, the ManageSieve client can see that it exists and it can retrieve its
+contents. If no normal script is active, the default is always listed as active.
+The user can replace the default with a custom script, by uploading it under the
+default script's name. If that custom script is ever deleted, the default script
+will reappear from the shadows implicitly.
+
+This way, ManageSieve clients will not need any special handling for this
+feature. If the name of the default script is equal to the name the client uses
+for the main script, it will initially see and read the default script when the
+user account is freshly created. The user can edit the script, and when the
+edited script is saved through the ManageSieve client, it will will override the
+default script. If the user ever wants to revert to the default, the user only
+needs to delete the edited script and the default will reappear.
+
+The name by which the default script will be known is configured using the
+`sieve_default_name' setting. Of course, the `sieve_default' setting needs to
+point to a valid script location as well for this to work. If the default script
+does not exist at the indicated location, it is not shown.
+
+For example:
+
+plugin {
+...
+  sieve = file:~/sieve;active=~/.dovecot.sieve
+
+  sieve_default = /var/lib/dovecot/sieve/default.sieve
+	sieve_default_name = roundcube
+}
+
+Sieve Interpreter - Extension Configuration
+-------------------------------------------
+
+- Editheader extension:
+
+  The editheader extension [RFC5293] enables sieve scripts to interact with
+  other components that consume or produce header fields by allowing the script
+  to delete and add header fields.
+
+  The editheader extension requires explicit configuration and is not enabled
+  for use by default. Refer to doc/extensions/editheader.txt for configuration
+  information.
+
+- Vacation extension:
+
+  The Sieve vacation extension [RFC5230] defines a mechanism to generate
+  automatic replies to incoming email messages.
+
+  The vacation extension is available by default, but it has its own specific
+  configuration options. Refer to doc/extensions/vacation.txt for settings
+  specific to the vacation extension.
+
+- Variables extension:
+
+  The Sieve variables extension [RFC5229] adds the concept of variables to the
+  Sieve language.
+
+  The variables extension is available by default, but it has its own specific
+  configuration options. Refer to doc/extensions/variables.txt for settings
+  specific to the variables extension.
+
+- Include extension:
+
+  The Sieve include extension (draft) permits users to include one Sieve script
+  into another.
+
+  The include extension is available by default, but it has its own specific
+  configuration options. Refer to doc/extensions/include.txt for settings
+  specific to the include extension.
+
+- Spamtest and virustest extensions:
+
+  Using the spamtest and virustest extensions (RFC 5235), the Sieve language
+  provides a uniform and standardized command interface for evaluating spam and
+  virus tests performed on the message. Users no longer need to know what headers
+  need to be checked and how the scanner's verdict is represented in the header
+  field value. They only need to know how to use the spamtest (spamtestplus) and
+  virustest extensions. This also gives GUI-based Sieve editors the means to
+  provide a portable and easy to install interface for spam and virus filter
+  configuration.
+
+  The spamtest, spamtestplus and virustest extensions require explicit
+  configuration and are not enabled for use by default. Refer to
+  doc/extensions/spamtest-virustest.txt for configuration information.
+
+- Duplicate extension:
+
+  The duplicate extension augments the Sieve filtering implementation with a
+  test that facilitates detecting and handling duplicate message deliveries,
+  e.g. as caused by mailinglists when people reply both to the mailinglist and
+  the user directly.
+
+  Refer to doc/extensions/vnd.dovecot.duplicate.txt for configuration
+  information.
+
+- Dovecot environment extension:
+
+  The vnd.dovecot.environment extension builds on the standard environment
+  extension. It adds a few extra environment items that are useful for Sieve
+  scripts used by Dovecot.
+
+  Refer to doc/extensions/vnd.dovecot.environment.txt for more information.
+
+- Extprograms plugin;
+    vnd.dovovecot.pipe, vnd.dovecot.filter, vnd.dovecot.execute extensions:
+
+  The "sieve_extprograms" plugin provides extensions to the Sieve filtering
+  language adding new action commands for invoking a predefined set of external
+  programs. Messages can be piped to or filtered through those programs	and
+  string data can be input to and retrieved from those programs.
+
+  This plugin and the extensions it provides require explicit configuration and
+  are not enabled for use by default. Refer to doc/plugins/sieve_extprograms.txt
+  for more information.
+
+- Imapsieve plugins
+
+  The "sieve_imapsieve" Sieve plugin and the "imap_sieve" IMAP plugin add Sieve
+  support to IMAP.
+
+  Refer to doc/plugins/imapsieve.txt for more information.
+
+- IMAP Filter Sieve plugin
+
+  The "imap_filter_sieve" plugin adds the ability to manually invoke Sieve
+  filtering in IMAP.
+
+  Refer to doc/plugins/imap_filter_sieve.txt for more information.
+
+Sieve Interpreter - Trace Debugging
+-----------------------------------
+
+Trace debugging provides detailed insight in the operations performed by
+the Sieve script. Messages about what the Sieve script is doing are written
+to the specified directory.
+
+WARNING: On a busy server, this functionality can quickly fill up the trace
+directory with a lot of trace files. Enable this only temporarily and as
+selective as possible.
+
+The following settings apply to both the LDA Sieve plugin and the IMAPSIEVE
+plugin:
+
+ sieve_trace_dir =  
+  The directory where trace files are written. Trace debugging is disabled if
+  this setting is not configured or if the directory does not exist. If the 
+  path is relative or it starts with "~/" it is interpreted relative to the
+  current user's home directory.
+
+ sieve_trace_level = 
+   The verbosity level of the trace messages. Trace debugging is disabled if
+   this setting is not configured. Possible values are:
+  
+     "actions"  - Only print executed action commands, like keep, fileinto,
+                  reject and redirect.
+     "commands" - Print any executed command, excluding test commands.
+     "tests"    - Print all executed commands and performed tests.
+     "matching" - Print all executed commands, performed tests and the values
+                  matched in those tests.
+
+ sieve_trace_debug = no  
+   Enables highly verbose debugging messages that are usually only useful for
+   developers.
+
+ sieve_trace_addresses = no   
+   Enables showing byte code addresses in the trace output, rather than only
+   the source line numbers.
+
+Sieve Interpreter - Migration from CMUSieve (Dovecot v1.0/v1.1)
+---------------------------------------------------------------
+
+For the most part, migration from CMUSieve to the Pigeonhole LDA Sieve plugin is
+just a matter of changing the used plugin name from 'cmusieve' to 'sieve' in the
+mail_plugins option in the protocol lda section of the config file (as explained
+above). However, there are a few important differences:
+
+ * The imapflags extension is now called imap4flags. The CMUSieve implementation
+   is based on an old draft specification that is not completely compatible.
+   Particularly, the mark and unmark commands were removed from the new
+   specification. For backwards compatibility, support for the old imapflags
+   extension can be enabled using the sieve_extensions setting (as explained
+   above). This is disabled by default.
+
+ * The notify extension is now called enotify. The CMUSieve implementation is
+   based on an old draft specification that is not completely compatible.
+   Particularly, the denotify command and $text$ substitutions were removed from
+   the new specification. For backwards compatibility, support for the old
+   imapflags extension can be enabled using the sieve_extensions setting (as
+   explained above). This is disabled by default.
+
+ * The include extension now requires your script file names to end with
+   ".sieve". This means that ` include :personal "myscript"; ' won't work unless
+   you rename "myscript" to "myscript.sieve"
+
+Sieve Interpreter - Migration from Dovecot Sieve v0.1.x (Dovecot v1.2)
+----------------------------------------------------------------------
+
+ * Dovecot v2.0 adds support for LMTP. Much like the Dovecot LDA, it can make
+   use of the Pigeonhole Sieve plugin. Since the LMTP service has its own
+   prototocol lmtp section in the config file, you need to add the Sieve plugin
+   to the mail_plugins setting there too when you decide to use LMTP.
+ * The 'sieve_subaddress_sep' setting for the Sieve subaddress extension is now
+   known as 'recipient_delimiter'. Although sieve_subaddress_sep is still
+   recognized for backwards compatibility, it is recommended to update the
+   setting to the new name, since the LMTP service also uses the
+   recipient_delimiter setting.
+
+ManageSieve Service - Basic Configuration
+-----------------------------------------
+
+IMPORTANT:
+
+  If you have used the LDA Sieve plugin without ManageSieve before and you have
+  .dovecot.sieve files in user directories, you are advised to make a backup
+  before installing ManageSieve. Although the ManageSieve service takes care to
+  move these files to the Sieve directory before it is substituted with a
+  symbolic link, this is not a very well tested operation, meaning that there is
+  a slim possibility that existing Sieve scripts get lost.
+
+Just like all other binaries that Dovecot uses, the managesieve and
+managesieve-login binaries are installed during make install.
+
+There are two things that have be done to activate the ManageSieve support in
+Dovecot:
+
+ * The first step is to add `sieve' to the protocols= configuration line in
+   your dovecot.conf.
+
+ * The next step is specifying an additional service type for the ManageSieve
+   service. This could be done in Dovecot's conf.d/master.conf or you can use
+   the 20-managesieve.conf file from the doc/example-config/conf.d directory of
+   this package.
+
+   For example:
+
+    service managesieve-login {
+      inet_listener sieve {
+        port = 4190
+      }
+    }
+
+Because the implementation of the ManageSieve daemon is largely based on the
+original IMAP implementation, it is very similar in terms of configuration. The
+following settings can be configured in the 'protocol sieve' section:
+
+ managesieve_max_line_length = 65536
+   The maximum ManageSieve command line length in bytes. This setting is
+   directly borrowed from IMAP. But, since long command lines are very unlikely
+   with ManageSieve, changing this will not be very useful.
+
+ managesieve_logout_format = bytes=%i/%o
+   Specifies the string pattern used to compose the logout message of an
+   authenticated session. The following substitutions are available:
+
+     %i - total number of bytes read from client
+     %o - total number of bytes sent to client
+
+ managesieve_implementation_string = Dovecot Pigeonhole
+   To fool ManageSieve clients that are focused on CMU's timesieved, you can
+   specify the IMPLEMENTATION capability that the Dovecot reports to clients
+   (e.g. 'Cyrus timsieved v2.2.13').
+
+ managesieve_max_compile_errors = 5
+   The maximum number of compile errors that are returned to the client upon
+   script upload or script verification.
+
+ managesieve_sieve_capability =
+ managesieve_notify_capability =
+   Respectively the SIEVE and NOTIFY capabilities reported by the ManageSieve
+   service before authentication. If left unassigned these will be assigned
+   dynamically according to what the Sieve interpreter supports by default
+   (after login this may differ depending on the authenticated user).
+
+Additionally, the ManageSieve service uses the following settings from the
+plugin section of the config file. These settings are the same ones used by
+the LDA Sieve plugin.
+
+ sieve = file:~/sieve;active=~/.dovecot.sieve
+   This specifies the location where the scripts that are uploaded through
+   ManageSieve are stored. During delivery, the LDA Sieve plugin uses this
+   location setting to find the active script for Sieve filtering. The Sieve
+   "include" extension uses this location for retrieving ":personal" scripts.
+   If the location type does not allow uploading scripts, the ManageSieve
+   service cannot be used. Currently, only the 'file' location type supports
+   ManageSieve. 
+
+   For the 'file' location type:
+
+    * The location is the path to the storage directory for all the user's
+      personal Sieve scripts. Scripts are stored as separate files with
+      extension .sieve. All other files are ignored when scripts are listed
+      by a ManageSieve client. The storage directory is always generated
+      automatically if it does not exist (as far as the system permits the
+      user to do so; no root privileges are used). This is similar to the
+      behavior of the mail daemons regarding the mail_location configuration.
+   
+    * ManageSieve maintains a symbolic link pointing to the currently active
+      script (the script executed at delivery). The location of this symbolic
+      link can be configured using the ;active=<path> option. If a regular
+      file already exists at the location specified by in the ;active=<path>
+      location option, it is moved to the storage directory before the
+      symbolic link is installed. It is renamed to dovecot.orig.sieve and
+      therefore listed as dovecot.orig by a ManageSieve client.
+
+      NOTE: It is not wise to place this active symbolic link inside your
+            mail store, as it may be mistaken for a mail folder. Inside a
+            maildir for instance, the default .dovecot.sieve would show up
+            as phantom folder /dovecot/sieve in your IMAP tree. 
+
+   For Pigeonhole versions before v0.3.1, this setting can only be a
+   filesystem path pointing to a script file, or - when ManageSieve is used -
+   it is the location of the symbolic link pointing to the active script in
+   the storage directory. That storage directory is then configured using the
+   deprecated `sieve_dir' setting. 
+
+The following provides an example configuration for Sieve and ManageSieve. Only
+sections and settings relevant to ManageSieve are shown. Refer to
+20-managesieve.conf in the doc/example-config/conf.d directory for a full
+example with comments, but don't forget to configure Sieve and add sieve to the
+'protocols = ...' setting if you use it.
+
+# *** conf.d/20-managesieve.conf ***
+
+service managesieve-login {
+  inet_listener sieve {
+    # Bind the daemon only to the specified address(es)
+    # (default: *, ::)
+    address = 127.0.0.1
+    # Specify an alternative port the daemon must listen on
+    # (default: 4190)
+    port = 2000
+  }
+}
+
+protocol sieve {
+  managesieve_max_compile_errors = 10
+}
+
+# ***  conf.d/90-sieve.conf ***
+
+plugin {
+  sieve=file:~/sieve;active=~/.dovecot.sieve
+}
+
+# *** dovecot.conf ***
+
+# .... (other config items)
+
+# Start imap, pop3, lmtp and managesieve services
+protocols = imap pop3 lmtp sieve
+
+# .... (other config items)
+
+ManageSieve Service - Quota Support
+-----------------------------------
+
+By default, users can manage an unlimited number of Sieve scripts on the server
+through ManageSieve. However, ManageSieve can be configured to enforce limits on
+the number of personal Sieve scripts per user and/or the amount of disk storage
+used by these scripts. The maximum size of individual uploaded scripts is
+dictated by the configuration of the Sieve plugin. The limits are configured in
+the plugin section of the Dovecot configuration as follows:
+
+ sieve_max_script_size = 1M
+   The maximum size of a Sieve script. If set to 0, no limit on the script size
+   is enforced.
+
+ sieve_quota_max_scripts = 0
+   The maximum number of personal Sieve scripts a single user can have. If set
+   to 0, no limit on the number of scripts is enforced.
+
+ sieve_quota_max_storage = 0
+   The maximum amount of disk storage a single user's scripts may occupy. If set
+   to 0, no limit on the used amount of disk storage is enforced.
+
+ManageSieve Service - Proxying
+------------------------------
+
+Like Dovecot's imapd, the ManageSieve login daemon supports proxying to multiple
+backend servers. Although the underlying code is copied from the imapd sources
+for the most part, it has some ManageSieve-specifics that have not seen much
+testing.
+
+The proxy configuration wiki page for POP3 and IMAP should apply to ManageSieve
+as well:
+
+http://wiki.dovecot.org/PasswordDatabase/ExtraFields/Proxy
+
+ManageSieve Service - Migration
+-------------------------------
+
+The following has changed since the ManageSieve releases for Dovecot v1.x:
+
+ * For Dovecot v1.0 and v1.1, the sieve_dir setting used by ManageSieve was
+   called sieve_storage. Also, the sieve and sieve_storage settings were located
+   inside the 'protocol managesieve' section of the configuration. As per
+   Dovecot v1.2, these settings are shared with the Sieve plugin and located in
+   the 'plugin' section of the configuration. Make sure you have updated the
+   name of the sieve_dir setting and the location of both these settings if you
+   are upgrading from ManageSieve for Dovecot v1.0/v1.1.
+ * Pigeonhole ManageSieve does not use the mail_location configuration as a
+   fall-back anymore to determine a default location for storing Sieve scripts.
+   It always uses the sieve_dir setting, with default value ~/sieve.
+ * The Pigeonhole ManageSieve service now binds to TCP port 4190 by default due
+   to the IANA port assignment for the ManageSieve service. When upgrading from
+   v1.x, this should be taken into account. For a smooth transition, the service
+   can be configured manually to listen on both port 2000 and port 4190, as
+   demonstrated in the example above.
+ * The Dovecot configuration now calls the ManageSieve protocol 'sieve' instead
+   of 'managesieve' because it is registered as such with IANA. The binaries and
+   the services are still called 'managesieve' and 'managesieve-login'. The
+   example above demonstrates how this affects the configuration.
+
+Test Suite
+==========
+
+This package includes a test suite to verify the basic processing of the Sieve
+interpreter on your particular platform. Note that the test suite is not
+available when this package is compiled against the Dovecot headers only. The
+test suite executes a list of test cases and halts when one of them fails. If it
+executes all test cases successfully, the test suite finishes. You can execute
+the basic test suite using `make test`, which does not include the plugins. You
+can test the plugins using `make test-plugins`.
+
+A failing test case is always a bug and a report is greatly appreciated.
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/Makefile.am
@@ -0,0 +1,214 @@
+aclocaldir = $(datadir)/aclocal
+
+if BUILD_DOCS
+DOCS = doc
+endif
+
+SUBDIRS = \
+	. \
+	src \
+	$(DOCS)
+
+ACLOCAL_AMFLAGS = -I m4
+
+EXTRA_DIST = \
+	tests \
+	examples \
+	COPYING.LGPL \
+	ChangeLog \
+	update-version.sh
+
+dist-hook:
+	rm -rf `find $(distdir)/tests -type f -name '*.svbin'`
+
+pkginc_libdir=$(dovecot_pkgincludedir)/sieve
+dist_pkginc_lib_HEADERS = \
+    pigeonhole-version.h
+nodist_pkginc_lib_HEADERS = \
+	pigeonhole-config.h
+
+ChangeLog:
+	git log --name-status \
+		--pretty="format:%ai %aN <%aE> (%h)%n%n%w(80,4,4)%s%n%n%b" > ChangeLog \
+			|| rm -f ChangeLog
+
+dist_aclocal_DATA = dovecot-pigeonhole.m4
+
+pigeonhole-version.h: noop
+	$(SHELL) $(top_srcdir)/update-version.sh $(top_srcdir) $(top_builddir)
+
+noop:
+
+DISTCLEANFILES = \
+    $(top_builddir)/pigeonhole-version.h \
+	$(top_builddir)/run-test.sh
+
+# Testsuite tests (FIXME: ugly)
+
+TESTSUITE_BIN = $(top_builddir)/src/testsuite/testsuite $(TESTSUITE_OPTIONS)
+
+TEST_BIN = $(RUN_TEST) $(TESTSUITE_BIN)
+
+if BUILD_UNFINISHED
+test_unfinished =
+else
+test_unfinished =
+endif
+
+test_cases = \
+	tests/testsuite.svtest \
+	tests/control-if.svtest \
+	tests/control-stop.svtest \
+	tests/test-allof.svtest \
+	tests/test-anyof.svtest \
+	tests/test-exists.svtest \
+	tests/test-header.svtest \
+	tests/test-address.svtest \
+	tests/test-size.svtest \
+	tests/compile/compile.svtest \
+	tests/compile/errors.svtest \
+	tests/compile/warnings.svtest \
+	tests/compile/recover.svtest \
+	tests/execute/errors.svtest \
+	tests/execute/actions.svtest \
+	tests/execute/smtp.svtest \
+	tests/execute/mailstore.svtest \
+	tests/execute/address-normalize.svtest \
+	tests/execute/examples.svtest \
+	tests/lexer.svtest \
+	tests/comparators/i-octet.svtest \
+	tests/comparators/i-ascii-casemap.svtest \
+	tests/match-types/is.svtest \
+	tests/match-types/contains.svtest \
+	tests/match-types/matches.svtest \
+	tests/multiscript/basic.svtest \
+	tests/multiscript/conflicts.svtest \
+	tests/extensions/encoded-character.svtest \
+	tests/extensions/envelope.svtest \
+	tests/extensions/variables/basic.svtest \
+	tests/extensions/variables/match.svtest \
+	tests/extensions/variables/modifiers.svtest \
+	tests/extensions/variables/quoting.svtest \
+	tests/extensions/variables/string.svtest \
+	tests/extensions/variables/errors.svtest \
+	tests/extensions/variables/regex.svtest \
+	tests/extensions/include/errors.svtest \
+	tests/extensions/include/variables.svtest \
+	tests/extensions/include/once.svtest \
+	tests/extensions/include/twice.svtest \
+	tests/extensions/include/optional.svtest \
+	tests/extensions/include/rfc.svtest \
+	tests/extensions/include/execute.svtest \
+	tests/extensions/imap4flags/basic.svtest \
+	tests/extensions/imap4flags/hasflag.svtest \
+	tests/extensions/imap4flags/execute.svtest \
+	tests/extensions/imap4flags/multiscript.svtest \
+	tests/extensions/imap4flags/flagstring.svtest \
+	tests/extensions/imap4flags/flagstore.svtest \
+	tests/extensions/body/basic.svtest \
+	tests/extensions/body/errors.svtest \
+	tests/extensions/body/raw.svtest \
+	tests/extensions/body/content.svtest \
+	tests/extensions/body/text.svtest \
+	tests/extensions/body/match-values.svtest \
+	tests/extensions/regex/basic.svtest \
+	tests/extensions/regex/match-values.svtest \
+	tests/extensions/regex/errors.svtest \
+	tests/extensions/reject/execute.svtest \
+	tests/extensions/reject/smtp.svtest \
+	tests/extensions/relational/basic.svtest \
+	tests/extensions/relational/rfc.svtest \
+	tests/extensions/relational/errors.svtest \
+	tests/extensions/relational/comparators.svtest \
+	tests/extensions/subaddress/basic.svtest \
+	tests/extensions/subaddress/rfc.svtest \
+	tests/extensions/subaddress/config.svtest \
+	tests/extensions/vacation/errors.svtest \
+	tests/extensions/vacation/execute.svtest \
+	tests/extensions/vacation/message.svtest \
+	tests/extensions/vacation/smtp.svtest \
+	tests/extensions/vacation/utf-8.svtest \
+	tests/extensions/vacation/reply.svtest \
+	tests/extensions/enotify/basic.svtest \
+	tests/extensions/enotify/encodeurl.svtest \
+	tests/extensions/enotify/valid_notify_method.svtest \
+	tests/extensions/enotify/notify_method_capability.svtest \
+	tests/extensions/enotify/errors.svtest \
+	tests/extensions/enotify/execute.svtest \
+	tests/extensions/enotify/mailto.svtest \
+	tests/extensions/environment/basic.svtest \
+	tests/extensions/environment/rfc.svtest \
+	tests/extensions/mailbox/execute.svtest \
+	tests/extensions/date/basic.svtest \
+	tests/extensions/date/date-parts.svtest \
+	tests/extensions/date/zones.svtest \
+	tests/extensions/index/basic.svtest \
+	tests/extensions/index/errors.svtest \
+	tests/extensions/spamvirustest/spamtest.svtest \
+	tests/extensions/spamvirustest/virustest.svtest \
+	tests/extensions/spamvirustest/spamtestplus.svtest \
+	tests/extensions/spamvirustest/errors.svtest \
+	tests/extensions/ihave/execute.svtest \
+	tests/extensions/ihave/errors.svtest \
+	tests/extensions/ihave/restrictions.svtest \
+	tests/extensions/editheader/addheader.svtest \
+	tests/extensions/editheader/deleteheader.svtest \
+	tests/extensions/editheader/alternating.svtest \
+	tests/extensions/editheader/utf8.svtest \
+	tests/extensions/editheader/protected.svtest \
+	tests/extensions/editheader/errors.svtest \
+	tests/extensions/editheader/execute.svtest \
+	tests/extensions/duplicate/errors.svtest \
+	tests/extensions/duplicate/execute.svtest \
+	tests/extensions/duplicate/execute-vnd.svtest \
+	tests/extensions/metadata/execute.svtest \
+	tests/extensions/metadata/errors.svtest \
+	tests/extensions/mime/errors.svtest \
+	tests/extensions/mime/header.svtest \
+	tests/extensions/mime/exists.svtest \
+	tests/extensions/mime/address.svtest \
+	tests/extensions/mime/execute.svtest \
+	tests/extensions/mime/content-header.svtest \
+	tests/extensions/mime/foreverypart.svtest \
+	tests/extensions/mime/extracttext.svtest \
+	tests/extensions/mime/calendar-example.svtest \
+	tests/extensions/vnd.dovecot/debug/execute.svtest \
+	tests/extensions/vnd.dovecot/environment/basic.svtest \
+	tests/extensions/vnd.dovecot/environment/variables.svtest \
+	tests/extensions/vnd.dovecot/report/errors.svtest \
+	tests/extensions/vnd.dovecot/report/execute.svtest \
+	tests/deprecated/notify/basic.svtest \
+	tests/deprecated/notify/mailto.svtest \
+	tests/deprecated/notify/errors.svtest \
+	tests/deprecated/notify/execute.svtest \
+	tests/deprecated/notify/denotify.svtest \
+	tests/deprecated/imapflags/execute.svtest \
+	tests/deprecated/imapflags/errors.svtest \
+	$(test_unfinished)
+
+$(test_cases):
+	@$(TEST_BIN) $(top_srcdir)/$@
+
+TEST_EXTPROGRAMS_BIN = $(TEST_BIN) \
+	-P src/plugins/sieve-extprograms/.libs/sieve_extprograms
+
+extprograms_test_cases = \
+	tests/plugins/extprograms/errors.svtest \
+	tests/plugins/extprograms/pipe/command.svtest \
+	tests/plugins/extprograms/pipe/errors.svtest \
+	tests/plugins/extprograms/pipe/execute.svtest \
+	tests/plugins/extprograms/filter/command.svtest \
+	tests/plugins/extprograms/filter/errors.svtest \
+	tests/plugins/extprograms/filter/execute.svtest \
+	tests/plugins/extprograms/execute/command.svtest \
+	tests/plugins/extprograms/execute/errors.svtest \
+	tests/plugins/extprograms/execute/execute.svtest
+
+$(extprograms_test_cases):
+	@$(TEST_EXTPROGRAMS_BIN) 	$(top_srcdir)/$@
+
+.PHONY: test test-plugins $(test_cases) $(extprograms_test_cases)
+test: all-am $(test_cases)
+test-plugins: all-am $(extprograms_test_cases)
+
+check: check-am test
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/NEWS
@@ -0,0 +1,1586 @@
+v0.5.4 2018-11-23  Stephan Bosch <stephan@rename-it.nl>
+
+	* Adjustments to several changes in Dovecot v2.3.4 make this Pigeonhole
+	  release dependent on that Dovecot release; it will not compile against
+	  older Dovecot versions. And, conversely, you need to upgrade
+	  Pigeonhole when upgrading Dovecot to v2.3.4.
+	* The changes regarding the default postmaster_address in Dovecot v2.3.4
+	  mainly apply to Pigeonhole. The new default should work for all
+	  existing installations, thereby fixing several reported v2.3/v0.5
+	  migration problems.
+	- IMAP FILTER=SIEVE capability: Fix assert crash occurring when running
+	  UID FILTER on a Sieve script with errors.
+
+v0.5.3 2018-10-01  Stephan Bosch <stephan@rename-it.nl>
+
+	- Fix assertion panic occurring when managesieve service fails to open
+	  INBOX while saving a Sieve script. This was caused by a lack of
+	  cleanup after failure.
+	- Fix specific messages causing an assert panic with actions that
+	  compose a reply (e.g. vacation). With some rather weird input from the
+	  original message, the header folding algorithm (as used for composing
+	  the References header for the reply) got confused, causing the panic.
+	- IMAP FILTER=SIEVE capability: Fix FILTER SIEVE SCRIPT command parsing.
+	  After finishing reading the Sieve script, the command parsing
+	  sometimes didn't continue with the search arguments. This is a time-
+	  critical bug that likely only occurs when the Sieve script is sent in
+	  the next TCP frame.
+
+v0.5.2 2018-06-29  Stephan Bosch <stephan@rename-it.nl>
+
+	+ Implement plugin for the a vendor-defined IMAP capability called
+	  "FILTER=SIEVE". It adds the ability to manually invoke Sieve filtering
+	  in IMAP. More information can be found in
+	  doc/plugins/imap_filter_sieve.txt.
+	- The Sieve addess test caused an assertion panic for invalid addresses
+	  with UTF-8 codepoints in the localpart. Fixed by properly detecting
+	  invalid addresses with UTF-8 codepoints in the localpart and skipping
+	  these like other invalid addresses while iterating addresses for the
+	  address test.
+	- Make the length of the subject header for the vacation response
+	  configurable and enforce the limit in UTF-8 codepoints rather than
+	  bytes. The subject header for a vacation response was statically
+	  truncated to 256 bytes, which is too limited for multi-byte UTF-8
+	  characters.
+	- Sieve editheader extension: Fix assertion panic occurring when it is
+	  used to manipulate a message header with a very large header field.
+	- Properly abort execution of the sieve_discard script upon error.
+	  Before, the LDA Sieve plugin attempted to execute the sieve_discard
+	  script when an error occurs. This can lead to the message being lost.
+	- Fix the interaction between quota and the sieve_discard script. When
+	  quota was used together with a sieve_discard script, the message
+	  delivery did not bounce when the quota was exceeded.
+
+v0.5.1 28-03-2018  Stephan Bosch <stephan@rename-it.nl>
+
+	- Explicitly disallow UTF-8 in localpart in addresses parsed from Sieve
+	  script.
+	- editheader extension: Corrected the stream position calculations
+	  performed while making the modified message available as a stream.
+	  Pigeonhole Sieve crashed in LMTP with an assertion panic when the
+	  Sieve editheader extension was used before the message was redirected.
+	  Experiments indicate that the problem occurred only with LMTP and that
+	  LDA is not affected.
+	- fileinto extension: Fix assert panic occurring when fileinto is used
+	  without being listed in the require line, while the copy extension is
+	  listed there. This is a very old bug.
+	- imapsieve plugin: Do not assert crash or log an error for messages
+	  that disappear concurrently while applying Sieve scripts. This event
+	  is now logged as a debug message.
+	- Sieve extprograms plugin: Large output from "execute" command crashed
+	  delivery. Fixed buffering issue in code that handles output from the
+	  external program.
+
+v0.5.0.1 05-01-2018  Stephan Bosch <stephan@rename-it.nl>
+
+	- imap4flags extension: Fix binary corruption occurring when
+	  setflag/addflag/removeflag flag-list is a variable.
+	- sieve-extprograms plugin: Fix segfault occurring when used in
+	  IMAPSieve context.
+
+v0.5.0 24-12-2017  Stephan Bosch <stephan@rename-it.nl>
+
+	* editheader extension: The implementation of header modifications is
+	  heavily updated. Although the functionality has not changed, the
+	  underlying code was updated to address several static analysis
+	  warnings, runtime integer arithmetic warnings (Clang), and to match
+	  updates in the Dovecot stream API.
+	+ variables extension: Made the maximum scope and variable size
+	  configurable.
+	+ subaddress: Support multiple recipient_delimiters.
+	- enotify extension: mailto method: Fixed parsing of mailto URI with
+	  only a header part.
+	- enotify plugin: mailto method: Make sure the "From:" header is set to
+	  a usable address and not "(null)".
+	- Fixed writing address headers to outgoing messages. Sometimes headers
+	  were MIME-encoded twice, yielding invalid results.
+
+v0.4.21 12-10-2017 Stephan Bosch <stephan@rename-it.nl>
+
+	* redirect action: Always set the X-Sieve-Redirected-From header to
+	  sieve_user_email if configured. Before, it would use the envelope recipient
+	  instead if available, which makes no sense if the primary e-mail address is
+	  available.
+	+ vacation extension: Allow ignoring the envelope sender while composing the
+	  "To:" header for the reply. Normally, the "To:" header is composed from
+	  the address found in the "Sender", "Resent-From" or "From" headers that is
+	  equal to the envelope sender. If none is then found, the bare envelope
+	  sender is used. This change adds a new setting
+	  "sieve_vacation_to_header_ignore_envelope". With this setting enabled, the
+	  "To:" header is always composed from those headers in the source message.
+	  The new setting thus allows ignoring the envelope, which is useful e.g.
+	  when SRS is used.
+	+ vacation extension: Compose the "To:" header from the full sender address
+	  found in the first "Sender:", "From:" or "Resent-From:" header. Before, it
+	  would create a "To:" header without a phrase part. The new behavior is
+	  nicer, since the reply will be addressed to the sender by name if possible.
+	- LDA Sieve plugin: Fixed sequential execution of LDAP-based scripts. A
+	  missing LDAP-based script could cause the script sequence to exit earlier.
+	- sieve-filter: Removed the (now) duplicate utf8 to mutf7 mailbox name
+	  conversion. This caused problems with mailbox names containing UTF-8
+	  characters. The Dovecot API was changed years ago, but apparently
+	  sieve-filter was never updated.
+
+v0.4.20 27-08-2017 Stephan Bosch <stephan@rename-it.nl>
+
+	+ Made the retention period for redirect duplicate identifiers configurable.
+	  For accounts that perform many redirects, the lda-dupes database could grow
+	  to impractical sizes. Changed the default retention period from 24 to 12
+	  hours.
+	- sieve-filter: Fixed memory leak: forgot to clean up script binary at end of
+	  execution. Normally, this would merely be an inconsequential memory leak.
+	  However, when the script comes from an LDAP storage, this would cause io
+	  leak warnings.
+	- managesieve-login: Fixed handling of AUTHENTICATE command. A second
+	  authenticate command would be parsed wrong. This problem was caused by
+	  changes in the previous release.
+	- LDA Sieve plugin: Fixed minor memory leak caused by not cleaning up the
+	  sieve_discard script.
+
+v0.4.19 26-06-2017 Stephan Bosch <stephan@rename-it.nl>
+
+	* This release adjusts Pigeonhole to several changes in the Dovecot API,
+	  making it depend on Dovecot v2.2.31. Previous versions of Pigeonhole will
+	  produce compile warnings with the recent Dovecot releases (but still work
+	  ok).
+	- Fixed bug in handling of implicit keep in some cases. Implicit side-effects,
+	  such as assigned flags, were not always applied correctly. This is in
+	  essence a very old bug, but it was exposed by recent changes.
+	- include extension: Fixed segfault that (sometimes) occurred when the global
+	  script location was left unconfigured.
+
+v0.4.18 12-04-2017 Stephan Bosch <stephan@rename-it.nl>
+
+	+ imapsieve plugin: Implemented the copy_source_after rule action. When this
+	  is enabled for a mailbox rule, the specified Sieve script is executed for
+	  the message in the source mailbox during a "COPY" event. This happens only
+	  after the Sieve script that is executed for the corresponding message in the
+	  destination mailbox finishes running successfully.
+	+ imapsieve plugin: Added non-standard Sieve environment items for the source
+	  and destination mailbox.
+	- multiscript: The execution of the discard script had an implicit "keep",
+	  rather than an implicit "discard".
+
+v0.4.17 26-02-2017 Stephan Bosch <stephan@rename-it.nl>
+
+	- LDA Sieve plugin: Fixed handling of an early explicit keep during
+	  multiscript execution. Action side-effects and the message snapshot would be
+	  lost at the final stage where the implicit keep is evaluated. This could
+	  result in the IMAP flags assigned to the message to be forgotten or that
+	  headers modified by the "editheader" extension would revert to their
+	  original state.
+	- file script storage: Amended the up-to-date time stamp comparison for
+	  on-disk binaries to include nanoseconds. This will fix problems occurring
+	  when both binary and script are saved within the same second. This fix is
+	  ineffective on older systems that have no support for nanoseconds in stat()
+	  timestamps, which	should be pretty rare nowadays.
+	- file script storage: Improve saving and listing permission error to include
+	  more details.
+	- imapsieve plugin: Make sure "INBOX" is upper case in static mailbox rules.
+	  Otherwise, the mailbox name would never match, since matching is performed
+	  case-sensitively and Dovecot only returns the upper-cased "INBOX".
+	- imapsieve plugin: Fixed assert failure occurring when used with virtual
+	  mailboxes.
+	- doveadm sieve plugin: Fixed crash when setting Sieve script via attribute's
+	  string value.
+
+v0.4.16 30-10-2016 Stephan Bosch <stephan@rename-it.nl>
+
+	* Part of the Sieve extprograms implementation was moved to Dovecot, which
+	  means that this release depends on Dovecot v2.2.26+.
+	* ManageSieve: The PUTSCRIPT command now allows uploading empty Sieve scripts.
+	  There was really no good reason to disallow doing that.
+	+ Sieve vnd.dovecot.report extension:
+	  + Added a Dovecot-Reporting-User field to the report body, which contains
+	    the e-mail address of the user sending the report.
+	  + Added support for configuring the "From:" address used in the report.
+	+ LDA sieve plugin: Implemented support for a "discard script" that is run
+	  when the message is going to be discarded. This allows doing something other
+	  than throwing the message away for good.
+	+ Sieve vnd.dovecot.environment extension: Added vnd.dovecot.config.*
+	  environment items. These environment items map to sieve_env_* settings from
+	  the plugin {} section in the configuration. Such values can of course also
+	  be returned from userdb.
+	+ Sieve vacation extension: Use the Microsoft X-Auto-Response-Suppress header
+	  to prevent unwanted responses from and to (older) Microsoft products.
+	+ ManageSieve: Added rawlog_dir setting to store ManageSieve traffic logs.
+	  This replaces at least partially the rawlog plugin (mimics similar IMAP/POP3
+	  change).
+	- doveadm sieve plugin: synchronization: Prevent setting file timestamps to
+	  unix epoch time. This occurred when Dovecot passed the timestamp as
+	  'unknown' during synchronization.
+	- Sieve exprograms plugin: Fixed spurious '+' sometimes returned at the end
+	  of socket-based program output.
+	- imapsieve plugin: Fixed crash occurring in specific situations.
+	- Performed various fixes based on static analysis and Clang warnings.
+
+v0.4.15 07-07-2016 Stephan Bosch <stephan@rename-it.nl>
+
+	* vacation extension: The sieve_user_email setting is now used in the check
+	  for implicit delivery.
+	- imapsieve plugin: For any mail transaction, the mailbox was opened a second
+	  time, even if no mailbox rule matched. This was unintentional, useless and
+	  caused problems when the imapsieve plugin was used with other plugins like
+	  acl.
+	- extprograms plugin: Significantly improved error handling. No stream errors
+	  were logged.
+	- extprograms plugin: Fixed bug in handling of result code from remote program
+	  (script service).
+	- extprograms plugin: Connection to remote program service was not retried.
+	- Several small fixes based on static analysis.
+	- Fixed handling of quoted string localparts in email addresses.
+
+v0.4.14 26-04-2016 Stephan Bosch <stephan@rename-it.nl>
+
+	* The address test now allows specifying the X-Original-To header.
+	+ Implemented the Sieve imapsieve extension and its IMAP counterpart
+	  (RFC 6785) as a set of plugins. This allows running Sieve scripts at IMAP
+	  activity, rather than at delivery. There are also facilities for the
+	  familiar sieve_before/sieve_after administrator scripts. A user script is
+	  defined for a mailbox using an IMAP METADATA entry, whereas administrator
+	  scripts are configured using mailbox matching rules defined in the plugin
+	  settings.
+	+ Adjusted the Sieve ihave extension to allow capability tests to be performed
+	  at runtime. This way, scripts can be written that work both at delivery and
+	  from IMAP.
+	+ Implemented support for runtime trace debugging. This means that detailed
+	  information about which commands, actions and tests are performed is written
+	  to a file. That file is created in the configured directory, but only if
+	  that directory exists. This way, a particular user can be easily singled out
+	  for debugging. This works much like the Dovecot rawlog facility. The trace
+	  output is identical to what is produced using sieve-test with its "-t"
+	  command line option.
+	+ Added a "sieve_user_email" setting that configures the user's primary email
+	  address. This is mainly useful to have a user email address available in
+	  IMAP, where envelope data is unavailable.
+	+ Implemented the dovecot-specific "vnd.dovecot.report" extension. This allows
+	  sending report messages in the Message Abuse Reporting Format (RFC 5965).
+	- extprograms plugin: Fixed epoll() panic caused by closing the output FD
+	  before the output stream.
+	- Made sure that the local part of a mail address is encoded properly using
+	  quoted string syntax when it is not a dot-atom.
+
+v0.4.13 18-03-2016 Stephan Bosch <stephan@rename-it.nl>
+
+	* redirect action: Added the list-id header to the duplicate ID for mail loop
+	  prevention. This means that the message sent directly to the user and the
+	  message coming through the mailing list itself are treated as different
+	  messages by the loop detection of the redirect command, even though their
+	  Message-ID may be identical.
+	* Changed the Sieve number type to uint64_t, which means that Sieve numbers
+	  can now technically range up to 2^64. Some other Sieve implementation
+	  allowed this, making this change necessary for successful migration.
+	+ Implemented the sieve_implicit_extensions setting. The extensions listed in
+	  this setting do not need to be enabled explicitly using the Sieve "require"
+	  command. This behavior directly violates the standard, but can be necessary
+	  for compatibility with some existing implementations of Sieve. Do not use
+	  this setting unless you really need to!
+	- redirect action: Made mail loop detection more robust by forcibly adding a
+	  Message-ID header if it is missing.
+	- Prevent logging a useless "script not found" error message for LDAP scripts
+	  for which the entry exists but no attribute containing a script. This is not
+	  necessarily an error.
+	- extprograms plugin: Changed the communication channel between parent and
+	  child process for a directly forked program from a socketpair to a double
+	  pipe. Linux does not support /dev/stdin, /dev/stdout and friends for
+	  sockets. For some shell program authors this may be confusing, so that is
+	  why it is changed. When using the script service, these device nodes are
+	  still not usable though.
+
+v0.4.12 06-02-2016 Stephan Bosch <stephan@rename-it.nl>
+
+	+ Implemented the Sieve extracttext extension (RFC 5703; Section 7). It is now
+	  possible to extract body text from a message into a variable.
+	* Increased ABI version due to changes in the Sieve interpreter's object
+	  definitions.
+	- multiscript: Fixed bug in handling of (implicit) keep; final keep action was
+	  always executed as though there was a failure. This caused the keep action
+	  to revert back to the initial message, causing editheader actions to be
+	  ignored.
+	- managesieve-login: Fixed proxy to allow SASL mechanisms other than PLAIN.
+	  Before, the proxy would fail if the server did not support the PLAIN
+	  mechanism.
+	- ldap storage: Prevent segfault occurring when assigning certain (global)
+	  configuration options.
+
+v0.4.11 08-01-2016 Stephan Bosch <stephan@rename-it.nl>
+
+	- Sieve mime extension: Fixed the header :mime :anychild test to work properly
+	  outside a foreverypart loop.
+	- Several fixes in message body part handling:
+	  - Fixed assert failure occurring when text extraction is attempted on a
+	    empty or broken text part.
+	  - Fixed assert failure in handling of body parts that are converted to text.
+	  - Fixed header unfolding for (mime) headers parsed from any mime part.
+	  - Fixed trimming for (mime) headers parsed from any mime part.
+	  - Fixed erroneous changes to the message part tree structure performed when
+	    re-parsing the message.
+	- LDA Sieve plugin: Fixed logging of actions; sometimes the configured log
+	  format was not followed.
+	- LDA Sieve plugin: Fixed bug in error handling of script storage
+	  initialization.
+	- Sieve Extprograms plugin: Ignored ENOTCONN error in shutdown(fd, SHUT_WR)
+	  call.
+	- Fixed duplication of discard actions in the script result. Each discard was
+	  counted as a separate action, which means that action limit would be crossed
+	  too early.
+	- Made sure that quota errors never get logged as errors in syslog.
+	- Fixed handling of implicit keep for a partially executed transaction that
+	  yielded a temporary failure.
+	- Fixed handling of global errors. If master and user error handler were
+	  identical, in some cases the log message could be lost.
+	- Fixed AIX compile issue in message body parser.
+
+v0.4.10 13-12-2015 Stephan Bosch <stephan@rename-it.nl>
+
+	+ Implemented the Sieve mime and foreverypart extensions (RFC 5703). These
+	  are fully implemented. The interaction with the editheader extension needs
+	  some work, but this should not influence most uses; i.e., changes by the
+	  editheader extension are not always visible using foreverypart/mime.
+	+ Sieve body extension: Properly implemented the `:text' body transform. It
+	  now extracts text for HTML message parts.
+	+ Sieve enotify extension: mailto method: Implemented the
+	  sieve_notify_mailto_envelope_from setting. This allows configuring the
+	  source of the notification sender address for e-mail notifications. This is
+	  similar to what already can be configured for redirect.
+	+ Added a sieve_enabled (defaults to 'yes') setting that allows explicitly
+	  disabling Sieve processing for particular users. This used to be possible by
+	  setting `sieve=', but ever since the sieve_before, sieve_after and
+	  sieve_default settings were added, this method was not reliable anymore.
+	- variables extension: Fixed handling of empty string by the `:length' set
+	  modifier. An empty string yielded an empty string rather than "0".
+	- Fixed memory leak in the Sieve script byte code dumping facility. Extension
+	  contexts were never actually freed.
+	- Fixed handling of implicit keep when the last Sieve script is a global one.
+	  In that case the implicit keep action was executed in global context, which
+	  could mean that trivial (quota) errors ended up in the system log file,
+	  rather than the user log file.
+	- doveadm sieve plugin: Fixed crashes caused by incorrect context allocation
+	  in the sieve command implementations.
+
+v0.4.9 04-10-2015 Stephan Bosch <stephan@rename-it.nl>
+
+	* Properly implemented checking of ABI version for Sieve interpreter plugins,
+	  much like Dovecot itself does for plugins. This will prevent plugin ABI
+	  mismatches. 
+	+ Implemented a vnd.dovecot.environment extension. This builds upon the
+	  standard environment extension and adds a few more environment items, such
+	  as username and default mailbox. It also creates a variables namespace so
+	  that environment items can be accessed directly. I am still thinking about
+	  more environment items that can be added.
+	+ Sieve extprograms plugin: Made line endings of the input passed to the
+	  external programs configurable. This can be configured separately for each
+	  of the three extensions.
+	+ ManageSieve: Implemented proxy XCLIENT support. This allows the proxy to
+	  pass client information to the back-end.
+	- ManageSieve: Fixed an assert failure occurring when a client disconnects
+	  during the GETSCRIPT command.
+	- doveadm sieve plugin: Fixed incorrect initialization of mail user. This
+	  caused a few memory leaks.
+	- sieve-filter command line tool: Fixed handling of failure-related implicit
+	  keep when there is an explicit default destination folder. This caused
+	  message duplication.
+	- lib-sieve: Fixed bug in RFC5322 header folding. Words longer than the
+	  optimal line length caused empty lines in the output, which would break the
+	  resulting message header. This surfaced in References: headers with very
+	  long message IDs.
+
+v0.4.8 15-05-2015 Stephan Bosch <stephan@rename-it.nl>
+
+	* LDA Sieve plugin: Dovecot changed the deliver_log_format setting to include
+	  %{delivery_time}. This prompted changes in Pigeonhole that make this release
+	  dependent on Dovecot v2.2.17.
+	+ Implemented magic to make sieve_default script visible from ManageSieve
+	  under a configurable name. This way, users can see the default rules, edit
+	  them and store a private adjusted version. This could also be achieved by
+	  copying the default script into the user's script storage, but updates to
+	  the global sieve_default script would be ignored that way.
+	+ ManageSieve: Implemented support for reporting command statistics at
+	  disconnect. Statistics include the number of bytes and scripts uploaded/
+	  downloaded/checked and the number of scripts deleted/renamed.
+	- Fixed problem in address test: erroneously decoded mime-encoded words in
+	  address headers.
+	- extprograms plugin: Fixed failure occurring when connecting to script
+	  service without the need to read back the output from the external program.
+	- Fixed bug in script storage path normalization occurring with relative
+	  symbolic links below root.
+	- Fixed and updated various parts of the documentation 
+	- ManageSieve: Used "managesieve" rather than "sieve" as login service name,
+	  which means that all managesieve-specific settings where ignored.
+	- Managesieve: Storage quota was not always enforced properly for scripts
+	  uploaded as quoted string. Nobody uses that, but it is allowed in the
+	  specification and we support it, so it should work properly.
+
+v0.4.7 19-03-2015 Stephan Bosch <stephan@rename-it.nl>
+
+	* editheader extension: Made protection against addition and deletion of
+	  headers configurable separately. Also, the `Received' and `Auto-Submitted'
+	  headers are no longer protected against addition by default.
+	* Turned message envelope address parse errors into warnings.
+	* The interpreter now accepts non-standard domain names, e.g. containing '_'.
+	+ Implemented the Sieve index extension (RFC 5260).
+	+ Implemented support for the mboxmetadata and servermetadata extensions
+	  (RFC 5490).
+	+ Implemented new sieve commands for the doveadm command line utility. These
+	  commands are currently limited to ManageSieve operations, but the other
+	  current sieve tools will be migrated to doveadm in the near future as well.
+	+ Added more debug output about binary up-to-date checking.
+	+ Added script metadata to binary dump output.
+	- Fixed Sieve script binary up-to-date checking by normalizing the script
+	  location.
+	- The Sieve interpreter now flushes the duplicate database during start phase
+	  of result execution rather than commit phase. This makes sure locks on the
+	  duplicate database are released as soon as possible, preventing contention.
+	- Performed a few optimizations in the lexical scanner of the language.
+	- Fixed bug in `:matches' match-type that made a pattern without
+	  wildcards match as if there were a '*' at the beginning.
+	- Fixed crash in validation of the string parameter of the comparator tag.
+	- extprograms extension: Made sure supplemental group privileges are also
+	  dropped. This was a problem reported by Debian lintian.
+	- Fixed bug in handling of binary errors for action side-effects and message
+	  overrides.
+	- file script storage: Restructured storage initialization to address
+	  backwards compatibility issues.
+	- dict script storage: Fixed small memory allocation bug.
+
+v0.4.6 02-11-2014 Stephan Bosch <stephan@rename-it.nl>
+
+	- After make distclean the distributed tarball would fail to recompile.
+	  This causes problems for some distribution builds.
+
+v0.4.5 30-10-2014 Stephan Bosch <stephan@rename-it.nl>
+
+	+ Added a Pigeonhole version banner to doveconf output. This way, future
+	  bug reports will also include Pigeonhole version information.
+	- Fixed handling of implicit keep. Last version erroneously reported that
+	  implicit keep succeeded after an earlier failure, while it in fact had
+	  failed. Particularly occurred for mailbox quota errors. 
+	- Fixed segfault occurring on SunOS systems when there is no active script.
+
+v0.4.4 28-10-2014 Stephan Bosch <stephan@rename-it.nl>
+
+	* Added support for Japanese mail addresses with dots at non-standard places
+	  in localpart.
+	* Changed handling of ENOSPACE into a normal temporary failure and added
+	  handling of ENOQUOTA as a user error.
+	* Restructured result execution, so that all actions which involve mail
+	  storage are always committed before all others.
+	+ Implemented support for generic Sieve storages. Using alternative storages
+	  now also possible for sieve_before/sieve_after.
+	+ Implemented storage driver for retrieving Sieve scripts from LDAP. This
+	  currently cannot be used with ManageSieve.
+	+ Implemented sieve_redirect_envelope_from setting, which allows configuring
+	  the envelope sender of redirected messages.
+	- Fixed handling of mail storage errors occurring while evaluating the input
+	  message.
+	- managesieve-login:
+	   - Removed bogus ALERT response code returned for AUTHENTICATE command.
+	   - Fixed handling of invalid initial response argument to AUTHENTICATE
+	     command.
+	- Fixed handling of stream errors in lexical scanner.
+	- Fixed handling of SMTP errors. Permanent and temporary errors were mixed up.
+	- Fixed several problems reported by CLang 3.4.
+	- duplicate extension: Fixed erroneous compile error about conflicting tags
+	  when `:handle' argument was used last.
+	- relational extension: Fixed error handling of `:value' match.
+	- editheader extension: Fixed header unfolding and header iteration.
+	- mailbox extension: Fixed the `:create' tag, which erroneously subscribed an
+	  existing folder.
+	- extprograms plugin: Fixed handling of error codes.
+	- doveadm-sieve plugin: Fixed several bugs. Synchronization of symbolic link
+	  in the file storage should now also work properly.
+
+v0.4.3 12-05-2014 Stephan Bosch <stephan@rename-it.nl>
+
+	* Editheader extension: Made control characters allowed for editheader, except
+	  NUL. Before, this would cause a runtime error.
+	+ Upgraded Dovecot-specific Sieve "vnd.dovecot.duplicate" extension to match
+	  the new draft "duplicate" extension.
+	- Fixed sieve_result_global_log_error to log only as i_info in administrator
+	  log (syslog) if executed from multiscript context.
+	- Sieve redirect extension: Adjusted loop detection to show leniency to resent
+	  messages.
+	- Sieve include extension: Fixed problem with handling of duplicate includes
+	  with different parameters :once or :optional.
+	- Sieve spamtest/virustest extensions: Tests were erroneously performed
+	  against the original message. When used together with extprograms filter to
+	  add the spam headers, the changes were not being used by the spamtest and
+	  virustest extensions. 
+	- Deprecated Sieve notify extension: Fixed segfault problems in message string
+	  substitution.
+	- ManageSieve: Fixed active link verification to handle redundant path slashes
+	  correctly.
+	- Sieve vacation extension:
+	  - Fixed interaction of sieve_vacation_dont_check_recipient with
+	    sieve_vacation_send_from_recipient setting.
+	  - Fixed log message for discarded response.
+	- Sieve extprograms plugin:
+	  - Forgot to disable the alarm() timeouts set for script execution.
+	  - Fixed fd leak and handling of output shutdown.
+	  - Fixed 'Bad filedescriptor' error occurring when disconnecting script
+	    client.
+	  - Made sure that programs are never forked with root privileges.
+
+v0.4.2 26-09-2013 Stephan Bosch <stephan@rename-it.nl>
+	
+	* Incompatible change in Sieve doveadm plugin: the root attribute for
+	  Sieve scripts is changed. Make sure that you update both sides of a dsync
+	  setup simultaneously when Sieve is involved, otherwise synchronization will
+	  likely fail.
+	+ Added support for sending Sieve vacation replies with an actual sender,
+	  rather than the default <> sender. Check the updated
+	  doc/extensions/vacation.txt for more information.
+	- Fixed a binary code read problem in the `set' command of the Sieve variables
+	  extension. Using the set command with a modifier and an empty string value
+	  would cause code corruption problems while running the script.
+	- Various fixes for doveadm-sieve plugin, mostly crashes. These include a fix
+	  for the `Invalid value for default sieve attribute' problem.
+	- Various fixes for compiler and static analyzer warnings, e.g. as reported
+	  by CLang and on 32 bit systems.
+	- Fixed the implementation of the new :options flag for the Sieve include
+	  extension.
+	- Fixed potential segfault bug at deinitialization of the lda-sieve plugin.
+	- Fixed messed up hex output for sieve-dump tool.
+
+v0.4.1 03-06-2013 Stephan Bosch <stephan@rename-it.nl>
+
+	+ Added support for handling temporary failures. These are passed back to
+	  LDA/LTMP to produce an appropriate response towards the MTA. 
+	- Sieve storage: Removed PATH_MAX limitation for active symlink. This caused
+	  problems for GNU/Hurd.
+	- Fixed line endings in X-Sieve headers added by redirect command.
+	- ManageSieve: Fixed '[' ']' stupidity for response codes (only happened
+	  before login).
+	- Fixed setting name in example-config/conf.d/20-managesieve.conf.
+	- Sieve extprograms plugin: Fixed interaction between pipe command and remote
+	  script service. The output from the script service was never read, causing a
+	  broken pipe error at the script service. Apparently, this was broken since
+	  the I/O handling for extprograms was last revised. 
+	- Fixed assertion failure due to datastack problem in message header
+	  composition.
+
+v0.4.0 09-05-2013 Stephan Bosch <stephan@rename-it.nl>
+
+	+ Added doveadm-sieve plugin that provides the possibility to synch Sieve
+	  scripts using doveadm sync along with the user's mailboxes.
+	+ Added the Sieve extprograms plugin to the main Pigeonhole package. It is
+	  still a plugin, but it is now included so that a separate compile is no
+	  longer necessary and distributors are likely to include it. The extprograms
+	  plugin provides Sieve language extensions that allows executing 
+	  (administrator-controlled) external programs for message delivery,
+	  message filtering and string manipulation. Refer to
+	  doc/plugins/sieve_extprograms.txt for more information.
+	+ Added debug message showing Pigeonhole version at initialization. Makes it
+	  very clear that the plugin is properly loaded.
+	+ Finished implementation of the Sieve include extension. It should now
+	  fully conform to RFC 6609. The main addition is the new :optional tag which
+	  makes the include command ignore missing included scripts without an error.
+	+ Finished implementation of the Sieve environment extension as much as
+	  possible. Environment items "location", "phase" and "domain" now also
+	  return a usable value.
+
+v0.3.6 26-09-2013 Stephan Bosch <stephan@rename-it.nl>
+
+	- Fixed a binary code read problem in the `set' command of the Sieve variables
+	  extension. Using the set command with a modifier and an empty string value
+	  would cause code corruption problems while running the script.
+	- Various fixes for compiler and static analyzer warnings, as reported
+	  by CLang.
+	- ManageSieve: Fixed '[' ']' stupidity for response codes (only happened
+	  before login).
+	- Fixed setting name in example-config/conf.d/20-managesieve.conf.
+	- Fixed messed up hex output for sieve-dump tool.
+
+v0.3.5 09-05-2013 Stephan Bosch <stephan@rename-it.nl>
+
+	- Sieve editheader extension: fixed interaction with the Sieve body extension.
+	  If used together, the deleteheader action could fail after a body test was
+	  performed.
+	- Test suite: fixed a time zone dependency in the Sieve date extension tests.
+
+v0.3.4 06-04-2013 Stephan Bosch <stephan@rename-it.nl>
+
+	* Changed error handling to be less of a nuisance for administrators. Strictly
+	  user-caused errors are only reported in user log. Some errors are logged as
+	  info instead.
+	* Sieve: Changed behavior of redirect in case of a duplicate message delivery
+	  or a mail loop. If a duplicate is detected the implicit keep is canceled,
+	  as though the redirect was successful. This prevents getting local
+	  deliveries. The original SMTP recipient is used when it is available to
+	  augment the entry in the LDA duplicate database. This way, duplicates are
+	  only detected when (initially) addressed to the same recipient.
+	+ Sieve vnd.dovecot.duplicate extension: added new features to the duplicate
+	  test, making it possible to manually compose the key value for duplicate
+	  checking. This extension is in the process of being standardized
+	  (https://tools.ietf.org/html/draft-bosch-sieve-duplicate-01).
+	+ Sieve date extension: generate warning when invalid date part is specified.
+	- Sieve editheader extension: fixed crash occurring when addheader :last was
+	  used.
+	- Sieve include extension: fixed missing error cleanup that caused a resource
+	  leak.
+	- Sieve vacation extension: fixed determination of From: address for when
+	  sieve_vacation_dont_check_recipient is active.
+	- Sieve tools: the -D option wasn't enabled and documented for all tools.
+	- Siev dict script storage: fixed potential segfault occurring when dict
+	  initialization fails.
+	- ManageSieve: fixed bug in skipping of CRLF at end of AUTHENTICATE command.
+	- ManageSieve: fixed handling of unkown commands pre-login.
+	- Fixed compile on Mageia Linux.
+
+v0.3.3 18-09-2012 Stephan Bosch <stephan@rename-it.nl>
+
+	- Fixed compile against installed Dovecot headers. This was broken by the
+	  ld.gold fix in the previous release.
+
+v0.3.2 18-09-2012 Stephan Bosch <stephan@rename-it.nl>
+
+	+ sieve-refilter tool: improved man page documentation by explicitly
+	  specifying the syntax used for mailbox arguments.
+	+ Sieve: spamtest and virustest extensions: improved trace debugging of score
+	  calculation.
+	+ Sieve: made error messages about exceeding the maximum number of actions
+	  more verbose.
+	- Sieve tools: fixed problems with running as root: sievec and sieve-dump now
+	  ignore mail_uid and mail_gid settings when run as root.
+	- Sieve: fixed bug in action accounting (for limit checking): increase action
+	  instance count only when an action is actually created.
+	- Sieve: include extension: fixed namespace separation of :global and
+	  :personal scripts.
+	- ManageSieve: fixed segfault bug triggered by CHECKSCRIPT command.
+	- Fixed linking with ld.gold.
+	- Fixed several Clang compile warnings and a few potential bugs.
+
+v0.3.1 25-05-2012 Stephan Bosch <stephan@rename-it.nl>
+
+	* Added support for retrieving Sieve scripts from dict lookup. This means that
+	  Sieve scripts can now be downloaded from a database. Compiled script
+	  binaries are still put on disk somewhere if used. The INSTALL documentation
+	  is updated with information on this new feature and the
+	  (backwards-compatible) changes to the configuration. Note that his feature
+	  is currently not supported for sieve_before/sieve_after or script management
+	  through ManageSieve.
+	+ Incorporated the sieve_duplicate plugin into main Pigeonhole tree as a
+	  normal extension (vnd.dovecot.duplicate). This Dovecot-specific extension
+	  adds the ability to check for duplicate deliveries based on message ID.
+	  Specification can be found in: doc/rfc/spec-bosch-sieve-duplicate.txt
+	+ Added support for specifying multiple sieve_before and sieve_after paths.
+	  This adds much more flexibility to the multiscript configuration. One
+	  application is to have user-specific Sieve scripts outside the user's
+	  normal control through ManageSieve.
+	+ Added a "session ID" string for managesieve connections, available in
+	  %{session} variable (analogous to Dovecot change).
+	- Fixed several small issues, including a few potential segfault bugs, based
+	  on static source code analysis.
+	- ManageSieve: changed use of EPROTO error to EIO in ManageSieve string stream
+	  implementation because it is apparently not known in BSD.
+	- Gave stamp.h.in (needed for autotools) some content to prevent it from
+	  disappearing in patch files.
+	- Fixed bug that caused a SunStudio CC compile failure (reported by Piotr
+	  Tarnowski).
+
+v0.3.0 16-02-2012 Stephan Bosch <stephan@rename-it.nl>
+
+	* Renamed sieve_global_path setting to sieve_default for clarity. Old name is
+	  still recognized for backwards compatibility. Support for the ancient (pre
+	  v1.1) name for this setting "global_script_path" is now dropped.
+	* Added means to prohibit use of redirect action. Setting sieve_max_redirects
+	  to 0 now means that redirect is disallowed instead of unlimited. Default
+	  value remains four.
+	* Fixed interaction of Sieve include extension with ManageSieve. It is updated
+	  to match new requirements in the draft include specification. Missing
+	  included scripts are no longer an error at upload time.
+	* Updated RFC2822 header field body verification to exclude non-printing
+	  characters (RFC5322). Only Sieve actions that can create unstructured header
+	  values (currently enotify/mailto and editheader) are affected by this
+	  change.
+	+ Completed sieve-filter tool to a useful state. The sieve-filter tool
+	  provides a means to (re)filter messages in a mailbox through a Sieve script.
+	+ Implemented the Sieve editheader extension. It is now possible to add and
+	  remove message headers from within Sieve.
+	+ ManageSieve: added support for reading quoted and literal strings as a
+	  stream. Fixes support for handing large SASL responses (analogous to similar
+	  changes in Dovecot). It is now also allowed to use a quoted string for the
+	  PUTSCRIPT script argument.
+	+ Added code to cleanup tmp directory in Sieve storage directory (sieve_dir)
+	  every once in a while.
+	+ Added support for substituting the entire message during Sieve processing.
+	  This is used for the filter action provided by the new sieve_extprograms
+	  plugin (provided separately for now). The filter action allows passing the
+	  message through an external program.
+	+ Added support for restricting certain Sieve language extensions to
+	  (admin-controled) global scripts. Restricted extensions can be configured
+	  using the new sieve_global_extensions setting. This is particularly useful
+	  for some of the Dovecot-specific (plugin-based) Sieve extensions, that can
+	  be somewhat hazardous when under direct control of users (e.g.
+	  sieve_extprograms).
+
+v0.2.6 13-02-2012 Stephan Bosch <stephan@rename-it.nl>
+
+	* This release fixes unintentional behavior of the include extension. Included
+	  scriptnames with a name like "name.sieve" would implicitly map to a script
+	  file called "name.sieve" and not "name.sieve.sieve". Keep in mind that the
+	  .sieve file extension has no meaning from within the Sieve language. A Sieve
+	  script is always stored with an appended .sieve file extension, also when
+	  the name already ends with a .sieve suffix.
+	  IMPORTANT: Some installations have relied on this unintentional feature, so
+	  check your script includes for issues before upgrading.
+	* Matched changes regarding auth_verbose setting in Dovecot. This means that
+	  this release will only compile against Dovecot v2.0.18.
+	- Fixed problem in ManageSieve that caused it to omit a WARNINGS response code
+	  when the uploaded script compiled with warnings.
+	- Made sure that locations of Sieve error never report `line 0'.
+	- Fixed potential segfault occurring when interpreter initialization fails.
+
+v0.2.5 19-11-2011 Stephan Bosch <stephan@rename-it.nl>
+
+	+ Sieve vacation extension: made discard message for implicit delivery more
+	  verbose
+	- The sieve-test tool: mixed up original and final envelope recipient in
+	  implementation of command line arguments.
+	- Sieve vacation extension: resolved FIXME regarding the use of variables in
+	  the :handle argument. Variables are now handled correctly.
+	- Sieve body extension: fixed handling of :content "message/rfc822". This now
+	  yields the headers of the embedded message as required by the specification.
+		Handling of :content "multipart" remains to be fixed.
+	- LDA Sieve plugin: fixed problem with recipient_delimiter configuration. Now
+	  falls back to global recipient_delimiter setting if
+	  plugin/recipient_delimiter is not set.
+
+v0.2.4 13-09-2011 Stephan Bosch <stephan@rename-it.nl>
+
+	+ Vacation extension: finally added support for using the original recipient
+	  in vacation address check. It is also possible to disable the recipient
+	  address check entirely. Check doc/vacation.txt for configuration
+	  information.
+	+ Include extension: made limits on the include depth and the total number of
+	  included scripts configurable. Check doc/include.txt for configuration
+	  information.
+	+ Implemented ihave extension. This allows checking for the availability
+	  of Sieve language extensions at 'runtime'. Actually, this is checked
+	  at compile time. At runtime the interpreter checks whether extensions
+	  that were not previously available are still unavailable. If the situation
+	  changed, the script is re-compiled and the ihave tests are evaluated again.
+	+ Sieve: optimized compilation of tests that yield constant results (i.e.
+	  known at compile tme), such as 'true' and 'false'. No code is produced
+	  anymore for script sections that are never executed. Also, semantics
+	  are not verified anymore in uncompiled script sections.
+	+ Made vnd.dovecot.debug extension available to the LDA plugin instead of
+	  only the command line tools.
+	+ Sieve: redirect action now adds X-Sieve-Redirected-From header (mainly for
+	  people using SPF/SRS).
+	- Sieve: fixed bug in handling flags and keywords; in case of error an
+	  assertion was triggered.
+	- Script storage: improved handling of unconfigured user home directory.
+	  Originally this would produce an unhelpful error message.
+	- Imap4flags extension: prevent forcibly enabling imap4flags when imapflags
+	  is enabled.
+	- Fixed various -Wunused-but-set-variable compiler warnings.
+	- Include extension: forgot to check variable identifier syntax for 'global'
+	  command.
+	- Sieve: fixed debug mode; no messages were logged in some situations.
+	- sievec tool: forgot to enable -D (debug) parameter.
+
+v0.2.3 14-04-2011 Stephan Bosch <stephan@rename-it.nl>
+
+	* Sieve filter tool: finished implementing basic functionality. It is not
+	  quite ready yet, but it is available for those willing to experiment
+	  with it (needs --with-unfinished-features config to compile). Also
+	  includes man page.
+	+ Vacation extension now inhibits replies to messages from sender listed
+	  in :addresses, thus preventing replies to one of the user's other known
+	  addresses.
+	+ Vacation extension: implemented the (draft) vacation-seconds extension.
+	  This also adds min/max period configuration settings. Refer to
+	  doc/vacation.txt for configuration information.
+	- ManageSieve: fixed bug in UTF-8 checking of string values. This is done
+	  by discarding the original implementation and migrating to the Dovecot
+	  API's UTF-8 functionality.
+	- Sieve command line tools now avoid initializing the mail store unless
+	  necessary. This prevents sievec and sieve-dump from failing when
+	  executed by root for example.
+	- Enotify extension: fixed inappropriate return type in mailto URI parse
+	  function, also fixing ARM compiler warning.
+	- Vacation extension: fixed handling of sendmail errors. It produced an
+	  additional confusing success message in case of error.
+	- Removed header MIME-decoding to fix erroneous address parsing. Applies to
+	  address test and vacation command.
+	- Fixed segfault bug in extension configuration, triggered when unknown
+	  extension is mentioned in sieve_extensions setting.
+
+v0.2.2 06-12-2010 Stephan Bosch <stephan@rename-it.nl>
+
+	* LDA Sieve plugin: started using Dovecot LDA reject API for the reject
+	  extension. This means that the LDA reject_reason and reject_subject
+	  settings now also work for Pigeonhole's LDA Sieve plugin.
+	* Did some work on the new sieve-filter tool. It is mostly functional, but
+	  it is not finished yet.
+	* Dovecot change: services' default vsz_limits weren't being enforced
+	  correctly in earlier v2.0 releases. Now that they are enforced, you might
+	  notice that the default limits are too low and you need to increase them.
+	  This problem will show up in logs as "out of memory" errors. See
+	  default_vsz_limit and service { vsz_limit } settings.
+	- Imap4flags: fixed segfault bug occurring in multiscript context.
+	- Added version checking to the ManageSieve settings plugin. This plugin was
+	  forgotten when the LDA plugin was updated with this change in the previous
+	  release.
+	- LDA Sieve plugin: fixed memory leak at deinitialization.
+
+v0.2.1 27-09-2010 Stephan Bosch <stephan@rename-it.nl>
+
+	+ Incorporated distinction between original and final envelope recipient in
+	  Sieve interpreter, as recently introduced in Dovecot.
+	+ Regex extension: added support for regex keys composed from variables.
+	- LDA Sieve plugin: added _version symbol to enable Dovecot's plugin version
+	  check. Without this check, people can forget to recompile the plugin, which
+	  can lead to unexpected effects.
+	- LDA Sieve plugin: turned debug message about an unconfigured home directory
+	  into a proper error and added script path information.
+	- Fixed unnecessary reporting of dummy extensions in ManageSieve SIEVE
+	  capability; the comparator-i;octet and comparator-i;ascii-numeric
+	  'extensions' were reported explicitly.
+
+v0.2.0 10-09-2010 Stephan Bosch <stephan@rename-it.nl>
+
+	* Merged Sieve and ManageSieve packages into a single Pigeonhole package.
+	  There is also no need to patch Dovecot anymore to gain ManageSieve support.
+	  Version numbering of previous Sieve releases is continued as v0.2.0. The
+	  sources originally branched off from Sieve v0.1.5 and ManageSieve v0.11.4,
+	  but the NEWS history of much more recent releases for Dovecot v1.2 is
+	  included since these changes are all included in this release as well.
+	* The ManageSieve service now binds to TCP port 4190 by default due to the
+	  IANA port assignment for the ManageSieve service. When upgrading from v1.2,
+	  this should be taken into account. The service can be configured manually to
+	  listen on both 2000 and 4190.
+	* The Dovecot configuration now calls the ManageSieve protocol 'sieve' in
+	  stead of 'managesieve' because it is registered as such with IANA. The
+	  binaries and the services are still called managesieve and
+	  managesieve-login.
+	* The binary representation of a compiled Sieve script is updated to include
+	  source code locations of all commands and arguments. This is implemented in
+	  a similar manner as such debug information is included in some system
+	  executables and libraries (DWARF-like). Run-time errors can now always refer
+	  to the proper line number in the Sieve source script.
+	* The Sieve plugin is adapted to work properly with the new LMTP service
+	  introduced with Dovecot v2.0. The same plugin is used for both LDA and LMTP.
+	* The 'sieve_subaddress_sep' setting for the Sieve subaddress extension is now
+	  known as 'recipient_delimiter'. Although the deprecated sieve_subaddress_sep
+	  setting is still recognized for backwards compatibility, it is recommended
+	  to update the setting to the new name, since the new LMTP service also uses
+	  the recipient_delimiter setting.
+	* ManageSieve: changed default IMPLEMENTATION capability to from 'Dovecot' to
+	  'Dovecot Pigeonhole'.
+	* Renamed the sieved tool to sieve-dump. The original name was somewhat
+	  confusing.
+	* Updated man pages to match style and structure of new Dovecot man pages.
+	* Made testsuite commands more uniform and cleaned up many of the testsuite
+	  scripts. Some minor new tests were added in the process.
+	+ Simplified string matching API to use abstract string lists as data sources.
+	  This will also make implementing the index extension easier in the future.
+	+ Significantly improved trace debugging with the sieve-test tool. The full
+	  execution of the script can be examined, including the matched values and
+	  keys of the respective Sieve test commands. The executed statements are
+	  listed with their line number (and code address when requested). The level
+	  of detail is configurable from the command line.
+	+ The SIEVE and NOTIFY capabilities reported by the ManageSieve protocol can
+	  now be configured manually. If left unconfigured, the capabilities are
+	  determined from the default Sieve and ManageSieve configuration.
+	  User-specific capabilities aren't reported until after authentication.
+	+ Significantly improved file error handling. This means that administrators
+	  get a more useful and informative log message when file operations fail. The
+	  most notable example is that when the LDA Sieve plugin is trying to store a
+	  binary for a global script, the resulting failure message also points the
+	  administrator towards pre-compiling the script with sievec.
+	+ Added runtime argument value checking for several commands (redirect, date
+	  vacation). When variables are used, these checks cannot be performed at
+	  compiletime. A proper runtime error now is produced when invalid data is
+	  encountered.
+	+ UTF8 validity of fileinto command argument is now checked either at compile
+	  time or at runtime. Previously, it was not checked until the store action
+	  was executed.
+	+ Validity of IMAP flags for the imap4flags extension is now checked also
+	  at runtime. Previously, it was not checked until the store action was
+	  executed.
+	+ Simplified and restructured error handling. Also made sure that user-caused
+	  errors are no longer written to the Dovecot master/LDA log.
+	- Multiscript: fixed duplicate implicit keep caused by erroneous execution
+	  state update.
+	- Prevented assertion failure due to currupt binary string representation.
+	  If the string was missing a final \0 character an assertion was produced in
+	  stead of a binary corruption error.
+	- Imap4flags: fixed bug in setflag command; when parameter was a stringlist,
+	  only the last item was actually set.
+	- Variables extension: fixed :length set modifier to recognize utf8 characters
+	  instead of octets.
+	- Testsuite: prevented innocent warning messages, i.e. those that are part of
+	  the test, from showing up by default.
+	- ManageSieve/Sieve storage: fixed error handling of PUTSCRIPT commmand; save
+	  commit errors would not make the command fail.
+	- ManageSieve: enforced protocol syntax better with some of the commands; some
+	  commands allowed spurious extra arguments.
+	- Fixed Sieve script name checking to properly handle length limit and added
+	  0x00ff as invalid character.
+	- Removed spurious old stdio.h (top) includes; these caused compile issues on
+	  specific systems.
+	- Fixed default Sieve capability (as reported by ManageSieve): extra
+	  extensions spamtest, spamtestplus and virustest were enabled by default.
+	  These should, however, only be enabled when properly configured and there
+	  is no default configuration.
+
+(Fused Dovecot Sieve and ManageSieve packages into a single Pigeonhole release)
+
+Dovecot Sieve NEWS history:
+---------------------------
+
+Dovecot 1.2:
+
+v0.1.17 19-06-2010 Stephan Bosch <stephan@rename-it.nl>
+
+	- Made sure source code positions for compiler messages are recorded at start
+	  of tokens.
+	- Fixed a few potential memory leaks in the Sieve compiler and the
+	  spam/virustest extensions.
+	- Made command line tools return proper exit status upon failure.
+
+v0.1.16 30-04-2010 Stephan Bosch <stephan@rename-it.nl>
+
+	* Finished implementation of spamtest, spamtestplus and virustest extensions.
+	  These are not enabled by default and need to be activated with the
+	  sieve_extensions setting. Documentation available in
+	  doc/spamtest-virustest.txt
+	+ Vacation extension: the from address of the generated reply is now by
+	  default equal to whatever known recipient alias matched the headers of the
+	  message. If it is one of the aliases specified with :addresses, it is used
+	  instead of the envelope recipient address that was used before.
+	+ Restructured and optimized the lexical scanner.
+	+ Added --with-docs configure option to allow disabling installation of
+	  documentation.
+	- Accidentally omitted 'extern' in two declarations of global variables in
+	  header files, causing compile failures on certain systems.
+	- Deprecated imapflags extension: fixed implicit assignment of flags. Turns
+	  out this never really worked, but the effect of this bug was obscured by the
+	  removeflag bug fixed in the previous release.
+	- Fixed various memset argument mixups in enotify extension. This caused
+	  warnings on certain systems, but luckily no adverse effects at runtime.
+
+v0.1.15 25-01-2010 Stephan Bosch <stephan@rename-it.nl>
+
+	* Enotify extension:
+	  - Adjusted notify method API for addition of new notification methods.
+	  - Set default importance level to 'normal' (was 'high').
+	* Include extension: updated implementation towards most recent specification
+	  (all should be backwards compatible):
+	  - Implemented global variables namespace.
+	  - Global command may now appear anywhere in a script.
+	  - Implemented script name checking using the requirements specified in the
+	    ManageSieve draft.
+	  - One issue remains: ManageSieve currently requires included scripts to be
+	    uploaded first, which is not according to specification.
+	* Changed envelope path parser to allow to and from envelope addresses that
+	  have no domain part.
+	+ Added preliminary support for Sieve plugins and added support for installing
+	  Sieve development headers.
+	+ Started work on the implementation of the spamtest, spamtestplus and
+	  virustest extensions (unfinished).
+	+ Deprecated notify extension: implemented denotify command.
+	+ Variables extension: added support for variable namespaces.
+	+ Added configurable script size limit. Compiler will refuse to compile files
+	  larger than sieve_max_script_size.
+	+ Testsuite changes:
+	  - Added support for changing and testing an extension's configuration.
+	  - Added a command line parameter for copying errors to stderr.
+	- Fixed a bug in the i;ascii-numeric comparator. If one of the strings started
+	  with a non-digit character, the comparator would always yield less-than.
+	- Imap4flags extension: fixed bug in removeflag: removing a single flag failed
+	  due to off-by-one error (bug report by Julian Cowley).
+	- Improved EACCES error messages for stat() and lstat() syscalls and slightly
+	  improved error messages that may uccur when saving a binary.
+	- Vacation extension: fixed typo in runtime log message (patch by Julian
+	  Cowley).
+	- Fixed use of minus '-' in man pages; it is now properly escaped.
+	- Fixed parser recovery. In particular cases it would trigger spurious errors
+	  after an initial valid error and sometimes additional errors were
+	  inappropriately ignored.
+
+v0.1.14 19-12-2009 Stephan Bosch <stephan@rename-it.nl>
+
+	* Made the imposed limits on the number of redirects and the number of
+	  actions configurable. The settings are called sieve_max_actions and
+	  sieve_max_redirects.
+	* Did a major rework of extension handling, making sure that no global state
+	  is maintained. This change was triggered by problems that global state info
+	  would cause for Dovecot v2.0, but it is also important for v1.2 as it
+	  significantly cleans up the library implementation.
+	+ Made LDA Sieve plugin recognize the deliver_log_format setting.
+	+ Message headers produced from user-supplied data are now RFC2047-encoded if
+	  necessary for outgoing messages. This is for example important for the
+	  :subject argument of the vacation action.
+	+ Added support for the $text$ substitution in the deprecated notify
+	  extension.
+	+ The subaddress extension now also accepts recipient_delimiter setting as an
+	  alias for sieve_subaddress_sep setting. This anticipates the
+	  recipient_delimiter setting in v2.0.
+	- Fixed logging of mailbox names. It logged the converted mUTF7 version in
+	  stead of the original UTF8 version supplied by the user.
+	- Fixed a minor memory leak in the multiscript support.
+	- Fixed a bug in the recompilation of Sieve scripts. Made sure that scripts
+	  are only recompiled when the script file - or the symlink pointing to it -
+	  is strictly newer.
+
+v0.1.13 18-10-2009 Stephan Bosch <stephan@rename-it.nl>
+
+	+ Body extension: implemented proper handling of the :raw transform and added
+	  various new tests to the test suite. However, :content "multipart" and
+	  :content "message/rfc822" are still not working.
+	+ Fixed race condition occurring when multiple instances are saving the same
+	  binary (patch by Timo Sirainen).
+	+ Test suite: added support for testing multiscript execution.
+	- Made compiler more lenient towars missing CRLF at the end of the script in a
+	  hash comment.
+	- Body extension: don't give SKIP_BODY_BLOCK flag to message parser, we want
+	  the body! (patch by Timo Sirainen).
+	- Fixed handling of implicit side effects for multiscript execution.
+	- Fixed bugs in multiscript support; subsequent keep actions were not always
+	  merged correctly and implicit side effects were not always handled
+	  correctly.
+	- Fixed a segfault bug in the sieve-test tool occurring when compile fails.
+	- Fixed segfault bug in action procesing. It was triggered while merging side
+	  effects in duplicate actions.
+	- Fixed bug in the Sieve plugin that caused it to try to stat() a NULL path,
+	  yielding a 'Bad address' error.
+
+v0.1.12 21-08-2009 Stephan Bosch <stephan@rename-it.nl>
+
+	+ Testsuite: added support for testing binaries stored on disk.
+	+ Implemented the new date extension. This allows matching against date values
+	  in header fields and the current date at the time of script evaluation.
+
+v0.1.11 08-08-2009 Stephan Bosch <stephan@rename-it.nl>
+
+	+ Built skeleton implementation for the date extension (RFC 5260). It
+	  compiles, but it does not do anything useful yet. Therefore, it is not part
+	  of the default compilation.
+	- Fixed ARM portability issues caused by char type not being signed on that
+	  platform. Reading optional operands from a binary would fail for action side
+	  effects. Also, an accidental mixup of an int return type with bool caused
+	  the interpreter to continue on ARM even though an error occured.
+	- Removed direct stdint.h includes to prevent portability issues.
+	- Fixed segfault bug in the handling of script open failures.
+	- Include: improved user error messages and system log messages.
+	- Fixed copy-paste mixup between sieve_after and sieve_before settings in the
+	  LDA Sieve plugin. If only a sieve_after script was active, nothing would
+	  have been executed. Patch by Mike Abbott.
+	- Include: fixed a bug in HOME substitution in the sieve_dir path. Surfaced in
+	  ManageSieve.
+
+v0.1.10 03-08-2009 Stephan Bosch <stephan@rename-it.nl>
+
+	* Changed action execution of fileinto and keep. These changes depend on API
+	  additions in Dovecot, making this release depend on Dovecot v1.2.2 or newer.
+	* Further developed the sieve-filter command line tool. This required a few
+	  changes to the action execution of the Sieve engine. The tool was
+	  successfully tested on folders with a few 100k spam messages. However, the
+	  commandline options are still incomplete, a man page is missing and it needs
+	  much more testing before I can recommend anyone to use this tool.
+	+ Added support for the mailbox extension. This allows checking whether a
+	  mailbox exists using the mailboxexists command and it adds the :create
+	  argument to the fileinto command to create the mailbox when it is missing.
+	  The :create feature is useless unless the Deliver LDA is run with the -n
+	  option.
+	+ Improved the testsuite with tests for message delivery. Messages stored
+	  using keep and fileinto can be fed back into the Sieve engine for
+	  verification. This includes testing of applied IMAP flags.
+	+ Updated the man pages with the new method of specifying the supported
+	  extensions using + and - (for the -x parameter of the sieve tools)
+	+ Further developed the deprecated notify extension. A dummy for the denotify
+	  command exists, meaning that its use does not cause an error anymore.
+	- Fixed a bug in the derivation of the binary path from the script path. A
+	  bare filename would yield a path relative to root.
+	- Fixed a bug in the value matching code. The context data now uses a proper
+	  pool instead of the data stack. Bug reported by Jan Sechser.
+	- Fixed assertion fail in the include extension caused by missing
+	  initialization upon binary load. This bug surfaces only for stored
+	  binaries. Bug reported by Tom Hendrikx.
+	- Fixed include error message for failed :global include. It mentioned the
+	  wrong config parameter.
+	- Fixed broken wiki reference in an error message of the plugin about the
+	  'sieve' setting.
+	- Fixed behavior of fileinto when delivering into a namespace prefix.
+	  Previous fix used the wrong storage.
+
+v0.1.9 22-07-2009  Stephan Bosch <stephan@rename-it.nl>
+
+	* Removed the unfinished sieve-filter tool from the default build. It is now
+	  only built when the --with-unfinished-features switch is supplied during
+	  configure.
+	+ Started building support for the ereject version of the reject action,
+	  which has a preference to use an SMTP/LMTP protocol error instead of a
+	  bounce message. This is to be used to make the Sieve plugin honour Deliver's
+	  -e parameter. This is not yet finished and not built by default.
+	+ Improved 'Permission denied' error messages just like Dovecot does,
+	  precisely specifying what permission is missing to access or create a file.
+	+ Added additional headers to the list of allowed headers for the address
+	  test. The restrictive nature of the address test is not always appropriate.
+	  Still thinking of a better, less restrictive implementation.
+	+ Made the deprecated notify extension compatible with the old CMUSieve
+	  plugin. However, the denotify command and the $text$ substitution are not
+	  yet supported.
+	+ Made the discard action log a message to avoid confusion about disappearing
+	  messages.
+	- Fixed behavior of fileinto when delivering into a namespace prefix. It now
+	  uses silent delivery into INBOX as fallback.
+	- Fixed logging of folder namespace prefix upon delivery into a prefixed
+	  namespace. Formerly it only logged the bare folder name.
+	- Fixed a potential segfault in the argument validation. It didn't surface
+	  because no command could have a :tag followed by an associated parameter as
+	  last argument.
+	- Fixed segfault bug occurring in envelope test when performed on null (<>)
+	  envelope path. The fix involves a rather large restructuring of the code to
+	  make sure envelope addresses are properly handled everywhere (bug reported
+	  by Nikita Koshikov)
+	- Envelope: fixed bug in application of address parts; failure to obtain
+	  the part would cause inappropriate match success (bug reported by Ron Lee)
+	- Fixed extension conflict checks during validation. It could sometimes
+	  produce useless errormessages. This is currently only used by the
+	  deprecated extensions.
+	- Forgot to remove old explicit storage library dependency (patch by
+	  Arkadiusz Miskiewicz).
+	- Fixed compiler warnings on certain platforms regarding the use fwrite for
+	  outgoing message construction
+
+v0.1.8 12-07-2009  Stephan Bosch <stephan@rename-it.nl>
+
+	- Fixed AIX compile problem. For portability, the typeof operator is
+	  not used anymore.
+	+ Added partial support for the deprecated notify extension. However, it
+	  turns out that the implementation provided by cmusieve is even older (2001),
+	  meaning that this is currently not backwards compatible with cmusieve.
+
+v0.1.7 05-07-2009  Stephan Bosch <stephan@rename-it.nl>
+
+	+ Added support for CRLF line breaks in strbuf error handler to fix a
+	  ManageSieve problem.
+	+ Improved consistency of sieve tool documentation and fixed missing
+	  parameters in internal tool help output.
+	+ Enhanced extensions configuration, allowing to specify the enabled
+	  extensions relatively to the default (patch by Steffen Kaiser).
+	- Forgot to initialize script execution status in Sieve plugin, causing
+	  segfaults on compile errors in specific conditions.
+	- Fixed logging in Sieve plugin for execution of default main script (went
+	  to STDERR).
+
+v0.1.6 18-06-2009  Stephan Bosch <stephan@rename-it.nl>
+
+	* Adjusted to changes in Dovecot to make it compile against v1.2.rc5
+	* Made default of sieve_dir setting match the ManageSieve implementation.
+	- Fixed a few problems in de body extension that caused assert failures in
+	  specific situations.
+
+v0.1.5 18-04-2009  Stephan Bosch <stephan@rename-it.nl>
+
+	* Ported the implementation of the Sieve include extension to the latest
+	  draft. This means that the import and export commands are replaced by a new
+	  command called global. The import and export commands are now DEPRICATED and
+	  are mere aliases for the global command. The new specification also adds the
+	  :once modifier to the include command. The also newly specified global.*
+	  variable namespace is not implemented yet as support for variable namespaces
+	  is currently missing.
+	* Did a major rework of the multiscript support for better error handling and
+	  made sure that persistent global scripts (sieve_before/sieve_after) are
+	  always executed, even when the user does not have a script of his own and
+	  a global default is missing.
+	+ Provided basic support for the environment extension. Currenly, the name,
+	  version and host items are useful. Others are pending.
+	+ Improved error message that is presented when an unknown Sieve extension is
+	  provided as argument to the require command. It now notifies the user that
+	  Sieve core commands do not need to be specified in require.
+	- Fixed bug in includes at levels deeper than one.
+	- Fixed bug in address matching that was caused by the failure to handle group
+	  specifications. In dovecot, these are marked by address items with NULL
+	  elements, which causes a segfault if not considered. The group 'undisclosed-
+	  recipients:;' in particular triggered this bug. Bug reported by Bernhard
+	  Schmidt.
+
+v0.1.4 21-03-2009  Stephan Bosch <stephan@rename-it.nl>
+
+	* Started work on the sieve-filter tool. With this command line tool it will
+	  be possible to (re-)apply Sieve filters on a mail folder. It is currently
+	  undocumented and far from functional.
+	+ Added a custom debug extension that provides the possibility to print debug
+	  messages from scripts executed by the Sieve tools.
+	- Fixed issue with opening relative paths as a mail file. Bug reported by Ian
+	  P. Christian.
+	- Fixed MAC OSX compile problem. Turns out the extern modifier was missing at
+	  multiple places. Bug reported by Edgar Fuss.
+	- Fixed Solaris compile problem: removed unecessary and unportable linker
+	  flags that caused compile to fail. Bug reported by Andrés Yacopino.
+
+v0.1.3 12-02-2009  Stephan Bosch <stephan@rename-it.nl>
+
+	* Adapted to changes in Dovecot, making this release dependent on Dovecot
+	  >= 1.2.beta1
+	* Made mail address comparison fully case-insensitive. This is particularly
+	  noticeable for the :addresses argument of the vacation command.
+	+ Finished enotify extension. Currently, only the mailto notification method
+	  is implemented. All still needs to be tested thoroughly.
+	+ Implemented multiscript support. It is now possible to execute multiple
+	  Sieve scripts sequentially. Administrator-controlled scripts can be
+	  executed before and after the user's script. Still needs to be tested
+	  thoroughly.
+	+ Implemented support for configuring the available Sieve extensions.
+	+ Made the subaddress extension (partially) configurable using the
+	  sieve_subaddress_sep setting, which allows specifying a (multi-charater)
+	  separator other than '+'.
+	+ Compiler now warns about invalid header field names used for the header and
+	  address tests.
+	+ Vacation extension now properly generates a References header for the
+	  response message.
+	+ Added testing of basic result execution to the test suite. Also added
+	  supportfor testing the outgoing messages produced by the Sieve interpreter.
+	+ Included execution of the actual result in the sieve-test command line tool.
+	  The undocumented sieve-exec tool that existed for this is now removed as
+	  planned.
+	+ Added support for the now obsolete 'imapflags' extension for backwards
+	  compatibility with CMUSieve. This also implements the mark/unmark commands.
+	- Fixed bugs in the regex extension: 1) if an optional match value did not in
+	  fact match, subsequent match values would get unexpected indexes. 2) fixed
+	  segfault bug occurring when regex is freed.
+	- Fixed bug in the use of the :from agrument for the vacation command. If this
+	  address included a phrase part, the response would not be a valid RFC822
+	  message.
+	- Plugged a theoretical security hole occurring when a directory is opened as a
+	  Sieve binary.
+	- Cleaned up and fixed various log messages.
+	- Fixed bug in the outgoing address verification. Addresses ending in ',' were
+	  erroneously accepted.
+
+v0.1.2 26-11-2008  Stephan Bosch <stephan@rename-it.nl>
+
+	- Fixed important bug in the redirect action (and probably other actions like
+	  reject and vacation that only send messages). This was a bug in the handling
+	  of context information during the execution of actions. It caused the sieve
+	  interpreter to crash with a segfault when redirect was executed.
+
+v0.1.1 24-11-2008  Stephan Bosch <stephan@rename-it.nl>
+
+	* Re-enabled support for compiling against dovecot headers. Much like
+	  cmusieve, command line tools like sievec and sieved are not compiled in this
+	  case.
+	* Started implementation of enotify extension. Not anywhere near finished
+	  though.
+	* Adapted to changes in Dovecot on various occasions, making this release
+	  dependent on Dovecot >= v1.2.alpa4.
+	+ Improved logging of errors at specific occasions and added debug messages to
+	  find script execution problems quicker.
+	+ Removed code duplication between command line tools and the test suite.
+	  Also restructured the sources of the tools.
+	+ Added UTF-8 to UTF-7 folder name conversion for compatibility with IMAP.
+	+ Created man pages for the command line tools. These are automatically
+	  installed upon 'make install'
+	+ Incorporated Valgrind support into the testsuite and fixed a few memory
+	  leaks in the process.
+	- Fixed compile error surfacing for gcc3.4. Forgot mask argument for the
+	  open() system call when the O_CREAT flag is specified. Bug found by
+	  Sergey Ivanov.
+	- Fixed bug in the sievec tool. -d output was always written to stdout.
+	- Fixed important bug in the imap4flags extension. When no :flags argument is
+	  specified, the previous version would always use the final value of the
+	  internal variable to set the flags. This means that modifications to the
+	  internal variable also affected the bare fileinto/keep actions executed
+	  earlier. This does not comply to the RFC.
+	- Fixed bug in the include extension's import/export commands. Duplicate
+	  import/exports caused problems.
+	- Fixed bug in the handling of non-existent scripts. Errors were sometimes
+	  ignored.
+	- Dovecot omitted unfolding multi-line headers. This was added to the cmusieve
+	  plugin after the code was incorporated into the new implementation. This is
+	  now mplicitly fixed by concurrent change in Dovecot.
+
+v0.1.0 23-10-2008  Stephan Bosch <stephan@rename-it.nl>
+
+	* Initial release
+
+Dovecot ManageSieve NEWS history:
+---------------------------------
+
+Dovecot 1.2:
+
+v0.11.11:
+	* This release contains adjustments to match changes in the Sieve API. This
+	  means that this release will only compile against Pigeonhole Sieve
+	  v0.1.15.
+	+ Implemented ManageSieve QUOTA enforcement.
+	+ Added MAXREDIRECTS capability after login.
+	+ Implemented new script name rules specified in most recent ManageSieve
+	  draft.
+	- Fixed assertion failure occurring with challenge-response SASL mechanisms.
+	- Made configure complain about trying to compile against installed Dovecot
+	  headers alone.
+	- Fixed compile warning for compilation against CMUSieve.
+
+v0.11.10:
+	* This release contains adjustments to match changes in the Sieve API. This
+	  means that this release will only compile against Pigeonhole Sieve
+	  v0.1.14.
+	- Fixed compilation of ManageSieve against CMUSieve.
+
+v0.11.9:
+	* Adjusted to changes in the Dovecot login proxy API. This release
+	  therefore depends on Dovecot v1.2.4.
+	+ Reintroduced ability to abort SASL with "*" response. Latest ManageSieve
+	  specification includes it.
+
+v0.11.8:
+	- Fixed TLS support for proxying ManageSieve. The protocol state machine
+	  was incorrect. Also added a check that disables ssl when 'starttls' is
+	  not enabled for the user. This produces a proper warning in the log file.
+	  There is no such thing as a managesieveS protocol which has SSL from the
+	  start.
+
+v0.11.7:
+	* Adjusted to changes in the Dovecot login API. This release now depends on
+	  Dovecot v1.2.1 or newer.
+	* Incorporated various small changes in IMAP into ManageSieve. This includes
+	  properly enabling the generation of core dumps.
+	- The previous release implicitly resolved the FreeBSD script truncation
+	  error. This release adds a small correction to the code that detects the
+	  truncation.
+	- Fixed panic occurring when many errors are produced by the Sieve compiler
+	  (bug found by Pascal Volk).
+	- Fixed memory leak in the PUTSCRIPT command.
+
+v0.11.6:
+	* Adjusted to changes in Dovecot regarding client idle timeout vs
+	  authentication timeout. This release now depends on Dovecot v1.2.rc6 or
+	  newer.
+	- Fixed CRLF line breaks in compile errors (bug reported by Pascal Volk).
+	- Corrected directory/file creation behavior with respect to mode bits
+	  and gid (bug reported by Pascal Volk).
+	- Improved handling of script truncation bugs: connection is now closed and
+	  an error is logged. bug itself not fixed yet).
+	- Prevented temp script name from showing up in error output.
+
+v0.11.5:
+	* Incorporated various changes from imap-login into managesieve-login. This
+	  includes changes in the proxy support.
+
+v0.11.4:
+	* Adjusted to changes in the Dovecot signal handler API.
+
+v0.11.3:
+	* Changed the SASL service name from "managesieve" into "sieve" as required
+	  in the protocol specification. Don't forget to adjust your configuration
+	  if your authentication mechanism depends on this service name.
+	* Adapted to changes in Dovecot, making this release dependent on Dovecot
+	  >= v1.2.beta1.
+	* Adapted to changes in the new Sieve implementation, making this release
+	  dependent on Dovecot Sieve >= v0.1.3 if used. The old cmusieve plugin is
+	  still supported.
+	+ Implemented making the SIEVE and NOTIFY capability fully dynamic, meaning
+	  that the sieve_extensions setting that was introduced for the new Sieve
+	  plugin properly affects the ManageSieve daemon as well.
+	+ Added support for the CHECKSCRIPT command. In terms of the supported
+	  commands, the ManageSieve daemon now complies with protocol VERSION 1.0 as
+	  listed in the CAPABILITY response.
+	- Fixed maximum permissions for uploaded scripts; was 0777. This
+	  was shielded however by the default umask (not documented to be
+	  configurable), so the actual permissions would never have been 0777.
+	- Fixed a segfault bug in the authentication time-out. Bug report and trace
+	  provided by Wolfgang Friebel.
+	- Fixed handling of ~/ in use of mail-data for script location.
+	- Fixed small problems in the login proxy support.
+
+v0.11.2:
+	* Adapted to changes in Dovecot, making this release dependent on Dovecot
+	  >= v1.2.alpa4.
+
+v0.11.1:
+	- Fixed security issue that gives virtual users the ability to read and
+	  modify each other's scripts if the directory structure of the sieve
+	  storage is known.
+	* Updated NOOP command to match new protocol specification
+	+ Improved error handling and implemented the new response codes:
+	  ACTIVE, NONEXISTENT, ALREADYEXISTS and WARNINGS
+
+v0.11.0:
+	* Upgraded to Dovecot v1.2
+	* Added support for new ManageSieve extensions RENAME and NOOP
+	* Moved sieve settings to plugin {} section of config file. Now the settings
+	  `sieve` and `sieve_dir` in the plugin section are used for the Sieve plugin
+	  and the ManageSieve service, avoiding the posibility of accidental
+	  differences in configuration.
+
+Dovecot 1.1:
+
+v0.10.3
+	* Removed erroneous inline declarations that caused compiler warnings. GCC 4.3
+	  turns out to fail entirely as reported by Joel Johnson.
+	* Fixed auto-dectection of Sieve implementation during ./configure. It now
+	  produces a proper error when the directory is invalid.
+
+v0.10.2
+	* Fixed bug that caused SASL mechanisms that require more than a single client
+	  response to fail. Reported by Steffen Kaiser and occured when he tried using
+	  the (obsolete) LOGIN mechanism.
+	* Updated installation and configuration documentation to match the
+	  information provided in the wiki
+
+v0.10.1
+	* Fixed bug introduced in v0.10.0: compiled scripts were also written to disk
+	  in the sieve/tmp directory and left there. This accumulates much .sievec
+	  junk in that directory over time.
+	* Fixed bug in tmp file generation for sieve-storage: errors other than EEXIST
+	  would cause the daemon to sleep() loop indefinitely.
+
+	+ Improved log lines to be more recognizable as being generated from
+	  managesieve.
+	+ Added short proxy configuration explanation to the README file
+	+ Added 'Known Issues' section to the README file
+	- Fixed assert bug in sieve-storage occurring when save is canceled.
+
+v0.10.0
+	* Upgraded to Dovecot 1.1:
+		- The actual managesieve implementation is now a separate package.
+		  The dovecot tree still needs to be patched though to make dovecot
+		  recognize the new managesieve service.
+		- Incorporated changes to imap/imap-login into the equivalent
+		  managesieve processes.
+		- Removed cmusieve implementation from managesieve sources. It is
+		  now linked externally from the dovecot-sieve-1.1 package.
+		- Restructured README.managesieve file into separate README, NEWS,
+		  TODO, DESIGN and INSTALL files.
+	* Added support for new libsieve implementation (to be released). This
+	  package can be compiled with either the new or the old Sieve
+	  implementation (autodetected). If the new Sieve becomes stable, this
+	  package will be merged with it to make a single package for Dovecot
+	  Sieve support.
+
+Dovecot 1.0:
+
+v9
+
++ Definitively fixed the segfault mentioned in V8. It proved to be
+  very time-constrained and thus hard to reproduce. The error turned out
+  to be related to the input handling of the login daemon during
+  authentication.
++ Checked for changes in the imap daemon that weren't propagated to the
+  managesieve implementation due to code duplication.
++ Fixed a bug in the autodetection of the sieve storage location.
++ Fixed bug in the sieve storage that failed to refresh the symlink if
+  the storage was moved.
++ Improved error handing in the sieve-storage implementation in various
+  places.
++ Fixed the situation in which the active script link is located in the
+  sieve storage.
++ Added managesieve configuration to dovecot-example.conf and made the example
+  in this file more concise.
+
+v8
+
++ Fixed a few incompatibilities with 1.0.7 version. For instance, the "Logged
+  in" message is now sent by the -login process and not by the managesieve
+  daemon anymore. This caused a segfault every once in a while.
++ Probably fixed the settings problem reported by Steffen Kaiser regarding
+  login_dir. 'dovecot -n' now reports correct results, but testing will show
+  whether the whole problem is solved.
++ The managesieve daemon now accepts the sieve_storage and sieve configuration
+  settings, so it is now possible to explicitly configure the location of the
+  sieve storage and the active script respectively. The daemon still falls back
+  to using the mail_location (MAIL) settings if nothing else is specified.
++ The cyrus timsieved does not use the + character in string literals and many
+  clients have adopted to this behaviour. The latest managesieve (08) advises to
+  accept a missing + from clients. The server should not send any + characters
+  as well. This behavior is now implemented on the server.
++ Cleaned up sieve-storage.c: split up the sieve_storage_create function in
+  various sub-functions for obtaining the various paths and directories.
++ Forced manual intervention if rescueing a non-symlink file at the active script
+  path fails somehow. Previously, this presented the admin with a log message
+  that it had just eaten the script, which is not very nice.
++ Restructured the README.managesieve file and added some more explanation with
+  regard to the configuration of the daemon.
+
+v7
+
+- Robin Breathe indicated that the regex capability was missing in the server's
+  SIEVE listing. It turns out I forgot to make arrangements for setting
+  ENABLE_REGEX in the cmu libsieve sources, so the regex extension was not
+  compiled in. I copied the configure.in section regarding ENABLE_REGEX from
+  dovecot-sieve-1.0.2 and that fixed the problem.
+
+v6
+
+- Corked the client output stream while producing the capability greeting and on
+  other some other occasions as well. Some naive client implementations expect to
+  receive this as a single tcp frame and it is a good practice to do so anyway.
+  Using this change the Thunderbird sieve extension (v0.1.1) seemed to work. However,
+  scripts larger than a tcp frame still caused failures. All these issues are fixed
+  in the latest version of the sieve add-on (currently v0.1.4).
+- Cleaned up the new proxy source. My editor made the indentation a complete mess
+  in terms of TABs vs spaces.
+- Added TRYLATER response codes to BYE and NO messages where appropriate.
+- Recopied the libsieve library into this patch to incorporate any changes that were
+  made (only sieve-cmu.c still needs to be compared to the old cmu-sieve.c). This
+  also solves the __attribute__((unused)) GCC dependencies. These were fixed long
+  ago by Timo....  the code duplication beast strikes again.
+- Removed spurious return value from void function in
+  src/lib-sieve/sieve-implementation.c as reported by Robin Breathe. GCC fails to
+  report these issues. The function involved is currently not used and serves only
+  as an example on how dovecot could support multiple sieve backends...
+
+v5
+
+- Applied patch by Uldis Pakuls to fix master_dump_settings bug
+- Added some compilation/installation info to this README
+- Moved README to source tree root as README.managesieve
+- Fixed minor error handling bug in sieve_storage.c with respect to a missing
+  root directory.
+- Now sieve capabilities are reported as they are specified by the implementing
+  library and not in forced upper case. The sieve RFC now explicitly states
+  that sieve capability identifiers are case-sensitive. This broke compatibility
+  with SquirrelMail/Avelsieve.
+- Disabled ANONYMOUS login entirely until proper support is implemented. V4
+  claimed to do so as well, but in fact it only stopped announcing it.
+- Implemented managesieve-proxy. It is not so much a clean copy of imap-proxy,
+  since the managesieve greeting is much more complex and requires parsing.
+  Configuration is identical to imap-proxy. This seems to be a little under-
+  documented however (http://wiki.dovecot.org/PasswordDatabase/ExtraFields).
+
+v4
+
+- Added managesieve_implementation_string setting to the managesieve
+  configuration. This can be used to customize the default "IMPLEMENTATION"
+  capability response.
+- Denied ANONYMOUS login until proper support is implemented
+- Fixed problem with authenticate command regarding continued responses. In
+  V3 only initial response would work. Problem was caused by rc2 -> rc28
+  upgrade. One of the clear reasons why code duplication is a very bad idea.
+- Fixed readlink bug as indicated by Timo: return value of readlink can also
+  be -1.
+- Fixed bug in the regular file rescue code, as introduced in the previous
+  version. Used stat instead of lstat. This caused the symlink to be rescued
+  subsequently in the next activation, thus still overwriting the initially
+  rescued script.
+
+v3
+
+- Updated source to compile with dovecot 1.0.rc27
+- Daemon now uses the same location for .dovecot.sieve as dovecot-lda
+  This is typically ~/.dovecot.sieve
+- If .dovecot.sieve is a regular file, it is now moved into the script storage as
+  dovecot.orig.sieve, preventing deletion of (important) active scripts
+  upon upgrade.
+- Changed error handling to yield a BYE message when the managesieve
+  daemon exits unexpectedly (upon login) before any commands are entered.
+  Horde-ingo would wait indefinitely for a response.
+
+v2
+
+- Fixed the bug (missing CRLF) in the authenticate command
+- Modified the sieve storage library making the interface much less crude.
+- The scripts put on the server using the putscript command are now
+  checked before they are accepted.
+- The reported SIEVE capability is now directly read from the sieve
+  implementation (in this case cmu), listing much more than "FILEINTO
+  VACATION".
+- Imported instance of libsieve source into this patch for implementation
+  of script checking and capability listing. THIS NEEDS TO BE CHANGED!
+- Fixed some minor bugs in the putscript command
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/README
@@ -0,0 +1,332 @@
+Pigeonhole for Dovecot v2.3
+
+Introduction
+============
+
+This package is part of the Pigeonhole project (http://pigeonhole.dovecot.org).
+It adds support for the Sieve language (RFC 5228) and the ManageSieve protocol
+(RFC 5804) to the Dovecot Secure IMAP Server. In the literal sense, a pigeonhole
+is a a hole or recess inside a dovecot for pigeons to nest in. It is, however,
+also the name for one of a series of small, open compartments in a cabinet used
+for filing or sorting mail. As a verb, it describes the act of putting an item
+into one of those pigeonholes. The name `Pigeonhole' therefore well describes an
+important part of the functionality that this project adds to Dovecot: sorting
+and filing e-mail messages.
+
+The Sieve language is used to specify how e-mail needs to be processed. By
+writing Sieve scripts, users can customize how messages are delivered, e.g.
+whether they are forwarded or stored in special folders. Unwanted messages can
+be discarded or rejected, and, when the user is not available, the Sieve
+interpreter can send an automated reply. Above all, the Sieve language is meant
+to be simple, extensible and system independent. And, unlike most other mail
+filtering script languages, it does not allow users to execute arbitrary
+programs. This is particularly useful to prevent virtual users from having full
+access to the mail store. The intention of the language is to make it impossible
+for users to do anything more complex (and dangerous) than write simple mail
+filters.
+
+Using the ManageSieve protocol, users can upload their Sieve scripts remotely,
+without needing direct filesystem access through FTP or SCP. Additionally, a
+ManageSieve server always makes sure that uploaded scripts are valid, preventing
+compile failures at mail delivery.
+
+This package provides Sieve support as a plugin to Dovecot's Local Delivery
+Agent (LDA) and Dovecot's LMTP service. The ManageSieve protocol is provided is
+an additional service, next to Dovecot's own POP3 and IMAP services.
+
+Features
+========
+
+  * The Pigeonhole Sieve implementation aims to be admin- and user-friendly.
+    Much like Dovecot itself, common error messages are made as easily
+    understandable as possible. Any crash, no matter how it happened, is
+    considered a bug that will be fixed. The compiler does not bail on the first
+    error, but it looks for more script errors to make debugging more efficient.
+
+  * The Pigeonhole Sieve implementation is, much like the Sieve language itself,
+    highly extensible with new Sieve capabilities. This includes support for
+    third-party plugins. It should eventually provide the necessary
+    infrastructure for at least all currently known relevant (proposed) Sieve
+    extensions. The goal is to keep the extension interface provided by the
+    Sieve implementation as generic as possible, i.e. without explicit support
+    for specific extensions. New similar extensions can then use the same
+    interface methods without changes to the Sieve engine code. If an extension
+    is not loaded using the require command, the compiler truly does not know of
+    its existence.
+
+  * The Pigeonhole Sieve plugin is backwards compatible with the old CMUSieve
+    plugin, which provided Sieve support for older versions of Dovecot. All
+    Sieve extensions supported by the old plugin are also supported by the
+    Pigeonhole Sieve plugin, including those that are now considered to be
+    deprecated.
+
+  * The Pigeonhole Sieve implementation supports executing multiple Sieve
+    scripts sequentially. Using this feature it is possible to execute
+    administrator-controlled Sieve scripts before and after the user's personal
+    Sieve script, guaranteeing that responses and message deliveries are never
+    duplicated. This implementation is based on a draft specification
+    (http://tools.ietf.org/html/draft-degener-sieve-multiscript-00), which
+    defines the Sieve behavior when multiple scripts are executed sequentially
+    on the same message.
+
+  * The Pigeonhole Sieve implementation includes a test suite to automatically
+    assess whether the compiled Sieve engine works correctly. The test suite is
+    an extension to the Sieve language and is therefore easily extended with new
+    tests. Currently, the test suite is mostly limited to testing script
+    processing. The performed actions are not tested fully yet.
+
+  * The Pigeonhole Sieve implementation supports the new and very useful
+    variables extension, which allows maintaining state information throughout
+    a Sieve script across subsequent rules.
+
+  * The Pigeonhole Sieve plugin is distributed with a sieve-test tool that
+    simplifies testing Sieve scripts and provides additional debugging
+    facilities.
+
+Sieve Implementation Status
+===========================
+
+The core of the language (as specified in RFC 5228) is fully supported. In
+addition to that, this Sieve implementation features various extensions. The
+following list outlines the implementation status of each supported extension:
+
+  The language extensions defined in the base specification are fully supported:
+
+    encoded-character (RFC 5228; page 10)
+    fileinto (RFC 5228; page 23)
+    envelope (RFC 5228; page 27)
+
+  The following Sieve language extensions are also supported:
+
+    copy (RFC 3894): fully supported.
+    body (RFC 5173): fully supported.
+    environment (RFC 5183): fully supported (v0.4.0+).
+    variables (RFC 5229): fully supported.
+    vacation (RFC 5230): fully supported.
+      + vacation-seconds (RFC 6131): fully supported (v0.2.3+).
+    relational (RFC 5231): fully supported.
+    imap4flags (RFC 5232): fully supported.
+    subaddress (RFC 5233): fully supported, but with limited configurability.
+    spamtest and virustest (RFC 5235): fully supported (v0.1.16+).
+    date (RFC 5260; Section 4): fully supported (v0.1.12+).
+    index (RFC 5260; Section 6): fully supported (v0.4.7+).
+    editheader (RFC 5293): fully supported (v0.3.0+).
+    reject (RFC 5429; Section 2.2): fully supported.
+    enotify (RFC 5435): fully supported (v0.1.3+).
+        mailto method (RFC 5436): fully supported (v0.1.3+).
+        xmpp method (RFC 5437): is under development and will become available
+          as a plugin.
+    ihave (RFC 5463): fully supported (v0.2.4+).
+    mailbox (RFC 5490; Section 3): fully supported (v0.1.10+), but ACL
+        permissions are not verified for mailboxexists.
+    mboxmetadata and servermetadata (RFC 5490): fully supported (v0.4.7+)
+    foreverypart (RFC 5703; Section 3): fully supported (v0.4.10+).
+    mime (RFC 5703; Section 4): fully supported (v0.4.10+).
+    extracttext (RFC 5703; Section 7): fully supported (v0.4.12+).
+    include (RFC 6609): fully supported (v0.4.0+).
+    imapsieve (RFC 6785): fully supported (v0.4.14+).
+    duplicate (RFC 7352): fully supported (v0.4.3+).
+    regex (draft v08; not latest version): almost fully supported, but
+        UTF-8 is not supported.
+
+  The following deprecated extensions are supported for backwards
+  compatibility:
+
+    imapflags (obsolete draft): fully backwards compatible (v0.1.3+)
+    notify (obsolete draft): fully backwards compatible (v0.1.15+)
+
+    The availability of these deprecated extensions is disabled by default.
+
+  The following Dovecot-specific Sieve extensions are available:
+
+    vnd.dovecot.debug (v0.3.0+):
+        Allows logging debug messages.
+    vnd.dovecot.execute (v0.4.0+; sieve_extprograms plugin):
+        Implements executing a pre-defined set of external programs with the
+        option to process string data through the external program.
+    vnd.dovecot.filter (v0.4.0+; sieve_extprograms plugin):
+        Implements filtering messages through a pre-defined set of external
+        programs.
+    vnd.dovecot.pipe (v0.4.0+; sieve_extprograms plugin):
+        Implements piping messages to a pre-defined set of external programs.
+    vnd.dovecot.report (v0.4.14):
+        Implements sending MARF reports (RFC 5965).
+
+  The following extensions are under development:
+
+    ereject (RFC 5429; page 4): implemented, but currently equal to reject.
+
+  Many more extensions to the language exist. Not all of these extensions are
+  useful for Dovecot in particular, but many of them are. Currently, the
+  author has taken notice of the following extensions:
+
+    replace (RFC 5703; Section 5): planned.
+    enclose (RFC 5703; Section 6): planned.
+    envelope-dsn, envelope-deliverby, redirect-dsn and
+      redirect-deliverby (RFC 6009): planned; depends on lib-smtp changes in
+        Dovecot.
+    extlists (RFC 6134): planned.
+    convert (RFC 6558): under consideration.
+
+    These extensions will be added as soon as the necessary infrastructure is
+    available.
+
+Check the TODO file for an up-to-date list of open issues and current
+development. 
+
+Compiling and Configuring
+=========================
+
+Refer to INSTALL file.
+
+Sieve Tools
+===========
+
+To test the sieve engine outside deliver, it is useful to try the commands that
+exist in the src/sieve-tools/ directory of this package. After installation,
+these are available at your $prefix/bin directory. The following commands are
+installed:
+
+sievec       - Compiles sieve scripts into a binary representation for later
+               execution. Refer to the next section on manually compiling Sieve
+               scripts.
+
+sieve-test   - This is a universal Sieve test tool for testing the effect of a
+               Sieve script on a particular message. It allows compiling,
+               running and testing Sieve scripts. It can either be used to
+               display the actions that would be performed on the provided test
+               message or it can be used to test the actual delivery of the
+               message and show the messages that would normally be sent through
+               SMTP.
+
+sieve-dump   - Dumps the content of a Sieve binary file for (development)
+               debugging purposes.
+
+sieve-filter - Allow running Sieve filters on messages already stored in a
+               mailbox. 
+
+When installed, man pages are also available for these commands. In this package
+the man pages are present in doc/man and can be viewed before install using
+e.g.:
+
+man -l doc/man/sieve-test.1
+
+Various example scripts are bundled in the directory 'examples'. These scripts
+were downloaded from various locations. View the top comment in the scripts for
+url and author information.
+
+Compiling Sieve Scripts
+=======================
+
+When the LDA Sieve plugin executes a script for the first time (or after it has
+been changed), it is compiled into into a binary form. The Pigeonhole Sieve
+implementation uses the .svbin extension to store compiled Sieve scripts (e.g.
+.dovecot.svbin). To store the binary, the plugin needs write access in the
+directory in which the script is located.
+
+A problem occurs when a global script is encountered by the plugin. For security
+reasons, global script directories are not supposed to be writable by the user.
+Therefore, the plugin cannot store the binary when the script is first compiled.
+Note that this doesn't mean that the old compiled version of the script is used
+when the binary cannot be written: it compiles and uses the current script
+version. The only real problem is that the plugin will not be able to update
+the binary on disk, meaning that the global script needs to be recompiled each
+time it needs to be executed, i.e. for every incoming message, which is
+inefficient.
+
+To mitigate this problem, the administrator must manually pre-compile global
+scripts using the sievec command line tool. For example:
+
+sievec /var/lib/dovecot/sieve/global/
+
+This is often necessary for scripts listed in the sieve_default, sieve_before
+and sieve_after settings. For global scripts that are only included in other
+scripts using the include extension, this step is not necessary, since included
+scripts are incorporated into the binary produced for the main script located in
+a user directory.
+
+Compile and Runtime Logging
+===========================
+
+Log messages produced at runtime by the Sieve plugin are written to two
+locations:
+
+  * Messages are primarily logged to the user log. By default this log file is
+    located in the same directory as the user's main active personal script (as
+    specified by the sieve setting). This log file bears the name of that script
+    file appended with ".log", e.g. ".dovecot.sieve.log". The location of the
+    user log file can also be explicitly configured using the sieve_user_log
+    setting (e.g. for when Sieve scripts are not stored on the local file
+    system).
+
+    If there are errors or warnings in the script, the messages are appended to
+    that log file until it eventually grows too large. When that happens, the
+    old log file is rotated to a ".log.0" file and an empty log file is started.
+    Informational messages are not written to this log file and the log file is
+    not created until messages are actually logged, i.e. when an error or
+    warning is produced.
+
+  * Messages that could be of interest to the system administrator are also
+    written to the Dovecot LDA logging facility (usually syslog). This includes
+    informational messages that indicate what actions are executed on incoming
+    messages. Compile errors encountered in the user's private script are not
+    logged here.
+
+The ManageSieve service reports compile errors and warnings only back to the
+user. System and configuration-related messages are written to the Dovecot
+logging facility.
+
+Known issues
+============
+
+Sieve
+-----
+
+Most open issues are outlined in the TODO file. The more generic ones are (re-)
+listed here:
+
+* Compile errors are sometimes a bit obscure and long. This needs work.
+  Suggestions for improvement are welcome.
+
+* The documentation needs work.
+
+ManageSieve
+-----------
+
+* Although this ManageSieve server should comply with the draft specification of
+  the ManageSieve protocol, quite a few clients don't. This is particularly true
+  for the TLS support. However, now that Cyrus' Timsieved has changed its
+  behavior towards protocol compliance, all those clients will follow
+  eventually.
+
+  Clients known to have TLS issues:
+	- Thunderbird Sieve add-on: fixed as per version 0.1.5
+	- AvelSieve: patch on the wiki:	http://wiki.dovecot.org/ManageSieve
+	- KMail + kio_sieve: TLS broken for old versions. This issue is fixed at
+	  least in kmail 1.9.9 / kde 3.5.9.
+
+  Unfortunately, there is no reliable way to provide a workaround for this
+  problem. We will have to wait for the authors of these clients to make the
+  proper adjustments.
+
+* Other client issues:
+
+	- SmartSieve, WebSieve:
+	  These clients are specifically written for Cyrus timsieved and fail on
+	  multiple stages of the protocol when connected to Pigeonhole ManageSieve.
+
+Authors
+=======
+
+Refer to AUTHORS file.
+
+Contact Info
+============
+
+Stephan Bosch <stephan at rename-it dot nl>
+IRC: Freenode, #dovecot, S[r]us
+Web: http://pigeonhole.dovecot.org
+
+Please use the Dovecot mailing list <dovecot at dovecot.org> for questions about
+this package. You can post to the list without subscribing, the mail then waits
+in a moderator queue for a while. See http://dovecot.org/mailinglists.html
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/TODO
@@ -0,0 +1,97 @@
+Current activities:
+
+* Rework string matching:
+	- Give Sieve its own runtime string type, rather than (ab)using string_t.
+	- Add support for stream matching for handling large values, e.g. from the
+	  body extension.
+	- Improve efficiency of :matches and :contains match types.
+* Build proper comparator support:
+	- Add normalize() method to comparators to normalize the string before
+	  matching (for efficiency).
+	- Allow for the existence of dynamic comparators (i.e. specified by
+	  variables).
+	- Implement comparator-i;unicode-casemap.
+
+Parallel plugin-based efforts (on hold at the moment):
+
+* Implement enotify xmpp method as a plugin.
+
+Next (mostly in order of descending priority/precedence):
+
+* Implement message modification and extraction API in order to:
+	- Properly implement the interaction between editheader and foreverypart/mime.
+	- Implement replace, enclose extensions.
+* Properly implement Sieve internationalization support (utf-8 handling),
+  currently it is not complete:
+	- Make this implementation fully conform section 2.7.2 of RFC5228 (Comparisons
+	  Across Character Sets).
+	- Verify validity of utf8 where necessary.
+* Further develop regex extension and update it to the latest draft:
+	- Implement the :quoteregex set modifier
+	- Investigate the use of the TRE regexp library to gain UTF-8 capability
+	  (posix regexes actually do support utf8, but only when locale is set
+	  accordingly)
+* Finish LDAP Sieve script storage for read-only access.
+	- Consolidate LDAP connections when more than a single Sieve script must be
+	  loaded from different storages linked to the same LDAP server.
+	- Adjust Sieve script API to support asynchronous script retrieval to
+	  retrieve scripts in parallel when possible.
+* Improve error handling.
+	- Implement dropping errors in the user's mailbox as a mail message.
+* Finish body extension:
+	- Build test cases for decoding MIME encodings to UTF-8
+* Cleanup the test suite
+	- Restructure test scripts
+	- Add more comment on purpose of tests
+* Finish the ereject extension
+* Vacation extension improvements:
+	- Implement configurable sender exclusion list.
+	- Implement mechanism for implicitly including an account's aliases in the
+	  vacation command's :addresses list.
+* Fix remaining RFC deviations:
+	- Fix issues listed in doc/rfc/RFC-questions.txt based on answers
+	- Verify outgoing mail addresses at runtime when necessary
+	  (e.g. after variables substitution)
+	- Improve handling of invalid addresses in headers (requires Dovecot changes)
+* Improve sieve_extprograms plugin:
+	- Redesign (forcible) local script termination. It should use SIGCHLD and
+	  a ioloop-based timeout.
+	- Add facility to trigger a temporary failure condition when a program
+	  fails rather than an implicit keep.
+	- Add a method to implicitly pass environment variables such as SENDER and
+	  RECIPIENT through the script socket service.
+* Make testsuite much more exhaustive:
+	- Add support for testing the content of result actions
+	- Test as many error/warning/info conditions as possible.
+	- Review the specification documents and check whether the given requirements
+	  are tested at least once.
+* Fix ManageSieve proxy to recognize response codes from the backend and forward
+  them to the user if appropriate/safe. Probably means implementing a proper
+  ManageSieve client library.
+* Test ManageSieve behavior thoroughly:
+	- Test pipelined behavior
+	- Test proxy authentication
+* Code cleanup:
+	- Make address handling more uniform.
+	- Review all FIXMEs
+
+* Build a server with test mail accounts that processes lots and lots of mail
+  (e.g. spam, mailing lists etc.)
+
+Low priority items:
+
+* Implement extlists extension as a plugin
+* Enotify extension: detect use of variable values extracted from the message
+  that are used in the method argument. RFC reports this as a security issue.
+* Provide a solution for mail_get_headers_utf8 reparsing the whole message each
+  time it is called (header and address test; Timo might provide solution from
+  within Dovecot)
+* Warn during compile if using non-existent folders.
+
+* Variables extension: implement compile time evaluation of constant values
+	- Detect assignment of too large constant values to variables at compile
+	  time.
+* Add development documentation, i.e. comment on library functions and document
+  the binary and byte-code format.
+* Implement sip-message notify mechanism.
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/autogen.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+# If you've non-standard directories, set these
+#ACLOCAL_DIR=
+#GETTEXT_DIR=
+
+if test "$ACLOCAL_DIR" != ""; then
+  ACLOCAL="aclocal -I $ACLOCAL_DIR"
+  export ACLOCAL
+fi
+
+for dir in $GETTEXT_DIR /usr/share/gettext; do
+  if test -f $dir/config.rpath; then
+    /bin/cp -f $dir/config.rpath .
+    break
+  fi
+done
+
+autoreconf -i
+
+rm -f ChangeLog
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/configure.ac
@@ -0,0 +1,243 @@
+AC_PREREQ([2.59])
+
+# Be sure to update ABI version also if anything changes that might require
+# recompiling plugins. Most importantly that means if any structs are changed.
+AC_INIT([Pigeonhole], [0.5.4], [dovecot@dovecot.org], [dovecot-2.3-pigeonhole])
+AC_DEFINE_UNQUOTED([PIGEONHOLE_ABI_VERSION], "0.5.ABIv0($PACKAGE_VERSION)", [Pigeonhole ABI version])
+
+AC_CONFIG_AUX_DIR([.])
+AC_CONFIG_SRCDIR([src])
+AC_CONFIG_MACRO_DIR([m4])
+
+# Autoheader is not needed and does more harm than good for this package. However, it is
+# tightly integrated in autoconf/automake and therefore it is difficult not to use it. As
+# a workaround we give autoheader a dummy config header to chew on and we handle the
+# real config header ourselves.
+AC_CONFIG_HEADERS([dummy-config.h pigeonhole-config.h])
+
+AC_DEFINE_UNQUOTED(PIGEONHOLE_NAME, "$PACKAGE_NAME",
+	[Define to the full name of Pigeonhole for Dovecot.])
+AC_DEFINE_UNQUOTED(PIGEONHOLE_VERSION, "$PACKAGE_VERSION",
+	[Define to the version of Pigeonhole for Dovecot.])
+
+AM_INIT_AUTOMAKE([no-define foreign tar-ustar])
+
+AM_MAINTAINER_MODE
+
+AC_PROG_CC
+AC_PROG_CPP
+AC_PROG_LIBTOOL
+
+# Couple with Dovecot
+#
+
+DC_DOVECOT
+DC_DOVECOT_MODULEDIR
+LIBDOVECOT_INCLUDE="$LIBDOVECOT_INCLUDE $LIBDOVECOT_STORAGE_INCLUDE"
+CFLAGS="$DOVECOT_CFLAGS -I\$(top_srcdir)"
+LIBS="$DOVECOT_LIBS"
+BINARY_CFLAGS="$DOVECOT_BINARY_CFLAGS"
+BINARY_LDFLAGS="$DOVECOT_BINARY_LDFLAGS"
+AC_SUBST(BINARY_CFLAGS)
+AC_SUBST(BINARY_LDFLAGS)
+AC_SUBST(LIBDOVECOT_INCLUDE)
+
+# Define Sieve documentation install dir
+#
+
+sieve_docdir='${dovecot_docdir}/sieve'
+AC_SUBST(sieve_docdir)
+
+# Extensions under development
+#
+
+AC_ARG_WITH(unfinished-features,
+[AC_HELP_STRING([--with-unfinished-features],
+	[Build unfinished new features/extensions [default=no]])],
+        if test x$withval = xno || test x$withval = xauto; then
+                want_unfinished_features=$withval
+        else
+                want_unfinished_features=yes
+        fi,
+        want_unfinished_features=no)
+AM_CONDITIONAL(BUILD_UNFINISHED, test "$want_unfinished_features" = "yes")
+
+if test "$want_unfinished_features" = "yes"; then
+	AC_DEFINE(HAVE_SIEVE_UNFINISHED,,
+		[Define to build unfinished features/extensions.])
+fi
+
+#
+#
+
+dnl TEST_WITH(name, value, [plugin])
+AC_DEFUN([TEST_WITH], [
+  want=want_`echo $1|sed s/-/_/g`
+  if test $2 = yes || test $2 = no || test $2 = auto; then
+    eval $want=$2
+  elif test $2 = plugin; then
+    if test "$3" = plugin; then
+      eval $want=plugin
+    else
+      AC_ERROR([--with-$1=plugin not supported])
+    fi
+  elif `echo $2|grep '^/' >/dev/null`; then
+    AC_ERROR([--with-$1=path not supported. You may want to use instead:
+CPPFLAGS=-I$2/include LDFLAGS=-L$2/lib ./configure --with-$1])
+  else
+    AC_ERROR([--with-$1: Unknown value: $2])
+  fi
+])
+
+AC_ARG_WITH(docs,
+[  --with-docs             Install documentation (default)],
+    if test x$withval = xno; then
+        want_docs=no
+    else
+        want_docs=yes
+    fi,
+    want_docs=yes)
+AM_CONDITIONAL(BUILD_DOCS, test "$want_docs" = "yes")
+
+AC_ARG_WITH(managesieve,
+[AC_HELP_STRING([--with-managesieve],
+	[Build ManageSieve service [default=yes]])],
+        if test x$withval = xno || test x$withval = xauto; then
+                want_managesieve=$withval
+        else
+                want_managesieve=yes
+        fi,
+        want_managesieve=yes)
+AM_CONDITIONAL(BUILD_MANAGESIEVE, test "$want_managesieve" = "yes")
+
+AC_ARG_WITH(ldap,
+AS_HELP_STRING([--with-ldap=yes|plugin], [Build with LDAP support]),
+  TEST_WITH(ldap, $withval, plugin),
+  want_ldap=no)
+
+# FIXME: Imported this from Dovecot auth for now. We're working on a proper
+# lib-ldap, but, until then, some code is duplicated.
+have_ldap=no
+if test $want_ldap != no; then
+	AC_CHECK_LIB(ldap, ldap_init, [
+		AC_CHECK_HEADER(ldap.h, [
+			AC_CHECK_LIB(ldap, ldap_initialize, [
+				AC_DEFINE(LDAP_HAVE_INITIALIZE,, [Define if you have ldap_initialize])
+			])
+			AC_CHECK_LIB(ldap, ldap_start_tls_s, [
+				AC_DEFINE(LDAP_HAVE_START_TLS_S,, [Define if you have ldap_start_tls_s])
+			])
+			LDAP_LIBS="-lldap"
+			AC_CHECK_LIB(ldap, ber_free, [
+			  # do nothing, default is to add -lldap to LIBS
+			  :
+			], [
+			  AC_CHECK_LIB(lber, ber_free, [
+			    LDAP_LIBS="$LDAP_LIBS -llber"
+			  ])
+			])
+			AC_SUBST(LDAP_LIBS)
+			if test $want_ldap != plugin; then
+				AC_DEFINE(SIEVE_BUILTIN_LDAP,, [LDAP support is built in])
+			fi
+
+  		AC_DEFINE(STORAGE_LDAP,, [Build with LDAP support])
+			AC_CHECK_HEADERS(sasl.h sasl/sasl.h)
+			have_ldap=yes
+		], [
+		  if test $want_ldap != auto; then
+		    AC_ERROR([Can't build with LDAP support: ldap.h not found])
+		  fi
+		])
+	], [
+	  if test $want_ldap != auto; then
+	    AC_ERROR([Can't build with LDAP support: libldap not found])
+	  fi
+	])
+fi
+
+if test $have_ldap = no; then
+  not_scriptloc="$not_scriptloc ldap"
+else
+  scriptloc="$scriptloc ldap"
+  if test $want_ldap = plugin; then
+    have_ldap_plugin=yes
+    scriptloc="$scriptloc (plugin)"
+  fi
+fi
+AM_CONDITIONAL(LDAP_PLUGIN, test "$have_ldap_plugin" = "yes")
+
+CFLAGS="$CFLAGS $EXTRA_CFLAGS"
+LDFLAGS="$LDFLAGS $EXTRA_LDFLAGS"
+
+AC_CONFIG_FILES([
+Makefile
+doc/Makefile
+doc/man/Makefile
+doc/example-config/Makefile
+doc/example-config/conf.d/Makefile
+doc/extensions/Makefile
+doc/locations/Makefile
+doc/plugins/Makefile
+src/Makefile
+src/lib-sieve/Makefile
+src/lib-sieve/util/Makefile
+src/lib-sieve/storage/Makefile
+src/lib-sieve/storage/data/Makefile
+src/lib-sieve/storage/file/Makefile
+src/lib-sieve/storage/dict/Makefile
+src/lib-sieve/storage/ldap/Makefile
+src/lib-sieve/plugins/Makefile
+src/lib-sieve/plugins/vacation/Makefile
+src/lib-sieve/plugins/subaddress/Makefile
+src/lib-sieve/plugins/comparator-i-ascii-numeric/Makefile
+src/lib-sieve/plugins/relational/Makefile
+src/lib-sieve/plugins/regex/Makefile
+src/lib-sieve/plugins/imap4flags/Makefile
+src/lib-sieve/plugins/copy/Makefile
+src/lib-sieve/plugins/include/Makefile
+src/lib-sieve/plugins/body/Makefile
+src/lib-sieve/plugins/variables/Makefile
+src/lib-sieve/plugins/enotify/Makefile
+src/lib-sieve/plugins/enotify/mailto/Makefile
+src/lib-sieve/plugins/notify/Makefile
+src/lib-sieve/plugins/environment/Makefile
+src/lib-sieve/plugins/mailbox/Makefile
+src/lib-sieve/plugins/date/Makefile
+src/lib-sieve/plugins/spamvirustest/Makefile
+src/lib-sieve/plugins/ihave/Makefile
+src/lib-sieve/plugins/editheader/Makefile
+src/lib-sieve/plugins/metadata/Makefile
+src/lib-sieve/plugins/duplicate/Makefile
+src/lib-sieve/plugins/index/Makefile
+src/lib-sieve/plugins/mime/Makefile
+src/lib-sieve/plugins/vnd.dovecot/Makefile
+src/lib-sieve/plugins/vnd.dovecot/debug/Makefile
+src/lib-sieve/plugins/vnd.dovecot/environment/Makefile
+src/lib-sieve/plugins/vnd.dovecot/report/Makefile
+src/lib-sieve-tool/Makefile
+src/lib-managesieve/Makefile
+src/plugins/Makefile
+src/plugins/doveadm-sieve/Makefile
+src/plugins/lda-sieve/Makefile
+src/plugins/sieve-extprograms/Makefile
+src/plugins/imapsieve/Makefile
+src/plugins/imap-filter-sieve/Makefile
+src/plugins/settings/Makefile
+src/sieve-tools/Makefile
+src/managesieve/Makefile
+src/managesieve-login/Makefile
+src/testsuite/Makefile
+stamp.h])
+
+AC_OUTPUT
+
+not_scriptloc=`echo "$not_scriptloc"|sed 's/ / -/g'`
+
+echo
+echo "Install prefix . : $prefix"
+echo "script drivers . : file dict$scriptloc"
+if test "$not_scriptloc" != ""; then
+  echo "                 :$not_scriptloc"
+fi
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/Makefile.am
@@ -0,0 +1,17 @@
+SUBDIRS = \
+	man \
+	example-config \
+	extensions \
+	locations \
+	plugins
+
+docfiles =
+
+if BUILD_DOCS
+sieve_doc_DATA = $(docfiles)
+endif
+
+EXTRA_DIST = \
+	devel \
+	$(docfiles)
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/devel/DESIGN
@@ -0,0 +1,45 @@
+The compiler consists of the following stages:
+
+PARSER: sieve-parser.c, sieve-lexer.c
+  Parses the scriptfile and produces an abstract syntax tree for it
+  (sieve-ast.c).
+
+VALIDATOR: sieve-validator.c
+  Performs contextual analysis on the ast produced by the parser. This checks
+  for the validity of commands, tests and arguments. Also, the ast is decorated
+  with any context data acquired during the process. This context is used by the
+  last compiler stage.
+
+GENERATOR: sieve-generator.c
+  This last compiler stage uses a visitor pattern to wander through the ast and
+  produces sieve byte code (sieve-binary.c).
+
+The resulting (in-memory) binary can be fed to the interpreter for execution:
+
+INTERPRETER: sieve-interpreter.c
+  The interpreter executes the byte code and produces a sieve_result object.
+  This result is no more than just a collection of actions to be performed.
+  During execution, action commands add actions to the result. Duplates and
+  conflicts between actions are handled in this execution phase.
+
+RESULT: sieve-result.c sieve-actions.c
+  When the result is to be executed, it needs no further checking, as the
+  validity of the result was verified during interpretation already. The
+  result's actions are executed in a transaction-like atomic manner. If one of
+  the actions fails, the whole transaction is rolled back meaning that either
+  everything succeeds or everything fails. This is only possible to some extent:
+  transmitted responses can of course not be rolled back. However, these are
+  executed in the commit phase, meaning that they will only be performed if all
+  other actions were successful.
+
+Debugging:
+
+BINARY-DUMPER: sieve-code-dumper.c sieve-binary-dumper.c
+  A loaded binary can be dumped to a stream in human-readable form using the
+  binary-dumper. The binary-dumper displays information on all the blocks that
+  the binary consists off. Program code blocks are dumped using the code-dumper.
+  It's implementation is similar to the interpreter, with the exception that it
+  performs no actions and just sequentially wanders through the byte code
+  printing instructions along the way. The term human-readable is a bit optimistic
+  though; currently, the presented data looks like an assembly language.
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/example-config/Makefile.am
@@ -0,0 +1,10 @@
+SUBDIRS = conf.d
+
+pkgsysconfdir = $(sysconfdir)/dovecot
+
+exampledir = $(dovecot_docdir)/example-config
+example_DATA = \
+	sieve-ldap.conf 
+
+EXTRA_DIST = \
+        $(example_DATA)
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/example-config/conf.d/20-managesieve.conf
@@ -0,0 +1,84 @@
+##
+## ManageSieve specific settings
+##
+
+# Uncomment to enable managesieve protocol:
+#protocols = $protocols sieve
+
+# Service definitions
+
+#service managesieve-login {
+  #inet_listener sieve {
+  #  port = 4190
+  #}
+
+  #inet_listener sieve_deprecated {
+  #  port = 2000
+  #}
+
+  # Number of connections to handle before starting a new process. Typically
+  # the only useful values are 0 (unlimited) or 1. 1 is more secure, but 0
+  # is faster. <doc/wiki/LoginProcess.txt>
+  #service_count = 1
+
+  # Number of processes to always keep waiting for more connections.
+  #process_min_avail = 0
+
+  # If you set service_count=0, you probably need to grow this.
+  #vsz_limit = 64M
+#}
+
+#service managesieve {
+  # Max. number of ManageSieve processes (connections)
+  #process_limit = 1024
+#}
+
+# Service configuration
+
+protocol sieve {
+  # Maximum ManageSieve command line length in bytes. ManageSieve usually does
+  # not involve overly long command lines, so this setting will not normally
+  # need adjustment
+  #managesieve_max_line_length = 65536
+
+  # Maximum number of ManageSieve connections allowed for a user from each IP
+  # address.
+  # NOTE: The username is compared case-sensitively.
+  #mail_max_userip_connections = 10
+
+  # Space separated list of plugins to load (none known to be useful so far).
+  # Do NOT try to load IMAP plugins here.
+  #mail_plugins =
+
+  # MANAGESIEVE logout format string:
+  #  %i - total number of bytes read from client
+  #  %o - total number of bytes sent to client
+  #  %{put_bytes} - Number of bytes saved using PUTSCRIPT command
+  #  %{put_count} - Number of scripts saved using PUTSCRIPT command
+  #  %{get_bytes} - Number of bytes read using GETCRIPT command
+  #  %{get_count} - Number of scripts read using GETSCRIPT command
+  #  %{get_bytes} - Number of bytes processed using CHECKSCRIPT command
+  #  %{get_count} - Number of scripts checked using CHECKSCRIPT command
+  #  %{deleted_count} - Number of scripts deleted using DELETESCRIPT command
+  #  %{renamed_count} - Number of scripts renamed using RENAMESCRIPT command
+  #managesieve_logout_format = bytes=%i/%o
+
+  # To fool ManageSieve clients that are focused on CMU's timesieved you can
+  # specify the IMPLEMENTATION capability that Dovecot reports to clients.
+  # For example: 'Cyrus timsieved v2.2.13'
+  #managesieve_implementation_string = Dovecot Pigeonhole
+
+  # Explicitly specify the SIEVE and NOTIFY capability reported by the server
+  # before login. If left unassigned these will be reported dynamically
+  # according to what the Sieve interpreter supports by default (after login
+  # this may differ depending on the user).
+  #managesieve_sieve_capability =
+  #managesieve_notify_capability =
+
+  # The maximum number of compile errors that are returned to the client upon
+  # script upload or script verification.
+  #managesieve_max_compile_errors = 5
+
+  # Refer to 90-sieve.conf for script quota configuration and configuration of
+  # Sieve execution limits.
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/example-config/conf.d/90-sieve-extprograms.conf
@@ -0,0 +1,44 @@
+# Sieve Extprograms plugin configuration
+
+# Don't forget to add the sieve_extprograms plugin to the sieve_plugins setting.
+# Also enable the extensions you need (one or more of vnd.dovecot.pipe,
+# vnd.dovecot.filter and vnd.dovecot.execute) by adding these	to the
+# sieve_extensions or sieve_global_extensions settings. Restricting these
+# extensions to a global context using sieve_global_extensions is recommended.
+
+plugin {
+
+  # The directory where the program sockets are located for the
+  # vnd.dovecot.pipe, vnd.dovecot.filter and vnd.dovecot.execute extension
+  # respectively. The name of each unix socket contained in that directory
+  # directly maps to a program-name referenced from the Sieve script.
+  #sieve_pipe_socket_dir = sieve-pipe
+  #sieve_filter_socket_dir = sieve-filter
+  #sieve_execute_socket_dir = sieve-execute
+
+  # The directory where the scripts are located for direct execution by the
+  # vnd.dovecot.pipe, vnd.dovecot.filter and vnd.dovecot.execute extension
+  # respectively. The name of each script contained in that directory
+  # directly maps to a program-name referenced from the Sieve script.
+  #sieve_pipe_bin_dir = /usr/lib/dovecot/sieve-pipe
+  #sieve_filter_bin_dir = /usr/lib/dovecot/sieve-filter
+  #sieve_execute_bin_dir = /usr/lib/dovecot/sieve-execute
+}
+
+# An example program service called 'do-something' to pipe messages to
+#service do-something {
+  # Define the executed script as parameter to the sieve service
+  #executable = script /usr/lib/dovecot/sieve-pipe/do-something.sh
+
+  # Use some unprivileged user for executing the program
+  #user = dovenull
+
+  # The unix socket located in the sieve_pipe_socket_dir (as defined in the 
+  # plugin {} section above)
+  #unix_listener sieve-pipe/do-something {
+    # LDA/LMTP must have access
+  #  user = vmail  
+  #  mode = 0600
+  #}
+#}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/example-config/conf.d/90-sieve.conf
@@ -0,0 +1,214 @@
+##
+## Settings for the Sieve interpreter
+##
+
+# Do not forget to enable the Sieve plugin in 15-lda.conf and 20-lmtp.conf
+# by adding it to the respective mail_plugins= settings.
+
+# The Sieve interpreter can retrieve Sieve scripts from several types of
+# locations. The default `file' location type is a local filesystem path
+# pointing to a Sieve script file or a directory containing multiple Sieve
+# script files. More complex setups can use other location types such as
+# `ldap' or `dict' to fetch Sieve scripts from remote databases.
+#
+# All settings that specify the location of one ore more Sieve scripts accept
+# the following syntax:
+#
+# location = [<type>:]path[;<option>[=<value>][;...]]
+#
+# If the type prefix is omitted, the script location type is 'file' and the 
+# location is interpreted as a local filesystem path pointing to a Sieve script
+# file or directory. Refer to Pigeonhole wiki or INSTALL file for more
+# information.
+
+plugin {
+  # The location of the user's main Sieve script or script storage. The LDA
+  # Sieve plugin uses this to find the active script for Sieve filtering at
+  # delivery. The "include" extension uses this location for retrieving
+  # :personal" scripts. This is also where the  ManageSieve service will store
+  # the user's scripts, if supported.
+  # 
+  # Currently only the 'file:' location type supports ManageSieve operation.
+  # Other location types like 'dict:' and 'ldap:' can currently only
+  # be used as a read-only script source ().
+  #
+  # For the 'file:' type: use the ';active=' parameter to specify where the
+  # active script symlink is located.
+  # For other types: use the ';name=' parameter to specify the name of the
+  # default/active script.
+  sieve = file:~/sieve;active=~/.dovecot.sieve
+
+  # The default Sieve script when the user has none. This is the location of a
+  # global sieve script file, which gets executed ONLY if user's personal Sieve
+  # script doesn't exist. Be sure to pre-compile this script manually using the
+  # sievec command line tool if the binary is not stored in a global location.
+  # --> See sieve_before for executing scripts before the user's personal
+  #     script.
+  #sieve_default = /var/lib/dovecot/sieve/default.sieve
+
+  # The name by which the default Sieve script (as configured by the 
+  # sieve_default setting) is visible to the user through ManageSieve. 
+  #sieve_default_name = 
+
+  # Location for ":global" include scripts as used by the "include" extension.
+  #sieve_global =
+
+  # The location of a Sieve script that is run for any message that is about to
+  # be discarded; i.e., it is not delivered anywhere by the normal Sieve
+  # execution. This only happens when the "implicit keep" is canceled, by e.g.
+  # the "discard" action, and no actions that deliver the message are executed.
+  # This "discard script" can prevent discarding the message, by executing
+  # alternative actions. If the discard script does nothing, the message is
+	# still discarded as it would be when no discard script is configured.
+  #sieve_discard =
+
+  # Location Sieve of scripts that need to be executed before the user's
+  # personal script. If a 'file' location path points to a directory, all the 
+  # Sieve scripts contained therein (with the proper `.sieve' extension) are
+  # executed. The order of execution within that directory is determined by the
+  # file names, using a normal 8bit per-character comparison.
+  #
+  # Multiple script locations can be specified by appending an increasing number
+  # to the setting name. The Sieve scripts found from these locations are added
+  # to the script execution sequence in the specified order. Reading the
+  # numbered sieve_before settings stops at the first missing setting, so no
+  # numbers may be skipped.
+  #sieve_before = /var/lib/dovecot/sieve.d/
+  #sieve_before2 = ldap:/etc/sieve-ldap.conf;name=ldap-domain
+  #sieve_before3 = (etc...)
+
+  # Identical to sieve_before, only the specified scripts are executed after the
+  # user's script (only when keep is still in effect!). Multiple script
+  # locations can be specified by appending an increasing number.
+  #sieve_after =
+  #sieve_after2 =
+  #sieve_after2 = (etc...)
+
+  # Which Sieve language extensions are available to users. By default, all
+  # supported extensions are available, except for deprecated extensions or
+  # those that are still under development. Some system administrators may want
+  # to disable certain Sieve extensions or enable those that are not available
+  # by default. This setting can use '+' and '-' to specify differences relative
+  # to the default. For example `sieve_extensions = +imapflags' will enable the
+  # deprecated imapflags extension in addition to all extensions were already
+  # enabled by default.
+  #sieve_extensions = +notify +imapflags
+
+  # Which Sieve language extensions are ONLY available in global scripts. This
+  # can be used to restrict the use of certain Sieve extensions to administrator
+  # control, for instance when these extensions can cause security concerns.
+  # This setting has higher precedence than the `sieve_extensions' setting
+  # (above), meaning that the extensions enabled with this setting are never
+  # available to the user's personal script no matter what is specified for the
+  # `sieve_extensions' setting. The syntax of this setting is similar to the
+  # `sieve_extensions' setting, with the difference that extensions are
+  # enabled or disabled for exclusive use in global scripts. Currently, no
+  # extensions are marked as such by default.
+  #sieve_global_extensions =
+
+  # The Pigeonhole Sieve interpreter can have plugins of its own. Using this
+  # setting, the used plugins can be specified. Check the Dovecot wiki
+  # (wiki2.dovecot.org) or the pigeonhole website
+  # (http://pigeonhole.dovecot.org) for available plugins.
+  # The sieve_extprograms plugin is included in this release.
+  #sieve_plugins =
+
+  # The separator that is expected between the :user and :detail
+  # address parts introduced by the subaddress extension. This may
+  # also be a sequence of characters (e.g. '--'). The current
+  # implementation looks for the separator from the left of the
+  # localpart and uses the first one encountered. The :user part is
+  # left of the separator and the :detail part is right. This setting
+  # is also used by Dovecot's LMTP service.
+  #recipient_delimiter = +
+
+  # The maximum size of a Sieve script. The compiler will refuse to compile any
+  # script larger than this limit. If set to 0, no limit on the script size is
+  # enforced.
+  #sieve_max_script_size = 1M
+
+  # The maximum number of actions that can be performed during a single script
+  # execution. If set to 0, no limit on the total number of actions is enforced.
+  #sieve_max_actions = 32
+
+  # The maximum number of redirect actions that can be performed during a single
+  # script execution. If set to 0, no redirect actions are allowed.
+  #sieve_max_redirects = 4
+
+  # The maximum number of personal Sieve scripts a single user can have. If set
+  # to 0, no limit on the number of scripts is enforced.
+  # (Currently only relevant for ManageSieve)
+  #sieve_quota_max_scripts = 0
+
+  # The maximum amount of disk storage a single user's scripts may occupy. If
+  # set to 0, no limit on the used amount of disk storage is enforced.
+  # (Currently only relevant for ManageSieve)
+  #sieve_quota_max_storage = 0
+
+  # The primary e-mail address for the user. This is used as a default when no
+  # other appropriate address is available for sending messages. If this setting
+  # is not configured, either the postmaster or null "<>" address is used as a
+  # sender, depending on the action involved. This setting is important when
+  # there is no message envelope to extract addresses from, such as when the
+  # script is executed in IMAP.
+  #sieve_user_email =
+
+  # The path to the file where the user log is written. If not configured, a
+  # default location is used. If the main user's personal Sieve (as configured
+  # with sieve=) is a file, the logfile is set to <filename>.log by default. If
+  # it is not a file, the default user log file is ~/.dovecot.sieve.log.
+  #sieve_user_log =
+
+  # Specifies what envelope sender address is used for redirected messages.
+  # The following values are supported for this setting:
+  #
+  #   "sender"         - The sender address is used (default).
+  #   "recipient"      - The final recipient address is used.
+  #   "orig_recipient" - The original recipient is used.
+  #   "user_email"     - The user's primary address is used. This is
+  #                      configured with the "sieve_user_email" setting. If
+  #                      that setting is unconfigured, "user_mail" is equal to
+  #                      "recipient".
+  #   "postmaster"     - The postmaster_address configured for the LDA.
+  #   "<user@domain>"  - Redirected messages are always sent from user@domain.
+  #                      The angle brackets are mandatory. The null "<>" address
+  #                      is also supported.
+  #
+  # This setting is ignored when the envelope sender is "<>". In that case the
+  # sender of the redirected message is also always "<>".
+  #sieve_redirect_envelope_from = sender
+
+  ## TRACE DEBUGGING
+  # Trace debugging provides detailed insight in the operations performed by
+  # the Sieve script. These settings apply to both the LDA Sieve plugin and the
+  # IMAPSIEVE plugin. 
+  #
+  # WARNING: On a busy server, this functionality can quickly fill up the trace
+  # directory with a lot of trace files. Enable this only temporarily and as
+  # selective as possible.
+  
+  # The directory where trace files are written. Trace debugging is disabled if
+  # this setting is not configured or if the directory does not exist. If the 
+  # path is relative or it starts with "~/" it is interpreted relative to the
+  # current user's home directory.
+  #sieve_trace_dir =
+  
+  # The verbosity level of the trace messages. Trace debugging is disabled if
+  # this setting is not configured. Possible values are:
+  #
+  #   "actions"        - Only print executed action commands, like keep,
+  #                      fileinto, reject and redirect.
+  #   "commands"       - Print any executed command, excluding test commands.
+  #   "tests"          - Print all executed commands and performed tests.
+  #   "matching"       - Print all executed commands, performed tests and the
+  #                      values matched in those tests.
+  #sieve_trace_level =
+  
+  # Enables highly verbose debugging messages that are usually only useful for
+  # developers.
+  #sieve_trace_debug = no
+  
+  # Enables showing byte code addresses in the trace output, rather than only
+  # the source line numbers.
+  #sieve_trace_addresses = no 
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/example-config/conf.d/Makefile.am
@@ -0,0 +1,10 @@
+pkgsysconfdir = $(sysconfdir)/dovecot
+
+exampledir = $(dovecot_docdir)/example-config/conf.d
+example_DATA = \
+	20-managesieve.conf \
+	90-sieve.conf \
+	90-sieve-extprograms.conf
+
+EXTRA_DIST = \
+	$(example_DATA)
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/example-config/sieve-ldap.conf
@@ -0,0 +1,74 @@
+# This file needs to be accessible by the Sieve interpreter running in LDA/LMTP.
+# This requires acces by the mail user. Don't use privileged LDAP credentials
+# here as these may likely leak. Only search and read access is required.
+
+# Space separated list of LDAP hosts to use. host:port is allowed too.
+hosts = localhost
+
+# LDAP URIs to use. You can use this instead of hosts list. Note that this
+# setting isn't supported by all LDAP libraries.
+#uris = 
+
+# Distinguished Name - the username used to login to the LDAP server.
+# Leave it commented out to bind anonymously.
+#dn = 
+
+# Password for LDAP server, if dn is specified.
+#dnpass = 
+
+# Use SASL binding instead of the simple binding. Note that this changes
+# ldap_version automatically to be 3 if it's lower.
+#sasl_bind = no
+# SASL mechanism name to use.
+#sasl_mech =
+# SASL realm to use.
+#sasl_realm =
+# SASL authorization ID, ie. the dnpass is for this "master user", but the
+# dn is still the logged in user. Normally you want to keep this empty.
+#sasl_authz_id =
+
+# Use TLS to connect to the LDAP server.
+#tls = no
+# TLS options, currently supported only with OpenLDAP:
+#tls_ca_cert_file =
+#tls_ca_cert_dir =
+#tls_cipher_suite =
+# TLS cert/key is used only if LDAP server requires a client certificate.
+#tls_cert_file =
+#tls_key_file =
+# Valid values: never, hard, demand, allow, try
+#tls_require_cert =
+
+# Use the given ldaprc path.
+#ldaprc_path =
+
+# LDAP library debug level as specified by LDAP_DEBUG_* in ldap_log.h.
+# -1 = everything. You may need to recompile OpenLDAP with debugging enabled
+# to get enough output.
+#debug_level = 0
+
+# LDAP protocol version to use. Likely 2 or 3.
+#ldap_version = 3
+
+# LDAP base. %variables can be used here.
+# For example: dc=mail, dc=example, dc=org
+base =
+
+# Dereference: never, searching, finding, always
+#deref = never
+
+# Search scope: base, onelevel, subtree
+#scope = subtree
+
+# Filter for user lookup. Some variables can be used:
+#   %u      - username
+#   %n      - user part in user@domain, same as %u if there's no domain
+#   %d      - domain part in user@domain, empty if there's no domain
+#   %{name} - name of the Sieve script
+#sieve_ldap_filter = (&(objectClass=posixAccount)(uid=%u))
+
+# Attribute containing the Sieve script
+#sieve_ldap_script_attr = mailSieveRuleSource
+
+# Attribute used for modification tracking
+#sieve_ldap_mod_attr = modifyTimestamp
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/extensions/Makefile.am
@@ -0,0 +1,17 @@
+docfiles = \
+	duplicate.txt \
+	editheader.txt \
+	include.txt \
+	spamtest-virustest.txt \
+	vacation.txt \
+	vnd.dovecot.environment.txt \
+	vnd.dovecot.report.txt
+
+if BUILD_DOCS
+extensions_docdir = $(sieve_docdir)/extensions
+extensions_doc_DATA = $(docfiles)
+endif
+
+EXTRA_DIST = \
+	$(docfiles)
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/extensions/duplicate.txt
@@ -0,0 +1,48 @@
+Duplicate Extension
+
+Relevant specifications
+=======================
+
+	doc/rfc/duplicate.rfc7352.txt
+
+Description
+===========
+
+The "duplicate" extension adds a new test command called "duplicate" to the
+Sieve language. This test adds the ability to detect duplications. The main
+application for this new test is handling duplicate deliveries commonly caused
+by mailing list subscriptions or redirected mail addresses. The detection is
+normally performed by matching the message ID to an internal list of message
+IDs from previously delivered messages.  For more complex applications, the
+"duplicate" test can also use the content of a specific header field or other
+parts of the message.
+
+Refer to doc/rfc/duplicate.rfc7352.txt for a specification of the Sieve language
+extension. Previously, this extension was Dovecot-specific and available under
+the name "vnd.dovecot.duplicate". That implementation differs significantly from
+what is now published as an RFC, but for backwards compatibility the original
+extension is still supported.
+
+Configuration
+=============
+
+The "duplicate" extension is available by default. The "duplicate" extension has
+its own specific settings. The following settings are available (default
+values are indicated):
+
+sieve_duplicate_default_period = 14d
+sieve_duplicate_max_period = 7d
+  These options respectively specify the default and the maximum value for the
+  period after which tracked values are purged from the duplicate tracking
+  database. The period is specified in s(econds), unless followed by a d(ay),
+  h(our) or m(inute) specifier character.
+
+Example
+=======
+
+plugin {
+  sieve = ~/.dovecot.sieve
+
+  sieve_duplicate_default_period = 1h
+	sieve_duplicate_max_period = 1d
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/extensions/editheader.txt
@@ -0,0 +1,64 @@
+Editheader Extension
+
+Relevant specifications
+=======================
+
+  RFC5293 - doc/rfc/editheader.rfc5293.txt
+
+Description
+===========
+
+The editheader extension [RFC5293] enables Sieve scripts to delete and add
+message header fields, thereby allowing interaction with other components that
+consume or produce header fields.
+
+Configuration
+=============
+
+The editheader extension is not available by default and needs to be enabled
+explicitly by adding it to the sieve_extensions setting.
+
+The following settings can be configured for the editheader extension (default
+values are indicated):
+
+sieve_editheader_max_header_size = 2048
+  The maximum size in bytes of a header field value passed to the addheader
+  command. The minimum value for this setting is 1024 bytes. The value is in
+  bytes, unless followed by a k(ilo).
+
+sieve_editheader_forbid_add =
+  A space-separated list of headers that cannot be added to the message header.
+  Addition of the `Subject:' header cannot be prohibited, as required by the RFC
+  specification. Therefore, adding this header to this setting has no effect.
+
+sieve_editheader_forbid_delete =
+  A space-separated list of headers that cannot be deleted from the message
+  header. Deleting the `Received:' and `Auto-Submitted:' fields is always 
+  forbidden, while removing the `Subject:' header cannot be prohibited,
+  as required by the RFC specification. Therefore, adding one of these headers
+  to this setting has no effect.
+
+sieve_editheader_protected =
+  A space-separated list of headers that cannot be added to or deleted from
+  the message header. This setting is provided for backwards compatibility. It
+  is a combination of the sieve_editheader_forbid_add and
+  sieve_editheader_forbid_delete settings. The same limitations apply. 
+
+Invalid values for the settings above will make the Sieve interpreter log
+a warning and revert to the default values.
+
+Example
+=======
+
+plugin {
+  # Use editheader
+  sieve_extensions = +editheader
+
+  # Header fiels must not exceed one 1k
+  sieve_editheader_max_header_size = 1k
+
+  # Protected special headers
+  sieve_editheader_forbid_add = X-Verified
+  sieve_editheader_forbid_delete = X-Verified X-Seen
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/extensions/include.txt
@@ -0,0 +1,32 @@
+Include Extension
+
+Relevant Specifications
+=======================
+
+  draft-ietf-sieve-include-05 - doc/rfc/draft-ietf-sieve-include-05.txt
+
+Description
+===========
+
+The Sieve include extension permits users to include one Sieve script into
+another. This can make managing large scripts or multiple sets of scripts much
+easier, and allows a site and its users to build up libraries of scripts. Users
+are able to include their own personal scripts or site-wide scripts.
+
+Included scripts can include more scripts of their own, yielding a tree of
+included scripts with the main script (typically the user's personal script) at
+its root.
+
+Configuration
+=============
+
+The include extension is available by default. The include extension has its own
+specific settings. The following settings can be configured for the include
+extension (default values are indicated):
+
+sieve_include_max_includes = 255
+  The maximum number of scripts that may be included. This is the total number
+  of scripts involved in the include tree.
+
+sieve_include_max_nesting_depth = 10
+  The maximum nesting depth for the include tree.
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/extensions/spamtest-virustest.txt
@@ -0,0 +1,140 @@
+Spamtest and Virustest Extensions
+
+Relevant Specifications
+=======================
+
+  RFC5235 - doc/rfc/spamvirustest.rfc5235.txt
+
+Description
+===========
+
+Using the spamtest and virustest extensions (RFC 5235), the Sieve language
+provides a uniform and standardized command interface for evaluating spam and
+virus tests performed on the message. Users no longer need to know what headers
+need to be checked and how the scanner's verdict is represented in the header
+field value. They only need to know how to use the spamtest (spamtestplus) and
+virustest extensions. This also gives GUI-based Sieve editors the means to
+provide a portable and easy to install interface for spam and virus filter
+configuration. The burden of specifying which headers need to be checked and how
+the scanner output is represented falls onto the Sieve administrator.
+
+Configuration
+=============
+
+The spamtest, spamtestplus and virustest extensions are not enabled by default
+and thus need to be enabled explicitly using the sieve_extensions setting.
+
+The following settings need to be configured for using the spamtest and
+spamtestplus extensions. The virustest extension has identical configuration
+settings, but with a `sieve_virustest_' prefix instead of a `sieve_spamtest_'
+prefix:
+
+sieve_spamtest_status_type = "score" / "strlen" / "text"
+  This specifies the type of status result that the spam/virus scanner produces.
+  This can either be a numeric score ("score"), a string of identical characters
+  ("strlen"), e.g. '*******', or a textual description, e.g. `Spam'
+  or `Not Spam'.
+
+sieve_spamtest_status_header = <header-field> [ ":" <regexp> ]
+  This specifies the header field that contains the result information of the
+  spam scanner and it may express the syntax of the content of the header. If no
+  matching header is found in the message, the spamtest command will match
+  against "0".
+
+  This is a structured setting. The first part specifies the header field name.
+  Optionally, an extended POSIX regular expression follows the header field
+  name, separated by a colon. Any whitespace directly following the colon is not
+  part of the regular expression. If the regular expression is omitted, any
+  header content is accepted and the full header value is used. When a regular
+  expression is used, it must specify one match value (inside brackets) that
+  yields the desired spam scanner result. If the header does not match the
+  regular expression or if no value match is found, the spamtest will match
+  against "0".
+
+sieve_spamtest_max_value =
+  This statically specifies the maximum value a numeric spam score can have.
+
+sieve_spamtest_max_header = <header-field> [ ":" <regexp> ]
+  Some spam scanners include the maximum score value in one of their status
+  headers. Using this setting, this maximum can be extracted from the message
+  itself instead of specifying the maximum manually using the setting
+  `sieve_spamtest_max_value' explained above. The syntax is identical to the
+  `sieve_spamtext_status_header' setting.
+
+sieve_spamtest_text_valueX =
+  When the `sieve_spamtest_status_type' setting is set to "text", these settings
+  specify that the spamtest command will match against "X" when the specified
+  string is equal to the text (extracted) from the status header. For spamtest,
+  values of X between 0 and 10 are recognized, while virustest only uses values
+  between 0 and 5.
+
+Examples
+========
+
+This section shows several configuration examples. Each example shows a specimen
+of valid virus/spam test headers that the given configuration will work on.
+
+Example 1
+---------
+
+Spam header: `X-Spam-Score: No, score=-3.2'
+
+plugin {
+  sieve_extensions = +spamtest +spamtestplus
+
+  sieve_spamtest_status_type = score
+  sieve_spamtest_status_header = \
+    X-Spam-Score: [[:alnum:]]+, score=(-?[[:digit:]]+\.[[:digit:]])
+  sieve_spamtest_max_value = 5.0
+}
+
+Example 2
+---------
+
+Spam header: `X-Spam-Status: Yes'
+
+plugin {
+  sieve_extensions = +spamtest +spamtestplus
+
+  sieve_spamtest_status_type = text
+  sieve_spamtest_status_header = X-Spam-Status
+  sieve_spamtest_text_value1 = No
+  sieve_spamtest_text_value10 = Yes
+}
+
+Example 3
+---------
+
+Spam header: `X-Spam-Score: sssssss'
+
+plugin {
+  sieve_extensions = +spamtest +spamtestplus
+
+  sieve_spamtest_status_header = X-Spam-Score
+  sieve_spamtest_status_type = strlen
+  sieve_spamtest_max_value = 5
+}
+
+Example 4
+---------
+
+Spam header: `X-Spam-Score: status=3.2 required=5.0'
+Virus header: `X-Virus-Scan: Found to be clean.'
+
+plugin {
+  sieve_extensions = +spamtest +spamtestplus +virustest
+
+  sieve_spamtest_status_type = score
+  sieve_spamtest_status_header = \
+    X-Spam-Score: score=(-?[[:digit:]]+\.[[:digit:]]).*
+  sieve_spamtest_max_header = \
+    X-Spam-Score: score=-?[[:digit:]]+\.[[:digit:]] required=([[:digit:]]+\.[[:digit:]])
+
+  sieve_virustest_status_type = text
+  sieve_virustest_status_header = X-Virus-Scan: Found to be (.+)\.
+  sieve_virustest_text_value1 = clean
+  sieve_virustest_text_value5 = infected
+}
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/extensions/vacation.txt
@@ -0,0 +1,122 @@
+Vacation Extension
+
+Relevant specifications
+=======================
+
+  RFC5230 - doc/rfc/vacation.rfc5230.txt
+  RFC6131 - doc/rfc/vacation-seconds.rfc6131.txt
+
+Description
+===========
+
+The Sieve vacation extension [RFC5230] defines a mechanism to generate automatic
+replies to incoming email messages. It takes various precautions to make sure
+replies are only sent when appropriate. Script authors specify how often replies
+are sent to a particular contact. In the original vacation extension, this
+interval is specified in days with a minimum of one day. When more granularity
+is necessary and particularly when replies must be sent more frequently than one
+day, the vacation-seconds extension [RFC6131] can be used. This allows
+specifying the minimum reply interval in seconds with a minimum of zero (reply
+is then always sent), depending on administrator configuration.
+
+Configuration
+=============
+
+The vacation extension is available by default. In contrast, the
+vacation-seconds extension - which implies the vacation extension when used - is
+not available by default and needs to be enabled explicitly by adding it to the
+sieve_extensions setting. The configuration also needs to be adjusted
+accordingly to allow a non-reply period of less than a day.
+
+The vacation and vacation-seconds extensions have their own specific settings.
+The settings that specify a period are specified in s(econds), unless followed
+by a d(ay), h(our) or m(inute) specifier character.
+
+The following settings can be configured for the vacation extension (default
+values are indicated):
+
+sieve_vacation_min_period = 1d
+  This specifies the minimum period that can be specified for the :days and
+  :seconds tags of the vacation command. A minimum of 0 indicates that users are
+  allowed to make the Sieve interpreter send a vacation response message for
+  every incoming message that meets the other reply criteria (refer to RFC5230).
+  A value of zero is however not recommended.
+
+sieve_vacation_max_period = 0
+  This specifies the maximum period that can be specified for the :days tag of
+  the vacation command. The configured value must be larger than the
+  sieve_vacation_min_period setting. A value of 0 has a special meaning: it
+  indicates that there is no upper limit.
+
+sieve_vacation_default_period = 7d
+  This specifies the default period that is used when no :days or :seconds tag
+  is specified. The configured value must lie between the
+  sieve_vacation_min_period and sieve_vacation_max_period.
+
+sieve_vacation_max_subject_codepoints = 256
+  The maximum number of Unicode codepoints used in the Subject header generated
+  for the outgoing vacation message. When composite characters are involved,
+  the number of actual charactes in the Subject text can be less than this
+  number, otherwise it is equal. When the subject text exceeds the limit, it is
+  truncated and the removed part is replaced with an ellipsis character ('...').
+
+sieve_vacation_use_original_recipient = no
+  This specifies whether the original envelope recipient should be used in the
+  check for implicit delivery.  The vacation command checks headers of the
+  incoming message, such as To: and Cc: for the address of the recipient, to
+  verify that the message is explicitly addressed at the recipient. If the
+  recipient address is not found, the vacation action will not trigger a
+  response to prevent sending a reply when it is not appropriate. Normally only
+  the final recipient address is used in this check. This setting allows
+  including the original recipient specified in the SMTP session if available.
+  This is useful to handle mail accounts with aliases. Use this option with
+  caution: if you are using aliases that point to more than a single account,
+  senders can get multiple vacation responses for a single message.
+
+sieve_vacation_dont_check_recipient = no
+  This disables the checks for implicit delivery entirely. This means that the
+  vacation command does not verify that the message is explicitly addressed at
+  the recipient. Use this option with caution. Specifying 'yes' will violate the
+  Sieve standards and can cause vacation replies to be sent for messages not
+  directly addressed at the recipient.
+
+sieve_vacation_send_from_recipient = no
+  This setting determines whether vacation messages are sent with the SMTP MAIL
+  FROM envelope address set to the recipient address of the Sieve script owner.
+  Normally this is set to <>, which is the default as recommended in the
+  specification. This is meant to prevent mail loops. However, there are
+  situations for which a valid sender address is required and this setting can
+  be used to accommodate for those.
+
+sieve_vacation_to_header_ignore_envelope = no
+  With this setting disabled (the default), the "To:" header in the composed
+  vacation reply is determined by finding the envelope sender address in the
+  first "Sender:", "Resent-From:", or "From:" headers (in that order). The
+  matching address is used in the "To:" header of the reply, which then includes
+  the "phrase" part of the address; i.e., usually the name of the person
+  associated with that address. If no match is found, the bare envelope sender
+  address is used instead. In contrast, with this setting enabled, the envelope
+  is completely ignored for this purpose and the first address found from the
+  mentioned headers is always used. This is useful when the envelope sender is
+  mangled somehow; e.g. by the Sender Rewriting Scheme (SRS).
+
+Invalid values for the settings above will make the Sieve interpreter log a
+warning and revert to the default values.
+
+Example
+=======
+
+plugin {
+  # Use vacation-seconds
+  sieve_extensions = +vacation-seconds
+
+  # One hour at minimum
+  sieve_vacation_min_period = 1h
+
+  # Ten days default
+  sieve_vacation_min_period = 10d
+
+  # Thirty days at maximum
+  sieve_vacation_max_period = 30d
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/extensions/variables.txt
@@ -0,0 +1,31 @@
+Variables Extension
+
+Relevant Specifications
+=======================
+
+  RFC5229 - doc/rfc/variables.rfc5229.txt
+
+Description
+===========
+
+The Sieve "variables" extension adds the concept of variables to the Sieve
+language.
+
+Configuration
+=============
+
+The "variables" extension is available by default. The "variables" extension has
+its own specific settings. The following settings can be configured for the
+"variables" extension (default values are indicated):
+
+sieve_variables_max_scope_size = 255
+  The maximum number of variables that can be declared in a scope. There are
+  currently two variable scopes: the normal script scope and the global scope
+  created by the "include" extension. The minimum value for this setting is 128.
+
+sieve_variables_max_variable_size = 4k
+  The maximum allowed size for the value of a variable. If exceeded at runtime,
+  the value is always truncated to the configured maximum. The minimum value for
+  this setting is 4000 bytes. The value is in bytes, unless followed by a
+  k(ilo).
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/extensions/vnd.dovecot.environment.txt
@@ -0,0 +1,48 @@
+Vnd.dovecot.environment Extension
+
+Relevant specifications
+=======================
+
+  doc/rfc/spec-bosch-sieve-dovecot-environment.txt
+
+Description
+===========
+
+The "vnd.dovecot.environment" extension builds upon the existing standard
+"environment" extension, which allows Sieve scripts to access information about
+their execution context, such as the name and version of the Sieve interpreter
+implementation. The new "vnd.dovecot.environment" extension adds a few more
+environment items that can be accessed by Sieve scripts. Additionally, it makes
+the environment items available directly as variables [VARIABLES].
+
+Configuration
+=============
+
+The "vnd.dovecot.environment" extension is not available by default; it needs
+to be added to the sieve_extensions or (rather) the sieve_global extensions
+setting.
+
+Currently, the "vnd.dovecot.environment" extension has no specific settings.
+However, this extension adds environment items with a "vnd.dovecot.config."
+prefix that can be used to access part of the Dovecot configuration. An
+environment item named "vnd.dovecot.config.identifier" yields the value of a
+plugin setting called "sieve_env_identifier".
+
+Example
+=======
+
+With the following configuration:
+
+plugin {
+  sieve = ~/.dovecot.sieve
+
+  sieve_env_reject_reason = Please don't mail me.
+}
+
+The following script will reject the message with the configured reason:
+
+require "reject";
+require "variables";
+require "vnd.dovecot.environment";
+
+reject "${vnd.dovecot.config.reject_reason}";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/extensions/vnd.dovecot.report.txt
@@ -0,0 +1,54 @@
+Vnd.dovecot.report Extension
+
+Relevant specifications
+=======================
+
+  doc/rfc/spec-bosch-sieve-report.txt
+
+Description
+===========
+
+The "vnd.dovecot.report" extension provides the means to send Messaging Abuse
+Reporting Format (MARF) reports (RFC 5965). This format is intended for
+communications regarding email abuse and related issues.  The "report" command
+allows (partially) automating the exchange of these reports, which is
+particularly useful when the Sieve script is executed for an IMAP event
+(RFC 6785) that is triggered by direct user action.
+
+Configuration
+=============
+
+The "vnd.dovecot.report" extension is not available by default; it needs
+to be added to the sieve_extensions setting (or any of the alternatives).
+
+The "vnd.dovecot.report" extension has its own specific settings. The following
+settings can be configured for the vacation extension (default values are
+indicated):
+
+ sieve_report_from = postmaster
+   Specifies what address is used for the "From:" header field in reports.
+   The following values are supported for this setting:
+   
+     "postmaster"     - The postmaster_address configured for the LDA (default).
+     "sender"         - The sender address is used.
+     "recipient"      - The final recipient address is used.
+     "orig_recipient" - The original recipient is used.
+     "user_email"     - The user's primary address is used. This is
+                        configured with the "sieve_user_email" setting. If
+                        that setting is unconfigured, "user_mail" is equal to
+                        "recipient".
+     "<user@domain>"  - Redirected messages are always sent from user@domain.
+                        The angle brackets are mandatory. The null "<>" address
+                        not supported and interpreted as "postmaster".
+
+Invalid values for the settings above will make the Sieve interpreter log a
+warning and revert to the default values.
+
+Example
+=======
+
+plugin {
+  sieve = file:~/sieve;active=~/.dovecot.sieve
+
+  sieve_report_from = <reporter@example.com>
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/locations/Makefile.am
@@ -0,0 +1,13 @@
+docfiles = \
+	dict.txt \
+	file.txt \
+	ldap.txt
+
+if BUILD_DOCS
+locations_docdir = $(sieve_docdir)/locations
+locations_doc_DATA = $(docfiles)
+endif
+
+EXTRA_DIST = \
+	$(docfiles)
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/locations/dict.txt
@@ -0,0 +1,145 @@
+DICT Sieve Script Location Type
+
+Description
+===========
+
+This location type is used to retrieve Sieve scripts from a Dovecot dict lookup.
+Such dictionaries use a file or an SQL database as backend. Refer to the Dovecot
+dict documentation for more information on dict lookups.
+
+To retrieve a Sieve script from the dict database, two lookups are performed.
+First, the name of the Sieve script is queried from the dict path
+`/priv/sieve/name/<name>'. If the Sieve script exists, this yields a data ID
+which in turn points to the actual script text. The script text is subsequently
+queried from the dict path '/priv/sieve/data/<dict-id>'.
+
+The second query is only necessary when no compiled binary is available or when
+the script has changed and needs to be recompiled. The data ID is used to detect
+changes in the dict's underlying database. Changing a Sieve script in the
+database must be done by first making a new script data item with a new data ID.
+Then, the mapping from name to data ID must be changed to point to the new
+script text, thereby changing the data ID returned from the name lookup, i.e.
+the first query mentioned above. Script binaries compiled from Sieve scripts
+contained in a dict database record the data ID. While the data ID contained in
+the binary is identical to the one returned from the dict lookup, the binary is
+assumed up-to-date. When the returned data ID is different, the new script text
+is retrieved using the second query and compiled into a new binary containing
+the updated data ID.
+
+Note that, by default, compiled binaries are not stored at all for Sieve scripts
+retrieved from a dict database. The bindir= option needs to be specified in the
+location specification. Refer to the INSTALL file for more general information
+about configuration of script locations.
+
+Configuration
+=============
+
+The script location syntax is specified as follows:
+
+location = dict:<dict-uri>[;<option>[=<value>][;...]]
+
+The following additional options are recognized:
+
+  user=<username>
+    Overrides the user name used for the dict lookup. Normally, the name of the
+    user running the Sieve interpreter is used.
+
+If the name of the Script is left unspecified and not otherwise provided by the
+Sieve interpreter, the name defaults to `default'.
+
+Examples
+========
+
+Example 1
+---------
+
+This example is mainly useful for performing a quick test of the dict location
+configuration without configuring an actual (SQL) database. For this example, a
+very simple file dict is assumed to be contained in the file
+/etc/dovecot/sieve.dict:
+
+priv/sieve/name/keep
+1
+priv/sieve/name/discard
+2
+priv/sieve/name/spam
+3
+priv/sieve/data/1
+keep;
+priv/sieve/data/2
+discard;
+priv/sieve/data/3
+require ["fileinto", "mailbox"]; fileinto :create "spam";
+
+To use this file dict for the main active script, you can change the
+configuration as follows (e.g. in /etc/dovecot/conf.d/90-sieve.conf):
+
+plugin {
+  sieve = dict:file:/etc/dovecot/sieve.dict;name=keep;bindir=~/.sieve-bin
+}
+
+The Sieve script named "keep" is retrieved from the file dict as the main
+script. Binaries are stored in the ~/.sieve-bin directory.
+
+Example 2
+---------
+
+This example uses a PostgreSQL database. Our database contains the following
+table:
+
+CREATE TABLE user_sieve_scripts (
+  id integer,
+  username varchar(40),
+  script_name varchar(256),
+  script_data varchar(10240),
+
+  PRIMARY KEY (id),
+  UNIQUE(username, script_name)
+);
+
+We create a file /etc/dovecot/dict-sieve-sql.conf with the following content:
+
+connect = host=localhost dbname=dovecot user=dovecot password=password
+map {
+  pattern = priv/sieve/name/$script_name
+  table = user_sieve_scripts
+  username_field = username
+  value_field = id
+  fields {
+    script_name = $script_name
+  }
+}
+map {
+  pattern = priv/sieve/data/$id
+  table = user_sieve_scripts
+  username_field = username
+  value_field = script_data
+  fields {
+    id = $id
+  }
+}
+
+These are the mappings used by the SQL dict. The first mapping is the name query
+that yields the id of the Sieve script. The second mapping is the query used to
+retrieve the Sieve script itself.
+
+Much like the dict configuration for mailbox quota, it is often not possible to
+directly use an SQL dict because the SQL drivers are not linked to binaries such
+as dovecot-lda and lmtp. You need to use the dict proxy service. Add the dict
+URI to the dict section (typically located in your main dovecot.conf):
+
+dict {
+  sieve = pgsql:/etc/dovecot/dict-sieve-sql.conf.ext
+}
+
+To use this SQL dict for the main active script, you can change the
+configuration as follows (e.g. in /etc/dovecot/conf.d/90-sieve.conf):
+
+plugin {
+  sieve = dict:proxy::sieve;name=active;bindir=~/.sieve-bin
+}
+
+This uses the proxy dict uri `proxy::sieve'. This refers to the `sieve =' entry
+in the dict {...} section above. With this configuration, a Sieve script called
+"main" is retrieved from the SQL dict. Binaries are stored in the ~/.sieve-bin
+directory.
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/locations/file.txt
@@ -0,0 +1,48 @@
+FILE Sieve Script Location Type
+
+Description
+===========
+
+This location type is used to retrieve Sieve scripts from the file system. The
+location can either point to a directory or to a regular file. If the location
+points to a directory, a script called "name" is retrieved by reading a file
+from that directory with the file name "name.sieve".
+
+When this location type is involved in a sieve_before/sieve_after script
+sequence and the location points to a directory, all files in that directory
+with a ".sieve" extension are part of the sequence. The sequence order of the
+scripts in that directory is determined by the file names, using a normal 8bit
+per-character comparison.
+ 
+Unless overridden using the `bindir=<path>' location option, compiled binaries
+for scripts retrieved from the `file' location type are by default stored in the
+same directory as where the script file was found if possible. Refer to the
+INSTALL file for more general information about configuration of script
+locations.
+
+Configuration
+=============
+
+The script location syntax is specified as follows:
+
+location = file:<path>[;<option>[=<value>][;...]]
+
+The following additional options are recognized:
+
+  active=<path>
+    When ManageSieve is used, one script in the storage can be active; i.e.,
+    evaluated at delivery. For the `file' location, the active script in the
+    storage directory is pointed to by a symbolic link. This option configures
+    where this symbolic link is located. If the `file' location path points to
+    a regular file, this setting has no effect (and ManageSieve cannot be used).
+
+Example
+=======
+
+plugin {
+	...
+  sieve = file:~/sieve;active=~/.dovecot.sieve
+
+  sieve_default = file:/var/lib/dovecot/;name=default
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/locations/ldap.txt
@@ -0,0 +1,73 @@
+LDAP Sieve Script Location Type
+
+Description
+===========
+
+This location type is used to retrieve Sieve scripts from an LDAP database. To
+retrieve a Sieve script from the LDAP database, at most two lookups are
+performed. First, the LDAP entry containing the Sieve script is searched using
+the specified LDAP search filter. If the LDAP entry changed since it was last
+retrieved (or it was never retieved before), the attribute containing the actual
+Sieve script is retrieved in a second lookup. In the first lookup, a special
+attribute is read and checked for changes. Usually, this is the
+`modifyTimestamp' attribute, but an alternative can be configured.
+
+Note that, by default, compiled binaries are not stored at all for Sieve scripts
+retrieved from an LDAP database. The bindir= option needs to be specified in the
+location specification. Refer to the INSTALL file for more general information
+about configuration of script locations.
+
+Depending on how Pigeonhole was configured and compiled (refer to INSTALL file
+for more information), LDAP support may only be available from a plugin called
+`sieve_storage_ldap'.
+
+Configuration
+=============
+
+If support for the LDAP location type is compiled as a plugin, it needs to be
+added to the sieve_plugins setting before it can be used, e.g.:
+
+sieve_plugins = sieve_storage_ldap
+
+The script location syntax is specified as follows:
+
+location = ldap:<config-file>[;<option>[=<value>][;...]]
+
+The <config-file> is a filesystem path that points to a configuration file
+containing the actual configuration for this LDAP script location.
+
+The following additional location options are recognized:
+
+  user=<username>
+    Overrides the user name used for the lookup. Normally, the name of the
+    user running the Sieve interpreter is used.
+
+If the name of the Script is left unspecified and not otherwise provided by the
+Sieve interpreter, the name defaults to `default'.
+
+The configuration file is based on the auth userdb/passdb LDAP configuration
+(refer to Dovecot wiki at http://wiki2.dovecot.org/AuthDatabase/LDAP). The
+following options are specific to the Sieve ldap location type:
+
+  sieve_ldap_filter = (&(objectClass=posixAccount)(uid=%u))
+    The LDAP search filter that is used to find the entry containing the Sieve
+    script.
+
+  sieve_ldap_script_attr = mailSieveRuleSource
+    The name of the attribute containing the Sieve script itself.
+
+  sieve_ldap_mod_attr = modifyTimestamp
+    The name of the attribute used to detect modifications to the LDAP entry.
+	
+Examples
+========
+
+plugin {
+  sieve = ldap:/etc/dovecot/sieve-ldap.conf;bindir=~/.sieve-bin/
+}
+
+An example LDAP location configuration is available in this package as
+doc/example-config/sieve-ldap.conf.
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/man/Makefile.am
@@ -0,0 +1,46 @@
+pkgsysconfdir = $(sysconfdir)/dovecot
+rundir = ${prefix}/var/run/dovecot
+
+SUFFIXES = .1.in .1 .7.in .7
+
+dist_man1_MANS = \
+	sieved.1
+
+nodist_man1_MANS = \
+	doveadm-sieve.1 \
+	sievec.1 \
+	sieve-dump.1 \
+	sieve-test.1 \
+	sieve-filter.1
+
+nodist_man7_MANS = \
+	pigeonhole.7
+
+man_includefiles = \
+	$(srcdir)/global-options-formatter.inc \
+	$(srcdir)/global-options.inc \
+	$(srcdir)/option-A.inc \
+	$(srcdir)/option-S-socket.inc \
+	$(srcdir)/option-u-user.inc \
+	$(srcdir)/reporting-bugs.inc
+
+EXTRA_DIST = \
+	doveadm-sieve.1.in \
+	sievec.1.in \
+	sieve-dump.1.in \
+	sieve-test.1.in \
+	sieve-filter.1.in \
+	pigeonhole.7.in \
+	sed.sh \
+	$(man_includefiles)
+
+CLEANFILES = $(nodist_man1_MANS) $(nodist_man7_MANS)
+
+.1.in.1: $(man_includefiles) Makefile
+	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
+		$(pkglibexecdir) < $< > $@
+.7.in.7: $(man_includefiles) Makefile
+	$(SHELL) $(srcdir)/sed.sh $(srcdir) $(rundir) $(pkgsysconfdir) \
+		$(pkglibexecdir) < $< > $@
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/man/doveadm-sieve.1.in
@@ -0,0 +1,125 @@
+.\" Copyright (c) 2010-2018 Pigeonhole authors, see the included COPYING file
+.TH DOVEADM\-SIEVE 1 "2017-12-18" "Pigeonhole v0.5 for Dovecot v2.3" "Pigeonhole"
+.SH NAME
+doveadm\-sieve \- Commands related to handling Sieve scripts
+.\"------------------------------------------------------------------------
+.SH SYNOPSIS
+.BR doveadm " [" \-Dv "] [" \-f
+.IR formatter "] " sieve_cmd " [" options "] [" arguments ]
+.\"------------------------------------------------------------------------
+.SH DESCRIPTION
+.PP
+The
+.B doveadm sieve
+commands are part of the Pigeonhole Project (\fBpigeonhole\fR(7)), which adds
+Sieve (RFC 5228) and ManageSieve (RFC 5804) support to the Dovecot secure IMAP
+and POP3 server (\fBdovecot\fR(1)). The
+.B doveadm sieve
+commands can be used to manage Sieve filtering.
+.\"------------------------------------------------------------------------
+@INCLUDE:global-options-formatter@
+.\" --- command specific options --- "/.
+.PP
+Command specific
+.IR options :
+.\"-------------------------------------
+@INCLUDE:option-A@
+.\"-------------------------------------
+@INCLUDE:option-S-socket@
+.\"-------------------------------------
+@INCLUDE:option-u-user@
+.\"------------------------------------------------------------------------
+.SH ARGUMENTS
+.TP
+.I scriptname
+Is the name of a
+.IR Sieve\ script ,
+as visible to ManageSieve clients.
+.IP
+NOTE: For Sieve scripts that are stored on disk, this is the filename without the
+".sieve" extension.
+.\"------------------------------------------------------------------------
+.SH COMMANDS
+.SS sieve put
+.B doveadm sieve put
+[\fB\-A\fP|\fB\-u\fP \fIuser\fP]
+[\fB\-S\fP \fIsocket_path\fP]
+.RB [ \-a ]
+.IR scriptname
+.PP
+This command puts one new Sieve script in the script storage. The script
+is read from standard input. If the script compiles successfully, it is stored
+under the provided 
+.IR scriptname\ . 
+If the
+.B \-a
+option is present, the Sieve script is subsequently marked as the active script
+for execution at delivery.
+.\"------------------------------------------------------------------------
+.SS sieve get
+.B doveadm sieve get
+[\fB\-A\fP|\fB\-u\fP \fIuser\fP]
+[\fB\-S\fP \fIsocket_path\fP]
+.I scriptname
+.PP
+This command retrieves the Sieve script named
+.IR scriptname .
+.\"------------------------------------------------------------------------
+.SS sieve delete
+.B doveadm sieve delete
+[\fB\-A\fP|\fB\-u\fP \fIuser\fP]
+[\fB\-S\fP \fIsocket_path\fP]
+.RB [ \-a ]
+.IR scriptname\  ...
+.PP
+This command deletes one or more Sieve scripts. The deleted script may not be the
+active script, unless the 
+.B \-a
+option is present.
+.\"------------------------------------------------------------------------
+.SS sieve list
+.B doveadm sieve list
+[\fB\-A\fP|\fB\-u\fP \fIuser\fP]
+[\fB\-S\fP \fIsocket_path\fP]
+.I scriptname
+.PP
+Use this command to get an overview of existing Sieve scripts.
+.\"------------------------------------------------------------------------
+.SS sieve rename
+.B doveadm sieve rename
+[\fB\-A\fP|\fB\-u\fP \fIuser\fP]
+[\fB\-S\fP \fIsocket_path\fP]
+.I old_name
+.I new_name
+.PP
+The
+.B sieve rename
+command is used to rename the Sieve script
+.I old_name
+to
+.IR new_name .
+.\"------------------------------------------------------------------------
+.SS sieve activate
+.B doveadm sieve activate
+[\fB\-A\fP|\fB\-u\fP \fIuser\fP]
+[\fB\-S\fP \fIsocket_path\fP]
+.IR scriptname
+.PP
+This command marks the Sieve script named 
+.I scriptname
+as the active script for execution at delivery.
+.\"------------------------------------------------------------------------
+.SS sieve deactivate
+.B doveadm sieve deactivate
+[\fB\-A\fP|\fB\-u\fP \fIuser\fP]
+[\fB\-S\fP \fIsocket_path\fP]
+.I scriptname
+.PP
+This command deactivates Sieve processing.
+.\"------------------------------------------------------------------------
+@INCLUDE:reporting-bugs@
+.\"------------------------------------------------------------------------
+.SH SEE ALSO
+.BR doveadm (1)
+.BR dovecot\-lda (1),
+.BR pigeonhole (7)
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/man/global-options-formatter.inc
@@ -0,0 +1,46 @@
+.SH OPTIONS
+Global
+.BR doveadm (1)
+.IR options :
+.TP
+.B \-D
+Enables verbosity and debug messages.
+.TP
+.BI \-f\  formatter
+Specifies the
+.I formatter
+for formatting the output.
+Supported formatters are:
+.RS
+.TP
+.B flow
+prints each line with
+.IB key = value
+pairs.
+.TP
+.B pager
+prints each
+.IR key :\  value
+pair on its own line and separates records with form feed character
+.RB ( ^L ).
+.TP
+.B tab
+prints a table header followed by tab separated value lines.
+.TP
+.B table
+prints a table header followed by adjusted value lines.
+.RE
+.TP
+.BI \-o\  setting = value
+Overrides the configuration
+.I setting
+from
+.I @pkgsysconfdir@/dovecot.conf
+and from the userdb with the given
+.IR value .
+In order to override multiple settings, the
+.B \-o
+option may be specified multiple times.
+.TP
+.B \-v
+Enables verbosity, including progress counter.
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/man/global-options.inc
@@ -0,0 +1,21 @@
+.SH OPTIONS
+Global
+.BR doveadm (1)
+.IR options :
+.TP
+.B \-D
+Enables verbosity and debug messages.
+.TP
+.BI \-o\  setting = value
+Overrides the configuration
+.I setting
+from
+.I @pkgsysconfdir@/dovecot.conf
+and from the userdb with the given
+.IR value .
+In order to override multiple settings, the
+.B \-o
+option may be specified multiple times.
+.TP
+.B \-v
+Enables verbosity, including progress counter.
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/man/option-A.inc
@@ -0,0 +1,27 @@
+.TP
+.B \-A
+If the
+.B \-A
+option is present, the
+.I command
+will be performed for all users.
+Using this option in combination with system users from
+.B userdb { driver = passwd }
+is not recommended, because it contains also users with a lower UID than
+the one configured with the
+.I first_valid_uid
+setting.
+.sp
+When the SQL userdb module is used make sure that the
+.I iterate_query
+setting in
+.I @pkgsysconfdir@/dovecot\-sql.conf.ext
+matches your database layout.
+When using the LDAP userdb module, make sure that the
+.IR iterate_attrs " and " iterate_filter
+settings in
+.I @pkgsysconfdir@/dovecot-ldap.conf.ext
+match your LDAP schema.
+Otherwise
+.BR doveadm (1)
+will be unable to iterate over all users.
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/man/option-S-socket.inc
@@ -0,0 +1,10 @@
+.TP
+.BI \-S\  socket_path
+The option\(aqs argument is either an absolute path to a local UNIX domain
+socket, or a hostname and port
+.RI ( hostname : port ),
+in order to connect a remote host via a TCP socket.
+.sp
+This allows an administrator to execute
+.BR doveadm (1)
+mail commands through the given socket.
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/man/option-u-user.inc
@@ -0,0 +1,20 @@
+.TP
+.BI \-u\  user/mask
+Run the
+.I command
+only for the given
+.IR user .
+It\(aqs also possible to use
+.RB \(aq * \(aq
+and
+.RB \(aq ? \(aq
+wildcards (e.g. \-u *@example.org).
+.br
+When neither the
+.B \-A
+option nor
+.BI \-u\  user
+was specified, the
+.I command
+will be executed with the environment of the
+currently logged in user.
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/man/pigeonhole.7.in
@@ -0,0 +1,99 @@
+.\" Copyright (c) 2010-2018 Pigeonhole authors, see the included COPYING file
+.TH "PIGEONHOLE" 7 "2017-12-18" "Pigeonhole v0.5 for Dovecot v2.3" "Pigeonhole"
+.\"------------------------------------------------------------------------
+.SH NAME
+pigeonhole \- Overview of the Pigeonhole project\(aqs Sieve support for the
+Dovecot secure IMAP and POP3 server
+.\"------------------------------------------------------------------------
+.SH DESCRIPTION
+.PP
+The Pigeonhole project <http://pigeonhole.dovecot.org> adds support for the
+Sieve language (RFC 5228) and the ManageSieve protocol (RFC 5804) to the
+Dovecot Secure IMAP and POP3 Server (\fBdovecot\fR(1)). In the literal sense,
+a pigeonhole is a a hole or recess inside a dovecot for pigeons to nest in.
+It is, however, also the name for one of a series of small, open compartments
+in a cabinet used for filing or sorting mail. As a verb, it describes the act
+of putting an item into one of those pigeonholes. The name \(dqPigeonhole\(dq
+therefore well describes an important part of the functionality that this
+project adds to Dovecot: sorting and filing e\-mail messages.
+.PP
+The Sieve language is used to specify how e\-mail needs to be processed. By
+writing Sieve scripts, users can customize how messages are delivered, e.g.
+whether they are forwarded or stored in special folders. Unwanted messages can
+be discarded or rejected, and, when the user is not available, the Sieve
+interpreter can send an automated reply. Above all, the Sieve language is meant
+to be simple, extensible and system independent. And, unlike most other mail
+filtering script languages, it does not allow users to execute arbitrary
+programs. This is particularly useful to prevent virtual users from having full
+access to the mail store. The intention of the language is to make it impossible
+for users to do anything more complex (and dangerous) than write simple mail
+filters.
+.PP
+Using the ManageSieve protocol, users can upload their Sieve scripts remotely,
+without needing direct filesystem access through FTP or SCP. Additionally, a
+ManageSieve server always makes sure that uploaded scripts are valid, preventing
+compile failures at mail delivery.
+.PP
+The Pigeonhole project provides the following items:
+.IP \(bu 4
+The LDA Sieve plugin for Dovecot\(aqs Local Delivery Agent (LDA)
+(\fBdovecot\-lda\fR(1)) that facilitates the actual Sieve filtering upon
+delivery.
+.IP \(bu
+The ManageSieve service that implements the ManageSieve protocol through which
+users can remotely manage Sieve scripts on the server.
+.IP \(bu
+A plugin for Dovecot\(aqs
+.BR doveadm (1)
+command line tool that adds new the new
+.BR doveadm-sieve (1)
+commands for management of Sieve filtering.
+.PP
+The functionality and configuration of the LDA Sieve plugin and the ManageSieve
+service is described in detail in the README and INSTALL files contained in the
+Pigeonhole package and in the Dovecot Wiki
+<http://wiki2.dovecot.org/Pigeonhole>.
+.PP
+The following command line tools are available outside of
+.BR doveadm :
+.TP
+.BR sievec (1)
+Compiles Sieve scripts into a binary representation for later execution.
+.TP
+.BR sieve\-test (1)
+The universal Sieve test tool for testing the effect of a Sieve script on a
+particular message.
+.TP
+.BR sieve\-filter (1)
+Filters all messages in a particular source mailbox through a Sieve script.
+.TP
+.BR sieve\-dump (1)
+Dumps the content of a Sieve binary file for (development) debugging purposes.
+.\"------------------------------------------------------------------------
+@INCLUDE:reporting-bugs@
+.\"------------------------------------------------------------------------
+.SH AUTHOR
+Pigeonhole <http://pigeonhole.dovecot.org> and its manual pages were written by
+the Pigeonhole authors <http://pigeonhole.dovecot.org/doc/AUTHORS>, mainly
+Stephan Bosch <stephan at rename\-it.nl>, and are licensed under the terms of the
+LGPLv2.1 license, which is the same license as Dovecot, see
+<http://dovecot.org/doc/COPYING> for details.
+.\"------------------------------------------------------------------------
+.SH "SEE ALSO"
+.BR dovecot (1),
+.BR dovecot\-lda (1),
+.BR doveadm (1),
+.BR doveadm-sieve (1),
+.BR sieve\-dump (1),
+.BR sieve\-test (1),
+.BR sieve\-filter (1),
+.BR sievec (1)
+.\"-------------------------------------
+.PP
+Additional resources:
+.IP "Dovecot website"
+http://www.dovecot.org
+.IP "Dovecot v2.x Wiki"
+http://wiki2.dovecot.org/Pigeonhole
+.IP "Pigeonhole website"
+http://pigeonhole.dovecot.org
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/man/reporting-bugs.inc
@@ -0,0 +1,6 @@
+.SH REPORTING BUGS
+Report bugs, including
+.I doveconf \-n
+output, to the Dovecot Mailing List <dovecot@dovecot.org>.
+Information about reporting bugs is available at:
+http://dovecot.org/bugreport.html
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/man/sed.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+SRCDIR="${1:-`pwd`}"
+RUNDIR="${2:-/usr/local/var/run/dovecot}"
+PKGSYSCONFDIR="${3:-/usr/local/etc/dovecot}"
+PKGLIBEXECDIR="${4:-/usr/local/libexec/dovecot}"
+
+sed -e "/^@INCLUDE:global-options@$/{
+		r ${SRCDIR}/global-options.inc
+		d
+	}" \
+	-e "/^@INCLUDE:global-options-formatter@$/{
+		r ${SRCDIR}/global-options-formatter.inc
+		d
+	}" \
+	-e "/^@INCLUDE:option-A@$/{
+		r ${SRCDIR}/option-A.inc
+		d
+	}" \
+	-e "/^@INCLUDE:option-S-socket@$/{
+		r ${SRCDIR}/option-S-socket.inc
+		d
+	}" \
+	-e "/^@INCLUDE:option-u-user@$/{
+		r ${SRCDIR}/option-u-user.inc
+		d
+	}" \
+	-e "/^@INCLUDE:reporting-bugs@$/{
+		r ${SRCDIR}/reporting-bugs.inc
+		d
+	}" | sed -e "s|@pkgsysconfdir@|${PKGSYSCONFDIR}|" \
+	-e "s|@rundir@|${RUNDIR}|" \
+	-e "s|@pkglibexecdir@|${PKGLIBEXECDIR}|"
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/man/sieve-dump.1.in
@@ -0,0 +1,122 @@
+.\" Copyright (c) 2010-2018 Pigeonhole authors, see the included COPYING file
+.TH "SIEVE\-DUMP" 1 "2017-12-18" "Pigeonhole v0.5 for Dovecot v2.3" "Pigeonhole"
+.\"------------------------------------------------------------------------
+.SH NAME
+sieve\-dump \- Pigeonhole\(aqs Sieve script binary dump tool
+.\"------------------------------------------------------------------------
+.SH SYNOPSIS
+.B sieve\-dump
+.RI [ options ]
+.I sieve\-binary
+.RI [ out\-file ]
+.\"------------------------------------------------------------------------
+.SH DESCRIPTION
+.PP
+The \fBsieve\-dump\fP command is part of the Pigeonhole Project
+(\fBpigeonhole\fR(7)), which adds Sieve (RFC 5228) support to the Dovecot
+secure IMAP and POP3 server (\fBdovecot\fR(1)).
+.PP
+Using the \fBsieve\-dump\fP command, Sieve binaries, which are produced for
+instance by \fBsievec\fP(1), can be transformed into a human\-readable textual
+representation. This can provide valuable insight in how the Sieve script is
+executed. This is also particularly useful to view corrupt binaries that can
+result from bugs in the Sieve implementation. This tool is intended mainly for
+development purposes, so normally system administrators and users will not need
+to use this tool.
+.PP
+The format of the output is not explained here in detail, but it should be
+relatively easy to understand. The Sieve binaries comprise a set of data blocks,
+each of which can contain arbitrary data. For the base language implementation
+two blocks are used: the first containing a specification of all required
+language extensions and the second containing the main Sieve program. Compiled
+Sieve programs are represented as flat byte code and therefore the dump of the
+main program is a disassembly listing of the interpreter operations. Extensions
+can define new operations and use additional blocks. Therefore, the output of
+\fBsieve\-dump\fP depends greatly on the language extensions used when compiling
+the binary.
+.\"------------------------------------------------------------------------
+.SH OPTIONS
+.TP
+.BI \-c\  config\-file
+Alternative Dovecot configuration file path.
+.TP
+.B \-D
+Enable Sieve debugging.
+.TP
+.B \-h
+Produce per\-block hexdump output of the whole binary instead of the normal
+human\-readable output.
+.TP
+.BI \-o\  setting = value
+Overrides the configuration
+.I setting
+from
+.I @pkgsysconfdir@/dovecot.conf
+and from the userdb with the given
+.IR value .
+In order to override multiple settings, the
+.B \-o
+option may be specified multiple times.
+.TP
+.BI \-u\  user
+Run the Sieve script for the given \fIuser\fP. When omitted, the
+.I command
+will be executed with the environment of the currently logged in user.
+.TP
+.BI \-x\  extensions
+Set the available extensions. The parameter is a space\-separated list of the
+active extensions. By prepending the extension identifiers with \fB+\fP or
+\fB\-\fP, extensions can be included or excluded relative to the configured set
+of active extensions. If no extensions have a \fB+\fP or \fB\-\fP prefix, only
+those extensions that are explicitly listed will be enabled. Unknown extensions
+are ignored and a warning is produced.
+
+For example \fB\-x\fP \(dq+imapflags \-enotify\(dq will enable the deprecated
+imapflags extension and disable the enotify extension. The rest of the active
+extensions depends on the \fIsieve_extensions\fP and
+\fIsieve_global_extensions\fP settings. By default, i.e.
+when \fIsieve_extensions\fP and \fIsieve_global_extensions\fP remain
+unconfigured, all supported extensions are available, except for deprecated
+extensions or those that are still under development.
+
+.\"------------------------------------------------------------------------
+.SH ARGUMENTS
+.TP
+.I sieve\-binary
+Specifies the Sieve binary file that needs to be dumped.
+.TP
+.I out\-file
+Specifies where the output must be written. This argument is optional. If
+omitted, the output is written to \fBstdout\fR.
+.\"------------------------------------------------------------------------
+.SH "EXIT STATUS"
+.B sieve\-dump
+will exit with one of the following values:
+.TP 4
+.B 0
+Dump was successful. (EX_OK, EXIT_SUCCESS)
+.TP
+.B 1
+Operation failed. This is returned for almost all failures.
+(EXIT_FAILURE)
+.TP
+.B 64
+Invalid parameter given. (EX_USAGE)
+.\"------------------------------------------------------------------------
+.SH FILES
+.TP
+.I @pkgsysconfdir@/dovecot.conf
+Dovecot\(aqs main configuration file.
+.TP
+.I @pkgsysconfdir@/conf.d/90\-sieve.conf
+Sieve interpreter settings (included from Dovecot\(aqs main configuration file)
+.\"------------------------------------------------------------------------
+@INCLUDE:reporting-bugs@
+.\"------------------------------------------------------------------------
+.SH "SEE ALSO"
+.BR dovecot (1),
+.BR dovecot\-lda (1),
+.BR sieve\-filter (1),
+.BR sieve\-test (1),
+.BR sievec (1),
+.BR pigeonhole (7)
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/man/sieve-filter.1.in
@@ -0,0 +1,253 @@
+.\" Copyright (c) 2010-2018 Pigeonhole authors, see the included COPYING file
+.TH "SIEVE\-FILTER" 1 "2017-12-18" "Pigeonhole v0.5 for Dovecot v2.3" "Pigeonhole"
+.SH NAME
+sieve\-filter \- Pigeonhole\(aqs Sieve mailbox filter tool
+
+.PP
+\fBWARNING: \fRThis tool is still experimental. Read this manual carefully, and
+backup any important mail before using this tool. Also note that some of the
+features documented here are not actually implemented yet; this is clearly
+indicated where applicable.
+.\"------------------------------------------------------------------------
+.SH SYNOPSIS
+.B sieve\-filter
+.RI [ options ]
+.I script\-file
+.I source\-mailbox
+.RI [ discard\-action ]
+.SH DESCRIPTION
+.PP
+The \fBsieve\-filter\fP command is part of the Pigeonhole Project
+(\fBpigeonhole\fR(7)), which adds Sieve (RFC 5228) support to the Dovecot
+secure IMAP and POP3 server (\fBdovecot\fR(1)).
+.PP
+The Sieve language was originally meant for filtering messages upon delivery.
+However, there are occasions when it is desirable to filter messages that are
+already stored in a mailbox, for instance when a bug in a Sieve script caused
+many messages to be delivered incorrectly. Using the sieve\-filter tool it is
+possible to apply a Sieve script on all messages in a particular
+\fIsource\-mailbox\fP, making it possible to delete messages, to store them in a
+different mailbox, to change their content, and to change the assigned IMAP
+flags and keywords. Attempts to send messages to the outside world are ignored
+by default for obvious reasons, but, using the proper command line options, it
+is possible to capture and handle outgoing mail as well.
+.PP
+If no options are specified, the sieve\-filter command runs in a simulation mode
+in which it only prints what would be performed, without actually doing
+anything. Use the \fB\-e\fP option to activate true script execution. Also, the
+\fIsource\-mailbox\fP is opened read\-only by default, meaning that it normally
+always remains unchanged. Use the \fB\-W\fP option to allow changes in the
+\fIsource\-mailbox\fP.
+.PP
+Even with the \fB\-W\fP option enabled, messages in the \fIsource\-mailbox\fP
+are only potentially modified or moved to a different folder. Messages are never
+lost unless a \fIdiscard\-action\fP argument other than \fBkeep\fP (the default)
+is specified. If the Sieve filter decides to store the message in the
+\fIsource\-mailbox\fP, where it obviously already exists, it is never duplicated
+there. In that case, the IMAP flags of the original message can be modified by
+the Sieve interpreter using the \fIimap4flags\fP extension, provided that
+\fB\-W\fP is specified. If the message itself is modified by the Sieve
+interpreter (e.g. using the \fIeditheader\fP extension), a new message is stored
+and the old one is expunged. However, if \fB-W\fP is omitted, the original
+message is left untouched and the modifications are discarded.
+
+.SS CAUTION
+Although this is a very useful tool, it can also be very destructive when used
+improperly. A small bug in your Sieve script in combination with the wrong
+command line options could cause it to discard the wrong e\-mails. And, even if
+the \fIsource\-mailbox\fP is opened in read\-only mode to prevent such mishaps,
+it can still litter other mailboxes with spurious copies of your e\-mails if
+your Sieve script decides to do so. Therefore, users are advised to read this
+manual carefully and to use the simulation mode first to check what the script
+will do. And, of course:
+.PP
+\fBMAKING A BACKUP IS IMPERATIVE FOR ANY IMPORTANT MAIL!\fP
+
+.\"------------------------------------------------------------------------
+.SH OPTIONS
+.TP
+.BI \-c\  config\-file
+Alternative Dovecot configuration file path.
+.TP
+.B \-C
+Force compilation. By default, the compiled binary is stored on disk. When this
+binary is found during the next execution of \fBsieve\-filter\fP and its
+modification time is more recent than the script file, it is used and the script
+is not compiled again. This option forces the script to be compiled, thus
+ignoring any present binary. Refer to \fBsievec\fP(1) for more information about
+Sieve compilation.
+.TP
+.B \-D
+Enable Sieve debugging.
+.TP
+.B \-e
+Turns on execution mode. By default, the sieve\-filter command runs in
+simulation mode in which it changes nothing, meaning that no mailbox is altered
+in any way and no actions are performed. It only prints what would be done.
+Using this option, the sieve\-filter command becomes active and performs the
+requested actions.
+.TP
+.BI \-m\  default\-mailbox
+The mailbox where the (implicit) \fBkeep\fP Sieve action stores messages. This
+is equal to the \fIsource\-mailbox\fP by default. Specifying a different folder
+will have the effect of moving (or copying if \fB\-W\fP is omitted) all kept
+messages to the indicated folder, instead of just leaving them in the
+\fIsource\-mailbox\fP. Refer to the explanation of the \fIsource\-mailbox\fP
+argument for more information on mailbox naming.
+.TP
+.BI \-o\  setting = value
+Overrides the configuration
+.I setting
+from
+.I @pkgsysconfdir@/dovecot.conf
+and from the userdb with the given
+.IR value .
+In order to override multiple settings, the
+.B \-o
+option may be specified multiple times.
+.TP
+.BI \-q\  output\-mailbox\  \fB[not\ implemented\ yet]\fP
+Store outgoing e\-mail into the indicated \fIoutput\-mailbox\fP. By default,
+the sieve\-filter command ignores Sieve actions such as redirect, reject,
+vacation and notify, but using this option outgoing messages can be appended to
+the indicated mailbox. This option has no effect in simulation mode. Flags of
+redirected messages are not preserved.
+.TP
+.BI \-Q\  mail\-command\  \fB[not\ implemented\ yet]\fP
+Send outgoing e\-mail (e.g. as produced by redirect, reject and vacation)
+through the specified program. By default, the sieve\-filter command ignores
+Sieve actions such as redirect, reject, vacation and notify, but using this
+option outgoing messages can be fed to the \fBstdin\fP of an external shell
+command. This option has no effect in simulation mode. Unless you really know
+what you are doing, \fBDO NOT USE THIS TO FEED MAIL TO SENDMAIL!\fP.
+.TP
+.BI \-s\  script\-file\  \fB[not\ implemented\ yet]\fP
+Specify additional scripts to be executed before the main script. Multiple
+\fB\-s\fP arguments are allowed and the specified scripts are executed
+sequentially in the order specified at the command line.
+.TP
+.BI \-u\  user
+Run the Sieve script for the given \fIuser\fP. When omitted, the
+.I command
+will be executed with the environment of the currently logged in user.
+.TP
+.B \-v
+Produce verbose output during filtering.
+.TP
+.B \-W
+Enables write access to the \fIsource\-mailbox\fP. This allows (re)moving the
+messages from the \fIsource\-mailbox\fP, changing their contents, and changing
+the assigned IMAP flags and keywords.
+.TP
+.BI \-x\  extensions
+Set the available extensions. The parameter is a space\-separated list of the
+active extensions. By prepending the extension identifiers with \fB+\fP or
+\fB\-\fP, extensions can be included or excluded relative to the configured set
+of active extensions. If no extensions have a \fB+\fP or \fB\-\fP prefix, only
+those extensions that are explicitly listed will be enabled. Unknown extensions
+are ignored and a warning is produced.
+
+For example \fB\-x\fP \(dq+imapflags \-enotify\(dq will enable the deprecated
+imapflags extension and disable the enotify extension. The rest of the active
+extensions depends on the \fIsieve_extensions\fP and
+\fIsieve_global_extensions\fP settings. By default, i.e.
+when \fIsieve_extensions\fP and \fIsieve_global_extensions\fP remain
+unconfigured, all supported extensions are available, except for deprecated
+extensions or those that are still under development.
+
+.\"------------------------------------------------------------------------
+.SH ARGUMENTS
+.TP
+.I script\-file
+Specifies the Sieve script to (compile and) execute.
+
+Note that this tool looks for a pre\-compiled binary file with a \fI.svbin\fP
+extension and with basename and path identical to the specified script. Use the
+\fB\-C\fP option to disable this behavior by forcing the script to be compiled
+into a new binary.
+.TP
+.I source\-mailbox
+Specifies the source mailbox containing the messages that the Sieve filter will
+act upon.
+
+This is the name of a mailbox, as visible to IMAP clients, except in UTF-8
+format. The hierarchy separator between a parent and child mailbox is commonly
+.RB \(aq / \(aq
+or
+.RB \(aq . \(aq,
+but this depends on your selected mailbox storage format and
+namespace configuration. The mailbox names may also require a namespace prefix.
+
+This mailbox is not modified unless the \fB\-W\fP option is specified.
+.TP
+.I discard\-action
+Specifies what is done with messages in the \fIsource\-mailbox\fP that where not
+kept or otherwise stored by the Sieve script; i.e. those messages that would
+normally be discarded if the Sieve script were executed at delivery.
+The \fIdiscard\-action\fP parameter accepts one of the following values:
+.RS 7
+.TP
+.BR keep\  (default)
+Keep discarded messages in source mailbox.
+.TP
+.BI move\  mailbox
+Move discarded messages to the indicated \fImailbox\fP. This is for instance
+useful to move messages to a Trash mailbox. Refer to the explanation of
+the \fIsource\-mailbox\fP argument for more information on mailbox naming.
+.TP
+.B delete
+Flag discarded messages as \\DELETED.
+.TP
+.B expunge
+Expunge discarded messages, meaning that these are removed irreversibly when the
+tool finishes filtering.
+.RE
+.IP
+When the \fB\-W\fP option is not specified, the \fIsource\-mailbox\fP is
+immutable and the specified \fIdiscard\-action\fP has no effect. This means that
+messages are at most \fIcopied\fP to a new location. In contrast, when the
+\fB\-W\fP is specified, messages that are successfully stored somewhere else by
+the Sieve script are \fBalways\fP expunged from the \fIsource\-mailbox\fP, with
+the effect that these are thus \fImoved\fP to the new location. This happens
+irrespective of the specified \fIdiscard\-action\fP. Remember: only discarded
+messages are affected by the specified \fIdiscard\-action\fP.
+
+.\"------------------------------------------------------------------------
+
+.SH EXAMPLES
+
+.TP
+[...]
+
+.\"------------------------------------------------------------------------
+.SH "EXIT STATUS"
+.B sieve\-filter
+will exit with one of the following values:
+.TP 4
+.B 0
+Sieve filter applied successfully. (EX_OK, EXIT_SUCCESS)
+.TP
+.B 1
+Operation failed. This is returned for almost all failures.
+(EXIT_FAILURE)
+.TP
+.B 64
+Invalid parameter given. (EX_USAGE)
+.\"------------------------------------------------------------------------
+.SH FILES
+.TP
+.I @pkgsysconfdir@/dovecot.conf
+Dovecot\(aqs main configuration file.
+.TP
+.I @pkgsysconfdir@/conf.d/90\-sieve.conf
+Sieve interpreter settings (included from Dovecot\(aqs main configuration file)
+.\"------------------------------------------------------------------------
+@INCLUDE:reporting-bugs@
+.\"------------------------------------------------------------------------
+.SH "SEE ALSO"
+.BR dovecot (1),
+.BR dovecot\-lda (1),
+.BR sieve\-dump (1),
+.BR sieve\-test (1),
+.BR sievec (1),
+.BR pigeonhole (7)
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/man/sieve-test.1.in
@@ -0,0 +1,257 @@
+.\" Copyright (c) 2010-2018 Pigeonhole authors, see the included COPYING file
+.TH "SIEVE\-TEST" 1 "2017-12-18" "Pigeonhole v0.5 for Dovecot v2.3" "Pigeonhole"
+.SH NAME
+sieve\-test \- Pigeonhole\(aqs Sieve script tester
+.\"------------------------------------------------------------------------
+.SH SYNOPSIS
+.B sieve\-test
+.RI [ options ]
+.I script\-file
+.I mail\-file
+.\"------------------------------------------------------------------------
+.SH DESCRIPTION
+.PP
+The \fBsieve\-test\fP command is part of the Pigeonhole Project
+(\fBpigeonhole\fR(7)), which adds Sieve (RFC 5228) support to the Dovecot
+secure IMAP and POP3 server (\fBdovecot\fR(1)).
+.PP
+Using the \fBsieve\-test\fP command, the execution of Sieve scripts can be
+tested. This evaluates the script for the provided message, yielding a set of
+Sieve actions. Unless the \fB\-e\fP option is specified, it does not actually
+execute these actions, meaning that it does not store or forward the message
+anywere. Instead, it prints a detailed list of what actions would normally take
+place. Note that, even when \fB\-e\fP is specified, no messages are ever
+transmitted to remote SMTP recipients. The outgoing messages are always printed
+to \fBstdout\fP instead.
+.PP
+This is a very useful tool to debug the execution of Sieve scripts. It can be
+used to verify newly installed scripts for the intended behaviour and it can
+provide more detailed information about script execution problems that are
+reported by the Sieve plugin, for example by tracing the execution and
+evaluation of commands and tests respectively.
+.\"------------------------------------------------------------------------
+.SH OPTIONS
+.TP
+.BI \-a\  orig\-recipient\-address
+The original envelope recipient address. This is what Sieve\(aqs envelope test
+will compare to when the \(dqto\(dq envelope part is requested. Some tests and
+actions will also use this as the script owner\(aqs e\-mail address. If this
+option is omitted, the recipient address is retrieved from the
+\(dqEnvelope-To:\(dq, or \(dqTo:\(dq message headers. If none of these headers
+is present either, the recipient address defaults to
+\fIrecipient@example.com\fP.
+.TP
+.BI \-c\  config\-file
+Alternative Dovecot configuration file path.
+.TP
+.B \-C
+Force compilation. By default, the compiled binary is stored on disk. When this
+binary is found during the next execution of \fBsieve\-test\fP and its
+modification time is more recent than the script file, it is used and the script
+is not compiled again. This option forces the script to be compiled, thus
+ignoring any present binary. Refer to \fBsievec\fP(1) for more information about
+Sieve compilation.
+.TP
+.B \-D
+Enable Sieve debugging.
+.TP
+.BI \-d\  dump\-file
+Causes a dump of the generated code to be written to the specified file. This is
+identical to the dump produced by \fBsieve\-dump\fR(1). Using \(aq\-\(aq as
+filename causes the dump to be written to \fBstdout\fP.
+.TP
+.BI \-e
+Enables true execution of the set of actions that results from running the
+script. In combination with the \fB\-l\fP parameter, the actual delivery of
+messages can be tested. Note that this will not transmit any messages to remote
+SMTP recipients. Such actions only print the outgoing message to \fBstdout\fP.
+.TP
+.BI \-f\  envelope\-sender
+The envelope sender address (return path). This is what Sieve\(aqs envelope test
+will compare to when the \(dqfrom\(dq envelope part is requested. Also, this is
+where response messages are \(aqsent\(aq to. If this option is omitted, the sender
+address is retrieved from the \(dqReturn-Path:\(dq, \(dqSender:\(dq or
+\(dqFrom:\(dq message headers. If none of these headers is present either,
+the sender envelope address defaults to \fIsender@example.com\fP.
+.TP
+.BI \-l\  mail\-location
+The location of the user\(aqs mail store. The syntax of this option\(aqs
+\fImail\-location\fP parameter is identical to what is used for the
+mail_location setting in the Dovecot config file. This parameter is typically
+used in combination with \fB\-e\fP to test the actual delivery of messages. If
+\fB\-l\fP is omitted when \fB\-e\fP is specified, mail store actions like
+fileinto and keep are skipped.
+.TP
+.BI \-m\  default\-mailbox
+The mailbox where the keep action stores the message. This is \(dqINBOX\(dq
+by default.
+.TP
+.BI \-o\  setting = value
+Overrides the configuration
+.I setting
+from
+.I @pkgsysconfdir@/dovecot.conf
+and from the userdb with the given
+.IR value .
+In order to override multiple settings, the
+.B \-o
+option may be specified multiple times.
+.TP
+.BI \-r\  recipient\-address
+The final envelope recipient address. Some tests and actions will
+use this as the script owner\(aqs e\-mail address. For example, this is what is
+used by the vacation action to check whether a reply is appropriate. If the
+\fB\-r\fP option is omitted, the original envelope recipient address will be used
+instead (see \fB\-a\fP option for more info).
+.TP
+.BI \-s\  script\-file
+Specify additional scripts to be executed before the main script. Multiple
+\fB\-s\fP arguments are allowed and the specified scripts are executed
+sequentially in the order specified at the command
+line.
+.TP
+.BI \-t\  trace\-file
+Enables runtime trace debugging. Trace debugging provides detailed insight in
+the operations performed by the Sieve script. Refer to the runtime trace
+debugging section below. The trace information is written to the specified file.
+Using '\-' as filename causes the trace data to be written to \fBstdout\fP.
+.TP
+.BI \-T\  trace\-option
+Configures runtime trace debugging, which is enabled with the \fP\-t\fP option.
+Refer to the runtime trace debugging section below.
+.TP
+.BI \-u\  user
+Run the Sieve script for the given \fIuser\fP. When omitted, the
+.I command
+will be executed with the environment of the currently logged in user.
+.TP
+.BI \-x\  extensions
+Set the available extensions. The parameter is a space\-separated list of the
+active extensions. By prepending the extension identifiers with \fB+\fP or
+\fB\-\fP, extensions can be included or excluded relative to the configured set
+of active extensions. If no extensions have a \fB+\fP or \fB\-\fP prefix, only
+those extensions that are explicitly listed will be enabled. Unknown extensions
+are ignored and a warning is produced.
+
+For example \fB\-x\fP \(dq+imapflags \-enotify\(dq will enable the deprecated
+imapflags extension and disable the enotify extension. The rest of the active
+extensions depends on the \fIsieve_extensions\fP and
+\fIsieve_global_extensions\fP settings. By default, i.e.
+when \fIsieve_extensions\fP and \fIsieve_global_extensions\fP remain
+unconfigured, all supported extensions are available, except for deprecated
+extensions or those that are still under development.
+
+.\"------------------------------------------------------------------------
+.SH ARGUMENTS
+.TP
+.I script\-file
+Specifies the script to (compile and) execute.
+
+Note that this tool looks for a pre\-compiled binary file with a \fI.svbin\fP
+extension and with basename and path identical to the specified script. Use the
+\fB\-C\fP option to disable this behavior by forcing the script to be compiled
+into a new binary.
+.TP
+.I mail\-file
+Specifies the file containing the e\-mail message to test with.
+.\"------------------------------------------------------------------------
+.SH USAGE
+.SS RUNTIME TRACE DEBUGGING
+.PP
+Using the \fB\-t\fP option, the \fBsieve\-test\fP tool can be configured to
+print detailed trace information on the Sieve script execution to a file or
+standard output. For example, the encountered commands, the performed tests and
+the matched values can be printed.
+.PP
+The runtime trace can be configured using the \fB\-T\fP option, which can be
+specified multiple times. It can be used as follows:
+
+.TP 2
+\fB\-Tlevel=...\fP
+Set the detail level of the trace debugging. One of the following values can
+be supplied:
+.RS 2
+.TP 3
+\fIactions\fP (default)
+Only print executed action commands, like keep, fileinto, reject and redirect.
+.TP
+\fIcommands\fP
+Print any executed command, excluding test commands.
+.TP
+\fItests\fP
+Print all executed commands and performed tests.
+.TP
+\fImatching\fP
+Print all executed commands, performed tests and the values matched in those
+tests.
+.RE
+.TP 2
+\fB\-Tdebug\fP
+Print debug messages as well. This is usually only useful for developers and
+is likely to produce messy output.
+.TP
+\fB\-Taddresses\fP
+Print byte code addresses for the current trace output. Normally, only the
+current Sieve source code position (line number) is printed. The byte code
+addresses are equal to those listed in a binary dump produced using the
+\fB\-d\fP option or by the \fBsieve\-dump(1)\fP command.
+.\"------------------------------------------------------------------------
+.SS DEBUG SIEVE EXTENSION
+.PP
+To improve script debugging, this Sieve implementation supports a custom Sieve
+language extension called \(aqvnd.dovecot.debug\(aq. It adds the \fBdebug_log\fP
+command that allows logging debug messages.
+.PP
+Example:
+.PP
+require \(dqvnd.dovecot.debug\(dq;
+.PP
+if header :contains \(dqsubject\(dq \(dqhello\(dq {
+.PP
+  debug_log \(dqSubject header contains hello!\(dq;
+.PP
+}
+.PP
+Tools such as \fBsieve\-test\fP, \fBsievec\fP and \fBsieve\-dump\fP have support
+for the vnd.dovecot.debug extension enabled by default and it is not necessary
+to enable nor possible to disable the availability of the debug extension with
+the \fB\-x\fP option. The logged messages are written to \fBstdout\fP in this
+case.
+
+In contrast, for the actual Sieve plugin for the Dovecot LDA
+(\fBdovecot\-lda\fR(1)) the vnd.dovecot.debug extension needs to be enabled
+explicitly using the \fIsieve_extensions\fP setting. The messages are then
+logged to the user's private script log file. If used in a global script, the
+messages are logged through the default Dovecot logging facility.
+.\"------------------------------------------------------------------------
+.SH "EXIT STATUS"
+.B sieve\-test
+will exit with one of the following values:
+.TP 4
+.B 0
+Execution was successful. (EX_OK, EXIT_SUCCESS)
+.TP
+.B 1
+Operation failed. This is returned for almost all failures.
+(EXIT_FAILURE)
+.TP
+.B 64
+Invalid parameter given. (EX_USAGE)
+.\"------------------------------------------------------------------------
+.SH FILES
+.TP
+.I @pkgsysconfdir@/dovecot.conf
+Dovecot\(aqs main configuration file.
+.TP
+.I @pkgsysconfdir@/conf.d/90\-sieve.conf
+Sieve interpreter settings (included from Dovecot\(aqs main configuration file)
+.\"------------------------------------------------------------------------
+@INCLUDE:reporting-bugs@
+.\"------------------------------------------------------------------------
+.SH "SEE ALSO"
+.BR dovecot (1),
+.BR dovecot\-lda (1),
+.BR sieve\-dump (1),
+.BR sieve\-filter (1),
+.BR sievec (1),
+.BR pigeonhole (7)
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/man/sievec.1.in
@@ -0,0 +1,142 @@
+.\" Copyright (c) 2010-2018 Pigeonhole authors, see the included COPYING file
+.TH "SIEVEC" 1 "2017-12-18" "Pigeonhole v0.5 for Dovecot v2.3" "Pigeonhole"
+.\"------------------------------------------------------------------------
+.SH NAME
+sievec \- Pigeonhole\(aqs Sieve script compiler
+.\"------------------------------------------------------------------------
+.SH SYNOPSIS
+.B sievec
+.RI [ options ]
+.I script\-file
+.RI [ out\-file ]
+.\"------------------------------------------------------------------------
+.SH DESCRIPTION
+.PP
+The \fBsievec\fP command is part of the Pigeonhole Project
+(\fBpigeonhole\fR(7)), which adds Sieve (RFC 5228) support to the Dovecot
+secure IMAP and POP3 server (\fBdovecot\fR(1)).
+.PP
+Using the \fBsievec\fP command, Sieve scripts can be compiled into a binary
+representation. The resulting binary can be used directly to process e\-mail
+messages during the delivery process. The delivery of mail messages and \- by
+means of the LDA Sieve plugin \- also the execution of Sieve scripts is
+performed by Dovecot\(aqs local delivery agent (LDA) called \fBdovecot\-lda\fP(1).
+Usually, it is not necessary to compile the Sieve script manually using
+\fBsievec\fP, because \fBdovecot\-lda\fP will do this automatically if the binary
+is missing. However, in some cases \fBdovecot\-lda\fP does not have permission to
+write the compiled binary to disk, forcing it to recompile the script every time
+it is executed. Using the \fBsievec\fP tool, this can be performed manually by
+an authorized user to increase performance.
+.PP
+The Pigeonhole Sieve implementation recognizes files with a \fB.sieve\fP
+extension as Sieve scripts and corresponding files with a \fB.svbin\fP extension
+as the associated compiled binary. This means for example that Dovecot\(aqs LDA
+process will first look for a binary file \(dqdovecot.svbin\(dq when it needs to
+execute \(dqdovecot.sieve\(dq. It will compile a new binary when it is missing
+or outdated.
+.PP
+The \fBsievec\fP command is also useful to verify Sieve scripts before using.
+Additionally, with the \fB\-d\fP option it can output a textual (and thus
+human\-readable) dump of the generated Sieve code to the specified file. The
+output is then identical to what the \fBsieve\-dump\fP(1) command produces for a
+stored binary file. This output is mainly useful to find bugs in the compiler
+that yield corrupt binaries.
+.\"------------------------------------------------------------------------
+.SH OPTIONS
+.TP
+.BI \-c\  config\-file
+Alternative Dovecot configuration file path.
+.TP
+.B \-d
+Don\(aqt write the binary to \fIout\-file\fP, but write a textual dump of the
+binary instead. In this context, the \fIout\-file\fP value '\-' has special
+meaning: it causes the the textual dump to be written to \fBstdout\fP.
+The \fIout\-file\fP argument may also be omitted, which has the same effect
+as '\-'.
+The output is identical to what the \fBsieve\-dump\fP(1) command produces
+for a compiled Sieve binary file. Note that this option is not allowed when the
+\fIout\-file\fP argument is a directory.
+.TP
+.B \-D
+Enable Sieve debugging.
+.TP
+.BI \-o\  setting = value
+Overrides the configuration
+.I setting
+from
+.I @pkgsysconfdir@/dovecot.conf
+and from the userdb with the given
+.IR value .
+In order to override multiple settings, the
+.B \-o
+option may be specified multiple times.
+.TP
+.BI \-u\  user
+Run the Sieve script for the given \fIuser\fP. When omitted, the
+.I command
+will be executed with the environment of the currently logged in user.
+.TP
+.BI \-x\  extensions
+Set the available extensions. The parameter is a space\-separated list of the
+active extensions. By prepending the extension identifiers with \fB+\fP or
+\fB\-\fP, extensions can be included or excluded relative to the configured set
+of active extensions. If no extensions have a \fB+\fP or \fB\-\fP prefix, only
+those extensions that are explicitly listed will be enabled. Unknown extensions
+are ignored and a warning is produced.
+
+For example \fB\-x\fP \(dq+imapflags \-enotify\(dq will enable the deprecated
+imapflags extension and disable the enotify extension. The rest of the active
+extensions depends on the \fIsieve_extensions\fP and
+\fIsieve_global_extensions\fP settings. By default, i.e.
+when \fIsieve_extensions\fP and \fIsieve_global_extensions\fP remain
+unconfigured, all supported extensions are available, except for deprecated
+extensions or those that are still under development.
+
+.\"------------------------------------------------------------------------
+.SH ARGUMENTS
+.TP
+.I script\-file
+Specifies the script to be compiled. If the \fIscript\-file\fP argument is a
+directory, all files in that directory with a \fI.sieve\fP extension are
+compiled into a corresponding \fI.svbin\fP binary file. The compilation is not
+halted upon errors; it attempts to compile as many scripts in the directory as
+possible. Note that the \fB\-d\fP option and the \fIout\-file\fP argument are
+not allowed when the \fIscript\-file\fP argument is a directory.
+.TP
+.I out\-file
+Specifies where the (binary) output is to be written. This argument is optional.
+If this argument is omitted, a binary compiled from <scriptname>.sieve is saved
+as <scriptname>.svbin. If this argument is omitted and \fB\-b\fP is specified,
+the binary dump is output to \fBstdout\fP.
+.\"------------------------------------------------------------------------
+.SH "EXIT STATUS"
+.B sievec
+will exit with one of the following values:
+.TP 4
+.B 0
+Compile was successful. (EX_OK, EXIT_SUCCESS)
+.TP
+.B 1
+Operation failed. This is returned for almost all failures.
+(EXIT_FAILURE)
+.TP
+.B 64
+Invalid parameter given. (EX_USAGE)
+.\"------------------------------------------------------------------------
+.SH FILES
+.TP
+.I @pkgsysconfdir@/dovecot.conf
+Dovecot\(aqs main configuration file.
+.TP
+.I @pkgsysconfdir@/conf.d/90\-sieve.conf
+Sieve interpreter settings (included from Dovecot\(aqs main configuration file)
+.\"------------------------------------------------------------------------
+@INCLUDE:reporting-bugs@
+.\"------------------------------------------------------------------------
+.SH "SEE ALSO"
+.BR dovecot (1),
+.BR dovecot\-lda (1),
+.BR sieve\-dump (1),
+.BR sieve\-filter (1),
+.BR sieve\-test (1),
+.BR pigeonhole (7)
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/man/sieved.1
@@ -0,0 +1 @@
+.so man1/sieve-dump.1
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/plugins/Makefile.am
@@ -0,0 +1,13 @@
+docfiles = \
+	imap_filter_sieve.txt \
+	imapsieve.txt \
+	sieve_extprograms.txt
+
+if BUILD_DOCS
+plugins_docdir = $(sieve_docdir)/plugins
+plugins_doc_DATA = $(docfiles)
+endif
+
+EXTRA_DIST = \
+	$(docfiles)
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/plugins/imap_filter_sieve.txt
@@ -0,0 +1,52 @@
+IMAP FILTER Sieve plugin for Pigeonhole
+
+Relevant specifications
+=======================
+
+	doc/rfc/draft-bosch-imap-filter-sieve-00.txt
+
+Introduction
+============
+
+Normally, Sieve filters can either be applied at initial mail delivery or
+triggered by certain events in the Internet Message Access Protocol (IMAPSIEVE;
+RFC 6785). The user can configure which Sieve scripts to run at these instances,
+but it is not possible to trigger the execution of Sieve scripts manually.
+However, this could be very useful; e.g, to test new Sieve rules and to
+re-filter messages that were erroneously handled by an earlier version of the
+Sieve scripts involved.
+
+Pigeonhole provides the imap_filter_sieve plugin, which provides a vendor-
+defined IMAP extension called "FILTER=SIEVE". This adds a new "FILTER" command
+that allows applying a mail filter (a Sieve script) on a set of messages that
+match the specified searching criteria.
+
+This plugin is experimental and the specification is likely to change.
+
+Configuration
+=============
+
+The IMAP FILTER Sieve plugin is activated by adding it to the mail_plugins
+setting for  the imap protocol:
+
+protocol imap {
+	mail_plugins = $mail_plugins imap_filter_sieve
+}
+
+Currently, no other settings specific to this plugin are defined. It uses the
+normal configuration settings used by the LDA Sieve plugin at delivery.
+
+The sieve_before and sieve_after scripts are currently ignored by this plugin.
+
+Example
+-------
+
+protocol imap {
+  # Space separated list of plugins to load (default is global mail_plugins).
+  mail_plugins = $mail_plugins imap_filter_sieve
+}
+
+plugin {
+  sieve_global = /usr/lib/dovecot/sieve-global.d
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/plugins/imapsieve.txt
@@ -0,0 +1,148 @@
+IMAPSIEVE plugins for Pigeonhole
+
+Relevant specifications
+=======================
+
+	doc/rfc/imapsieve.rfc6785.txt
+
+Introduction
+============
+
+As defined in the base specification, the Sieve language is used only during
+delivery. However, in principle, it can be used at any point in the processing
+of an email message. RFC 6785 defines the use of Sieve filtering in IMAP,
+operating when messages are created or their attributes are changed. This
+feature extends both Sieve and IMAP. Therefore, Pigeonhole provides both an
+IMAP and a Sieve plugin. The IMAP plugin is called "imap_sieve" and the Sieve
+plugin is called "sieve_imapsieve".
+
+The basic IMAPSIEVE capability allows attaching a Sieve script to a mailbox
+(or any mailbox) by setting a special IMAP METADATA entry. This way, users can
+configure Sieve scripts that are run for IMAP events in their mailboxes. The
+Pigeonhole implementation also adds the ability for administrators to configure
+Sieve scripts outside the user's control, that are run either before or after a
+user's script if there is one.
+
+Dovecot-specific Environment Items
+==================================
+
+The "imapsieve" extension defined in RFC 6785 defines additional environment
+items for the "environment" extension defined in RFC 5183. Beyond those, Dovecot
+defines a few more. These are available when the Dovecot-specific
+"vnd.dovecot.imapsieve" extension is enabled using the "require" command. The
+"vnd.dovecot.imapsieve" extension implicitly uses the "imapsieve" extension, so
+that extension does not need to be enabled as well in that case. The following
+Dovecot-specific environment items are added:
+
+vnd.dovecot.mailbox-from
+  The mailbox where the message is being copied or moved from (the source
+  mailbox). This environment item is only set when the Sieve script is executed
+  from an IMAP COPY or MOVE command.
+
+vnd.dovecot.mailbox-to
+  The mailbox where the message is being copied to (the destination mailbox).
+  If the Sieve script is not executed from an IMAP COPY or MOVE command, this
+  environment is always equal to the "imap.mailbox" environment item. Otherwise,
+  the "imap.mailbox" item points to the mailbox where the Sieve script is
+  executing for, which may actually be the source mailbox (see the
+  imapsieve_mailboxXXX_copy_source_after setting).
+
+Configuration
+=============
+
+The IMAP plugin is activated by adding it to the mail_plugins setting for 
+the imap protocol:
+
+protocol imap {
+	mail_plugins = $mail_plugins imap_sieve
+}
+
+This only will enable support for administrator scripts. User scripts are only
+supported when additionally a Sieve URL is configured using the imapsieve_url
+plugin setting. This URL points to the ManageSieve server that users need to use
+to upload their Sieve scripts. This URL will be shown to the client in the IMAP
+CAPABILITY response as IMAPSIEVE=<URL>.
+
+The Sieve plugin is activated by adding it to the sieve_plugins setting:
+
+sieve_plugins = sieve_imapsieve
+
+This plugin registers the "imapsieve" extension with the Sieve interpreter. This
+extension is enabled implicitly, which means that it does not need to be added
+to the "sieve_extensions" setting.
+
+Note that the "imapsieve" extension can only be used from IMAP. When it is used
+in the active delivery script, it will cause runtime errors. To make a Sieve
+script suitable for both delivery and IMAP, the availability of the extension
+can be tested using the "ihave" test (RFC 5463) as usual.
+
+The following settings are recognized the "imap_sieve" plugin:
+
+imapsieve_url =
+  If configured, this setting enables support for user Sieve scripts in IMAP.
+  So, leave this unconfigured if you don't want users to have the ability to
+  associate Sieve scripts with mailboxes. The value is an URL pointing to the
+  ManageSieve server that users must use to upload their Sieve scripts. 
+
+imapsieve_mailboxXXX_name =
+  This setting configures the name of a mailbox for which administrator scripts
+  are configured. The `XXX' in this setting is a sequence number, which allows
+  configuring multiple associations between Sieve scripts and mailboxes. The 
+  settings defined hereafter with matching sequence numbers apply to the mailbox
+  named by this setting. The sequence of configured mailboxes ends at the first
+  missing "imapsieve_mailboxXXX_name" setting. This setting supports wildcards
+  with a syntax compatible with the IMAP LIST command, meaning that this
+  setting can apply to multiple or even all ("*") mailboxes.
+
+imapsieve_mailboxXXX_before =
+imapsieve_mailboxXXX_after =
+  When an IMAP event of interest occurs, these sieve scripts are executed before
+  and after any user script respectively. These settings each specify the
+  location of a single sieve script. The semantics of these settings are very
+  similar to the "sieve_before" and "sieve_after" settings: the specified
+  scripts form a sequence together with the user script in which the next script
+  is only executed when an (implicit) keep action is executed.
+
+imapsieve_mailboxXXX_causes =
+  Only execute the administrator Sieve scripts for the mailbox configured with
+  "imapsieve_mailboxXXX_name" when one of the listed IMAPSIEVE causes apply.
+  This has no effect on the user script, which is always executed no matter the
+  cause.
+
+imapsieve_mailboxXXX_from =
+  Only execute the administrator Sieve scripts for the mailbox configured with
+  "imapsieve_mailboxXXX_name" when the message originates from the indicated
+  mailbox. This setting supports wildcards with a syntax compatible with the
+  IMAP LIST command
+
+imapsieve_mailboxXXX_copy_source_after =
+  When the cause is "COPY", run the specified Sieve script for the message in
+  the source mailbox after the Sieve script for the corresponding message in the
+  destination mailbox successfully finishes executing . This does not apply to
+  moved messages, since the message is removed from the source mailbox in that
+  case.
+
+Example
+-------
+
+protocol imap {
+  # Space separated list of plugins to load (default is global mail_plugins).
+  mail_plugins = $mail_plugins imap_sieve
+}
+
+plugin {
+  sieve_plugins = sieve_imapsieve
+
+  imapsieve_url = sieve://sieve.example.org
+
+  # From elsewhere to Spam folder
+  imapsieve_mailbox1_name = Spam
+  imapsieve_mailbox1_causes = COPY
+  imapsieve_mailbox1_before = file:/usr/lib/dovecot/sieve/report-spam.sieve
+  # From Spam folder to elsewhere
+  imapsieve_mailbox2_name = *
+  imapsieve_mailbox2_from = Spam
+  imapsieve_mailbox2_causes = COPY
+  imapsieve_mailbox2_before = file:/usr/lib/dovecot/sieve/report-ham.sieve
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/doc/plugins/sieve_extprograms.txt
@@ -0,0 +1,182 @@
+Sieve Extprograms plugin for Pigeonhole
+
+Relevant specifications
+=======================
+
+	doc/rfc/spec-bosch-sieve-extprograms.txt
+
+Introduction
+============
+
+Sieve (RFC 5228) is a highly extensible machine language specifically tailored
+for internet message filtering. For the Dovecot Secure IMAP server, Sieve
+support is provided by the Pigeonhole Sieve plugin. This package includes a
+plugin for Pigeonhole called "sieve_extprograms", which extends the Sieve 
+filtering implementation with action commands for invoking a predefined set of
+external programs. Messages can be piped to or filtered through those programs
+and string data can be input to and retrieved from those programs.
+
+The Sieve language is explicitly designed to be powerful enough to be useful yet
+limited in order to allow for a safe server-side filtering system. Therefore,
+the base specification of the language makes it impossible for users to do
+anything more complex (and dangerous) than write simple mail filters. One of the
+consequences of this security-minded design is that users cannot execute
+external programs from their mail filter. Particularly for server-side filtering
+setups in which mail accounts have no corresponding system account, allowing the
+execution of arbitrary programs from the mail filter can be a significant
+security risk. However, such functionality can also be very useful, for instance
+to easily implement a custom action or external effect that Sieve normally
+cannot provide.
+
+The "sieve_extprograms" plugin provides an extension to the Sieve filtering
+language adding new action commands for invoking a predefined set of external
+programs. To mitigate the security concerns, the external programs cannot be
+chosen arbitrarily; the available programs are restricted through administrator
+configuration.
+
+This extension is specific to the Pigeonhole Sieve implementation for the
+Dovecot Secure IMAP server. It will therefore most likely not be supported by
+web interfaces or GUI-based Sieve editors. This extension is primarily meant for
+use in small setups or global scripts that are managed by the systems
+administrator.
+
+Implementation Status
+---------------------
+
+The "vnd.dovecot.pipe", "vnd.dovecot.filter" and "vnd.dovecot.execute" Sieve
+language extensions introduced by this plugin are vendor-specific with draft
+status and their implementation for Pigeonhole is experimental, which means that
+the language extensions are still subject to change and that the current
+implementation is not thoroughly tested.
+
+Configuration
+=============
+
+The plugin is activated by adding it to the sieve_plugins setting:
+
+sieve_plugins = sieve_extprograms
+
+This plugin registers the "vnd.dovecot.pipe", "vnd.dovecot.filter" and
+"vnd.dovecot.execute" extensions with the Sieve interpreter. However, these
+extensions are not enabled by default and thus need to be enabled explicitly. It
+is recommended to restrict the use of these extensions to global context by
+adding these to the "sieve_global_extensions" setting. If personal user scripts
+also need to directly access external programs, the extensions need to be added
+to the "sieve_extensions" setting. 
+
+The commands introduced by the Sieve language extensions in this plugin can
+directly pipe a message or string data to an external program (typically a shell
+script) by forking a new process. Alternatively, these can connect to a unix
+socket behind which a Dovecot script service is listening to start the external
+program, e.g. to execute as a different user or for added security.
+
+The program name specified for the new Sieve "pipe", "filter" and "execute"
+commands is used to find the program or socket in a configured directory.
+Separate directories are specified for the sockets and the directly executed
+binaries. The socket directory is searched first. Since the use of "/" in
+program names is prohibited, it is not possible to build a hierarchical
+structure.
+
+Directly forked programs are executed with a limited set of environment
+variables: HOME, USER, HOST, SENDER, RECIPIENT and ORIG_RECIPIENT. Programs
+executed through the script-pipe socket service currently have no environment
+set at all.
+
+If a shell script is expected to read a message or string data, it must fully
+read the provided input until the data ends with EOF, otherwise the Sieve action
+invoking the program will fail. The action will also fail when the shell script
+returns a nonzero exit code. Standard output is available for returning a
+message (for the filter command) or string data (for the execute command) to the
+Sieve interpreter. Standard error is written to the LDA log file. 
+
+The three extensions introduced by this plugin - "vnd.dovecot.pipe",
+"vnd.dovecot.filter" and "vnd.dovecot.pipe" - each have separate but similar
+configuration. The settings that specify a time period are specified in
+s(econds), unless followed by a d(ay), h(our) or m(inute) specifier character.
+The following configuration settings are used, for which "<extension>" in the
+setting name is replaced by either "pipe", "filter" or "execute" depending on
+which extension is being configured.
+
+sieve_<extension>_socket_dir =
+  Points to a directory relative to the Dovecot base_dir where the plugin looks
+  for script service sockets. 
+
+sieve_<extension>_bin_dir =
+  Points to a directory where the plugin looks for programs (shell scripts) to
+  execute directly and pipe messages to.
+
+sieve_<extension>_exec_timeout = 10s
+  Configures the maximum execution time after which the program is forcibly
+  terminated.
+
+sieve_<extension>_input_eol = crlf
+  Determines the end-of-line character sequence used for the data piped to
+  external programs. The default is currently "crlf", which represents a
+  sequence of the carriage return (CR) and line feed (LF) characters. This
+  matches the Internet Message Format (RFC5322) and what Sieve itself uses as a
+  line ending. Set this setting to "lf" to use a single LF character instead.
+
+Examples
+--------
+
+Example 1: socket service for "pipe" and "execute"
+
+plugin {
+  sieve = ~/.dovecot.sieve
+
+  sieve_plugins = sieve_extprograms
+  sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.execute
+
+  # pipe sockets in /var/run/dovecot/sieve-pipe
+  sieve_pipe_socket_dir = sieve-pipe        
+
+  # execute sockets in /var/run/dovecot/sieve-execute
+  sieve_execute_socket_dir = sieve-execute
+}
+
+service sieve-pipe-script {
+  # This script is executed for each service connection
+  executable = script /usr/lib/dovecot/sieve-extprograms/sieve-pipe-action.sh
+
+  # use some unprivileged user for execution
+  user = dovenull
+
+  # socket name is program-name in Sieve (without sieve-pipe/ prefix)
+  unix_listener sieve-pipe/sieve-pipe-script {
+  }
+}
+
+service sieve-execute-action {
+  # This script is executed for each service connection
+  executable = script /usr/lib/dovecot/sieve-extprograms/sieve-execute-action.sh
+
+  # use some unprivileged user for execution
+  user = dovenull
+
+  # socket name is program-name in Sieve (without sieve-execute/ prefix)
+  unix_listener sieve-execute/sieve-execute-action {
+  }
+}
+
+Example 2: direct execution for "pipe" and "filter"
+
+plugin {
+  sieve = ~/.dovecot.sieve
+
+  sieve_plugins = sieve_extprograms
+  sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.filter
+
+  # This directory contains the scripts that are available for the pipe command.
+  sieve_pipe_bin_dir = /usr/lib/dovecot/sieve-pipe
+
+  # This directory contains the scripts that are available for the filter
+  # command.
+  sieve_filter_bin_dir = /usr/lib/dovecot/sieve-filter
+}
+
+Using
+=====
+
+Refer to doc/rfc/spec-bosch-sieve-extprograms.txt for a specification of the
+Sieve language extensions.
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/dovecot-pigeonhole.m4
@@ -0,0 +1,62 @@
+# pigeonhole.m4 - Check presence of pigeonhole -*-Autoconf-*-
+#.
+
+# serial 5
+
+AC_DEFUN([DC_PIGEONHOLE],[
+	AC_ARG_WITH(pigeonhole,
+	[  --with-pigeonhole=DIR   Pigeonhole base directory],
+	pigeonholedir="$withval",
+	[
+		pg_prefix=$prefix
+		test "x$pg_prefix" = xNONE && pg_prefix=$ac_default_prefix
+		pigeonholedir="$pg_prefix/include/dovecot/sieve"
+	]
+	)
+
+	AC_MSG_CHECKING([for pigeonhole in "$pigeonholedir"])
+
+	top=`pwd`
+	cd $pigeonholedir
+	pigeonholedir=`pwd`
+	cd $top
+	AC_SUBST(pigeonholedir)
+
+	PIGEONHOLE_TESTSUITE=
+	if test -f "$pigeonholedir/src/lib-sieve/sieve.h"; then
+		AC_MSG_RESULT([found])
+		pigeonhole_incdir="$pigeonholedir"
+		LIBSIEVE_INCLUDE='\
+			-I$(pigeonhole_incdir) \
+			-I$(pigeonhole_incdir)/src/lib-sieve \
+			-I$(pigeonhole_incdir)/src/lib-sieve/util \
+			-I$(pigeonhole_incdir)/src/lib-sieve/plugins/copy \
+			-I$(pigeonhole_incdir)/src/lib-sieve/plugins/enotify \
+			-I$(pigeonhole_incdir)/src/lib-sieve/plugins/imap4flags \
+			-I$(pigeonhole_incdir)/src/lib-sieve/plugins/mailbox \
+			-I$(pigeonhole_incdir)/src/lib-sieve/plugins/variables'
+		PIGEONHOLE_TESTSUITE="${pigeonholedir}/src/testsuite/testsuite"
+	elif test -f "$pigeonholedir/sieve.h"; then
+		AC_MSG_RESULT([found])
+		pigeonhole_incdir="$pigeonholedir"
+		LIBSIEVE_INCLUDE='-I$(pigeonhole_incdir)'
+	else
+		AC_MSG_RESULT([not found])
+		AC_MSG_NOTICE([
+			Pigeonhole Sieve headers not found from $pigeonholedir and they
+			are not installed in the Dovecot include path, use --with-pigeonhole=PATH
+ 			to give path to Pigeonhole sources or installed headers.])
+		AC_MSG_ERROR([pigeonhole not found])
+	fi
+
+	DISTCHECK_CONFIGURE_FLAGS="$DISTCHECK_CONFIGURE_FLAGS --with-pigeonhole=$pigeonholedir"
+	
+	AM_CONDITIONAL(PIGEONHOLE_TESTSUITE_AVAILABLE, ! test -z "$PIGEONHOLE_TESTSUITE")
+
+	pigeonhole_incdir="$pigeonholedir"
+
+	AC_SUBST(pigeonhole_incdir)
+
+	AC_SUBST(LIBSIEVE_INCLUDE)
+	AC_SUBST(PIGEONHOLE_TESTSUITE)
+])
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/examples/elvey.sieve
@@ -0,0 +1,153 @@
+# Example Sieve Script
+#   Author: Matthew Elvey (Slightly modified to remove syntax and context errors)
+#   URL: http://www.elvey.com/it/sieve/SieveScript.txt
+
+# Initial version completed and put in place 4/1/02 by Matthew Elvey  (firstname@lastname.com ; I've checked and it's not a valid address.); Copyright (C).and.current as of 5/19/2002 
+#Change log:
+#+ spam[:high]; major reordering; +DFC,BugTraq, PB up +Economist, FolderPath corrections 
+#+ redid .0 matches. +Korean + whitelist +@f(useful once I start bouncing mail!)
+#+open mag, simplifications, to fm=spamNOTwhite, Bulk changes, IETF rules, +lst
+#Reword spam bounce.+scalable@ re-correction+++Work+activate Spam Optimization, etc...
+#oops high = 2x threshold, so 2x1 is 2!  Too low. To @fm:bounce.  Added tons of comments.
+require ["fileinto", "reject", "vacation", "envelope", "regex"];
+
+if header :contains "subject" ["un eject", "lastname.com/spamoff.htm agreed to"] {  #I give out "uneject" to people to let them bypass the spam or size filters.
+  keep;
+} elsif header :contains "subject" ["ADV:", "bounceme", "2002 Gov Grants",   #bounceme is useful for testing.
+             "ADV:ADLT", "ADV-ADULT", "ADULT ADVERTISEMENT"] {  #Subject text required by various US State laws
+  reject text: 
+  Hello.  The server content filter/spam detector I use has bounced your message. It appears to be spam. 
+
+  I do not accept spam/UCE (Unsolicited Commercial Email). 
+
+Please ask me how to bypass this filter if your email is not UCE.  In that case, I am sorry about this 
+highly unusual error.  The filter is >99% accurate.
+
+  (This is an automated message; I will not be aware that your message did not get through if I do not hear from you again.)
+
+  -Firstname
+
+  (P.S. You may also override the filter if you accept the terms at http://www.lastname.com/spamoff.htm, 
+         by including "lastname.com/spamoff.htm agreed to." in the subject.)
+.
+   ;
+}
+# LINE 30.
+  elsif size :over 10M {    # (note that the four leading dots get "stuffed" to three)
+
+  reject text:
+   Message NOT delivered!
+   This system normally accepts email that is less than 10MB in size, because that is how I configured it.
+   You may want to put your file on a server and send me the URL.
+   Or, you may request override permission and/or unreject instructions via another (smaller) email.
+   Sorry for the inconvenience.
+
+   Thanks,
+
+.... Firstname
+   (This is an automated message; I will not be aware that your message did not get through if I do not hear from you again.)
+
+   Unsolicited advertising sent to this E-Mail address is expressly prohibited 
+   under USC Title 47, Section 227.  Violators are subject to charge of up to 
+   $1,500 per incident or treble actual costs, whichever is greater.
+.
+  ; 
+#LINE 47.
+} elsif header :contains "From" "Firstname@lastname.com" {	#if I send myself email, leave it in the Inbox.
+  keep;			#next, is the processing for the various mailing lists I'm on.  
+} elsif header :contains ["Sender", "X-Sender", "Mailing-List", "Delivered-To", "List-Post", "Subject", "To", "Cc", "From", "Reply-to", "Received"] "burningman" {
+  fileinto "INBOX.DaBurn";
+} elsif header :contains ["Subject", "From", "Received"] ["E*TRADE", "Datek", "TD Waterhouse", "NetBank"] {
+  fileinto "INBOX.finances.status";
+} elsif header :contains "subject" "\[pacbell" {
+  fileinto "INBOX.pacbell.dslreports";
+} elsif header :contains "From" ["owner-te-wg ", "te-wg ", "iana.org"] {
+  fileinto "INBOX.lst.IETF";
+} elsif header :contains ["Mailing-List", "Subject", "From", "Received"] ["Red Hat", "Double Funk Crunch", "@economist.com", "Open Magazine", "@nytimes.com", "mottimorell", "Harrow Technology Report"] {
+  fileinto "INBOX.lst.interesting";
+} elsif header :contains ["Mailing-List", "Subject", "From", "Received", "X-LinkName"] ["DJDragonfly", "Ebates", "Webmonkey", "DHJ8091@aol.com", "Expedia Fare Tracker", "SoulShine", "Martel and Nabiel", "\[ecc\]"] {
+  fileinto "INBOX.lst.lame";
+} elsif header :contains ["Subject", "From", "To"] ["guru.com", "monster.com", "hotjobs", "dice.com", "linkify.com"] {  #job boards and current clients.
+  fileinto "INBOX.lst.jobs";
+} elsif header :contains "subject" "\[yaba" {
+  fileinto "INBOX.rec.yaba";
+} elsif header :contains ["to", "cc"] "scalable@" {
+  fileinto "INBOX.lst.scalable";
+} elsif header :contains ["Sender", "To", "Return-Path", "Received"] "NTBUGTRAQ@listserv.ntbugtraq.com" {
+  fileinto "INBOX.lst.bugtraq";
+} elsif header :contains "subject" "Wired" {
+  fileinto "INBOX.lst.wired";
+#LINE 72.
+} elsif anyof (header :contains "From" ["postmaster", "daemon", "abuse"], header :contains "Subject" ["warning:", "returned mail", "failure notice", "undelivered mail"] ) {
+keep;		#this one is important - don't want to miss any bounce messages!
+#LINE 77.
+} elsif anyof (header :contains "From" ["and here I put a whitelist of pretty much all the email addresses in my address book - it's several pages..."]) {
+  fileinto "INBOX.white"; 
+# better than keep;
+# LINE 106.
+
+
+} elsif anyof (address :all :is ["To", "CC", "BCC"] "Firstname.lastname@fastmail.fm",    #a couple people send to this, but I have have all their addrs in whitelist so OK.
+           header :matches "X-Spam-score"  ["9.?" , "10.?", "9", "10", "11.?", "12.?" ,"13.?", "14.?", "11", "12","13", "14", "15.?", "16.?", "17.?" ,"18.?", "19.?", "15", "16", "17" ,"18", "19", "2?.?", "2?", "3?.?" , "3?", "40"]) { 		 #"5.?", "6.?", "5", "6" "7.?" , "8.?" , "7", "8"
+  reject text: 
+  Hello.  The server content filter/spam detector I use has bounced your message. It appears to be spam. 
+
+  I do not accept spam/UCE (Unsolicited Commercial Email). 
+
+Please ask me how to bypass this filter if your email is not UCE.  In that case, I am sorry about this 
+highly unusual error.  The filter is >99% accurate.
+
+  (This is an automated message; I will not be aware that your message did not get through if I do not hear from you again.)
+
+  -Firstname
+
+  (P.S. You may also override the filter if you accept the terms at http://www.lastname.com/spamoff.htm, 
+         by including "lastname.com/spamoff.htm agreed to." in the subject.)
+.
+   ;
+#LINE 127.
+ 
+} elsif 
+header :matches "X-Spam" ["spam", "high"] { if					#optimization idea line 1/2
+           header :matches "X-Spam-score" ["5.?", "6.?", "5", "6"] { 
+  fileinto "INBOX.Spam.5-7"; 
+} elsif header :matches "X-Spam-score" ["7.?" , "8.?" , "7", "8"] { 
+  fileinto "INBOX.Spam.7-9"; 
+#} elsif header :matches "X-Spam-score" ["9.?" , "10.?" , "9", "10"] { 	#These lines obsoleted by reject text rule above, but others will find 'em useful!
+#  fileinto "INBOX.Spam.9-11"; 
+#} elsif header :matches "X-Spam-score" ["11.?" , "12.?" ,"13.?" , "14.?", "11" , "12" ,"13" , "14"] { 
+#  fileinto "INBOX.Spam.11-15"; 
+#} elsif header :matches "X-Spam-score" ["15.?" , "16.?" ,"17.?" ,"18.?" , "19.?", "15" , "16" ,"17" ,"18" , "19"] { 
+#  fileinto "INBOX.Spam.15-20"; 
+#} elsif header :matches "X-Spam-score" ["2?.?", "2?" ] {
+#  fileinto "Inbox.Spam.20-30";
+#} elsif header :matches "X-Spam-score" ["3?.?" , "3?", "40"] {
+#fileinto "Inbox.Spam.30-40";
+ }											#optimization idea  line 2/2 
+
+#LINE 149.
+	
+} elsif header:contains ["Content-Type","Subject"] ["ks_c_5601-1987","euc_kr","euc-kr"]{
+  fileinto "Inbox.Spam.kr";								#block Korean; it's prolly spam and I certainly can't read it.
+} elsif header :contains "Received" "yale.edu" {
+  fileinto "INBOX.Yale";								#if it made it past all the filters above, it's probably of interest.
+      } elsif anyof (header :contains "Subject" ["HR 1910", "viagra", "MLM", "               ","	" ], # common in spam.  (prolly redundant to SpamAssassin.)
+      not exists ["From", "Date"], 						#RFC822 violations common in spam.
+      header :contains ["Sender", "X-Sender", "Mailing-List", "X-Apparently-From", "X-Version", "X-Sender-IP", "Received", "Return-Path", "Delivered-To", "List-Post", "Date", "Subject", "To", "Cc", "From", "Reply-to", "X-AntiAbuse", "Content-Type", "Received", "X-LinkName"] ["btamail.net.cn", "@arabia.com" ] ) {               #spam havens.
+  fileinto "INBOX.GreyMail";
+} elsif header :contains ["Precedence", "Priority", "X-Priority", "Mailing-List", "Subject", "From", "Received", "X-LinkName"] ["Bulk", "Newsletter"] {
+  fileinto "INBOX.Bulk Precedence";
+} elsif header :contains ["to", "cc", "Received"] ["IT@lastname.com", "mail.freeservers.com"] {
+  fileinto "INBOX.lastname.IT";
+} elsif header :contains ["To", "CC"] "Firstname@lastname.com" {
+  fileinto "INBOX.lastname.non-BCC";
+}
+#LINE 167.
+#END OF SCRIPT.  Implied 'keep' is part of the Sieve spec.
+
+
+
+
+
+ 
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/examples/jerry.sieve
@@ -0,0 +1,224 @@
+# Example Sieve Script
+#   Author: Jerry
+#   URL: http://www.emaildiscussions.com/showthread.php?postid=145322#post145322
+
+require ["fileinto", "reject", "vacation", "regex", "relational",
+"comparator-i;ascii-numeric"];
+
+
+#### BLACKLIST - BOUNCE ANYTHING THAT MATCHES
+#    From individual addresses
+         if header :contains "from"
+         [
+           "username@example.com",
+           "username@example.net"
+         ]
+         { reject "Message bounced by server content filter"; stop; }
+
+#    From domains
+         elsif header :contains "from"
+         [
+           "example.com",
+           "example.net"
+         ]
+         { reject "Message bounced by server content filter"; stop; }
+
+
+
+#### BLACKLIST - DELETE ANYTHING THAT MATCHES
+#    From individual addresses
+         elsif header :contains "from"
+         [
+           "username@example.com",
+           "username@example.net"
+         ]
+         { discard; stop; }
+
+#    From domains
+         elsif header :contains "from"
+         [
+           "example.com",
+           "example.net"
+         ]
+         { discard; stop; }
+
+#    I just added the following section after the joe-job
+#    that we all suffered at the hands of "inbox.com".
+#    The "myusername" is MY username at FastMail.
+#    DISCARDing this mail instead of directing it to a
+#    SPAM folder kept me from going over quota repeatedly.
+
+#    To individual addresses
+         elsif header :contains "to"
+         [
+           "myusername@inbox.com",
+           "myusername@example.net"
+         ]
+         { discard; stop; }
+
+         elsif  allof
+             (
+                 not anyof
+                 (
+#### WHITELIST - KEEP ANYTHING THAT MATCHES
+#    From individual addresses
+                     header :contains "from"
+                     [
+                       "username@example.com",
+                       "username@example.net"
+                     ],
+
+#    From trusted domains
+                     header :contains "from"
+                     [
+                       "example.com",
+                       "example.net"
+                     ],
+
+#    Specific "to" address (mailing lists etc)
+                     header :contains ["to", "cc"]
+                     [
+                       "username@example.com",
+                       "username@example.net"
+                     ],
+
+#    Specific "subject" keywords
+                     header :contains "subject"
+                     [
+                       "code_word_for_friend_#1",
+                       "code_word_for_friend_#2"
+                     ]
+
+                 ),
+                 anyof
+                 (
+
+#    Filter by keywords in subject or from headers
+                     header :contains ["subject", "from"]
+                     [
+                       "adilt", "adult", "advertise", "affordable",
+                       "as seen on tv", "antenna", "alarm",
+                       "background check", "bankrupt", "bargain",
+                       "best price", "bikini", "boost reliability",
+                       "brand new", "breast", "business directory",
+                       "business opportunity", "based business", "best
+                       deal", "bachelor's", "benefits", "cable",
+                       "career", "casino", "celeb", "cheapest", "child
+                       support", "cd-r", "catalog", "classified ad",
+                       "click here", "coed", "classmate", "commerce",
+                       "congratulations", "credit", "cruise", "cds",
+                       "complimentary", "columbia house", "crushlink",
+                       "debt", "detective", "diploma", "directv",
+                       "directtv", "dish", "dream vacation", "deluxe",
+                       "drug", "dvds", "dvd movie", "doubleclick",
+                       "digital tv", "erotic", "exciting new",
+                       "equalamail", "fantastic business", "fat
+                       burning", "financial independence", "finalist",
+                       "for life", "financing", "fitness", "fixed
+                       rate", "four reports", "free!", "free
+                       business", "from home", "funds", "fbi know",
+                       "fortune", "gambl", "getaway", "girls", "great
+                       price", "guaranteed", "get big", "get large",
+                       "giveaway", "hard core", "hardcore", "home
+                       document imaging", "home employment directory",
+                       "homeowner", "home owner", "homeworker", "home
+                       security", "home video", "immediate release",
+                       "information you requested", "income",
+                       "inkjet", "insurance", "interest rate",
+                       "invest", "internet connection", "join price",
+                       "judicial judgment", "just released", "know
+                       your rights", "legal", "license", "loan", "long
+                       distance", "look great", "low interest",
+                       "low-interest", "low rate", "lust", "lbs",
+                       "make money", "market", "master card",
+                       "mastercard", "meg web", "merchant account",
+                       "millionaire", "mini-vacation", "mortgage",
+                       "master's", "magazine", "nasty", "new car",
+                       "nigeria", "nude", "nympho", "naked",
+                       "obligation", "online business", "opportunity",
+                       "pager", "paying too much", "pda", "penis",
+                       "pennies", "pills", "porn", "pounds",
+                       "pre-approved", "prescri", "prscri", "prize",
+                       "prostate", "printer ink", "quote", "refinanc",
+                       "remove fat", "removing fat", "reward",
+                       "sales", "satellite", "saw your site",
+                       "scrambler", "sex", "smoking", "snoring", "some
+                       people succeed", "special invitation", "special
+                       offer", "stock", "saving", "singles", "teen",
+                       "ticket", "tired of", "truth about anyone",
+                       "the best", "ucking", "unbelievable",
+                       "uncensored", "uncollected", "unlimited", "USA
+                       domains", "urgent", "valium", "viagra",
+                       "venture capital", "virgin", "visa", "vitamin",
+                       "waist", "wealth", "webcam", "weight", "win a",
+                       "winner", "win one", "work smarter", "work at
+                       home", "xxx", "younger", "your web site", "your
+                       money", "your date is wait",
+                       "!!!", "$", "%", "10K"
+                     ],
+
+#    Filter when the subject is all uppercase (no lowercase)
+                     header :regex :comparator
+                     "i;octet" "subject" "^[^[:lower:]]+$",
+
+#    Filter using regular expressions on the subject
+                     header :regex    "subject"
+                     [
+                       "start.+business", "live.+auction",
+                       "discover.+card", "pay.+college", "apr$",
+                       "apr[^[:alnum:]]", "adv[^[:alnum:]]",
+                       "free.+(coupon|info|install|money)",
+                       "free.+(phone|sample|test|trial)",
+                       "(buy|sell).+(house|home)"
+                     ],
+
+#    Filter with tracker codes in the subject
+                     header :regex    "subject"
+                     "[[:space:].\-_]{4}#?\[?[[:alnum:]-]+\]?$",
+
+#    Filter spam with no to/from address set
+                     not exists    ["To", "From"],
+
+#    Filter spam not addressed to me
+#        Put here all of your own addresses (and alias) that you expect
+#        mail addressed to.  I found a lot of my spam didn't have my
+#        name in the TO or CC fields at all -- it must have been in the
+#        BCC (which doesn't show in the headers).  I can still get BCC
+#        mail from legitimate sources because everyone in my address
+#        book is on the WHITELIST above.
+
+                     not header :contains ["to", "cc"]
+                     [
+                       "myusername@example.com",
+                       "myusername@example.net"
+                     ]
+
+                 )
+             )
+         { fileinto "INBOX.1_spam"; }
+
+
+
+#### Virus Filter
+         elsif  header :contains ["subject", "from"]
+         [
+           "infected file rejected",
+           "infected file rejected"
+         ]
+         { fileinto "INBOX.1_virus"; }
+
+
+#### Telephone Alerts
+#        Any message that gets this far should not be spam,
+#        and a copy gets sent to my cell-phone as a TEXT message.
+
+         elsif  header :contains ["to", "cc"]
+         [
+           "myusername@example.com",
+           "myaliasname@example.com"
+         ]
+         { redirect "2135551234@mobile.example.net"; keep; }
+
+
+
+# END OF SCRIPT
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/examples/mjohnson.sieve
@@ -0,0 +1,421 @@
+# Example Sieve Script
+#  Author: Matthew Johnson
+#  URL: http://wiki.fastmail.fm/index.php?title=MatthewJohnson
+
+##########################################################################
+#######  SIEVE SCRIPT by Matthew Johnson - MRJ Solutions, Inc. ###########
+#######  Email me at mailto:mattjohnson2005@gmail.com ##
+#######  Code Version: 12JUN2004                               ###########
+##########################################################################
+require ["envelope", "fileinto", "reject", "vacation", "regex", "relational",
+         "comparator-i;ascii-numeric"];
+#
+# todo:
+# change to a nested format with
+#   allof()s and nots.
+# add "in address book" check. ex:"header :is :comparator "i;octet" "X-Spam-Known-Sender" "yes""
+# finish reformating lines to <= 75 col (for web edit box)
+#   and delete rulers.
+# Mine Michael Klose script for ideas.
+# Check out the update to the Sieve pages on the Fastmail Wiki.
+#
+
+#---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+
+require ["envelope", "fileinto", "reject", "vacation", "regex",
+         "relational", "comparator-i;ascii-numeric"];
+
+
+
+# BLACKLIST - Mails to discard, drop on the floor.
+#   -high spam values except those delivered to me
+#   -Chinese content except for low spam values
+#   -virus rejected notifications
+#   -known spam addresses
+#   -newsletters that refuse my removal requests
+#   -twit-list
+#   -double twit-list
+#   -other
+
+
+#---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+
+if  anyof
+    (
+      allof       # combo test one - high spam values except for mail to/from me
+      (
+        # spam score is greater or equal to 14
+        header :value "ge" :comparator "i;ascii-numeric"
+                           ["X-Spam-score"] ["14"],
+        not header :contains "X-Spam-Score" "-",  
+        not header :contains "X-Spam-Score" "0.0",
+        not header :contains ["to","from","cc","bcc","received"]
+           [
+             # do not discard email to me, will file or discard
+             # as spam later if needed
+             "matt@zeta.net",
+             "matthew@bigsc.com",
+             "matthew_johnson@bigsmallcompany.com",
+             "mmm@spend.com",
+             "finger@spend.com",
+             "myyaacct@yahoo.com"
+           ]
+       ), # end allof
+      allof       #combo test two - chinese content except for low spam values
+      (
+        anyof
+        (
+           header :regex "Subject"  "^=\\?(gb|GB)2312\\?",  # Chinese ecoding at subject
+           header :regex "Subject"  "^=\\?big5\\?", # Other kind of  Chinese mail
+
+           # Chinese content type
+           header :contains "Content-Type"
+            [
+             "GB2312",
+             "big5"
+            ]
+        ), #end anyof
+        not anyof
+        (
+           #We have to check the sign and the value separately: ascii-numeric, defined at
+           #header :contains "X-Spam-Score" "-",
+           header :value "lt" :comparator "i;ascii-numeric" "X-Spam-Score" "3"
+         )  #end not anyof
+     ), # end allof - test two
+
+     # single tests
+
+     # discard fastmail virus notifications
+     header :is ["subject"] ["Infected file rejected"],
+
+     # black list, invalid addresses receiving a large amount of spam
+     # or spam bounces,rejected zeta.net accounts.
+     header :contains ["X-Delivered-to"]
+
+                        ["eagleeye@zeta.net","ealgeeye@zeta.net",
+                        "alica.thiele@zeta.net", "2005@theta.com",
+                        "jimlovingu2@zeta.net",
+                        "alpha@zeta.net",
+                        "JoshuaS@zeta.net",
+                        "donnaf@zeta.net",
+                        "pspinks@zeta.net",
+                        "jsherman@zeta.net",
+                        "holly@zeta.net",
+                        "clabarca@zeta.net",
+                        "meghanr@zeta.net",
+                        "rtaylor@zeta.net",
+                        "lboone@zeta.net",
+                        "brower@zeta.net",
+                        "jenj@zeta.net",
+                        "cbackus@zeta.net",
+                        "spengles@zeta.net",
+                        "adams@zeta.net",
+                        "dsmith@zeta.net",
+                        "jwilderman@zeta.net",
+                        "TimF@zeta.net",
+                        "zd@zeta.net",
+                        "louise@zeta.net"]
+
+     # single 'not' tests
+     # ---out for testing---  not header :is :comparator "i;octet" "X-Spam-Known-Sender" "yes"
+    ) # end anyof()
+{
+   discard;
+   stop;
+}
+
+
+#
+# WHITELIST - Keep these mails and put them in the inbox
+#             (some kept getting put in Junk Mail)
+#             Family, Friends, Current Vendors, Customers
+#             Contents of fastmail address book.
+#
+#---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+
+if  anyof (  header :contains ["from","to","cc","bcc"]
+                     [ "notification@eBay.com",
+                       "MAILER-DAEMON@zeta.net",
+                       "USPS_Track_Confirm@usps.com",
+                       "credit.services@target.com",
+                       "Comcast_Paydirect@comcast.net",
+                       "mary@zeta.net",
+                       "betty@zeta.net",
+                       "andmanymore@zeta.net"
+                       ],
+            header :is :comparator "i;octet" "X-Spam-Known-Sender" "yes"
+          )
+{
+  fileinto "INBOX";
+  stop;
+}
+
+# redirects
+if header :contains ["to", "cc"] "mary1@zeta.net"
+ {
+  redirect "mary@zeta.net";
+  stop;
+ }
+
+
+#
+#   +Spam filtering by score on 3, 5 and 14(above).
+#
+#
+if  header :value "ge" :comparator "i;ascii-numeric" ["X-Spam-score"] ["5"]  {
+    fileinto "INBOX.Junk Mail.ge5";
+    stop;
+#---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+
+} elsif  header :value "ge" :comparator "i;ascii-numeric" ["X-Spam-score"] ["3"]  {
+    fileinto "INBOX.Junk Mail.ge3";
+    stop;
+}
+
+
+# Potential Blacklist, start with soft discard, then migrate to full discard above
+#
+# Blacklist (2nd) During testing, throw into "Junk Mail.discard" until
+#                 ready to discard.
+#
+if anyof
+   (
+    # rejects for accounts across all domains
+    header :contains ["X-Delivered-to"]
+                  [
+                  "drjoe@","VX@",
+                  "alfa@zeta.net",
+                  "media@zeta.net",
+                  "zeta@zeta.net",
+                  "xyz@zeta.net"
+                  ],
+
+    # other criteria - weird message from this account
+    header :contains ["from"] ["Charlie Root"],
+    # mailers that are always sending spam returns to me
+    header :contains ["from"] ["MAILER-DAEMON@aol.com"] ,
+    header :contains ["from"] ["MAILER-DAEMON@otenet.gr"] ,
+
+    # common account names that I don't use in any of my domains and that spammers like
+    header :contains ["X-Delivered-to"]
+                     [ "biz@","sales@","support@", "service@", "reg@",
+                       "registration@", "regisration@", "root@", "webmaster@", "noreply@"
+                     ],
+    # zeta.net common account names to reject
+    header :contains ["X-Delivered-to"] ["info@zeta.net"],
+    # bigsc.com  rejects
+    header :contains ["X-Delivered-to"] ["info@bigsc.com"],
+    # theta.com rejects
+    header :contains ["X-Delivered-to"] ["info@theta.com"],
+    header :contains ["X-Delivered-to"] ["reg@theta.com"]
+#---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+
+        # saves for use maybe later
+        #   header :contains ["X-Delivered-to"] ["webmaster@zeta.net"],
+        #   header :contains ["X-Delivered-to"] ["webmaster@theta.com"],
+        #   header :contains ["X-Delivered-to"] ["sales@bs.com"],
+        #   header :contains ["X-Delivered-to"] ["sales@theta.com"],
+        #   header :contains ["X-Delivered-to"] ["sales@bigsc.com"],
+        #   header :contains ["X-Delivered-to"] "root@zeta.net",
+
+   )   #end  anyof() 2nd blacklist
+{
+
+  fileinto "INBOX.Junk Mail.discard";
+  stop;
+}
+
+
+#  +Greylist, move to "INBOX.Junk Mail.greylist"
+#
+#   'Soft' Blacklist  ?Greylist?
+#
+
+#annoying person(s) that send questionable attachments
+#  look at occationally
+if  header :contains "from" "alex@yahoo.com"
+{
+  fileinto "INBOX.Junk Mail.greylist";
+} elsif  header :contains "subject" "MAILER-DAEMON@fastmail.fm"
+                                     #  non-person, but might
+                                     # want to look at it while
+								     # figuring issues
+{
+  fileinto "INBOX.Junk Mail.greylist";
+  stop;
+}
+
+#   +Spammy domains to filter
+#
+# domains that are known to be present in spam
+#
+if  header :contains ["from", "received"] [".ru",".jp", ".kr", ".pt",
+					                     ".pl",".at",".cz",".cn",".lu" ]
+{
+  fileinto "INBOX.Junk Mail.discard";
+  stop;
+}
+
+
+#
+#  Annoying newsletters that won't unsubscribe me, reject
+#
+
+if anyof (
+           #annoying newsletters
+           header :contains ["from"] "VistaPrintNews",               # 2003
+           header :contains ["from"] "newsletter@briantracyintl.com", # 2003
+           header :contains ["from"] "info@yogalist.com",            # 2003
+           header :contains ["from"] "The Angela Larson Real Estate Team",
+           header :contains ["from"] "Brian Tracy"
+         )
+#---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+
+{
+   reject "I HAVE TRIED TO UNSUBSCRIBE; I DO NOT WANT YOUR NEWSLETTER; PLEASE UNSUBSCRIBE ME";
+  stop;
+}
+
+
+
+
+#
+# Suspected zeta.net user from/to Zeta Institute, NY - reject
+#
+#
+#
+if    header :contains ["X-Delivered-to","from"]
+          [
+          # aaaaNEW_ENTRIES_ABOVE  ###################################
+          "neville@zeta.net",
+          "animika@zeta.net",
+          "linda@zeta.net",
+          "jerry@zeta.net",
+          "adamS@zeta.net",
+          "lkdamon@zeta.net",
+          "AdamS@zeta.net",
+          "DConnor@zeta.net",
+          "LOUISR@zeta.net",
+
+          # Start of Alpha #############################################
+          "Allanv@zeta.net",
+          "AmberJ@zeta.net",
+          "DANDERSON@zeta.net",
+          "Jonas@zeta.net",
+          "KarenE@zeta.net",
+          "J.R.C.@zeta.net", # check to see if this is working
+          "PMackey@zeta.net",
+
+          "adrienne@zeta.net","alpha@zeta.net","amina@zeta.net",
+          "anamika@zeta.net",
+          "claborca@zeta.net","communications@zeta.net",
+          "cz241@zeta.net",
+          "dee@zeta.net",
+          "ellenb@zeta.net","evis@zeta.net",
+          "frivera@zeta.net",
+          "gblack@zeta.net","gbrown@zeta.net","george@zeta.net","grace@zeta.net",
+          "happygolucky@zeta.net","hsp@zeta.net",
+          "ila@zeta.net",
+          "jacqueline_fenatifa@zeta.net","jlengler@zeta.net",
+          "joel@zeta.net","jolsen@zeta.net", "jsherman@zeta.net",
+          "kronjeklandish@zeta.net","kwilcox@zeta.net","bettyb@zeta.net",
+          "laurie@zeta.net","llmansell@zeta.net",
+          "louise@zeta.net","lzollo@zeta.net",
+          "mcraft@zeta.net","meganB@zeta.net","mwezi@zeta.net",
+          "nanwile@zeta.net",
+          "zetasound@zeta.net",
+          "peter@zeta.net",
+          "randi@zeta.net", "rcbackus@zeta.net", "registration@zeta.net",
+          "registration@omgea.org",
+          "rtaylor@zeta.net",
+          "sdonnarumma@zeta.net","stephanR@zeta.net","suzanne@zeta.net","suzzane@zeta.net",
+          "taryngaughan_dn@zeta.net"
+          # zzzzEND_OF_LIST####
+          ]   #end of Xdelivered-to list for possible zeta institute users
+
+{
+  reject text:
+      ERROR: Your email has not been delivered.
+
+      You have reached the mailer at zeta.net
+
+      Perhaps you want to send to Zeta Institute in DillyDally, NY, USA?
+
+      Use  USER@zeta.net for them
+
+      or try registration@zeta.net
+      Check the website at  http://www.zeta.net/zeta/contact/
+      Call Registration at    1 800 944 1001.
+
+      or use this information:
+
+      Zeta Institute
+      150 River Drive
+      DillyDally, NY 12666
+      Registration: 800-900-0000
+      Ph: 845-200-0000
+      Fax: 845-200-0001
+      registration@zeta.net
+
+      sincerely, POSTMASTER
+.
+;
+  fileinto "Inbox.Junk Mail.ezeta";
+  stop;
+ }
+#---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+
+# +Move messages into folders
+#
+# Process other messages into separate folders
+#
+ # newsletters and mail lists
+if  header :contains  ["subject"]
+                      [ "newsletter", "[tc-ieee-", "[icntc",
+                        "JUG News", "Xdesksoftware",
+                        "announcement"   ]
+{
+  fileinto "INBOX.Newsletters";
+} elsif header :contains ["from","subject"] ["Anthony Robbins"] {
+  fileinto "INBOX.Newsletters";
+} elsif  header :contains ["from","subject"] ["MN Entrepreneurs","ME!"]  {
+  fileinto "INBOX.Newsletters";
+} elsif  header :contains ["from","received"] "adc.apple.com" {
+  fileinto "INBOX.Newsletters";
+} elsif  header :contains "from" "wnewadmn@ieee.org" {
+  fileinto "INBOX.Newsletters";
+} elsif  header :contains "from" "@lb.bcentral.com" {  # techworthy@lb.bcentral.com
+  fileinto "INBOX.Newsletters";
+} elsif  header :contains "from" "announcement@netbriefings.com" {  #st paul company
+  fileinto "INBOX.Newsletters";
+} elsif  header :contains "from" "newsletter@eletters.extremetech.com" {  #semi-annoying rag
+  fileinto "INBOX.Newsletters";
+#---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+
+# my newsletter throw-away addresses
+} elsif  header :contains "to" ["microcenter@zeta.net","nmha@zeta.net"] {
+  fileinto "INBOX.Newsletters";
+
+#---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+
+#
+# Alerts mailbox
+} elsif header :contains ["subject", "from"]
+                         [
+                          "Alert",                         # F-Prot virus alert service, matches:
+                                                           # "FRISK Virus Alert"
+                                                           #     or use s:FRISK Virus Alert:
+                                                           #     or use f:support@f-prot.com
+                          "Payment",                       # Alerts from other payments
+                          "credit.services@target.com",    # Target Card Payments
+                          "notify@quickbase.com"           # Tic Talkers Database changes
+                         ]
+{
+  fileinto "INBOX.Alerts";
+  stop;
+}
+
+# +Announcements from Dave Rolm, forward
+#
+# Perl Announcements from Dave Rolm
+if  header :contains "from" "dave@other.org"
+{
+  fileinto "Inbox";
+  keep;
+}
+#---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+
+#######################################################################
+#### END OF SIEVE SCRIPT by Matthew Johnson - MRJ Solutions, Inc. #####
+################ email me at mailto:mattjohnson2005@gmail.com   #
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/examples/mklose.sieve
@@ -0,0 +1,303 @@
+# Example Sieve Script
+#   Author: Michael Klose
+#   URL: http://wiki.fastmail.fm/index.php?title=MichaelKloseSieveScript
+
+require ["fileinto", "reject", "vacation", "regex", "relational", "comparator-i;ascii-numeric"];
+
+# Experimental
+
+# End experimental
+
+
+
+# ----------------------------------------------
+#    Discard messages (high Spam values)
+# ----------------------------------------------
+
+if anyof
+    (
+     allof
+      (
+       #Spam score > 17?
+       #We have to check the sign and the value separately: ascii-numeric, defined at http://www.ietf.org/rfc/rfc2244.txt, doesn't see minus signs or decimal points ("-" or ".").
+       header :value "ge" :comparator "i;ascii-numeric" "X-Spam-Score" "17",
+       not header :contains "X-Spam-Score" "-",
+
+       not header :contains ["to","cc"]
+        [
+         "@my-domain.de",
+         "myemail@myotherdomain.us",
+         "myotheremail@myotherdomain.us",
+         "myotheremail2@myotherdomain.us"
+         # Do not discard stuff going to me - gets filed into Junk later
+        ],
+       not header :contains "from"
+        [
+         "lockergnome.com",
+         "Excite@info.excite.com" # gets filed into Junk later
+        ]
+
+
+      ),
+     allof
+      (
+       header :contains "X-LinkName" "hotmail", # OR anything from Hotmail with low spam
+       allof
+        (
+         header :value "ge" :comparator "i;ascii-numeric" "X-Spam-Score" "7",
+         not header :contains "X-Spam-Score" "-"
+        )
+      ),
+
+     # Black List
+
+     header :contains "from"
+      [
+       "ahbbcom@cncorn.com",
+       "Darg. B."
+      ],
+
+     # Chinese Encoding at BEGINNING of Subject
+
+     allof
+      (
+       anyof
+        (
+         header :regex "Subject"  "^=\\?(gb|GB)2312\\?",  # Chinese ecoding at subject
+         header :regex "Subject"  "^=\\?big5\\?", # Other kind of Chinese mail
+
+         # Chinese content type
+
+         header :contains "Content-Type"
+          [
+           "GB2312",
+           "big5"
+          ]
+        ),
+       not anyof
+        (
+      #Spam score > -4? <sic> - ascii-numeric ignores the ".9"!.  -Or is this correct?
+       #We have to check the sign and the value separately: ascii-numeric, defined at http://www.ietf.org/rfc/rfc2244.txt, doesn't see minus signs or decimal points ("-" or ".").
+
+         header :contains "X-Spam-Score" "-",
+         header :value "lt" :comparator "i;ascii-numeric" "X-Spam-Score" "4"
+        )
+      )
+    )
+
+{
+
+
+  # discard;
+
+  if header :contains "X-LinkName" "hotmail"
+   { discard; }
+  else
+   { fileinto "INBOX.Junk.Reject"; }
+   # I used to reject this stuff, but I wanted to know what I was rejecting, and this stuck.
+  stop;
+}
+
+
+
+# Addresses that need to be forwarded to a different domain here before spam checking
+# ******************************Michael - I don't understand what you're doing here!  -elvey
+# REPLY: this here is actually used to forward stuff addressed to my sister (using my domain)
+# to her - without using one of the own-domain aliases.
+
+if header :contains ["to", "cc"]
+ [
+  "bla@blabla.de",
+  "bla2@blabla.us",
+  "bla3@blabla.us"
+ ]
+ {
+  redirect "otheremailaddress@something.com";
+  redirect "anotheremailadress@something.com";
+  stop;
+ }
+
+
+# File into a folder before Spam filtering
+
+if header :contains ["to","cc"]
+ [
+  "important@mydomain.us",
+  "important2@mydomain.us"
+ ]
+ {
+  fileinto "Inbox.Important";
+  stop;
+ }
+
+
+
+# -------------------------------------------
+#              Filing rules
+# -------------------------------------------
+
+
+# Pre-SPAM
+
+
+if size :over 750K
+ {
+  fileinto "INBOX.largemail";
+  stop;
+ }
+
+
+if header :contains "from"
+   [
+
+# White list 1 (with SMS notification)
+
+    "Fred Bloggs",
+    "f.bloggs@hotmail.com",
+    "myboss@somecompany.com",
+    "Trisha",
+    "endofauction@ebay.de" # I want to know about end of auctions
+    ]
+ {
+  fileinto "Inbox";
+
+  # Send an SMS
+  redirect "smsgateway@somegateway.de";
+  keep;
+
+  stop;
+ }
+
+  # Advertising I want to receive, which normally ends up in the SPAM filter
+
+  if anyof
+   (
+    header :contains "from"
+
+     [
+
+# Advertising whitelist
+
+      "Mark Libbert",
+      "newsletter@snapfish.dom"
+     ],
+    header :contains "Return-Path" "mailings@gmx.dom"
+   )
+   { fileinto "INBOX.Ads"; }
+  elsif  header :contains "from"
+   [
+    "newsletter@neuseelandhaus.dom",
+    "Lockergnome",
+    "CNET News.com"
+   ]
+   { fileinto "INBOX.Newsletter";
+
+
+
+# Spam protection
+
+
+} elsif anyof
+   (
+
+    #Spam assasin
+    allof
+     (
+      header :value "ge" :comparator "i;ascii-numeric" "X-Spam-Score" "6",
+      not header :contains "X-Spam-Score" "-",
+      not anyof # White list
+       (
+        header :contains "From"         # Whitelist From addresses
+         [
+          "CNN Quick News",
+          "FastMail.FM Support",
+          "lockergnome.com"
+         ]
+       )
+     ),
+
+    # User defined
+
+    # Filter out Femalename1234z12@ spam (base64 encoded)
+    allof
+     (
+      header :regex "From" "alpha:{2,}digit:{2,}alpha:+digit:{2,}@",
+      header :contains "Content-Type" "multipart/mixed"
+     ),
+    # Filter our Spam with invalid headers. You can see this because FM adds
+    # @fastmail.fm to them. For safty, check that mklose@ @michael-klose mkmail@gmx do
+    # not appear
+
+    # Mklose: addition: The only negative side effect I have seen of the condition below
+    # is that it catches the FM newsletters. So far I find them in the spam occasionly
+    # but since they are so few, I have never bothered changing this to not catch them.
+
+    allof
+     (
+      header :contains "To" "@fastmail.fm", # I do not have a fastmail address   # This doesn't catch BCC's; you should be checking the envelop instead.  -elvey
+      not header :contains ["To", "CC", "Reply-To"] ["klose","mkmail@gmx.dom", "chaospower"]
+     )
+   )
+  {
+   fileinto "INBOX.Junk";
+   stop;
+  }
+
+
+# Post Spam-protection
+
+  elsif  header :contains ["to", "cc"] "gpc@gnu.dom" {
+  fileinto "INBOX.GPC";
+} elsif  header :contains ["to", "cc"] "alfs\-discuss@linuxfromscratch.dom" {
+  fileinto "INBOX.LFS-Support.ALFS";
+} elsif  header :contains "subject" "(usagi\-users" {
+  fileinto "INBOX.Usagi";
+} elsif anyof (header :contains "Subject" "\[eplus-de\]", header :contains "Reply-To" "eplus-de") {
+  fileinto "INBOX.E-Plus";
+} elsif  header :contains ["to", "cc"] "lfs\-support@linuxfromscratch.dom" {
+  fileinto "INBOX.LFS-Support";
+} elsif  header :contains ["to", "cc"] "netdev@oss.sgi.dom" {
+  fileinto "INBOX.NetDev";
+} elsif  header :contains ["to", "cc"] "lfs\-dev@linuxfromscratch.dom" {
+  fileinto "INBOX.LFS-DEV";
+} elsif  header :contains "from" "GMX Best Price" {
+  fileinto "INBOX.Werbung";
+} elsif  header :contains "subject" "RHN Errata Alert" {
+  fileinto "INBOX.Notifications";
+} elsif  header :contains "from"
+  [
+   "EmailDiscussions.com Mailer",
+   "help1@dungorm.dom"
+  ] {
+  fileinto "INBOX.Notifications";
+} elsif  header :contains "subject" "\[Gaim\-commits\]" {
+  fileinto "INBOX.Notifications";
+} elsif  header :contains "subject" "\[Bug" {
+  fileinto "INBOX.Notifications.Bugzilla";
+} elsif header :contains "X-LinkName" "hotmail" {
+  fileinto "INBOX.Old Hotmail.new";
+}
+
+
+# -----------------------------------------------------------------------
+#               SMS notifications and forwarding
+# -----------------------------------------------------------------------
+
+if allof
+    (
+     header :contains "to" ["@mydomain1.de","email@mydomain2.us","email2@somedomain"],
+     not header :contains "from"
+      [
+
+# This avoids sending SMS notifications if I am the sender
+
+       "@mydomain1.de",
+       "myotheremail@somedomain.de",
+       "myotheremail@someotherdomain.de"
+      ]
+    )
+ {
+  redirect "smsgateway@somegateway.com";
+  keep;
+ }
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/examples/relational.rfc5231.sieve
@@ -0,0 +1,33 @@
+require ["relational", "comparator-i;ascii-numeric", "fileinto"];
+
+if header :value "lt" :comparator "i;ascii-numeric"
+	["x-priority"] ["3"]
+{
+	fileinto "Priority";
+}
+
+elsif address :count "gt" :comparator "i;ascii-numeric"
+	["to"] ["5"]
+{
+	# everything with more than 5 recipients in the "to" field
+	# is considered SPAM
+	fileinto "SPAM";
+}
+
+elsif address :value "gt" :all :comparator "i;ascii-casemap"
+	["from"] ["M"]
+{
+	fileinto "From N-Z";
+} else {
+	fileinto "From A-M";
+}
+
+if allof ( 
+	address :count "eq" :comparator "i;ascii-numeric"
+		["to", "cc"] ["1"] ,
+	address :all :comparator "i;ascii-casemap"
+		["to", "cc"] ["me@foo.example.com"] )
+{
+	fileinto "Only me";
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/examples/rfc3028.sieve
@@ -0,0 +1,58 @@
+#
+# Example Sieve Filter
+# Declare any optional features or extension used by the script
+#
+require ["fileinto", "reject"];
+
+#
+# Reject any large messages (note that the four leading dots get
+# "stuffed" to three)
+#
+if size :over 1M
+    {
+    reject text:
+Please do not send me large attachments.
+Put your file on a server and send me the URL.
+Thank you.
+.... Fred
+.
+;
+    stop;
+    }
+#
+
+# Handle messages from known mailing lists
+# Move messages from IETF filter discussion list to filter folder
+#
+if header :is "Sender" "owner-ietf-mta-filters@imc.org"
+    {
+    fileinto "filter";  # move to "filter" folder
+    }
+#
+# Keep all messages to or from people in my company
+#
+elsif address :domain :is ["From", "To"] "example.com"
+    {
+    keep;               # keep in "In" folder
+    }
+
+#
+# Try and catch unsolicited email.  If a message is not to me,
+# or it contains a subject known to be spam, file it away.
+#
+elsif anyof (not address :all :contains
+         ["To", "Cc", "Bcc"] "me@example.com",
+     header :matches "subject"
+         ["*make*money*fast*", "*university*dipl*mas*"])
+    {
+    # If message header does not contain my address,
+    # it's from a list.
+    fileinto "spam";   # move to "spam" folder
+    }
+ else
+    {
+    # Move all other (non-company) mail to "personal"
+    # folder.
+    fileinto "personal";
+    }
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/examples/sanjay.sieve
@@ -0,0 +1,171 @@
+# Example Sieve Script
+#   Author: SanjaySheth
+#   URL: http://wiki.fastmail.fm/index.php?title=SanjaySieveSpamFilter
+
+require "fileinto";
+
+if anyof (
+
+      # Blacklisted sender domains
+      header :contains ["from", "Received", "X-Sender", "Sender",
+                        "To","CC","Subject","X-Mail-from"]
+             [ "123greetings", "allfreewebsite.com",
+               "new-fields.com","atlasrewards","azogle.com",
+               "bannerport.net","bettingextreme.com","bigemailoffers.com",
+               "BlingMail.com",
+               "beyondoffers.net", ".biz ", ".biz]",
+               "cavalrymail.com","ciol.com","citywire.co.uk",
+               "cosmicclick.com",
+               "consumergamblingreport","creativemailoffers.com","creativeoffers.com",
+               "daily-promotions.com",
+               "dailypromo.","dailypromotions.",
+               "dandyoffers","dlbdirect",
+               "e54.org",  "email-specials.net","email-ware.com","emailoffersondemand",
+               "emailbargain.com","emailofferz","emailrewardz","etoll.net","emailvalues.com",
+               "evaluemarketing.com","exitrequest.com",
+               "fantastic-bargain.com","fpsamplesmail.com","freelotto",
+               "findtv.com", "freddysfabulousfinds.com",
+               "genuinerewards.com",
+               "hotdailydeal.com","hulamediamail","hy-e.net",
+               "inboxbargains.com","idealemail.com",
+               "jackpot.com","jpmailer.com",
+               "lolita","lund.com.br",
+               "mafgroup.com","mailasia.com","mailtonic.net","migada.com","ms83.com",
+               "nationaloffers.com","nexdeals.com ",
+               "offercatch.com","offermagnet.com","offerservice.net","offertime.com",
+               "offersdaily.net","optnetwork.net",
+               "ombramarketing.com","on-line-offers.com","outblaze.com",
+               "permissionpass","primetimedirect.net","productsontheweb.net",
+               "rapid-e.net","recessionspecials", "redmoss","remit2india",
+               "sampleoffers.com","savingsmansion.com","sendoutmail.com","simpleoffers.com",
+               "specialdailydeals4u.com","Select-Point.net",
+               "speedyvalues.com","sportsoffers","sporttime.info","suntekglobal.com",
+               "superstorespecials.com", "synapseconnect","sunsetterawnings.com",
+               "thefreesamplenews","truemail.net",
+               "ub-kool","ultimatesports.info","uniquemailoffers","utopiad.com",
+               "unixlovers.net",
+               "valuesdirect","virtualoffers.net",
+               "wagerzine", "webdpoffrz",
+               "yestshirt.com",
+               "z-offer.com", "zipido.com"
+             ],
+
+      # Blacklisted ip subnets due to excessive spam from them
+      header :contains "Received"
+             [ "[4.63.221.224",
+               "[24.244.141.112",
+               "[61.171.253.177",
+               "[63.123.149.", "[63.209.206.", "(63.233.30.73", "[63.251.200.",
+               "[64.41.183.","[64.49.250.", "[64.57.188.", "[64.57.221.",
+               "[64.62.204.",
+               "[64.70.17.", "[64.70.44.", "[64.70.53.",
+               "[64.39.27.6", "[64.39.27.7","[64.191.25.","[64.191.36.",
+               "[64.191.9.",
+               "[64.125.181.", "[64.191.123.", "[64.191.23.", "[64.239.182.",
+               "[65.211.3.",
+               "[66.46.150.", "[66.62.162.", "[66.118.170.", "[66.129.124.",
+               "[66.205.217.", "[66.216.111.", "[66.239.204.",
+               "[67.86.69.",
+               "[80.34.206.", "[80.80.98.",
+               "[81.72.233.13",
+               "[128.242.120.",
+               "[157.238.18",
+               "[168.234.195.18]",
+               "[193.253.198.57",
+               "[194.25.83.1",
+               "[200.24.129.", "[200.161.203.",
+               "[202.164.182.76]","[202.57.69.116",
+               "[203.19.220.","[203.22.104.","[203.22.105.",
+               "[204.188.52.",
+               "[205.153.154.203",
+               "[206.26.195.", "[206.154.33.","[206.169.178",
+               "[207.142.3.",
+               "[208.46.5.","[208.187.",
+               "[209.164.27.","[209.236.",
+               "[210.90.75.129]",
+               "[211.101.138.199","[211.185.7.125]","[211.239.231.",
+               "[212.240.95.",
+               "[213.47.250.139", "[213.225.61.",
+               "[216.22.79.","[216.39.115.","[216.99.240.",
+               "[216.126.32.", "[216.187.123.","[217.36.124.53",
+               "[218.145.25","[218.52.71.103","[218.158.136.115",
+               "[218.160.42.74", "[218.242.112.4]"
+             ],
+
+      # Blacklisted SpamAssassin flags
+      header :contains ["SPAM", "X-Spam-hits"]
+             ["ADDRESSES_ON_CD","ACT_NOW","ADULT_SITE", "ALL_CAP_PORN",
+              "AMATEUR_PORN", "AS_SEEN_ON",
+              "BAD_CREDIT", "BALANCE_FOR_LONG_20K", "BARELY_LEGAL", "BEEN_TURNED_DOWN",
+              "BANG_GUARANTEE", "BANG_MONEY","BASE64_ENC_TEXT",
+              "BAYES_99","BAYES_90",
+              "BE_BOSS", "BEST_PORN", "BULK_EMAIL",
+              "CASINO", "CONSOLIDATE_DEBT", "COPY_ACCURATELY", "COPY_DVD",
+              "DIET", "DO_IT_TODAY","DOMAIN_4U2",
+              "EMAIL_MARKETING","EMAIL_ROT13", "EXPECT_TO_EARN","EARN_MONEY",
+              "FIND_ANYTHING", "FORGED_AOL_RCVD",
+              "FORGED_HOTMAIL_RCVD", "FORGED_YAHOO_RCVD",
+              "FORGED_RCVD_TRAIL", "FORGED_JUNO_RCVD",
+              "FORGED_MUA_",
+              "FREE_MONEY","FREE_PORN",
+              "GENTLE_FEROCITY", "GET_PAID", "GUARANTEED_STUFF", "GUARANTEED_100_PERCENT",
+              "HAIR_LOSS", "HIDDEN_ASSETS", "HGH,", "HOME_EMPLOYMENT","HOT_NASTY","HTTP_ESCAPED_HOST",
+              "HTTP_USERNAME_USED","HTML_FONT_INVISIBLE",
+              "IMPOTENCE","INVALID_MSGID","INVESTMENT",
+              "LESBIAN","LIVE_PORN","LOSE_POUNDS",
+              "MARKETING_PARTNERS", "MORTGAGE_OBFU", "MORTGAGE_RATES",
+              "NIGERIAN_SCAM", "NIGERIAN_TRANSACTION_1", "NIGERIAN_BODY", "NUMERIC_HTTP_ADDR",
+              "NO_MX_FOR_FROM","NO_DNS_FOR_FROM",
+              "OBFUSCATING_COMMENT", "ONLINE_PHARMACY",
+              "PENIS_ENLARGE",
+              "PREST_NON_ACCREDITED", "PURE_PROFIT","PORN_4",
+              "RCVD_IN_DSBL", "RCVD_IN_OSIRUSOFT_COM","RCVD_IN_BL_SPAMCOP_NET", "RCVD_IN_SBL",
+              "RCVD_IN_MULTIHOP_DSBL", "RCVD_IN_RELAYS_ORDB_ORG", "RCVD_IN_UNCONFIRMED_DSBL",
+              "RCVD_FAKE_HELO_DOTCOM", "RCVD_IN_RFCI", "RCVD_IN_NJABL","RCVD_IN_SORBS",
+              "REFINANCE", "REVERSE_AGING",
+              "SAVE_ON_INSURANCE","SPAM_REDIRECTOR", "STOCK_ALERT", "STOCK_PICK", "STRONG_BUY",
+              "SEE_FOR_YOURSELF", "SUPPLIES_LIMITED",
+              "THE_BEST_RATE","TONER",
+              "UNSECURED_CREDIT",
+              "VACATION_SCAM", "VIAGRA", "VJESTIKA",
+              "WHILE_SUPPLIES", "WORK_AT_HOME",
+              "X_OSIRU_DUL", "X_OSIRU_SPAMWARE_SITE", "X_OSIRU_SPAM_SRC"
+             ],
+
+
+      # Blacklisted subjects
+
+      header :contains ["From","Subject"]
+             [" penis ",
+              "ADV:", "adult dvd", "adult movie", "adultdirect", "adultemail",
+              "background check", "bankrupt", "boobs", "business opportunity","big@boss.com",
+              "casino", "cash guarantee",
+              "debt free", "diet bread", "ebay secrets", "erection",
+              "financial freedom", "free credit",
+              "gambl", "gov grants", "jackpot",
+              "life insurance", "lottery", "lotto",
+              "mortgage", "nude", "OTCBB",
+              "penis", "porn", "promotion", "proven System",
+              " rape ",
+              " sex ", "skin resurfacing", "special offer",
+              "ultimate software", "viagra", "V1AGRA", "vivatrim",
+              "win money","work from home", "xxx"
+             ],
+
+      # often spam emails to multiple addresses with same name & different domain
+      header :matches ["To","CC"]
+             ["*fastmail*fastmail*fastmail*fastmail*fastmail*"],
+
+      # Almost all emails from these domains is spam (at least for me)
+      header :contains ["from", "received"]
+                       [".ru ",".jp ", ".kr ", ".pt ",".pl ",".at ",".cz ",
+                        ".ru>",".jp>", ".kr>", ".pt>", ".pl>",".at>",".cz>"],
+
+      # Really high SpamAssassin scores (15.0+)
+      header :matches ["X-Spam-score","X-Remote-Spam-score"] [
+          "1?.?", "2?.?", "3?.?", "4?.?", "5?.?", "6?.?"     # 10.0 to 69.9
+      ]
+) {
+      fileinto "INBOX.Spam.discard";
+      stop;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/examples/sieve_examples.sieve
@@ -0,0 +1,73 @@
+# Example Sieve Script
+#   Author: unknown
+#   URL: http://wiki.fastmail.fm/index.php?title=MoreSieveExamples
+
+require ["fileinto", "reject"];
+
+###BYPASSES###
+
+if anyof (
+              header :contains ["From"] "friend1",
+              header :contains ["From"] "friend12",
+              header :contains ["From"] "friend3",
+              header :contains ["From"] "friendsdomanin",
+              header :contains ["Subject"] "elephant"  ##a safeword
+         )
+             {
+                   fileinto "INBOX";
+                   stop;
+             }
+
+###BIG MESSAGE PROTECTION
+if size :over 5000K {
+         reject "Message over 5MB size limit.  Please contact me before sending this.";
+}
+
+##SPAM FILTERING##
+if header :contains ["X-Spam"] "high" {
+      discard;
+      stop;
+}
+if header :contains ["X-Spam-Flag"] "HIGH" {
+      discard;
+      stop;
+}
+if header :contains ["X-Spam"] "spam" {
+      fileinto "INBOX.spam";  #emails forwarded from my unviersity account get SA tagged like this
+      stop;
+}
+if header :contains ["X-Spam-Flag"] "YES" {
+      fileinto "INBOX.spam";
+      stop;
+}
+
+####LOCAL SPAM RULES#######
+if header :contains ["From"]  "bannerport" { discard; stop; }  ##keyword filters for when SA doesn't quite catch them
+if header :contains ["To"]  "MATT NOONE" { discard; stop; }
+###AUTO management rules###
+
+####Student Digest stuff#### ###   Examples of boolean OR rules
+if anyof (
+            header :contains ["X-BeenThere"] "student-digest@list.xxx.edu",
+            header :contains ["X-BeenThere"] "firstyear-digest@list.xxx.edu",
+            header :contains ["X-BeenThere"] "secondyear-digest@list.xxx.edu",
+            header :contains ["X-BeenThere"] "thirdyear-digest@list.xxx.edu",
+            header :contains ["X-BeenThere"] "fourthyear-digest@list.xxx.edu"
+         )
+         {
+            fileinto "INBOX.lists.digests";
+            stop;
+         }
+if allof (   ###A Boolean AND rule
+            header :contains ["From"] "buddy1",
+            header :contains ["To"]   "myotheraddress"
+         )
+         {
+            fileinto "INBOX.scc.annoy";
+            stop;
+         }
+
+#other local rules
+if header :contains ["Subject"]  "helmreich" { fileinto "INBOX.lists.helmreich"; stop; }
+if header :contains ["Subject"]  "helmcomm" { fileinto "INBOX.lists.helmreich"; stop; }
+if header :contains ["Subject"]  "packeteer" { fileinto "INBOX.lists"; stop; }
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/examples/subaddress.rfc5233.sieve
@@ -0,0 +1,23 @@
+require ["envelope", "subaddress", "fileinto"];
+
+# In this example the same user account receives mail for both
+# "ken@example.com" and "postmaster@example.com"
+
+# File all messages to postmaster into a single mailbox,
+# ignoring the :detail part.
+if envelope :user "to" "postmaster" {
+	fileinto "inbox.postmaster";
+	stop;
+}
+
+# File mailing list messages (subscribed as "ken+mta-filters").
+if envelope :detail "to" "mta-filters" {
+	fileinto "inbox.ietf-mta-filters";
+}
+
+# Redirect all mail sent to "ken+foo".
+if envelope :detail "to" "foo" {
+	redirect "ken@example.net";
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/examples/vacation.sieve
@@ -0,0 +1,23 @@
+require ["fileinto","reject", "vacation"];
+if allof (header :contains  "X-Spam-Flag" "YES") 
+{
+    discard ;
+}
+
+elsif allof (header :contains "subject" "<quation>") 
+{
+vacation 
+:addresses "<name@domain.ru>"
+:subject "<Answear>" 
+:mime "MIME-Version: 1.0
+Content-Type: text/html; charset=KOI8-R
+Content-Transfer-Encoding: 7bit
+<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
+<HTML><HEAD><META http-equiv=Content-Type content=\"text/html; charset=windows-KOI8-R\">
+</HEAD><BODY>123</BODY></HTML>";
+ discard ;
+}
+else 
+{
+     keep;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/examples/vivil.sieve
@@ -0,0 +1,94 @@
+# Example Sieve Script
+#   Author: Vivil
+#   URL: http://wiki.fastmail.fm/index.php?title=Vivil
+#   Removed unused notify require
+
+# *************************************************************************
+require ["envelope", "fileinto", "reject", "vacation", "regex", "relational", 
+"comparator-i;ascii-numeric"];
+
+
+if size :over 2048K {
+  reject "Message not delivered; size over limit accepted by recipient";
+  stop;  
+}
+
+#because of the use of elsif below, none of the "stop;"'s below are needed, but they're good 'defensive programming'. Only the one above is actually needed.
+
+redirect "login@gmail.dom";
+
+if header :contains ["from","cc"]
+[
+  "from-begin@beginbeginbeginbeginbeginbeginbeginbeginbegin.fr",
+  "sex.com newsletter",
+  "ad@gator.com",
+  "newsletter@takecareof.com",
+  "from-end@endendendendendendendendendendendendendendendend.fr"
+]
+{
+  discard;
+  stop;
+}
+
+elsif header :contains ["from"]
+[
+  "mygirlfriend-who-use-incredimail@foo.dom"
+]
+{
+  fileinto "INBOX.PRIORITY";
+  stop;
+}
+
+#use of "to" field detection next lines is ONLY USEFUL FOR DOMAIN NAME OWNERS if you forward your mail to your fastmail account, some virus/spam send mail to well known addresses as info@willemijns.dom i never use...
+
+elsif header :contains ["to","cc"]
+[
+  "to-begin@beginbeginbeginbeginbeginbeginbeginbeginbegin.fr",
+  "FTPsebastien@willemijns.dom",
+  "info@willemijns.dom",
+  "webmaster@willemijns.dom",
+  "to-end@endendendendendendendendendendendendendendendend.fr"
+]
+{
+  discard;
+  stop;
+}
+
+elsif header :contains ["subject"]
+[
+  "subject-begin@beginbeginbeginbeginbeginbeginbeginbeginbegin.fr",
+  "Undeliverable mail: Registration is accepted",
+  "subject-end@endendendendendendendendendendendendendendendend.fr"
+]
+{
+  discard;
+  stop;
+}
+elsif header :value "ge" :comparator "i;ascii-numeric" ["X-Spam-score"] ["6"]  {
+  fileinto "INBOX.Junk Mail";
+  stop;
+}
+elsif header :contains "from" "reflector@launay.dom" {
+  fileinto "INBOX.TEST";
+  stop;
+}
+elsif header :contains "from" "do-not-reply@franconews.dom" {
+  fileinto "INBOX.TEST";
+  stop;
+}
+elsif header :contains "from" "devnull@news.telefonica.dom" {
+  fileinto "INBOX.TEST";
+  stop;
+}
+elsif header :contains ["to"] ["sebastien@willemijns.dom"] {
+  fileinto "INBOX.PRIORITY";
+  stop;
+}
+elsif header :contains ["to"] ["seb@willemijns.dom"] {
+  fileinto "INBOX.PRIORITY";
+  stop;
+}
+else {
+  fileinto "INBOX";
+}
+# ********************************************************************
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/m4/dovecot.m4
@@ -0,0 +1,184 @@
+# dovecot.m4 - Check presence of dovecot -*-Autoconf-*-
+#
+#   Copyright (C) 2010 Dennis Schridde
+#
+# This file is free software; the authors give
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved.
+
+# serial 25
+
+AC_DEFUN([DC_DOVECOT_MODULEDIR],[
+	AC_ARG_WITH(moduledir,
+	[  --with-moduledir=DIR    Base directory for dynamically loadable modules],
+		moduledir="$withval",
+		moduledir=$libdir/dovecot
+	)
+	AC_SUBST(moduledir)
+])
+
+AC_DEFUN([DC_PLUGIN_DEPS],[
+	_plugin_deps=yes
+	AC_MSG_CHECKING([whether OS supports plugin dependencies])
+	case "$host_os" in
+	  darwin*)
+	    # OSX loads the plugins twice, which breaks stuff
+	    _plugin_deps=no
+	    ;;
+	esac
+	AC_MSG_RESULT([$_plugin_deps])
+	AM_CONDITIONAL([DOVECOT_PLUGIN_DEPS], [test "x$_plugin_deps" = "xyes"])
+	unset _plugin_deps
+])
+
+AC_DEFUN([DC_DOVECOT_TEST_WRAPPER],[
+  AC_CHECK_PROG(VALGRIND, valgrind, yes, no)
+  if test $VALGRIND = yes; then
+    cat > run-test.sh <<EOF
+#!/bin/sh
+top_srcdir=\$[1]
+shift
+
+if test "\$NOUNDEF" != ""; then
+  noundef="--undef-value-errors=no"
+else
+  noundef=""
+fi
+
+if test "\$NOVALGRIND" != ""; then
+  \$[*]
+  ret=\$?
+else
+  trap "rm -f test.out.\$\$" 0 1 2 3 15
+  supp_path="\$top_srcdir/run-test-valgrind.supp"
+  if test -r "\$supp_path"; then
+    valgrind -q --trace-children=yes --leak-check=full --suppressions="\$supp_path" --log-file=test.out.\$\$ \$noundef \$[*]
+  else
+    valgrind -q --trace-children=yes --leak-check=full --log-file=test.out.\$\$ \$noundef \$[*]
+  fi
+  ret=\$?
+  if test -s test.out.\$\$; then
+    cat test.out.\$\$
+    ret=1
+  fi
+fi
+if test \$ret != 0; then
+  echo "Failed to run: \$[*]" >&2
+fi
+exit \$ret
+EOF
+    RUN_TEST='$(SHELL) $(top_builddir)/run-test.sh $(top_srcdir)'
+  else
+    RUN_TEST=''
+  fi
+  AC_SUBST(RUN_TEST)
+])
+
+# Substitute every var in the given comma seperated list
+AC_DEFUN([AX_SUBST_L],[
+	m4_foreach([__var__], [$@], [AC_SUBST(__var__)])
+])
+
+AC_DEFUN([DC_DOVECOT],[
+	AC_ARG_WITH(dovecot,
+	  [  --with-dovecot=DIR      Dovecot base directory],
+			[ dovecotdir="$withval" ], [
+			  dc_prefix=$prefix
+			  test "x$dc_prefix" = xNONE && dc_prefix=$ac_default_prefix
+			  dovecotdir="$dc_prefix/lib/dovecot"
+			]
+	)
+
+	AC_ARG_WITH(dovecot-install-dirs,
+		[AC_HELP_STRING([--with-dovecot-install-dirs],
+		[Use install directories configured for Dovecot (default)])],
+	if test x$withval = xno; then
+		use_install_dirs=no
+	else
+		use_install_dirs=yes
+	fi,
+	use_install_dirs=yes)
+
+	AC_MSG_CHECKING([for "$dovecotdir/dovecot-config"])
+	if test -f "$dovecotdir/dovecot-config"; then
+		AC_MSG_RESULT([$dovecotdir/dovecot-config])
+	else
+		AC_MSG_RESULT([not found])
+		AC_MSG_NOTICE([])
+		AC_MSG_NOTICE([Use --with-dovecot=DIR to provide the path to the dovecot-config file.])
+		AC_MSG_ERROR([dovecot-config not found])
+	fi
+
+	old=`pwd`
+	cd $dovecotdir
+	abs_dovecotdir=`pwd`
+	cd $old
+	DISTCHECK_CONFIGURE_FLAGS="--with-dovecot=$abs_dovecotdir --without-dovecot-install-dirs"
+
+	eval `grep -i '^dovecot_[[a-z_]]*=' "$dovecotdir"/dovecot-config`
+	eval `grep '^LIBDOVECOT[[A-Z0-9_]]*=' "$dovecotdir"/dovecot-config`
+
+	dovecot_installed_moduledir="$dovecot_moduledir"
+
+	if test "$use_install_dirs" = "no"; then
+		# the main purpose of these is to fix make distcheck for plugins
+		# other than that, they don't really make much sense
+		dovecot_pkgincludedir='$(pkgincludedir)'
+		dovecot_pkglibdir='$(pkglibdir)'
+		dovecot_pkglibexecdir='$(libexecdir)/dovecot'
+		dovecot_docdir='$(docdir)'
+		dovecot_moduledir='$(moduledir)'
+		dovecot_statedir='$(statedir)'
+	fi
+
+	AX_SUBST_L([DISTCHECK_CONFIGURE_FLAGS], [dovecotdir], [dovecot_moduledir], [dovecot_installed_moduledir], [dovecot_pkgincludedir], [dovecot_pkglibexecdir], [dovecot_pkglibdir], [dovecot_docdir], [dovecot_statedir])
+	AX_SUBST_L([DOVECOT_INSTALLED], [DOVECOT_CFLAGS], [DOVECOT_LIBS], [DOVECOT_SSL_LIBS], [DOVECOT_SQL_LIBS], [DOVECOT_COMPRESS_LIBS], [DOVECOT_BINARY_CFLAGS], [DOVECOT_BINARY_LDFLAGS])
+	AX_SUBST_L([LIBDOVECOT], [LIBDOVECOT_LOGIN], [LIBDOVECOT_SQL], [LIBDOVECOT_SSL], [LIBDOVECOT_COMPRESS], [LIBDOVECOT_LDA], [LIBDOVECOT_STORAGE], [LIBDOVECOT_DSYNC], [LIBDOVECOT_LIBFTS])
+	AX_SUBST_L([LIBDOVECOT_DEPS], [LIBDOVECOT_LOGIN_DEPS], [LIBDOVECOT_SQL_DEPS], [LIBDOVECOT_SSL_DEPS], [LIBDOVECOT_COMPRESS_DEPS], [LIBDOVECOT_LDA_DEPS], [LIBDOVECOT_STORAGE_DEPS], [LIBDOVECOT_DSYNC_DEPS], [LIBDOVECOT_LIBFTS_DEPS])
+	AX_SUBST_L([LIBDOVECOT_INCLUDE], [LIBDOVECOT_LDA_INCLUDE], [LIBDOVECOT_AUTH_INCLUDE], [LIBDOVECOT_DOVEADM_INCLUDE], [LIBDOVECOT_SERVICE_INCLUDE], [LIBDOVECOT_STORAGE_INCLUDE], [LIBDOVECOT_LOGIN_INCLUDE], [LIBDOVECOT_SQL_INCLUDE])
+	AX_SUBST_L([LIBDOVECOT_IMAP_LOGIN_INCLUDE], [LIBDOVECOT_CONFIG_INCLUDE], [LIBDOVECOT_IMAP_INCLUDE], [LIBDOVECOT_POP3_INCLUDE], [LIBDOVECOT_DSYNC_INCLUDE], [LIBDOVECOT_IMAPC_INCLUDE], [LIBDOVECOT_FTS_INCLUDE])
+	AX_SUBST_L([LIBDOVECOT_NOTIFY_INCLUDE], [LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE], [LIBDOVECOT_ACL_INCLUDE], [LIBDOVECOT_LIBFTS_INCLUDE])
+
+	AM_CONDITIONAL(DOVECOT_INSTALLED, test "$DOVECOT_INSTALLED" = "yes")
+
+	DC_PLUGIN_DEPS
+	DC_DOVECOT_TEST_WRAPPER
+])
+
+AC_DEFUN([DC_CC_WRAPPER],[
+  if test "$want_shared_libs" != "yes"; then
+    # want_shared_libs=no is for internal use. the liblib.la check is for plugins
+    if test "$want_shared_libs" = "no" || echo "$LIBDOVECOT" | grep "/liblib.la" > /dev/null; then
+      if test "$with_gnu_ld" = yes; then
+	# libtool can't handle using whole-archive flags, so we need to do this
+	# with a CC wrapper.. shouldn't be much of a problem, since most people
+	# are building with shared libs.
+	cat > cc-wrapper.sh <<EOF
+#!/bin/sh
+
+if echo "\$[*]" | grep -- -ldl > /dev/null; then
+  # the binary uses plugins. make sure we include everything from .a libs
+  exec $CC -Wl,--whole-archive \$[*] -Wl,--no-whole-archive
+else
+  exec $CC \$[*]
+fi
+EOF
+	chmod +x cc-wrapper.sh
+	CC=`pwd`/cc-wrapper.sh
+      fi
+    fi
+  fi
+])
+
+AC_DEFUN([DC_PANDOC], [
+  AC_ARG_VAR(PANDOC, [Path to pandoc program])
+
+  # Optional tool for making documentation
+  AC_CHECK_PROGS(PANDOC, [pandoc], [true])
+
+  if test "$PANDOC" = "true"; then
+   if test ! -e README; then
+     AC_MSG_ERROR([Cannot produce documentation without pandoc - disable with PANDOC=false ./configure])
+   fi
+  fi
+])
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/pigeonhole-config.h.in
@@ -0,0 +1,15 @@
+
+/* Define to the full name of Pigeonhole for Dovecot. */
+#undef PIGEONHOLE_NAME
+
+/* Define to the version of Pigeonhole for Dovecot. */
+#undef PIGEONHOLE_VERSION
+
+/* Pigeonhole ABI version */
+#undef PIGEONHOLE_ABI_VERSION
+
+/* Define to build unfinished features/extensions. */
+#undef HAVE_SIEVE_UNFINISHED
+
+/* LDAP support is built in */
+#undef SIEVE_BUILTIN_LDAP
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/Makefile.am
@@ -0,0 +1,21 @@
+
+
+sieve_subdirs = \
+	lib-sieve \
+	plugins \
+	lib-sieve-tool \
+	sieve-tools \
+	testsuite
+
+if BUILD_MANAGESIEVE
+managesieve_subdirs = \
+	lib-managesieve \
+	managesieve \
+	managesieve-login
+else
+managesieve_subdirs =
+endif
+
+SUBDIRS = \
+	$(sieve_subdirs) \
+	$(managesieve_subdirs)
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-managesieve/Makefile.am
@@ -0,0 +1,15 @@
+noinst_LTLIBRARIES = libmanagesieve.la
+
+AM_CPPFLAGS = \
+	$(LIBDOVECOT_INCLUDE) \
+	-I$(top_srcdir)
+
+libmanagesieve_la_SOURCES = \
+	managesieve-arg.c \
+	managesieve-quote.c \
+	managesieve-parser.c
+
+noinst_HEADERS = \
+	managesieve-arg.h \
+	managesieve-quote.h \
+	managesieve-parser.h
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-managesieve/managesieve-arg.c
@@ -0,0 +1,129 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "managesieve-arg.h"
+
+bool managesieve_arg_get_atom
+(const struct managesieve_arg *arg, const char **str_r)
+{
+	if (arg->type != MANAGESIEVE_ARG_ATOM)
+		return FALSE;
+
+	*str_r = arg->_data.str;
+	return TRUE;
+}
+
+bool managesieve_arg_get_number
+(const struct managesieve_arg *arg, uoff_t *number_r)
+{
+	const char *data;
+	uoff_t num = 0;
+	size_t i;
+
+	if ( arg->type != MANAGESIEVE_ARG_ATOM )
+		return FALSE;
+
+	data = arg->_data.str;
+	for ( i = 0; i < arg->str_len; i++ ) {
+		uoff_t newnum;
+
+		if (data[i] < '0' || data[i] > '9')
+			return FALSE;
+
+		newnum = num*10 + (data[i] -'0');
+		if ( newnum < num )
+			return FALSE;
+
+		num = newnum;
+	}
+
+	*number_r = num;
+	return TRUE;
+}
+
+bool managesieve_arg_get_quoted
+(const struct managesieve_arg *arg, const char **str_r)
+{
+	if (arg->type != MANAGESIEVE_ARG_STRING)
+		return FALSE;
+
+	*str_r = arg->_data.str;
+	return TRUE;
+}
+
+bool managesieve_arg_get_string
+(const struct managesieve_arg *arg, const char **str_r)
+{
+	if (arg->type != MANAGESIEVE_ARG_STRING
+		&& arg->type != MANAGESIEVE_ARG_LITERAL)
+		return FALSE;
+
+	*str_r = arg->_data.str;
+	return TRUE;
+}
+
+bool managesieve_arg_get_string_stream
+(const struct managesieve_arg *arg, struct istream **stream_r)
+{
+	if ( arg->type != MANAGESIEVE_ARG_STRING_STREAM )
+		return FALSE;
+
+	*stream_r = arg->_data.str_stream;
+	return TRUE;
+}
+
+bool managesieve_arg_get_list
+(const struct managesieve_arg *arg, const struct managesieve_arg **list_r)
+{
+	unsigned int count;
+
+	return managesieve_arg_get_list_full(arg, list_r, &count);
+}
+
+bool managesieve_arg_get_list_full
+(const struct managesieve_arg *arg, const struct managesieve_arg **list_r,
+	unsigned int *list_count_r)
+{
+	unsigned int count;
+
+	if (arg->type != MANAGESIEVE_ARG_LIST)
+		return FALSE;
+
+	*list_r = array_get(&arg->_data.list, &count);
+
+	/* drop MANAGESIEVE_ARG_EOL from list size */
+	i_assert(count > 0);
+	*list_count_r = count - 1;
+	return TRUE;
+}
+
+struct istream *managesieve_arg_as_string_stream
+(const struct managesieve_arg *arg)
+{
+	struct istream *stream;
+
+	if (!managesieve_arg_get_string_stream(arg, &stream))
+		i_unreached();
+	return stream;
+}
+
+const struct managesieve_arg *
+managesieve_arg_as_list(const struct managesieve_arg *arg)
+{
+	const struct managesieve_arg *ret;
+
+	if (!managesieve_arg_get_list(arg, &ret))
+		i_unreached();
+	return ret;
+}
+
+bool managesieve_arg_atom_equals
+(const struct managesieve_arg *arg, const char *str)
+{
+	const char *value;
+
+	if (!managesieve_arg_get_atom(arg, &value))
+		return FALSE;
+	return strcasecmp(value, str) == 0;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-managesieve/managesieve-arg.h
@@ -0,0 +1,108 @@
+#ifndef MANAGESIEVE_ARG_H
+#define MANAGESIEVE_ARG_H
+
+#include "array.h"
+
+/*
+ * QUOTED-SPECIALS    = <"> / "\"
+ */
+#define IS_QUOTED_SPECIAL(c) \
+	((c) == '"' || (c) == '\\')
+
+/*
+ * ATOM-SPECIALS      = "(" / ")" / "{" / SP / CTL / QUOTED-SPECIALS
+ */
+#define IS_ATOM_SPECIAL(c) \
+	((c) == '(' || (c) == ')' || (c) == '{' || \
+	 (c) <= 32 || (c) == 0x7f || \
+	 IS_QUOTED_SPECIAL(c))
+
+/*
+ * CHAR               = %x01-7F
+ */
+#define IS_CHAR(c) \
+	(((c) & 0x80) == 0)
+
+/*
+ * TEXT-CHAR          = %x01-09 / %x0B-0C / %x0E-7F
+ *                       ;; any CHAR except CR and LF
+ */
+#define IS_TEXT_CHAR(c) \
+	(IS_CHAR(c) && (c) != '\r' && (c) != '\n')
+
+/*
+ * SAFE-CHAR          = %x01-09 / %x0B-0C / %x0E-21 /
+ *                      %x23-5B / %x5D-7F
+ *                      ;; any TEXT-CHAR except QUOTED-SPECIALS
+ */
+#define IS_SAFE_CHAR(c) \
+	(IS_TEXT_CHAR(c) && !IS_QUOTED_SPECIAL(c))
+
+enum managesieve_arg_type {
+	MANAGESIEVE_ARG_NONE = 0,
+	MANAGESIEVE_ARG_ATOM,
+	MANAGESIEVE_ARG_STRING,
+	MANAGESIEVE_ARG_STRING_STREAM,
+
+	MANAGESIEVE_ARG_LIST,
+
+	/* literals are returned as MANAGESIEVE_ARG_STRING by default */
+	MANAGESIEVE_ARG_LITERAL,
+
+	MANAGESIEVE_ARG_EOL /* end of argument list */
+};
+
+ARRAY_DEFINE_TYPE(managesieve_arg_list, struct managesieve_arg);
+struct managesieve_arg {
+	enum managesieve_arg_type type;
+	struct managesieve_arg *parent; /* always of type MANAGESIEVE_ARG_LIST */
+
+	/* Set when _data.str is set */
+	size_t str_len;
+
+	union {
+		const char *str;
+		struct istream *str_stream;
+		ARRAY_TYPE(managesieve_arg_list) list;
+	} _data;
+};
+
+#define MANAGESIEVE_ARG_IS_EOL(arg) \
+	((arg)->type == MANAGESIEVE_ARG_EOL)
+
+bool managesieve_arg_get_atom
+	(const struct managesieve_arg *arg, const char **str_r)
+	ATTR_WARN_UNUSED_RESULT;
+bool managesieve_arg_get_number
+	(const struct managesieve_arg *arg, uoff_t *number_r)
+	ATTR_WARN_UNUSED_RESULT;
+bool managesieve_arg_get_quoted
+	(const struct managesieve_arg *arg, const char **str_r)
+	ATTR_WARN_UNUSED_RESULT;
+bool managesieve_arg_get_string
+	(const struct managesieve_arg *arg, const char **str_r)
+	ATTR_WARN_UNUSED_RESULT;
+
+bool managesieve_arg_get_string_stream
+	(const struct managesieve_arg *arg, struct istream **stream_r)
+	ATTR_WARN_UNUSED_RESULT;
+
+bool managesieve_arg_get_list
+	(const struct managesieve_arg *arg, const struct managesieve_arg **list_r)
+	ATTR_WARN_UNUSED_RESULT;
+bool managesieve_arg_get_list_full
+	(const struct managesieve_arg *arg, const struct managesieve_arg **list_r,
+		unsigned int *list_count_r)
+	ATTR_WARN_UNUSED_RESULT;
+
+/* Similar to above, but assumes that arg is already of correct type. */
+struct istream *managesieve_arg_as_string_stream
+	(const struct managesieve_arg *arg);
+const struct managesieve_arg *managesieve_arg_as_list
+	(const struct managesieve_arg *arg);
+
+/* Returns TRUE if arg is atom and case-insensitively matches str */
+bool managesieve_arg_atom_equals
+	(const struct managesieve_arg *arg, const char *str);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-managesieve/managesieve-parser.c
@@ -0,0 +1,751 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "unichar.h"
+#include "istream-private.h"
+#include "ostream.h"
+#include "strescape.h"
+#include "managesieve-parser.h"
+
+#define is_linebreak(c) \
+	((c) == '\r' || (c) == '\n')
+
+#define LIST_INIT_COUNT 7
+
+enum arg_parse_type {
+	ARG_PARSE_NONE = 0,
+	ARG_PARSE_ATOM,
+	ARG_PARSE_STRING,
+	ARG_PARSE_LITERAL,
+	ARG_PARSE_LITERAL_DATA
+};
+
+struct managesieve_parser {
+	/* permanent */
+	pool_t pool;
+	struct istream *input;
+	size_t max_line_size;
+	enum managesieve_parser_flags flags;
+
+	/* reset by managesieve_parser_reset(): */
+	size_t line_size;
+	ARRAY_TYPE(managesieve_arg_list) root_list;
+	ARRAY_TYPE(managesieve_arg_list) *cur_list;
+	struct managesieve_arg *list_arg;
+
+	enum arg_parse_type cur_type;
+	size_t cur_pos; /* parser position in input buffer */
+
+	int str_first_escape; /* ARG_PARSE_STRING: index to first '\' */
+	uoff_t literal_size; /* ARG_PARSE_LITERAL: string size */
+
+	struct istream *str_stream;
+
+	const char *error;
+
+	bool literal_skip_crlf:1;
+	bool literal_nonsync:1;
+	bool eol:1;
+	bool fatal_error:1;
+};
+
+static struct istream *quoted_string_istream_create
+	(struct managesieve_parser *parser);
+
+struct managesieve_parser *
+managesieve_parser_create(struct istream *input, size_t max_line_size)
+{
+	struct managesieve_parser *parser;
+
+	parser = i_new(struct managesieve_parser, 1);
+	parser->pool = pool_alloconly_create("MANAGESIEVE parser", 8192);
+	parser->input = input;
+	parser->max_line_size = max_line_size;
+
+	p_array_init(&parser->root_list, parser->pool, LIST_INIT_COUNT);
+	parser->cur_list = &parser->root_list;
+	return parser;
+}
+
+void managesieve_parser_destroy(struct managesieve_parser **parser)
+{
+	i_stream_unref(&(*parser)->str_stream);
+
+	pool_unref(&(*parser)->pool);
+	i_free(*parser);
+	*parser = NULL;
+}
+
+void managesieve_parser_reset(struct managesieve_parser *parser)
+{
+	p_clear(parser->pool);
+
+	parser->line_size = 0;
+
+	p_array_init(&parser->root_list, parser->pool, LIST_INIT_COUNT);
+	parser->cur_list = &parser->root_list;
+	parser->list_arg = NULL;
+
+	parser->cur_type = ARG_PARSE_NONE;
+	parser->cur_pos = 0;
+
+	parser->str_first_escape = 0;
+	parser->literal_size = 0;
+
+	parser->error = NULL;
+
+	parser->literal_skip_crlf = FALSE;
+	parser->eol = FALSE;
+
+	i_stream_unref(&parser->str_stream);
+}
+
+const char *managesieve_parser_get_error
+(struct managesieve_parser *parser, bool *fatal)
+{
+	*fatal = parser->fatal_error;
+	return parser->error;
+}
+
+/* skip over everything parsed so far, plus the following whitespace */
+static bool
+managesieve_parser_skip_to_next(struct managesieve_parser *parser,
+				    const unsigned char **data,
+				    size_t *data_size)
+{
+	size_t i;
+
+	for (i = parser->cur_pos; i < *data_size; i++) {
+		if ((*data)[i] != ' ')
+			break;
+	}
+
+	parser->line_size += i;
+	i_stream_skip(parser->input, i);
+	parser->cur_pos = 0;
+
+	*data += i;
+	*data_size -= i;
+	return *data_size > 0;
+}
+
+static struct managesieve_arg *managesieve_arg_create
+(struct managesieve_parser *parser)
+{
+	struct managesieve_arg *arg;
+
+	arg = array_append_space(parser->cur_list);
+	arg->parent = parser->list_arg;
+
+	return arg;
+}
+
+static void managesieve_parser_save_arg(struct managesieve_parser *parser,
+				 const unsigned char *data, size_t size)
+{
+	struct managesieve_arg *arg;
+	char *str;
+
+	arg = managesieve_arg_create(parser);
+
+	switch (parser->cur_type) {
+	case ARG_PARSE_ATOM:
+		/* simply save the string */
+		arg->type = MANAGESIEVE_ARG_ATOM;
+		arg->_data.str = p_strndup(parser->pool, data, size);
+		arg->str_len = size;
+		break;
+	case ARG_PARSE_STRING:
+		/* data is quoted and may contain escapes. */
+		if ((parser->flags & MANAGESIEVE_PARSE_FLAG_STRING_STREAM) != 0) {
+			arg->type = MANAGESIEVE_ARG_STRING_STREAM;
+			arg->_data.str_stream = parser->str_stream;
+		} else {
+			i_assert(size > 0);
+
+			arg->type = MANAGESIEVE_ARG_STRING;
+			str = p_strndup(parser->pool, data+1, size-1);
+
+			/* remove the escapes */
+			if (parser->str_first_escape >= 0 &&
+				  (parser->flags & MANAGESIEVE_PARSE_FLAG_NO_UNESCAPE) == 0)
+				(void)str_unescape(str);
+
+			arg->_data.str = str;
+			arg->str_len = strlen(str);
+		}
+		break;
+	case ARG_PARSE_LITERAL_DATA:
+		if ((parser->flags & MANAGESIEVE_PARSE_FLAG_STRING_STREAM) != 0) {
+			arg->type = MANAGESIEVE_ARG_STRING_STREAM;
+			arg->_data.str_stream = parser->str_stream;
+		} else if ((parser->flags & MANAGESIEVE_PARSE_FLAG_LITERAL_TYPE) != 0) {
+			arg->type = MANAGESIEVE_ARG_LITERAL;
+			arg->_data.str = p_strndup(parser->pool, data, size);
+			arg->str_len = size;
+		} else {
+			arg->type = MANAGESIEVE_ARG_STRING;
+			arg->_data.str = p_strndup(parser->pool, data, size);
+			arg->str_len = size;
+		}
+		break;
+	default:
+		i_unreached();
+	}
+
+	parser->cur_type = ARG_PARSE_NONE;
+}
+
+static bool
+is_valid_atom_char(struct managesieve_parser *parser, char chr)
+{
+	if (IS_ATOM_SPECIAL((unsigned char)chr)) {
+		parser->error = "Invalid characters in atom";
+		return FALSE;
+	} else if ((chr & 0x80) != 0) {
+		parser->error = "8bit data in atom";
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static bool
+managesieve_parser_read_atom(struct managesieve_parser *parser,
+				 const unsigned char *data, size_t data_size)
+{
+	size_t i;
+
+	/* read until we've found space, CR or LF. */
+	for (i = parser->cur_pos; i < data_size; i++) {
+		if (data[i] == ' ' || data[i] == ')' ||
+			 is_linebreak(data[i])) {
+			managesieve_parser_save_arg(parser, data, i);
+			break;
+		} else if (!is_valid_atom_char(parser, data[i]))
+			return FALSE;
+	}
+
+	parser->cur_pos = i;
+	return ( parser->cur_type == ARG_PARSE_NONE );
+}
+
+static bool
+managesieve_parser_read_string(struct managesieve_parser *parser,
+				   const unsigned char *data, size_t data_size)
+{
+	size_t i;
+
+	/* QUOTED-CHAR        = SAFE-UTF8-CHAR / "\" QUOTED-SPECIALS
+	 * quoted             = <"> *QUOTED-CHAR <">
+	 *                    ;; limited to 1024 octets between the <">s
+	 */
+
+	/* read until we've found non-escaped ", CR or LF */
+	for (i = parser->cur_pos; i < data_size; i++) {
+		if (data[i] == '"') {
+
+			if ( !uni_utf8_data_is_valid(data+1, i-1) ) {
+				parser->error = "Invalid UTF-8 character in quoted-string.";
+				return FALSE;
+			}
+
+			managesieve_parser_save_arg(parser, data, i);
+			i++; /* skip the trailing '"' too */
+			break;
+		}
+
+		if (data[i] == '\0') {
+			parser->error = "NULs not allowed in strings";
+			return FALSE;
+		}
+
+		if (data[i] == '\\') {
+			if (i+1 == data_size) {
+				/* known data ends with '\' - leave it to
+				   next time as well if it happens to be \" */
+				break;
+			}
+
+			/* save the first escaped char */
+			if (parser->str_first_escape < 0)
+				parser->str_first_escape = i;
+
+			/* skip the escaped char */
+			i++;
+
+			if ( !IS_QUOTED_SPECIAL(data[i]) ) {
+				parser->error =
+					"Escaped quoted-string character is not a QUOTED-SPECIAL.";
+				return FALSE;
+			}
+
+			continue;
+		}
+
+		if ( (data[i] & 0x80) == 0 && !IS_SAFE_CHAR(data[i]) ) {
+			parser->error = "String contains invalid character.";
+			return FALSE;
+		}
+	}
+
+	parser->cur_pos = i;
+	return ( parser->cur_type == ARG_PARSE_NONE );
+}
+
+static bool
+managesieve_parser_literal_end(struct managesieve_parser *parser)
+{
+	if ((parser->flags & MANAGESIEVE_PARSE_FLAG_STRING_STREAM) == 0) {
+		if (parser->line_size >= parser->max_line_size ||
+		    parser->literal_size >
+		    	parser->max_line_size - parser->line_size) {
+			/* too long string, abort. */
+			parser->error = "Literal size too large";
+			parser->fatal_error = TRUE;
+			return FALSE;
+		}
+	}
+
+	parser->cur_type = ARG_PARSE_LITERAL_DATA;
+	parser->literal_skip_crlf = TRUE;
+
+	parser->cur_pos = 0;
+	return TRUE;
+}
+
+static bool
+managesieve_parser_read_literal(struct managesieve_parser *parser,
+				    const unsigned char *data,
+				    size_t data_size)
+{
+	size_t i, prev_size;
+
+	/* expecting digits + "}" */
+	for (i = parser->cur_pos; i < data_size; i++) {
+		if (data[i] == '}') {
+			parser->line_size += i+1;
+			i_stream_skip(parser->input, i+1);
+
+			return managesieve_parser_literal_end(parser);
+		}
+
+		if (parser->literal_nonsync) {
+			parser->error = "Expecting '}' after '+'";
+			return FALSE;
+		}
+
+		if (data[i] == '+') {
+			parser->literal_nonsync = TRUE;
+			continue;
+		}
+
+		if (data[i] < '0' || data[i] > '9') {
+			parser->error = "Invalid literal size";
+			return FALSE;
+		}
+
+		prev_size = parser->literal_size;
+		parser->literal_size = parser->literal_size*10 + (data[i]-'0');
+
+		if (parser->literal_size < prev_size) {
+			/* wrapped around, abort. */
+			parser->error = "Literal size too large";
+			return FALSE;
+		}
+	}
+
+	parser->cur_pos = i;
+	return FALSE;
+}
+
+static bool
+managesieve_parser_read_literal_data(struct managesieve_parser *parser,
+					 const unsigned char *data,
+					 size_t data_size)
+{
+	if (parser->literal_skip_crlf) {
+
+		/* skip \r\n or \n, anything else gives an error */
+		if (data_size == 0)
+			return FALSE;
+
+		if (*data == '\r') {
+			parser->line_size++;
+			data++; data_size--;
+			i_stream_skip(parser->input, 1);
+
+			if (data_size == 0)
+				return FALSE;
+		}
+
+		if (*data != '\n') {
+			parser->error = "Missing LF after literal size";
+			return FALSE;
+		}
+
+		parser->line_size++;
+		data++; data_size--;
+		i_stream_skip(parser->input, 1);
+
+		parser->literal_skip_crlf = FALSE;
+
+		i_assert(parser->cur_pos == 0);
+	}
+
+	if ((parser->flags & MANAGESIEVE_PARSE_FLAG_STRING_STREAM) == 0) {
+		/* now we just wait until we've read enough data */
+		if (data_size < parser->literal_size) {
+			return FALSE;
+		} else {
+			if ( !uni_utf8_data_is_valid
+				(data, (size_t)parser->literal_size) ) {
+				parser->error = "Invalid UTF-8 character in literal string.";
+				return FALSE;
+			}
+
+			managesieve_parser_save_arg(parser, data,
+					     (size_t)parser->literal_size);
+			parser->cur_pos = (size_t)parser->literal_size;
+			return TRUE;
+		}
+	} else {
+		/* we don't read the data; we just create a stream for the literal */
+		parser->eol = TRUE;
+		parser->str_stream = i_stream_create_limit
+			(parser->input, parser->literal_size);
+		managesieve_parser_save_arg(parser, NULL, 0);
+		return TRUE;
+	}
+}
+
+/* Returns TRUE if argument was fully processed. Also returns TRUE if
+   an argument inside a list was processed. */
+static bool
+managesieve_parser_read_arg(struct managesieve_parser *parser)
+{
+	const unsigned char *data;
+	size_t data_size;
+
+	data = i_stream_get_data(parser->input, &data_size);
+	if (data_size == 0)
+		return FALSE;
+
+	while (parser->cur_type == ARG_PARSE_NONE) {
+		/* we haven't started parsing yet */
+		if (!managesieve_parser_skip_to_next(parser, &data, &data_size))
+			return FALSE;
+		i_assert(parser->cur_pos == 0);
+
+		switch (data[0]) {
+		case '\r':
+		case '\n':
+			/* unexpected end of line */
+			parser->eol = TRUE;
+			return FALSE;
+		case '"':
+			parser->cur_type = ARG_PARSE_STRING;
+			parser->str_first_escape = -1;
+			break;
+		case '{':
+			parser->cur_type = ARG_PARSE_LITERAL;
+			parser->literal_size = 0;
+			parser->literal_nonsync = FALSE;
+			break;
+		default:
+			if (!is_valid_atom_char(parser, data[0]))
+				return FALSE;
+			parser->cur_type = ARG_PARSE_ATOM;
+			break;
+		}
+
+		parser->cur_pos++;
+	}
+
+	i_assert(data_size > 0);
+
+	switch (parser->cur_type) {
+	case ARG_PARSE_ATOM:
+		if (!managesieve_parser_read_atom(parser, data, data_size))
+			return FALSE;
+		break;
+	case ARG_PARSE_STRING:
+		if ((parser->flags & MANAGESIEVE_PARSE_FLAG_STRING_STREAM) != 0) {
+			parser->eol = TRUE;
+			parser->line_size += parser->cur_pos;
+			i_stream_skip(parser->input, parser->cur_pos);
+			parser->cur_pos = 0;
+			parser->str_stream = quoted_string_istream_create(parser);
+			managesieve_parser_save_arg(parser, NULL, 0);
+
+		} else if (!managesieve_parser_read_string(parser, data, data_size)) {
+			return FALSE;
+		}
+		break;
+	case ARG_PARSE_LITERAL:
+		if (!managesieve_parser_read_literal(parser, data, data_size))
+			return FALSE;
+
+		/* pass through to parsing data. since input->skip was
+		   modified, we need to get the data start position again. */
+		data = i_stream_get_data(parser->input, &data_size);
+
+		/* fall through */
+	case ARG_PARSE_LITERAL_DATA:
+		if (!managesieve_parser_read_literal_data(parser, data, data_size))
+			return FALSE;
+		break;
+	default:
+		i_unreached();
+	}
+
+	i_assert(parser->cur_type == ARG_PARSE_NONE);
+	return TRUE;
+}
+
+/* ARG_PARSE_NONE checks that last argument isn't only partially parsed. */
+#define IS_UNFINISHED(parser) \
+	((parser)->cur_type != ARG_PARSE_NONE || \
+	(parser)->cur_list != &parser->root_list)
+
+static int finish_line
+(struct managesieve_parser *parser, unsigned int count,
+	const struct managesieve_arg **args_r)
+{
+	struct managesieve_arg *arg;
+	int ret = array_count(&parser->root_list);
+
+	parser->line_size += parser->cur_pos;
+	i_stream_skip(parser->input, parser->cur_pos);
+	parser->cur_pos = 0;
+
+	/* fill the missing parameters with NILs */
+	while (count > array_count(&parser->root_list)) {
+		arg = array_append_space(&parser->root_list);
+		arg->type = MANAGESIEVE_ARG_NONE;
+	}
+	arg = array_append_space(&parser->root_list);
+	arg->type = MANAGESIEVE_ARG_EOL;
+
+	*args_r = array_get(&parser->root_list, &count);
+	return ret;
+}
+
+int managesieve_parser_read_args
+(struct managesieve_parser *parser, unsigned int count,
+	enum managesieve_parser_flags flags, const struct managesieve_arg **args_r)
+{
+	parser->flags = flags;
+
+	while ( !parser->eol && (count == 0 || IS_UNFINISHED(parser)
+		|| array_count(&parser->root_list) < count) ) {
+		if ( !managesieve_parser_read_arg(parser) )
+			break;
+
+		if ( parser->line_size > parser->max_line_size ) {
+			parser->error = "MANAGESIEVE command line too large";
+			break;
+		}
+	}
+
+	if ( parser->error != NULL ) {
+		/* error, abort */
+		parser->line_size += parser->cur_pos;
+		i_stream_skip(parser->input, parser->cur_pos);
+		parser->cur_pos = 0;
+		*args_r = NULL;
+		return -1;
+	} else if ( (!IS_UNFINISHED(parser) && count > 0
+		&& array_count(&parser->root_list) >= count) || parser->eol ) {
+		/* all arguments read / end of line. */
+		return finish_line(parser, count, args_r);
+	} else {
+		/* need more data */
+		*args_r = NULL;
+		return -2;
+	}
+}
+
+int managesieve_parser_finish_line
+(struct managesieve_parser *parser, unsigned int count,
+	enum managesieve_parser_flags flags, const struct managesieve_arg **args_r)
+{
+	const unsigned char *data;
+	size_t data_size;
+	int ret;
+
+	ret = managesieve_parser_read_args(parser, count, flags, args_r);
+	if (ret == -2) {
+		/* we should have noticed end of everything except atom */
+		if (parser->cur_type == ARG_PARSE_ATOM) {
+			data = i_stream_get_data(parser->input, &data_size);
+			managesieve_parser_save_arg(parser, data, data_size);
+		}
+	}
+	return finish_line(parser, count, args_r);
+}
+
+const char *managesieve_parser_read_word(struct managesieve_parser *parser)
+{
+	const unsigned char *data;
+	size_t i, data_size;
+
+	data = i_stream_get_data(parser->input, &data_size);
+
+	for (i = 0; i < data_size; i++) {
+		if (data[i] == ' ' || data[i] == '\r' || data[i] == '\n')
+			break;
+	}
+
+	if (i < data_size) {
+		data_size = i + (data[i] == ' ' ? 1 : 0);
+		parser->line_size += data_size;
+		i_stream_skip(parser->input, data_size);
+		return p_strndup(parser->pool, data, i);
+	} else {
+		return NULL;
+	}
+}
+
+/*
+ * Quoted string stream
+ */
+
+struct quoted_string_istream {
+	struct istream_private istream;
+
+	/* The end '"' was found */
+	bool str_end:1;
+};
+
+static int
+quoted_string_istream_read_parent(struct quoted_string_istream *qsstream,
+				  unsigned int min_bytes)
+{
+	struct istream_private *stream = &qsstream->istream;
+	size_t size, avail;
+	ssize_t ret;
+
+	size = i_stream_get_data_size(stream->parent);
+	while (size < min_bytes) {
+		ret = i_stream_read_memarea(stream->parent);
+		if (ret <= 0) {
+			if (ret == -2) {
+				/* tiny parent buffer size - shouldn't happen */
+				return -2;
+			}
+			stream->istream.stream_errno =
+				stream->parent->stream_errno;
+			stream->istream.eof = stream->parent->eof;
+			if (ret == -1 && stream->istream.stream_errno == 0) {
+				io_stream_set_error(&stream->iostream,
+					"Quoted string ends without closing quotes");
+				stream->istream.stream_errno = EPIPE;
+			}
+			return ret;
+		}
+		size = i_stream_get_data_size(stream->parent);
+	}
+
+	if (!i_stream_try_alloc(stream, size, &avail))
+		return -2;
+	return 1;
+}
+
+static ssize_t quoted_string_istream_read(struct istream_private *stream)
+{
+	struct quoted_string_istream *qsstream =
+		(struct quoted_string_istream *)stream;
+	const unsigned char *data;
+	unsigned int extra;
+	size_t i, dest, size;
+	ssize_t ret;
+
+	if (qsstream->str_end) {
+		stream->istream.eof = TRUE;
+		return -1;
+	}
+
+	ret = quoted_string_istream_read_parent(qsstream, 1);
+	if (ret <= 0)
+		return ret;
+
+	/* @UNSAFE */
+	dest = stream->pos;
+	extra = 0;
+
+	data = i_stream_get_data(stream->parent, &size);
+	for (i = 0; i < size && dest < stream->buffer_size; ) {
+		if (data[i] == '"') {
+			i++;
+			qsstream->str_end = TRUE;
+			if (dest == stream->pos) {
+				i_stream_skip(stream->parent, i);
+				stream->istream.eof = TRUE;
+				return -1;
+			}
+			break;
+		} else if (data[i] == '\\') {
+			if (i+1 == size) {
+				/* not enough input for \x */
+				extra = 1;
+				break;
+			}
+			i++;
+
+			if (!IS_QUOTED_SPECIAL(data[i])) {
+				/* invalid string */
+				io_stream_set_error(&stream->iostream,
+					"Escaped quoted-string character is not a QUOTED-SPECIAL");
+				stream->istream.stream_errno = EINVAL;
+				return -1;
+			}
+			stream->w_buffer[dest++] = data[i];
+			i++;
+		} else {
+			if (data[i] == '\r' || data[i] == '\n') {
+				io_stream_set_error(&stream->iostream,
+					"Quoted string contains an invalid character");
+				stream->istream.stream_errno = EINVAL;
+				return -1;
+			}
+
+			stream->w_buffer[dest++] = data[i];
+			i++;
+		}
+		i_assert(dest <= stream->buffer_size);
+	}
+	i_stream_skip(stream->parent, i);
+
+	ret = dest - stream->pos;
+	if (ret == 0) {
+		/* not enough input */
+		i_assert(i == 0);
+		i_assert(extra > 0);
+		ret = quoted_string_istream_read_parent(qsstream, extra+1);
+		if (ret <= 0)
+			return ret;
+		return quoted_string_istream_read(stream);
+	}
+	i_assert(ret > 0);
+	stream->pos = dest;
+	return ret;
+}
+
+static struct istream *quoted_string_istream_create
+(struct managesieve_parser *parser)
+{
+	struct quoted_string_istream *qsstream;
+
+	qsstream = i_new(struct quoted_string_istream, 1);
+	qsstream->istream.max_buffer_size =
+		parser->input->real_stream->max_buffer_size;
+	qsstream->istream.read = quoted_string_istream_read;
+
+	qsstream->istream.istream.readable_fd = FALSE;
+	qsstream->istream.istream.blocking = parser->input->blocking;
+	qsstream->istream.istream.seekable = FALSE;
+	return i_stream_create(&qsstream->istream, parser->input,
+			       i_stream_get_fd(parser->input), 0);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-managesieve/managesieve-parser.h
@@ -0,0 +1,65 @@
+#ifndef MANAGESIEVE_PARSER_H
+#define MANAGESIEVE_PARSER_H
+
+#include "managesieve-arg.h"
+
+enum managesieve_parser_flags {
+	/* Set this flag if you want to read a string argument as as stream. Useful
+	   when you need to deal with large strings. The string must be the last read
+	   argument. */
+	MANAGESIEVE_PARSE_FLAG_STRING_STREAM = 0x01,
+	/* Don't remove '\' chars from string arguments */
+	MANAGESIEVE_PARSE_FLAG_NO_UNESCAPE	= 0x02,
+	/* Return literals as MANAGESIEVE_ARG_LITERAL instead of
+	   MANAGESIEVE_ARG_STRING */
+	MANAGESIEVE_PARSE_FLAG_LITERAL_TYPE	= 0x04
+};
+
+struct managesieve_parser;
+
+/* Create new MANAGESIEVE argument parser.
+
+   max_line_size can be used to approximately limit the maximum amount of
+   memory that gets allocated when parsing a line. Input buffer size limits
+   the maximum size of each parsed token.
+
+   Usually the largest lines are large only because they have a one huge
+   message set token, so you'll probably want to keep input buffer size the
+   same as max_line_size. That means the maximum memory usage is around
+   2 * max_line_size. */
+struct managesieve_parser *
+managesieve_parser_create(struct istream *input, size_t max_line_size);
+void managesieve_parser_destroy(struct managesieve_parser **parser);
+
+/* Reset the parser to initial state. */
+void managesieve_parser_reset(struct managesieve_parser *parser);
+
+/* Return the last error in parser. fatal is set to TRUE if there's no way to
+   continue parsing, currently only if too large non-sync literal size was
+   given. */
+const char *managesieve_parser_get_error
+	(struct managesieve_parser *parser, bool *fatal);
+
+/* Read a number of arguments. This function doesn't call i_stream_read(), you
+   need to do that. Returns number of arguments read (may be less than count
+   in case of EOL), -2 if more data is needed or -1 if error occurred.
+
+   count-sized array of arguments are stored into args when return value is
+   0 or larger. If all arguments weren't read, they're set to NIL. count
+   can be set to 0 to read all arguments in the line. Last element in
+   args is always of type MANAGESIEVE_ARG_EOL. */
+int managesieve_parser_read_args
+	(struct managesieve_parser *parser, unsigned int count,
+		enum managesieve_parser_flags flags, const struct managesieve_arg **args_r);
+
+/* just like managesieve_parser_read_args(), but assume \n at end of data in
+   input stream. */
+int managesieve_parser_finish_line
+	(struct managesieve_parser *parser, unsigned int count,
+		enum managesieve_parser_flags flags, const struct managesieve_arg **args_r);
+
+/* Read one word - used for reading command name.
+   Returns NULL if more data is needed. */
+const char *managesieve_parser_read_word(struct managesieve_parser *parser);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-managesieve/managesieve-quote.c
@@ -0,0 +1,121 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "unichar.h"
+#include "managesieve-parser.h"
+#include "managesieve-quote.h"
+
+/* Turn the value string into a valid MANAGESIEVE string or literal, no matter
+ * what. QUOTED-SPECIALS are escaped, but any invalid (UTF-8) character
+ * is simply removed. Linebreak characters are not considered invalid, but
+ * they do force the generation of a string literal.
+ */
+void managesieve_quote_append(string_t *str, const unsigned char *value,
+		       size_t value_len, bool compress_lwsp)
+{
+	size_t i, extra = 0, escape = 0;
+	string_t *tmp;
+	bool
+		last_lwsp = TRUE,
+		literal = FALSE,
+		modify = FALSE;
+
+ 	if (value == NULL) {
+		str_append(str, "\"\"");
+		return;
+	}
+
+	if (value_len == (size_t)-1)
+		value_len = strlen((const char *) value);
+
+	for (i = 0; i < value_len; i++) {
+		switch (value[i]) {
+		case ' ':
+		case '\t':
+			if (last_lwsp && compress_lwsp) {
+				modify = TRUE;
+				extra++;
+			}
+			last_lwsp = TRUE;
+			break;
+		case '"':
+		case '\\':
+			escape++;
+			last_lwsp = FALSE;
+			break;
+		case 13:
+		case 10:
+			literal = TRUE;
+			last_lwsp = TRUE;
+			break;
+		default:
+			last_lwsp = FALSE;
+		}
+	}
+
+	if (!literal) {
+		/* no linebreak chars, return as (escaped) "string" */
+		str_append_c(str, '"');
+	} else {
+		/* return as literal */
+		str_printfa(str, "{%"PRIuSIZE_T"}\r\n", value_len - extra);
+	}
+
+	tmp = t_str_new(value_len+escape+4);
+	if (!modify && (literal || escape == 0))
+		str_append_data(tmp, value, value_len);
+	else {
+		last_lwsp = TRUE;
+		for (i = 0; i < value_len; i++) {
+			switch (value[i]) {
+			case '"':
+			case '\\':
+				last_lwsp = FALSE;
+				if (!literal)
+					str_append_c(tmp, '\\');
+				str_append_c(tmp, value[i]);
+				break;
+			case ' ':
+			case '\t':
+				if (!last_lwsp || !compress_lwsp)
+					str_append_c(tmp, ' ');
+				last_lwsp = TRUE;
+				break;
+			case 13:
+			case 10:
+				last_lwsp = TRUE;
+				str_append_c(tmp, value[i]);
+				break;
+			default:
+				last_lwsp = FALSE;
+				str_append_c(tmp, value[i]);
+				break;
+			}
+		}
+	}
+
+	if ( uni_utf8_get_valid_data(str_data(tmp), str_len(tmp), str) )
+		str_append_str(str, tmp);
+
+	if (!literal)
+		str_append_c(str, '"');
+}
+
+char *managesieve_quote(pool_t pool, const unsigned char *value, size_t value_len)
+{
+	string_t *str;
+	char *ret;
+
+	if (value == NULL)
+		return "\"\"";
+
+	T_BEGIN {
+		str = t_str_new(value_len + MAX_INT_STRLEN + 5);
+		managesieve_quote_append(str, value, value_len, TRUE);
+		ret = p_strndup(pool, str_data(str), str_len(str));
+	} T_END;
+
+	return ret;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-managesieve/managesieve-quote.h
@@ -0,0 +1,17 @@
+#ifndef IMAP_QUOTE_H
+#define IMAP_QUOTE_H
+
+/* Return value suitable for sending to client, either as quoted-string or
+   literal. Note that this also converts TABs into spaces, multiple spaces
+   into single space and NULs to #128. */
+char *managesieve_quote(pool_t pool, const unsigned char *value, size_t value_len);
+
+/* Append to existing string. */
+void managesieve_quote_append(string_t *str, const unsigned char *value,
+		       size_t value_len, bool compress_lwsp);
+
+#define managesieve_quote_append_string(str, value, compress_lwsp) \
+	managesieve_quote_append(str, (const unsigned char *)(value), \
+			  (size_t)-1, compress_lwsp)
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve-tool/Makefile.am
@@ -0,0 +1,13 @@
+noinst_LTLIBRARIES = libsieve-tool.la
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib-sieve \
+	-I$(top_srcdir)/src/lib-sieve/util \
+	$(LIBDOVECOT_INCLUDE) \
+	$(LIBDOVECOT_SERVICE_INCLUDE)
+
+libsieve_tool_la_SOURCES = \
+	sieve-tool.c
+
+noinst_HEADERS = \
+	sieve-tool.h
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve-tool/sieve-tool.c
@@ -0,0 +1,658 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "lib-signals.h"
+#include "array.h"
+#include "ioloop.h"
+#include "ostream.h"
+#include "hostpid.h"
+#include "dict.h"
+#include "mail-namespace.h"
+#include "mail-storage.h"
+#include "mail-user.h"
+#include "message-address.h"
+#include "smtp-params.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "mail-storage-service.h"
+
+#include "sieve.h"
+#include "sieve-plugins.h"
+#include "sieve-extensions.h"
+
+#include "mail-raw.h"
+
+#include "sieve-tool.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <sysexits.h>
+
+/*
+ * Global state
+ */
+
+struct sieve_tool {
+	char *name;
+
+	bool no_config;
+
+	char *username;
+	char *homedir;
+
+	char *sieve_extensions;
+	ARRAY_TYPE(const_string) sieve_plugins;
+
+	sieve_tool_setting_callback_t setting_callback;
+	void *setting_callback_context;
+
+	struct sieve_instance *svinst;
+
+	struct mail_storage_service_ctx *storage_service;
+	struct mail_storage_service_user *service_user;
+	struct mail_user *mail_user_dovecot;
+	struct mail_user *mail_user;
+
+	struct mail_user *mail_raw_user;
+	struct mail_raw *mail_raw;
+
+	bool debug:1;
+};
+
+struct sieve_tool *sieve_tool;
+
+/*
+ * Settings management
+ */
+
+static const char *sieve_tool_sieve_get_setting
+(void *context, const char *identifier)
+{
+	struct sieve_tool *tool = (struct sieve_tool *) context;
+
+	if ( tool->setting_callback != NULL )
+		return tool->setting_callback(tool->setting_callback_context, identifier);
+
+	if ( tool->mail_user_dovecot == NULL )
+		return NULL;
+
+	return mail_user_plugin_getenv(tool->mail_user_dovecot, identifier);
+}
+
+static const char *sieve_tool_sieve_get_homedir
+(void *context)
+{
+	struct sieve_tool *tool = (struct sieve_tool *) context;
+
+	return sieve_tool_get_homedir(tool);
+}
+
+const struct sieve_callbacks sieve_tool_callbacks = {
+	sieve_tool_sieve_get_homedir,
+	sieve_tool_sieve_get_setting
+};
+
+/*
+ * Initialization
+ */
+
+static void sieve_tool_get_user_data
+(const char **username_r, const char **homedir_r)
+{
+	uid_t process_euid = geteuid();
+	struct passwd *pw;
+	const char *user = NULL, *home = NULL;
+
+	user = getenv("USER");
+	home = getenv("HOME");
+
+	if ( user == NULL || *user == '\0' ||
+		home == NULL || *home == '\0' ) {
+
+		if ((pw = getpwuid(process_euid)) != NULL) {
+			user = pw->pw_name;
+			home = pw->pw_dir;
+		}
+	}
+
+	if ( username_r != NULL ) {
+		if ( user == NULL || *user == '\0' ) {
+			i_fatal("couldn't lookup our username (uid=%s)",
+				dec2str(process_euid));
+		}
+
+		*username_r = t_strdup(user);
+	}
+
+	if ( homedir_r != NULL )
+		*homedir_r = t_strdup(home);
+}
+
+
+struct sieve_tool *sieve_tool_init
+(const char *name, int *argc, char **argv[], const char *getopt_str,
+	bool no_config)
+{
+	struct sieve_tool *tool;
+	enum master_service_flags service_flags =
+		MASTER_SERVICE_FLAG_STANDALONE |
+		MASTER_SERVICE_FLAG_DONT_SEND_STATS |
+		MASTER_SERVICE_FLAG_NO_INIT_DATASTACK_FRAME;
+
+	if ( no_config )
+		service_flags |= MASTER_SERVICE_FLAG_NO_CONFIG_SETTINGS;
+
+	master_service = master_service_init
+		(name, service_flags, argc, argv, getopt_str);
+
+	tool = i_new(struct sieve_tool, 1);
+	tool->name = i_strdup(name);
+	tool->no_config = no_config;
+
+	i_array_init(&tool->sieve_plugins, 16);
+
+	return tool;
+}
+
+int sieve_tool_getopt(struct sieve_tool *tool)
+{
+	int c;
+
+	while ( (c = master_getopt(master_service)) > 0 ) {
+		switch ( c ) {
+		case 'x':
+			/* extensions */
+			if ( tool->sieve_extensions != NULL ) {
+				i_fatal_status(EX_USAGE,
+					"duplicate -x option specified, but only one allowed.");
+			}
+
+			tool->sieve_extensions = i_strdup(optarg);
+			break;
+		case 'u':
+			if ( tool->username == NULL )
+				tool->username = i_strdup(optarg);
+			break;
+		case 'P':
+			/* Plugin */
+			{
+				const char *plugin;
+
+				plugin = t_strdup(optarg);
+				array_append(&tool->sieve_plugins, &plugin, 1);
+			}
+			break;
+		case 'D':
+			tool->debug = TRUE;
+			break;
+		default:
+			return c;
+		}
+	}
+
+	return c;
+}
+
+static void sieve_tool_load_plugins
+(struct sieve_tool *tool)
+{
+	unsigned int i, count;
+	const char * const *plugins;
+
+	plugins = array_get(&tool->sieve_plugins, &count);
+	for ( i = 0; i < count; i++ ) {
+		const char *path, *file = strrchr(plugins[i], '/');
+
+		if ( file != NULL ) {
+			path = t_strdup_until(plugins[i], file);
+			file = file+1;
+		} else {
+			path = NULL;
+			file = plugins[i];
+		}
+
+		sieve_plugins_load(tool->svinst, path, file);
+	}
+}
+
+struct sieve_instance *sieve_tool_init_finish
+(struct sieve_tool *tool, bool init_mailstore, bool preserve_root)
+{
+	enum mail_storage_service_flags storage_service_flags =
+		MAIL_STORAGE_SERVICE_FLAG_NO_CHDIR |
+		MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT |
+		MAIL_STORAGE_SERVICE_FLAG_USE_SYSEXITS;
+	struct mail_storage_service_input service_input;
+	struct sieve_environment svenv;
+	const char *username = tool->username;
+	const char *homedir = tool->homedir;
+	const char *errstr;
+
+	master_service_init_finish(master_service);
+
+	if ( username == NULL ) {
+		sieve_tool_get_user_data(&username, &homedir);
+
+		username = tool->username = i_strdup(username);
+
+		if ( tool->homedir != NULL )
+			i_free(tool->homedir);
+		tool->homedir = i_strdup(homedir);
+
+		if ( preserve_root ) {
+			storage_service_flags |=
+				MAIL_STORAGE_SERVICE_FLAG_NO_RESTRICT_ACCESS;
+		}
+	} else {
+		storage_service_flags |=
+			MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP;
+	}
+
+	if ( !init_mailstore )
+		storage_service_flags |=
+			MAIL_STORAGE_SERVICE_FLAG_NO_NAMESPACES;
+
+	i_zero(&service_input);
+	service_input.module = "mail";
+	service_input.service = tool->name;
+	service_input.username = username;
+
+	tool->storage_service = mail_storage_service_init
+		(master_service, NULL, storage_service_flags);
+	if (mail_storage_service_lookup_next
+		(tool->storage_service, &service_input, &tool->service_user,
+			&tool->mail_user_dovecot, &errstr) <= 0)
+		i_fatal("%s", errstr);
+
+	if ( master_service_set
+		(master_service, "mail_full_filesystem_access=yes") < 0 )
+		i_unreached();
+
+	memset((void *)&svenv, 0, sizeof(svenv));
+	svenv.username = username;
+	(void)mail_user_get_home(tool->mail_user_dovecot, &svenv.home_dir);
+	svenv.hostname = my_hostdomain();
+	svenv.base_dir = tool->mail_user_dovecot->set->base_dir;
+	svenv.temp_dir = tool->mail_user_dovecot->set->mail_temp_dir;
+	svenv.location = SIEVE_ENV_LOCATION_MS;
+	svenv.delivery_phase = SIEVE_DELIVERY_PHASE_POST;
+
+	/* Initialize Sieve Engine */
+	if ( (tool->svinst=sieve_init
+		(&svenv, &sieve_tool_callbacks, tool, tool->debug)) == NULL )
+		i_fatal("failed to initialize sieve implementation");
+
+	/* Load Sieve plugins */
+	if ( array_count(&tool->sieve_plugins) > 0 ) {
+		sieve_tool_load_plugins(tool);
+	}
+
+	/* Set active Sieve extensions */
+	if ( tool->sieve_extensions != NULL ) {
+		sieve_set_extensions(tool->svinst, tool->sieve_extensions);
+	} else if ( tool->no_config ) {
+		sieve_set_extensions(tool->svinst, NULL);
+	}
+
+	return tool->svinst;
+}
+
+void sieve_tool_deinit(struct sieve_tool **_tool)
+{
+	struct sieve_tool *tool = *_tool;
+
+	*_tool = NULL;
+
+	/* Deinitialize Sieve engine */
+	sieve_deinit(&tool->svinst);
+
+	/* Free options */
+
+	if ( tool->username != NULL )
+		i_free(tool->username);
+	if ( tool->homedir != NULL )
+		i_free(tool->homedir);
+
+	if ( tool->sieve_extensions != NULL )
+		i_free(tool->sieve_extensions);
+	array_free(&tool->sieve_plugins);
+
+	/* Free raw mail */
+
+	if ( tool->mail_raw != NULL )
+		mail_raw_close(&tool->mail_raw);
+
+	if ( tool->mail_raw_user != NULL )
+		mail_user_unref(&tool->mail_raw_user);
+
+	/* Free mail service */
+
+	if ( tool->mail_user != NULL )
+		mail_user_unref(&tool->mail_user);
+	if ( tool->mail_user_dovecot != NULL )
+		mail_user_unref(&tool->mail_user_dovecot);
+
+	mail_storage_service_user_unref(&tool->service_user);
+	mail_storage_service_deinit(&tool->storage_service);
+
+	/* Free sieve tool object */
+
+	i_free(tool->name);
+	i_free(tool);
+
+	/* Deinitialize service */
+	master_service_deinit(&master_service);
+}
+
+/*
+ * Mail environment
+ */
+
+void sieve_tool_init_mail_user
+(struct sieve_tool *tool, const char *mail_location)
+{
+	struct mail_user *mail_user_dovecot = tool->mail_user_dovecot;
+	const char *username = tool->username;
+	struct mail_namespace *ns = NULL;
+	const char *home = NULL, *errstr = NULL;
+
+	tool->mail_user = mail_user_alloc
+		(NULL, username, mail_user_dovecot->set_info, mail_user_dovecot->unexpanded_set);
+
+	if ( (home=sieve_tool_get_homedir(sieve_tool)) != NULL ) {
+		mail_user_set_home(tool->mail_user, home);
+	}
+
+	if ( mail_user_init(tool->mail_user, &errstr) < 0 )
+ 		i_fatal("Test user initialization failed: %s", errstr);
+
+	if ( mail_namespaces_init_location
+		(tool->mail_user, mail_location, &errstr) < 0 )
+		i_fatal("Test storage creation failed: %s", errstr);
+
+	ns = tool->mail_user->namespaces;
+	ns->flags |= NAMESPACE_FLAG_NOQUOTA | NAMESPACE_FLAG_NOACL;
+}
+
+struct mail *sieve_tool_open_file_as_mail
+(struct sieve_tool *tool, const char *path)
+{
+	if ( tool->mail_raw_user == NULL )
+		tool->mail_raw_user = mail_raw_user_create
+			(master_service, tool->mail_user_dovecot);
+
+	if ( tool->mail_raw != NULL )
+		mail_raw_close(&tool->mail_raw);
+
+	tool->mail_raw = mail_raw_open_file(tool->mail_raw_user, path);
+
+	return tool->mail_raw->mail;
+}
+
+struct mail *sieve_tool_open_data_as_mail
+(struct sieve_tool *tool, string_t *mail_data)
+{
+	if ( tool->mail_raw_user == NULL )
+		tool->mail_raw_user = mail_raw_user_create
+			(master_service, tool->mail_user_dovecot);
+
+	if ( tool->mail_raw != NULL )
+		mail_raw_close(&tool->mail_raw);
+
+	tool->mail_raw = mail_raw_open_data(tool->mail_raw_user, mail_data);
+
+	return tool->mail_raw->mail;
+}
+
+/*
+ * Configuration
+ */
+
+void sieve_tool_set_homedir(struct sieve_tool *tool, const char *homedir)
+{
+	if ( tool->homedir != NULL ) {
+		if ( strcmp(homedir, tool->homedir) == 0 )
+			return;
+
+		i_free(tool->homedir);
+	}
+
+	tool->homedir = i_strdup(homedir);
+
+	if ( tool->mail_user_dovecot != NULL )
+		mail_user_set_home(tool->mail_user_dovecot, tool->homedir);
+	if ( tool->mail_user != NULL )
+		mail_user_set_home(tool->mail_user, tool->homedir);
+}
+
+void sieve_tool_set_setting_callback
+(struct sieve_tool *tool, sieve_tool_setting_callback_t callback, void *context)
+{
+	tool->setting_callback = callback;
+	tool->setting_callback_context = context;
+}
+
+/*
+ * Accessors
+ */
+
+const char *sieve_tool_get_username
+(struct sieve_tool *tool)
+{
+	const char *username;
+
+	if ( tool->username == NULL ) {
+		sieve_tool_get_user_data(&username, NULL);
+		return username;
+	}
+
+	return tool->username;
+}
+
+const char *sieve_tool_get_homedir
+(struct sieve_tool *tool)
+{
+	const char *homedir = NULL;
+
+	if ( tool->homedir != NULL )
+		return tool->homedir;
+
+	if ( tool->mail_user_dovecot != NULL &&
+		mail_user_get_home(tool->mail_user_dovecot, &homedir) > 0 ) {
+		return tool->homedir;
+	}
+
+	sieve_tool_get_user_data(NULL, &homedir);
+	return homedir;
+}
+
+struct mail_user *sieve_tool_get_mail_user
+(struct sieve_tool *tool)
+{
+	return ( tool->mail_user == NULL ?
+		tool->mail_user_dovecot : tool->mail_user );
+}
+
+/*
+ * Commonly needed functionality
+ */
+
+static const struct smtp_address *
+sieve_tool_get_address(struct mail *mail, const char *header)
+{
+    struct message_address *addr;
+    const char *str;
+
+    if (mail_get_first_header(mail, header, &str) <= 0)
+        return NULL;
+    addr = message_address_parse(pool_datastack_create(),
+                     (const unsigned char *)str,
+                     strlen(str), 1, 0);
+    return addr == NULL || addr->mailbox == NULL || addr->domain == NULL ||
+        *addr->mailbox == '\0' || *addr->domain == '\0' ?
+        NULL : smtp_address_create_temp(addr->mailbox, addr->domain);
+}
+
+void sieve_tool_get_envelope_data
+(struct sieve_message_data *msgdata, struct mail *mail,
+	const struct smtp_address *sender,
+	const struct smtp_address *rcpt_orig,
+	const struct smtp_address *rcpt_final)
+{
+	struct smtp_params_rcpt *rcpt_params;
+
+	/* Get sender address */
+	if ( sender == NULL )
+		sender = sieve_tool_get_address(mail, "Return-path");
+	if ( sender == NULL )
+		sender = sieve_tool_get_address(mail, "Sender");
+	if ( sender == NULL )
+		sender = sieve_tool_get_address(mail, "From");
+	if ( sender == NULL )
+		sender = smtp_address_create_temp("sender", "example.com");
+
+	/* Get recipient address */
+	if ( rcpt_final == NULL )
+		rcpt_final = sieve_tool_get_address(mail, "Envelope-To");
+	if ( rcpt_final == NULL )
+		rcpt_final = sieve_tool_get_address(mail, "To");
+	if ( rcpt_final == NULL )
+		rcpt_final = smtp_address_create_temp("recipient", "example.com");
+	if ( rcpt_orig == NULL )
+		rcpt_orig = rcpt_final;
+
+	msgdata->envelope.mail_from = sender;
+	msgdata->envelope.rcpt_to = rcpt_final;
+
+	rcpt_params = t_new(struct smtp_params_rcpt, 1);
+	rcpt_params->orcpt.addr = rcpt_orig;
+
+	msgdata->envelope.rcpt_params = rcpt_params;
+}
+
+/*
+ * File I/O
+ */
+
+struct ostream *sieve_tool_open_output_stream(const char *filename)
+{
+	struct ostream *outstream;
+	int fd;
+
+	if ( strcmp(filename, "-") == 0 )
+		outstream = o_stream_create_fd(1, 0);
+	else {
+		if ( (fd = open(filename, O_WRONLY | O_TRUNC | O_CREAT, 0600)) < 0 ) {
+			i_fatal("failed to open file for writing: %m");
+		}
+
+		outstream = o_stream_create_fd_autoclose(&fd, 0);
+	}
+
+	return outstream;
+}
+
+/*
+ * Sieve script handling
+ */
+
+struct sieve_binary *sieve_tool_script_compile
+(struct sieve_instance *svinst, const char *filename, const char *name)
+{
+	struct sieve_error_handler *ehandler;
+	struct sieve_binary *sbin;
+
+	ehandler = sieve_stderr_ehandler_create(svinst, 0);
+	sieve_error_handler_accept_infolog(ehandler, TRUE);
+	sieve_error_handler_accept_debuglog(ehandler, svinst->debug);
+
+	if ( (sbin = sieve_compile
+		(svinst, filename, name, ehandler, 0, NULL)) == NULL )
+		i_fatal("failed to compile sieve script '%s'", filename);
+
+	sieve_error_handler_unref(&ehandler);
+
+	return sbin;
+}
+
+struct sieve_binary *sieve_tool_script_open
+(struct sieve_instance *svinst, const char *filename)
+{
+	struct sieve_error_handler *ehandler;
+	struct sieve_binary *sbin;
+
+	ehandler = sieve_stderr_ehandler_create(svinst, 0);
+	sieve_error_handler_accept_infolog(ehandler, TRUE);
+	sieve_error_handler_accept_debuglog(ehandler, svinst->debug);
+
+	if ( (sbin = sieve_open
+		(svinst, filename, NULL, ehandler, 0, NULL)) == NULL ) {
+		sieve_error_handler_unref(&ehandler);
+		i_fatal("failed to compile sieve script");
+	}
+
+	sieve_error_handler_unref(&ehandler);
+
+	sieve_save(sbin, FALSE, NULL);
+	return sbin;
+}
+
+void sieve_tool_dump_binary_to
+(struct sieve_binary *sbin, const char *filename, bool hexdump)
+{
+	struct ostream *dumpstream;
+
+	if ( filename == NULL ) return;
+
+	dumpstream = sieve_tool_open_output_stream(filename);
+	if ( dumpstream != NULL ) {
+		if ( hexdump )
+			(void) sieve_hexdump(sbin, dumpstream);
+		else
+			(void) sieve_dump(sbin, dumpstream, FALSE);
+		if (o_stream_finish(dumpstream) < 0) {
+			i_fatal("write(%s) failed: %s", filename,
+				o_stream_get_error(dumpstream));
+		}
+		o_stream_destroy(&dumpstream);
+	} else {
+		i_fatal("Failed to create stream for sieve code dump.");
+	}
+}
+
+/*
+ * Commandline option parsing
+ */
+
+void sieve_tool_parse_trace_option
+(struct sieve_trace_config *tr_config, const char *tr_option)
+{
+    if ( str_begins(tr_option, "level=")) {
+        const char *lvl = &tr_option[6];
+
+        if ( strcmp(lvl, "none") == 0 ) {
+            tr_config->level = SIEVE_TRLVL_NONE;
+        } else if ( strcmp(lvl, "actions") == 0 ) {
+            tr_config->level = SIEVE_TRLVL_ACTIONS;
+        } else if ( strcmp(lvl, "commands") == 0 ) {
+            tr_config->level = SIEVE_TRLVL_COMMANDS;
+        } else if ( strcmp(lvl, "tests") == 0 ) {
+            tr_config->level = SIEVE_TRLVL_TESTS;
+        } else if ( strcmp(lvl, "matching") == 0 ) {
+            tr_config->level = SIEVE_TRLVL_MATCHING;
+        } else {
+            i_fatal_status(EX_USAGE, "Unknown -tlevel= trace level: %s", lvl);
+        }
+    } else if ( strcmp(tr_option, "debug") == 0 ) {
+        tr_config->flags |= SIEVE_TRFLG_DEBUG;
+    } else if ( strcmp(tr_option, "addresses") == 0 ) {
+        tr_config->flags |= SIEVE_TRFLG_ADDRESSES;
+    } else {
+        i_fatal_status(EX_USAGE, "Unknown -t trace option value: %s", tr_option);
+    }
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve-tool/sieve-tool.h
@@ -0,0 +1,100 @@
+#ifndef SIEVE_TOOL_H
+#define SIEVE_TOOL_H
+
+#include "sieve-common.h"
+
+/*
+ * Types
+ */
+
+typedef const char *(*sieve_tool_setting_callback_t)
+	(void *context, const char *identifier);
+
+/*
+ * Global variables
+ */
+
+extern struct sieve_tool *sieve_tool;
+
+/*
+ * Initialization
+ */
+
+struct sieve_tool *sieve_tool_init
+	(const char *name, int *argc, char **argv[], const char *getopt_str,
+		bool no_config);
+
+int sieve_tool_getopt(struct sieve_tool *tool);
+
+struct sieve_instance *sieve_tool_init_finish
+	(struct sieve_tool *tool, bool init_mailstore, bool preserve_root);
+
+void sieve_tool_deinit(struct sieve_tool **_tool);
+
+/*
+ * Mail environment
+ */
+
+void sieve_tool_init_mail_user
+	(struct sieve_tool *tool, const char *mail_location);
+
+struct mail *sieve_tool_open_file_as_mail
+	(struct sieve_tool *tool, const char *path);
+struct mail *sieve_tool_open_data_as_mail
+	(struct sieve_tool *tool, string_t *mail_data);
+
+/*
+ * Accessors
+ */
+
+const char *sieve_tool_get_username
+	(struct sieve_tool *tool);
+const char *sieve_tool_get_homedir
+(struct sieve_tool *tool);
+struct mail_user *sieve_tool_get_mail_user
+	(struct sieve_tool *tool);
+
+/*
+ * Configuration
+ */
+
+void sieve_tool_set_homedir(struct sieve_tool *tool, const char *homedir);
+void sieve_tool_set_setting_callback
+	(struct sieve_tool *tool, sieve_tool_setting_callback_t callback,
+		void *context);
+
+/*
+ * Commonly needed functionality
+ */
+
+void sieve_tool_get_envelope_data
+(struct sieve_message_data *msgdata, struct mail *mail,
+	const struct smtp_address *sender,
+	const struct smtp_address *rcpt_orig,
+	const struct smtp_address *rcpt_final);
+
+/*
+ * File I/O
+ */
+
+struct ostream *sieve_tool_open_output_stream(const char *filename);
+
+/*
+ * Sieve script handling
+ */
+
+struct sieve_binary *sieve_tool_script_compile
+	(struct sieve_instance *svinst, const char *filename, const char *name);
+struct sieve_binary *sieve_tool_script_open
+	(struct sieve_instance *svinst, const char *filename);
+void sieve_tool_dump_binary_to
+	(struct sieve_binary *sbin, const char *filename, bool hexdump);
+
+/*
+ * Command line option parsing
+ */
+
+void sieve_tool_parse_trace_option
+	(struct sieve_trace_config *tr_config, const char *tr_option);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/Makefile.am
@@ -0,0 +1,185 @@
+SUBDIRS = util storage plugins
+
+dovecot_pkglib_LTLIBRARIES = libdovecot-sieve.la
+
+AM_CPPFLAGS = \
+	$(LIBDOVECOT_INCLUDE) \
+	$(LIBDOVECOT_SERVICE_INCLUDE) \
+	-I$(top_srcdir)/src/lib-sieve/util \
+	-DMODULEDIR=\""$(dovecot_moduledir)"\"
+
+tests = \
+	tst-truefalse.c \
+	tst-not.c \
+	tst-anyof.c \
+	tst-allof.c \
+	tst-address.c \
+	tst-header.c \
+	tst-exists.c \
+	tst-size.c
+
+commands = \
+	cmd-require.c \
+	cmd-stop.c \
+	cmd-if.c \
+	cmd-keep.c \
+	cmd-redirect.c \
+	cmd-discard.c
+
+extensions = \
+	ext-fileinto.c \
+	ext-reject.c \
+	ext-envelope.c \
+	ext-encoded-character.c
+
+match_types = \
+	mcht-is.c \
+	mcht-contains.c \
+	mcht-matches.c
+
+comparators = \
+	cmp-i-octet.c \
+	cmp-i-ascii-casemap.c
+
+if BUILD_UNFINISHED
+unfinished_storages =
+unfinished_plugins =
+endif
+
+strgdir = $(top_builddir)/src/lib-sieve/storage
+storages = \
+	$(strgdir)/data/libsieve_storage_data.la \
+	$(strgdir)/file/libsieve_storage_file.la \
+	$(strgdir)/dict/libsieve_storage_dict.la \
+	$(strgdir)/ldap/libsieve_storage_ldap.la \
+	$(unfinished_storages)
+
+extdir = $(top_builddir)/src/lib-sieve/plugins
+plugins = \
+	$(extdir)/vacation/libsieve_ext_vacation.la \
+	$(extdir)/subaddress/libsieve_ext_subaddress.la \
+ 	$(extdir)/comparator-i-ascii-numeric/libsieve_ext_comparator-i-ascii-numeric.la \
+	$(extdir)/relational/libsieve_ext_relational.la \
+	$(extdir)/regex/libsieve_ext_regex.la \
+	$(extdir)/copy/libsieve_ext_copy.la \
+	$(extdir)/imap4flags/libsieve_ext_imap4flags.la \
+	$(extdir)/include/libsieve_ext_include.la \
+	$(extdir)/body/libsieve_ext_body.la \
+	$(extdir)/variables/libsieve_ext_variables.la \
+	$(extdir)/enotify/libsieve_ext_enotify.la \
+	$(extdir)/notify/libsieve_ext_notify.la \
+	$(extdir)/environment/libsieve_ext_environment.la \
+	$(extdir)/mailbox/libsieve_ext_mailbox.la \
+	$(extdir)/date/libsieve_ext_date.la \
+	$(extdir)/spamvirustest/libsieve_ext_spamvirustest.la \
+	$(extdir)/ihave/libsieve_ext_ihave.la \
+	$(extdir)/editheader/libsieve_ext_editheader.la \
+	$(extdir)/duplicate/libsieve_ext_duplicate.la \
+	$(extdir)/index/libsieve_ext_index.la \
+	$(extdir)/metadata/libsieve_ext_metadata.la \
+	$(extdir)/mime/libsieve_ext_mime.la \
+	$(extdir)/vnd.dovecot/debug/libsieve_ext_debug.la \
+	$(extdir)/vnd.dovecot/environment/libsieve_ext_vnd_environment.la \
+	$(extdir)/vnd.dovecot/report/libsieve_ext_vnd_report.la \
+	$(unfinished_plugins)
+
+libdovecot_sieve_la_DEPENDENCIES = \
+	$(storages) \
+	$(plugins) \
+	$(top_builddir)/src/lib-sieve/util/libsieve_util.la \
+	$(LIBDOVECOT_STORAGE_DEPS) \
+	$(LIBDOVECOT_DEPS)
+libdovecot_sieve_la_LIBADD = \
+	$(storages) \
+	$(plugins) \
+	$(top_builddir)/src/lib-sieve/util/libsieve_util.la \
+	$(LIBDOVECOT_STORAGE) \
+	$(LIBDOVECOT)
+
+libdovecot_sieve_la_SOURCES = \
+	sieve-settings.c \
+	sieve-message.c \
+	sieve-smtp.c \
+	sieve-lexer.c \
+	sieve-script.c \
+	sieve-storage.c \
+	sieve-storage-sync.c \
+	sieve-ast.c \
+	sieve-binary.c \
+	sieve-binary-file.c \
+	sieve-binary-code.c \
+	sieve-binary-debug.c \
+	sieve-parser.c \
+	sieve-address.c \
+	sieve-validator.c \
+	sieve-generator.c \
+	sieve-interpreter.c \
+	sieve-runtime-trace.c \
+	sieve-code-dumper.c \
+	sieve-binary-dumper.c \
+	sieve-result.c \
+	sieve-error.c \
+	sieve-objects.c \
+	sieve-stringlist.c \
+	sieve-comparators.c \
+	sieve-match-types.c \
+	sieve-address-parts.c \
+	sieve-address-source.c \
+	sieve-match.c \
+	sieve-commands.c \
+	sieve-code.c \
+	sieve-actions.c \
+	sieve-extensions.c \
+	sieve-plugins.c \
+	$(comparators) \
+	$(match_types) \
+	$(tests) \
+	$(commands) \
+	$(extensions) \
+	sieve.c
+
+headers = \
+	sieve-config.h \
+	sieve-types.h \
+	sieve-common.h \
+	sieve-limits.h \
+	sieve-settings.h \
+	sieve-message.h \
+	sieve-smtp.h \
+	sieve-lexer.h \
+	sieve-script.h \
+	sieve-script-private.h \
+	sieve-storage.h \
+	sieve-storage-private.h \
+	sieve-ast.h \
+	sieve-binary.h \
+	sieve-binary-private.h \
+	sieve-parser.h \
+	sieve-address.h \
+	sieve-validator.h \
+	sieve-generator.h \
+	sieve-interpreter.h \
+	sieve-runtime-trace.h \
+	sieve-runtime.h \
+	sieve-code-dumper.h \
+	sieve-binary-dumper.h \
+	sieve-dump.h \
+	sieve-result.h \
+	sieve-error.h \
+	sieve-error-private.h \
+	sieve-objects.h \
+	sieve-stringlist.h \
+	sieve-match.h \
+	sieve-comparators.h \
+	sieve-match-types.h \
+	sieve-address-parts.h \
+	sieve-address-source.h \
+	sieve-commands.h \
+	sieve-code.h \
+	sieve-actions.h \
+	sieve-extensions.h \
+	sieve-plugins.h \
+	sieve.h
+
+pkginc_libdir=$(dovecot_pkgincludedir)/sieve
+pkginc_lib_HEADERS = $(headers)
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/cmd-discard.c
@@ -0,0 +1,164 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-dump.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+
+/*
+ * Discard command
+ *
+ * Syntax
+ *   discard
+ */
+
+static bool cmd_discard_generate
+	(const struct sieve_codegen_env *cgenv,
+		struct sieve_command *ctx ATTR_UNUSED);
+
+const struct sieve_command_def cmd_discard = {
+	.identifier = "discard",
+	.type = SCT_COMMAND,
+	.positional_args = 0,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.generate = cmd_discard_generate
+};
+
+/*
+ * Discard operation
+ */
+
+static bool cmd_discard_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_discard_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def cmd_discard_operation = {
+	.mnemonic = "DISCARD",
+	.code = SIEVE_OPERATION_DISCARD,
+	.dump = cmd_discard_operation_dump,
+	.execute = cmd_discard_operation_execute
+};
+
+/*
+ * Discard actions
+ */
+
+static bool act_discard_equals
+	(const struct sieve_script_env *senv, const struct sieve_action *act1,
+		const struct sieve_action *act2);
+static int act_discard_check_duplicate
+	(const struct sieve_runtime_env *renv,
+		const struct sieve_action *act,
+		const struct sieve_action *act_other);
+static void act_discard_print
+	(const struct sieve_action *action,
+		const struct sieve_result_print_env *rpenv, bool *keep);
+static int act_discard_commit
+	(const struct sieve_action *action,
+		const struct sieve_action_exec_env *aenv, void *tr_context, bool *keep);
+
+const struct sieve_action_def act_discard = {
+	.name = "discard",
+	.equals = act_discard_equals,
+	.check_duplicate = act_discard_check_duplicate,
+	.print = act_discard_print,
+	.commit = act_discard_commit,
+};
+
+/*
+ * Code generation
+ */
+
+static bool cmd_discard_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd ATTR_UNUSED)
+{
+	sieve_operation_emit(cgenv->sblock, NULL, &cmd_discard_operation);
+
+	return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_discard_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "DISCARD");
+	sieve_code_descend(denv);
+
+	return ( sieve_action_opr_optional_dump(denv, address, NULL) == 0 );
+}
+
+/*
+ * Interpretation
+ */
+
+static int cmd_discard_operation_execute
+(const struct sieve_runtime_env *renv ATTR_UNUSED,
+	sieve_size_t *address ATTR_UNUSED)
+{
+	sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS,
+		"discard action; cancel implicit keep");
+
+	if ( sieve_result_add_action
+		(renv, NULL, &act_discard, NULL, NULL, 0, FALSE) < 0 )
+		return SIEVE_EXEC_FAILURE;
+
+	return SIEVE_EXEC_OK;
+}
+
+/*
+ * Action implementation
+ */
+
+static bool act_discard_equals
+(const struct sieve_script_env *senv ATTR_UNUSED,
+	const struct sieve_action *act1 ATTR_UNUSED,
+	const struct sieve_action *act2 ATTR_UNUSED)
+{
+	return TRUE;
+}
+
+static int act_discard_check_duplicate
+(const struct sieve_runtime_env *renv ATTR_UNUSED,
+	const struct sieve_action *act ATTR_UNUSED,
+	const struct sieve_action *act_other ATTR_UNUSED)
+{
+	return 1;
+}
+
+static void act_discard_print
+(const struct sieve_action *action ATTR_UNUSED,
+	const struct sieve_result_print_env *rpenv, bool *keep)
+{
+	sieve_result_action_printf(rpenv, "discard");
+
+	*keep = FALSE;
+}
+
+static int act_discard_commit
+(const struct sieve_action *action ATTR_UNUSED,
+	const struct sieve_action_exec_env *aenv,
+	void *tr_context ATTR_UNUSED, bool *keep)
+{
+	sieve_result_global_log(aenv,
+		"marked message to be discarded if not explicitly delivered "
+		"(discard action)");
+	*keep = FALSE;
+
+	return SIEVE_EXEC_OK;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/cmd-if.c
@@ -0,0 +1,277 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+
+/*
+ * Commands
+ */
+
+static bool cmd_if_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_elsif_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_if_validate_const
+	(struct sieve_validator *valdtr, struct sieve_command *cmd,
+		int *const_current, int const_next);
+static bool cmd_if_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+static bool cmd_else_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+/* If command
+ *
+ * Syntax:
+ *   if <test1: test> <block1: block>
+ */
+
+const struct sieve_command_def cmd_if = {
+	.identifier = "if",
+	.type = SCT_COMMAND,
+	.positional_args = 0,
+	.subtests = 1,
+	.block_allowed = TRUE,
+	.block_required = TRUE,
+	.validate = cmd_if_validate,
+	.validate_const = cmd_if_validate_const,
+	.generate = cmd_if_generate
+};
+
+/* ElsIf command
+ *
+ * Santax:
+ *   elsif <test2: test> <block2: block>
+ */
+
+const struct sieve_command_def cmd_elsif = {
+	.identifier = "elsif",
+	.type = SCT_COMMAND,
+	.positional_args = 0,
+	.subtests = 1,
+	.block_allowed = TRUE,
+	.block_required = TRUE,
+	.validate = cmd_elsif_validate,
+	.validate_const = cmd_if_validate_const,
+	.generate = cmd_if_generate
+};
+
+/* Else command
+ *
+ * Syntax:
+ *   else <block>
+ */
+
+const struct sieve_command_def cmd_else = {
+	.identifier = "else",
+	.type = SCT_COMMAND,
+	.positional_args = 0,
+	.subtests = 0,
+	.block_allowed = TRUE,
+	.block_required = TRUE,
+	.validate = cmd_elsif_validate,
+	.validate_const = cmd_if_validate_const,
+	.generate = cmd_else_generate
+};
+
+/*
+ * Context management
+ */
+
+struct cmd_if_context_data {
+	struct cmd_if_context_data *previous;
+	struct cmd_if_context_data *next;
+
+	int const_condition;
+
+	bool jump_generated;
+	sieve_size_t exit_jump;
+};
+
+static void cmd_if_initialize_context_data
+(struct sieve_command *cmd, struct cmd_if_context_data *previous)
+{
+	struct cmd_if_context_data *cmd_data;
+
+	/* Assign context */
+	cmd_data = p_new(sieve_command_pool(cmd), struct cmd_if_context_data, 1);
+	cmd_data->exit_jump = 0;
+	cmd_data->jump_generated = FALSE;
+
+	/* Update linked list of contexts */
+	cmd_data->previous = previous;
+	cmd_data->next = NULL;
+	if ( previous != NULL )
+		previous->next = cmd_data;
+
+	/* Check const status */
+	cmd_data->const_condition = -1;
+	while ( previous != NULL ) {
+		if ( previous->const_condition > 0 ) {
+			cmd_data->const_condition = 0;
+			break;
+		}
+		previous = previous->previous;
+	}
+
+	/* Assign to command context */
+	cmd->data = cmd_data;
+}
+
+/*
+ * Validation
+ */
+
+static bool cmd_if_validate
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *cmd)
+{
+	/* Start if-command structure */
+	cmd_if_initialize_context_data(cmd, NULL);
+
+	return TRUE;
+}
+
+static bool cmd_elsif_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+	struct sieve_command *prev;
+
+	i_assert(cmd != NULL);
+	prev = sieve_command_prev(cmd);
+
+	/* Check valid command placement */
+	if ( prev == NULL ||
+		( !sieve_command_is(prev, cmd_if) && !sieve_command_is(prev, cmd_elsif) ) )
+	{
+		sieve_command_validate_error(valdtr, cmd,
+			"the %s command must follow an if or elseif command",
+			sieve_command_identifier(cmd));
+		return FALSE;
+	}
+
+	/* Previous command in this block is 'if' or 'elsif', so we can safely refer
+	 * to its context data
+	 */
+	cmd_if_initialize_context_data(cmd, prev->data);
+
+	return TRUE;
+}
+
+static bool cmd_if_validate_const
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *cmd,
+	int *const_current, int const_next)
+{
+	struct cmd_if_context_data *cmd_data =
+		(struct cmd_if_context_data *) cmd->data;
+
+	if ( cmd_data != NULL ) {
+		if ( cmd_data->const_condition == 0 ) {
+			*const_current = cmd_data->const_condition;
+			return FALSE;
+		}
+
+		cmd_data->const_condition = const_next;
+	}
+
+	*const_current = const_next;
+
+	return ( const_next < 0 );
+}
+
+/*
+ * Code generation
+ */
+
+/* The if command does not generate specific IF-ELSIF-ELSE opcodes, but only uses
+ * JMP instructions. This is why the implementation of the if command does not
+ * include an opcode implementation.
+ */
+
+static void cmd_if_resolve_exit_jumps
+(struct sieve_binary_block *sblock, struct cmd_if_context_data *cmd_data)
+{
+	struct cmd_if_context_data *if_ctx = cmd_data->previous;
+
+	/* Iterate backwards through all if-command contexts and resolve the
+	 * exit jumps to the current code position.
+	 */
+	while ( if_ctx != NULL ) {
+		if ( if_ctx->jump_generated )
+			sieve_binary_resolve_offset(sblock, if_ctx->exit_jump);
+		if_ctx = if_ctx->previous;
+	}
+}
+
+static bool cmd_if_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	struct sieve_binary_block *sblock = cgenv->sblock;
+	struct cmd_if_context_data *cmd_data =
+		(struct cmd_if_context_data *) cmd->data;
+	struct sieve_ast_node *test;
+	struct sieve_jumplist jmplist;
+
+	/* Generate test condition */
+	if ( cmd_data->const_condition < 0 ) {
+		/* Prepare jumplist */
+		sieve_jumplist_init_temp(&jmplist, sblock);
+
+		test = sieve_ast_test_first(cmd->ast_node);
+		if ( !sieve_generate_test(cgenv, test, &jmplist, FALSE) )
+			return FALSE;
+	}
+
+	/* Case true { */
+	if ( cmd_data->const_condition != 0 ) {
+		if ( !sieve_generate_block(cgenv, cmd->ast_node) )
+			return FALSE;
+	}
+
+	/* Are we the final command in this if-elsif-else structure? */
+	if ( cmd_data->next == NULL || cmd_data->const_condition == 1 ) {
+		/* Yes, Resolve previous exit jumps to this point */
+		cmd_if_resolve_exit_jumps(sblock, cmd_data);
+
+	} else if ( cmd_data->const_condition < 0 ) {
+		/* No, generate jump to end of if-elsif-else structure (resolved later)
+		 * This of course is not necessary if the {} block contains a command
+		 * like stop at top level that unconditionally exits the block already
+		 * anyway.
+		 */
+		if ( !sieve_command_block_exits_unconditionally(cmd) ) {
+			sieve_operation_emit(sblock, NULL, &sieve_jmp_operation);
+			cmd_data->exit_jump = sieve_binary_emit_offset(sblock, 0);
+			cmd_data->jump_generated = TRUE;
+		}
+	}
+
+	if ( cmd_data->const_condition < 0 ) {
+		/* Case false ... (subsequent elsif/else commands might generate more) */
+		sieve_jumplist_resolve(&jmplist);
+	}
+
+	return TRUE;
+}
+
+static bool cmd_else_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	struct cmd_if_context_data *cmd_data =
+		(struct cmd_if_context_data *) cmd->data;
+
+	/* Else { */
+	if ( cmd_data->const_condition != 0 ) {
+		if ( !sieve_generate_block(cgenv, cmd->ast_node) )
+			return FALSE;
+
+		/* } End: resolve all exit blocks */
+		cmd_if_resolve_exit_jumps(cgenv->sblock, cmd_data);
+	}
+
+	return TRUE;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/cmd-keep.c
@@ -0,0 +1,113 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-dump.h"
+#include "sieve-message.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+
+/*
+ * Keep command
+ *
+ * Syntax:
+ *   keep
+ */
+
+static bool cmd_keep_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+const struct sieve_command_def cmd_keep = {
+	.identifier = "keep",
+	.type = SCT_COMMAND,
+	.positional_args = 0,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.generate = cmd_keep_generate
+};
+
+/*
+ * Keep operation
+ */
+
+static bool cmd_keep_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_keep_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def cmd_keep_operation = {
+	.mnemonic = "KEEP",
+	.code = SIEVE_OPERATION_KEEP,
+	.dump = cmd_keep_operation_dump,
+	.execute = cmd_keep_operation_execute
+};
+
+/*
+ * Code generation
+ */
+
+static bool cmd_keep_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	/* Emit opcode */
+	sieve_operation_emit(cgenv->sblock, NULL, &cmd_keep_operation);
+
+	/* Generate arguments */
+	return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_keep_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "KEEP");
+	sieve_code_descend(denv);
+
+	return ( sieve_action_opr_optional_dump(denv, address, NULL) == 0 );
+}
+
+/*
+ * Interpretation
+ */
+
+static int cmd_keep_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	struct sieve_side_effects_list *slist = NULL;
+	int ret = 0;
+
+	/*
+	 * Read data
+	 */
+
+	/* Optional operands (side effects only) */
+	if ( sieve_action_opr_optional_read(renv, address, NULL, &ret, &slist) != 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS,
+		"keep action; store message in default mailbox");
+
+	/* Add keep action to result.
+	 */
+	if ( sieve_result_add_keep(renv, slist) < 0 )
+		return SIEVE_EXEC_FAILURE;
+
+	return SIEVE_EXEC_OK;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/cmd-redirect.c
@@ -0,0 +1,508 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "str-sanitize.h"
+#include "strfuncs.h"
+#include "istream.h"
+#include "istream-header-filter.h"
+#include "ostream.h"
+#include "mail-storage.h"
+
+#include "rfc2822.h"
+
+#include "sieve-common.h"
+#include "sieve-limits.h"
+#include "sieve-address.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-code-dumper.h"
+#include "sieve-result.h"
+#include "sieve-smtp.h"
+#include "sieve-message.h"
+
+#include <stdio.h>
+
+/*
+ * Redirect command
+ *
+ * Syntax
+ *   redirect <address: string>
+ */
+
+static bool cmd_redirect_validate
+	(struct sieve_validator *validator, struct sieve_command *cmd);
+static bool cmd_redirect_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+const struct sieve_command_def cmd_redirect = {
+	.identifier = "redirect",
+	.type = SCT_COMMAND,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = cmd_redirect_validate,
+	.generate = cmd_redirect_generate
+};
+
+/*
+ * Redirect operation
+ */
+
+static bool cmd_redirect_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_redirect_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def cmd_redirect_operation = {
+	.mnemonic = "REDIRECT",
+	.code = SIEVE_OPERATION_REDIRECT,
+	.dump = cmd_redirect_operation_dump,
+	.execute = cmd_redirect_operation_execute
+};
+
+/*
+ * Redirect action
+ */
+
+static bool act_redirect_equals
+	(const struct sieve_script_env *senv, const struct sieve_action *act1,
+		const struct sieve_action *act2);
+static int act_redirect_check_duplicate
+	(const struct sieve_runtime_env *renv,
+		const struct sieve_action *act,
+		const struct sieve_action *act_other);
+static void act_redirect_print
+	(const struct sieve_action *action, const struct sieve_result_print_env *rpenv,
+		bool *keep);
+static int act_redirect_commit
+	(const struct sieve_action *action, const struct sieve_action_exec_env *aenv,
+		void *tr_context, bool *keep);
+
+const struct sieve_action_def act_redirect = {
+	.name = "redirect",
+	.flags = SIEVE_ACTFLAG_TRIES_DELIVER,
+	.equals = act_redirect_equals,
+	.check_duplicate = act_redirect_check_duplicate,
+	.print = act_redirect_print,
+	.commit = act_redirect_commit
+};
+
+/*
+ * Validation
+ */
+
+static bool cmd_redirect_validate
+(struct sieve_validator *validator, struct sieve_command *cmd)
+{
+	struct sieve_instance *svinst = sieve_validator_svinst(validator);
+	struct sieve_ast_argument *arg = cmd->first_positional;
+
+	/* Check and activate address argument */
+
+	if ( !sieve_validate_positional_argument
+		(validator, cmd, arg, "address", 1, SAAT_STRING) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(validator, cmd, arg, FALSE) )
+		return FALSE;
+
+	/* We can only assess the validity of the outgoing address when it is
+	 * a string literal. For runtime-generated strings this needs to be
+	 * done at runtime.
+	 */
+	if ( sieve_argument_is_string_literal(arg) ) {
+		string_t *raw_address = sieve_ast_argument_str(arg);
+		const char *error;
+		bool result;
+
+		T_BEGIN {
+			/* Parse the address */
+			result = sieve_address_validate_str(raw_address, &error);
+			if ( !result ) {
+				sieve_argument_validate_error(validator, arg,
+					"specified redirect address '%s' is invalid: %s",
+					str_sanitize(str_c(raw_address),128), error);
+			}
+		} T_END;
+
+		return result;
+	}
+
+	if ( svinst->max_redirects == 0 ) {
+		sieve_command_validate_error(validator, cmd,
+			"local policy prohibits the use of a redirect action");
+		return FALSE;
+	}
+	return TRUE;
+}
+
+
+
+/*
+ * Code generation
+ */
+
+static bool cmd_redirect_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	sieve_operation_emit(cgenv->sblock, NULL,  &cmd_redirect_operation);
+
+	/* Generate arguments */
+	return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_redirect_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "REDIRECT");
+	sieve_code_descend(denv);
+
+	if ( sieve_action_opr_optional_dump(denv, address, NULL) != 0 )
+		return FALSE;
+
+	return sieve_opr_string_dump(denv, address, "address");
+}
+
+/*
+ * Code execution
+ */
+
+static int cmd_redirect_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	struct sieve_instance *svinst = renv->svinst;
+	struct sieve_side_effects_list *slist = NULL;
+	string_t *redirect;
+	const struct smtp_address *to_address;
+	const char *error;
+	int ret;
+
+	/*
+	 * Read data
+	 */
+
+	/* Optional operands (side effects only) */
+	if ( sieve_action_opr_optional_read(renv, address, NULL, &ret, &slist) != 0 )
+		return ret;
+
+	/* Read the address */
+	if ( (ret=sieve_opr_string_read
+		(renv, address, "address", &redirect)) <= 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	/* Parse the address */
+	to_address = sieve_address_parse_str(redirect, &error);
+	if ( to_address == NULL ) {
+		sieve_runtime_error(renv, NULL,
+			"specified redirect address '%s' is invalid: %s",
+			str_sanitize(str_c(redirect),128), error);
+		return SIEVE_EXEC_FAILURE;
+	}
+
+	if ( svinst->max_redirects == 0 ) {
+		sieve_runtime_error(renv, NULL,
+			"local policy prohibits the use of a redirect action");
+		return SIEVE_EXEC_FAILURE;
+	}
+
+	if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_ACTIONS) ) {
+		sieve_runtime_trace(renv, 0, "redirect action");
+		sieve_runtime_trace_descend(renv);
+		sieve_runtime_trace(renv, 0, "forward message to address %s",
+			smtp_address_encode_path(to_address));
+	}
+
+	/* Add redirect action to the result */
+
+	return sieve_act_redirect_add_to_result
+		(renv, slist, to_address);
+}
+
+/*
+ * Action implementation
+ */
+
+static bool act_redirect_equals
+(const struct sieve_script_env *senv ATTR_UNUSED,
+	const struct sieve_action *act1, const struct sieve_action *act2)
+{
+	struct act_redirect_context *rd_ctx1 =
+		(struct act_redirect_context *) act1->context;
+	struct act_redirect_context *rd_ctx2 =
+		(struct act_redirect_context *) act2->context;
+
+	/* Address is already normalized */
+	return ( smtp_address_equals
+		(rd_ctx1->to_address, rd_ctx2->to_address) );
+}
+
+static int act_redirect_check_duplicate
+(const struct sieve_runtime_env *renv ATTR_UNUSED,
+	const struct sieve_action *act,
+	const struct sieve_action *act_other)
+{
+	return ( act_redirect_equals
+		(renv->scriptenv, act, act_other) ? 1 : 0 );
+}
+
+static void act_redirect_print
+(const struct sieve_action *action,
+	const struct sieve_result_print_env *rpenv, bool *keep)
+{
+	struct act_redirect_context *ctx =
+		(struct act_redirect_context *) action->context;
+
+	sieve_result_action_printf(rpenv, "redirect message to: %s",
+		smtp_address_encode_path(ctx->to_address));
+
+	*keep = FALSE;
+}
+
+static int act_redirect_send
+(const struct sieve_action_exec_env *aenv, struct mail *mail,
+	struct act_redirect_context *ctx, const char *new_msg_id)
+	ATTR_NULL(4)
+{
+	static const char *hide_headers[] =
+		{ "Return-Path", "X-Sieve", "X-Sieve-Redirected-From" };
+	struct sieve_instance *svinst = aenv->svinst;
+	struct sieve_message_context *msgctx = aenv->msgctx;
+	const struct sieve_script_env *senv = aenv->scriptenv;
+	struct sieve_address_source env_from = svinst->redirect_from;
+	struct istream *input;
+	struct ostream *output;
+	const struct smtp_address *sender;
+	const char *error;
+	struct sieve_smtp_context *sctx;
+	int ret;
+
+	/* Just to be sure */
+	if ( !sieve_smtp_available(senv) ) {
+		sieve_result_global_warning
+			(aenv, "redirect action has no means to send mail.");
+		return SIEVE_EXEC_FAILURE;
+	}
+
+	if (mail_get_stream(mail, NULL, NULL, &input) < 0) {
+		return sieve_result_mail_error(aenv, mail,
+			"redirect action: failed to read input message");
+	}
+
+	/* Determine which sender to use
+
+	   From RFC 5228, Section 4.2:
+
+		 The envelope sender address on the outgoing message is chosen by the
+		 sieve implementation.  It MAY be copied from the message being
+		 processed.  However, if the message being processed has an empty
+		 envelope sender address the outgoing message MUST also have an empty
+		 envelope sender address.  This last requirement is imposed to prevent
+		 loops in the case where a message is redirected to an invalid address
+		 when then returns a delivery status notification that also ends up
+		 being redirected to the same invalid address.
+	 */
+	if ( (aenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0 ) {
+		/* Envelope available */
+		sender = sieve_message_get_sender(msgctx);
+		if ( sender != NULL &&
+			sieve_address_source_get_address(&env_from, svinst,
+				senv, msgctx, aenv->flags, &sender) < 0 )
+			sender = NULL;
+	} else {
+		/* No envelope available */
+		if ( (ret=sieve_address_source_get_address(&env_from, svinst,
+			senv, msgctx, aenv->flags, &sender)) < 0 ) {
+			sender = NULL;
+		} else if ( ret == 0 ) {
+			sender = svinst->user_email;
+		}
+	}
+
+	/* Open SMTP transport */
+	sctx = sieve_smtp_start_single(senv, ctx->to_address, sender, &output);
+
+	/* Remove unwanted headers */
+	input = i_stream_create_header_filter
+		(input, HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR, hide_headers,
+			N_ELEMENTS(hide_headers), *null_header_filter_callback, (void *)NULL);
+
+	T_BEGIN {
+		string_t *hdr = t_str_new(256);
+		const struct smtp_address *user_email;
+
+		/* Prepend sieve headers (should not affect signatures) */
+		rfc2822_header_append(hdr,
+			"X-Sieve", SIEVE_IMPLEMENTATION, FALSE, NULL);
+		if ( svinst->user_email == NULL &&
+			(aenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0 )
+			user_email = sieve_message_get_final_recipient(msgctx);
+		else
+			user_email = sieve_get_user_email(aenv->svinst);
+		if ( user_email != NULL ) {
+			rfc2822_header_append(hdr, "X-Sieve-Redirected-From",
+				smtp_address_encode(user_email), FALSE, NULL);
+		}
+
+		/* Add new Message-ID if message doesn't have one */
+		if ( new_msg_id != NULL )
+			rfc2822_header_write(hdr, "Message-ID", new_msg_id);
+
+		o_stream_nsend(output, str_data(hdr), str_len(hdr));
+	} T_END;
+
+	o_stream_nsend_istream(output, input);
+
+	if (input->stream_errno != 0) {
+		sieve_result_critical(aenv,
+			"redirect action: failed to read input message",
+			"redirect action: read(%s) failed: %s",
+			i_stream_get_name(input),
+			i_stream_get_error(input));
+		i_stream_unref(&input);
+		return SIEVE_EXEC_TEMP_FAILURE;
+	}
+  i_stream_unref(&input);
+
+	/* Close SMTP transport */
+	if ( (ret=sieve_smtp_finish(sctx, &error)) <= 0 ) {
+		if ( ret < 0 ) {
+			sieve_result_global_error(aenv,
+				"failed to redirect message to <%s>: %s "
+				"(temporary failure)",
+				smtp_address_encode(ctx->to_address),
+				str_sanitize(error, 512));
+			return SIEVE_EXEC_TEMP_FAILURE;
+		}
+
+		sieve_result_global_log_error(aenv,
+			"failed to redirect message to <%s>: %s "
+			"(permanent failure)",
+			smtp_address_encode(ctx->to_address),
+			str_sanitize(error, 512));
+		return SIEVE_EXEC_FAILURE;
+	}
+
+	return SIEVE_EXEC_OK;
+}
+
+static int act_redirect_commit
+(const struct sieve_action *action,
+	const struct sieve_action_exec_env *aenv, void *tr_context ATTR_UNUSED,
+	bool *keep)
+{
+	struct sieve_instance *svinst = aenv->svinst;
+	struct act_redirect_context *ctx =
+		(struct act_redirect_context *) action->context;
+	struct sieve_message_context *msgctx = aenv->msgctx;
+	struct mail *mail =	( action->mail != NULL ?
+		action->mail : sieve_message_get_mail(msgctx) );
+	const struct sieve_message_data *msgdata = aenv->msgdata;
+	const struct sieve_script_env *senv = aenv->scriptenv;
+	const struct smtp_address *recipient;
+	const char *msg_id = msgdata->id, *new_msg_id = NULL;
+	const char *dupeid, *resent_id = NULL;
+	const char *list_id = NULL;
+	int ret;
+
+	/*
+	 * Prevent mail loops
+	 */
+
+	/* Read identifying headers */
+	if ( mail_get_first_header
+		(msgdata->mail, "resent-message-id", &resent_id) < 0 ) {
+		return sieve_result_mail_error(aenv, mail,
+			"failed to read header field `resent-message-id'");
+	}
+	if ( resent_id == NULL ) {
+		if ( mail_get_first_header
+			(msgdata->mail, "resent-from", &resent_id) < 0 ) {
+			return sieve_result_mail_error(aenv, mail,
+				"failed to read header field `resent-from'");
+		}
+	}
+	if ( mail_get_first_header
+		(msgdata->mail, "list-id", &list_id) < 0 ) {
+		return sieve_result_mail_error(aenv, mail,
+			"failed to read header field `list-id'");
+	}
+
+	/* Create Message-ID for the message if it has none */
+	if ( msg_id == NULL ) {	
+		msg_id = new_msg_id =
+			sieve_message_get_new_id(aenv->svinst);
+	}
+
+	if ( (aenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0 )
+		recipient = sieve_message_get_orig_recipient(msgctx);
+	else
+		recipient = sieve_get_user_email(aenv->svinst);
+
+	/* Base the duplicate ID on:
+	   - the message id
+	   - the recipient running this Sieve script
+	   - redirect target address
+	   - if this message is resent: the message-id or from-address of
+		   the original message
+	   - if the message came through a mailing list: the mailinglist ID
+	 */
+	dupeid = t_strdup_printf("%s-%s-%s-%s-%s", msg_id,
+		(recipient != NULL ? smtp_address_encode(recipient) : ""),
+		smtp_address_encode(ctx->to_address),
+		(resent_id != NULL ? resent_id : ""),
+		(list_id != NULL ? list_id : ""));
+
+	/* Check whether we've seen this message before */
+	if (sieve_action_duplicate_check
+		(senv, dupeid, strlen(dupeid))) {
+		sieve_result_global_log(aenv,
+			"discarded duplicate forward to <%s>",
+			smtp_address_encode(ctx->to_address));
+		*keep = FALSE;
+		return SIEVE_EXEC_OK;
+	}
+
+	/*
+	 * Try to forward the message
+	 */
+
+	if ( (ret=act_redirect_send
+		(aenv, mail, ctx, new_msg_id)) == SIEVE_EXEC_OK) {
+
+		/* Mark this message id as forwarded to the specified destination */
+		sieve_action_duplicate_mark(senv, dupeid, strlen(dupeid),
+			ioloop_time + svinst->redirect_duplicate_period);
+
+		sieve_result_global_log(aenv, "forwarded to <%s>",
+			smtp_address_encode(ctx->to_address));
+
+		/* Indicate that message was successfully forwarded */
+		aenv->exec_status->message_forwarded = TRUE;
+
+		/* Cancel implicit keep */
+		*keep = FALSE;
+
+		return SIEVE_EXEC_OK;
+	}
+
+	return ret;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/cmd-require.c
@@ -0,0 +1,86 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-extensions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+
+/*
+ * Require command
+ *
+ * Syntax
+ *   Syntax: require <capabilities: string-list>
+ */
+
+static bool cmd_require_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+
+const struct sieve_command_def cmd_require = {
+	.identifier = "require",
+	.type = SCT_COMMAND,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = cmd_require_validate
+};
+
+/*
+ * Validation
+ */
+
+static bool cmd_require_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+	bool result = TRUE;
+	struct sieve_ast_argument *arg;
+	struct sieve_command *prev = sieve_command_prev(cmd);
+
+	/* Check valid command placement */
+	if ( !sieve_command_is_toplevel(cmd) ||
+		( !sieve_command_is_first(cmd) && prev != NULL &&
+			!sieve_command_is(prev, cmd_require) ) )
+	{
+		sieve_command_validate_error(valdtr, cmd,
+			"require commands can only be placed at top level "
+			"at the beginning of the file");
+		return FALSE;
+	}
+
+	/* Check argument and load specified extension(s) */
+
+	arg = cmd->first_positional;
+	if ( sieve_ast_argument_type(arg) == SAAT_STRING ) {
+		/* Single string */
+		const struct sieve_extension *ext = sieve_validator_extension_load_by_name
+			(valdtr, cmd, arg, sieve_ast_argument_strc(arg));
+
+		if ( ext == NULL ) result = FALSE;
+
+	} else if ( sieve_ast_argument_type(arg) == SAAT_STRING_LIST ) {
+		/* String list */
+		struct sieve_ast_argument *stritem = sieve_ast_strlist_first(arg);
+
+		while ( stritem != NULL ) {
+			const struct sieve_extension *ext = sieve_validator_extension_load_by_name
+				(valdtr, cmd, stritem, sieve_ast_strlist_strc(stritem));
+
+			if ( ext == NULL ) result = FALSE;
+
+			stritem = sieve_ast_strlist_next(stritem);
+		}
+	} else {
+		/* Something else */
+		sieve_argument_validate_error(valdtr, arg,
+			"the require command accepts a single string or string list argument, "
+			"but %s was found",
+			sieve_ast_argument_name(arg));
+		return FALSE;
+	}
+
+	return result;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/cmd-stop.c
@@ -0,0 +1,86 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+
+/*
+ * Stop command
+ *
+ * Syntax
+ *   stop
+ */
+
+static bool cmd_stop_generate
+	(const struct sieve_codegen_env *cgenv,
+		struct sieve_command *ctx ATTR_UNUSED);
+static bool cmd_stop_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+
+const struct sieve_command_def cmd_stop = {
+	.identifier = "stop",
+	.type = SCT_COMMAND,
+	.positional_args = 0,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = cmd_stop_validate,
+	.generate = cmd_stop_generate
+};
+
+/*
+ * Stop operation
+ */
+
+static int opc_stop_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def cmd_stop_operation = {
+	.mnemonic = "STOP",
+	.code = SIEVE_OPERATION_STOP,
+	.execute = opc_stop_execute
+};
+
+/*
+ * Command validation
+ */
+
+static bool cmd_stop_validate
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *cmd)
+{
+	sieve_command_exit_block_unconditionally(cmd);
+
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_stop_generate
+(const struct sieve_codegen_env *cgenv,
+	struct sieve_command *cmd ATTR_UNUSED)
+{
+	sieve_operation_emit(cgenv->sblock, NULL, &cmd_stop_operation);
+
+	return TRUE;
+}
+
+/*
+ * Code execution
+ */
+
+static int opc_stop_execute
+(const struct sieve_runtime_env *renv,  sieve_size_t *address ATTR_UNUSED)
+{
+	sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS,
+		"stop command; end all script execution");
+
+	sieve_interpreter_interrupt(renv->interp);
+
+	return SIEVE_EXEC_OK;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/cmp-i-ascii-casemap.c
@@ -0,0 +1,99 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Comparator 'i;ascii-casemap':
+ *
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-comparators.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+/*
+ * Forward declarations
+ */
+
+static int cmp_i_ascii_casemap_compare
+	(const struct sieve_comparator *cmp,
+		const char *val1, size_t val1_size, const char *val2, size_t val2_size);
+static bool cmp_i_ascii_casemap_char_match
+	(const struct sieve_comparator *cmp, const char **val1, const char *val1_end,
+		const char **val2, const char *val2_end);
+
+/*
+ * Comparator object
+ */
+
+const struct sieve_comparator_def i_ascii_casemap_comparator = {
+	SIEVE_OBJECT("i;ascii-casemap",
+		&comparator_operand, SIEVE_COMPARATOR_I_ASCII_CASEMAP),
+	.flags =
+		SIEVE_COMPARATOR_FLAG_ORDERING |
+		SIEVE_COMPARATOR_FLAG_EQUALITY |
+		SIEVE_COMPARATOR_FLAG_SUBSTRING_MATCH |
+		SIEVE_COMPARATOR_FLAG_PREFIX_MATCH,
+	.compare = cmp_i_ascii_casemap_compare,
+	.char_match = cmp_i_ascii_casemap_char_match,
+	.char_skip = sieve_comparator_octet_skip
+};
+
+/*
+ * Comparator implementation
+ */
+
+static int cmp_i_ascii_casemap_compare(
+	const struct sieve_comparator *cmp ATTR_UNUSED,
+	const char *val1, size_t val1_size, const char *val2, size_t val2_size)
+{
+	int result;
+
+	if ( val1_size == val2_size ) {
+		return strncasecmp(val1, val2, val1_size);
+	}
+
+	if ( val1_size > val2_size ) {
+		result = strncasecmp(val1, val2, val2_size);
+
+		if ( result == 0 ) return 1;
+
+		return result;
+	}
+
+	result = strncasecmp(val1, val2, val1_size);
+
+	if ( result == 0 ) return -1;
+
+	return result;
+}
+
+static bool cmp_i_ascii_casemap_char_match
+	(const struct sieve_comparator *cmp ATTR_UNUSED,
+		const char **val, const char *val_end,
+		const char **key, const char *key_end)
+{
+	const char *val_begin = *val;
+	const char *key_begin = *key;
+
+	while ( i_tolower(**val) == i_tolower(**key) &&
+		*val < val_end && *key < key_end ) {
+		(*val)++;
+		(*key)++;
+	}
+
+	if ( *key < key_end ) {
+		/* Reset */
+		*val = val_begin;
+		*key = key_begin;
+
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/cmp-i-octet.c
@@ -0,0 +1,97 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Comparator 'i;octet':
+ *
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-comparators.h"
+
+#include <string.h>
+#include <stdio.h>
+
+/*
+ * Forward declarations
+ */
+
+static int cmp_i_octet_compare
+	(const struct sieve_comparator *cmp,
+		const char *val1, size_t val1_size, const char *val2, size_t val2_size);
+static bool cmp_i_octet_char_match
+	(const struct sieve_comparator *cmp, const char **val1, const char *val1_end,
+		const char **val2, const char *val2_end);
+
+/*
+ * Comparator object
+ */
+
+const struct sieve_comparator_def i_octet_comparator = {
+	SIEVE_OBJECT("i;octet",
+		&comparator_operand, SIEVE_COMPARATOR_I_OCTET),
+	.flags =
+		SIEVE_COMPARATOR_FLAG_ORDERING |
+		SIEVE_COMPARATOR_FLAG_EQUALITY |
+		SIEVE_COMPARATOR_FLAG_SUBSTRING_MATCH |
+		SIEVE_COMPARATOR_FLAG_PREFIX_MATCH,
+	.compare = cmp_i_octet_compare,
+	.char_match = cmp_i_octet_char_match,
+	.char_skip = sieve_comparator_octet_skip
+};
+
+/*
+ * Comparator implementation
+ */
+
+static int cmp_i_octet_compare(
+	const struct sieve_comparator *cmp ATTR_UNUSED,
+	const char *val1, size_t val1_size, const char *val2, size_t val2_size)
+{
+	int result;
+
+	if ( val1_size == val2_size ) {
+		return memcmp((void *) val1, (void *) val2, val1_size);
+	}
+
+	if ( val1_size > val2_size ) {
+		result = memcmp((void *) val1, (void *) val2, val2_size);
+
+		if ( result == 0 ) return 1;
+
+		return result;
+	}
+
+	result = memcmp((void *) val1, (void *) val2, val1_size);
+
+	if ( result == 0 ) return -1;
+
+	return result;
+}
+
+static bool cmp_i_octet_char_match
+	(const struct sieve_comparator *cmp ATTR_UNUSED,
+		const char **val, const char *val_end,
+		const char **key, const char *key_end)
+{
+	const char *val_begin = *val;
+	const char *key_begin = *key;
+
+	while ( **val == **key && *val < val_end && *key < key_end ) {
+		(*val)++;
+		(*key)++;
+	}
+
+	if ( *key < key_end ) {
+		/* Reset */
+		*val = val_begin;
+		*key = key_begin;
+
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/ext-encoded-character.c
@@ -0,0 +1,271 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension encoded-character
+ * ---------------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5228
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+#include "unichar.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+
+#include <ctype.h>
+
+/*
+ * Extension
+ */
+
+static bool ext_encoded_character_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def encoded_character_extension = {
+	.name = "encoded-character",
+	.validator_load = ext_encoded_character_validator_load,
+};
+
+/*
+ * Encoded string argument
+ */
+
+bool arg_encoded_string_validate
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *context);
+
+const struct sieve_argument_def encoded_string_argument = {
+	.identifier = "@encoded-string",
+	.validate = arg_encoded_string_validate
+};
+
+/* Parsing */
+
+static bool _skip_whitespace
+	(const char **in, const char *inend)
+{
+	while ( *in < inend ) {
+		if ( **in == '\r' ) {
+			(*in)++;
+			if ( **in != '\n' )
+				return FALSE;
+			continue;
+		}
+
+		/* (Loose LF is non-standard) */
+		if ( **in != ' ' && **in != '\n' && **in != '\t' )
+			break;
+
+		(*in)++;
+	}
+
+	return TRUE;
+}
+
+static bool _parse_hexint
+(const char **in, const char *inend, int max_digits, unsigned int *result)
+{
+	int digit = 0;
+	*result = 0;
+
+	while ( *in < inend && (max_digits == 0 || digit < max_digits) ) {
+
+		if ( (**in) >= '0' && (**in) <= '9' )
+			*result = ((*result) << 4) + (**in) - ((unsigned int) '0');
+		else if ( (**in) >= 'a' && (**in) <= 'f' )
+			*result = ((*result) << 4) + (**in) - ((unsigned int) 'a') + 0x0a;
+		else if ( (**in) >= 'A' && (**in) <= 'F' )
+			*result = ((*result) << 4) + (**in) - ((unsigned int) 'A') + 0x0a;
+		else
+			return ( digit > 0 );
+
+		(*in)++;
+		digit++;
+	}
+
+	if ( digit == max_digits ) {
+		/* Hex digit _MUST_ end here */
+		if ( (**in >= '0' && **in <= '9')	|| (**in >= 'a' && **in <= 'f') ||
+			(**in >= 'A' && **in <= 'F') )
+			return FALSE;
+
+		return TRUE;
+	}
+
+	return ( digit > 0 );
+}
+
+static bool _decode_hex
+(const char **in, const char *inend, string_t *result)
+{
+	int values = 0;
+
+	while ( *in < inend ) {
+		unsigned int hexpair;
+
+		if ( !_skip_whitespace(in, inend) ) return FALSE;
+
+		if ( !_parse_hexint(in, inend, 2, &hexpair) ) break;
+
+		str_append_c(result, (unsigned char) hexpair);
+		values++;
+	}
+
+	return ( values > 0 );
+}
+
+static bool _decode_unicode
+(const char **in, const char *inend, string_t *result,
+	unsigned int *error_hex)
+{
+	int values = 0;
+	bool valid = TRUE;
+
+	while ( *in < inend ) {
+		unsigned int unicode_hex;
+
+		if ( !_skip_whitespace(in, inend) ) return FALSE;
+
+		if ( !_parse_hexint(in, inend, 0, &unicode_hex) ) break;
+
+		if ( uni_is_valid_ucs4((unichar_t) unicode_hex) )
+			uni_ucs4_to_utf8_c((unichar_t) unicode_hex, result);
+		else {
+			if ( valid ) *error_hex = unicode_hex;
+			valid = FALSE;
+		}
+		values++;
+	}
+
+	return ( values > 0 );
+}
+
+bool arg_encoded_string_validate
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd)
+{
+	bool result = TRUE;
+	enum { ST_NONE, ST_OPEN, ST_TYPE, ST_CLOSE }
+		state = ST_NONE;
+	string_t *str = sieve_ast_argument_str(*arg);
+	string_t *tmpstr, *newstr = NULL;
+	const char *p, *mark, *strstart, *substart = NULL;
+	const char *strval = (const char *) str_data(str);
+	const char *strend = strval + str_len(str);
+	unsigned int error_hex = 0;
+
+	T_BEGIN {
+		tmpstr = t_str_new(32);
+
+		p = strval;
+		strstart = p;
+		while ( result && p < strend ) {
+			switch ( state ) {
+			/* Normal string */
+			case ST_NONE:
+				if ( *p == '$' ) {
+					substart = p;
+					state = ST_OPEN;
+				}
+				p++;
+				break;
+			/* Parsed '$' */
+			case ST_OPEN:
+				if ( *p == '{' ) {
+					state = ST_TYPE;
+					p++;
+				} else
+					state = ST_NONE;
+				break;
+			/* Parsed '${' */
+			case ST_TYPE:
+				mark = p;
+				/* Scan for 'hex' or 'unicode' */
+				while ( p < strend && i_isalpha(*p) ) p++;
+
+				if ( *p != ':' ) {
+					state = ST_NONE;
+					break;
+				}
+
+				state = ST_CLOSE;
+
+				str_truncate(tmpstr, 0);
+				if ( strncasecmp(mark, "hex", p - mark) == 0 ) {
+					/* Hexadecimal */
+					p++;
+					if ( !_decode_hex(&p, strend, tmpstr) )
+						state = ST_NONE;
+				} else if ( strncasecmp(mark, "unicode", p - mark) == 0 ) {
+					/* Unicode */
+					p++;
+					if ( !_decode_unicode(&p, strend, tmpstr, &error_hex) )
+						state = ST_NONE;
+				} else {
+					/* Invalid encoding */
+					p++;
+					state = ST_NONE;
+				}
+				break;
+			case ST_CLOSE:
+				if ( *p == '}' ) {
+					/* We now know that the substitution is valid */
+
+					if ( error_hex != 0 ) {
+						sieve_argument_validate_error(valdtr, *arg,
+							"invalid unicode character 0x%08x in encoded character substitution",
+							error_hex);
+						result = FALSE;
+						break;
+					}
+
+					if ( newstr == NULL ) {
+						newstr = str_new(sieve_ast_pool((*arg)->ast), str_len(str)*2);
+					}
+
+					str_append_data(newstr, strstart, substart-strstart);
+					str_append_str(newstr, tmpstr);
+
+					strstart = p + 1;
+					substart = strstart;
+
+					p++;
+				}
+				state = ST_NONE;
+			}
+		}
+	} T_END;
+
+	if ( !result ) return FALSE;
+
+	if ( newstr != NULL ) {
+		if ( strstart != strend )
+			str_append_data(newstr, strstart, strend-strstart);
+
+		sieve_ast_argument_string_set(*arg, newstr);
+	}
+
+	/* Pass the processed string to a (possible) next layer of processing */
+	return sieve_validator_argument_activate_super
+		(valdtr, cmd, *arg, TRUE);
+}
+
+/*
+ * Extension implementation
+ */
+
+static bool ext_encoded_character_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	/* Override the constant string argument with our own */
+	sieve_validator_argument_override
+		(valdtr, SAT_CONST_STRING, ext, &encoded_string_argument);
+
+	return TRUE;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/ext-envelope.c
@@ -0,0 +1,703 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension envelope
+ * ------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5228
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-address.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-address-parts.h"
+#include "sieve-message.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+/*
+ * Forward declarations
+ */
+
+static const struct sieve_command_def envelope_test;
+const struct sieve_operation_def envelope_operation;
+const struct sieve_extension_def envelope_extension;
+
+/*
+ * Extension
+ */
+
+static bool ext_envelope_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+static bool ext_envelope_interpreter_load
+	(const struct sieve_extension *ext,
+		const struct sieve_runtime_env *renv,
+		sieve_size_t *address);
+
+static bool ext_envelope_validator_validate
+	(const struct sieve_extension *ext,
+		struct sieve_validator *valdtr, void *context,
+		struct sieve_ast_argument *require_arg,
+		bool required);
+static int ext_envelope_interpreter_run
+	(const struct sieve_extension *this_ext,
+		const struct sieve_runtime_env *renv,
+		void *context, bool deferred);
+
+const struct sieve_extension_def envelope_extension = {
+	.name = "envelope",
+	.interpreter_load = ext_envelope_interpreter_load,
+	.validator_load = ext_envelope_validator_load,
+	SIEVE_EXT_DEFINE_OPERATION(envelope_operation)
+};
+const struct sieve_validator_extension
+envelope_validator_extension = {
+	.ext = &envelope_extension,
+	.validate = ext_envelope_validator_validate
+};
+const struct sieve_interpreter_extension
+envelope_interpreter_extension = {
+	.ext_def = &envelope_extension,
+	.run = ext_envelope_interpreter_run
+};
+
+static bool ext_envelope_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	/* Register new test */
+	sieve_validator_register_command(valdtr, ext, &envelope_test);
+
+	sieve_validator_extension_register
+		(valdtr, ext, &envelope_validator_extension, NULL);
+	return TRUE;
+}
+
+static bool ext_envelope_interpreter_load
+(const struct sieve_extension *ext,
+	const struct sieve_runtime_env *renv,
+	sieve_size_t *address ATTR_UNUSED)
+{
+	sieve_interpreter_extension_register
+		(renv->interp, ext, &envelope_interpreter_extension, NULL);
+	return TRUE;
+}
+
+static bool ext_envelope_validator_validate
+(const struct sieve_extension *ext,
+	struct sieve_validator *valdtr, void *context ATTR_UNUSED,
+	struct sieve_ast_argument *require_arg,
+	bool required)
+{
+	if (required) {
+		enum sieve_compile_flags flags =
+			sieve_validator_compile_flags(valdtr);
+
+		if ( (flags & SIEVE_COMPILE_FLAG_NO_ENVELOPE) != 0 ) {
+			sieve_argument_validate_error(valdtr, require_arg,
+				"the %s extension cannot be used in this context "
+				"(needs access to message envelope)",
+				sieve_extension_name(ext));
+			return FALSE;
+		}
+	}
+	return TRUE;
+}
+
+static int ext_envelope_interpreter_run
+(const struct sieve_extension *ext,
+	const struct sieve_runtime_env *renv,
+	void *context ATTR_UNUSED, bool deferred)
+{
+	if ( (renv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) != 0 ) {
+		if ( !deferred ) {
+			sieve_runtime_error(renv, NULL,
+				"the %s extension cannot be used in this context "
+				"(needs access to message envelope)",
+				sieve_extension_name(ext));
+		}
+		return SIEVE_EXEC_FAILURE;
+	}
+	return SIEVE_EXEC_OK;
+}
+
+/*
+ * Envelope test
+ *
+ * Syntax
+ *   envelope [COMPARATOR] [ADDRESS-PART] [MATCH-TYPE]
+ *     <envelope-part: string-list> <key-list: string-list>
+ */
+
+static bool tst_envelope_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool tst_envelope_validate
+	(struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_envelope_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+static const struct sieve_command_def envelope_test = {
+	.identifier = "envelope",
+	.type = SCT_TEST,
+	.positional_args= 2,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = tst_envelope_registered,
+	.validate = tst_envelope_validate,
+	.generate = tst_envelope_generate
+};
+
+/*
+ * Envelope operation
+ */
+
+static bool ext_envelope_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int ext_envelope_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def envelope_operation = {
+	.mnemonic = "ENVELOPE",
+	.ext_def = &envelope_extension,
+	.dump = ext_envelope_operation_dump,
+	.execute = ext_envelope_operation_execute
+};
+
+/*
+ * Envelope parts
+ *
+ * FIXME: not available to extensions
+ */
+
+struct sieve_envelope_part {
+	const char *identifier;
+
+	const struct smtp_address *const *(*get_addresses)
+		(const struct sieve_runtime_env *renv);
+	const char * const *(*get_values)
+		(const struct sieve_runtime_env *renv);
+};
+
+static const struct smtp_address *const *_from_part_get_addresses
+	(const struct sieve_runtime_env *renv);
+static const char *const *_from_part_get_values
+	(const struct sieve_runtime_env *renv);
+static const struct smtp_address *const *_to_part_get_addresses
+	(const struct sieve_runtime_env *renv);
+static const char *const *_to_part_get_values
+	(const struct sieve_runtime_env *renv);
+static const char *const *_auth_part_get_values
+	(const struct sieve_runtime_env *renv);
+
+static const struct sieve_envelope_part _from_part = {
+	"from",
+	_from_part_get_addresses,
+	_from_part_get_values,
+};
+
+static const struct sieve_envelope_part _to_part = {
+	"to",
+	_to_part_get_addresses,
+	_to_part_get_values,
+};
+
+static const struct sieve_envelope_part _auth_part = {
+	"auth",
+	NULL,
+	_auth_part_get_values,
+};
+
+static const struct sieve_envelope_part *_envelope_parts[] = {
+	/* Required */
+	&_from_part, &_to_part,
+
+	/* Non-standard */
+	&_auth_part
+};
+
+static unsigned int _envelope_part_count = N_ELEMENTS(_envelope_parts);
+
+static const struct sieve_envelope_part *_envelope_part_find
+(const char *identifier)
+{
+	unsigned int i;
+
+	for ( i = 0; i < _envelope_part_count; i++ ) {
+		if ( strcasecmp( _envelope_parts[i]->identifier, identifier ) == 0 ) {
+			return _envelope_parts[i];
+        }
+	}
+
+	return NULL;
+}
+
+/* Envelope parts implementation */
+
+static const struct smtp_address *const *_from_part_get_addresses
+(const struct sieve_runtime_env *renv)
+{
+	ARRAY(const struct smtp_address *) envelope_values;
+	const struct smtp_address *address =
+		sieve_message_get_sender(renv->msgctx);
+
+	t_array_init(&envelope_values, 2);
+
+	if ( address == NULL )
+		address = smtp_address_create_temp(NULL, NULL);
+	array_append(&envelope_values, &address, 1);
+
+  (void)array_append_space(&envelope_values);
+	return array_idx(&envelope_values, 0);
+}
+
+static const char *const *_from_part_get_values
+(const struct sieve_runtime_env *renv)
+{
+	ARRAY(const char *) envelope_values;
+	const struct smtp_address *address =
+		sieve_message_get_sender(renv->msgctx);
+	const char *value;
+
+	t_array_init(&envelope_values, 2);
+
+	value = "";
+	if ( !smtp_address_isnull(address) )
+		value = smtp_address_encode(address);
+	array_append(&envelope_values, &value, 1);
+
+	(void)array_append_space(&envelope_values);
+
+	return array_idx(&envelope_values, 0);
+}
+
+static const struct smtp_address *const *_to_part_get_addresses
+(const struct sieve_runtime_env *renv)
+{
+	ARRAY(const struct smtp_address *) envelope_values;
+	const struct smtp_address *address =
+		sieve_message_get_orig_recipient(renv->msgctx);
+
+	if ( address != NULL && address->localpart != NULL ) {
+		t_array_init(&envelope_values, 2);
+
+		array_append(&envelope_values, &address, 1);
+
+		(void)array_append_space(&envelope_values);
+		return array_idx(&envelope_values, 0);
+	}
+
+	return NULL;
+}
+
+static const char *const *_to_part_get_values
+(const struct sieve_runtime_env *renv)
+{
+	ARRAY(const char *) envelope_values;
+	const struct smtp_address *address =
+		sieve_message_get_orig_recipient(renv->msgctx);
+
+	t_array_init(&envelope_values, 2);
+
+	if ( address != NULL && address->localpart != NULL) {
+		const char *value = smtp_address_encode(address);
+		array_append(&envelope_values, &value, 1);
+	}
+
+	(void)array_append_space(&envelope_values);
+
+	return array_idx(&envelope_values, 0);
+}
+
+static const char *const *_auth_part_get_values
+(const struct sieve_runtime_env *renv)
+{
+	ARRAY(const char *) envelope_values;
+
+	t_array_init(&envelope_values, 2);
+
+	if ( renv->msgdata->auth_user != NULL )
+		array_append(&envelope_values, &renv->msgdata->auth_user, 1);
+
+	(void)array_append_space(&envelope_values);
+
+	return array_idx(&envelope_values, 0);
+}
+
+/*
+ * Envelope address list
+ */
+
+/* Forward declarations */
+
+static int sieve_envelope_address_list_next_string_item
+	(struct sieve_stringlist *_strlist, string_t **str_r);
+static int sieve_envelope_address_list_next_item
+	(struct sieve_address_list *_addrlist, struct smtp_address *addr_r,
+		string_t **unparsed_r);
+static void sieve_envelope_address_list_reset
+	(struct sieve_stringlist *_strlist);
+
+/* Stringlist object */
+
+struct sieve_envelope_address_list {
+	struct sieve_address_list addrlist;
+
+	struct sieve_stringlist *env_parts;
+
+	const struct smtp_address *const *cur_addresses;
+	const char * const *cur_values;
+
+	int value_index;
+};
+
+static struct sieve_address_list *sieve_envelope_address_list_create
+(const struct sieve_runtime_env *renv, struct sieve_stringlist *env_parts)
+{
+	struct sieve_envelope_address_list *addrlist;
+
+	addrlist = t_new(struct sieve_envelope_address_list, 1);
+	addrlist->addrlist.strlist.runenv = renv;
+	addrlist->addrlist.strlist.exec_status = SIEVE_EXEC_OK;
+	addrlist->addrlist.strlist.next_item =
+		sieve_envelope_address_list_next_string_item;
+	addrlist->addrlist.strlist.reset = sieve_envelope_address_list_reset;
+	addrlist->addrlist.next_item = sieve_envelope_address_list_next_item;
+	addrlist->env_parts = env_parts;
+
+	return &addrlist->addrlist;
+}
+
+static int sieve_envelope_address_list_next_item
+(struct sieve_address_list *_addrlist, struct smtp_address *addr_r,
+	string_t **unparsed_r)
+{
+	struct sieve_envelope_address_list *addrlist =
+		(struct sieve_envelope_address_list *) _addrlist;
+	const struct sieve_runtime_env *renv = _addrlist->strlist.runenv;
+
+	if ( addr_r != NULL )
+		smtp_address_init(addr_r, NULL, NULL);
+	if ( unparsed_r != NULL ) *unparsed_r = NULL;
+
+	while ( addrlist->cur_addresses == NULL && addrlist->cur_values == NULL ) {
+		const struct sieve_envelope_part *epart;
+		string_t *envp_item = NULL;
+		int ret;
+
+		/* Read next header value from source list */
+		if ( (ret=sieve_stringlist_next_item(addrlist->env_parts, &envp_item))
+			<= 0 )
+			return ret;
+
+		if ( _addrlist->strlist.trace ) {
+			sieve_runtime_trace(_addrlist->strlist.runenv, 0,
+				"getting `%s' part from message envelope",
+				str_sanitize(str_c(envp_item), 80));
+		}
+
+		if ( (epart=_envelope_part_find(str_c(envp_item))) != NULL ) {
+			addrlist->value_index = 0;
+
+			if ( epart->get_addresses != NULL ) {
+				/* Field contains addresses */
+				addrlist->cur_addresses = epart->get_addresses(renv);
+
+				/* Drop empty list */
+				if ( addrlist->cur_addresses != NULL &&
+					addrlist->cur_addresses[0] == NULL )
+					addrlist->cur_addresses = NULL;
+			}
+
+			if ( addrlist->cur_addresses == NULL && epart->get_values != NULL ) {
+				/* Field contains something else */
+				addrlist->cur_values = epart->get_values(renv);
+
+				/* Drop empty list */
+				if ( addrlist->cur_values != NULL && addrlist->cur_values[0] == NULL )
+					addrlist->cur_values = NULL;
+			}
+		}
+	}
+
+	/* Return next item */
+	if ( addrlist->cur_addresses != NULL ) {
+		const struct smtp_address *addr =
+			addrlist->cur_addresses[addrlist->value_index];
+
+		if ( addr->localpart == NULL ) {
+			/* Null path <> */
+			if ( unparsed_r != NULL )
+				*unparsed_r = t_str_new_const("", 0);
+		} else {
+			if ( addr_r != NULL )
+				*addr_r = *addr;
+		}
+
+		/* Advance to next value */
+		addrlist->value_index++;
+		if ( addrlist->cur_addresses[addrlist->value_index] == NULL ) {
+			addrlist->cur_addresses = NULL;
+			addrlist->value_index = 0;
+		}
+	} else {
+		if ( unparsed_r != NULL ) {
+			const char *value = addrlist->cur_values[addrlist->value_index];
+
+			*unparsed_r = t_str_new_const(value, strlen(value));
+		}
+
+		/* Advance to next value */
+		addrlist->value_index++;
+		if ( addrlist->cur_values[addrlist->value_index] == NULL ) {
+			addrlist->cur_values = NULL;
+			addrlist->value_index = 0;
+		}
+	}
+
+	return 1;
+}
+
+static int sieve_envelope_address_list_next_string_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+	struct sieve_address_list *addrlist =
+		(struct sieve_address_list *)_strlist;
+	struct smtp_address addr;
+	int ret;
+
+	if ( (ret=sieve_envelope_address_list_next_item
+		(addrlist, &addr, str_r)) <= 0 )
+		return ret;
+
+	if ( addr.localpart != NULL ) {
+		const char *addr_str = smtp_address_encode(&addr);
+		if (str_r != NULL)
+			*str_r = t_str_new_const(addr_str, strlen(addr_str));
+	}
+
+	return 1;
+}
+
+static void sieve_envelope_address_list_reset
+(struct sieve_stringlist *_strlist)
+{
+	struct sieve_envelope_address_list *addrlist =
+		(struct sieve_envelope_address_list *)_strlist;
+
+	sieve_stringlist_reset(addrlist->env_parts);
+	addrlist->cur_addresses = NULL;
+	addrlist->cur_values = NULL;
+	addrlist->value_index = 0;
+}
+
+/*
+ * Command Registration
+ */
+
+static bool tst_envelope_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext ATTR_UNUSED,
+	struct sieve_command_registration *cmd_reg)
+{
+	/* The order of these is not significant */
+	sieve_comparators_link_tag(valdtr, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR);
+	sieve_match_types_link_tags(valdtr, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
+	sieve_address_parts_link_tags(valdtr, cmd_reg, SIEVE_AM_OPT_ADDRESS_PART);
+	return TRUE;
+}
+
+/*
+ * Validation
+ */
+
+static int _envelope_part_is_supported
+(void *context, struct sieve_ast_argument *arg)
+{
+	const struct sieve_envelope_part **not_address =
+		(const struct sieve_envelope_part **) context;
+
+	if ( sieve_argument_is_string_literal(arg) ) {
+		const struct sieve_envelope_part *epart;
+
+		if ( (epart=_envelope_part_find(sieve_ast_strlist_strc(arg))) != NULL ) {
+			if ( epart->get_addresses == NULL ) {
+				if ( *not_address == NULL )
+					*not_address = epart;
+			}
+
+			return 1;
+		}
+
+		return 0;
+	}
+
+	return 1; /* Can't check at compile time */
+}
+
+static bool tst_envelope_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+	struct sieve_ast_argument *arg = tst->first_positional;
+	struct sieve_ast_argument *epart;
+	struct sieve_comparator cmp_default =
+		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+	struct sieve_match_type mcht_default =
+		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+	const struct sieve_envelope_part *not_address = NULL;
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "envelope part", 1, SAAT_STRING_LIST) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+		return FALSE;
+
+	/* Check whether supplied envelope parts are supported
+	 *   FIXME: verify dynamic envelope parts at runtime
+	 */
+	epart = arg;
+	if ( sieve_ast_stringlist_map(&epart, (void *) &not_address,
+		_envelope_part_is_supported) <= 0 ) {
+		i_assert(epart != NULL);
+		sieve_argument_validate_error(valdtr, epart,
+			"specified envelope part '%s' is not supported by the envelope test",
+				str_sanitize(sieve_ast_strlist_strc(epart), 64));
+		return FALSE;
+	}
+
+	if ( not_address != NULL ) {
+		struct sieve_ast_argument *addrp_arg =
+			sieve_command_find_argument(tst, &address_part_tag);
+
+		if ( addrp_arg != NULL ) {
+			sieve_argument_validate_error(valdtr, addrp_arg,
+				"address part ':%s' specified while non-address envelope part '%s' "
+				"is tested with the envelope test",
+                sieve_ast_argument_tag(addrp_arg), not_address->identifier);
+	        return FALSE;
+		}
+	}
+
+	arg = sieve_ast_argument_next(arg);
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "key list", 2, SAAT_STRING_LIST) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+		return FALSE;
+
+	/* Validate the key argument to a specified match type */
+	return sieve_match_type_validate
+		(valdtr, tst, arg, &mcht_default, &cmp_default);
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_envelope_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	(void)sieve_operation_emit(cgenv->sblock, cmd->ext, &envelope_operation);
+
+	/* Generate arguments */
+	if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
+		return FALSE;
+
+	return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool ext_envelope_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "ENVELOPE");
+	sieve_code_descend(denv);
+
+	/* Handle any optional arguments */
+	if ( sieve_addrmatch_opr_optional_dump(denv, address, NULL) != 0 )
+		return FALSE;
+
+	return
+		sieve_opr_stringlist_dump(denv, address, "envelope part") &&
+		sieve_opr_stringlist_dump(denv, address, "key list");
+}
+
+/*
+ * Interpretation
+ */
+
+static int ext_envelope_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	struct sieve_comparator cmp =
+		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+	struct sieve_match_type mcht =
+		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+	struct sieve_address_part addrp =
+		SIEVE_ADDRESS_PART_DEFAULT(all_address_part);
+	struct sieve_stringlist *env_part_list, *value_list, *key_list;
+	struct sieve_address_list *addr_list;
+	int match, ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Read optional operands */
+	if ( sieve_addrmatch_opr_optional_read
+		(renv, address, NULL, &ret, &addrp, &mcht, &cmp) < 0 )
+		return ret;
+
+	/* Read envelope-part */
+	if ( (ret=sieve_opr_stringlist_read
+		(renv, address, "envelope-part", &env_part_list)) <= 0 )
+		return ret;
+
+	/* Read key-list */
+	if ( (ret=sieve_opr_stringlist_read(renv, address, "key-list", &key_list))
+		<= 0 )
+		return ret;
+
+	/*
+	 * Perform test
+	 */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "envelope test");
+
+	/* Create value stringlist */
+	addr_list = sieve_envelope_address_list_create(renv, env_part_list);
+	value_list = sieve_address_part_stringlist_create(renv, &addrp, addr_list);
+
+	/* Perform match */
+	if ( (match=sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret)) < 0 )
+		return ret;
+
+	/* Set test result for subsequent conditional jump */
+	sieve_interpreter_set_test_result(renv->interp, match > 0);
+	return SIEVE_EXEC_OK;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/ext-fileinto.c
@@ -0,0 +1,223 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension fileinto
+ * ------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5228
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+#include "unichar.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-binary.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-result.h"
+
+/*
+ * Forward declarations
+ */
+
+static const struct sieve_command_def fileinto_command;
+const struct sieve_operation_def fileinto_operation;
+const struct sieve_extension_def fileinto_extension;
+
+/*
+ * Extension
+ */
+
+static bool ext_fileinto_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def fileinto_extension = {
+	.name = "fileinto",
+	.validator_load = ext_fileinto_validator_load,
+	SIEVE_EXT_DEFINE_OPERATION(fileinto_operation)
+};
+
+static bool ext_fileinto_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	/* Register new command */
+	sieve_validator_register_command(valdtr, ext, &fileinto_command);
+
+	return TRUE;
+}
+
+/*
+ * Fileinto command
+ *
+ * Syntax:
+ *   fileinto <folder: string>
+ */
+
+static bool cmd_fileinto_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_fileinto_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+static const struct sieve_command_def fileinto_command = {
+	.identifier = "fileinto",
+	.type = SCT_COMMAND,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = cmd_fileinto_validate,
+	.generate = cmd_fileinto_generate
+};
+
+/*
+ * Fileinto operation
+ */
+
+static bool ext_fileinto_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int ext_fileinto_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def fileinto_operation = {
+	.mnemonic = "FILEINTO",
+	.ext_def = &fileinto_extension,
+	.dump = ext_fileinto_operation_dump,
+	.execute = ext_fileinto_operation_execute
+};
+
+/*
+ * Validation
+ */
+
+static bool cmd_fileinto_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *arg = cmd->first_positional;
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "folder", 1, SAAT_STRING) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, cmd, arg, FALSE) )
+		return FALSE;
+
+	/* Check name validity when folder argument is not a variable */
+	if ( sieve_argument_is_string_literal(arg) ) {
+		const char *folder = sieve_ast_argument_strc(arg), *error;
+
+		if ( !sieve_mailbox_check_name(folder, &error) ) {
+			sieve_command_validate_error(valdtr, cmd,
+				"invalid folder name `%s' specified for fileinto command: %s",
+				str_sanitize(folder, 256), error);
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_fileinto_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	sieve_operation_emit(cgenv->sblock, cmd->ext, &fileinto_operation);
+
+	/* Generate arguments */
+	return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool ext_fileinto_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "FILEINTO");
+	sieve_code_descend(denv);
+
+	if ( sieve_action_opr_optional_dump(denv, address, NULL) != 0 )
+		return FALSE;
+
+	return sieve_opr_string_dump(denv, address, "folder");
+}
+
+/*
+ * Execution
+ */
+
+static int ext_fileinto_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	struct sieve_side_effects_list *slist = NULL;
+	string_t *folder;
+	bool folder_literal;
+	bool trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_ACTIONS);
+	int ret = 0;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Optional operands (side effects only) */
+	if ( sieve_action_opr_optional_read(renv, address, NULL, &ret, &slist) != 0 )
+		return ret;
+
+	/* Folder operand */
+	if ( (ret=sieve_opr_string_read_ex
+		(renv, address, "folder", FALSE, &folder, &folder_literal)) <= 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	if ( trace ) {
+		sieve_runtime_trace(renv, 0, "fileinto action");
+		sieve_runtime_trace_descend(renv);
+	}
+
+	if ( !folder_literal && !uni_utf8_str_is_valid(str_c(folder)) ) {
+		sieve_runtime_error(renv, NULL,
+			"folder name specified for fileinto command is not utf-8: %s",
+			str_c(folder));
+		return SIEVE_EXEC_FAILURE;
+	}
+
+
+	if ( trace ) {
+		sieve_runtime_trace(renv, 0, "store message in mailbox `%s'",
+			str_sanitize(str_c(folder), 80));
+	}
+
+	/* Add action to result */
+	if ( sieve_act_store_add_to_result
+		(renv, slist, str_c(folder)) < 0 )
+		return SIEVE_EXEC_FAILURE;
+
+	sieve_message_snapshot(renv->msgctx);
+
+	return SIEVE_EXEC_OK;
+}
+
+
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/ext-reject.c
@@ -0,0 +1,531 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension reject
+ * ----------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5429
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "hostpid.h"
+#include "str-sanitize.h"
+#include "message-date.h"
+#include "message-size.h"
+#include "istream.h"
+#include "istream-header-filter.h"
+
+#include "rfc2822.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-result.h"
+#include "sieve-message.h"
+#include "sieve-smtp.h"
+
+/*
+ * Forward declarations
+ */
+
+static const struct sieve_command_def reject_command;
+static const struct sieve_operation_def reject_operation;
+
+static const struct sieve_command_def ereject_command;
+static const struct sieve_operation_def ereject_operation;
+
+/*
+ * Extensions
+ */
+
+static bool ext_reject_validator_validate
+	(const struct sieve_extension *ext,
+		struct sieve_validator *valdtr, void *context,
+		struct sieve_ast_argument *require_arg,
+		bool required);
+static int ext_reject_interpreter_run
+	(const struct sieve_extension *this_ext,
+		const struct sieve_runtime_env *renv,
+		void *context, bool deferred);
+
+/* Reject */
+
+static bool ext_reject_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+static bool ext_reject_interpreter_load
+	(const struct sieve_extension *ext,
+		const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_extension_def reject_extension = {
+	.name = "reject",
+	.validator_load =	ext_reject_validator_load,
+	.interpreter_load = ext_reject_interpreter_load,
+	SIEVE_EXT_DEFINE_OPERATION(reject_operation)
+};
+const struct sieve_validator_extension
+reject_validator_extension = {
+	.ext = &reject_extension,
+	.validate = ext_reject_validator_validate
+};
+const struct sieve_interpreter_extension
+reject_interpreter_extension = {
+	.ext_def = &reject_extension,
+	.run = ext_reject_interpreter_run
+};
+
+static bool ext_reject_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	/* Register new command */
+	sieve_validator_register_command(valdtr, ext, &reject_command);
+
+	sieve_validator_extension_register
+		(valdtr, ext, &reject_validator_extension, NULL);
+	return TRUE;
+}
+
+static bool ext_reject_interpreter_load
+(const struct sieve_extension *ext,
+	const struct sieve_runtime_env *renv,
+	sieve_size_t *address ATTR_UNUSED)
+{
+	sieve_interpreter_extension_register
+		(renv->interp, ext, &reject_interpreter_extension, NULL);
+	return TRUE;
+}
+
+/* EReject */
+
+static bool ext_ereject_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+static bool ext_ereject_interpreter_load
+	(const struct sieve_extension *ext,
+		const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_extension_def ereject_extension = {
+	.name = "ereject",
+	.validator_load = ext_ereject_validator_load,
+	.interpreter_load = ext_ereject_interpreter_load,
+	SIEVE_EXT_DEFINE_OPERATION(ereject_operation)
+};
+const struct sieve_validator_extension
+ereject_validator_extension = {
+	.ext = &ereject_extension,
+	.validate = ext_reject_validator_validate
+};
+const struct sieve_interpreter_extension
+ereject_interpreter_extension = {
+	.ext_def = &ereject_extension,
+	.run = ext_reject_interpreter_run
+};
+
+static bool ext_ereject_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	/* Register new command */
+	sieve_validator_register_command(valdtr, ext, &ereject_command);
+
+	sieve_validator_extension_register
+		(valdtr, ext, &ereject_validator_extension, NULL);
+	return TRUE;
+}
+
+static bool ext_ereject_interpreter_load
+(const struct sieve_extension *ext,
+	const struct sieve_runtime_env *renv,
+	sieve_size_t *address ATTR_UNUSED)
+{
+	sieve_interpreter_extension_register
+		(renv->interp, ext, &ereject_interpreter_extension, NULL);
+	return TRUE;
+}
+
+/* Environment checking */
+
+static bool ext_reject_validator_validate
+(const struct sieve_extension *ext,
+	struct sieve_validator *valdtr, void *context ATTR_UNUSED,
+	struct sieve_ast_argument *require_arg,
+	bool required)
+{
+	if (required) {
+		enum sieve_compile_flags flags =
+			sieve_validator_compile_flags(valdtr);
+
+		if ( (flags & SIEVE_COMPILE_FLAG_NO_ENVELOPE) != 0 ) {
+			sieve_argument_validate_error(valdtr, require_arg,
+				"the %s extension cannot be used in this context "
+				"(needs access to message envelope)",
+				sieve_extension_name(ext));
+			return FALSE;
+		}
+	}
+	return TRUE;
+}
+
+static int ext_reject_interpreter_run
+(const struct sieve_extension *ext,
+	const struct sieve_runtime_env *renv,
+	void *context ATTR_UNUSED, bool deferred)
+{
+	if ( (renv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) != 0 ) {
+		if ( !deferred ) {
+			sieve_runtime_error(renv, NULL,
+				"the %s extension cannot be used in this context "
+				"(needs access to message envelope)",
+				sieve_extension_name(ext));
+		}
+		return SIEVE_EXEC_FAILURE;
+	}
+	return SIEVE_EXEC_OK;
+}
+
+/*
+ * Commands
+ */
+
+/* Forward declarations */
+
+static bool cmd_reject_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_reject_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+/* Reject command
+ *
+ * Syntax:
+ *   reject <reason: string>
+ */
+
+static const struct sieve_command_def reject_command = {
+	.identifier = "reject",
+	.type = SCT_COMMAND,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = cmd_reject_validate,
+	.generate = cmd_reject_generate
+};
+
+/* EReject command
+ *
+ * Syntax:
+ *   ereject <reason: string>
+ */
+
+static const struct sieve_command_def ereject_command = {
+	.identifier = "ereject",
+	.type = SCT_COMMAND,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = cmd_reject_validate,
+	.generate = cmd_reject_generate,
+};
+
+/*
+ * Operations
+ */
+
+/* Forward declarations */
+
+static bool ext_reject_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int ext_reject_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+/* Reject operation */
+
+static const struct sieve_operation_def reject_operation = {
+	.mnemonic = "REJECT",
+	.ext_def = &reject_extension,
+	.dump = ext_reject_operation_dump,
+	.execute = ext_reject_operation_execute
+};
+
+/* EReject operation */
+
+static const struct sieve_operation_def ereject_operation = {
+	.mnemonic = "EREJECT",
+	.ext_def = &ereject_extension,
+	.dump = ext_reject_operation_dump,
+	.execute = ext_reject_operation_execute
+};
+
+/*
+ * Reject action
+ */
+
+static int act_reject_check_duplicate
+	(const struct sieve_runtime_env *renv,
+		const struct sieve_action *act,
+		const struct sieve_action *act_other);
+int act_reject_check_conflict
+	(const struct sieve_runtime_env *renv,
+		const struct sieve_action *act,
+		const struct sieve_action *act_other);
+static void act_reject_print
+	(const struct sieve_action *action,
+		const struct sieve_result_print_env *rpenv, bool *keep);
+static int act_reject_commit
+	(const struct sieve_action *action ATTR_UNUSED,
+		const struct sieve_action_exec_env *aenv, void *tr_context, bool *keep);
+
+const struct sieve_action_def act_reject = {
+	.name = "reject",
+	.flags = SIEVE_ACTFLAG_SENDS_RESPONSE,
+	.check_duplicate = act_reject_check_duplicate,
+	.check_conflict = act_reject_check_conflict,
+	.print = act_reject_print,
+	.commit = act_reject_commit,
+};
+
+struct act_reject_context {
+	const char *reason;
+	bool ereject;
+};
+
+/*
+ * Validation
+ */
+
+static bool cmd_reject_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *arg = cmd->first_positional;
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "reason", 1, SAAT_STRING) ) {
+		return FALSE;
+	}
+
+	return sieve_validator_argument_activate(valdtr, cmd, arg, FALSE);
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_reject_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	if ( sieve_command_is(cmd, reject_command) )
+		sieve_operation_emit(cgenv->sblock, cmd->ext, &reject_operation);
+	else
+		sieve_operation_emit(cgenv->sblock, cmd->ext, &ereject_operation);
+
+	/* Generate arguments */
+	return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool ext_reject_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "%s", sieve_operation_mnemonic(denv->oprtn));
+	sieve_code_descend(denv);
+
+	if ( sieve_action_opr_optional_dump(denv, address, NULL) != 0 )
+		return FALSE;
+
+	return sieve_opr_string_dump(denv, address, "reason");
+}
+
+/*
+ * Interpretation
+ */
+
+static int ext_reject_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	const struct sieve_operation *oprtn = renv->oprtn;
+	const struct sieve_extension *this_ext = oprtn->ext;
+	struct sieve_side_effects_list *slist = NULL;
+	struct act_reject_context *act;
+	string_t *reason;
+	pool_t pool;
+	int ret;
+
+	/*
+	 * Read data
+	 */
+
+	/* Optional operands (side effects only) */
+	if ( sieve_action_opr_optional_read(renv, address, NULL, &ret, &slist) != 0 )
+		return ret;
+
+	/* Read rejection reason */
+	if ( (ret=sieve_opr_string_read(renv, address, "reason", &reason)) <= 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_ACTIONS) ) {
+		if ( sieve_operation_is(oprtn, ereject_operation) )
+			sieve_runtime_trace(renv, 0, "ereject action");
+		else
+			sieve_runtime_trace(renv, 0, "reject action");
+
+		sieve_runtime_trace_descend(renv);
+		sieve_runtime_trace(renv, 0, "reject message with reason `%s'",
+			str_sanitize(str_c(reason), 64));
+	}
+
+	/* Add reject action to the result */
+	pool = sieve_result_pool(renv->result);
+	act = p_new(pool, struct act_reject_context, 1);
+	act->reason = p_strdup(pool, str_c(reason));
+	act->ereject = ( sieve_operation_is(oprtn, ereject_operation) );
+
+	if ( sieve_result_add_action
+		(renv, this_ext, &act_reject, slist, (void *) act, 0, FALSE) < 0 )
+		return SIEVE_EXEC_FAILURE;
+
+	return SIEVE_EXEC_OK;
+}
+
+/*
+ * Action implementation
+ */
+
+static int act_reject_check_duplicate
+(const struct sieve_runtime_env *renv ATTR_UNUSED,
+	const struct sieve_action *act,
+	const struct sieve_action *act_other)
+{
+	if ( !act_other->executed ) {
+		sieve_runtime_error(renv, act->location,
+			"duplicate reject/ereject action not allowed "
+			"(previously triggered one was here: %s)", act_other->location);
+		return -1;
+	}
+
+	return 1;
+}
+
+int act_reject_check_conflict
+(const struct sieve_runtime_env *renv,
+	const struct sieve_action *act,
+	const struct sieve_action *act_other)
+{
+	if ( (act_other->def->flags & SIEVE_ACTFLAG_TRIES_DELIVER) > 0 ) {
+		if ( !act_other->executed ) {
+			sieve_runtime_error(renv, act->location,
+				"reject/ereject action conflicts with other action: "
+				"the %s action (%s) tries to deliver the message",
+				act_other->def->name, act_other->location);
+			return -1;
+		}
+	}
+
+	if ( (act_other->def->flags & SIEVE_ACTFLAG_SENDS_RESPONSE) > 0 ) {
+		struct act_reject_context *rj_ctx;
+
+		if ( !act_other->executed ) {
+			sieve_runtime_error(renv, act->location,
+				"reject/ereject action conflicts with other action: "
+				"the %s action (%s) also sends a response to the sender",
+				act_other->def->name, act_other->location);
+			return -1;
+		}
+
+		/* Conflicting action was already executed, transform reject into discard
+		 * equivalent.
+		 */
+		rj_ctx = (struct act_reject_context *) act->context;
+		rj_ctx->reason = NULL;
+	}
+
+	return 0;
+}
+
+static void act_reject_print
+(const struct sieve_action *action,	const struct sieve_result_print_env *rpenv,
+	bool *keep)
+{
+	struct act_reject_context *rj_ctx =
+		(struct act_reject_context *) action->context;
+
+	if ( rj_ctx->reason != NULL ) {
+		sieve_result_action_printf(rpenv, "reject message with reason: %s",
+			str_sanitize(rj_ctx->reason, 128));
+	} else {
+		sieve_result_action_printf(rpenv,
+			"reject message without sending a response (discard)");
+	}
+
+	*keep = FALSE;
+}
+
+static int act_reject_commit
+(const struct sieve_action *action, const struct sieve_action_exec_env *aenv,
+	void *tr_context ATTR_UNUSED, bool *keep)
+{
+	struct act_reject_context *rj_ctx =
+		(struct act_reject_context *) action->context;
+	const struct smtp_address *sender, *recipient;
+	int ret;
+
+	sender = sieve_message_get_sender(aenv->msgctx);
+	recipient = sieve_message_get_orig_recipient(aenv->msgctx);
+
+	if ((aenv->flags & SIEVE_EXECUTE_FLAG_SKIP_RESPONSES) != 0) {
+		sieve_result_global_log(aenv,
+			"not sending reject message (skipped)");
+		return SIEVE_EXEC_OK;
+	}
+
+	if ( smtp_address_isnull(recipient) ) {
+		sieve_result_global_warning(aenv,
+			"reject action aborted: envelope recipient is <>");
+		return SIEVE_EXEC_OK;
+	}
+
+	if ( rj_ctx->reason == NULL ) {
+		sieve_result_global_log(aenv,
+			"not sending reject message (would cause second response to sender)");
+
+		*keep = FALSE;
+		return SIEVE_EXEC_OK;
+	}
+
+	if ( smtp_address_isnull(sender) ) {
+		sieve_result_global_log(aenv, "not sending reject message to <>");
+
+		*keep = FALSE;
+		return SIEVE_EXEC_OK;
+	}
+
+	if ( (ret=sieve_action_reject_mail
+		(aenv, recipient, rj_ctx->reason)) <= 0 )
+		return ret;
+
+	sieve_result_global_log(aenv,
+		"rejected message from <%s> (%s)",
+		smtp_address_encode(sender),
+		( rj_ctx->ereject ? "ereject" : "reject" ));
+
+	*keep = FALSE;
+	return SIEVE_EXEC_OK;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/mcht-contains.c
@@ -0,0 +1,66 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Match-type ':contains'
+ */
+
+#include "lib.h"
+
+#include "sieve-match-types.h"
+#include "sieve-comparators.h"
+#include "sieve-match.h"
+
+#include <string.h>
+#include <stdio.h>
+
+/*
+ * Forward declarations
+ */
+
+static int mcht_contains_match_key
+	(struct sieve_match_context *mctx, const char *val, size_t val_size,
+		const char *key, size_t key_size);
+
+/*
+ * Match-type object
+ */
+
+const struct sieve_match_type_def contains_match_type = {
+	SIEVE_OBJECT("contains",
+		&match_type_operand, SIEVE_MATCH_TYPE_CONTAINS),
+	.validate_context = sieve_match_substring_validate_context,
+	.match_key = mcht_contains_match_key
+};
+
+/*
+ * Match-type implementation
+ */
+
+/* FIXME: Naive substring match implementation. Should switch to more
+ * efficient algorithm if large values need to be searched (e.g. message body).
+ */
+static int mcht_contains_match_key
+(struct sieve_match_context *mctx, const char *val, size_t val_size,
+	const char *key, size_t key_size)
+{
+	const struct sieve_comparator *cmp = mctx->comparator;
+	const char *vend = (const char *) val + val_size;
+	const char *kend = (const char *) key + key_size;
+	const char *vp = val;
+	const char *kp = key;
+
+	if ( val_size == 0 )
+		return ( key_size == 0 ? 1 : 0 );
+
+	if ( cmp->def == NULL || cmp->def->char_match == NULL )
+		return 0;
+
+	while ( (vp < vend) && (kp < kend) ) {
+		if ( !cmp->def->char_match(cmp, &vp, vend, &kp, kend) )
+			vp++;
+	}
+
+	return ( kp == kend ? 1 : 0 );
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/mcht-is.c
@@ -0,0 +1,52 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Match-type ':is':
+ */
+
+#include "lib.h"
+
+#include "sieve-match-types.h"
+#include "sieve-comparators.h"
+#include "sieve-match.h"
+
+#include <string.h>
+#include <stdio.h>
+
+/*
+ * Forward declarations
+ */
+
+static int mcht_is_match_key
+	(struct sieve_match_context *mctx, const char *val, size_t val_size,
+		const char *key, size_t key_size);
+
+/*
+ * Match-type object
+ */
+
+const struct sieve_match_type_def is_match_type = {
+	SIEVE_OBJECT("is",
+		&match_type_operand, SIEVE_MATCH_TYPE_IS),
+	.match_key = mcht_is_match_key
+};
+
+/*
+ * Match-type implementation
+ */
+
+static int mcht_is_match_key
+(struct sieve_match_context *mctx ATTR_UNUSED,
+	const char *val, size_t val_size,
+	const char *key, size_t key_size)
+{
+	if ( val_size == 0 )
+		return ( key_size == 0 ? 1 : 0 );
+
+	if ( mctx->comparator->def != NULL && mctx->comparator->def->compare != NULL )
+		return (mctx->comparator->def->compare(mctx->comparator,
+			val, val_size, key, key_size) == 0 ? 1 : 0 );
+
+	return 0;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/mcht-matches.c
@@ -0,0 +1,440 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Match-type ':matches'
+ */
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve-match-types.h"
+#include "sieve-comparators.h"
+#include "sieve-match.h"
+
+#include <string.h>
+#include <stdio.h>
+
+/*
+ * Forward declarations
+ */
+
+static int mcht_matches_match_key
+	(struct sieve_match_context *mctx, const char *val, size_t val_size,
+		const char *key, size_t key_size);
+
+/*
+ * Match-type object
+ */
+
+const struct sieve_match_type_def matches_match_type = {
+	SIEVE_OBJECT("matches",
+		&match_type_operand, SIEVE_MATCH_TYPE_MATCHES),
+	.validate_context = sieve_match_substring_validate_context,
+	.match_key = mcht_matches_match_key
+};
+
+/*
+ * Match-type implementation
+ */
+
+/* Quick 'n dirty debug */
+//#define MATCH_DEBUG
+#ifdef MATCH_DEBUG
+#define debug_printf(...) printf ("match debug: " __VA_ARGS__)
+#else
+#define debug_printf(...)
+#endif
+
+/* FIXME: Naive implementation, substitute this with dovecot src/lib/str-find.c
+ */
+static inline bool _string_find(const struct sieve_comparator *cmp,
+	const char **valp, const char *vend, const char **keyp, const char *kend)
+{
+	while ( (*valp < vend) && (*keyp < kend) ) {
+		if ( !cmp->def->char_match(cmp, valp, vend, keyp, kend) )
+			(*valp)++;
+	}
+
+	return (*keyp == kend);
+}
+
+static char _scan_key_section
+	(string_t *section, const char **wcardp, const char *key_end)
+{
+	/* Find next wildcard and resolve escape sequences */
+	str_truncate(section, 0);
+	while ( *wcardp < key_end && **wcardp != '*' && **wcardp != '?') {
+		if ( **wcardp == '\\' ) {
+			(*wcardp)++;
+		}
+		str_append_c(section, **wcardp);
+		(*wcardp)++;
+	}
+
+	/* Record wildcard character or \0 */
+	if ( *wcardp < key_end ) {
+		return **wcardp;
+	}
+
+	i_assert( *wcardp == key_end );
+	return '\0';
+}
+
+static int mcht_matches_match_key
+(struct sieve_match_context *mctx, const char *val, size_t val_size,
+	const char *key, size_t key_size)
+{
+	const struct sieve_comparator *cmp = mctx->comparator;
+	struct sieve_match_values *mvalues;
+	string_t *mvalue = NULL, *mchars = NULL;
+	string_t *section, *subsection;
+	const char *vend, *kend, *vp, *kp, *wp, *pvp;
+	bool backtrack = FALSE; /* TRUE: match of '?'-connected sections failed */
+	char wcard = '\0';      /* Current wildcard */
+	char next_wcard = '\0'; /* Next  widlcard */
+	unsigned int key_offset = 0;
+
+	if ( cmp->def == NULL || cmp->def->char_match == NULL )
+		return 0;
+
+	/* Key sections */
+	section = t_str_new(32);    /* Section (after beginning or *) */
+	subsection = t_str_new(32); /* Sub-section (after ?) */
+
+	/* Mark end of value and key */
+	vend = (const char *) val + val_size;
+	kend = (const char *) key + key_size;
+
+	/* Initialize pointers */
+	vp = val;                   /* Value pointer */
+	kp = key;                   /* Key pointer */
+	wp = key;                   /* Wildcard (key) pointer */
+
+	/* Start match values list if requested */
+	if ( (mvalues = sieve_match_values_start(mctx->runenv)) != NULL ) {
+		/* Skip ${0} for now; added when match succeeds */
+		sieve_match_values_add(mvalues, NULL);
+
+		mvalue = t_str_new(32);     /* Match value (*) */
+		mchars = t_str_new(32);     /* Match characters (.?..?.??) */
+	}
+
+	/* Match the pattern:
+	 *   <pattern> = <section>*<section>*<section>...
+	 *   <section> = <sub-section>?<sub-section>?<sub-section>...
+	 *
+	 * Escape sequences \? and \* need special attention.
+	 */
+
+	debug_printf("=== Start ===\n");
+	debug_printf("  key:   %s\n", t_strdup_until(key, kend));
+	debug_printf("  value: %s\n", t_strdup_until(val, vend));
+
+	/* Loop until either key or value ends */
+	while (kp < kend && vp < vend ) {
+		const char *needle, *nend;
+
+		if ( !backtrack ) {
+			/* Search the next '*' wildcard in the key string */
+
+			wcard = next_wcard;
+
+			/* Find the needle to look for in the string */
+			key_offset = 0;
+			for (;;) {
+				next_wcard = _scan_key_section(section, &wp, kend);
+
+				if ( wcard == '\0' || str_len(section) > 0 )
+					break;
+
+				if ( next_wcard == '*' ) {
+					break;
+				}
+
+				if ( wp < kend )
+					wp++;
+				else
+					break;
+				key_offset++;
+			}
+
+			debug_printf("found wildcard '%c' at pos [%d]\n",
+				next_wcard, (int) (wp-key));
+
+			if ( mvalues != NULL )
+				str_truncate(mvalue, 0);
+		} else {
+			/* Backtracked; '*' wildcard is retained */
+			debug_printf("backtracked");
+			backtrack = FALSE;
+		}
+
+		/* Determine what we are looking for */
+		needle = str_c(section);
+		nend = PTR_OFFSET(needle, str_len(section));
+
+		debug_printf("  section needle:  '%s'\n", t_strdup_until(needle, nend));
+		debug_printf("  section key:     '%s'\n", t_strdup_until(kp, kend));
+		debug_printf("  section remnant: '%s'\n", t_strdup_until(wp, kend));
+		debug_printf("  value remnant:   '%s'\n", t_strdup_until(vp, vend));
+		debug_printf("  key offset:      %d\n", key_offset);
+
+		pvp = vp;
+		if ( next_wcard == '\0' ) {
+			if ( wcard == '\0' ) {
+				/* No current wildcard; match needs to happen right at the beginning */
+				debug_printf("next_wcard = NULL && wcard = NUL; needle should be equal to value.\n");
+
+				if ( (vend - vp) != (nend - needle) ||
+					!cmp->def->char_match(cmp, &vp, vend, &needle, nend) ) {
+					debug_printf("  key not equal to value\n");
+					break;
+				}
+
+			} else {
+				const char *qp, *qend;
+				size_t slen;
+
+				/* No more wildcards; find the needle substring at the end of string */
+				debug_printf("next_wcard = NUL; must find needle at end\n");
+
+				/* Check if the value is still large enough */
+				slen = str_len(section);
+				if ( (vp + slen) > vend ) {
+					debug_printf("  wont match: value is too short\n");
+					break;
+				}
+
+				/* Move value pointer to where the needle should be */
+				vp = vend - slen;
+
+				/* Record match values */
+				qend = vp;
+				qp = vp - key_offset;
+
+				if ( mvalues != NULL )
+					str_append_data(mvalue, pvp, qp-pvp);
+
+				/* Compare needle to end of value string */
+				if ( !cmp->def->char_match(cmp, &vp, vend, &needle, nend) ) {
+					debug_printf("  match at end failed\n");
+					break;
+				}
+
+				/* Add match values */
+				if ( mvalues != NULL ) {
+					/* Append '*' match value */
+					sieve_match_values_add(mvalues, mvalue);
+
+					/* Append any initial '?' match values */
+					for ( ; qp < qend; qp++ )
+						sieve_match_values_add_char(mvalues, *qp);
+				}
+			}
+
+			/* Finish match */
+			kp = kend;
+			vp = vend;
+
+			debug_printf("  matched end of value\n");
+			break;
+		} else {
+			/* Next wildcard found; match needle before next wildcard */
+
+			const char *prv = NULL; /* Stored value pointer for backtrack */
+			const char *prk = NULL; /* Stored key pointer for backtrack */
+			const char *prw = NULL; /* Stored wildcard pointer for backtrack */
+			const char *chars;
+
+			/* Reset '?' match values */
+			if ( mvalues != NULL )
+				str_truncate(mchars, 0);
+
+			if ( wcard == '\0' ) {
+				/* No current wildcard; match needs to happen right at the beginning */
+				debug_printf("wcard = NUL; needle should be found at the beginning.\n");
+				debug_printf("  begin needle: '%s'\n", t_strdup_until(needle, nend));
+				debug_printf("  begin value:  '%s'\n", t_strdup_until(vp, vend));
+
+				if ( !cmp->def->char_match(cmp, &vp, vend, &needle, nend) ) {
+					debug_printf("  failed to find needle at beginning\n");
+					break;
+				}
+
+			} else {
+				/* Current wildcard present; match needle between current and next wildcard */
+				debug_printf("wcard != NUL; must find needle at an offset (>= %d).\n",
+					key_offset);
+
+				/* Match may happen at any offset (>= key offset): find substring */
+				vp += key_offset;
+				if ( (vp >= vend) || !_string_find(cmp, &vp, vend, &needle, nend) ) {
+					debug_printf("  failed to find needle at an offset\n");
+					break;
+				}
+
+				prv = vp - str_len(section);
+				prk = kp;
+				prw = wp;
+
+				/* Append match values */
+				if ( mvalues != NULL ) {
+					const char *qend = vp - str_len(section);
+					const char *qp = qend - key_offset;
+
+					/* Append '*' match value */
+					str_append_data(mvalue, pvp, qp-pvp);
+
+					/* Append any initial '?' match values (those that caused the key
+					 * offset.
+					 */
+					for ( ; qp < qend; qp++ )
+						str_append_c(mchars, *qp);
+				}
+			}
+
+			/* Update wildcard and key pointers for next wildcard scan */
+			if ( wp < kend ) wp++;
+			kp = wp;
+
+			/* Scan successive '?' wildcards */
+			while ( next_wcard == '?' ) {
+				debug_printf("next_wcard = '?'; need to match arbitrary character\n");
+
+				/* Add match value */
+				if ( mvalues != NULL )
+					str_append_c(mchars, *vp);
+
+				vp++;
+
+				/* Scan for next '?' wildcard */
+				next_wcard = _scan_key_section(subsection, &wp, kend);
+				debug_printf("found next wildcard '%c' at pos [%d] (fixed match)\n",
+					next_wcard, (int) (wp-key));
+
+				/* Determine what we are looking for */
+				needle = str_c(subsection);
+				nend = needle + str_len(subsection);
+
+				debug_printf("  sub key:       '%s'\n", t_strdup_until(needle, nend));
+				debug_printf("  value remnant: '%s'\n", vp <= vend ? t_strdup_until(vp, vend) : "");
+
+				/* Try matching the needle at fixed position */
+				if ( (needle == nend && next_wcard == '\0' && vp < vend ) ||
+					!cmp->def->char_match(cmp, &vp, vend, &needle, nend) ) {
+
+					/* Match failed: now we have a problem. We need to backtrack to the previous
+					 * '*' wildcard occurrence and start scanning for the next possible match.
+					 */
+
+					debug_printf("  failed fixed match\n");
+
+					/* Start backtrack */
+					if ( prv != NULL && prv + 1 < vend ) {
+						/* Restore pointers */
+						vp = prv;
+						kp = prk;
+						wp = prw;
+
+						/* Skip forward one value character to scan the next possible match */
+						if ( mvalues != NULL )
+							str_append_c(mvalue, *vp);
+						vp++;
+
+						/* Set wildcard state appropriately */
+						wcard = '*';
+						next_wcard = '?';
+
+						/* Backtrack */
+						backtrack = TRUE;
+
+						debug_printf("  BACKTRACK\n");
+					}
+
+					/* Break '?' wildcard scanning loop */
+					break;
+				}
+
+				/* Update wildcard and key pointers for next wildcard scan */
+				if ( wp < kend ) wp++;
+				kp = wp;
+			}
+
+			if ( !backtrack ) {
+				unsigned int i;
+
+				if ( next_wcard == '?' ) {
+					debug_printf("failed to match '?'\n");
+					break;
+				}
+
+				if ( mvalues != NULL ) {
+					if ( prv != NULL )
+						sieve_match_values_add(mvalues, mvalue);
+
+					chars = (const char *) str_data(mchars);
+
+					for ( i = 0; i < str_len(mchars); i++ ) {
+						sieve_match_values_add_char(mvalues, chars[i]);
+					}
+				}
+
+				if ( next_wcard != '*' ) {
+					debug_printf("failed to match at end of string\n");
+					break;
+				}
+			}
+		}
+
+		/* Check whether string ends in a wildcard
+		 * (avoid scanning the rest of the string)
+		 */
+		if ( kp == kend && next_wcard == '*' ) {
+			/* Add the rest of the string as match value */
+			if ( mvalues != NULL ) {
+				str_truncate(mvalue, 0);
+				str_append_data(mvalue, vp, vend-vp);
+				sieve_match_values_add(mvalues, mvalue);
+			}
+
+			/* Finish match */
+			kp = kend;
+			vp = vend;
+
+			debug_printf("key ends with '*'\n");
+			break;
+		}
+
+		debug_printf("== Loop ==\n");
+	}
+
+	/* Eat away a trailing series of *s */
+	if ( vp == vend ) {
+		while ( kp < kend && *kp == '*' ) kp++;
+	}
+
+	/* By definition, the match is only successful if both value and key pattern
+	 * are exhausted.
+	 */
+
+	debug_printf("=== Finish ===\n");
+	debug_printf("  result: %s\n", (kp == kend && vp == vend) ? "true" : "false");
+
+	if (kp == kend && vp == vend) {
+		/* Activate new match values after successful match */
+		if ( mvalues != NULL ) {
+			/* Set ${0} */
+			string_t *matched = str_new_const(pool_datastack_create(), val, val_size);
+			sieve_match_values_set(mvalues, 0, matched);
+
+			/* Commit new match values */
+			sieve_match_values_commit(mctx->runenv, &mvalues);
+		}
+		return 1;
+	}
+
+	/* No match; drop collected match values */
+	sieve_match_values_abort(&mvalues);
+	return 0;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/Makefile.am
@@ -0,0 +1,31 @@
+if BUILD_UNFINISHED
+UNFINISHED = 
+endif
+
+SUBDIRS = \
+	vacation \
+	subaddress \
+	comparator-i-ascii-numeric \
+	relational \
+	regex \
+	imap4flags \
+	copy \
+	include \
+	body \
+	variables \
+	enotify \
+	notify \
+	environment \
+	mailbox \
+	date \
+	spamvirustest \
+	ihave \
+	editheader \
+	duplicate \
+	index \
+	metadata \
+	mime \
+	vnd.dovecot \
+	$(UNFINISHED)
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/body/Makefile.am
@@ -0,0 +1,16 @@
+noinst_LTLIBRARIES = libsieve_ext_body.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../.. \
+	$(LIBDOVECOT_INCLUDE)
+
+tsts = \
+	tst-body.c
+
+libsieve_ext_body_la_SOURCES = \
+	ext-body-common.c \
+	$(tsts) \
+	ext-body.c
+
+noinst_HEADERS = \
+	ext-body-common.h
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/body/ext-body-common.c
@@ -0,0 +1,102 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "mempool.h"
+#include "buffer.h"
+#include "array.h"
+#include "str.h"
+#include "istream.h"
+#include "mail-storage.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+#include "sieve-interpreter.h"
+
+#include "ext-body-common.h"
+
+
+/*
+ * Body part stringlist
+ */
+
+static int ext_body_stringlist_next_item
+	(struct sieve_stringlist *_strlist, string_t **str_r);
+static void ext_body_stringlist_reset
+	(struct sieve_stringlist *_strlist);
+
+struct ext_body_stringlist {
+	struct sieve_stringlist strlist;
+
+	struct sieve_message_part_data *body_parts;
+	struct sieve_message_part_data *body_parts_iter;
+};
+
+int ext_body_get_part_list
+(const struct sieve_runtime_env *renv, enum tst_body_transform transform,
+	const char * const *content_types, struct sieve_stringlist **strlist_r)
+{
+	static const char * const _no_content_types[] = { "", NULL };
+	struct ext_body_stringlist *strlist;
+	struct sieve_message_part_data *body_parts = NULL;
+	int ret;
+
+	*strlist_r = NULL;
+
+	if ( content_types == NULL ) content_types = _no_content_types;
+
+	switch ( transform ) {
+	case TST_BODY_TRANSFORM_RAW:
+		if ( (ret=sieve_message_body_get_raw(renv, &body_parts)) <= 0 )
+			return ret;
+		break;
+	case TST_BODY_TRANSFORM_CONTENT:
+		if ( (ret=sieve_message_body_get_content
+			(renv, content_types, &body_parts)) <= 0 )
+			return ret;
+		break;
+	case TST_BODY_TRANSFORM_TEXT:
+		if ( (ret=sieve_message_body_get_text(renv, &body_parts)) <= 0 )
+			return ret;
+		break;
+	default:
+		i_unreached();
+	}
+
+	strlist = t_new(struct ext_body_stringlist, 1);
+	strlist->strlist.runenv = renv;
+	strlist->strlist.next_item = ext_body_stringlist_next_item;
+	strlist->strlist.reset = ext_body_stringlist_reset;
+	strlist->body_parts = body_parts;
+	strlist->body_parts_iter = body_parts;
+
+	*strlist_r = &strlist->strlist;
+	return SIEVE_EXEC_OK;
+}
+
+static int ext_body_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+	struct ext_body_stringlist *strlist =
+		(struct ext_body_stringlist *)_strlist;
+
+	*str_r = NULL;
+
+	if ( strlist->body_parts_iter->content == NULL ) return 0;
+
+	*str_r = t_str_new_const
+		(strlist->body_parts_iter->content, strlist->body_parts_iter->size);
+	strlist->body_parts_iter++;
+	return 1;
+}
+
+static void ext_body_stringlist_reset
+(struct sieve_stringlist *_strlist)
+{
+	struct ext_body_stringlist *strlist =
+		(struct ext_body_stringlist *)_strlist;
+
+	strlist->body_parts_iter = strlist->body_parts;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/body/ext-body-common.h
@@ -0,0 +1,40 @@
+#ifndef EXT_BODY_COMMON_H
+#define EXT_BODY_COMMON_H
+
+/*
+ * Types
+ */
+
+enum tst_body_transform {
+	TST_BODY_TRANSFORM_RAW,
+	TST_BODY_TRANSFORM_CONTENT,
+	TST_BODY_TRANSFORM_TEXT
+};
+
+/*
+ * Extension
+ */
+
+extern const struct sieve_extension_def body_extension;
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def body_test;
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def body_operation;
+
+/*
+ * Message body part extraction
+ */
+
+int ext_body_get_part_list
+	(const struct sieve_runtime_env *renv, enum tst_body_transform transform,
+		const char * const *content_types, struct sieve_stringlist **strlist_r);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/body/ext-body.c
@@ -0,0 +1,54 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension body
+ * ------------------
+ *
+ * Authors: Stephan Bosch
+ *          Original CMUSieve implementation by Timo Sirainen
+ * Specification: RFC 5173
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+#include "array.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-address-parts.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-body-common.h"
+
+/*
+ * Extension
+ */
+
+static bool ext_body_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def body_extension = {
+	.name = "body",
+	.validator_load =	ext_body_validator_load,
+	SIEVE_EXT_DEFINE_OPERATION(body_operation)
+};
+
+static bool ext_body_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	/* Register new test */
+	sieve_validator_register_command(valdtr, ext, &body_test);
+
+	return TRUE;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/body/tst-body.c
@@ -0,0 +1,385 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-address-parts.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+#include "ext-body-common.h"
+
+/*
+ * Body test
+ *
+ * Syntax
+ *   body [COMPARATOR] [MATCH-TYPE] [BODY-TRANSFORM]
+ *     <key-list: string-list>
+ */
+
+static bool tst_body_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool tst_body_validate
+	(struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_body_generate
+	(const struct sieve_codegen_env *cgenv,	struct sieve_command *ctx);
+
+const struct sieve_command_def body_test = {
+	.identifier = "body",
+	.type = SCT_TEST,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = tst_body_registered,
+	.validate = tst_body_validate,
+	.generate = tst_body_generate
+};
+
+/*
+ * Body operation
+ */
+
+static bool ext_body_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int ext_body_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def body_operation = {
+	.mnemonic = "body",
+	.ext_def = &body_extension,
+	.dump = ext_body_operation_dump,
+	.execute = ext_body_operation_execute
+};
+
+/*
+ * Optional operands
+ */
+
+enum tst_body_optional {
+	OPT_BODY_TRANSFORM = SIEVE_MATCH_OPT_LAST
+};
+
+/*
+ * Tagged arguments
+ */
+
+/* Forward declarations */
+
+static bool tag_body_transform_validate
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+static bool tag_body_transform_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+		struct sieve_command *cmd);
+
+/* Argument objects */
+
+static const struct sieve_argument_def body_raw_tag = {
+	.identifier = "raw",
+	.validate = tag_body_transform_validate,
+	.generate = tag_body_transform_generate
+};
+
+static const struct sieve_argument_def body_content_tag = {
+	.identifier = "content",
+	.validate = tag_body_transform_validate,
+	.generate = tag_body_transform_generate
+};
+
+static const struct sieve_argument_def body_text_tag = {
+	.identifier = "text",
+	.validate = tag_body_transform_validate,
+	.generate = tag_body_transform_generate
+};
+
+/* Argument implementation */
+
+static bool tag_body_transform_validate
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	enum tst_body_transform transform;
+	struct sieve_ast_argument *tag = *arg;
+
+	/* BODY-TRANSFORM:
+	 *   :raw
+	 *     / :content <content-types: string-list>
+	 *     / :text
+	 */
+	if ( (bool) cmd->data ) {
+		sieve_argument_validate_error(valdtr, *arg,
+			"the :raw, :content and :text arguments for the body test are mutually "
+			"exclusive, but more than one was specified");
+		return FALSE;
+	}
+
+	/* Skip tag */
+	*arg = sieve_ast_argument_next(*arg);
+
+	/* :content tag has a string-list argument */
+	if ( sieve_argument_is(tag, body_raw_tag) )
+		transform = TST_BODY_TRANSFORM_RAW;
+
+	else if ( sieve_argument_is(tag, body_text_tag) )
+		transform = TST_BODY_TRANSFORM_TEXT;
+
+	else if ( sieve_argument_is(tag, body_content_tag) ) {
+		/* Check syntax:
+		 *   :content <content-types: string-list>
+		 */
+		if ( !sieve_validate_tag_parameter
+			(valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING_LIST, FALSE) ) {
+			return FALSE;
+		}
+
+		/* Assign tag parameters */
+		tag->parameters = *arg;
+		*arg = sieve_ast_arguments_detach(*arg,1);
+
+		transform = TST_BODY_TRANSFORM_CONTENT;
+	} else
+		return FALSE;
+
+	/* Signal the presence of this tag */
+	cmd->data = (void *) TRUE;
+
+	/* Assign context data */
+	tag->argument->data = (void *) transform;
+
+	return TRUE;
+}
+
+/*
+ * Command Registration
+ */
+
+static bool tst_body_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg)
+{
+	/* The order of these is not significant */
+	sieve_comparators_link_tag(valdtr, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR);
+	sieve_match_types_link_tags(valdtr, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
+
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &body_raw_tag, OPT_BODY_TRANSFORM);
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &body_content_tag, OPT_BODY_TRANSFORM);
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &body_text_tag, OPT_BODY_TRANSFORM);
+
+	return TRUE;
+}
+
+/*
+ * Validation
+ */
+
+static bool tst_body_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+	struct sieve_ast_argument *arg = tst->first_positional;
+	const struct sieve_match_type mcht_default =
+		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+	const struct sieve_comparator cmp_default =
+		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "key list", 1, SAAT_STRING_LIST) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+		return FALSE;
+
+	/* Validate the key argument to a specified match type */
+	return sieve_match_type_validate
+		(valdtr, tst, arg, &mcht_default, &cmp_default);
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_body_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	(void)sieve_operation_emit(cgenv->sblock, cmd->ext, &body_operation);
+
+	/* Generate arguments */
+	return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+static bool tag_body_transform_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+	struct sieve_command *cmd ATTR_UNUSED)
+{
+	enum tst_body_transform transform =
+		(enum tst_body_transform) arg->argument->data;
+
+	sieve_binary_emit_byte(cgenv->sblock, transform);
+	sieve_generate_argument_parameters(cgenv, cmd, arg);
+
+	return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool ext_body_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	unsigned int transform;
+	int opt_code = 0;
+
+	sieve_code_dumpf(denv, "BODY");
+	sieve_code_descend(denv);
+
+	/* Handle any optional arguments */
+	for (;;) {
+		int opt;
+
+		if ( (opt=sieve_match_opr_optional_dump(denv, address, &opt_code))
+			< 0 )
+			return FALSE;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_BODY_TRANSFORM:
+			if ( !sieve_binary_read_byte(denv->sblock, address, &transform) )
+				return FALSE;
+
+			switch ( transform ) {
+			case TST_BODY_TRANSFORM_RAW:
+				sieve_code_dumpf(denv, "BODY-TRANSFORM: RAW");
+				break;
+			case TST_BODY_TRANSFORM_TEXT:
+				sieve_code_dumpf(denv, "BODY-TRANSFORM: TEXT");
+				break;
+			case TST_BODY_TRANSFORM_CONTENT:
+				sieve_code_dumpf(denv, "BODY-TRANSFORM: CONTENT");
+
+				sieve_code_descend(denv);
+				if ( !sieve_opr_stringlist_dump(denv, address, "content types") )
+					return FALSE;
+				sieve_code_ascend(denv);
+				break;
+			default:
+				return FALSE;
+			}
+			break;
+		default:
+			return FALSE;
+		}
+	};
+
+	return sieve_opr_stringlist_dump(denv, address, "key list");
+}
+
+/*
+ * Interpretation
+ */
+
+static int ext_body_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	int opt_code = 0;
+	struct sieve_comparator cmp =
+		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+	struct sieve_match_type mcht =
+		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+	unsigned int transform = TST_BODY_TRANSFORM_TEXT;
+	struct sieve_stringlist *ctype_list, *value_list, *key_list;
+	bool mvalues_active;
+	const char * const *content_types = NULL;
+	int match, ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Optional operands */
+
+	ctype_list = NULL;
+	for (;;) {
+		int opt;
+
+		if ( (opt=sieve_match_opr_optional_read
+			(renv, address, &opt_code, &ret, &cmp, &mcht)) < 0 )
+			return ret;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_BODY_TRANSFORM:
+			if ( !sieve_binary_read_byte(renv->sblock, address, &transform) ||
+				transform > TST_BODY_TRANSFORM_TEXT ) {
+				sieve_runtime_trace_error(renv, "invalid body transform type");
+				return SIEVE_EXEC_BIN_CORRUPT;
+			}
+
+			if ( transform == TST_BODY_TRANSFORM_CONTENT &&
+				(ret=sieve_opr_stringlist_read
+					(renv, address, "content-type-list", &ctype_list)) <= 0 )
+				return ret;
+
+			break;
+
+		default:
+			sieve_runtime_trace_error(renv, "unknown optional operand");
+			return SIEVE_EXEC_BIN_CORRUPT;
+		}
+	}
+
+	/* Read key-list */
+
+	if ( (ret=sieve_opr_stringlist_read(renv, address, "key-list", &key_list))
+		<= 0 )
+		return ret;
+
+	if ( ctype_list != NULL && sieve_stringlist_read_all
+		(ctype_list, pool_datastack_create(), &content_types) < 0 ) {
+		sieve_runtime_trace_error(renv, "failed to read content-type-list operand");
+		return ctype_list->exec_status;
+	}
+
+	/*
+	 * Perform operation
+	 */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "body test");
+
+	/* Extract requested parts */
+	if ( (ret=ext_body_get_part_list(renv,
+		(enum tst_body_transform) transform, content_types,&value_list)) <= 0 )
+		return ret;
+
+	/* Disable match values processing as required by RFC */
+	mvalues_active = sieve_match_values_set_enabled(renv, FALSE);
+
+	/* Perform match */
+	match = sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret);
+
+	/* Restore match values processing */
+	(void)sieve_match_values_set_enabled(renv, mvalues_active);
+
+	if ( match < 0 )
+		return ret;
+
+	/* Set test result for subsequent conditional jump */
+	sieve_interpreter_set_test_result(renv->interp, match > 0);
+	return SIEVE_EXEC_OK;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/comparator-i-ascii-numeric/Makefile.am
@@ -0,0 +1,8 @@
+noinst_LTLIBRARIES = libsieve_ext_comparator-i-ascii-numeric.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../.. \
+	$(LIBDOVECOT_INCLUDE)
+
+libsieve_ext_comparator_i_ascii_numeric_la_SOURCES = \
+	ext-cmp-i-ascii-numeric.c
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/comparator-i-ascii-numeric/ext-cmp-i-ascii-numeric.c
@@ -0,0 +1,160 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension comparator-i;ascii-numeric
+ * ------------------------------------
+ *
+ * Author: Stephan Bosch
+ * Specification: RFC 2244
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-comparators.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+
+#include <ctype.h>
+
+/*
+ * Forward declarations
+ */
+
+static const struct sieve_operand_def my_comparator_operand;
+
+const struct sieve_comparator_def i_ascii_numeric_comparator;
+
+/*
+ * Extension
+ */
+
+static bool ext_cmp_i_ascii_numeric_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *validator);
+
+const struct sieve_extension_def comparator_i_ascii_numeric_extension = {
+	.name = "comparator-i;ascii-numeric",
+	.validator_load = ext_cmp_i_ascii_numeric_validator_load,
+	SIEVE_EXT_DEFINE_OPERAND(my_comparator_operand)
+};
+
+static bool ext_cmp_i_ascii_numeric_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *validator)
+{
+	sieve_comparator_register(validator, ext, &i_ascii_numeric_comparator);
+	return TRUE;
+}
+
+/*
+ * Operand
+ */
+
+static const struct sieve_extension_objects ext_comparators =
+	SIEVE_EXT_DEFINE_COMPARATOR(i_ascii_numeric_comparator);
+
+static const struct sieve_operand_def my_comparator_operand = {
+	.name = "comparator-i;ascii-numeric",
+	.ext_def = &comparator_i_ascii_numeric_extension,
+	.class = &sieve_comparator_operand_class,
+	.interface = &ext_comparators
+};
+
+/*
+ * Comparator
+ */
+
+/* Forward declarations */
+
+static int cmp_i_ascii_numeric_compare
+	(const struct sieve_comparator *cmp,
+		const char *val1, size_t val1_size, const char *val2, size_t val2_size);
+
+/* Comparator object */
+
+const struct sieve_comparator_def i_ascii_numeric_comparator = {
+	SIEVE_OBJECT("i;ascii-numeric",
+		&my_comparator_operand, 0),
+	.flags =
+		SIEVE_COMPARATOR_FLAG_ORDERING |
+		SIEVE_COMPARATOR_FLAG_EQUALITY,
+	.compare = cmp_i_ascii_numeric_compare
+};
+
+/* Comparator implementation */
+
+static int cmp_i_ascii_numeric_compare
+	(const struct sieve_comparator *cmp ATTR_UNUSED,
+		const char *val, size_t val_size, const char *key, size_t key_size)
+{
+	const char *vend = val + val_size;
+	const char *kend = key + key_size;
+	const char *vp = val;
+	const char *kp = key;
+	int digits, i;
+
+	/* RFC 4790: All input is valid; strings that do not start with a digit
+	 * represent positive infinity.
+	 */
+	if ( !i_isdigit(*vp) ) {
+		if ( i_isdigit(*kp) ) {
+			/* Value is greater */
+			return 1;
+		}
+	} else {
+		if ( !i_isdigit(*kp) ) {
+			/* Value is less */
+			return -1;
+		}
+	}
+
+	/* Ignore leading zeros */
+
+	while ( *vp == '0' && vp < vend )
+		vp++;
+
+	while ( *kp == '0' && kp < kend )
+		kp++;
+
+	/* Check whether both numbers are equally long in terms of digits */
+
+	digits = 0;
+	while ( vp < vend && kp < kend && i_isdigit(*vp) && i_isdigit(*kp) ) {
+		vp++;
+		kp++;
+		digits++;
+	}
+
+	if ( vp == vend || !i_isdigit(*vp) ) {
+		if ( kp != kend && i_isdigit(*kp) ) {
+			/* Value is less */
+			return -1;
+		}
+	} else {
+		/* Value is greater */
+		return 1;
+	}
+
+	/* Equally long: compare digits */
+
+	vp -= digits;
+	kp -= digits;
+	i = 0;
+	while ( i < digits ) {
+		if ( *vp > *kp )
+			return 1;
+		else if ( *vp < *kp )
+			return -1;
+
+		kp++;
+		vp++;
+		i++;
+	}
+
+	return 0;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/copy/Makefile.am
@@ -0,0 +1,18 @@
+noinst_LTLIBRARIES = libsieve_ext_copy.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../.. \
+	$(LIBDOVECOT_INCLUDE)
+
+libsieve_ext_copy_la_SOURCES = \
+	ext-copy.c
+
+public_headers = \
+	sieve-ext-copy.h
+
+headers =
+
+pkginc_libdir=$(dovecot_pkgincludedir)/sieve
+pkginc_lib_HEADERS = $(public_headers)
+noinst_HEADERS = $(headers)
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/copy/ext-copy.c
@@ -0,0 +1,180 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension copy
+ * --------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 3894
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-actions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+
+#include "sieve-ext-copy.h"
+
+/*
+ * Forward declarations
+ */
+
+static const struct sieve_argument_def copy_tag;
+static const struct sieve_operand_def copy_side_effect_operand;
+
+/*
+ * Extension
+ */
+
+static bool ext_copy_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def copy_extension = {
+	.name = "copy",
+	.validator_load = ext_copy_validator_load,
+	SIEVE_EXT_DEFINE_OPERAND(copy_side_effect_operand)
+};
+
+static bool ext_copy_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	/* Register copy tag with redirect and fileinto commands and we don't care
+	 * whether these commands are registered or even whether they will be
+	 * registered at all. The validator handles either situation gracefully
+	 */
+	sieve_validator_register_external_tag
+		(valdtr, "redirect", ext, &copy_tag, SIEVE_OPT_SIDE_EFFECT);
+	sieve_validator_register_external_tag
+		(valdtr, "fileinto", ext, &copy_tag, SIEVE_OPT_SIDE_EFFECT);
+
+	return TRUE;
+}
+
+/*
+ * Side effect
+ */
+
+static void seff_copy_print
+	(const struct sieve_side_effect *seffect, const struct sieve_action *action,
+		const struct sieve_result_print_env *rpenv, bool *keep);
+static void seff_copy_post_commit
+	(const struct sieve_side_effect *seffect, const struct sieve_action *action,
+		const struct sieve_action_exec_env *aenv, void *tr_context, bool *keep);
+
+const struct sieve_side_effect_def copy_side_effect = {
+	SIEVE_OBJECT("copy", &copy_side_effect_operand, 0),
+	.to_action = &act_store,
+	.print = seff_copy_print,
+	.post_commit = seff_copy_post_commit
+};
+
+/*
+ * Tagged argument
+ */
+
+static bool tag_copy_validate
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+static bool tag_copy_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+    struct sieve_command *cmd);
+
+static const struct sieve_argument_def copy_tag = {
+	.identifier = "copy",
+	.validate = tag_copy_validate,
+	.generate = tag_copy_generate
+};
+
+/*
+ * Operand
+ */
+
+static const struct sieve_extension_objects ext_side_effects =
+	SIEVE_EXT_DEFINE_SIDE_EFFECT(copy_side_effect);
+
+static const struct sieve_operand_def copy_side_effect_operand = {
+	.name = "copy operand",
+	.ext_def = &copy_extension,
+	.class = &sieve_side_effect_operand_class,
+	.interface = &ext_side_effects
+};
+
+/*
+ * Tag registration
+ */
+
+void sieve_ext_copy_register_tag
+(struct sieve_validator *valdtr, const struct sieve_extension *copy_ext,
+	const char *command)
+{
+	if ( sieve_validator_extension_loaded(valdtr, copy_ext) ) {
+		sieve_validator_register_external_tag
+			(valdtr, command, copy_ext, &copy_tag, SIEVE_OPT_SIDE_EFFECT);
+	}
+}
+
+/*
+ * Tag validation
+ */
+
+static bool tag_copy_validate
+	(struct sieve_validator *valdtr ATTR_UNUSED,
+	struct sieve_ast_argument **arg ATTR_UNUSED,
+	struct sieve_command *cmd ATTR_UNUSED)
+{
+	*arg = sieve_ast_argument_next(*arg);
+
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool tag_copy_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+	struct sieve_command *cmd ATTR_UNUSED)
+{
+	if ( sieve_ast_argument_type(arg) != SAAT_TAG ) {
+		return FALSE;
+	}
+
+	sieve_opr_side_effect_emit
+		(cgenv->sblock, sieve_argument_ext(arg), &copy_side_effect);
+
+	return TRUE;
+}
+
+/*
+ * Side effect implementation
+ */
+
+static void seff_copy_print
+(const struct sieve_side_effect *seffect ATTR_UNUSED,
+	const struct sieve_action *action ATTR_UNUSED,
+	const struct sieve_result_print_env *rpenv, bool *keep)
+{
+	sieve_result_seffect_printf(rpenv, "preserve implicit keep");
+
+	*keep = TRUE;
+}
+
+static void seff_copy_post_commit
+(const struct sieve_side_effect *seffect ATTR_UNUSED,
+	const struct sieve_action *action ATTR_UNUSED,
+	const struct sieve_action_exec_env *aenv ATTR_UNUSED,
+		void *tr_context ATTR_UNUSED, bool *keep)
+{
+	*keep = TRUE;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/copy/sieve-ext-copy.h
@@ -0,0 +1,21 @@
+#ifndef SIEVE_EXT_COPY_H
+#define SIEVE_EXT_COPY_H
+
+/* sieve_ext_copy_get_extension():
+ *   Get the extension struct for the copy extension.
+ */
+static inline const struct sieve_extension *sieve_ext_copy_get_extension
+(struct sieve_instance *svinst)
+{
+	return sieve_extension_get_by_name(svinst, "copy");
+}
+
+/* sieve_ext_copy_register_tag():
+ *   Register the :copy tagged argument for a command other than fileinto and
+ *   redirect.
+ */
+void sieve_ext_copy_register_tag
+	(struct sieve_validator *valdtr, const struct sieve_extension *copy_ext,
+		const char *command);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/date/Makefile.am
@@ -0,0 +1,16 @@
+noinst_LTLIBRARIES = libsieve_ext_date.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../.. \
+	$(LIBDOVECOT_INCLUDE)
+
+tests = \
+	tst-date.c
+
+libsieve_ext_date_la_SOURCES = \
+	$(tests) \
+	ext-date-common.c \
+	ext-date.c
+
+noinst_HEADERS = \
+	ext-date-common.h
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/date/ext-date-common.c
@@ -0,0 +1,593 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "utc-offset.h"
+#include "str.h"
+#include "iso8601-date.h"
+#include "message-date.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-interpreter.h"
+#include "sieve-message.h"
+
+#include "ext-date-common.h"
+
+#include <time.h>
+#include <ctype.h>
+
+struct ext_date_context {
+	time_t current_date;
+	int zone_offset;
+};
+
+/*
+ * Runtime initialization
+ */
+
+static int ext_date_runtime_init
+(const struct sieve_extension *ext,
+	const struct sieve_runtime_env *renv,
+	void *context ATTR_UNUSED, bool deferred ATTR_UNUSED)
+{
+	struct ext_date_context *dctx;
+	pool_t pool;
+	struct timeval msg_time;
+	time_t current_date;
+	struct tm *tm;
+	int zone_offset;
+
+	/* Get current time at instance main script is started */
+	sieve_message_context_time(renv->msgctx, &msg_time);
+	current_date = msg_time.tv_sec;
+
+	tm = localtime(&current_date);
+	zone_offset = utc_offset(tm, current_date);
+
+	/* Create context */
+	pool = sieve_message_context_pool(renv->msgctx);
+	dctx = p_new(pool, struct ext_date_context, 1);
+	dctx->current_date = current_date;
+	dctx->zone_offset = zone_offset;
+
+	sieve_message_context_extension_set
+		(renv->msgctx, ext, (void *) dctx);
+	return SIEVE_EXEC_OK;
+}
+
+static struct sieve_interpreter_extension
+date_interpreter_extension = {
+	.ext_def = &date_extension,
+	.run = ext_date_runtime_init
+};
+
+bool ext_date_interpreter_load
+(const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+	sieve_size_t *address ATTR_UNUSED)
+{
+	/* Register runtime hook to obtain stript start timestamp */
+	if ( renv->msgctx == NULL ||
+		sieve_message_context_extension_get(renv->msgctx, ext) == NULL ) {
+		sieve_interpreter_extension_register
+			(renv->interp, ext, &date_interpreter_extension, NULL);
+	}
+
+	return TRUE;
+}
+
+/*
+ * Zone string
+ */
+
+bool ext_date_parse_timezone
+(const char *zone, int *zone_offset_r)
+{
+	const unsigned char *str = (const unsigned char *) zone;
+	size_t len = strlen(zone);
+
+	if (len == 5 && (*str == '+' || *str == '-')) {
+		int offset;
+
+		if (!i_isdigit(str[1]) || !i_isdigit(str[2]) ||
+		    !i_isdigit(str[3]) || !i_isdigit(str[4]))
+			return FALSE;
+
+		offset = ((str[1]-'0') * 10 + (str[2]-'0')) * 60  +
+			(str[3]-'0') * 10 + (str[4]-'0');
+
+		if ( zone_offset_r != NULL )
+			*zone_offset_r = *str == '+' ? offset : -offset;
+
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+/*
+ * Current date
+ */
+
+time_t ext_date_get_current_date
+(const struct sieve_runtime_env *renv, int *zone_offset_r)
+{
+	const struct sieve_extension *this_ext = renv->oprtn->ext;
+	struct ext_date_context *dctx = (struct ext_date_context *)
+		sieve_message_context_extension_get(renv->msgctx, this_ext);
+
+	if ( dctx == NULL ) {
+		ext_date_runtime_init(this_ext, renv, NULL, FALSE);
+		dctx = (struct ext_date_context *)
+			sieve_message_context_extension_get(renv->msgctx, this_ext);
+
+		i_assert(dctx != NULL);
+	}
+
+	/* Read script start timestamp from message context */
+
+	if ( zone_offset_r != NULL )
+		*zone_offset_r = dctx->zone_offset;
+
+	return dctx->current_date;
+}
+
+/*
+ * Date parts
+ */
+
+/* "year"      => the year, "0000" .. "9999".
+ */
+
+static const char *ext_date_year_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part year_date_part = {
+	"year",
+	ext_date_year_part_get
+};
+
+/* "month"     => the month, "01" .. "12".
+ */
+
+static const char *ext_date_month_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part month_date_part = {
+	"month",
+	ext_date_month_part_get
+};
+
+/* "day"       => the day, "01" .. "31".
+ */
+
+static const char *ext_date_day_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part day_date_part = {
+	"day",
+	ext_date_day_part_get
+};
+
+/* "date"      => the date in "yyyy-mm-dd" format.
+ */
+
+static const char *ext_date_date_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part date_date_part = {
+	"date",
+	ext_date_date_part_get
+};
+
+/* "julian"    => the Modified Julian Day, that is, the date
+ *              expressed as an integer number of days since
+ *              00:00 UTC on November 17, 1858 (using the Gregorian
+ *              calendar).  This corresponds to the regular
+ *              Julian Day minus 2400000.5.  Sample routines to
+ *              convert to and from modified Julian dates are
+ *              given in Appendix A.
+ */
+
+static const char *ext_date_julian_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part julian_date_part = {
+	"julian",
+	ext_date_julian_part_get
+};
+
+/* "hour"      => the hour, "00" .. "23".
+ */
+static const char *ext_date_hour_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part hour_date_part = {
+	"hour",
+	ext_date_hour_part_get
+};
+
+/* "minute"    => the minute, "00" .. "59".
+ */
+static const char *ext_date_minute_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part minute_date_part = {
+	"minute",
+	ext_date_minute_part_get
+};
+
+/* "second"    => the second, "00" .. "60".
+ */
+static const char *ext_date_second_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part second_date_part = {
+	"second",
+	ext_date_second_part_get
+};
+
+/* "time"      => the time in "hh:mm:ss" format.
+ */
+static const char *ext_date_time_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part time_date_part = {
+	"time",
+	ext_date_time_part_get
+};
+
+/* "iso8601"   => the date and time in restricted ISO 8601 format.
+ */
+static const char *ext_date_iso8601_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part iso8601_date_part = {
+	"iso8601",
+	ext_date_iso8601_part_get
+};
+
+/* "std11"     => the date and time in a format appropriate
+ *                for use in a Date: header field [RFC2822].
+ */
+static const char *ext_date_std11_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part std11_date_part = {
+	"std11",
+	ext_date_std11_part_get
+};
+
+/* "zone"      => the time zone in use.  If the user specified a
+ *                time zone with ":zone", "zone" will
+ *                contain that value.  If :originalzone is specified
+ *                this value will be the original zone specified
+ *                in the date-time value.  If neither argument is
+ *                specified the value will be the server's default
+ *                time zone in offset format "+hhmm" or "-hhmm".  An
+ *                 offset of 0 (Zulu) always has a positive sign.
+ */
+static const char *ext_date_zone_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part zone_date_part = {
+	"zone",
+	ext_date_zone_part_get
+};
+
+/* "weekday"   => the day of the week expressed as an integer between
+ *                "0" and "6". "0" is Sunday, "1" is Monday, etc.
+ */
+static const char *ext_date_weekday_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part weekday_date_part = {
+	"weekday",
+	ext_date_weekday_part_get
+};
+
+/*
+ * Date part extraction
+ */
+
+static const struct ext_date_part *date_parts[] = {
+	&year_date_part, &month_date_part, &day_date_part, &date_date_part,
+	&julian_date_part, &hour_date_part, &minute_date_part, &second_date_part,
+	&time_date_part, &iso8601_date_part, &std11_date_part, &zone_date_part,
+	&weekday_date_part
+};
+
+unsigned int date_parts_count = N_ELEMENTS(date_parts);
+
+const struct ext_date_part *ext_date_part_find(const char *part)
+{
+	unsigned int i;
+
+	for ( i = 0; i < date_parts_count; i++ ) {
+		if ( strcasecmp(date_parts[i]->identifier, part) == 0 ) {
+			return date_parts[i];
+		}
+	}
+
+	return NULL;
+}
+
+const char *ext_date_part_extract
+(const struct ext_date_part *dpart, struct tm *tm, int zone_offset)
+{
+	if ( dpart == NULL || dpart->get_string == NULL )
+		return NULL;
+
+	return dpart->get_string(tm, zone_offset);
+}
+
+/*
+ * Date part implementations
+ */
+
+static const char *month_names[] = {
+	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+static const char *weekday_names[] = {
+	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+
+static const char *ext_date_year_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+	return t_strdup_printf("%04d", tm->tm_year + 1900);
+}
+
+static const char *ext_date_month_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+	return t_strdup_printf("%02d", tm->tm_mon + 1);
+}
+
+static const char *ext_date_day_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+	return t_strdup_printf("%02d", tm->tm_mday);
+}
+
+static const char *ext_date_date_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+	return t_strdup_printf("%04d-%02d-%02d",
+		tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
+}
+
+static const char *ext_date_julian_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+	int year = tm->tm_year+1900;
+	int month = tm->tm_mon+1;
+	int day = tm->tm_mday;
+	int c, ya, jd;
+
+	/* Modified from RFC 5260 Appendix A (refer to Errata) */
+
+	if ( month > 2 )
+		month -= 3;
+	else {
+		month += 9;
+		year--;
+	}
+
+	c = year / 100;
+	ya = year - c * 100;
+
+	jd = c * 146097 / 4 + ya * 1461 / 4 + (month * 153 + 2) / 5 + day + 1721119;
+
+	return t_strdup_printf("%d", jd - 2400001);
+}
+
+static const char *ext_date_hour_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+	return t_strdup_printf("%02d", tm->tm_hour);
+}
+
+static const char *ext_date_minute_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+	return t_strdup_printf("%02d", tm->tm_min);
+}
+
+static const char *ext_date_second_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+	return t_strdup_printf("%02d", tm->tm_sec);
+}
+
+static const char *ext_date_time_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+	return t_strdup_printf("%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);
+}
+
+static const char *ext_date_iso8601_part_get
+(struct tm *tm, int zone_offset)
+{
+	/* From RFC: `The restricted ISO 8601 format is specified by the date-time
+	 * ABNF production given in [RFC3339], Section 5.6, with the added
+   * restrictions that the letters "T" and "Z" MUST be in upper case, and
+	 * a time zone offset of zero MUST be represented by "Z" and not "+00:00".
+	 */
+	if ( zone_offset == 0 )
+		zone_offset = INT_MAX;
+
+	return iso8601_date_create_tm(tm, zone_offset);
+}
+
+
+static const char *ext_date_std11_part_get
+(struct tm *tm, int zone_offset)
+{
+	return t_strdup_printf("%s, %02d %s %04d %02d:%02d:%02d %s",
+		weekday_names[tm->tm_wday],
+		tm->tm_mday,
+		month_names[tm->tm_mon],
+		tm->tm_year+1900,
+		tm->tm_hour, tm->tm_min, tm->tm_sec,
+		ext_date_zone_part_get(tm, zone_offset));
+}
+
+static const char *ext_date_zone_part_get
+(struct tm *tm ATTR_UNUSED, int zone_offset)
+{
+	bool negative;
+	int offset = zone_offset;
+
+	if (zone_offset >= 0)
+		negative = FALSE;
+	else {
+		negative = TRUE;
+		offset = -offset;
+	}
+
+	return t_strdup_printf
+		("%c%02d%02d", negative ? '-' : '+', offset / 60, offset % 60);
+}
+
+static const char *ext_date_weekday_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+	return t_strdup_printf("%d", tm->tm_wday);
+}
+
+/*
+ * Date stringlist
+ */
+
+/* Forward declarations */
+
+static int ext_date_stringlist_next_item
+	(struct sieve_stringlist *_strlist, string_t **str_r);
+static void ext_date_stringlist_reset
+	(struct sieve_stringlist *_strlist);
+
+/* Stringlist object */
+
+struct ext_date_stringlist {
+	struct sieve_stringlist strlist;
+
+	struct sieve_stringlist *field_values;
+	int time_zone;
+	const struct ext_date_part *date_part;
+
+	time_t local_time;
+	int local_zone;
+
+	bool read:1;
+};
+
+struct sieve_stringlist *ext_date_stringlist_create
+(const struct sieve_runtime_env *renv, struct sieve_stringlist *field_values,
+	int time_zone, const struct ext_date_part *dpart)
+{
+	struct ext_date_stringlist *strlist;
+
+	strlist = t_new(struct ext_date_stringlist, 1);
+	strlist->strlist.runenv = renv;
+	strlist->strlist.exec_status = SIEVE_EXEC_OK;
+	strlist->strlist.next_item = ext_date_stringlist_next_item;
+	strlist->strlist.reset = ext_date_stringlist_reset;
+	strlist->field_values = field_values;
+	strlist->time_zone = time_zone;
+	strlist->date_part = dpart;
+
+	strlist->local_time = ext_date_get_current_date(renv, &strlist->local_zone);
+
+	return &strlist->strlist;
+}
+
+/* Stringlist implementation */
+
+static int ext_date_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+	struct ext_date_stringlist *strlist =
+		(struct ext_date_stringlist *) _strlist;
+	bool got_date = FALSE;
+	time_t date_value;
+	const char *part_value = NULL;
+	int original_zone;
+
+	/* Check whether the item was already read */
+	if ( strlist->read ) return 0;
+
+	if ( strlist->field_values != NULL ) {
+		string_t *hdr_item;
+		const char *header_value, *date_string;
+		int ret;
+
+		/* Use header field value */
+
+		/* Read first */
+		if ( (ret=sieve_stringlist_next_item(strlist->field_values, &hdr_item))
+			<= 0 )
+			return ret;
+
+		/* Extract the date string value */
+
+		header_value = str_c(hdr_item);
+		date_string = strrchr(header_value, ';');
+
+		if ( date_string == NULL ) {
+			/* Direct header value */
+			date_string = header_value;
+		} else {
+			/* Delimited by ';', e.g. a Received: header */
+			date_string++;
+		}
+
+		/* Parse the date value */
+		if ( message_date_parse((const unsigned char *) date_string,
+			strlen(date_string), &date_value, &original_zone) ) {
+			got_date = TRUE;
+		}
+	} else {
+		/* Use time stamp recorded at the time the script first started */
+		date_value = strlist->local_time;
+		original_zone = strlist->local_zone;
+		got_date = TRUE;
+	}
+
+	if ( got_date ) {
+		int wanted_zone;
+		struct tm *date_tm;
+
+		/* Apply wanted timezone */
+
+		switch ( strlist->time_zone ) {
+		case EXT_DATE_TIMEZONE_LOCAL:
+			wanted_zone = strlist->local_zone;
+			break;
+		case EXT_DATE_TIMEZONE_ORIGINAL:
+			wanted_zone = original_zone;
+			break;
+		default:
+			wanted_zone = strlist->time_zone;
+		}
+
+		date_value += wanted_zone * 60;
+
+		/* Convert timestamp to struct tm */
+
+		if ( (date_tm=gmtime(&date_value)) != NULL ) {
+			/* Extract the date part */
+			part_value = ext_date_part_extract
+				(strlist->date_part, date_tm, wanted_zone);
+		}
+	}
+
+	strlist->read = TRUE;
+
+	if ( part_value == NULL )
+		return 0;
+
+	*str_r = t_str_new_const(part_value, strlen(part_value));
+	return 1;
+}
+
+static void ext_date_stringlist_reset
+(struct sieve_stringlist *_strlist)
+{
+	struct ext_date_stringlist *strlist =
+		(struct ext_date_stringlist *) _strlist;
+
+	if ( strlist->field_values != NULL )
+		sieve_stringlist_reset(strlist->field_values);
+	strlist->read = FALSE;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/date/ext-date-common.h
@@ -0,0 +1,80 @@
+#ifndef EXT_DATE_COMMON_H
+#define EXT_DATE_COMMON_H
+
+#include "sieve-common.h"
+
+#include <time.h>
+
+/*
+ * Extension
+ */
+
+extern const struct sieve_extension_def date_extension;
+
+bool ext_date_interpreter_load
+	(const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+		sieve_size_t *address ATTR_UNUSED);
+
+/*
+ * Tests
+ */
+
+extern const struct sieve_command_def date_test;
+extern const struct sieve_command_def currentdate_test;
+
+/*
+ * Operations
+ */
+
+enum ext_date_opcode {
+	EXT_DATE_OPERATION_DATE,
+	EXT_DATE_OPERATION_CURRENTDATE
+};
+
+extern const struct sieve_operation_def date_operation;
+extern const struct sieve_operation_def currentdate_operation;
+
+/*
+ * Zone string
+ */
+
+bool ext_date_parse_timezone(const char *zone, int *zone_offset_r);
+
+/*
+ * Current date
+ */
+
+time_t ext_date_get_current_date
+	(const struct sieve_runtime_env *renv, int *zone_offset_r);
+
+/*
+ * Date part
+ */
+
+struct ext_date_part {
+	const char *identifier;
+
+	const char *(*get_string)(struct tm *tm, int zone_offset);
+};
+
+const struct ext_date_part *ext_date_part_find(const char *part);
+
+const char *ext_date_part_extract
+	(const struct ext_date_part *dpart, struct tm *tm, int zone_offset);
+
+/*
+ * Date stringlist
+ */
+
+enum ext_date_timezone_special {
+	EXT_DATE_TIMEZONE_LOCAL    = 100,
+	EXT_DATE_TIMEZONE_ORIGINAL = 101
+};
+
+struct sieve_stringlist *ext_date_stringlist_create
+(const struct sieve_runtime_env *renv, struct sieve_stringlist *field_values,
+	int time_zone, const struct ext_date_part *dpart);
+
+
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/date/ext-date.c
@@ -0,0 +1,62 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension date
+ * ------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5260
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+#include "array.h"
+
+#include "sieve-common.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-address-parts.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-date-common.h"
+
+/*
+ * Extension
+ */
+
+static bool ext_date_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *validator);
+
+const struct sieve_operation_def *ext_date_operations[] = {
+	&date_operation,
+	&currentdate_operation
+};
+
+const struct sieve_extension_def date_extension = {
+	.name = "date",
+	.validator_load = ext_date_validator_load,
+	.interpreter_load = ext_date_interpreter_load,
+	SIEVE_EXT_DEFINE_OPERATIONS(ext_date_operations)
+};
+
+static bool ext_date_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	/* Register new test */
+	sieve_validator_register_command(valdtr, ext, &date_test);
+	sieve_validator_register_command(valdtr, ext, &currentdate_test);
+
+	return TRUE;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/date/tst-date.c
@@ -0,0 +1,496 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-address-parts.h"
+#include "sieve-message.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+#include "ext-date-common.h"
+
+#include <time.h>
+
+/*
+ * Tests
+ */
+
+static bool tst_date_validate
+	(struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_date_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+/* Date test
+ *
+ * Syntax:
+ *    date [<":zone" <time-zone: string>> / ":originalzone"]
+ *         [COMPARATOR] [MATCH-TYPE] <header-name: string>
+ *         <date-part: string> <key-list: string-list>
+ */
+
+static bool tst_date_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+
+const struct sieve_command_def date_test = {
+	.identifier = "date",
+	.type = SCT_TEST,
+	.positional_args = 3,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = tst_date_registered,
+	.validate = tst_date_validate,
+	.generate = tst_date_generate
+};
+
+/* Currentdate test
+ *
+ * Syntax:
+ *    currentdate [":zone" <time-zone: string>]
+ *                [COMPARATOR] [MATCH-TYPE]
+ *                <date-part: string> <key-list: string-list>
+ */
+
+static bool tst_currentdate_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+
+const struct sieve_command_def currentdate_test = {
+	.identifier = "currentdate",
+	.type = SCT_TEST,
+	.positional_args = 2,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = tst_currentdate_registered,
+	.validate = tst_date_validate,
+	.generate = tst_date_generate
+};
+
+/*
+ * Tagged arguments
+ */
+
+/* Forward declarations */
+
+static bool tag_zone_validate
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+static bool tag_zone_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+		struct sieve_command *cmd);
+
+/* Argument objects */
+
+static const struct sieve_argument_def date_zone_tag = {
+ 	.identifier = "zone",
+	.validate = tag_zone_validate,
+	.generate = tag_zone_generate
+};
+
+static const struct sieve_argument_def date_originalzone_tag = {
+	.identifier = "originalzone",
+	.validate = tag_zone_validate,
+	.generate = tag_zone_generate
+};
+
+/*
+ * Date operation
+ */
+
+static bool tst_date_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_date_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def date_operation = {
+	.mnemonic = "DATE",
+	.ext_def = &date_extension,
+	.code = EXT_DATE_OPERATION_DATE,
+	.dump = tst_date_operation_dump,
+	.execute = tst_date_operation_execute
+};
+
+const struct sieve_operation_def currentdate_operation = {
+	.mnemonic = "CURRENTDATE",
+	.ext_def = &date_extension,
+	.code = EXT_DATE_OPERATION_CURRENTDATE,
+	.dump = tst_date_operation_dump,
+	.execute = tst_date_operation_execute
+};
+
+/*
+ * Optional operands
+ */
+
+enum tst_date_optional {
+	OPT_DATE_ZONE = SIEVE_AM_OPT_LAST,
+	OPT_DATE_LAST
+};
+
+/*
+ * Tag implementation
+ */
+
+static bool tag_zone_validate
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+    struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *tag = *arg;
+
+	if ( (bool) cmd->data ) {
+		if ( sieve_command_is(cmd, date_test) ) {
+			sieve_argument_validate_error(valdtr, *arg,
+				"multiple :zone or :originalzone arguments specified for "
+				"the currentdate test");
+		} else {
+			sieve_argument_validate_error(valdtr, *arg,
+				"multiple :zone arguments specified for the currentdate test");
+		}
+		return FALSE;
+	}
+
+	/* Skip tag */
+ 	*arg = sieve_ast_argument_next(*arg);
+
+	/* :content tag has a string-list argument */
+	if ( sieve_argument_is(tag, date_zone_tag) ) {
+
+		/* Check syntax:
+		 *   :zone <time-zone: string>
+		 */
+		if ( !sieve_validate_tag_parameter
+			(valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING, FALSE) ) {
+			return FALSE;
+		}
+
+		/* Check it */
+		if ( sieve_argument_is_string_literal(*arg) ) {
+			const char *zone = sieve_ast_argument_strc(*arg);
+
+			if ( !ext_date_parse_timezone(zone, NULL) ) {
+				sieve_argument_validate_warning(valdtr, *arg,
+					"specified :zone argument '%s' is not a valid timezone",
+					str_sanitize(zone, 40));
+			}
+		}
+
+		/* Assign tag parameters */
+		tag->parameters = *arg;
+		*arg = sieve_ast_arguments_detach(*arg,1);
+	}
+
+	cmd->data = (void *) TRUE;
+
+	return TRUE;
+}
+
+/*
+ * Test registration
+ */
+
+static bool tst_date_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg)
+{
+	sieve_comparators_link_tag(valdtr, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR);
+	sieve_match_types_link_tags(valdtr, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
+
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &date_zone_tag, OPT_DATE_ZONE);
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &date_originalzone_tag, OPT_DATE_ZONE);
+
+	return TRUE;
+}
+
+static bool tst_currentdate_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg)
+{
+	sieve_comparators_link_tag(valdtr, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR);
+	sieve_match_types_link_tags(valdtr, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
+
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &date_zone_tag, OPT_DATE_ZONE);
+
+	return TRUE;
+}
+
+/*
+ * Validation
+ */
+
+static bool tst_date_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+	struct sieve_ast_argument *arg = tst->first_positional;
+	unsigned int arg_offset = 0 ;
+	const struct sieve_match_type mcht_default =
+		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+	const struct sieve_comparator cmp_default =
+		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+
+	/* Check header name */
+
+	if ( sieve_command_is(tst, date_test) ) {
+		arg_offset = 1;
+
+		if ( !sieve_validate_positional_argument
+			(valdtr, tst, arg, "header name", 1, SAAT_STRING) ) {
+			return FALSE;
+		}
+
+		if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+			return FALSE;
+
+		if ( !sieve_command_verify_headers_argument(valdtr, arg) )
+			return FALSE;
+
+		arg = sieve_ast_argument_next(arg);
+	}
+
+	/* Check date part */
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "date part", arg_offset + 1, SAAT_STRING) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+		return FALSE;
+
+	if ( sieve_argument_is_string_literal(arg) ) {
+		const char * part = sieve_ast_argument_strc(arg);
+
+		if ( ext_date_part_find(part) == NULL ) {
+			sieve_argument_validate_warning
+				(valdtr, arg, "specified date part `%s' is not known",
+					str_sanitize(part, 80));
+		}
+	}
+
+	arg = sieve_ast_argument_next(arg);
+
+	/* Check key list */
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "key list", arg_offset + 2, SAAT_STRING_LIST) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+		return FALSE;
+
+	/* Validate the key argument to a specified match type */
+	return sieve_match_type_validate
+		(valdtr, tst, arg, &mcht_default, &cmp_default);
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_date_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
+{
+	if ( sieve_command_is(tst, date_test) )
+		sieve_operation_emit(cgenv->sblock, tst->ext, &date_operation);
+	else if ( sieve_command_is(tst, currentdate_test) )
+		sieve_operation_emit(cgenv->sblock, tst->ext, &currentdate_operation);
+	else
+		i_unreached();
+
+	/* Generate arguments */
+	return sieve_generate_arguments(cgenv, tst, NULL);
+}
+
+static bool tag_zone_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+    struct sieve_command *cmd)
+{
+	if ( arg->parameters == NULL ) {
+		sieve_opr_omitted_emit(cgenv->sblock);
+		return TRUE;
+	}
+
+	return sieve_generate_argument_parameters(cgenv, cmd, arg);
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_date_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	int opt_code = 0;
+	const struct sieve_operation *op = denv->oprtn;
+
+	sieve_code_dumpf(denv, "%s", sieve_operation_mnemonic(op));
+	sieve_code_descend(denv);
+
+	/* Handle any optional arguments */
+	for (;;) {
+		int opt;
+
+		if ( (opt=sieve_message_opr_optional_dump(denv, address, &opt_code)) < 0 )
+			return FALSE;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_DATE_ZONE:
+			if ( !sieve_opr_string_dump_ex(denv, address, "zone", "ORIGINAL") )
+				return FALSE;
+			break;
+		default:
+			return FALSE;
+		}
+	}
+
+	if ( sieve_operation_is(op, date_operation) &&
+		!sieve_opr_string_dump(denv, address, "header name") )
+		return FALSE;
+
+	return
+		sieve_opr_string_dump(denv, address, "date part") &&
+		sieve_opr_stringlist_dump(denv, address, "key list");
+}
+
+
+/*
+ * Code execution
+ */
+
+static int tst_date_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	const struct sieve_operation *op = renv->oprtn;
+	int opt_code = 0;
+	struct sieve_match_type mcht =
+		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+	struct sieve_comparator cmp =
+		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+	ARRAY_TYPE(sieve_message_override) svmos;
+	string_t *date_part = NULL, *zone = NULL;
+	struct sieve_stringlist *hdr_list = NULL, *hdr_value_list;
+	struct sieve_stringlist *value_list, *key_list;
+	bool zone_specified = FALSE, zone_literal = TRUE;
+	const struct ext_date_part *dpart;
+	int time_zone;
+	int match, ret;
+
+	/* Read optional operands */
+	for (;;) {
+		int opt;
+
+		/* Optional operands */
+		i_zero(&svmos);
+		if ( (opt=sieve_message_opr_optional_read
+			(renv, address, &opt_code, &ret, NULL, &mcht, &cmp, &svmos)) < 0 )
+			return ret;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_DATE_ZONE:
+			if ( (ret=sieve_opr_string_read_ex
+				(renv, address, "zone", TRUE, &zone, &zone_literal)) <= 0 )
+				return ret;
+
+			zone_specified = TRUE;
+			break;
+		default:
+			sieve_runtime_trace_error(renv, "unknown optional operand");
+			return SIEVE_EXEC_BIN_CORRUPT;
+		}
+	}
+
+	if ( sieve_operation_is(op, date_operation) ) {
+		/* Read header name as stringlist */
+		if ( (ret=sieve_opr_stringlist_read
+			(renv, address, "header-name", &hdr_list)) <= 0 )
+			return ret;
+	}
+
+	/* Read date part */
+	if ( (ret=sieve_opr_string_read(renv, address, "date-part", &date_part))
+		<= 0 )
+		return ret;
+
+	/* Read key-list */
+	if ( (ret=sieve_opr_stringlist_read(renv, address, "key-list", &key_list))
+		<= 0 )
+		return ret;
+
+	/* Determine what time zone to use in the result */
+	if ( !zone_specified ) {
+		time_zone = EXT_DATE_TIMEZONE_LOCAL;
+	} else if ( zone == NULL ) {
+		time_zone = EXT_DATE_TIMEZONE_ORIGINAL;
+	} else if ( !ext_date_parse_timezone(str_c(zone), &time_zone) ) {
+		if ( !zone_literal )
+			sieve_runtime_warning(renv, NULL,
+				"specified :zone argument `%s' is not a valid timezone "
+				"(using local zone)", str_sanitize(str_c(zone), 40));
+		time_zone = EXT_DATE_TIMEZONE_LOCAL;
+	}
+
+	if ( (dpart=ext_date_part_find(str_c(date_part))) == NULL ) {
+		sieve_runtime_warning(renv, NULL,
+			"specified date part argument `%s' is not known",
+			str_sanitize(str_c(date_part), 40));
+		sieve_interpreter_set_test_result(renv->interp, FALSE);
+		return SIEVE_EXEC_OK;
+	}
+
+	/*
+	 * Perform test
+	 */
+
+	if ( sieve_operation_is(op, date_operation) ) {
+
+		sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "date test");
+
+		/* Get header */
+		sieve_runtime_trace_descend(renv);
+		if ( (ret=sieve_message_get_header_fields
+			(renv, hdr_list, &svmos, FALSE, &hdr_value_list)) <= 0 )
+			return ret;
+		sieve_runtime_trace_ascend(renv);
+
+		/* Create value stringlist */
+		value_list = ext_date_stringlist_create
+			(renv, hdr_value_list, time_zone, dpart);
+
+	} else if ( sieve_operation_is(op, currentdate_operation) ) {
+		/* Use time stamp recorded at the time the script first started */
+
+		sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "currentdatedate test");
+
+		/* Create value stringlist */
+		value_list = ext_date_stringlist_create(renv, NULL, time_zone, dpart);
+	} else {
+		i_unreached();
+	}
+
+	/* Perform match */
+	if ( (match=sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret)) < 0 )
+		return ret;
+
+	/* Set test result for subsequent conditional jump */
+	sieve_interpreter_set_test_result(renv->interp, match > 0);
+	return SIEVE_EXEC_OK;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/duplicate/Makefile.am
@@ -0,0 +1,20 @@
+noinst_LTLIBRARIES = libsieve_ext_duplicate.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../.. \
+	$(LIBDOVECOT_INCLUDE)
+
+tests = \
+	tst-duplicate.c
+
+extensions = \
+	ext-duplicate.c
+
+libsieve_ext_duplicate_la_SOURCES = \
+	$(tests) \
+	$(extensions) \
+	ext-duplicate-common.c
+
+noinst_HEADERS = \
+	ext-duplicate-common.h
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate-common.c
@@ -0,0 +1,262 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "md5.h"
+#include "ioloop.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-settings.h"
+#include "sieve-error.h"
+#include "sieve-extensions.h"
+#include "sieve-message.h"
+#include "sieve-code.h"
+#include "sieve-runtime.h"
+#include "sieve-interpreter.h"
+#include "sieve-actions.h"
+#include "sieve-result.h"
+
+#include "ext-duplicate-common.h"
+
+/*
+ * Extension configuration
+ */
+
+#define EXT_DUPLICATE_DEFAULT_PERIOD (12*60*60)
+#define EXT_DUPLICATE_DEFAULT_MAX_PERIOD (2*24*60*60)
+
+bool ext_duplicate_load
+(const struct sieve_extension *ext, void **context)
+{
+	struct sieve_instance *svinst = ext->svinst;
+	struct ext_duplicate_config *config;
+	sieve_number_t default_period, max_period;
+
+	if ( *context != NULL )
+		ext_duplicate_unload(ext);
+
+	if ( !sieve_setting_get_duration_value
+		(svinst, "sieve_duplicate_default_period", &default_period) ) {
+		default_period = EXT_DUPLICATE_DEFAULT_PERIOD;
+	}
+
+	if ( !sieve_setting_get_duration_value
+		(svinst, "sieve_duplicate_max_period", &max_period) ) {
+		max_period = EXT_DUPLICATE_DEFAULT_MAX_PERIOD;
+	}
+
+	config = i_new(struct ext_duplicate_config, 1);
+	config->default_period = default_period;
+	config->max_period = max_period;
+
+	*context = (void *) config;
+	return TRUE;
+}
+
+void ext_duplicate_unload
+(const struct sieve_extension *ext)
+{
+	struct ext_duplicate_config *config =
+		(struct ext_duplicate_config *) ext->context;
+
+	i_free(config);
+}
+
+/*
+ * Duplicate_mark action
+ */
+
+struct act_duplicate_mark_data {
+	const char *handle;
+	unsigned int period;
+	unsigned char hash[MD5_RESULTLEN];
+	bool last:1;
+};
+
+static void act_duplicate_mark_print
+	(const struct sieve_action *action,
+		const struct sieve_result_print_env *rpenv, bool *keep);
+static void act_duplicate_mark_finish
+	(const struct sieve_action *action,
+		const struct sieve_action_exec_env *aenv, void *tr_context, int status);
+
+static const struct sieve_action_def act_duplicate_mark = {
+	.name = "duplicate_mark",
+	.print = act_duplicate_mark_print,
+	.finish = act_duplicate_mark_finish
+};
+
+static void act_duplicate_mark_print
+(const struct sieve_action *action,
+	const struct sieve_result_print_env *rpenv, bool *keep ATTR_UNUSED)
+{
+	struct act_duplicate_mark_data *data =
+		(struct act_duplicate_mark_data *) action->context;
+	const char *last = (data->last ? " last" : "");
+
+	if (data->handle != NULL) {
+		sieve_result_action_printf(rpenv, "track%s duplicate with handle: %s",
+			last, str_sanitize(data->handle, 128));
+	} else {
+		sieve_result_action_printf(rpenv, "track%s duplicate", last);
+	}
+}
+
+static void act_duplicate_mark_finish
+(const struct sieve_action *action,
+	const struct sieve_action_exec_env *aenv,
+	void *tr_context ATTR_UNUSED, int status)
+{
+	const struct sieve_script_env *senv = aenv->scriptenv;
+	struct act_duplicate_mark_data *data =
+		(struct act_duplicate_mark_data *) action->context;
+
+	if ( status == SIEVE_EXEC_OK ) {
+		/* Message was handled successfully, so track duplicate for this
+		 * message.
+		 */
+		sieve_action_duplicate_mark
+			(senv, data->hash, sizeof(data->hash), ioloop_time + data->period);
+	}
+}
+
+/*
+ * Duplicate checking
+ */
+
+struct ext_duplicate_handle {
+	const char *handle;
+	bool last:1;
+	bool duplicate:1;
+};
+
+struct ext_duplicate_context {
+	ARRAY(struct ext_duplicate_handle) handles;
+
+	bool nohandle_duplicate:1;
+	bool nohandle_checked:1;
+};
+
+static void ext_duplicate_hash
+(string_t *handle, const char *value, size_t value_len, bool last,
+	unsigned char hash_r[])
+{
+	static const char *id = "sieve duplicate";
+	struct md5_context md5ctx;
+
+	md5_init(&md5ctx);
+	md5_update(&md5ctx, id, strlen(id));
+	if (last)
+		md5_update(&md5ctx, "0", 1);
+	else
+		md5_update(&md5ctx, "+", 1);
+	if (handle != NULL) {
+		md5_update(&md5ctx, "h-", 2);
+		md5_update(&md5ctx, str_c(handle), str_len(handle));
+	} else {
+		md5_update(&md5ctx, "default", 7);
+	}
+	md5_update(&md5ctx, value, value_len);
+	md5_final(&md5ctx, hash_r);
+}
+
+int ext_duplicate_check
+(const struct sieve_runtime_env *renv, string_t *handle,
+	const char *value, size_t value_len, sieve_number_t period,
+	bool last)
+{
+	const struct sieve_extension *this_ext = renv->oprtn->ext;
+	const struct sieve_script_env *senv = renv->scriptenv;
+	struct ext_duplicate_context *rctx;
+	bool duplicate = FALSE;
+	pool_t msg_pool = NULL, result_pool = NULL;
+	struct act_duplicate_mark_data *act;
+
+	if ( !sieve_action_duplicate_check_available(senv) ) {
+		sieve_runtime_warning(renv, NULL, "duplicate test: "
+			"duplicate checking not available in this context");
+		return 0;
+	}
+		
+	if ( value == NULL )
+		return 0;
+
+	/* Get context; find out whether duplicate was checked earlier */
+	rctx = (struct ext_duplicate_context *)
+		sieve_message_context_extension_get(renv->msgctx, this_ext);
+
+	if ( rctx == NULL ) {
+		/* Create context */
+		msg_pool = sieve_message_context_pool(renv->msgctx);
+		rctx = p_new(msg_pool, struct ext_duplicate_context, 1);
+		sieve_message_context_extension_set(renv->msgctx, this_ext, (void *)rctx);
+	} else {
+		if ( handle == NULL ) {
+			if ( rctx->nohandle_checked  ) {
+				/* Already checked for duplicate */
+				return ( rctx->nohandle_duplicate ? 1 : 0 );
+			}
+		} else if ( array_is_created(&rctx->handles) ) {
+			const struct ext_duplicate_handle *record;
+			array_foreach (&rctx->handles, record) {
+				if ( strcmp(record->handle, str_c(handle)) == 0 &&
+					record->last == last )
+					return ( record->duplicate ? 1 : 0 );
+			}
+		}
+	}
+
+	result_pool = sieve_result_pool(renv->result);
+	act = p_new(result_pool, struct act_duplicate_mark_data, 1);
+	if (handle != NULL)
+		act->handle = p_strdup(result_pool, str_c(handle));
+	act->period = period;
+	act->last = last;
+
+	/* Create hash */
+	ext_duplicate_hash(handle, value, value_len, last, act->hash);
+
+	/* Check duplicate */
+	duplicate = sieve_action_duplicate_check(senv, act->hash, sizeof(act->hash));
+
+	if (!duplicate && last) {
+		unsigned char no_last_hash[MD5_RESULTLEN];
+
+		/* Check for entry without :last */
+		ext_duplicate_hash(handle, value, value_len, FALSE, no_last_hash);
+		sieve_action_duplicate_check(senv, no_last_hash, sizeof(no_last_hash));
+	}
+
+	/* We may only mark the message as duplicate when Sieve script executes
+	 * successfully; therefore defer this operation until successful result
+	 * execution.
+	 */
+	if (!duplicate || last) {
+		if ( sieve_result_add_action
+			(renv, NULL, &act_duplicate_mark, NULL, (void *) act, 0, FALSE) < 0 )
+			return -1;
+	}
+
+	/* Cache result */
+	if ( handle == NULL ) {
+		rctx->nohandle_duplicate = duplicate;
+		rctx->nohandle_checked = TRUE;
+	} else {
+		struct ext_duplicate_handle *record;
+
+		if ( msg_pool == NULL )
+			msg_pool = sieve_message_context_pool(renv->msgctx);
+		if ( !array_is_created(&rctx->handles) )
+			p_array_init(&rctx->handles, msg_pool, 64);
+		record = array_append_space(&rctx->handles);
+		record->handle = p_strdup(msg_pool, str_c(handle));
+		record->last = last;
+		record->duplicate = duplicate;
+	}
+
+	return ( duplicate ? 1 : 0 );
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate-common.h
@@ -0,0 +1,43 @@
+#ifndef EXT_DUPLICATE_COMMON_H
+#define EXT_DUPLICATE_COMMON_H
+
+#include "sieve-common.h"
+
+/*
+ * Extension
+ */
+
+struct ext_duplicate_config {
+	unsigned int default_period;
+	unsigned int max_period;
+};
+
+bool ext_duplicate_load
+	(const struct sieve_extension *ext, void **context);
+void ext_duplicate_unload
+	(const struct sieve_extension *ext);
+
+extern const struct sieve_extension_def duplicate_extension;
+extern const struct sieve_extension_def vnd_duplicate_extension;
+
+/*
+ * Tests
+ */
+
+extern const struct sieve_command_def tst_duplicate;
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def tst_duplicate_operation;
+
+/*
+ * Duplicate checking
+ */
+
+int ext_duplicate_check
+	(const struct sieve_runtime_env *renv, string_t *handle,
+		const char *value, size_t value_len, sieve_number_t period, bool last);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate.c
@@ -0,0 +1,107 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension duplicate
+ * -------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: vendor-defined; spec-bosch-sieve-duplicate
+ * Implementation: full
+ * Status: experimental
+ *
+ */
+
+/* Extension vnd.dovecot.duplicate
+ * -------------------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: vendor-defined; spec-bosch-sieve-duplicate
+ * Implementation: full, but deprecated; provided for backwards compatibility
+ * Status: experimental
+ *
+ */
+
+#include "lib.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-binary.h"
+
+#include "sieve-validator.h"
+
+#include "ext-duplicate-common.h"
+
+/*
+ * Extensions
+ */
+
+static bool ext_duplicate_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def duplicate_extension = {
+	.name = "duplicate",
+	.load = ext_duplicate_load,
+	.unload = ext_duplicate_unload,
+	.validator_load = ext_duplicate_validator_load,
+	SIEVE_EXT_DEFINE_OPERATION(tst_duplicate_operation)
+};
+
+const struct sieve_extension_def vnd_duplicate_extension = {
+	.name = "vnd.dovecot.duplicate",
+	.load = ext_duplicate_load,
+	.unload = ext_duplicate_unload,
+	.validator_load = ext_duplicate_validator_load,
+	SIEVE_EXT_DEFINE_OPERATION(tst_duplicate_operation)
+};
+
+/*
+ * Validation
+ */
+
+static bool ext_duplicate_validator_check_conflict
+	(const struct sieve_extension *ext,
+		struct sieve_validator *valdtr, void *context,
+		struct sieve_ast_argument *require_arg,
+		const struct sieve_extension *ext_other,
+		bool required);
+
+const struct sieve_validator_extension
+duplicate_validator_extension = {
+	.ext = &vnd_duplicate_extension,
+	.check_conflict = ext_duplicate_validator_check_conflict
+};
+
+static bool ext_duplicate_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	/* Register validator extension to check for conflict between
+	   vnd.dovecot.duplicate and duplicate extensions */
+	if ( sieve_extension_is(ext, vnd_duplicate_extension) ) {
+		sieve_validator_extension_register
+			(valdtr, ext, &duplicate_validator_extension, NULL);
+	}
+
+	/* Register duplicate test */
+	sieve_validator_register_command(valdtr, ext, &tst_duplicate);
+
+	return TRUE;
+}
+
+static bool ext_duplicate_validator_check_conflict
+(const struct sieve_extension *ext ATTR_UNUSED,
+	struct sieve_validator *valdtr, void *context ATTR_UNUSED,
+	struct sieve_ast_argument *require_arg,
+	const struct sieve_extension *ext_other,
+	bool required ATTR_UNUSED)
+{
+	/* Check for conflict with duplicate extension */
+	if ( sieve_extension_name_is(ext_other, "duplicate") ) {
+		sieve_argument_validate_error(valdtr, require_arg,
+			"the (deprecated) vnd.dovecot.duplicate extension "
+			"cannot be used together with the duplicate extension");
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/duplicate/tst-duplicate.c
@@ -0,0 +1,416 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "mail-storage.h"
+
+#include "sieve-common.h"
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-duplicate-common.h"
+
+/* Duplicate test
+ *
+ * Syntax:
+ *   Usage: "duplicate" [":handle" <handle: string>]
+ *                      [":header" <header-name: string> /
+ *                          ":uniqueid" <value: string>]
+ *                      [":seconds" <timeout: number>] [":last"]
+ */
+
+static bool tst_duplicate_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool tst_duplicate_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def tst_duplicate = {
+	.identifier = "duplicate",
+	.type = SCT_TEST,
+	.positional_args = 0,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = tst_duplicate_registered,
+	.generate = tst_duplicate_generate
+};
+
+/*
+ * Duplicate test tags
+ */
+
+static bool tst_duplicate_validate_number_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+static bool tst_duplicate_validate_string_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+
+static const struct sieve_argument_def duplicate_seconds_tag = {
+	.identifier = "seconds",
+	.validate = tst_duplicate_validate_number_tag
+};
+
+static const struct sieve_argument_def duplicate_header_tag = {
+	.identifier = "header",
+	.validate = tst_duplicate_validate_string_tag
+};
+
+static const struct sieve_argument_def duplicate_uniqueid_tag = {
+	.identifier = "uniqueid",
+	.validate = tst_duplicate_validate_string_tag
+};
+
+static const struct sieve_argument_def duplicate_value_tag = {
+	.identifier = "value", /* vnd.dovecot.duplicate (deprecated) */
+	.validate = tst_duplicate_validate_string_tag
+};
+
+static const struct sieve_argument_def duplicate_handle_tag = {
+	.identifier = "handle",
+	.validate = tst_duplicate_validate_string_tag
+};
+
+static const struct sieve_argument_def duplicate_last_tag = {
+	.identifier = "last"
+};
+
+/* Codes for optional arguments */
+
+enum tst_duplicate_optional {
+	OPT_END,
+	OPT_SECONDS,
+	OPT_HEADER,
+	OPT_UNIQUEID,
+	OPT_LAST,
+	OPT_HANDLE
+};
+
+/*
+ * Duplicate operation
+ */
+
+static bool tst_duplicate_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_duplicate_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def tst_duplicate_operation = {
+	.mnemonic = "DUPLICATE",
+	.ext_def = &duplicate_extension,
+	.dump = tst_duplicate_operation_dump,
+	.execute = tst_duplicate_operation_execute
+};
+
+/*
+ * Tag validation
+ */
+
+static bool tst_duplicate_validate_number_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	const struct sieve_extension *ext = sieve_argument_ext(*arg);
+	const struct ext_duplicate_config *config =
+		(const struct ext_duplicate_config *) ext->context;
+	struct sieve_ast_argument *tag = *arg;
+	sieve_number_t seconds;
+
+	/* Detach the tag itself */
+	*arg = sieve_ast_arguments_detach(*arg,1);
+
+	/* Check syntax:
+	 *   :seconds number
+	 */
+	if ( !sieve_validate_tag_parameter
+		(valdtr, cmd, tag, *arg, NULL, 0, SAAT_NUMBER, FALSE) ) {
+		return FALSE;
+	}
+
+	seconds = sieve_ast_argument_number(*arg);
+	/* Enforce :days <= max_period */
+	if ( config->max_period > 0 && seconds > config->max_period ) {
+		seconds = config->max_period;
+
+		sieve_argument_validate_warning(valdtr, *arg,
+			"specified :seconds value '%llu' is over the maximum",
+			(unsigned long long)seconds);
+	}
+
+	sieve_ast_argument_number_set(*arg, seconds);
+
+	/* Skip parameter */
+	*arg = sieve_ast_argument_next(*arg);
+	return TRUE;
+}
+
+static bool tst_duplicate_validate_string_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	const struct sieve_extension *ext = cmd->ext;
+	struct sieve_ast_argument *tag = *arg;
+
+	/* Detach the tag itself */
+	*arg = sieve_ast_arguments_detach(*arg,1);
+
+	/* Check syntax:
+	 *   :header <header-name: string>
+	 *   :value <value: string>
+	 *   :handle <handle: string>
+	 */
+	if ( !sieve_validate_tag_parameter
+		(valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING, FALSE) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_argument_is(tag, duplicate_handle_tag) && (bool)cmd->data ) {
+		sieve_argument_validate_error(valdtr, *arg,
+			"conflicting :header and %s arguments specified "
+			"for the duplicate test",
+			(sieve_extension_is(ext, duplicate_extension) ? ":uniqueid" : ":value"));
+		return FALSE;
+	}
+
+	/* :header <header-name: string> */
+	if ( sieve_argument_is(tag, duplicate_header_tag) ) {
+		if ( !sieve_command_verify_headers_argument(valdtr, *arg) )
+			return FALSE;
+		cmd->data = (void *)TRUE;
+	/* :handle <handle: string> */
+	} else if ( sieve_argument_is(tag, duplicate_handle_tag) ) {
+		/* nothing to be done */
+	} else if ( sieve_argument_is(tag, duplicate_uniqueid_tag) ) {
+		i_assert(sieve_extension_is(ext, duplicate_extension));
+		cmd->data = (void *)TRUE;
+	/* :value <value: string> (vnd.dovecot.duplicate) */
+	} else if ( sieve_argument_is(tag, duplicate_value_tag) ) {
+		i_assert(sieve_extension_is(ext, vnd_duplicate_extension));
+		cmd->data = (void *)TRUE;
+	} else {
+		i_unreached();
+	}
+
+	/* Skip parameter */
+	*arg = sieve_ast_argument_next(*arg);
+	return TRUE;
+}
+
+/*
+ * Command registration
+ */
+
+static bool tst_duplicate_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg)
+{
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &duplicate_seconds_tag, OPT_SECONDS);
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &duplicate_last_tag, OPT_LAST);
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &duplicate_header_tag, OPT_HEADER);
+	if ( sieve_extension_is(ext, duplicate_extension) ) {
+		sieve_validator_register_tag
+			(valdtr, cmd_reg, ext, &duplicate_uniqueid_tag, OPT_UNIQUEID);
+	} else {
+		sieve_validator_register_tag
+			(valdtr, cmd_reg, ext, &duplicate_value_tag, OPT_UNIQUEID);
+	}
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &duplicate_handle_tag, OPT_HANDLE);
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_duplicate_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	sieve_operation_emit(cgenv->sblock, cmd->ext, &tst_duplicate_operation);
+
+	if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
+		return FALSE;
+
+	return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_duplicate_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	const struct sieve_extension *ext = denv->oprtn->ext;
+	int opt_code = 0;
+
+	sieve_code_dumpf(denv, "DUPLICATE");
+	sieve_code_descend(denv);
+
+	/* Dump optional operands */
+
+	for (;;) {
+		int opt;
+		bool opok = TRUE;
+
+		if ( (opt=sieve_opr_optional_dump(denv, address, &opt_code)) < 0 )
+			return FALSE;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_SECONDS:
+			opok = sieve_opr_number_dump(denv, address, "seconds");
+			break;
+		case OPT_LAST:
+			sieve_code_dumpf(denv, "last");
+			break;
+		case OPT_HEADER:
+			opok = sieve_opr_string_dump(denv, address, "header");
+			break;
+		case OPT_UNIQUEID:
+			if ( sieve_extension_is(ext, duplicate_extension) )
+				opok = sieve_opr_string_dump(denv, address, "uniqueid");
+			else
+				opok = sieve_opr_string_dump(denv, address, "value");
+			break;
+		case OPT_HANDLE:
+			opok = sieve_opr_string_dump(denv, address, "handle");
+			break;
+		default:
+			return FALSE;
+		}
+
+		if ( !opok ) return FALSE;
+	}
+
+	return TRUE;
+}
+
+/*
+ * Code execution
+ */
+
+static int tst_duplicate_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED)
+{
+	const struct sieve_extension *ext = renv->oprtn->ext;
+	const struct ext_duplicate_config *config =
+		(const struct ext_duplicate_config *) ext->context;
+	struct mail *mail = renv->msgdata->mail;
+	int opt_code = 0;
+	string_t *handle = NULL, *header = NULL, *uniqueid = NULL;
+	const char *val = NULL;
+	size_t val_len = 0;
+	sieve_number_t seconds = config->default_period;
+	bool last = FALSE, duplicate = FALSE;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Optional operands */
+
+	for (;;) {
+		int opt;
+
+		if ( (opt=sieve_opr_optional_read(renv, address, &opt_code)) < 0 )
+			return SIEVE_EXEC_BIN_CORRUPT;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_SECONDS:
+			ret = sieve_opr_number_read(renv, address, "seconds", &seconds);
+			break;
+		case OPT_LAST:
+			last = TRUE;
+			ret = SIEVE_EXEC_OK;
+			break;
+		case OPT_HEADER:
+			ret = sieve_opr_string_read(renv, address, "header", &header);
+			break;
+		case OPT_UNIQUEID:
+			if ( sieve_extension_is(ext, duplicate_extension) )
+				ret = sieve_opr_string_read(renv, address, "uniqueid", &uniqueid);
+			else
+				ret = sieve_opr_string_read(renv, address, "value", &uniqueid);
+			break;
+		case OPT_HANDLE:
+			ret = sieve_opr_string_read(renv, address, "handle", &handle);
+			break;
+		default:
+			sieve_runtime_trace_error(renv, "unknown optional operand");
+			ret = SIEVE_EXEC_BIN_CORRUPT;
+		}
+
+		if ( ret <= 0 ) return ret;
+	}
+
+	/*
+	 * Perform operation
+	 */
+
+	/* Trace */
+	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "duplicate test");
+	sieve_runtime_trace_descend(renv);
+
+	/* Get value */
+	if ( uniqueid != NULL ) {
+		val = str_c(uniqueid);
+		val_len = str_len(uniqueid);
+	} else {
+		if ( header == NULL ) {
+			ret = mail_get_first_header_utf8(mail, "Message-ID", &val);
+			if ( ret < 0 ) {
+				return sieve_runtime_mail_error	(renv, mail,
+					"duplicate test: "
+					"failed to read header field `message-id'");
+			}
+		} else {
+			ret = mail_get_first_header_utf8(mail, str_c(header), &val);
+			if ( ret < 0 ) {
+				return sieve_runtime_mail_error	(renv, mail,
+					"duplicate test: "
+					"failed to read header field `%s'", str_c(header));
+			}
+		}
+
+		if ( ret > 0 )
+			val_len = strlen(val);
+	}
+
+	/* Check duplicate */
+	if (val == NULL) {
+		duplicate = FALSE;
+	} else {	
+		if ((ret=ext_duplicate_check
+			(renv, handle, val, val_len, seconds, last)) < 0)
+			return SIEVE_EXEC_FAILURE;
+		duplicate = ( ret > 0 );
+	}
+
+	/* Trace */
+	if (duplicate) {
+		sieve_runtime_trace(renv,	SIEVE_TRLVL_TESTS,
+			"message is a duplicate");
+	}	else {
+		sieve_runtime_trace(renv,	SIEVE_TRLVL_TESTS,
+			"message is not a duplicate");
+	}
+
+	/* Set test result for subsequent conditional jump */
+	sieve_interpreter_set_test_result(renv->interp, duplicate);
+	return SIEVE_EXEC_OK;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/editheader/Makefile.am
@@ -0,0 +1,19 @@
+noinst_LTLIBRARIES = libsieve_ext_editheader.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../.. \
+	-I$(srcdir)/../../util \
+	$(LIBDOVECOT_INCLUDE)
+
+commands = \
+	cmd-addheader.c \
+	cmd-deleteheader.c
+
+libsieve_ext_editheader_la_SOURCES = \
+	$(commands) \
+	ext-editheader.c \
+	ext-editheader-common.c
+
+noinst_HEADERS = \
+	ext-editheader-limits.h \
+	ext-editheader-common.h
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/editheader/cmd-addheader.c
@@ -0,0 +1,337 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+#include "mail-storage.h"
+
+#include "rfc2822.h"
+#include "edit-mail.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-editheader-common.h"
+
+/*
+ * Addheader command
+ *
+ * Syntax
+ *   "addheader" [":last"] <field-name: string> <value: string>
+ */
+
+static bool cmd_addheader_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool cmd_addheader_validate
+	(struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool cmd_addheader_generate
+	(const struct sieve_codegen_env *cgenv,	struct sieve_command *ctx);
+
+const struct sieve_command_def addheader_command = {
+	.identifier = "addheader",
+	.type = SCT_COMMAND,
+	.positional_args = 2,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = cmd_addheader_registered,
+	.validate = cmd_addheader_validate,
+	.generate = cmd_addheader_generate
+};
+
+/*
+ * Addheader command tags
+ */
+
+/* Argument objects */
+
+static const struct sieve_argument_def addheader_last_tag = {
+	.identifier = "last"
+};
+
+/* Codes for optional arguments */
+
+enum cmd_addheader_optional {
+	OPT_END,
+	OPT_LAST
+};
+
+/*
+ * Addheader operation
+ */
+
+static bool cmd_addheader_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_addheader_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def addheader_operation = {
+	.mnemonic = "ADDHEADER",
+	.ext_def = &editheader_extension,
+	.code = EXT_EDITHEADER_OPERATION_ADDHEADER,
+	.dump = cmd_addheader_operation_dump,
+	.execute = cmd_addheader_operation_execute
+};
+
+/*
+ * Utility
+ */
+
+static bool _str_contains_nul(const string_t *str)
+{
+	const unsigned char *p, *pend;
+
+	p = str_data(str);
+	pend = p + str_len(str);
+	while (p < pend) {
+		if (*p == '\0')
+			return TRUE;
+		p++;
+	}
+	return FALSE;
+}
+
+/*
+ * Validation
+ */
+
+static bool cmd_addheader_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *arg = cmd->first_positional;
+
+	/* Check field-name syntax */
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "field-name", 1, SAAT_STRING) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, cmd, arg, FALSE) )
+		return FALSE;
+
+	if ( sieve_argument_is_string_literal(arg) ) {
+		string_t *fname = sieve_ast_argument_str(arg);
+
+		if ( !rfc2822_header_field_name_verify(str_c(fname), str_len(fname)) ) {
+			sieve_argument_validate_error
+				(valdtr, arg, "addheader command: specified field name `%s' is invalid",
+					str_sanitize(str_c(fname), 80));
+			return FALSE;
+		}
+
+		if ( !ext_editheader_header_allow_add
+			(cmd->ext, str_c(fname)) ) {
+			sieve_argument_validate_warning
+				(valdtr, arg, "addheader command: "
+					"adding specified header field `%s' is forbidden; "
+					"modification will be denied",
+					str_sanitize(str_c(fname), 80));
+		}
+	}
+
+	/* Check value syntax */
+
+	arg = sieve_ast_argument_next(arg);
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "value", 2, SAAT_STRING) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, cmd, arg, FALSE) )
+		return FALSE;
+
+	if ( sieve_argument_is_string_literal(arg) ) {
+		string_t *fvalue = sieve_ast_argument_str(arg);
+
+		if ( _str_contains_nul(fvalue) ) {
+			sieve_argument_validate_error(valdtr, arg,
+				"addheader command: specified value `%s' is invalid "
+				"(contains NUL character)",	str_sanitize(str_c(fvalue), 80));
+			return FALSE;
+		}	
+
+		if ( !rfc2822_header_field_body_verify
+			(str_c(fvalue), str_len(fvalue), TRUE, TRUE) ) {
+			sieve_argument_validate_warning(valdtr, arg,
+				"addheader command: specified value `%s' is invalid",
+				str_sanitize(str_c(fvalue), 80));
+		}
+
+		if ( ext_editheader_header_too_large(cmd->ext, str_len(fvalue)) ) {
+			sieve_argument_validate_error(valdtr, arg, "addheader command: "
+				"specified header value `%s' is too large (%"PRIuSIZE_T" bytes)",
+				str_sanitize(str_c(fvalue), 80), str_len(fvalue));
+			return SIEVE_EXEC_FAILURE;
+		}
+	}
+
+	return TRUE;
+}
+
+/*
+ * Command registration
+ */
+
+static bool cmd_addheader_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg)
+{
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &addheader_last_tag, OPT_LAST);
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_addheader_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	(void)sieve_operation_emit(cgenv->sblock, cmd->ext, &addheader_operation);
+
+	/* Generate arguments */
+	return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_addheader_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	int opt_code = 0;
+
+	sieve_code_dumpf(denv, "addheader");
+	sieve_code_descend(denv);
+
+	/* Dump optional operands */
+
+	for (;;) {
+		int opt;
+
+		if ( (opt=sieve_opr_optional_dump(denv, address, &opt_code)) < 0 )
+			return FALSE;
+
+		if ( opt == 0 ) break;
+
+		if ( opt_code == OPT_LAST ) {
+			sieve_code_dumpf(denv, "last");
+		} else {
+			return FALSE;
+		}
+	}
+
+	return
+		sieve_opr_string_dump(denv, address, "field-name") &&
+		sieve_opr_string_dump(denv, address, "value");
+}
+
+/*
+ * Interpretation
+ */
+
+static int cmd_addheader_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	const struct sieve_extension *this_ext = renv->oprtn->ext;
+	string_t *field_name;
+	string_t *value;
+	struct edit_mail *edmail;
+	bool last = FALSE;
+	int opt_code = 0;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Optional operands */
+
+	for (;;) {
+		int opt;
+
+		if ( (opt=sieve_opr_optional_read(renv, address, &opt_code)) < 0 )
+			return SIEVE_EXEC_BIN_CORRUPT;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_LAST:
+			last = TRUE;
+			break;
+		default:
+			sieve_runtime_trace_error(renv, "unknown optional operand");
+			return SIEVE_EXEC_BIN_CORRUPT;
+		}
+	}
+
+	/* Read message */
+
+	if ( (ret=sieve_opr_string_read
+		(renv, address, "field-name", &field_name)) <= 0 )
+		return ret;
+
+	if ( (ret=sieve_opr_string_read
+		(renv, address, "value", &value)) <= 0 )
+		return ret;
+
+	/*
+	 * Verify arguments
+	 */
+
+	if ( !rfc2822_header_field_name_verify
+		(str_c(field_name), str_len(field_name)) ) {
+		sieve_runtime_error(renv, NULL, "addheader action: "
+			"specified field name `%s' is invalid",
+			str_sanitize(str_c(field_name), 80));
+		return SIEVE_EXEC_FAILURE;
+	}
+
+	if ( !ext_editheader_header_allow_add
+		(this_ext, str_c(field_name)) ) {
+		sieve_runtime_warning(renv, NULL, "addheader action: "
+			"adding specified header field `%s' is forbidden; "
+			"modification denied", str_sanitize(str_c(field_name), 80));
+		return SIEVE_EXEC_OK;
+	}
+
+	if ( _str_contains_nul(value) ) {
+		sieve_runtime_error(renv, NULL, "addheader action: "
+			"specified value `%s' is invalid (contains NUL character)",
+			str_sanitize(str_c(value), 80));
+		return SIEVE_EXEC_FAILURE;
+	}
+
+	if ( ext_editheader_header_too_large(this_ext, str_len(value)) ) {
+		sieve_runtime_error(renv, NULL, "addheader action: "
+			"specified header value `%s' is too large (%"PRIuSIZE_T" bytes)",
+			str_sanitize(str_c(value), 80), str_len(value));
+		return SIEVE_EXEC_FAILURE;
+	}
+
+	/*
+	 * Perform operation
+	 */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS, "addheader \"%s: %s\"",
+		str_sanitize(str_c(field_name), 80), str_sanitize(str_c(value), 80));
+
+	edmail = sieve_message_edit(renv->msgctx);
+	edit_mail_header_add(edmail,
+		rfc2822_header_field_name_sanitize(str_c(field_name)),
+		str_c(value), last);
+	return SIEVE_EXEC_OK;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/editheader/cmd-deleteheader.c
@@ -0,0 +1,551 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+#include "mail-storage.h"
+
+#include "rfc2822.h"
+#include "edit-mail.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+#include "ext-editheader-common.h"
+
+/*
+ * Deleteheader command
+ *
+ * Syntax:
+ *   deleteheader [":index" <fieldno: number> [":last"]]
+ *                [COMPARATOR] [MATCH-TYPE]
+ *                <field-name: string> [<value-patterns: string-list>]
+ */
+
+static bool cmd_deleteheader_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool cmd_deleteheader_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_deleteheader_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+const struct sieve_command_def deleteheader_command = {
+	.identifier = "deleteheader",
+	.type = SCT_COMMAND,
+	.positional_args = -1, /* We check positional arguments ourselves */
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = cmd_deleteheader_registered,
+	.validate = cmd_deleteheader_validate,
+	.generate = cmd_deleteheader_generate
+};
+
+/*
+ * Deleteheader command tags
+ */
+
+/* Forward declarations */
+
+static bool cmd_deleteheader_validate_index_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+static bool cmd_deleteheader_validate_last_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+
+/* Argument objects */
+
+static const struct sieve_argument_def deleteheader_index_tag = {
+	.identifier = "index",
+	.validate = cmd_deleteheader_validate_index_tag
+};
+
+static const struct sieve_argument_def deleteheader_last_tag = {
+	.identifier = "last",
+	.validate = cmd_deleteheader_validate_last_tag
+};
+
+/* Codes for optional arguments */
+
+enum cmd_deleteheader_optional {
+	OPT_INDEX = SIEVE_MATCH_OPT_LAST,
+	OPT_LAST
+};
+
+/*
+ * Deleteheader operation
+ */
+
+static bool cmd_deleteheader_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_deleteheader_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def deleteheader_operation = {
+	.mnemonic = "DELETEHEADER",
+	.ext_def = &editheader_extension,
+	.code = EXT_EDITHEADER_OPERATION_DELETEHEADER,
+	.dump = cmd_deleteheader_operation_dump,
+	.execute = cmd_deleteheader_operation_execute
+};
+
+/*
+ * Command registration
+ */
+
+static bool cmd_deleteheader_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext ATTR_UNUSED,
+	struct sieve_command_registration *cmd_reg)
+{
+	/* The order of these is not significant */
+	sieve_comparators_link_tag(valdtr, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR);
+	sieve_match_types_link_tags(valdtr, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
+
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &deleteheader_index_tag, OPT_INDEX);
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &deleteheader_last_tag, OPT_LAST);
+
+	return TRUE;
+}
+
+/*
+ * Command validation context
+ */
+
+struct cmd_deleteheader_context_data {
+	struct sieve_ast_argument *arg_index;
+	struct sieve_ast_argument *arg_last;
+};
+
+/*
+ * Tag validation
+ */
+
+static struct cmd_deleteheader_context_data *
+cmd_deleteheader_get_context
+(struct sieve_command *cmd)
+{
+	struct cmd_deleteheader_context_data *ctx_data =
+		(struct cmd_deleteheader_context_data *)cmd->data;
+
+	if ( ctx_data != NULL ) return ctx_data;
+
+	ctx_data = p_new
+		(sieve_command_pool(cmd), struct cmd_deleteheader_context_data, 1);
+	cmd->data = (void *)ctx_data;
+
+	return ctx_data;
+}
+
+static bool cmd_deleteheader_validate_index_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *tag = *arg;
+	struct cmd_deleteheader_context_data *ctx_data;
+	sieve_number_t index;
+
+	/* Detach the tag itself */
+	*arg = sieve_ast_arguments_detach(*arg,1);
+
+	/* Check syntax:
+	 *   :index number
+	 */
+	if ( !sieve_validate_tag_parameter
+		(valdtr, cmd, tag, *arg, NULL, 0, SAAT_NUMBER, FALSE) ) {
+		return FALSE;
+	}
+
+	index = sieve_ast_argument_number(*arg);
+	if ( index > INT_MAX ) {
+		sieve_argument_validate_warning(valdtr, *arg,
+			"the :%s tag for the %s %s has a parameter value '%llu' "
+			"exceeding the maximum (%d)",
+			sieve_argument_identifier(tag), sieve_command_identifier(cmd),
+			sieve_command_type_name(cmd), (unsigned long long) index,
+			INT_MAX);
+		return FALSE;
+	}
+
+	ctx_data = cmd_deleteheader_get_context(cmd);
+	ctx_data->arg_index = *arg;
+
+	/* Skip parameter */
+	*arg = sieve_ast_argument_next(*arg);
+
+	return TRUE;
+}
+
+static bool cmd_deleteheader_validate_last_tag
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	struct cmd_deleteheader_context_data *ctx_data;
+
+	ctx_data = cmd_deleteheader_get_context(cmd);
+	ctx_data->arg_last = *arg;
+
+	/* Skip parameter */
+	*arg = sieve_ast_argument_next(*arg);
+
+	return TRUE;
+}
+
+/*
+ * Validation
+ */
+
+static bool cmd_deleteheader_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *arg = cmd->first_positional;
+	struct cmd_deleteheader_context_data *ctx_data =
+		(struct cmd_deleteheader_context_data *)cmd->data;
+	struct sieve_comparator cmp_default =
+		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+	struct sieve_match_type mcht_default =
+		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+
+	if ( ctx_data != NULL ) {
+		if ( ctx_data->arg_last != NULL && ctx_data->arg_index == NULL ) {
+			sieve_argument_validate_error(valdtr, ctx_data->arg_last,
+				"the :last tag for the %s %s cannot be specified "
+				"without the :index tag",
+				sieve_command_identifier(cmd), sieve_command_type_name(cmd));
+		}
+	}
+
+	/* Field name argument */
+
+	if ( arg == NULL ) {
+		sieve_command_validate_error(valdtr, cmd,
+			"the %s %s expects at least one positional argument, but none was found",
+			sieve_command_identifier(cmd), sieve_command_type_name(cmd));
+		return FALSE;
+	}
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "field name", 1, SAAT_STRING_LIST) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, cmd, arg, FALSE) )
+		return FALSE;
+
+	if ( sieve_argument_is_string_literal(arg) ) {
+		string_t *fname = sieve_ast_argument_str(arg);
+
+		if ( !rfc2822_header_field_name_verify(str_c(fname), str_len(fname)) ) {
+			sieve_argument_validate_error(valdtr, arg, "deleteheader command:"
+				"specified field name `%s' is invalid",
+				str_sanitize(str_c(fname), 80));
+			return FALSE;
+		}
+
+		if ( !ext_editheader_header_allow_delete
+			(cmd->ext, str_c(fname)) ) {
+			sieve_argument_validate_warning
+				(valdtr, arg, "deleteheader command: "
+					"deleting specified header field `%s' is forbidden; "
+					"modification will be denied",
+					str_sanitize(str_c(fname), 80));
+		}
+	}
+
+	/* Value patterns argument */
+
+	arg = sieve_ast_argument_next(arg);
+	if ( arg == NULL ) {
+		/* There is none; let's not generate code for useless match arguments */
+		sieve_match_type_arguments_remove(valdtr, cmd);
+
+		return TRUE;
+	}
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "value patterns", 2, SAAT_STRING_LIST) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, cmd, arg, FALSE) )
+		return FALSE;
+
+	/* Validate the value patterns to a specified match type */
+	return sieve_match_type_validate
+		(valdtr, cmd, arg, &mcht_default, &cmp_default);
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_deleteheader_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	sieve_operation_emit(cgenv->sblock, cmd->ext, &deleteheader_operation);
+
+ 	/* Generate arguments */
+	if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
+		return FALSE;
+
+	/* Emit a placeholder when the value-patterns argument is missing */
+	if ( sieve_ast_argument_next(cmd->first_positional) == NULL )
+		sieve_opr_omitted_emit(cgenv->sblock);
+
+	return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_deleteheader_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	int opt_code = 0;
+
+	sieve_code_dumpf(denv, "DELETEHEADER");
+	sieve_code_descend(denv);
+
+	/* Optional operands */
+	for (;;) {
+		int opt;
+
+		if ( (opt=sieve_match_opr_optional_dump(denv, address, &opt_code)) < 0 )
+			return FALSE;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_INDEX:
+			if ( !sieve_opr_number_dump(denv, address, "index") )
+				return FALSE;
+			break;
+		case OPT_LAST:
+			sieve_code_dumpf(denv, "last");
+			break;
+		default:
+			return FALSE;
+		}
+	};
+
+	if ( !sieve_opr_string_dump(denv, address, "field name") )
+		return FALSE;
+
+	return sieve_opr_stringlist_dump_ex(denv, address, "value patterns", "");
+}
+
+/*
+ * Code execution
+ */
+
+static int cmd_deleteheader_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	const struct sieve_extension *this_ext = renv->oprtn->ext;
+	int opt_code = 0;
+	struct sieve_comparator cmp =
+		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+	struct sieve_match_type mcht =
+		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+	string_t *field_name;
+	struct sieve_stringlist *vpattern_list = NULL;
+	struct edit_mail *edmail;
+	sieve_number_t index_offset = 0;
+	bool index_last = FALSE;
+	bool trace = FALSE;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	for (;;) {
+		int opt;
+
+		if ( (opt=sieve_match_opr_optional_read
+			(renv, address, &opt_code, &ret, &cmp, &mcht)) < 0 )
+			return ret;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_INDEX:
+			if ( (ret=sieve_opr_number_read(renv, address, "index", &index_offset))
+				<= 0 )
+				return ret;
+
+			if ( index_offset > INT_MAX ) {
+				sieve_runtime_trace_error(renv, "index is > %d", INT_MAX);
+				return SIEVE_EXEC_BIN_CORRUPT;
+			}
+
+			break;
+		case OPT_LAST:
+			index_last = TRUE;
+			break;
+		default:
+			sieve_runtime_trace_error(renv, "unknown optional operand");
+			return SIEVE_EXEC_BIN_CORRUPT;
+		}
+	}
+
+	/* Read field-name */
+	if ( (ret=sieve_opr_string_read(renv, address, "field-name", &field_name))
+		<= 0 )
+		return ret;
+
+	/* Read value-patterns */
+	if ( (ret=sieve_opr_stringlist_read_ex
+		(renv, address, "value-patterns", TRUE, &vpattern_list)) <= 0 )
+		return ret;
+
+	/*
+	 * Verify arguments
+	 */
+
+	if ( !rfc2822_header_field_name_verify
+		(str_c(field_name), str_len(field_name)) ) {
+		sieve_runtime_error(renv, NULL, "deleteheader action: "
+			"specified field name `%s' is invalid",
+			str_sanitize(str_c(field_name), 80));
+		return SIEVE_EXEC_FAILURE;
+	}
+
+	if ( !ext_editheader_header_allow_delete
+		(this_ext, str_c(field_name)) ) {
+		sieve_runtime_warning(renv, NULL, "deleteheader action: "
+			"deleting specified header field `%s' is forbidden; "
+			"modification denied",
+			str_sanitize(str_c(field_name), 80));
+		return SIEVE_EXEC_OK;
+	}
+
+	/*
+	 * Execute command
+	 */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS, "deleteheader command");
+
+	/* Start editing the mail */
+	edmail = sieve_message_edit(renv->msgctx);
+
+	trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS);
+
+	/* Either do string matching or just kill all/indexed notify action(s) */
+	if ( vpattern_list != NULL ) {
+		struct edit_mail_header_iter *edhiter;
+		struct sieve_match_context *mctx;
+
+		if ( trace ) {
+			sieve_runtime_trace_descend(renv);
+			if ( index_offset != 0 ) {
+				sieve_runtime_trace(renv, 0,
+					"deleting matching occurrences of header `%s' at index %llu%s",
+					str_c(field_name), (unsigned long long)index_offset,
+					( index_last ? " from last": ""));
+			} else {
+				sieve_runtime_trace(renv, 0,
+					"deleting matching occurrences of header `%s'", str_c(field_name));
+			}
+		}
+
+		/* Iterate through all headers and delete those that match */
+		if ( (ret=edit_mail_headers_iterate_init
+			(edmail, str_c(field_name), index_last, &edhiter)) > 0 )
+		{
+			int mret = 0;
+			sieve_number_t pos = 0;
+
+			/* Initialize match */
+			mctx = sieve_match_begin(renv, &mcht, &cmp);
+
+			/* Match */
+			for (;;) {
+				pos++;
+
+				/* Check index if any */
+				if ( index_offset == 0 || pos == index_offset ) {
+					const char *value;
+					int match;
+
+					/* Match value against all value patterns */
+					edit_mail_headers_iterate_get(edhiter, &value);
+					if ( (match=sieve_match_value
+						(mctx, value, strlen(value), vpattern_list)) < 0 )
+						break;
+
+					if ( match > 0 ) {
+						/* Remove it and iterate to next */
+						sieve_runtime_trace(renv, 0, "deleting header with value `%s'",
+							value);
+
+						if ( !edit_mail_headers_iterate_remove(edhiter) ) break;
+						continue;
+					}
+				}
+
+				if ( !edit_mail_headers_iterate_next(edhiter) )
+					break;
+			}
+
+			/* Finish match */
+			mret = sieve_match_end(&mctx, &ret);
+
+			edit_mail_headers_iterate_deinit(&edhiter);
+
+			if ( mret < 0 )
+				return ret;
+		}
+
+		if ( ret == 0 ) {
+			sieve_runtime_trace(renv, 0, "header `%s' not found", str_c(field_name));
+		} else if ( ret < 0 ) {
+			sieve_runtime_warning(renv, NULL, "deleteheader action: "
+				"failed to delete occurrences of header `%s' (this should not happen!)",
+				str_c(field_name));
+		}
+
+	} else {
+		int index = ( index_last ? -((int)index_offset) : ((int)index_offset) );
+
+		if ( trace ) {
+			sieve_runtime_trace_descend(renv);
+			if ( index_offset != 0 ) {
+				sieve_runtime_trace(renv, 0, "deleting header `%s' at index %llu%s",
+					str_c(field_name), (unsigned long long)index_offset,
+					( index_last ? " from last": ""));
+			} else {
+				sieve_runtime_trace(renv, 0, "deleting header `%s'", str_c(field_name));
+			}
+		}
+
+		/* Delete all occurrences of header */
+		ret = edit_mail_header_delete(edmail, str_c(field_name), index);
+
+		if ( ret < 0 ) {
+			sieve_runtime_warning(renv, NULL, "deleteheader action: "
+				"failed to delete occurrences of header `%s' (this should not happen!)",
+				str_c(field_name));
+		} else if ( trace ) {
+			sieve_runtime_trace(renv, 0, "deleted %d occurrences of header `%s'",
+				ret, str_c(field_name));
+		}
+
+	}
+
+	return SIEVE_EXEC_OK;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/editheader/ext-editheader-common.c
@@ -0,0 +1,200 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "mempool.h"
+#include "array.h"
+
+#include "rfc2822.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-settings.h"
+#include "sieve-extensions.h"
+
+#include "ext-editheader-limits.h"
+#include "ext-editheader-common.h"
+
+/*
+ * Extension configuration
+ */
+
+struct ext_editheader_header {
+	const char *name;
+
+	bool forbid_add:1;
+	bool forbid_delete:1;
+};
+
+struct ext_editheader_config {
+	pool_t pool;
+
+	ARRAY(struct ext_editheader_header) headers;
+
+	size_t max_header_size;
+};
+
+static struct ext_editheader_header *ext_editheader_config_header_find
+(struct ext_editheader_config *ext_config, const char *hname)
+{
+	struct ext_editheader_header *headers;
+	unsigned int count, i;
+
+	headers = array_get_modifiable(&ext_config->headers, &count);
+	for ( i = 0; i < count; i++ ) {
+		if ( strcasecmp(hname, headers[i].name) == 0 )
+			return &headers[i];
+	}
+
+	return NULL;
+}
+
+static void ext_editheader_config_headers
+(struct sieve_instance *svinst,
+	struct ext_editheader_config *ext_config,
+	const char *setting, bool forbid_add, bool forbid_delete)
+{
+	const char *setval;
+
+	setval = sieve_setting_get(svinst, setting);
+	if ( setval != NULL ) {
+		const char **headers = t_strsplit_spaces(setval, " \t");
+
+		while ( *headers != NULL ) {
+			struct ext_editheader_header *header;
+
+			if ( !rfc2822_header_field_name_verify
+				(*headers, strlen(*headers)) ) {
+				sieve_sys_warning(svinst, "editheader: "
+					"setting %s contains invalid header field name "
+					"`%s' (ignored)", setting, *headers);
+				continue;
+			}
+
+			header=ext_editheader_config_header_find(ext_config, *headers);
+			if ( header == NULL ) {
+				header = array_append_space(&ext_config->headers);
+				header->name = p_strdup(ext_config->pool, *headers);
+			}
+
+			if (forbid_add)
+				header->forbid_add = TRUE;
+			if (forbid_delete)
+				header->forbid_delete = TRUE;
+
+			headers++;
+		}
+	}
+}
+
+bool ext_editheader_load
+(const struct sieve_extension *ext, void **context)
+{
+	struct ext_editheader_config *ext_config;
+	struct sieve_instance *svinst = ext->svinst;
+	size_t max_header_size;
+	pool_t pool;
+
+	if ( *context != NULL ) {
+		ext_editheader_unload(ext);
+		*context = NULL;
+	}
+
+	T_BEGIN {
+		pool = pool_alloconly_create("editheader_config", 1024);
+		ext_config = p_new(pool, struct ext_editheader_config, 1);
+		ext_config->pool = pool;
+		ext_config->max_header_size = EXT_EDITHEADER_DEFAULT_MAX_HEADER_SIZE;
+
+		p_array_init(&ext_config->headers, pool, 16);
+
+		ext_editheader_config_headers(svinst, ext_config,
+			"sieve_editheader_protected", TRUE, TRUE);
+		ext_editheader_config_headers(svinst, ext_config,
+			"sieve_editheader_forbid_add", TRUE, FALSE);
+		ext_editheader_config_headers(svinst, ext_config,
+			"sieve_editheader_forbid_delete", FALSE, TRUE);
+
+		if ( sieve_setting_get_size_value
+			(svinst, "sieve_editheader_max_header_size", &max_header_size) ) {
+			if ( max_header_size < EXT_EDITHEADER_MINIMUM_MAX_HEADER_SIZE ) {
+				sieve_sys_warning(svinst,
+					"editheader: value of sieve_editheader_max_header_size setting "
+					"(=%"PRIuSIZE_T") is less than the minimum (=%"PRIuSIZE_T") "
+					"(ignored)", max_header_size,
+					(size_t) EXT_EDITHEADER_MINIMUM_MAX_HEADER_SIZE);
+			} else {
+				ext_config->max_header_size = max_header_size;
+			}
+		}
+	} T_END;
+
+	*context = (void *) ext_config;
+	return TRUE;
+}
+
+void ext_editheader_unload(const struct sieve_extension *ext)
+{
+	struct ext_editheader_config *ext_config =
+		(struct ext_editheader_config *) ext->context;
+
+	if ( ext_config != NULL ) {
+		pool_unref(&ext_config->pool);
+	}
+}
+
+/*
+ * Protected headers
+ */
+
+bool ext_editheader_header_allow_add
+(const struct sieve_extension *ext, const char *hname)
+{
+	struct ext_editheader_config *ext_config =
+		(struct ext_editheader_config *) ext->context;
+	const struct ext_editheader_header *header;
+
+	if ( strcasecmp(hname, "subject") == 0 )
+		return TRUE;
+
+	if ( (header=ext_editheader_config_header_find
+		(ext_config, hname)) == NULL )
+		return TRUE;
+
+	return !header->forbid_add;
+}
+
+bool ext_editheader_header_allow_delete
+(const struct sieve_extension *ext, const char *hname)
+{
+	struct ext_editheader_config *ext_config =
+		(struct ext_editheader_config *) ext->context;
+	const struct ext_editheader_header *header;
+
+	if ( strcasecmp(hname, "received") == 0
+		|| strcasecmp(hname, "auto-submitted") == 0 )
+		return FALSE;
+
+	if ( strcasecmp(hname, "subject") == 0 )
+		return TRUE;
+
+	if ( (header=ext_editheader_config_header_find
+		(ext_config, hname)) == NULL )
+		return TRUE;
+
+	return !header->forbid_delete;
+}
+
+/*
+ * Limits
+ */
+
+bool ext_editheader_header_too_large
+(const struct sieve_extension *ext, size_t size)
+{
+	struct ext_editheader_config *ext_config =
+		(struct ext_editheader_config *) ext->context;
+
+	return size > ext_config->max_header_size;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/editheader/ext-editheader-common.h
@@ -0,0 +1,49 @@
+#ifndef EXT_EDITHEADER_COMMON_H
+#define EXT_EDITHEADER_COMMON_H
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def addheader_command;
+extern const struct sieve_command_def deleteheader_command;
+
+/*
+ * Operations
+ */
+
+enum ext_imap4flags_opcode {
+	EXT_EDITHEADER_OPERATION_ADDHEADER,
+	EXT_EDITHEADER_OPERATION_DELETEHEADER
+};
+
+extern const struct sieve_operation_def addheader_operation;
+extern const struct sieve_operation_def deleteheader_operation;
+
+/*
+ * Extension
+ */
+
+extern const struct sieve_extension_def editheader_extension;
+
+bool ext_editheader_load
+	(const struct sieve_extension *ext, void **context);
+void ext_editheader_unload(const struct sieve_extension *ext);
+
+/*
+ * Protected headers
+ */
+
+bool ext_editheader_header_allow_add
+	(const struct sieve_extension *ext, const char *hname);
+bool ext_editheader_header_allow_delete
+	(const struct sieve_extension *ext, const char *hname);
+
+/*
+ * Limits
+ */
+
+bool ext_editheader_header_too_large
+	(const struct sieve_extension *ext, size_t size);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/editheader/ext-editheader-limits.h
@@ -0,0 +1,7 @@
+#ifndef EXT_EDITHEADER_LIMITS_H
+#define EXT_EDITHEADER_LIMITS_H
+
+#define EXT_EDITHEADER_MINIMUM_MAX_HEADER_SIZE  1024
+#define EXT_EDITHEADER_DEFAULT_MAX_HEADER_SIZE  2048
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/editheader/ext-editheader.c
@@ -0,0 +1,66 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension debug
+ * ---------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5293
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+#include "array.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-address-parts.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-editheader-common.h"
+
+/*
+ * Operations
+ */
+
+const struct sieve_operation_def *editheader_operations[] = {
+	&addheader_operation,
+	&deleteheader_operation
+};
+
+/*
+ * Extension
+ */
+
+static bool ext_editheader_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *validator);
+
+const struct sieve_extension_def editheader_extension = {
+	.name = "editheader",
+	.load = ext_editheader_load,
+	.unload = ext_editheader_unload,
+	.validator_load = ext_editheader_validator_load,
+	SIEVE_EXT_DEFINE_OPERATIONS(editheader_operations)
+};
+
+static bool ext_editheader_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *validator)
+{
+	/* Register new commands */
+	sieve_validator_register_command(validator, ext, &addheader_command);
+	sieve_validator_register_command(validator, ext, &deleteheader_command);
+
+	return TRUE;
+}
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/enotify/Makefile.am
@@ -0,0 +1,44 @@
+SUBDIRS = mailto
+
+noinst_LTLIBRARIES = libsieve_ext_enotify.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../.. \
+	-I$(srcdir)/../variables \
+	$(LIBDOVECOT_INCLUDE)
+
+commands = \
+	cmd-notify.c
+
+tests = \
+	tst-valid-notify-method.c \
+	tst-notify-method-capability.c
+
+var_modifiers = \
+	vmodf-encodeurl.c
+
+notify_methods = \
+	./mailto/libsieve_ext_enotify_mailto.la
+
+libsieve_ext_enotify_la_DEPENDENCIES = \
+	$(notify_methods)
+libsieve_ext_enotify_la_LIBADD = \
+	$(notify_methods)
+
+libsieve_ext_enotify_la_SOURCES = \
+	ext-enotify.c \
+	ext-enotify-common.c \
+	$(commands) \
+	$(tests) \
+	$(var_modifiers)
+
+public_headers = \
+	sieve-ext-enotify.h
+
+headers = \
+	ext-enotify-limits.h \
+	ext-enotify-common.h
+
+pkginc_libdir=$(dovecot_pkgincludedir)/sieve
+pkginc_lib_HEADERS = $(public_headers)
+noinst_HEADERS = $(headers)
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/enotify/cmd-notify.c
@@ -0,0 +1,590 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-result.h"
+
+#include "ext-enotify-common.h"
+
+/*
+ * Forward declarations
+ */
+
+static const struct sieve_argument_def notify_importance_tag;
+static const struct sieve_argument_def notify_from_tag;
+static const struct sieve_argument_def notify_options_tag;
+static const struct sieve_argument_def notify_message_tag;
+
+/*
+ * Notify command
+ *
+ * Syntax:
+ *    notify [":from" string]
+ *           [":importance" <"1" / "2" / "3">]
+ *           [":options" string-list]
+ *           [":message" string]
+ *           <method: string>
+ */
+
+static bool cmd_notify_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool cmd_notify_pre_validate
+	(struct sieve_validator *validator, struct sieve_command *cmd);
+static bool cmd_notify_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_notify_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def notify_command = {
+	.identifier = "notify",
+	.type = SCT_COMMAND,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = cmd_notify_registered,
+	.pre_validate = cmd_notify_pre_validate,
+	.validate = cmd_notify_validate,
+	.generate = cmd_notify_generate,
+};
+
+/*
+ * Notify command tags
+ */
+
+/* Forward declarations */
+
+static bool cmd_notify_validate_string_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+static bool cmd_notify_validate_stringlist_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+static bool cmd_notify_validate_importance_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+
+/* Argument objects */
+
+static const struct sieve_argument_def notify_from_tag = {
+	.identifier = "from",
+	.validate = cmd_notify_validate_string_tag
+};
+
+static const struct sieve_argument_def notify_options_tag = {
+	.identifier = "options",
+	.validate = cmd_notify_validate_stringlist_tag
+};
+
+static const struct sieve_argument_def notify_message_tag = {
+	.identifier = "message",
+	.validate = cmd_notify_validate_string_tag
+};
+
+static const struct sieve_argument_def notify_importance_tag = {
+	.identifier = "importance",
+	.validate = cmd_notify_validate_importance_tag
+};
+
+/*
+ * Notify operation
+ */
+
+static bool cmd_notify_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_notify_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def notify_operation = {
+	.mnemonic = "NOTIFY",
+	.ext_def = &enotify_extension,
+	.code = EXT_ENOTIFY_OPERATION_NOTIFY,
+	.dump = cmd_notify_operation_dump,
+	.execute = cmd_notify_operation_execute
+};
+
+/*
+ * Notify action
+ */
+
+/* Forward declarations */
+
+static int act_notify_check_duplicate
+	(const struct sieve_runtime_env *renv,
+		const struct sieve_action *act,
+		const struct sieve_action *act_other);
+static void act_notify_print
+	(const struct sieve_action *action, const struct sieve_result_print_env *rpenv,
+		bool *keep);
+static int act_notify_commit
+	(const struct sieve_action *action,	const struct sieve_action_exec_env *aenv,
+		void *tr_context, bool *keep);
+
+/* Action object */
+
+const struct sieve_action_def act_notify = {
+	.name = "notify",
+	.check_duplicate =act_notify_check_duplicate,
+	.print = act_notify_print,
+	.commit = act_notify_commit,
+};
+
+/*
+ * Command validation context
+ */
+
+struct cmd_notify_context_data {
+	struct sieve_ast_argument *from;
+	struct sieve_ast_argument *message;
+	struct sieve_ast_argument *options;
+};
+
+/*
+ * Tag validation
+ */
+
+static bool cmd_notify_validate_string_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *tag = *arg;
+	struct cmd_notify_context_data *ctx_data =
+		(struct cmd_notify_context_data *) cmd->data;
+
+	/* Detach the tag itself */
+	*arg = sieve_ast_arguments_detach(*arg,1);
+
+	/* Check syntax:
+	 *   :from <string>
+	 *   :message <string>
+	 */
+	if ( !sieve_validate_tag_parameter
+		(valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING, FALSE) )
+		return FALSE;
+
+	if ( sieve_argument_is(tag, notify_from_tag) ) {
+		ctx_data->from = *arg;
+
+		/* Skip parameter */
+		*arg = sieve_ast_argument_next(*arg);
+
+	} else if ( sieve_argument_is(tag, notify_message_tag) ) {
+		ctx_data->message = *arg;
+
+		/* Skip parameter */
+		*arg = sieve_ast_argument_next(*arg);
+	}
+
+	return TRUE;
+}
+
+static bool cmd_notify_validate_stringlist_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *tag = *arg;
+	struct cmd_notify_context_data *ctx_data =
+		(struct cmd_notify_context_data *) cmd->data;
+
+	/* Detach the tag itself */
+	*arg = sieve_ast_arguments_detach(*arg,1);
+
+	/* Check syntax:
+	 *   :options string-list
+	 */
+	if ( !sieve_validate_tag_parameter
+		(valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING_LIST, FALSE) )
+		return FALSE;
+
+	/* Assign context */
+	ctx_data->options = *arg;
+
+	/* Skip parameter */
+	*arg = sieve_ast_argument_next(*arg);
+
+	return TRUE;
+}
+
+static bool cmd_notify_validate_importance_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd ATTR_UNUSED)
+{
+	const struct sieve_ast_argument *tag = *arg;
+	const char *impstr;
+
+	/* Detach the tag itself */
+	*arg = sieve_ast_arguments_detach(*arg,1);
+
+	/* Check syntax:
+	 *   :importance <"1" / "2" / "3">
+	 */
+
+	if ( sieve_ast_argument_type(*arg) != SAAT_STRING ) {
+		/* Not a string */
+		sieve_argument_validate_error(valdtr, *arg,
+			"the :importance tag for the notify command requires a string parameter, "
+			"but %s was found", sieve_ast_argument_name(*arg));
+		return FALSE;
+	}
+
+	impstr = sieve_ast_argument_strc(*arg);
+
+	if ( impstr[0] < '1' || impstr[0]  > '3' || impstr[1] != '\0' ) {
+		/* Invalid importance */
+		sieve_argument_validate_error(valdtr, *arg,
+			"invalid :importance value for notify command: %s", impstr);
+		return FALSE;
+	}
+
+	sieve_ast_argument_number_substitute(*arg, impstr[0] - '0');
+	(*arg)->argument = sieve_argument_create
+		((*arg)->ast, &number_argument, tag->argument->ext, tag->argument->id_code);
+
+	/* Skip parameter */
+	*arg = sieve_ast_argument_next(*arg);
+
+	return TRUE;
+}
+
+
+/*
+ * Command registration
+ */
+
+static bool cmd_notify_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg)
+{
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &notify_importance_tag, CMD_NOTIFY_OPT_IMPORTANCE);
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &notify_from_tag, CMD_NOTIFY_OPT_FROM);
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &notify_options_tag, CMD_NOTIFY_OPT_OPTIONS);
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &notify_message_tag, CMD_NOTIFY_OPT_MESSAGE);
+
+	return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+static bool cmd_notify_pre_validate
+(struct sieve_validator *validator ATTR_UNUSED,
+	struct sieve_command *cmd)
+{
+	struct cmd_notify_context_data *ctx_data;
+
+	/* Assign context */
+	ctx_data = p_new(sieve_command_pool(cmd),
+		struct cmd_notify_context_data, 1);
+	cmd->data = ctx_data;
+
+	return TRUE;
+}
+
+static bool cmd_notify_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *arg = cmd->first_positional;
+	struct cmd_notify_context_data *ctx_data =
+		(struct cmd_notify_context_data *) cmd->data;
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "method", 1, SAAT_STRING) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, cmd, arg, FALSE) )
+		return FALSE;
+
+	return ext_enotify_compile_check_arguments
+		(valdtr, cmd, arg, ctx_data->message, ctx_data->from, ctx_data->options);
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_notify_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	sieve_operation_emit(cgenv->sblock, cmd->ext, &notify_operation);
+
+	/* Generate arguments */
+	return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_notify_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	int opt_code = 0;
+
+	sieve_code_dumpf(denv, "NOTIFY");
+	sieve_code_descend(denv);
+
+	/* Dump optional operands */
+
+	for (;;) {
+		int opt;
+		bool opok = TRUE;
+
+		if ( (opt=sieve_opr_optional_dump(denv, address, &opt_code)) < 0 )
+			return FALSE;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case CMD_NOTIFY_OPT_IMPORTANCE:
+			opok = sieve_opr_number_dump(denv, address, "importance");
+			break;
+		case CMD_NOTIFY_OPT_FROM:
+			opok = sieve_opr_string_dump(denv, address, "from");
+			break;
+		case CMD_NOTIFY_OPT_OPTIONS:
+			opok = sieve_opr_stringlist_dump(denv, address, "options");
+			break;
+		case CMD_NOTIFY_OPT_MESSAGE:
+			opok = sieve_opr_string_dump(denv, address, "message");
+			break;
+		default:
+			return FALSE;
+		}
+
+		if ( !opok ) return FALSE;
+	}
+
+	/* Dump method operand */
+	return sieve_opr_string_dump(denv, address, "method");
+}
+
+/*
+ * Code execution
+ */
+
+static int cmd_notify_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	const struct sieve_extension *this_ext = renv->oprtn->ext;
+	struct sieve_side_effects_list *slist = NULL;
+	struct sieve_enotify_action *act;
+	void *method_context;
+	pool_t pool;
+	int opt_code = 0;
+	sieve_number_t importance = 2;
+	struct sieve_stringlist *options = NULL;
+	const struct sieve_enotify_method *method;
+	string_t *method_uri, *message = NULL, *from = NULL;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Optional operands */
+
+	for (;;) {
+		int opt;
+
+		if ( (opt=sieve_opr_optional_read(renv, address, &opt_code)) < 0 )
+			return SIEVE_EXEC_BIN_CORRUPT;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case CMD_NOTIFY_OPT_IMPORTANCE:
+			ret = sieve_opr_number_read(renv, address, "importance", &importance);
+			break;
+		case CMD_NOTIFY_OPT_FROM:
+			ret = sieve_opr_string_read(renv, address, "from", &from);
+			break;
+		case CMD_NOTIFY_OPT_MESSAGE:
+			ret = sieve_opr_string_read(renv, address, "message", &message);
+			break;
+		case CMD_NOTIFY_OPT_OPTIONS:
+			ret = sieve_opr_stringlist_read(renv, address, "options", &options);
+			break;
+		default:
+			sieve_runtime_trace_error(renv, "unknown optional operand");
+			return SIEVE_EXEC_BIN_CORRUPT;
+		}
+
+		if ( ret <= 0 ) return ret;
+	}
+
+	/* Method operand */
+
+	if ( (ret=sieve_opr_string_read(renv, address, "method", &method_uri)) <= 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	/* Enforce 0 < importance < 4 (just to be sure) */
+
+	if ( importance < 1 )
+		importance = 1;
+	else if ( importance > 3 )
+		importance = 3;
+
+	/* Trace */
+
+	if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_ACTIONS) ) {
+		sieve_runtime_trace(renv, 0, "notify action");
+		sieve_runtime_trace_descend(renv);
+		sieve_runtime_trace(renv, 0, "notify with uri `%s'",
+			str_sanitize(str_c(method_uri), 80));
+	}
+
+	/* Check operands */
+
+	if ( (ret=ext_enotify_runtime_check_operands
+		(renv, method_uri, message, from, options, &method,
+			&method_context)) > 0 )
+	{
+		/* Add notify action to the result */
+
+		pool = sieve_result_pool(renv->result);
+		act = p_new(pool, struct sieve_enotify_action, 1);
+		act->method = method;
+		act->method_context = method_context;
+		act->importance = importance;
+		if ( message != NULL )
+			act->message = p_strdup(pool, str_c(message));
+		if ( from != NULL )
+			act->from = p_strdup(pool, str_c(from));
+
+		if ( sieve_result_add_action
+			(renv, this_ext, &act_notify, slist, (void *) act, 0, FALSE) < 0 )
+			return SIEVE_EXEC_FAILURE;
+
+		return SIEVE_EXEC_OK;
+	}
+
+	return ret;
+}
+
+/*
+ * Action
+ */
+
+/* Runtime verification */
+
+static int act_notify_check_duplicate
+(const struct sieve_runtime_env *renv, const struct sieve_action *act,
+	const struct sieve_action *act_other)
+{
+	const struct sieve_enotify_action *nact, *nact_other;
+	const struct sieve_enotify_method_def *nmth_def;
+	struct sieve_enotify_env nenv;
+	int result;
+
+	if ( act->context == NULL || act_other->context == NULL )
+		return 0;
+
+	nact = (const struct sieve_enotify_action *) act->context;
+	nact_other = (const struct sieve_enotify_action *) act_other->context;
+
+	if ( nact->method == NULL || nact->method->def == NULL )
+		return 0;
+
+	nmth_def = nact->method->def;
+	if ( nmth_def->action_check_duplicates == NULL )
+		return 0;
+
+	i_zero(&nenv);
+	nenv.svinst = renv->svinst;
+	nenv.method = nact->method;
+	nenv.ehandler = sieve_prefix_ehandler_create
+		(renv->ehandler, act->location, "notify");
+
+	result = nmth_def->action_check_duplicates(&nenv, nact, nact_other);
+
+	sieve_error_handler_unref(&nenv.ehandler);
+
+	return result;
+}
+
+/* Result printing */
+
+static void act_notify_print
+(const struct sieve_action *action, const struct sieve_result_print_env *rpenv,
+	bool *keep ATTR_UNUSED)
+{
+	const struct sieve_enotify_action *act =
+		(const struct sieve_enotify_action *) action->context;
+	const struct sieve_enotify_method *method;
+
+	method = act->method;
+
+	if ( method->def != NULL ) {
+		sieve_result_action_printf
+			( rpenv, "send notification with method '%s:':", method->def->identifier);
+
+		if ( method->def->action_print != NULL ) {
+			struct sieve_enotify_print_env penv;
+
+			i_zero(&penv);
+			penv.result_penv = rpenv;
+
+			method->def->action_print(&penv, act);
+		}
+	}
+}
+
+/* Result execution */
+
+static int act_notify_commit
+(const struct sieve_action *action,	const struct sieve_action_exec_env *aenv,
+	void *tr_context ATTR_UNUSED, bool *keep ATTR_UNUSED)
+{
+	const struct sieve_enotify_action *act =
+		(const struct sieve_enotify_action *) action->context;
+	const struct sieve_enotify_method *method = act->method;
+	struct sieve_enotify_exec_env nenv;
+	int ret = 0;
+
+	if ( method->def != NULL && method->def->action_execute != NULL )	{
+		/* Compose log structure */
+		i_zero(&nenv);
+		nenv.svinst = aenv->svinst;
+		nenv.flags = aenv->flags;
+		nenv.method = method;
+		nenv.scriptenv = aenv->scriptenv;
+		nenv.msgdata = aenv->msgdata;
+		nenv.msgctx = aenv->msgctx;
+
+		nenv.ehandler = sieve_prefix_ehandler_create
+			(aenv->ehandler, NULL, "notify action");
+
+		ret = method->def->action_execute(&nenv, act);
+
+		sieve_error_handler_unref(&nenv.ehandler);
+	}
+
+	return ( ret >= 0 ? SIEVE_EXEC_OK : SIEVE_EXEC_TEMP_FAILURE );
+}
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/enotify/ext-enotify-common.c
@@ -0,0 +1,679 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-ast.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+
+#include "ext-enotify-limits.h"
+#include "ext-enotify-common.h"
+
+#include <ctype.h>
+
+/* FIXME: (from draft RFC)
+ *
+ * Header/envelope tests [Sieve] together with Sieve variables can be
+ * used to extract the list of users to receive notifications from the
+ * incoming email message or its envelope.  This is potentially quite
+ * dangerous, as this can be used for Deny Of Service attacks on
+ * recipients controlled by the message sender.  For this reason
+ * implementations SHOULD NOT allow use of variables containing values
+ * extracted from the email message in the method parameter to the
+ * notify action.  Note that violation of this SHOULD NOT may result in
+ * the creation of an open relay, i.e. any sender would be able to
+ * create specially crafted email messages that would result in
+ * notifications delivered to recipients under the control of the
+ * sender.  In worst case this might result in financial loss by user
+ * controlling the Sieve script and/or by recipients of notifications
+ * (e.g. if a notification is an SMS message).
+ *
+ * --> This is currently not possible to check.
+ */
+
+/*
+ * Notify capability
+ */
+
+static const char *ext_notify_get_methods_string
+	(const struct sieve_extension *ntfy_ext);
+
+const struct sieve_extension_capabilities notify_capabilities = {
+	"notify",
+	ext_notify_get_methods_string
+};
+
+/*
+ * Core notification methods
+ */
+
+extern const struct sieve_enotify_method_def mailto_notify;
+
+/*
+ * Notify method registry
+ */
+
+static const struct sieve_enotify_method *ext_enotify_method_register
+(struct sieve_instance *svinst, struct ext_enotify_context *ectx,
+	const struct sieve_enotify_method_def *nmth_def)
+{
+	struct sieve_enotify_method *nmth;
+	int nmth_id = (int) array_count(&ectx->notify_methods);
+
+	nmth = array_append_space(&ectx->notify_methods);
+	nmth->def = nmth_def;
+	nmth->id = nmth_id;
+	nmth->svinst = svinst;
+
+	if ( nmth_def->load != NULL )
+		nmth_def->load(nmth, &nmth->context);
+
+	return nmth;
+}
+
+void ext_enotify_methods_init
+(struct sieve_instance *svinst, struct ext_enotify_context *ectx)
+{
+	p_array_init(&ectx->notify_methods, default_pool, 4);
+
+	ext_enotify_method_register(svinst, ectx, &mailto_notify);
+}
+
+void ext_enotify_methods_deinit(struct ext_enotify_context *ectx)
+{
+	const struct sieve_enotify_method *methods;
+	unsigned int meth_count, i;
+
+	methods = array_get(&ectx->notify_methods, &meth_count);
+	for ( i = 0; i < meth_count; i++ ) {
+		if ( methods[i].def != NULL && methods[i].def->unload != NULL )
+			methods[i].def->unload(&methods[i]);
+	}
+
+	array_free(&ectx->notify_methods);
+}
+
+const struct sieve_enotify_method *sieve_enotify_method_register
+(struct sieve_instance *svinst,
+	const struct sieve_enotify_method_def *nmth_def)
+{
+	const struct sieve_extension *ntfy_ext =
+		sieve_extension_get_by_name(svinst, "enotify");
+
+	if ( ntfy_ext != NULL ) {
+		struct ext_enotify_context *ectx =
+			(struct ext_enotify_context *) ntfy_ext->context;
+
+		return ext_enotify_method_register(svinst, ectx, nmth_def);
+	}
+
+	return NULL;
+}
+
+void sieve_enotify_method_unregister
+(const struct sieve_enotify_method *nmth)
+{
+	struct sieve_instance *svinst = nmth->svinst;
+	const struct sieve_extension *ntfy_ext =
+		sieve_extension_get_by_name(svinst, "enotify");
+
+	if ( ntfy_ext != NULL ) {
+		struct ext_enotify_context *ectx =
+			(struct ext_enotify_context *) ntfy_ext->context;
+		int nmth_id = nmth->id;
+
+		if ( nmth_id >= 0 && nmth_id < (int)array_count(&ectx->notify_methods) ) {
+			struct sieve_enotify_method *nmth_mod =
+				array_idx_modifiable(&ectx->notify_methods, nmth_id);
+
+			nmth_mod->def = NULL;
+		}
+	}
+}
+
+const struct sieve_enotify_method *ext_enotify_method_find
+(const struct sieve_extension *ntfy_ext, const char *identifier)
+{
+	struct ext_enotify_context *ectx =
+		(struct ext_enotify_context *) ntfy_ext->context;
+	unsigned int meth_count, i;
+	const struct sieve_enotify_method *methods;
+
+	methods = array_get(&ectx->notify_methods, &meth_count);
+
+	for ( i = 0; i < meth_count; i++ ) {
+		if ( methods[i].def == NULL ) continue;
+
+		if ( strcasecmp(methods[i].def->identifier, identifier) == 0 ) {
+			return &methods[i];
+		}
+	}
+
+	return NULL;
+}
+
+static const char *ext_notify_get_methods_string
+(const struct sieve_extension *ntfy_ext)
+{
+	struct ext_enotify_context *ectx =
+		(struct ext_enotify_context *) ntfy_ext->context;
+	unsigned int meth_count, i;
+	const struct sieve_enotify_method *methods;
+	string_t *result = t_str_new(128);
+
+	methods = array_get(&ectx->notify_methods, &meth_count);
+
+	if ( meth_count > 0 ) {
+		for ( i = 0; i < meth_count; i++ ) {
+			if ( str_len(result) > 0 )
+				str_append_c(result, ' ');
+
+			if ( methods[i].def != NULL )
+				str_append(result, methods[i].def->identifier);
+		}
+
+		return str_c(result);
+	}
+
+	return NULL;
+}
+
+/*
+ * Compile-time argument validation
+ */
+
+static const char *ext_enotify_uri_scheme_parse(const char **uri_p)
+{
+	string_t *scheme = t_str_new(EXT_ENOTIFY_MAX_SCHEME_LEN);
+	const char *p = *uri_p;
+	unsigned int len = 0;
+
+	/* RFC 3968:
+	 *
+	 *   scheme  = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
+	 *
+	 * FIXME: we do not allow '%' in schemes. Is this correct?
+	 */
+
+	if ( !i_isalpha(*p) )
+		return NULL;
+
+	str_append_c(scheme, *p);
+	p++;
+
+	while ( *p != '\0' && len < EXT_ENOTIFY_MAX_SCHEME_LEN ) {
+
+		if ( !i_isalnum(*p) && *p != '+' && *p != '-' && *p != '.' )
+			break;
+
+		str_append_c(scheme, *p);
+		p++;
+		len++;
+	}
+
+	if ( *p != ':' )
+		return NULL;
+	p++;
+
+	*uri_p = p;
+	return str_c(scheme);
+}
+
+static bool ext_enotify_option_parse
+(struct sieve_enotify_env *nenv, const char *option, bool name_only,
+	const char **opt_name_r, const char **opt_value_r)
+{
+	const char *p = option;
+
+	/* "<optionname>=<value>".
+	 *
+	 * l-d = ALPHA / DIGIT
+	 * l-d-p = l-d / "." / "-" / "_"
+	 * optionname = l-d *l-d-p
+	 * value = *(%x01-09 / %x0B-0C / %x0E-FF)
+	 */
+
+	/*
+	 * Parse option name
+	 *
+	 * optionname = l-d *l-d-p
+	 */
+
+	/* Explicitly report empty option as such */
+	if ( *p == '\0' ) {
+		sieve_enotify_error(nenv, "empty option specified");
+		return FALSE;
+	}
+
+	/* l-d = ALPHA / DIGIT */
+	if ( i_isalnum(*p) ) {
+		p++;
+
+		/* l-d-p = l-d / "." / "-" / "_" */
+		while ( i_isalnum(*p) || *p == '.' || *p == '-' || *p == '_' )
+			p++;
+	}
+
+	/* Parsing must end at '=' and we must parse at least one character */
+	if ( *p != '=' || p == option ) {
+		sieve_enotify_error(nenv, "invalid option name specified in option '%s'",
+				str_sanitize(option, 80));
+		return FALSE;
+	}
+
+	/* Assign option name */
+	if ( opt_name_r != NULL )
+		*opt_name_r = t_strdup_until(option, p);
+
+	/* Skip '=' */
+	p++;
+
+	/* Exit now if only the option name is of interest */
+	if ( name_only )
+		return TRUE;
+
+	/*
+	 * Parse option value
+	 */
+
+	/* value = *(%x01-09 / %x0B-0C / %x0E-FF) */
+	while ( *p != '\0' && *p != 0x0A && *p != 0x0D )
+		p++;
+
+	/* Parse must end at end of string */
+	if ( *p != '\0' ) {
+		sieve_enotify_error(nenv,
+			"notify command: invalid option value specified in option '%s'",
+				str_sanitize(option, 80));
+		return FALSE;
+	}
+
+	/* Assign option value */
+	if ( opt_value_r != NULL )
+		*opt_value_r = p;
+
+	return TRUE;
+}
+
+struct _ext_enotify_option_check_context {
+	struct sieve_instance *svinst;
+	struct sieve_validator *valdtr;
+	const struct sieve_enotify_method *method;
+};
+
+static int _ext_enotify_option_check
+(void *context, struct sieve_ast_argument *arg)
+{
+	struct _ext_enotify_option_check_context *optn_context =
+		(struct _ext_enotify_option_check_context *) context;
+	struct sieve_validator *valdtr = optn_context->valdtr;
+	const struct sieve_enotify_method *method = optn_context->method;
+	struct sieve_enotify_env nenv;
+	const char *option = sieve_ast_argument_strc(arg);
+	const char *opt_name = NULL, *opt_value = NULL;
+	bool check = TRUE;
+	int result = 1;
+
+	/* Compose log structure */
+	i_zero(&nenv);
+	nenv.svinst = optn_context->svinst;
+	nenv.method = method;
+	nenv.ehandler = sieve_prefix_ehandler_create
+		(sieve_validator_error_handler(valdtr),
+			sieve_error_script_location
+				(sieve_validator_script(valdtr), arg->source_line),
+			"notify command");
+
+	/* Parse option */
+	if ( !sieve_argument_is_string_literal(arg) ) {
+		/* Variable string: partial option parse
+		 *
+		 * If the string item is not a string literal, it cannot be validated fully
+		 * at compile time. We can however check whether the '=' is in the string
+		 * specification and whether the part before the '=' is a valid option name.
+		 * In that case, the method option check function is called with the value
+		 * parameter equal to NULL, meaning that it should only check the validity
+		 * of the option itself and not the assigned value.
+		 */
+		if ( !ext_enotify_option_parse(NULL, option, TRUE, &opt_name, &opt_value) )
+			check = FALSE;
+	} else {
+		/* Literal string: full option parse */
+		if ( !ext_enotify_option_parse
+			(&nenv, option, FALSE, &opt_name, &opt_value) )
+			result = -1;
+	}
+
+	/* Call method's option check function */
+	if ( result > 0 && check && method->def != NULL &&
+		method->def->compile_check_option != NULL ) {
+		result = ( method->def->compile_check_option
+			(&nenv, opt_name, opt_value) ? 1 : -1 );
+	}
+
+	sieve_error_handler_unref(&nenv.ehandler);
+
+	return result;
+}
+
+bool ext_enotify_compile_check_arguments
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+	struct sieve_ast_argument *uri_arg, struct sieve_ast_argument *msg_arg,
+	struct sieve_ast_argument *from_arg, struct sieve_ast_argument *options_arg)
+{
+	const struct sieve_extension *this_ext = cmd->ext;
+	struct sieve_instance *svinst = this_ext->svinst;
+	const char *uri = sieve_ast_argument_strc(uri_arg);
+	const char *scheme;
+	const struct sieve_enotify_method *method;
+	struct sieve_enotify_env nenv;
+	bool result = TRUE;
+
+	/* If the uri string is not a constant literal, we cannot determine which
+	 * method is used, so we bail out successfully and defer checking to runtime.
+	 */
+	if ( !sieve_argument_is_string_literal(uri_arg) )
+		return TRUE;
+
+	/* Parse scheme part of URI */
+	if ( (scheme=ext_enotify_uri_scheme_parse(&uri)) == NULL ) {
+		sieve_argument_validate_error(valdtr, uri_arg,
+			"notify command: invalid scheme part for method URI '%s'",
+			str_sanitize(sieve_ast_argument_strc(uri_arg), 80));
+		return FALSE;
+	}
+
+	/* Find used method with the parsed scheme identifier */
+	if ( (method=ext_enotify_method_find(this_ext, scheme)) == NULL ) {
+		sieve_argument_validate_error(valdtr, uri_arg,
+			"notify command: invalid method '%s'", scheme);
+		return FALSE;
+	}
+
+	if ( method->def == NULL ) return TRUE;
+
+	/* Compose log structure */
+	i_zero(&nenv);
+	nenv.svinst = svinst;
+	nenv.method = method;
+
+	/* Check URI itself */
+	if ( result && method->def->compile_check_uri != NULL ) {
+		/* Set log location to location of URI argument */
+		nenv.ehandler = sieve_prefix_ehandler_create
+		(sieve_validator_error_handler(valdtr),
+			sieve_error_script_location
+				(sieve_validator_script(valdtr), uri_arg->source_line),
+			"notify command");
+
+		/* Execute method check function */
+		result = method->def->compile_check_uri
+			(&nenv, sieve_ast_argument_strc(uri_arg), uri);
+	}
+
+	/* Check :message argument */
+	if ( result && msg_arg != NULL && sieve_argument_is_string_literal(msg_arg)
+		&& method->def->compile_check_message != NULL ) {
+		/* Set log location to location of :message argument */
+		sieve_error_handler_unref(&nenv.ehandler);
+		nenv.ehandler = sieve_prefix_ehandler_create
+		(sieve_validator_error_handler(valdtr),
+			sieve_error_script_location
+				(sieve_validator_script(valdtr), msg_arg->source_line),
+			"notify command");
+
+		/* Execute method check function */
+		result = method->def->compile_check_message
+			(&nenv, sieve_ast_argument_str(msg_arg));
+	}
+
+	/* Check :from argument */
+	if ( result && from_arg != NULL && sieve_argument_is_string_literal(from_arg)
+		&& method->def->compile_check_from != NULL ) {
+		/* Set log location to location of :from argument */
+		sieve_error_handler_unref(&nenv.ehandler);
+		nenv.ehandler = sieve_prefix_ehandler_create
+		(sieve_validator_error_handler(valdtr),
+			sieve_error_script_location
+				(sieve_validator_script(valdtr), from_arg->source_line),
+				"notify command");
+
+		/* Execute method check function */
+		result = method->def->compile_check_from
+			(&nenv, sieve_ast_argument_str(from_arg));
+	}
+
+	sieve_error_handler_unref(&nenv.ehandler);
+
+	/* Check :options argument */
+	if ( result && options_arg != NULL ) {
+		struct sieve_ast_argument *option = options_arg;
+		struct _ext_enotify_option_check_context optn_context =
+			{ svinst, valdtr, method };
+
+		/* Parse and check options */
+		result = ( sieve_ast_stringlist_map
+			(&option, (void *) &optn_context, _ext_enotify_option_check) > 0 );
+
+		/* Discard argument if options are not accepted by method */
+		if ( result && method->def->compile_check_option == NULL ) {
+			sieve_argument_validate_warning(valdtr, options_arg,
+				"notify command: method '%s' accepts no options", scheme);
+			(void)sieve_ast_arguments_detach(options_arg,1);
+		}
+	}
+
+	return result;
+}
+
+/*
+ * Runtime operand checking
+ */
+
+bool ext_enotify_runtime_method_validate
+(const struct sieve_runtime_env *renv, string_t *method_uri)
+{
+	const struct sieve_extension *this_ext = renv->oprtn->ext;
+	const struct sieve_enotify_method *method;
+	const char *uri = str_c(method_uri);
+	const char *scheme;
+	bool result = TRUE;
+
+	/* Get the method */
+
+	if ( (scheme=ext_enotify_uri_scheme_parse(&uri)) == NULL )
+		return FALSE;
+
+	if ( (method=ext_enotify_method_find(this_ext, scheme)) == NULL )
+		return FALSE;
+
+	/* Validate the provided URI */
+
+	if ( method->def != NULL && method->def->runtime_check_uri != NULL ) {
+		struct sieve_enotify_env nenv;
+
+		i_zero(&nenv);
+		nenv.svinst = renv->svinst;
+		nenv.method = method;
+		nenv.ehandler = sieve_prefix_ehandler_create
+			(renv->ehandler, sieve_runtime_get_full_command_location(renv),
+				"valid_notify_method test");
+
+		/* Use the method check function to validate the URI */
+		result = method->def->runtime_check_uri(&nenv, str_c(method_uri), uri);
+
+		sieve_error_handler_unref(&nenv.ehandler);
+	}
+
+	return result;
+}
+
+static const struct sieve_enotify_method *ext_enotify_get_method
+(const struct sieve_runtime_env *renv, string_t *method_uri,
+	const char **uri_body_r)
+{
+	const struct sieve_extension *this_ext = renv->oprtn->ext;
+	const struct sieve_enotify_method *method;
+	const char *uri = str_c(method_uri);
+	const char *scheme;
+
+	/* Parse part before ':' of the uri (the scheme) and use it to identify
+	 * notify method.
+	 */
+	if ( (scheme=ext_enotify_uri_scheme_parse(&uri)) == NULL ) {
+		sieve_runtime_error(renv, NULL,
+			"invalid scheme part for method URI '%s'",
+			str_sanitize(str_c(method_uri), 80));
+		return NULL;
+	}
+
+	/* Find the notify method */
+	if ( (method=ext_enotify_method_find(this_ext, scheme)) == NULL ) {
+		sieve_runtime_error(renv, NULL,
+			"invalid notify method '%s'", scheme);
+		return NULL;
+	}
+
+	/* Return the parse pointer and the found method */
+	*uri_body_r = uri;
+	return method;
+}
+
+const char *ext_enotify_runtime_get_method_capability
+(const struct sieve_runtime_env *renv,
+	string_t *method_uri, const char *capability)
+{
+	const struct sieve_enotify_method *method;
+	const char *uri_body;
+	const char *result = NULL;
+
+	/* Get method */
+	method = ext_enotify_get_method(renv, method_uri, &uri_body);
+	if ( method == NULL ) return NULL;
+
+	/* Get requested capability */
+	if ( method->def != NULL &&
+		method->def->runtime_get_method_capability != NULL ) {
+		struct sieve_enotify_env nenv;
+
+		i_zero(&nenv);
+		nenv.svinst = renv->svinst;
+		nenv.method = method;
+		nenv.ehandler = sieve_prefix_ehandler_create
+			(renv->ehandler, sieve_runtime_get_full_command_location(renv),
+				"notify_method_capability test");
+
+		/* Execute method function to acquire capability value */
+		result = method->def->runtime_get_method_capability
+			(&nenv, str_c(method_uri), uri_body, capability);
+		sieve_error_handler_unref(&nenv.ehandler);
+	}
+
+	return result;
+}
+
+int ext_enotify_runtime_check_operands
+(const struct sieve_runtime_env *renv,
+	string_t *method_uri, string_t *message, string_t *from,
+	struct sieve_stringlist *options,
+	const struct sieve_enotify_method **method_r, void **method_context)
+{
+	const struct sieve_enotify_method *method;
+	const char *uri_body;
+
+	/* Get method */
+	method = ext_enotify_get_method(renv, method_uri, &uri_body);
+	if ( method == NULL ) return SIEVE_EXEC_FAILURE;
+
+	/* Check provided operands */
+	if ( method->def != NULL && method->def->runtime_check_operands != NULL ) {
+		struct sieve_enotify_env nenv;
+		int result = SIEVE_EXEC_OK;
+
+		i_zero(&nenv);
+		nenv.svinst = renv->svinst;
+		nenv.method = method;
+		nenv.ehandler = sieve_prefix_ehandler_create
+			(renv->ehandler, sieve_runtime_get_full_command_location(renv),
+				"notify action");
+
+		/* Execute check function */
+		if ( method->def->runtime_check_operands
+			(&nenv, str_c(method_uri), uri_body, message, from,
+				sieve_result_pool(renv->result), method_context) ) {
+
+			/* Check any provided options */
+			if ( options != NULL ) {
+				string_t *option = NULL;
+				int ret;
+
+				/* Iterate through all provided options */
+				while ( (ret=sieve_stringlist_next_item(options, &option)) > 0 ) {
+					const char *opt_name = NULL, *opt_value = NULL;
+
+					/* Parse option into <optionname> and <value> */
+					if ( ext_enotify_option_parse
+						(&nenv, str_c(option), FALSE, &opt_name, &opt_value) ) {
+
+						/* Set option */
+						if ( method->def->runtime_set_option != NULL ) {
+							(void) method->def->runtime_set_option
+								(&nenv, *method_context, opt_name, opt_value);
+						}
+					}
+				}
+
+				/* Check for binary corruptions encountered during string list iteration
+				 */
+				if ( ret >= 0 ) {
+					*method_r = method;
+				} else {
+					/* Binary corrupt */
+					sieve_runtime_trace_error
+						(renv, "invalid item in options string list");
+					result = SIEVE_EXEC_BIN_CORRUPT;
+				}
+
+			} else {
+				/* No options */
+				*method_r = method;
+			}
+
+		} else {
+			/* Operand check failed */
+			result = SIEVE_EXEC_FAILURE;
+		}
+
+		sieve_error_handler_unref(&nenv.ehandler);
+		return result;
+	}
+
+	/* No check function defined: a most unlikely situation */
+	*method_context = NULL;
+	*method_r = method;
+	return SIEVE_EXEC_OK;
+}
+
+/*
+ * Notify method printing
+ */
+
+void sieve_enotify_method_printf
+(const struct sieve_enotify_print_env *penv, const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	sieve_result_vprintf(penv->result_penv, fmt, args);
+	va_end(args);
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/enotify/ext-enotify-common.h
@@ -0,0 +1,123 @@
+#ifndef EXT_ENOTIFY_COMMON_H
+#define EXT_ENOTIFY_COMMON_H
+
+#include "lib.h"
+#include "array.h"
+
+#include "sieve-common.h"
+
+#include "sieve-ext-variables.h"
+
+#include "sieve-ext-enotify.h"
+
+/*
+ * Extension
+ */
+
+extern const struct sieve_extension_def enotify_extension;
+extern const struct sieve_extension_capabilities notify_capabilities;
+
+struct ext_enotify_context {
+	const struct sieve_extension *var_ext;
+	ARRAY(struct sieve_enotify_method) notify_methods;
+};
+
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def notify_command;
+
+/* Codes for optional arguments */
+
+enum cmd_notify_optional {
+    CMD_NOTIFY_OPT_END,
+    CMD_NOTIFY_OPT_FROM,
+    CMD_NOTIFY_OPT_OPTIONS,
+    CMD_NOTIFY_OPT_MESSAGE,
+    CMD_NOTIFY_OPT_IMPORTANCE
+};
+
+/*
+ * Tests
+ */
+
+extern const struct sieve_command_def valid_notify_method_test;
+extern const struct sieve_command_def notify_method_capability_test;
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def notify_operation;
+extern const struct sieve_operation_def valid_notify_method_operation;
+extern const struct sieve_operation_def notify_method_capability_operation;
+
+enum ext_variables_opcode {
+	EXT_ENOTIFY_OPERATION_NOTIFY,
+	EXT_ENOTIFY_OPERATION_VALID_NOTIFY_METHOD,
+	EXT_ENOTIFY_OPERATION_NOTIFY_METHOD_CAPABILITY
+};
+
+/*
+ * Operands
+ */
+
+extern const struct sieve_operand_def encodeurl_operand;
+
+/*
+ * Modifiers
+ */
+
+extern const struct sieve_variables_modifier_def encodeurl_modifier;
+
+/*
+ * Notify methods
+ */
+
+void ext_enotify_methods_init
+	(struct sieve_instance *svinst, struct ext_enotify_context *ectx);
+void ext_enotify_methods_deinit
+	(struct ext_enotify_context *ectx);
+
+const struct sieve_enotify_method *ext_enotify_method_find
+	(const struct sieve_extension *ntfy_ext, const char *identifier);
+
+/*
+ * Validation
+ */
+
+bool ext_enotify_compile_check_arguments
+	(struct sieve_validator *valdtr, struct sieve_command *cmd,
+		struct sieve_ast_argument *uri_arg, struct sieve_ast_argument *msg_arg,
+		struct sieve_ast_argument *from_arg,
+		struct sieve_ast_argument *options_arg);
+
+/*
+ * Runtime
+ */
+
+bool ext_enotify_runtime_method_validate
+	(const struct sieve_runtime_env *renv,
+		string_t *method_uri);
+
+const char *ext_enotify_runtime_get_method_capability
+	(const struct sieve_runtime_env *renv,
+		string_t *method_uri, const char *capability);
+
+int ext_enotify_runtime_check_operands
+	(const struct sieve_runtime_env *renv,
+		string_t *method_uri, string_t *message, string_t *from,
+		struct sieve_stringlist *options,
+		const struct sieve_enotify_method **method_r, void **method_context);
+
+/*
+ * Method printing
+ */
+
+struct sieve_enotify_print_env {
+	const struct sieve_result_print_env *result_penv;
+};
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/enotify/ext-enotify-limits.h
@@ -0,0 +1,6 @@
+#ifndef EXT_ENOTIFY_LIMITS_H
+#define EXT_ENOTIFY_LIMITS_H
+
+#define EXT_ENOTIFY_MAX_SCHEME_LEN  32
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/enotify/ext-enotify.c
@@ -0,0 +1,103 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension enotify
+ * ------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5435
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-actions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+
+#include "sieve-ext-variables.h"
+
+#include "ext-enotify-common.h"
+
+/*
+ * Operations
+ */
+
+const struct sieve_operation_def *ext_enotify_operations[] = {
+	&notify_operation,
+	&valid_notify_method_operation,
+	&notify_method_capability_operation
+};
+
+/*
+ * Extension
+ */
+
+static bool ext_enotify_load(const struct sieve_extension *ext, void **context);
+static void ext_enotify_unload(const struct sieve_extension *ext);
+static bool ext_enotify_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def enotify_extension = {
+	.name = "enotify",
+	.load = ext_enotify_load,
+	.unload = ext_enotify_unload,
+	.validator_load = ext_enotify_validator_load,
+	SIEVE_EXT_DEFINE_OPERATIONS(ext_enotify_operations),
+	SIEVE_EXT_DEFINE_OPERAND(encodeurl_operand)
+};
+
+static bool ext_enotify_load(const struct sieve_extension *ext, void **context)
+{
+	struct ext_enotify_context *ectx;
+
+	if ( *context != NULL ) {
+		ext_enotify_unload(ext);
+	}
+
+	ectx = i_new(struct ext_enotify_context, 1);
+	ectx->var_ext = sieve_ext_variables_get_extension(ext->svinst);
+	*context = (void *) ectx;
+
+	ext_enotify_methods_init(ext->svinst, ectx);
+
+	sieve_extension_capabilities_register(ext, &notify_capabilities);
+
+	return TRUE;
+}
+
+static void ext_enotify_unload(const struct sieve_extension *ext)
+{
+	struct ext_enotify_context *ectx =
+		(struct ext_enotify_context *) ext->context;
+
+	ext_enotify_methods_deinit(ectx);
+
+	i_free(ectx);
+}
+
+static bool ext_enotify_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	struct ext_enotify_context *ectx =
+		(struct ext_enotify_context *) ext->context;
+
+	/* Register new commands */
+	sieve_validator_register_command(valdtr, ext, &notify_command);
+	sieve_validator_register_command(valdtr, ext, &valid_notify_method_test);
+	sieve_validator_register_command(valdtr, ext, &notify_method_capability_test);
+
+	/* Register new set modifier for variables extension */
+	sieve_variables_modifier_register
+		(ectx->var_ext, valdtr, ext, &encodeurl_modifier);
+
+	return TRUE;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/enotify/mailto/Makefile.am
@@ -0,0 +1,16 @@
+noinst_LTLIBRARIES = libsieve_ext_enotify_mailto.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/.. \
+	-I$(srcdir)/../../.. \
+	-I$(srcdir)/../../../util \
+	$(LIBDOVECOT_INCLUDE)
+
+libsieve_ext_enotify_mailto_la_SOURCES = \
+	uri-mailto.c \
+	ntfy-mailto.c
+
+noinst_HEADERS = \
+	uri-mailto.h
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/enotify/mailto/ntfy-mailto.c
@@ -0,0 +1,725 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Notify method mailto
+ * --------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5436
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+/* FIXME: URI syntax conforms to something somewhere in between RFC 2368 and
+ *   draft-duerst-mailto-bis-05.txt. Should fully migrate to new specification
+ *   when it matures. This requires modifications to the address parser (no
+ *   whitespace allowed within the address itself) and UTF-8 support will be
+ *   required in the URL.
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "ioloop.h"
+#include "str-sanitize.h"
+#include "ostream.h"
+#include "message-date.h"
+#include "mail-storage.h"
+
+#include "sieve-common.h"
+#include "sieve-address.h"
+#include "sieve-address-source.h"
+#include "sieve-message.h"
+#include "sieve-smtp.h"
+#include "sieve-settings.h"
+
+#include "sieve-ext-enotify.h"
+
+#include "rfc2822.h"
+
+#include "uri-mailto.h"
+
+/*
+ * Configuration
+ */
+
+#define NTFY_MAILTO_MAX_RECIPIENTS  8
+#define NTFY_MAILTO_MAX_HEADERS     16
+#define NTFY_MAILTO_MAX_SUBJECT     256
+
+/*
+ * Mailto notification configuration
+ */
+
+struct ntfy_mailto_config {
+	pool_t pool;
+	struct sieve_address_source envelope_from;
+};
+
+/*
+ * Mailto notification method
+ */
+
+static bool ntfy_mailto_load
+	(const struct sieve_enotify_method *nmth, void **context);
+static void ntfy_mailto_unload
+	(const struct sieve_enotify_method *nmth);
+
+static bool ntfy_mailto_compile_check_uri
+	(const struct sieve_enotify_env *nenv, const char *uri, const char *uri_body);
+static bool ntfy_mailto_compile_check_from
+	(const struct sieve_enotify_env *nenv, string_t *from);
+
+static const char *ntfy_mailto_runtime_get_notify_capability
+	(const struct sieve_enotify_env *nenv, const char *uri, const char *uri_body,
+		const char *capability);
+static bool ntfy_mailto_runtime_check_uri
+	(const struct sieve_enotify_env *nenv, const char *uri, const char *uri_body);
+static bool ntfy_mailto_runtime_check_operands
+	(const struct sieve_enotify_env *nenv, const char *uri,const char *uri_body,
+		string_t *message, string_t *from, pool_t context_pool,
+		void **method_context);
+
+static int ntfy_mailto_action_check_duplicates
+	(const struct sieve_enotify_env *nenv,
+		const struct sieve_enotify_action *nact,
+		const struct sieve_enotify_action *nact_other);
+
+static void ntfy_mailto_action_print
+	(const struct sieve_enotify_print_env *penv,
+		const struct sieve_enotify_action *nact);
+
+static int ntfy_mailto_action_execute
+	(const struct sieve_enotify_exec_env *nenv,
+		const struct sieve_enotify_action *nact);
+
+const struct sieve_enotify_method_def mailto_notify = {
+	"mailto",
+	ntfy_mailto_load,
+	ntfy_mailto_unload,
+	ntfy_mailto_compile_check_uri,
+	NULL,
+	ntfy_mailto_compile_check_from,
+	NULL,
+	ntfy_mailto_runtime_check_uri,
+	ntfy_mailto_runtime_get_notify_capability,
+	ntfy_mailto_runtime_check_operands,
+	NULL,
+	ntfy_mailto_action_check_duplicates,
+	ntfy_mailto_action_print,
+	ntfy_mailto_action_execute
+};
+
+/*
+ * Reserved and unique headers
+ */
+
+static const char *_reserved_headers[] = {
+	"auto-submitted",
+	"received",
+	"message-id",
+	"data",
+	"bcc",
+	"in-reply-to",
+	"references",
+	"resent-date",
+	"resent-from",
+	"resent-sender",
+	"resent-to",
+	"resent-cc",
+ 	"resent-bcc",
+	"resent-msg-id",
+	"from",
+	"sender",
+	NULL
+};
+
+static const char *_unique_headers[] = {
+	"reply-to",
+	NULL
+};
+
+/*
+ * Method context data
+ */
+
+struct ntfy_mailto_context {
+	struct uri_mailto *uri;
+	const struct smtp_address *from_address;
+};
+
+/*
+ * Method registration
+ */
+
+static bool ntfy_mailto_load
+(const struct sieve_enotify_method *nmth, void **context)
+{
+	struct sieve_instance *svinst = nmth->svinst;
+	struct ntfy_mailto_config *config;
+	pool_t pool;
+
+	if ( *context != NULL ) {
+		ntfy_mailto_unload(nmth);
+	}
+
+	pool = pool_alloconly_create("ntfy_mailto_config", 256);
+	config = p_new(pool, struct ntfy_mailto_config, 1);
+	config->pool = pool;
+
+	(void)sieve_address_source_parse_from_setting	(svinst,
+		config->pool, "sieve_notify_mailto_envelope_from",
+		&config->envelope_from);
+
+	*context = (void *) config;
+
+	return TRUE;
+}
+
+static void ntfy_mailto_unload
+(const struct sieve_enotify_method *nmth)
+{
+	struct ntfy_mailto_config *config =
+		(struct ntfy_mailto_config *) nmth->context;
+
+	pool_unref(&config->pool);
+}
+
+/*
+ * Validation
+ */
+
+static bool ntfy_mailto_compile_check_uri
+(const struct sieve_enotify_env *nenv, const char *uri ATTR_UNUSED,
+	const char *uri_body)
+{
+	return uri_mailto_validate
+		(uri_body, _reserved_headers, _unique_headers,
+			NTFY_MAILTO_MAX_RECIPIENTS, NTFY_MAILTO_MAX_HEADERS, nenv->ehandler);
+}
+
+static bool ntfy_mailto_compile_check_from
+(const struct sieve_enotify_env *nenv, string_t *from)
+{
+	const char *error;
+	bool result = FALSE;
+
+	T_BEGIN {
+		result = sieve_address_validate_str(from, &error);
+		if ( !result ) {
+			sieve_enotify_error(nenv,
+				"specified :from address '%s' is invalid for "
+				"the mailto method: %s",
+				str_sanitize(str_c(from), 128), error);
+		}
+	} T_END;
+
+	return result;
+}
+
+/*
+ * Runtime
+ */
+
+static const char *ntfy_mailto_runtime_get_notify_capability
+(const struct sieve_enotify_env *nenv ATTR_UNUSED, const char *uri ATTR_UNUSED,
+	const char *uri_body, const char *capability)
+{
+	if ( !uri_mailto_validate(uri_body, _reserved_headers, _unique_headers,
+			NTFY_MAILTO_MAX_RECIPIENTS, NTFY_MAILTO_MAX_HEADERS, NULL) ) {
+		return NULL;
+	}
+
+	if ( strcasecmp(capability, "online") == 0 )
+		return "maybe";
+
+	return NULL;
+}
+
+static bool ntfy_mailto_runtime_check_uri
+(const struct sieve_enotify_env *nenv ATTR_UNUSED, const char *uri ATTR_UNUSED,
+	const char *uri_body)
+{
+	return uri_mailto_validate
+		(uri_body, _reserved_headers, _unique_headers,
+			NTFY_MAILTO_MAX_RECIPIENTS, NTFY_MAILTO_MAX_HEADERS, NULL);
+}
+
+static bool ntfy_mailto_runtime_check_operands
+(const struct sieve_enotify_env *nenv, const char *uri ATTR_UNUSED,
+	const char *uri_body, string_t *message ATTR_UNUSED, string_t *from,
+	pool_t context_pool, void **method_context)
+{
+	struct ntfy_mailto_context *mtctx;
+	struct uri_mailto *parsed_uri;
+	const struct smtp_address *address;
+	const char *error;
+
+	/* Need to create context before validation to have arrays present */
+	mtctx = p_new(context_pool, struct ntfy_mailto_context, 1);
+
+	/* Validate :from */
+	if ( from != NULL ) {
+		T_BEGIN {
+			address = sieve_address_parse_str(from, &error);
+			if ( address == NULL ) {
+				sieve_enotify_error(nenv,
+					"specified :from address '%s' is invalid for "
+					"the mailto method: %s",
+					str_sanitize(str_c(from), 128), error);
+			} else
+				mtctx->from_address =
+					smtp_address_clone(context_pool, address);
+		} T_END;
+
+		if ( address == NULL ) return FALSE;
+	}
+
+	if ( (parsed_uri=uri_mailto_parse
+		(uri_body, context_pool, _reserved_headers,
+			_unique_headers, NTFY_MAILTO_MAX_RECIPIENTS, NTFY_MAILTO_MAX_HEADERS,
+			nenv->ehandler)) == NULL ) {
+		return FALSE;
+	}
+
+	mtctx->uri = parsed_uri;
+	*method_context = (void *) mtctx;
+	return TRUE;
+}
+
+/*
+ * Action duplicates
+ */
+
+static int ntfy_mailto_action_check_duplicates
+(const struct sieve_enotify_env *nenv ATTR_UNUSED,
+	const struct sieve_enotify_action *nact,
+	const struct sieve_enotify_action *nact_other)
+{
+	struct ntfy_mailto_context *mtctx =
+		(struct ntfy_mailto_context *) nact->method_context;
+	struct ntfy_mailto_context *mtctx_other =
+		(struct ntfy_mailto_context *) nact_other->method_context;
+	const struct uri_mailto_recipient *new_rcpts, *old_rcpts;
+	unsigned int new_count, old_count, i, j;
+	unsigned int del_start = 0, del_len = 0;
+
+	new_rcpts = array_get(&mtctx->uri->recipients, &new_count);
+	old_rcpts = array_get(&mtctx_other->uri->recipients, &old_count);
+
+	for ( i = 0; i < new_count; i++ ) {
+		for ( j = 0; j < old_count; j++ ) {
+			if ( smtp_address_equals
+				(new_rcpts[i].address, old_rcpts[j].address) )
+				break;
+		}
+
+		if ( j == old_count ) {
+			/* Not duplicate */
+			if ( del_len > 0 ) {
+				/* Perform pending deletion */
+				array_delete(&mtctx->uri->recipients, del_start, del_len);
+
+				/* Make sure the loop integrity is maintained */
+				i -= del_len;
+				new_rcpts = array_get(&mtctx->uri->recipients, &new_count);
+			}
+			del_len = 0;
+		} else {
+			/* Mark deletion */
+			if ( del_len == 0 )
+				del_start = i;
+			del_len++;
+		}
+	}
+
+	/* Perform pending deletion */
+	if ( del_len > 0 ) {
+		array_delete(&mtctx->uri->recipients, del_start, del_len);
+	}
+
+	return ( array_count(&mtctx->uri->recipients) > 0 ? 0 : 1 );
+}
+
+/*
+ * Action printing
+ */
+
+static void ntfy_mailto_action_print
+(const struct sieve_enotify_print_env *penv,
+	const struct sieve_enotify_action *nact)
+{
+	unsigned int count, i;
+	const struct uri_mailto_recipient *recipients;
+	const struct uri_mailto_header_field *headers;
+	struct ntfy_mailto_context *mtctx =
+		(struct ntfy_mailto_context *) nact->method_context;
+
+	/* Print main method parameters */
+
+	sieve_enotify_method_printf
+		(penv,   "    => importance   : %llu\n",
+			(unsigned long long)nact->importance);
+
+	if ( nact->message != NULL )
+		sieve_enotify_method_printf
+			(penv, "    => subject      : %s\n", nact->message);
+	else if ( mtctx->uri->subject != NULL )
+		sieve_enotify_method_printf
+			(penv, "    => subject      : %s\n", mtctx->uri->subject);
+
+	if ( nact->from != NULL )
+		sieve_enotify_method_printf
+			(penv, "    => from         : %s\n", nact->from);
+
+	/* Print mailto: recipients */
+
+	sieve_enotify_method_printf(penv,   "    => recipients   :\n" );
+
+	recipients = array_get(&mtctx->uri->recipients, &count);
+	if ( count == 0 ) {
+		sieve_enotify_method_printf(penv,   "       NONE, action has no effect\n");
+	} else {
+		for ( i = 0; i < count; i++ ) {
+			if ( recipients[i].carbon_copy )
+				sieve_enotify_method_printf
+					(penv,   "       + Cc: %s\n", recipients[i].full);
+			else
+				sieve_enotify_method_printf
+					(penv,   "       + To: %s\n", recipients[i].full);
+		}
+	}
+
+	/* Print accepted headers for notification message */
+
+	headers = array_get(&mtctx->uri->headers, &count);
+	if ( count > 0 ) {
+		sieve_enotify_method_printf(penv,   "    => headers      :\n" );
+		for ( i = 0; i < count; i++ ) {
+			sieve_enotify_method_printf(penv,   "       + %s: %s\n",
+				headers[i].name, headers[i].body);
+		}
+	}
+
+	/* Print body for notification message */
+
+	if ( mtctx->uri->body != NULL )
+		sieve_enotify_method_printf
+			(penv, "    => body         : \n--\n%s\n--\n", mtctx->uri->body);
+
+	/* Finish output with an empty line */
+
+	sieve_enotify_method_printf(penv,   "\n");
+}
+
+/*
+ * Action execution
+ */
+
+static bool _contains_8bit(const char *msg)
+{
+	const unsigned char *s = (const unsigned char *)msg;
+
+	for (; *s != '\0'; s++) {
+		if ((*s & 0x80) != 0)
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+static int ntfy_mailto_send
+(const struct sieve_enotify_exec_env *nenv,
+	const struct sieve_enotify_action *nact,
+	const struct smtp_address *owner_email)
+{
+	struct sieve_instance *svinst = nenv->svinst;
+	const struct sieve_message_data *msgdata = nenv->msgdata;
+	const struct sieve_script_env *senv = nenv->scriptenv;
+	struct ntfy_mailto_context *mtctx =
+		(struct ntfy_mailto_context *) nact->method_context;
+	struct ntfy_mailto_config *mth_config =
+		(struct ntfy_mailto_config *)nenv->method->context;
+	struct sieve_address_source env_from =
+		mth_config->envelope_from;
+	const char *from = NULL;
+	const struct smtp_address *from_smtp = NULL;
+	const char *subject = mtctx->uri->subject;
+	const char *body = mtctx->uri->body;
+	string_t *to, *cc, *all;
+	const struct uri_mailto_recipient *recipients;
+	const struct uri_mailto_header_field *headers;
+	struct sieve_smtp_context *sctx;
+	struct ostream *output;
+	string_t *msg;
+	unsigned int count, i, hcount, h;
+	const char *outmsgid, *error;
+	int ret;
+
+	/* Get recipients */
+	recipients = array_get(&mtctx->uri->recipients, &count);
+	if ( count == 0  ) {
+		sieve_enotify_warning(nenv,
+			"notify mailto uri specifies no recipients; action has no effect");
+		return 0;
+	}
+
+	/* Just to be sure */
+	if ( !sieve_smtp_available(senv) ) {
+		sieve_enotify_global_warning(nenv,
+			"notify mailto method has no means to send mail");
+		return 0;
+	}
+
+	/* Determine which sender to use
+
+	   From RFC 5436, Section 2.3:
+
+		 The ":from" tag overrides the default sender of the notification
+		 message.  "Sender", here, refers to the value used in the [RFC5322]
+		 "From" header.  Implementations MAY also use this value in the
+		 [RFC5321] "MAIL FROM" command (the "envelope sender"), or they may
+		 prefer to establish a mailbox that receives bounces from notification
+		 messages.
+	 */
+	if ( (nenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0 ) {
+		from_smtp = sieve_message_get_sender(nenv->msgctx);
+		if ( from_smtp == NULL ) {
+			/* "<>" */
+			i_zero(&env_from);
+			env_from.type = SIEVE_ADDRESS_SOURCE_EXPLICIT;
+		}
+	}
+	from = nact->from;
+	if ( (ret=sieve_address_source_get_address(&env_from, svinst,
+		senv, nenv->msgctx, nenv->flags, &from_smtp)) < 0 ) {
+		from_smtp = NULL;
+	} else if ( ret == 0 ) {
+		if ( mtctx->from_address != NULL )
+			from_smtp = mtctx->from_address;
+		else if ( svinst->user_email != NULL )
+			from_smtp = svinst->user_email;
+		else {
+			from_smtp = sieve_get_postmaster_smtp(senv);
+			if (from == NULL)
+				from = sieve_get_postmaster_address(senv);
+		}
+	}
+
+	/* Determine message from address */
+	if ( from == NULL ) {
+		if ( from_smtp == NULL )
+			from = sieve_get_postmaster_address(senv);
+		else {
+			from = t_strdup_printf("<%s>",
+				smtp_address_encode(from_smtp));
+		}
+	}
+
+	/* Determine subject */
+	if ( nact->message != NULL ) {
+		/* FIXME: handle UTF-8 */
+		subject = str_sanitize(nact->message, NTFY_MAILTO_MAX_SUBJECT);
+	} else if ( subject == NULL ) {
+		const char *const *hsubject;
+
+		/* Fetch subject from original message */
+		if ( mail_get_headers_utf8
+			(msgdata->mail, "subject", &hsubject) > 0 )
+			subject = str_sanitize(t_strdup_printf("Notification: %s", hsubject[0]),
+				NTFY_MAILTO_MAX_SUBJECT);
+		else
+			subject = "Notification: (no subject)";
+	}
+
+	/* Compose To and Cc headers */
+	to = NULL;
+	cc = NULL;
+	all = t_str_new(256);
+	for ( i = 0; i < count; i++ ) {
+		if ( recipients[i].carbon_copy ) {
+			if ( cc == NULL ) {
+				cc = t_str_new(256);
+				str_append(cc, recipients[i].full);
+			} else {
+				str_append(cc, ", ");
+				str_append(cc, recipients[i].full);
+			}
+		} else {
+			if ( to == NULL ) {
+				to = t_str_new(256);
+				str_append(to, recipients[i].full);
+			} else {
+				str_append(to, ", ");
+				str_append(to, recipients[i].full);
+			}
+		}
+		if ( i < 3) {
+			if ( i > 0 )
+				str_append(all, ", ");
+			str_append(all,
+				smtp_address_encode_path(recipients[i].address));
+		} else if (i == 3) {
+			str_printfa(all, ", ... (%u total)", count);
+		}
+	}
+
+	msg = t_str_new(512);
+	outmsgid = sieve_message_get_new_id(svinst);
+
+	rfc2822_header_write(msg, "X-Sieve", SIEVE_IMPLEMENTATION);
+	rfc2822_header_write(msg, "Message-ID", outmsgid);
+	rfc2822_header_write(msg, "Date", message_date_create(ioloop_time));
+	rfc2822_header_utf8_printf(msg, "Subject", "%s", subject);
+
+	rfc2822_header_write_address(msg, "From", from);
+
+	if ( to != NULL )
+		rfc2822_header_write_address(msg, "To", str_c(to));
+	if ( cc != NULL )
+		rfc2822_header_write_address(msg, "Cc", str_c(cc));
+
+	rfc2822_header_printf(msg, "Auto-Submitted",
+		"auto-notified; owner-email=\"%s\"",
+		smtp_address_encode(owner_email));
+	rfc2822_header_write(msg, "Precedence", "bulk");
+
+	/* Set importance */
+	switch ( nact->importance ) {
+	case 1:
+		rfc2822_header_write(msg, "X-Priority", "1 (Highest)");
+		rfc2822_header_write(msg, "Importance", "High");
+		break;
+	case 3:
+		rfc2822_header_write(msg, "X-Priority", "5 (Lowest)");
+		rfc2822_header_write(msg, "Importance", "Low");
+		break;
+	case 2:
+	default:
+		rfc2822_header_write(msg, "X-Priority", "3 (Normal)");
+		rfc2822_header_write(msg, "Importance", "Normal");
+		break;
+	}
+
+	/* Add custom headers */
+
+	headers = array_get(&mtctx->uri->headers, &hcount);
+	for ( h = 0; h < hcount; h++ ) {
+		const char *name = rfc2822_header_field_name_sanitize(headers[h].name);
+
+		rfc2822_header_write(msg, name, headers[h].body);
+	}
+
+	/* Generate message body */
+
+	rfc2822_header_write(msg, "MIME-Version", "1.0");
+	if ( body != NULL ) {
+		if (_contains_8bit(body)) {
+			rfc2822_header_write
+				(msg, "Content-Type", "text/plain; charset=utf-8");
+			rfc2822_header_write(msg, "Content-Transfer-Encoding", "8bit");
+		} else {
+			rfc2822_header_write
+				(msg, "Content-Type", "text/plain; charset=us-ascii");
+			rfc2822_header_write(msg, "Content-Transfer-Encoding", "7bit");
+		}
+		str_printfa(msg, "\r\n%s\r\n", body);
+
+	} else {
+		rfc2822_header_write
+			(msg, "Content-Type", "text/plain; charset=US-ASCII");
+		rfc2822_header_write(msg, "Content-Transfer-Encoding", "7bit");
+
+		str_append(msg, "\r\nNotification of new message.\r\n");
+	}
+
+	sctx = sieve_smtp_start(senv, from_smtp);
+
+	/* Send message to all recipients */
+	for ( i = 0; i < count; i++ )
+		sieve_smtp_add_rcpt(sctx, recipients[i].address);
+
+	output = sieve_smtp_send(sctx);
+	o_stream_nsend(output, str_data(msg), str_len(msg));
+
+	if ( (ret=sieve_smtp_finish(sctx, &error)) <= 0 ) {
+		if (ret < 0)  {
+			sieve_enotify_global_error(nenv,
+				"failed to send mail notification to %s: %s (temporary failure)",
+				str_c(all),	str_sanitize(error, 512));
+		} else {
+			sieve_enotify_global_log_error(nenv,
+				"failed to send mail notification to %s: %s (permanent failure)",
+				str_c(all),	str_sanitize(error, 512));
+		}
+	} else {
+		sieve_enotify_global_info(nenv,
+			"sent mail notification to %s", str_c(all));
+	}
+
+	return 0;
+}
+
+static int ntfy_mailto_action_execute
+(const struct sieve_enotify_exec_env *nenv,
+	const struct sieve_enotify_action *nact)
+{
+	struct sieve_instance *svinst = nenv->svinst;
+	const struct sieve_script_env *senv = nenv->scriptenv;
+	struct mail *mail = nenv->msgdata->mail;
+	const struct smtp_address *owner_email;
+	const char *const *hdsp;
+	int ret;
+
+	owner_email = svinst->user_email;
+	if ( owner_email == NULL &&
+		(nenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0 )
+		owner_email = sieve_message_get_final_recipient(nenv->msgctx);
+	if ( owner_email == NULL ) {
+		owner_email = sieve_get_postmaster_smtp(senv);
+	}
+	i_assert( owner_email != NULL );
+
+	/* Is the message an automatic reply ? */
+	if ( (ret=mail_get_headers(mail, "auto-submitted", &hdsp)) < 0 ) {
+		sieve_enotify_critical(nenv,
+			"mailto notification: "
+				"failed to read `auto-submitted' header field",
+			"mailto notification: "
+				"failed to read `auto-submitted' header field: %s",
+			mailbox_get_last_error(mail->box, NULL));
+		return -1;
+	}
+
+	/* Theoretically multiple headers could exist, so lets make sure */
+	if ( ret > 0 ) {
+		while ( *hdsp != NULL ) {
+			if ( strcasecmp(*hdsp, "no") != 0 ) {
+				const struct smtp_address *sender = NULL;
+				const char *from;
+
+				if ( (nenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0 )
+					sender = sieve_message_get_sender(nenv->msgctx);
+				from = (sender == NULL ? "" : t_strdup_printf
+						(" from <%s>", smtp_address_encode(sender)));
+
+				sieve_enotify_global_info(nenv,
+					"not sending notification "
+					"for auto-submitted message%s", from);
+				return 0;
+			}
+			hdsp++;
+		}
+	}
+
+	T_BEGIN {
+		ret = ntfy_mailto_send(nenv, nact, owner_email);
+	} T_END;
+
+	return ret;
+}
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/enotify/mailto/uri-mailto.c
@@ -0,0 +1,621 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* FIXME: URI syntax conforms to something somewhere in between RFC 2368 and
+ *   draft-duerst-mailto-bis-05.txt. Should fully migrate to new specification
+ *   when it matures. This requires modifications to the address parser (no
+ *   whitespace allowed within the address itself) and UTF-8 support will be
+ *   required in the URL.
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "str-sanitize.h"
+
+#include "rfc2822.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-address.h"
+#include "sieve-message.h"
+
+#include "uri-mailto.h"
+
+/* Util macros */
+
+#define uri_mailto_error(PARSER, ...) \
+	sieve_error((PARSER)->ehandler, NULL, "invalid mailto URI: " __VA_ARGS__ )
+
+#define uri_mailto_warning(PARSER, ...) \
+	sieve_warning((PARSER)->ehandler, NULL, "mailto URI: " __VA_ARGS__ )
+
+/* Parser object */
+
+struct uri_mailto_parser {
+	pool_t pool;
+	struct sieve_error_handler *ehandler;
+
+	struct uri_mailto *uri;
+
+	const char **reserved_headers;
+	const char **unique_headers;
+
+	int max_recipients;
+	int max_headers;
+};
+
+/*
+ * Reserved and unique headers
+ */
+
+static inline bool uri_mailto_header_is_reserved
+(struct uri_mailto_parser *parser, const char *field_name)
+{
+	const char **hdr = parser->reserved_headers;
+
+	if ( hdr == NULL ) return FALSE;
+
+	/* Check whether it is reserved */
+	while ( *hdr != NULL ) {
+		if ( strcasecmp(field_name, *hdr) == 0 )
+			return TRUE;
+		hdr++;
+	}
+
+	return FALSE;
+}
+
+static inline bool uri_mailto_header_is_unique
+(struct uri_mailto_parser *parser, const char *field_name)
+{
+	const char **hdr = parser->unique_headers;
+
+	if ( hdr == NULL ) return FALSE;
+
+	/* Check whether it is supposed to be unique */
+	while ( *hdr != NULL ) {
+		if ( strcasecmp(field_name, *hdr) == 0 )
+			return TRUE;
+		hdr++;
+	}
+
+	return FALSE;
+}
+
+/*
+ * Low-level URI parsing.
+ *
+ * FIXME: much of this implementation will be common to other URI schemes. This
+ *        should be merged into a common implementation.
+ */
+
+static const char _qchar_lookup[256] = {
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 00
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 10
+	0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0,  // 20
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,  // 30
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 40
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,  // 50
+	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 60
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0,  // 70
+
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 80
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 90
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // A0
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // B0
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // C0
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // D0
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // E0
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // F0
+};
+
+static inline bool _is_qchar(unsigned char c)
+{
+	return ((_qchar_lookup[c] & 0x01) != 0);
+}
+
+static inline int _decode_hex_digit(unsigned char digit)
+{
+	switch ( digit ) {
+	case '0': case '1': case '2': case '3': case '4':
+	case '5': case '6': case '7': case '8': case '9':
+		return (int) digit - '0';
+
+	case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+		return (int) digit - 'a' + 0x0a;
+
+	case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+		return (int) digit - 'A' + 0x0A;
+	}
+
+	return -1;
+}
+
+static bool _parse_hex_value(const char **in, char *out)
+{
+	int value, digit;
+
+	if ( (digit=_decode_hex_digit((unsigned char) **in)) < 0 )
+		return FALSE;
+
+	value = digit << 4;
+	(*in)++;
+
+	if ( (digit=_decode_hex_digit((unsigned char) **in)) < 0 )
+		return FALSE;
+
+	value |= digit;
+	(*in)++;
+
+	if ( value == 0 )
+		return FALSE;
+
+	*out = (char) value;
+	return TRUE;
+}
+
+/*
+ * URI recipient parsing
+ */
+
+static bool uri_mailto_add_valid_recipient
+(struct uri_mailto_parser *parser, string_t *recipient, bool cc)
+{
+	struct uri_mailto *uri = parser->uri;
+	struct uri_mailto_recipient *new_recipient;
+	struct uri_mailto_recipient *rcpts;
+	unsigned int count, i;
+	const char *error;
+	const struct smtp_address *address;
+
+	/* Verify recipient */
+	if ( (address=sieve_address_parse_str
+		(recipient, &error)) == NULL ) {
+		uri_mailto_error(parser, "invalid recipient '%s': %s",
+			str_sanitize(str_c(recipient), 80), error);
+		return FALSE;
+	}
+
+	/* Add recipient to the uri */
+	if ( uri != NULL ) {
+		/* Get current recipients */
+		rcpts = array_get_modifiable(&uri->recipients, &count);
+
+		/* Enforce limits */
+		if ( parser->max_recipients > 0 && (int)count >= parser->max_recipients ) {
+			if ( (int)count == parser->max_recipients) {
+				uri_mailto_warning(parser,
+					"more than the maximum %u recipients specified; "
+					"rest is discarded", parser->max_recipients);
+			}
+			return TRUE;
+		}
+
+		/* Check for duplicate first */
+		for ( i = 0; i < count; i++ ) {
+			if ( smtp_address_equals(rcpts[i].address, address) ) {
+				/* Upgrade existing Cc: recipient to a To: recipient if possible */
+				rcpts[i].carbon_copy = ( rcpts[i].carbon_copy && cc );
+
+				uri_mailto_warning(parser, "ignored duplicate recipient '%s'",
+					str_sanitize(str_c(recipient), 80));
+				return TRUE;
+			}
+		}
+
+		/* Add */
+		new_recipient = array_append_space(&uri->recipients);
+		new_recipient->carbon_copy = cc;
+		new_recipient->full = p_strdup(parser->pool, str_c(recipient));
+		new_recipient->address = smtp_address_clone(parser->pool, address);
+	}
+
+	return TRUE;
+}
+
+static bool uri_mailto_parse_recipients
+(struct uri_mailto_parser *parser, const char **uri_p)
+{
+	string_t *to = t_str_new(128);
+	const char *p = *uri_p;
+
+	if ( *p == '\0' || *p == '?' )
+		return TRUE;
+
+	while ( *p != '\0' && *p != '?' ) {
+		if ( *p == '%' ) {
+			/* % encoded character */
+			char ch;
+
+			p++;
+
+			/* Parse 2-digit hex value */
+			if ( !_parse_hex_value(&p, &ch) ) {
+				uri_mailto_error(parser, "invalid %% encoding");
+				return FALSE;
+			}
+
+			/* Check for delimiter */
+			if ( ch == ',' ) {
+				/* Verify and add recipient */
+				if ( !uri_mailto_add_valid_recipient(parser, to, FALSE) )
+					return FALSE;
+
+				/* Reset for next recipient */
+				str_truncate(to, 0);
+			}	else {
+				/* Content character */
+				str_append_c(to, ch);
+			}
+		} else {
+			if ( *p == ':' || *p == ';' || *p == ',' || !_is_qchar(*p) ) {
+				uri_mailto_error
+					(parser, "invalid character '%c' in 'to' part", *p);
+				return FALSE;
+			}
+
+			/* Content character */
+			str_append_c(to, *p);
+			p++;
+		}
+	}
+
+	i_assert( *p == '\0' || *p == '?' );
+
+	/* Verify and add recipient */
+	if ( !uri_mailto_add_valid_recipient(parser, to, FALSE) )
+		return FALSE;
+
+	*uri_p = p;
+	return TRUE;
+}
+
+static bool uri_mailto_parse_header_recipients
+(struct uri_mailto_parser *parser, string_t *rcpt_header, bool cc)
+{
+	string_t *to = t_str_new(128);
+	const char *p = (const char *) str_data(rcpt_header);
+	const char *pend = p + str_len(rcpt_header);
+
+	while ( p < pend ) {
+		if ( *p == ',' ) {
+			/* Verify and add recipient */
+			if ( !uri_mailto_add_valid_recipient(parser, to, cc) )
+				return FALSE;
+
+			/* Reset for next recipient */
+			str_truncate(to, 0);
+		} else {
+			/* Content character */
+			str_append_c(to, *p);
+		}
+		p++;
+	}
+
+	/* Verify and add recipient */
+	if ( !uri_mailto_add_valid_recipient(parser, to, cc) )
+		return FALSE;
+
+	return TRUE;
+}
+
+/* URI header parsing */
+
+static bool uri_mailto_header_is_duplicate
+(struct uri_mailto_parser *parser, const char *field_name)
+{
+	struct uri_mailto *uri = parser->uri;
+
+	if ( uri == NULL ) return FALSE;
+
+	if ( uri_mailto_header_is_unique(parser, field_name) ) {
+		const struct uri_mailto_header_field *hdrs;
+		unsigned int count, i;
+
+		hdrs = array_get(&uri->headers, &count);
+		for ( i = 0; i < count; i++ ) {
+			if ( strcasecmp(hdrs[i].name, field_name) == 0 )
+				return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
+static bool uri_mailto_parse_headers
+(struct uri_mailto_parser *parser, const char **uri_p)
+{
+	struct uri_mailto *uri = parser->uri;
+	unsigned int header_count = 0;
+	string_t *field = t_str_new(128);
+	const char *p = *uri_p;
+
+	while ( *p != '\0' ) {
+		enum {
+			_HNAME_IGNORED,
+			_HNAME_GENERIC,
+			_HNAME_TO,
+			_HNAME_CC,
+			_HNAME_SUBJECT,
+			_HNAME_BODY
+		} hname_type = _HNAME_GENERIC;
+		struct uri_mailto_header_field *hdrf = NULL;
+		const char *field_name;
+
+		/* Parse field name */
+		while ( *p != '\0' && *p != '=' ) {
+			char ch = *p;
+			p++;
+
+			if ( ch == '%' ) {
+				/* Encoded, parse 2-digit hex value */
+				if ( !_parse_hex_value(&p, &ch) ) {
+					uri_mailto_error(parser, "invalid %% encoding");
+					return FALSE;
+				}
+			} else if ( ch != '=' && !_is_qchar(ch) ) {
+				uri_mailto_error
+					(parser, "invalid character '%c' in header field name part", ch);
+				return FALSE;
+			}
+
+			str_append_c(field, ch);
+		}
+		if ( *p != '\0' ) p++;
+
+		/* Verify field name */
+		if ( !rfc2822_header_field_name_verify(str_c(field), str_len(field)) ) {
+			uri_mailto_error(parser, "invalid header field name");
+			return FALSE;
+		}
+
+		if ( parser->max_headers > -1 &&
+			(int)header_count >= parser->max_headers ) {
+			/* Refuse to accept more headers than allowed by policy */
+			if ( (int)header_count == parser->max_headers ) {
+				uri_mailto_warning(parser, "more than the maximum %u headers specified; "
+					"rest is discarded", parser->max_headers);
+			}
+
+			hname_type = _HNAME_IGNORED;
+		} else {
+			/* Add new header field to array and assign its name */
+
+			field_name = str_c(field);
+			if ( strcasecmp(field_name, "to") == 0 )
+				hname_type = _HNAME_TO;
+			else if ( strcasecmp(field_name, "cc") == 0 )
+				hname_type = _HNAME_CC;
+			else if ( strcasecmp(field_name, "subject") == 0 )
+				hname_type = _HNAME_SUBJECT;
+			else if ( strcasecmp(field_name, "body") == 0 )
+				hname_type = _HNAME_BODY;
+			else if ( !uri_mailto_header_is_reserved(parser, field_name) ) {
+				if ( uri != NULL ) {
+					if ( !uri_mailto_header_is_duplicate(parser, field_name) ) {
+						hdrf = array_append_space(&uri->headers);
+						hdrf->name = p_strdup(parser->pool, field_name);
+					} else {
+						uri_mailto_warning(parser,
+							"ignored duplicate for unique header field '%s'",
+							str_sanitize(field_name, 32));
+						hname_type = _HNAME_IGNORED;
+					}
+				} else {
+					hname_type = _HNAME_IGNORED;
+				}
+			} else {
+				uri_mailto_warning(parser, "ignored reserved header field '%s'",
+					str_sanitize(field_name, 32));
+				hname_type = _HNAME_IGNORED;
+			}
+		}
+
+		header_count++;
+
+		/* Reset for field body */
+		str_truncate(field, 0);
+
+		/* Parse field body */
+		while ( *p != '\0' && *p != '&' ) {
+			char ch = *p;
+			p++;
+
+			if ( ch == '%' ) {
+				/* Encoded, parse 2-digit hex value */
+				if ( !_parse_hex_value(&p, &ch) ) {
+					uri_mailto_error(parser, "invalid %% encoding");
+					return FALSE;
+				}
+			} else if ( ch != '=' && !_is_qchar(ch) ) {
+				uri_mailto_error
+					(parser, "invalid character '%c' in header field value part", ch);
+				return FALSE;
+			}
+			str_append_c(field, ch);
+		}
+		if ( *p != '\0' ) p++;
+
+		/* Verify field body */
+		if ( hname_type == _HNAME_BODY ) {
+			// FIXME: verify body ...
+		} else {
+			if ( !rfc2822_header_field_body_verify
+				(str_c(field), str_len(field), FALSE, FALSE) ) {
+				uri_mailto_error(parser, "invalid header field body");
+				return FALSE;
+			}
+		}
+
+		/* Assign field body */
+
+		switch ( hname_type ) {
+		case _HNAME_IGNORED:
+			break;
+		case _HNAME_TO:
+			/* Gracefully allow duplicate To fields */
+			if ( !uri_mailto_parse_header_recipients(parser, field, FALSE) )
+				return FALSE;
+			break;
+		case _HNAME_CC:
+			/* Gracefully allow duplicate Cc fields */
+			if ( !uri_mailto_parse_header_recipients(parser, field, TRUE) )
+				return FALSE;
+			break;
+		case _HNAME_SUBJECT:
+			/* Igore duplicate subject field */
+			if ( uri != NULL ) {
+				if ( uri->subject == NULL )
+					uri->subject = p_strdup(parser->pool, str_c(field));
+				else
+					uri_mailto_warning(parser, "ignored duplicate subject field");
+			}
+			break;
+		case _HNAME_BODY:
+			/* Igore duplicate body field */
+			if ( uri != NULL ) {
+				if ( uri->body == NULL )
+					uri->body = p_strdup(parser->pool, str_c(field));
+				else
+					uri_mailto_warning(parser, "ignored duplicate body field");
+			}
+			break;
+		case _HNAME_GENERIC:
+			if ( uri != NULL && hdrf != NULL )
+				hdrf->body = p_strdup(parser->pool, str_c(field));
+			break;
+		}
+
+		/* Reset for next name */
+		str_truncate(field, 0);
+	}
+
+	/* Skip '&' */
+	if ( *p != '\0' ) p++;
+
+	*uri_p = p;
+	return TRUE;
+}
+
+static bool uri_mailto_parse_uri
+(struct uri_mailto_parser *parser, const char *uri_body)
+{
+	const char *p = uri_body;
+
+	/*
+	 * mailtoURI   = "mailto:" [ to ] [ hfields ]
+	 * to          = [ addr-spec *("%2C" addr-spec ) ]
+	 * hfields     = "?" hfield *( "&" hfield )
+	 * hfield      = hfname "=" hfvalue
+	 * hfname      = *qchar
+	 * hfvalue     = *qchar
+	 * addr-spec   = local-part "@" domain
+	 * local-part  = dot-atom / quoted-string
+	 * qchar       = unreserved / pct-encoded / some-delims
+	 * some-delims = "!" / "$" / "'" / "(" / ")" / "*"
+	 *               / "+" / "," / ";" / ":" / "@"
+	 *
+	 * to         ~= *tqchar
+	 * tqchar     ~= <qchar> without ";" and ":"
+	 *
+	 * Scheme 'mailto:' already parsed, starting parse after colon
+	 */
+
+	/* First extract to-part by searching for '?' and decoding % items
+	 */
+
+	if ( !uri_mailto_parse_recipients(parser, &p) )
+		return FALSE;
+
+	if ( *p == '\0' )
+		return TRUE;
+	i_assert( *p == '?' );
+	p++;
+
+	/* Extract hfield items */
+
+	while ( *p != '\0' ) {
+		/* Extract hfield item by searching for '&' and decoding '%' items */
+		if ( !uri_mailto_parse_headers(parser, &p) )
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+/*
+ * Validation
+ */
+
+bool uri_mailto_validate
+(const char *uri_body, const char **reserved_headers,
+	const char **unique_headers, int max_recipients, int max_headers,
+	struct sieve_error_handler *ehandler)
+{
+	struct uri_mailto_parser parser;
+
+	i_zero(&parser);
+	parser.ehandler = ehandler;
+	parser.max_recipients = max_recipients;
+	parser.max_headers = max_headers;
+	parser.reserved_headers = reserved_headers;
+	parser.unique_headers = unique_headers;
+
+	/* If no errors are reported, we don't need to record any data */
+	if ( ehandler != NULL ) {
+		parser.pool = pool_datastack_create();
+
+		parser.uri = p_new(parser.pool, struct uri_mailto, 1);
+		p_array_init(&parser.uri->recipients, parser.pool, max_recipients);
+		p_array_init(&parser.uri->headers, parser.pool, max_headers);
+	}
+
+	if ( !uri_mailto_parse_uri(&parser, uri_body) )
+		return FALSE;
+
+	if ( ehandler != NULL ) {
+		if ( array_count(&parser.uri->recipients) == 0 )
+			uri_mailto_warning(&parser, "notification URI specifies no recipients");
+	}
+
+	return TRUE;
+}
+
+/*
+ * Parsing
+ */
+
+struct uri_mailto *uri_mailto_parse
+(const char *uri_body, pool_t pool, const char **reserved_headers,
+	const char **unique_headers, int max_recipients, int max_headers,
+	struct sieve_error_handler *ehandler)
+{
+	struct uri_mailto_parser parser;
+
+	parser.pool = pool;
+	parser.ehandler = ehandler;
+	parser.max_recipients = max_recipients;
+	parser.max_headers = max_headers;
+	parser.reserved_headers = reserved_headers;
+	parser.unique_headers = unique_headers;
+
+	parser.uri = p_new(pool, struct uri_mailto, 1);
+	p_array_init(&parser.uri->recipients, pool, max_recipients);
+	p_array_init(&parser.uri->headers, pool, max_headers);
+
+	if ( !uri_mailto_parse_uri(&parser, uri_body) )
+		return NULL;
+
+	if ( ehandler != NULL ) {
+		if ( array_count(&parser.uri->recipients) == 0 )
+			uri_mailto_warning(&parser, "notification URI specifies no recipients");
+	}
+
+	return parser.uri;
+}
+
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/enotify/mailto/uri-mailto.h
@@ -0,0 +1,41 @@
+#ifndef URI_MAILTO_H
+#define URI_MAILTO_H
+
+/*
+ * Types
+ */
+
+struct uri_mailto_header_field {
+	const char *name;
+	const char *body;
+};
+
+struct uri_mailto_recipient {
+	const char *full;
+	const struct smtp_address *address;
+	bool carbon_copy;
+};
+
+ARRAY_DEFINE_TYPE(recipients, struct uri_mailto_recipient);
+ARRAY_DEFINE_TYPE(headers, struct uri_mailto_header_field);
+
+struct uri_mailto {
+	ARRAY_TYPE(recipients) recipients;
+	ARRAY_TYPE(headers) headers;
+	const char *subject;
+	const char *body;
+};
+
+bool uri_mailto_validate
+	(const char *uri_body, const char **reserved_headers,
+		const char **unique_headers, int max_recipients, int max_headers,
+		struct sieve_error_handler *ehandler);
+
+struct uri_mailto *uri_mailto_parse
+(const char *uri_body, pool_t pool, const char **reserved_headers,
+	const char **unique_headers, int max_recipients, int max_headers,
+	struct sieve_error_handler *ehandler);
+
+#endif
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/enotify/sieve-ext-enotify.h
@@ -0,0 +1,177 @@
+#ifndef SIEVE_EXT_ENOTIFY_H
+#define SIEVE_EXT_ENOTIFY_H
+
+#include "lib.h"
+#include "compat.h"
+#include <stdarg.h>
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+
+/*
+ * Forward declarations
+ */
+
+struct sieve_enotify_method;
+struct sieve_enotify_env;
+struct sieve_enotify_action;
+struct sieve_enotify_print_env;
+struct sieve_enotify_exec_env;
+
+/*
+ * Notify method definition
+ */
+
+struct sieve_enotify_method_def {
+	const char *identifier;
+
+	/* Registration */
+	bool (*load)
+		(const struct sieve_enotify_method *nmth, void **context);
+	void (*unload)
+		(const struct sieve_enotify_method *nmth);
+
+	/* Validation */
+	bool (*compile_check_uri)
+		(const struct sieve_enotify_env *nenv, const char *uri,
+			const char *uri_body);
+	bool (*compile_check_message)
+		(const struct sieve_enotify_env *nenv, string_t *message);
+	bool (*compile_check_from)
+		(const struct sieve_enotify_env *nenv, string_t *from);
+	bool (*compile_check_option)
+		(const struct sieve_enotify_env *nenv, const char *option,
+			const char *value);
+
+	/* Runtime */
+	bool (*runtime_check_uri)
+		(const struct sieve_enotify_env *nenv, const char *uri,
+			const char *uri_body);
+	const char *(*runtime_get_method_capability)
+		(const struct sieve_enotify_env *nenv, const char *uri,
+			const char *uri_body, const char *capability);
+	bool (*runtime_check_operands)
+		(const struct sieve_enotify_env *nenv, const char *uri,
+			const char *uri_body, string_t *message, string_t *from,
+			pool_t context_pool, void **method_context);
+	bool (*runtime_set_option)
+		(const struct sieve_enotify_env *nenv, void *method_context,
+			const char *option, const char *value);
+
+	/* Action duplicates */
+	int (*action_check_duplicates)
+		(const struct sieve_enotify_env *nenv,
+			const struct sieve_enotify_action *nact,
+			const struct sieve_enotify_action *nact_other);
+
+	/* Action print */
+	void (*action_print)
+		(const struct sieve_enotify_print_env *penv,
+			const struct sieve_enotify_action *nact);
+
+	/* Action execution
+     (returns 0 if all is ok and -1 for temporary error)
+	 */
+	int (*action_execute)
+		(const struct sieve_enotify_exec_env *nenv,
+			const struct sieve_enotify_action *nact);
+};
+
+/*
+ * Notify method instance
+ */
+
+struct sieve_enotify_method {
+	const struct sieve_enotify_method_def *def;
+	int id;
+
+	struct sieve_instance *svinst;
+	void *context;
+};
+
+const struct sieve_enotify_method *sieve_enotify_method_register
+	(struct sieve_instance *svinst,
+		const struct sieve_enotify_method_def *nmth_def);
+void  sieve_enotify_method_unregister
+	(const struct sieve_enotify_method *nmth);
+
+/*
+ * Notify method environment
+ */
+
+struct sieve_enotify_env {
+	struct sieve_instance *svinst;
+
+	const struct sieve_enotify_method *method;
+
+	struct sieve_error_handler *ehandler;
+};
+
+/*
+ * Notify method printing
+ */
+
+void sieve_enotify_method_printf
+	(const struct sieve_enotify_print_env *penv, const char *fmt, ...)
+		ATTR_FORMAT(2, 3);
+
+/*
+ * Notify execution environment
+ */
+
+struct sieve_enotify_exec_env {
+	struct sieve_instance *svinst;
+	enum sieve_execute_flags flags;
+
+	const struct sieve_enotify_method *method;
+
+	const struct sieve_script_env *scriptenv;
+	const struct sieve_message_data *msgdata;
+	struct sieve_message_context *msgctx;
+
+	struct sieve_error_handler *ehandler;
+};
+
+/*
+ * Notify action
+ */
+
+struct sieve_enotify_action {
+	const struct sieve_enotify_method *method;
+	void *method_context;
+
+	sieve_number_t importance;
+	const char *message;
+	const char *from;
+};
+
+/*
+ * Error handling
+ */
+
+#define sieve_enotify_error(ENV, ...) \
+	sieve_error((ENV)->ehandler, NULL, __VA_ARGS__ )
+
+#define sieve_enotify_warning(ENV, ...) \
+	sieve_warning((ENV)->ehandler, NULL, __VA_ARGS__ )
+
+#define sieve_enotify_info(ENV, ...) \
+	sieve_info((ENV)->ehandler, NULL, __VA_ARGS__ )
+
+#define sieve_enotify_critical(ENV, ...) \
+	sieve_critical((ENV)->svinst, (ENV)->ehandler, NULL, __VA_ARGS__ )
+
+#define sieve_enotify_global_error(ENV, ...) \
+	sieve_global_error((ENV)->svinst, (ENV)->ehandler, NULL, __VA_ARGS__ )
+
+#define sieve_enotify_global_warning(ENV, ...) \
+	sieve_global_warning((ENV)->svinst, (ENV)->ehandler, NULL, __VA_ARGS__ )
+
+#define sieve_enotify_global_info(ENV, ...) \
+	sieve_global_info((ENV)->svinst, (ENV)->ehandler, NULL, __VA_ARGS__ )
+
+#define sieve_enotify_global_log_error(ENV, ...) \
+	sieve_global_info_error((ENV)->svinst, (ENV)->ehandler, NULL, __VA_ARGS__ )
+
+#endif
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/enotify/tst-notify-method-capability.c
@@ -0,0 +1,233 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+#include "ext-enotify-common.h"
+
+/*
+ * String test
+ *
+ * Syntax:
+ *   notify_method_capability [COMPARATOR] [MATCH-TYPE]
+ *     <notification-uri: string>
+ *     <notification-capability: string>
+ *     <key-list: string-list>
+ */
+
+static bool tst_notifymc_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool tst_notifymc_validate
+	(struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_notifymc_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def notify_method_capability_test = {
+	.identifier = "notify_method_capability",
+	.type = SCT_TEST,
+	.positional_args = 3,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = tst_notifymc_registered,
+	.validate = tst_notifymc_validate,
+	.generate = tst_notifymc_generate
+};
+
+/*
+ * String operation
+ */
+
+static bool tst_notifymc_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_notifymc_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def notify_method_capability_operation = {
+	.mnemonic = "NOTIFY_METHOD_CAPABILITY",
+	.ext_def = &enotify_extension,
+	.code = EXT_ENOTIFY_OPERATION_NOTIFY_METHOD_CAPABILITY,
+	.dump = tst_notifymc_operation_dump,
+	.execute = tst_notifymc_operation_execute
+};
+
+/*
+ * Optional arguments
+ */
+
+enum tst_notifymc_optional {
+	OPT_END,
+	OPT_COMPARATOR,
+	OPT_MATCH_TYPE
+};
+
+/*
+ * Test registration
+ */
+
+static bool tst_notifymc_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext ATTR_UNUSED,
+	struct sieve_command_registration *cmd_reg)
+{
+	/* The order of these is not significant */
+	sieve_comparators_link_tag(valdtr, cmd_reg, OPT_COMPARATOR);
+	sieve_match_types_link_tags(valdtr, cmd_reg, OPT_MATCH_TYPE);
+
+	return TRUE;
+}
+
+/*
+ * Test validation
+ */
+
+static bool tst_notifymc_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+	struct sieve_ast_argument *arg = tst->first_positional;
+	const struct sieve_match_type mcht_default =
+		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+	const struct sieve_comparator cmp_default =
+		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "notification-uri", 1, SAAT_STRING) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+		return FALSE;
+
+	arg = sieve_ast_argument_next(arg);
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "notification-capability", 2, SAAT_STRING) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+		return FALSE;
+
+	arg = sieve_ast_argument_next(arg);
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "key-list", 3, SAAT_STRING_LIST) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+		return FALSE;
+
+	/* Validate the key argument to a specified match type */
+	return sieve_match_type_validate
+		(valdtr, tst, arg, &mcht_default, &cmp_default);
+}
+
+/*
+ * Test generation
+ */
+
+static bool tst_notifymc_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	sieve_operation_emit
+		(cgenv->sblock, cmd->ext, &notify_method_capability_operation);
+
+ 	/* Generate arguments */
+	return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_notifymc_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "NOTIFY_METHOD_CAPABILITY");
+	sieve_code_descend(denv);
+
+	/* Handle any optional arguments */
+	if ( sieve_match_opr_optional_dump(denv, address, NULL) != 0 )
+		return FALSE;
+
+	return
+		sieve_opr_string_dump(denv, address, "notify uri") &&
+		sieve_opr_string_dump(denv, address, "notify capability") &&
+		sieve_opr_stringlist_dump(denv, address, "key list");
+}
+
+/*
+ * Code execution
+ */
+
+static int tst_notifymc_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	struct sieve_match_type mcht =
+		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+	struct sieve_comparator cmp =
+		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+	string_t *notify_uri, *notify_capability;
+	struct sieve_stringlist *value_list, *key_list;
+	const char *cap_value;
+	int match, ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Handle match-type and comparator operands */
+	if ( sieve_match_opr_optional_read
+		(renv, address, NULL, &ret, &cmp, &mcht) < 0 )
+		return ret;
+
+	/* Read notify uri */
+	if ( (ret=sieve_opr_string_read(renv, address, "notify-uri", &notify_uri))
+		<= 0 )
+		return ret;
+
+	/* Read notify capability */
+	if ( (ret=sieve_opr_string_read
+		(renv, address, "notify-capability", &notify_capability)) <= 0 )
+		return ret;
+
+	/* Read key-list */
+	if ( (ret=sieve_opr_stringlist_read(renv, address, "key-list", &key_list))
+		<= 0)
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "notify_method_capability test");
+
+	cap_value = ext_enotify_runtime_get_method_capability
+		(renv, notify_uri, str_c(notify_capability));
+
+	if ( cap_value != NULL ) {
+		value_list = sieve_single_stringlist_create_cstr(renv, cap_value, TRUE);
+
+		/* Perform match */
+		if ( (match=sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret))
+			< 0 )
+			return ret;
+	} else {
+		match = 0;
+	}
+
+	/* Set test result for subsequent conditional jump */
+	sieve_interpreter_set_test_result(renv->interp, match > 0);
+	return SIEVE_EXEC_OK;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/enotify/tst-valid-notify-method.c
@@ -0,0 +1,144 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+#include "ext-enotify-common.h"
+
+/*
+ * Valid_notify_method test
+ *
+ * Syntax:
+ *   valid_notify_method <notification-uris: string-list>
+ */
+
+static bool tst_vnotifym_validate
+	(struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_vnotifym_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def valid_notify_method_test = {
+	.identifier = "valid_notify_method",
+	.type = SCT_TEST,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = tst_vnotifym_validate,
+	.generate = tst_vnotifym_generate
+};
+
+/*
+ * Valid_notify_method operation
+ */
+
+static bool tst_vnotifym_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_vnotifym_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def valid_notify_method_operation = {
+	.mnemonic = "VALID_NOTIFY_METHOD",
+	.ext_def = &enotify_extension,
+	.code = EXT_ENOTIFY_OPERATION_VALID_NOTIFY_METHOD,
+	.dump = tst_vnotifym_operation_dump,
+	.execute = tst_vnotifym_operation_execute
+};
+
+/*
+ * Test validation
+ */
+
+static bool tst_vnotifym_validate
+	(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+	struct sieve_ast_argument *arg = tst->first_positional;
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "notification-uris", 1, SAAT_STRING_LIST) ) {
+		return FALSE;
+	}
+
+	return sieve_validator_argument_activate(valdtr, tst, arg, FALSE);
+}
+
+/*
+ * Test generation
+ */
+
+static bool tst_vnotifym_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	sieve_operation_emit(cgenv->sblock, cmd->ext, &valid_notify_method_operation);
+
+ 	/* Generate arguments */
+	return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_vnotifym_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "VALID_NOTIFY_METHOD");
+	sieve_code_descend(denv);
+
+	return
+		sieve_opr_stringlist_dump(denv, address, "notify-uris");
+}
+
+/*
+ * Code execution
+ */
+
+static int tst_vnotifym_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	struct sieve_stringlist *notify_uris;
+	string_t *uri_item;
+	bool all_valid = TRUE;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Read notify uris */
+	if ( (ret=sieve_opr_stringlist_read
+		(renv, address, "notify-uris", &notify_uris)) <= 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "valid_notify_method test");
+
+	uri_item = NULL;
+	while ( (ret=sieve_stringlist_next_item(notify_uris, &uri_item)) > 0 ) {
+		if ( !ext_enotify_runtime_method_validate(renv, uri_item) ) {
+			all_valid = FALSE;
+			break;
+		}
+	}
+
+	if ( ret < 0 ) {
+		sieve_runtime_trace_error(renv, "invalid method uri item");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	sieve_interpreter_set_test_result(renv->interp, all_valid);
+	return SIEVE_EXEC_OK;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/enotify/vmodf-encodeurl.c
@@ -0,0 +1,88 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve-common.h"
+#include "sieve-code.h"
+
+#include "sieve-ext-variables.h"
+
+#include "ext-enotify-common.h"
+
+/*
+ * Encodeurl modifier
+ */
+
+bool mod_encodeurl_modify(string_t *in, string_t **result);
+
+const struct sieve_variables_modifier_def encodeurl_modifier = {
+	SIEVE_OBJECT("encodeurl", &encodeurl_operand, 0),
+	15,
+	mod_encodeurl_modify
+};
+
+/*
+ * Modifier operand
+ */
+
+static const struct sieve_extension_objects ext_enotify_modifiers =
+	SIEVE_VARIABLES_DEFINE_MODIFIER(encodeurl_modifier);
+
+const struct sieve_operand_def encodeurl_operand = {
+	.name = "modifier",
+	.ext_def = &enotify_extension,
+	.class = &sieve_variables_modifier_operand_class,
+	.interface = &ext_enotify_modifiers
+};
+
+/*
+ * Modifier implementation
+ */
+
+static const char _uri_reserved_lookup[256] = {
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 00
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 10
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1,  // 20
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,  // 30
+	1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 40
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0,  // 50
+	1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 60
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1,  // 70
+
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 80
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 90
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // A0
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // B0
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // C0
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // D0
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // E0
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // F0
+};
+
+bool mod_encodeurl_modify(string_t *in, string_t **result)
+{
+	unsigned int i;
+	const unsigned char *c;
+
+	if ( str_len(in) == 0 ) {
+		*result = in;
+		return TRUE;
+	}
+
+	*result = t_str_new(2*str_len(in));
+	c = str_data(in);
+
+	for ( i = 0; i < str_len(in); i++, c++ ) {
+		if ( (_uri_reserved_lookup[*c] & 0x01) != 0 ) {
+			str_printfa(*result, "%%%02X", *c);
+		} else {
+			str_append_c(*result, *c);
+		}
+	}
+
+	return TRUE;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/environment/Makefile.am
@@ -0,0 +1,24 @@
+noinst_LTLIBRARIES = libsieve_ext_environment.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../.. \
+	$(LIBDOVECOT_INCLUDE)
+
+tests = \
+	tst-environment.c
+
+libsieve_ext_environment_la_SOURCES = \
+	$(tests) \
+	ext-environment-common.c \
+	ext-environment.c
+
+public_headers = \
+	sieve-ext-environment.h
+
+headers = \
+	ext-environment-common.h
+
+pkginc_libdir=$(dovecot_pkgincludedir)/sieve
+pkginc_lib_HEADERS = $(public_headers)
+noinst_HEADERS = $(headers)
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/environment/ext-environment-common.c
@@ -0,0 +1,336 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "hash.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-interpreter.h"
+
+#include "ext-environment-common.h"
+
+struct ext_environment_interpreter_context;
+
+/*
+ * Core environment items
+ */
+
+static const struct sieve_environment_item *core_env_items[] = {
+	&domain_env_item,
+	&host_env_item,
+	&location_env_item,
+	&phase_env_item,
+	&name_env_item,
+	&version_env_item
+};
+
+static unsigned int core_env_items_count = N_ELEMENTS(core_env_items);
+
+static void sieve_environment_item_insert
+(struct ext_environment_interpreter_context *ctx,
+	const struct sieve_environment_item *item);
+
+/*
+ * Validator context
+ */
+
+struct ext_environment_interpreter_context {
+	HASH_TABLE(const char *,
+		   const struct sieve_environment_item *) name_items;
+	ARRAY(const struct sieve_environment_item *) prefix_items;
+
+	bool active:1;
+};
+
+static void ext_environment_interpreter_extension_free
+	(const struct sieve_extension *ext, struct sieve_interpreter *interp,
+		void *context);
+
+struct sieve_interpreter_extension environment_interpreter_extension = {
+	.ext_def = &environment_extension,
+	.free = ext_environment_interpreter_extension_free,
+};
+
+static struct ext_environment_interpreter_context *
+ext_environment_interpreter_context_create
+(const struct sieve_extension *this_ext, struct sieve_interpreter *interp)
+{
+	pool_t pool = sieve_interpreter_pool(interp);
+	struct ext_environment_interpreter_context *ctx;
+
+	ctx = p_new(pool, struct ext_environment_interpreter_context, 1);
+
+	hash_table_create
+		(&ctx->name_items, default_pool, 0, str_hash, strcmp);
+	i_array_init(&ctx->prefix_items, 16);
+
+	sieve_interpreter_extension_register
+		(interp, this_ext, &environment_interpreter_extension, (void *)ctx);
+	return ctx;
+}
+
+static void ext_environment_interpreter_extension_free
+(const struct sieve_extension *ext ATTR_UNUSED,
+	struct sieve_interpreter *interp ATTR_UNUSED, 	void *context)
+{
+	struct ext_environment_interpreter_context *ctx =
+		(struct ext_environment_interpreter_context *)context;
+
+	hash_table_destroy(&ctx->name_items);
+	array_free(&ctx->prefix_items);
+}
+
+static struct ext_environment_interpreter_context *
+ext_environment_interpreter_context_get
+(const struct sieve_extension *this_ext, struct sieve_interpreter *interp)
+{
+	struct ext_environment_interpreter_context *ctx =
+		(struct ext_environment_interpreter_context *)
+		sieve_interpreter_extension_get_context(interp, this_ext);
+
+	if ( ctx == NULL )
+		ctx = ext_environment_interpreter_context_create(this_ext, interp);
+
+	return ctx;
+}
+
+void ext_environment_interpreter_init
+(const struct sieve_extension *this_ext, struct sieve_interpreter *interp)
+{
+	struct ext_environment_interpreter_context *ctx;
+	unsigned int i;
+
+	/* Create our context */
+	ctx = ext_environment_interpreter_context_get(this_ext, interp);
+
+	for ( i = 0; i < core_env_items_count; i++ )
+		sieve_environment_item_insert(ctx, core_env_items[i]);
+
+	ctx->active = TRUE;
+}
+
+bool sieve_ext_environment_is_active
+(const struct sieve_extension *env_ext, struct sieve_interpreter *interp)
+{
+	struct ext_environment_interpreter_context *ctx =
+		ext_environment_interpreter_context_get(env_ext, interp);
+
+	return ( ctx != NULL && ctx->active );
+}
+
+/*
+ * Registration
+ */
+
+static void sieve_environment_item_insert
+(struct ext_environment_interpreter_context *ctx,
+	const struct sieve_environment_item *item)
+{
+	if (!item->prefix)
+		hash_table_insert(ctx->name_items, item->name, item);
+	else {
+		array_append(&ctx->prefix_items, &item, 1);
+	}
+}
+
+void sieve_environment_item_register
+(const struct sieve_extension *env_ext, struct sieve_interpreter *interp,
+	const struct sieve_environment_item *item)
+{
+	struct ext_environment_interpreter_context *ctx;
+
+	i_assert( sieve_extension_is(env_ext, environment_extension) );
+	ctx = ext_environment_interpreter_context_get(env_ext, interp);
+
+	sieve_environment_item_insert(ctx, item);
+}
+
+/*
+ * Retrieval
+ */
+
+static const struct sieve_environment_item *
+ext_environment_item_lookup
+(struct ext_environment_interpreter_context *ctx, const char **_name)
+{
+	const struct sieve_environment_item *const *item_idx;
+	const struct sieve_environment_item *item;
+	const char *name = *_name;
+
+	item = hash_table_lookup(ctx->name_items, name);
+	if ( item != NULL )
+		return item;
+
+	array_foreach(&ctx->prefix_items, item_idx) {
+		size_t prefix_len;
+
+		item = *item_idx;
+		i_assert(item->prefix);
+		prefix_len = strlen(item->name);
+
+		if ( str_begins(name, item->name)) {
+			if ( name[prefix_len] == '.' ) {
+				*_name = &name[prefix_len+1];
+				return item;
+			} else if ( name[prefix_len] == '\0' ) {
+				*_name = &name[prefix_len+1];
+				return item;
+			}
+		}
+	}
+	return NULL;
+}
+
+const char *ext_environment_item_get_value
+(const struct sieve_extension *env_ext,
+	const struct sieve_runtime_env *renv, const char *name)
+{
+	struct ext_environment_interpreter_context *ctx;
+	const struct sieve_environment_item *item;
+
+	i_assert( sieve_extension_is(env_ext, environment_extension) );
+	ctx =	ext_environment_interpreter_context_get(env_ext, renv->interp);
+
+	item = ext_environment_item_lookup(ctx, &name);
+	if ( item == NULL )
+		return NULL;
+
+	if ( item->value != NULL )
+		return item->value;
+
+	if ( item->get_value != NULL )
+		return item->get_value(renv, name);
+
+	return NULL;
+}
+
+/*
+ * Default environment items
+ */
+
+/* "domain":
+ *
+ *   The primary DNS domain associated with the Sieve execution context, usually
+ *   but not always a proper suffix of the host name.
+ */
+
+static const char *envit_domain_get_value
+(const struct sieve_runtime_env *renv,
+	const char *name ATTR_UNUSED)
+{
+	return renv->svinst->domainname;
+}
+
+const struct sieve_environment_item domain_env_item = {
+	.name = "domain",
+	.get_value = envit_domain_get_value,
+};
+
+/* "host":
+ *
+ *   The fully-qualified domain name of the host where the Sieve script is
+ *   executing.
+ */
+
+static const char *envit_host_get_value
+(const struct sieve_runtime_env *renv,
+	const char *name ATTR_UNUSED)
+{
+	return renv->svinst->hostname;
+}
+
+const struct sieve_environment_item host_env_item = {
+	.name = "host",
+	.get_value = envit_host_get_value,
+};
+
+/* "location":
+ *
+ *   Sieve evaluation can be performed at various different points as messages
+ *   are processed. This item provides additional information about the type of
+ *   service that is evaluating the script.  Possible values are:
+ *    "MTA" - the Sieve script is being evaluated by a Message Transfer Agent
+ *    "MDA" - evaluation is being performed by a Mail Delivery Agent
+ *    "MUA" - evaluation is being performed by a Mail User Agent (right...)
+ *    "MS"  - evaluation is being performed by a Message Store
+ */
+
+static const char *envit_location_get_value
+(const struct sieve_runtime_env *renv,
+	const char *name ATTR_UNUSED)
+{
+	switch ( renv->svinst->env_location ) {
+	case SIEVE_ENV_LOCATION_MDA:
+		return "MDA";
+	case SIEVE_ENV_LOCATION_MTA:
+		return "MTA";
+	case SIEVE_ENV_LOCATION_MS:
+		return "MS";
+	default:
+		break;
+	}
+	return NULL;
+}
+
+const struct sieve_environment_item location_env_item = {
+	.name = "location",
+	.get_value = envit_location_get_value
+};
+
+/* "phase":
+ *
+ *   The point relative to final delivery where the Sieve script is being
+ *   evaluated.  Possible values are "pre", "during", and "post", referring
+ *   respectively to processing before, during, and after final delivery has
+ *   taken place.
+ */
+
+static const char *envit_phase_get_value
+(const struct sieve_runtime_env *renv,
+	const char *name ATTR_UNUSED)
+{
+	switch ( renv->svinst->delivery_phase ) {
+	case SIEVE_DELIVERY_PHASE_PRE:
+		return "pre";
+	case SIEVE_DELIVERY_PHASE_DURING:
+		return "during";
+	case SIEVE_DELIVERY_PHASE_POST:
+		return "post";
+	default:
+		break;
+	}
+	return NULL;
+}
+
+const struct sieve_environment_item phase_env_item = {
+	.name = "phase",
+	.get_value = envit_phase_get_value
+};
+
+/* "name":
+ *
+ *  The product name associated with the Sieve interpreter.
+ */
+
+const struct sieve_environment_item name_env_item = {
+	.name = "name",
+	.value = PIGEONHOLE_NAME" Sieve"
+};
+
+/* "version":
+ *
+ * The product version associated with the Sieve interpreter. The meaning of the
+ * product version string is product-specific and should always be considered
+ * in the context of the product name given by the "name" item.
+ */
+
+const struct sieve_environment_item version_env_item = {
+	.name = "version",
+	.value = PIGEONHOLE_VERSION,
+};
+
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/environment/ext-environment-common.h
@@ -0,0 +1,53 @@
+#ifndef EXT_ENVIRONMENT_COMMON_H
+#define EXT_ENVIRONMENT_COMMON_H
+
+#include "lib.h"
+
+#include "sieve-common.h"
+
+#include "sieve-ext-environment.h"
+
+/*
+ * Extension
+ */
+
+extern const struct sieve_extension_def environment_extension;
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def tst_environment;
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def tst_environment_operation;
+
+/*
+ * Environment items
+ */
+
+extern const struct sieve_environment_item domain_env_item;
+extern const struct sieve_environment_item host_env_item;
+extern const struct sieve_environment_item location_env_item;
+extern const struct sieve_environment_item phase_env_item;
+extern const struct sieve_environment_item name_env_item;
+extern const struct sieve_environment_item version_env_item;
+
+/*
+ * Initialization
+ */
+
+bool ext_environment_init(const struct sieve_extension *ext, void **context);
+void ext_environment_deinit(const struct sieve_extension *ext);
+
+/*
+ * Validator context
+ */
+
+void ext_environment_interpreter_init
+(const struct sieve_extension *this_ext, struct sieve_interpreter *interp);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/environment/ext-environment.c
@@ -0,0 +1,59 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension variables
+ * -------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5183
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "unichar.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+
+#include "sieve-validator.h"
+
+#include "ext-environment-common.h"
+
+/*
+ * Extension
+ */
+
+static bool ext_environment_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+static bool ext_environment_interpreter_load
+(const struct sieve_extension *ext,
+	const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_extension_def environment_extension = {
+	.name = "environment",
+	.validator_load = ext_environment_validator_load,
+	.interpreter_load = ext_environment_interpreter_load,
+	SIEVE_EXT_DEFINE_OPERATION(tst_environment_operation)
+};
+
+static bool ext_environment_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	sieve_validator_register_command(valdtr, ext, &tst_environment);
+	return TRUE;
+}
+
+static bool ext_environment_interpreter_load
+(const struct sieve_extension *ext,
+	const struct sieve_runtime_env *renv,
+	sieve_size_t *address ATTR_UNUSED)
+{
+	ext_environment_interpreter_init(ext, renv->interp);
+	return TRUE;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/environment/sieve-ext-environment.h
@@ -0,0 +1,54 @@
+#ifndef SIEVE_EXT_ENVIRONMENT_H
+#define SIEVE_EXT_ENVIRONMENT_H
+
+#include "sieve-common.h"
+
+/*
+ * Environment extension
+ */
+
+/* FIXME: this is not suitable for future plugin support */
+
+extern const struct sieve_extension_def environment_extension;
+
+static inline const struct sieve_extension *
+sieve_ext_environment_get_extension
+(struct sieve_instance *svinst)
+{
+	return sieve_extension_register
+		(svinst, &environment_extension, FALSE);
+}
+
+static inline const struct sieve_extension *
+sieve_ext_environment_require_extension
+(struct sieve_instance *svinst)
+{
+	return sieve_extension_require
+		(svinst, &environment_extension, TRUE);
+}
+
+bool sieve_ext_environment_is_active
+	(const struct sieve_extension *env_ext,
+		struct sieve_interpreter *interp);
+
+/*
+ * Environment item
+ */
+
+struct sieve_environment_item {
+	const char *name;
+	bool prefix;
+
+	const char *value;
+	const char *(*get_value)
+		(const struct sieve_runtime_env *renv, const char *name);
+};
+
+void sieve_environment_item_register
+	(const struct sieve_extension *env_ext, struct sieve_interpreter *interp,
+		const struct sieve_environment_item *item);
+const char *ext_environment_item_get_value
+	(const struct sieve_extension *env_ext,
+		const struct sieve_runtime_env *renv, const char *name);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/environment/tst-environment.c
@@ -0,0 +1,215 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+#include "ext-environment-common.h"
+
+/*
+ * Environment test
+ *
+ * Syntax:
+ *   environment [COMPARATOR] [MATCH-TYPE]
+ *      <name: string> <key-list: string-list>
+ */
+
+static bool tst_environment_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool tst_environment_validate
+	(struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_environment_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+const struct sieve_command_def tst_environment = {
+	.identifier = "environment",
+	.type = SCT_TEST,
+	.positional_args = 2,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = tst_environment_registered,
+	.validate = tst_environment_validate,
+	.generate = tst_environment_generate
+};
+
+/*
+ * Environment operation
+ */
+
+static bool tst_environment_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_environment_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def tst_environment_operation = {
+	.mnemonic = "ENVIRONMENT",
+	.ext_def = &environment_extension,
+	.dump = tst_environment_operation_dump,
+	.execute = tst_environment_operation_execute
+};
+
+/*
+ * Test registration
+ */
+
+static bool tst_environment_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext ATTR_UNUSED,
+	struct sieve_command_registration *cmd_reg)
+{
+	/* The order of these is not significant */
+	sieve_comparators_link_tag(valdtr, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR);
+	sieve_match_types_link_tags(valdtr, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
+
+	return TRUE;
+}
+
+/*
+ * Test validation
+ */
+
+static bool tst_environment_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+	struct sieve_ast_argument *arg = tst->first_positional;
+	const struct sieve_match_type mcht_default =
+		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+	const struct sieve_comparator cmp_default =
+		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "name", 1, SAAT_STRING) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+		return FALSE;
+
+	arg = sieve_ast_argument_next(arg);
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "key list", 2, SAAT_STRING_LIST) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+		return FALSE;
+
+	/* Validate the key argument to a specified match type */
+	return sieve_match_type_validate
+		(valdtr, tst, arg, &mcht_default, &cmp_default);
+}
+
+/*
+ * Test generation
+ */
+
+static bool tst_environment_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	sieve_operation_emit(cgenv->sblock, cmd->ext, &tst_environment_operation);
+
+ 	/* Generate arguments */
+	if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
+		return FALSE;
+
+	return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_environment_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "ENVIRONMENT");
+	sieve_code_descend(denv);
+
+	/* Optional operands */
+	if ( sieve_match_opr_optional_dump(denv, address, NULL) != 0 )
+		return FALSE;
+
+	return
+		sieve_opr_string_dump(denv, address, "name") &&
+		sieve_opr_stringlist_dump(denv, address, "key list");
+}
+
+/*
+ * Code execution
+ */
+
+static int tst_environment_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	const struct sieve_extension *this_ext = renv->oprtn->ext;
+	struct sieve_match_type mcht =
+		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+	struct sieve_comparator cmp =
+		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+	string_t *name;
+	struct sieve_stringlist *value_list, *key_list;
+	const char *env_item;
+	int match, ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Handle match-type and comparator operands */
+	if ( sieve_match_opr_optional_read
+		(renv, address, NULL, &ret, &cmp, &mcht) < 0 )
+		return ret;
+
+	/* Read source */
+	if ( (ret=sieve_opr_string_read(renv, address, "name", &name)) <= 0 )
+		return ret;
+
+	/* Read key-list */
+	if ( (ret=sieve_opr_stringlist_read(renv, address, "key-list", &key_list))
+		<= 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "environment test");
+
+	env_item = ext_environment_item_get_value
+		(this_ext, renv, str_c(name));
+
+	if ( env_item != NULL ) {
+		/* Construct value list */
+		value_list = sieve_single_stringlist_create_cstr(renv, env_item, FALSE);
+
+		/* Perform match */
+		if ( (match=sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret))
+			< 0 )
+			return ret;
+	} else {
+		match = 0;
+
+		sieve_runtime_trace_descend(renv);
+		sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+			"environment item `%s' not found",
+			str_sanitize(str_c(name), 128));
+	}
+
+	/* Set test result for subsequent conditional jump */
+	sieve_interpreter_set_test_result(renv->interp, match > 0);
+	return SIEVE_EXEC_OK;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/ihave/Makefile.am
@@ -0,0 +1,22 @@
+noinst_LTLIBRARIES = libsieve_ext_ihave.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../.. \
+	$(LIBDOVECOT_INCLUDE)
+
+tests = \
+	tst-ihave.c
+
+commands = \
+	cmd-error.c
+
+libsieve_ext_ihave_la_SOURCES = \
+	$(tests) \
+	$(commands) \
+	ext-ihave-binary.c \
+	ext-ihave-common.c \
+	ext-ihave.c
+
+noinst_HEADERS = \
+	ext-ihave-binary.h \
+	ext-ihave-common.h
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/ihave/cmd-error.c
@@ -0,0 +1,131 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-ihave-common.h"
+
+/*
+ * Error command
+ *
+ * Syntax
+ *   error <message: string>
+ */
+
+static bool cmd_error_validate
+	(struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool cmd_error_generate
+	(const struct sieve_codegen_env *cgenv,	struct sieve_command *ctx);
+
+const struct sieve_command_def error_command = {
+	.identifier = "error",
+	.type = SCT_COMMAND,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = cmd_error_validate,
+	.generate = cmd_error_generate
+};
+
+/*
+ * Body operation
+ */
+
+static bool cmd_error_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_error_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def cmd_error_operation = {
+	.mnemonic = "ERROR",
+	.ext_def = &ihave_extension,
+	.code = EXT_IHAVE_OPERATION_ERROR,
+	.dump = cmd_error_operation_dump,
+	.execute = cmd_error_operation_execute
+};
+
+/*
+ * Validation
+ */
+
+static bool cmd_error_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+	struct sieve_ast_argument *arg = tst->first_positional;
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "message", 1, SAAT_STRING) ) {
+		return FALSE;
+	}
+
+	return sieve_validator_argument_activate(valdtr, tst, arg, FALSE);
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_error_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	(void)sieve_operation_emit(cgenv->sblock, cmd->ext, &cmd_error_operation);
+
+	/* Generate arguments */
+	return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_error_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "ERROR");
+	sieve_code_descend(denv);
+
+	return sieve_opr_string_dump(denv, address, "message");
+}
+
+/*
+ * Interpretation
+ */
+
+static int cmd_error_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	string_t *message;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Read message */
+
+	if ( (ret=sieve_opr_string_read(renv, address, "message", &message)) <= 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS, "error \"%s\"",
+		str_sanitize(str_c(message), 80));
+
+	sieve_runtime_error(renv, NULL, "%s", str_c(message));
+
+	return SIEVE_EXEC_FAILURE;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/ihave/ext-ihave-binary.c
@@ -0,0 +1,247 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-error.h"
+#include "sieve-script.h"
+#include "sieve-binary.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-ihave-common.h"
+#include "ext-ihave-binary.h"
+
+/*
+ * Forward declarations
+ */
+
+static bool ext_ihave_binary_pre_save
+	(const struct sieve_extension *ext, struct sieve_binary *sbin,
+		void *context, enum sieve_error *error_r);
+static bool ext_ihave_binary_open
+	(const struct sieve_extension *ext, struct sieve_binary *sbin,
+		void *context);
+static bool ext_ihave_binary_up_to_date
+	(const struct sieve_extension *ext, struct sieve_binary *sbin,
+		void *context, enum sieve_compile_flags cpflags);
+
+/*
+ * Binary include extension
+ */
+
+const struct sieve_binary_extension ihave_binary_ext = {
+	.extension = &ihave_extension,
+	.binary_pre_save = ext_ihave_binary_pre_save,
+	.binary_open = ext_ihave_binary_open,
+	.binary_up_to_date = ext_ihave_binary_up_to_date
+};
+
+/*
+ * Binary context management
+ */
+
+struct ext_ihave_binary_context {
+	struct sieve_binary *binary;
+	struct sieve_binary_block *block;
+
+	ARRAY(const char *) missing_extensions;
+};
+
+static struct ext_ihave_binary_context *ext_ihave_binary_create_context
+(const struct sieve_extension *this_ext, struct sieve_binary *sbin)
+{
+	pool_t pool = sieve_binary_pool(sbin);
+
+	struct ext_ihave_binary_context *ctx =
+		p_new(pool, struct ext_ihave_binary_context, 1);
+
+	ctx->binary = sbin;
+	p_array_init(&ctx->missing_extensions, pool, 64);
+
+	sieve_binary_extension_set(sbin, this_ext, &ihave_binary_ext, ctx);
+	return ctx;
+}
+
+struct ext_ihave_binary_context *ext_ihave_binary_get_context
+(const struct sieve_extension *this_ext, struct sieve_binary *sbin)
+{
+	struct ext_ihave_binary_context *ctx = (struct ext_ihave_binary_context *)
+		sieve_binary_extension_get_context(sbin, this_ext);
+
+	if ( ctx == NULL )
+		ctx = ext_ihave_binary_create_context(this_ext, sbin);
+
+	return ctx;
+}
+
+struct ext_ihave_binary_context *ext_ihave_binary_init
+(const struct sieve_extension *this_ext, struct sieve_binary *sbin,
+	struct sieve_ast *ast)
+{
+	struct ext_ihave_ast_context *ast_ctx =
+		ext_ihave_get_ast_context(this_ext, ast);
+	struct ext_ihave_binary_context *binctx;
+	const char *const *exts;
+	unsigned int i, count;
+
+	binctx = ext_ihave_binary_get_context(this_ext, sbin);
+
+	exts = array_get(&ast_ctx->missing_extensions, &count);
+
+	if ( count > 0 ) {
+		pool_t pool = sieve_binary_pool(sbin);
+
+		if ( binctx->block == NULL )
+			binctx->block = sieve_binary_extension_create_block(sbin, this_ext);
+
+		for ( i = 0; i < count; i++ ) {
+			const char *ext_name = p_strdup(pool, exts[i]);
+
+			array_append(&binctx->missing_extensions, &ext_name, 1);
+		}
+	}
+
+	return binctx;
+}
+
+/*
+ * Binary extension
+ */
+
+static bool ext_ihave_binary_pre_save
+(const struct sieve_extension *ext, struct sieve_binary *sbin,
+	void *context, enum sieve_error *error_r ATTR_UNUSED)
+{
+	struct ext_ihave_binary_context *binctx =
+		(struct ext_ihave_binary_context *) context;
+	const char *const *exts;
+	unsigned int count, i;
+
+	exts = array_get(&binctx->missing_extensions, &count);
+
+	if ( binctx->block != NULL )
+		sieve_binary_block_clear(binctx->block);
+
+	if ( count > 0 ) {
+		if ( binctx->block == NULL )
+			binctx->block = sieve_binary_extension_create_block(sbin, ext);
+
+		sieve_binary_emit_unsigned(binctx->block, count);
+
+		for ( i = 0; i < count; i++ ) {
+			sieve_binary_emit_cstring(binctx->block, exts[i]);
+		}
+	}
+
+	return TRUE;
+}
+
+static bool ext_ihave_binary_open
+(const struct sieve_extension *ext, struct sieve_binary *sbin, void *context)
+{
+	struct sieve_instance *svinst = ext->svinst;
+	struct ext_ihave_binary_context *binctx =
+		(struct ext_ihave_binary_context *) context;
+	struct sieve_binary_block *sblock;
+	unsigned int i, count, block_id;
+	sieve_size_t offset;
+
+	sblock = sieve_binary_extension_get_block(sbin, ext);
+
+	if ( sblock != NULL ) {
+		binctx->block = sblock;
+		block_id = sieve_binary_block_get_id(sblock);
+
+		offset = 0;
+
+		/* Read number of missing extensions to read subsequently */
+		if ( !sieve_binary_read_unsigned(sblock, &offset, &count) ) {
+			sieve_sys_error(svinst,
+				"ihave: failed to read missing extension count "
+				"from block %d of binary %s", block_id, sieve_binary_path(sbin));
+			return FALSE;
+		}
+
+		/* Read dependencies */
+		for ( i = 0; i < count; i++ ) {
+			string_t *ext_name;
+			const char *name;
+
+			if ( !sieve_binary_read_string(sblock, &offset, &ext_name) ) {
+				/* Binary is corrupt, recompile */
+				sieve_sys_error(svinst,
+					"ihave: failed to read missing extension name "
+					"from block %d of binary %s", block_id, sieve_binary_path(sbin));
+				return FALSE;
+			}
+
+			name = str_c(ext_name);
+			array_append(&binctx->missing_extensions, &name, 1);
+		}
+	}
+
+	return TRUE;
+}
+
+static bool ext_ihave_binary_up_to_date
+(const struct sieve_extension *ext, struct sieve_binary *sbin ATTR_UNUSED,
+	void *context, enum sieve_compile_flags cpflags)
+{
+	struct ext_ihave_binary_context *binctx =
+		(struct ext_ihave_binary_context *) context;
+	const struct sieve_extension *mext;
+	const char *const *mexts;
+	unsigned int count, i;
+
+	mexts = array_get(&binctx->missing_extensions, &count);
+	for ( i = 0; i < count; i++ ) {
+		if ( (mext=sieve_extension_get_by_name(ext->svinst, mexts[i])) != NULL &&
+			((cpflags & SIEVE_COMPILE_FLAG_NOGLOBAL) == 0 || !mext->global) )
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+/*
+ * Main extension interface
+ */
+
+bool ext_ihave_binary_load
+(const struct sieve_extension *ext, struct sieve_binary *sbin)
+{
+	(void)ext_ihave_binary_get_context(ext, sbin);
+
+	return TRUE;
+}
+
+bool ext_ihave_binary_dump
+(const struct sieve_extension *ext, struct sieve_dumptime_env *denv)
+{
+	struct sieve_binary *sbin = denv->sbin;
+	struct ext_ihave_binary_context *binctx =
+		ext_ihave_binary_get_context(ext, sbin);
+	const char *const *exts;
+	unsigned int count, i;
+
+	exts = array_get(&binctx->missing_extensions, &count);
+
+	if ( count > 0 ) {
+		sieve_binary_dump_sectionf(denv,
+			"Extensions missing at compile (block: %d)",
+			sieve_binary_block_get_id(binctx->block));
+
+		for ( i = 0; i < count; i++ ) {
+			sieve_binary_dumpf(denv, "  -  %s\n", exts[i]);
+		}
+	}
+
+	return TRUE;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/ihave/ext-ihave-binary.h
@@ -0,0 +1,33 @@
+#ifndef EXT_IHAVE_BINARY_H
+#define EXT_IHAVE_BINARY_H
+
+/*
+ * Binary context management
+ */
+
+struct ext_ihave_binary_context;
+
+struct ext_ihave_binary_context *ext_ihave_binary_get_context
+	(const struct sieve_extension *this_ext, struct sieve_binary *sbin);
+struct ext_ihave_binary_context *ext_ihave_binary_init
+	(const struct sieve_extension *this_ext, struct sieve_binary *sbin,
+		struct sieve_ast *ast);
+
+/*
+ * Registering missing extension
+ */
+
+void ext_ihave_binary_add_missing_extension
+	(struct ext_ihave_binary_context *binctx, const char *ext_name);
+
+/*
+ * Main extension interface
+ */
+
+bool ext_ihave_binary_load
+	(const struct sieve_extension *ext, struct sieve_binary *sbin);
+bool ext_ihave_binary_dump
+	(const struct sieve_extension *ext, struct sieve_dumptime_env *denv);
+
+#endif
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/ihave/ext-ihave-common.c
@@ -0,0 +1,52 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-ast.h"
+
+#include "ext-ihave-common.h"
+
+/*
+ * AST context management
+ */
+
+struct ext_ihave_ast_context *ext_ihave_get_ast_context
+(const struct sieve_extension *this_ext, struct sieve_ast *ast)
+{
+	struct ext_ihave_ast_context *actx = (struct ext_ihave_ast_context *)
+		sieve_ast_extension_get_context(ast, this_ext);
+	pool_t pool;
+
+	if ( actx != NULL )
+		return actx;
+
+	pool = sieve_ast_pool(ast);
+	actx = p_new(pool, struct ext_ihave_ast_context, 1);
+	p_array_init(&actx->missing_extensions, pool, 64);
+
+	sieve_ast_extension_set_context(ast, this_ext, (void *) actx);
+
+	return actx;
+}
+
+void ext_ihave_ast_add_missing_extension
+(const struct sieve_extension *this_ext, struct sieve_ast *ast,
+	const char *ext_name)
+{
+	struct ext_ihave_ast_context *actx =
+		ext_ihave_get_ast_context(this_ext, ast);
+	const char *const *exts;
+	unsigned int i, count;
+
+	exts = array_get(&actx->missing_extensions, &count);
+	for ( i = 0; i < count; i++ ) {
+		if ( strcmp(exts[i], ext_name) == 0 )
+			return;
+	}
+
+	array_append(&actx->missing_extensions, &ext_name, 1);
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/ihave/ext-ihave-common.h
@@ -0,0 +1,52 @@
+#ifndef EXT_IHAVE_COMMON_H
+#define EXT_IHAVE_COMMON_H
+
+#include "sieve-common.h"
+
+/*
+ * Extensions
+ */
+
+extern const struct sieve_extension_def ihave_extension;
+
+/*
+ * Tests
+ */
+
+extern const struct sieve_command_def ihave_test;
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def error_command;
+
+/*
+ * Operations
+ */
+
+enum ext_ihave_opcode {
+	EXT_IHAVE_OPERATION_IHAVE,
+	EXT_IHAVE_OPERATION_ERROR
+};
+
+extern const struct sieve_operation_def tst_ihave_operation;
+extern const struct sieve_operation_def cmd_error_operation;
+
+/*
+ * AST context
+ */
+
+struct ext_ihave_ast_context {
+  ARRAY(const char *) missing_extensions;
+};
+
+struct ext_ihave_ast_context *ext_ihave_get_ast_context
+	(const struct sieve_extension *this_ext, struct sieve_ast *ast);
+
+void ext_ihave_ast_add_missing_extension
+	(const struct sieve_extension *this_ext, struct sieve_ast *ast,
+		const char *ext_name);
+
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/ihave/ext-ihave.c
@@ -0,0 +1,70 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension ihave
+ * ---------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5463
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+
+#include "ext-ihave-common.h"
+#include "ext-ihave-binary.h"
+
+/*
+ * Operations
+ */
+
+const struct sieve_operation_def *ext_ihave_operations[] = {
+	&tst_ihave_operation,
+	&cmd_error_operation
+};
+
+/*
+ * Extension
+ */
+
+static bool ext_ihave_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *validator);
+static bool ext_ihave_generator_load
+	(const struct sieve_extension *ext, const struct sieve_codegen_env *cgenv);
+
+const struct sieve_extension_def ihave_extension = {
+	"ihave",
+	.version = 1,
+	.validator_load = ext_ihave_validator_load,
+	.generator_load = ext_ihave_generator_load,
+	.binary_load = ext_ihave_binary_load,
+	.binary_dump = ext_ihave_binary_dump,
+	SIEVE_EXT_DEFINE_OPERATIONS(ext_ihave_operations)
+};
+
+static bool ext_ihave_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *validator)
+{
+	sieve_validator_register_command(validator, ext, &ihave_test);
+	sieve_validator_register_command(validator, ext, &error_command);
+
+	return TRUE;
+}
+
+static bool ext_ihave_generator_load
+(const struct sieve_extension *ext, const struct sieve_codegen_env *cgenv)
+{
+	(void)ext_ihave_binary_init(ext, cgenv->sbin, cgenv->ast);
+
+	return TRUE;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/ihave/tst-ihave.c
@@ -0,0 +1,283 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-extensions.h"
+#include "sieve-code.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+
+#include "ext-ihave-common.h"
+
+/*
+ * Ihave test
+ *
+ * Syntax:
+ *   ihave <capabilities: string-list>
+ */
+
+static bool tst_ihave_validate
+	(struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_ihave_validate_const
+	(struct sieve_validator *valdtr, struct sieve_command *tst,
+		int *const_current, int const_next);
+static bool tst_ihave_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *tst);
+
+const struct sieve_command_def ihave_test = {
+	.identifier = "ihave",
+	.type = SCT_TEST,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = tst_ihave_validate,
+	.validate_const = tst_ihave_validate_const,
+	.generate = tst_ihave_generate
+};
+
+/*
+ * Ihave operation
+ */
+
+static bool tst_ihave_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_ihave_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def tst_ihave_operation = {
+	.mnemonic = "IHAVE",
+	.ext_def = &ihave_extension,
+	.code = EXT_IHAVE_OPERATION_IHAVE,
+	.dump = tst_ihave_operation_dump,
+	.execute = tst_ihave_operation_execute
+};
+
+/*
+ * Code validation
+ */
+
+static bool tst_ihave_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+	struct _capability {
+		const struct sieve_extension *ext;
+		struct sieve_ast_argument *arg;
+	};
+
+	struct sieve_ast_argument *arg = tst->first_positional;
+	struct sieve_ast_argument *stritem;
+	enum sieve_compile_flags cpflags = sieve_validator_compile_flags(valdtr);
+	bool no_global = ( (cpflags & SIEVE_COMPILE_FLAG_NOGLOBAL) != 0 );
+	ARRAY(struct _capability) capabilities;
+	struct _capability capability;
+	const struct _capability *caps;
+	unsigned int i, count;
+	bool all_known = TRUE;
+
+	t_array_init(&capabilities, 64);
+
+	tst->data = (void *) FALSE;
+
+	/* Check stringlist argument */
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "capabilities", 1, SAAT_STRING_LIST) ) {
+		return FALSE;
+	}
+
+	switch ( sieve_ast_argument_type(arg) ) {
+	case SAAT_STRING:
+		/* Single string */
+		capability.arg = arg;
+		capability.ext = sieve_extension_get_by_name
+			(tst->ext->svinst, sieve_ast_argument_strc(arg));
+
+		if ( capability.ext == NULL || (no_global && capability.ext->global)) {
+			all_known = FALSE;
+
+			ext_ihave_ast_add_missing_extension
+				(tst->ext, tst->ast_node->ast, sieve_ast_argument_strc(arg));
+		} else {
+			array_append(&capabilities, &capability, 1);
+		}
+
+		break;
+
+	case SAAT_STRING_LIST:
+		/* String list */
+		stritem = sieve_ast_strlist_first(arg);
+
+		while ( stritem != NULL ) {
+			capability.arg = stritem;
+			capability.ext = sieve_extension_get_by_name
+				(tst->ext->svinst, sieve_ast_argument_strc(stritem));
+
+			if ( capability.ext == NULL || (no_global && capability.ext->global)) {
+				all_known = FALSE;
+
+				ext_ihave_ast_add_missing_extension
+					(tst->ext, tst->ast_node->ast, sieve_ast_argument_strc(stritem));
+			} else {
+				array_append(&capabilities, &capability, 1);
+			}
+
+			stritem = sieve_ast_strlist_next(stritem);
+		}
+
+		break;
+	default:
+		i_unreached();
+	}
+
+	if ( !all_known )
+		return TRUE;
+
+	/* RFC 5463, Section 4, page 4:
+	 *
+	 * The "ihave" extension is designed to be used with other extensions
+	 * that add tests, actions, comparators, or arguments.  Implementations
+	 * MUST NOT allow it to be used with extensions that change the
+	 * underlying Sieve grammar, or extensions like encoded-character
+	 * [RFC5228], or variables [RFC5229] that change how the content of
+	 * Sieve scripts are interpreted.  The test MUST fail and the extension
+	 * MUST NOT be enabled if such usage is attempted.
+	 *
+	 * FIXME: current implementation of this restriction is hardcoded and
+	 * therefore highly inflexible
+	 */
+	caps = array_get(&capabilities, &count);
+	for ( i = 0; i < count; i++ ) {
+		if ( sieve_extension_name_is(caps[i].ext, "variables") ||
+			sieve_extension_name_is(caps[i].ext, "encoded-character") )
+			return TRUE;
+	}
+
+	/* Load all extensions */
+	caps = array_get(&capabilities, &count);
+	for ( i = 0; i < count; i++ ) {
+		if ( !sieve_validator_extension_load
+			(valdtr, tst, caps[i].arg, caps[i].ext, FALSE) )
+			return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate
+		(valdtr, tst, arg, FALSE) )
+		return FALSE;
+
+	tst->data = (void *) TRUE;
+	return TRUE;
+}
+
+static bool tst_ihave_validate_const
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *tst,
+	int *const_current, int const_next ATTR_UNUSED)
+{
+	if ( (bool)tst->data == TRUE )
+		*const_current = -1;
+	else
+		*const_current = 0;
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+bool tst_ihave_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
+{
+	/* Emit opcode */
+	sieve_operation_emit(cgenv->sblock,
+		tst->ext, &tst_ihave_operation);
+
+	/* Generate arguments */
+	return sieve_generate_arguments(cgenv, tst, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_ihave_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "IHAVE");
+	sieve_code_descend(denv);
+
+	return sieve_opr_stringlist_dump
+		(denv, address, "capabilities");
+}
+
+/*
+ * Code execution
+ */
+
+static int tst_ihave_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	struct sieve_instance *svinst = renv->svinst;
+	struct sieve_stringlist *capabilities;
+	string_t *cap_item;
+	bool matched;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Read capabilities */
+	if ( (ret=sieve_opr_stringlist_read
+		(renv, address, "capabilities", &capabilities)) <= 0 )
+		return ret;
+
+	/*
+	 * Perform test
+	 */
+
+	/* Perform the test */
+	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "ihave test");
+	sieve_runtime_trace_descend(renv);
+
+	cap_item = NULL;
+	matched = TRUE;
+	while ( matched &&
+		(ret=sieve_stringlist_next_item(capabilities, &cap_item)) > 0 ) {
+		const struct sieve_extension *ext;
+		int sret;
+
+		ext = sieve_extension_get_by_name(svinst, str_c(cap_item));
+		if (ext == NULL) {
+			sieve_runtime_trace_error(renv,
+				"ihave: invalid extension name");
+			return SIEVE_EXEC_BIN_CORRUPT;
+		}
+		sret = sieve_interpreter_extension_start(renv->interp, ext);
+		if ( sret == SIEVE_EXEC_FAILURE ) {
+			sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+				"extension `%s' not available",
+				sieve_extension_name(ext));
+			matched = FALSE;
+		} else if ( sret == SIEVE_EXEC_OK ) {
+			sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+				"extension `%s' available",
+				sieve_extension_name(ext));
+		} else {
+			return sret;
+		}
+	}
+	if ( ret < 0 ) {
+		sieve_runtime_trace_error(renv,
+			"invalid capabilities item");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	/* Set test result for subsequent conditional jump */
+	sieve_interpreter_set_test_result(renv->interp, matched);
+	return SIEVE_EXEC_OK;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/imap4flags/Makefile.am
@@ -0,0 +1,33 @@
+noinst_LTLIBRARIES = libsieve_ext_imap4flags.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../.. \
+	-I$(srcdir)/../variables  \
+	$(LIBDOVECOT_INCLUDE)
+
+commands = \
+	cmd-flag.c
+
+tests = \
+	tst-hasflag.c
+
+tags = \
+	tag-flags.c
+
+libsieve_ext_imap4flags_la_SOURCES = \
+	ext-imap4flags-common.c \
+	$(commands) \
+	$(tests) \
+	$(tags) \
+	ext-imap4flags.c \
+	ext-imapflags.c
+
+public_headers = \
+	sieve-ext-imap4flags.h
+
+headers = \
+	ext-imap4flags-common.h
+
+pkginc_libdir=$(dovecot_pkgincludedir)/sieve
+pkginc_lib_HEADERS = $(public_headers)
+noinst_HEADERS = $(headers)
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/imap4flags/cmd-flag.c
@@ -0,0 +1,251 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-code.h"
+#include "sieve-stringlist.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-imap4flags-common.h"
+
+/*
+ * Commands
+ */
+
+/* Forward declarations */
+
+static bool cmd_flag_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+/* Setflag command
+ *
+ * Syntax:
+ *   setflag [<variablename: string>] <list-of-flags: string-list>
+ */
+
+const struct sieve_command_def cmd_setflag = {
+	.identifier = "setflag",
+	.type = SCT_COMMAND,
+	.positional_args = -1, /* We check positional arguments ourselves */
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = ext_imap4flags_command_validate,
+	.generate = cmd_flag_generate
+};
+
+/* Addflag command
+ *
+ * Syntax:
+ *   addflag [<variablename: string>] <list-of-flags: string-list>
+ */
+
+const struct sieve_command_def cmd_addflag = {
+	.identifier = "addflag",
+	.type = SCT_COMMAND,
+	.positional_args = -1, /* We check positional arguments ourselves */
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = ext_imap4flags_command_validate,
+	.generate = cmd_flag_generate
+};
+
+
+/* Removeflag command
+ *
+ * Syntax:
+ *   removeflag [<variablename: string>] <list-of-flags: string-list>
+ */
+
+const struct sieve_command_def cmd_removeflag = {
+	.identifier = "removeflag",
+	.type = SCT_COMMAND,
+	.positional_args = -1, /* We check positional arguments ourselves */
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = ext_imap4flags_command_validate,
+	.generate = cmd_flag_generate
+};
+
+/*
+ * Operations
+ */
+
+/* Forward declarations */
+
+bool cmd_flag_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_flag_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+/* Setflag operation */
+
+const struct sieve_operation_def setflag_operation = {
+	.mnemonic = "SETFLAG",
+	.ext_def = &imap4flags_extension,
+	.code = EXT_IMAP4FLAGS_OPERATION_SETFLAG,
+	.dump = cmd_flag_operation_dump,
+	.execute = cmd_flag_operation_execute
+};
+
+/* Addflag operation */
+
+const struct sieve_operation_def addflag_operation = {
+	.mnemonic = "ADDFLAG",
+	.ext_def = &imap4flags_extension,
+	.code = EXT_IMAP4FLAGS_OPERATION_ADDFLAG,
+	.dump = cmd_flag_operation_dump,
+	.execute = cmd_flag_operation_execute
+};
+
+/* Removeflag operation */
+
+const struct sieve_operation_def removeflag_operation = {
+	.mnemonic = "REMOVEFLAG",
+	.ext_def = &imap4flags_extension,
+	.code = EXT_IMAP4FLAGS_OPERATION_REMOVEFLAG,
+	.dump = cmd_flag_operation_dump,
+	.execute = cmd_flag_operation_execute
+};
+
+/*
+ * Code generation
+ */
+
+static bool cmd_flag_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *arg1, *arg2;
+
+	/* Emit operation */
+	if ( sieve_command_is(cmd, cmd_setflag) )
+		sieve_operation_emit(cgenv->sblock, cmd->ext, &setflag_operation);
+	else if ( sieve_command_is(cmd, cmd_addflag) )
+		sieve_operation_emit(cgenv->sblock, cmd->ext, &addflag_operation);
+	else if ( sieve_command_is(cmd, cmd_removeflag) )
+		sieve_operation_emit(cgenv->sblock, cmd->ext, &removeflag_operation);
+
+	arg1 = cmd->first_positional;
+	arg2 = sieve_ast_argument_next(arg1);
+
+	if ( arg2 == NULL ) {
+		/* No variable */
+		sieve_opr_omitted_emit(cgenv->sblock);
+		if ( !sieve_generate_argument(cgenv, arg1, cmd) )
+			return FALSE;
+	} else {
+		/* Full command */
+		if ( !sieve_generate_argument(cgenv, arg1, cmd) )
+			return FALSE;
+		if ( !sieve_generate_argument(cgenv, arg2, cmd) )
+			return FALSE;
+	}
+	return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+bool cmd_flag_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	struct sieve_operand oprnd;
+
+	sieve_code_dumpf(denv, "%s", sieve_operation_mnemonic(denv->oprtn));
+	sieve_code_descend(denv);
+
+	sieve_code_mark(denv);
+	if ( !sieve_operand_read(denv->sblock, address, NULL, &oprnd) ) {
+		sieve_code_dumpf(denv, "ERROR: INVALID OPERAND");
+		return FALSE;
+	}
+
+	if ( !sieve_operand_is_omitted(&oprnd) ) {
+		return
+			sieve_opr_string_dump_data(denv, &oprnd, address, "variable name") &&
+			sieve_opr_stringlist_dump(denv, address, "list of flags");
+	}
+
+	return
+		sieve_opr_stringlist_dump(denv, address, "list of flags");
+}
+
+/*
+ * Code execution
+ */
+
+static int cmd_flag_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	const struct sieve_operation *op = renv->oprtn;
+	struct sieve_operand oprnd;
+	struct sieve_stringlist *flag_list;
+	struct sieve_variable_storage *storage;
+	unsigned int var_index;
+	ext_imapflag_flag_operation_t flag_op;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Read bare operand (two types possible) */
+	if ( (ret=sieve_operand_runtime_read
+		(renv, address, NULL, &oprnd)) <= 0 )
+		return ret;
+
+	/* Variable operand (optional) */
+	if ( !sieve_operand_is_omitted(&oprnd) ) {
+		/* Read the variable operand */
+		if ( (ret=sieve_variable_operand_read_data
+			(renv, &oprnd, address, "variable", &storage, &var_index)) <= 0 )
+			return ret;
+
+		/* Read flag list */
+		if ( (ret=sieve_opr_stringlist_read(renv, address, "flag-list", &flag_list))
+			<= 0 )
+			return ret;
+
+	/* Flag-list operand */
+	} else {
+		storage = NULL;
+		var_index = 0;
+
+		/* Read flag list */
+		if ( (ret=sieve_opr_stringlist_read(renv, address,
+			"flag-list", &flag_list)) <= 0 )
+			return ret;
+	}
+
+	/*
+	 * Perform operation
+	 */
+
+	/* Determine what to do */
+
+	if ( sieve_operation_is(op, setflag_operation) ) {
+		sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS, "setflag command");
+		flag_op = sieve_ext_imap4flags_set_flags;
+	} else if ( sieve_operation_is(op, addflag_operation) ) {
+		sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS, "addflag command");
+		flag_op = sieve_ext_imap4flags_add_flags;
+	} else if ( sieve_operation_is(op, removeflag_operation) ) {
+		sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS, "removeflag command");
+		flag_op = sieve_ext_imap4flags_remove_flags;
+	} else {
+		i_unreached();
+	}
+
+	sieve_runtime_trace_descend(renv);
+
+	/* Perform requested operation */
+	return flag_op(renv, op->ext, storage, var_index, flag_list);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/imap4flags/ext-imap4flags-common.c
@@ -0,0 +1,733 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "mail-storage.h"
+#include "imap-arg.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-stringlist.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+#include "sieve-dump.h"
+
+#include "sieve-ext-variables.h"
+
+#include "ext-imap4flags-common.h"
+
+/*
+ * Tagged arguments
+ */
+
+extern const struct sieve_argument_def tag_flags;
+extern const struct sieve_argument_def tag_flags_implicit;
+
+/*
+ * Common command functions
+ */
+
+bool ext_imap4flags_command_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *arg = cmd->first_positional;
+	struct sieve_ast_argument *arg2;
+	const struct sieve_extension *var_ext;
+
+	/* Check arguments */
+
+	if ( arg == NULL ) {
+		sieve_command_validate_error(valdtr, cmd,
+			"the %s %s expects at least one argument, but none was found",
+			sieve_command_identifier(cmd), sieve_command_type_name(cmd));
+		return FALSE;
+	}
+
+	if ( sieve_ast_argument_type(arg) != SAAT_STRING &&
+		sieve_ast_argument_type(arg) != SAAT_STRING_LIST )
+	{
+		sieve_argument_validate_error(valdtr, arg,
+			"the %s %s expects either a string (variable name) or "
+			"a string-list (list of flags) as first argument, but %s was found",
+			sieve_command_identifier(cmd), sieve_command_type_name(cmd),
+			sieve_ast_argument_name(arg));
+		return FALSE;
+	}
+
+	arg2 = sieve_ast_argument_next(arg);
+	if ( arg2 != NULL ) {
+		/* First, check syntax sanity */
+
+		if ( sieve_ast_argument_type(arg) != SAAT_STRING )
+		{
+			if ( sieve_command_is(cmd, tst_hasflag) ) {
+				if ( sieve_ast_argument_type(arg) != SAAT_STRING_LIST ) {
+					sieve_argument_validate_error(valdtr, arg,
+						"if a second argument is specified for the hasflag, the first "
+						"must be a string-list (variable-list), but %s was found",
+						sieve_ast_argument_name(arg));
+					return FALSE;
+				}
+			} else {
+				sieve_argument_validate_error(valdtr, arg,
+					"if a second argument is specified for the %s %s, the first "
+					"must be a string (variable name), but %s was found",
+					sieve_command_identifier(cmd), sieve_command_type_name(cmd),
+					sieve_ast_argument_name(arg));
+				return FALSE;
+			}
+		}
+
+		/* Then, check whether the second argument is permitted */
+
+		var_ext = sieve_ext_variables_get_extension(cmd->ext->svinst);
+
+		if ( var_ext == NULL || !sieve_ext_variables_is_active(var_ext, valdtr) )
+			{
+			sieve_argument_validate_error(valdtr,arg,
+				"the %s %s only allows for the specification of a "
+				"variable name when the variables extension is active",
+				sieve_command_identifier(cmd), sieve_command_type_name(cmd));
+			return FALSE;
+		}
+
+		if ( !sieve_variable_argument_activate(var_ext, var_ext,
+			valdtr, cmd, arg, !sieve_command_is(cmd, tst_hasflag) ) )
+			return FALSE;
+
+		if ( sieve_ast_argument_type(arg2) != SAAT_STRING &&
+			sieve_ast_argument_type(arg2) != SAAT_STRING_LIST )
+		{
+			sieve_argument_validate_error(valdtr, arg2,
+				"the %s %s expects a string list (list of flags) as "
+				"second argument when two arguments are specified, "
+				"but %s was found",
+				sieve_command_identifier(cmd), sieve_command_type_name(cmd),
+				sieve_ast_argument_name(arg2));
+			return FALSE;
+		}
+	} else
+		arg2 = arg;
+
+	if ( !sieve_validator_argument_activate(valdtr, cmd, arg2, FALSE) )
+		return FALSE;
+
+	if ( !sieve_command_is(cmd, tst_hasflag) &&
+		sieve_argument_is_string_literal(arg2) ) {
+		struct ext_imap4flags_iter fiter;
+		const char *flag;
+
+		/* Warn the user about validity of verifiable flags */
+		ext_imap4flags_iter_init(&fiter, sieve_ast_argument_str(arg));
+
+		while ( (flag=ext_imap4flags_iter_get_flag(&fiter)) != NULL ) {
+			if ( !sieve_ext_imap4flags_flag_is_valid(flag) ) {
+				sieve_argument_validate_warning(valdtr, arg,
+					"IMAP flag '%s' specified for the %s command is invalid "
+					"and will be ignored (only first invalid is reported)",
+					str_sanitize(flag, 64), sieve_command_identifier(cmd));
+				break;
+			}
+		}
+	}
+
+	return TRUE;
+}
+
+/*
+ * Flags tag registration
+ */
+
+void ext_imap4flags_attach_flags_tag
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	const char *command)
+{
+	/* Register :flags tag with the command and we don't care whether it is
+	 * registered or even whether it will be registered at all. The validator
+	 * handles either situation gracefully
+	 */
+
+	/* Tag specified by user */
+	sieve_validator_register_external_tag
+		(valdtr, command, ext, &tag_flags, SIEVE_OPT_SIDE_EFFECT);
+}
+
+void sieve_ext_imap4flags_register_side_effect
+(struct sieve_validator *valdtr, const struct sieve_extension *flg_ext,
+	const char *command)
+{
+	/* Implicit tag if none is specified */
+	sieve_validator_register_persistent_tag
+		(valdtr, command, flg_ext, &tag_flags_implicit);
+}
+
+
+/*
+ * Result context
+ */
+
+struct ext_imap4flags_result_context {
+    string_t *internal_flags;
+};
+
+static void _get_initial_flags
+(struct sieve_result *result, string_t *flags)
+{
+	const struct sieve_message_data *msgdata =
+		sieve_result_get_message_data(result);
+	enum mail_flags mail_flags;
+	const char *const *mail_keywords;
+
+	mail_flags = mail_get_flags(msgdata->mail);
+	mail_keywords = mail_get_keywords(msgdata->mail);
+
+	if ( (mail_flags & MAIL_FLAGGED) > 0 )
+		str_printfa(flags, " \\flagged");
+
+	if ( (mail_flags & MAIL_ANSWERED) > 0 )
+		str_printfa(flags, " \\answered");
+
+	if ( (mail_flags & MAIL_DELETED) > 0 )
+		str_printfa(flags, " \\deleted");
+
+	if ( (mail_flags & MAIL_SEEN) > 0 )
+		str_printfa(flags, " \\seen");
+
+	if ( (mail_flags & MAIL_DRAFT) > 0 )
+		str_printfa(flags, " \\draft");
+
+	while ( *mail_keywords != NULL ) {
+		str_printfa(flags, " %s", *mail_keywords);
+		mail_keywords++;
+	}
+}
+
+static inline struct ext_imap4flags_result_context *_get_result_context
+(const struct sieve_extension *this_ext, struct sieve_result *result)
+{
+	struct ext_imap4flags_result_context *rctx =
+		(struct ext_imap4flags_result_context *)
+		sieve_result_extension_get_context(result, this_ext);
+
+	if ( rctx == NULL ) {
+		pool_t pool = sieve_result_pool(result);
+
+		rctx =p_new(pool, struct ext_imap4flags_result_context, 1);
+		rctx->internal_flags = str_new(pool, 32);
+		_get_initial_flags(result, rctx->internal_flags);
+
+		sieve_result_extension_set_context
+			(result, this_ext, rctx);
+	}
+
+	return rctx;
+}
+
+static string_t *_get_flags_string
+(const struct sieve_extension *this_ext, struct sieve_result *result)
+{
+	struct ext_imap4flags_result_context *ctx =
+		_get_result_context(this_ext, result);
+
+	return ctx->internal_flags;
+}
+
+/*
+ * Runtime initialization
+ */
+
+static int ext_imap4flags_runtime_init
+(const struct sieve_extension *ext,
+	const struct sieve_runtime_env *renv,
+	void *context ATTR_UNUSED, bool deferred ATTR_UNUSED)
+{
+	sieve_result_add_implicit_side_effect
+		(renv->result, NULL, TRUE, ext, &flags_side_effect, NULL);
+	return SIEVE_EXEC_OK;
+}
+
+const struct sieve_interpreter_extension
+imap4flags_interpreter_extension = {
+	.ext_def = &imap4flags_extension,
+	.run = ext_imap4flags_runtime_init
+};
+
+/*
+ * Flag handling
+ */
+
+/* FIXME: This currently accepts a potentially unlimited number of
+ * flags, making the internal or variable flag list indefinitely long
+ */
+
+bool sieve_ext_imap4flags_flag_is_valid(const char *flag)
+{
+	if ( *flag == '\0' )
+		return FALSE;
+
+	if ( *flag == '\\' ) {
+		/* System flag */
+		const char *atom = t_str_ucase(flag);
+
+		if (
+			(strcmp(atom, "\\ANSWERED") != 0) &&
+			(strcmp(atom, "\\FLAGGED") != 0) &&
+			(strcmp(atom, "\\DELETED") != 0) &&
+			(strcmp(atom, "\\SEEN") != 0) &&
+			(strcmp(atom, "\\DRAFT") != 0) )
+		{
+			return FALSE;
+		}
+	} else {
+		const char *p;
+
+		/* Custom keyword:
+		 *
+		 * Syntax (IMAP4rev1, RFC 3501, Section 9. Formal Syntax) :
+		 *  flag-keyword    = atom
+		 *  atom            = 1*ATOM-CHAR
+		 */
+		p = flag;
+		while ( *p != '\0' ) {
+			if ( !IS_ATOM_CHAR(*p) )
+				return FALSE;
+			p++;
+		}
+	}
+
+	return TRUE;
+}
+
+/* Flag iterator */
+
+static void ext_imap4flags_iter_clear
+(struct ext_imap4flags_iter *iter)
+{
+	i_zero(iter);
+}
+
+void ext_imap4flags_iter_init
+(struct ext_imap4flags_iter *iter, string_t *flags_list)
+{
+	ext_imap4flags_iter_clear(iter);
+	iter->flags_list = flags_list;
+}
+
+static string_t *ext_imap4flags_iter_get_flag_str
+(struct ext_imap4flags_iter *iter)
+{
+	unsigned int len;
+	const unsigned char *fp;
+	const unsigned char *fbegin;
+	const unsigned char *fstart;
+	const unsigned char *fend;
+
+	/* Return if not initialized */
+	if ( iter->flags_list == NULL ) return NULL;
+
+	/* Return if no more flags are available */
+	len = str_len(iter->flags_list);
+	if ( iter->offset >= len ) return NULL;
+
+	/* Mark string boundries */
+	fbegin = str_data(iter->flags_list);
+	fend = fbegin + len;
+
+	/* Start of this flag */
+	fstart = fbegin + iter->offset;
+
+	/* Scan for next flag */
+	fp = fstart;
+	for (;;) {
+		/* Have we reached the end or a flag boundary? */
+		if ( fp >= fend || *fp == ' ' ) {
+			/* Did we scan more than nothing ? */
+			if ( fp > fstart ) {
+				/* Return flag */
+				string_t *flag = t_str_new(fp-fstart+1);
+				str_append_data(flag, fstart, fp-fstart);
+
+				iter->last = fstart - fbegin;
+				iter->offset = fp - fbegin;
+
+				return flag;
+			}
+
+			fstart = fp + 1;
+		}
+
+		if ( fp >= fend ) break;
+
+		fp++;
+	}
+
+	iter->last = fstart - fbegin;
+	iter->offset = fp - fbegin;
+	return NULL;
+}
+
+const char *ext_imap4flags_iter_get_flag
+(struct ext_imap4flags_iter *iter)
+{
+	string_t *flag = ext_imap4flags_iter_get_flag_str(iter);
+
+	if ( flag == NULL ) return NULL;
+
+	return str_c(flag);
+}
+
+static void ext_imap4flags_iter_delete_last
+(struct ext_imap4flags_iter *iter)
+{
+	iter->offset++;
+	if ( iter->offset > str_len(iter->flags_list) )
+		iter->offset = str_len(iter->flags_list);
+	if ( iter->offset == str_len(iter->flags_list) && iter->last > 0 )
+		iter->last--;
+
+	str_delete(iter->flags_list, iter->last, iter->offset - iter->last);
+
+	iter->offset = iter->last;
+}
+
+/* Flag operations */
+
+static string_t *ext_imap4flags_get_flag_variable
+(const struct sieve_runtime_env *renv,
+	const struct sieve_extension *flg_ext,
+	struct sieve_variable_storage *storage,
+	unsigned int var_index)
+	ATTR_NULL(2);
+
+static bool flags_list_flag_exists
+(string_t *flags_list, const char *flag)
+{
+	const char *flg;
+	struct ext_imap4flags_iter flit;
+
+	ext_imap4flags_iter_init(&flit, flags_list);
+
+	while ( (flg=ext_imap4flags_iter_get_flag(&flit)) != NULL ) {
+		if ( strcasecmp(flg, flag) == 0 )
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+static void flags_list_flag_delete
+(string_t *flags_list, const char *flag)
+{
+	const char *flg;
+	struct ext_imap4flags_iter flit;
+
+	ext_imap4flags_iter_init(&flit, flags_list);
+
+	while ( (flg=ext_imap4flags_iter_get_flag(&flit)) != NULL ) {
+		if ( strcasecmp(flg, flag) == 0 ) {
+			ext_imap4flags_iter_delete_last(&flit);
+		}
+	}
+}
+
+static void flags_list_add_flags
+(string_t *flags_list, string_t *flags)
+{
+	const char *flg;
+	struct ext_imap4flags_iter flit;
+
+	ext_imap4flags_iter_init(&flit, flags);
+
+	while ( (flg=ext_imap4flags_iter_get_flag(&flit)) != NULL ) {
+		if ( sieve_ext_imap4flags_flag_is_valid(flg) &&
+			!flags_list_flag_exists(flags_list, flg) ) {
+			if ( str_len(flags_list) != 0 )
+				str_append_c(flags_list, ' ');
+			str_append(flags_list, flg);
+		}
+	}
+}
+
+static void flags_list_remove_flags
+(string_t *flags_list, string_t *flags)
+{
+	const char *flg;
+	struct ext_imap4flags_iter flit;
+
+	ext_imap4flags_iter_init(&flit, flags);
+
+	while ( (flg=ext_imap4flags_iter_get_flag(&flit)) != NULL ) {
+		flags_list_flag_delete(flags_list, flg);
+	}
+}
+
+static void flags_list_set_flags
+(string_t *flags_list, string_t *flags)
+{
+	str_truncate(flags_list, 0);
+	flags_list_add_flags(flags_list, flags);
+}
+
+static void flags_list_clear_flags
+(string_t *flags_list)
+{
+	str_truncate(flags_list, 0);
+}
+
+static string_t *ext_imap4flags_get_flag_variable
+(const struct sieve_runtime_env *renv,
+	const struct sieve_extension *flg_ext,
+	struct sieve_variable_storage *storage,
+	unsigned int var_index)
+{
+	string_t *flags;
+
+	if ( storage != NULL ) {
+		if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
+			const char *var_name, *var_id;
+
+			(void)sieve_variable_get_identifier(storage, var_index, &var_name);
+			var_id = sieve_variable_get_varid(storage, var_index);
+
+			sieve_runtime_trace(renv, 0, "update variable `%s' [%s]",
+				var_name, var_id);
+		}
+
+		if ( !sieve_variable_get_modifiable(storage, var_index, &flags) )
+			return NULL;
+	} else {
+		i_assert( sieve_extension_is(flg_ext, imap4flags_extension) );
+		flags = _get_flags_string(flg_ext, renv->result);
+	}
+
+	return flags;
+}
+
+int sieve_ext_imap4flags_set_flags
+(const struct sieve_runtime_env *renv,
+	const struct sieve_extension *flg_ext,
+	struct sieve_variable_storage *storage,
+	unsigned int var_index,
+	struct sieve_stringlist *flags)
+{
+	string_t *cur_flags = ext_imap4flags_get_flag_variable
+		(renv, flg_ext, storage, var_index);
+
+	if ( cur_flags != NULL ) {
+		string_t *flags_item;
+		int ret;
+
+		flags_list_clear_flags(cur_flags);
+		while ( (ret=sieve_stringlist_next_item(flags, &flags_item)) > 0 ) {
+			sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS,
+				"set flags `%s'", str_c(flags_item));
+
+			flags_list_add_flags(cur_flags, flags_item);
+		}
+
+		if ( ret < 0 ) return SIEVE_EXEC_BIN_CORRUPT;
+
+		return SIEVE_EXEC_OK;
+	}
+
+	return SIEVE_EXEC_BIN_CORRUPT;
+}
+
+int sieve_ext_imap4flags_add_flags
+(const struct sieve_runtime_env *renv,
+	const struct sieve_extension *flg_ext,
+	struct sieve_variable_storage *storage,
+	unsigned int var_index,
+	struct sieve_stringlist *flags)
+{
+	string_t *cur_flags = ext_imap4flags_get_flag_variable
+		(renv, flg_ext, storage, var_index);
+
+	if ( cur_flags != NULL ) {
+		string_t *flags_item;
+		int ret;
+
+		while ( (ret=sieve_stringlist_next_item(flags, &flags_item)) > 0 ) {
+			sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS,
+				"add flags `%s'", str_c(flags_item));
+
+			flags_list_add_flags(cur_flags, flags_item);
+		}
+
+		if ( ret < 0 ) return SIEVE_EXEC_BIN_CORRUPT;
+
+		return SIEVE_EXEC_OK;
+	}
+
+	return SIEVE_EXEC_BIN_CORRUPT;
+}
+
+int sieve_ext_imap4flags_remove_flags
+(const struct sieve_runtime_env *renv,
+	const struct sieve_extension *flg_ext,
+	struct sieve_variable_storage *storage,
+	unsigned int var_index,
+	struct sieve_stringlist *flags)
+{
+	string_t *cur_flags = ext_imap4flags_get_flag_variable
+		(renv, flg_ext, storage, var_index);
+
+	if ( cur_flags != NULL ) {
+		string_t *flags_item;
+		int ret;
+
+		while ( (ret=sieve_stringlist_next_item(flags, &flags_item)) > 0 ) {
+			sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS,
+				"remove flags `%s'", str_c(flags_item));
+
+			flags_list_remove_flags(cur_flags, flags_item);
+		}
+
+		if ( ret < 0 ) return SIEVE_EXEC_BIN_CORRUPT;
+
+		return SIEVE_EXEC_OK;
+	}
+
+	return SIEVE_EXEC_BIN_CORRUPT;
+}
+
+/* Flag stringlist */
+
+static int ext_imap4flags_stringlist_next_item
+	(struct sieve_stringlist *_strlist, string_t **str_r);
+static void ext_imap4flags_stringlist_reset
+	(struct sieve_stringlist *_strlist);
+
+struct ext_imap4flags_stringlist {
+	struct sieve_stringlist strlist;
+
+	struct sieve_stringlist *flags_list;
+	string_t *flags_string;
+	struct ext_imap4flags_iter flit;
+
+	bool normalize:1;
+};
+
+static struct sieve_stringlist *ext_imap4flags_stringlist_create
+(const struct sieve_runtime_env *renv, struct sieve_stringlist *flags_list,
+	bool normalize)
+{
+	struct ext_imap4flags_stringlist *strlist;
+
+	strlist = t_new(struct ext_imap4flags_stringlist, 1);
+	strlist->strlist.exec_status = SIEVE_EXEC_OK;
+	strlist->strlist.runenv = renv;
+	strlist->strlist.next_item = ext_imap4flags_stringlist_next_item;
+	strlist->strlist.reset = ext_imap4flags_stringlist_reset;
+	strlist->normalize = normalize;
+
+	strlist->flags_list = flags_list;
+
+	return &strlist->strlist;
+}
+
+static struct sieve_stringlist *ext_imap4flags_stringlist_create_single
+(const struct sieve_runtime_env *renv, string_t *flags_string, bool normalize)
+{
+	struct ext_imap4flags_stringlist *strlist;
+
+	strlist = t_new(struct ext_imap4flags_stringlist, 1);
+	strlist->strlist.exec_status = SIEVE_EXEC_OK;
+	strlist->strlist.runenv = renv;
+	strlist->strlist.next_item = ext_imap4flags_stringlist_next_item;
+	strlist->strlist.reset = ext_imap4flags_stringlist_reset;
+	strlist->normalize = normalize;
+
+	if ( normalize ) {
+		strlist->flags_string = t_str_new(256);
+		flags_list_set_flags(strlist->flags_string, flags_string);
+	} else {
+		strlist->flags_string = flags_string;
+	}
+
+	ext_imap4flags_iter_init(&strlist->flit, strlist->flags_string);
+
+	return &strlist->strlist;
+}
+
+static int ext_imap4flags_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+	struct ext_imap4flags_stringlist *strlist =
+		(struct ext_imap4flags_stringlist *)_strlist;
+
+	while ( (*str_r=ext_imap4flags_iter_get_flag_str(&strlist->flit)) == NULL ) {
+		int ret;
+
+		if ( strlist->flags_list == NULL )
+			return 0;
+
+		if ( (ret=sieve_stringlist_next_item
+			(strlist->flags_list, &strlist->flags_string)) <= 0 )
+			return ret;
+
+		if ( strlist->flags_string == NULL )
+			return -1;
+
+		if ( strlist->normalize ) {
+			string_t *flags_string = t_str_new(256);
+
+			flags_list_set_flags(flags_string, strlist->flags_string);
+			strlist->flags_string = flags_string;
+		}
+
+		ext_imap4flags_iter_init(&strlist->flit, strlist->flags_string);
+	}
+
+	return 1;
+}
+
+static void ext_imap4flags_stringlist_reset
+(struct sieve_stringlist *_strlist)
+{
+	struct ext_imap4flags_stringlist *strlist =
+		(struct ext_imap4flags_stringlist *)_strlist;
+
+	if ( strlist->flags_list != NULL ) {
+		sieve_stringlist_reset(strlist->flags_list);
+		ext_imap4flags_iter_clear(&strlist->flit);
+	} else {
+		ext_imap4flags_iter_init(&strlist->flit, strlist->flags_string);
+	}
+}
+
+/* Flag access */
+
+struct sieve_stringlist *sieve_ext_imap4flags_get_flags
+(const struct sieve_runtime_env *renv,
+	const struct sieve_extension *flg_ext,
+	struct sieve_stringlist *flags_list)
+{
+	if ( flags_list == NULL ) {
+		i_assert( sieve_extension_is(flg_ext, imap4flags_extension) );
+		return ext_imap4flags_stringlist_create_single
+			(renv, _get_flags_string(flg_ext, renv->result), FALSE);
+	}
+
+	return ext_imap4flags_stringlist_create(renv, flags_list, TRUE);
+}
+
+void ext_imap4flags_get_implicit_flags_init
+(struct ext_imap4flags_iter *iter, const struct sieve_extension *this_ext,
+	struct sieve_result *result)
+{
+	string_t *cur_flags = _get_flags_string(this_ext, result);
+
+	ext_imap4flags_iter_init(iter, cur_flags);
+}
+
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/imap4flags/ext-imap4flags-common.h
@@ -0,0 +1,97 @@
+#ifndef EXT_IMAP4FLAGS_COMMON_H
+#define EXT_IMAP4FLAGS_COMMON_H
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-ext-variables.h"
+
+#include "sieve-ext-imap4flags.h"
+
+/*
+ * Side effect
+ */
+
+extern const struct sieve_side_effect_def flags_side_effect;
+
+/*
+ * Operands
+ */
+
+extern const struct sieve_operand_def flags_side_effect_operand;
+
+/*
+ * Operations
+ */
+
+enum ext_imap4flags_opcode {
+	EXT_IMAP4FLAGS_OPERATION_SETFLAG,
+	EXT_IMAP4FLAGS_OPERATION_ADDFLAG,
+	EXT_IMAP4FLAGS_OPERATION_REMOVEFLAG,
+	EXT_IMAP4FLAGS_OPERATION_HASFLAG
+};
+
+extern const struct sieve_operation_def setflag_operation;
+extern const struct sieve_operation_def addflag_operation;
+extern const struct sieve_operation_def removeflag_operation;
+extern const struct sieve_operation_def hasflag_operation;
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def cmd_setflag;
+extern const struct sieve_command_def cmd_addflag;
+extern const struct sieve_command_def cmd_removeflag;
+
+extern const struct sieve_command_def tst_hasflag;
+
+/*
+ * Common command functions
+ */
+
+bool ext_imap4flags_command_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+
+/*
+ * Flags tagged argument
+ */
+
+void ext_imap4flags_attach_flags_tag
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		const char *command);
+
+/*
+ * Flag management
+ */
+
+struct ext_imap4flags_iter {
+	string_t *flags_list;
+	unsigned int offset;
+	unsigned int last;
+};
+
+void ext_imap4flags_iter_init
+	(struct ext_imap4flags_iter *iter, string_t *flags_list);
+
+const char *ext_imap4flags_iter_get_flag
+	(struct ext_imap4flags_iter *iter);
+
+/* Flag operations */
+
+typedef int (*ext_imapflag_flag_operation_t)
+	(const struct sieve_runtime_env *renv,
+		const struct sieve_extension *flg_ext,
+		struct sieve_variable_storage *storage,
+		unsigned int var_index, struct sieve_stringlist *flags)
+		ATTR_NULL(2);
+
+/* Flags access */
+
+void ext_imap4flags_get_implicit_flags_init
+	(struct ext_imap4flags_iter *iter, const struct sieve_extension *this_ext,
+		struct sieve_result *result);
+
+
+#endif
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/imap4flags/ext-imap4flags.c
@@ -0,0 +1,96 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension imap4flags
+ * --------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5232
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+#include "mempool.h"
+#include "str.h"
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-actions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+
+#include "ext-imap4flags-common.h"
+
+/*
+ * Operations
+ */
+
+const struct sieve_operation_def *imap4flags_operations[] = {
+	&setflag_operation,
+	&addflag_operation,
+	&removeflag_operation,
+	&hasflag_operation
+};
+
+/*
+ * Extension
+ */
+
+static bool ext_imap4flags_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+static bool ext_imap4flags_interpreter_load
+	(const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+		sieve_size_t *address);
+
+const struct sieve_extension_def imap4flags_extension = {
+	.name = "imap4flags",
+	.version = 1,
+	.validator_load = ext_imap4flags_validator_load,
+	.interpreter_load = ext_imap4flags_interpreter_load,
+	SIEVE_EXT_DEFINE_OPERATIONS(imap4flags_operations),
+	SIEVE_EXT_DEFINE_OPERAND(flags_side_effect_operand)
+};
+
+static bool ext_imap4flags_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	/* Register commands */
+	sieve_validator_register_command(valdtr, ext, &cmd_setflag);
+	sieve_validator_register_command(valdtr, ext, &cmd_addflag);
+	sieve_validator_register_command(valdtr, ext, &cmd_removeflag);
+	sieve_validator_register_command(valdtr, ext, &tst_hasflag);
+
+	/* Attach :flags tag to keep and fileinto commands */
+	ext_imap4flags_attach_flags_tag(valdtr, ext, "keep");
+	ext_imap4flags_attach_flags_tag(valdtr, ext, "fileinto");
+
+	/* Attach flags side-effect to keep and fileinto actions */
+	sieve_ext_imap4flags_register_side_effect(valdtr, ext, "keep");
+	sieve_ext_imap4flags_register_side_effect(valdtr, ext, "fileinto");
+
+	return TRUE;
+}
+
+void sieve_ext_imap4flags_interpreter_load
+(const struct sieve_extension *ext, const struct sieve_runtime_env *renv)
+{
+	sieve_interpreter_extension_register
+		(renv->interp, ext, &imap4flags_interpreter_extension, NULL);
+}
+
+static bool ext_imap4flags_interpreter_load
+(const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+	sieve_size_t *address ATTR_UNUSED)
+{
+	sieve_ext_imap4flags_interpreter_load(ext, renv);
+	return TRUE;
+}
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/imap4flags/ext-imapflags.c
@@ -0,0 +1,213 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension imapflags
+ * --------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: draft-melnikov-sieve-imapflags-03.txt
+ * Implementation: full, but deprecated; provided for backwards compatibility
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+#include "mempool.h"
+#include "str.h"
+
+#include "sieve-common.h"
+
+#include "sieve-ast.h"
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-actions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+
+#include "ext-imap4flags-common.h"
+
+/*
+ * Commands
+ */
+
+static bool cmd_mark_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+
+/* Mark command
+ *
+ * Syntax:
+ *   mark
+ */
+
+static const struct sieve_command_def cmd_mark = {
+	.identifier = "mark",
+	.type = SCT_COMMAND,
+	.positional_args = 0,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = cmd_mark_validate
+};
+
+/* Unmark command
+ *
+ * Syntax:
+ *   unmark
+ */
+static const struct sieve_command_def cmd_unmark = {
+	.identifier = "unmark",
+	.type = SCT_COMMAND,
+	.positional_args = 0,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = cmd_mark_validate
+};
+
+/*
+ * Extension
+ */
+
+static bool ext_imapflags_load
+	(const struct sieve_extension *ext, void **context);
+static bool ext_imapflags_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+static bool ext_imapflags_interpreter_load
+	(const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+		sieve_size_t *address);
+
+const struct sieve_extension_def imapflags_extension = {
+	.name = "imapflags",
+	.load = ext_imapflags_load,
+	.validator_load = ext_imapflags_validator_load,
+	.interpreter_load = ext_imapflags_interpreter_load
+};
+
+static bool ext_imapflags_load
+(const struct sieve_extension *ext, void **context)
+{
+	if ( *context == NULL ) {
+		/* Make sure real extension is registered, it is needed by the binary */
+		*context = (void *)
+			sieve_extension_require(ext->svinst, &imap4flags_extension, FALSE);
+	}
+
+	return TRUE;
+}
+
+/*
+ * Validator
+ */
+
+static bool ext_imapflags_validator_check_conflict
+	(const struct sieve_extension *ext,
+		struct sieve_validator *valdtr, void *context,
+		struct sieve_ast_argument *require_arg,
+		const struct sieve_extension *other_ext,
+		bool required);
+static bool ext_imapflags_validator_validate
+	(const struct sieve_extension *ext,
+		struct sieve_validator *valdtr, void *context,
+		struct sieve_ast_argument *require_arg,
+		bool required);
+
+const struct sieve_validator_extension
+imapflags_validator_extension = {
+	.ext = &imapflags_extension,
+	.check_conflict = ext_imapflags_validator_check_conflict,
+	.validate = ext_imapflags_validator_validate
+};
+
+static bool ext_imapflags_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	sieve_validator_extension_register
+		(valdtr, ext, &imapflags_validator_extension, NULL);
+
+	return TRUE;
+}
+
+static bool ext_imapflags_validator_check_conflict
+(const struct sieve_extension *ext,
+	struct sieve_validator *valdtr, void *context ATTR_UNUSED,
+	struct sieve_ast_argument *require_arg,
+	const struct sieve_extension *ext_other,
+	bool required ATTR_UNUSED)
+{
+	const struct sieve_extension *master_ext =
+		(const struct sieve_extension *) ext->context;
+
+	if ( ext_other == master_ext ) {
+		sieve_argument_validate_error(valdtr, require_arg,
+			"the (deprecated) imapflags extension cannot be used "
+			"together with the imap4flags extension");
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static bool ext_imapflags_validator_validate
+(const struct sieve_extension *ext,
+	struct sieve_validator *valdtr, void *context ATTR_UNUSED,
+	struct sieve_ast_argument *require_arg ATTR_UNUSED,
+	bool required ATTR_UNUSED)
+{
+	const struct sieve_extension *master_ext =
+		(const struct sieve_extension *) ext->context;
+
+	/* No conflicts */
+
+	/* Register commands */
+	sieve_validator_register_command(valdtr, master_ext, &cmd_setflag);
+	sieve_validator_register_command(valdtr, master_ext, &cmd_addflag);
+	sieve_validator_register_command(valdtr, master_ext, &cmd_removeflag);
+
+	sieve_validator_register_command(valdtr, master_ext, &cmd_mark);
+	sieve_validator_register_command(valdtr, master_ext, &cmd_unmark);
+
+	/* Attach flags side-effect to keep and fileinto actions */
+	sieve_ext_imap4flags_register_side_effect(valdtr, master_ext, "keep");
+	sieve_ext_imap4flags_register_side_effect(valdtr, master_ext, "fileinto");
+
+	return TRUE;
+}
+
+/*
+ * Interpreter
+ */
+
+static bool ext_imapflags_interpreter_load
+(const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+	sieve_size_t *address ATTR_UNUSED)
+{
+	const struct sieve_extension *master_ext =
+		(const struct sieve_extension *) ext->context;
+
+	sieve_ext_imap4flags_interpreter_load(master_ext, renv);
+	return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+static bool cmd_mark_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+	if ( sieve_command_is(cmd, cmd_mark) )
+		cmd->def = &cmd_addflag;
+	else
+		cmd->def = &cmd_removeflag;
+
+	cmd->first_positional = sieve_ast_argument_cstring_create
+		(cmd->ast_node, "\\flagged", cmd->ast_node->source_line);
+
+	if ( !sieve_validator_argument_activate
+		(valdtr, cmd, cmd->first_positional, FALSE) )
+		return FALSE;
+
+	return TRUE;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/imap4flags/sieve-ext-imap4flags.h
@@ -0,0 +1,74 @@
+#ifndef SIEVE_EXT_IMAP4FLAGS_H
+#define SIEVE_EXT_IMAP4FLAGS_H
+
+struct sieve_variable_storage;
+
+/*
+ * Imap4flags extension
+ */
+
+/* FIXME: this is not suitable for future plugin support */
+
+extern const struct sieve_extension_def imap4flags_extension;
+extern const struct sieve_interpreter_extension
+	imap4flags_interpreter_extension;
+
+static inline const struct sieve_extension *
+sieve_ext_imap4flags_require_extension
+(struct sieve_instance *svinst)
+{
+	return sieve_extension_require
+		(svinst, &imap4flags_extension, TRUE);
+}
+
+void sieve_ext_imap4flags_interpreter_load
+(const struct sieve_extension *ext,
+	const struct sieve_runtime_env *renv);
+
+/*
+ * Action side-effect
+ */
+
+void sieve_ext_imap4flags_register_side_effect
+(struct sieve_validator *valdtr, const struct sieve_extension *flg_ext,
+        const char *command);
+
+/*
+ * Flag syntax
+ */
+
+bool sieve_ext_imap4flags_flag_is_valid(const char *flag);
+
+/*
+ * Flag manipulation
+ */
+
+int sieve_ext_imap4flags_set_flags
+(const struct sieve_runtime_env *renv,
+	const struct sieve_extension *flg_ext,
+	struct sieve_variable_storage *storage,
+	unsigned int var_index,
+	struct sieve_stringlist *flags) ATTR_NULL(3);
+int sieve_ext_imap4flags_add_flags
+(const struct sieve_runtime_env *renv,
+	const struct sieve_extension *flg_ext,
+	struct sieve_variable_storage *storage,
+	unsigned int var_index,
+	struct sieve_stringlist *flags) ATTR_NULL(3);
+int sieve_ext_imap4flags_remove_flags
+(const struct sieve_runtime_env *renv,
+	const struct sieve_extension *flg_ext,
+	struct sieve_variable_storage *storage,
+	unsigned int var_index,
+	struct sieve_stringlist *flags) ATTR_NULL(3);
+
+/*
+ * Flag retrieval
+ */
+
+struct sieve_stringlist *sieve_ext_imap4flags_get_flags
+(const struct sieve_runtime_env *renv,
+	const struct sieve_extension *flg_ext,
+	struct sieve_stringlist *flags_list);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/imap4flags/tag-flags.c
@@ -0,0 +1,404 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+#include "array.h"
+#include "mail-storage.h"
+
+#include "sieve-code.h"
+#include "sieve-stringlist.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-result.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-actions.h"
+#include "sieve-dump.h"
+
+#include "ext-imap4flags-common.h"
+
+#include <ctype.h>
+
+/*
+ * Flags tagged argument
+ */
+
+static bool tag_flags_validate
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+static bool tag_flags_validate_persistent
+	(struct sieve_validator *valdtr, struct sieve_command *cmd,
+		const struct sieve_extension *ext);
+static bool tag_flags_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+		struct sieve_command *cmd);
+
+const struct sieve_argument_def tag_flags = {
+	.identifier = "flags",
+	.validate = tag_flags_validate,
+	.generate = tag_flags_generate
+};
+
+const struct sieve_argument_def tag_flags_implicit = {
+	.identifier = "flags-implicit",
+	.validate_persistent = tag_flags_validate_persistent,
+	.generate = tag_flags_generate
+};
+
+/*
+ * Side effect
+ */
+
+static bool seff_flags_dump_context
+	(const struct sieve_side_effect *seffect,
+    	const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int seff_flags_read_context
+	(const struct sieve_side_effect *seffect,
+		const struct sieve_runtime_env *renv, sieve_size_t *address,
+		void **context);
+
+static int seff_flags_merge
+	(const struct sieve_runtime_env *renv, const struct sieve_action *action,
+		const struct sieve_side_effect *old_seffect,
+		const struct sieve_side_effect *new_seffect, void **old_context);
+
+static void seff_flags_print
+	(const struct sieve_side_effect *seffect, const struct sieve_action *action,
+		const struct sieve_result_print_env *rpenv, bool *keep);
+static int seff_flags_pre_execute
+	(const struct sieve_side_effect *seffect, const struct sieve_action *action,
+		const struct sieve_action_exec_env *aenv, void **context, void *tr_context);
+
+const struct sieve_side_effect_def flags_side_effect = {
+	SIEVE_OBJECT("flags", &flags_side_effect_operand, 0),
+	&act_store,
+
+	seff_flags_dump_context,
+	seff_flags_read_context,
+	seff_flags_merge,
+	seff_flags_print,
+	seff_flags_pre_execute,
+	NULL, NULL, NULL
+};
+
+/*
+ * Operand
+ */
+
+static const struct sieve_extension_objects ext_side_effects =
+	SIEVE_EXT_DEFINE_SIDE_EFFECT(flags_side_effect);
+
+const struct sieve_operand_def flags_side_effect_operand = {
+	.name = "flags operand",
+	.ext_def = &imap4flags_extension,
+	.class = &sieve_side_effect_operand_class,
+	.interface = &ext_side_effects
+};
+
+/*
+ * Tag validation
+ */
+
+static bool tag_flags_validate_persistent
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *cmd,
+	const struct sieve_extension *ext)
+{
+	if ( sieve_command_find_argument(cmd, &tag_flags) == NULL ) {
+		sieve_command_add_dynamic_tag(cmd, ext, &tag_flags_implicit, -1);
+	}
+
+	return TRUE;
+}
+
+static bool tag_flags_validate
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *tag = *arg;
+
+	/* Detach the tag itself */
+	*arg = sieve_ast_argument_next(*arg);
+
+	/* Check syntax:
+	 *   :flags <list-of-flags: string-list>
+	 */
+	if ( !sieve_validate_tag_parameter
+		(valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING_LIST, FALSE) ) {
+		return FALSE;
+	}
+
+	tag->parameters = *arg;
+
+	/* Detach parameter */
+	*arg = sieve_ast_arguments_detach(*arg,1);
+
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool tag_flags_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+	struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *param;
+
+	if ( sieve_ast_argument_type(arg) != SAAT_TAG ) {
+		return FALSE;
+	}
+
+	sieve_opr_side_effect_emit
+		(cgenv->sblock, arg->argument->ext, &flags_side_effect);
+
+	if ( sieve_argument_is(arg, tag_flags) ) {
+		/* Explicit :flags tag */
+		param = arg->parameters;
+
+		/* Call the generation function for the argument */
+		if ( param->argument != NULL && param->argument->def != NULL &&
+			param->argument->def->generate != NULL &&
+			!param->argument->def->generate(cgenv, param, cmd) )
+			return FALSE;
+
+	} else if ( sieve_argument_is(arg, tag_flags_implicit) ) {
+		/* Implicit flags */
+		sieve_opr_omitted_emit(cgenv->sblock);
+
+	} else {
+		/* Something else?! */
+		i_unreached();
+	}
+
+	return TRUE;
+}
+
+/*
+ * Side effect implementation
+ */
+
+/* Context data */
+
+struct seff_flags_context {
+	ARRAY(const char *) keywords;
+	enum mail_flags flags;
+};
+
+/* Context coding */
+
+static bool seff_flags_dump_context
+(const struct sieve_side_effect *seffect ATTR_UNUSED,
+	const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	return sieve_opr_stringlist_dump_ex(denv, address, "flags", "INTERNAL");
+}
+
+static struct seff_flags_context *seff_flags_get_implicit_context
+(const struct sieve_extension *this_ext, struct sieve_result *result)
+{
+	pool_t pool = sieve_result_pool(result);
+	struct seff_flags_context *ctx;
+	const char *flag;
+	struct ext_imap4flags_iter flit;
+
+	ctx = p_new(pool, struct seff_flags_context, 1);
+	p_array_init(&ctx->keywords, pool, 2);
+
+	T_BEGIN {
+
+		/* Unpack */
+		ext_imap4flags_get_implicit_flags_init(&flit, this_ext, result);
+		while ( (flag=ext_imap4flags_iter_get_flag(&flit)) != NULL ) {
+			if (flag != NULL && *flag != '\\') {
+				/* keyword */
+				const char *keyword = p_strdup(pool, flag);
+				array_append(&ctx->keywords, &keyword, 1);
+			} else {
+				/* system flag */
+				if (flag == NULL || strcasecmp(flag, "\\flagged") == 0)
+					ctx->flags |= MAIL_FLAGGED;
+				else if (strcasecmp(flag, "\\answered") == 0)
+					ctx->flags |= MAIL_ANSWERED;
+				else if (strcasecmp(flag, "\\deleted") == 0)
+					ctx->flags |= MAIL_DELETED;
+				else if (strcasecmp(flag, "\\seen") == 0)
+					ctx->flags |= MAIL_SEEN;
+				else if (strcasecmp(flag, "\\draft") == 0)
+					ctx->flags |= MAIL_DRAFT;
+			}
+		}
+
+	} T_END;
+
+	return ctx;
+}
+
+static int seff_flags_do_read_context
+(const struct sieve_side_effect *seffect,
+	const struct sieve_runtime_env *renv, sieve_size_t *address,
+	void **se_context)
+{
+	pool_t pool = sieve_result_pool(renv->result);
+	struct seff_flags_context *ctx;
+	string_t *flags_item;
+	struct sieve_stringlist *flag_list = NULL;
+	int ret;
+
+	if ( (ret=sieve_opr_stringlist_read_ex
+		(renv, address, "flags", TRUE, &flag_list)) <= 0 )
+		return ret;
+
+	if ( flag_list == NULL ) {
+		/* Flag list is omitted, use current value of internal
+		 * variable to construct side effect context.
+		 */
+		*se_context = seff_flags_get_implicit_context
+			(SIEVE_OBJECT_EXTENSION(seffect), renv->result);
+		return SIEVE_EXEC_OK;
+	}
+
+	ctx = p_new(pool, struct seff_flags_context, 1);
+	p_array_init(&ctx->keywords, pool, 2);
+
+	/* Unpack */
+	flags_item = NULL;
+	while ( (ret=sieve_stringlist_next_item(flag_list, &flags_item)) > 0 ) {
+		const char *flag;
+		struct ext_imap4flags_iter flit;
+
+		ext_imap4flags_iter_init(&flit, flags_item);
+
+		while ( (flag=ext_imap4flags_iter_get_flag(&flit)) != NULL ) {
+			if (flag != NULL && *flag != '\\') {
+				/* keyword */
+				const char *keyword = p_strdup(pool, flag);
+
+				/* FIXME: should check for duplicates (cannot trust variables) */
+				array_append(&ctx->keywords, &keyword, 1);
+
+			} else {
+				/* system flag */
+				if (flag == NULL || strcasecmp(flag, "\\flagged") == 0)
+					ctx->flags |= MAIL_FLAGGED;
+				else if (strcasecmp(flag, "\\answered") == 0)
+					ctx->flags |= MAIL_ANSWERED;
+				else if (strcasecmp(flag, "\\deleted") == 0)
+					ctx->flags |= MAIL_DELETED;
+				else if (strcasecmp(flag, "\\seen") == 0)
+					ctx->flags |= MAIL_SEEN;
+				else if (strcasecmp(flag, "\\draft") == 0)
+					ctx->flags |= MAIL_DRAFT;
+			}
+		}
+	}
+
+	if ( ret < 0 )
+		return flag_list->exec_status;
+
+	*se_context = (void *) ctx;
+
+	return SIEVE_EXEC_OK;
+}
+
+static int seff_flags_read_context
+(const struct sieve_side_effect *seffect,
+	const struct sieve_runtime_env *renv, sieve_size_t *address,
+	void **se_context)
+{
+	int ret;
+
+	T_BEGIN {
+		ret = seff_flags_do_read_context(seffect, renv, address, se_context);
+	} T_END;
+
+	return ret;
+}
+
+/* Result verification */
+
+static int seff_flags_merge
+(const struct sieve_runtime_env *renv ATTR_UNUSED,
+	const struct sieve_action *action ATTR_UNUSED,
+	const struct sieve_side_effect *old_seffect ATTR_UNUSED,
+	const struct sieve_side_effect *new_seffect,
+	void **old_context)
+{
+	if ( new_seffect != NULL )
+		*old_context = new_seffect->context;
+
+	return 1;
+}
+
+/* Result printing */
+
+static void seff_flags_print
+(const struct sieve_side_effect *seffect,
+	const struct sieve_action *action ATTR_UNUSED,
+	const struct sieve_result_print_env *rpenv,bool *keep ATTR_UNUSED)
+{
+	struct sieve_result *result = rpenv->result;
+	struct seff_flags_context *ctx =
+		(struct seff_flags_context *) seffect->context;
+	unsigned int i;
+
+	if ( ctx == NULL )
+		ctx = seff_flags_get_implicit_context
+			(SIEVE_OBJECT_EXTENSION(seffect), result);
+
+	if ( ctx->flags != 0 || array_count(&ctx->keywords) > 0 ) {
+		T_BEGIN {
+			string_t *flags = t_str_new(128);
+
+			if ( (ctx->flags & MAIL_FLAGGED) > 0 )
+				str_printfa(flags, " \\flagged");
+
+			if ( (ctx->flags & MAIL_ANSWERED) > 0 )
+				str_printfa(flags, " \\answered");
+
+			if ( (ctx->flags & MAIL_DELETED) > 0 )
+				str_printfa(flags, " \\deleted");
+
+			if ( (ctx->flags & MAIL_SEEN) > 0 )
+				str_printfa(flags, " \\seen");
+
+			if ( (ctx->flags & MAIL_DRAFT) > 0 )
+				str_printfa(flags, " \\draft");
+
+			for ( i = 0; i < array_count(&ctx->keywords); i++ ) {
+				const char *const *keyword = array_idx(&ctx->keywords, i);
+				str_printfa(flags, " %s", str_sanitize(*keyword, 64));
+			}
+
+			sieve_result_seffect_printf(rpenv, "add IMAP flags:%s", str_c(flags));
+		} T_END;
+	}
+}
+
+/* Result execution */
+
+static int seff_flags_pre_execute
+(const struct sieve_side_effect *seffect,
+	const struct sieve_action *action ATTR_UNUSED,
+	const struct sieve_action_exec_env *aenv, void **context, void *tr_context)
+{
+	struct seff_flags_context *ctx = (struct seff_flags_context *) *context;
+	const char *const *keywords;
+
+	if ( ctx == NULL ) {
+		ctx = seff_flags_get_implicit_context
+			(SIEVE_OBJECT_EXTENSION(seffect), aenv->result);
+		*context = (void *) ctx;
+	}
+
+	(void)array_append_space(&ctx->keywords);
+	keywords = array_idx(&ctx->keywords, 0);
+
+	sieve_act_store_add_flags(aenv, tr_context, keywords, ctx->flags);
+
+	return SIEVE_EXEC_OK;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/imap4flags/tst-hasflag.c
@@ -0,0 +1,248 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-commands.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+#include "ext-imap4flags-common.h"
+
+/*
+ * Hasflag test
+ *
+ * Syntax:
+ *   hasflag [MATCH-TYPE] [COMPARATOR] [<variable-list: string-list>]
+ *       <list-of-flags: string-list>
+ */
+
+static bool tst_hasflag_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool tst_hasflag_validate
+	(struct sieve_validator *valdtr,	struct sieve_command *tst);
+static bool tst_hasflag_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+const struct sieve_command_def tst_hasflag = {
+	.identifier = "hasflag",
+	.type = SCT_TEST,
+	.positional_args = -1, /* We check positional arguments ourselves */
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = tst_hasflag_registered,
+	.validate = tst_hasflag_validate,
+	.generate = tst_hasflag_generate
+};
+
+/*
+ * Hasflag operation
+ */
+
+static bool tst_hasflag_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_hasflag_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def hasflag_operation = {
+	.mnemonic = "HASFLAG",
+	.ext_def = &imap4flags_extension,
+	.code = EXT_IMAP4FLAGS_OPERATION_HASFLAG,
+	.dump = tst_hasflag_operation_dump,
+	.execute = tst_hasflag_operation_execute
+};
+
+/*
+ * Optional arguments
+ */
+
+enum tst_hasflag_optional {
+	OPT_VARIABLES = SIEVE_MATCH_OPT_LAST,
+};
+
+/*
+ * Tag registration
+ */
+
+static bool tst_hasflag_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext ATTR_UNUSED,
+	struct sieve_command_registration *cmd_reg)
+{
+	/* The order of these is not significant */
+	sieve_comparators_link_tag(valdtr, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR);
+	sieve_match_types_link_tags(valdtr, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
+
+	return TRUE;
+}
+
+/*
+ * Validation
+ */
+
+static bool tst_hasflag_validate
+(struct sieve_validator *valdtr,	struct sieve_command *tst)
+{
+	struct sieve_ast_argument *vars = tst->first_positional;
+	struct sieve_ast_argument *keys = sieve_ast_argument_next(vars);
+	const struct sieve_match_type mcht_default =
+		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+	const struct sieve_comparator cmp_default =
+		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+
+	if ( !ext_imap4flags_command_validate(valdtr, tst) )
+		return FALSE;
+
+	if ( keys == NULL ) {
+		keys = vars;
+		vars = NULL;
+	} else {
+		vars->argument->id_code = OPT_VARIABLES;
+	}
+
+	/* Validate the key argument to a specified match type */
+	return sieve_match_type_validate
+		(valdtr, tst, keys, &mcht_default, &cmp_default);
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_hasflag_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	sieve_operation_emit(cgenv->sblock, cmd->ext, &hasflag_operation);
+
+	/* Generate arguments */
+	if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
+		return FALSE;
+
+	return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_hasflag_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	int opt_code = 0;
+
+	sieve_code_dumpf(denv, "HASFLAG");
+	sieve_code_descend(denv);
+
+	/* Optional operands */
+
+	for (;;) {
+		bool opok = TRUE;
+		int opt;
+
+		if ( (opt=sieve_match_opr_optional_dump(denv, address, &opt_code))
+			< 0 )
+			return FALSE;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_VARIABLES:
+			opok = sieve_opr_stringlist_dump(denv, address, "variables");
+			break;
+		default:
+			return FALSE;
+		}
+
+		if ( !opok ) return FALSE;
+	}
+
+	return
+		sieve_opr_stringlist_dump(denv, address, "list of flags");
+}
+
+/*
+ * Interpretation
+ */
+
+static int tst_hasflag_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	const struct sieve_operation *op = renv->oprtn;
+	int opt_code = 0;
+	struct sieve_comparator cmp =
+		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+	struct sieve_match_type mcht =
+		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+	struct sieve_stringlist *flag_list, *variables_list, *value_list, *key_list;
+	int match, ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Optional operands */
+
+	variables_list = NULL;
+	for (;;) {
+		int opt;
+
+		if ( (opt=sieve_match_opr_optional_read
+			(renv, address, &opt_code, &ret, &cmp, &mcht)) < 0 )
+			return ret;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_VARIABLES:
+			ret = sieve_opr_stringlist_read
+				(renv, address, "variables-list", &variables_list);
+			break;
+		default:
+			sieve_runtime_trace_error(renv, "invalid optional operand");
+			ret = SIEVE_EXEC_BIN_CORRUPT;
+		}
+
+		if ( ret <= 0 ) return ret;
+	}
+
+	/* Fixed operands */
+
+	if ( (ret=sieve_opr_stringlist_read(renv, address, "flag-list", &flag_list))
+		<= 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "hasflag test");
+
+	value_list = sieve_ext_imap4flags_get_flags
+		(renv, op->ext, variables_list);
+
+	if ( sieve_match_type_is(&mcht, is_match_type) ||
+		sieve_match_type_is(&mcht, contains_match_type) ) {
+		key_list = sieve_ext_imap4flags_get_flags
+			(renv, op->ext, flag_list);
+	} else {
+		key_list = flag_list;
+	}
+
+	/* Perform match */
+	if ( (match=sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret)) < 0 )
+		return ret;
+
+	/* Set test result for subsequent conditional jump */
+	sieve_interpreter_set_test_result(renv->interp, match > 0);
+	return SIEVE_EXEC_OK;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/include/Makefile.am
@@ -0,0 +1,24 @@
+noinst_LTLIBRARIES = libsieve_ext_include.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../.. \
+	-I$(srcdir)/../variables \
+	$(LIBDOVECOT_INCLUDE)
+
+cmds = \
+	cmd-include.c \
+	cmd-return.c \
+	cmd-global.c
+
+libsieve_ext_include_la_SOURCES = \
+	$(cmds) \
+	ext-include-common.c \
+	ext-include-binary.c \
+	ext-include-variables.c \
+	ext-include.c
+
+noinst_HEADERS = \
+	ext-include-common.h \
+	ext-include-limits.h \
+	ext-include-binary.h \
+	ext-include-variables.h
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/include/cmd-global.c
@@ -0,0 +1,329 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-code.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "sieve-ext-variables.h"
+
+#include "ext-include-common.h"
+#include "ext-include-binary.h"
+#include "ext-include-variables.h"
+
+/*
+ * Commands
+ */
+
+static bool cmd_global_validate
+  (struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_global_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+const struct sieve_command_def cmd_global = {
+  .identifier = "global",
+  .type = SCT_COMMAND,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+  .validate = cmd_global_validate,
+  .generate = cmd_global_generate,
+};
+
+/* DEPRICATED:
+ */
+
+/* Import command
+ *
+ * Syntax
+ *   import
+ */
+const struct sieve_command_def cmd_import = {
+	.identifier = "import",
+	.type = SCT_COMMAND,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = cmd_global_validate,
+	.generate = cmd_global_generate,
+};
+
+/* Export command
+ *
+ * Syntax
+ *   export
+ */
+const struct sieve_command_def cmd_export = {
+	.identifier = "export",
+	.type = SCT_COMMAND,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = cmd_global_validate,
+	.generate = cmd_global_generate,
+};
+
+/*
+ * Operations
+ */
+
+static bool opc_global_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int opc_global_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+/* Global operation */
+
+const struct sieve_operation_def global_operation = {
+	.mnemonic = "GLOBAL",
+	.ext_def = &include_extension,
+	.code = EXT_INCLUDE_OPERATION_GLOBAL,
+	.dump = opc_global_dump,
+	.execute = opc_global_execute
+};
+
+/*
+ * Validation
+ */
+
+static inline struct sieve_argument *_create_variable_argument
+(struct sieve_command *cmd, struct sieve_variable *var)
+{
+	struct sieve_argument *argument = sieve_argument_create
+		(cmd->ast_node->ast, NULL, cmd->ext, 0);
+
+	argument->data = (void *) var;
+
+	return argument;
+}
+
+static bool cmd_global_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+	const struct sieve_extension *this_ext = cmd->ext;
+	struct sieve_ast_argument *arg = cmd->first_positional;
+	struct sieve_command *prev = sieve_command_prev(cmd);
+
+	/* DEPRECATED: Check valid command placement */
+	if ( !sieve_command_is(cmd, cmd_global) ) {
+		if ( !sieve_command_is_toplevel(cmd) ||
+			( !sieve_command_is_first(cmd) && prev != NULL &&
+				!sieve_command_is(prev, cmd_require) &&
+				!sieve_command_is(prev, cmd_import) &&
+				!sieve_command_is(prev, cmd_export) ) ) {
+			sieve_command_validate_error(valdtr, cmd,
+				"the DEPRECATED %s command can only be placed at top level "
+				"at the beginning of the file after any require or "
+				"import/export commands",
+				sieve_command_identifier(cmd));
+			return FALSE;
+		}
+	}
+
+	/* Check for use of variables extension */
+	if ( !ext_include_validator_have_variables(this_ext, valdtr) ) {
+		sieve_command_validate_error(valdtr, cmd,
+			"%s command requires that variables extension is active",
+			sieve_command_identifier(cmd));
+		return FALSE;
+	}
+
+	/* Register global variable */
+	if ( sieve_ast_argument_type(arg) == SAAT_STRING ) {
+		/* Single string */
+		const char *identifier = sieve_ast_argument_strc(arg);
+		struct sieve_variable *var;
+
+		if ( (var=ext_include_variable_import_global
+			(valdtr, cmd, identifier)) == NULL )
+			return FALSE;
+
+		arg->argument = _create_variable_argument(cmd, var);
+
+	} else if ( sieve_ast_argument_type(arg) == SAAT_STRING_LIST ) {
+		/* String list */
+		struct sieve_ast_argument *stritem = sieve_ast_strlist_first(arg);
+
+		while ( stritem != NULL ) {
+			const char *identifier = sieve_ast_argument_strc(stritem);
+			struct sieve_variable *var;
+
+			if ( (var=ext_include_variable_import_global
+				(valdtr, cmd, identifier)) == NULL )
+				return FALSE;
+
+			stritem->argument = _create_variable_argument(cmd, var);
+
+			stritem = sieve_ast_strlist_next(stritem);
+		}
+	} else {
+		/* Something else */
+		sieve_argument_validate_error(valdtr, arg,
+			"the %s command accepts a single string or string list argument, "
+			"but %s was found", sieve_command_identifier(cmd),
+			sieve_ast_argument_name(arg));
+		return FALSE;
+	}
+
+	/* Join global commands with predecessors if possible */
+	if ( sieve_commands_equal(prev, cmd) ) {
+		/* Join this command's string list with the previous one */
+		prev->first_positional = sieve_ast_stringlist_join
+			(prev->first_positional, cmd->first_positional);
+
+		if ( prev->first_positional == NULL ) {
+			/* Not going to happen unless MAXINT stringlist items are specified */
+			sieve_command_validate_error(valdtr, cmd,
+				"compiler reached AST limit (script too complex)");
+			return FALSE;
+		}
+
+		/* Detach this command node */
+		sieve_ast_node_detach(cmd->ast_node);
+	}
+
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_global_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *arg = cmd->first_positional;
+
+	sieve_operation_emit(cgenv->sblock, cmd->ext, &global_operation);
+
+	if ( sieve_ast_argument_type(arg) == SAAT_STRING ) {
+		/* Single string */
+		struct sieve_variable *var = (struct sieve_variable *) arg->argument->data;
+
+		(void)sieve_binary_emit_unsigned(cgenv->sblock, 1);
+		(void)sieve_binary_emit_unsigned(cgenv->sblock, var->index);
+
+	} else if ( sieve_ast_argument_type(arg) == SAAT_STRING_LIST ) {
+		/* String list */
+		struct sieve_ast_argument *stritem = sieve_ast_strlist_first(arg);
+
+		(void)sieve_binary_emit_unsigned
+			(cgenv->sblock, sieve_ast_strlist_count(arg));
+
+		while ( stritem != NULL ) {
+			struct sieve_variable *var =
+				(struct sieve_variable *) stritem->argument->data;
+
+			(void)sieve_binary_emit_unsigned(cgenv->sblock, var->index);
+
+			stritem = sieve_ast_strlist_next(stritem);
+		}
+	} else {
+		i_unreached();
+	}
+
+	return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool opc_global_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	const struct sieve_extension *this_ext = denv->oprtn->ext;
+	unsigned int count, i, var_count;
+	struct sieve_variable_scope_binary *global_vars;
+	struct sieve_variable_scope *global_scope;
+	struct sieve_variable * const *vars;
+
+	if ( !sieve_binary_read_unsigned(denv->sblock, address, &count) )
+		return FALSE;
+
+	sieve_code_dumpf(denv, "GLOBAL (count: %u):", count);
+
+	global_vars = ext_include_binary_get_global_scope(this_ext, denv->sbin);
+	global_scope = sieve_variable_scope_binary_get(global_vars);
+	vars = sieve_variable_scope_get_variables(global_scope, &var_count);
+
+	sieve_code_descend(denv);
+
+	for ( i = 0; i < count; i++ ) {
+		unsigned int index;
+
+		sieve_code_mark(denv);
+		if ( !sieve_binary_read_unsigned(denv->sblock, address, &index) ||
+			index >= var_count )
+			return FALSE;
+
+		sieve_code_dumpf(denv, "%d: VAR[%d]: '%s'", i, index, vars[index]->identifier);
+	}
+
+	return TRUE;
+}
+
+/*
+ * Execution
+ */
+
+static int opc_global_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	const struct sieve_extension *this_ext = renv->oprtn->ext;
+	struct sieve_variable_scope_binary *global_vars;
+	struct sieve_variable_scope *global_scope;
+	struct sieve_variable_storage *storage;
+	struct sieve_variable * const *vars;
+	unsigned int var_count, count, i;
+
+	if ( !sieve_binary_read_unsigned(renv->sblock, address, &count) ) {
+		sieve_runtime_trace_error(renv,
+			"global: count operand invalid");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	global_vars = ext_include_binary_get_global_scope(this_ext, renv->sbin);
+	global_scope = sieve_variable_scope_binary_get(global_vars);
+	vars = sieve_variable_scope_get_variables(global_scope, &var_count);
+	storage = ext_include_interpreter_get_global_variables
+		(this_ext, renv->interp);
+
+	for ( i = 0; i < count; i++ ) {
+		unsigned int index;
+
+		if ( !sieve_binary_read_unsigned(renv->sblock, address, &index) ) {
+			sieve_runtime_trace_error(renv,
+				"global: variable index operand invalid");
+			return SIEVE_EXEC_BIN_CORRUPT;
+		}
+
+		if ( index >= var_count ) {
+			sieve_runtime_trace_error(renv,
+				"global: variable index %u is invalid in global storage (> %u)",
+				index, var_count);
+			return SIEVE_EXEC_BIN_CORRUPT;
+		}
+
+		sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS,
+			"global: exporting variable '%s' [gvid: %u, vid: %u]",
+			vars[index]->identifier, i, index);
+
+		/* Make sure variable is initialized (export) */
+		(void)sieve_variable_get_modifiable(storage, index, NULL);
+	}
+
+	return SIEVE_EXEC_OK;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/include/cmd-include.c
@@ -0,0 +1,406 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-script.h"
+#include "sieve-storage.h"
+#include "sieve-ast.h"
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-binary.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-include-common.h"
+#include "ext-include-binary.h"
+
+/*
+ * Include command
+ *
+ * Syntax:
+ *   include [LOCATION] [":once"] [":optional"] <value: string>
+ *
+ * [LOCATION]:
+ *   ":personal" / ":global"
+ */
+
+static bool cmd_include_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool cmd_include_pre_validate
+	(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *cmd);
+static bool cmd_include_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_include_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def cmd_include = {
+	.identifier = "include",
+	.type = SCT_COMMAND,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = cmd_include_registered,
+	.pre_validate = cmd_include_pre_validate,
+	.validate = cmd_include_validate,
+	.generate = cmd_include_generate
+};
+
+/*
+ * Include operation
+ */
+
+static bool opc_include_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int opc_include_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def include_operation = {
+	.mnemonic = "include",
+	.ext_def = &include_extension,
+	.code = EXT_INCLUDE_OPERATION_INCLUDE,
+	.dump = opc_include_dump,
+	.execute = opc_include_execute
+};
+
+/*
+ * Context structures
+ */
+
+struct cmd_include_context_data {
+	enum ext_include_script_location location;
+
+	struct sieve_script *script;
+	enum ext_include_flags flags;
+
+	bool location_assigned:1;
+};
+
+/*
+ * Tagged arguments
+ */
+
+static bool cmd_include_validate_location_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+
+static const struct sieve_argument_def include_personal_tag = {
+	.identifier = "personal",
+	.validate = cmd_include_validate_location_tag	
+};
+
+static const struct sieve_argument_def include_global_tag = {
+	.identifier = "global",
+	.validate = cmd_include_validate_location_tag
+};
+
+static bool cmd_include_validate_boolean_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+
+static const struct sieve_argument_def include_once_tag = {
+	.identifier = "once",
+	.validate = cmd_include_validate_boolean_tag
+};
+
+static const struct sieve_argument_def include_optional_tag = {
+	.identifier = "optional",
+	.validate = cmd_include_validate_boolean_tag
+};
+
+/*
+ * Tag validation
+ */
+
+static bool cmd_include_validate_location_tag
+(struct sieve_validator *valdtr,	struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	struct cmd_include_context_data *ctx_data =
+		(struct cmd_include_context_data *) cmd->data;
+
+	if ( ctx_data->location_assigned) {
+		sieve_argument_validate_error(valdtr, *arg,
+			"include: cannot use location tags ':personal' and ':global' "
+			"multiple times");
+		return FALSE;
+	}
+
+	if ( sieve_argument_is(*arg, include_personal_tag) )
+		ctx_data->location = EXT_INCLUDE_LOCATION_PERSONAL;
+	else if ( sieve_argument_is(*arg, include_global_tag) )
+		ctx_data->location = EXT_INCLUDE_LOCATION_GLOBAL;
+	else
+		return FALSE;
+
+	ctx_data->location_assigned = TRUE;
+
+	/* Delete this tag (for now) */
+	*arg = sieve_ast_arguments_detach(*arg, 1);
+
+	return TRUE;
+}
+
+static bool cmd_include_validate_boolean_tag
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	struct cmd_include_context_data *ctx_data =
+		(struct cmd_include_context_data *) cmd->data;
+
+	if ( sieve_argument_is(*arg, include_once_tag) )
+		ctx_data->flags |= EXT_INCLUDE_FLAG_ONCE;
+	else
+		ctx_data->flags |= EXT_INCLUDE_FLAG_OPTIONAL;
+
+	/* Delete this tag (for now) */
+	*arg = sieve_ast_arguments_detach(*arg, 1);
+
+	return TRUE;
+}
+
+/*
+ * Command registration
+ */
+
+static bool cmd_include_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg)
+{
+	sieve_validator_register_tag(valdtr, cmd_reg, ext, &include_personal_tag, 0);
+	sieve_validator_register_tag(valdtr, cmd_reg, ext, &include_global_tag, 0);
+	sieve_validator_register_tag(valdtr, cmd_reg, ext, &include_once_tag, 0);
+	sieve_validator_register_tag(valdtr, cmd_reg, ext, &include_optional_tag, 0);
+
+	return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+static bool cmd_include_pre_validate
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *cmd)
+{
+	struct cmd_include_context_data *ctx_data;
+
+	/* Assign context */
+	ctx_data = p_new(sieve_command_pool(cmd), struct cmd_include_context_data, 1);
+	ctx_data->location = EXT_INCLUDE_LOCATION_PERSONAL;
+	cmd->data = ctx_data;
+
+	return TRUE;
+}
+
+static bool cmd_include_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+	const struct sieve_extension *this_ext = cmd->ext;
+	struct sieve_ast_argument *arg = cmd->first_positional;
+	struct cmd_include_context_data *ctx_data =
+		(struct cmd_include_context_data *) cmd->data;
+	struct sieve_storage *storage;
+	struct sieve_script *script;
+	const char *script_name;
+	enum sieve_error error = SIEVE_ERROR_NONE;
+	int ret;
+
+	/* Check argument */
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "value", 1, SAAT_STRING) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, cmd, arg, FALSE) )
+		return FALSE;
+
+	/*
+	 * Variables are not allowed.
+	 */
+	if ( !sieve_argument_is_string_literal(arg) ) {
+		sieve_argument_validate_error(valdtr, arg,
+			"the include command requires a constant string for its value argument");
+		return FALSE;
+	}
+
+	/* Find the script */
+
+	script_name = sieve_ast_argument_strc(arg);
+
+	if ( !sieve_script_name_is_valid(script_name) ) {
+ 		sieve_argument_validate_error(valdtr, arg,
+			"include: invalid script name '%s'",
+			str_sanitize(script_name, 80));
+		return FALSE;
+	}
+
+	storage = ext_include_get_script_storage
+		(this_ext, ctx_data->location, script_name,	&error);
+	if ( storage == NULL ) {
+		// FIXME: handle ':optional' in this case
+		if (error == SIEVE_ERROR_NOT_FOUND) {
+			sieve_argument_validate_error(valdtr, arg,
+				"include: %s location for included script `%s' is unavailable "
+				"(contact system administrator for more information)",
+				ext_include_script_location_name(ctx_data->location),
+				str_sanitize(script_name, 80));
+		} else {
+			sieve_argument_validate_error(valdtr, arg,
+				"include: failed to access %s location for included script `%s' "
+				"(contact system administrator for more information)",
+				ext_include_script_location_name(ctx_data->location),
+				str_sanitize(script_name, 80));
+		}
+		return FALSE;
+	}
+
+	/* Create script object */
+	script = sieve_storage_get_script
+		(storage, script_name, &error);
+	if ( script == NULL )
+		return FALSE;
+
+	ret = sieve_script_open(script, &error);
+	if ( ret < 0 ) {
+		if ( error != SIEVE_ERROR_NOT_FOUND ) {
+			sieve_argument_validate_error(valdtr, arg,
+				"failed to access included %s script '%s': %s",
+				ext_include_script_location_name(ctx_data->location),
+				str_sanitize(script_name, 80),
+				sieve_script_get_last_error_lcase(script));
+			sieve_script_unref(&script);
+			return FALSE;
+
+		/* Not found */
+		} else {
+			enum sieve_compile_flags cpflags =
+				sieve_validator_compile_flags(valdtr);
+
+			if ( (ctx_data->flags & EXT_INCLUDE_FLAG_OPTIONAL) != 0 ) {
+				/* :optional */
+
+			} else if ( (cpflags & SIEVE_COMPILE_FLAG_UPLOADED) != 0 ) {
+				/* Script is being uploaded */
+				sieve_argument_validate_warning(valdtr, arg,
+					"included %s script '%s' does not exist (ignored during upload)",
+					ext_include_script_location_name(ctx_data->location),
+					str_sanitize(script_name, 80));
+				ctx_data->flags |= EXT_INCLUDE_FLAG_MISSING_AT_UPLOAD;
+
+			} else {
+				/* Should have existed */
+				sieve_argument_validate_error(valdtr, arg,
+					"included %s script '%s' does not exist",
+					ext_include_script_location_name(ctx_data->location),
+					str_sanitize(script_name, 80));
+				sieve_script_unref(&script);
+				return FALSE;
+			}
+		}
+	}
+
+	ext_include_ast_link_included_script(cmd->ext, cmd->ast_node->ast, script);
+	ctx_data->script = script;
+
+	(void)sieve_ast_arguments_detach(arg, 1);
+	return TRUE;
+}
+
+/*
+ * Code Generation
+ */
+
+static bool cmd_include_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	struct cmd_include_context_data *ctx_data =
+		(struct cmd_include_context_data *) cmd->data;
+	const struct ext_include_script_info *included;
+	int ret;
+
+	/* Compile (if necessary) and include the script into the binary.
+	 * This yields the id of the binary block containing the compiled byte code.
+	 */
+	if ( (ret=ext_include_generate_include
+		(cgenv, cmd, ctx_data->location, ctx_data->flags, ctx_data->script,
+			&included)) < 0 )
+ 		return FALSE;
+
+	if ( ret > 0 ) {
+	 	(void)sieve_operation_emit(cgenv->sblock, cmd->ext, &include_operation);
+		(void)sieve_binary_emit_unsigned(cgenv->sblock, included->id);
+		(void)sieve_binary_emit_byte(cgenv->sblock, ctx_data->flags);
+	}
+
+	return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool opc_include_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	const struct ext_include_script_info *included;
+	struct ext_include_binary_context *binctx;
+	unsigned int include_id, flags;
+
+	sieve_code_dumpf(denv, "INCLUDE:");
+
+	sieve_code_mark(denv);
+	if ( !sieve_binary_read_unsigned(denv->sblock, address, &include_id) )
+		return FALSE;
+
+	if ( !sieve_binary_read_byte(denv->sblock, address, &flags) )
+		return FALSE;
+
+	binctx = ext_include_binary_get_context(denv->oprtn->ext, denv->sbin);
+	included = ext_include_binary_script_get_included(binctx, include_id);
+	if ( included == NULL )
+		return FALSE;
+
+	sieve_code_descend(denv);
+	sieve_code_dumpf(denv, "script: `%s' from %s %s%s[ID: %d, BLOCK: %d]",
+		sieve_script_name(included->script), sieve_script_location(included->script),
+		((flags & EXT_INCLUDE_FLAG_ONCE) != 0 ? "(once) " : ""), 
+		((flags & EXT_INCLUDE_FLAG_OPTIONAL) != 0 ? "(optional) " : ""),
+		include_id, sieve_binary_block_get_id(included->block));
+
+	return TRUE;
+}
+
+/*
+ * Execution
+ */
+
+static int opc_include_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	unsigned int include_id, flags;
+
+	if ( !sieve_binary_read_unsigned(renv->sblock, address, &include_id) ) {
+		sieve_runtime_trace_error(renv, "invalid include-id operand");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	if ( !sieve_binary_read_unsigned(renv->sblock, address, &flags) ) {
+		sieve_runtime_trace_error(renv, "invalid flags operand");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	return ext_include_execute_include
+		(renv, include_id, (enum ext_include_flags)flags);
+}
+
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/include/cmd-return.c
@@ -0,0 +1,71 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-code.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+
+#include "ext-include-common.h"
+
+/*
+ * Return command
+ *
+ * Syntax
+ *   return
+ */
+
+static bool cmd_return_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+const struct sieve_command_def cmd_return = {
+	.identifier = "return",
+	.type = SCT_COMMAND,
+	.positional_args = 0,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.generate = cmd_return_generate
+};
+
+/*
+ * Return operation
+ */
+
+static int opc_return_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def return_operation = {
+	.mnemonic = "RETURN",
+	.ext_def = &include_extension,
+	.code = EXT_INCLUDE_OPERATION_RETURN,
+	.execute = opc_return_execute
+};
+
+/*
+ * Code generation
+ */
+
+static bool cmd_return_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	sieve_operation_emit(cgenv->sblock, cmd->ext, &return_operation);
+
+	return TRUE;
+}
+
+/*
+ * Execution
+ */
+
+static int opc_return_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED)
+{
+	ext_include_execute_return(renv);
+	return SIEVE_EXEC_OK;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/include/ext-include-binary.c
@@ -0,0 +1,488 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-script.h"
+#include "sieve-storage.h"
+#include "sieve-binary.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "sieve-ext-variables.h"
+
+#include "ext-include-common.h"
+#include "ext-include-limits.h"
+#include "ext-include-variables.h"
+#include "ext-include-binary.h"
+
+/*
+ * Forward declarations
+ */
+
+static bool ext_include_binary_pre_save
+	(const struct sieve_extension *ext, struct sieve_binary *sbin,
+		void *context, enum sieve_error *error_r);
+static bool ext_include_binary_open
+	(const struct sieve_extension *ext, struct sieve_binary *sbin,
+		void *context);
+static bool ext_include_binary_up_to_date
+	(const struct sieve_extension *ext, struct sieve_binary *sbin,
+		void *context, enum sieve_compile_flags cpflags);
+static void ext_include_binary_free
+	(const struct sieve_extension *ext, struct sieve_binary *sbin,
+		void *context);
+
+/*
+ * Binary include extension
+ */
+
+const struct sieve_binary_extension include_binary_ext = {
+	.extension = &include_extension,
+	.binary_pre_save = ext_include_binary_pre_save,
+	.binary_open = ext_include_binary_open,
+	.binary_free = ext_include_binary_free,
+	.binary_up_to_date = ext_include_binary_up_to_date
+};
+
+/*
+ * Binary context management
+ */
+
+struct ext_include_binary_context {
+	struct sieve_binary *binary;
+	struct sieve_binary_block *dependency_block;
+
+	HASH_TABLE(struct sieve_script *,
+		   struct ext_include_script_info *) included_scripts;
+	ARRAY(struct ext_include_script_info *) include_index;
+
+	struct sieve_variable_scope_binary *global_vars;
+
+	bool outdated:1;
+};
+
+static struct ext_include_binary_context *ext_include_binary_create_context
+(const struct sieve_extension *this_ext, struct sieve_binary *sbin)
+{
+	pool_t pool = sieve_binary_pool(sbin);
+
+	struct ext_include_binary_context *ctx =
+		p_new(pool, struct ext_include_binary_context, 1);
+
+	ctx->binary = sbin;
+	hash_table_create(&ctx->included_scripts, pool, 0,
+		sieve_script_hash, sieve_script_cmp);
+	p_array_init(&ctx->include_index, pool, 128);
+
+	sieve_binary_extension_set(sbin, this_ext, &include_binary_ext, ctx);
+
+	return ctx;
+}
+
+struct ext_include_binary_context *ext_include_binary_get_context
+(const struct sieve_extension *this_ext, struct sieve_binary *sbin)
+{
+	struct ext_include_binary_context *ctx = (struct ext_include_binary_context *)
+		sieve_binary_extension_get_context(sbin, this_ext);
+
+	if ( ctx == NULL )
+		ctx = ext_include_binary_create_context(this_ext, sbin);
+
+	return ctx;
+}
+
+struct ext_include_binary_context *ext_include_binary_init
+(const struct sieve_extension *this_ext, struct sieve_binary *sbin,
+	struct sieve_ast *ast)
+{
+	struct ext_include_ast_context *ast_ctx =
+		ext_include_get_ast_context(this_ext, ast);
+	struct ext_include_binary_context *ctx;
+
+	/* Get/create our context from the binary we are working on */
+	ctx = ext_include_binary_get_context(this_ext, sbin);
+
+	/* Create dependency block */
+	if ( ctx->dependency_block == 0 )
+		ctx->dependency_block =
+			sieve_binary_extension_create_block(sbin, this_ext);
+
+	if ( ctx->global_vars == NULL ) {
+		ctx->global_vars =
+			sieve_variable_scope_binary_create(ast_ctx->global_vars);
+		sieve_variable_scope_binary_ref(ctx->global_vars);
+	}
+
+	return ctx;
+}
+
+/*
+ * Script inclusion
+ */
+
+struct ext_include_script_info *ext_include_binary_script_include
+(struct ext_include_binary_context *binctx, 
+	enum ext_include_script_location location, enum ext_include_flags flags,
+	struct sieve_script *script,	struct sieve_binary_block *inc_block)
+{
+	pool_t pool = sieve_binary_pool(binctx->binary);
+	struct ext_include_script_info *incscript;
+
+	incscript = p_new(pool, struct ext_include_script_info, 1);
+	incscript->id = array_count(&binctx->include_index)+1;
+	incscript->location = location;
+	incscript->flags = flags;
+	incscript->script = script;
+	incscript->block = inc_block;
+
+	/* Unreferenced on binary_free */
+	sieve_script_ref(script);
+
+	hash_table_insert(binctx->included_scripts, script, incscript);
+	array_append(&binctx->include_index, &incscript, 1);
+
+	return incscript;
+}
+
+struct ext_include_script_info *ext_include_binary_script_get_include_info
+(struct ext_include_binary_context *binctx, struct sieve_script *script)
+{
+	struct ext_include_script_info *incscript =
+		hash_table_lookup(binctx->included_scripts, script);
+
+	return incscript;
+}
+
+const struct ext_include_script_info *ext_include_binary_script_get_included
+(struct ext_include_binary_context *binctx, unsigned int include_id)
+{
+	if ( include_id > 0 &&
+		(include_id - 1) < array_count(&binctx->include_index) ) {
+		struct ext_include_script_info *const *sinfo =
+			array_idx(&binctx->include_index, include_id - 1);
+
+		return *sinfo;
+	}
+
+	return NULL;
+}
+
+const struct ext_include_script_info *ext_include_binary_script_get
+(struct ext_include_binary_context *binctx, struct sieve_script *script)
+{
+	return hash_table_lookup(binctx->included_scripts, script);
+}
+
+unsigned int ext_include_binary_script_get_count
+(struct ext_include_binary_context *binctx)
+{
+	return array_count(&binctx->include_index);
+}
+
+/*
+ * Variables
+ */
+
+struct sieve_variable_scope_binary *ext_include_binary_get_global_scope
+(const struct sieve_extension *this_ext, struct sieve_binary *sbin)
+{
+	struct ext_include_binary_context *binctx =
+		ext_include_binary_get_context(this_ext, sbin);
+
+	return binctx->global_vars;
+}
+
+/*
+ * Binary extension
+ */
+
+static bool ext_include_binary_pre_save
+(const struct sieve_extension *ext ATTR_UNUSED,
+	struct sieve_binary *sbin ATTR_UNUSED, void *context,
+	enum sieve_error *error_r)
+{
+	struct ext_include_binary_context *binctx =
+		(struct ext_include_binary_context *) context;
+	struct ext_include_script_info *const *scripts;
+	struct sieve_binary_block *sblock = binctx->dependency_block;
+	unsigned int script_count, i;
+	bool result = TRUE;
+
+	sieve_binary_block_clear(sblock);
+
+	scripts = array_get(&binctx->include_index, &script_count);
+
+	sieve_binary_emit_unsigned(sblock, script_count);
+
+	for ( i = 0; i < script_count; i++ ) {
+		struct ext_include_script_info *incscript = scripts[i];
+
+		if ( incscript->block != NULL ) {
+			sieve_binary_emit_unsigned
+				(sblock, sieve_binary_block_get_id(incscript->block));
+		} else {
+			sieve_binary_emit_unsigned(sblock, 0);
+		}
+		sieve_binary_emit_byte(sblock, incscript->location);
+		sieve_binary_emit_cstring(sblock, sieve_script_name(incscript->script));
+		sieve_binary_emit_byte(sblock, incscript->flags);
+		sieve_script_binary_write_metadata(incscript->script, sblock);
+	}
+
+	result = ext_include_variables_save(sblock, binctx->global_vars, error_r);
+
+	return result;
+}
+
+static bool ext_include_binary_open
+(const struct sieve_extension *ext, struct sieve_binary *sbin, void *context)
+{
+	struct sieve_instance *svinst = ext->svinst;
+	struct ext_include_context *ext_ctx =
+		(struct ext_include_context *)ext->context;
+	struct ext_include_binary_context *binctx =
+		(struct ext_include_binary_context *) context;
+	struct sieve_binary_block *sblock;
+	unsigned int depcount, i, block_id;
+	sieve_size_t offset;
+
+	sblock = sieve_binary_extension_get_block(sbin, ext);
+	block_id = sieve_binary_block_get_id(sblock);
+
+	offset = 0;
+
+	if ( !sieve_binary_read_unsigned(sblock, &offset, &depcount) ) {
+		sieve_sys_error(svinst,
+			"include: failed to read include count "
+			"for dependency block %d of binary %s", block_id,
+			sieve_binary_path(sbin));
+		return FALSE;
+	}
+
+	/* Check include limit */
+	if ( depcount > ext_ctx->max_includes ) {
+		sieve_sys_error(svinst,
+			"include: binary %s includes too many scripts (%u > %u)",
+			sieve_binary_path(sbin), depcount, ext_ctx->max_includes);
+		return FALSE;
+	}
+
+	/* Read dependencies */
+	for ( i = 0; i < depcount; i++ ) {
+		unsigned int inc_block_id;
+		struct sieve_binary_block *inc_block = NULL;
+		unsigned int location, flags;
+		string_t *script_name;
+		struct sieve_storage *storage;
+		struct sieve_script *script;
+		enum sieve_error error;
+		int ret;
+
+		if (
+			!sieve_binary_read_unsigned(sblock, &offset, &inc_block_id) ||
+			!sieve_binary_read_byte(sblock, &offset, &location) ||
+			!sieve_binary_read_string(sblock, &offset, &script_name) ||
+			!sieve_binary_read_byte(sblock, &offset, &flags) ) {
+			/* Binary is corrupt, recompile */
+			sieve_sys_error(svinst,
+				"include: failed to read included script "
+				"from dependency block %d of binary %s", block_id,
+				sieve_binary_path(sbin));
+			return FALSE;
+		}
+
+		if ( inc_block_id != 0 &&
+			(inc_block=sieve_binary_block_get(sbin, inc_block_id)) == NULL ) {
+			sieve_sys_error(svinst,
+				"include: failed to find block %d for included script "
+				"from dependency block %d of binary %s", inc_block_id, block_id,
+				sieve_binary_path(sbin));
+			return FALSE;
+		}
+
+		if ( location >= EXT_INCLUDE_LOCATION_INVALID ) {
+			/* Binary is corrupt, recompile */
+			sieve_sys_error(svinst,
+				"include: dependency block %d of binary %s "
+				"uses invalid script location (id %d)",
+				block_id, sieve_binary_path(sbin), location);
+			return FALSE;
+		}
+
+		/* Can we find the script dependency ? */
+		storage = ext_include_get_script_storage
+			(ext, location, str_c(script_name), &error);
+		if ( storage == NULL ) {
+			/* No, recompile */
+			// FIXME: handle ':optional' in this case
+			return FALSE;
+		}
+
+		/* Can we open the script dependency ? */
+		script = sieve_storage_get_script
+			(storage, str_c(script_name), &error);
+		if ( script == NULL ) {
+			/* No, recompile */
+			return FALSE;
+		}
+		if ( sieve_script_open(script, &error) < 0 ) {
+			if ( error != SIEVE_ERROR_NOT_FOUND ) {
+				/* No, recompile */
+				return FALSE;
+			}
+
+			if ( (flags & EXT_INCLUDE_FLAG_OPTIONAL) == 0 ) {
+				/* Not supposed to be missing, recompile */
+				if ( svinst->debug ) {
+					sieve_sys_debug(svinst,
+						"include: script '%s' included in binary %s is missing, "
+						"so recompile", str_c(script_name), sieve_binary_path(sbin));
+				}
+				return FALSE;
+			}
+
+		} else if (inc_block == NULL) {
+			/* Script exists, but it is missing from the binary, recompile no matter
+			 * what.
+			 */
+			if ( svinst->debug ) {
+				sieve_sys_debug(svinst,
+					"include: script '%s' is missing in binary %s, but is now available, "
+					"so recompile", str_c(script_name), sieve_binary_path(sbin));
+			}
+			sieve_script_unref(&script);
+			return FALSE;
+		}
+
+		/* Can we read script metadata ? */
+		if ( (ret=sieve_script_binary_read_metadata
+			(script, sblock, &offset))	< 0 ) {
+			/* Binary is corrupt, recompile */
+			sieve_sys_error(svinst,
+				"include: dependency block %d of binary %s "
+				"contains invalid script metadata for script %s",
+				block_id, sieve_binary_path(sbin), sieve_script_location(script));
+			sieve_script_unref(&script);
+			return FALSE;
+		}
+
+		if ( ret == 0 )
+			binctx->outdated = TRUE;
+
+		(void)ext_include_binary_script_include
+			(binctx, location, flags, script, inc_block);
+
+		sieve_script_unref(&script);
+	}
+
+	if ( !ext_include_variables_load
+		(ext, sblock, &offset, &binctx->global_vars) )
+		return FALSE;
+
+	return TRUE;
+}
+
+static bool ext_include_binary_up_to_date
+(const struct sieve_extension *ext ATTR_UNUSED,
+	struct sieve_binary *sbin ATTR_UNUSED, void *context,
+	enum sieve_compile_flags cpflags ATTR_UNUSED)
+{
+	struct ext_include_binary_context *binctx =
+		(struct ext_include_binary_context *) context;
+
+	return !binctx->outdated;
+}
+
+static void ext_include_binary_free
+(const struct sieve_extension *ext ATTR_UNUSED,
+	struct sieve_binary *sbin ATTR_UNUSED, void *context)
+{
+	struct ext_include_binary_context *binctx =
+		(struct ext_include_binary_context *) context;
+	struct hash_iterate_context *hctx;
+	struct sieve_script *script;
+	struct ext_include_script_info *incscript;
+
+	/* Release references to all included script objects */
+	hctx = hash_table_iterate_init(binctx->included_scripts);
+	while ( hash_table_iterate
+		(hctx, binctx->included_scripts, &script, &incscript) )
+		sieve_script_unref(&incscript->script);
+	hash_table_iterate_deinit(&hctx);
+
+	hash_table_destroy(&binctx->included_scripts);
+
+	if ( binctx->global_vars != NULL )
+		sieve_variable_scope_binary_unref(&binctx->global_vars);
+}
+
+/*
+ * Dumping the binary
+ */
+
+bool ext_include_binary_dump
+(const struct sieve_extension *ext, struct sieve_dumptime_env *denv)
+{
+	struct sieve_binary *sbin = denv->sbin;
+	struct ext_include_binary_context *binctx =
+		ext_include_binary_get_context(ext, sbin);
+	struct hash_iterate_context *hctx;
+	struct sieve_script *script;
+	struct ext_include_script_info *incscript;
+
+	if ( !ext_include_variables_dump(denv, binctx->global_vars) )
+		return FALSE;
+
+	hctx = hash_table_iterate_init(binctx->included_scripts);
+	while ( hash_table_iterate
+		(hctx, binctx->included_scripts, &script, &incscript) ) {
+
+		if ( incscript->block == NULL ) {
+			sieve_binary_dump_sectionf(denv, "Included %s script '%s' (MISSING)",
+				ext_include_script_location_name(incscript->location),
+				sieve_script_name(incscript->script));
+
+		} else {
+			unsigned int block_id = sieve_binary_block_get_id(incscript->block);
+
+			sieve_binary_dump_sectionf(denv, "Included %s script '%s' (block: %d)",
+				ext_include_script_location_name(incscript->location),
+				sieve_script_name(incscript->script), block_id);
+
+			denv->sblock = incscript->block;
+			denv->cdumper = sieve_code_dumper_create(denv);
+
+			if ( denv->cdumper == NULL )
+				return FALSE;
+
+			sieve_code_dumper_run(denv->cdumper);
+			sieve_code_dumper_free(&(denv->cdumper));
+		}
+	}
+	hash_table_iterate_deinit(&hctx);
+
+	return TRUE;
+}
+
+bool ext_include_code_dump
+(const struct sieve_extension *ext, const struct sieve_dumptime_env *denv,
+	sieve_size_t *address ATTR_UNUSED)
+{
+	struct sieve_binary *sbin = denv->sbin;
+	struct ext_include_binary_context *binctx =
+		ext_include_binary_get_context(ext, sbin);
+	struct ext_include_context *ectx = ext_include_get_context(ext);
+
+	sieve_ext_variables_dump_set_scope
+		(ectx->var_ext, denv, ext,
+			sieve_variable_scope_binary_get(binctx->global_vars));
+
+	return TRUE;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/include/ext-include-binary.h
@@ -0,0 +1,64 @@
+#ifndef EXT_INCLUDE_BINARY_H
+#define EXT_INCLUDE_BINARY_H
+
+#include "sieve-common.h"
+
+/*
+ * Binary context management
+ */
+
+struct ext_include_binary_context;
+
+struct ext_include_binary_context *ext_include_binary_init
+	(const struct sieve_extension *this_ext, struct sieve_binary *sbin,
+		struct sieve_ast *ast);
+struct ext_include_binary_context *ext_include_binary_get_context
+	(const struct sieve_extension *this_ext, struct sieve_binary *sbin);
+
+/*
+ * Variables
+ */
+
+struct sieve_variable_scope_binary *ext_include_binary_get_global_scope
+	(const struct sieve_extension *this_ext, struct sieve_binary *sbin);
+
+/*
+ * Including scripts
+ */
+
+struct ext_include_script_info {
+	unsigned int id;
+
+	struct sieve_script *script;
+	enum ext_include_flags flags;
+	enum ext_include_script_location location;
+
+	struct sieve_binary_block *block;
+};
+
+struct ext_include_script_info *ext_include_binary_script_include
+	(struct ext_include_binary_context *binctx, 
+		enum ext_include_script_location location, enum ext_include_flags flags,
+		struct sieve_script *script, struct sieve_binary_block *inc_block);
+struct ext_include_script_info *ext_include_binary_script_get_include_info
+	(struct ext_include_binary_context *binctx, struct sieve_script *script);
+
+const struct ext_include_script_info *ext_include_binary_script_get_included
+	(struct ext_include_binary_context *binctx, unsigned int include_id);
+const struct ext_include_script_info *ext_include_binary_script_get
+	(struct ext_include_binary_context *binctx, struct sieve_script *script);
+unsigned int ext_include_binary_script_get_count
+	(struct ext_include_binary_context *binctx);
+
+/*
+ * Dumping the binary
+ */
+
+bool ext_include_binary_dump
+	(const struct sieve_extension *ext, struct sieve_dumptime_env *denv);
+bool ext_include_code_dump
+	(const struct sieve_extension *ext, const struct sieve_dumptime_env *denv,
+		sieve_size_t *address ATTR_UNUSED);
+
+#endif
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/include/ext-include-common.c
@@ -0,0 +1,849 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "str-sanitize.h"
+#include "home-expand.h"
+
+#include "sieve-common.h"
+#include "sieve-settings.h"
+#include "sieve-error.h"
+#include "sieve-script.h"
+#include "sieve-storage.h"
+#include "sieve-ast.h"
+#include "sieve-binary.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+
+#include "ext-include-common.h"
+#include "ext-include-limits.h"
+#include "ext-include-binary.h"
+#include "ext-include-variables.h"
+
+
+/*
+ * Forward declarations
+ */
+
+/* Generator context */
+
+struct ext_include_generator_context {
+	unsigned int nesting_depth;
+	struct sieve_script *script;
+	struct ext_include_generator_context *parent;
+};
+
+static inline struct ext_include_generator_context *
+	ext_include_get_generator_context
+	(const struct sieve_extension *ext_this, struct sieve_generator *gentr);
+
+/* Interpreter context */
+
+struct ext_include_interpreter_global {
+	ARRAY(struct sieve_script *) included_scripts;
+
+	struct sieve_variable_scope_binary *var_scope;
+	struct sieve_variable_storage *var_storage;
+};
+
+struct ext_include_interpreter_context {
+	struct ext_include_interpreter_context *parent;
+	struct ext_include_interpreter_global *global;
+
+	struct sieve_interpreter *interp;
+	pool_t pool;
+
+	unsigned int nesting_depth;
+
+	struct sieve_script *script;
+	const struct ext_include_script_info *script_info;
+
+	const struct ext_include_script_info *include;
+	bool returned;
+};
+
+/*
+ * Extension configuration
+ */
+
+/* Extension hooks */
+
+bool ext_include_load
+(const struct sieve_extension *ext, void **context)
+{
+	struct sieve_instance *svinst = ext->svinst;
+	struct ext_include_context *ctx;
+	const char *location;
+	unsigned long long int uint_setting;
+
+	if ( *context != NULL ) {
+		ext_include_unload(ext);
+	}
+
+	ctx = i_new(struct ext_include_context, 1);
+
+	/* Get location for :global scripts */
+	location = sieve_setting_get(svinst, "sieve_global");
+	if ( location == NULL )
+		location = sieve_setting_get(svinst, "sieve_global_dir");
+
+	if ( location == NULL && svinst->debug ) {
+		sieve_sys_debug(svinst, "include: sieve_global is not set; "
+			"it is currently not possible to include `:global' scripts.");
+	}
+
+	ctx->global_location = i_strdup(location);
+
+	/* Get limits */
+	ctx->max_nesting_depth = EXT_INCLUDE_DEFAULT_MAX_NESTING_DEPTH;
+	ctx->max_includes = EXT_INCLUDE_DEFAULT_MAX_INCLUDES;
+
+	if ( sieve_setting_get_uint_value
+		(svinst, "sieve_include_max_nesting_depth", &uint_setting) ) {
+		ctx->max_nesting_depth = (unsigned int) uint_setting;
+	}
+
+	if ( sieve_setting_get_uint_value
+		(svinst, "sieve_include_max_includes", &uint_setting) ) {
+		ctx->max_includes = (unsigned int) uint_setting;
+	}
+
+	/* Extension dependencies */
+	ctx->var_ext = sieve_ext_variables_get_extension(ext->svinst);
+
+	*context = (void *)ctx;
+
+	return TRUE;
+}
+
+void ext_include_unload
+(const struct sieve_extension *ext)
+{
+	struct ext_include_context *ctx =
+		(struct ext_include_context *) ext->context;
+
+	if ( ctx->global_storage != NULL )
+		sieve_storage_unref(&ctx->global_storage);
+	if ( ctx->personal_storage != NULL )
+		sieve_storage_unref(&ctx->personal_storage);
+
+	i_free(ctx->global_location);
+	i_free(ctx);
+}
+
+/*
+ * Script access
+ */
+
+struct sieve_storage *ext_include_get_script_storage
+(const struct sieve_extension *ext,
+	enum ext_include_script_location location,
+	const char *script_name, enum sieve_error *error_r)
+{
+	struct sieve_instance *svinst = ext->svinst;
+	struct ext_include_context *ctx =
+		(struct ext_include_context *) ext->context;
+
+	switch ( location ) {
+	case EXT_INCLUDE_LOCATION_PERSONAL:
+		if ( ctx->personal_storage == NULL ) {
+			ctx->personal_storage = sieve_storage_create_main
+				(svinst, NULL, 0, error_r);
+		}
+		return ctx->personal_storage;
+
+	case EXT_INCLUDE_LOCATION_GLOBAL:
+		if ( ctx->global_location == NULL ) {
+			sieve_sys_info(svinst, "include: sieve_global is unconfigured; "
+				"include of `:global' script `%s' is therefore not possible",
+				str_sanitize(script_name, 80));
+			if ( error_r != NULL )
+				*error_r = SIEVE_ERROR_NOT_FOUND;
+			return NULL;
+		}
+		if ( ctx->global_storage == NULL ) {
+			ctx->global_storage = sieve_storage_create
+				(svinst, ctx->global_location, 0, error_r);
+		}
+		return ctx->global_storage;
+	default:
+		break;
+	}
+
+	i_unreached();
+	return NULL;
+}
+
+/*
+ * AST context management
+ */
+
+static void ext_include_ast_free
+(const struct sieve_extension *ext ATTR_UNUSED,
+	struct sieve_ast *ast ATTR_UNUSED, void *context)
+{
+	struct ext_include_ast_context *actx =
+		(struct ext_include_ast_context *) context;
+	struct sieve_script **scripts;
+	unsigned int count, i;
+
+	/* Unreference included scripts */
+	scripts = array_get_modifiable(&actx->included_scripts, &count);
+	for ( i = 0; i < count; i++ ) {
+		sieve_script_unref(&scripts[i]);
+	}
+
+	/* Unreference variable scopes */
+	if ( actx->global_vars != NULL )
+		sieve_variable_scope_unref(&actx->global_vars);
+}
+
+static const struct sieve_ast_extension include_ast_extension = {
+	&include_extension,
+	ext_include_ast_free
+};
+
+struct ext_include_ast_context *ext_include_create_ast_context
+(const struct sieve_extension *this_ext, struct sieve_ast *ast,
+	struct sieve_ast *parent)
+{
+	struct ext_include_ast_context *actx;
+
+	pool_t pool = sieve_ast_pool(ast);
+	actx = p_new(pool, struct ext_include_ast_context, 1);
+	p_array_init(&actx->included_scripts, pool, 32);
+
+	if ( parent != NULL ) {
+		struct ext_include_ast_context *parent_ctx =
+			(struct ext_include_ast_context *)
+				sieve_ast_extension_get_context(parent, this_ext);
+		actx->global_vars = parent_ctx->global_vars;
+
+		i_assert( actx->global_vars != NULL );
+
+		sieve_variable_scope_ref(actx->global_vars);
+	} else {
+		struct ext_include_context *ectx =
+			ext_include_get_context(this_ext);
+		actx->global_vars = sieve_variable_scope_create
+			(this_ext->svinst, ectx->var_ext, this_ext);
+	}
+
+	sieve_ast_extension_register
+		(ast, this_ext, &include_ast_extension, (void *) actx);
+
+	return actx;
+}
+
+struct ext_include_ast_context *ext_include_get_ast_context
+(const struct sieve_extension *this_ext, struct sieve_ast *ast)
+{
+	struct ext_include_ast_context *actx = (struct ext_include_ast_context *)
+		sieve_ast_extension_get_context(ast, this_ext);
+
+	if ( actx != NULL ) return actx;
+
+	return ext_include_create_ast_context(this_ext, ast, NULL);
+}
+
+void ext_include_ast_link_included_script
+(const struct sieve_extension *this_ext, struct sieve_ast *ast,
+	struct sieve_script *script)
+{
+	struct ext_include_ast_context *actx =
+		ext_include_get_ast_context(this_ext, ast);
+
+	array_append(&actx->included_scripts, &script, 1);
+}
+
+bool ext_include_validator_have_variables
+(const struct sieve_extension *this_ext, struct sieve_validator *valdtr)
+{
+	struct ext_include_context *ectx = ext_include_get_context(this_ext);
+
+	return sieve_ext_variables_is_active(ectx->var_ext, valdtr);
+}
+
+/*
+ * Generator context management
+ */
+
+static struct ext_include_generator_context *
+ext_include_create_generator_context
+(struct sieve_generator *gentr, struct ext_include_generator_context *parent,
+	struct sieve_script *script)
+{
+	struct ext_include_generator_context *ctx;
+
+	pool_t pool = sieve_generator_pool(gentr);
+	ctx = p_new(pool, struct ext_include_generator_context, 1);
+	ctx->parent = parent;
+	ctx->script = script;
+	if ( parent == NULL ) {
+		ctx->nesting_depth = 0;
+	} else {
+		ctx->nesting_depth = parent->nesting_depth + 1;
+	}
+
+	return ctx;
+}
+
+static inline struct ext_include_generator_context *
+	ext_include_get_generator_context
+(const struct sieve_extension *this_ext, struct sieve_generator *gentr)
+{
+	return (struct ext_include_generator_context *)
+		sieve_generator_extension_get_context(gentr, this_ext);
+}
+
+static inline void ext_include_initialize_generator_context
+(const struct sieve_extension *this_ext, struct sieve_generator *gentr,
+	struct ext_include_generator_context *parent, struct sieve_script *script)
+{
+	sieve_generator_extension_set_context(gentr, this_ext,
+		ext_include_create_generator_context(gentr, parent, script));
+}
+
+void ext_include_register_generator_context
+(const struct sieve_extension *this_ext, const struct sieve_codegen_env *cgenv)
+{
+	struct ext_include_generator_context *ctx =
+		ext_include_get_generator_context(this_ext, cgenv->gentr);
+
+	/* Initialize generator context if necessary */
+	if ( ctx == NULL ) {
+		ctx = ext_include_create_generator_context(
+			cgenv->gentr, NULL, cgenv->script);
+
+		sieve_generator_extension_set_context
+			(cgenv->gentr, this_ext, (void *) ctx);
+	}
+
+	/* Initialize ast context if necessary */
+	(void)ext_include_get_ast_context(this_ext, cgenv->ast);
+	(void)ext_include_binary_init(this_ext, cgenv->sbin, cgenv->ast);
+}
+
+/*
+ * Runtime initialization
+ */
+
+static int ext_include_runtime_init
+(const struct sieve_extension *this_ext,
+	const struct sieve_runtime_env *renv,
+	void *context, bool deferred ATTR_UNUSED)
+{
+	struct ext_include_interpreter_context *ctx =
+		(struct ext_include_interpreter_context *) context;
+	struct ext_include_context *ectx = ext_include_get_context(this_ext);
+
+	if ( ctx->parent == NULL ) {
+		ctx->global = p_new(ctx->pool, struct ext_include_interpreter_global, 1);
+		p_array_init(&ctx->global->included_scripts, ctx->pool, 10);
+
+		ctx->global->var_scope =
+			ext_include_binary_get_global_scope(this_ext, renv->sbin);
+		ctx->global->var_storage =
+			sieve_variable_storage_create(ectx->var_ext,
+				ctx->pool, ctx->global->var_scope);
+	} else {
+		ctx->global = ctx->parent->global;
+	}
+
+	sieve_ext_variables_runtime_set_storage
+		(ectx->var_ext, renv, this_ext, ctx->global->var_storage);
+	return SIEVE_EXEC_OK;
+}
+
+static struct sieve_interpreter_extension include_interpreter_extension = {
+	.ext_def = &include_extension,
+	.run = ext_include_runtime_init
+};
+
+/*
+ * Interpreter context management
+ */
+
+static struct ext_include_interpreter_context *
+	ext_include_interpreter_context_create
+(struct sieve_interpreter *interp,
+	struct ext_include_interpreter_context *parent,
+	struct sieve_script *script, const struct ext_include_script_info *sinfo)
+{
+	struct ext_include_interpreter_context *ctx;
+
+	pool_t pool = sieve_interpreter_pool(interp);
+	ctx = p_new(pool, struct ext_include_interpreter_context, 1);
+	ctx->pool = pool;
+	ctx->parent = parent;
+	ctx->interp = interp;
+	ctx->script = script;
+	ctx->script_info = sinfo;
+
+	if ( parent == NULL ) {
+		ctx->nesting_depth = 0;
+	} else {
+		ctx->nesting_depth = parent->nesting_depth + 1;
+	}
+
+	return ctx;
+}
+
+static inline struct ext_include_interpreter_context *
+	ext_include_get_interpreter_context
+(const struct sieve_extension *this_ext, struct sieve_interpreter *interp)
+{
+	return (struct ext_include_interpreter_context *)
+		sieve_interpreter_extension_get_context(interp, this_ext);
+}
+
+static inline struct ext_include_interpreter_context *
+	ext_include_interpreter_context_init_child
+(const struct sieve_extension *this_ext, struct sieve_interpreter *interp,
+	struct ext_include_interpreter_context *parent,
+	struct sieve_script *script, const struct ext_include_script_info *sinfo)
+{
+	struct ext_include_interpreter_context *ctx =
+		ext_include_interpreter_context_create(interp, parent, script, sinfo);
+
+	sieve_interpreter_extension_register
+		(interp, this_ext, &include_interpreter_extension, ctx);
+
+	return ctx;
+}
+
+void ext_include_interpreter_context_init
+(const struct sieve_extension *this_ext, struct sieve_interpreter *interp)
+{
+	struct ext_include_interpreter_context *ctx =
+		ext_include_get_interpreter_context(this_ext, interp);
+
+	/* Is this is the top-level interpreter ? */
+	if ( ctx == NULL ) {
+		struct sieve_script *script;
+
+		/* Initialize top context */
+		script = sieve_interpreter_script(interp);
+		ctx = ext_include_interpreter_context_create
+			(interp, NULL, script, NULL);
+
+		sieve_interpreter_extension_register
+			(interp, this_ext, &include_interpreter_extension, (void *) ctx);
+	}
+}
+
+struct sieve_variable_storage *ext_include_interpreter_get_global_variables
+(const struct sieve_extension *this_ext, struct sieve_interpreter *interp)
+{
+	struct ext_include_interpreter_context *ctx =
+		ext_include_get_interpreter_context(this_ext, interp);
+
+	return ctx->global->var_storage;
+}
+
+/*
+ * Including a script during code generation
+ */
+
+int ext_include_generate_include
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd,
+	enum ext_include_script_location location, 	enum ext_include_flags flags,
+	struct sieve_script *script,
+	const struct ext_include_script_info **included_r)
+{
+	const struct sieve_extension *this_ext = cmd->ext;
+	struct ext_include_context *ext_ctx =
+		(struct ext_include_context *)this_ext->context;
+	int result = 1;
+	struct sieve_ast *ast;
+	struct sieve_binary *sbin = cgenv->sbin;
+	struct sieve_generator *gentr = cgenv->gentr;
+	struct ext_include_binary_context *binctx;
+	struct sieve_generator *subgentr;
+	struct ext_include_generator_context *ctx =
+		ext_include_get_generator_context(this_ext, gentr);
+	struct ext_include_generator_context *pctx;
+	struct sieve_error_handler *ehandler = sieve_generator_error_handler(gentr);
+	struct ext_include_script_info *included;
+
+	*included_r = NULL;
+
+	/* Just to be sure: do not include more scripts when errors have occured
+	 * already.
+	 */
+	if ( sieve_get_errors(ehandler) > 0 )
+		return -1;
+
+	/* Limit nesting level */
+	if ( ctx->nesting_depth >= ext_ctx->max_nesting_depth ) {
+		sieve_command_generate_error
+			(gentr, cmd, "cannot nest includes deeper than %d levels",
+				ext_ctx->max_nesting_depth);
+		return -1;
+	}
+
+	/* Check for circular include */
+	if ( (flags & EXT_INCLUDE_FLAG_ONCE) == 0 ) {
+		pctx = ctx;
+		while ( pctx != NULL ) {
+			if ( sieve_script_equals(pctx->script, script) ) {
+				/* Just drop circular include when uploading inactive script;
+				 * not an error
+				 */
+				if ( (cgenv->flags & SIEVE_COMPILE_FLAG_UPLOADED) != 0 &&
+					(cgenv->flags & SIEVE_COMPILE_FLAG_ACTIVATED) == 0 ) {
+					sieve_command_generate_warning
+						(gentr, cmd, "circular include (ignored during upload)");
+					return 0;
+				}
+
+				sieve_command_generate_error(gentr, cmd, "circular include");
+				return -1;
+			}
+
+			pctx = pctx->parent;
+		}
+	}
+
+	/* Get binary context */
+	binctx = ext_include_binary_init(this_ext, sbin, cgenv->ast);
+
+	/* Is the script already compiled into the current binary? */
+	included = ext_include_binary_script_get_include_info(binctx, script);
+	if ( included != NULL ) {
+		/* Yes, only update flags */
+		if ( (flags & EXT_INCLUDE_FLAG_OPTIONAL) == 0 )
+			included->flags &= ~EXT_INCLUDE_FLAG_OPTIONAL;
+		if ( (flags & EXT_INCLUDE_FLAG_ONCE) == 0 )
+			included->flags &= ~EXT_INCLUDE_FLAG_ONCE;
+	} else 	{
+		const char *script_name = sieve_script_name(script);
+		enum sieve_compile_flags cpflags = cgenv->flags;
+
+		/* No, include new script */
+
+		/* Check whether include limit is exceeded */
+		if ( ext_include_binary_script_get_count(binctx) >=
+			ext_ctx->max_includes ) {
+	 		sieve_command_generate_error(gentr, cmd,
+	 			"failed to include script '%s': no more than %u includes allowed",
+				str_sanitize(script_name, 80), ext_ctx->max_includes);
+	 		return -1;
+		}
+
+		/* Allocate a new block in the binary and mark the script as included.
+		 */
+		if ( !sieve_script_is_open(script) ) {
+			/* Just making an empty entry to mark a missing script */
+			i_assert((flags & EXT_INCLUDE_FLAG_MISSING_AT_UPLOAD) != 0 ||
+				(flags & EXT_INCLUDE_FLAG_OPTIONAL) != 0);
+			included = ext_include_binary_script_include
+				(binctx, location, flags, script, NULL);
+			result = 0;
+
+		}	else {
+			struct sieve_binary_block *inc_block = sieve_binary_block_create(sbin);
+
+			/* Real include */
+			included = ext_include_binary_script_include
+				(binctx, location, flags, script, inc_block);
+
+			/* Parse */
+			if ( (ast = sieve_parse(script, ehandler, NULL)) == NULL ) {
+		 		sieve_command_generate_error(gentr, cmd,
+		 			"failed to parse included script '%s'", str_sanitize(script_name, 80));
+		 		return -1;
+			}
+
+			/* Included scripts inherit global variable scope */
+			(void)ext_include_create_ast_context(this_ext, ast, cmd->ast_node->ast);
+
+			if ( location == EXT_INCLUDE_LOCATION_GLOBAL )
+				cpflags &= ~SIEVE_EXECUTE_FLAG_NOGLOBAL;
+			else
+				cpflags |= SIEVE_EXECUTE_FLAG_NOGLOBAL;
+
+			/* Validate */
+			if ( !sieve_validate(ast, ehandler, cpflags, NULL) ) {
+				sieve_command_generate_error(gentr, cmd,
+					"failed to validate included script '%s'",
+					str_sanitize(script_name, 80));
+		 		sieve_ast_unref(&ast);
+		 		return -1;
+		 	}
+
+			/* Generate
+			 *
+			 * FIXME: It might not be a good idea to recurse code generation for
+			 * included scripts.
+			 */
+		 	subgentr = sieve_generator_create(ast, ehandler, cpflags);
+			ext_include_initialize_generator_context(cmd->ext, subgentr, ctx, script);
+	
+			if ( sieve_generator_run(subgentr, &inc_block) == NULL ) {
+				sieve_command_generate_error(gentr, cmd,
+					"failed to generate code for included script '%s'",
+					str_sanitize(script_name, 80));
+		 		result = -1;
+			}
+
+			sieve_generator_free(&subgentr);
+
+			/* Cleanup */
+			sieve_ast_unref(&ast);
+		}
+	}
+	
+	if ( result > 0 ) *included_r = included;
+
+	return result;
+}
+
+/*
+ * Executing an included script during interpretation
+ */
+
+static bool ext_include_runtime_check_circular
+(struct ext_include_interpreter_context *ctx,
+	const struct ext_include_script_info *include)
+{
+	struct ext_include_interpreter_context *pctx;
+
+	pctx = ctx;
+	while ( pctx != NULL ) {
+
+		if ( sieve_script_equals(include->script, pctx->script) )
+			return TRUE;
+
+		pctx = pctx->parent;
+	}
+
+	return FALSE;
+}
+
+static bool ext_include_runtime_include_mark
+(struct ext_include_interpreter_context *ctx,
+	const struct ext_include_script_info *include, bool once)
+{
+	struct sieve_script *const *includes;
+	unsigned int count, i;
+
+	includes = array_get(&ctx->global->included_scripts, &count);
+	for ( i = 0; i < count; i++ )	{
+
+		if ( sieve_script_equals(include->script, includes[i]) )
+			return ( !once );
+	}
+
+	array_append(&ctx->global->included_scripts, &include->script, 1);
+
+	return TRUE;
+}
+
+int ext_include_execute_include
+(const struct sieve_runtime_env *renv, unsigned int include_id,
+	enum ext_include_flags flags)
+{
+	const struct sieve_extension *this_ext = renv->oprtn->ext;
+	int result = SIEVE_EXEC_OK;
+	struct ext_include_interpreter_context *ctx;
+	const struct ext_include_script_info *included;
+	struct ext_include_binary_context *binctx =
+		ext_include_binary_get_context(this_ext, renv->sbin);
+	bool once = ( (flags & EXT_INCLUDE_FLAG_ONCE) != 0 );
+	unsigned int block_id;
+
+	/* Check for invalid include id (== corrupt binary) */
+	included = ext_include_binary_script_get_included(binctx, include_id);
+	if ( included == NULL ) {
+		sieve_runtime_trace_error(renv, "include: include id %d is invalid",
+			include_id);
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	ctx = ext_include_get_interpreter_context(this_ext, renv->interp);
+
+	block_id = sieve_binary_block_get_id(included->block);
+
+	/* If :once modifier is specified, check for duplicate include */
+	if ( ext_include_runtime_include_mark(ctx, included, once) ) {
+		sieve_runtime_trace(renv, SIEVE_TRLVL_NONE,
+			"include: start script '%s' [inc id: %d, block: %d]",
+			sieve_script_name(included->script), include_id, block_id);
+	} else {
+		/* skip */
+		sieve_runtime_trace(renv, SIEVE_TRLVL_NONE,
+			"include: skipped include for script '%s' [inc id: %d, block: %d]; "
+			"already run once",
+			sieve_script_name(included->script), include_id, block_id);
+		return result;
+	}
+
+	/* Check circular include during interpretation as well.
+	 * Let's not trust binaries.
+	 */
+	if ( ext_include_runtime_check_circular(ctx, included) ) {
+		sieve_runtime_trace_error(renv,
+			"include: circular include of script '%s' [inc id: %d, block: %d]",
+			sieve_script_name(included->script), include_id, block_id);
+
+		/* Situation has no valid way to emerge at runtime */
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	if ( ctx->parent == NULL ) {
+		struct ext_include_interpreter_context *curctx = NULL;
+		struct sieve_error_handler *ehandler = renv->ehandler;
+		struct sieve_interpreter *subinterp;
+		bool interrupted = FALSE;
+
+		/* We are the top-level interpreter instance */
+
+		if ( result == SIEVE_EXEC_OK ) {
+			enum sieve_execute_flags exflags = renv->flags;
+
+			if ( included->location != EXT_INCLUDE_LOCATION_GLOBAL )
+				exflags |= SIEVE_EXECUTE_FLAG_NOGLOBAL;
+			else
+				exflags &= ~SIEVE_EXECUTE_FLAG_NOGLOBAL;
+
+			/* Create interpreter for top-level included script
+			 * (first sub-interpreter)
+			 */
+			subinterp = sieve_interpreter_create_for_block
+				(included->block, included->script, renv->interp,
+					renv->msgdata, renv->scriptenv, ehandler, exflags);
+
+			if ( subinterp != NULL ) {
+				curctx = ext_include_interpreter_context_init_child
+					(this_ext, subinterp, ctx, included->script, included);
+
+				/* Activate and start the top-level included script */
+				result = sieve_interpreter_start
+					(subinterp, renv->result, &interrupted);
+			} else
+				result = SIEVE_EXEC_BIN_CORRUPT;
+		}
+
+		/* Included scripts can have includes of their own. This is not implemented
+		 * recursively. Rather, the sub-interpreter interrupts and defers the
+		 * include to the top-level interpreter, which is here.
+		 */
+		if ( result == SIEVE_EXEC_OK && interrupted && !curctx->returned ) {
+			while ( result == SIEVE_EXEC_OK ) {
+
+				if ( ( (interrupted && curctx->returned) || (!interrupted) ) &&
+					curctx->parent != NULL ) {
+					const struct ext_include_script_info *ended_script =
+						curctx->script_info;
+
+					/* Sub-interpreter ended or executed return */
+
+					/* Ascend interpreter stack */
+					curctx = curctx->parent;
+					sieve_interpreter_free(&subinterp);
+
+					sieve_runtime_trace(renv, SIEVE_TRLVL_NONE,
+						"include: script '%s' ended [inc id: %d, block: %d]",
+						sieve_script_name(ended_script->script), ended_script->id,
+						sieve_binary_block_get_id(ended_script->block));
+
+					/* This is the top-most sub-interpreter, bail out */
+					if ( curctx->parent == NULL ) break;
+
+					subinterp = curctx->interp;
+
+					/* Continue parent */
+					curctx->include = NULL;
+					curctx->returned = FALSE;
+
+					result = sieve_interpreter_continue(subinterp, &interrupted);
+				} else {
+					if ( curctx->include != NULL ) {
+						/* Sub-include requested */
+
+						if ( result == SIEVE_EXEC_OK ) {
+							enum sieve_execute_flags exflags = renv->flags;
+
+							if ( curctx->include->location != EXT_INCLUDE_LOCATION_GLOBAL )
+								exflags |= SIEVE_EXECUTE_FLAG_NOGLOBAL;
+							else
+								exflags &= ~SIEVE_EXECUTE_FLAG_NOGLOBAL;
+
+							/* Create sub-interpreter */
+							subinterp = sieve_interpreter_create_for_block
+								(curctx->include->block, curctx->include->script,
+									curctx->interp, renv->msgdata,
+									renv->scriptenv, ehandler, exflags);
+
+							if ( subinterp != NULL ) {
+								curctx = ext_include_interpreter_context_init_child
+									(this_ext, subinterp, curctx, curctx->include->script,
+										curctx->include);
+
+								/* Start the sub-include's interpreter */
+								curctx->include = NULL;
+								curctx->returned = FALSE;
+								result = sieve_interpreter_start
+									(subinterp, renv->result, &interrupted);
+							} else
+								result = SIEVE_EXEC_BIN_CORRUPT;
+						}
+					} else {
+						/* Sub-interpreter was interrupted outside this extension, probably
+						 * stop command was executed. Generate an interrupt ourselves,
+						 * ending all script execution.
+						 */
+						sieve_interpreter_interrupt(renv->interp);
+						break;
+					}
+				}
+			}
+		}
+
+		/* Free any sub-interpreters that might still be active */
+		while ( curctx != NULL && curctx->parent != NULL ) {
+			struct ext_include_interpreter_context *nextctx	= curctx->parent;
+			struct sieve_interpreter *killed_interp = curctx->interp;
+			const struct ext_include_script_info *ended_script =
+				curctx->script_info;
+
+			/* This kills curctx too */
+			sieve_interpreter_free(&killed_interp);
+
+			sieve_runtime_trace(renv, SIEVE_TRLVL_NONE,
+				"include: script '%s' ended [id: %d, block: %d]",
+				sieve_script_name(ended_script->script),
+				ended_script->id, sieve_binary_block_get_id(ended_script->block));
+
+			/* Luckily we recorded the parent earlier */
+			curctx = nextctx;
+		}
+
+	} else {
+		/* We are an included script already, defer inclusion to main interpreter */
+
+		ctx->include = included;
+		sieve_interpreter_interrupt(renv->interp);
+	}
+
+	return result;
+}
+
+void ext_include_execute_return
+(const struct sieve_runtime_env *renv)
+{
+	const struct sieve_extension *this_ext = renv->oprtn->ext;
+	struct ext_include_interpreter_context *ctx =
+		ext_include_get_interpreter_context(this_ext, renv->interp);
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS,
+		"return: exiting included script");
+
+	ctx->returned = TRUE;
+	sieve_interpreter_interrupt(renv->interp);
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/include/ext-include-common.h
@@ -0,0 +1,169 @@
+#ifndef EXT_INCLUDE_COMMON_H
+#define EXT_INCLUDE_COMMON_H
+
+#include "lib.h"
+#include "hash.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+
+/*
+ * Forward declarations
+ */
+
+struct ext_include_script_info;
+struct ext_include_binary_context;
+
+/*
+ * Types
+ */
+
+enum ext_include_flags { // stored in one byte
+	EXT_INCLUDE_FLAG_ONCE = 0x01,
+	EXT_INCLUDE_FLAG_OPTIONAL = 0x02,
+	EXT_INCLUDE_FLAG_MISSING_AT_UPLOAD = 0x04
+};
+
+enum ext_include_script_location {
+	EXT_INCLUDE_LOCATION_PERSONAL,
+	EXT_INCLUDE_LOCATION_GLOBAL,
+	EXT_INCLUDE_LOCATION_INVALID
+};
+
+static inline const char *ext_include_script_location_name
+(enum ext_include_script_location location)
+{
+	switch ( location ) {
+	case EXT_INCLUDE_LOCATION_PERSONAL:
+		return "personal";
+
+	case EXT_INCLUDE_LOCATION_GLOBAL:
+		return "global";
+
+	default:
+		break;
+	}
+
+	return "[INVALID LOCATION]";
+}
+
+
+/*
+ * Extension
+ */
+
+extern const struct sieve_extension_def include_extension;
+extern const struct sieve_binary_extension include_binary_ext;
+
+bool ext_include_load
+	(const struct sieve_extension *ext, void **context);
+void ext_include_unload
+	(const struct sieve_extension *ext);
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def cmd_include;
+extern const struct sieve_command_def cmd_return;
+extern const struct sieve_command_def cmd_global;
+
+/* DEPRICATED */
+extern const struct sieve_command_def cmd_import;
+extern const struct sieve_command_def cmd_export;
+
+/*
+ * Operations
+ */
+
+enum ext_include_opcode {
+	EXT_INCLUDE_OPERATION_INCLUDE,
+	EXT_INCLUDE_OPERATION_RETURN,
+	EXT_INCLUDE_OPERATION_GLOBAL
+};
+
+extern const struct sieve_operation_def include_operation;
+extern const struct sieve_operation_def return_operation;
+extern const struct sieve_operation_def global_operation;
+
+/*
+ * Script access
+ */
+
+struct sieve_storage *ext_include_get_script_storage
+	(const struct sieve_extension *ext,
+		enum ext_include_script_location location,
+		const char *script_name, enum sieve_error *error_r);
+/*
+ * Context
+ */
+
+/* Extension context */
+
+struct ext_include_context {
+	/* Extension dependencies */
+	const struct sieve_extension *var_ext;
+
+	/* Configuration */
+ 	char *global_location;
+
+	struct sieve_storage *global_storage;
+	struct sieve_storage *personal_storage;
+
+	unsigned int max_nesting_depth;
+	unsigned int max_includes;
+};
+
+static inline struct ext_include_context *ext_include_get_context
+(const struct sieve_extension *ext)
+{
+	return (struct ext_include_context *) ext->context;
+}
+
+/* AST Context */
+
+struct ext_include_ast_context {
+  struct sieve_variable_scope *global_vars;
+
+  ARRAY(struct sieve_script *) included_scripts;
+};
+
+struct ext_include_ast_context *ext_include_create_ast_context
+	(const struct sieve_extension *this_ext, struct sieve_ast *ast,
+		struct sieve_ast *parent);
+struct ext_include_ast_context *ext_include_get_ast_context
+	(const struct sieve_extension *this_ext, struct sieve_ast *ast);
+
+void ext_include_ast_link_included_script
+	(const struct sieve_extension *this_ext, struct sieve_ast *ast,
+		struct sieve_script *script);
+
+bool ext_include_validator_have_variables
+	(const struct sieve_extension *this_ext, struct sieve_validator *valdtr);
+
+/* Generator context */
+
+void ext_include_register_generator_context
+	(const struct sieve_extension *this_ext,
+		const struct sieve_codegen_env *cgenv);
+
+int ext_include_generate_include
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd,
+		enum ext_include_script_location location,
+		enum ext_include_flags flags, struct sieve_script *script,
+		const struct ext_include_script_info **included_r);
+
+/* Interpreter context */
+
+void ext_include_interpreter_context_init
+	(const struct sieve_extension *this_ext, struct sieve_interpreter *interp);
+
+int ext_include_execute_include
+	(const struct sieve_runtime_env *renv, unsigned int block_id,
+		enum ext_include_flags flags);
+void ext_include_execute_return(const struct sieve_runtime_env *renv);
+
+struct sieve_variable_storage *ext_include_interpreter_get_global_variables
+	(const struct sieve_extension *this_ext, struct sieve_interpreter *interp);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/include/ext-include-limits.h
@@ -0,0 +1,9 @@
+#ifndef EXT_INCLUDE_LIMITS_H
+#define EXT_INCLUDE_LIMITS_H
+
+#include "sieve-common.h"
+
+#define EXT_INCLUDE_DEFAULT_MAX_NESTING_DEPTH 10
+#define EXT_INCLUDE_DEFAULT_MAX_INCLUDES      255
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/include/ext-include-variables.c
@@ -0,0 +1,254 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-script.h"
+#include "sieve-ast.h"
+#include "sieve-binary.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "sieve-ext-variables.h"
+
+#include "ext-include-common.h"
+#include "ext-include-binary.h"
+#include "ext-include-variables.h"
+
+/*
+ * Variable import-export
+ */
+
+struct sieve_variable *ext_include_variable_import_global
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+	const char *variable)
+{
+	const struct sieve_extension *this_ext = cmd->ext;
+	struct sieve_ast *ast = cmd->ast_node->ast;
+	struct ext_include_ast_context *ctx =
+		ext_include_get_ast_context(this_ext, ast);
+	struct ext_include_context *ectx = ext_include_get_context(this_ext);
+	struct sieve_variable_scope *local_scope;
+	struct sieve_variable_scope *global_scope = ctx->global_vars;
+	struct sieve_variable *global_var = NULL, *local_var;
+
+	/* Sanity safeguard */
+	i_assert ( ctx->global_vars != NULL );
+
+	if ( !sieve_variable_identifier_is_valid(variable) ) {
+		sieve_command_validate_error(valdtr, cmd,
+			"invalid variable identifier '%s'", str_sanitize(variable,80));
+		return NULL;
+	}
+
+	/* Get/Declare the variable in the global scope */
+	global_var = sieve_variable_scope_declare(global_scope, variable);
+
+	/* Check whether scope is over its size limit */
+	if ( global_var == NULL ) {
+		sieve_command_validate_error(valdtr, cmd,
+			"declaration of new global variable '%s' exceeds the limit "
+			"(max variables: %u)", variable,
+			sieve_variables_get_max_scope_size(ectx->var_ext));
+		return NULL;
+	}
+
+	/* Import the global variable into the local script scope */
+	local_scope = sieve_ext_variables_get_local_scope(ectx->var_ext, valdtr);
+
+	local_var = sieve_variable_scope_get_variable(local_scope, variable);
+	if ( local_var != NULL && local_var->ext != this_ext ) {
+		/* FIXME: indicate location of conflicting set statement */
+		sieve_command_validate_error(valdtr, cmd,
+			"declaration of new global variable '%s' conflicts with earlier local "
+			"use", variable);
+		return NULL;
+	}
+
+	return sieve_variable_scope_import(local_scope, global_var);
+}
+
+/*
+ * Binary symbol table
+ */
+
+bool ext_include_variables_save
+(struct sieve_binary_block *sblock,
+	struct sieve_variable_scope_binary *global_vars,
+	enum sieve_error *error_r ATTR_UNUSED)
+{
+	struct sieve_variable_scope *global_scope =
+		sieve_variable_scope_binary_get(global_vars);
+	unsigned int count = sieve_variable_scope_size(global_scope);
+	sieve_size_t jump;
+
+	sieve_binary_emit_unsigned(sblock, count);
+
+	jump = sieve_binary_emit_offset(sblock, 0);
+
+	if ( count > 0 ) {
+		unsigned int size, i;
+		struct sieve_variable *const *vars =
+			sieve_variable_scope_get_variables(global_scope, &size);
+
+		for ( i = 0; i < size; i++ ) {
+			sieve_binary_emit_cstring(sblock, vars[i]->identifier);
+		}
+	}
+
+	sieve_binary_resolve_offset(sblock, jump);
+
+	return TRUE;
+}
+
+bool ext_include_variables_load
+(const struct sieve_extension *this_ext, struct sieve_binary_block *sblock,
+	sieve_size_t *offset, struct sieve_variable_scope_binary **global_vars_r)
+{
+	struct ext_include_context *ectx =
+		ext_include_get_context(this_ext);
+
+	/* Sanity assert */
+	i_assert( *global_vars_r == NULL );
+
+	*global_vars_r = sieve_variable_scope_binary_read
+		(this_ext->svinst, ectx->var_ext, this_ext, sblock, offset);
+
+	return ( *global_vars_r != NULL );
+}
+
+bool ext_include_variables_dump
+(struct sieve_dumptime_env *denv,
+	struct sieve_variable_scope_binary *global_vars)
+{
+	struct sieve_variable_scope *global_scope =
+		sieve_variable_scope_binary_get(global_vars);
+	unsigned int size;
+	struct sieve_variable *const *vars;
+
+	i_assert(global_scope != NULL);
+
+	vars = sieve_variable_scope_get_variables(global_scope, &size);
+
+	if ( size > 0 ) {
+		unsigned int i;
+
+		sieve_binary_dump_sectionf(denv, "Global variables");
+
+		for ( i = 0; i < size; i++ ) {
+			sieve_binary_dumpf(denv, "%3d: '%s' \n", i, vars[i]->identifier);
+		}
+	}
+
+	return TRUE;
+}
+
+/*
+ * Global variables namespace
+ */
+
+bool vnspc_global_variables_validate
+	(struct sieve_validator *valdtr, const struct sieve_variables_namespace *nspc,
+		struct sieve_ast_argument *arg, struct sieve_command *cmd,
+		ARRAY_TYPE(sieve_variable_name) *var_name, void **var_data,
+		bool assignment);
+bool vnspc_global_variables_generate
+	(const struct sieve_codegen_env *cgenv,
+		const struct sieve_variables_namespace *nspc,
+		struct sieve_ast_argument *arg, struct sieve_command *cmd, void *var_data);
+
+static const struct sieve_variables_namespace_def
+global_variables_namespace = {
+	SIEVE_OBJECT("global", NULL, 0),
+	.validate = vnspc_global_variables_validate,
+	.generate = vnspc_global_variables_generate
+};
+
+bool vnspc_global_variables_validate
+(struct sieve_validator *valdtr,
+	const struct sieve_variables_namespace *nspc, struct sieve_ast_argument *arg,
+	struct sieve_command *cmd ATTR_UNUSED,
+	ARRAY_TYPE(sieve_variable_name) *var_name, void **var_data,
+	bool assignment ATTR_UNUSED)
+{
+	const struct sieve_extension *this_ext = SIEVE_OBJECT_EXTENSION(nspc);
+	struct sieve_ast *ast = arg->ast;
+	struct ext_include_context *ectx =
+		ext_include_get_context(this_ext);
+	struct ext_include_ast_context *ctx =
+		ext_include_get_ast_context(this_ext, ast);
+	struct sieve_variable *var = NULL;
+	const struct sieve_variable_name *name_element;
+	const char *variable;
+
+	/* Sanity safeguard */
+	i_assert ( ctx->global_vars != NULL );
+
+	/* Check variable name */
+
+	if ( array_count(var_name) != 2 ) {
+		sieve_argument_validate_error(valdtr, arg,
+			"invalid variable name within global namespace: "
+			"encountered sub-namespace");
+		return FALSE;
+	}
+
+	name_element = array_idx(var_name, 1);
+	if ( name_element->num_variable >= 0 ) {
+		sieve_argument_validate_error(valdtr, arg,
+			"invalid variable name within global namespace: "
+			"encountered numeric variable name");
+		return FALSE;
+	}
+
+	variable = str_c(name_element->identifier);
+
+	/* Get/Declare the variable in the global scope */
+
+	var = sieve_variable_scope_declare(ctx->global_vars, variable);
+
+	if ( var == NULL ) {
+		sieve_argument_validate_error(valdtr, arg,
+			"(implicit) declaration of new global variable '%s' exceeds the limit "
+			"(max variables: %u)", variable,
+			sieve_variables_get_max_scope_size(ectx->var_ext));
+		return FALSE;
+	}
+
+	*var_data = (void *) var;
+
+	return TRUE;
+}
+
+bool vnspc_global_variables_generate
+(const struct sieve_codegen_env *cgenv,
+	const struct sieve_variables_namespace *nspc,
+	struct sieve_ast_argument *arg ATTR_UNUSED,
+	struct sieve_command *cmd ATTR_UNUSED, void *var_data)
+{
+	const struct sieve_extension *this_ext = SIEVE_OBJECT_EXTENSION(nspc);
+	struct ext_include_context *ectx = ext_include_get_context(this_ext);
+	struct sieve_variable *var = (struct sieve_variable *) var_data;
+
+	sieve_variables_opr_variable_emit(cgenv->sblock, ectx->var_ext, var);
+
+	return TRUE;
+}
+
+void ext_include_variables_global_namespace_init
+(const struct sieve_extension *this_ext, struct sieve_validator *valdtr)
+{
+	struct ext_include_context *ectx = ext_include_get_context(this_ext);
+
+	sieve_variables_namespace_register
+		(ectx->var_ext, valdtr, this_ext, &global_variables_namespace);
+}
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/include/ext-include-variables.h
@@ -0,0 +1,41 @@
+#ifndef EXT_INCLUDE_VARIABLES_H
+#define EXT_INCLUDE_VARIABLES_H
+
+#include "sieve-common.h"
+
+#include "sieve-ext-variables.h"
+
+#include "ext-include-common.h"
+
+/*
+ * Variable import-export
+ */
+
+struct sieve_variable *ext_include_variable_import_global
+	(struct sieve_validator *valdtr, struct sieve_command *cmd,
+		const char *variable);
+
+/*
+ * Binary symbol table
+ */
+
+bool ext_include_variables_save
+	(struct sieve_binary_block *sblock,
+		struct sieve_variable_scope_binary *global_vars,
+		enum sieve_error *error_r);
+bool ext_include_variables_load
+	(const struct sieve_extension *this_ext, struct sieve_binary_block *sblock,
+		sieve_size_t *offset, struct sieve_variable_scope_binary **global_vars_r);
+bool ext_include_variables_dump
+	(struct sieve_dumptime_env *denv,
+		struct sieve_variable_scope_binary *global_vars);
+
+/*
+ * Validation
+ */
+
+void ext_include_variables_global_namespace_init
+	(const struct sieve_extension *this_ext, struct sieve_validator *valdtr);
+
+#endif
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/include/ext-include.c
@@ -0,0 +1,121 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension include
+ * -----------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 6609
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+/* FIXME: Current include implementation does not allow for parts of the script
+ * to be located in external binaries; all included scripts are recompiled and
+ * the resulting byte code is imported into the main binary in separate blocks.
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+
+#include "sieve-extensions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+
+#include "sieve-ext-variables.h"
+
+#include "ext-include-common.h"
+#include "ext-include-binary.h"
+#include "ext-include-variables.h"
+
+/*
+ * Operations
+ */
+
+static const struct sieve_operation_def *ext_include_operations[] = {
+	&include_operation,
+	&return_operation,
+	&global_operation
+};
+
+/*
+ * Extension
+ */
+
+/* Forward declaration */
+
+static bool ext_include_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *validator);
+static bool ext_include_generator_load
+	(const struct sieve_extension *ext, const struct sieve_codegen_env *cgenv);
+static bool ext_include_interpreter_load
+	(const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+		sieve_size_t *address);
+static bool ext_include_binary_load
+	(const struct sieve_extension *ext, struct sieve_binary *binary);
+
+/* Extension objects */
+
+const struct sieve_extension_def include_extension = {
+	.name = "include",
+	.version = 1,
+
+	.load = ext_include_load,
+	.unload = ext_include_unload,
+	.validator_load = ext_include_validator_load,
+	.generator_load = ext_include_generator_load,
+	.interpreter_load = ext_include_interpreter_load,
+	.binary_load = ext_include_binary_load,
+	.binary_dump = ext_include_binary_dump,
+	.code_dump = ext_include_code_dump,
+
+	SIEVE_EXT_DEFINE_OPERATIONS(ext_include_operations)
+};
+
+static bool ext_include_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	/* Register new commands */
+	sieve_validator_register_command(valdtr, ext, &cmd_include);
+	sieve_validator_register_command(valdtr, ext, &cmd_return);
+	sieve_validator_register_command(valdtr, ext, &cmd_global);
+
+	/* DEPRICATED */
+	sieve_validator_register_command(valdtr, ext, &cmd_import);
+	sieve_validator_register_command(valdtr, ext, &cmd_export);
+
+	/* Initialize global variables namespace */
+	ext_include_variables_global_namespace_init(ext, valdtr);
+
+	return TRUE;
+}
+
+static bool ext_include_generator_load
+(const struct sieve_extension *ext, const struct sieve_codegen_env *cgenv)
+{
+	ext_include_register_generator_context(ext, cgenv);
+
+	return TRUE;
+}
+
+static bool ext_include_interpreter_load
+(const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+	sieve_size_t *address ATTR_UNUSED)
+{
+	ext_include_interpreter_context_init(ext, renv->interp);
+
+	return TRUE;
+}
+
+static bool ext_include_binary_load
+(const struct sieve_extension *ext, struct sieve_binary *sbin)
+{
+	(void)ext_include_binary_get_context(ext, sbin);
+
+	return TRUE;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/index/Makefile.am
@@ -0,0 +1,13 @@
+noinst_LTLIBRARIES = libsieve_ext_index.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../.. \
+	$(LIBDOVECOT_INCLUDE)
+
+libsieve_ext_index_la_SOURCES = \
+	ext-index-common.c \
+	ext-index.c \
+	tag-index.c
+
+noinst_HEADERS = \
+	ext-index-common.h
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/index/ext-index-common.c
@@ -0,0 +1,15 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "utc-offset.h"
+#include "str.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-interpreter.h"
+#include "sieve-message.h"
+
+#include "ext-index-common.h"
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/index/ext-index-common.h
@@ -0,0 +1,29 @@
+#ifndef EXT_INDEX_COMMON_H
+#define EXT_INDEX_COMMON_H
+
+#include "sieve-common.h"
+
+#include <time.h>
+
+#define SIEVE_EXT_INDEX_HDR_OVERRIDE_SEQUENCE 100
+
+/*
+ * Tagged arguments
+ */
+
+extern const struct sieve_argument_def index_tag;
+extern const struct sieve_argument_def last_tag;
+
+/*
+ * Operands
+ */
+
+extern const struct sieve_operand_def index_operand;
+
+/*
+ * Extension
+ */
+
+extern const struct sieve_extension_def index_extension;
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/index/ext-index.c
@@ -0,0 +1,69 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension index
+ * ------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5260
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-message.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-index-common.h"
+
+/*
+ * Extension
+ */
+
+static bool ext_index_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *validator);
+
+const struct sieve_extension_def index_extension = {
+	.name = "index",
+	.validator_load = ext_index_validator_load,
+	SIEVE_EXT_DEFINE_OPERAND(index_operand)
+};
+
+static bool ext_index_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	/* Register :index and :last tags with header, address and date test commands
+	 * and we don't care whether these command are registered or even whether
+	 * these will be registered at all. The validator handles either situation
+	 * gracefully.
+	 */
+	sieve_validator_register_external_tag
+		(valdtr, "header", ext, &index_tag, SIEVE_OPT_MESSAGE_OVERRIDE);
+	sieve_validator_register_external_tag
+		(valdtr, "header", ext, &last_tag, 0);
+
+	sieve_validator_register_external_tag
+		(valdtr, "address", ext, &index_tag, SIEVE_OPT_MESSAGE_OVERRIDE);
+	sieve_validator_register_external_tag
+		(valdtr, "address", ext, &last_tag, 0);
+
+	sieve_validator_register_external_tag
+		(valdtr, "date", ext, &index_tag, SIEVE_OPT_MESSAGE_OVERRIDE);
+	sieve_validator_register_external_tag
+		(valdtr, "date", ext, &last_tag, 0);
+
+	return TRUE;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/index/tag-index.c
@@ -0,0 +1,267 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "mail-storage.h"
+#include "mail-namespace.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-binary.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+#include "sieve-result.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+
+#include "ext-index-common.h"
+
+/*
+ * Tagged argument
+ */
+
+static bool tag_index_validate
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+static bool tag_index_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+    struct sieve_command *context);
+
+const struct sieve_argument_def index_tag = {
+	.identifier = "index",
+	.validate = tag_index_validate,
+	.generate = tag_index_generate
+};
+
+static bool tag_last_validate
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+
+const struct sieve_argument_def last_tag = {
+	.identifier = "last",
+	.validate = tag_last_validate,
+};
+
+/*
+ * Header override
+ */
+
+static bool svmo_index_dump_context
+	(const struct sieve_message_override *svmo,
+		const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int svmo_index_read_context
+	(const struct sieve_message_override *svmo,
+		const struct sieve_runtime_env *renv, sieve_size_t *address,
+		void **ho_context);
+static int svmo_index_header_override
+	(const struct sieve_message_override *svmo,
+		const struct sieve_runtime_env *renv,
+		bool mime_decode, struct sieve_stringlist **headers);
+
+const struct sieve_message_override_def index_header_override = {
+	SIEVE_OBJECT("index", &index_operand, 0),
+	.sequence = SIEVE_EXT_INDEX_HDR_OVERRIDE_SEQUENCE,
+	.dump_context = svmo_index_dump_context,
+	.read_context = svmo_index_read_context,
+	.header_override = svmo_index_header_override
+};
+
+/*
+ * Operand
+ */
+
+static const struct sieve_extension_objects ext_header_overrides =
+	SIEVE_EXT_DEFINE_MESSAGE_OVERRIDE(index_header_override);
+
+const struct sieve_operand_def index_operand = {
+	.name = "index operand",
+	.ext_def = &index_extension,
+	.class = &sieve_message_override_operand_class,
+	.interface = &ext_header_overrides
+};
+
+/*
+ * Tag data
+ */
+
+struct tag_index_data {
+	sieve_number_t fieldno;
+	bool last:1;
+};
+
+/*
+ * Tag validation
+ */
+
+static bool tag_index_validate
+(struct sieve_validator *valdtr ATTR_UNUSED,
+	struct sieve_ast_argument **arg, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *tag = *arg;
+	struct tag_index_data *data;
+
+	/* Skip the tag itself */
+	*arg = sieve_ast_argument_next(*arg);
+
+	/* Check syntax:
+	 *   ":index" <fieldno: number>
+	 */
+	if ( !sieve_validate_tag_parameter
+		(valdtr, cmd, tag, *arg, NULL, 0, SAAT_NUMBER, FALSE) ) {
+		return FALSE;
+	}
+
+	if (tag->argument->data == NULL) {
+		data = p_new(sieve_command_pool(cmd), struct tag_index_data, 1);
+		tag->argument->data = (void *)data;
+	} else {
+		data = (struct tag_index_data *)tag->argument->data;
+	}
+
+	data->fieldno = sieve_ast_argument_number(*arg);
+
+	/* Detach parameter */
+	*arg = sieve_ast_arguments_detach(*arg,1);
+	return TRUE;
+}
+
+static bool tag_last_validate
+(struct sieve_validator *valdtr ATTR_UNUSED,
+	struct sieve_ast_argument **arg, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *index_arg;
+	struct tag_index_data *data;
+
+	index_arg = sieve_command_find_argument(cmd, &index_tag);
+	if (index_arg == NULL) {
+		sieve_argument_validate_error(valdtr, *arg,
+			"the :last tag for the %s %s cannot be specified "
+			"without the :index tag",
+			sieve_command_identifier(cmd), sieve_command_type_name(cmd));
+		return FALSE;
+	}
+
+	/* Set :last flag */
+	if (index_arg->argument->data == NULL) {
+		data = p_new(sieve_command_pool(cmd), struct tag_index_data, 1);
+		index_arg->argument->data = (void*)data;
+	} else {
+		data = (struct tag_index_data *)index_arg->argument->data;
+	}
+	data->last = TRUE;
+
+	/* Detach */
+	*arg = sieve_ast_arguments_detach(*arg,1);
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool tag_index_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+	struct sieve_command *cmd ATTR_UNUSED)
+{
+	struct tag_index_data *data =
+		(struct tag_index_data *)arg->argument->data;
+
+	if ( sieve_ast_argument_type(arg) != SAAT_TAG )
+		return FALSE;
+
+	sieve_opr_message_override_emit
+		(cgenv->sblock, arg->argument->ext, &index_header_override);
+
+	(void)sieve_binary_emit_integer
+		(cgenv->sblock, data->fieldno);
+	(void)sieve_binary_emit_byte
+		(cgenv->sblock, ( data->last ? 1 : 0 ));
+
+	return TRUE;
+}
+
+/*
+ * Header override implementation
+ */
+
+/* Context data */
+
+struct svmo_index_context {
+	unsigned int fieldno;
+	bool last:1;
+};
+
+/* Context coding */
+
+static bool svmo_index_dump_context
+(const struct sieve_message_override *svmo ATTR_UNUSED,
+	const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_number_t fieldno = 0;
+	unsigned int last;
+
+	if ( !sieve_binary_read_integer(denv->sblock, address, &fieldno) )
+		return FALSE;
+
+	sieve_code_dumpf(denv, "fieldno: %llu",
+		(unsigned long long) fieldno);
+
+	if ( !sieve_binary_read_byte(denv->sblock, address, &last) )
+		return FALSE;
+
+	if (last > 0)
+		sieve_code_dumpf(denv, "last");
+	return TRUE;
+}
+
+static int svmo_index_read_context
+(const struct sieve_message_override *svmo ATTR_UNUSED,
+	const struct sieve_runtime_env *renv, sieve_size_t *address,
+	void **ho_context)
+{
+	pool_t pool = sieve_result_pool(renv->result);
+	struct svmo_index_context *ctx;
+	sieve_number_t fieldno;
+	unsigned int last = 0;
+
+	if ( !sieve_binary_read_integer(renv->sblock, address, &fieldno) ) {
+		sieve_runtime_trace_error(renv, "fieldno: invalid number");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	if ( !sieve_binary_read_byte(renv->sblock, address, &last) ) {
+		sieve_runtime_trace_error(renv, "last: invalid byte");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	ctx = p_new(pool, struct svmo_index_context, 1);
+	ctx->fieldno = fieldno;
+	ctx->last = (last == 0 ? FALSE : TRUE);
+
+	*ho_context = (void *) ctx;
+
+	return SIEVE_EXEC_OK;
+}
+
+/* Override */
+
+static int svmo_index_header_override
+(const struct sieve_message_override *svmo,
+	const struct sieve_runtime_env *renv,
+	bool mime_decode ATTR_UNUSED,
+	struct sieve_stringlist **headers)
+{
+	struct svmo_index_context *ctx =
+		(struct svmo_index_context *)svmo->context;
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
+		"header index override: only returning index %d%s",
+		ctx->fieldno, ( ctx->last ? " (from last)" : "" ));
+
+	*headers = sieve_index_stringlist_create(renv, *headers,
+		(int)ctx->fieldno * ( ctx->last ? -1 : 1 ));
+	return SIEVE_EXEC_OK;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/mailbox/Makefile.am
@@ -0,0 +1,26 @@
+noinst_LTLIBRARIES = libsieve_ext_mailbox.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../.. \
+	$(LIBDOVECOT_INCLUDE)
+
+tags = \
+	tag-mailbox-create.c
+
+tests = \
+	tst-mailboxexists.c
+
+libsieve_ext_mailbox_la_SOURCES = \
+	$(tags) \
+	$(tests) \
+	ext-mailbox.c
+
+public_headers = \
+	sieve-ext-mailbox.h
+
+headers = \
+	ext-mailbox-common.h
+
+pkginc_libdir=$(dovecot_pkgincludedir)/sieve
+pkginc_lib_HEADERS = $(public_headers)
+noinst_HEADERS = $(headers)
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/mailbox/ext-mailbox-common.h
@@ -0,0 +1,39 @@
+#ifndef EXT_MAILBOX_COMMON_H
+#define EXT_MAILBOX_COMMON_H
+
+#include "sieve-common.h"
+
+#include "sieve-ext-mailbox.h"
+
+/*
+ * Tagged arguments
+ */
+
+extern const struct sieve_argument_def mailbox_create_tag;
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def mailboxexists_test;
+
+/*
+ * Operands
+ */
+
+extern const struct sieve_operand_def mailbox_create_operand;
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def mailboxexists_operation;
+
+/*
+ * Extension
+ */
+
+extern const struct sieve_extension_def mailbox_extension;
+
+#endif
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/mailbox/ext-mailbox.c
@@ -0,0 +1,72 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension mailbox
+ * ------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5490
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-actions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+
+#include "ext-mailbox-common.h"
+
+/*
+ * Tag registration
+ */
+
+void sieve_ext_mailbox_register_create_tag
+(struct sieve_validator *valdtr, const struct sieve_extension *mailbox_ext,
+	const char *command)
+{
+	if ( sieve_validator_extension_loaded(valdtr, mailbox_ext) ) {
+		sieve_validator_register_external_tag(valdtr, command,
+			mailbox_ext, &mailbox_create_tag, SIEVE_OPT_SIDE_EFFECT);
+	}
+}
+
+
+/*
+ * Extension
+ */
+
+static bool ext_mailbox_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def mailbox_extension = {
+	.name = "mailbox",
+	.validator_load = ext_mailbox_validator_load,
+	SIEVE_EXT_DEFINE_OPERATION(mailboxexists_operation),
+	SIEVE_EXT_DEFINE_OPERAND(mailbox_create_operand)
+};
+
+static bool ext_mailbox_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	/* Register :create tag with fileinto command and we don't care whether this
+	 * command is registered or even whether it will be registered at all. The
+	 * validator handles either situation gracefully
+	 */
+	sieve_validator_register_external_tag
+		(valdtr, "fileinto", ext, &mailbox_create_tag, SIEVE_OPT_SIDE_EFFECT);
+
+	/* Register new test */
+	sieve_validator_register_command(valdtr, ext, &mailboxexists_test);
+
+	return TRUE;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/mailbox/sieve-ext-mailbox.h
@@ -0,0 +1,21 @@
+#ifndef SIEVE_EXT_MAILBOX_H
+#define SIEVE_EXT_MAILBOX_H
+
+/* sieve_ext_mailbox_get_extension():
+ *   Get the extension struct for the mailbox extension.
+ */
+static inline const struct sieve_extension *sieve_ext_mailbox_get_extension
+(struct sieve_instance *svinst)
+{
+	return sieve_extension_get_by_name(svinst, "mailbox");
+}
+
+/* sieve_ext_mailbox_register_create_tag():
+ *   Register the :create tagged argument for a command other than fileinto and
+ *   redirect.
+ */
+void sieve_ext_mailbox_register_create_tag
+	(struct sieve_validator *valdtr, const struct sieve_extension *mailbox_ext,
+		const char *command);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/mailbox/tag-mailbox-create.c
@@ -0,0 +1,172 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "mail-storage.h"
+#include "mail-namespace.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-actions.h"
+#include "sieve-code.h"
+#include "sieve-actions.h"
+#include "sieve-result.h"
+#include "sieve-generator.h"
+
+#include "ext-mailbox-common.h"
+
+/*
+ * Tagged argument
+ */
+
+static bool tag_mailbox_create_validate
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+static bool tag_mailbox_create_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+    struct sieve_command *context);
+
+const struct sieve_argument_def mailbox_create_tag = {
+	.identifier = "create",
+	.validate = tag_mailbox_create_validate,
+	.generate = tag_mailbox_create_generate
+};
+
+/*
+ * Side effect
+ */
+
+static void seff_mailbox_create_print
+	(const struct sieve_side_effect *seffect, const struct sieve_action *action,
+		const struct sieve_result_print_env *rpenv, bool *keep);
+static int seff_mailbox_create_pre_execute
+	(const struct sieve_side_effect *seffect, const struct sieve_action *action,
+		const struct sieve_action_exec_env *aenv, void **se_context,
+		void *tr_context);
+
+const struct sieve_side_effect_def mailbox_create_side_effect = {
+	SIEVE_OBJECT("create", &mailbox_create_operand, 0),
+	.to_action = &act_store,
+	.print = seff_mailbox_create_print,
+	.pre_execute = seff_mailbox_create_pre_execute
+};
+
+/*
+ * Operand
+ */
+
+static const struct sieve_extension_objects ext_side_effects =
+	SIEVE_EXT_DEFINE_SIDE_EFFECT(mailbox_create_side_effect);
+
+const struct sieve_operand_def mailbox_create_operand = {
+	.name = "create operand",
+	.ext_def = &mailbox_extension,
+	.class = &sieve_side_effect_operand_class,
+	.interface = &ext_side_effects
+};
+
+/*
+ * Tag validation
+ */
+
+static bool tag_mailbox_create_validate
+(struct sieve_validator *valdtr ATTR_UNUSED,
+	struct sieve_ast_argument **arg, struct sieve_command *cmd ATTR_UNUSED)
+{
+	*arg = sieve_ast_argument_next(*arg);
+
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool tag_mailbox_create_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+	struct sieve_command *context ATTR_UNUSED)
+{
+	if ( sieve_ast_argument_type(arg) != SAAT_TAG ) {
+		return FALSE;
+	}
+
+	sieve_opr_side_effect_emit
+		(cgenv->sblock, arg->argument->ext, &mailbox_create_side_effect);
+
+	return TRUE;
+}
+
+/*
+ * Side effect implementation
+ */
+
+static void seff_mailbox_create_print
+(const struct sieve_side_effect *seffect ATTR_UNUSED,
+	const struct sieve_action *action ATTR_UNUSED,
+	const struct sieve_result_print_env *rpenv, bool *keep ATTR_UNUSED)
+{
+	sieve_result_seffect_printf(rpenv, "create mailbox if it does not exist");
+}
+
+static int seff_mailbox_create_pre_execute
+(const struct sieve_side_effect *seffect ATTR_UNUSED,
+	const struct sieve_action *action ATTR_UNUSED,
+	const struct sieve_action_exec_env *aenv ATTR_UNUSED,
+	void **se_context ATTR_UNUSED, void *tr_context ATTR_UNUSED)
+{
+	struct act_store_transaction *trans =
+		(struct act_store_transaction *) tr_context;
+	struct mail_storage **storage = &(aenv->exec_status->last_storage);
+	enum mail_error error;
+
+	/* Check whether creation is necessary */
+	if ( trans->box == NULL || trans->disabled )
+		return SIEVE_EXEC_OK;
+
+	/* Check whether creation has a chance of working */
+	switch ( trans->error_code ) {
+	case MAIL_ERROR_NONE:
+		return SIEVE_EXEC_OK;
+	case MAIL_ERROR_NOTFOUND:
+		break;
+	case MAIL_ERROR_TEMP:
+		return SIEVE_EXEC_TEMP_FAILURE;
+	default:
+		return SIEVE_EXEC_FAILURE;
+	}
+
+	trans->error = NULL;
+	trans->error_code = MAIL_ERROR_NONE;
+
+	*storage = mailbox_get_storage(trans->box);
+
+	/* Create mailbox */
+	if ( mailbox_create(trans->box, NULL, FALSE) < 0 ) {
+		(void)mail_storage_get_last_error(*storage, &error);
+		if ( error != MAIL_ERROR_EXISTS ) {
+			sieve_act_store_get_storage_error(aenv, trans);
+			return ( trans->error_code == MAIL_ERROR_TEMP ?
+				SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE );
+		}
+	}
+
+	/* Subscribe to it if necessary */
+	if ( aenv->scriptenv->mailbox_autosubscribe ) {
+		(void)mailbox_list_set_subscribed
+			(mailbox_get_namespace(trans->box)->list,
+			 mailbox_get_name(trans->box), TRUE);
+	}
+
+	/* Try opening again */
+	if ( mailbox_open(trans->box) < 0 ) {
+		/* Failed definitively */
+		sieve_act_store_get_storage_error(aenv, trans);
+		return ( trans->error_code == MAIL_ERROR_TEMP ?
+			SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE );
+	}
+
+	return SIEVE_EXEC_OK;
+}
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/mailbox/tst-mailboxexists.c
@@ -0,0 +1,248 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+#include "mail-storage.h"
+#include "mail-namespace.h"
+
+#include "sieve-common.h"
+#include "sieve-actions.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-mailbox-common.h"
+
+/*
+ * Mailboxexists command
+ *
+ * Syntax:
+ *    mailboxexists <mailbox-names: string-list>
+ */
+
+static bool tst_mailboxexists_validate
+	(struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_mailboxexists_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def mailboxexists_test = {
+	.identifier = "mailboxexists",
+	.type = SCT_TEST,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = tst_mailboxexists_validate,
+	.generate = tst_mailboxexists_generate
+};
+
+/*
+ * Mailboxexists operation
+ */
+
+static bool tst_mailboxexists_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_mailboxexists_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def mailboxexists_operation = {
+	.mnemonic = "MAILBOXEXISTS",
+	.ext_def = &mailbox_extension,
+	.dump = tst_mailboxexists_operation_dump,
+	.execute = tst_mailboxexists_operation_execute
+};
+
+/*
+ * Test validation
+ */
+
+struct _validate_context {
+	struct sieve_validator *valdtr;
+	struct sieve_command *tst;
+};
+
+static int tst_mailboxexists_mailbox_validate
+(void *context, struct sieve_ast_argument *arg)
+{
+	struct _validate_context *valctx =
+		(struct _validate_context *)context;
+
+	if ( sieve_argument_is_string_literal(arg) ) {
+		const char *mailbox = sieve_ast_argument_strc(arg), *error;
+
+		if ( !sieve_mailbox_check_name(mailbox, &error) ) {
+			sieve_argument_validate_warning
+				(valctx->valdtr, arg, "%s test: "
+					"invalid mailbox name `%s' specified: %s",
+					sieve_command_identifier(valctx->tst),
+					str_sanitize(mailbox, 256), error);
+		}
+	}
+
+	return 1;
+}
+
+static bool tst_mailboxexists_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+	struct sieve_ast_argument *arg = tst->first_positional;
+	struct sieve_ast_argument *aarg; 
+	struct _validate_context valctx;
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "mailbox-names", 1, SAAT_STRING_LIST) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+		return FALSE;
+
+	aarg = arg;
+	i_zero(&valctx);
+	valctx.valdtr = valdtr;
+	valctx.tst = tst;
+
+	return ( sieve_ast_stringlist_map(&aarg,
+		(void*)&valctx, tst_mailboxexists_mailbox_validate) >= 0 );
+}
+
+/*
+ * Test generation
+ */
+
+static bool tst_mailboxexists_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
+{
+	sieve_operation_emit(cgenv->sblock, tst->ext, &mailboxexists_operation);
+
+ 	/* Generate arguments */
+	return sieve_generate_arguments(cgenv, tst, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_mailboxexists_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "MAILBOXEXISTS");
+	sieve_code_descend(denv);
+
+	return
+		sieve_opr_stringlist_dump(denv, address, "mailbox-names");
+}
+
+/*
+ * Code execution
+ */
+
+static int tst_mailboxexists_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	struct sieve_stringlist *mailbox_names;
+	string_t *mailbox_item;
+	bool trace = FALSE;
+	bool all_exist = TRUE;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Read notify uris */
+	if ( (ret=sieve_opr_stringlist_read
+		(renv, address, "mailbox-names", &mailbox_names)) <= 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_TESTS) ) {
+		sieve_runtime_trace(renv, 0, "mailboxexists test");
+		sieve_runtime_trace_descend(renv);
+
+		trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING);
+	}
+
+	if ( renv->scriptenv->user != NULL ) {
+		int ret;
+
+		mailbox_item = NULL;
+		while ( (ret=sieve_stringlist_next_item(mailbox_names, &mailbox_item)) > 0 )
+			{
+			struct mail_namespace *ns;
+			const char *mailbox = str_c(mailbox_item);
+			struct mailbox *box;
+
+			/* Find the namespace */
+			ns = mail_namespace_find(renv->scriptenv->user->namespaces, mailbox);
+			if ( ns == NULL) {
+				if ( trace ) {
+					sieve_runtime_trace(renv, 0, "mailbox `%s' not found",
+						str_sanitize(mailbox, 80));
+				}
+
+				all_exist = FALSE;
+				break;
+			}
+
+			/* Open the box */
+			box = mailbox_alloc(ns->list, mailbox, 0);
+			if ( mailbox_open(box) < 0 ) {
+				if ( trace ) {
+					sieve_runtime_trace(renv, 0, "mailbox `%s' cannot be opened",
+						str_sanitize(mailbox, 80));
+				}
+
+				all_exist = FALSE;
+				mailbox_free(&box);
+				break;
+			}
+
+			/* Also fail when it is readonly */
+			if ( mailbox_is_readonly(box) ) {
+				if ( trace ) {
+					sieve_runtime_trace(renv, 0, "mailbox `%s' is read-only",
+						str_sanitize(mailbox, 80));
+				}
+
+				all_exist = FALSE;
+				mailbox_free(&box);
+				break;
+			}
+
+			/* FIXME: check acl for 'p' or 'i' ACL permissions as required by RFC */
+
+			if ( trace ) {
+				sieve_runtime_trace(renv, 0, "mailbox `%s' exists",
+					str_sanitize(mailbox, 80));
+			}
+
+			/* Close mailbox */
+			mailbox_free(&box);
+		}
+
+		if ( ret < 0 ) {
+			sieve_runtime_trace_error(renv, "invalid mailbox name item");
+			return SIEVE_EXEC_BIN_CORRUPT;
+		}
+	}
+
+	if ( trace ) {
+		if ( all_exist )
+			sieve_runtime_trace(renv, 0, "all mailboxes are available");
+		else
+			sieve_runtime_trace(renv, 0, "some mailboxes are unavailable");
+	}
+
+	sieve_interpreter_set_test_result(renv->interp, all_exist);
+	return SIEVE_EXEC_OK;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/metadata/Makefile.am
@@ -0,0 +1,24 @@
+noinst_LTLIBRARIES = libsieve_ext_metadata.la
+
+libsieve_ext_metadata_la_LDFLAGS = -module -avoid-version
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../.. \
+	-I$(srcdir)/../variables \
+	$(LIBDOVECOT_INCLUDE) \
+	$(LIBDOVECOT_STORAGE_INCLUDE)
+
+tests = \
+	tst-metadata.c \
+	tst-metadataexists.c
+
+extensions = \
+	ext-metadata.c
+
+libsieve_ext_metadata_la_SOURCES = \
+	$(tests) \
+	$(extensions)
+
+noinst_HEADERS = \
+	ext-metadata-common.h
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/metadata/ext-metadata-common.h
@@ -0,0 +1,40 @@
+#ifndef EXT_METADATA_COMMON_H
+#define EXT_METADATA_COMMON_H
+
+#include "lib.h"
+#include "mail-storage.h"
+#include "imap-metadata.h"
+
+#include "sieve-common.h"
+
+/*
+ * Extension
+ */
+
+extern const struct sieve_extension_def mboxmetadata_extension;
+extern const struct sieve_extension_def servermetadata_extension;
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def metadata_test;
+extern const struct sieve_command_def servermetadata_test;
+extern const struct sieve_command_def metadataexists_test;
+extern const struct sieve_command_def servermetadataexists_test;
+
+/*
+ * Operations
+ */
+
+enum ext_metadata_opcode {
+	EXT_METADATA_OPERATION_METADATA,
+	EXT_METADATA_OPERATION_METADATAEXISTS
+};
+
+extern const struct sieve_operation_def metadata_operation;
+extern const struct sieve_operation_def servermetadata_operation;
+extern const struct sieve_operation_def metadataexists_operation;
+extern const struct sieve_operation_def servermetadataexists_operation;
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/metadata/ext-metadata.c
@@ -0,0 +1,83 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-binary.h"
+
+#include "sieve-validator.h"
+#include "sieve-interpreter.h"
+
+#include "ext-metadata-common.h"
+
+/*
+ * Extension mboxmetadata
+ * -----------------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5490; Section 3
+ * Implementation: skeleton
+ * Status: development
+ *
+ */
+
+const struct sieve_operation_def *mboxmetadata_operations[] = {
+	&metadata_operation,
+	&metadataexists_operation,
+};
+
+static bool ext_mboxmetadata_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def mboxmetadata_extension = {
+	.name = "mboxmetadata",
+	.validator_load = ext_mboxmetadata_validator_load,
+	SIEVE_EXT_DEFINE_OPERATIONS(mboxmetadata_operations)
+};
+
+static bool ext_mboxmetadata_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	sieve_validator_register_command(valdtr, ext, &metadata_test);
+	sieve_validator_register_command(valdtr, ext, &metadataexists_test);
+
+	return TRUE;
+}
+
+/*
+ * Extension servermetadata
+ * -----------------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5490; Section 4
+ * Implementation: skeleton
+ * Status: development
+ *
+ */
+
+const struct sieve_operation_def *servermetadata_operations[] = {
+	&servermetadata_operation,
+	&servermetadataexists_operation,
+};
+
+static bool ext_servermetadata_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def servermetadata_extension = {
+	.name = "servermetadata",
+	.validator_load = ext_servermetadata_validator_load,
+	SIEVE_EXT_DEFINE_OPERATIONS(servermetadata_operations)
+};
+
+static bool ext_servermetadata_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	sieve_validator_register_command(valdtr, ext, &servermetadata_test);
+	sieve_validator_register_command(valdtr, ext, &servermetadataexists_test);
+
+	return TRUE;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/metadata/tst-metadata.c
@@ -0,0 +1,420 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+#include "istream.h"
+
+#include "sieve-common.h"
+#include "sieve-limits.h"
+#include "sieve-commands.h"
+#include "sieve-actions.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+#include "ext-metadata-common.h"
+
+#include <ctype.h>
+
+#define TST_METADATA_MAX_MATCH_SIZE SIEVE_MAX_STRING_LEN
+
+/*
+ * Test definitions
+ */
+
+/* Forward declarations */
+
+static bool tst_metadata_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool tst_metadata_validate
+	(struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_metadata_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+/* Metadata test
+ *
+ * Syntax:
+ *   metadata [MATCH-TYPE] [COMPARATOR]
+ *            <mailbox: string>
+ *            <annotation-name: string> <key-list: string-list>
+ */
+
+const struct sieve_command_def metadata_test = {
+	.identifier = "metadata",
+	.type = SCT_TEST,
+	.positional_args = 3,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = tst_metadata_registered,
+	.validate = tst_metadata_validate,
+	.generate = tst_metadata_generate,
+};
+
+/* Servermetadata test
+ *
+ * Syntax:
+ *   servermetadata [MATCH-TYPE] [COMPARATOR]
+ *            <annotation-name: string> <key-list: string-list>
+ */
+
+const struct sieve_command_def servermetadata_test = {
+	.identifier = "servermetadata",
+	.type = SCT_TEST,
+	.positional_args = 2,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = tst_metadata_registered,
+	.validate = tst_metadata_validate,
+	.generate = tst_metadata_generate
+};
+
+/*
+ * Opcode definitions
+ */
+
+static bool tst_metadata_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_metadata_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+/* Metadata operation */
+
+const struct sieve_operation_def metadata_operation = {
+	.mnemonic = "METADATA",
+	.ext_def = &mboxmetadata_extension,
+	.code = EXT_METADATA_OPERATION_METADATA,
+	.dump = tst_metadata_operation_dump,
+	.execute = tst_metadata_operation_execute
+};
+
+/* Servermetadata operation */
+
+const struct sieve_operation_def servermetadata_operation = {
+	.mnemonic = "SERVERMETADATA",
+	.ext_def = &servermetadata_extension,
+	.code = EXT_METADATA_OPERATION_METADATA,
+	.dump = tst_metadata_operation_dump,
+	.execute = tst_metadata_operation_execute
+};
+
+/*
+ * Test registration
+ */
+
+static bool tst_metadata_registered
+(struct sieve_validator *valdtr,
+	const struct sieve_extension *ext ATTR_UNUSED,
+	struct sieve_command_registration *cmd_reg)
+{
+	/* The order of these is not significant */
+	sieve_comparators_link_tag(valdtr, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR);
+	sieve_match_types_link_tags(valdtr, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
+
+	return TRUE;
+}
+
+/*
+ * Test validation
+ */
+
+static bool tst_metadata_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+	struct sieve_ast_argument *arg = tst->first_positional;
+	const struct sieve_match_type mcht_default =
+		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+	const struct sieve_comparator cmp_default =
+		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+	unsigned int arg_index = 1;
+	const char *error;
+
+	/* mailbox */
+	if ( sieve_command_is(tst, metadata_test) ) {
+		if ( !sieve_validate_positional_argument
+			(valdtr, tst, arg, "mailbox", arg_index++, SAAT_STRING) ) {
+			return FALSE;
+		}
+
+		if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+			return FALSE;
+
+		/* Check name validity when mailbox argument is not a variable */
+		if ( sieve_argument_is_string_literal(arg) ) {
+			const char *mailbox = sieve_ast_argument_strc(arg), *error;
+
+			if ( !sieve_mailbox_check_name(mailbox, &error) ) {
+				sieve_argument_validate_warning
+					(valdtr, arg, "%s test: "
+						"invalid mailbox name `%s' specified: %s",
+						sieve_command_identifier(tst),
+						str_sanitize(mailbox, 256), error);
+			}
+		}
+
+		arg = sieve_ast_argument_next(arg);
+	}
+
+	/* annotation-name */
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "annotation-name", arg_index++, SAAT_STRING) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+		return FALSE;
+
+	if ( sieve_argument_is_string_literal(arg) ) {
+		string_t *aname = sieve_ast_argument_str(arg);
+
+		if ( !imap_metadata_verify_entry_name(str_c(aname), &error) ) {
+			char *lcerror = t_strdup_noconst(error);
+			lcerror[0] = i_tolower(lcerror[0]);
+			sieve_argument_validate_warning
+				(valdtr, arg, "%s test: "
+					"specified annotation name `%s' is invalid: %s",
+					sieve_command_identifier(tst),
+					str_sanitize(str_c(aname), 256), lcerror);
+		}
+	}
+
+	arg = sieve_ast_argument_next(arg);
+
+	/* key-list */
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "key-list", arg_index++, SAAT_STRING_LIST) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+		return FALSE;
+
+	/* Validate the key argument to a specified match type */
+	return sieve_match_type_validate
+		(valdtr, tst, arg, &mcht_default, &cmp_default);
+}
+
+/*
+ * Test generation
+ */
+
+static bool tst_metadata_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
+{
+	if ( sieve_command_is(tst, metadata_test) ) {
+		sieve_operation_emit
+			(cgenv->sblock, tst->ext, &metadata_operation);
+	} else if ( sieve_command_is(tst, servermetadata_test) ) {
+		sieve_operation_emit
+			(cgenv->sblock, tst->ext, &servermetadata_operation);
+	} else {
+		i_unreached();
+	}
+
+ 	/* Generate arguments */
+	if ( !sieve_generate_arguments(cgenv, tst, NULL) )
+		return FALSE;
+
+	return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_metadata_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	bool metadata = sieve_operation_is(denv->oprtn, metadata_operation);
+
+	if ( metadata )
+		sieve_code_dumpf(denv, "METADATA");
+	else
+		sieve_code_dumpf(denv, "SERVERMETADATA");
+
+	sieve_code_descend(denv);
+
+	/* Handle any optional arguments */
+	if ( sieve_match_opr_optional_dump(denv, address, NULL) != 0 )
+ 		return FALSE;
+
+	if ( metadata && !sieve_opr_string_dump(denv, address, "mailbox") )
+		return FALSE;
+
+	return
+		sieve_opr_string_dump(denv, address, "annotation-name") &&
+		sieve_opr_stringlist_dump(denv, address, "key list");
+}
+
+/*
+ * Code execution
+ */
+
+static inline const char *_lc_error(const char *error)
+{
+	char *lcerror = t_strdup_noconst(error);
+	lcerror[0] = i_tolower(lcerror[0]);
+
+	return lcerror;
+}
+
+static int tst_metadata_get_annotation
+(const struct sieve_runtime_env *renv, const char *mailbox,
+	const char *aname, const char **annotation_r)
+{
+	struct mail_user *user = renv->scriptenv->user;
+	struct mailbox *box;
+	struct imap_metadata_transaction *imtrans;
+	struct mail_attribute_value avalue;
+	int status, ret;
+
+	*annotation_r = NULL;
+
+	if ( user == NULL )
+		return SIEVE_EXEC_OK;
+
+	if ( mailbox != NULL ) {
+		struct mail_namespace *ns;
+		ns = mail_namespace_find(user->namespaces, mailbox);
+		box = mailbox_alloc(ns->list, mailbox, 0);
+		imtrans = imap_metadata_transaction_begin(box);
+	} else {
+		box = NULL;
+		imtrans = imap_metadata_transaction_begin_server(user);
+	}
+
+	status = SIEVE_EXEC_OK;
+	ret = imap_metadata_get(imtrans, aname, &avalue);
+	if (ret < 0) {
+		enum mail_error error_code;
+		const char *error;
+
+		error = imap_metadata_transaction_get_last_error
+			(imtrans, &error_code);
+
+		sieve_runtime_error(renv, NULL, "%s test: "
+			"failed to retrieve annotation `%s': %s%s",
+			(mailbox != NULL ? "metadata" : "servermetadata"),
+			str_sanitize(aname, 256), _lc_error(error),
+			(error_code == MAIL_ERROR_TEMP ? " (temporary failure)" : ""));
+
+		status = ( error_code == MAIL_ERROR_TEMP ?
+			SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE );
+
+	} else if (avalue.value != NULL) {
+		*annotation_r = avalue.value;
+	}
+	(void)imap_metadata_transaction_commit(&imtrans, NULL, NULL);
+	if ( box != NULL )
+		mailbox_free(&box);
+	return status;
+}
+
+static int tst_metadata_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	bool metadata = sieve_operation_is(renv->oprtn, metadata_operation);
+	struct sieve_match_type mcht =
+		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+	struct sieve_comparator cmp =
+		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+	string_t *mailbox, *aname;
+	struct sieve_stringlist *value_list, *key_list;
+	const char *annotation = NULL, *error;
+	int match, ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Handle match-type and comparator operands */
+	if ( sieve_match_opr_optional_read
+		(renv, address, NULL, &ret, &cmp, &mcht) < 0 )
+		return ret;
+
+	/* Read mailbox */
+	if ( metadata ) {
+		if ( (ret=sieve_opr_string_read(renv, address, "mailbox", &mailbox)) <= 0 )
+			return ret;
+	}
+
+	/* Read annotation-name */
+	if ( (ret=sieve_opr_string_read
+		(renv, address, "annotation-name", &aname)) <= 0 )
+		return ret;
+
+	/* Read key-list */
+	if ( (ret=sieve_opr_stringlist_read
+		(renv, address, "key-list", &key_list)) <= 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	if ( metadata )
+		sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "metadata test");
+	else
+		sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "servermetadata test");
+	sieve_runtime_trace_descend(renv);
+
+	if ( !imap_metadata_verify_entry_name(str_c(aname), &error) ) {
+		sieve_runtime_warning(renv, NULL, "%s test: "
+			"specified annotation name `%s' is invalid: %s",
+			(metadata ? "metadata" : "servermetadata"),
+			str_sanitize(str_c(aname), 256), _lc_error(error));
+		sieve_interpreter_set_test_result(renv->interp, FALSE);
+		return SIEVE_EXEC_OK;
+	}
+
+	if ( metadata ) {
+		if ( !sieve_mailbox_check_name(str_c(mailbox), &error) ) {
+			sieve_runtime_warning(renv, NULL, "metadata test: "
+				"invalid mailbox name `%s' specified: %s",
+				str_sanitize(str_c(mailbox), 256), error);
+			sieve_interpreter_set_test_result(renv->interp, FALSE);
+			return SIEVE_EXEC_OK;
+		}
+
+		sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+			"retrieving annotation `%s' from mailbox `%s'",
+			str_sanitize(str_c(aname), 256),
+			str_sanitize(str_c(mailbox), 80));
+	} else {
+		sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+			"retrieving server annotation `%s'",
+			str_sanitize(str_c(aname), 256));
+	}
+
+	/* Get annotation */
+	if ( (ret=tst_metadata_get_annotation
+		(renv, (metadata ? str_c(mailbox) : NULL), str_c(aname), &annotation))
+			== SIEVE_EXEC_OK ) {
+		/* Perform match */
+		if ( annotation != NULL ) {
+			/* Create value stringlist */
+			value_list = sieve_single_stringlist_create_cstr(renv, annotation, FALSE);
+
+			/* Perform match */
+			if ( (match=sieve_match
+				(renv, &mcht, &cmp, value_list, key_list, &ret)) < 0 )
+				return ret;
+		} else {
+			match = 0;
+		}
+	}
+
+	/* Set test result for subsequent conditional jump */
+	if (ret == SIEVE_EXEC_OK)
+		sieve_interpreter_set_test_result(renv->interp, match > 0);
+	return ret;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/metadata/tst-metadataexists.c
@@ -0,0 +1,398 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+#include "mail-storage.h"
+#include "mail-namespace.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-actions.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-metadata-common.h"
+
+#include <ctype.h>
+
+
+/*
+ * Command definitions
+ */
+
+/* Forward declarations */
+
+static bool tst_metadataexists_validate
+	(struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_metadataexists_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+/* Metadataexists command
+ *
+ * Syntax:
+ *    metadataexists <mailbox: string> <annotation-names: string-list>
+ */
+
+static bool tst_metadataexists_validate
+	(struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_metadataexists_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def metadataexists_test = {
+	.identifier = "metadataexists",
+	.type = SCT_TEST,
+	.positional_args = 2,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = tst_metadataexists_validate,
+	.generate = tst_metadataexists_generate,
+};
+
+/* Servermetadataexists command
+ *
+ * Syntax:
+ *    servermetadataexists <annotation-names: string-list>
+ */
+
+const struct sieve_command_def servermetadataexists_test = {
+	.identifier = "servermetadataexists",
+	.type = SCT_TEST,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = tst_metadataexists_validate,
+	.generate = tst_metadataexists_generate,
+};
+
+/*
+ * Opcode definitions
+ */
+
+static bool tst_metadataexists_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_metadataexists_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+/* Metadata operation */
+
+const struct sieve_operation_def metadataexists_operation = {
+	.mnemonic = "METADATAEXISTS",
+	.ext_def = &mboxmetadata_extension,
+	.code = EXT_METADATA_OPERATION_METADATAEXISTS,
+	.dump = tst_metadataexists_operation_dump,
+	.execute = tst_metadataexists_operation_execute
+};
+
+/* Mailboxexists operation */
+
+const struct sieve_operation_def servermetadataexists_operation = {
+	.mnemonic = "SERVERMETADATAEXISTS",
+	.ext_def = &servermetadata_extension,
+	.code = EXT_METADATA_OPERATION_METADATAEXISTS,
+	.dump = tst_metadataexists_operation_dump,
+	.execute = tst_metadataexists_operation_execute
+};
+
+/*
+ * Test validation
+ */
+
+struct _validate_context {
+	struct sieve_validator *valdtr;
+	struct sieve_command *tst;
+};
+
+static int tst_metadataexists_annotation_validate
+(void *context, struct sieve_ast_argument *arg)
+{
+	struct _validate_context *valctx =
+		(struct _validate_context *)context;
+
+	if ( sieve_argument_is_string_literal(arg) ) {
+		const char *aname = sieve_ast_strlist_strc(arg);
+		const char *error;
+
+		if ( !imap_metadata_verify_entry_name(aname, &error) ) {
+			char *lcerror = t_strdup_noconst(error);
+			lcerror[0] = i_tolower(lcerror[0]);
+			sieve_argument_validate_warning
+				(valctx->valdtr, arg, "%s test: "
+					"specified annotation name `%s' is invalid: %s",
+					sieve_command_identifier(valctx->tst),
+					str_sanitize(aname, 256), lcerror);
+		}
+	}
+
+	return 1; /* Can't check at compile time */
+}
+
+static bool tst_metadataexists_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+	struct sieve_ast_argument *arg = tst->first_positional;
+	struct sieve_ast_argument *aarg; 
+	struct _validate_context valctx;
+	unsigned int arg_index = 1;
+
+	if ( sieve_command_is(tst, metadataexists_test) ) {
+		if ( !sieve_validate_positional_argument
+			(valdtr, tst, arg, "mailbox", arg_index++, SAAT_STRING) ) {
+			return FALSE;
+		}
+
+		if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+			return FALSE;
+
+		/* Check name validity when mailbox argument is not a variable */
+		if ( sieve_argument_is_string_literal(arg) ) {
+			const char *mailbox = sieve_ast_argument_strc(arg), *error;
+
+			if ( !sieve_mailbox_check_name(mailbox, &error) ) {
+				sieve_argument_validate_warning
+					(valdtr, arg, "%s test: "
+						"invalid mailbox name `%s' specified: %s",
+						sieve_command_identifier(tst),
+						str_sanitize(mailbox, 256), error);
+			}
+		}
+
+		arg = sieve_ast_argument_next(arg);
+	}
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "annotation-names", arg_index++, SAAT_STRING_LIST) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+		return FALSE;
+
+	aarg = arg;
+	i_zero(&valctx);
+	valctx.valdtr = valdtr;
+	valctx.tst = tst;
+
+	return (sieve_ast_stringlist_map(&aarg,
+		(void*)&valctx, tst_metadataexists_annotation_validate) >= 0);
+}
+
+/*
+ * Test generation
+ */
+
+static bool tst_metadataexists_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
+{
+	if ( sieve_command_is(tst, metadataexists_test) ) {
+		sieve_operation_emit
+			(cgenv->sblock, tst->ext, &metadataexists_operation);
+	} else if ( sieve_command_is(tst, servermetadataexists_test) ) {
+		sieve_operation_emit
+			(cgenv->sblock, tst->ext, &servermetadataexists_operation);
+	} else {
+		i_unreached();
+	}
+
+ 	/* Generate arguments */
+	return sieve_generate_arguments(cgenv, tst, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_metadataexists_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	bool metadata = sieve_operation_is(denv->oprtn, metadataexists_operation);
+
+	if ( metadata )
+		sieve_code_dumpf(denv, "METADATAEXISTS");
+	else
+		sieve_code_dumpf(denv, "SERVERMETADATAEXISTS");
+
+	sieve_code_descend(denv);
+
+	if ( metadata && !sieve_opr_string_dump(denv, address, "mailbox") )
+		return FALSE;
+
+	return
+		sieve_opr_stringlist_dump(denv, address, "annotation-names");
+}
+
+/*
+ * Code execution
+ */
+
+static inline const char *_lc_error(const char *error)
+{
+	char *lcerror = t_strdup_noconst(error);
+	lcerror[0] = i_tolower(lcerror[0]);
+
+	return lcerror;
+}
+
+static int tst_metadataexists_check_annotations
+(const struct sieve_runtime_env *renv, const char *mailbox,
+	struct sieve_stringlist *anames, bool *all_exist_r)
+{
+	struct mail_user *user = renv->scriptenv->user;
+	struct mailbox *box = NULL;
+	struct imap_metadata_transaction *imtrans;
+	string_t *aname;
+	bool all_exist = TRUE;
+	int ret, sret, status;
+
+	*all_exist_r = FALSE;
+
+	if ( user == NULL )
+		return SIEVE_EXEC_OK;
+
+	if ( mailbox != NULL ) {
+		struct mail_namespace *ns;
+		ns = mail_namespace_find(user->namespaces, mailbox);
+		box = mailbox_alloc(ns->list, mailbox, 0);
+		imtrans = imap_metadata_transaction_begin(box);
+	} else {
+		imtrans = imap_metadata_transaction_begin_server(user);
+	}
+
+	if ( mailbox != NULL ) {
+		sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+			"checking annotations of mailbox `%s':",
+			str_sanitize(mailbox, 80));
+	} else {
+		sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+			"checking server annotations");
+	}
+
+	aname = NULL;
+	status = SIEVE_EXEC_OK;
+	while ( all_exist &&
+		(sret=sieve_stringlist_next_item(anames, &aname)) > 0 ) {
+		struct mail_attribute_value avalue;
+		const char *error;
+
+		if ( !imap_metadata_verify_entry_name(str_c(aname), &error) ) {
+			sieve_runtime_warning(renv, NULL, "%s test: "
+				"specified annotation name `%s' is invalid: %s",
+				(mailbox != NULL ? "metadataexists" : "servermetadataexists"),
+				str_sanitize(str_c(aname), 256), _lc_error(error));
+			all_exist = FALSE;
+			break;;
+		}
+
+		ret = imap_metadata_get(imtrans, str_c(aname), &avalue);
+		if (ret < 0) {
+			enum mail_error error_code;
+			const char *error;
+
+			error = imap_metadata_transaction_get_last_error
+				(imtrans, &error_code);
+			sieve_runtime_error(renv, NULL, "%s test: "
+				"failed to retrieve annotation `%s': %s%s",
+				(mailbox != NULL ? "metadataexists" : "servermetadataexists"),
+				str_sanitize(str_c(aname), 256), _lc_error(error),
+				(error_code == MAIL_ERROR_TEMP ? " (temporary failure)" : ""));
+
+			all_exist = FALSE;
+			status = ( error_code == MAIL_ERROR_TEMP ?
+				SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE );
+			break;
+
+		} else if (avalue.value == NULL && avalue.value_stream == NULL) {
+			all_exist = FALSE;
+			sieve_runtime_trace(renv, 0,
+				"annotation `%s': not found", str_c(aname));
+			break;
+
+		} else {
+			sieve_runtime_trace(renv, 0,
+				"annotation `%s': found", str_c(aname));
+		}
+	}
+
+	if ( sret < 0 ) {
+		sieve_runtime_trace_error
+			(renv, "invalid annotation name stringlist item");
+		status = SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	(void)imap_metadata_transaction_commit(&imtrans, NULL, NULL);
+	if ( box != NULL )
+		mailbox_free(&box);
+
+	*all_exist_r = all_exist;
+	return status;
+}
+
+static int tst_metadataexists_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	bool metadata = sieve_operation_is(renv->oprtn, metadataexists_operation);
+	struct sieve_stringlist *anames;
+	string_t *mailbox;
+	bool trace = FALSE, all_exist = TRUE;
+	const char *error;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Read mailbox */
+	if ( metadata ) {
+		if ( (ret=sieve_opr_string_read(renv, address, "mailbox", &mailbox)) <= 0 )
+			return ret;
+	}
+
+	/* Read annotation names */
+	if ( (ret=sieve_opr_stringlist_read
+		(renv, address, "annotation-names", &anames)) <= 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	if ( metadata && !sieve_mailbox_check_name(str_c(mailbox), &error) ) {
+		sieve_runtime_warning(renv, NULL, "metadata test: "
+			"invalid mailbox name `%s' specified: %s",
+			str_sanitize(str_c(mailbox), 256), error);
+		sieve_interpreter_set_test_result(renv->interp, FALSE);
+		return SIEVE_EXEC_OK;
+	}
+
+	if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_TESTS) ) {
+		if ( metadata )
+			sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "metadataexists test");
+		else
+			sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "servermetadataexists test");
+
+		sieve_runtime_trace_descend(renv);
+
+		trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING);
+	}
+
+	if ( (ret=tst_metadataexists_check_annotations
+		(renv, (metadata ? str_c(mailbox) : NULL), anames,
+			&all_exist)) <= 0 )
+		return ret;
+
+	if ( trace ) {
+		if ( all_exist )
+			sieve_runtime_trace(renv, 0, "all annotations exist");
+		else
+			sieve_runtime_trace(renv, 0, "some annotations do not exist");
+	}
+
+	sieve_interpreter_set_test_result(renv->interp, all_exist);
+	return SIEVE_EXEC_OK;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/mime/Makefile.am
@@ -0,0 +1,30 @@
+noinst_LTLIBRARIES = libsieve_ext_mime.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../.. \
+	-I$(srcdir)/../../util \
+	-I$(srcdir)/../variables  \
+	$(LIBDOVECOT_INCLUDE)
+
+commands = \
+	cmd-foreverypart.c \
+	cmd-break.c \
+	cmd-extracttext.c
+
+tags = \
+	tag-mime.c
+
+extensions = \
+	ext-mime.c \
+	ext-foreverypart.c \
+	ext-extracttext.c
+
+libsieve_ext_mime_la_SOURCES = \
+	ext-mime-common.c \
+	$(commands) \
+	$(tags) \
+	$(extensions)
+
+noinst_HEADERS = \
+	ext-mime-common.h
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/mime/cmd-break.c
@@ -0,0 +1,273 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+
+#include "ext-mime-common.h"
+
+#include <ctype.h>
+
+/* break
+ *
+ * Syntax:
+ *   break [":name" <name: string>]
+ *
+ */
+
+static bool cmd_break_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool cmd_break_pre_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_break_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_break_generate
+	(const struct sieve_codegen_env *cgenv,
+		struct sieve_command *ctx);
+
+const struct sieve_command_def cmd_break = {
+	.identifier = "break",
+	.type = SCT_COMMAND,
+	.positional_args = 0,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = cmd_break_registered,
+	.pre_validate = cmd_break_pre_validate,
+	.validate = cmd_break_validate,
+	.generate = cmd_break_generate,
+};
+
+/*
+ * Tagged arguments
+ */
+
+/* Forward declarations */
+
+static bool cmd_break_validate_name_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+
+/* Argument objects */
+
+static const struct sieve_argument_def break_name_tag = {
+	.identifier = "name",
+	.validate = cmd_break_validate_name_tag
+};
+
+/*
+ * Break operation
+ */
+
+static bool cmd_break_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_break_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def break_operation = {
+	.mnemonic = "BREAK",
+	.ext_def = &foreverypart_extension,
+	.code = EXT_FOREVERYPART_OPERATION_BREAK,
+	.dump = cmd_break_operation_dump,
+	.execute = cmd_break_operation_execute
+};
+
+/*
+ * Validation data
+ */
+
+struct cmd_break_data {
+	struct sieve_ast_argument *name;
+	struct sieve_command *loop_cmd;
+};
+
+/*
+ * Tag validation
+ */
+
+static bool cmd_break_validate_name_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+    struct sieve_command *cmd)
+{
+	struct cmd_break_data *data =
+		(struct cmd_break_data *)cmd->data;
+	struct sieve_ast_argument *tag = *arg;
+
+	/* Detach the tag itself */
+	*arg = sieve_ast_arguments_detach(*arg, 1);
+
+	/* Check syntax:
+	 *   :name <string>
+	 */
+	if ( !sieve_validate_tag_parameter
+		(valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING, TRUE) )
+		return FALSE;
+	data->name = *arg;
+
+	/* Skip parameter */
+	*arg = sieve_ast_argument_next(*arg);
+	return TRUE;
+}
+
+/*
+ * Command registration
+ */
+
+static bool cmd_break_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg)
+{
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &break_name_tag, 0);
+	
+	return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+static bool cmd_break_pre_validate
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *cmd)
+{
+	struct cmd_break_data *data;
+	pool_t pool = sieve_command_pool(cmd);
+	
+	data = p_new(pool, struct cmd_break_data, 1);
+	cmd->data = data;
+	return TRUE;
+}
+
+static bool cmd_break_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+	struct cmd_break_data *data =
+		(struct cmd_break_data *)cmd->data;
+	struct sieve_ast_node *node = cmd->ast_node;
+	const char *name =	( data->name == NULL ?
+		NULL : sieve_ast_argument_strc(data->name) );
+
+	i_assert(node != NULL);
+	while ( node != NULL && node->command != NULL ) {
+		if ( sieve_command_is(node->command, cmd_foreverypart) ) {
+			struct ext_foreverypart_loop *loop =
+				(struct ext_foreverypart_loop *)node->command->data;
+			if ( name == NULL ||
+				(name != NULL && loop->name != NULL &&
+					strcmp(name, loop->name) == 0) ) {
+				data->loop_cmd = node->command;
+				break;
+			}
+		}
+		node = sieve_ast_node_parent(node);
+	}
+
+	if ( data->loop_cmd == NULL ) {
+		if ( name == NULL ) {
+			sieve_command_validate_error(valdtr, cmd,
+				"the break command is not placed inside "
+				"a foreverypart loop");
+		} else {
+			sieve_command_validate_error(valdtr, cmd,
+				"the break command is not placed inside "
+				"a foreverypart loop named `%s'",
+				name);
+		}
+		return FALSE;
+	}
+
+	sieve_command_exit_block_unconditionally(cmd);
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_break_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	struct cmd_break_data *data =
+		(struct cmd_break_data *)cmd->data;
+	struct ext_foreverypart_loop *loop;
+
+	i_assert( data->loop_cmd != NULL );
+	loop = (struct ext_foreverypart_loop *)data->loop_cmd->data;
+
+	sieve_operation_emit(cgenv->sblock, cmd->ext, &break_operation);
+	sieve_jumplist_add(loop->exit_jumps,
+		sieve_binary_emit_offset(cgenv->sblock, 0));
+	return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_break_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	unsigned int pc = *address;
+	sieve_offset_t offset;
+
+	sieve_code_dumpf(denv, "BREAK");
+	sieve_code_descend(denv);
+
+ 	if ( !sieve_binary_read_offset(denv->sblock, address, &offset) )
+		return FALSE;
+
+	sieve_code_dumpf(denv, "END: %d [%08x]", offset, pc + offset);
+	return TRUE;
+}
+
+/*
+ * Code execution
+ */
+
+static int cmd_break_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	struct sieve_interpreter_loop *loop;
+	unsigned int pc = *address;
+	sieve_offset_t offset;
+	sieve_size_t loop_end;
+
+	/*
+	 * Read operands
+	 */
+
+	if ( !sieve_binary_read_offset(renv->sblock, address, &offset) )
+	{
+		sieve_runtime_trace_error(renv, "invalid loop end offset");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	loop_end = pc + offset;
+
+	/*
+	 * Perform operation
+	 */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS, "break command");
+	sieve_runtime_trace_descend(renv);
+
+	loop = sieve_interpreter_loop_get
+		(renv->interp, loop_end, &foreverypart_extension);
+	if ( loop == NULL ) {
+		sieve_runtime_trace_error(renv, "no matching loop found");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	sieve_interpreter_loop_break(renv->interp, loop);
+	return SIEVE_EXEC_OK;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/mime/cmd-extracttext.c
@@ -0,0 +1,370 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-code.h"
+#include "sieve-ast.h"
+#include "sieve-commands.h"
+#include "sieve-binary.h"
+#include "sieve-message.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "sieve-ext-variables.h"
+
+#include "ext-mime-common.h"
+
+/*
+ * Extracttext command
+ *
+ * Syntax:
+ *    extracttext [MODIFIER] [":first" number] <varname: string>
+ */
+
+static bool cmd_extracttext_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool cmd_extracttext_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_extracttext_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def cmd_extracttext = {
+	.identifier = "extracttext",
+	.type = SCT_COMMAND,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = cmd_extracttext_registered,
+	.validate = cmd_extracttext_validate,
+	.generate = cmd_extracttext_generate
+};
+
+/*
+ * Extracttext command tags
+ */
+
+enum cmd_extracttext_optional {
+	CMD_EXTRACTTEXT_OPT_END,
+	CMD_EXTRACTTEXT_OPT_FIRST
+};
+
+static bool cmd_extracttext_validate_first_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+
+static const struct sieve_argument_def extracttext_from_tag = {
+	.identifier = "first",
+	.validate = cmd_extracttext_validate_first_tag
+};
+
+/*
+ * Extracttext operation
+ */
+
+static bool cmd_extracttext_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_extracttext_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def extracttext_operation = {
+	.mnemonic = "EXTRACTTEXT",
+	.ext_def = &extracttext_extension,
+	.dump = cmd_extracttext_operation_dump,
+	.execute = cmd_extracttext_operation_execute
+};
+
+/*
+ * Compiler context
+ */
+
+struct cmd_extracttext_context {
+	ARRAY_TYPE(sieve_variables_modifier) modifiers;
+};
+
+/*
+ * Tag validation
+ */
+
+static bool cmd_extracttext_validate_first_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *tag = *arg;
+
+	/* Detach the tag itself */
+	*arg = sieve_ast_arguments_detach(*arg,1);
+
+	/* Check syntax:
+	 *   :first <number>
+	 */
+	if ( !sieve_validate_tag_parameter
+		(valdtr, cmd, tag, *arg, NULL, 0, SAAT_NUMBER, FALSE) )
+		return FALSE;
+
+	/* Skip parameter */
+	*arg = sieve_ast_argument_next(*arg);
+
+	return TRUE;
+}
+
+/* Command registration */
+
+static bool cmd_extracttext_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg)
+{
+	struct ext_extracttext_context *ectx =
+		(struct ext_extracttext_context *)ext->context;
+
+	sieve_validator_register_tag(valdtr, cmd_reg, ext,
+		&extracttext_from_tag, CMD_EXTRACTTEXT_OPT_FIRST);
+	sieve_variables_modifiers_link_tag
+		(valdtr, ectx->var_ext, cmd_reg);
+	return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+static bool cmd_extracttext_validate(struct sieve_validator *valdtr,
+	struct sieve_command *cmd)
+{
+	const struct sieve_extension *this_ext = cmd->ext;
+	struct ext_extracttext_context *ectx =
+		(struct ext_extracttext_context *)this_ext->context;
+	struct sieve_ast_node *node = cmd->ast_node;
+	struct sieve_ast_argument *arg = cmd->first_positional;
+	pool_t pool = sieve_command_pool(cmd);
+	struct cmd_extracttext_context *sctx;
+
+	/* Create command context */
+	sctx = p_new(pool, struct cmd_extracttext_context, 1);
+	p_array_init(&sctx->modifiers, pool, 4);
+	cmd->data = (void *) sctx;
+
+	/* Validate modifiers */
+	if ( !sieve_variables_modifiers_validate
+		(valdtr, cmd, &sctx->modifiers) )
+		return FALSE;
+
+	/* Validate varname argument */
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "varname", 1, SAAT_STRING) ) {
+		return FALSE;
+	}
+	if ( !sieve_variable_argument_activate
+		(ectx->var_ext, ectx->var_ext, valdtr, cmd, arg, TRUE) )
+		return FALSE;
+
+	/* Check foreverypart context */
+	i_assert(node != NULL);
+	while ( node != NULL ) {
+		if ( node->command != NULL &&
+			sieve_command_is(node->command, cmd_foreverypart) )
+			break;
+		node = sieve_ast_node_parent(node);
+	}
+
+	if ( node == NULL ) {
+		sieve_command_validate_error(valdtr, cmd,
+			"the extracttext command is not placed inside "
+			"a foreverypart loop");
+		return FALSE;
+	}
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_extracttext_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	const struct sieve_extension *this_ext = cmd->ext;
+	struct sieve_binary_block *sblock = cgenv->sblock;
+	struct cmd_extracttext_context *sctx =
+		(struct cmd_extracttext_context *) cmd->data;
+
+	sieve_operation_emit(sblock, this_ext, &extracttext_operation);
+
+	/* Generate arguments */
+	if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
+		return FALSE;
+
+	/* Generate modifiers */
+	if ( !sieve_variables_modifiers_generate
+		(cgenv, &sctx->modifiers) )
+		return FALSE;
+
+	return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_extracttext_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	int opt_code = 0;
+
+	sieve_code_dumpf(denv, "EXTRACTTEXT");
+	sieve_code_descend(denv);
+
+	/* Dump optional operands */
+
+	for (;;) {
+		int opt;
+		bool opok = TRUE;
+
+		if ( (opt=sieve_opr_optional_dump(denv, address, &opt_code)) < 0 )
+			return FALSE;
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case CMD_EXTRACTTEXT_OPT_FIRST:
+			opok = sieve_opr_number_dump(denv, address, "first");
+			break;
+		default:
+			return FALSE;
+		}
+		if ( !opok ) return FALSE;
+	}
+
+	/* Print both variable name and string value */
+	if ( !sieve_opr_string_dump(denv, address, "varname") )
+		return FALSE;
+
+	return sieve_variables_modifiers_code_dump(denv, address);
+}
+
+/*
+ * Code execution
+ */
+
+static int cmd_extracttext_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	const struct sieve_extension *this_ext = renv->oprtn->ext;
+	struct ext_extracttext_context *ectx =
+		(struct ext_extracttext_context *)this_ext->context;
+	struct sieve_variable_storage *storage;
+	ARRAY_TYPE(sieve_variables_modifier) modifiers;
+	struct ext_foreverypart_runtime_loop *sfploop;
+	struct sieve_message_part *mpart;
+	struct sieve_message_part_data mpart_data;
+	int opt_code = 0;
+	sieve_number_t first = 0;
+	string_t *value;
+	unsigned int var_index;
+	bool have_first = FALSE;
+	int ret = SIEVE_EXEC_OK;
+
+	/*
+	 * Read the normal operands
+	 */
+
+	/* Optional operands */
+
+	for (;;) {
+		int opt;
+
+		if ( (opt=sieve_opr_optional_read
+			(renv, address, &opt_code)) < 0 )
+			return SIEVE_EXEC_BIN_CORRUPT;
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case CMD_EXTRACTTEXT_OPT_FIRST:
+			ret = sieve_opr_number_read
+				(renv, address, "first", &first);
+			have_first = TRUE;
+			break;
+		default:
+			sieve_runtime_trace_error(renv, "unknown optional operand");
+			return SIEVE_EXEC_BIN_CORRUPT;
+		}
+		if ( ret <= 0 ) return ret;
+	}
+
+	/* Varname operand */
+
+	if ( (ret=sieve_variable_operand_read
+		(renv, address, "varname", &storage, &var_index)) <= 0 )
+		return ret;
+
+	/* Modifiers */
+
+	if ( (ret=sieve_variables_modifiers_code_read
+		(renv, address, &modifiers)) <= 0 )
+		return ret;
+
+	/*
+	 * Determine and assign the value
+	 */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS, "extracttext command");
+	sieve_runtime_trace_descend(renv);
+
+	sfploop = ext_foreverypart_runtime_loop_get_current(renv);
+	if ( sfploop == NULL ) {
+		sieve_runtime_trace_error(renv,
+			"outside foreverypart context");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	/* Get current message part */
+	mpart = sieve_message_part_iter_current(&sfploop->part_iter);
+	i_assert( mpart != NULL );
+
+	/* Get message part content */
+	sieve_message_part_get_data(mpart, &mpart_data, TRUE);
+
+	/* Apply ":first" limit, if any */
+	if ( !have_first || (size_t)first > mpart_data.size ) {
+		value = t_str_new_const(mpart_data.content, mpart_data.size);
+	} else {
+		value = t_str_new((size_t)first);
+		str_append_data(value, mpart_data.content, (size_t)first);
+	}
+
+	/* Apply modifiers */
+	if ( (ret=sieve_variables_modifiers_apply
+		(renv, ectx->var_ext, &modifiers, &value)) <= 0 )
+		return ret;
+
+	/* Actually assign the value if all is well */
+	i_assert ( value != NULL );
+	if ( !sieve_variable_assign(storage, var_index, value) )
+		return SIEVE_EXEC_BIN_CORRUPT;
+
+	/* Trace */
+	if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
+		const char *var_name, *var_id;
+
+		(void)sieve_variable_get_identifier(storage, var_index, &var_name);
+		var_id = sieve_variable_get_varid(storage, var_index);
+
+		sieve_runtime_trace_here(renv, 0, "assign `%s' [%s] = \"%s\"",
+			var_name, var_id, str_c(value));
+	}
+
+	return SIEVE_EXEC_OK;
+}
+
+
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/mime/cmd-foreverypart.c
@@ -0,0 +1,377 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-limits.h"
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+#include "sieve-message.h"
+
+#include "ext-mime-common.h"
+
+#include <ctype.h>
+
+/* Foreverypart
+ *
+ * Syntax:
+ *   foreverypart [":name" <name: string>] <block>
+ *
+ */
+
+static bool cmd_foreverypart_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool cmd_foreverypart_pre_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_foreverypart_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_foreverypart_generate
+	(const struct sieve_codegen_env *cgenv,
+		struct sieve_command *ctx);
+
+const struct sieve_command_def cmd_foreverypart = {
+	.identifier = "foreverypart",
+	.type = SCT_COMMAND,
+	.positional_args = 0,
+	.subtests = 0,
+	.block_allowed = TRUE,
+	.block_required = TRUE,
+	.registered = cmd_foreverypart_registered,
+	.pre_validate = cmd_foreverypart_pre_validate,
+	.validate = cmd_foreverypart_validate,
+	.generate = cmd_foreverypart_generate
+};
+
+/*
+ * Tagged arguments
+ */
+
+/* Forward declarations */
+
+static bool cmd_foreverypart_validate_name_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+
+/* Argument objects */
+
+static const struct sieve_argument_def foreverypart_name_tag = {
+	.identifier = "name",
+	.validate = cmd_foreverypart_validate_name_tag,
+};
+
+/*
+ * foreverypart operation
+ */
+
+static bool cmd_foreverypart_begin_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_foreverypart_begin_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def foreverypart_begin_operation = {
+	.mnemonic = "FOREVERYPART_BEGIN",
+	.ext_def = &foreverypart_extension,
+	.code = EXT_FOREVERYPART_OPERATION_FOREVERYPART_BEGIN,
+	.dump = cmd_foreverypart_begin_operation_dump,
+	.execute = cmd_foreverypart_begin_operation_execute
+};
+
+static bool cmd_foreverypart_end_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_foreverypart_end_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def foreverypart_end_operation = {
+	.mnemonic = "FOREVERYPART_END",
+	.ext_def = &foreverypart_extension,
+	.code = EXT_FOREVERYPART_OPERATION_FOREVERYPART_END,
+	.dump = cmd_foreverypart_end_operation_dump,
+	.execute = cmd_foreverypart_end_operation_execute
+};
+
+/*
+ * Tag validation
+ */
+
+static bool cmd_foreverypart_validate_name_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+    struct sieve_command *cmd)
+{
+	struct ext_foreverypart_loop *loop =
+		(struct ext_foreverypart_loop *)cmd->data;
+	struct sieve_ast_argument *tag = *arg;
+
+	/* Detach the tag itself */
+	*arg = sieve_ast_arguments_detach(*arg, 1);
+
+	/* Check syntax:
+	 *   :name <string>
+	 */
+	if ( !sieve_validate_tag_parameter
+		(valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING, TRUE) )
+		return FALSE;
+	loop->name = sieve_ast_argument_strc(*arg);
+
+	/* Detach parameter */
+	*arg = sieve_ast_arguments_detach(*arg, 1);
+	return TRUE;
+}
+
+/*
+ * Command registration
+ */
+
+static bool cmd_foreverypart_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg)
+{
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &foreverypart_name_tag, 0);
+	return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+static bool cmd_foreverypart_pre_validate
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *cmd)
+{
+	struct ext_foreverypart_loop *loop;
+	pool_t pool = sieve_command_pool(cmd);
+	
+	loop = p_new(pool, struct ext_foreverypart_loop, 1);
+	cmd->data = loop;		
+
+	return TRUE;
+}
+
+static bool cmd_foreverypart_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+	struct sieve_ast_node *node = cmd->ast_node;
+	unsigned int nesting = 0;
+
+	/* Determine nesting depth of foreverypart commands at this point. */
+	i_assert(node != NULL);
+	node = sieve_ast_node_parent(node);
+	while ( node != NULL && node->command != NULL ) {
+		if ( sieve_command_is(node->command, cmd_foreverypart) )
+			nesting++;
+		node = sieve_ast_node_parent(node);
+	}
+
+	/* Enforce nesting limit
+	   NOTE: this only recognizes the foreverypart command as a loop; if
+	   new loop commands are introduced in the future, these must be 
+	   recognized somehow. */
+	if ( nesting + 1 > SIEVE_MAX_LOOP_DEPTH ) {
+		sieve_command_validate_error(valdtr, cmd,
+			"the nested foreverypart loop exceeds "
+			"the nesting limit (<= %u levels)",
+			SIEVE_MAX_LOOP_DEPTH);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_foreverypart_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	struct ext_foreverypart_loop *loop =
+		(struct ext_foreverypart_loop *)cmd->data;
+	sieve_size_t block_begin, loop_jump;
+
+	/* Emit FOREVERYPART_BEGIN operation */
+	sieve_operation_emit(cgenv->sblock,
+		cmd->ext, &foreverypart_begin_operation);
+
+	/* Emit exit address */
+	loop->exit_jumps = sieve_jumplist_create
+		(sieve_command_pool(cmd), cgenv->sblock);
+	sieve_jumplist_add(loop->exit_jumps,
+		sieve_binary_emit_offset(cgenv->sblock, 0));
+	block_begin = sieve_binary_block_get_size(cgenv->sblock);
+
+	/* Generate loop block */
+	if ( !sieve_generate_block(cgenv, cmd->ast_node) )
+		return FALSE;
+
+	/* Emit FOREVERYPART_END operation */
+	sieve_operation_emit(cgenv->sblock,
+		cmd->ext, &foreverypart_end_operation);
+	loop_jump = sieve_binary_block_get_size(cgenv->sblock);
+	i_assert(loop_jump > block_begin);
+	(void)sieve_binary_emit_offset
+		(cgenv->sblock, (loop_jump - block_begin));
+	
+	/* Resolve exit address */
+	sieve_jumplist_resolve(loop->exit_jumps);
+
+	return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_foreverypart_begin_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	unsigned int pc = *address;
+	sieve_offset_t offset;
+
+	sieve_code_dumpf(denv, "FOREVERYPART_BEGIN");
+	sieve_code_descend(denv);
+
+ 	if ( !sieve_binary_read_offset(denv->sblock, address, &offset) )
+		return FALSE;
+
+	sieve_code_dumpf(denv, "END: %d [%08x]", offset, pc + offset);
+	return TRUE;
+}
+
+static bool cmd_foreverypart_end_operation_dump
+(const struct sieve_dumptime_env *denv,
+	sieve_size_t *address ATTR_UNUSED)
+{
+	unsigned int pc = *address;
+	sieve_offset_t offset;
+
+	sieve_code_dumpf(denv, "FOREVERYPART_END");
+	sieve_code_descend(denv);
+
+	if ( !sieve_binary_read_offset(denv->sblock, address, &offset) )
+		return FALSE;
+
+	sieve_code_dumpf(denv, "BEGIN: -%d [%08x]", offset, pc - offset);
+	return TRUE;
+}
+
+/*
+ * Code execution
+ */
+
+static int cmd_foreverypart_begin_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	struct sieve_interpreter_loop *loop;
+	struct ext_foreverypart_runtime_loop *fploop, *sfploop;
+	unsigned int pc = *address;
+	sieve_offset_t offset;
+	sieve_size_t loop_end;
+	pool_t pool;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	if ( !sieve_binary_read_offset(renv->sblock, address, &offset) )
+	{
+		sieve_runtime_trace_error(renv, "invalid loop end offset");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	loop_end = pc + offset;
+
+	/*
+	 * Perform operation
+	 */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS,
+		"foreverypart loop begin");
+	sieve_runtime_trace_descend(renv);
+
+	sfploop = ext_foreverypart_runtime_loop_get_current(renv);
+
+	if ( (ret=sieve_interpreter_loop_start(renv->interp,
+		loop_end, &foreverypart_extension, &loop)) <= 0 )
+		return ret;
+
+	pool = sieve_interpreter_loop_get_pool(loop);
+	fploop = p_new(pool, struct ext_foreverypart_runtime_loop, 1);
+	
+	if ( sfploop == NULL ) {
+		if ( (ret=sieve_message_part_iter_init
+			(&fploop->part_iter, renv)) <= 0 )
+			return ret;
+	} else {
+		sieve_message_part_iter_children(&sfploop->part_iter,
+			&fploop->part_iter);
+	}
+	fploop->part = sieve_message_part_iter_current(&fploop->part_iter);
+	if (fploop->part != NULL) {
+		sieve_interpreter_loop_set_context(loop, (void*)fploop);
+	} else {
+		/* No children parts to iterate */
+		sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS,
+			"no children at this level");
+		sieve_interpreter_loop_break(renv->interp, loop);
+	} 
+	return SIEVE_EXEC_OK;
+}
+
+static int cmd_foreverypart_end_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	struct sieve_interpreter_loop *loop;
+	struct ext_foreverypart_runtime_loop *fploop;
+	unsigned int pc = *address;
+	sieve_offset_t offset;
+	sieve_size_t loop_begin;
+
+	/*
+	 * Read operands
+	 */
+
+	if ( !sieve_binary_read_offset(renv->sblock, address, &offset) )
+	{
+		sieve_runtime_trace_error(renv, "invalid loop begin offset");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	loop_begin = pc - offset;
+
+	/*
+	 * Perform operation
+	 */
+
+	sieve_runtime_trace(renv,
+		SIEVE_TRLVL_COMMANDS, "foreverypart loop end");
+	sieve_runtime_trace_descend(renv);
+
+	loop = sieve_interpreter_loop_get
+		(renv->interp, *address, &foreverypart_extension);
+	if ( loop == NULL ) {
+		sieve_runtime_trace_error(renv, "no matching loop found");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	fploop = (struct ext_foreverypart_runtime_loop *)
+		sieve_interpreter_loop_get_context(loop);
+	i_assert(fploop->part != NULL);
+	fploop->part = sieve_message_part_iter_next(&fploop->part_iter);
+	if ( fploop->part == NULL ) {
+		sieve_runtime_trace(renv,
+			SIEVE_TRLVL_COMMANDS, "no more message parts");
+		return sieve_interpreter_loop_break(renv->interp, loop);
+	}
+
+	sieve_runtime_trace(renv,
+		SIEVE_TRLVL_COMMANDS, "switched to next message part");
+	return sieve_interpreter_loop_next(renv->interp, loop, loop_begin);
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/mime/ext-extracttext.c
@@ -0,0 +1,130 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension extracttext
+ * ---------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5703, Section 7
+ * Implementation: full
+ * Status: experimental
+ *
+ */
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-actions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+
+#include "sieve-ext-variables.h"
+
+#include "ext-mime-common.h"
+
+/*
+ * Extension
+ */
+
+static bool ext_extracttext_load
+	(const struct sieve_extension *ext, void **context);
+static void ext_extracttext_unload
+	(const struct sieve_extension *ext);
+static bool ext_extracttext_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def extracttext_extension = {
+	.name = "extracttext",
+	.load = ext_extracttext_load,
+	.unload = ext_extracttext_unload,
+	.validator_load = ext_extracttext_validator_load,
+	SIEVE_EXT_DEFINE_OPERATION(extracttext_operation)
+};
+
+static bool ext_extracttext_load
+(const struct sieve_extension *ext, void **context)
+{
+	struct sieve_instance *svinst = ext->svinst;
+	struct ext_extracttext_context *ectx;
+
+	if ( *context != NULL )
+		ext_extracttext_unload(ext);
+
+	ectx = i_new(struct ext_extracttext_context, 1);
+	ectx->var_ext = sieve_ext_variables_get_extension(ext->svinst);
+	ectx->fep_ext = sieve_extension_register
+		(svinst, &foreverypart_extension, FALSE);
+	*context = (void *)ectx;
+	return TRUE;
+}
+
+static void ext_extracttext_unload
+(const struct sieve_extension *ext)
+{
+	struct ext_extracttext_context *ctx =
+		(struct ext_extracttext_context *) ext->context;
+
+	i_free(ctx);
+}
+
+/*
+ * Extension validation
+ */
+
+static bool ext_extracttext_validator_validate
+	(const struct sieve_extension *ext,
+		struct sieve_validator *valdtr, void *context,
+		struct sieve_ast_argument *require_arg,
+		bool required);
+
+const struct sieve_validator_extension
+extracttext_validator_extension = {
+	.ext = &extracttext_extension,
+	.validate = ext_extracttext_validator_validate
+};
+
+static bool ext_extracttext_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	/* Register validator extension to check for conflict with eextracttext */
+	sieve_validator_extension_register
+		(valdtr, ext, &extracttext_validator_extension, NULL);
+
+	/* Register new commands */
+	sieve_validator_register_command(valdtr, ext, &cmd_extracttext);
+
+	return TRUE;
+}
+
+static bool ext_extracttext_validator_validate
+(const struct sieve_extension *ext,
+	struct sieve_validator *valdtr, void *context ATTR_UNUSED,
+	struct sieve_ast_argument *require_arg,
+	bool required ATTR_UNUSED)
+{
+	struct ext_extracttext_context *ectx =
+		(struct ext_extracttext_context *)ext->context;
+
+	if ( ectx->var_ext == NULL ||
+		!sieve_ext_variables_is_active
+			(ectx->var_ext, valdtr) ) {
+		sieve_argument_validate_error(valdtr, require_arg,
+			"extracttext extension cannot be used "
+			"without variables extension");
+		return FALSE;
+	}
+	if ( ectx->fep_ext == NULL ||
+		!sieve_validator_extension_loaded
+	    (valdtr, ectx->fep_ext) ) {
+		sieve_argument_validate_error(valdtr, require_arg,
+			"extracttext extension cannot be used "
+			"without foreverypart extension");
+		return FALSE;
+	}
+
+	return TRUE;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/mime/ext-foreverypart.c
@@ -0,0 +1,62 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension foreverypart
+ * ----------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5703, Section 3
+ * Implementation: full
+ * Status: experimental
+ *
+ */
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-actions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+
+#include "ext-mime-common.h"
+
+/*
+ * Operations
+ */
+
+const struct sieve_operation_def *ext_foreverypart_operations[] = {
+	&foreverypart_begin_operation,
+	&foreverypart_end_operation,
+	&break_operation
+};
+
+/*
+ * Extension
+ */
+
+static bool ext_foreverypart_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def foreverypart_extension = {
+	.name = "foreverypart",
+	.validator_load = ext_foreverypart_validator_load,
+	SIEVE_EXT_DEFINE_OPERATIONS(ext_foreverypart_operations)
+};
+
+/*
+ * Extension validation
+ */
+
+static bool ext_foreverypart_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	/* Register new commands */
+	sieve_validator_register_command(valdtr, ext, &cmd_foreverypart);
+	sieve_validator_register_command(valdtr, ext, &cmd_break);
+
+	return TRUE;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/mime/ext-mime-common.c
@@ -0,0 +1,27 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-interpreter.h"
+
+#include "ext-mime-common.h"
+
+struct ext_foreverypart_runtime_loop *
+ext_foreverypart_runtime_loop_get_current
+(const struct sieve_runtime_env *renv)
+{
+	struct sieve_interpreter_loop *loop;
+	struct ext_foreverypart_runtime_loop *fploop;
+
+	loop = sieve_interpreter_loop_get_global
+		(renv->interp, NULL, &foreverypart_extension);
+	if ( loop == NULL ) {
+		fploop = NULL;
+	} else {
+		fploop = (struct ext_foreverypart_runtime_loop *)
+			sieve_interpreter_loop_get_context(loop);
+		i_assert(fploop->part != NULL);
+	}
+
+	return fploop;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/mime/ext-mime-common.h
@@ -0,0 +1,85 @@
+#ifndef EXT_FOREVERYPART_COMMON_H
+#define EXT_FOREVERYPART_COMMON_H
+
+#include "sieve-message.h"
+
+/*
+ * Extension
+ */
+
+struct ext_extracttext_context {
+	const struct sieve_extension *var_ext;
+	const struct sieve_extension *fep_ext;
+};
+
+extern const struct sieve_extension_def foreverypart_extension;
+extern const struct sieve_extension_def mime_extension;
+extern const struct sieve_extension_def extracttext_extension;
+
+/*
+ * Tagged arguments
+ */
+
+extern const struct sieve_argument_def mime_tag;
+extern const struct sieve_argument_def mime_anychild_tag;
+extern const struct sieve_argument_def mime_type_tag;
+extern const struct sieve_argument_def mime_subtype_tag;
+extern const struct sieve_argument_def mime_contenttype_tag;
+extern const struct sieve_argument_def mime_param_tag;
+
+/*
+ * Commands
+ */
+
+struct ext_foreverypart_loop {
+	const char *name;
+	struct sieve_jumplist *exit_jumps;
+};
+
+extern const struct sieve_command_def cmd_foreverypart;
+extern const struct sieve_command_def cmd_break;
+extern const struct sieve_command_def cmd_extracttext;
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def foreverypart_begin_operation;
+extern const struct sieve_operation_def foreverypart_end_operation;
+extern const struct sieve_operation_def break_operation;
+extern const struct sieve_operation_def extracttext_operation;
+
+enum ext_foreverypart_opcode {
+	EXT_FOREVERYPART_OPERATION_FOREVERYPART_BEGIN,
+	EXT_FOREVERYPART_OPERATION_FOREVERYPART_END,
+	EXT_FOREVERYPART_OPERATION_BREAK,
+};
+
+/*
+ * Operands
+ */
+
+enum ext_mime_option {
+	EXT_MIME_OPTION_NONE = 0,
+	EXT_MIME_OPTION_TYPE,
+	EXT_MIME_OPTION_SUBTYPE,
+	EXT_MIME_OPTION_CONTENTTYPE,
+	EXT_MIME_OPTION_PARAM
+};
+
+extern const struct sieve_operand_def mime_operand;
+
+/*
+ * Foreverypart loop
+ */
+
+struct ext_foreverypart_runtime_loop {
+	struct sieve_message_part_iter part_iter;
+	struct sieve_message_part *part;
+};
+
+struct ext_foreverypart_runtime_loop *
+ext_foreverypart_runtime_loop_get_current
+(const struct sieve_runtime_env *renv);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/mime/ext-mime.c
@@ -0,0 +1,77 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension mime
+ * --------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5703, Section 4
+ * Implementation: full
+ * Status: experimental
+ *
+ */
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-actions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-message.h"
+#include "sieve-result.h"
+
+#include "ext-mime-common.h"
+
+/*
+ * Extension
+ */
+
+static bool ext_mime_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def mime_extension = {
+	.name = "mime",
+	.validator_load = ext_mime_validator_load,
+	SIEVE_EXT_DEFINE_OPERAND(mime_operand)
+};
+
+/*
+ * Extension validation
+ */
+
+static bool ext_mime_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	/* Register :mime tag and friends with header, address and exists test
+	 * commands and we don't care whether these command are registered or
+	 * even whether these will be registered at all. The validator handles
+	 * either situation gracefully.
+	 */
+	sieve_validator_register_external_tag
+		(valdtr, "header", ext, &mime_tag, SIEVE_OPT_MESSAGE_OVERRIDE);
+	sieve_validator_register_external_tag
+		(valdtr, "header", ext, &mime_anychild_tag, 0);
+	sieve_validator_register_external_tag
+		(valdtr, "header", ext, &mime_type_tag, 0);
+	sieve_validator_register_external_tag
+		(valdtr, "header", ext, &mime_subtype_tag, 0);
+	sieve_validator_register_external_tag
+		(valdtr, "header", ext, &mime_contenttype_tag, 0);
+	sieve_validator_register_external_tag
+		(valdtr, "header", ext, &mime_param_tag, 0);
+
+	sieve_validator_register_external_tag
+		(valdtr, "address", ext, &mime_tag, SIEVE_OPT_MESSAGE_OVERRIDE);
+	sieve_validator_register_external_tag
+		(valdtr, "address", ext, &mime_anychild_tag, 0);
+
+	sieve_validator_register_external_tag
+		(valdtr, "exists", ext, &mime_tag, SIEVE_OPT_MESSAGE_OVERRIDE);
+	sieve_validator_register_external_tag
+		(valdtr, "exists", ext, &mime_anychild_tag, 0);
+
+	return TRUE;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/mime/tag-mime.c
@@ -0,0 +1,757 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "rfc822-parser.h"
+#include "rfc2231-parser.h"
+#include "mail-storage.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-binary.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+#include "sieve-result.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+
+#include "ext-mime-common.h"
+
+/*
+ * Tagged argument
+ */
+
+static bool tag_mime_validate
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+static bool tag_mime_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+    struct sieve_command *context);
+
+const struct sieve_argument_def mime_tag = {
+	.identifier = "mime",
+	.validate = tag_mime_validate,
+	.generate = tag_mime_generate
+};
+
+static bool tag_mime_option_validate
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+
+const struct sieve_argument_def mime_anychild_tag = {
+	.identifier = "anychild",
+	.validate = tag_mime_option_validate
+};
+
+const struct sieve_argument_def mime_type_tag = {
+	.identifier = "type",
+	.validate = tag_mime_option_validate
+};
+
+const struct sieve_argument_def mime_subtype_tag = {
+	.identifier = "subtype",
+	.validate = tag_mime_option_validate
+};
+
+const struct sieve_argument_def mime_contenttype_tag = {
+	.identifier = "contenttype",
+	.validate = tag_mime_option_validate
+};
+
+const struct sieve_argument_def mime_param_tag = {
+	.identifier = "param",
+	.validate = tag_mime_option_validate
+};
+
+/*
+ * Header override
+ */
+
+static bool svmo_mime_dump_context
+	(const struct sieve_message_override *svmo,
+		const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int svmo_mime_read_context
+	(const struct sieve_message_override *svmo,
+		const struct sieve_runtime_env *renv, sieve_size_t *address,
+		void **ho_context);
+static int svmo_mime_header_override
+	(const struct sieve_message_override *svmo,
+		const struct sieve_runtime_env *renv,
+		bool mime_decode, struct sieve_stringlist **headers);
+
+const struct sieve_message_override_def mime_header_override = {
+	SIEVE_OBJECT("mime", &mime_operand, 0),
+	.sequence = 0, /* Completely replace header source */
+	.dump_context = svmo_mime_dump_context,
+	.read_context = svmo_mime_read_context,
+	.header_override = svmo_mime_header_override
+};
+
+/*
+ * Operand
+ */
+
+static const struct sieve_extension_objects ext_header_overrides =
+	SIEVE_EXT_DEFINE_MESSAGE_OVERRIDE(mime_header_override);
+
+const struct sieve_operand_def mime_operand = {
+	.name = "mime operand",
+	.ext_def = &mime_extension,
+	.class = &sieve_message_override_operand_class,
+	.interface = &ext_header_overrides
+};
+
+/*
+ * Tag data
+ */
+
+struct tag_mime_data {
+	enum ext_mime_option mimeopt;
+	struct sieve_ast_argument *param_arg;
+	bool anychild:1;
+};
+
+/*
+ * Tag validation
+ */
+
+static struct tag_mime_data *
+tag_mime_get_data(struct sieve_command *cmd,
+	struct sieve_ast_argument *tag)
+{
+	struct tag_mime_data *data;
+
+	if (tag->argument->data == NULL) {
+		data = p_new(sieve_command_pool(cmd), struct tag_mime_data, 1);
+		tag->argument->data = (void *)data;
+	} else {
+		data = (struct tag_mime_data *)tag->argument->data;
+	}
+
+	return data;
+}
+
+static bool tag_mime_validate
+(struct sieve_validator *valdtr ATTR_UNUSED,
+	struct sieve_ast_argument **arg, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *tag = *arg;
+
+	/* Skip the tag itself */
+	*arg = sieve_ast_argument_next(*arg);
+
+	(void)tag_mime_get_data(cmd, tag);
+	return TRUE;
+}
+
+static bool tag_mime_option_validate
+(struct sieve_validator *valdtr ATTR_UNUSED,
+	struct sieve_ast_argument **arg, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *tag = *arg;
+	struct sieve_ast_argument *mime_arg;
+	struct tag_mime_data *data;
+
+	i_assert(tag != NULL);
+
+	/* Detach tag itself */
+	*arg = sieve_ast_arguments_detach(*arg,1);
+
+	/* Find required ":mime" tag */
+	mime_arg = sieve_command_find_argument(cmd, &mime_tag);
+	if ( mime_arg == NULL ) {
+		sieve_argument_validate_error(valdtr, tag,
+			"the :%s tag for the %s %s cannot be specified "
+			"without the :mime tag", sieve_ast_argument_tag(tag),
+			sieve_command_identifier(cmd), sieve_command_type_name(cmd));
+		return FALSE;
+	}
+
+	/* Annotate ":mime" tag with the data provided by this option tag */
+	data = tag_mime_get_data(cmd, mime_arg);
+	if ( sieve_argument_is(tag, mime_anychild_tag) )
+		data->anychild = TRUE;
+	else 	{
+		if ( data->mimeopt != EXT_MIME_OPTION_NONE ) {
+			sieve_argument_validate_error(valdtr, *arg,
+				"the :type, :subtype, :contenttype, and :param "
+				"arguments for the %s test are mutually exclusive, "
+				"but more than one was specified",
+				sieve_command_identifier(cmd));
+			return FALSE;
+		}
+		if ( sieve_argument_is(tag, mime_type_tag) )
+			data->mimeopt = EXT_MIME_OPTION_TYPE;
+		else 	if ( sieve_argument_is(tag, mime_subtype_tag) )
+			data->mimeopt = EXT_MIME_OPTION_SUBTYPE;
+		else 	if ( sieve_argument_is(tag, mime_contenttype_tag) )
+			data->mimeopt = EXT_MIME_OPTION_CONTENTTYPE;
+		else 	if ( sieve_argument_is(tag, mime_param_tag) ) {
+			/* Check syntax:
+			 *   ":param" <param-list: string-list>
+			 */
+			if ( !sieve_validate_tag_parameter
+				(valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING_LIST, FALSE) ) {
+				return FALSE;
+			}
+
+			data->mimeopt = EXT_MIME_OPTION_PARAM;
+			data->param_arg = *arg;
+
+			/* Detach parameter */
+			*arg = sieve_ast_arguments_detach(*arg,1);
+		} else
+			i_unreached();
+	}
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool tag_mime_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+	struct sieve_command *cmd)
+{
+	struct tag_mime_data *data =
+		(struct tag_mime_data *)arg->argument->data;
+
+	if ( sieve_ast_argument_type(arg) != SAAT_TAG )
+		return FALSE;
+
+	sieve_opr_message_override_emit
+		(cgenv->sblock, arg->argument->ext, &mime_header_override);
+
+	(void)sieve_binary_emit_byte
+		(cgenv->sblock, ( data->anychild ? 1 : 0 ));
+	(void)sieve_binary_emit_byte
+		(cgenv->sblock, data->mimeopt);
+	if ( data->mimeopt == EXT_MIME_OPTION_PARAM &&
+		!sieve_generate_argument(cgenv, data->param_arg, cmd) )
+		return FALSE;
+	return TRUE;
+}
+
+/*
+ * Content-type stringlist
+ */
+
+enum content_type_part {
+	CONTENT_TYPE_PART_NONE = 0,
+	CONTENT_TYPE_PART_TYPE,
+	CONTENT_TYPE_PART_SUBTYPE,
+	CONTENT_TYPE_PART_CONTENTTYPE,
+};
+
+/* Object */
+
+static int content_header_stringlist_next_item
+	(struct sieve_stringlist *_strlist, string_t **str_r);
+static void content_header_stringlist_reset
+	(struct sieve_stringlist *_strlist);
+static int content_header_stringlist_get_length
+	(struct sieve_stringlist *_strlist);
+static void content_header_stringlist_set_trace
+	(struct sieve_stringlist *strlist, bool trace);
+
+struct content_header_stringlist {
+	struct sieve_stringlist strlist;
+
+	struct sieve_header_list *source;
+
+	enum ext_mime_option option;
+	const char *const *params;
+
+	const char *const *param_values;
+};
+
+static struct sieve_stringlist *content_header_stringlist_create
+(const struct sieve_runtime_env *renv,
+	struct sieve_header_list *source,
+	enum ext_mime_option option, const char *const *params)
+{
+	struct content_header_stringlist *strlist;
+
+	strlist = t_new(struct content_header_stringlist, 1);
+	strlist->strlist.runenv = renv;
+	strlist->strlist.exec_status = SIEVE_EXEC_OK;
+	strlist->strlist.next_item = content_header_stringlist_next_item;
+	strlist->strlist.reset = content_header_stringlist_reset;
+	strlist->strlist.set_trace = content_header_stringlist_set_trace;
+	strlist->source = source;
+	strlist->option = option;
+	strlist->params = params;
+
+	if ( option != EXT_MIME_OPTION_PARAM ) {
+		/* One header can have multiple parameters, so we cannot rely
+		   on the source length for the :param option. */
+		strlist->strlist.get_length = content_header_stringlist_get_length;
+	}
+
+	return &strlist->strlist;
+}
+
+/* Implementation */
+
+static inline int _decode_hex_digit(const unsigned char digit)
+{
+	switch ( digit ) {
+	case '0': case '1': case '2': case '3': case '4':
+	case '5': case '6': case '7': case '8': case '9':
+		return digit - '0';
+
+	case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+		return digit - 'a' + 0x0a;
+
+	case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+		return digit - 'A' + 0x0A;
+	}
+	return -1;
+}
+
+static string_t *
+content_type_param_decode(const char *value)
+{
+	const unsigned char *p, *plast;
+
+	string_t *str = t_str_new(64);
+	plast = p = (const unsigned char *)value;
+	while ( *p != '\0' ) {
+		unsigned char ch;
+		int digit;
+
+		if ( *p == '%' ) {
+			if ( p - plast > 0 )
+				str_append_data(str, plast, (p - plast));
+			p++;
+			if ( *p == '\0' || (digit=_decode_hex_digit(*p)) < 0 )
+				return NULL;
+			ch = (unsigned char)digit;
+			p++;
+			if ( *p == '\0' || (digit=_decode_hex_digit(*p)) < 0 )
+				return NULL;
+			ch = (ch << 4) + (unsigned char)digit;
+			str_append_data(str, &ch, 1);
+			plast = p + 1;
+		}
+		p++;
+	}
+	if ( p - plast > 0 )
+		str_append_data(str, plast, (p - plast));
+	return str;
+}
+
+static string_t *
+content_type_param_next(struct content_header_stringlist *strlist)
+{	
+	const struct sieve_runtime_env *renv = strlist->strlist.runenv;
+	const char *const *values = strlist->param_values;
+	bool trace = strlist->strlist.trace;
+
+	i_assert( strlist->params != NULL );
+
+	/* Iterate over all parsed parameter values */
+	for ( ; *values != NULL; values += 2 ) {
+		const char *const *params = strlist->params;
+		const char *name = values[0], *value = values[1];
+		size_t nlen = strlen(name);
+
+		/* Iterate over all interesting parameter names */
+		for ( ; *params != NULL; params++ ) {
+			size_t plen = strlen(*params);
+
+			if ( plen != nlen &&
+				(nlen != plen + 1 || name[nlen-1] != '*') )
+				continue;
+
+			if ( plen == nlen ) {
+				if ( strcasecmp(name, *params) == 0 ) {
+					/* Return raw value */
+					if ( trace ) {
+						sieve_runtime_trace(renv, 0,
+							"found mime parameter `%s' in mime header",
+							*params);
+					}
+
+					strlist->param_values = values + 2;
+					return t_str_new_const(value, strlen(value));
+				}
+			} else {
+				if ( trace ) {
+					sieve_runtime_trace(renv, 0,
+						"found encoded parameter `%s' in mime header",
+						*params);
+				}
+
+				if ( strncasecmp(name, *params, plen) == 0 ) {
+					string_t *result = NULL;
+
+					strlist->param_values = values + 2;
+
+					/* Decode value first */
+					// FIXME: transcode charset
+					value = strchr(value, '\'');
+					if (value != NULL)
+						value = strchr(value+1, '\'');
+					if (value != NULL)
+						result = content_type_param_decode(value + 1);
+					if (result == NULL)
+						strlist->param_values = NULL;
+					return result;
+				}
+			}
+		}
+	}
+
+	strlist->param_values = NULL;
+	return NULL;
+}
+
+// FIXME: not too happy with the use of string_t like this.
+// Sieve should have a special runtime string type (TODO)
+static string_t *
+content_header_parse(struct content_header_stringlist *strlist,
+	const char *hdr_name, string_t *str)
+{
+	const struct sieve_runtime_env *renv = strlist->strlist.runenv;
+	bool trace = strlist->strlist.trace;
+	struct rfc822_parser_context parser;
+	const char *type, *p;
+	bool is_ctype = FALSE;
+	string_t *content;
+
+	if ( strlist->option == EXT_MIME_OPTION_NONE )
+		return str;
+
+	if ( strcasecmp(hdr_name, "content-type") == 0 )
+		is_ctype = TRUE;
+	else if ( strcasecmp(hdr_name, "content-disposition") != 0 ) {
+		if ( trace ) {
+			sieve_runtime_trace(renv, 0,
+				"non-mime header yields empty string");
+		}
+		return t_str_new(0);
+	}
+
+	/* Initialize parsing */
+	rfc822_parser_init(&parser, str_data(str), str_len(str), NULL);
+	(void)rfc822_skip_lwsp(&parser);
+
+	/* Parse content type/disposition */
+	content = t_str_new(64);
+	if ( is_ctype ){ 
+		if (rfc822_parse_content_type(&parser, content) < 0) {
+			str_truncate(content, 0);
+			return content;
+		}
+	} else {
+		if (rfc822_parse_mime_token(&parser, content) < 0) {
+			str_truncate(content, 0);
+			return content;
+		}
+	}
+
+	/* Content-type value must end here, otherwise it is invalid after all */
+	(void)rfc822_skip_lwsp(&parser);
+	if ( parser.data != parser.end && *parser.data != ';' ) {
+		str_truncate(content, 0);
+		return content;
+	}
+
+	if ( strlist->option == EXT_MIME_OPTION_PARAM ) {
+		string_t *param_val;
+
+		/* MIME parameter */
+		i_assert( strlist->params != NULL );
+
+		// FIXME: not very nice when multiple parameters in the same header
+		// are queried in successive tests.
+		str_truncate(content, 0);
+		rfc2231_parse(&parser, &strlist->param_values);
+
+		param_val = content_type_param_next(strlist);
+		if ( param_val != NULL )
+			content = param_val;
+	} else {
+		/* Get :type/:subtype:/:contenttype value */
+		type = str_c(content);
+		p = strchr(type, '/');
+		switch ( strlist->option ) {
+		case EXT_MIME_OPTION_TYPE:
+			if ( trace ) {
+				sieve_runtime_trace(renv, 0,
+					"extracted MIME type");
+			}
+			if ( p != NULL ) {
+				i_assert( is_ctype );
+				str_truncate(content, (p - type));
+			}
+			break;	
+		case EXT_MIME_OPTION_SUBTYPE:
+			if ( p == NULL ) {
+				i_assert( !is_ctype );
+				if ( trace ) {
+					sieve_runtime_trace(renv, 0,
+						"no MIME sub-type for content-disposition");
+				}
+				str_truncate(content, 0);
+				break;
+			}
+
+			i_assert( is_ctype );
+			if ( trace ) {
+				sieve_runtime_trace(renv, 0,
+					"extracted MIME sub-type");
+			}
+			str_delete(content, 0, (p - type) + 1);
+			break;	
+		case EXT_MIME_OPTION_CONTENTTYPE:
+			sieve_runtime_trace(renv, 0,
+				"extracted full MIME contenttype");
+			break;
+		default:
+			break;
+		}
+	}
+
+	/* Success */
+	return content;
+}
+
+static int content_header_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+	struct content_header_stringlist *strlist =
+		(struct content_header_stringlist *)_strlist;
+	const char *hdr_name;
+	int ret;
+
+	if ( strlist->param_values != NULL ) {
+		string_t *param_val;
+
+		i_assert( strlist->option == EXT_MIME_OPTION_PARAM );
+		param_val = content_type_param_next(strlist);
+		if ( param_val != NULL ) {
+			*str_r = param_val;
+			return 1;
+		}
+	}
+
+	if ( (ret=sieve_header_list_next_item
+		(strlist->source, &hdr_name, str_r)) <= 0 ) {
+		if (ret < 0) {
+			_strlist->exec_status =
+				strlist->source->strlist.exec_status;
+		}
+		return ret;
+	}
+
+	*str_r = content_header_parse(strlist, hdr_name, *str_r);
+	return 1;
+}
+
+static void content_header_stringlist_reset
+(struct sieve_stringlist *_strlist)
+{
+	struct content_header_stringlist *strlist =
+		(struct content_header_stringlist *)_strlist;
+	sieve_header_list_reset(strlist->source);
+}
+
+static int content_header_stringlist_get_length
+(struct sieve_stringlist *_strlist)
+{
+	struct content_header_stringlist *strlist =
+		(struct content_header_stringlist *)_strlist;
+	return sieve_header_list_get_length(strlist->source);
+}
+
+static void content_header_stringlist_set_trace
+(struct sieve_stringlist *_strlist, bool trace)
+{
+	struct content_header_stringlist *strlist =
+		(struct content_header_stringlist *)_strlist;
+	sieve_header_list_set_trace(strlist->source, trace);
+}
+
+/*
+ * Header override implementation
+ */
+
+/* Context data */
+
+struct svmo_mime_context {
+	enum ext_mime_option mimeopt;
+	const char *const *params;
+	bool anychild:1;
+};
+
+/* Context coding */
+
+static bool svmo_mime_dump_context
+(const struct sieve_message_override *svmo ATTR_UNUSED,
+	const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	unsigned int anychild, mimeopt;
+
+	if ( !sieve_binary_read_byte(denv->sblock, address, &anychild) )
+		return FALSE;
+	if ( anychild > 0 )
+		sieve_code_dumpf(denv, "anychild");
+
+	if ( !sieve_binary_read_byte(denv->sblock, address, &mimeopt) )
+		return FALSE;
+
+	switch ( mimeopt ) {
+	case EXT_MIME_OPTION_NONE:
+		break;
+	case EXT_MIME_OPTION_TYPE:
+		sieve_code_dumpf(denv, "option: type");
+		break;
+	case EXT_MIME_OPTION_SUBTYPE:
+		sieve_code_dumpf(denv, "option: subtype");
+		break;
+	case EXT_MIME_OPTION_CONTENTTYPE:
+		sieve_code_dumpf(denv, "option: contenttype");
+		break;
+	case EXT_MIME_OPTION_PARAM:
+		sieve_code_dumpf(denv, "option: param");
+		sieve_code_descend(denv);
+		if ( !sieve_opr_stringlist_dump(denv, address, "param-list") )
+			return FALSE;
+		sieve_code_ascend(denv);
+		break;
+	default:
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static int svmo_mime_read_context
+(const struct sieve_message_override *svmo ATTR_UNUSED,
+	const struct sieve_runtime_env *renv, sieve_size_t *address,
+	void **ho_context)
+{
+	pool_t pool = sieve_result_pool(renv->result); // FIXME: investigate
+	struct svmo_mime_context *ctx;
+	unsigned int anychild = 0, mimeopt = EXT_MIME_OPTION_NONE;
+	struct sieve_stringlist *param_list = NULL;
+	int ret;
+
+	if ( !sieve_binary_read_byte
+		(renv->sblock, address, &anychild) ) {
+		sieve_runtime_trace_error(renv,
+			"anychild: invalid byte");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	if ( !sieve_binary_read_byte
+		(renv->sblock, address, &mimeopt) ||
+		mimeopt > EXT_MIME_OPTION_PARAM ) {
+		sieve_runtime_trace_error(renv,
+			"option: invalid mime option code");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	if ( mimeopt == EXT_MIME_OPTION_PARAM &&
+		(ret=sieve_opr_stringlist_read
+			(renv, address, "param-list", &param_list)) <= 0 )
+		return ret;
+
+	ctx = p_new(pool, struct svmo_mime_context, 1);
+	ctx->anychild = (anychild == 0 ? FALSE : TRUE);
+	ctx->mimeopt = (enum ext_mime_option)mimeopt;
+
+	if ( param_list != NULL && sieve_stringlist_read_all
+		(param_list, pool, &ctx->params) < 0 ) {
+		sieve_runtime_trace_error(renv,
+			"failed to read param-list operand");
+		return param_list->exec_status;
+	}
+
+	*ho_context = (void *) ctx;
+	return SIEVE_EXEC_OK;
+}
+
+/* Override */
+
+static int svmo_mime_header_override
+(const struct sieve_message_override *svmo,
+	const struct sieve_runtime_env *renv, bool mime_decode,
+	struct sieve_stringlist **headers_r)
+{
+	struct svmo_mime_context *ctx =
+		(struct svmo_mime_context *)svmo->context;
+	struct ext_foreverypart_runtime_loop *sfploop;
+	struct sieve_header_list *headers;
+	struct sieve_stringlist *values;
+	int ret;
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
+		"header mime override:");
+	sieve_runtime_trace_descend(renv);
+
+	if ( ctx->anychild ) {
+		sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
+			"headers from current mime part and children");
+	} else {
+		sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
+			"headers from current mime part");
+	}
+
+	sfploop = ext_foreverypart_runtime_loop_get_current(renv);
+	if ( sfploop != NULL ) {
+		headers = sieve_mime_header_list_create
+			(renv, *headers_r, &sfploop->part_iter,
+				mime_decode, ctx->anychild);
+	} else if ( ctx->anychild ) {
+		struct sieve_message_part_iter part_iter;
+
+		if ( (ret=sieve_message_part_iter_init
+			(&part_iter, renv)) <= 0 )
+			return ret;
+
+		headers = sieve_mime_header_list_create
+			(renv, *headers_r, &part_iter, mime_decode, TRUE);
+	} else {
+		headers = sieve_message_header_list_create
+			(renv, *headers_r, mime_decode);
+	}
+	values = &headers->strlist;
+
+	switch ( ctx->mimeopt ) {
+	case EXT_MIME_OPTION_NONE:
+		break;
+	case EXT_MIME_OPTION_TYPE:
+		sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
+			"extract mime type from header value");
+		break;
+	case EXT_MIME_OPTION_SUBTYPE:
+		sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
+			"extract mime subtype from header value");
+		break;
+	case EXT_MIME_OPTION_CONTENTTYPE:
+		sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
+			"extract mime contenttype from header value");
+		break;
+	case EXT_MIME_OPTION_PARAM:
+		sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
+			"extract mime parameters from header value");
+		break;
+	default:
+		i_unreached();
+	}
+
+	if ( ctx->mimeopt != EXT_MIME_OPTION_NONE ) {
+		values = content_header_stringlist_create
+			(renv, headers, ctx->mimeopt, ctx->params);
+	}
+	*headers_r = values;
+
+	sieve_runtime_trace_ascend(renv);
+	return SIEVE_EXEC_OK;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/notify/Makefile.am
@@ -0,0 +1,20 @@
+noinst_LTLIBRARIES = libsieve_ext_notify.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../.. \
+	-I$(srcdir)/../../util \
+	$(LIBDOVECOT_INCLUDE)
+
+commands = \
+	cmd-notify.c \
+	cmd-denotify.c
+
+libsieve_ext_notify_la_SOURCES = \
+	ext-notify.c \
+	ext-notify-common.c \
+	$(commands)
+
+noinst_HEADERS = \
+	ext-notify-common.h \
+	ext-notify-limits.h
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/notify/cmd-denotify.c
@@ -0,0 +1,389 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-ast.h"
+#include "sieve-commands.h"
+#include "sieve-match-types.h"
+#include "sieve-comparators.h"
+#include "sieve-match.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-result.h"
+
+#include "ext-notify-common.h"
+
+/*
+ * Denotify command
+ *
+ * Syntax:
+ *   denotify [MATCH-TYPE string] [<":low" / ":normal" / ":high">]
+ */
+
+static bool cmd_denotify_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool cmd_denotify_pre_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_denotify_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_denotify_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+const struct sieve_command_def cmd_denotify = {
+	.identifier = "denotify",
+	.type = SCT_COMMAND,
+	.positional_args = 0,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = cmd_denotify_registered,
+	.pre_validate = cmd_denotify_pre_validate,
+	.validate = cmd_denotify_validate,
+	.generate = cmd_denotify_generate
+};
+
+/*
+ * Tagged arguments
+ */
+
+/* Forward declarations */
+
+static bool tag_match_type_is_instance_of
+	(struct sieve_validator *validator, struct sieve_command *cmd,
+		const struct sieve_extension *ext, const char *identifier, void **data);
+static bool tag_match_type_validate
+	(struct sieve_validator *validator, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+
+/* Argument object */
+
+const struct sieve_argument_def denotify_match_tag = {
+	.identifier = "MATCH-TYPE-STRING",
+	.is_instance_of = tag_match_type_is_instance_of,
+	.validate = tag_match_type_validate
+};
+
+/* Codes for optional operands */
+
+enum cmd_denotify_optional {
+	OPT_END,
+	OPT_IMPORTANCE,
+	OPT_MATCH_TYPE,
+	OPT_MATCH_KEY
+};
+
+/*
+ * Denotify operation
+ */
+
+static bool cmd_denotify_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_denotify_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def denotify_operation = {
+	.mnemonic = "DENOTIFY",
+	.ext_def = &notify_extension,
+	.code = EXT_NOTIFY_OPERATION_DENOTIFY,
+	.dump = cmd_denotify_operation_dump,
+	.execute = cmd_denotify_operation_execute
+};
+
+/*
+ * Command validation context
+ */
+
+struct cmd_denotify_context_data {
+	struct sieve_ast_argument *match_key_arg;
+};
+
+/*
+ * Tag validation
+ */
+
+static bool tag_match_type_is_instance_of
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+	const struct sieve_extension *ext, const char *identifier, void **data)
+{
+	return match_type_tag.is_instance_of(valdtr, cmd, ext, identifier, data);
+}
+
+static bool tag_match_type_validate
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	struct cmd_denotify_context_data *cmd_data =
+        (struct cmd_denotify_context_data *) cmd->data;
+	struct sieve_ast_argument *tag = *arg;
+
+	i_assert(tag != NULL);
+
+	if ( !match_type_tag.validate(valdtr, arg, cmd) )
+		return FALSE;
+
+	if ( *arg == NULL ) {
+		sieve_argument_validate_error(valdtr, tag,
+			"the MATCH-TYPE argument (:%s) for the denotify command requires "
+			"an additional key-string parameter, but no more arguments were found",
+			sieve_ast_argument_tag(tag));
+		return FALSE;
+	}
+
+	if ( sieve_ast_argument_type(*arg) != SAAT_STRING )
+	{
+		sieve_argument_validate_error(valdtr, *arg,
+			"the MATCH-TYPE argument (:%s) for the denotify command requires "
+			"an additional key-string parameter, but %s was found",
+			sieve_ast_argument_tag(tag), sieve_ast_argument_name(*arg));
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, cmd, *arg, FALSE) )
+		return FALSE;
+
+	tag->argument->def = &match_type_tag;
+	tag->argument->ext = NULL;
+
+	(*arg)->argument->id_code = OPT_MATCH_KEY;
+	cmd_data->match_key_arg = *arg;
+
+	*arg = sieve_ast_argument_next(*arg);
+
+	return TRUE;
+}
+
+/*
+ * Command registration
+ */
+
+static bool cmd_denotify_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg)
+{
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &denotify_match_tag, OPT_MATCH_TYPE);
+
+	ext_notify_register_importance_tags(valdtr, cmd_reg, ext, OPT_IMPORTANCE);
+
+	return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+static bool cmd_denotify_pre_validate
+(struct sieve_validator *valdtr ATTR_UNUSED,
+	struct sieve_command *cmd)
+{
+	struct cmd_denotify_context_data *ctx_data;
+
+	/* Assign context */
+	ctx_data = p_new(sieve_command_pool(cmd),
+		struct cmd_denotify_context_data, 1);
+	cmd->data = (void *) ctx_data;
+
+	return TRUE;
+}
+
+static bool cmd_denotify_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+    struct cmd_denotify_context_data *ctx_data =
+        (struct cmd_denotify_context_data *) cmd->data;
+	struct sieve_ast_argument *key_arg = ctx_data->match_key_arg;
+	const struct sieve_match_type mcht_default =
+		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+	const struct sieve_comparator cmp_default =
+		SIEVE_COMPARATOR_DEFAULT(i_octet_comparator);
+
+	if ( key_arg != NULL ) {
+		if ( !sieve_match_type_validate
+			(valdtr, cmd, key_arg, &mcht_default, &cmp_default) )
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_denotify_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	sieve_operation_emit(cgenv->sblock, cmd->ext, &denotify_operation);
+
+	/* Generate arguments */
+	return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_denotify_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	const struct sieve_operation *op = denv->oprtn;
+	int opt_code = 0;
+
+	sieve_code_dumpf(denv, "%s", sieve_operation_mnemonic(op));
+	sieve_code_descend(denv);
+
+	for (;;) {
+		int opt;
+		bool opok = TRUE;
+
+		if ( (opt=sieve_opr_optional_dump(denv, address, &opt_code)) < 0 )
+			return FALSE;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_MATCH_KEY:
+			opok = sieve_opr_string_dump(denv, address, "key-string");
+			break;
+		case OPT_MATCH_TYPE:
+			opok = sieve_opr_match_type_dump(denv, address);
+			break;
+		case OPT_IMPORTANCE:
+			opok = sieve_opr_number_dump(denv, address, "importance");
+			break;
+		default:
+			return FALSE;
+		}
+
+		if ( !opok ) return FALSE;
+	}
+
+	return TRUE;
+}
+
+/*
+ * Code execution
+ */
+
+static int cmd_denotify_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	int opt_code = 0;
+	struct sieve_match_type mcht =
+		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+	const struct sieve_comparator cmp =
+		SIEVE_COMPARATOR_DEFAULT(i_octet_comparator);
+	struct sieve_stringlist *match_key = NULL;
+	sieve_number_t importance = 0;
+	struct sieve_match_context *mctx;
+	struct sieve_result_iterate_context *rictx;
+	const struct sieve_action *action;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Optional operands */
+
+	for (;;) {
+		int opt;
+
+		if ( (opt=sieve_opr_optional_read(renv, address, &opt_code)) < 0 )
+			return SIEVE_EXEC_BIN_CORRUPT;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_MATCH_TYPE:
+			ret = sieve_opr_match_type_read(renv, address, &mcht);
+			break;
+		case OPT_MATCH_KEY:
+			ret = sieve_opr_stringlist_read(renv, address, "match key", &match_key);
+			break;
+		case OPT_IMPORTANCE:
+			ret = sieve_opr_number_read(renv, address, "importance", &importance);
+			break;
+		default:
+			sieve_runtime_trace_error(renv, "unknown optional operand");
+			return SIEVE_EXEC_BIN_CORRUPT;
+		}
+
+		if ( ret <= 0 ) return ret;
+	}
+
+	/*
+	 * Perform operation
+	 */
+
+	/* Enforce 0 < importance < 4 (just to be sure) */
+
+	if ( importance < 1 )
+		importance = 1;
+	else if ( importance > 3 )
+		importance = 3;
+
+	/* Trace */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS, "denotify action");
+
+	/* Either do string matching or just kill all notify actions */
+	if ( match_key != NULL ) {
+		/* Initialize match */
+		mctx = sieve_match_begin(renv, &mcht, &cmp);
+
+		/* Iterate through all notify actions and delete those that match */
+		rictx = sieve_result_iterate_init(renv->result);
+
+		while ( (action=sieve_result_iterate_next(rictx, NULL)) != NULL ) {
+			if ( sieve_action_is(action, act_notify_old) ) {
+				struct ext_notify_action *nact =
+					(struct ext_notify_action *) action->context;
+
+				if ( importance == 0 || nact->importance == importance ) {
+					int match;
+
+					if ( (match=sieve_match_value
+						(mctx, nact->id, strlen(nact->id), match_key)) < 0 )
+						break;
+
+					if ( match > 0 )
+						sieve_result_iterate_delete(rictx);
+				}
+			}
+		}
+
+		/* Finish match */
+		if ( sieve_match_end(&mctx, &ret) < 0 )
+			return ret;
+
+	} else {
+		/* Delete all notify actions */
+		rictx = sieve_result_iterate_init(renv->result);
+
+		while ( (action=sieve_result_iterate_next(rictx, NULL)) != NULL ) {
+
+			if ( sieve_action_is(action, act_notify_old) ) {
+				struct ext_notify_action *nact =
+					(struct ext_notify_action *) action->context;
+
+				if ( importance == 0 || nact->importance == importance )
+					sieve_result_iterate_delete(rictx);
+			}
+		}
+	}
+
+	return SIEVE_EXEC_OK;
+}
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/notify/cmd-notify.c
@@ -0,0 +1,849 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "ioloop.h"
+#include "str-sanitize.h"
+#include "ostream.h"
+#include "message-date.h"
+#include "mail-storage.h"
+
+#include "rfc2822.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-result.h"
+#include "sieve-address.h"
+#include "sieve-message.h"
+#include "sieve-smtp.h"
+
+#include "ext-notify-common.h"
+#include "ext-notify-limits.h"
+
+#include <ctype.h>
+
+/* Notify command (DEPRECATED)
+ *
+ * Syntax:
+ *   notify [":method" string] [":id" string] [":options" string-list]
+ *          [<":low" / ":normal" / ":high">] ["message:" string]
+ *
+ */
+
+static bool cmd_notify_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool cmd_notify_pre_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_notify_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_notify_generate
+	(const struct sieve_codegen_env *cgenv,
+		struct sieve_command *ctx);
+
+const struct sieve_command_def cmd_notify_old = {
+	.identifier = "notify",
+	.type = SCT_COMMAND,
+	.positional_args = 0,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = cmd_notify_registered,
+	.pre_validate = cmd_notify_pre_validate,
+	.validate = cmd_notify_validate,
+	.generate = cmd_notify_generate
+};
+
+/*
+ * Tagged arguments
+ */
+
+/* Forward declarations */
+
+static bool cmd_notify_validate_string_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+static bool cmd_notify_validate_stringlist_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+
+/* Argument objects */
+
+static const struct sieve_argument_def notify_method_tag = {
+	.identifier = "method",
+	.validate = cmd_notify_validate_string_tag
+};
+
+static const struct sieve_argument_def notify_options_tag = {
+	.identifier = "options",
+	.validate = cmd_notify_validate_stringlist_tag
+};
+
+static const struct sieve_argument_def notify_id_tag = {
+	.identifier = "id",
+	.validate = cmd_notify_validate_string_tag
+};
+
+static const struct sieve_argument_def notify_message_tag = {
+	.identifier = "message",
+	.validate = cmd_notify_validate_string_tag
+};
+
+/*
+ * Notify operation
+ */
+
+static bool cmd_notify_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_notify_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def notify_old_operation = {
+	.mnemonic = "NOTIFY",
+	.ext_def = &notify_extension,
+	.code = EXT_NOTIFY_OPERATION_NOTIFY,
+	.dump = cmd_notify_operation_dump,
+	.execute = cmd_notify_operation_execute
+};
+
+/* Codes for optional operands */
+
+enum cmd_notify_optional {
+  OPT_END,
+  OPT_MESSAGE,
+  OPT_IMPORTANCE,
+  OPT_OPTIONS,
+  OPT_ID
+};
+
+/*
+ * Notify action
+ */
+
+/* Forward declarations */
+
+static int act_notify_check_duplicate
+	(const struct sieve_runtime_env *renv,
+		const struct sieve_action *act,
+		const struct sieve_action *act_other);
+static void act_notify_print
+	(const struct sieve_action *action, const struct sieve_result_print_env *rpenv,
+		bool *keep);
+static int act_notify_commit
+	(const struct sieve_action *action,	const struct sieve_action_exec_env *aenv,
+		void *tr_context, bool *keep);
+
+/* Action object */
+
+const struct sieve_action_def act_notify_old = {
+	.name = "notify",
+	.check_duplicate = act_notify_check_duplicate,
+	.print = act_notify_print,
+	.commit = act_notify_commit
+};
+
+/*
+ * Command validation context
+ */
+
+struct cmd_notify_context_data {
+	struct sieve_ast_argument *id;
+	struct sieve_ast_argument *method;
+	struct sieve_ast_argument *options;
+	struct sieve_ast_argument *message;
+};
+
+/*
+ * Tag validation
+ */
+
+static bool cmd_notify_validate_string_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+    struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *tag = *arg;
+	struct cmd_notify_context_data *ctx_data =
+		(struct cmd_notify_context_data *) cmd->data;
+
+	/* Detach the tag itself */
+	*arg = sieve_ast_arguments_detach(*arg, 1);
+
+	/* Check syntax:
+	 *   :id <string>
+	 *   :method <string>
+	 *   :message <string>
+	 */
+	if ( !sieve_validate_tag_parameter
+		(valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING, FALSE) )
+		return FALSE;
+
+	if ( sieve_argument_is(tag, notify_method_tag) ) {
+		ctx_data->method = *arg;
+
+		/* Removed */
+		*arg = sieve_ast_arguments_detach(*arg, 1);
+
+	} else if ( sieve_argument_is(tag, notify_id_tag) ) {
+		ctx_data->id = *arg;
+
+		/* Skip parameter */
+		*arg = sieve_ast_argument_next(*arg);
+
+	} else if ( sieve_argument_is(tag, notify_message_tag) ) {
+		ctx_data->message = *arg;
+
+		/* Skip parameter */
+		*arg = sieve_ast_argument_next(*arg);
+	}
+
+	return TRUE;
+}
+
+static bool cmd_notify_validate_stringlist_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *tag = *arg;
+	struct cmd_notify_context_data *ctx_data =
+		(struct cmd_notify_context_data *) cmd->data;
+
+	/* Detach the tag itself */
+	*arg = sieve_ast_arguments_detach(*arg,1);
+
+	/* Check syntax:
+	 *   :options string-list
+	 */
+	if ( !sieve_validate_tag_parameter
+		(valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING_LIST, FALSE) )
+		return FALSE;
+
+	/* Assign context */
+	ctx_data->options = *arg;
+
+	/* Skip parameter */
+	*arg = sieve_ast_argument_next(*arg);
+
+	return TRUE;
+}
+
+/*
+ * Command registration
+ */
+
+static bool cmd_notify_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg)
+{
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &notify_method_tag, 0);
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &notify_id_tag, OPT_ID);
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &notify_message_tag, OPT_MESSAGE);
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &notify_options_tag, OPT_OPTIONS);
+
+	ext_notify_register_importance_tags(valdtr, cmd_reg, ext, OPT_IMPORTANCE);
+
+	return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+static bool cmd_notify_pre_validate
+(struct sieve_validator *valdtr ATTR_UNUSED,
+	struct sieve_command *cmd)
+{
+	struct cmd_notify_context_data *ctx_data;
+
+	/* Create context */
+	ctx_data = p_new(sieve_command_pool(cmd),	struct cmd_notify_context_data, 1);
+	cmd->data = ctx_data;
+
+	return TRUE;
+}
+
+static int cmd_notify_address_validate
+(void *context, struct sieve_ast_argument *arg)
+{
+	struct sieve_validator *valdtr = (struct sieve_validator *) context;
+
+	if ( sieve_argument_is_string_literal(arg) ) {
+		string_t *address = sieve_ast_argument_str(arg);
+		const char *error;
+		int result;
+
+		T_BEGIN {
+			result = ( sieve_address_validate_str(address, &error) ? 1 : -1 );
+
+			if ( result <= 0 ) {
+				sieve_argument_validate_error(valdtr, arg,
+					"specified :options address '%s' is invalid for "
+					"the mailto notify method: %s",
+					str_sanitize(str_c(address), 128), error);
+			}
+		} T_END;
+
+		return result;
+	}
+
+	return 1;
+}
+
+static bool cmd_notify_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+	struct cmd_notify_context_data *ctx_data =
+		(struct cmd_notify_context_data *) cmd->data;
+
+	/* Check :method argument */
+	if ( ctx_data->method != NULL )	{
+		const char *method = sieve_ast_argument_strc(ctx_data->method);
+
+		if ( strcasecmp(method, "mailto") != 0 ) {
+			sieve_command_validate_error(valdtr, cmd,
+				"the notify command of the deprecated notify extension "
+				"only supports the 'mailto' notification method");
+			return FALSE;
+		}
+	}
+
+	/* Check :options argument */
+	if ( ctx_data->options != NULL ) {
+		struct sieve_ast_argument *option = ctx_data->options;
+
+		/* Parse and check options */
+		if ( sieve_ast_stringlist_map
+			(&option, (void *) valdtr, cmd_notify_address_validate) <= 0 ) {
+			return FALSE;
+		}
+	} else {
+		sieve_command_validate_warning(valdtr, cmd,
+			"no :options (and hence recipients) specified for the notify command");
+	}
+
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_notify_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	sieve_operation_emit(cgenv->sblock, cmd->ext, &notify_old_operation);
+
+	/* Generate arguments */
+	return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_notify_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	int opt_code = 0;
+
+	sieve_code_dumpf(denv, "NOTIFY");
+	sieve_code_descend(denv);
+
+	/* Dump optional operands */
+	for (;;) {
+		int opt;
+		bool opok = TRUE;
+
+		if ( (opt=sieve_opr_optional_dump(denv, address, &opt_code)) < 0 )
+			return FALSE;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_IMPORTANCE:
+			opok = sieve_opr_number_dump(denv, address, "importance");
+			break;
+		case OPT_ID:
+			opok = sieve_opr_string_dump(denv, address, "id");
+			break;
+		case OPT_OPTIONS:
+			opok = sieve_opr_stringlist_dump(denv, address, "options");
+			break;
+		case OPT_MESSAGE:
+			opok = sieve_opr_string_dump(denv, address, "message");
+			break;
+		default:
+			return FALSE;
+		}
+
+		if ( !opok ) return FALSE;
+	}
+
+	return TRUE;
+}
+
+/*
+ * Code execution
+ */
+
+
+static int cmd_notify_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	const struct sieve_extension *this_ext = renv->oprtn->ext;
+	struct ext_notify_action *act;
+	pool_t pool;
+	int opt_code = 0;
+	sieve_number_t importance = 1;
+	struct sieve_stringlist *options = NULL;
+	string_t *message = NULL, *id = NULL;
+	int ret = 0;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Optional operands */
+
+	for (;;) {
+		int opt;
+
+		if ( (opt=sieve_opr_optional_read(renv, address, &opt_code)) < 0 )
+			return SIEVE_EXEC_BIN_CORRUPT;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_IMPORTANCE:
+			ret = sieve_opr_number_read(renv, address, "importance", &importance);
+			break;
+		case OPT_ID:
+			ret = sieve_opr_string_read(renv, address, "id", &id);
+			break;
+		case OPT_MESSAGE:
+			ret = sieve_opr_string_read(renv, address, "from", &message);
+			break;
+		case OPT_OPTIONS:
+			ret = sieve_opr_stringlist_read(renv, address, "options", &options);
+			break;
+		default:
+			sieve_runtime_trace_error(renv, "unknown optional operand");
+			return SIEVE_EXEC_BIN_CORRUPT;
+		}
+
+		if ( ret <= 0 ) return ret;
+	}
+
+	/*
+	 * Perform operation
+	 */
+
+	/* Enforce 0 < importance < 4 (just to be sure) */
+
+	if ( importance < 1 )
+		importance = 1;
+	else if ( importance > 3 )
+		importance = 3;
+
+	/* Trace */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS, "notify action");
+
+	/* Compose action */
+	if ( options != NULL ) {
+		string_t *raw_address;
+		string_t *out_message;
+
+		pool = sieve_result_pool(renv->result);
+		act = p_new(pool, struct ext_notify_action, 1);
+		if ( id != NULL )
+				act->id = p_strdup(pool, str_c(id));
+		act->importance = importance;
+
+		/* Process message */
+
+		out_message = t_str_new(1024);
+		if ( (ret=ext_notify_construct_message
+			(renv, (message == NULL ? NULL : str_c(message)), out_message)) <= 0 )
+			return ret;
+		act->message = p_strdup(pool, str_c(out_message));
+
+		/* Normalize and verify all :options addresses */
+
+		sieve_stringlist_reset(options);
+
+		p_array_init(&act->recipients, pool, 4);
+
+		raw_address = NULL;
+		while ( (ret=sieve_stringlist_next_item(options, &raw_address)) > 0 ) {
+			const char *error = NULL;
+			const struct smtp_address *address;
+
+			/* Add if valid address */
+			address = sieve_address_parse_str(raw_address, &error);
+			if ( address != NULL ) {
+				const struct ext_notify_recipient *rcpts;
+				unsigned int rcpt_count, i;
+
+				/* Prevent duplicates */
+				rcpts = array_get(&act->recipients, &rcpt_count);
+
+				for ( i = 0; i < rcpt_count; i++ ) {
+					if ( smtp_address_equals(rcpts[i].address, address) )
+						break;
+				}
+
+				/* Add only if unique */
+				if ( i != rcpt_count ) {
+					sieve_runtime_warning(renv, NULL,
+						"duplicate recipient '%s' specified in the :options argument of "
+						"the deprecated notify command",
+						str_sanitize(str_c(raw_address), 128));
+
+				}	else if
+					( array_count(&act->recipients) >= EXT_NOTIFY_MAX_RECIPIENTS ) {
+					sieve_runtime_warning(renv, NULL,
+						"more than the maximum %u recipients are specified "
+						"for the deprecated notify command; "
+						"the rest is discarded", EXT_NOTIFY_MAX_RECIPIENTS);
+					break;
+
+				} else {
+					struct ext_notify_recipient recipient;
+
+					recipient.full = p_strdup(pool, str_c(raw_address));
+					recipient.address = smtp_address_clone(pool, address);
+
+					array_append(&act->recipients, &recipient, 1);
+				}
+			} else {
+				sieve_runtime_error(renv, NULL,
+					"specified :options address '%s' is invalid for "
+					"the deprecated notify command: %s",
+					str_sanitize(str_c(raw_address), 128), error);
+				return SIEVE_EXEC_FAILURE;
+			}
+		}
+
+		if ( ret < 0 ) {
+			sieve_runtime_trace_error(renv, "invalid options stringlist");
+			return SIEVE_EXEC_BIN_CORRUPT;
+		}
+
+		if ( sieve_result_add_action
+			(renv, this_ext, &act_notify_old, NULL, (void *) act, 0, FALSE) < 0 )
+			return SIEVE_EXEC_FAILURE;
+	}
+
+	return SIEVE_EXEC_OK;
+}
+
+/*
+ * Action
+ */
+
+/* Runtime verification */
+
+static int act_notify_check_duplicate
+(const struct sieve_runtime_env *renv ATTR_UNUSED,
+	const struct sieve_action *act ATTR_UNUSED,
+	const struct sieve_action *act_other ATTR_UNUSED)
+{
+	struct ext_notify_action *new_nact, *old_nact;
+	const struct ext_notify_recipient *new_rcpts;
+	const struct ext_notify_recipient *old_rcpts;
+	unsigned int new_count, old_count, i, j;
+	unsigned int del_start = 0, del_len = 0;
+
+	if ( act->context == NULL || act_other->context == NULL )
+		return 0;
+
+	new_nact = (struct ext_notify_action *) act->context;
+	old_nact = (struct ext_notify_action *) act_other->context;
+
+	new_rcpts = array_get(&new_nact->recipients, &new_count);
+	old_rcpts = array_get(&old_nact->recipients, &old_count);
+
+	for ( i = 0; i < new_count; i++ ) {
+		for ( j = 0; j < old_count; j++ ) {
+			if ( smtp_address_equals
+				(new_rcpts[i].address, old_rcpts[j].address) )
+				break;
+		}
+
+		if ( j == old_count ) {
+			/* Not duplicate */
+			if ( del_len > 0 ) {
+				/* Perform pending deletion */
+				array_delete(&new_nact->recipients, del_start, del_len);
+
+				/* Make sure the loop integrity is maintained */
+				i -= del_len;
+				new_rcpts = array_get(&new_nact->recipients, &new_count);
+			}
+
+			del_len = 0;
+		} else {
+			/* Mark deletion */
+			if ( del_len == 0 )
+				del_start = i;
+			del_len++;
+		}
+	}
+
+	/* Perform pending deletion */
+	if ( del_len > 0 ) {
+		array_delete(&new_nact->recipients, del_start, del_len);
+	}
+
+	return ( array_count(&new_nact->recipients) > 0 ? 0 : 1 );
+}
+
+/* Result printing */
+
+static void act_notify_print
+(const struct sieve_action *action,	const struct sieve_result_print_env *rpenv,
+	bool *keep ATTR_UNUSED)
+{
+	const struct ext_notify_action *act =
+		(const struct ext_notify_action *) action->context;
+	const struct ext_notify_recipient *recipients;
+	unsigned int count, i;
+
+	sieve_result_action_printf
+		( rpenv, "send (deprecated) notification with method 'mailto':");
+
+	/* Print main method parameters */
+
+	sieve_result_printf
+		( rpenv, "    => importance    : %llu\n",
+			(unsigned long long)act->importance);
+
+	if ( act->message != NULL )
+		sieve_result_printf
+			( rpenv, "    => message       : %s\n", act->message);
+
+	if ( act->id != NULL )
+		sieve_result_printf
+			( rpenv, "    => id            : %s \n", act->id);
+
+	/* Print mailto: recipients */
+
+	sieve_result_printf
+		( rpenv, "    => recipients    :\n" );
+
+	recipients = array_get(&act->recipients, &count);
+	if ( count == 0 ) {
+		sieve_result_printf(rpenv, "       NONE, action has no effect\n");
+	} else {
+		for ( i = 0; i < count; i++ ) {
+			sieve_result_printf
+				( rpenv, "       + To: %s\n", recipients[i].full);
+		}
+	}
+
+	/* Finish output with an empty line */
+
+	sieve_result_printf(rpenv, "\n");
+}
+
+/* Result execution */
+
+static bool contains_8bit(const char *msg)
+{
+	const unsigned char *s = (const unsigned char *)msg;
+
+	for (; *s != '\0'; s++) {
+		if ((*s & 0x80) != 0)
+			return TRUE;
+	}
+	return FALSE;
+}
+
+static bool act_notify_send
+(const struct sieve_action_exec_env *aenv,
+	const struct ext_notify_action *act)
+{
+	const struct sieve_script_env *senv = aenv->scriptenv;
+	const struct ext_notify_recipient *recipients;
+	struct sieve_smtp_context *sctx;
+	unsigned int count, i;
+	struct ostream *output;
+	string_t *msg, *to, *all;
+	const char *outmsgid, *error;
+	int ret;
+
+	/* Get recipients */
+	recipients = array_get(&act->recipients, &count);
+	if ( count == 0  ) {
+		sieve_result_warning(aenv,
+			"notify action specifies no recipients; action has no effect");
+		return TRUE;
+	}
+
+	/* Just to be sure */
+	if ( !sieve_smtp_available(senv) ) {
+		sieve_result_global_warning(aenv,
+			"notify action has no means to send mail");
+		return TRUE;
+	}
+
+	/* Compose common headers */
+	msg = t_str_new(512);
+	rfc2822_header_write(msg, "X-Sieve", SIEVE_IMPLEMENTATION);
+	rfc2822_header_write(msg, "Date", message_date_create(ioloop_time));
+
+	/* Set importance */
+	switch ( act->importance ) {
+	case 1:
+		rfc2822_header_write(msg, "X-Priority", "1 (Highest)");
+		rfc2822_header_write(msg, "Importance", "High");
+		break;
+	case 3:
+		rfc2822_header_write(msg, "X-Priority", "5 (Lowest)");
+		rfc2822_header_write(msg, "Importance", "Low");
+		break;
+	case 2:
+	default:
+		rfc2822_header_write(msg, "X-Priority", "3 (Normal)");
+		rfc2822_header_write(msg, "Importance", "Normal");
+		break;
+	}
+
+	rfc2822_header_write(msg, "From",
+		sieve_get_postmaster_address(senv));
+
+	rfc2822_header_write(msg, "Subject", "[SIEVE] New mail notification");
+
+	rfc2822_header_write(msg, "Auto-Submitted", "auto-generated (notify)");
+	rfc2822_header_write(msg, "Precedence", "bulk");
+
+	rfc2822_header_write(msg, "MIME-Version", "1.0");
+	if (contains_8bit(act->message)) {
+		rfc2822_header_write(msg,
+			"Content-Type", "text/plain; charset=utf-8");
+		rfc2822_header_write(msg, "Content-Transfer-Encoding", "8bit");
+	} else {
+		rfc2822_header_write(msg,
+			"Content-Type", "text/plain; charset=us-ascii");
+		rfc2822_header_write(msg, "Content-Transfer-Encoding", "7bit");
+	}
+
+	outmsgid = sieve_message_get_new_id(aenv->svinst);
+	rfc2822_header_write(msg, "Message-ID", outmsgid);
+
+	if ( (aenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0 &&
+		sieve_message_get_sender(aenv->msgctx) != NULL ) {
+		sctx = sieve_smtp_start(senv, sieve_get_postmaster_smtp(senv));
+	} else {
+		sctx = sieve_smtp_start(senv, NULL);
+	}
+
+	/* Add all recipients (and compose To header field) */
+	to = t_str_new(128);
+	all = t_str_new(256);
+	for ( i = 0; i < count; i++ ) {
+		sieve_smtp_add_rcpt(sctx, recipients[i].address);
+		if ( i > 0 )
+			str_append(to, ", ");
+		str_append(to, recipients[i].full);
+		if ( i < 3) {
+			if ( i > 0 )
+				str_append(all, ", ");
+			str_append(all, smtp_address_encode_path(recipients[i].address));
+		} else if (i == 3) {
+			str_printfa(all, ", ... (%u total)", count);
+		}
+	}
+
+	rfc2822_header_write_address(msg, "To", str_c(to));
+
+	/* Generate message body */
+	str_printfa(msg, "\r\n%s\r\n", act->message);
+
+	output = sieve_smtp_send(sctx);
+	o_stream_nsend(output, str_data(msg), str_len(msg));
+
+	if ( (ret=sieve_smtp_finish(sctx, &error)) <= 0 ) {
+		if (ret < 0) {
+			sieve_result_global_error(aenv,
+				"failed to send mail notification to %s: %s (temporary failure)",
+				str_c(all),	str_sanitize(error, 512));
+		} else {
+			sieve_result_global_log_error(aenv,
+				"failed to send mail notification to %s: %s (permanent failure)",
+				str_c(all),	str_sanitize(error, 512));
+		}
+	} else {
+		sieve_result_global_log(aenv,
+			"sent mail notification to %s", str_c(all));
+	}
+
+	return TRUE;
+}
+
+static int act_notify_commit
+(const struct sieve_action *action, const struct sieve_action_exec_env *aenv,
+	void *tr_context ATTR_UNUSED, bool *keep ATTR_UNUSED)
+{
+	struct mail *mail = aenv->msgdata->mail;
+	const struct ext_notify_action *act =
+		(const struct ext_notify_action *) action->context;
+	const char *const *hdsp;
+	bool result;
+	int ret;
+
+	/* Is the message an automatic reply ? */
+	if ( (ret=mail_get_headers(mail, "auto-submitted", &hdsp)) < 0 ) {
+		return sieve_result_mail_error(aenv, mail,
+			"notify action: "
+			"failed to read `auto-submitted' header field");
+	}
+
+	/* Theoretically multiple headers could exist, so lets make sure */
+	if (ret > 0) {
+		while ( *hdsp != NULL ) {
+			if ( strcasecmp(*hdsp, "no") != 0 ) {
+				const struct smtp_address *sender = NULL;
+				const char *from;
+
+				if ( (aenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0 )
+					sender = sieve_message_get_sender(aenv->msgctx);
+				from = (sender == NULL ? "" : t_strdup_printf
+					(" from <%s>", smtp_address_encode(sender)));
+
+				sieve_result_global_log(aenv,
+					"not sending notification for auto-submitted message%s",
+					from);
+				return SIEVE_EXEC_OK;
+			}
+			hdsp++;
+		}
+	}
+
+	T_BEGIN {
+		result = act_notify_send(aenv, act);
+	} T_END;
+
+	return ( result ? SIEVE_EXEC_OK : SIEVE_EXEC_FAILURE );
+}
+
+
+
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/notify/ext-notify-common.c
@@ -0,0 +1,344 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "istream.h"
+#include "rfc822-parser.h"
+#include "message-parser.h"
+#include "message-decoder.h"
+#include "mail-storage.h"
+
+#include "sieve-common.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-result.h"
+
+#include "ext-notify-common.h"
+
+#include <ctype.h>
+
+/*
+ * Importance argument
+ */
+
+static bool tag_importance_validate
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+
+static const struct sieve_argument_def importance_low_tag = {
+	.identifier = "low",
+	.validate = tag_importance_validate,
+};
+
+static const struct sieve_argument_def importance_normal_tag = {
+	.identifier = "normal",
+	.validate = tag_importance_validate,
+};
+
+static const struct sieve_argument_def importance_high_tag = {
+	.identifier = "high",
+	.validate = tag_importance_validate,
+};
+
+static bool tag_importance_validate
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd ATTR_UNUSED)
+{
+	struct sieve_ast_argument *tag = *arg;
+
+	if ( sieve_argument_is(tag, importance_low_tag) )
+		sieve_ast_argument_number_substitute(tag, 3);
+	else if ( sieve_argument_is(tag, importance_normal_tag) )
+		sieve_ast_argument_number_substitute(tag, 2);
+	else
+		sieve_ast_argument_number_substitute(tag, 1);
+
+	tag->argument = sieve_argument_create
+		(tag->ast, &number_argument, tag->argument->ext, tag->argument->id_code);
+
+	/* Skip parameter */
+	*arg = sieve_ast_argument_next(*arg);
+
+	return TRUE;
+}
+
+void ext_notify_register_importance_tags
+(struct sieve_validator *valdtr, struct sieve_command_registration *cmd_reg,
+	const struct sieve_extension *ext, unsigned int id_code)
+{
+	sieve_validator_register_tag(valdtr, cmd_reg, ext, &importance_low_tag, id_code);
+	sieve_validator_register_tag(valdtr, cmd_reg, ext, &importance_normal_tag, id_code);
+	sieve_validator_register_tag(valdtr, cmd_reg, ext, &importance_high_tag, id_code);
+}
+
+/*
+ * Body extraction
+ */
+
+/* FIXME: overlaps somewhat with body extension */
+
+struct ext_notify_message_context {
+	pool_t pool;
+	buffer_t *body_text;
+};
+
+static struct ext_notify_message_context *ext_notify_get_message_context
+(const struct sieve_extension *this_ext, struct sieve_message_context *msgctx)
+{
+	struct ext_notify_message_context *ctx;
+
+	/* Get message context (contains cached message body information) */
+	ctx = (struct ext_notify_message_context *)
+		sieve_message_context_extension_get(msgctx, this_ext);
+
+	/* Create it if it does not exist already */
+	if ( ctx == NULL ) {
+		pool_t pool = sieve_message_context_pool(msgctx);
+		ctx = p_new(pool, struct ext_notify_message_context, 1);
+		ctx->pool = pool;
+		ctx->body_text = NULL;
+
+		/* Register context */
+		sieve_message_context_extension_set
+			(msgctx, this_ext, (void *) ctx);
+	}
+
+	return ctx;
+}
+
+static bool _is_text_content(const struct message_header_line *hdr)
+{
+	struct rfc822_parser_context parser;
+	string_t *content_type;
+	const char *data;
+
+	/* Initialize parsing */
+	rfc822_parser_init(&parser, hdr->full_value, hdr->full_value_len, NULL);
+	(void)rfc822_skip_lwsp(&parser);
+
+	/* Parse content type */
+	content_type = t_str_new(64);
+	if (rfc822_parse_content_type(&parser, content_type) < 0)
+		return FALSE;
+
+	/* Content-type value must end here, otherwise it is invalid after all */
+	(void)rfc822_skip_lwsp(&parser);
+	if ( parser.data != parser.end && *parser.data != ';' )
+		return FALSE;
+
+	/* Success */
+	data = str_c(content_type);
+	if (str_begins(data, "text/")) {
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static int cmd_notify_extract_body_text
+(const struct sieve_runtime_env *renv,
+	const char **body_text_r, size_t *body_size_r)
+{
+	const struct sieve_extension *this_ext = renv->oprtn->ext;
+	struct ext_notify_message_context *mctx;
+	struct mail *mail = renv->msgdata->mail;
+	struct message_parser_ctx *parser;
+	struct message_decoder_context *decoder;
+	struct message_part *parts;
+	struct message_block block, decoded;
+	struct istream *input;
+	bool is_text, save_body;
+	int ret = 1;
+
+	*body_text_r = NULL;
+	*body_size_r = 0;
+
+	/* Return cached result if available */
+	mctx = ext_notify_get_message_context(this_ext, renv->msgctx);
+	if ( mctx->body_text != NULL ) {
+		*body_text_r = (const char *)
+			buffer_get_data(mctx->body_text, body_size_r);
+		return SIEVE_EXEC_OK;
+	}
+
+	/* Create buffer */
+	mctx->body_text = buffer_create_dynamic(mctx->pool, 1024*64);
+
+	/* Get the message stream */
+	if ( mail_get_stream(mail, NULL, NULL, &input) < 0 ) {
+		return sieve_runtime_mail_error(renv, mail,
+			"notify action: failed to read input message");
+	}
+
+	/* Initialize body decoder */
+	decoder = message_decoder_init(NULL, 0);
+
+	parser = message_parser_init(mctx->pool, input, 0, 0);
+	is_text = TRUE;
+	save_body = FALSE;
+	while ( (ret=message_parser_parse_next_block(parser, &block)) > 0 ) {
+		if ( block.hdr != NULL || block.size == 0 ) {
+			/* Decode block */
+			(void)message_decoder_decode_next_block(decoder, &block, &decoded);
+
+			/* Check for end of headers */
+			if ( block.hdr == NULL ) {
+				save_body = is_text;
+				continue;
+			}
+
+			/* We're interested of only Content-Type: header */
+			if ( strcasecmp(block.hdr->name, "Content-Type" ) != 0)
+				continue;
+
+			/* Header can have folding whitespace. Acquire the full value before
+			 * continuing
+			 */
+			if ( block.hdr->continues ) {
+				block.hdr->use_full_value = TRUE;
+				continue;
+			}
+
+			/* Is it a text part? */
+			T_BEGIN {
+				is_text = _is_text_content(block.hdr);
+			} T_END;
+
+			continue;
+		}
+
+		/* Read text body */
+		if ( save_body ) {
+			(void)message_decoder_decode_next_block(decoder, &block, &decoded);
+			buffer_append(mctx->body_text, decoded.data, decoded.size);
+			is_text = TRUE;
+		}
+	}
+
+	/* Cleanup */
+	(void)message_parser_deinit(&parser, &parts);
+	message_decoder_deinit(&decoder);
+
+	if ( ret < 0 && input->stream_errno != 0 ) {
+		sieve_runtime_critical(renv, NULL,
+			"notify action: failed to read input message",
+			"notify action: read(%s) failed: %s",
+			i_stream_get_name(input),
+			i_stream_get_error(input));
+		return SIEVE_EXEC_TEMP_FAILURE;
+	}
+
+	/* Return status */
+	*body_text_r = (const char *)
+		buffer_get_data(mctx->body_text, body_size_r);
+	return SIEVE_EXEC_OK;
+}
+
+int ext_notify_construct_message
+(const struct sieve_runtime_env *renv, const char *msg_format,
+	string_t *out_msg)
+{
+	const struct sieve_message_data *msgdata = renv->msgdata;
+	struct sieve_message_context *msgctx = renv->msgctx;
+	const struct smtp_address *return_path =
+		sieve_message_get_sender(msgctx);
+	const char *p;
+	int ret;
+
+	if ( msg_format == NULL )
+		msg_format = "$from$: $subject$";
+
+	/* Scan message for substitutions */
+	p = msg_format;
+	while ( *p != '\0' ) {
+		const char *header;
+
+		if ( strncasecmp(p, "$from$", 6) == 0 ) {
+			p += 6;
+
+			/* Fetch sender from original message */
+			if ( (ret=mail_get_first_header_utf8
+				(msgdata->mail, "from", &header)) < 0 ) {
+				return sieve_runtime_mail_error	(renv, msgdata->mail,
+					"failed to read header field `from'");
+			}
+			if ( ret > 0 )
+				str_append(out_msg, header);
+
+		} else if ( strncasecmp(p, "$env-from$", 10) == 0 ) {
+			p += 10;
+
+			if ( return_path != NULL )
+				smtp_address_write(out_msg, return_path);
+
+		} else if ( strncasecmp(p, "$subject$", 9) == 0 ) {
+			p += 9;
+
+			/* Fetch sender from oriinal message */
+			if ( (ret=mail_get_first_header_utf8
+				(msgdata->mail, "subject", &header)) < 0 ) {
+				return sieve_runtime_mail_error	(renv, msgdata->mail,
+					"failed to read header field `subject'");
+			}
+			if ( ret > 0 )
+				 str_append(out_msg, header);
+
+		} else if ( strncasecmp(p, "$text", 5) == 0
+			&& (p[5] == '[' || p[5] == '$') ) {
+			size_t num = 0;
+			const char *begin = p;
+			bool valid = TRUE;
+
+			p += 5;
+			if ( *p == '[' ) {
+				p += 1;
+
+				while ( i_isdigit(*p) ) {
+					num = num * 10 + (*p - '0');
+					p++;
+				}
+
+				if ( *p++ != ']' || *p++ != '$' ) {
+					str_append_data(out_msg, begin, p-begin);
+					valid = FALSE;
+				}
+			} else {
+				p += 1;
+			}
+
+			if ( valid ) {
+				size_t body_size;
+				const char *body_text;
+					
+				if ( (ret=cmd_notify_extract_body_text
+					(renv, &body_text, &body_size)) <= 0 ) {
+					return ret;
+				}
+
+				if ( num > 0 && num < body_size)
+					str_append_data(out_msg, body_text, num);
+				else
+					str_append_data(out_msg, body_text, body_size);
+			}
+		} else {
+			size_t len;
+
+			/* Find next substitution */
+			len = strcspn(p + 1, "$") + 1;
+
+			/* Copy normal text */
+			str_append_data(out_msg, p, len);
+			p += len;
+		}
+  }
+
+	return SIEVE_EXEC_OK;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/notify/ext-notify-common.h
@@ -0,0 +1,66 @@
+#ifndef EXT_NOTIFY_COMMON_H
+#define EXT_NOTIFY_COMMON_H
+
+/*
+ * Extension
+ */
+
+extern const struct sieve_extension_def notify_extension;
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def cmd_notify_old;
+extern const struct sieve_command_def cmd_denotify;
+
+/*
+ * Arguments
+ */
+
+void ext_notify_register_importance_tags
+	(struct sieve_validator *valdtr, struct sieve_command_registration *cmd_reg,
+		const struct sieve_extension *this_ext, unsigned int id_code);
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def notify_old_operation;
+extern const struct sieve_operation_def denotify_operation;
+
+enum ext_notify_opcode {
+	EXT_NOTIFY_OPERATION_NOTIFY,
+	EXT_NOTIFY_OPERATION_DENOTIFY,
+};
+
+/*
+ * Actions
+ */
+
+extern const struct sieve_action_def act_notify_old;
+
+struct ext_notify_recipient {
+	const char *full;
+	const struct smtp_address *address;
+};
+
+ARRAY_DEFINE_TYPE(recipients, struct ext_notify_recipient);
+
+struct ext_notify_action {
+	const char *id;
+	const char *message;
+	sieve_number_t importance;
+
+	ARRAY_TYPE(recipients) recipients;
+};
+
+/*
+ * Message construct
+ */
+
+int ext_notify_construct_message
+	(const struct sieve_runtime_env *renv, const char *msg_format,
+		string_t *out_msg);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/notify/ext-notify-limits.h
@@ -0,0 +1,7 @@
+#ifndef EXT_NOTIFY_LIMITS_H
+#define EXT_NOTIFY_LIMITS_H
+
+#define EXT_NOTIFY_MAX_RECIPIENTS  8
+#define EXT_NOTIFY_MAX_MESSAGE     256
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/notify/ext-notify.c
@@ -0,0 +1,108 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension notify
+ * ----------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: draft-ietf-sieve-notify-00.txt
+ * Implementation: full, but deprecated; provided for backwards compatibility
+ * Status: testing
+ *
+ */
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-actions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+
+#include "ext-notify-common.h"
+
+/*
+ * Operations
+ */
+
+const struct sieve_operation_def *ext_notify_operations[] = {
+	&notify_old_operation,
+	&denotify_operation
+};
+
+/*
+ * Extension
+ */
+
+static bool ext_notify_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def notify_extension = {
+	.name = "notify",
+	.validator_load = ext_notify_validator_load,
+	SIEVE_EXT_DEFINE_OPERATIONS(ext_notify_operations)
+};
+
+/*
+ * Extension validation
+ */
+
+static bool ext_notify_validator_check_conflict
+	(const struct sieve_extension *ext,
+		struct sieve_validator *valdtr, void *context,
+		struct sieve_ast_argument *require_arg,
+		const struct sieve_extension *ext_other,
+		bool required);
+static bool ext_notify_validator_validate
+	(const struct sieve_extension *ext,
+		struct sieve_validator *valdtr, void *context,
+		struct sieve_ast_argument *require_arg,
+		bool required);
+
+const struct sieve_validator_extension notify_validator_extension = {
+	.ext = &notify_extension,
+	.check_conflict = ext_notify_validator_check_conflict,
+	.validate = ext_notify_validator_validate	
+};
+
+static bool ext_notify_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	/* Register validator extension to check for conflict with enotify */
+	sieve_validator_extension_register
+		(valdtr, ext, &notify_validator_extension, NULL);
+	return TRUE;
+}
+
+static bool ext_notify_validator_check_conflict
+(const struct sieve_extension *ext ATTR_UNUSED,
+	struct sieve_validator *valdtr, void *context ATTR_UNUSED,
+	struct sieve_ast_argument *require_arg,
+	const struct sieve_extension *ext_other,
+	bool required ATTR_UNUSED)
+{
+	/* Check for conflict with enotify */
+	if ( sieve_extension_name_is(ext_other, "enotify") ) {
+		sieve_argument_validate_error(valdtr, require_arg,
+			"the (deprecated) notify extension cannot be used "
+			"together with the enotify extension");
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static bool ext_notify_validator_validate
+(const struct sieve_extension *ext,
+	struct sieve_validator *valdtr, void *context ATTR_UNUSED,
+	struct sieve_ast_argument *require_arg ATTR_UNUSED,
+	bool required ATTR_UNUSED)
+{
+	/* No conflicts: register new commands */
+	sieve_validator_register_command(valdtr, ext, &cmd_notify_old);
+	sieve_validator_register_command(valdtr, ext, &cmd_denotify);
+	return TRUE;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/regex/Makefile.am
@@ -0,0 +1,13 @@
+noinst_LTLIBRARIES = libsieve_ext_regex.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../.. \
+	$(LIBDOVECOT_INCLUDE)
+
+libsieve_ext_regex_la_SOURCES = \
+	mcht-regex.c \
+	ext-regex-common.c \
+	ext-regex.c
+
+noinst_HEADERS = \
+	ext-regex-common.h
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/regex/ext-regex-common.c
@@ -0,0 +1,22 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-match-types.h"
+
+#include "ext-regex-common.h"
+
+/*
+ * Regex match type operand
+ */
+
+static const struct sieve_extension_objects ext_match_types =
+	SIEVE_EXT_DEFINE_MATCH_TYPE(regex_match_type);
+
+const struct sieve_operand_def regex_match_type_operand = {
+	.name = "regex match",
+	.ext_def = &regex_extension,
+	.class = &sieve_match_type_operand_class,
+	.interface = &ext_match_types
+};
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/regex/ext-regex-common.h
@@ -0,0 +1,24 @@
+#ifndef EXT_REGEX_COMMON_H
+#define EXT_REGEX_COMMON_H
+
+/*
+ * Extension
+ */
+
+extern const struct sieve_extension_def regex_extension;
+
+/*
+ * Operand
+ */
+
+extern const struct sieve_operand_def regex_match_type_operand;
+
+/*
+ * Match type
+ */
+
+extern const struct sieve_match_type_def regex_match_type;
+
+#endif
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/regex/ext-regex.c
@@ -0,0 +1,65 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension regex
+ * ---------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: draft-murchison-sieve-regex-08 (not latest)
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+/* FIXME: Regular expressions are compiled during compilation and
+ * again during interpretation. This is suboptimal and should be
+ * changed. This requires dumping the compiled regex to the binary.
+ * Most likely, this will only be possible when we implement regular
+ * expressions ourselves.
+ *
+ */
+
+#include "lib.h"
+#include "mempool.h"
+#include "buffer.h"
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+
+#include "ext-regex-common.h"
+
+#include <sys/types.h>
+#include <regex.h>
+
+/*
+ * Extension
+ */
+
+static bool ext_regex_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *validator);
+
+const struct sieve_extension_def regex_extension = {
+	.name = "regex",
+	.validator_load = ext_regex_validator_load,
+	SIEVE_EXT_DEFINE_OPERAND(regex_match_type_operand)
+};
+
+static bool ext_regex_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	sieve_match_type_register(valdtr, ext, &regex_match_type);
+
+	return TRUE;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/regex/mcht-regex.c
@@ -0,0 +1,385 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Match-type ':regex'
+ */
+
+#include "lib.h"
+#include "mempool.h"
+#include "buffer.h"
+#include "array.h"
+#include "str.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-limits.h"
+#include "sieve-ast.h"
+#include "sieve-stringlist.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-interpreter.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-match.h"
+
+#include "ext-regex-common.h"
+
+#include <sys/types.h>
+#include <regex.h>
+#include <ctype.h>
+
+/*
+ * Configuration
+ */
+
+#define MCHT_REGEX_MAX_SUBSTITUTIONS SIEVE_MAX_MATCH_VALUES
+
+/*
+ * Match type
+ */
+
+static bool mcht_regex_validate_context
+(struct sieve_validator *valdtr, struct sieve_ast_argument *arg,
+    struct sieve_match_type_context *ctx, struct sieve_ast_argument *key_arg);
+
+static void mcht_regex_match_init(struct sieve_match_context *mctx);
+static int mcht_regex_match_keys
+	(struct sieve_match_context *mctx, const char *val, size_t val_size,
+    struct sieve_stringlist *key_list);
+static void mcht_regex_match_deinit(struct sieve_match_context *mctx);
+
+const struct sieve_match_type_def regex_match_type = {
+	SIEVE_OBJECT("regex", &regex_match_type_operand, 0),
+	.validate_context = mcht_regex_validate_context,
+	.match_init = mcht_regex_match_init,
+	.match_keys = mcht_regex_match_keys,
+	.match_deinit = mcht_regex_match_deinit
+};
+
+/*
+ * Match type validation
+ */
+
+/* Wrapper around the regerror function for easy access */
+static const char *_regexp_error(regex_t *regexp, int errorcode)
+{
+	size_t errsize = regerror(errorcode, regexp, NULL, 0);
+
+	if ( errsize > 0 ) {
+		char *errbuf;
+
+		buffer_t *error_buf =
+			buffer_create_dynamic(pool_datastack_create(), errsize);
+		errbuf = buffer_get_space_unsafe(error_buf, 0, errsize);
+
+		errsize = regerror(errorcode, regexp, errbuf, errsize);
+
+		/* We don't want the error to start with a capital letter */
+		errbuf[0] = i_tolower(errbuf[0]);
+
+		buffer_append_space_unsafe(error_buf, errsize);
+
+		return str_c(error_buf);
+	}
+
+	return "";
+}
+
+static int mcht_regex_validate_regexp
+(struct sieve_validator *valdtr,
+	struct sieve_match_type_context *mtctx ATTR_UNUSED,
+	struct sieve_ast_argument *key, int cflags)
+{
+	int ret;
+	regex_t regexp;
+	const char *regex_str = sieve_ast_argument_strc(key);
+
+	if ( (ret=regcomp(&regexp, regex_str, cflags)) != 0 ) {
+		sieve_argument_validate_error(valdtr, key,
+			"invalid regular expression '%s' for regex match: %s",
+			str_sanitize(regex_str, 128), _regexp_error(&regexp, ret));
+
+		regfree(&regexp);
+		return -1;
+	}
+
+	regfree(&regexp);
+	return 1;
+}
+
+struct _regex_key_context {
+	struct sieve_validator *valdtr;
+	struct sieve_match_type_context *mtctx;
+	int cflags;
+};
+
+static int mcht_regex_validate_key_argument
+(void *context, struct sieve_ast_argument *key)
+{
+	struct _regex_key_context *keyctx = (struct _regex_key_context *) context;
+
+	/* FIXME: We can currently only handle string literal argument, so
+	 * variables are not allowed.
+	 */
+	if ( sieve_argument_is_string_literal(key) ) {
+		return mcht_regex_validate_regexp
+			(keyctx->valdtr, keyctx->mtctx, key, keyctx->cflags);
+	}
+
+	return 1;
+}
+
+static bool mcht_regex_validate_context
+(struct sieve_validator *valdtr, struct sieve_ast_argument *arg ATTR_UNUSED,
+	struct sieve_match_type_context *mtctx, struct sieve_ast_argument *key_arg)
+{
+	const struct sieve_comparator *cmp = mtctx->comparator;
+	int cflags = REG_EXTENDED | REG_NOSUB;
+	struct _regex_key_context keyctx;
+	struct sieve_ast_argument *kitem;
+
+	if ( cmp != NULL ) {
+		if ( sieve_comparator_is(cmp, i_ascii_casemap_comparator) )
+			cflags =  REG_EXTENDED | REG_NOSUB | REG_ICASE;
+		else if ( sieve_comparator_is(cmp, i_octet_comparator) )
+			cflags =  REG_EXTENDED | REG_NOSUB;
+		else {
+			sieve_argument_validate_error(valdtr, mtctx->argument,
+				"regex match type only supports "
+				"i;octet and i;ascii-casemap comparators" );
+			return FALSE;
+		}
+	}
+
+	/* Validate regular expression keys */
+
+	keyctx.valdtr = valdtr;
+	keyctx.mtctx = mtctx;
+	keyctx.cflags = cflags;
+
+	kitem = key_arg;
+	if ( sieve_ast_stringlist_map(&kitem, (void *) &keyctx,
+		mcht_regex_validate_key_argument) <= 0 )
+		return FALSE;
+
+	return TRUE;
+}
+
+/*
+ * Match type implementation
+ */
+
+struct mcht_regex_key {
+	regex_t regexp;
+	int status;
+};
+
+struct mcht_regex_context {
+	ARRAY(struct mcht_regex_key) reg_expressions;
+	regmatch_t *pmatch;
+	size_t nmatch;
+	bool all_compiled:1;
+};
+
+static void mcht_regex_match_init
+(struct sieve_match_context *mctx)
+{
+	pool_t pool = mctx->pool;
+	struct mcht_regex_context *ctx;
+
+	/* Create context */
+	ctx = p_new(pool, struct mcht_regex_context, 1);
+
+	/* Create storage for match values if match values are requested */
+	if ( sieve_match_values_are_enabled(mctx->runenv) ) {
+		ctx->pmatch = p_new(pool, regmatch_t, MCHT_REGEX_MAX_SUBSTITUTIONS);
+		ctx->nmatch = MCHT_REGEX_MAX_SUBSTITUTIONS;
+	} else {
+		ctx->pmatch = NULL;
+		ctx->nmatch = 0;
+	}
+
+	/* Assign context */
+	mctx->data = (void *) ctx;
+}
+
+static int mcht_regex_match_key
+(struct sieve_match_context *mctx, const char *val,
+	const regex_t *regexp)
+{
+	struct mcht_regex_context *ctx = (struct mcht_regex_context *) mctx->data;
+	int ret;
+
+	/* Execute regex */
+
+	ret = regexec(regexp, val, ctx->nmatch, ctx->pmatch, 0);
+
+	/* Handle match values if necessary */
+
+	if ( ret == 0 ) {
+		if ( ctx->nmatch > 0 ) {
+			struct sieve_match_values *mvalues;
+			size_t i;
+			int skipped = 0;
+			string_t *subst = t_str_new(32);
+
+			/* Start new list of match values */
+			mvalues = sieve_match_values_start(mctx->runenv);
+
+			i_assert( mvalues != NULL );
+
+			/* Add match values from regular expression */
+			for ( i = 0; i < ctx->nmatch; i++ ) {
+				str_truncate(subst, 0);
+
+				if ( ctx->pmatch[i].rm_so != -1 ) {
+					if ( skipped > 0 ) {
+						sieve_match_values_skip(mvalues, skipped);
+						skipped = 0;
+					}
+
+					str_append_data(subst, val + ctx->pmatch[i].rm_so,
+						ctx->pmatch[i].rm_eo - ctx->pmatch[i].rm_so);
+					sieve_match_values_add(mvalues, subst);
+				} else
+					skipped++;
+			}
+
+			/* Substitute the new match values */
+			sieve_match_values_commit(mctx->runenv, &mvalues);
+		}
+
+		return 1;
+	}
+
+	return 0;
+}
+
+static int mcht_regex_match_keys
+(struct sieve_match_context *mctx, const char *val, size_t val_size ATTR_UNUSED,
+	struct sieve_stringlist *key_list)
+{
+	const struct sieve_runtime_env *renv = mctx->runenv;
+	bool trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING);
+	struct mcht_regex_context *ctx = (struct mcht_regex_context *) mctx->data;
+	const struct sieve_comparator *cmp = mctx->comparator;
+	int match;
+
+	if ( !ctx->all_compiled ) {
+		string_t *key_item = NULL;
+		unsigned int i;
+		int ret;
+
+		/* Regular expressions still need to be compiled */
+
+		if ( !array_is_created(&ctx->reg_expressions) )
+			p_array_init(&ctx->reg_expressions, mctx->pool, 16);
+
+		i = 0;
+		match = 0;
+		while ( match == 0 &&
+			(ret=sieve_stringlist_next_item(key_list, &key_item)) > 0 ) {
+
+			T_BEGIN {
+				struct mcht_regex_key *rkey;
+
+				if ( i >= array_count(&ctx->reg_expressions) ) {
+					int cflags;
+
+					rkey = array_append_space(&ctx->reg_expressions);
+
+					/* Configure case-sensitivity according to comparator */
+					if ( sieve_comparator_is(cmp, i_octet_comparator) )
+						cflags =  REG_EXTENDED;
+					else if ( sieve_comparator_is(cmp, i_ascii_casemap_comparator) )
+						cflags =  REG_EXTENDED | REG_ICASE;
+					else
+						rkey->status = -1; /* Not supported */
+
+					if ( rkey->status >= 0 ) {
+						const char *regex_str = str_c(key_item);
+						int rxret;
+
+						/* Indicate whether match values need to be produced */
+						if ( ctx->nmatch == 0 ) cflags |= REG_NOSUB;
+
+						/* Compile regular expression */
+						if ( (rxret=regcomp(&rkey->regexp, regex_str, cflags)) != 0 ) {
+							sieve_runtime_error(renv, NULL,
+								"invalid regular expression '%s' for regex match: %s",
+								str_sanitize(regex_str, 128),
+								_regexp_error(&rkey->regexp, rxret));
+							rkey->status = -1;
+						} else {
+							rkey->status = 1;
+						}
+					}
+				} else {
+					rkey = array_idx_modifiable(&ctx->reg_expressions, 1);
+				}
+
+				if ( rkey->status > 0 ) {
+					match = mcht_regex_match_key(mctx, val, &rkey->regexp);
+
+					if ( trace ) {
+						sieve_runtime_trace(renv, 0,
+							"with regex `%s' [id=%d] => %d",
+							str_sanitize(str_c(key_item), 80),
+							array_count(&ctx->reg_expressions)-1, match);
+					}
+				}
+			} T_END;
+
+			i++;
+		}
+
+		if ( ret == 0 ) {
+			ctx->all_compiled = TRUE;
+		} else if ( ret < 0 ) {
+			mctx->exec_status = key_list->exec_status;
+			match = -1;
+		}
+
+	} else {
+		const struct mcht_regex_key *rkeys;
+		unsigned int i, count;
+
+		/* Regular expressions are compiled */
+
+		rkeys = array_get(&ctx->reg_expressions, &count);
+
+		i = 0;
+		match = 0;
+		while ( match == 0 && i < count ) {
+			if ( rkeys[i].status > 0 ) {
+				match = mcht_regex_match_key(mctx, val, &rkeys[i].regexp);
+
+				if ( trace ) {
+					sieve_runtime_trace(renv, 0,
+						"with compiled regex [id=%d] => %d", i, match);
+				}
+			}
+
+			i++;
+		}
+	}
+
+	return match;
+}
+
+void mcht_regex_match_deinit
+(struct sieve_match_context *mctx)
+{
+	struct mcht_regex_context *ctx = (struct mcht_regex_context *) mctx->data;
+	struct mcht_regex_key *rkeys;
+	unsigned int count, i;
+
+	/* Clean up compiled regular expressions */
+	if ( array_is_created(&ctx->reg_expressions) ) {
+		rkeys = array_get_modifiable(&ctx->reg_expressions, &count);
+		for ( i = 0; i < count; i++ ) {
+			regfree(&rkeys[i].regexp);
+		}
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/relational/Makefile.am
@@ -0,0 +1,14 @@
+noinst_LTLIBRARIES = libsieve_ext_relational.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../.. \
+	$(LIBDOVECOT_INCLUDE)
+
+libsieve_ext_relational_la_SOURCES = \
+	ext-relational-common.c \
+	mcht-value.c \
+	mcht-count.c \
+	ext-relational.c
+
+noinst_HEADERS = \
+	ext-relational-common.h
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/relational/ext-relational-common.c
@@ -0,0 +1,165 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Syntax:
+ *   MATCH-TYPE =/ COUNT / VALUE
+ *   COUNT = ":count" relational-match
+ *   VALUE = ":value" relational-match
+ *   relational-match = DQUOTE ( "gt" / "ge" / "lt"
+ *                             / "le" / "eq" / "ne" ) DQUOTE
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-ast.h"
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+
+#include "ext-relational-common.h"
+
+/*
+ * Forward declarations
+ */
+
+const struct sieve_match_type_def *rel_match_types[];
+
+/*
+ * Validation
+ */
+
+bool mcht_relational_validate
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_match_type_context *ctx)
+{
+	struct sieve_match_type *mcht;
+	enum relational_match rel_match = REL_MATCH_INVALID;
+	string_t *rel_match_ident;
+
+	/* Check syntax:
+	 *   relational-match = DQUOTE ( "gt" / "ge" / "lt"
+ 	 *                             / "le" / "eq" / "ne" ) DQUOTE
+ 	 *
+	 * So, actually this must be a constant string and it is implemented as such
+	 */
+
+	/* Did we get a string in the first place ? */
+	if ( (*arg)->type != SAAT_STRING ) {
+		sieve_argument_validate_error(valdtr, *arg,
+			"the :%s match-type requires a constant string argument being "
+			"one of \"gt\", \"ge\", \"lt\", \"le\", \"eq\" or \"ne\", "
+			"but %s was found",
+			sieve_match_type_name(ctx->match_type), sieve_ast_argument_name(*arg));
+		return FALSE;
+	}
+
+	/* Check the relational match id */
+
+	rel_match_ident = sieve_ast_argument_str(*arg);
+	if ( str_len(rel_match_ident) == 2 ) {
+		const char *rel_match_id = str_c(rel_match_ident);
+
+		switch ( rel_match_id[0] ) {
+		/* "gt" or "ge" */
+		case 'g':
+			switch ( rel_match_id[1] ) {
+			case 't':
+				rel_match = REL_MATCH_GREATER;
+				break;
+			case 'e':
+				rel_match = REL_MATCH_GREATER_EQUAL;
+				break;
+			default:
+				rel_match = REL_MATCH_INVALID;
+			}
+			break;
+		/* "lt" or "le" */
+		case 'l':
+			switch ( rel_match_id[1] ) {
+			case 't':
+				rel_match = REL_MATCH_LESS;
+				break;
+			case 'e':
+				rel_match = REL_MATCH_LESS_EQUAL;
+				break;
+			default:
+				rel_match = REL_MATCH_INVALID;
+			}
+			break;
+		/* "eq" */
+		case 'e':
+			if ( rel_match_id[1] == 'q' )
+				rel_match = REL_MATCH_EQUAL;
+			else
+				rel_match = REL_MATCH_INVALID;
+			break;
+		/* "ne" */
+		case 'n':
+			if ( rel_match_id[1] == 'e' )
+				rel_match = REL_MATCH_NOT_EQUAL;
+			else
+				rel_match = REL_MATCH_INVALID;
+			break;
+		/* invalid */
+		default:
+			rel_match = REL_MATCH_INVALID;
+		}
+	}
+
+	if ( rel_match >= REL_MATCH_INVALID ) {
+		sieve_argument_validate_error(valdtr, *arg,
+			"the :%s match-type requires a constant string argument being "
+			"one of \"gt\", \"ge\", \"lt\", \"le\", \"eq\" or \"ne\", "
+			"but \"%s\" was found",
+			sieve_match_type_name(ctx->match_type),
+			str_sanitize(str_c(rel_match_ident), 32));
+		return FALSE;
+	}
+
+	/* Delete argument */
+	*arg = sieve_ast_arguments_detach(*arg, 1);
+
+	/* Not used just yet */
+	ctx->ctx_data = (void *) rel_match;
+
+	/* Override the actual match type with a parameter-specific one
+	 * FIXME: ugly!
+	 */
+	mcht = p_new(sieve_ast_argument_pool(*arg), struct sieve_match_type, 1);
+	mcht->object.ext = ctx->match_type->object.ext;
+	SIEVE_OBJECT_SET_DEF(mcht, rel_match_types
+		[REL_MATCH_INDEX(ctx->match_type->object.def->code, rel_match)]);
+	ctx->match_type = mcht;
+
+	return TRUE;
+}
+
+/*
+ * Relational match-type operand
+ */
+
+const struct sieve_match_type_def *rel_match_types[] = {
+	&rel_match_value_gt, &rel_match_value_ge, &rel_match_value_lt,
+	&rel_match_value_le, &rel_match_value_eq, &rel_match_value_ne,
+	&rel_match_count_gt, &rel_match_count_ge, &rel_match_count_lt,
+	&rel_match_count_le, &rel_match_count_eq, &rel_match_count_ne
+};
+
+static const struct sieve_extension_objects ext_match_types =
+	SIEVE_EXT_DEFINE_MATCH_TYPES(rel_match_types);
+
+const struct sieve_operand_def rel_match_type_operand = {
+	.name = "relational match",
+	.ext_def = &relational_extension,
+	.class = &sieve_match_type_operand_class,
+	.interface = &ext_match_types
+};
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/relational/ext-relational-common.h
@@ -0,0 +1,90 @@
+#ifndef EXT_RELATIONAL_COMMON_H
+#define EXT_RELATIONAL_COMMON_H
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve-common.h"
+
+/*
+ * Types
+ */
+
+enum ext_relational_match_type {
+	RELATIONAL_VALUE,
+	RELATIONAL_COUNT
+};
+
+enum relational_match {
+	REL_MATCH_GREATER,
+	REL_MATCH_GREATER_EQUAL,
+	REL_MATCH_LESS,
+	REL_MATCH_LESS_EQUAL,
+	REL_MATCH_EQUAL,
+	REL_MATCH_NOT_EQUAL,
+	REL_MATCH_INVALID
+};
+
+#define REL_MATCH_INDEX(type, match) \
+	(type * REL_MATCH_INVALID + match)
+#define REL_MATCH_TYPE(index) \
+	(index / REL_MATCH_INVALID)
+#define REL_MATCH(index) \
+	(index % REL_MATCH_INVALID)
+
+/*
+ * Extension definitions
+ */
+
+extern const struct sieve_extension_def relational_extension;
+
+/*
+ * Match types
+ */
+
+/* Registered for validation */
+
+extern const struct sieve_match_type_def value_match_type;
+extern const struct sieve_match_type_def count_match_type;
+
+/* Used in byte code */
+
+extern const struct sieve_match_type_def rel_match_count_gt;
+extern const struct sieve_match_type_def rel_match_count_ge;
+extern const struct sieve_match_type_def rel_match_count_lt;
+extern const struct sieve_match_type_def rel_match_count_le;
+extern const struct sieve_match_type_def rel_match_count_eq;
+extern const struct sieve_match_type_def rel_match_count_ne;
+
+extern const struct sieve_match_type_def rel_match_value_gt;
+extern const struct sieve_match_type_def rel_match_value_ge;
+extern const struct sieve_match_type_def rel_match_value_lt;
+extern const struct sieve_match_type_def rel_match_value_le;
+extern const struct sieve_match_type_def rel_match_value_eq;
+extern const struct sieve_match_type_def rel_match_value_ne;
+
+/*
+ * Operand
+ */
+
+extern const struct sieve_operand_def rel_match_type_operand;
+
+
+/*
+ * Match type validation
+ */
+
+bool mcht_relational_validate
+	(struct sieve_validator *validator, struct sieve_ast_argument **arg,
+		struct sieve_match_type_context *ctx);
+
+/*
+ * Value match function (also used by :count)
+ */
+
+int mcht_value_match_key
+	(struct sieve_match_context *mctx, const char *val, size_t val_size,
+		const char *key, size_t key_size);
+
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/relational/ext-relational.c
@@ -0,0 +1,53 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension relational
+ * --------------------
+ *
+ * Author: Stephan Bosch
+ * Specification: RFC 3431
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve-common.h"
+
+#include "sieve-ast.h"
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+
+#include "ext-relational-common.h"
+
+/*
+ * Extension
+ */
+
+static bool ext_relational_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def relational_extension = {
+	.name = "relational",
+	.validator_load = ext_relational_validator_load,
+	SIEVE_EXT_DEFINE_OPERAND(rel_match_type_operand)
+};
+
+static bool ext_relational_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	sieve_match_type_register(valdtr, ext, &value_match_type);
+	sieve_match_type_register(valdtr, ext, &count_match_type);
+
+	return TRUE;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/relational/mcht-count.c
@@ -0,0 +1,119 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Match-type ':count'
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-ast.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-runtime-trace.h"
+#include "sieve-match.h"
+
+#include "ext-relational-common.h"
+
+/*
+ * Forward declarations
+ */
+
+static int mcht_count_match
+	(struct sieve_match_context *mctx, struct sieve_stringlist *value_list,
+		struct sieve_stringlist *key_list);
+
+/*
+ * Match-type objects
+ */
+
+const struct sieve_match_type_def count_match_type = {
+	SIEVE_OBJECT("count",
+		&rel_match_type_operand, RELATIONAL_COUNT),
+	.validate = mcht_relational_validate
+};
+
+#define COUNT_MATCH_TYPE(name, rel_match)                      \
+const struct sieve_match_type_def rel_match_count_ ## name = { \
+	SIEVE_OBJECT("count-" #name,                                 \
+    &rel_match_type_operand,                                   \
+		REL_MATCH_INDEX(RELATIONAL_COUNT, rel_match)),             \
+	.match = mcht_count_match,                                   \
+}
+
+COUNT_MATCH_TYPE(gt, REL_MATCH_GREATER);
+COUNT_MATCH_TYPE(ge, REL_MATCH_GREATER_EQUAL);
+COUNT_MATCH_TYPE(lt, REL_MATCH_LESS);
+COUNT_MATCH_TYPE(le, REL_MATCH_LESS_EQUAL);
+COUNT_MATCH_TYPE(eq, REL_MATCH_EQUAL);
+COUNT_MATCH_TYPE(ne, REL_MATCH_NOT_EQUAL);
+
+/*
+ * Match-type implementation
+ */
+
+static int mcht_count_match
+(struct sieve_match_context *mctx, struct sieve_stringlist *value_list,
+	struct sieve_stringlist *key_list)
+{
+	const struct sieve_runtime_env *renv = mctx->runenv;
+	bool trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING);
+	int count;
+	string_t *key_item;
+	int match, ret;
+
+	if ( (count=sieve_stringlist_get_length(value_list)) < 0 ) {
+		mctx->exec_status = value_list->exec_status;
+		return -1;
+	}
+
+	sieve_stringlist_reset(key_list);
+
+	string_t *value = t_str_new(20);
+	str_printfa(value, "%d", count);
+
+	if ( trace ) {
+		sieve_runtime_trace(renv, 0,
+			"matching count value `%s'", str_sanitize(str_c(value), 80));
+	}
+
+	sieve_runtime_trace_descend(renv);
+
+  /* Match to all key values */
+  key_item = NULL;
+	match = 0;
+  while ( match == 0 &&
+		(ret=sieve_stringlist_next_item(key_list, &key_item)) > 0 )
+  {
+		match = mcht_value_match_key
+			(mctx, str_c(value), str_len(value), str_c(key_item), str_len(key_item));
+
+		if ( trace ) {
+			sieve_runtime_trace(renv, 0,
+				"with key `%s' => %d", str_sanitize(str_c(key_item), 80), ret);
+		}
+	}
+
+	sieve_runtime_trace_ascend(renv);
+
+	if ( ret < 0 ) {
+		mctx->exec_status = key_list->exec_status;
+		match = -1;
+	}
+
+	return match;
+}
+
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/relational/mcht-value.c
@@ -0,0 +1,80 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve-common.h"
+
+#include "sieve-ast.h"
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-match.h"
+
+#include "ext-relational-common.h"
+
+/*
+ * Match-type objects
+ */
+
+const struct sieve_match_type_def value_match_type = {
+	SIEVE_OBJECT("value",
+		&rel_match_type_operand, RELATIONAL_VALUE),
+	.validate = mcht_relational_validate
+};
+
+#define VALUE_MATCH_TYPE(name, rel_match)                       \
+const struct sieve_match_type_def rel_match_value_ ## name = {  \
+	SIEVE_OBJECT("value-" #name,                                  \
+		&rel_match_type_operand,                                    \
+		REL_MATCH_INDEX(RELATIONAL_VALUE, rel_match)),              \
+	.match_key = mcht_value_match_key,                            \
+}
+
+VALUE_MATCH_TYPE(gt, REL_MATCH_GREATER);
+VALUE_MATCH_TYPE(ge, REL_MATCH_GREATER_EQUAL);
+VALUE_MATCH_TYPE(lt, REL_MATCH_LESS);
+VALUE_MATCH_TYPE(le, REL_MATCH_LESS_EQUAL);
+VALUE_MATCH_TYPE(eq, REL_MATCH_EQUAL);
+VALUE_MATCH_TYPE(ne, REL_MATCH_NOT_EQUAL);
+
+/*
+ * Match-type implementation
+ */
+
+int mcht_value_match_key
+(struct sieve_match_context *mctx, const char *val, size_t val_size,
+	const char *key, size_t key_size)
+{
+	const struct sieve_match_type *mtch = mctx->match_type;
+	unsigned int rel_match = REL_MATCH(mtch->object.def->code);
+	int cmp_result;
+
+	cmp_result = mctx->comparator->def->
+		compare(mctx->comparator, val, val_size, key, key_size);
+
+	switch ( rel_match ) {
+	case REL_MATCH_GREATER:
+		return ( cmp_result > 0 ? 1 : 0 );
+	case REL_MATCH_GREATER_EQUAL:
+		return ( cmp_result >= 0 ? 1 : 0 );
+	case REL_MATCH_LESS:
+		return ( cmp_result < 0 ? 1 : 0 );
+	case REL_MATCH_LESS_EQUAL:
+		return ( cmp_result <= 0 ? 1 : 0 );
+	case REL_MATCH_EQUAL:
+		return ( cmp_result == 0 ? 1 : 0);
+	case REL_MATCH_NOT_EQUAL:
+		return ( cmp_result != 0 ? 1 : 0);
+	}
+
+	i_unreached();
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/spamvirustest/Makefile.am
@@ -0,0 +1,16 @@
+noinst_LTLIBRARIES = libsieve_ext_spamvirustest.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../.. \
+	$(LIBDOVECOT_INCLUDE)
+
+tests = \
+	tst-spamvirustest.c
+
+libsieve_ext_spamvirustest_la_SOURCES = \
+	$(tests) \
+	ext-spamvirustest-common.c \
+	ext-spamvirustest.c
+
+noinst_HEADERS = \
+	ext-spamvirustest-common.h
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/spamvirustest/ext-spamvirustest-common.c
@@ -0,0 +1,668 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "strfuncs.h"
+#include "mail-storage.h"
+
+#include "sieve-common.h"
+#include "sieve-settings.h"
+#include "sieve-error.h"
+#include "sieve-extensions.h"
+#include "sieve-message.h"
+#include "sieve-interpreter.h"
+#include "sieve-runtime-trace.h"
+
+#include "ext-spamvirustest-common.h"
+
+#include <sys/types.h>
+#include <regex.h>
+#include <ctype.h>
+
+/*
+ * Extension data
+ */
+
+enum ext_spamvirustest_status_type {
+	EXT_SPAMVIRUSTEST_STATUS_TYPE_SCORE,
+	EXT_SPAMVIRUSTEST_STATUS_TYPE_STRLEN,
+	EXT_SPAMVIRUSTEST_STATUS_TYPE_TEXT,
+};
+
+struct ext_spamvirustest_header_spec {
+	const char *header_name;
+	regex_t regexp;
+	bool regexp_match;
+};
+
+struct ext_spamvirustest_data {
+	pool_t pool;
+
+	int reload;
+
+	struct ext_spamvirustest_header_spec status_header;
+	struct ext_spamvirustest_header_spec max_header;
+
+	enum ext_spamvirustest_status_type status_type;
+
+	float max_value;
+
+	const char *text_values[11];
+};
+
+/*
+ * Regexp utility
+ */
+
+static bool _regexp_compile
+(regex_t *regexp, const char *data, const char **error_r)
+{
+	size_t errsize;
+	int ret;
+
+	*error_r = "";
+
+	if ( (ret=regcomp(regexp, data, REG_EXTENDED)) == 0 ) {
+		return TRUE;
+	}
+
+	errsize = regerror(ret, regexp, NULL, 0);
+
+	if ( errsize > 0 ) {
+		char *errbuf = t_malloc0(errsize);
+
+		(void)regerror(ret, regexp, errbuf, errsize);
+
+		/* We don't want the error to start with a capital letter */
+		errbuf[0] = i_tolower(errbuf[0]);
+
+		*error_r = errbuf;
+	}
+
+	return FALSE;
+}
+
+static const char *_regexp_match_get_value
+(const char *string, int index, regmatch_t pmatch[], int nmatch)
+{
+	if ( index > -1 && index < nmatch && pmatch[index].rm_so != -1 ) {
+		return t_strndup(string + pmatch[index].rm_so,
+						pmatch[index].rm_eo - pmatch[index].rm_so);
+	}
+	return NULL;
+}
+
+/*
+ * Configuration parser
+ */
+
+static bool ext_spamvirustest_header_spec_parse
+(struct ext_spamvirustest_header_spec *spec, pool_t pool, const char *data,
+	const char **error_r)
+{
+	const char *p;
+	const char *regexp_error;
+
+	if ( *data == '\0' ) {
+		*error_r = "empty header specification";
+		return FALSE;
+	}
+
+	/* Parse header name */
+
+	p = data;
+
+	while ( *p == ' ' || *p == '\t' ) p++;
+	while ( *p != ':' && *p != '\0' && *p != ' ' && *p != '\t' ) p++;
+
+	if ( *p == '\0' ) {
+		spec->header_name = p_strdup(pool, data);
+		return TRUE;
+	}
+
+	spec->header_name = p_strdup_until(pool, data, p);
+	while ( *p == ' ' || *p == '\t' ) p++;
+
+	if ( *p == '\0' ) {
+		spec->regexp_match = FALSE;
+		return TRUE;
+	}
+
+	/* Parse and compile regular expression */
+
+	if ( *p != ':' ) {
+		*error_r = t_strdup_printf("expecting ':', but found '%c'", *p);
+		return FALSE;
+	}
+	p++;
+	while ( *p == ' ' || *p == '\t' ) p++;
+
+	spec->regexp_match = TRUE;
+	if ( !_regexp_compile(&spec->regexp, p, &regexp_error) ) {
+		*error_r = t_strdup_printf("failed to compile regular expression '%s': "
+			"%s", p, regexp_error);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static void ext_spamvirustest_header_spec_free
+(struct ext_spamvirustest_header_spec *spec)
+{
+	regfree(&spec->regexp);
+}
+
+static bool ext_spamvirustest_parse_strlen_value
+(const char *str_value, float *value_r, const char **error_r)
+{
+	const char *p = str_value;
+	char ch = *p;
+
+	if ( *str_value == '\0' ) {
+		*value_r = 0;
+		return TRUE;
+	}
+
+	while ( *p == ch ) p++;
+
+	if ( *p != '\0' ) {
+		*error_r = t_strdup_printf(
+			"different character '%c' encountered in strlen value",
+			*p);
+		return FALSE;
+	}
+
+	*value_r = ( p - str_value );
+
+	return TRUE;
+}
+
+static bool ext_spamvirustest_parse_decimal_value
+(const char *str_value, float *value_r, const char **error_r)
+{
+	const char *p = str_value;
+	float value;
+	float sign = 1;
+	int digits;
+
+	if ( *p == '\0' ) {
+		*error_r = "empty value";
+		return FALSE;
+	}
+
+	if ( *p == '+' || *p == '-' ) {
+		if ( *p == '-' )
+			sign = -1;
+
+		p++;
+	}
+
+	value = 0;
+	digits = 0;
+	while ( i_isdigit(*p) ) {
+		value = value*10 + (*p-'0');
+		if ( digits++ > 4 ) {
+			*error_r = t_strdup_printf
+				("decimal value has too many digits before radix point: %s",
+					str_value);
+			return FALSE;
+		}
+		p++;
+	}
+
+	if ( *p == '.' || *p == ',' ) {
+		float radix = .1;
+		p++;
+
+		digits = 0;
+		while ( i_isdigit(*p) ) {
+			value = value + (*p-'0')*radix;
+
+			if ( digits++ > 4 ) {
+				*error_r = t_strdup_printf
+					("decimal value has too many digits after radix point: %s",
+						str_value);
+				return FALSE;
+			}
+			radix /= 10;
+			p++;
+		}
+	}
+
+	if ( *p != '\0' ) {
+		*error_r = t_strdup_printf
+			("invalid decimal point value: %s", str_value);
+		return FALSE;
+	}
+
+	*value_r = value * sign;
+
+	return TRUE;
+}
+
+/*
+ * Extension initialization
+ */
+
+bool ext_spamvirustest_load
+(const struct sieve_extension *ext, void **context)
+{
+	struct ext_spamvirustest_data *ext_data =
+		(struct ext_spamvirustest_data *) *context;
+	struct sieve_instance *svinst = ext->svinst;
+	const char *ext_name, *status_header, *max_header, *status_type,
+		*max_value;
+	enum ext_spamvirustest_status_type type;
+	const char *error;
+	pool_t pool;
+	bool result = TRUE;
+	int reload = 0;
+
+	if ( *context != NULL ) {
+		reload = ext_data->reload + 1;
+		ext_spamvirustest_unload(ext);
+		*context = NULL;
+	}
+
+	/* FIXME:
+	 *   Prevent loading of both spamtest and spamtestplus: let these share
+	 *   contexts.
+	 */
+
+	if ( sieve_extension_is(ext, spamtest_extension) ||
+		sieve_extension_is(ext, spamtestplus_extension) ) {
+		ext_name = spamtest_extension.name;
+	} else {
+		ext_name = sieve_extension_name(ext);
+	}
+
+	/* Get settings */
+
+	status_header = sieve_setting_get
+		(svinst, t_strconcat("sieve_", ext_name, "_status_header", NULL));
+	status_type = sieve_setting_get
+		(svinst, t_strconcat("sieve_", ext_name, "_status_type", NULL));
+	max_header = sieve_setting_get
+		(svinst, t_strconcat("sieve_", ext_name, "_max_header", NULL));
+	max_value = sieve_setting_get
+		(svinst, t_strconcat("sieve_", ext_name, "_max_value", NULL));
+
+	/* Base configuration */
+
+	if ( status_header == NULL ) {
+		return TRUE;
+	}
+
+	if ( status_type == NULL || strcmp(status_type, "score") == 0 ) {
+		type = EXT_SPAMVIRUSTEST_STATUS_TYPE_SCORE;
+	} else if ( strcmp(status_type, "strlen") == 0 ) {
+		type = EXT_SPAMVIRUSTEST_STATUS_TYPE_STRLEN;
+	} else if ( strcmp(status_type, "text") == 0 ) {
+		type = EXT_SPAMVIRUSTEST_STATUS_TYPE_TEXT;
+	} else {
+		sieve_sys_error(svinst,
+			"%s: invalid status type '%s'", ext_name, status_type);
+		return FALSE;
+	}
+
+	/* Verify settings */
+
+	if ( type != EXT_SPAMVIRUSTEST_STATUS_TYPE_TEXT ) {
+
+		if ( max_header != NULL && max_value != NULL ) {
+			sieve_sys_error(svinst,
+				"%s: sieve_%s_max_header and sieve_%s_max_value "
+				"cannot both be configured", ext_name, ext_name, ext_name);
+			return TRUE;
+		}
+
+		if ( max_header == NULL && max_value == NULL ) {
+			sieve_sys_error(svinst,
+				"%s: none of sieve_%s_max_header or sieve_%s_max_value "
+				"is configured", ext_name, ext_name, ext_name);
+			return TRUE;
+		}
+	} else {
+		if ( max_header != NULL ) {
+			sieve_sys_warning(svinst,
+				"%s: setting sieve_%s_max_header has no meaning "
+				"for sieve_%s_status_type=text", ext_name, ext_name, ext_name);
+		}
+
+		if ( max_value != NULL ) {
+			sieve_sys_warning(svinst,
+				"%s: setting sieve_%s_max_value has no meaning "
+				"for sieve_%s_status_type=text", ext_name, ext_name, ext_name);
+		}
+	}
+
+	pool = pool_alloconly_create("spamvirustest_data", 512);
+	ext_data = p_new(pool, struct ext_spamvirustest_data, 1);
+	ext_data->pool = pool;
+	ext_data->reload = reload;
+	ext_data->status_type = type;
+
+	if ( !ext_spamvirustest_header_spec_parse
+		(&ext_data->status_header, ext_data->pool, status_header, &error) ) {
+		sieve_sys_error(svinst,
+			"%s: invalid status header specification "
+			"'%s': %s", ext_name, status_header, error);
+		result = FALSE;
+	}
+
+	if ( result ) {
+		if ( type != EXT_SPAMVIRUSTEST_STATUS_TYPE_TEXT ) {
+			/* Parse max header */
+
+			if ( max_header != NULL && !ext_spamvirustest_header_spec_parse
+				(&ext_data->max_header, ext_data->pool, max_header, &error) ) {
+				sieve_sys_error(svinst,
+					"%s: invalid max header specification "
+					"'%s': %s", ext_name, max_header, error);
+				result = FALSE;
+			}
+
+			/* Parse max value */
+
+			if ( result && max_value != NULL ) {
+				if ( !ext_spamvirustest_parse_decimal_value
+					(max_value, &ext_data->max_value, &error) ) {
+					sieve_sys_error(svinst,
+						"%s: invalid max value specification "
+						"'%s': %s", ext_name, max_value, error);
+					result = FALSE;
+				}
+			}
+
+		} else {
+			unsigned int i, max_text;
+
+			max_text = ( sieve_extension_is(ext, virustest_extension) ? 5 : 10 );
+
+			/* Get text values */
+			for ( i = 0; i <= max_text; i++ ) {
+				const char *value = sieve_setting_get
+					(svinst, t_strdup_printf("sieve_%s_text_value%d", ext_name, i));
+
+				if ( value != NULL && *value != '\0' )
+					ext_data->text_values[i] = p_strdup(ext_data->pool, value);
+			}
+
+			ext_data->max_value = 1;
+		}
+	}
+
+	if ( result ) {
+		*context = (void *) ext_data;
+	} else {
+		sieve_sys_warning(svinst,
+			"%s: extension not configured, tests will always match against \"0\"",
+			ext_name);
+		ext_spamvirustest_unload(ext);
+		*context = NULL;
+	}
+
+	return result;
+}
+
+void ext_spamvirustest_unload(const struct sieve_extension *ext)
+{
+	struct ext_spamvirustest_data *ext_data =
+		(struct ext_spamvirustest_data *) ext->context;
+
+	if ( ext_data != NULL ) {
+		ext_spamvirustest_header_spec_free(&ext_data->status_header);
+		ext_spamvirustest_header_spec_free(&ext_data->max_header);
+		pool_unref(&ext_data->pool);
+	}
+}
+
+/*
+ * Runtime
+ */
+
+struct ext_spamvirustest_message_context {
+	int reload;
+	float score_ratio;
+};
+
+static const char *ext_spamvirustest_get_score
+(const struct sieve_extension *ext, float score_ratio, bool percent)
+{
+	int score;
+
+	if ( score_ratio < 0 )
+		return "0";
+
+	if ( score_ratio > 1 )
+		score_ratio = 1;
+
+	if ( percent )
+		score = score_ratio * 100 + 0.001;
+	else if ( sieve_extension_is(ext, virustest_extension) )
+		score = score_ratio * 4 + 1.001;
+	else
+		score = score_ratio * 9 + 1.001;
+
+	return t_strdup_printf("%d", score);
+}
+
+int ext_spamvirustest_get_value
+(const struct sieve_runtime_env *renv, const struct sieve_extension *ext,
+	 bool percent, const char **value_r)
+{
+	struct ext_spamvirustest_data *ext_data =
+		(struct ext_spamvirustest_data *) ext->context;
+	struct ext_spamvirustest_header_spec *status_header, *max_header;
+	struct sieve_message_context *msgctx = renv->msgctx;
+	struct ext_spamvirustest_message_context *mctx;
+	struct mail *mail;
+	regmatch_t match_values[2];
+	const char *header_value, *error;
+	const char *status = NULL, *max = NULL;
+	float status_value, max_value;
+	unsigned int i, max_text;
+	pool_t pool = sieve_interpreter_pool(renv->interp);
+
+	*value_r = "0";
+
+	/*
+	 * Check whether extension is properly configured
+	 */
+	if ( ext_data == NULL ) {
+		sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+			"error: extension not configured");
+		return SIEVE_EXEC_OK;
+	}
+
+	/*
+	 * Check wether a cached result is available
+	 */
+
+	mctx = 	(struct ext_spamvirustest_message_context *)
+		sieve_message_context_extension_get(msgctx, ext);
+
+	if ( mctx == NULL ) {
+		/* Create new context */
+		mctx = p_new(pool, struct ext_spamvirustest_message_context, 1);
+		sieve_message_context_extension_set(msgctx, ext, (void *)mctx);
+	} else if ( mctx->reload == ext_data->reload ) {
+		/* Use cached result */
+		*value_r = ext_spamvirustest_get_score(ext, mctx->score_ratio, percent);
+		return SIEVE_EXEC_OK;
+	} else {
+		/* Extension was reloaded (probably in testsuite) */
+	}
+
+	mctx->reload = ext_data->reload;
+
+	/*
+	 * Get max status value
+	 */
+
+	mail = sieve_message_get_mail(renv->msgctx);
+	status_header = &ext_data->status_header;
+	max_header = &ext_data->max_header;
+
+	if ( ext_data->status_type != EXT_SPAMVIRUSTEST_STATUS_TYPE_TEXT ) {
+		if ( max_header->header_name != NULL ) {
+			/* Get header from message */
+			if ( mail_get_first_header_utf8
+				(mail, max_header->header_name, &header_value) < 0 ) {
+				return sieve_runtime_mail_error	(renv, mail,
+					"%s test: failed to read header field `%s'",
+					sieve_extension_name(ext), max_header->header_name);
+			}
+			if (	header_value == NULL ) {
+				sieve_runtime_trace(renv,  SIEVE_TRLVL_TESTS,
+					"header '%s' not found in message",
+					max_header->header_name);
+				goto failed;
+			}
+
+			if ( max_header->regexp_match ) {
+				/* Execute regex */
+				if ( regexec(&max_header->regexp, header_value, 2, match_values, 0)
+					!= 0 ) {
+					sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+						"regexp for header '%s' did not match "
+						"on value '%s'", max_header->header_name, header_value);
+					goto failed;
+				}
+
+				max = _regexp_match_get_value(header_value, 1, match_values, 2);
+				if ( max == NULL ) {
+					sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+						"regexp did not return match value "
+						"for string '%s'", header_value);
+					goto failed;
+				}
+			} else {
+				max = header_value;
+			}
+
+			if ( !ext_spamvirustest_parse_decimal_value(max, &max_value, &error) ) {
+				sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+					"failed to parse maximum value: %s", error);
+				goto failed;
+			}
+		} else {
+			max_value = ext_data->max_value;
+		}
+
+		if ( max_value == 0 ) {
+			sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+				"error: max value is 0");
+			goto failed;
+		}
+	} else {
+		max_value = ( sieve_extension_is(ext, virustest_extension) ? 5 : 10 );
+	}
+
+	/*
+	 * Get status value
+	 */
+
+	/* Get header from message */
+	if ( mail_get_first_header_utf8
+		(mail, status_header->header_name, &header_value) < 0 ) {
+		return sieve_runtime_mail_error	(renv, mail,
+			"%s test: failed to read header field `%s'",
+			sieve_extension_name(ext), status_header->header_name);
+	}
+	if ( header_value == NULL ) {
+		sieve_runtime_trace(renv,  SIEVE_TRLVL_TESTS,
+			"header '%s' not found in message",
+			status_header->header_name);
+		goto failed;
+	}
+
+	/* Execute regex */
+	if ( status_header->regexp_match ) {
+		if ( regexec(&status_header->regexp, header_value, 2, match_values, 0)
+			!= 0 ) {
+			sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+				"regexp for header '%s' did not match on value '%s'",
+				status_header->header_name, header_value);
+			goto failed;
+		}
+
+		status = _regexp_match_get_value(header_value, 1, match_values, 2);
+		if ( status == NULL ) {
+			sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+				"regexp did not return match value for string '%s'",
+				header_value);
+			goto failed;
+		}
+	} else {
+		status = header_value;
+	}
+
+	switch ( ext_data->status_type ) {
+	case EXT_SPAMVIRUSTEST_STATUS_TYPE_SCORE:
+		if ( !ext_spamvirustest_parse_decimal_value
+			(status, &status_value, &error) ) {
+			sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+				"failed to parse status value '%s': %s",
+				status, error);
+			goto failed;
+		}
+		break;
+	case EXT_SPAMVIRUSTEST_STATUS_TYPE_STRLEN:
+		if ( !ext_spamvirustest_parse_strlen_value
+			(status, &status_value, &error) ) {
+			sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+				"failed to parse status value '%s': %s",
+				status, error);
+			goto failed;
+		}
+		break;
+	case EXT_SPAMVIRUSTEST_STATUS_TYPE_TEXT:
+		max_text = ( sieve_extension_is(ext, virustest_extension) ? 5 : 10 );
+		status_value = 0;
+
+		i = 0;
+		while ( i <= max_text ) {
+			if ( ext_data->text_values[i] != NULL &&
+				strcmp(status, ext_data->text_values[i]) == 0 ) {
+				status_value = (float) i;
+				break;
+			}
+			i++;
+		}
+
+		if ( i > max_text ) {
+			sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+				"failed to match textstatus value '%s'",
+				status);
+			goto failed;
+		}
+		break;
+	default:
+		i_unreached();
+		break;
+	}
+
+	/* Calculate value */
+	if ( status_value < 0 )
+		mctx->score_ratio = 0;
+	else if ( status_value > max_value )
+		mctx->score_ratio = 1;
+	else
+		mctx->score_ratio = (status_value / max_value);
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+		"extracted score=%.3f, max=%.3f, ratio=%.0f %%",
+		status_value, max_value, mctx->score_ratio * 100);
+
+	*value_r = ext_spamvirustest_get_score(ext, mctx->score_ratio, percent);
+	return SIEVE_EXEC_OK;
+
+failed:
+	mctx->score_ratio = -1;
+	*value_r = "0";
+	return SIEVE_EXEC_OK;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/spamvirustest/ext-spamvirustest-common.h
@@ -0,0 +1,35 @@
+#ifndef EXT_SPAMVIRUSTEST_COMMON_H
+#define EXT_SPAMVIRUSTEST_COMMON_H
+
+#include "sieve-common.h"
+
+/*
+ * Extensions
+ */
+
+extern const struct sieve_extension_def spamtest_extension;
+extern const struct sieve_extension_def spamtestplus_extension;
+extern const struct sieve_extension_def virustest_extension;
+
+bool ext_spamvirustest_load(const struct sieve_extension *ext, void **context);
+void ext_spamvirustest_unload(const struct sieve_extension *ext);
+
+/*
+ * Tests
+ */
+
+extern const struct sieve_command_def spamtest_test;
+extern const struct sieve_command_def virustest_test;
+
+int ext_spamvirustest_get_value
+(const struct sieve_runtime_env *renv, const struct sieve_extension *ext,
+	 bool percent, const char **value_r);
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def spamtest_operation;
+extern const struct sieve_operation_def virustest_operation;
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/spamvirustest/ext-spamvirustest.c
@@ -0,0 +1,146 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extensions spamtest, spamtestplus and virustest
+ * -----------------------------------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5235
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+/* Configuration examples:
+ *
+ * # 1: X-Spam-Score: No, score=-3.2
+ *
+ * sieve_spamtest_status_header = \
+ *   X-Spam-Score: [[:alnum:]]+, score=(-?[[:digit:]]+\.[[:digit:]])
+ * sieve_spamtest_max_value = 5.0
+ *
+ * # 2: X-Spam-Status: Yes
+ *
+ * sieve_spamtest_status_header = X-Spam-Status
+ * sieve_spamtest_status_type = yesno
+ * sieve_spamtest_max_value = Yes
+ *
+ * # 3: X-Spam-Score: sssssss
+ * sieve_spamtest_status_header = X-Spam-Score
+ * sieve_spamtest_status_type = strlen
+ * sieve_spamtest_max_value = 5
+ *
+ * # 4: X-Spam-Score: status=3.2 required=5.0
+ *
+ * sieve_spamtest_status_header = \
+ *   X-Spam-Score: score=(-?[[:digit:]]+\.[[:digit:]]).*
+ * sieve_spamtest_max_header = \
+ *   X-Spam-Score: score=-?[[:digit:]]+\.[[:digit:]] required=([[:digit:]]+\.[[:digit:]])
+ *
+ * # 5: X-Virus-Scan: Found to be clean.
+ *
+ * sieve_virustest_status_header = \
+ *   X-Virus-Scan: Found to be (.+)\.
+ * sieve_virustest_status_type = text
+ * sieve_virustest_text_value1 = clean
+ * sieve_virustest_text_value5 = infected
+ */
+
+#include "lib.h"
+#include "array.h"
+
+#include "sieve-common.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+
+#include "sieve-validator.h"
+
+#include "ext-spamvirustest-common.h"
+
+/*
+ * Extensions
+ */
+
+/* Spamtest */
+
+static bool ext_spamvirustest_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *validator);
+
+const struct sieve_extension_def spamtest_extension = {
+	.name = "spamtest",
+	.load = ext_spamvirustest_load,
+	.unload = ext_spamvirustest_unload,
+	.validator_load = ext_spamvirustest_validator_load,
+	SIEVE_EXT_DEFINE_OPERATION(spamtest_operation)
+};
+
+const struct sieve_extension_def spamtestplus_extension = {
+	.name = "spamtestplus",
+	.load = ext_spamvirustest_load,
+	.unload = ext_spamvirustest_unload,
+	.validator_load = ext_spamvirustest_validator_load,
+	SIEVE_EXT_DEFINE_OPERATION(spamtest_operation)
+};
+
+const struct sieve_extension_def virustest_extension = {
+	.name = "virustest",
+	.load = ext_spamvirustest_load,
+	.unload = ext_spamvirustest_unload,
+	.validator_load = ext_spamvirustest_validator_load,
+	SIEVE_EXT_DEFINE_OPERATION(virustest_operation)
+};
+
+/*
+ * Implementation
+ */
+
+static bool ext_spamtest_validator_check_conflict
+	(const struct sieve_extension *ext,
+		struct sieve_validator *valdtr, void *context,
+		struct sieve_ast_argument *require_arg,
+		const struct sieve_extension *ext_other,
+		bool required);
+
+const struct sieve_validator_extension spamtest_validator_extension = {
+	.ext = &spamtest_extension,
+	.check_conflict = ext_spamtest_validator_check_conflict
+};
+
+static bool ext_spamvirustest_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	/* Register new test */
+
+	if ( sieve_extension_is(ext, virustest_extension) ) {
+		sieve_validator_register_command(valdtr, ext, &virustest_test);
+	} else {
+		if ( sieve_extension_is(ext, spamtest_extension) ) {
+			/* Register validator extension to warn for duplicate */
+			sieve_validator_extension_register
+				(valdtr, ext, &spamtest_validator_extension, NULL);
+		}
+
+		sieve_validator_register_command(valdtr, ext, &spamtest_test);
+	}
+
+	return TRUE;
+}
+
+static bool ext_spamtest_validator_check_conflict
+(const struct sieve_extension *ext ATTR_UNUSED,
+	struct sieve_validator *valdtr, void *context ATTR_UNUSED,
+	struct sieve_ast_argument *require_arg,
+	const struct sieve_extension *ext_other,
+	bool required ATTR_UNUSED)
+{
+	if ( sieve_extension_name_is(ext_other, "spamtestplus") ) {
+		sieve_argument_validate_warning(valdtr, require_arg,
+			"the spamtest and spamtestplus extensions should "
+			"not be specified at the same time");
+	}
+
+	return TRUE;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/spamvirustest/tst-spamvirustest.c
@@ -0,0 +1,304 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-address-parts.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+#include "ext-spamvirustest-common.h"
+
+/*
+ * Tests
+ */
+
+static bool tst_spamvirustest_validate
+	(struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_spamvirustest_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+static bool tst_spamvirustest_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+
+/* Spamtest test
+ *
+ * Syntax:
+ *   spamtest [":percent"] [COMPARATOR] [MATCH-TYPE] <value: string>
+ */
+
+const struct sieve_command_def spamtest_test = {
+	.identifier = "spamtest",
+	.type = SCT_TEST,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = tst_spamvirustest_registered,
+	.validate = tst_spamvirustest_validate,
+	.generate = tst_spamvirustest_generate
+};
+
+/* Virustest test
+ *
+ * Syntax:
+ *   virustest [COMPARATOR] [MATCH-TYPE] <value: string>
+ */
+
+const struct sieve_command_def virustest_test = {
+	.identifier = "virustest",
+	.type = SCT_TEST,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = tst_spamvirustest_registered,
+	.validate = tst_spamvirustest_validate,
+	.generate = tst_spamvirustest_generate
+};
+
+/*
+ * Tagged arguments
+ */
+
+static bool tst_spamtest_validate_percent_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *tst);
+
+static const struct sieve_argument_def spamtest_percent_tag = {
+ 	.identifier = "percent",
+	.validate = tst_spamtest_validate_percent_tag
+};
+
+/*
+ * Spamtest and virustest operations
+ */
+
+static bool tst_spamvirustest_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_spamvirustest_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def spamtest_operation = {
+	.mnemonic = "SPAMTEST",
+	.ext_def = &spamtest_extension,
+	.dump = tst_spamvirustest_operation_dump,
+	.execute = tst_spamvirustest_operation_execute
+};
+
+const struct sieve_operation_def virustest_operation = {
+	.mnemonic = "VIRUSTEST",
+	.ext_def = &virustest_extension,
+	.dump = tst_spamvirustest_operation_dump,
+	.execute = tst_spamvirustest_operation_execute
+};
+
+/*
+ * Optional operands
+ */
+
+enum tst_spamvirustest_optional {
+	OPT_SPAMTEST_PERCENT = SIEVE_MATCH_OPT_LAST,
+	OPT_SPAMTEST_LAST
+};
+
+/*
+ * Test registration
+ */
+
+static bool tst_spamvirustest_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg)
+{
+	sieve_comparators_link_tag(valdtr, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR);
+	sieve_match_types_link_tags(valdtr, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
+
+	if ( sieve_extension_is(ext, spamtestplus_extension) ||
+		sieve_extension_is(ext, spamtest_extension) ) {
+		sieve_validator_register_tag
+			(valdtr, cmd_reg, ext, &spamtest_percent_tag, OPT_SPAMTEST_PERCENT);
+	}
+
+	return TRUE;
+}
+
+/*
+ * Validation
+ */
+
+static bool tst_spamtest_validate_percent_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_command *tst)
+{
+	if ( !sieve_extension_is(tst->ext, spamtestplus_extension) ) {
+		sieve_argument_validate_error(valdtr, *arg,
+			"the spamtest test only accepts the :percent argument when "
+			"the spamtestplus extension is active");
+		return FALSE;
+	}
+
+	/* Skip tag */
+	*arg = sieve_ast_argument_next(*arg);
+
+	return TRUE;
+}
+
+static bool tst_spamvirustest_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+	struct sieve_ast_argument *arg = tst->first_positional;
+	const struct sieve_match_type mcht_default =
+		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+	const struct sieve_comparator cmp_default =
+		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+
+	/* Check value */
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "value", 1, SAAT_STRING) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+		return FALSE;
+
+	/* Validate the key argument to a specified match type */
+	return sieve_match_type_validate
+		(valdtr, tst, arg, &mcht_default, &cmp_default);
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_spamvirustest_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
+{
+	if ( sieve_command_is(tst, spamtest_test) )
+		sieve_operation_emit(cgenv->sblock, tst->ext, &spamtest_operation);
+	else if ( sieve_command_is(tst, virustest_test) )
+		sieve_operation_emit(cgenv->sblock, tst->ext, &virustest_operation);
+	else
+		i_unreached();
+
+	/* Generate arguments */
+	return sieve_generate_arguments(cgenv, tst, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_spamvirustest_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	int opt_code = 0;
+	const struct sieve_operation *op = denv->oprtn;
+
+	sieve_code_dumpf(denv, "%s", sieve_operation_mnemonic(op));
+	sieve_code_descend(denv);
+
+	/* Optional operands */
+	for (;;) {
+		int opt;
+
+		if ( (opt=sieve_match_opr_optional_dump(denv, address, &opt_code)) < 0 )
+			return FALSE;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_SPAMTEST_PERCENT:
+			sieve_code_dumpf(denv, "percent");
+			break;
+    default:
+			return FALSE;
+		}
+	}
+
+	return
+		sieve_opr_string_dump(denv, address, "value");
+}
+
+/*
+ * Code execution
+ */
+
+static int tst_spamvirustest_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	const struct sieve_operation *op = renv->oprtn;
+	const struct sieve_extension *this_ext = op->ext;
+	int opt_code = 0;
+	struct sieve_match_type mcht =
+		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+	struct sieve_comparator cmp =
+		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+	bool percent = FALSE;
+	struct sieve_stringlist *value_list, *key_list;
+	const char *score_value;
+	int match, ret;
+
+	/* Read optional operands */
+	for (;;) {
+		int opt;
+
+		if ( (opt=sieve_match_opr_optional_read
+			(renv, address, &opt_code, &ret, &cmp, &mcht)) < 0 )
+			return ret;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_SPAMTEST_PERCENT:
+			percent = TRUE;
+			break;
+		default:
+			sieve_runtime_trace_error(renv, "unknown optional operand");
+			return SIEVE_EXEC_BIN_CORRUPT;
+		}
+	}
+
+	/* Read value part */
+	if ( (ret=sieve_opr_stringlist_read(renv, address, "value", &key_list)) <= 0 )
+		return ret;
+
+	/* Perform test */
+
+	if ( sieve_operation_is(op, spamtest_operation) ) {
+		sieve_runtime_trace
+			(renv, SIEVE_TRLVL_TESTS, "spamtest test [percent=%s]",
+				( percent ? "true" : "false" ));
+	} else {
+		sieve_runtime_trace
+			(renv, SIEVE_TRLVL_TESTS, "virustest test");
+	}
+
+	/* Get score value */
+	sieve_runtime_trace_descend(renv);
+	if ( (ret=ext_spamvirustest_get_value
+		(renv, this_ext, percent, &score_value)) <= 0 )
+		return ret;
+	sieve_runtime_trace_ascend(renv);
+
+	/* Construct value list */
+	value_list = sieve_single_stringlist_create_cstr(renv, score_value, TRUE);
+
+	/* Perform match */
+	if ( (match=sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret)) < 0 )
+		return ret;
+
+	/* Set test result for subsequent conditional jump */
+	sieve_interpreter_set_test_result(renv->interp, match > 0);
+	return SIEVE_EXEC_OK;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/subaddress/Makefile.am
@@ -0,0 +1,8 @@
+noinst_LTLIBRARIES = libsieve_ext_subaddress.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../.. \
+	$(LIBDOVECOT_INCLUDE)
+
+libsieve_ext_subaddress_la_SOURCES = \
+	ext-subaddress.c
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/subaddress/ext-subaddress.c
@@ -0,0 +1,191 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension subaddress
+ * --------------------
+ *
+ * Author: Stephan Bosch
+ * Specification: RFC 3598
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "sieve-common.h"
+
+#include "sieve-settings.h"
+#include "sieve-code.h"
+#include "sieve-address.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-address-parts.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+
+#include <string.h>
+
+/*
+ * Configuration
+ */
+
+#define SUBADDRESS_DEFAULT_DELIM "+"
+
+struct ext_subaddress_config {
+	char *delimiter;
+};
+
+/*
+ * Forward declarations
+ */
+
+const struct sieve_address_part_def user_address_part;
+const struct sieve_address_part_def detail_address_part;
+
+static struct sieve_operand_def subaddress_operand;
+
+/*
+ * Extension
+ */
+
+static bool ext_subaddress_load
+	(const struct sieve_extension *ext, void **context);
+static void ext_subaddress_unload
+	(const struct sieve_extension *ext);
+static bool ext_subaddress_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *validator);
+
+const struct sieve_extension_def subaddress_extension = {
+	.name = "subaddress",
+	.load = ext_subaddress_load,
+	.unload = ext_subaddress_unload,
+	.validator_load = ext_subaddress_validator_load,
+	SIEVE_EXT_DEFINE_OPERAND(subaddress_operand)
+};
+
+static bool ext_subaddress_load
+(const struct sieve_extension *ext, void **context)
+{
+	struct ext_subaddress_config *config;
+	const char *delim;
+
+	if ( *context != NULL ) {
+		ext_subaddress_unload(ext);
+	}
+
+	delim = sieve_setting_get(ext->svinst, "recipient_delimiter");
+
+	if ( delim == NULL )
+		delim = SUBADDRESS_DEFAULT_DELIM;
+
+	config = i_new(struct ext_subaddress_config, 1);
+	config->delimiter = i_strdup(delim);
+
+	*context = (void *) config;
+
+	return TRUE;
+}
+
+static void ext_subaddress_unload
+(const struct sieve_extension *ext)
+{
+	struct ext_subaddress_config *config =
+		(struct ext_subaddress_config *) ext->context;
+
+	i_free(config->delimiter);
+	i_free(config);
+}
+
+static bool ext_subaddress_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *validator)
+{
+	sieve_address_part_register(validator, ext, &user_address_part);
+	sieve_address_part_register(validator, ext, &detail_address_part);
+
+	return TRUE;
+}
+
+/*
+ * Address parts
+ */
+
+enum ext_subaddress_address_part {
+  SUBADDRESS_USER,
+  SUBADDRESS_DETAIL
+};
+
+/* Forward declarations */
+
+static const char *subaddress_user_extract_from
+	(const struct sieve_address_part *addrp, const struct smtp_address *address);
+static const char *subaddress_detail_extract_from
+	(const struct sieve_address_part *addrp, const struct smtp_address *address);
+
+/* Address part objects */
+
+const struct sieve_address_part_def user_address_part = {
+	SIEVE_OBJECT("user",
+		&subaddress_operand, SUBADDRESS_USER),
+	subaddress_user_extract_from
+};
+
+const struct sieve_address_part_def detail_address_part = {
+	SIEVE_OBJECT("detail",
+		&subaddress_operand, SUBADDRESS_DETAIL),
+	.extract_from = subaddress_detail_extract_from
+};
+
+/* Address part implementation */
+
+static const char *subaddress_user_extract_from
+(const struct sieve_address_part *addrp, const struct smtp_address *address)
+{
+	struct ext_subaddress_config *config =
+		(struct ext_subaddress_config *) addrp->object.ext->context;
+	const char *delim;
+	size_t idx;
+
+	idx = strcspn(address->localpart, config->delimiter);
+	delim = address->localpart[idx] != '\0' ? address->localpart + idx : NULL;
+
+	if ( delim == NULL ) return address->localpart;
+
+	return t_strdup_until(address->localpart, delim);
+}
+
+static const char *subaddress_detail_extract_from
+(const struct sieve_address_part *addrp, const struct smtp_address *address)
+{
+	struct ext_subaddress_config *config =
+		(struct ext_subaddress_config *) addrp->object.ext->context;
+	const char *delim;
+	size_t idx;
+
+	idx = strcspn(address->localpart, config->delimiter);
+	delim = address->localpart[idx] != '\0' ? address->localpart + idx + 1: NULL;
+
+	/* Just to be sure */
+	if ( delim == NULL ||
+		delim > (address->localpart + strlen(address->localpart)) )
+		return NULL;
+	return delim;
+}
+
+/*
+ * Operand
+ */
+
+const struct sieve_address_part_def *ext_subaddress_parts[] = {
+	&user_address_part, &detail_address_part
+};
+
+static const struct sieve_extension_objects ext_address_parts =
+	SIEVE_EXT_DEFINE_ADDRESS_PARTS(ext_subaddress_parts);
+
+static struct sieve_operand_def subaddress_operand = {
+	.name = "address-part",
+	.ext_def = &subaddress_extension,
+	.class = &sieve_address_part_operand_class,
+	.interface = &ext_address_parts
+};
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/vacation/Makefile.am
@@ -0,0 +1,19 @@
+noinst_LTLIBRARIES = libsieve_ext_vacation.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../.. \
+	-I$(srcdir)/../../util \
+	$(LIBDOVECOT_INCLUDE)
+
+cmds = \
+	cmd-vacation.c
+
+libsieve_ext_vacation_la_SOURCES = \
+	$(cmds) \
+	ext-vacation-common.c \
+	ext-vacation.c \
+	ext-vacation-seconds.c
+
+noinst_HEADERS = \
+	ext-vacation-common.h
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/vacation/cmd-vacation.c
@@ -0,0 +1,1444 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "strfuncs.h"
+#include "md5.h"
+#include "hostpid.h"
+#include "str-sanitize.h"
+#include "ostream.h"
+#include "message-address.h"
+#include "message-date.h"
+#include "ioloop.h"
+#include "mail-storage.h"
+
+#include "rfc2822.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-address.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-result.h"
+#include "sieve-message.h"
+#include "sieve-smtp.h"
+
+#include "ext-vacation-common.h"
+
+#include <stdio.h>
+
+/*
+ * Forward declarations
+ */
+
+static const struct sieve_argument_def vacation_days_tag;
+static const struct sieve_argument_def vacation_subject_tag;
+static const struct sieve_argument_def vacation_from_tag;
+static const struct sieve_argument_def vacation_addresses_tag;
+static const struct sieve_argument_def vacation_mime_tag;
+static const struct sieve_argument_def vacation_handle_tag;
+
+/*
+ * Vacation command
+ *
+ * Syntax:
+ *    vacation [":days" number] [":subject" string]
+ *                 [":from" string] [":addresses" string-list]
+ *                 [":mime"] [":handle" string] <reason: string>
+ */
+
+static bool cmd_vacation_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool cmd_vacation_pre_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_vacation_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_vacation_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+const struct sieve_command_def vacation_command = {
+	.identifier = "vacation",
+	.type = SCT_COMMAND,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = cmd_vacation_registered,
+	.pre_validate = cmd_vacation_pre_validate,
+	.validate = cmd_vacation_validate,
+	.generate = cmd_vacation_generate,
+};
+
+/*
+ * Vacation command tags
+ */
+
+/* Forward declarations */
+
+static bool cmd_vacation_validate_number_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+static bool cmd_vacation_validate_string_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+static bool cmd_vacation_validate_stringlist_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+static bool cmd_vacation_validate_mime_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+
+/* Argument objects */
+
+static const struct sieve_argument_def vacation_days_tag = {
+	.identifier = "days",
+	.validate = cmd_vacation_validate_number_tag
+};
+
+static const struct sieve_argument_def vacation_seconds_tag = {
+	.identifier = "seconds",
+	.validate = cmd_vacation_validate_number_tag
+};
+
+static const struct sieve_argument_def vacation_subject_tag = {
+	.identifier = "subject",
+	.validate = cmd_vacation_validate_string_tag
+};
+
+static const struct sieve_argument_def vacation_from_tag = {
+	.identifier = "from",
+	.validate = cmd_vacation_validate_string_tag
+};
+
+static const struct sieve_argument_def vacation_addresses_tag = {
+	.identifier = "addresses",
+	.validate = cmd_vacation_validate_stringlist_tag
+};
+
+static const struct sieve_argument_def vacation_mime_tag = {
+	.identifier = "mime",
+	.validate = cmd_vacation_validate_mime_tag
+};
+
+static const struct sieve_argument_def vacation_handle_tag = {
+	.identifier = "handle",
+	.validate = cmd_vacation_validate_string_tag
+};
+
+/* Codes for optional arguments */
+
+enum cmd_vacation_optional {
+	OPT_END,
+	OPT_SECONDS,
+	OPT_SUBJECT,
+	OPT_FROM,
+	OPT_ADDRESSES,
+	OPT_MIME
+};
+
+/*
+ * Vacation operation
+ */
+
+static bool ext_vacation_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int ext_vacation_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def vacation_operation = {
+	.mnemonic = "VACATION",
+	.ext_def = &vacation_extension,
+	.dump = ext_vacation_operation_dump,
+	.execute = ext_vacation_operation_execute
+};
+
+/*
+ * Vacation action
+ */
+
+/* Forward declarations */
+
+static int act_vacation_check_duplicate
+	(const struct sieve_runtime_env *renv,
+		const struct sieve_action *act,
+		const struct sieve_action *act_other);
+int act_vacation_check_conflict
+	(const struct sieve_runtime_env *renv,
+		const struct sieve_action *act,
+		const struct sieve_action *act_other);
+static void act_vacation_print
+	(const struct sieve_action *action,
+		const struct sieve_result_print_env *rpenv, bool *keep);
+static int act_vacation_commit
+	(const struct sieve_action *action,	const struct sieve_action_exec_env *aenv,
+		void *tr_context, bool *keep);
+
+/* Action object */
+
+const struct sieve_action_def act_vacation = {
+	.name = "vacation",
+	.flags = SIEVE_ACTFLAG_SENDS_RESPONSE,
+	.check_duplicate = act_vacation_check_duplicate,
+	.check_conflict = act_vacation_check_conflict,
+	.print = act_vacation_print,
+	.commit = act_vacation_commit
+};
+
+/* Action context information */
+
+struct act_vacation_context {
+	const char *reason;
+
+	sieve_number_t seconds;
+	const char *subject;
+	const char *handle;
+	bool mime;
+	const char *from;
+	const struct smtp_address *from_address;
+	const struct smtp_address *const *addresses;
+};
+
+/*
+ * Command validation context
+ */
+
+struct cmd_vacation_context_data {
+	string_t *from;
+	string_t *subject;
+
+	bool mime;
+
+	struct sieve_ast_argument *handle_arg;
+};
+
+/*
+ * Tag validation
+ */
+
+static bool cmd_vacation_validate_number_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	const struct sieve_extension *ext = sieve_argument_ext(*arg);
+	const struct ext_vacation_config *config =
+		(const struct ext_vacation_config *) ext->context;
+	struct sieve_ast_argument *tag = *arg;
+	sieve_number_t period, seconds;
+
+	/* Detach the tag itself */
+	*arg = sieve_ast_arguments_detach(*arg,1);
+
+	/* Check syntax:
+	 *   :days number
+	 */
+	if ( !sieve_validate_tag_parameter
+		(valdtr, cmd, tag, *arg, NULL, 0, SAAT_NUMBER, FALSE) ) {
+		return FALSE;
+	}
+
+	period = sieve_ast_argument_number(*arg);
+	if ( sieve_argument_is(tag, vacation_days_tag) ) {
+		seconds = period * (24*60*60);
+
+	} else if ( sieve_argument_is(tag, vacation_seconds_tag) ) {
+		seconds = period;
+
+	} else {
+		i_unreached();
+	}
+
+	/* Enforce :seconds >= min_period */
+	if ( seconds < config->min_period ) {
+		seconds = config->min_period;
+
+		sieve_argument_validate_warning(valdtr, *arg,
+			"specified :%s value '%llu' is under the minimum",
+			sieve_argument_identifier(tag),
+			(unsigned long long)period);
+
+	/* Enforce :days <= max_period */
+	} else if ( config->max_period > 0 && seconds > config->max_period ) {
+		seconds = config->max_period;
+
+		sieve_argument_validate_warning(valdtr, *arg,
+			"specified :%s value '%llu' is over the maximum",
+			sieve_argument_identifier(tag),
+			(unsigned long long)period);
+	}
+
+	sieve_ast_argument_number_set(*arg, seconds);
+
+	/* Skip parameter */
+	*arg = sieve_ast_argument_next(*arg);
+
+	return TRUE;
+}
+
+static bool cmd_vacation_validate_string_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *tag = *arg;
+	struct cmd_vacation_context_data *ctx_data =
+		(struct cmd_vacation_context_data *) cmd->data;
+
+	/* Detach the tag itself */
+	*arg = sieve_ast_arguments_detach(*arg,1);
+
+	/* Check syntax:
+	 *   :subject string
+	 *   :from string
+	 *   :handle string
+	 */
+	if ( !sieve_validate_tag_parameter
+		(valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING, FALSE) ) {
+		return FALSE;
+	}
+
+	if ( sieve_argument_is(tag, vacation_from_tag) ) {
+		if ( sieve_argument_is_string_literal(*arg) ) {
+			string_t *address = sieve_ast_argument_str(*arg);
+			const char *error;
+	 		bool result;
+
+	 		T_BEGIN {
+				result = sieve_address_validate_str(address, &error);
+
+				if ( !result ) {
+					sieve_argument_validate_error(valdtr, *arg,
+						"specified :from address '%s' is invalid for vacation action: %s",
+						str_sanitize(str_c(address), 128), error);
+				}
+			} T_END;
+
+			if ( !result )
+				return FALSE;
+		}
+
+		ctx_data->from = sieve_ast_argument_str(*arg);
+
+		/* Skip parameter */
+		*arg = sieve_ast_argument_next(*arg);
+
+	} else if ( sieve_argument_is(tag, vacation_subject_tag) ) {
+		ctx_data->subject = sieve_ast_argument_str(*arg);
+
+		/* Skip parameter */
+		*arg = sieve_ast_argument_next(*arg);
+
+	} else if ( sieve_argument_is(tag, vacation_handle_tag) ) {
+		ctx_data->handle_arg = *arg;
+
+		/* Detach optional argument (emitted as mandatory) */
+		*arg = sieve_ast_arguments_detach(*arg, 1);
+	}
+
+	return TRUE;
+}
+
+static bool cmd_vacation_validate_stringlist_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *tag = *arg;
+
+	/* Detach the tag itself */
+	*arg = sieve_ast_arguments_detach(*arg,1);
+
+	/* Check syntax:
+	 *   :addresses string-list
+	 */
+	if ( !sieve_validate_tag_parameter
+		(valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING_LIST, FALSE) ) {
+		return FALSE;
+	}
+
+	/* Skip parameter */
+	*arg = sieve_ast_argument_next(*arg);
+
+	return TRUE;
+}
+
+static bool cmd_vacation_validate_mime_tag
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	struct cmd_vacation_context_data *ctx_data =
+		(struct cmd_vacation_context_data *) cmd->data;
+
+	ctx_data->mime = TRUE;
+
+	/* Skip tag */
+	*arg = sieve_ast_argument_next(*arg);
+
+	return TRUE;
+}
+
+/*
+ * Command registration
+ */
+
+static bool cmd_vacation_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg)
+{
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &vacation_days_tag, OPT_SECONDS);
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &vacation_subject_tag, OPT_SUBJECT);
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &vacation_from_tag, OPT_FROM);
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &vacation_addresses_tag, OPT_ADDRESSES);
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &vacation_mime_tag, OPT_MIME);
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &vacation_handle_tag, 0);
+
+	return TRUE;
+}
+
+bool ext_vacation_register_seconds_tag
+(struct sieve_validator *valdtr, const struct sieve_extension *vacation_ext)
+{
+	sieve_validator_register_external_tag
+		(valdtr, vacation_command.identifier, vacation_ext, &vacation_seconds_tag,
+			OPT_SECONDS);
+
+	return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+static bool cmd_vacation_pre_validate
+(struct sieve_validator *valdtr ATTR_UNUSED,
+	struct sieve_command *cmd)
+{
+	struct cmd_vacation_context_data *ctx_data;
+
+	/* Assign context */
+	ctx_data = p_new(sieve_command_pool(cmd),
+		struct cmd_vacation_context_data, 1);
+	cmd->data = ctx_data;
+
+	return TRUE;
+}
+
+static const char _handle_empty_subject[] = "<default-subject>";
+static const char _handle_empty_from[] = "<default-from>";
+static const char _handle_mime_enabled[] = "<MIME>";
+static const char _handle_mime_disabled[] = "<NO-MIME>";
+
+static bool cmd_vacation_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *arg = cmd->first_positional;
+	struct cmd_vacation_context_data *ctx_data =
+		(struct cmd_vacation_context_data *) cmd->data;
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "reason", 1, SAAT_STRING) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, cmd, arg, FALSE) )
+		return FALSE;
+
+	/* Construct handle if not set explicitly */
+	if ( ctx_data->handle_arg == NULL ) {
+		T_BEGIN {
+			string_t *handle;
+			string_t *reason = sieve_ast_argument_str(arg);
+			unsigned int size = str_len(reason);
+
+			/* Precalculate the size of it all */
+			size += ctx_data->subject == NULL ?
+				sizeof(_handle_empty_subject) - 1 : str_len(ctx_data->subject);
+			size += ctx_data->from == NULL ?
+				sizeof(_handle_empty_from) - 1 : str_len(ctx_data->from);
+			size += ctx_data->mime ?
+				sizeof(_handle_mime_enabled) - 1 : sizeof(_handle_mime_disabled) - 1;
+
+			/* Construct the string */
+			handle = t_str_new(size);
+			str_append_str(handle, reason);
+
+			if ( ctx_data->subject != NULL )
+				str_append_str(handle, ctx_data->subject);
+			else
+				str_append(handle, _handle_empty_subject);
+
+			if ( ctx_data->from != NULL )
+				str_append_str(handle, ctx_data->from);
+			else
+				str_append(handle, _handle_empty_from);
+
+			str_append(handle,
+				ctx_data->mime ? _handle_mime_enabled : _handle_mime_disabled );
+
+			/* Create positional handle argument */
+			ctx_data->handle_arg = sieve_ast_argument_string_create
+				(cmd->ast_node, handle, sieve_ast_node_line(cmd->ast_node));
+		} T_END;
+
+		if ( !sieve_validator_argument_activate
+			(valdtr, cmd, ctx_data->handle_arg, TRUE) )
+			return FALSE;
+	} else {
+		/* Attach explicit handle argument as positional */
+		(void)sieve_ast_argument_attach(cmd->ast_node, ctx_data->handle_arg);
+	}
+
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_vacation_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	sieve_operation_emit(cgenv->sblock, cmd->ext, &vacation_operation);
+
+	/* Generate arguments */
+	if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
+		return FALSE;
+
+	return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool ext_vacation_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	int opt_code = 0;
+
+	sieve_code_dumpf(denv, "VACATION");
+	sieve_code_descend(denv);
+
+	/* Dump optional operands */
+
+	for (;;) {
+		int opt;
+		bool opok = TRUE;
+
+		if ( (opt=sieve_opr_optional_dump(denv, address, &opt_code)) < 0 )
+			return FALSE;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_SECONDS:
+			opok = sieve_opr_number_dump(denv, address, "seconds");
+			break;
+		case OPT_SUBJECT:
+			opok = sieve_opr_string_dump(denv, address, "subject");
+			break;
+		case OPT_FROM:
+			opok = sieve_opr_string_dump(denv, address, "from");
+			break;
+		case OPT_ADDRESSES:
+			opok = sieve_opr_stringlist_dump(denv, address, "addresses");
+			break;
+		case OPT_MIME:
+			sieve_code_dumpf(denv, "mime");
+			break;
+		default:
+			return FALSE;
+		}
+
+		if ( !opok ) return FALSE;
+	}
+
+	/* Dump reason and handle operands */
+	return
+		sieve_opr_string_dump(denv, address, "reason") &&
+		sieve_opr_string_dump(denv, address, "handle");
+}
+
+/*
+ * Code execution
+ */
+
+static int ext_vacation_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	const struct sieve_extension *this_ext = renv->oprtn->ext;
+	const struct ext_vacation_config *config =
+		(const struct ext_vacation_config *) this_ext->context;
+	struct sieve_side_effects_list *slist = NULL;
+	struct act_vacation_context *act;
+	pool_t pool;
+	int opt_code = 0;
+	sieve_number_t seconds = config->default_period;
+	bool mime = FALSE;
+	struct sieve_stringlist *addresses = NULL;
+	string_t *reason, *subject = NULL, *from = NULL, *handle = NULL;
+	const struct smtp_address *from_address = NULL;
+	int ret;
+
+	/*
+	 * Read code
+	 */
+
+	/* Optional operands */
+
+	for (;;) {
+		int opt;
+
+		if ( (opt=sieve_opr_optional_read(renv, address, &opt_code)) < 0 )
+			return SIEVE_EXEC_BIN_CORRUPT;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_SECONDS:
+			ret = sieve_opr_number_read(renv, address, "seconds", &seconds);
+			break;
+		case OPT_SUBJECT:
+			ret = sieve_opr_string_read(renv, address, "subject", &subject);
+			break;
+		case OPT_FROM:
+			ret = sieve_opr_string_read(renv, address, "from", &from);
+			break;
+		case OPT_ADDRESSES:
+			ret = sieve_opr_stringlist_read(renv, address, "addresses", &addresses);
+			break;
+		case OPT_MIME:
+			mime = TRUE;
+			ret = SIEVE_EXEC_OK;
+			break;
+		default:
+			sieve_runtime_trace_error(renv, "unknown optional operand");
+			ret = SIEVE_EXEC_BIN_CORRUPT;
+		}
+
+		if ( ret <= 0 ) return ret;
+	}
+
+	/* Fixed operands */
+
+	if ( (ret=sieve_opr_string_read(renv, address, "reason", &reason)) <= 0 ||
+		(ret=sieve_opr_string_read(renv, address, "handle", &handle)) <= 0 ) {
+		return ret;
+	}
+
+	/*
+	 * Perform operation
+	 */
+
+	/* Trace */
+
+	if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_ACTIONS) ) {
+		sieve_runtime_trace(renv, 0, "vacation action");
+		sieve_runtime_trace_descend(renv);
+		sieve_runtime_trace(renv, 0, "auto-reply with message `%s'",
+			str_sanitize(str_c(reason), 80));
+	}
+
+	/* Parse :from address */
+	if ( from != NULL ) {
+		const char *error;
+
+		from_address = sieve_address_parse_str(from, &error);
+		if ( from_address == NULL) {
+			sieve_runtime_error(renv, NULL,
+				"specified :from address '%s' is invalid for vacation action: %s",
+				str_sanitize(str_c(from), 128), error);
+   		}
+	}
+
+	/* Add vacation action to the result */
+
+	pool = sieve_result_pool(renv->result);
+	act = p_new(pool, struct act_vacation_context, 1);
+	act->reason = p_strdup(pool, str_c(reason));
+	act->handle = p_strdup(pool, str_c(handle));
+	act->seconds = seconds;
+	act->mime = mime;
+	if ( subject != NULL )
+		act->subject = p_strdup(pool, str_c(subject));
+	if ( from != NULL ) {
+		act->from = p_strdup(pool, str_c(from));
+		act->from_address = smtp_address_clone(pool, from_address);
+	}
+
+	/* Normalize all addresses */
+	if ( addresses != NULL ) {
+		ARRAY_TYPE(smtp_address_const) addrs;
+		string_t *raw_address;
+		int ret;
+
+		sieve_stringlist_reset(addresses);
+
+		p_array_init(&addrs, pool, 4);
+
+		raw_address = NULL;
+		while ( (ret=sieve_stringlist_next_item(addresses, &raw_address)) > 0 ) {
+			const struct smtp_address *addr;
+			const char *error;
+
+			addr = sieve_address_parse_str(raw_address, &error);
+			if ( addr != NULL ) {
+				addr = smtp_address_clone(pool, addr);
+				array_append(&addrs, &addr, 1);
+
+			} else {
+				sieve_runtime_error(renv, NULL,
+					"specified :addresses item '%s' is invalid: %s for vacation action "
+					"(ignored)",
+					str_sanitize(str_c(raw_address),128), error);
+			}
+		}
+
+		if ( ret < 0 ) {
+			sieve_runtime_trace_error(renv, "invalid addresses stringlist");
+			return SIEVE_EXEC_BIN_CORRUPT;
+		}
+
+		(void)array_append_space(&addrs);
+		act->addresses = array_idx(&addrs, 0);
+	}
+
+	if ( sieve_result_add_action
+		(renv, this_ext, &act_vacation, slist, (void *) act, 0, FALSE) < 0 )
+		return SIEVE_EXEC_FAILURE;
+
+	return SIEVE_EXEC_OK;
+}
+
+/*
+ * Action
+ */
+
+/* Runtime verification */
+
+static int act_vacation_check_duplicate
+(const struct sieve_runtime_env *renv ATTR_UNUSED,
+	const struct sieve_action *act,
+	const struct sieve_action *act_other)
+{
+	if ( !act_other->executed ) {
+		sieve_runtime_error(renv, act->location,
+			"duplicate vacation action not allowed "
+			"(previously triggered one was here: %s)", act_other->location);
+		return -1;
+	}
+
+	/* Not an error if executed in preceeding script */
+	return 1;
+}
+
+int act_vacation_check_conflict
+(const struct sieve_runtime_env *renv,
+	const struct sieve_action *act,
+	const struct sieve_action *act_other)
+{
+	if ( (act_other->def->flags & SIEVE_ACTFLAG_SENDS_RESPONSE) > 0 ) {
+		if ( !act_other->executed && !act->executed) {
+			sieve_runtime_error(renv, act->location,
+				"vacation action conflicts with other action: "
+				"the %s action (%s) also sends a response back to the sender",
+				act_other->def->name, act_other->location);
+			return -1;
+		} else {
+			/* Not an error if executed in preceeding script */
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+/* Result printing */
+
+static void act_vacation_print
+(const struct sieve_action *action ATTR_UNUSED,
+	const struct sieve_result_print_env *rpenv, bool *keep ATTR_UNUSED)
+{
+	struct act_vacation_context *ctx =
+		(struct act_vacation_context *) action->context;
+
+	sieve_result_action_printf( rpenv, "send vacation message:");
+	sieve_result_printf(rpenv, "    => seconds : %llu\n",
+		(unsigned long long)ctx->seconds);
+	if ( ctx->subject != NULL )
+		sieve_result_printf(rpenv, "    => subject : %s\n", ctx->subject);
+	if ( ctx->from != NULL )
+		sieve_result_printf(rpenv, "    => from    : %s\n", ctx->from);
+	if ( ctx->handle != NULL )
+		sieve_result_printf(rpenv, "    => handle  : %s\n", ctx->handle);
+	sieve_result_printf(rpenv, "\nSTART MESSAGE\n%s\nEND MESSAGE\n", ctx->reason);
+}
+
+/* Result execution */
+
+/* Headers known to be associated with mailing lists
+ */
+static const char * const _list_headers[] = {
+	"list-id",
+	"list-owner",
+	"list-subscribe",
+	"list-post",
+	"list-unsubscribe",
+	"list-help",
+	"list-archive",
+	NULL
+};
+
+/* Headers that should be searched for the user's own mail address(es)
+ */
+
+static const char * const _my_address_headers[] = {
+	"to",
+	"cc",
+	"bcc",
+	"resent-to",
+	"resent-cc",
+	"resent-bcc",
+	NULL
+};
+
+/* Headers that should be searched for the full sender address
+ */
+
+static const char * const _sender_headers[] = {
+	"sender",
+	"resent-from",
+	"from",
+	NULL
+};
+
+static inline bool _is_system_address
+(const struct smtp_address *address)
+{
+	if ( strcasecmp(address->localpart, "MAILER-DAEMON") == 0 )
+		return TRUE;
+
+	if ( strcasecmp(address->localpart, "LISTSERV") == 0 )
+		return TRUE;
+
+	if ( strcasecmp(address->localpart, "majordomo") == 0 )
+		return TRUE;
+
+	if ( strstr(address->localpart, "-request") != NULL )
+		return TRUE;
+
+	if ( str_begins(address->localpart, "owner-") )
+		return TRUE;
+
+	return FALSE;
+}
+
+static inline bool _contains_my_address
+(const char * const *headers,
+	const struct smtp_address *my_address)
+{
+	const char *const *hdsp = headers;
+	bool result = FALSE;
+
+	while ( *hdsp != NULL && !result ) {
+		const struct message_address *msg_addr;
+
+		T_BEGIN {
+			msg_addr = message_address_parse
+				(pool_datastack_create(), (const unsigned char *) *hdsp,
+					strlen(*hdsp), 256, 0);
+			while ( msg_addr != NULL && !result ) {
+				if (msg_addr->domain != NULL) {
+					struct smtp_address addr;
+
+					i_assert(msg_addr->mailbox != NULL);
+					if ( smtp_address_init_from_msg(&addr, msg_addr) >= 0 &&
+						smtp_address_equals(&addr, my_address) ) {
+						result = TRUE;
+						break;
+					}
+				}
+
+				msg_addr = msg_addr->next;
+			}
+		} T_END;
+
+		hdsp++;
+	}
+
+	return result;
+}
+
+static bool _contains_8bit(const char *text)
+{
+	const unsigned char *p = (const unsigned char *) text;
+
+	for (; *p != '\0'; p++) {
+		if ((*p & 0x80) != 0)
+			return TRUE;
+	}
+	return FALSE;
+}
+
+static int _get_full_reply_recipient
+(const struct sieve_action_exec_env *aenv,
+	const struct ext_vacation_config *config,
+	const struct smtp_address *smtp_to,
+	struct message_address *reply_to_r)
+{
+	const struct sieve_message_data *msgdata = aenv->msgdata;
+	const char *const *hdsp;
+	int ret;
+
+	hdsp = _sender_headers;
+	while ( *hdsp != NULL ) {
+		const char *header;
+
+		if ( (ret=mail_get_first_header(msgdata->mail,
+			*hdsp, &header)) < 0 ) {
+			return sieve_result_mail_error(aenv, msgdata->mail,
+				"vacation action: "
+				"failed to read header field `%s'", *hdsp);
+		}
+		if ( ret > 0 && header != NULL ) {
+			const struct message_address *addr;
+
+			addr = message_address_parse
+				(pool_datastack_create(), (const unsigned char *) header,
+					strlen(header), 256, 0);
+
+			while ( addr != NULL ) {
+				if ( addr->domain != NULL && !addr->invalid_syntax ) {
+					struct smtp_address saddr;
+					bool matched = config->to_header_ignore_envelope;
+
+					if (!matched) {
+						i_assert(addr->mailbox != NULL);
+
+						matched = ( smtp_address_init_from_msg(&saddr, addr) >= 0 &&
+							smtp_address_equals(smtp_to, &saddr) );
+					}
+
+					if (matched) {
+						*reply_to_r = *addr;
+						return SIEVE_EXEC_OK;
+					}
+				}
+
+				addr = addr->next;
+			}
+		}
+		hdsp++;
+	}
+
+	reply_to_r->mailbox = smtp_to->localpart;
+	reply_to_r->domain = smtp_to->domain;
+	return SIEVE_EXEC_OK;
+}
+
+static int act_vacation_send
+(const struct sieve_action_exec_env *aenv,
+	const struct ext_vacation_config *config,
+	struct act_vacation_context *ctx,
+	const struct smtp_address *smtp_to,
+	const struct smtp_address *smtp_from,
+	const struct message_address *reply_from)
+{
+	const struct sieve_message_data *msgdata = aenv->msgdata;
+	const struct sieve_script_env *senv = aenv->scriptenv;
+	struct sieve_smtp_context *sctx;
+	struct ostream *output;
+	string_t *msg;
+	struct message_address reply_to;
+	const char *header, *outmsgid, *subject, *error;
+	int ret;
+
+	/* Check smpt functions just to be sure */
+
+	if ( !sieve_smtp_available(senv) ) {
+		sieve_result_global_warning(aenv,
+			"vacation action has no means to send mail");
+		return SIEVE_EXEC_OK;
+	}
+
+	/* Make sure we have a subject for our reply */
+
+	if ( ctx->subject == NULL || *(ctx->subject) == '\0' ) {
+		if ( (ret=mail_get_first_header_utf8
+			(msgdata->mail, "subject", &header)) < 0 ) {
+			return sieve_result_mail_error(aenv, msgdata->mail,
+				"vacation action: "
+				"failed to read header field `subject'");
+		}
+		if ( ret > 0 && header != NULL ) {
+			subject = t_strconcat("Auto: ", header, NULL);
+		}	else {
+			subject = "Automated reply";
+		}
+	}	else {
+		subject = ctx->subject;
+	}
+
+	subject = str_sanitize_utf8(subject, config->max_subject_codepoints);
+
+	/* Obtain full To address for reply */
+
+	i_zero(&reply_to);
+	reply_to.mailbox = smtp_to->localpart;
+	reply_to.domain = smtp_to->domain;
+	if ((ret=_get_full_reply_recipient(aenv, config,
+		smtp_to, &reply_to)) <= 0)
+		return ret;
+
+	/* Open smtp session */
+
+	sctx = sieve_smtp_start_single(senv, smtp_to, smtp_from, &output);
+
+	outmsgid = sieve_message_get_new_id(aenv->svinst);
+
+	/* Produce a proper reply */
+
+	msg = t_str_new(512);
+	rfc2822_header_write(msg, "X-Sieve", SIEVE_IMPLEMENTATION);
+	rfc2822_header_write(msg, "Message-ID", outmsgid);
+	rfc2822_header_write(msg, "Date", message_date_create(ioloop_time));
+
+	if ( ctx->from != NULL && *(ctx->from) != '\0' ) {
+		rfc2822_header_write_address(msg, "From", ctx->from);
+	} else {
+		if ( reply_from == NULL || reply_from->mailbox == NULL ||
+			*reply_from->mailbox == '\0' )
+			reply_from = sieve_get_postmaster(senv);
+		rfc2822_header_write(msg, "From",
+			message_address_first_to_string(reply_from));
+	}
+
+	rfc2822_header_write(msg, "To",
+		message_address_first_to_string(&reply_to));
+
+	if ( _contains_8bit(subject) )
+		rfc2822_header_utf8_printf(msg, "Subject", "%s", subject);
+	else
+		rfc2822_header_printf(msg, "Subject", "%s", subject);
+
+	/* Compose proper in-reply-to and references headers */
+
+	if ( (ret=mail_get_first_header
+		(msgdata->mail, "references", &header)) < 0 ) {
+		return sieve_result_mail_error(aenv, msgdata->mail,
+			"vacation action: "
+			"failed to read header field `references'");
+	}
+
+	if ( msgdata->id != NULL ) {
+		rfc2822_header_write(msg, "In-Reply-To", msgdata->id);
+
+		if ( ret > 0 && header != NULL ) {
+			rfc2822_header_write(msg, "References",
+				t_strconcat(header, " ", msgdata->id, NULL));
+		} else {
+			rfc2822_header_write(msg, "References", msgdata->id);
+		}
+	} else if ( ret > 0 && header != NULL ) {
+		rfc2822_header_write(msg, "References", header);
+	}
+
+	rfc2822_header_write(msg, "Auto-Submitted", "auto-replied (vacation)");
+	rfc2822_header_write(msg, "Precedence", "bulk");
+
+	/* Prevent older Microsoft products from replying to this message */
+	rfc2822_header_write(msg, "X-Auto-Response-Suppress", "All");
+
+	rfc2822_header_write(msg, "MIME-Version", "1.0");
+
+	if ( !ctx->mime ) {
+		rfc2822_header_write(msg, "Content-Type", "text/plain; charset=utf-8");
+		rfc2822_header_write(msg, "Content-Transfer-Encoding", "8bit");
+		str_append(msg, "\r\n");
+	}
+
+	str_printfa(msg, "%s\r\n", ctx->reason);
+	o_stream_nsend(output, str_data(msg), str_len(msg));
+
+	/* Close smtp session */
+	if ( (ret=sieve_smtp_finish(sctx, &error)) <= 0 ) {
+		if ( ret < 0 ) {
+			sieve_result_global_error(aenv,
+				"failed to send vacation response to %s: "
+				"<%s> (temporary error)",
+				smtp_address_encode(smtp_to),
+				str_sanitize(error, 512));
+		} else {
+			sieve_result_global_log_error(aenv,
+				"failed to send vacation response to %s: "
+				"<%s> (permanent error)",
+				smtp_address_encode(smtp_to),
+				str_sanitize(error, 512));
+		}
+		/* This error will be ignored in the end */
+		return SIEVE_EXEC_FAILURE;
+	}
+
+	return SIEVE_EXEC_OK;
+}
+
+static void act_vacation_hash
+(struct act_vacation_context *vctx, const char *sender, unsigned char hash_r[])
+{
+	const char *rpath = t_str_lcase(sender);
+	struct md5_context ctx;
+
+	md5_init(&ctx);
+	md5_update(&ctx, rpath, strlen(rpath));
+
+	md5_update(&ctx, vctx->handle, strlen(vctx->handle));
+
+	md5_final(&ctx, hash_r);
+}
+
+static int act_vacation_commit
+(const struct sieve_action *action, const struct sieve_action_exec_env *aenv,
+	void *tr_context ATTR_UNUSED, bool *keep ATTR_UNUSED)
+{
+	const struct sieve_extension *ext = action->ext;
+	struct sieve_instance *svinst = aenv->svinst;
+	const struct ext_vacation_config *config =
+		(const struct ext_vacation_config *) ext->context;
+	const struct sieve_script_env *senv = aenv->scriptenv;
+	struct act_vacation_context *ctx =
+		(struct act_vacation_context *) action->context;
+	unsigned char dupl_hash[MD5_RESULTLEN];
+	struct mail *mail = sieve_message_get_mail(aenv->msgctx);
+	const struct smtp_address *sender, *recipient;
+	const struct smtp_address *orig_recipient, *user_email;
+	const struct smtp_address *smtp_from;
+	struct message_address reply_from;
+	const char *const *hdsp, *const *headers;
+	int ret;
+
+	if ((aenv->flags & SIEVE_EXECUTE_FLAG_SKIP_RESPONSES) != 0) {
+		sieve_result_global_log(aenv,
+			"not sending vacation reply (skipped)");
+		return SIEVE_EXEC_OK;
+	}
+
+	sender = sieve_message_get_sender(aenv->msgctx);
+	recipient = sieve_message_get_final_recipient(aenv->msgctx);
+
+	i_zero(&reply_from);
+	smtp_from = orig_recipient = user_email = NULL;
+
+	/* Is the recipient unset?
+	 */
+	if ( smtp_address_isnull(recipient) ) {
+		sieve_result_global_warning
+			(aenv, "vacation action aborted: envelope recipient is <>");
+		return SIEVE_EXEC_OK;
+	}
+
+	/* Is the return path unset ?
+	 */
+	if ( smtp_address_isnull(sender) ) {
+		sieve_result_global_log(aenv, "discarded vacation reply to <>");
+		return SIEVE_EXEC_OK;
+	}
+
+	/* Are we perhaps trying to respond to ourselves ?
+	 */
+	if ( smtp_address_equals(sender, recipient) ) {
+		sieve_result_global_log(aenv,
+			"discarded vacation reply to own address <%s>",
+			smtp_address_encode(sender));
+		return SIEVE_EXEC_OK;
+	}
+
+	/* Are we perhaps trying to respond to one of our alternative :addresses?
+	 */
+	if ( ctx->addresses != NULL ) {
+		const struct smtp_address * const *alt_address;
+
+		alt_address = ctx->addresses;
+		while ( *alt_address != NULL ) {
+			if ( smtp_address_equals(sender, *alt_address) ) {
+				sieve_result_global_log(aenv,
+					"discarded vacation reply to own address <%s> "
+					"(as specified using :addresses argument)",
+					smtp_address_encode(sender));
+				return SIEVE_EXEC_OK;
+			}
+			alt_address++;
+		}
+	}
+
+	/* Did whe respond to this user before? */
+	if ( sieve_action_duplicate_check_available(senv) ) {
+		act_vacation_hash(ctx,
+			smtp_address_encode(sender), dupl_hash);
+
+		if ( sieve_action_duplicate_check
+			(senv, dupl_hash, sizeof(dupl_hash)) )
+		{
+			sieve_result_global_log(aenv,
+				"discarded duplicate vacation response to <%s>",
+				smtp_address_encode(sender));
+			return SIEVE_EXEC_OK;
+		}
+	}
+
+	/* Are we trying to respond to a mailing list ? */
+	hdsp = _list_headers;
+	while ( *hdsp != NULL ) {
+		if ( (ret=mail_get_headers(mail, *hdsp, &headers)) < 0 ) {
+			return sieve_result_mail_error(aenv, mail,
+				"vacation action: "
+				"failed to read header field `%s'", *hdsp);
+		}
+
+		if ( ret > 0 && headers[0] != NULL ) {
+			/* Yes, bail out */
+			sieve_result_global_log(aenv,
+				"discarding vacation response "
+				"to mailinglist recipient <%s>",
+				smtp_address_encode(sender));
+			return SIEVE_EXEC_OK;
+		}
+		hdsp++;
+	}
+
+	/* Is the message that we are replying to an automatic reply ? */
+	if ( (ret=mail_get_headers
+		(mail, "auto-submitted", &headers)) < 0 ) {
+		return sieve_result_mail_error(aenv, mail,
+			"vacation action: "
+			"failed to read header field `auto-submitted'");
+	}
+	/* Theoretically multiple headers could exist, so lets make sure */
+	if ( ret > 0 ) {
+		hdsp = headers;
+		while ( *hdsp != NULL ) {
+			if ( strcasecmp(*hdsp, "no") != 0 ) {
+				sieve_result_global_log(aenv,
+					"discarding vacation response "
+					"to auto-submitted message from <%s>",
+					smtp_address_encode(sender));
+					return SIEVE_EXEC_OK;
+			}
+			hdsp++;
+		}
+	}
+
+	/* Check for the (non-standard) precedence header */
+	if ( (ret=mail_get_headers
+		(mail, "precedence", &headers)) < 0 ) {
+		return sieve_result_mail_error(aenv, mail,
+			"vacation action: "
+			"failed to read header field `precedence'");
+	}
+	/* Theoretically multiple headers could exist, so lets make sure */
+	if ( ret > 0 ) {
+		hdsp = headers;
+		while ( *hdsp != NULL ) {
+			if ( strcasecmp(*hdsp, "junk") == 0 ||
+				strcasecmp(*hdsp, "bulk") == 0 ||
+				strcasecmp(*hdsp, "list") == 0 ) {
+				sieve_result_global_log(aenv,
+					"discarding vacation response "
+					"to precedence=%s message from <%s>",
+					*hdsp, smtp_address_encode(sender));
+					return SIEVE_EXEC_OK;
+			}
+			hdsp++;
+		}
+	}
+
+	/* Check for the (non-standard) Microsoft X-Auto-Response-Suppress header */
+	if ( (ret=mail_get_headers
+		(mail, "x-auto-response-suppress", &headers)) < 0 ) {
+		return sieve_result_mail_error(aenv, mail,
+			"vacation action: "
+			"failed to read header field `x-auto-response-suppress'");
+	}
+	/* Theoretically multiple headers could exist, so lets make sure */
+	if ( ret > 0 ) {
+		hdsp = headers;
+		while ( *hdsp != NULL ) {
+			const char *const *flags = t_strsplit(*hdsp, ",");
+			while ( *flags != NULL ) {
+				const char *flag = t_str_trim(*flags, " \t");
+				if ( strcasecmp(flag, "All") == 0 ||
+					strcasecmp(flag, "OOF") == 0 ) {
+					sieve_result_global_log(aenv,
+						"discarding vacation response to message from <%s> "
+						"(`%s' flag found in x-auto-response-suppress header)",
+						smtp_address_encode(sender), flag);
+						return SIEVE_EXEC_OK;
+				}
+				flags++;
+			}
+			hdsp++;
+		}
+	}
+
+	/* Do not reply to system addresses */
+	if ( _is_system_address(sender) ) {
+		sieve_result_global_log(aenv,
+			"not sending vacation response to system address <%s>",
+			smtp_address_encode(sender));
+		return SIEVE_EXEC_OK;
+	}
+
+	/* Fetch original recipient if necessary */
+	if ( config->use_original_recipient  )
+		orig_recipient = sieve_message_get_orig_recipient(aenv->msgctx);
+	/* Fetch explicitly configured user email address */
+	if (svinst->user_email != NULL)
+		user_email = svinst->user_email;
+
+	/* Is the original message directly addressed to the user or the addresses
+	 * specified using the :addresses tag?
+	 */
+	hdsp = _my_address_headers;
+	while ( *hdsp != NULL ) {
+		if ( (ret=mail_get_headers(mail, *hdsp, &headers)) < 0 ) {
+			return sieve_result_mail_error(aenv, mail,
+				"vacation action: "
+				"failed to read header field `%s'", *hdsp);
+		}
+		if ( ret > 0 && headers[0] != NULL ) {
+
+			/* Final recipient directly listed in headers? */
+			if ( _contains_my_address(headers, recipient) ) {
+				smtp_from = recipient;
+				message_address_init_from_smtp(&reply_from,
+					NULL, recipient);
+				break;
+			}
+
+			/* Original recipient directly listed in headers? */
+			if ( !smtp_address_isnull(orig_recipient) &&
+				_contains_my_address(headers, orig_recipient) ) {
+				smtp_from = orig_recipient;
+				message_address_init_from_smtp(&reply_from,
+					NULL, orig_recipient);
+				break;
+			}
+
+			/* User-provided :addresses listed in headers? */
+			if ( ctx->addresses != NULL ) {
+				bool found = FALSE;
+				const struct smtp_address * const *my_address;
+
+				my_address = ctx->addresses;
+				while ( !found && *my_address != NULL ) {
+					if ( (found=_contains_my_address(headers, *my_address)) ) {
+						/* Avoid letting user determine SMTP sender directly */
+						smtp_from =
+							( orig_recipient == NULL ? recipient : orig_recipient );
+						message_address_init_from_smtp(&reply_from,
+							NULL, *my_address);
+					}
+					my_address++;
+				}
+
+				if ( found ) break;
+			}
+
+			/* Explicitly-configured user email address directly listed in
+			   headers? */
+			if ( user_email != NULL &&
+				_contains_my_address(headers, user_email) ) {
+				smtp_from = user_email;
+				message_address_init_from_smtp(&reply_from,
+					NULL, smtp_from);
+				break;
+			}
+		}
+		hdsp++;
+	}
+
+	/* My address not found in the headers; we got an implicit delivery */
+	if ( *hdsp == NULL ) {
+		if ( config->dont_check_recipient ) {
+			/* Send reply from envelope recipient address */
+			smtp_from = ( orig_recipient == NULL ?
+				recipient : orig_recipient );
+			if (user_email == NULL)
+				user_email = sieve_get_user_email(svinst);
+			message_address_init_from_smtp(&reply_from,
+				NULL, user_email);
+
+		} else {
+			const char *orig_rcpt_str = "", *user_email_str = "";
+
+			/* Bail out */
+
+			if ( config->use_original_recipient ) {
+				orig_rcpt_str = t_strdup_printf("original-recipient=<%s>, ",
+					( orig_recipient == NULL ? "UNAVAILABLE" :
+						smtp_address_encode(orig_recipient) ));
+			}
+
+			if ( user_email != NULL ) {
+				user_email_str = t_strdup_printf("user-email=<%s>, ",
+						smtp_address_encode(user_email));
+			}
+
+			sieve_result_global_log(aenv,
+				"discarding vacation response for implicitly delivered message; "
+				"no known (envelope) recipient address found in message headers "
+				"(recipient=<%s>, %s%sand%s additional `:addresses' are specified)",
+				smtp_address_encode(recipient), orig_rcpt_str, user_email_str,
+				(ctx->addresses == NULL || *ctx->addresses == NULL ? " no" : ""));
+			return SIEVE_EXEC_OK;
+		}
+	}
+
+	/* Send the message */
+
+	T_BEGIN {
+		ret = act_vacation_send(aenv, config, ctx, sender,
+			(config->send_from_recipient ? smtp_from : NULL),
+			&reply_from);
+	} T_END;
+
+	if ( ret == SIEVE_EXEC_OK ) {
+		sieve_number_t seconds;
+
+		sieve_result_global_log(aenv, "sent vacation response to <%s>",
+			smtp_address_encode(sender));
+
+		/* Check period limits once more */
+		seconds = ctx->seconds;
+		if ( seconds < config->min_period )
+			seconds = config->min_period;
+		else if ( config->max_period > 0 && seconds > config->max_period )
+			seconds = config->max_period;
+
+		/* Mark as replied */
+		if ( seconds > 0  ) {
+			sieve_action_duplicate_mark
+				(senv, dupl_hash, sizeof(dupl_hash), ioloop_time + seconds);
+		}
+	}
+
+	if ( ret == SIEVE_EXEC_TEMP_FAILURE )
+		return SIEVE_EXEC_TEMP_FAILURE;
+
+	/* Ignore all other errors */
+	return SIEVE_EXEC_OK;
+}
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/vacation/ext-vacation-common.c
@@ -0,0 +1,103 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-settings.h"
+#include "sieve-extensions.h"
+
+#include "ext-vacation-common.h"
+
+bool ext_vacation_load
+(const struct sieve_extension *ext, void **context)
+{
+	struct sieve_instance *svinst = ext->svinst;
+	struct ext_vacation_config *config;
+	sieve_number_t min_period, max_period, default_period;
+	bool use_original_recipient, dont_check_recipient, send_from_recipient,
+		to_header_ignore_envelope;
+	unsigned long long max_subject_codepoints;
+
+	if ( *context != NULL ) {
+		ext_vacation_unload(ext);
+	}
+
+	if ( !sieve_setting_get_duration_value
+		(svinst, "sieve_vacation_min_period", &min_period) ) {
+		min_period = EXT_VACATION_DEFAULT_MIN_PERIOD;
+	}
+
+	if ( !sieve_setting_get_duration_value
+		(svinst, "sieve_vacation_max_period", &max_period) ) {
+		max_period = EXT_VACATION_DEFAULT_MAX_PERIOD;
+	}
+
+	if ( !sieve_setting_get_duration_value
+		(svinst, "sieve_vacation_default_period", &default_period) ) {
+		default_period = EXT_VACATION_DEFAULT_PERIOD;
+	}
+
+	if ( max_period > 0
+		&& (min_period > max_period || default_period < min_period
+			|| default_period > max_period) ) {
+		min_period = EXT_VACATION_DEFAULT_MIN_PERIOD;
+		max_period = EXT_VACATION_DEFAULT_MAX_PERIOD;
+		default_period = EXT_VACATION_DEFAULT_PERIOD;
+
+		sieve_sys_warning(svinst,
+			"vacation extension: invalid settings: violated "
+			"sieve_vacation_min_period < sieve_vacation_default_period < "
+			"sieve_vacation_max_period");
+	}
+
+	if ( !sieve_setting_get_uint_value
+		(svinst, "sieve_vacation_max_subject_codepoints", &max_subject_codepoints) ) {
+		max_subject_codepoints = EXT_VACATION_DEFAULT_MAX_SUBJECT_CODEPOINTS;
+	}
+
+	if ( !sieve_setting_get_bool_value
+		(svinst, "sieve_vacation_use_original_recipient", &use_original_recipient) ) {
+		use_original_recipient = FALSE;
+	}
+
+	if ( !sieve_setting_get_bool_value
+		(svinst, "sieve_vacation_dont_check_recipient", &dont_check_recipient) ) {
+		dont_check_recipient = FALSE;
+	}
+
+	if ( !sieve_setting_get_bool_value
+		(svinst, "sieve_vacation_send_from_recipient", &send_from_recipient) ) {
+		send_from_recipient = FALSE;
+	}
+
+	if ( !sieve_setting_get_bool_value(svinst,
+		"sieve_vacation_to_header_ignore_envelope",
+		&to_header_ignore_envelope) ) {
+		to_header_ignore_envelope = FALSE;
+	}
+
+	config = i_new(struct ext_vacation_config, 1);
+	config->min_period = min_period;
+	config->max_period = max_period;
+	config->default_period = default_period;
+	config->max_subject_codepoints = max_subject_codepoints;
+	config->use_original_recipient = use_original_recipient;
+	config->dont_check_recipient = dont_check_recipient;
+	config->send_from_recipient = send_from_recipient;
+	config->to_header_ignore_envelope = to_header_ignore_envelope;
+
+	*context = (void *) config;
+
+	return TRUE;
+}
+
+void ext_vacation_unload
+(const struct sieve_extension *ext)
+{
+	struct ext_vacation_config *config =
+		(struct ext_vacation_config *) ext->context;
+
+	i_free(config);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/vacation/ext-vacation-common.h
@@ -0,0 +1,58 @@
+#ifndef EXT_VACATION_COMMON_H
+#define EXT_VACATION_COMMON_H
+
+#include "sieve-common.h"
+
+/*
+ * Extension configuration
+ */
+
+#define EXT_VACATION_DEFAULT_PERIOD (7*24*60*60)
+#define EXT_VACATION_DEFAULT_MIN_PERIOD (24*60*60)
+#define EXT_VACATION_DEFAULT_MAX_PERIOD 0
+#define EXT_VACATION_DEFAULT_MAX_SUBJECT_CODEPOINTS 256
+
+struct ext_vacation_config {
+	unsigned int min_period;
+	unsigned int max_period;
+	unsigned int default_period;
+	unsigned long long max_subject_codepoints;
+	bool use_original_recipient;
+	bool dont_check_recipient;
+	bool send_from_recipient;
+	bool to_header_ignore_envelope;
+};
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def vacation_command;
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def vacation_operation;
+
+/*
+ * Extensions
+ */
+
+/* Vacation */
+
+extern const struct sieve_extension_def vacation_extension;
+
+bool ext_vacation_load
+	(const struct sieve_extension *ext, void **context);
+void ext_vacation_unload
+	(const struct sieve_extension *ext);
+
+/* Vacation-seconds */
+
+extern const struct sieve_extension_def vacation_seconds_extension;
+
+bool ext_vacation_register_seconds_tag
+	(struct sieve_validator *valdtr, const struct sieve_extension *vacation_ext);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/vacation/ext-vacation-seconds.c
@@ -0,0 +1,66 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension vacation-seconds
+ * --------------------------
+ *
+ * Authors: Stephan Bosch <stephan@rename-it.nl>
+ * Specification: RFC 6131
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+
+#include "sieve-extensions.h"
+#include "sieve-validator.h"
+
+#include "ext-vacation-common.h"
+
+/*
+ * Extension
+ */
+
+bool ext_vacation_seconds_load
+	(const struct sieve_extension *ext, void **context);
+static bool ext_vacation_seconds_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def vacation_seconds_extension = {
+	.name = "vacation-seconds",
+	.load = ext_vacation_seconds_load,
+	.validator_load = ext_vacation_seconds_validator_load,
+};
+
+bool ext_vacation_seconds_load
+(const struct sieve_extension *ext, void **context)
+{
+	if ( *context == NULL ) {
+		/* Make sure vacation extension is registered */
+		*context = (void *)
+			sieve_extension_require(ext->svinst, &vacation_extension, TRUE);
+	}
+
+	return TRUE;
+}
+
+static bool ext_vacation_seconds_validator_load
+(const struct sieve_extension *ext ATTR_UNUSED, struct sieve_validator *valdtr)
+{
+	const struct sieve_extension *vacation_ext;
+
+	/* Load vacation extension implicitly */
+
+	vacation_ext = sieve_validator_extension_load_implicit
+		(valdtr, vacation_extension.name);
+
+	if ( vacation_ext == NULL )
+		return FALSE;
+
+	/* Add seconds tag to vacation command */
+
+	return ext_vacation_register_seconds_tag(valdtr, vacation_ext);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/vacation/ext-vacation.c
@@ -0,0 +1,124 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension vacation
+ * ------------------
+ *
+ * Authors: Stephan Bosch <stephan@rename-it.nl>
+ * Specification: RFC 5230
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-vacation-common.h"
+
+/*
+ * Extension
+ */
+
+static bool ext_vacation_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+static bool ext_vacation_interpreter_load
+	(const struct sieve_extension *ext,
+		const struct sieve_runtime_env *renv,
+		sieve_size_t *address);
+
+static bool ext_vacation_validator_validate
+	(const struct sieve_extension *ext,
+		struct sieve_validator *valdtr, void *context,
+		struct sieve_ast_argument *require_arg,
+		bool required);
+static int ext_vacation_interpreter_run
+	(const struct sieve_extension *this_ext,
+		const struct sieve_runtime_env *renv,
+		void *context, bool deferred);
+
+const struct sieve_extension_def vacation_extension = {
+	.name = "vacation",
+	.load = ext_vacation_load,
+	.unload = ext_vacation_unload,
+	.validator_load = ext_vacation_validator_load,
+	.interpreter_load = ext_vacation_interpreter_load,
+	SIEVE_EXT_DEFINE_OPERATION(vacation_operation)
+};
+const struct sieve_validator_extension
+vacation_validator_extension = {
+	.ext = &vacation_extension,
+	.validate = ext_vacation_validator_validate
+};
+const struct sieve_interpreter_extension
+vacation_interpreter_extension = {
+	.ext_def = &vacation_extension,
+	.run = ext_vacation_interpreter_run
+};
+
+static bool ext_vacation_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	/* Register new command */
+	sieve_validator_register_command(valdtr, ext, &vacation_command);
+
+	sieve_validator_extension_register
+		(valdtr, ext, &vacation_validator_extension, NULL);
+	return TRUE;
+}
+
+static bool ext_vacation_interpreter_load
+(const struct sieve_extension *ext,
+	const struct sieve_runtime_env *renv,
+	sieve_size_t *address ATTR_UNUSED)
+{
+	sieve_interpreter_extension_register(renv->interp,
+		ext, &vacation_interpreter_extension, NULL);
+	return TRUE;
+}
+
+static bool ext_vacation_validator_validate
+(const struct sieve_extension *ext,
+	struct sieve_validator *valdtr, void *context ATTR_UNUSED,
+	struct sieve_ast_argument *require_arg,
+	bool required)
+{
+	if (required) {
+		enum sieve_compile_flags flags =
+			sieve_validator_compile_flags(valdtr);
+
+		if ( (flags & SIEVE_COMPILE_FLAG_NO_ENVELOPE) != 0 ) {
+			sieve_argument_validate_error(valdtr, require_arg,
+				"the %s extension cannot be used in this context "
+				"(needs access to message envelope)",
+				sieve_extension_name(ext));
+			return FALSE;
+		}
+	}
+	return TRUE;
+}
+
+static int ext_vacation_interpreter_run
+(const struct sieve_extension *ext,
+	const struct sieve_runtime_env *renv,
+	void *context ATTR_UNUSED, bool deferred)
+{
+	if ( (renv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) != 0 ) {
+		if ( !deferred ) {
+			sieve_runtime_error(renv, NULL,
+				"the %s extension cannot be used in this context "
+				"(needs access to message envelope)",
+				sieve_extension_name(ext));
+		}
+		return SIEVE_EXEC_FAILURE;
+	}
+	return SIEVE_EXEC_OK;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/variables/Makefile.am
@@ -0,0 +1,41 @@
+noinst_LTLIBRARIES = libsieve_ext_variables.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../.. \
+	$(LIBDOVECOT_INCLUDE)
+
+cmds = \
+	cmd-set.c
+
+tsts = \
+	tst-string.c
+
+libsieve_ext_variables_la_SOURCES = \
+	ext-variables-common.c \
+	ext-variables-name.c \
+	ext-variables-namespaces.c \
+	ext-variables-arguments.c \
+	ext-variables-operands.c \
+	ext-variables-modifiers.c \
+	ext-variables-dump.c \
+	$(cmds) \
+	$(tsts) \
+	ext-variables.c
+
+public_headers = \
+	sieve-ext-variables.h
+
+headers = \
+	ext-variables-common.h \
+	ext-variables-limits.h \
+	ext-variables-name.h \
+	ext-variables-namespaces.h \
+	ext-variables-arguments.h \
+	ext-variables-operands.h \
+	ext-variables-modifiers.h \
+	ext-variables-dump.h
+
+pkginc_libdir=$(dovecot_pkgincludedir)/sieve
+pkginc_lib_HEADERS = $(public_headers)
+noinst_HEADERS = $(headers)
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/variables/cmd-set.c
@@ -0,0 +1,235 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+
+#include "sieve-code.h"
+#include "sieve-ast.h"
+#include "sieve-commands.h"
+#include "sieve-binary.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-variables-common.h"
+
+/*
+ * Set command
+ *
+ * Syntax:
+ *    set [MODIFIER] <name: string> <value: string>
+ */
+
+static bool cmd_set_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool cmd_set_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_set_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def cmd_set = {
+	.identifier = "set",
+	.type = SCT_COMMAND,
+	.positional_args = 2,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = cmd_set_registered,
+	.validate = cmd_set_validate,
+	.generate = cmd_set_generate,
+};
+
+/*
+ * Set operation
+ */
+
+static bool cmd_set_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_set_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def cmd_set_operation = {
+	.mnemonic = "SET",
+	.ext_def = &variables_extension,
+	.code = EXT_VARIABLES_OPERATION_SET,
+	.dump = cmd_set_operation_dump,
+	.execute = cmd_set_operation_execute
+};
+
+/*
+ * Compiler context
+ */
+
+struct cmd_set_context {
+	ARRAY_TYPE(sieve_variables_modifier) modifiers;
+};
+
+/* Command registration */
+
+static bool cmd_set_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg)
+{
+	sieve_variables_modifiers_link_tag(valdtr, ext, cmd_reg);
+
+	return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+static bool cmd_set_validate(struct sieve_validator *valdtr,
+	struct sieve_command *cmd)
+{
+	const struct sieve_extension *this_ext = cmd->ext;
+	struct sieve_ast_argument *arg = cmd->first_positional;
+	pool_t pool = sieve_command_pool(cmd);
+	struct cmd_set_context *sctx;
+
+	/* Create command context */
+	sctx = p_new(pool, struct cmd_set_context, 1);
+	p_array_init(&sctx->modifiers, pool, 4);
+	cmd->data = (void *) sctx;
+
+	/* Validate modifiers */
+	if ( !sieve_variables_modifiers_validate
+		(valdtr, cmd, &sctx->modifiers) )
+		return FALSE;
+
+	/* Validate name argument */
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "name", 1, SAAT_STRING) ) {
+		return FALSE;
+	}
+	if ( !sieve_variable_argument_activate
+		(this_ext, this_ext, valdtr, cmd, arg, TRUE) ) {
+		return FALSE;
+	}
+	arg = sieve_ast_argument_next(arg);
+
+	/* Validate value argument */
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "value", 2, SAAT_STRING) ) {
+		return FALSE;
+	}
+	return sieve_validator_argument_activate
+		(valdtr, cmd, arg, FALSE);
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_set_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	const struct sieve_extension *this_ext = cmd->ext;
+	struct sieve_binary_block *sblock = cgenv->sblock;
+	struct cmd_set_context *sctx = (struct cmd_set_context *) cmd->data;
+
+	sieve_operation_emit(sblock, this_ext, &cmd_set_operation);
+
+	/* Generate arguments */
+	if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
+		return FALSE;
+
+	/* Generate modifiers */
+	if ( !sieve_variables_modifiers_generate
+		(cgenv, &sctx->modifiers) )
+		return FALSE;
+
+	return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_set_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "SET");
+	sieve_code_descend(denv);
+
+	/* Print both variable name and string value */
+	if ( !sieve_opr_string_dump(denv, address, "variable") ||
+		!sieve_opr_string_dump(denv, address, "value") )
+		return FALSE;
+
+	return sieve_variables_modifiers_code_dump(denv, address);
+}
+
+/*
+ * Code execution
+ */
+
+static int cmd_set_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	const struct sieve_extension *this_ext = renv->oprtn->ext;
+	struct sieve_variable_storage *storage;
+	ARRAY_TYPE(sieve_variables_modifier) modifiers;
+	unsigned int var_index;
+	string_t *value;
+	int ret = SIEVE_EXEC_OK;
+
+	/*
+	 * Read the normal operands
+	 */
+
+	if ( (ret=sieve_variable_operand_read
+		(renv, address, "variable", &storage, &var_index)) <= 0 )
+		return ret;
+
+	if ( (ret=sieve_opr_string_read(renv, address, "string", &value)) <= 0 )
+		return ret;
+
+	if ( (ret=sieve_variables_modifiers_code_read
+		(renv, address, &modifiers)) <= 0 )
+		return ret;
+
+	/*
+	 * Determine and assign the value
+	 */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS, "set command");
+	sieve_runtime_trace_descend(renv);
+
+	/* Apply modifiers */
+	if ( (ret=sieve_variables_modifiers_apply
+		(renv, this_ext, &modifiers, &value)) <= 0 )
+		return ret;
+
+	/* Actually assign the value if all is well */
+	i_assert ( value != NULL );
+	if ( !sieve_variable_assign(storage, var_index, value) )
+		return SIEVE_EXEC_BIN_CORRUPT;
+
+	/* Trace */
+	if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
+		const char *var_name, *var_id;
+
+		(void)sieve_variable_get_identifier(storage, var_index, &var_name);
+		var_id = sieve_variable_get_varid(storage, var_index);
+
+		sieve_runtime_trace_here(renv, 0, "assign `%s' [%s] = \"%s\"",
+			var_name, var_id, str_c(value));
+	}
+
+	return SIEVE_EXEC_OK;
+}
+
+
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-arguments.c
@@ -0,0 +1,420 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-ast.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-dump.h"
+
+#include "ext-variables-common.h"
+#include "ext-variables-limits.h"
+#include "ext-variables-name.h"
+#include "ext-variables-operands.h"
+#include "ext-variables-namespaces.h"
+#include "ext-variables-arguments.h"
+
+/*
+ * Variable argument implementation
+ */
+
+static bool arg_variable_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+		struct sieve_command *context);
+
+const struct sieve_argument_def variable_argument = {
+	.identifier = "@variable",
+	.generate = arg_variable_generate
+};
+
+static bool ext_variables_variable_argument_activate
+(const struct sieve_extension *var_ext,
+	const struct sieve_extension *this_ext,
+	struct sieve_validator *valdtr, struct sieve_ast_argument *arg,
+	const char *variable)
+{
+	struct sieve_ast *ast = arg->ast;
+	struct sieve_variable *var;
+
+	var = ext_variables_validator_declare_variable(this_ext, valdtr, variable);
+
+	if ( var == NULL ) {
+		sieve_argument_validate_error(valdtr, arg,
+			"(implicit) declaration of new variable '%s' exceeds the limit "
+			"(max variables: %u)", variable,
+			sieve_variables_get_max_scope_size(var_ext));
+		return FALSE;
+	}
+
+	arg->argument = sieve_argument_create(ast, &variable_argument, this_ext, 0);
+	arg->argument->data = (void *) var;
+	return TRUE;
+}
+
+static struct sieve_ast_argument *ext_variables_variable_argument_create
+(const struct sieve_extension *this_ext, struct sieve_validator *valdtr,
+	struct sieve_ast_argument *parent_arg, const char *variable)
+{
+	struct sieve_ast *ast = parent_arg->ast;
+	struct sieve_ast_argument *new_arg;
+
+	new_arg = sieve_ast_argument_create(ast, sieve_ast_argument_line(parent_arg));
+	new_arg->type = SAAT_STRING;
+
+	if ( !ext_variables_variable_argument_activate
+		(this_ext, this_ext, valdtr, new_arg, variable) )
+		return NULL;
+
+	return new_arg;
+}
+
+static bool arg_variable_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+	struct sieve_command *context ATTR_UNUSED)
+{
+	struct sieve_argument *argument = arg->argument;
+	struct sieve_variable *var = (struct sieve_variable *) argument->data;
+
+	sieve_variables_opr_variable_emit(cgenv->sblock, argument->ext, var);
+
+	return TRUE;
+}
+
+/*
+ * Match value argument implementation
+ */
+
+static bool arg_match_value_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+	struct sieve_command *context ATTR_UNUSED);
+
+const struct sieve_argument_def match_value_argument = {
+	.identifier = "@match_value",
+	.generate = arg_match_value_generate
+};
+
+static bool ext_variables_match_value_argument_activate
+(const struct sieve_extension *this_ext,
+	struct sieve_validator *valdtr, struct sieve_ast_argument *arg,
+	unsigned int index, bool assignment)
+{
+	struct sieve_ast *ast = arg->ast;
+
+	if ( assignment ) {
+		sieve_argument_validate_error(valdtr, arg,
+			"cannot assign to match variable");
+		return FALSE;
+	}
+
+	if ( index > EXT_VARIABLES_MAX_MATCH_INDEX ) {
+		sieve_argument_validate_error(valdtr, arg,
+			"match value index %u out of range (max: %u)", index,
+			EXT_VARIABLES_MAX_MATCH_INDEX);
+		return FALSE;
+	}
+
+	arg->argument = sieve_argument_create
+		(ast, &match_value_argument, this_ext, 0);
+	arg->argument->data = (void *) POINTER_CAST(index);
+	return TRUE;
+}
+
+static struct sieve_ast_argument *ext_variables_match_value_argument_create
+(const struct sieve_extension *this_ext, struct sieve_validator *valdtr,
+	struct sieve_ast_argument *parent_arg, unsigned int index)
+{
+	struct sieve_ast *ast = parent_arg->ast;
+	struct sieve_ast_argument *new_arg;
+
+	new_arg = sieve_ast_argument_create(ast, sieve_ast_argument_line(parent_arg));
+	new_arg->type = SAAT_STRING;
+
+	if ( !ext_variables_match_value_argument_activate
+		(this_ext, valdtr, new_arg, index, FALSE) ) {
+		return NULL;
+  }
+
+	return new_arg;
+}
+
+static bool arg_match_value_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+	struct sieve_command *context ATTR_UNUSED)
+{
+	struct sieve_argument *argument = arg->argument;
+	unsigned int index = POINTER_CAST_TO(argument->data, unsigned int);
+
+	sieve_variables_opr_match_value_emit(cgenv->sblock, argument->ext, index);
+
+	return TRUE;
+}
+
+/*
+ * Variable string argument implementation
+ */
+
+static bool arg_variable_string_validate
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+
+const struct sieve_argument_def variable_string_argument = {
+	.identifier = "@variable-string",
+	.validate = arg_variable_string_validate,
+	.generate = sieve_arg_catenated_string_generate,
+};
+
+static bool arg_variable_string_validate
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	const struct sieve_extension *this_ext = (*arg)->argument->ext;
+	enum { ST_NONE, ST_OPEN, ST_VARIABLE, ST_CLOSE } state = ST_NONE;
+	pool_t pool = sieve_ast_pool((*arg)->ast);
+	struct sieve_arg_catenated_string *catstr = NULL;
+	string_t *str = sieve_ast_argument_str(*arg);
+	const char *p, *strstart, *substart = NULL;
+	const char *strval = (const char *) str_data(str);
+	const char *strend = strval + str_len(str);
+	bool result = TRUE;
+	ARRAY_TYPE(sieve_variable_name) substitution;
+	int nelements = 0;
+
+	T_BEGIN {
+		/* Initialize substitution structure */
+		t_array_init(&substitution, 2);
+
+		p = strval;
+		strstart = p;
+		while ( result && p < strend ) {
+			switch ( state ) {
+
+			/* Nothing found yet */
+			case ST_NONE:
+				if ( *p == '$' ) {
+					substart = p;
+					state = ST_OPEN;
+				}
+				p++;
+				break;
+
+			/* Got '$' */
+			case ST_OPEN:
+				if ( *p == '{' ) {
+					state = ST_VARIABLE;
+					p++;
+				} else
+					state = ST_NONE;
+				break;
+
+			/* Got '${' */
+			case ST_VARIABLE:
+				nelements = ext_variable_name_parse(&substitution, &p, strend);
+
+				if ( nelements < 0 )
+					state = ST_NONE;
+				else
+					state = ST_CLOSE;
+
+				break;
+
+			/* Finished parsing name, expecting '}' */
+			case ST_CLOSE:
+				if ( *p == '}' ) {
+					struct sieve_ast_argument *strarg;
+
+					/* We now know that the substitution is valid */
+
+					if ( catstr == NULL ) {
+						catstr = sieve_arg_catenated_string_create(*arg);
+					}
+
+					/* Add the substring that is before the substitution to the
+					 * variable-string AST.
+					 *
+					 * FIXME: For efficiency, if the variable is not found we should
+					 * coalesce this substring with the one after the substitution.
+					 */
+					if ( substart > strstart ) {
+						string_t *newstr = str_new(pool, substart - strstart);
+						str_append_data(newstr, strstart, substart - strstart);
+
+						strarg = sieve_ast_argument_string_create_raw
+							((*arg)->ast, newstr, (*arg)->source_line);
+						sieve_arg_catenated_string_add_element(catstr, strarg);
+
+						/* Give other substitution extensions a chance to do their work */
+						if ( !sieve_validator_argument_activate_super
+							(valdtr, cmd, strarg, FALSE) ) {
+							result = FALSE;
+							break;
+						}
+					}
+
+					/* Find the variable */
+					if ( nelements == 1 ) {
+						const struct sieve_variable_name *cur_element =
+							array_idx(&substitution, 0);
+
+						if ( cur_element->num_variable == -1 ) {
+							/* Add variable argument '${identifier}' */
+
+							strarg = ext_variables_variable_argument_create
+								(this_ext, valdtr, *arg, str_c(cur_element->identifier));
+
+						} else {
+							/* Add match value argument '${000}' */
+
+							strarg = ext_variables_match_value_argument_create
+								(this_ext, valdtr, *arg, cur_element->num_variable);
+						}
+					} else {
+						strarg = ext_variables_namespace_argument_create
+							(this_ext, valdtr, *arg, cmd, &substitution);
+					}
+
+					if ( strarg != NULL )
+						sieve_arg_catenated_string_add_element(catstr, strarg);
+
+					strstart = p + 1;
+					substart = strstart;
+
+					p++;
+				}
+
+				/* Finished, reset for the next substitution */
+				state = ST_NONE;
+			}
+		}
+	} T_END;
+
+	/* Bail out early if substitution is invalid */
+	if ( !result ) return FALSE;
+
+	/* Check whether any substitutions were found */
+	if ( catstr == NULL ) {
+		/* No substitutions in this string, pass it on to any other substution
+		 * extension.
+		 */
+		return sieve_validator_argument_activate_super(valdtr, cmd, *arg, TRUE);
+	}
+
+	/* Add the final substring that comes after the last substitution to the
+	 * variable-string AST.
+	 */
+	if ( strend > strstart ) {
+		struct sieve_ast_argument *strarg;
+		string_t *newstr = str_new(pool, strend - strstart);
+		str_append_data(newstr, strstart, strend - strstart);
+
+		strarg = sieve_ast_argument_string_create_raw
+			((*arg)->ast, newstr, (*arg)->source_line);
+		sieve_arg_catenated_string_add_element(catstr, strarg);
+
+		/* Give other substitution extensions a chance to do their work */
+		if ( !sieve_validator_argument_activate_super
+			(valdtr, cmd, strarg, FALSE) )
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+/*
+ * Variable argument interface
+ */
+
+static bool _sieve_variable_argument_activate
+(const struct sieve_extension *var_ext,
+	const struct sieve_extension *this_ext,
+	struct sieve_validator *valdtr, struct sieve_command *cmd,
+	struct sieve_ast_argument *arg, bool assignment)
+{
+	bool result = FALSE;
+	string_t *variable;
+	const char *varstr, *varend;
+	ARRAY_TYPE(sieve_variable_name) vname;
+	int nelements = 0;
+
+	T_BEGIN {
+		t_array_init(&vname, 2);
+
+		variable = sieve_ast_argument_str(arg);
+		varstr = str_c(variable);
+		varend = PTR_OFFSET(varstr, str_len(variable));
+		nelements = ext_variable_name_parse(&vname, &varstr, varend);
+
+		/* Check whether name parsing succeeded */
+		if ( nelements <= 0 || varstr != varend ) {
+			/* Parse failed */
+			sieve_argument_validate_error(valdtr, arg,
+				"invalid variable name '%s'", str_sanitize(str_c(variable),80));
+		} else if ( nelements == 1 ) {
+			/* Normal (match) variable */
+
+			const struct sieve_variable_name *cur_element =
+				array_idx(&vname, 0);
+
+			if ( cur_element->num_variable < 0 ) {
+				/* Variable */
+				result = ext_variables_variable_argument_activate(var_ext,
+					this_ext, valdtr, arg, str_c(cur_element->identifier));
+
+			} else {
+				/* Match value */
+				result = ext_variables_match_value_argument_activate
+					(this_ext, valdtr, arg, cur_element->num_variable, assignment);
+			}
+
+		} else {
+			/* Namespace variable */
+			result = ext_variables_namespace_argument_activate
+				(this_ext, valdtr, arg, cmd, &vname, assignment);
+		}
+	} T_END;
+
+	return result;
+}
+
+bool sieve_variable_argument_activate
+(const struct sieve_extension *var_ext,
+	const struct sieve_extension *this_ext,
+	struct sieve_validator *valdtr, struct sieve_command *cmd,
+	struct sieve_ast_argument *arg, bool assignment)
+{
+	if ( sieve_ast_argument_type(arg) == SAAT_STRING ) {
+		/* Single string */
+		return _sieve_variable_argument_activate(var_ext,
+			this_ext, valdtr, cmd, arg, assignment);
+
+	} else if ( sieve_ast_argument_type(arg) == SAAT_STRING_LIST ) {
+		/* String list */
+		struct sieve_ast_argument *stritem;
+
+		i_assert ( !assignment );
+
+		stritem = sieve_ast_strlist_first(arg);
+		while ( stritem != NULL ) {
+			if ( !_sieve_variable_argument_activate(var_ext,
+				this_ext, valdtr, cmd, stritem, assignment) )
+				return FALSE;
+
+			stritem = sieve_ast_strlist_next(stritem);
+		}
+
+		arg->argument = sieve_argument_create
+			(arg->ast, &string_list_argument, NULL, 0);
+
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-arguments.h
@@ -0,0 +1,24 @@
+#ifndef EXT_VARIABLES_ARGUMENTS_H
+#define EXT_VARIABLES_ARGUMENTS_H
+
+#include "sieve-common.h"
+
+/*
+ * Variable argument
+ */
+
+extern const struct sieve_argument_def variable_argument;
+
+/*
+ * Match value argument
+ */
+
+extern const struct sieve_argument_def match_value_argument;
+
+/*
+ * Variable string argument
+ */
+
+extern const struct sieve_argument_def variable_string_argument;
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-common.c
@@ -0,0 +1,928 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "hash.h"
+#include "str.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-settings.h"
+
+#include "sieve-ast.h"
+#include "sieve-binary.h"
+#include "sieve-code.h"
+#include "sieve-objects.h"
+#include "sieve-match-types.h"
+
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-dump.h"
+#include "sieve-interpreter.h"
+
+#include "ext-variables-common.h"
+#include "ext-variables-limits.h"
+#include "ext-variables-name.h"
+#include "ext-variables-modifiers.h"
+
+/*
+ * Limits
+ */
+
+unsigned int
+sieve_variables_get_max_scope_size(const struct sieve_extension *var_ext)
+{
+	const struct ext_variables_config *config =
+		ext_variables_get_config(var_ext);
+
+	return config->max_scope_size;
+}
+
+/*
+ * Extension configuration
+ */
+
+bool ext_variables_load
+(const struct sieve_extension *ext, void **context)
+{
+	struct sieve_instance *svinst = ext->svinst;
+	struct ext_variables_config *config;
+	unsigned long long int uint_setting;
+	size_t size_setting;
+
+	if ( *context != NULL )
+		ext_variables_unload(ext);
+
+	config = i_new(struct ext_variables_config, 1);
+
+	/* Get limits */
+	config->max_scope_size = EXT_VARIABLES_DEFAULT_MAX_SCOPE_SIZE;
+	config->max_variable_size = EXT_VARIABLES_DEFAULT_MAX_VARIABLE_SIZE;
+
+	if ( sieve_setting_get_uint_value(svinst,
+		"sieve_variables_max_scope_size", &uint_setting) ) {
+		if ( uint_setting < EXT_VARIABLES_REQUIRED_MAX_SCOPE_SIZE ) {
+			sieve_sys_warning(svinst,
+				"variables: setting sieve_variables_max_scope_size "
+				"is lower than required by standards (>= %llu items)",
+				(unsigned long long)EXT_VARIABLES_REQUIRED_MAX_SCOPE_SIZE);
+		} else {
+			config->max_scope_size = (unsigned int)uint_setting;
+		}
+	}
+
+	if ( sieve_setting_get_size_value(svinst,
+		"sieve_variables_max_variable_size", &size_setting) ) {
+		if ( size_setting < EXT_VARIABLES_REQUIRED_MAX_VARIABLE_SIZE ) {
+			sieve_sys_warning(svinst,
+				"variables: setting sieve_variables_max_variable_size "
+				"is lower than required by standards (>= %"PRIuSIZE_T" bytes)",
+				(size_t)EXT_VARIABLES_REQUIRED_MAX_VARIABLE_SIZE);
+		} else {
+			config->max_variable_size = size_setting;
+		}
+	}
+
+	*context = (void *)config;
+	return TRUE;
+}
+
+void ext_variables_unload
+(const struct sieve_extension *ext)
+{
+	struct ext_variables_config *config =
+		(struct ext_variables_config *) ext->context;
+
+	i_free(config);
+}
+
+const struct ext_variables_config *ext_variables_get_config
+(const struct sieve_extension *var_ext)
+{
+	const struct ext_variables_config *config =
+		(const struct ext_variables_config *)var_ext->context;
+	i_assert(var_ext->def == &variables_extension);
+
+	return config;
+}
+
+/*
+ * Variable scope
+ */
+
+struct sieve_variable_scope {
+	pool_t pool;
+	int refcount;
+
+	struct sieve_instance *svinst;
+	const struct sieve_extension *var_ext;
+	const struct sieve_extension *ext;
+
+	struct sieve_variable *error_var;
+
+	HASH_TABLE(const char *, struct sieve_variable *) variables;
+	ARRAY(struct sieve_variable *) variable_index;
+};
+
+struct sieve_variable_scope_binary {
+	struct sieve_variable_scope *scope;
+
+	unsigned int size;
+	struct sieve_binary_block *sblock;
+	sieve_size_t address;
+};
+
+struct sieve_variable_scope_iter {
+	struct sieve_variable_scope *scope;
+	struct hash_iterate_context *hctx;
+};
+
+struct sieve_variable_scope *sieve_variable_scope_create
+(struct sieve_instance *svinst,
+	const struct sieve_extension *var_ext,
+	const struct sieve_extension *ext)
+{
+	struct sieve_variable_scope *scope;
+	pool_t pool;
+
+	i_assert(var_ext->def == &variables_extension);
+
+	pool = pool_alloconly_create("sieve_variable_scope", 4096);
+	scope = p_new(pool, struct sieve_variable_scope, 1);
+	scope->pool = pool;
+	scope->refcount = 1;
+
+	scope->svinst = svinst;
+	scope->var_ext = var_ext;
+	scope->ext = ext;
+
+	hash_table_create(&scope->variables, pool, 0, strcase_hash, strcasecmp);
+	p_array_init(&scope->variable_index, pool, 128);
+
+	return scope;
+}
+
+void sieve_variable_scope_ref(struct sieve_variable_scope *scope)
+{
+	scope->refcount++;
+}
+
+void sieve_variable_scope_unref(struct sieve_variable_scope **_scope)
+{
+	struct sieve_variable_scope *scope = *_scope;
+
+	i_assert(scope->refcount > 0);
+
+	if (--scope->refcount != 0)
+		return;
+
+	hash_table_destroy(&scope->variables);
+
+	*_scope = NULL;
+	pool_unref(&scope->pool);
+}
+
+pool_t sieve_variable_scope_pool(struct sieve_variable_scope *scope)
+{
+	return scope->pool;
+}
+
+struct sieve_variable *sieve_variable_scope_declare
+(struct sieve_variable_scope *scope, const char *identifier)
+{
+	unsigned int max_scope_size;
+	struct sieve_variable *var;
+
+	var = hash_table_lookup(scope->variables, identifier);
+	if (var != NULL)
+		return var;
+
+	max_scope_size = sieve_variables_get_max_scope_size(scope->var_ext);
+	if ( array_count(&scope->variable_index) >= max_scope_size ) {
+		if ( scope->error_var == NULL ) {
+			var = p_new(scope->pool, struct sieve_variable, 1);
+			var->identifier = "@ERROR@";
+			var->index = 0;
+
+			scope->error_var = var;
+			return NULL;
+		}
+
+		return scope->error_var;
+	}
+
+	var = p_new(scope->pool, struct sieve_variable, 1);
+	var->ext = scope->ext;
+	var->identifier = p_strdup(scope->pool, identifier);
+	var->index = array_count(&scope->variable_index);
+
+	hash_table_insert(scope->variables, var->identifier, var);
+	array_append(&scope->variable_index, &var, 1);
+	return var;
+}
+
+struct sieve_variable *sieve_variable_scope_get_variable
+(struct sieve_variable_scope *scope, const char *identifier)
+{
+	return hash_table_lookup(scope->variables, identifier);
+}
+
+struct sieve_variable *sieve_variable_scope_import
+(struct sieve_variable_scope *scope, struct sieve_variable *var)
+{
+	struct sieve_variable *old_var, *new_var;
+
+	old_var = sieve_variable_scope_get_variable
+		(scope, var->identifier);
+	if (old_var != NULL) {
+		i_assert(memcmp(old_var, var, sizeof(*var)) == 0);
+		return old_var;
+	}
+
+	new_var = p_new(scope->pool, struct sieve_variable, 1);
+	memcpy(new_var, var, sizeof(*new_var));
+
+	hash_table_insert(scope->variables, new_var->identifier, new_var);
+
+	/* Not entered into the index because it is an external variable
+	 * (This can be done unlimited; only limited by the size of the external scope)
+	 */
+
+	return new_var;
+}
+
+struct sieve_variable_scope_iter *sieve_variable_scope_iterate_init
+(struct sieve_variable_scope *scope)
+{
+	struct sieve_variable_scope_iter *iter;
+
+	iter = t_new(struct sieve_variable_scope_iter, 1);
+	iter->scope = scope;
+	iter->hctx = hash_table_iterate_init(scope->variables);
+
+	return iter;
+}
+
+bool sieve_variable_scope_iterate
+(struct sieve_variable_scope_iter *iter, struct sieve_variable **var_r)
+{
+	const char *key;
+
+	return hash_table_iterate
+		(iter->hctx, iter->scope->variables, &key, var_r);
+}
+
+void sieve_variable_scope_iterate_deinit
+(struct sieve_variable_scope_iter **iter)
+{
+	hash_table_iterate_deinit(&(*iter)->hctx);
+	*iter = NULL;
+}
+
+unsigned int sieve_variable_scope_declarations
+(struct sieve_variable_scope *scope)
+{
+	return hash_table_count(scope->variables);
+}
+
+unsigned int sieve_variable_scope_size
+(struct sieve_variable_scope *scope)
+{
+	return array_count(&scope->variable_index);
+}
+
+struct sieve_variable * const *sieve_variable_scope_get_variables
+(struct sieve_variable_scope *scope, unsigned int *size_r)
+{
+	return array_get(&scope->variable_index, size_r);
+}
+
+struct sieve_variable *sieve_variable_scope_get_indexed
+(struct sieve_variable_scope *scope, unsigned int index)
+{
+	struct sieve_variable * const *var;
+
+	if ( index >= array_count(&scope->variable_index) )
+		return NULL;
+
+	var = array_idx(&scope->variable_index, index);
+
+	return *var;
+}
+
+/* Scope binary */
+
+struct sieve_variable_scope *sieve_variable_scope_binary_dump
+(struct sieve_instance *svinst,
+	const struct sieve_extension *var_ext,
+	const struct sieve_extension *ext,
+	const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	struct sieve_variable_scope *local_scope;
+	unsigned int i, scope_size;
+	sieve_size_t pc;
+	sieve_offset_t end_offset;
+
+	/* Read scope size */
+	sieve_code_mark(denv);
+	if ( !sieve_binary_read_unsigned(denv->sblock, address, &scope_size) )
+		return NULL;
+
+	/* Read offset */
+	pc = *address;
+	if ( !sieve_binary_read_offset(denv->sblock, address, &end_offset) )
+		return NULL;
+
+	/* Create scope */
+	local_scope = sieve_variable_scope_create(svinst, var_ext, ext);
+
+	/* Read and dump scope itself */
+
+	sieve_code_dumpf(denv, "VARIABLES SCOPE [%u] (end: %08x)",
+		scope_size, (unsigned int) (pc + end_offset));
+
+	for ( i = 0; i < scope_size; i++ ) {
+		string_t *identifier;
+
+		sieve_code_mark(denv);
+		if (!sieve_binary_read_string(denv->sblock, address, &identifier) ) {
+			return NULL;
+		}
+
+		sieve_code_dumpf(denv, "%3d: '%s'", i, str_c(identifier));
+
+		(void) sieve_variable_scope_declare(local_scope, str_c(identifier));
+	}
+
+	return local_scope;
+}
+
+struct sieve_variable_scope_binary *sieve_variable_scope_binary_create
+(struct sieve_variable_scope *scope)
+{
+	struct sieve_variable_scope_binary *scpbin;
+
+	scpbin = p_new(scope->pool, struct sieve_variable_scope_binary, 1);
+	scpbin->scope = scope;
+
+	return scpbin;
+}
+
+void sieve_variable_scope_binary_ref
+(struct sieve_variable_scope_binary *scpbin)
+{
+	sieve_variable_scope_ref(scpbin->scope);
+}
+
+void sieve_variable_scope_binary_unref
+(struct sieve_variable_scope_binary **scpbin)
+{
+	sieve_variable_scope_unref(&(*scpbin)->scope);
+	*scpbin = NULL;
+}
+
+struct sieve_variable_scope_binary *sieve_variable_scope_binary_read
+(struct sieve_instance *svinst,
+	const struct sieve_extension *var_ext,
+	const struct sieve_extension *ext,
+	struct sieve_binary_block *sblock, sieve_size_t *address)
+{
+	struct sieve_variable_scope *scope;
+	struct sieve_variable_scope_binary *scpbin;
+	unsigned int scope_size, max_scope_size;
+	const char *ext_name =
+		( ext == NULL ? "variables" : sieve_extension_name(ext) );
+	sieve_size_t pc;
+	sieve_offset_t end_offset;
+
+	/* Read scope size */
+	if ( !sieve_binary_read_unsigned(sblock, address, &scope_size) ) {
+		sieve_sys_error
+			(svinst, "%s: variable scope: failed to read size", ext_name);
+		return NULL;
+	}
+
+	/* Check size limit */
+	max_scope_size = sieve_variables_get_max_scope_size(var_ext);
+	if ( scope_size > max_scope_size ) {
+		sieve_sys_error(svinst,
+			"%s: variable scope: size exceeds the limit (%u > %u)",
+			ext_name, scope_size, max_scope_size );
+		return NULL;
+	}
+
+	/* Read offset */
+	pc = *address;
+	if ( !sieve_binary_read_offset(sblock, address, &end_offset) ) {
+		sieve_sys_error(svinst,
+			"%s: variable scope: failed to read end offset", ext_name);
+		return NULL;
+	}
+
+	/* Create scope */
+	scope = sieve_variable_scope_create(svinst, var_ext, ext);
+
+	scpbin = sieve_variable_scope_binary_create(scope);
+	scpbin->size = scope_size;
+	scpbin->sblock = sblock;
+	scpbin->address = *address;
+
+	*address = pc + end_offset;
+
+	return scpbin;
+}
+
+struct sieve_variable_scope *sieve_variable_scope_binary_get
+(struct sieve_variable_scope_binary *scpbin)
+{
+	const struct sieve_extension *ext = scpbin->scope->ext;
+	struct sieve_instance *svinst = scpbin->scope->svinst;
+	const char *ext_name =
+		( ext == NULL ? "variables" : sieve_extension_name(ext) );
+	unsigned int i;
+
+	if ( scpbin->sblock != NULL ) {
+		sieve_size_t *address = &scpbin->address;
+
+		/* Read scope itself */
+		for ( i = 0; i < scpbin->size; i++ ) {
+			struct sieve_variable *var;
+			string_t *identifier;
+
+			if (!sieve_binary_read_string(scpbin->sblock, address, &identifier) ) {
+				sieve_sys_error(svinst,
+					"%s: variable scope: failed to read variable name", ext_name);
+				return NULL;
+			}
+
+			var = sieve_variable_scope_declare(scpbin->scope, str_c(identifier));
+
+			i_assert( var != NULL );
+			i_assert( var->index == i );
+		}
+
+		scpbin->sblock = NULL;
+	}
+
+	return scpbin->scope;
+}
+
+unsigned int sieve_variable_scope_binary_get_size
+(struct sieve_variable_scope_binary *scpbin)
+{
+	if ( scpbin->sblock != NULL )
+		return scpbin->size;
+
+	return array_count(&scpbin->scope->variable_index);
+}
+
+/*
+ * Variable storage
+ */
+
+struct sieve_variable_storage {
+	pool_t pool;
+	const struct sieve_extension *var_ext;
+	struct sieve_variable_scope *scope;
+	struct sieve_variable_scope_binary *scope_bin;
+	unsigned int max_size;
+	ARRAY(string_t *) var_values;
+};
+
+struct sieve_variable_storage *sieve_variable_storage_create
+(const struct sieve_extension *var_ext, pool_t pool,
+	struct sieve_variable_scope_binary *scpbin)
+{
+	struct sieve_variable_storage *storage;
+
+	storage = p_new(pool, struct sieve_variable_storage, 1);
+	storage->pool = pool;
+	storage->var_ext = var_ext;
+	storage->scope_bin = scpbin;
+	storage->scope = NULL;
+
+	storage->max_size = sieve_variable_scope_binary_get_size(scpbin);
+
+	p_array_init(&storage->var_values, pool, 4);
+
+	return storage;
+}
+
+static inline bool sieve_variable_valid
+(struct sieve_variable_storage *storage, unsigned int index)
+{
+	if ( storage->scope_bin == NULL )
+		return TRUE;
+
+	return ( index < storage->max_size );
+}
+
+bool sieve_variable_get_identifier
+(struct sieve_variable_storage *storage, unsigned int index,
+	const char **identifier)
+{
+	struct sieve_variable * const *var;
+
+	*identifier = NULL;
+
+	if ( storage->scope_bin == NULL )
+		return TRUE;
+
+	if ( storage->scope == NULL ) {
+		storage->scope = sieve_variable_scope_binary_get(storage->scope_bin);
+		if ( storage->scope == NULL ) return FALSE;
+	}
+
+	/* FIXME: direct invasion of the scope object is a bit ugly */
+	if ( index >= array_count(&storage->scope->variable_index) )
+		return FALSE;
+
+	var = array_idx(&storage->scope->variable_index, index);
+
+	if ( *var != NULL )
+		*identifier = (*var)->identifier;
+
+	return TRUE;
+}
+
+const char *sieve_variable_get_varid
+(struct sieve_variable_storage *storage, unsigned int index)
+{
+	if ( storage->scope_bin == NULL )
+		return t_strdup_printf("%ld", (long) index);
+
+	if ( storage->scope == NULL ) {
+		storage->scope = sieve_variable_scope_binary_get(storage->scope_bin);
+		if ( storage->scope == NULL ) return NULL;
+	}
+
+	return sieve_ext_variables_get_varid(storage->scope->ext, index);
+}
+
+bool sieve_variable_get
+(struct sieve_variable_storage *storage, unsigned int index, string_t **value)
+{
+	*value = NULL;
+
+	if  ( index < array_count(&storage->var_values) ) {
+		string_t * const *varent;
+
+		varent = array_idx(&storage->var_values, index);
+
+		*value = *varent;
+	} else if ( !sieve_variable_valid(storage, index) )
+		return FALSE;
+
+	return TRUE;
+}
+
+bool sieve_variable_get_modifiable
+(struct sieve_variable_storage *storage, unsigned int index, string_t **value)
+{
+	string_t *dummy;
+
+	if ( value == NULL ) value = &dummy;
+
+	if ( !sieve_variable_get(storage, index, value) )
+		return FALSE;
+
+	if ( *value == NULL ) {
+		*value = str_new(storage->pool, 256);
+		array_idx_set(&storage->var_values, index, value);
+	}
+
+	return TRUE;
+}
+
+bool sieve_variable_assign
+(struct sieve_variable_storage *storage, unsigned int index,
+	const string_t *value)
+{
+	const struct ext_variables_config *config =
+		ext_variables_get_config(storage->var_ext);
+	string_t *varval;
+
+	if ( !sieve_variable_get_modifiable(storage, index, &varval) )
+		return FALSE;
+
+	str_truncate(varval, 0);
+	str_append_str(varval, value);
+
+	/* Just a precaution, caller should prevent this in the first place */
+	if ( str_len(varval) > config->max_variable_size )
+		str_truncate(varval, config->max_variable_size);
+
+	return TRUE;
+}
+
+bool sieve_variable_assign_cstr
+(struct sieve_variable_storage *storage, unsigned int index,
+	const char *value)
+{
+	const struct ext_variables_config *config =
+		ext_variables_get_config(storage->var_ext);
+	string_t *varval;
+
+	if ( !sieve_variable_get_modifiable(storage, index, &varval) )
+		return FALSE;
+
+	str_truncate(varval, 0);
+	str_append(varval, value);
+
+	/* Just a precaution, caller should prevent this in the first place */
+	if ( str_len(varval) > config->max_variable_size )
+		str_truncate(varval, config->max_variable_size);
+
+	return TRUE;
+}
+
+/*
+ * AST Context
+ */
+
+static void ext_variables_ast_free
+(const struct sieve_extension *ext ATTR_UNUSED,
+	struct sieve_ast *ast ATTR_UNUSED, void *context)
+{
+	struct sieve_variable_scope *local_scope =
+		(struct sieve_variable_scope *) context;
+
+	/* Unreference main variable scope */
+	sieve_variable_scope_unref(&local_scope);
+}
+
+static const struct sieve_ast_extension variables_ast_extension = {
+    &variables_extension,
+    ext_variables_ast_free
+};
+
+static struct sieve_variable_scope *ext_variables_create_local_scope
+(const struct sieve_extension *this_ext, struct sieve_ast *ast)
+{
+	struct sieve_variable_scope *scope;
+
+	scope = sieve_variable_scope_create
+		(this_ext->svinst, this_ext, NULL);
+
+	sieve_ast_extension_register
+		(ast, this_ext, &variables_ast_extension, (void *) scope);
+
+	return scope;
+}
+
+static struct sieve_variable_scope *ext_variables_ast_get_local_scope
+(const struct sieve_extension *this_ext, struct sieve_ast *ast)
+{
+	struct sieve_variable_scope *local_scope = (struct sieve_variable_scope *)
+		sieve_ast_extension_get_context(ast, this_ext);
+
+	return local_scope;
+}
+
+/*
+ * Validator context
+ */
+
+static struct ext_variables_validator_context *
+ext_variables_validator_context_create
+(const struct sieve_extension *this_ext, struct sieve_validator *valdtr)
+{
+	pool_t pool = sieve_validator_pool(valdtr);
+	struct ext_variables_validator_context *ctx;
+	struct sieve_ast *ast = sieve_validator_ast(valdtr);
+
+	ctx = p_new(pool, struct ext_variables_validator_context, 1);
+	ctx->modifiers = sieve_validator_object_registry_create(valdtr);
+	ctx->namespaces = sieve_validator_object_registry_create(valdtr);
+	ctx->local_scope = ext_variables_create_local_scope(this_ext, ast);
+
+	sieve_validator_extension_set_context(valdtr, this_ext, (void *) ctx);
+	return ctx;
+}
+
+struct ext_variables_validator_context *ext_variables_validator_context_get
+(const struct sieve_extension *this_ext, struct sieve_validator *valdtr)
+{
+	struct ext_variables_validator_context *ctx;
+
+	i_assert( sieve_extension_is(this_ext, variables_extension) );
+	ctx = (struct ext_variables_validator_context *)
+		sieve_validator_extension_get_context(valdtr, this_ext);
+
+	if ( ctx == NULL ) {
+		ctx = ext_variables_validator_context_create(this_ext, valdtr);
+	}
+
+	return ctx;
+}
+
+void ext_variables_validator_initialize
+(const struct sieve_extension *this_ext, struct sieve_validator *valdtr)
+{
+	struct ext_variables_validator_context *ctx;
+
+	/* Create our context */
+	ctx = ext_variables_validator_context_get(this_ext, valdtr);
+
+	ext_variables_register_core_modifiers(this_ext, ctx);
+
+	ctx->active = TRUE;
+}
+
+struct sieve_variable *ext_variables_validator_get_variable
+(const struct sieve_extension *this_ext, struct sieve_validator *validator,
+	const char *variable)
+{
+	struct ext_variables_validator_context *ctx =
+		ext_variables_validator_context_get(this_ext, validator);
+
+	return sieve_variable_scope_get_variable(ctx->local_scope, variable);
+}
+
+struct sieve_variable *ext_variables_validator_declare_variable
+(const struct sieve_extension *this_ext, struct sieve_validator *validator,
+	const char *variable)
+{
+	struct ext_variables_validator_context *ctx =
+		ext_variables_validator_context_get(this_ext, validator);
+
+	return sieve_variable_scope_declare(ctx->local_scope, variable);
+}
+
+struct sieve_variable_scope *sieve_ext_variables_get_local_scope
+(const struct sieve_extension *var_ext, struct sieve_validator *validator)
+{
+	struct ext_variables_validator_context *ctx =
+		ext_variables_validator_context_get(var_ext, validator);
+
+	return ctx->local_scope;
+}
+
+bool sieve_ext_variables_is_active
+(const struct sieve_extension *var_ext, struct sieve_validator *valdtr)
+{
+	struct ext_variables_validator_context *ctx =
+		ext_variables_validator_context_get(var_ext, valdtr);
+
+	return ( ctx != NULL && ctx->active );
+}
+
+/*
+ * Code generation
+ */
+
+bool ext_variables_generator_load
+(const struct sieve_extension *ext, const struct sieve_codegen_env *cgenv)
+{
+	struct sieve_variable_scope *local_scope =
+		ext_variables_ast_get_local_scope(ext, cgenv->ast);
+	unsigned int count = sieve_variable_scope_size(local_scope);
+	sieve_size_t jump;
+
+	sieve_binary_emit_unsigned(cgenv->sblock, count);
+
+	jump = sieve_binary_emit_offset(cgenv->sblock, 0);
+
+	if ( count > 0 ) {
+		unsigned int size, i;
+		struct sieve_variable *const *vars =
+			sieve_variable_scope_get_variables(local_scope, &size);
+
+		for ( i = 0; i < size; i++ ) {
+			sieve_binary_emit_cstring(cgenv->sblock, vars[i]->identifier);
+		}
+	}
+
+	sieve_binary_resolve_offset(cgenv->sblock, jump);
+
+	return TRUE;
+}
+
+/*
+ * Interpreter context
+ */
+
+struct ext_variables_interpreter_context {
+	pool_t pool;
+
+	struct sieve_variable_scope *local_scope;
+	struct sieve_variable_scope_binary *local_scope_bin;
+
+	struct sieve_variable_storage *local_storage;
+	ARRAY(struct sieve_variable_storage *) ext_storages;
+};
+
+static void ext_variables_interpreter_free
+(const struct sieve_extension *ext ATTR_UNUSED,
+	struct sieve_interpreter *interp ATTR_UNUSED, void *context)
+{
+	struct ext_variables_interpreter_context *ctx =
+		(struct ext_variables_interpreter_context *)context;
+
+	sieve_variable_scope_binary_unref(&ctx->local_scope_bin);
+}
+
+static struct sieve_interpreter_extension
+variables_interpreter_extension = {
+	.ext_def = &variables_extension,
+	.free = ext_variables_interpreter_free
+};
+
+static struct ext_variables_interpreter_context *
+ext_variables_interpreter_context_create
+(const struct sieve_extension *this_ext, struct sieve_interpreter *interp,
+	struct sieve_variable_scope_binary *scpbin)
+{
+	pool_t pool = sieve_interpreter_pool(interp);
+	struct ext_variables_interpreter_context *ctx;
+
+	ctx = p_new(pool, struct ext_variables_interpreter_context, 1);
+	ctx->pool = pool;
+	ctx->local_scope = NULL;
+	ctx->local_scope_bin = scpbin;
+	ctx->local_storage =
+		sieve_variable_storage_create(this_ext, pool, scpbin);
+	p_array_init(&ctx->ext_storages, pool,
+		sieve_extensions_get_count(this_ext->svinst));
+
+	sieve_interpreter_extension_register
+		(interp, this_ext, &variables_interpreter_extension, (void *) ctx);
+
+	return ctx;
+}
+
+bool ext_variables_interpreter_load
+(const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+	sieve_size_t *address)
+{
+	struct sieve_variable_scope_binary *scpbin;
+
+	scpbin = sieve_variable_scope_binary_read
+		(renv->svinst, ext, NULL, renv->sblock, address);
+	if ( scpbin == NULL )
+		return FALSE;
+
+	/* Create our context */
+	(void)ext_variables_interpreter_context_create
+		(ext, renv->interp, scpbin);
+
+	/* Enable support for match values */
+	(void) sieve_match_values_set_enabled(renv, TRUE);
+
+	return TRUE;
+}
+
+static inline struct ext_variables_interpreter_context *
+ext_variables_interpreter_context_get
+	(const struct sieve_extension *this_ext, struct sieve_interpreter *interp)
+{
+	struct ext_variables_interpreter_context *ctx;
+
+	i_assert( sieve_extension_is(this_ext, variables_extension) );
+	ctx = (struct ext_variables_interpreter_context *)
+		sieve_interpreter_extension_get_context(interp, this_ext);
+
+	return ctx;
+}
+
+struct sieve_variable_storage *sieve_ext_variables_runtime_get_storage
+(const struct sieve_extension *var_ext, const struct sieve_runtime_env *renv,
+	const struct sieve_extension *ext)
+{
+	struct ext_variables_interpreter_context *ctx =
+		ext_variables_interpreter_context_get(var_ext, renv->interp);
+	struct sieve_variable_storage * const *storage;
+
+	if ( ext == NULL )
+		return ctx->local_storage;
+
+	if ( ext->id >= (int) array_count(&ctx->ext_storages) ) {
+		storage = NULL;
+	} else {
+		storage = array_idx(&ctx->ext_storages, ext->id);
+	}
+
+	if ( storage == NULL ) return NULL;
+
+	return *storage;
+}
+
+void sieve_ext_variables_runtime_set_storage
+(const struct sieve_extension *var_ext, const struct sieve_runtime_env *renv,
+	const struct sieve_extension *ext, struct sieve_variable_storage *storage)
+{
+	struct ext_variables_interpreter_context *ctx =
+		ext_variables_interpreter_context_get(var_ext, renv->interp);
+
+	if ( ctx == NULL || ext == NULL || storage == NULL )
+		return;
+
+	if ( ext->id < 0 ) return;
+
+	array_idx_set(&ctx->ext_storages, (unsigned int) ext->id, &storage);
+}
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-common.h
@@ -0,0 +1,101 @@
+#ifndef EXT_VARIABLES_COMMON_H
+#define EXT_VARIABLES_COMMON_H
+
+#include "sieve-common.h"
+#include "sieve-validator.h"
+
+#include "sieve-ext-variables.h"
+
+/*
+ * Extension
+ */
+
+struct ext_variables_config {
+	/* Maximum number of variables (in a scope) */
+	unsigned int max_scope_size;
+	/* Maximum size of variable value */
+	size_t max_variable_size;
+};
+
+extern const struct sieve_extension_def variables_extension;
+
+bool ext_variables_load
+	(const struct sieve_extension *ext, void **context);
+void ext_variables_unload
+	(const struct sieve_extension *ext);
+
+const struct ext_variables_config *ext_variables_get_config
+	(const struct sieve_extension *var_ext);
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def cmd_set;
+extern const struct sieve_command_def tst_string;
+
+/*
+ * Operands
+ */
+
+enum ext_variables_operand {
+	EXT_VARIABLES_OPERAND_VARIABLE,
+	EXT_VARIABLES_OPERAND_MATCH_VALUE,
+	EXT_VARIABLES_OPERAND_NAMESPACE_VARIABLE,
+	EXT_VARIABLES_OPERAND_MODIFIER
+};
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def cmd_set_operation;
+extern const struct sieve_operation_def tst_string_operation;
+
+enum ext_variables_opcode {
+	EXT_VARIABLES_OPERATION_SET,
+	EXT_VARIABLES_OPERATION_STRING
+};
+
+/*
+ * Validator context
+ */
+
+struct ext_variables_validator_context {
+	bool active;
+
+	struct sieve_validator_object_registry *modifiers;
+	struct sieve_validator_object_registry *namespaces;
+
+	struct sieve_variable_scope *local_scope;
+};
+
+void ext_variables_validator_initialize
+	(const struct sieve_extension *this_ext, struct sieve_validator *validator);
+
+struct ext_variables_validator_context *ext_variables_validator_context_get
+	(const struct sieve_extension *this_ext, struct sieve_validator *valdtr);
+
+struct sieve_variable *ext_variables_validator_get_variable
+	(const struct sieve_extension *this_ext, struct sieve_validator *validator,
+		const char *variable);
+struct sieve_variable *ext_variables_validator_declare_variable
+	(const struct sieve_extension *this_ext, struct sieve_validator *validator,
+		const char *variable);
+
+/*
+ * Code generation
+ */
+
+bool ext_variables_generator_load
+	(const struct sieve_extension *ext, const struct sieve_codegen_env *cgenv);
+
+/*
+ * Interpreter context
+ */
+
+bool ext_variables_interpreter_load
+	(const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+		sieve_size_t *address);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-dump.c
@@ -0,0 +1,137 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve-common.h"
+#include "sieve-dump.h"
+#include "sieve-binary.h"
+#include "sieve-code.h"
+
+#include "ext-variables-common.h"
+#include "ext-variables-dump.h"
+
+/*
+ * Code dumper extension
+ */
+
+static void ext_variables_code_dumper_free
+	(struct sieve_code_dumper *dumper, void *context);
+
+static const struct sieve_code_dumper_extension
+variables_dump_extension = {
+	&variables_extension,
+	ext_variables_code_dumper_free
+};
+
+/*
+ * Code dump context
+ */
+
+struct ext_variables_dump_context {
+	struct sieve_variable_scope *local_scope;
+	ARRAY(struct sieve_variable_scope *) ext_scopes;
+};
+
+static void ext_variables_code_dumper_free
+(struct sieve_code_dumper *dumper ATTR_UNUSED, void *context)
+{
+	struct ext_variables_dump_context *dctx =
+		(struct ext_variables_dump_context *) context;
+
+	if ( dctx == NULL || dctx->local_scope == NULL )
+		return;
+
+	sieve_variable_scope_unref(&dctx->local_scope);
+}
+
+static struct ext_variables_dump_context *ext_variables_dump_get_context
+(const struct sieve_extension *this_ext, const struct sieve_dumptime_env *denv)
+{
+	struct sieve_code_dumper *dumper = denv->cdumper;
+	struct ext_variables_dump_context *dctx;
+	pool_t pool;
+
+	i_assert( sieve_extension_is(this_ext, variables_extension) );
+	dctx = sieve_dump_extension_get_context(dumper, this_ext);
+
+	if ( dctx == NULL ) {
+		/* Create dumper context */
+		pool = sieve_code_dumper_pool(dumper);
+		dctx = p_new(pool, struct ext_variables_dump_context, 1);
+		p_array_init(&dctx->ext_scopes, pool,
+			sieve_extensions_get_count(this_ext->svinst));
+
+		sieve_dump_extension_register
+			(dumper, this_ext, &variables_dump_extension, dctx);
+	}
+
+	return dctx;
+}
+
+bool ext_variables_code_dump
+(const struct sieve_extension *ext,
+	const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	struct ext_variables_dump_context *dctx;
+	struct sieve_variable_scope *local_scope;
+
+	local_scope = sieve_variable_scope_binary_dump
+		(ext->svinst, ext, NULL, denv, address);
+
+	dctx = ext_variables_dump_get_context(ext, denv);
+	dctx->local_scope = local_scope;
+
+	return TRUE;
+}
+
+/*
+ * Scope registry
+ */
+
+void sieve_ext_variables_dump_set_scope
+(const struct sieve_extension *var_ext, const struct sieve_dumptime_env *denv,
+	const struct sieve_extension *ext, struct sieve_variable_scope *scope)
+{
+	struct ext_variables_dump_context *dctx =
+		ext_variables_dump_get_context(var_ext, denv);
+
+	if ( ext->id < 0 ) return;
+
+	array_idx_set(&dctx->ext_scopes, (unsigned int) ext->id, &scope);
+}
+
+/*
+ * Variable identifier dump
+ */
+
+const char *ext_variables_dump_get_identifier
+(const struct sieve_extension *var_ext, const struct sieve_dumptime_env *denv,
+	const struct sieve_extension *ext, unsigned int index)
+{
+	struct ext_variables_dump_context *dctx =
+		ext_variables_dump_get_context(var_ext, denv);
+	struct sieve_variable_scope *scope;
+	struct sieve_variable *var;
+
+	if ( ext == NULL )
+		scope = dctx->local_scope;
+	else {
+		struct sieve_variable_scope *const *ext_scope;
+
+		if  ( ext->id < 0 || ext->id >= (int) array_count(&dctx->ext_scopes) )
+			return NULL;
+
+		ext_scope = array_idx(&dctx->ext_scopes, (unsigned int) ext->id);
+		scope = *ext_scope;
+	}
+
+	if ( scope == NULL )
+		return NULL;
+
+	var = sieve_variable_scope_get_indexed(scope, index);
+
+	return var->identifier;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-dump.h
@@ -0,0 +1,22 @@
+#ifndef EXT_VARIABLES_DUMP_H
+#define EXT_VARIABLES_DUMP_H
+
+#include "sieve-common.h"
+
+/*
+ * Code dump context
+ */
+
+bool ext_variables_code_dump
+	(const struct sieve_extension *ext, const struct sieve_dumptime_env *denv,
+		sieve_size_t *address);
+
+/*
+ * Variable identifier dump
+ */
+
+const char *ext_variables_dump_get_identifier
+(const struct sieve_extension *var_ext, const struct sieve_dumptime_env *denv,
+	const struct sieve_extension *ext, unsigned int index);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-limits.h
@@ -0,0 +1,35 @@
+#ifndef EXT_VARIABLES_LIMITS_H
+#define EXT_VARIABLES_LIMITS_H
+
+#include "sieve-limits.h"
+
+/* From RFC 5229:
+ *
+ * 6.  Implementation Limits
+ *
+ *  An implementation of this document MUST support at least 128 distinct
+ *  variables.  The supported length of variable names MUST be at least
+ *  32 characters.  Each variable MUST be able to hold at least 4000
+ *  characters.  Attempts to set the variable to a value larger than what
+ *  the implementation supports SHOULD be reported as an error at
+ *  compile-time if possible.  If the attempt is discovered during run-
+ *  time, the value SHOULD be truncated, and it MUST NOT be treated as an
+ *  error.
+
+ *  Match variables ${1} through ${9} MUST be supported.  References to
+ *  higher indices than those the implementation supports MUST be treated
+ *  as a syntax error, which SHOULD be discovered at compile-time.
+ */
+
+#define EXT_VARIABLES_DEFAULT_MAX_SCOPE_SIZE     255
+#define EXT_VARIABLES_DEFAULT_MAX_VARIABLE_SIZE  (4 * 1024)
+
+#define EXT_VARIABLES_REQUIRED_MAX_SCOPE_SIZE    128
+#define EXT_VARIABLES_REQUIRED_MAX_VARIABLE_SIZE 4000
+
+#define EXT_VARIABLES_MAX_VARIABLE_NAME_LEN      64
+#define EXT_VARIABLES_MAX_NAMESPACE_ELEMENTS     10
+
+#define EXT_VARIABLES_MAX_MATCH_INDEX            SIEVE_MAX_MATCH_VALUES
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-modifiers.c
@@ -0,0 +1,520 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "unichar.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-runtime.h"
+
+#include "ext-variables-common.h"
+#include "ext-variables-limits.h"
+#include "ext-variables-modifiers.h"
+
+#include <ctype.h>
+
+/*
+ * Core modifiers
+ */
+
+extern const struct sieve_variables_modifier_def lower_modifier;
+extern const struct sieve_variables_modifier_def upper_modifier;
+extern const struct sieve_variables_modifier_def lowerfirst_modifier;
+extern const struct sieve_variables_modifier_def upperfirst_modifier;
+extern const struct sieve_variables_modifier_def quotewildcard_modifier;
+extern const struct sieve_variables_modifier_def length_modifier;
+
+enum ext_variables_modifier_code {
+    EXT_VARIABLES_MODIFIER_LOWER,
+    EXT_VARIABLES_MODIFIER_UPPER,
+    EXT_VARIABLES_MODIFIER_LOWERFIRST,
+    EXT_VARIABLES_MODIFIER_UPPERFIRST,
+    EXT_VARIABLES_MODIFIER_QUOTEWILDCARD,
+    EXT_VARIABLES_MODIFIER_LENGTH
+};
+
+const struct sieve_variables_modifier_def *ext_variables_core_modifiers[] = {
+	&lower_modifier,
+	&upper_modifier,
+	&lowerfirst_modifier,
+	&upperfirst_modifier,
+	&quotewildcard_modifier,
+	&length_modifier
+};
+
+const unsigned int ext_variables_core_modifiers_count =
+    N_ELEMENTS(ext_variables_core_modifiers);
+
+#define ext_variables_modifier_name(modf) \
+	(modf)->object->def->name
+#define ext_variables_modifiers_equal(modf1, modf2) \
+	( (modf1)->def == (modf2)->def )
+#define ext_variables_modifiers_equal_precedence(modf1, modf2) \
+	( (modf1)->def->precedence == (modf2)->def->precendence )
+
+/*
+ * Modifier registry
+ */
+
+void sieve_variables_modifier_register
+(const struct sieve_extension *var_ext, struct sieve_validator *valdtr,
+	const struct sieve_extension *ext,
+	const struct sieve_variables_modifier_def *smodf_def)
+{
+	struct ext_variables_validator_context *ctx =
+		ext_variables_validator_context_get(var_ext, valdtr);
+
+	sieve_validator_object_registry_add(ctx->modifiers, ext, &smodf_def->obj_def);
+}
+
+bool ext_variables_modifier_exists
+(const struct sieve_extension *var_ext, struct sieve_validator *valdtr,
+	const char *identifier)
+{
+	struct ext_variables_validator_context *ctx =
+		ext_variables_validator_context_get(var_ext, valdtr);
+
+	return sieve_validator_object_registry_find(ctx->modifiers, identifier, NULL);
+}
+
+const struct sieve_variables_modifier *ext_variables_modifier_create_instance
+(const struct sieve_extension *var_ext, struct sieve_validator *valdtr,
+	struct sieve_command *cmd, const char *identifier)
+{
+	struct ext_variables_validator_context *ctx =
+		ext_variables_validator_context_get(var_ext, valdtr);
+	struct sieve_object object;
+	struct sieve_variables_modifier *modf;
+	pool_t pool;
+
+	if ( !sieve_validator_object_registry_find
+		(ctx->modifiers, identifier, &object) )
+		return NULL;
+
+	pool = sieve_command_pool(cmd);
+	modf = p_new(pool, struct sieve_variables_modifier, 1);
+	modf->object = object;
+	modf->def = (const struct sieve_variables_modifier_def *) object.def;
+
+  return modf;
+}
+
+void ext_variables_register_core_modifiers
+(const struct sieve_extension *ext, struct ext_variables_validator_context *ctx)
+{
+	unsigned int i;
+
+	/* Register core modifiers*/
+	for ( i = 0; i < ext_variables_core_modifiers_count; i++ ) {
+		sieve_validator_object_registry_add
+			(ctx->modifiers, ext, &(ext_variables_core_modifiers[i]->obj_def));
+	}
+}
+
+/*
+ * Core modifiers
+ */
+
+/* Forward declarations */
+
+bool mod_lower_modify(string_t *in, string_t **result);
+bool mod_upper_modify(string_t *in, string_t **result);
+bool mod_lowerfirst_modify(string_t *in, string_t **result);
+bool mod_upperfirst_modify(string_t *in, string_t **result);
+bool mod_length_modify(string_t *in, string_t **result);
+bool mod_quotewildcard_modify(string_t *in, string_t **result);
+
+/* Modifier objects */
+
+const struct sieve_variables_modifier_def lower_modifier = {
+	SIEVE_OBJECT("lower", &modifier_operand, EXT_VARIABLES_MODIFIER_LOWER),
+	40,
+	mod_lower_modify
+};
+
+const struct sieve_variables_modifier_def upper_modifier = {
+	SIEVE_OBJECT("upper", &modifier_operand, EXT_VARIABLES_MODIFIER_UPPER),
+	40,
+	mod_upper_modify
+};
+
+const struct sieve_variables_modifier_def lowerfirst_modifier = {
+	SIEVE_OBJECT
+		("lowerfirst", &modifier_operand, EXT_VARIABLES_MODIFIER_LOWERFIRST),
+	30,
+	mod_lowerfirst_modify
+};
+
+const struct sieve_variables_modifier_def upperfirst_modifier = {
+	SIEVE_OBJECT
+		("upperfirst", &modifier_operand,	EXT_VARIABLES_MODIFIER_UPPERFIRST),
+	30,
+	mod_upperfirst_modify
+};
+
+const struct sieve_variables_modifier_def quotewildcard_modifier = {
+	SIEVE_OBJECT
+		("quotewildcard", &modifier_operand, EXT_VARIABLES_MODIFIER_QUOTEWILDCARD),
+	20,
+	mod_quotewildcard_modify
+};
+
+const struct sieve_variables_modifier_def length_modifier = {
+	SIEVE_OBJECT("length", &modifier_operand, EXT_VARIABLES_MODIFIER_LENGTH),
+	10,
+	mod_length_modify
+};
+
+/* Modifier implementations */
+
+bool mod_upperfirst_modify(string_t *in, string_t **result)
+{
+	char *content;
+
+	if ( str_len(in) == 0 ) {
+		*result = in;
+		return TRUE;
+	}
+
+	*result = t_str_new(str_len(in));
+	str_append_str(*result, in);
+
+	content = str_c_modifiable(*result);
+	content[0] = i_toupper(content[0]);
+
+	return TRUE;
+}
+
+bool mod_lowerfirst_modify(string_t *in, string_t **result)
+{
+	char *content;
+
+	if ( str_len(in) == 0 ) {
+		*result = in;
+		return TRUE;
+	}
+
+	*result = t_str_new(str_len(in));
+	str_append_str(*result, in);
+
+	content = str_c_modifiable(*result);
+	content[0] = i_tolower(content[0]);
+
+	return TRUE;
+}
+
+bool mod_upper_modify(string_t *in, string_t **result)
+{
+	char *content;
+
+	if ( str_len(in) == 0 ) {
+		*result = in;
+		return TRUE;
+	}
+
+	*result = t_str_new(str_len(in));
+	str_append_str(*result, in);
+
+	content = str_c_modifiable(*result);
+	(void)str_ucase(content);
+
+	return TRUE;
+}
+
+bool mod_lower_modify(string_t *in, string_t **result)
+{
+	char *content;
+
+	if ( str_len(in) == 0 ) {
+		*result = in;
+		return TRUE;
+	}
+
+	*result = t_str_new(str_len(in));
+	str_append_str(*result, in);
+
+	content = str_c_modifiable(*result);
+	(void)str_lcase(content);
+
+	return TRUE;
+}
+
+bool mod_length_modify(string_t *in, string_t **result)
+{
+	*result = t_str_new(64);
+	str_printfa(*result, "%llu", (unsigned long long)
+		uni_utf8_strlen_n(str_data(in), str_len(in)));
+	return TRUE;
+}
+
+bool mod_quotewildcard_modify(string_t *in, string_t **result)
+{
+	unsigned int i;
+	const char *content;
+
+	if ( str_len(in) == 0 ) {
+		*result = in;
+		return TRUE;
+	}
+
+	*result = t_str_new(str_len(in) * 2);
+	content = (const char *) str_data(in);
+
+	for ( i = 0; i < str_len(in); i++ ) {
+		if ( content[i] == '*' || content[i] == '?' || content[i] == '\\' ) {
+			str_append_c(*result, '\\');
+		}
+		str_append_c(*result, content[i]);
+	}
+
+	return TRUE;
+}
+
+/*
+ * Modifier argument
+ */
+
+/* [MODIFIER]:
+ *   ":lower" / ":upper" / ":lowerfirst" / ":upperfirst" /
+ *             ":quotewildcard" / ":length"
+ */
+
+/* Forward declarations */
+
+static bool tag_modifier_is_instance_of
+	(struct sieve_validator *valdtr, struct sieve_command *cmd,
+		const struct sieve_extension *ext, const char *identifier, void **context);
+
+/* Modifier tag object */
+
+static const struct sieve_argument_def modifier_tag = {
+	.identifier = "MODIFIER",
+	.flags = SIEVE_ARGUMENT_FLAG_MULTIPLE,
+	.is_instance_of = tag_modifier_is_instance_of
+};
+
+/* Modifier tag implementation */
+
+static bool tag_modifier_is_instance_of
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+	const struct sieve_extension *ext, const char *identifier, void **data)
+{
+	const struct sieve_variables_modifier *modf;
+
+	if ( data == NULL ) {
+		return ext_variables_modifier_exists(ext, valdtr, identifier);
+	}
+
+	if ( (modf=ext_variables_modifier_create_instance
+		(ext, valdtr, cmd, identifier)) == NULL )
+		return FALSE;
+
+	*data = (void *) modf;
+
+	return TRUE;
+}
+
+/* Registration */
+
+void sieve_variables_modifiers_link_tag
+(struct sieve_validator *valdtr, const struct sieve_extension *var_ext,
+	struct sieve_command_registration *cmd_reg)
+{
+	sieve_validator_register_tag(valdtr, cmd_reg, var_ext, &modifier_tag, 0);
+}
+
+/* Validation */
+
+bool sieve_variables_modifiers_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+	ARRAY_TYPE(sieve_variables_modifier) *modifiers)
+{
+	struct sieve_ast_argument *arg;
+
+	arg = sieve_command_first_argument(cmd);
+	while ( arg != NULL && arg != cmd->first_positional ) {
+		const struct sieve_variables_modifier *modfs;
+		const struct sieve_variables_modifier *modf;
+		unsigned int i, modf_count;
+		bool inserted;
+
+		if ( !sieve_argument_is(arg, modifier_tag) ) {
+			arg = sieve_ast_argument_next(arg);
+			continue;
+		}
+		modf = (const struct sieve_variables_modifier *)
+			arg->argument->data;
+
+		inserted = FALSE;
+		modfs = array_get(modifiers, &modf_count);
+		for ( i = 0; i < modf_count && !inserted; i++ ) {
+
+			if ( modfs[i].def->precedence == modf->def->precedence ) {
+				sieve_argument_validate_error(valdtr, arg,
+					"modifiers :%s and :%s specified for the set command conflict "
+					"having equal precedence",
+					modfs[i].def->obj_def.identifier, modf->def->obj_def.identifier);
+				return FALSE;
+			}
+
+			if ( modfs[i].def->precedence < modf->def->precedence ) {
+				array_insert(modifiers, i, modf, 1);
+				inserted = TRUE;
+			}
+		}
+
+		if ( !inserted )
+			array_append(modifiers, modf, 1);
+
+		/* Added to modifier list;
+		   self-destruct to prevent implicit code generation */
+		arg = sieve_ast_arguments_detach(arg, 1);
+	}
+	return TRUE;
+}
+
+bool sieve_variables_modifiers_generate
+(const struct sieve_codegen_env *cgenv,
+	ARRAY_TYPE(sieve_variables_modifier) *modifiers)
+{
+	struct sieve_binary_block *sblock = cgenv->sblock;
+	const struct sieve_variables_modifier *modfs;
+	unsigned int i, modf_count;
+
+	sieve_binary_emit_byte(sblock, array_count(modifiers));
+
+	modfs = array_get(modifiers, &modf_count);
+	for ( i = 0; i < modf_count; i++ ) {
+		ext_variables_opr_modifier_emit(sblock,
+			modfs[i].object.ext, modfs[i].def);
+	}
+	return TRUE;
+}
+
+/*
+ * Modifier coding
+ */
+
+const struct sieve_operand_class sieve_variables_modifier_operand_class =
+	{ "modifier" };
+
+static const struct sieve_extension_objects core_modifiers =
+	SIEVE_VARIABLES_DEFINE_MODIFIERS(ext_variables_core_modifiers);
+
+const struct sieve_operand_def modifier_operand = {
+	.name = "modifier",
+	.ext_def = &variables_extension,
+	.code = EXT_VARIABLES_OPERAND_MODIFIER,
+	.class = &sieve_variables_modifier_operand_class,
+	.interface = &core_modifiers
+};
+
+bool sieve_variables_modifiers_code_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	unsigned int mdfs, i;
+
+	/* Read the number of applied modifiers we need to read */
+	if ( !sieve_binary_read_byte(denv->sblock, address, &mdfs) )
+		return FALSE;
+
+	/* Print all modifiers (sorted during code generation already) */
+	for ( i = 0; i < mdfs; i++ ) {
+		if ( !ext_variables_opr_modifier_dump(denv, address) )
+			return FALSE;
+	}
+	return TRUE;
+}
+
+int sieve_variables_modifiers_code_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+	ARRAY_TYPE(sieve_variables_modifier) *modifiers)
+{
+	unsigned int lprec, mdfs, i;
+
+	if ( !sieve_binary_read_byte(renv->sblock, address, &mdfs) ) {
+		sieve_runtime_trace_error(renv, "invalid modifier count");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	t_array_init(modifiers, mdfs);
+
+	lprec = (unsigned int)-1;
+	for ( i = 0; i < mdfs; i++ ) {
+		struct sieve_variables_modifier modf;
+
+		if ( !ext_variables_opr_modifier_read(renv, address, &modf) )
+			return SIEVE_EXEC_BIN_CORRUPT;
+		if ( modf.def != NULL ) {
+			if ( modf.def->precedence >= lprec ) {
+				sieve_runtime_trace_error(renv,
+					"unsorted modifier precedence");
+				return SIEVE_EXEC_BIN_CORRUPT;
+			}
+			lprec = modf.def->precedence;
+		}
+
+		array_append(modifiers, &modf, 1);
+	}
+
+	return SIEVE_EXEC_OK;
+}
+
+/*
+ * Modifier application
+ */
+
+int sieve_variables_modifiers_apply
+(const struct sieve_runtime_env *renv,
+	const struct sieve_extension *var_ext,
+	ARRAY_TYPE(sieve_variables_modifier) *modifiers,
+	string_t **value)
+{	
+	const struct ext_variables_config *config =
+		ext_variables_get_config(var_ext);
+	const struct sieve_variables_modifier *modfs;
+	unsigned int i, modf_count;
+
+	/* Hold value within limits */
+	if ( str_len(*value) > config->max_variable_size )
+		str_truncate(*value, config->max_variable_size);
+	
+	if ( !array_is_created(modifiers) )
+		return SIEVE_EXEC_OK;
+
+	modfs = array_get(modifiers, &modf_count);
+	if ( modf_count == 0 )
+		return SIEVE_EXEC_OK;
+
+	for ( i = 0; i < modf_count; i++ ) {
+		string_t *new_value;
+		const struct sieve_variables_modifier *modf = &modfs[i];
+
+		if ( modf->def != NULL && modf->def->modify != NULL ) {
+			if ( !modf->def->modify(*value, &new_value) )
+				return SIEVE_EXEC_FAILURE;
+
+			*value = new_value;
+			if ( *value == NULL )
+				return SIEVE_EXEC_FAILURE;
+
+			sieve_runtime_trace_here
+				(renv, SIEVE_TRLVL_COMMANDS,
+					"modify :%s \"%s\" => \"%s\"",
+					sieve_variables_modifier_name(modf),
+					str_sanitize(str_c(*value), 256),
+					str_sanitize(str_c(new_value), 256));
+
+			/* Hold value within limits */
+			if ( str_len(*value) > config->max_variable_size )
+				str_truncate(*value, config->max_variable_size);
+		}
+	}
+	return SIEVE_EXEC_OK;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-modifiers.h
@@ -0,0 +1,63 @@
+#ifndef EXT_VARIABLES_MODIFIERS_H
+#define EXT_VARIABLES_MODIFIERS_H
+
+#include "sieve-common.h"
+#include "sieve-runtime-trace.h"
+
+#include "ext-variables-common.h"
+
+#define ext_variables_namespace_name(nspc) \
+	(nspc)->object->def->name
+#define ext_variables_namespaces_equal(nspc1, nspc2) \
+	( (nspc1)->def == (nspc2)->def ))
+
+/*
+ * Modifier registry
+ */
+
+bool ext_variables_modifier_exists
+	(const struct sieve_extension *var_ext, struct sieve_validator *valdtr,
+		const char *identifier);
+const struct sieve_variables_modifier *ext_variables_modifier_create_instance
+	(const struct sieve_extension *var_ext, struct sieve_validator *valdtr,
+		struct sieve_command *cmd, const char *identifier);
+
+void ext_variables_register_core_modifiers
+	(const struct sieve_extension *var_ext,
+		struct ext_variables_validator_context *ctx);
+
+/*
+ * Modifier operand
+ */
+
+extern const struct sieve_operand_def modifier_operand;
+
+static inline void ext_variables_opr_modifier_emit
+(struct sieve_binary_block *sblock, const struct sieve_extension *ext,
+	const struct sieve_variables_modifier_def *modf_def)
+{
+	sieve_opr_object_emit(sblock, ext, &modf_def->obj_def);
+}
+
+static inline bool ext_variables_opr_modifier_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+	struct sieve_variables_modifier *modf)
+{
+	if ( !sieve_opr_object_read
+		(renv, &sieve_variables_modifier_operand_class, address, &modf->object) ) {
+		sieve_runtime_trace_error(renv, "invalid modifier operand");
+		return FALSE;
+	}
+
+	modf->def = (const struct sieve_variables_modifier_def *) modf->object.def;
+	return TRUE;
+}
+
+static inline bool ext_variables_opr_modifier_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	return sieve_opr_object_dump
+		(denv, &sieve_variables_modifier_operand_class, address, NULL);
+}
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-name.c
@@ -0,0 +1,110 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "array.h"
+
+#include "sieve-common.h"
+
+#include "ext-variables-common.h"
+#include "ext-variables-limits.h"
+#include "ext-variables-name.h"
+
+#include <ctype.h>
+
+bool sieve_variable_identifier_is_valid(const char *identifier)
+{
+	const char *p = identifier;
+	size_t plen = strlen(identifier);
+	const char *pend;
+
+	if ( plen == 0 || plen >= EXT_VARIABLES_MAX_VARIABLE_NAME_LEN )
+		return FALSE;
+
+	pend = PTR_OFFSET(identifier, plen);
+
+	if ( *p == '_' || i_isalpha(*p) ) {
+		p++;
+
+		while ( p < pend && (*p == '_' || i_isalnum(*p)) ) {
+			p++;
+		}
+	}
+
+	return ( p == pend );
+}
+
+int ext_variable_name_parse
+(ARRAY_TYPE(sieve_variable_name) *vname, const char **str, const char *strend)
+{
+	const char *p = *str;
+
+	array_clear(vname);
+
+	while ( p < strend ) {
+		struct sieve_variable_name *cur_element;
+		string_t *cur_ident;
+
+		/* Acquire current position in the array */
+
+		if ( array_count(vname) >= EXT_VARIABLES_MAX_NAMESPACE_ELEMENTS )
+			return -1;
+
+		cur_element = array_append_space(vname);
+		cur_ident = cur_element->identifier = t_str_new(32);
+
+		/* Parse element */
+
+		/* Identifier */
+		if ( *p == '_' || i_isalpha(*p) ) {
+			cur_element->num_variable = -1;
+			str_truncate(cur_ident, 0);
+			str_append_c(cur_ident, *p);
+			p++;
+
+			while ( p < strend && (*p == '_' || i_isalnum(*p)) ) {
+				if ( str_len(cur_ident) >= EXT_VARIABLES_MAX_VARIABLE_NAME_LEN )
+					return -1;
+				str_append_c(cur_ident, *p);
+				p++;
+			}
+
+		/* Num-variable */
+		} else if ( i_isdigit(*p) ) {
+			cur_element->num_variable = *p - '0';
+			p++;
+
+			while ( p < strend && i_isdigit(*p) ) {
+				cur_element->num_variable = cur_element->num_variable*10 + (*p - '0');
+				p++;
+			}
+
+			/* If a num-variable is first, no more elements can follow because no
+			 * namespace is specified.
+			 */
+			if ( array_count(vname) == 1 ) {
+				*str = p;
+				return 1;
+			}
+		} else {
+			*str = p;
+			return -1;
+		}
+
+		/* Check whether next name element is present */
+		if ( p < strend && *p == '.' ) {
+			p++;
+
+			/* It may not be empty */
+			if ( p >= strend )
+				return -1;
+		} else
+			break;
+	}
+
+	*str = p;
+	return array_count(vname);
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-name.h
@@ -0,0 +1,43 @@
+#ifndef EXT_VARIABLES_NAME_H
+#define EXT_VARIABLES_NAME_H
+
+/* Variable Substitution
+ * ---------------------
+ *
+ * The variable strings are preprocessed into an AST list consisting of variable
+ * substitutions and constant parts of the string. The variables to which
+ * the substitutions link are looked up and their index in their scope storage
+ * is what is added to the list and eventually emitted as byte code. So, in
+ * bytecode a variable string will look as a series of substrings interrupted by
+ * integer operands that refer to variables. During execution, the strings and
+ * the looked-up variables are concatenated to obtain the desired result. The
+ * the variable references are simple indexes into an array of variables, so
+ * looking these up during execution is a trivial process.
+ *
+ * However (RFC 5229):
+ *   Tests or actions in future extensions may need to access the
+ *   unexpanded version of the string argument and, e.g., do the expansion
+ *   after setting variables in its namespace.  The design of the
+ *   implementation should allow this.
+ *
+ * Various options exist to provide this feature. If the extension is entirely
+ * namespace-based there is actually not very much of a problem. The variable
+ * list can easily be extended with new argument-types that refer to a variable
+ * identifier instead of an index in the variable's storage.
+ */
+
+#include "lib.h"
+#include "array.h"
+
+#include "sieve-common.h"
+
+#include "ext-variables-common.h"
+
+/*
+ * Variable name parsing
+ */
+
+int ext_variable_name_parse
+	(ARRAY_TYPE(sieve_variable_name) *vname, const char **str, const char *strend);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-namespaces.c
@@ -0,0 +1,236 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+
+#include "ext-variables-common.h"
+#include "ext-variables-namespaces.h"
+
+#include <ctype.h>
+
+/*
+ * Namespace registry
+ */
+
+void sieve_variables_namespace_register
+(const struct sieve_extension *var_ext, struct sieve_validator *valdtr,
+	const struct sieve_extension *ext,
+	const struct sieve_variables_namespace_def *nspc_def)
+{
+	struct ext_variables_validator_context *ctx =
+		ext_variables_validator_context_get(var_ext, valdtr);
+
+	sieve_validator_object_registry_add(ctx->namespaces, ext, &nspc_def->obj_def);
+}
+
+bool ext_variables_namespace_exists
+(const struct sieve_extension *var_ext, struct sieve_validator *valdtr,
+	const char *identifier)
+{
+	struct ext_variables_validator_context *ctx =
+		ext_variables_validator_context_get(var_ext, valdtr);
+
+	return sieve_validator_object_registry_find
+		(ctx->namespaces, identifier, NULL);
+}
+
+const struct sieve_variables_namespace *ext_variables_namespace_create_instance
+(const struct sieve_extension *var_ext, struct sieve_validator *valdtr,
+	struct sieve_command *cmd, const char *identifier)
+{
+	struct ext_variables_validator_context *ctx =
+		ext_variables_validator_context_get(var_ext, valdtr);
+	struct sieve_object object;
+	struct sieve_variables_namespace *nspc;
+	pool_t pool;
+
+	if ( !sieve_validator_object_registry_find
+		(ctx->namespaces, identifier, &object) )
+		return NULL;
+
+	pool = sieve_command_pool(cmd);
+	nspc = p_new(pool, struct sieve_variables_namespace, 1);
+	nspc->object = object;
+	nspc->def = (const struct sieve_variables_namespace_def *) object.def;
+
+  return nspc;
+}
+
+/*
+ * Namespace variable argument
+ */
+
+struct arg_namespace_variable {
+	const struct sieve_variables_namespace *namespace;
+
+	void *data;
+};
+
+static bool arg_namespace_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+	struct sieve_command *context ATTR_UNUSED);
+
+const struct sieve_argument_def namespace_argument = {
+	.identifier = "@namespace",
+	.generate = arg_namespace_generate
+};
+
+bool ext_variables_namespace_argument_activate
+(const struct sieve_extension *this_ext, struct sieve_validator *valdtr,
+	struct sieve_ast_argument *arg, struct sieve_command *cmd,
+	ARRAY_TYPE(sieve_variable_name) *var_name, bool assignment)
+{
+	pool_t pool = sieve_command_pool(cmd);
+	struct sieve_ast *ast = arg->ast;
+	const struct sieve_variables_namespace *nspc;
+	struct arg_namespace_variable *var;
+	const struct sieve_variable_name *name_element = array_idx(var_name, 0);
+	void *var_data = NULL;
+
+	nspc = ext_variables_namespace_create_instance
+		(this_ext, valdtr, cmd, str_c(name_element->identifier));
+	if ( nspc == NULL ) {
+		sieve_argument_validate_error(valdtr, arg,
+			"referring to variable in unknown namespace '%s'",
+			str_c(name_element->identifier));
+		return FALSE;
+	}
+
+	if ( nspc->def != NULL && nspc->def->validate != NULL &&
+		!nspc->def->validate
+			(valdtr, nspc, arg, cmd, var_name, &var_data, assignment) ) {
+		return FALSE;
+	}
+
+	var = p_new(pool, struct arg_namespace_variable, 1);
+	var->namespace = nspc;
+	var->data = var_data;
+
+	arg->argument = sieve_argument_create(ast, &namespace_argument, this_ext, 0);
+	arg->argument->data = (void *) var;
+
+	return TRUE;
+}
+
+struct sieve_ast_argument *ext_variables_namespace_argument_create
+(const struct sieve_extension *this_ext,
+	struct sieve_validator *valdtr, struct sieve_ast_argument *parent_arg,
+	struct sieve_command *cmd, ARRAY_TYPE(sieve_variable_name) *var_name)
+{
+	struct sieve_ast *ast = parent_arg->ast;
+	struct sieve_ast_argument *new_arg;
+
+	new_arg = sieve_ast_argument_create(ast, sieve_ast_argument_line(parent_arg));
+	new_arg->type = SAAT_STRING;
+
+	if ( !ext_variables_namespace_argument_activate
+		(this_ext, valdtr, new_arg, cmd, var_name, FALSE) )
+		return NULL;
+
+	return new_arg;
+}
+
+static bool arg_namespace_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+	struct sieve_command *cmd)
+{
+	struct sieve_argument *argument = arg->argument;
+	struct arg_namespace_variable *var =
+		(struct arg_namespace_variable *) argument->data;
+	const struct sieve_variables_namespace *nspc = var->namespace;
+
+	if ( nspc->def != NULL && nspc->def->generate != NULL )
+		return nspc->def->generate(cgenv, nspc, arg, cmd, var->data);
+
+	return TRUE;
+}
+
+/*
+ * Namespace variable operands
+ */
+
+const struct sieve_operand_class sieve_variables_namespace_operand_class =
+	{ "variable-namespace" };
+
+static bool opr_namespace_variable_dump
+	(const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+		sieve_size_t *address);
+static int opr_namespace_variable_read
+	(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+		sieve_size_t *address, string_t **str_r);
+
+static const struct sieve_opr_string_interface namespace_variable_interface = {
+	opr_namespace_variable_dump,
+	opr_namespace_variable_read
+};
+
+const struct sieve_operand_def namespace_variable_operand = {
+	.name = "namespace",
+	.ext_def = &variables_extension,
+	.code = EXT_VARIABLES_OPERAND_NAMESPACE_VARIABLE,
+	.class = &string_class,
+	.interface = &namespace_variable_interface
+};
+
+void sieve_variables_opr_namespace_variable_emit
+(struct sieve_binary_block *sblock, const struct sieve_extension *var_ext,
+	const struct sieve_extension *ext,
+	const struct sieve_variables_namespace_def *nspc_def)
+{
+	i_assert( sieve_extension_is(var_ext, variables_extension) );
+	sieve_operand_emit(sblock, var_ext, &namespace_variable_operand);
+	sieve_opr_object_emit(sblock, ext, &nspc_def->obj_def);
+}
+
+static bool opr_namespace_variable_dump
+(const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+	sieve_size_t *address)
+{
+	struct sieve_variables_namespace nspc;
+	struct sieve_operand nsoprnd;
+
+	if ( !sieve_operand_read(denv->sblock, address, NULL, &nsoprnd) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_opr_object_read_data
+		(denv->sblock, &nsoprnd, &sieve_variables_namespace_operand_class, address,
+			&nspc.object) ) {
+		return FALSE;
+  }
+
+	nspc.def = (const struct sieve_variables_namespace_def *) nspc.object.def;
+
+	if ( nspc.def == NULL || nspc.def->dump_variable == NULL )
+		return FALSE;
+
+	return nspc.def->dump_variable(denv, &nspc, oprnd, address);
+}
+
+static int opr_namespace_variable_read
+(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+	sieve_size_t *address, string_t **str_r)
+{
+	struct sieve_variables_namespace nspc;
+
+	if ( !sieve_opr_object_read
+		(renv, &sieve_variables_namespace_operand_class, address, &nspc.object) ) {
+		sieve_runtime_trace_operand_error(renv, oprnd,
+			"variable namespace operand corrupt: failed to read");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	nspc.def = (const struct sieve_variables_namespace_def *) nspc.object.def;
+
+	if ( nspc.def == NULL || nspc.def->read_variable == NULL )
+		return SIEVE_EXEC_FAILURE;
+
+	return nspc.def->read_variable(renv, &nspc, oprnd, address, str_r);
+}
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-namespaces.h
@@ -0,0 +1,43 @@
+#ifndef EXT_VARIABLES_NAMESPACES_H
+#define EXT_VARIABLES_NAMESPACES_H
+
+#include "sieve-common.h"
+
+#include "ext-variables-common.h"
+#include "sieve-ext-variables.h"
+
+/*
+ * Namespace registry
+ */
+
+bool ext_variables_namespace_exists
+	(const struct sieve_extension *var_ext, struct sieve_validator *valdtr,
+		const char *identifier);
+const struct sieve_variables_namespace *ext_variables_namespace_create_instance
+	(const struct sieve_extension *var_ext, struct sieve_validator *valdtr,
+		struct sieve_command *cmd, const char *identifier);
+
+void ext_variables_register_core_namespaces
+	(const struct sieve_extension *var_ext,
+		struct ext_variables_validator_context *ctx);
+
+/*
+ * Namespace argument
+ */
+
+struct sieve_ast_argument *ext_variables_namespace_argument_create
+	(const struct sieve_extension *this_ext,
+		struct sieve_validator *valdtr, struct sieve_ast_argument *parent_arg,
+		struct sieve_command *cmd, ARRAY_TYPE(sieve_variable_name) *var_name);
+bool ext_variables_namespace_argument_activate
+	(const struct sieve_extension *this_ext, struct sieve_validator *valdtr,
+		struct sieve_ast_argument *arg, struct sieve_command *cmd,
+		ARRAY_TYPE(sieve_variable_name) *var_name, bool assignment);
+
+/*
+ * Namespace operand
+ */
+
+extern const struct sieve_operand_def namespace_variable_operand;
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-operands.c
@@ -0,0 +1,279 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "hash.h"
+#include "str.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-ast.h"
+#include "sieve-binary.h"
+#include "sieve-code.h"
+#include "sieve-match-types.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-dump.h"
+#include "sieve-interpreter.h"
+
+#include "ext-variables-common.h"
+#include "ext-variables-limits.h"
+#include "ext-variables-name.h"
+#include "ext-variables-dump.h"
+#include "ext-variables-operands.h"
+
+/*
+ * Variable operand
+ */
+
+static bool opr_variable_dump
+	(const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+		sieve_size_t *address);
+static int opr_variable_read_value
+	(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+		sieve_size_t *address, string_t **str_r);
+
+const struct sieve_opr_string_interface variable_interface = {
+	opr_variable_dump,
+	opr_variable_read_value
+};
+
+const struct sieve_operand_def variable_operand = {
+	.name = "variable",
+	.ext_def = &variables_extension,
+	.code = EXT_VARIABLES_OPERAND_VARIABLE,
+	.class = &string_class,
+	.interface = &variable_interface
+};
+
+void sieve_variables_opr_variable_emit
+(struct sieve_binary_block *sblock, const struct sieve_extension *var_ext,
+	struct sieve_variable *var)
+{
+	i_assert( sieve_extension_is(var_ext, variables_extension) );
+
+	if ( var->ext == NULL ) {
+		/* Default variable storage */
+		(void) sieve_operand_emit(sblock, var_ext, &variable_operand);
+		(void) sieve_binary_emit_byte(sblock, 0); /* Default */
+		(void) sieve_binary_emit_unsigned(sblock, var->index);
+		return;
+	}
+
+	(void) sieve_operand_emit(sblock, var_ext, &variable_operand);
+	(void) sieve_binary_emit_extension(sblock, var->ext, 1); /* Extension */
+	(void) sieve_binary_emit_unsigned(sblock, var->index);
+}
+
+static bool opr_variable_dump
+(const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+	sieve_size_t *address)
+{
+	const struct sieve_extension *this_ext = oprnd->ext;
+	unsigned int index = 0;
+	const struct sieve_extension *ext;
+	unsigned int code = 1; /* Initially set to offset value */
+	const char *identifier;
+
+	if ( !sieve_binary_read_extension(denv->sblock, address, &code, &ext) )
+		return FALSE;
+
+	if ( !sieve_binary_read_unsigned(denv->sblock, address, &index) )
+		return FALSE;
+
+	identifier = ext_variables_dump_get_identifier(this_ext, denv, ext, index);
+	identifier = identifier == NULL ? "??" : identifier;
+
+	if ( oprnd->field_name != NULL )
+		sieve_code_dumpf(denv, "%s: VAR[%s] ${%s}",
+			oprnd->field_name, sieve_ext_variables_get_varid(ext, index), identifier);
+	else
+		sieve_code_dumpf(denv, "VAR[%s] ${%s}",
+			sieve_ext_variables_get_varid(ext, index), identifier);
+
+	return TRUE;
+}
+
+static int opr_variable_read_value
+(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+	sieve_size_t *address, string_t **str_r)
+{
+	const struct sieve_extension *this_ext = oprnd->ext;
+	const struct sieve_extension *ext;
+	unsigned int code = 1; /* Initially set to offset value */
+	struct sieve_variable_storage *storage;
+	unsigned int index = 0;
+
+	if ( !sieve_binary_read_extension(renv->sblock, address, &code, &ext) ) {
+		sieve_runtime_trace_operand_error(renv, oprnd,
+			"variable operand corrupt: invalid extension byte");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	storage = sieve_ext_variables_runtime_get_storage
+		(this_ext, renv, ext);
+	if ( storage == NULL ) {
+		sieve_runtime_trace_operand_error(renv, oprnd,
+			"variable operand corrupt: extension has no storage");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	if ( sieve_binary_read_unsigned(renv->sblock, address, &index) ) {
+		/* Parameter str can be NULL if we are requested to only skip and not
+		 * actually read the argument.
+		 */
+		if ( str_r != NULL ) {
+			if ( !sieve_variable_get(storage, index, str_r) )
+				return SIEVE_EXEC_FAILURE;
+
+			if ( *str_r == NULL ) *str_r = t_str_new(0);
+		}
+
+		return SIEVE_EXEC_OK;
+	}
+
+	sieve_runtime_trace_operand_error(renv, oprnd,
+		"variable operand corrupt: invalid variable index");
+	return SIEVE_EXEC_BIN_CORRUPT;
+}
+
+int sieve_variable_operand_read_data
+(const struct sieve_runtime_env *renv, struct sieve_operand *oprnd,
+	sieve_size_t *address, const char *field_name,
+	struct sieve_variable_storage **storage_r, unsigned int *var_index_r)
+{
+	const struct sieve_extension *ext;
+	unsigned int code = 1; /* Initially set to offset value */
+	unsigned int idx = 0;
+
+	oprnd->field_name = field_name;
+
+	if ( !sieve_operand_is_variable(oprnd) ) {
+		sieve_runtime_trace_operand_error(renv, oprnd,
+			"expected variable operand but found %s",	sieve_operand_name(oprnd));
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	if ( !sieve_binary_read_extension(renv->sblock, address, &code, &ext) ) {
+		sieve_runtime_trace_operand_error(renv, oprnd,
+			"variable operand corrupt: invalid extension byte");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	*storage_r = sieve_ext_variables_runtime_get_storage
+		(oprnd->ext, renv, ext);
+	if ( *storage_r == NULL )	{
+		sieve_runtime_trace_operand_error(renv, oprnd,
+			"variable operand corrupt: extension has no storage");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	if ( !sieve_binary_read_unsigned(renv->sblock, address, &idx) ) {
+		sieve_runtime_trace_operand_error(renv, oprnd,
+			"variable operand corrupt: invalid variable index");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	*var_index_r = idx;
+	return SIEVE_EXEC_OK;
+}
+
+int sieve_variable_operand_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+	const char *field_name, struct sieve_variable_storage **storage_r,
+	unsigned int *var_index_r)
+{
+	struct sieve_operand operand;
+	int ret;
+
+	if ( (ret=sieve_operand_runtime_read(renv, address, field_name, &operand))
+		<= 0)
+		return ret;
+
+	return sieve_variable_operand_read_data
+		(renv, &operand, address, field_name, storage_r, var_index_r);
+}
+
+/*
+ * Match value operand
+ */
+
+static bool opr_match_value_dump
+	(const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+		sieve_size_t *address);
+static int opr_match_value_read
+	(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+		sieve_size_t *address, string_t **str_r);
+
+const struct sieve_opr_string_interface match_value_interface = {
+	opr_match_value_dump,
+	opr_match_value_read
+};
+
+const struct sieve_operand_def match_value_operand = {
+	.name = "match-value",
+	.ext_def = &variables_extension,
+	.code = EXT_VARIABLES_OPERAND_MATCH_VALUE,
+	.class = &string_class,
+	.interface = &match_value_interface
+};
+
+void sieve_variables_opr_match_value_emit
+(struct sieve_binary_block *sblock, const struct sieve_extension *var_ext,
+	unsigned int index)
+{
+	i_assert( sieve_extension_is(var_ext, variables_extension) );
+	(void) sieve_operand_emit(sblock, var_ext, &match_value_operand);
+	(void) sieve_binary_emit_unsigned(sblock, index);
+}
+
+static bool opr_match_value_dump
+(const struct sieve_dumptime_env *denv,	const struct sieve_operand *oprnd,
+	sieve_size_t *address)
+{
+	unsigned int index = 0;
+
+	if (sieve_binary_read_unsigned(denv->sblock, address, &index) ) {
+		if ( oprnd->field_name != NULL )
+			sieve_code_dumpf
+				(denv, "%s: MATCHVAL %lu", oprnd->field_name, (unsigned long) index);
+		else
+			sieve_code_dumpf(denv, "MATCHVAL %lu", (unsigned long) index);
+
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static int opr_match_value_read
+(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+	sieve_size_t *address, string_t **str_r)
+{
+	const struct sieve_extension *this_ext = oprnd->ext;
+	const struct ext_variables_config *config =
+		ext_variables_get_config(this_ext);
+	unsigned int index = 0;
+
+	if ( sieve_binary_read_unsigned(renv->sblock, address, &index) ) {
+		/* Parameter str can be NULL if we are requested to only skip and not
+		 * actually read the argument.
+		 	*/
+		if ( str_r != NULL ) {
+			sieve_match_values_get(renv, index, str_r);
+
+			if ( *str_r == NULL )
+				*str_r = t_str_new(0);
+			else if ( str_len(*str_r) > config->max_variable_size )
+				str_truncate(*str_r, config->max_variable_size);
+		}
+
+		return SIEVE_EXEC_OK;
+	}
+
+	sieve_runtime_trace_operand_error(renv, oprnd,
+		"match value operand corrupt: invalid index data");
+	return SIEVE_EXEC_BIN_CORRUPT;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-operands.h
@@ -0,0 +1,37 @@
+#ifndef EXT_VARIABLES_OPERANDS_H
+#define EXT_VARIABLES_OPERANDS_H
+
+#include "lib.h"
+#include "hash.h"
+#include "str.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "ext-variables-common.h"
+
+/*
+ * Variable operand
+ */
+
+extern const struct sieve_operand_def variable_operand;
+
+bool ext_variables_opr_variable_read
+	(const struct sieve_runtime_env *renv, sieve_size_t *address,
+		struct sieve_variable_storage **storage, unsigned int *var_index);
+
+/*
+ * Match value operand
+ */
+
+extern const struct sieve_operand_def match_value_operand;
+
+/*
+ * Variable string operand
+ */
+
+void ext_variables_opr_variable_string_emit
+	(struct sieve_binary *sbin, unsigned int elements);
+
+
+#endif
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/variables/ext-variables.c
@@ -0,0 +1,84 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension variables
+ * -------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5229
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "unichar.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+
+#include "sieve-validator.h"
+
+#include "ext-variables-common.h"
+#include "ext-variables-arguments.h"
+#include "ext-variables-operands.h"
+#include "ext-variables-namespaces.h"
+#include "ext-variables-modifiers.h"
+#include "ext-variables-dump.h"
+
+/*
+ * Operations
+ */
+
+const struct sieve_operation_def *ext_variables_operations[] = {
+	&cmd_set_operation,
+	&tst_string_operation
+};
+
+/*
+ * Operands
+ */
+
+const struct sieve_operand_def *ext_variables_operands[] = {
+	&variable_operand,
+	&match_value_operand,
+	&namespace_variable_operand,
+	&modifier_operand
+};
+
+/*
+ * Extension
+ */
+
+static bool ext_variables_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *validator);
+
+const struct sieve_extension_def variables_extension = {
+	.name = "variables",
+	.load = ext_variables_load,
+	.unload = ext_variables_unload,
+	.validator_load = ext_variables_validator_load,
+	.generator_load = ext_variables_generator_load,
+	.interpreter_load = ext_variables_interpreter_load,
+	.code_dump = ext_variables_code_dump,
+	SIEVE_EXT_DEFINE_OPERATIONS(ext_variables_operations),
+	SIEVE_EXT_DEFINE_OPERANDS(ext_variables_operands)
+};
+
+static bool ext_variables_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *validator)
+{
+	sieve_validator_argument_override
+		(validator, SAT_VAR_STRING, ext, &variable_string_argument);
+
+	sieve_validator_register_command(validator, ext, &cmd_set);
+	sieve_validator_register_command(validator, ext, &tst_string);
+
+	ext_variables_validator_initialize(ext, validator);
+
+	return TRUE;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/variables/sieve-ext-variables.h
@@ -0,0 +1,357 @@
+#ifndef SIEVE_EXT_VARIABLES_H
+#define SIEVE_EXT_VARIABLES_H
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-objects.h"
+#include "sieve-code.h"
+
+/* Public interface for other extensions to use
+ */
+
+/*
+ * Limits
+ */
+
+unsigned int
+sieve_variables_get_max_scope_size(const struct sieve_extension *var_ext);
+
+/*
+ * Variable extension
+ */
+
+/* FIXME: this is not suitable for future plugin support */
+
+extern const struct sieve_extension_def variables_extension;
+
+static inline const struct sieve_extension *sieve_ext_variables_get_extension
+(struct sieve_instance *svinst)
+{
+	return sieve_extension_register(svinst, &variables_extension, FALSE);
+}
+
+/*
+ * Variable name
+ */
+
+struct sieve_variable_name {
+	string_t *identifier;
+	int num_variable;
+};
+
+ARRAY_DEFINE_TYPE(sieve_variable_name, struct sieve_variable_name);
+
+bool sieve_variable_identifier_is_valid(const char *identifier);
+
+/*
+ * Variable scope
+ */
+
+struct sieve_variable {
+	const char *identifier;
+	unsigned int index;
+
+	const struct sieve_extension *ext;
+	void *context;
+};
+
+struct sieve_variable_scope;
+
+struct sieve_variable_scope *sieve_variable_scope_create
+	(struct sieve_instance *svinst, const struct sieve_extension *var_ext,
+		const struct sieve_extension *ext);
+void sieve_variable_scope_ref
+	(struct sieve_variable_scope *scope);
+void sieve_variable_scope_unref
+	(struct sieve_variable_scope **scope);
+pool_t sieve_variable_scope_pool
+	(struct sieve_variable_scope *scope);
+
+struct sieve_variable *sieve_variable_scope_declare
+	(struct sieve_variable_scope *scope, const char *identifier);
+struct sieve_variable *sieve_variable_scope_import
+	(struct sieve_variable_scope *scope, struct sieve_variable *var);
+struct sieve_variable *sieve_variable_scope_get_variable
+	(struct sieve_variable_scope *scope, const char *identifier);
+struct sieve_variable *sieve_variable_scope_get_indexed
+	(struct sieve_variable_scope *scope, unsigned int index);
+
+/* Binary */
+
+struct sieve_variable_scope_binary *sieve_variable_scope_binary_create
+	(struct sieve_variable_scope *scope);
+
+void sieve_variable_scope_binary_ref
+	(struct sieve_variable_scope_binary *scpbin);
+void sieve_variable_scope_binary_unref
+	(struct sieve_variable_scope_binary **scpbin);
+
+struct sieve_variable_scope *sieve_variable_scope_binary_dump
+	(struct sieve_instance *svinst,
+		const struct sieve_extension *var_ext,
+		const struct sieve_extension *ext,
+		const struct sieve_dumptime_env *denv, sieve_size_t *address);
+struct sieve_variable_scope_binary *sieve_variable_scope_binary_read
+	(struct sieve_instance *svinst,
+		const struct sieve_extension *var_ext,
+		const struct sieve_extension *ext,
+		struct sieve_binary_block *sblock, sieve_size_t *address);
+
+struct sieve_variable_scope *sieve_variable_scope_binary_get
+	(struct sieve_variable_scope_binary *scpbin);
+unsigned int sieve_variable_scope_binary_get_size
+	(struct sieve_variable_scope_binary *scpbin);
+
+/*
+ * Variable namespaces
+ */
+
+struct sieve_variables_namespace;
+
+struct sieve_variables_namespace_def {
+	struct sieve_object_def obj_def;
+
+	bool (*validate)
+		(struct sieve_validator *valdtr,
+			const struct sieve_variables_namespace *nspc,
+			struct sieve_ast_argument *arg, struct sieve_command *cmd,
+			ARRAY_TYPE(sieve_variable_name) *var_name, void **var_data,
+			bool assignment);
+	bool (*generate)
+		(const struct sieve_codegen_env *cgenv,
+			const struct sieve_variables_namespace *nspc,
+			struct sieve_ast_argument *arg, struct sieve_command *cmd,
+			void *var_data);
+
+	bool (*dump_variable)
+		(const struct sieve_dumptime_env *denv,
+			const struct sieve_variables_namespace *nspc,
+			const struct sieve_operand *oprnd, sieve_size_t *address);
+	int (*read_variable)
+		(const struct sieve_runtime_env *renv,
+			const struct sieve_variables_namespace *nspc,
+			const struct sieve_operand *oprnd, sieve_size_t *address, string_t **str);
+};
+
+#define SIEVE_VARIABLES_DEFINE_NAMESPACE(OP) SIEVE_EXT_DEFINE_OBJECT(OP)
+#define SIEVE_VARIABLES_DEFINE_NAMESPACES(OPS) SIEVE_EXT_DEFINE_OBJECTS(OPS)
+
+struct sieve_variables_namespace {
+	struct sieve_object object;
+
+	const struct sieve_variables_namespace_def *def;
+};
+
+void sieve_variables_namespace_register
+(const struct sieve_extension *var_ext, struct sieve_validator *valdtr,
+	const struct sieve_extension *ext,
+	const struct sieve_variables_namespace_def *nspc_def);
+
+extern const struct sieve_operand_class sieve_variables_namespace_operand_class;
+
+void sieve_variables_opr_namespace_variable_emit
+	(struct sieve_binary_block *sblock, const struct sieve_extension *var_ext,
+    const struct sieve_extension *ext,
+    const struct sieve_variables_namespace_def *nspc_def);
+
+/* Iteration over all declared variables */
+
+struct sieve_variable_scope_iter;
+
+struct sieve_variable_scope_iter *sieve_variable_scope_iterate_init
+	(struct sieve_variable_scope *scope);
+bool sieve_variable_scope_iterate
+	(struct sieve_variable_scope_iter *iter, struct sieve_variable **var_r);
+void sieve_variable_scope_iterate_deinit
+	(struct sieve_variable_scope_iter **iter);
+
+/* Statistics */
+
+unsigned int sieve_variable_scope_declarations
+	(struct sieve_variable_scope *scope);
+unsigned int sieve_variable_scope_size
+	(struct sieve_variable_scope *scope);
+
+/* Get all native variables */
+
+struct sieve_variable * const *sieve_variable_scope_get_variables
+	(struct sieve_variable_scope *scope, unsigned int *size_r);
+
+/*
+ * Variable storage
+ */
+
+struct sieve_variable_storage;
+
+struct sieve_variable_storage *sieve_variable_storage_create
+	(const struct sieve_extension *var_ext, pool_t pool,
+		struct sieve_variable_scope_binary *scpbin);
+bool sieve_variable_get
+	(struct sieve_variable_storage *storage, unsigned int index,
+		string_t **value);
+bool sieve_variable_get_modifiable
+	(struct sieve_variable_storage *storage, unsigned int index,
+		string_t **value);
+bool sieve_variable_assign
+	(struct sieve_variable_storage *storage, unsigned int index,
+		const string_t *value);
+bool sieve_variable_assign_cstr
+	(struct sieve_variable_storage *storage, unsigned int index,
+		const char *value);
+bool sieve_variable_get_identifier
+	(struct sieve_variable_storage *storage, unsigned int index,
+		const char **identifier);
+const char *sieve_variable_get_varid
+	(struct sieve_variable_storage *storage, unsigned int index);
+
+/*
+ * Variables access
+ */
+
+bool sieve_ext_variables_is_active
+	(const struct sieve_extension *var_ext, struct sieve_validator *valdtr);
+
+struct sieve_variable_scope *sieve_ext_variables_get_local_scope
+	(const struct sieve_extension *var_ext, struct sieve_validator *valdtr);
+
+/* Runtime */
+
+static inline const char *sieve_ext_variables_get_varid
+(const struct sieve_extension *ext, unsigned int index)
+{
+	if ( ext == NULL )
+		return t_strdup_printf("%ld", (long) index);
+
+	return t_strdup_printf("%s:%ld", sieve_extension_name(ext), (long) index);
+}
+
+struct sieve_variable_storage *sieve_ext_variables_runtime_get_storage
+	(const struct sieve_extension *var_ext, const struct sieve_runtime_env *renv,
+		const struct sieve_extension *ext);
+void sieve_ext_variables_runtime_set_storage
+	(const struct sieve_extension *var_ext, const struct sieve_runtime_env *renv,
+		const struct sieve_extension *ext, struct sieve_variable_storage *storage);
+
+const char *sieve_ext_variables_runtime_get_identifier
+(const struct sieve_extension *var_ext, const struct sieve_runtime_env *renv,
+	const struct sieve_extension *ext, unsigned int index);
+
+/*
+ * Variable arguments
+ */
+
+bool sieve_variable_argument_activate
+	(const struct sieve_extension *var_ext,
+		const struct sieve_extension *this_ext,
+		struct sieve_validator *valdtr, struct sieve_command *cmd,
+		struct sieve_ast_argument *arg, bool assignment);
+
+/*
+ * Variable operands
+ */
+
+extern const struct sieve_operand_def variable_operand;
+
+void sieve_variables_opr_variable_emit
+	(struct sieve_binary_block *sblock, const struct sieve_extension *var_ext,
+		struct sieve_variable *var);
+void sieve_variables_opr_match_value_emit
+	(struct sieve_binary_block *sblock, const struct sieve_extension *var_ext,
+		unsigned int index);
+
+int sieve_variable_operand_read_data
+	(const struct sieve_runtime_env *renv, struct sieve_operand *operand,
+		sieve_size_t *address, const char *field_name,
+		struct sieve_variable_storage **storage_r, unsigned int *var_index_r);
+int sieve_variable_operand_read
+	(const struct sieve_runtime_env *renv, sieve_size_t *address,
+		const char *field_name, struct sieve_variable_storage **storage_r,
+		unsigned int *var_index_r);
+
+static inline bool sieve_operand_is_variable
+(const struct sieve_operand *operand)
+{
+	return ( operand != NULL && operand->def != NULL &&
+		operand->def == &variable_operand );
+}
+
+/*
+ * Modifiers
+ */
+
+/* Definition */
+
+struct sieve_variables_modifier_def {
+	struct sieve_object_def obj_def;
+
+	unsigned int precedence;
+
+	bool (*modify)(string_t *in, string_t **result);
+};
+
+struct sieve_variables_modifier {
+	struct sieve_object object;
+
+	const struct sieve_variables_modifier_def *def;
+};
+
+#define SIEVE_VARIABLES_DEFINE_MODIFIER(OP) SIEVE_EXT_DEFINE_OBJECT(OP)
+#define SIEVE_VARIABLES_DEFINE_MODIFIERS(OPS) SIEVE_EXT_DEFINE_OBJECTS(OPS)
+
+#define sieve_variables_modifier_name(smodf) \
+	( (smodf)->object.def->identifier )
+
+ARRAY_DEFINE_TYPE(sieve_variables_modifier,
+	struct sieve_variables_modifier);
+
+/* Registry */
+
+void sieve_variables_modifier_register
+	(const struct sieve_extension *var_ext, struct sieve_validator *valdtr,
+		const struct sieve_extension *ext,
+		const struct sieve_variables_modifier_def *smodf);
+
+/* Tagged argument */
+
+void sieve_variables_modifiers_link_tag
+	(struct sieve_validator *valdtr, const struct sieve_extension *var_ext,
+		struct sieve_command_registration *cmd_reg);
+
+bool sieve_variables_modifiers_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd,
+		ARRAY_TYPE(sieve_variables_modifier) *modifiers);
+
+bool sieve_variables_modifiers_generate
+	(const struct sieve_codegen_env *cgenv,
+		ARRAY_TYPE(sieve_variables_modifier) *modifiers);
+
+/* Coding */
+
+extern const struct sieve_operand_class
+	sieve_variables_modifier_operand_class;
+
+bool sieve_variables_modifiers_code_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+int sieve_variables_modifiers_code_read
+	(const struct sieve_runtime_env *renv, sieve_size_t *address,
+		ARRAY_TYPE(sieve_variables_modifier) *modifiers);
+
+/* Application */
+
+int sieve_variables_modifiers_apply
+(const struct sieve_runtime_env *renv,
+	const struct sieve_extension *var_ext,
+	ARRAY_TYPE(sieve_variables_modifier) *modifiers,
+	string_t **value);
+
+/*
+ * Code dumping
+ */
+
+void sieve_ext_variables_dump_set_scope
+(const struct sieve_extension *var_ext, const struct sieve_dumptime_env *denv,
+	const struct sieve_extension *ext, struct sieve_variable_scope *scope);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/variables/tst-string.c
@@ -0,0 +1,271 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+#include "ext-variables-common.h"
+
+/*
+ * String test
+ *
+ * Syntax:
+ *   string [COMPARATOR] [MATCH-TYPE]
+ *     <source: string-list> <key-list: string-list>
+ */
+
+static bool tst_string_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool tst_string_validate
+	(struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_string_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def tst_string = {
+	.identifier = "string",
+	.type = SCT_TEST,
+	.positional_args = 2,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = tst_string_registered,
+	.validate = tst_string_validate,
+	.generate = tst_string_generate
+};
+
+/*
+ * String operation
+ */
+
+static bool tst_string_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_string_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def tst_string_operation = {
+	.mnemonic = "STRING",
+	.ext_def = &variables_extension,
+	.code = EXT_VARIABLES_OPERATION_STRING,
+	.dump = tst_string_operation_dump,
+	.execute = tst_string_operation_execute
+};
+
+/*
+ * Optional arguments
+ */
+
+enum tst_string_optional {
+	OPT_END,
+	OPT_COMPARATOR,
+	OPT_MATCH_TYPE
+};
+
+/*
+ * Test registration
+ */
+
+static bool tst_string_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext ATTR_UNUSED,
+	struct sieve_command_registration *cmd_reg)
+{
+	/* The order of these is not significant */
+	sieve_comparators_link_tag(valdtr, cmd_reg, OPT_COMPARATOR);
+	sieve_match_types_link_tags(valdtr, cmd_reg, OPT_MATCH_TYPE);
+
+	return TRUE;
+}
+
+/*
+ * Test validation
+ */
+
+static bool tst_string_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+	struct sieve_ast_argument *arg = tst->first_positional;
+	const struct sieve_match_type mcht_default =
+		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+	const struct sieve_comparator cmp_default =
+		SIEVE_COMPARATOR_DEFAULT(i_octet_comparator);
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "source", 1, SAAT_STRING_LIST) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+		return FALSE;
+
+	arg = sieve_ast_argument_next(arg);
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "key list", 2, SAAT_STRING_LIST) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+		return FALSE;
+
+	/* Validate the key argument to a specified match type */
+	return sieve_match_type_validate
+		(valdtr, tst, arg, &mcht_default, &cmp_default);
+}
+
+/*
+ * Test generation
+ */
+
+static bool tst_string_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	sieve_operation_emit(cgenv->sblock, cmd->ext, &tst_string_operation);
+
+ 	/* Generate arguments */
+	if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
+		return FALSE;
+
+	return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_string_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "STRING-TEST");
+	sieve_code_descend(denv);
+
+	/* Optional operands */
+	if ( sieve_match_opr_optional_dump(denv, address, NULL) != 0 )
+		return FALSE;
+
+	return
+		sieve_opr_stringlist_dump(denv, address, "source") &&
+		sieve_opr_stringlist_dump(denv, address, "key list");
+}
+
+/*
+ * Code execution
+ */
+
+static int tst_string_stringlist_next_item
+	(struct sieve_stringlist *_strlist, string_t **str_r);
+static void tst_string_stringlist_reset
+	(struct sieve_stringlist *_strlist);
+static int tst_string_stringlist_get_length
+	(struct sieve_stringlist *_strlist);
+
+struct tst_string_stringlist {
+	struct sieve_stringlist strlist;
+
+	struct sieve_stringlist *value_list;
+};
+
+static struct sieve_stringlist *tst_string_stringlist_create
+(const struct sieve_runtime_env *renv, struct sieve_stringlist *value_list)
+{
+	struct tst_string_stringlist *strlist;
+
+	strlist = t_new(struct tst_string_stringlist, 1);
+	strlist->strlist.runenv = renv;
+	strlist->strlist.exec_status = SIEVE_EXEC_OK;
+	strlist->strlist.next_item = tst_string_stringlist_next_item;
+	strlist->strlist.reset = tst_string_stringlist_reset;
+	strlist->strlist.get_length = tst_string_stringlist_get_length;
+	strlist->value_list = value_list;
+
+	return &strlist->strlist;
+}
+
+static int tst_string_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+	struct tst_string_stringlist *strlist =
+		(struct tst_string_stringlist *)_strlist;
+
+	return sieve_stringlist_next_item(strlist->value_list, str_r);
+}
+
+static void tst_string_stringlist_reset
+(struct sieve_stringlist *_strlist)
+{
+	struct tst_string_stringlist *strlist =
+		(struct tst_string_stringlist *)_strlist;
+
+	sieve_stringlist_reset(strlist->value_list);
+}
+
+static int tst_string_stringlist_get_length
+(struct sieve_stringlist *_strlist)
+{
+	struct tst_string_stringlist *strlist =
+		(struct tst_string_stringlist *)_strlist;
+	string_t *item;
+	int length = 0;
+	int ret;
+
+	while ( (ret=sieve_stringlist_next_item(strlist->value_list, &item)) > 0 ) {
+		if ( str_len(item) > 0 )
+			length++;
+	}
+
+	return ( ret < 0 ? -1 : length );
+}
+
+static int tst_string_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	struct sieve_match_type mcht =
+		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+	struct sieve_comparator cmp =
+		SIEVE_COMPARATOR_DEFAULT(i_octet_comparator);
+	struct sieve_stringlist *source, *value_list, *key_list;
+	int match, ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Handle match-type and comparator operands */
+	if ( sieve_match_opr_optional_read
+		(renv, address, NULL, &ret, &cmp, &mcht) < 0 )
+		return ret;
+
+	/* Read source */
+	if ( (ret=sieve_opr_stringlist_read(renv, address, "source", &source)) <= 0 )
+		return ret;
+
+	/* Read key-list */
+	if ( (ret=sieve_opr_stringlist_read(renv, address, "key-list", &key_list))
+		<= 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "string test");
+
+	/* Create wrapper string list wich does not count empty string items */
+	value_list = tst_string_stringlist_create(renv, source);
+
+	/* Perform match */
+	if ( (match=sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret)) < 0 )
+		return ret;
+
+	/* Set test result for subsequent conditional jump */
+	sieve_interpreter_set_test_result(renv->interp, match > 0);
+	return SIEVE_EXEC_OK;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/Makefile.am
@@ -0,0 +1,2 @@
+SUBDIRS = debug environment report
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/Makefile.am
@@ -0,0 +1,15 @@
+noinst_LTLIBRARIES = libsieve_ext_debug.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../../.. \
+	$(LIBDOVECOT_INCLUDE)
+
+commands = \
+	cmd-debug-log.c
+
+libsieve_ext_debug_la_SOURCES = \
+	$(commands) \
+	ext-debug.c
+
+noinst_HEADERS = \
+	ext-debug-common.h
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/cmd-debug-log.c
@@ -0,0 +1,130 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-debug-common.h"
+
+/*
+ * Debug_log command
+ *
+ * Syntax
+ *   debug_log <message: string>
+ */
+
+static bool cmd_debug_log_validate
+	(struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool cmd_debug_log_generate
+	(const struct sieve_codegen_env *cgenv,	struct sieve_command *ctx);
+
+const struct sieve_command_def debug_log_command = {
+	.identifier = "debug_log",
+	.type = SCT_COMMAND,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = cmd_debug_log_validate,
+	.generate = cmd_debug_log_generate
+};
+
+/*
+ * Body operation
+ */
+
+static bool cmd_debug_log_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_debug_log_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def debug_log_operation = {
+	.mnemonic = "DEBUG_LOG",
+	.ext_def = &vnd_debug_extension,
+	.dump = cmd_debug_log_operation_dump,
+	.execute = cmd_debug_log_operation_execute
+};
+
+/*
+ * Validation
+ */
+
+static bool cmd_debug_log_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+	struct sieve_ast_argument *arg = tst->first_positional;
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "message", 1, SAAT_STRING) ) {
+		return FALSE;
+	}
+
+	return sieve_validator_argument_activate(valdtr, tst, arg, FALSE);
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_debug_log_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	(void)sieve_operation_emit(cgenv->sblock, cmd->ext, &debug_log_operation);
+
+	/* Generate arguments */
+	return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_debug_log_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "DEBUG_LOG");
+	sieve_code_descend(denv);
+
+	return sieve_opr_string_dump(denv, address, "key list");
+}
+
+/*
+ * Interpretation
+ */
+
+static int cmd_debug_log_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	string_t *message;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Read message */
+
+	if ( (ret=sieve_opr_string_read(renv, address, "message", &message)) <= 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS, "debug_log \"%s\"",
+		str_sanitize(str_c(message), 80));
+
+	sieve_runtime_log(renv, NULL, "DEBUG: %s", str_c(message));
+
+	return SIEVE_EXEC_OK;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/ext-debug-common.h
@@ -0,0 +1,22 @@
+#ifndef EXT_DEBUG_COMMON_H
+#define EXT_DEBUG_COMMON_H
+
+/*
+ * Extensions
+ */
+
+extern const struct sieve_extension_def vnd_debug_extension;
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def debug_log_command;
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def debug_log_operation;
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/ext-debug.c
@@ -0,0 +1,70 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension debug
+ * ---------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: vendor-defined; spec-bosch-sieve-debug
+ * Implementation: full
+ * Status: experimental
+ *
+ */
+
+#include "lib.h"
+#include "array.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-address-parts.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-debug-common.h"
+
+/*
+ * Extension
+ */
+
+static bool ext_debug_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *validator);
+static bool ext_debug_interpreter_load
+	(const struct sieve_extension *ext ATTR_UNUSED,
+		const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED);
+
+
+const struct sieve_extension_def vnd_debug_extension = {
+	.name = "vnd.dovecot.debug",
+	.validator_load = ext_debug_validator_load,
+	.interpreter_load = ext_debug_interpreter_load,
+	SIEVE_EXT_DEFINE_OPERATION(debug_log_operation),
+};
+
+static bool ext_debug_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *validator)
+{
+	/* Register new test */
+	sieve_validator_register_command(validator, ext, &debug_log_command);
+
+	return TRUE;
+}
+
+static bool ext_debug_interpreter_load
+(const struct sieve_extension *ext ATTR_UNUSED,
+	const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED)
+{
+	if ( renv->ehandler != NULL ) {
+		sieve_error_handler_accept_infolog(renv->ehandler, TRUE);
+	}
+
+	return TRUE;
+}
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/Makefile.am
@@ -0,0 +1,16 @@
+noinst_LTLIBRARIES = libsieve_ext_vnd_environment.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../../.. \
+	-I$(srcdir)/../../environment \
+	-I$(srcdir)/../../variables \
+	$(LIBDOVECOT_INCLUDE)
+
+libsieve_ext_vnd_environment_la_SOURCES = \
+	ext-vnd-environment.c \
+	ext-vnd-environment-items.c \
+	ext-vnd-environment-variables.c
+
+noinst_HEADERS = \
+	ext-vnd-environment-common.h
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment-common.h
@@ -0,0 +1,37 @@
+#ifndef EXT_VND_ENVIRONMENT_COMMON_H
+#define EXT_VND_ENVIRONMENT_COMMON_H
+
+#include "sieve-ext-environment.h"
+
+/*
+ * Extension
+ */
+
+struct ext_vnd_environment_context {
+	const struct sieve_extension *env_ext;
+	const struct sieve_extension *var_ext;
+};
+
+extern const struct sieve_extension_def vnd_environment_extension;
+
+/*
+ * Operands
+ */
+
+extern const struct sieve_operand_def environment_namespace_operand;
+
+/*
+ * Environment items
+ */
+
+void ext_vnd_environment_items_register
+(const struct sieve_extension *ext, const struct sieve_runtime_env *renv);
+
+/*
+ * Variables
+ */
+
+void ext_environment_variables_init
+(const struct sieve_extension *this_ext, struct sieve_validator *valdtr);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment-items.c
@@ -0,0 +1,90 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+
+#include "sieve-settings.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-address-parts.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-vnd-environment-common.h"
+
+/*
+ * Environment items
+ */
+
+/* default_mailbox */
+
+static const char *envit_default_mailbox_get_value
+(const struct sieve_runtime_env *renv,
+	const char *name ATTR_UNUSED)
+{
+	i_assert(renv->scriptenv->default_mailbox != NULL);
+	return renv->scriptenv->default_mailbox;
+}
+
+const struct sieve_environment_item default_mailbox_env_item = {
+	.name = "vnd.dovecot.default-mailbox",
+	.get_value = envit_default_mailbox_get_value
+};
+
+/* username */
+
+static const char *envit_username_get_value
+(const struct sieve_runtime_env *renv,
+	const char *name ATTR_UNUSED)
+{
+	return renv->svinst->username;
+}
+
+const struct sieve_environment_item username_env_item = {
+	.name = "vnd.dovecot.username",
+	.get_value = envit_username_get_value
+};
+
+/* config.* */
+
+static const char *envit_config_get_value
+(const struct sieve_runtime_env *renv, const char *name)
+{
+	if (*name == '\0')
+		return NULL;
+
+	return sieve_setting_get(renv->svinst,
+		t_strconcat("sieve_env_", name, NULL));
+}
+
+const struct sieve_environment_item config_env_item = {
+	.name = "vnd.dovecot.config",
+	.prefix = TRUE,
+	.get_value = envit_config_get_value
+};
+
+
+/*
+ * Register
+ */
+
+void ext_vnd_environment_items_register
+(const struct sieve_extension *ext, const struct sieve_runtime_env *renv)
+{
+	struct ext_vnd_environment_context *ectx =
+		(struct ext_vnd_environment_context *) ext->context;
+
+	sieve_environment_item_register
+		(ectx->env_ext, renv->interp, &default_mailbox_env_item);
+	sieve_environment_item_register
+		(ectx->env_ext, renv->interp, &username_env_item);
+	sieve_environment_item_register
+		(ectx->env_ext, renv->interp, &config_env_item);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment-variables.c
@@ -0,0 +1,206 @@
+/* Copyright (c) 2015-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve-common.h"
+#include "sieve-ast.h"
+#include "sieve-binary.h"
+#include "sieve-code.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "sieve-ext-variables.h"
+
+#include "ext-vnd-environment-common.h"
+
+static bool vnspc_vnd_environment_validate
+	(struct sieve_validator *valdtr,
+		const struct sieve_variables_namespace *nspc,
+		struct sieve_ast_argument *arg, struct sieve_command *cmd,
+		ARRAY_TYPE(sieve_variable_name) *var_name, void **var_data,
+		bool assignment);
+static bool vnspc_vnd_environment_generate
+	(const struct sieve_codegen_env *cgenv,
+		const struct sieve_variables_namespace *nspc,
+		struct sieve_ast_argument *arg,
+		struct sieve_command *cmd, void *var_data);
+static bool vnspc_vnd_environment_dump_variable
+	(const struct sieve_dumptime_env *denv,
+		const struct sieve_variables_namespace *nspc, 
+		const struct sieve_operand *oprnd, sieve_size_t *address);
+static int vnspc_vnd_environment_read_variable
+	(const struct sieve_runtime_env *renv,
+		const struct sieve_variables_namespace *nspc,
+		const struct sieve_operand *oprnd,
+		sieve_size_t *address, string_t **str_r);
+
+static const struct sieve_variables_namespace_def
+environment_namespace = {
+	SIEVE_OBJECT("env", &environment_namespace_operand, 0),
+	.validate = vnspc_vnd_environment_validate,
+	.generate = vnspc_vnd_environment_generate,
+	.dump_variable = vnspc_vnd_environment_dump_variable,
+	.read_variable = vnspc_vnd_environment_read_variable
+};
+
+static bool vnspc_vnd_environment_validate
+(struct sieve_validator *valdtr, 
+	const struct sieve_variables_namespace *nspc ATTR_UNUSED,
+	struct sieve_ast_argument *arg, struct sieve_command *cmd ATTR_UNUSED,
+	ARRAY_TYPE(sieve_variable_name) *var_name, void **var_data,
+	bool assignment)
+{
+	struct sieve_ast *ast = arg->ast;
+	const struct sieve_variable_name *name_elements;
+	unsigned int i, count;
+	const char *variable;
+	string_t *name;
+
+	/* Compose environment name from parsed variable name */
+	name = t_str_new(64);
+	name_elements = array_get(var_name, &count);
+	i_assert(count > 1);
+	for (i = 1; i < count; i++) {
+		if ( name_elements[i].num_variable >= 0 ) {
+			sieve_argument_validate_error(valdtr, arg,
+				"vnd.dovecot.environment: invalid variable name within "
+				"env namespace `env.%d': "
+				"encountered numeric variable name",
+				name_elements[i].num_variable);
+			return FALSE;
+		}
+		if (str_len(name) > 0)
+			str_append_c(name, '.');
+		str_append_str(name, name_elements[i].identifier);
+	}
+
+	variable = str_c(name);
+
+	if ( assignment ) {
+		sieve_argument_validate_error(valdtr, arg,
+			"vnd.dovecot.environment: cannot assign to environment "
+			"variable `env.%s'", variable);
+		return FALSE;
+	}
+
+	*var_data = (void *) p_strdup(sieve_ast_pool(ast), variable);
+	return TRUE;
+}
+
+static bool vnspc_vnd_environment_generate
+(const struct sieve_codegen_env *cgenv,
+	const struct sieve_variables_namespace *nspc,
+	struct sieve_ast_argument *arg ATTR_UNUSED,
+	struct sieve_command *cmd ATTR_UNUSED, void *var_data)
+{
+	const struct sieve_extension *this_ext = SIEVE_OBJECT_EXTENSION(nspc);	
+	const char *variable = (const char *) var_data;
+	struct ext_vnd_environment_context *ext_data;
+
+	if ( this_ext == NULL )
+		return FALSE;
+
+	ext_data = (struct ext_vnd_environment_context *) this_ext->context;
+
+	sieve_variables_opr_namespace_variable_emit
+		(cgenv->sblock, ext_data->var_ext, this_ext, &environment_namespace);
+	sieve_binary_emit_cstring(cgenv->sblock, variable);
+
+	return TRUE;
+}
+
+static bool vnspc_vnd_environment_dump_variable
+(const struct sieve_dumptime_env *denv,
+	const struct sieve_variables_namespace *nspc ATTR_UNUSED,
+	const struct sieve_operand *oprnd, sieve_size_t *address)
+{
+	string_t *var_name;
+
+	if ( !sieve_binary_read_string(denv->sblock, address, &var_name) )
+		return FALSE;
+
+	if ( oprnd->field_name != NULL )
+		sieve_code_dumpf(denv, "%s: VAR ${env.%s}",
+			oprnd->field_name, str_c(var_name));
+	else
+		sieve_code_dumpf(denv, "VAR ${env.%s}",
+			str_c(var_name));
+
+	return TRUE;
+}
+
+static int vnspc_vnd_environment_read_variable
+(const struct sieve_runtime_env *renv,
+	const struct sieve_variables_namespace *nspc,
+	const struct sieve_operand *oprnd, sieve_size_t *address,
+	string_t **str_r)
+{
+	const struct sieve_extension *this_ext = SIEVE_OBJECT_EXTENSION(nspc);	
+	struct ext_vnd_environment_context *ectx =
+		(struct ext_vnd_environment_context *) this_ext->context;
+	string_t *var_name;
+	const char *ext_value;
+
+	if ( !sieve_binary_read_string(renv->sblock, address, &var_name) ) {
+		sieve_runtime_trace_operand_error(renv, oprnd,
+			"environment variable operand corrupt: invalid name");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	if ( str_r !=  NULL ) {
+		const char *vname = str_c(var_name);
+
+		ext_value = ext_environment_item_get_value
+			(ectx->env_ext, renv, vname);
+
+		if ( ext_value == NULL && strchr(vname, '_') != NULL) {
+			char *p, *aname;
+
+			/* Try again with '_' replaced with '-' */
+			aname = t_strdup_noconst(vname);
+			for (p = aname; *p != '\0'; p++) {
+				if (*p == '_')
+					*p = '-';
+			}
+			ext_value = ext_environment_item_get_value
+				(ectx->env_ext, renv, aname);
+		}
+
+		if ( ext_value == NULL ) {
+			*str_r = t_str_new_const("", 0);
+			return SIEVE_EXEC_OK;
+		}
+
+		*str_r = t_str_new_const(ext_value, strlen(ext_value));
+	}
+	return SIEVE_EXEC_OK;
+}
+
+/*
+ * Namespace registration
+ */
+
+static const struct sieve_extension_objects environment_namespaces =
+	SIEVE_VARIABLES_DEFINE_NAMESPACE(environment_namespace);
+
+const struct sieve_operand_def environment_namespace_operand = {
+	.name = "env-namespace",
+	.ext_def = &vnd_environment_extension,
+	.class = &sieve_variables_namespace_operand_class,
+	.interface = &environment_namespaces
+};
+
+void ext_environment_variables_init
+(const struct sieve_extension *this_ext, struct sieve_validator *valdtr)
+{
+	struct ext_vnd_environment_context *ext_data =
+		(struct ext_vnd_environment_context *) this_ext->context;
+
+	sieve_variables_namespace_register
+		(ext_data->var_ext, valdtr, this_ext, &environment_namespace);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment.c
@@ -0,0 +1,112 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension vnd.dovecot.environment
+ * ---------------------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: vendor-defined;
+ *   spec-bosch-sieve-dovecot-environment
+ * Implementation: preliminary
+ * Status: experimental
+ *
+ */
+
+#include "lib.h"
+#include "array.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-address-parts.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "sieve-ext-variables.h"
+
+#include "ext-vnd-environment-common.h"
+
+/*
+ * Extension
+ */
+
+static bool ext_vnd_environment_load
+	(const struct sieve_extension *ext, void **context);
+static void ext_vnd_environment_unload
+	(const struct sieve_extension *ext);
+static bool ext_vnd_environment_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+static bool ext_vnd_environment_interpreter_load
+	(const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+		sieve_size_t *address);
+
+const struct sieve_extension_def vnd_environment_extension = {
+	.name = "vnd.dovecot.environment",
+	.load = ext_vnd_environment_load,
+	.unload = ext_vnd_environment_unload,
+	.validator_load = ext_vnd_environment_validator_load,
+	.interpreter_load = ext_vnd_environment_interpreter_load,
+	SIEVE_EXT_DEFINE_OPERAND(environment_namespace_operand)
+};
+
+static bool ext_vnd_environment_load
+(const struct sieve_extension *ext, void **context)
+{
+	struct ext_vnd_environment_context *ectx;
+
+	if ( *context != NULL )
+		ext_vnd_environment_unload(ext);
+
+	ectx = i_new(struct ext_vnd_environment_context, 1);
+	ectx->env_ext = sieve_ext_environment_require_extension(ext->svinst);
+	ectx->var_ext = sieve_ext_variables_get_extension(ext->svinst);
+	*context = (void *) ectx;
+
+	return TRUE;
+}
+
+static void ext_vnd_environment_unload
+(const struct sieve_extension *ext)
+{
+	struct ext_vnd_environment_context *ectx =
+		(struct ext_vnd_environment_context *) ext->context;
+
+	i_free(ectx);
+}
+
+/*
+ * Validator
+ */
+
+static bool ext_vnd_environment_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	const struct sieve_extension *env_ext;
+
+	/* Load environment extension implicitly */
+
+	env_ext = sieve_validator_extension_load_implicit
+		(valdtr, environment_extension.name);
+	if ( env_ext == NULL )
+		return FALSE;
+
+	ext_environment_variables_init(ext, valdtr);
+	return TRUE;
+}
+
+/*
+ * Interpreter
+ */
+
+static bool ext_vnd_environment_interpreter_load
+(const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+	sieve_size_t *address ATTR_UNUSED)
+{
+	ext_vnd_environment_items_register(ext, renv);
+	return TRUE;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/Makefile.am
@@ -0,0 +1,17 @@
+noinst_LTLIBRARIES = libsieve_ext_vnd_report.la
+
+AM_CPPFLAGS = \
+	-I$(srcdir)/../../.. \
+	-I$(srcdir)/../../../util \
+	$(LIBDOVECOT_INCLUDE)
+
+commands = \
+	cmd-report.c
+
+libsieve_ext_vnd_report_la_SOURCES = \
+	ext-vnd-report.c \
+	ext-vnd-report-common.c \
+	$(commands)
+
+noinst_HEADERS = \
+	ext-vnd-report-common.h
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/cmd-report.c
@@ -0,0 +1,684 @@
+/* Copyright (c) 2016-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "ioloop.h"
+#include "hostpid.h"
+#include "str-sanitize.h"
+#include "istream.h"
+#include "ostream.h"
+#include "message-date.h"
+#include "message-size.h"
+#include "mail-storage.h"
+
+#include "rfc2822.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-address.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-message.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-result.h"
+#include "sieve-address.h"
+#include "sieve-message.h"
+#include "sieve-smtp.h"
+
+#include "ext-vnd-report-common.h"
+
+#include <ctype.h>
+
+/* Report command
+ *
+ * Syntax:
+ *    report [:headers_only] <feedback-type: string>
+ *           <message: string> <address: string>
+ *
+ */
+
+static bool cmd_report_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool cmd_report_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_report_generate
+	(const struct sieve_codegen_env *cgenv,
+		struct sieve_command *ctx);
+
+const struct sieve_command_def cmd_report = {
+	.identifier = "report",
+	.type = SCT_COMMAND,
+	.positional_args = 3,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = cmd_report_registered,
+	.validate = cmd_report_validate,
+	.generate = cmd_report_generate
+};
+
+/*
+ * Tagged arguments
+ */
+
+static const struct sieve_argument_def report_headers_only_tag = {
+	.identifier = "headers_only"
+};
+
+/*
+ * Report operation
+ */
+
+static bool cmd_report_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_report_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def report_operation = {
+	.mnemonic = "REPORT",
+	.ext_def = &vnd_report_extension,
+	.code = 0,
+	.dump = cmd_report_operation_dump,
+	.execute = cmd_report_operation_execute
+};
+
+/* Codes for optional operands */
+
+enum cmd_report_optional {
+  OPT_END,
+  OPT_HEADERS_ONLY
+};
+
+/*
+ * Report action
+ */
+
+/* Forward declarations */
+
+static int act_report_check_duplicate
+	(const struct sieve_runtime_env *renv,
+		const struct sieve_action *act,
+		const struct sieve_action *act_other);
+static void act_report_print
+	(const struct sieve_action *action, const struct sieve_result_print_env *rpenv,
+		bool *keep);
+static int act_report_commit
+	(const struct sieve_action *action,	const struct sieve_action_exec_env *aenv,
+		void *tr_context, bool *keep);
+
+/* Action object */
+
+const struct sieve_action_def act_report = {
+	.name = "report",
+	.check_duplicate = act_report_check_duplicate,
+	.print = act_report_print,
+	.commit = act_report_commit
+};
+
+/* Action data */
+
+struct act_report_data {
+	const char *feedback_type;
+	const char *message;
+	struct smtp_address *to_address;
+	bool headers_only:1;
+};
+
+/*
+ * Command registration
+ */
+
+static bool cmd_report_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg)
+{
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &report_headers_only_tag, OPT_HEADERS_ONLY);
+
+	return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+static bool cmd_report_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *arg = cmd->first_positional;
+
+	/* type */
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "feedback-type", 1, SAAT_STRING) ) {
+		return FALSE;
+	}
+	if ( !sieve_validator_argument_activate
+		(valdtr, cmd, arg, FALSE) )
+		return FALSE;
+
+	if ( sieve_argument_is_string_literal(arg) ) {
+		string_t *fbtype = sieve_ast_argument_str(arg);
+		const char *feedback_type;
+
+		T_BEGIN {
+			/* Check feedback type */
+			feedback_type = ext_vnd_report_parse_feedback_type
+				(str_c(fbtype));
+
+			if ( feedback_type == NULL ) {
+				sieve_argument_validate_error(valdtr, arg,
+					"specified feedback type `%s' is invalid",
+					str_sanitize(str_c(fbtype),128));
+			}
+		} T_END;
+
+		if ( feedback_type == NULL )
+			return FALSE;
+	}
+	arg = sieve_ast_argument_next(arg);
+
+	/* message */
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "message", 2, SAAT_STRING) ) {
+		return FALSE;
+	}
+	if ( !sieve_validator_argument_activate
+		(valdtr, cmd, arg, FALSE) )
+		return FALSE;
+	arg = sieve_ast_argument_next(arg);
+
+	/* address */
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "address", 3, SAAT_STRING) ) {
+		return FALSE;
+	}
+	if ( !sieve_validator_argument_activate
+		(valdtr, cmd, arg, FALSE) )
+		return FALSE;
+
+	/* We can only assess the validity of the outgoing address when it is
+	 * a string literal. For runtime-generated strings this needs to be
+	 * done at runtime.
+	 */
+	if ( sieve_argument_is_string_literal(arg) ) {
+		string_t *raw_address = sieve_ast_argument_str(arg);
+		const char *error;
+		bool result;
+
+		T_BEGIN {
+			/* Parse the address */
+			result = sieve_address_validate_str(raw_address, &error);
+			if ( !result ) {
+				sieve_argument_validate_error(valdtr, arg,
+					"specified report address '%s' is invalid: %s",
+					str_sanitize(str_c(raw_address),128), error);
+			}
+		} T_END;
+
+		return result;
+	}
+
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_report_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	sieve_operation_emit(cgenv->sblock, cmd->ext, &report_operation);
+
+	/* Generate arguments */
+	return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_report_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	int opt_code = 0;
+
+	sieve_code_dumpf(denv, "REPORT");
+	sieve_code_descend(denv);
+
+	/* Dump optional operands */
+	for (;;) {
+		int opt;
+
+		if ( (opt=sieve_opr_optional_dump
+			(denv, address, &opt_code)) < 0 )
+			return FALSE;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_HEADERS_ONLY:
+			sieve_code_dumpf(denv, "headers_only");
+			break;
+		default:
+			return FALSE;
+		}
+	}
+
+	return
+		sieve_opr_string_dump(denv, address, "feedback-type") &&
+		sieve_opr_string_dump(denv, address, "message") &&
+		sieve_opr_string_dump(denv, address, "address");
+}
+
+/*
+ * Code execution
+ */
+
+
+static int cmd_report_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	const struct sieve_extension *this_ext = renv->oprtn->ext;
+	struct act_report_data *act;
+	string_t *fbtype, *message, *to_address;
+	const char *feedback_type, *error;
+	const struct smtp_address *parsed_address;
+	int opt_code = 0, ret = 0;
+	bool headers_only = FALSE;
+	pool_t pool;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Optional operands */
+
+	for (;;) {
+		int opt;
+
+		if ( (opt=sieve_opr_optional_read(renv, address, &opt_code)) < 0 )
+			return SIEVE_EXEC_BIN_CORRUPT;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_HEADERS_ONLY:
+			headers_only = TRUE;
+			break;
+		default:
+			sieve_runtime_trace_error(renv, "unknown optional operand");
+			return SIEVE_EXEC_BIN_CORRUPT;
+		}
+	}
+
+	/* Fixed operands */
+
+	if ( (ret=sieve_opr_string_read
+		(renv, address, "feedback-type", &fbtype)) <= 0 )
+		return ret;
+
+	if ( (ret=sieve_opr_string_read
+		(renv, address, "message", &message)) <= 0 )
+		return ret;
+
+	if ( (ret=sieve_opr_string_read
+		(renv, address, "address", &to_address)) <= 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	/* Verify and trim feedback type */
+	feedback_type = ext_vnd_report_parse_feedback_type(str_c(fbtype));
+	if ( feedback_type == NULL ) {
+		sieve_runtime_error(renv, NULL,
+			"specified report feedback type `%s' is invalid",
+			str_sanitize(str_c(fbtype), 256));
+		return SIEVE_EXEC_FAILURE;
+	}
+
+	/* Verify and normalize the address to 'local_part@domain' */
+	parsed_address = sieve_address_parse_str(to_address, &error);
+	if ( parsed_address == NULL ) {
+		sieve_runtime_error(renv, NULL,
+			"specified report address '%s' is invalid: %s",
+			str_sanitize(str_c(to_address),128), error);
+		return SIEVE_EXEC_FAILURE;
+	}
+
+	/* Trace */
+	if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_ACTIONS) ) {
+		sieve_runtime_trace(renv, 0, "report action");
+		sieve_runtime_trace_descend(renv);
+		sieve_runtime_trace(renv, 0,
+			"report incoming message as `%s' to address %s",
+			str_sanitize(str_c(fbtype), 32),
+			smtp_address_encode_path(parsed_address));
+	}
+
+	/* Add report action to the result */
+
+	pool = sieve_result_pool(renv->result);
+	act = p_new(pool, struct act_report_data, 1);
+	act->headers_only = headers_only;
+	act->feedback_type = p_strdup(pool, feedback_type);
+	act->message = p_strdup(pool, str_c(message));
+	act->to_address = smtp_address_clone(pool, parsed_address);
+
+	if ( sieve_result_add_action(renv,
+		this_ext, &act_report, NULL, (void *) act, 0, TRUE) < 0 )
+		return SIEVE_EXEC_FAILURE;
+
+	return SIEVE_EXEC_OK;
+}
+
+/*
+ * Action
+ */
+
+/* Runtime verification */
+
+static bool act_report_equals
+(const struct sieve_script_env *senv ATTR_UNUSED,
+	const struct sieve_action *act1,
+	const struct sieve_action *act2)
+{
+	struct act_report_data *rdd1 =
+		(struct act_report_data *) act1->context;
+	struct act_report_data *rdd2 =
+		(struct act_report_data *) act2->context;
+
+	/* Address is already normalized */
+	return ( smtp_address_equals
+		(rdd1->to_address, rdd2->to_address) );
+}
+
+static int act_report_check_duplicate
+(const struct sieve_runtime_env *renv ATTR_UNUSED,
+	const struct sieve_action *act,
+	const struct sieve_action *act_other)
+{
+	return ( act_report_equals
+		(renv->scriptenv, act, act_other) ? 1 : 0 );
+}
+
+/* Result printing */
+
+static void act_report_print
+(const struct sieve_action *action,
+	const struct sieve_result_print_env *rpenv,
+	bool *keep ATTR_UNUSED)
+{
+	const struct act_report_data *rdd =
+		(struct act_report_data *) action->context;
+
+	sieve_result_action_printf(rpenv,
+		"report incoming message as `%s' to: %s",
+		str_sanitize(rdd->feedback_type, 32),
+		smtp_address_encode_path(rdd->to_address));
+}
+
+/* Result execution */
+
+static bool _contains_8bit(const char *msg)
+{
+	const unsigned char *s = (const unsigned char *)msg;
+
+	for (; *s != '\0'; s++) {
+		if ((*s & 0x80) != 0)
+			return TRUE;
+	}
+	return FALSE;
+}
+
+static int act_report_send
+(const struct sieve_action_exec_env *aenv,
+	const struct ext_report_config *config,
+	const struct act_report_data *act)
+{
+	struct sieve_instance *svinst = aenv->svinst;
+	struct sieve_message_context *msgctx = aenv->msgctx;
+	const struct sieve_script_env *senv = aenv->scriptenv;
+	const struct sieve_message_data *msgdata = aenv->msgdata;
+	struct sieve_address_source report_from = config->report_from;
+	const struct smtp_address *sender, *user;
+	struct sieve_smtp_context *sctx;
+	struct istream *input;
+	struct ostream *output;
+	string_t *msg;
+	const char *const *headers;
+	const char *outmsgid, *boundary, *error, *subject, *from;
+	int ret;
+
+	/* Just to be sure */
+	if ( !sieve_smtp_available(senv) ) {
+		sieve_result_global_warning(aenv,
+			"report action has no means to send mail");
+		return SIEVE_EXEC_OK;
+	}
+
+	/* Make sure we have a subject for our report */
+	if ( (ret=mail_get_headers_utf8
+		(msgdata->mail, "subject", &headers)) < 0 ) {
+		return sieve_result_mail_error(aenv, msgdata->mail,
+			"report action: "
+			"failed to read header field `subject'");
+	}
+	if ( ret > 0 && headers[0] != NULL ) {
+		subject = t_strconcat("Report: ", headers[0], NULL);
+	}	else {
+		subject = "Report: (message without subject)";
+	}
+
+	/* Determine from address */
+	if ( report_from.type == SIEVE_ADDRESS_SOURCE_POSTMASTER ) {
+		report_from.type = SIEVE_ADDRESS_SOURCE_DEFAULT;
+		report_from.address = NULL;
+	}
+	if ( (ret=sieve_address_source_get_address
+		(&report_from, svinst, senv, msgctx,
+			aenv->flags, &sender)) > 0 && sender != NULL) {
+		from = smtp_address_encode_path(sender);
+	} else {
+		from = sieve_get_postmaster_address(senv);
+	}
+
+	/* Start message */
+	sctx = sieve_smtp_start_single
+		(senv, act->to_address, NULL, &output);
+
+	outmsgid = sieve_message_get_new_id(aenv->svinst);
+	boundary = t_strdup_printf("%s/%s", my_pid, svinst->hostname);
+
+	/* Compose main report headers */
+	msg = t_str_new(512);
+	rfc2822_header_write(msg, "X-Sieve", SIEVE_IMPLEMENTATION);
+	rfc2822_header_write(msg, "Message-ID", outmsgid);
+	rfc2822_header_write(msg, "Date", message_date_create(ioloop_time));
+
+	rfc2822_header_write(msg, "From", from);
+	rfc2822_header_write(msg, "To",
+		smtp_address_encode_path(act->to_address));
+
+	if ( _contains_8bit(subject) )
+		rfc2822_header_utf8_printf(msg, "Subject", "%s", subject);
+	else
+		rfc2822_header_printf(msg, "Subject", "%s", subject);
+
+	rfc2822_header_write(msg, "Auto-Submitted", "auto-generated (report)");
+
+	rfc2822_header_write(msg, "MIME-Version", "1.0");
+	rfc2822_header_printf(msg, "Content-Type",
+		"multipart/report; report-type=feedback-report;\n"
+		"boundary=\"%s\"", boundary);
+
+	str_append(msg, "\r\nThis is a MIME-encapsulated message\r\n\r\n");
+
+	/* Human-readable report */
+	str_printfa(msg, "--%s\r\n", boundary);
+	if (_contains_8bit(act->message)) {
+		rfc2822_header_write(msg,
+			"Content-Type", "text/plain; charset=utf-8");
+		rfc2822_header_write(msg, "Content-Transfer-Encoding", "8bit");
+	} else {
+		rfc2822_header_write(msg,
+			"Content-Type", "text/plain; charset=us-ascii");
+		rfc2822_header_write(msg, "Content-Transfer-Encoding", "7bit");
+	}
+	rfc2822_header_write(msg, "Content-Disposition", "inline");
+
+	str_printfa(msg, "\r\n%s\r\n\r\n", act->message);
+	o_stream_nsend(output, str_data(msg), str_len(msg));
+
+	/* Machine-readable report */
+  str_truncate(msg, 0);
+	str_printfa(msg, "--%s\r\n", boundary);
+	rfc2822_header_write(msg,
+		"Content-Type", "message/feedback-report");
+	str_append(msg, "\r\n");
+
+	rfc2822_header_write(msg,	"Version", "1");
+	rfc2822_header_write(msg,
+		"Feedback-Type", act->feedback_type);
+	rfc2822_header_write(msg,	"User-Agent",
+		PACKAGE_NAME "/" PACKAGE_VERSION " "
+		PIGEONHOLE_NAME "/" PIGEONHOLE_VERSION);
+
+	if ( (aenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0 ) {
+		const struct smtp_address *sender, *orig_recipient;
+
+		sender = sieve_message_get_sender(msgctx);
+		orig_recipient = sieve_message_get_orig_recipient(msgctx);
+
+		rfc2822_header_write(msg,
+			"Original-Mail-From",
+			smtp_address_encode_path(sender));
+		if (orig_recipient != NULL) {
+			rfc2822_header_write(msg,
+				"Original-Rcpt-To",
+				smtp_address_encode_path(orig_recipient));
+		}
+	}
+	if (svinst->user_email != NULL)
+		user = svinst->user_email;
+	else if ((aenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) != 0 ||
+		(user=sieve_message_get_orig_recipient(msgctx)) == NULL)
+		user = sieve_get_user_email(svinst);
+	if (user != NULL) {
+		rfc2822_header_write(msg, "Dovecot-Reporting-User",
+			smtp_address_encode_path(user));
+	}
+	str_append(msg, "\r\n");
+
+	o_stream_nsend(output, str_data(msg), str_len(msg));
+
+	/* Original message */
+  str_truncate(msg, 0);
+	str_printfa(msg, "--%s\r\n", boundary);
+	if (act->headers_only) {
+		rfc2822_header_write(msg,
+			"Content-Type", "text/rfc822-headers");
+	} else {
+		rfc2822_header_write(msg,
+			"Content-Type", "message/rfc822");
+	}
+	rfc2822_header_write(msg,
+		"Content-Disposition", "attachment");
+	str_append(msg, "\r\n");
+	o_stream_nsend(output, str_data(msg), str_len(msg));
+
+	if (act->headers_only) {
+		struct message_size hdr_size;
+		ret = mail_get_hdr_stream(msgdata->mail, &hdr_size, &input);
+		if (ret >= 0)
+			input = i_stream_create_limit(input, hdr_size.physical_size);
+	} else {
+		ret = mail_get_stream(msgdata->mail, NULL, NULL, &input);
+		if (ret >= 0)
+			i_stream_ref(input);
+	}
+	if (ret < 0) {
+		return sieve_result_mail_error(aenv, msgdata->mail,
+			"report action: failed to read input message");
+	}
+
+	o_stream_nsend_istream(output, input);
+
+	if ( input->stream_errno != 0 ) {
+		/* Error; clean up */
+		sieve_result_critical(aenv,
+			"report action: failed to read input message",
+			"report action: read(%s) failed: %s",
+			i_stream_get_name(input),
+			i_stream_get_error(input));
+		i_stream_unref(&input);
+		return SIEVE_EXEC_OK;
+	}
+	i_stream_unref(&input);
+
+  str_truncate(msg, 0);
+	if (!act->headers_only)
+		str_printfa(msg, "\r\n");
+	str_printfa(msg, "\r\n--%s--\r\n", boundary);
+  o_stream_nsend(output, str_data(msg), str_len(msg));
+
+	/* Finish sending message */
+	if ( (ret=sieve_smtp_finish(sctx, &error)) <= 0 ) {
+		if (ret < 0) {
+			sieve_result_global_error(aenv,
+				"failed to send `%s' report to <%s>: %s "
+				"(temporary failure)",
+				str_sanitize(act->feedback_type, 32),
+				smtp_address_encode(act->to_address),
+				str_sanitize(error, 512));
+		} else {
+			sieve_result_global_log_error(aenv,
+				"failed to send `%s' report to <%s>: %s "
+				"(permanent failure)",
+				str_sanitize(act->feedback_type, 32),
+				smtp_address_encode(act->to_address),
+				str_sanitize(error, 512));
+		}
+	} else {
+		sieve_result_global_log(aenv,
+			"sent `%s' report to <%s>",
+			str_sanitize(act->feedback_type, 32),
+			smtp_address_encode(act->to_address));
+	}
+
+	return SIEVE_EXEC_OK;
+}
+
+static int act_report_commit
+(const struct sieve_action *action,
+	const struct sieve_action_exec_env *aenv,
+	void *tr_context ATTR_UNUSED,
+	bool *keep ATTR_UNUSED)
+{
+	const struct sieve_extension *ext = action->ext;
+	const struct ext_report_config *config =
+		(const struct ext_report_config *) ext->context;
+	const struct act_report_data *act =
+		(const struct act_report_data *) action->context;
+	int ret;
+
+	T_BEGIN {
+		ret = act_report_send(aenv, config, act);
+	} T_END;
+
+	if ( ret == SIEVE_EXEC_TEMP_FAILURE )
+		return SIEVE_EXEC_TEMP_FAILURE;
+
+	/* Ignore all other errors */
+	return SIEVE_EXEC_OK;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/ext-vnd-report-common.c
@@ -0,0 +1,51 @@
+/* Copyright (c) 2016-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "rfc822-parser.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+
+#include "ext-vnd-report-common.h"
+
+bool ext_report_load
+(const struct sieve_extension *ext, void **context)
+{
+	struct sieve_instance *svinst = ext->svinst;
+	struct ext_report_config *config;
+
+	config = p_new(svinst->pool, struct ext_report_config, 1);
+
+	(void)sieve_address_source_parse_from_setting(svinst,
+		svinst->pool, "sieve_report_from", &config->report_from);
+
+	*context = (void *) config;
+	return TRUE;
+}
+
+const char *
+ext_vnd_report_parse_feedback_type(const char *feedback_type)
+{
+	struct rfc822_parser_context parser;
+	string_t *token;
+
+	/* Initialize parsing */
+	rfc822_parser_init(&parser,
+		(const unsigned char *)feedback_type, strlen(feedback_type), NULL);
+	(void)rfc822_skip_lwsp(&parser);
+
+	/* Parse MIME token */
+	token = t_str_new(64);
+	if (rfc822_parse_mime_token(&parser, token) < 0)
+		return NULL;
+
+	/* Content-type value must end here, otherwise it is invalid after all */
+	(void)rfc822_skip_lwsp(&parser);
+	if ( parser.data != parser.end )
+		return NULL;
+
+	/* Success */
+	return str_c(token);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/ext-vnd-report-common.h
@@ -0,0 +1,40 @@
+#ifndef EXT_REPORT_COMMON_H
+#define EXT_REPORT_COMMON_H
+
+/*
+ * Extension configuration
+ */
+
+struct ext_report_config {
+	struct sieve_address_source report_from;
+};
+
+/*
+ * Extension
+ */
+
+extern const struct sieve_extension_def vnd_report_extension;
+
+bool ext_report_load
+	(const struct sieve_extension *ext, void **context);
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def cmd_report;
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def report_operation;
+
+/*
+ * RFC 5965 feedback-type
+ */
+
+const char *
+ext_vnd_report_parse_feedback_type(const char *feedback_type);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/ext-vnd-report.c
@@ -0,0 +1,52 @@
+/* Copyright (c) 2016-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension report
+ * ----------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: draft-ietf-sieve-report-00.txt
+ * Implementation: full, but deprecated; provided for backwards compatibility
+ * Status: testing
+ *
+ */
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-actions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+
+#include "ext-vnd-report-common.h"
+
+/*
+ * Extension
+ */
+
+static bool ext_report_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def vnd_report_extension = {
+	.name = "vnd.dovecot.report",
+	.load = ext_report_load,
+	.validator_load = ext_report_validator_load,
+	SIEVE_EXT_DEFINE_OPERATION(report_operation)
+};
+
+/*
+ * Extension validation
+ */
+
+static bool ext_report_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	/* Register new commands */
+	sieve_validator_register_command(valdtr, ext, &cmd_report);
+
+	return TRUE;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-actions.c
@@ -0,0 +1,982 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "strfuncs.h"
+#include "ioloop.h"
+#include "hostpid.h"
+#include "str-sanitize.h"
+#include "unichar.h"
+#include "istream.h"
+#include "istream-header-filter.h"
+#include "ostream.h"
+#include "smtp-params.h"
+#include "mail-storage.h"
+#include "message-date.h"
+#include "message-size.h"
+
+#include "rfc2822.h"
+
+#include "sieve-code.h"
+#include "sieve-settings.h"
+#include "sieve-extensions.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-result.h"
+#include "sieve-actions.h"
+#include "sieve-message.h"
+#include "sieve-smtp.h"
+
+#include <ctype.h>
+
+/*
+ * Side-effect operand
+ */
+
+const struct sieve_operand_class sieve_side_effect_operand_class =
+	{ "SIDE-EFFECT" };
+
+bool sieve_opr_side_effect_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	struct sieve_side_effect seffect;
+	const struct sieve_side_effect_def *sdef;
+
+	if ( !sieve_opr_object_dump
+		(denv, &sieve_side_effect_operand_class, address, &seffect.object) )
+		return FALSE;
+
+	sdef = seffect.def =
+		(const struct sieve_side_effect_def *) seffect.object.def;
+
+	if ( sdef->dump_context != NULL ) {
+		sieve_code_descend(denv);
+		if ( !sdef->dump_context(&seffect, denv, address) ) {
+			return FALSE;
+		}
+		sieve_code_ascend(denv);
+	}
+
+	return TRUE;
+}
+
+int sieve_opr_side_effect_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+	struct sieve_side_effect *seffect)
+{
+	const struct sieve_side_effect_def *sdef;
+	int ret;
+
+	seffect->context = NULL;
+
+	if ( !sieve_opr_object_read
+		(renv, &sieve_side_effect_operand_class, address, &seffect->object) )
+		return SIEVE_EXEC_BIN_CORRUPT;
+
+	sdef = seffect->def =
+		(const struct sieve_side_effect_def *) seffect->object.def;
+
+	if ( sdef->read_context != NULL && (ret=sdef->read_context
+		(seffect, renv, address, &seffect->context)) <= 0 ) {
+		return ret;
+	}
+
+	return SIEVE_EXEC_OK;
+}
+
+/*
+ * Optional operands
+ */
+
+int sieve_action_opr_optional_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+	signed int *opt_code)
+{
+	signed int _opt_code = 0;
+	bool final = FALSE, opok = TRUE;
+
+	if ( opt_code == NULL ) {
+		opt_code = &_opt_code;
+		final = TRUE;
+	}
+
+	while ( opok ) {
+		int opt;
+
+		if ( (opt=sieve_opr_optional_dump(denv, address, opt_code)) <= 0 )
+			return opt;
+
+		if ( *opt_code == SIEVE_OPT_SIDE_EFFECT ) {
+			opok = sieve_opr_side_effect_dump(denv, address);
+		} else {
+			return ( final ? -1 : 1 );
+		}
+	}
+
+	return -1;
+}
+
+int sieve_action_opr_optional_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+	signed int *opt_code, int *exec_status,
+	struct sieve_side_effects_list **list)
+{
+	signed int _opt_code = 0;
+	bool final = FALSE;
+	int ret;
+
+	if ( opt_code == NULL ) {
+		opt_code = &_opt_code;
+		final = TRUE;
+	}
+
+	if ( exec_status != NULL )
+		*exec_status = SIEVE_EXEC_OK;
+
+	for ( ;; ) {
+		int opt;
+
+		if ( (opt=sieve_opr_optional_read(renv, address, opt_code)) <= 0 ) {
+			if ( opt < 0 && exec_status != NULL )
+				*exec_status = SIEVE_EXEC_BIN_CORRUPT;
+			return opt;
+		}
+
+		if ( *opt_code == SIEVE_OPT_SIDE_EFFECT ) {
+			struct sieve_side_effect seffect;
+
+			i_assert( list != NULL );
+
+			if ( (ret=sieve_opr_side_effect_read(renv, address, &seffect)) <= 0 ) {
+				if ( exec_status != NULL )
+					*exec_status = ret;
+				return -1;
+			}
+
+			if ( *list == NULL )
+				*list = sieve_side_effects_list_create(renv->result);
+
+			sieve_side_effects_list_add(*list, &seffect);
+		} else {
+			if ( final ) {
+				sieve_runtime_trace_error(renv, "invalid optional operand");
+				if ( exec_status != NULL )
+					*exec_status = SIEVE_EXEC_BIN_CORRUPT;
+				return -1;
+			}
+			return 1;
+		}
+	}
+
+	i_unreached();
+	return -1;
+}
+
+/*
+ * Store action
+ */
+
+/* Forward declarations */
+
+static bool act_store_equals
+	(const struct sieve_script_env *senv,
+		const struct sieve_action *act1, const struct sieve_action *act2);
+
+static int act_store_check_duplicate
+	(const struct sieve_runtime_env *renv,
+		const struct sieve_action *act,
+		const struct sieve_action *act_other);
+static void act_store_print
+	(const struct sieve_action *action,
+		const struct sieve_result_print_env *rpenv, bool *keep);
+
+static int act_store_start
+	(const struct sieve_action *action,
+		const struct sieve_action_exec_env *aenv, void **tr_context);
+static int act_store_execute
+	(const struct sieve_action *action,
+		const struct sieve_action_exec_env *aenv, void *tr_context);
+static int act_store_commit
+	(const struct sieve_action *action,
+		const struct sieve_action_exec_env *aenv, void *tr_context, bool *keep);
+static void act_store_rollback
+	(const struct sieve_action *action,
+		const struct sieve_action_exec_env *aenv, void *tr_context, bool success);
+
+/* Action object */
+
+const struct sieve_action_def act_store = {
+	.name = "store",
+	.flags =
+		SIEVE_ACTFLAG_TRIES_DELIVER | 
+		SIEVE_ACTFLAG_MAIL_STORAGE,
+	.equals = act_store_equals,
+	.check_duplicate = act_store_check_duplicate,
+	.print = act_store_print,
+	.start = act_store_start,
+	.execute = act_store_execute,
+	.commit = act_store_commit,
+	.rollback = act_store_rollback,
+};
+
+/* API */
+
+int sieve_act_store_add_to_result
+(const struct sieve_runtime_env *renv,
+	struct sieve_side_effects_list *seffects, const char *mailbox)
+{
+	pool_t pool;
+	struct act_store_context *act;
+
+	/* Add redirect action to the result */
+	pool = sieve_result_pool(renv->result);
+	act = p_new(pool, struct act_store_context, 1);
+	act->mailbox = p_strdup(pool, mailbox);
+
+	return sieve_result_add_action
+		(renv, NULL, &act_store, seffects, (void *)act, 0, TRUE);
+}
+
+void sieve_act_store_add_flags
+(const struct sieve_action_exec_env *aenv, void *tr_context,
+	const char *const *keywords, enum mail_flags flags)
+{
+	struct act_store_transaction *trans =
+		(struct act_store_transaction *) tr_context;
+
+	i_assert(trans != NULL);
+
+	/* Assign mail keywords for subsequent mailbox_copy() */
+	if ( *keywords != NULL ) {
+		const char *const *kw;
+
+		if ( !array_is_created(&trans->keywords) ) {
+			pool_t pool = sieve_result_pool(aenv->result);
+			p_array_init(&trans->keywords, pool, 2);
+		}
+
+		kw = keywords;
+		while ( *kw != NULL ) {
+
+			const char *kw_error;
+
+			if ( trans->box != NULL && trans->error_code == MAIL_ERROR_NONE ) {
+				if ( mailbox_keyword_is_valid(trans->box, *kw, &kw_error) )
+					array_append(&trans->keywords, kw, 1);
+				else {
+					char *error = "";
+					if ( kw_error != NULL && *kw_error != '\0' ) {
+						error = t_strdup_noconst(kw_error);
+						error[0] = i_tolower(error[0]);
+					}
+
+					sieve_result_warning(aenv,
+						"specified IMAP keyword '%s' is invalid (ignored): %s",
+						str_sanitize(*kw, 64), error);
+				}
+			}
+
+			kw++;
+		}
+	}
+
+	/* Assign mail flags for subsequent mailbox_copy() */
+	trans->flags |= flags;
+
+	trans->flags_altered = TRUE;
+}
+
+/* Equality */
+
+static bool act_store_equals
+(const struct sieve_script_env *senv,
+	const struct sieve_action *act1, const struct sieve_action *act2)
+{
+	struct act_store_context *st_ctx1 =
+		( act1 == NULL ? NULL : (struct act_store_context *) act1->context );
+	struct act_store_context *st_ctx2 =
+		( act2 == NULL ? NULL : (struct act_store_context *) act2->context );
+	const char *mailbox1, *mailbox2;
+
+	/* FIXME: consider namespace aliases */
+
+	if ( st_ctx1 == NULL && st_ctx2 == NULL )
+		return TRUE;
+
+	mailbox1 = ( st_ctx1 == NULL ?
+		SIEVE_SCRIPT_DEFAULT_MAILBOX(senv) : st_ctx1->mailbox );
+	mailbox2 = ( st_ctx2 == NULL ?
+		SIEVE_SCRIPT_DEFAULT_MAILBOX(senv) : st_ctx2->mailbox );
+
+	if ( strcmp(mailbox1, mailbox2) == 0 )
+		return TRUE;
+
+	return
+		( strcasecmp(mailbox1, "INBOX") == 0 && strcasecmp(mailbox2, "INBOX") == 0 );
+
+}
+
+/* Result verification */
+
+static int act_store_check_duplicate
+(const struct sieve_runtime_env *renv,
+	const struct sieve_action *act,
+	const struct sieve_action *act_other)
+{
+	return ( act_store_equals(renv->scriptenv, act, act_other) ? 1 : 0 );
+}
+
+/* Result printing */
+
+static void act_store_print
+(const struct sieve_action *action,
+	const struct sieve_result_print_env *rpenv, bool *keep)
+{
+	struct act_store_context *ctx = (struct act_store_context *) action->context;
+	const char *mailbox;
+
+	mailbox = ( ctx == NULL ?
+		SIEVE_SCRIPT_DEFAULT_MAILBOX(rpenv->scriptenv) : ctx->mailbox );
+
+	sieve_result_action_printf(rpenv, "store message in folder: %s",
+		str_sanitize(mailbox, 128));
+
+	*keep = FALSE;
+}
+
+/* Action implementation */
+
+void sieve_act_store_get_storage_error
+(const struct sieve_action_exec_env *aenv,
+	struct act_store_transaction *trans)
+{
+	pool_t pool = sieve_result_pool(aenv->result);
+
+	trans->error = p_strdup(pool,
+		mail_storage_get_last_error(mailbox_get_storage(trans->box),
+		&trans->error_code));
+}
+
+static bool act_store_mailbox_open
+(const struct sieve_action_exec_env *aenv, const char *mailbox,
+	struct mailbox **box_r, enum mail_error *error_code_r, const char **error_r)
+{
+	struct mailbox *box;
+	struct mail_storage **storage = &(aenv->exec_status->last_storage);
+	enum mailbox_flags flags = 0;
+
+	*box_r = NULL;
+	*error_code_r = MAIL_ERROR_NONE;
+	*error_r = NULL;
+
+	if ( !uni_utf8_str_is_valid(mailbox) ) {
+		/* Just a precaution; already (supposed to be) checked at
+		 * compiletime/runtime.
+		 */
+		*error_r = t_strdup_printf("mailbox name not utf-8: %s", mailbox);
+		*error_code_r = MAIL_ERROR_PARAMS;
+		return FALSE;
+	}
+
+	if (aenv->scriptenv->mailbox_autocreate)
+		flags |= MAILBOX_FLAG_AUTO_CREATE;
+	if (aenv->scriptenv->mailbox_autosubscribe)
+		flags |= MAILBOX_FLAG_AUTO_SUBSCRIBE;
+	*box_r = box = mailbox_alloc_delivery(
+		aenv->scriptenv->user, mailbox, flags);
+	*storage = mailbox_get_storage(box);
+
+	if (mailbox_open(box) == 0)
+		return TRUE;
+	*error_r = mailbox_get_last_error(box, error_code_r);
+	return FALSE;
+}
+
+static int act_store_start
+(const struct sieve_action *action,
+	const struct sieve_action_exec_env *aenv, void **tr_context)
+{
+	struct act_store_context *ctx = (struct act_store_context *) action->context;
+	const struct sieve_script_env *senv = aenv->scriptenv;
+	struct act_store_transaction *trans;
+	struct mailbox *box = NULL;
+	pool_t pool = sieve_result_pool(aenv->result);
+	const char *error = NULL;
+	enum mail_error error_code = MAIL_ERROR_NONE;
+	bool disabled = FALSE, open_failed = FALSE;
+
+	/* If context is NULL, the store action is the result of (implicit) keep */
+	if ( ctx == NULL ) {
+		ctx = p_new(pool, struct act_store_context, 1);
+		ctx->mailbox = p_strdup(pool, SIEVE_SCRIPT_DEFAULT_MAILBOX(senv));
+	}
+
+	/* Open the requested mailbox */
+
+	/* NOTE: The caller of the sieve library is allowed to leave user set
+	 * to NULL. This implementation will then skip actually storing the message.
+	 */
+	if ( senv->user != NULL ) {
+		if ( !act_store_mailbox_open
+			(aenv, ctx->mailbox, &box, &error_code, &error) ) {
+			open_failed = TRUE;
+		}
+	} else {
+		disabled = TRUE;
+	}
+
+	/* Create transaction context */
+	trans = p_new(pool, struct act_store_transaction, 1);
+
+	trans->context = ctx;
+	trans->box = box;
+	trans->flags = 0;
+
+	trans->disabled = disabled;
+
+	if ( open_failed  ) {
+		trans->error = error;
+		trans->error_code = error_code;
+	} else {
+		trans->error_code = MAIL_ERROR_NONE;
+	}
+
+	*tr_context = (void *)trans;
+
+	switch ( trans->error_code ) {
+	case MAIL_ERROR_NONE:
+	case MAIL_ERROR_NOTFOUND:
+		return SIEVE_EXEC_OK;
+	case MAIL_ERROR_TEMP:
+		return SIEVE_EXEC_TEMP_FAILURE;
+	default:
+		break;
+	}
+	
+	return SIEVE_EXEC_FAILURE;
+}
+
+static struct mail_keywords *act_store_keywords_create
+(const struct sieve_action_exec_env *aenv, ARRAY_TYPE(const_string) *keywords,
+	struct mailbox *box)
+{
+	struct mail_keywords *box_keywords = NULL;
+
+	if ( array_is_created(keywords) && array_count(keywords) > 0 )
+	{
+		const char *const *kwds;
+
+		(void)array_append_space(keywords);
+		kwds = array_idx(keywords, 0);
+
+		if ( mailbox_keywords_create(box, kwds, &box_keywords) < 0) {
+			sieve_result_error(aenv, "invalid keywords set for stored message");
+			return NULL;
+		}
+	}
+
+	return box_keywords;
+}
+
+static int act_store_execute
+(const struct sieve_action *action,
+	const struct sieve_action_exec_env *aenv, void *tr_context)
+{
+	struct act_store_transaction *trans =
+		(struct act_store_transaction *) tr_context;
+	struct mail *mail =	( action->mail != NULL ?
+		action->mail : aenv->msgdata->mail );
+	struct mail_save_context *save_ctx;
+	struct mail_keywords *keywords = NULL;
+	bool backends_equal = FALSE;
+	int status = SIEVE_EXEC_OK;
+
+	/* Verify transaction */
+	if ( trans == NULL ) return SIEVE_EXEC_FAILURE;
+
+	/* Check whether we need to do anything */
+	if ( trans->disabled ) return SIEVE_EXEC_OK;
+
+	/* Exit early if mailbox is not available */
+	if ( trans->box == NULL )
+		return SIEVE_EXEC_FAILURE;
+
+	/* Exit early if transaction already failed */
+ 	switch ( trans->error_code ) {
+	case MAIL_ERROR_NONE:
+		break;
+	case MAIL_ERROR_TEMP:
+		return SIEVE_EXEC_TEMP_FAILURE;
+	default:
+		return SIEVE_EXEC_FAILURE;
+	}
+
+
+	/* If the message originates from the target mailbox, only update the flags
+	 * and keywords (if not read-only)
+	 */
+	if ( mailbox_backends_equal(trans->box, mail->box) ) {
+		backends_equal = TRUE;
+	} else {
+		struct mail *real_mail;
+
+		if ( mail_get_backend_mail(mail, &real_mail) < 0 )
+			return SIEVE_EXEC_FAILURE;
+		if ( real_mail != mail &&
+			mailbox_backends_equal(trans->box, real_mail->box) )
+			backends_equal = TRUE;
+	}
+	if (backends_equal) {
+		trans->redundant = TRUE;
+
+		if ( trans->flags_altered && !mailbox_is_readonly(mail->box) ) {
+			keywords = act_store_keywords_create
+				(aenv, &trans->keywords, mail->box);
+
+			if ( keywords != NULL ) {
+				mail_update_keywords(mail, MODIFY_REPLACE, keywords);
+				mailbox_keywords_unref(&keywords);
+			}
+
+			mail_update_flags(mail, MODIFY_REPLACE, trans->flags);
+		}
+
+		return SIEVE_EXEC_OK;
+
+	/* If the message is modified, only store it in the source mailbox when it is
+	 * not opened read-only. Mail structs of modified messages have their own
+	 * mailbox, unrelated to the orignal mail, so this case needs to be handled
+	 * separately.
+	 */
+	} else if ( mail != aenv->msgdata->mail
+		&& mailbox_is_readonly(aenv->msgdata->mail->box)
+		&& ( mailbox_backends_equal(trans->box, aenv->msgdata->mail->box) ) ) {
+
+		trans->redundant = TRUE;
+		return SIEVE_EXEC_OK;
+	}
+
+	/* Mark attempt to store in default mailbox */
+	if ( strcmp(trans->context->mailbox,
+		SIEVE_SCRIPT_DEFAULT_MAILBOX(aenv->scriptenv)) == 0 )
+		aenv->exec_status->tried_default_save = TRUE;
+
+	/* Mark attempt to use storage. Can only get here when all previous actions
+	 * succeeded.
+	 */
+	aenv->exec_status->last_storage = mailbox_get_storage(trans->box);
+
+	/* Start mail transaction */
+	trans->mail_trans = mailbox_transaction_begin
+		(trans->box, MAILBOX_TRANSACTION_FLAG_EXTERNAL, __func__);
+
+	/* Store the message */
+	save_ctx = mailbox_save_alloc(trans->mail_trans);
+
+	/* Apply keywords and flags that side-effects may have added */
+	if ( trans->flags_altered ) {
+		keywords = act_store_keywords_create(aenv, &trans->keywords, trans->box);
+
+		mailbox_save_set_flags(save_ctx, trans->flags, keywords);
+	} else {
+		mailbox_save_copy_flags(save_ctx, mail);
+	}
+
+	if ( mailbox_save_using_mail(&save_ctx, mail) < 0 ) {
+		sieve_act_store_get_storage_error(aenv, trans);
+		status = ( trans->error_code == MAIL_ERROR_TEMP ?
+			SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE );
+	}
+
+	/* Deallocate keywords */
+ 	if ( keywords != NULL ) {
+ 		mailbox_keywords_unref(&keywords);
+ 	}
+
+	return status;
+}
+
+static void act_store_log_status
+(struct act_store_transaction *trans, const struct sieve_action_exec_env *aenv,
+	bool rolled_back, bool status )
+{
+	const char *mailbox_name;
+
+	mailbox_name = str_sanitize(trans->context->mailbox, 128);
+
+	if ( trans->box != NULL ) {
+		const char *mailbox_vname = str_sanitize(mailbox_get_vname(trans->box), 128);
+
+		if ( strcmp(mailbox_name, mailbox_vname) != 0 )
+			mailbox_name =
+				t_strdup_printf("'%s' (%s)", mailbox_name, mailbox_vname);
+		else
+			mailbox_name = t_strdup_printf("'%s'", mailbox_name);
+	} else {
+		mailbox_name = t_strdup_printf("'%s'", mailbox_name);
+	}
+
+	/* Store disabled? */
+	if ( trans->disabled ) {
+		sieve_result_global_log
+			(aenv, "store into mailbox %s skipped", mailbox_name);
+
+	/* Store redundant? */
+	} else if ( trans->redundant ) {
+		sieve_result_global_log
+			(aenv, "left message in mailbox %s", mailbox_name);
+
+	/* Store failed? */
+	} else if ( !status ) {
+		const char *errstr;
+		enum mail_error error_code;
+
+		if ( trans->error == NULL )
+			sieve_act_store_get_storage_error(aenv, trans);
+
+		errstr = trans->error;
+		error_code = trans->error_code;
+
+		if ( error_code == MAIL_ERROR_NOQUOTA ) {
+			/* Never log quota problems as error in global log */
+			sieve_result_global_log_error(aenv,
+				"failed to store into mailbox %s: %s",
+				mailbox_name, errstr);
+		} else if ( error_code == MAIL_ERROR_NOTFOUND ||
+			error_code == MAIL_ERROR_PARAMS ||
+			error_code == MAIL_ERROR_PERM ) {
+			sieve_result_error(aenv,
+				"failed to store into mailbox %s: %s",
+				mailbox_name, errstr);
+		} else {
+			sieve_result_global_error(aenv,
+				"failed to store into mailbox %s: %s",
+				mailbox_name, errstr);
+		}
+
+	/* Store aborted? */
+	} else if ( rolled_back ) {
+		sieve_result_global_log
+			(aenv, "store into mailbox %s aborted", mailbox_name);
+
+	/* Succeeded */
+	} else {
+		sieve_result_global_log
+			(aenv, "stored mail into mailbox %s", mailbox_name);
+	}
+}
+
+static int act_store_commit
+(const struct sieve_action *action ATTR_UNUSED,
+	const struct sieve_action_exec_env *aenv, void *tr_context, bool *keep)
+{
+	struct act_store_transaction *trans =
+		(struct act_store_transaction *) tr_context;
+	bool status = TRUE;
+
+	/* Verify transaction */
+	if ( trans == NULL ) return SIEVE_EXEC_FAILURE;
+
+	/* Check whether we need to do anything */
+	if ( trans->disabled ) {
+		act_store_log_status(trans, aenv, FALSE, status);
+		*keep = FALSE;
+		if ( trans->box != NULL )
+			mailbox_free(&trans->box);
+		return SIEVE_EXEC_OK;
+	} else if ( trans->redundant ) {
+		act_store_log_status(trans, aenv, FALSE, status);
+		aenv->exec_status->keep_original = TRUE;
+		aenv->exec_status->message_saved = TRUE;
+		if ( trans->box != NULL )
+			mailbox_free(&trans->box);
+		return SIEVE_EXEC_OK;
+	}
+
+	/* Mark attempt to use storage. Can only get here when all previous actions
+	 * succeeded.
+	 */
+	aenv->exec_status->last_storage = mailbox_get_storage(trans->box);
+
+	/* Commit mailbox transaction */
+	status = ( mailbox_transaction_commit(&trans->mail_trans) == 0 );
+
+	/* Note the fact that the message was stored at least once */
+	if ( status )
+		aenv->exec_status->message_saved = TRUE;
+	else
+		aenv->exec_status->store_failed = TRUE;
+
+	/* Log our status */
+	act_store_log_status(trans, aenv, FALSE, status);
+
+	/* Cancel implicit keep if all went well */
+	*keep = !status;
+
+	/* Close mailbox */
+	if ( trans->box != NULL )
+		mailbox_free(&trans->box);
+
+	if (status)
+		return SIEVE_EXEC_OK;
+
+	return ( trans->error_code == MAIL_ERROR_TEMP ?
+			SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE );
+}
+
+static void act_store_rollback
+(const struct sieve_action *action ATTR_UNUSED,
+	const struct sieve_action_exec_env *aenv, void *tr_context, bool success)
+{
+	struct act_store_transaction *trans =
+		(struct act_store_transaction *) tr_context;
+
+	if ( trans == NULL ) return;
+
+	i_assert( trans->box != NULL );
+
+	if (!success) {
+		aenv->exec_status->last_storage = mailbox_get_storage(trans->box);
+		aenv->exec_status->store_failed = TRUE;
+	}
+
+	/* Log status */
+	act_store_log_status(trans, aenv, TRUE, success);
+
+	/* Rollback mailbox transaction */
+	if ( trans->mail_trans != NULL )
+		mailbox_transaction_rollback(&trans->mail_trans);
+
+	/* Close the mailbox */
+	mailbox_free(&trans->box);
+}
+
+/*
+ * Redirect action
+ */
+
+int sieve_act_redirect_add_to_result
+(const struct sieve_runtime_env *renv,
+	struct sieve_side_effects_list *seffects,
+	const struct smtp_address *to_address)
+{
+	struct sieve_instance *svinst = renv->svinst;
+	struct act_redirect_context *act;
+	pool_t pool;
+
+	pool = sieve_result_pool(renv->result);
+	act = p_new(pool, struct act_redirect_context, 1);
+	act->to_address = smtp_address_clone(pool, to_address);
+
+	if ( sieve_result_add_action
+		(renv, NULL, &act_redirect, seffects, (void *) act,
+			svinst->max_redirects, TRUE) < 0 )
+		return SIEVE_EXEC_FAILURE;
+
+	return SIEVE_EXEC_OK;
+}
+
+/*
+ * Action utility functions
+ */
+
+/* Checking for duplicates */
+
+bool sieve_action_duplicate_check_available
+(const struct sieve_script_env *senv)
+{
+	return ( senv->duplicate_check != NULL && senv->duplicate_mark != NULL );
+}
+
+bool sieve_action_duplicate_check
+(const struct sieve_script_env *senv, const void *id, size_t id_size)
+{
+	if ( senv->duplicate_check == NULL || senv->duplicate_mark == NULL)
+		return FALSE;
+
+	return senv->duplicate_check(senv, id, id_size);
+}
+
+void sieve_action_duplicate_mark
+(const struct sieve_script_env *senv, const void *id, size_t id_size,
+	time_t time)
+{
+	if ( senv->duplicate_check == NULL || senv->duplicate_mark == NULL)
+		return;
+
+	senv->duplicate_mark(senv, id, id_size, time);
+}
+
+void sieve_action_duplicate_flush
+(const struct sieve_script_env *senv)
+{
+	if ( senv->duplicate_flush == NULL )
+		return;
+	senv->duplicate_flush(senv);
+}
+
+
+/* Rejecting the mail */
+
+static int sieve_action_do_reject_mail
+(const struct sieve_action_exec_env *aenv,
+	const struct smtp_address *recipient, const char *reason)
+{
+	struct sieve_instance *svinst = aenv->svinst;
+	const struct sieve_script_env *senv = aenv->scriptenv;
+	const struct sieve_message_data *msgdata = aenv->msgdata;
+	const struct smtp_address *sender, *orig_recipient;
+	struct istream *input;
+	struct ostream *output;
+	struct sieve_smtp_context *sctx;
+	const char *new_msgid, *boundary, *error;
+  string_t *hdr;
+	int ret;
+
+	sender = sieve_message_get_sender(aenv->msgctx);
+	orig_recipient = msgdata->envelope.rcpt_params->orcpt.addr;
+
+	sctx = sieve_smtp_start_single(senv, sender, NULL, &output);
+
+	/* Just to be sure */
+	if ( sctx == NULL ) {
+		sieve_result_global_warning
+			(aenv, "reject action has no means to send mail");
+		return SIEVE_EXEC_OK;
+	}
+
+	new_msgid = sieve_message_get_new_id(svinst);
+	boundary = t_strdup_printf("%s/%s", my_pid, svinst->hostname);
+
+  hdr = t_str_new(512);
+	rfc2822_header_write(hdr, "X-Sieve", SIEVE_IMPLEMENTATION);
+	rfc2822_header_write(hdr, "Message-ID", new_msgid);
+	rfc2822_header_write(hdr, "Date", message_date_create(ioloop_time));
+	rfc2822_header_write(hdr, "From", sieve_get_postmaster_address(senv));
+	rfc2822_header_printf(hdr, "To", "<%s>",
+		smtp_address_encode(sender));
+	rfc2822_header_write(hdr, "Subject", "Automatically rejected mail");
+	rfc2822_header_write(hdr, "Auto-Submitted", "auto-replied (rejected)");
+	rfc2822_header_write(hdr, "Precedence", "bulk");
+
+	rfc2822_header_write(hdr, "MIME-Version", "1.0");
+	rfc2822_header_printf(hdr, "Content-Type",
+		"multipart/report; report-type=disposition-notification;\r\n"
+		"boundary=\"%s\"", boundary);
+
+	str_append(hdr, "\r\nThis is a MIME-encapsulated message\r\n\r\n");
+
+	/* Human readable status report */
+	str_printfa(hdr, "--%s\r\n", boundary);
+	rfc2822_header_write(hdr, "Content-Type", "text/plain; charset=utf-8");
+	rfc2822_header_write(hdr, "Content-Disposition", "inline");
+	rfc2822_header_write(hdr, "Content-Transfer-Encoding", "8bit");
+
+	str_printfa(hdr, "\r\nYour message to <%s> was automatically rejected:\r\n"
+		"%s\r\n", smtp_address_encode(recipient), reason);
+
+	/* MDN status report */
+	str_printfa(hdr, "--%s\r\n", boundary);
+	rfc2822_header_write(hdr, "Content-Type", "message/disposition-notification");
+	str_append(hdr, "\r\n");
+	rfc2822_header_write(hdr, "Reporting-UA: %s; Dovecot Mail Delivery Agent",
+		svinst->hostname);	
+	if ( orig_recipient != NULL ) {
+		rfc2822_header_printf
+			(hdr, "Original-Recipient", "rfc822; %s",
+				smtp_address_encode(orig_recipient));
+	}
+	rfc2822_header_printf(hdr, "Final-Recipient", "rfc822; %s",
+		smtp_address_encode(recipient));
+
+	if ( msgdata->id != NULL )
+		rfc2822_header_write(hdr, "Original-Message-ID", msgdata->id);
+	rfc2822_header_write(hdr, "Disposition",
+		"automatic-action/MDN-sent-automatically; deleted");
+	str_append(hdr, "\r\n");
+
+	/* original message's headers */
+	str_printfa(hdr, "--%s\r\n", boundary);
+	rfc2822_header_write(hdr, "Content-Type", "message/rfc822");
+	str_append(hdr, "\r\n");
+	o_stream_nsend(output, str_data(hdr), str_len(hdr));
+
+	if (mail_get_hdr_stream(msgdata->mail, NULL, &input) == 0) {
+    /* Note: If you add more headers, they need to be sorted.
+       We'll drop Content-Type because we're not including the message
+       body, and having a multipart Content-Type may confuse some
+       MIME parsers when they don't see the message boundaries. */
+    static const char *const exclude_headers[] = {
+	    "Content-Type"
+    };
+
+    input = i_stream_create_header_filter(input,
+    		HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR |
+		HEADER_FILTER_HIDE_BODY, exclude_headers,
+		N_ELEMENTS(exclude_headers),
+		*null_header_filter_callback, (void *)NULL);
+
+    o_stream_nsend_istream(output, input);
+    i_stream_unref(&input);
+  }
+
+  str_truncate(hdr, 0);
+  str_printfa(hdr, "\r\n\r\n--%s--\r\n", boundary);
+  o_stream_nsend(output, str_data(hdr), str_len(hdr));
+
+	if ( (ret=sieve_smtp_finish(sctx, &error)) <= 0 ) {
+		if ( ret < 0 ) {
+			sieve_result_global_error(aenv,
+				"failed to send rejection message to <%s>: %s "
+				"(temporary failure)",
+				smtp_address_encode(sender),
+				str_sanitize(error, 512));
+		} else {
+			sieve_result_global_log_error(aenv,
+				"failed to send rejection message to <%s>: %s "
+				"(permanent failure)",
+				smtp_address_encode(sender),
+				str_sanitize(error, 512));
+		}
+		return SIEVE_EXEC_FAILURE;
+	}
+
+	return SIEVE_EXEC_OK;
+}
+
+int sieve_action_reject_mail
+(const struct sieve_action_exec_env *aenv,
+	const struct smtp_address *recipient, const char *reason)
+{
+	const struct sieve_script_env *senv = aenv->scriptenv;
+	int result;
+
+	T_BEGIN {
+		if ( senv->reject_mail != NULL ) {
+			result =
+				( senv->reject_mail(senv, recipient, reason) >= 0 ?
+					SIEVE_EXEC_OK : SIEVE_EXEC_FAILURE );
+		} else {
+			result = sieve_action_do_reject_mail(aenv, recipient, reason);
+		}
+	} T_END;
+
+	return result;
+}
+
+/*
+ * Mailbox
+ */
+
+bool sieve_mailbox_check_name(const char *mailbox, const char **error_r)
+{
+	if ( !uni_utf8_str_is_valid(mailbox) ) {
+		*error_r = "mailbox is utf-8";
+		return FALSE;
+	}
+	return TRUE;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-actions.h
@@ -0,0 +1,293 @@
+#ifndef SIEVE_ACTIONS_H
+#define SIEVE_ACTIONS_H
+
+#include "lib.h"
+#include "mail-types.h"
+#include "mail-error.h"
+
+#include "sieve-common.h"
+#include "sieve-objects.h"
+#include "sieve-extensions.h"
+
+/*
+ * Action execution environment
+ */
+
+struct sieve_action_exec_env {
+	struct sieve_instance *svinst;
+
+	struct sieve_result *result;
+	enum sieve_execute_flags flags;
+	struct sieve_error_handler *ehandler;
+
+	const struct sieve_message_data *msgdata;
+	struct sieve_message_context *msgctx;
+	const struct sieve_script_env *scriptenv;
+	struct sieve_exec_status *exec_status;
+};
+
+const char *sieve_action_get_location(const struct sieve_action_exec_env *aenv);
+
+/*
+ * Action flags
+ */
+
+enum sieve_action_flags {
+	SIEVE_ACTFLAG_TRIES_DELIVER = (1 << 0),
+	SIEVE_ACTFLAG_SENDS_RESPONSE = (1 << 1),
+	SIEVE_ACTFLAG_MAIL_STORAGE = (1 << 2)
+};
+
+/*
+ * Action definition
+ */
+
+struct sieve_action_def {
+	const char *name;
+	unsigned int flags;
+
+	bool (*equals)
+		(const struct sieve_script_env *senv, const struct sieve_action *act1,
+			const struct sieve_action *act2);
+
+	/* Result verification */
+
+	int (*check_duplicate)
+		(const struct sieve_runtime_env *renv,
+			const struct sieve_action *act,
+			const struct sieve_action *act_other);
+	int (*check_conflict)
+		(const struct sieve_runtime_env *renv,
+			const struct sieve_action *act,
+			const struct sieve_action *act_other);
+
+	/* Result printing */
+
+	void (*print)
+		(const struct sieve_action *action,
+			const struct sieve_result_print_env *penv, bool *keep);
+
+	/* Result execution */
+
+	int (*start)
+		(const struct sieve_action *action,
+			const struct sieve_action_exec_env *aenv, void **tr_context);
+	int (*execute)
+		(const struct sieve_action *action,
+			const struct sieve_action_exec_env *aenv, void *tr_context);
+	int (*commit)
+		(const struct sieve_action *action,
+			const struct sieve_action_exec_env *aenv, void *tr_context,
+			bool *keep);
+	void (*rollback)
+		(const struct sieve_action *action,
+			const struct sieve_action_exec_env *aenv, void *tr_context,
+			bool success);
+	void (*finish)
+		(const struct sieve_action *action,
+			const struct sieve_action_exec_env *aenv, void *tr_context,
+			int status);
+};
+
+/*
+ * Action instance
+ */
+
+struct sieve_action {
+	const struct sieve_action_def *def;
+	const struct sieve_extension *ext;
+
+	const char *location;
+	void *context;
+	struct mail *mail;
+	bool executed;
+};
+
+#define sieve_action_is(act, definition) \
+	( (act)->def == &(definition) )
+
+/*
+ * Action side effects
+ */
+
+/* Side effect object */
+
+struct sieve_side_effect_def {
+	struct sieve_object_def obj_def;
+
+	/* The action it is supposed to link to */
+
+	const struct sieve_action_def *to_action;
+
+	/* Context coding */
+
+	bool (*dump_context)
+		(const struct sieve_side_effect *seffect,
+			const struct sieve_dumptime_env *renv, sieve_size_t *address);
+	int (*read_context)
+		(const struct sieve_side_effect *seffect,
+			const struct sieve_runtime_env *renv, sieve_size_t *address,
+			void **se_context);
+
+	/* Result verification */
+
+	int (*merge)
+		(const struct sieve_runtime_env *renv, const struct sieve_action *action,
+			const struct sieve_side_effect *old_seffect,
+			const struct sieve_side_effect *new_seffect, void **old_context);
+
+	/* Result printing */
+
+	void (*print)
+		(const struct sieve_side_effect *seffect, const struct sieve_action *action,
+			const struct sieve_result_print_env *penv, bool *keep);
+
+	/* Result execution */
+
+	int (*pre_execute)
+		(const struct sieve_side_effect *seffect, const struct sieve_action *action,
+			const struct sieve_action_exec_env *aenv, void **context,
+			void *tr_context);
+	int (*post_execute)
+		(const struct sieve_side_effect *seffect, const struct sieve_action *action,
+			const struct sieve_action_exec_env *aenv, void *tr_context);
+	void (*post_commit)
+		(const struct sieve_side_effect *seffect, const struct sieve_action *action,
+			const struct sieve_action_exec_env *aenv, void *tr_context, bool *keep);
+	void (*rollback)
+		(const struct sieve_side_effect *seffect, const struct sieve_action *action,
+			const struct sieve_action_exec_env *aenv, void *tr_context, bool success);
+};
+
+struct sieve_side_effect {
+	struct sieve_object object;
+
+	const struct sieve_side_effect_def *def;
+
+	void *context;
+};
+
+/*
+ * Side effect operand
+ */
+
+#define SIEVE_EXT_DEFINE_SIDE_EFFECT(SEF) SIEVE_EXT_DEFINE_OBJECT(SEF)
+#define SIEVE_EXT_DEFINE_SIDE_EFFECTS(SEFS) SIEVE_EXT_DEFINE_OBJECTS(SEFS)
+
+#define SIEVE_OPT_SIDE_EFFECT (-1)
+
+extern const struct sieve_operand_class sieve_side_effect_operand_class;
+
+static inline void sieve_opr_side_effect_emit
+(struct sieve_binary_block *sblock, const struct sieve_extension *ext,
+	const struct sieve_side_effect_def *seff)
+{
+	sieve_opr_object_emit(sblock, ext, &seff->obj_def);
+}
+
+bool sieve_opr_side_effect_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+int sieve_opr_side_effect_read
+	(const struct sieve_runtime_env *renv, sieve_size_t *address,
+		struct sieve_side_effect *seffect);
+
+/*
+ * Optional operands
+ */
+
+int sieve_action_opr_optional_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+		signed int *opt_code);
+
+int sieve_action_opr_optional_read
+	(const struct sieve_runtime_env *renv, sieve_size_t *address,
+		signed int *opt_code, int *exec_status,
+		struct sieve_side_effects_list **list);
+
+/*
+ * Core actions
+ */
+
+extern const struct sieve_action_def act_redirect;
+extern const struct sieve_action_def act_store;
+extern const struct sieve_action_def act_discard;
+
+/*
+ * Store action
+ */
+
+struct act_store_context {
+	/* Folder name represented in utf-8 */
+	const char *mailbox;
+};
+
+struct act_store_transaction {
+	struct act_store_context *context;
+	struct mailbox *box;
+	struct mailbox_transaction_context *mail_trans;
+
+	const char *error;
+	enum mail_error error_code;
+
+	enum mail_flags flags;
+	ARRAY_TYPE(const_string) keywords;
+
+	bool flags_altered:1;
+	bool disabled:1;
+	bool redundant:1;
+};
+
+int sieve_act_store_add_to_result
+	(const struct sieve_runtime_env *renv,
+		struct sieve_side_effects_list *seffects, const char *folder);
+
+void sieve_act_store_add_flags
+	(const struct sieve_action_exec_env *aenv, void *tr_context,
+		const char *const *keywords, enum mail_flags flags);
+
+void sieve_act_store_get_storage_error
+	(const struct sieve_action_exec_env *aenv, struct act_store_transaction *trans);
+
+/*
+ * Redirect action
+ */
+
+struct act_redirect_context {
+	const struct smtp_address *to_address;
+};
+
+int sieve_act_redirect_add_to_result
+(const struct sieve_runtime_env *renv,
+	struct sieve_side_effects_list *seffects,
+	const struct smtp_address *to_address);
+
+/*
+ * Action utility functions
+ */
+
+/* Checking for duplicates */
+
+bool sieve_action_duplicate_check_available
+	(const struct sieve_script_env *senv);
+bool sieve_action_duplicate_check
+	(const struct sieve_script_env *senv, const void *id, size_t id_size);
+void sieve_action_duplicate_mark
+	(const struct sieve_script_env *senv, const void *id, size_t id_size,
+		time_t time);
+void sieve_action_duplicate_flush
+	(const struct sieve_script_env *senv);
+
+/* Rejecting mail */
+
+int sieve_action_reject_mail
+(const struct sieve_action_exec_env *aenv,
+	const struct smtp_address *recipient, const char *reason);
+
+/*
+ * Mailbox
+ */
+
+// FIXME: move this to a more appropriate location
+bool sieve_mailbox_check_name(const char *mailbox, const char **error_r);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-address-parts.c
@@ -0,0 +1,495 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "compat.h"
+#include "mempool.h"
+#include "hash.h"
+#include "array.h"
+#include "str-sanitize.h"
+
+#include "sieve-extensions.h"
+#include "sieve-code.h"
+#include "sieve-address.h"
+#include "sieve-commands.h"
+#include "sieve-binary.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+#include "sieve-address-parts.h"
+
+#include <string.h>
+
+/*
+ * Default address parts
+ */
+
+const struct sieve_address_part_def *sieve_core_address_parts[] = {
+	&all_address_part, &local_address_part, &domain_address_part
+};
+
+const unsigned int sieve_core_address_parts_count =
+	N_ELEMENTS(sieve_core_address_parts);
+
+/*
+ * Address-part 'extension'
+ */
+
+static bool addrp_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def address_part_extension = {
+	.name = "@address-parts",
+	.validator_load = addrp_validator_load
+};
+
+/*
+ * Validator context:
+ *   name-based address-part registry.
+ */
+
+static struct sieve_validator_object_registry *_get_object_registry
+(struct sieve_validator *valdtr)
+{
+	struct sieve_instance *svinst;
+	const struct sieve_extension *adrp_ext;
+
+	svinst = sieve_validator_svinst(valdtr);
+	adrp_ext = sieve_get_address_part_extension(svinst);
+	return sieve_validator_object_registry_get(valdtr, adrp_ext);
+}
+
+void sieve_address_part_register
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	const struct sieve_address_part_def *addrp_def)
+{
+	struct sieve_validator_object_registry *regs = _get_object_registry(valdtr);
+
+	sieve_validator_object_registry_add(regs, ext, &addrp_def->obj_def);
+}
+
+static bool sieve_address_part_exists
+(struct sieve_validator *valdtr, const char *identifier)
+{
+	struct sieve_validator_object_registry *regs = _get_object_registry(valdtr);
+
+	return sieve_validator_object_registry_find(regs, identifier, NULL);
+}
+
+static const struct sieve_address_part *sieve_address_part_create_instance
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+	const char *identifier)
+{
+	struct sieve_validator_object_registry *regs = _get_object_registry(valdtr);
+	struct sieve_object object;
+	struct sieve_address_part *addrp;
+
+	if ( !sieve_validator_object_registry_find(regs, identifier, &object) )
+		return NULL;
+
+	addrp = p_new(sieve_command_pool(cmd), struct sieve_address_part, 1);
+	addrp->object = object;
+	addrp->def = (const struct sieve_address_part_def *) object.def;
+
+  return addrp;
+}
+
+static bool addrp_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	struct sieve_validator_object_registry *regs =
+		sieve_validator_object_registry_init(valdtr, ext);
+	unsigned int i;
+
+	/* Register core address-parts */
+	for ( i = 0; i < sieve_core_address_parts_count; i++ ) {
+		sieve_validator_object_registry_add
+			(regs, NULL, &(sieve_core_address_parts[i]->obj_def));
+	}
+
+	return TRUE;
+}
+
+void sieve_address_parts_link_tags
+(struct sieve_validator *valdtr, struct sieve_command_registration *cmd_reg,
+	int id_code)
+{
+	struct sieve_instance *svinst;
+	const struct sieve_extension *adrp_ext;
+
+	svinst = sieve_validator_svinst(valdtr);
+	adrp_ext = sieve_get_address_part_extension(svinst);
+
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, adrp_ext, &address_part_tag, id_code);
+}
+
+/*
+ * Address-part tagged argument
+ */
+
+/* Forward declarations */
+
+static bool tag_address_part_is_instance_of
+	(struct sieve_validator *valdtr, struct sieve_command *cmd,
+		const struct sieve_extension *ext, const char *identifier, void **data);
+static bool tag_address_part_validate
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+static bool tag_address_part_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+		struct sieve_command *cmd);
+
+/* Argument object */
+
+const struct sieve_argument_def address_part_tag = {
+	.identifier = "ADDRESS-PART",
+	.is_instance_of = tag_address_part_is_instance_of,
+	.validate = tag_address_part_validate,
+	.generate = tag_address_part_generate
+};
+
+/* Argument implementation */
+
+static bool tag_address_part_is_instance_of
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+	const struct sieve_extension *ext ATTR_UNUSED, const char *identifier,
+	void **data)
+{
+	const struct sieve_address_part *addrp;
+
+	if ( data == NULL )
+		return sieve_address_part_exists(valdtr, identifier);
+
+	if ( (addrp=sieve_address_part_create_instance
+		(valdtr, cmd, identifier)) == NULL )
+		return FALSE;
+
+	*data = (void *) addrp;
+	return TRUE;
+}
+
+static bool tag_address_part_validate
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd ATTR_UNUSED)
+{
+	/* NOTE: Currenly trivial, but might need to allow for further validation for
+	 * future extensions.
+	 */
+
+	/* Syntax:
+	 *   ":localpart" / ":domain" / ":all" (subject to extension)
+   	 */
+
+	/* Skip tag */
+	*arg = sieve_ast_argument_next(*arg);
+
+	return TRUE;
+}
+
+static bool tag_address_part_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+	struct sieve_command *cmd ATTR_UNUSED)
+{
+	struct sieve_address_part *addrp =
+		(struct sieve_address_part *) arg->argument->data;
+
+	sieve_opr_address_part_emit(cgenv->sblock, addrp);
+
+	return TRUE;
+}
+
+/*
+ * Address-part operand
+ */
+
+const struct sieve_operand_class sieve_address_part_operand_class =
+	{ "address part" };
+
+static const struct sieve_extension_objects core_address_parts =
+	SIEVE_EXT_DEFINE_MATCH_TYPES(sieve_core_address_parts);
+
+const struct sieve_operand_def address_part_operand = {
+	.name = "address-part",
+	.code = SIEVE_OPERAND_ADDRESS_PART,
+	.class = &sieve_address_part_operand_class,
+	.interface = &core_address_parts
+};
+
+/*
+ * Address-part string list
+ */
+
+static int sieve_address_part_stringlist_next_item
+	(struct sieve_stringlist *_strlist, string_t **str_r);
+static void sieve_address_part_stringlist_reset
+	(struct sieve_stringlist *_strlist);
+static int sieve_address_part_stringlist_get_length
+	(struct sieve_stringlist *_strlist);
+static void sieve_address_part_stringlist_set_trace
+(struct sieve_stringlist *_strlist, bool trace);
+
+struct sieve_address_part_stringlist {
+	struct sieve_stringlist strlist;
+
+	const struct sieve_address_part *addrp;
+	struct sieve_address_list *addresses;
+};
+
+struct sieve_stringlist *sieve_address_part_stringlist_create
+(const struct sieve_runtime_env *renv, const struct sieve_address_part *addrp,
+	struct sieve_address_list *addresses)
+{
+	struct sieve_address_part_stringlist *strlist;
+
+	strlist = t_new(struct sieve_address_part_stringlist, 1);
+	strlist->strlist.runenv = renv;
+	strlist->strlist.next_item = sieve_address_part_stringlist_next_item;
+	strlist->strlist.reset = sieve_address_part_stringlist_reset;
+	strlist->strlist.get_length = sieve_address_part_stringlist_get_length;
+	strlist->strlist.set_trace = sieve_address_part_stringlist_set_trace;
+
+	strlist->addrp = addrp;
+	strlist->addresses = addresses;
+
+	return &strlist->strlist;
+}
+
+static int sieve_address_part_stringlist_next_item
+	(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+	struct sieve_address_part_stringlist *strlist =
+		(struct sieve_address_part_stringlist *)_strlist;
+	struct smtp_address item;
+	string_t *item_unparsed;
+	int ret;
+
+	*str_r = NULL;
+
+	while ( *str_r == NULL ) {
+		if ( (ret=sieve_address_list_next_item
+			(strlist->addresses, &item, &item_unparsed)) <= 0 )
+			return ret;
+
+		if ( item.localpart == NULL ) {
+			if ( item_unparsed != NULL ) {
+				if ( _strlist->trace ) {
+					sieve_runtime_trace(_strlist->runenv, 0,
+						"extracting `%s' part from non-address value `%s'",
+						sieve_address_part_name(strlist->addrp),
+						str_sanitize(str_c(item_unparsed), 80));
+				}
+
+				if ( str_len(item_unparsed) == 0 ||
+					sieve_address_part_is(strlist->addrp, all_address_part) )
+					*str_r = item_unparsed;
+			}
+		} else {
+			const struct sieve_address_part *addrp = strlist->addrp;
+			const char *part = NULL;
+
+			if ( _strlist->trace ) {
+				sieve_runtime_trace(_strlist->runenv, 0,
+					"extracting `%s' part from address %s",
+					sieve_address_part_name(strlist->addrp),
+					smtp_address_encode_path(&item));
+			}
+
+			if ( addrp->def != NULL && addrp->def->extract_from != NULL )
+				part = addrp->def->extract_from(addrp, &item);
+
+			if ( part != NULL )
+				*str_r = t_str_new_const(part, strlen(part));
+		}
+	}
+
+	return 1;
+}
+
+static void sieve_address_part_stringlist_reset
+	(struct sieve_stringlist *_strlist)
+{
+	struct sieve_address_part_stringlist *strlist =
+		(struct sieve_address_part_stringlist *)_strlist;
+
+	sieve_address_list_reset(strlist->addresses);
+}
+
+static int sieve_address_part_stringlist_get_length
+	(struct sieve_stringlist *_strlist)
+{
+	struct sieve_address_part_stringlist *strlist =
+		(struct sieve_address_part_stringlist *)_strlist;
+
+	return sieve_address_list_get_length(strlist->addresses);
+}
+
+static void sieve_address_part_stringlist_set_trace
+(struct sieve_stringlist *_strlist, bool trace)
+{
+	struct sieve_address_part_stringlist *strlist =
+		(struct sieve_address_part_stringlist *)_strlist;
+
+	sieve_address_list_set_trace(strlist->addresses, trace);
+}
+
+/*
+ * Default ADDRESS-PART, MATCH-TYPE, COMPARATOR access
+ */
+
+int sieve_addrmatch_opr_optional_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+	signed int *opt_code)
+{
+	signed int _opt_code = 0;
+	bool final = FALSE, opok = TRUE;
+
+	if ( opt_code == NULL ) {
+		opt_code = &_opt_code;
+		final = TRUE;
+	}
+
+	while ( opok ) {
+		int opt;
+
+		if ( (opt=sieve_opr_optional_dump(denv, address, opt_code)) <= 0 )
+			return opt;
+
+		switch ( *opt_code ) {
+		case SIEVE_MATCH_OPT_COMPARATOR:
+			opok = sieve_opr_comparator_dump(denv, address);
+			break;
+		case SIEVE_MATCH_OPT_MATCH_TYPE:
+			opok = sieve_opr_match_type_dump(denv, address);
+			break;
+		case SIEVE_AM_OPT_ADDRESS_PART:
+			opok = sieve_opr_address_part_dump(denv, address);
+			break;
+		default:
+			return ( final ? -1 : 1 );
+		}
+	}
+
+	return -1;
+}
+
+int sieve_addrmatch_opr_optional_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+	signed int *opt_code, int *exec_status, struct sieve_address_part *addrp,
+	struct sieve_match_type *mtch, struct sieve_comparator *cmp)
+{
+	signed int _opt_code = 0;
+	bool final = FALSE;
+	int status = SIEVE_EXEC_OK;
+
+	if ( opt_code == NULL ) {
+		opt_code = &_opt_code;
+		final = TRUE;
+	}
+
+	if ( exec_status != NULL )
+		*exec_status = SIEVE_EXEC_OK;
+
+	while ( status == SIEVE_EXEC_OK ) {
+		int opt;
+
+		if ( (opt=sieve_opr_optional_read(renv, address, opt_code)) <= 0 ){
+			if ( opt < 0 && exec_status != NULL )
+				*exec_status = SIEVE_EXEC_BIN_CORRUPT;
+			return opt;
+		}
+
+		switch ( *opt_code ) {
+		case SIEVE_MATCH_OPT_COMPARATOR:
+			if (cmp == NULL) {
+				sieve_runtime_trace_error(renv, "unexpected comparator operand");
+				if ( exec_status != NULL )
+					*exec_status = SIEVE_EXEC_BIN_CORRUPT;
+				return -1;
+			}
+			status = sieve_opr_comparator_read(renv, address, cmp);
+			break;
+		case SIEVE_MATCH_OPT_MATCH_TYPE:
+			if (mtch == NULL) {
+				sieve_runtime_trace_error(renv, "unexpected match-type operand");
+				if ( exec_status != NULL )
+					*exec_status = SIEVE_EXEC_BIN_CORRUPT;
+				return -1;
+			}
+			status = sieve_opr_match_type_read(renv, address, mtch);
+			break;
+		case SIEVE_AM_OPT_ADDRESS_PART:
+			if (addrp == NULL) {
+				sieve_runtime_trace_error(renv, "unexpected address-part operand");
+				if ( exec_status != NULL )
+					*exec_status = SIEVE_EXEC_BIN_CORRUPT;
+				return -1;
+			}
+			status = sieve_opr_address_part_read(renv, address, addrp);
+			break;
+		default:
+			if ( final ) {
+				sieve_runtime_trace_error(renv, "invalid optional operand");
+				if ( exec_status != NULL )
+					*exec_status = SIEVE_EXEC_BIN_CORRUPT;
+				return -1;
+			}
+			return 1;
+		}
+	}
+
+	if ( exec_status != NULL )
+		*exec_status = status;
+	return -1;
+}
+
+/*
+ * Core address-part modifiers
+ */
+
+static const char *addrp_all_extract_from
+(const struct sieve_address_part *addrp ATTR_UNUSED,
+	const struct smtp_address *address)
+{
+	if ( address->localpart == NULL )
+		return NULL;
+	return smtp_address_encode(address);
+}
+
+static const char *addrp_domain_extract_from
+(const struct sieve_address_part *addrp ATTR_UNUSED,
+	const struct smtp_address *address)
+{
+	return address->domain;
+}
+
+static const char *addrp_localpart_extract_from
+(const struct sieve_address_part *addrp ATTR_UNUSED,
+	const struct smtp_address *address)
+{
+	return address->localpart;
+}
+
+const struct sieve_address_part_def all_address_part = {
+	SIEVE_OBJECT("all",
+		&address_part_operand, SIEVE_ADDRESS_PART_ALL),
+	.extract_from = addrp_all_extract_from
+};
+
+const struct sieve_address_part_def local_address_part = {
+	SIEVE_OBJECT("localpart",
+		&address_part_operand, SIEVE_ADDRESS_PART_LOCAL),
+	.extract_from = addrp_localpart_extract_from
+};
+
+const struct sieve_address_part_def domain_address_part = {
+	SIEVE_OBJECT("domain",
+		&address_part_operand,	SIEVE_ADDRESS_PART_DOMAIN),
+	.extract_from = addrp_domain_extract_from
+};
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-address-parts.h
@@ -0,0 +1,135 @@
+#ifndef SIEVE_ADDRESS_PARTS_H
+#define SIEVE_ADDRESS_PARTS_H
+
+#include "message-address.h"
+
+#include "sieve-common.h"
+#include "sieve-match.h"
+#include "sieve-extensions.h"
+#include "sieve-objects.h"
+
+/*
+ * Address part definition
+ */
+
+struct sieve_address_part_def {
+	struct sieve_object_def obj_def;
+
+	const char *(*extract_from)
+		(const struct sieve_address_part *addrp,
+			const struct smtp_address *address);
+};
+
+/*
+ * Address part instance
+ */
+
+struct sieve_address_part {
+	struct sieve_object object;
+
+	const struct sieve_address_part_def *def;
+};
+
+#define SIEVE_ADDRESS_PART_DEFAULT(definition) \
+	{ SIEVE_OBJECT_DEFAULT(definition), &(definition) };
+
+#define sieve_address_part_name(addrp) \
+	( (addrp)->object.def->identifier )
+#define sieve_address_part_is(addrp, definition) \
+	( (addrp)->def == &(definition) )
+
+/*
+ * Core address parts
+ */
+
+enum sieve_address_part_code {
+	SIEVE_ADDRESS_PART_ALL,
+	SIEVE_ADDRESS_PART_LOCAL,
+	SIEVE_ADDRESS_PART_DOMAIN,
+	SIEVE_ADDRESS_PART_CUSTOM
+};
+
+extern const struct sieve_address_part_def all_address_part;
+extern const struct sieve_address_part_def local_address_part;
+extern const struct sieve_address_part_def domain_address_part;
+
+/*
+ * Address part tagged argument
+ */
+
+extern const struct sieve_argument_def address_part_tag;
+
+void sieve_address_parts_link_tags
+	(struct sieve_validator *valdtr, struct sieve_command_registration *cmd_reg,
+		int id_code);
+
+/*
+ * Address part registry
+ */
+
+void sieve_address_part_register
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		const struct sieve_address_part_def *addrp);
+
+/*
+ * Address part operand
+ */
+
+extern const struct sieve_operand_def address_part_operand;
+extern const struct sieve_operand_class sieve_address_part_operand_class;
+
+#define SIEVE_EXT_DEFINE_ADDRESS_PART(OP) SIEVE_EXT_DEFINE_OBJECT(OP)
+#define SIEVE_EXT_DEFINE_ADDRESS_PARTS(OPS) SIEVE_EXT_DEFINE_OBJECTS(OPS)
+
+static inline void sieve_opr_address_part_emit
+(struct sieve_binary_block *sblock, const struct sieve_address_part *addrp)
+{
+	sieve_opr_object_emit(sblock, addrp->object.ext, addrp->object.def);
+}
+
+static inline bool sieve_opr_address_part_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	return sieve_opr_object_dump
+		(denv, &sieve_address_part_operand_class, address, NULL);
+}
+
+static inline int sieve_opr_address_part_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+	struct sieve_address_part *addrp)
+{
+	if ( !sieve_opr_object_read
+		(renv, &sieve_address_part_operand_class, address, &addrp->object) )
+		return SIEVE_EXEC_BIN_CORRUPT;
+
+	addrp->def = (const struct sieve_address_part_def *) addrp->object.def;
+	return SIEVE_EXEC_OK;
+}
+
+/*
+ * Address-part string list
+ */
+
+struct sieve_stringlist *sieve_address_part_stringlist_create
+	(const struct sieve_runtime_env *renv, const struct sieve_address_part *addrp,
+		struct sieve_address_list *addresses);
+
+/*
+ * Match utility
+ */
+
+enum sieve_addrmatch_opt_operand {
+	SIEVE_AM_OPT_ADDRESS_PART = SIEVE_MATCH_OPT_LAST,
+	SIEVE_AM_OPT_LAST
+};
+
+int sieve_addrmatch_opr_optional_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+		signed int *opt_code);
+
+int sieve_addrmatch_opr_optional_read
+	(const struct sieve_runtime_env *renv, sieve_size_t *address,
+		signed int *opt_code, int *exec_status, struct sieve_address_part *addrp,
+		struct sieve_match_type *mtch, struct sieve_comparator *cmp);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-address-source.c
@@ -0,0 +1,120 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-settings.h"
+#include "sieve-address.h"
+#include "sieve-message.h"
+
+#include "sieve-address-source.h"
+
+bool sieve_address_source_parse
+(pool_t pool, const char *value,
+	struct sieve_address_source *asrc)
+{
+	struct smtp_address *address;
+	const char *error;
+	size_t val_len;
+
+	i_zero(asrc);
+
+	value = t_str_trim(value, "\t ");
+	value = t_str_lcase(value);
+	val_len = strlen(value);
+	if ( val_len > 0 ) {
+		if ( strcmp(value, "default") == 0 ) {
+			asrc->type = SIEVE_ADDRESS_SOURCE_DEFAULT;
+		} else if ( strcmp(value, "sender") == 0 ) {
+			asrc->type = SIEVE_ADDRESS_SOURCE_SENDER;
+		} else if ( strcmp(value, "recipient") == 0 ) {
+			asrc->type = SIEVE_ADDRESS_SOURCE_RECIPIENT;
+		} else if ( strcmp(value, "orig_recipient") == 0 ) {
+			asrc->type = SIEVE_ADDRESS_SOURCE_ORIG_RECIPIENT;
+		} else if ( strcmp(value, "user_email") == 0 ) {
+			asrc->type = SIEVE_ADDRESS_SOURCE_USER_EMAIL;
+		} else if ( strcmp(value, "postmaster") == 0 ) {
+			asrc->type = SIEVE_ADDRESS_SOURCE_POSTMASTER;
+		} else if ( smtp_address_parse_path(pool_datastack_create(), value,
+			SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY, &address, &error) >= 0 ) {
+			asrc->type = SIEVE_ADDRESS_SOURCE_EXPLICIT;
+			asrc->address = smtp_address_clone(pool, address);
+		} else {
+			return FALSE;
+		}
+	}
+	return TRUE;
+}
+
+bool sieve_address_source_parse_from_setting
+(struct sieve_instance *svinst, pool_t pool,
+	const char *setting, struct sieve_address_source *asrc)
+{
+	const char *value;
+
+	value = sieve_setting_get(svinst, setting);
+	if ( value == NULL )
+		return FALSE;
+
+	if ( !sieve_address_source_parse(pool, value, asrc) ) {
+		sieve_sys_warning(svinst,
+			"Invalid value for setting '%s': '%s'",
+			setting, value);
+		return FALSE;
+	}
+	return TRUE;
+}
+
+int sieve_address_source_get_address
+(struct sieve_address_source *asrc,
+	struct sieve_instance *svinst,
+	const struct sieve_script_env *senv,
+	struct sieve_message_context *msgctx,
+	enum sieve_execute_flags flags,
+	const struct smtp_address **addr_r)
+{
+	enum sieve_address_source_type type = asrc->type;
+
+	if ( type == SIEVE_ADDRESS_SOURCE_USER_EMAIL &&
+		svinst->user_email == NULL )
+		type = SIEVE_ADDRESS_SOURCE_RECIPIENT;
+
+	if ( (flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) != 0 ) {
+		switch ( type ) {
+		case SIEVE_ADDRESS_SOURCE_SENDER:
+		case SIEVE_ADDRESS_SOURCE_RECIPIENT:
+		case SIEVE_ADDRESS_SOURCE_ORIG_RECIPIENT:
+			/* We have no envelope */
+			type = SIEVE_ADDRESS_SOURCE_DEFAULT;
+			break;
+		default:
+			break;
+		}
+	}
+
+	switch ( type ) {
+	case SIEVE_ADDRESS_SOURCE_SENDER:
+		*addr_r = sieve_message_get_sender(msgctx);
+		return 1;
+	case SIEVE_ADDRESS_SOURCE_RECIPIENT:
+		*addr_r = sieve_message_get_final_recipient(msgctx);
+		return 1;
+	case SIEVE_ADDRESS_SOURCE_ORIG_RECIPIENT:
+		*addr_r = sieve_message_get_orig_recipient(msgctx);
+		return 1;
+	case SIEVE_ADDRESS_SOURCE_USER_EMAIL:
+		*addr_r = svinst->user_email;
+		return 1;
+	case SIEVE_ADDRESS_SOURCE_POSTMASTER:
+		*addr_r = sieve_get_postmaster_smtp(senv);
+		return 1;
+	case SIEVE_ADDRESS_SOURCE_EXPLICIT:
+		*addr_r = asrc->address;
+		return 1;
+	case SIEVE_ADDRESS_SOURCE_DEFAULT:
+		break;
+	}
+	return 0;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-address-source.h
@@ -0,0 +1,36 @@
+#ifndef SIEVE_ADDRESS_SOURCE_H
+#define SIEVE_ADDRESS_SOURCE_H
+
+#include "sieve-common.h"
+
+enum sieve_address_source_type {
+	SIEVE_ADDRESS_SOURCE_DEFAULT = 0,
+	SIEVE_ADDRESS_SOURCE_SENDER,
+	SIEVE_ADDRESS_SOURCE_RECIPIENT,
+	SIEVE_ADDRESS_SOURCE_ORIG_RECIPIENT,
+	SIEVE_ADDRESS_SOURCE_USER_EMAIL,
+	SIEVE_ADDRESS_SOURCE_POSTMASTER,
+	SIEVE_ADDRESS_SOURCE_EXPLICIT
+};
+
+struct sieve_address_source {
+	enum sieve_address_source_type type;
+	const struct smtp_address *address;
+};
+
+bool sieve_address_source_parse
+	(pool_t pool, const char *value,
+		struct sieve_address_source *asrc);
+bool sieve_address_source_parse_from_setting
+	(struct sieve_instance *svinst, pool_t pool,
+		const char *setting, struct sieve_address_source *asrc);
+
+int sieve_address_source_get_address
+	(struct sieve_address_source *asrc,
+		struct sieve_instance *svinst,
+		const struct sieve_script_env *senv,
+		struct sieve_message_context *msgctx,
+		enum sieve_execute_flags flags,
+		const struct smtp_address **addr_r);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-address.c
@@ -0,0 +1,545 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "rfc822-parser.h"
+#include "message-address.h"
+
+#include "sieve-common.h"
+#include "sieve-runtime-trace.h"
+
+#include "sieve-address.h"
+
+#include <ctype.h>
+
+/*
+ * Header address list
+ */
+
+/* Forward declarations */
+
+static int sieve_header_address_list_next_string_item
+	(struct sieve_stringlist *_strlist, string_t **str_r);
+static int sieve_header_address_list_next_item
+	(struct sieve_address_list *_addrlist, struct smtp_address *addr_r,
+		string_t **unparsed_r);
+static void sieve_header_address_list_reset
+	(struct sieve_stringlist *_strlist);
+static void sieve_header_address_list_set_trace
+	(struct sieve_stringlist *_strlist, bool trace);
+
+/* Stringlist object */
+
+struct sieve_header_address_list {
+	struct sieve_address_list addrlist;
+
+	struct sieve_stringlist *field_values;
+	const struct message_address *cur_address;
+};
+
+struct sieve_address_list *sieve_header_address_list_create
+(const struct sieve_runtime_env *renv, struct sieve_stringlist *field_values)
+{
+	struct sieve_header_address_list *addrlist;
+
+	addrlist = t_new(struct sieve_header_address_list, 1);
+	addrlist->addrlist.strlist.runenv = renv;
+	addrlist->addrlist.strlist.exec_status = SIEVE_EXEC_OK;
+	addrlist->addrlist.strlist.next_item =
+		sieve_header_address_list_next_string_item;
+	addrlist->addrlist.strlist.reset = sieve_header_address_list_reset;
+	addrlist->addrlist.strlist.set_trace = sieve_header_address_list_set_trace;
+	addrlist->addrlist.next_item = sieve_header_address_list_next_item;
+	addrlist->field_values = field_values;
+
+	return &addrlist->addrlist;
+}
+
+static int
+sieve_header_address_list_next_address(
+	struct sieve_header_address_list *addrlist, struct smtp_address *addr_r)
+{
+	struct smtp_address adummy;
+	int ret = 0;
+
+	if (addr_r == NULL)
+		addr_r = &adummy;
+
+	while (addrlist->cur_address != NULL) {
+		const struct message_address *aitem = addrlist->cur_address;
+
+		addrlist->cur_address = addrlist->cur_address->next;
+
+		if (!aitem->invalid_syntax && aitem->domain != NULL &&
+		    smtp_address_init_from_msg(addr_r, aitem) >= 0)
+			return 1;
+		ret = -1;
+	}
+	return ret;
+}
+
+static int sieve_header_address_list_next_item
+(struct sieve_address_list *_addrlist, struct smtp_address *addr_r,
+	string_t **unparsed_r)
+{
+	struct sieve_header_address_list *addrlist =
+		(struct sieve_header_address_list *) _addrlist;
+	const struct sieve_runtime_env *runenv = _addrlist->strlist.runenv;
+	string_t *value_item = NULL;
+	bool trace = _addrlist->strlist.trace;
+
+	if ( addr_r != NULL )
+		smtp_address_init(addr_r, NULL, NULL);
+	if ( unparsed_r != NULL ) *unparsed_r = NULL;
+
+	for (;;) {
+		int ret;
+
+		if ((ret=sieve_header_address_list_next_address(addrlist, addr_r)) < 0 &&
+		    value_item != NULL) {
+			/* completely invalid address list is returned as-is */
+			if (trace) {
+				sieve_runtime_trace(runenv, 0,
+					"invalid address value `%s'",
+					str_sanitize(str_c(value_item), 80));
+			}
+			if ( unparsed_r != NULL ) *unparsed_r = value_item;
+			return 1;
+		}
+		if (ret > 0) {
+			if (trace) {
+				sieve_runtime_trace(runenv, 0,
+					"address value `%s'",
+					str_sanitize(smtp_address_encode(addr_r), 80));
+			}
+			return 1;
+		}
+
+		/* Read next header value from source list */
+		if ( (ret=sieve_stringlist_next_item(addrlist->field_values,
+						     &value_item)) <= 0 )
+			return ret;
+		if (str_len(value_item) == 0) {
+			/* empty header value is returned as-is */
+			if (trace) {
+				sieve_runtime_trace(runenv, 0,
+					"empty address value");
+			}
+			addrlist->cur_address = NULL;
+			if ( unparsed_r != NULL ) *unparsed_r = value_item;
+			return 1;
+		}
+
+		if (trace) {
+			sieve_runtime_trace(runenv, 0,
+				"parsing address header value `%s'",
+				str_sanitize(str_c(value_item), 80));
+		}
+
+		addrlist->cur_address = message_address_parse(
+			pool_datastack_create(),
+			(const unsigned char *) str_data(value_item),
+			str_len(value_item), 256, 0);
+	}
+	i_unreached();
+}
+
+static int sieve_header_address_list_next_string_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+	struct sieve_address_list *addrlist = (struct sieve_address_list *)_strlist;
+	struct smtp_address addr;
+	int ret;
+
+	if ( (ret=sieve_header_address_list_next_item
+		(addrlist, &addr, str_r)) <= 0 )
+		return ret;
+
+	if ( addr.localpart != NULL ) {
+		const char *addr_str =	smtp_address_encode(&addr);
+		if ( str_r != NULL )
+			*str_r = t_str_new_const(addr_str, strlen(addr_str));
+	}
+
+	return 1;
+}
+
+static void sieve_header_address_list_reset
+(struct sieve_stringlist *_strlist)
+{
+	struct sieve_header_address_list *addrlist =
+		(struct sieve_header_address_list *)_strlist;
+
+	sieve_stringlist_reset(addrlist->field_values);
+	addrlist->cur_address = NULL;
+}
+
+static void sieve_header_address_list_set_trace
+(struct sieve_stringlist *_strlist, bool trace)
+{
+	struct sieve_header_address_list *addrlist =
+		(struct sieve_header_address_list *)_strlist;
+
+	sieve_stringlist_set_trace(addrlist->field_values, trace);
+}
+
+/*
+ * RFC 2822 addresses
+ */
+
+/* Mail message address according to RFC 2822 and implemented in the Dovecot
+ * message address parser:
+ *
+ *   address         =       mailbox / group
+ *   mailbox         =       name-addr / addr-spec
+ *   name-addr       =       [display-name] angle-addr
+ *   angle-addr      =       [CFWS] "<" addr-spec ">" [CFWS] / obs-angle-addr
+ *   group           =       display-name ":" [mailbox-list / CFWS] ";" [CFWS]
+ *   display-name    =       phrase
+ *
+ *   addr-spec       =       local-part "@" domain
+ *   local-part      =       dot-atom / quoted-string / obs-local-part
+ *   domain          =       dot-atom / domain-literal / obs-domain
+ *   domain-literal  =       [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS]
+ *   dcontent        =       dtext / quoted-pair
+ *   dtext           =       NO-WS-CTL /     ; Non white space controls
+ *                           %d33-90 /       ; The rest of the US-ASCII
+ *                           %d94-126        ;  characters not including "[",
+ *                                           ;  "]", or "\"
+ *
+ *   atext           =       ALPHA / DIGIT / ; Any character except controls,
+ *                           "!" / "#" /     ;  SP, and specials.
+ *                           "$" / "%" /     ;  Used for atoms
+ *                           "&" / "'" /
+ *                           "*" / "+" /
+ *                           "-" / "/" /
+ *                           "=" / "?" /
+ *                           "^" / "_" /
+ *                           "`" / "{" /
+ *                           "|" / "}" /
+ *                           "~"
+ *   atom            =       [CFWS] 1*atext [CFWS]
+ *   dot-atom        =       [CFWS] dot-atom-text [CFWS]
+ *   dot-atom-text   =       1*atext *("." 1*atext)
+ *   word            =       atom / quoted-string
+ *   phrase          =       1*word / obs-phrase
+ *
+ * Message address specification as allowed bij the RFC 5228 SIEVE
+ * specification:
+ *   sieve-address   =       addr-spec                  ; simple address
+ *                           / phrase "<" addr-spec ">" ; name & addr-spec\
+ *
+ * Which concisely is about equal to:
+ *   sieve-address   =       mailbox
+ */
+
+/*
+ * Address parse context
+ */
+
+struct sieve_message_address_parser {
+	struct rfc822_parser_context parser;
+
+	string_t *str;
+	string_t *local_part;
+	string_t *domain;
+
+	string_t *error;
+};
+
+/*
+ * Error handling
+ */
+
+static inline void sieve_address_error
+	(struct sieve_message_address_parser *ctx, const char *fmt, ...)
+		ATTR_FORMAT(2, 3);
+
+static inline void sieve_address_error
+	(struct sieve_message_address_parser *ctx, const char *fmt, ...)
+{
+	va_list args;
+
+	if ( str_len(ctx->error) == 0 ) {
+		va_start(args, fmt);
+		str_vprintfa(ctx->error, fmt, args);
+		va_end(args);
+	}
+}
+
+/*
+ * Partial RFC 2822 address parser
+ *
+ *   FIXME: lots of overlap with dovecot/src/lib-mail/message-parser.c
+ *          --> this implementation adds textual error reporting
+ *          MERGE!
+ */
+
+static int check_local_part(struct sieve_message_address_parser *ctx)
+{
+	const unsigned char *p, *pend;
+
+	p = str_data(ctx->local_part);
+	pend = p + str_len(ctx->local_part);
+	while (p < pend) {
+		if (*p < 0x20 || *p > 0x7e)
+			return -1;
+		p++;
+	}
+	return 0;
+}
+
+static int parse_local_part(struct sieve_message_address_parser *ctx)
+{
+	int ret;
+
+	/*
+	   local-part      = dot-atom / quoted-string / obs-local-part
+	   obs-local-part  = word *("." word)
+	*/
+	if (ctx->parser.data == ctx->parser.end) {
+		sieve_address_error(ctx, "empty local part");
+		return -1;
+	}
+
+	str_truncate(ctx->local_part, 0);
+	if (*ctx->parser.data == '"') {
+		ret = rfc822_parse_quoted_string(&ctx->parser, ctx->local_part);
+	} else {
+		ret = -1;
+		/* NOTE: this deviates from dot-atom syntax to allow some Japanese
+		   mail addresses with dots at non-standard places to be accepted. */
+		do {
+			while (*ctx->parser.data == '.') {
+				str_append_c(ctx->local_part, '.');
+				ctx->parser.data++;
+				if (ctx->parser.data == ctx->parser.end) {
+					/* @domain is missing, but local-part
+					   parsing was successful */
+					return 0;
+				}
+				ret = 1;
+			}
+			if (*ctx->parser.data == '@')
+				break;
+			ret = rfc822_parse_atom(&ctx->parser, ctx->local_part);
+		} while (ret > 0 && *ctx->parser.data == '.');
+	}
+
+	if (ret < 0 || check_local_part(ctx) < 0) {
+		sieve_address_error(ctx, "invalid local part");
+		return -1;
+	}
+
+	return ret;
+}
+
+static int parse_domain(struct sieve_message_address_parser *ctx)
+{
+	int ret;
+
+	str_truncate(ctx->domain, 0);
+	if ((ret = rfc822_parse_domain(&ctx->parser, ctx->domain)) < 0) {
+		sieve_address_error(ctx, "invalid or missing domain");
+		return -1;
+	}
+
+	return ret;
+}
+
+static int parse_addr_spec(struct sieve_message_address_parser *ctx)
+{
+	/* addr-spec       = local-part "@" domain */
+	int ret;
+
+	if ((ret = parse_local_part(ctx)) < 0)
+		return ret;
+
+	if ( ret > 0 && *ctx->parser.data == '@') {
+		return parse_domain(ctx);
+	}
+
+	sieve_address_error(ctx, "invalid or lonely local part '%s' (expecting '@')",
+		str_sanitize(str_c(ctx->local_part), 80));
+	return -1;
+}
+
+static int parse_mailbox(struct sieve_message_address_parser *ctx)
+{
+	int ret;
+	const unsigned char *start;
+
+	/* sieve-address   =       addr-spec                  ; simple address
+	 *                         / phrase "<" addr-spec ">" ; name & addr-spec
+	 */
+
+	/* Record parser state in case we fail at our first attempt */
+	start = ctx->parser.data;
+
+	/* First try: phrase "<" addr-spec ">" ; name & addr-spec */
+	str_truncate(ctx->str, 0);
+	if (rfc822_parse_phrase(&ctx->parser, ctx->str) <= 0 ||
+	    *ctx->parser.data != '<') {
+	  /* Failed; try just bare addr-spec */
+	  ctx->parser.data = start;
+	  return parse_addr_spec(ctx);
+	}
+
+	/* "<" addr-spec ">" */
+	ctx->parser.data++;
+
+	if ((ret = rfc822_skip_lwsp(&ctx->parser)) <= 0 ) {
+		if ( ret < 0 )
+			sieve_address_error(ctx, "invalid characters after <");
+		return ret;
+	}
+
+	if (parse_addr_spec(ctx) < 0)
+		return -1;
+
+	if (*ctx->parser.data != '>') {
+		sieve_address_error(ctx, "missing '>'");
+		return -1;
+	}
+	ctx->parser.data++;
+
+	if ( (ret=rfc822_skip_lwsp(&ctx->parser)) < 0 )
+		sieve_address_error(ctx, "address ends with invalid characters");
+
+	return ret;
+}
+
+static bool parse_mailbox_address
+(struct sieve_message_address_parser *ctx, const unsigned char *address,
+	unsigned int addr_size)
+{
+	/* Initialize parser */
+
+	rfc822_parser_init(&ctx->parser, address, addr_size, NULL);
+
+	/* Parse */
+
+	rfc822_skip_lwsp(&ctx->parser);
+
+	if (ctx->parser.data == ctx->parser.end) {
+		sieve_address_error(ctx, "empty address");
+		return FALSE;
+	}
+
+	if (parse_mailbox(ctx) < 0)
+		return FALSE;
+
+	if (ctx->parser.data != ctx->parser.end) {
+		if ( *ctx->parser.data == ',' )
+			sieve_address_error(ctx, "not a single addres (found ',')");
+		else
+			sieve_address_error(ctx, "address ends in invalid characters");
+		return FALSE;
+	}
+
+	if ( str_len(ctx->domain) == 0 ) {
+		/* Not gonna happen */
+		sieve_address_error(ctx, "missing domain");
+		return FALSE;
+	}
+
+	if ( str_len(ctx->local_part) == 0 ) {
+		sieve_address_error(ctx, "missing local part");
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static bool sieve_address_do_validate
+(const unsigned char *address, size_t size,
+	const char **error_r)
+{
+	struct sieve_message_address_parser ctx;
+
+	if ( address == NULL ) {
+		*error_r = "null address";
+		return FALSE;
+	}
+
+	i_zero(&ctx);
+
+	ctx.local_part = t_str_new(128);
+	ctx.domain = t_str_new(128);
+	ctx.str = t_str_new(128);
+	ctx.error = t_str_new(128);
+
+	if ( !parse_mailbox_address(&ctx, address, size) ) {
+		if ( error_r != NULL )
+			*error_r = str_c(ctx.error);
+		return FALSE;
+	}
+
+	if ( error_r != NULL )
+		*error_r = NULL;
+
+	return TRUE;
+}
+
+static const struct smtp_address *sieve_address_do_parse
+(const unsigned char *address, size_t size,
+	const char **error_r)
+{
+	struct sieve_message_address_parser ctx;
+
+	if ( error_r != NULL )
+		*error_r = NULL;
+
+	if ( address == NULL ) return NULL;
+
+	i_zero(&ctx);
+
+	ctx.local_part = t_str_new(128);
+	ctx.domain = t_str_new(128);
+	ctx.str = t_str_new(128);
+	ctx.error = t_str_new(128);
+
+	if ( !parse_mailbox_address(&ctx, address, size) ) {
+		if ( error_r != NULL )
+			*error_r = str_c(ctx.error);
+		return NULL;
+	}
+
+	(void)str_lcase(str_c_modifiable(ctx.domain));
+
+	return smtp_address_create_temp(str_c(ctx.local_part), str_c(ctx.domain));
+}
+
+/*
+ * Sieve address
+ */
+
+const struct smtp_address *sieve_address_parse
+(const char *address, const char **error_r)
+{
+	return sieve_address_do_parse
+		((const unsigned char *)address, strlen(address), error_r);
+}
+
+const struct smtp_address *sieve_address_parse_str
+(string_t *address, const char **error_r)
+{
+	return sieve_address_do_parse
+		(str_data(address), str_len(address), error_r);
+}
+
+bool sieve_address_validate
+(const char *address, const char **error_r)
+{
+	return sieve_address_do_validate
+		((const unsigned char *)address, strlen(address), error_r);
+}
+
+bool sieve_address_validate_str
+(string_t *address, const char **error_r)
+{
+	return sieve_address_do_validate
+		(str_data(address), str_len(address), error_r);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-address.h
@@ -0,0 +1,69 @@
+#ifndef SIEVE_ADDRESS_H
+#define SIEVE_ADDRESS_H
+
+#include "lib.h"
+#include "strfuncs.h"
+#include "smtp-address.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+
+/*
+ * Address list API
+ */
+
+struct sieve_address_list {
+	struct sieve_stringlist strlist;
+
+	int (*next_item)
+		(struct sieve_address_list *_addrlist, struct smtp_address *addr_r,
+			string_t **unparsed_r);
+};
+
+static inline int sieve_address_list_next_item
+(struct sieve_address_list *addrlist, struct smtp_address *addr_r,
+	string_t **unparsed_r)
+{
+	return addrlist->next_item(addrlist, addr_r, unparsed_r);
+}
+
+static inline void sieve_address_list_reset
+(struct sieve_address_list *addrlist)
+{
+	sieve_stringlist_reset(&addrlist->strlist);
+}
+
+static inline int sieve_address_list_get_length
+(struct sieve_address_list *addrlist)
+{
+	return sieve_stringlist_get_length(&addrlist->strlist);
+}
+
+static inline void sieve_address_list_set_trace
+(struct sieve_address_list *addrlist, bool trace)
+{
+	sieve_stringlist_set_trace(&addrlist->strlist, trace);
+}
+
+/*
+ * Header address list
+ */
+
+struct sieve_address_list *sieve_header_address_list_create
+	(const struct sieve_runtime_env *renv, struct sieve_stringlist *field_values);
+
+/*
+ * Sieve address parsing/validatin
+ */
+
+const struct smtp_address *sieve_address_parse
+	(const char *address, const char **error_r);
+const struct smtp_address *sieve_address_parse_str
+	(string_t *address, const char **error_r);
+
+bool sieve_address_validate
+	(const char *address, const char **error_r);
+bool sieve_address_validate_str
+	(string_t *address, const char **error_r);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-ast.c
@@ -0,0 +1,1101 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "mempool.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-script.h"
+#include "sieve-extensions.h"
+
+#include "sieve-ast.h"
+
+#include <stdio.h>
+
+/*
+ * Forward declarations
+ */
+
+static struct sieve_ast_node *sieve_ast_node_create
+	(struct sieve_ast *ast, struct sieve_ast_node *parent,
+		enum sieve_ast_type type, unsigned int source_line);
+
+/*
+ * Types
+ */
+
+/* Extensions to the AST */
+
+struct sieve_ast_extension_reg {
+	const struct sieve_extension *ext;
+	const struct sieve_ast_extension *ast_ext;
+	void *context;
+
+	bool required:1;
+};
+
+/*
+ * AST object
+ */
+
+struct sieve_ast {
+	pool_t pool;
+	int refcount;
+
+	struct sieve_instance *svinst;
+
+	struct sieve_script *script;
+
+	struct sieve_ast_node *root;
+
+	ARRAY(const struct sieve_extension *) linked_extensions;
+	ARRAY(struct sieve_ast_extension_reg) extensions;
+};
+
+struct sieve_ast *sieve_ast_create
+(struct sieve_script *script)
+{
+	pool_t pool;
+	struct sieve_ast *ast;
+	unsigned int ext_count;
+
+	pool = pool_alloconly_create("sieve_ast", 32768);
+	ast = p_new(pool, struct sieve_ast, 1);
+	ast->pool = pool;
+	ast->refcount = 1;
+
+	ast->script = script;
+	sieve_script_ref(script);
+	ast->svinst = sieve_script_svinst(script);
+
+	ast->root = sieve_ast_node_create(ast, NULL, SAT_ROOT, 0);
+	ast->root->identifier = "ROOT";
+
+	ext_count = sieve_extensions_get_count(ast->svinst);
+	p_array_init(&ast->linked_extensions, pool, ext_count);
+	p_array_init(&ast->extensions, pool, ext_count);
+
+	return ast;
+}
+
+void sieve_ast_ref(struct sieve_ast *ast)
+{
+	ast->refcount++;
+}
+
+void sieve_ast_unref(struct sieve_ast **ast)
+{
+	unsigned int i, ext_count;
+	const struct sieve_ast_extension_reg *extrs;
+
+	i_assert((*ast)->refcount > 0);
+
+	if (--(*ast)->refcount != 0)
+		return;
+
+	/* Release script reference */
+	sieve_script_unref(&(*ast)->script);
+
+	/* Signal registered extensions that the AST is being destroyed */
+	extrs = array_get(&(*ast)->extensions, &ext_count);
+	for ( i = 0; i < ext_count; i++ ) {
+		if ( extrs[i].ast_ext != NULL &&
+			extrs[i].ast_ext->free != NULL )
+			extrs[i].ast_ext->free(extrs[i].ext, *ast, extrs[i].context);
+	}
+
+	/* Destroy AST */
+	pool_unref(&(*ast)->pool);
+
+	*ast = NULL;
+}
+
+struct sieve_ast_node *sieve_ast_root(struct sieve_ast *ast)
+{
+	return ast->root;
+}
+
+pool_t sieve_ast_pool(struct sieve_ast *ast)
+{
+	return ast->pool;
+}
+
+struct sieve_script *sieve_ast_script(struct sieve_ast *ast)
+{
+	return ast->script;
+}
+
+/*
+ * Extension support
+ */
+
+void sieve_ast_extension_link
+(struct sieve_ast *ast, const struct sieve_extension *ext,
+	bool required)
+{
+	unsigned int i, ext_count;
+	const struct sieve_extension *const *extensions;
+	struct sieve_ast_extension_reg *reg;
+
+	if ( ext->id < 0 ) return;
+
+	/* Initialize registration */
+	reg = array_idx_get_space(&ast->extensions,
+		(unsigned int) ext->id);
+	i_assert(reg->ext == NULL || reg->ext == ext);
+	reg->ext = ext;
+	reg->required = reg->required || required;
+
+	/* Prevent duplicates */
+	extensions = array_get(&ast->linked_extensions, &ext_count);
+	for ( i = 0; i < ext_count; i++ ) {
+		if ( extensions[i] == ext )
+			return;
+	}
+
+	/* Add extension */
+	array_append(&ast->linked_extensions, &ext, 1);
+}
+
+const struct sieve_extension * const *sieve_ast_extensions_get
+(struct sieve_ast *ast, unsigned int *count_r)
+{
+	return array_get(&ast->linked_extensions, count_r);
+}
+
+void sieve_ast_extension_register
+(struct sieve_ast *ast, const struct sieve_extension *ext,
+	const struct sieve_ast_extension *ast_ext, void *context)
+{
+	struct sieve_ast_extension_reg *reg;
+
+	if ( ext->id < 0 ) return;
+
+	/* Initialize registration */
+	reg = array_idx_get_space(&ast->extensions, (unsigned int) ext->id);
+	i_assert(reg->ext == NULL || reg->ext == ext);
+	reg->ext = ext;
+	reg->ast_ext = ast_ext;
+	reg->context = context;
+}
+
+void sieve_ast_extension_set_context
+(struct sieve_ast *ast, const struct sieve_extension *ext, void *context)
+{
+	struct sieve_ast_extension_reg *reg;
+
+	if ( ext->id < 0 ) return;
+
+	reg = array_idx_get_space(&ast->extensions, (unsigned int) ext->id);
+	reg->context = context;
+}
+
+void *sieve_ast_extension_get_context
+(struct sieve_ast *ast, const struct sieve_extension *ext)
+{
+	const struct sieve_ast_extension_reg *reg;
+
+	if  ( ext->id < 0 || ext->id >= (int) array_count(&ast->extensions) )
+		return NULL;
+
+	reg = array_idx(&ast->extensions, (unsigned int) ext->id);
+
+	return reg->context;
+}
+
+bool sieve_ast_extension_is_required
+(struct sieve_ast *ast, const struct sieve_extension *ext)
+{
+	const struct sieve_ast_extension_reg *reg;
+
+	i_assert( ext->id >= 0 &&
+		ext->id < (int) array_count(&ast->extensions) );
+
+	reg = array_idx(&ast->extensions, (unsigned int)ext->id);
+	return reg->required;
+}
+
+/*
+ * AST list implementations
+ */
+
+/* Very simplistic linked list implementation
+ * FIXME: Move to separate file
+ */
+#define __LIST_CREATE(pool, type) { \
+		type *list = p_new(pool, type, 1); \
+		list->head = NULL; \
+		list->tail = NULL; \
+		list->len = 0;		\
+		return list; \
+	}
+
+#define __LIST_ADD(list, node) { \
+		if ( list->len + 1 < list->len ) \
+			return FALSE; \
+		\
+		node->next = NULL; \
+		if ( list->head == NULL ) { \
+			node->prev = NULL; \
+			list->head = node; \
+			list->tail = node; \
+		} else { \
+			list->tail->next = node; \
+			node->prev = list->tail; \
+			list->tail = node; \
+		} \
+		list->len++; \
+		node->list = list; \
+		return TRUE; \
+	}
+
+#define __LIST_INSERT(list, before, node) { \
+		if ( list->len + 1 < list->len ) \
+			return FALSE; \
+		\
+		node->next = before; \
+		if ( list->head == before ) { \
+			node->prev = NULL; \
+			list->head = node; \
+		} else { \
+			before->prev->next = node; \
+		} \
+		node->prev = before->prev; \
+		before->prev = node; \
+		list->len++; \
+		node->list = list; \
+		\
+		return TRUE; \
+	}
+
+#define __LIST_JOIN(list, node_type, items) { \
+		node_type *node; \
+		\
+		if ( list->len + items->len < list->len ) \
+			return FALSE; \
+		\
+		if ( items->len == 0 ) return TRUE; \
+		\
+		if ( list->head == NULL ) { \
+			list->head = items->head; \
+			list->tail = items->tail; \
+		} else { \
+			list->tail->next = items->head; \
+			items->head->prev = list->tail; \
+			list->tail = items->tail; \
+		} \
+		list->len += items->len; \
+		\
+		node = items->head; \
+		while ( node != NULL ) { \
+			node->list = list; \
+			node = node->next; \
+		} \
+		return TRUE; \
+	}
+
+#define __LIST_DETACH(first, node_type, count) { \
+		node_type *last, *result; \
+		unsigned int left; \
+		\
+		i_assert(first->list != NULL); \
+		\
+		left = count - 1; \
+		last = first; \
+		while ( left > 0 && last->next != NULL ) { \
+			left--; \
+			last = last->next; \
+		} \
+		\
+		if ( first->list->head == first ) \
+			first->list->head = last->next; \
+		if ( first->list->tail == last ) \
+			first->list->tail = first->prev; \
+		\
+		if ( first->prev != NULL ) \
+			first->prev->next = last->next;	\
+		if ( last->next != NULL ) \
+			last->next->prev = first->prev; \
+		\
+		first->list->len -= count - left; \
+		\
+		result = last->next; \
+		first->prev = NULL; \
+		last->next = NULL; \
+		\
+		return result; \
+	}
+
+/* List of AST nodes */
+
+static struct sieve_ast_list *sieve_ast_list_create(pool_t pool)
+	__LIST_CREATE(pool, struct sieve_ast_list)
+
+static bool sieve_ast_list_add
+(struct sieve_ast_list *list, struct sieve_ast_node *node)
+	__LIST_ADD(list, node)
+
+static struct sieve_ast_node *sieve_ast_list_detach
+(struct sieve_ast_node *first, unsigned int count)
+	__LIST_DETACH(first, struct sieve_ast_node, count)
+
+/* List of argument AST nodes */
+
+struct sieve_ast_arg_list *sieve_ast_arg_list_create(pool_t pool)
+	__LIST_CREATE(pool, struct sieve_ast_arg_list)
+
+bool sieve_ast_arg_list_add
+(struct sieve_ast_arg_list *list, struct sieve_ast_argument *argument)
+	__LIST_ADD(list, argument)
+
+bool sieve_ast_arg_list_insert
+(struct sieve_ast_arg_list *list, struct sieve_ast_argument *before,
+	struct sieve_ast_argument *argument)
+	__LIST_INSERT(list, before, argument)
+
+static bool sieve_ast_arg_list_join
+(struct sieve_ast_arg_list *list, struct sieve_ast_arg_list *items)
+	__LIST_JOIN(list, struct sieve_ast_argument, items)
+
+static struct sieve_ast_argument *sieve_ast_arg_list_detach
+(struct sieve_ast_argument *first, const unsigned int count)
+	__LIST_DETACH(first, struct sieve_ast_argument, count)
+
+void sieve_ast_arg_list_substitute
+(struct sieve_ast_arg_list *list, struct sieve_ast_argument *argument,
+	struct sieve_ast_argument *replacement)
+{
+	if ( list->head == argument )
+		list->head = replacement;
+	if ( list->tail == argument )
+		list->tail = replacement;
+
+	if ( argument->prev != NULL )
+		argument->prev->next = replacement;
+	if ( argument->next != NULL )
+		argument->next->prev = replacement;
+
+	replacement->prev = argument->prev;
+	replacement->next = argument->next;
+	replacement->list = argument->list;
+
+	argument->next = NULL;
+	argument->prev = NULL;
+}
+
+/*
+ * AST node
+ */
+
+static struct sieve_ast_node *sieve_ast_node_create
+(struct sieve_ast *ast, struct sieve_ast_node *parent, enum sieve_ast_type type,
+	unsigned int source_line)
+{
+	struct sieve_ast_node *node = p_new(ast->pool, struct sieve_ast_node, 1);
+
+	node->ast = ast;
+	node->parent = parent;
+	node->type = type;
+
+	node->prev = NULL;
+	node->next = NULL;
+
+	node->arguments = NULL;
+	node->tests = NULL;
+	node->commands = NULL;
+
+	node->test_list = FALSE;
+	node->block = FALSE;
+
+	node->source_line = source_line;
+
+	return node;
+}
+
+static bool sieve_ast_node_add_command
+(struct sieve_ast_node *node, struct sieve_ast_node *command)
+{
+	i_assert( command->type == SAT_COMMAND &&
+		(node->type == SAT_ROOT || node->type == SAT_COMMAND) );
+
+	if (node->commands == NULL)
+		node->commands = sieve_ast_list_create(node->ast->pool);
+
+	return sieve_ast_list_add(node->commands, command);
+}
+
+static bool sieve_ast_node_add_test
+(struct sieve_ast_node *node, struct sieve_ast_node *test)
+{
+	i_assert( test->type == SAT_TEST &&
+		(node->type == SAT_TEST || node->type == SAT_COMMAND) );
+
+	if (node->tests == NULL)
+		node->tests = sieve_ast_list_create(node->ast->pool);
+
+	return sieve_ast_list_add(node->tests, test);
+}
+
+static bool sieve_ast_node_add_argument
+(struct sieve_ast_node *node, struct sieve_ast_argument *argument)
+{
+	i_assert( node->type == SAT_TEST || node->type == SAT_COMMAND );
+
+	if (node->arguments == NULL)
+		node->arguments = sieve_ast_arg_list_create(node->ast->pool);
+
+	return sieve_ast_arg_list_add(node->arguments, argument);
+}
+
+struct sieve_ast_node *sieve_ast_node_detach
+(struct sieve_ast_node *first)
+{
+	return sieve_ast_list_detach(first, 1);
+}
+
+const char *sieve_ast_type_name
+(enum sieve_ast_type ast_type)
+{
+	switch ( ast_type ) {
+
+	case SAT_NONE: return "none";
+	case SAT_ROOT: return "ast root node";
+	case SAT_COMMAND: return "command";
+	case SAT_TEST: return "test";
+
+	default: return "??AST NODE??";
+	}
+}
+
+/*
+ * Argument AST node
+ */
+
+struct sieve_ast_argument *sieve_ast_argument_create
+(struct sieve_ast *ast, unsigned int source_line)
+{
+	struct sieve_ast_argument *arg =
+		p_new(ast->pool, struct sieve_ast_argument, 1);
+
+	arg->ast = ast;
+
+	arg->prev = NULL;
+	arg->next = NULL;
+
+	arg->source_line = source_line;
+
+	arg->argument = NULL;
+
+	return arg;
+}
+
+static void sieve_ast_argument_substitute
+(struct sieve_ast_argument *argument, struct sieve_ast_argument *replacement)
+{
+	sieve_ast_arg_list_substitute(argument->list, argument, replacement);
+}
+
+struct sieve_ast_argument *sieve_ast_argument_string_create_raw
+(struct sieve_ast *ast, string_t *str, unsigned int source_line)
+{
+	struct sieve_ast_argument *argument = sieve_ast_argument_create
+		(ast, source_line);
+
+	argument->type = SAAT_STRING;
+	argument->_value.str = str;
+
+	return argument;
+}
+
+struct sieve_ast_argument *sieve_ast_argument_string_create
+(struct sieve_ast_node *node, const string_t *str, unsigned int source_line)
+{
+	struct sieve_ast_argument *argument;
+	string_t *newstr;
+
+	/* Allocate new internal string buffer */
+	newstr = str_new(node->ast->pool, str_len(str));
+
+	/* Clone string */
+	str_append_str(newstr, str);
+
+	/* Create string argument */
+	argument = sieve_ast_argument_string_create_raw
+		(node->ast, newstr, source_line);
+
+	/* Add argument to command/test node */
+	sieve_ast_node_add_argument(node, argument);
+
+	return argument;
+}
+
+struct sieve_ast_argument *sieve_ast_argument_cstring_create
+(struct sieve_ast_node *node, const char *str, unsigned int source_line)
+{
+	struct sieve_ast_argument *argument;
+	string_t *newstr;
+
+	/* Allocate new internal string buffer */
+	newstr = str_new(node->ast->pool, strlen(str));
+
+	/* Clone string */
+	str_append(newstr, str);
+
+	/* Create string argument */
+	argument = sieve_ast_argument_string_create_raw
+		(node->ast, newstr, source_line);
+
+	/* Add argument to command/test node */
+	sieve_ast_node_add_argument(node, argument);
+
+	return argument;
+}
+
+void sieve_ast_argument_string_set
+(struct sieve_ast_argument *argument, string_t *newstr)
+{
+	i_assert( argument->type == SAAT_STRING);
+	argument->_value.str = newstr;
+}
+
+void sieve_ast_argument_string_setc
+(struct sieve_ast_argument *argument, const char *newstr)
+{
+	i_assert( argument->type == SAAT_STRING);
+
+	str_truncate(argument->_value.str, 0);
+	str_append(argument->_value.str, newstr);
+}
+
+void sieve_ast_argument_number_substitute
+(struct sieve_ast_argument *argument, unsigned int number)
+{
+	argument->type = SAAT_NUMBER;
+	argument->_value.number = number;
+}
+
+struct sieve_ast_argument *sieve_ast_argument_stringlist_create
+(struct sieve_ast_node *node, unsigned int source_line)
+{
+	struct sieve_ast_argument *argument =
+		sieve_ast_argument_create(node->ast, source_line);
+
+	argument->type = SAAT_STRING_LIST;
+	argument->_value.strlist = NULL;
+
+	sieve_ast_node_add_argument(node, argument);
+
+	return argument;
+}
+
+struct sieve_ast_argument *sieve_ast_argument_stringlist_substitute
+(struct sieve_ast_node *node, struct sieve_ast_argument *arg)
+{
+	struct sieve_ast_argument *argument =
+		sieve_ast_argument_create(node->ast, arg->source_line);
+
+	argument->type = SAAT_STRING_LIST;
+	argument->_value.strlist = NULL;
+
+	sieve_ast_argument_substitute(arg, argument);
+
+	return argument;
+}
+
+static inline bool _sieve_ast_stringlist_add_item
+(struct sieve_ast_argument *list, struct sieve_ast_argument *item)
+{
+	i_assert( list->type == SAAT_STRING_LIST );
+
+	if ( list->_value.strlist == NULL )
+		list->_value.strlist = sieve_ast_arg_list_create(list->ast->pool);
+
+	return sieve_ast_arg_list_add(list->_value.strlist, item);
+}
+
+static bool sieve_ast_stringlist_add_stringlist
+(struct sieve_ast_argument *list, struct sieve_ast_argument *items)
+{
+	i_assert( list->type == SAAT_STRING_LIST );
+	i_assert( items->type == SAAT_STRING_LIST );
+
+	if ( list->_value.strlist == NULL )
+		list->_value.strlist = sieve_ast_arg_list_create(list->ast->pool);
+
+	return sieve_ast_arg_list_join(list->_value.strlist, items->_value.strlist);
+}
+
+static bool _sieve_ast_stringlist_add_str
+(struct sieve_ast_argument *list, string_t *str, unsigned int source_line)
+{
+	struct sieve_ast_argument *stritem;
+
+	stritem = sieve_ast_argument_create(list->ast, source_line);
+	stritem->type = SAAT_STRING;
+	stritem->_value.str = str;
+
+	return _sieve_ast_stringlist_add_item(list, stritem);
+}
+
+bool sieve_ast_stringlist_add
+(struct sieve_ast_argument *list, const string_t *str, unsigned int source_line)
+{
+	string_t *copied_str = str_new(list->ast->pool, str_len(str));
+	str_append_str(copied_str, str);
+
+	return _sieve_ast_stringlist_add_str(list, copied_str, source_line);
+}
+
+bool sieve_ast_stringlist_add_strc
+(struct sieve_ast_argument *list, const char *str, unsigned int source_line)
+{
+	string_t *copied_str = str_new(list->ast->pool, strlen(str));
+	str_append(copied_str, str);
+
+	return _sieve_ast_stringlist_add_str(list, copied_str, source_line);
+}
+
+struct sieve_ast_argument *sieve_ast_argument_tag_create
+(struct sieve_ast_node *node, const char *tag, unsigned int source_line)
+{
+	struct sieve_ast_argument *argument =
+		sieve_ast_argument_create(node->ast, source_line);
+
+	argument->type = SAAT_TAG;
+	argument->_value.tag = p_strdup(node->ast->pool, tag);
+
+	if ( !sieve_ast_node_add_argument(node, argument) )
+		return NULL;
+
+	return argument;
+}
+
+struct sieve_ast_argument *sieve_ast_argument_tag_insert
+(struct sieve_ast_argument *before, const char *tag, unsigned int source_line)
+{
+	struct sieve_ast_argument *argument =
+		sieve_ast_argument_create(before->ast, source_line);
+
+	argument->type = SAAT_TAG;
+	argument->_value.tag = p_strdup(before->ast->pool, tag);
+
+	if ( !sieve_ast_arg_list_insert(before->list, before, argument) )
+		return NULL;
+
+	return argument;
+}
+
+struct sieve_ast_argument *sieve_ast_argument_number_create
+(struct sieve_ast_node *node, unsigned int number, unsigned int source_line)
+{
+
+	struct sieve_ast_argument *argument =
+		sieve_ast_argument_create(node->ast, source_line);
+
+	argument->type = SAAT_NUMBER;
+	argument->_value.number = number;
+
+	if ( !sieve_ast_node_add_argument(node, argument) )
+		return NULL;
+
+	return argument;
+}
+
+void sieve_ast_argument_number_set
+(struct sieve_ast_argument *argument, unsigned int newnum)
+{
+	i_assert( argument->type == SAAT_NUMBER );
+	argument->_value.number = newnum;
+}
+
+
+struct sieve_ast_argument *sieve_ast_arguments_detach
+(struct sieve_ast_argument *first, unsigned int count)
+{
+	return sieve_ast_arg_list_detach(first, count);
+}
+
+bool sieve_ast_argument_attach
+(struct sieve_ast_node *node, struct sieve_ast_argument *argument)
+{
+	return sieve_ast_node_add_argument(node, argument);
+}
+
+const char *sieve_ast_argument_type_name
+(enum sieve_ast_argument_type arg_type)
+{
+	switch ( arg_type ) {
+
+	case SAAT_NONE: return "none";
+	case SAAT_STRING_LIST: return "a string list";
+	case SAAT_STRING: return "a string";
+	case SAAT_NUMBER: return "a number";
+	case SAAT_TAG: return "a tag";
+
+	default: return "??ARGUMENT??";
+	}
+}
+
+/* Test AST node */
+
+struct sieve_ast_node *sieve_ast_test_create
+(struct sieve_ast_node *parent, const char *identifier,
+	unsigned int source_line)
+{
+	struct sieve_ast_node *test = sieve_ast_node_create
+		(parent->ast, parent, SAT_TEST, source_line);
+
+	test->identifier = p_strdup(parent->ast->pool, identifier);
+
+	if ( !sieve_ast_node_add_test(parent, test) )
+		return NULL;
+
+	return test;
+}
+
+/* Command AST node */
+
+struct sieve_ast_node *sieve_ast_command_create
+(struct sieve_ast_node *parent, const char *identifier,
+	unsigned int source_line)
+{
+
+	struct sieve_ast_node *command = sieve_ast_node_create
+		(parent->ast, parent, SAT_COMMAND, source_line);
+
+	command->identifier = p_strdup(parent->ast->pool, identifier);
+
+	if ( !sieve_ast_node_add_command(parent, command) )
+		return NULL;
+
+	return command;
+}
+
+/*
+ * Utility
+ */
+
+int sieve_ast_stringlist_map
+(struct sieve_ast_argument **listitem, void *context,
+	int (*map_function)(void *context, struct sieve_ast_argument *arg))
+{
+	if ( sieve_ast_argument_type(*listitem) == SAAT_STRING ) {
+		/* Single string */
+		return map_function(context, *listitem);
+	} else if ( sieve_ast_argument_type(*listitem) == SAAT_STRING_LIST ) {
+		int ret = 0;
+
+		/* String list */
+		*listitem = sieve_ast_strlist_first(*listitem);
+
+		while ( *listitem != NULL ) {
+
+			if ( (ret=map_function(context, *listitem)) <= 0 )
+				return ret;
+
+			*listitem = sieve_ast_strlist_next(*listitem);
+		}
+
+		return ret;
+	}
+
+	i_unreached();
+	return -1;
+}
+
+struct sieve_ast_argument *sieve_ast_stringlist_join
+(struct sieve_ast_argument *list, struct sieve_ast_argument *items)
+{
+	enum sieve_ast_argument_type list_type, items_type;
+	struct sieve_ast_argument *newlist;
+
+	list_type = sieve_ast_argument_type(list);
+	items_type = sieve_ast_argument_type(items);
+
+	switch ( list_type ) {
+
+	case SAAT_STRING:
+		switch ( items_type ) {
+
+		case SAAT_STRING:
+			newlist =
+				sieve_ast_argument_create(list->ast, list->source_line);
+			newlist->type = SAAT_STRING_LIST;
+			newlist->_value.strlist = NULL;
+
+			sieve_ast_argument_substitute(list, newlist);
+			sieve_ast_arguments_detach(items, 1);
+
+			if ( !_sieve_ast_stringlist_add_item(newlist, list) ||
+				!_sieve_ast_stringlist_add_item(newlist, items) ) {
+				return NULL;
+			}
+
+			return newlist;
+
+		case SAAT_STRING_LIST:
+			/* Adding stringlist to string; make them swith places and add one to the
+			 * other.
+			 */
+			sieve_ast_arguments_detach(items, 1);
+			sieve_ast_argument_substitute(list, items);
+			if ( !_sieve_ast_stringlist_add_item(items, list) )
+				return NULL;
+
+			return list;
+
+		default:
+			i_unreached();
+		}
+		break;
+
+	case SAAT_STRING_LIST:
+		switch ( items_type ) {
+
+		case SAAT_STRING:
+			/* Adding string to stringlist; straightforward add */
+			sieve_ast_arguments_detach(items, 1);
+			if ( !_sieve_ast_stringlist_add_item(list, items) )
+				return NULL;
+
+			return list;
+
+		case SAAT_STRING_LIST:
+			/* Adding stringlist to stringlist; perform actual join */
+			sieve_ast_arguments_detach(items, 1);
+			if ( !sieve_ast_stringlist_add_stringlist(list, items) )
+				return NULL;
+
+			return list;
+
+		default:
+			i_unreached();
+		}
+
+		break;
+	default:
+		i_unreached();
+	}
+
+	return NULL;
+}
+
+
+/* Debug */
+
+/* Unparsing, currently implemented using plain printf()s */
+
+static void sieve_ast_unparse_string(const string_t *strval)
+{
+	char *str = t_strdup_noconst(str_c((string_t *) strval));
+
+	if ( strchr(str, '\n') != NULL && str[strlen(str)-1] == '\n' ) {
+		/* Print it as a multi-line string and do required dotstuffing */
+		char *spos = str;
+		char *epos = strchr(str, '\n');
+		printf("text:\n");
+
+		while ( epos != NULL ) {
+			*epos = '\0';
+			if ( *spos == '.' )
+				printf(".");
+
+			printf("%s\n", spos);
+
+			spos = epos+1;
+			epos = strchr(spos, '\n');
+		}
+		if ( *spos == '.' )
+				printf(".");
+
+		printf("%s\n.\n", spos);
+	} else {
+		/* Print it as a quoted string and escape " */
+		char *spos = str;
+		char *epos = strchr(str, '"');
+		printf("\"");
+
+		while ( epos != NULL ) {
+			*epos = '\0';
+			printf("%s\\\"", spos);
+
+			spos = epos+1;
+			epos = strchr(spos, '"');
+		}
+
+		printf("%s\"", spos);
+	}
+}
+
+static void sieve_ast_unparse_argument
+	(struct sieve_ast_argument *argument, int level);
+
+static void sieve_ast_unparse_stringlist
+(struct sieve_ast_argument *strlist, int level)
+{
+	struct sieve_ast_argument *stritem;
+
+	if ( sieve_ast_strlist_count(strlist) > 1 ) {
+		int i;
+
+		printf("[\n");
+
+		/* Create indent */
+		for ( i = 0; i < level+2; i++ )
+			printf("  ");
+
+		stritem = sieve_ast_strlist_first(strlist);
+		if ( stritem != NULL ) {
+			sieve_ast_unparse_string(sieve_ast_strlist_str(stritem));
+
+			stritem = sieve_ast_strlist_next(stritem);
+			while ( stritem != NULL ) {
+				printf(",\n");
+				for ( i = 0; i < level+2; i++ )
+					printf("  ");
+				sieve_ast_unparse_string(sieve_ast_strlist_str(stritem));
+				stritem = sieve_ast_strlist_next(stritem);
+			}
+		}
+
+		printf(" ]");
+	} else {
+		stritem = sieve_ast_strlist_first(strlist);
+		if ( stritem != NULL )
+			sieve_ast_unparse_string(sieve_ast_strlist_str(stritem));
+	}
+}
+
+static void sieve_ast_unparse_argument
+(struct sieve_ast_argument *argument, int level)
+{
+	switch ( argument->type ) {
+	case SAAT_STRING:
+		sieve_ast_unparse_string(sieve_ast_argument_str(argument));
+		break;
+	case SAAT_STRING_LIST:
+		sieve_ast_unparse_stringlist(argument, level+1);
+		break;
+	case SAAT_NUMBER:
+		printf("%d", sieve_ast_argument_number(argument));
+		break;
+	case SAAT_TAG:
+		printf(":%s", sieve_ast_argument_tag(argument));
+		break;
+	default:
+		printf("??ARGUMENT??");
+		break;
+	}
+}
+
+static void sieve_ast_unparse_test
+	(struct sieve_ast_node *node, int level);
+
+static void sieve_ast_unparse_tests
+(struct sieve_ast_node *node, int level)
+{
+	struct sieve_ast_node *test;
+
+	if ( sieve_ast_test_count(node) > 1 ) {
+		int i;
+
+		printf(" (\n");
+
+		/* Create indent */
+		for ( i = 0; i < level+2; i++ )
+			printf("  ");
+
+		test = sieve_ast_test_first(node);
+		sieve_ast_unparse_test(test, level+1);
+
+		test = sieve_ast_test_next(test);
+		while ( test != NULL ) {
+			printf(", \n");
+			for ( i = 0; i < level+2; i++ )
+				printf("  ");
+			sieve_ast_unparse_test(test, level+1);
+		  test = sieve_ast_test_next(test);
+	  }
+
+		printf(" )");
+	} else {
+		test = sieve_ast_test_first(node);
+		if ( test != NULL )
+			sieve_ast_unparse_test(test, level);
+	}
+}
+
+static void sieve_ast_unparse_test
+(struct sieve_ast_node *node, int level)
+{
+	struct sieve_ast_argument *argument;
+
+	printf(" %s", node->identifier);
+
+	argument = sieve_ast_argument_first(node);
+	while ( argument != NULL ) {
+		printf(" ");
+		sieve_ast_unparse_argument(argument, level);
+		argument = sieve_ast_argument_next(argument);
+	}
+
+	sieve_ast_unparse_tests(node, level);
+}
+
+static void sieve_ast_unparse_command
+(struct sieve_ast_node *node, int level)
+{
+	struct sieve_ast_node *command;
+	struct sieve_ast_argument *argument;
+
+	int i;
+
+	/* Create indent */
+	for ( i = 0; i < level; i++ )
+		printf("  ");
+
+	printf("%s", node->identifier);
+
+	argument = sieve_ast_argument_first(node);
+	while ( argument != NULL ) {
+		printf(" ");
+		sieve_ast_unparse_argument(argument, level);
+		argument = sieve_ast_argument_next(argument);
+	}
+
+	sieve_ast_unparse_tests(node, level);
+
+	command = sieve_ast_command_first(node);
+	if ( command != NULL ) {
+		printf(" {\n");
+
+		while ( command != NULL) {
+			sieve_ast_unparse_command(command, level+1);
+			command = sieve_ast_command_next(command);
+		}
+
+		for ( i = 0; i < level; i++ )
+			printf("  ");
+		printf("}\n");
+	} else
+		printf(";\n");
+}
+
+void sieve_ast_unparse(struct sieve_ast *ast)
+{
+	struct sieve_ast_node *command;
+
+	printf("Unparsing Abstract Syntax Tree:\n");
+
+	T_BEGIN {
+		command = sieve_ast_command_first(sieve_ast_root(ast));
+		while ( command != NULL ) {
+			sieve_ast_unparse_command(command, 0);
+			command = sieve_ast_command_next(command);
+		}
+	} T_END;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-ast.h
@@ -0,0 +1,371 @@
+#ifndef SIEVE_AST_H
+#define SIEVE_AST_H
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+
+/*
+	Abstract Syntax Tree (AST) structure:
+
+	sieve_ast (root)
+	[*command]
+	 |
+	 +-- command:
+	 |   ....
+	 +-- command:
+	 |	 [identifier *argument                      *test *command]
+	 |                +-- argument:                 |     \--> as from root
+	 |                |   ....                      |
+ 	 |                +-- argument:                 V (continued below)
+	 |                |   [number | tag | *string]
+	 |                .
+	 .
+
+	 *test
+	 +-- test:
+	 |   ....
+	 +-- test:
+	 |   [identifier *argument                     *test]
+	 |               +-- argument:                 \-->  as from the top
+	 .               |   ....                              of this tree
+	                 +-- argument:
+	                 |   [number | tag | *string]
+	                 .
+
+	 Tests and commands are defined using the same structure: sieve_ast_node.
+	 However, arguments and string-lists are described using sieve_ast_argument.
+*/
+
+/* IMPORTANT NOTICE: Do not decorate the AST with objects other than those
+ * allocated on the ast's pool or static const objects. Otherwise it is possible
+ * that pointers in the tree become dangling which is highly undesirable.
+ */
+
+/*
+ * Forward declarations
+ */
+
+struct sieve_ast_list;
+struct sieve_ast_arg_list;
+
+/*
+ * Types
+ */
+
+enum sieve_ast_argument_type {
+	SAAT_NONE,
+	SAAT_NUMBER,
+	SAAT_STRING,
+	SAAT_STRING_LIST,
+	SAAT_TAG,
+};
+
+enum sieve_ast_type {
+	SAT_NONE,
+	SAT_ROOT,
+	SAT_COMMAND,
+	SAT_TEST,
+};
+
+/*
+ * AST Nodes
+ */
+
+/* Argument node */
+
+struct sieve_ast_argument {
+	enum sieve_ast_argument_type type;
+
+	/* Back reference to the AST object */
+	struct sieve_ast *ast;
+
+	/* List related */
+	struct sieve_ast_arg_list *list;
+	struct sieve_ast_argument *next;
+	struct sieve_ast_argument *prev;
+
+	/* Parser-assigned data */
+
+	union {
+		string_t *str;
+		struct sieve_ast_arg_list *strlist;
+		const char *tag;
+		unsigned int number;
+	} _value;
+
+	unsigned int source_line;
+
+	/* Assigned during validation */
+
+	/* Argument associated with this ast element  */
+	struct sieve_argument *argument;
+
+	/* Parameters to this (tag) argument */
+	struct sieve_ast_argument *parameters;
+};
+
+struct sieve_ast_node {
+	enum sieve_ast_type type;
+
+	/* Back reference to the AST object */
+	struct sieve_ast *ast;
+
+	/* Back reference to this node's parent */
+	struct sieve_ast_node *parent;
+
+	/* Linked list references */
+	struct sieve_ast_list *list;
+	struct sieve_ast_node *next;
+	struct sieve_ast_node *prev;
+
+	/* Commands (NULL if not allocated) */
+	bool block;
+	struct sieve_ast_list *commands;
+
+	/* Tests (NULL if not allocated)*/
+	bool test_list;
+	struct sieve_ast_list *tests;
+
+	/* Arguments (NULL if not allocated) */
+	struct sieve_ast_arg_list *arguments;
+
+	/* Identifier of command or test */
+	const char *identifier;
+
+	/* The location in the file where this command was started */
+	unsigned int source_line;
+
+	/* Assigned during validation */
+
+	/* Context */
+	struct sieve_command *command;
+};
+
+/*
+ * AST node lists
+ */
+
+struct sieve_ast_list {
+	struct sieve_ast_node *head;
+	struct sieve_ast_node *tail;
+	unsigned int len;
+};
+
+struct sieve_ast_arg_list {
+	struct sieve_ast_argument *head;
+	struct sieve_ast_argument *tail;
+	unsigned int len;
+};
+
+/*
+ * AST object
+ */
+
+struct sieve_ast;
+
+struct sieve_ast *sieve_ast_create(struct sieve_script *script);
+void sieve_ast_ref(struct sieve_ast *ast);
+void sieve_ast_unref(struct sieve_ast **ast);
+
+struct sieve_ast_node *sieve_ast_root(struct sieve_ast *ast);
+pool_t sieve_ast_pool(struct sieve_ast *ast);
+struct sieve_script *sieve_ast_script(struct sieve_ast *ast);
+
+/* Extension support */
+
+struct sieve_ast_extension {
+	const struct sieve_extension_def *ext;
+
+	void (*free)(const struct sieve_extension *ext, struct sieve_ast *ast,
+		void *context);
+};
+
+void sieve_ast_extension_link
+	(struct sieve_ast *ast, const struct sieve_extension *ext,
+		bool required);
+const struct sieve_extension * const *sieve_ast_extensions_get
+	(struct sieve_ast *ast, unsigned int *count_r);
+
+void sieve_ast_extension_register
+	(struct sieve_ast *ast, const struct sieve_extension *ext,
+		const struct sieve_ast_extension *ast_ext, void *context);
+void sieve_ast_extension_set_context
+	(struct sieve_ast *ast, const struct sieve_extension *ext, void *context);
+void *sieve_ast_extension_get_context
+	(struct sieve_ast *ast, const struct sieve_extension *ext);
+
+bool sieve_ast_extension_is_required
+	(struct sieve_ast *ast, const struct sieve_extension *ext);
+
+/*
+ * AST node manipulation
+ */
+
+/* Command nodes */
+
+struct sieve_ast_node *sieve_ast_test_create
+	(struct sieve_ast_node *parent, const char *identifier,
+		unsigned int source_line);
+struct sieve_ast_node *sieve_ast_command_create
+	(struct sieve_ast_node *parent, const char *identifier,
+		unsigned int source_line);
+
+struct sieve_ast_node *sieve_ast_node_detach
+	(struct sieve_ast_node *first);
+
+const char *sieve_ast_type_name(enum sieve_ast_type ast_type);
+
+/* Argument nodes */
+
+struct sieve_ast_argument *sieve_ast_argument_create
+	(struct sieve_ast *ast, unsigned int source_line);
+
+struct sieve_ast_arg_list *sieve_ast_arg_list_create(pool_t pool);
+bool sieve_ast_arg_list_add
+	(struct sieve_ast_arg_list *list, struct sieve_ast_argument *argument);
+bool sieve_ast_arg_list_insert
+	(struct sieve_ast_arg_list *list, struct sieve_ast_argument *before,
+		struct sieve_ast_argument *argument);
+void sieve_ast_arg_list_substitute
+	(struct sieve_ast_arg_list *list, struct sieve_ast_argument *argument,
+		struct sieve_ast_argument *replacement);
+
+struct sieve_ast_argument *sieve_ast_argument_string_create_raw
+	(struct sieve_ast *ast, string_t *str, unsigned int source_line);
+struct sieve_ast_argument *sieve_ast_argument_string_create
+	(struct sieve_ast_node *node, const string_t *str, unsigned int source_line);
+struct sieve_ast_argument *sieve_ast_argument_cstring_create
+	(struct sieve_ast_node *node, const char *str, unsigned int source_line);
+
+struct sieve_ast_argument *sieve_ast_argument_tag_create
+	(struct sieve_ast_node *node, const char *tag, unsigned int source_line);
+
+struct sieve_ast_argument *sieve_ast_argument_number_create
+	(struct sieve_ast_node *node, unsigned int number, unsigned int source_line);
+
+void sieve_ast_argument_string_set
+	(struct sieve_ast_argument *argument, string_t *newstr);
+void sieve_ast_argument_string_setc
+	(struct sieve_ast_argument *argument, const char *newstr);
+
+void sieve_ast_argument_number_set
+	(struct sieve_ast_argument *argument, unsigned int newnum);
+void sieve_ast_argument_number_substitute
+	(struct sieve_ast_argument *argument, unsigned int number);
+
+struct sieve_ast_argument *sieve_ast_argument_tag_insert
+(struct sieve_ast_argument *before, const char *tag, unsigned int source_line);
+
+struct sieve_ast_argument *sieve_ast_argument_stringlist_create
+	(struct sieve_ast_node *node, unsigned int source_line);
+struct sieve_ast_argument *sieve_ast_argument_stringlist_substitute
+	(struct sieve_ast_node *node, struct sieve_ast_argument *arg);
+
+struct sieve_ast_argument *sieve_ast_arguments_detach
+	(struct sieve_ast_argument *first, unsigned int count);
+bool sieve_ast_argument_attach
+	(struct sieve_ast_node *node, struct sieve_ast_argument *argument);
+
+const char *sieve_ast_argument_type_name(enum sieve_ast_argument_type arg_type);
+#define sieve_ast_argument_name(argument) \
+	sieve_ast_argument_type_name((argument)->type)
+
+bool sieve_ast_stringlist_add
+	(struct sieve_ast_argument *list, const string_t *str,
+		unsigned int source_line);
+bool sieve_ast_stringlist_add_strc
+	(struct sieve_ast_argument *list, const char *str,
+		unsigned int source_line);
+
+/*
+ * Utility
+ */
+
+int sieve_ast_stringlist_map
+	(struct sieve_ast_argument **listitem, void *context,
+		int (*map_function)(void *context, struct sieve_ast_argument *arg));
+struct sieve_ast_argument *sieve_ast_stringlist_join
+	(struct sieve_ast_argument *list, struct sieve_ast_argument *items);
+
+/*
+ * AST access macros
+ */
+
+/* Generic list access macros */
+#define __AST_LIST_FIRST(list) \
+	((list) == NULL ? NULL : (list)->head)
+#define __AST_LIST_LAST(list) \
+	((list) == NULL ? NULL : (list)->tail)
+#define __AST_LIST_COUNT(list) \
+	((list) == NULL || (list)->head == NULL ? 0 : (list)->len)
+#define __AST_LIST_NEXT(item) ((item)->next)
+#define __AST_LIST_PREV(item) ((item)->prev)
+
+#define __AST_NODE_LIST_FIRST(node, list) __AST_LIST_FIRST((node)->list)
+#define __AST_NODE_LIST_LAST(node, list) __AST_LIST_LAST((node)->list)
+#define __AST_NODE_LIST_COUNT(node, list) __AST_LIST_COUNT((node)->list)
+
+/* AST macros */
+
+/* AST node macros */
+#define sieve_ast_node_pool(node) (sieve_ast_pool((node)->ast))
+#define sieve_ast_node_parent(node) ((node)->parent)
+#define sieve_ast_node_prev(node) __AST_LIST_PREV(node)
+#define sieve_ast_node_next(node) __AST_LIST_NEXT(node)
+#define sieve_ast_node_type(node) ((node) == NULL ? SAT_NONE : (node)->type)
+#define sieve_ast_node_line(node) ((node) == NULL ? 0 : (node)->source_line)
+
+/* AST command node macros */
+#define sieve_ast_command_first(node) __AST_NODE_LIST_FIRST(node, commands)
+#define sieve_ast_command_count(node) __AST_NODE_LIST_COUNT(node, commands)
+#define sieve_ast_command_prev(command) __AST_LIST_PREV(command)
+#define sieve_ast_command_next(command) __AST_LIST_NEXT(command)
+
+/* Compare the identifier of the previous command */
+#define sieve_ast_prev_cmd_is(cmd, id) \
+	( (cmd)->prev == NULL ? FALSE : \
+		strncasecmp((cmd)->prev->identifier, id, sizeof(id)-1) == 0 )
+
+/* AST test macros */
+#define sieve_ast_test_count(node) __AST_NODE_LIST_COUNT(node, tests)
+#define sieve_ast_test_first(node) __AST_NODE_LIST_FIRST(node, tests)
+#define sieve_ast_test_next(test) __AST_LIST_NEXT(test)
+
+/* AST argument macros */
+#define sieve_ast_argument_pool(node) (sieve_ast_pool((node)->ast))
+#define sieve_ast_argument_first(node) __AST_NODE_LIST_FIRST(node, arguments)
+#define sieve_ast_argument_last(node) __AST_NODE_LIST_LAST(node, arguments)
+#define sieve_ast_argument_count(node) __AST_NODE_LIST_COUNT(node, arguments)
+#define sieve_ast_argument_prev(argument) __AST_LIST_PREV(argument)
+#define sieve_ast_argument_next(argument) __AST_LIST_NEXT(argument)
+#define sieve_ast_argument_type(argument) (argument)->type
+#define sieve_ast_argument_line(argument) (argument)->source_line
+
+#define sieve_ast_argument_str(argument) ((argument)->_value.str)
+#define sieve_ast_argument_strc(argument) (str_c((argument)->_value.str))
+#define sieve_ast_argument_tag(argument) ((argument)->_value.tag)
+#define sieve_ast_argument_number(argument) ((argument)->_value.number)
+
+/* AST string list macros */
+// @UNSAFE: should check whether we are actually accessing a string list
+#define sieve_ast_strlist_first(list) \
+	__AST_NODE_LIST_FIRST(list, _value.strlist)
+#define sieve_ast_strlist_last(list) \
+	__AST_NODE_LIST_LAST(list, _value.strlist)
+#define sieve_ast_strlist_count(list) \
+	__AST_NODE_LIST_COUNT(list, _value.strlist)
+#define sieve_ast_strlist_next(str) __AST_LIST_NEXT(str)
+#define sieve_ast_strlist_prev(str) __AST_LIST_PREV(str)
+#define sieve_ast_strlist_str(str) sieve_ast_argument_str(str)
+#define sieve_ast_strlist_strc(str) sieve_ast_argument_strc(str)
+
+/*
+ * Debug
+ */
+
+void sieve_ast_unparse(struct sieve_ast *ast);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-binary-code.c
@@ -0,0 +1,404 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "mempool.h"
+#include "buffer.h"
+#include "hash.h"
+#include "array.h"
+#include "ostream.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-extensions.h"
+#include "sieve-code.h"
+#include "sieve-script.h"
+
+#include "sieve-binary-private.h"
+
+/*
+ * Forward declarations
+ */
+
+static inline sieve_size_t sieve_binary_emit_dynamic_data
+	(struct sieve_binary_block *sblock, const void *data, size_t size);
+
+/*
+ * Emission functions
+ */
+
+/* Low-level emission functions */
+
+static inline void _sieve_binary_emit_data
+(struct sieve_binary_block *sblock, const void *data, sieve_size_t size)
+{
+	buffer_append(sblock->data, data, size);
+}
+
+static inline void _sieve_binary_emit_byte
+(struct sieve_binary_block *sblock, uint8_t byte)
+{
+	_sieve_binary_emit_data(sblock, &byte, 1);
+}
+
+static inline void _sieve_binary_update_data
+(struct sieve_binary_block *sblock, sieve_size_t address, const void *data,
+	sieve_size_t size)
+{
+	buffer_write(sblock->data, address, data, size);
+}
+
+sieve_size_t sieve_binary_emit_data
+(struct sieve_binary_block *sblock, const void *data, sieve_size_t size)
+{
+	sieve_size_t address = _sieve_binary_block_get_size(sblock);
+
+	_sieve_binary_emit_data(sblock, data, size);
+
+	return address;
+}
+
+sieve_size_t sieve_binary_emit_byte
+(struct sieve_binary_block *sblock, uint8_t byte)
+{
+	sieve_size_t address = _sieve_binary_block_get_size(sblock);
+
+	_sieve_binary_emit_data(sblock, &byte, 1);
+
+	return address;
+}
+
+void sieve_binary_update_data
+(struct sieve_binary_block *sblock, sieve_size_t address, const void *data,
+	sieve_size_t size)
+{
+	_sieve_binary_update_data(sblock, address, data, size);
+}
+
+/* Offset emission functions */
+
+sieve_size_t sieve_binary_emit_offset
+(struct sieve_binary_block *sblock, sieve_offset_t offset)
+{
+	sieve_size_t address = _sieve_binary_block_get_size(sblock);
+	uint8_t encoded[sizeof(offset)];
+	int i;
+
+	for ( i = sizeof(offset)-1; i >= 0; i-- ) {
+		encoded[i] = (uint8_t)offset;
+		offset >>= 8;
+	}
+
+	_sieve_binary_emit_data(sblock, encoded, sizeof(offset));
+
+	return address;
+}
+
+void sieve_binary_resolve_offset
+(struct sieve_binary_block *sblock, sieve_size_t address)
+{
+	sieve_size_t cur_address = _sieve_binary_block_get_size(sblock);
+	sieve_offset_t offset;
+	uint8_t encoded[sizeof(offset)];
+	int i;
+
+	i_assert(cur_address > address);
+	i_assert((cur_address - address) <= (sieve_offset_t)-1);
+	offset = cur_address - address;
+	for ( i = sizeof(offset)-1; i >= 0; i-- ) {
+		encoded[i] = (uint8_t)offset;
+		offset >>= 8;
+	}
+
+	_sieve_binary_update_data
+		(sblock, address, encoded, sizeof(offset));
+}
+
+/* Literal emission */
+
+sieve_size_t sieve_binary_emit_integer
+(struct sieve_binary_block *sblock, sieve_number_t integer)
+{
+	sieve_size_t address = _sieve_binary_block_get_size(sblock);
+	uint8_t buffer[sizeof(sieve_number_t) + 1];
+	int bufpos = sizeof(buffer) - 1;
+
+	/* Encode last byte [0xxxxxxx]; msb == 0 marks the last byte */
+	buffer[bufpos] = integer & 0x7F;
+	bufpos--;
+
+	/* Encode first bytes [1xxxxxxx] */
+	integer >>= 7;
+	while ( integer > 0 ) {
+		buffer[bufpos] = (integer & 0x7F) | 0x80;
+		bufpos--;
+		integer >>= 7;
+	}
+
+	/* Emit encoded integer */
+	bufpos++;
+	_sieve_binary_emit_data(sblock, buffer + bufpos, sizeof(buffer) - bufpos);
+
+	return address;
+}
+
+static inline sieve_size_t sieve_binary_emit_dynamic_data
+(struct sieve_binary_block *sblock, const void *data, sieve_size_t size)
+{
+	sieve_size_t address = sieve_binary_emit_integer
+		(sblock, (sieve_number_t) size);
+
+	_sieve_binary_emit_data(sblock, data, size);
+
+	return address;
+}
+
+sieve_size_t sieve_binary_emit_cstring
+(struct sieve_binary_block *sblock, const char *str)
+{
+	sieve_size_t address = sieve_binary_emit_dynamic_data
+		(sblock, (void *) str, (sieve_size_t) strlen(str));
+	_sieve_binary_emit_byte(sblock, 0);
+
+	return address;
+}
+
+sieve_size_t sieve_binary_emit_string
+(struct sieve_binary_block *sblock, const string_t *str)
+{
+	sieve_size_t address = sieve_binary_emit_dynamic_data
+		(sblock, (void *) str_data(str), (sieve_size_t) str_len(str));
+	_sieve_binary_emit_byte(sblock, 0);
+
+	return address;
+}
+
+/*
+ * Extension emission
+ */
+
+sieve_size_t sieve_binary_emit_extension
+(struct sieve_binary_block *sblock, const struct sieve_extension *ext,
+	unsigned int offset)
+{
+	sieve_size_t address = _sieve_binary_block_get_size(sblock);
+	struct sieve_binary_extension_reg *ereg = NULL;
+
+	(void)sieve_binary_extension_register(sblock->sbin, ext, &ereg);
+
+	i_assert(ereg != NULL);
+
+	_sieve_binary_emit_byte(sblock, offset + ereg->index);
+	return address;
+}
+
+void sieve_binary_emit_extension_object
+(struct sieve_binary_block *sblock, const struct sieve_extension_objects *objs,
+	unsigned int code)
+{
+	if ( objs->count > 1 )
+		_sieve_binary_emit_byte(sblock, code);
+}
+
+/*
+ * Code retrieval
+ */
+
+#define ADDR_CODE_READ(block) \
+	size_t _code_size; \
+	const int8_t *_code = buffer_get_data((block)->data, &_code_size)
+
+#define ADDR_CODE_AT(address) \
+	((int8_t) (_code[*address]))
+#define ADDR_DATA_AT(address) \
+	((uint8_t) (_code[*address]))
+#define ADDR_POINTER(address) \
+	((const int8_t *) (&_code[*address]))
+
+#define ADDR_BYTES_LEFT(address) \
+	((*address) > _code_size ? 0 : ((_code_size) - (*address)))
+#define ADDR_JUMP(address, offset) \
+	(*address) += offset
+
+/* Literals */
+
+bool sieve_binary_read_byte
+(struct sieve_binary_block *sblock, sieve_size_t *address, unsigned int *byte_r)
+{
+	ADDR_CODE_READ(sblock);
+
+	if ( ADDR_BYTES_LEFT(address) >= 1 ) {
+		if ( byte_r != NULL )
+			*byte_r = ADDR_DATA_AT(address);
+		ADDR_JUMP(address, 1);
+
+		return TRUE;
+	}
+
+	if ( byte_r != NULL )
+		*byte_r = 0;
+	return FALSE;
+}
+
+bool sieve_binary_read_code
+(struct sieve_binary_block *sblock, sieve_size_t *address, signed int *code_r)
+{
+	ADDR_CODE_READ(sblock);
+
+	if ( ADDR_BYTES_LEFT(address) >= 1 ) {
+		if ( code_r != NULL )
+			*code_r = ADDR_CODE_AT(address);
+		ADDR_JUMP(address, 1);
+
+		return TRUE;
+	}
+
+	if ( code_r != NULL )
+		*code_r = 0;
+	return FALSE;
+}
+
+
+bool sieve_binary_read_offset
+(struct sieve_binary_block *sblock, sieve_size_t *address, sieve_offset_t *offset_r)
+{
+	sieve_offset_t offs = 0;
+	ADDR_CODE_READ(sblock);
+
+	if ( ADDR_BYTES_LEFT(address) >= 4 ) {
+		int i;
+
+		for ( i = 0; i < 4; i++ ) {
+			offs = (offs << 8) + ADDR_DATA_AT(address);
+			ADDR_JUMP(address, 1);
+		}
+
+		if ( offset_r != NULL )
+			*offset_r = offs;
+
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+/* FIXME: might need negative numbers in the future */
+bool sieve_binary_read_integer
+(struct sieve_binary_block *sblock, sieve_size_t *address, sieve_number_t *int_r)
+{
+	int bits = sizeof(sieve_number_t) * 8;
+	sieve_number_t integer = 0;
+
+	ADDR_CODE_READ(sblock);
+
+	if ( ADDR_BYTES_LEFT(address) == 0 )
+		return FALSE;
+
+	/* Read first integer bytes [1xxxxxxx] */
+	while ( (ADDR_DATA_AT(address) & 0x80) > 0 ) {
+		if ( ADDR_BYTES_LEFT(address) > 0 && bits > 0) {
+			integer |= ADDR_DATA_AT(address) & 0x7F;
+			ADDR_JUMP(address, 1);
+
+			/* Each byte encodes 7 bits of the integer */
+			integer <<= 7;
+			bits -= 7;
+		} else {
+			/* This is an error */
+			return FALSE;
+		}
+	}
+
+	/* Read last byte [0xxxxxxx] */
+	integer |= ADDR_DATA_AT(address) & 0x7F;
+	ADDR_JUMP(address, 1);
+
+	if ( int_r != NULL )
+		*int_r = integer;
+	return TRUE;
+}
+
+bool sieve_binary_read_string
+(struct sieve_binary_block *sblock, sieve_size_t *address, string_t **str_r)
+{
+	unsigned int strlen = 0;
+	const char *strdata;
+
+	ADDR_CODE_READ(sblock);
+
+	if ( !sieve_binary_read_unsigned(sblock, address, &strlen) )
+		return FALSE;
+
+	if ( strlen > ADDR_BYTES_LEFT(address) )
+		return FALSE;
+
+	strdata = (const char *) ADDR_POINTER(address);
+	ADDR_JUMP(address, strlen);
+
+	if ( ADDR_CODE_AT(address) != 0 )
+		return FALSE;
+
+ 	if ( str_r != NULL )
+		*str_r = t_str_new_const(strdata, strlen);
+
+	ADDR_JUMP(address, 1);
+
+	return TRUE;
+}
+
+bool sieve_binary_read_extension
+(struct sieve_binary_block *sblock, sieve_size_t *address,
+	unsigned int *offset_r, const struct sieve_extension **ext_r)
+{
+	unsigned int code;
+	unsigned int offset = *offset_r;
+	const struct sieve_extension *ext = NULL;
+
+	ADDR_CODE_READ(sblock);
+
+	if ( ADDR_BYTES_LEFT(address) == 0 )
+		return FALSE;
+
+	(*offset_r) = code = ADDR_DATA_AT(address);
+	ADDR_JUMP(address, 1);
+
+	if ( code >= offset ) {
+		ext = sieve_binary_extension_get_by_index(sblock->sbin, code - offset);
+
+		if ( ext == NULL )
+			return FALSE;
+	}
+
+	if ( ext_r != NULL )
+		(*ext_r) = ext;
+
+	return TRUE;
+}
+
+const void *sieve_binary_read_extension_object
+(struct sieve_binary_block *sblock, sieve_size_t *address,
+	const struct sieve_extension_objects *objs)
+{
+	unsigned int code;
+
+	ADDR_CODE_READ(sblock);
+
+	if ( objs->count == 0 )
+		return NULL;
+
+	if ( objs->count == 1 )
+		return objs->objects;
+
+	if ( ADDR_BYTES_LEFT(address) == 0 )
+		return NULL;
+
+	code = ADDR_DATA_AT(address);
+	ADDR_JUMP(address, 1);
+
+	if ( code >= objs->count )
+		return NULL;
+
+	return ((const void *const *) objs->objects)[code];
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-binary-debug.c
@@ -0,0 +1,256 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-code.h"
+
+#include "sieve-binary-private.h"
+
+/* Quick 'n dirty debug */
+#if 0
+#define debug_printf(...) printf ("lineinfo: " __VA_ARGS__)
+#else
+#define debug_printf(...)
+#endif
+
+/*
+ * Opcodes
+ */
+
+enum {
+	LINPROG_OP_COPY,
+	LINPROG_OP_ADVANCE_PC,
+	LINPROG_OP_ADVANCE_LINE,
+	LINPROG_OP_SET_COLUMN,
+	LINPROG_OP_SPECIAL_BASE
+};
+
+#define LINPROG_LINE_BASE   0
+#define LINPROG_LINE_RANGE  4
+
+/*
+ * Lineinfo writer
+ */
+
+struct sieve_binary_debug_writer {
+	struct sieve_binary_block *sblock;
+
+	sieve_size_t address;
+	unsigned long int line;
+	unsigned long int column;
+};
+
+struct sieve_binary_debug_writer *sieve_binary_debug_writer_init
+(struct sieve_binary_block *sblock)
+{
+	struct sieve_binary_debug_writer *dwriter;
+
+	dwriter = i_new(struct sieve_binary_debug_writer, 1);
+	dwriter->sblock = sblock;
+
+	return dwriter;
+}
+
+void sieve_binary_debug_writer_deinit
+(struct sieve_binary_debug_writer **dwriter)
+{
+	i_free(*dwriter);
+	*dwriter = NULL;
+}
+
+void sieve_binary_debug_emit
+(struct sieve_binary_debug_writer *dwriter, sieve_size_t code_address,
+	unsigned int code_line, unsigned int code_column)
+{
+	struct sieve_binary_block *sblock = dwriter->sblock;
+	sieve_size_t address_inc = code_address - dwriter->address;
+	unsigned int line_inc = code_line - dwriter->line;
+	unsigned int sp_opcode = 0;
+
+	/* Check for applicability of special opcode */
+	if ( (LINPROG_LINE_BASE + LINPROG_LINE_RANGE - 1) >= line_inc ) {
+		sp_opcode = LINPROG_OP_SPECIAL_BASE + (line_inc - LINPROG_LINE_BASE) +
+			(LINPROG_LINE_RANGE * address_inc);
+
+		if ( sp_opcode > 255 )
+			sp_opcode = 0;
+	}
+
+	/* Update line and address */
+	if ( sp_opcode == 0 ) {
+		if ( line_inc > 0 ) {
+			(void)sieve_binary_emit_byte(sblock, LINPROG_OP_ADVANCE_LINE);
+			(void)sieve_binary_emit_unsigned(sblock, line_inc);
+		}
+
+		if ( address_inc > 0 ) {
+			(void)sieve_binary_emit_byte(sblock, LINPROG_OP_ADVANCE_PC);
+			(void)sieve_binary_emit_unsigned(sblock, address_inc);
+		}
+	} else {
+		(void)sieve_binary_emit_byte(sblock, sp_opcode);
+	}
+
+	/* Set column */
+	if ( dwriter->column != code_column ) {
+		(void)sieve_binary_emit_byte(sblock, LINPROG_OP_SET_COLUMN);
+		(void)sieve_binary_emit_unsigned(sblock, code_column);
+	}
+
+	/* Generate matrix row */
+	(void)sieve_binary_emit_byte(sblock, LINPROG_OP_COPY);
+
+	dwriter->address = code_address;
+	dwriter->line = code_line;
+	dwriter->column = code_column;
+}
+
+/*
+ * Debug reader
+ */
+
+struct sieve_binary_debug_reader {
+	struct sieve_binary_block *sblock;
+
+	sieve_size_t address, last_address;
+	unsigned long int line, last_line;
+
+	unsigned long int column;
+
+	sieve_size_t state;
+};
+
+struct sieve_binary_debug_reader *sieve_binary_debug_reader_init
+(struct sieve_binary_block *sblock)
+{
+	struct sieve_binary_debug_reader *dreader;
+
+	dreader = i_new(struct sieve_binary_debug_reader, 1);
+	dreader->sblock = sblock;
+
+	return dreader;
+}
+
+void sieve_binary_debug_reader_deinit
+(struct sieve_binary_debug_reader **dreader)
+{
+	i_free(*dreader);
+	*dreader = NULL;
+}
+
+void sieve_binary_debug_reader_reset
+(struct sieve_binary_debug_reader *dreader)
+{
+	dreader->address = 0;
+	dreader->line = 0;
+	dreader->column = 0;
+	dreader->state = 0;
+}
+
+unsigned int sieve_binary_debug_read_line
+(struct sieve_binary_debug_reader *dreader, sieve_size_t code_address)
+{
+	size_t linprog_size;
+	sieve_size_t address;
+	unsigned long int line;
+
+	if ( code_address < dreader->last_address )
+		sieve_binary_debug_reader_reset(dreader);
+
+	if ( code_address >= dreader->last_address &&
+		code_address < dreader->address ) {
+		debug_printf("%08llx: NOOP [%08llx]\n",
+			(unsigned long long) dreader->state, (unsigned long long) code_address);
+		return dreader->last_line;
+	}
+
+	address = dreader->address;
+	line = dreader->line;
+
+	debug_printf("%08llx: READ [%08llx]\n",
+		(unsigned long long) dreader->state, (unsigned long long) code_address);
+
+	linprog_size = sieve_binary_block_get_size(dreader->sblock);
+	while ( dreader->state < linprog_size ) {
+		unsigned int opcode;
+		unsigned int value;
+
+		if ( sieve_binary_read_byte(dreader->sblock, &dreader->state, &opcode) ) {
+			switch ( opcode ) {
+
+			case LINPROG_OP_COPY:
+				debug_printf("%08llx: COPY ==> %08llx: %ld\n",
+					(unsigned long long) dreader->state, (unsigned long long) address,
+					line);
+
+				dreader->last_address = dreader->address;
+				dreader->last_line = dreader->line;
+
+				dreader->address = address;
+				dreader->line = line;
+
+				if ( code_address < address ) {
+					return dreader->last_line;
+				} else if ( code_address == address ) {
+					return dreader->line;
+				}
+				break;
+
+			case LINPROG_OP_ADVANCE_PC:
+				debug_printf("%08llx: ADV_PC\n", (unsigned long long) dreader->state);
+				if ( !sieve_binary_read_unsigned
+					(dreader->sblock, &dreader->state, &value) ) {
+					sieve_binary_debug_reader_reset(dreader);
+					return 0;
+				}
+				debug_printf("        : + %d\n", value);
+				address += value;
+				break;
+
+			case LINPROG_OP_ADVANCE_LINE:
+				debug_printf("%08llx: ADV_LINE\n", (unsigned long long) dreader->state);
+				if ( !sieve_binary_read_unsigned
+					(dreader->sblock, &dreader->state, &value) ) {
+					sieve_binary_debug_reader_reset(dreader);
+					return 0;
+				}
+				debug_printf("        : + %d\n", value);
+				line += value;
+				break;
+
+			case LINPROG_OP_SET_COLUMN:
+				debug_printf("%08llx: SET_COL\n", (unsigned long long) dreader->state);
+				if ( !sieve_binary_read_unsigned
+					(dreader->sblock, &dreader->state, &value) ) {
+					sieve_binary_debug_reader_reset(dreader);
+					return 0;
+				}
+				debug_printf("        : = %d\n", value);
+				dreader->column = value;
+				break;
+
+			default:
+				opcode -= LINPROG_OP_SPECIAL_BASE;
+
+				address += (opcode / LINPROG_LINE_RANGE);
+				line += LINPROG_LINE_BASE + (opcode % LINPROG_LINE_RANGE);
+
+				debug_printf("%08llx: SPECIAL\n", (unsigned long long) dreader->state);
+				debug_printf("        :  +A %d +L %d\n", (opcode / LINPROG_LINE_RANGE),
+					LINPROG_LINE_BASE + (opcode % LINPROG_LINE_RANGE));
+				break;
+			}
+		} else {
+			debug_printf("OPCODE READ FAILED\n");
+			sieve_binary_debug_reader_reset(dreader);
+			return 0;
+		}
+	}
+
+	return dreader->line;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-binary-dumper.c
@@ -0,0 +1,291 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "ostream.h"
+#include "array.h"
+#include "buffer.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-dump.h"
+#include "sieve-script.h"
+
+#include "sieve-binary-private.h"
+
+/*
+ * Binary dumper object
+ */
+
+struct sieve_binary_dumper {
+	pool_t pool;
+
+	/* Dumptime environment */
+	struct sieve_dumptime_env dumpenv;
+};
+
+struct sieve_binary_dumper *sieve_binary_dumper_create
+(struct sieve_binary *sbin)
+{
+	pool_t pool;
+	struct sieve_binary_dumper *dumper;
+
+	pool = pool_alloconly_create("sieve_binary_dumper", 4096);
+	dumper = p_new(pool, struct sieve_binary_dumper, 1);
+	dumper->pool = pool;
+	dumper->dumpenv.dumper = dumper;
+
+	dumper->dumpenv.sbin = sbin;
+	sieve_binary_ref(sbin);
+
+	dumper->dumpenv.svinst = sieve_binary_svinst(sbin);
+
+	return dumper;
+}
+
+void sieve_binary_dumper_free(struct sieve_binary_dumper **dumper)
+{
+	sieve_binary_unref(&(*dumper)->dumpenv.sbin);
+	pool_unref(&((*dumper)->pool));
+
+	*dumper = NULL;
+}
+
+pool_t sieve_binary_dumper_pool(struct sieve_binary_dumper *dumper)
+{
+	return dumper->pool;
+}
+
+/*
+ * Formatted output
+ */
+
+void sieve_binary_dumpf
+(const struct sieve_dumptime_env *denv, const char *fmt, ...)
+{
+	string_t *outbuf = t_str_new(128);
+	va_list args;
+
+	va_start(args, fmt);
+	str_vprintfa(outbuf, fmt, args);
+	va_end(args);
+
+	o_stream_nsend(denv->stream, str_data(outbuf), str_len(outbuf));
+}
+
+void sieve_binary_dump_sectionf
+(const struct sieve_dumptime_env *denv, const char *fmt, ...)
+{
+	string_t *outbuf = t_str_new(128);
+	va_list args;
+
+	va_start(args, fmt);
+	str_printfa(outbuf, "\n* ");
+	str_vprintfa(outbuf, fmt, args);
+	str_printfa(outbuf, ":\n\n");
+	va_end(args);
+
+	o_stream_nsend(denv->stream, str_data(outbuf), str_len(outbuf));
+}
+
+/*
+ * Dumping the binary
+ */
+
+bool sieve_binary_dumper_run
+(struct sieve_binary_dumper *dumper, struct ostream *stream, bool verbose)
+{
+	struct sieve_binary *sbin = dumper->dumpenv.sbin;
+	struct sieve_script *script = sieve_binary_script(sbin);
+	struct sieve_dumptime_env *denv = &(dumper->dumpenv);
+	struct sieve_binary_block *sblock;
+	bool success = TRUE;
+	sieve_size_t offset;
+	int count, i;
+
+	dumper->dumpenv.stream = stream;
+
+	/* Dump list of binary blocks */
+
+	if ( verbose ) {
+		count = sieve_binary_block_count(sbin);
+
+		sieve_binary_dump_sectionf
+			(denv, "Binary blocks (count: %d)", count);
+
+		for ( i = 0; i < count; i++ ) {
+			struct sieve_binary_block *sblock = sieve_binary_block_get(sbin, i);
+
+			sieve_binary_dumpf(denv,
+				"%3d: size: %"PRIuSIZE_T" bytes\n", i,
+				sieve_binary_block_get_size(sblock));
+		}
+	}
+
+	/* Dump script metadata */
+
+	sieve_binary_dump_sectionf
+		(denv, "Script metadata (block: %d)", SBIN_SYSBLOCK_SCRIPT_DATA);
+	sblock = sieve_binary_block_get(sbin, SBIN_SYSBLOCK_SCRIPT_DATA);
+
+	T_BEGIN {
+		offset = 0;
+		success = sieve_script_binary_dump_metadata
+			(script, denv, sblock, &offset);
+	} T_END;
+	if ( !success ) return FALSE;
+
+	/* Dump list of used extensions */
+
+	count = sieve_binary_extensions_count(sbin);
+	if ( count > 0 ) {
+		sieve_binary_dump_sectionf
+			(denv, "Required extensions (block: %d)", SBIN_SYSBLOCK_EXTENSIONS);
+
+		for ( i = 0; i < count; i++ ) {
+			const struct sieve_extension *ext = sieve_binary_extension_get_by_index
+				(sbin, i);
+
+			sblock = sieve_binary_extension_get_block(sbin, ext);
+
+			if ( sblock == NULL ) {
+				sieve_binary_dumpf(denv, "%3d: %s (id: %d)\n",
+					i, sieve_extension_name(ext), ext->id);
+			} else {
+				sieve_binary_dumpf(denv, "%3d: %s (id: %d; block: %d)\n",
+					i, sieve_extension_name(ext), ext->id,
+					sieve_binary_block_get_id(sblock));
+			}
+		}
+	}
+
+	/* Dump extension-specific elements of the binary */
+
+	count = sieve_binary_extensions_count(sbin);
+	if ( count > 0 ) {
+		for ( i = 0; i < count; i++ ) {
+			success = TRUE;
+
+			T_BEGIN {
+				const struct sieve_extension *ext = sieve_binary_extension_get_by_index
+					(sbin, i);
+
+				if ( ext->def != NULL && ext->def->binary_dump != NULL ) {
+					success = ext->def->binary_dump(ext, denv);
+				}
+			} T_END;
+
+			if ( !success ) return FALSE;
+		}
+	}
+
+	/* Dump main program */
+
+	sieve_binary_dump_sectionf
+		(denv, "Main program (block: %d)", SBIN_SYSBLOCK_MAIN_PROGRAM);
+
+	dumper->dumpenv.sblock =
+		sieve_binary_block_get(sbin, SBIN_SYSBLOCK_MAIN_PROGRAM);
+	dumper->dumpenv.cdumper = sieve_code_dumper_create(&(dumper->dumpenv));
+
+	if ( dumper->dumpenv.cdumper != NULL ) {
+		sieve_code_dumper_run(dumper->dumpenv.cdumper);
+
+		sieve_code_dumper_free(&dumper->dumpenv.cdumper);
+	}
+
+	/* Finish with empty line */
+	sieve_binary_dumpf(denv, "\n");
+
+	return TRUE;
+}
+
+/*
+ * Hexdump production
+ */
+
+void sieve_binary_dumper_hexdump
+(struct sieve_binary_dumper *dumper, struct ostream *stream)
+{
+	struct sieve_binary *sbin = dumper->dumpenv.sbin;
+	struct sieve_dumptime_env *denv = &(dumper->dumpenv);
+	int count, i;
+
+	dumper->dumpenv.stream = stream;
+
+	count = sieve_binary_block_count(sbin);
+
+	/* Block overview */
+
+	sieve_binary_dump_sectionf
+		(denv, "Binary blocks (count: %d)", count);
+
+	for ( i = 0; i < count; i++ ) {
+		struct sieve_binary_block *sblock = sieve_binary_block_get(sbin, i);
+
+		sieve_binary_dumpf(denv,
+			"%3d: size: %"PRIuSIZE_T" bytes\n", i,
+			sieve_binary_block_get_size(sblock));
+	}
+
+	/* Hexdump for each block */
+
+	for ( i = 0; i < count; i++ ) {
+		struct sieve_binary_block *sblock = sieve_binary_block_get(sbin, i);
+		buffer_t *blockbuf = sieve_binary_block_get_buffer(sblock);
+		string_t *line;
+		size_t data_size;
+		const unsigned char *data;
+		size_t offset;
+
+		data = buffer_get_data(blockbuf, &data_size);
+
+		// FIXME: calculate offset more nicely.
+		sieve_binary_dump_sectionf
+			(denv, "Block %d (%"PRIuSIZE_T" bytes, file offset %08llx)", i,
+				data_size, (unsigned long long int) sblock->offset + 8);
+
+		line = t_str_new(128);
+		offset = 0;
+		while ( offset < data_size ) {
+			size_t len = ( data_size - offset >= 16 ? 16 : data_size - offset );
+			size_t b;
+
+			str_printfa(line, "%08llx  ", (unsigned long long) offset);
+
+			for ( b = 0; b < len; b++ ) {
+				str_printfa(line, "%02x ", data[offset+b]);
+				if ( b == 7 ) str_append_c(line, ' ');
+			}
+
+			if ( len < 16 ) {
+				if ( len <= 7 ) str_append_c(line, ' ');
+
+				for ( b = len; b < 16; b++ ) {
+					str_append(line, "   ");
+				}
+			}
+
+			str_append(line, " |");
+
+			for ( b = 0; b < len; b++ ) {
+				const unsigned char c = data[offset+b];
+
+				if ( c >= 32 && c <= 126 )
+					str_append_c(line, (const char)c);
+				else
+					str_append_c(line, '.');
+			}
+
+			str_append(line, "|\n");
+			o_stream_nsend(stream, str_data(line), str_len(line));
+			str_truncate(line, 0);
+			offset += len;
+		}
+
+		str_printfa(line, "%08llx\n", (unsigned long long) offset);
+		o_stream_nsend(stream, str_data(line), str_len(line));
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-binary-dumper.h
@@ -0,0 +1,45 @@
+#ifndef SIEVE_BINARY_DUMPER_H
+#define SIEVE_BINARY_DUMPER_H
+
+#include "sieve-common.h"
+
+/*
+ * Binary dumper object
+ */
+
+struct sieve_binary_dumper;
+
+struct sieve_binary_dumper *sieve_binary_dumper_create
+	(struct sieve_binary *sbin);
+void sieve_binary_dumper_free
+	(struct sieve_binary_dumper **dumper);
+
+pool_t sieve_binary_dumper_pool
+	(struct sieve_binary_dumper *dumper);
+
+/*
+ * Formatted output
+ */
+
+void sieve_binary_dumpf
+	(const struct sieve_dumptime_env *denv, const char *fmt, ...)
+		ATTR_FORMAT(2, 3);
+void sieve_binary_dump_sectionf
+	(const struct sieve_dumptime_env *denv, const char *fmt, ...)
+		ATTR_FORMAT(2, 3);
+
+/*
+ * Dumping the binary
+ */
+
+bool sieve_binary_dumper_run
+	(struct sieve_binary_dumper *dumper, struct ostream *stream, bool verbose);
+
+/*
+ * Hexdump production
+ */
+
+void sieve_binary_dumper_hexdump
+(struct sieve_binary_dumper *dumper, struct ostream *stream);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-binary-file.c
@@ -0,0 +1,936 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "mempool.h"
+#include "buffer.h"
+#include "hash.h"
+#include "array.h"
+#include "ostream.h"
+#include "eacces-error.h"
+#include "safe-mkstemp.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-extensions.h"
+#include "sieve-code.h"
+#include "sieve-script.h"
+
+#include "sieve-binary-private.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+/*
+ * Macros
+ */
+
+#define SIEVE_BINARY_MAGIC              0xcafebabe
+#define SIEVE_BINARY_MAGIC_OTHER_ENDIAN 0xbebafeca
+
+#define SIEVE_BINARY_ALIGN(offset) \
+	(((offset) + 3) & ~3)
+#define SIEVE_BINARY_ALIGN_PTR(ptr) \
+	((void *) SIEVE_BINARY_ALIGN(((size_t) ptr)))
+
+/*
+ * Header and record structures of the binary on disk
+ */
+
+struct sieve_binary_header {
+	uint32_t magic;
+	uint16_t version_major;
+	uint16_t version_minor;
+	uint32_t blocks;
+};
+
+struct sieve_binary_block_index {
+	uint32_t id;
+	uint32_t size;
+	uint32_t offset;
+	uint32_t ext_id;
+};
+
+struct sieve_binary_block_header {
+	uint32_t id;
+	uint32_t size;
+};
+
+/*
+ * Saving the binary to a file.
+ */
+
+static inline bool _save_skip
+(struct sieve_binary *sbin, struct ostream *stream, size_t size)
+{
+	if ( (o_stream_seek(stream, stream->offset + size)) <= 0 ) {
+		sieve_sys_error(sbin->svinst,
+			"binary save: failed to skip output stream "
+			"to position %"PRIuUOFF_T": %s", stream->offset + size,
+			strerror(stream->stream_errno));
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static inline bool _save_skip_aligned
+(struct sieve_binary *sbin, struct ostream *stream, size_t size,
+	uoff_t *offset)
+{
+	uoff_t aligned_offset = SIEVE_BINARY_ALIGN(stream->offset);
+
+	if ( (o_stream_seek(stream, aligned_offset + size)) <= 0 ) {
+		sieve_sys_error(sbin->svinst, "binary save: failed to skip output stream "
+			"to position %"PRIuUOFF_T": %s", aligned_offset + size,
+			strerror(stream->stream_errno));
+		return FALSE;
+	}
+
+	if ( offset != NULL )
+		*offset = aligned_offset;
+
+	return TRUE;
+}
+
+/* FIXME: Is this even necessary for a file? */
+static bool _save_full
+(struct sieve_binary *sbin, struct ostream *stream, const void *data, size_t size)
+{
+	size_t bytes_left = size;
+	const void *pdata = data;
+
+	while ( bytes_left > 0 ) {
+		ssize_t ret;
+
+		if ( (ret=o_stream_send(stream, pdata, bytes_left)) <= 0 ) {
+			sieve_sys_error(sbin->svinst,
+				"binary save: failed to write %"PRIuSIZE_T" bytes "
+				"to output stream: %s", bytes_left, strerror(stream->stream_errno));
+			return FALSE;
+		}
+
+		pdata = PTR_OFFSET(pdata, ret);
+		bytes_left -= ret;
+	}
+
+	return TRUE;
+}
+
+static bool _save_aligned
+(struct sieve_binary *sbin, struct ostream *stream, const void *data,
+	size_t size, uoff_t *offset)
+{
+	uoff_t aligned_offset = SIEVE_BINARY_ALIGN(stream->offset);
+
+	o_stream_cork(stream);
+
+	/* Align the data by adding zeroes to the output stream */
+	if ( stream->offset < aligned_offset ) {
+		if ( !_save_skip(sbin, stream, aligned_offset - stream->offset) )
+			return FALSE;
+	}
+
+	if ( !_save_full(sbin, stream, data, size) )
+		return FALSE;
+
+	o_stream_uncork(stream);
+
+	if ( offset != NULL )
+		*offset = aligned_offset;
+
+	return TRUE;
+}
+
+static bool _save_block
+(struct sieve_binary *sbin, struct ostream *stream, unsigned int id)
+{
+	struct sieve_binary_block_header block_header;
+	struct sieve_binary_block *block;
+	const void *data;
+	size_t size;
+
+	block = sieve_binary_block_get(sbin, id);
+	if ( block == NULL )
+		return FALSE;
+
+	data = buffer_get_data(block->data, &size);
+
+	block_header.id = id;
+	block_header.size = size;
+
+	if ( !_save_aligned(sbin, stream, &block_header,
+		sizeof(block_header), &block->offset) )
+		return FALSE;
+
+	return _save_aligned(sbin, stream, data, size, NULL);
+}
+
+static bool _save_block_index_record
+(struct sieve_binary *sbin, struct ostream *stream, unsigned int id)
+{
+	struct sieve_binary_block *block;
+	struct sieve_binary_block_index header;
+
+	block = sieve_binary_block_get(sbin, id);
+	if ( block == NULL )
+		return FALSE;
+
+	header.id = id;
+	header.size = buffer_get_used_size(block->data);
+	header.ext_id = block->ext_index;
+	header.offset = block->offset;
+
+	if ( !_save_full(sbin, stream, &header, sizeof(header)) ) {
+		sieve_sys_error(sbin->svinst,
+			"binary save: failed to save block index header %d", id);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static bool _sieve_binary_save
+(struct sieve_binary *sbin, struct ostream *stream)
+{
+	struct sieve_binary_header header;
+	struct sieve_binary_block *ext_block;
+	unsigned int ext_count, blk_count, i;
+	uoff_t block_index;
+
+	blk_count = sieve_binary_block_count(sbin);
+
+	/* Create header */
+
+	header.magic = SIEVE_BINARY_MAGIC;
+	header.version_major = SIEVE_BINARY_VERSION_MAJOR;
+	header.version_minor = SIEVE_BINARY_VERSION_MINOR;
+	header.blocks = blk_count;
+
+	if ( !_save_aligned(sbin, stream, &header, sizeof(header), NULL) ) {
+		sieve_sys_error(sbin->svinst, "binary save: failed to save header");
+		return FALSE;
+	}
+
+	/* Skip block index for now */
+
+	if ( !_save_skip_aligned(sbin, stream,
+		sizeof(struct sieve_binary_block_index) * blk_count, &block_index) )
+		return FALSE;
+
+	/* Create block containing all used extensions */
+
+	ext_block = sieve_binary_block_get(sbin, SBIN_SYSBLOCK_EXTENSIONS);
+	i_assert( ext_block != NULL );
+	sieve_binary_block_clear(ext_block);
+
+	ext_count = array_count(&sbin->linked_extensions);
+	sieve_binary_emit_unsigned(ext_block, ext_count);
+
+	for ( i = 0; i < ext_count; i++ ) {
+		struct sieve_binary_extension_reg * const *ext
+			= array_idx(&sbin->linked_extensions, i);
+
+		sieve_binary_emit_cstring
+			(ext_block, sieve_extension_name((*ext)->extension));
+		sieve_binary_emit_unsigned
+			(ext_block, sieve_extension_version((*ext)->extension));
+		sieve_binary_emit_unsigned(ext_block, (*ext)->block_id);
+	}
+
+	/* Save all blocks into the binary */
+
+	for ( i = 0; i < blk_count; i++ ) {
+		if ( !_save_block(sbin, stream, i) )
+			return FALSE;
+	}
+
+	/* Create the block index */
+	o_stream_seek(stream, block_index);
+	for ( i = 0; i < blk_count; i++ ) {
+		if ( !_save_block_index_record(sbin, stream, i) )
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+int sieve_binary_save
+(struct sieve_binary *sbin, const char *path, bool update, mode_t save_mode,
+	enum sieve_error *error_r)
+{
+	int result, fd;
+	string_t *temp_path;
+	struct ostream *stream;
+	struct sieve_binary_extension_reg *const *regs;
+	unsigned int ext_count, i;
+
+	if ( error_r != NULL )
+		*error_r = SIEVE_ERROR_NONE;
+
+	/* Check whether saving is necessary */
+	if ( !update && sbin->path != NULL && strcmp(sbin->path, path) == 0 ) {
+		if ( sbin->svinst->debug ) {
+			sieve_sys_debug(sbin->svinst, "binary save: not saving binary %s, "
+				"because it is already stored", path);
+		}
+		return 0;
+	}
+
+	/* Open it as temp file first, as not to overwrite an existing just yet */
+	temp_path = t_str_new(256);
+	str_append(temp_path, path);
+	str_append_c(temp_path, '.');
+	fd = safe_mkstemp_hostpid(temp_path, save_mode, (uid_t)-1, (gid_t)-1);
+	if ( fd < 0 ) {
+		if ( errno == EACCES ) {
+			sieve_sys_error(sbin->svinst,
+				"binary save: failed to create temporary file: %s",
+				eacces_error_get_creating("open", str_c(temp_path)));
+			if ( error_r != NULL )
+				*error_r = SIEVE_ERROR_NO_PERMISSION;
+		} else {
+			sieve_sys_error(sbin->svinst,
+				"binary save: failed to create temporary file: open(%s) failed: %m",
+				str_c(temp_path));
+			if ( error_r != NULL )
+				*error_r = SIEVE_ERROR_TEMP_FAILURE;
+		}
+		return -1;
+	}
+
+	/* Signal all extensions that we're about to save the binary */
+	regs = array_get(&sbin->extensions, &ext_count);
+	for ( i = 0; i < ext_count; i++ ) {
+		const struct sieve_binary_extension *binext = regs[i]->binext;
+
+		if ( binext != NULL && binext->binary_pre_save != NULL &&
+			!binext->binary_pre_save
+				(regs[i]->extension, sbin, regs[i]->context, error_r)) {
+			return -1;
+		}
+	}
+
+	/* Save binary */
+	result = 1;
+	stream = o_stream_create_fd(fd, 0);
+	if ( !_sieve_binary_save(sbin, stream) ) {
+		result = -1;
+		if ( error_r != NULL )
+			*error_r = SIEVE_ERROR_TEMP_FAILURE;
+	}
+	o_stream_destroy(&stream);
+
+	/* Close saved binary */
+	if ( close(fd) < 0 ) {
+		sieve_sys_error(sbin->svinst,
+			"binary save: failed to close temporary file: "
+			"close(fd=%s) failed: %m", str_c(temp_path));
+	}
+
+	/* Replace any original binary atomically */
+	if ( result > 0 && (rename(str_c(temp_path), path) < 0) ) {
+		if ( errno == EACCES ) {
+			sieve_sys_error(sbin->svinst, "binary save: failed to save binary: %s",
+				eacces_error_get_creating("rename", path));
+			if ( error_r != NULL )
+				*error_r = SIEVE_ERROR_NO_PERMISSION;
+		} else {
+			sieve_sys_error(sbin->svinst, "binary save: failed to save binary: "
+				"rename(%s, %s) failed: %m", str_c(temp_path), path);
+			if ( error_r != NULL )
+				*error_r = SIEVE_ERROR_TEMP_FAILURE;
+		}
+		result = -1;
+	}
+
+	if ( result < 0 ) {
+		/* Get rid of temp output (if any) */
+		if ( unlink(str_c(temp_path)) < 0 && errno != ENOENT ) {
+			sieve_sys_error(sbin->svinst,
+				"binary save: failed to clean up after error: unlink(%s) failed: %m",
+				str_c(temp_path));
+		}
+	} else {
+		if ( sbin->path == NULL )
+			sbin->path = p_strdup(sbin->pool, path);
+
+		/* Signal all extensions that we successfully saved the binary */
+		regs = array_get(&sbin->extensions, &ext_count);
+		for ( i = 0; i < ext_count; i++ ) {
+			const struct sieve_binary_extension *binext = regs[i]->binext;
+
+			if ( binext != NULL && binext->binary_post_save != NULL &&
+				!binext->binary_post_save
+					(regs[i]->extension, sbin, regs[i]->context, error_r)) {
+				result = -1;
+				break;
+			}
+		}
+
+		if ( result < 0 && unlink(path) < 0 && errno != ENOENT ) {
+			sieve_sys_error(sbin->svinst,
+				"binary save: failed to clean up after error: "
+				"unlink(%s) failed: %m", path);
+		}
+		sbin->path = NULL;
+	}
+
+	return result;
+}
+
+/*
+ * Binary file management
+ */
+
+bool sieve_binary_file_open
+(struct sieve_binary_file *file,
+	struct sieve_instance *svinst, const char *path, enum sieve_error *error_r)
+{
+	int fd;
+	bool result = TRUE;
+	struct stat st;
+
+	if ( error_r != NULL )
+		*error_r = SIEVE_ERROR_NONE;
+
+	if ( (fd=open(path, O_RDONLY)) < 0 ) {
+		switch ( errno ) {
+		case ENOENT:
+			if ( error_r != NULL )
+				*error_r = SIEVE_ERROR_NOT_FOUND;
+			break;
+		case EACCES:
+			sieve_sys_error(svinst, "binary open: failed to open: %s",
+				eacces_error_get("open", path));
+			if ( error_r != NULL )
+				*error_r = SIEVE_ERROR_NO_PERMISSION;
+			break;
+		default:
+			sieve_sys_error(svinst, "binary open: failed to open: "
+				"open(%s) failed: %m", path);
+			if ( error_r != NULL )
+				*error_r = SIEVE_ERROR_TEMP_FAILURE;
+			break;
+		}
+		return FALSE;
+	}
+
+	if ( fstat(fd, &st) < 0 ) {
+		if ( errno != ENOENT ) {
+			sieve_sys_error(svinst,
+				"binary open: fstat(fd=%s) failed: %m", path);
+		}
+		result = FALSE;
+	}
+
+	if ( result && !S_ISREG(st.st_mode) ) {
+		sieve_sys_error(svinst,
+			"binary open: %s is not a regular file", path);
+		result = FALSE;
+	}
+
+	if ( !result )	{
+		if ( close(fd) < 0 ) {
+			sieve_sys_error(svinst,
+				"binary open: close(fd=%s) failed after error: %m", path);
+		}
+		return FALSE;
+	}
+
+	file->svinst = svinst;
+	file->fd = fd;
+	file->st = st;
+
+	return TRUE;
+}
+
+void sieve_binary_file_close(struct sieve_binary_file **file)
+{
+	if ( (*file)->fd != -1 ) {
+		if ( close((*file)->fd) < 0 ) {
+			sieve_sys_error((*file)->svinst,
+				"binary close: failed to close: close(fd=%s) failed: %m",
+				(*file)->path);
+		}
+	}
+
+	pool_unref(&(*file)->pool);
+
+	*file = NULL;
+}
+
+#if 0 /* file_memory is currently unused */
+
+/* File loaded/mapped to memory */
+
+struct _file_memory {
+	struct sieve_binary_file binfile;
+
+	/* Pointer to the binary in memory */
+	const void *memory;
+	off_t memory_size;
+};
+
+static const void *_file_memory_load_data
+	(struct sieve_binary_file *file, off_t *offset, size_t size)
+{
+	struct _file_memory *fmem = (struct _file_memory *) file;
+
+	*offset = SIEVE_BINARY_ALIGN(*offset);
+
+	if ( (*offset) + size <= fmem->memory_size ) {
+		const void *data = PTR_OFFSET(fmem->memory, *offset);
+		*offset += size;
+		file->offset = *offset;
+
+		return data;
+	}
+
+	return NULL;
+}
+
+static buffer_t *_file_memory_load_buffer
+	(struct sieve_binary_file *file, off_t *offset, size_t size)
+{
+	struct _file_memory *fmem = (struct _file_memory *) file;
+
+	*offset = SIEVE_BINARY_ALIGN(*offset);
+
+	if ( (*offset) + size <= fmem->memory_size ) {
+		const void *data = PTR_OFFSET(fmem->memory, *offset);
+		*offset += size;
+		file->offset = *offset;
+
+		return buffer_create_const_data(file->pool, data, size);
+	}
+
+	return NULL;
+}
+
+static bool _file_memory_load(struct sieve_binary_file *file)
+{
+	struct _file_memory *fmem = (struct _file_memory *) file;
+	int ret;
+	size_t size;
+	void *indata;
+
+	i_assert(file->fd > 0);
+
+	/* Allocate memory buffer
+	 */
+	indata = p_malloc(file->pool, file->st.st_size);
+	size = file->st.st_size;
+
+	file->offset = 0;
+	fmem->memory = indata;
+	fmem->memory_size = file->st.st_size;
+
+	/* Return to beginning of the file */
+	if ( lseek(file->fd, 0, SEEK_SET) == (off_t) -1 ) {
+		sieve_sys_error("failed to seek() in binary %s: %m", file->path);
+		return FALSE;
+	}
+
+	/* Read the whole file into memory */
+	while (size > 0) {
+		if ( (ret=read(file->fd, indata, size)) <= 0 ) {
+			sieve_sys_error("failed to read from binary %s: %m", file->path);
+			break;
+		}
+
+		indata = PTR_OFFSET(indata, ret);
+		size -= ret;
+	}
+
+	if ( size != 0 ) {
+		/* Failed to read the whole file */
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static struct sieve_binary_file *_file_memory_open(const char *path)
+{
+	pool_t pool;
+	struct _file_memory *file;
+
+	pool = pool_alloconly_create("sieve_binary_file_memory", 1024);
+	file = p_new(pool, struct _file_memory, 1);
+	file->binfile.pool = pool;
+	file->binfile.path = p_strdup(pool, path);
+	file->binfile.load = _file_memory_load;
+	file->binfile.load_data = _file_memory_load_data;
+	file->binfile.load_buffer = _file_memory_load_buffer;
+
+	if ( !sieve_binary_file_open(&file->binfile, path) ) {
+		pool_unref(&pool);
+		return NULL;
+	}
+
+	return &file->binfile;
+}
+
+#endif /* file_memory is currently unused */
+
+/* File open in lazy mode (only read what is needed into memory) */
+
+static bool _file_lazy_read
+(struct sieve_binary_file *file, off_t *offset, void *buffer, size_t size)
+{
+	struct sieve_instance *svinst = file->svinst;
+	int ret;
+	void *indata = buffer;
+	size_t insize = size;
+
+	*offset = SIEVE_BINARY_ALIGN(*offset);
+
+	/* Seek to the correct position */
+	if ( *offset != file->offset &&
+		lseek(file->fd, *offset, SEEK_SET) == (off_t) -1 ) {
+		sieve_sys_error(svinst, "binary read:"
+			"failed to seek(fd, %lld, SEEK_SET) in binary %s: %m",
+			(long long) *offset, file->path);
+		return FALSE;
+	}
+
+	/* Read record into memory */
+	while (insize > 0) {
+		if ( (ret=read(file->fd, indata, insize)) <= 0 ) {
+			if ( ret == 0 )
+				sieve_sys_error(svinst,
+					"binary read: binary %s is truncated (more data expected)",
+					file->path);
+			else
+				sieve_sys_error(svinst,
+					"binary read: failed to read from binary %s: %m", file->path);
+			break;
+		}
+
+		indata = PTR_OFFSET(indata, ret);
+		insize -= ret;
+	}
+
+	if ( insize != 0 ) {
+		/* Failed to read the whole requested record */
+		return FALSE;
+	}
+
+	*offset += size;
+	file->offset = *offset;
+
+	return TRUE;
+}
+
+static const void *_file_lazy_load_data
+(struct sieve_binary_file *file, off_t *offset, size_t size)
+{
+	void *data = t_malloc_no0(size);
+
+	if ( _file_lazy_read(file, offset, data, size) ) {
+		return data;
+	}
+
+	return NULL;
+}
+
+static buffer_t *_file_lazy_load_buffer
+(struct sieve_binary_file *file, off_t *offset, size_t size)
+{
+	buffer_t *buffer = buffer_create_dynamic(file->pool, size);
+
+	if ( _file_lazy_read
+		(file, offset, buffer_get_space_unsafe(buffer, 0, size), size) ) {
+		return buffer;
+	}
+
+	return NULL;
+}
+
+static struct sieve_binary_file *_file_lazy_open
+(struct sieve_instance *svinst, const char *path, enum sieve_error *error_r)
+{
+	pool_t pool;
+	struct sieve_binary_file *file;
+
+	pool = pool_alloconly_create("sieve_binary_file_lazy", 4096);
+	file = p_new(pool, struct sieve_binary_file, 1);
+	file->pool = pool;
+	file->path = p_strdup(pool, path);
+	file->load_data = _file_lazy_load_data;
+	file->load_buffer = _file_lazy_load_buffer;
+
+	if ( !sieve_binary_file_open(file, svinst, path, error_r) ) {
+		pool_unref(&pool);
+		return NULL;
+	}
+
+	return file;
+}
+
+/*
+ * Load binary from a file
+ */
+
+#define LOAD_HEADER(sbin, offset, header) \
+	(header *) sbin->file->load_data(sbin->file, offset, sizeof(header))
+
+bool sieve_binary_load_block
+(struct sieve_binary_block *sblock)
+{
+	struct sieve_binary *sbin = sblock->sbin;
+	unsigned int id = sblock->id;
+	off_t offset = sblock->offset;
+	const struct sieve_binary_block_header *header =
+		LOAD_HEADER(sbin, &offset, const struct sieve_binary_block_header);
+
+	if ( header == NULL ) {
+		sieve_sys_error(sbin->svinst,
+			"binary load: binary %s is corrupt: "
+			"failed to read header of block %d", sbin->path, id);
+		return FALSE;
+	}
+
+	if ( header->id != id ) {
+		sieve_sys_error(sbin->svinst,
+			"binary load: binary %s is corrupt: "
+			"header of block %d has non-matching id %d",
+			sbin->path, id, header->id);
+		return FALSE;
+	}
+
+	sblock->data = sbin->file->load_buffer(sbin->file, &offset, header->size);
+	if ( sblock->data == NULL ) {
+		sieve_sys_error(sbin->svinst,
+			"binary load: failed to read block %d of binary %s (size=%d)",
+			id, sbin->path, header->size);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static bool _read_block_index_record
+(struct sieve_binary *sbin, off_t *offset, unsigned int id)
+{
+	const struct sieve_binary_block_index *record =
+		LOAD_HEADER(sbin, offset, const struct sieve_binary_block_index);
+	struct sieve_binary_block *block;
+
+	if ( record == NULL ) {
+		sieve_sys_error(sbin->svinst,
+			"binary open: binary %s is corrupt: "
+			"failed to load block index record %d", sbin->path, id);
+		return FALSE;
+	}
+
+	if ( record->id != id ) {
+		sieve_sys_error(sbin->svinst,
+			"binary open: binary %s is corrupt: "
+			"block index record %d has unexpected id %d", sbin->path, id, record->id);
+		return FALSE;
+	}
+
+	block = sieve_binary_block_create_id(sbin, id);
+	block->ext_index = record->ext_id;
+	block->offset = record->offset;
+
+	return TRUE;
+}
+
+static int _read_extensions(struct sieve_binary_block *sblock)
+{
+	struct sieve_binary *sbin = sblock->sbin;
+	sieve_size_t offset = 0;
+	unsigned int i, count;
+	int result = 1;
+
+	if ( !sieve_binary_read_unsigned(sblock, &offset, &count) )
+		return -1;
+
+	for ( i = 0; result > 0 && i < count; i++ ) {
+		T_BEGIN {
+			string_t *extension;
+			const struct sieve_extension *ext;
+			unsigned int version;
+
+			if ( sieve_binary_read_string(sblock, &offset, &extension) ) {
+				ext = sieve_extension_get_by_name(sbin->svinst, str_c(extension));
+
+				if ( ext == NULL ) {
+					sieve_sys_error(sbin->svinst,
+						"binary open: binary %s requires unknown extension `%s'",
+						sbin->path, str_sanitize(str_c(extension), 128));
+					result = 0;
+				} else {
+					struct sieve_binary_extension_reg *ereg = NULL;
+
+					(void) sieve_binary_extension_register(sbin, ext, &ereg);
+					if ( !sieve_binary_read_unsigned(sblock, &offset, &version) ||
+						!sieve_binary_read_unsigned(sblock, &offset, &ereg->block_id) ) {
+						result = -1;
+					} else if ( !sieve_extension_version_is(ext, version) ) {
+						sieve_sys_debug(sbin->svinst,
+							"binary open: binary %s was compiled with different version "
+							"of the `%s' extension (compiled v%d, expected v%d;"
+							"automatically fixed when re-compiled)", sbin->path,
+							sieve_extension_name(ext), version, sieve_extension_version(ext));
+						result = 0;
+					}
+				}
+			}	else
+				result = -1;
+		} T_END;
+	}
+
+	return result;
+}
+
+static bool _sieve_binary_open(struct sieve_binary *sbin)
+{
+	bool result = TRUE;
+	off_t offset = 0;
+	const struct sieve_binary_header *header;
+	struct sieve_binary_block *ext_block;
+	unsigned int i, blk_count;
+	int ret;
+
+	/* Verify header */
+
+	T_BEGIN {
+		header = LOAD_HEADER(sbin, &offset, const struct sieve_binary_header);
+		/* Check header presence */
+		if ( header == NULL ) {
+			sieve_sys_error(sbin->svinst,
+				"binary_open: file %s is not large enough to contain the header.",
+				sbin->path);
+			result = FALSE;
+
+		/* Check header validity */
+		} else if ( header->magic != SIEVE_BINARY_MAGIC ) {
+			if ( header->magic != SIEVE_BINARY_MAGIC_OTHER_ENDIAN )
+				sieve_sys_error(sbin->svinst,
+					"binary_open: binary %s has corrupted header "
+					"(0x%08x) or it is not a Sieve binary", sbin->path, header->magic);
+			else if ( sbin->svinst->debug )
+				sieve_sys_debug(sbin->svinst,
+					"binary open: binary %s stored with in different endian format "
+					"(automatically fixed when re-compiled)",
+					sbin->path);
+			result = FALSE;
+
+		/* Check binary version */
+		} else if ( result && (
+		  header->version_major != SIEVE_BINARY_VERSION_MAJOR ||
+			header->version_minor != SIEVE_BINARY_VERSION_MINOR ) ) {
+
+			/* Binary is of different version. Caller will have to recompile */
+
+			if ( sbin->svinst->debug ) {
+				sieve_sys_debug(sbin->svinst,
+					"binary open: binary %s stored with different binary version %d.%d "
+					"(!= %d.%d; automatically fixed when re-compiled)", sbin->path,
+					(int) header->version_major, header->version_minor,
+					SIEVE_BINARY_VERSION_MAJOR, SIEVE_BINARY_VERSION_MINOR);
+			}
+			result = FALSE;
+
+		/* Check block content */
+		} else if ( result && header->blocks == 0 ) {
+			sieve_sys_error(sbin->svinst,
+				"binary open: binary %s is corrupt: it contains no blocks",
+				sbin->path);
+			result = FALSE;
+
+		/* Valid */
+		} else {
+			blk_count = header->blocks;
+		}
+	} T_END;
+
+	if ( !result ) return FALSE;
+
+	/* Load block index */
+
+	for ( i = 0; i < blk_count && result; i++ ) {
+		T_BEGIN {
+			if ( !_read_block_index_record(sbin, &offset, i) ) {
+				result = FALSE;
+			}
+		} T_END;
+	}
+
+	if ( !result ) return FALSE;
+
+	/* Load extensions used by this binary */
+
+	T_BEGIN {
+		ext_block = sieve_binary_block_get(sbin, SBIN_SYSBLOCK_EXTENSIONS);
+		if ( ext_block == NULL ) {
+			result = FALSE;
+		} else if ( (ret=_read_extensions(ext_block)) <= 0 ) {
+			if ( ret < 0 ) {
+				sieve_sys_error(sbin->svinst,
+					"binary open: binary %s is corrupt: failed to load extension block",
+					sbin->path);
+			}
+			result = FALSE;
+		}
+	} T_END;
+
+	return result;
+}
+
+struct sieve_binary *sieve_binary_open
+(struct sieve_instance *svinst, const char *path, struct sieve_script *script,
+	enum sieve_error *error_r)
+{
+	struct sieve_binary_extension_reg *const *regs;
+	unsigned int ext_count, i;
+	struct sieve_binary *sbin;
+	struct sieve_binary_file *file;
+
+	i_assert( script == NULL || sieve_script_svinst(script) == svinst );
+
+	//file = _file_memory_open(path);
+	if ( (file=_file_lazy_open(svinst, path, error_r)) == NULL )
+		return NULL;
+
+	/* Create binary object */
+	sbin = sieve_binary_create(svinst, script);
+	sbin->path = p_strdup(sbin->pool, path);
+	sbin->file = file;
+
+	if ( !_sieve_binary_open(sbin) ) {
+		sieve_binary_unref(&sbin);
+		if ( error_r != NULL )
+			*error_r = SIEVE_ERROR_NOT_VALID;
+		return NULL;
+	}
+
+	sieve_binary_activate(sbin);
+
+	/* Signal open event to extensions */
+	regs = array_get(&sbin->extensions, &ext_count);
+	for ( i = 0; i < ext_count; i++ ) {
+		const struct sieve_binary_extension *binext = regs[i]->binext;
+
+		if ( binext != NULL && binext->binary_open != NULL &&
+			!binext->binary_open(regs[i]->extension, sbin, regs[i]->context) ) {
+			/* Extension thinks its corrupt */
+
+			if ( error_r != NULL )
+				*error_r = SIEVE_ERROR_NOT_VALID;
+
+			sieve_binary_unref(&sbin);
+			return NULL;
+		}
+	}
+
+	return sbin;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-binary-private.h
@@ -0,0 +1,206 @@
+#ifndef SIEVE_BINARY_PRIVATE_H
+#define SIEVE_BINARY_PRIVATE_H
+
+#include "sieve-common.h"
+#include "sieve-binary.h"
+#include "sieve-extensions.h"
+
+#include <sys/stat.h>
+
+/*
+ * Binary file
+ */
+
+struct sieve_binary_file {
+	pool_t pool;
+	const char *path;
+	struct sieve_instance *svinst;
+
+	struct stat st;
+	int fd;
+	off_t offset;
+
+	const void *(*load_data)
+		(struct sieve_binary_file *file, off_t *offset, size_t size);
+	buffer_t *(*load_buffer)
+		(struct sieve_binary_file *file, off_t *offset, size_t size);
+};
+
+bool sieve_binary_file_open
+	(struct sieve_binary_file *file, struct sieve_instance *svinst,
+		const char *path, enum sieve_error *error_r);
+void sieve_binary_file_close(struct sieve_binary_file **file);
+
+/*
+ * Internal structures
+ */
+
+/* Extension registration */
+
+struct sieve_binary_extension_reg {
+	/* The identifier of the extension within this binary */
+	int index;
+
+	/* Global extension object */
+	const struct sieve_extension *extension;
+
+	/* Extension to the binary; typically used to manage extension-specific blocks
+	 * in the binary and as a means to get a binary_free notification to release
+	 * references held by extensions.
+	 */
+	const struct sieve_binary_extension *binext;
+
+	/* Context data associated to the binary by this extension */
+	void *context;
+
+	/* Main block for this extension */
+	unsigned int block_id;
+};
+
+/* Block */
+
+struct sieve_binary_block {
+	struct sieve_binary *sbin;
+	unsigned int id;
+	int ext_index;
+
+	buffer_t *data;
+
+	uoff_t offset;
+};
+
+/*
+ * Binary object
+ */
+
+struct sieve_binary {
+	pool_t pool;
+	int refcount;
+
+	struct sieve_instance *svinst;
+
+	struct sieve_script *script;
+
+	struct sieve_binary_file *file;
+
+	/* When the binary is loaded into memory or when it is being constructed by
+	 * the generator, extensions can be associated to the binary. The extensions
+	 * array is a sequential list of all linked extensions. The extension_index
+	 * array is a mapping ext_id -> binary_extension. This is used to obtain the
+	 * index code associated with an extension for this particular binary. The
+	 * linked_extensions list all extensions linked to this binary object other
+	 * than the preloaded language features implemented as 'extensions'.
+	 *
+	 * All arrays refer to the same extension registration objects. Upon loading
+	 * a binary, the 'require'd extensions will sometimes need to associate
+	 * context data to the binary object in memory. This is stored in these
+	 * registration objects as well.
+	 */
+	ARRAY(struct sieve_binary_extension_reg *) extensions;
+	ARRAY(struct sieve_binary_extension_reg *) extension_index;
+	ARRAY(struct sieve_binary_extension_reg *) linked_extensions;
+
+	/* Attributes of a loaded binary */
+	const char *path;
+
+	/* Blocks */
+	ARRAY(struct sieve_binary_block *) blocks;
+};
+
+struct sieve_binary *sieve_binary_create
+	(struct sieve_instance *svinst, struct sieve_script *script);
+
+/* Blocks management */
+
+static inline struct sieve_binary_block *sieve_binary_block_index
+(struct sieve_binary *sbin, unsigned int id)
+{
+	struct sieve_binary_block * const *sblock;
+
+	if  ( id >= array_count(&sbin->blocks) )
+		return NULL;
+
+	sblock = array_idx(&sbin->blocks, id);
+
+	if ( *sblock == NULL ) {
+		return NULL;
+	}
+
+	return *sblock;
+}
+
+static inline size_t _sieve_binary_block_get_size
+(const struct sieve_binary_block *sblock)
+{
+	return buffer_get_used_size(sblock->data);
+}
+
+struct sieve_binary_block *sieve_binary_block_create_id
+	(struct sieve_binary *sbin, unsigned int id);
+
+buffer_t *sieve_binary_block_get_buffer
+	(struct sieve_binary_block *sblock);
+
+/* Extension registration */
+
+static inline struct sieve_binary_extension_reg *
+	sieve_binary_extension_create_reg
+(struct sieve_binary *sbin, const struct sieve_extension *ext)
+{
+	int index = array_count(&sbin->extensions);
+	struct sieve_binary_extension_reg *ereg;
+
+	if ( ext->id < 0 ) return NULL;
+
+	ereg = p_new(sbin->pool, struct sieve_binary_extension_reg, 1);
+	ereg->index = index;
+	ereg->extension = ext;
+
+	array_idx_set(&sbin->extensions, (unsigned int) index, &ereg);
+	array_idx_set(&sbin->extension_index, (unsigned int) ext->id, &ereg);
+
+	return ereg;
+}
+
+static inline struct sieve_binary_extension_reg *sieve_binary_extension_get_reg
+(struct sieve_binary *sbin, const struct sieve_extension *ext, bool create)
+{
+	struct sieve_binary_extension_reg *reg = NULL;
+
+	if ( ext->id >= 0 && ext->id < (int) array_count(&sbin->extension_index) ) {
+		struct sieve_binary_extension_reg * const *ereg =
+			array_idx(&sbin->extension_index, (unsigned int) ext->id);
+
+		reg = *ereg;
+	}
+
+	/* Register if not known */
+	if ( reg == NULL && create )
+		return sieve_binary_extension_create_reg(sbin, ext);
+
+	return reg;
+}
+
+static inline int sieve_binary_extension_register
+(struct sieve_binary *sbin, const struct sieve_extension *ext,
+	struct sieve_binary_extension_reg **reg_r)
+{
+	struct sieve_binary_extension_reg *ereg;
+
+	if ( (ereg=sieve_binary_extension_get_reg(sbin, ext, FALSE)) == NULL ) {
+		ereg = sieve_binary_extension_create_reg(sbin, ext);
+
+		if ( ereg == NULL ) return -1;
+
+		array_append(&sbin->linked_extensions, &ereg, 1);
+	}
+
+	if ( reg_r != NULL ) *reg_r = ereg;
+	return ereg->index;
+}
+
+/* Load/Save */
+
+bool sieve_binary_load_block(struct sieve_binary_block *);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-binary.c
@@ -0,0 +1,489 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "mempool.h"
+#include "buffer.h"
+#include "hash.h"
+#include "array.h"
+#include "ostream.h"
+#include "eacces-error.h"
+#include "safe-mkstemp.h"
+
+#include "sieve-error.h"
+#include "sieve-extensions.h"
+#include "sieve-code.h"
+#include "sieve-script.h"
+
+#include "sieve-binary-private.h"
+
+/*
+ * Forward declarations
+ */
+
+static inline struct sieve_binary_extension_reg *sieve_binary_extension_get_reg
+	(struct sieve_binary *sbin, const struct sieve_extension *ext,
+		bool create);
+
+static inline int sieve_binary_extension_register
+	(struct sieve_binary *sbin, const struct sieve_extension *ext,
+		struct sieve_binary_extension_reg **reg);
+
+/*
+ * Binary object
+ */
+
+struct sieve_binary *sieve_binary_create
+(struct sieve_instance *svinst, struct sieve_script *script)
+{
+	pool_t pool;
+	struct sieve_binary *sbin;
+	const struct sieve_extension *const *ext_preloaded;
+	unsigned int i, ext_count;
+
+	pool = pool_alloconly_create("sieve_binary", 16384);
+	sbin = p_new(pool, struct sieve_binary, 1);
+	sbin->pool = pool;
+	sbin->refcount = 1;
+	sbin->svinst = svinst;
+
+	sbin->script = script;
+	if ( script != NULL )
+		sieve_script_ref(script);
+
+	ext_count = sieve_extensions_get_count(svinst);
+
+	p_array_init(&sbin->linked_extensions, pool, ext_count);
+	p_array_init(&sbin->extensions, pool, ext_count);
+	p_array_init(&sbin->extension_index, pool, ext_count);
+
+	p_array_init(&sbin->blocks, pool, 16);
+
+	/* Pre-load core language features implemented as 'extensions' */
+	ext_preloaded = sieve_extensions_get_preloaded(svinst, &ext_count);
+	for ( i = 0; i < ext_count; i++ ) {
+		const struct sieve_extension_def *ext_def = ext_preloaded[i]->def;
+
+		if ( ext_def != NULL && ext_def->binary_load != NULL )
+			(void)ext_def->binary_load(ext_preloaded[i], sbin);
+	}
+
+	return sbin;
+}
+
+struct sieve_binary *sieve_binary_create_new(struct sieve_script *script)
+{
+	struct sieve_binary *sbin = sieve_binary_create
+		(sieve_script_svinst(script), script);
+	struct sieve_binary_block *sblock;
+	unsigned int i;
+
+	/* Create script metadata block */
+	sblock = sieve_binary_block_create(sbin);
+	sieve_script_binary_write_metadata(script, sblock);
+
+	/* Create other system blocks */
+	for ( i = 1; i < SBIN_SYSBLOCK_LAST; i++ ) {
+		(void) sieve_binary_block_create(sbin);
+	}
+
+	return sbin;
+}
+
+void sieve_binary_ref(struct sieve_binary *sbin)
+{
+	sbin->refcount++;
+}
+
+static inline void sieve_binary_extensions_free(struct sieve_binary *sbin)
+{
+	struct sieve_binary_extension_reg *const *regs;
+	unsigned int ext_count, i;
+
+	/* Cleanup binary extensions */
+	regs = array_get(&sbin->extensions, &ext_count);
+	for ( i = 0; i < ext_count; i++ ) {
+		const struct sieve_binary_extension *binext = regs[i]->binext;
+
+		if ( binext != NULL && binext->binary_free != NULL )
+			binext->binary_free(regs[i]->extension, sbin, regs[i]->context);
+	}
+}
+
+void sieve_binary_unref(struct sieve_binary **sbin)
+{
+	i_assert((*sbin)->refcount > 0);
+
+	if (--(*sbin)->refcount != 0)
+		return;
+
+	sieve_binary_extensions_free(*sbin);
+
+	if ( (*sbin)->file != NULL )
+		sieve_binary_file_close(&(*sbin)->file);
+
+	if ( (*sbin)->script != NULL )
+		sieve_script_unref(&(*sbin)->script);
+
+	pool_unref(&((*sbin)->pool));
+
+	*sbin = NULL;
+}
+
+/*
+ * Accessors
+ */
+
+pool_t sieve_binary_pool(struct sieve_binary *sbin)
+{
+	return sbin->pool;
+}
+
+struct sieve_script *sieve_binary_script(struct sieve_binary *sbin)
+{
+	return sbin->script;
+}
+
+const char *sieve_binary_path(struct sieve_binary *sbin)
+{
+	return sbin->path;
+}
+
+bool sieve_binary_saved(struct sieve_binary *sbin)
+{
+	return ( sbin->path != NULL );
+}
+
+bool sieve_binary_loaded(struct sieve_binary *sbin)
+{
+	return ( sbin->file != NULL );
+}
+
+const char *sieve_binary_source(struct sieve_binary *sbin)
+{
+	if ( sbin->script != NULL && (sbin->path == NULL || sbin->file == NULL) )
+		return sieve_script_location(sbin->script);
+
+	return sbin->path;
+}
+
+struct sieve_instance *sieve_binary_svinst(struct sieve_binary *sbin)
+{
+	return sbin->svinst;
+}
+
+time_t sieve_binary_mtime
+(struct sieve_binary *sbin)
+{
+	i_assert(sbin->file != NULL);
+	return sbin->file->st.st_mtime;
+}
+
+const struct stat *sieve_binary_stat
+(struct sieve_binary *sbin)
+{
+	i_assert(sbin->file != NULL);
+	return &sbin->file->st;
+}
+
+const char *sieve_binary_script_name(struct sieve_binary *sbin)
+{
+	return ( sbin->script == NULL ? NULL : sieve_script_name(sbin->script) );
+}
+
+const char *sieve_binary_script_location(struct sieve_binary *sbin)
+{
+	return ( sbin->script == NULL ? NULL : sieve_script_location(sbin->script) );
+}
+
+/*
+ * Utility
+ */
+
+const char *sieve_binfile_from_name(const char *name)
+{
+	return t_strconcat(name, "."SIEVE_BINARY_FILEEXT, NULL);
+}
+
+/*
+ * Block management
+ */
+
+unsigned int sieve_binary_block_count
+(struct sieve_binary *sbin)
+{
+	return array_count(&sbin->blocks);
+}
+
+struct sieve_binary_block *sieve_binary_block_create(struct sieve_binary *sbin)
+{
+	unsigned int id = sieve_binary_block_count(sbin);
+	struct sieve_binary_block *sblock;
+
+	sblock = p_new(sbin->pool, struct sieve_binary_block, 1);
+	sblock->data = buffer_create_dynamic(sbin->pool, 64);
+	sblock->sbin = sbin;
+	sblock->id = id;
+
+	array_append(&sbin->blocks, &sblock, 1);
+
+	return sblock;
+}
+
+struct sieve_binary_block *sieve_binary_block_create_id
+(struct sieve_binary *sbin, unsigned int id)
+{
+	struct sieve_binary_block *sblock;
+
+	sblock = p_new(sbin->pool, struct sieve_binary_block, 1);
+
+	array_idx_set(&sbin->blocks, id, &sblock);
+	sblock->data = NULL;
+	sblock->sbin = sbin;
+	sblock->id = id;
+
+	return sblock;
+}
+
+static bool sieve_binary_block_fetch(struct sieve_binary_block *sblock)
+{
+	struct sieve_binary *sbin = sblock->sbin;
+
+	if ( sbin->file != NULL ) {
+		/* Try to acces the block in the binary on disk (apperently we were lazy)
+		 */
+		if ( !sieve_binary_load_block(sblock) || sblock->data == NULL )
+			return FALSE;
+	} else {
+		sblock->data = buffer_create_dynamic(sbin->pool, 64);
+		return TRUE;
+	}
+
+	return TRUE;
+}
+
+struct sieve_binary_block *sieve_binary_block_get
+(struct sieve_binary *sbin, unsigned int id)
+{
+	struct sieve_binary_block *sblock = sieve_binary_block_index(sbin, id);
+
+	if ( sblock == NULL )
+		return NULL;
+
+	if ( sblock->data == NULL && !sieve_binary_block_fetch(sblock) )
+		return NULL;
+
+	return sblock;
+}
+
+void sieve_binary_block_clear
+(struct sieve_binary_block *sblock)
+{
+	buffer_set_used_size(sblock->data, 0);
+}
+
+buffer_t *sieve_binary_block_get_buffer
+(struct sieve_binary_block *sblock)
+{
+	if ( sblock->data == NULL && !sieve_binary_block_fetch(sblock) )
+		return NULL;
+
+	return sblock->data;
+}
+
+struct sieve_binary *sieve_binary_block_get_binary
+(const struct sieve_binary_block *sblock)
+{
+	return sblock->sbin;
+}
+
+unsigned int sieve_binary_block_get_id
+(const struct sieve_binary_block *sblock)
+{
+	return sblock->id;
+}
+
+size_t sieve_binary_block_get_size
+(const struct sieve_binary_block *sblock)
+{
+	return _sieve_binary_block_get_size(sblock);
+}
+
+/*
+ * Up-to-date checking
+ */
+
+bool sieve_binary_up_to_date
+(struct sieve_binary *sbin, enum sieve_compile_flags cpflags)
+{
+	struct sieve_binary_extension_reg *const *regs;
+	struct sieve_binary_block *sblock;
+	sieve_size_t offset = 0;
+	unsigned int ext_count, i;
+	int ret;
+
+	i_assert(sbin->file != NULL);
+
+	sblock = sieve_binary_block_get(sbin, SBIN_SYSBLOCK_SCRIPT_DATA);
+	if ( sblock == NULL || sbin->script == NULL )
+		return FALSE;
+
+	if ( (ret=sieve_script_binary_read_metadata
+		(sbin->script, sblock, &offset)) <= 0 ) {
+		if (ret < 0) {
+			sieve_sys_debug(sbin->svinst, "binary up-to-date: "
+				"failed to read script metadata from binary %s",
+				sbin->path);
+		} else {
+			sieve_sys_debug(sbin->svinst, "binary up-to-date: "
+				"script metadata indicates that binary %s is not up-to-date",
+				sbin->path);
+		}
+		return FALSE;
+	}
+
+	regs = array_get(&sbin->extensions, &ext_count);
+	for ( i = 0; i < ext_count; i++ ) {
+		const struct sieve_binary_extension *binext = regs[i]->binext;
+
+		if ( binext != NULL && binext->binary_up_to_date != NULL &&
+			!binext->binary_up_to_date
+				(regs[i]->extension, sbin, regs[i]->context, cpflags) ) {
+			sieve_sys_debug(sbin->svinst, "binary up-to-date: "
+				"the %s extension indicates binary %s is not up-to-date",
+				sieve_extension_name(regs[i]->extension), sbin->path);
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+/*
+ * Activate the binary (after code generation)
+ */
+
+void sieve_binary_activate(struct sieve_binary *sbin)
+{
+	struct sieve_binary_extension_reg *const *regs;
+	unsigned int i, ext_count;
+
+	/* Load other extensions into binary */
+	regs = array_get(&sbin->linked_extensions, &ext_count);
+	for ( i = 0; i < ext_count; i++ ) {
+		const struct sieve_extension *ext = regs[i]->extension;
+
+		if ( ext != NULL && ext->def != NULL && ext->def->binary_load != NULL )
+			ext->def->binary_load(ext, sbin);
+	}
+}
+
+/*
+ * Extension handling
+ */
+
+void sieve_binary_extension_set_context
+(struct sieve_binary *sbin, const struct sieve_extension *ext, void *context)
+{
+	struct sieve_binary_extension_reg *ereg =
+		sieve_binary_extension_get_reg(sbin, ext, TRUE);
+
+	if ( ereg != NULL )
+		ereg->context = context;
+}
+
+const void *sieve_binary_extension_get_context
+	(struct sieve_binary *sbin, const struct sieve_extension *ext)
+{
+	struct sieve_binary_extension_reg *ereg =
+		sieve_binary_extension_get_reg(sbin, ext, TRUE);
+
+	if ( ereg != NULL ) {
+		return ereg->context;
+	}
+
+	return NULL;
+}
+
+void sieve_binary_extension_set
+(struct sieve_binary *sbin, const struct sieve_extension *ext,
+	const struct sieve_binary_extension *bext, void *context)
+{
+	struct sieve_binary_extension_reg *ereg =
+		sieve_binary_extension_get_reg(sbin, ext, TRUE);
+
+	if ( ereg != NULL ) {
+		ereg->binext = bext;
+
+		if ( context != NULL )
+			ereg->context = context;
+	}
+}
+
+struct sieve_binary_block *sieve_binary_extension_create_block
+(struct sieve_binary *sbin, const struct sieve_extension *ext)
+{
+	struct sieve_binary_block *sblock;
+	struct sieve_binary_extension_reg *ereg =
+		sieve_binary_extension_get_reg(sbin, ext, TRUE);
+
+	i_assert(ereg != NULL);
+
+	sblock = sieve_binary_block_create(sbin);
+
+	if ( ereg->block_id < SBIN_SYSBLOCK_LAST )
+		ereg->block_id = sblock->id;
+	sblock->ext_index = ereg->index;
+
+	return sblock;
+}
+
+struct sieve_binary_block *sieve_binary_extension_get_block
+(struct sieve_binary *sbin, const struct sieve_extension *ext)
+{
+	struct sieve_binary_extension_reg *ereg =
+		sieve_binary_extension_get_reg(sbin, ext, TRUE);
+
+	i_assert(ereg != NULL);
+
+	if ( ereg->block_id < SBIN_SYSBLOCK_LAST )
+		return NULL;
+
+	return sieve_binary_block_get(sbin, ereg->block_id);
+}
+
+int sieve_binary_extension_link
+(struct sieve_binary *sbin, const struct sieve_extension *ext)
+{
+	return sieve_binary_extension_register(sbin, ext, NULL);
+}
+
+const struct sieve_extension *sieve_binary_extension_get_by_index
+(struct sieve_binary *sbin, int index)
+{
+	struct sieve_binary_extension_reg * const *ereg;
+
+	if ( index < (int) array_count(&sbin->extensions) ) {
+		ereg = array_idx(&sbin->extensions, (unsigned int) index);
+
+		return (*ereg)->extension;
+	}
+
+	return NULL;
+}
+
+int sieve_binary_extension_get_index
+	(struct sieve_binary *sbin, const struct sieve_extension *ext)
+{
+	struct sieve_binary_extension_reg *ereg =
+		sieve_binary_extension_get_reg(sbin, ext, FALSE);
+
+	return ( ereg == NULL ? -1 : ereg->index );
+}
+
+int sieve_binary_extensions_count(struct sieve_binary *sbin)
+{
+	return (int) array_count(&sbin->extensions);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-binary.h
@@ -0,0 +1,277 @@
+#ifndef SIEVE_BINARY_H
+#define SIEVE_BINARY_H
+
+#include "lib.h"
+
+#include "sieve-common.h"
+
+/*
+ * Config
+ */
+
+#define SIEVE_BINARY_VERSION_MAJOR     1
+#define SIEVE_BINARY_VERSION_MINOR     4
+
+/*
+ * Binary object
+ */
+
+struct sieve_binary;
+
+struct sieve_binary *sieve_binary_create_new(struct sieve_script *script);
+void sieve_binary_ref(struct sieve_binary *sbin);
+void sieve_binary_unref(struct sieve_binary **sbin);
+
+/*
+ * Accessors
+ */
+
+pool_t sieve_binary_pool(struct sieve_binary *sbin);
+struct sieve_instance *sieve_binary_svinst(struct sieve_binary *sbin);
+const char *sieve_binary_path(struct sieve_binary *sbin);
+struct sieve_script *sieve_binary_script(struct sieve_binary *sbin);
+
+time_t sieve_binary_mtime(struct sieve_binary *sbin);
+const struct stat *sieve_binary_stat
+(struct sieve_binary *sbin);
+
+const char *sieve_binary_script_name(struct sieve_binary *sbin);
+const char *sieve_binary_script_location(struct sieve_binary *sbin);
+
+const char *sieve_binary_source(struct sieve_binary *sbin);
+bool sieve_binary_loaded(struct sieve_binary *sbin);
+bool sieve_binary_saved(struct sieve_binary *sbin);
+
+/*
+ * Utility
+ */
+
+const char *sieve_binfile_from_name(const char *name);
+
+/*
+ * Activation after code generation
+ */
+
+void sieve_binary_activate(struct sieve_binary *sbin);
+
+/*
+ * Saving the binary
+ */
+
+int sieve_binary_save
+	(struct sieve_binary *sbin, const char *path, bool update, mode_t save_mode,
+		enum sieve_error *error_r);
+
+/*
+ * Loading the binary
+ */
+
+struct sieve_binary *sieve_binary_open
+	(struct sieve_instance *svinst, const char *path,
+		struct sieve_script *script, enum sieve_error *error_r);
+bool sieve_binary_up_to_date
+	(struct sieve_binary *sbin, enum sieve_compile_flags cpflags);
+
+/*
+ * Block management
+ */
+
+enum sieve_binary_system_block {
+	SBIN_SYSBLOCK_SCRIPT_DATA,
+	SBIN_SYSBLOCK_EXTENSIONS,
+	SBIN_SYSBLOCK_MAIN_PROGRAM,
+	SBIN_SYSBLOCK_LAST
+};
+
+struct sieve_binary_block *sieve_binary_block_create
+	(struct sieve_binary *sbin);
+
+unsigned int sieve_binary_block_count
+	(struct sieve_binary *sbin);
+
+struct sieve_binary_block *sieve_binary_block_get
+	(struct sieve_binary *sbin, unsigned int id);
+
+void sieve_binary_block_clear
+	(struct sieve_binary_block *sblock);
+
+size_t sieve_binary_block_get_size
+	(const struct sieve_binary_block *sblock);
+
+struct sieve_binary *sieve_binary_block_get_binary
+	(const struct sieve_binary_block *sblock);
+
+unsigned int sieve_binary_block_get_id
+	(const struct sieve_binary_block *sblock);
+
+/*
+ * Extension support
+ */
+
+struct sieve_binary_extension {
+	const struct sieve_extension_def *extension;
+
+	bool (*binary_pre_save)
+		(const struct sieve_extension *ext, struct sieve_binary *sbin,
+			void *context, enum sieve_error *error_r);
+	bool (*binary_post_save)
+		(const struct sieve_extension *ext, struct sieve_binary *sbin,
+			void *context, enum sieve_error *error_r);
+	bool (*binary_open)
+		(const struct sieve_extension *ext, struct sieve_binary *sbin,
+			void *context);
+
+	void (*binary_free)
+		(const struct sieve_extension *ext, struct sieve_binary *sbin,
+			void *context);
+
+	bool (*binary_up_to_date)
+		(const struct sieve_extension *ext, struct sieve_binary *sbin,
+			void *context, enum sieve_compile_flags cpflags);
+};
+
+void sieve_binary_extension_set_context
+	(struct sieve_binary *sbin, const struct sieve_extension *ext, void *context);
+const void *sieve_binary_extension_get_context
+	(struct sieve_binary *sbin, const struct sieve_extension *ext);
+
+void sieve_binary_extension_set
+	(struct sieve_binary *sbin, const struct sieve_extension *ext,
+		const struct sieve_binary_extension *bext, void *context);
+
+struct sieve_binary_block *sieve_binary_extension_create_block
+	(struct sieve_binary *sbin, const struct sieve_extension *ext);
+struct sieve_binary_block *sieve_binary_extension_get_block
+(struct sieve_binary *sbin, const struct sieve_extension *ext);
+
+int sieve_binary_extension_link
+	(struct sieve_binary *sbin, const struct sieve_extension *ext);
+const struct sieve_extension *sieve_binary_extension_get_by_index
+	(struct sieve_binary *sbin, int index);
+int sieve_binary_extension_get_index
+	(struct sieve_binary *sbin, const struct sieve_extension *ext);
+int sieve_binary_extensions_count(struct sieve_binary *sbin);
+
+
+/*
+ * Code emission
+ */
+
+/* Low-level emission functions */
+
+sieve_size_t sieve_binary_emit_data
+	(struct sieve_binary_block *sblock, const void *data, sieve_size_t size);
+sieve_size_t sieve_binary_emit_byte
+	(struct sieve_binary_block *sblock, uint8_t byte);
+void sieve_binary_update_data
+	(struct sieve_binary_block *sblock, sieve_size_t address, const void *data,
+		sieve_size_t size);
+
+/* Offset emission functions */
+
+sieve_size_t sieve_binary_emit_offset
+	(struct sieve_binary_block *sblock, sieve_offset_t offset);
+void sieve_binary_resolve_offset
+	(struct sieve_binary_block *sblock, sieve_size_t address);
+
+/* Literal emission functions */
+
+sieve_size_t sieve_binary_emit_integer
+	(struct sieve_binary_block *sblock, sieve_number_t integer);
+sieve_size_t sieve_binary_emit_string
+	(struct sieve_binary_block *sblock, const string_t *str);
+sieve_size_t sieve_binary_emit_cstring
+	(struct sieve_binary_block *sblock, const char *str);
+
+static inline sieve_size_t sieve_binary_emit_unsigned
+	(struct sieve_binary_block *sblock, unsigned int count)
+{
+	return sieve_binary_emit_integer(sblock, count);
+}
+
+/* Extension emission functions */
+
+sieve_size_t sieve_binary_emit_extension
+	(struct sieve_binary_block *sblock, const struct sieve_extension *ext,
+		unsigned int offset);
+void sieve_binary_emit_extension_object
+	(struct sieve_binary_block *sblock,
+		const struct sieve_extension_objects *objs, unsigned int code);
+
+/*
+ * Code retrieval
+ */
+
+/* Literals */
+
+bool sieve_binary_read_byte
+	(struct sieve_binary_block *sblock, sieve_size_t *address,
+		unsigned int *byte_r) ATTR_NULL(3);
+bool sieve_binary_read_code
+	(struct sieve_binary_block *sblock, sieve_size_t *address,
+		signed int *code_r) ATTR_NULL(3);
+bool sieve_binary_read_offset
+	(struct sieve_binary_block *sblock, sieve_size_t *address,
+		sieve_offset_t *offset_r) ATTR_NULL(3);
+bool sieve_binary_read_integer
+  (struct sieve_binary_block *sblock, sieve_size_t *address,
+		sieve_number_t *int_r) ATTR_NULL(3);
+bool sieve_binary_read_string
+  (struct sieve_binary_block *sblock, sieve_size_t *address,
+		string_t **str_r) ATTR_NULL(3);
+
+static inline bool sieve_binary_read_unsigned
+(struct sieve_binary_block *sblock, sieve_size_t *address,
+	unsigned int *count_r) ATTR_NULL(3)
+{
+	sieve_number_t integer = 0;
+
+	if ( !sieve_binary_read_integer(sblock, address, &integer) )
+		return FALSE;
+
+	if ( count_r != NULL )
+		*count_r = integer;
+
+	return TRUE;
+}
+
+/* Extensions */
+
+bool sieve_binary_read_extension
+	(struct sieve_binary_block *sblock, sieve_size_t *address,
+		unsigned int *offset_r, const struct sieve_extension **ext_r);
+const void *sieve_binary_read_extension_object
+	(struct sieve_binary_block *sblock, sieve_size_t *address,
+    const struct sieve_extension_objects *objs);
+
+/*
+ * Debug info
+ */
+
+/* Writer */
+
+struct sieve_binary_debug_writer;
+
+struct sieve_binary_debug_writer *sieve_binary_debug_writer_init
+	(struct sieve_binary_block *sblock);
+void sieve_binary_debug_writer_deinit
+	(struct sieve_binary_debug_writer **dwriter);
+
+void sieve_binary_debug_emit
+	(struct sieve_binary_debug_writer *dwriter, sieve_size_t code_address,
+		unsigned int code_line, unsigned int code_column);
+
+/* Reader */
+
+struct sieve_binary_debug_reader *sieve_binary_debug_reader_init
+	(struct sieve_binary_block *sblock);
+void sieve_binary_debug_reader_deinit
+	(struct sieve_binary_debug_reader **dreader);
+
+void sieve_binary_debug_reader_reset
+	(struct sieve_binary_debug_reader *dreader);
+
+unsigned int sieve_binary_debug_read_line
+	(struct sieve_binary_debug_reader *dreader, sieve_size_t code_address);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-code-dumper.c
@@ -0,0 +1,351 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "lib.h"
+#include "str.h"
+#include "mempool.h"
+#include "ostream.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-actions.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-result.h"
+#include "sieve-comparators.h"
+
+#include "sieve-dump.h"
+
+/*
+ * Code dumper extension
+ */
+
+struct sieve_code_dumper_extension_reg {
+	const struct sieve_code_dumper_extension *cdmpext;
+	const struct sieve_extension *ext;
+	void *context;
+};
+
+struct sieve_code_dumper {
+	pool_t pool;
+
+	/* Dump status */
+	struct sieve_operation oprtn;
+	sieve_size_t mark_address;
+	unsigned int mark_line;
+	unsigned int mark_last_line;
+	unsigned int indent;
+
+	/* Dump environment */
+	struct sieve_dumptime_env *dumpenv;
+
+	struct sieve_binary_debug_reader *dreader;
+
+	ARRAY(struct sieve_code_dumper_extension_reg) extensions;
+};
+
+struct sieve_code_dumper *sieve_code_dumper_create
+(struct sieve_dumptime_env *denv)
+{
+	pool_t pool;
+	struct sieve_code_dumper *cdumper;
+
+	pool = pool_alloconly_create("sieve_code_dumper", 4096);
+	cdumper = p_new(pool, struct sieve_code_dumper, 1);
+	cdumper->pool = pool;
+	cdumper->dumpenv = denv;
+
+	/* Setup storage for extension contexts */
+	p_array_init(&cdumper->extensions, pool,
+		sieve_extensions_get_count(denv->svinst));
+
+	return cdumper;
+}
+
+void sieve_code_dumper_free(struct sieve_code_dumper **_cdumper)
+{
+	struct sieve_code_dumper *cdumper = *_cdumper;
+	const struct sieve_code_dumper_extension_reg *eregs;
+	unsigned int count, i;
+
+	sieve_binary_debug_reader_deinit(&cdumper->dreader);
+
+	/* Signal registered extensions that the dumper is being destroyed */
+	eregs = array_get(&cdumper->extensions, &count);
+	for ( i = 0; i < count; i++ ) {
+		if ( eregs[i].cdmpext != NULL && eregs[i].cdmpext->free != NULL )
+			eregs[i].cdmpext->free(cdumper, eregs[i].context);
+	}
+
+	pool_unref(&cdumper->pool);
+	*_cdumper = NULL;
+}
+
+pool_t sieve_code_dumper_pool(struct sieve_code_dumper *cdumper)
+{
+	return cdumper->pool;
+}
+
+/* EXtension support */
+
+void sieve_dump_extension_register
+(struct sieve_code_dumper *cdumper, const struct sieve_extension *ext,
+	const struct sieve_code_dumper_extension *cdmpext, void *context)
+{
+	struct sieve_code_dumper_extension_reg *reg;
+
+	if ( ext->id < 0 ) return;
+
+	reg = array_idx_get_space(&cdumper->extensions, (unsigned int) ext->id);
+	reg->cdmpext = cdmpext;
+	reg->ext = ext;
+	reg->context = context;
+}
+
+void sieve_dump_extension_set_context
+(struct sieve_code_dumper *cdumper, const struct sieve_extension *ext,
+	void *context)
+{
+	struct sieve_code_dumper_extension_reg *reg;
+
+	if ( ext->id < 0 ) return;
+
+	reg = array_idx_get_space(&cdumper->extensions, (unsigned int) ext->id);
+	reg->context = context;
+}
+
+void *sieve_dump_extension_get_context
+(struct sieve_code_dumper *cdumper, const struct sieve_extension *ext)
+{
+	const struct sieve_code_dumper_extension_reg *reg;
+
+	if  ( ext->id < 0 || ext->id >= (int) array_count(&cdumper->extensions) )
+		return NULL;
+
+	reg = array_idx(&cdumper->extensions, (unsigned int) ext->id);
+
+	return reg->context;
+}
+
+/* Dump functions */
+
+void sieve_code_dumpf
+(const struct sieve_dumptime_env *denv, const char *fmt, ...)
+{
+	struct sieve_code_dumper *cdumper = denv->cdumper;
+	unsigned tab = cdumper->indent;
+
+	string_t *outbuf = t_str_new(128);
+	va_list args;
+
+	va_start(args, fmt);
+	str_printfa(outbuf, "%08llx: ", (unsigned long long) cdumper->mark_address);
+
+	if ( cdumper->mark_line > 0 && (cdumper->indent == 0 ||
+		cdumper->mark_line != cdumper->mark_last_line) ) {
+		str_printfa(outbuf, "%4u: ", cdumper->mark_line);
+		cdumper->mark_last_line = cdumper->mark_line;
+	} else {
+		str_append(outbuf, "      ");
+	}
+
+	while ( tab > 0 )	{
+		str_append(outbuf, "  ");
+		tab--;
+	}
+
+	str_vprintfa(outbuf, fmt, args);
+	str_append_c(outbuf, '\n');
+	va_end(args);
+
+	o_stream_nsend(denv->stream, str_data(outbuf), str_len(outbuf));
+}
+
+static inline void sieve_code_line_mark
+(const struct sieve_dumptime_env *denv, sieve_size_t location)
+{
+	if ( denv->cdumper->dreader != NULL )  {
+		denv->cdumper->mark_line = sieve_binary_debug_read_line
+			(denv->cdumper->dreader, location);
+	}
+}
+
+void sieve_code_mark(const struct sieve_dumptime_env *denv)
+{
+	denv->cdumper->mark_address = denv->offset;
+	sieve_code_line_mark(denv, denv->offset);
+}
+
+void sieve_code_mark_specific
+(const struct sieve_dumptime_env *denv, sieve_size_t location)
+{
+	denv->cdumper->mark_address = location;
+	sieve_code_line_mark(denv, location);
+}
+
+void sieve_code_descend(const struct sieve_dumptime_env *denv)
+{
+	denv->cdumper->indent++;
+}
+
+void sieve_code_ascend(const struct sieve_dumptime_env *denv)
+{
+	if ( denv->cdumper->indent > 0 )
+		denv->cdumper->indent--;
+}
+
+/* Code Dump */
+
+static bool sieve_code_dumper_print_operation
+(struct sieve_code_dumper *cdumper)
+{
+	struct sieve_dumptime_env *denv = cdumper->dumpenv;
+	struct sieve_operation *oprtn = &(cdumper->oprtn);
+	sieve_size_t *address	= &(denv->offset);
+
+	/* Mark start address of operation */
+	cdumper->indent = 0;
+	cdumper->mark_address = *address;
+
+	sieve_code_line_mark(denv, *address);
+
+	/* Read operation */
+	if ( sieve_operation_read(denv->sblock, address, oprtn) ) {
+		const struct sieve_operation_def *opdef = oprtn->def;
+
+		if ( opdef->dump != NULL )
+			return opdef->dump(denv, address);
+		else if ( opdef->mnemonic != NULL )
+			sieve_code_dumpf(denv, "%s", opdef->mnemonic);
+		else
+			return FALSE;
+
+		return TRUE;
+	}
+
+	sieve_code_dumpf(denv, "Failed to read opcode.");
+	return FALSE;
+}
+
+static bool sieve_code_dumper_print_extension
+(struct sieve_code_dumper *cdumper)
+{
+	struct sieve_dumptime_env *denv = cdumper->dumpenv;
+	sieve_size_t *address	= &(denv->offset);
+	struct sieve_binary_block *sblock = denv->sblock;
+	unsigned int code = 0, deferred;
+	const struct sieve_extension *ext;
+
+	sieve_code_mark(denv);
+
+	if ( !sieve_binary_read_extension
+			(sblock, address, &code, &ext) ||
+		!sieve_binary_read_byte
+			(sblock, address, &deferred) ) {
+		return FALSE;
+	}
+
+	if ( ext->def == NULL) {
+		sieve_code_dumpf(denv, "[undefined]");
+
+	} else {
+		sieve_code_dumpf(denv, "%s%s",
+			sieve_extension_name(ext),
+			(deferred > 0 ? " (deferred)" : ""));
+
+		if (ext->def->code_dump != NULL ) {
+			sieve_code_descend(denv);
+			if ( !ext->def->code_dump(ext, denv, address) )
+				return FALSE;
+			sieve_code_ascend(denv);
+		}
+	}
+	return TRUE;
+}
+
+void sieve_code_dumper_run(struct sieve_code_dumper *cdumper)
+{
+	struct sieve_dumptime_env *denv = cdumper->dumpenv;
+	struct sieve_binary *sbin = denv->sbin;
+	struct sieve_binary_block *sblock = denv->sblock;
+	unsigned int debug_block_id, ext_count;
+	bool success;
+	sieve_size_t *address;
+
+	denv->offset = 0;
+	denv->oprtn = &(cdumper->oprtn);
+	address = &(denv->offset);
+
+	/* Heading */
+	o_stream_nsend_str(denv->stream, "Address   Line  Code\n");
+
+	/* Load debug block */
+	sieve_code_mark(denv);
+
+	if ( sieve_binary_read_unsigned(sblock, address, &debug_block_id) ) {
+		struct sieve_binary_block *debug_block =
+			sieve_binary_block_get(sbin, debug_block_id);
+
+		if ( debug_block == NULL ) {
+			sieve_code_dumpf(denv, "Invalid id %d for debug block.", debug_block_id);
+			return;
+		} else {
+			/* Initialize debug reader */
+			cdumper->dreader = sieve_binary_debug_reader_init(debug_block);
+
+			/* Dump block id */
+			sieve_code_dumpf(denv, "DEBUG BLOCK: %d", debug_block_id);
+		}
+	} else {
+		sieve_code_dumpf(denv, "Binary code header is corrupt.");
+		return;
+	}
+
+	/* Load and dump extensions listed in code */
+	sieve_code_mark(denv);
+
+	success = TRUE;
+	if ( sieve_binary_read_unsigned(sblock, address, &ext_count) ) {
+		unsigned int i;
+
+		sieve_code_dumpf(denv, "EXTENSIONS [%d]:", ext_count);
+		sieve_code_descend(denv);
+
+		for ( i = 0; success && (i < ext_count); i++ ) {
+			T_BEGIN {
+				success = success &&
+					sieve_code_dumper_print_extension(cdumper);
+			} T_END;
+		}
+
+		sieve_code_ascend(denv);
+	}	else
+		success = FALSE;
+
+	if ( !success ) {
+		sieve_code_dumpf(denv, "Binary code header is corrupt.");
+		return;
+	}
+
+	while ( *address < sieve_binary_block_get_size(sblock) ) {
+
+		T_BEGIN {
+			success = sieve_code_dumper_print_operation(cdumper);
+		} T_END;
+
+		if ( !success ) {
+			sieve_code_dumpf(denv, "Binary is corrupt.");
+			return;
+		}
+	}
+
+	/* Mark end of the binary */
+	cdumper->indent = 0;
+	cdumper->mark_address = sieve_binary_block_get_size(sblock);
+	sieve_code_dumpf(denv, "[End of code]");
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-code-dumper.h
@@ -0,0 +1,55 @@
+#ifndef SIEVE_CODE_DUMPER_H
+#define SIEVE_CODE_DUMPER_H
+
+#include "sieve-common.h"
+
+struct sieve_code_dumper;
+
+struct sieve_code_dumper *sieve_code_dumper_create
+	(struct sieve_dumptime_env *denv);
+void sieve_code_dumper_free
+	(struct sieve_code_dumper **_dumper);
+pool_t sieve_code_dumper_pool
+	(struct sieve_code_dumper *dumper);
+
+/*
+ * Extension support
+ */
+
+struct sieve_code_dumper_extension {
+	const struct sieve_extension_def *ext;
+
+	void (*free)(struct sieve_code_dumper *dumper, void *context);
+};
+
+void sieve_dump_extension_register
+(struct sieve_code_dumper *dumper, const struct sieve_extension *ext,
+	const struct sieve_code_dumper_extension *dump_ext, void *context);
+void sieve_dump_extension_set_context
+	(struct sieve_code_dumper *dumper, const struct sieve_extension *ext,
+		void *context);
+void *sieve_dump_extension_get_context
+	(struct sieve_code_dumper *dumper, const struct sieve_extension *ext);
+
+/* Dump functions */
+
+void sieve_code_dumpf
+	(const struct sieve_dumptime_env *denv, const char *fmt, ...)
+		ATTR_FORMAT(2, 3);
+
+void sieve_code_mark(const struct sieve_dumptime_env *denv);
+void sieve_code_mark_specific
+	(const struct sieve_dumptime_env *denv, sieve_size_t location);
+void sieve_code_descend(const struct sieve_dumptime_env *denv);
+void sieve_code_ascend(const struct sieve_dumptime_env *denv);
+
+/* Operations and operands */
+
+bool sieve_code_dumper_print_optional_operands
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+
+/* Code dump (debugging purposes) */
+
+void sieve_code_dumper_run(struct sieve_code_dumper *dumper);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-code.c
@@ -0,0 +1,1169 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-limits.h"
+#include "sieve-extensions.h"
+#include "sieve-stringlist.h"
+#include "sieve-actions.h"
+#include "sieve-binary.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "sieve-code.h"
+
+#include <stdio.h>
+
+/*
+ * Code stringlist
+ */
+
+/* Forward declarations */
+
+static int sieve_code_stringlist_next_item
+	(struct sieve_stringlist *_strlist, string_t **str_r);
+static void sieve_code_stringlist_reset
+	(struct sieve_stringlist *_strlist);
+static int sieve_code_stringlist_get_length
+	(struct sieve_stringlist *_strlist);
+
+/* Coded stringlist object */
+
+struct sieve_code_stringlist {
+	struct sieve_stringlist strlist;
+
+	sieve_size_t start_address;
+	sieve_size_t end_address;
+	sieve_size_t current_offset;
+	int length;
+	int index;
+};
+
+static struct sieve_stringlist *sieve_code_stringlist_create
+(const struct sieve_runtime_env *renv,
+	 sieve_size_t start_address, unsigned int length, sieve_size_t end)
+{
+	struct sieve_code_stringlist *strlist;
+
+	if ( end > sieve_binary_block_get_size(renv->sblock) )
+  		return NULL;
+
+	strlist = t_new(struct sieve_code_stringlist, 1);
+	strlist->strlist.runenv = renv;
+	strlist->strlist.exec_status = SIEVE_EXEC_OK;
+	strlist->strlist.next_item = sieve_code_stringlist_next_item;
+	strlist->strlist.reset = sieve_code_stringlist_reset;
+	strlist->strlist.get_length = sieve_code_stringlist_get_length;
+	strlist->start_address = start_address;
+	strlist->current_offset = start_address;
+	strlist->end_address = end;
+	strlist->length = length;
+	strlist->index = 0;
+
+	return &strlist->strlist;
+}
+
+/* Stringlist implementation */
+
+static int sieve_code_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+	struct sieve_code_stringlist *strlist =
+		(struct sieve_code_stringlist *) _strlist;
+	sieve_size_t address;
+	*str_r = NULL;
+	int ret;
+
+	/* Check for end of list */
+	if ( strlist->index >= strlist->length )
+		return 0;
+
+	/* Read next item */
+	address = strlist->current_offset;
+	if ( (ret=sieve_opr_string_read(_strlist->runenv, &address, NULL, str_r))
+		== SIEVE_EXEC_OK ) {
+		strlist->index++;
+		strlist->current_offset = address;
+		return 1;
+	}
+
+	_strlist->exec_status = ret;
+	return -1;
+}
+
+static void sieve_code_stringlist_reset
+(struct sieve_stringlist *_strlist)
+{
+	struct sieve_code_stringlist *strlist =
+		(struct sieve_code_stringlist *) _strlist;
+
+	strlist->current_offset = strlist->start_address;
+	strlist->index = 0;
+}
+
+static int sieve_code_stringlist_get_length
+(struct sieve_stringlist *_strlist)
+{
+	struct sieve_code_stringlist *strlist =
+		(struct sieve_code_stringlist *) _strlist;
+
+	return strlist->length;
+}
+
+static bool sieve_code_stringlist_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+	unsigned int length, sieve_size_t end, const char *field_name)
+{
+	unsigned int i;
+
+	if ( end > sieve_binary_block_get_size(denv->sblock) )
+  		return FALSE;
+
+	if ( field_name != NULL )
+		sieve_code_dumpf(denv, "%s: STRLIST [%u] (end: %08llx)",
+			field_name, length, (unsigned long long) end);
+	else
+		sieve_code_dumpf(denv, "STRLIST [%u] (end: %08llx)",
+			length, (unsigned long long) end);
+
+	sieve_code_descend(denv);
+
+	for ( i = 0; i < length; i++ ) {
+		bool success = TRUE;
+
+		T_BEGIN {
+			success = sieve_opr_string_dump(denv, address, NULL);
+		} T_END;
+
+		if ( !success || *address > end )
+			return FALSE;
+	}
+
+	if ( *address != end ) return FALSE;
+
+	sieve_code_ascend(denv);
+
+	return TRUE;
+}
+
+/*
+ * Core operands
+ */
+
+extern const struct sieve_operand_def comparator_operand;
+extern const struct sieve_operand_def match_type_operand;
+extern const struct sieve_operand_def address_part_operand;
+
+const struct sieve_operand_def *sieve_operands[] = {
+	&omitted_operand, /* SIEVE_OPERAND_OPTIONAL */
+	&number_operand,
+	&string_operand,
+	&stringlist_operand,
+	&comparator_operand,
+	&match_type_operand,
+	&address_part_operand,
+	&catenated_string_operand
+};
+
+const unsigned int sieve_operand_count =
+	N_ELEMENTS(sieve_operands);
+
+/*
+ * Operand functions
+ */
+
+sieve_size_t sieve_operand_emit
+(struct sieve_binary_block *sblock, const struct sieve_extension *ext,
+	const struct sieve_operand_def *opr_def)
+{
+	sieve_size_t address;
+
+	if ( ext != NULL ) {
+		address = sieve_binary_emit_extension
+			(sblock, ext, sieve_operand_count);
+
+		sieve_binary_emit_extension_object
+			(sblock, &opr_def->ext_def->operands, opr_def->code);
+
+		return address;
+	}
+
+	return sieve_binary_emit_byte(sblock, opr_def->code);
+}
+
+bool sieve_operand_read
+(struct sieve_binary_block *sblock, sieve_size_t *address,
+	const char *field_name, struct sieve_operand *operand)
+{
+	unsigned int code = sieve_operand_count;
+
+	operand->address = *address;
+	operand->field_name = field_name;
+	operand->ext = NULL;
+	operand->def = NULL;
+
+	if ( !sieve_binary_read_extension(sblock, address, &code, &operand->ext) )
+		return FALSE;
+
+	if ( operand->ext == NULL ) {
+		if ( code < sieve_operand_count )
+			operand->def = sieve_operands[code];
+
+		return ( operand->def != NULL );
+	}
+
+	if ( operand->ext->def == NULL )
+		return FALSE;
+
+	operand->def = (const struct sieve_operand_def *)
+		sieve_binary_read_extension_object(sblock, address,
+			&operand->ext->def->operands);
+
+	return ( operand->def != NULL );
+}
+
+/*
+ * Optional operand
+ */
+
+int sieve_opr_optional_next
+(struct sieve_binary_block *sblock, sieve_size_t *address, signed int *opt_code)
+{
+	/* Start of optional operand block */
+	if ( *opt_code == 0 ) {
+		sieve_size_t tmp_addr = *address;
+		unsigned int op;
+
+		if ( !sieve_binary_read_byte(sblock, &tmp_addr, &op) ||
+			op != SIEVE_OPERAND_OPTIONAL )
+			return 0;
+
+		*address = tmp_addr;
+	}
+
+	/* Read optional operand code */
+	if ( !sieve_binary_read_code(sblock, address, opt_code) )
+		return -1;
+
+	/* Return 0 at end of list */
+	return ( *opt_code != 0 ? 1 : 0 );
+}
+
+/*
+ * Operand definitions
+ */
+
+/* Omitted */
+
+const struct sieve_operand_class omitted_class =
+	{ "OMITTED" };
+
+const struct sieve_operand_def omitted_operand = {
+	.name = "@OMITTED",
+	.code = SIEVE_OPERAND_OPTIONAL,
+	.class = &omitted_class
+};
+
+/* Number */
+
+static bool opr_number_dump
+	(const struct sieve_dumptime_env *denv,	const struct sieve_operand *oprnd,
+		sieve_size_t *address);
+static int opr_number_read
+	(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+		sieve_size_t *address, sieve_number_t *number_r);
+
+const struct sieve_opr_number_interface number_interface = {
+	opr_number_dump,
+	opr_number_read
+};
+
+const struct sieve_operand_class number_class =
+	{ "number" };
+
+const struct sieve_operand_def number_operand = {
+	.name = "@number",
+	.code = SIEVE_OPERAND_NUMBER,
+	.class = &number_class,
+	.interface = &number_interface
+};
+
+/* String */
+
+static bool opr_string_dump
+	(const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+		sieve_size_t *address);
+static int opr_string_read
+	(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+		sieve_size_t *address, string_t **str_r);
+
+const struct sieve_opr_string_interface string_interface ={
+	opr_string_dump,
+	opr_string_read
+};
+
+const struct sieve_operand_class string_class =
+	{ "string" };
+
+const struct sieve_operand_def string_operand = {
+	.name = "@string",
+	.code = SIEVE_OPERAND_STRING,
+	.class = &string_class,
+	.interface = &string_interface
+};
+
+/* String List */
+
+static bool opr_stringlist_dump
+	(const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+		sieve_size_t *address);
+static int opr_stringlist_read
+	(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+		sieve_size_t *address, struct sieve_stringlist **strlist_r);
+
+const struct sieve_opr_stringlist_interface stringlist_interface = {
+	opr_stringlist_dump,
+	opr_stringlist_read
+};
+
+const struct sieve_operand_class stringlist_class =
+	{ "string-list" };
+
+const struct sieve_operand_def stringlist_operand =	{
+	.name = "@string-list",
+	.code = SIEVE_OPERAND_STRING_LIST,
+	.class = &stringlist_class,
+	.interface = &stringlist_interface
+};
+
+/* Catenated String */
+
+static bool opr_catenated_string_dump
+	(const struct sieve_dumptime_env *denv, const struct sieve_operand *operand,
+		sieve_size_t *address);
+static int opr_catenated_string_read
+	(const struct sieve_runtime_env *renv, const struct sieve_operand *operand,
+		sieve_size_t *address, string_t **str);
+
+const struct sieve_opr_string_interface catenated_string_interface = {
+	opr_catenated_string_dump,
+	opr_catenated_string_read
+};
+
+const struct sieve_operand_def catenated_string_operand = {
+	.name = "@catenated-string",
+	.code = SIEVE_OPERAND_CATENATED_STRING,
+	.class = &string_class,
+	.interface = &catenated_string_interface
+};
+
+/*
+ * Operand implementations
+ */
+
+/* Omitted */
+
+void sieve_opr_omitted_emit(struct sieve_binary_block *sblock)
+{
+	(void) sieve_operand_emit(sblock, NULL, &omitted_operand);
+}
+
+/* Number */
+
+void sieve_opr_number_emit
+(struct sieve_binary_block *sblock, sieve_number_t number)
+{
+	(void) sieve_operand_emit(sblock, NULL, &number_operand);
+	(void) sieve_binary_emit_integer(sblock, number);
+}
+
+bool sieve_opr_number_dump_data
+(const struct sieve_dumptime_env *denv, struct sieve_operand *oprnd,
+	sieve_size_t *address, const char *field_name)
+{
+	const struct sieve_opr_number_interface *intf;
+
+	oprnd->field_name = field_name;
+
+	if ( !sieve_operand_is_number(oprnd) )
+		return FALSE;
+
+	intf = (const struct sieve_opr_number_interface *) oprnd->def->interface;
+
+	if ( intf->dump == NULL )
+		return FALSE;
+
+	return intf->dump(denv, oprnd, address);
+}
+
+bool sieve_opr_number_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+	const char *field_name)
+{
+	struct sieve_operand operand;
+
+	sieve_code_mark(denv);
+
+	if ( !sieve_operand_read(denv->sblock, address, field_name, &operand) )
+		return FALSE;
+
+	return sieve_opr_number_dump_data(denv, &operand, address, field_name);
+}
+
+int sieve_opr_number_read_data
+(const struct sieve_runtime_env *renv, struct sieve_operand *oprnd,
+	sieve_size_t *address, const char *field_name, sieve_number_t *number_r)
+{
+	const struct sieve_opr_number_interface *intf;
+
+	oprnd->field_name = field_name;
+
+	if ( !sieve_operand_is_number(oprnd) ) {
+		sieve_runtime_trace_operand_error(renv, oprnd,
+			"expected number operand but found %s", sieve_operand_name(oprnd));
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	intf = (const struct sieve_opr_number_interface *) oprnd->def->interface;
+
+	if ( intf->read == NULL ) {
+		sieve_runtime_trace_operand_error(renv, oprnd,
+			"number operand not implemented");
+		return SIEVE_EXEC_FAILURE;
+	}
+
+	return intf->read(renv, oprnd, address, number_r);
+}
+
+int sieve_opr_number_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+	const char *field_name, sieve_number_t *number_r)
+{
+	struct sieve_operand operand;
+	int ret;
+
+	if ( (ret=sieve_operand_runtime_read(renv, address, field_name, &operand))
+		<= 0)
+		return ret;
+
+	return sieve_opr_number_read_data
+		(renv, &operand, address, field_name, number_r);
+}
+
+static bool opr_number_dump
+(const struct sieve_dumptime_env *denv,	const struct sieve_operand *oprnd,
+	sieve_size_t *address)
+{
+	sieve_number_t number = 0;
+
+	if (sieve_binary_read_integer(denv->sblock, address, &number) ) {
+		if ( oprnd->field_name != NULL )
+			sieve_code_dumpf(denv, "%s: NUM %llu", oprnd->field_name,
+				(unsigned long long) number);
+		else
+			sieve_code_dumpf(denv, "NUM %llu", (unsigned long long) number);
+
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static int opr_number_read
+(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+	sieve_size_t *address, sieve_number_t *number_r)
+{
+	if ( !sieve_binary_read_integer(renv->sblock, address, number_r) ) {
+		sieve_runtime_trace_operand_error(renv, oprnd, "invalid number operand");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	return SIEVE_EXEC_OK;
+}
+
+/* String */
+
+void sieve_opr_string_emit(struct sieve_binary_block *sblock, string_t *str)
+{
+	(void) sieve_operand_emit(sblock, NULL, &string_operand);
+	(void) sieve_binary_emit_string(sblock, str);
+}
+
+bool sieve_opr_string_dump_data
+(const struct sieve_dumptime_env *denv, struct sieve_operand *oprnd,
+	sieve_size_t *address, const char *field_name)
+{
+	const struct sieve_opr_string_interface *intf;
+
+	oprnd->field_name = field_name;
+
+	if ( !sieve_operand_is_string(oprnd) ) {
+		sieve_code_dumpf(denv, "ERROR: INVALID STRING OPERAND %s",
+			sieve_operand_name(oprnd));
+		return FALSE;
+	}
+
+	intf = (const struct sieve_opr_string_interface *) oprnd->def->interface;
+
+	if ( intf->dump == NULL ) {
+		sieve_code_dumpf(denv, "ERROR: DUMP STRING OPERAND");
+		return FALSE;
+	}
+
+	return intf->dump(denv, oprnd, address);
+}
+
+bool sieve_opr_string_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+	const char *field_name)
+{
+	struct sieve_operand operand;
+
+	sieve_code_mark(denv);
+
+	if ( !sieve_operand_read(denv->sblock, address, field_name, &operand) ) {
+		sieve_code_dumpf(denv, "ERROR: INVALID OPERAND");
+		return FALSE;
+	}
+
+	return sieve_opr_string_dump_data(denv, &operand, address, field_name);
+}
+
+bool sieve_opr_string_dump_ex
+(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+	const char *field_name, const char *omitted_value)
+{
+	struct sieve_operand operand;
+
+	sieve_code_mark(denv);
+	if ( !sieve_operand_read(denv->sblock, address, field_name, &operand) ) {
+		sieve_code_dumpf(denv, "ERROR: INVALID OPERAND");
+		return FALSE;
+	}
+
+	if ( omitted_value != NULL && sieve_operand_is_omitted(&operand) ) {
+		if ( *omitted_value != '\0' )
+			sieve_code_dumpf(denv, "%s: %s", field_name, omitted_value);
+		return TRUE;
+	}
+
+	return sieve_opr_string_dump_data(denv, &operand, address, field_name);
+}
+
+int sieve_opr_string_read_data
+(const struct sieve_runtime_env *renv, struct sieve_operand *oprnd,
+	sieve_size_t *address, const char *field_name, string_t **str_r)
+{
+	const struct sieve_opr_string_interface *intf;
+
+	oprnd->field_name = field_name;
+
+	if ( !sieve_operand_is_string(oprnd) ) {
+		sieve_runtime_trace_operand_error(renv, oprnd,
+			"expected string operand but found %s", sieve_operand_name(oprnd));
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	intf = (const struct sieve_opr_string_interface *) oprnd->def->interface;
+
+	if ( intf->read == NULL ) {
+		sieve_runtime_trace_operand_error(renv, oprnd,
+			"string operand not implemented");
+		return SIEVE_EXEC_FAILURE;
+	}
+
+	return intf->read(renv, oprnd, address, str_r);
+}
+
+int sieve_opr_string_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+	const char *field_name, string_t **str_r)
+{
+	struct sieve_operand operand;
+	int ret;
+
+	if ( (ret=sieve_operand_runtime_read(renv, address, field_name, &operand))
+		<= 0 )
+		return ret;
+
+	return sieve_opr_string_read_data(renv, &operand, address, field_name, str_r);
+}
+
+int sieve_opr_string_read_ex
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+	const char *field_name, bool optional, string_t **str_r, bool *literal_r)
+{
+	struct sieve_operand operand;
+	int ret;
+
+	if ( (ret=sieve_operand_runtime_read(renv, address, field_name, &operand))
+		<= 0 )
+		return ret;
+
+	if ( optional && sieve_operand_is_omitted(&operand) ) {
+		*str_r = NULL;
+		return 1;
+	}
+
+	if ( literal_r != NULL )
+		*literal_r = sieve_operand_is_string_literal(&operand);
+
+	return sieve_opr_string_read_data(renv, &operand, address, field_name, str_r);
+}
+
+static void _dump_string
+(const struct sieve_dumptime_env *denv, string_t *str,
+	const char *field_name)
+{
+	if ( str_len(str) > 80 ) {
+		if ( field_name != NULL )
+			sieve_code_dumpf(denv, "%s: STR[%ld] \"%s",
+				field_name, (long) str_len(str), str_sanitize(str_c(str), 80));
+		else
+			sieve_code_dumpf(denv, "STR[%ld] \"%s",
+				(long) str_len(str), str_sanitize(str_c(str), 80));
+	} else {
+		if ( field_name != NULL )
+			sieve_code_dumpf(denv, "%s: STR[%ld] \"%s\"",
+				field_name, (long) str_len(str), str_sanitize(str_c(str), 80));
+		else
+			sieve_code_dumpf(denv, "STR[%ld] \"%s\"",
+				(long) str_len(str), str_sanitize(str_c(str), 80));
+	}
+}
+
+bool opr_string_dump
+(const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+	sieve_size_t *address)
+{
+	string_t *str;
+
+	if ( sieve_binary_read_string(denv->sblock, address, &str) ) {
+		_dump_string(denv, str, oprnd->field_name);
+
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static int opr_string_read
+(const struct sieve_runtime_env *renv, 	const struct sieve_operand *oprnd,
+	sieve_size_t *address, string_t **str_r)
+{
+	if ( !sieve_binary_read_string(renv->sblock, address, str_r) ) {
+		sieve_runtime_trace_operand_error(renv, oprnd,
+			"invalid string operand");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	return SIEVE_EXEC_OK;
+}
+
+/* String list */
+
+void sieve_opr_stringlist_emit_start
+(struct sieve_binary_block *sblock, unsigned int listlen, void **context)
+{
+	sieve_size_t *end_offset = t_new(sieve_size_t, 1);
+
+	/* Emit byte identifying the type of operand */
+	(void) sieve_operand_emit(sblock, NULL, &stringlist_operand);
+
+	/* Give the interpreter an easy way to skip over this string list */
+	*end_offset = sieve_binary_emit_offset(sblock, 0);
+	*context = (void *) end_offset;
+
+	/* Emit the length of the list */
+	(void) sieve_binary_emit_unsigned(sblock, listlen);
+}
+
+void sieve_opr_stringlist_emit_item
+(struct sieve_binary_block *sblock, void *context ATTR_UNUSED, string_t *item)
+{
+	(void) sieve_opr_string_emit(sblock, item);
+}
+
+void sieve_opr_stringlist_emit_end
+(struct sieve_binary_block *sblock, void *context)
+{
+	sieve_size_t *end_offset = (sieve_size_t *) context;
+
+	(void) sieve_binary_resolve_offset(sblock, *end_offset);
+}
+
+bool sieve_opr_stringlist_dump_data
+(const struct sieve_dumptime_env *denv, struct sieve_operand *oprnd,
+	sieve_size_t *address, const char *field_name)
+{
+	if ( oprnd == NULL || oprnd->def == NULL )
+		return FALSE;
+
+	oprnd->field_name = field_name;
+
+	if ( oprnd->def->class == &stringlist_class ) {
+		const struct sieve_opr_stringlist_interface *intf =
+			(const struct sieve_opr_stringlist_interface *) oprnd->def->interface;
+
+		if ( intf->dump == NULL )
+			return FALSE;
+
+		return intf->dump(denv, oprnd, address);
+	} else if ( oprnd->def->class == &string_class ) {
+		const struct sieve_opr_string_interface *intf =
+			(const struct sieve_opr_string_interface *) oprnd->def->interface;
+
+		if ( intf->dump == NULL )
+			return FALSE;
+
+		return intf->dump(denv, oprnd, address);
+	}
+
+	return FALSE;
+}
+
+bool sieve_opr_stringlist_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+	const char *field_name)
+{
+	struct sieve_operand operand;
+
+	sieve_code_mark(denv);
+
+	if ( !sieve_operand_read(denv->sblock, address, field_name, &operand) ) {
+		return FALSE;
+	}
+
+	return sieve_opr_stringlist_dump_data(denv, &operand, address, field_name);
+}
+
+bool sieve_opr_stringlist_dump_ex
+(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+	const char *field_name, const char *omitted_value)
+{
+	struct sieve_operand operand;
+
+	sieve_code_mark(denv);
+
+	if ( !sieve_operand_read(denv->sblock, address, field_name, &operand) ) {
+		return FALSE;
+	}
+
+	if ( omitted_value != NULL && sieve_operand_is_omitted(&operand) ) {
+		if ( *omitted_value != '\0' )
+			sieve_code_dumpf(denv, "%s: %s", field_name, omitted_value);
+		return TRUE;
+	}
+
+	return sieve_opr_stringlist_dump_data(denv, &operand, address, field_name);
+}
+
+int sieve_opr_stringlist_read_data
+(const struct sieve_runtime_env *renv, struct sieve_operand *oprnd,
+	sieve_size_t *address, const char *field_name,
+	struct sieve_stringlist **strlist_r)
+{
+	if ( oprnd == NULL || oprnd->def == NULL )
+		return SIEVE_EXEC_FAILURE;
+
+	oprnd->field_name = field_name;
+
+	if ( oprnd->def->class == &stringlist_class ) {
+		const struct sieve_opr_stringlist_interface *intf =
+			(const struct sieve_opr_stringlist_interface *) oprnd->def->interface;
+		int ret;
+
+		if ( intf->read == NULL ) {
+			sieve_runtime_trace_operand_error(renv, oprnd,
+				"stringlist operand not implemented");
+			return SIEVE_EXEC_FAILURE;
+		}
+
+		if ( (ret=intf->read(renv, oprnd, address, strlist_r)) <= 0 )
+			return ret;
+
+		return SIEVE_EXEC_OK;
+	} else if ( oprnd->def->class == &string_class ) {
+		/* Special case, accept single string as string list as well. */
+		const struct sieve_opr_string_interface *intf =
+			(const struct sieve_opr_string_interface *) oprnd->def->interface;
+		int ret;
+
+		if ( intf->read == NULL ) {
+			sieve_runtime_trace_operand_error(renv, oprnd,
+				"stringlist string operand not implemented");
+			return SIEVE_EXEC_FAILURE;
+		}
+
+		if ( strlist_r == NULL ) {
+			if ( (ret=intf->read(renv, oprnd, address, NULL)) <= 0 )
+				return ret;
+		} else {
+			string_t *stritem;
+			if ( (ret=intf->read(renv, oprnd, address, &stritem)) <= 0 )
+				return ret;
+
+			*strlist_r = sieve_single_stringlist_create
+				(renv, stritem, FALSE);
+		}
+		return SIEVE_EXEC_OK;
+	}
+
+	sieve_runtime_trace_operand_error(renv, oprnd,
+		"expected stringlist or string operand but found %s",
+		sieve_operand_name(oprnd));
+	return SIEVE_EXEC_BIN_CORRUPT;
+}
+
+int sieve_opr_stringlist_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+	const char *field_name, struct sieve_stringlist **strlist_r)
+{
+	struct sieve_operand operand;
+	int ret;
+
+	if ( (ret=sieve_operand_runtime_read(renv, address, field_name, &operand))
+		<= 0 )
+		return ret;
+
+	return sieve_opr_stringlist_read_data
+		(renv, &operand, address, field_name, strlist_r);
+}
+
+int sieve_opr_stringlist_read_ex
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+	const char *field_name, bool optional, struct sieve_stringlist **strlist_r)
+{
+	struct sieve_operand operand;
+	int ret;
+
+	if ( (ret=sieve_operand_runtime_read(renv, address, field_name, &operand))
+		<= 0 )
+		return ret;
+
+	if ( optional && sieve_operand_is_omitted(&operand) ) {
+		*strlist_r = NULL;
+		return 1;
+	}
+
+	return sieve_opr_stringlist_read_data
+		(renv, &operand, address, field_name, strlist_r);
+}
+
+static bool opr_stringlist_dump
+(const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+	sieve_size_t *address)
+{
+	sieve_size_t pc = *address;
+	sieve_size_t end;
+	unsigned int length = 0;
+ 	sieve_offset_t end_offset;
+
+	if ( !sieve_binary_read_offset(denv->sblock, address, &end_offset) )
+		return FALSE;
+
+	end = pc + end_offset;
+
+	if ( !sieve_binary_read_unsigned(denv->sblock, address, &length) )
+		return FALSE;
+
+	return sieve_code_stringlist_dump
+		(denv, address, length, end, oprnd->field_name);
+}
+
+static int opr_stringlist_read
+(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+	sieve_size_t *address, struct sieve_stringlist **strlist_r)
+{
+	sieve_size_t pc = *address;
+	sieve_size_t end;
+	unsigned int length = 0;
+	sieve_offset_t end_offset;
+
+	if ( !sieve_binary_read_offset(renv->sblock, address, &end_offset) ) {
+		sieve_runtime_trace_operand_error(renv, oprnd,
+			"stringlist corrupt: invalid end offset");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	end = pc + end_offset;
+
+	if ( !sieve_binary_read_unsigned(renv->sblock, address, &length) ) {
+		sieve_runtime_trace_operand_error(renv, oprnd,
+			"stringlist corrupt: invalid length data");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	if ( strlist_r != NULL )
+		*strlist_r = sieve_code_stringlist_create
+			(renv, *address, (unsigned int) length, end);
+
+	/* Skip over the string list for now */
+	*address = end;
+
+	return SIEVE_EXEC_OK;
+}
+
+/* Catenated String */
+
+void sieve_opr_catenated_string_emit
+(struct sieve_binary_block *sblock, unsigned int elements)
+{
+	(void) sieve_operand_emit(sblock, NULL, &catenated_string_operand);
+	(void) sieve_binary_emit_unsigned(sblock, elements);
+}
+
+static bool opr_catenated_string_dump
+(const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+	sieve_size_t *address)
+{
+	unsigned int elements = 0;
+	unsigned int i;
+
+	if ( !sieve_binary_read_unsigned(denv->sblock, address, &elements) )
+		return FALSE;
+
+	if ( oprnd->field_name != NULL )
+		sieve_code_dumpf(denv, "%s: CAT-STR [%ld]:",
+			oprnd->field_name, (long) elements);
+	else
+		sieve_code_dumpf(denv, "CAT-STR [%ld]:", (long) elements);
+
+	sieve_code_descend(denv);
+	for ( i = 0; i < (unsigned int) elements; i++ ) {
+		if ( !sieve_opr_string_dump(denv, address, NULL) )
+			return FALSE;
+	}
+	sieve_code_ascend(denv);
+
+	return TRUE;
+}
+
+static int opr_catenated_string_read
+(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+	sieve_size_t *address, string_t **str)
+{
+	unsigned int elements = 0;
+	unsigned int i;
+	int ret;
+
+	if ( !sieve_binary_read_unsigned(renv->sblock, address, &elements) ) {
+		sieve_runtime_trace_operand_error(renv, oprnd,
+			"catenated string corrupt: invalid element count data");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	/* Parameter str can be NULL if we are requested to only skip and not
+	 * actually read the argument.
+	 */
+	if ( str == NULL ) {
+		for ( i = 0; i < (unsigned int) elements; i++ ) {
+			if ( (ret=sieve_opr_string_read(renv, address, NULL, NULL)) <= 0 )
+				return ret;
+		}
+	} else {
+		string_t *strelm;
+		string_t **elm = &strelm;
+
+		*str = t_str_new(128);
+		for ( i = 0; i < (unsigned int) elements; i++ ) {
+
+			if ( (ret=sieve_opr_string_read(renv, address, NULL, elm)) <= 0 )
+				return ret;
+
+			if ( elm != NULL ) {
+				str_append_str(*str, strelm);
+
+				if ( str_len(*str) > SIEVE_MAX_STRING_LEN ) {
+					str_truncate(*str, SIEVE_MAX_STRING_LEN);
+					elm = NULL;
+				}
+			}
+		}
+	}
+
+	return SIEVE_EXEC_OK;
+}
+
+/*
+ * Core operations
+ */
+
+/* Forward declarations */
+
+static bool opc_jmp_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+
+static int opc_jmp_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+static int opc_jmptrue_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+static int opc_jmpfalse_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+/* Operation objects defined in this file */
+
+const struct sieve_operation_def sieve_jmp_operation = {
+	.mnemonic = "JMP",
+	.code = SIEVE_OPERATION_JMP,
+	.dump = opc_jmp_dump,
+	.execute = opc_jmp_execute
+};
+
+const struct sieve_operation_def sieve_jmptrue_operation = {
+	.mnemonic = "JMPTRUE",
+	.code = SIEVE_OPERATION_JMPTRUE,
+	.dump = opc_jmp_dump,
+	.execute = opc_jmptrue_execute
+};
+
+const struct sieve_operation_def sieve_jmpfalse_operation = {
+	.mnemonic = "JMPFALSE",
+	.code = SIEVE_OPERATION_JMPFALSE,
+	.dump = opc_jmp_dump,
+	.execute = opc_jmpfalse_execute
+};
+
+/* Operation objects defined in other files */
+
+extern const struct sieve_operation_def cmd_stop_operation;
+extern const struct sieve_operation_def cmd_keep_operation;
+extern const struct sieve_operation_def cmd_discard_operation;
+extern const struct sieve_operation_def cmd_redirect_operation;
+
+extern const struct sieve_operation_def tst_address_operation;
+extern const struct sieve_operation_def tst_header_operation;
+extern const struct sieve_operation_def tst_exists_operation;
+extern const struct sieve_operation_def tst_size_over_operation;
+extern const struct sieve_operation_def tst_size_under_operation;
+
+const struct sieve_operation_def *sieve_operations[] = {
+	NULL,
+
+	&sieve_jmp_operation,
+	&sieve_jmptrue_operation,
+	&sieve_jmpfalse_operation,
+
+	&cmd_stop_operation,
+	&cmd_keep_operation,
+	&cmd_discard_operation,
+	&cmd_redirect_operation,
+
+	&tst_address_operation,
+	&tst_header_operation,
+	&tst_exists_operation,
+	&tst_size_over_operation,
+	&tst_size_under_operation
+};
+
+const unsigned int sieve_operation_count =
+	N_ELEMENTS(sieve_operations);
+
+/*
+ * Operation functions
+ */
+
+sieve_size_t sieve_operation_emit
+(struct sieve_binary_block *sblock, const struct sieve_extension *ext,
+	const struct sieve_operation_def *op_def)
+{
+	sieve_size_t address;
+
+  if ( ext != NULL ) {
+		i_assert( op_def->ext_def != NULL );
+		address = sieve_binary_emit_extension
+			(sblock, ext, sieve_operation_count);
+
+		sieve_binary_emit_extension_object
+			(sblock, &op_def->ext_def->operations, op_def->code);
+		return address;
+  }
+
+	i_assert( op_def->ext_def == NULL );
+  return sieve_binary_emit_byte(sblock, op_def->code);
+}
+
+bool sieve_operation_read
+(struct sieve_binary_block *sblock, sieve_size_t *address,
+	struct sieve_operation *oprtn)
+{
+	unsigned int code = sieve_operation_count;
+
+	oprtn->address = *address;
+	oprtn->def = NULL;
+	oprtn->ext = NULL;
+
+	if ( !sieve_binary_read_extension(sblock, address, &code, &oprtn->ext) )
+		return FALSE;
+
+	if ( oprtn->ext == NULL ) {
+		if ( code < sieve_operation_count ) {
+			oprtn->def = sieve_operations[code];
+		}
+
+		return ( oprtn->def != NULL );
+	}
+
+	oprtn->def = (const struct sieve_operation_def *)
+		sieve_binary_read_extension_object(sblock, address,
+			&oprtn->ext->def->operations);
+
+	return ( oprtn->def != NULL );
+}
+
+/*
+ * Jump operations
+ */
+
+/* Code dump */
+
+static bool opc_jmp_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	const struct sieve_operation *oprtn = denv->oprtn;
+	unsigned int pc = *address;
+	sieve_offset_t offset;
+
+	if ( sieve_binary_read_offset(denv->sblock, address, &offset) )
+		sieve_code_dumpf(denv, "%s %d [%08x]",
+			sieve_operation_mnemonic(oprtn), offset, pc + offset);
+	else
+		return FALSE;
+
+	return TRUE;
+}
+
+/* Code execution */
+
+static int opc_jmp_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED)
+{
+	return sieve_interpreter_program_jump(renv->interp, TRUE, FALSE);
+}
+
+static int opc_jmptrue_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED)
+{
+	bool result = sieve_interpreter_get_test_result(renv->interp);
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS, "jump if result is true");
+	sieve_runtime_trace_descend(renv);
+
+	return sieve_interpreter_program_jump(renv->interp, result, FALSE);
+}
+
+static int opc_jmpfalse_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED)
+{
+	bool result = sieve_interpreter_get_test_result(renv->interp);
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS, "jump if result is false");
+	sieve_runtime_trace_descend(renv);
+
+	return sieve_interpreter_program_jump(renv->interp, !result, FALSE);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-code.h
@@ -0,0 +1,351 @@
+#ifndef SIEVE_CODE_H
+#define SIEVE_CODE_H
+
+#include "lib.h"
+#include "buffer.h"
+#include "mempool.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-runtime.h"
+#include "sieve-runtime-trace.h"
+#include "sieve-dump.h"
+
+/*
+ * Operand object
+ */
+
+struct sieve_operand_class {
+	const char *name;
+};
+
+struct sieve_operand_def {
+	const char *name;
+
+	const struct sieve_extension_def *ext_def;
+	unsigned int code;
+
+	const struct sieve_operand_class *class;
+	const void *interface;
+};
+
+struct sieve_operand {
+	const struct sieve_operand_def *def;
+	const struct sieve_extension *ext;
+	sieve_size_t address;
+	const char *field_name;
+};
+
+#define sieve_operand_name(opr) \
+	( (opr)->def == NULL ? "(NULL)" : (opr)->def->name )
+#define sieve_operand_is(opr, definition) \
+	( (opr)->def == &(definition) )
+
+sieve_size_t sieve_operand_emit
+	(struct sieve_binary_block *sblock, const struct sieve_extension *ext,
+		const struct sieve_operand_def *oprnd);
+bool sieve_operand_read
+	(struct sieve_binary_block *sblock, sieve_size_t *address,
+		const char *field_name, struct sieve_operand *oprnd);
+
+static inline int sieve_operand_runtime_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+	const char *field_name, struct sieve_operand *operand)
+{
+	if ( !sieve_operand_read(renv->sblock, address, field_name, operand) ) {
+		sieve_runtime_trace_operand_error(renv, operand, "invalid operand");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	return SIEVE_EXEC_OK;
+}
+
+/*
+ * Optional operands
+ */
+
+int sieve_opr_optional_next
+(struct sieve_binary_block *sblock, sieve_size_t *address,
+	signed int *opt_code);
+
+static inline int sieve_opr_optional_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+	signed int *opt_code)
+{
+	sieve_size_t pc = *address;
+	int ret;
+
+	if ( (ret=sieve_opr_optional_next(denv->sblock, address, opt_code)) <= 0 )
+		return ret;
+
+	sieve_code_mark_specific(denv, pc);
+	return ret;
+}
+
+static inline int sieve_opr_optional_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+	signed int *opt_code)
+{
+	int ret;
+
+	if ( (ret=sieve_opr_optional_next(renv->sblock, address, opt_code)) < 0 )
+		sieve_runtime_trace_error(renv, "invalid optional operand code");
+
+	return ret;
+}
+
+/*
+ * Core operands
+ */
+
+/* Operand codes */
+
+enum sieve_core_operand {
+	SIEVE_OPERAND_OPTIONAL = 0x00,
+	SIEVE_OPERAND_NUMBER,
+	SIEVE_OPERAND_STRING,
+	SIEVE_OPERAND_STRING_LIST,
+	SIEVE_OPERAND_COMPARATOR,
+	SIEVE_OPERAND_MATCH_TYPE,
+	SIEVE_OPERAND_ADDRESS_PART,
+	SIEVE_OPERAND_CATENATED_STRING,
+
+	SIEVE_OPERAND_CUSTOM
+};
+
+/* Operand classes */
+
+extern const struct sieve_operand_class number_class;
+extern const struct sieve_operand_class string_class;
+extern const struct sieve_operand_class stringlist_class;
+
+/* Operand objects */
+
+extern const struct sieve_operand_def omitted_operand;
+extern const struct sieve_operand_def number_operand;
+extern const struct sieve_operand_def string_operand;
+extern const struct sieve_operand_def stringlist_operand;
+extern const struct sieve_operand_def catenated_string_operand;
+
+extern const struct sieve_operand_def *sieve_operands[];
+extern const unsigned int sieve_operand_count;
+
+/* Operand object interfaces */
+
+struct sieve_opr_number_interface {
+	bool (*dump)
+		(const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+			sieve_size_t *address);
+	int (*read)
+	  (const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+			sieve_size_t *address, sieve_number_t *number_r);
+};
+
+struct sieve_opr_string_interface {
+	bool (*dump)
+		(const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+			sieve_size_t *address);
+	int (*read)
+		(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+		 	sieve_size_t *address, string_t **str_r);
+};
+
+struct sieve_opr_stringlist_interface {
+	bool (*dump)
+		(const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+			sieve_size_t *address);
+	int (*read)
+		(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+			sieve_size_t *address, struct sieve_stringlist **strlist_r);
+};
+
+/*
+ * Core operand functions
+ */
+
+/* Omitted */
+
+void sieve_opr_omitted_emit(struct sieve_binary_block *sblock);
+
+static inline bool sieve_operand_is_omitted
+(const struct sieve_operand *operand)
+{
+	return ( operand != NULL && operand->def != NULL &&
+		operand->def == &omitted_operand );
+}
+
+/* Number */
+
+void sieve_opr_number_emit
+	(struct sieve_binary_block *sblock, sieve_number_t number);
+bool sieve_opr_number_dump_data
+	(const struct sieve_dumptime_env *denv, struct sieve_operand *operand,
+		sieve_size_t *address, const char *field_name);
+bool sieve_opr_number_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+		const char *field_name);
+int sieve_opr_number_read_data
+	(const struct sieve_runtime_env *renv, struct sieve_operand *operand,
+		sieve_size_t *address, const char *field_name, sieve_number_t *number_r);
+int sieve_opr_number_read
+	(const struct sieve_runtime_env *renv, sieve_size_t *address,
+		const char *field_name, sieve_number_t *number_r);
+
+static inline bool sieve_operand_is_number
+(const struct sieve_operand *operand)
+{
+	return ( operand != NULL && operand->def != NULL &&
+		operand->def->class == &number_class );
+}
+
+/* String */
+
+void sieve_opr_string_emit
+	(struct sieve_binary_block *sblock, string_t *str);
+bool sieve_opr_string_dump_data
+	(const struct sieve_dumptime_env *denv, struct sieve_operand *operand,
+		sieve_size_t *address, const char *field_name);
+bool sieve_opr_string_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+		const char *field_name);
+bool sieve_opr_string_dump_ex
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+		const char *field_name, const char *omitted_value);
+int sieve_opr_string_read_data
+	(const struct sieve_runtime_env *renv, struct sieve_operand *operand,
+		sieve_size_t *address, const char *field_name, string_t **str_r);
+int sieve_opr_string_read
+	(const struct sieve_runtime_env *renv, sieve_size_t *address,
+		const char *field_name, string_t **str_r);
+int sieve_opr_string_read_ex
+	(const struct sieve_runtime_env *renv, sieve_size_t *address,
+		const char *field_name, bool optional, string_t **str_r, bool *literal_r);
+
+static inline bool sieve_operand_is_string
+(const struct sieve_operand *operand)
+{
+	return ( operand != NULL && operand->def != NULL &&
+		operand->def->class == &string_class );
+}
+
+static inline bool sieve_operand_is_string_literal
+(const struct sieve_operand *operand)
+{
+	return ( operand != NULL && sieve_operand_is(operand, string_operand) );
+}
+
+/* String list */
+
+void sieve_opr_stringlist_emit_start
+	(struct sieve_binary_block *sblock, unsigned int listlen, void **context);
+void sieve_opr_stringlist_emit_item
+	(struct sieve_binary_block *sblock, void *context ATTR_UNUSED,
+		string_t *item);
+void sieve_opr_stringlist_emit_end
+	(struct sieve_binary_block *sblock, void *context);
+bool sieve_opr_stringlist_dump_data
+	(const struct sieve_dumptime_env *denv, struct sieve_operand *operand,
+		sieve_size_t *address, const char *field_name);
+bool sieve_opr_stringlist_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+		const char *field_name);
+bool sieve_opr_stringlist_dump_ex
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+		const char *field_name, const char *omitted_value);
+int sieve_opr_stringlist_read_data
+	(const struct sieve_runtime_env *renv, struct sieve_operand *operand,
+		sieve_size_t *address, const char *field_name,
+		struct sieve_stringlist **strlist_r);
+int sieve_opr_stringlist_read
+	(const struct sieve_runtime_env *renv, sieve_size_t *address,
+		const char *field_name, struct sieve_stringlist **strlist_r);
+int sieve_opr_stringlist_read_ex
+	(const struct sieve_runtime_env *renv, sieve_size_t *address,
+		const char *field_name, bool optional, struct sieve_stringlist **strlist_r);
+
+static inline bool sieve_operand_is_stringlist
+(const struct sieve_operand *operand)
+{
+	return ( operand != NULL && operand->def != NULL &&
+		(operand->def->class == &stringlist_class ||
+			operand->def->class == &string_class) );
+}
+
+/* Catenated string */
+
+void sieve_opr_catenated_string_emit
+	(struct sieve_binary_block *sblock, unsigned int elements);
+
+/*
+ * Operation object
+ */
+
+struct sieve_operation_def {
+	const char *mnemonic;
+
+	const struct sieve_extension_def *ext_def;
+	unsigned int code;
+
+	bool (*dump)
+		(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+	int (*execute)
+		(const struct sieve_runtime_env *renv, sieve_size_t *address);
+};
+
+struct sieve_operation {
+	const struct sieve_operation_def *def;
+	const struct sieve_extension *ext;
+
+	sieve_size_t address;
+};
+
+#define sieve_operation_is(oprtn, definition) \
+	( (oprtn)->def == &(definition) )
+#define sieve_operation_mnemonic(oprtn) \
+	( (oprtn)->def == NULL ? "(NULL)" : (oprtn)->def->mnemonic )
+
+sieve_size_t sieve_operation_emit
+	(struct sieve_binary_block *sblock, const struct sieve_extension *ext,
+		const struct sieve_operation_def *op_def);
+bool sieve_operation_read
+	(struct sieve_binary_block *sblock, sieve_size_t *address,
+		struct sieve_operation *oprtn);
+const char *sieve_operation_read_string
+	(struct sieve_binary_block *sblock, sieve_size_t *address);
+
+/*
+ * Core operations
+ */
+
+/* Opcodes */
+
+enum sieve_operation_code {
+	SIEVE_OPERATION_INVALID,
+	SIEVE_OPERATION_JMP,
+	SIEVE_OPERATION_JMPTRUE,
+	SIEVE_OPERATION_JMPFALSE,
+
+	SIEVE_OPERATION_STOP,
+	SIEVE_OPERATION_KEEP,
+	SIEVE_OPERATION_DISCARD,
+	SIEVE_OPERATION_REDIRECT,
+
+	SIEVE_OPERATION_ADDRESS,
+	SIEVE_OPERATION_HEADER,
+	SIEVE_OPERATION_EXISTS,
+	SIEVE_OPERATION_SIZE_OVER,
+	SIEVE_OPERATION_SIZE_UNDER,
+
+	SIEVE_OPERATION_CUSTOM
+};
+
+/* Operation objects */
+
+extern const struct sieve_operation_def sieve_jmp_operation;
+extern const struct sieve_operation_def sieve_jmptrue_operation;
+extern const struct sieve_operation_def sieve_jmpfalse_operation;
+
+extern const struct sieve_operation_def *sieve_operations[];
+extern const unsigned int sieve_operations_count;
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-commands.c
@@ -0,0 +1,403 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+
+#include "rfc2822.h"
+
+#include "sieve-common.h"
+#include "sieve-ast.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-interpreter.h"
+
+/*
+ * Literal arguments
+ */
+
+/* Forward declarations */
+
+static bool arg_number_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+		struct sieve_command *context);
+static bool arg_string_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+		struct sieve_command *context);
+static bool arg_string_list_validate
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *context);
+static bool arg_string_list_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+		struct sieve_command *context);
+
+/* Argument objects */
+
+const struct sieve_argument_def number_argument = {
+	.identifier = "@number",
+	.generate = arg_number_generate
+};
+
+const struct sieve_argument_def string_argument = {
+	.identifier = "@string",
+	.generate = arg_string_generate
+};
+
+const struct sieve_argument_def string_list_argument = {
+	.identifier = "@string-list",
+	.validate = arg_string_list_validate,
+	.generate = arg_string_list_generate
+};
+
+/* Argument implementations */
+
+static bool arg_number_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+	struct sieve_command *cmd ATTR_UNUSED)
+{
+	sieve_opr_number_emit(cgenv->sblock, sieve_ast_argument_number(arg));
+
+	return TRUE;
+}
+
+static bool arg_string_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+	struct sieve_command *cmd ATTR_UNUSED)
+{
+	sieve_opr_string_emit(cgenv->sblock, sieve_ast_argument_str(arg));
+
+	return TRUE;
+}
+
+static bool arg_string_list_validate
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *stritem;
+
+	stritem = sieve_ast_strlist_first(*arg);
+	while ( stritem != NULL ) {
+		if ( !sieve_validator_argument_activate(valdtr, cmd, stritem, FALSE) )
+			return FALSE;
+
+		stritem = sieve_ast_strlist_next(stritem);
+	}
+
+	return TRUE;
+}
+
+static bool emit_string_list_operand
+(const struct sieve_codegen_env *cgenv, const struct sieve_ast_argument *strlist,
+	struct sieve_command *cmd)
+{
+	void *list_context;
+	struct sieve_ast_argument *stritem;
+
+	sieve_opr_stringlist_emit_start
+		(cgenv->sblock, sieve_ast_strlist_count(strlist), &list_context);
+
+	stritem = sieve_ast_strlist_first(strlist);
+	while ( stritem != NULL ) {
+		if ( !sieve_generate_argument(cgenv, stritem, cmd) )
+			return FALSE;
+
+		stritem = sieve_ast_strlist_next(stritem);
+	}
+
+	sieve_opr_stringlist_emit_end(cgenv->sblock, list_context);
+
+	return TRUE;
+}
+
+static bool arg_string_list_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+	struct sieve_command *cmd)
+{
+	if ( sieve_ast_argument_type(arg) == SAAT_STRING ) {
+		return ( sieve_generate_argument(cgenv, arg, cmd) );
+
+	} else if ( sieve_ast_argument_type(arg) == SAAT_STRING_LIST ) {
+		bool result = TRUE;
+
+		if ( sieve_ast_strlist_count(arg) == 1 )
+			return ( sieve_generate_argument
+				(cgenv, sieve_ast_strlist_first(arg), cmd) );
+		else {
+			T_BEGIN {
+				result=emit_string_list_operand(cgenv, arg, cmd);
+			} T_END;
+		}
+
+		return result;
+	}
+
+	return FALSE;
+}
+
+/*
+ * Abstract arguments
+ *
+ *   (Generated by processing and not by parsing the grammar)
+ */
+
+/* Catenated string */
+
+struct sieve_arg_catenated_string {
+	struct sieve_ast_arg_list *str_parts;
+};
+
+struct sieve_arg_catenated_string *sieve_arg_catenated_string_create
+(struct sieve_ast_argument *orig_arg)
+{
+	pool_t pool = sieve_ast_pool(orig_arg->ast);
+	struct sieve_ast_arg_list *arglist;
+	struct sieve_arg_catenated_string *catstr;
+
+	arglist = sieve_ast_arg_list_create(pool);
+
+	catstr = p_new(pool, struct sieve_arg_catenated_string, 1);
+	catstr->str_parts = arglist;
+	(orig_arg)->argument->data = (void *) catstr;
+
+	return catstr;
+}
+
+void sieve_arg_catenated_string_add_element
+(struct sieve_arg_catenated_string *catstr,
+	struct sieve_ast_argument *element)
+{
+	sieve_ast_arg_list_add(catstr->str_parts, element);
+}
+
+#define _cat_string_first(catstr) __AST_LIST_FIRST((catstr)->str_parts)
+#define _cat_string_count(catstr) __AST_LIST_COUNT((catstr)->str_parts)
+#define _cat_string_next(item) __AST_LIST_NEXT(item)
+
+bool sieve_arg_catenated_string_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+	struct sieve_command *cmd)
+{
+	struct sieve_arg_catenated_string *catstr =
+		(struct sieve_arg_catenated_string *) arg->argument->data;
+	struct sieve_ast_argument *strpart;
+
+	if ( _cat_string_count(catstr) == 1 )
+		sieve_generate_argument(cgenv, _cat_string_first(catstr), cmd);
+	else {
+		sieve_opr_catenated_string_emit(cgenv->sblock, _cat_string_count(catstr));
+
+		strpart = _cat_string_first(catstr);
+		while ( strpart != NULL ) {
+			if ( !sieve_generate_argument(cgenv, strpart, cmd) )
+				return FALSE;
+
+			strpart = _cat_string_next(strpart);
+		}
+	}
+
+	return TRUE;
+}
+
+/*
+ * Argument creation
+ */
+
+struct sieve_argument *sieve_argument_create
+(struct sieve_ast *ast, const struct sieve_argument_def *def,
+	const struct sieve_extension *ext, int id_code)
+{
+	struct sieve_argument *arg;
+	pool_t pool;
+
+	pool = sieve_ast_pool(ast);
+	arg = p_new(pool, struct sieve_argument, 1);
+	arg->def = def;
+	arg->ext = ext;
+	arg->id_code = id_code;
+
+	return arg;
+}
+
+/*
+ * Core tests and commands
+ */
+
+const struct sieve_command_def *sieve_core_tests[] = {
+	&tst_false, &tst_true,
+	&tst_not, &tst_anyof, &tst_allof,
+	&tst_address, &tst_header, &tst_exists, &tst_size
+};
+
+const unsigned int sieve_core_tests_count = N_ELEMENTS(sieve_core_tests);
+
+const struct sieve_command_def *sieve_core_commands[] = {
+	&cmd_require,
+	&cmd_stop, &cmd_if, &cmd_elsif, &cmd_else,
+	&cmd_keep, &cmd_discard, &cmd_redirect
+};
+
+const unsigned int sieve_core_commands_count = N_ELEMENTS(sieve_core_commands);
+
+/*
+ * Command context
+ */
+
+struct sieve_command *sieve_command_prev
+(struct sieve_command *cmd)
+{
+	struct sieve_ast_node *node = sieve_ast_node_prev(cmd->ast_node);
+
+	if ( node != NULL ) {
+		return node->command;
+	}
+
+	return NULL;
+}
+
+struct sieve_command *sieve_command_parent
+(struct sieve_command *cmd)
+{
+	struct sieve_ast_node *node = sieve_ast_node_parent(cmd->ast_node);
+
+	return ( node != NULL ? node->command : NULL );
+}
+
+struct sieve_command *sieve_command_create
+(struct sieve_ast_node *cmd_node, const struct sieve_extension *ext,
+	const struct sieve_command_def *cmd_def,
+	struct sieve_command_registration *cmd_reg)
+{
+	struct sieve_command *cmd;
+
+	cmd = p_new(sieve_ast_node_pool(cmd_node), struct sieve_command, 1);
+
+	cmd->ast_node = cmd_node;
+	cmd->def = cmd_def;
+	cmd->ext = ext;
+	cmd->reg = cmd_reg;
+
+	cmd->block_exit_command = NULL;
+
+	return cmd;
+}
+
+const char *sieve_command_def_type_name
+(const struct sieve_command_def *cmd_def)
+{
+	switch ( cmd_def->type ) {
+	case SCT_NONE: return "command of unspecified type (bug)";
+	case SCT_TEST: return "test";
+	case SCT_COMMAND: return "command";
+	case SCT_HYBRID: return "command or test";
+	default:
+		break;
+	}
+	return "??COMMAND-TYPE??";
+}
+
+const char *sieve_command_type_name
+	(const struct sieve_command *cmd)
+{
+	switch ( cmd->def->type ) {
+	case SCT_NONE: return "command of unspecified type (bug)";
+	case SCT_TEST: return "test";
+	case SCT_COMMAND: return "command";
+	case SCT_HYBRID:
+		if ( cmd->ast_node->type == SAT_TEST )
+			return "test";
+		return "command";
+	default:
+		break;
+	}
+	return "??COMMAND-TYPE??";
+}
+
+struct sieve_ast_argument *sieve_command_add_dynamic_tag
+(struct sieve_command *cmd, const struct sieve_extension *ext,
+	const struct sieve_argument_def *tag, int id_code)
+{
+	struct sieve_ast_argument *arg;
+
+	if ( cmd->first_positional != NULL )
+		arg = sieve_ast_argument_tag_insert
+			(cmd->first_positional, tag->identifier, cmd->ast_node->source_line);
+	else
+		arg = sieve_ast_argument_tag_create
+			(cmd->ast_node, tag->identifier, cmd->ast_node->source_line);
+
+	arg->argument = sieve_argument_create(cmd->ast_node->ast, tag, ext, id_code);
+
+	return arg;
+}
+
+struct sieve_ast_argument *sieve_command_find_argument
+(struct sieve_command *cmd, const struct sieve_argument_def *arg_def)
+{
+	struct sieve_ast_argument *arg = sieve_ast_argument_first(cmd->ast_node);
+
+	/* Visit tagged and optional arguments */
+	while ( arg != NULL ) {
+		if ( arg->argument != NULL && arg->argument->def == arg_def )
+			return arg;
+
+		arg = sieve_ast_argument_next(arg);
+	}
+
+	return arg;
+}
+
+/* Use this function with caution. The command commits to exiting the block.
+ * When it for some reason does not, the interpretation will break later on,
+ * because exiting jumps are not generated when they would otherwise be
+ * necessary.
+ */
+void sieve_command_exit_block_unconditionally
+	(struct sieve_command *cmd)
+{
+	struct sieve_command *parent = sieve_command_parent(cmd);
+
+	/* Only the first unconditional exit is of importance */
+	if ( parent != NULL && parent->block_exit_command == NULL )
+		parent->block_exit_command = cmd;
+}
+
+bool sieve_command_block_exits_unconditionally
+	(struct sieve_command *cmd)
+{
+	return ( cmd->block_exit_command != NULL );
+}
+
+/*
+ * Command utility functions
+ */
+
+/* NOTE: this may be moved */
+
+static int _verify_header_name_item
+(void *context, struct sieve_ast_argument *header)
+{
+	struct sieve_validator *valdtr = (struct sieve_validator *) context;
+	string_t *name = sieve_ast_argument_str(header);
+
+	if ( sieve_argument_is_string_literal(header) &&
+		!rfc2822_header_field_name_verify(str_c(name), str_len(name)) ) {
+		sieve_argument_validate_warning
+			(valdtr, header, "specified header field name '%s' is invalid",
+				str_sanitize(str_c(name), 80));
+
+		return 0;
+	}
+
+	return 1;
+}
+
+bool sieve_command_verify_headers_argument
+(struct sieve_validator *valdtr, struct sieve_ast_argument *headers)
+{
+	return ( sieve_ast_stringlist_map
+		(&headers, (void *) valdtr, _verify_header_name_item) >= 0 );
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-commands.h
@@ -0,0 +1,286 @@
+#ifndef SIEVE_COMMANDS_H
+#define SIEVE_COMMANDS_H
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-ast.h"
+
+/*
+ * Argument definition
+ */
+
+enum sieve_argument_flag {
+	/* More than one of this (type of) tagged argument is allowed */
+	SIEVE_ARGUMENT_FLAG_MULTIPLE = (1 << 0)
+};
+
+struct sieve_argument_def {
+	const char *identifier;
+	enum sieve_argument_flag flags;
+
+	bool (*is_instance_of)
+		(struct sieve_validator *valdtr, struct sieve_command *cmd,
+			const struct sieve_extension *ext, const char *identifier, void **data);
+
+	bool (*validate)
+		(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+			struct sieve_command *cmd);
+	bool (*validate_context)
+		(struct sieve_validator *valdtr, struct sieve_ast_argument *arg,
+			struct sieve_command *cmd);
+	bool (*validate_persistent)
+		(struct sieve_validator *valdtr, struct sieve_command *cmd,
+			const struct sieve_extension *ext);
+
+	bool (*generate)
+		(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+			struct sieve_command *cmd);
+};
+
+/*
+ * Argument instance
+ */
+
+struct sieve_argument {
+	const struct sieve_argument_def *def;
+	const struct sieve_extension *ext;
+	int id_code;
+
+	/* Context data */
+	void *data;
+};
+
+#define sieve_argument_is(ast_arg, definition) \
+	( (ast_arg)->argument->def == &(definition) )
+#define sieve_argument_ext(ast_arg) \
+	( (ast_arg)->argument->ext )
+#define sieve_argument_identifier(ast_arg) \
+	( (ast_arg)->argument->def->identifier )
+
+/* Utility macros */
+
+#define sieve_argument_is_string_literal(arg) \
+	( (arg)->argument->def == &string_argument )
+
+/* Error handling */
+
+#define sieve_argument_validate_error(validator, arg_node, ...) \
+	sieve_validator_error(validator, \
+		((arg_node) == NULL ? 0 : (arg_node)->source_line), \
+		__VA_ARGS__)
+#define sieve_argument_validate_warning(validator, arg_node, ...) \
+	sieve_validator_warning(validator, \
+		((arg_node) == NULL ? 0 : (arg_node)->source_line), \
+		__VA_ARGS__)
+
+#define sieve_argument_generate_error(gentr, arg_node, ...) \
+	sieve_generator_error(gentr, \
+		((arg_node) == NULL ? 0 : (arg_node)->source_line), \
+		__VA_ARGS__)
+#define sieve_argument_generate_warning(gentr, arg_node, ...) \
+	sieve_generator_warning(gentr, \
+		((arg_node) == NULL ? 0 : (arg_node)->source_line), \
+		__VA_ARGS__)
+
+/* Argument API */
+
+struct sieve_argument *sieve_argument_create
+	(struct sieve_ast *ast, const struct sieve_argument_def *def,
+		const struct sieve_extension *ext, int id_code);
+
+/* Literal arguments */
+
+extern const struct sieve_argument_def number_argument;
+extern const struct sieve_argument_def string_argument;
+extern const struct sieve_argument_def string_list_argument;
+
+/* Catenated string argument */
+
+bool sieve_arg_catenated_string_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+		struct sieve_command *context);
+
+struct sieve_arg_catenated_string;
+
+struct sieve_arg_catenated_string *sieve_arg_catenated_string_create
+	(struct sieve_ast_argument *orig_arg);
+void sieve_arg_catenated_string_add_element
+	(struct sieve_arg_catenated_string *strdata,
+		struct sieve_ast_argument *element);
+
+/*
+ * Command definition
+ */
+
+enum sieve_command_type {
+	SCT_NONE,
+	SCT_COMMAND,
+	SCT_TEST,
+	SCT_HYBRID
+};
+
+struct sieve_command_def {
+	const char *identifier;
+	enum sieve_command_type type;
+
+	/* High-level command syntax */
+	int positional_args;
+	int subtests;
+	bool block_allowed;
+	bool block_required;
+
+	bool (*registered)
+		(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+			struct sieve_command_registration *cmd_reg);
+	bool (*pre_validate)
+		(struct sieve_validator *valdtr, struct sieve_command *cmd);
+	bool (*validate)
+		(struct sieve_validator *valdtr, struct sieve_command *cmd);
+	bool (*validate_const)
+		(struct sieve_validator *valdtr, struct sieve_command *cmd,
+			int *const_current, int const_next);
+	bool (*generate)
+		(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+	bool (*control_generate)
+		(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd,
+		struct sieve_jumplist *jumps, bool jump_true);
+};
+
+/*
+ * Command instance
+ */
+
+struct sieve_command {
+	const struct sieve_command_def *def;
+	const struct sieve_extension *ext;
+
+	/* The registration of this command in the validator (sieve-validator.h) */
+	struct sieve_command_registration *reg;
+
+	/* The ast node of this command */
+	struct sieve_ast_node *ast_node;
+
+	/* First positional argument, found during argument validation */
+	struct sieve_ast_argument *first_positional;
+
+	/* The child ast node that unconditionally exits this command's block */
+	struct sieve_command *block_exit_command;
+
+	/* Context data*/
+	void *data;
+};
+
+#define sieve_command_is(cmd, definition) \
+	( (cmd)->def == &(definition) )
+#define sieve_command_identifier(cmd) \
+	( (cmd)->def->identifier )
+
+#define sieve_commands_equal(cmd1, cmd2) \
+	( (cmd1) != NULL && (cmd2) != NULL && (cmd1)->def == (cmd2)->def )
+
+/* Context API */
+
+struct sieve_command *sieve_command_create
+	(struct sieve_ast_node *cmd_node, const struct sieve_extension *ext,
+		const struct sieve_command_def *cmd_def,
+		struct sieve_command_registration *cmd_reg);
+
+const char *sieve_command_def_type_name
+	(const struct sieve_command_def *cmd_def);
+const char *sieve_command_type_name
+	(const struct sieve_command *cmd);
+
+struct sieve_command *sieve_command_prev
+	(struct sieve_command *cmd);
+struct sieve_command *sieve_command_parent
+	(struct sieve_command *cmd);
+
+struct sieve_ast_argument *sieve_command_add_dynamic_tag
+	(struct sieve_command *cmd, const struct sieve_extension *ext,
+		const struct sieve_argument_def *tag, int id_code);
+struct sieve_ast_argument *sieve_command_find_argument
+	(struct sieve_command *cmd, const struct sieve_argument_def *argument);
+
+void sieve_command_exit_block_unconditionally
+	(struct sieve_command *cmd);
+bool sieve_command_block_exits_unconditionally
+	(struct sieve_command *cmd);
+
+/* Error handling */
+
+#define sieve_command_validate_error(validator, context, ...) \
+	sieve_validator_error(validator, \
+		((context) == NULL ? 0 : (context)->ast_node->source_line), \
+		__VA_ARGS__)
+#define sieve_command_validate_warning(validator, context, ...) \
+	sieve_validator_warning(validator, \
+		((context) == NULL ? 0 : (context)->ast_node->source_line), \
+		__VA_ARGS__)
+
+#define sieve_command_generate_error(gentr, context, ...) \
+	sieve_generator_error(gentr, \
+		((context) == NULL ? 0 : (context)->ast_node->source_line), \
+		__VA_ARGS__)
+#define sieve_command_generate_warning(gentr, context, ...) \
+	sieve_generator_warning(gentr, \
+		((context) == NULL ? 0 : (context)->ast_node->source_line), \
+		__VA_ARGS__)
+
+/* Utility macros */
+
+#define sieve_command_pool(context) \
+	sieve_ast_node_pool((context)->ast_node)
+
+#define sieve_command_source_line(context) \
+	(context)->ast_node->source_line
+
+#define sieve_command_first_argument(context) \
+	sieve_ast_argument_first((context)->ast_node)
+
+#define sieve_command_is_toplevel(context) \
+	( sieve_ast_node_type(sieve_ast_node_parent((context)->ast_node)) == SAT_ROOT )
+#define sieve_command_is_first(context) \
+	( sieve_ast_node_prev((context)->ast_node) == NULL )
+
+/*
+ * Core commands
+ */
+
+extern const struct sieve_command_def cmd_require;
+extern const struct sieve_command_def cmd_stop;
+extern const struct sieve_command_def cmd_if;
+extern const struct sieve_command_def cmd_elsif;
+extern const struct sieve_command_def cmd_else;
+extern const struct sieve_command_def cmd_redirect;
+extern const struct sieve_command_def cmd_keep;
+extern const struct sieve_command_def cmd_discard;
+
+extern const struct sieve_command_def *sieve_core_commands[];
+extern const unsigned int sieve_core_commands_count;
+
+/*
+ * Core tests
+ */
+
+extern const struct sieve_command_def tst_true;
+extern const struct sieve_command_def tst_false;
+extern const struct sieve_command_def tst_not;
+extern const struct sieve_command_def tst_anyof;
+extern const struct sieve_command_def tst_allof;
+extern const struct sieve_command_def tst_address;
+extern const struct sieve_command_def tst_header;
+extern const struct sieve_command_def tst_exists;
+extern const struct sieve_command_def tst_size;
+
+extern const struct sieve_command_def *sieve_core_tests[];
+extern const unsigned int sieve_core_tests_count;
+
+/*
+ * Command utility functions
+ */
+
+bool sieve_command_verify_headers_argument
+(struct sieve_validator *valdtr, struct sieve_ast_argument *headers);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-common.h
@@ -0,0 +1,231 @@
+#ifndef SIEVE_COMMON_H
+#define SIEVE_COMMON_H
+
+#include "lib.h"
+
+#include "sieve-config.h"
+#include "sieve-types.h"
+
+#include <sys/types.h>
+
+/*
+ * Types
+ */
+
+typedef size_t sieve_size_t;
+typedef uint32_t sieve_offset_t;
+typedef uint64_t sieve_number_t;
+
+#define SIEVE_MAX_NUMBER ((sieve_number_t)-1)
+
+/*
+ * Forward declarations
+ */
+
+/* sieve-error.h */
+struct sieve_error_handler;
+
+/* sieve-ast.h */
+enum sieve_ast_argument_type;
+
+struct sieve_ast;
+struct sieve_ast_node;
+struct sieve_ast_argument;
+
+/* sieve-commands.h */
+struct sieve_argument;
+struct sieve_argument_def;
+struct sieve_command;
+struct sieve_command_def;
+struct sieve_command_context;
+struct sieve_command_registration;
+
+/* sieve-stringlist.h */
+struct sieve_stringlist;
+
+/* sieve-code.h */
+struct sieve_operation_extension;
+
+/* sieve-lexer.h */
+struct sieve_lexer;
+
+/* sieve-parser.h */
+struct sieve_parser;
+
+/* sieve-validator.h */
+struct sieve_validator;
+
+/* sieve-generator.h */
+struct sieve_jumplist;
+struct sieve_generator;
+struct sieve_codegen_env;
+
+/* sieve-runtime.h */
+struct sieve_runtime_env;
+
+/* sieve-interpreter.h */
+struct sieve_interpreter;
+
+/* sieve-dump.h */
+struct sieve_dumptime_env;
+
+/* sieve-binary-dumper.h */
+struct sieve_binary_dumper;
+
+/* sieve-code-dumper.h */
+struct sieve_code_dumper;
+
+/* sieve-extension.h */
+struct sieve_extension;
+struct sieve_extension_def;
+struct sieve_extension_objects;
+
+/* sieve-code.h */
+struct sieve_operand;
+struct sieve_operand_def;
+struct sieve_operand_class;
+struct sieve_operation;
+struct sieve_coded_stringlist;
+
+/* sieve-binary.h */
+struct sieve_binary;
+struct sieve_binary_block;
+struct sieve_binary_debug_writer;
+struct sieve_binary_debug_reader;
+
+/* sieve-objects.h */
+struct sieve_object_def;
+struct sieve_object;
+
+/* sieve-comparator.h */
+struct sieve_comparator;
+
+/* sieve-match-types.h */
+struct sieve_match_type;
+
+/* sieve-match.h */
+struct sieve_match_context;
+
+/* sieve-address.h */
+struct sieve_address_list;
+
+/* sieve-address-parts.h */
+struct sieve_address_part_def;
+struct sieve_address_part;
+
+/* sieve-result.h */
+struct sieve_result;
+struct sieve_side_effects_list;
+struct sieve_result_print_env;
+
+/* sieve-actions.h */
+struct sieve_action_exec_env;
+struct sieve_action;
+struct sieve_action_def;
+struct sieve_side_effect;
+struct sieve_side_effect_def;
+
+/* sieve-script.h */
+struct sieve_script;
+struct sieve_script_sequence;
+
+/* sieve-storage.h */
+struct sieve_storage_class_registry;
+struct sieve_storage;
+
+/* sieve-message.h */
+struct sieve_message_context;
+struct sieve_message_override;
+struct sieve_message_override_def;
+
+/* sieve-plugins.h */
+struct sieve_plugin;
+
+/* sieve.c */
+struct sieve_ast *sieve_parse
+	(struct sieve_script *script, struct sieve_error_handler *ehandler,
+		enum sieve_error *error_r);
+bool sieve_validate
+	(struct sieve_ast *ast, struct sieve_error_handler *ehandler,
+		enum sieve_compile_flags flags, enum sieve_error *error_r);
+
+/*
+ * Sieve engine instance
+ */
+
+#include "sieve-address-source.h"
+
+struct sieve_instance {
+	/* Main engine pool */
+	pool_t pool;
+
+	/* System environment */
+	const char *hostname;
+	const char *domainname;
+	const char *base_dir;
+	const char *temp_dir;
+
+	/* User environment */
+	const char *username;
+	const char *home_dir;
+
+	/* Flags */
+	enum sieve_flag flags;
+
+	/* Callbacks */
+	const struct sieve_callbacks *callbacks;
+	void *context;
+
+	/* Engine debug */
+	bool debug;
+
+	/* Extension registry */
+	struct sieve_extension_registry *ext_reg;
+
+	/* Storage class registry */
+	struct sieve_storage_class_registry *storage_reg;
+
+	/* System error handler */
+	struct sieve_error_handler *system_ehandler;
+
+	/* Plugin modules */
+	struct sieve_plugin *plugins;
+	enum sieve_env_location env_location;
+	enum sieve_delivery_phase delivery_phase;
+
+	/* Settings */
+	size_t max_script_size;
+	unsigned int max_actions;
+	unsigned int max_redirects;
+	const struct smtp_address *user_email, *user_email_implicit;
+	struct sieve_address_source redirect_from;
+	unsigned int redirect_duplicate_period;
+};
+
+/*
+ * Script trace log
+ */
+
+void sieve_trace_log_write_line
+	(struct sieve_trace_log *trace_log, const string_t *line)
+	ATTR_NULL(2);
+
+/*
+ * User e-mail address
+ */
+
+const struct smtp_address *sieve_get_user_email
+	(struct sieve_instance *svinst);
+
+/*
+ * Postmaster address 
+ */
+
+const struct message_address *
+sieve_get_postmaster(const struct sieve_script_env *senv);
+const struct smtp_address *
+sieve_get_postmaster_smtp(const struct sieve_script_env *senv);
+const char *
+sieve_get_postmaster_address(const struct sieve_script_env *senv);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-comparators.c
@@ -0,0 +1,260 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+#include "hash.h"
+#include "array.h"
+
+#include "sieve-extensions.h"
+#include "sieve-code.h"
+#include "sieve-commands.h"
+#include "sieve-binary.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "sieve-comparators.h"
+
+#include <string.h>
+#include <stdio.h>
+
+/*
+ * Core comparators
+ */
+
+const struct sieve_comparator_def *sieve_core_comparators[] = {
+	&i_octet_comparator, &i_ascii_casemap_comparator
+};
+
+const unsigned int sieve_core_comparators_count =
+	N_ELEMENTS(sieve_core_comparators);
+
+/*
+ * Comparator 'extension'
+ */
+
+static bool cmp_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def comparator_extension = {
+	.name = "@comparators",
+	.validator_load = cmp_validator_load
+};
+
+/*
+ * Validator context:
+ *   name-based comparator registry.
+ */
+
+static struct sieve_validator_object_registry *_get_object_registry
+(struct sieve_validator *valdtr)
+{
+	struct sieve_instance *svinst;
+	const struct sieve_extension *mcht_ext;
+
+	svinst = sieve_validator_svinst(valdtr);
+	mcht_ext = sieve_get_comparator_extension(svinst);
+	return sieve_validator_object_registry_get(valdtr, mcht_ext);
+}
+
+void sieve_comparator_register
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	const struct sieve_comparator_def *cmp)
+{
+	struct sieve_validator_object_registry *regs = _get_object_registry(valdtr);
+
+	sieve_validator_object_registry_add(regs, ext, &cmp->obj_def);
+}
+
+static struct sieve_comparator *sieve_comparator_create
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+	const char *identifier)
+{
+	struct sieve_validator_object_registry *regs = _get_object_registry(valdtr);
+	struct sieve_object object;
+	struct sieve_comparator *cmp;
+
+	if ( !sieve_validator_object_registry_find(regs, identifier, &object) )
+		return NULL;
+
+	cmp = p_new(sieve_command_pool(cmd), struct sieve_comparator, 1);
+	cmp->object = object;
+	cmp->def = (const struct sieve_comparator_def *) object.def;
+
+  return cmp;
+}
+
+bool cmp_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	struct sieve_validator_object_registry *regs =
+		sieve_validator_object_registry_init(valdtr, ext);
+	unsigned int i;
+
+	/* Register core comparators */
+	for ( i = 0; i < sieve_core_comparators_count; i++ ) {
+		sieve_validator_object_registry_add
+			(regs, NULL, &(sieve_core_comparators[i]->obj_def));
+	}
+
+	return TRUE;
+}
+
+/*
+ * Comparator tagged argument
+ */
+
+/* Forward declarations */
+
+static bool tag_comparator_validate
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+static bool tag_comparator_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+		struct sieve_command *cmd);
+
+/* Argument object */
+
+const struct sieve_argument_def comparator_tag = {
+	.identifier = "comparator",
+	.validate = tag_comparator_validate,
+	.generate = tag_comparator_generate
+};
+
+/* Argument implementation */
+
+static bool tag_comparator_validate
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *tag = *arg;
+	const struct sieve_comparator *cmp;
+
+	/* Skip tag */
+	*arg = sieve_ast_argument_next(*arg);
+
+	/* Check syntax:
+	 *   ":comparator" <comparator-name: string>
+	 */
+	if ( !sieve_validate_tag_parameter
+		(valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING, FALSE) ) {
+		return FALSE;
+	}
+
+	/* FIXME: We can currently only handle string literal argument, so
+	 * variables are not allowed.
+	 */
+	if ( !sieve_argument_is_string_literal(*arg) ) {
+		sieve_argument_validate_error(valdtr, *arg,
+			"this Sieve implementation currently only supports "
+			"a literal string argument for the :comparator tag");
+		return FALSE;
+	}
+
+	/* Get comparator from registry */
+	cmp = sieve_comparator_create(valdtr, cmd, sieve_ast_argument_strc(*arg));
+
+	if ( cmp == NULL ) {
+		sieve_argument_validate_error(valdtr, *arg,
+			"unknown comparator '%s'",
+			str_sanitize(sieve_ast_argument_strc(*arg),80));
+
+		return FALSE;
+	}
+
+	/* String argument not needed during code generation, so detach it from
+	 * argument list
+	 */
+	*arg = sieve_ast_arguments_detach(*arg, 1);
+
+	/* Store comparator in context */
+	tag->argument->data = (void *) cmp;
+
+	return TRUE;
+}
+
+static bool tag_comparator_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+	struct sieve_command *cmd ATTR_UNUSED)
+{
+	const struct sieve_comparator *cmp =
+		(const struct sieve_comparator *) arg->argument->data;
+
+	sieve_opr_comparator_emit(cgenv->sblock, cmp);
+
+	return TRUE;
+}
+
+/* Functions to enable and evaluate comparator tag for commands */
+
+void sieve_comparators_link_tag
+(struct sieve_validator *valdtr, struct sieve_command_registration *cmd_reg,
+	int id_code)
+{
+	struct sieve_instance *svinst;
+	const struct sieve_extension *mcht_ext;
+
+	svinst = sieve_validator_svinst(valdtr);
+	mcht_ext = sieve_get_comparator_extension(svinst);
+
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, mcht_ext, &comparator_tag, id_code);
+}
+
+bool sieve_comparator_tag_is
+(struct sieve_ast_argument *tag, const struct sieve_comparator_def *cmp_def)
+{
+	const struct sieve_comparator *cmp;
+
+	if ( !sieve_argument_is(tag, comparator_tag) )
+		return FALSE;
+
+	cmp = (const struct sieve_comparator *) tag->argument->data;
+
+	return ( cmp->def == cmp_def );
+}
+
+const struct sieve_comparator *sieve_comparator_tag_get
+(struct sieve_ast_argument *tag)
+{
+	if ( !sieve_argument_is(tag, comparator_tag) )
+		return NULL;
+
+
+	return (const struct sieve_comparator *) tag->argument->data;
+}
+
+/*
+ * Comparator coding
+ */
+
+const struct sieve_operand_class sieve_comparator_operand_class =
+	{ "comparator" };
+
+static const struct sieve_extension_objects core_comparators =
+	SIEVE_EXT_DEFINE_COMPARATORS(sieve_core_comparators);
+
+const struct sieve_operand_def comparator_operand = {
+	.name = "comparator",
+	.code = SIEVE_OPERAND_COMPARATOR,
+	.class = &sieve_comparator_operand_class,
+	.interface = &core_comparators
+};
+
+/*
+ * Trivial/Common comparator method implementations
+ */
+
+bool sieve_comparator_octet_skip
+	(const struct sieve_comparator *cmp ATTR_UNUSED,
+		const char **val, const char *val_end)
+{
+	if ( *val < val_end ) {
+		(*val)++;
+		return TRUE;
+	}
+
+	return FALSE;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-comparators.h
@@ -0,0 +1,153 @@
+#ifndef SIEVE_COMPARATORS_H
+#define SIEVE_COMPARATORS_H
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-objects.h"
+#include "sieve-code.h"
+
+/*
+ * Core comparators
+ */
+
+enum sieve_comparator_code {
+	SIEVE_COMPARATOR_I_OCTET,
+	SIEVE_COMPARATOR_I_ASCII_CASEMAP,
+	SIEVE_COMPARATOR_CUSTOM
+};
+
+extern const struct sieve_comparator_def i_octet_comparator;
+extern const struct sieve_comparator_def i_ascii_casemap_comparator;
+
+/*
+ * Comparator flags
+ */
+
+enum sieve_comparator_flags {
+	SIEVE_COMPARATOR_FLAG_ORDERING = (1 << 0),
+	SIEVE_COMPARATOR_FLAG_EQUALITY = (1 << 1),
+	SIEVE_COMPARATOR_FLAG_PREFIX_MATCH = (1 << 2),
+	SIEVE_COMPARATOR_FLAG_SUBSTRING_MATCH = (1 << 3),
+};
+
+/*
+ * Comparator definition
+ */
+
+struct sieve_comparator_def {
+	struct sieve_object_def obj_def;
+
+	unsigned int flags;
+
+	/* Equality and ordering */
+
+	int (*compare)(const struct sieve_comparator *cmp,
+		const char *val1, size_t val1_size,
+		const char *val2, size_t val2_size);
+
+	/* Prefix and substring match */
+
+	bool (*char_match)(const struct sieve_comparator *cmp,
+		const char **val, const char *val_end,
+		const char **key, const char *key_end);
+	bool (*char_skip)(const struct sieve_comparator *cmp,
+		const char **val, const char *val_end);
+};
+
+/*
+ * Comparator instance
+ */
+
+struct sieve_comparator {
+	struct sieve_object object;
+
+	const struct sieve_comparator_def *def;
+};
+
+#define SIEVE_COMPARATOR_DEFAULT(definition) \
+	{ SIEVE_OBJECT_DEFAULT(definition), &(definition) }
+
+#define sieve_comparator_name(cmp) \
+	( (cmp)->object.def->identifier )
+#define sieve_comparator_is(cmp, definition) \
+	( (cmp)->def == &(definition) )
+
+static inline const struct sieve_comparator *sieve_comparator_copy
+(pool_t pool, const struct sieve_comparator *cmp_orig)
+{
+	struct sieve_comparator *cmp = p_new(pool, struct sieve_comparator, 1);
+
+	*cmp = *cmp_orig;
+
+	return cmp;
+}
+
+/*
+ * Comparator tagged argument
+ */
+
+extern const struct sieve_argument_def comparator_tag;
+
+static inline bool sieve_argument_is_comparator
+(struct sieve_ast_argument *arg)
+{
+	return ( arg->argument != NULL &&
+		(arg->argument->def == &comparator_tag) );
+}
+
+void sieve_comparators_link_tag
+	(struct sieve_validator *validator,
+		struct sieve_command_registration *cmd_reg,	int id_code);
+bool sieve_comparator_tag_is
+	(struct sieve_ast_argument *tag, const struct sieve_comparator_def *cmp);
+const struct sieve_comparator *sieve_comparator_tag_get
+	(struct sieve_ast_argument *tag);
+
+void sieve_comparator_register
+	(struct sieve_validator *validator, const struct sieve_extension *ext,
+		const struct sieve_comparator_def *cmp);
+
+/*
+ * Comparator operand
+ */
+
+#define SIEVE_EXT_DEFINE_COMPARATOR(OP) SIEVE_EXT_DEFINE_OBJECT(OP)
+#define SIEVE_EXT_DEFINE_COMPARATORS(OPS) SIEVE_EXT_DEFINE_OBJECTS(OPS)
+
+extern const struct sieve_operand_class sieve_comparator_operand_class;
+extern const struct sieve_operand_def comparator_operand;
+
+static inline void sieve_opr_comparator_emit
+(struct sieve_binary_block *sblock, const struct sieve_comparator *cmp)
+{
+	sieve_opr_object_emit(sblock, cmp->object.ext, cmp->object.def);
+}
+static inline bool sieve_opr_comparator_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	return sieve_opr_object_dump
+		(denv, &sieve_comparator_operand_class, address, NULL);
+}
+
+static inline int sieve_opr_comparator_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+	struct sieve_comparator *cmp)
+{
+	if ( !sieve_opr_object_read
+		(renv, &sieve_comparator_operand_class, address, &cmp->object) )
+		return SIEVE_EXEC_BIN_CORRUPT;
+
+	cmp->def = (const struct sieve_comparator_def *) cmp->object.def;
+	return SIEVE_EXEC_OK;
+}
+
+/*
+ * Trivial/Common comparator method implementations
+ */
+
+bool sieve_comparator_octet_skip
+	(const struct sieve_comparator *cmp ATTR_UNUSED,
+		const char **val, const char *val_end);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-config.h
@@ -0,0 +1,17 @@
+#ifndef SIEVE_CONFIG_H
+#define SIEVE_CONFIG_H
+
+#include "pigeonhole-config.h"
+#include "pigeonhole-version.h"
+
+#define SIEVE_IMPLEMENTATION PIGEONHOLE_NAME " Sieve " PIGEONHOLE_VERSION_FULL
+
+#define SIEVE_SCRIPT_FILEEXT "sieve"
+#define SIEVE_BINARY_FILEEXT "svbin"
+
+#define DEFAULT_ENVELOPE_SENDER \
+	SMTP_ADDRESS_LITERAL("MAILER-DAEMON", NULL)
+
+#define DEFAULT_REDIRECT_DUPLICATE_PERIOD (3600 * 12)
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-dump.h
@@ -0,0 +1,30 @@
+#ifndef SIEVE_DUMP_H
+#define SIEVE_DUMP_H
+
+#include "sieve-common.h"
+#include "sieve-code-dumper.h"
+#include "sieve-binary-dumper.h"
+
+/*
+ * Dumptime environment
+ */
+
+struct sieve_dumptime_env {
+	/* Dumpers */
+	struct sieve_instance *svinst;
+	struct sieve_binary_dumper *dumper;
+	struct sieve_code_dumper *cdumper;
+
+	/* Binary */
+	struct sieve_binary *sbin;
+	struct sieve_binary_block *sblock;
+
+	/* Code position */
+	const struct sieve_operation *oprtn;
+	sieve_size_t offset;
+
+	/* Output stream */
+	struct ostream *stream;
+};
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-error-private.h
@@ -0,0 +1,136 @@
+#ifndef SIEVE_ERROR_PRIVATE_H
+#define SIEVE_ERROR_PRIVATE_H
+
+#include "sieve-error.h"
+
+/*
+ * Types
+ */
+
+enum sieve_error_flags {
+	SIEVE_ERROR_FLAG_GLOBAL = (1 << 0),
+	SIEVE_ERROR_FLAG_GLOBAL_MAX_INFO = (1 << 1),
+};
+
+/*
+ * Initialization
+ */
+
+void sieve_errors_init(struct sieve_instance *svinst);
+void sieve_errors_deinit(struct sieve_instance *svinst);
+
+/*
+ * Error handler object
+ */
+
+struct sieve_error_handler {
+	pool_t pool;
+	int refcount;
+
+	struct sieve_instance *svinst;
+
+	struct sieve_error_handler *parent;
+
+	unsigned int max_errors;
+
+	unsigned int errors;
+	unsigned int warnings;
+
+	/* Should the errorhandler handle or discard info/debug log?
+	 * (This does not influence the previous setting)
+	 */
+	bool log_info;
+	bool log_debug;
+
+	void (*verror)
+		(struct sieve_error_handler *ehandler, unsigned int flags,
+			const char *location, const char *fmt, va_list args) ATTR_FORMAT(4, 0);
+	void (*vwarning)
+		(struct sieve_error_handler *ehandler, unsigned int flags,
+			const char *location, const char *fmt, va_list args) ATTR_FORMAT(4, 0);;
+	void (*vinfo)
+		(struct sieve_error_handler *ehandler, unsigned int flags,
+			const char *location, const char *fmt, va_list args) ATTR_FORMAT(4, 0);;
+	void (*vdebug)
+		(struct sieve_error_handler *ehandler, unsigned int flags,
+			const char *location, const char *fmt, va_list args) ATTR_FORMAT(4, 0);;
+
+	void (*free)
+		(struct sieve_error_handler *ehandler);
+};
+
+void sieve_error_handler_init
+	(struct sieve_error_handler *ehandler, struct sieve_instance *svinst,
+		pool_t pool, unsigned int max_errors);
+
+void sieve_error_handler_init_from_parent
+	(struct sieve_error_handler *ehandler, pool_t pool,
+		struct sieve_error_handler *parent);
+
+/*
+ * Direct handler calls
+ */
+
+void sieve_direct_verror
+	(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+		unsigned int flags, const char *location, const char *fmt, va_list args) ATTR_FORMAT(5, 0);
+void sieve_direct_vwarning
+	(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+		unsigned int flags, const char *location, const char *fmt, va_list args) ATTR_FORMAT(5, 0);
+void sieve_direct_vinfo
+	(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+		unsigned int flags, const char *location, const char *fmt, va_list args) ATTR_FORMAT(5, 0);
+void sieve_direct_vdebug
+	(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+		unsigned int flags, const char *location, const char *fmt, va_list args) ATTR_FORMAT(5, 0);
+
+static inline void ATTR_FORMAT(5, 6) sieve_direct_error
+(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+	unsigned int flags, const char *location, const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+
+	sieve_direct_verror(svinst, ehandler, flags, location, fmt, args);
+
+	va_end(args);
+}
+
+static inline void ATTR_FORMAT(5, 6) sieve_direct_warning
+(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+	unsigned int flags, const char *location, const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+
+	sieve_direct_vwarning(svinst, ehandler, flags, location, fmt, args);
+
+	va_end(args);
+}
+
+static inline void ATTR_FORMAT(5, 6) sieve_direct_info
+(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+	unsigned int flags, const char *location, const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+
+	sieve_direct_vinfo(svinst, ehandler, flags, location, fmt, args);
+
+	va_end(args);
+}
+
+static inline void ATTR_FORMAT(5, 6) sieve_direct_debug
+(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+	unsigned int flags, const char *location, const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+
+	sieve_direct_vdebug(svinst, ehandler, flags, location, fmt, args);
+
+	va_end(args);
+}
+
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-error.c
@@ -0,0 +1,1405 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "array.h"
+#include "ostream.h"
+#include "var-expand.h"
+#include "eacces-error.h"
+
+#include "sieve-common.h"
+#include "sieve-script.h"
+#include "sieve-error-private.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+
+/*
+ * Definitions
+ */
+
+#define CRITICAL_MSG \
+	"internal error occurred: refer to server log for more information."
+#define CRITICAL_MSG_STAMP CRITICAL_MSG " [%Y-%m-%d %H:%M:%S]"
+
+/* Logfile error handler will rotate log when it exceeds 10k bytes */
+#define LOGFILE_MAX_SIZE (10 * 1024)
+
+/*
+ * Utility
+ */
+
+const char *sieve_error_script_location
+(const struct sieve_script *script, unsigned int source_line)
+{
+	const char *sname;
+
+	sname = ( script == NULL ? NULL : sieve_script_name(script) );
+
+	if ( sname == NULL || *sname == '\0' ) {
+		if ( source_line == 0 )
+			return NULL;
+
+		return t_strdup_printf("line %d", source_line);
+	}
+
+	if ( source_line == 0 )
+		return sname;
+
+	return t_strdup_printf("%s: line %d", sname, source_line);
+}
+
+/*
+ * Initialization
+ */
+
+void sieve_errors_init(struct sieve_instance *svinst)
+{
+	svinst->system_ehandler = sieve_master_ehandler_create(svinst, NULL, 0);
+}
+
+void sieve_errors_deinit(struct sieve_instance *svinst)
+{
+	sieve_error_handler_unref(&svinst->system_ehandler);
+}
+
+/*
+ * Direct handler calls
+ */
+
+void sieve_direct_verror
+(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+	unsigned int flags, const char *location, const char *fmt, va_list args)
+{
+	if ( (flags & SIEVE_ERROR_FLAG_GLOBAL) != 0 &&
+		(ehandler == NULL || ehandler->parent == NULL)) {
+		i_assert(svinst->system_ehandler != NULL);
+		if (svinst->system_ehandler != ehandler ||
+			(flags & SIEVE_ERROR_FLAG_GLOBAL_MAX_INFO) != 0) {
+			va_list args_copy;
+
+			VA_COPY(args_copy, args);
+
+			if ( (flags & SIEVE_ERROR_FLAG_GLOBAL_MAX_INFO) != 0 ) {
+				if (svinst->system_ehandler->vinfo != NULL ) {
+					svinst->system_ehandler->vinfo
+						(svinst->system_ehandler, 0, location, fmt, args_copy);
+				}
+			} else {
+				if ( svinst->system_ehandler->verror != NULL ) {
+					svinst->system_ehandler->verror
+						(svinst->system_ehandler, 0, location, fmt, args_copy);
+				}
+			}
+			va_end(args_copy);
+			if (svinst->system_ehandler == ehandler)
+				return;
+		}
+	}
+
+	if ( ehandler == NULL )
+		return;
+
+	if ( ehandler->parent != NULL || sieve_errors_more_allowed(ehandler) ) {
+		if ( ehandler->verror != NULL )
+			ehandler->verror(ehandler, flags, location, fmt, args);
+
+		if ( ehandler->pool != NULL )
+			ehandler->errors++;
+	}
+}
+
+void sieve_direct_vwarning
+(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+	unsigned int flags, const char *location, const char *fmt, va_list args)
+{
+	if ( (flags & SIEVE_ERROR_FLAG_GLOBAL) != 0 &&
+		(ehandler == NULL || ehandler->parent == NULL)) {
+		i_assert(svinst->system_ehandler != NULL);
+		if (svinst->system_ehandler != ehandler ||
+			(flags & SIEVE_ERROR_FLAG_GLOBAL_MAX_INFO) != 0) {
+			va_list args_copy;
+
+			VA_COPY(args_copy, args);
+
+			if ( (flags & SIEVE_ERROR_FLAG_GLOBAL_MAX_INFO) != 0 ) {
+				if (svinst->system_ehandler->vinfo != NULL ) {
+					svinst->system_ehandler->vinfo
+						(svinst->system_ehandler, 0, location, fmt, args_copy);
+				}
+			} else {
+				if ( svinst->system_ehandler->vwarning != NULL ) {
+					svinst->system_ehandler->vwarning
+						(svinst->system_ehandler, 0, location, fmt, args_copy);
+				}
+			}
+			va_end(args_copy);
+			if (svinst->system_ehandler == ehandler)
+				return;
+		}
+	}
+
+	if ( ehandler == NULL )
+		return;
+
+	if ( ehandler->vwarning != NULL )
+		ehandler->vwarning(ehandler, flags, location, fmt, args);
+
+	if ( ehandler->pool != NULL )
+		ehandler->warnings++;
+}
+
+void sieve_direct_vinfo
+(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+	unsigned int flags, const char *location, const char *fmt, va_list args)
+{
+	if ( (flags & SIEVE_ERROR_FLAG_GLOBAL) != 0 &&
+		(ehandler == NULL || ehandler->parent == NULL) &&
+		svinst->system_ehandler != ehandler) {
+		i_assert(svinst->system_ehandler != NULL);
+		if (svinst->system_ehandler->vinfo != NULL ) {
+			va_list args_copy;
+
+			VA_COPY(args_copy, args);
+			svinst->system_ehandler->vinfo
+				(svinst->system_ehandler, 0, location, fmt, args_copy);
+			va_end(args_copy);
+		}
+	}
+
+	if ( ehandler == NULL )
+		return;
+
+	if ( ehandler->parent != NULL || ehandler->log_info ) {
+		if ( ehandler->vinfo != NULL )
+			ehandler->vinfo(ehandler, flags, location, fmt, args);
+	}
+}
+
+void sieve_direct_vdebug
+(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+	unsigned int flags, const char *location, const char *fmt, va_list args)
+{
+	if ( (flags & SIEVE_ERROR_FLAG_GLOBAL) != 0 &&
+		(ehandler == NULL || ehandler->parent == NULL) &&
+		svinst->system_ehandler != ehandler) {
+		i_assert(svinst->system_ehandler != NULL);
+		if (svinst->system_ehandler->vdebug != NULL ) {
+			va_list args_copy;
+
+			VA_COPY(args_copy, args);
+			svinst->system_ehandler->vdebug
+				(svinst->system_ehandler, 0, location, fmt, args_copy);
+			va_end(args_copy);
+		}
+	}
+
+	if ( ehandler == NULL )
+		return;
+
+	if ( ehandler->parent != NULL || ehandler->log_debug ) {
+		if ( ehandler->vdebug != NULL )
+			ehandler->vdebug(ehandler, flags, location, fmt, args);
+	}
+}
+
+/*
+ * System errors
+ */
+
+void sieve_sys_verror
+(struct sieve_instance *svinst, const char *fmt, va_list args)
+{
+	T_BEGIN {
+		sieve_direct_verror(svinst, svinst->system_ehandler, 0, NULL, fmt, args);
+	} T_END;
+}
+
+void sieve_sys_vwarning
+(struct sieve_instance *svinst, const char *fmt, va_list args)
+{
+	T_BEGIN {
+		sieve_direct_vwarning(svinst, svinst->system_ehandler, 0, NULL, fmt, args);
+	} T_END;
+}
+
+void sieve_sys_vinfo
+(struct sieve_instance *svinst, const char *fmt, va_list args)
+{
+	T_BEGIN {
+		sieve_direct_vinfo(svinst, svinst->system_ehandler, 0, NULL, fmt, args);
+	} T_END;
+}
+
+void sieve_sys_vdebug
+(struct sieve_instance *svinst, const char *fmt, va_list args)
+{
+	T_BEGIN {
+		sieve_direct_vdebug(svinst, svinst->system_ehandler, 0, NULL, fmt, args);
+	} T_END;
+}
+
+void sieve_sys_error(struct sieve_instance *svinst, const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+
+	T_BEGIN {
+		sieve_direct_verror(svinst, svinst->system_ehandler, 0, NULL, fmt, args);
+	} T_END;
+
+	va_end(args);
+}
+
+void sieve_sys_warning(struct sieve_instance *svinst, const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+
+	T_BEGIN {
+		sieve_direct_vwarning(svinst, svinst->system_ehandler, 0, NULL, fmt, args);
+	} T_END;
+
+	va_end(args);
+}
+
+void sieve_sys_info(struct sieve_instance *svinst, const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+
+	T_BEGIN {
+		sieve_direct_vinfo(svinst, svinst->system_ehandler, 0, NULL, fmt, args);
+	} T_END;
+
+	va_end(args);
+}
+
+void sieve_sys_debug(struct sieve_instance *svinst, const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+
+	T_BEGIN {
+		sieve_direct_vdebug(svinst, svinst->system_ehandler, 0, NULL, fmt, args);
+	} T_END;
+
+	va_end(args);
+}
+
+void sieve_system_ehandler_set
+(struct sieve_error_handler *ehandler)
+{
+	struct sieve_instance *svinst = ehandler->svinst;
+
+	sieve_error_handler_unref(&svinst->system_ehandler);
+	svinst->system_ehandler = ehandler;
+	sieve_error_handler_ref(ehandler);
+}
+
+struct sieve_error_handler *sieve_system_ehandler_get
+(struct sieve_instance *svinst)
+{
+	return svinst->system_ehandler;
+}
+
+/*
+ * User errors
+ */
+
+void sieve_global_verror
+(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+	const char *location, const char *fmt, va_list args)
+{
+	sieve_direct_verror
+		(svinst, ehandler, SIEVE_ERROR_FLAG_GLOBAL, location, fmt, args);
+}
+
+void sieve_global_vwarning
+(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+	const char *location, const char *fmt, va_list args)
+{
+	sieve_direct_vwarning
+		(svinst, ehandler, SIEVE_ERROR_FLAG_GLOBAL, location, fmt, args);
+}
+
+void sieve_global_vinfo
+(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+	const char *location, const char *fmt, va_list args)
+{
+	sieve_direct_vinfo
+		(svinst, ehandler, SIEVE_ERROR_FLAG_GLOBAL, location, fmt, args);
+}
+
+void sieve_global_info_verror
+(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+	const char *location, const char *fmt, va_list args)
+{
+	sieve_direct_verror(svinst, ehandler,
+		(SIEVE_ERROR_FLAG_GLOBAL | SIEVE_ERROR_FLAG_GLOBAL_MAX_INFO),
+		location, fmt, args);
+}
+
+void sieve_global_info_vwarning
+(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+	const char *location, const char *fmt, va_list args)
+{
+	sieve_direct_vwarning(svinst, ehandler,
+		(SIEVE_ERROR_FLAG_GLOBAL | SIEVE_ERROR_FLAG_GLOBAL_MAX_INFO),
+		location, fmt, args);
+}
+
+void sieve_global_error
+(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+	const char *location, const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+
+	T_BEGIN {
+		sieve_global_verror(svinst, ehandler, location, fmt, args);
+	} T_END;
+
+	va_end(args);
+}
+
+void sieve_global_warning
+(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+	const char *location, const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+
+	T_BEGIN {
+		sieve_global_vwarning(svinst, ehandler, location, fmt, args);
+	} T_END;
+
+	va_end(args);
+}
+
+void sieve_global_info
+(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+	const char *location, const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+
+	T_BEGIN {
+		sieve_global_vinfo(svinst, ehandler, location, fmt, args);
+	} T_END;
+
+	va_end(args);
+}
+
+void sieve_global_info_error
+(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+	const char *location, const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+
+	T_BEGIN {
+		sieve_global_info_verror(svinst, ehandler, location, fmt, args);
+	} T_END;
+
+	va_end(args);
+}
+
+void sieve_global_info_warning
+(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+	const char *location, const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+
+	T_BEGIN {
+		sieve_global_info_vwarning(svinst, ehandler, location, fmt, args);
+	} T_END;
+
+	va_end(args);
+}
+
+/*
+ * Default (user) error functions
+ */
+
+void sieve_verror
+(struct sieve_error_handler *ehandler, const char *location,
+	const char *fmt, va_list args)
+{
+	if ( ehandler == NULL ) return;
+
+	sieve_direct_verror(ehandler->svinst, ehandler, 0, location, fmt, args);
+}
+
+void sieve_vwarning
+(struct sieve_error_handler *ehandler, const char *location,
+	const char *fmt, va_list args)
+{
+	if ( ehandler == NULL ) return;
+
+	sieve_direct_vwarning(ehandler->svinst, ehandler, 0, location, fmt, args);
+}
+
+void sieve_vinfo
+(struct sieve_error_handler *ehandler, const char *location,
+	const char *fmt, va_list args)
+{
+	if ( ehandler == NULL ) return;
+
+	sieve_direct_vinfo(ehandler->svinst, ehandler, 0, location, fmt, args);
+}
+
+void sieve_vdebug
+(struct sieve_error_handler *ehandler, const char *location,
+	const char *fmt, va_list args)
+{
+	if ( ehandler == NULL ) return;
+
+	sieve_direct_vdebug(ehandler->svinst, ehandler, 0, location, fmt, args);
+}
+
+void sieve_vcritical
+(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+	const char *location, const char *user_prefix, const char *fmt, va_list args)
+{
+	if ( location == NULL || *location == '\0' ) {
+		sieve_direct_verror
+			(svinst, svinst->system_ehandler, 0, NULL, fmt, args);
+	} else {
+		sieve_direct_verror
+			(svinst, svinst->system_ehandler, 0, location, fmt, args);
+	}
+
+	sieve_internal_error(ehandler, location, user_prefix);
+}
+
+void sieve_error
+(struct sieve_error_handler *ehandler, const char *location,
+	const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+
+	T_BEGIN { sieve_verror(ehandler, location, fmt, args); } T_END;
+
+	va_end(args);
+}
+
+void sieve_warning
+(struct sieve_error_handler *ehandler, const char *location,
+	const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+
+	T_BEGIN { sieve_vwarning(ehandler, location, fmt, args); } T_END;
+
+	va_end(args);
+}
+
+void sieve_info
+(struct sieve_error_handler *ehandler, const char *location,
+	const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+
+	T_BEGIN { sieve_vinfo(ehandler, location, fmt, args); } T_END;
+
+	va_end(args);
+}
+
+void sieve_debug
+(struct sieve_error_handler *ehandler, const char *location,
+	const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+
+	T_BEGIN { sieve_vdebug(ehandler, location, fmt, args); } T_END;
+
+	va_end(args);
+}
+
+void sieve_critical
+(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+	const char *location, const char *user_prefix, const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+
+	T_BEGIN {
+		sieve_vcritical(svinst, ehandler, location, user_prefix, fmt, args);
+	} T_END;
+
+	va_end(args);
+}
+
+void sieve_internal_error
+(struct sieve_error_handler *ehandler, const char *location,
+	const char *user_prefix)
+{
+	char str[256];
+	struct tm *tm;
+
+	if ( ehandler == NULL ||
+		ehandler == ehandler->svinst->system_ehandler )
+		return;
+
+	tm = localtime(&ioloop_time);
+
+	if ( user_prefix == NULL || *user_prefix == '\0' ) {
+		sieve_direct_error(ehandler->svinst, ehandler, 0,
+			location, "%s",
+			( strftime(str, sizeof(str), CRITICAL_MSG_STAMP, tm) > 0 ?
+				str : CRITICAL_MSG ));
+	} else {
+		sieve_direct_error(ehandler->svinst, ehandler, 0,
+			location, "%s: %s", user_prefix,
+			( strftime(str, sizeof(str), CRITICAL_MSG_STAMP, tm) > 0 ?
+				str : CRITICAL_MSG ));
+	}
+}
+
+/*
+ * Error statistics
+ */
+
+unsigned int sieve_get_errors(struct sieve_error_handler *ehandler)
+{
+	if ( ehandler == NULL || ehandler->pool == NULL ) return 0;
+
+	return ehandler->errors;
+}
+
+unsigned int sieve_get_warnings(struct sieve_error_handler *ehandler)
+{
+	if ( ehandler == NULL || ehandler->pool == NULL ) return 0;
+
+	return ehandler->warnings;
+}
+
+bool sieve_errors_more_allowed(struct sieve_error_handler *ehandler)
+{
+	if ( ehandler == NULL || ehandler->pool == NULL )
+		return TRUE;
+
+	return ehandler->max_errors == 0 || ehandler->errors < ehandler->max_errors;
+}
+
+/*
+ * Error handler configuration
+ */
+
+void sieve_error_handler_accept_infolog
+(struct sieve_error_handler *ehandler, bool enable)
+{
+	while ( ehandler != NULL ) {
+		ehandler->log_info = enable;
+		ehandler = ehandler->parent;
+	}
+}
+
+void sieve_error_handler_accept_debuglog
+(struct sieve_error_handler *ehandler, bool enable)
+{
+	while ( ehandler != NULL ) {
+		ehandler->log_debug = enable;
+		ehandler = ehandler->parent;
+	}
+}
+
+/*
+ * Error handler init
+ */
+
+void sieve_error_handler_init
+(struct sieve_error_handler *ehandler, struct sieve_instance *svinst,
+	pool_t pool, unsigned int max_errors)
+{
+	ehandler->pool = pool;
+	ehandler->svinst = svinst;
+	ehandler->refcount = 1;
+	ehandler->max_errors = max_errors;
+
+	ehandler->errors = 0;
+	ehandler->warnings = 0;
+}
+
+void sieve_error_handler_init_from_parent
+(struct sieve_error_handler *ehandler, pool_t pool, struct sieve_error_handler *parent)
+{
+	i_assert( parent != NULL );
+
+	sieve_error_handler_init(ehandler, parent->svinst, pool, parent->max_errors);
+
+	ehandler->parent = parent;
+	sieve_error_handler_ref(parent);
+
+	ehandler->log_info = parent->log_info;
+	ehandler->log_debug = parent->log_debug;
+}
+
+void sieve_error_handler_ref(struct sieve_error_handler *ehandler)
+{
+	if ( ehandler == NULL || ehandler->pool == NULL ) return;
+
+	ehandler->refcount++;
+}
+
+void sieve_error_handler_unref(struct sieve_error_handler **ehandler)
+{
+	if ( *ehandler == NULL || (*ehandler)->pool == NULL ) return;
+
+	i_assert((*ehandler)->refcount > 0);
+
+	if (--(*ehandler)->refcount != 0)
+        	return;
+
+	if ( (*ehandler)->parent != NULL )
+		sieve_error_handler_unref(&(*ehandler)->parent);
+
+	if ( (*ehandler)->free != NULL )
+		(*ehandler)->free(*ehandler);
+
+	pool_unref(&((*ehandler)->pool));
+
+	*ehandler = NULL;
+}
+
+void sieve_error_handler_reset(struct sieve_error_handler *ehandler)
+{
+	if ( ehandler == NULL || ehandler->pool == NULL ) return;
+
+	ehandler->errors = 0;
+	ehandler->warnings = 0;
+}
+
+/*
+ * Master/System error handler
+ *
+ * - Output errors directly to Dovecot master log
+ */
+
+struct sieve_master_ehandler {
+	struct sieve_error_handler handler;
+
+	const char *prefix;
+};
+
+typedef void (*master_log_func_t)(const char *fmt, ...) ATTR_FORMAT(1, 2);
+
+static void ATTR_FORMAT(4, 0) sieve_master_vlog
+(struct sieve_error_handler *_ehandler, master_log_func_t log_func,
+	const char *location, const char *fmt, va_list args)
+{
+	struct sieve_master_ehandler *ehandler =
+		(struct sieve_master_ehandler *) _ehandler;
+	string_t *str;
+
+	str = t_str_new(256);
+	if ( ehandler->prefix != NULL)
+		str_printfa(str, "%s: ", ehandler->prefix);
+
+	str_append(str, "sieve: ");
+
+	if ( location != NULL && *location != '\0' )
+		str_printfa(str, "%s: ", location);
+
+	str_vprintfa(str, fmt, args);
+
+	log_func("%s", str_c(str));
+}
+
+static void ATTR_FORMAT(4, 0) sieve_master_verror
+(struct sieve_error_handler *ehandler,
+	unsigned int flags ATTR_UNUSED, const char *location, const char *fmt,
+	va_list args)
+{
+	sieve_master_vlog(ehandler, i_error, location, fmt, args);
+}
+
+static void ATTR_FORMAT(4, 0) sieve_master_vwarning
+(struct sieve_error_handler *ehandler ATTR_UNUSED,
+	unsigned int flags ATTR_UNUSED, const char *location, const char *fmt,
+	va_list args)
+{
+	sieve_master_vlog(ehandler, i_warning, location, fmt, args);
+}
+
+static void ATTR_FORMAT(4, 0) sieve_master_vinfo
+(struct sieve_error_handler *ehandler ATTR_UNUSED,
+	unsigned int flags ATTR_UNUSED, const char *location, const char *fmt,
+	va_list args)
+{
+	sieve_master_vlog(ehandler, i_info, location, fmt, args);
+}
+
+static void ATTR_FORMAT(4, 0) sieve_master_vdebug
+(struct sieve_error_handler *ehandler ATTR_UNUSED,
+	unsigned int flags ATTR_UNUSED, const char *location, const char *fmt,
+	va_list args)
+{
+	sieve_master_vlog(ehandler, i_debug, location, fmt, args);
+}
+
+struct sieve_error_handler *sieve_master_ehandler_create
+(struct sieve_instance *svinst, const char *prefix, unsigned int max_errors)
+{
+	pool_t pool;
+	struct sieve_master_ehandler *ehandler;
+
+	pool = pool_alloconly_create("master_error_handler", 256);
+	ehandler = p_new(pool, struct sieve_master_ehandler, 1);
+	sieve_error_handler_init(&ehandler->handler, svinst, pool, max_errors);
+
+	ehandler->handler.verror = sieve_master_verror;
+	ehandler->handler.vwarning = sieve_master_vwarning;
+	ehandler->handler.vinfo = sieve_master_vinfo;
+	ehandler->handler.vdebug = sieve_master_vdebug;
+
+	if ( prefix != NULL )
+		ehandler->prefix = p_strdup(pool, prefix);
+
+	ehandler->handler.log_debug = svinst->debug;
+
+	return &ehandler->handler;
+}
+
+/*
+ * STDERR error handler
+ *
+ * - Output errors directly to stderror
+ */
+
+static void ATTR_FORMAT(4, 0) sieve_stderr_vmessage
+(struct sieve_error_handler *ehandler ATTR_UNUSED, const char *prefix,
+	const char *location, const char *fmt, va_list args)
+{
+	if ( location == NULL || *location == '\0' )
+		fprintf(stderr, "%s: %s.\n", prefix, t_strdup_vprintf(fmt, args));
+	else
+		fprintf(stderr, "%s: %s: %s.\n", location, prefix, t_strdup_vprintf(fmt, args));
+}
+
+static void ATTR_FORMAT(4, 0) sieve_stderr_verror
+(struct sieve_error_handler *ehandler, unsigned int flags ATTR_UNUSED,
+	const char *location, const char *fmt, va_list args)
+{
+	sieve_stderr_vmessage(ehandler, "error", location, fmt, args);
+}
+
+static void ATTR_FORMAT(4, 0) sieve_stderr_vwarning
+(struct sieve_error_handler *ehandler, unsigned int flags ATTR_UNUSED,
+	const char *location, const char *fmt, va_list args)
+{
+	sieve_stderr_vmessage(ehandler, "warning", location, fmt, args);
+}
+
+static void ATTR_FORMAT(4, 0) sieve_stderr_vinfo
+(struct sieve_error_handler *ehandler, unsigned int flags ATTR_UNUSED,
+	const char *location, const char *fmt, va_list args)
+{
+	sieve_stderr_vmessage(ehandler, "info", location, fmt, args);
+}
+
+static void ATTR_FORMAT(4, 0) sieve_stderr_vdebug
+(struct sieve_error_handler *ehandler, unsigned int flags ATTR_UNUSED,
+	const char *location, const char *fmt, va_list args)
+{
+	sieve_stderr_vmessage(ehandler, "debug", location, fmt, args);
+}
+
+struct sieve_error_handler *sieve_stderr_ehandler_create
+(struct sieve_instance *svinst, unsigned int max_errors)
+{
+	pool_t pool;
+	struct sieve_error_handler *ehandler;
+
+	/* Pool is not strictly necessary, but other handler types will need
+	 * a pool, so this one will have one too.
+	 */
+	pool = pool_alloconly_create
+		("stderr_error_handler", sizeof(struct sieve_error_handler));
+	ehandler = p_new(pool, struct sieve_error_handler, 1);
+	sieve_error_handler_init(ehandler, svinst, pool, max_errors);
+
+	ehandler->verror = sieve_stderr_verror;
+	ehandler->vwarning = sieve_stderr_vwarning;
+	ehandler->vinfo = sieve_stderr_vinfo;
+	ehandler->vdebug = sieve_stderr_vdebug;
+
+	return ehandler;
+}
+
+/* String buffer error handler
+ *
+ * - Output errors to a string buffer
+ */
+
+struct sieve_strbuf_ehandler {
+	struct sieve_error_handler handler;
+
+	string_t *errors;
+	bool crlf;
+};
+
+static void ATTR_FORMAT(4, 0) sieve_strbuf_vmessage
+(struct sieve_error_handler *ehandler, const char *prefix,
+	const char *location, const char *fmt, va_list args)
+{
+	struct sieve_strbuf_ehandler *handler =
+		(struct sieve_strbuf_ehandler *) ehandler;
+
+	if ( location != NULL && *location != '\0' )
+		str_printfa(handler->errors, "%s: ", location);
+	str_printfa(handler->errors, "%s: ", prefix);
+	str_vprintfa(handler->errors, fmt, args);
+
+	if ( !handler->crlf )
+		str_append(handler->errors, ".\n");
+	else
+		str_append(handler->errors, ".\r\n");
+}
+
+static void ATTR_FORMAT(4, 0) sieve_strbuf_verror
+(struct sieve_error_handler *ehandler, unsigned int flags ATTR_UNUSED,
+	const char *location, const char *fmt, va_list args)
+{
+	sieve_strbuf_vmessage(ehandler, "error", location, fmt, args);
+}
+
+static void ATTR_FORMAT(4, 0) sieve_strbuf_vwarning
+(struct sieve_error_handler *ehandler, unsigned int flags ATTR_UNUSED,
+	const char *location, const char *fmt, va_list args)
+{
+	sieve_strbuf_vmessage(ehandler, "warning", location, fmt, args);
+}
+
+static void ATTR_FORMAT(4, 0) sieve_strbuf_vinfo
+(struct sieve_error_handler *ehandler, unsigned int flags ATTR_UNUSED,
+	const char *location, const char *fmt, va_list args)
+{
+	sieve_strbuf_vmessage(ehandler, "info", location, fmt, args);
+}
+
+static void ATTR_FORMAT(4, 0) sieve_strbuf_vdebug
+(struct sieve_error_handler *ehandler, unsigned int flags ATTR_UNUSED,
+	const char *location, const char *fmt, va_list args)
+{
+	sieve_strbuf_vmessage(ehandler, "debug", location, fmt, args);
+}
+
+struct sieve_error_handler *sieve_strbuf_ehandler_create
+(struct sieve_instance *svinst, string_t *strbuf, bool crlf,
+	unsigned int max_errors)
+{
+	pool_t pool;
+	struct sieve_strbuf_ehandler *ehandler;
+
+	pool = pool_alloconly_create("strbuf_error_handler", 256);
+	ehandler = p_new(pool, struct sieve_strbuf_ehandler, 1);
+	ehandler->errors = strbuf;
+
+	sieve_error_handler_init(&ehandler->handler, svinst, pool, max_errors);
+
+	ehandler->handler.verror = sieve_strbuf_verror;
+	ehandler->handler.vwarning = sieve_strbuf_vwarning;
+	ehandler->handler.vinfo = sieve_strbuf_vinfo;
+	ehandler->handler.vdebug = sieve_strbuf_vdebug;
+
+	ehandler->crlf = crlf;
+
+	return &(ehandler->handler);
+}
+
+/*
+ * Logfile error handler
+ *
+ * - Output errors to a log file
+ */
+
+struct sieve_logfile_ehandler {
+	struct sieve_error_handler handler;
+
+	const char *logfile;
+	bool started;
+	int fd;
+	struct ostream *stream;
+};
+
+static void ATTR_FORMAT(4, 0) sieve_logfile_vprintf
+(struct sieve_logfile_ehandler *ehandler, const char *location,
+	const char *prefix, const char *fmt, va_list args)
+{
+	string_t *outbuf;
+	ssize_t ret = 0, remain;
+	const char *data;
+
+	if ( ehandler->stream == NULL ) return;
+
+	T_BEGIN {
+		outbuf = t_str_new(256);
+		if ( location != NULL && *location != '\0' )
+			str_printfa(outbuf, "%s: ", location);
+		str_printfa(outbuf, "%s: ", prefix);
+		str_vprintfa(outbuf, fmt, args);
+		str_append(outbuf, ".\n");
+
+		remain = str_len(outbuf);
+		data = (const char *) str_data(outbuf);
+
+		while ( remain > 0 ) {
+			if ( (ret=o_stream_send(ehandler->stream, data, remain)) < 0 )
+				break;
+
+			remain -= ret;
+			data += ret;
+		}
+	} T_END;
+
+	if ( ret < 0 ) {
+		sieve_sys_error(ehandler->handler.svinst,
+			"o_stream_send() failed on logfile %s: %m", ehandler->logfile);
+	}
+}
+
+inline static void ATTR_FORMAT(4, 5) sieve_logfile_printf
+(struct sieve_logfile_ehandler *ehandler, const char *location,
+	const char *prefix, const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+
+	sieve_logfile_vprintf(ehandler, location, prefix, fmt, args);
+
+	va_end(args);
+}
+
+static void sieve_logfile_start(struct sieve_logfile_ehandler *ehandler)
+{
+	struct sieve_instance *svinst = ehandler->handler.svinst;
+	struct ostream *ostream = NULL;
+	struct stat st;
+	struct tm *tm;
+	char buf[256];
+	time_t now;
+	int fd;
+
+	/* Open the logfile */
+
+	fd = open(ehandler->logfile, O_CREAT | O_APPEND | O_WRONLY, 0600);
+	if (fd == -1) {
+		if ( errno == EACCES ) {
+			sieve_sys_error(svinst, "failed to open logfile (LOGGING TO STDERR): %s",
+				eacces_error_get_creating("open", ehandler->logfile));
+		} else {
+			sieve_sys_error(svinst, "failed to open logfile (LOGGING TO STDERR): "
+				"open(%s) failed: %m", ehandler->logfile);
+		}
+		fd = STDERR_FILENO;
+	} else {
+		/* fd_close_on_exec(fd, TRUE); Necessary? */
+
+		/* Stat the log file to obtain size information */
+		if ( fstat(fd, &st) != 0 ) {
+			sieve_sys_error(svinst, "failed to stat logfile (logging to STDERR): "
+				"fstat(fd=%s) failed: %m", ehandler->logfile);
+
+			if ( close(fd) < 0 ) {
+				sieve_sys_error(svinst, "failed to close logfile after error: "
+					"close(fd=%s) failed: %m", ehandler->logfile);
+			}
+
+			fd = STDERR_FILENO;
+		}
+
+		/* Rotate log when it has grown too large */
+		if ( st.st_size >= LOGFILE_MAX_SIZE ) {
+			const char *rotated;
+
+			/* Close open file */
+			if ( close(fd) < 0 ) {
+				sieve_sys_error(svinst,
+					"failed to close logfile: close(fd=%s) failed: %m", ehandler->logfile);
+			}
+
+			/* Rotate logfile */
+			rotated = t_strconcat(ehandler->logfile, ".0", NULL);
+			if ( rename(ehandler->logfile, rotated) < 0 && errno != ENOENT ) {
+				if ( errno == EACCES ) {
+					sieve_sys_error(svinst,
+						"failed to rotate logfile: %s",
+						eacces_error_get_creating("rename",
+							t_strconcat(ehandler->logfile, ", ", rotated, NULL)));
+				} else {
+					sieve_sys_error(svinst,
+						"failed to rotate logfile: rename(%s, %s) failed: %m",
+						ehandler->logfile, rotated);
+				}
+			}
+
+			/* Open clean logfile (overwrites existing if rename() failed earlier) */
+			fd = open(ehandler->logfile,
+				O_CREAT | O_APPEND | O_WRONLY | O_TRUNC, 0600);
+			if (fd == -1) {
+				if ( errno == EACCES ) {
+					sieve_sys_error(svinst,
+						"failed to open logfile (LOGGING TO STDERR): %s",
+						eacces_error_get_creating("open", ehandler->logfile));
+				} else {
+					sieve_sys_error(svinst,
+						"failed to open logfile (LOGGING TO STDERR): open(%s) failed: %m",
+						ehandler->logfile);
+				}
+				fd = STDERR_FILENO;
+			}
+		}
+	}
+
+	ostream = o_stream_create_fd(fd, 0);
+	if ( ostream == NULL ) {
+		/* Can't we do anything else in this most awkward situation? */
+		sieve_sys_error(svinst, "failed to open log stream on open file: "
+			"o_stream_create_fd(fd=%s) failed "
+			"(non-critical messages are not logged!)", ehandler->logfile);
+	}
+
+	ehandler->fd = fd;
+	ehandler->stream = ostream;
+	ehandler->started = TRUE;
+
+	if ( ostream != NULL ) {
+		now = time(NULL);
+		tm = localtime(&now);
+
+		if (strftime(buf, sizeof(buf), "%b %d %H:%M:%S", tm) > 0) {
+			sieve_logfile_printf(ehandler, "sieve", "info",
+				"started log at %s", buf);
+		}
+	}
+}
+
+static void ATTR_FORMAT(4, 0) sieve_logfile_verror
+(struct sieve_error_handler *ehandler, unsigned int flags ATTR_UNUSED,
+	const char *location, const char *fmt, va_list args)
+{
+	struct sieve_logfile_ehandler *handler =
+		(struct sieve_logfile_ehandler *) ehandler;
+
+	if ( !handler->started ) sieve_logfile_start(handler);
+
+	sieve_logfile_vprintf(handler, location, "error", fmt, args);
+}
+
+static void ATTR_FORMAT(4, 0) sieve_logfile_vwarning
+(struct sieve_error_handler *ehandler, unsigned int flags ATTR_UNUSED,
+	const char *location, const char *fmt, va_list args)
+{
+	struct sieve_logfile_ehandler *handler =
+		(struct sieve_logfile_ehandler *) ehandler;
+
+	if ( !handler->started ) sieve_logfile_start(handler);
+
+	sieve_logfile_vprintf(handler, location, "warning", fmt, args);
+}
+
+static void ATTR_FORMAT(4, 0) sieve_logfile_vinfo
+(struct sieve_error_handler *ehandler, unsigned int flags ATTR_UNUSED,
+	const char *location, const char *fmt, va_list args)
+{
+	struct sieve_logfile_ehandler *handler =
+		(struct sieve_logfile_ehandler *) ehandler;
+
+	if ( !handler->started ) sieve_logfile_start(handler);
+
+	sieve_logfile_vprintf(handler, location, "info", fmt, args);
+}
+
+static void ATTR_FORMAT(4, 0) sieve_logfile_vdebug
+(struct sieve_error_handler *ehandler, unsigned int flags ATTR_UNUSED,
+	const char *location, const char *fmt, va_list args)
+{
+	struct sieve_logfile_ehandler *handler =
+		(struct sieve_logfile_ehandler *) ehandler;
+
+	if ( !handler->started ) sieve_logfile_start(handler);
+
+	sieve_logfile_vprintf(handler, location, "debug", fmt, args);
+}
+
+static void sieve_logfile_free
+(struct sieve_error_handler *ehandler)
+{
+	struct sieve_logfile_ehandler *handler =
+		(struct sieve_logfile_ehandler *) ehandler;
+
+	if ( handler->stream != NULL ) {
+		o_stream_destroy(&(handler->stream));
+		if ( handler->fd != STDERR_FILENO ){
+			if ( close(handler->fd) < 0 ) {
+				sieve_sys_error(ehandler->svinst, "failed to close logfile: "
+					"close(fd=%s) failed: %m", handler->logfile);
+			}
+		}
+	}
+}
+
+struct sieve_error_handler *sieve_logfile_ehandler_create
+(struct sieve_instance *svinst, const char *logfile, unsigned int max_errors)
+{
+	pool_t pool;
+	struct sieve_logfile_ehandler *ehandler;
+
+	pool = pool_alloconly_create("logfile_error_handler", 512);
+	ehandler = p_new(pool, struct sieve_logfile_ehandler, 1);
+	sieve_error_handler_init(&ehandler->handler, svinst, pool, max_errors);
+
+	ehandler->handler.verror = sieve_logfile_verror;
+	ehandler->handler.vwarning = sieve_logfile_vwarning;
+	ehandler->handler.vinfo = sieve_logfile_vinfo;
+	ehandler->handler.vdebug = sieve_logfile_vdebug;
+	ehandler->handler.free = sieve_logfile_free;
+
+	/* Don't open logfile until something is actually logged.
+	 * Let's not pullute the sieve directory with useless logfiles.
+	 */
+	ehandler->logfile = p_strdup(pool, logfile);
+	ehandler->started = FALSE;
+	ehandler->stream = NULL;
+	ehandler->fd = -1;
+
+	return &(ehandler->handler);
+}
+
+/*
+ * Prefix error handler
+ *
+ *   Encapsulates an existing error handler and prefixes all messages with
+ *   the given prefix.
+ */
+
+struct sieve_prefix_ehandler {
+	struct sieve_error_handler handler;
+
+	struct sieve_error_handler *parent;
+
+	const char *location;
+	const char *prefix;
+};
+
+static const char *ATTR_FORMAT(3, 0) _prefix_message
+(struct sieve_prefix_ehandler *ehandler,
+	const char *location, const char *fmt, va_list args)
+{
+	string_t *str = t_str_new(256);
+
+	if ( ehandler->prefix != NULL)
+		str_printfa(str, "%s: ", ehandler->prefix);
+	if ( location != NULL)
+		str_printfa(str, "%s: ", location);
+	str_vprintfa(str, fmt, args);
+
+	return str_c(str);
+}
+
+static void ATTR_FORMAT(4, 0) sieve_prefix_verror
+(struct sieve_error_handler *_ehandler, unsigned int flags,
+	const char *location, const char *fmt, va_list args)
+{
+	struct sieve_prefix_ehandler *ehandler =
+		(struct sieve_prefix_ehandler *) _ehandler;
+
+	sieve_direct_error(_ehandler->svinst, _ehandler->parent, flags,
+		ehandler->location, "%s", _prefix_message(ehandler, location, fmt, args));
+}
+
+static void ATTR_FORMAT(4, 0) sieve_prefix_vwarning
+(struct sieve_error_handler *_ehandler, unsigned int flags,
+	const char *location, const char *fmt, va_list args)
+{
+	struct sieve_prefix_ehandler *ehandler =
+		(struct sieve_prefix_ehandler *) _ehandler;
+
+	sieve_direct_warning(_ehandler->svinst, _ehandler->parent, flags,
+		ehandler->location, "%s", _prefix_message(ehandler, location, fmt, args));
+}
+
+static void ATTR_FORMAT(4, 0) sieve_prefix_vinfo
+(struct sieve_error_handler *_ehandler, unsigned int flags,
+	const char *location, const char *fmt, va_list args)
+{
+	struct sieve_prefix_ehandler *ehandler =
+		(struct sieve_prefix_ehandler *) _ehandler;
+
+	sieve_direct_info(_ehandler->svinst, _ehandler->parent, flags,
+		ehandler->location, "%s", _prefix_message(ehandler, location, fmt, args));
+}
+
+static void ATTR_FORMAT(4, 0) sieve_prefix_vdebug
+(struct sieve_error_handler *_ehandler, unsigned int flags,
+	const char *location, const char *fmt, va_list args)
+{
+	struct sieve_prefix_ehandler *ehandler =
+		(struct sieve_prefix_ehandler *) _ehandler;
+
+	sieve_direct_debug(_ehandler->svinst, _ehandler->parent, flags,
+		ehandler->location, "%s", _prefix_message(ehandler, location, fmt, args));
+}
+
+struct sieve_error_handler *sieve_prefix_ehandler_create
+(struct sieve_error_handler *parent, const char *location, const char *prefix)
+{
+	pool_t pool;
+	struct sieve_prefix_ehandler *ehandler;
+
+	if ( parent == NULL )
+		return NULL;
+
+	pool = pool_alloconly_create("sieve_prefix_error_handler", 512);
+	ehandler = p_new(pool, struct sieve_prefix_ehandler, 1);
+	sieve_error_handler_init_from_parent(&ehandler->handler, pool, parent);
+
+	ehandler->location = p_strdup(pool, location);
+	ehandler->prefix = p_strdup(pool, prefix);
+
+	ehandler->handler.verror = sieve_prefix_verror;
+	ehandler->handler.vwarning = sieve_prefix_vwarning;
+	ehandler->handler.vinfo = sieve_prefix_vinfo;
+	ehandler->handler.vdebug = sieve_prefix_vdebug;
+
+	return &(ehandler->handler);
+}
+
+/*
+ * Varexpand error handler
+ *
+ *   Encapsulates an existing error handler and formats all messages using the
+ *   provided format string and variables;
+ */
+
+struct sieve_varexpand_ehandler {
+	struct sieve_error_handler handler;
+
+	const char *format;
+	ARRAY(struct var_expand_table) table;
+};
+
+static const char *ATTR_FORMAT(3, 0) _expand_message
+(struct sieve_error_handler *_ehandler,
+	const char *location, const char *fmt, va_list args)
+{
+	struct sieve_varexpand_ehandler *ehandler =
+		(struct sieve_varexpand_ehandler *) _ehandler;
+	unsigned int count;
+	struct var_expand_table *table =
+		array_get_modifiable(&ehandler->table, &count);
+	string_t *str = t_str_new(256);
+	const char *error;
+	static bool expand_error_logged = FALSE;
+
+	/* Fill in substitution items */
+	table[0].value = t_strdup_vprintf(fmt, args);
+	table[1].value = location;
+
+	/* Expand variables */
+	if (var_expand(str, ehandler->format, table, &error) <= 0 &&
+	    !expand_error_logged) {
+		/* Log the error only once. This also prevents recursively
+		   looping back here. */
+		expand_error_logged = TRUE;
+		sieve_sys_error(_ehandler->svinst,
+			"Failed to expand error message: %s", error);
+	}
+
+	return str_c(str);
+}
+
+static void ATTR_FORMAT(4, 0) sieve_varexpand_verror
+(struct sieve_error_handler *ehandler, unsigned int flags,
+	const char *location, const char *fmt, va_list args)
+{
+	sieve_direct_error(ehandler->svinst, ehandler->parent, flags, location,
+		"%s", _expand_message(ehandler, location, fmt, args));
+}
+
+static void ATTR_FORMAT(4, 0) sieve_varexpand_vwarning
+(struct sieve_error_handler *ehandler, unsigned int flags,
+	const char *location, const char *fmt, va_list args)
+{
+	sieve_direct_warning(ehandler->svinst, ehandler->parent, flags, location,
+		"%s", _expand_message(ehandler, location, fmt, args));
+}
+
+static void ATTR_FORMAT(4, 0) sieve_varexpand_vinfo
+(struct sieve_error_handler *ehandler, unsigned int flags,
+	const char *location, const char *fmt, va_list args)
+{
+	sieve_direct_info(ehandler->svinst, ehandler->parent, flags, location,
+		"%s", _expand_message(ehandler, location, fmt, args));
+}
+
+static void ATTR_FORMAT(4, 0) sieve_varexpand_vdebug
+(struct sieve_error_handler *ehandler, unsigned int flags,
+	const char *location, const char *fmt, va_list args)
+{
+	sieve_direct_debug(ehandler->svinst, ehandler->parent, flags, location,
+		"%s", _expand_message(ehandler, location, fmt, args));
+}
+
+struct sieve_error_handler *sieve_varexpand_ehandler_create
+(struct sieve_error_handler *parent, const char *format,
+	const struct var_expand_table *table)
+{
+	pool_t pool;
+	struct sieve_varexpand_ehandler *ehandler;
+	struct var_expand_table *entry;
+	int i;
+
+	if ( parent == NULL )
+		return NULL;
+
+	if ( format == NULL ) {
+		sieve_error_handler_ref(parent);
+		return parent;
+	}
+
+	pool = pool_alloconly_create("sieve_varexpand_error_handler", 2048);
+	ehandler = p_new(pool, struct sieve_varexpand_ehandler, 1);
+	sieve_error_handler_init_from_parent(&ehandler->handler, pool, parent);
+
+	ehandler->format = format;
+	p_array_init(&ehandler->table, pool, 10);
+
+	entry = array_append_space(&ehandler->table);
+	entry->key = '$';
+	entry = array_append_space(&ehandler->table);
+	entry->key = 'l';
+	entry->long_key = "location";
+
+	for (i = 0; table[i].key != '\0'; i++) {
+		entry = array_append_space(&ehandler->table);
+
+		/* Sanitize substitution items */
+		entry->key = table[i].key;
+
+		if ( table[i].value != NULL )
+			entry->value = p_strdup(pool, table[i].value);
+		if ( table[i].long_key != NULL )
+			entry->long_key = p_strdup(pool, table[i].long_key);
+	}
+
+	(void)array_append_space(&ehandler->table);
+
+	ehandler->handler.verror = sieve_varexpand_verror;
+	ehandler->handler.vwarning = sieve_varexpand_vwarning;
+	ehandler->handler.vinfo = sieve_varexpand_vinfo;
+	ehandler->handler.vdebug = sieve_varexpand_vdebug;
+
+	return &(ehandler->handler);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-error.h
@@ -0,0 +1,205 @@
+#ifndef SIEVE_ERROR_H
+#define SIEVE_ERROR_H
+
+#include "lib.h"
+#include "compat.h"
+
+#include <stdarg.h>
+
+/*
+ * Forward declarations
+ */
+
+struct var_expand_table;
+
+struct sieve_instance;
+struct sieve_script;
+struct sieve_error_handler;
+
+/*
+ * Types
+ */
+
+typedef void (*sieve_error_vfunc_t)
+	(struct sieve_error_handler *ehandler, const char *location,
+		const char *fmt, va_list args) ATTR_FORMAT(3, 0);;
+typedef void (*sieve_error_func_t)
+	(struct sieve_error_handler *ehandler, const char *location,
+		const char *fmt, ...) ATTR_FORMAT(3, 4);
+
+typedef void (*sieve_sys_error_vfunc_t)
+	(struct sieve_instance *svinst, const char *fmt, va_list args) ATTR_FORMAT(2, 0);;
+typedef void (*sieve_sys_error_func_t)
+	(struct sieve_instance *svinst, const char *fmt, ...) ATTR_FORMAT(2, 3);
+
+/*
+ * System errors
+ */
+
+void sieve_sys_verror
+	(struct sieve_instance *svinst, const char *fmt, va_list args) ATTR_FORMAT(2, 0);;
+void sieve_sys_vwarning
+	(struct sieve_instance *svinst, const char *fmt, va_list args) ATTR_FORMAT(2, 0);;
+void sieve_sys_vinfo
+	(struct sieve_instance *svinst, const char *fmt, va_list args) ATTR_FORMAT(2, 0);;
+void sieve_sys_vdebug
+	(struct sieve_instance *svinst, const char *fmt, va_list args) ATTR_FORMAT(2, 0);;
+
+void sieve_sys_error
+	(struct sieve_instance *svinst, const char *fmt, ...) ATTR_FORMAT(2, 3);
+void sieve_sys_warning
+	(struct sieve_instance *svinst, const char *fmt, ...) ATTR_FORMAT(2, 3);
+void sieve_sys_info
+	(struct sieve_instance *svinst, const char *fmt, ...) ATTR_FORMAT(2, 3);
+void sieve_sys_debug
+	(struct sieve_instance *svinst, const char *fmt, ...) ATTR_FORMAT(2, 3);
+
+void sieve_system_ehandler_set
+	(struct sieve_error_handler *ehandler);
+struct sieve_error_handler *sieve_system_ehandler_get
+	(struct sieve_instance *svinst);
+
+/*
+ * Global (user+system) errors
+ */
+
+void sieve_global_verror
+	(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+		const char *location, const char *fmt, va_list args) ATTR_FORMAT(4, 0);
+void sieve_global_vwarning
+	(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+		const char *location, const char *fmt, va_list args) ATTR_FORMAT(4, 0);
+void sieve_global_vinfo
+	(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+		const char *location, const char *fmt, va_list args) ATTR_FORMAT(4, 0);
+void sieve_global_info_verror
+	(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+		const char *location, const char *fmt, va_list args) ATTR_FORMAT(4, 0);
+void sieve_global_info_vwarning
+	(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+		const char *location, const char *fmt, va_list args) ATTR_FORMAT(4, 0);
+
+void sieve_global_error
+	(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+		const char *location, const char *fmt, ...) ATTR_FORMAT(4, 5);
+void sieve_global_warning
+	(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+		const char *location, const char *fmt, ...) ATTR_FORMAT(4, 5);
+void sieve_global_info
+	(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+		const char *location, const char *fmt, ...) ATTR_FORMAT(4, 5);
+void sieve_global_info_error
+	(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+		const char *location, const char *fmt, ...) ATTR_FORMAT(4, 5);
+void sieve_global_info_warning
+	(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+		const char *location, const char *fmt, ...) ATTR_FORMAT(4, 5);
+
+/*
+ * Main (user) error functions
+ */
+
+/* For these functions it is the responsibility of the caller to
+ * manage the datastack.
+ */
+
+const char *sieve_error_script_location
+	(const struct sieve_script *script, unsigned int source_line);
+
+void sieve_verror
+	(struct sieve_error_handler *ehandler, const char *location,
+		const char *fmt, va_list args) ATTR_FORMAT(3, 0);
+void sieve_vwarning
+	(struct sieve_error_handler *ehandler, const char *location,
+		const char *fmt, va_list args) ATTR_FORMAT(3, 0);
+void sieve_vinfo
+	(struct sieve_error_handler *ehandler, const char *location,
+		const char *fmt, va_list args) ATTR_FORMAT(3, 0);
+void sieve_vdebug
+	(struct sieve_error_handler *ehandler, const char *location,
+		const char *fmt, va_list args) ATTR_FORMAT(3, 0);
+void sieve_vcritical
+	(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+		const char *location, const char *user_prefix, const char *fmt,
+		va_list args) ATTR_FORMAT(5, 0);
+
+void sieve_error
+	(struct sieve_error_handler *ehandler, const char *location,
+		const char *fmt, ...) ATTR_FORMAT(3, 4);
+void sieve_warning
+	(struct sieve_error_handler *ehandler, const char *location,
+		const char *fmt, ...) ATTR_FORMAT(3, 4);
+void sieve_info
+	(struct sieve_error_handler *ehandler, const char *location,
+		const char *fmt, ...) ATTR_FORMAT(3, 4);
+void sieve_debug
+	(struct sieve_error_handler *ehandler, const char *location,
+		const char *fmt, ...) ATTR_FORMAT(3, 4);
+void sieve_critical
+	(struct sieve_instance *svinst, struct sieve_error_handler *ehandler,
+		const char *location, const char *user_prefix, const char *fmt, ...)
+		ATTR_FORMAT(5, 6);
+
+void sieve_internal_error
+(struct sieve_error_handler *ehandler, const char *location,
+	const char *user_prefix) ATTR_NULL(1, 2, 3);
+
+/*
+ * Error handler configuration
+ */
+
+void sieve_error_handler_accept_infolog
+	(struct sieve_error_handler *ehandler, bool enable);
+void sieve_error_handler_accept_debuglog
+	(struct sieve_error_handler *ehandler, bool enable);
+
+/*
+ * Error handler statistics
+ */
+
+unsigned int sieve_get_errors(struct sieve_error_handler *ehandler);
+unsigned int sieve_get_warnings(struct sieve_error_handler *ehandler);
+
+bool sieve_errors_more_allowed(struct sieve_error_handler *ehandler);
+
+/*
+ * Error handler object
+ */
+
+void sieve_error_handler_ref(struct sieve_error_handler *ehandler);
+void sieve_error_handler_unref(struct sieve_error_handler **ehandler);
+
+void sieve_error_handler_reset(struct sieve_error_handler *ehandler);
+
+/*
+ * Error handlers
+ */
+
+/* Write errors to dovecot master log */
+struct sieve_error_handler *sieve_master_ehandler_create
+	(struct sieve_instance *svinst, const char *prefix, unsigned int max_errors);
+
+/* Write errors to stderr */
+struct sieve_error_handler *sieve_stderr_ehandler_create
+	(struct sieve_instance *svinst, unsigned int max_errors);
+
+/* Write errors into a string buffer */
+struct sieve_error_handler *sieve_strbuf_ehandler_create
+	(struct sieve_instance *svinst, string_t *strbuf, bool crlf,
+		unsigned int max_errors);
+
+/* Write errors to a logfile */
+struct sieve_error_handler *sieve_logfile_ehandler_create
+	(struct sieve_instance *svinst, const char *logfile, unsigned int max_errors);
+
+/* Wrapper: prefix all log messages */
+struct sieve_error_handler *sieve_prefix_ehandler_create
+	(struct sieve_error_handler *parent, const char *location,
+		const char *prefix);
+
+/* Wrapper: make messages part of var expansion */
+struct sieve_error_handler *sieve_varexpand_ehandler_create
+(struct sieve_error_handler *parent, const char *format,
+	const struct var_expand_table *table);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-extensions.c
@@ -0,0 +1,876 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "mempool.h"
+#include "hash.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-settings.h"
+#include "sieve-extensions.h"
+
+/*
+ * Forward declarations
+ */
+
+static void sieve_extension_registry_init(struct sieve_instance *svinst);
+static void sieve_extension_registry_deinit(struct sieve_instance *svinst);
+
+static void sieve_capability_registry_init(struct sieve_instance *svinst);
+static void sieve_capability_registry_deinit(struct sieve_instance *svinst);
+
+static struct sieve_extension *_sieve_extension_register
+	(struct sieve_instance *svinst, const struct sieve_extension_def *extdef,
+		bool load, bool required);
+
+/*
+ * Instance global context
+ */
+
+struct sieve_extension_registry {
+	ARRAY(struct sieve_extension *) extensions;
+	HASH_TABLE(const char *, struct sieve_extension *) extension_index;
+	HASH_TABLE(const char *, struct sieve_capability_registration *) capabilities_index;
+
+	/* Core language 'extensions' */
+	const struct sieve_extension *comparator_extension;
+	const struct sieve_extension *match_type_extension;
+	const struct sieve_extension *address_part_extension;
+
+	/* Preloaded extensions */
+	ARRAY(const struct sieve_extension *) preloaded_extensions;
+};
+
+/*
+ * Pre-loaded 'extensions'
+ */
+
+extern const struct sieve_extension_def comparator_extension;
+extern const struct sieve_extension_def match_type_extension;
+extern const struct sieve_extension_def address_part_extension;
+
+/*
+ * Dummy extensions
+ */
+
+/* FIXME: This is stupid. Define a comparator-* extension and be done with it */
+
+const struct sieve_extension_def comparator_i_octet_extension = {
+	.name = "comparator-i;octet",
+};
+
+const struct sieve_extension_def comparator_i_ascii_casemap_extension = {
+	.name = "comparator-i;ascii-casemap",
+};
+
+/*
+ * List of native extensions
+ */
+
+/* Dummy extensions */
+
+extern const struct sieve_extension_def comparator_i_octet_extension;
+extern const struct sieve_extension_def comparator_i_ascii_casemap_extension;
+
+const struct sieve_extension_def *sieve_dummy_extensions[] = {
+	&comparator_i_octet_extension, &comparator_i_ascii_casemap_extension
+};
+
+const unsigned int sieve_dummy_extensions_count =
+	N_ELEMENTS(sieve_dummy_extensions);
+
+/* Core */
+
+extern const struct sieve_extension_def fileinto_extension;
+extern const struct sieve_extension_def reject_extension;
+extern const struct sieve_extension_def envelope_extension;
+extern const struct sieve_extension_def encoded_character_extension;
+
+extern const struct sieve_extension_def vacation_extension;
+extern const struct sieve_extension_def subaddress_extension;
+extern const struct sieve_extension_def comparator_i_ascii_numeric_extension;
+extern const struct sieve_extension_def relational_extension;
+extern const struct sieve_extension_def regex_extension;
+extern const struct sieve_extension_def imap4flags_extension;
+extern const struct sieve_extension_def copy_extension;
+extern const struct sieve_extension_def include_extension;
+extern const struct sieve_extension_def body_extension;
+extern const struct sieve_extension_def variables_extension;
+extern const struct sieve_extension_def enotify_extension;
+extern const struct sieve_extension_def environment_extension;
+extern const struct sieve_extension_def mailbox_extension;
+extern const struct sieve_extension_def date_extension;
+extern const struct sieve_extension_def index_extension;
+extern const struct sieve_extension_def ihave_extension;
+extern const struct sieve_extension_def duplicate_extension;
+extern const struct sieve_extension_def mime_extension;
+extern const struct sieve_extension_def foreverypart_extension;
+extern const struct sieve_extension_def extracttext_extension;
+extern const struct sieve_extension_def mboxmetadata_extension;
+extern const struct sieve_extension_def servermetadata_extension;
+
+const struct sieve_extension_def *sieve_core_extensions[] = {
+	/* Core extensions */
+	&fileinto_extension, &reject_extension, &envelope_extension,
+	&encoded_character_extension,
+
+	/* 'Plugins' */
+	&vacation_extension, &subaddress_extension,
+	&comparator_i_ascii_numeric_extension,
+	&relational_extension, &regex_extension, &imap4flags_extension,
+	&copy_extension, &include_extension, &body_extension,
+	&variables_extension, &enotify_extension, &environment_extension,
+	&mailbox_extension, &date_extension, &index_extension, &ihave_extension,
+	&duplicate_extension, &mime_extension, &foreverypart_extension,
+	&extracttext_extension
+};
+
+const unsigned int sieve_core_extensions_count =
+	N_ELEMENTS(sieve_core_extensions);
+
+/* Extra;
+ *   These are not enabled by default, e.g. because explicit configuration is
+ *   necessary to make these useful.
+ */
+
+extern const struct sieve_extension_def vacation_seconds_extension;
+extern const struct sieve_extension_def spamtest_extension;
+extern const struct sieve_extension_def spamtestplus_extension;
+extern const struct sieve_extension_def virustest_extension;
+extern const struct sieve_extension_def editheader_extension;
+
+extern const struct sieve_extension_def vnd_debug_extension;
+extern const struct sieve_extension_def vnd_environment_extension;
+extern const struct sieve_extension_def vnd_report_extension;
+
+const struct sieve_extension_def *sieve_extra_extensions[] = {
+	&vacation_seconds_extension, &spamtest_extension, &spamtestplus_extension,
+	&virustest_extension, &editheader_extension,
+	&mboxmetadata_extension, &servermetadata_extension,
+
+	/* vnd.dovecot. */
+	&vnd_debug_extension, &vnd_environment_extension, &vnd_report_extension
+};
+
+const unsigned int sieve_extra_extensions_count =
+	N_ELEMENTS(sieve_extra_extensions);
+
+/*
+ * Deprecated extensions
+ */
+
+extern const struct sieve_extension_def imapflags_extension;
+extern const struct sieve_extension_def notify_extension;
+extern const struct sieve_extension_def vnd_duplicate_extension;
+
+const struct sieve_extension_def *sieve_deprecated_extensions[] = {
+	&imapflags_extension,
+	&notify_extension,
+	&vnd_duplicate_extension
+};
+
+const unsigned int sieve_deprecated_extensions_count =
+	N_ELEMENTS(sieve_deprecated_extensions);
+
+/*
+ * Unfinished extensions
+ */
+
+#ifdef HAVE_SIEVE_UNFINISHED
+
+extern const struct sieve_extension_def ereject_extension;
+
+const struct sieve_extension_def *sieve_unfinished_extensions[] = {
+	&ereject_extension
+};
+
+const unsigned int sieve_unfinished_extensions_count =
+	N_ELEMENTS(sieve_unfinished_extensions);
+
+#endif /* HAVE_SIEVE_UNFINISHED */
+
+/*
+ * Extensions init/deinit
+ */
+
+bool sieve_extensions_init(struct sieve_instance *svinst)
+{
+	unsigned int i;
+	struct sieve_extension_registry *ext_reg =
+		p_new(svinst->pool, struct sieve_extension_registry, 1);
+	struct sieve_extension *ext;
+
+	svinst->ext_reg = ext_reg;
+
+	sieve_extension_registry_init(svinst);
+	sieve_capability_registry_init(svinst);
+
+	/* Preloaded 'extensions' */
+	ext_reg->comparator_extension =
+		sieve_extension_register(svinst, &comparator_extension, TRUE);
+	ext_reg->match_type_extension =
+		sieve_extension_register(svinst, &match_type_extension, TRUE);
+	ext_reg->address_part_extension =
+		sieve_extension_register(svinst, &address_part_extension, TRUE);
+
+	p_array_init(&ext_reg->preloaded_extensions, svinst->pool, 5);
+	array_append(&ext_reg->preloaded_extensions,
+		&ext_reg->comparator_extension, 1);
+	array_append(&ext_reg->preloaded_extensions,
+		&ext_reg->match_type_extension, 1);
+	array_append(&ext_reg->preloaded_extensions,
+		&ext_reg->address_part_extension, 1);
+
+	/* Pre-load dummy extensions */
+	for ( i = 0; i < sieve_dummy_extensions_count; i++ ) {
+		if ( (ext=_sieve_extension_register
+			(svinst, sieve_dummy_extensions[i], TRUE, FALSE)) == NULL )
+			return FALSE;
+
+		ext->dummy = TRUE;
+	}
+
+	/* Pre-load core extensions */
+	for ( i = 0; i < sieve_core_extensions_count; i++ ) {
+		if ( sieve_extension_register
+			(svinst, sieve_core_extensions[i], TRUE) == NULL )
+			return FALSE;
+	}
+
+	/* Pre-load extra extensions */
+	for ( i = 0; i < sieve_extra_extensions_count; i++ ) {
+		if ( sieve_extension_register
+			(svinst, sieve_extra_extensions[i], FALSE) == NULL )
+			return FALSE;
+	}
+
+	/* Register deprecated extensions */
+	for ( i = 0; i < sieve_deprecated_extensions_count; i++ ) {
+		if ( sieve_extension_register
+			(svinst, sieve_deprecated_extensions[i], FALSE) == NULL )
+			return FALSE;
+	}
+
+#ifdef HAVE_SIEVE_UNFINISHED
+	/* Register unfinished extensions */
+	for ( i = 0; i < sieve_unfinished_extensions_count; i++ ) {
+		if ( sieve_extension_register
+			(svinst, sieve_unfinished_extensions[i], FALSE) == NULL )
+			return FALSE;
+	}
+#endif
+
+	/* More extensions can be added through plugins */
+
+	return TRUE;
+}
+
+void sieve_extensions_configure(struct sieve_instance *svinst)
+{
+	const char *extensions;
+
+	/* Apply sieve_extensions configuration */
+
+	if ( (extensions=sieve_setting_get
+		(svinst, "sieve_extensions")) != NULL )
+		sieve_extensions_set_string(svinst, extensions, FALSE, FALSE);
+
+	/* Apply sieve_global_extensions configuration */
+
+	if ( (extensions=sieve_setting_get
+		(svinst, "sieve_global_extensions")) != NULL )
+		sieve_extensions_set_string(svinst, extensions, TRUE, FALSE);
+
+	/* Apply sieve_implicit_extensions configuration */
+
+	if ( (extensions=sieve_setting_get
+		(svinst, "sieve_implicit_extensions")) != NULL )
+		sieve_extensions_set_string(svinst, extensions, FALSE, TRUE);
+}
+
+void sieve_extensions_deinit(struct sieve_instance *svinst)
+{
+	sieve_extension_registry_deinit(svinst);
+	sieve_capability_registry_deinit(svinst);
+}
+
+/*
+ * Pre-loaded extensions
+ */
+
+const struct sieve_extension *const *sieve_extensions_get_preloaded
+(struct sieve_instance *svinst, unsigned int *count_r)
+{
+	struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+
+	return array_get(&ext_reg->preloaded_extensions, count_r);
+}
+
+/*
+ * Extension registry
+ */
+
+static bool _sieve_extension_load(struct sieve_extension *ext)
+{
+	/* Call load handler */
+	if ( ext->def != NULL && ext->def->load != NULL &&
+		!ext->def->load(ext, &ext->context) ) {
+		sieve_sys_error(ext->svinst,
+			"failed to load '%s' extension support.", ext->def->name);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static void _sieve_extension_unload(struct sieve_extension *ext)
+{
+	/* Call unload handler */
+	if ( ext->def != NULL && ext->def->unload != NULL )
+		ext->def->unload(ext);
+	ext->context = NULL;
+}
+
+static void sieve_extension_registry_init(struct sieve_instance *svinst)
+{
+	struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+
+	p_array_init(&ext_reg->extensions, svinst->pool, 50);
+	hash_table_create
+		(&ext_reg->extension_index, default_pool, 0, str_hash, strcmp);
+}
+
+static void sieve_extension_registry_deinit(struct sieve_instance *svinst)
+{
+	struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+	struct sieve_extension * const *exts;
+    unsigned int i, ext_count;
+
+	if ( !hash_table_is_created(ext_reg->extension_index) ) return;
+
+    exts = array_get_modifiable(&ext_reg->extensions, &ext_count);
+	for ( i = 0; i < ext_count; i++ ) {
+		_sieve_extension_unload(exts[i]);
+	}
+
+	hash_table_destroy(&ext_reg->extension_index);
+}
+
+bool sieve_extension_reload(const struct sieve_extension *ext)
+{
+	struct sieve_extension_registry *ext_reg = ext->svinst->ext_reg;
+	struct sieve_extension * const *mod_ext;
+	int ext_id = ext->id;
+
+	/* Let's not just cast the 'const' away */
+	if ( ext_id >= 0 && ext_id < (int) array_count(&ext_reg->extensions) ) {
+		mod_ext = array_idx(&ext_reg->extensions, ext_id);
+
+		return _sieve_extension_load(*mod_ext);
+	}
+
+	return FALSE;
+}
+
+static struct sieve_extension *sieve_extension_lookup
+(struct sieve_instance *svinst, const char *name)
+{
+	struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+
+	return 	hash_table_lookup(ext_reg->extension_index, name);
+}
+
+static struct sieve_extension *sieve_extension_alloc
+(struct sieve_instance *svinst,
+	const struct sieve_extension_def *extdef)
+{
+	struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+	struct sieve_extension *ext, **extr;
+	int ext_id;
+
+	ext_id = (int)array_count(&ext_reg->extensions);
+
+	/* Add extension to the registry */
+	extr = array_append_space(&ext_reg->extensions);
+	*extr = ext = p_new(svinst->pool, struct sieve_extension, 1);
+	ext->id = ext_id;
+	ext->def = extdef;
+	ext->svinst = svinst;
+	return ext;
+}
+
+static struct sieve_extension *_sieve_extension_register
+(struct sieve_instance *svinst, const struct sieve_extension_def *extdef,
+	bool load, bool required)
+{
+	struct sieve_extension *ext;
+
+	ext = sieve_extension_lookup(svinst, extdef->name);
+
+	/* Register extension if it is not registered already */
+	if ( ext == NULL ) {
+		ext = sieve_extension_alloc(svinst, extdef);
+		hash_table_insert
+			(svinst->ext_reg->extension_index, extdef->name, ext);
+
+	} else if ( ext->overridden ) {
+		/* Create a dummy */
+		ext = sieve_extension_alloc(svinst, extdef);
+
+	} else {
+		/* Re-register it if it were previously unregistered
+		 * (not going to happen)
+		 */
+		i_assert( ext->def == NULL || ext->def == extdef );
+		ext->def = extdef;
+	}
+
+	/* Enable extension */
+	if ( load || required ) {
+		ext->enabled = ( ext->enabled || load );
+
+		/* Call load handler if extension was not loaded already */
+		if ( !ext->loaded ) {
+			if ( !_sieve_extension_load(ext) )
+				return NULL;
+		}
+
+		ext->loaded = TRUE;
+	}
+
+	ext->required = ( ext->required || required );
+
+	return ext;
+}
+
+const struct sieve_extension *sieve_extension_register
+(struct sieve_instance *svinst, const struct sieve_extension_def *extdef,
+	bool load)
+{
+	return _sieve_extension_register(svinst, extdef, load, FALSE);
+}
+
+void sieve_extension_unregister(const struct sieve_extension *ext)
+{
+	struct sieve_extension_registry *ext_reg = ext->svinst->ext_reg;
+	struct sieve_extension * const *mod_ext;
+	int ext_id = ext->id;
+
+	if ( ext_id >= 0 && ext_id < (int) array_count(&ext_reg->extensions) ) {
+		mod_ext = array_idx(&ext_reg->extensions, ext_id);
+
+		sieve_extension_capabilities_unregister(*mod_ext);
+		_sieve_extension_unload(*mod_ext);
+		(*mod_ext)->loaded = FALSE;
+		(*mod_ext)->enabled = FALSE;
+		(*mod_ext)->def = NULL;
+	}
+}
+
+const struct sieve_extension *sieve_extension_replace
+(struct sieve_instance *svinst, const struct sieve_extension_def *extdef,
+	bool load)
+{
+	struct sieve_extension *ext;
+
+	ext = sieve_extension_lookup(svinst, extdef->name);
+	if (ext != NULL)
+		sieve_extension_unregister(ext);
+	return sieve_extension_register(svinst, extdef, load);
+}
+
+const struct sieve_extension *sieve_extension_require
+(struct sieve_instance *svinst, const struct sieve_extension_def *extdef,
+	bool load)
+{
+	return _sieve_extension_register(svinst, extdef, load, TRUE);
+}
+
+void sieve_extension_override
+(struct sieve_instance *svinst, const char *name,
+	const struct sieve_extension *ext)
+{
+	struct sieve_extension_registry *ext_reg = ext->svinst->ext_reg;
+	struct sieve_extension * const *mod_ext;
+	struct sieve_extension *old_ext;
+
+	old_ext = sieve_extension_lookup(svinst, name);
+	if (old_ext == ext)
+		return;
+	i_assert( old_ext == NULL || !old_ext->overridden );
+
+	i_assert( ext->id >= 0 &&
+		ext->id < (int) array_count(&ext_reg->extensions) );
+	mod_ext = array_idx(&ext_reg->extensions, ext->id);
+
+	hash_table_update
+		(ext_reg->extension_index, name, *mod_ext);
+	if ( old_ext != NULL )
+		old_ext->overridden = TRUE;
+}
+
+unsigned int sieve_extensions_get_count(struct sieve_instance *svinst)
+{
+	struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+
+	return array_count(&ext_reg->extensions);
+}
+
+const struct sieve_extension *const *
+sieve_extensions_get_all(struct sieve_instance *svinst,
+	unsigned int *count_r)
+{
+	struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+
+	return (const struct sieve_extension *const *)
+		array_get(&ext_reg->extensions, count_r);
+}
+
+const struct sieve_extension *sieve_extension_get_by_id
+(struct sieve_instance *svinst, unsigned int ext_id)
+{
+	struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+	struct sieve_extension * const *ext;
+
+	if ( ext_id < array_count(&ext_reg->extensions) ) {
+		ext = array_idx(&ext_reg->extensions, ext_id);
+
+		if ( (*ext)->def != NULL && ((*ext)->enabled || (*ext)->required) )
+			return *ext;
+	}
+
+	return NULL;
+}
+
+const struct sieve_extension *sieve_extension_get_by_name
+(struct sieve_instance *svinst, const char *name)
+{
+	const struct sieve_extension *ext;
+
+	if ( *name == '@' )
+		return NULL;
+
+	if ( strlen(name) > 128 )
+		return NULL;
+
+	ext = sieve_extension_lookup(svinst, name);
+	if ( ext == NULL || ext->def == NULL || (!ext->enabled && !ext->required))
+		return NULL;
+
+	return ext;
+}
+
+static inline bool _sieve_extension_listable(const struct sieve_extension *ext)
+{
+	return ( ext->enabled && ext->def != NULL && *(ext->def->name) != '@'
+		&& !ext->dummy && !ext->global && !ext->overridden);
+}
+
+const char *sieve_extensions_get_string(struct sieve_instance *svinst)
+{
+	struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+	string_t *extstr = t_str_new(256);
+	struct sieve_extension * const *exts;
+	unsigned int i, ext_count;
+
+	exts = array_get(&ext_reg->extensions, &ext_count);
+
+	if ( ext_count > 0 ) {
+		i = 0;
+
+		/* Find first listable extension */
+		while ( i < ext_count && !_sieve_extension_listable(exts[i]) )
+			i++;
+
+		if ( i < ext_count ) {
+			/* Add first to string */
+			str_append(extstr, exts[i]->def->name);
+			i++;
+
+	 		/* Add others */
+			for ( ; i < ext_count; i++ ) {
+				if ( _sieve_extension_listable(exts[i]) ) {
+					str_append_c(extstr, ' ');
+					str_append(extstr, exts[i]->def->name);
+				}
+			}
+		}
+	}
+
+	return str_c(extstr);
+}
+
+static void sieve_extension_set_enabled
+(struct sieve_extension *ext, bool enabled)
+{
+	if ( enabled ) {
+		ext->enabled = TRUE;
+
+		if ( !ext->loaded ) {
+			(void)_sieve_extension_load(ext);
+		}
+
+		ext->loaded = TRUE;
+	} else {
+		ext->enabled = FALSE;
+	}
+}
+
+static void sieve_extension_set_global
+(struct sieve_extension *ext, bool enabled)
+{
+	if ( enabled ) {
+		sieve_extension_set_enabled(ext, TRUE);
+		ext->global = TRUE;
+	} else {
+		ext->global = FALSE;
+	}
+}
+
+static void sieve_extension_set_implicit
+(struct sieve_extension *ext, bool enabled)
+{
+	if ( enabled ) {
+		sieve_extension_set_enabled(ext, TRUE);
+		ext->implicit = TRUE;
+	} else {
+		ext->implicit = FALSE;
+	}
+}
+
+void sieve_extensions_set_string
+(struct sieve_instance *svinst, const char *ext_string,
+	bool global, bool implicit)
+{
+	struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+	ARRAY(const struct sieve_extension *) enabled_extensions;
+	ARRAY(const struct sieve_extension *) disabled_extensions;
+	const struct sieve_extension *const *ext_enabled;
+	const struct sieve_extension *const *ext_disabled;
+	struct sieve_extension **exts;
+	const char **ext_names;
+	unsigned int i, ext_count, ena_count, dis_count;
+	bool relative = FALSE;
+
+	if ( ext_string == NULL ) {
+		if ( global || implicit ) return;
+
+		/* Enable all */
+		exts = array_get_modifiable(&ext_reg->extensions, &ext_count);
+
+		for ( i = 0; i < ext_count; i++ )
+			sieve_extension_set_enabled(exts[i], TRUE);
+
+		return;
+	}
+
+	T_BEGIN {
+		t_array_init(&enabled_extensions, array_count(&ext_reg->extensions));
+		t_array_init(&disabled_extensions, array_count(&ext_reg->extensions));
+
+		ext_names = t_strsplit_spaces(ext_string, " \t");
+
+		while ( *ext_names != NULL ) {
+			const char *name = *ext_names;
+
+			ext_names++;
+
+			if ( *name != '\0' ) {
+				const struct sieve_extension *ext;
+				char op = '\0'; /* No add/remove operation */
+
+				if ( *name == '+' 		/* Add to existing config */
+					|| *name == '-' ) {	/* Remove from existing config */
+				 	op = *name++;
+				 	relative = TRUE;
+				}
+
+				if ( *name == '@' )
+					ext = NULL;
+				else
+					ext = hash_table_lookup(ext_reg->extension_index, name);
+
+				if ( ext == NULL || ext->def == NULL ) {
+					sieve_sys_warning(svinst,
+						"ignored unknown extension '%s' while configuring "
+						"available extensions", name);
+					continue;
+				}
+
+				if ( op == '-' )
+					array_append(&disabled_extensions, &ext, 1);
+				else
+					array_append(&enabled_extensions, &ext, 1);
+			}
+		}
+
+		exts = array_get_modifiable(&ext_reg->extensions, &ext_count);
+		ext_enabled = array_get(&enabled_extensions, &ena_count);
+		ext_disabled = array_get(&disabled_extensions, &dis_count);
+
+		/* Set new extension status */
+
+		for ( i = 0; i < ext_count; i++ ) {
+			unsigned int j;
+			bool enabled = FALSE;
+
+			if ( exts[i]->id < 0 || exts[i]->def == NULL ||
+				*(exts[i]->def->name) == '@' ) {
+				continue;
+			}
+
+			/* If extensions are specified relative to the default set,
+			 * we first need to check which ones are disabled
+			 */
+
+			if ( relative ) {
+				if ( global )
+					enabled = exts[i]->global;
+				else if ( implicit )
+					enabled = exts[i]->implicit;
+				else
+					enabled = exts[i]->enabled;
+
+				if ( enabled ) {
+					/* Disable if explicitly disabled */
+					for ( j = 0; j < dis_count; j++ ) {
+						if ( ext_disabled[j]->def == exts[i]->def ) {
+							enabled = FALSE;
+							break;
+						}
+					}
+				}
+			}
+
+			/* Enable if listed with '+' or no prefix */
+
+			for ( j = 0; j < ena_count; j++ ) {
+				if ( ext_enabled[j]->def == exts[i]->def ) {
+					enabled = TRUE;
+					break;
+				}
+			}
+
+			/* Perform actual activation/deactivation */
+			if ( global ) {
+				sieve_extension_set_global(exts[i], enabled);
+			} else if ( implicit ) {
+				sieve_extension_set_implicit(exts[i], enabled);
+			} else {
+				sieve_extension_set_enabled(exts[i], enabled);
+			}
+		}
+	} T_END;
+}
+
+const struct sieve_extension *sieve_get_match_type_extension
+	(struct sieve_instance *svinst)
+{
+	return svinst->ext_reg->match_type_extension;
+}
+
+const struct sieve_extension *sieve_get_comparator_extension
+	(struct sieve_instance *svinst)
+{
+	return svinst->ext_reg->comparator_extension;
+}
+
+const struct sieve_extension *sieve_get_address_part_extension
+	(struct sieve_instance *svinst)
+{
+	return svinst->ext_reg->address_part_extension;
+}
+
+void sieve_enable_debug_extension(struct sieve_instance *svinst)
+{
+	(void) sieve_extension_register(svinst, &vnd_debug_extension, TRUE);
+}
+
+/*
+ * Extension capabilities
+ */
+
+struct sieve_capability_registration {
+	const struct sieve_extension *ext;
+	const struct sieve_extension_capabilities *capabilities;
+};
+
+void sieve_capability_registry_init(struct sieve_instance *svinst)
+{
+	struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+
+	hash_table_create
+		(&ext_reg->capabilities_index, default_pool, 0, str_hash, strcmp);
+}
+
+void sieve_capability_registry_deinit(struct sieve_instance *svinst)
+{
+	struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+
+	if ( !hash_table_is_created(ext_reg->capabilities_index) ) return;
+
+	hash_table_destroy(&svinst->ext_reg->capabilities_index);
+}
+
+void sieve_extension_capabilities_register
+(const struct sieve_extension *ext,
+	const struct sieve_extension_capabilities *cap)
+{
+	struct sieve_instance *svinst = ext->svinst;
+	struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+	struct sieve_capability_registration *reg;
+
+	reg = hash_table_lookup(ext_reg->capabilities_index, cap->name);
+	if (reg != NULL) {
+		/* Already registered */
+		return;
+	}
+
+	reg = p_new(svinst->pool, struct sieve_capability_registration, 1);
+	reg->ext = ext;
+	reg->capabilities = cap;
+
+	hash_table_insert(ext_reg->capabilities_index, cap->name, reg);
+}
+
+void sieve_extension_capabilities_unregister
+(const struct sieve_extension *ext)
+{
+	struct sieve_extension_registry *ext_reg = ext->svinst->ext_reg;
+	struct hash_iterate_context *hictx;
+	const char *name;
+	struct sieve_capability_registration *reg;
+
+	hictx = hash_table_iterate_init(ext_reg->capabilities_index);
+	while ( hash_table_iterate(hictx, ext_reg->capabilities_index, &name, &reg) ) {
+		if ( reg->ext == ext )
+			hash_table_remove(ext_reg->capabilities_index, name);
+	}
+	hash_table_iterate_deinit(&hictx);
+}
+
+const char *sieve_extension_capabilities_get_string
+(struct sieve_instance *svinst, const char *cap_name)
+{
+	struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+	const struct sieve_capability_registration *cap_reg =
+		hash_table_lookup(ext_reg->capabilities_index, cap_name);
+	const struct sieve_extension_capabilities *cap;
+
+	if ( cap_reg == NULL || cap_reg->capabilities == NULL )
+		return NULL;
+
+	cap = cap_reg->capabilities;
+
+	if ( cap->get_string == NULL || !cap_reg->ext->enabled )
+		return NULL;
+
+	return cap->get_string(cap_reg->ext);
+}
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-extensions.h
@@ -0,0 +1,191 @@
+#ifndef SIEVE_EXTENSIONS_H
+#define SIEVE_EXTENSIONS_H
+
+#include "lib.h"
+#include "sieve-common.h"
+
+/*
+ * Per-extension object registry
+ */
+
+struct sieve_extension_objects {
+	const void *objects;
+	unsigned int count;
+};
+
+/*
+ * Extension definition
+ */
+
+struct sieve_extension_def {
+	const char *name;
+
+	/* Version */
+	unsigned int version;
+
+	/* Registration */
+	bool (*load)(const struct sieve_extension *ext, void **context);
+	void (*unload)(const struct sieve_extension *ext);
+
+	/* Compilation */
+	bool (*validator_load)
+		(const struct sieve_extension *ext, struct sieve_validator *validator);
+	bool (*generator_load)
+		(const struct sieve_extension *ext, const struct sieve_codegen_env *cgenv);
+	bool (*interpreter_load)
+		(const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+			sieve_size_t *address);
+	bool (*binary_load)
+		(const struct sieve_extension *ext, struct sieve_binary *binary);
+
+	/* Code dump */
+	bool (*binary_dump)
+		(const struct sieve_extension *ext, struct sieve_dumptime_env *denv);
+	bool (*code_dump)
+		(const struct sieve_extension *ext, const struct sieve_dumptime_env *denv,
+			sieve_size_t *address);
+
+	/* Objects */
+	struct sieve_extension_objects operations;
+	struct sieve_extension_objects operands;
+};
+
+/* Defining opcodes and operands */
+
+#define SIEVE_EXT_DEFINE_NO_OBJECTS \
+	{ NULL, 0 }
+#define SIEVE_EXT_DEFINE_OBJECT(OBJ) \
+	{ &OBJ, 1 }
+#define SIEVE_EXT_DEFINE_OBJECTS(OBJS) \
+	{ OBJS, N_ELEMENTS(OBJS) }
+
+#define SIEVE_EXT_GET_OBJECTS_COUNT(ext, field) \
+	ext->field->count;
+
+#define SIEVE_EXT_DEFINE_NO_OPERATIONS \
+	.operations = SIEVE_EXT_DEFINE_NO_OBJECTS
+#define SIEVE_EXT_DEFINE_OPERATION(OP) \
+	.operations = SIEVE_EXT_DEFINE_OBJECT(OP)
+#define SIEVE_EXT_DEFINE_OPERATIONS(OPS) \
+	.operations = SIEVE_EXT_DEFINE_OBJECTS(OPS)
+
+#define SIEVE_EXT_DEFINE_NO_OPERANDS \
+	.operands = SIEVE_EXT_DEFINE_NO_OBJECTS
+#define SIEVE_EXT_DEFINE_OPERAND(OP) \
+	.operands = SIEVE_EXT_DEFINE_OBJECT(OP)
+#define SIEVE_EXT_DEFINE_OPERANDS(OPS) \
+	.operands = SIEVE_EXT_DEFINE_OBJECTS(OPS)
+
+/*
+ * Extension instance
+ */
+
+struct sieve_extension {
+	const struct sieve_extension_def *def;
+	int id;
+
+	struct sieve_instance *svinst;
+	void *context;
+
+	bool required:1;
+	bool loaded:1;
+	bool enabled:1;
+	bool dummy:1;
+	bool global:1;
+	bool implicit:1;
+	bool overridden:1;
+};
+
+#define sieve_extension_is(ext, definition) \
+	( (ext)->def == &(definition) )
+#define sieve_extension_name(ext) \
+	((ext)->def->name)
+#define sieve_extension_name_is(ext, _name) \
+	( strcmp((ext)->def->name, (_name)) == 0 )
+#define sieve_extension_version(ext) \
+	((ext)->def->version)
+#define sieve_extension_version_is(ext, _version) \
+	((ext)->def->version == (_version))
+
+/*
+ * Extensions init/deinit
+ */
+
+bool sieve_extensions_init(struct sieve_instance *svinst);
+void sieve_extensions_configure(struct sieve_instance *svinst);
+void sieve_extensions_deinit(struct sieve_instance *svinst);
+
+/*
+ * Pre-loaded extensions
+ */
+
+const struct sieve_extension *const *sieve_extensions_get_preloaded
+	(struct sieve_instance *svinst, unsigned int *count_r);
+
+/*
+ * Extension registry
+ */
+
+const struct sieve_extension *sieve_extension_register
+	(struct sieve_instance *svinst, const struct sieve_extension_def *extension,
+		bool load);
+const struct sieve_extension *sieve_extension_require
+	(struct sieve_instance *svinst, const struct sieve_extension_def *extension,
+		bool load);
+bool sieve_extension_reload(const struct sieve_extension *ext);
+
+void sieve_extension_unregister(const struct sieve_extension *ext);
+
+const struct sieve_extension *sieve_extension_replace
+	(struct sieve_instance *svinst,
+		const struct sieve_extension_def *extdef,
+		bool load);
+void sieve_extension_override
+	(struct sieve_instance *svinst, const char *name,
+		const struct sieve_extension *ext);
+
+unsigned int sieve_extensions_get_count(struct sieve_instance *svinst);
+const struct sieve_extension *const *
+sieve_extensions_get_all(struct sieve_instance *svinst,
+	unsigned int *count_r);
+
+const struct sieve_extension *sieve_extension_get_by_id
+	(struct sieve_instance *svinst, unsigned int ext_id);
+const struct sieve_extension *sieve_extension_get_by_name
+	(struct sieve_instance *svinst, const char *name);
+
+const char *sieve_extensions_get_string
+	(struct sieve_instance *svinst);
+void sieve_extensions_set_string
+	(struct sieve_instance *svinst, const char *ext_string,
+		bool global, bool implicit);
+
+const struct sieve_extension *sieve_get_match_type_extension
+	(struct sieve_instance *svinst);
+const struct sieve_extension *sieve_get_comparator_extension
+	(struct sieve_instance *svinst);
+const struct sieve_extension *sieve_get_address_part_extension
+	(struct sieve_instance *svinst);
+
+void sieve_enable_debug_extension(struct sieve_instance *svinst);
+
+/*
+ * Capability registries
+ */
+
+struct sieve_extension_capabilities {
+	const char *name;
+
+	const char *(*get_string)(const struct sieve_extension *ext);
+};
+
+void sieve_extension_capabilities_register
+	(const struct sieve_extension *ext,
+		const struct sieve_extension_capabilities *cap);
+void sieve_extension_capabilities_unregister
+	(const struct sieve_extension *ext);
+
+const char *sieve_extension_capabilities_get_string
+	(struct sieve_instance *svinst, const char *cap_name);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-generator.c
@@ -0,0 +1,530 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "mempool.h"
+
+#include "sieve-common.h"
+#include "sieve-script.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+
+#include "sieve-generator.h"
+
+/*
+ * Jump list
+ */
+
+struct sieve_jumplist *sieve_jumplist_create
+(pool_t pool, struct sieve_binary_block *sblock)
+{
+	struct sieve_jumplist *jlist;
+
+	jlist = p_new(pool, struct sieve_jumplist, 1);
+	jlist->block = sblock;
+	p_array_init(&jlist->jumps, pool, 4);
+
+	return jlist;
+}
+
+void sieve_jumplist_init_temp
+(struct sieve_jumplist *jlist, struct sieve_binary_block *sblock)
+{
+	jlist->block = sblock;
+	t_array_init(&jlist->jumps, 4);
+}
+
+void sieve_jumplist_reset
+	(struct sieve_jumplist *jlist)
+{
+	array_clear(&jlist->jumps);
+}
+
+void sieve_jumplist_add(struct sieve_jumplist *jlist, sieve_size_t jump)
+{
+	array_append(&jlist->jumps, &jump, 1);
+}
+
+void sieve_jumplist_resolve(struct sieve_jumplist *jlist)
+{
+	unsigned int i;
+
+	for ( i = 0; i < array_count(&jlist->jumps); i++ ) {
+		const sieve_size_t *jump = array_idx(&jlist->jumps, i);
+
+		sieve_binary_resolve_offset(jlist->block, *jump);
+	}
+}
+
+/*
+ * Code Generator
+ */
+
+struct sieve_generator {
+	pool_t pool;
+
+	struct sieve_instance *instance;
+
+	struct sieve_error_handler *ehandler;
+
+	struct sieve_codegen_env genenv;
+	struct sieve_binary_debug_writer *dwriter;
+
+	ARRAY(void *) ext_contexts;
+};
+
+struct sieve_generator *sieve_generator_create
+(struct sieve_ast *ast, struct sieve_error_handler *ehandler,
+	enum sieve_compile_flags flags)
+{
+	pool_t pool;
+	struct sieve_generator *gentr;
+	struct sieve_script *script;
+	struct sieve_instance *svinst;
+
+	pool = pool_alloconly_create("sieve_generator", 4096);
+	gentr = p_new(pool, struct sieve_generator, 1);
+	gentr->pool = pool;
+
+	gentr->ehandler = ehandler;
+	sieve_error_handler_ref(ehandler);
+
+	gentr->genenv.gentr = gentr;
+	gentr->genenv.flags = flags;
+	gentr->genenv.ast = ast;
+	sieve_ast_ref(ast);
+
+	script = sieve_ast_script(ast);
+	svinst = sieve_script_svinst(script);
+
+	gentr->genenv.script = script;
+	gentr->genenv.svinst = svinst;
+
+	/* Setup storage for extension contexts */
+	p_array_init(&gentr->ext_contexts, pool, sieve_extensions_get_count(svinst));
+
+	return gentr;
+}
+
+void sieve_generator_free(struct sieve_generator **gentr)
+{
+	sieve_ast_unref(&(*gentr)->genenv.ast);
+
+	sieve_error_handler_unref(&(*gentr)->ehandler);
+	sieve_binary_debug_writer_deinit(&(*gentr)->dwriter);
+
+	if ( (*gentr)->genenv.sbin != NULL )
+		sieve_binary_unref(&(*gentr)->genenv.sbin);
+
+	pool_unref(&((*gentr)->pool));
+
+	*gentr = NULL;
+}
+
+/*
+ * Accessors
+ */
+
+struct sieve_error_handler *sieve_generator_error_handler
+(struct sieve_generator *gentr)
+{
+	return gentr->ehandler;
+}
+
+pool_t sieve_generator_pool(struct sieve_generator *gentr)
+{
+	return gentr->pool;
+}
+
+struct sieve_script *sieve_generator_script
+(struct sieve_generator *gentr)
+{
+	return gentr->genenv.script;
+}
+
+struct sieve_binary *sieve_generator_get_binary
+(struct sieve_generator *gentr)
+{
+	return gentr->genenv.sbin;
+}
+
+struct sieve_binary_block *sieve_generator_get_block
+(struct sieve_generator *gentr)
+{
+	return gentr->genenv.sblock;
+}
+
+/*
+ * Error handling
+ */
+
+void sieve_generator_warning
+(struct sieve_generator *gentr, unsigned int source_line,
+	const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	sieve_vwarning(gentr->ehandler,
+        sieve_error_script_location(gentr->genenv.script, source_line),
+        fmt, args);
+	va_end(args);
+}
+
+void sieve_generator_error
+(struct sieve_generator *gentr, unsigned int source_line,
+	const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	sieve_verror(gentr->ehandler,
+        sieve_error_script_location(gentr->genenv.script, source_line),
+        fmt, args);
+	va_end(args);
+}
+
+void sieve_generator_critical
+(struct sieve_generator *gentr, unsigned int source_line,
+	const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	sieve_vwarning(gentr->ehandler,
+        sieve_error_script_location(gentr->genenv.script, source_line),
+        fmt, args);
+	va_end(args);
+}
+
+/*
+ * Extension support
+ */
+
+void sieve_generator_extension_set_context
+(struct sieve_generator *gentr, const struct sieve_extension *ext, void *context)
+{
+	if ( ext->id < 0 ) return;
+
+	array_idx_set(&gentr->ext_contexts, (unsigned int) ext->id, &context);
+}
+
+const void *sieve_generator_extension_get_context
+(struct sieve_generator *gentr, const struct sieve_extension *ext)
+{
+	void * const *ctx;
+
+	if  ( ext->id < 0 || ext->id >= (int) array_count(&gentr->ext_contexts) )
+		return NULL;
+
+	ctx = array_idx(&gentr->ext_contexts, (unsigned int) ext->id);
+
+	return *ctx;
+}
+
+/*
+ * Code generation API
+ */
+
+static void sieve_generate_debug_from_ast_node
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_node *ast_node)
+{
+	sieve_size_t address = sieve_binary_block_get_size(cgenv->sblock);
+	unsigned int line = sieve_ast_node_line(ast_node);
+
+	sieve_binary_debug_emit(cgenv->gentr->dwriter, address, line, 0);
+}
+
+static void sieve_generate_debug_from_ast_argument
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *ast_arg)
+{
+	sieve_size_t address = sieve_binary_block_get_size(cgenv->sblock);
+	unsigned int line = sieve_ast_argument_line(ast_arg);
+
+	sieve_binary_debug_emit(cgenv->gentr->dwriter, address, line, 0);
+}
+
+bool sieve_generate_argument
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+	struct sieve_command *cmd)
+{
+	const struct sieve_argument_def *arg_def;
+
+	if ( arg->argument == NULL || arg->argument->def == NULL ) return FALSE;
+
+	arg_def = arg->argument->def;
+
+	if ( arg_def->generate == NULL )
+		return TRUE;
+
+	sieve_generate_debug_from_ast_argument(cgenv, arg);
+
+	return arg_def->generate(cgenv, arg, cmd);
+}
+
+bool sieve_generate_arguments
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd,
+	struct sieve_ast_argument **last_arg_r)
+{
+	enum { ARG_START, ARG_OPTIONAL, ARG_POSITIONAL } state = ARG_START;
+	struct sieve_ast_argument *arg = sieve_ast_argument_first(cmd->ast_node);
+
+	/* Generate all arguments with assigned generator function */
+
+	while ( arg != NULL ) {
+		const struct sieve_argument *argument;
+		const struct sieve_argument_def *arg_def;
+
+		if ( arg->argument == NULL || arg->argument->def == NULL )
+			return FALSE;
+
+		argument = arg->argument;
+		arg_def = argument->def;
+
+		switch ( state ) {
+		case ARG_START:
+			if ( argument->id_code == 0 )
+				state = ARG_POSITIONAL;
+			else {
+				/* Mark start of optional operands with 0 operand identifier */
+				sieve_binary_emit_byte(cgenv->sblock, SIEVE_OPERAND_OPTIONAL);
+
+				/* Emit argument id for optional operand */
+				sieve_binary_emit_byte
+					(cgenv->sblock, (unsigned char) argument->id_code);
+
+				state = ARG_OPTIONAL;
+			}
+			break;
+		case ARG_OPTIONAL:
+			if ( argument->id_code == 0 )
+				state = ARG_POSITIONAL;
+
+			/* Emit argument id for optional operand (0 marks the end of the optionals) */
+			sieve_binary_emit_byte
+				(cgenv->sblock, (unsigned char) argument->id_code);
+
+			break;
+		case ARG_POSITIONAL:
+			if ( argument->id_code != 0 )
+				return FALSE;
+			break;
+		}
+
+		/* Call the generation function for the argument */
+		if ( arg_def->generate != NULL ) {
+			sieve_generate_debug_from_ast_argument(cgenv, arg);
+
+			if ( !arg_def->generate(cgenv, arg, cmd) )
+				return FALSE;
+		} else if ( state == ARG_POSITIONAL ) break;
+
+		arg = sieve_ast_argument_next(arg);
+	}
+
+	/* Mark end of optional list if it is still open */
+	if ( state == ARG_OPTIONAL )
+		sieve_binary_emit_byte(cgenv->sblock, 0);
+
+	if ( last_arg_r != NULL )
+		*last_arg_r = arg;
+
+	return TRUE;
+}
+
+bool sieve_generate_argument_parameters
+(const struct sieve_codegen_env *cgenv,
+	struct sieve_command *cmd, struct sieve_ast_argument *arg)
+{
+	struct sieve_ast_argument *param = arg->parameters;
+
+	/* Generate all parameters with assigned generator function */
+
+	while ( param != NULL ) {
+		if ( param->argument != NULL && param->argument->def != NULL ) {
+			const struct sieve_argument_def *parameter = param->argument->def;
+
+			/* Call the generation function for the parameter */
+			if ( parameter->generate != NULL ) {
+				sieve_generate_debug_from_ast_argument(cgenv, param);
+
+				if ( !parameter->generate(cgenv, param, cmd) )
+					return FALSE;
+			}
+		}
+
+		param = sieve_ast_argument_next(param);
+	}
+
+	return TRUE;
+}
+
+bool sieve_generate_test
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_node *tst_node,
+	struct sieve_jumplist *jlist, bool jump_true)
+{
+	struct sieve_command *test;
+	const struct sieve_command_def *tst_def;
+
+	i_assert( tst_node->command != NULL && tst_node->command->def != NULL );
+
+	test = tst_node->command;
+	tst_def = test->def;
+
+	if ( tst_def->control_generate != NULL ) {
+		sieve_generate_debug_from_ast_node(cgenv, tst_node);
+
+		if ( tst_def->control_generate(cgenv, test, jlist, jump_true) )
+			return TRUE;
+
+		return FALSE;
+	}
+
+	if ( tst_def->generate != NULL ) {
+		sieve_generate_debug_from_ast_node(cgenv, tst_node);
+
+		if ( tst_def->generate(cgenv, test) ) {
+
+			if ( jump_true )
+				sieve_operation_emit(cgenv->sblock, NULL, &sieve_jmptrue_operation);
+			else
+				sieve_operation_emit(cgenv->sblock, NULL, &sieve_jmpfalse_operation);
+			sieve_jumplist_add(jlist, sieve_binary_emit_offset(cgenv->sblock, 0));
+
+			return TRUE;
+		}
+
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static bool sieve_generate_command
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_node *cmd_node)
+{
+	struct sieve_command *command;
+	const struct sieve_command_def *cmd_def;
+
+	i_assert( cmd_node->command != NULL && cmd_node->command->def != NULL );
+
+	command = cmd_node->command;
+	cmd_def = command->def;
+
+	if ( cmd_def->generate != NULL ) {
+		sieve_generate_debug_from_ast_node(cgenv, cmd_node);
+
+		return cmd_def->generate(cgenv, command);
+	}
+
+	return TRUE;
+}
+
+bool sieve_generate_block
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_node *block)
+{
+	bool result = TRUE;
+	struct sieve_ast_node *cmd_node;
+
+	T_BEGIN {
+		cmd_node = sieve_ast_command_first(block);
+		while ( result && cmd_node != NULL ) {
+			result = sieve_generate_command(cgenv, cmd_node);
+			cmd_node = sieve_ast_command_next(cmd_node);
+		}
+	} T_END;
+
+	return result;
+}
+
+struct sieve_binary *sieve_generator_run
+(struct sieve_generator *gentr, struct sieve_binary_block **sblock_r)
+{
+	bool topmost = ( sblock_r == NULL || *sblock_r == NULL );
+	struct sieve_binary *sbin;
+	struct sieve_binary_block *sblock, *debug_block;
+	const struct sieve_extension *const *extensions;
+	unsigned int i, ext_count;
+	bool result = TRUE;
+
+	/* Initialize */
+
+	if ( topmost ) {
+		sbin = sieve_binary_create_new(sieve_ast_script(gentr->genenv.ast));
+		sblock = sieve_binary_block_get(sbin, SBIN_SYSBLOCK_MAIN_PROGRAM);
+	} else {
+		sblock = *sblock_r;
+		sbin = sieve_binary_block_get_binary(sblock);
+	}
+
+	i_assert(sbin != NULL);
+
+	sieve_binary_ref(sbin);
+	gentr->genenv.sbin = sbin;
+	gentr->genenv.sblock = sblock;
+
+	/* Create debug block */
+	debug_block = sieve_binary_block_create(sbin);
+	gentr->dwriter = sieve_binary_debug_writer_init(debug_block);
+	(void)sieve_binary_emit_unsigned
+		(sblock, sieve_binary_block_get_id(debug_block));
+
+	/* Load extensions linked to the AST and emit a list in code */
+	extensions = sieve_ast_extensions_get(gentr->genenv.ast, &ext_count);
+	(void) sieve_binary_emit_unsigned(sblock, ext_count);
+	for ( i = 0; i < ext_count; i++ ) {
+		const struct sieve_extension *ext = extensions[i];
+		bool deferred;
+
+		/* Link to binary */
+		(void)sieve_binary_extension_link(sbin, ext);
+
+		/* Emit */
+		sieve_binary_emit_extension(sblock, ext, 0);
+
+		/* Emit deferred flag */
+		deferred = !sieve_ast_extension_is_required
+			(gentr->genenv.ast, ext);
+		sieve_binary_emit_byte(sblock, (deferred ? 1 : 0));
+
+		/* Load */
+		if ( ext->def != NULL && ext->def->generator_load != NULL &&
+			!ext->def->generator_load(ext, &gentr->genenv) )
+			result = FALSE;
+	}
+
+	/* Generate code */
+
+	if ( result ) {
+		if ( !sieve_generate_block
+			(&gentr->genenv, sieve_ast_root(gentr->genenv.ast)))
+			result = FALSE;
+		else if ( topmost )
+			sieve_binary_activate(sbin);
+	}
+
+	/* Cleanup */
+
+	gentr->genenv.sbin = NULL;
+	gentr->genenv.sblock = NULL;
+	sieve_binary_unref(&sbin);
+
+	if ( !result ) {
+		if ( topmost ) {
+			sieve_binary_unref(&sbin);
+			if ( sblock_r != NULL )
+				*sblock_r = NULL;
+		}
+		sbin = NULL;
+	} else {
+		if ( sblock_r != NULL )
+			*sblock_r = sblock;
+	}
+
+	return sbin;
+}
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-generator.h
@@ -0,0 +1,111 @@
+#ifndef SIEVE_GENERATOR_H
+#define SIEVE_GENERATOR_H
+
+#include "sieve-common.h"
+
+/*
+ * Code generator
+ */
+
+struct sieve_generator;
+
+struct sieve_codegen_env {
+	struct sieve_generator *gentr;
+
+	struct sieve_instance *svinst;
+	enum sieve_compile_flags flags;
+
+	struct sieve_script *script;
+	struct sieve_ast *ast;
+
+	struct sieve_binary *sbin;
+	struct sieve_binary_block *sblock;
+};
+
+struct sieve_generator *sieve_generator_create
+	(struct sieve_ast *ast, struct sieve_error_handler *ehandler,\
+		enum sieve_compile_flags flags);
+void sieve_generator_free(struct sieve_generator **generator);
+
+/*
+ * Accessors
+ */
+
+struct sieve_error_handler *sieve_generator_error_handler
+	(struct sieve_generator *gentr);
+pool_t sieve_generator_pool(struct sieve_generator *gentr);
+struct sieve_script *sieve_generator_script
+	(struct sieve_generator *gentr);
+struct sieve_binary *sieve_generator_get_binary
+	(struct sieve_generator *gentr);
+struct sieve_binary_block *sieve_generator_get_block
+	(struct sieve_generator *gentr);
+
+/*
+ * Error handling
+ */
+
+void sieve_generator_warning
+(struct sieve_generator *gentr, unsigned int source_line,
+	const char *fmt, ...) ATTR_FORMAT(3, 4);
+void sieve_generator_error
+(struct sieve_generator *gentr, unsigned int source_line,
+	const char *fmt, ...) ATTR_FORMAT(3, 4);
+void sieve_generator_critical
+(struct sieve_generator *gentr, unsigned int source_line,
+	const char *fmt, ...) ATTR_FORMAT(3, 4);
+
+/*
+ * Extension support
+ */
+
+void sieve_generator_extension_set_context
+	(struct sieve_generator *gentr, const struct sieve_extension *ext,
+		void *context);
+const void *sieve_generator_extension_get_context
+	(struct sieve_generator *gentr, const struct sieve_extension *ext);
+
+/*
+ * Jump list
+ */
+
+struct sieve_jumplist {
+	pool_t pool;
+	struct sieve_binary_block *block;
+	ARRAY(sieve_size_t) jumps;
+};
+
+struct sieve_jumplist *sieve_jumplist_create
+	(pool_t pool, struct sieve_binary_block *sblock);
+void sieve_jumplist_init_temp
+	(struct sieve_jumplist *jlist, struct sieve_binary_block *sblock);
+void sieve_jumplist_reset
+	(struct sieve_jumplist *jlist);
+void sieve_jumplist_add
+	(struct sieve_jumplist *jlist, sieve_size_t jump);
+void sieve_jumplist_resolve(struct sieve_jumplist *jlist);
+
+/*
+ * Code generation API
+ */
+
+bool sieve_generate_argument
+	(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+		struct sieve_command *cmd);
+bool sieve_generate_arguments
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd,
+		struct sieve_ast_argument **last_arg_r);
+bool sieve_generate_argument_parameters
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd,
+		struct sieve_ast_argument *arg);
+
+bool sieve_generate_block
+	(const struct sieve_codegen_env *cgenv, struct sieve_ast_node *block);
+bool sieve_generate_test
+	(const struct sieve_codegen_env *cgenv, struct sieve_ast_node *tst_node,
+		struct sieve_jumplist *jlist, bool jump_true);
+struct sieve_binary *sieve_generator_run
+	(struct sieve_generator *gentr, struct sieve_binary_block **sblock_r);
+
+#endif
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-interpreter.c
@@ -0,0 +1,1006 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "ostream.h"
+#include "mempool.h"
+#include "array.h"
+#include "hash.h"
+#include "mail-storage.h"
+
+#include "sieve-common.h"
+#include "sieve-limits.h"
+#include "sieve-script.h"
+#include "sieve-error.h"
+#include "sieve-extensions.h"
+#include "sieve-message.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-actions.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-result.h"
+#include "sieve-comparators.h"
+#include "sieve-runtime-trace.h"
+
+#include "sieve-interpreter.h"
+
+#include <string.h>
+
+/*
+ * Interpreter extension
+ */
+
+struct sieve_interpreter_extension_reg {
+	const struct sieve_interpreter_extension *intext;
+	const struct sieve_extension *ext;
+
+	void *context;
+
+	bool deferred:1;
+	bool started:1;
+};
+
+/*
+ * Code loop
+ */
+
+struct sieve_interpreter_loop {
+	unsigned int level;
+	sieve_size_t begin, end;
+	const struct sieve_extension_def *ext_def;
+	pool_t pool;
+	void *context;
+};
+
+/*
+ * Interpreter
+ */
+
+struct sieve_interpreter {
+	pool_t pool;
+	struct sieve_interpreter *parent;
+
+	/* Runtime data for extensions */
+	ARRAY(struct sieve_interpreter_extension_reg) extensions;
+
+	sieve_size_t reset_vector;
+
+	/* Execution status */
+
+	sieve_size_t pc;          /* Program counter */
+	bool interrupted;         /* Interpreter interrupt requested */
+	bool test_result;         /* Result of previous test command */
+
+	/* Loop stack */
+	ARRAY(struct sieve_interpreter_loop) loop_stack;
+	sieve_size_t loop_limit;
+	unsigned int parent_loop_level;
+
+	/* Runtime environment */
+	struct sieve_runtime_env runenv;
+	struct sieve_runtime_trace trace;
+
+	/* Current operation */
+	struct sieve_operation oprtn;
+
+	/* Location information */
+	struct sieve_binary_debug_reader *dreader;
+	unsigned int command_line;
+};
+
+static struct sieve_interpreter *_sieve_interpreter_create
+(struct sieve_binary *sbin,
+	struct sieve_binary_block *sblock,
+	struct sieve_script *script,
+	struct sieve_interpreter *parent,
+	const struct sieve_message_data *msgdata,
+	const struct sieve_script_env *senv,
+	struct sieve_error_handler *ehandler,
+	enum sieve_execute_flags flags)
+	ATTR_NULL(3, 4)
+{
+	unsigned int i, ext_count;
+	struct sieve_interpreter *interp;
+	pool_t pool;
+	struct sieve_instance *svinst;
+	const struct sieve_extension *const *ext_preloaded;
+	unsigned int debug_block_id;
+	sieve_size_t *address;
+	bool success = TRUE;
+
+	pool = pool_alloconly_create("sieve_interpreter", 4096);
+	interp = p_new(pool, struct sieve_interpreter, 1);
+	interp->parent = parent;
+	interp->pool = pool;
+
+	interp->runenv.ehandler = ehandler;
+	sieve_error_handler_ref(ehandler);
+
+	interp->runenv.interp = interp;
+	interp->runenv.oprtn = &interp->oprtn;
+	interp->runenv.sbin = sbin;
+	interp->runenv.sblock = sblock;
+	interp->runenv.flags = flags;
+	sieve_binary_ref(sbin);
+
+	svinst = sieve_binary_svinst(sbin);
+
+	interp->runenv.svinst = svinst;
+	interp->runenv.msgdata = msgdata;
+	interp->runenv.scriptenv = senv;
+
+	if ( senv->trace_log != NULL ) {
+		interp->trace.log = senv->trace_log;
+		interp->trace.config = senv->trace_config;
+		interp->trace.indent = 0;
+		interp->runenv.trace = &interp->trace;
+	}
+
+	if ( senv->exec_status == NULL )
+		interp->runenv.exec_status = p_new(interp->pool, struct sieve_exec_status, 1);
+	else
+		interp->runenv.exec_status = senv->exec_status;
+
+	if ( script == NULL )
+		interp->runenv.script = sieve_binary_script(sbin);
+	else
+		interp->runenv.script = script;
+
+	interp->runenv.pc = 0;
+	address = &(interp->runenv.pc);
+
+	sieve_runtime_trace_begin(&(interp->runenv));
+
+	p_array_init(&interp->extensions, pool, sieve_extensions_get_count(svinst));
+
+	interp->parent_loop_level = 0;
+	if ( parent != NULL && array_is_created(&parent->loop_stack) ) {
+		interp->parent_loop_level = parent->parent_loop_level +
+			array_count(&parent->loop_stack);
+	}
+
+	/* Pre-load core language features implemented as 'extensions' */
+	ext_preloaded = sieve_extensions_get_preloaded(svinst, &ext_count);
+	for ( i = 0; i < ext_count; i++ ) {
+		const struct sieve_extension_def *ext_def = ext_preloaded[i]->def;
+
+		if ( ext_def != NULL && ext_def->interpreter_load != NULL )
+			(void)ext_def->interpreter_load
+				(ext_preloaded[i], &interp->runenv, address);
+	}
+
+	/* Load debug block */
+	if ( sieve_binary_read_unsigned(sblock, address, &debug_block_id) ) {
+		struct sieve_binary_block *debug_block =
+			sieve_binary_block_get(sbin, debug_block_id);
+
+		if ( debug_block == NULL ) {
+			sieve_runtime_trace_error(&interp->runenv, "invalid id for debug block");
+			success = FALSE;
+		} else {
+			/* Initialize debug reader */
+			interp->dreader = sieve_binary_debug_reader_init(debug_block);
+		}
+	}
+
+	/* Load other extensions listed in code */
+	if ( success &&
+		sieve_binary_read_unsigned(sblock, address, &ext_count) ) {
+
+		for ( i = 0; i < ext_count; i++ ) {
+			unsigned int code = 0, deferred;
+			struct sieve_interpreter_extension_reg *reg;
+			const struct sieve_extension *ext;
+
+			if ( !sieve_binary_read_extension
+					(sblock, address, &code, &ext) ||
+				!sieve_binary_read_byte
+					(sblock, address, &deferred) ) {
+				success = FALSE;
+				break;
+			}
+
+			if ( deferred > 0 && ext->id >= 0 ) {
+				reg = array_idx_get_space
+					(&interp->extensions, (unsigned int)ext->id);
+				reg->deferred = TRUE;
+			}
+
+			if ( ext->def != NULL ) {
+				if ( ext->global && (flags & SIEVE_EXECUTE_FLAG_NOGLOBAL) != 0 ) {
+					sieve_runtime_error(&interp->runenv, NULL,
+						"failed to enable extension `%s': "
+						"its use is restricted to global scripts",
+						sieve_extension_name(ext));
+					success = FALSE;
+					break;
+				}
+
+				if ( ext->def->interpreter_load != NULL &&
+					!ext->def->interpreter_load(ext, &interp->runenv, address) ) {
+					success = FALSE;
+					break;
+				}
+			}
+		}
+	}	else
+		success = FALSE;
+
+	if ( !success ) {
+		sieve_interpreter_free(&interp);
+		interp = NULL;
+	} else {
+		interp->reset_vector = *address;
+	}
+
+	return interp;
+}
+
+struct sieve_interpreter *sieve_interpreter_create
+(struct sieve_binary *sbin,
+	struct sieve_interpreter *parent,
+	const struct sieve_message_data *msgdata,
+	const struct sieve_script_env *senv,
+	struct sieve_error_handler *ehandler,
+	enum sieve_execute_flags flags)
+{
+	struct sieve_binary_block *sblock;
+
+	if ( (sblock=sieve_binary_block_get(sbin, SBIN_SYSBLOCK_MAIN_PROGRAM))
+		== NULL )
+		return NULL;
+
+ 	return _sieve_interpreter_create(sbin, sblock, NULL,
+		parent, msgdata, senv, ehandler, flags);
+}
+
+struct sieve_interpreter *sieve_interpreter_create_for_block
+(struct sieve_binary_block *sblock,
+	struct sieve_script *script,
+	struct sieve_interpreter *parent,
+	const struct sieve_message_data *msgdata,
+	const struct sieve_script_env *senv,
+	struct sieve_error_handler *ehandler,
+	enum sieve_execute_flags flags)
+{
+	if ( sblock == NULL ) return NULL;
+
+ 	return _sieve_interpreter_create
+		(sieve_binary_block_get_binary(sblock), sblock, script,
+			parent, msgdata, senv, ehandler, flags);
+}
+
+void sieve_interpreter_free(struct sieve_interpreter **_interp)
+{
+	struct sieve_interpreter *interp = *_interp;
+	struct sieve_runtime_env *renv = &interp->runenv;
+	const struct sieve_interpreter_extension_reg *eregs;
+	struct sieve_interpreter_loop *loops;
+	unsigned int count, i;
+
+	if ( array_is_created(&interp->loop_stack) ) {
+		loops = array_get_modifiable(&interp->loop_stack, &count);
+		for ( i = 0; i < count; i++ )
+			pool_unref(&loops[i].pool);
+	}
+
+	interp->trace.indent = 0;
+	sieve_runtime_trace_end(renv);
+
+	/* Signal registered extensions that the interpreter is being destroyed */
+	eregs = array_get(&interp->extensions, &count);
+	for ( i = 0; i < count; i++ ) {
+		if ( eregs[i].intext != NULL && eregs[i].intext->free != NULL )
+			eregs[i].intext->free(eregs[i].ext, interp, eregs[i].context);
+	}
+
+	sieve_binary_debug_reader_deinit(&interp->dreader);
+	sieve_binary_unref(&renv->sbin);
+	sieve_error_handler_unref(&renv->ehandler);
+
+	pool_unref(&interp->pool);
+	*_interp = NULL;
+}
+
+/*
+ * Accessors
+ */
+
+pool_t sieve_interpreter_pool(struct sieve_interpreter *interp)
+{
+	return interp->pool;
+}
+
+struct sieve_interpreter *
+sieve_interpreter_get_parent(struct sieve_interpreter *interp)
+{
+	return interp->parent;
+}
+
+struct sieve_script *sieve_interpreter_script
+(struct sieve_interpreter *interp)
+{
+	return interp->runenv.script;
+}
+
+struct sieve_error_handler *sieve_interpreter_get_error_handler
+(struct sieve_interpreter *interp)
+{
+	return interp->runenv.ehandler;
+}
+
+struct sieve_instance *sieve_interpreter_svinst
+(struct sieve_interpreter *interp)
+{
+	return interp->runenv.svinst;
+}
+
+/* Do not use this function for normal sieve extensions. This is intended for
+ * the testsuite only.
+ */
+void sieve_interpreter_set_result
+(struct sieve_interpreter *interp, struct sieve_result *result)
+{
+	sieve_result_unref(&interp->runenv.result);
+	interp->runenv.result = result;
+	interp->runenv.msgctx = sieve_result_get_message_context(result);
+	sieve_result_ref(result);
+}
+
+/*
+ * Error handling
+ */
+
+static inline void sieve_runtime_vmsg
+(const struct sieve_runtime_env *renv, sieve_error_vfunc_t msg_func,
+	const char *location, const char *fmt, va_list args)
+{
+	T_BEGIN {
+		if ( location == NULL )
+			location = sieve_runtime_get_full_command_location(renv);
+
+		msg_func(renv->ehandler, location, fmt, args);
+	} T_END;
+}
+
+void sieve_runtime_error
+(const struct sieve_runtime_env *renv, const char *location,
+	const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	sieve_runtime_vmsg(renv, sieve_verror, location, fmt, args);
+	va_end(args);
+}
+
+void sieve_runtime_warning
+(const struct sieve_runtime_env *renv, const char *location,
+	const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	sieve_runtime_vmsg(renv, sieve_vwarning, location, fmt, args);
+	va_end(args);
+}
+
+void sieve_runtime_log
+(const struct sieve_runtime_env *renv, const char *location,
+	const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	sieve_runtime_vmsg(renv, sieve_vinfo, location, fmt, args);
+	va_end(args);
+}
+
+void sieve_runtime_critical
+(const struct sieve_runtime_env *renv, const char *location,
+	const char *user_prefix, const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+
+	T_BEGIN {
+		if ( location == NULL )
+			location = sieve_runtime_get_full_command_location(renv);
+
+		sieve_vcritical
+			(renv->svinst, renv->ehandler, location, user_prefix, fmt, args);
+	} T_END;
+
+	va_end(args);
+}
+
+int sieve_runtime_mail_error
+(const struct sieve_runtime_env *renv, struct mail *mail,
+	const char *fmt, ...)
+{
+	const char *error_msg, *user_prefix;
+	va_list args;
+
+	error_msg = mailbox_get_last_error(mail->box, NULL);
+
+	va_start(args, fmt);
+	user_prefix = t_strdup_vprintf(fmt, args);
+	sieve_runtime_critical(renv, NULL, user_prefix,
+		"%s: %s", user_prefix, error_msg);
+	va_end(args);
+
+	return 	SIEVE_EXEC_TEMP_FAILURE;
+}
+
+/*
+ * Source location
+ */
+
+unsigned int sieve_runtime_get_source_location
+(const struct sieve_runtime_env *renv, sieve_size_t code_address)
+{
+	struct sieve_interpreter *interp = renv->interp;
+
+	if ( interp->dreader == NULL )
+		return 0;
+
+	if ( interp->command_line == 0 ) {
+		interp->command_line = sieve_binary_debug_read_line
+			(interp->dreader, renv->oprtn->address);
+	}
+
+	return sieve_binary_debug_read_line(interp->dreader, code_address);
+}
+
+unsigned int sieve_runtime_get_command_location
+(const struct sieve_runtime_env *renv)
+{
+	struct sieve_interpreter *interp = renv->interp;
+
+	if ( interp->dreader == NULL )
+		return 0;
+
+	if ( interp->command_line == 0 )
+		interp->command_line = sieve_binary_debug_read_line
+			(interp->dreader, renv->oprtn->address);
+
+	return interp->command_line;
+}
+
+const char *sieve_runtime_get_full_command_location
+(const struct sieve_runtime_env *renv)
+{
+	return sieve_error_script_location
+		(renv->script, sieve_runtime_get_command_location(renv));
+}
+
+/*
+ * Extension support
+ */
+
+void sieve_interpreter_extension_register
+(struct sieve_interpreter *interp, const struct sieve_extension *ext,
+	const struct sieve_interpreter_extension *intext, void *context)
+{
+	struct sieve_interpreter_extension_reg *reg;
+
+	if ( ext->id < 0 ) return;
+
+	reg = array_idx_get_space(&interp->extensions, (unsigned int) ext->id);
+	reg->intext = intext;
+	reg->ext = ext;
+	reg->context = context;
+}
+
+void sieve_interpreter_extension_set_context
+(struct sieve_interpreter *interp, const struct sieve_extension *ext,
+	void *context)
+{
+	struct sieve_interpreter_extension_reg *reg;
+
+	if ( ext->id < 0 ) return;
+
+	reg = array_idx_get_space(&interp->extensions, (unsigned int) ext->id);
+	reg->context = context;
+}
+
+void *sieve_interpreter_extension_get_context
+(struct sieve_interpreter *interp, const struct sieve_extension *ext)
+{
+	const struct sieve_interpreter_extension_reg *reg;
+
+	if  ( ext->id < 0 || ext->id >= (int) array_count(&interp->extensions) )
+		return NULL;
+
+	reg = array_idx(&interp->extensions, (unsigned int) ext->id);
+
+	return reg->context;
+}
+
+int sieve_interpreter_extension_start
+(struct sieve_interpreter *interp,
+	const struct sieve_extension *ext)
+{
+	struct sieve_interpreter_extension_reg *reg;
+	int ret;
+
+	i_assert( ext->id >= 0 );
+
+	if ( ext->id >= (int) array_count(&interp->extensions) )
+		return SIEVE_EXEC_OK;
+
+	reg = array_idx_modifiable
+		(&interp->extensions, (unsigned int)ext->id);
+
+	if (!reg->deferred)
+		return SIEVE_EXEC_OK;
+	reg->deferred = FALSE;
+	reg->started = TRUE;
+
+	if ( reg->intext != NULL &&
+		reg->intext->run != NULL &&
+		(ret=reg->intext->run(ext,
+			&interp->runenv, reg->context, TRUE)) <= 0 )
+		return ret;
+	return SIEVE_EXEC_OK;
+}
+
+/*
+ * Loop handling
+ */
+
+int sieve_interpreter_loop_start
+(struct sieve_interpreter *interp, sieve_size_t loop_end,
+	const struct sieve_extension_def *ext_def,
+	struct sieve_interpreter_loop **loop_r)
+{
+	const struct sieve_runtime_env *renv = &interp->runenv;
+	struct sieve_interpreter_loop *loop;
+
+	i_assert( loop_end > interp->runenv.pc );
+
+	/* Check supplied end offset */
+	if ( loop_end > sieve_binary_block_get_size(renv->sblock) ) {
+		sieve_runtime_trace_error(renv,
+			"loop end offset out of range");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	/* Trace */
+	if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
+		unsigned int line =
+			sieve_runtime_get_source_location(renv, loop_end);
+
+		if ( sieve_runtime_trace_hasflag(renv, SIEVE_TRFLG_ADDRESSES) ) {
+			sieve_runtime_trace(renv, 0, "loop ends at line %d [%08llx]",
+				line, (long long unsigned int) loop_end);
+		} else {
+			sieve_runtime_trace(renv, 0, "loop ends at line %d", line);
+		}
+	}
+
+	/* Check loop nesting limit */
+	if ( !array_is_created(&interp->loop_stack) )
+		p_array_init(&interp->loop_stack, interp->pool, 8);
+	if ( (interp->parent_loop_level + array_count(&interp->loop_stack))
+		>= SIEVE_MAX_LOOP_DEPTH ) {
+		/* Should normally be caught at compile time */
+		sieve_runtime_error(renv, NULL,
+			"new program loop exceeds "
+			"the nesting limit (<= %u levels)",
+			SIEVE_MAX_LOOP_DEPTH);
+		return SIEVE_EXEC_FAILURE;
+	}
+
+	/* Create new loop */
+	loop = array_append_space(&interp->loop_stack);
+	loop->level = array_count(&interp->loop_stack)-1;
+	loop->ext_def = ext_def;
+	loop->begin = interp->runenv.pc;
+	loop->end = loop_end;
+	loop->pool =  pool_alloconly_create("sieve_interpreter", 128);
+
+	/* Set new loop limit */
+	interp->loop_limit = loop_end;
+	
+	*loop_r = loop;
+	return SIEVE_EXEC_OK;
+}
+
+struct sieve_interpreter_loop *sieve_interpreter_loop_get
+(struct sieve_interpreter *interp, sieve_size_t loop_end,
+	const struct sieve_extension_def *ext_def)
+{
+	struct sieve_interpreter_loop *loops;
+	unsigned int count, i;
+
+	if ( !array_is_created(&interp->loop_stack) )
+		return NULL;
+
+	loops = array_get_modifiable(&interp->loop_stack, &count);
+	for ( i = count; i > 0; i-- ) {
+		/* We're really making sure our loop matches */
+		if ( loops[i-1].end == loop_end &&
+			loops[i-1].ext_def == ext_def )
+			return &loops[i-1];
+	}
+	return NULL;
+}
+
+int sieve_interpreter_loop_next(struct sieve_interpreter *interp,
+	struct sieve_interpreter_loop *loop,
+	sieve_size_t loop_begin)
+{
+	const struct sieve_runtime_env *renv = &interp->runenv;
+	struct sieve_interpreter_loop *loops;
+	unsigned int count;
+
+	/* Trace */
+	if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
+		unsigned int line =
+			sieve_runtime_get_source_location(renv, loop_begin);
+
+		if ( sieve_runtime_trace_hasflag(renv, SIEVE_TRFLG_ADDRESSES) ) {
+			sieve_runtime_trace(renv, 0, "looping back to line %d [%08llx]",
+				line, (long long unsigned int) loop_begin);
+		} else {
+			sieve_runtime_trace(renv, 0, "looping back to line %d", line);
+		}
+	}
+
+	/* Check the code for corruption */
+	if ( loop->begin != loop_begin ) {
+		sieve_runtime_trace_error(renv,
+			"loop begin offset invalid");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	/* Check invariants */
+	i_assert( array_is_created(&interp->loop_stack) );
+	loops = array_get_modifiable(&interp->loop_stack, &count);
+	i_assert( &loops[count-1] == loop );
+
+	/* Return to beginning */
+	interp->runenv.pc = loop_begin;
+	return SIEVE_EXEC_OK;
+}
+
+int sieve_interpreter_loop_break(struct sieve_interpreter *interp,
+	struct sieve_interpreter_loop *loop)
+{
+	const struct sieve_runtime_env *renv = &interp->runenv;
+	struct sieve_interpreter_loop *loops;
+	sieve_size_t loop_end = loop->end;
+	unsigned int count, i;
+
+	/* Find the loop */
+	i_assert( array_is_created(&interp->loop_stack) );
+	loops = array_get_modifiable(&interp->loop_stack, &count);
+	i_assert( count > 0 );
+
+	i = count;
+	do {
+		pool_unref(&loops[i-1].pool);
+		i--;
+	} while ( i > 0 && &loops[i] != loop );
+	i_assert( &loops[i] == loop );
+
+	/* Set new loop limit */
+	if ( i > 0 )
+		interp->loop_limit = loops[i].end;
+	else
+		interp->loop_limit = 0;
+
+	/* Delete it and all deeper loops */
+	array_delete(&interp->loop_stack, i, count - i);
+
+	/* Trace */
+	if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
+		unsigned int jmp_line =
+			sieve_runtime_get_source_location(renv, loop_end);
+
+		if ( sieve_runtime_trace_hasflag(renv, SIEVE_TRFLG_ADDRESSES) ) {
+			sieve_runtime_trace(renv, 0, "exiting loops at line %d [%08llx]",
+				jmp_line, (long long unsigned int) loop_end);
+		} else {
+			sieve_runtime_trace(renv, 0, "exiting loops at line %d", jmp_line);
+		}
+	}
+
+	/* Exit loop */
+	interp->runenv.pc = loop->end;
+	return SIEVE_EXEC_OK;
+}
+
+static int
+sieve_interpreter_loop_break_out(struct sieve_interpreter *interp,
+	sieve_size_t target)
+{
+	struct sieve_interpreter_loop *loops;
+	unsigned int count, i;
+
+	if ( !array_is_created(&interp->loop_stack) )
+		return SIEVE_EXEC_OK;
+
+	loops = array_get_modifiable(&interp->loop_stack, &count);
+	for ( i = count; i > 0; i-- ) {
+		/* We're really making sure our loop matches */
+		if ( loops[i-1].end > target )
+			break;
+	}
+	if ( i == count )
+		return SIEVE_EXEC_OK;
+
+	return sieve_interpreter_loop_break(interp, &loops[i]);
+}
+
+struct sieve_interpreter_loop *sieve_interpreter_loop_get_local
+(struct sieve_interpreter *interp,
+	struct sieve_interpreter_loop *loop,
+	const struct sieve_extension_def *ext_def)
+{
+	struct sieve_interpreter_loop *loops;
+	unsigned int count, i;
+
+	if ( !array_is_created(&interp->loop_stack) )
+		return NULL;
+
+	loops = array_get_modifiable(&interp->loop_stack, &count);
+	i_assert(loop == NULL || loop->level < count);
+
+	for ( i = (loop == NULL ? count : loop->level); i > 0; i-- ) {
+		if ( ext_def == NULL || loops[i-1].ext_def == ext_def )
+			return &loops[i-1];
+	}
+	return NULL;
+}
+
+struct sieve_interpreter_loop *sieve_interpreter_loop_get_global
+(struct sieve_interpreter *interp,
+	struct sieve_interpreter_loop *loop,
+	const struct sieve_extension_def *ext_def)
+{
+	struct sieve_interpreter_loop *result;
+
+	while (interp != NULL) {
+		result = sieve_interpreter_loop_get_local
+			(interp, loop, ext_def);
+		if (result != NULL)
+			return result;
+		interp = interp->parent;
+	}
+	return NULL;
+}
+
+pool_t sieve_interpreter_loop_get_pool
+(struct sieve_interpreter_loop *loop)
+{
+	return loop->pool;
+}
+
+void *sieve_interpreter_loop_get_context
+(struct sieve_interpreter_loop *loop)
+{
+	return loop->context;
+}
+
+void sieve_interpreter_loop_set_context
+(struct sieve_interpreter_loop *loop, void *context)
+{
+	loop->context = context;
+}
+
+/*
+ * Program flow
+ */
+
+void sieve_interpreter_reset(struct sieve_interpreter *interp)
+{
+	interp->runenv.pc = interp->reset_vector;
+	interp->interrupted = FALSE;
+	interp->test_result = FALSE;
+	interp->runenv.result = NULL;
+}
+
+void sieve_interpreter_interrupt(struct sieve_interpreter *interp)
+{
+	interp->interrupted = TRUE;
+}
+
+sieve_size_t sieve_interpreter_program_counter(struct sieve_interpreter *interp)
+{
+	return interp->runenv.pc;
+}
+
+int sieve_interpreter_program_jump
+(struct sieve_interpreter *interp, bool jump, bool break_loops)
+{
+	const struct sieve_runtime_env *renv = &interp->runenv;
+	sieve_size_t *address = &(interp->runenv.pc);
+	sieve_size_t jmp_start = *address, jmp_target;
+	sieve_size_t loop_limit = ( break_loops ? 0 : interp->loop_limit );
+	sieve_offset_t jmp_offset;
+	int ret;
+
+	if ( !sieve_binary_read_offset(renv->sblock, address, &jmp_offset) )
+	{
+		sieve_runtime_trace_error(renv, "invalid jump offset");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	jmp_target = jmp_start + jmp_offset;
+	if ( jmp_target <= sieve_binary_block_get_size(renv->sblock) &&
+		(loop_limit == 0 || jmp_target < loop_limit) &&
+		jmp_start + jmp_offset > 0 )
+	{
+		if ( jump ) {
+			if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
+				unsigned int jmp_line =
+					sieve_runtime_get_source_location(renv, jmp_target);
+
+				if ( sieve_runtime_trace_hasflag(renv, SIEVE_TRFLG_ADDRESSES) ) {
+					sieve_runtime_trace(renv, 0, "jumping to line %d [%08llx]",
+						jmp_line, (long long unsigned int) jmp_target);
+				} else {
+					sieve_runtime_trace(renv, 0, "jumping to line %d", jmp_line);
+				}
+			}
+
+			if ( break_loops && 
+				(ret=sieve_interpreter_loop_break_out(interp, jmp_target)) <= 0 )
+				return ret;
+			*address = jmp_target;
+		} else {
+			sieve_runtime_trace(renv, 0, "not jumping");
+		}
+
+		return SIEVE_EXEC_OK;
+	}
+
+	if ( interp->loop_limit != 0 ) {
+		sieve_runtime_trace_error(renv,
+			"jump offset crosses loop boundary");
+	} else {
+		sieve_runtime_trace_error(renv,
+			"jump offset out of range");
+	}
+	return SIEVE_EXEC_BIN_CORRUPT;
+}
+
+/*
+ * Test results
+ */
+
+void sieve_interpreter_set_test_result
+(struct sieve_interpreter *interp, bool result)
+{
+	interp->test_result = result;
+}
+
+bool sieve_interpreter_get_test_result
+(struct sieve_interpreter *interp)
+{
+	return interp->test_result;
+}
+
+/*
+ * Code execute
+ */
+
+static int sieve_interpreter_operation_execute
+(struct sieve_interpreter *interp)
+{
+	struct sieve_operation *oprtn = &(interp->oprtn);
+	sieve_size_t *address = &(interp->runenv.pc);
+
+	sieve_runtime_trace_toplevel(&interp->runenv);
+
+	/* Read the operation */
+	if ( sieve_operation_read(interp->runenv.sblock, address, oprtn) ) {
+		const struct sieve_operation_def *op = oprtn->def;
+		int result = SIEVE_EXEC_OK;
+
+		/* Reset cached command location */
+		interp->command_line = 0;
+
+		/* Execute the operation */
+		if ( op->execute != NULL ) { /* Noop ? */
+			T_BEGIN {
+				result = op->execute(&(interp->runenv), address);
+			} T_END;
+		} else {
+			sieve_runtime_trace
+				(&interp->runenv, SIEVE_TRLVL_COMMANDS, "OP: %s (NOOP)",
+					sieve_operation_mnemonic(oprtn));
+		}
+
+		return result;
+	}
+
+	/* Binary corrupt */
+	sieve_runtime_trace_error(&interp->runenv, "Encountered invalid operation");
+	return SIEVE_EXEC_BIN_CORRUPT;
+}
+
+int sieve_interpreter_continue
+(struct sieve_interpreter *interp, bool *interrupted)
+{
+	const struct sieve_runtime_env *renv = &interp->runenv;
+	sieve_size_t *address = &(interp->runenv.pc);
+	int ret = SIEVE_EXEC_OK;
+
+	sieve_result_ref(renv->result);
+	interp->interrupted = FALSE;
+
+	if ( interrupted != NULL )
+		*interrupted = FALSE;
+
+	while ( ret == SIEVE_EXEC_OK && !interp->interrupted &&
+		*address < sieve_binary_block_get_size(renv->sblock) ) {
+		if ( interp->loop_limit != 0 && *address > interp->loop_limit ) {
+			sieve_runtime_trace_error(renv,
+				"program crossed loop boundary");
+			ret = SIEVE_EXEC_BIN_CORRUPT;
+			break;
+		}
+
+		ret = sieve_interpreter_operation_execute(interp);
+	}
+
+	if ( ret != SIEVE_EXEC_OK ) {
+		sieve_runtime_trace(&interp->runenv, SIEVE_TRLVL_NONE,
+			"[[EXECUTION ABORTED]]");
+	}
+
+	if ( interrupted != NULL )
+		*interrupted = interp->interrupted;
+
+	sieve_result_unref(&interp->runenv.result);
+	return ret;
+}
+
+int sieve_interpreter_start
+(struct sieve_interpreter *interp, struct sieve_result *result, bool *interrupted)
+{
+	struct sieve_interpreter_extension_reg *eregs;
+	unsigned int ext_count, i;
+	int ret;
+
+	interp->runenv.result = result;
+	interp->runenv.msgctx = sieve_result_get_message_context(result);
+
+	/* Signal registered extensions that the interpreter is being run */
+	eregs = array_get_modifiable(&interp->extensions, &ext_count);
+	for ( i = 0; i < ext_count; i++ ) {
+		if ( !eregs[i].deferred ) {
+			eregs[i].started = TRUE;
+			if (eregs[i].intext != NULL &&
+				eregs[i].intext->run != NULL &&
+				(ret=eregs[i].intext->run(eregs[i].ext,
+					&interp->runenv, eregs[i].context, FALSE)) <= 0 ) {
+				return ret;
+			}
+		}
+	}
+
+	return sieve_interpreter_continue(interp, interrupted);
+}
+
+int sieve_interpreter_run
+(struct sieve_interpreter *interp, struct sieve_result *result)
+{
+	int ret = 0;
+
+	sieve_interpreter_reset(interp);
+	sieve_result_ref(result);
+
+	ret = sieve_interpreter_start(interp, result, NULL);
+
+	sieve_result_unref(&result);
+
+	return ret;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-interpreter.h
@@ -0,0 +1,196 @@
+#ifndef SIEVE_INTERPRETER_H
+#define SIEVE_INTERPRETER_H
+
+#include "lib.h"
+#include "array.h"
+#include "buffer.h"
+
+#include "sieve-common.h"
+#include "sieve-runtime.h"
+
+/*
+ * Interpreter
+ */
+
+struct sieve_interpreter *sieve_interpreter_create
+	(struct sieve_binary *sbin,
+		struct sieve_interpreter *parent,
+		const struct sieve_message_data *msgdata,
+		const struct sieve_script_env *senv,
+		struct sieve_error_handler *ehandler,
+		enum sieve_execute_flags flags)
+	ATTR_NULL(2);
+struct sieve_interpreter *sieve_interpreter_create_for_block
+	(struct sieve_binary_block *sblock,
+		struct sieve_script *script,
+		struct sieve_interpreter *parent,
+		const struct sieve_message_data *msgdata,
+		const struct sieve_script_env *senv,
+		struct sieve_error_handler *ehandler,
+		enum sieve_execute_flags flags);
+	ATTR_NULL(3);
+void sieve_interpreter_free(struct sieve_interpreter **_interp);
+
+/*
+ * Accessors
+ */
+
+pool_t sieve_interpreter_pool
+	(struct sieve_interpreter *interp);
+struct sieve_interpreter *
+sieve_interpreter_get_parent(struct sieve_interpreter *interp);
+struct sieve_script *sieve_interpreter_script
+	(struct sieve_interpreter *interp);
+struct sieve_error_handler *sieve_interpreter_get_error_handler
+	(struct sieve_interpreter *interp);
+struct sieve_instance *sieve_interpreter_svinst
+	(struct sieve_interpreter *interp);
+
+/* Do not use this function for normal sieve extensions. This is intended for
+ * the testsuite only.
+ */
+void sieve_interpreter_set_result
+	(struct sieve_interpreter *interp, struct sieve_result *result);
+
+/*
+ * Loop handling
+ */
+
+struct sieve_interpreter_loop;
+
+int sieve_interpreter_loop_start
+	(struct sieve_interpreter *interp, sieve_size_t loop_end,
+		const struct sieve_extension_def *ext_def,
+		struct sieve_interpreter_loop **loop_r);
+struct sieve_interpreter_loop *sieve_interpreter_loop_get
+	(struct sieve_interpreter *interp, sieve_size_t loop_end,
+		const struct sieve_extension_def *ext_def);
+int sieve_interpreter_loop_next
+	(struct sieve_interpreter *interp,
+		struct sieve_interpreter_loop *loop,
+		sieve_size_t loop_begin);
+int sieve_interpreter_loop_break
+	(struct sieve_interpreter *interp,
+		struct sieve_interpreter_loop *loop);
+
+struct sieve_interpreter_loop *sieve_interpreter_loop_get_local
+(struct sieve_interpreter *interp,
+	struct sieve_interpreter_loop *loop,
+	const struct sieve_extension_def *ext_def) ATTR_NULL(2, 3);
+struct sieve_interpreter_loop *sieve_interpreter_loop_get_global
+(struct sieve_interpreter *interp,
+	struct sieve_interpreter_loop *loop,
+	const struct sieve_extension_def *ext_def) ATTR_NULL(2, 3);
+
+pool_t sieve_interpreter_loop_get_pool
+	(struct sieve_interpreter_loop *loop) ATTR_PURE;
+void *sieve_interpreter_loop_get_context
+	(struct sieve_interpreter_loop *loop) ATTR_PURE;
+void sieve_interpreter_loop_set_context
+	(struct sieve_interpreter_loop *loop, void *context);
+
+/*
+ * Program flow
+ */
+
+void sieve_interpreter_reset
+	(struct sieve_interpreter *interp);
+void sieve_interpreter_interrupt
+	(struct sieve_interpreter *interp);
+sieve_size_t sieve_interpreter_program_counter
+	(struct sieve_interpreter *interp);
+
+int sieve_interpreter_program_jump
+	(struct sieve_interpreter *interp, bool jump, bool break_loops);
+
+/*
+ * Test results
+ */
+
+void sieve_interpreter_set_test_result
+	(struct sieve_interpreter *interp, bool result);
+bool sieve_interpreter_get_test_result
+	(struct sieve_interpreter *interp);
+
+/*
+ * Source location
+ */
+
+unsigned int sieve_runtime_get_source_location
+	(const struct sieve_runtime_env *renv, sieve_size_t code_address);
+
+unsigned int sieve_runtime_get_command_location
+	(const struct sieve_runtime_env *renv);
+const char *sieve_runtime_get_full_command_location
+	(const struct sieve_runtime_env *renv);
+
+/*
+ * Error handling
+ */
+
+void sieve_runtime_error
+	(const struct sieve_runtime_env *renv, const char *location,
+		const char *fmt, ...) ATTR_FORMAT(3, 4);
+void sieve_runtime_warning
+	(const struct sieve_runtime_env *renv, const char *location,
+		const char *fmt, ...) ATTR_FORMAT(3, 4);
+void sieve_runtime_log
+	(const struct sieve_runtime_env *renv, const char *location,
+		const char *fmt, ...) ATTR_FORMAT(3, 4);
+void sieve_runtime_critical
+	(const struct sieve_runtime_env *renv, const char *location,
+		const char *user_prefix, const char *fmt, ...) ATTR_FORMAT(4, 5);
+int sieve_runtime_mail_error
+	(const struct sieve_runtime_env *renv, struct mail *mail,
+		const char *fmt, ...) ATTR_FORMAT(3, 4);
+
+/*
+ * Extension support
+ */
+
+struct sieve_interpreter_extension {
+	const struct sieve_extension_def *ext_def;
+
+	int (*run)
+		(const struct sieve_extension *ext,
+			const struct sieve_runtime_env *renv,
+			void *context, bool deferred);
+	void (*free)
+		(const struct sieve_extension *ext,
+			struct sieve_interpreter *interp, void *context);
+};
+
+void sieve_interpreter_extension_register
+	(struct sieve_interpreter *interp, const struct sieve_extension *ext,
+		const struct sieve_interpreter_extension *intext, void *context);
+void sieve_interpreter_extension_set_context
+	(struct sieve_interpreter *interp, const struct sieve_extension *ext,
+		void *context);
+void *sieve_interpreter_extension_get_context
+	(struct sieve_interpreter *interp, const struct sieve_extension *ext);
+
+int sieve_interpreter_extension_start
+	(struct sieve_interpreter *interp,
+		const struct sieve_extension *ext);
+
+/*
+ * Opcodes and operands
+ */
+
+int sieve_interpreter_handle_optional_operands
+	(const struct sieve_runtime_env *renv, sieve_size_t *address,
+		struct sieve_side_effects_list **list);
+
+/*
+ * Code execute
+ */
+
+int sieve_interpreter_continue
+	(struct sieve_interpreter *interp, bool *interrupted);
+int sieve_interpreter_start
+	(struct sieve_interpreter *interp, struct sieve_result *result,
+		bool *interrupted);
+int sieve_interpreter_run
+	(struct sieve_interpreter *interp, struct sieve_result *result);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-lexer.c
@@ -0,0 +1,841 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "compat.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "istream.h"
+
+#include "sieve-common.h"
+#include "sieve-limits.h"
+#include "sieve-error.h"
+#include "sieve-script.h"
+
+#include "sieve-lexer.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <ctype.h>
+
+/*
+ * Useful macros
+ */
+
+#define DIGIT_VAL(c) ( c - '0' )
+
+/*
+ * Forward declarations
+ */
+
+inline static void sieve_lexer_error
+	(const struct sieve_lexer *lexer, const char *fmt, ...) ATTR_FORMAT(2, 3);
+inline static void sieve_lexer_warning
+	(const struct sieve_lexer *lexer, const char *fmt, ...) ATTR_FORMAT(2, 3);
+
+/*
+ * Lexer object
+ */
+
+struct sieve_lexical_scanner {
+	pool_t pool;
+	struct sieve_instance *svinst;
+
+	struct sieve_script *script;
+	struct istream *input;
+
+	struct sieve_error_handler *ehandler;
+
+	/* Currently scanned data */
+	const unsigned char *buffer;
+	size_t buffer_size;
+	size_t buffer_pos;
+
+	struct sieve_lexer lexer;
+
+	int current_line;
+};
+
+const struct sieve_lexer *sieve_lexer_create
+(struct sieve_script *script, struct sieve_error_handler *ehandler,
+	enum sieve_error *error_r)
+{
+	struct sieve_lexical_scanner *scanner;
+	struct sieve_instance *svinst = sieve_script_svinst(script);
+	struct istream *stream;
+	const struct stat *st;
+
+	/* Open script as stream */
+	if ( sieve_script_get_stream(script, &stream, error_r) < 0 )
+		return NULL;
+
+	/* Check script size */
+	if ( i_stream_stat(stream, TRUE, &st) >= 0 && st->st_size > 0 &&
+		svinst->max_script_size > 0 &&
+		(uoff_t)st->st_size > svinst->max_script_size ) {
+		sieve_error(ehandler, sieve_script_name(script),
+			"sieve script is too large (max %"PRIuSIZE_T" bytes)",
+			svinst->max_script_size);
+		if ( error_r != NULL )
+			*error_r = SIEVE_ERROR_NOT_POSSIBLE;
+		return NULL;
+	}
+
+	scanner = i_new(struct sieve_lexical_scanner, 1);
+	scanner->lexer.scanner = scanner;
+
+	scanner->ehandler = ehandler;
+	sieve_error_handler_ref(ehandler);
+
+	scanner->input = stream;
+	i_stream_ref(scanner->input);
+
+	scanner->script = script;
+	sieve_script_ref(script);
+
+	scanner->buffer = NULL;
+	scanner->buffer_size = 0;
+	scanner->buffer_pos = 0;
+
+	scanner->lexer.token_type = STT_NONE;
+	scanner->lexer.token_str_value = str_new(default_pool, 256);
+	scanner->lexer.token_int_value = 0;
+	scanner->lexer.token_line = 1;
+
+	scanner->current_line = 1;
+
+	return &scanner->lexer;
+}
+
+void sieve_lexer_free(const struct sieve_lexer **_lexer)
+{
+	const struct sieve_lexer *lexer = *_lexer;
+	struct sieve_lexical_scanner *scanner = lexer->scanner;
+
+	i_stream_unref(&scanner->input);
+	sieve_script_unref(&scanner->script);
+	sieve_error_handler_unref(&scanner->ehandler);
+	str_free(&scanner->lexer.token_str_value);
+
+	i_free(scanner);
+	*_lexer = NULL;
+}
+
+/*
+ * Internal error handling
+ */
+
+inline static void sieve_lexer_error
+(const struct sieve_lexer *lexer, const char *fmt, ...)
+{
+	struct sieve_lexical_scanner *scanner = lexer->scanner;
+
+	va_list args;
+	va_start(args, fmt);
+
+	T_BEGIN {
+		sieve_verror(scanner->ehandler,
+			sieve_error_script_location(scanner->script, scanner->current_line),
+			fmt, args);
+	} T_END;
+
+	va_end(args);
+}
+
+inline static void sieve_lexer_warning
+(const struct sieve_lexer *lexer, const char *fmt, ...)
+{
+	struct sieve_lexical_scanner *scanner = lexer->scanner;
+
+	va_list args;
+	va_start(args, fmt);
+
+	T_BEGIN {
+		sieve_vwarning(scanner->ehandler,
+			sieve_error_script_location(scanner->script, scanner->current_line),
+			fmt, args);
+	} T_END;
+
+	va_end(args);
+}
+
+const char *sieve_lexer_token_description
+(const struct sieve_lexer *lexer)
+{
+	switch ( lexer->token_type ) {
+		case STT_NONE: return "no token (bug)";
+		case STT_WHITESPACE: return "whitespace (bug)";
+		case STT_EOF: return "end of file";
+
+		case STT_NUMBER: return "number";
+		case STT_IDENTIFIER: return "identifier";
+		case STT_TAG: return "tag";
+		case STT_STRING: return "string";
+
+		case STT_RBRACKET: return "')'";
+		case STT_LBRACKET: return "'('";
+		case STT_RCURLY: return "'}'";
+		case STT_LCURLY: return "'{'";
+		case STT_RSQUARE: return "']'";
+		case STT_LSQUARE: return "'['";
+		case STT_SEMICOLON: return "';'";
+		case STT_COMMA: return "','";
+
+		case STT_SLASH: return "'/'";
+		case STT_COLON: return "':'";
+
+		case STT_GARBAGE: return "unknown characters";
+		case STT_ERROR: return "error token (bug)";
+	}
+
+	return "unknown token (bug)";
+}
+
+/*
+ * Debug
+ */
+
+void sieve_lexer_token_print(const struct sieve_lexer *lexer)
+{
+	switch ( lexer->token_type ) {
+		case STT_NONE: printf("??NONE?? "); break;
+		case STT_WHITESPACE: printf("??WHITESPACE?? "); break;
+		case STT_EOF: printf("EOF\n"); break;
+
+		case STT_NUMBER: printf("NUMBER "); break;
+		case STT_IDENTIFIER: printf("IDENTIFIER "); break;
+		case STT_TAG: printf("TAG "); break;
+		case STT_STRING: printf("STRING "); break;
+
+		case STT_RBRACKET: printf(") "); break;
+		case STT_LBRACKET: printf("( "); break;
+		case STT_RCURLY: printf("}\n"); break;
+		case STT_LCURLY: printf("{\n"); break;
+		case STT_RSQUARE: printf("] "); break;
+		case STT_LSQUARE: printf("[ "); break;
+		case STT_SEMICOLON: printf(";\n"); break;
+		case STT_COMMA: printf(", "); break;
+
+		case STT_SLASH: printf("/ "); break;
+		case STT_COLON: printf(": "); break;
+
+		case STT_GARBAGE: printf(">>GARBAGE<<"); break;
+		case STT_ERROR: printf(">>ERROR<<"); break;
+	default:
+		printf("UNKNOWN ");
+		break;
+	}
+}
+
+/*
+ * Lexical scanning
+ */
+
+static void sieve_lexer_shift(struct sieve_lexical_scanner *scanner)
+{
+	if ( scanner->buffer_size > 0 && scanner->buffer[scanner->buffer_pos] == '\n' )
+		scanner->current_line++;
+
+	if ( scanner->buffer_size > 0 &&
+		scanner->buffer_pos + 1 < scanner->buffer_size )
+		scanner->buffer_pos++;
+	else {
+		if ( scanner->buffer_size > 0 )
+			i_stream_skip(scanner->input, scanner->buffer_size);
+
+		scanner->buffer = i_stream_get_data(scanner->input, &scanner->buffer_size);
+
+		if ( scanner->buffer_size == 0 && i_stream_read(scanner->input) > 0 )
+	  		scanner->buffer = i_stream_get_data
+					(scanner->input, &scanner->buffer_size);
+
+		scanner->buffer_pos = 0;
+	}
+}
+
+static inline int sieve_lexer_curchar(struct sieve_lexical_scanner *scanner)
+{
+	if ( scanner->buffer_size == 0 )
+		return -1;
+
+	return scanner->buffer[scanner->buffer_pos];
+}
+
+static inline const char *_char_sanitize(int ch)
+{
+	if ( ch > 31 && ch < 127 )
+		return t_strdup_printf("'%c'", ch);
+
+	return t_strdup_printf("0x%02x", ch);
+}
+
+static bool
+sieve_lexer_scan_number(struct sieve_lexical_scanner *scanner)
+{
+	struct sieve_lexer *lexer = &scanner->lexer;
+	uintmax_t value;
+	string_t *str;
+	bool overflow = FALSE;
+
+	str_truncate(lexer->token_str_value,0);
+	str = lexer->token_str_value;
+
+	while ( i_isdigit(sieve_lexer_curchar(scanner)) ) {
+		str_append_c(str, sieve_lexer_curchar(scanner));
+		sieve_lexer_shift(scanner);
+	}
+
+	if (str_to_uintmax(str_c(str), &value) < 0 ||
+		value > (sieve_number_t)-1) {
+		overflow = TRUE;
+	} else {
+		switch ( sieve_lexer_curchar(scanner) ) {
+		case 'k':
+		case 'K': /* Kilo */
+			if ( value > (SIEVE_MAX_NUMBER >> 10) )
+				overflow = TRUE;
+			else
+				value = value << 10;
+			sieve_lexer_shift(scanner);
+			break;
+		case 'm':
+		case 'M': /* Mega */
+			if ( value > (SIEVE_MAX_NUMBER >> 20) )
+				overflow = TRUE;
+			else
+				value = value << 20;
+			sieve_lexer_shift(scanner);
+			break;
+		case 'g':
+		case 'G': /* Giga */
+			if ( value > (SIEVE_MAX_NUMBER >> 30) )
+				overflow = TRUE;
+			else
+				value = value << 30;
+			sieve_lexer_shift(scanner);
+			break;
+		default:
+			/* Next token */
+			break;
+		}
+	}
+
+	/* Check for integer overflow */
+	if ( overflow ) {
+		sieve_lexer_error(lexer,
+			"number exceeds integer limits (max %llu)",
+			(long long) SIEVE_MAX_NUMBER);
+		lexer->token_type = STT_ERROR;
+		return FALSE;
+	}
+
+	lexer->token_type = STT_NUMBER;
+	lexer->token_int_value = (sieve_number_t)value;
+	return TRUE;
+
+}
+
+static bool
+sieve_lexer_scan_hash_comment(struct sieve_lexical_scanner *scanner)
+{
+	struct sieve_lexer *lexer = &scanner->lexer;
+
+	while ( sieve_lexer_curchar(scanner) != '\n' ) {
+		switch( sieve_lexer_curchar(scanner) ) {
+		case -1:
+			if ( !scanner->input->eof ) {
+				lexer->token_type = STT_ERROR;
+				return FALSE;
+			}
+			sieve_lexer_warning(lexer,
+				"no newline (CRLF) at end of hash comment at end of file");
+			lexer->token_type = STT_WHITESPACE;
+			return TRUE;
+		case '\0':
+			sieve_lexer_error
+				(lexer, "encountered NUL character in hash comment");
+			lexer->token_type = STT_ERROR;
+			return FALSE;
+		default:
+			break;
+		}
+
+		/* Stray CR is ignored */
+
+		sieve_lexer_shift(scanner);
+	}
+
+	sieve_lexer_shift(scanner);
+
+	lexer->token_type = STT_WHITESPACE;
+	return TRUE;
+}
+
+/* sieve_lexer_scan_raw_token:
+ *   Scans valid tokens and whitespace
+ */
+static bool
+sieve_lexer_scan_raw_token(struct sieve_lexical_scanner *scanner)
+{
+	struct sieve_lexer *lexer = &scanner->lexer;
+	string_t *str;
+	int ret;
+
+	/* Read first character */
+	if ( lexer->token_type == STT_NONE ) {
+		if ( (ret=i_stream_read(scanner->input)) < 0 ) {
+			i_assert( ret != -2 );
+			if ( !scanner->input->eof ) {
+				lexer->token_type = STT_ERROR;
+				return FALSE;
+			}
+		}
+		sieve_lexer_shift(scanner);
+	}
+
+	lexer->token_line = scanner->current_line;
+
+	switch ( sieve_lexer_curchar(scanner) ) {
+
+	/* whitespace */
+
+	// hash-comment = ( "#" *CHAR-NOT-CRLF CRLF )
+	case '#':
+		sieve_lexer_shift(scanner);
+		return sieve_lexer_scan_hash_comment(scanner);
+
+	// bracket-comment = "/*" *(CHAR-NOT-STAR / ("*" CHAR-NOT-SLASH)) "*/"
+	//        ;; No */ allowed inside a comment.
+	//        ;; (No * is allowed unless it is the last character,
+	//        ;; or unless it is followed by a character that isn't a
+	//        ;; slash.)
+	case '/':
+		sieve_lexer_shift(scanner);
+
+		if ( sieve_lexer_curchar(scanner) == '*' ) {
+			sieve_lexer_shift(scanner);
+
+			while ( TRUE ) {
+				switch ( sieve_lexer_curchar(scanner) ) {
+				case -1:
+					if ( scanner->input->eof ) {
+						sieve_lexer_error(lexer,
+							"end of file before end of bracket comment ('/* ... */') "
+							"started at line %d", lexer->token_line);
+					}
+					lexer->token_type = STT_ERROR;
+					return FALSE;
+				case '*':
+					sieve_lexer_shift(scanner);
+
+					if ( sieve_lexer_curchar(scanner) == '/' ) {
+						sieve_lexer_shift(scanner);
+
+						lexer->token_type = STT_WHITESPACE;
+						return TRUE;
+
+					} else if ( sieve_lexer_curchar(scanner) == -1 ) {
+						sieve_lexer_error(lexer,
+							"end of file before end of bracket comment ('/* ... */') "
+							"started at line %d", lexer->token_line);
+						lexer->token_type = STT_ERROR;
+						return FALSE;
+					}
+					break;
+				case '\0':
+					sieve_lexer_error(lexer,
+						"encountered NUL character in bracket comment");
+					lexer->token_type = STT_ERROR;
+					return FALSE;
+				default:
+					sieve_lexer_shift(scanner);
+				}
+			}
+
+			i_unreached();
+			return FALSE;
+		}
+
+		lexer->token_type = STT_SLASH;
+		return TRUE;
+
+	// comment = bracket-comment / hash-comment
+	// white-space = 1*(SP / CRLF / HTAB) / comment
+	case '\t':
+	case '\r':
+	case '\n':
+	case ' ':
+		sieve_lexer_shift(scanner);
+
+		while ( sieve_lexer_curchar(scanner) == '\t' ||
+			sieve_lexer_curchar(scanner) == '\r' ||
+			sieve_lexer_curchar(scanner) == '\n' ||
+			sieve_lexer_curchar(scanner) == ' ' ) {
+
+			sieve_lexer_shift(scanner);
+		}
+
+		lexer->token_type = STT_WHITESPACE;
+		return TRUE;
+
+	/* quoted-string */
+	case '"':
+		sieve_lexer_shift(scanner);
+
+		str_truncate(lexer->token_str_value, 0);
+		str = lexer->token_str_value;
+
+		while ( sieve_lexer_curchar(scanner) != '"' ) {
+			if ( sieve_lexer_curchar(scanner) == '\\' )
+				sieve_lexer_shift(scanner);
+
+			switch ( sieve_lexer_curchar(scanner) ) {
+
+			/* End of file */
+			case -1:
+				if ( scanner->input->eof ) {
+					sieve_lexer_error(lexer,
+						"end of file before end of quoted string "
+						"started at line %d", lexer->token_line);
+				}
+				lexer->token_type = STT_ERROR;
+				return FALSE;
+
+			/* NUL character */
+			case '\0':
+				sieve_lexer_error(lexer,
+					"encountered NUL character in quoted string "
+					"started at line %d", lexer->token_line);
+				lexer->token_type = STT_ERROR;
+				return FALSE;
+
+			/* CR .. check for LF */
+			case '\r':
+				sieve_lexer_shift(scanner);
+
+				if ( sieve_lexer_curchar(scanner) != '\n' ) {
+					sieve_lexer_error(lexer,
+						"found stray carriage-return (CR) character "
+						"in quoted string started at line %d", lexer->token_line);
+					lexer->token_type = STT_ERROR;
+					return FALSE;
+				}
+
+				if ( str_len(str) <= SIEVE_MAX_STRING_LEN )
+					str_append(str, "\r\n");
+				break;
+
+			/* Loose LF is allowed (non-standard) and converted to CRLF */
+			case '\n':
+				if ( str_len(str) <= SIEVE_MAX_STRING_LEN )
+					str_append(str, "\r\n");
+				break;
+
+			/* Other characters */
+			default:
+				if ( str_len(str) <= SIEVE_MAX_STRING_LEN )
+					str_append_c(str, sieve_lexer_curchar(scanner));
+			}
+
+			sieve_lexer_shift(scanner);
+		}
+
+		sieve_lexer_shift(scanner);
+
+		if ( str_len(str) > SIEVE_MAX_STRING_LEN ) {
+			sieve_lexer_error(lexer,
+				"quoted string started at line %d is too long "
+				"(longer than %llu bytes)", lexer->token_line,
+				(long long) SIEVE_MAX_STRING_LEN);
+			lexer->token_type = STT_ERROR;
+			return FALSE;
+		}
+
+		lexer->token_type = STT_STRING;
+		return TRUE;
+
+	/* single character tokens */
+	case ']':
+		sieve_lexer_shift(scanner);
+		lexer->token_type = STT_RSQUARE;
+		return TRUE;
+	case '[':
+		sieve_lexer_shift(scanner);
+		lexer->token_type = STT_LSQUARE;
+		return TRUE;
+	case '}':
+		sieve_lexer_shift(scanner);
+		lexer->token_type = STT_RCURLY;
+		return TRUE;
+	case '{':
+		sieve_lexer_shift(scanner);
+		lexer->token_type = STT_LCURLY;
+		return TRUE;
+	case ')':
+		sieve_lexer_shift(scanner);
+		lexer->token_type = STT_RBRACKET;
+		return TRUE;
+	case '(':
+		sieve_lexer_shift(scanner);
+		lexer->token_type = STT_LBRACKET;
+		return TRUE;
+	case ';':
+		sieve_lexer_shift(scanner);
+		lexer->token_type = STT_SEMICOLON;
+		return TRUE;
+	case ',':
+		sieve_lexer_shift(scanner);
+		lexer->token_type = STT_COMMA;
+		return TRUE;
+
+	/* EOF */
+	case -1:
+		if ( !scanner->input->eof ) {
+			lexer->token_type = STT_ERROR;
+			return FALSE;
+		}
+		lexer->token_type = STT_EOF;
+		return TRUE;
+
+	default:
+		/* number */
+		if ( i_isdigit(sieve_lexer_curchar(scanner)) ) {
+			return sieve_lexer_scan_number(scanner);
+
+		/* identifier / tag */
+		} else if ( i_isalpha(sieve_lexer_curchar(scanner)) ||
+			sieve_lexer_curchar(scanner) == '_' ||
+			sieve_lexer_curchar(scanner) == ':' ) {
+
+			enum sieve_token_type type = STT_IDENTIFIER;
+			str_truncate(lexer->token_str_value,0);
+			str = lexer->token_str_value;
+
+			/* If it starts with a ':' it is a tag and not an identifier */
+ 			if ( sieve_lexer_curchar(scanner) == ':' ) {
+				sieve_lexer_shift(scanner); // discard colon
+				type = STT_TAG;
+
+				/* First character still can't be a DIGIT */
+ 				if ( i_isalpha(sieve_lexer_curchar(scanner)) ||
+					sieve_lexer_curchar(scanner) == '_' ) {
+					str_append_c(str, sieve_lexer_curchar(scanner));
+					sieve_lexer_shift(scanner);
+				} else {
+					/* Hmm, otherwise it is just a spurious colon */
+					lexer->token_type = STT_COLON;
+					return TRUE;
+				}
+			} else {
+				str_append_c(str, sieve_lexer_curchar(scanner));
+				sieve_lexer_shift(scanner);
+			}
+
+			/* Scan the rest of the identifier */
+			while ( i_isalnum(sieve_lexer_curchar(scanner)) ||
+				sieve_lexer_curchar(scanner) == '_' ) {
+
+				if ( str_len(str) <= SIEVE_MAX_IDENTIFIER_LEN ) {
+	 				str_append_c(str, sieve_lexer_curchar(scanner));
+				}
+				sieve_lexer_shift(scanner);
+			}
+
+			/* Is this in fact a multiline text string ? */
+			if ( sieve_lexer_curchar(scanner) == ':' &&
+				type == STT_IDENTIFIER && str_len(str) == 4 &&
+				strncasecmp(str_c(str), "text", 4) == 0 ) {
+				sieve_lexer_shift(scanner); // discard colon
+
+				/* Discard SP and HTAB whitespace */
+				while ( sieve_lexer_curchar(scanner) == ' ' ||
+					sieve_lexer_curchar(scanner) == '\t' )
+ 					sieve_lexer_shift(scanner);
+
+				/* Discard hash comment or handle single CRLF */
+				if ( sieve_lexer_curchar(scanner) == '\r' )
+					sieve_lexer_shift(scanner);
+ 				switch ( sieve_lexer_curchar(scanner) ) {
+				case '#':
+					if ( !sieve_lexer_scan_hash_comment(scanner) )
+						return FALSE;
+					if ( scanner->input->eof ) {
+						sieve_lexer_error(lexer,
+							"end of file before end of multi-line string");
+						lexer->token_type = STT_ERROR;
+						return FALSE;
+					} else if ( scanner->input->stream_errno != 0 ) {
+						lexer->token_type = STT_ERROR;
+						return FALSE;
+					}
+					break;
+				case '\n':
+					sieve_lexer_shift(scanner);
+					break;
+				case -1:
+					if ( scanner->input->eof ) {
+						sieve_lexer_error(lexer,
+							"end of file before end of multi-line string");
+					}
+					lexer->token_type = STT_ERROR;
+					return FALSE;
+				default:
+ 					sieve_lexer_error(lexer,
+ 						"invalid character %s after 'text:' in multiline string",
+						_char_sanitize(sieve_lexer_curchar(scanner)));
+					lexer->token_type = STT_ERROR;
+					return FALSE;
+				}
+
+				/* Start over */
+				str_truncate(str, 0);
+
+ 				/* Parse literal lines */
+				while ( TRUE ) {
+					bool cr_shifted = FALSE;
+
+					/* Remove dot-stuffing or detect end of text */
+					if ( sieve_lexer_curchar(scanner) == '.' ) {
+						sieve_lexer_shift(scanner);
+
+						/* Check for CR.. */
+						if ( sieve_lexer_curchar(scanner) == '\r' ) {
+							sieve_lexer_shift(scanner);
+							cr_shifted = TRUE;
+						}
+
+						/* ..LF */
+						if ( sieve_lexer_curchar(scanner) == '\n' ) {
+							sieve_lexer_shift(scanner);
+
+							/* End of multi-line string */
+
+							/* Check whether length limit was violated */
+							if ( str_len(str) > SIEVE_MAX_STRING_LEN ) {
+								sieve_lexer_error(lexer,
+									"multi-line string started at line %d is too long "
+									"(longer than %llu bytes)", lexer->token_line,
+									(long long) SIEVE_MAX_STRING_LEN);
+									lexer->token_type = STT_ERROR;
+									return FALSE;
+							}
+
+							lexer->token_type = STT_STRING;
+							return TRUE;
+						} else if ( cr_shifted ) {
+							/* Seen CR, but no LF */
+							if ( sieve_lexer_curchar(scanner) != -1 ||
+								!scanner->input->eof ) {
+								sieve_lexer_error(lexer,
+									"found stray carriage-return (CR) character "
+									"in multi-line string started at line %d", lexer->token_line);
+							}
+							lexer->token_type = STT_ERROR;
+							return FALSE;
+						}
+
+						/* Handle dot-stuffing */
+						if ( str_len(str) <= SIEVE_MAX_STRING_LEN )
+							str_append_c(str, '.');
+						if ( sieve_lexer_curchar(scanner) == '.' )
+							sieve_lexer_shift(scanner);
+					}
+
+					/* Scan the rest of the line */
+					while ( sieve_lexer_curchar(scanner) != '\n' &&
+						sieve_lexer_curchar(scanner) != '\r' ) {
+
+						switch ( sieve_lexer_curchar(scanner) ) {
+						case -1:
+							if ( scanner->input->eof ) {
+								sieve_lexer_error(lexer,
+									"end of file before end of multi-line string");
+							}
+ 							lexer->token_type = STT_ERROR;
+ 							return FALSE;
+						case '\0':
+							sieve_lexer_error(lexer,
+								"encountered NUL character in quoted string "
+								"started at line %d", lexer->token_line);
+							lexer->token_type = STT_ERROR;
+							return FALSE;
+						default:
+							if ( str_len(str) <= SIEVE_MAX_STRING_LEN )
+  								str_append_c(str, sieve_lexer_curchar(scanner));
+						}
+
+						sieve_lexer_shift(scanner);
+					}
+
+					/* If exited loop due to CR, skip it */
+					if ( sieve_lexer_curchar(scanner) == '\r' )
+						sieve_lexer_shift(scanner);
+
+					/* Now we must see an LF */
+					if ( sieve_lexer_curchar(scanner) != '\n' ) {
+						if ( sieve_lexer_curchar(scanner) != -1 ||
+							!scanner->input->eof ) {
+							sieve_lexer_error(lexer,
+								"found stray carriage-return (CR) character "
+								"in multi-line string started at line %d", lexer->token_line);
+						}
+ 						lexer->token_type = STT_ERROR;
+ 						return FALSE;
+					}
+
+					if ( str_len(str) <= SIEVE_MAX_STRING_LEN )
+						str_append(str, "\r\n");
+
+					sieve_lexer_shift(scanner);
+				}
+
+ 				i_unreached();
+				lexer->token_type = STT_ERROR;
+				return FALSE;
+			}
+
+			if ( str_len(str) > SIEVE_MAX_IDENTIFIER_LEN ) {
+				sieve_lexer_error(lexer,
+					"encountered impossibly long %s%s'",
+					(type == STT_TAG ? "tag identifier ':" : "identifier '"),
+					str_sanitize(str_c(str), SIEVE_MAX_IDENTIFIER_LEN));
+				lexer->token_type = STT_ERROR;
+				return FALSE;
+			}
+
+			lexer->token_type = type;
+			return TRUE;
+		}
+
+		/* Error (unknown character and EOF handled already) */
+		if ( lexer->token_type != STT_GARBAGE )
+			sieve_lexer_error(lexer, "unexpected character(s) starting with %s",
+				_char_sanitize(sieve_lexer_curchar(scanner)));
+		sieve_lexer_shift(scanner);
+		lexer->token_type = STT_GARBAGE;
+		return FALSE;
+	}
+}
+
+void sieve_lexer_skip_token(const struct sieve_lexer *lexer)
+{
+	/* Scan token while skipping whitespace */
+	do {
+		struct sieve_lexical_scanner *scanner = lexer->scanner;
+
+		if ( !sieve_lexer_scan_raw_token(scanner) ) {
+			if ( !scanner->input->eof && scanner->input->stream_errno != 0 ) {
+				sieve_critical(scanner->svinst, scanner->ehandler,
+					sieve_error_script_location(scanner->script, scanner->current_line),
+					"error reading script",
+					"error reading script during lexical analysis: %s",
+					i_stream_get_error(scanner->input));
+			}
+			return;
+		}
+	} while ( lexer->token_type == STT_WHITESPACE );
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-lexer.h
@@ -0,0 +1,123 @@
+#ifndef SIEVE_LEXER_H
+#define SIEVE_LEXER_H
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve-common.h"
+
+enum sieve_token_type {
+	STT_NONE,
+	STT_WHITESPACE,
+	STT_EOF,
+
+	STT_NUMBER,
+	STT_IDENTIFIER,
+	STT_TAG,
+	STT_STRING,
+
+	STT_RBRACKET,
+	STT_LBRACKET,
+	STT_RCURLY,
+	STT_LCURLY,
+	STT_RSQUARE,
+	STT_LSQUARE,
+	STT_SEMICOLON,
+	STT_COMMA,
+
+	/* These are currently not used in the lexical specification, but a token
+	 * is assigned to these to generate proper error messages (these are
+	 * technically not garbage and possibly part of mistyped but otherwise
+	 * valid tokens).
+	 */
+	STT_SLASH,
+	STT_COLON,
+
+	/* Error tokens */
+	STT_GARBAGE, /* Error reporting deferred to parser */
+	STT_ERROR    /* Lexer is responsible for error, parser won't report additional
+	                errors */
+};
+
+/*
+ * Lexer object;
+ */
+
+struct sieve_lexical_scanner;
+
+struct sieve_lexer {
+	struct sieve_lexical_scanner *scanner;
+
+	enum sieve_token_type token_type;
+	string_t *token_str_value;
+	int token_int_value;
+
+	int token_line;
+};
+
+const struct sieve_lexer *sieve_lexer_create
+	(struct sieve_script *script, struct sieve_error_handler *ehandler,
+		enum sieve_error *error_r);
+void sieve_lexer_free(const struct sieve_lexer **lexer);
+
+/*
+ * Scanning
+ */
+
+void sieve_lexer_skip_token(const struct sieve_lexer *lexer);
+
+/*
+ * Token access
+ */
+
+static inline enum sieve_token_type sieve_lexer_token_type
+(const struct sieve_lexer *lexer)
+{
+	return lexer->token_type;
+}
+
+static inline const string_t *sieve_lexer_token_str
+(const struct sieve_lexer *lexer)
+{
+	i_assert(	lexer->token_type == STT_STRING );
+
+	return lexer->token_str_value;
+}
+
+static inline const char *sieve_lexer_token_ident
+(const struct sieve_lexer *lexer)
+{
+	i_assert(
+		lexer->token_type == STT_TAG ||
+		lexer->token_type == STT_IDENTIFIER);
+
+	return str_c(lexer->token_str_value);
+}
+
+static inline int sieve_lexer_token_int
+(const struct sieve_lexer *lexer)
+{
+	i_assert(lexer->token_type == STT_NUMBER);
+
+	return lexer->token_int_value;
+}
+
+static inline bool sieve_lexer_eof
+(const struct sieve_lexer *lexer)
+{
+	return lexer->token_type == STT_EOF;
+}
+
+static inline int sieve_lexer_token_line
+(const struct sieve_lexer *lexer)
+{
+	return lexer->token_line;
+}
+
+const char *sieve_lexer_token_description
+	(const struct sieve_lexer *lexer);
+
+void sieve_lexer_token_print
+	(const struct sieve_lexer *lexer);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-limits.h
@@ -0,0 +1,42 @@
+#ifndef SIEVE_LIMITS_H
+#define SIEVE_LIMITS_H
+
+/*
+ * Scripts
+ */
+
+#define SIEVE_MAX_SCRIPT_NAME_LEN      256
+
+#define SIEVE_DEFAULT_MAX_SCRIPT_SIZE  (1 << 20)
+
+#define SIEVE_MAX_LOOP_DEPTH           4
+
+/*
+ * Lexer
+ */
+
+#define SIEVE_MAX_STRING_LEN           (1 << 20)
+#define SIEVE_MAX_IDENTIFIER_LEN       32
+
+/*
+ * AST
+ */
+
+#define SIEVE_MAX_COMMAND_ARGUMENTS    32
+#define SIEVE_MAX_BLOCK_NESTING        32
+#define SIEVE_MAX_TEST_NESTING         32
+
+/*
+ * Runtime
+ */
+
+#define SIEVE_MAX_MATCH_VALUES         32
+
+/*
+ * Actions
+ */
+
+#define SIEVE_DEFAULT_MAX_ACTIONS      32
+#define SIEVE_DEFAULT_MAX_REDIRECTS    4
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-match-types.c
@@ -0,0 +1,569 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "compat.h"
+#include "mempool.h"
+#include "hash.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-limits.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-comparators.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "sieve-match-types.h"
+
+#include <string.h>
+
+/*
+ * Types
+ */
+
+struct sieve_match_values {
+	pool_t pool;
+	ARRAY(string_t *) values;
+	unsigned count;
+};
+
+/*
+ * Default match types
+ */
+
+const struct sieve_match_type_def *sieve_core_match_types[] = {
+	&is_match_type, &contains_match_type, &matches_match_type
+};
+
+const unsigned int sieve_core_match_types_count =
+	N_ELEMENTS(sieve_core_match_types);
+
+/*
+ * Match-type 'extension'
+ */
+
+static bool mtch_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def match_type_extension = {
+	.name = "@match-types",
+	.validator_load = mtch_validator_load
+};
+
+/*
+ * Validator context:
+ *   name-based match-type registry.
+ */
+
+static struct sieve_validator_object_registry *_get_object_registry
+(struct sieve_validator *valdtr)
+{
+	struct sieve_instance *svinst;
+	const struct sieve_extension *mcht_ext;
+
+	svinst = sieve_validator_svinst(valdtr);
+	mcht_ext = sieve_get_match_type_extension(svinst);
+	return sieve_validator_object_registry_get(valdtr, mcht_ext);
+}
+
+void sieve_match_type_register
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	const struct sieve_match_type_def *mcht_def)
+{
+	struct sieve_validator_object_registry *regs = _get_object_registry(valdtr);
+
+	sieve_validator_object_registry_add(regs, ext, &mcht_def->obj_def);
+}
+
+static bool sieve_match_type_exists
+(struct sieve_validator *valdtr, const char *identifier)
+{
+	struct sieve_validator_object_registry *regs = _get_object_registry(valdtr);
+
+	return sieve_validator_object_registry_find(regs, identifier, NULL);
+}
+
+static const struct sieve_match_type *sieve_match_type_create_instance
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+	const char *identifier)
+{
+	struct sieve_validator_object_registry *regs = _get_object_registry(valdtr);
+	struct sieve_object object;
+	struct sieve_match_type *mcht;
+
+	if ( !sieve_validator_object_registry_find(regs, identifier, &object) )
+		return NULL;
+
+	mcht = p_new(sieve_command_pool(cmd), struct sieve_match_type, 1);
+	mcht->object = object;
+	mcht->def = (const struct sieve_match_type_def *) object.def;
+
+  return mcht;
+}
+
+bool mtch_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	struct sieve_validator_object_registry *regs =
+		sieve_validator_object_registry_init(valdtr, ext);
+	unsigned int i;
+
+	/* Register core match-types */
+	for ( i = 0; i < sieve_core_match_types_count; i++ ) {
+		sieve_validator_object_registry_add
+			(regs, NULL, &(sieve_core_match_types[i]->obj_def));
+	}
+
+	return TRUE;
+}
+
+/*
+ * Interpreter context
+ */
+
+struct mtch_interpreter_context {
+	struct sieve_match_values *match_values;
+	bool match_values_enabled;
+};
+
+static void mtch_interpreter_free
+(const struct sieve_extension *ext ATTR_UNUSED,
+	struct sieve_interpreter *interp ATTR_UNUSED, void *context)
+{
+	struct mtch_interpreter_context *mctx =
+		(struct mtch_interpreter_context *) context;
+
+	if ( mctx->match_values != NULL ) {
+		pool_unref(&mctx->match_values->pool);
+	}
+}
+
+struct sieve_interpreter_extension
+mtch_interpreter_extension = {
+	.ext_def = &match_type_extension,
+	.free = mtch_interpreter_free
+};
+
+static inline struct mtch_interpreter_context *get_interpreter_context
+(struct sieve_interpreter *interp, bool create)
+{
+	struct sieve_instance *svinst;
+	const struct sieve_extension *mcht_ext;
+	struct mtch_interpreter_context *ctx;
+
+	svinst = sieve_interpreter_svinst(interp);
+	mcht_ext = sieve_get_match_type_extension(svinst);
+
+	ctx = (struct mtch_interpreter_context *)
+		sieve_interpreter_extension_get_context(interp, mcht_ext);
+
+	if ( ctx == NULL && create ) {
+		pool_t pool = sieve_interpreter_pool(interp);
+		ctx = p_new(pool, struct mtch_interpreter_context, 1);
+
+		sieve_interpreter_extension_register
+			(interp, mcht_ext, &mtch_interpreter_extension, (void *) ctx);
+	}
+
+	return ctx;
+}
+
+/*
+ * Match values
+ */
+
+bool sieve_match_values_set_enabled
+(const struct sieve_runtime_env *renv, bool enable)
+{
+	struct mtch_interpreter_context *ctx =
+		get_interpreter_context(renv->interp, enable);
+
+	if ( ctx != NULL ) {
+		bool previous = ctx->match_values_enabled;
+
+		ctx->match_values_enabled = enable;
+		return previous;
+	}
+
+	return FALSE;
+}
+
+bool sieve_match_values_are_enabled
+(const struct sieve_runtime_env *renv)
+{
+	struct mtch_interpreter_context *ctx =
+		get_interpreter_context(renv->interp, FALSE);
+
+	return ( ctx == NULL ? FALSE : ctx->match_values_enabled );
+}
+
+struct sieve_match_values *sieve_match_values_start
+(const struct sieve_runtime_env *renv)
+{
+	struct mtch_interpreter_context *ctx =
+		get_interpreter_context(renv->interp, FALSE);
+	struct sieve_match_values *match_values;
+
+	if ( ctx == NULL || !ctx->match_values_enabled )
+		return NULL;
+
+	pool_t pool = pool_alloconly_create("sieve_match_values", 1024);
+
+	match_values = p_new(pool, struct sieve_match_values, 1);
+	match_values->pool = pool;
+	match_values->count = 0;
+
+	p_array_init(&match_values->values, pool, 4);
+
+	return match_values;
+}
+
+static string_t *sieve_match_values_add_entry
+(struct sieve_match_values *mvalues)
+{
+	string_t *entry;
+
+	if ( mvalues == NULL ) return NULL;
+
+	if ( mvalues->count >= SIEVE_MAX_MATCH_VALUES ) return NULL;
+
+	if ( mvalues->count >= array_count(&mvalues->values) ) {
+		entry = str_new(mvalues->pool, 64);
+		array_append(&mvalues->values, &entry, 1);	} else {
+		string_t * const *ep = array_idx(&mvalues->values, mvalues->count);
+		entry = *ep;
+		str_truncate(entry, 0);
+	}
+
+	mvalues->count++;
+
+	return entry;
+}
+
+void sieve_match_values_set
+(struct sieve_match_values *mvalues, unsigned int index, string_t *value)
+{
+	if ( mvalues != NULL && index < array_count(&mvalues->values) ) {
+		string_t * const *ep = array_idx(&mvalues->values, index);
+    	string_t *entry = *ep;
+
+	    if ( entry != NULL && value != NULL ) {
+			str_truncate(entry, 0);
+        	str_append_str(entry, value);
+		}
+	}
+}
+
+void sieve_match_values_add
+(struct sieve_match_values *mvalues, string_t *value)
+{
+	string_t *entry = sieve_match_values_add_entry(mvalues);
+
+	if ( entry != NULL && value != NULL )
+		str_append_str(entry, value);
+}
+
+void sieve_match_values_add_char
+(struct sieve_match_values *mvalues, char c)
+{
+	string_t *entry = sieve_match_values_add_entry(mvalues);
+
+	if ( entry != NULL )
+		str_append_c(entry, c);
+}
+
+void sieve_match_values_skip
+(struct sieve_match_values *mvalues, int num)
+{
+	int i;
+
+	for ( i = 0; i < num; i++ )
+		(void) sieve_match_values_add_entry(mvalues);
+}
+
+void sieve_match_values_commit
+(const struct sieve_runtime_env *renv, struct sieve_match_values **mvalues)
+{
+	struct mtch_interpreter_context *ctx;
+
+	if ( (*mvalues) == NULL ) return;
+
+	ctx = get_interpreter_context(renv->interp, FALSE);
+	if ( ctx == NULL || !ctx->match_values_enabled )
+		return;
+
+	if ( ctx->match_values != NULL ) {
+		pool_unref(&ctx->match_values->pool);
+		ctx->match_values = NULL;
+	}
+
+	ctx->match_values = *mvalues;
+	*mvalues = NULL;
+}
+
+void sieve_match_values_abort
+(struct sieve_match_values **mvalues)
+{
+	if ( (*mvalues) == NULL ) return;
+
+	pool_unref(&(*mvalues)->pool);
+	*mvalues = NULL;
+}
+
+void sieve_match_values_get
+(const struct sieve_runtime_env *renv, unsigned int index, string_t **value_r)
+{
+	struct mtch_interpreter_context *ctx =
+		get_interpreter_context(renv->interp, FALSE);
+	struct sieve_match_values *mvalues;
+
+	if ( ctx == NULL || ctx->match_values == NULL ) {
+		*value_r = NULL;
+		return;
+	}
+
+	mvalues = ctx->match_values;
+	if ( index < array_count(&mvalues->values) && index < mvalues->count ) {
+		string_t * const *entry = array_idx(&mvalues->values, index);
+
+		*value_r = *entry;
+		return;
+	}
+
+	*value_r = NULL;
+}
+
+/*
+ * Match-type tagged argument
+ */
+
+/* Forward declarations */
+
+static bool tag_match_type_is_instance_of
+	(struct sieve_validator *valdtr, struct sieve_command *cmd,
+		const struct sieve_extension *ext, const char *identifier, void **data);
+static bool tag_match_type_validate
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+static bool tag_match_type_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+		struct sieve_command *cmd);
+
+/* Argument object */
+
+const struct sieve_argument_def match_type_tag = {
+	.identifier = "MATCH-TYPE",
+	.is_instance_of = tag_match_type_is_instance_of,
+	.validate = tag_match_type_validate,
+	.generate = tag_match_type_generate
+};
+
+/* Argument implementation */
+
+static bool tag_match_type_is_instance_of
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+	const struct sieve_extension *ext ATTR_UNUSED, const char *identifier,
+	void **data)
+{
+	const struct sieve_match_type *mcht;
+
+	if ( data == NULL )
+		return sieve_match_type_exists(valdtr, identifier);
+
+	if ( (mcht=sieve_match_type_create_instance
+		(valdtr, cmd, identifier)) == NULL )
+		return FALSE;
+
+	*data = (void *) mcht;
+	return TRUE;
+}
+
+static bool tag_match_type_validate
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd ATTR_UNUSED)
+{
+	const struct sieve_match_type *mcht =
+		(const struct sieve_match_type *) (*arg)->argument->data;
+	struct sieve_match_type_context *mtctx;
+
+	mtctx = p_new(sieve_command_pool(cmd), struct sieve_match_type_context, 1);
+	mtctx->match_type = mcht;
+	mtctx->argument = *arg;
+	mtctx->comparator = NULL; /* Can be filled in later */
+
+	(*arg)->argument->data = mtctx;
+
+	/* Syntax:
+	 *   ":is" / ":contains" / ":matches" (subject to extension)
+	 */
+
+	/* Skip tag */
+	*arg = sieve_ast_argument_next(*arg);
+
+	/* Check whether this match type requires additional validation.
+	 * Additional validation can override the match type recorded in the context
+	 * for later code generation.
+	 */
+	if ( mcht->def != NULL && mcht->def->validate != NULL ) {
+		return mcht->def->validate(valdtr, arg, mtctx);
+	}
+
+	return TRUE;
+}
+
+static bool tag_match_type_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+	struct sieve_command *cmd ATTR_UNUSED)
+{
+	struct sieve_match_type_context *mtctx =
+		(struct sieve_match_type_context *) arg->argument->data;
+
+	(void) sieve_opr_match_type_emit(cgenv->sblock, mtctx->match_type);
+
+	return TRUE;
+}
+
+void sieve_match_types_link_tags
+(struct sieve_validator *valdtr,
+	struct sieve_command_registration *cmd_reg, int id_code)
+{
+	struct sieve_instance *svinst;
+	const struct sieve_extension *mcht_ext;
+
+	svinst = sieve_validator_svinst(valdtr);
+	mcht_ext = sieve_get_comparator_extension(svinst);
+
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, mcht_ext, &match_type_tag, id_code);
+}
+
+/*
+ * Validation
+ */
+
+bool sieve_match_type_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+	struct sieve_ast_argument *key_arg,
+	const struct sieve_match_type *mcht_default,
+	const struct sieve_comparator *cmp_default)
+{
+	struct sieve_ast_argument *arg = sieve_command_first_argument(cmd);
+	struct sieve_ast_argument *mt_arg = NULL;
+	struct sieve_match_type_context *mtctx;
+	const struct sieve_match_type *mcht = NULL;
+	const struct sieve_comparator *cmp = NULL;
+
+	/* Find match type and comparator among the arguments */
+	while ( arg != NULL && arg != cmd->first_positional ) {
+		if ( sieve_argument_is_comparator(arg) ) {
+			cmp = sieve_comparator_tag_get(arg);
+			if ( mt_arg != NULL ) break;
+		}
+
+		if ( sieve_argument_is_match_type(arg) ) {
+			mt_arg = arg;
+			if ( cmp != NULL ) break;
+		}
+		arg = sieve_ast_argument_next(arg);
+	}
+
+	/* Verify using the default comparator if none is specified explicitly */
+	if ( cmp == NULL ) {
+		cmp = sieve_comparator_copy(sieve_command_pool(cmd), cmp_default);
+	}
+
+	/* Verify the default match type if none is specified explicitly */
+	if ( mt_arg == NULL || mt_arg->argument == NULL ||
+		mt_arg->argument->data == NULL ) {		
+		mcht = sieve_match_type_copy(sieve_command_pool(cmd), mcht_default);
+		mtctx = t_new(struct sieve_match_type_context, 1);
+		mtctx->command = cmd;
+		mtctx->match_type = mcht;
+	} else {
+		mtctx = (struct sieve_match_type_context *) mt_arg->argument->data;
+		mcht = mtctx->match_type;
+	}
+	mtctx->comparator = cmp;
+
+	/* Check whether this match type requires additional validation.
+	 * Additional validation can override the match type recorded in the context
+	 * for later code generation.
+	 */
+	if ( mcht != NULL && mcht->def != NULL &&
+		mcht->def->validate_context != NULL ) {
+		return mcht->def->validate_context(valdtr, mt_arg, mtctx, key_arg);
+	}
+
+	return TRUE;
+}
+
+void sieve_match_type_arguments_remove
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *arg = sieve_command_first_argument(cmd);
+
+	/* Remove any comparator and match type arguments */
+	while ( arg != NULL && arg != cmd->first_positional ) {
+		if ( sieve_argument_is_comparator(arg) ) {
+			arg = sieve_ast_arguments_detach(arg, 1);
+			continue;
+		}
+
+		if ( sieve_argument_is_match_type(arg) ) {
+			arg = sieve_ast_arguments_detach(arg, 1);
+			continue;
+		}
+
+		arg = sieve_ast_argument_next(arg);
+	}
+}
+
+
+/*
+ * Match-type operand
+ */
+
+const struct sieve_operand_class sieve_match_type_operand_class =
+	{ "match type" };
+
+static const struct sieve_extension_objects core_match_types =
+	SIEVE_EXT_DEFINE_MATCH_TYPES(sieve_core_match_types);
+
+const struct sieve_operand_def match_type_operand = {
+	.name = "match-type",
+	.code = SIEVE_OPERAND_MATCH_TYPE,
+	.class = &sieve_match_type_operand_class,
+	.interface = &core_match_types
+};
+
+/*
+ * Common validation implementation
+ */
+
+bool sieve_match_substring_validate_context
+(struct sieve_validator *valdtr, struct sieve_ast_argument *arg,
+	struct sieve_match_type_context *ctx,
+	struct sieve_ast_argument *key_arg ATTR_UNUSED)
+{
+	const struct sieve_comparator *cmp = ctx->comparator;
+
+	if ( cmp == NULL || cmp->def == NULL )
+		return TRUE;
+
+	if ( (cmp->def->flags & SIEVE_COMPARATOR_FLAG_SUBSTRING_MATCH) == 0 ) {
+		sieve_argument_validate_error(valdtr, arg,
+			"the specified %s comparator does not support "
+			"sub-string matching as required by the :%s match type",
+			cmp->object.def->identifier, ctx->match_type->object.def->identifier );
+
+		return FALSE;
+	}
+
+	return TRUE;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-match-types.h
@@ -0,0 +1,233 @@
+#ifndef SIEVE_MATCH_TYPES_H
+#define SIEVE_MATCH_TYPES_H
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-objects.h"
+
+/*
+ * Types
+ */
+
+struct sieve_match_type_context;
+
+/*
+ * Core match types
+ */
+
+enum sieve_match_type_code {
+	SIEVE_MATCH_TYPE_IS,
+	SIEVE_MATCH_TYPE_CONTAINS,
+	SIEVE_MATCH_TYPE_MATCHES,
+	SIEVE_MATCH_TYPE_CUSTOM
+};
+
+extern const struct sieve_match_type_def is_match_type;
+extern const struct sieve_match_type_def contains_match_type;
+extern const struct sieve_match_type_def matches_match_type;
+
+/*
+ * Match type definition
+ */
+
+struct sieve_match_type_def {
+	struct sieve_object_def obj_def;
+
+	bool (*validate)
+		(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+			struct sieve_match_type_context *ctx);
+	bool (*validate_context)
+		(struct sieve_validator *valdtr, struct sieve_ast_argument *arg,
+			struct sieve_match_type_context *ctx, struct sieve_ast_argument *key_arg);
+
+	/*
+	 * Matching
+ 	 */
+
+	/* Custom implementation */
+
+	int (*match)
+		(struct sieve_match_context *mctx, struct sieve_stringlist *value_list,
+			struct sieve_stringlist *key_list);
+
+	/* Default match loop */
+
+	void (*match_init)(struct sieve_match_context *mctx);
+
+	int (*match_keys)
+		(struct sieve_match_context *mctx, const char *val, size_t val_size,
+			struct sieve_stringlist *key_list);
+	int (*match_key)
+		(struct sieve_match_context *mctx, const char *val, size_t val_size,
+			const char *key, size_t key_size);
+
+	void (*match_deinit)(struct sieve_match_context *mctx);
+};
+
+/*
+ * Match type instance
+ */
+
+struct sieve_match_type {
+	struct sieve_object object;
+
+	const struct sieve_match_type_def *def;
+};
+
+#define SIEVE_MATCH_TYPE_DEFAULT(definition) \
+	{ SIEVE_OBJECT_DEFAULT(definition), &(definition) }
+
+#define sieve_match_type_name(mcht) \
+	( (mcht)->object.def->identifier )
+#define sieve_match_type_is(mcht, definition) \
+	( (mcht)->def == &(definition) )
+
+static inline const struct sieve_match_type *sieve_match_type_copy
+(pool_t pool, const struct sieve_match_type *cmp_orig)
+{
+	struct sieve_match_type *cmp = p_new(pool, struct sieve_match_type, 1);
+
+	*cmp = *cmp_orig;
+
+	return cmp;
+}
+
+/*
+ * Match type context
+ */
+
+struct sieve_match_type_context {
+	struct sieve_command *command;
+	struct sieve_ast_argument *argument;
+
+	const struct sieve_match_type *match_type;
+
+	/* Only filled in when match_type->validate_context() is called */
+	const struct sieve_comparator *comparator;
+
+	/* Context data could be used in the future to pass data between validator and
+	 * generator in match types that use extra parameters. Currently not
+	 * necessary, not even for the relational extension.
+	 */
+	void *ctx_data;
+};
+
+/*
+ * Match type registration
+ */
+
+void sieve_match_type_register
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		const struct sieve_match_type_def *mcht);
+
+/*
+ * Match values
+ */
+
+struct sieve_match_values;
+
+bool sieve_match_values_set_enabled
+	(const struct sieve_runtime_env *renv, bool enable);
+bool sieve_match_values_are_enabled
+	(const struct sieve_runtime_env *renv);
+
+struct sieve_match_values *sieve_match_values_start
+	(const struct sieve_runtime_env *renv);
+void sieve_match_values_set
+	(struct sieve_match_values *mvalues, unsigned int index, string_t *value);
+void sieve_match_values_add
+	(struct sieve_match_values *mvalues, string_t *value);
+void sieve_match_values_add_char
+	(struct sieve_match_values *mvalues, char c);
+void sieve_match_values_skip
+	(struct sieve_match_values *mvalues, int num);
+
+void sieve_match_values_commit
+	(const struct sieve_runtime_env *renv, struct sieve_match_values **mvalues);
+void sieve_match_values_abort
+	(struct sieve_match_values **mvalues);
+
+void sieve_match_values_get
+	(const struct sieve_runtime_env *renv, unsigned int index, string_t **value_r);
+
+/*
+ * Match type tagged argument
+ */
+
+extern const struct sieve_argument_def match_type_tag;
+
+static inline bool sieve_argument_is_match_type
+	(struct sieve_ast_argument *arg)
+{
+	return ( arg->argument->def == &match_type_tag );
+}
+
+void sieve_match_types_link_tags
+	(struct sieve_validator *valdtr,
+		struct sieve_command_registration *cmd_reg, int id_code);
+
+/*
+ * Validation
+ */
+
+bool sieve_match_type_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd,
+		struct sieve_ast_argument *key_arg,
+		const struct sieve_match_type *mcht_default,
+		const struct sieve_comparator *cmp_default);
+
+void sieve_match_type_arguments_remove
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+
+/*
+ * Match type operand
+ */
+
+extern const struct sieve_operand_def match_type_operand;
+extern const struct sieve_operand_class sieve_match_type_operand_class;
+
+#define SIEVE_EXT_DEFINE_MATCH_TYPE(OP) SIEVE_EXT_DEFINE_OBJECT(OP)
+#define SIEVE_EXT_DEFINE_MATCH_TYPES(OPS) SIEVE_EXT_DEFINE_OBJECTS(OPS)
+
+static inline bool sieve_operand_is_match_type
+(const struct sieve_operand *operand)
+{
+	return ( operand != NULL && operand->def != NULL &&
+		operand->def->class == &sieve_match_type_operand_class );
+}
+
+static inline void sieve_opr_match_type_emit
+(struct sieve_binary_block *sblock, const struct sieve_match_type *mcht)
+{
+	sieve_opr_object_emit(sblock, mcht->object.ext, mcht->object.def);
+}
+
+static inline bool sieve_opr_match_type_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	return sieve_opr_object_dump
+		(denv, &sieve_match_type_operand_class, address, NULL);
+}
+
+static inline int sieve_opr_match_type_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+	struct sieve_match_type *mcht)
+{
+	if ( !sieve_opr_object_read
+		(renv, &sieve_match_type_operand_class, address, &mcht->object) )
+		return SIEVE_EXEC_BIN_CORRUPT;
+
+	mcht->def = (const struct sieve_match_type_def *) mcht->object.def;
+	return SIEVE_EXEC_OK;
+}
+
+/* Common validation implementation */
+
+bool sieve_match_substring_validate_context
+	(struct sieve_validator *valdtr, struct sieve_ast_argument *arg,
+		struct sieve_match_type_context *ctx,
+		struct sieve_ast_argument *key_arg);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-match.c
@@ -0,0 +1,293 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "mempool.h"
+#include "hash.h"
+#include "array.h"
+#include "str-sanitize.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-runtime-trace.h"
+
+#include "sieve-match.h"
+
+/*
+ * Matching implementation
+ */
+
+struct sieve_match_context *sieve_match_begin
+(const struct sieve_runtime_env *renv,
+	const struct sieve_match_type *mcht,
+	const struct sieve_comparator *cmp)
+{
+	struct sieve_match_context *mctx;
+	pool_t pool;
+
+	/* Reject unimplemented match-type */
+	if ( mcht->def == NULL || (mcht->def->match == NULL &&
+			mcht->def->match_keys == NULL && mcht->def->match_key == NULL) )
+			return NULL;
+
+	/* Create match context */
+	pool = pool_alloconly_create("sieve_match_context", 1024);
+	mctx = p_new(pool, struct sieve_match_context, 1);
+	mctx->pool = pool;
+	mctx->runenv = renv;
+	mctx->match_type = mcht;
+	mctx->comparator = cmp;
+	mctx->exec_status = SIEVE_EXEC_OK;
+	mctx->trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING);
+
+	/* Trace */
+	if ( mctx->trace ) {
+		sieve_runtime_trace_descend(renv);
+		sieve_runtime_trace(renv, 0,
+			"starting `:%s' match with `%s' comparator:",
+			sieve_match_type_name(mcht), sieve_comparator_name(cmp));
+	}
+
+	/* Initialize match type */
+	if ( mcht->def != NULL && mcht->def->match_init != NULL ) {
+		mcht->def->match_init(mctx);
+	}
+
+	return mctx;
+}
+
+int sieve_match_value
+(struct sieve_match_context *mctx, const char *value, size_t value_size,
+	struct sieve_stringlist *key_list)
+{
+	const struct sieve_match_type *mcht = mctx->match_type;
+	const struct sieve_runtime_env *renv = mctx->runenv;
+	int match, ret;
+
+	if ( mctx->trace ) {
+		sieve_runtime_trace(renv, 0,
+			"matching value `%s'", str_sanitize(value, 80));
+	}
+
+	/* Match to key values */
+
+	sieve_stringlist_reset(key_list);
+
+	if ( mctx->trace )
+		sieve_stringlist_set_trace(key_list, TRUE);
+
+	sieve_runtime_trace_descend(renv);
+
+	if ( mcht->def->match_keys != NULL ) {
+		/* Call match-type's own key match handler */
+		match = mcht->def->match_keys(mctx, value, value_size, key_list);
+	} else {
+		string_t *key_item = NULL;
+
+		/* Default key match loop */
+		match = 0;
+		while ( match == 0 &&
+			(ret=sieve_stringlist_next_item(key_list, &key_item)) > 0 ) {
+			T_BEGIN {
+				match = mcht->def->match_key
+					(mctx, value, value_size, str_c(key_item), str_len(key_item));
+
+				if ( mctx->trace ) {
+					sieve_runtime_trace(renv, 0,
+						"with key `%s' => %d", str_sanitize(str_c(key_item), 80),
+						match);
+				}
+			} T_END;
+		}
+
+		if ( ret < 0 ) {
+			mctx->exec_status = key_list->exec_status;
+			match = -1;
+		}
+	}
+
+	sieve_runtime_trace_ascend(renv);
+
+	if ( mctx->match_status < 0 || match < 0 )
+		mctx->match_status = -1;
+	else
+		mctx->match_status =
+			( mctx->match_status > match ? mctx->match_status : match );
+	return match;
+}
+
+int sieve_match_end(struct sieve_match_context **mctx, int *exec_status)
+{
+	const struct sieve_match_type *mcht = (*mctx)->match_type;
+	const struct sieve_runtime_env *renv = (*mctx)->runenv;
+	int match = (*mctx)->match_status;
+
+	if ( mcht->def != NULL && mcht->def->match_deinit != NULL )
+		mcht->def->match_deinit(*mctx);
+
+	if ( exec_status != NULL )
+		*exec_status = (*mctx)->exec_status;
+
+	pool_unref(&(*mctx)->pool);
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
+		"finishing match with result: %s",
+		( match > 0 ? "matched" : ( match < 0 ? "error" : "not matched" ) ));
+	sieve_runtime_trace_ascend(renv);
+
+	return match;
+}
+
+int sieve_match
+(const struct sieve_runtime_env *renv,
+	const struct sieve_match_type *mcht,
+	const struct sieve_comparator *cmp,
+	struct sieve_stringlist *value_list,
+	struct sieve_stringlist *key_list,
+	int *exec_status)
+{
+	struct sieve_match_context *mctx;
+	string_t *value_item = NULL;
+	int match, ret;
+
+	if ( (mctx=sieve_match_begin(renv, mcht, cmp)) == NULL )
+		return 0;
+
+	/* Match value to keys */
+
+	sieve_stringlist_reset(value_list);
+
+	if ( mctx->trace )
+		sieve_stringlist_set_trace(value_list, TRUE);
+
+	if ( mcht->def->match != NULL ) {
+		/* Call match-type's match handler */
+		match = mctx->match_status =
+			mcht->def->match(mctx, value_list, key_list);
+
+	} else {
+		/* Default value match loop */
+
+		match = 0;
+		while ( match == 0 &&
+			(ret=sieve_stringlist_next_item(value_list, &value_item)) > 0 ) {
+
+			match = sieve_match_value
+				(mctx, str_c(value_item), str_len(value_item), key_list);
+		}
+
+		if ( ret < 0 ) {
+			mctx->exec_status = value_list->exec_status;
+			match = -1;
+		}
+	}
+
+	(void)sieve_match_end(&mctx, exec_status);
+	return match;
+}
+
+/*
+ * Reading match operands
+ */
+
+int sieve_match_opr_optional_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address, int *opt_code)
+{
+	int _opt_code = 0;
+	bool final = FALSE, opok = TRUE;
+
+	if ( opt_code == NULL ) {
+		opt_code = &_opt_code;
+		final = TRUE;
+	}
+
+	while ( opok ) {
+		int opt;
+
+		if ( (opt=sieve_opr_optional_dump(denv, address, opt_code)) <= 0 )
+			return opt;
+
+		switch ( *opt_code ) {
+		case SIEVE_MATCH_OPT_COMPARATOR:
+			opok = sieve_opr_comparator_dump(denv, address);
+			break;
+		case SIEVE_MATCH_OPT_MATCH_TYPE:
+			opok = sieve_opr_match_type_dump(denv, address);
+			break;
+		default:
+			return ( final ? -1 : 1 );
+		}
+	}
+
+	return -1;
+}
+
+int sieve_match_opr_optional_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address, int *opt_code,
+	int *exec_status, struct sieve_comparator *cmp, struct sieve_match_type *mcht)
+{
+	int _opt_code = 0;
+	bool final = FALSE;
+	int status = SIEVE_EXEC_OK;
+
+	if ( opt_code == NULL ) {
+		opt_code = &_opt_code;
+		final = TRUE;
+	}
+
+	if ( exec_status != NULL )
+		*exec_status = SIEVE_EXEC_OK;
+
+	while ( status == SIEVE_EXEC_OK ) {
+		int opt;
+
+		if ( (opt=sieve_opr_optional_read(renv, address, opt_code)) <= 0 ){
+			if ( opt < 0 && exec_status != NULL )
+				*exec_status = SIEVE_EXEC_BIN_CORRUPT;
+			return opt;
+		}
+
+		switch ( *opt_code ) {
+		case SIEVE_MATCH_OPT_COMPARATOR:
+			if (cmp == NULL) {
+				sieve_runtime_trace_error(renv, "unexpected comparator operand");
+				if ( exec_status != NULL )
+					*exec_status = SIEVE_EXEC_BIN_CORRUPT;
+				return -1;
+			}
+			status = sieve_opr_comparator_read(renv, address, cmp);
+			break;
+		case SIEVE_MATCH_OPT_MATCH_TYPE:
+			if (mcht == NULL) {
+				sieve_runtime_trace_error(renv, "unexpected match-type operand");
+				if ( exec_status != NULL )
+					*exec_status = SIEVE_EXEC_BIN_CORRUPT;
+				return -1;
+			}
+			status = sieve_opr_match_type_read(renv, address, mcht);
+			break;
+		default:
+			if ( final ) {
+				sieve_runtime_trace_error(renv, "invalid optional operand");
+				if ( exec_status != NULL )
+					*exec_status = SIEVE_EXEC_BIN_CORRUPT;
+				return -1;
+			}
+			return 1;
+		}
+	}
+
+	if ( exec_status != NULL )
+		*exec_status = status;
+	return -1;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-match.h
@@ -0,0 +1,68 @@
+#ifndef SIEVE_MATCH_H
+#define SIEVE_MATCH_H
+
+#include "sieve-common.h"
+
+/*
+ * Matching context
+ */
+
+struct sieve_match_context {
+	pool_t pool;
+
+	const struct sieve_runtime_env *runenv;
+
+	const struct sieve_match_type *match_type;
+	const struct sieve_comparator *comparator;
+
+	void *data;
+
+	int match_status;
+	int exec_status;
+
+	bool trace:1;
+};
+
+/*
+ * Matching implementation
+ */
+
+/* Manual value iteration (for when multiple matches are allowed) */
+struct sieve_match_context *sieve_match_begin
+	(const struct sieve_runtime_env *renv,
+		const struct sieve_match_type *mcht,
+		const struct sieve_comparator *cmp);
+int sieve_match_value
+	(struct sieve_match_context *mctx, const char *value, size_t value_size,
+		struct sieve_stringlist *key_list);
+int sieve_match_end(struct sieve_match_context **mctx, int *exec_status);
+
+/* Default matching operation */
+int sieve_match
+	(const struct sieve_runtime_env *renv,
+		const struct sieve_match_type *mcht,
+		const struct sieve_comparator *cmp,
+		struct sieve_stringlist *value_list,
+		struct sieve_stringlist *key_list,
+		int *exec_status);
+
+/*
+ * Read matching operands
+ */
+
+enum sieve_match_opt_operand {
+	SIEVE_MATCH_OPT_END,
+	SIEVE_MATCH_OPT_COMPARATOR,
+	SIEVE_MATCH_OPT_MATCH_TYPE,
+	SIEVE_MATCH_OPT_LAST
+};
+
+int sieve_match_opr_optional_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address, int *opt_code);
+
+int sieve_match_opr_optional_read
+	(const struct sieve_runtime_env *renv, sieve_size_t *address, int *opt_code,
+		int *exec_status, struct sieve_comparator *cmp,
+		struct sieve_match_type *mcht);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-message.c
@@ -0,0 +1,1841 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "mempool.h"
+#include "array.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "istream.h"
+#include "rfc822-parser.h"
+#include "message-date.h"
+#include "message-parser.h"
+#include "message-decoder.h"
+#include "message-header-decode.h"
+#include "mail-html2text.h"
+#include "mail-storage.h"
+#include "mail-user.h"
+#include "smtp-params.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "raw-storage.h"
+
+#include "edit-mail.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-error.h"
+#include "sieve-extensions.h"
+#include "sieve-code.h"
+#include "sieve-address.h"
+#include "sieve-address-parts.h"
+#include "sieve-runtime.h"
+#include "sieve-runtime-trace.h"
+#include "sieve-match.h"
+#include "sieve-interpreter.h"
+
+#include "sieve-message.h"
+
+/*
+ * Message transmission
+ */
+
+const char *sieve_message_get_new_id(const struct sieve_instance *svinst)
+{
+	static int count = 0;
+
+	return t_strdup_printf("<dovecot-sieve-%s-%s-%d@%s>",
+		dec2str(ioloop_timeval.tv_sec), dec2str(ioloop_timeval.tv_usec),
+    count++, svinst->hostname);
+}
+
+/*
+ * Message context
+ */
+
+struct sieve_message_header {
+	const char *name;
+
+	const unsigned char *value, *utf8_value;
+	size_t value_len, utf8_value_len;
+};
+
+struct sieve_message_part {
+	struct sieve_message_part *parent, *next, *children;
+
+	ARRAY(struct sieve_message_header) headers;
+
+	const char *content_type;
+	const char *content_disposition;
+
+	const char *decoded_body;
+	const char *text_body;
+	size_t decoded_body_size;
+	size_t text_body_size;
+
+	bool have_body:1; /* there's the empty end-of-headers line */
+	bool epilogue:1;  /* this is a multipart epilogue */
+};
+
+struct sieve_message_version {
+	struct mail *mail;
+	struct mailbox *box;
+	struct mailbox_transaction_context *trans;
+	struct edit_mail *edit_mail;
+};
+
+struct sieve_message_context {
+	pool_t pool;
+	pool_t context_pool;
+	int refcount;
+
+	struct sieve_instance *svinst;
+	struct timeval time;
+
+	struct mail_user *mail_user;
+	const struct sieve_message_data *msgdata;
+
+	/* Message versioning */
+
+	struct mail_user *raw_mail_user;
+	ARRAY(struct sieve_message_version) versions;
+
+	/* Context data for extensions */
+
+	ARRAY(void *) ext_contexts;
+
+	/* Body */
+
+	ARRAY(struct sieve_message_part *) cached_body_parts;
+	ARRAY(struct sieve_message_part_data) return_body_parts;
+	buffer_t *raw_body;
+
+	bool edit_snapshot:1;
+	bool substitute_snapshot:1;
+};
+
+/*
+ * Message versions
+ */
+
+static inline struct sieve_message_version *sieve_message_version_new
+(struct sieve_message_context *msgctx)
+{
+	return array_append_space(&msgctx->versions);
+}
+
+static inline struct sieve_message_version *sieve_message_version_get
+(struct sieve_message_context *msgctx)
+{
+	struct sieve_message_version *versions;
+	unsigned int count;
+
+	versions = array_get_modifiable(&msgctx->versions, &count);
+	if ( count == 0 )
+		return array_append_space(&msgctx->versions);
+
+	return &versions[count-1];
+}
+
+static inline void sieve_message_version_free
+(struct sieve_message_version *version)
+{
+	if ( version->edit_mail != NULL ) {
+		edit_mail_unwrap(&version->edit_mail);
+		version->edit_mail = NULL;
+	}
+
+	if ( version->mail != NULL ) {
+		mail_free(&version->mail);
+		mailbox_transaction_rollback(&version->trans);
+		mailbox_free(&version->box);
+		version->mail = NULL;
+	}
+}
+
+/*
+ * Message context object
+ */
+
+struct sieve_message_context *sieve_message_context_create
+(struct sieve_instance *svinst, struct mail_user *mail_user,
+	const struct sieve_message_data *msgdata)
+{
+	struct sieve_message_context *msgctx;
+
+	msgctx = i_new(struct sieve_message_context, 1);
+	msgctx->refcount = 1;
+	msgctx->svinst = svinst;
+
+	msgctx->mail_user = mail_user;
+	msgctx->msgdata = msgdata;
+
+	if (gettimeofday(&msgctx->time, NULL) < 0)
+		i_fatal("gettimeofday(): %m");
+
+	sieve_message_context_reset(msgctx);
+
+	return msgctx;
+}
+
+void sieve_message_context_ref(struct sieve_message_context *msgctx)
+{
+	msgctx->refcount++;
+}
+
+static void sieve_message_context_clear(struct sieve_message_context *msgctx)
+{
+	struct sieve_message_version *versions;
+	unsigned int count, i;
+
+	if ( msgctx->pool != NULL ) {
+		versions = array_get_modifiable(&msgctx->versions, &count);
+
+		for ( i = 0; i < count; i++ ) {
+			sieve_message_version_free(&versions[i]);
+		}
+
+		pool_unref(&(msgctx->pool));
+	}
+}
+
+void sieve_message_context_unref(struct sieve_message_context **msgctx)
+{
+	i_assert((*msgctx)->refcount > 0);
+
+	if (--(*msgctx)->refcount != 0)
+		return;
+
+	if ( (*msgctx)->raw_mail_user != NULL )
+		mail_user_unref(&(*msgctx)->raw_mail_user);
+
+	sieve_message_context_clear(*msgctx);
+
+	if ( (*msgctx)->context_pool != NULL )
+		pool_unref(&((*msgctx)->context_pool));
+
+	i_free(*msgctx);
+	*msgctx = NULL;
+}
+
+static void sieve_message_context_flush(struct sieve_message_context *msgctx)
+{
+	pool_t pool;
+
+	if ( msgctx->context_pool != NULL )
+		pool_unref(&(msgctx->context_pool));
+
+	msgctx->context_pool = pool =
+		pool_alloconly_create("sieve_message_context_data", 2048);
+
+	p_array_init(&msgctx->ext_contexts, pool,
+		sieve_extensions_get_count(msgctx->svinst));
+
+	p_array_init(&msgctx->cached_body_parts, pool, 8);
+	p_array_init(&msgctx->return_body_parts, pool, 8);
+	msgctx->raw_body = NULL;
+}
+
+void sieve_message_context_reset(struct sieve_message_context *msgctx)
+{
+	sieve_message_context_clear(msgctx);
+
+	msgctx->pool = pool_alloconly_create("sieve_message_context", 1024);
+
+	p_array_init(&msgctx->versions, msgctx->pool, 4);
+
+	sieve_message_context_flush(msgctx);
+}
+
+pool_t sieve_message_context_pool(struct sieve_message_context *msgctx)
+{
+	return msgctx->context_pool;
+}
+
+void sieve_message_context_time(struct sieve_message_context *msgctx,
+   struct timeval *time)
+{
+   *time = msgctx->time;
+}
+
+/* Extension support */
+
+void sieve_message_context_extension_set
+(struct sieve_message_context *msgctx, const struct sieve_extension *ext,
+	void *context)
+{
+	if ( ext->id < 0 ) return;
+
+	array_idx_set(&msgctx->ext_contexts, (unsigned int) ext->id, &context);
+}
+
+const void *sieve_message_context_extension_get
+(struct sieve_message_context *msgctx, const struct sieve_extension *ext)
+{
+	void * const *ctx;
+
+	if  ( ext->id < 0 || ext->id >= (int) array_count(&msgctx->ext_contexts) )
+		return NULL;
+
+	ctx = array_idx(&msgctx->ext_contexts, (unsigned int) ext->id);
+
+	return *ctx;
+}
+
+/* Envelope */
+
+const struct smtp_address *sieve_message_get_orig_recipient
+(struct sieve_message_context *msgctx)
+{
+	const struct sieve_message_data *msgdata = msgctx->msgdata;
+	const struct smtp_address *orcpt_to = NULL;
+
+	if ( msgdata->envelope.rcpt_params != NULL ) {
+		orcpt_to = msgdata->envelope.rcpt_params->orcpt.addr;
+		if ( !smtp_address_isnull(orcpt_to) )
+			return orcpt_to;
+	}
+
+	orcpt_to = msgdata->envelope.rcpt_to;
+	return ( !smtp_address_isnull(orcpt_to) ? orcpt_to : NULL );
+}
+
+const struct smtp_address *sieve_message_get_final_recipient
+(struct sieve_message_context *msgctx)
+{
+	const struct sieve_message_data *msgdata = msgctx->msgdata;
+	const struct smtp_address *rcpt_to = msgdata->envelope.rcpt_to;
+
+	return ( !smtp_address_isnull(rcpt_to) ? rcpt_to : NULL);
+}
+
+const struct smtp_address *sieve_message_get_sender
+(struct sieve_message_context *msgctx)
+{
+	const struct sieve_message_data *msgdata = msgctx->msgdata;
+	const struct smtp_address *mail_from = msgdata->envelope.mail_from;
+
+	return ( !smtp_address_isnull(mail_from) ? mail_from : NULL);
+}
+
+/*
+ * Mail
+ */
+
+int sieve_message_substitute
+(struct sieve_message_context *msgctx, struct istream *input)
+{
+	static const char *wanted_headers[] = {
+		"From", "Message-ID", "Subject", "Return-Path", NULL
+	};
+	struct mail_user *mail_user = msgctx->mail_user;
+	struct sieve_message_version *version;
+	struct mailbox_header_lookup_ctx *headers_ctx;
+	struct mailbox *box = NULL;
+	const struct smtp_address *sender;
+	int ret;
+
+	i_assert(input->blocking);
+
+	if ( msgctx->raw_mail_user == NULL ) {
+		void **sets = master_service_settings_get_others(master_service);
+
+		msgctx->raw_mail_user =
+			raw_storage_create_from_set(mail_user->set_info, sets[0]);
+	}
+
+	i_stream_seek(input, 0);
+	sender = sieve_message_get_sender(msgctx);
+	sender = (sender == NULL ? DEFAULT_ENVELOPE_SENDER : sender );
+	ret = raw_mailbox_alloc_stream(msgctx->raw_mail_user, input, (time_t)-1,
+		smtp_address_encode(sender), &box);
+
+	if ( ret < 0 ) {
+		sieve_sys_error(msgctx->svinst, "can't open substituted mail as raw: %s",
+			mailbox_get_last_error(box, NULL));
+		return -1;
+	}
+
+	if ( msgctx->substitute_snapshot ) {
+		version = sieve_message_version_new(msgctx);
+	} else {
+		version = sieve_message_version_get(msgctx);
+		sieve_message_version_free(version);
+	}
+
+	version->box = box;
+	version->trans = mailbox_transaction_begin(box, 0, __func__);
+	headers_ctx = mailbox_header_lookup_init(box, wanted_headers);
+	version->mail = mail_alloc(version->trans, 0, headers_ctx);
+	mailbox_header_lookup_unref(&headers_ctx);
+	mail_set_seq(version->mail, 1);
+
+	sieve_message_context_flush(msgctx);
+
+	msgctx->substitute_snapshot = FALSE;
+	msgctx->edit_snapshot = FALSE;
+
+	return 1;
+}
+
+struct mail *sieve_message_get_mail
+(struct sieve_message_context *msgctx)
+{
+	const struct sieve_message_version *versions;
+	unsigned int count;
+
+	versions = array_get(&msgctx->versions, &count);
+	if ( count == 0 )
+		return msgctx->msgdata->mail;
+
+	if ( versions[count-1].edit_mail != NULL )
+		return edit_mail_get_mail(versions[count-1].edit_mail);
+
+	return versions[count-1].mail;
+}
+
+struct edit_mail *sieve_message_edit
+(struct sieve_message_context *msgctx)
+{
+	struct sieve_message_version *version;
+
+	version = sieve_message_version_get(msgctx);
+
+	if ( version->edit_mail == NULL ) {
+		version->edit_mail = edit_mail_wrap
+			(( version->mail == NULL ? msgctx->msgdata->mail : version->mail ));
+	} else if ( msgctx->edit_snapshot ) {
+		version->edit_mail = edit_mail_snapshot(version->edit_mail);
+	}
+
+	msgctx->edit_snapshot = FALSE;
+
+	return version->edit_mail;
+}
+
+void sieve_message_snapshot
+(struct sieve_message_context *msgctx)
+{
+	msgctx->edit_snapshot = TRUE;
+	msgctx->substitute_snapshot = TRUE;
+}
+
+/*
+ * Message header list
+ */
+
+/* Forward declarations */
+
+static int sieve_message_header_list_next_item
+	(struct sieve_header_list *_hdrlist, const char **name_r,
+		string_t **value_r);
+static int sieve_message_header_list_next_value
+	(struct sieve_stringlist *_strlist, string_t **value_r);
+static void sieve_message_header_list_reset
+	(struct sieve_stringlist *_strlist);
+
+/* String list object */
+
+struct sieve_message_header_list {
+	struct sieve_header_list hdrlist;
+
+	struct sieve_stringlist *field_names;
+
+	const char *header_name;
+	const char *const *headers;
+	int headers_index;
+
+	bool mime_decode:1;
+};
+
+struct sieve_header_list *sieve_message_header_list_create
+(const struct sieve_runtime_env *renv,
+	struct sieve_stringlist *field_names,
+	bool mime_decode)
+{
+	struct sieve_message_header_list *hdrlist;
+
+	hdrlist = t_new(struct sieve_message_header_list, 1);
+	hdrlist->hdrlist.strlist.runenv = renv;
+	hdrlist->hdrlist.strlist.exec_status = SIEVE_EXEC_OK;
+	hdrlist->hdrlist.strlist.next_item = sieve_message_header_list_next_value;
+	hdrlist->hdrlist.strlist.reset = sieve_message_header_list_reset;
+	hdrlist->hdrlist.next_item = sieve_message_header_list_next_item;
+	hdrlist->field_names = field_names;
+	hdrlist->mime_decode = mime_decode;
+
+	return &hdrlist->hdrlist;
+}
+
+// NOTE: get rid of this once we have a proper Sieve string type
+static inline string_t *_header_right_trim(const char *raw)
+{
+	string_t *result;
+	const char *p, *pend;
+
+	pend = raw + strlen(raw);
+	if (raw == pend) {
+		result = t_str_new(1);
+	} else {
+		for ( p = pend-1; p >= raw; p-- ) {
+			if ( *p != ' ' && *p != '\t' ) break;
+		}
+		result = t_str_new(p - raw + 1);
+		str_append_data(result, raw, p - raw + 1);
+	}
+	return result;
+}
+
+/* String list implementation */
+
+static int sieve_message_header_list_next_item
+(struct sieve_header_list *_hdrlist, const char **name_r,
+	string_t **value_r)
+{
+	struct sieve_message_header_list *hdrlist =
+		(struct sieve_message_header_list *) _hdrlist;
+	const struct sieve_runtime_env *renv = _hdrlist->strlist.runenv;
+	struct mail *mail = sieve_message_get_mail(renv->msgctx);
+
+	if ( name_r != NULL )
+		*name_r = NULL;
+	*value_r = NULL;
+
+	/* Check for end of current header list */
+	if ( hdrlist->headers == NULL ) {
+		hdrlist->headers_index = 0;
+ 	} else if ( hdrlist->headers[hdrlist->headers_index] == NULL ) {
+		hdrlist->headers = NULL;
+		hdrlist->headers_index = 0;
+	}
+
+	/* Fetch next header */
+	while ( hdrlist->headers == NULL ) {
+		string_t *hdr_item = NULL;
+		int ret;
+
+		/* Read next header name from source list */
+		if ( (ret=sieve_stringlist_next_item
+			(hdrlist->field_names, &hdr_item)) <= 0 )
+			return ret;
+
+		hdrlist->header_name = str_c(hdr_item);
+
+		if ( _hdrlist->strlist.trace ) {
+			sieve_runtime_trace(renv, 0,
+				"extracting `%s' headers from message",
+				str_sanitize(str_c(hdr_item), 80));
+		}
+
+		/* Fetch all matching headers from the e-mail */
+		if ( hdrlist->mime_decode ) {
+			ret = mail_get_headers_utf8(mail,
+				str_c(hdr_item), &hdrlist->headers);
+		} else {
+			ret = mail_get_headers(mail,
+				str_c(hdr_item), &hdrlist->headers);
+		}
+
+		if (ret < 0) {
+			_hdrlist->strlist.exec_status =
+				sieve_runtime_mail_error(renv, mail,
+					"failed to read header field `%s'", str_c(hdr_item));
+			return -1;
+		}
+
+		if ( ret == 0 || hdrlist->headers[0] == NULL ) {
+			/* Try next item when no headers found */
+			hdrlist->headers = NULL;
+		}
+	}
+
+	/* Return next item */
+	if ( name_r != NULL )
+		*name_r = hdrlist->header_name;
+	*value_r = _header_right_trim(hdrlist->headers[hdrlist->headers_index++]);
+	return 1;
+}
+
+static int sieve_message_header_list_next_value
+(struct sieve_stringlist *_strlist, string_t **value_r)
+{
+	struct sieve_header_list *hdrlist =
+		(struct sieve_header_list *) _strlist;
+
+	return sieve_message_header_list_next_item
+		(hdrlist, NULL, value_r);
+}
+
+static void sieve_message_header_list_reset
+(struct sieve_stringlist *strlist)
+{
+	struct sieve_message_header_list *hdrlist =
+		(struct sieve_message_header_list *) strlist;
+
+	hdrlist->headers = NULL;
+	hdrlist->headers_index = 0;
+	sieve_stringlist_reset(hdrlist->field_names);
+}
+
+/*
+ * Header override operand
+ */
+
+const struct sieve_operand_class sieve_message_override_operand_class =
+	{ "header-override" };
+
+bool sieve_opr_message_override_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	struct sieve_message_override svmo;
+	const struct sieve_message_override_def *hodef;
+
+	if ( !sieve_opr_object_dump
+		(denv, &sieve_message_override_operand_class, address, &svmo.object) )
+		return FALSE;
+
+	hodef = svmo.def =
+		(const struct sieve_message_override_def *) svmo.object.def;
+
+	if ( hodef->dump_context != NULL ) {
+		sieve_code_descend(denv);
+		if ( !hodef->dump_context(&svmo, denv, address) ) {
+			return FALSE;
+		}
+		sieve_code_ascend(denv);
+	}
+
+	return TRUE;
+}
+
+int sieve_opr_message_override_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+	struct sieve_message_override *svmo)
+{
+	const struct sieve_message_override_def *hodef;
+	int ret;
+
+	svmo->context = NULL;
+
+	if ( !sieve_opr_object_read
+		(renv, &sieve_message_override_operand_class, address, &svmo->object) )
+		return SIEVE_EXEC_BIN_CORRUPT;
+
+	hodef = svmo->def =
+		(const struct sieve_message_override_def *) svmo->object.def;
+
+	if ( hodef->read_context != NULL &&
+		(ret=hodef->read_context(svmo, renv, address, &svmo->context)) <= 0 )
+		return ret;
+
+	return SIEVE_EXEC_OK;
+}
+
+/*
+ * Optional operands
+ */
+
+int sieve_message_opr_optional_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+	signed int *opt_code)
+{
+	signed int _opt_code = 0;
+	bool final = FALSE, opok = TRUE;
+
+	if ( opt_code == NULL ) {
+		opt_code = &_opt_code;
+		final = TRUE;
+	}
+
+	while ( opok ) {
+		int opt;
+
+		if ( (opt=sieve_addrmatch_opr_optional_dump
+			(denv, address, opt_code)) <= 0 )
+			return opt;
+
+		if ( *opt_code == SIEVE_OPT_MESSAGE_OVERRIDE ) {
+			opok = sieve_opr_message_override_dump(denv, address);
+		} else {
+			return ( final ? -1 : 1 );
+		}
+	}
+
+	return -1;
+}
+
+int sieve_message_opr_optional_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+	signed int *opt_code, int *exec_status,
+	struct sieve_address_part *addrp, struct sieve_match_type *mcht,
+	struct sieve_comparator *cmp, 
+	ARRAY_TYPE(sieve_message_override) *svmos)
+{
+	signed int _opt_code = 0;
+	bool final = FALSE;
+	int ret;
+
+	if ( opt_code == NULL ) {
+		opt_code = &_opt_code;
+		final = TRUE;
+	}
+
+	if ( exec_status != NULL )
+		*exec_status = SIEVE_EXEC_OK;
+
+	for ( ;; ) {
+		int opt;
+
+		if ( (opt=sieve_addrmatch_opr_optional_read
+			(renv, address, opt_code, exec_status, addrp, mcht, cmp)) <= 0 )
+			return opt;
+
+		if ( *opt_code == SIEVE_OPT_MESSAGE_OVERRIDE ) {
+			struct sieve_message_override svmo;
+			const struct sieve_message_override *svmo_idx;
+			unsigned int count, i;
+
+			if ( (ret=sieve_opr_message_override_read
+				(renv, address, &svmo)) <= 0 ) {
+				if ( exec_status != NULL )
+					*exec_status = ret;
+				return -1;
+			}
+
+			if ( !array_is_created(svmos) )
+				t_array_init(svmos, 8);
+			/* insert in sorted sequence */
+			svmo_idx = array_get(svmos, &count);
+			for (i = 0; i < count; i++) {
+				if (svmo.def->sequence < svmo_idx[i].def->sequence) {
+					array_insert(svmos, i, &svmo, 1);
+					break;
+				}
+			}
+			if (count == i)
+				array_append(svmos, &svmo, 1);
+		} else {
+			if ( final ) {
+				sieve_runtime_trace_error(renv, "invalid optional operand");
+				if ( exec_status != NULL )
+					*exec_status = SIEVE_EXEC_BIN_CORRUPT;
+				return -1;
+			}
+			return 1;
+		}
+	}
+
+	i_unreached();
+	return -1;
+}
+
+/*
+ * Message header
+ */
+
+int sieve_message_get_header_fields
+(const struct sieve_runtime_env *renv,
+	struct sieve_stringlist *field_names,
+	ARRAY_TYPE(sieve_message_override) *svmos,
+	bool mime_decode, struct sieve_stringlist **fields_r)
+{
+	const struct sieve_message_override *svmo;
+	unsigned int count, i;
+	int ret;
+
+	if ( svmos == NULL || !array_is_created(svmos) ||
+		array_count(svmos) == 0 ) {
+		struct sieve_header_list *headers;
+		headers = sieve_message_header_list_create
+			(renv, field_names, mime_decode);
+		*fields_r = &headers->strlist;
+		return SIEVE_EXEC_OK;
+	}
+
+	svmo = array_get(svmos, &count);
+	if ( svmo[0].def->sequence == 0 &&
+		svmo[0].def->header_override != NULL ) {
+		*fields_r = field_names;
+	} else {
+		struct sieve_header_list *headers;
+		headers = sieve_message_header_list_create
+			(renv, field_names, mime_decode);
+		*fields_r = &headers->strlist;
+	}
+
+	for ( i = 0; i < count; i++ ) {
+		if ( svmo[i].def->header_override != NULL &&
+			(ret=svmo[i].def->header_override
+				(&svmo[i], renv, mime_decode, fields_r)) <= 0 )
+			return ret;
+	}
+	return SIEVE_EXEC_OK;
+}
+
+/*
+ * Message part
+ */
+
+struct sieve_message_part *sieve_message_part_parent
+(struct sieve_message_part *mpart)
+{
+	return mpart->parent;
+}
+
+struct sieve_message_part *sieve_message_part_next
+(struct sieve_message_part *mpart)
+{
+	return mpart->next;
+}
+
+struct sieve_message_part *sieve_message_part_children
+(struct sieve_message_part *mpart)
+{
+	return mpart->children;
+}
+
+const char *sieve_message_part_content_type
+(struct sieve_message_part *mpart)
+{
+	return mpart->content_type;
+}
+
+const char *sieve_message_part_content_disposition
+(struct sieve_message_part *mpart)
+{
+	return mpart->content_disposition;
+}
+
+int sieve_message_part_get_first_header
+(struct sieve_message_part *mpart, const char *field,
+	const char **value_r)
+{
+	const struct sieve_message_header *headers;
+	unsigned int i, count;
+
+	headers = array_get(&mpart->headers, &count);
+	for ( i = 0; i < count; i++ ) {
+		if ( strcasecmp( headers[i].name, field) == 0 ) {
+			i_assert( headers[i].value[headers[i].value_len] == '\0' );
+			*value_r = (const char *)headers[i].value;
+			return 1;
+		}
+	}
+
+	*value_r = NULL;
+	return 0;
+}
+
+void sieve_message_part_get_data
+(struct sieve_message_part *mpart,
+	struct sieve_message_part_data *data, bool text)
+{
+	i_zero(data);
+	data->content_type = mpart->content_type;
+	data->content_disposition = mpart->content_disposition;
+
+	if ( !text ) {
+		data->content = mpart->decoded_body;
+		data->size = mpart->decoded_body_size;
+	} else if ( mpart->children != NULL ) {
+		data->content = "";
+		data->size = 0;
+	} else {
+		data->content = mpart->text_body;
+		data->size = mpart->text_body_size;
+	}
+}
+
+/*
+ * Message body
+ */
+
+static void str_replace_nuls(string_t *str)
+{
+	char *data = str_c_modifiable(str);
+	unsigned int i, len = str_len(str);
+
+	for (i = 0; i < len; i++) {
+		if (data[i] == '\0')
+			data[i] = ' ';
+	}
+}
+
+static bool _is_wanted_content_type
+(const char * const *wanted_types, const char *content_type)
+ATTR_NULL(1)
+{
+	const char *subtype;
+	size_t type_len;
+
+	if ( wanted_types == NULL )
+		return TRUE;
+
+	subtype = strchr(content_type, '/');
+	type_len = ( subtype == NULL ? strlen(content_type) :
+		(size_t)(subtype - content_type) );
+
+	i_assert( wanted_types != NULL );
+
+	for (; *wanted_types != NULL; wanted_types++) {
+		const char *wanted_subtype;
+
+		if (**wanted_types == '\0') {
+			/* empty string matches everything */
+			return TRUE;
+		}
+
+		wanted_subtype = strchr(*wanted_types, '/');
+		if (wanted_subtype == NULL) {
+			/* match only main type */
+			if (strlen(*wanted_types) == type_len &&
+			  strncasecmp(*wanted_types, content_type, type_len) == 0)
+				return TRUE;
+		} else {
+			/* match whole type/subtype */
+			if (strcasecmp(*wanted_types, content_type) == 0)
+				return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+static bool sieve_message_body_get_return_parts
+(const struct sieve_runtime_env *renv,
+	const char * const *wanted_types,
+	bool extract_text)
+{
+	struct sieve_message_context *msgctx = renv->msgctx;
+	struct sieve_message_part *const *body_parts;
+	unsigned int i, count;
+	struct sieve_message_part_data *return_part;
+
+	/* Check whether any body parts are cached already */
+	body_parts = array_get(&msgctx->cached_body_parts, &count);
+	if ( count == 0 )
+		return FALSE;
+
+	/* Clear result array */
+	array_clear(&msgctx->return_body_parts);
+
+	/* Fill result array with requested content_types */
+	for (i = 0; i < count; i++) {
+		if (!body_parts[i]->have_body) {
+			/* Part has no body; according to RFC this MUST not match to anything and
+			 * therefore it is not included in the result.
+			 */
+			continue;
+		}
+
+		/* Skip content types that are not requested */
+		if (!_is_wanted_content_type
+			(wanted_types, body_parts[i]->content_type))
+			continue;
+
+		/* Add new item to the result */
+		return_part = array_append_space(&msgctx->return_body_parts);
+		return_part->content_type = body_parts[i]->content_type;
+		return_part->content_disposition = body_parts[i]->content_disposition;
+
+		/* Depending on whether a decoded body part is requested, the appropriate
+		 * cache item is read. If it is missing, this function fails and the cache
+		 * needs to be completed by sieve_message_parts_add_missing().
+		 */
+		if (extract_text) {
+			if (body_parts[i]->text_body == NULL)
+				return FALSE;
+			return_part->content = body_parts[i]->text_body;
+			return_part->size = body_parts[i]->text_body_size;
+		} else {
+			if (body_parts[i]->decoded_body == NULL)
+				return FALSE;
+			return_part->content = body_parts[i]->decoded_body;
+			return_part->size = body_parts[i]->decoded_body_size;				
+		}
+	}
+
+	return TRUE;
+}
+
+static void sieve_message_part_save
+(const struct sieve_runtime_env *renv, buffer_t *buf,
+	struct sieve_message_part *body_part,
+	bool extract_text)
+{
+	struct sieve_message_context *msgctx = renv->msgctx;
+	pool_t pool = msgctx->context_pool;
+	buffer_t *result_buf, *text_buf = NULL;
+	char *part_data;
+	size_t part_size;
+
+	/* Extract text if requested */
+	result_buf = buf;
+	if ( extract_text && body_part->children == NULL &&
+		!body_part->epilogue ) {
+
+		if ( buf->used > 0 && mail_html2text_content_type_match
+			(body_part->content_type) ) {
+			struct mail_html2text *html2text;
+
+			text_buf = buffer_create_dynamic(default_pool, 4096);
+
+			/* Remove HTML markup */
+			html2text = mail_html2text_init(0);
+			mail_html2text_more(html2text, buf->data, buf->used, text_buf);
+			mail_html2text_deinit(&html2text);
+
+			result_buf = text_buf;
+		}
+	}
+
+	/* Add terminating NUL to the body part buffer */
+	buffer_append_c(result_buf, '\0');
+
+	/* Make copy of the buffer */
+	part_data = p_malloc(pool, result_buf->used);
+	memcpy(part_data, result_buf->data, result_buf->used);
+	part_size = result_buf->used - 1;
+
+	/* Free text buffer if used */
+	if ( text_buf != NULL)
+		buffer_free(&text_buf);
+
+	/* Depending on whether the part is processed into text, store message
+	 * body in the appropriate cache location.
+	 */
+	if ( !extract_text ) {
+		body_part->decoded_body = part_data;
+		body_part->decoded_body_size = part_size;
+	} else {
+		body_part->text_body = part_data;
+		body_part->text_body_size = part_size;
+	}
+
+	/* Clear buffer */
+	buffer_set_used_size(buf, 0);
+}
+
+static const char *
+_parse_content_type(const struct message_header_line *hdr)
+{
+	struct rfc822_parser_context parser;
+	string_t *content_type;
+
+	/* Initialize parsing */
+	rfc822_parser_init(&parser, hdr->full_value, hdr->full_value_len, NULL);
+	(void)rfc822_skip_lwsp(&parser);
+
+	/* Parse content type */
+	content_type = t_str_new(64);
+	if (rfc822_parse_content_type(&parser, content_type) < 0)
+		return "";
+
+	/* Content-type value must end here, otherwise it is invalid after all */
+	(void)rfc822_skip_lwsp(&parser);
+	if ( parser.data != parser.end && *parser.data != ';' )
+		return "";
+
+	/* Success */
+	return str_c(content_type);
+}
+
+static const char *
+_parse_content_disposition(const struct message_header_line *hdr)
+{
+	struct rfc822_parser_context parser;
+	string_t *content_disp;
+
+	/* Initialize parsing */
+	rfc822_parser_init(&parser, hdr->full_value, hdr->full_value_len, NULL);
+	(void)rfc822_skip_lwsp(&parser);
+
+	/* Parse content type */
+	content_disp = t_str_new(64);
+	if (rfc822_parse_mime_token(&parser, content_disp) < 0)
+		return "";
+
+	/* Content-type value must end here, otherwise it is invalid after all */
+	(void)rfc822_skip_lwsp(&parser);
+	if ( parser.data != parser.end && *parser.data != ';' )
+		return "";
+
+	/* Success */
+	return str_c(content_disp);
+}
+
+/* sieve_message_parts_add_missing():
+ *   Add requested message body parts to the cache that are missing.
+ */
+static int sieve_message_parts_add_missing
+(const struct sieve_runtime_env *renv,
+	const char *const *content_types,
+	bool extract_text, bool iter_all)
+	ATTR_NULL(2)
+{
+	struct sieve_message_context *msgctx = renv->msgctx;
+	pool_t pool = msgctx->context_pool;
+	struct mail *mail = sieve_message_get_mail(renv->msgctx);
+	enum message_parser_flags mparser_flags =
+		MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS;
+	enum message_header_parser_flags hparser_flags =
+		MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP;
+	ARRAY(struct sieve_message_header) headers;
+	struct sieve_message_part *body_part, *header_part, *last_part;
+	struct message_parser_ctx *parser;
+	struct message_decoder_context *decoder;
+	struct message_block block, decoded;
+	struct message_part *mparts, *prev_mpart = NULL;
+	buffer_t *buf;
+	struct istream *input;
+	unsigned int idx = 0;
+	bool save_body = FALSE, have_all;
+	string_t *hdr_content = NULL;
+	int ret;
+
+	/* First check whether any are missing */
+	if ( !iter_all && sieve_message_body_get_return_parts
+		(renv, content_types, extract_text) ) {
+		/* Cache hit; all are present */
+		return SIEVE_EXEC_OK;
+	}
+
+	/* Get the message stream */
+	if ( mail_get_stream(mail, NULL, NULL, &input) < 0 ) {
+		return sieve_runtime_mail_error(renv, mail,
+			"failed to open input message");
+	}
+	if (mail_get_parts(mail, &mparts) < 0) {
+		return sieve_runtime_mail_error(renv, mail,
+			"failed to parse input message parts");
+	}
+
+	buf = buffer_create_dynamic(default_pool, 4096);
+	body_part = header_part = last_part = NULL;
+
+	if (iter_all) {
+		t_array_init(&headers, 64);
+		hdr_content = t_str_new(512);
+		hparser_flags |= MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE;
+	} else {
+		i_zero(&headers);
+	}
+
+	/* Initialize body decoder */
+	decoder = message_decoder_init(NULL, 0);
+
+	// FIXME: currently not tested with edit-mail.
+		//parser = message_parser_init_from_parts(parts, input,
+		// hparser_flags, mparser_flags);
+	parser = message_parser_init(pool_datastack_create(),
+		input, hparser_flags, mparser_flags);
+	while ( (ret=message_parser_parse_next_block
+		(parser, &block)) > 0 ) {
+		struct sieve_message_part **body_part_idx;
+		struct message_header_line *hdr = block.hdr;
+		struct sieve_message_header *header;
+		unsigned char *data;
+
+		if ( block.part != prev_mpart ) {
+			bool message_rfc822 = FALSE;
+
+			/* Save previous body part */
+			if ( body_part != NULL ) {
+				/* Treat message/rfc822 separately; headers become content */
+				if ( block.part->parent == prev_mpart &&
+					strcmp(body_part->content_type, "message/rfc822") == 0 ) {
+					message_rfc822 = TRUE;
+				} else {
+					if ( save_body ) {
+						sieve_message_part_save
+							(renv, buf, body_part, extract_text);
+					}
+				}
+				if ( iter_all && !array_is_created(&body_part->headers) &&
+					array_count(&headers) > 0 ) {
+					p_array_init(&body_part->headers, pool, array_count(&headers));
+					array_copy(&body_part->headers.arr, 0,
+						&headers.arr, 0, array_count(&headers));
+				}
+			}
+
+			/* Start processing next part */
+			body_part_idx = array_idx_get_space
+				(&msgctx->cached_body_parts, idx);
+			if ( *body_part_idx == NULL )
+				*body_part_idx = p_new(pool, struct sieve_message_part, 1);
+			body_part = *body_part_idx;
+			body_part->content_type = "text/plain";
+			if ( iter_all )
+				array_clear(&headers);
+
+			/* Copy tree structure */
+			if ( block.part->context != NULL ) {
+				struct sieve_message_part *epipart =
+					(struct sieve_message_part *)block.part->context;
+				i_assert(epipart != NULL);
+
+				/* multipart epilogue */
+				body_part->content_type = epipart->content_type;
+				body_part->have_body = TRUE;
+				body_part->epilogue = TRUE;
+				save_body = iter_all || _is_wanted_content_type
+					(content_types, body_part->content_type);
+
+			} else {
+				struct sieve_message_part *parent = NULL;
+
+				if ( block.part->parent != NULL ) {
+					body_part->parent = parent =
+						(struct sieve_message_part *)
+							block.part->parent->context;
+				}
+
+				/* new part */
+				block.part->context = (void*)body_part;
+
+				if ( last_part != NULL ) {
+					i_assert( parent != NULL );
+					if ( last_part->parent == parent ) {
+						last_part->next = body_part;
+					}	else if (parent->children == NULL) {
+						parent->children = body_part;
+					} else {
+						struct sieve_message_part *child = parent->children;
+						while (child->next != NULL && child != body_part)
+							child = child->next;
+						if (child != body_part)
+							child->next = body_part;
+					}
+				}
+			}
+			last_part = body_part;
+
+			/* If this is message/rfc822 content, retain the enveloping part for
+			 * storing headers as content.
+			 */
+			if ( message_rfc822 ) {
+				i_assert(idx > 0);
+				body_part_idx = array_idx_modifiable
+					(&msgctx->cached_body_parts, idx-1);
+				header_part = *body_part_idx;
+			} else {
+				header_part = NULL;
+			}
+
+			prev_mpart = block.part;
+			idx++;
+		}
+
+		if ( hdr != NULL || block.size == 0 ) {
+			enum {
+				_HDR_CONTENT_TYPE,
+				_HDR_CONTENT_DISPOSITION,
+				_HDR_OTHER
+			} hdr_field;
+
+			/* Reading headers */
+
+			/* Decode block */
+			(void)message_decoder_decode_next_block
+				(decoder, &block, &decoded);
+
+			/* Check for end of headers */
+			if ( hdr == NULL ) {
+				/* Save headers for message/rfc822 part */
+				if ( header_part != NULL ) {
+					sieve_message_part_save
+						(renv, buf, header_part, FALSE);
+					header_part = NULL;
+				}
+
+				/* Save bodies only if we have a wanted content-type */
+				i_assert( body_part != NULL );
+				save_body = iter_all || _is_wanted_content_type
+					(content_types, body_part->content_type);
+				continue;
+			}
+
+			/* Encountered the empty line that indicates the end of the headers and
+			 * the start of the body
+			 */
+			if ( hdr->eoh ) {
+				i_assert( body_part != NULL );
+				body_part->have_body = TRUE;
+				continue;
+			} else if ( header_part != NULL ) {
+				/* Save message/rfc822 header as part content */
+				if ( hdr->continued ) {
+					buffer_append(buf, hdr->value, hdr->value_len);
+				} else {
+					buffer_append(buf, hdr->name, hdr->name_len);
+					buffer_append(buf, hdr->middle, hdr->middle_len);
+					buffer_append(buf, hdr->value, hdr->value_len);
+				}
+				if ( !hdr->no_newline ) {
+					buffer_append(buf, "\r\n", 2);
+				}
+			}
+
+			if ( strcasecmp(hdr->name, "Content-Type" ) == 0 )
+				hdr_field = _HDR_CONTENT_TYPE;
+			else if ( strcasecmp(hdr->name, "Content-Disposition" ) == 0 )
+				hdr_field = _HDR_CONTENT_DISPOSITION;
+			else if ( iter_all && !array_is_created(&body_part->headers) )
+				hdr_field = _HDR_OTHER;
+			else {
+				/* Not interested in this header */
+				continue;
+			}
+
+			/* Header can have folding whitespace. Acquire the full value before
+			 * continuing
+			 */
+			if ( hdr->continues ) {
+				hdr->use_full_value = TRUE;
+				continue;
+			}
+
+			if ( iter_all && !array_is_created(&body_part->headers) ) {
+				const unsigned char *value, *vp;
+				size_t vlen;
+
+				/* Add header */
+				header = array_append_space(&headers);
+				header->name = p_strdup(pool, hdr->name);
+
+				/* Trim end of field value (not done by parser) */
+				value = hdr->full_value;
+				vp = value + hdr->full_value_len;
+				while ( vp > value &&
+					(vp[-1] == '\t' || vp[-1] == ' ') )
+					vp--;
+				vlen = (size_t)(vp - value);
+	
+				/* Decode MIME encoded-words. */
+				str_truncate(hdr_content, 0);
+				message_header_decode_utf8
+					(value, vlen, hdr_content, NULL);
+				if ( vlen != str_len(hdr_content) ||
+					strncmp(str_c(hdr_content), (const char *)value,
+						vlen) != 0 ) {
+					if ( strlen(str_c(hdr_content)) != str_len(hdr_content) ) {
+						/* replace NULs with spaces */
+						str_replace_nuls(hdr_content);
+					}
+					/* store raw */
+					data = p_malloc(pool, vlen + 1);
+					data[vlen] = '\0';
+					header->value = memcpy(data, value, vlen);
+					header->value_len = vlen;
+					/* store decoded */
+					data = p_malloc(pool, str_len(hdr_content) + 1);
+					data[str_len(hdr_content)] = '\0';
+					header->utf8_value = memcpy(data,
+						str_data(hdr_content), str_len(hdr_content));
+					header->utf8_value_len = str_len(hdr_content);
+				} else {
+					/* raw == decoded */
+					data = p_malloc(pool, vlen + 1);
+					data[vlen] = '\0';
+					header->value = header->utf8_value =
+						memcpy(data, value, vlen);
+					header->value_len = header->utf8_value_len = vlen;
+				}
+
+				if ( hdr_field == _HDR_OTHER )
+					continue;
+			}
+
+			i_assert( body_part != NULL );
+
+			/* Parse the content type from the Content-type header */
+			T_BEGIN {
+				switch ( hdr_field ) {
+				case _HDR_CONTENT_TYPE:
+					body_part->content_type =
+						p_strdup(pool, _parse_content_type(block.hdr));
+					break;
+				case _HDR_CONTENT_DISPOSITION:
+					body_part->content_disposition =
+						p_strdup(pool, _parse_content_disposition(block.hdr));
+					break;
+				default:
+					i_unreached();
+				}
+			} T_END;
+
+			continue;
+		}
+
+		/* Reading body */
+		if ( save_body ) {
+			(void)message_decoder_decode_next_block
+					(decoder, &block, &decoded);
+			buffer_append(buf, decoded.data, decoded.size);
+		}
+	}
+
+	/* Save last body part if necessary */
+	if ( header_part != NULL ) {
+		sieve_message_part_save
+			(renv, buf, header_part, FALSE);
+	} else if ( body_part != NULL && save_body ) {
+		sieve_message_part_save
+			(renv, buf, body_part, extract_text);
+	}
+	if ( iter_all && !array_is_created(&body_part->headers) &&
+		array_count(&headers) > 0 ) {
+		p_array_init(&body_part->headers, pool, array_count(&headers));
+		array_copy(&body_part->headers.arr, 0,
+			&headers.arr, 0, array_count(&headers));
+	}
+
+	/* Try to fill the return_body_parts array once more */
+	have_all = iter_all || sieve_message_body_get_return_parts
+		(renv, content_types, extract_text);
+
+	/* This time, failure is a bug */
+	i_assert(have_all);
+
+	/* Cleanup */
+	(void)message_parser_deinit(&parser, &mparts);
+	message_decoder_deinit(&decoder);
+	buffer_free(&buf);
+
+	/* Return status */
+	if ( input->stream_errno != 0 ) {
+		sieve_runtime_critical(renv, NULL,
+			"failed to read input message",
+			"read(%s) failed: %s",
+			i_stream_get_name(input),
+			i_stream_get_error(input));
+		return SIEVE_EXEC_TEMP_FAILURE;
+	}
+	return SIEVE_EXEC_OK;
+}
+
+int sieve_message_body_get_content
+(const struct sieve_runtime_env *renv,
+	const char * const *content_types,
+	struct sieve_message_part_data **parts_r)
+{
+	struct sieve_message_context *msgctx = renv->msgctx;
+	int status;
+
+	T_BEGIN {
+		/* Fill the return_body_parts array */
+		status = sieve_message_parts_add_missing
+			(renv, content_types, FALSE, FALSE);
+	} T_END;
+
+	/* Check status */
+	if ( status <= 0 )
+		return status;
+
+	/* Return the array of body items */
+	(void) array_append_space(&msgctx->return_body_parts); /* NULL-terminate */
+	*parts_r = array_idx_modifiable(&msgctx->return_body_parts, 0);
+
+	return status;
+}
+
+int sieve_message_body_get_text
+(const struct sieve_runtime_env *renv,
+	struct sieve_message_part_data **parts_r)
+{
+	static const char * const _text_content_types[] =
+		{ "application/xhtml+xml", "text", NULL };
+	struct sieve_message_context *msgctx = renv->msgctx;
+	int status;
+
+	/* We currently only support extracting plain text from:
+
+	    - text/html -> HTML
+	    - application/xhtml+xml -> XHTML
+
+	   Other text types are read as is. Any non-text types are skipped.
+	 */
+
+	T_BEGIN {
+		/* Fill the return_body_parts array */
+		status = sieve_message_parts_add_missing
+			(renv, _text_content_types, TRUE, FALSE);
+	} T_END;
+
+	/* Check status */
+	if ( status <= 0 )
+		return status;
+
+	/* Return the array of body items */
+	(void) array_append_space(&msgctx->return_body_parts); /* NULL-terminate */
+	*parts_r = array_idx_modifiable(&msgctx->return_body_parts, 0);
+
+	return status;
+}
+
+int sieve_message_body_get_raw
+(const struct sieve_runtime_env *renv,
+	struct sieve_message_part_data **parts_r)
+{
+	struct sieve_message_context *msgctx = renv->msgctx;
+	struct sieve_message_part_data *return_part;
+	buffer_t *buf;
+
+	if ( msgctx->raw_body == NULL ) {
+		struct mail *mail = sieve_message_get_mail(renv->msgctx);
+		struct istream *input;
+		struct message_size hdr_size, body_size;
+		const unsigned char *data;
+		size_t size;
+		int ret;
+
+		msgctx->raw_body = buf = buffer_create_dynamic
+			(msgctx->context_pool, 1024*64);
+
+		/* Get stream for message */
+ 		if ( mail_get_stream(mail, &hdr_size, &body_size, &input) < 0 ) {
+			return sieve_runtime_mail_error(renv, mail,
+				"failed to open input message");
+		}
+
+		/* Skip stream to beginning of body */
+		i_stream_skip(input, hdr_size.physical_size);
+
+		/* Read raw message body */
+		while ( (ret=i_stream_read_more(input, &data, &size)) > 0 ) {
+			buffer_append(buf, data, size);
+
+			i_stream_skip(input, size);
+		}
+
+		if ( ret < 0 && input->stream_errno != 0 ) {
+			sieve_runtime_critical(renv, NULL,
+				"failed to read input message",
+				"read(%s) failed: %s",
+				i_stream_get_name(input),
+				i_stream_get_error(input));
+			return SIEVE_EXEC_TEMP_FAILURE;
+		}
+
+		/* Add terminating NUL to the body part buffer */
+		buffer_append_c(buf, '\0');
+
+	} else {
+		buf = msgctx->raw_body;
+	}
+
+	/* Clear result array */
+	array_clear(&msgctx->return_body_parts);
+
+	if ( buf->used > 1  ) {
+		const char *data = (const char *)buf->data;
+		size_t size = buf->used - 1;
+
+		i_assert( data[size] == '\0' );
+
+		/* Add single item to the result */
+		return_part = array_append_space(&msgctx->return_body_parts);
+		return_part->content = data;
+		return_part->size = size;
+	}
+
+	/* Return the array of body items */
+	(void) array_append_space(&msgctx->return_body_parts); /* NULL-terminate */
+	*parts_r = array_idx_modifiable(&msgctx->return_body_parts, 0);
+
+	return SIEVE_EXEC_OK;
+}
+
+/*
+ * Message part iterator
+ */
+
+int sieve_message_part_iter_init
+(struct sieve_message_part_iter *iter,
+	const struct sieve_runtime_env *renv)
+{
+	struct sieve_message_context *msgctx = renv->msgctx;
+	struct sieve_message_part *const *parts;
+	unsigned int count;
+	int status;
+
+	T_BEGIN {
+		/* Fill the return_body_parts array */
+		status = sieve_message_parts_add_missing
+			(renv, NULL, TRUE, TRUE);
+	} T_END;
+
+	/* Check status */
+	if ( status <= 0 )
+		return status;
+
+	i_zero(iter);
+	iter->renv = renv;
+	iter->index = 0;
+	iter->offset = 0;
+
+	parts = array_get(&msgctx->cached_body_parts, &count);
+	if (count == 0)
+		iter->root = NULL;
+	else
+		iter->root = parts[0];
+
+	return SIEVE_EXEC_OK;
+}
+
+void sieve_message_part_iter_subtree(struct sieve_message_part_iter *iter,
+	struct sieve_message_part_iter *subtree)
+{
+	const struct sieve_runtime_env *renv = iter->renv;
+	struct sieve_message_context *msgctx = renv->msgctx;
+	struct sieve_message_part *const *parts;
+	unsigned int count;
+
+	*subtree = *iter;
+
+	parts = array_get(&msgctx->cached_body_parts, &count);
+	if ( subtree->index >= count)
+		subtree->root = NULL;
+	else
+		subtree->root = parts[subtree->index];
+	subtree->offset = subtree->index;
+}
+
+void sieve_message_part_iter_children(struct sieve_message_part_iter *iter,
+	struct sieve_message_part_iter *child)
+{
+	const struct sieve_runtime_env *renv = iter->renv;
+	struct sieve_message_context *msgctx = renv->msgctx;
+	struct sieve_message_part *const *parts;
+	unsigned int count;
+
+	*child = *iter;
+
+	parts = array_get(&msgctx->cached_body_parts, &count);	
+	if ( (child->index+1) >= count || parts[child->index]->children == NULL)
+		child->root = NULL;
+	else
+		child->root = parts[child->index++];
+	child->offset = child->index;
+}
+
+struct sieve_message_part *sieve_message_part_iter_current
+(struct sieve_message_part_iter *iter)
+{
+	const struct sieve_runtime_env *renv = iter->renv;
+	struct sieve_message_context *msgctx = renv->msgctx;
+	struct sieve_message_part *const *parts;
+	unsigned int count;
+
+	if ( iter->root == NULL )
+		return NULL;
+
+	parts = array_get(&msgctx->cached_body_parts, &count);
+	if ( iter->index >= count )
+		return NULL;
+	do {
+		if ( parts[iter->index] == iter->root->next )
+			return NULL;
+		if ( parts[iter->index] == iter->root->parent )
+			return NULL;
+	} while ( parts[iter->index]->epilogue && ++iter->index < count );
+	if ( iter->index >= count )
+		return NULL;
+	return parts[iter->index];
+}
+
+struct sieve_message_part *sieve_message_part_iter_next
+(struct sieve_message_part_iter *iter)
+{
+	const struct sieve_runtime_env *renv = iter->renv;
+	struct sieve_message_context *msgctx = renv->msgctx;
+
+	if ( iter->index >= array_count(&msgctx->cached_body_parts) )
+		return NULL;
+	iter->index++;
+
+	return sieve_message_part_iter_current(iter);
+}
+
+void sieve_message_part_iter_reset
+(struct sieve_message_part_iter *iter)
+{
+	iter->index = iter->offset;
+}
+
+/*
+ * MIME header list
+ */
+
+/* Forward declarations */
+
+static int sieve_mime_header_list_next_item
+	(struct sieve_header_list *_hdrlist, const char **name_r,
+		string_t **value_r);
+static int sieve_mime_header_list_next_value
+	(struct sieve_stringlist *_strlist, string_t **value_r);
+static void sieve_mime_header_list_reset
+	(struct sieve_stringlist *_strlist);
+
+/* Header list object */
+
+struct sieve_mime_header_list {
+	struct sieve_header_list hdrlist;
+
+	struct sieve_stringlist *field_names;
+
+	struct sieve_message_part_iter part_iter;
+
+	const char *header_name;
+	const struct sieve_message_header *headers;
+	unsigned int headers_index, headers_count;
+
+	bool mime_decode:1;
+	bool children:1;
+};
+
+struct sieve_header_list *sieve_mime_header_list_create
+(const struct sieve_runtime_env *renv,
+	struct sieve_stringlist *field_names,
+	struct sieve_message_part_iter *part_iter,
+	bool mime_decode, bool children)
+{
+	struct sieve_mime_header_list *hdrlist;
+
+	hdrlist = t_new(struct sieve_mime_header_list, 1);
+	hdrlist->hdrlist.strlist.runenv = renv;
+	hdrlist->hdrlist.strlist.exec_status = SIEVE_EXEC_OK;
+	hdrlist->hdrlist.strlist.next_item = sieve_mime_header_list_next_value;
+	hdrlist->hdrlist.strlist.reset = sieve_mime_header_list_reset;
+	hdrlist->hdrlist.next_item = sieve_mime_header_list_next_item;
+	hdrlist->field_names = field_names;
+	hdrlist->mime_decode = mime_decode;
+	hdrlist->children = children;
+
+	sieve_message_part_iter_subtree(part_iter, &hdrlist->part_iter);
+
+	return &hdrlist->hdrlist;
+}
+
+/* MIME list implementation */
+
+static void sieve_mime_header_list_next_name
+(struct sieve_mime_header_list *hdrlist)
+{
+	struct sieve_message_part *mpart;
+
+	sieve_message_part_iter_reset(&hdrlist->part_iter);
+	mpart = sieve_message_part_iter_current(&hdrlist->part_iter);
+
+	if ( mpart != NULL && array_is_created(&mpart->headers) ) {
+		hdrlist->headers = array_get
+			(&mpart->headers, &hdrlist->headers_count);
+		hdrlist->headers_index = 0;
+	}
+}
+
+static int sieve_mime_header_list_next_item
+(struct sieve_header_list *_hdrlist, const char **name_r,
+	string_t **value_r)
+{
+	struct sieve_mime_header_list *hdrlist =
+		(struct sieve_mime_header_list *) _hdrlist;
+	const struct sieve_runtime_env *renv = _hdrlist->strlist.runenv;
+
+	if ( name_r != NULL )
+		*name_r = NULL;
+	*value_r = NULL;
+
+	for (;;) {
+		/* Check for end of current header list */
+		if ( hdrlist->headers_count == 0 ||
+			hdrlist->headers_index >= hdrlist->headers_count ) {
+			hdrlist->headers_count = 0;
+			hdrlist->headers_index = 0;
+			hdrlist->headers = NULL;
+		}
+
+		/* Fetch more headers */
+		while ( hdrlist->headers_count == 0 ) {
+			string_t *hdr_item = NULL;
+			int ret;
+
+			if ( hdrlist->header_name != NULL && hdrlist->children ) {
+				struct sieve_message_part *mpart;
+
+				mpart = sieve_message_part_iter_next(&hdrlist->part_iter);
+				if ( mpart != NULL && array_is_created(&mpart->headers) ) {
+					hdrlist->headers = array_get
+						(&mpart->headers, &hdrlist->headers_count);
+					hdrlist->headers_index = 0;
+				}
+				if ( hdrlist->headers_count > 0 ) {
+					if ( _hdrlist->strlist.trace ) {
+						sieve_runtime_trace(renv, 0,
+							"moving to next message part");
+					}
+					break;
+				}
+			}
+
+			/* Read next header name from source list */
+			if ( (ret=sieve_stringlist_next_item
+				(hdrlist->field_names, &hdr_item)) <= 0 )
+				return ret;
+
+			hdrlist->header_name = str_c(hdr_item);
+
+			if ( _hdrlist->strlist.trace ) {
+				sieve_runtime_trace(renv, 0,
+					"extracting `%s' headers from message part",
+					str_sanitize(str_c(hdr_item), 80));
+			}
+
+			sieve_mime_header_list_next_name(hdrlist);
+		}
+
+		for ( ; hdrlist->headers_index < hdrlist->headers_count;
+			hdrlist->headers_index++ ) {
+			const struct sieve_message_header *header =
+				&hdrlist->headers[hdrlist->headers_index];
+
+			if ( strcasecmp(header->name, hdrlist->header_name) == 0 ) {
+				if ( name_r != NULL )
+					*name_r = hdrlist->header_name;
+				if ( hdrlist->mime_decode ) {
+					*value_r = t_str_new_const
+						((const char *)header->utf8_value, header->utf8_value_len);
+				} else {
+					*value_r = t_str_new_const
+						((const char *)header->value, header->value_len);
+				}
+				hdrlist->headers_index++;
+				return 1;
+			}
+		}
+	}
+	
+	i_unreached();	
+	return -1;
+}
+
+static int sieve_mime_header_list_next_value
+(struct sieve_stringlist *_strlist, string_t **value_r)
+{
+	struct sieve_header_list *hdrlist =
+		(struct sieve_header_list *) _strlist;
+
+	return sieve_mime_header_list_next_item
+		(hdrlist, NULL, value_r);
+}
+
+static void sieve_mime_header_list_reset
+(struct sieve_stringlist *strlist)
+{
+	struct sieve_mime_header_list *hdrlist =
+		(struct sieve_mime_header_list *) strlist;
+
+	sieve_stringlist_reset(hdrlist->field_names);
+	hdrlist->header_name = NULL;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-message.h
@@ -0,0 +1,280 @@
+#ifndef SIEVE_MESSAGE_H
+#define SIEVE_MESSAGE_H
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-objects.h"
+
+/*
+ * Message transmission
+ */
+
+const char *sieve_message_get_new_id(const struct sieve_instance *svinst);
+
+/*
+ * Message context
+ */
+
+struct sieve_message_context;
+
+struct sieve_message_context *sieve_message_context_create
+	(struct sieve_instance *svinst, struct mail_user *mail_user,
+		const struct sieve_message_data *msgdata);
+void sieve_message_context_ref(struct sieve_message_context *msgctx);
+void sieve_message_context_unref(struct sieve_message_context **msgctx);
+
+void sieve_message_context_reset(struct sieve_message_context *msgctx);
+
+pool_t sieve_message_context_pool
+	(struct sieve_message_context *msgctx) ATTR_PURE;
+void sieve_message_context_time(struct sieve_message_context *msgctx,
+	struct timeval *time);
+
+/* Extension support */
+
+void sieve_message_context_extension_set
+	(struct sieve_message_context *msgctx, const struct sieve_extension *ext,
+		void *context);
+const void *sieve_message_context_extension_get
+	(struct sieve_message_context *msgctx, const struct sieve_extension *ext);
+
+/* Envelope */
+
+const struct smtp_address *sieve_message_get_final_recipient
+	(struct sieve_message_context *msgctx);
+const struct smtp_address *sieve_message_get_orig_recipient
+	(struct sieve_message_context *msgctx);
+
+const struct smtp_address *sieve_message_get_sender
+	(struct sieve_message_context *msgctx);
+
+/* Mail */
+
+struct mail *sieve_message_get_mail
+	(struct sieve_message_context *msgctx);
+
+int sieve_message_substitute
+	(struct sieve_message_context *msgctx, struct istream *input);
+struct edit_mail *sieve_message_edit
+	(struct sieve_message_context *msgctx);
+void sieve_message_snapshot
+	(struct sieve_message_context *msgctx);
+
+/*
+ * Header stringlist
+ */
+
+struct sieve_header_list {
+	struct sieve_stringlist strlist;
+
+	int (*next_item)
+		(struct sieve_header_list *_hdrlist, const char **name_r,
+			string_t **value_r) ATTR_NULL(2);
+};
+
+static inline int sieve_header_list_next_item
+(struct sieve_header_list *hdrlist, const char **name_r,
+	string_t **value_r) ATTR_NULL(2)
+{
+	return hdrlist->next_item(hdrlist, name_r, value_r);
+}
+
+static inline void sieve_header_list_reset
+(struct sieve_header_list *hdrlist)
+{
+	sieve_stringlist_reset(&hdrlist->strlist);
+}
+
+static inline int sieve_header_list_get_length
+(struct sieve_header_list *hdrlist)
+{
+	return sieve_stringlist_get_length(&hdrlist->strlist);
+}
+
+static inline void sieve_header_list_set_trace
+(struct sieve_header_list *hdrlist, bool trace)
+{
+	sieve_stringlist_set_trace(&hdrlist->strlist, trace);
+}
+
+struct sieve_header_list *sieve_message_header_list_create
+	(const struct sieve_runtime_env *renv,
+		struct sieve_stringlist *field_names,
+		bool mime_decode);
+
+/*
+ * Message override
+ */
+
+/* Header override object */
+
+struct sieve_message_override_def {
+	struct sieve_object_def obj_def;
+
+	unsigned int sequence;
+
+	/* Context coding */
+
+	bool (*dump_context)
+		(const struct sieve_message_override *svmo,
+			const struct sieve_dumptime_env *denv, sieve_size_t *address);
+	int (*read_context)
+		(const struct sieve_message_override *svmo,
+			const struct sieve_runtime_env *renv, sieve_size_t *address,
+			void **se_context);
+
+	/* Override */
+
+	int (*header_override)
+		(const struct sieve_message_override *svmo,
+			const struct sieve_runtime_env *renv,
+			bool mime_decode, struct sieve_stringlist **headers);
+};
+
+struct sieve_message_override {
+	struct sieve_object object;
+
+	const struct sieve_message_override_def *def;
+
+	void *context;
+};
+
+ARRAY_DEFINE_TYPE(sieve_message_override,
+	struct sieve_message_override);
+
+/*
+ * Message override operand
+ */
+
+#define SIEVE_EXT_DEFINE_MESSAGE_OVERRIDE(SVMO) SIEVE_EXT_DEFINE_OBJECT(SVMO)
+#define SIEVE_EXT_DEFINE_MESSAGE_OVERRIDES(SVMOS) SIEVE_EXT_DEFINE_OBJECTS(SMOS)
+
+#define SIEVE_OPT_MESSAGE_OVERRIDE (-2)
+
+extern const struct sieve_operand_class
+	sieve_message_override_operand_class;
+
+static inline void sieve_opr_message_override_emit
+(struct sieve_binary_block *sblock, const struct sieve_extension *ext,
+	const struct sieve_message_override_def *seff)
+{
+	sieve_opr_object_emit(sblock, ext, &seff->obj_def);
+}
+
+bool sieve_opr_message_override_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+int sieve_opr_message_override_read
+	(const struct sieve_runtime_env *renv, sieve_size_t *address,
+		struct sieve_message_override *svmo);
+
+/*
+ * Optional operands
+ */
+
+int sieve_message_opr_optional_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+		signed int *opt_code);
+
+int sieve_message_opr_optional_read
+	(const struct sieve_runtime_env *renv, sieve_size_t *address,
+		signed int *opt_code, int *exec_status,
+		struct sieve_address_part *addrp, struct sieve_match_type *mcht,
+		struct sieve_comparator *cmp, 
+		ARRAY_TYPE(sieve_message_override) *svmos);
+
+/*
+ * Message header
+ */
+
+int sieve_message_get_header_fields
+	(const struct sieve_runtime_env *renv,
+		struct sieve_stringlist *field_names,
+		ARRAY_TYPE(sieve_message_override) *svmos,
+		bool mime_decode, struct sieve_stringlist **fields_r);
+
+/*
+ * Message part
+ */
+
+struct sieve_message_part;
+
+struct sieve_message_part_data {
+	const char *content_type;
+	const char *content_disposition;
+
+	const char *content;
+	unsigned long size;
+};
+
+struct sieve_message_part *sieve_message_part_parent
+	(struct sieve_message_part *mpart) ATTR_PURE;
+struct sieve_message_part *sieve_message_part_next
+	(struct sieve_message_part *mpart) ATTR_PURE;
+struct sieve_message_part *sieve_message_part_children
+	(struct sieve_message_part *mpart) ATTR_PURE;
+
+const char *sieve_message_part_content_type
+	(struct sieve_message_part *mpart) ATTR_PURE;
+const char *sieve_message_part_content_disposition
+	(struct sieve_message_part *mpart) ATTR_PURE;
+
+int sieve_message_part_get_first_header
+	(struct sieve_message_part *mpart, const char *field,
+		const char **value_r);
+
+void sieve_message_part_get_data
+	(struct sieve_message_part *mpart,
+		struct sieve_message_part_data *data, bool text);
+
+/*
+ * Message body
+ */
+
+int sieve_message_body_get_content
+	(const struct sieve_runtime_env *renv,
+		const char * const *content_types,
+		struct sieve_message_part_data **parts_r);
+int sieve_message_body_get_text
+	(const struct sieve_runtime_env *renv,
+		struct sieve_message_part_data **parts_r);
+int sieve_message_body_get_raw
+	(const struct sieve_runtime_env *renv,
+		struct sieve_message_part_data **parts_r);
+
+/*
+ * Message part iterator
+ */
+
+struct sieve_message_part_iter {
+	const struct sieve_runtime_env *renv;
+	struct sieve_message_part *root;
+	unsigned int index, offset;
+};
+
+int sieve_message_part_iter_init
+(struct sieve_message_part_iter *iter,
+	const struct sieve_runtime_env *renv);
+void sieve_message_part_iter_subtree(struct sieve_message_part_iter *iter,
+	struct sieve_message_part_iter *subtree);
+void sieve_message_part_iter_children(struct sieve_message_part_iter *iter,
+	struct sieve_message_part_iter *child);
+
+struct sieve_message_part *sieve_message_part_iter_current
+(struct sieve_message_part_iter *iter);
+struct sieve_message_part *sieve_message_part_iter_next
+(struct sieve_message_part_iter *iter);
+
+void sieve_message_part_iter_reset
+(struct sieve_message_part_iter *iter);
+
+/*
+ * MIME header list
+ */
+
+struct sieve_header_list *sieve_mime_header_list_create
+(const struct sieve_runtime_env *renv,
+	struct sieve_stringlist *field_names,
+	struct sieve_message_part_iter *part_iter,
+	bool mime_decode, bool children);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-objects.c
@@ -0,0 +1,111 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+#include "sieve-interpreter.h"
+
+#include "sieve-objects.h"
+
+/*
+ * Object coding
+ */
+
+void sieve_opr_object_emit
+(struct sieve_binary_block *sblock, const struct sieve_extension *ext,
+	const struct sieve_object_def *obj_def)
+{
+	struct sieve_extension_objects *objs =
+		(struct sieve_extension_objects *) obj_def->operand->interface;
+
+	(void) sieve_operand_emit(sblock, ext, obj_def->operand);
+
+	if ( objs->count > 1 ) {
+		(void) sieve_binary_emit_byte(sblock, obj_def->code);
+	}
+}
+
+bool sieve_opr_object_read_data
+(struct sieve_binary_block *sblock, const struct sieve_operand *operand,
+	const struct sieve_operand_class *opclass, sieve_size_t *address,
+	struct sieve_object *obj)
+{
+	const struct sieve_extension_objects *objs;
+	unsigned int obj_code;
+
+	if ( operand == NULL || operand->def->class != opclass )
+		return FALSE;
+
+	objs = (struct sieve_extension_objects *) operand->def->interface;
+	if ( objs == NULL )
+		return FALSE;
+
+	if ( objs->count > 1 ) {
+		if ( !sieve_binary_read_byte(sblock, address, &obj_code) )
+			return FALSE;
+
+		if ( obj_code < objs->count ) {
+			const struct sieve_object_def *const *objects =
+				(const struct sieve_object_def *const *) objs->objects;
+
+			obj->def = objects[obj_code];
+			obj->ext = operand->ext;
+			return TRUE;
+		}
+	}
+
+	obj->def = (const struct sieve_object_def *) objs->objects;
+	obj->ext = operand->ext;
+	return TRUE;
+}
+
+bool sieve_opr_object_read
+(const struct sieve_runtime_env *renv,
+	const struct sieve_operand_class *opclass, sieve_size_t *address,
+	struct sieve_object *obj)
+{
+	struct sieve_operand operand;
+
+	if ( !sieve_operand_read(renv->sblock, address, NULL, &operand) ) {
+		return FALSE;
+	}
+
+	return sieve_opr_object_read_data
+		(renv->sblock, &operand, opclass, address, obj);
+}
+
+bool sieve_opr_object_dump
+(const struct sieve_dumptime_env *denv,
+	const struct sieve_operand_class *opclass, sieve_size_t *address,
+	struct sieve_object *obj)
+{
+	struct sieve_operand operand;
+	struct sieve_object obj_i;
+	const char *class;
+
+	if ( obj == NULL )
+		obj = &obj_i;
+
+	sieve_code_mark(denv);
+
+	if ( !sieve_operand_read(denv->sblock, address, NULL, &operand) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_opr_object_read_data
+		(denv->sblock, &operand, opclass, address, obj) )
+		return FALSE;
+
+	if ( operand.def->class == NULL )
+		class = "OBJECT";
+	else
+		class = operand.def->class->name;
+
+	sieve_code_dumpf(denv, "%s: %s", class, obj->def->identifier);
+
+	return TRUE;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-objects.h
@@ -0,0 +1,67 @@
+#ifndef SIEVE_OBJECTS_H
+#define SIEVE_OBJECTS_H
+
+/*
+ * Object definition
+ */
+
+struct sieve_object_def {
+	const char *identifier;
+	const struct sieve_operand_def *operand;
+	unsigned int code;
+};
+
+#define SIEVE_OBJECT(_identifier, _operand, _code) \
+	.obj_def = { \
+		.identifier = (_identifier), \
+		.operand = (_operand), \
+		.code = (_code) \
+	}
+
+/*
+ * Object instance
+ */
+
+struct sieve_object {
+	const struct sieve_object_def *def;
+	const struct sieve_extension *ext;
+};
+
+#define SIEVE_OBJECT_DEFAULT(_obj) \
+	{ &((_obj).obj_def), NULL }
+
+#define SIEVE_OBJECT_EXTENSION(_obj) \
+	(_obj->object.ext)
+
+#define SIEVE_OBJECT_SET_DEF(_obj, def_value) \
+	STMT_START { \
+			(_obj)->def = def_value;	\
+			(_obj)->object.def = &(_obj)->def->obj_def; \
+	} STMT_END
+
+
+/*
+ * Object coding
+ */
+
+void sieve_opr_object_emit
+	(struct sieve_binary_block *sblock, const struct sieve_extension *ext,
+		const struct sieve_object_def *obj_def);
+
+bool sieve_opr_object_read_data
+	(struct sieve_binary_block *sblock, const struct sieve_operand *operand,
+		const struct sieve_operand_class *opclass, sieve_size_t *address,
+		struct sieve_object *obj);
+
+bool sieve_opr_object_read
+	(const struct sieve_runtime_env *renv,
+		const struct sieve_operand_class *opclass, sieve_size_t *address,
+		struct sieve_object *obj);
+
+bool sieve_opr_object_dump
+	(const struct sieve_dumptime_env *denv,
+		const struct sieve_operand_class *opclass, sieve_size_t *address,
+		struct sieve_object *obj);
+
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-parser.c
@@ -0,0 +1,654 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "istream.h"
+#include "failures.h"
+
+#include "sieve-common.h"
+#include "sieve-limits.h"
+#include "sieve-script.h"
+#include "sieve-lexer.h"
+#include "sieve-parser.h"
+#include "sieve-error.h"
+#include "sieve-ast.h"
+
+/*
+ * Forward declarations
+ */
+
+inline static void sieve_parser_error
+	(struct sieve_parser *parser, const char *fmt, ...) ATTR_FORMAT(2, 3);
+
+static int sieve_parser_recover
+	(struct sieve_parser *parser, enum sieve_token_type end_token);
+
+/*
+ * Parser object
+ */
+
+struct sieve_parser {
+	pool_t pool;
+
+	bool valid;
+
+	struct sieve_script *script;
+
+	struct sieve_error_handler *ehandler;
+
+	const struct sieve_lexer *lexer;
+	struct sieve_ast *ast;
+};
+
+struct sieve_parser *sieve_parser_create
+(struct sieve_script *script, struct sieve_error_handler *ehandler,
+	enum sieve_error *error_r)
+{
+	struct sieve_parser *parser;
+	const struct sieve_lexer *lexer;
+
+	lexer = sieve_lexer_create(script, ehandler, error_r);
+
+	if ( lexer != NULL ) {
+		pool_t pool = pool_alloconly_create("sieve_parser", 4096);
+
+		parser = p_new(pool, struct sieve_parser, 1);
+		parser->pool = pool;
+		parser->valid = TRUE;
+
+		parser->ehandler = ehandler;
+		sieve_error_handler_ref(ehandler);
+
+		parser->script = script;
+		sieve_script_ref(script);
+
+		parser->lexer = lexer;
+		parser->ast = NULL;
+
+		return parser;
+	}
+
+	return NULL;
+}
+
+void sieve_parser_free(struct sieve_parser **parser)
+{
+	if ((*parser)->ast != NULL)
+		sieve_ast_unref(&(*parser)->ast);
+
+	sieve_lexer_free(&(*parser)->lexer);
+	sieve_script_unref(&(*parser)->script);
+
+	sieve_error_handler_unref(&(*parser)->ehandler);
+
+	pool_unref(&(*parser)->pool);
+
+	*parser = NULL;
+}
+
+/*
+ * Internal error handling
+ */
+
+inline static void sieve_parser_error
+(struct sieve_parser *parser, const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+
+	/* Don't report a parse error if the lexer complained already */
+	if ( sieve_lexer_token_type(parser->lexer) != STT_ERROR )
+	{
+		T_BEGIN {
+			sieve_verror(parser->ehandler,
+				sieve_error_script_location(parser->script,
+					sieve_lexer_token_line(parser->lexer)),
+				fmt, args);
+		} T_END;
+	}
+
+	parser->valid = FALSE;
+
+	va_end(args);
+}
+
+/*
+ * Sieve grammar parsing
+ */
+
+/* sieve_parse_arguments():
+ *
+ * Parses both command arguments and sub-tests:
+ *   arguments = *argument [test / test-list]
+ *   argument = string-list / number / tag
+ *   string = quoted-string / multi-line   [[implicitly handled in lexer]]
+ *   string-list = "[" string *("," string) "]" / string         ;; if
+ *     there is only a single string, the brackets are optional
+ *   test-list = "(" test *("," test) ")"
+ *   test = identifier arguments
+ */
+static int sieve_parse_arguments
+(struct sieve_parser *parser, struct sieve_ast_node *node, unsigned int depth)
+{
+	const struct sieve_lexer *lexer = parser->lexer;
+	struct sieve_ast_node *test = NULL;
+	bool test_present = TRUE;
+	bool arg_present = TRUE;
+	int result = 1; /* Indicates whether the parser is in a defined, not
+	                    necessarily error-free state */
+
+	/* Parse arguments */
+	while ( arg_present && result > 0 ) {
+		struct sieve_ast_argument *arg;
+
+		if ( !parser->valid && !sieve_errors_more_allowed(parser->ehandler) ) {
+			result = 0;
+			break;
+		}
+
+		switch ( sieve_lexer_token_type(lexer) ) {
+
+		/* String list */
+		case STT_LSQUARE:
+			/* Create stinglist object */
+			arg = sieve_ast_argument_stringlist_create
+				(node, sieve_lexer_token_line(parser->lexer));
+
+			if ( arg == NULL ) break;
+
+			sieve_lexer_skip_token(lexer);
+
+			if ( sieve_lexer_token_type(lexer) == STT_STRING ) {
+				bool add_failed = FALSE;
+
+				/* Add the string to the list */
+				if ( !sieve_ast_stringlist_add
+					(arg, sieve_lexer_token_str(lexer),
+						sieve_lexer_token_line(parser->lexer)) )
+					add_failed = TRUE;
+
+				sieve_lexer_skip_token(lexer);
+
+				while ( !add_failed && sieve_lexer_token_type(lexer) == STT_COMMA ) {
+					sieve_lexer_skip_token(lexer);
+
+					/* Check parser status */
+					if ( !parser->valid && !sieve_errors_more_allowed(parser->ehandler) ) {
+						result = sieve_parser_recover(parser, STT_RSQUARE);
+						break;
+					}
+
+					if ( sieve_lexer_token_type(lexer) == STT_STRING ) {
+						/* Add the string to the list */
+						if ( !sieve_ast_stringlist_add
+							(arg, sieve_lexer_token_str(lexer),
+								sieve_lexer_token_line(parser->lexer)) )
+							add_failed = TRUE;
+
+						sieve_lexer_skip_token(lexer);
+					} else {
+						sieve_parser_error(parser,
+							"expecting string after ',' in string list, but found %s",
+							sieve_lexer_token_description(lexer));
+
+						result = sieve_parser_recover(parser, STT_RSQUARE);
+						break;
+					}
+				}
+
+				if ( add_failed ) {
+					sieve_parser_error(parser,
+						"failed to accept more items in string list");
+					return -1;
+				}
+			} else {
+				sieve_parser_error(parser,
+					"expecting string after '[' in string list, but found %s",
+					sieve_lexer_token_description(lexer));
+
+				result = sieve_parser_recover(parser, STT_RSQUARE);
+			}
+
+			/* Finish the string list */
+			if ( sieve_lexer_token_type(lexer) == STT_RSQUARE ) {
+				sieve_lexer_skip_token(lexer);
+			} else {
+				sieve_parser_error(parser,
+					"expecting ',' or end of string list ']', but found %s",
+					sieve_lexer_token_description(lexer));
+
+				if ( (result=sieve_parser_recover(parser, STT_RSQUARE)) > 0 )
+					sieve_lexer_skip_token(lexer);
+			}
+
+			break;
+
+		/* Single string */
+		case STT_STRING:
+			arg = sieve_ast_argument_string_create
+				(node, sieve_lexer_token_str(lexer),
+					sieve_lexer_token_line(parser->lexer));
+
+			sieve_lexer_skip_token(lexer);
+			break;
+
+		/* Number */
+		case STT_NUMBER:
+			arg = sieve_ast_argument_number_create
+				(node, sieve_lexer_token_int(lexer),
+					sieve_lexer_token_line(parser->lexer));
+			sieve_lexer_skip_token(lexer);
+			break;
+
+		/* Tag */
+		case STT_TAG:
+			arg = sieve_ast_argument_tag_create
+				(node, sieve_lexer_token_ident(lexer),
+					sieve_lexer_token_line(parser->lexer));
+			sieve_lexer_skip_token(lexer);
+			break;
+
+		/* End of argument list, continue with tests */
+		default:
+			arg_present = FALSE;
+			break;
+		}
+
+		if ( arg_present && arg == NULL ) {
+			sieve_parser_error(parser,
+				"failed to accept more arguments for command '%s'", node->identifier);
+			return -1;
+		}
+
+		if ( sieve_ast_argument_count(node) > SIEVE_MAX_COMMAND_ARGUMENTS ) {
+			sieve_parser_error(parser,
+				"too many arguments for command '%s'", node->identifier);
+			return 0;
+		}
+	}
+
+	if ( result <= 0 ) return result; /* Defer recovery to caller */
+
+	/* --> [ test / test-list ]
+ 	 * test-list = "(" test *("," test) ")"
+	 * test = identifier arguments
+	 */
+	switch ( sieve_lexer_token_type(lexer) ) {
+
+	/* Single test */
+	case STT_IDENTIFIER:
+		if ( depth+1 > SIEVE_MAX_TEST_NESTING ) {
+			sieve_parser_error(parser,
+				"cannot nest tests deeper than %u levels",
+				SIEVE_MAX_TEST_NESTING);
+			return 0;
+		}
+
+		test = sieve_ast_test_create
+			(node, sieve_lexer_token_ident(lexer),
+				sieve_lexer_token_line(parser->lexer));
+		sieve_lexer_skip_token(lexer);
+
+		/* Theoretically, test can be NULL */
+		if ( test == NULL ) break;
+
+		/* Parse test arguments, which may include more tests (recurse) */
+		if ( sieve_parse_arguments(parser, test, depth+1) <= 0 ) {
+			return 0; /* Defer recovery to caller */
+		}
+
+		break;
+
+	/* Test list */
+	case STT_LBRACKET:
+		sieve_lexer_skip_token(lexer);
+
+		if ( depth+1 > SIEVE_MAX_TEST_NESTING ) {
+			sieve_parser_error(parser,
+				"cannot nest tests deeper than %u levels",
+				SIEVE_MAX_TEST_NESTING);
+			result = sieve_parser_recover(parser, STT_RBRACKET);
+
+			if ( result > 0 ) sieve_lexer_skip_token(lexer);
+			return result;
+		}
+
+		node->test_list = TRUE;
+
+		/* Test starts with identifier */
+		if ( sieve_lexer_token_type(lexer) == STT_IDENTIFIER ) {
+			test = sieve_ast_test_create
+				(node, sieve_lexer_token_ident(lexer),
+					sieve_lexer_token_line(parser->lexer));
+			sieve_lexer_skip_token(lexer);
+
+			if ( test == NULL ) break;
+
+			/* Parse test arguments, which may include more tests (recurse) */
+			if ( (result=sieve_parse_arguments(parser, test, depth+1)) > 0 ) {
+
+				/* More tests ? */
+				while ( sieve_lexer_token_type(lexer) == STT_COMMA ) {
+					sieve_lexer_skip_token(lexer);
+
+					/* Check parser status */
+					if ( !parser->valid && !sieve_errors_more_allowed(parser->ehandler) ) {
+						result = sieve_parser_recover(parser, STT_RBRACKET);
+						break;
+					}
+
+					/* Test starts with identifier */
+					if ( sieve_lexer_token_type(lexer) == STT_IDENTIFIER ) {
+						test = sieve_ast_test_create
+							(node, sieve_lexer_token_ident(lexer),
+								sieve_lexer_token_line(parser->lexer));
+						sieve_lexer_skip_token(lexer);
+
+						if ( test == NULL ) break;
+
+						/* Parse test arguments, which may include more tests (recurse) */
+						if ( (result=sieve_parse_arguments(parser, test, depth+1)) <= 0 ) {
+							if ( result < 0 ) return result;
+							result = sieve_parser_recover(parser, STT_RBRACKET);
+							break;
+						}
+					} else {
+						sieve_parser_error(parser,
+							"expecting test identifier after ',' in test list, but found %s",
+							sieve_lexer_token_description(lexer));
+
+						result = sieve_parser_recover(parser, STT_RBRACKET);
+						break;
+					}
+				}
+
+				if ( test == NULL ) break;
+			} else {
+				if ( result < 0 ) return result;
+
+				result = sieve_parser_recover(parser, STT_RBRACKET);
+			}
+		} else {
+			sieve_parser_error(parser,
+				"expecting test identifier after '(' in test list, but found %s",
+				sieve_lexer_token_description(lexer));
+
+			result = sieve_parser_recover(parser, STT_RBRACKET);
+		}
+
+		/* The next token should be a ')', indicating the end of the test list
+		 *   --> previous sieve_parser_recover calls try to restore this situation
+		 *       after parse errors.
+		 */
+ 		if ( sieve_lexer_token_type(lexer) == STT_RBRACKET ) {
+			sieve_lexer_skip_token(lexer);
+		} else {
+			sieve_parser_error(parser,
+				"expecting ',' or end of test list ')', but found %s",
+				sieve_lexer_token_description(lexer));
+
+			/* Recover function tries to make next token equal to ')'. If it succeeds
+			 * we need to skip it.
+			 */
+			if ( (result=sieve_parser_recover(parser, STT_RBRACKET)) > 0 )
+				sieve_lexer_skip_token(lexer);
+		}
+		break;
+
+	default:
+		/* Not an error: test / test-list is optional
+		 *   --> any errors are detected by the caller
+		 */
+		test_present = FALSE;
+		break;
+	}
+
+	if ( test_present && test == NULL ) {
+		sieve_parser_error(parser,
+			"failed to accept more tests for command '%s'", node->identifier);
+		return -1;
+	}
+
+	return result;
+}
+
+/* commands = *command
+ * command = identifier arguments ( ";" / block )
+ * block = "{" commands "}"
+ */
+static int sieve_parse_commands
+(struct sieve_parser *parser, struct sieve_ast_node *block, unsigned int depth)
+{
+	const struct sieve_lexer *lexer = parser->lexer;
+	int result = 1;
+
+	while ( result > 0 &&
+		sieve_lexer_token_type(lexer) == STT_IDENTIFIER ) {
+		struct sieve_ast_node *command;
+
+		/* Check parser status */
+		if ( !parser->valid && !sieve_errors_more_allowed(parser->ehandler) ) {
+			result = sieve_parser_recover(parser, STT_SEMICOLON);
+			break;
+		}
+
+		/* Create command node */
+		command = sieve_ast_command_create
+			(block, sieve_lexer_token_ident(lexer),
+				sieve_lexer_token_line(parser->lexer));
+		sieve_lexer_skip_token(lexer);
+
+		if ( command == NULL ) {
+			sieve_parser_error(parser,
+				"failed to accept more commands inside the block of command '%s'",
+				block->identifier);
+			return -1;
+		}
+
+		result = sieve_parse_arguments(parser, command, 1);
+
+		/* Check whether the command is properly terminated
+		 * (i.e. with ; or a new block)
+		 */
+		if ( result > 0 &&
+			sieve_lexer_token_type(lexer) != STT_SEMICOLON &&
+			sieve_lexer_token_type(lexer) != STT_LCURLY ) {
+
+			sieve_parser_error(parser,
+				"expected end of command ';' or the beginning of a compound block '{', "
+				"but found %s",
+				sieve_lexer_token_description(lexer));
+			result = 0;
+		}
+
+		/* Try to recover from parse errors to reacquire a defined state */
+		if ( result == 0 ) {
+			result = sieve_parser_recover(parser, STT_SEMICOLON);
+		}
+
+		/* Don't bother to continue if we are not in a defined state */
+		if ( result <= 0 ) {
+			return result;
+		}
+
+		switch ( sieve_lexer_token_type(lexer) ) {
+
+		/* End of the command */
+		case STT_SEMICOLON:
+			sieve_lexer_skip_token(lexer);
+			break;
+
+		/* Command has a block {...} */
+		case STT_LCURLY:
+			sieve_lexer_skip_token(lexer);
+
+			/* Check current depth first */
+			if ( depth+1 > SIEVE_MAX_BLOCK_NESTING ) {
+				sieve_parser_error(parser,
+					"cannot nest command blocks deeper than %u levels",
+					SIEVE_MAX_BLOCK_NESTING);
+				result = sieve_parser_recover(parser, STT_RCURLY);
+
+				if ( result > 0 )
+					sieve_lexer_skip_token(lexer);
+				break;
+			}
+
+			command->block = TRUE;
+
+			if ( (result=sieve_parse_commands(parser, command, depth+1)) > 0 ) {
+
+				if ( sieve_lexer_token_type(lexer) != STT_RCURLY ) {
+					sieve_parser_error(parser,
+						"expected end of compound block '}', but found %s",
+						sieve_lexer_token_description(lexer));
+					result = sieve_parser_recover(parser, STT_RCURLY);
+				} else
+					sieve_lexer_skip_token(lexer);
+			} else {
+				if ( result < 0 ) return result;
+
+				if ( (result=sieve_parser_recover(parser, STT_RCURLY)) > 0 )
+					sieve_lexer_skip_token(lexer);
+			}
+
+			break;
+
+		default:
+			/* Recovered previously, so this cannot happen */
+			i_unreached();
+		}
+	}
+
+	return result;
+}
+
+bool sieve_parser_run
+(struct sieve_parser *parser, struct sieve_ast **ast)
+{
+	if ( parser->ast != NULL )
+		sieve_ast_unref(&parser->ast);
+
+	/* Create AST object if none is provided */
+	if ( *ast == NULL )
+		*ast = sieve_ast_create(parser->script);
+	else
+		sieve_ast_ref(*ast);
+
+	parser->ast = *ast;
+
+	/* Scan first token */
+	sieve_lexer_skip_token(parser->lexer);
+
+	/* Parse */
+	if ( sieve_parse_commands(parser, sieve_ast_root(parser->ast), 1) > 0 &&
+		parser->valid ) {
+
+		/* Parsed right to EOF ? */
+		if ( sieve_lexer_token_type(parser->lexer) != STT_EOF ) {
+			sieve_parser_error(parser,
+				"unexpected %s found at (the presumed) end of file",
+				sieve_lexer_token_description(parser->lexer));
+			parser->valid = FALSE;
+		}
+	} else parser->valid = FALSE;
+
+	/* Clean up AST if parse failed */
+	if ( !parser->valid ) {
+		parser->ast = NULL;
+		sieve_ast_unref(ast);
+	}
+
+	return parser->valid;
+}
+
+/* Error recovery:
+ *   To continue parsing after an error it is important to find the next
+ *   parsible item in the stream. The recover function skips over the remaining
+ *   garbage after an error. It tries  to find the end of the failed syntax
+ *   structure and takes nesting of structures into account.
+ */
+
+/* Assign useful names to priorities for readability */
+enum sieve_grammatical_prio {
+	SGP_BLOCK = 3,
+	SGP_COMMAND = 2,
+	SGP_TEST_LIST = 1,
+	SGP_STRING_LIST = 0,
+
+	SGP_OTHER = -1
+};
+
+static inline enum sieve_grammatical_prio __get_token_priority
+(enum sieve_token_type token)
+{
+	switch ( token ) {
+	case STT_LCURLY:
+	case STT_RCURLY:
+		return SGP_BLOCK;
+	case STT_SEMICOLON:
+		return SGP_COMMAND;
+	case STT_LBRACKET:
+	case STT_RBRACKET:
+		return SGP_TEST_LIST;
+	case STT_LSQUARE:
+	case STT_RSQUARE:
+		return SGP_STRING_LIST;
+	default:
+		break;
+	}
+
+	return SGP_OTHER;
+}
+
+static int sieve_parser_recover
+(struct sieve_parser *parser, enum sieve_token_type end_token)
+{
+	/* The tokens that begin/end a specific block/command/list in order
+ 	 * of ascending grammatical priority.
+ 	 */
+ 	static const enum sieve_token_type begin_tokens[4] =
+ 		{ STT_LSQUARE, STT_LBRACKET, STT_NONE, STT_LCURLY };
+	static const enum sieve_token_type end_tokens[4] =
+		{ STT_RSQUARE, STT_RBRACKET, STT_SEMICOLON, STT_RCURLY};
+
+	const struct sieve_lexer *lexer = parser->lexer;
+	int nesting = 1;
+	enum sieve_grammatical_prio end_priority = __get_token_priority(end_token);
+
+	i_assert( end_priority != SGP_OTHER );
+
+	while ( sieve_lexer_token_type(lexer) != STT_EOF &&
+		__get_token_priority(sieve_lexer_token_type(lexer)) <= end_priority ) {
+
+		if ( sieve_lexer_token_type(lexer) == begin_tokens[end_priority] ) {
+			nesting++;
+			sieve_lexer_skip_token(lexer);
+			continue;
+		}
+
+		if ( sieve_lexer_token_type(lexer) == end_tokens[end_priority] ) {
+			nesting--;
+
+			if ( nesting == 0 ) {
+				/* Next character is the end */
+				return 1;
+			}
+		}
+
+		sieve_lexer_skip_token(lexer);
+	}
+
+	/* Special case: COMMAND */
+	if (end_token == STT_SEMICOLON &&
+		sieve_lexer_token_type(lexer) == STT_LCURLY) {
+		return 1;
+	}
+
+	/* End not found before eof or end of surrounding grammatical structure
+	 */
+	return 0;
+}
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-parser.h
@@ -0,0 +1,16 @@
+#ifndef SIEVE_PARSER_H
+#define SIEVE_PARSER_H
+
+#include "lib.h"
+
+#include "sieve-common.h"
+
+struct sieve_parser;
+
+struct sieve_parser *sieve_parser_create
+	(struct sieve_script *script, struct sieve_error_handler *ehandler,
+		enum sieve_error *error_r);
+void sieve_parser_free(struct sieve_parser **parser);
+bool sieve_parser_run(struct sieve_parser *parser, struct sieve_ast **ast);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-plugins.c
@@ -0,0 +1,181 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "module-dir.h"
+
+#include "sieve-settings.h"
+#include "sieve-extensions.h"
+
+#include "sieve-common.h"
+#include "sieve-plugins.h"
+
+/*
+ * Types
+ */
+
+typedef void (*sieve_plugin_load_func_t)
+	(struct sieve_instance *svinst, void **context);
+typedef void (*sieve_plugin_unload_func_t)
+	(struct sieve_instance *svinst, void *context);
+
+struct sieve_plugin {
+	struct module *module;
+
+	void *context;
+
+	struct sieve_plugin *next;
+};
+
+/*
+ * Plugin support
+ */
+
+static struct module *sieve_modules = NULL;
+static int sieve_modules_refcount = 0;
+
+static struct module *sieve_plugin_module_find(const char *name)
+{
+	struct module *module;
+
+	module = sieve_modules;
+	while ( module != NULL ) {
+		const char *mod_name;
+
+		/* Strip module names */
+		mod_name = module_get_plugin_name(module);
+
+		if ( strcmp(mod_name, name) == 0 )
+			return module;
+
+		module = module->next;
+	}
+
+	return NULL;
+}
+
+void sieve_plugins_load
+(struct sieve_instance *svinst, const char *path, const char *plugins)
+{
+	struct module *module;
+	struct module_dir_load_settings mod_set;
+	const char **module_names;
+	unsigned int i;
+
+	/* Determine what to load */
+
+	if ( path == NULL && plugins == NULL ) {
+		path = sieve_setting_get(svinst, "sieve_plugin_dir");
+		plugins = sieve_setting_get(svinst, "sieve_plugins");
+	}
+
+	if ( plugins == NULL || *plugins == '\0' )
+		return;
+
+	if ( path == NULL || *path == '\0' )
+		path = MODULEDIR"/sieve";
+
+	i_zero(&mod_set);
+	mod_set.abi_version = PIGEONHOLE_ABI_VERSION;
+	mod_set.require_init_funcs = TRUE;
+	mod_set.debug = FALSE;
+
+	/* Load missing plugin modules */
+
+	sieve_modules = module_dir_load_missing
+		(sieve_modules, path, plugins, &mod_set);
+
+	/* Call plugin load functions for this Sieve instance */
+
+	if ( svinst->plugins == NULL ) {
+		sieve_modules_refcount++;
+	}
+
+	module_names = t_strsplit_spaces(plugins, ", ");
+
+	for (i = 0; module_names[i] != NULL; i++) {
+		/* Allow giving the module names also in non-base form. */
+		module_names[i] = module_file_get_name(module_names[i]);
+	}
+
+ 	for (i = 0; module_names[i] != NULL; i++) {
+		struct sieve_plugin *plugin;
+		const char *name = module_names[i];
+		sieve_plugin_load_func_t load_func;
+
+		/* Find the module */
+		module = sieve_plugin_module_find(name);
+		i_assert(module != NULL);
+
+		/* Check whether the plugin is already loaded in this instance */
+		plugin = svinst->plugins;
+		while ( plugin != NULL ) {
+			if ( plugin->module == module )
+				break;
+			plugin = plugin->next;
+		}
+
+		/* Skip it if it is loaded already */
+		if ( plugin != NULL )
+			continue;
+
+		/* Create plugin list item */
+		plugin = p_new(svinst->pool, struct sieve_plugin, 1);
+		plugin->module = module;
+
+		/* Call load function */
+		load_func = (sieve_plugin_load_func_t) module_get_symbol
+			(module, t_strdup_printf("%s_load", module->name));
+		if ( load_func != NULL ) {
+			load_func(svinst, &plugin->context);
+		}
+
+		/* Add plugin to the instance */
+		if ( svinst->plugins == NULL )
+			svinst->plugins = plugin;
+		else {
+			struct sieve_plugin *plugin_last;
+
+			plugin_last = svinst->plugins;
+			while ( plugin_last->next != NULL )
+				plugin_last = plugin_last->next;
+
+			plugin_last->next = plugin;
+		}
+	}
+}
+
+void sieve_plugins_unload(struct sieve_instance *svinst)
+{
+	struct sieve_plugin *plugin;
+
+	if ( svinst->plugins == NULL )
+		return;
+
+	/* Call plugin unload functions for this instance */
+
+	plugin = svinst->plugins;
+	while ( plugin != NULL ) {
+		struct module *module = plugin->module;
+		sieve_plugin_unload_func_t unload_func;
+
+		unload_func = (sieve_plugin_unload_func_t)module_get_symbol
+			(module, t_strdup_printf("%s_unload", module->name));
+		if ( unload_func != NULL ) {
+			unload_func(svinst, plugin->context);
+		}
+
+		plugin = plugin->next;
+	}
+
+	/* Physically unload modules */
+
+	i_assert(sieve_modules_refcount > 0);
+
+	if ( --sieve_modules_refcount != 0 )
+		return;
+
+	module_dir_unload(&sieve_modules);
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-plugins.h
@@ -0,0 +1,9 @@
+#ifndef SIEVE_PLUGINS_H
+#define SIEVE_PLUGINS_H
+
+#include "sieve-common.h"
+
+void sieve_plugins_load(struct sieve_instance *svinst, const char *path, const char *plugins);
+void sieve_plugins_unload(struct sieve_instance *svinst);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-result.c
@@ -0,0 +1,1585 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "mempool.h"
+#include "ostream.h"
+#include "hash.h"
+#include "str.h"
+#include "strfuncs.h"
+#include "str-sanitize.h"
+#include "var-expand.h"
+#include "message-address.h"
+#include "mail-storage.h"
+
+#include "sieve-common.h"
+#include "sieve-limits.h"
+#include "sieve-script.h"
+#include "sieve-error.h"
+#include "sieve-interpreter.h"
+#include "sieve-actions.h"
+#include "sieve-message.h"
+
+#include "sieve-result.h"
+
+#include <stdio.h>
+
+/*
+ * Types
+ */
+
+struct sieve_result_action {
+	struct sieve_action action;
+
+	void *tr_context;
+	bool success;
+
+	bool keep;
+
+	struct sieve_side_effects_list *seffects;
+
+	struct sieve_result_action *prev, *next;
+};
+
+struct sieve_side_effects_list {
+	struct sieve_result *result;
+
+	struct sieve_result_side_effect *first_effect;
+	struct sieve_result_side_effect *last_effect;
+};
+
+struct sieve_result_side_effect {
+	struct sieve_side_effect seffect;
+
+	struct sieve_result_side_effect *prev, *next;
+};
+
+struct sieve_result_action_context {
+	const struct sieve_action_def *action;
+	struct sieve_side_effects_list *seffects;
+};
+
+/*
+ * Result object
+ */
+
+struct sieve_result {
+	pool_t pool;
+	int refcount;
+
+	struct sieve_instance *svinst;
+
+	/* Context data for extensions */
+	ARRAY(void *) ext_contexts;
+
+	struct sieve_action_exec_env action_env;
+
+	struct sieve_action keep_action;
+	struct sieve_action failure_action;
+
+	unsigned int action_count;
+	struct sieve_result_action *first_action;
+	struct sieve_result_action *last_action;
+
+	struct sieve_result_action *last_attempted_action;
+
+	HASH_TABLE(const struct sieve_action_def *,
+			   struct sieve_result_action_context *) action_contexts;
+
+	bool executed:1;
+	bool executed_delivery:1;
+};
+
+struct sieve_result *sieve_result_create
+(struct sieve_instance *svinst,
+	const struct sieve_message_data *msgdata,
+	const struct sieve_script_env *senv)
+{
+	pool_t pool;
+	struct sieve_result *result;
+
+	pool = pool_alloconly_create("sieve_result", 4096);
+	result = p_new(pool, struct sieve_result, 1);
+	result->refcount = 1;
+	result->pool = pool;
+	result->svinst = svinst;
+
+	p_array_init(&result->ext_contexts, pool, 4);
+
+	result->action_env.svinst = svinst;
+	result->action_env.result = result;
+	result->action_env.scriptenv = senv;
+	result->action_env.msgdata = msgdata;
+	result->action_env.msgctx = sieve_message_context_create
+		(svinst, senv->user, msgdata);
+
+	result->keep_action.def = &act_store;
+	result->keep_action.ext = NULL;
+	result->failure_action.def = &act_store;
+	result->failure_action.ext = NULL;
+
+	result->action_count = 0;
+	result->first_action = NULL;
+	result->last_action = NULL;
+
+	return result;
+}
+
+void sieve_result_ref(struct sieve_result *result)
+{
+	result->refcount++;
+}
+
+void sieve_result_unref(struct sieve_result **result)
+{
+	i_assert((*result)->refcount > 0);
+
+	if (--(*result)->refcount != 0)
+		return;
+
+	sieve_message_context_unref(&(*result)->action_env.msgctx);
+
+	if ( hash_table_is_created((*result)->action_contexts) )
+        hash_table_destroy(&(*result)->action_contexts);
+
+	if ( (*result)->action_env.ehandler != NULL )
+		sieve_error_handler_unref(&(*result)->action_env.ehandler);
+
+	pool_unref(&(*result)->pool);
+
+ 	*result = NULL;
+}
+
+pool_t sieve_result_pool(struct sieve_result *result)
+{
+	return result->pool;
+}
+
+/*
+ * Getters/Setters
+ */
+
+const struct sieve_script_env *sieve_result_get_script_env
+(struct sieve_result *result)
+{
+    return result->action_env.scriptenv;
+}
+
+const struct sieve_message_data *sieve_result_get_message_data
+(struct sieve_result *result)
+{
+	return result->action_env.msgdata;
+}
+
+struct sieve_message_context *sieve_result_get_message_context
+(struct sieve_result *result)
+{
+	return result->action_env.msgctx;
+}
+
+/*
+ * Extension support
+ */
+
+void sieve_result_extension_set_context
+(struct sieve_result *result, const struct sieve_extension *ext, void *context)
+{
+	if ( ext->id < 0 ) return;
+
+	array_idx_set(&result->ext_contexts, (unsigned int) ext->id, &context);
+}
+
+const void *sieve_result_extension_get_context
+(struct sieve_result *result, const struct sieve_extension *ext)
+{
+	void * const *ctx;
+
+	if  ( ext->id < 0 || ext->id >= (int) array_count(&result->ext_contexts) )
+		return NULL;
+
+	ctx = array_idx(&result->ext_contexts, (unsigned int) ext->id);
+
+	return *ctx;
+}
+
+/*
+ * Error handling
+ */
+
+void sieve_result_error
+(const struct sieve_action_exec_env *aenv, const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	sieve_verror(aenv->ehandler, NULL, fmt, args);
+	va_end(args);
+}
+
+void sieve_result_global_error
+(const struct sieve_action_exec_env *aenv, const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	sieve_global_verror(aenv->svinst, aenv->ehandler, NULL, fmt, args);
+	va_end(args);
+}
+
+void sieve_result_warning
+(const struct sieve_action_exec_env *aenv, const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	sieve_vwarning(aenv->ehandler, NULL, fmt, args);
+	va_end(args);
+}
+
+void sieve_result_global_warning
+(const struct sieve_action_exec_env *aenv, const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	sieve_global_vwarning(aenv->svinst, aenv->ehandler, NULL, fmt, args);
+	va_end(args);
+}
+
+void sieve_result_log
+(const struct sieve_action_exec_env *aenv, const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	sieve_vinfo(aenv->ehandler, NULL, fmt, args);
+	va_end(args);
+}
+
+void sieve_result_global_log
+(const struct sieve_action_exec_env *aenv, const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	sieve_global_vinfo(aenv->svinst, aenv->ehandler, NULL, fmt, args);
+	va_end(args);
+}
+
+void sieve_result_global_log_error
+(const struct sieve_action_exec_env *aenv, const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	sieve_global_info_verror(aenv->svinst, aenv->ehandler, NULL, fmt, args);
+	va_end(args);
+}
+
+void sieve_result_global_log_warning
+(const struct sieve_action_exec_env *aenv, const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	sieve_global_info_vwarning(aenv->svinst, aenv->ehandler, NULL, fmt, args);
+	va_end(args);
+}
+
+void sieve_result_critical
+(const struct sieve_action_exec_env *aenv,
+	const char *user_prefix, const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+
+	T_BEGIN {
+		sieve_vcritical
+			(aenv->svinst, aenv->ehandler, NULL, user_prefix, fmt, args);
+	} T_END;
+
+	va_end(args);
+}
+
+int sieve_result_mail_error
+(const struct sieve_action_exec_env *aenv, struct mail *mail,
+	const char *fmt, ...)
+{
+	const char *error_msg, *user_prefix;
+	va_list args;
+
+	error_msg = mailbox_get_last_error(mail->box, NULL);
+
+	va_start(args, fmt);
+	user_prefix = t_strdup_vprintf(fmt, args);
+	sieve_result_critical(aenv, user_prefix,
+		"%s: %s", user_prefix, error_msg);
+	va_end(args);
+
+	return 	SIEVE_EXEC_TEMP_FAILURE;
+}
+
+/*
+ * Result composition
+ */
+
+void sieve_result_add_implicit_side_effect
+(struct sieve_result *result, const struct sieve_action_def *to_action,
+	bool to_keep, const struct sieve_extension *ext,
+	const struct sieve_side_effect_def *seff_def, void *context)
+{
+	struct sieve_result_action_context *actctx = NULL;
+	struct sieve_side_effect seffect;
+
+	to_action = to_keep ? &act_store : to_action;
+
+	if ( !hash_table_is_created(result->action_contexts) ) {
+		hash_table_create_direct(&result->action_contexts, result->pool, 0);
+	} else {
+		actctx = hash_table_lookup(result->action_contexts, to_action);
+	}
+
+	if ( actctx == NULL ) {
+		actctx = p_new
+			(result->pool, struct sieve_result_action_context, 1);
+		actctx->action = to_action;
+		actctx->seffects = sieve_side_effects_list_create(result);
+
+		hash_table_insert(result->action_contexts, to_action, actctx);
+	}
+
+	seffect.object.def = &seff_def->obj_def;
+	seffect.object.ext = ext;
+	seffect.def = seff_def;
+	seffect.context = context;
+
+	sieve_side_effects_list_add(actctx->seffects, &seffect);
+}
+
+static int sieve_result_side_effects_merge
+(const struct sieve_runtime_env *renv, const struct sieve_action *action,
+	struct sieve_result_action *old_action,
+	struct sieve_side_effects_list *new_seffects)
+{
+	struct sieve_side_effects_list *old_seffects = old_action->seffects;
+	int ret;
+	struct sieve_result_side_effect *rsef, *nrsef;
+
+	/* Allow side-effects to merge with existing copy */
+
+	/* Merge existing side effects */
+	rsef = old_seffects != NULL ? old_seffects->first_effect : NULL;
+	while ( rsef != NULL ) {
+		struct sieve_side_effect *seffect = &rsef->seffect;
+		bool found = FALSE;
+
+		if ( seffect->def != NULL && seffect->def->merge != NULL ) {
+
+			/* Try to find it among the new */
+			nrsef = new_seffects != NULL ? new_seffects->first_effect : NULL;
+			while ( nrsef != NULL ) {
+				struct sieve_side_effect *nseffect = &nrsef->seffect;
+
+				if ( nseffect->def == seffect->def ) {
+					if ( seffect->def->merge
+						(renv, action, seffect, nseffect, &seffect->context) < 0 )
+						return -1;
+
+					found = TRUE;
+					break;
+				}
+
+				nrsef = nrsef->next;
+			}
+
+			/* Not found? */
+			if ( !found && seffect->def->merge
+				(renv, action, seffect, NULL, &rsef->seffect.context) < 0 )
+				return -1;
+		}
+
+		rsef = rsef->next;
+	}
+
+	/* Merge new Side effects */
+	nrsef = new_seffects != NULL ? new_seffects->first_effect : NULL;
+	while ( nrsef != NULL ) {
+		struct sieve_side_effect *nseffect = &nrsef->seffect;
+		bool found = FALSE;
+
+		if ( nseffect->def != NULL && nseffect->def->merge != NULL ) {
+
+			/* Try to find it among the exising */
+			rsef = old_seffects != NULL ? old_seffects->first_effect : NULL;
+			while ( rsef != NULL ) {
+				if ( rsef->seffect.def == nseffect->def ) {
+					found = TRUE;
+					break;
+				}
+				rsef = rsef->next;
+			}
+
+			/* Not found? */
+			if ( !found ) {
+				void *new_context = NULL;
+
+				if ( (ret=nseffect->def->merge
+					(renv, action, nseffect, nseffect, &new_context)) < 0 )
+					return -1;
+
+				if ( ret != 0 ) {
+					if ( old_action->seffects == NULL )
+						old_action->seffects = old_seffects =
+							sieve_side_effects_list_create(renv->result);
+
+					nseffect->context = new_context;
+
+					/* Add side effect */
+					sieve_side_effects_list_add(old_seffects, nseffect);
+				}
+			}
+		}
+
+		nrsef = nrsef->next;
+	}
+
+	return 1;
+}
+
+static void sieve_result_action_detach
+(struct sieve_result *result, struct sieve_result_action *raction)
+{
+	if ( result->first_action == raction )
+		result->first_action = raction->next;
+
+	if ( result->last_action == raction )
+		result->last_action = raction->prev;
+
+	if ( result->last_attempted_action == raction )
+		result->last_attempted_action = raction->prev;
+
+	if ( raction->next != NULL ) raction->next->prev = raction->prev;
+	if ( raction->prev != NULL ) raction->prev->next = raction->next;
+
+	raction->next = NULL;
+	raction->prev = NULL;
+
+	if ( result->action_count > 0 )
+		result->action_count--;
+}
+
+static int _sieve_result_add_action
+(const struct sieve_runtime_env *renv, const struct sieve_extension *ext,
+	const struct sieve_action_def *act_def,
+	struct sieve_side_effects_list *seffects,
+	void *context, unsigned int instance_limit, bool preserve_mail, bool keep)
+{
+	int ret = 0;
+	unsigned int instance_count = 0;
+	struct sieve_instance *svinst = renv->svinst;
+	struct sieve_result *result = renv->result;
+	struct sieve_result_action *raction = NULL, *kaction = NULL;
+	struct sieve_action action;
+
+	action.def = act_def;
+	action.ext = ext;
+	action.location = sieve_runtime_get_full_command_location(renv);
+	action.context = context;
+	action.executed = FALSE;
+
+	/* First, check for duplicates or conflicts */
+	raction = result->first_action;
+	while ( raction != NULL ) {
+		const struct sieve_action *oact = &raction->action;
+
+		if ( keep && raction->keep ) {
+
+			/* Duplicate keep */
+			if ( raction->action.def == NULL || raction->action.executed ) {
+				/* Keep action from preceeding execution */
+
+				/* Detach existing keep action */
+				sieve_result_action_detach(result, raction);
+
+				/* Merge existing side-effects with new keep action */
+				if ( kaction == NULL )
+					kaction = raction;
+
+				if ( (ret=sieve_result_side_effects_merge
+					(renv, &action, kaction, seffects)) <= 0 )
+					return ret;
+			} else {
+				/* True duplicate */
+				return sieve_result_side_effects_merge
+					(renv, &action, raction, seffects);
+			}
+
+		} if ( act_def != NULL && raction->action.def == act_def ) {
+			instance_count++;
+
+			/* Possible duplicate */
+			if ( act_def->check_duplicate != NULL ) {
+				if ( (ret=act_def->check_duplicate(renv, &action, &raction->action))
+					< 0 )
+					return ret;
+
+				/* Duplicate */
+				if ( ret == 1 ) {
+					if ( keep && !raction->keep ) {
+						/* New keep has higher precedence than existing duplicate non-keep
+						 * action. So, take over the result action object and transform it
+						 * into a keep.
+						 */
+
+						if ( (ret=sieve_result_side_effects_merge
+							(renv, &action, raction, seffects)) < 0 )
+							return ret;
+
+						if ( kaction == NULL ) {
+							raction->action.context = NULL;
+							raction->action.location =
+								p_strdup(result->pool, action.location);
+
+							/* Note that existing execution status is retained, making sure
+							 * that keep is not executed multiple times.
+							 */
+
+							kaction = raction;
+
+						} else {
+							sieve_result_action_detach(result, raction);
+
+							if ( (ret=sieve_result_side_effects_merge
+								(renv, &action, kaction, raction->seffects)) < 0 )
+								return ret;
+						}
+					} else {
+						/* Merge side-effects, but don't add new action */
+						return sieve_result_side_effects_merge
+							(renv, &action, raction, seffects);
+					}
+				}
+			}
+		} else {
+			if ( act_def != NULL && oact->def != NULL ) {
+				/* Check conflict */
+				if ( act_def->check_conflict != NULL &&
+					(ret=act_def->check_conflict(renv, &action, &raction->action)) != 0 )
+					return ret;
+
+				if ( !raction->action.executed && oact->def->check_conflict != NULL &&
+					(ret=oact->def->check_conflict
+						(renv, &raction->action, &action)) != 0 )
+					return ret;
+			}
+		}
+		raction = raction->next;
+	}
+
+	if ( kaction != NULL ) {
+		/* Use existing keep action to define new one */
+		raction = kaction;
+	} else {
+		/* Check policy limit on total number of actions */
+		if ( svinst->max_actions > 0 && result->action_count >= svinst->max_actions )
+		{
+			sieve_runtime_error(renv, action.location,
+				"total number of actions exceeds policy limit (%u > %u)",
+				result->action_count+1, svinst->max_actions);
+			return -1;
+		}
+
+		/* Check policy limit on number of this class of actions */
+		if ( instance_limit > 0 && instance_count >= instance_limit ) {
+			sieve_runtime_error(renv, action.location,
+				"number of %s actions exceeds policy limit (%u > %u)",
+				act_def->name, instance_count+1, instance_limit);
+			return -1;
+		}
+
+		/* Create new action object */
+		raction = p_new(result->pool, struct sieve_result_action, 1);
+		raction->action.executed = FALSE;
+		raction->seffects = seffects;
+		raction->tr_context = NULL;
+		raction->success = FALSE;
+	}
+
+	raction->action.context = context;
+	raction->action.def = act_def;
+	raction->action.ext = ext;
+	raction->action.location = p_strdup(result->pool, action.location);
+	raction->keep = keep;
+
+	if ( raction->prev == NULL && raction != result->first_action ) {
+		/* Add */
+		if ( result->first_action == NULL ) {
+			result->first_action = raction;
+			result->last_action = raction;
+			raction->prev = NULL;
+			raction->next = NULL;
+		} else {
+			result->last_action->next = raction;
+			raction->prev = result->last_action;
+			result->last_action = raction;
+			raction->next = NULL;
+		}
+		result->action_count++;
+
+		/* Apply any implicit side effects */
+		if ( hash_table_is_created(result->action_contexts) ) {
+			struct sieve_result_action_context *actctx;
+
+			/* Check for implicit side effects to this particular action */
+			actctx = hash_table_lookup(result->action_contexts,
+					( keep ? &act_store : act_def ));
+
+			if ( actctx != NULL ) {
+				struct sieve_result_side_effect *iseff;
+
+				/* Iterate through all implicit side effects and add those that are
+				 * missing.
+				 */
+				iseff = actctx->seffects->first_effect;
+				while ( iseff != NULL ) {
+					struct sieve_result_side_effect *seff;
+					bool exists = FALSE;
+
+					/* Scan for presence */
+					if ( seffects != NULL ) {
+						seff = seffects->first_effect;
+						while ( seff != NULL ) {
+							if ( seff->seffect.def == iseff->seffect.def ) {
+								exists = TRUE;
+								break;
+							}
+
+							seff = seff->next;
+						}
+					} else {
+						raction->seffects = seffects =
+							sieve_side_effects_list_create(result);
+					}
+
+					/* If not present, add it */
+					if ( !exists ) {
+						sieve_side_effects_list_add(seffects, &iseff->seffect);
+					}
+
+					iseff = iseff->next;
+				}
+			}
+		}
+	}
+
+	if ( preserve_mail ) {
+		raction->action.mail = sieve_message_get_mail(renv->msgctx);
+		sieve_message_snapshot(renv->msgctx);
+	} else {
+		raction->action.mail = NULL;
+	}
+
+	return 0;
+}
+
+int sieve_result_add_action
+(const struct sieve_runtime_env *renv, const struct sieve_extension *ext,
+	const struct sieve_action_def *act_def,
+	struct sieve_side_effects_list *seffects,	void *context,
+	unsigned int instance_limit, bool preserve_mail)
+{
+	return _sieve_result_add_action(renv, ext, act_def, seffects, context,
+		instance_limit, preserve_mail, FALSE);
+}
+
+int sieve_result_add_keep
+(const struct sieve_runtime_env *renv, struct sieve_side_effects_list *seffects)
+{
+	return _sieve_result_add_action
+		(renv, renv->result->keep_action.ext, renv->result->keep_action.def,
+			seffects, NULL, 0, TRUE, TRUE);
+}
+
+void sieve_result_set_keep_action
+(struct sieve_result *result, const struct sieve_extension *ext,
+	const struct sieve_action_def *act_def)
+{
+	result->keep_action.def = act_def;
+	result->keep_action.ext = ext;
+}
+
+void sieve_result_set_failure_action
+(struct sieve_result *result, const struct sieve_extension *ext,
+	const struct sieve_action_def *act_def)
+{
+	result->failure_action.def = act_def;
+	result->failure_action.ext = ext;
+}
+
+/*
+ * Result printing
+ */
+
+void sieve_result_vprintf
+(const struct sieve_result_print_env *penv, const char *fmt, va_list args)
+{
+	string_t *outbuf = t_str_new(128);
+
+	str_vprintfa(outbuf, fmt, args);
+
+	o_stream_nsend(penv->stream, str_data(outbuf), str_len(outbuf));
+}
+
+void sieve_result_printf
+(const struct sieve_result_print_env *penv, const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	sieve_result_vprintf(penv, fmt, args);
+	va_end(args);
+}
+
+void sieve_result_action_printf
+(const struct sieve_result_print_env *penv, const char *fmt, ...)
+{
+	string_t *outbuf = t_str_new(128);
+	va_list args;
+
+	va_start(args, fmt);
+	str_append(outbuf, " * ");
+	str_vprintfa(outbuf, fmt, args);
+	str_append_c(outbuf, '\n');
+	va_end(args);
+
+	o_stream_nsend(penv->stream, str_data(outbuf), str_len(outbuf));
+}
+
+void sieve_result_seffect_printf
+(const struct sieve_result_print_env *penv, const char *fmt, ...)
+{
+	string_t *outbuf = t_str_new(128);
+	va_list args;
+
+	va_start(args, fmt);
+	str_append(outbuf, "        + ");
+	str_vprintfa(outbuf, fmt, args);
+	str_append_c(outbuf, '\n');
+	va_end(args);
+
+	o_stream_nsend(penv->stream, str_data(outbuf), str_len(outbuf));
+}
+
+static void sieve_result_print_side_effects
+(struct sieve_result_print_env *rpenv, const struct sieve_action *action,
+	struct sieve_side_effects_list *slist, bool *implicit_keep)
+{
+	struct sieve_result_side_effect *rsef;
+
+	/* Print side effects */
+	rsef = slist != NULL ? slist->first_effect : NULL;
+	while ( rsef != NULL ) {
+		if ( rsef->seffect.def != NULL ) {
+			const struct sieve_side_effect *sef = &rsef->seffect;
+
+			if ( sef->def->print != NULL )
+				sef->def->print(sef, action, rpenv, implicit_keep);
+		}
+		rsef = rsef->next;
+	}
+}
+
+static void sieve_result_print_implicit_side_effects
+(struct sieve_result_print_env *rpenv)
+{
+	struct sieve_result *result = rpenv->result;
+	bool dummy = TRUE;
+
+	/* Print any implicit side effects if applicable */
+	if ( hash_table_is_created(result->action_contexts) ) {
+		struct sieve_result_action_context *actctx;
+
+		/* Check for implicit side effects to keep action */
+		actctx = hash_table_lookup(rpenv->result->action_contexts, &act_store);
+
+		if ( actctx != NULL && actctx->seffects != NULL )
+			sieve_result_print_side_effects
+				(rpenv, &result->keep_action, actctx->seffects, &dummy);
+	}
+}
+
+bool sieve_result_print
+(struct sieve_result *result, const struct sieve_script_env *senv,
+	struct ostream *stream, bool *keep)
+{
+	struct sieve_action act_keep = result->keep_action;
+	struct sieve_result_print_env penv;
+	bool implicit_keep = TRUE;
+	struct sieve_result_action *rac, *first_action;
+
+	first_action = ( result->last_attempted_action == NULL ?
+		result->first_action : result->last_attempted_action->next );
+
+	if ( keep != NULL ) *keep = FALSE;
+
+	/* Prepare environment */
+
+	penv.result = result;
+	penv.stream = stream;
+	penv.scriptenv = senv;
+
+	sieve_result_printf(&penv, "\nPerformed actions:\n\n");
+
+	if ( first_action == NULL ) {
+		sieve_result_printf(&penv, "  (none)\n");
+	} else {
+		rac = first_action;
+		while ( rac != NULL ) {
+			bool impl_keep = TRUE;
+			const struct sieve_action *act = &rac->action;
+
+			if ( rac->keep && keep != NULL ) *keep = TRUE;
+
+			if ( act->def != NULL ) {
+				if ( act->def->print != NULL )
+					act->def->print(act, &penv, &impl_keep);
+				else
+					sieve_result_action_printf(&penv, "%s", act->def->name);
+			} else {
+				if ( rac->keep ) {
+					sieve_result_action_printf(&penv, "keep");
+					impl_keep = FALSE;
+				} else {
+					sieve_result_action_printf(&penv, "[NULL]");
+				}
+			}
+
+			/* Print side effects */
+			sieve_result_print_side_effects
+				(&penv, &rac->action, rac->seffects, &impl_keep);
+
+			implicit_keep = implicit_keep && impl_keep;
+
+			rac = rac->next;
+		}
+	}
+
+	if ( implicit_keep && keep != NULL ) *keep = TRUE;
+
+	sieve_result_printf(&penv, "\nImplicit keep:\n\n");
+
+	if ( implicit_keep ) {
+		bool dummy = TRUE;
+
+		if ( act_keep.def == NULL ) {
+			sieve_result_action_printf(&penv, "keep");
+
+			sieve_result_print_implicit_side_effects(&penv);
+		} else {
+			/* Scan for execution of keep-equal actions */
+			rac = result->first_action;
+			while ( act_keep.def != NULL && rac != NULL ) {
+				if ( rac->action.def == act_keep.def && act_keep.def->equals != NULL
+					&& act_keep.def->equals(senv, NULL, &rac->action)
+						&& rac->action.executed ) {
+					act_keep.def = NULL;
+				}
+
+				rac = rac->next;
+			}
+
+			if ( act_keep.def == NULL ) {
+				sieve_result_printf(&penv,
+					"  (none; keep or equivalent action executed earlier)\n");
+			} else {
+				act_keep.def->print(&act_keep, &penv, &dummy);
+
+				sieve_result_print_implicit_side_effects(&penv);
+			}
+		}
+	} else
+		sieve_result_printf(&penv, "  (none)\n");
+
+	sieve_result_printf(&penv, "\n");
+
+	return TRUE;
+}
+
+/*
+ * Result execution
+ */
+
+static void _sieve_result_prepare_execution(struct sieve_result *result,
+	struct sieve_error_handler *ehandler,
+	enum sieve_execute_flags flags)
+{
+	const struct sieve_script_env *senv = result->action_env.scriptenv;
+
+	result->action_env.flags = flags;
+	result->action_env.ehandler = ehandler;
+	result->action_env.exec_status =
+		( senv->exec_status == NULL ?
+			t_new(struct sieve_exec_status, 1) : senv->exec_status );
+}
+
+static int _sieve_result_implicit_keep
+(struct sieve_result *result, bool rollback)
+{
+	const struct sieve_action_exec_env *aenv = &result->action_env;
+	struct sieve_result_action *rac, *kac;
+	int status = SIEVE_EXEC_OK;
+	struct sieve_result_side_effect *rsef, *rsef_first = NULL;
+	void *tr_context = NULL;
+	struct sieve_action act_keep;
+
+	if ( (aenv->flags & SIEVE_EXECUTE_FLAG_DEFER_KEEP) != 0 )
+		return SIEVE_EXEC_OK;
+
+	if ( rollback )
+		act_keep = result->failure_action;
+	else
+		act_keep = result->keep_action;
+	act_keep.mail = NULL;
+
+	/* If keep is a non-action, return right away */
+	if ( act_keep.def == NULL )
+		return SIEVE_EXEC_OK;
+
+	/* Scan for deferred keep */
+	kac = result->last_action;
+	while ( kac != NULL && kac->action.executed ) {
+		if ( kac->keep && kac->action.def == NULL )
+			break;
+		kac = kac->prev;
+	}
+
+	if (kac == NULL) {
+		if ( !rollback )
+			act_keep.mail = sieve_message_get_mail(aenv->msgctx);
+
+		/* Scan for execution of keep-equal actions */
+		rac = result->first_action;
+		while ( rac != NULL ) {
+			if ( rac->action.def == act_keep.def && act_keep.def->equals != NULL &&
+				act_keep.def->equals(aenv->scriptenv, NULL, &rac->action) &&
+				rac->action.executed )
+				return SIEVE_EXEC_OK;
+
+			rac = rac->next;
+		}
+	} else if ( !rollback ) {
+		act_keep.location = kac->action.location;
+		act_keep.mail = kac->action.mail;
+		if ( kac->seffects != NULL )
+			rsef_first = kac->seffects->first_effect;
+	}
+
+	if (rsef_first == NULL) {
+		/* Apply any implicit side effects if applicable */
+		if ( !rollback && hash_table_is_created(result->action_contexts) ) {
+			struct sieve_result_action_context *actctx;
+
+			/* Check for implicit side effects to keep action */
+			actctx = hash_table_lookup(result->action_contexts, act_keep.def);
+
+			if ( actctx != NULL && actctx->seffects != NULL )
+				rsef_first = actctx->seffects->first_effect;
+		}
+	}
+
+	/* Start keep action */
+	if ( act_keep.def->start != NULL ) {
+		status = act_keep.def->start
+			(&act_keep, aenv, &tr_context);
+	}
+
+	/* Execute keep action */
+	if ( status == SIEVE_EXEC_OK ) {
+		rsef = rsef_first;
+		while ( status == SIEVE_EXEC_OK && rsef != NULL ) {
+			struct sieve_side_effect *sef = &rsef->seffect;
+
+			if ( sef->def->pre_execute != NULL ) {
+				status = sef->def->pre_execute
+					(sef, &act_keep, aenv, &sef->context, tr_context);
+			}
+			rsef = rsef->next;
+		}
+
+		if ( status == SIEVE_EXEC_OK && act_keep.def->execute != NULL ) {
+			status = act_keep.def->execute
+				(&act_keep, aenv, tr_context);
+		}
+
+		rsef = rsef_first;
+		while ( status == SIEVE_EXEC_OK && rsef != NULL ) {
+			struct sieve_side_effect *sef = &rsef->seffect;
+
+			if ( sef->def->post_execute != NULL ) {
+				status = sef->def->post_execute
+					(sef, &act_keep, aenv, tr_context);
+			}
+			rsef = rsef->next;
+		}
+	}
+
+	/* Commit keep action */
+	if ( status == SIEVE_EXEC_OK ) {
+		bool dummy = TRUE;
+
+		if ( act_keep.def->commit != NULL ) {
+			status = act_keep.def->commit
+				(&act_keep, aenv, tr_context, &dummy);
+		}
+
+		rsef = rsef_first;
+		while ( rsef != NULL ) {
+			struct sieve_side_effect *sef = &rsef->seffect;
+			bool keep = TRUE;
+
+			if ( sef->def->post_commit != NULL ) {
+				sef->def->post_commit
+					(sef, &act_keep, aenv, tr_context, &keep);
+			}
+			rsef = rsef->next;
+		}
+	} else {
+		/* Failed, rollback */
+		if ( act_keep.def->rollback != NULL ) {
+			act_keep.def->rollback(&act_keep,
+				aenv, tr_context, ( status == SIEVE_EXEC_OK ));
+		}
+	}
+
+	/* Finish keep action */
+	if ( act_keep.def->finish != NULL ) {
+		act_keep.def->finish
+			(&act_keep, aenv, tr_context, status);
+	}
+
+	if (status == SIEVE_EXEC_FAILURE)
+		status = SIEVE_EXEC_KEEP_FAILED;
+	return status;
+}
+
+int sieve_result_implicit_keep
+(struct sieve_result *result,
+	struct sieve_error_handler *ehandler,
+	enum sieve_execute_flags flags,
+	bool success)
+{
+	int ret;
+
+	_sieve_result_prepare_execution(result, ehandler, flags);
+
+	ret = _sieve_result_implicit_keep(result, !success);
+
+	result->action_env.ehandler = NULL;
+
+	return ret;
+}
+
+void sieve_result_mark_executed(struct sieve_result *result)
+{
+	struct sieve_result_action *first_action, *rac;
+
+	first_action = ( result->last_attempted_action == NULL ?
+		result->first_action : result->last_attempted_action->next );
+	result->last_attempted_action = result->last_action;
+
+	rac = first_action;
+	while ( rac != NULL ) {
+		if ( rac->action.def != NULL )
+			rac->action.executed = TRUE;
+
+		rac = rac->next;
+	}
+}
+
+
+bool sieve_result_executed(struct sieve_result *result)
+{
+	return result->executed;
+}
+
+bool sieve_result_executed_delivery(struct sieve_result *result)
+{
+	return result->executed_delivery;
+}
+
+static int sieve_result_transaction_start
+(struct sieve_result *result, struct sieve_result_action *first,
+	struct sieve_result_action **last_r)
+{
+	const struct sieve_script_env *senv = result->action_env.scriptenv;
+	struct sieve_result_action *rac = first;
+	int status = SIEVE_EXEC_OK;
+	bool dup_flushed = FALSE;
+
+	while ( status == SIEVE_EXEC_OK && rac != NULL ) {
+		struct sieve_action *act = &rac->action;
+
+		/* Skip non-actions (inactive keep) and executed ones */
+		if ( act->def == NULL || act->executed ) {
+			rac = rac->next;
+			continue;
+		}
+
+		if ((act->def->flags & SIEVE_ACTFLAG_MAIL_STORAGE) != 0 &&
+			!dup_flushed) {
+			sieve_action_duplicate_flush(senv);
+			dup_flushed = TRUE;
+		}
+
+		if ( act->def->start != NULL ) {
+			status = act->def->start
+				(act, &result->action_env, &rac->tr_context);
+			rac->success = ( status == SIEVE_EXEC_OK );
+		}
+
+		rac = rac->next;
+	}
+
+	*last_r = rac;
+	return status;
+}
+
+static int sieve_result_transaction_execute
+(struct sieve_result *result, struct sieve_result_action *first)
+{
+	struct sieve_result_action *rac = first;
+	int status = SIEVE_EXEC_OK;
+
+	while ( status == SIEVE_EXEC_OK && rac != NULL ) {
+		struct sieve_action *act = &rac->action;
+		struct sieve_result_side_effect *rsef;
+		struct sieve_side_effect *sef;
+
+		/* Skip non-actions (inactive keep) and executed ones */
+		if ( act->def == NULL || act->executed ) {
+			rac = rac->next;
+			continue;
+		}
+
+		/* Execute pre-execute event of side effects */
+		rsef = rac->seffects != NULL ? rac->seffects->first_effect : NULL;
+		while ( status == SIEVE_EXEC_OK && rsef != NULL ) {
+			sef = &rsef->seffect;
+			if ( sef->def != NULL && sef->def->pre_execute != NULL )
+				status = sef->def->pre_execute
+					(sef, act, &result->action_env, &sef->context, rac->tr_context);
+			rsef = rsef->next;
+		}
+
+		/* Execute the action itself */
+		if ( status == SIEVE_EXEC_OK && act->def != NULL &&
+			act->def->execute != NULL ) {
+			status = act->def->execute
+				(act, &result->action_env, rac->tr_context);
+		}
+
+		/* Execute post-execute event of side effects */
+		rsef = rac->seffects != NULL ? rac->seffects->first_effect : NULL;
+		while ( status == SIEVE_EXEC_OK && rsef != NULL ) {
+			sef = &rsef->seffect;
+			if ( sef->def != NULL && sef->def->post_execute != NULL )
+				status = sef->def->post_execute
+					(sef, act, &result->action_env, rac->tr_context);
+			rsef = rsef->next;
+		}
+
+		rac->success = ( status == SIEVE_EXEC_OK );
+		rac = rac->next;
+	}
+
+	return status;
+}
+
+static int sieve_result_action_commit
+(struct sieve_result *result, struct sieve_result_action *rac,
+	bool *impl_keep)
+{
+	struct sieve_action *act = &rac->action;
+	struct sieve_result_side_effect *rsef;
+	int cstatus = SIEVE_EXEC_OK;
+
+	if ( act->def->commit != NULL ) {
+		cstatus = act->def->commit
+			(act, &result->action_env, rac->tr_context, impl_keep);
+		if ( cstatus == SIEVE_EXEC_OK ) {
+			act->executed = TRUE;
+			result->executed = TRUE;
+		}
+	}
+
+	if ( cstatus == SIEVE_EXEC_OK ) {
+		/* Execute post_commit event of side effects */
+		rsef = rac->seffects != NULL ? rac->seffects->first_effect : NULL;
+		while ( rsef != NULL ) {
+			struct sieve_side_effect *sef = &rsef->seffect;
+			if ( sef->def->post_commit != NULL )
+				sef->def->post_commit
+					(sef, act, &result->action_env, rac->tr_context, impl_keep);
+			rsef = rsef->next;
+		}
+	}
+
+	return cstatus;
+}
+
+static void sieve_result_action_rollback
+(struct sieve_result *result, struct sieve_result_action *rac)
+{
+	struct sieve_action *act = &rac->action;
+	struct sieve_result_side_effect *rsef;
+
+	if ( act->def->rollback != NULL ) {
+		act->def->rollback
+			(act, &result->action_env, rac->tr_context, rac->success);
+	}
+
+	/* Rollback side effects */
+	rsef = rac->seffects != NULL ? rac->seffects->first_effect : NULL;
+	while ( rsef != NULL ) {
+		struct sieve_side_effect *sef = &rsef->seffect;
+		if ( sef->def != NULL && sef->def->rollback != NULL )
+			sef->def->rollback
+				(sef, act, &result->action_env, rac->tr_context, rac->success);
+		rsef = rsef->next;
+	}
+}
+
+static int sieve_result_action_commit_or_rollback
+(struct sieve_result *result, struct sieve_result_action *rac,
+	int status, bool *implicit_keep, bool *keep, int *commit_status)
+{
+	struct sieve_action *act = &rac->action;
+
+	if ( status == SIEVE_EXEC_OK ) {
+		bool impl_keep = TRUE;
+		int cstatus = SIEVE_EXEC_OK;
+
+		if ( rac->keep && keep != NULL ) *keep = TRUE;
+
+		/* Skip non-actions (inactive keep) and executed ones */
+		if ( act->def == NULL || act->executed )
+			return status;
+
+		cstatus = sieve_result_action_commit(result, rac, &impl_keep);
+		if (cstatus != SIEVE_EXEC_OK) {
+			/* This is bad; try to salvage as much as possible */
+			if (*commit_status == SIEVE_EXEC_OK) {
+				*commit_status = cstatus;
+				if (!result->executed) {
+					/* We haven't executed anything yet; continue as rollback */
+					status = cstatus;
+				}
+			}
+			impl_keep = TRUE;
+		}
+
+		*implicit_keep = *implicit_keep && impl_keep;
+	} else {
+		/* Skip non-actions (inactive keep) and executed ones */
+		if ( act->def == NULL || act->executed )
+			return status;
+
+		sieve_result_action_rollback(result, rac);
+	}
+
+	if (rac->keep) {
+		if (status == SIEVE_EXEC_FAILURE)
+			status = SIEVE_EXEC_KEEP_FAILED;
+		if (*commit_status == SIEVE_EXEC_FAILURE)
+			*commit_status = SIEVE_EXEC_KEEP_FAILED;
+	}
+
+	return status;
+}
+
+static int sieve_result_transaction_commit_or_rollback
+(struct sieve_result *result, int status,
+	struct sieve_result_action *first,
+	struct sieve_result_action *last,
+	bool *implicit_keep, bool *keep)
+{
+	struct sieve_result_action *rac;
+	int commit_status = status;
+	bool seen_delivery = FALSE;
+
+	/* First commit/rollback all storage actions */
+	rac = first;
+	while ( rac != NULL && rac != last ) {
+		struct sieve_action *act = &rac->action;
+
+		if (act->def == NULL ||
+			(act->def->flags & SIEVE_ACTFLAG_MAIL_STORAGE) == 0) {
+			rac = rac->next;
+			continue;
+		}
+
+		status = sieve_result_action_commit_or_rollback
+			(result, rac, status, implicit_keep, keep, &commit_status);
+		
+		if ( act->def != NULL &&
+			(act->def->flags & SIEVE_ACTFLAG_TRIES_DELIVER) != 0 )
+			seen_delivery = TRUE;
+
+		rac = rac->next;
+	}
+
+	/* Then commit/rollback all other actions */
+	rac = first;
+	while ( rac != NULL && rac != last ) {
+		struct sieve_action *act = &rac->action;
+
+		if (act->def != NULL &&
+			(act->def->flags & SIEVE_ACTFLAG_MAIL_STORAGE) != 0) {
+			rac = rac->next;
+			continue;
+		}
+
+		status = sieve_result_action_commit_or_rollback
+			(result, rac, status, implicit_keep, keep, &commit_status);
+
+		if ( act->def != NULL &&
+			(act->def->flags & SIEVE_ACTFLAG_TRIES_DELIVER) != 0 )
+			seen_delivery = TRUE;
+
+		rac = rac->next;
+	}
+
+	if ( *implicit_keep && keep != NULL ) *keep = TRUE;
+
+	if ( commit_status == SIEVE_EXEC_OK ) {
+		result->executed_delivery =
+			result->executed_delivery || seen_delivery;
+	}
+
+	return commit_status;
+}
+
+static void sieve_result_transaction_finish
+(struct sieve_result *result, struct sieve_result_action *first,
+	int status)
+{
+	struct sieve_result_action *rac = first;
+
+	while ( rac != NULL ) {
+		struct sieve_action *act = &rac->action;
+		/* Skip non-actions (inactive keep) and executed ones */
+		if ( act->def == NULL || act->executed ) {
+			rac = rac->next;
+			continue;
+		}
+
+		if ( act->def->finish != NULL ) {
+			act->def->finish
+				(act, &result->action_env, rac->tr_context, status);
+		}
+
+		rac = rac->next;
+	}
+}
+
+int sieve_result_execute
+(struct sieve_result *result, bool *keep,
+	struct sieve_error_handler *ehandler,
+	enum sieve_execute_flags flags)
+{
+	int status = SIEVE_EXEC_OK, result_status;
+	struct sieve_result_action *first_action, *last_action;
+	bool implicit_keep = TRUE;
+	int ret;
+
+	if ( keep != NULL ) *keep = FALSE;
+
+	/* Prepare environment */
+
+	_sieve_result_prepare_execution(result, ehandler, flags);
+
+	/* Make notice of this attempt */
+
+	first_action = ( result->last_attempted_action == NULL ?
+		result->first_action : result->last_attempted_action->next );
+	result->last_attempted_action = result->last_action;
+
+	/* Transaction start */
+
+	status = sieve_result_transaction_start
+		(result, first_action, &last_action);
+
+	/* Transaction execute */
+
+	if (status == SIEVE_EXEC_OK) {
+		status = sieve_result_transaction_execute
+			(result, first_action);
+	}
+
+	/* Transaction commit/rollback */
+
+	status = sieve_result_transaction_commit_or_rollback
+		(result, status, first_action, last_action,
+			&implicit_keep, keep);
+
+	/* Perform implicit keep if necessary */
+
+	result_status = status;
+	if ( result->executed || status != SIEVE_EXEC_TEMP_FAILURE ) {
+		/* Execute implicit keep if the transaction failed or when the implicit
+		 * keep was not canceled during transaction.
+		 */
+		if ( status != SIEVE_EXEC_OK || implicit_keep ) {
+			switch ((ret=_sieve_result_implicit_keep
+				(result, ( status != SIEVE_EXEC_OK ))) ) {
+			case SIEVE_EXEC_OK:
+				if ( result_status == SIEVE_EXEC_TEMP_FAILURE )
+					result_status = SIEVE_EXEC_FAILURE;
+				break;
+			case SIEVE_EXEC_TEMP_FAILURE:
+				if (!result->executed) {
+					result_status = ret;
+					break;
+				}
+				/* fall through */
+			default:
+				result_status = SIEVE_EXEC_KEEP_FAILED;
+			}
+		}
+		if (status == SIEVE_EXEC_OK)
+			status = result_status;
+	}
+
+	/* Finish execution */
+
+	sieve_result_transaction_finish
+		(result, first_action, status);
+
+	result->action_env.ehandler = NULL;
+	return result_status;
+}
+
+/*
+ * Result evaluation
+ */
+
+struct sieve_result_iterate_context {
+	struct sieve_result *result;
+	struct sieve_result_action *current_action;
+	struct sieve_result_action *next_action;
+};
+
+struct sieve_result_iterate_context *sieve_result_iterate_init
+(struct sieve_result *result)
+{
+	struct sieve_result_iterate_context *rictx =
+		t_new(struct sieve_result_iterate_context, 1);
+
+	rictx->result = result;
+	rictx->current_action = NULL;
+	rictx->next_action = result->first_action;
+
+	return rictx;
+}
+
+const struct sieve_action *sieve_result_iterate_next
+(struct sieve_result_iterate_context *rictx, bool *keep)
+{
+	struct sieve_result_action *rac;
+
+	if ( rictx == NULL )
+		return  NULL;
+
+	rac = rictx->current_action = rictx->next_action;
+	if ( rac != NULL ) {
+		rictx->next_action = rac->next;
+
+		if ( keep != NULL )
+			*keep = rac->keep;
+
+		return &rac->action;
+	}
+
+	return NULL;
+}
+
+void sieve_result_iterate_delete
+(struct sieve_result_iterate_context *rictx)
+{
+	struct sieve_result *result;
+	struct sieve_result_action *rac;
+
+	if ( rictx == NULL || rictx->current_action == NULL )
+		return;
+
+	result = rictx->result;
+	rac = rictx->current_action;
+
+	/* Delete action */
+
+	if ( rac->prev == NULL )
+		result->first_action = rac->next;
+	else
+		rac->prev->next = rac->next;
+
+	if ( rac->next == NULL )
+		result->last_action = rac->prev;
+	else
+		rac->next->prev = rac->prev;
+
+	/* Skip to next action in iteration */
+
+	rictx->current_action = NULL;
+}
+
+/*
+ * Side effects list
+ */
+
+struct sieve_side_effects_list *sieve_side_effects_list_create
+	(struct sieve_result *result)
+{
+	struct sieve_side_effects_list *list =
+		p_new(result->pool, struct sieve_side_effects_list, 1);
+
+	list->result = result;
+	list->first_effect = NULL;
+	list->last_effect = NULL;
+
+	return list;
+}
+
+void sieve_side_effects_list_add
+(struct sieve_side_effects_list *list, const struct sieve_side_effect *seffect)
+{
+	struct sieve_result_side_effect *reffect;
+
+	/* Prevent duplicates */
+	reffect = list->first_effect;
+	while ( reffect != NULL ) {
+		if ( reffect->seffect.def == seffect->def ) return;
+
+		reffect = reffect->next;
+	}
+
+	/* Create new side effect object */
+	reffect = p_new(list->result->pool, struct sieve_result_side_effect, 1);
+	reffect->seffect = *seffect;
+
+	/* Add */
+	if ( list->first_effect == NULL ) {
+		list->first_effect = reffect;
+		list->last_effect = reffect;
+		reffect->prev = NULL;
+		reffect->next = NULL;
+	} else {
+		list->last_effect->next = reffect;
+		reffect->prev = list->last_effect;
+		list->last_effect = reffect;
+		reffect->next = NULL;
+	}
+}
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-result.h
@@ -0,0 +1,183 @@
+#ifndef SIEVE_RESULT_H
+#define SIEVE_RESULT_H
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+
+/*
+ * Types
+ */
+
+struct sieve_side_effects_list;
+
+/*
+ * Result object
+ */
+
+struct sieve_result;
+
+struct sieve_result *sieve_result_create
+	(struct sieve_instance *svinst,
+		const struct sieve_message_data *msgdata,
+		const struct sieve_script_env *senv);
+
+void sieve_result_ref(struct sieve_result *result);
+
+void sieve_result_unref(struct sieve_result **result);
+
+pool_t sieve_result_pool(struct sieve_result *result);
+
+/*
+ * Getters/Setters
+ */
+
+const struct sieve_script_env *sieve_result_get_script_env
+	(struct sieve_result *result);
+const struct sieve_message_data *sieve_result_get_message_data
+	(struct sieve_result *result);
+struct sieve_message_context *sieve_result_get_message_context
+	(struct sieve_result *result);
+
+/*
+ * Extension support
+ */
+
+void sieve_result_extension_set_context
+	(struct sieve_result *result, const struct sieve_extension *ext,
+		void *context);
+const void *sieve_result_extension_get_context
+	(struct sieve_result *result, const struct sieve_extension *ext);
+
+/*
+ * Result printing
+ */
+
+struct sieve_result_print_env {
+	struct sieve_result *result;
+	const struct sieve_script_env *scriptenv;
+	struct ostream *stream;
+};
+
+void sieve_result_vprintf
+	(const struct sieve_result_print_env *penv, const char *fmt, va_list args)
+		ATTR_FORMAT(2, 0);
+void sieve_result_printf
+	(const struct sieve_result_print_env *penv, const char *fmt, ...)
+		ATTR_FORMAT(2, 3);
+void sieve_result_action_printf
+	(const struct sieve_result_print_env *penv, const char *fmt, ...)
+		ATTR_FORMAT(2, 3);
+void sieve_result_seffect_printf
+	(const struct sieve_result_print_env *penv, const char *fmt, ...)
+		ATTR_FORMAT(2, 3);
+
+bool sieve_result_print
+	(struct sieve_result *result, const struct sieve_script_env *senv,
+		struct ostream *stream, bool *keep);
+
+/*
+ * Error handling
+ */
+
+void sieve_result_error
+(const struct sieve_action_exec_env *aenv, const char *fmt, ...)
+	ATTR_FORMAT(2, 3);
+void sieve_result_global_error
+(const struct sieve_action_exec_env *aenv, const char *fmt, ...)
+	ATTR_FORMAT(2, 3);
+void sieve_result_warning
+(const struct sieve_action_exec_env *aenv, const char *fmt, ...)
+	ATTR_FORMAT(2, 3);
+void sieve_result_global_warning
+(const struct sieve_action_exec_env *aenv, const char *fmt, ...)
+	ATTR_FORMAT(2, 3);
+void sieve_result_log
+(const struct sieve_action_exec_env *aenv, const char *fmt, ...)
+	ATTR_FORMAT(2, 3);
+void sieve_result_global_log
+(const struct sieve_action_exec_env *aenv, const char *fmt, ...)
+	ATTR_FORMAT(2, 3);
+void sieve_result_global_log_error
+(const struct sieve_action_exec_env *aenv, const char *fmt, ...)
+	ATTR_FORMAT(2, 3);
+void sieve_result_global_log_warning
+(const struct sieve_action_exec_env *aenv, const char *fmt, ...)
+	ATTR_FORMAT(2, 3);
+
+void sieve_result_critical
+(const struct sieve_action_exec_env *aenv,
+	const char *user_prefix, const char *fmt, ...) ATTR_FORMAT(3, 4);
+int sieve_result_mail_error
+(const struct sieve_action_exec_env *aenv, struct mail *mail,
+	const char *fmt, ...) ATTR_FORMAT(3, 4);
+
+/*
+ * Result composition
+ */
+
+void sieve_result_add_implicit_side_effect
+(struct sieve_result *result, const struct sieve_action_def *to_action,
+	bool to_keep, const struct sieve_extension *ext,
+	const struct sieve_side_effect_def *seffect, void *context);
+
+int sieve_result_add_action
+	(const struct sieve_runtime_env *renv, const struct sieve_extension *ext,
+		const struct sieve_action_def *act_def,
+		struct sieve_side_effects_list *seffects,	void *context,
+		unsigned int instance_limit, bool preserve_mail);
+int sieve_result_add_keep
+	(const struct sieve_runtime_env *renv,
+		struct sieve_side_effects_list *seffects);
+
+void sieve_result_set_keep_action
+	(struct sieve_result *result, const struct sieve_extension *ext,
+		const struct sieve_action_def *act_def);
+void sieve_result_set_failure_action
+	(struct sieve_result *result, const struct sieve_extension *ext,
+		const struct sieve_action_def *act_def);
+
+/*
+ * Result execution
+ */
+
+int sieve_result_implicit_keep
+	(struct sieve_result *result,
+		struct sieve_error_handler *ehandler,
+		enum sieve_execute_flags flags,
+		bool success);
+
+void sieve_result_mark_executed(struct sieve_result *result);
+
+int sieve_result_execute
+	(struct sieve_result *result, bool *keep,
+		struct sieve_error_handler *ehandler,
+		enum sieve_execute_flags flags);
+
+bool sieve_result_executed(struct sieve_result *result);
+
+bool sieve_result_executed_delivery(struct sieve_result *result);
+
+/*
+ * Result evaluation
+ */
+
+struct sieve_result_iterate_context;
+
+struct sieve_result_iterate_context *sieve_result_iterate_init
+	(struct sieve_result *result);
+const struct sieve_action *sieve_result_iterate_next
+	(struct sieve_result_iterate_context *rictx, bool *keep);
+void sieve_result_iterate_delete
+	(struct sieve_result_iterate_context *rictx);
+
+/*
+ * Side effects list
+ */
+
+struct sieve_side_effects_list *sieve_side_effects_list_create
+	(struct sieve_result *result);
+void sieve_side_effects_list_add
+	(struct sieve_side_effects_list *list,
+		const struct sieve_side_effect *seffect);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-runtime-trace.c
@@ -0,0 +1,151 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "ostream.h"
+
+#include "sieve-common.h"
+#include "sieve-script.h"
+#include "sieve-binary.h"
+#include "sieve-code.h"
+#include "sieve-interpreter.h"
+#include "sieve-runtime.h"
+#include "sieve-runtime-trace.h"
+
+static inline string_t *_trace_line_new
+(const struct sieve_runtime_env *renv, sieve_size_t address, unsigned int cmd_line)
+{
+	string_t *trline;
+	unsigned int i;
+
+	trline = t_str_new(128);
+	if ( (renv->trace->config.flags & SIEVE_TRFLG_ADDRESSES) > 0 )
+		str_printfa(trline, "%08llx: ", (unsigned long long) address);
+	if ( cmd_line > 0 )
+		str_printfa(trline, "%4d: ", cmd_line);
+	else
+		str_append(trline, "      ");
+
+	for ( i = 0; i < renv->trace->indent; i++ )
+		str_append(trline, "  ");
+
+	return trline;
+}
+
+static inline void _trace_line_print
+(string_t *trline, const struct sieve_runtime_env *renv)
+{
+	sieve_trace_log_write_line(renv->trace->log, trline);
+}
+
+static inline void _trace_line_print_empty
+(const struct sieve_runtime_env *renv)
+{
+	sieve_trace_log_write_line(renv->trace->log, NULL);
+}
+
+/*
+ * Trace errors
+ */
+
+void _sieve_runtime_trace_error
+(const struct sieve_runtime_env *renv, const char *fmt, va_list args)
+{
+	string_t *trline = _trace_line_new(renv, renv->pc, 0);
+
+	str_printfa(trline, "%s: #ERROR#: ", sieve_operation_mnemonic(renv->oprtn));
+	str_vprintfa(trline, fmt, args);
+
+	_trace_line_print(trline, renv);
+}
+
+void _sieve_runtime_trace_operand_error
+(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+	const char *fmt, va_list args)
+{
+	string_t *trline = _trace_line_new(renv, oprnd->address,
+		sieve_runtime_get_source_location(renv, oprnd->address));
+
+	str_printfa(trline, "%s: #ERROR#: ", sieve_operation_mnemonic(renv->oprtn));
+
+	if ( oprnd->field_name != NULL )
+		str_printfa(trline, "%s: ", oprnd->field_name);
+
+	str_vprintfa(trline, fmt, args);
+
+	_trace_line_print(trline, renv);
+}
+
+/*
+ * Trace info
+ */
+
+static inline void ATTR_FORMAT(4, 0) _sieve_runtime_trace_vprintf
+(const struct sieve_runtime_env *renv, sieve_size_t address,
+	unsigned int cmd_line, const char *fmt, va_list args)
+{
+	string_t *trline = _trace_line_new(renv, address, cmd_line);
+
+	str_vprintfa(trline, fmt, args);
+
+	_trace_line_print(trline, renv);
+}
+
+static inline void ATTR_FORMAT(4, 5) _sieve_runtime_trace_printf
+(const struct sieve_runtime_env *renv, sieve_size_t address,
+	unsigned int cmd_line, const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	_sieve_runtime_trace_vprintf(renv, address, cmd_line, fmt, args);
+	va_end(args);
+}
+
+void ATTR_FORMAT(2, 0) _sieve_runtime_trace
+(const struct sieve_runtime_env *renv, const char *fmt, va_list args)
+{
+	_sieve_runtime_trace_vprintf
+		(renv, renv->oprtn->address, sieve_runtime_get_command_location(renv),
+			fmt, args);
+}
+
+void _sieve_runtime_trace_address
+(const struct sieve_runtime_env *renv, sieve_size_t address,
+	const char *fmt, va_list args)
+{
+	_sieve_runtime_trace_vprintf
+		(renv, address, sieve_runtime_get_source_location(renv, address), fmt,
+			args);
+}
+
+/*
+ * Trace boundaries
+ */
+
+void _sieve_runtime_trace_begin(const struct sieve_runtime_env *renv)
+{
+	const char *script_name = ( renv->script != NULL ?
+		sieve_script_name(renv->script) : sieve_binary_path(renv->sbin) );
+
+	_trace_line_print_empty(renv);
+	_sieve_runtime_trace_printf(renv, renv->pc, 0,
+		"## Started executing script '%s'", script_name);
+}
+
+void _sieve_runtime_trace_end(const struct sieve_runtime_env *renv)
+{
+	const char *script_name = ( renv->script != NULL ?
+		sieve_script_name(renv->script) : sieve_binary_path(renv->sbin) );
+
+	_sieve_runtime_trace_printf(renv, renv->pc, 0,
+		"## Finished executing script '%s'", script_name);
+	_trace_line_print_empty(renv);
+}
+
+void _sieve_runtime_trace_sep(const struct sieve_runtime_env *renv)
+{
+	_trace_line_print_empty(renv);
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-runtime-trace.h
@@ -0,0 +1,182 @@
+#ifndef SIEVE_RUNTIME_TRACE_H
+#define SIEVE_RUNTIME_TRACE_H
+
+#include "sieve-common.h"
+#include "sieve-runtime.h"
+
+/*
+ * Runtime trace
+ */
+
+struct sieve_runtime_trace {
+	struct sieve_trace_config config;
+	struct sieve_trace_log *log;
+	unsigned int indent;
+};
+
+/* Trace configuration */
+
+static inline bool sieve_runtime_trace_active
+(const struct sieve_runtime_env *renv, sieve_trace_level_t trace_level)
+{
+	return ( renv->trace != NULL && trace_level <= renv->trace->config.level );
+}
+
+static inline bool sieve_runtime_trace_hasflag
+(const struct sieve_runtime_env *renv, unsigned int flag)
+{
+	return ( renv->trace != NULL && (renv->trace->config.flags & flag) != 0 );
+}
+
+/* Trace indent */
+
+static inline void sieve_runtime_trace_descend
+(const struct sieve_runtime_env *renv)
+{
+	if ( renv->trace != NULL ) renv->trace->indent++;
+}
+
+static inline void sieve_runtime_trace_ascend
+(const struct sieve_runtime_env *renv)
+{
+	if ( renv->trace != NULL ) renv->trace->indent--;
+}
+
+static inline void sieve_runtime_trace_toplevel
+(const struct sieve_runtime_env *renv)
+{
+	if ( renv->trace != NULL ) renv->trace->indent = 0;
+}
+
+/* Trace errors */
+
+void _sieve_runtime_trace_error
+	(const struct sieve_runtime_env *renv, const char *fmt, va_list args)
+		ATTR_FORMAT(2, 0);
+
+void _sieve_runtime_trace_operand_error
+	(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+		const char *fmt, va_list args) ATTR_FORMAT(3, 0);
+
+static inline void sieve_runtime_trace_error
+	(const struct sieve_runtime_env *renv, const char *fmt, ...)
+		ATTR_FORMAT(2, 3);
+
+static inline void sieve_runtime_trace_operand_error
+	(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+		const char *fmt, ...) ATTR_FORMAT(3, 4);
+
+static inline void ATTR_FORMAT(2, 3) sieve_runtime_trace_error
+	(const struct sieve_runtime_env *renv, const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	if ( renv->trace != NULL )
+		_sieve_runtime_trace_error(renv, fmt, args);
+	va_end(args);
+}
+
+static inline void ATTR_FORMAT(3, 4) sieve_runtime_trace_operand_error
+	(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+		const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	if ( renv->trace != NULL )
+		_sieve_runtime_trace_operand_error(renv, oprnd, fmt, args);
+	va_end(args);
+}
+
+/* Trace info */
+
+void _sieve_runtime_trace
+	(const struct sieve_runtime_env *renv, const char *fmt, va_list args)
+		ATTR_FORMAT(2, 0);
+
+static inline void sieve_runtime_trace
+	(const struct sieve_runtime_env *renv, sieve_trace_level_t trace_level,
+		const char *fmt, ...) ATTR_FORMAT(3, 4);
+
+static inline void ATTR_FORMAT(3, 4) sieve_runtime_trace
+(const struct sieve_runtime_env *renv, sieve_trace_level_t trace_level,
+	const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+
+	if ( renv->trace != NULL && trace_level <= renv->trace->config.level ) {
+		_sieve_runtime_trace(renv, fmt, args);
+	}
+
+	va_end(args);
+}
+
+void _sieve_runtime_trace_address
+	(const struct sieve_runtime_env *renv, sieve_size_t address,
+		const char *fmt, va_list args) ATTR_FORMAT(3, 0);
+
+static inline void sieve_runtime_trace_address
+	(const struct sieve_runtime_env *renv, sieve_trace_level_t trace_level,
+		sieve_size_t address, const char *fmt, ...) ATTR_FORMAT(4, 5);
+
+static inline void ATTR_FORMAT(4, 5) sieve_runtime_trace_address
+(const struct sieve_runtime_env *renv, sieve_trace_level_t trace_level,
+	sieve_size_t address, const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+
+	if ( renv->trace != NULL && trace_level <= renv->trace->config.level ) {
+		_sieve_runtime_trace_address(renv, address, fmt, args);
+	}
+
+	va_end(args);
+}
+
+static inline void ATTR_FORMAT(3, 4) sieve_runtime_trace_here
+(const struct sieve_runtime_env *renv, sieve_trace_level_t trace_level,
+	const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+
+	if ( renv->trace != NULL && trace_level <= renv->trace->config.level ) {
+		_sieve_runtime_trace_address(renv, renv->pc, fmt, args);
+	}
+
+	va_end(args);
+}
+
+/* Trace boundaries */
+
+void _sieve_runtime_trace_begin(const struct sieve_runtime_env *renv);
+void _sieve_runtime_trace_end(const struct sieve_runtime_env *renv);
+void _sieve_runtime_trace_sep(const struct sieve_runtime_env *renv);
+
+static inline void sieve_runtime_trace_begin
+(const struct sieve_runtime_env *renv)
+{
+	if ( renv->trace != NULL )
+		_sieve_runtime_trace_begin(renv);
+}
+
+static inline void sieve_runtime_trace_end
+(const struct sieve_runtime_env *renv)
+{
+	if ( renv->trace != NULL )
+		_sieve_runtime_trace_end(renv);
+}
+
+static inline void sieve_runtime_trace_sep
+(const struct sieve_runtime_env *renv)
+{
+	if ( renv->trace != NULL )
+		_sieve_runtime_trace_sep(renv);
+}
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-runtime.h
@@ -0,0 +1,41 @@
+#ifndef SIEVE_RUNTIME_H
+#define SIEVE_RUNTIME_H
+
+#include "sieve-common.h"
+
+/*
+ * Runtime environment
+ */
+
+struct sieve_runtime_env {
+	/* Interpreter */
+	struct sieve_instance *svinst;
+	struct sieve_interpreter *interp;
+	enum sieve_execute_flags flags;
+	struct sieve_error_handler *ehandler;
+
+	/* Executing script */
+	struct sieve_script *script;
+	const struct sieve_script_env *scriptenv;
+	struct sieve_exec_status *exec_status;
+
+	/* Executing binary */
+	struct sieve_binary *sbin;
+	struct sieve_binary_block *sblock;
+
+	/* Current code */
+	sieve_size_t pc;
+	const struct sieve_operation *oprtn;
+
+	/* Tested message */
+	const struct sieve_message_data *msgdata;
+	struct sieve_message_context *msgctx;
+
+	/* Filter result */
+	struct sieve_result *result;
+
+	/* Runtime tracing */
+	struct sieve_runtime_trace *trace;
+};
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-script-private.h
@@ -0,0 +1,119 @@
+#ifndef SIEVE_SCRIPT_PRIVATE_H
+#define SIEVE_SCRIPT_PRIVATE_H
+
+#include "sieve-common.h"
+#include "sieve-script.h"
+
+/*
+ * Script object
+ */
+
+struct sieve_script_vfuncs {
+	void (*destroy)(struct sieve_script *script);
+
+	int (*open)
+		(struct sieve_script *script, enum sieve_error *error_r);
+
+	int (*get_stream)
+		(struct sieve_script *script, struct istream **stream_r,
+			enum sieve_error *error_r);
+	
+	/* binary */
+	int (*binary_read_metadata)
+		(struct sieve_script *_script, struct sieve_binary_block *sblock,
+			sieve_size_t *offset);
+	void (*binary_write_metadata)
+		(struct sieve_script *script, struct sieve_binary_block *sblock);
+	bool (*binary_dump_metadata)
+		(struct sieve_script *script, struct sieve_dumptime_env *denv,
+			struct sieve_binary_block *sblock, sieve_size_t *offset);
+	struct sieve_binary *(*binary_load)
+		(struct sieve_script *script, enum sieve_error *error_r);
+	int (*binary_save)
+		(struct sieve_script *script, struct sieve_binary *sbin,
+			bool update, enum sieve_error *error_r);
+	const char *(*binary_get_prefix)
+		(struct sieve_script *script);
+
+	/* management */
+	int (*rename)
+		(struct sieve_script *script, const char *newname);
+	int (*delete)(struct sieve_script *script);
+	int (*is_active)(struct sieve_script *script);
+	int (*activate)(struct sieve_script *script);
+
+	/* properties */
+	int (*get_size)
+		(const struct sieve_script *script, uoff_t *size_r);
+
+	/* matching */
+	bool (*equals)
+		(const struct sieve_script *script, const struct sieve_script *other);
+};
+
+struct sieve_script {
+	pool_t pool;
+	unsigned int refcount;
+	struct sieve_storage *storage;
+
+	const char *driver_name;
+	const struct sieve_script *script_class;
+	struct sieve_script_vfuncs v;
+
+	const char *name;
+	const char *location;
+
+	/* Stream */
+	struct istream *stream;
+
+	bool open:1;
+};
+
+void sieve_script_init
+(struct sieve_script *script, struct sieve_storage *storage,
+	const struct sieve_script *script_class, const char *location,
+	const char *name);
+
+/*
+ * Built-in script drivers
+ */
+
+extern const struct sieve_script sieve_data_script;
+extern const struct sieve_script sieve_file_script;
+extern const struct sieve_script sieve_dict_script;
+extern const struct sieve_script sieve_ldap_script;
+
+/*
+ * Error handling
+ */
+
+void sieve_script_set_error
+	(struct sieve_script *script, enum sieve_error error,
+		const char *fmt, ...) ATTR_FORMAT(3, 4);
+void sieve_script_set_internal_error
+	(struct sieve_script *script);
+void sieve_script_set_critical
+	(struct sieve_script *script, const char *fmt, ...)
+		ATTR_FORMAT(2, 3);
+
+void sieve_script_sys_error
+	(struct sieve_script *script, const char *fmt, ...)
+		ATTR_FORMAT(2, 3);
+void sieve_script_sys_warning
+	(struct sieve_script *script, const char *fmt, ...)
+		ATTR_FORMAT(2, 3);
+void sieve_script_sys_info
+	(struct sieve_script *script, const char *fmt, ...)
+		ATTR_FORMAT(2, 3);
+void sieve_script_sys_debug
+	(struct sieve_script *script, const char *fmt, ...)
+		ATTR_FORMAT(2, 3);
+
+/*
+ * Script sequence
+ */
+
+void sieve_script_sequence_init
+(struct sieve_script_sequence *seq, struct sieve_storage *storage);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-script.c
@@ -0,0 +1,891 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "compat.h"
+#include "unichar.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "hash.h"
+#include "array.h"
+#include "eacces-error.h"
+#include "istream.h"
+
+#include "sieve-common.h"
+#include "sieve-limits.h"
+#include "sieve-settings.h"
+#include "sieve-error.h"
+#include "sieve-dump.h"
+#include "sieve-binary.h"
+
+#include "sieve-storage-private.h"
+#include "sieve-script-private.h"
+
+#include <ctype.h>
+
+/*
+ * Script name
+ */
+
+bool sieve_script_name_is_valid(const char *scriptname)
+{
+	ARRAY_TYPE(unichars) uni_name;
+	unsigned int count, i;
+	const unichar_t *name_chars;
+	size_t namelen = strlen(scriptname);
+
+	/* Check minimum length */
+	if ( namelen == 0 )
+		return FALSE;
+
+	/* Check worst-case maximum length */
+	if ( namelen > SIEVE_MAX_SCRIPT_NAME_LEN * 4 )
+		return FALSE;
+
+	/* Intialize array for unicode characters */
+	t_array_init(&uni_name, namelen * 4);
+
+	/* Convert UTF-8 to UCS4/UTF-32 */
+	if ( uni_utf8_to_ucs4(scriptname, &uni_name) < 0 )
+		return FALSE;
+	name_chars = array_get(&uni_name, &count);
+
+	/* Check true maximum length */
+	if ( count > SIEVE_MAX_SCRIPT_NAME_LEN )
+		return FALSE;
+
+	/* Scan name for invalid characters
+	 *   FIXME: compliance with Net-Unicode Definition (Section 2 of
+	 *          RFC 5198) is not checked fully and no normalization
+	 *          is performed.
+	 */
+	for ( i = 0; i < count; i++ ) {
+
+		/* 0000-001F; [CONTROL CHARACTERS] */
+		if ( name_chars[i] <= 0x001f )
+			return FALSE;
+
+		/* 002F; SLASH (not RFC-prohibited, but '/' is dangerous) */
+		if ( name_chars[i] == 0x002f )
+			return FALSE;
+
+		/* 007F; DELETE */
+		if ( name_chars[i] == 0x007f )
+			return FALSE;
+
+		/* 0080-009F; [CONTROL CHARACTERS] */
+		if ( name_chars[i] >= 0x0080 && name_chars[i] <= 0x009f )
+			return FALSE;
+
+		/* 00FF */
+		if ( name_chars[i] == 0x00ff )
+			return FALSE;
+
+		/* 2028; LINE SEPARATOR */
+		/* 2029; PARAGRAPH SEPARATOR */
+		if ( name_chars[i] == 0x2028 || name_chars[i] == 0x2029 )
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+/*
+ * Script instance
+ */
+
+void sieve_script_init
+(struct sieve_script *script, struct sieve_storage *storage,
+	const struct sieve_script *script_class,	const char *location,
+	const char *name)
+{
+	i_assert( storage != NULL );
+
+	script->script_class = script_class;
+	script->refcount = 1;
+	script->storage = storage;
+	script->location = p_strdup_empty(script->pool, location);
+	script->name = p_strdup(script->pool, name);
+
+	sieve_storage_ref(storage);
+}
+
+struct sieve_script *sieve_script_create
+(struct sieve_instance *svinst, const char *location, const char *name,
+	enum sieve_error *error_r)
+{
+	struct sieve_storage *storage;
+	struct sieve_script *script;
+	enum sieve_error error;
+
+	if ( error_r != NULL )
+		*error_r = SIEVE_ERROR_NONE;
+	else
+		error_r = &error;
+
+	storage = sieve_storage_create(svinst, location, 0, error_r);
+	if ( storage == NULL )
+		return NULL;
+
+	script = sieve_storage_get_script(storage, name, error_r);
+	
+	sieve_storage_unref(&storage);
+	return script;
+}
+
+void sieve_script_ref(struct sieve_script *script)
+{
+	script->refcount++;
+}
+
+void sieve_script_unref(struct sieve_script **_script)
+{
+	struct sieve_script *script = *_script;
+
+	i_assert( script->refcount > 0 );
+
+	if ( --script->refcount != 0 )
+		return;
+
+	i_stream_unref(&script->stream);
+
+	if ( script->v.destroy != NULL )
+		script->v.destroy(script);
+
+	sieve_storage_unref(&script->storage);
+	pool_unref(&script->pool);
+	*_script = NULL;
+}
+
+int sieve_script_open
+(struct sieve_script *script, enum sieve_error *error_r)
+{
+	enum sieve_error error;
+
+	if ( error_r != NULL )
+		*error_r = SIEVE_ERROR_NONE;
+	else
+		error_r = &error;
+
+	if ( script->open )
+		return 0;
+
+	if ( script->v.open(script, error_r) < 0 )
+		return -1;
+
+	i_assert( script->location != NULL );
+	i_assert( script->name != NULL );
+	script->open = TRUE;
+
+	if ( *script->name != '\0' ) {
+		sieve_script_sys_debug(script,
+			"Opened script `%s' from `%s'",
+			script->name, script->location);
+	} else {
+		sieve_script_sys_debug(script,
+			"Opened nameless script from `%s'",
+			script->location);
+	}
+	return 0;
+}
+
+int sieve_script_open_as
+(struct sieve_script *script, const char *name, enum sieve_error *error_r)
+{
+	if ( sieve_script_open(script, error_r) < 0 )
+		return -1;
+
+	/* override name */
+	script->name = p_strdup(script->pool, name);
+	return 0;
+}
+
+struct sieve_script *sieve_script_create_open
+(struct sieve_instance *svinst, const char *location, const char *name,
+	enum sieve_error *error_r)
+{
+	struct sieve_script *script;
+
+	script = sieve_script_create(svinst, location, name, error_r);
+	if ( script == NULL )
+		return NULL;
+
+	if ( sieve_script_open(script, error_r) < 0 ) {
+		sieve_script_unref(&script);
+		return NULL;
+	}
+	
+	return script;
+}
+
+int sieve_script_check
+(struct sieve_instance *svinst, const char *location, const char *name,
+	enum sieve_error *error_r)
+{
+	struct sieve_script *script;
+	enum sieve_error error;
+
+	if (error_r == NULL)
+		error_r = &error;
+
+	script = sieve_script_create_open(svinst, location, name, error_r);
+	if (script == NULL)
+		return ( *error_r == SIEVE_ERROR_NOT_FOUND ? 0 : -1);
+
+	sieve_script_unref(&script);
+	return 1;
+}
+
+/*
+ * Properties
+ */
+
+const char *sieve_script_name(const struct sieve_script *script)
+{
+	return script->name;
+}
+
+const char *sieve_script_location(const struct sieve_script *script)
+{
+	return script->location;
+}
+
+struct sieve_instance *sieve_script_svinst(const struct sieve_script *script)
+{
+	return script->storage->svinst;
+}
+
+int sieve_script_get_size(struct sieve_script *script, uoff_t *size_r)
+{
+	struct istream *stream;
+	int ret;
+
+	if ( script->v.get_size != NULL ) {
+		if ( (ret=script->v.get_size(script, size_r)) != 0)
+			return ret;
+	}
+
+	/* Try getting size from the stream */
+	if ( script->stream == NULL &&
+		sieve_script_get_stream(script, &stream, NULL) < 0 )
+		return -1;
+
+	if (i_stream_get_size(script->stream, TRUE, size_r) < 0) {
+		sieve_storage_set_critical(script->storage,
+			"i_stream_get_size(%s) failed: %s",
+			i_stream_get_name(script->stream),
+			i_stream_get_error(script->stream));
+		return -1;
+	}
+	return 0;
+}
+
+bool sieve_script_is_open(const struct sieve_script *script)
+{
+	return script->open;
+}
+
+bool sieve_script_is_default(const struct sieve_script *script)
+{
+	return script->storage->is_default;
+}
+
+/*
+ * Stream management
+ */
+
+int sieve_script_get_stream
+(struct sieve_script *script, struct istream **stream_r,
+	enum sieve_error *error_r)
+{
+	enum sieve_error error;
+	int ret;
+
+	if ( error_r != NULL )
+		*error_r = SIEVE_ERROR_NONE;
+	else
+		error_r = &error;
+
+	if ( script->stream != NULL ) {
+		*stream_r = script->stream;
+		return 0;
+	}
+
+	// FIXME: necessary?
+	i_assert( script->open );
+
+	T_BEGIN {
+		ret = script->v.get_stream(script, &script->stream, error_r);
+	} T_END;
+
+	if ( ret < 0 )
+		return -1;
+
+	*stream_r = script->stream;
+	return 0;
+}
+
+/*
+ * Comparison
+ */
+
+bool sieve_script_equals
+(const struct sieve_script *script, const struct sieve_script *other)
+{
+	if ( script == other )
+		return TRUE;
+
+	if ( script == NULL || other == NULL )
+		return FALSE;
+
+	if ( script->script_class != other->script_class )
+		return FALSE;
+
+	if ( script->v.equals == NULL ) {
+		i_assert ( script->location != NULL && other->location != NULL);
+
+		return ( strcmp(script->location, other->location) == 0 );
+	}
+
+	return script->v.equals(script, other);
+}
+
+unsigned int sieve_script_hash(const struct sieve_script *script)
+{
+	i_assert( script->name != NULL );
+
+	return str_hash(script->name);
+}
+
+/*
+ * Binary
+ */
+
+int sieve_script_binary_read_metadata
+(struct sieve_script *script, struct sieve_binary_block *sblock,
+	sieve_size_t *offset)
+{
+	struct sieve_binary *sbin = sieve_binary_block_get_binary(sblock);
+	string_t *storage_class, *location;
+	unsigned int version;
+
+	if ( sieve_binary_block_get_size(sblock) - *offset == 0 )
+		return 0;
+
+	/* storage class */
+	if ( !sieve_binary_read_string(sblock, offset, &storage_class) ) {
+		sieve_script_sys_error(script,
+			"Binary `%s' has invalid metadata for script `%s': "
+			"Invalid storage class",
+			sieve_binary_path(sbin), script->location);
+		return -1;
+	}
+	if ( strcmp(str_c(storage_class), script->driver_name) != 0 ) {
+		sieve_script_sys_debug(script,
+			"Binary `%s' reports unexpected driver name for script `%s' "
+			"(`%s' rather than `%s')",
+			sieve_binary_path(sbin), script->location,
+			str_c(storage_class), script->driver_name);
+		return 0;
+	}
+
+	/* version */
+	if ( !sieve_binary_read_unsigned(sblock, offset, &version) ) {
+		sieve_script_sys_error(script,
+			"Binary `%s' has invalid metadata for script `%s': "
+			"Invalid version",
+			sieve_binary_path(sbin), script->location);
+		return -1;
+	}
+	if ( script->storage->version != version ) {
+		sieve_script_sys_debug(script,
+			"Binary `%s' was compiled with "
+			"a different version of the `%s' script storage class "
+			"(compiled v%d, expected v%d; "
+				"automatically fixed when re-compiled)",
+			sieve_binary_path(sbin), script->driver_name,
+		 	version, script->storage->version);
+		return 0;
+	}
+
+	/* location */
+	if ( !sieve_binary_read_string(sblock, offset, &location) ) {
+		sieve_script_sys_error(script,
+			"Binary `%s' has invalid metadata for script `%s': "
+			"Invalid location",
+			sieve_binary_path(sbin), script->location);
+		return -1;
+	}
+	i_assert( script->location != NULL );
+	if ( strcmp(str_c(location), script->location) != 0 ) {
+		sieve_script_sys_debug(script,
+			"Binary `%s' reports different location "
+			"for script `%s' (binary points to `%s')",
+			sieve_binary_path(sbin), script->location, str_c(location));
+		return 0;
+	}
+	
+	if ( script->v.binary_read_metadata == NULL )
+		return 1;
+
+	return script->v.binary_read_metadata(script, sblock, offset);
+}
+
+void sieve_script_binary_write_metadata
+(struct sieve_script *script, struct sieve_binary_block *sblock)
+{
+	sieve_binary_emit_cstring(sblock, script->driver_name);
+	sieve_binary_emit_unsigned(sblock, script->storage->version);
+	sieve_binary_emit_cstring(sblock,
+		( script->location == NULL ? "" : script->location ));
+
+	if ( script->v.binary_write_metadata == NULL )
+		return;
+
+	script->v.binary_write_metadata(script, sblock);
+}
+
+bool sieve_script_binary_dump_metadata
+(struct sieve_script *script, struct sieve_dumptime_env *denv,
+	struct sieve_binary_block *sblock, sieve_size_t *offset)
+{
+	struct sieve_binary *sbin = sieve_binary_block_get_binary(sblock);
+	struct sieve_instance *svinst = sieve_binary_svinst(sbin);
+	string_t *storage_class, *location;
+	struct sieve_script *adhoc_script = NULL;
+	unsigned int version;
+	bool result = TRUE;
+
+	/* storage class */
+	if ( !sieve_binary_read_string(sblock, offset, &storage_class) )
+		return FALSE;
+	sieve_binary_dumpf(denv, "class = %s\n", str_c(storage_class));
+
+	/* version */
+	if ( !sieve_binary_read_unsigned(sblock, offset, &version) )
+		return FALSE;
+	sieve_binary_dumpf(denv, "class.version = %d\n", version);
+
+	/* location */
+	if ( !sieve_binary_read_string(sblock, offset, &location) )
+		return FALSE;
+	sieve_binary_dumpf(denv, "location = %s\n", str_c(location));
+	
+	if ( script == NULL ) {
+		script = adhoc_script = sieve_script_create
+			(svinst, str_c(location), NULL, NULL);
+	}
+
+	if ( script != NULL ) {
+		if ( script->v.binary_dump_metadata == NULL )
+			return TRUE;
+
+		result = script->v.binary_dump_metadata(script, denv, sblock, offset);
+	}
+
+	if ( adhoc_script != NULL )
+		sieve_script_unref(&adhoc_script);
+	return result;
+}
+
+struct sieve_binary *sieve_script_binary_load
+(struct sieve_script *script, enum sieve_error *error_r)
+{
+	if ( script->v.binary_load == NULL ) {
+		*error_r = SIEVE_ERROR_NOT_POSSIBLE;
+		return NULL;
+	}
+
+	return script->v.binary_load(script, error_r);
+}
+
+int sieve_script_binary_save
+(struct sieve_script *script, struct sieve_binary *sbin, bool update,
+	enum sieve_error *error_r)
+{
+	struct sieve_script *bin_script = sieve_binary_script(sbin);
+	enum sieve_error error;
+
+	if ( error_r != NULL )
+		*error_r = SIEVE_ERROR_NONE;
+	else
+		error_r = &error;
+
+	i_assert(bin_script == NULL || sieve_script_equals(bin_script, script));
+
+	if ( script->v.binary_save == NULL ) {
+		*error_r = SIEVE_ERROR_NOT_POSSIBLE;
+		return -1;
+	}
+
+	return script->v.binary_save(script, sbin, update, error_r);
+}
+
+const char *sieve_script_binary_get_prefix
+(struct sieve_script *script)
+{
+	struct sieve_storage *storage = script->storage;
+
+	if ( storage->bin_dir != NULL &&
+		sieve_storage_setup_bindir(storage, 0700) >= 0 ) {
+		return t_strconcat(storage->bin_dir, "/", script->name, NULL);
+	}
+
+	if ( script->v.binary_get_prefix == NULL )
+		return NULL;
+
+	return script->v.binary_get_prefix(script);
+}
+
+/* 
+ * Management
+ */
+
+int sieve_script_rename
+(struct sieve_script *script, const char *newname)
+{
+	struct sieve_storage *storage = script->storage;
+	const char *oldname = script->name;
+	int ret;
+
+	i_assert( newname != NULL );
+
+	/* Check script name */
+	if ( !sieve_script_name_is_valid(newname) ) {
+		sieve_script_set_error(script,
+			SIEVE_ERROR_BAD_PARAMS,
+			"Invalid new Sieve script name `%s'.",
+			str_sanitize(newname, 80));
+		return -1;
+	}
+
+	i_assert( script->open ); // FIXME: auto-open?
+
+	if ( storage->default_for == NULL ) {
+		i_assert( (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 );
+
+		/* rename script */
+		i_assert( script->v.rename != NULL );
+		ret = script->v.rename(script, newname);
+
+		/* rename INBOX mailbox attribute */
+		if ( ret >= 0 && oldname != NULL )
+			(void)sieve_storage_sync_script_rename(storage, oldname, newname);
+
+	} else if ( sieve_storage_check_script
+			(storage->default_for, newname, NULL) > 0 ) {
+		sieve_script_set_error(script, SIEVE_ERROR_EXISTS,
+			"A sieve script with that name already exists.");
+		sieve_storage_copy_error(storage->default_for, storage);
+		ret = -1;
+
+	} else {
+		struct istream *input;
+		
+		/* copy from default */
+		if ( (ret=sieve_script_open(script, NULL)) >= 0 &&
+			(ret=sieve_script_get_stream(script, &input, NULL)) >= 0 ) {
+			ret = sieve_storage_save_as
+				(storage->default_for, input, newname);
+
+			if ( ret < 0 ) {
+				sieve_storage_copy_error(storage, storage->default_for);
+
+			} else if ( sieve_script_is_active(script) > 0 ) {
+				struct sieve_script *newscript;
+				enum sieve_error error;
+
+				newscript = sieve_storage_open_script
+					(storage->default_for, newname, &error);
+				if ( newscript == NULL ) {
+					/* Somehow not actually saved */
+					ret = ( error == SIEVE_ERROR_NOT_FOUND ? 0 : -1 );
+				} else if ( sieve_script_activate(newscript, (time_t)-1) < 0 ) {
+					/* Failed to activate; roll back */
+					ret = -1;
+					(void)sieve_script_delete(newscript, TRUE);
+					sieve_script_unref(&newscript);
+				}
+
+				if (ret < 0) {
+					sieve_storage_sys_error(storage,
+						"Failed to implicitly activate script `%s' "
+						"after rename",	newname);
+					sieve_storage_copy_error(storage->default_for, storage);
+				}
+			}
+		} else {
+			sieve_storage_copy_error(storage->default_for, storage);
+		}
+	}
+
+	return ret;
+}
+
+int sieve_script_delete(struct sieve_script *script,
+	bool ignore_active)
+{
+	struct sieve_storage *storage = script->storage;
+	bool is_active = FALSE;
+	int ret = 0;
+
+	i_assert( script->open ); // FIXME: auto-open?
+
+	/* Is the requested script active? */
+	if ( sieve_script_is_active(script) > 0 ) {
+		is_active = TRUE;
+		if ( !ignore_active ) {
+			sieve_script_set_error(script, SIEVE_ERROR_ACTIVE,
+				"Cannot delete the active Sieve script.");
+			if (storage->default_for != NULL)
+				sieve_storage_copy_error(storage->default_for, storage);
+			return -1;
+		}
+	}
+
+	/* Trying to delete the default script? */
+	if ( storage->is_default ) {
+		/* ignore */
+		return 0;
+	}
+
+	i_assert( (script->storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 );
+
+	/* Deactivate it explicity */
+	if ( ignore_active && is_active )
+		(void)sieve_storage_deactivate(storage, (time_t)-1);
+
+	i_assert( script->v.delete != NULL );
+	ret = script->v.delete(script);
+
+	/* unset INBOX mailbox attribute */
+	if ( ret >= 0 )
+		(void)sieve_storage_sync_script_delete(storage, script->name);
+	return ret;
+}
+
+int sieve_script_is_active(struct sieve_script *script)
+{
+	struct sieve_storage *storage = script->storage;
+
+	/* Special handling if this is a default script */
+	if ( storage->default_for != NULL ) {
+		int ret = sieve_storage_active_script_is_default
+			(storage->default_for);
+		if (ret < 0)
+			sieve_storage_copy_error(storage, storage->default_for);
+		return ret;
+	}	
+
+	if ( script->v.is_active == NULL )
+		return 0;
+	return script->v.is_active(script);
+}
+
+int sieve_script_activate(struct sieve_script *script, time_t mtime)
+{
+	struct sieve_storage *storage = script->storage;
+	int ret = 0;
+
+	i_assert( script->open ); // FIXME: auto-open?
+
+	if (storage->default_for == NULL) {
+		i_assert( (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 );
+	
+		i_assert( script->v.activate != NULL );
+		ret = script->v.activate(script);
+
+		if (ret >= 0) {
+			sieve_storage_set_modified(storage, mtime);
+			(void)sieve_storage_sync_script_activate(storage);
+		}
+
+	} else {
+		/* Activating the default script is equal to deactivating
+		   the storage */
+		ret = sieve_storage_deactivate
+			(storage->default_for, (time_t)-1);
+		if (ret < 0)
+			sieve_storage_copy_error(storage, storage->default_for);
+	}
+
+	return ret;
+}
+
+/*
+ * Error handling
+ */
+
+void sieve_script_set_error
+(struct sieve_script *script, enum sieve_error error,
+	const char *fmt, ...)
+{
+	struct sieve_storage *storage = script->storage;
+	va_list va;
+
+	sieve_storage_clear_error(storage);
+
+	if (fmt != NULL) {
+		va_start(va, fmt);
+		storage->error = i_strdup_vprintf(fmt, va);
+		va_end(va);
+	}
+	storage->error_code = error;
+}
+
+void sieve_script_set_internal_error
+(struct sieve_script *script)
+{
+	sieve_storage_set_internal_error(script->storage);
+}
+
+void sieve_script_set_critical
+(struct sieve_script *script, const char *fmt, ...)
+{
+	struct sieve_storage *storage = script->storage;
+
+	va_list va;
+
+	if (fmt != NULL) {
+		if ( (storage->flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0 ) {
+			va_start(va, fmt);
+			sieve_sys_error(storage->svinst, "%s script: %s",
+				storage->driver_name, t_strdup_vprintf(fmt, va));
+			va_end(va);
+
+			sieve_storage_set_internal_error(storage);
+
+		} else {
+			sieve_storage_clear_error(storage);
+
+			/* no user is involved while synchronizing, so do it the
+			   normal way */
+			va_start(va, fmt);
+			storage->error = i_strdup_vprintf(fmt, va);
+			va_end(va);
+
+			storage->error_code = SIEVE_ERROR_TEMP_FAILURE;
+		}
+	}
+}
+
+const char *sieve_script_get_last_error
+(struct sieve_script *script, enum sieve_error *error_r)
+{
+	return sieve_storage_get_last_error(script->storage, error_r);
+}
+
+const char *sieve_script_get_last_error_lcase
+(struct sieve_script *script)
+{
+	char *errormsg = t_strdup_noconst(script->storage->error);
+	errormsg[0] = i_tolower(errormsg[0]);		
+	return errormsg;
+}
+
+void sieve_script_sys_error
+(struct sieve_script *script, const char *fmt, ...)
+{
+	struct sieve_instance *svinst = script->storage->svinst;
+	va_list va;
+
+	va_start(va, fmt);
+	sieve_sys_error(svinst, "%s script: %s",
+		script->driver_name, t_strdup_vprintf(fmt, va));
+	va_end(va);	
+}
+
+void sieve_script_sys_warning
+(struct sieve_script *script, const char *fmt, ...)
+{
+	struct sieve_instance *svinst = script->storage->svinst;
+	va_list va;
+
+	va_start(va, fmt);
+	sieve_sys_warning(svinst, "%s script: %s",
+		script->driver_name, t_strdup_vprintf(fmt, va));
+	va_end(va);	
+}
+
+void sieve_script_sys_info
+(struct sieve_script *script, const char *fmt, ...)
+{
+	struct sieve_instance *svinst = script->storage->svinst;
+	va_list va;
+
+	va_start(va, fmt);
+	sieve_sys_info(svinst, "%s script: %s",
+		script->driver_name, t_strdup_vprintf(fmt, va));
+	va_end(va);	
+}
+
+void sieve_script_sys_debug
+(struct sieve_script *script, const char *fmt, ...)
+{
+	struct sieve_instance *svinst = script->storage->svinst;
+	va_list va;
+
+	if (!svinst->debug)
+		return;
+
+	va_start(va, fmt);
+	sieve_sys_debug(svinst, "%s script: %s",
+		script->driver_name, t_strdup_vprintf(fmt, va));
+	va_end(va);	
+}
+
+/*
+ * Script sequence
+ */
+
+void sieve_script_sequence_init
+(struct sieve_script_sequence *seq, struct sieve_storage *storage)
+{
+	seq->storage = storage;
+	sieve_storage_ref(storage);
+}
+
+struct sieve_script_sequence *sieve_script_sequence_create
+(struct sieve_instance *svinst, const char *location,
+	enum sieve_error *error_r)
+{
+	struct sieve_storage *storage;
+	struct sieve_script_sequence *seq;
+	enum sieve_error error;
+
+	if ( error_r != NULL )
+		*error_r = SIEVE_ERROR_NONE;
+	else
+		error_r = &error;
+
+	storage = sieve_storage_create(svinst, location, 0, error_r);
+	if ( storage == NULL )
+		return NULL;
+
+	seq = sieve_storage_get_script_sequence(storage, error_r);
+	
+	sieve_storage_unref(&storage);
+	return seq;
+}
+
+struct sieve_script *sieve_script_sequence_next
+(struct sieve_script_sequence *seq, enum sieve_error *error_r)
+{
+	struct sieve_storage *storage = seq->storage;
+
+	i_assert( storage->v.script_sequence_next != NULL );
+	return storage->v.script_sequence_next(seq, error_r);	
+}
+
+void sieve_script_sequence_free(struct sieve_script_sequence **_seq)
+{
+	struct sieve_script_sequence *seq = *_seq;
+	struct sieve_storage *storage = seq->storage;
+
+	if ( storage->v.script_sequence_destroy != NULL )
+		storage->v.script_sequence_destroy(seq);
+
+	sieve_storage_unref(&storage);
+	*_seq = NULL;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-script.h
@@ -0,0 +1,174 @@
+#ifndef SIEVE_SCRIPT_H
+#define SIEVE_SCRIPT_H
+
+#include "sieve-common.h"
+
+#include <sys/types.h>
+
+
+/*
+ * Sieve script name
+ */
+
+bool sieve_script_name_is_valid(const char *scriptname);
+
+/*
+ * Sieve script file
+ */
+
+bool sieve_script_file_has_extension(const char *filename);
+
+/*
+ * Sieve script class
+ */
+
+void sieve_script_class_register
+	(struct sieve_instance *svinst, const struct sieve_script *script_class);
+void sieve_script_class_unregister
+	(struct sieve_instance *svinst, const struct sieve_script *script_class);	
+
+/*
+ * Sieve script instance
+ */
+
+struct sieve_script;
+
+ARRAY_DEFINE_TYPE(sieve_script, struct sieve_script *);
+
+struct sieve_script *sieve_script_create
+	(struct sieve_instance *svinst, const char *location, const char *name,
+		enum sieve_error *error_r)
+		ATTR_NULL(3,4);
+
+void sieve_script_ref(struct sieve_script *script);
+void sieve_script_unref(struct sieve_script **script);
+
+int sieve_script_open
+	(struct sieve_script *script, enum sieve_error *error_r)
+		ATTR_NULL(2);
+int sieve_script_open_as
+	(struct sieve_script *script, const char *name,
+		enum sieve_error *error_r) ATTR_NULL(3);
+
+struct sieve_script *sieve_script_create_open
+	(struct sieve_instance *svinst, const char *location,
+		const char *name, enum sieve_error *error_r)
+		ATTR_NULL(3, 4);
+int sieve_script_check
+	(struct sieve_instance *svinst, const char *location,
+		const char *name, enum sieve_error *error_r)
+		ATTR_NULL(3, 4);
+
+/*
+ * Data script
+ */
+
+struct sieve_script *sieve_data_script_create_from_input
+	(struct sieve_instance *svinst, const char *name,
+		struct istream *input);
+
+/*
+ * Binary
+ */
+
+int sieve_script_binary_read_metadata
+	(struct sieve_script *script, struct sieve_binary_block *sblock,
+		sieve_size_t *offset);
+void sieve_script_binary_write_metadata
+	(struct sieve_script *script, struct sieve_binary_block *sblock);
+bool sieve_script_binary_dump_metadata
+	(struct sieve_script *script, struct sieve_dumptime_env *denv,
+		struct sieve_binary_block *sblock, sieve_size_t *offset)
+		ATTR_NULL(1);
+
+struct sieve_binary *sieve_script_binary_load
+	(struct sieve_script *script, enum sieve_error *error_r);
+int sieve_script_binary_save
+	(struct sieve_script *script, struct sieve_binary *sbin, bool update,
+		enum sieve_error *error_r) ATTR_NULL(4);
+
+const char *sieve_script_binary_get_prefix
+	(struct sieve_script *script);
+
+/*
+ * Stream management
+ */
+
+int sieve_script_get_stream
+	(struct sieve_script *script, struct istream **stream_r,
+		enum sieve_error *error_r) ATTR_NULL(3);
+
+/*
+ * Management
+ */
+
+// FIXME: check read/write flag!
+
+int sieve_script_rename
+	(struct sieve_script *script, const char *newname);
+int sieve_script_is_active(struct sieve_script *script);
+int sieve_script_activate
+	(struct sieve_script *script, time_t mtime);
+int sieve_script_delete
+	(struct sieve_script *script, bool ignore_active);
+
+/*
+ * Properties
+ */
+
+const char *sieve_script_name
+	(const struct sieve_script *script) ATTR_PURE;
+const char *sieve_script_location
+	(const struct sieve_script *script) ATTR_PURE;
+struct sieve_instance *sieve_script_svinst
+	(const struct sieve_script *script) ATTR_PURE;
+
+int sieve_script_get_size(struct sieve_script *script, uoff_t *size_r);
+bool sieve_script_is_open
+	(const struct sieve_script *script) ATTR_PURE;
+bool sieve_script_is_default
+	(const struct sieve_script *script) ATTR_PURE;
+
+const char *sieve_file_script_get_dirpath
+	(const struct sieve_script *script) ATTR_PURE;
+const char *sieve_file_script_get_path
+	(const struct sieve_script *script) ATTR_PURE;
+
+/*
+ * Comparison
+ */
+
+bool sieve_script_equals
+	(const struct sieve_script *script, const struct sieve_script *other);
+
+unsigned int sieve_script_hash(const struct sieve_script *script);
+static inline int sieve_script_cmp
+(const struct sieve_script *script, const struct sieve_script *other)
+{
+	return ( sieve_script_equals(script, other) ? 0 : -1 );
+}
+
+/*
+ * Error handling
+ */
+
+const char *sieve_script_get_last_error
+	(struct sieve_script *script, enum sieve_error *error_r)
+	ATTR_NULL(2);
+const char *sieve_script_get_last_error_lcase
+	(struct sieve_script *script);
+
+/*
+ * Script sequence
+ */
+
+struct sieve_script_sequence;
+
+struct sieve_script_sequence *sieve_script_sequence_create
+(struct sieve_instance *svinst, const char *location,
+	enum sieve_error *error_r);
+struct sieve_script *sieve_script_sequence_next
+	(struct sieve_script_sequence *seq, enum sieve_error *error_r);
+void sieve_script_sequence_free(struct sieve_script_sequence **_seq);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-settings.c
@@ -0,0 +1,257 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-limits.h"
+#include "sieve-error.h"
+#include "sieve-address.h"
+#include "sieve-address-source.h"
+#include "sieve-settings.h"
+
+#include <ctype.h>
+
+/*
+ * Access to settings
+ */
+
+bool sieve_setting_get_uint_value
+(struct sieve_instance *svinst, const char *setting,
+	unsigned long long int *value_r)
+{
+	const char *str_value;
+
+	str_value = sieve_setting_get(svinst, setting);
+
+	if ( str_value == NULL || *str_value == '\0' )
+		return FALSE;
+
+	if ( str_to_ullong(str_value, value_r) < 0 ) {
+		sieve_sys_warning(svinst,
+			"invalid unsigned integer value for setting '%s': '%s'",
+			setting, str_value);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+bool sieve_setting_get_int_value
+(struct sieve_instance *svinst, const char *setting,
+	long long int *value_r)
+{
+	const char *str_value;
+
+	str_value = sieve_setting_get(svinst, setting);
+
+	if ( str_value == NULL || *str_value == '\0' )
+		return FALSE;
+
+	if ( str_to_llong(str_value, value_r) < 0 ) {
+		sieve_sys_warning(svinst,
+			"invalid integer value for setting '%s': '%s'",
+			setting, str_value);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+bool sieve_setting_get_size_value
+(struct sieve_instance *svinst, const char *setting,
+	size_t *value_r)
+{
+	const char *str_value;
+	uintmax_t value, multiply = 1;
+	const char *endp;
+
+	str_value = sieve_setting_get(svinst, setting);
+
+	if ( str_value == NULL || *str_value == '\0' )
+		return FALSE;
+
+	if ( str_parse_uintmax(str_value, &value, &endp) < 0 ) {
+		sieve_sys_warning(svinst,
+			"invalid size value for setting '%s': '%s'",
+			setting, str_value);
+		return FALSE;
+	}
+	switch (i_toupper(*endp)) {
+	case '\0': /* default */
+	case 'B': /* byte (useless) */
+		multiply = 1;
+		break;
+	case 'K': /* kilobyte */
+		multiply = 1024;
+		break;
+	case 'M': /* megabyte */
+		multiply = 1024*1024;
+		break;
+	case 'G': /* gigabyte */
+		multiply = 1024*1024*1024;
+		break;
+	case 'T': /* terabyte */
+		multiply = 1024ULL*1024*1024*1024;
+		break;
+	default:
+		sieve_sys_warning(svinst,
+			"invalid size value for setting '%s': '%s'",
+			setting, str_value);
+		return FALSE;
+	}
+
+	if ( value > SSIZE_T_MAX / multiply ) {
+		sieve_sys_warning(svinst,
+			"overflowing size value for setting '%s': '%s'",
+			setting, str_value);
+		return FALSE;
+	}
+
+	*value_r = (size_t) (value * multiply);
+	return TRUE;
+}
+
+bool sieve_setting_get_bool_value
+(struct sieve_instance *svinst, const char *setting,
+	bool *value_r)
+{
+	const char *str_value;
+
+	str_value = sieve_setting_get(svinst, setting);
+	if ( str_value == NULL )
+		return FALSE;
+
+	str_value = t_str_trim(str_value, "\t ");
+	if ( *str_value == '\0' )
+		return FALSE;
+
+ 	if ( strcasecmp(str_value, "yes" ) == 0) {
+        *value_r = TRUE;
+		return TRUE;
+	}
+
+ 	if ( strcasecmp(str_value, "no" ) == 0) {
+        *value_r = FALSE;
+		return TRUE;
+	}
+
+	sieve_sys_warning(svinst,
+		"invalid boolean value for setting '%s': '%s'",
+		setting, str_value);
+	return FALSE;
+}
+
+bool sieve_setting_get_duration_value
+(struct sieve_instance *svinst, const char *setting,
+	sieve_number_t *value_r)
+{
+	const char *str_value;
+	uintmax_t value, multiply = 1;
+	const char *endp;
+
+	str_value = sieve_setting_get(svinst, setting);
+	if ( str_value == NULL )
+		return FALSE;
+
+	str_value = t_str_trim(str_value, "\t ");
+	if ( *str_value == '\0' )
+		return FALSE;
+
+	if ( str_parse_uintmax(str_value, &value, &endp) < 0 ) {
+		sieve_sys_warning(svinst,
+			"invalid duration value for setting '%s': '%s'",
+			setting, str_value);
+		return FALSE;
+	}
+	switch (i_tolower(*endp)) {
+	case '\0': /* default */
+	case 's': /* seconds */
+		multiply = 1;
+		break;
+	case 'm': /* minutes */
+		multiply = 60;
+		break;
+	case 'h': /* hours */
+		multiply = 60*60;
+		break;
+	case 'd': /* days */
+		multiply = 24*60*60;
+		break;
+	default:
+		sieve_sys_warning(svinst,
+			"invalid duration value for setting '%s': '%s'",
+			setting, str_value);
+		return FALSE;
+	}
+
+	if ( value > SIEVE_MAX_NUMBER / multiply ) {
+		sieve_sys_warning(svinst,
+			"overflowing duration value for setting '%s': '%s'",
+			setting, str_value);
+		return FALSE;
+	}
+
+	*value_r = (unsigned int) (value * multiply);
+	return TRUE;
+}
+
+/*
+ * Main Sieve engine settings
+ */
+
+void sieve_settings_load
+(struct sieve_instance *svinst)
+{
+	const char *str_setting, *error;
+	unsigned long long int uint_setting;
+	size_t size_setting;
+	sieve_number_t period;
+
+	svinst->max_script_size = SIEVE_DEFAULT_MAX_SCRIPT_SIZE;
+	if ( sieve_setting_get_size_value
+		(svinst, "sieve_max_script_size", &size_setting) ) {
+		svinst->max_script_size = size_setting;
+	}
+
+	svinst->max_actions = SIEVE_DEFAULT_MAX_ACTIONS;
+	if ( sieve_setting_get_uint_value
+		(svinst, "sieve_max_actions", &uint_setting) ) {
+		svinst->max_actions = (unsigned int) uint_setting;
+	}
+
+	svinst->max_redirects = SIEVE_DEFAULT_MAX_REDIRECTS;
+	if ( sieve_setting_get_uint_value
+		(svinst, "sieve_max_redirects", &uint_setting) ) {
+		svinst->max_redirects = (unsigned int) uint_setting;
+	}
+
+	(void)sieve_address_source_parse_from_setting(svinst,
+		svinst->pool, "sieve_redirect_envelope_from",
+		&svinst->redirect_from);
+
+	svinst->redirect_duplicate_period = DEFAULT_REDIRECT_DUPLICATE_PERIOD;
+	if ( sieve_setting_get_duration_value
+		(svinst, "sieve_redirect_duplicate_period", &period) ) {
+		if (period > UINT_MAX)
+			svinst->redirect_duplicate_period = UINT_MAX;
+		else
+			svinst->redirect_duplicate_period = (unsigned int)period;
+	}
+
+	str_setting = sieve_setting_get(svinst, "sieve_user_email");
+	if ( str_setting != NULL && *str_setting != '\0' ) {
+		struct smtp_address *address;
+		if (smtp_address_parse_path(svinst->pool, str_setting,
+			SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL,
+			&address, &error) < 0) {
+			sieve_sys_warning(svinst,
+				"Invalid address value for setting "
+				"`sieve_user_email': %s", error);
+		} else {
+			svinst->user_email = address;
+		}
+	}
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-settings.h
@@ -0,0 +1,62 @@
+#ifndef SIEVE_SETTINGS_H
+#define SIEVE_SETTINGS_H
+
+#include "sieve-common.h"
+
+/*
+ * Access to settings
+ */
+
+static inline const char *sieve_setting_get
+(struct sieve_instance *svinst, const char *identifier)
+{
+	const struct sieve_callbacks *callbacks = svinst->callbacks;
+
+	if ( callbacks == NULL || callbacks->get_setting == NULL )
+		return NULL;
+
+	return callbacks->get_setting(svinst->context, identifier);
+}
+
+bool sieve_setting_get_uint_value
+	(struct sieve_instance *svinst, const char *setting,
+		unsigned long long int *value_r);
+bool sieve_setting_get_int_value
+	(struct sieve_instance *svinst, const char *setting,
+		long long int *value_r);
+bool sieve_setting_get_size_value
+	(struct sieve_instance *svinst, const char *setting,
+		size_t *value_r);
+bool sieve_setting_get_bool_value
+	(struct sieve_instance *svinst, const char *setting,
+		bool *value_r);
+bool sieve_setting_get_duration_value
+	(struct sieve_instance *svinst, const char *setting,
+		sieve_number_t *value_r);
+
+/*
+ * Main Sieve engine settings
+ */
+
+void sieve_settings_load
+	(struct sieve_instance *svinst);
+
+/*
+ * Home directory
+ */
+
+static inline const char *sieve_environment_get_homedir
+(struct sieve_instance *svinst)
+{
+	const struct sieve_callbacks *callbacks = svinst->callbacks;
+
+	if ( svinst->home_dir != NULL )
+		return svinst->home_dir;
+
+	if ( callbacks == NULL || callbacks->get_homedir == NULL )
+		return NULL;
+
+	return callbacks->get_homedir(svinst->context);
+}
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-smtp.c
@@ -0,0 +1,95 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+#include "lib.h"
+#include "smtp-address.h"
+
+#include "sieve-common.h"
+#include "sieve-smtp.h"
+
+struct sieve_smtp_context {
+	const struct sieve_script_env *senv;
+	void *handle;
+	
+	bool sent:1;
+};
+
+bool sieve_smtp_available
+(const struct sieve_script_env *senv)
+{
+	return ( senv->smtp_start != NULL && senv->smtp_add_rcpt != NULL &&
+		senv->smtp_send != NULL && senv->smtp_finish != NULL );
+}
+
+struct sieve_smtp_context *sieve_smtp_start
+(const struct sieve_script_env *senv,
+	const struct smtp_address *mail_from)
+{
+	struct sieve_smtp_context *sctx;
+	void *handle;
+
+	if ( !sieve_smtp_available(senv) )
+		return NULL;
+
+	handle = senv->smtp_start(senv, mail_from);
+	i_assert( handle != NULL );
+	
+	sctx = i_new(struct sieve_smtp_context, 1);
+	sctx->senv = senv;
+	sctx->handle = handle;
+
+	return sctx;
+}
+
+void sieve_smtp_add_rcpt
+(struct sieve_smtp_context *sctx,
+	const struct smtp_address *rcpt_to)
+{
+	i_assert(!sctx->sent);
+	sctx->senv->smtp_add_rcpt(sctx->senv, sctx->handle, rcpt_to);
+}
+
+struct ostream *sieve_smtp_send
+(struct sieve_smtp_context *sctx)
+{
+	i_assert(!sctx->sent);
+	sctx->sent = TRUE;
+
+	return sctx->senv->smtp_send(sctx->senv, sctx->handle);
+}
+
+struct sieve_smtp_context *sieve_smtp_start_single
+(const struct sieve_script_env *senv,
+	const struct smtp_address *rcpt_to,
+		const struct smtp_address *mail_from,
+	struct ostream **output_r)
+{
+	struct sieve_smtp_context *sctx;
+
+	sctx = sieve_smtp_start(senv, mail_from);
+	sieve_smtp_add_rcpt(sctx, rcpt_to);
+	*output_r = sieve_smtp_send(sctx);
+
+	return sctx;
+}
+
+void sieve_smtp_abort
+(struct sieve_smtp_context *sctx)
+{
+	const struct sieve_script_env *senv = sctx->senv;
+	void *handle = sctx->handle;
+
+	i_free(sctx);
+	i_assert(senv->smtp_abort != NULL);
+	senv->smtp_abort(senv, handle);
+}
+
+int sieve_smtp_finish
+(struct sieve_smtp_context *sctx, const char **error_r)
+{
+	const struct sieve_script_env *senv = sctx->senv;
+	void *handle = sctx->handle;
+
+	i_free(sctx);
+	return senv->smtp_finish(senv, handle, error_r);
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-smtp.h
@@ -0,0 +1,31 @@
+#ifndef SIEVE_SMTP_H
+#define SIEVE_SMTP_H
+
+#include "sieve-common.h"
+
+bool sieve_smtp_available
+	(const struct sieve_script_env *senv);
+
+struct sieve_smtp_context;
+
+struct sieve_smtp_context *sieve_smtp_start
+	(const struct sieve_script_env *senv,
+		const struct smtp_address *mail_from);
+void sieve_smtp_add_rcpt
+	(struct sieve_smtp_context *sctx,
+		const struct smtp_address *rcpt_to);
+struct ostream *sieve_smtp_send
+	(struct sieve_smtp_context *sctx);
+
+struct sieve_smtp_context *sieve_smtp_start_single
+	(const struct sieve_script_env *senv,
+		const struct smtp_address *rcpt_to,
+		const struct smtp_address *mail_from,
+		struct ostream **output_r);
+
+void sieve_smtp_abort
+	(struct sieve_smtp_context *sctx);
+int sieve_smtp_finish
+	(struct sieve_smtp_context *sctx, const char **error_r);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-storage-private.h
@@ -0,0 +1,276 @@
+#ifndef SIEVE_STORAGE_PRIVATE_H
+#define SIEVE_STORAGE_PRIVATE_H
+
+#include "sieve.h"
+#include "sieve-error-private.h"
+
+#include "sieve-storage.h"
+
+#define MAILBOX_ATTRIBUTE_PREFIX_SIEVE \
+	MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER"sieve/"
+#define MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES \
+	MAILBOX_ATTRIBUTE_PREFIX_SIEVE"files/"
+#define MAILBOX_ATTRIBUTE_SIEVE_DEFAULT \
+	MAILBOX_ATTRIBUTE_PREFIX_SIEVE"default"
+
+#define MAILBOX_ATTRIBUTE_SIEVE_DEFAULT_LINK 'L'
+#define MAILBOX_ATTRIBUTE_SIEVE_DEFAULT_SCRIPT 'S'
+
+struct sieve_storage;
+
+ARRAY_DEFINE_TYPE(sieve_storage_class, const struct sieve_storage *);
+
+struct sieve_storage_vfuncs {
+	struct sieve_storage *(*alloc)(void);
+	void (*destroy)(struct sieve_storage *storage);
+	int (*init)
+		(struct sieve_storage *storage, const char *const *options,
+			enum sieve_error *error_r);
+
+	int (*get_last_change)
+		(struct sieve_storage *storage, time_t *last_change_r);
+	void (*set_modified)
+		(struct sieve_storage *storage, time_t mtime);
+
+	int (*is_singular)(struct sieve_storage *storage);
+
+	/* script access */
+	struct sieve_script *(*get_script)
+		(struct sieve_storage *storage, const char *name);
+
+	/* script sequence */
+	struct sieve_script_sequence *(*get_script_sequence)
+		(struct sieve_storage *storage, enum sieve_error *error_r);
+	struct sieve_script *(*script_sequence_next)
+		(struct sieve_script_sequence *seq, enum sieve_error *error_r);
+	void (*script_sequence_destroy)(struct sieve_script_sequence *seq);
+
+	/* active script */
+	int (*active_script_get_name)
+		(struct sieve_storage *storage, const char **name_r);
+	struct sieve_script *(*active_script_open)
+		(struct sieve_storage *storage);
+	int (*deactivate)
+		(struct sieve_storage *storage);
+	int (*active_script_get_last_change)
+		(struct sieve_storage *storage, time_t *last_change_r);
+
+	/* listing scripts */
+	struct sieve_storage_list_context *(*list_init)
+		(struct sieve_storage *storage);
+	const char *(*list_next)
+		(struct sieve_storage_list_context *lctx, bool *active_r);
+	int (*list_deinit)
+		(struct sieve_storage_list_context *lctx);
+
+	/* saving scripts */
+	// FIXME: simplify this API; reduce this mostly to a single save function
+	struct sieve_storage_save_context *(*save_init)
+		(struct sieve_storage *storage, const char *scriptname,
+			struct istream *input);
+	int (*save_continue)(struct sieve_storage_save_context *sctx);
+	int (*save_finish)(struct sieve_storage_save_context *sctx);
+	struct sieve_script *(*save_get_tempscript)
+		(struct sieve_storage_save_context *sctx);
+	void (*save_cancel)(struct sieve_storage_save_context *sctx);
+	int (*save_commit)(struct sieve_storage_save_context *sctx);
+	int (*save_as)
+		(struct sieve_storage *storage, struct istream *input,
+			const char *name);
+	int (*save_as_active)
+		(struct sieve_storage *storage, struct istream *input,
+			time_t mtime);
+
+	/* checking quota */
+	int (*quota_havespace)
+		(struct sieve_storage *storage, const char *scriptname,
+			size_t size, enum sieve_storage_quota *quota_r, uint64_t *limit_r);
+};
+
+struct sieve_storage {
+	pool_t pool;
+	unsigned int refcount;
+	struct sieve_instance *svinst;
+
+	const char *driver_name;
+	unsigned int version;
+
+	const struct sieve_storage *storage_class;
+	struct sieve_storage_vfuncs v;
+
+	uint64_t max_scripts;
+	uint64_t max_storage;
+
+	char *error;
+	enum sieve_error error_code;
+
+	const char *data;
+	const char *location;
+	const char *script_name;
+	const char *bin_dir;
+
+	const char *default_name;
+	const char *default_location;
+	struct sieve_storage *default_for;
+
+	struct mail_namespace *sync_inbox_ns;
+
+	enum sieve_storage_flags flags;
+
+	/* this is the main personal storage */
+	bool main_storage:1;
+	bool allows_synchronization:1;
+	bool is_default:1;
+};
+
+struct sieve_storage *sieve_storage_alloc
+	(struct sieve_instance *svinst, 
+		const struct sieve_storage *storage_class, const char *data,
+		enum sieve_storage_flags flags, bool main);
+
+int sieve_storage_setup_bindir
+	(struct sieve_storage *storage, mode_t mode);
+
+/*
+ * Active script
+ */
+
+int sieve_storage_active_script_is_default
+	(struct sieve_storage *storage);
+
+/*
+ * Listing scripts
+ */
+
+struct sieve_storage_list_context {
+	struct sieve_storage *storage;
+
+	bool seen_active:1; // Just present for assertions
+	bool seen_default:1;
+};
+
+/*
+ * Script sequence
+ */
+
+struct sieve_script_sequence {
+	struct sieve_storage *storage;
+};
+
+/*
+ * Saving scripts
+ */
+
+struct sieve_storage_save_context {
+	pool_t pool;
+	struct sieve_storage *storage;
+
+	const char *scriptname, *active_scriptname;
+	struct sieve_script *scriptobject;
+
+	struct istream *input;
+
+	time_t mtime;
+
+	bool failed:1;
+	bool finished:1;
+};
+
+/*
+ * Storage class
+ */
+
+struct sieve_storage_class_registry;
+
+void sieve_storages_init(struct sieve_instance *svinst);
+void sieve_storages_deinit(struct sieve_instance *svinst);
+
+void sieve_storage_class_register
+	(struct sieve_instance *svinst,
+		const struct sieve_storage *storage_class);
+void sieve_storage_class_unregister
+	(struct sieve_instance *svinst,
+		const struct sieve_storage *storage_class);
+const struct sieve_storage *sieve_storage_find_class
+	(struct sieve_instance *svinst, const char *name);
+
+/*
+ * Built-in storage drivers
+ */
+
+/* data (currently only for internal use) */
+
+#define SIEVE_DATA_STORAGE_DRIVER_NAME "data"
+
+extern const struct sieve_storage sieve_data_storage;
+
+/* file */
+
+#define SIEVE_FILE_STORAGE_DRIVER_NAME "file"
+
+extern const struct sieve_storage sieve_file_storage;
+
+struct sieve_storage *sieve_file_storage_init_legacy
+	(struct sieve_instance *svinst, const char *active_path,
+		const char *storage_path, enum sieve_storage_flags flags,
+		enum sieve_error *error_r) ATTR_NULL(6);
+
+/* dict */
+
+#define SIEVE_DICT_STORAGE_DRIVER_NAME "dict"
+
+extern const struct sieve_storage sieve_dict_storage;
+
+/* ldap */
+
+#define SIEVE_LDAP_STORAGE_DRIVER_NAME "ldap"
+
+extern const struct sieve_storage sieve_ldap_storage;
+
+/*
+ * Error handling
+ */
+
+void sieve_storage_set_internal_error
+	(struct sieve_storage *storage);
+
+void sieve_storage_copy_error
+	(struct sieve_storage *storage, const struct sieve_storage *source);
+
+void sieve_storage_sys_error
+	(struct sieve_storage *storage, const char *fmt, ...)
+	ATTR_FORMAT(2, 3);
+void sieve_storage_sys_warning
+	(struct sieve_storage *storage, const char *fmt, ...)
+	ATTR_FORMAT(2, 3);
+void sieve_storage_sys_info
+	(struct sieve_storage *storage, const char *fmt, ...)
+	ATTR_FORMAT(2, 3);
+void sieve_storage_sys_debug
+	(struct sieve_storage *storage, const char *fmt, ...)
+	ATTR_FORMAT(2, 3);
+
+/*
+ * Synchronization
+ */
+
+int sieve_storage_sync_init
+	(struct sieve_storage *storage, struct mail_user *user);
+void sieve_storage_sync_deinit
+	(struct sieve_storage *storage);
+
+int sieve_storage_sync_script_save
+	(struct sieve_storage *storage, const char *name);
+int sieve_storage_sync_script_rename
+	(struct sieve_storage *storage, const char *oldname,
+		const char *newname);
+int sieve_storage_sync_script_delete
+	(struct sieve_storage *storage, const char *name);
+
+int sieve_storage_sync_script_activate
+(struct sieve_storage *storage);
+int sieve_storage_sync_deactivate
+(struct sieve_storage *storage);
+
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-storage-sync.c
@@ -0,0 +1,195 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "str-sanitize.h"
+#include "home-expand.h"
+#include "eacces-error.h"
+#include "mkdir-parents.h"
+#include "ioloop.h"
+#include "mail-storage-private.h"
+
+#include "sieve-common.h"
+#include "sieve-settings.h"
+#include "sieve-error-private.h"
+
+#include "sieve-script-private.h"
+#include "sieve-storage-private.h"
+
+/*
+ * Synchronization
+ */
+
+int sieve_storage_sync_init
+(struct sieve_storage *storage, struct mail_user *user)
+{
+	enum sieve_storage_flags sflags = storage->flags;
+
+	if ( (sflags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0 &&
+		(sflags & SIEVE_STORAGE_FLAG_READWRITE) == 0 )
+		return 0;
+
+	if ( !storage->allows_synchronization ) {
+		if ( (sflags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) != 0 )
+			return -1;
+		return 0;
+	}
+
+	sieve_storage_sys_debug(storage, "sync: "
+		"Synchronization active");
+
+	storage->sync_inbox_ns = mail_namespace_find_inbox(user->namespaces);
+	return 0;
+}
+
+void sieve_storage_sync_deinit
+(struct sieve_storage *storage ATTR_UNUSED)
+{
+	/* nothing */
+}
+
+/*
+ * Sync attributes
+ */
+
+static int sieve_storage_sync_transaction_begin
+(struct sieve_storage *storage, struct mailbox_transaction_context **trans_r)
+{
+	enum mailbox_flags mflags = MAILBOX_FLAG_IGNORE_ACLS;
+	struct mail_namespace *ns = storage->sync_inbox_ns;
+	struct mailbox *inbox;
+	enum mail_error error;
+
+	if (ns == NULL)
+		return 0;
+
+	inbox = mailbox_alloc(ns->list, "INBOX", mflags);
+	if (mailbox_open(inbox) < 0) {
+		sieve_storage_sys_warning(storage, "sync: "
+			"Failed to open user INBOX for attribute modifications: %s",
+			mailbox_get_last_error(inbox, &error));
+		mailbox_free(&inbox);
+		return -1;
+	}
+
+	*trans_r = mailbox_transaction_begin(inbox,
+					     MAILBOX_TRANSACTION_FLAG_EXTERNAL,
+					     __func__);
+	return 1;
+}
+
+static int sieve_storage_sync_transaction_finish
+(struct sieve_storage *storage, struct mailbox_transaction_context **trans)
+{
+	struct mailbox *inbox;
+	int ret;
+
+	inbox = mailbox_transaction_get_mailbox(*trans);
+
+	if ((ret=mailbox_transaction_commit(trans)) < 0) {
+		enum mail_error error;
+		
+		sieve_storage_sys_warning(storage, "sync: "
+			"Failed to update INBOX attributes: %s",
+			mail_storage_get_last_error(mailbox_get_storage(inbox), &error));
+	}
+
+	mailbox_free(&inbox);
+	return ret;
+}
+
+int sieve_storage_sync_script_save
+(struct sieve_storage *storage, const char *name)
+{
+	struct mailbox_transaction_context *trans;
+	const char *key;
+	int ret;
+
+	if ((ret=sieve_storage_sync_transaction_begin
+		(storage, &trans)) <= 0)
+		return ret;
+
+	key = t_strconcat
+		(MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES, name, NULL);
+
+	mail_index_attribute_set(trans->itrans, TRUE, key, ioloop_time, 0);
+
+	return sieve_storage_sync_transaction_finish(storage, &trans);
+}
+
+int sieve_storage_sync_script_rename
+(struct sieve_storage *storage, const char *oldname,
+	const char *newname)
+{
+	struct mailbox_transaction_context *trans;
+	const char *oldkey, *newkey;
+	int ret;
+
+	if ((ret=sieve_storage_sync_transaction_begin
+		(storage, &trans)) <= 0)
+		return ret;
+
+	oldkey = t_strconcat
+		(MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES, oldname, NULL);
+	newkey = t_strconcat
+		(MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES, newname, NULL);
+
+	mail_index_attribute_unset(trans->itrans, TRUE, oldkey, ioloop_time);
+	mail_index_attribute_set(trans->itrans, TRUE, newkey, ioloop_time, 0);
+
+	return sieve_storage_sync_transaction_finish(storage, &trans);
+}
+
+int sieve_storage_sync_script_delete
+(struct sieve_storage *storage, const char *name)
+{
+	struct mailbox_transaction_context *trans;
+	const char *key;
+	int ret;
+
+	if ((ret=sieve_storage_sync_transaction_begin
+		(storage, &trans)) <= 0)
+		return ret;
+
+	key = t_strconcat
+		(MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES, name, NULL);
+
+	mail_index_attribute_unset(trans->itrans, TRUE, key, ioloop_time);
+
+	return sieve_storage_sync_transaction_finish(storage, &trans);
+}
+
+int sieve_storage_sync_script_activate
+(struct sieve_storage *storage)
+{
+	struct mailbox_transaction_context *trans;
+	int ret;
+
+	if ((ret=sieve_storage_sync_transaction_begin
+		(storage, &trans)) <= 0)
+		return ret;
+
+	mail_index_attribute_set(trans->itrans,
+		TRUE, MAILBOX_ATTRIBUTE_SIEVE_DEFAULT, ioloop_time, 0);
+
+	return sieve_storage_sync_transaction_finish(storage, &trans);
+}
+
+int sieve_storage_sync_deactivate
+(struct sieve_storage *storage)
+{
+	struct mailbox_transaction_context *trans;
+	int ret;
+
+	if ((ret=sieve_storage_sync_transaction_begin
+		(storage, &trans)) <= 0)
+		return ret;
+	
+	mail_index_attribute_unset(trans->itrans,
+		TRUE, MAILBOX_ATTRIBUTE_SIEVE_DEFAULT, ioloop_time);
+
+	return sieve_storage_sync_transaction_finish(storage, &trans);
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-storage.c
@@ -0,0 +1,1458 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "str-sanitize.h"
+#include "home-expand.h"
+#include "eacces-error.h"
+#include "mkdir-parents.h"
+#include "ioloop.h"
+
+#include "sieve-common.h"
+#include "sieve-settings.h"
+#include "sieve-error-private.h"
+
+#include "sieve-script-private.h"
+#include "sieve-storage-private.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <time.h>
+#include <utime.h>
+
+#define CRITICAL_MSG \
+  "Internal error occurred. Refer to server log for more information."
+#define CRITICAL_MSG_STAMP CRITICAL_MSG " [%Y-%m-%d %H:%M:%S]"
+
+/*
+ * Storage class
+ */
+
+struct sieve_storage_class_registry {
+	ARRAY_TYPE(sieve_storage_class) storage_classes;
+};
+
+void sieve_storages_init(struct sieve_instance *svinst)
+{
+	svinst->storage_reg =
+		p_new(svinst->pool, struct sieve_storage_class_registry, 1);
+	p_array_init(&svinst->storage_reg->storage_classes, svinst->pool, 8);
+
+	sieve_storage_class_register(svinst, &sieve_file_storage);
+	sieve_storage_class_register(svinst, &sieve_dict_storage);
+	sieve_storage_class_register(svinst, &sieve_ldap_storage);
+}
+
+void sieve_storages_deinit(struct sieve_instance *svinst ATTR_UNUSED)
+{
+	/* nothing yet */
+}
+
+void sieve_storage_class_register
+(struct sieve_instance *svinst, const struct sieve_storage *storage_class)
+{
+	struct sieve_storage_class_registry *reg = svinst->storage_reg;
+	const struct sieve_storage *old_class;
+
+	old_class = sieve_storage_find_class
+		(svinst, storage_class->driver_name);
+	if (old_class != NULL) {
+		if (old_class->v.alloc == NULL) {
+			/* replacing a "support not compiled in" storage class */
+			sieve_storage_class_unregister(svinst, old_class);
+		} else {
+			i_panic("sieve_storage_class_register(%s): Already registered",
+				storage_class->driver_name);
+		}
+	}
+
+	array_append(&reg->storage_classes, &storage_class, 1);
+}
+
+void sieve_storage_class_unregister
+(struct sieve_instance *svinst, const struct sieve_storage *storage_class)
+{
+	struct sieve_storage_class_registry *reg = svinst->storage_reg;
+	const struct sieve_storage *const *classes;
+	unsigned int i, count;
+
+	classes = array_get(&reg->storage_classes, &count);
+	for ( i = 0; i < count; i++ ) {
+		if ( classes[i] == storage_class ) {
+			array_delete(&reg->storage_classes, i, 1);
+			break;
+		}
+	}
+}
+
+const struct sieve_storage *sieve_storage_find_class
+(struct sieve_instance *svinst, const char *name)
+{
+	struct sieve_storage_class_registry *reg = svinst->storage_reg;
+	const struct sieve_storage *const *classes;
+	unsigned int i, count;
+
+	i_assert(name != NULL);
+
+	classes = array_get(&reg->storage_classes, &count);
+	for ( i = 0; i < count; i++ ) {
+		if ( strcasecmp(classes[i]->driver_name, name) == 0 )
+			return classes[i];
+	}
+	return NULL;
+}
+
+/*
+ * Storage instance
+ */
+
+static const char *split_next_arg(const char *const **_args)
+{
+	const char *const *args = *_args;
+	const char *str = args[0];
+
+	/* join arguments for escaped ";" separator */
+
+	args++;
+	while (*args != NULL && **args == '\0') {
+		args++;
+		if (*args == NULL) {
+			/* string ends with ";", just ignore it. */
+			break;
+		}
+		str = t_strconcat(str, ";", *args, NULL);
+		args++;
+	}
+	*_args = args;
+	return str;
+}
+
+static int sieve_storage_driver_parse
+(struct sieve_instance *svinst, const char **data,
+	const struct sieve_storage **driver_r)
+{
+	const struct sieve_storage *storage_class = NULL;
+	const char *p;
+
+	p = strchr(*data, ':');
+	if ( p == NULL )
+		return 0;
+		
+	/* Lookup storage driver */
+	T_BEGIN {
+		const char *driver;
+
+		driver = t_strdup_until(*data, p);
+		*data = p+1;
+
+		storage_class = sieve_storage_find_class(svinst, driver);
+		if ( storage_class == NULL ) {
+			sieve_sys_error(svinst,
+				"Unknown storage driver module `%s'",
+				driver);
+		} else if ( storage_class->v.alloc == NULL ) {
+			sieve_sys_error(svinst,
+				"Support not compiled in for storage driver `%s'",
+				driver);
+			storage_class = NULL;
+		}
+	} T_END;
+
+	*driver_r = storage_class;
+	return ( storage_class == NULL ? -1 : 1 );
+}
+
+static int sieve_storage_data_parse
+(struct sieve_storage *storage, const char *data, const char **location_r,
+	const char *const **options_r)
+{
+	ARRAY_TYPE(const_string) options;
+	const char *const *tmp;
+
+	if (*data == '\0') {
+		*options_r = NULL;
+		*location_r = data;
+		return 0;
+	}
+
+	/* <location> */
+	tmp = t_strsplit(data, ";");
+	*location_r = split_next_arg(&tmp);
+
+	if ( options_r != NULL ) {
+		t_array_init(&options, 8);
+
+		/* [<option> *(';' <option>)] */
+		while (*tmp != NULL) {
+			const char *option = split_next_arg(&tmp);
+
+			if ( strncasecmp(option, "name=", 5) == 0 ) {
+				if ( option[5] == '\0' ) {
+					sieve_storage_sys_error(storage,
+						"Failed to parse storage location: "
+						"Empty name not allowed");
+					return -1;
+				}
+
+				if ( storage->script_name == NULL ) {
+					if ( !sieve_script_name_is_valid(option+5) ) {
+						sieve_storage_sys_error(storage,
+							"Failed to parse storage location: "
+							"Invalid script name `%s'.",
+							str_sanitize(option+5, 80));
+						return -1;
+					}
+					storage->script_name = p_strdup(storage->pool, option+5);
+				}
+
+			} else if ( strncasecmp(option, "bindir=", 7) == 0 ) {
+				const char *bin_dir = option+7;
+
+				if ( bin_dir[0] == '\0' ) {
+					sieve_storage_sys_error(storage,
+						"Failed to parse storage location: "
+						"Empty bindir not allowed");
+					return -1;
+				}
+
+				if ( bin_dir[0] == '~' ) {
+					/* home-relative path. change to absolute. */
+					const char *home = sieve_environment_get_homedir(storage->svinst);
+
+					if ( home != NULL ) {
+						bin_dir = home_expand_tilde(bin_dir, home);
+					} else if ( bin_dir[1] == '/' || bin_dir[1] == '\0' ) {
+						sieve_storage_sys_error(storage,
+							"Failed to parse storage location: "
+							"bindir is relative to home directory (~/), "
+							"but home directory cannot be determined");
+						return -1;
+					}
+				}
+
+				storage->bin_dir = p_strdup(storage->pool, bin_dir);
+			} else {
+				array_append(&options, &option, 1);
+			}
+		}
+
+		(void)array_append_space(&options);
+		*options_r = array_idx(&options, 0);
+	}
+
+	return 0;
+}
+
+struct sieve_storage *sieve_storage_alloc
+(struct sieve_instance *svinst, 
+	const struct sieve_storage *storage_class, const char *data,
+	enum sieve_storage_flags flags, bool main)
+{
+	struct sieve_storage *storage;
+
+	i_assert(storage_class->v.alloc != NULL);
+	storage = storage_class->v.alloc();
+
+	storage->storage_class = storage_class;
+	storage->refcount = 1;
+	storage->svinst = svinst;
+	storage->flags = flags;
+	storage->data = p_strdup_empty(storage->pool, data);
+	storage->main_storage = main;
+
+	return storage;
+}
+
+static struct sieve_storage *sieve_storage_init
+(struct sieve_instance *svinst, 
+	const struct sieve_storage *storage_class, const char *data,
+	enum sieve_storage_flags flags, bool main, enum sieve_error *error_r)
+{
+	struct sieve_storage *storage;
+	const char *const *options;
+	const char *location;
+	enum sieve_error error;
+
+	if ( error_r != NULL )
+		*error_r = SIEVE_ERROR_NONE;
+	else
+		error_r = &error;
+
+	i_assert( storage_class->v.init != NULL );
+
+	if ( (flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) != 0 &&
+		!storage_class->allows_synchronization ) {
+		sieve_sys_debug(svinst, "%s storage: "
+			"Storage does not support synchronization",
+			storage_class->driver_name);
+		*error_r = SIEVE_ERROR_NOT_POSSIBLE;
+		return NULL;
+	}
+
+	if ((flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 &&
+		storage_class->v.save_init == NULL) {
+		sieve_sys_error(svinst, "%s storage: "
+			"Storage does not support write access",
+			storage_class->driver_name);
+		*error_r = SIEVE_ERROR_TEMP_FAILURE;
+		return NULL;
+	}
+
+	T_BEGIN {	
+		storage = sieve_storage_alloc
+			(svinst, storage_class, data, flags, main);
+
+		if ( sieve_storage_data_parse
+			(storage, data, &location, &options) < 0 ) {
+			*error_r = SIEVE_ERROR_TEMP_FAILURE;
+			sieve_storage_unref(&storage);
+			storage = NULL;
+		} else {
+			storage->location = p_strdup(storage->pool, location);
+
+			if ( storage_class->v.init
+					(storage, options, error_r) < 0 ) {
+				sieve_storage_unref(&storage);
+				storage = NULL;
+			}
+		}
+	} T_END;
+
+	return storage;
+}
+
+struct sieve_storage *sieve_storage_create
+(struct sieve_instance *svinst, const char *location,
+	enum sieve_storage_flags flags, enum sieve_error *error_r)
+{
+	const struct sieve_storage *storage_class;
+	enum sieve_error error;
+	const char *data;
+	int ret;
+
+	/* Dont use this function for creating a synchronizing storage */
+	i_assert( (flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0);
+
+	if ( error_r != NULL )
+		*error_r = SIEVE_ERROR_NONE;
+	else
+		error_r = &error;
+
+	data = location;
+	if ( (ret=sieve_storage_driver_parse
+		(svinst, &data, &storage_class)) < 0) {
+		*error_r = SIEVE_ERROR_TEMP_FAILURE;
+		return NULL;
+	}
+	
+	if ( ret == 0 )
+		storage_class = &sieve_file_storage;
+
+	return sieve_storage_init
+		(svinst, storage_class, data, flags, FALSE, error_r);
+}
+
+static struct sieve_storage *sieve_storage_do_create_main
+(struct sieve_instance *svinst, struct mail_user *user,
+	enum sieve_storage_flags flags, enum sieve_error *error_r)
+{
+	bool debug = svinst->debug;
+	struct sieve_storage *storage = NULL;
+	const struct sieve_storage
+		*sieve_class = NULL,
+		*sieve_dir_class = NULL;
+	const char *set_sieve, *set_sieve_dir;
+	const char *data, *storage_path;
+	unsigned long long int uint_setting;
+	size_t size_setting;
+	int ret;	
+
+	/* Sieve storage location */
+
+	set_sieve = sieve_setting_get(svinst, "sieve");
+
+	if ( set_sieve != NULL ) {
+		if ( *set_sieve == '\0' ) {
+			/* disabled */
+			if ( debug ) {
+				sieve_sys_debug(svinst,	"storage: "
+					"Personal storage is disabled (sieve=\"\")");
+			}
+			*error_r = SIEVE_ERROR_NOT_FOUND;
+			return NULL;
+		}
+
+		data = set_sieve;
+		if ( (ret=sieve_storage_driver_parse
+			(svinst, &data, &sieve_class)) < 0 ) {
+			*error_r = SIEVE_ERROR_TEMP_FAILURE;
+			return NULL;
+		}
+
+		if ( ret > 0 ) {
+			/* The normal case: explicit driver name */
+			storage = sieve_storage_init
+				(svinst, sieve_class, data, flags, TRUE, error_r);
+			if ( storage == NULL )
+				return NULL;
+		}
+
+		/* No driver name */
+	}
+
+	if ( storage == NULL ) {
+		/* Script storage directory configuration (deprecated) */
+
+		set_sieve_dir = sieve_setting_get(svinst, "sieve_dir");
+		if ( set_sieve_dir == NULL )
+			set_sieve_dir = sieve_setting_get(svinst, "sieve_storage");
+
+		if ( set_sieve_dir == NULL || *set_sieve_dir == '\0' ) {
+			storage_path = "";
+		} else {
+			const char *p;
+
+			/* Parse and check driver */
+			storage_path = set_sieve_dir;
+			if ( (ret=sieve_storage_driver_parse
+				(svinst, &storage_path, &sieve_dir_class)) < 0) {
+				*error_r = SIEVE_ERROR_TEMP_FAILURE;
+				return NULL;
+			}
+
+			if ( ret > 0 && sieve_dir_class != &sieve_file_storage ) {
+				sieve_sys_error(svinst, "storage: "
+					"Cannot use deprecated sieve_dir= setting "
+					"with `%s' driver for main Sieve storage",
+					sieve_dir_class->driver_name);
+			}
+
+			/* Ignore any options */
+			p = strchr(storage_path, ';');
+			if ( p != NULL )
+				storage_path = t_strdup_until(storage_path, p);
+		}
+
+		storage = sieve_file_storage_init_legacy
+			(svinst, set_sieve, storage_path, flags, error_r);
+	}
+
+	if ( storage == NULL )
+		return NULL;
+
+	(void)sieve_storage_sync_init(storage, user);
+
+	/* Get quota settings if storage driver provides none */
+
+	if ( storage->max_storage == 0 &&
+		sieve_setting_get_size_value
+			(svinst, "sieve_quota_max_storage", &size_setting) ) {
+		storage->max_storage = size_setting;
+	}
+
+	if ( storage->max_scripts == 0 &&
+		sieve_setting_get_uint_value
+			(svinst, "sieve_quota_max_scripts", &uint_setting) ) {
+		storage->max_scripts = uint_setting;
+	}
+
+	if ( debug ) {
+		if ( storage->max_storage > 0 ) {
+			sieve_storage_sys_debug(storage, "quota: "
+				"Storage limit: %llu bytes",
+				(unsigned long long int) storage->max_storage);
+		}
+		if ( storage->max_scripts > 0 ) {
+			sieve_storage_sys_debug(storage, "quota: "
+				"Script count limit: %llu scripts",
+				(unsigned long long int) storage->max_scripts);
+		}
+	}
+	return storage;
+}
+
+struct sieve_storage *sieve_storage_create_main
+(struct sieve_instance *svinst, struct mail_user *user,
+	enum sieve_storage_flags flags, enum sieve_error *error_r)
+{
+	struct sieve_storage *storage;
+	const char *set_enabled, *set_default, *set_default_name;
+	bool debug = svinst->debug;
+	enum sieve_error error;
+
+	if ( error_r != NULL )
+		*error_r = SIEVE_ERROR_NONE;
+	else
+		error_r = &error;
+
+	/* Check whether Sieve is disabled for this user */
+	if ( (set_enabled=sieve_setting_get
+		(svinst, "sieve_enabled")) != NULL &&
+		strcasecmp(set_enabled, "no") == 0) {
+		if ( debug ) {
+			sieve_sys_debug(svinst,
+				"Sieve is disabled for this user");
+		}
+		*error_r = SIEVE_ERROR_NOT_POSSIBLE;
+		return NULL;
+	}
+
+	/* Determine location for default script */
+	set_default =
+		 sieve_setting_get(svinst, "sieve_default");
+	if ( set_default == NULL ) {
+		/* For backwards compatibility */
+		set_default =
+			 sieve_setting_get(svinst, "sieve_global_path");
+	}
+
+	/* Attempt to locate user's main storage */
+	storage = sieve_storage_do_create_main(svinst, user, flags, error_r);
+	if ( storage != NULL ) {
+		/* Success; record default script location for later use */
+		storage->default_location =
+			p_strdup_empty(storage->pool, set_default);
+
+		set_default_name =
+			 sieve_setting_get(svinst, "sieve_default_name");
+		if ( set_default_name != NULL && *set_default_name != '\0' &&
+			!sieve_script_name_is_valid(set_default_name) ) {
+			sieve_storage_sys_error(storage,
+				"Invalid script name `%s' for `sieve_default_name' setting.",
+				str_sanitize(set_default_name, 80));
+			set_default_name = NULL;
+		}
+		storage->default_name =
+			p_strdup_empty(storage->pool, set_default_name);
+
+		if (storage->default_location != NULL &&
+			storage->default_name != NULL) {
+			sieve_storage_sys_debug(storage,
+				"Default script at `%s' is visible by name `%s'",
+				storage->default_location, storage->default_name);
+		}
+	} else if ( *error_r != SIEVE_ERROR_TEMP_FAILURE &&
+		(flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0 &&
+		(flags & SIEVE_STORAGE_FLAG_READWRITE) == 0 ) {
+
+		/* Failed; try using default script location
+		   (not for temporary failures, read/write access, or dsync) */
+		if ( set_default == NULL ) {
+			if ( debug ) {
+				sieve_sys_debug(svinst, "storage: "
+					"No default script location configured");
+			}
+		} else {
+			if ( debug ) {
+				sieve_sys_debug(svinst, "storage: "
+					"Trying default script location `%s'",
+					set_default);
+			}
+
+			storage = sieve_storage_create
+				(svinst, set_default, 0, error_r);
+			if ( storage == NULL ) {
+				switch ( *error_r ) {
+				case SIEVE_ERROR_NOT_FOUND:
+					if ( debug ) {
+						sieve_sys_debug(svinst, "storage: "
+							"Default script location `%s' not found",
+							set_default);
+					}
+					break;
+				case SIEVE_ERROR_TEMP_FAILURE:
+					sieve_sys_error(svinst, "storage: "
+						"Failed to access default script location `%s' "
+						"(temporary failure)",
+						set_default);
+					break;
+				default:
+					sieve_sys_error(svinst, "storage: "
+						"Failed to access default script location `%s'",
+						set_default);
+					break;
+				}
+			}
+		}
+		if (storage != NULL)
+			storage->is_default = TRUE;
+	}
+	return storage;
+}
+
+void sieve_storage_ref(struct sieve_storage *storage)
+{
+	storage->refcount++;
+}
+
+void sieve_storage_unref(struct sieve_storage **_storage)
+{
+	struct sieve_storage *storage = *_storage;
+
+	i_assert(storage->refcount > 0);
+
+	if (--storage->refcount != 0)
+		return;
+
+	if ( storage->default_for != NULL ) {
+		i_assert(storage->is_default);
+		sieve_storage_unref(&storage->default_for);
+	}
+
+	sieve_storage_sync_deinit(storage);
+
+	if ( storage->v.destroy != NULL )
+		storage->v.destroy(storage);
+
+	i_free(storage->error);
+	pool_unref(&storage->pool);
+	*_storage = NULL;
+}
+
+int sieve_storage_setup_bindir
+(struct sieve_storage *storage, mode_t mode)
+{
+	struct sieve_instance *svinst = storage->svinst;
+	const char *bin_dir = storage->bin_dir;
+	struct stat st;
+
+	if ( bin_dir == NULL )
+		return -1;
+
+	if ( stat(bin_dir, &st) == 0 )
+		return 0;
+
+	if ( errno == EACCES ) {
+		sieve_storage_sys_error(storage,
+			"Failed to setup directory for binaries: "
+			"%s",	eacces_error_get("stat", bin_dir));
+		return -1;
+	} else if ( errno != ENOENT ) {
+		sieve_storage_sys_error(storage,
+			"Failed to setup directory for binaries: "
+			"stat(%s) failed: %m",
+			bin_dir);
+		return -1;
+	}
+
+	if ( mkdir_parents(bin_dir, mode) == 0 ) {
+		if ( svinst->debug )
+			sieve_storage_sys_debug(storage,
+				"Created directory for binaries: %s", bin_dir);
+		return 1;
+	}
+
+	switch ( errno ) {
+	case EEXIST:
+		return 0;
+	case ENOENT:
+		sieve_storage_sys_error(storage,
+			"Directory for binaries was deleted while it was being created");
+		break;
+	case EACCES:
+		sieve_storage_sys_error(storage,
+			"%s",	eacces_error_get_creating("mkdir_parents_chgrp", bin_dir));
+		break;
+	default:
+		sieve_storage_sys_error(storage,
+			"mkdir_parents_chgrp(%s) failed: %m", bin_dir);
+		break;
+	}
+
+	return -1;
+}
+
+int sieve_storage_is_singular
+(struct sieve_storage *storage)
+{
+	if ( storage->v.is_singular == NULL )
+		return 1;
+	return storage->v.is_singular(storage);
+}
+
+int sieve_storage_get_last_change
+(struct sieve_storage *storage, time_t *last_change_r)
+{
+	i_assert(storage->v.get_last_change != NULL);
+	return storage->v.get_last_change(storage, last_change_r);
+}
+
+void sieve_storage_set_modified
+(struct sieve_storage *storage, time_t mtime)
+{
+	if (storage->v.set_modified == NULL)
+		return;
+
+	storage->v.set_modified(storage, mtime);
+}
+
+/*
+ * Script access
+ */
+
+static struct sieve_script *sieve_storage_get_script_direct
+(struct sieve_storage *storage, const char *name,
+	enum sieve_error *error_r)
+{
+	struct sieve_script *script;
+
+	if ( error_r != NULL )
+		*error_r = SIEVE_ERROR_NONE;
+	sieve_storage_clear_error(storage);
+
+	/* Validate script name */
+	if ( name != NULL && !sieve_script_name_is_valid(name) ) {
+		sieve_storage_set_error(storage,
+			SIEVE_ERROR_BAD_PARAMS,
+			"Invalid script name `%s'.",
+			str_sanitize(name, 80));
+		if (error_r != NULL)
+			*error_r = storage->error_code;
+		return NULL;
+	}
+
+	i_assert(storage->v.get_script != NULL);
+	script = storage->v.get_script(storage, name);
+	return script;
+}
+
+struct sieve_script *sieve_storage_get_script
+(struct sieve_storage *storage, const char *name,
+	enum sieve_error *error_r)
+{
+	struct sieve_instance *svinst = storage->svinst;
+	struct sieve_script *script;
+
+	script = sieve_storage_get_script_direct
+		(storage, name, error_r);
+	if ( script == NULL ) {
+		/* Error */
+		if ( storage->error_code == SIEVE_ERROR_NOT_FOUND &&
+			(storage->flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0 &&
+			storage->default_name != NULL &&
+			storage->default_location != NULL &&
+			strcmp(storage->default_name, name) == 0 ) {
+			/* Not found; if this name maps to the default script,
+			   try to access that instead */
+			i_assert(*storage->default_location != '\0');
+
+			sieve_storage_sys_debug(storage,
+				"Trying default script instead");
+
+			script = sieve_script_create(svinst,
+				storage->default_location, NULL, error_r);
+			if (script != NULL) {
+				script->storage->is_default = TRUE;
+				script->storage->default_for = storage;
+				sieve_storage_ref(storage);
+			}
+
+		} else 	if ( error_r != NULL ) {
+			*error_r = storage->error_code;
+		}
+	}
+	return script;
+}
+
+struct sieve_script *sieve_storage_open_script
+(struct sieve_storage *storage, const char *name,
+	enum sieve_error *error_r)
+{
+	struct sieve_instance *svinst = storage->svinst;
+	struct sieve_script *script;
+
+	script = sieve_storage_get_script(storage, name, error_r);
+	if ( script == NULL )
+		return NULL;
+
+	if ( sieve_script_open(script, error_r) >= 0 )
+		return script;
+
+	/* Error */
+	sieve_script_unref(&script);
+	script = NULL;
+
+	if ( storage->error_code == SIEVE_ERROR_NOT_FOUND &&
+		(storage->flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0 &&
+		storage->default_name != NULL &&
+		storage->default_location != NULL &&
+		strcmp(storage->default_name, name) == 0 ) {
+		/* Not found; if this name maps to the default script,
+		   try to open that instead */
+		i_assert(*storage->default_location != '\0');
+
+		sieve_storage_sys_debug(storage,
+			"Trying default script instead");
+
+		script = sieve_script_create_open(svinst,
+			storage->default_location, NULL, error_r);
+		if (script != NULL) {
+			script->storage->is_default = TRUE;
+			script->storage->default_for = storage;
+			sieve_storage_ref(storage);
+		}
+	}
+	return script;
+}
+
+static int sieve_storage_check_script_direct
+(struct sieve_storage *storage, const char *name,
+	enum sieve_error *error_r) ATTR_NULL(3)
+{
+	struct sieve_script *script;
+	enum sieve_error error;
+	int ret;
+
+	if ( error_r == NULL )
+		error_r = &error;
+
+	script = sieve_storage_get_script_direct(storage, name, error_r);
+	if ( script == NULL )
+		return ( *error_r == SIEVE_ERROR_NOT_FOUND ? 0 : -1 );
+
+	ret = sieve_script_open(script, error_r);
+	return ( ret >= 0 ? 1 :
+		( *error_r == SIEVE_ERROR_NOT_FOUND ? 0 : -1 ) );
+}
+
+int sieve_storage_check_script
+(struct sieve_storage *storage, const char *name,
+	enum sieve_error *error_r)
+{
+	struct sieve_script *script;
+	enum sieve_error error;
+
+	if ( error_r == NULL )
+		error_r = &error;
+
+	script = sieve_storage_open_script(storage, name, error_r);
+	if ( script == NULL )
+		return ( *error_r == SIEVE_ERROR_NOT_FOUND ? 0 : -1 );
+
+	sieve_script_unref(&script);
+	return 1;
+}
+
+/*
+ * Script sequence
+ */
+
+struct sieve_script_sequence *sieve_storage_get_script_sequence
+(struct sieve_storage *storage, enum sieve_error *error_r)
+{
+	enum sieve_error error;
+
+	if ( error_r != NULL )
+		*error_r = SIEVE_ERROR_NONE;
+	else
+		error_r = &error;
+
+	i_assert( storage->v.get_script_sequence != NULL );
+	return storage->v.get_script_sequence(storage, error_r);
+}
+
+/*
+ * Active script
+ */
+
+static int sieve_storage_active_script_do_get_name
+(struct sieve_storage *storage, const char **name_r,
+	bool *default_r) ATTR_NULL(3)
+{
+	struct sieve_instance *svinst = storage->svinst;
+	enum sieve_error error;
+	int ret;
+
+	if (default_r != NULL)
+		*default_r = FALSE;
+
+	i_assert(storage->v.active_script_get_name != NULL);
+	ret = storage->v.active_script_get_name(storage, name_r);
+
+	if ( ret != 0 ||
+		(storage->flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) != 0 ||
+		storage->default_location == NULL ||
+		storage->default_name == NULL ) {
+		return ret;
+	}
+
+	*name_r = storage->default_name;
+
+	ret = sieve_script_check
+		(svinst, storage->default_location, NULL, &error);
+	if (ret <= 0)
+		return ret;
+
+	if (default_r != NULL)
+		*default_r = TRUE;
+	return 1;
+}
+
+int sieve_storage_active_script_get_name
+(struct sieve_storage *storage, const char **name_r)
+{
+	return sieve_storage_active_script_do_get_name
+		(storage, name_r, NULL);
+}
+
+int sieve_storage_active_script_is_default
+(struct sieve_storage *storage)
+{
+	const char *name;
+	bool is_default = FALSE;
+	int ret;
+
+	ret = sieve_storage_active_script_do_get_name
+		(storage, &name, &is_default);
+	return ( ret < 0 ? -1 : ( is_default ? 1 : 0 ) );
+}
+
+struct sieve_script *sieve_storage_active_script_open
+(struct sieve_storage *storage, enum sieve_error *error_r)
+{
+	struct sieve_instance *svinst = storage->svinst;
+	struct sieve_script *script;
+
+	i_assert(storage->v.active_script_open != NULL);
+	script = storage->v.active_script_open(storage);
+
+	if ( script != NULL ||
+		(storage->flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) != 0 ||
+		storage->default_location == NULL) {
+		if ( error_r != NULL )
+			*error_r = storage->error_code;
+		return script;
+	}
+	
+	/* Try default script location */
+	script = sieve_script_create_open(svinst,
+		storage->default_location, NULL, error_r);
+	if (script != NULL) {
+		script->storage->is_default = TRUE;
+		script->storage->default_for = storage;
+		sieve_storage_ref(storage);
+	}
+	return script;
+}
+
+int sieve_storage_deactivate
+(struct sieve_storage *storage, time_t mtime)
+{
+	int ret;
+
+	i_assert( (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 );
+
+	i_assert(storage->v.deactivate != NULL);
+	ret = storage->v.deactivate(storage);
+	
+	if (ret >= 0) {
+		sieve_storage_set_modified(storage, mtime);
+		(void)sieve_storage_sync_deactivate(storage);
+	}
+
+	return ret;
+}
+
+int sieve_storage_active_script_get_last_change
+(struct sieve_storage *storage, time_t *last_change_r)
+{
+	i_assert( storage->v.active_script_get_last_change != NULL);
+	
+	return storage->v.active_script_get_last_change(storage, last_change_r);
+}
+
+/*
+ * Listing scripts
+ */
+
+struct sieve_storage_list_context *sieve_storage_list_init
+(struct sieve_storage *storage)
+{
+	struct sieve_storage_list_context *lctx;
+	
+	i_assert(storage->v.list_init != NULL);
+	lctx = storage->v.list_init(storage);
+
+	if (lctx != NULL)
+		lctx->storage = storage;
+	
+	return lctx;
+}
+
+const char *sieve_storage_list_next
+(struct sieve_storage_list_context *lctx, bool *active_r)
+{
+	struct sieve_storage *storage = lctx->storage;
+	struct sieve_instance *svinst = storage->svinst;
+	const char *scriptname;
+	bool have_default, script_active = FALSE;
+
+	have_default = ( storage->default_name != NULL &&
+		storage->default_location != NULL &&
+		(storage->flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0 );
+
+	i_assert(storage->v.list_next != NULL);
+	scriptname = storage->v.list_next(lctx, &script_active);
+
+	i_assert( !script_active || !lctx->seen_active );
+	if ( script_active )
+		lctx->seen_active = TRUE;
+
+	if ( scriptname != NULL ) {
+		/* Remember when we see that the storage has its own script for
+		   default */
+		if (have_default &&
+			strcmp(scriptname, storage->default_name) == 0)
+			lctx->seen_default = TRUE;
+
+	} else if ( have_default && !lctx->seen_default &&
+		sieve_script_check(svinst,
+			storage->default_location, NULL, NULL) > 0) {
+
+		/* Return default script at the end if it was not listed
+		   thus far (storage backend has no script under default
+		   name) */
+		scriptname = storage->default_name;
+		lctx->seen_default = TRUE;
+
+		/* Mark default as active if no normal script is active */
+		if ( !lctx->seen_active ) {
+			script_active = TRUE;			
+			lctx->seen_active = TRUE;
+		}
+	}
+
+	if ( active_r != NULL )
+		*active_r = script_active;	
+	return scriptname;
+}
+
+int sieve_storage_list_deinit
+(struct sieve_storage_list_context **_lctx)
+{	
+	struct sieve_storage_list_context *lctx = *_lctx;
+	struct sieve_storage *storage = lctx->storage;
+	int ret;
+
+	i_assert(storage->v.list_deinit != NULL);
+	ret = storage->v.list_deinit(lctx);
+
+	*_lctx = NULL;	
+	return ret;
+}
+
+/*
+ * Saving scripts
+ */
+
+struct sieve_storage_save_context *
+sieve_storage_save_init(struct sieve_storage *storage,
+	const char *scriptname, struct istream *input)
+{
+	struct sieve_storage_save_context *sctx;
+
+	if ( scriptname != NULL ) {
+		/* Validate script name */
+		if ( !sieve_script_name_is_valid(scriptname) ) {
+			sieve_storage_set_error(storage,
+				SIEVE_ERROR_BAD_PARAMS,
+				"Invalid Sieve script name `%s'.",
+				str_sanitize(scriptname, 80));
+			return NULL;
+		}
+	}
+
+	i_assert( (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 );
+
+	i_assert(storage->v.save_init != NULL);
+	if ((sctx=storage->v.save_init(storage, scriptname, input)) == NULL)
+		return NULL;
+
+	sctx->storage = storage;
+	sctx->mtime = (time_t)-1;
+
+	i_assert( sctx->input != NULL );
+
+	return sctx;
+}
+
+int sieve_storage_save_continue(struct sieve_storage_save_context *sctx)
+{
+	struct sieve_storage *storage = sctx->storage;
+	int ret;
+
+	i_assert(storage->v.save_continue != NULL);
+	ret = storage->v.save_continue(sctx);
+	if (ret < 0)
+		sctx->failed = TRUE;
+	return ret;
+}
+
+int sieve_storage_save_finish(struct sieve_storage_save_context *sctx)
+{
+	struct sieve_storage *storage = sctx->storage;
+
+	i_assert(!sctx->finished);
+	sctx->finished = TRUE;
+
+	i_assert(storage->v.save_finish != NULL);
+	return storage->v.save_finish(sctx);
+}
+
+void sieve_storage_save_set_mtime
+(struct sieve_storage_save_context *sctx, time_t mtime)
+{
+	sctx->mtime = mtime;
+}
+
+static void sieve_storage_save_deinit(struct sieve_storage_save_context *sctx)
+{
+	if (sctx->scriptobject != NULL)
+		sieve_script_unref(&sctx->scriptobject);
+}
+
+struct sieve_script *sieve_storage_save_get_tempscript
+(struct sieve_storage_save_context *sctx)
+{
+	struct sieve_storage *storage = sctx->storage;
+
+	if ( sctx->failed )
+		return NULL;
+
+	if ( sctx->scriptobject != NULL )
+		return sctx->scriptobject;
+
+	i_assert( storage->v.save_get_tempscript != NULL );
+	sctx->scriptobject = storage->v.save_get_tempscript(sctx);
+
+	i_assert( sctx->scriptobject != NULL ||
+		storage->error_code != SIEVE_ERROR_NONE );
+	return sctx->scriptobject;
+}
+
+bool sieve_storage_save_will_activate
+(struct sieve_storage_save_context *sctx)
+{
+	if ( sctx->scriptname == NULL )
+		return FALSE;
+
+	if ( sctx->active_scriptname == NULL ) {
+		const char *scriptname;
+
+		if ( sieve_storage_active_script_get_name
+			(sctx->storage, &scriptname) > 0 )
+			sctx->active_scriptname = p_strdup(sctx->pool, scriptname);
+	}
+
+ 	/* Is the requested script active? */
+	return ( sctx->active_scriptname != NULL &&
+		strcmp(sctx->scriptname, sctx->active_scriptname) == 0 );
+}
+
+int sieve_storage_save_commit(struct sieve_storage_save_context **_sctx)
+{
+	struct sieve_storage_save_context *sctx = *_sctx;
+	struct sieve_storage *storage = sctx->storage;
+	const char *scriptname;
+	bool default_activate = FALSE;
+	int ret;	
+
+	i_assert(sctx->finished);
+	i_assert(sctx->scriptname != NULL);
+
+	/* Check whether we're replacing the default active script */
+	if ( storage->default_name != NULL &&
+		storage->default_location != NULL &&
+		(storage->flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0 &&
+		strcmp(sctx->scriptname, storage->default_name) == 0 &&
+		sieve_storage_save_will_activate(sctx) &&
+		sieve_storage_check_script_direct
+			(storage, storage->default_name, NULL) <= 0 )
+		default_activate = TRUE;
+
+	scriptname = t_strdup(sctx->scriptname);
+	sieve_storage_save_deinit(sctx);
+
+	i_assert(storage->v.save_commit != NULL);
+	ret = storage->v.save_commit(sctx);
+	*_sctx = NULL;
+
+	/* Implicitly activate it when we're replacing the default
+	   active script */
+	if ( ret >= 0 && default_activate ) {
+		struct sieve_script *script;
+		enum sieve_error error;
+
+		script = sieve_storage_open_script(storage, scriptname, &error);
+		if ( script == NULL ) {
+			/* Somehow not actually saved */
+			ret = ( error == SIEVE_ERROR_NOT_FOUND ? 0 : -1 );
+		} else if ( sieve_script_activate(script, (time_t)-1) < 0 ) {
+			/* Failed to activate; roll back */
+			ret = -1;
+			(void)sieve_script_delete(script, TRUE);
+			sieve_script_unref(&script);
+		}
+
+		if (ret < 0) {
+			sieve_storage_sys_error(storage,
+				"Failed to implicitly activate script `%s' "
+				"while replacing the default active script",
+				scriptname);
+		}
+	}
+
+	/* set INBOX mailbox attribute */
+	if ( ret >= 0 ) {
+		(void)sieve_storage_sync_script_save(storage, scriptname);
+	}
+
+	return ret;
+}
+
+void sieve_storage_save_cancel(struct sieve_storage_save_context **_sctx)
+{
+	struct sieve_storage_save_context *sctx = *_sctx;
+	struct sieve_storage *storage = sctx->storage;
+
+	sctx->failed = TRUE;
+
+	sieve_storage_save_deinit(sctx);
+
+	if (!sctx->finished)
+		(void)sieve_storage_save_finish(sctx);
+	
+	i_assert(storage->v.save_cancel != NULL);
+	storage->v.save_cancel(sctx);
+	*_sctx = NULL;
+}
+
+int sieve_storage_save_as_active
+(struct sieve_storage *storage, struct istream *input,
+	time_t mtime)
+{
+	i_assert( storage->v.save_as_active != NULL );
+	return storage->v.save_as_active(storage, input, mtime);
+}
+
+int sieve_storage_save_as
+(struct sieve_storage *storage, struct istream *input,
+	const char *name)
+{
+	i_assert( storage->v.save_as != NULL );
+	return storage->v.save_as(storage, input, name);
+}
+
+/*
+ * Checking quota
+ */
+
+bool sieve_storage_quota_validsize
+(struct sieve_storage *storage, size_t size, uint64_t *limit_r)
+{
+	uint64_t max_size;
+
+	max_size = sieve_max_script_size(storage->svinst);
+	if ( max_size > 0 && size > max_size ) {
+		*limit_r = max_size;
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+uint64_t sieve_storage_quota_max_script_size
+(struct sieve_storage *storage)
+{
+	return sieve_max_script_size(storage->svinst);
+}
+
+int sieve_storage_quota_havespace
+(struct sieve_storage *storage, const char *scriptname, size_t size,
+	enum sieve_storage_quota *quota_r, uint64_t *limit_r)
+{
+	*quota_r = SIEVE_STORAGE_QUOTA_NONE;
+	*limit_r = 0;
+
+	/* Check the script size */
+	if ( !sieve_storage_quota_validsize(storage, size, limit_r) ) {
+		*quota_r = SIEVE_STORAGE_QUOTA_MAXSIZE;
+		return 0;
+	}
+
+	/* Do we need to scan the storage (quota enabled) ? */
+	if ( storage->max_scripts == 0 && storage->max_storage == 0 ) {
+		return 1;
+	}
+
+	if (storage->v.quota_havespace == NULL)
+		return 1;
+
+	return storage->v.quota_havespace
+		(storage, scriptname,	size, quota_r, limit_r);
+}
+
+/*
+ * Properties
+ */
+
+const char *sieve_storage_location(const struct sieve_storage *storage)
+{
+	return storage->location;
+}
+
+bool sieve_storage_is_default(const struct sieve_storage *storage)
+{
+	return storage->is_default;
+}
+
+/*
+ * Error handling
+ */
+
+void sieve_storage_clear_error(struct sieve_storage *storage)
+{
+	i_free(storage->error);
+	storage->error_code = SIEVE_ERROR_NONE;
+	storage->error = NULL;
+}
+
+void sieve_storage_set_error
+(struct sieve_storage *storage, enum sieve_error error,
+	const char *fmt, ...)
+{
+	va_list va;
+
+	sieve_storage_clear_error(storage);
+
+	if (fmt != NULL) {
+		va_start(va, fmt);
+		storage->error = i_strdup_vprintf(fmt, va);
+		va_end(va);
+	}
+
+	storage->error_code = error;
+}
+
+void sieve_storage_copy_error
+(struct sieve_storage *storage, const struct sieve_storage *source)
+{
+	sieve_storage_clear_error(storage);
+	storage->error = i_strdup(source->error);
+	storage->error_code = source->error_code;	
+}
+
+void sieve_storage_set_internal_error
+(struct sieve_storage *storage)
+{
+	struct tm *tm;
+	char str[256];
+
+	sieve_storage_clear_error(storage);
+
+	/* critical errors may contain sensitive data, so let user
+	   see only "Internal error" with a timestamp to make it
+	   easier to look from log files the actual error message. */
+	tm = localtime(&ioloop_time);
+
+	storage->error =
+		strftime(str, sizeof(str), CRITICAL_MSG_STAMP, tm) > 0 ?
+		i_strdup(str) : i_strdup(CRITICAL_MSG);
+
+	storage->error_code = SIEVE_ERROR_TEMP_FAILURE;
+}
+
+void sieve_storage_set_critical
+(struct sieve_storage *storage, const char *fmt, ...)
+{
+	va_list va;
+
+	if (fmt != NULL) {
+		if ( (storage->flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0 ) {
+			va_start(va, fmt);
+			sieve_sys_error(storage->svinst, "%s storage: %s",
+				storage->driver_name, t_strdup_vprintf(fmt, va));
+			va_end(va);
+
+			sieve_storage_set_internal_error(storage);
+
+		} else {
+			sieve_storage_clear_error(storage);
+
+			/* no user is involved while synchronizing, so do it the
+			   normal way */
+			va_start(va, fmt);
+			storage->error = i_strdup_vprintf(fmt, va);
+			va_end(va);
+
+			storage->error_code = SIEVE_ERROR_TEMP_FAILURE;
+		}
+	}
+}
+
+const char *sieve_storage_get_last_error
+(struct sieve_storage *storage, enum sieve_error *error_r)
+{
+	/* We get here only in error situations, so we have to return some
+	   error. If storage->error is NULL, it means we forgot to set it at
+	   some point..
+	 */
+
+	if ( error_r != NULL )
+		*error_r = storage->error_code;
+
+	return storage->error != NULL ? storage->error : "Unknown error";
+}
+
+void sieve_storage_sys_error
+(struct sieve_storage *storage, const char *fmt, ...)
+{
+	struct sieve_instance *svinst = storage->svinst;
+	va_list va;
+
+	va_start(va, fmt);
+	sieve_sys_error(svinst, "%s storage: %s",
+		storage->driver_name, t_strdup_vprintf(fmt, va));
+	va_end(va);	
+}
+
+void sieve_storage_sys_warning
+(struct sieve_storage *storage, const char *fmt, ...)
+{
+	struct sieve_instance *svinst = storage->svinst;
+	va_list va;
+
+	va_start(va, fmt);
+	sieve_sys_warning(svinst, "%s storage: %s",
+		storage->driver_name, t_strdup_vprintf(fmt, va));
+	va_end(va);	
+}
+
+void sieve_storage_sys_info
+(struct sieve_storage *storage, const char *fmt, ...)
+{
+	struct sieve_instance *svinst = storage->svinst;
+	va_list va;
+
+	va_start(va, fmt);
+	sieve_sys_info(svinst, "%s storage: %s",
+		storage->driver_name, t_strdup_vprintf(fmt, va));
+	va_end(va);	
+}
+
+void sieve_storage_sys_debug
+(struct sieve_storage *storage, const char *fmt, ...)
+{
+	struct sieve_instance *svinst = storage->svinst;
+	va_list va;
+
+	if (!svinst->debug)
+		return;
+
+	va_start(va, fmt);
+	sieve_sys_debug(svinst, "%s storage: %s",
+		storage->driver_name, t_strdup_vprintf(fmt, va));
+	va_end(va);	
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-storage.h
@@ -0,0 +1,194 @@
+#ifndef SIEVE_STORAGE_H
+#define SIEVE_STORAGE_H
+
+#define MAILBOX_ATTRIBUTE_PREFIX_SIEVE \
+	MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER"sieve/"
+#define MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES \
+	MAILBOX_ATTRIBUTE_PREFIX_SIEVE"files/"
+#define MAILBOX_ATTRIBUTE_SIEVE_DEFAULT \
+	MAILBOX_ATTRIBUTE_PREFIX_SIEVE"default"
+
+#define MAILBOX_ATTRIBUTE_SIEVE_DEFAULT_LINK 'L'
+#define MAILBOX_ATTRIBUTE_SIEVE_DEFAULT_SCRIPT 'S'
+
+/*
+ * Storage object
+ */
+
+enum sieve_storage_flags {
+	/* Storage is opened for read/write access (e.g. ManageSieve) */
+	SIEVE_STORAGE_FLAG_READWRITE         = 0x01,
+	/* This storage is used for synchronization (and not normal ManageSieve)
+	 */
+	SIEVE_STORAGE_FLAG_SYNCHRONIZING     = 0x02
+};
+
+struct sieve_storage;
+
+struct sieve_storage *sieve_storage_create
+(struct sieve_instance *svinst, const char *location,
+	enum sieve_storage_flags flags, enum sieve_error *error_r)
+	ATTR_NULL(4);
+struct sieve_storage *sieve_storage_create_main
+(struct sieve_instance *svinst, struct mail_user *user,
+	enum sieve_storage_flags flags, enum sieve_error *error_r)
+	ATTR_NULL(4);
+
+void sieve_storage_ref(struct sieve_storage *storage);
+void sieve_storage_unref(struct sieve_storage **_storage);
+
+/*
+ * Script access
+ */
+
+struct sieve_script *sieve_storage_get_script
+	(struct sieve_storage *storage, const char *name,
+		enum sieve_error *error_r) ATTR_NULL(3);
+struct sieve_script *sieve_storage_open_script
+	(struct sieve_storage *storage, const char *name,
+		enum sieve_error *error_r) ATTR_NULL(3);
+int sieve_storage_check_script
+	(struct sieve_storage *storage, const char *name,
+		enum sieve_error *error_r) ATTR_NULL(3);
+
+/*
+ * Script sequence
+ */
+
+struct sieve_script_sequence *sieve_storage_get_script_sequence
+(struct sieve_storage *storage, enum sieve_error *error_r);
+
+/*
+ * Active script
+ */
+
+int sieve_storage_active_script_get_name
+	(struct sieve_storage *storage, const char **name_r);
+
+struct sieve_script *sieve_storage_active_script_open
+	(struct sieve_storage *storage, enum sieve_error *error_r)
+	ATTR_NULL(2);
+
+int sieve_storage_active_script_get_last_change
+	(struct sieve_storage *storage, time_t *last_change_r);
+
+/*
+ * Listing scripts in storage
+ */
+
+struct sieve_storage_list_context;
+
+/* Create a context for listing the scripts in the storage */
+struct sieve_storage_list_context *sieve_storage_list_init
+	(struct sieve_storage *storage);
+/* Get the next script in the storage. */
+const char *sieve_storage_list_next
+	(struct sieve_storage_list_context *lctx, bool *active_r)
+	ATTR_NULL(2);
+/* Destroy the listing context */
+int sieve_storage_list_deinit
+	(struct sieve_storage_list_context **lctx);
+
+/*
+ * Saving scripts in storage
+ */
+
+struct sieve_storage_save_context;
+
+struct sieve_storage_save_context *
+sieve_storage_save_init(struct sieve_storage *storage,
+	const char *scriptname, struct istream *input);
+
+int sieve_storage_save_continue(struct sieve_storage_save_context *sctx);
+
+int sieve_storage_save_finish(struct sieve_storage_save_context *sctx);
+
+struct sieve_script *sieve_storage_save_get_tempscript
+  (struct sieve_storage_save_context *sctx);
+
+bool sieve_storage_save_will_activate
+	(struct sieve_storage_save_context *sctx);
+
+void sieve_storage_save_set_mtime
+	(struct sieve_storage_save_context *sctx, time_t mtime);
+
+void sieve_storage_save_cancel(struct sieve_storage_save_context **sctx);
+
+int sieve_storage_save_commit(struct sieve_storage_save_context **sctx);
+
+int sieve_storage_save_as
+	(struct sieve_storage *storage, struct istream *input,
+		const char *name);
+
+/* Saves input directly as a regular file at the active script path.
+ * This is needed for the doveadm-sieve plugin.
+ */
+int sieve_storage_save_as_active
+	(struct sieve_storage *storage, struct istream *input, time_t mtime);
+
+/*
+ * Management
+ */
+
+int sieve_storage_deactivate(struct sieve_storage *storage, time_t mtime);
+
+/*
+ * Storage quota
+ */
+
+enum sieve_storage_quota {
+	SIEVE_STORAGE_QUOTA_NONE,
+	SIEVE_STORAGE_QUOTA_MAXSIZE,
+	SIEVE_STORAGE_QUOTA_MAXSCRIPTS,
+	SIEVE_STORAGE_QUOTA_MAXSTORAGE
+};
+
+bool sieve_storage_quota_validsize
+	(struct sieve_storage *storage, size_t size, uint64_t *limit_r);
+
+uint64_t sieve_storage_quota_max_script_size
+	(struct sieve_storage *storage);
+
+int sieve_storage_quota_havespace
+	(struct sieve_storage *storage, const char *scriptname, size_t size,
+		enum sieve_storage_quota *quota_r, uint64_t *limit_r);
+
+/*
+ * Properties
+ */
+
+const char *sieve_storage_location
+	(const struct sieve_storage *storage) ATTR_PURE;
+bool sieve_storage_is_default
+	(const struct sieve_storage *storage) ATTR_PURE;
+
+int sieve_storage_is_singular
+	(struct sieve_storage *storage);
+
+/*
+ * Error handling
+ */
+
+void sieve_storage_clear_error(struct sieve_storage *storage);
+
+void sieve_storage_set_error
+	(struct sieve_storage *storage, enum sieve_error error,
+		const char *fmt, ...) ATTR_FORMAT(3, 4);
+void sieve_storage_set_critical
+	(struct sieve_storage *storage, 	const char *fmt, ...)
+		ATTR_FORMAT(2, 3);
+
+const char *sieve_storage_get_last_error
+	(struct sieve_storage *storage, enum sieve_error *error_r)
+	ATTR_NULL(2);
+
+/*
+ *
+ */
+
+int sieve_storage_get_last_change
+	(struct sieve_storage *storage, time_t *last_change_r);
+void sieve_storage_set_modified
+	(struct sieve_storage *storage, time_t mtime);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-stringlist.c
@@ -0,0 +1,275 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+
+/*
+ * Default implementation
+ */
+
+int sieve_stringlist_read_all
+(struct sieve_stringlist *strlist, pool_t pool,
+	const char * const **list_r)
+{
+	if ( strlist->read_all == NULL ) {
+		ARRAY(const char *) items;
+		string_t *item;
+		int ret;
+
+		sieve_stringlist_reset(strlist);
+
+		p_array_init(&items, pool, 4);
+
+		item = NULL;
+		while ( (ret=sieve_stringlist_next_item(strlist, &item)) > 0 ) {
+			const char *stritem = p_strdup(pool, str_c(item));
+
+			array_append(&items, &stritem, 1);
+		}
+
+		(void)array_append_space(&items);
+		*list_r = array_idx(&items, 0);
+
+		return ( ret < 0 ? -1 : 1 );
+	}
+
+	return strlist->read_all(strlist, pool, list_r);
+}
+
+int sieve_stringlist_get_length
+(struct sieve_stringlist *strlist)
+{
+	if ( strlist->get_length == NULL ) {
+		string_t *item;
+		int count = 0;
+		int ret;
+
+		sieve_stringlist_reset(strlist);
+		while ( (ret=sieve_stringlist_next_item(strlist, &item)) > 0 ) {
+			count++;
+		}
+		sieve_stringlist_reset(strlist);
+
+		return ( ret < 0 ? -1 : count );
+	}
+
+	return strlist->get_length(strlist);
+}
+
+/*
+ * Single Stringlist
+ */
+
+/* Object */
+
+static int sieve_single_stringlist_next_item
+	(struct sieve_stringlist *_strlist, string_t **str_r);
+static void sieve_single_stringlist_reset
+	(struct sieve_stringlist *_strlist);
+static int sieve_single_stringlist_get_length
+	(struct sieve_stringlist *_strlist);
+
+struct sieve_single_stringlist {
+	struct sieve_stringlist strlist;
+
+	string_t *value;
+
+	bool end:1;
+	bool count_empty:1;
+};
+
+struct sieve_stringlist *sieve_single_stringlist_create
+(const struct sieve_runtime_env *renv, string_t *str, bool count_empty)
+{
+	struct sieve_single_stringlist *strlist;
+
+	strlist = t_new(struct sieve_single_stringlist, 1);
+	strlist->strlist.runenv = renv;
+	strlist->strlist.exec_status = SIEVE_EXEC_OK;
+	strlist->strlist.next_item = sieve_single_stringlist_next_item;
+	strlist->strlist.reset = sieve_single_stringlist_reset;
+	strlist->strlist.get_length = sieve_single_stringlist_get_length;
+	strlist->count_empty = count_empty;
+	strlist->value = str;
+
+	return &strlist->strlist;
+}
+
+struct sieve_stringlist *sieve_single_stringlist_create_cstr
+(const struct sieve_runtime_env *renv, const char *cstr, bool count_empty)
+{
+	string_t *str = t_str_new_const(cstr, strlen(cstr));
+
+	return sieve_single_stringlist_create(renv, str, count_empty);
+}
+
+/* Implementation */
+
+static int sieve_single_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+	struct sieve_single_stringlist *strlist =
+		(struct sieve_single_stringlist *)_strlist;
+
+	if ( strlist->end ) {
+		*str_r = NULL;
+		return 0;
+	}
+
+	*str_r = strlist->value;
+	strlist->end = TRUE;
+	return 1;
+}
+
+static void sieve_single_stringlist_reset
+(struct sieve_stringlist *_strlist)
+{
+	struct sieve_single_stringlist *strlist =
+		(struct sieve_single_stringlist *)_strlist;
+
+	strlist->end = FALSE;
+}
+
+static int sieve_single_stringlist_get_length
+(struct sieve_stringlist *_strlist)
+{
+	struct sieve_single_stringlist *strlist =
+		(struct sieve_single_stringlist *)_strlist;
+
+	return ( strlist->count_empty || str_len(strlist->value) > 0 ? 1 : 0 );
+}
+
+/*
+ * Index Stringlist
+ */
+
+/* Object */
+
+static int sieve_index_stringlist_next_item
+	(struct sieve_stringlist *_strlist, string_t **str_r);
+static void sieve_index_stringlist_reset
+	(struct sieve_stringlist *_strlist);
+static int sieve_index_stringlist_get_length
+	(struct sieve_stringlist *_strlist);
+static void sieve_index_stringlist_set_trace
+	(struct sieve_stringlist *strlist, bool trace);
+
+struct sieve_index_stringlist {
+	struct sieve_stringlist strlist;
+
+	struct sieve_stringlist *source;
+
+	int index;
+	bool end:1;
+};
+
+struct sieve_stringlist *sieve_index_stringlist_create
+(const struct sieve_runtime_env *renv, struct sieve_stringlist *source,
+	int index)
+{
+	struct sieve_index_stringlist *strlist;
+
+	strlist = t_new(struct sieve_index_stringlist, 1);
+	strlist->strlist.runenv = renv;
+	strlist->strlist.exec_status = SIEVE_EXEC_OK;
+	strlist->strlist.next_item = sieve_index_stringlist_next_item;
+	strlist->strlist.reset = sieve_index_stringlist_reset;
+	strlist->strlist.get_length = sieve_index_stringlist_get_length;
+	strlist->strlist.set_trace = sieve_index_stringlist_set_trace;
+	strlist->source = source;
+	strlist->index = index;
+
+	return &strlist->strlist;
+}
+
+/* Implementation */
+
+static int sieve_index_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+	struct sieve_index_stringlist *strlist =
+		(struct sieve_index_stringlist *)_strlist;
+	int index, ret;
+
+	if ( strlist->end ) {
+		*str_r = NULL;
+		return 0;
+	}
+
+	if ( strlist->index < 0 ) {
+		int len = sieve_stringlist_get_length(strlist->source);
+		if (len < 0) {
+			_strlist->exec_status = strlist->source->exec_status;
+			return -1;
+		}
+
+		if (len < -strlist->index) {
+			*str_r = NULL;
+			strlist->end = TRUE;
+			return 0;
+		}
+		index = len + 1 + strlist->index;
+	} else {
+		index = strlist->index;
+	}
+
+	while ( index > 0 ) {
+		if ( (ret=sieve_stringlist_next_item(strlist->source, str_r)) <= 0 ) {
+			if (ret < 0)
+				_strlist->exec_status = strlist->source->exec_status;
+			return ret;
+		}
+	
+		index--;
+	}
+		
+	strlist->end = TRUE;
+	return 1;
+}
+
+static void sieve_index_stringlist_reset
+(struct sieve_stringlist *_strlist)
+{
+	struct sieve_index_stringlist *strlist =
+		(struct sieve_index_stringlist *)_strlist;
+
+	sieve_stringlist_reset(strlist->source);
+	strlist->end = FALSE;
+}
+
+static int sieve_index_stringlist_get_length
+(struct sieve_stringlist *_strlist)
+{
+	struct sieve_index_stringlist *strlist =
+		(struct sieve_index_stringlist *)_strlist;
+	int len;
+
+	len = sieve_stringlist_get_length(strlist->source);
+	if (len < 0) {
+		_strlist->exec_status = strlist->source->exec_status;
+		return -1;
+	}
+
+	if ( strlist->index < 0 ) {
+		if ( -strlist->index >= len )
+			return 0;
+	} else if ( strlist->index >= len ) {
+			return 0;
+	}
+
+	return 1;
+}
+
+static void sieve_index_stringlist_set_trace
+(struct sieve_stringlist *_strlist, bool trace)
+{
+	struct sieve_index_stringlist *strlist =
+		(struct sieve_index_stringlist *)_strlist;
+
+	sieve_stringlist_set_trace(strlist->source, trace);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-stringlist.h
@@ -0,0 +1,74 @@
+#ifndef SIEVE_STRINGLIST_H
+#define SIEVE_STRINGLIST_H
+
+/*
+ * Stringlist API
+ */
+
+struct sieve_stringlist {
+	int (*next_item)
+		(struct sieve_stringlist *strlist, string_t **str_r);
+	void (*reset)
+		(struct sieve_stringlist *strlist);
+	int (*get_length)
+		(struct sieve_stringlist *strlist);
+
+	int (*read_all)
+		(struct sieve_stringlist *strlist, pool_t pool,
+			const char * const **list_r);
+
+	void (*set_trace)
+		(struct sieve_stringlist *strlist, bool trace);
+
+	const struct sieve_runtime_env *runenv;
+	int exec_status;
+
+	bool trace:1;
+};
+
+static inline void sieve_stringlist_set_trace
+(struct sieve_stringlist *strlist, bool trace)
+{
+	strlist->trace = trace;
+
+	if ( strlist->set_trace != NULL )
+		strlist->set_trace(strlist, trace);
+}
+
+static inline int sieve_stringlist_next_item
+(struct sieve_stringlist *strlist, string_t **str_r)
+{
+	return strlist->next_item(strlist, str_r);
+}
+
+static inline void sieve_stringlist_reset
+(struct sieve_stringlist *strlist)
+{
+	strlist->reset(strlist);
+}
+
+int sieve_stringlist_get_length
+	(struct sieve_stringlist *strlist);
+
+int sieve_stringlist_read_all
+	(struct sieve_stringlist *strlist, pool_t pool,
+		const char * const **list_r);
+
+/*
+ * Single Stringlist
+ */
+
+struct sieve_stringlist *sieve_single_stringlist_create
+	(const struct sieve_runtime_env *renv, string_t *str, bool count_empty);
+struct sieve_stringlist *sieve_single_stringlist_create_cstr
+(const struct sieve_runtime_env *renv, const char *cstr, bool count_empty);
+
+/*
+ * Index Stringlist
+ */
+
+struct sieve_stringlist *sieve_index_stringlist_create
+	(const struct sieve_runtime_env *renv, struct sieve_stringlist *source,
+		int index);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-types.h
@@ -0,0 +1,275 @@
+#ifndef SIEVE_TYPES_H
+#define SIEVE_TYPES_H
+
+#include "lib.h"
+#include "smtp-address.h"
+
+#include <stdio.h>
+
+/*
+ * Forward declarations
+ */
+
+struct smtp_params_mail;
+struct smtp_params_rcpt;
+
+struct sieve_instance;
+struct sieve_callbacks;
+
+struct sieve_script;
+struct sieve_binary;
+
+struct sieve_message_data;
+struct sieve_script_env;
+struct sieve_exec_status;
+struct sieve_trace_log;
+
+/*
+ * System environment
+ */
+
+enum sieve_flag {
+	/* Relative paths are resolved to HOME */
+	SIEVE_FLAG_HOME_RELATIVE = (1 << 0)
+};
+
+/* Sieve evaluation can be performed at various different points as messages
+   are processed. */
+enum sieve_env_location {
+	/* Unknown */
+	SIEVE_ENV_LOCATION_UNKNOWN = 0,
+	/* "MDA" - evaluation is being performed by a Mail Delivery Agent */
+	SIEVE_ENV_LOCATION_MDA,
+	/* "MTA" - the Sieve script is being evaluated by a Message Transfer Agent */
+	SIEVE_ENV_LOCATION_MTA,
+	/* "MS"  - evaluation is being performed by a Message Store */
+	SIEVE_ENV_LOCATION_MS
+};
+
+/* The point relative to final delivery where the Sieve script is being
+   evaluated. */
+enum sieve_delivery_phase {
+	SIEVE_DELIVERY_PHASE_UNKNOWN = 0,
+	SIEVE_DELIVERY_PHASE_PRE,
+	SIEVE_DELIVERY_PHASE_DURING,
+	SIEVE_DELIVERY_PHASE_POST,
+};
+
+struct sieve_environment {
+	const char *hostname;
+	const char *domainname;
+
+	const char *base_dir;
+	const char *username;
+	const char *home_dir;
+	const char *temp_dir;
+
+	enum sieve_flag flags;
+	enum sieve_env_location location;
+	enum sieve_delivery_phase delivery_phase;
+};
+
+/*
+ * Callbacks
+ */
+
+struct sieve_callbacks {
+	const char *(*get_homedir)(void *context);
+	const char *(*get_setting)(void *context, const char *identifier);
+};
+
+/*
+ * Errors
+ */
+
+enum sieve_error {
+	SIEVE_ERROR_NONE = 0,
+
+	/* Temporary internal error */
+	SIEVE_ERROR_TEMP_FAILURE,
+	/* It's not possible to do the wanted operation */
+	SIEVE_ERROR_NOT_POSSIBLE,
+	/* Invalid parameters (eg. script name not valid) */
+	SIEVE_ERROR_BAD_PARAMS,
+	/* No permission to do the request */
+	SIEVE_ERROR_NO_PERMISSION,
+	/* Out of disk space */
+	SIEVE_ERROR_NO_QUOTA,
+	/* Item (e.g. script or binary) cannot be found */
+	SIEVE_ERROR_NOT_FOUND,
+	/* Item (e.g. script or binary) already exists */
+	SIEVE_ERROR_EXISTS,
+	/* Referenced item (e.g. script or binary) is not valid or currupt */
+	SIEVE_ERROR_NOT_VALID,
+	/* Not allowed to perform the operation because the item is in active use */
+	SIEVE_ERROR_ACTIVE
+};
+
+/*
+ * Compile flags
+ */
+
+enum sieve_compile_flags {
+	/* No global extensions are allowed
+	 *  (as marked by sieve_global_extensions setting)
+	 */
+	SIEVE_COMPILE_FLAG_NOGLOBAL = (1<<0),
+	/* Script is being uploaded (usually through ManageSieve) */
+	SIEVE_COMPILE_FLAG_UPLOADED = (1<<1),
+	/* Script is being activated (usually through ManageSieve) */
+	SIEVE_COMPILE_FLAG_ACTIVATED = (1<<2),
+	/* Compiled for environment with no access to envelope */
+	SIEVE_COMPILE_FLAG_NO_ENVELOPE = (1<<3)
+};
+
+/*
+ * Message data
+ *
+ * - The mail message + envelope data
+ */
+
+struct sieve_message_data {
+	struct mail *mail;
+
+	const char *auth_user;
+	const char *id;
+
+	struct {
+		const struct smtp_address *mail_from;
+		const struct smtp_params_mail *mail_params;
+
+		const struct smtp_address *rcpt_to;
+		const struct smtp_params_rcpt *rcpt_params;
+	} envelope;
+};
+
+/*
+ * Runtime flags
+ */
+
+enum sieve_execute_flags {
+	/* No global extensions are allowed
+	 *  (as marked by sieve_global_extensions setting)
+	 */
+	SIEVE_EXECUTE_FLAG_NOGLOBAL = (1<<0),
+	/* Do not execute (implicit keep) at the end */
+	SIEVE_EXECUTE_FLAG_DEFER_KEEP = (1<<1),
+	/* There is no envelope */
+	SIEVE_EXECUTE_FLAG_NO_ENVELOPE = (1<<2),
+	/* Skip sending responses */
+	SIEVE_EXECUTE_FLAG_SKIP_RESPONSES = (1<<3),
+};
+
+/*
+ * Runtime trace settings
+ */
+
+typedef enum {
+	SIEVE_TRLVL_NONE = 0,
+	SIEVE_TRLVL_ACTIONS,
+	SIEVE_TRLVL_COMMANDS,
+	SIEVE_TRLVL_TESTS,
+	SIEVE_TRLVL_MATCHING
+} sieve_trace_level_t;
+
+enum {
+	SIEVE_TRFLG_DEBUG = (1 << 0),
+	SIEVE_TRFLG_ADDRESSES = (1 << 1)
+};
+
+struct sieve_trace_config {
+	sieve_trace_level_t level;
+	unsigned int flags;
+};
+
+/*
+ * Script environment
+ *
+ * - Environment for currently executing script
+ */
+
+struct sieve_script_env {
+	/* Mail-related */
+	struct mail_user *user;
+	const struct message_address *postmaster_address;
+	const char *default_mailbox;
+	bool mailbox_autocreate;
+	bool mailbox_autosubscribe;
+
+	/* External context data */
+
+	void *script_context;
+
+	/* Callbacks */
+
+	/* Interface for sending mail */
+	void *(*smtp_start)
+		(const struct sieve_script_env *senv,
+			const struct smtp_address *mail_from);
+	/* Add a new recipient */
+	void (*smtp_add_rcpt)	
+		(const struct sieve_script_env *senv, void *handle,
+			const struct smtp_address *rcpt_to);
+	/* Get an output stream where the message can be written to. The recipients
+	   must already be added before calling this. */
+	struct ostream *(*smtp_send)
+		(const struct sieve_script_env *senv, void *handle);
+	/* Abort the SMTP transaction after smtp_send() is already issued */
+	void (*smtp_abort)
+		(const struct sieve_script_env *senv, void *handle);
+	/* Returns 1 on success, 0 on permanent failure, -1 on temporary failure. */
+	int (*smtp_finish)
+		(const struct sieve_script_env *senv, void *handle,
+			const char **error_r);
+
+	/* Interface for marking and checking duplicates */
+	bool (*duplicate_check)
+		(const struct sieve_script_env *senv, const void *id, size_t id_size);
+	void (*duplicate_mark)
+		(const struct sieve_script_env *senv, const void *id, size_t id_size,
+			time_t time);
+	void (*duplicate_flush)
+		(const struct sieve_script_env *senv);
+
+	/* Interface for rejecting mail */
+	int (*reject_mail)(const struct sieve_script_env *senv,
+		const struct smtp_address *recipient, const char *reason);
+
+	/* Execution status record */
+	struct sieve_exec_status *exec_status;
+
+	/* Runtime trace*/
+	struct sieve_trace_log *trace_log;
+	struct sieve_trace_config trace_config;
+};
+
+#define SIEVE_SCRIPT_DEFAULT_MAILBOX(senv) \
+	(senv->default_mailbox == NULL ? "INBOX" : senv->default_mailbox )
+
+/*
+ * Script execution status
+ */
+
+struct sieve_exec_status {
+	struct mail_storage *last_storage;
+
+	bool message_saved:1;
+	bool message_forwarded:1;
+	bool tried_default_save:1;
+	bool keep_original:1;
+	bool store_failed:1;
+};
+
+/*
+ * Execution exit codes
+ */
+
+enum sieve_execution_exitcode {
+	SIEVE_EXEC_OK           = 1,
+	SIEVE_EXEC_FAILURE      = 0,
+	SIEVE_EXEC_TEMP_FAILURE = -1,
+	SIEVE_EXEC_BIN_CORRUPT  = -2,
+	SIEVE_EXEC_KEEP_FAILED  = -3
+};
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-validator.c
@@ -0,0 +1,1582 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "array.h"
+#include "buffer.h"
+#include "mempool.h"
+#include "hash.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-script.h"
+#include "sieve-ast.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+
+#include "sieve-comparators.h"
+#include "sieve-address-parts.h"
+
+/*
+ * Forward declarations
+ */
+
+static void sieve_validator_register_core_commands
+	(struct sieve_validator *valdtr);
+static void sieve_validator_register_core_tests
+	(struct sieve_validator *valdtr);
+
+/*
+ * Types
+ */
+
+/* Tag registration */
+
+struct sieve_tag_registration {
+	const struct sieve_argument_def *tag_def;
+	const struct sieve_extension *ext;
+
+	const char *identifier;
+	int id_code;
+};
+
+/* Command registration */
+
+struct sieve_command_registration {
+	const struct sieve_command_def *cmd_def;
+	const struct sieve_extension *ext;
+
+	ARRAY(struct sieve_tag_registration *) normal_tags;
+	ARRAY(struct sieve_tag_registration *) instanced_tags;
+	ARRAY(struct sieve_tag_registration *) persistent_tags;
+};
+
+/* Default (literal) arguments */
+
+struct sieve_default_argument {
+	const struct sieve_argument_def *arg_def;
+	const struct sieve_extension *ext;
+
+	struct sieve_default_argument *overrides;
+};
+
+/*
+ * Validator extension
+ */
+
+struct sieve_validator_extension_reg {
+	const struct sieve_validator_extension *valext;
+	const struct sieve_extension *ext;
+	struct sieve_ast_argument *arg;
+	void *context;
+
+	bool loaded:1;
+	bool required:1;
+};
+
+/*
+ * Validator
+ */
+
+struct sieve_validator {
+	pool_t pool;
+
+	struct sieve_instance *svinst;
+	struct sieve_ast *ast;
+	struct sieve_script *script;
+	enum sieve_compile_flags flags;
+
+	struct sieve_error_handler *ehandler;
+
+	bool finished_require;
+
+	/* Registries */
+
+	HASH_TABLE(const char *, struct sieve_command_registration *) commands;
+
+	ARRAY(struct sieve_validator_extension_reg) extensions;
+
+	/* This is currently a wee bit ugly and needs more thought */
+	struct sieve_default_argument default_arguments[SAT_COUNT];
+
+	/* Default argument processing state (FIXME: ugly) */
+	struct sieve_default_argument *current_defarg;
+	enum sieve_argument_type current_defarg_type;
+	bool current_defarg_constant;
+};
+
+/*
+ * Error handling
+ */
+
+void sieve_validator_warning
+(struct sieve_validator *valdtr, unsigned int source_line,
+	const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	sieve_vwarning(valdtr->ehandler,
+		sieve_error_script_location(valdtr->script, source_line),
+		fmt, args);
+	va_end(args);
+
+}
+
+void sieve_validator_error
+(struct sieve_validator *valdtr, unsigned int source_line,
+	const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	sieve_verror(valdtr->ehandler,
+		sieve_error_script_location(valdtr->script, source_line),
+		fmt, args);
+	va_end(args);
+}
+
+/*
+ * Validator object
+ */
+
+struct sieve_validator *sieve_validator_create
+(struct sieve_ast *ast, struct sieve_error_handler *ehandler,
+	enum sieve_compile_flags flags)
+{
+	pool_t pool;
+	struct sieve_validator *valdtr;
+	const struct sieve_extension *const *ext_preloaded;
+	unsigned int i, ext_count;
+
+	pool = pool_alloconly_create("sieve_validator", 16384);
+	valdtr = p_new(pool, struct sieve_validator, 1);
+	valdtr->pool = pool;
+
+	valdtr->ehandler = ehandler;
+	sieve_error_handler_ref(ehandler);
+
+	valdtr->ast = ast;
+	sieve_ast_ref(ast);
+
+	valdtr->script = sieve_ast_script(ast);
+	valdtr->svinst = sieve_script_svinst(valdtr->script);
+	valdtr->flags = flags;
+
+	/* Setup default arguments */
+	valdtr->default_arguments[SAT_NUMBER].arg_def = &number_argument;
+	valdtr->default_arguments[SAT_NUMBER].ext = NULL;
+	valdtr->default_arguments[SAT_VAR_STRING].arg_def = &string_argument;
+	valdtr->default_arguments[SAT_VAR_STRING].ext = NULL;
+	valdtr->default_arguments[SAT_CONST_STRING].arg_def = &string_argument;
+	valdtr->default_arguments[SAT_CONST_STRING].ext = NULL;
+	valdtr->default_arguments[SAT_STRING_LIST].arg_def = &string_list_argument;
+	valdtr->default_arguments[SAT_STRING_LIST].ext = NULL;
+
+	/* Setup storage for extension contexts */
+	p_array_init(&valdtr->extensions, pool,
+		sieve_extensions_get_count(valdtr->svinst));
+
+	/* Setup command registry */
+	hash_table_create
+		(&valdtr->commands, pool, 0, strcase_hash, strcasecmp);
+	sieve_validator_register_core_commands(valdtr);
+	sieve_validator_register_core_tests(valdtr);
+
+	/* Pre-load core language features implemented as 'extensions' */
+	ext_preloaded = sieve_extensions_get_preloaded(valdtr->svinst, &ext_count);
+	for ( i = 0; i < ext_count; i++ ) {
+		const struct sieve_extension_def *ext_def = ext_preloaded[i]->def;
+
+		if ( ext_def != NULL && ext_def->validator_load != NULL )
+			(void)ext_def->validator_load(ext_preloaded[i], valdtr);
+	}
+
+	return valdtr;
+}
+
+void sieve_validator_free(struct sieve_validator **valdtr)
+{
+	const struct sieve_validator_extension_reg *extrs;
+	unsigned int ext_count, i;
+
+	hash_table_destroy(&(*valdtr)->commands);
+	sieve_ast_unref(&(*valdtr)->ast);
+
+	sieve_error_handler_unref(&(*valdtr)->ehandler);
+
+	/* Signal registered extensions that the validator is being destroyed */
+	extrs = array_get(&(*valdtr)->extensions, &ext_count);
+	for ( i = 0; i < ext_count; i++ ) {
+		if ( extrs[i].valext != NULL && extrs[i].valext->free != NULL )
+			extrs[i].valext->free(extrs[i].ext, *valdtr, extrs[i].context);
+	}
+
+	pool_unref(&(*valdtr)->pool);
+
+	*valdtr = NULL;
+}
+
+/*
+ * Accessors
+ */
+
+// FIXME: build validate environment
+
+pool_t sieve_validator_pool(struct sieve_validator *valdtr)
+{
+	return valdtr->pool;
+}
+
+struct sieve_error_handler *sieve_validator_error_handler
+(struct sieve_validator *valdtr)
+{
+	return valdtr->ehandler;
+}
+
+struct sieve_ast *sieve_validator_ast
+(struct sieve_validator *valdtr)
+{
+	return valdtr->ast;
+}
+
+struct sieve_script *sieve_validator_script
+(struct sieve_validator *valdtr)
+{
+	return valdtr->script;
+}
+
+struct sieve_instance *sieve_validator_svinst
+(struct sieve_validator *valdtr)
+{
+	return valdtr->svinst;
+}
+
+enum sieve_compile_flags sieve_validator_compile_flags
+	(struct sieve_validator *valdtr)
+{
+	return valdtr->flags;
+}
+
+/*
+ * Command registry
+ */
+
+/* Dummy command object to mark unknown commands in the registry */
+
+static bool _cmd_unknown_validate
+(struct sieve_validator *valdtr ATTR_UNUSED,
+	struct sieve_command *cmd ATTR_UNUSED)
+{
+	i_unreached();
+	return FALSE;
+}
+
+static const struct sieve_command_def unknown_command = {
+	.identifier = "",
+	.type = SCT_NONE,
+	.positional_args = 0,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = _cmd_unknown_validate
+};
+
+/* Registration of the core commands of the language */
+
+static void sieve_validator_register_core_tests
+(struct sieve_validator *valdtr)
+{
+	unsigned int i;
+
+	for ( i = 0; i < sieve_core_tests_count; i++ ) {
+		sieve_validator_register_command(valdtr, NULL, sieve_core_tests[i]);
+	}
+}
+
+static void sieve_validator_register_core_commands
+(struct sieve_validator *valdtr)
+{
+	unsigned int i;
+
+	for ( i = 0; i < sieve_core_commands_count; i++ ) {
+		sieve_validator_register_command(valdtr, NULL, sieve_core_commands[i]);
+	}
+}
+
+/* Registry functions */
+
+static struct sieve_command_registration *
+sieve_validator_find_command_registration
+(struct sieve_validator *valdtr, const char *command)
+{
+	return hash_table_lookup(valdtr->commands, command);
+}
+
+static struct sieve_command_registration *_sieve_validator_register_command
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	const struct sieve_command_def *cmd_def, const char *identifier)
+{
+	struct sieve_command_registration *cmd_reg =
+		p_new(valdtr->pool, struct sieve_command_registration, 1);
+
+	cmd_reg->cmd_def = cmd_def;
+	cmd_reg->ext = ext;
+
+	hash_table_insert(valdtr->commands, identifier, cmd_reg);
+
+	return cmd_reg;
+}
+
+void sieve_validator_register_command
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	const struct sieve_command_def *cmd_def)
+{
+	struct sieve_command_registration *cmd_reg =
+		sieve_validator_find_command_registration(valdtr, cmd_def->identifier);
+
+	if ( cmd_reg == NULL )
+		cmd_reg = _sieve_validator_register_command
+			(valdtr, ext, cmd_def, cmd_def->identifier);
+	else {
+		cmd_reg->cmd_def = cmd_def;
+		cmd_reg->ext = ext;
+	}
+
+	if ( cmd_def->registered != NULL )
+		cmd_def->registered(valdtr, ext, cmd_reg);
+}
+
+static void sieve_validator_register_unknown_command
+(struct sieve_validator *valdtr, const char *command)
+{
+	struct sieve_command_registration *cmd_reg =
+		sieve_validator_find_command_registration(valdtr, command);
+
+	if (cmd_reg == NULL) {
+		(void)_sieve_validator_register_command
+			(valdtr, NULL, &unknown_command, command);
+	} else {
+		i_assert(cmd_reg->cmd_def == NULL);
+		cmd_reg->cmd_def = &unknown_command;
+	}
+}
+
+/*const struct sieve_command *sieve_validator_find_command
+(struct sieve_validator *valdtr, const char *command)
+{
+  struct sieve_command_registration *cmd_reg =
+  	sieve_validator_find_command_registration(valdtr, command);
+
+  return ( record == NULL ? NULL : record->command );
+}*/
+
+/*
+ * Per-command tagged argument registry
+ */
+
+/* Dummy argument object to mark unknown arguments in the registry */
+
+static bool _unknown_tag_validate
+(struct sieve_validator *valdtr ATTR_UNUSED,
+	struct sieve_ast_argument **arg ATTR_UNUSED,
+	struct sieve_command *tst ATTR_UNUSED)
+{
+	i_unreached();
+	return FALSE;
+}
+
+static const struct sieve_argument_def _unknown_tag = {
+	.identifier = "",
+	.validate = _unknown_tag_validate,
+};
+
+static inline bool _tag_registration_is_unknown
+(struct sieve_tag_registration *tag_reg)
+{
+	return ( tag_reg != NULL && tag_reg->tag_def == &_unknown_tag );
+}
+
+/* Registry functions */
+
+static void _sieve_validator_register_tag
+(struct sieve_validator *valdtr, struct sieve_command_registration *cmd_reg,
+	const struct sieve_extension *ext, const struct sieve_argument_def *tag_def,
+	const char *identifier, int id_code)
+{
+	struct sieve_tag_registration *reg;
+
+	reg = p_new(valdtr->pool, struct sieve_tag_registration, 1);
+	reg->ext = ext;
+	reg->tag_def = tag_def;
+	reg->id_code = id_code;
+	if ( identifier == NULL )
+		reg->identifier = tag_def->identifier;
+	else
+		reg->identifier = p_strdup(valdtr->pool, identifier);
+
+	if ( !array_is_created(&cmd_reg->normal_tags) )
+		p_array_init(&cmd_reg->normal_tags, valdtr->pool, 4);
+
+	array_append(&cmd_reg->normal_tags, &reg, 1);
+}
+
+void sieve_validator_register_persistent_tag
+(struct sieve_validator *valdtr, const char *command,
+	const struct sieve_extension *ext, const struct sieve_argument_def *tag_def)
+{
+	/* Add the tag to the persistent tags list if necessary */
+	if ( tag_def->validate_persistent != NULL ) {
+		struct sieve_command_registration *cmd_reg =
+			sieve_validator_find_command_registration(valdtr, command);
+
+		if ( cmd_reg == NULL ) {
+			cmd_reg = _sieve_validator_register_command(valdtr, NULL, NULL, command);
+		}
+
+		struct sieve_tag_registration *reg;
+
+		if ( !array_is_created(&cmd_reg->persistent_tags) )
+			p_array_init(&cmd_reg->persistent_tags, valdtr->pool, 4);
+		else {
+			struct sieve_tag_registration *const *reg_idx;
+
+			/* Avoid dupplicate registration */
+			array_foreach (&cmd_reg->persistent_tags, reg_idx) {
+				if ((*reg_idx)->tag_def == tag_def)
+					return;
+			}
+		}
+
+		reg = p_new(valdtr->pool, struct sieve_tag_registration, 1);
+		reg->ext = ext;
+		reg->tag_def = tag_def;
+		reg->id_code = -1;
+
+		array_append(&cmd_reg->persistent_tags, &reg, 1);
+	}
+}
+
+void sieve_validator_register_external_tag
+(struct sieve_validator *valdtr, const char *command,
+	const struct sieve_extension *ext, const struct sieve_argument_def *tag_def,
+	int id_code)
+{
+	struct sieve_command_registration *cmd_reg =
+		sieve_validator_find_command_registration(valdtr, command);
+
+	if ( cmd_reg == NULL ) {
+		cmd_reg = _sieve_validator_register_command(valdtr, NULL, NULL, command);
+	}
+
+	_sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, tag_def, NULL, id_code);
+}
+
+void sieve_validator_register_tag
+(struct sieve_validator *valdtr, struct sieve_command_registration *cmd_reg,
+	const struct sieve_extension *ext, const struct sieve_argument_def *tag_def,
+	int id_code)
+{
+	if ( tag_def->is_instance_of == NULL )
+		_sieve_validator_register_tag(valdtr, cmd_reg, ext, tag_def, NULL, id_code);
+	else {
+		struct sieve_tag_registration *reg =
+			p_new(valdtr->pool, struct sieve_tag_registration, 1);
+		reg->ext = ext;
+		reg->tag_def = tag_def;
+		reg->id_code = id_code;
+
+		if ( !array_is_created(&cmd_reg->instanced_tags) )
+				p_array_init(&cmd_reg->instanced_tags, valdtr->pool, 4);
+
+		array_append(&cmd_reg->instanced_tags, &reg, 1);
+	}
+}
+
+static void sieve_validator_register_unknown_tag
+(struct sieve_validator *valdtr, struct sieve_command_registration *cmd_reg,
+	const char *tag)
+{
+	_sieve_validator_register_tag(valdtr, cmd_reg, NULL, &_unknown_tag, tag, 0);
+}
+
+static struct sieve_tag_registration *_sieve_validator_command_tag_get
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+	const char *tag, void **data)
+{
+	struct sieve_command_registration *cmd_reg = cmd->reg;
+	struct sieve_tag_registration * const *regs;
+	unsigned int i, reg_count;
+
+	/* First check normal tags */
+	if ( array_is_created(&cmd_reg->normal_tags) ) {
+		regs = array_get(&cmd_reg->normal_tags, &reg_count);
+
+		for ( i = 0; i < reg_count; i++ ) {
+			if ( regs[i]->tag_def != NULL &&
+				strcasecmp(regs[i]->identifier, tag) == 0) {
+
+				return regs[i];
+			}
+		}
+	}
+
+	/* Not found so far, try the instanced tags */
+	if ( array_is_created(&cmd_reg->instanced_tags) ) {
+		regs = array_get(&cmd_reg->instanced_tags, &reg_count);
+
+		for ( i = 0; i < reg_count; i++ ) {
+			if ( regs[i]->tag_def != NULL ) {
+				if ( regs[i]->tag_def->is_instance_of
+					(valdtr, cmd, regs[i]->ext, tag, data) )
+					return regs[i];
+			}
+		}
+	}
+
+	return NULL;
+}
+
+static bool sieve_validator_command_tag_exists
+(struct sieve_validator *valdtr, struct sieve_command *cmd, const char *tag)
+{
+	return ( _sieve_validator_command_tag_get(valdtr, cmd, tag, NULL) != NULL );
+}
+
+static struct sieve_tag_registration *sieve_validator_command_tag_get
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+	struct sieve_ast_argument *arg, void **data)
+{
+	const char *tag = sieve_ast_argument_tag(arg);
+
+	return _sieve_validator_command_tag_get(valdtr, cmd, tag, data);
+}
+
+/*
+ * Extension support
+ */
+
+static bool sieve_validator_extensions_check_conficts
+(struct sieve_validator *valdtr,
+	struct sieve_ast_argument *ext_arg,
+	const struct sieve_extension *ext)
+{
+	struct sieve_validator_extension_reg *ext_reg;
+	struct sieve_validator_extension_reg *regs;
+	unsigned int count, i;
+
+	if ( ext->id < 0 )
+		return TRUE;
+
+	ext_reg = array_idx_get_space
+		(&valdtr->extensions, (unsigned int) ext->id);
+
+	regs = array_get_modifiable(&valdtr->extensions, &count);
+	for ( i = 0; i < count; i++ ) {
+		bool required = ext_reg->required && regs[i].required;
+
+		if (regs[i].ext == NULL)
+			continue;
+		if (regs[i].ext == ext)
+			continue;
+		if (!regs[i].loaded)
+			continue;		
+
+		/* Check this extension vs other extension */
+		if ( ext_reg->valext != NULL &&
+			ext_reg->valext->check_conflict != NULL ) {
+			struct sieve_ast_argument *this_ext_arg =
+				(ext_arg == NULL ? regs[i].arg : ext_arg);
+
+			if ( !ext_reg->valext->check_conflict(ext,
+				valdtr, ext_reg->context, this_ext_arg,
+				regs[i].ext, required) )
+				return FALSE;
+		}
+
+		/* Check other extension vs this extension */
+		if ( regs[i].valext != NULL &&
+			regs[i].valext->check_conflict != NULL ) {
+			if ( !regs[i].valext->check_conflict(regs[i].ext,
+				valdtr, regs[i].context, regs[i].arg, ext,
+				required) )
+				return FALSE;
+		}
+	}
+	return TRUE;
+}
+
+bool sieve_validator_extension_load
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+	struct sieve_ast_argument *ext_arg,
+	const struct sieve_extension *ext,
+	bool required)
+{
+	const struct sieve_extension_def *extdef = ext->def;
+	struct sieve_validator_extension_reg *reg = NULL;
+
+	if ( ext->global && (valdtr->flags & SIEVE_COMPILE_FLAG_NOGLOBAL) != 0 ) {
+		const char *cmd_prefix = (cmd == NULL ? "" :
+			t_strdup_printf("%s %s: ", sieve_command_identifier(cmd),
+				sieve_command_type_name(cmd)));
+		sieve_argument_validate_error(valdtr, ext_arg,
+			"%sfailed to load Sieve capability `%s': "
+			"its use is restricted to global scripts",
+			cmd_prefix, sieve_extension_name(ext));
+		return FALSE;
+	}
+
+	/* Register extension no matter what and store the
+	 * AST argument registering it */
+	if ( ext->id >= 0 ) {
+		reg = array_idx_get_space
+			(&valdtr->extensions, (unsigned int) ext->id);
+		i_assert(reg->ext == NULL || reg->ext == ext);
+		reg->ext = ext;
+		reg->required = reg->required || required;
+		if ( reg->arg == NULL )
+			reg->arg = ext_arg;
+	}
+
+	if ( extdef->validator_load != NULL &&
+		!extdef->validator_load(ext, valdtr) ) {
+		const char *cmd_prefix = (cmd == NULL ? "" :
+			t_strdup_printf("%s %s: ", sieve_command_identifier(cmd),
+				sieve_command_type_name(cmd)));
+		sieve_argument_validate_error(valdtr, ext_arg,
+			"%sfailed to load Sieve capability `%s'",
+			cmd_prefix, sieve_extension_name(ext));
+		return FALSE;
+	}
+
+	/* Check conflicts with other extensions */
+	if ( !sieve_validator_extensions_check_conficts
+		(valdtr, ext_arg, ext) )
+		return FALSE;
+
+	/* Link extension to AST for use at code generation */
+	if ( reg != NULL ) {
+		sieve_ast_extension_link(valdtr->ast, ext, reg->required);
+		reg->loaded = TRUE;
+	}
+
+	return TRUE;
+}
+
+const struct sieve_extension *sieve_validator_extension_load_by_name
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+	struct sieve_ast_argument *ext_arg, const char *ext_name)
+{
+	const struct sieve_extension *ext;
+
+	ext = sieve_extension_get_by_name(valdtr->svinst, ext_name);
+
+	if ( ext == NULL || ext->def == NULL || !ext->enabled ) {
+		unsigned int i;
+		bool core_test = FALSE;
+		bool core_command = FALSE;
+
+		for ( i = 0; !core_command && i < sieve_core_commands_count; i++ ) {
+			if ( strcasecmp(sieve_core_commands[i]->identifier, ext_name) == 0 )
+				core_command = TRUE;
+		}
+
+		for ( i = 0; !core_test && i < sieve_core_tests_count; i++ ) {
+			if ( strcasecmp(sieve_core_tests[i]->identifier, ext_name) == 0 )
+				core_test = TRUE;
+		}
+
+		if ( core_test || core_command ) {
+			sieve_argument_validate_error(valdtr, ext_arg,
+				"%s %s: `%s' is not known as a Sieve capability, "
+				"but it is known as a Sieve %s that is always available",
+				sieve_command_identifier(cmd), sieve_command_type_name(cmd),
+				str_sanitize(ext_name, 128), ( core_test ? "test" : "command" ));
+		} else {
+			sieve_argument_validate_error(valdtr, ext_arg,
+				"%s %s: unknown Sieve capability `%s'",
+				sieve_command_identifier(cmd), sieve_command_type_name(cmd),
+				str_sanitize(ext_name, 128));
+		}
+		return NULL;
+	}
+
+	if ( !sieve_validator_extension_load
+		(valdtr, cmd, ext_arg, ext, TRUE) )
+		return NULL;
+
+	return ext;
+}
+
+const struct sieve_extension *sieve_validator_extension_load_implicit
+(struct sieve_validator *valdtr, const char *ext_name)
+{
+	const struct sieve_extension *ext;
+
+	ext = sieve_extension_get_by_name(valdtr->svinst, ext_name);
+
+	if ( ext == NULL || ext->def == NULL )
+		return NULL;
+
+	if ( !sieve_validator_extension_load
+		(valdtr, NULL, NULL, ext, TRUE) )
+		return NULL;
+
+	return ext;
+}
+
+void sieve_validator_extension_register
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	const struct sieve_validator_extension *valext, void *context)
+{
+	struct sieve_validator_extension_reg *reg;
+
+	if ( ext->id < 0 ) return;
+
+	reg = array_idx_get_space(&valdtr->extensions, (unsigned int) ext->id);
+	i_assert(reg->ext == NULL || reg->ext == ext);
+	reg->ext = ext;
+	reg->valext = valext;
+	reg->context = context;
+}
+
+bool sieve_validator_extension_loaded
+(struct sieve_validator *valdtr, const struct sieve_extension *ext)
+{
+	const struct sieve_validator_extension_reg *reg;
+
+	if ( ext->id < 0 || ext->id >= (int) array_count(&valdtr->extensions))
+		return FALSE;
+
+	reg = array_idx(&valdtr->extensions, (unsigned int) ext->id);
+
+	return ( reg->loaded );
+}
+
+void sieve_validator_extension_set_context
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	void *context)
+{
+	struct sieve_validator_extension_reg *reg;
+
+	if ( ext->id < 0 ) return;
+
+	reg = array_idx_get_space(&valdtr->extensions, (unsigned int) ext->id);
+	reg->context = context;
+}
+
+void *sieve_validator_extension_get_context
+(struct sieve_validator *valdtr, const struct sieve_extension *ext)
+{
+	const struct sieve_validator_extension_reg *reg;
+
+	if  ( ext->id < 0 || ext->id >= (int) array_count(&valdtr->extensions) )
+		return NULL;
+
+	reg = array_idx(&valdtr->extensions, (unsigned int) ext->id);
+
+	return reg->context;
+}
+
+/*
+ * Overriding the default literal arguments
+ */
+
+void sieve_validator_argument_override
+(struct sieve_validator *valdtr, enum sieve_argument_type type,
+	const struct sieve_extension *ext, const struct sieve_argument_def *arg_def)
+{
+	struct sieve_default_argument *arg;
+
+	if ( valdtr->default_arguments[type].arg_def != NULL ) {
+		arg = p_new(valdtr->pool, struct sieve_default_argument, 1);
+		*arg = valdtr->default_arguments[type];
+
+		valdtr->default_arguments[type].overrides = arg;
+	}
+
+	valdtr->default_arguments[type].arg_def = arg_def;
+	valdtr->default_arguments[type].ext = ext;
+}
+
+static bool sieve_validator_argument_default_activate
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+	struct sieve_default_argument *defarg, struct sieve_ast_argument *arg)
+{
+	bool result = TRUE;
+	struct sieve_default_argument *prev_defarg;
+
+	prev_defarg = valdtr->current_defarg;
+	valdtr->current_defarg = defarg;
+
+	if ( arg->argument == NULL ) {
+		arg->argument = sieve_argument_create
+			(arg->ast, defarg->arg_def, defarg->ext, 0);
+	} else {
+		arg->argument->def = defarg->arg_def;
+		arg->argument->ext = defarg->ext;
+	}
+
+	if (defarg->arg_def != NULL && defarg->arg_def->validate != NULL )
+		result = defarg->arg_def->validate(valdtr, &arg, cmd);
+
+	valdtr->current_defarg = prev_defarg;
+
+	return result;
+}
+
+bool sieve_validator_argument_activate_super
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+	struct sieve_ast_argument *arg, bool constant ATTR_UNUSED)
+{
+	struct sieve_default_argument *defarg;
+
+	if ( valdtr->current_defarg == NULL ||
+		valdtr->current_defarg->overrides == NULL )
+		return FALSE;
+
+	if ( valdtr->current_defarg->overrides->arg_def == &string_argument ) {
+		switch ( valdtr->current_defarg_type) {
+		case SAT_CONST_STRING:
+			if ( !valdtr->current_defarg_constant ) {
+				valdtr->current_defarg_type = SAT_VAR_STRING;
+				defarg = &valdtr->default_arguments[SAT_VAR_STRING];
+			} else
+				defarg = valdtr->current_defarg->overrides;
+			break;
+		case SAT_VAR_STRING:
+			defarg = valdtr->current_defarg->overrides;
+			break;
+		default:
+			return FALSE;
+		}
+	} else
+		defarg = valdtr->current_defarg->overrides;
+
+	return sieve_validator_argument_default_activate
+		(valdtr, cmd, defarg, arg);
+}
+
+/*
+ * Argument Validation API
+ */
+
+bool sieve_validator_argument_activate
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+	struct sieve_ast_argument *arg, bool constant)
+{
+	struct sieve_default_argument *defarg;
+
+	switch ( sieve_ast_argument_type(arg) ) {
+	case SAAT_NUMBER:
+		valdtr->current_defarg_type = SAT_NUMBER;
+		break;
+	case SAAT_STRING:
+		valdtr->current_defarg_type = SAT_CONST_STRING;
+		break;
+	case SAAT_STRING_LIST:
+		valdtr->current_defarg_type = SAT_STRING_LIST;
+		break;
+	default:
+		return FALSE;
+	}
+
+	valdtr->current_defarg_constant = constant;
+	defarg = &valdtr->default_arguments[valdtr->current_defarg_type];
+
+	if ( !constant && defarg->arg_def == &string_argument ) {
+		valdtr->current_defarg_type = SAT_VAR_STRING;
+		defarg = &valdtr->default_arguments[SAT_VAR_STRING];
+	}
+
+	return sieve_validator_argument_default_activate(valdtr, cmd, defarg, arg);
+}
+
+bool sieve_validate_positional_argument
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+	struct sieve_ast_argument *arg, const char *arg_name, unsigned int arg_pos,
+	enum sieve_ast_argument_type req_type)
+{
+	i_assert( arg != NULL );
+
+	if ( sieve_ast_argument_type(arg) != req_type &&
+		(sieve_ast_argument_type(arg) != SAAT_STRING ||
+			req_type != SAAT_STRING_LIST) )
+	{
+		sieve_argument_validate_error(valdtr, arg,
+			"the %s %s expects %s as argument %d (%s), but %s was found",
+			sieve_command_identifier(cmd), sieve_command_type_name(cmd),
+			sieve_ast_argument_type_name(req_type),
+			arg_pos, arg_name, sieve_ast_argument_name(arg));
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+bool sieve_validate_tag_parameter
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+	struct sieve_ast_argument *tag, struct sieve_ast_argument *param,
+	const char *arg_name, unsigned int arg_pos,
+	enum sieve_ast_argument_type req_type, bool constant)
+{
+	i_assert(tag != NULL);
+
+	if ( param == NULL ) {
+		const char *position = ( arg_pos == 0 ? "" :
+			t_strdup_printf(" %d (%s)", arg_pos, arg_name) );
+
+		sieve_argument_validate_error(valdtr, tag,
+			"the :%s tag for the %s %s requires %s as parameter%s, "
+			"but no parameters were found", sieve_ast_argument_tag(tag),
+			sieve_command_identifier(cmd), sieve_command_type_name(cmd),
+			sieve_ast_argument_type_name(req_type), position);
+		return FALSE;
+	}
+
+	if ( sieve_ast_argument_type(param) != req_type &&
+		(sieve_ast_argument_type(param) != SAAT_STRING ||
+			req_type != SAAT_STRING_LIST) )
+	{
+		const char *position = ( arg_pos == 0 ? "" :
+			t_strdup_printf(" %d (%s)", arg_pos, arg_name) );
+
+		sieve_argument_validate_error(valdtr, param,
+			"the :%s tag for the %s %s requires %s as parameter%s, "
+			"but %s was found", sieve_ast_argument_tag(tag),
+			sieve_command_identifier(cmd), sieve_command_type_name(cmd),
+			sieve_ast_argument_type_name(req_type),	position,
+			sieve_ast_argument_name(param));
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, cmd, param, constant) )
+		return FALSE;
+
+	param->argument->id_code = tag->argument->id_code;
+
+	return TRUE;
+}
+
+/*
+ * Command argument validation
+ */
+
+static bool sieve_validate_command_arguments
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+	int arg_count = cmd->def->positional_args;
+	int real_count = 0;
+	struct sieve_ast_argument *arg;
+	struct sieve_command_registration *cmd_reg = cmd->reg;
+
+	/* Resolve tagged arguments */
+	arg = sieve_ast_argument_first(cmd->ast_node);
+	while ( arg != NULL ) {
+		void *arg_data = NULL;
+		struct sieve_tag_registration *tag_reg;
+		const struct sieve_argument_def *tag_def;
+
+		if (sieve_ast_argument_type(arg) != SAAT_TAG) {
+			arg = sieve_ast_argument_next(arg);
+			continue;
+		}
+
+		tag_reg = sieve_validator_command_tag_get(valdtr, cmd, arg, &arg_data);
+
+		if ( tag_reg == NULL ) {
+			sieve_argument_validate_error(valdtr, arg,
+				"unknown tagged argument ':%s' for the %s %s "
+				"(reported only once at first occurrence)",
+				sieve_ast_argument_tag(arg), sieve_command_identifier(cmd),
+				sieve_command_type_name(cmd));
+			sieve_validator_register_unknown_tag
+				(valdtr, cmd_reg, sieve_ast_argument_tag(arg));
+			return FALSE;
+		}
+
+		/* Check whether previously tagged as unknown */
+		if ( _tag_registration_is_unknown(tag_reg) )
+			return FALSE;
+
+		tag_def = tag_reg->tag_def;
+
+		/* Assign the tagged argument type to the ast for later reference */
+		arg->argument = sieve_argument_create
+			(arg->ast, tag_def, tag_reg->ext, tag_reg->id_code);
+		arg->argument->data = arg_data;
+
+		arg = sieve_ast_argument_next(arg);
+	}
+
+	/* Validate tagged arguments */
+	arg = sieve_ast_argument_first(cmd->ast_node);
+	while ( arg != NULL && sieve_ast_argument_type(arg) == SAAT_TAG) {
+		const struct sieve_argument_def *tag_def = arg->argument->def;
+		struct sieve_ast_argument *parg;
+
+		/* Scan backwards for any duplicates */
+		if ( (tag_def->flags & SIEVE_ARGUMENT_FLAG_MULTIPLE) == 0 ) {
+			parg = sieve_ast_argument_prev(arg);
+			while ( parg != NULL ) {
+				if ( (sieve_ast_argument_type(parg) == SAAT_TAG &&
+						parg->argument->def == tag_def)
+					|| (arg->argument->id_code > 0 && parg->argument != NULL &&
+						parg->argument->id_code == arg->argument->id_code) )
+				{
+					const char *tag_id = sieve_ast_argument_tag(arg);
+					const char *tag_desc =
+						strcmp(tag_def->identifier, tag_id) != 0 ?
+						t_strdup_printf("%s argument (:%s)", tag_def->identifier, tag_id) :
+						t_strdup_printf(":%s argument", tag_def->identifier);
+
+					sieve_argument_validate_error(valdtr, arg,
+						"encountered duplicate %s for the %s %s",
+						tag_desc, sieve_command_identifier(cmd),
+						sieve_command_type_name(cmd));
+
+					return FALSE;
+				}
+
+				parg = sieve_ast_argument_prev(parg);
+			}
+		}
+
+		/* Call the validation function for the tag (if present)
+		 *   Fail if the validation fails:
+		 *     Let's not whine multiple	times about a single command having multiple
+		 *     bad arguments...
+		 */
+		if ( tag_def->validate != NULL ) {
+			if ( !tag_def->validate(valdtr, &arg, cmd) )
+				return FALSE;
+		} else {
+			arg = sieve_ast_argument_next(arg);
+		}
+	}
+
+	/* Remaining arguments should be positional (tags are not allowed here) */
+	cmd->first_positional = arg;
+
+	while ( arg != NULL ) {
+		if ( sieve_ast_argument_type(arg) == SAAT_TAG ) {
+			sieve_argument_validate_error(valdtr, arg,
+				"encountered an unexpected tagged argument ':%s' "
+				"while validating positional arguments for the %s %s",
+				sieve_ast_argument_tag(arg), sieve_command_identifier(cmd),
+				sieve_command_type_name(cmd));
+			return FALSE;
+		}
+
+		real_count++;
+
+		arg = sieve_ast_argument_next(arg);
+	}
+
+	/* Check the required count versus the real number of arguments */
+	if ( arg_count >= 0 && real_count != arg_count ) {
+		sieve_command_validate_error(valdtr, cmd,
+			"the %s %s requires %d positional argument(s), but %d is/are specified",
+			sieve_command_identifier(cmd), sieve_command_type_name(cmd),
+			arg_count, real_count);
+		return FALSE;
+	}
+
+	/* Call initial validation for persistent arguments */
+	if ( array_is_created(&cmd_reg->persistent_tags) ) {
+		struct sieve_tag_registration * const *regs;
+		unsigned int i, reg_count;
+
+  	regs = array_get(&cmd_reg->persistent_tags, &reg_count);
+		for ( i = 0; i < reg_count; i++ ) {
+
+			const struct sieve_argument_def *tag_def = regs[i]->tag_def;
+
+			if ( tag_def != NULL && tag_def->validate_persistent != NULL ) {
+				/* To be sure */
+				if ( !tag_def->validate_persistent(valdtr, cmd, regs[i]->ext) )
+	  				return FALSE;
+			}
+		}
+	}
+
+	return TRUE;
+}
+
+static bool sieve_validate_arguments_context
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *arg =
+		sieve_command_first_argument(cmd);
+
+	while ( arg != NULL ) {
+		const struct sieve_argument *argument = arg->argument;
+
+		if ( argument != NULL && argument->def != NULL &&
+			argument->def->validate_context != NULL ) {
+
+			if ( !argument->def->validate_context(valdtr, arg, cmd) )
+				return FALSE;
+		}
+
+		arg = sieve_ast_argument_next(arg);
+	}
+
+	return TRUE;
+}
+
+/*
+ * Command Validation API
+ */
+
+static bool sieve_validate_command_subtests
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+	const unsigned int count)
+{
+	switch ( count ) {
+
+	case 0:
+	 	if ( sieve_ast_test_count(cmd->ast_node) > 0 ) {
+			/* Unexpected command specified */
+			enum sieve_command_type ctype = SCT_NONE;
+			struct sieve_command_registration *cmd_reg;
+			struct sieve_ast_node *test = sieve_ast_test_first(cmd->ast_node);
+
+			cmd_reg = sieve_validator_find_command_registration
+				(valdtr, test->identifier);
+
+			/* First check what we are dealing with */
+			if ( cmd_reg != NULL && cmd_reg->cmd_def != NULL )
+				ctype = cmd_reg->cmd_def->type;
+
+			switch ( ctype ) {
+			case SCT_TEST: /* Spurious test */
+			case SCT_HYBRID:
+				sieve_command_validate_error(valdtr, cmd,
+					"the %s %s accepts no sub-tests, but tests are specified",
+					sieve_command_identifier(cmd), sieve_command_type_name(cmd));
+				break;
+
+			case SCT_NONE: /* Unknown command */
+
+				/* Is it perhaps a tag for which the ':' was omitted ? */
+				if ( sieve_validator_command_tag_exists
+					(valdtr, cmd, test->identifier) ) {
+					sieve_command_validate_error(valdtr, cmd,
+						"missing colon ':' before ':%s' tag in %s %s", test->identifier,
+						sieve_command_identifier(cmd), sieve_command_type_name(cmd));
+					break;
+				}
+				/* Fall through */
+
+			case SCT_COMMAND:
+				sieve_command_validate_error(valdtr, cmd,
+					"missing semicolon ';' after %s %s",
+					sieve_command_identifier(cmd), sieve_command_type_name(cmd));
+				break;
+			}
+			return FALSE;
+		}
+		break;
+	case 1:
+		if ( sieve_ast_test_count(cmd->ast_node) == 0 ) {
+			sieve_command_validate_error(valdtr, cmd,
+				"the %s %s requires one sub-test, but none is specified",
+				sieve_command_identifier(cmd), sieve_command_type_name(cmd));
+
+			return FALSE;
+
+		} else if ( sieve_ast_test_count(cmd->ast_node) > 1 ||
+			cmd->ast_node->test_list ) {
+
+			sieve_command_validate_error(valdtr, cmd,
+				"the %s %s requires one sub-test, but a list of tests is specified",
+				sieve_command_identifier(cmd), sieve_command_type_name(cmd));
+
+			return FALSE;
+		}
+		break;
+
+	default:
+		if ( sieve_ast_test_count(cmd->ast_node) == 0 ) {
+			sieve_command_validate_error(valdtr, cmd,
+				"the %s %s requires a list of sub-tests, but none is specified",
+				sieve_command_identifier(cmd), sieve_command_type_name(cmd));
+
+			return FALSE;
+
+		} else if ( sieve_ast_test_count(cmd->ast_node) == 1 &&
+			!cmd->ast_node->test_list ) {
+
+			sieve_command_validate_error(valdtr, cmd,
+				"the %s %s requires a list of sub-tests, "
+				"but a single test is specified",
+				sieve_command_identifier(cmd), sieve_command_type_name(cmd) );
+
+			return FALSE;
+		}
+		break;
+	}
+
+	return TRUE;
+}
+
+static bool sieve_validate_command_block
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+	bool block_allowed, bool block_required)
+{
+	i_assert( cmd->ast_node->type == SAT_COMMAND );
+
+	if ( block_required ) {
+		if ( !cmd->ast_node->block ) {
+			sieve_command_validate_error(valdtr, cmd,
+				"the %s command requires a command block, but it is missing",
+				sieve_command_identifier(cmd));
+
+			return FALSE;
+		}
+	} else if ( !block_allowed && cmd->ast_node->block ) {
+		sieve_command_validate_error(valdtr, cmd,
+			"the %s command does not accept a command block, "
+			"but one is specified anyway",
+			sieve_command_identifier(cmd) );
+
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+/*
+ * AST Validation
+ */
+
+static bool sieve_validate_test_list
+	(struct sieve_validator *valdtr, struct sieve_ast_node *test_list,
+		int *const_r);
+static bool sieve_validate_block
+	(struct sieve_validator *valdtr, struct sieve_ast_node *block);
+static bool sieve_validate_command
+	(struct sieve_validator *valdtr, struct sieve_ast_node *cmd_node,
+		int *const_r);
+
+static bool sieve_validate_command_context
+(struct sieve_validator *valdtr, struct sieve_ast_node *cmd_node)
+{
+	enum sieve_ast_type ast_type = sieve_ast_node_type(cmd_node);
+	struct sieve_command_registration *cmd_reg;
+
+	i_assert( ast_type == SAT_TEST || ast_type == SAT_COMMAND );
+
+	/* Verify the command specified by this node */
+	cmd_reg = sieve_validator_find_command_registration
+		(valdtr, cmd_node->identifier);
+
+	if ( cmd_reg != NULL && cmd_reg->cmd_def != NULL ) {
+		const struct sieve_command_def *cmd_def = cmd_reg->cmd_def;
+
+		/* Identifier = "" when the command was previously marked as unknown */
+		if ( *(cmd_def->identifier) != '\0' ) {
+			if ( (cmd_def->type == SCT_COMMAND && ast_type == SAT_TEST)
+				|| (cmd_def->type == SCT_TEST && ast_type == SAT_COMMAND) ) {
+				sieve_validator_error(
+					valdtr, cmd_node->source_line, "attempted to use %s '%s' as %s",
+					sieve_command_def_type_name(cmd_def), cmd_node->identifier,
+					sieve_ast_type_name(ast_type));
+
+			 	return FALSE;
+			}
+
+			cmd_node->command =
+				sieve_command_create(cmd_node, cmd_reg->ext, cmd_def, cmd_reg);
+		} else {
+			return FALSE;
+		}
+
+	}	else {
+		sieve_validator_error(
+			valdtr, cmd_node->source_line,
+			"unknown %s '%s' (only reported once at first occurrence)",
+			sieve_ast_type_name(ast_type), cmd_node->identifier);
+
+		sieve_validator_register_unknown_command(valdtr, cmd_node->identifier);
+
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static bool sieve_validate_command
+(struct sieve_validator *valdtr, struct sieve_ast_node *cmd_node, int *const_r)
+{
+	enum sieve_ast_type ast_type = sieve_ast_node_type(cmd_node);
+	struct sieve_command *cmd = ( cmd_node == NULL ? NULL : cmd_node->command );
+	const struct sieve_command_def *cmd_def = ( cmd != NULL ? cmd->def : NULL );
+	bool result = TRUE;
+
+	i_assert( ast_type == SAT_TEST || ast_type == SAT_COMMAND );
+
+	if ( cmd_def != NULL && *(cmd_def->identifier) != '\0' ) {
+
+		if ( cmd_def->pre_validate == NULL
+			|| cmd_def->pre_validate(valdtr, cmd) ) {
+
+			/* Check argument syntax */
+			if ( !sieve_validate_command_arguments(valdtr, cmd) ) {
+				result = FALSE;
+
+				/* A missing ':' causes a tag to become a test. This can be the cause
+				 * of the arguments validation failing. Therefore we must produce an
+				 * error for the sub-tests as well if appropriate.
+				 */
+				(void)sieve_validate_command_subtests(valdtr, cmd, cmd_def->subtests);
+
+			} else if (
+				!sieve_validate_command_subtests(valdtr, cmd, cmd_def->subtests) ||
+				(ast_type == SAT_COMMAND && !sieve_validate_command_block
+					(valdtr, cmd, cmd_def->block_allowed, cmd_def->block_required)) ) {
+
+				result = FALSE;
+
+			} else {
+				/* Call command validation function if specified */
+				if ( cmd_def->validate != NULL )
+					result = cmd_def->validate(valdtr, cmd) && result;
+			}
+		} else {
+			/* If pre-validation fails, don't bother to validate further
+			 * as context might be missing and doing so is not very useful for
+			 * further error reporting anyway
+			 */
+			return FALSE;
+		}
+
+		result = result && sieve_validate_arguments_context(valdtr, cmd);
+	}
+
+	/*
+	 * Descend further into the AST
+	 */
+
+	if ( cmd_def != NULL ) {
+		/* Tests */
+		if ( cmd_def->subtests > 0 ) {
+			if ( result || sieve_errors_more_allowed(valdtr->ehandler) ) {
+				result = sieve_validate_test_list(valdtr, cmd_node, const_r) && result;
+			}
+		} else if ( result ) {
+			if ( cmd_def->validate_const != NULL ) {
+				(void)cmd_def->validate_const(valdtr, cmd, const_r, -1);
+			} else {
+				*const_r = -1;
+			}
+		}
+
+		/* Skip block if result of test is const FALSE */
+		if ( result && *const_r == 0 )
+			return TRUE;
+
+		/* Command block */
+		if ( cmd_def->block_allowed && ast_type == SAT_COMMAND &&
+			(result || sieve_errors_more_allowed(valdtr->ehandler)) ) {
+			result = sieve_validate_block(valdtr, cmd_node) && result;
+		}
+	}
+
+	return result;
+}
+
+static bool sieve_validate_test_list
+(struct sieve_validator *valdtr, struct sieve_ast_node *test_node,
+	int *const_r)
+{
+	struct sieve_command *tst = test_node->command;
+	const struct sieve_command_def *tst_def = ( tst != NULL ? tst->def : NULL );
+	struct sieve_ast_node *test;
+	bool result = TRUE;
+
+	if ( tst_def != NULL && tst_def->validate_const != NULL ) {
+		if ( !tst_def->validate_const(valdtr, tst, const_r, -2) )
+			return TRUE;
+	}
+
+	test = sieve_ast_test_first(test_node);
+	while ( test != NULL
+		&& (result || sieve_errors_more_allowed(valdtr->ehandler)) ) {
+		int const_value = -2;
+
+		result =
+			sieve_validate_command_context(valdtr, test) &&
+			sieve_validate_command(valdtr, test, &const_value) &&
+			result;
+
+		if ( result ) {
+			if ( tst_def != NULL && tst_def->validate_const != NULL ) {
+				if ( !tst_def->validate_const(valdtr, tst, const_r, const_value) )
+					return TRUE;
+			} else {
+				*const_r = -1;
+			}
+		}
+
+		if ( result && const_value >= 0 )
+			test = sieve_ast_node_detach(test);
+		else
+			test = sieve_ast_test_next(test);
+	}
+
+	return result;
+}
+
+static bool sieve_validate_block
+(struct sieve_validator *valdtr, struct sieve_ast_node *block)
+{
+	bool result = TRUE, fatal = FALSE;
+	struct sieve_ast_node *cmd_node, *next;
+
+	T_BEGIN {
+		cmd_node = sieve_ast_command_first(block);
+		while ( !fatal && cmd_node != NULL
+			&& (result || sieve_errors_more_allowed(valdtr->ehandler)) ) {
+			bool command_success;
+			int const_value = -2;
+
+			next = sieve_ast_command_next(cmd_node);
+
+	 		/* Check if this is the first non-require command */
+			if ( sieve_ast_node_type(block) == SAT_ROOT
+				&& !valdtr->finished_require &&
+				strcasecmp(cmd_node->identifier, cmd_require.identifier) != 0 ) {
+				const struct sieve_validator_extension_reg *extrs;
+				const struct sieve_extension *const *exts;
+				unsigned int ext_count, i;
+
+				valdtr->finished_require = TRUE;
+
+				/* Load implicit extensions */
+				exts = sieve_extensions_get_all(valdtr->svinst, &ext_count);
+				for (i = 0; i < ext_count; i++) {
+					if ( exts[i]->implicit ) {
+						(void)sieve_validator_extension_load
+							(valdtr, NULL, NULL, exts[i], TRUE);
+					}
+				}
+
+				/* Validate all 'require'd extensions */
+				extrs = array_get(&valdtr->extensions, &ext_count);
+				for ( i = 0; i < ext_count; i++ ) {
+					if ( extrs[i].loaded && extrs[i].valext != NULL
+						&& extrs[i].valext->validate != NULL ) {
+
+						if ( !extrs[i].valext->validate(extrs[i].ext,
+							valdtr, extrs[i].context, extrs[i].arg,
+							extrs[i].required) ) {
+							fatal = TRUE;
+							break;
+						}
+					}
+				}
+			}
+
+			command_success = sieve_validate_command_context(valdtr, cmd_node);
+			result = command_success && result;
+
+			result = !fatal && sieve_validate_command(valdtr, cmd_node, &const_value)
+				&& result;
+
+			cmd_node = next;
+		}
+	} T_END;
+
+	return result && !fatal;
+}
+
+bool sieve_validator_run(struct sieve_validator *valdtr)
+{
+	return sieve_validate_block(valdtr, sieve_ast_root(valdtr->ast));
+}
+
+/*
+ * Validator object registry
+ */
+
+struct sieve_validator_object_reg {
+	const struct sieve_object_def *obj_def;
+	const struct sieve_extension *ext;
+};
+
+struct sieve_validator_object_registry {
+	struct sieve_validator *valdtr;
+	ARRAY(struct sieve_validator_object_reg) registrations;
+};
+
+struct sieve_validator_object_registry *sieve_validator_object_registry_get
+(struct sieve_validator *valdtr, const struct sieve_extension *ext)
+{
+	return (struct sieve_validator_object_registry *)
+		sieve_validator_extension_get_context(valdtr, ext);
+}
+
+void sieve_validator_object_registry_add
+(struct sieve_validator_object_registry *regs,
+	const struct sieve_extension *ext, const struct sieve_object_def *obj_def)
+{
+	struct sieve_validator_object_reg *reg;
+
+	reg = array_append_space(&regs->registrations);
+	reg->ext = ext;
+	reg->obj_def = obj_def;
+}
+
+bool sieve_validator_object_registry_find
+(struct sieve_validator_object_registry *regs, const char *identifier,
+	struct sieve_object *obj)
+{
+	unsigned int i;
+
+	for ( i = 0; i < array_count(&regs->registrations); i++ ) {
+		const struct sieve_validator_object_reg *reg =
+			array_idx(&regs->registrations, i);
+
+		if ( strcasecmp(reg->obj_def->identifier, identifier) == 0) {
+			if ( obj != NULL ) {
+				obj->def = reg->obj_def;
+				obj->ext = reg->ext;
+			}
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
+struct sieve_validator_object_registry *sieve_validator_object_registry_create
+(struct sieve_validator *valdtr)
+{
+	pool_t pool = valdtr->pool;
+	struct sieve_validator_object_registry *regs =
+		p_new(pool, struct sieve_validator_object_registry, 1);
+
+	/* Setup registry */
+	p_array_init(&regs->registrations, valdtr->pool, 4);
+
+	regs->valdtr = valdtr;
+
+	return regs;
+}
+
+struct sieve_validator_object_registry *sieve_validator_object_registry_init
+(struct sieve_validator *valdtr, const struct sieve_extension *ext)
+{
+	struct sieve_validator_object_registry *regs =
+		sieve_validator_object_registry_create(valdtr);
+
+	sieve_validator_extension_set_context(valdtr, ext, regs);
+	return regs;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve-validator.h
@@ -0,0 +1,186 @@
+#ifndef SIEVE_VALIDATOR_H
+#define SIEVE_VALIDATOR_H
+
+#include "lib.h"
+
+#include "sieve-common.h"
+
+/*
+ * Types
+ */
+
+enum sieve_argument_type {
+	SAT_NUMBER,
+	SAT_CONST_STRING,
+	SAT_VAR_STRING,
+	SAT_STRING_LIST,
+
+	SAT_COUNT
+};
+
+struct sieve_command_registration;
+
+/*
+ * Validator
+ */
+
+struct sieve_validator;
+
+struct sieve_validator *sieve_validator_create
+	(struct sieve_ast *ast, struct sieve_error_handler *ehandler,
+		enum sieve_compile_flags flags);
+void sieve_validator_free(struct sieve_validator **valdtr);
+pool_t sieve_validator_pool(struct sieve_validator *valdtr);
+
+bool sieve_validator_run(struct sieve_validator *valdtr);
+
+/*
+ * Accessors
+ */
+
+struct sieve_error_handler *sieve_validator_error_handler
+	(struct sieve_validator *valdtr);
+struct sieve_ast *sieve_validator_ast
+	(struct sieve_validator *valdtr);
+struct sieve_script *sieve_validator_script
+	(struct sieve_validator *valdtr);
+struct sieve_instance *sieve_validator_svinst
+	(struct sieve_validator *valdtr);
+enum sieve_compile_flags sieve_validator_compile_flags
+	(struct sieve_validator *valdtr);
+
+/*
+ * Error handling
+ */
+
+void sieve_validator_warning
+	(struct sieve_validator *valdtr, unsigned int source_line,
+		const char *fmt, ...) ATTR_FORMAT(3, 4);
+void sieve_validator_error
+	(struct sieve_validator *valdtr, unsigned int source_line,
+		const char *fmt, ...) ATTR_FORMAT(3, 4);
+void sieve_validator_critical
+	(struct sieve_validator *valdtr, unsigned int source_line,
+		const char *fmt, ...) ATTR_FORMAT(3, 4);
+
+/*
+ * Command/Test registry
+ */
+
+void sieve_validator_register_command
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		const struct sieve_command_def *command);
+
+/*
+ * Per-command tagged argument registry
+ */
+
+void sieve_validator_register_tag
+	(struct sieve_validator *valdtr, struct sieve_command_registration *cmd_reg,
+		const struct sieve_extension *ext, const struct sieve_argument_def *tag_def,
+		int id_code);
+void sieve_validator_register_external_tag
+	(struct sieve_validator *valdtr, const char *command,
+		const struct sieve_extension *ext, const struct sieve_argument_def *tag_def,
+		int id_code);
+void sieve_validator_register_persistent_tag
+	(struct sieve_validator *valdtr, const char *command,
+		const struct sieve_extension *ext,
+		const struct sieve_argument_def *tag_def);
+
+/*
+ * Overriding the default literal arguments
+ */
+
+void sieve_validator_argument_override
+(struct sieve_validator *valdtr, enum sieve_argument_type type,
+	const struct sieve_extension *ext, const struct sieve_argument_def *arg_def);
+bool sieve_validator_argument_activate_super
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+	struct sieve_ast_argument *arg, bool constant);
+
+/*
+ * Argument validation API
+ */
+
+bool sieve_validate_positional_argument
+	(struct sieve_validator *valdtr, struct sieve_command *cmd,
+		struct sieve_ast_argument *arg, const char *arg_name, unsigned int arg_pos,
+		enum sieve_ast_argument_type req_type);
+bool sieve_validator_argument_activate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd,
+		struct sieve_ast_argument *arg, bool constant);
+
+bool sieve_validate_tag_parameter
+	(struct sieve_validator *valdtr, struct sieve_command *cmd,
+		struct sieve_ast_argument *tag, struct sieve_ast_argument *param,
+		const char *arg_name, unsigned int arg_pos,
+		enum sieve_ast_argument_type req_type, bool constant);
+
+/*
+ * Extension support
+ */
+
+struct sieve_validator_extension {
+	const struct sieve_extension_def *ext;
+
+	bool (*check_conflict)
+		(const struct sieve_extension *ext,
+			struct sieve_validator *valdtr, void *context,
+			struct sieve_ast_argument *require_arg,
+			const struct sieve_extension *ext_other,
+			bool required);
+	bool (*validate)
+		(const struct sieve_extension *ext,
+			struct sieve_validator *valdtr, void *context,
+			struct sieve_ast_argument *require_arg,
+			bool required);
+
+	void (*free)
+		(const struct sieve_extension *ext,
+			struct sieve_validator *valdtr, void *context);
+};
+
+bool sieve_validator_extension_load
+	(struct sieve_validator *valdtr, struct sieve_command *cmd,
+		struct sieve_ast_argument *ext_arg,
+		const struct sieve_extension *ext,
+		bool required) ATTR_NULL(2, 3);
+const struct sieve_extension *sieve_validator_extension_load_by_name
+	(struct sieve_validator *valdtr, struct sieve_command *cmd,
+		struct sieve_ast_argument *ext_arg, const char *ext_name);
+const struct sieve_extension *sieve_validator_extension_load_implicit
+	(struct sieve_validator *valdtr, const char *ext_name);
+
+void sieve_validator_extension_register
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		const struct sieve_validator_extension *valext, void *context);
+bool sieve_validator_extension_loaded
+    (struct sieve_validator *valdtr, const struct sieve_extension *ext);
+
+void sieve_validator_extension_set_context
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	void *context);
+void *sieve_validator_extension_get_context
+(struct sieve_validator *valdtr, const struct sieve_extension *ext);
+
+/*
+ * Validator object registry
+ */
+
+struct sieve_validator_object_registry;
+
+struct sieve_validator_object_registry *sieve_validator_object_registry_get
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext);
+void sieve_validator_object_registry_add
+	(struct sieve_validator_object_registry *regs,
+		const struct sieve_extension *ext, const struct sieve_object_def *obj_def);
+bool sieve_validator_object_registry_find
+	(struct sieve_validator_object_registry *regs, const char *identifier,
+		struct sieve_object *obj);
+struct sieve_validator_object_registry *sieve_validator_object_registry_create
+	(struct sieve_validator *valdtr);
+struct sieve_validator_object_registry *sieve_validator_object_registry_init
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve.c
@@ -0,0 +1,1135 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "istream.h"
+#include "ostream.h"
+#include "buffer.h"
+#include "time-util.h"
+#include "eacces-error.h"
+#include "home-expand.h"
+#include "hostpid.h"
+#include "message-address.h"
+#include "mail-user.h"
+
+#include "sieve-settings.h"
+#include "sieve-extensions.h"
+#include "sieve-plugins.h"
+
+#include "sieve-address.h"
+#include "sieve-script.h"
+#include "sieve-storage-private.h"
+#include "sieve-ast.h"
+#include "sieve-binary.h"
+#include "sieve-actions.h"
+#include "sieve-result.h"
+
+#include "sieve-parser.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-binary-dumper.h"
+
+#include "sieve.h"
+#include "sieve-common.h"
+#include "sieve-error-private.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <dirent.h>
+
+/*
+ * Main Sieve library interface
+ */
+
+struct sieve_instance *sieve_init
+(const struct sieve_environment *env,
+	const struct sieve_callbacks *callbacks, void *context, bool debug)
+{
+	struct sieve_instance *svinst;
+	const char *domain;
+	pool_t pool;
+
+	/* Create Sieve engine instance */
+	pool = pool_alloconly_create("sieve", 8192);
+	svinst = p_new(pool, struct sieve_instance, 1);
+	svinst->pool = pool;
+	svinst->callbacks = callbacks;
+	svinst->context = context;
+	svinst->debug = debug;
+	svinst->base_dir = p_strdup_empty(pool, env->base_dir);
+	svinst->username = p_strdup_empty(pool, env->username);
+	svinst->home_dir = p_strdup_empty(pool, env->home_dir);
+	svinst->temp_dir = p_strdup_empty(pool, env->temp_dir);
+	svinst->flags = env->flags;
+	svinst->env_location = env->location;
+	svinst->delivery_phase = env->delivery_phase;
+
+	/* Determine domain */
+	if ( env->domainname != NULL && *(env->domainname) != '\0' ) {
+		domain = env->domainname;
+	} else {
+		/* Fall back to parsing username localpart@domain */
+		domain = svinst->username == NULL ? NULL :
+			strchr(svinst->username, '@');
+		if ( domain == NULL || *(domain+1) == '\0' ) {
+			/* Fall back to parsing hostname host.domain */
+			domain = ( env->hostname != NULL ? strchr(env->hostname, '.') : NULL );
+			if ( domain == NULL || *(domain+1) == '\0'
+				|| strchr(domain+1, '.') == NULL ) {
+				/* Fall back to bare hostname */
+				domain = env->hostname;
+			} else {
+				domain++;
+			}
+		} else {
+			domain++;
+		}
+	}
+	svinst->hostname = p_strdup_empty(pool, env->hostname);
+	svinst->domainname = p_strdup(pool, domain);
+
+	sieve_errors_init(svinst);
+
+	if ( debug ) {
+		sieve_sys_debug(svinst, "%s version %s initializing",
+			PIGEONHOLE_NAME, PIGEONHOLE_VERSION_FULL);
+	}
+
+	/* Read configuration */
+
+	sieve_settings_load(svinst);
+
+	/* Initialize extensions */
+	if ( !sieve_extensions_init(svinst) ) {
+		sieve_deinit(&svinst);
+		return NULL;
+	}
+
+	/* Initialize storage classes */
+	sieve_storages_init(svinst);
+
+	/* Initialize plugins */
+	sieve_plugins_load(svinst, NULL, NULL);
+
+	/* Configure extensions */
+	sieve_extensions_configure(svinst);
+
+	return svinst;
+}
+
+void sieve_deinit(struct sieve_instance **_svinst)
+{
+	struct sieve_instance *svinst = *_svinst;
+
+	sieve_plugins_unload(svinst);
+	sieve_storages_deinit(svinst);
+	sieve_extensions_deinit(svinst);
+	sieve_errors_deinit(svinst);
+
+	pool_unref(&(svinst)->pool);
+	*_svinst = NULL;
+}
+
+void sieve_set_extensions
+(struct sieve_instance *svinst, const char *extensions)
+{
+	sieve_extensions_set_string(svinst, extensions, FALSE, FALSE);
+}
+
+const char *sieve_get_capabilities
+(struct sieve_instance *svinst, const char *name)
+{
+	if ( name == NULL || *name == '\0' )
+		return sieve_extensions_get_string(svinst);
+
+	return sieve_extension_capabilities_get_string(svinst, name);
+}
+
+/*
+ * Low-level compiler functions
+ */
+
+struct sieve_ast *sieve_parse
+(struct sieve_script *script, struct sieve_error_handler *ehandler,
+	enum sieve_error *error_r)
+{
+	struct sieve_parser *parser;
+	struct sieve_ast *ast = NULL;
+
+	/* Parse */
+	if ( (parser = sieve_parser_create(script, ehandler, error_r)) == NULL )
+		return NULL;
+
+ 	if ( !sieve_parser_run(parser, &ast) ) {
+ 		ast = NULL;
+ 	} else
+		sieve_ast_ref(ast);
+
+	sieve_parser_free(&parser);
+
+	if ( error_r != NULL ) {
+		if ( ast == NULL )
+			*error_r = SIEVE_ERROR_NOT_VALID;
+		else
+			*error_r = SIEVE_ERROR_NONE;
+	}
+
+	return ast;
+}
+
+bool sieve_validate
+(struct sieve_ast *ast, struct sieve_error_handler *ehandler,
+	enum sieve_compile_flags flags, enum sieve_error *error_r)
+{
+	bool result = TRUE;
+	struct sieve_validator *validator =
+		sieve_validator_create(ast, ehandler, flags);
+
+	if ( !sieve_validator_run(validator) )
+		result = FALSE;
+
+	sieve_validator_free(&validator);
+
+	if ( error_r != NULL ) {
+		if ( !result )
+			*error_r = SIEVE_ERROR_NOT_VALID;
+		else
+			*error_r = SIEVE_ERROR_NONE;
+	}
+
+	return result;
+}
+
+static struct sieve_binary *sieve_generate
+(struct sieve_ast *ast, struct sieve_error_handler *ehandler,
+	enum sieve_compile_flags flags, enum sieve_error *error_r)
+{
+	struct sieve_generator *generator =
+		sieve_generator_create(ast, ehandler, flags);
+	struct sieve_binary *sbin = NULL;
+
+	sbin = sieve_generator_run(generator, NULL);
+
+	sieve_generator_free(&generator);
+
+	if ( error_r != NULL ) {
+		if ( sbin == NULL )
+			*error_r = SIEVE_ERROR_NOT_VALID;
+		else
+			*error_r = SIEVE_ERROR_NONE;
+	}
+
+	return sbin;
+}
+
+/*
+ * Sieve compilation
+ */
+
+struct sieve_binary *sieve_compile_script
+(struct sieve_script *script, struct sieve_error_handler *ehandler,
+	enum sieve_compile_flags flags, enum sieve_error *error_r)
+{
+	struct sieve_ast *ast;
+	struct sieve_binary *sbin;
+	enum sieve_error error, *errorp;
+
+	if ( error_r != NULL )
+		errorp = error_r;
+	else
+		errorp = &error;
+	*errorp = SIEVE_ERROR_NONE;
+
+	/* Parse */
+	if ( (ast = sieve_parse(script, ehandler, errorp)) == NULL ) {
+		switch ( *errorp ) {
+		case SIEVE_ERROR_NOT_FOUND:
+			if (error_r == NULL) {
+				sieve_error(ehandler, sieve_script_name(script),
+					"script not found");
+			}
+			break;
+		default:
+			sieve_error(ehandler, sieve_script_name(script),
+				"parse failed");
+		}
+		return NULL;
+	}
+
+	/* Validate */
+	if ( !sieve_validate(ast, ehandler, flags, errorp) ) {
+		sieve_error(ehandler, sieve_script_name(script),
+			"validation failed");
+
+ 		sieve_ast_unref(&ast);
+ 		return NULL;
+ 	}
+
+	/* Generate */
+	if ( (sbin=sieve_generate(ast, ehandler, flags, errorp)) == NULL ) {
+		sieve_error(ehandler, sieve_script_name(script),
+			"code generation failed");
+		sieve_ast_unref(&ast);
+		return NULL;
+	}
+
+	/* Cleanup */
+	sieve_ast_unref(&ast);
+	return sbin;
+}
+
+struct sieve_binary *sieve_compile
+(struct sieve_instance *svinst, const char *script_location,
+	const char *script_name, struct sieve_error_handler *ehandler,
+	enum sieve_compile_flags flags, enum sieve_error *error_r)
+{
+	struct sieve_script *script;
+	struct sieve_binary *sbin;
+	enum sieve_error error;
+
+	if ( (script = sieve_script_create_open
+		(svinst, script_location, script_name, &error)) == NULL ) {
+		if (error_r != NULL)
+			*error_r = error;
+		switch ( error ) {
+		case SIEVE_ERROR_NOT_FOUND:
+			sieve_error(ehandler, script_name, "script not found");
+			break;
+		default:
+			sieve_internal_error(ehandler, script_name, "failed to open script");
+		}
+		return NULL;
+	}
+
+	sbin = sieve_compile_script(script, ehandler, flags, error_r);
+
+	if ( svinst->debug && sbin != NULL ) {
+		sieve_sys_debug(svinst, "Script `%s' from %s successfully compiled",
+			sieve_script_name(script), sieve_script_location(script));
+	}
+
+	sieve_script_unref(&script);
+
+	return sbin;
+}
+
+/*
+ * Sieve runtime
+ */
+
+static int sieve_run
+(struct sieve_binary *sbin, struct sieve_result **result,
+	const struct sieve_message_data *msgdata, const struct sieve_script_env *senv,
+	struct sieve_error_handler *ehandler, enum sieve_execute_flags flags)
+{
+	struct sieve_interpreter *interp;
+	int ret = 0;
+
+	/* Create the interpreter */
+	if ( (interp=sieve_interpreter_create
+		(sbin, NULL, msgdata, senv, ehandler, flags)) == NULL )
+		return SIEVE_EXEC_BIN_CORRUPT;
+
+	/* Reset execution status */
+	if ( senv->exec_status != NULL )
+		i_zero(senv->exec_status);
+
+	/* Create result object */
+	if ( *result == NULL ) {
+		*result = sieve_result_create
+			(sieve_binary_svinst(sbin), msgdata, senv);
+	}
+
+	/* Run the interpreter */
+	ret = sieve_interpreter_run(interp, *result);
+
+	/* Free the interpreter */
+	sieve_interpreter_free(&interp);
+
+	return ret;
+}
+
+/*
+ * Reading/writing sieve binaries
+ */
+
+struct sieve_binary *sieve_load
+(struct sieve_instance *svinst, const char *bin_path, enum sieve_error *error_r)
+{
+	return sieve_binary_open(svinst, bin_path, NULL, error_r);
+}
+
+struct sieve_binary *sieve_open_script
+(struct sieve_script *script, struct sieve_error_handler *ehandler,
+	enum sieve_compile_flags flags, enum sieve_error *error_r)
+{
+	struct sieve_instance *svinst = sieve_script_svinst(script);
+	struct sieve_binary *sbin;
+
+	T_BEGIN {
+		/* Then try to open the matching binary */
+		sbin = sieve_script_binary_load(script, error_r);
+
+		if (sbin != NULL) {
+			/* Ok, it exists; now let's see if it is up to date */
+			if ( !sieve_binary_up_to_date(sbin, flags) ) {
+				/* Not up to date */
+				if ( svinst->debug ) {
+					sieve_sys_debug(svinst, "Script binary %s is not up-to-date",
+						sieve_binary_path(sbin));
+				}
+
+				sieve_binary_unref(&sbin);
+				sbin = NULL;
+			}
+		}
+
+		/* If the binary does not exist or is not up-to-date, we need
+		 * to (re-)compile.
+		 */
+		if ( sbin != NULL ) {
+			if ( svinst->debug ) {
+				sieve_sys_debug(svinst,
+					"Script binary %s successfully loaded",
+					sieve_binary_path(sbin));
+			}
+
+		} else {
+			sbin = sieve_compile_script(script, ehandler, flags, error_r);
+
+			if ( sbin != NULL ) {
+				if ( svinst->debug ) {
+					sieve_sys_debug(svinst,
+						"Script `%s' from %s successfully compiled",
+						sieve_script_name(script), sieve_script_location(script));
+				}
+			}
+		}
+	} T_END;
+
+	return sbin;
+}
+
+struct sieve_binary *sieve_open
+(struct sieve_instance *svinst, const char *script_location,
+	const char *script_name, struct sieve_error_handler *ehandler,
+	enum sieve_compile_flags flags, enum sieve_error *error_r)
+{
+	struct sieve_script *script;
+	struct sieve_binary *sbin;
+	enum sieve_error error;
+
+	/* First open the scriptfile itself */
+	if ( (script=sieve_script_create_open
+		(svinst, script_location, script_name, &error)) == NULL ) {
+		/* Failed */
+		if (error_r != NULL)
+			*error_r = error;
+		switch ( error ) {
+		case SIEVE_ERROR_NOT_FOUND:
+			sieve_error(ehandler, script_name, "script not found");
+			break;
+		default:
+			sieve_internal_error(ehandler, script_name, "failed to open script");
+		}
+		return NULL;
+	}
+
+	sbin = sieve_open_script(script, ehandler, flags, error_r);
+
+	/* Drop script reference, if sbin != NULL it holds a reference of its own.
+	 * Otherwise the script object is freed here.
+	 */
+	sieve_script_unref(&script);
+
+	return sbin;
+}
+
+const char *sieve_get_source(struct sieve_binary *sbin)
+{
+	return sieve_binary_source(sbin);
+}
+
+bool sieve_is_loaded(struct sieve_binary *sbin)
+{
+	return sieve_binary_loaded(sbin);
+}
+
+int sieve_save_as
+(struct sieve_binary *sbin, const char *bin_path, bool update,
+	mode_t save_mode, enum sieve_error *error_r)
+{
+	if  ( bin_path == NULL )
+		return sieve_save(sbin, update, error_r);
+
+	return sieve_binary_save(sbin, bin_path, update, save_mode, error_r);
+}
+
+int sieve_save
+(struct sieve_binary *sbin, bool update, enum sieve_error *error_r)
+{
+	struct sieve_script *script = sieve_binary_script(sbin);
+
+	if ( script == NULL ) {
+		return sieve_binary_save(sbin, NULL, update, 0600, error_r);
+	}
+
+	return sieve_script_binary_save(script, sbin, update, error_r);
+}
+
+void sieve_close(struct sieve_binary **sbin)
+{
+	sieve_binary_unref(sbin);
+}
+
+/*
+ * Debugging
+ */
+
+void sieve_dump
+(struct sieve_binary *sbin, struct ostream *stream, bool verbose)
+{
+	struct sieve_binary_dumper *dumpr = sieve_binary_dumper_create(sbin);
+
+	sieve_binary_dumper_run(dumpr, stream, verbose);
+
+	sieve_binary_dumper_free(&dumpr);
+}
+
+void sieve_hexdump
+(struct sieve_binary *sbin, struct ostream *stream)
+{
+	struct sieve_binary_dumper *dumpr = sieve_binary_dumper_create(sbin);
+
+	sieve_binary_dumper_hexdump(dumpr, stream);
+
+	sieve_binary_dumper_free(&dumpr);
+}
+
+int sieve_test
+(struct sieve_binary *sbin, const struct sieve_message_data *msgdata,
+	const struct sieve_script_env *senv, struct sieve_error_handler *ehandler,
+	struct ostream *stream, enum sieve_execute_flags flags, bool *keep)
+{
+	struct sieve_result *result = NULL;
+	int ret;
+
+	if ( keep != NULL ) *keep = FALSE;
+
+	/* Run the script */
+	ret = sieve_run(sbin, &result, msgdata, senv, ehandler, flags);
+
+	/* Print result if successful */
+	if ( ret > 0 ) {
+		ret = ( sieve_result_print(result, senv, stream, keep) ?
+			SIEVE_EXEC_OK : SIEVE_EXEC_FAILURE );
+	} else if ( ret == 0 ) {
+		if ( keep != NULL ) *keep = TRUE;
+	}
+
+	/* Cleanup */
+	if ( result != NULL )
+		sieve_result_unref(&result);
+
+	return ret;
+}
+
+/*
+ * Script execution
+ */
+
+int sieve_script_env_init(struct sieve_script_env *senv,
+	struct mail_user *user, const char **error_r)
+{
+	const struct message_address *postmaster;
+	const char *error;
+
+	if (!mail_user_get_postmaster_address(user, &postmaster, &error)) {
+		*error_r = t_strdup_printf(
+			"Invalid postmaster_address: %s", error);
+		return -1;
+	}
+
+	i_zero(senv);
+	senv->user = user;
+	senv->postmaster_address = postmaster;
+	return 0;
+}
+
+int sieve_execute
+(struct sieve_binary *sbin, const struct sieve_message_data *msgdata,
+	const struct sieve_script_env *senv,
+	struct sieve_error_handler *exec_ehandler,
+	struct sieve_error_handler *action_ehandler,
+	enum sieve_execute_flags flags, bool *keep)
+{
+	struct sieve_result *result = NULL;
+	int ret;
+
+	if ( keep != NULL ) *keep = FALSE;
+
+	/* Run the script */
+	ret = sieve_run(sbin, &result, msgdata, senv, exec_ehandler, flags);
+
+	/* Evaluate status and execute the result:
+	 *   Strange situations, e.g. currupt binaries, must be handled by the caller.
+	 *   In that case no implicit keep is attempted, because the situation may be
+	 *   resolved.
+	 */
+	if ( ret > 0 ) {
+		/* Execute result */
+		ret = sieve_result_execute(result, keep, action_ehandler, flags);
+	} else if ( ret == SIEVE_EXEC_FAILURE ) {
+		/* Perform implicit keep if script failed with a normal runtime error */
+		switch ( sieve_result_implicit_keep
+			(result, action_ehandler, flags, FALSE) ) {
+		case SIEVE_EXEC_OK:
+			if ( keep != NULL ) *keep = TRUE;
+			break;
+		case SIEVE_EXEC_TEMP_FAILURE:
+			ret = SIEVE_EXEC_TEMP_FAILURE;
+			break;
+		default:
+			ret = SIEVE_EXEC_KEEP_FAILED;
+		}
+	}
+
+	/* Cleanup */
+	if ( result != NULL )
+		sieve_result_unref(&result);
+
+	return ret;
+}
+
+/*
+ * Multiscript support
+ */
+
+struct sieve_multiscript {
+	struct sieve_instance *svinst;
+	struct sieve_result *result;
+	const struct sieve_message_data *msgdata;
+	const struct sieve_script_env *scriptenv;
+
+	int status;
+	bool keep;
+
+	struct ostream *teststream;
+
+	bool active:1;
+	bool discard_handled:1;
+};
+
+struct sieve_multiscript *sieve_multiscript_start_execute
+(struct sieve_instance *svinst,	const struct sieve_message_data *msgdata,
+	const struct sieve_script_env *senv)
+{
+	pool_t pool;
+	struct sieve_result *result;
+	struct sieve_multiscript *mscript;
+
+	result = sieve_result_create(svinst, msgdata, senv);
+	pool = sieve_result_pool(result);
+
+	sieve_result_set_keep_action(result, NULL, NULL);
+
+	mscript = p_new(pool, struct sieve_multiscript, 1);
+	mscript->svinst = svinst;
+	mscript->result = result;
+	mscript->msgdata = msgdata;
+	mscript->scriptenv = senv;
+	mscript->status = SIEVE_EXEC_OK;
+	mscript->active = TRUE;
+	mscript->keep = TRUE;
+
+	return mscript;
+}
+
+struct sieve_multiscript *sieve_multiscript_start_test
+(struct sieve_instance *svinst, const struct sieve_message_data *msgdata,
+	const struct sieve_script_env *senv, struct ostream *stream)
+{
+	struct sieve_multiscript *mscript =
+		sieve_multiscript_start_execute(svinst, msgdata, senv);
+
+	mscript->teststream = stream;
+
+	return mscript;
+}
+
+static void sieve_multiscript_test
+(struct sieve_multiscript *mscript, bool *keep)
+{
+	if ( mscript->status > 0 ) {
+		mscript->status = ( sieve_result_print(mscript->result,
+			mscript->scriptenv, mscript->teststream, keep) ?
+				SIEVE_EXEC_OK : SIEVE_EXEC_FAILURE );
+	} else {
+		if ( keep != NULL ) *keep = TRUE;
+	}
+
+	sieve_result_mark_executed(mscript->result);
+}
+
+static void sieve_multiscript_execute
+(struct sieve_multiscript *mscript,
+	struct sieve_error_handler *ehandler,
+	enum sieve_execute_flags flags, bool *keep)
+{
+	if ( mscript->status > 0 ) {
+		mscript->status = sieve_result_execute
+			(mscript->result, keep, ehandler, flags);
+	} else {
+		if ( sieve_result_implicit_keep
+			(mscript->result, ehandler, flags, FALSE) <= 0 )
+			mscript->status = SIEVE_EXEC_KEEP_FAILED;
+		else
+			if ( keep != NULL ) *keep = TRUE;
+	}
+}
+
+bool sieve_multiscript_run
+(struct sieve_multiscript *mscript, struct sieve_binary *sbin,
+	struct sieve_error_handler *exec_ehandler,
+	struct sieve_error_handler *action_ehandler,
+	enum sieve_execute_flags flags)
+{
+	if ( !mscript->active ) return FALSE;
+
+	/* Run the script */
+	mscript->status = sieve_run(sbin, &mscript->result, mscript->msgdata,
+		mscript->scriptenv, exec_ehandler, flags);
+
+	if ( mscript->status >= 0 ) {
+		mscript->keep = FALSE;
+
+		if ( mscript->teststream != NULL ) {
+			sieve_multiscript_test(mscript, &mscript->keep);
+		} else {
+			sieve_multiscript_execute(mscript,
+				action_ehandler, flags, &mscript->keep);
+		}
+		mscript->active =
+			( mscript->active && mscript->keep && mscript->status > 0 );
+	}
+
+	if ( mscript->status <= 0 )
+		return FALSE;
+
+	return mscript->active;
+}
+
+bool sieve_multiscript_will_discard
+(struct sieve_multiscript *mscript)
+{
+	return ( !mscript->active && mscript->status == SIEVE_EXEC_OK &&
+		!sieve_result_executed_delivery(mscript->result) );
+}
+
+void sieve_multiscript_run_discard
+(struct sieve_multiscript *mscript, struct sieve_binary *sbin,
+	struct sieve_error_handler *exec_ehandler,
+	struct sieve_error_handler *action_ehandler,
+	enum sieve_execute_flags flags)
+{
+	if ( !sieve_multiscript_will_discard(mscript) )
+		return;
+	i_assert( !mscript->discard_handled );
+
+	sieve_result_set_keep_action
+		(mscript->result, NULL, &act_store);
+
+	/* Run the discard script */
+	flags |= SIEVE_EXECUTE_FLAG_DEFER_KEEP;
+	mscript->status = sieve_run(sbin, &mscript->result, mscript->msgdata,
+		mscript->scriptenv, exec_ehandler, flags);
+
+	if ( mscript->status >= 0 ) {
+		mscript->keep = FALSE;
+
+		if ( mscript->teststream != NULL ) {
+			sieve_multiscript_test(mscript, &mscript->keep);
+		} else {
+			sieve_multiscript_execute(mscript,
+				action_ehandler, flags, &mscript->keep);
+		}
+		if (mscript->status == SIEVE_EXEC_FAILURE)
+			mscript->status = SIEVE_EXEC_KEEP_FAILED;
+		mscript->active = FALSE;
+	}
+
+	mscript->discard_handled = TRUE;
+}
+
+int sieve_multiscript_status(struct sieve_multiscript *mscript)
+{
+	return mscript->status;
+}
+
+int sieve_multiscript_tempfail(struct sieve_multiscript **_mscript,
+	struct sieve_error_handler *action_ehandler,
+	enum sieve_execute_flags flags)
+{
+	struct sieve_multiscript *mscript = *_mscript;
+	struct sieve_result *result = mscript->result;
+	int ret = mscript->status;
+
+	sieve_result_set_keep_action
+		(mscript->result, NULL, &act_store);
+
+	if ( mscript->active ) {
+		ret = SIEVE_EXEC_TEMP_FAILURE;
+
+		if ( mscript->teststream == NULL && sieve_result_executed(result) ) {
+			/* Part of the result is already executed, need to fall back to
+			 * to implicit keep (FIXME)
+			 */
+			switch ( sieve_result_implicit_keep
+				(result, action_ehandler, flags, FALSE) ) {
+			case SIEVE_EXEC_OK:
+				ret = SIEVE_EXEC_FAILURE;
+				break;
+			default:
+				ret = SIEVE_EXEC_KEEP_FAILED;
+			}
+		}
+	}
+
+	/* Cleanup */
+	sieve_result_unref(&result);
+	*_mscript = NULL;
+
+	return ret;
+}
+
+int sieve_multiscript_finish(struct sieve_multiscript **_mscript,
+	struct sieve_error_handler *action_ehandler,
+	enum sieve_execute_flags flags, bool *keep)
+{
+	struct sieve_multiscript *mscript = *_mscript;
+	struct sieve_result *result = mscript->result;
+	int ret = mscript->status;
+
+	sieve_result_set_keep_action
+		(mscript->result, NULL, &act_store);
+
+	if ( mscript->active ) {
+		if ( mscript->teststream != NULL ) {
+			mscript->keep = TRUE;
+		} else {
+			switch ( sieve_result_implicit_keep
+				(result, action_ehandler, flags, TRUE) ) {
+			case SIEVE_EXEC_OK:
+				mscript->keep = TRUE;
+				break;
+			case SIEVE_EXEC_TEMP_FAILURE:
+				if (!sieve_result_executed(result)) {
+					ret = SIEVE_EXEC_TEMP_FAILURE;
+					break;
+				}
+				/* fall through */
+			default:
+				ret = SIEVE_EXEC_KEEP_FAILED;
+			}
+		}
+	}
+
+	if ( keep != NULL ) *keep = mscript->keep;
+
+	/* Cleanup */
+	sieve_result_unref(&result);
+	*_mscript = NULL;
+	return ret;
+}
+
+/*
+ * Configured Limits
+ */
+
+unsigned int sieve_max_redirects(struct sieve_instance *svinst)
+{
+	return svinst->max_redirects;
+}
+
+unsigned int sieve_max_actions(struct sieve_instance *svinst)
+{
+	return svinst->max_actions;
+}
+
+size_t sieve_max_script_size(struct sieve_instance *svinst)
+{
+	return svinst->max_script_size;
+}
+
+/*
+ * User log
+ */
+
+const char *sieve_user_get_log_path
+(struct sieve_instance *svinst,
+	struct sieve_script *user_script)
+{
+	const char *log_path = NULL;
+
+	/* Determine user log file path */
+	if ( (log_path=sieve_setting_get
+		(svinst, "sieve_user_log")) == NULL ) {
+		const char *path;
+
+		if ( user_script == NULL ||
+			(path=sieve_file_script_get_path(user_script)) == NULL ) {
+			/* Default */
+			if ( svinst->home_dir != NULL ) {
+				log_path = t_strconcat
+					(svinst->home_dir, "/.dovecot.sieve.log", NULL);
+			}
+		} else {
+			/* Use script file as a base (legacy behavior) */
+			log_path = t_strconcat(path, ".log", NULL);
+		}
+	} else if ( svinst->home_dir != NULL ) {
+		/* Expand home dir if necessary */
+		if ( log_path[0] == '~' ) {
+			log_path = home_expand_tilde(log_path, svinst->home_dir);
+		} else if ( log_path[0] != '/' ) {
+			log_path = t_strconcat(svinst->home_dir, "/", log_path, NULL);
+		}
+	}
+	return log_path;
+}
+
+/*
+ * Script trace log
+ */
+
+struct sieve_trace_log {
+	struct ostream *output;
+};
+
+int sieve_trace_log_create
+(struct sieve_instance *svinst, const char *path,
+	struct sieve_trace_log **trace_log_r)
+{
+	struct sieve_trace_log *trace_log;
+	struct ostream *output;
+	int fd;
+
+	*trace_log_r = NULL;
+
+	if ( path == NULL ) {
+		output = o_stream_create_fd(1, 0);
+	} else {
+		fd = open(path, O_CREAT | O_APPEND | O_WRONLY, 0600);
+		if ( fd == -1 ) {
+			sieve_sys_error(svinst, "trace: "
+				"creat(%s) failed: %m", path);
+			return -1;
+		}
+		output = o_stream_create_fd_autoclose(&fd, 0);
+		o_stream_set_name(output, path);
+	}
+
+	trace_log = i_new(struct sieve_trace_log, 1);
+	trace_log->output = output;
+
+	*trace_log_r = trace_log;
+	return 0;
+}
+
+int sieve_trace_log_create_dir
+(struct sieve_instance *svinst, const char *dir,
+	const char *label, struct sieve_trace_log **trace_log_r)
+{
+	static unsigned int counter = 0;
+	const char *timestamp, *prefix;
+	struct stat st;
+
+	*trace_log_r = NULL;
+
+	if (stat(dir, &st) < 0) {
+		if (errno != ENOENT && errno != EACCES) {
+			sieve_sys_error(svinst, "trace: "
+				"stat(%s) failed: %m", dir);
+		}
+		return -1;
+	}
+
+	timestamp = t_strflocaltime("%Y%m%d-%H%M%S", ioloop_time);
+
+	counter++;
+	if ( label != NULL ) {
+		prefix = t_strdup_printf("%s/%s.%s.%s.%u.trace",
+			dir, label, timestamp, my_pid, counter);
+	} else {
+		prefix = t_strdup_printf("%s/%s.%s.%u.trace",
+			dir, timestamp, my_pid, counter);
+	}
+	return sieve_trace_log_create(svinst, prefix, trace_log_r);
+}
+
+int sieve_trace_log_open
+(struct sieve_instance *svinst, const char *label,
+	struct sieve_trace_log **trace_log_r)
+{
+	const char *trace_dir =
+		sieve_setting_get(svinst, "sieve_trace_dir");
+
+	*trace_log_r = NULL;
+	if (trace_dir == NULL)
+		return -1;
+
+	if ( svinst->home_dir != NULL ) {
+		/* Expand home dir if necessary */
+		if ( trace_dir[0] == '~' ) {
+			trace_dir = home_expand_tilde(trace_dir, svinst->home_dir);
+		} else if ( trace_dir[0] != '/' ) {
+			trace_dir = t_strconcat(svinst->home_dir, "/", trace_dir, NULL);
+		}
+	}
+
+	return sieve_trace_log_create_dir
+		(svinst, trace_dir, label, trace_log_r)	;
+}
+
+void sieve_trace_log_write_line
+(struct sieve_trace_log *trace_log, const string_t *line)
+{
+	struct const_iovec iov[2];
+
+	if (line == NULL) {
+		o_stream_nsend_str(trace_log->output, "\n");
+		return;
+	}
+
+	memset(iov, 0, sizeof(iov));
+	iov[0].iov_base = str_data(line);
+	iov[0].iov_len = str_len(line);
+	iov[1].iov_base = "\n";
+	iov[1].iov_len = 1;
+	o_stream_nsendv(trace_log->output, iov, 2);
+}
+
+void sieve_trace_log_free(struct sieve_trace_log **_trace_log)
+{
+	struct sieve_trace_log *trace_log = *_trace_log;
+
+	*_trace_log = NULL;
+
+	if (o_stream_finish(trace_log->output) < 0) {
+		i_error("write(%s) failed: %s",
+			o_stream_get_name(trace_log->output),
+			o_stream_get_error(trace_log->output));
+	}
+	o_stream_destroy(&trace_log->output);
+	i_free(trace_log);
+}
+
+int sieve_trace_config_get(struct sieve_instance *svinst,
+	struct sieve_trace_config *tr_config)
+{
+	const char *tr_level =
+		sieve_setting_get(svinst, "sieve_trace_level");
+	bool tr_debug, tr_addresses;
+
+	i_zero(tr_config);
+
+	if ( tr_level == NULL || *tr_level == '\0' ||
+		strcasecmp(tr_level, "none") == 0 )
+		return -1;
+
+	if ( strcasecmp(tr_level, "actions") == 0 ) {
+		tr_config->level = SIEVE_TRLVL_ACTIONS;
+	} else if ( strcasecmp(tr_level, "commands") == 0 ) {
+		tr_config->level = SIEVE_TRLVL_COMMANDS;
+	} else if ( strcasecmp(tr_level, "tests") == 0 ) {
+		tr_config->level = SIEVE_TRLVL_TESTS;
+	} else if ( strcasecmp(tr_level, "matching") == 0 ) {
+		tr_config->level = SIEVE_TRLVL_MATCHING;
+	} else {
+		sieve_sys_error(svinst,
+			"Unknown trace level: %s", tr_level);
+		return -1;
+	}
+
+	tr_debug = FALSE;
+	(void)sieve_setting_get_bool_value
+		(svinst, "sieve_trace_debug", &tr_debug);
+	tr_addresses = FALSE;
+	(void)sieve_setting_get_bool_value
+		(svinst, "sieve_trace_addresses", &tr_addresses);
+
+	if (tr_debug)
+		tr_config->flags |= SIEVE_TRFLG_DEBUG;
+	if (tr_addresses)
+		tr_config->flags |= SIEVE_TRFLG_ADDRESSES;
+	return 0;
+}
+
+/*
+ * User e-mail address
+ */
+
+const struct smtp_address *sieve_get_user_email
+(struct sieve_instance *svinst)
+{
+	struct smtp_address *address;
+	const char *username = svinst->username;
+
+	if (svinst->user_email_implicit != NULL)
+		return svinst->user_email_implicit;
+	if (svinst->user_email != NULL)
+		return svinst->user_email;
+
+	if (smtp_address_parse_mailbox(svinst->pool, username,
+		0, &address, NULL) >= 0) {
+		svinst->user_email_implicit = address;
+		return svinst->user_email_implicit;
+	}
+
+	if ( svinst->domainname != NULL ) {
+		svinst->user_email_implicit = smtp_address_create(svinst->pool,
+			username, svinst->domainname);
+		return svinst->user_email_implicit;
+	}
+	return NULL;
+}
+
+/*
+ * Postmaster address
+ */
+
+const struct message_address *
+sieve_get_postmaster(const struct sieve_script_env *senv)
+{
+	i_assert(senv->postmaster_address != NULL);
+	return senv->postmaster_address;
+}
+
+const struct smtp_address *
+sieve_get_postmaster_smtp(const struct sieve_script_env *senv)
+{
+	struct smtp_address *addr;
+	int ret;
+
+	ret = smtp_address_create_from_msg_temp(
+		sieve_get_postmaster(senv), &addr);
+	i_assert(ret >= 0);
+	return addr;
+}
+
+const char *
+sieve_get_postmaster_address(const struct sieve_script_env *senv)
+{
+	const struct message_address *postmaster =
+		sieve_get_postmaster(senv);
+	string_t *addr = t_str_new(256);
+
+	message_address_write(addr, postmaster);
+	return str_c(addr);
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/sieve.h
@@ -0,0 +1,262 @@
+#ifndef SIEVE_H
+#define SIEVE_H
+
+struct sieve_script;
+struct sieve_binary;
+
+#include "sieve-config.h"
+#include "sieve-types.h"
+#include "sieve-error.h"
+
+/*
+ * Main Sieve library interface
+ */
+
+/* sieve_init():
+ *   Initializes the sieve engine. Must be called before any sieve functionality
+ *   is used.
+ */
+struct sieve_instance *sieve_init
+	(const struct sieve_environment *env, const struct sieve_callbacks *callbacks,
+		void *context, bool debug);
+
+/* sieve_deinit():
+ *   Frees all memory allocated by the sieve engine.
+ */
+void sieve_deinit(struct sieve_instance **_svinst);
+
+/* sieve_get_capabilities():
+ *
+ */
+const char *sieve_get_capabilities
+	(struct sieve_instance *svinst, const char *name);
+
+/* sieve_set_extensions():
+ *
+ */
+void sieve_set_extensions
+	(struct sieve_instance *svinst, const char *extensions);
+
+/*
+ * Script compilation
+ */
+
+/* sieve_compile_script:
+ */
+struct sieve_binary *sieve_compile_script
+	(struct sieve_script *script, struct sieve_error_handler *ehandler,
+		enum sieve_compile_flags flags, enum sieve_error *error_r)
+		ATTR_NULL(2, 4);
+
+/* sieve_compile:
+ *
+ *   Compiles the script into a binary.
+ */
+struct sieve_binary *sieve_compile
+	(struct sieve_instance *svinst, const char *script_location,
+		const char *script_name, struct sieve_error_handler *ehandler,
+		enum sieve_compile_flags flags, enum sieve_error *error_r)
+		ATTR_NULL(3, 4, 6);
+
+/*
+ * Reading/writing Sieve binaries
+ */
+
+/* sieve_load:
+ *
+ *  Loads the sieve binary indicated by the provided path.
+ */
+struct sieve_binary *sieve_load
+	(struct sieve_instance *svinst, const char *bin_path,
+		enum sieve_error *error_r);
+
+/* sieve_open_script:
+ *
+ *   First tries to open the binary version of the specified script and if it
+ *   does not exist or if it contains errors, the script is (re-)compiled. Note
+ *   that errors in the bytecode are caught only at runtime.
+ */
+struct sieve_binary *sieve_open_script
+	(struct sieve_script *script, struct sieve_error_handler *ehandler,
+		enum sieve_compile_flags flags, enum sieve_error *error_r);
+
+/* sieve_open:
+ *
+ *   First tries to open the binary version of the specified script and if it
+ *   does not exist or if it contains errors, the script is (re-)compiled. Note
+ *   that errors in the bytecode are caught only at runtime.
+ */
+struct sieve_binary *sieve_open
+	(struct sieve_instance *svinst, const char *script_location,
+		const char *script_name, struct sieve_error_handler *ehandler,
+		enum sieve_compile_flags flags, enum sieve_error *error_r);
+
+/* sieve_save_as:
+ *
+ *  Saves the binary as the file indicated by the path parameter. This function
+ *  will not write the binary to disk when it was loaded from the indicated
+ *  bin_path, unless update is TRUE.
+ */
+int sieve_save_as
+	(struct sieve_binary *sbin, const char *bin_path, bool update,
+		mode_t save_mode, enum sieve_error *error_r);
+
+/* sieve_save:
+ *
+ *  Saves the binary to the default location. This function will not overwrite
+ *  the binary it was loaded earlier from the default location, unless update
+ *  is TRUE.
+ */
+int sieve_save
+	(struct sieve_binary *sbin, bool update, enum sieve_error *error_r);
+
+/* sieve_close:
+ *
+ *   Closes a compiled/opened sieve binary.
+ */
+void sieve_close(struct sieve_binary **sbin);
+
+/* sieve_get_source:
+ *
+ *   Obtains the path the binary was compiled or loaded from
+ */
+const char *sieve_get_source(struct sieve_binary *sbin);
+
+/*
+ * sieve_is_loeded:
+ *
+ *   Indicates whether the binary was loaded from a pre-compiled file.
+ */
+bool sieve_is_loaded(struct sieve_binary *sbin);
+
+/*
+ * Debugging
+ */
+
+/* sieve_dump:
+ *
+ *   Dumps the byte code in human-readable form to the specified ostream.
+ */
+void sieve_dump
+	(struct sieve_binary *sbin, struct ostream *stream, bool verbose);
+
+/* sieve_hexdump:
+ *
+ *   Dumps the byte code in hexdump form to the specified ostream.
+ */
+
+void sieve_hexdump
+	(struct sieve_binary *sbin, struct ostream *stream);
+
+/* sieve_test:
+ *
+ *   Executes the bytecode, but only prints the result to the given stream.
+ */
+int sieve_test
+	(struct sieve_binary *sbin, const struct sieve_message_data *msgdata,
+		const struct sieve_script_env *senv, struct sieve_error_handler *ehandler,
+		struct ostream *stream, enum sieve_execute_flags flags, bool *keep);
+
+/*
+ * Script execution
+ */
+
+/* sieve_script_env_init:
+ *
+ *   Initializes the scirpt environment from the given mail_user.
+ */
+int sieve_script_env_init(struct sieve_script_env *senv,
+	struct mail_user *user, const char **error_r);
+
+/* sieve_execute:
+ *
+ *   Executes the binary, including the result.
+ */
+int sieve_execute
+	(struct sieve_binary *sbin, const struct sieve_message_data *msgdata,
+		const struct sieve_script_env *senv,
+		struct sieve_error_handler *exec_ehandler,
+		struct sieve_error_handler *action_ehandler,
+		enum sieve_execute_flags flags, bool *keep);
+
+/*
+ * Multiscript support
+ */
+
+struct sieve_multiscript;
+
+struct sieve_multiscript *sieve_multiscript_start_execute
+	(struct sieve_instance *svinst, const struct sieve_message_data *msgdata,
+		const struct sieve_script_env *senv);
+struct sieve_multiscript *sieve_multiscript_start_test
+	(struct sieve_instance *svinst, const struct sieve_message_data *msgdata,
+		const struct sieve_script_env *senv, struct ostream *stream);
+
+bool sieve_multiscript_run
+	(struct sieve_multiscript *mscript, struct sieve_binary *sbin,
+		struct sieve_error_handler *exec_ehandler,
+		struct sieve_error_handler *action_ehandler,
+		enum sieve_execute_flags flags);
+
+bool sieve_multiscript_will_discard
+	(struct sieve_multiscript *mscript);
+void sieve_multiscript_run_discard
+	(struct sieve_multiscript *mscript, struct sieve_binary *sbin,
+		struct sieve_error_handler *exec_ehandler,
+		struct sieve_error_handler *action_ehandler,
+		enum sieve_execute_flags flags);
+
+int sieve_multiscript_status(struct sieve_multiscript *mscript);
+
+int sieve_multiscript_tempfail
+	(struct sieve_multiscript **_mscript,
+		struct sieve_error_handler *action_ehandler,
+		enum sieve_execute_flags flags);
+int sieve_multiscript_finish
+	(struct sieve_multiscript **_mscript,
+		struct sieve_error_handler *action_ehandler,
+		enum sieve_execute_flags flags, bool *keep);
+
+/*
+ * Configured limits
+ */
+
+unsigned int sieve_max_redirects(struct sieve_instance *svinst);
+unsigned int sieve_max_actions(struct sieve_instance *svinst);
+size_t sieve_max_script_size(struct sieve_instance *svinst);
+
+/*
+ * User log
+ */
+
+const char *sieve_user_get_log_path
+	(struct sieve_instance *svinst,
+		struct sieve_script *user_script)
+	ATTR_NULL(2);
+
+/*
+ * Script trace log
+ */
+
+struct sieve_trace_log;
+
+int sieve_trace_log_create
+	(struct sieve_instance *svinst, const char *path,
+		struct sieve_trace_log **trace_log_r)
+	ATTR_NULL(2);
+int sieve_trace_log_create_dir
+	(struct sieve_instance *svinst, const char *dir,
+		const char *label, struct sieve_trace_log **trace_log_r)
+	ATTR_NULL(3);
+
+int sieve_trace_log_open
+	(struct sieve_instance *svinst, const char *label,
+		struct sieve_trace_log **trace_log_r)
+	ATTR_NULL(2);
+
+void sieve_trace_log_free(struct sieve_trace_log **_trace_log);
+
+int sieve_trace_config_get(struct sieve_instance *svinst,
+	struct sieve_trace_config *tr_config);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/storage/Makefile.am
@@ -0,0 +1,5 @@
+SUBDIRS = \
+	data \
+	file \
+	dict \
+	ldap
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/storage/data/Makefile.am
@@ -0,0 +1,13 @@
+noinst_LTLIBRARIES = libsieve_storage_data.la
+
+AM_CPPFLAGS = \
+	$(LIBDOVECOT_INCLUDE) \
+	-I$(top_srcdir) \
+	-I$(top_srcdir)/src/lib-sieve
+
+libsieve_storage_data_la_SOURCES = \
+	sieve-data-script.c \
+	sieve-data-storage.c
+
+noinst_HEADERS = \
+	sieve-data-storage.h
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/storage/data/sieve-data-script.c
@@ -0,0 +1,94 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "strfuncs.h"
+#include "istream.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-dump.h"
+#include "sieve-binary.h"
+
+#include "sieve-data-storage.h"
+
+/*
+ * Script data implementation
+ */
+
+static struct sieve_data_script *sieve_data_script_alloc(void)
+{
+	struct sieve_data_script *dscript;
+	pool_t pool;
+
+	pool = pool_alloconly_create("sieve_data_script", 1024);
+	dscript = p_new(pool, struct sieve_data_script, 1);
+	dscript->script = sieve_data_script;
+	dscript->script.pool = pool;
+
+	return dscript;
+}
+
+struct sieve_script *sieve_data_script_create_from_input
+(struct sieve_instance *svinst, const char *name, struct istream *input)
+{
+	struct sieve_storage *storage;
+	struct sieve_data_script *dscript = NULL;
+
+	storage = sieve_storage_alloc(svinst, &sieve_data_storage, "", 0, FALSE);
+
+	dscript = sieve_data_script_alloc();
+	sieve_script_init(&dscript->script,
+		storage, &sieve_data_script, "data:", name);
+
+	dscript->data = input;
+	i_stream_ref(dscript->data);
+
+	sieve_storage_unref(&storage);
+
+	dscript->script.open = TRUE;
+
+	return &dscript->script;
+}
+
+static void sieve_data_script_destroy(struct sieve_script *script)
+{
+	struct sieve_data_script *dscript =
+		(struct sieve_data_script *)script;
+
+	i_stream_unref(&dscript->data);
+}
+
+static int sieve_data_script_get_stream
+(struct sieve_script *script, struct istream **stream_r,
+	enum sieve_error *error_r)
+{
+	struct sieve_data_script *dscript =
+		(struct sieve_data_script *)script;
+
+	i_stream_ref(dscript->data);
+	i_stream_seek(dscript->data, 0);
+
+	*stream_r = dscript->data;
+	*error_r = SIEVE_ERROR_NONE;
+	return 0;
+}
+
+static bool sieve_data_script_equals
+(const struct sieve_script *script ATTR_UNUSED,
+	const struct sieve_script *other ATTR_UNUSED)
+{
+	return ( script == other );
+}
+
+const struct sieve_script sieve_data_script = {
+	.driver_name = SIEVE_DATA_STORAGE_DRIVER_NAME,
+	.v = {
+		.destroy = sieve_data_script_destroy,
+
+		.get_stream = sieve_data_script_get_stream,
+
+		.equals = sieve_data_script_equals
+	}
+};
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/storage/data/sieve-data-storage.c
@@ -0,0 +1,47 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+
+#include "sieve-data-storage.h"
+
+/*
+ * Storage class
+ */
+
+static struct sieve_storage *sieve_data_storage_alloc(void)
+{
+	struct sieve_data_storage *dstorage;
+	pool_t pool;
+
+	pool = pool_alloconly_create("sieve_data_storage", 1024);
+	dstorage = p_new(pool, struct sieve_data_storage, 1);
+	dstorage->storage = sieve_data_storage;
+	dstorage->storage.pool = pool;
+
+	return &dstorage->storage;
+}
+
+static int sieve_data_storage_init
+(struct sieve_storage *storage ATTR_UNUSED,
+	const char *const *options ATTR_UNUSED,
+	enum sieve_error *error_r ATTR_UNUSED)
+{
+	return 0;
+}
+
+/*
+ * Driver definition
+ */
+
+const struct sieve_storage sieve_data_storage = {
+	.driver_name = SIEVE_DATA_STORAGE_DRIVER_NAME,
+	.version = 0,
+	.v = {
+		.alloc = sieve_data_storage_alloc,
+		.init = sieve_data_storage_init,
+	}
+};
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/storage/data/sieve-data-storage.h
@@ -0,0 +1,30 @@
+#ifndef SIEVE_DATA_STORAGE_H
+#define SIEVE_DATA_STORAGE_H
+
+#include "sieve.h"
+#include "sieve-script-private.h"
+#include "sieve-storage-private.h"
+
+/*
+ * Storage class
+ */
+
+struct sieve_data_storage {
+	struct sieve_storage storage;
+};
+
+/*
+ * Script class
+ */
+
+struct sieve_data_script {
+	struct sieve_script script;
+
+	struct istream *data;
+};
+
+struct sieve_script *sieve_data_script_create_from_input
+	(struct sieve_instance *svinst, const char *name,
+		struct istream *input);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/storage/dict/Makefile.am
@@ -0,0 +1,13 @@
+noinst_LTLIBRARIES = libsieve_storage_dict.la
+
+AM_CPPFLAGS = \
+	$(LIBDOVECOT_INCLUDE) \
+	-I$(top_srcdir) \
+	-I$(top_srcdir)/src/lib-sieve
+
+libsieve_storage_dict_la_SOURCES = \
+	sieve-dict-script.c \
+	sieve-dict-storage.c
+
+noinst_HEADERS = \
+	sieve-dict-storage.h
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/storage/dict/sieve-dict-script.c
@@ -0,0 +1,335 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "strfuncs.h"
+#include "istream.h"
+#include "dict.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-dump.h"
+#include "sieve-binary.h"
+
+#include "sieve-dict-storage.h"
+
+/*
+ * Script dict implementation
+ */
+
+static struct sieve_dict_script *sieve_dict_script_alloc(void)
+{
+	struct sieve_dict_script *dscript;
+	pool_t pool;
+
+	pool = pool_alloconly_create("sieve_dict_script", 1024);
+	dscript = p_new(pool, struct sieve_dict_script, 1);
+	dscript->script = sieve_dict_script;
+	dscript->script.pool = pool;
+
+	return dscript;
+}
+
+struct sieve_dict_script *sieve_dict_script_init
+(struct sieve_dict_storage *dstorage, const char *name)
+{
+	struct sieve_storage *storage = &dstorage->storage;
+	struct sieve_dict_script *dscript = NULL;
+	const char *location;
+
+	if ( name == NULL ) {
+		name = SIEVE_DICT_SCRIPT_DEFAULT;
+		location = storage->location;
+    } else {
+		location = t_strconcat
+			(storage->location, ";name=", name, NULL);
+    }
+
+	dscript = sieve_dict_script_alloc();
+	sieve_script_init(&dscript->script,
+		storage, &sieve_dict_script, location, name);
+
+	return dscript;
+}
+
+static void sieve_dict_script_destroy(struct sieve_script *script)
+{
+	struct sieve_dict_script *dscript =
+		(struct sieve_dict_script *)script;
+
+	if ( dscript->data_pool != NULL )
+		pool_unref(&dscript->data_pool);
+}
+
+static int sieve_dict_script_open
+(struct sieve_script *script, enum sieve_error *error_r)
+{
+	struct sieve_dict_script *dscript =
+		(struct sieve_dict_script *)script;
+	struct sieve_storage *storage = script->storage;
+	struct sieve_dict_storage *dstorage =
+		(struct sieve_dict_storage *)storage;
+	const char *name = script->name;
+	const char *path, *data_id, *error;
+	int ret;
+
+	if ( sieve_dict_storage_get_dict
+		(dstorage, &dscript->dict, error_r) < 0 )
+		return -1;
+
+	path = t_strconcat
+		(DICT_SIEVE_NAME_PATH, dict_escape_string(name), NULL);
+
+	ret = dict_lookup
+		(dscript->dict, script->pool, path, &data_id, &error);
+	if ( ret <= 0 ) {
+		if ( ret < 0 ) {
+			sieve_script_set_critical(script,
+				"Failed to lookup script id from path %s: %s", path, error);
+			*error_r = SIEVE_ERROR_TEMP_FAILURE;
+		} else {
+			sieve_script_sys_debug(script,
+				"Script `%s' not found at path %s", name, path);
+			sieve_script_set_error(script,
+				SIEVE_ERROR_NOT_FOUND,
+				"Sieve script `%s' not found", name);
+			*error_r = SIEVE_ERROR_NOT_FOUND;
+		}
+		return -1;
+	}
+
+	dscript->data_id = p_strdup(script->pool, data_id);
+	return 0;
+}
+
+static int sieve_dict_script_get_stream
+(struct sieve_script *script, struct istream **stream_r,
+	enum sieve_error *error_r)
+{
+	struct sieve_dict_script *dscript =
+		(struct sieve_dict_script *)script;
+	const char *path, *name = script->name, *data, *error;
+	int ret;
+
+	dscript->data_pool =
+		pool_alloconly_create("sieve_dict_script data pool", 1024);
+
+	path = t_strconcat
+		(DICT_SIEVE_DATA_PATH, dict_escape_string(dscript->data_id), NULL);
+
+	ret = dict_lookup
+		(dscript->dict, dscript->data_pool, path, &data, &error);
+	if ( ret <= 0 ) {
+		if ( ret < 0 ) {
+			sieve_script_set_critical(script,
+				"Failed to lookup data with id `%s' "
+				"for script `%s' from path %s: %s",
+				dscript->data_id, name, path, error);
+		} else {
+			sieve_script_set_critical(script,
+				"Data with id `%s' for script `%s' "
+				"not found at path %s",
+				dscript->data_id, name, path);
+		}
+		*error_r = SIEVE_ERROR_TEMP_FAILURE;
+		return -1;
+	}
+	
+	dscript->data = p_strdup(script->pool, data);
+	*stream_r = i_stream_create_from_data(dscript->data, strlen(dscript->data));
+	return 0;
+}
+
+static int sieve_dict_script_binary_read_metadata
+(struct sieve_script *script, struct sieve_binary_block *sblock,
+	sieve_size_t *offset)
+{
+	struct sieve_dict_script *dscript =
+		(struct sieve_dict_script *)script;
+	struct sieve_binary *sbin =
+		sieve_binary_block_get_binary(sblock);
+	string_t *data_id;
+
+	if ( dscript->data_id == NULL &&
+		sieve_script_open(script, NULL) < 0 )
+		return 0;
+
+	if ( !sieve_binary_read_string(sblock, offset, &data_id) ) {
+		sieve_script_sys_error(script,
+			"Binary `%s' has invalid metadata for script `%s'",
+			sieve_binary_path(sbin), sieve_script_location(script));
+		return -1;
+	}
+	i_assert( dscript->data_id != NULL );
+	if ( strcmp(str_c(data_id), dscript->data_id) != 0 ) {
+		sieve_script_sys_debug(script,
+			"Binary `%s' reports different data ID for script `%s' "
+			"(`%s' rather than `%s')",
+			sieve_binary_path(sbin), sieve_script_location(script),
+			str_c(data_id), dscript->data_id);
+		return 0;
+	}
+	return 1;
+}
+
+static void sieve_dict_script_binary_write_metadata
+(struct sieve_script *script, struct sieve_binary_block *sblock)
+{
+	struct sieve_dict_script *dscript =
+		(struct sieve_dict_script *)script;
+
+	sieve_binary_emit_cstring(sblock, dscript->data_id);
+}
+
+static bool sieve_dict_script_binary_dump_metadata
+(struct sieve_script *script ATTR_UNUSED, struct sieve_dumptime_env *denv,
+	struct sieve_binary_block *sblock, sieve_size_t *offset)
+{
+	string_t *data_id;
+
+	if ( !sieve_binary_read_string(sblock, offset, &data_id) )
+		return FALSE;
+	sieve_binary_dumpf(denv, "dict.data_id = %s\n", str_c(data_id));
+
+	return TRUE;
+}
+
+static const char * sieve_dict_script_get_binpath
+(struct sieve_dict_script *dscript)
+{
+	struct sieve_script *script = &dscript->script;
+	struct sieve_storage *storage = script->storage;
+
+	if ( dscript->binpath == NULL ) {
+		if ( storage->bin_dir == NULL )
+			return NULL;
+		dscript->binpath = p_strconcat(script->pool,
+			storage->bin_dir, "/",
+			sieve_binfile_from_name(script->name), NULL);
+	}
+
+	return dscript->binpath;
+}
+
+static struct sieve_binary *sieve_dict_script_binary_load
+(struct sieve_script *script, enum sieve_error *error_r)
+{
+	struct sieve_dict_script *dscript =
+		(struct sieve_dict_script *)script;
+
+	if ( sieve_dict_script_get_binpath(dscript) == NULL )
+		return NULL;
+
+	return sieve_binary_open(script->storage->svinst,
+		dscript->binpath, script, error_r);
+}
+
+static int sieve_dict_script_binary_save
+(struct sieve_script *script, struct sieve_binary *sbin, bool update,
+	enum sieve_error *error_r)
+{
+	struct sieve_dict_script *dscript =
+		(struct sieve_dict_script *)script;
+
+	if ( sieve_dict_script_get_binpath(dscript) == NULL )
+		return 0;
+	if ( sieve_storage_setup_bindir(script->storage, 0700) < 0 )
+		return -1;
+
+	return sieve_binary_save(sbin,
+		dscript->binpath, update, 0600, error_r);
+}
+
+static bool sieve_dict_script_equals
+(const struct sieve_script *script, const struct sieve_script *other)
+{
+	struct sieve_storage *storage = script->storage;
+	struct sieve_storage *sother = other->storage;
+
+	if ( strcmp(storage->location, sother->location) != 0 )
+		return FALSE;
+
+	i_assert( script->name != NULL && other->name != NULL );
+
+	return ( strcmp(script->name, other->name) == 0 );
+}
+
+const struct sieve_script sieve_dict_script = {
+	.driver_name = SIEVE_DICT_STORAGE_DRIVER_NAME,
+	.v = {
+		.destroy = sieve_dict_script_destroy,
+
+		.open = sieve_dict_script_open,
+
+		.get_stream = sieve_dict_script_get_stream,
+
+		.binary_read_metadata =sieve_dict_script_binary_read_metadata,
+		.binary_write_metadata = sieve_dict_script_binary_write_metadata,
+		.binary_dump_metadata = sieve_dict_script_binary_dump_metadata,
+		.binary_load = sieve_dict_script_binary_load,
+		.binary_save = sieve_dict_script_binary_save,
+
+		.equals = sieve_dict_script_equals
+	}
+};
+
+/*
+ * Script sequence
+ */
+
+struct sieve_dict_script_sequence {
+	struct sieve_script_sequence seq;
+
+	bool done:1;
+};
+
+struct sieve_script_sequence *sieve_dict_storage_get_script_sequence
+(struct sieve_storage *storage, enum sieve_error *error_r)
+{
+	struct sieve_dict_script_sequence *dseq = NULL;
+
+	if ( error_r != NULL )
+		*error_r = SIEVE_ERROR_NONE;
+
+	/* Create sequence object */
+	dseq = i_new(struct sieve_dict_script_sequence, 1);
+	sieve_script_sequence_init(&dseq->seq, storage);
+
+	return &dseq->seq;
+}
+
+struct sieve_script *sieve_dict_script_sequence_next
+(struct sieve_script_sequence *seq, enum sieve_error *error_r)
+{
+	struct sieve_dict_script_sequence *dseq =
+		(struct sieve_dict_script_sequence *)seq;
+	struct sieve_dict_storage *dstorage =
+		(struct sieve_dict_storage *)seq->storage;
+	struct sieve_dict_script *dscript;
+
+	if ( error_r != NULL )
+		*error_r = SIEVE_ERROR_NONE;
+
+	if ( dseq->done )
+		return NULL;
+	dseq->done = TRUE;
+
+	dscript = sieve_dict_script_init
+		(dstorage, seq->storage->script_name);
+	if ( sieve_script_open(&dscript->script, error_r) < 0 ) {
+		struct sieve_script *script = &dscript->script;
+		sieve_script_unref(&script);
+		return NULL;
+	}
+
+	return &dscript->script;
+}
+
+void sieve_dict_script_sequence_destroy(struct sieve_script_sequence *seq)
+{
+	struct sieve_dict_script_sequence *dseq =
+		(struct sieve_dict_script_sequence *)seq;
+	i_free(dseq);
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/storage/dict/sieve-dict-storage.c
@@ -0,0 +1,195 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "dict.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+
+#include "sieve-dict-storage.h"
+
+/*
+ * Storage class
+ */
+
+static struct sieve_storage *sieve_dict_storage_alloc(void)
+{
+	struct sieve_dict_storage *dstorage;
+	pool_t pool;
+
+	pool = pool_alloconly_create("sieve_dict_storage", 1024);
+	dstorage = p_new(pool, struct sieve_dict_storage, 1);
+	dstorage->storage = sieve_dict_storage;
+	dstorage->storage.pool = pool;
+
+	return &dstorage->storage;
+}
+
+static int sieve_dict_storage_init
+(struct sieve_storage *storage, const char *const *options,
+	enum sieve_error *error_r)
+{
+	struct sieve_dict_storage *dstorage =
+		(struct sieve_dict_storage *)storage;
+	struct sieve_instance *svinst = storage->svinst;
+	const char *uri = storage->location, *username = NULL;
+
+	if ( options != NULL ) {
+		while ( *options != NULL ) {
+			const char *option = *options;
+
+			if ( strncasecmp(option, "user=", 5) == 0 && option[5] != '\0' ) {
+				username = option+5;
+			} else {
+				sieve_storage_set_critical(storage,
+					"Invalid option `%s'", option);
+				*error_r = SIEVE_ERROR_TEMP_FAILURE;
+				return -1;
+			}
+
+			options++;
+		}
+	}
+
+	if ( username == NULL ) {
+		if ( svinst->username == NULL ) {
+			sieve_storage_set_critical(storage,
+				"No username specified");
+			*error_r = SIEVE_ERROR_TEMP_FAILURE;
+			return -1;
+		}
+		username = svinst->username;
+	}
+
+	if ( svinst->base_dir == NULL ) {
+		sieve_storage_set_critical(storage,
+			"BUG: Sieve interpreter is initialized without a base_dir");
+		*error_r = SIEVE_ERROR_TEMP_FAILURE;
+		return -1;
+	}
+
+	sieve_storage_sys_debug(storage,
+		"user=%s, uri=%s", username, uri);
+
+	dstorage->uri = p_strdup(storage->pool, uri);
+	dstorage->username = p_strdup(storage->pool, username);
+
+	storage->location = p_strconcat(storage->pool,
+		SIEVE_DICT_STORAGE_DRIVER_NAME, ":", storage->location,
+		";user=", username, NULL);
+
+	return 0;
+}
+
+int sieve_dict_storage_get_dict
+(struct sieve_dict_storage *dstorage, struct dict **dict_r,
+	enum sieve_error *error_r)
+{
+	struct sieve_storage *storage = &dstorage->storage;
+	struct sieve_instance *svinst = storage->svinst;
+	struct dict_settings dict_set;
+	const char *error;
+	int ret;
+
+	if ( dstorage->dict == NULL ) {
+		i_zero(&dict_set);
+		dict_set.username = dstorage->username;
+		dict_set.base_dir = svinst->base_dir;
+		ret = dict_init(dstorage->uri, &dict_set, &dstorage->dict, &error);
+		if ( ret < 0 ) {
+			sieve_storage_set_critical(storage,
+				"Failed to initialize dict with data `%s' for user `%s': %s",
+				dstorage->uri, dstorage->username, error);
+			*error_r = SIEVE_ERROR_TEMP_FAILURE;
+			return -1;
+		}
+	}
+
+	*dict_r = dstorage->dict;
+	return 0;
+}
+
+static void sieve_dict_storage_destroy(struct sieve_storage *storage)
+{
+	struct sieve_dict_storage *dstorage =
+		(struct sieve_dict_storage *)storage;
+
+	if ( dstorage->dict != NULL )
+		dict_deinit(&dstorage->dict);
+}
+
+/*
+ * Script access
+ */
+
+static struct sieve_script *sieve_dict_storage_get_script
+(struct sieve_storage *storage, const char *name)
+{
+	struct sieve_dict_storage *dstorage =
+		(struct sieve_dict_storage *)storage;
+	struct sieve_dict_script *dscript;
+
+	T_BEGIN {
+		dscript = sieve_dict_script_init(dstorage, name);
+	} T_END;
+
+	return &dscript->script;
+}
+
+/*
+ * Active script
+ */
+
+struct sieve_script *sieve_dict_storage_active_script_open
+(struct sieve_storage *storage)
+{
+	struct sieve_dict_storage *dstorage =
+		(struct sieve_dict_storage *)storage;
+	struct sieve_dict_script *dscript;
+
+	dscript = sieve_dict_script_init
+		(dstorage, storage->script_name);
+	if ( sieve_script_open(&dscript->script, NULL) < 0 ) {
+		struct sieve_script *script = &dscript->script;
+		sieve_script_unref(&script);
+		return NULL;
+	}
+
+	return &dscript->script;
+}
+
+int sieve_dict_storage_active_script_get_name
+(struct sieve_storage *storage, const char **name_r)
+{
+	if ( storage->script_name != NULL )
+		*name_r = storage->script_name;
+	else
+		*name_r = SIEVE_DICT_SCRIPT_DEFAULT;
+	return 0;
+}
+
+/*
+ * Driver definition
+ */
+
+const struct sieve_storage sieve_dict_storage = {
+	.driver_name = SIEVE_DICT_STORAGE_DRIVER_NAME,
+	.version = 0,
+	.v = {
+		.alloc = sieve_dict_storage_alloc,
+		.destroy = sieve_dict_storage_destroy,
+		.init = sieve_dict_storage_init,
+
+		.get_script = sieve_dict_storage_get_script,
+
+		.get_script_sequence = sieve_dict_storage_get_script_sequence,
+		.script_sequence_next = sieve_dict_script_sequence_next,
+		.script_sequence_destroy = sieve_dict_script_sequence_destroy,
+
+		.active_script_get_name = sieve_dict_storage_active_script_get_name,
+		.active_script_open = sieve_dict_storage_active_script_open,
+
+		// FIXME: impement management interface
+	}
+};
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/storage/dict/sieve-dict-storage.h
@@ -0,0 +1,66 @@
+#ifndef SIEVE_DICT_STORAGE_H
+#define SIEVE_DICT_STORAGE_H
+
+#include "sieve.h"
+#include "sieve-script-private.h"
+#include "sieve-storage-private.h"
+
+#define DICT_SIEVE_PATH DICT_PATH_PRIVATE"sieve/"
+#define DICT_SIEVE_NAME_PATH DICT_SIEVE_PATH"name/"
+#define DICT_SIEVE_DATA_PATH DICT_SIEVE_PATH"data/"
+
+#define SIEVE_DICT_SCRIPT_DEFAULT "default"
+
+/*
+ * Storage class
+ */
+
+struct sieve_dict_storage {
+	struct sieve_storage storage;
+
+	const char *username;
+	const char *uri;
+
+	struct dict *dict;
+};
+
+int sieve_dict_storage_get_dict
+	(struct sieve_dict_storage *dstorage, struct dict **dict_r,
+		enum sieve_error *error_r);
+
+struct sieve_script *sieve_dict_storage_active_script_open
+	(struct sieve_storage *storage);
+int sieve_dict_storage_active_script_get_name
+	(struct sieve_storage *storage, const char **name_r);
+
+/*
+ * Script class
+ */
+
+struct sieve_dict_script {
+	struct sieve_script script;
+
+	struct dict *dict;
+
+	pool_t data_pool;
+	const char *data_id;
+	const char *data;
+
+	const char *binpath;
+};
+
+struct sieve_dict_script *sieve_dict_script_init
+	(struct sieve_dict_storage *dstorage, const char *name);
+
+/*
+ * Script sequence
+ */
+
+struct sieve_script_sequence *sieve_dict_storage_get_script_sequence
+	(struct sieve_storage *storage, enum sieve_error *error_r);
+
+struct sieve_script *sieve_dict_script_sequence_next
+    (struct sieve_script_sequence *seq, enum sieve_error *error_r);
+void sieve_dict_script_sequence_destroy(struct sieve_script_sequence *seq);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/storage/file/Makefile.am
@@ -0,0 +1,19 @@
+noinst_LTLIBRARIES = libsieve_storage_file.la
+
+AM_CPPFLAGS = \
+	$(LIBDOVECOT_INCLUDE) \
+	-I$(top_srcdir) \
+	-I$(top_srcdir)/src/lib-sieve \
+	-I$(top_srcdir)/src/lib-sieve/util
+
+libsieve_storage_file_la_SOURCES = \
+	sieve-file-script.c \
+	sieve-file-script-sequence.c \
+	sieve-file-storage-active.c \
+	sieve-file-storage-save.c \
+	sieve-file-storage-list.c \
+	sieve-file-storage-quota.c \
+	sieve-file-storage.c
+
+noinst_HEADERS = \
+	sieve-file-storage.h
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/storage/file/sieve-file-script-sequence.c
@@ -0,0 +1,244 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "array.h"
+#include "eacces-error.h"
+
+#include "sieve-common.h"
+#include "sieve-script-private.h"
+
+#include "sieve-file-storage.h"
+
+#include <stdio.h>
+#include <dirent.h>
+
+/*
+ * Script sequence
+ */
+
+struct sieve_file_script_sequence {
+	struct sieve_script_sequence seq;
+	pool_t pool;
+
+	ARRAY_TYPE(const_string) script_files;
+	unsigned int index;
+
+	bool storage_is_file:1;
+};
+
+static int sieve_file_script_sequence_read_dir
+(struct sieve_file_script_sequence *fseq, const char *path)
+{
+	struct sieve_storage *storage = fseq->seq.storage;
+	DIR *dirp;
+	int ret = 0;
+
+	/* Open the directory */
+	if ( (dirp = opendir(path)) == NULL ) {
+		switch ( errno ) {
+		case ENOENT:
+			sieve_storage_set_error(storage,
+				SIEVE_ERROR_NOT_FOUND,
+				"Script sequence location not found");
+			break;
+		case EACCES:
+			sieve_storage_set_error(storage,
+				SIEVE_ERROR_NO_PERMISSION,
+				"Script sequence location not accessible");
+			sieve_storage_sys_error(storage,
+				"Failed to open sieve sequence: "
+				"%s",	eacces_error_get("stat", path));
+			break;
+		default:
+			sieve_storage_set_critical(storage,
+				"Failed to open sieve sequence: "
+				"opendir(%s) failed: %m", path);
+			break;
+		}
+		return -1;
+	}
+
+	/* Read and sort script files */
+	for (;;) {
+		const char *const *files;
+		unsigned int count, i;
+		const char *file;
+		struct dirent *dp;
+		struct stat st;
+
+		errno = 0;
+		if ( (dp=readdir(dirp)) == NULL )
+			break;
+
+		if ( !sieve_script_file_has_extension(dp->d_name) )
+			continue;
+
+		file = NULL;
+		T_BEGIN {
+			if ( path[strlen(path)-1] == '/' )
+				file = t_strconcat(path, dp->d_name, NULL);
+			else
+				file = t_strconcat(path, "/", dp->d_name, NULL);
+
+			if ( stat(file, &st) == 0 && S_ISREG(st.st_mode) )
+				file = p_strdup(fseq->pool, dp->d_name);
+			else
+				file = NULL;
+		} T_END;
+
+		if (file == NULL)
+			continue;
+		
+		/* Insert into sorted array */
+		files = array_get(&fseq->script_files, &count);
+		for ( i = 0; i < count; i++ ) {
+			if ( strcmp(file, files[i]) < 0 )
+				break;
+		}
+
+		if ( i == count )
+			array_append(&fseq->script_files, &file, 1);
+		else
+			array_insert(&fseq->script_files, i, &file, 1);
+	} 
+
+	if ( errno != 0 ) {
+		sieve_storage_set_critical(storage,
+			"Failed to read sequence directory: "
+			"readdir(%s) failed: %m", path);
+		ret = -1;
+	}
+
+	/* Close the directory */
+	if ( dirp != NULL && closedir(dirp) < 0 ) {
+		sieve_storage_sys_error(storage,
+			"Failed to close sequence directory: "
+			"closedir(%s) failed: %m", path);
+	}
+	return ret;
+}
+
+struct sieve_script_sequence *sieve_file_storage_get_script_sequence
+(struct sieve_storage *storage, enum sieve_error *error_r)
+{
+	struct sieve_file_storage *fstorage =
+		(struct sieve_file_storage *)storage;
+	struct sieve_file_script_sequence *fseq = NULL;
+	const char *name = storage->script_name;
+	const char *file;
+	pool_t pool;
+	struct stat st;
+
+	/* Specified path can either be a regular file or a directory */
+	if ( stat(fstorage->path, &st) != 0 ) {
+		switch ( errno ) {
+		case ENOENT:
+			sieve_storage_set_error(storage,
+				SIEVE_ERROR_NOT_FOUND,
+				"Script sequence location not found");
+			break;
+		case EACCES:
+			sieve_storage_set_error(storage,
+				SIEVE_ERROR_NO_PERMISSION,
+				"Script sequence location not accessible");
+			sieve_storage_sys_error(storage,
+				"Failed to open sieve sequence: "
+				"%s",	eacces_error_get("stat", fstorage->path));
+			break;
+		default:
+			sieve_storage_set_critical(storage,
+				"Failed to open sieve sequence: "
+				"stat(%s) failed: %m", fstorage->path);
+			break;
+		}
+		*error_r = storage->error_code;
+		return NULL;
+	}
+
+	/* Create sequence object */
+	pool = pool_alloconly_create("sieve_file_script_sequence", 1024);
+	fseq = p_new(pool, struct sieve_file_script_sequence, 1);
+	fseq->pool = pool;
+	sieve_script_sequence_init(&fseq->seq, storage);
+
+	if ( S_ISDIR(st.st_mode) ) {
+		i_array_init(&fseq->script_files, 16);
+
+		/* Path is directory */
+		if (name == 0 || *name == '\0') {
+			/* Read all '.sieve' files in directory */
+			if (sieve_file_script_sequence_read_dir
+				(fseq, fstorage->path) < 0) {
+				*error_r = storage->error_code;
+				sieve_file_script_sequence_destroy(&fseq->seq);
+				return NULL;
+			}
+
+		}	else {
+			/* Read specific script file */
+			file = sieve_script_file_from_name(name);
+			file = p_strdup(pool, file);
+			array_append(&fseq->script_files, &file, 1);
+		}
+
+	} else {
+		/* Path is a file
+		   (apparently; we'll see about that once it is opened) */
+		fseq->storage_is_file = TRUE;
+	}
+		
+	return &fseq->seq;
+}
+
+struct sieve_script *sieve_file_script_sequence_next
+(struct sieve_script_sequence *seq, enum sieve_error *error_r)
+{
+	struct sieve_file_script_sequence *fseq =
+		(struct sieve_file_script_sequence *)seq;
+	struct sieve_file_storage *fstorage =
+		(struct sieve_file_storage *)seq->storage;
+	struct sieve_file_script *fscript;
+	const char *const *files;
+	unsigned int count;
+
+	if ( error_r != NULL )
+		*error_r = SIEVE_ERROR_NONE;
+
+	fscript = NULL;
+	if ( fseq->storage_is_file ) {
+		if ( fseq->index++ < 1 )
+			fscript = sieve_file_script_open_from_name(fstorage, NULL);
+
+	} else {
+		files = array_get(&fseq->script_files, &count);
+
+		while ( fseq->index < count ) {
+			fscript = sieve_file_script_open_from_filename
+				(fstorage, files[fseq->index++], NULL);
+			if (fscript != NULL)
+				break;
+			if (seq->storage->error_code != SIEVE_ERROR_NOT_FOUND)
+				break;
+			sieve_storage_clear_error(seq->storage);
+		}
+	}
+
+	if (fscript == NULL ) {
+		if ( error_r != NULL ) 
+			*error_r = seq->storage->error_code;
+		return NULL;
+	}
+	return &fscript->script;
+}
+
+void sieve_file_script_sequence_destroy(struct sieve_script_sequence *seq)
+{
+	struct sieve_file_script_sequence *fseq =
+		(struct sieve_file_script_sequence *)seq;
+
+	if ( array_is_created(&fseq->script_files) )
+		array_free(&fseq->script_files);
+	pool_unref(&fseq->pool);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/storage/file/sieve-file-script.c
@@ -0,0 +1,833 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "mempool.h"
+#include "path-util.h"
+#include "istream.h"
+#include "time-util.h"
+#include "eacces-error.h"
+
+#include "sieve-binary.h"
+#include "sieve-script-private.h"
+
+#include "sieve-file-storage.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <time.h>
+#include <fcntl.h>
+
+/*
+ * Filename to name/name to filename
+ */
+
+const char *sieve_script_file_get_scriptname(const char *filename)
+{
+	const char *ext;
+
+	/* Extract the script name */
+	ext = strrchr(filename, '.');
+	if ( ext == NULL || ext == filename ||
+		strcmp(ext, "."SIEVE_SCRIPT_FILEEXT) != 0 )
+		return NULL;
+
+	return t_strdup_until(filename, ext);
+}
+
+bool sieve_script_file_has_extension(const char *filename)
+{
+	return ( sieve_script_file_get_scriptname(filename) != NULL );
+}
+
+const char *sieve_script_file_from_name(const char *name)
+{
+	return t_strconcat(name, "."SIEVE_SCRIPT_FILEEXT, NULL);
+}
+
+/*
+ * Common error handling
+ */
+
+static void sieve_file_script_handle_error
+(struct sieve_file_script *fscript, const char *op, const char *path,
+	const char *name, enum sieve_error *error_r)
+{
+	struct sieve_script *script = &fscript->script;
+	const char *abspath, *error;
+
+	switch ( errno ) {
+	case ENOENT:
+		if (t_abspath(path, &abspath, &error) < 0) {
+			sieve_script_set_error(script,
+				SIEVE_ERROR_TEMP_FAILURE,
+				"t_abspath(%s) failed: %s",
+				path, error);
+			*error_r = SIEVE_ERROR_TEMP_FAILURE;
+			break;
+		}
+		sieve_script_sys_debug(script, "File `%s' not found",
+				       abspath);
+		sieve_script_set_error(script,
+			SIEVE_ERROR_NOT_FOUND,
+			"Sieve script `%s' not found", name);
+		*error_r = SIEVE_ERROR_NOT_FOUND;
+		break;
+	case EACCES:
+		sieve_script_set_critical(script,
+			"Failed to %s sieve script: %s",
+			op, eacces_error_get(op, path));
+		*error_r = SIEVE_ERROR_NO_PERMISSION;
+		break;
+	default:
+		sieve_script_set_critical(script,
+			"Failed to %s sieve script: %s(%s) failed: %m",
+			op, op, path);
+		*error_r = SIEVE_ERROR_TEMP_FAILURE;
+		break;
+	}
+}
+
+/*
+ *
+ */
+
+static struct sieve_file_script *sieve_file_script_alloc(void)
+{
+	struct sieve_file_script *fscript;
+	pool_t pool;
+
+	pool = pool_alloconly_create("sieve_file_script", 2048);
+	fscript = p_new(pool, struct sieve_file_script, 1);
+	fscript->script = sieve_file_script;
+	fscript->script.pool = pool;
+
+	return fscript;
+}
+
+struct sieve_file_script *sieve_file_script_init_from_filename
+(struct sieve_file_storage *fstorage, const char *filename,
+	const char *scriptname)
+{
+	struct sieve_storage *storage = &fstorage->storage;
+	struct sieve_file_script *fscript = NULL;
+
+	/* Prevent initializing the active script link as a script when it
+	 * resides in the sieve storage directory.
+	 */
+	if ( scriptname != NULL && fstorage->link_path != NULL &&
+		*(fstorage->link_path) == '\0' ) {
+		if ( strcmp(filename, fstorage->active_fname) == 0 ) {
+			sieve_storage_set_error(storage,
+				SIEVE_ERROR_NOT_FOUND,
+				"Script `%s' does not exist.", scriptname);
+			return NULL;
+		}
+	}
+
+	fscript = sieve_file_script_alloc();
+	sieve_script_init
+		(&fscript->script, storage, &sieve_file_script,
+			sieve_file_storage_path_extend(fstorage, filename), scriptname);
+	fscript->filename = p_strdup(fscript->script.pool, filename);
+	return fscript;
+}
+
+struct sieve_file_script *sieve_file_script_open_from_filename
+(struct sieve_file_storage *fstorage, const char *filename,
+	const char *scriptname)
+{
+	struct sieve_file_script *fscript;
+	enum sieve_error error;
+
+	fscript = sieve_file_script_init_from_filename
+		(fstorage, filename, scriptname);
+	if ( fscript == NULL )
+		return NULL;
+
+	if ( sieve_script_open(&fscript->script, &error) < 0 ) {
+		struct sieve_script *script = &fscript->script;
+		sieve_script_unref(&script);
+		return NULL;
+	}
+
+	return fscript;
+}
+
+struct sieve_file_script *sieve_file_script_init_from_name
+(struct sieve_file_storage *fstorage, const char *name)
+{
+	struct sieve_storage *storage = &fstorage->storage;
+	struct sieve_file_script *fscript;
+
+	if (name != NULL && S_ISDIR(fstorage->st.st_mode)) {
+		return sieve_file_script_init_from_filename
+			(fstorage, sieve_script_file_from_name(name), name);
+	}
+
+	fscript = sieve_file_script_alloc();
+	sieve_script_init
+		(&fscript->script, storage, &sieve_file_script,
+			fstorage->active_path, name);
+	return fscript;
+}
+
+struct sieve_file_script *sieve_file_script_open_from_name
+(struct sieve_file_storage *fstorage, const char *name)
+{
+	struct sieve_file_script *fscript;
+	enum sieve_error error;
+
+	fscript = sieve_file_script_init_from_name(fstorage, name);
+	if ( fscript == NULL )
+		return NULL;
+
+	if ( sieve_script_open(&fscript->script, &error) < 0 ) {
+		struct sieve_script *script = &fscript->script;
+		sieve_script_unref(&script);
+		return NULL;
+	}
+
+	return fscript;
+}
+
+struct sieve_file_script *sieve_file_script_init_from_path
+(struct sieve_file_storage *fstorage, const char *path,
+	const char *scriptname, enum sieve_error *error_r)
+{
+	struct sieve_instance *svinst = fstorage->storage.svinst;
+	struct sieve_file_storage *fsubstorage;
+	struct sieve_file_script *fscript;
+	struct sieve_storage *substorage;
+	enum sieve_error error;
+
+	if ( error_r != NULL )
+		*error_r = SIEVE_ERROR_NONE;
+	else
+		error_r = &error;
+
+	fsubstorage = sieve_file_storage_init_from_path
+		(svinst, path, 0, error_r);
+	if (fsubstorage == NULL)
+		return NULL;
+	substorage = &fsubstorage->storage;
+
+	fscript = sieve_file_script_alloc();
+	sieve_script_init(&fscript->script,
+		substorage, &sieve_file_script, path, scriptname);
+	sieve_storage_unref(&substorage);
+
+	return fscript;
+}
+
+struct sieve_file_script *sieve_file_script_open_from_path
+(struct sieve_file_storage *fstorage, const char *path,
+	const char *scriptname, enum sieve_error *error_r)
+{
+	struct sieve_storage *storage = &fstorage->storage;
+	struct sieve_file_script *fscript;
+	enum sieve_error error;
+
+	if ( error_r != NULL )
+		*error_r = SIEVE_ERROR_NONE;
+	else
+		error_r = &error;
+
+	fscript = sieve_file_script_init_from_path
+		(fstorage, path, scriptname, error_r);
+	if (fscript == NULL) {
+		sieve_storage_set_error(storage,
+			*error_r, "Failed to open script");
+		return NULL;
+	}
+
+	if ( sieve_script_open(&fscript->script, error_r) < 0 ) {
+		struct sieve_script *script = &fscript->script;
+		const char *errormsg;
+
+		errormsg = sieve_script_get_last_error(&fscript->script, error_r);
+		sieve_storage_set_error(storage,
+			*error_r, "%s", errormsg);
+		sieve_script_unref(&script);
+		return NULL;
+	}
+
+	return fscript;
+}
+
+/*
+ * Open
+ */
+
+static int sieve_file_script_stat
+(const char *path, struct stat *st, struct stat *lnk_st)
+{
+	if ( lstat(path, st) < 0 )
+		return -1;
+
+	*lnk_st = *st;
+
+	if ( S_ISLNK(st->st_mode) && stat(path, st) < 0 )
+		return -1;
+
+	return 0;
+}
+
+static const char *
+path_split_filename(const char *path, const char **dirpath_r)
+{
+	const char *filename;
+
+	filename = strrchr(path, '/');
+	if ( filename == NULL ) {
+		*dirpath_r = "";
+		filename = path;
+	} else {
+		*dirpath_r = t_strdup_until(path, filename);
+		filename++;
+	}
+	return filename;
+}
+
+static int sieve_file_script_open
+(struct sieve_script *script, enum sieve_error *error_r)
+{
+	struct sieve_file_script *fscript =
+		(struct sieve_file_script *)script;
+	struct sieve_storage *storage = script->storage;
+	struct sieve_file_storage *fstorage =
+		(struct sieve_file_storage *)storage;
+	pool_t pool = script->pool;
+	const char *filename, *name, *path;
+	const char *dirpath, *basename, *binpath, *binprefix;
+	struct stat st, lnk_st;
+	bool success = TRUE;
+	int ret = 0;
+
+	filename = fscript->filename;
+	basename = NULL;
+	name = script->name;
+	st = fstorage->st;
+	lnk_st = fstorage->lnk_st;
+
+	if (name == NULL)
+		name = storage->script_name;
+
+	T_BEGIN {
+		if ( S_ISDIR(st.st_mode) ) {
+			/* Storage is a directory */
+			path = fstorage->path;
+
+			if ( (filename == NULL || *filename == '\0') &&
+				name != NULL && *name != '\0' ) {
+				/* Name is used to find actual filename */
+				filename = sieve_script_file_from_name(name);
+				basename = name;
+			}
+			if ( filename == NULL || *filename == '\0' ) {
+				sieve_script_set_critical(script,
+					"Sieve script file path '%s' is a directory.", path);
+				*error_r = SIEVE_ERROR_TEMP_FAILURE;
+				success = FALSE;
+			}	else {
+				/* Extend storage path with filename */
+				if (name == NULL) {
+					if ( basename == NULL &&
+						(basename=sieve_script_file_get_scriptname(filename)) == NULL )
+						basename = filename;
+					name = basename;
+				} else if (basename == NULL) {
+					basename = name;
+				}
+				dirpath = path;
+
+				path = sieve_file_storage_path_extend(fstorage, filename);
+				ret = sieve_file_script_stat(path, &st, &lnk_st);
+			}
+
+		} else {
+			/* Storage is a single file */
+			path = fstorage->active_path;
+
+			/* Extract filename from path */
+			filename = path_split_filename(path, &dirpath);
+
+			if ( (basename=sieve_script_file_get_scriptname(filename)) == NULL )
+				basename = filename;
+
+			if ( name == NULL )
+				name = basename;
+		}
+
+		if ( success ) {
+			if ( ret < 0 ) {
+				/* Make sure we have a script name for the error */
+				if ( name == NULL ) {
+					i_assert( basename != NULL );
+					name = basename;
+				}
+				sieve_file_script_handle_error
+					(fscript, "stat", path, name, error_r);
+				success = FALSE;
+
+			} else if ( !S_ISREG(st.st_mode) ) {
+				sieve_script_set_critical(script,
+					"Sieve script file '%s' is not a regular file.", path);
+				*error_r = SIEVE_ERROR_TEMP_FAILURE;
+				success = FALSE;
+			}
+		}
+
+		if ( success ) {
+			const char *bpath, *bfile, *bprefix;
+
+			if ( storage->bin_dir != NULL ) {
+				bpath = storage->bin_dir;
+				bfile = sieve_binfile_from_name(name);
+				bprefix = name;
+
+			} else {
+				bpath = dirpath;
+				bfile = sieve_binfile_from_name(basename);
+				bprefix = basename;
+			}
+
+			if ( *bpath == '\0' ) {
+				binpath = bfile;
+				binprefix = bprefix;
+			} else 	if ( bpath[strlen(bpath)-1] == '/' ) {
+				binpath = t_strconcat(bpath, bfile, NULL);
+				binprefix = t_strconcat(bpath, bprefix, NULL);
+			} else {
+				binpath = t_strconcat(bpath, "/", bfile, NULL);
+				binprefix = t_strconcat(bpath, "/", bprefix, NULL);
+			}
+
+			fscript->st = st;
+			fscript->lnk_st = lnk_st;
+			fscript->path = p_strdup(pool, path);
+			fscript->filename = p_strdup(pool, filename);
+			fscript->dirpath = p_strdup(pool, dirpath);
+			fscript->binpath = p_strdup(pool, binpath);
+			fscript->binprefix = p_strdup(pool, binprefix);
+
+			fscript->script.location = fscript->path;
+
+			if ( fscript->script.name == NULL )
+				fscript->script.name = p_strdup(pool, basename);
+		}
+	} T_END;
+
+	return ( success ? 0 : -1 );
+}
+
+static int sieve_file_script_get_stream
+(struct sieve_script *script, struct istream **stream_r,
+	enum sieve_error *error_r)
+{
+	struct sieve_file_script *fscript = (struct sieve_file_script *)script;
+	struct stat st;
+	struct istream *result;
+	int fd;
+
+	if ( (fd=open(fscript->path, O_RDONLY)) < 0 ) {
+		sieve_file_script_handle_error
+			(fscript, "open", fscript->path, fscript->script.name, error_r);
+		return -1;
+	}
+
+	if ( fstat(fd, &st) != 0 ) {
+		sieve_script_set_critical(script,
+			"Failed to open sieve script: fstat(fd=%s) failed: %m",
+			fscript->path);
+		*error_r = SIEVE_ERROR_TEMP_FAILURE;
+		result = NULL;
+	} else {
+		/* Re-check the file type just to be sure */
+		if ( !S_ISREG(st.st_mode) ) {
+			sieve_script_set_critical(script,
+				"Sieve script file `%s' is not a regular file", fscript->path);
+			*error_r = SIEVE_ERROR_TEMP_FAILURE;
+			result = NULL;
+		} else {
+			result = i_stream_create_fd_autoclose(&fd, SIEVE_FILE_READ_BLOCK_SIZE);
+			fscript->st = fscript->lnk_st = st;
+		}
+	}
+
+	if ( result == NULL ) {
+		/* Something went wrong, close the fd */
+		if ( fd >= 0 && close(fd) != 0 ) {
+			sieve_script_sys_error(script,
+				"Failed to close sieve script: "
+				"close(fd=%s) failed: %m", fscript->path);
+		}
+		return -1;
+	}
+
+	*stream_r = result;
+	return 0;
+}
+
+/*
+ * Binary
+ */
+
+static int sieve_file_script_binary_read_metadata
+(struct sieve_script *script, struct sieve_binary_block *sblock,
+	sieve_size_t *offset ATTR_UNUSED)
+{
+	struct sieve_file_script *fscript = (struct sieve_file_script *)script;
+	struct sieve_instance *svinst = script->storage->svinst;
+	struct sieve_binary *sbin = sieve_binary_block_get_binary(sblock);
+	const struct stat *sstat, *bstat;
+
+	bstat = sieve_binary_stat(sbin);
+	if ( fscript->st.st_mtime > fscript->lnk_st.st_mtime ||
+		(fscript->st.st_mtime == fscript->lnk_st.st_mtime &&
+		 ST_MTIME_NSEC(fscript->st) >= ST_MTIME_NSEC(fscript->lnk_st)) ) {
+		sstat = &fscript->st;
+	} else {
+		sstat = &fscript->lnk_st;
+	}
+
+	if ( bstat->st_mtime < sstat->st_mtime ||
+		(bstat->st_mtime == sstat->st_mtime &&
+			ST_MTIME_NSEC(*bstat) <= ST_MTIME_NSEC(*sstat)) ) {
+		if ( svinst->debug ) {
+			sieve_script_sys_debug(script,
+				"Sieve binary `%s' is not newer "
+				"than the Sieve script `%s' (%s.%lu <= %s.%lu)",
+				sieve_binary_path(sbin), sieve_script_location(script),
+				t_strflocaltime("%Y-%m-%d %H:%M:%S", bstat->st_mtime),
+				ST_MTIME_NSEC(*bstat),
+				t_strflocaltime("%Y-%m-%d %H:%M:%S", sstat->st_mtime),
+				ST_MTIME_NSEC(*sstat));
+		}
+		return 0;
+	}
+
+	return 1;
+}
+
+static struct sieve_binary *sieve_file_script_binary_load
+(struct sieve_script *script, enum sieve_error *error_r)
+{
+	struct sieve_file_script *fscript = (struct sieve_file_script *)script;
+	struct sieve_instance *svinst = script->storage->svinst;
+
+	return sieve_binary_open(svinst, fscript->binpath, script, error_r);
+}
+
+static int sieve_file_script_binary_save
+(struct sieve_script *script, struct sieve_binary *sbin, bool update,
+	enum sieve_error *error_r)
+{
+	struct sieve_storage *storage = script->storage;
+	struct sieve_file_script *fscript = (struct sieve_file_script *)script;
+
+	if ( storage->bin_dir != NULL &&
+		sieve_storage_setup_bindir(storage, 0700) < 0 )
+		return -1;
+
+	return sieve_binary_save(sbin, fscript->binpath, update,
+		fscript->st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO), error_r);
+}
+
+static const char *sieve_file_script_binary_get_prefix
+(struct sieve_script *script)
+{
+	struct sieve_file_script *fscript = (struct sieve_file_script *)script;
+	
+	return fscript->binprefix;
+}
+
+/*
+ * Management
+ */
+
+static int sieve_file_storage_script_is_active(struct sieve_script *script)
+{
+	struct sieve_file_script *fscript =
+		(struct sieve_file_script *) script;
+	struct sieve_file_storage *fstorage =
+		(struct sieve_file_storage *)script->storage;
+	const char *afile;
+	int ret = 0;
+
+	T_BEGIN {
+		ret = sieve_file_storage_active_script_get_file(fstorage, &afile);
+
+		if ( ret > 0 ) {
+		 	/* Is the requested script active? */
+			ret = ( strcmp(fscript->filename, afile) == 0 ? 1 : 0 );
+		}
+	} T_END;
+
+	return ret;
+}
+
+static int sieve_file_storage_script_delete(struct sieve_script *script)
+{
+	struct sieve_file_script *fscript =
+		(struct sieve_file_script *)script;
+	int ret = 0;
+
+	if ( sieve_file_storage_pre_modify(script->storage) < 0 )
+		return -1;
+
+	ret = unlink(fscript->path);
+	if ( ret < 0 ) {
+		if ( errno == ENOENT ) {
+			sieve_script_set_error(script,
+				SIEVE_ERROR_NOT_FOUND,
+				"Sieve script does not exist.");
+		} else {
+			sieve_script_set_critical(script,
+				"Performing unlink() failed on sieve file `%s': %m",
+				fscript->path);
+		}
+	}
+	return ret;
+}
+
+static int _sieve_file_storage_script_activate
+(struct sieve_file_script *fscript)
+{
+	struct sieve_script *script = &fscript->script;
+	struct sieve_storage *storage = script->storage;
+	struct sieve_file_storage *fstorage =
+		(struct sieve_file_storage *)storage;
+	struct stat st;
+	const char *link_path, *afile;
+	int activated = 0;
+	int ret;
+
+	/* Find out whether there is an active script, but recreate
+	 * the symlink either way. This way, any possible error in the symlink
+	 * resolves automatically. This step is only necessary to provide a
+	 * proper return value indicating whether the script was already active.
+	 */
+	ret = sieve_file_storage_active_script_get_file(fstorage, &afile);
+
+	/* Is the requested script already active? */
+	if ( ret <= 0 || strcmp(fscript->filename, afile) != 0 )
+		activated = 1;
+
+	i_assert( fstorage->link_path != NULL );
+
+	/* Check the scriptfile we are trying to activate */
+	if ( lstat(fscript->path, &st) != 0 ) {
+		sieve_script_set_critical(script,
+		  "Failed to activate Sieve script: lstat(%s) failed: %m.",
+			fscript->path);
+		return -1;
+	}
+
+	/* Rescue a possible .dovecot.sieve regular file remaining from old
+	 * installations.
+	 */
+	if ( !sieve_file_storage_active_rescue_regular(fstorage) ) {
+		/* Rescue failed, manual intervention is necessary */
+		return -1;
+	}
+
+	/* Just try to create the symlink first */
+	link_path = t_strconcat
+	  ( fstorage->link_path, fscript->filename, NULL );
+
+ 	ret = symlink(link_path, fstorage->active_path);
+	if ( ret < 0 ) {
+		if ( errno == EEXIST ) {
+			ret = sieve_file_storage_active_replace_link(fstorage, link_path);
+			if ( ret < 0 ) {
+				return ret;
+			}
+		} else {
+			/* Other error, critical */
+			sieve_script_set_critical(script,
+				"Failed to activate Sieve script: "
+				"symlink(%s, %s) failed: %m",
+					link_path, fstorage->active_path);
+			return -1;
+		}
+	}
+	return activated;
+}
+
+static int sieve_file_storage_script_activate
+(struct sieve_script *script)
+{
+	struct sieve_file_script *fscript =
+		(struct sieve_file_script *)script;
+	int ret;
+
+	if ( sieve_file_storage_pre_modify(script->storage) < 0 )
+		return -1;
+
+	T_BEGIN {
+		ret = _sieve_file_storage_script_activate(fscript);
+	} T_END;
+
+	return ret;
+}
+
+static int sieve_file_storage_script_rename
+(struct sieve_script *script, const char *newname)
+{
+	struct sieve_file_script *fscript =
+		(struct sieve_file_script *)script;
+	struct sieve_storage *storage = script->storage;
+	struct sieve_file_storage *fstorage =
+		(struct sieve_file_storage *)storage;
+	const char *newpath, *newfile, *link_path;
+	int ret = 0;
+
+	if ( sieve_file_storage_pre_modify(storage) < 0 )
+		return -1;
+
+	T_BEGIN {
+		newfile = sieve_script_file_from_name(newname);
+		newpath = t_strconcat( fstorage->path, "/", newfile, NULL );
+
+		/* The normal rename() system call overwrites the existing file without
+		 * notice. Also, active scripts must not be disrupted by renaming a script.
+		 * That is why we use a link(newpath) [activate newpath] unlink(oldpath)
+		 */
+
+		/* Link to the new path */
+		ret = link(fscript->path, newpath);
+		if ( ret >= 0 ) {
+			/* Is the requested script active? */
+			if ( sieve_script_is_active(script) > 0 ) {
+				/* Active; make active link point to the new copy */
+				i_assert( fstorage->link_path != NULL );
+				link_path = t_strconcat
+					( fstorage->link_path, newfile, NULL );
+
+				ret = sieve_file_storage_active_replace_link(fstorage, link_path);
+			}
+
+			if ( ret >= 0 ) {
+				/* If all is good, remove the old link */
+				if ( unlink(fscript->path) < 0 ) {
+					sieve_script_sys_error(script,
+						"Failed to clean up after rename: "
+						"unlink(%s) failed: %m", fscript->path);
+				}
+
+				if ( script->name != NULL && *script->name != '\0' )
+					script->name = p_strdup(script->pool, newname);
+				fscript->path = p_strdup(script->pool, newpath);
+				fscript->filename = p_strdup(script->pool, newfile);
+			} else {
+				/* If something went wrong, remove the new link to restore previous
+				 * state
+				 */
+				if ( unlink(newpath) < 0 ) {
+					sieve_script_sys_error(script,
+						"Failed to clean up after failed rename: "
+						"unlink(%s) failed: %m", newpath);
+				}
+			}
+		} else {
+			/* Our efforts failed right away */
+			switch ( errno ) {
+			case ENOENT:
+				sieve_script_set_error(script, SIEVE_ERROR_NOT_FOUND,
+					"Sieve script does not exist.");
+				break;
+			case EEXIST:
+				sieve_script_set_error(script, SIEVE_ERROR_EXISTS,
+					"A sieve script with that name already exists.");
+				break;
+			default:
+				sieve_script_set_critical(script,
+					"Failed to rename Sieve script: "
+					"link(%s, %s) failed: %m", fscript->path, newpath);
+			}
+		}
+	} T_END;
+
+	return ret;
+}
+
+/*
+ * Properties
+ */
+
+static int sieve_file_script_get_size
+(const struct sieve_script *script, uoff_t *size_r)
+{
+	struct sieve_file_script *fscript = (struct sieve_file_script *)script;
+
+	*size_r = fscript->st.st_size;
+	return 1;
+}
+
+const char *sieve_file_script_get_dirpath
+(const struct sieve_script *script)
+{
+	struct sieve_file_script *fscript = (struct sieve_file_script *)script;
+
+	if ( script->driver_name != sieve_file_script.driver_name )
+		return NULL;
+
+	return fscript->dirpath;
+}
+
+const char *sieve_file_script_get_path
+(const struct sieve_script *script)
+{
+	struct sieve_file_script *fscript = (struct sieve_file_script *)script;
+
+	if ( script->driver_name != sieve_file_script.driver_name )
+		return NULL;
+
+	return fscript->path;
+}
+
+/*
+ * Matching
+ */
+
+static bool sieve_file_script_equals
+(const struct sieve_script *script, const struct sieve_script *other)
+{
+	struct sieve_file_script *fscript =
+		(struct sieve_file_script *)script;
+	struct sieve_file_script *fother =
+		(struct sieve_file_script *)other;
+
+	return ( CMP_DEV_T(fscript->st.st_dev, fother->st.st_dev) &&
+		fscript->st.st_ino == fother->st.st_ino );
+}
+
+/*
+ * Driver definition
+ */
+
+const struct sieve_script sieve_file_script = {
+	.driver_name = SIEVE_FILE_STORAGE_DRIVER_NAME,
+	.v = {
+		.open = sieve_file_script_open,
+
+		.get_stream = sieve_file_script_get_stream,
+
+		.binary_read_metadata = sieve_file_script_binary_read_metadata,
+		.binary_load = sieve_file_script_binary_load,
+		.binary_save = sieve_file_script_binary_save,
+		.binary_get_prefix = sieve_file_script_binary_get_prefix,
+
+		.rename = sieve_file_storage_script_rename,
+		.delete = sieve_file_storage_script_delete,
+		.is_active = sieve_file_storage_script_is_active,
+		.activate = sieve_file_storage_script_activate,
+
+		.get_size = sieve_file_script_get_size,
+
+		.equals = sieve_file_script_equals
+	}
+};
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage-active.c
@@ -0,0 +1,403 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "path-util.h"
+#include "ioloop.h"
+#include "hostpid.h"
+#include "file-copy.h"
+
+#include "sieve-file-storage.h"
+
+#include <unistd.h>
+
+/*
+ * Symlink manipulation
+ */
+
+static int sieve_file_storage_active_read_link
+(struct sieve_file_storage *fstorage, const char **link_r)
+{
+	struct sieve_storage *storage = &fstorage->storage;
+	const char *error = NULL;
+	int ret;
+
+	ret = t_readlink(fstorage->active_path, link_r, &error);
+
+	if ( ret < 0 ) {
+		*link_r = NULL;
+
+		if ( errno == EINVAL ) {
+			/* Our symlink is no symlink. Report 'no active script'.
+			 * Activating a script will automatically resolve this, so
+			 * there is no need to panic on this one.
+			 */
+			if ( (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 &&
+				(storage->flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0 ) {
+				sieve_storage_sys_warning(storage,
+					"Active sieve script symlink %s is no symlink.",
+				  fstorage->active_path);
+			}
+			return 0;
+		}
+
+		if ( errno == ENOENT ) {
+			/* Symlink not found */
+			return 0;
+		}
+
+		/* We do need to panic otherwise */
+		sieve_storage_set_critical(storage,
+			"Performing t_readlink() on active sieve symlink '%s' failed: %s",
+			fstorage->active_path, error);
+		return -1;
+	}
+
+	/* ret is now assured to be valid, i.e. > 0 */
+	return 1;
+}
+
+static const char *sieve_file_storage_active_parse_link
+(struct sieve_file_storage *fstorage, const char *link,
+	const char **scriptname_r)
+{
+	struct sieve_storage *storage = &fstorage->storage;
+	const char *fname, *scriptname, *scriptpath, *link_dir;
+
+	/* Split off directory from link path */
+	fname = strrchr(fstorage->active_path, '/');
+	if (fname == NULL)
+		link_dir = "";
+	else
+		link_dir = t_strdup_until(fstorage->active_path, fname+1);
+
+	/* Split link into path and filename */
+	fname = strrchr(link, '/');
+	if ( fname != NULL ) {
+		scriptpath = t_strdup_until(link, fname+1);
+		fname++;
+	} else {
+		scriptpath = "";
+		fname = link;
+	}
+
+	/* Check the script name */
+	scriptname = sieve_script_file_get_scriptname(fname);
+
+	/* Warn if link is deemed to be invalid */
+	if ( scriptname == NULL ) {
+		sieve_storage_sys_warning(storage,
+			"Active Sieve script symlink %s is broken: "
+			"Invalid scriptname (points to %s).",
+			fstorage->active_path, link);
+		return NULL;
+	}
+
+	/* Check whether the path is any good */
+	const char *error = NULL;
+	if ( t_normpath_to(scriptpath, link_dir, &scriptpath, &error) < 0 ) {
+		sieve_storage_sys_warning(storage,
+			"Failed to check active Sieve script symlink %s: "
+			"Failed to normalize path (points to %s): %s",
+			fstorage->active_path, scriptpath, error);
+		return NULL;
+	}
+	if ( strcmp(scriptpath, fstorage->path) != 0 ) {
+		sieve_storage_sys_warning(storage,
+			"Active sieve script symlink %s is broken: "
+			"Invalid/unknown path to storage (points to %s).",
+			fstorage->active_path, scriptpath);
+		return NULL;
+	}
+
+	if ( scriptname_r != NULL )
+		*scriptname_r = scriptname;
+
+	return fname;
+}
+
+int sieve_file_storage_active_replace_link
+(struct sieve_file_storage *fstorage, const char *link_path)
+{
+	struct sieve_storage *storage = &fstorage->storage;
+	const char *active_path_new;
+	struct timeval *tv, tv_now;
+	int ret = 0;
+
+	tv = &ioloop_timeval;
+
+	for (;;) {
+		/* First the new symlink is created with a different filename */
+		active_path_new = t_strdup_printf
+			("%s-new.%s.P%sM%s.%s",
+				fstorage->active_path,
+				dec2str(tv->tv_sec), my_pid,
+				dec2str(tv->tv_usec), my_hostname);
+
+		ret = symlink(link_path, active_path_new);
+
+		if ( ret < 0 ) {
+			/* If link exists we try again later */
+			if ( errno == EEXIST ) {
+				/* Wait and try again - very unlikely */
+				sleep(2);
+				tv = &tv_now;
+				if (gettimeofday(&tv_now, NULL) < 0)
+					i_fatal("gettimeofday(): %m");
+				continue;
+			}
+
+			/* Other error, critical */
+			sieve_storage_set_critical(storage,
+				"Creating symlink() %s to %s failed: %m",
+				active_path_new, link_path);
+			return -1;
+		}
+
+		/* Link created */
+		break;
+	}
+
+	/* Replace the existing link. This activates the new script */
+	ret = rename(active_path_new, fstorage->active_path);
+
+	if ( ret < 0 ) {
+		/* Failed; created symlink must be deleted */
+		i_unlink(active_path_new);
+		sieve_storage_set_critical(storage,
+			"Performing rename() %s to %s failed: %m",
+			active_path_new, fstorage->active_path);
+		return -1;
+	}
+
+	return 1;
+}
+
+/*
+ * Active script properties
+ */
+
+int sieve_file_storage_active_script_get_file
+(struct sieve_file_storage *fstorage, const char **file_r)
+{
+	const char *link, *scriptfile;
+	int ret;
+
+	*file_r = NULL;
+
+	/* Read the active link */
+	if ( (ret=sieve_file_storage_active_read_link(fstorage, &link)) <= 0 )
+		return ret;
+
+	/* Parse the link */
+	scriptfile = sieve_file_storage_active_parse_link(fstorage, link, NULL);
+
+	if (scriptfile == NULL) {
+		/* Obviously, someone has been playing with our symlink:
+		 * ignore this situation and report 'no active script'.
+		 * Activation should fix this situation.
+		 */
+		return 0;
+	}
+
+	*file_r = scriptfile;
+	return 1;
+}
+
+int sieve_file_storage_active_script_get_name
+(struct sieve_storage *storage, const char **name_r)
+{
+	struct sieve_file_storage *fstorage =
+		(struct sieve_file_storage *)storage;
+	const char *link;
+	int ret;
+
+	*name_r = NULL;
+
+	/* Read the active link */
+	if ( (ret=sieve_file_storage_active_read_link
+		(fstorage, &link)) <= 0 )
+		return ret;
+
+	if ( sieve_file_storage_active_parse_link
+		(fstorage, link, name_r) == NULL ) {
+		/* Obviously, someone has been playing with our symlink:
+		 * ignore this situation and report 'no active script'.
+		 * Activation should fix this situation.
+		 */
+		return 0;
+	}
+
+	return 1;
+}
+
+/*
+ * Active script
+ */ 
+
+struct sieve_script *sieve_file_storage_active_script_open
+(struct sieve_storage *storage)
+{
+	struct sieve_file_storage *fstorage =
+		(struct sieve_file_storage *)storage;
+	struct sieve_file_script *fscript;
+	const char *scriptfile, *link;
+	int ret;
+
+	sieve_storage_clear_error(storage);
+
+	/* Read the active link */
+	if ( (ret=sieve_file_storage_active_read_link(fstorage, &link)) <= 0 ) {
+		if ( ret < 0 )
+			return NULL;
+
+		/* Try to open the active_path as a regular file */
+		if ( S_ISDIR(fstorage->st.st_mode) ) {
+			fscript = sieve_file_script_open_from_path(fstorage,
+				fstorage->active_path, NULL, NULL);			
+		} else {
+			fscript = sieve_file_script_open_from_name(fstorage, NULL);
+		}
+		if ( fscript == NULL ) {
+			if ( storage->error_code != SIEVE_ERROR_NOT_FOUND ) {
+				sieve_storage_set_critical(storage,
+					"Failed to open active path `%s' as regular file: %s",
+					fstorage->active_path, storage->error);
+			}
+			return NULL;
+		}
+
+		return &fscript->script;
+	} 
+
+	/* Parse the link */
+	scriptfile = sieve_file_storage_active_parse_link(fstorage, link, NULL);
+	if (scriptfile == NULL) {
+		/* Obviously someone has been playing with our symlink,
+		 * ignore this situation and report 'no active script'.
+		 * Activation should fix this situation.
+		 */
+		sieve_storage_set_error(storage, SIEVE_ERROR_NOT_FOUND,
+			"Active script is invalid");
+		return NULL;
+	}
+
+	fscript = sieve_file_script_open_from_path(fstorage,
+		fstorage->active_path,
+		sieve_script_file_get_scriptname(scriptfile),
+		NULL);
+	if ( fscript == NULL && storage->error_code == SIEVE_ERROR_NOT_FOUND ) {
+		sieve_storage_sys_warning(storage,
+			"Active sieve script symlink %s points to non-existent script "
+			"(points to %s).", fstorage->active_path, link);
+	}
+	return (fscript != NULL ? &fscript->script : NULL);
+}
+
+int sieve_file_storage_active_script_get_last_change
+(struct sieve_storage *storage, time_t *last_change_r)
+{
+	struct sieve_file_storage *fstorage =
+		(struct sieve_file_storage *)storage;
+	struct stat st;
+
+	/* Try direct lstat first */
+	if ( lstat(fstorage->active_path, &st) == 0 ) {
+		if ( !S_ISLNK(st.st_mode) ) {
+			*last_change_r = st.st_mtime;
+			return 0;
+		}
+	}
+	/* Check error */
+	else if ( errno != ENOENT ) {
+		sieve_storage_set_critical(storage,
+			"lstat(%s) failed: %m", fstorage->active_path);
+	}
+
+	/* Fall back to statting storage directory */
+	return sieve_storage_get_last_change(storage, last_change_r);
+}
+
+bool sieve_file_storage_active_rescue_regular
+(struct sieve_file_storage *fstorage)
+{
+	struct sieve_storage *storage = &fstorage->storage;
+	struct stat st;
+
+	/* Stat the file */
+	if ( lstat(fstorage->active_path, &st) != 0 ) {
+		if ( errno != ENOENT ) {
+			sieve_storage_set_critical(storage,
+				"Failed to stat active sieve script symlink (%s): %m.",
+				fstorage->active_path);
+			return FALSE;
+		}
+		return TRUE;
+	}
+
+	if ( S_ISLNK( st.st_mode ) ) {
+		sieve_storage_sys_debug(storage,
+			"Nothing to rescue %s.", fstorage->active_path);
+		return TRUE; /* Nothing to rescue */
+	}
+
+	/* Only regular files can be rescued */
+	if ( S_ISREG( st.st_mode ) ) {
+		const char *dstpath;
+		bool result = TRUE;
+
+ 		T_BEGIN {
+
+			dstpath = t_strconcat( fstorage->path, "/",
+				sieve_script_file_from_name("dovecot.orig"), NULL );
+			if ( file_copy(fstorage->active_path, dstpath, TRUE) < 1 ) {
+				sieve_storage_set_critical(storage,
+					"Active sieve script file '%s' is a regular file "
+					"and copying it to the script storage as '%s' failed. "
+					"This needs to be fixed manually.",
+					fstorage->active_path, dstpath);
+				result = FALSE;
+			} else {
+				sieve_storage_sys_info(storage,
+					"Moved active sieve script file '%s' "
+					"to script storage as '%s'.",
+					fstorage->active_path, dstpath);
+			}
+		} T_END;
+
+		return result;
+	}
+
+	sieve_storage_set_critical(storage,
+		"Active sieve script file '%s' is no symlink nor a regular file. "
+		"This needs to be fixed manually.", fstorage->active_path);
+	return FALSE;
+}
+
+int sieve_file_storage_deactivate(struct sieve_storage *storage)
+{
+	struct sieve_file_storage *fstorage =
+		(struct sieve_file_storage *)storage;
+	int ret;
+
+	if ( sieve_file_storage_pre_modify(storage) < 0 )
+		return -1;
+
+	if ( !sieve_file_storage_active_rescue_regular(fstorage) )
+		return -1;
+
+	/* Delete the symlink, so no script is active */
+	ret = unlink(fstorage->active_path);
+
+	if ( ret < 0 ) {
+		if ( errno != ENOENT ) {
+			sieve_storage_set_critical(storage,
+				"Failed to deactivate Sieve: "
+				"unlink(%s) failed: %m", fstorage->active_path);
+			return -1;
+		} else {
+			return 0;
+		}
+	}
+	return 1;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage-list.c
@@ -0,0 +1,141 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "eacces-error.h"
+
+#include "sieve-common.h"
+#include "sieve-script-private.h"
+
+#include "sieve-file-storage.h"
+
+#include <stdio.h>
+#include <dirent.h>
+
+struct sieve_file_list_context {
+	struct sieve_storage_list_context context;
+	pool_t pool;
+
+	const char *active;
+	const char *dir;
+	DIR *dirp;
+};
+
+struct sieve_storage_list_context *sieve_file_storage_list_init
+(struct sieve_storage *storage)
+{
+	struct sieve_file_storage *fstorage =
+		(struct sieve_file_storage *)storage;
+	struct sieve_file_list_context *flctx;
+	const char *active = NULL;
+	pool_t pool;
+	DIR *dirp;
+
+	/* Open the directory */
+	if ( (dirp = opendir(fstorage->path)) == NULL ) {
+		switch ( errno ) {
+		case ENOENT:
+			sieve_storage_set_error(storage,
+				SIEVE_ERROR_NOT_FOUND,
+				"Script storage not found");
+			break;
+		case EACCES:
+			sieve_storage_set_error(storage,
+				SIEVE_ERROR_NO_PERMISSION,
+				"Script storage not accessible");
+			sieve_storage_sys_error(storage,
+				"Failed to list scripts: "
+				"%s", eacces_error_get("opendir", fstorage->path));
+			break;
+		default:
+			sieve_storage_set_critical(storage,
+				"Failed to list scripts: "
+				"opendir(%s) failed: %m", fstorage->path);
+			break;
+		}
+		return NULL;
+	}
+
+	T_BEGIN {
+		/* Get the name of the active script */
+		if ( sieve_file_storage_active_script_get_file(fstorage, &active) < 0) {
+			flctx = NULL;
+		} else {
+			pool = pool_alloconly_create("sieve_file_list_context", 1024);
+			flctx = p_new(pool, struct sieve_file_list_context, 1);
+			flctx->pool = pool;
+			flctx->dirp = dirp;
+			flctx->active = ( active != NULL ? p_strdup(pool, active) : NULL );
+		}
+	} T_END;
+
+	if ( flctx == NULL ) {
+		if ( closedir(dirp) < 0) {
+			sieve_storage_sys_error(storage,
+				"closedir(%s) failed: %m", fstorage->path);	
+		}
+		return NULL;
+	}
+	return &flctx->context;
+}
+
+const char *sieve_file_storage_list_next
+(struct sieve_storage_list_context *ctx, bool *active)
+{
+	struct sieve_file_list_context *flctx =
+		(struct sieve_file_list_context *)ctx;
+	const struct sieve_file_storage *fstorage =
+		(const struct sieve_file_storage *)ctx->storage;
+	struct dirent *dp;
+	const char *scriptname;
+
+	*active = FALSE;
+
+	for (;;) {
+		if ( (dp = readdir(flctx->dirp)) == NULL )
+			return NULL;
+
+		scriptname = sieve_script_file_get_scriptname(dp->d_name);
+		if (scriptname != NULL ) {
+			/* Don't list our active sieve script link if the link
+			 * resides in the script dir (generally a bad idea).
+			 */
+			i_assert( fstorage->link_path != NULL );
+			if ( *(fstorage->link_path) == '\0' &&
+				strcmp(fstorage->active_fname, dp->d_name) == 0 )
+				continue;
+
+			break;
+		}
+	}
+
+	if ( flctx->active != NULL && strcmp(dp->d_name, flctx->active) == 0 ) {
+		*active = TRUE;
+		flctx->active = NULL;
+	}
+
+	return scriptname;
+}
+
+int sieve_file_storage_list_deinit(struct sieve_storage_list_context *lctx)
+{
+	struct sieve_file_list_context *flctx =
+		(struct sieve_file_list_context *)lctx;
+	const struct sieve_file_storage *fstorage =
+		(const struct sieve_file_storage *)lctx->storage;
+
+	if (closedir(flctx->dirp) < 0) {
+		sieve_storage_sys_error(lctx->storage,
+			"closedir(%s) failed: %m", fstorage->path);
+	}
+
+	pool_unref(&flctx->pool);
+
+	// FIXME: return error here if something went wrong during listing
+	return 0;
+}
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage-quota.c
@@ -0,0 +1,120 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve.h"
+#include "sieve-script.h"
+
+#include "sieve-file-storage.h"
+
+#include <stdio.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+int sieve_file_storage_quota_havespace
+(struct sieve_storage *storage, const char *scriptname, size_t size,
+	enum sieve_storage_quota *quota_r, uint64_t *limit_r)
+{
+	struct sieve_file_storage *fstorage =
+		(struct sieve_file_storage *)storage;
+	struct dirent *dp;
+	DIR *dirp;
+	uint64_t script_count = 1;
+	uint64_t script_storage = size;
+	int result = 1;
+
+	/* Open the directory */
+	if ( (dirp = opendir(fstorage->path)) == NULL ) {
+		sieve_storage_set_critical(storage,
+			"quota: opendir(%s) failed: %m", fstorage->path);
+		return -1;
+	}
+
+	/* Scan all files */
+	for (;;) {
+		const char *name;
+		bool replaced = FALSE;
+
+		/* Read next entry */
+		errno = 0;
+		if ( (dp = readdir(dirp)) == NULL ) {
+			if ( errno != 0 ) {
+				sieve_storage_set_critical(storage,
+					"quota: readdir(%s) failed: %m", fstorage->path);
+				result = -1;
+			}
+			break;
+		}
+
+		/* Parse filename */
+		name = sieve_script_file_get_scriptname(dp->d_name);
+
+		/* Ignore non-script files */
+		if ( name == NULL )
+			continue;
+
+		/* Don't list our active sieve script link if the link
+		 * resides in the script dir (generally a bad idea).
+		 */
+		i_assert( fstorage->link_path != NULL );
+		if ( *(fstorage->link_path) == '\0' &&
+			strcmp(fstorage->active_fname, dp->d_name) == 0 )
+			continue;
+
+		if ( strcmp(name, scriptname) == 0 )
+			replaced = TRUE;
+
+		/* Check count quota if necessary */
+		if ( storage->max_scripts > 0 ) {
+			if ( !replaced ) {
+				script_count++;
+
+				if ( script_count > storage->max_scripts ) {
+					*quota_r = SIEVE_STORAGE_QUOTA_MAXSCRIPTS;
+					*limit_r = storage->max_scripts;
+					result = 0;
+					break;
+				}
+			}
+		}
+
+		/* Check storage quota if necessary */
+		if ( storage->max_storage > 0 ) {
+			const char *path;
+			struct stat st;
+
+			path = t_strconcat(fstorage->path, "/", dp->d_name, NULL);
+
+			if ( stat(path, &st) < 0 ) {
+				sieve_storage_sys_warning(storage,
+					"quota: stat(%s) failed: %m", path);
+				continue;
+			}
+
+			if ( !replaced ) {
+				script_storage += st.st_size;
+
+				if ( script_storage > storage->max_storage ) {
+					*quota_r = SIEVE_STORAGE_QUOTA_MAXSTORAGE;
+					*limit_r = storage->max_storage;
+					result = 0;
+					break;
+				}
+			}
+		}
+	}
+
+	/* Close directory */
+	if ( closedir(dirp) < 0 ) {
+		sieve_storage_set_critical(storage,
+			"quota: closedir(%s) failed: %m", fstorage->path);
+	}
+	return result;
+}
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage-save.c
@@ -0,0 +1,534 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "hostpid.h"
+#include "ioloop.h"
+#include "array.h"
+#include "buffer.h"
+#include "istream.h"
+#include "ostream.h"
+#include "str.h"
+#include "eacces-error.h"
+#include "safe-mkstemp.h"
+
+#include "sieve-file-storage.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <utime.h>
+
+struct sieve_file_save_context {
+	struct sieve_storage_save_context context;
+
+	pool_t pool;
+
+	struct ostream *output;
+	int fd;
+	const char *tmp_path;
+
+	time_t mtime;
+
+	bool failed:1;
+	bool finished:1;
+};
+
+static const char *sieve_generate_tmp_filename(const char *scriptname)
+{
+	static struct timeval last_tv = { 0, 0 };
+	struct timeval tv;
+
+	/* use secs + usecs to guarantee uniqueness within this process. */
+	if (ioloop_timeval.tv_sec > last_tv.tv_sec ||
+		(ioloop_timeval.tv_sec == last_tv.tv_sec &&
+		ioloop_timeval.tv_usec > last_tv.tv_usec)) {
+		tv = ioloop_timeval;
+	} else {
+		tv = last_tv;
+		if (++tv.tv_usec == 1000000) {
+			tv.tv_sec++;
+			tv.tv_usec = 0;
+		}
+	}
+	last_tv = tv;
+
+	if ( scriptname == NULL ) {
+		return t_strdup_printf("%s.M%sP%s.%s.tmp",
+			dec2str(tv.tv_sec), dec2str(tv.tv_usec),
+			my_pid, my_hostname);
+	}
+
+	scriptname = t_strdup_printf("%s_%s.M%sP%s.%s",
+		scriptname,	dec2str(tv.tv_sec), dec2str(tv.tv_usec),
+		my_pid, my_hostname);
+	return sieve_script_file_from_name(scriptname);
+}
+
+static int sieve_file_storage_create_tmp
+(struct sieve_file_storage *fstorage, const char *scriptname,
+	const char **fpath_r)
+{
+	struct sieve_storage *storage = &fstorage->storage;
+	struct stat st;
+	unsigned int prefix_len;
+	const char *tmp_fname = NULL;
+	string_t *path;
+	int fd;
+
+	path = t_str_new(256);
+	str_append(path, fstorage->path);
+	str_append(path, "/tmp/");
+	prefix_len = str_len(path);
+
+	for (;;) {
+		tmp_fname = sieve_generate_tmp_filename(scriptname);
+		str_truncate(path, prefix_len);
+		str_append(path, tmp_fname);
+
+		/* stat() first to see if it exists. pretty much the only
+		   possibility of that happening is if time had moved
+		   backwards, but even then it's highly unlikely. */
+		if (stat(str_c(path), &st) == 0) {
+			/* try another file name */
+		} else if (errno != ENOENT) {
+			switch ( errno ) {
+			case EACCES:
+				sieve_storage_set_critical(storage, "save: %s",
+					eacces_error_get("stat", fstorage->path));
+				break;
+			default:
+				sieve_storage_set_critical(storage, "save: "
+					"stat(%s) failed: %m", str_c(path));
+				break;
+			}
+			return -1;
+		} else {
+			/* doesn't exist */
+			mode_t old_mask = umask(0777 & ~(fstorage->file_create_mode));
+			fd = open(str_c(path),
+				O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0777);
+			umask(old_mask);
+
+			if (fd != -1 || errno != EEXIST)
+				break;
+			/* race condition between stat() and open().
+				highly unlikely. */
+		}
+	}
+
+	*fpath_r = str_c(path);
+	if (fd == -1) {
+		if (ENOQUOTA(errno)) {
+			sieve_storage_set_error(storage,
+				SIEVE_ERROR_NO_QUOTA,
+				"Not enough disk quota");
+		} else {
+			switch ( errno ) {
+			case EACCES:
+				sieve_storage_set_critical(storage, "save: %s",
+					eacces_error_get("open", fstorage->path));
+				break;
+			default:
+				sieve_storage_set_critical(storage, "save: "
+					"open(%s) failed: %m", str_c(path));
+				break;
+			}
+		}
+	}
+
+	return fd;
+}
+
+static int sieve_file_storage_script_move
+(struct sieve_file_save_context *fsctx, const char *dst)
+{
+	struct sieve_storage_save_context *sctx = &fsctx->context;
+	struct sieve_storage *storage = sctx->storage;
+	int result = 0;
+
+	T_BEGIN {
+
+		/* Using rename() to ensure existing files are replaced
+		 * without conflicts with other processes using the same
+		 * file. The kernel wont fully delete the original until
+		 * all processes have closed the file.
+		 */
+		if (rename(fsctx->tmp_path, dst) == 0)
+			result = 0;
+		else {
+			result = -1;
+			if ( ENOQUOTA(errno) ) {
+				sieve_storage_set_error(storage,
+					SIEVE_ERROR_NO_QUOTA,
+					"Not enough disk quota");
+			} else if ( errno == EACCES ) {
+				sieve_storage_set_critical(storage, "save: "
+					"Failed to save Sieve script: "
+					"%s", eacces_error_get("rename", dst));
+			} else {
+				sieve_storage_set_critical(storage, "save: "
+					"rename(%s, %s) failed: %m", fsctx->tmp_path, dst);
+			}
+		}
+
+		/* Always destroy temp file */
+		if (unlink(fsctx->tmp_path) < 0 && errno != ENOENT) {
+			sieve_storage_sys_warning(storage, "save: "
+				"unlink(%s) failed: %m", fsctx->tmp_path);
+		}
+	} T_END;
+
+	return result;
+}
+
+struct sieve_storage_save_context *
+sieve_file_storage_save_init(struct sieve_storage *storage,
+	const char *scriptname, struct istream *input)
+{
+	struct sieve_file_storage *fstorage =
+		(struct sieve_file_storage *)storage;
+	struct sieve_file_save_context *fsctx;
+	pool_t pool;
+	const char *path;
+	int fd;
+
+	if ( sieve_file_storage_pre_modify(storage) < 0 )
+		return NULL;
+
+	if ( scriptname != NULL ) {
+		/* Prevent overwriting the active script link when it resides in the
+		 * sieve storage directory.
+		 */
+		i_assert( fstorage->link_path != NULL );
+		if ( *(fstorage->link_path) == '\0' ) {
+			const char *svext;
+			size_t namelen;
+
+			svext = strrchr(fstorage->active_fname, '.');
+			namelen = svext - fstorage->active_fname;
+			if ( svext != NULL && str_begins(svext+1, "sieve") &&
+			     strlen(scriptname) == namelen &&
+			     str_begins(fstorage->active_fname, scriptname) )
+			{
+				sieve_storage_set_error(storage,
+					SIEVE_ERROR_BAD_PARAMS,
+					"Script name `%s' is reserved for internal use.",
+					scriptname);
+				return NULL;
+			}
+		}
+	}
+
+	T_BEGIN {
+		fd = sieve_file_storage_create_tmp(fstorage, scriptname, &path);
+		if (fd == -1) {
+			fsctx = NULL;
+		} else {
+			pool = pool_alloconly_create("sieve_file_save_context", 1024);
+			fsctx = p_new(pool, struct sieve_file_save_context, 1);
+			fsctx->context.scriptname = p_strdup(pool, scriptname);
+			fsctx->context.input = input;
+			fsctx->context.pool = pool;
+			fsctx->fd = fd;
+			fsctx->output = o_stream_create_fd(fsctx->fd, 0);
+			fsctx->tmp_path = p_strdup(pool, path);
+		}
+	} T_END;
+
+	return &fsctx->context;
+}
+
+int sieve_file_storage_save_continue
+(struct sieve_storage_save_context *sctx)
+{
+	struct sieve_file_save_context *fsctx =
+		(struct sieve_file_save_context *)sctx;
+
+	switch (o_stream_send_istream(fsctx->output, sctx->input)) {
+	case OSTREAM_SEND_ISTREAM_RESULT_FINISHED:
+	case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT:
+		return 0;
+	case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT:
+		i_unreached();
+	case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT:
+		sieve_storage_set_critical(sctx->storage,
+			"save: read(%s) failed: %s",
+			i_stream_get_name(sctx->input),
+			i_stream_get_error(sctx->input));
+		return -1;
+	case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT:
+		sieve_storage_set_critical(sctx->storage,
+			"save: write(%s) failed: %s", fsctx->tmp_path,
+			o_stream_get_error(fsctx->output));
+		return -1;
+	}
+	return 0;
+}
+
+int sieve_file_storage_save_finish
+(struct sieve_storage_save_context *sctx)
+{
+	struct sieve_file_save_context *fsctx =
+		(struct sieve_file_save_context *)sctx;
+	struct sieve_storage *storage = sctx->storage;
+	int output_errno;
+
+	if ( sctx->failed && fsctx->fd == -1 ) {
+		/* tmp file creation failed */
+		return -1;
+	}
+
+	T_BEGIN {
+		output_errno = fsctx->output->stream_errno;
+		o_stream_destroy(&fsctx->output);
+
+		if ( fsync(fsctx->fd) < 0 ) {
+			sieve_storage_set_critical(storage, "save: "
+				"fsync(%s) failed: %m", fsctx->tmp_path);
+			sctx->failed = TRUE;
+		}
+		if ( close(fsctx->fd) < 0 ) {
+			sieve_storage_set_critical(storage, "save: "
+				"close(%s) failed: %m", fsctx->tmp_path);
+			sctx->failed = TRUE;
+		}
+		fsctx->fd = -1;
+
+		if ( sctx->failed ) {
+			/* delete the tmp file */
+			if (unlink(fsctx->tmp_path) < 0 && errno != ENOENT) {
+				sieve_storage_sys_warning(storage, "save: "
+					"unlink(%s) failed: %m", fsctx->tmp_path);
+			}
+
+			fsctx->tmp_path = NULL;
+			
+			errno = output_errno;
+			if ( ENOQUOTA(errno) ) {
+				sieve_storage_set_error(storage,
+					SIEVE_ERROR_NO_QUOTA,
+					"Not enough disk quota");
+			} else if ( errno != 0 ) {
+				sieve_storage_set_critical(storage, "save: "
+					"write(%s) failed: %m", fsctx->tmp_path);
+			}
+		}
+	} T_END;
+
+	return ( sctx->failed ? -1 : 0 );
+}
+
+struct sieve_script *sieve_file_storage_save_get_tempscript
+(struct sieve_storage_save_context *sctx)
+{
+	struct sieve_file_save_context *fsctx =
+		(struct sieve_file_save_context *)sctx;
+	struct sieve_file_storage *fstorage = 
+		(struct sieve_file_storage *)sctx->storage;
+	struct sieve_file_script *tmpscript;
+	enum sieve_error error;
+	const char *scriptname;
+
+	if (sctx->failed)
+		return NULL;
+
+	if ( sctx->scriptobject != NULL )
+		return sctx->scriptobject;
+
+	scriptname =
+		( sctx->scriptname == NULL ? "" : sctx->scriptname );
+	tmpscript = sieve_file_script_open_from_path
+		(fstorage, fsctx->tmp_path, scriptname, &error);
+
+	if ( tmpscript == NULL ) {
+		if ( error == SIEVE_ERROR_NOT_FOUND ) {
+			sieve_storage_set_critical(sctx->storage, "save: "
+				"Temporary script file `%s' got lost, "
+				"which should not happen (possibly deleted externally).",
+				fsctx->tmp_path);
+		} else {
+			sieve_storage_set_critical(sctx->storage, "save: "
+				"Failed to open temporary script file `%s'",
+				fsctx->tmp_path);
+		}
+		return NULL;
+	}
+
+	return &tmpscript->script;
+}
+
+static void sieve_file_storage_update_mtime
+(struct sieve_storage *storage, const char *path, time_t mtime)
+{
+	struct utimbuf times = { .actime = mtime, .modtime = mtime };
+
+	if ( utime(path, &times) < 0 ) {
+		switch ( errno ) {	
+		case ENOENT:
+			break;
+		case EACCES:
+			sieve_storage_sys_error(storage, "save: "
+				"%s", eacces_error_get("utime", path));
+			break;
+		default:
+			sieve_storage_sys_error(storage, "save: "
+				"utime(%s) failed: %m", path);
+		}
+	}
+}
+
+int sieve_file_storage_save_commit
+(struct sieve_storage_save_context *sctx)
+{
+	struct sieve_file_save_context *fsctx =
+		(struct sieve_file_save_context *)sctx;
+	struct sieve_storage *storage = sctx->storage;
+	struct sieve_file_storage *fstorage =
+		(struct sieve_file_storage *)sctx->storage;
+	const char *dest_path;
+	bool failed = FALSE;
+
+	i_assert(fsctx->output == NULL);
+
+	T_BEGIN {
+		dest_path = t_strconcat(fstorage->path, "/",
+			sieve_script_file_from_name(sctx->scriptname), NULL);
+
+		failed = ( sieve_file_storage_script_move(fsctx, dest_path) < 0 );
+		if ( sctx->mtime != (time_t)-1 )
+			sieve_file_storage_update_mtime(storage, dest_path, sctx->mtime);
+	} T_END;
+
+	pool_unref(&sctx->pool);
+	
+	return ( failed ? -1 : 0 );
+}
+
+void sieve_file_storage_save_cancel(struct sieve_storage_save_context *sctx)
+{
+	struct sieve_file_save_context *fsctx =
+		(struct sieve_file_save_context *)sctx;
+	struct sieve_storage *storage = sctx->storage;
+
+	if (fsctx->tmp_path != NULL &&
+		unlink(fsctx->tmp_path) < 0 && errno != ENOENT) {
+		sieve_storage_sys_warning(storage, "save: "
+			"unlink(%s) failed: %m", fsctx->tmp_path);
+	}
+
+	i_assert(fsctx->output == NULL);
+}
+
+static int
+sieve_file_storage_save_to(struct sieve_file_storage *fstorage,
+	string_t *temp_path, struct istream *input,
+	const char *target)
+{
+	struct sieve_storage *storage = &fstorage->storage;
+	struct ostream *output;
+	int fd;
+
+	// FIXME: move this to base class
+	// FIXME: use io_stream_temp
+
+	fd = safe_mkstemp_hostpid
+		(temp_path, fstorage->file_create_mode, (uid_t)-1, (gid_t)-1);
+	if ( fd < 0 ) {
+		if ( errno == EACCES ) {
+			sieve_storage_set_critical(storage,
+				"Failed to create temporary file: %s",
+				eacces_error_get_creating("open", str_c(temp_path)));
+		} else {
+			sieve_storage_set_critical(storage,
+				"Failed to create temporary file: open(%s) failed: %m",
+				str_c(temp_path));
+		}
+		return -1;
+	}
+
+	output = o_stream_create_fd(fd, 0);
+	switch ( o_stream_send_istream(output, input) ) {
+	case OSTREAM_SEND_ISTREAM_RESULT_FINISHED:
+		break;
+	case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT:
+	case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT:
+		i_unreached();
+	case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT:
+		sieve_storage_set_critical(storage,
+			"read(%s) failed: %s", i_stream_get_name(input),
+			i_stream_get_error(input));
+		o_stream_destroy(&output);
+		i_unlink(str_c(temp_path));
+		return -1;
+	case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT:
+		sieve_storage_set_critical(storage,
+			"write(%s) failed: %s", str_c(temp_path),
+			o_stream_get_error(output));
+		o_stream_destroy(&output);
+		i_unlink(str_c(temp_path));
+		return -1;
+	}
+	o_stream_destroy(&output);
+
+	if ( rename(str_c(temp_path), target) < 0 ) {
+		if ( ENOQUOTA(errno) ) {
+			sieve_storage_set_error(storage,
+				SIEVE_ERROR_NO_QUOTA,
+				"Not enough disk quota");
+		} else if ( errno == EACCES ) {
+			sieve_storage_set_critical(storage,
+				"%s", eacces_error_get("rename", target));
+		} else {
+			sieve_storage_set_critical(storage,
+				"rename(%s, %s) failed: %m",
+				str_c(temp_path), target);
+		}
+		i_unlink(str_c(temp_path));
+	}
+	return 0;
+}
+
+int sieve_file_storage_save_as
+(struct sieve_storage *storage, struct istream *input,
+	const char *name)
+{
+	struct sieve_file_storage *fstorage =
+		(struct sieve_file_storage *)storage;
+	string_t *temp_path;
+	const char *dest_path;
+
+	temp_path = t_str_new(256);
+	str_append(temp_path, fstorage->path);
+	str_append(temp_path, "/tmp/");
+	str_append(temp_path, sieve_script_file_from_name(name));
+	str_append_c(temp_path, '.');
+
+	dest_path = t_strconcat(fstorage->path, "/",
+		sieve_script_file_from_name(name), NULL);
+
+	return sieve_file_storage_save_to
+		(fstorage, temp_path, input, dest_path);
+}
+
+int sieve_file_storage_save_as_active
+(struct sieve_storage *storage, struct istream *input,
+	time_t mtime)
+{
+	struct sieve_file_storage *fstorage =
+		(struct sieve_file_storage *)storage;
+	string_t *temp_path;
+
+	temp_path = t_str_new(256);
+	str_append(temp_path, fstorage->active_path);
+	str_append_c(temp_path, '.');
+
+	if ( sieve_file_storage_save_to
+		(fstorage, temp_path, input, fstorage->active_path) < 0 )
+		return -1;
+
+	sieve_file_storage_update_mtime
+		(storage, fstorage->active_path, mtime);
+	return 0;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage.c
@@ -0,0 +1,919 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "path-util.h"
+#include "home-expand.h"
+#include "ioloop.h"
+#include "mkdir-parents.h"
+#include "eacces-error.h"
+#include "unlink-old-files.h"
+#include "mail-storage-private.h"
+
+#include "sieve.h"
+#include "sieve-common.h"
+#include "sieve-settings.h"
+#include "sieve-error-private.h"
+#include "sieve-settings.h"
+
+#include "sieve-file-storage.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <utime.h>
+#include <sys/time.h>
+
+
+#define MAX_DIR_CREATE_MODE 0770
+
+/*
+ * Utility
+ */
+
+const char *sieve_file_storage_path_extend
+(struct sieve_file_storage *fstorage, const char *filename)
+{
+	const char *path = fstorage->path;
+
+	if ( path[strlen(path)-1] == '/' )
+		return t_strconcat(path, filename, NULL);
+
+	return t_strconcat(path, "/", filename , NULL);
+}
+
+/*
+ *
+ */
+
+static int sieve_file_storage_stat
+(struct sieve_file_storage *fstorage, const char *path,
+	enum sieve_error *error_r)
+{
+	struct sieve_storage *storage = &fstorage->storage;
+	struct stat st;
+	const char *abspath, *error;
+
+	if ( lstat(path, &st) == 0 ) {
+		fstorage->lnk_st = st;
+
+		if ( !S_ISLNK(st.st_mode) || stat(path, &st) == 0 ) {
+			fstorage->st = st;
+			return 0;
+		}
+	}
+
+	switch ( errno ) {
+	case ENOENT:
+		if (t_abspath(path, &abspath, &error) < 0) {
+			sieve_storage_set_critical(storage,
+				"t_abspath(%s) failed: %s", path, error);
+			*error_r = SIEVE_ERROR_TEMP_FAILURE;
+			break;
+		}
+		sieve_storage_sys_debug(storage,
+			"Storage path `%s' not found", abspath);
+		sieve_storage_set_internal_error(storage); // should be overriden
+		*error_r = SIEVE_ERROR_NOT_FOUND;
+		break;
+	case EACCES:
+		sieve_storage_set_critical(storage,
+			"Failed to stat sieve storage path: %s",
+			eacces_error_get("stat", path));
+		*error_r = SIEVE_ERROR_NO_PERMISSION;
+		break;
+	default:
+		sieve_storage_set_critical(storage,
+			"Failed to stat sieve storage path: "
+			"stat(%s) failed: %m", path);
+		*error_r = SIEVE_ERROR_TEMP_FAILURE;
+		break;
+	}
+
+	return -1;
+}
+
+static const char *sieve_storage_get_relative_link_path
+	(const char *active_path, const char *storage_dir)
+{
+	const char *link_path, *p;
+	size_t pathlen;
+
+	/* Determine to what extent the sieve storage and active script
+	 * paths match up. This enables the managed symlink to be short and the
+	 * sieve storages can be moved around without trouble (if the active
+	 * script path is common to the script storage).
+	 */
+	p = strrchr(active_path, '/');
+	if ( p == NULL ) {
+		link_path = storage_dir;
+	} else {
+		pathlen = p - active_path;
+
+		if ( strncmp( storage_dir, active_path, pathlen ) == 0 &&
+			(storage_dir[pathlen] == '/' || storage_dir[pathlen] == '\0') )
+		{
+			if ( storage_dir[pathlen] == '\0' )
+				link_path = "";
+			else
+				link_path = storage_dir + pathlen + 1;
+		} else
+			link_path = storage_dir;
+	}
+
+	/* Add trailing '/' when link path is not empty
+	 */
+	pathlen = strlen(link_path);
+	if ( pathlen != 0 && link_path[pathlen-1] != '/')
+		return t_strconcat(link_path, "/", NULL);
+
+	return t_strdup(link_path);
+}
+
+static mode_t get_dir_mode(mode_t mode)
+{
+	/* Add the execute bit if either read or write bit is set */
+
+	if ((mode & 0600) != 0) mode |= 0100;
+	if ((mode & 0060) != 0) mode |= 0010;
+	if ((mode & 0006) != 0) mode |= 0001;
+
+	return mode;
+}
+
+static int mkdir_verify
+(struct sieve_storage *storage, const char *dir,
+	mode_t mode, gid_t gid, const char *gid_origin)
+{
+	struct stat st;
+
+	if ( stat(dir, &st) == 0 )
+		return 0;
+
+	if ( errno == EACCES ) {
+		sieve_storage_sys_error(storage,
+			"mkdir_verify: %s", eacces_error_get("stat", dir));
+		return -1;
+	} else if ( errno != ENOENT ) {
+		sieve_storage_sys_error(storage,
+			"mkdir_verify: stat(%s) failed: %m", dir);
+		return -1;
+	}
+
+	if ( mkdir_parents_chgrp(dir, mode, gid, gid_origin) == 0 ) {
+		sieve_storage_sys_debug(storage,
+			"Created storage directory %s", dir);
+		return 0;
+	}
+
+	switch ( errno ) {
+	case EEXIST:
+		return 0;
+	case ENOENT:
+		sieve_storage_sys_error(storage,
+			"Storage was deleted while it was being created");
+		break;
+	case EACCES:
+		sieve_storage_sys_error(storage,
+			"%s",	eacces_error_get_creating("mkdir_parents_chgrp", dir));
+		break;
+	default:
+		sieve_storage_sys_error(storage,
+			"mkdir_parents_chgrp(%s) failed: %m", dir);
+		break;
+	}
+
+	return -1;
+}
+
+static int check_tmp(struct sieve_storage *storage, const char *path)
+{
+	struct stat st;
+
+	/* If tmp/ directory exists, we need to clean it up once in a while */
+	if ( stat(path, &st) < 0 ) {
+		if ( errno == ENOENT )
+			return 0;
+		if ( errno == EACCES ) {
+			sieve_storage_sys_error(storage,
+				"check_tmp: %s", eacces_error_get("stat", path));
+			return -1;
+		}
+		sieve_storage_sys_error(storage,
+			"check_tmp: stat(%s) failed: %m", path);
+		return -1;
+	}
+
+	if ( st.st_atime > st.st_ctime + SIEVE_FILE_STORAGE_TMP_DELETE_SECS ) {
+		/* The directory should be empty. we won't do anything
+		   until ctime changes. */
+	} else if ( st.st_atime < ioloop_time - SIEVE_FILE_STORAGE_TMP_SCAN_SECS ) {
+		/* Time to scan */
+		(void)unlink_old_files(path, "",
+			ioloop_time - SIEVE_FILE_STORAGE_TMP_DELETE_SECS);
+	}
+	return 1;
+}
+
+static struct sieve_storage *sieve_file_storage_alloc(void)
+{
+	struct sieve_file_storage *fstorage;
+	pool_t pool;
+
+	pool = pool_alloconly_create("sieve_file_storage", 2048);
+	fstorage = p_new(pool, struct sieve_file_storage, 1);
+	fstorage->storage = sieve_file_storage;
+	fstorage->storage.pool = pool;
+
+	return &fstorage->storage;
+}
+
+static int sieve_file_storage_get_full_path
+(struct sieve_file_storage *fstorage, const char **storage_path,
+	enum sieve_error *error_r)
+{
+	struct sieve_storage *storage = &fstorage->storage;
+	struct sieve_instance *svinst = storage->svinst;
+	const char *path = *storage_path;
+
+	/* Get full storage path */
+
+	if ( path != NULL &&
+		((path[0] == '~' && (path[1] == '/' || path[1] == '\0')) ||
+		(((svinst->flags & SIEVE_FLAG_HOME_RELATIVE) != 0 ) && path[0] != '/')) ) {
+		/* home-relative path. change to absolute. */
+		const char *home = sieve_environment_get_homedir(svinst);
+
+		if ( home != NULL ) {
+			if ( path[0] == '~' && (path[1] == '/' || path[1] == '\0') )
+				path = home_expand_tilde(path, home);
+			else
+				path = t_strconcat(home, "/", path, NULL);
+		} else {
+			sieve_storage_set_critical(storage,
+				"Sieve storage path `%s' is relative to home directory, "
+				"but home directory is not available.", path);
+			*error_r = SIEVE_ERROR_TEMP_FAILURE;
+			return -1;
+		}
+	}
+	*storage_path = path;
+	return 0;
+}
+
+static int sieve_file_storage_get_full_active_path
+(struct sieve_file_storage *fstorage, const char **active_path,
+	enum sieve_error *error_r)
+{
+	struct sieve_storage *storage = &fstorage->storage;
+	struct sieve_instance *svinst = storage->svinst;
+	const char *path = *active_path;
+
+	if ( path != NULL && *path != '\0' &&
+		((path[0] == '~' && (path[1] == '/' || path[1] == '\0')) ||
+		(((svinst->flags & SIEVE_FLAG_HOME_RELATIVE) != 0 ) && path[0] != '/'))
+		)	{
+		/* home-relative path. change to absolute. */
+		const char *home = sieve_environment_get_homedir(svinst);
+
+		if ( home != NULL ) {
+			if ( path[0] == '~' && (path[1] == '/' || path[1] == '\0') )
+				path = home_expand_tilde(path, home);
+			else
+				path = t_strconcat(home, "/", path, NULL);
+		} else {
+			sieve_storage_set_critical(storage,
+				"Sieve storage active script path `%s' is relative to home directory, "
+				"but home directory is not available.", path);
+			*error_r = SIEVE_ERROR_TEMP_FAILURE;
+			return -1;
+		}
+	}
+	*active_path = path;
+	return 0;
+}
+
+static int sieve_file_storage_init_common
+(struct sieve_file_storage *fstorage, const char *active_path,
+	const char *storage_path, bool exists, enum sieve_error *error_r)
+	ATTR_NULL(2, 3)
+{
+	struct sieve_storage *storage = &fstorage->storage;
+	const char *tmp_dir, *link_path, *active_fname, *storage_dir, *error;
+	bool have_link = FALSE;
+	int ret;
+
+	i_assert( storage_path != NULL || active_path != NULL );
+
+	fstorage->prev_mtime = (time_t)-1;
+
+	/* Get active script path */
+
+	if ( sieve_file_storage_get_full_active_path
+		(fstorage, &active_path, error_r) < 0 )
+		return -1;
+
+	/* Get the filename for the active script link */
+
+	active_fname = NULL;
+	if ( active_path != NULL && *active_path != '\0' ) {
+		const char *active_dir;
+
+		active_fname = strrchr(active_path, '/');
+		if ( active_fname == NULL ) {
+			active_fname = active_path;
+			active_dir = "";
+		} else {
+			active_dir = t_strdup_until(active_path, active_fname);
+			active_fname++;
+		}
+
+		if ( *active_fname == '\0' ) {
+			/* Link cannot be just a path ending in '/' */
+			sieve_storage_set_critical(storage,
+				"Path to %sscript must include the filename (path=%s)",
+				( storage_path != NULL ? "active link/" : "" ),
+				active_path);
+			*error_r = SIEVE_ERROR_TEMP_FAILURE;
+			return -1;
+		}
+
+		if (t_realpath(active_dir, &active_dir, &error) < 0) {
+			if (errno != ENOENT) {
+				sieve_storage_sys_error(storage,
+					"Failed to normalize active script directory (path=%s): %s",
+					active_dir, error);
+				*error_r = SIEVE_ERROR_TEMP_FAILURE;
+				return -1;
+			} 
+			sieve_storage_sys_debug(storage,
+				"Failed to normalize active script directory (path=%s): "
+				"Part of the path does not exist (yet)",
+				active_dir);			
+		} else {
+			active_path = t_abspath_to(active_fname, active_dir);
+		}
+
+		sieve_storage_sys_debug(storage,
+			"Using %sSieve script path: %s",
+			( storage_path != NULL ? "active " : "" ),
+			active_path);
+
+		fstorage->active_path = p_strdup(storage->pool, active_path);
+		fstorage->active_fname = p_strdup(storage->pool, active_fname);
+	}
+
+	/* Determine storage path */
+
+	storage_dir = storage_path;
+	if ( storage_path != NULL && *storage_path != '\0' ) {
+		sieve_storage_sys_debug(storage,
+			"Using script storage path: %s", storage_path);
+		have_link = TRUE;
+
+	} else {
+		if ((storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 ) {
+			sieve_storage_set_critical(storage,
+				"Storage path cannot be empty for write access");
+			*error_r = SIEVE_ERROR_TEMP_FAILURE;
+			return -1;
+		}
+
+		storage_path = active_path;
+	}
+
+	i_assert(storage_path != NULL);
+
+	/* Prepare for write access */
+
+	if ( (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 ) {
+		mode_t dir_create_mode, file_create_mode;
+		gid_t file_create_gid;
+		const char *file_create_gid_origin;
+
+		/* Use safe permission defaults */
+		file_create_mode = 0600;
+		dir_create_mode = 0700;
+		file_create_gid = (gid_t)-1;
+		file_create_gid_origin = "defaults";
+
+		/* Get actual permissions */
+		if ( exists ) {
+			file_create_mode = (fstorage->st.st_mode & 0666) | 0600;
+			dir_create_mode = (fstorage->st.st_mode & 0777) | 0700;
+			file_create_gid_origin = storage_dir;
+
+			if ( !S_ISDIR(fstorage->st.st_mode) ) {
+				/* We're getting permissions from a file.
+				   Apply +x modes as necessary. */
+				dir_create_mode = get_dir_mode(dir_create_mode);
+			}
+
+			if (S_ISDIR(fstorage->st.st_mode) &&
+				(fstorage->st.st_mode & S_ISGID) != 0) {
+				/* Directory's GID is used automatically for new files */
+				file_create_gid = (gid_t)-1;
+			} else if ((fstorage->st.st_mode & 0070) >> 3 ==
+				(fstorage->st.st_mode & 0007)) {
+				/* Group has same permissions as world, so don't bother changing it */
+				file_create_gid = (gid_t)-1;
+			} else if (getegid() == fstorage->st.st_gid) {
+				/* Using our own gid, no need to change it */
+				file_create_gid = (gid_t)-1;
+			} else {
+				file_create_gid = fstorage->st.st_gid;
+			}
+		}
+
+		sieve_storage_sys_debug(storage,
+			"Using permissions from %s: mode=0%o gid=%ld",
+			file_create_gid_origin, (int)dir_create_mode,
+			file_create_gid == (gid_t)-1 ? -1L : (long)file_create_gid);
+
+		/*
+		 * Ensure sieve local directory structure exists (full autocreate):
+		 *  This currently only consists of a ./tmp direcory
+		 */
+
+		tmp_dir = t_strconcat(storage_path, "/tmp", NULL);
+
+		/* Try to find and clean up tmp dir */
+		if ( (ret=check_tmp(storage, tmp_dir)) < 0 ) {
+			*error_r = SIEVE_ERROR_TEMP_FAILURE;
+			return -1;
+		}
+
+		/* Auto-create if necessary */
+		if ( ret == 0 && mkdir_verify(storage, tmp_dir,
+			dir_create_mode, file_create_gid, file_create_gid_origin) < 0 ) {
+			*error_r = SIEVE_ERROR_TEMP_FAILURE;
+			return -1;
+		}
+
+		fstorage->dir_create_mode = dir_create_mode;
+		fstorage->file_create_mode = file_create_mode;
+		fstorage->file_create_gid = file_create_gid;
+	}
+
+	if ( !exists && sieve_file_storage_stat
+		(fstorage, storage_path, error_r) < 0 )
+		return -1;
+
+	if ( have_link ) {
+		if ( t_realpath(storage_path, &storage_path, &error) < 0 ) {
+			sieve_storage_sys_error(storage,
+				"Failed to normalize storage path (path=%s): %s",
+				storage_path, error);
+			*error_r = SIEVE_ERROR_TEMP_FAILURE;
+			return -1;
+		}
+		if ( active_path != NULL && *active_path != '\0' ) {
+			/* Get the path to be prefixed to the script name in the symlink
+			 * pointing to the active script.
+			 */
+			link_path = sieve_storage_get_relative_link_path
+				(fstorage->active_path, storage_path);
+
+			sieve_storage_sys_debug(storage,
+				"Relative path to sieve storage in active link: %s",
+				link_path);
+
+			fstorage->link_path = p_strdup(storage->pool, link_path);
+		}
+	}
+
+	fstorage->path = p_strdup(storage->pool, storage_path);
+	storage->location = fstorage->path;
+
+	return 0;
+}
+
+static int sieve_file_storage_init
+(struct sieve_storage *storage, const char *const *options,
+	enum sieve_error *error_r)
+{
+	struct sieve_file_storage *fstorage =
+		(struct sieve_file_storage *)storage;
+	const char *storage_path = storage->location;
+	const char *active_path = "";
+	bool exists = FALSE;
+
+	if ( options != NULL ) {
+		while ( *options != NULL ) {
+			const char *option = *options;
+
+			if ( strncasecmp(option, "active=", 7) == 0 && option[7] != '\0' ) {
+				active_path = option+7;
+			} else {
+				sieve_storage_set_critical(storage,
+					"Invalid option `%s'", option);
+				*error_r = SIEVE_ERROR_TEMP_FAILURE;
+				return -1;
+			}
+
+			options++;
+		}
+	}
+
+	/* Get full storage path */
+
+	if ( sieve_file_storage_get_full_path
+		(fstorage, &storage_path, error_r) < 0 )
+		return -1;
+
+	/* Stat storage directory */
+
+	if ( storage_path != NULL && *storage_path != '\0' ) {
+		if ( sieve_file_storage_stat(fstorage, storage_path, error_r) < 0 ) {
+			if ( *error_r != SIEVE_ERROR_NOT_FOUND )
+				return -1;
+			if ( (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) == 0 ) {
+				/* For backwards compatibility, recognize when storage directory
+				   does not exist while active script exists and is a regular
+				   file. */
+				if ( active_path == NULL || *active_path == '\0' )
+					return -1;
+				if ( sieve_file_storage_get_full_active_path
+					(fstorage, &active_path, error_r) < 0 )
+					return -1;
+				if ( sieve_file_storage_stat
+					(fstorage, active_path, error_r) < 0 )
+					return -1;
+				if ( !S_ISREG(fstorage->lnk_st.st_mode) )
+					return -1;
+				sieve_storage_sys_debug(storage,
+					"Sieve storage path `%s' not found, "
+					"but the active script `%s' is a regular file, "
+					"so this is used for backwards compatibility.",
+					storage_path, active_path);
+				storage_path = NULL;
+			}
+		} else {
+			exists = TRUE;
+
+			if ( !S_ISDIR(fstorage->st.st_mode) ) {
+				if ( (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 ) {
+					sieve_storage_set_critical(storage,
+						"Sieve storage path `%s' is not a directory, "
+						"but it is to be opened for write access", storage_path);
+					*error_r = SIEVE_ERROR_TEMP_FAILURE;
+					return -1;
+				}
+				if ( active_path != NULL && *active_path != '\0' ) {
+					sieve_storage_sys_warning(storage,
+						"Explicitly specified active script path `%s' is ignored; "
+						"storage path `%s' is not a directory",
+						active_path, storage_path);
+				}
+				active_path = storage_path;
+				storage_path = NULL;
+			} 
+		}
+	}
+
+	if ( active_path == NULL || *active_path == '\0' ) {
+		if ( storage->main_storage ||
+			(storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0) {
+			sieve_storage_sys_debug(storage,
+				"Active script path is unconfigured; "
+				"using default (path=%s)", SIEVE_FILE_DEFAULT_PATH);
+				active_path = SIEVE_FILE_DEFAULT_PATH;
+		}
+	}
+
+	return sieve_file_storage_init_common
+		(fstorage, active_path, storage_path, exists, error_r);
+}
+
+static void sieve_file_storage_autodetect
+(struct sieve_file_storage *fstorage, const char **storage_path_r)
+{
+	struct sieve_storage *storage = &fstorage->storage;
+	struct sieve_instance *svinst = storage->svinst;
+	const char *home = sieve_environment_get_homedir(svinst);
+	int mode = ( (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 ?
+		R_OK|W_OK|X_OK : R_OK|X_OK );
+
+	sieve_storage_sys_debug(storage,
+		"Performing auto-detection");
+
+	/* We'll need to figure out the storage location ourself.
+	 *
+	 * It's $HOME/sieve or /sieve when (presumed to be) chrooted.
+	 */
+	if ( home != NULL && *home != '\0' ) {
+		/* Use default ~/sieve */
+		sieve_storage_sys_debug(storage, "Use home (%s)", home);
+		*storage_path_r = t_strconcat(home, "/sieve", NULL);
+	} else {
+			sieve_storage_sys_debug(storage,
+				"HOME is not set");
+
+		if (access("/sieve", mode) == 0) {
+			*storage_path_r = "/sieve";
+			sieve_storage_sys_debug(storage,
+				"Directory `/sieve' exists, assuming chroot");
+		}
+	}
+}
+
+static int sieve_file_storage_do_init_legacy
+(struct sieve_file_storage *fstorage, const char *active_path,
+	const char *storage_path, enum sieve_error *error_r)
+{
+	struct sieve_storage *storage = &fstorage->storage;
+	bool explicit = FALSE, exists = FALSE;
+
+	if ( storage_path == NULL || *storage_path == '\0' ) {
+		/* Try autodectection */	
+		sieve_file_storage_autodetect(fstorage, &storage_path);
+
+		if ( storage_path != NULL && *storage_path != '\0') {
+			/* Got something: stat it */
+			if (sieve_file_storage_stat
+				(fstorage, storage_path, error_r) < 0 ) {
+				if (*error_r != SIEVE_ERROR_NOT_FOUND) {
+					/* Error */
+					return -1;
+				}
+			} else 	if ( S_ISDIR(fstorage->st.st_mode) ) {
+				/* Success */
+				exists = TRUE;
+			}
+		}
+
+		if ( (storage_path == NULL || *storage_path == '\0') &&
+			(storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 ) {
+			sieve_storage_set_critical(storage,
+				"Could not find storage root directory for write access; "
+				"path was left unconfigured and autodetection failed");
+			*error_r = SIEVE_ERROR_TEMP_FAILURE;
+			return -1;
+		}
+
+	} else { 
+		/* Get full storage path */
+		if ( sieve_file_storage_get_full_path
+			(fstorage, &storage_path, error_r) < 0 )
+			return -1;
+
+		/* Stat storage directory */
+		if ( sieve_file_storage_stat(fstorage, storage_path, error_r) < 0 ) {
+			if ( (*error_r != SIEVE_ERROR_NOT_FOUND) )
+				return -1;
+			if ( (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) == 0 )
+				storage_path = NULL;
+		} else {
+			exists = TRUE;
+		}
+	
+		/* Storage path must be a directory */
+		if ( exists && !S_ISDIR(fstorage->st.st_mode) ) {
+			sieve_storage_set_critical(storage,
+				"Sieve storage path `%s' configured using sieve_dir "
+				"is not a directory", storage_path);
+			*error_r = SIEVE_ERROR_TEMP_FAILURE;
+			return -1;
+		}
+
+		explicit = TRUE;
+	}
+
+	if ( (active_path == NULL || *active_path == '\0') ) {
+		if ( storage->main_storage ||
+		(storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0) {
+			sieve_storage_sys_debug(storage,
+				"Active script path is unconfigured; "
+				"using default (path=%s)", SIEVE_FILE_DEFAULT_PATH);
+			active_path = SIEVE_FILE_DEFAULT_PATH;
+		} else {
+			return -1;
+		}
+	}
+
+	if ( !explicit && !exists &&
+		active_path != NULL && *active_path != '\0' &&
+		(storage->flags & SIEVE_STORAGE_FLAG_READWRITE) == 0 )
+		storage_path = NULL;
+	
+	if ( sieve_file_storage_init_common
+		(fstorage, active_path, storage_path, exists, error_r) < 0 )
+		return -1;
+	return 0;
+}
+
+struct sieve_storage *sieve_file_storage_init_legacy
+(struct sieve_instance *svinst, const char *active_path,
+	const char *storage_path, enum sieve_storage_flags flags,
+	enum sieve_error *error_r)
+{
+	struct sieve_storage *storage;
+	struct sieve_file_storage *fstorage;
+
+	storage = sieve_storage_alloc
+		(svinst, &sieve_file_storage, "", flags, TRUE);
+	fstorage = (struct sieve_file_storage *)storage;
+
+	T_BEGIN {
+		if ( sieve_file_storage_do_init_legacy
+			(fstorage, active_path, storage_path, error_r) < 0 ) {
+			sieve_storage_unref(&storage);
+			storage = NULL;
+		}
+	} T_END;
+
+	return storage;
+}
+
+struct sieve_file_storage *sieve_file_storage_init_from_path
+(struct sieve_instance *svinst, const char *path,
+	enum sieve_storage_flags flags, enum sieve_error *error_r)
+{
+	struct sieve_storage *storage;
+	struct sieve_file_storage *fstorage;
+
+	i_assert( path != NULL );
+
+	storage = sieve_storage_alloc
+		(svinst, &sieve_file_storage, "", flags, FALSE);
+	fstorage = (struct sieve_file_storage *)storage;
+	
+	T_BEGIN {
+		if ( sieve_file_storage_init_common
+			(fstorage, path, NULL, FALSE, error_r) < 0 ) {
+			sieve_storage_unref(&storage);
+			fstorage = NULL;
+		}
+	} T_END;
+
+	return fstorage;
+}
+
+static int sieve_file_storage_is_singular
+(struct sieve_storage *storage)
+{
+	struct sieve_file_storage *fstorage =
+		(struct sieve_file_storage *)storage;
+	struct stat st;
+
+	if ( fstorage->active_path == NULL )
+		return 1;
+
+	/* Stat the file */
+	if ( lstat(fstorage->active_path, &st) != 0 ) {
+		if ( errno != ENOENT ) {
+			sieve_storage_set_critical(storage,
+				"Failed to stat active sieve script symlink (%s): %m.",
+				fstorage->active_path);
+			return -1;
+		}
+		return 0;
+	}
+
+	if ( S_ISLNK( st.st_mode ) )
+		return 0;
+	if ( !S_ISREG( st.st_mode ) ) {
+		sieve_storage_set_critical(storage,
+			"Active sieve script file '%s' is no symlink nor a regular file.",
+			fstorage->active_path);
+		return -1;
+	}
+	return 1;
+}
+
+/*
+ *
+ */
+
+static int sieve_file_storage_get_last_change
+(struct sieve_storage *storage, time_t *last_change_r)
+{
+	struct sieve_file_storage *fstorage =
+		(struct sieve_file_storage *)storage;
+	struct stat st;
+
+	if ( fstorage->prev_mtime == (time_t)-1 ) {
+		/* Get the storage mtime before we modify it ourself */
+		if ( stat(fstorage->path, &st) < 0 ) {
+			if ( errno != ENOENT ) {
+				sieve_storage_sys_error(storage,
+					"stat(%s) failed: %m", fstorage->path);
+				return -1;
+			}
+			st.st_mtime = 0;
+		}
+
+		fstorage->prev_mtime = st.st_mtime;
+	}
+		
+	if ( last_change_r != NULL )
+		*last_change_r = fstorage->prev_mtime;
+	return 0;
+}
+
+int sieve_file_storage_pre_modify
+(struct sieve_storage *storage)
+{
+	i_assert( (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 );
+
+	return sieve_storage_get_last_change(storage, NULL);
+}
+
+static void sieve_file_storage_set_modified
+(struct sieve_storage *storage, time_t mtime)
+{
+	struct sieve_file_storage *fstorage =
+		(struct sieve_file_storage *)storage;
+	struct utimbuf times;
+	time_t cur_mtime;
+
+	if ( mtime != (time_t)-1 ) {
+		if ( sieve_storage_get_last_change(storage, &cur_mtime) >= 0 &&
+			cur_mtime > mtime )
+			return;
+	} else {
+		mtime = ioloop_time;
+	}
+
+	times.actime = mtime;
+	times.modtime = mtime;
+	if ( utime(fstorage->path, &times) < 0 ) {
+		switch ( errno ) {
+		case ENOENT:
+			break;
+		case EACCES:
+			sieve_storage_sys_error(storage,
+				"%s", eacces_error_get("utime", fstorage->path));
+			break;
+		default:
+			sieve_storage_sys_error(storage,
+				"utime(%s) failed: %m", fstorage->path);
+		}
+	} else {
+		fstorage->prev_mtime = mtime;
+	}
+}
+
+/*
+ * Script access
+ */
+
+static struct sieve_script *sieve_file_storage_get_script
+(struct sieve_storage *storage, const char *name)
+{
+	struct sieve_file_storage *fstorage =
+		(struct sieve_file_storage *)storage;
+	struct sieve_file_script *fscript;
+
+	T_BEGIN {
+		fscript = sieve_file_script_init_from_name(fstorage, name);
+	} T_END;
+
+	return &fscript->script;
+}
+
+/*
+ * Driver definition
+ */
+
+const struct sieve_storage sieve_file_storage = {
+	.driver_name = SIEVE_FILE_STORAGE_DRIVER_NAME,
+	.version = 0,
+	.allows_synchronization = TRUE,
+	.v = {
+		.alloc = sieve_file_storage_alloc,
+		.init = sieve_file_storage_init,
+
+		.get_last_change = sieve_file_storage_get_last_change,
+		.set_modified = sieve_file_storage_set_modified,
+
+		.is_singular = sieve_file_storage_is_singular,
+
+		.get_script = sieve_file_storage_get_script,
+
+		.get_script_sequence = sieve_file_storage_get_script_sequence,
+		.script_sequence_next = sieve_file_script_sequence_next,
+		.script_sequence_destroy = sieve_file_script_sequence_destroy,
+
+		.active_script_get_name = sieve_file_storage_active_script_get_name,
+		.active_script_open = sieve_file_storage_active_script_open,
+		.deactivate = sieve_file_storage_deactivate,
+		.active_script_get_last_change =
+			sieve_file_storage_active_script_get_last_change,
+
+		.list_init = sieve_file_storage_list_init,
+		.list_next = sieve_file_storage_list_next,
+		.list_deinit = sieve_file_storage_list_deinit,
+
+		.save_init = sieve_file_storage_save_init,
+		.save_continue = sieve_file_storage_save_continue,
+		.save_finish = sieve_file_storage_save_finish,
+		.save_get_tempscript = sieve_file_storage_save_get_tempscript,
+		.save_cancel = sieve_file_storage_save_cancel,
+		.save_commit = sieve_file_storage_save_commit,
+		.save_as = sieve_file_storage_save_as,
+		.save_as_active = sieve_file_storage_save_as_active,
+
+		.quota_havespace = sieve_file_storage_quota_havespace
+	}
+};
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage.h
@@ -0,0 +1,185 @@
+#ifndef SIEVE_FILE_STORAGE_H
+#define SIEVE_FILE_STORAGE_H
+
+#include "lib.h"
+#include "mail-user.h"
+
+#include "sieve.h"
+#include "sieve-script-private.h"
+#include "sieve-storage-private.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#define SIEVE_FILE_READ_BLOCK_SIZE (1024*8)
+
+#define SIEVE_FILE_DEFAULT_PATH "~/.dovecot."SIEVE_SCRIPT_FILEEXT
+
+/* How often to scan tmp/ directory for old files (based on dir's atime) */
+#define SIEVE_FILE_STORAGE_TMP_SCAN_SECS (8*60*60)
+/* Delete files having ctime older than this from tmp/. 36h is standard. */
+#define SIEVE_FILE_STORAGE_TMP_DELETE_SECS (36*60*60)
+
+/*
+ * Storage class
+ */
+
+struct sieve_file_storage {
+	struct sieve_storage storage;
+
+	const char *path;
+	const char *active_path;
+	const char *active_fname;
+	const char *link_path;
+
+	struct stat st;
+	struct stat lnk_st;
+
+	mode_t dir_create_mode;
+	mode_t file_create_mode;
+	gid_t file_create_gid;
+
+	time_t prev_mtime;
+};
+
+const char *sieve_file_storage_path_extend
+	(struct sieve_file_storage *fstorage, const char *filename);
+
+struct sieve_file_storage *sieve_file_storage_init_from_path
+(struct sieve_instance *svinst, const char *path,
+	enum sieve_storage_flags flags, enum sieve_error *error_r)
+	ATTR_NULL(4);
+
+int sieve_file_storage_pre_modify
+	(struct sieve_storage *storage);
+
+/* Active script */
+
+int sieve_file_storage_active_replace_link
+	(struct sieve_file_storage *fstorage, const char *link_path);
+bool sieve_file_storage_active_rescue_regular
+	(struct sieve_file_storage *fstorage);
+
+int sieve_file_storage_active_script_get_name
+	(struct sieve_storage *storage, const char **name_r);
+struct sieve_script *sieve_file_storage_active_script_open
+	(struct sieve_storage *storage);
+
+int sieve_file_storage_active_script_get_file
+	(struct sieve_file_storage *fstorage, const char **file_r);
+int sieve_file_storage_active_script_is_no_link
+	(struct sieve_file_storage *fstorage);
+
+int sieve_file_storage_deactivate
+	(struct sieve_storage *storage);
+
+int sieve_file_storage_active_script_get_last_change
+	(struct sieve_storage *storage, time_t *last_change_r);
+
+/* Listing */
+
+struct sieve_storage_list_context *sieve_file_storage_list_init
+	(struct sieve_storage *storage);
+const char *sieve_file_storage_list_next
+	(struct sieve_storage_list_context *ctx, bool *active);
+int sieve_file_storage_list_deinit
+	(struct sieve_storage_list_context *lctx);
+
+/* Saving */
+
+struct sieve_storage_save_context *sieve_file_storage_save_init
+	(struct sieve_storage *storage, 	const char *scriptname,
+		struct istream *input);
+int sieve_file_storage_save_continue
+	(struct sieve_storage_save_context *sctx);
+int sieve_file_storage_save_finish
+	(struct sieve_storage_save_context *sctx);
+struct sieve_script *sieve_file_storage_save_get_tempscript
+	(struct sieve_storage_save_context *sctx);
+int sieve_file_storage_save_commit
+	(struct sieve_storage_save_context *sctx);
+void sieve_file_storage_save_cancel
+	(struct sieve_storage_save_context *sctx);
+
+int sieve_file_storage_save_as
+	(struct sieve_storage *storage, struct istream *input,
+		const char *name);
+int sieve_file_storage_save_as_active
+	(struct sieve_storage *storage, struct istream *input,
+		time_t mtime);
+
+/* Quota */
+
+int sieve_file_storage_quota_havespace
+(struct sieve_storage *storage, const char *scriptname, size_t size,
+	enum sieve_storage_quota *quota_r, uint64_t *limit_r);
+
+/*
+ * Sieve script filenames
+ */
+
+const char *sieve_script_file_get_scriptname(const char *filename);
+const char *sieve_script_file_from_name(const char *name);
+
+/*
+ * Script class
+ */
+
+struct sieve_file_script {
+	struct sieve_script script;
+
+	struct stat st;
+	struct stat lnk_st;
+
+	const char *path;
+	const char *dirpath;
+	const char *filename;
+	const char *binpath;
+	const char *binprefix;
+
+	time_t prev_mtime;
+};
+
+struct sieve_file_script *sieve_file_script_init_from_filename
+	(struct sieve_file_storage *fstorage, const char *filename,
+		const char *scriptname);
+struct sieve_file_script *sieve_file_script_open_from_filename
+	(struct sieve_file_storage *fstorage, const char *filename,
+		const char *scriptname);
+struct sieve_file_script *sieve_file_script_init_from_name
+	(struct sieve_file_storage *fstorage, const char *name);
+struct sieve_file_script *sieve_file_script_open_from_name
+	(struct sieve_file_storage *fstorage, const char *name);
+
+struct sieve_file_script *sieve_file_script_init_from_path
+	(struct sieve_file_storage *fstorage, const char *path,
+		const char *scriptname, enum sieve_error *error_r)
+		ATTR_NULL(4);
+struct sieve_file_script *sieve_file_script_open_from_path
+	(struct sieve_file_storage *fstorage, const char *path,
+		const char *scriptname, enum sieve_error *error_r)
+		ATTR_NULL(4);
+
+/* Return directory where script resides in. Returns NULL if this is not a file
+ * script.
+ */
+const char *sieve_file_script_get_dirpath
+	(const struct sieve_script *script);
+
+/* Return full path to file script. Returns NULL if this is not a file script.
+ */
+const char *sieve_file_script_get_path
+	(const struct sieve_script *script);
+
+/*
+ * Script sequence
+ */
+
+struct sieve_script_sequence *sieve_file_storage_get_script_sequence
+	(struct sieve_storage *storage, enum sieve_error *error_r);
+
+struct sieve_script *sieve_file_script_sequence_next
+	(struct sieve_script_sequence *seq, enum sieve_error *error_r);
+void sieve_file_script_sequence_destroy(struct sieve_script_sequence *seq);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/storage/ldap/Makefile.am
@@ -0,0 +1,32 @@
+noinst_LTLIBRARIES = libsieve_storage_ldap.la
+
+sieve_plugindir = $(dovecot_moduledir)/sieve
+sieve_plugin_LTLIBRARIES =
+
+AM_CPPFLAGS = \
+	$(LIBDOVECOT_INCLUDE) \
+	-I$(top_srcdir) \
+	-I$(top_srcdir)/src/lib-sieve
+
+ldap_sources = \
+	sieve-ldap-db.c \
+	sieve-ldap-script.c \
+	sieve-ldap-storage.c \
+	sieve-ldap-storage-settings.c
+
+libsieve_storage_ldap_la_SOURCES = $(ldap_sources)
+libsieve_storage_ldap_la_LIBADD = $(LDAP_LIBS)
+
+noinst_HEADERS = \
+	sieve-ldap-db.h \
+	sieve-ldap-storage.h
+
+if LDAP_PLUGIN
+sieve_plugin_LTLIBRARIES += lib10_sieve_storage_ldap_plugin.la
+
+lib10_sieve_storage_ldap_plugin_la_LDFLAGS = -module -avoid-version
+lib10_sieve_storage_ldap_plugin_la_LIBADD = $(LDAP_LIBS)
+lib10_sieve_storage_ldap_plugin_la_DEPENDENCIES = $(LDAP_LIBS)
+lib10_sieve_storage_ldap_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) -DPLUGIN_BUILD
+lib10_sieve_storage_ldap_plugin_la_SOURCES = $(ldap_sources)
+endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-db.c
@@ -0,0 +1,1388 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+
+#include "sieve-ldap-storage.h"
+
+/* FIXME: Imported this from Dovecot auth for now. We're working on a proper
+   lib-ldap, but, until then, some code is duplicated here. */
+
+#if defined(SIEVE_BUILTIN_LDAP) || defined(PLUGIN_BUILD)
+
+#include "net.h"
+#include "ioloop.h"
+#include "array.h"
+#include "hash.h"
+#include "aqueue.h"
+#include "str.h"
+#include "time-util.h"
+#include "env-util.h"
+#include "var-expand.h"
+#include "istream.h"
+
+#include <stddef.h>
+#include <unistd.h>
+
+struct db_ldap_result {
+	int refcount;
+	LDAPMessage *msg;
+};
+
+struct db_ldap_result_iterate_context {
+	pool_t pool;
+
+	struct auth_request *auth_request;
+	const ARRAY_TYPE(ldap_field) *attr_map;
+	unsigned int attr_idx;
+
+	/* attribute name => value */
+	HASH_TABLE(char *, struct db_ldap_value *) ldap_attrs;
+
+	const char *val_1_arr[2];
+	string_t *var, *debug;
+
+	bool skip_null_values;
+	bool iter_dn_values;
+};
+
+struct db_ldap_sasl_bind_context {
+	const char *authcid;
+	const char *passwd;
+	const char *realm;
+	const char *authzid;
+};
+
+static struct ldap_connection *ldap_connections = NULL;
+
+static int db_ldap_bind(struct ldap_connection *conn);
+static void db_ldap_conn_close(struct ldap_connection *conn);
+
+int ldap_deref_from_str(const char *str, int *deref_r)
+{
+	if (strcasecmp(str, "never") == 0)
+		*deref_r = LDAP_DEREF_NEVER;
+	else if (strcasecmp(str, "searching") == 0)
+		*deref_r = LDAP_DEREF_SEARCHING;
+	else if (strcasecmp(str, "finding") == 0)
+		*deref_r = LDAP_DEREF_FINDING;
+	else if (strcasecmp(str, "always") == 0)
+		*deref_r = LDAP_DEREF_ALWAYS;
+	else 
+		return -1;
+	return 0;
+}
+
+int ldap_scope_from_str(const char *str, int *scope_r)
+{
+	if (strcasecmp(str, "base") == 0)
+		*scope_r = LDAP_SCOPE_BASE;
+	else if (strcasecmp(str, "onelevel") == 0)
+		*scope_r = LDAP_SCOPE_ONELEVEL;
+	else if (strcasecmp(str, "subtree") == 0)
+		*scope_r = LDAP_SCOPE_SUBTREE;
+	else
+		return -1;
+	return 0;
+}
+
+#ifdef OPENLDAP_TLS_OPTIONS
+int ldap_tls_require_cert_from_str(const char *str, int *opt_x_tls_r)
+{
+	if (strcasecmp(str, "never") == 0)
+		*opt_x_tls_r = LDAP_OPT_X_TLS_NEVER;
+	else if (strcasecmp(str, "hard") == 0)
+		*opt_x_tls_r = LDAP_OPT_X_TLS_HARD;
+	else if (strcasecmp(str, "demand") == 0)
+		*opt_x_tls_r = LDAP_OPT_X_TLS_DEMAND;
+	else if (strcasecmp(str, "allow") == 0)
+		*opt_x_tls_r = LDAP_OPT_X_TLS_ALLOW;
+	else if (strcasecmp(str, "try") == 0)
+		*opt_x_tls_r = LDAP_OPT_X_TLS_TRY;
+	else
+		return -1;
+	return 0;
+}
+#endif
+
+
+static int ldap_get_errno(struct ldap_connection *conn)
+{
+	struct sieve_storage *storage = &conn->lstorage->storage;
+	int ret, err;
+
+	ret = ldap_get_option(conn->ld, LDAP_OPT_ERROR_NUMBER, (void *) &err);
+	if (ret != LDAP_SUCCESS) {
+		sieve_storage_sys_error(storage, "db: "
+			"Can't get error number: %s",
+			ldap_err2string(ret));
+		return LDAP_UNAVAILABLE;
+	}
+
+	return err;
+}
+
+const char *ldap_get_error(struct ldap_connection *conn)
+{
+	const char *ret;
+	char *str = NULL;
+
+	ret = ldap_err2string(ldap_get_errno(conn));
+
+	ldap_get_option(conn->ld, LDAP_OPT_ERROR_STRING, (void *)&str);
+	if (str != NULL) {
+		ret = t_strconcat(ret, ", ", str, NULL);
+		ldap_memfree(str);
+	}
+	ldap_set_option(conn->ld, LDAP_OPT_ERROR_STRING, NULL);
+	return ret;
+}
+
+static void ldap_conn_reconnect(struct ldap_connection *conn)
+{
+	db_ldap_conn_close(conn);
+	if (sieve_ldap_db_connect(conn) < 0)
+		db_ldap_conn_close(conn);
+}
+
+static int ldap_handle_error(struct ldap_connection *conn)
+{
+	int err = ldap_get_errno(conn);
+
+	switch (err) {
+	case LDAP_SUCCESS:
+		i_unreached();
+	case LDAP_SIZELIMIT_EXCEEDED:
+	case LDAP_TIMELIMIT_EXCEEDED:
+	case LDAP_NO_SUCH_ATTRIBUTE:
+	case LDAP_UNDEFINED_TYPE:
+	case LDAP_INAPPROPRIATE_MATCHING:
+	case LDAP_CONSTRAINT_VIOLATION:
+	case LDAP_TYPE_OR_VALUE_EXISTS:
+	case LDAP_INVALID_SYNTAX:
+	case LDAP_NO_SUCH_OBJECT:
+	case LDAP_ALIAS_PROBLEM:
+	case LDAP_INVALID_DN_SYNTAX:
+	case LDAP_IS_LEAF:
+	case LDAP_ALIAS_DEREF_PROBLEM:
+	case LDAP_FILTER_ERROR:
+		/* invalid input */
+		return -1;
+	case LDAP_SERVER_DOWN:
+	case LDAP_TIMEOUT:
+	case LDAP_UNAVAILABLE:
+	case LDAP_BUSY:
+#ifdef LDAP_CONNECT_ERROR
+	case LDAP_CONNECT_ERROR:
+#endif
+	case LDAP_LOCAL_ERROR:
+	case LDAP_INVALID_CREDENTIALS:
+	case LDAP_OPERATIONS_ERROR:
+	default:
+		/* connection problems */
+		ldap_conn_reconnect(conn);
+		return 0;
+	}
+}
+
+static int db_ldap_request_search(struct ldap_connection *conn,
+				  struct ldap_request *request)
+{
+	struct sieve_storage *storage = &conn->lstorage->storage;
+
+	i_assert(conn->conn_state == LDAP_CONN_STATE_BOUND);
+	i_assert(request->msgid == -1);
+
+	request->msgid =
+		ldap_search(conn->ld, *request->base == '\0' ? NULL :
+			    request->base, request->scope,
+			    request->filter, request->attributes, 0);
+	if (request->msgid == -1) {
+		sieve_storage_sys_error(storage, "db: "
+			"ldap_search(%s) parsing failed: %s",
+			request->filter, ldap_get_error(conn));
+		if (ldap_handle_error(conn) < 0) {
+			/* broken request, remove it */
+			return 0;
+		}
+		return -1;
+	}
+	return 1;
+}
+
+static bool db_ldap_request_queue_next(struct ldap_connection *conn)
+{
+	struct ldap_request *const *requestp, *request;
+	int ret = -1;
+
+	/* connecting may call db_ldap_connect_finish(), which gets us back
+	   here. so do the connection before checking the request queue. */
+	if (sieve_ldap_db_connect(conn) < 0)
+		return FALSE;
+
+	if (conn->pending_count == aqueue_count(conn->request_queue)) {
+		/* no non-pending requests */
+		return FALSE;
+	}
+	if (conn->pending_count > DB_LDAP_MAX_PENDING_REQUESTS) {
+		/* wait until server has replied to some requests */
+		return FALSE;
+	}
+
+	requestp = array_idx(&conn->request_array,
+			     aqueue_idx(conn->request_queue,
+					conn->pending_count));
+	request = *requestp;
+
+	switch (conn->conn_state) {
+	case LDAP_CONN_STATE_DISCONNECTED:
+	case LDAP_CONN_STATE_BINDING:
+		/* wait until we're in bound state */
+		return FALSE;
+	case LDAP_CONN_STATE_BOUND:
+		/* we can do anything in this state */
+		break;
+	}
+
+	ret = db_ldap_request_search(conn, request);
+	if (ret > 0) {
+		/* success */
+		i_assert(request->msgid != -1);
+		conn->pending_count++;
+		return TRUE;
+	} else if (ret < 0) {
+		/* disconnected */
+		return FALSE;
+	} else {
+		/* broken request, remove from queue */
+		aqueue_delete_tail(conn->request_queue);
+		request->callback(conn, request, NULL);
+		return TRUE;
+	}
+}
+
+static bool
+db_ldap_check_limits(struct ldap_connection *conn)
+{
+	struct sieve_storage *storage = &conn->lstorage->storage;
+	struct ldap_request *const *first_requestp;
+	unsigned int count;
+	time_t secs_diff;
+
+	count = aqueue_count(conn->request_queue);
+	if (count == 0)
+		return TRUE;
+
+	first_requestp = array_idx(&conn->request_array,
+				   aqueue_idx(conn->request_queue, 0));
+	secs_diff = ioloop_time - (*first_requestp)->create_time;
+	if (secs_diff > DB_LDAP_REQUEST_LOST_TIMEOUT_SECS) {
+		sieve_storage_sys_error(storage, "db: "
+			"Connection appears to be hanging, reconnecting");
+		ldap_conn_reconnect(conn);
+		return TRUE;
+	}
+	return TRUE;
+}
+
+void db_ldap_request(struct ldap_connection *conn,
+		     struct ldap_request *request)
+{
+	request->msgid = -1;
+	request->create_time = ioloop_time;
+
+	if (!db_ldap_check_limits(conn)) {
+		request->callback(conn, request, NULL);
+		return;
+	}
+
+	aqueue_append(conn->request_queue, &request);
+	(void)db_ldap_request_queue_next(conn);
+}
+
+static int db_ldap_connect_finish(struct ldap_connection *conn, int ret)
+{
+	struct sieve_storage *storage = &conn->lstorage->storage;
+	const struct sieve_ldap_storage_settings *set = &conn->lstorage->set;
+
+	if (ret == LDAP_SERVER_DOWN) {
+		sieve_storage_sys_error(storage, "db: "
+			"Can't connect to server: %s",
+			set->uris != NULL ?
+			set->uris : set->hosts);
+		return -1;
+	}
+	if (ret != LDAP_SUCCESS) {
+		sieve_storage_sys_error(storage, "db: "
+			"binding failed (dn %s): %s",
+			set->dn == NULL ? "(none)" : set->dn,
+			ldap_get_error(conn));
+		return -1;
+	}
+
+	timeout_remove(&conn->to);
+	conn->conn_state = LDAP_CONN_STATE_BOUND;
+	sieve_storage_sys_debug(storage, "db: "
+		"Successfully bound (dn %s)",
+			set->dn == NULL ? "(none)" : set->dn);
+	while (db_ldap_request_queue_next(conn))
+		;
+	return 0;
+}
+
+static void db_ldap_default_bind_finished(struct ldap_connection *conn,
+					  struct db_ldap_result *res)
+{
+	int ret;
+
+	i_assert(conn->pending_count == 0);
+	conn->default_bind_msgid = -1;
+
+	ret = ldap_result2error(conn->ld, res->msg, FALSE);
+	if (db_ldap_connect_finish(conn, ret) < 0) {
+		/* lost connection, close it */
+		db_ldap_conn_close(conn);
+	}
+}
+
+static void db_ldap_abort_requests(struct ldap_connection *conn,
+				   unsigned int max_count,
+				   unsigned int timeout_secs,
+				   bool error, const char *reason)
+{
+	struct sieve_storage *storage = &conn->lstorage->storage;
+	struct ldap_request *const *requestp, *request;
+	time_t diff;
+
+	while (aqueue_count(conn->request_queue) > 0 && max_count > 0) {
+		requestp = array_idx(&conn->request_array,
+				     aqueue_idx(conn->request_queue, 0));
+		request = *requestp;
+
+		diff = ioloop_time - request->create_time;
+		if (diff < (time_t)timeout_secs)
+			break;
+
+		/* timed out, abort */
+		aqueue_delete_tail(conn->request_queue);
+
+		if (request->msgid != -1) {
+			i_assert(conn->pending_count > 0);
+			conn->pending_count--;
+		}
+		if (error) {
+			sieve_storage_sys_error(storage, "db: "
+				"%s", reason);
+		} else {
+			sieve_storage_sys_debug(storage, "db: "
+				"%s", reason);
+		}
+		request->callback(conn, request, NULL);
+		max_count--;
+	}
+}
+
+static struct ldap_request *
+db_ldap_find_request(struct ldap_connection *conn, int msgid,
+		     unsigned int *idx_r)
+{
+	struct ldap_request *const *requests, *request = NULL;
+	unsigned int i, count;
+
+	count = aqueue_count(conn->request_queue);
+	if (count == 0)
+		return NULL;
+
+	requests = array_idx(&conn->request_array, 0);
+	for (i = 0; i < count; i++) {
+		request = requests[aqueue_idx(conn->request_queue, i)];
+		if (request->msgid == msgid) {
+			*idx_r = i;
+			return request;
+		}
+		if (request->msgid == -1)
+			break;
+	}
+	return NULL;
+}
+
+static bool
+db_ldap_handle_request_result(struct ldap_connection *conn,
+			      struct ldap_request *request, unsigned int idx,
+			      struct db_ldap_result *res)
+{
+	struct sieve_storage *storage = &conn->lstorage->storage;
+	int ret;
+	bool final_result;
+
+	i_assert(conn->pending_count > 0);
+
+	switch (ldap_msgtype(res->msg)) {
+	case LDAP_RES_SEARCH_ENTRY:
+	case LDAP_RES_SEARCH_RESULT:
+		break;
+	case LDAP_RES_SEARCH_REFERENCE:
+		/* we're going to ignore this */
+		return FALSE;
+	default:
+		sieve_storage_sys_error(storage, "db: "
+			"Reply with unexpected type %d",
+			ldap_msgtype(res->msg));
+		return TRUE;
+	}
+
+	if (ldap_msgtype(res->msg) == LDAP_RES_SEARCH_ENTRY) {
+		ret = LDAP_SUCCESS;
+		final_result = FALSE;
+	} else {
+		final_result = TRUE;
+		ret = ldap_result2error(conn->ld, res->msg, 0);
+	}
+	if (ret != LDAP_SUCCESS) {
+		/* handle search failures here */
+		sieve_storage_sys_error(storage, "db: "
+			"ldap_search(base=%s filter=%s) failed: %s",
+			request->base, request->filter,
+			ldap_err2string(ret));
+		res = NULL;
+	} else {
+		if (!final_result && storage->svinst->debug) {
+			sieve_storage_sys_debug(storage, "db: "
+				"ldap_search(base=%s filter=%s) returned entry: %s",
+				request->base, request->filter,
+				ldap_get_dn(conn->ld, res->msg));
+		}
+	}
+	if (res == NULL && !final_result) {
+		/* wait for the final reply */
+		request->failed = TRUE;
+		return TRUE;
+	}
+	if (request->failed)
+		res = NULL;
+	if (final_result) {
+		conn->pending_count--;
+		aqueue_delete(conn->request_queue, idx);
+	}
+
+	T_BEGIN {
+		request->callback(conn, request, res == NULL ? NULL : res->msg);
+	} T_END;
+
+	if (idx > 0) {
+		/* see if there are timed out requests */
+		db_ldap_abort_requests(conn, idx,
+				       DB_LDAP_REQUEST_LOST_TIMEOUT_SECS,
+				       TRUE, "Request lost");
+	}
+	return TRUE;
+}
+
+static void db_ldap_result_unref(struct db_ldap_result **_res)
+{
+	struct db_ldap_result *res = *_res;
+
+	*_res = NULL;
+	i_assert(res->refcount > 0);
+	if (--res->refcount == 0) {
+		ldap_msgfree(res->msg);
+		i_free(res);
+	}
+}
+
+static void
+db_ldap_request_free(struct ldap_request *request)
+{
+	if (request->result != NULL)
+		db_ldap_result_unref(&request->result);
+}
+
+static void
+db_ldap_handle_result(struct ldap_connection *conn, struct db_ldap_result *res)
+{
+	struct sieve_storage *storage = &conn->lstorage->storage;
+	struct ldap_request *request;
+	unsigned int idx;
+	int msgid;
+
+	msgid = ldap_msgid(res->msg);
+	if (msgid == conn->default_bind_msgid) {
+		db_ldap_default_bind_finished(conn, res);
+		return;
+	}
+
+	request = db_ldap_find_request(conn, msgid, &idx);
+	if (request == NULL) {
+		sieve_storage_sys_error(storage, "db: "
+			"Reply with unknown msgid %d", msgid);
+		return;
+	}
+
+	if (db_ldap_handle_request_result(conn, request, idx, res))
+		db_ldap_request_free(request);
+}
+
+static void ldap_input(struct ldap_connection *conn)
+{
+	struct sieve_storage *storage = &conn->lstorage->storage;
+	struct timeval timeout;
+	struct db_ldap_result *res;
+	LDAPMessage *msg;
+	time_t prev_reply_diff;
+	int ret;
+
+	do {
+		if (conn->ld == NULL)
+			return;
+
+		i_zero(&timeout);
+		ret = ldap_result(conn->ld, LDAP_RES_ANY, 0, &timeout, &msg);
+#ifdef OPENLDAP_ASYNC_WORKAROUND
+		if (ret == 0) {
+			/* try again, there may be another in buffer */
+			ret = ldap_result(conn->ld, LDAP_RES_ANY, 0,
+					  &timeout, &msg);
+		}
+#endif
+		if (ret <= 0)
+			break;
+
+		res = i_new(struct db_ldap_result, 1);
+		res->refcount = 1;
+		res->msg = msg;
+		db_ldap_handle_result(conn, res);
+		db_ldap_result_unref(&res);
+	} while (conn->io != NULL);
+
+	prev_reply_diff = ioloop_time - conn->last_reply_stamp;
+	conn->last_reply_stamp = ioloop_time;
+
+	if (ret > 0) {
+		/* input disabled, continue once it's enabled */
+		i_assert(conn->io == NULL);
+	} else if (ret == 0) {
+		/* send more requests */
+		while (db_ldap_request_queue_next(conn))
+			;
+	} else if (ldap_get_errno(conn) != LDAP_SERVER_DOWN) {
+		sieve_storage_sys_error(storage, "db: "
+			"ldap_result() failed: %s", ldap_get_error(conn));
+		ldap_conn_reconnect(conn);
+	} else if (aqueue_count(conn->request_queue) > 0 ||
+		   prev_reply_diff < DB_LDAP_IDLE_RECONNECT_SECS) {
+		sieve_storage_sys_error(storage, "db: "
+			"Connection lost to LDAP server, reconnecting");
+		ldap_conn_reconnect(conn);
+	} else {
+		/* server probably disconnected an idle connection. don't
+		   reconnect until the next request comes. */
+		db_ldap_conn_close(conn);
+	}
+}
+
+#ifdef HAVE_LDAP_SASL
+static int
+sasl_interact(LDAP *ld ATTR_UNUSED, unsigned flags ATTR_UNUSED,
+	      void *defaults, void *interact)
+{
+	struct db_ldap_sasl_bind_context *context = defaults;
+	sasl_interact_t *in;
+	const char *str;
+
+	for (in = interact; in->id != SASL_CB_LIST_END; in++) {
+		switch (in->id) {
+		case SASL_CB_GETREALM:
+			str = context->realm;
+			break;
+		case SASL_CB_AUTHNAME:
+			str = context->authcid;
+			break;
+		case SASL_CB_USER:
+			str = context->authzid;
+			break;
+		case SASL_CB_PASS:
+			str = context->passwd;
+			break;
+		default:
+			str = NULL;
+			break;
+		}
+		if (str != NULL) {
+			in->len = strlen(str);
+			in->result = str;
+		}
+		
+	}
+	return LDAP_SUCCESS;
+}
+#endif
+
+static void ldap_connection_timeout(struct ldap_connection *conn)
+{
+	struct sieve_storage *storage = &conn->lstorage->storage;
+	i_assert(conn->conn_state == LDAP_CONN_STATE_BINDING);
+
+	sieve_storage_sys_error(storage, "db: "
+		"Initial binding to LDAP server timed out");
+	db_ldap_conn_close(conn);
+}
+
+static int db_ldap_bind(struct ldap_connection *conn)
+{
+	const struct sieve_ldap_storage_settings *set = &conn->lstorage->set;
+	int msgid;
+
+	i_assert(conn->conn_state != LDAP_CONN_STATE_BINDING);
+	i_assert(conn->default_bind_msgid == -1);
+	i_assert(conn->pending_count == 0);
+
+	msgid = ldap_bind(conn->ld, set->dn, set->dnpass,
+			  LDAP_AUTH_SIMPLE);
+	if (msgid == -1) {
+		i_assert(ldap_get_errno(conn) != LDAP_SUCCESS);
+		if (db_ldap_connect_finish(conn, ldap_get_errno(conn)) < 0) {
+			/* lost connection, close it */
+			db_ldap_conn_close(conn);
+		}
+		return -1;
+	}
+
+	conn->conn_state = LDAP_CONN_STATE_BINDING;
+	conn->default_bind_msgid = msgid;
+
+	timeout_remove(&conn->to);
+	conn->to = timeout_add(DB_LDAP_REQUEST_LOST_TIMEOUT_SECS*1000,
+			       ldap_connection_timeout, conn);
+	return 0;
+}
+
+static int db_ldap_get_fd(struct ldap_connection *conn)
+{
+	struct sieve_storage *storage = &conn->lstorage->storage;
+	int ret;
+
+	/* get the connection's fd */
+	ret = ldap_get_option(conn->ld, LDAP_OPT_DESC, (void *)&conn->fd);
+	if (ret != LDAP_SUCCESS) {
+		sieve_storage_sys_error(storage, "db: "
+			"Can't get connection fd: %s", ldap_err2string(ret));
+		return -1;
+	}
+	if (conn->fd <= STDERR_FILENO) {
+		/* Solaris LDAP library seems to be broken */
+		sieve_storage_sys_error(storage, "db: "
+			"Buggy LDAP library returned wrong fd: %d",	conn->fd);
+		return -1;
+	}
+	i_assert(conn->fd != -1);
+	net_set_nonblock(conn->fd, TRUE);
+	return 0;
+}
+
+static int
+db_ldap_set_opt(struct ldap_connection *conn, int opt, const void *value,
+		const char *optname, const char *value_str)
+{
+	struct sieve_storage *storage = &conn->lstorage->storage;
+	int ret;
+
+	ret = ldap_set_option(conn->ld, opt, value);
+	if (ret != LDAP_SUCCESS) {
+		sieve_storage_sys_error(storage, "db: "
+			"Can't set option %s to %s: %s",
+			optname, value_str, ldap_err2string(ret));
+		return -1;
+	}
+	return 0;
+}
+
+static int
+db_ldap_set_opt_str(struct ldap_connection *conn, int opt, const char *value,
+		    const char *optname)
+{
+	if (value != NULL)
+		return db_ldap_set_opt(conn, opt, value, optname, value);
+	return 0;
+}
+
+static int db_ldap_set_tls_options(struct ldap_connection *conn)
+{
+	const struct sieve_ldap_storage_settings *set = &conn->lstorage->set;
+
+	if (!set->tls)
+		return 0;
+
+#ifdef OPENLDAP_TLS_OPTIONS
+	if (db_ldap_set_opt_str(conn, LDAP_OPT_X_TLS_CACERTFILE,
+			    set->tls_ca_cert_file, "tls_ca_cert_file") < 0)
+		return -1;
+	if (db_ldap_set_opt_str(conn, LDAP_OPT_X_TLS_CACERTDIR,
+			    set->tls_ca_cert_dir, "tls_ca_cert_dir") < 0)
+		return -1;
+	if (db_ldap_set_opt_str(conn, LDAP_OPT_X_TLS_CERTFILE,
+			    set->tls_cert_file, "tls_cert_file") < 0)
+		return -1;
+	if (db_ldap_set_opt_str(conn, LDAP_OPT_X_TLS_KEYFILE,
+			    set->tls_key_file, "tls_key_file") < 0)
+		return -1;
+	if (db_ldap_set_opt_str(conn, LDAP_OPT_X_TLS_CIPHER_SUITE,
+			    set->tls_cipher_suite, "tls_cipher_suite") < 0)
+		return -1;
+	if (set->tls_require_cert != NULL) {
+		if (db_ldap_set_opt(conn, LDAP_OPT_X_TLS_REQUIRE_CERT,
+				&set->ldap_tls_require_cert,
+				"tls_require_cert", set->tls_require_cert) < 0)
+			return -1;
+	}
+#else
+	if (set->tls_ca_cert_file != NULL ||
+	    set->tls_ca_cert_dir != NULL ||
+	    set->tls_cert_file != NULL ||
+	    set->tls_key_file != NULL ||
+	    set->tls_cipher_suite != NULL) {
+		sieve_storage_sys_warning(&conn->lstorage->storage, "db: "
+			"tls_* settings ignored, "
+			"your LDAP library doesn't seem to support them");
+	}
+#endif
+	return 0;
+}
+
+static int db_ldap_set_options(struct ldap_connection *conn)
+{
+	const struct sieve_ldap_storage_settings *set = &conn->lstorage->set;
+	struct sieve_storage *storage = &conn->lstorage->storage;
+	unsigned int ldap_version;
+	int value;
+
+	if (db_ldap_set_opt(conn, LDAP_OPT_DEREF, &set->ldap_deref,
+			"deref", set->deref) < 0)
+		return -1;
+#ifdef LDAP_OPT_DEBUG_LEVEL
+	if (str_to_int(set->debug_level, &value) >= 0 && value != 0) {
+		if (db_ldap_set_opt(conn, LDAP_OPT_DEBUG_LEVEL, &value,
+				"debug_level", set->debug_level) < 0)
+			return -1;
+	}
+#endif
+
+	if (set->ldap_version < 3) {
+		if (set->sasl_bind) {
+			sieve_storage_sys_error(storage, "db: "
+				"sasl_bind=yes requires ldap_version=3");
+			return -1;
+		}
+		if (set->tls) {
+			sieve_storage_sys_error(storage, "db: "
+			"tls=yes requires ldap_version=3");
+			return -1;
+		}
+	}
+
+	ldap_version = set->ldap_version;
+	if (db_ldap_set_opt(conn, LDAP_OPT_PROTOCOL_VERSION, &ldap_version,
+			"protocol_version", dec2str(ldap_version)) < 0)
+		return -1;
+	if (db_ldap_set_tls_options(conn) < 0)
+		return -1;
+	return 0;
+}
+
+int sieve_ldap_db_connect(struct ldap_connection *conn)
+{
+	const struct sieve_ldap_storage_settings *set = &conn->lstorage->set;
+	struct sieve_storage *storage = &conn->lstorage->storage;
+	struct timeval start, end;
+	int debug_level;
+	bool debug;
+#if defined(HAVE_LDAP_SASL) || defined(LDAP_HAVE_START_TLS_S)
+	int ret;
+#endif
+
+	if (conn->conn_state != LDAP_CONN_STATE_DISCONNECTED)
+		return 0;
+
+	debug = FALSE;
+	if (str_to_int(set->debug_level, &debug_level) >= 0)
+		debug = debug_level > 0;
+
+	if (debug) {
+		if (gettimeofday(&start, NULL) < 0)
+			i_zero(&start);
+	}
+	i_assert(conn->pending_count == 0);
+	if (conn->ld == NULL) {
+		if (set->uris != NULL) {
+#ifdef LDAP_HAVE_INITIALIZE
+			if (ldap_initialize(&conn->ld, set->uris) != LDAP_SUCCESS)
+				conn->ld = NULL;
+#else
+			sieve_storage_sys_error(storage, "db: "
+				"Your LDAP library doesn't support "
+				"'uris' setting, use 'hosts' instead.");
+			return -1;
+#endif
+		} else
+			conn->ld = ldap_init(set->hosts, LDAP_PORT);
+
+		if (conn->ld == NULL) {
+			sieve_storage_sys_error(storage, "db: "
+				"ldap_init() failed with hosts: %s",	set->hosts);
+			return -1;
+		}
+
+		if (db_ldap_set_options(conn) < 0)
+			return -1;
+	}
+
+	if (set->tls) {
+#ifdef LDAP_HAVE_START_TLS_S
+		ret = ldap_start_tls_s(conn->ld, NULL, NULL);
+		if (ret != LDAP_SUCCESS) {
+			if (ret == LDAP_OPERATIONS_ERROR &&
+			    set->uris != NULL &&
+			    str_begins(set->uris, "ldaps:")) {
+				sieve_storage_sys_error(storage, "db: "
+					"Don't use both tls=yes and ldaps URI");
+			}
+			sieve_storage_sys_error(storage, "db: "
+				"ldap_start_tls_s() failed: %s", ldap_err2string(ret));
+			return -1;
+		}
+#else
+		sieve_storage_sys_error(storage, "db: "
+			"Your LDAP library doesn't support TLS");
+		return -1;
+#endif
+	}
+
+	if (set->sasl_bind) {
+#ifdef HAVE_LDAP_SASL
+		struct db_ldap_sasl_bind_context context;
+
+		i_zero(&context);
+		context.authcid = set->dn;
+		context.passwd = set->dnpass;
+		context.realm = set->sasl_realm;
+		context.authzid = set->sasl_authz_id;
+
+		/* There doesn't seem to be a way to do SASL binding
+		   asynchronously.. */
+		ret = ldap_sasl_interactive_bind_s(conn->ld, NULL,
+						   set->sasl_mech,
+						   NULL, NULL, LDAP_SASL_QUIET,
+						   sasl_interact, &context);
+		if (db_ldap_connect_finish(conn, ret) < 0)
+			return -1;
+#else
+		sieve_storage_sys_error(storage, "db: "
+			"sasl_bind=yes but no SASL support compiled in");
+		return -1;
+#endif
+		conn->conn_state = LDAP_CONN_STATE_BOUND;
+	} else {
+		if (db_ldap_bind(conn) < 0)
+			return -1;
+	}
+	if (debug) {
+		if (gettimeofday(&end, NULL) == 0) {
+			int msecs = timeval_diff_msecs(&end, &start);
+			sieve_storage_sys_debug(storage, "db: "
+				"Initialization took %d msecs", msecs);
+		}
+	}
+
+	if (db_ldap_get_fd(conn) < 0)
+		return -1;
+	conn->io = io_add(conn->fd, IO_READ, ldap_input, conn);
+	return 0;
+}
+
+void db_ldap_enable_input(struct ldap_connection *conn, bool enable)
+{
+	if (!enable) {
+		io_remove(&conn->io);
+	} else {
+		if (conn->io == NULL && conn->fd != -1) {
+			conn->io = io_add(conn->fd, IO_READ, ldap_input, conn);
+			ldap_input(conn);
+		}
+	}
+}
+
+static void db_ldap_disconnect_timeout(struct ldap_connection *conn)
+{
+	db_ldap_abort_requests(conn, UINT_MAX,
+		DB_LDAP_REQUEST_DISCONNECT_TIMEOUT_SECS, FALSE,
+		"Aborting (timeout), we're not connected to LDAP server");
+
+	if (aqueue_count(conn->request_queue) == 0) {
+		/* no requests left, remove this timeout handler */
+		timeout_remove(&conn->to);
+	}
+}
+
+static void db_ldap_conn_close(struct ldap_connection *conn)
+{
+	struct ldap_request *const *requests, *request;
+	unsigned int i;
+
+	conn->conn_state = LDAP_CONN_STATE_DISCONNECTED;
+	conn->default_bind_msgid = -1;
+
+	timeout_remove(&conn->to);
+
+	if (conn->pending_count != 0) {
+		requests = array_idx(&conn->request_array, 0);
+		for (i = 0; i < conn->pending_count; i++) {
+			request = requests[aqueue_idx(conn->request_queue, i)];
+
+			i_assert(request->msgid != -1);
+			request->msgid = -1;
+		}
+		conn->pending_count = 0;
+	}
+
+	if (conn->ld != NULL) {
+		ldap_unbind(conn->ld);
+		conn->ld = NULL;
+	}
+	conn->fd = -1;
+
+	/* the fd may have already been closed before ldap_unbind(),
+	   so we'll have to use io_remove_closed(). */
+	io_remove_closed(&conn->io);
+
+	if (aqueue_count(conn->request_queue) > 0) {
+		conn->to = timeout_add(DB_LDAP_REQUEST_DISCONNECT_TIMEOUT_SECS *
+				       1000/2, db_ldap_disconnect_timeout, conn);
+	}
+}
+
+struct ldap_field_find_context {
+	ARRAY_TYPE(string) attr_names;
+	pool_t pool;
+};
+
+#define IS_LDAP_ESCAPED_CHAR(c) \
+	((c) == '*' || (c) == '(' || (c) == ')' || (c) == '\\')
+
+const char *ldap_escape(const char *str)
+{
+	const char *p;
+	string_t *ret;
+
+	for (p = str; *p != '\0'; p++) {
+		if (IS_LDAP_ESCAPED_CHAR(*p))
+			break;
+	}
+
+	if (*p == '\0')
+		return str;
+
+	ret = t_str_new((size_t) (p - str) + 64);
+	str_append_data(ret, str, (size_t) (p - str));
+
+	for (; *p != '\0'; p++) {
+		if (IS_LDAP_ESCAPED_CHAR(*p))
+			str_append_c(ret, '\\');
+		str_append_c(ret, *p);
+	}
+	return str_c(ret);
+}
+
+struct ldap_connection *
+sieve_ldap_db_init(struct sieve_ldap_storage *lstorage)
+{
+	struct ldap_connection *conn;
+	pool_t pool;
+
+	pool = pool_alloconly_create("ldap_connection", 1024);
+	conn = p_new(pool, struct ldap_connection, 1);
+	conn->pool = pool;
+	conn->refcount = 1;
+	conn->lstorage = lstorage;
+
+	conn->conn_state = LDAP_CONN_STATE_DISCONNECTED;
+	conn->default_bind_msgid = -1;
+	conn->fd = -1;
+
+	i_array_init(&conn->request_array, 512);
+	conn->request_queue = aqueue_init(&conn->request_array.arr);
+
+	conn->next = ldap_connections;
+	ldap_connections = conn;
+	return conn;
+}
+
+void sieve_ldap_db_unref(struct ldap_connection **_conn)
+{
+	struct ldap_connection *conn = *_conn;
+	struct ldap_connection **p;
+
+	*_conn = NULL;
+	i_assert(conn->refcount >= 0);
+	if (--conn->refcount > 0)
+		return;
+
+	for (p = &ldap_connections; *p != NULL; p = &(*p)->next) {
+		if (*p == conn) {
+			*p = conn->next;
+			break;
+		}
+	}
+
+	db_ldap_abort_requests(conn, UINT_MAX, 0, FALSE, "Shutting down");
+	i_assert(conn->pending_count == 0);
+	db_ldap_conn_close(conn);
+	i_assert(conn->to == NULL);
+
+	array_free(&conn->request_array);
+	aqueue_deinit(&conn->request_queue);
+
+	pool_unref(&conn->pool);
+}
+
+static void db_ldap_switch_ioloop(struct ldap_connection *conn)
+{
+	if (conn->to != NULL)
+		conn->to = io_loop_move_timeout(&conn->to);
+	if (conn->io != NULL)
+		conn->io = io_loop_move_io(&conn->io);
+}
+
+static void db_ldap_wait(struct ldap_connection *conn)
+{
+	struct sieve_storage *storage = &conn->lstorage->storage;
+	struct ioloop *prev_ioloop = current_ioloop;
+
+	i_assert(conn->ioloop == NULL);
+
+	if (aqueue_count(conn->request_queue) == 0)
+		return;
+
+	conn->ioloop = io_loop_create();
+	db_ldap_switch_ioloop(conn);
+	/* either we're waiting for network I/O or we're getting out of a
+	   callback using timeout_add_short(0) */
+	i_assert(io_loop_have_ios(conn->ioloop) ||
+		 io_loop_have_immediate_timeouts(conn->ioloop));
+
+	do {
+		sieve_storage_sys_debug(storage, "db: "
+			"Waiting for %d requests to finish",
+			aqueue_count(conn->request_queue) );
+		io_loop_run(conn->ioloop);
+	} while (aqueue_count(conn->request_queue) > 0);
+
+	sieve_storage_sys_debug(storage, "db: "
+		"All requests finished");
+
+	current_ioloop = prev_ioloop;
+	db_ldap_switch_ioloop(conn);
+	current_ioloop = conn->ioloop;
+	io_loop_destroy(&conn->ioloop);
+}
+
+static void sieve_ldap_db_script_free(unsigned char *script)
+{
+	i_free(script);
+}
+
+static int
+sieve_ldap_db_get_script_modattr(struct ldap_connection *conn,
+	LDAPMessage *entry, pool_t pool, const char **modattr_r)
+{
+	const struct sieve_ldap_storage_settings *set = &conn->lstorage->set;
+	struct sieve_storage *storage = &conn->lstorage->storage;
+	char *attr, **vals;
+	BerElement *ber;
+
+	*modattr_r = NULL;
+
+	attr = ldap_first_attribute(conn->ld, entry, &ber);
+	while (attr != NULL) {
+		if (strcmp(attr, set->sieve_ldap_mod_attr) == 0) {
+			vals = ldap_get_values(conn->ld, entry, attr);
+			if (vals == NULL || vals[0] == NULL)
+				return 0;
+
+			if (vals[1] != NULL) {
+				sieve_storage_sys_warning(storage, "db: "
+					"Search returned more than one Sieve modified attribute `%s'; "
+					"using only the first one.", set->sieve_ldap_mod_attr);
+			} 
+
+			*modattr_r = p_strdup(pool, vals[0]);
+		
+			ldap_value_free(vals);
+			ldap_memfree(attr);
+			return 1;
+		}
+		ldap_memfree(attr);
+		attr = ldap_next_attribute(conn->ld, entry, ber);
+	}
+	ber_free(ber, 0);
+
+	return 0;
+}
+
+static int
+sieve_ldap_db_get_script(struct ldap_connection *conn,
+	LDAPMessage *entry, struct istream **script_r)
+{
+	const struct sieve_ldap_storage_settings *set = &conn->lstorage->set;
+	struct sieve_storage *storage = &conn->lstorage->storage;
+	char *attr;
+	unsigned char *data;
+	size_t size;
+	struct berval **vals;
+	BerElement *ber;
+
+	attr = ldap_first_attribute(conn->ld, entry, &ber);
+	while (attr != NULL) {
+		if (strcmp(attr, set->sieve_ldap_script_attr) == 0) {
+			vals = ldap_get_values_len(conn->ld, entry, attr);
+			if (vals == NULL || vals[0] == NULL)
+				return 0;
+
+			if (vals[1] != NULL) {
+				sieve_storage_sys_warning(storage, "db: "
+					"Search returned more than one Sieve script attribute `%s'; "
+					"using only the first one.", set->sieve_ldap_script_attr);
+			} 
+
+			size = vals[0]->bv_len;
+			data = i_malloc(size);
+
+			sieve_storage_sys_debug(storage, "db: "
+				"Found script with length %"PRIuSIZE_T, size);
+
+			memcpy(data, vals[0]->bv_val, size);
+		
+			ldap_value_free_len(vals);
+			ldap_memfree(attr);
+
+			*script_r = i_stream_create_from_data(data, size);
+			i_stream_add_destroy_callback
+				(*script_r,	sieve_ldap_db_script_free, data);
+			return 1;
+		}
+		ldap_memfree(attr);
+		attr = ldap_next_attribute(conn->ld, entry, ber);
+	}
+	ber_free(ber, 0);
+
+	return 0;
+}
+
+const struct var_expand_table
+auth_request_var_expand_static_tab[] = {
+	{ 'u', NULL, "user" },
+	{ 'n', NULL, "username" },
+	{ 'd', NULL, "domain" },
+	{ 'h', NULL, "home" },
+	{ '\0', NULL, "name" },
+	{ '\0', NULL, NULL }
+};
+
+static const struct var_expand_table *
+db_ldap_get_var_expand_table(struct ldap_connection *conn,
+	const char *name)
+{
+	struct sieve_ldap_storage *lstorage = conn->lstorage;
+	struct sieve_instance *svinst = lstorage->storage.svinst;
+	const unsigned int auth_count =
+		N_ELEMENTS(auth_request_var_expand_static_tab);
+	struct var_expand_table *tab;
+
+	/* keep the extra fields at the beginning. the last static_tab field
+	   contains the ending NULL-fields. */
+	tab = t_malloc_no0((auth_count) * sizeof(*tab));
+
+	memcpy(tab, auth_request_var_expand_static_tab,
+	       auth_count * sizeof(*tab));
+
+	tab[0].value = ldap_escape(lstorage->username);
+	tab[1].value = ldap_escape(t_strcut(lstorage->username, '@'));
+	tab[2].value = strchr(lstorage->username, '@');
+	if (tab[2].value != NULL)
+		tab[2].value = ldap_escape(tab[2].value+1);
+	tab[3].value = ldap_escape(svinst->home_dir);
+	tab[4].value = ldap_escape(name);
+	return tab;
+}
+
+struct sieve_ldap_script_lookup_request {
+	struct ldap_request request;
+
+	unsigned int entries;
+	const char *result_dn;
+	const char *result_modattr;
+};
+
+static void
+sieve_ldap_lookup_script_callback(struct ldap_connection *conn,
+			  struct ldap_request *request, LDAPMessage *res)
+{
+	struct sieve_storage *storage = &conn->lstorage->storage;
+	struct sieve_ldap_script_lookup_request *srequest =
+		(struct sieve_ldap_script_lookup_request *)request;
+
+	if (res == NULL) {
+		io_loop_stop(conn->ioloop);
+		return;
+	}
+
+	if (ldap_msgtype(res) != LDAP_RES_SEARCH_RESULT) {
+		if (srequest->result_dn == NULL) {
+			srequest->result_dn = p_strdup
+				(request->pool, ldap_get_dn(conn->ld, res));
+			(void)sieve_ldap_db_get_script_modattr
+				(conn, res, request->pool, &srequest->result_modattr);
+		} else if (srequest->entries++ == 0) {
+			sieve_storage_sys_warning(storage, "db: "
+				"Search returned more than one entry for Sieve script; "
+				"using only the first one.");
+		}
+	} else {
+		io_loop_stop(conn->ioloop);
+		return;
+	}
+}
+
+int sieve_ldap_db_lookup_script(struct ldap_connection *conn,
+	const char *name, const char **dn_r, const char **modattr_r)
+{
+	struct sieve_ldap_storage *lstorage = conn->lstorage;
+	struct sieve_storage *storage = &lstorage->storage;
+	const struct sieve_ldap_storage_settings *set = &lstorage->set;
+	struct sieve_ldap_script_lookup_request *request;
+	const struct var_expand_table *tab;
+	char **attr_names;
+	const char *error;
+	string_t *str;
+
+	pool_t pool = pool_alloconly_create
+		("sieve_ldap_script_lookup_request", 512);
+	request = p_new(pool, struct sieve_ldap_script_lookup_request, 1);
+	request->request.pool = pool;
+
+	tab = db_ldap_get_var_expand_table(conn, name);
+
+	str = t_str_new(512);
+	if (var_expand(str, set->base, tab, &error) <= 0) {
+		sieve_storage_sys_error(storage, "db: "
+			"Failed to expand base=%s: %s",
+			set->base, error);
+		return -1;
+	}
+	request->request.base = p_strdup(pool, str_c(str));
+
+	attr_names = p_new(pool, char *, 3);
+	attr_names[0] = p_strdup(pool, set->sieve_ldap_mod_attr);
+
+	str_truncate(str, 0);
+	if (var_expand(str, set->sieve_ldap_filter, tab, &error) <= 0) {
+		sieve_storage_sys_error(storage, "db: "
+			"Failed to expand sieve_ldap_filter=%s: %s",
+			set->sieve_ldap_filter, error);
+		return -1;
+	}
+
+	request->request.scope = lstorage->set.ldap_scope;
+	request->request.filter = p_strdup(pool, str_c(str));
+	request->request.attributes = attr_names;
+
+	sieve_storage_sys_debug(storage,
+			       "base=%s scope=%s filter=%s fields=%s",
+			       request->request.base, lstorage->set.scope,
+			       request->request.filter,
+			       t_strarray_join((const char **)attr_names, ","));
+
+	request->request.callback = sieve_ldap_lookup_script_callback;
+	db_ldap_request(conn, &request->request);
+	db_ldap_wait(conn);
+
+	*dn_r = t_strdup(request->result_dn);
+	*modattr_r = t_strdup(request->result_modattr);
+	pool_unref(&request->request.pool);
+	return (*dn_r == NULL ? 0 : 1);
+}
+
+struct sieve_ldap_script_read_request {
+	struct ldap_request request;
+
+	unsigned int entries;
+	struct istream *result;
+};
+
+static void
+sieve_ldap_read_script_callback(struct ldap_connection *conn,
+			  struct ldap_request *request, LDAPMessage *res)
+{
+	struct sieve_storage *storage = &conn->lstorage->storage;
+	struct sieve_ldap_script_read_request *srequest =
+		(struct sieve_ldap_script_read_request *)request;
+
+	if (res == NULL) {
+		io_loop_stop(conn->ioloop);
+		return;
+	}
+
+	if (ldap_msgtype(res) != LDAP_RES_SEARCH_RESULT) {
+
+		if (srequest->result == NULL) {
+			(void)sieve_ldap_db_get_script(conn, res, &srequest->result);
+		} else {
+			sieve_storage_sys_error(storage, "db: "
+				"Search returned more than one entry for Sieve script DN");
+			i_stream_unref(&srequest->result);
+		}
+
+	} else {
+		io_loop_stop(conn->ioloop);
+		return;
+	}
+}
+
+int sieve_ldap_db_read_script(struct ldap_connection *conn,
+	const char *dn, struct istream **script_r)
+{
+	struct sieve_ldap_storage *lstorage = conn->lstorage;
+	struct sieve_storage *storage = &lstorage->storage;
+	const struct sieve_ldap_storage_settings *set = &lstorage->set;
+	struct sieve_ldap_script_read_request *request;
+	char **attr_names;
+
+	pool_t pool = pool_alloconly_create
+		("sieve_ldap_script_read_request", 512);
+	request = p_new(pool, struct sieve_ldap_script_read_request, 1);
+	request->request.pool = pool;
+	request->request.base = p_strdup(pool, dn);
+
+	attr_names = p_new(pool, char *, 3);
+	attr_names[0] = p_strdup(pool, set->sieve_ldap_script_attr);
+
+	request->request.scope = LDAP_SCOPE_BASE;
+	request->request.filter = "(objectClass=*)";
+	request->request.attributes = attr_names;
+
+	sieve_storage_sys_debug(storage,
+			       "base=%s scope=base filter=%s fields=%s",
+			       request->request.base, request->request.filter,
+			       t_strarray_join((const char **)attr_names, ","));
+
+	request->request.callback = sieve_ldap_read_script_callback;
+	db_ldap_request(conn, &request->request);
+	db_ldap_wait(conn);
+
+	*script_r = request->result;
+	pool_unref(&request->request.pool);
+	return (*script_r == NULL ? 0 : 1);
+}
+
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-db.h
@@ -0,0 +1,140 @@
+#ifndef DB_LDAP_H
+#define DB_LDAP_H
+
+/* Functions like ldap_bind() have been deprecated in OpenLDAP 2.3
+   This define enables them until the code here can be refactored */
+#define LDAP_DEPRECATED 1
+
+/* Maximum number of pending requests before delaying new requests. */
+#define DB_LDAP_MAX_PENDING_REQUESTS 8
+/* If LDAP connection is down, fail requests after waiting for this long. */
+#define DB_LDAP_REQUEST_DISCONNECT_TIMEOUT_SECS 4
+/* If request is still in queue after this many seconds and other requests
+   have been replied, assume the request was lost and abort it. */
+#define DB_LDAP_REQUEST_LOST_TIMEOUT_SECS 60
+/* If server disconnects us, don't reconnect if no requests have been sent
+   for this many seconds. */
+#define DB_LDAP_IDLE_RECONNECT_SECS 60
+
+#include <ldap.h>
+
+#define HAVE_LDAP_SASL
+#ifdef HAVE_SASL_SASL_H
+#  include <sasl/sasl.h>
+#elif defined (HAVE_SASL_H)
+#  include <sasl.h>
+#else
+#  undef HAVE_LDAP_SASL
+#endif
+#ifdef LDAP_OPT_X_TLS
+#  define OPENLDAP_TLS_OPTIONS
+#endif
+#if !defined(SASL_VERSION_MAJOR) || SASL_VERSION_MAJOR < 2
+#  undef HAVE_LDAP_SASL
+#endif
+
+#ifndef LDAP_SASL_QUIET
+#  define LDAP_SASL_QUIET 0 /* Doesn't exist in Solaris LDAP */
+#endif
+
+/* Older versions may require calling ldap_result() twice */
+#if LDAP_VENDOR_VERSION <= 20112
+#  define OPENLDAP_ASYNC_WORKAROUND
+#endif
+
+/* Solaris LDAP library doesn't have LDAP_OPT_SUCCESS */
+#ifndef LDAP_OPT_SUCCESS
+#  define LDAP_OPT_SUCCESS LDAP_SUCCESS
+#endif
+
+struct ldap_connection;
+struct ldap_request;
+
+typedef void db_search_callback_t(struct ldap_connection *conn,
+				  struct ldap_request *request,
+				  LDAPMessage *res);
+struct ldap_request {
+	pool_t pool;
+
+	/* msgid for sent requests, -1 if not sent */
+	int msgid;
+	/* timestamp when request was created */
+	time_t create_time;
+
+	bool failed;
+
+	db_search_callback_t *callback;
+
+	const char *base;
+	const char *filter;
+	int scope;
+	char **attributes;
+
+	struct db_ldap_result *result;
+};
+
+enum ldap_connection_state {
+	/* Not connected */
+	LDAP_CONN_STATE_DISCONNECTED,
+	/* Binding - either to default dn or doing auth bind */
+	LDAP_CONN_STATE_BINDING,
+	/* Bound */
+	LDAP_CONN_STATE_BOUND
+};
+
+struct ldap_connection {
+	struct ldap_connection *next;
+
+	struct sieve_ldap_storage *lstorage;
+
+	pool_t pool;
+	int refcount;
+
+	LDAP *ld;
+	enum ldap_connection_state conn_state;
+	int default_bind_msgid;
+
+	int fd;
+	struct io *io;
+	struct timeout *to;
+	struct ioloop *ioloop;
+
+	/* Request queue contains sent requests at tail (msgid != -1) and
+	   queued requests at head (msgid == -1). */
+	struct aqueue *request_queue;
+	ARRAY(struct ldap_request *) request_array;
+	/* Number of messages in queue with msgid != -1 */
+	unsigned int pending_count;
+
+	/* Timestamp when we last received a reply */
+	time_t last_reply_stamp;
+};
+
+
+int ldap_deref_from_str(const char *str, int *deref_r);
+int ldap_scope_from_str(const char *str, int *scope_r);
+#ifdef OPENLDAP_TLS_OPTIONS
+int ldap_tls_require_cert_from_str(const char *str, int *opt_x_tls_r);
+#endif
+
+/* Send/queue request */
+void db_ldap_request(struct ldap_connection *conn,
+		     struct ldap_request *request);
+
+void db_ldap_enable_input(struct ldap_connection *conn, bool enable);
+
+const char *ldap_escape(const char *str);
+const char *ldap_get_error(struct ldap_connection *conn);
+
+int sieve_ldap_db_connect(struct ldap_connection *conn);
+
+struct ldap_connection *
+sieve_ldap_db_init(struct sieve_ldap_storage *lstorage);
+void sieve_ldap_db_unref(struct ldap_connection **conn);
+
+int sieve_ldap_db_lookup_script(struct ldap_connection *conn,
+	const char *name, const char **dn_r, const char **modattr_r);
+int sieve_ldap_db_read_script(struct ldap_connection *conn,
+	const char *dn, struct istream **script_r);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-script.c
@@ -0,0 +1,371 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "time-util.h"
+#include "istream.h"
+
+#include "sieve-ldap-storage.h"
+
+#if defined(SIEVE_BUILTIN_LDAP) || defined(PLUGIN_BUILD)
+
+#include "str.h"
+#include "strfuncs.h"
+
+#include "sieve-error.h"
+#include "sieve-dump.h"
+#include "sieve-binary.h"
+
+/*
+ * Script file implementation
+ */
+
+static struct sieve_ldap_script *sieve_ldap_script_alloc(void)
+{
+	struct sieve_ldap_script *lscript;
+	pool_t pool;
+
+	pool = pool_alloconly_create("sieve_ldap_script", 1024);
+	lscript = p_new(pool, struct sieve_ldap_script, 1);
+	lscript->script = sieve_ldap_script;
+	lscript->script.pool = pool;
+
+	return lscript;
+}
+
+struct sieve_ldap_script *sieve_ldap_script_init
+(struct sieve_ldap_storage *lstorage, const char *name)
+{
+	struct sieve_storage *storage = &lstorage->storage;
+	struct sieve_ldap_script *lscript = NULL;
+	const char *location;
+
+	if ( name == NULL ) {
+		name = SIEVE_LDAP_SCRIPT_DEFAULT;
+		location = storage->location;
+	} else {
+		location = t_strconcat
+			(storage->location, ";name=", name, NULL);
+	}
+
+	lscript = sieve_ldap_script_alloc();
+	sieve_script_init(&lscript->script,
+		storage, &sieve_ldap_script, location, name);
+	return lscript;
+}
+
+static int sieve_ldap_script_open
+(struct sieve_script *script, enum sieve_error *error_r)
+{
+	struct sieve_ldap_script *lscript =
+		(struct sieve_ldap_script *)script;
+	struct sieve_storage *storage = script->storage;
+	struct sieve_ldap_storage *lstorage =
+		(struct sieve_ldap_storage *)storage;
+	int ret;
+
+	if ( sieve_ldap_db_connect(lstorage->conn) < 0 ) {
+		sieve_storage_set_critical(storage,
+			"Failed to connect to LDAP database");
+		*error_r = storage->error_code;
+		return -1;
+	}
+
+	if ( (ret=sieve_ldap_db_lookup_script(lstorage->conn,
+		script->name, &lscript->dn, &lscript->modattr)) <= 0 ) {
+		if ( ret == 0 ) {
+			sieve_script_sys_debug(script,
+				"Script entry not found");
+			sieve_script_set_error(script,
+				SIEVE_ERROR_NOT_FOUND,
+				"Sieve script not found");
+		} else {
+			sieve_script_set_internal_error(script);
+		}
+		*error_r = script->storage->error_code;
+		return -1;
+	}
+
+	return 0;
+}
+
+static int sieve_ldap_script_get_stream
+(struct sieve_script *script, struct istream **stream_r,
+	enum sieve_error *error_r)
+{	
+	struct sieve_ldap_script *lscript =
+		(struct sieve_ldap_script *)script;
+	struct sieve_storage *storage = script->storage;
+	struct sieve_ldap_storage *lstorage =
+		(struct sieve_ldap_storage *)storage;
+	int ret;
+
+	i_assert(lscript->dn != NULL);
+
+	if ( (ret=sieve_ldap_db_read_script(
+		lstorage->conn, lscript->dn, stream_r)) <= 0 ) {
+		if ( ret == 0 ) {
+			sieve_script_sys_debug(script,
+				"Script attribute not found");
+			sieve_script_set_error(script,
+				SIEVE_ERROR_NOT_FOUND,
+				"Sieve script not found");
+		} else {
+			sieve_script_set_internal_error(script);
+		}
+		*error_r = script->storage->error_code;
+		return -1;
+	}
+	return 0;
+}
+
+static int sieve_ldap_script_binary_read_metadata
+(struct sieve_script *script, struct sieve_binary_block *sblock,
+	sieve_size_t *offset)
+{
+	struct sieve_ldap_script *lscript =
+		(struct sieve_ldap_script *)script;
+	struct sieve_instance *svinst = script->storage->svinst;
+	struct sieve_ldap_storage *lstorage =
+		(struct sieve_ldap_storage *)script->storage;		
+	struct sieve_binary *sbin =
+		sieve_binary_block_get_binary(sblock);
+	time_t bmtime = sieve_binary_mtime(sbin);
+	string_t *dn, *modattr;
+
+	/* config file changed? */
+	if ( bmtime <= lstorage->set_mtime ) {
+		if ( svinst->debug ) {
+			sieve_script_sys_debug(script,
+				"Sieve binary `%s' is not newer "
+				"than the LDAP configuration `%s' (%s <= %s)",
+				sieve_binary_path(sbin), lstorage->config_file,
+				t_strflocaltime("%Y-%m-%d %H:%M:%S", bmtime),
+				t_strflocaltime("%Y-%m-%d %H:%M:%S", lstorage->set_mtime));
+		}
+		return 0;
+	}
+
+	/* open script if not open already */
+	if ( lscript->dn == NULL &&
+		sieve_script_open(script, NULL) < 0 )
+		return 0;
+
+	/* if modattr not found, recompile always */
+	if ( lscript->modattr == NULL || *lscript->modattr == '\0' ) {
+		sieve_script_sys_error(script,
+			"LDAP entry for script `%s' "
+			"has no modified attribute `%s'",
+			sieve_script_location(script),
+			lstorage->set.sieve_ldap_mod_attr);
+		return 0;
+	}
+
+	/* compare DN in binary and from search result */
+	if ( !sieve_binary_read_string(sblock, offset, &dn) ) {
+		sieve_script_sys_error(script,
+			"Binary `%s' has invalid metadata for script `%s': "
+			"Invalid DN",
+			sieve_binary_path(sbin), sieve_script_location(script));
+		return -1;
+	}
+	i_assert( lscript->dn != NULL );
+	if ( strcmp(str_c(dn), lscript->dn) != 0 ) {
+		sieve_script_sys_debug(script,
+			"Binary `%s' reports different LDAP DN for script `%s' "
+			"(`%s' rather than `%s')",
+			sieve_binary_path(sbin), sieve_script_location(script),
+			str_c(dn), lscript->dn);
+		return 0;
+	}
+
+	/* compare modattr in binary and from search result */
+	if ( !sieve_binary_read_string(sblock, offset, &modattr) ) {
+		sieve_script_sys_error(script,
+			"Binary `%s' has invalid metadata for script `%s': "
+			"Invalid modified attribute",
+			sieve_binary_path(sbin), sieve_script_location(script));
+		return -1;
+	}
+	if ( strcmp(str_c(modattr), lscript->modattr) != 0 ) {
+		sieve_script_sys_debug(script,
+			"Binary `%s' reports different modified attribute content "
+			"for script `%s' (`%s' rather than `%s')",
+			sieve_binary_path(sbin), sieve_script_location(script),
+			str_c(modattr), lscript->modattr);
+		return 0;
+	}
+	return 1;
+}
+
+static void sieve_ldap_script_binary_write_metadata
+(struct sieve_script *script, struct sieve_binary_block *sblock)
+{
+	struct sieve_ldap_script *lscript =
+		(struct sieve_ldap_script *)script;
+
+	sieve_binary_emit_cstring(sblock, lscript->dn);
+	if (lscript->modattr == NULL)
+		sieve_binary_emit_cstring(sblock, "");
+	else
+		sieve_binary_emit_cstring(sblock, lscript->modattr);
+}
+
+static bool sieve_ldap_script_binary_dump_metadata
+(struct sieve_script *script ATTR_UNUSED, struct sieve_dumptime_env *denv,
+	struct sieve_binary_block *sblock, sieve_size_t *offset)
+{
+	string_t *dn, *modattr;
+
+	if ( !sieve_binary_read_string(sblock, offset, &dn) )
+		return FALSE;
+	sieve_binary_dumpf(denv, "ldap.dn = %s\n", str_c(dn));
+
+	if ( !sieve_binary_read_string(sblock, offset, &modattr) )
+		return FALSE;
+	sieve_binary_dumpf(denv, "ldap.mod_attr = %s\n", str_c(modattr));
+
+	return TRUE;
+}
+
+static const char *sieve_ldap_script_get_binpath
+(struct sieve_ldap_script *lscript)
+{
+	struct sieve_script *script = &lscript->script;
+	struct sieve_storage *storage = script->storage;
+
+	if ( lscript->binpath == NULL ) {
+		if ( storage->bin_dir == NULL )
+			return NULL;
+		lscript->binpath = p_strconcat(script->pool,
+			storage->bin_dir, "/",
+			sieve_binfile_from_name(script->name), NULL);
+	}
+
+	return lscript->binpath;
+}
+
+static struct sieve_binary *sieve_ldap_script_binary_load
+(struct sieve_script *script, enum sieve_error *error_r)
+{
+	struct sieve_storage *storage = script->storage;
+	struct sieve_ldap_script *lscript =
+		(struct sieve_ldap_script *)script;
+
+	if ( sieve_ldap_script_get_binpath(lscript) == NULL )
+		return NULL;
+
+	return sieve_binary_open(storage->svinst,
+		lscript->binpath, script, error_r);
+}
+
+static int sieve_ldap_script_binary_save
+(struct sieve_script *script, struct sieve_binary *sbin, bool update,
+	enum sieve_error *error_r)
+{
+	struct sieve_ldap_script *lscript =
+		(struct sieve_ldap_script *)script;
+
+	if ( sieve_ldap_script_get_binpath(lscript) == NULL )
+		return 0;
+
+	if ( sieve_storage_setup_bindir(script->storage, 0700) < 0 )
+		return -1;
+
+	return sieve_binary_save
+		(sbin, lscript->binpath, update, 0600, error_r);
+}
+
+static bool sieve_ldap_script_equals
+(const struct sieve_script *script, const struct sieve_script *other)
+{
+	struct sieve_storage *storage = script->storage;
+	struct sieve_storage *sother = other->storage;
+
+	if ( strcmp(storage->location, sother->location) != 0 )
+		return FALSE;
+
+	i_assert( script->name != NULL && other->name != NULL );
+
+	return ( strcmp(script->name, other->name) == 0 );
+}
+
+const struct sieve_script sieve_ldap_script = {
+	.driver_name = SIEVE_LDAP_STORAGE_DRIVER_NAME,
+	.v = {
+		.open = sieve_ldap_script_open,
+
+		.get_stream = sieve_ldap_script_get_stream,
+
+		.binary_read_metadata = sieve_ldap_script_binary_read_metadata,
+		.binary_write_metadata = sieve_ldap_script_binary_write_metadata,
+		.binary_dump_metadata = sieve_ldap_script_binary_dump_metadata,
+		.binary_load = sieve_ldap_script_binary_load,
+		.binary_save = sieve_ldap_script_binary_save,
+
+		.equals = sieve_ldap_script_equals
+	}
+};
+
+/*
+ * Script sequence
+ */
+
+struct sieve_ldap_script_sequence {
+	struct sieve_script_sequence seq;
+
+	bool done:1;
+};
+
+struct sieve_script_sequence *sieve_ldap_storage_get_script_sequence
+(struct sieve_storage *storage, enum sieve_error *error_r)
+{
+	struct sieve_ldap_script_sequence *lsec = NULL;
+
+	if ( error_r != NULL )
+		*error_r = SIEVE_ERROR_NONE;
+
+	/* Create sequence object */
+	lsec = i_new(struct sieve_ldap_script_sequence, 1);
+	sieve_script_sequence_init(&lsec->seq, storage);
+
+	return &lsec->seq;
+}
+
+struct sieve_script *sieve_ldap_script_sequence_next
+(struct sieve_script_sequence *seq, enum sieve_error *error_r)
+{
+	struct sieve_ldap_script_sequence *lsec =
+		(struct sieve_ldap_script_sequence *)seq;
+	struct sieve_ldap_storage *lstorage =
+		(struct sieve_ldap_storage *)seq->storage;
+	struct sieve_ldap_script *lscript;
+
+	if ( error_r != NULL )
+		*error_r = SIEVE_ERROR_NONE;
+
+	if ( lsec->done )
+		return NULL;
+	lsec->done = TRUE;
+
+	lscript = sieve_ldap_script_init
+		(lstorage, seq->storage->script_name);
+	if ( sieve_script_open(&lscript->script, error_r) < 0 ) {
+		struct sieve_script *script = &lscript->script;
+		sieve_script_unref(&script);
+		return NULL;
+	}
+
+	return &lscript->script;
+}
+
+void sieve_ldap_script_sequence_destroy
+(struct sieve_script_sequence *seq)
+{
+	struct sieve_ldap_script_sequence *lsec =
+		(struct sieve_ldap_script_sequence *)seq;
+	i_free(lsec);
+}
+
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-storage-settings.c
@@ -0,0 +1,169 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "env-util.h"
+#include "settings.h"
+
+#include "sieve-common.h"
+
+#include "sieve-ldap-storage.h"
+
+#if defined(SIEVE_BUILTIN_LDAP) || defined(PLUGIN_BUILD)
+
+#include "sieve-error.h"
+
+#include "sieve-ldap-db.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define DEF_STR(name) DEF_STRUCT_STR(name, sieve_ldap_storage_settings)
+#define DEF_INT(name) DEF_STRUCT_INT(name, sieve_ldap_storage_settings)
+#define DEF_BOOL(name) DEF_STRUCT_BOOL(name, sieve_ldap_storage_settings)
+
+static struct setting_def setting_defs[] = {
+	DEF_STR(hosts),
+	DEF_STR(uris),
+	DEF_STR(dn),
+	DEF_STR(dnpass),
+	DEF_BOOL(tls),
+	DEF_BOOL(sasl_bind),
+	DEF_STR(sasl_mech),
+	DEF_STR(sasl_realm),
+	DEF_STR(sasl_authz_id),
+	DEF_STR(tls_ca_cert_file),
+	DEF_STR(tls_ca_cert_dir),
+	DEF_STR(tls_cert_file),
+	DEF_STR(tls_key_file),
+	DEF_STR(tls_cipher_suite),
+	DEF_STR(tls_require_cert),
+	DEF_STR(deref),
+	DEF_STR(scope),
+	DEF_STR(base),
+	DEF_INT(ldap_version),
+	DEF_STR(debug_level),
+	DEF_STR(ldaprc_path),
+	DEF_STR(sieve_ldap_script_attr),
+	DEF_STR(sieve_ldap_mod_attr),
+	DEF_STR(sieve_ldap_filter),
+
+	{ 0, NULL, 0 }
+};
+
+static struct sieve_ldap_storage_settings default_settings = {
+	.hosts = NULL,
+	.uris = NULL,
+	.dn = NULL,
+	.dnpass = NULL,
+	.tls = FALSE,
+	.sasl_bind = FALSE,
+	.sasl_mech = NULL,
+	.sasl_realm = NULL,
+	.sasl_authz_id = NULL,
+	.tls_ca_cert_file = NULL,
+	.tls_ca_cert_dir = NULL,
+	.tls_cert_file = NULL,
+	.tls_key_file = NULL,
+	.tls_cipher_suite = NULL,
+	.tls_require_cert = NULL,
+	.deref = "never",
+	.scope = "subtree",
+	.base = NULL,
+	.ldap_version = 3,
+	.debug_level = "0",
+	.ldaprc_path = "",
+	.sieve_ldap_script_attr = "mailSieveRuleSource",
+	.sieve_ldap_mod_attr = "modifyTimestamp",
+	.sieve_ldap_filter = "(&(objectClass=posixAccount)(uid=%u))",
+};
+
+static const char *parse_setting(const char *key, const char *value,
+				 struct sieve_ldap_storage *lstorage)
+{
+	return parse_setting_from_defs
+		(lstorage->storage.pool, setting_defs, &lstorage->set, key, value);
+}
+
+int sieve_ldap_storage_read_settings
+(struct sieve_ldap_storage *lstorage, const char *config_path)
+{
+	struct sieve_storage *storage = &lstorage->storage;
+	const char *str, *error;
+	struct stat st;
+
+	if ( stat(config_path, &st) < 0 ) {
+		sieve_storage_sys_error(storage,
+			"Failed to read LDAP storage config: "
+			"stat(%s) failed: %m", config_path);
+		return -1;
+	}
+
+	lstorage->set = default_settings;
+	lstorage->set_mtime = st.st_mtime;
+	
+	if (!settings_read_nosection
+		(config_path, parse_setting, lstorage, &error)) {
+		sieve_storage_set_critical(storage,
+			"Failed to read LDAP storage config `%s': %s",
+			config_path, error);
+		return -1;
+	}
+
+	if (lstorage->set.base == NULL) {
+		sieve_storage_set_critical(storage,
+			"Invalid LDAP storage config `%s': "
+			"No search base given", config_path);
+		return -1;
+	}
+
+	if (lstorage->set.uris == NULL && lstorage->set.hosts == NULL) {
+		sieve_storage_set_critical(storage,
+			"Invalid LDAP storage config `%s': "
+			"No uris or hosts set", config_path);
+		return -1;
+	}
+
+	if (*lstorage->set.ldaprc_path != '\0') {
+		str = getenv("LDAPRC");
+		if (str != NULL && strcmp(str, lstorage->set.ldaprc_path) != 0) {
+			sieve_storage_set_critical(storage,
+				"Invalid LDAP storage config `%s': "
+				"Multiple different ldaprc_path settings not allowed "
+				"(%s and %s)", config_path, str, lstorage->set.ldaprc_path);
+			return -1;
+		}
+		env_put(t_strconcat("LDAPRC=", lstorage->set.ldaprc_path, NULL));
+	}
+
+	if ( ldap_deref_from_str
+		(lstorage->set.deref, &lstorage->set.ldap_deref) < 0 ) {
+		sieve_storage_set_critical(storage,
+			"Invalid LDAP storage config `%s': "
+			"Invalid deref option `%s'",
+			config_path, lstorage->set.deref);;
+	}
+
+	if ( ldap_scope_from_str
+		(lstorage->set.scope, &lstorage->set.ldap_scope) < 0 ) {
+		sieve_storage_set_critical(storage,
+			"Invalid LDAP storage config `%s': "
+			"Invalid scope option `%s'",
+			config_path, lstorage->set.scope);;
+	}
+
+#ifdef OPENLDAP_TLS_OPTIONS	
+	if ( lstorage->set.tls_require_cert != NULL &&
+		ldap_tls_require_cert_from_str(lstorage->set.tls_require_cert,
+		&lstorage->set.ldap_tls_require_cert) < 0) {
+		sieve_storage_set_critical(storage,
+			"Invalid LDAP storage config `%s': "
+			"Invalid tls_require_cert option `%s'",
+			config_path, lstorage->set.tls_require_cert);
+	}
+#endif
+	return 0;
+}
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-storage.c
@@ -0,0 +1,232 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+//#include "ldap.h"
+
+#include "sieve-common.h"
+
+#include "sieve-ldap-storage.h"
+
+#if defined(SIEVE_BUILTIN_LDAP) || defined(PLUGIN_BUILD)
+
+#include "sieve-error.h"
+
+#ifndef PLUGIN_BUILD
+const struct sieve_storage sieve_ldap_storage;
+#else
+const struct sieve_storage sieve_ldap_storage_plugin;
+#endif
+
+/*
+ * Storage class
+ */
+
+static struct sieve_storage *sieve_ldap_storage_alloc(void)
+{
+	struct sieve_ldap_storage *lstorage;
+	pool_t pool;
+
+	pool = pool_alloconly_create("sieve_ldap_storage", 1024);
+	lstorage = p_new(pool, struct sieve_ldap_storage, 1);
+#ifndef PLUGIN_BUILD
+	lstorage->storage = sieve_ldap_storage;
+#else
+	lstorage->storage = sieve_ldap_storage_plugin;
+#endif
+	lstorage->storage.pool = pool;
+
+	return &lstorage->storage;
+}
+
+static int sieve_ldap_storage_init
+(struct sieve_storage *storage, const char *const *options,
+	enum sieve_error *error_r)
+{
+	struct sieve_ldap_storage *lstorage =
+		(struct sieve_ldap_storage *)storage;
+	struct sieve_instance *svinst = storage->svinst;
+	const char *username = NULL;
+
+	if ( options != NULL ) {
+		while ( *options != NULL ) {
+			const char *option = *options;
+
+			if ( strncasecmp(option, "user=", 5) == 0 && option[5] != '\0' ) {
+				username = option+5;
+			} else {
+				sieve_storage_set_critical(storage,
+					"Invalid option `%s'", option);
+				*error_r = SIEVE_ERROR_TEMP_FAILURE;
+				return -1;
+			}
+
+			options++;
+		}
+	}
+
+	if ( username == NULL ) {
+		if ( svinst->username == NULL ) {
+			sieve_storage_set_critical(storage,
+				"No username specified");
+			*error_r = SIEVE_ERROR_TEMP_FAILURE;
+			return -1;
+		}
+		username = svinst->username;
+	}
+
+	sieve_storage_sys_debug(storage,
+		"user=%s, config=%s", username, storage->location);
+
+	if ( sieve_ldap_storage_read_settings(lstorage, storage->location) < 0 )
+		return -1;
+
+	lstorage->username = p_strdup(storage->pool, username);
+	lstorage->config_file = p_strdup(storage->pool, storage->location);
+	lstorage->conn = sieve_ldap_db_init(lstorage);
+
+	storage->location = p_strconcat(storage->pool,
+		SIEVE_LDAP_STORAGE_DRIVER_NAME, ":", storage->location,
+		";user=", username, NULL);
+
+	return 0;
+}
+
+static void sieve_ldap_storage_destroy
+(struct sieve_storage *storage)
+{
+	struct sieve_ldap_storage *lstorage =
+		(struct sieve_ldap_storage *)storage;
+
+	if ( lstorage->conn != NULL )
+		sieve_ldap_db_unref(&lstorage->conn);
+}
+
+/*
+ * Script access
+ */
+
+static struct sieve_script *sieve_ldap_storage_get_script
+(struct sieve_storage *storage, const char *name)
+{
+	struct sieve_ldap_storage *lstorage =
+		(struct sieve_ldap_storage *)storage;
+	struct sieve_ldap_script *lscript;
+
+	T_BEGIN {
+		lscript = sieve_ldap_script_init(lstorage, name);
+	} T_END;
+
+	return &lscript->script;
+}
+
+/*
+ * Active script
+ */
+
+struct sieve_script *sieve_ldap_storage_active_script_open
+(struct sieve_storage *storage)
+{
+	struct sieve_ldap_storage *lstorage =
+		(struct sieve_ldap_storage *)storage;
+	struct sieve_ldap_script *lscript;
+
+	lscript = sieve_ldap_script_init
+		(lstorage, storage->script_name);
+	if ( sieve_script_open(&lscript->script, NULL) < 0 ) {
+		struct sieve_script *script = &lscript->script;
+		sieve_script_unref(&script);
+		return NULL;
+	}
+
+	return &lscript->script;
+}
+
+int sieve_ldap_storage_active_script_get_name
+(struct sieve_storage *storage, const char **name_r)
+{
+	if ( storage->script_name != NULL )
+		*name_r = storage->script_name;
+	else
+		*name_r = SIEVE_LDAP_SCRIPT_DEFAULT;
+	return 0;
+}
+
+/*
+ * Driver definition
+ */
+
+#ifndef PLUGIN_BUILD
+const struct sieve_storage sieve_ldap_storage = {
+#else
+const struct sieve_storage sieve_ldap_storage_plugin = {
+#endif
+	.driver_name = SIEVE_LDAP_STORAGE_DRIVER_NAME,
+	.version = 0,
+	.v = {
+		.alloc = sieve_ldap_storage_alloc,
+		.init = sieve_ldap_storage_init,
+		.destroy = sieve_ldap_storage_destroy,
+
+		.get_script = sieve_ldap_storage_get_script,
+
+		.get_script_sequence = sieve_ldap_storage_get_script_sequence,
+		.script_sequence_next = sieve_ldap_script_sequence_next,
+		.script_sequence_destroy = sieve_ldap_script_sequence_destroy,
+
+		.active_script_get_name = sieve_ldap_storage_active_script_get_name,
+		.active_script_open = sieve_ldap_storage_active_script_open,
+
+		// FIXME: impement management interface
+	}
+};
+
+#ifndef SIEVE_BUILTIN_LDAP
+/* Building a plugin */
+
+const char *sieve_storage_ldap_plugin_version = PIGEONHOLE_ABI_VERSION;
+
+void sieve_storage_ldap_plugin_load
+(struct sieve_instance *svinst, void **context);
+void sieve_storage_ldap_plugin_unload
+(struct sieve_instance *svinst, void *context);
+void sieve_storage_ldap_plugin_init(void);
+void sieve_storage_ldap_plugin_deinit(void);
+
+void sieve_storage_ldap_plugin_load
+(struct sieve_instance *svinst, void **context ATTR_UNUSED)
+{
+	sieve_storage_class_register
+		(svinst, &sieve_ldap_storage_plugin);	
+
+	if ( svinst->debug ) {
+		sieve_sys_debug(svinst,
+			"Sieve LDAP storage plugin for %s version %s loaded",
+			PIGEONHOLE_NAME, PIGEONHOLE_VERSION_FULL);
+	}
+}
+
+void sieve_storage_ldap_plugin_unload
+(struct sieve_instance *svinst ATTR_UNUSED,
+	void *context ATTR_UNUSED)
+{
+	sieve_storage_class_unregister
+		(svinst, &sieve_ldap_storage_plugin);	
+}
+
+void sieve_storage_ldap_plugin_init(void)
+{
+	/* Nothing */
+}
+
+void sieve_storage_ldap_plugin_deinit(void)
+{
+	/* Nothing */
+}
+#endif
+
+#else /* !defined(SIEVE_BUILTIN_LDAP) && !defined(PLUGIN_BUILD) */
+const struct sieve_storage sieve_ldap_storage = {
+	.driver_name = SIEVE_LDAP_STORAGE_DRIVER_NAME
+};
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-storage.h
@@ -0,0 +1,108 @@
+#ifndef SIEVE_LDAP_STORAGE_H
+#define SIEVE_LDAP_STORAGE_H
+
+#include "sieve.h"
+#include "sieve-script-private.h"
+#include "sieve-storage-private.h"
+
+#define SIEVE_LDAP_SCRIPT_DEFAULT "default"
+
+#if defined(SIEVE_BUILTIN_LDAP) || defined(PLUGIN_BUILD)
+
+#include "sieve-ldap-db.h"
+
+struct sieve_ldap_storage;
+
+/*
+ * LDAP settings
+ */
+
+struct sieve_ldap_storage_settings {
+	const char *hosts;
+	const char *uris;
+	const char *dn;
+	const char *dnpass;
+
+	bool tls;
+	bool sasl_bind;
+	const char *sasl_mech;
+	const char *sasl_realm;
+	const char *sasl_authz_id;
+
+	const char *tls_ca_cert_file;
+	const char *tls_ca_cert_dir;
+	const char *tls_cert_file;
+	const char *tls_key_file;
+	const char *tls_cipher_suite;
+	const char *tls_require_cert;
+
+	const char *deref;
+	const char *scope;
+	const char *base;
+	unsigned int ldap_version;
+
+	const char *ldaprc_path;
+	const char *debug_level;
+
+	const char *sieve_ldap_script_attr;
+	const char *sieve_ldap_mod_attr;
+	const char *sieve_ldap_filter;
+
+	/* ... */
+	int ldap_deref, ldap_scope, ldap_tls_require_cert;
+};
+
+int sieve_ldap_storage_read_settings
+	(struct sieve_ldap_storage *lstorage, const char *config_path);
+
+/*
+ * Storage class
+ */
+
+struct sieve_ldap_storage {
+	struct sieve_storage storage;
+
+	struct sieve_ldap_storage_settings set;
+	time_t set_mtime;
+
+	const char *config_file;
+	const char *username; // FIXME: needed?
+
+	struct ldap_connection *conn;
+};
+
+struct sieve_script *sieve_ldap_storage_active_script_open
+	(struct sieve_storage *storage);
+int sieve_ldap_storage_active_script_get_name
+	(struct sieve_storage *storage, const char **name_r);
+
+/*
+ * Script class
+ */
+
+struct sieve_ldap_script {
+	struct sieve_script script;
+
+	const char *dn;
+	const char *modattr;
+
+	const char *binpath;
+};
+
+struct sieve_ldap_script *sieve_ldap_script_init
+	(struct sieve_ldap_storage *lstorage, const char *name);
+
+/*
+ * Script sequence
+ */
+
+struct sieve_script_sequence *sieve_ldap_storage_get_script_sequence
+	(struct sieve_storage *storage, enum sieve_error *error_r);
+
+struct sieve_script *sieve_ldap_script_sequence_next
+    (struct sieve_script_sequence *seq, enum sieve_error *error_r);
+void sieve_ldap_script_sequence_destroy(struct sieve_script_sequence *seq);
+
+#endif
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/tst-address.c
@@ -0,0 +1,280 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-message.h"
+#include "sieve-address.h"
+#include "sieve-address-parts.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+#include <stdio.h>
+
+/*
+ * Address test
+ *
+ * Syntax:
+ *    address [ADDRESS-PART] [COMPARATOR] [MATCH-TYPE]
+ *       <header-list: string-list> <key-list: string-list>
+ */
+
+static bool tst_address_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool tst_address_validate
+	(struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_address_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def tst_address = {
+	.identifier = "address",
+	.type = SCT_TEST,
+	.positional_args = 2,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = tst_address_registered,
+	.validate = tst_address_validate,
+	.generate = tst_address_generate
+};
+
+/*
+ * Address operation
+ */
+
+static bool tst_address_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_address_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def tst_address_operation = {
+	.mnemonic = "ADDRESS",
+	.code = SIEVE_OPERATION_ADDRESS,
+	.dump = tst_address_operation_dump,
+	.execute = tst_address_operation_execute
+};
+
+/*
+ * Test registration
+ */
+
+static bool tst_address_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext ATTR_UNUSED,
+	struct sieve_command_registration *cmd_reg)
+{
+	/* The order of these is not significant */
+	sieve_comparators_link_tag
+		(valdtr, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR );
+	sieve_match_types_link_tags
+		(valdtr, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
+	sieve_address_parts_link_tags
+		(valdtr, cmd_reg, SIEVE_AM_OPT_ADDRESS_PART);
+	return TRUE;
+}
+
+/*
+ * Validation
+ */
+
+/* List of valid headers:
+ *   Implementations MUST restrict the address test to headers that
+ *   contain addresses, but MUST include at least From, To, Cc, Bcc,
+ *   Sender, Resent-From, and Resent-To, and it SHOULD include any other
+ *   header that utilizes an "address-list" structured header body.
+ *
+ * This list explicitly does not contain the envelope-to and return-path
+ * headers. The envelope test must be used to test against these addresses.
+ *
+ * FIXME: this restriction is somewhat odd. Sieve list advises to allow
+ *        any other header as long as its content matches the address-list
+ *        grammar.
+ */
+static const char * const _allowed_headers[] = {
+	/* Required */
+	"from", "to", "cc", "bcc", "sender", "resent-from", "resent-to",
+
+	/* Additional (RFC 822 / RFC 2822) */
+	"reply-to", "resent-reply-to", "resent-sender", "resent-cc", "resent-bcc",
+
+	/* Non-standard (RFC 2076, draft-palme-mailext-headers-08.txt) */
+	"for-approval", "for-handling", "for-comment", "apparently-to", "errors-to",
+	"delivered-to", "return-receipt-to", "x-admin", "read-receipt-to",
+	"x-confirm-reading-to", "return-receipt-requested",
+	"registered-mail-reply-requested-by", "mail-followup-to", "mail-reply-to",
+	"abuse-reports-to", "x-complaints-to", "x-report-abuse-to",
+
+	/* Undocumented */
+	"x-beenthere", "x-original-to",
+
+	NULL
+};
+
+static int _header_is_allowed
+(void *context ATTR_UNUSED, struct sieve_ast_argument *arg)
+{
+	if ( sieve_argument_is_string_literal(arg) ) {
+		const char *header = sieve_ast_strlist_strc(arg);
+
+		const char * const *hdsp = _allowed_headers;
+		while ( *hdsp != NULL ) {
+			if ( strcasecmp( *hdsp, header ) == 0 )
+				return 1;
+
+			hdsp++;
+		}
+
+		return 0;
+	}
+
+	return 1;
+}
+
+static bool tst_address_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+	struct sieve_ast_argument *arg = tst->first_positional;
+	struct sieve_ast_argument *header;
+	struct sieve_comparator cmp_default =
+		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+	struct sieve_match_type mcht_default =
+		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "header list", 1, SAAT_STRING_LIST) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+		return FALSE;
+
+	if ( !sieve_command_verify_headers_argument(valdtr, arg) )
+        return FALSE;
+
+	/* Check if supplied header names are allowed
+	 *   FIXME: verify dynamic header names at runtime
+	 */
+	header = arg;
+	if ( sieve_ast_stringlist_map
+		(&header, NULL, _header_is_allowed) <= 0 ) {
+		i_assert(header != NULL);
+		sieve_argument_validate_error(valdtr, header,
+			"specified header '%s' is not allowed for the address test",
+			str_sanitize(sieve_ast_strlist_strc(header), 64));
+		return FALSE;
+	}
+
+	/* Check key list */
+
+	arg = sieve_ast_argument_next(arg);
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "key list", 2, SAAT_STRING_LIST) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+		return FALSE;
+
+	/* Validate the key argument to a specified match type */
+	return sieve_match_type_validate
+		(valdtr, tst, arg, &mcht_default, &cmp_default);
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_address_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
+{
+	sieve_operation_emit(cgenv->sblock, NULL, &tst_address_operation);
+
+	/* Generate arguments */
+	return sieve_generate_arguments(cgenv, tst, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_address_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "ADDRESS");
+	sieve_code_descend(denv);
+
+	/* Handle any optional arguments */
+	if ( sieve_message_opr_optional_dump(denv, address, NULL) != 0 )
+		return FALSE;
+
+	return
+		sieve_opr_stringlist_dump(denv, address, "header list") &&
+		sieve_opr_stringlist_dump(denv, address, "key list");
+}
+
+/*
+ * Code execution
+ */
+
+static int tst_address_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	struct sieve_comparator cmp =
+		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+	struct sieve_match_type mcht =
+		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+	struct sieve_address_part addrp =
+		SIEVE_ADDRESS_PART_DEFAULT(all_address_part);
+	struct sieve_stringlist *hdr_list, *hdr_value_list, *value_list, *key_list;
+	struct sieve_address_list *addr_list;
+	ARRAY_TYPE(sieve_message_override) svmos;
+	int match, ret;
+
+	/* Read optional operands */
+	i_zero(&svmos);
+	if ( sieve_message_opr_optional_read
+		(renv, address, NULL, &ret, &addrp, &mcht, &cmp, &svmos) < 0 )
+		return ret;
+
+	/* Read header-list */
+	if ( (ret=sieve_opr_stringlist_read(renv, address, "header-list", &hdr_list))
+		<= 0 )
+		return ret;
+
+	/* Read key-list */
+	if ( (ret=sieve_opr_stringlist_read(renv, address, "key-list", &key_list))
+		<= 0 )
+		return ret;
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "address test");
+
+	/* Get header */
+	sieve_runtime_trace_descend(renv);
+	if ( (ret=sieve_message_get_header_fields
+		(renv, hdr_list, &svmos, FALSE, &hdr_value_list)) <= 0 )
+		return ret;
+	sieve_runtime_trace_ascend(renv);
+
+	/* Create value stringlist */
+	addr_list = sieve_header_address_list_create(renv, hdr_value_list);
+	value_list = sieve_address_part_stringlist_create(renv, &addrp, addr_list);
+
+	/* Perform match */
+	if ( (match=sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret)) < 0 )
+		return ret;
+
+	/* Set test result for subsequent conditional jump */
+	sieve_interpreter_set_test_result(renv->interp, match > 0);
+	return SIEVE_EXEC_OK;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/tst-allof.c
@@ -0,0 +1,108 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+
+/*
+ * Allof test
+ *
+ * Syntax
+ *   allof <tests: test-list>
+ */
+
+static bool tst_allof_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx,
+		struct sieve_jumplist *jumps, bool jump_true);
+static bool tst_allof_validate_const
+	(struct sieve_validator *valdtr, struct sieve_command *tst,
+		int *const_current, int const_new);
+
+const struct sieve_command_def tst_allof = {
+	.identifier = "allof",
+	.type = SCT_TEST,
+	.positional_args = 0,
+	.subtests = 2,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate_const = tst_allof_validate_const,
+	.control_generate = tst_allof_generate
+};
+
+/*
+ * Code validation
+ */
+
+static bool tst_allof_validate_const
+(struct sieve_validator *valdtr ATTR_UNUSED,
+	struct sieve_command *tst ATTR_UNUSED, int *const_current, int const_next)
+{
+	if ( const_next == 0 ) {
+		*const_current = 0;
+		return FALSE;
+	}
+
+	if ( *const_current != -1 )
+		*const_current = const_next;
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_allof_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx,
+	struct sieve_jumplist *jumps, bool jump_true)
+{
+	struct sieve_binary_block *sblock = cgenv->sblock;
+	struct sieve_ast_node *test;
+	struct sieve_jumplist false_jumps;
+
+	if ( sieve_ast_test_count(ctx->ast_node) > 1 ) {
+		if ( jump_true ) {
+			/* Prepare jumplist */
+			sieve_jumplist_init_temp(&false_jumps, sblock);
+		}
+
+		test = sieve_ast_test_first(ctx->ast_node);
+		while ( test != NULL ) {
+			bool result;
+
+			/* If this test list must jump on false, all sub-tests can simply add their jumps
+			 * to the caller's jump list, otherwise this test redirects all false jumps to the
+			 * end of the currently generated code. This is just after a final jump to the true
+			 * case
+			 */
+			if ( jump_true )
+				result = sieve_generate_test(cgenv, test, &false_jumps, FALSE);
+			else
+				result = sieve_generate_test(cgenv, test, jumps, FALSE);
+
+			if ( !result ) return FALSE;
+
+			test = sieve_ast_test_next(test);
+		}
+
+		if ( jump_true ) {
+			/* All tests succeeded, jump to case TRUE */
+			sieve_operation_emit(cgenv->sblock, NULL, &sieve_jmp_operation);
+			sieve_jumplist_add(jumps, sieve_binary_emit_offset(sblock, 0));
+
+			/* All false exits jump here */
+			sieve_jumplist_resolve(&false_jumps);
+		}
+	} else {
+		/* Script author is being inefficient; we can optimize the allof test away */
+		test = sieve_ast_test_first(ctx->ast_node);
+		sieve_generate_test(cgenv, test, jumps, jump_true);
+	}
+
+	return TRUE;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/tst-anyof.c
@@ -0,0 +1,107 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-generator.h"
+#include "sieve-validator.h"
+#include "sieve-binary.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+
+/*
+ * Anyof test
+ *
+ * Syntax
+ *   anyof <tests: test-list>
+ */
+
+static bool tst_anyof_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx,
+		struct sieve_jumplist *jumps, bool jump_true);
+static bool tst_anyof_validate_const
+	(struct sieve_validator *valdtr, struct sieve_command *tst,
+		int *const_current, int const_next);
+
+const struct sieve_command_def tst_anyof = {
+	.identifier = "anyof",
+	.type = SCT_TEST,
+	.positional_args = 0,
+	.subtests = 2,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate_const = tst_anyof_validate_const,
+	.control_generate = tst_anyof_generate
+};
+
+/*
+ * Code validation
+ */
+
+static bool tst_anyof_validate_const
+(struct sieve_validator *valdtr ATTR_UNUSED,
+	struct sieve_command *tst ATTR_UNUSED, int *const_current, int const_next)
+{
+	if ( const_next > 0 ) {
+		*const_current = 1;
+		return FALSE;
+	}
+
+	if ( *const_current != -1 )
+		*const_current = const_next;
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_anyof_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx,
+		struct sieve_jumplist *jumps, bool jump_true)
+{
+	struct sieve_binary_block *sblock = cgenv->sblock;
+	struct sieve_ast_node *test;
+	struct sieve_jumplist true_jumps;
+
+	if ( sieve_ast_test_count(ctx->ast_node) > 1 ) {
+		if ( !jump_true ) {
+			/* Prepare jumplist */
+			sieve_jumplist_init_temp(&true_jumps, sblock);
+		}
+
+		test = sieve_ast_test_first(ctx->ast_node);
+		while ( test != NULL ) {
+			bool result;
+
+			/* If this test list must jump on true, all sub-tests can simply add their jumps
+			 * to the caller's jump list, otherwise this test redirects all true jumps to the
+			 * end of the currently generated code. This is just after a final jump to the false
+			 * case
+			 */
+			if ( !jump_true )
+				result = sieve_generate_test(cgenv, test, &true_jumps, TRUE);
+			else
+				result = sieve_generate_test(cgenv, test, jumps, TRUE);
+
+			if ( !result ) return FALSE;
+
+			test = sieve_ast_test_next(test);
+		}
+
+		if ( !jump_true ) {
+			/* All tests failed, jump to case FALSE */
+			sieve_operation_emit(sblock, NULL, &sieve_jmp_operation);
+			sieve_jumplist_add(jumps, sieve_binary_emit_offset(sblock, 0));
+
+			/* All true exits jump here */
+			sieve_jumplist_resolve(&true_jumps);
+		}
+	} else {
+		/* Script author is being inefficient; we can optimize the allof test away */
+		test = sieve_ast_test_first(ctx->ast_node);
+		sieve_generate_test(cgenv, test, jumps, jump_true);
+	}
+
+	return TRUE;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/tst-exists.c
@@ -0,0 +1,179 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-code-dumper.h"
+
+/*
+ * Exists test
+ *
+ * Syntax:
+ *    exists <header-names: string-list>
+ */
+
+static bool tst_exists_validate
+	(struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_exists_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *tst);
+
+const struct sieve_command_def tst_exists = {
+	.identifier = "exists",
+	.type = SCT_TEST,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = tst_exists_validate,
+	.generate = tst_exists_generate
+};
+
+/*
+ * Exists operation
+ */
+
+static bool tst_exists_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_exists_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def tst_exists_operation = {
+	.mnemonic = "EXISTS",
+	.code = SIEVE_OPERATION_EXISTS,
+	.dump = tst_exists_operation_dump,
+	.execute = tst_exists_operation_execute
+};
+
+/*
+ * Validation
+ */
+
+static bool tst_exists_validate
+  (struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+	struct sieve_ast_argument *arg = tst->first_positional;
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "header names", 1, SAAT_STRING_LIST) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+		return FALSE;
+
+	return sieve_command_verify_headers_argument(valdtr, arg);
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_exists_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
+{
+	sieve_operation_emit(cgenv->sblock, NULL, &tst_exists_operation);
+
+ 	/* Generate arguments */
+    return sieve_generate_arguments(cgenv, tst, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_exists_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "EXISTS");
+	sieve_code_descend(denv);
+
+	/* Optional operands */
+	if ( sieve_message_opr_optional_dump(denv, address, NULL) != 0 )
+		return FALSE;
+
+	return sieve_opr_stringlist_dump(denv, address, "header names");
+}
+
+/*
+ * Code execution
+ */
+
+static int tst_exists_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	struct sieve_stringlist *hdr_list;
+	ARRAY_TYPE(sieve_message_override) svmos;
+	string_t *hdr_item;
+	bool matched;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Optional operands */
+	i_zero(&svmos);
+	if ( sieve_message_opr_optional_read
+		(renv, address, NULL, &ret, NULL, NULL, NULL, &svmos) < 0 )
+		return ret;
+
+	/* Read header-list */
+	if ( (ret=sieve_opr_stringlist_read(renv, address, "header-list", &hdr_list))
+		<= 0 )
+		return ret;
+
+	/*
+	 * Perfrom test
+	 */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "exists test");
+	sieve_runtime_trace_descend(renv);
+
+	/* Iterate through all requested headers to match (must find all specified) */
+	hdr_item = NULL;
+	matched = TRUE;
+	while ( matched &&
+		(ret=sieve_stringlist_next_item(hdr_list, &hdr_item)) > 0 ) {
+		struct sieve_stringlist *value_list;
+		string_t *dummy;
+
+		/* Get header */
+		if ( (ret=sieve_message_get_header_fields
+			(renv, sieve_single_stringlist_create(renv, hdr_item, FALSE),
+				&svmos, FALSE, &value_list)) <= 0 )
+			return ret;
+
+		if ( (ret=sieve_stringlist_next_item(value_list, &dummy)) < 0)
+			return value_list->exec_status;
+
+		if ( ret == 0 )
+			matched = FALSE;
+
+		sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
+			"header `%s' %s", str_sanitize(str_c(hdr_item), 80),
+			( matched ? "exists" : "is missing" ));
+	}
+
+	if ( matched )
+		sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING, "all headers exist");
+	else
+		sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING, "headers are missing");
+
+	/* Set test result for subsequent conditional jump */
+	if ( ret >= 0 ) {
+		sieve_interpreter_set_test_result(renv->interp, matched);
+		return SIEVE_EXEC_OK;
+	}
+
+	sieve_runtime_trace_error(renv, "invalid header-list item");
+	return SIEVE_EXEC_BIN_CORRUPT;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/tst-header.c
@@ -0,0 +1,204 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+/*
+ * Header test
+ *
+ * Syntax:
+ *   header [COMPARATOR] [MATCH-TYPE]
+ *     <header-names: string-list> <key-list: string-list>
+ */
+
+static bool tst_header_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool tst_header_validate
+	(struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_header_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *tst);
+
+const struct sieve_command_def tst_header = {
+	.identifier = "header",
+	.type = SCT_TEST,
+	.positional_args = 2,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = tst_header_registered,
+	.validate = tst_header_validate,
+	.generate = tst_header_generate
+};
+
+/*
+ * Header operation
+ */
+
+static bool tst_header_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_header_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def tst_header_operation = {
+	.mnemonic = "HEADER",
+	.code = SIEVE_OPERATION_HEADER,
+	.dump = tst_header_operation_dump,
+	.execute = tst_header_operation_execute
+};
+
+/*
+ * Test registration
+ */
+
+static bool tst_header_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext ATTR_UNUSED,
+	struct sieve_command_registration *cmd_reg)
+{
+	/* The order of these is not significant */
+	sieve_comparators_link_tag(valdtr, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR);
+	sieve_match_types_link_tags(valdtr, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
+
+	return TRUE;
+}
+
+/*
+ * Validation
+ */
+
+static bool tst_header_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+	struct sieve_ast_argument *arg = tst->first_positional;
+	struct sieve_comparator cmp_default =
+		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+	struct sieve_match_type mcht_default =
+		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "header names", 1, SAAT_STRING_LIST) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+		return FALSE;
+
+	if ( !sieve_command_verify_headers_argument(valdtr, arg) )
+		return FALSE;
+
+	arg = sieve_ast_argument_next(arg);
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "key list", 2, SAAT_STRING_LIST) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+		return FALSE;
+
+	/* Validate the key argument to a specified match type */
+	return sieve_match_type_validate
+		(valdtr, tst, arg, &mcht_default, &cmp_default);
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_header_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
+{
+	sieve_operation_emit(cgenv->sblock, NULL, &tst_header_operation);
+
+ 	/* Generate arguments */
+	return sieve_generate_arguments(cgenv, tst, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_header_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "HEADER");
+	sieve_code_descend(denv);
+
+	/* Optional operands */
+	if ( sieve_message_opr_optional_dump(denv, address, NULL) != 0 )
+		return FALSE;
+
+	return
+		sieve_opr_stringlist_dump(denv, address, "header names") &&
+		sieve_opr_stringlist_dump(denv, address, "key list");
+}
+
+/*
+ * Code execution
+ */
+
+static int tst_header_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	struct sieve_comparator cmp =
+		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+	struct sieve_match_type mcht =
+		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+	struct sieve_stringlist *hdr_list, *key_list, *value_list;
+	ARRAY_TYPE(sieve_message_override) svmos;
+	int match, ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Optional operands */
+	i_zero(&svmos);
+	if ( sieve_message_opr_optional_read
+		(renv, address, NULL, &ret, NULL, &mcht, &cmp, &svmos) < 0 )
+		return ret;
+
+	/* Read header-list */
+	if ( (ret=sieve_opr_stringlist_read(renv, address, "header-list", &hdr_list))
+		<= 0 )
+		return ret;
+
+	/* Read key-list */
+	if ( (ret=sieve_opr_stringlist_read(renv, address, "key-list", &key_list))
+		<= 0 )
+		return ret;
+
+	/*
+	 * Perform test
+	 */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "header test");
+
+	/* Get header */
+	sieve_runtime_trace_descend(renv);
+	if ( (ret=sieve_message_get_header_fields
+		(renv, hdr_list, &svmos, TRUE, &value_list)) <= 0 )
+		return ret;
+	sieve_runtime_trace_ascend(renv);
+
+	/* Perform match */
+	if ( (match=sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret)) < 0 )
+		return ret;
+
+	/* Set test result for subsequent conditional jump */
+	sieve_interpreter_set_test_result(renv->interp, match > 0);
+	return SIEVE_EXEC_OK;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/tst-not.c
@@ -0,0 +1,67 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+
+/*
+ * Not test
+ *
+ * Syntax:
+ *   not <tests: test-list>
+ */
+
+static bool tst_not_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx,
+		struct sieve_jumplist *jumps, bool jump_true);
+static bool tst_not_validate_const
+	(struct sieve_validator *valdtr, struct sieve_command *tst,
+		int *const_current, int const_next);
+
+const struct sieve_command_def tst_not = {
+	.identifier = "not",
+	.type = SCT_TEST,
+	.positional_args = 0,
+	.subtests = 1,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate_const = tst_not_validate_const,
+	.control_generate = tst_not_generate
+};
+
+/*
+ * Code validation
+ */
+
+static bool tst_not_validate_const
+(struct sieve_validator *valdtr ATTR_UNUSED,
+	struct sieve_command *tst ATTR_UNUSED, int *const_current, int const_next)
+{
+	if ( const_next < 0 )
+		*const_current = -1;
+	else if ( const_next > 0 )
+		*const_current = 0;
+	else
+		*const_current = 1;
+
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_not_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx,
+	struct sieve_jumplist *jumps, bool jump_true)
+{
+	struct sieve_ast_node *test;
+
+	/* Validator verified the existance of the single test already */
+	test = sieve_ast_test_first(ctx->ast_node);
+
+	return sieve_generate_test(cgenv, test, jumps, !jump_true);
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/tst-size.c
@@ -0,0 +1,304 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "mail-storage.h"
+
+#include "sieve-common.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+/*
+ * Size test
+ *
+ * Syntax:
+ *    size <":over" / ":under"> <limit: number>
+ */
+
+static bool tst_size_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool tst_size_pre_validate
+	(struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_size_validate
+	(struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_size_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def tst_size = {
+	.identifier = "size",
+	.type = SCT_TEST,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = tst_size_registered,
+	.pre_validate = tst_size_pre_validate,
+	.validate = tst_size_validate,
+	.generate = tst_size_generate
+};
+
+/*
+ * Size operations
+ */
+
+static bool tst_size_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_size_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def tst_size_over_operation = {
+	.mnemonic = "SIZE-OVER",
+	.code = SIEVE_OPERATION_SIZE_OVER,
+	.dump = tst_size_operation_dump,
+	.execute = tst_size_operation_execute
+};
+
+const struct sieve_operation_def tst_size_under_operation = {
+	.mnemonic = "SIZE-UNDER",
+	.code = SIEVE_OPERATION_SIZE_UNDER,
+	.dump = tst_size_operation_dump,
+	.execute = tst_size_operation_execute
+};
+
+/*
+ * Context data
+ */
+
+struct tst_size_context_data {
+	enum { SIZE_UNASSIGNED, SIZE_UNDER, SIZE_OVER } type;
+};
+
+#define TST_SIZE_ERROR_DUP_TAG \
+	"exactly one of the ':under' or ':over' tags must be specified " \
+	"for the size test, but more were found"
+
+/*
+ * Tag validation
+ */
+
+static bool tst_size_validate_over_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_command *tst)
+{
+	struct tst_size_context_data *ctx_data =
+		(struct tst_size_context_data *) tst->data;
+
+	if ( ctx_data->type != SIZE_UNASSIGNED ) {
+		sieve_argument_validate_error(valdtr, *arg, TST_SIZE_ERROR_DUP_TAG);
+		return FALSE;
+	}
+
+	ctx_data->type = SIZE_OVER;
+
+	/* Delete this tag */
+	*arg = sieve_ast_arguments_detach(*arg, 1);
+
+	return TRUE;
+}
+
+static bool tst_size_validate_under_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg ATTR_UNUSED,
+	struct sieve_command *tst)
+{
+	struct tst_size_context_data *ctx_data =
+		(struct tst_size_context_data *) tst->data;
+
+	if ( ctx_data->type != SIZE_UNASSIGNED ) {
+		sieve_argument_validate_error(valdtr, *arg, TST_SIZE_ERROR_DUP_TAG);
+		return FALSE;
+	}
+
+	ctx_data->type = SIZE_UNDER;
+
+	/* Delete this tag */
+	*arg = sieve_ast_arguments_detach(*arg, 1);
+
+	return TRUE;
+}
+
+/*
+ * Test registration
+ */
+
+static const struct sieve_argument_def size_over_tag = {
+	.identifier = "over",
+	.validate = tst_size_validate_over_tag
+};
+
+static const struct sieve_argument_def size_under_tag = {
+	.identifier = "under",
+	.validate = tst_size_validate_under_tag,
+};
+
+static bool tst_size_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext ATTR_UNUSED,
+	struct sieve_command_registration *cmd_reg)
+{
+	/* Register our tags */
+	sieve_validator_register_tag(valdtr, cmd_reg, NULL, &size_over_tag, 0);
+	sieve_validator_register_tag(valdtr, cmd_reg, NULL, &size_under_tag, 0);
+
+	return TRUE;
+}
+
+/*
+ * Test validation
+ */
+
+static bool tst_size_pre_validate
+(struct sieve_validator *valdtr ATTR_UNUSED,
+	struct sieve_command *tst)
+{
+	struct tst_size_context_data *ctx_data;
+
+	/* Assign context */
+	ctx_data = p_new(sieve_command_pool(tst), struct tst_size_context_data, 1);
+	ctx_data->type = SIZE_UNASSIGNED;
+	tst->data = ctx_data;
+
+	return TRUE;
+}
+
+static bool tst_size_validate
+	(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+	struct tst_size_context_data *ctx_data =
+		(struct tst_size_context_data *) tst->data;
+	struct sieve_ast_argument *arg = tst->first_positional;
+
+	if ( ctx_data->type == SIZE_UNASSIGNED ) {
+		sieve_command_validate_error(valdtr, tst,
+			"the size test requires either the :under or the :over tag "
+			"to be specified");
+		return FALSE;
+	}
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "limit", 1, SAAT_NUMBER) ) {
+		return FALSE;
+	}
+
+	return sieve_validator_argument_activate(valdtr, tst, arg, FALSE);
+}
+
+/*
+ * Code generation
+ */
+
+bool tst_size_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
+{
+	struct tst_size_context_data *ctx_data =
+		(struct tst_size_context_data *) tst->data;
+
+	if ( ctx_data->type == SIZE_OVER )
+		sieve_operation_emit(cgenv->sblock, NULL, &tst_size_over_operation);
+	else
+		sieve_operation_emit(cgenv->sblock, NULL, &tst_size_under_operation);
+
+ 	/* Generate arguments */
+	if ( !sieve_generate_arguments(cgenv, tst, NULL) )
+		return FALSE;
+
+	return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_size_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "%s", sieve_operation_mnemonic(denv->oprtn));
+	sieve_code_descend(denv);
+
+	return
+		sieve_opr_number_dump(denv, address, "limit");
+}
+
+/*
+ * Code execution
+ */
+
+static inline bool tst_size_get
+(const struct sieve_runtime_env *renv, sieve_number_t *size)
+{
+	struct mail *mail = sieve_message_get_mail(renv->msgctx);
+	uoff_t psize;
+
+	if ( mail_get_physical_size(mail, &psize) < 0 )
+		return FALSE;
+
+	*size = psize;
+
+	return TRUE;
+}
+
+static int tst_size_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	sieve_number_t mail_size, limit;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Read size limit */
+	if ( (ret=sieve_opr_number_read(renv, address, "limit", &limit)) <= 0 )
+		return ret;
+
+	/*
+	 * Perform test
+	 */
+
+	/* Get the size of the message */
+	if ( !tst_size_get(renv, &mail_size) ) {
+		/* FIXME: improve this error */
+		sieve_sys_error(renv->svinst, "failed to assess message size");
+		return SIEVE_EXEC_FAILURE;
+	}
+
+	/* Perform the test */
+	if ( sieve_operation_is(renv->oprtn, tst_size_over_operation) ) {
+		sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "size :over test");
+
+		if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING) ) {
+			sieve_runtime_trace_descend(renv);
+
+			sieve_runtime_trace(renv, 0,
+				"comparing message size %llu",
+				(unsigned long long)mail_size);
+			sieve_runtime_trace(renv, 0,
+				"with upper limit %llu",
+				(unsigned long long)limit);
+		}
+
+		sieve_interpreter_set_test_result(renv->interp, (mail_size > limit));
+	} else {
+		sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "size :under test");
+
+		if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING) ) {
+			sieve_runtime_trace_descend(renv);
+
+			sieve_runtime_trace(renv, 0,
+				"comparing message size %llu",
+				(unsigned long long)mail_size);
+			sieve_runtime_trace(renv, 0,
+				"with lower limit %llu",
+				(unsigned long long)limit);
+		}
+
+		sieve_interpreter_set_test_result(renv->interp, (mail_size < limit));
+	}
+
+	return SIEVE_EXEC_OK;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/tst-truefalse.c
@@ -0,0 +1,104 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-ast.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-interpreter.h"
+
+/*
+ * True/False test command
+ */
+
+static bool tst_false_validate_const
+	(struct sieve_validator *valdtr, struct sieve_command *tst,
+		int *const_current, int const_next);
+static bool tst_false_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd,
+		struct sieve_jumplist *jumps, bool jump_true);
+
+const struct sieve_command_def tst_false = {
+	.identifier = "false",
+	.type = SCT_TEST,
+	.positional_args = 0,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate_const = tst_false_validate_const,
+	.control_generate = tst_false_generate
+};
+
+static bool tst_true_validate_const
+	(struct sieve_validator *valdtr, struct sieve_command *tst,
+		int *const_current, int const_next);
+static bool tst_true_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd,
+		struct sieve_jumplist *jumps, bool jump_true);
+
+const struct sieve_command_def tst_true = {
+	.identifier = "true",
+	.type = SCT_TEST,
+	.positional_args = 0,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate_const = tst_true_validate_const,
+	.control_generate = tst_true_generate
+};
+
+/*
+ * Code validation
+ */
+
+static bool tst_false_validate_const
+(struct sieve_validator *valdtr ATTR_UNUSED,
+	struct sieve_command *tst ATTR_UNUSED, int *const_current,
+	int const_next ATTR_UNUSED)
+{
+	*const_current = 0;
+	return TRUE;
+}
+
+static bool tst_true_validate_const
+(struct sieve_validator *valdtr ATTR_UNUSED,
+	struct sieve_command *tst ATTR_UNUSED, int *const_current,
+	int const_next ATTR_UNUSED)
+{
+	*const_current = 1;
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_false_generate
+(const struct sieve_codegen_env *cgenv,
+	struct sieve_command *cmd ATTR_UNUSED,
+	struct sieve_jumplist *jumps, bool jump_true)
+{
+	if ( !jump_true ) {
+		sieve_operation_emit(cgenv->sblock, NULL, &sieve_jmp_operation);
+		sieve_jumplist_add(jumps, sieve_binary_emit_offset(cgenv->sblock, 0));
+	}
+
+	return TRUE;
+}
+
+static bool tst_true_generate
+(const struct sieve_codegen_env *cgenv,
+	struct sieve_command *cmd ATTR_UNUSED,
+	struct sieve_jumplist *jumps, bool jump_true)
+{
+	if ( jump_true ) {
+		sieve_operation_emit(cgenv->sblock, NULL, &sieve_jmp_operation);
+		sieve_jumplist_add(jumps, sieve_binary_emit_offset(cgenv->sblock, 0));
+	}
+
+	return TRUE;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/util/Makefile.am
@@ -0,0 +1,51 @@
+noinst_LTLIBRARIES = libsieve_util.la
+
+AM_CPPFLAGS = \
+	$(LIBDOVECOT_INCLUDE) \
+	$(LIBDOVECOT_SERVICE_INCLUDE) \
+	-DMODULEDIR=\""$(dovecot_moduledir)"\"
+
+libsieve_util_la_DEPENDENCIES = $(LIBDOVECOT_STORAGE_DEPS) $(LIBDOVECOT_DEPS)
+
+libsieve_util_la_SOURCES = \
+	mail-raw.c \
+	edit-mail.c \
+	rfc2822.c
+
+headers = \
+	mail-raw.h \
+	edit-mail.h \
+	rfc2822.h
+
+pkginc_libdir=$(dovecot_pkgincludedir)/sieve
+pkginc_lib_HEADERS = $(headers)
+
+test_programs = \
+	test-edit-mail \
+	test-rfc2822
+
+noinst_PROGRAMS = $(test_programs)
+
+test_libs = \
+	libsieve_util.la \
+	$(LIBDOVECOT_STORAGE) \
+	$(LIBDOVECOT)
+test_deps = \
+	libsieve_util.la \
+	$(LIBDOVECOT_STORAGE_DEPS) \
+	$(LIBDOVECOT_DEPS)
+
+test_edit_mail_SOURCES = test-edit-mail.c
+test_edit_mail_LDADD = $(test_libs)
+test_edit_mail_DEPENDENCIES = $(test_deps)
+
+test_rfc2822_SOURCES = test-rfc2822.c
+test_rfc2822_LDADD = $(test_libs)
+test_rfc2822_DEPENDENCIES = $(test_deps)
+
+check: check-am check-test
+check-test: all-am
+	for bin in $(test_programs); do \
+	  if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+	done
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/util/edit-mail.c
@@ -0,0 +1,2185 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "mempool.h"
+#include "llist.h"
+#include "istream-private.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "message-parser.h"
+#include "message-header-encode.h"
+#include "message-header-decode.h"
+#include "mail-user.h"
+#include "mail-storage-private.h"
+#include "index-mail.h"
+#include "raw-storage.h"
+
+#include "rfc2822.h"
+
+#include "edit-mail.h"
+
+/*
+ * Forward declarations
+ */
+
+struct _header_field_index;
+struct _header_field;
+struct _header_index;
+struct _header;
+
+static struct mail_vfuncs edit_mail_vfuncs;
+
+struct edit_mail_istream;
+struct istream *edit_mail_istream_create(struct edit_mail *edmail);
+
+static struct _header_index *edit_mail_header_clone
+	(struct edit_mail *edmail, struct _header *header);
+
+/*
+ * Raw storage
+ */
+
+static struct mail_user *edit_mail_user = NULL;
+static unsigned int edit_mail_refcount = 0;
+
+static struct mail_user *edit_mail_raw_storage_get(struct mail_user *mail_user)
+{
+	if ( edit_mail_user == NULL ) {
+		void **sets = master_service_settings_get_others(master_service);
+
+		edit_mail_user = raw_storage_create_from_set(mail_user->set_info, sets[0]);
+	}
+
+	edit_mail_refcount++;
+
+	return edit_mail_user;
+}
+
+static void edit_mail_raw_storage_drop(void)
+{
+	i_assert(edit_mail_refcount > 0);
+
+	if ( --edit_mail_refcount != 0)
+		return;
+
+	mail_user_unref(&edit_mail_user);
+	edit_mail_user = NULL;
+}
+
+/*
+ * Headers
+ */
+
+struct _header_field {
+	struct _header *header;
+
+	unsigned int refcount;
+
+	char *data;
+	size_t size;
+	size_t virtual_size;
+	uoff_t offset;
+	unsigned int lines;
+
+	uoff_t body_offset;
+
+	char *utf8_value;
+};
+
+struct _header_field_index {
+	struct _header_field_index *prev, *next;
+
+	struct _header_field *field;
+	struct _header_index *header;
+};
+
+struct _header {
+	unsigned int refcount;
+
+	char *name;
+};
+
+struct _header_index {
+	struct _header_index *prev, *next;
+
+	struct _header *header;
+
+	struct _header_field_index *first, *last;
+
+	unsigned int count;
+};
+
+static inline struct _header *_header_create(const char *name)
+{
+	struct _header *header;
+
+	header = i_new(struct _header, 1);
+	header->name = i_strdup(name);
+	header->refcount = 1;
+
+	return header;
+}
+
+static inline void _header_ref(struct _header *header)
+{
+	header->refcount++;
+}
+
+static inline void _header_unref(struct _header *header)
+{
+	i_assert( header->refcount > 0 );
+	if ( --header->refcount != 0 )
+		return;
+
+	i_free(header->name);
+	i_free(header);
+}
+
+static inline struct _header_field *_header_field_create(struct _header *header)
+{
+	struct _header_field *hfield;
+
+	hfield = i_new(struct _header_field, 1);
+	hfield->refcount = 1;
+	hfield->header = header;
+	if ( header != NULL )
+		_header_ref(header);
+
+	return hfield;
+}
+
+static inline void _header_field_ref(struct _header_field *hfield)
+{
+	hfield->refcount++;
+}
+
+static inline void _header_field_unref(struct _header_field *hfield)
+{
+	i_assert( hfield->refcount > 0 );
+	if ( --hfield->refcount != 0 )
+		return;
+
+	if ( hfield->header != NULL )
+		_header_unref(hfield->header);
+
+	if ( hfield->data != NULL )
+		i_free(hfield->data);
+	if ( hfield->utf8_value != NULL )
+		i_free(hfield->utf8_value);
+	i_free(hfield);
+}
+
+/*
+ * Edit mail object
+ */
+
+struct edit_mail {
+	struct mail_private mail;
+	struct mail_private *wrapped;
+
+	struct edit_mail *parent;
+	unsigned int refcount;
+
+	struct istream *wrapped_stream;
+	struct istream *stream;
+
+	struct _header_index *headers_head, *headers_tail;
+	struct _header_field_index *header_fields_head, *header_fields_tail;
+	struct message_size hdr_size, body_size;
+
+	struct message_size wrapped_hdr_size, wrapped_body_size;
+
+	struct _header_field_index *header_fields_appended;
+	struct message_size appended_hdr_size;
+
+	bool modified:1;
+	bool snapshot_modified:1;
+	bool crlf:1;
+	bool eoh_crlf:1;
+	bool headers_parsed:1;
+	bool destroying_stream:1;
+};
+
+struct edit_mail *edit_mail_wrap(struct mail *mail)
+{
+	struct mail_private *mailp = (struct mail_private *) mail;
+	struct edit_mail *edmail;
+	struct mail_user *raw_mail_user;
+	struct mailbox *raw_box = NULL;
+	struct mailbox_transaction_context *raw_trans;
+	struct message_size hdr_size, body_size;
+	struct istream *wrapped_stream;
+	uoff_t size_diff;
+	pool_t pool;
+
+	if ( mail_get_stream(mail, &hdr_size, &body_size, &wrapped_stream) < 0 ) {
+		return NULL;
+	}
+
+	/* Create dummy raw mailbox for our wrapper */
+
+	raw_mail_user = edit_mail_raw_storage_get(mail->box->storage->user);
+
+	if ( raw_mailbox_alloc_stream(raw_mail_user, wrapped_stream, (time_t)-1,
+		"editor@example.com", &raw_box) < 0 ) {
+		i_error("edit-mail: failed to open raw box: %s",
+				mailbox_get_last_error(raw_box, NULL));
+		mailbox_free(&raw_box);
+		edit_mail_raw_storage_drop();
+		return NULL;
+	}
+
+	raw_trans = mailbox_transaction_begin(raw_box, 0, __func__);
+
+	/* Create the wrapper mail */
+
+	pool = pool_alloconly_create("edit_mail", 1024);
+	edmail = p_new(pool, struct edit_mail, 1);
+	edmail->refcount = 1;
+	edmail->mail.pool = pool;
+
+	edmail->wrapped = mailp;
+	edmail->wrapped_hdr_size = hdr_size;
+	edmail->wrapped_body_size = body_size;
+
+	edmail->wrapped_stream = wrapped_stream;
+	i_stream_ref(edmail->wrapped_stream);
+
+	/* Determine whether we should use CRLF or LF for the physical message */
+	size_diff = (hdr_size.virtual_size + body_size.virtual_size) -
+		(hdr_size.physical_size + body_size.physical_size);
+	if ( size_diff == 0 || size_diff <= (hdr_size.lines + body_size.lines)/2 )
+		edmail->crlf = edmail->eoh_crlf = TRUE;
+
+	array_create(&edmail->mail.module_contexts, pool, sizeof(void *), 5);
+
+	edmail->mail.v = edit_mail_vfuncs;
+	edmail->mail.mail.seq = 1;
+	edmail->mail.mail.box = raw_box;
+	edmail->mail.mail.transaction = raw_trans;
+	edmail->mail.wanted_fields = mailp->wanted_fields;
+	edmail->mail.wanted_headers = mailp->wanted_headers;
+
+	return edmail;
+}
+
+struct edit_mail *edit_mail_snapshot(struct edit_mail *edmail)
+{
+	struct _header_field_index *field_idx, *field_idx_new;
+	struct edit_mail *edmail_new;
+	pool_t pool;
+
+	if ( !edmail->snapshot_modified ) {
+		return edmail;
+	}
+
+	pool = pool_alloconly_create("edit_mail", 1024);
+	edmail_new = p_new(pool, struct edit_mail, 1);
+	edmail_new->refcount = 1;
+	edmail_new->mail.pool = pool;
+
+	edmail_new->wrapped = edmail->wrapped;
+	edmail_new->wrapped_hdr_size = edmail->wrapped_hdr_size;
+	edmail_new->wrapped_body_size = edmail->wrapped_body_size;
+	edmail_new->hdr_size = edmail->hdr_size;
+	edmail_new->body_size = edmail->body_size;
+	edmail_new->appended_hdr_size = edmail->appended_hdr_size;
+
+	edmail_new->wrapped_stream = edmail->wrapped_stream;
+	i_stream_ref(edmail_new->wrapped_stream);
+
+	edmail_new->crlf = edmail->crlf;
+	edmail_new->eoh_crlf = edmail->eoh_crlf;
+
+	array_create(&edmail_new->mail.module_contexts, pool, sizeof(void *), 5);
+
+	edmail_new->mail.v = edit_mail_vfuncs;
+	edmail_new->mail.mail.seq = 1;
+	edmail_new->mail.mail.box = edmail->mail.mail.box;
+	edmail_new->mail.mail.transaction = edmail->mail.mail.transaction;
+	edmail_new->mail.wanted_fields = 	edmail->mail.wanted_fields;
+	edmail_new->mail.wanted_headers = edmail->mail.wanted_headers;
+
+	edmail_new->stream = NULL;
+
+	if ( edmail->modified ) {
+		field_idx = edmail->header_fields_head;
+		while ( field_idx != NULL ) {
+			struct _header_field_index *next = field_idx->next;
+
+			field_idx_new = i_new(struct _header_field_index, 1);
+
+			field_idx_new->header =
+				edit_mail_header_clone(edmail_new, field_idx->header->header);
+
+			field_idx_new->field = field_idx->field;
+			_header_field_ref(field_idx_new->field);
+
+			DLLIST2_APPEND
+				(&edmail_new->header_fields_head, &edmail_new->header_fields_tail,
+					field_idx_new);
+
+			field_idx_new->header->count++;
+			if ( field_idx->header->first == field_idx )
+				field_idx_new->header->first = field_idx_new;
+			if ( field_idx->header->last == field_idx )
+				field_idx_new->header->last = field_idx_new;
+
+			if ( field_idx == edmail->header_fields_appended )
+				edmail_new->header_fields_appended = field_idx_new;
+
+			field_idx = next;
+		}
+
+		edmail_new->modified = TRUE;
+	}
+
+	edmail_new->headers_parsed = edmail->headers_parsed;
+
+	edmail_new->parent = edmail;
+	//edmail->refcount++;
+
+	return edmail_new;
+}
+
+void edit_mail_reset(struct edit_mail *edmail)
+{
+	struct _header_index *header_idx;
+	struct _header_field_index *field_idx;
+
+	i_stream_unref(&edmail->stream);
+
+	field_idx = edmail->header_fields_head;
+	while ( field_idx != NULL ) {
+		struct _header_field_index *next = field_idx->next;
+
+		_header_field_unref(field_idx->field);
+		i_free(field_idx);
+
+		field_idx = next;
+	}
+
+	header_idx = edmail->headers_head;
+	while ( header_idx != NULL ) {
+		struct _header_index *next = header_idx->next;
+
+		_header_unref(header_idx->header);
+		i_free(header_idx);
+
+		header_idx = next;
+	}
+
+	edmail->modified = FALSE;
+}
+
+void edit_mail_unwrap(struct edit_mail **edmail)
+{
+	struct edit_mail *parent;
+
+	i_assert( (*edmail)->refcount > 0 );
+	if ( --(*edmail)->refcount != 0 )
+		return;
+
+	edit_mail_reset(*edmail);
+	i_stream_unref(&(*edmail)->wrapped_stream);
+
+	parent = (*edmail)->parent;
+
+	if ( parent == NULL ) {
+		mailbox_transaction_rollback(&(*edmail)->mail.mail.transaction);
+		mailbox_free(&(*edmail)->mail.mail.box);
+		edit_mail_raw_storage_drop();
+	}
+
+	pool_unref(&(*edmail)->mail.pool);
+	*edmail = NULL;
+
+	if ( parent != NULL )
+		edit_mail_unwrap(&parent);
+}
+
+struct mail *edit_mail_get_mail(struct edit_mail *edmail)
+{
+	/* Return wrapped mail when nothing is modified yet */
+	if ( !edmail->modified )
+		return &edmail->wrapped->mail;
+
+	return &edmail->mail.mail;
+}
+
+/*
+ * Editing
+ */
+
+static inline void edit_mail_modify(struct edit_mail *edmail)
+{
+	edmail->mail.mail.seq++;
+	edmail->modified = TRUE;
+	edmail->snapshot_modified = TRUE;
+}
+
+/* Header modification */
+
+static inline char *_header_value_unfold
+(const char *value)
+{
+	string_t *out;
+	unsigned int i;
+
+	for ( i = 0; value[i] != '\0'; i++ ) {
+		if (value[i] == '\r' || value[i] == '\n')
+			break;
+	}
+	if ( value[i] == '\0' ) {
+		return i_strdup(value);
+	}
+
+	out = t_str_new(i + strlen(value+i) + 10);
+	str_append_data(out, value, i);
+	for ( ; value[i] != '\0'; i++ ) {
+		if (value[i] == '\n') {
+			i++;
+			if (value[i] == '\0')
+				break;
+
+			switch ( value[i] ) {
+			case ' ': 
+				str_append_c(out, ' ');
+				break;
+			case '\t':
+			default:
+				str_append_c(out, '\t');
+			}
+		} else {
+			if (value[i] != '\r')
+				str_append_c(out, value[i]);
+		}
+	}
+
+	return i_strndup(str_c(out), str_len(out));
+}
+
+static struct _header_index *edit_mail_header_find
+(struct edit_mail *edmail, const char *field_name)
+{
+	struct _header_index *header_idx;
+
+	header_idx = edmail->headers_head;
+	while ( header_idx != NULL ) {
+		if ( strcasecmp(header_idx->header->name, field_name) == 0 )
+			return header_idx;
+
+		header_idx = header_idx->next;
+	}
+
+	return NULL;
+}
+
+static struct _header_index *edit_mail_header_create
+(struct edit_mail *edmail, const char *field_name)
+{
+	struct _header_index *header_idx;
+
+	if ( (header_idx=edit_mail_header_find(edmail, field_name)) == NULL ) {
+		header_idx = i_new(struct _header_index, 1);
+		header_idx->header = _header_create(field_name);
+
+		DLLIST2_APPEND(&edmail->headers_head, &edmail->headers_tail, header_idx);
+	}
+
+	return header_idx;
+}
+
+static struct _header_index *edit_mail_header_clone
+(struct edit_mail *edmail, struct _header *header)
+{
+	struct _header_index *header_idx;
+
+	header_idx = edmail->headers_head;
+	while ( header_idx != NULL ) {
+		if ( header_idx->header == header )
+			return header_idx;
+
+		header_idx = header_idx->next;
+	}
+
+	header_idx = i_new(struct _header_index, 1);
+	header_idx->header = header;
+	_header_ref(header);
+	DLLIST2_APPEND(&edmail->headers_head, &edmail->headers_tail, header_idx);
+
+	return header_idx;
+}
+
+static struct _header_field_index *
+edit_mail_header_field_create
+(struct edit_mail *edmail, const char *field_name, const char *value)
+{
+	struct _header_index *header_idx;
+	struct _header *header;
+	struct _header_field_index *field_idx;
+	struct _header_field *field;
+	unsigned int lines;
+
+	/* Get/create header index item */
+	header_idx = edit_mail_header_create(edmail, field_name);
+	header = header_idx->header;
+
+	/* Create new field index item */
+	field_idx = i_new(struct _header_field_index, 1);
+	field_idx->header = header_idx;
+	field_idx->field = field = _header_field_create(header);
+
+	/* Create header field data (folded if necessary) */
+	T_BEGIN {
+		string_t *enc_value, *data;
+
+		enc_value = t_str_new(strlen(field_name) + strlen(value) + 64);
+		data = t_str_new(strlen(field_name) + strlen(value) + 128);
+
+		message_header_encode(value, enc_value);
+
+		lines = rfc2822_header_append
+			(data, field_name, str_c(enc_value), edmail->crlf, &field->body_offset);
+
+		/* Copy to new field */
+		field->data = i_strndup(str_data(data), str_len(data));
+		field->size = str_len(data);
+		field->virtual_size = ( edmail->crlf ? field->size : field->size + lines );
+		field->lines = lines;
+	} T_END;
+
+	/* Record original (utf8) value */
+	field->utf8_value = _header_value_unfold(value);
+
+	return field_idx;
+}
+
+static void edit_mail_header_field_delete
+(struct edit_mail *edmail, struct _header_field_index *field_idx,
+	bool update_index)
+{
+	struct _header_index *header_idx = field_idx->header;
+	struct _header_field *field = field_idx->field;
+
+	i_assert( header_idx != NULL );
+
+	edmail->hdr_size.physical_size -= field->size;
+	edmail->hdr_size.virtual_size -= field->virtual_size;
+	edmail->hdr_size.lines -= field->lines;
+
+	header_idx->count--;
+	if ( update_index ) {
+		if ( header_idx->count == 0 ) {
+			DLLIST2_REMOVE(&edmail->headers_head, &edmail->headers_tail, header_idx);
+			_header_unref(header_idx->header);
+			i_free(header_idx);
+		} else if ( header_idx->first == field_idx ) {
+			struct _header_field_index *hfield = header_idx->first->next;
+
+			while ( hfield != NULL && hfield->header != header_idx ) {
+				hfield = hfield->next;
+			}
+
+			i_assert( hfield != NULL );
+			header_idx->first = hfield;
+		} else if ( header_idx->last == field_idx ) {
+			struct _header_field_index *hfield = header_idx->last->prev;
+
+			while ( hfield != NULL && hfield->header != header_idx ) {
+				hfield = hfield->prev;
+			}
+
+			i_assert( hfield != NULL );
+			header_idx->last = hfield;
+		}
+	}
+
+	DLLIST2_REMOVE
+		(&edmail->header_fields_head, &edmail->header_fields_tail, field_idx);
+	_header_field_unref(field_idx->field);
+	i_free(field_idx);
+}
+
+static struct _header_field_index *
+edit_mail_header_field_replace
+(struct edit_mail *edmail, struct _header_field_index *field_idx,
+	const char *newname, const char *newvalue, bool update_index)
+{
+	struct _header_field_index *field_idx_new;
+	struct _header_index *header_idx = field_idx->header, *header_idx_new;
+	struct _header_field *field = field_idx->field, *field_new;
+
+	i_assert( header_idx != NULL );
+	i_assert( newname != NULL || newvalue != NULL );
+
+	if ( newname == NULL )
+		newname = header_idx->header->name;
+	if ( newvalue == NULL )
+		newvalue = field_idx->field->utf8_value;
+	field_idx_new = edit_mail_header_field_create
+		(edmail, newname, newvalue);
+	field_new = field_idx_new->field;
+	header_idx_new = field_idx_new->header;
+
+	edmail->hdr_size.physical_size -= field->size;
+	edmail->hdr_size.virtual_size -= field->virtual_size;
+	edmail->hdr_size.lines -= field->lines;
+
+	edmail->hdr_size.physical_size += field_new->size;
+	edmail->hdr_size.virtual_size += field_new->virtual_size;
+	edmail->hdr_size.lines += field_new->lines;
+
+	/* Replace header field index */
+	field_idx_new->prev = field_idx->prev;
+	field_idx_new->next = field_idx->next;
+	if ( field_idx->prev != NULL )
+		field_idx->prev->next = field_idx_new;
+	if ( field_idx->next != NULL )
+		field_idx->next->prev = field_idx_new;
+	if (edmail->header_fields_head == field_idx)
+		edmail->header_fields_head = field_idx_new;
+	if (edmail->header_fields_tail == field_idx)
+		edmail->header_fields_tail = field_idx_new;
+
+	if ( header_idx_new == header_idx ) {
+		if (header_idx->first == field_idx)
+			header_idx->first = field_idx_new;
+		if (header_idx->last == field_idx)
+			header_idx->last = field_idx_new;
+	} else {
+		header_idx->count--;
+		header_idx_new->count++;
+
+		if ( update_index ) {
+			if ( header_idx->count == 0 ) {
+				DLLIST2_REMOVE(&edmail->headers_head, &edmail->headers_tail, header_idx);
+				_header_unref(header_idx->header);
+				i_free(header_idx);
+			} else if ( header_idx->first == field_idx ) {
+				struct _header_field_index *hfield = header_idx->first->next;
+
+				while ( hfield != NULL && hfield->header != header_idx ) {
+					hfield = hfield->next;
+				}
+
+				i_assert( hfield != NULL );
+				header_idx->first = hfield;
+			} else if ( header_idx->last == field_idx ) {
+				struct _header_field_index *hfield = header_idx->last->prev;
+
+				while ( hfield != NULL && hfield->header != header_idx ) {
+					hfield = hfield->prev;
+				}
+
+				i_assert( hfield != NULL );
+				header_idx->last = hfield;
+			}
+			if ( header_idx_new->count > 0 ) {
+				struct _header_field_index *hfield;
+
+				hfield = edmail->header_fields_head;
+				while ( hfield != NULL && hfield->header != header_idx_new ) {
+					hfield = hfield->next;
+				}
+
+				i_assert( hfield != NULL );
+				header_idx_new->first = hfield;
+
+				hfield = edmail->header_fields_tail;
+				while ( hfield != NULL && hfield->header != header_idx_new ) {
+					hfield = hfield->prev;
+				}
+
+				i_assert( hfield != NULL );
+				header_idx_new->last = hfield;
+			}
+		}
+	}
+
+	_header_field_unref(field_idx->field);
+	i_free(field_idx);
+	return field_idx_new;
+}
+
+static inline char *_header_decode
+(const unsigned char *hdr_data, size_t hdr_data_len)
+{
+	string_t *str = t_str_new(512);
+
+	/* hdr_data is already unfolded */
+
+	/* Decode MIME encoded-words. */
+	message_header_decode_utf8
+		((const unsigned char *)hdr_data, hdr_data_len, str, NULL);
+	return i_strdup(str_c(str));
+}
+
+static int edit_mail_headers_parse
+(struct edit_mail *edmail)
+{
+	struct message_header_parser_ctx *hparser;
+	enum message_header_parser_flags hparser_flags =
+		MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP |
+		MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE;
+	struct message_header_line *hdr;
+	struct _header_index *header_idx;
+	struct _header_field_index *head = NULL, *tail = NULL, *current;
+	string_t *hdr_data;
+	uoff_t offset = 0, body_offset = 0, vsize_diff = 0;
+	unsigned int lines = 0;
+	int ret;
+
+	if ( edmail->headers_parsed ) return 1;
+
+	i_stream_seek(edmail->wrapped_stream, 0);
+	hparser = message_parse_header_init
+		(edmail->wrapped_stream, NULL, hparser_flags);
+
+	T_BEGIN {
+		hdr_data = t_str_new(1024);
+		while ( (ret=message_parse_header_next(hparser, &hdr)) > 0 ) {
+			struct _header_field_index *field_idx_new;
+			struct _header_field *field;
+
+			if ( hdr->eoh ) {
+				/* Record whether header ends in CRLF or LF */
+				edmail->eoh_crlf = hdr->crlf_newline;
+			}
+
+			if ( hdr == NULL || hdr->eoh ) break;
+
+			/* We deny the existence of any `Content-Length:' header. This header is
+			 * non-standard and it can wreak havok when the message is modified.
+			 */
+			if ( strcasecmp(hdr->name, "Content-Length" ) == 0 )
+				continue;
+
+			if ( hdr->continued ) {
+				/* Continued line of folded header */
+				buffer_append(hdr_data, hdr->value, hdr->value_len);
+			} else {
+				/* First line of header */
+				offset = hdr->name_offset;
+				body_offset = hdr->name_len + hdr->middle_len;
+				str_truncate(hdr_data, 0);
+				buffer_append(hdr_data, hdr->name, hdr->name_len);
+				buffer_append(hdr_data, hdr->middle, hdr->middle_len);
+				buffer_append(hdr_data, hdr->value, hdr->value_len);
+				lines = 0;
+				vsize_diff = 0;
+			}
+
+			if ( !hdr->no_newline ) {
+				lines++;
+
+				if ( hdr->crlf_newline ) {
+					buffer_append(hdr_data, "\r\n", 2);
+				} else {
+					buffer_append(hdr_data, "\n", 1);
+					vsize_diff++;
+				}
+			}
+
+			if ( hdr->continues ) {
+				hdr->use_full_value = TRUE;
+				continue;
+			}
+
+			/* Create new header field index entry */
+
+			field_idx_new = i_new(struct _header_field_index, 1);
+
+			header_idx = edit_mail_header_create(edmail, hdr->name);
+			header_idx->count++;
+			field_idx_new->header = header_idx;
+			field_idx_new->field = field = _header_field_create(header_idx->header);
+
+			i_assert( body_offset > 0 );
+			field->body_offset = body_offset;
+
+			field->utf8_value = _header_decode(hdr->full_value, hdr->full_value_len);
+
+			field->size = str_len(hdr_data);
+			field->virtual_size = field->size + vsize_diff;
+			field->data = i_strndup(str_data(hdr_data), field->size);
+			field->offset = offset;
+			field->lines = lines;
+
+			DLLIST2_APPEND(&head, &tail, field_idx_new);
+
+			edmail->hdr_size.physical_size += field->size;
+			edmail->hdr_size.virtual_size += field->virtual_size;
+			edmail->hdr_size.lines += lines;
+		}
+	} T_END;
+
+	message_parse_header_deinit(&hparser);
+
+	/* blocking i/o required */
+	i_assert( ret != 0 );
+
+	if ( ret < 0 && edmail->wrapped_stream->stream_errno != 0 ) {
+		/* Error; clean up */
+		i_error("read(%s) failed: %s",
+			i_stream_get_name(edmail->wrapped_stream),
+			i_stream_get_error(edmail->wrapped_stream));
+		current = head;
+		while ( current != NULL ) {
+			struct _header_field_index *next = current->next;
+
+			_header_field_unref(current->field);
+			i_free(current);
+
+			current = next;
+		}
+
+		return ret;
+	}
+
+	/* Insert header field index items in main list */
+	if ( head != NULL && tail != NULL ) {
+		if ( edmail->header_fields_appended != NULL ) {
+			if ( edmail->header_fields_head != edmail->header_fields_appended ) {
+				edmail->header_fields_appended->prev->next = head;
+				head->prev = edmail->header_fields_appended->prev;
+			} else {
+				edmail->header_fields_head = head;
+			}
+
+			tail->next = edmail->header_fields_appended;
+			edmail->header_fields_appended->prev = tail;
+		} else if ( edmail->header_fields_tail != NULL ) {
+			edmail->header_fields_tail->next = head;
+			head->prev = edmail->header_fields_tail;
+			edmail->header_fields_tail = tail;
+		} else {
+			edmail->header_fields_head = head;
+			edmail->header_fields_tail = tail;
+		}
+	}
+
+	/* Rebuild header index */
+	current = edmail->header_fields_head;
+	while ( current != NULL ) {
+		if ( current->header->first == NULL )
+			current->header->first = current;
+		current->header->last = current;
+
+		current = current->next;
+	}
+
+	/* Clear appended headers */
+	edmail->header_fields_appended = NULL;
+	edmail->appended_hdr_size.physical_size = 0;
+	edmail->appended_hdr_size.virtual_size = 0;
+	edmail->appended_hdr_size.lines = 0;
+
+	/* Do not parse headers again */
+	edmail->headers_parsed = TRUE;
+
+	return 1;
+}
+
+void edit_mail_header_add
+(struct edit_mail *edmail, const char *field_name, const char *value,
+	bool last)
+{
+	struct _header_index *header_idx;
+	struct _header_field_index *field_idx;
+	struct _header_field *field;
+
+	edit_mail_modify(edmail);
+
+	field_idx = edit_mail_header_field_create(edmail, field_name, value);
+	header_idx = field_idx->header;
+	field = field_idx->field;
+
+	/* Add it to the header field index */
+	if ( last ) {
+		DLLIST2_APPEND
+			(&edmail->header_fields_head, &edmail->header_fields_tail, field_idx);
+
+		header_idx->last = field_idx;
+		if ( header_idx->first == NULL )
+			header_idx->first = field_idx;
+
+		if ( !edmail->headers_parsed )  {
+			if ( edmail->header_fields_appended == NULL ) {
+				/* Record beginning of appended headers */
+				edmail->header_fields_appended = field_idx;
+			}
+
+			edmail->appended_hdr_size.physical_size += field->size;
+			edmail->appended_hdr_size.virtual_size += field->virtual_size;
+			edmail->appended_hdr_size.lines += field->lines;
+		}
+	} else {
+		DLLIST2_PREPEND
+			(&edmail->header_fields_head, &edmail->header_fields_tail, field_idx);
+
+		header_idx->first = field_idx;
+		if ( header_idx->last == NULL )
+			header_idx->last = field_idx;
+	}
+
+	header_idx->count++;
+
+	edmail->hdr_size.physical_size += field->size;
+	edmail->hdr_size.virtual_size += field->virtual_size;
+	edmail->hdr_size.lines += field->lines;
+}
+
+int edit_mail_header_delete
+(struct edit_mail *edmail, const char *field_name, int index)
+{
+	struct _header_index *header_idx;
+	struct _header_field_index *field_idx;
+	int pos = 0;
+	int ret = 0;
+
+	/* Make sure headers are parsed */
+	if ( edit_mail_headers_parse(edmail) <= 0 )
+		return -1;
+
+	/* Find the header entry */
+	if ( (header_idx=edit_mail_header_find(edmail, field_name)) == NULL ) {
+		/* Not found */
+		return 0;
+	}
+
+	/* Signal modification */
+	edit_mail_modify(edmail);
+
+	/* Iterate through all header fields and remove those that match */
+	field_idx = ( index >= 0 ? header_idx->first : header_idx->last );
+	while ( field_idx != NULL ) {
+		struct _header_field_index *next =
+			( index >= 0 ? field_idx->next : field_idx->prev );
+
+		if ( field_idx->field->header == header_idx->header ) {
+			bool final;
+
+			if ( index >= 0 ) {
+				pos++;
+				final = ( header_idx->last == field_idx );
+			} else {
+				pos--;
+				final = ( header_idx->first == field_idx );
+			}
+
+			if ( index == 0 || index == pos ) {
+				if ( header_idx->first == field_idx ) header_idx->first = NULL;
+				if ( header_idx->last == field_idx ) header_idx->last = NULL;
+				edit_mail_header_field_delete(edmail, field_idx, FALSE);
+				ret++;
+			}
+
+			if ( final || (index != 0 && index == pos) )
+				break;
+		}
+
+		field_idx = next;
+	}
+
+	if ( index == 0 || header_idx->count == 0 ) {
+		DLLIST2_REMOVE(&edmail->headers_head, &edmail->headers_tail, header_idx);
+		_header_unref(header_idx->header);
+		i_free(header_idx);
+	} else if ( header_idx->first == NULL || header_idx->last == NULL ) {
+		struct _header_field_index *current = edmail->header_fields_head;
+
+		while ( current != NULL ) {
+			if ( current->header == header_idx ) {
+				if ( header_idx->first == NULL )
+					header_idx->first = current;
+				header_idx->last = current;
+			}
+			current = current->next;
+		}
+	}
+
+	return ret;
+}
+
+int edit_mail_header_replace
+(struct edit_mail *edmail, const char *field_name, int index,
+	const char *newname, const char *newvalue)
+{
+	struct _header_index *header_idx, *header_idx_new;
+	struct _header_field_index *field_idx, *field_idx_new;
+	int pos = 0;
+	int ret = 0;
+
+	/* Make sure headers are parsed */
+	if ( edit_mail_headers_parse(edmail) <= 0 )
+		return -1;
+
+	/* Find the header entry */
+	if ( (header_idx=edit_mail_header_find(edmail, field_name)) == NULL ) {
+		/* Not found */
+		return 0;
+	}
+
+	/* Signal modification */
+	edit_mail_modify(edmail);
+
+	/* Iterate through all header fields and replace those that match */
+	field_idx = ( index >= 0 ? header_idx->first : header_idx->last );
+	field_idx_new = NULL;
+	while ( field_idx != NULL ) {
+		struct _header_field_index *next =
+			( index >= 0 ? field_idx->next : field_idx->prev );
+
+		if ( field_idx->field->header == header_idx->header ) {
+			bool final;
+
+			if ( index >= 0 ) {
+				pos++;
+				final = ( header_idx->last == field_idx );
+			} else {
+				pos--;
+				final = ( header_idx->first == field_idx );
+			}
+
+			if ( index == 0 || index == pos ) {
+				if ( header_idx->first == field_idx ) header_idx->first = NULL;
+				if ( header_idx->last == field_idx ) header_idx->last = NULL;
+				field_idx_new = edit_mail_header_field_replace
+					(edmail, field_idx, newname, newvalue, FALSE);
+				ret++;
+			}
+
+			if ( final || (index != 0 && index == pos) )
+				break;
+		}
+
+		field_idx = next;
+	}
+
+	/* Update old header index */
+	if ( header_idx->count == 0 ) {
+		DLLIST2_REMOVE(&edmail->headers_head, &edmail->headers_tail, header_idx);
+		_header_unref(header_idx->header);
+		i_free(header_idx);
+	} else if ( header_idx->first == NULL || header_idx->last == NULL ) {
+		struct _header_field_index *current = edmail->header_fields_head;
+
+		while ( current != NULL ) {
+			if ( current->header == header_idx ) {
+				if ( header_idx->first == NULL )
+					header_idx->first = current;
+				header_idx->last = current;
+			}
+			current = current->next;
+		}
+	}
+
+	/* Update new header index */	
+	if ( field_idx_new != NULL ) {
+		struct _header_field_index *current = edmail->header_fields_head;
+	
+		header_idx_new = field_idx_new->header;	
+		while ( current != NULL ) {
+			if ( current->header == header_idx_new ) {
+				if ( header_idx_new->first == NULL )
+					header_idx_new->first = current;
+				header_idx_new->last = current;
+			}
+			current = current->next;
+		}
+	}
+
+	return ret;
+}
+
+struct edit_mail_header_iter
+{
+	struct edit_mail *mail;
+	struct _header_index *header;
+	struct _header_field_index *current;
+
+	bool reverse:1;
+};
+
+int edit_mail_headers_iterate_init
+(struct edit_mail *edmail, const char *field_name, bool reverse,
+	struct edit_mail_header_iter **edhiter_r)
+{
+	struct edit_mail_header_iter *edhiter;
+	struct _header_index *header_idx = NULL;
+	struct _header_field_index *current = NULL;
+
+	/* Make sure headers are parsed */
+	if ( edit_mail_headers_parse(edmail) <= 0 ) {
+		/* Failure */
+		return -1;
+	}
+
+	header_idx = edit_mail_header_find(edmail, field_name);
+
+	if ( field_name != NULL && header_idx == NULL ) {
+		current = NULL;
+	} else if ( !reverse ) {
+		current =
+			( header_idx != NULL ? header_idx->first : edmail->header_fields_head );
+	} else {
+		current =
+			( header_idx != NULL ? header_idx->last : edmail->header_fields_tail );
+		if ( current->header == NULL )
+			current = current->prev;
+	}
+
+	if ( current ==  NULL )
+		return 0; 
+
+ 	edhiter = i_new(struct edit_mail_header_iter, 1);
+	edhiter->mail = edmail;
+	edhiter->header = header_idx;
+	edhiter->reverse = reverse;
+	edhiter->current = current;
+
+	*edhiter_r = edhiter;
+	return 1;
+}
+
+void edit_mail_headers_iterate_deinit
+(struct edit_mail_header_iter **edhiter)
+{
+	i_free(*edhiter);
+	*edhiter = NULL;
+}
+
+void edit_mail_headers_iterate_get
+(struct edit_mail_header_iter *edhiter, const char **value_r)
+{
+	const char *raw;
+	int i;
+
+	i_assert( edhiter->current != NULL && edhiter->current->header != NULL);
+
+	raw = edhiter->current->field->utf8_value;
+	for ( i = strlen(raw)-1; i >= 0; i-- ) {
+		if ( raw[i] != ' ' && raw[i] != '\t' ) break;
+	}
+
+	*value_r = t_strndup(raw, i+1);
+}
+
+bool edit_mail_headers_iterate_next
+(struct edit_mail_header_iter *edhiter)
+{
+	if ( edhiter->current == NULL )
+		return FALSE;
+
+	do {
+		edhiter->current = 
+			( !edhiter->reverse ? edhiter->current->next : edhiter->current->prev );
+	} while ( edhiter->current != NULL && edhiter->current->header != NULL &&
+		edhiter->header != NULL && edhiter->current->header != edhiter->header );
+
+	return ( edhiter->current != NULL && edhiter->current->header != NULL);
+}
+
+bool edit_mail_headers_iterate_remove
+(struct edit_mail_header_iter *edhiter)
+{
+	struct _header_field_index *field_idx;
+	bool next;
+
+	i_assert( edhiter->current != NULL && edhiter->current->header != NULL);
+
+	edit_mail_modify(edhiter->mail);
+
+	field_idx = edhiter->current;
+	next = edit_mail_headers_iterate_next(edhiter);
+	edit_mail_header_field_delete(edhiter->mail, field_idx, TRUE);
+
+	return next;
+}
+
+bool edit_mail_headers_iterate_replace
+(struct edit_mail_header_iter *edhiter,
+	const char *newname, const char *newvalue)
+{
+	struct _header_field_index *field_idx;
+	bool next;
+
+	i_assert( edhiter->current != NULL && edhiter->current->header != NULL);
+
+	edit_mail_modify(edhiter->mail);
+
+	field_idx = edhiter->current;
+	next = edit_mail_headers_iterate_next(edhiter);
+	edit_mail_header_field_replace
+		(edhiter->mail, field_idx, newname, newvalue, TRUE);
+
+	return next;
+}
+
+/* Body modification */
+
+// FIXME: implement
+
+/*
+ * Mail API
+ */
+
+static void edit_mail_close(struct mail *mail)
+{
+	struct edit_mail *edmail = (struct edit_mail *)mail;
+
+	edmail->wrapped->v.close(&edmail->wrapped->mail);
+}
+
+static void edit_mail_free(struct mail *mail)
+{
+	struct edit_mail *edmail = (struct edit_mail *)mail;
+
+	edmail->wrapped->v.free(&edmail->wrapped->mail);
+
+	edit_mail_unwrap(&edmail);
+}
+
+static void edit_mail_set_seq
+(struct mail *mail ATTR_UNUSED, uint32_t seq ATTR_UNUSED,
+	bool saving ATTR_UNUSED)
+{
+	i_panic("edit_mail_set_seq() not implemented");
+}
+
+static bool ATTR_NORETURN edit_mail_set_uid
+(struct mail *mail ATTR_UNUSED, uint32_t uid ATTR_UNUSED)
+{
+	i_panic("edit_mail_set_uid() not implemented");
+}
+
+static void edit_mail_set_uid_cache_updates(struct mail *mail, bool set)
+{
+	struct edit_mail *edmail = (struct edit_mail *)mail;
+
+	edmail->wrapped->v.set_uid_cache_updates(&edmail->wrapped->mail, set);
+}
+
+static void edit_mail_add_temp_wanted_fields
+(struct mail *mail ATTR_UNUSED, enum mail_fetch_field fields ATTR_UNUSED,
+	struct mailbox_header_lookup_ctx *headers ATTR_UNUSED)
+{
+  /* Nothing */
+}
+
+static enum mail_flags edit_mail_get_flags(struct mail *mail)
+{
+	struct edit_mail *edmail = (struct edit_mail *)mail;
+
+	return edmail->wrapped->v.get_flags(&edmail->wrapped->mail);
+}
+
+static const char *const *edit_mail_get_keywords(struct mail *mail)
+{
+	struct edit_mail *edmail = (struct edit_mail *)mail;
+
+	return edmail->wrapped->v.get_keywords(&edmail->wrapped->mail);
+}
+
+static const ARRAY_TYPE(keyword_indexes) *edit_mail_get_keyword_indexes
+(struct mail *mail)
+{
+	struct edit_mail *edmail = (struct edit_mail *)mail;
+
+	return edmail->wrapped->v.get_keyword_indexes(&edmail->wrapped->mail);
+}
+
+static uint64_t edit_mail_get_modseq(struct mail *mail)
+{
+	struct edit_mail *edmail = (struct edit_mail *)mail;
+
+	return edmail->wrapped->v.get_modseq(&edmail->wrapped->mail);
+}
+
+static uint64_t edit_mail_get_pvt_modseq(struct mail *mail)
+{
+	struct edit_mail *edmail = (struct edit_mail *)mail;
+
+	return edmail->wrapped->v.get_pvt_modseq(&edmail->wrapped->mail);
+}
+
+static int edit_mail_get_parts
+(struct mail *mail, struct message_part **parts_r)
+{
+	struct edit_mail *edmail = (struct edit_mail *)mail;
+
+	return edmail->wrapped->v.get_parts(&edmail->wrapped->mail, parts_r);
+}
+
+static int edit_mail_get_date
+(struct mail *mail, time_t *date_r, int *timezone_r)
+{
+	struct edit_mail *edmail = (struct edit_mail *)mail;
+
+	return edmail->wrapped->v.get_date(&edmail->wrapped->mail, date_r, timezone_r);
+}
+
+static int edit_mail_get_received_date(struct mail *mail, time_t *date_r)
+{
+	struct edit_mail *edmail = (struct edit_mail *)mail;
+
+	return edmail->wrapped->v.get_received_date(&edmail->wrapped->mail, date_r);
+}
+
+static int edit_mail_get_save_date(struct mail *mail, time_t *date_r)
+{
+	struct edit_mail *edmail = (struct edit_mail *)mail;
+
+	return edmail->wrapped->v.get_save_date(&edmail->wrapped->mail, date_r);
+}
+
+static int edit_mail_get_virtual_size(struct mail *mail, uoff_t *size_r)
+{
+	struct edit_mail *edmail = (struct edit_mail *)mail;
+
+	if ( !edmail->headers_parsed ) {
+		*size_r = edmail->wrapped_hdr_size.virtual_size +
+			edmail->wrapped_body_size.virtual_size;
+
+		if ( !edmail->modified )
+			return 0;
+	} else {
+		*size_r = edmail->wrapped_body_size.virtual_size + 2;
+	}
+
+	*size_r += edmail->hdr_size.virtual_size + edmail->body_size.virtual_size;
+	return 0;
+}
+
+static int edit_mail_get_physical_size(struct mail *mail, uoff_t *size_r)
+{
+	struct edit_mail *edmail = (struct edit_mail *)mail;
+
+	*size_r = 0;
+	if ( !edmail->headers_parsed ) {
+		*size_r = edmail->wrapped_hdr_size.physical_size +
+			edmail->wrapped_body_size.physical_size;
+
+		if ( !edmail->modified )
+			return 0;
+	} else {
+		*size_r = edmail->wrapped_body_size.physical_size +
+			( edmail->eoh_crlf ? 2 : 1 );
+	}
+
+	*size_r += edmail->hdr_size.physical_size + edmail->body_size.physical_size;
+	return 0;
+}
+
+static int edit_mail_get_first_header
+(struct mail *mail, const char *field_name, bool decode_to_utf8,
+	const char **value_r)
+{
+	struct edit_mail *edmail = (struct edit_mail *)mail;
+	struct _header_index *header_idx;
+	struct _header_field *field;
+	int ret;
+
+	/* Check whether mail headers were modified at all */
+	if ( !edmail->modified || edmail->headers_head == NULL ) {
+		/* Unmodified */
+		return edmail->wrapped->v.get_first_header
+			(&edmail->wrapped->mail, field_name, decode_to_utf8, value_r);
+	}
+
+	/* Try to find modified header */
+	if ( (header_idx=edit_mail_header_find(edmail, field_name)) == NULL ||
+		header_idx->count == 0 ) {
+
+		if ( !edmail->headers_parsed ) {
+			/* No new header */
+			return edmail->wrapped->v.get_first_header
+				(&edmail->wrapped->mail, field_name, decode_to_utf8, value_r);
+		}
+
+		*value_r = NULL;
+		return 0;
+	}
+
+	/* Get the first occurrence */
+	if ( edmail->header_fields_appended == NULL ) {
+		/* There are no appended headers, so first is found directly */
+		field = header_idx->first->field;
+	} else {
+		struct _header_field_index *field_idx;
+
+		/* Scan prepended headers */
+		field_idx = edmail->header_fields_head;
+		while ( field_idx != NULL ) {
+			if ( field_idx->header == header_idx )
+				break;
+
+			if ( field_idx == edmail->header_fields_appended ) {
+				field_idx = NULL;
+				break;
+			}
+			field_idx = field_idx->next;
+		}
+
+		if ( field_idx == NULL ) {
+			/* Check original message */
+			if ( (ret=edmail->wrapped->v.get_first_header
+				(&edmail->wrapped->mail, field_name, decode_to_utf8, value_r)) != 0 )
+				return ret;
+
+			/* Use first (apparently appended) header */
+			field = header_idx->first->field;
+		} else {
+			field = field_idx->field;
+		}
+	}
+
+	if ( decode_to_utf8 )
+		*value_r = field->utf8_value;
+	else
+		*value_r = (const char *) (field->data + field->body_offset);
+	return 1;
+}
+
+static int edit_mail_get_headers
+(struct mail *mail, const char *field_name, bool decode_to_utf8,
+	const char *const **value_r)
+{
+	struct edit_mail *edmail = (struct edit_mail *)mail;
+	struct _header_index *header_idx;
+	struct _header_field_index *field_idx;
+	const char *const *headers;
+	ARRAY(const char *) header_values;
+
+	if ( !edmail->modified || edmail->headers_head == NULL ) {
+		/* Unmodified */
+		return edmail->wrapped->v.get_headers
+			(&edmail->wrapped->mail, field_name, decode_to_utf8, value_r);
+	}
+
+	if ( (header_idx=edit_mail_header_find(edmail, field_name)) == NULL ||
+		header_idx->count == 0 ) {
+		if ( !edmail->headers_parsed ) {
+			/* No new header */
+			return edmail->wrapped->v.get_headers
+				(&edmail->wrapped->mail, field_name, decode_to_utf8, value_r);
+		}
+
+		p_array_init(&header_values, edmail->mail.pool, 1);
+		(void)array_append_space(&header_values);
+		*value_r = array_idx(&header_values, 0);
+		return 0;
+	}
+
+	/* Merge */
+
+	/* Read original headers too if message headers are not parsed */
+	headers = NULL;
+	if ( !edmail->headers_parsed && edmail->wrapped->v.get_headers
+			(&edmail->wrapped->mail, field_name, decode_to_utf8, &headers) < 0 ) {
+		return -1;
+	}
+
+	/* Fill result array */
+	p_array_init(&header_values, edmail->mail.pool, 32);
+	field_idx = header_idx->first;
+	while ( field_idx != NULL ) {
+
+		/* If current field is the first appended one, we need to add original
+		 * headers first.
+		 */
+		if ( field_idx == edmail->header_fields_appended && headers != NULL ) {
+			while ( *headers != NULL ) {
+				array_append(&header_values, headers, 1);
+
+				headers++;
+			}
+		}
+
+		/* Add modified header to the list */
+		if ( field_idx->field->header == header_idx->header ) {
+			struct _header_field *field = field_idx->field;
+
+			const char *value;
+			if ( decode_to_utf8 )
+				value = field->utf8_value;
+			else
+				value = (const char *)(field->data + field->body_offset);
+
+			array_append(&header_values, &value, 1);
+
+			if ( field_idx == header_idx->last )
+				break;
+		}
+
+		field_idx = field_idx->next;
+	}
+
+	/* Add original headers if necessary */
+	if ( headers != NULL ) {
+		while ( *headers != NULL ) {
+			array_append(&header_values, headers, 1);
+
+			headers++;
+		}
+	}
+
+	(void)array_append_space(&header_values);
+	*value_r = array_idx(&header_values, 0);
+	return 1;
+}
+
+static int ATTR_NORETURN edit_mail_get_header_stream
+(struct mail *mail ATTR_UNUSED,
+	struct mailbox_header_lookup_ctx *headers ATTR_UNUSED,
+	struct istream **stream_r ATTR_UNUSED)
+{
+	// FIXME: implement!
+	i_panic("edit_mail_get_header_stream() not implemented");
+}
+
+static int edit_mail_get_stream
+(struct mail *mail, bool get_body ATTR_UNUSED, struct message_size *hdr_size,
+	struct message_size *body_size, struct istream **stream_r)
+{
+	struct edit_mail *edmail = (struct edit_mail *)mail;
+
+	if ( edmail->stream == NULL ) {
+		edmail->stream = edit_mail_istream_create(edmail);
+	}
+
+	if ( hdr_size != NULL ) {
+		*hdr_size = edmail->wrapped_hdr_size;
+		hdr_size->physical_size += edmail->hdr_size.physical_size;
+		hdr_size->virtual_size += edmail->hdr_size.virtual_size;
+		hdr_size->lines += edmail->hdr_size.lines;
+	}
+
+	if ( body_size != NULL ) {
+		*body_size = edmail->wrapped_body_size;
+	}
+
+	*stream_r = edmail->stream;
+	i_stream_seek(edmail->stream, 0);
+
+	return 0;
+}
+
+static int edit_mail_get_special
+(struct mail *mail, enum mail_fetch_field field, const char **value_r)
+{
+	struct edit_mail *edmail = (struct edit_mail *)mail;
+
+	if ( edmail->modified ) {
+		/* Block certain fields when modified */
+
+		switch (field) {
+		case MAIL_FETCH_GUID:
+			/* This is in essence a new message */
+			*value_r = "";
+			return 0;
+		case MAIL_FETCH_STORAGE_ID:
+			/* Prevent hardlink copying */
+			*value_r = "";
+			return 0;
+		default:
+			break;
+		}
+	}
+
+	return edmail->wrapped->v.get_special(&edmail->wrapped->mail, field, value_r);
+}
+
+static int
+edit_mail_get_backend_mail(struct mail *mail, struct mail **real_mail_r)
+{
+	struct edit_mail *edmail = (struct edit_mail *)mail;
+
+	*real_mail_r = edit_mail_get_mail(edmail);
+	return 0;
+}
+
+static void edit_mail_update_flags
+(struct mail *mail, enum modify_type modify_type, enum mail_flags flags)
+{
+	struct edit_mail *edmail = (struct edit_mail *)mail;
+
+	edmail->wrapped->v.update_flags(&edmail->wrapped->mail, modify_type, flags);
+}
+
+static void edit_mail_update_keywords
+(struct mail *mail, enum modify_type modify_type,
+	struct mail_keywords *keywords)
+{
+	struct edit_mail *edmail = (struct edit_mail *)mail;
+
+	edmail->wrapped->v.update_keywords
+		(&edmail->wrapped->mail, modify_type, keywords);
+}
+
+static void edit_mail_update_modseq(struct mail *mail, uint64_t min_modseq)
+{
+	struct edit_mail *edmail = (struct edit_mail *)mail;
+
+	edmail->wrapped->v.update_modseq(&edmail->wrapped->mail, min_modseq);
+}
+
+static void edit_mail_update_pvt_modseq(struct mail *mail, uint64_t min_pvt_modseq)
+{
+	struct edit_mail *edmail = (struct edit_mail *)mail;
+
+	edmail->wrapped->v.update_pvt_modseq(&edmail->wrapped->mail, min_pvt_modseq);
+}
+
+static void edit_mail_update_pop3_uidl(struct mail *mail, const char *uidl)
+{
+	struct edit_mail *edmail = (struct edit_mail *)mail;
+
+	if ( edmail->wrapped->v.update_pop3_uidl != NULL )
+		edmail->wrapped->v.update_pop3_uidl(&edmail->wrapped->mail, uidl);
+}
+
+static void edit_mail_expunge(struct mail *mail ATTR_UNUSED)
+{
+	/* NOOP */
+}
+
+static void edit_mail_set_cache_corrupted
+(struct mail *mail, enum mail_fetch_field field,
+	const char *reason)
+{
+	struct edit_mail *edmail = (struct edit_mail *)mail;
+
+	edmail->wrapped->v.set_cache_corrupted
+		(&edmail->wrapped->mail, field, reason);
+}
+
+static struct mail_vfuncs edit_mail_vfuncs = {
+	edit_mail_close,
+	edit_mail_free,
+	edit_mail_set_seq,
+	edit_mail_set_uid,
+	edit_mail_set_uid_cache_updates,
+	NULL,
+	NULL,
+	edit_mail_add_temp_wanted_fields,
+	edit_mail_get_flags,
+	edit_mail_get_keywords,
+	edit_mail_get_keyword_indexes,
+	edit_mail_get_modseq,
+	edit_mail_get_pvt_modseq,
+	edit_mail_get_parts,
+	edit_mail_get_date,
+	edit_mail_get_received_date,
+	edit_mail_get_save_date,
+	edit_mail_get_virtual_size,
+	edit_mail_get_physical_size,
+	edit_mail_get_first_header,
+	edit_mail_get_headers,
+	edit_mail_get_header_stream,
+	edit_mail_get_stream,
+	index_mail_get_binary_stream,
+	edit_mail_get_special,
+	edit_mail_get_backend_mail,
+	edit_mail_update_flags,
+	edit_mail_update_keywords,
+	edit_mail_update_modseq,
+	edit_mail_update_pvt_modseq,
+	edit_mail_update_pop3_uidl,
+	edit_mail_expunge,
+	edit_mail_set_cache_corrupted,
+	NULL,
+};
+
+/*
+ * Edit Mail Stream
+ */
+
+struct edit_mail_istream {
+	struct istream_private istream;
+	pool_t pool;
+
+	struct edit_mail *mail;
+
+	struct _header_field_index *cur_header;
+	uoff_t cur_header_v_offset;
+
+	bool parent_buffer:1;
+	bool header_read:1;
+	bool eof:1;
+};
+
+static void edit_mail_istream_destroy(struct iostream_private *stream)
+{
+	struct edit_mail_istream *edstream =
+		(struct edit_mail_istream *)stream;
+
+	i_stream_unref(&edstream->istream.parent);
+	i_stream_free_buffer(&edstream->istream);
+	pool_unref(&edstream->pool);
+}
+
+static ssize_t merge_from_parent
+(struct edit_mail_istream *edstream, uoff_t parent_v_offset,
+	uoff_t parent_end_v_offset, uoff_t copy_v_offset)
+{
+	struct istream_private *stream = &edstream->istream;
+	uoff_t v_offset, append_v_offset;
+	const unsigned char *data;
+	size_t pos, cur_pos, parent_bytes_left;
+	bool parent_buffer = edstream->parent_buffer;
+	ssize_t ret;
+
+	i_assert(parent_v_offset <= parent_end_v_offset);
+	edstream->parent_buffer = FALSE;
+
+	v_offset = stream->istream.v_offset;
+	if (v_offset >= copy_v_offset) {
+		i_assert((v_offset - copy_v_offset) <= parent_end_v_offset);
+		if ((v_offset - copy_v_offset) == parent_end_v_offset) {
+			/* Parent data is all read */
+			return 0;
+		}
+	}
+
+	/* Determine where we are appending more data to the stream */
+	append_v_offset = v_offset + (stream->pos - stream->skip);
+
+	if (v_offset >= copy_v_offset) {
+		/* Parent buffer used */
+		cur_pos = (stream->pos - stream->skip);
+		parent_v_offset += (v_offset - copy_v_offset);
+	} else {
+		cur_pos = 0;
+		i_assert(append_v_offset >= copy_v_offset);
+		parent_v_offset += (append_v_offset - copy_v_offset);
+	}
+
+	/* Seek parent to required position */
+	i_stream_seek(stream->parent, parent_v_offset);
+
+	/* Read from parent */
+	data = i_stream_get_data(stream->parent, &pos);
+	if (pos > cur_pos)
+		ret = 0;
+	else do {
+		/* Use normal read here, since parent data can be returned directly
+		   to caller. */
+		ret = i_stream_read(stream->parent);
+
+		stream->istream.stream_errno = stream->parent->stream_errno;
+		stream->istream.eof = stream->parent->eof;
+		edstream->eof = stream->parent->eof;
+		data = i_stream_get_data(stream->parent, &pos);
+		/* check again, in case the parent stream had been seeked
+		   backwards and the previous read() didn't get us far
+		   enough. */
+	} while (pos <= cur_pos && ret > 0);
+
+	/* Don't read beyond parent end offset */
+	if (parent_end_v_offset != (uoff_t)-1) {
+		parent_bytes_left = (size_t)(parent_end_v_offset - parent_v_offset);
+		if (pos >= parent_bytes_left) {
+			pos = parent_bytes_left;
+		}
+	}
+
+	if (v_offset < copy_v_offset || ret == -2 ||
+		(parent_buffer && (append_v_offset + 1) >= parent_end_v_offset)) {
+		/* Merging with our local buffer; copying data from parent */
+		if (pos > 0) {
+			size_t avail;
+
+			if (parent_buffer) {
+				stream->pos = stream->skip = 0;
+				stream->buffer = NULL;
+			}
+			if (!i_stream_try_alloc(stream, pos, &avail))
+				return -2;
+			pos = (pos > avail ? avail : pos);
+
+			memcpy(stream->w_buffer + stream->pos, data, pos);
+			stream->pos += pos;
+			stream->buffer = stream->w_buffer;
+
+			if (cur_pos >= pos)
+				ret = 0;
+			else
+				ret = (ssize_t)(pos - cur_pos);
+		} else {
+			ret = (ret == 0 ? 0 : -1);
+		}
+	} else {
+		/* Just passing buffers from parent; no copying */
+		ret = (pos > cur_pos ? (ssize_t)(pos - cur_pos) :
+			(ret == 0 ? 0 : -1));
+		stream->buffer = data;
+		stream->pos = pos;
+		stream->skip = 0;
+		edstream->parent_buffer = TRUE;
+	}
+
+	i_assert(ret != -1 || stream->istream.eof ||
+		 stream->istream.stream_errno != 0);
+	return ret;
+}
+
+static ssize_t merge_modified_headers(struct edit_mail_istream *edstream)
+{
+	struct istream_private *stream = &edstream->istream;
+	struct edit_mail *edmail = edstream->mail;
+	uoff_t v_offset = stream->istream.v_offset, append_v_offset;
+	size_t appended, written, avail, size;
+
+	if (edstream->cur_header == NULL) {
+		/* No (more) headers */
+		return 0;
+	}
+
+	/* Caller must already have committed remaining parent data to
+	   our stream buffer. */
+	i_assert(!edstream->parent_buffer);
+
+	/* Add modified headers to buffer */
+	written = 0;
+	while ( edstream->cur_header != NULL) {
+		size_t wsize;
+
+		/* Determine what part of the header was already buffered */
+		append_v_offset = v_offset + (stream->pos - stream->skip);
+		i_assert(append_v_offset >= edstream->cur_header_v_offset);
+		if (append_v_offset >= edstream->cur_header_v_offset)
+			appended = (size_t)(append_v_offset - edstream->cur_header_v_offset);
+		else
+			appended = 0;
+		i_assert(appended <= edstream->cur_header->field->size);
+
+		/* Determine how much we want to write */
+		size = edstream->cur_header->field->size - appended;
+		if (size > 0) {
+			/* Determine how much we can write */
+			if (!i_stream_try_alloc(stream, size, &avail))
+				return -2;
+			wsize = (size >= avail ? avail : size);
+
+			/* Write (part of) the header to buffer */
+			memcpy(stream->w_buffer + stream->pos,
+				edstream->cur_header->field->data + appended, wsize);
+			stream->pos += wsize;
+			stream->buffer = stream->w_buffer;
+			written += wsize;
+
+			if (wsize < size) {
+				/* Could not write whole header; finish here */
+				break;
+			}
+		}
+
+		/* Skip to next header */
+		edstream->cur_header_v_offset +=
+			edstream->cur_header->field->size;
+		edstream->cur_header = edstream->cur_header->next;
+
+		/* Stop at end of prepended headers if original header is left unparsed */
+		if ( !edmail->headers_parsed
+			&& edstream->cur_header == edmail->header_fields_appended )
+			edstream->cur_header = NULL;
+	}
+
+	if (edstream->cur_header == NULL) {
+		/* Clear offset too, just to be tidy */
+		edstream->cur_header_v_offset = 0;
+	}
+
+	i_assert(written > 0);
+	return (ssize_t)written;
+}
+
+static ssize_t edit_mail_istream_read(struct istream_private *stream)
+{
+	struct edit_mail_istream *edstream =
+		(struct edit_mail_istream *)stream;
+	struct edit_mail *edmail = edstream->mail;
+	uoff_t v_offset, append_v_offset;
+	uoff_t parent_v_offset, parent_end_v_offset, copy_v_offset;
+	uoff_t prep_hdr_size, hdr_size;
+	ssize_t ret = 0;
+
+	if (edstream->eof) {
+		stream->istream.eof = TRUE;
+		return -1;
+	}
+
+	if (edstream->parent_buffer && stream->skip == stream->pos) {
+		edstream->parent_buffer = FALSE;
+		stream->pos = stream->skip = 0;
+		stream->buffer = NULL;
+	}
+
+	/* Merge prepended headers */
+	if (!edstream->parent_buffer) {
+		if ( (ret=merge_modified_headers(edstream)) != 0 )
+			return ret;
+	}
+	v_offset = stream->istream.v_offset;
+	append_v_offset = v_offset + (stream->pos - stream->skip);
+
+	if ( !edmail->headers_parsed &&
+		edmail->header_fields_appended != NULL &&
+		!edstream->header_read) {
+		/* Output headers from original stream */
+
+		/* Size of the prepended header */
+		i_assert(edmail->hdr_size.physical_size >=
+			edmail->appended_hdr_size.physical_size);
+		prep_hdr_size = edmail->hdr_size.physical_size -
+			edmail->appended_hdr_size.physical_size;
+
+		/* Offset of header end or appended header
+		 * Any final CR is dealt with later
+		 */
+		hdr_size = prep_hdr_size + edmail->wrapped_hdr_size.physical_size;
+		i_assert(hdr_size > 0);
+		if ( append_v_offset <= hdr_size - 1 &&
+			edmail->wrapped_hdr_size.physical_size > 0) {
+
+			parent_v_offset = stream->parent_start_offset;
+			parent_end_v_offset = stream->parent_start_offset +
+				edmail->wrapped_hdr_size.physical_size - 1;
+			copy_v_offset = prep_hdr_size;
+
+			if ( (ret=merge_from_parent(edstream, parent_v_offset,
+				parent_end_v_offset, copy_v_offset)) < 0 )
+				return ret;
+			append_v_offset = v_offset + (stream->pos - stream->skip);
+			i_assert(append_v_offset <= hdr_size - 1);
+
+			if ( append_v_offset == hdr_size - 1 ) {
+				/* Strip final CR too when it is present */
+				if ( stream->buffer != NULL &&
+					stream->buffer[stream->pos-1] == '\r' ) {
+					stream->pos--;
+					append_v_offset--;
+					ret--;
+				}
+
+				i_assert(ret >= 0);
+				edstream->cur_header = edmail->header_fields_appended;
+				edstream->cur_header_v_offset = append_v_offset;
+				if (!edstream->parent_buffer)
+					edstream->header_read = TRUE;
+			}
+
+			if (ret != 0)
+				return ret;
+		} else {
+			edstream->header_read = TRUE;
+		}
+
+		/* Merge appended headers */
+		if ( (ret=merge_modified_headers(edstream)) != 0 )
+			return ret;
+	}
+
+	/* Header does not come from original mail at all */
+	if ( edmail->headers_parsed ) {
+		parent_v_offset = stream->parent_start_offset +
+			edmail->wrapped_hdr_size.physical_size - ( edmail->eoh_crlf ? 2 : 1);
+		copy_v_offset = edmail->hdr_size.physical_size;
+
+	/* Header comes partially from original mail and headers are added between
+	   header and body.
+	 */
+	} else if (edmail->header_fields_appended != NULL) {
+		parent_v_offset = stream->parent_start_offset +
+			edmail->wrapped_hdr_size.physical_size - ( edmail->eoh_crlf ? 2 : 1);
+		copy_v_offset = edmail->hdr_size.physical_size +
+			edmail->wrapped_hdr_size.physical_size - ( edmail->eoh_crlf ? 2 : 1);
+
+	/* Header comes partially from original mail, but headers are only prepended.
+	 */
+	} else {
+		parent_v_offset = stream->parent_start_offset;
+		copy_v_offset = edmail->hdr_size.physical_size;
+	}
+
+	return merge_from_parent(edstream,
+		parent_v_offset, (uoff_t)-1, copy_v_offset);
+}
+
+static void
+stream_reset_to(struct edit_mail_istream *edstream, uoff_t v_offset)
+{
+	edstream->istream.istream.v_offset = v_offset;
+	edstream->istream.skip = 0;
+	edstream->istream.pos = 0;
+	edstream->istream.buffer = NULL;
+	edstream->parent_buffer = FALSE;
+	edstream->eof = FALSE;
+	i_stream_seek(edstream->istream.parent, 0);
+}
+
+static void edit_mail_istream_seek
+(struct istream_private *stream, uoff_t v_offset, bool mark ATTR_UNUSED)
+{
+	struct edit_mail_istream *edstream =
+		(struct edit_mail_istream *)stream;
+	struct _header_field_index *cur_header;
+	struct edit_mail *edmail = edstream->mail;
+	uoff_t offset;
+
+	edstream->header_read = FALSE;
+	edstream->cur_header = NULL;
+	edstream->cur_header_v_offset = 0;
+
+	/* The beginning */
+	if ( v_offset == 0 ) {
+		stream_reset_to(edstream, 0);
+
+		if ( edmail->header_fields_head != edmail->header_fields_appended )
+			edstream->cur_header = edmail->header_fields_head;
+		return;
+	}
+
+	/* Inside (prepended) headers */
+	if ( edmail->headers_parsed ) {
+		offset = edmail->hdr_size.physical_size;
+	} else {
+		offset = edmail->hdr_size.physical_size -
+			edmail->appended_hdr_size.physical_size;
+	}
+
+	if ( v_offset < offset ) {
+		stream_reset_to(edstream, v_offset);
+
+		/* Find the header */
+		cur_header = edmail->header_fields_head;
+		i_assert( cur_header != NULL &&
+			cur_header != edmail->header_fields_appended );
+		edstream->cur_header_v_offset = 0;
+		offset = cur_header->field->size;
+		while ( v_offset > offset ) {
+			cur_header = cur_header->next;
+			i_assert( cur_header != NULL &&
+				cur_header != edmail->header_fields_appended );
+
+			edstream->cur_header_v_offset = offset;
+			offset += cur_header->field->size;
+		}
+
+		edstream->cur_header = cur_header;
+		return;
+	}
+
+	if ( !edmail->headers_parsed ) {
+		/* Inside original header */
+		offset = edmail->hdr_size.physical_size -
+			edmail->appended_hdr_size.physical_size +
+			edmail->wrapped_hdr_size.physical_size;
+		if ( v_offset < offset ) {
+			stream_reset_to(edstream, v_offset);
+			return;
+		}
+
+		edstream->header_read = TRUE;
+
+		/* Inside appended header */
+		offset = edmail->hdr_size.physical_size +
+			edmail->wrapped_hdr_size.physical_size;
+		if ( v_offset < offset ) {
+			stream_reset_to(edstream, v_offset);
+
+			offset -= edmail->appended_hdr_size.physical_size;
+
+			cur_header = edmail->header_fields_appended;
+			i_assert( cur_header != NULL );
+			edstream->cur_header_v_offset = offset;
+			offset += cur_header->field->size;
+
+			while ( v_offset > offset ) {
+				cur_header = cur_header->next;
+				i_assert( cur_header != NULL );
+
+				edstream->cur_header_v_offset = offset;
+				offset += cur_header->field->size;
+			}
+
+			edstream->cur_header = cur_header;
+			return;
+		}
+	}
+
+	stream_reset_to(edstream, v_offset);
+	edstream->cur_header = NULL;
+}
+
+static void ATTR_NORETURN
+edit_mail_istream_sync(struct istream_private *stream ATTR_UNUSED)
+{
+	i_panic("edit-mail istream sync() not implemented");
+}
+
+static int
+edit_mail_istream_stat(struct istream_private *stream, bool exact)
+{
+	struct edit_mail_istream *edstream =
+		(struct edit_mail_istream *)stream;
+	struct edit_mail *edmail = edstream->mail;
+	const struct stat *st;
+
+	/* Stat the original stream */
+	if (i_stream_stat(stream->parent, exact, &st) < 0)
+		return -1;
+
+	stream->statbuf = *st;
+	if (st->st_size == -1 || !exact)
+		return 0;
+
+	if ( !edmail->headers_parsed ) {
+		if ( !edmail->modified )
+			return 0;
+	} else {
+		stream->statbuf.st_size = edmail->wrapped_body_size.physical_size +
+			( edmail->eoh_crlf ? 2 : 1 );
+	}
+
+	stream->statbuf.st_size += edmail->hdr_size.physical_size +
+		edmail->body_size.physical_size;
+	return 0;
+}
+
+struct istream *edit_mail_istream_create
+(struct edit_mail *edmail)
+{
+	struct edit_mail_istream *edstream;
+	struct istream *wrapped = edmail->wrapped_stream;
+
+	edstream = i_new(struct edit_mail_istream, 1);
+	edstream->pool = pool_alloconly_create(MEMPOOL_GROWING
+					      "edit mail stream", 4096);
+	edstream->mail = edmail;
+
+	edstream->istream.max_buffer_size = wrapped->real_stream->max_buffer_size;
+
+	edstream->istream.iostream.destroy = edit_mail_istream_destroy;
+	edstream->istream.read = edit_mail_istream_read;
+	edstream->istream.seek = edit_mail_istream_seek;
+	edstream->istream.sync = edit_mail_istream_sync;
+	edstream->istream.stat = edit_mail_istream_stat;
+
+	edstream->istream.istream.readable_fd = FALSE;
+	edstream->istream.istream.blocking = wrapped->blocking;
+	edstream->istream.istream.seekable = wrapped->seekable;
+
+	if ( edmail->header_fields_head != edmail->header_fields_appended )
+		edstream->cur_header = edmail->header_fields_head;
+
+	i_stream_seek(wrapped, 0);
+
+	return i_stream_create(&edstream->istream, wrapped, -1, 0);
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/util/edit-mail.h
@@ -0,0 +1,53 @@
+#ifndef EDIT_MAIL_H
+#define EDIT_MAIL_H
+
+struct edit_mail;
+
+struct edit_mail *edit_mail_wrap(struct mail *mail);
+void edit_mail_unwrap(struct edit_mail **edmail);
+struct edit_mail *edit_mail_snapshot(struct edit_mail *edmail);
+
+void edit_mail_reset(struct edit_mail *edmail);
+
+struct mail *edit_mail_get_mail(struct edit_mail *edmail);
+
+/*
+ * Header modification
+ */
+
+/* Simple API */
+
+void edit_mail_header_add
+	(struct edit_mail *edmail, const char *field_name, const char *value,
+		bool last);
+int edit_mail_header_delete
+	(struct edit_mail *edmail, const char *field_name, int index);
+int edit_mail_header_replace
+	(struct edit_mail *edmail, const char *field_name, int index,
+		const char *newname, const char *newvalue);
+
+/* Iterator */
+
+struct edit_mail_header_iter;
+
+int edit_mail_headers_iterate_init
+	(struct edit_mail *edmail, const char *field_name, bool reverse,
+		struct edit_mail_header_iter **edhiter_r);
+void edit_mail_headers_iterate_deinit
+	(struct edit_mail_header_iter **edhiter);
+
+void edit_mail_headers_iterate_get
+	(struct edit_mail_header_iter *edhiter, const char **value_r);
+
+bool edit_mail_headers_iterate_next
+	(struct edit_mail_header_iter *edhiter);
+
+bool edit_mail_headers_iterate_remove
+	(struct edit_mail_header_iter *edhiter);
+bool edit_mail_headers_iterate_replace
+	(struct edit_mail_header_iter *edhiter,
+		const char *newname, const char *newvalue);
+
+
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/util/mail-raw.c
@@ -0,0 +1,247 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "istream.h"
+#include "istream-seekable.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "strescape.h"
+#include "safe-mkstemp.h"
+#include "path-util.h"
+#include "message-address.h"
+#include "mbox-from.h"
+#include "raw-storage.h"
+#include "mail-namespace.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "settings-parser.h"
+#include "mail-raw.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <pwd.h>
+
+/*
+ * Configuration
+ */
+
+#define DEFAULT_ENVELOPE_SENDER "MAILER-DAEMON"
+
+/* After buffer grows larger than this, create a temporary file to /tmp
+   where to read the mail. */
+#define MAIL_MAX_MEMORY_BUFFER (1024*128)
+
+static const char *wanted_headers[] = {
+	"From", "Message-ID", "Subject", "Return-Path",
+	NULL
+};
+
+/*
+ * Global data
+ */
+
+struct mail_raw_user {
+	struct mail_namespace *ns;
+	struct mail_user *mail_user;
+};
+
+/*
+ * Raw mail implementation
+ */
+
+static int seekable_fd_callback
+(const char **path_r, void *context)
+{
+	struct mail_user *ruser = (struct mail_user *)context;
+	string_t *path;
+	int fd;
+
+	path = t_str_new(128);
+	mail_user_set_get_temp_prefix(path, ruser->set);
+	fd = safe_mkstemp(path, 0600, (uid_t)-1, (gid_t)-1);
+	if (fd == -1) {
+		i_error("safe_mkstemp(%s) failed: %m", str_c(path));
+		return -1;
+	}
+
+	/* we just want the fd, unlink it */
+	if (i_unlink(str_c(path)) < 0) {
+		/* shouldn't happen.. */
+		i_close_fd(&fd);
+		return -1;
+	}
+
+	*path_r = str_c(path);
+	return fd;
+}
+
+static struct istream *mail_raw_create_stream
+(struct mail_user *ruser, int fd, time_t *mtime_r, const char **sender)
+{
+	struct istream *input, *input2, *input_list[2];
+	const unsigned char *data;
+	size_t i, size;
+	int ret, tz;
+	char *env_sender = NULL;
+
+	*mtime_r = (time_t)-1;
+	fd_set_nonblock(fd, FALSE);
+
+	input = i_stream_create_fd(fd, 4096);
+	input->blocking = TRUE;
+	/* If input begins with a From-line, drop it */
+	ret = i_stream_read_bytes(input, &data, &size, 5);
+	if (ret > 0 && memcmp(data, "From ", 5) == 0) {
+		/* skip until the first LF */
+		i_stream_skip(input, 5);
+		while ( i_stream_read_more(input, &data, &size) > 0 ) {
+			for (i = 0; i < size; i++) {
+				if (data[i] == '\n')
+					break;
+			}
+			if (i != size) {
+				(void)mbox_from_parse(data, i, mtime_r, &tz, &env_sender);
+				i_stream_skip(input, i + 1);
+				break;
+			}
+			i_stream_skip(input, size);
+		}
+	}
+
+	if (env_sender != NULL && sender != NULL) {
+		*sender = t_strdup(env_sender);
+	}
+	i_free(env_sender);
+
+	if (input->v_offset == 0) {
+		input2 = input;
+		i_stream_ref(input2);
+	} else {
+		input2 = i_stream_create_limit(input, (uoff_t)-1);
+	}
+	i_stream_unref(&input);
+
+	input_list[0] = input2; input_list[1] = NULL;
+	input = i_stream_create_seekable(input_list, MAIL_MAX_MEMORY_BUFFER,
+		seekable_fd_callback, (void*)ruser);
+	i_stream_unref(&input2);
+	return input;
+}
+
+/*
+ * Init/Deinit
+ */
+
+struct mail_user *mail_raw_user_create
+(struct master_service *service, struct mail_user *mail_user)
+{
+	void **sets = master_service_settings_get_others(service);
+
+	return raw_storage_create_from_set(mail_user->set_info, sets[0]);
+}
+
+/*
+ * Open raw mail data
+ */
+
+static struct mail_raw *mail_raw_create
+(struct mail_user *ruser, struct istream *input,
+	const char *mailfile, const char *sender, time_t mtime)
+{
+	struct mail_raw *mailr;
+	struct mailbox_header_lookup_ctx *headers_ctx;
+	const char *envelope_sender, *error;
+	int ret;
+
+	if ( mailfile != NULL && *mailfile != '/' )
+		if (t_abspath(mailfile, &mailfile, &error) < 0)
+			i_fatal("t_abspath(%s) failed: %s",
+				mailfile, error);
+
+	mailr = i_new(struct mail_raw, 1);
+
+	envelope_sender = sender != NULL ? sender : DEFAULT_ENVELOPE_SENDER;
+	if ( mailfile == NULL ) {
+		ret = raw_mailbox_alloc_stream(ruser, input, mtime,
+					       envelope_sender, &mailr->box);
+	} else {
+		ret = raw_mailbox_alloc_path(ruser, mailfile, (time_t)-1,
+					     envelope_sender, &mailr->box);
+	}
+
+	if ( ret < 0 ) {
+		if ( mailfile == NULL ) {
+			i_fatal("Can't open delivery mail as raw: %s",
+				mailbox_get_last_error(mailr->box, NULL));
+		} else {
+			i_fatal("Can't open delivery mail as raw (file=%s): %s",
+				mailfile, mailbox_get_last_error(mailr->box, NULL));
+		}
+	}
+
+	mailr->trans = mailbox_transaction_begin(mailr->box, 0, __func__);
+	headers_ctx = mailbox_header_lookup_init(mailr->box, wanted_headers);
+	mailr->mail = mail_alloc(mailr->trans, 0, headers_ctx);
+	mailbox_header_lookup_unref(&headers_ctx);
+	mail_set_seq(mailr->mail, 1);
+
+	return mailr;
+}
+
+struct mail_raw *mail_raw_open_stream
+(struct mail_user *ruser, struct istream *input)
+{
+	struct mail_raw *mailr;
+
+	i_assert(input->seekable);
+	i_stream_set_name(input, "data");
+	mailr = mail_raw_create(ruser, input, NULL, NULL, (time_t)-1);
+
+	return mailr;
+}
+
+struct mail_raw *mail_raw_open_data
+(struct mail_user *ruser, string_t *mail_data)
+{
+	struct mail_raw *mailr;
+	struct istream *input;
+
+	input = i_stream_create_from_data(str_data(mail_data), str_len(mail_data));
+
+	mailr = mail_raw_open_stream(ruser, input);
+
+	i_stream_unref(&input);
+	return mailr;
+}
+
+struct mail_raw *mail_raw_open_file
+(struct mail_user *ruser, const char *path)
+{
+	struct mail_raw *mailr;
+	struct istream *input = NULL;
+	time_t mtime = (time_t)-1;
+	const char *sender = NULL;
+
+	if ( path == NULL || strcmp(path, "-") == 0 ) {
+		path = NULL;
+		input = mail_raw_create_stream(ruser, 0, &mtime, &sender);
+	}
+
+	mailr = mail_raw_create(ruser, input, path, sender, mtime);
+	i_stream_unref(&input);
+
+	return mailr;
+}
+
+void mail_raw_close(struct mail_raw **mailr)
+{
+	mail_free(&(*mailr)->mail);
+	mailbox_transaction_rollback(&(*mailr)->trans);
+	mailbox_free(&(*mailr)->box);
+
+	i_free(*mailr);
+	*mailr = NULL;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/util/mail-raw.h
@@ -0,0 +1,27 @@
+#ifndef MAIL_RAW_H
+#define MAIL_RAW_H
+
+#include "lib.h"
+#include "master-service.h"
+
+struct mail_raw {
+	pool_t pool;
+	struct mail *mail;
+
+	struct mailbox *box;
+	struct mailbox_transaction_context *trans;
+};
+
+struct mail_user *mail_raw_user_create
+	(struct master_service *service, struct mail_user *mail_user);
+
+struct mail_raw *mail_raw_open_stream
+	(struct mail_user *ruser, struct istream *input);
+struct mail_raw *mail_raw_open_file
+	(struct mail_user *ruser, const char *path);
+struct mail_raw *mail_raw_open_data
+	(struct mail_user *ruser, string_t *mail_data);
+void mail_raw_close(struct mail_raw **mailr);
+
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/util/rfc2822.c
@@ -0,0 +1,277 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* NOTE: much of the functionality implemented here should eventually appear
+ * somewhere in Dovecot itself.
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "unichar.h"
+
+#include "rfc2822.h"
+
+#include "message-header-encode.h"
+
+#include <stdio.h>
+#include <ctype.h>
+
+bool rfc2822_header_field_name_verify
+(const char *field_name, unsigned int len)
+{
+	const char *p = field_name;
+	const char *pend = p + len;
+
+	/* field-name   =   1*ftext
+	 * ftext        =   %d33-57 /               ; Any character except
+	 *                  %d59-126                ;  controls, SP, and
+	 *                                          ;  ":".
+	 */
+
+	while ( p < pend ) {
+		if ( *p < 33 || *p == ':' )
+			return FALSE;
+
+		p++;
+	}
+
+	return TRUE;
+}
+
+bool rfc2822_header_field_body_verify
+(const char *field_body, unsigned int len, bool allow_crlf, bool allow_utf8)
+{
+	const unsigned char *p = (const unsigned char *)field_body;
+	const unsigned char *pend = p + len;
+	bool is8bit = FALSE;
+
+	/* RFC5322:
+	 *
+	 * unstructured    =  (*([FWS] VCHAR) *WSP)
+	 * VCHAR           =  %x21-7E
+	 * FWS             =  ([*WSP CRLF] 1*WSP) /   ; Folding white space
+	 * WSP             =  SP / HTAB               ; White space
+	 */
+
+	while ( p < pend ) {
+		if ( *p < 0x20 ) {
+			if ( (*p == '\r' || *p == '\n') ) {
+				if ( !allow_crlf )
+					return FALSE;
+			} else if ( *p != '\t' ) {
+				return FALSE;
+			}
+		}
+
+		if ( !is8bit && *p > 127 ) {
+			if ( !allow_utf8 )
+				return FALSE;
+
+			is8bit = TRUE;
+		}
+
+		p++;
+	}
+
+	if ( is8bit && !uni_utf8_str_is_valid(field_body) ) {
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+/*
+ *
+ */
+
+const char *rfc2822_header_field_name_sanitize(const char *name)
+{
+	char *result = t_strdup_noconst(name);
+	char *p;
+
+	/* Make the whole name lower case ... */
+	result = str_lcase(result);
+
+	/* ... except for the first letter and those that follow '-' */
+	p = result;
+	*p = i_toupper(*p);
+	while ( *p != '\0' ) {
+		if ( *p == '-' ) {
+			p++;
+
+			if ( *p != '\0' )
+				*p = i_toupper(*p);
+
+			continue;
+		}
+
+		p++;
+	}
+
+	return result;
+}
+
+/*
+ * Message construction
+ */
+
+/* FIXME: This should be collected into a Dovecot API for composing internet
+ * mail messages.
+ */
+
+unsigned int rfc2822_header_append
+(string_t *header, const char *name, const char *body, bool crlf,
+	uoff_t *body_offset_r)
+{
+	static const unsigned int max_line = 80;
+
+	const char *bp = body;  /* Pointer */
+	const char *sp = body;  /* Start pointer */
+	const char *wp = NULL;  /* Whitespace pointer */
+	const char *nlp = NULL; /* New-line pointer */
+	unsigned int line_len = strlen(name);
+	unsigned int lines = 0;
+
+	/* Write header field name first */
+	str_append(header, name);
+	str_append(header, ": ");
+
+	if ( body_offset_r != NULL )
+		*body_offset_r = str_len(header);
+
+	line_len +=  2;
+
+	/* Add field body; fold it if necessary and account for existing folding */
+	while ( *bp != '\0' ) {
+		bool ws_first = TRUE;
+
+		while ( *bp != '\0' && nlp == NULL &&
+			(wp == NULL || line_len < max_line) ) {
+			if ( *bp == ' ' || *bp == '\t' ) {
+				if (ws_first)
+					wp = bp;
+				ws_first = FALSE;
+			} else if ( *bp == '\r' || *bp == '\n' ) {
+				if (ws_first)
+					nlp = bp;
+				else
+					nlp = wp;
+				break;
+			} else {
+				ws_first = TRUE;
+			}
+
+			bp++; line_len++;
+		}
+
+		if ( *bp == '\0' ) break;
+
+		/* Existing newline ? */
+		if ( nlp != NULL ) {			
+			/* Replace any consecutive newline and whitespace for
+			   consistency */
+			while ( *bp == ' ' || *bp == '\t' || *bp == '\r' || *bp == '\n' )
+				bp++;
+
+			str_append_data(header, sp, nlp-sp);
+
+			if ( crlf )
+				str_append(header, "\r\n");
+			else
+				str_append(header, "\n");
+
+			while ( *bp == ' ' || *bp == '\t' )
+				bp++;
+			if ( *bp != '\0' ) {
+				/* Continued line; replace leading whitespace with single TAB */
+				str_append_c(header, '\t');
+			}
+
+			sp = bp;
+		} else {
+			/* Insert newline at last whitespace within the max_line limit */
+			i_assert(wp >= sp);
+			str_append_data(header, sp, wp-sp);
+
+			/* Force continued line; drop any existing whitespace */
+			while ( *wp == ' ' || *wp == '\t' )
+				wp++;
+
+			if ( crlf )
+				str_append(header, "\r\n");
+			else
+				str_append(header, "\n");
+
+			/* Insert single TAB instead of the original whitespace */
+			str_append_c(header, '\t');
+
+			sp = wp;
+			if (sp > bp)
+				bp = sp;
+		}
+
+		lines++;
+
+		line_len = bp - sp;
+		wp = NULL;
+		nlp = NULL;
+	}
+
+	if ( bp != sp || lines == 0 ) {
+		str_append_data(header, sp, bp-sp);
+		if ( crlf )
+			str_append(header, "\r\n");
+		else
+			str_append(header, "\n");
+		lines++;
+	}
+
+	return lines;
+}
+
+void rfc2822_header_printf
+(string_t *header, const char *name, const char *fmt, ...)
+{
+	const char *body;
+	va_list args;
+
+	va_start(args, fmt);
+	body = t_strdup_vprintf(fmt, args);
+	va_end(args);
+
+	rfc2822_header_write(header, name, body);
+}
+
+void rfc2822_header_utf8_printf
+(string_t *header, const char *name, const char *fmt, ...)
+{
+	string_t *body = t_str_new(256);
+	va_list args;
+
+	va_start(args, fmt);
+	message_header_encode(t_strdup_vprintf(fmt, args), body);
+	va_end(args);
+
+	rfc2822_header_write(header, name, str_c(body));
+}
+
+
+void rfc2822_header_write_address(string_t *header,
+	const char *name, const char *address)
+{
+	bool has_8bit = FALSE;
+	const char *p;
+
+	for (p = address; *p != '\0'; p++) {
+		if ((*p & 0x80) != 0)
+			has_8bit = TRUE;
+	}
+
+	if (!has_8bit) {
+		rfc2822_header_write(header, name, address);
+	} else {
+		string_t *body = t_str_new(256);
+		message_header_encode(address, body);
+		rfc2822_header_write(header, name, str_c(body));
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/util/rfc2822.h
@@ -0,0 +1,46 @@
+#ifndef RFC2822_H
+#define RFC2822_H
+
+#include "lib.h"
+
+#include <stdio.h>
+
+/*
+ * Verification
+ */
+
+bool rfc2822_header_field_name_verify
+	(const char *field_name, unsigned int len);
+bool rfc2822_header_field_body_verify
+	(const char *field_body, unsigned int len, bool allow_crlf, bool allow_utf8);
+
+/*
+ *
+ */
+
+const char *rfc2822_header_field_name_sanitize(const char *name);
+
+/*
+ * Message composition
+ */
+
+unsigned int rfc2822_header_append
+	(string_t *header, const char *name, const char *body, bool crlf,
+		uoff_t *body_offset_r);
+
+static inline void rfc2822_header_write
+(string_t *header, const char *name, const char *body)
+{
+	(void)rfc2822_header_append(header, name, body, TRUE, NULL);
+}
+
+void rfc2822_header_printf
+	(string_t *header, const char *name, const char *fmt, ...) ATTR_FORMAT(3, 4);
+void rfc2822_header_utf8_printf
+	(string_t *header, const char *name, const char *fmt, ...) ATTR_FORMAT(3, 4);
+
+void rfc2822_header_write_address(string_t *header,
+	const char *name, const char *address);
+
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/util/test-edit-mail.c
@@ -0,0 +1,773 @@
+/* Copyright (c) 2018 Pigeonhole authors, see the included COPYING file */
+
+#include "lib.h"
+#include "test-common.h"
+#include "path-util.h"
+#include "buffer.h"
+#include "str.h"
+#include "istream.h"
+#include "istream-concat.h"
+#include "istream-crlf.h"
+#include "unlink-directory.h"
+#include "master-service.h"
+#include "istream-header-filter.h"
+#include "mail-storage.h"
+#include "mail-storage-service.h"
+#include "mail-user.h"
+
+#include "mail-raw.h"
+#include "edit-mail.h"
+
+#include <time.h>
+
+static pool_t test_pool;
+
+static struct mail_storage_service_ctx *mail_storage_service = NULL;
+static struct mail_user *test_mail_user = NULL;
+static struct mail_storage_service_user *test_service_user = NULL;
+static const char *mail_home;
+static char *test_dir;
+
+static struct mail_user *test_raw_mail_user = NULL;
+
+static void str_append_no_cr(string_t *str, const char *cstr)
+{
+	const char *p, *poff;
+
+	poff = p = cstr;
+	while (*p != '\0') {
+		if (*p == '\r') {
+			str_append_data(str, poff, (p - poff));
+			poff = p+1;
+		}
+		p++;
+	}
+	str_append_data(str, poff, (p - poff));
+}
+
+static int test_init_mail_user(void)
+{
+	const char *error;
+
+	mail_home = p_strdup_printf(test_pool, "%s/test_user.%ld.%ld",
+				    test_dir, (long)time(NULL), (long)getpid());
+
+	struct mail_storage_service_input input = {
+		.userdb_fields = (const char*const[]){
+			t_strdup_printf("mail=maildir:~/"),
+			t_strdup_printf("home=%s", mail_home),
+			NULL
+		},
+		.username = "test@example.com",
+		.no_userdb_lookup = TRUE,
+		.debug = TRUE,
+	};
+
+	mail_storage_service = mail_storage_service_init(master_service, NULL,
+		MAIL_STORAGE_SERVICE_FLAG_NO_RESTRICT_ACCESS |
+		MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT |
+		MAIL_STORAGE_SERVICE_FLAG_NO_PLUGINS);
+
+	if (mail_storage_service_lookup(mail_storage_service, &input,
+					&test_service_user, &error) < 0)
+	{
+		i_error("Cannot lookup test user: %s", error);
+		return -1;
+	}
+
+	if (mail_storage_service_next(mail_storage_service, test_service_user,
+				      &test_mail_user, &error) < 0)
+	{
+		 i_error("Cannot lookup test user: %s", error);
+		 return -1;
+	}
+
+	return 0;
+}
+
+static void test_deinit_mail_user()
+{
+	const char *error;
+	mail_user_unref(&test_mail_user);
+	mail_storage_service_user_unref(&test_service_user);
+	mail_storage_service_deinit(&mail_storage_service);
+	if (unlink_directory(mail_home, UNLINK_DIRECTORY_FLAG_RMDIR,
+			     &error) < 0)
+		i_error("unlink_directory(%s) failed: %s", mail_home, error);
+}
+
+static void test_init(void)
+{
+	test_pool = pool_alloconly_create(MEMPOOL_GROWING"test pool", 128);
+
+	test_init_mail_user();
+	test_raw_mail_user =
+		mail_raw_user_create(master_service, test_mail_user);
+}
+
+static void test_deinit(void)
+{
+	mail_user_unref(&test_raw_mail_user);
+	test_deinit_mail_user();
+	pool_unref(&test_pool);
+}
+
+static void test_stream_data(struct istream *input, buffer_t *buffer)
+{
+	const unsigned char *data;
+	size_t size;
+
+	while (i_stream_read_more(input, &data, &size) > 0) {
+		buffer_append(buffer, data, size);
+		i_stream_skip(input, size);
+	}
+
+	test_assert(!i_stream_have_bytes_left(input));
+	test_assert(input->stream_errno == 0);
+}
+
+static void test_stream_data_slow(struct istream *input, buffer_t *buffer)
+{
+	const unsigned char *data;
+	size_t size;
+	int ret;
+
+	ret = i_stream_read(input);
+	while (ret > 0 || i_stream_have_bytes_left(input) || ret == -2) {
+		data = i_stream_get_data(input, &size);
+		buffer_append(buffer, data, 1);
+		i_stream_skip(input, 1);
+
+		ret = i_stream_read(input);
+	}
+
+	test_assert(!i_stream_have_bytes_left(input));
+	test_assert(input->stream_errno == 0);
+}
+
+static void test_edit_mail_concatenated(void)
+{
+	static const char *hide_headers[] =
+		{ "Return-Path", "X-Sieve", "X-Sieve-Redirected-From" };
+	static const char *msg_part1 =
+		"Received: from example.com ([127.0.0.1] helo=example.com)\r\n"
+		"	by example.org with LMTP (Dovecot)\r\n"
+		"	(envelope-from <frop-bounces@example.com>)\r\n"
+		"	id 1er3e8-0015df-QO\r\n"
+		"	for timo@example.org;\r\n"
+		"	Sat, 03 Mar 2018 10:40:05 +0100\r\n";
+	static const char *msg_part2 =
+		"Return-Path: <stephan@example.com>\r\n";
+	static const char *msg_part3 =
+		"Delivered-To: <timo@example.org>\r\n";
+	static const char *msg_part4 =
+		"From: <stephan@example.com>\r\n"
+		"To: <timo@example.org>\r\n"
+		"Subject: Sieve editheader breaks with LMTP\r\n"
+		"\r\n"
+		"Hi,\r\n"
+		"\r\n"
+		"Sieve editheader seems to be broken when used from LMTP\r\n"
+		"\r\n"
+		"Regards,\r\n"
+		"\r\n"
+		"Stephan.\r\n";
+	static const char *msg_added =
+		"X-Filter-Junk-Type: NONE\r\n"
+		"X-Filter-Junk-Flag: NO\r\n";
+	struct istream *inputs[5], *input_msg, *input_filt, *input_mail, *input;
+	buffer_t *buffer;
+	struct mail_raw *rawmail;
+	struct edit_mail *edmail;
+	struct mail *mail;
+	string_t *expected;
+	const char *value;
+
+	test_begin("edit-mail - concatenated");
+	test_init();
+
+	/* compose the message */
+
+	inputs[0] = i_stream_create_from_data(msg_part1, strlen(msg_part1));
+	inputs[1] = i_stream_create_from_data(msg_part2, strlen(msg_part2));
+	inputs[2] = i_stream_create_from_data(msg_part3, strlen(msg_part3));
+	inputs[3] = i_stream_create_from_data(msg_part4, strlen(msg_part4));
+	inputs[4] = NULL;
+
+	input_msg = i_stream_create_concat(inputs);
+
+	i_stream_unref(&inputs[0]);
+	i_stream_unref(&inputs[1]);
+	i_stream_unref(&inputs[2]);
+	i_stream_unref(&inputs[3]);
+
+	rawmail = mail_raw_open_stream(test_raw_mail_user, input_msg);
+
+	/* add headers */
+
+	edmail = edit_mail_wrap(rawmail->mail);
+
+	edit_mail_header_add(edmail, "X-Filter-Junk-Flag", "NO", FALSE);
+	edit_mail_header_add(edmail, "X-Filter-Junk-Type", "NONE", FALSE);
+
+	mail = edit_mail_get_mail(edmail);
+
+	/* evaluate modified header */
+
+	test_assert(mail_get_first_header_utf8(mail, "Subject", &value) > 0);
+	test_assert(strcmp(value, "Sieve editheader breaks with LMTP") == 0);
+
+	test_assert(mail_get_first_header_utf8(mail, "X-Filter-Junk-Flag",
+					  &value) > 0);
+	test_assert(strcmp(value, "NO") == 0);
+	test_assert(mail_get_first_header_utf8(mail, "X-Filter-Junk-Type",
+					  &value) > 0);
+	test_assert(strcmp(value, "NONE") == 0);
+
+	test_assert(mail_get_first_header_utf8(mail, "Delivered-To",
+					  &value) > 0);
+
+	/* prepare tests */
+
+	if (mail_get_stream(mail, NULL, NULL, &input_mail) < 0) {
+		i_fatal("Failed to open mail stream: %s",
+			mailbox_get_last_error(mail->box, NULL));
+	}
+
+	buffer = buffer_create_dynamic(default_pool, 1024);
+	expected = t_str_new(1024);
+
+	/* added */
+
+	i_stream_seek(input_mail, 0);
+	buffer_set_used_size(buffer, 0);
+	input = input_mail;
+
+	test_stream_data(input_mail, buffer);
+
+	str_truncate(expected, 0);
+	str_append(expected, msg_added);
+	str_append(expected, msg_part1);
+	str_append(expected, msg_part2);
+	str_append(expected, msg_part3);
+	str_append(expected, msg_part4);
+
+	test_out("added",
+		 strcmp(str_c(buffer), str_c(expected)) == 0);
+
+	/* added, slow */
+
+	i_stream_seek(input_mail, 0);
+	buffer_set_used_size(buffer, 0);
+
+	test_stream_data_slow(input_mail, buffer);
+
+	str_truncate(expected, 0);
+	str_append(expected, msg_added);
+	str_append(expected, msg_part1);
+	str_append(expected, msg_part2);
+	str_append(expected, msg_part3);
+	str_append(expected, msg_part4);
+
+	test_out("added, slow",
+		 strcmp(str_c(buffer), str_c(expected)) == 0);
+
+	/* added, filtered */
+
+	i_stream_seek(input_mail, 0);
+	buffer_set_used_size(buffer, 0);
+
+	input_filt = i_stream_create_header_filter(input_mail,
+		HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR, hide_headers,
+		N_ELEMENTS(hide_headers), *null_header_filter_callback,
+		(void *)NULL);
+	input = i_stream_create_lf(input_filt);
+	i_stream_unref(&input_filt);
+
+	test_stream_data(input, buffer);
+	test_assert(!i_stream_have_bytes_left(input_mail));
+	test_assert(input_mail->stream_errno == 0);
+
+	str_truncate(expected, 0);
+	str_append_no_cr(expected, msg_added);
+	str_append_no_cr(expected, msg_part1);
+	str_append_no_cr(expected, msg_part3);
+	str_append_no_cr(expected, msg_part4);
+
+	test_out("added, filtered",
+		 strcmp(str_c(buffer), str_c(expected)) == 0);
+
+	i_stream_unref(&input);
+
+	/* added, filtered, slow */
+
+	i_stream_seek(input_mail, 0);
+	buffer_set_used_size(buffer, 0);
+
+	input_filt = i_stream_create_header_filter(input_mail,
+		HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR, hide_headers,
+		N_ELEMENTS(hide_headers), *null_header_filter_callback,
+		(void *)NULL);
+	input = i_stream_create_lf(input_filt);
+	i_stream_unref(&input_filt);
+
+	test_stream_data_slow(input, buffer);
+	test_assert(!i_stream_have_bytes_left(input_mail));
+	test_assert(input_mail->stream_errno == 0);
+
+	str_truncate(expected, 0);
+	str_append_no_cr(expected, msg_added);
+	str_append_no_cr(expected, msg_part1);
+	str_append_no_cr(expected, msg_part3);
+	str_append_no_cr(expected, msg_part4);
+
+	test_out("added, filtered, slow",
+		 strcmp(str_c(buffer), str_c(expected)) == 0);
+
+	i_stream_unref(&input);
+
+	/* delete header */
+
+	edit_mail_header_delete(edmail, "Delivered-To", 0);
+
+	/* evaluate modified header */
+
+	test_assert(mail_get_first_header_utf8(mail, "Subject", &value) > 0);
+	test_assert(strcmp(value, "Sieve editheader breaks with LMTP") == 0);
+
+	test_assert(mail_get_first_header_utf8(mail, "X-Filter-Junk-Flag",
+					  &value) > 0);
+	test_assert(strcmp(value, "NO") == 0);
+	test_assert(mail_get_first_header_utf8(mail, "X-Filter-Junk-Type",
+					  &value) > 0);
+	test_assert(strcmp(value, "NONE") == 0);
+
+	test_assert(mail_get_first_header_utf8(mail, "Delivered-To",
+					  &value) == 0);
+
+	/* deleted */
+
+	i_stream_seek(input_mail, 0);
+	buffer_set_used_size(buffer, 0);
+	input = input_mail;
+
+	test_stream_data(input_mail, buffer);
+
+	str_truncate(expected, 0);
+	str_append(expected, msg_added);
+	str_append(expected, msg_part1);
+	str_append(expected, msg_part2);
+	str_append(expected, msg_part4);
+
+	test_out("deleted",
+		 strcmp(str_c(buffer), str_c(expected)) == 0);
+
+	/* deleted, slow */
+
+	i_stream_seek(input_mail, 0);
+	buffer_set_used_size(buffer, 0);
+
+	test_stream_data_slow(input_mail, buffer);
+
+	str_truncate(expected, 0);
+	str_append(expected, msg_added);
+	str_append(expected, msg_part1);
+	str_append(expected, msg_part2);
+	str_append(expected, msg_part4);
+
+	test_out("deleted, slow",
+		 strcmp(str_c(buffer), str_c(expected)) == 0);
+
+	/* deleted, filtered */
+
+	i_stream_seek(input_mail, 0);
+	buffer_set_used_size(buffer, 0);
+
+	input_filt = i_stream_create_header_filter(input_mail,
+		HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR, hide_headers,
+		N_ELEMENTS(hide_headers), *null_header_filter_callback,
+		(void *)NULL);
+	input = i_stream_create_lf(input_filt);
+	i_stream_unref(&input_filt);
+
+	test_stream_data(input, buffer);
+	test_assert(!i_stream_have_bytes_left(input_mail));
+	test_assert(input_mail->stream_errno == 0);
+
+	str_truncate(expected, 0);
+	str_append_no_cr(expected, msg_added);
+	str_append_no_cr(expected, msg_part1);
+	str_append_no_cr(expected, msg_part4);
+
+	test_out("deleted, filtered",
+		 strcmp(str_c(buffer), str_c(expected)) == 0);
+
+	i_stream_unref(&input);
+
+	/* deleted, filtered, slow */
+
+	i_stream_seek(input_mail, 0);
+	buffer_set_used_size(buffer, 0);
+
+	input_filt = i_stream_create_header_filter(input_mail,
+		HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR, hide_headers,
+		N_ELEMENTS(hide_headers), *null_header_filter_callback,
+		(void *)NULL);
+	input = i_stream_create_lf(input_filt);
+	i_stream_unref(&input_filt);
+
+	test_stream_data_slow(input, buffer);
+	test_assert(!i_stream_have_bytes_left(input_mail));
+	test_assert(input_mail->stream_errno == 0);
+
+	str_truncate(expected, 0);
+	str_append_no_cr(expected, msg_added);
+	str_append_no_cr(expected, msg_part1);
+	str_append_no_cr(expected, msg_part4);
+
+	test_out("deleted, filtered, slow",
+		 strcmp(str_c(buffer), str_c(expected)) == 0);
+
+	i_stream_unref(&input);
+
+	/* clean up */
+
+	buffer_free(&buffer);
+	edit_mail_unwrap(&edmail);
+	mail_raw_close(&rawmail);
+	i_stream_unref(&input_msg);
+	test_deinit();
+	test_end();
+}
+
+static const char *big_header =
+	"X-A: AAAA\n"
+	"X-Big-One: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	" AAAAAAAAAAAAAAAAAAAAAAAAA\n"
+	"X-B: BBBB\n"
+	"\n"
+	"Frop!\n";
+
+static void test_edit_mail_big_header(void)
+{
+	struct istream *input_msg, *input_mail;
+	buffer_t *buffer;
+	struct mail_raw *rawmail;
+	struct edit_mail *edmail;
+	struct mail *mail;
+	const char *value;
+
+	test_begin("edit-mail - big header");
+	test_init();
+
+	/* compose the message */
+
+	input_msg = i_stream_create_from_data(big_header, strlen(big_header));
+
+	rawmail = mail_raw_open_stream(test_raw_mail_user, input_msg);
+
+	edmail = edit_mail_wrap(rawmail->mail);
+
+	/* delete header */
+
+	edit_mail_header_delete(edmail, "X-B", 0);
+	mail = edit_mail_get_mail(edmail);
+
+	/* prepare tests */
+
+	if (mail_get_stream(mail, NULL, NULL, &input_mail) < 0) {
+		i_fatal("Failed to open mail stream: %s",
+			mailbox_get_last_error(mail->box, NULL));
+	}
+
+	buffer = buffer_create_dynamic(default_pool, 1024);
+
+	/* evaluate modified header */
+
+	test_assert(mail_get_first_header_utf8(mail, "X-B",
+					  &value) == 0);
+
+	/* deleted */
+
+	i_stream_seek(input_mail, 0);
+	buffer_set_used_size(buffer, 0);
+
+	test_stream_data(input_mail, buffer);
+
+	/* clean up */
+
+	buffer_free(&buffer);
+	edit_mail_unwrap(&edmail);
+	mail_raw_close(&rawmail);
+	i_stream_unref(&input_msg);
+	test_deinit();
+	test_end();
+}
+
+int main(int argc, char *argv[])
+{
+	static void (*test_functions[])(void) = {
+		test_edit_mail_concatenated,
+		test_edit_mail_big_header,
+		NULL
+	};
+	const enum master_service_flags service_flags =
+		MASTER_SERVICE_FLAG_STANDALONE |
+		MASTER_SERVICE_FLAG_DONT_SEND_STATS |
+		MASTER_SERVICE_FLAG_NO_CONFIG_SETTINGS;
+	const char *cwd, *error;
+
+	master_service = master_service_init("test-edit-header", service_flags,
+					     &argc, &argv, "");
+	master_service_init_finish(master_service);
+
+	if (t_get_working_dir(&cwd, &error) < 0)
+		i_fatal("getcwd() failed: %s", error);
+	test_dir = i_strdup(cwd);
+
+	test_run(test_functions);
+
+	i_free(test_dir);
+	master_service_deinit(&master_service);
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/lib-sieve/util/test-rfc2822.c
@@ -0,0 +1,197 @@
+/* Copyright (c) 2018 Pigeonhole authors, see the included COPYING file */
+
+#include "lib.h"
+#include "test-common.h"
+#include "str.h"
+
+#include "rfc2822.h"
+
+struct test_header_write {
+	const char *name;
+	const char *body;
+	const char *output;
+};
+
+static const struct test_header_write header_write_tests[] = {
+	{
+		.name = "Frop",
+		.body = "Bladiebla",
+		.output = "Frop: Bladiebla\r\n"
+	},{
+		.name = "Subject",
+		.body = "This is a very long subject that well exceeds the "
+			"boundary of 80 characters. It should therefore "
+			"trigger the header folding algorithm.",
+		.output =
+			"Subject: This is a very long subject that well "
+			"exceeds the boundary of 80\r\n"
+			"\tcharacters. It should therefore trigger the header "
+			"folding algorithm.\r\n"
+	},{
+		.name = "Subject",
+		.body = "This\tis\ta\tvery\tlong\tsubject\tthat\twell\texceeds"
+			"\tthe\tboundary\tof\t80\tcharacters.\tIt\tshould\t"
+			"therefore\ttrigger\tthe\theader\tfolding\talgorithm.",
+		.output =
+			"Subject: This\tis\ta\tvery\tlong\tsubject\tthat\twell"
+			"\texceeds\tthe\tboundary\tof\t80\r\n"
+			"\tcharacters.\tIt\tshould\ttherefore\ttrigger\tthe\t"
+			"header\tfolding\talgorithm.\r\n"
+	},{
+		.name = "Comment",
+		.body = "This header already contains newlines.\n"
+			"The header folding algorithm should respect these.\n"
+			"It also should convert between CRLF and LF when "
+			"needed.",
+		.output = "Comment: This header already contains newlines.\r\n"
+			"\tThe header folding algorithm should respect "
+			"these.\r\n"
+			"\tIt also should convert between CRLF and LF when "
+			"needed.\r\n"
+	},{
+		.name = "References",
+		.body = "<messageid1@example.com> <messageid2@example.com> "
+			"<extremelylonglonglonglonglonglonglonglonglonglong"
+			"longlongmessageid3@example.com> "
+			"<messageid4@example.com>",
+		.output = "References: <messageid1@example.com> "
+			"<messageid2@example.com>\r\n"
+			"\t<extremelylonglonglonglonglonglonglonglonglonglong"
+			"longlongmessageid3@example.com>\r\n"
+			"\t<messageid4@example.com>\r\n",
+	},{
+		.name = "Cc",
+		.body = "\"Richard Edgar Cipient\"   "
+			"<r.e.cipient@example.com>,   \"Albert Buser\"   "
+			"<a.buser@example.com>,   \"Steven Pammer\"  "
+			"<s.pammer@example.com>",
+		.output = "Cc: \"Richard Edgar Cipient\"   "
+			"<r.e.cipient@example.com>,   \"Albert Buser\"\r\n"
+			"\t<a.buser@example.com>,   \"Steven Pammer\"  "
+			"<s.pammer@example.com>\r\n"
+	},{
+		.name = "References",
+		.body = "<00fd01d31b6c$33d98e30$9b8caa90$@karel@aa.example.org"
+			"> <00f201d31c36$afbfa320$0f3ee960$@karel@aa.example.o"
+			"rg>   <015c01d32023$fe3840c0$faa8c240$@karel@aa.examp"
+			"le.org>    <014601d325a4$ece1ed90$c6a5c8b0$@karel@aa."
+			"example.org>   <012801d32b24$7734c380$659e4a80$@karel"
+			"@aa.example.org> <00da01d32be9$2d9944b0$88cbce10$@kar"
+			"el@aa.example.org>      <006a01d336ef$6825d5b0$387181"
+			"10$@karel@aa.example.org>  <018501d33880$58b654f0$0a2"
+			"2fed0$@frederik@aa.example.org> <00e601d33ba3$be50f10"
+			"0$3af2d300$@frederik@aa.example.org>  <016501d341ee$e"
+			"678e1a0$b36aa4e0$@frederik@aa.example.org>    <00ab01"
+			"d348f9$ae2e1ab0$0a8a5010$@karel@aa.example.org> <0086"
+			"01d349c1$98ff4ba0$cafde2e0$@frederik@aa.example.org> "
+			" <019301d357e6$a2190680$e64b1380$@frederik@aa.example"
+			".org>                             <025f01d384b0$24d2c"
+			"660$6e785320$@karel@aa.example.org>   <01cf01d3889e$7"
+			"280cb90$578262b0$@karel@aa.example.org>    <013701d38"
+			"bc2$9164b950$b42e2bf0$@karel@aa.example.org>         "
+			"       <014f01d3a5b1$a51afc80$ef\n"
+			"               \n"
+			"\t \t \t \t \t \t \t \t5\t0\tf\t5\t8\t0\t$\t@\tk\ta\t"
+			"r\te\tl\t@\taa.example.org>        <01cb01d3af29$dd7d"
+			"1b40$987751c0$@karel@aa.example.org>                 "
+			"                      <00b401d3f2bc$6ad8c180$408a4480"
+			"$@karel@aa.example.org>   <011a01d3f6ab$0eeb0480$2cc1"
+			"0d80$@frederik@aa.example.org> <005c01d3f774$37f1b210"
+			"$a7d51630$@richard@aa.example.org>   <01a801d3fc2d$59"
+			"0f7730$0b2e6590$@frederik@aa.example.org> <007501d3fc"
+			"f5$23d75ce0$6b8616a0$@frederik@aa.example.org> <015d0"
+			"1d3fdbf$136da510$3a48ef30$@frederik@aa.example.org> <"
+			"021a01d3fe87$556d68b0$00483a10$@frederik@aa.example.o"
+			"rg> <013f01d3ff4e$a2d13d30$e873b790$@frederik@aa.exam"
+			"ple.org> <001f01d401ab$31e7b090$95b711b0$@frederik@aa"
+			".example.org> <017201d40273$a118d200$e34a7600$@freder"
+			"ik@aa.example.org> <017401d4033e$ca3602e0$5ea208a0$@f"
+			"rederik@aa.example.org> <02a601d40404$608b9e10$21a2da"
+			"30$@frederik@aa.example.org> <014301d404d0$b65269b0$2"
+			"2f73d10$@frederik@aa.example.org> <015901d4072b$b5a1b"
+			"950$20e52bf0$@frederik@aa.example.org> <01b401d407f3$"
+			"bef52050$3cdf\n"
+			" 60 \n"
+			"\tf0 \t$@ \tfr \ted \teri\tk@aa.example.org> <012801d"
+			"408bd$6602fce0$3208f6a0$@frederik@aa.example.org> <01"
+			"c801d40984$ae4b23c0$0ae16b40$@frederik@aa.example.org"
+			"> <00ec01d40a4d$12859190$3790b4b0$@frederik@aa.exampl"
+			"e.org> <02af01d40d74$589c9050$09d5b0f0$@frederik@aa.e"
+			"xample.org> <000d01d40ec8$d3d337b0$7b79a710$@richard@"
+			"aa.example.org>\n",
+		.output = "References: <00fd01d31b6c$33d98e30$9b8caa90$@karel@aa.example.org>\r\n"
+			"\t<00f201d31c36$afbfa320$0f3ee960$@karel@aa.example.org>\r\n"
+			"\t<015c01d32023$fe3840c0$faa8c240$@karel@aa.example.org>\r\n"
+			"\t<014601d325a4$ece1ed90$c6a5c8b0$@karel@aa.example.org>\r\n"
+			"\t<012801d32b24$7734c380$659e4a80$@karel@aa.example.org>\r\n"
+			"\t<00da01d32be9$2d9944b0$88cbce10$@karel@aa.example.org>\r\n"
+			"\t<006a01d336ef$6825d5b0$38718110$@karel@aa.example.org>\r\n"
+			"\t<018501d33880$58b654f0$0a22fed0$@frederik@aa.example.org>\r\n"
+			"\t<00e601d33ba3$be50f100$3af2d300$@frederik@aa.example.org>\r\n"
+			"\t<016501d341ee$e678e1a0$b36aa4e0$@frederik@aa.example.org>\r\n"
+			"\t<00ab01d348f9$ae2e1ab0$0a8a5010$@karel@aa.example.org>\r\n"
+			"\t<008601d349c1$98ff4ba0$cafde2e0$@frederik@aa.example.org>\r\n"
+			"\t<019301d357e6$a2190680$e64b1380$@frederik@aa.example.org>\r\n"
+			"\t<025f01d384b0$24d2c660$6e785320$@karel@aa.example.org>\r\n"
+			"\t<01cf01d3889e$7280cb90$578262b0$@karel@aa.example.org>\r\n"
+			"\t<013701d38bc2$9164b950$b42e2bf0$@karel@aa.example.org>\r\n"
+			"\t<014f01d3a5b1$a51afc80$ef\r\n"
+			"\t5\t0\tf\t5\t8\t0\t$\t@\tk\ta\tr\te\tl\t@\taa.example.org>\r\n"
+			"\t<01cb01d3af29$dd7d1b40$987751c0$@karel@aa.example.org>\r\n"
+			"\t<00b401d3f2bc$6ad8c180$408a4480$@karel@aa.example.org>\r\n"
+			"\t<011a01d3f6ab$0eeb0480$2cc10d80$@frederik@aa.example.org>\r\n"
+			"\t<005c01d3f774$37f1b210$a7d51630$@richard@aa.example.org>\r\n"
+			"\t<01a801d3fc2d$590f7730$0b2e6590$@frederik@aa.example.org>\r\n"
+			"\t<007501d3fcf5$23d75ce0$6b8616a0$@frederik@aa.example.org>\r\n"
+			"\t<015d01d3fdbf$136da510$3a48ef30$@frederik@aa.example.org>\r\n"
+			"\t<021a01d3fe87$556d68b0$00483a10$@frederik@aa.example.org>\r\n"
+			"\t<013f01d3ff4e$a2d13d30$e873b790$@frederik@aa.example.org>\r\n"
+			"\t<001f01d401ab$31e7b090$95b711b0$@frederik@aa.example.org>\r\n"
+			"\t<017201d40273$a118d200$e34a7600$@frederik@aa.example.org>\r\n"
+			"\t<017401d4033e$ca3602e0$5ea208a0$@frederik@aa.example.org>\r\n"
+			"\t<02a601d40404$608b9e10$21a2da30$@frederik@aa.example.org>\r\n"
+			"\t<014301d404d0$b65269b0$22f73d10$@frederik@aa.example.org>\r\n"
+			"\t<015901d4072b$b5a1b950$20e52bf0$@frederik@aa.example.org>\r\n"
+			"\t<01b401d407f3$bef52050$3cdf\r\n"
+			"\t60\r\n"
+			"\tf0 \t$@ \tfr \ted \teri\tk@aa.example.org>\r\n"
+			"\t<012801d408bd$6602fce0$3208f6a0$@frederik@aa.example.org>\r\n"
+			"\t<01c801d40984$ae4b23c0$0ae16b40$@frederik@aa.example.org>\r\n"
+			"\t<00ec01d40a4d$12859190$3790b4b0$@frederik@aa.example.org>\r\n"
+			"\t<02af01d40d74$589c9050$09d5b0f0$@frederik@aa.example.org>\r\n"
+			"\t<000d01d40ec8$d3d337b0$7b79a710$@richard@aa.example.org>\r\n"
+	}
+};
+
+static const unsigned int header_write_tests_count =
+	N_ELEMENTS(header_write_tests);
+
+static void test_rfc2822_header_write(void)
+{
+	string_t *header;
+	unsigned int i;
+
+	test_begin("rfc2822 - header write");
+
+	header = t_str_new(1024);
+	for (i = 0; i < header_write_tests_count; i++) {
+		const struct test_header_write *test = &header_write_tests[i];
+
+		str_truncate(header, 0);
+		rfc2822_header_write(header, test->name, test->body);
+
+		test_assert_idx(strcmp(str_c(header), test->output) == 0, i);
+	}
+
+	test_end();
+}
+
+int main(void)
+{
+	static void (*test_functions[])(void) = {
+		test_rfc2822_header_write,
+		NULL
+	};
+	return test_run(test_functions);
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve-login/Makefile.am
@@ -0,0 +1,43 @@
+settingsdir = $(dovecot_moduledir)/settings
+
+dovecot_pkglibexec_PROGRAMS = managesieve-login
+
+AM_CPPFLAGS = \
+	$(LIBDOVECOT_INCLUDE) \
+	$(LIBDOVECOT_SERVICE_INCLUDE) \
+	$(LIBDOVECOT_LOGIN_INCLUDE) \
+	-I$(top_srcdir) \
+	-I$(top_srcdir)/src/lib-managesieve
+
+libmanagesieve_login_settings_la_LDFLAGS = -module -avoid-version
+
+settings_LTLIBRARIES = \
+	libmanagesieve_login_settings.la
+
+libmanagesieve_login_settings_la_SOURCES = \
+	managesieve-login-settings.c \
+	managesieve-login-settings-plugin.c
+
+libmanagesieve_login_settings_la_CFLAGS = \
+	$(AM_CFLAGS) $(LIBDOVECOT_CONFIG_INCLUDE) -DPKG_LIBEXECDIR=\""$(dovecot_pkglibexecdir)"\"
+
+libs = \
+	$(top_builddir)/src/lib-managesieve/libmanagesieve.la
+
+managesieve_login_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS)
+managesieve_login_LDFLAGS = $(BINARY_LDFLAGS)
+managesieve_login_LDADD = $(libs) $(LIBDOVECOT_LOGIN) $(LIBDOVECOT)
+managesieve_login_DEPENDENCIES = $(libs) $(LIBDOVECOT_LOGIN_DEPS) $(LIBDOVECOT_DEPS)
+
+managesieve_login_SOURCES = \
+	client.c \
+	client-authenticate.c \
+	managesieve-login-settings.c \
+	managesieve-proxy.c
+
+noinst_HEADERS = \
+	client.h \
+	client-authenticate.h \
+	managesieve-login-settings.h \
+	managesieve-login-settings-plugin.h \
+	managesieve-proxy.h
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve-login/client-authenticate.c
@@ -0,0 +1,321 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "login-common.h"
+#include "base64.h"
+#include "buffer.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "safe-memset.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "auth-client.h"
+
+#include "managesieve-parser.h"
+#include "managesieve-quote.h"
+#include "client.h"
+
+#include "client-authenticate.h"
+#include "managesieve-proxy.h"
+
+
+const char *client_authenticate_get_capabilities
+(struct client *client)
+{
+	const struct auth_mech_desc *mech;
+	unsigned int i, count;
+	bool first = TRUE;
+	string_t *str;
+
+	str = t_str_new(128);
+	mech = sasl_server_get_advertised_mechs(client, &count);
+
+	for (i = 0; i < count; i++) {
+		/* Filter ANONYMOUS mechanism, ManageSieve has no use-case for it */
+		if ( (mech[i].flags & MECH_SEC_ANONYMOUS) == 0 ) {
+			if ( !first )
+				str_append_c(str, ' ');
+			else
+				first = FALSE;
+
+			str_append(str, mech[i].name);
+		}
+	}
+
+	return str_c(str);
+}
+
+void managesieve_client_auth_result(struct client *client,
+				   enum client_auth_result result,
+				   const struct client_auth_reply *reply, const char *text)
+{
+	struct managesieve_client *msieve_client =
+		(struct managesieve_client *)client;
+	string_t *referral;
+
+	switch (result) {
+	case CLIENT_AUTH_RESULT_SUCCESS:
+		/* nothing to be done for IMAP */
+		break;
+	case CLIENT_AUTH_RESULT_REFERRAL_SUCCESS:
+	case CLIENT_AUTH_RESULT_REFERRAL_NOLOGIN:
+		/* MANAGESIEVE referral
+
+		   [nologin] referral host=.. [port=..] [destuser=..]
+		   [reason=..]
+
+		   NO [REFERRAL sieve://user;AUTH=mech@host:port/] "Can't login."
+		   OK [...] "Logged in, but you should use this server instead."
+		   .. [REFERRAL ..] Reason from auth server
+		*/
+		referral = t_str_new(128);
+		str_printfa(referral, "REFERRAL sieve://%s;AUTH=%s@%s",
+			    reply->destuser, client->auth_mech_name, reply->host);
+		if ( reply->port != 4190 )
+			str_printfa(referral, ":%u", reply->port);
+
+		if ( result == CLIENT_AUTH_RESULT_REFERRAL_SUCCESS ) {
+			client_send_okresp(client, str_c(referral), text);;
+		} else {
+			client_send_noresp(client, str_c(referral), text);
+		}
+		break;
+	case CLIENT_AUTH_RESULT_ABORTED:
+	case CLIENT_AUTH_RESULT_AUTHFAILED_REASON:
+	case CLIENT_AUTH_RESULT_AUTHZFAILED:
+		client_send_no(client, text);
+		break;
+	case CLIENT_AUTH_RESULT_TEMPFAIL:
+		client_send_noresp(client, "TRYLATER", text);
+		break;
+	case CLIENT_AUTH_RESULT_SSL_REQUIRED:
+		client_send_noresp(client, "ENCRYPT-NEEDED", text);
+		break;
+	case CLIENT_AUTH_RESULT_AUTHFAILED:
+	default:
+		client_send_no(client,  text);
+		break;
+	}
+
+	msieve_client->auth_response_input = NULL;
+	managesieve_parser_reset(msieve_client->parser);
+}
+
+void managesieve_client_auth_send_challenge
+(struct client *client, const char *data)
+{
+	struct managesieve_client *msieve_client =
+		(struct managesieve_client *) client;
+
+	T_BEGIN {
+		string_t *str = t_str_new(256);
+
+		managesieve_quote_append_string(str, data, TRUE);
+		str_append(str, "\r\n");
+
+		client_send_raw_data(client, str_c(str), str_len(str));
+	} T_END;
+
+	msieve_client->auth_response_input = NULL;
+	managesieve_parser_reset(msieve_client->parser);
+}
+
+static int managesieve_client_auth_read_response
+(struct managesieve_client *msieve_client, bool initial, const char **error_r)
+{
+	struct client *client = &msieve_client->common;
+	const struct managesieve_arg *args;
+	const char *error;
+	bool fatal;
+	const unsigned char *data;
+	size_t size;
+	uoff_t resp_size;
+	int ret;
+
+	*error_r = NULL;
+
+	if ( i_stream_read(client->input) == -1 ) {
+		/* disconnected */
+		client_destroy(client, "Disconnected");
+		return -1;
+	}
+
+	if ( msieve_client->auth_response_input == NULL ) {
+
+		if ( msieve_client->skip_line ) {
+			if ( i_stream_next_line(client->input) == NULL )
+				return 0;
+
+			msieve_client->skip_line = FALSE;
+		}
+
+		switch ( managesieve_parser_read_args(msieve_client->parser, 0,
+			MANAGESIEVE_PARSE_FLAG_STRING_STREAM, &args) ) {
+		case -1:
+			error = managesieve_parser_get_error(msieve_client->parser, &fatal);
+			if (fatal) {
+				client_send_bye(client, error);
+				client_destroy(client, t_strconcat
+					("Disconnected: parse error during auth: ", error, NULL));
+			} else {
+				*error_r = error;
+			}
+			msieve_client->skip_line = TRUE;
+			return -1;
+
+		case -2:
+			/* not enough data */
+			return 0;
+
+		default:
+			break;
+		}
+
+		if ( MANAGESIEVE_ARG_IS_EOL(&args[0]) ) {
+			if (!initial) {
+				*error_r = "Received empty AUTHENTICATE client response line.";
+				msieve_client->skip_line = TRUE;
+				return -1;
+			}
+			msieve_client->skip_line = TRUE;
+			return 1;
+		}
+
+		if ( !managesieve_arg_get_string_stream
+				(&args[0], &msieve_client->auth_response_input)
+			|| !MANAGESIEVE_ARG_IS_EOL(&args[1]) ) {
+			if ( !initial )
+				*error_r = "Invalid AUTHENTICATE client response.";
+			else
+				*error_r = "Invalid AUTHENTICATE initial response.";
+			msieve_client->skip_line = TRUE;
+			return -1;
+		}
+
+		if ( i_stream_get_size
+			(msieve_client->auth_response_input, FALSE, &resp_size) <= 0 )
+			resp_size = 0;
+
+		if (client->auth_response == NULL)
+			client->auth_response = str_new(default_pool, I_MAX(resp_size+1, 256));
+	}
+
+	while ( (ret=i_stream_read_more
+		(msieve_client->auth_response_input, &data, &size) ) > 0 ) {
+
+		if (str_len(client->auth_response) + size > LOGIN_MAX_AUTH_BUF_SIZE) {
+			client_destroy(client, "Authentication response too large");
+			return -1;
+		}
+
+		str_append_data(client->auth_response, data, size);
+		i_stream_skip(msieve_client->auth_response_input, size);
+	}
+
+	if ( ret == 0 ) return 0;
+
+	if ( msieve_client->auth_response_input->stream_errno != 0 ) {
+		if ( !client->input->eof &&
+			msieve_client->auth_response_input->stream_errno == EINVAL ) {
+			msieve_client->skip_line = TRUE;
+			*error_r = t_strconcat
+				("Error in AUTHENTICATE response string: ",
+					i_stream_get_error(msieve_client->auth_response_input), NULL);
+			return -1;
+		}
+
+		client_destroy(client, "Disconnected");
+		return -1;
+	}
+
+	if ( i_stream_next_line(client->input) == NULL )
+		return 0;
+
+	return 1;
+}
+
+void managesieve_client_auth_parse_response(struct client *client)
+{
+	struct managesieve_client *msieve_client =
+		(struct managesieve_client *) client;
+	const char *error = NULL;
+	int ret;
+
+	if ( (ret=managesieve_client_auth_read_response(msieve_client, FALSE, &error))
+		< 0 ) {
+		if ( error != NULL )
+			client_auth_fail(client, error);
+		return;
+	}
+
+	if ( ret == 0 ) return;
+
+	if ( strcmp(str_c(client->auth_response), "*") == 0 ) {
+		client_auth_abort(client);
+		return;
+	}
+
+	client_auth_respond(client, str_c(client->auth_response));
+
+	memset(str_c_modifiable(client->auth_response), 0,
+	       str_len(client->auth_response));
+}
+
+int cmd_authenticate
+(struct managesieve_client *msieve_client, const struct managesieve_arg *args)
+{
+	/* NOTE: This command's input is handled specially because the
+	   SASL-IR can be large. */
+	struct client *client = &msieve_client->common;
+	const char *mech_name, *init_response;
+	const char *error;
+	int ret;
+
+	if (!msieve_client->auth_mech_name_parsed) {
+		i_assert(args != NULL);
+
+		/* one mandatory argument: authentication mechanism name */
+		if ( !managesieve_arg_get_string(&args[0], &mech_name) )
+			return -1;
+
+		if (*mech_name == '\0')
+			return -1;
+
+		/* Refuse the ANONYMOUS mechanism. */
+		if ( strncasecmp(mech_name, "ANONYMOUS", 9) == 0 ) {
+			client_send_no(client, "ANONYMOUS login is not allowed.");
+			return 1;
+		}
+
+		i_free(client->auth_mech_name);
+		client->auth_mech_name = i_strdup(mech_name);
+		msieve_client->auth_mech_name_parsed = TRUE;
+
+		msieve_client->auth_response_input = NULL;
+		managesieve_parser_reset(msieve_client->parser);
+	}
+
+	msieve_client->skip_line = FALSE;
+	if ( (ret=managesieve_client_auth_read_response(msieve_client, TRUE, &error))
+		< 0 ) {
+		msieve_client->auth_mech_name_parsed = FALSE;
+		if ( error != NULL ) {
+			client_send_no(client, error);
+		}
+		return 1;
+	}
+
+	if ( ret == 0 ) return 0;
+
+	init_response = ( client->auth_response == NULL ? NULL :
+		t_strdup(str_c(client->auth_response)) );
+	msieve_client->auth_mech_name_parsed = FALSE;
+	if ( (ret=client_auth_begin
+		(client, t_strdup(client->auth_mech_name), init_response)) < 0 )
+		return ret;
+
+	msieve_client->cmd_finished = TRUE;
+	return 0;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve-login/client-authenticate.h
@@ -0,0 +1,21 @@
+#ifndef CLIENT_AUTHENTICATE_H
+#define CLIENT_AUTHENTICATE_H
+
+struct managesieve_arg;
+
+const char *client_authenticate_get_capabilities
+	(struct client *client);
+
+void managesieve_client_auth_result
+	(struct client *client, enum client_auth_result result,
+		const struct client_auth_reply *reply, const char *text);
+
+void managesieve_client_auth_send_challenge
+	(struct client *client, const char *data);
+void managesieve_client_auth_parse_response
+	(struct client *client);
+
+int cmd_authenticate
+	(struct managesieve_client *client, const struct managesieve_arg *args);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve-login/client.c
@@ -0,0 +1,540 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "login-common.h"
+#include "buffer.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "safe-memset.h"
+#include "str.h"
+#include "strescape.h"
+#include "master-service.h"
+#include "master-auth.h"
+#include "auth-client.h"
+
+#include "managesieve-parser.h"
+#include "managesieve-quote.h"
+
+#include "client.h"
+#include "client-authenticate.h"
+
+#include "managesieve-login-settings.h"
+#include "managesieve-proxy.h"
+
+
+/* Disconnect client when it sends too many bad commands */
+#define CLIENT_MAX_BAD_COMMANDS 3
+
+struct managesieve_command {
+	const char *name;
+	int (*func)
+		(struct managesieve_client *client, const struct managesieve_arg *args);
+	int preparsed_args;
+};
+
+/* Skip incoming data until newline is found,
+   returns TRUE if newline was found. */
+bool client_skip_line(struct managesieve_client *client)
+{
+	const unsigned char *data;
+	size_t i, data_size;
+
+	data = i_stream_get_data(client->common.input, &data_size);
+
+	for (i = 0; i < data_size; i++) {
+		if (data[i] == '\n') {
+			i_stream_skip(client->common.input, i+1);
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
+static void client_send_capabilities(struct client *client)
+{
+	struct managesieve_client *msieve_client =
+		(struct managesieve_client *) client;
+	const char *saslcap;
+
+	T_BEGIN {
+		saslcap = client_authenticate_get_capabilities(client);
+
+		/* Default capabilities */
+		client_send_raw(client, t_strconcat("\"IMPLEMENTATION\" \"",
+			msieve_client->set->managesieve_implementation_string, "\"\r\n", NULL));
+		client_send_raw(client, t_strconcat("\"SIEVE\" \"",
+			msieve_client->set->managesieve_sieve_capability, "\"\r\n", NULL));
+		if ( msieve_client->set->managesieve_notify_capability != NULL )
+			client_send_raw(client, t_strconcat("\"NOTIFY\" \"",
+				msieve_client->set->managesieve_notify_capability, "\"\r\n", NULL));
+		client_send_raw
+			(client, t_strconcat("\"SASL\" \"", saslcap, "\"\r\n", NULL));
+
+		/* STARTTLS */
+		if (login_ssl_initialized && !client->tls)
+			client_send_raw(client, "\"STARTTLS\"\r\n" );
+
+		/* Protocol version */
+		client_send_raw(client, "\"VERSION\" \"1.0\"\r\n");
+
+		/* XCLIENT */
+	    if (client->trusted)
+        	client_send_raw(client, "\"XCLIENT\"\r\n");
+	} T_END;
+}
+
+static int cmd_capability
+(struct managesieve_client *client,
+	const struct managesieve_arg *args ATTR_UNUSED)
+{
+	o_stream_cork(client->common.output);
+
+	client_send_capabilities(&client->common);
+	client_send_ok(&client->common, "Capability completed.");
+
+	o_stream_uncork(client->common.output);
+
+	return 1;
+}
+
+static int cmd_starttls
+(struct managesieve_client *client,
+	const struct managesieve_arg *args ATTR_UNUSED)
+{
+	client_cmd_starttls(&client->common);
+	return 1;
+}
+
+static void managesieve_client_notify_starttls
+(struct client *client, bool success, const char *text)
+{
+	if ( success )
+		client_send_ok(client, text);
+	else
+		client_send_no(client, text);
+}
+
+static int cmd_noop
+(struct managesieve_client *client,
+	const struct managesieve_arg *args)
+{
+	const char *text;
+	string_t *resp_code;
+
+	if ( MANAGESIEVE_ARG_IS_EOL(&args[0]) ) {
+		client_send_ok(&client->common, "NOOP Completed");
+		return 1;
+	}
+
+	if ( !MANAGESIEVE_ARG_IS_EOL(&args[1]) )
+		return -1;
+
+	if ( !managesieve_arg_get_string(&args[0], &text) ) {
+		client_send_no(&client->common, "Invalid echo tag.");
+		return 1;
+	}
+
+	resp_code = t_str_new(256);
+	str_append(resp_code, "TAG ");
+	managesieve_quote_append_string(resp_code, text, FALSE);
+
+	client_send_okresp(&client->common, str_c(resp_code), "Done");
+	return 1;
+}
+
+static int cmd_logout
+(struct managesieve_client *client,
+	const struct managesieve_arg *args ATTR_UNUSED)
+{
+	client_send_ok(&client->common, "Logout completed.");
+	client_destroy(&client->common, "Aborted login");
+	return 1;
+}
+
+static int cmd_xclient
+(struct managesieve_client *client,
+	const struct managesieve_arg *args)
+{
+	const char *arg;
+	bool args_ok = TRUE;
+
+	if ( !client->common.trusted ) {
+		client_send_no(&client->common,
+			"You are not from trusted IP");
+		return 1;
+	}
+	while (!MANAGESIEVE_ARG_IS_EOL(&args[0]) &&
+		managesieve_arg_get_atom(&args[0], &arg)) {
+		if ( strncasecmp(arg, "ADDR=", 5) == 0 ) {
+			if (net_addr2ip(arg + 5, &client->common.ip) < 0)
+				args_ok = FALSE;
+		} else if ( strncasecmp(arg, "PORT=", 5) == 0 ) {
+			if (net_str2port(arg + 5, &client->common.remote_port) < 0)
+				args_ok = FALSE;
+		} else if ( strncasecmp(arg, "SESSION=", 8) == 0 ) {
+			const char *value = arg + 8;
+
+			if (strlen(value) <= LOGIN_MAX_SESSION_ID_LEN) {
+				client->common.session_id =
+					p_strdup(client->common.pool, value);
+			}
+		} else if ( strncasecmp(arg, "TTL=", 4 ) == 0) {
+			if (str_to_uint(arg + 4, &client->common.proxy_ttl) < 0)
+				args_ok = FALSE;
+		}
+		args++;
+	}
+	if ( !args_ok || !MANAGESIEVE_ARG_IS_EOL(&args[0]))
+		return -1;
+
+	client_send_ok(&client->common, "Updated");
+	return 1;
+}
+
+static struct managesieve_command commands[] = {
+	{ "AUTHENTICATE", cmd_authenticate, 1 },
+	{ "CAPABILITY", cmd_capability, -1 },
+	{ "STARTTLS", cmd_starttls, -1 },
+	{ "NOOP", cmd_noop, 0 },
+	{ "LOGOUT", cmd_logout, -1 },
+	{ "XCLIENT", cmd_xclient, 0 },
+	{ NULL, NULL, 0 }
+};
+
+static bool client_handle_input(struct managesieve_client *client)
+{
+	i_assert(!client->common.authenticating);
+
+	if (client->cmd_finished) {
+		/* clear the previous command from memory */
+		client->cmd_name = NULL;
+		client->cmd_parsed_args = FALSE;
+		client->cmd = NULL;
+		managesieve_parser_reset(client->parser);
+
+		/* remove \r\n */
+		if (client->skip_line) {
+			if (!client_skip_line(client))
+				return FALSE;
+			client->skip_line = FALSE;
+		}
+
+		client->cmd_finished = FALSE;
+	}
+
+	if (client->cmd == NULL) {
+		struct managesieve_command *cmd;
+		const char *cmd_name;
+
+		client->cmd_name = managesieve_parser_read_word(client->parser);
+		if (client->cmd_name == NULL)
+			return FALSE; /* need more data */
+
+		cmd_name = t_str_ucase(client->cmd_name);
+		cmd = commands;
+		while ( cmd->name != NULL ) {
+			if ( strcmp(cmd->name, cmd_name) == 0 )
+				break;
+			cmd++;
+		}
+
+		if ( cmd->name != NULL )
+			client->cmd = cmd;
+		else
+			client->skip_line = TRUE;
+	}
+	return client->common.v.input_next_cmd(&client->common);
+}
+
+static bool managesieve_client_input_next_cmd(struct client *_client)
+{
+	struct managesieve_client *client =
+		(struct managesieve_client *)_client;
+	const struct managesieve_arg *args = NULL;
+	const char *msg;
+	int ret = 1;
+	bool fatal;
+
+	if (client->cmd == NULL) {
+		/* unknown command */
+		ret = -1;
+	} else if ( !client->cmd_parsed_args ) {
+		unsigned int arg_count =
+			( client->cmd->preparsed_args > 0 ? client->cmd->preparsed_args : 0 );
+		switch (managesieve_parser_read_args(client->parser, arg_count, 0, &args)) {
+		case -1:
+			/* error */
+			msg = managesieve_parser_get_error(client->parser, &fatal);
+			if (fatal) {
+				client_send_bye(&client->common, msg);
+				client_destroy(&client->common, t_strconcat("Disconnected: ",
+					msg, NULL));
+				return FALSE;
+			}
+
+			client_send_no(&client->common, msg);
+			client->cmd_finished = TRUE;
+			client->skip_line = TRUE;
+			return TRUE;
+		case -2:
+			/* not enough data */
+			return FALSE;
+		}
+		i_assert(args != NULL);
+
+		if (arg_count == 0 ) {
+			/* we read the entire line - skip over the CRLF */
+			if (!client_skip_line(client))
+				i_unreached();
+		} else {
+			/* get rid of it later */
+			client->skip_line = TRUE;
+		}
+
+		client->cmd_parsed_args = TRUE;
+
+		if (client->cmd->preparsed_args == -1) {
+			/* check absence of arguments */
+			if ( args[0].type != MANAGESIEVE_ARG_EOL )
+				ret = -1;
+		}
+	}
+	if (ret > 0) {
+		i_assert(client->cmd != NULL);
+		ret = client->cmd->func(client, args);
+	}
+
+	if (ret != 0)
+		client->cmd_finished = TRUE;
+	if (ret < 0) {
+		if (++client->common.bad_counter >= CLIENT_MAX_BAD_COMMANDS) {
+			client_send_bye(&client->common,
+				"Too many invalid MANAGESIEVE commands.");
+			client_destroy(&client->common,
+				"Disconnected: Too many invalid commands.");
+			return FALSE;
+		}
+		client_send_no(&client->common,
+			"Error in MANAGESIEVE command received by server.");
+	}
+
+	return ret != 0 && !client->common.destroyed;
+}
+
+static void managesieve_client_input(struct client *client)
+{
+	struct managesieve_client *managesieve_client =
+		(struct managesieve_client *) client;
+
+	if (!client_read(client))
+		return;
+
+	client_ref(client);
+
+	o_stream_cork(managesieve_client->common.output);
+	for (;;) {
+		if (!auth_client_is_connected(auth_client)) {
+			/* we're not currently connected to auth process -
+			   don't allow any commands */
+			/* FIXME: Can't do untagged responses with managesieve. Any other ways?
+			client_send_ok(client, AUTH_SERVER_WAITING_MSG);
+			*/
+			timeout_remove(&client->to_auth_waiting);
+
+			client->input_blocked = TRUE;
+			break;
+		} else {
+			if (!client_handle_input(managesieve_client))
+				break;
+		}
+	}
+	o_stream_uncork(managesieve_client->common.output);
+	client_unref(&client);
+}
+
+static struct client *managesieve_client_alloc(pool_t pool)
+{
+	struct managesieve_client *msieve_client;
+
+	msieve_client = p_new(pool, struct managesieve_client, 1);
+	return &msieve_client->common;
+}
+
+static void managesieve_client_create
+(struct client *client, void **other_sets)
+{
+	struct managesieve_client *msieve_client =
+		(struct managesieve_client *) client;
+
+	msieve_client->set = other_sets[0];
+	msieve_client->parser = managesieve_parser_create
+		(msieve_client->common.input, MAX_MANAGESIEVE_LINE);
+	client->io = io_add(client->fd, IO_READ, client_input, client);
+}
+
+static void managesieve_client_destroy(struct client *client)
+{
+	struct managesieve_client *managesieve_client =
+		(struct managesieve_client *) client;
+
+	managesieve_parser_destroy(&managesieve_client->parser);
+}
+
+static void managesieve_client_notify_auth_ready(struct client *client)
+{
+	/* Cork the stream to send the capability data as a single tcp frame
+	 *   Some naive clients break if we don't.
+	 */
+	o_stream_cork(client->output);
+
+	/* Send initial capabilities */
+	client_send_capabilities(client);
+	client_send_ok(client, client->set->login_greeting);
+
+	o_stream_uncork(client->output);
+
+	client->banner_sent = TRUE;
+}
+
+static void managesieve_client_starttls(struct client *client)
+{
+	struct managesieve_client *msieve_client =
+		(struct managesieve_client *) client;
+
+	managesieve_parser_destroy(&msieve_client->parser);
+	msieve_client->parser = managesieve_parser_create
+		(msieve_client->common.input, MAX_MANAGESIEVE_LINE);
+
+	/* CRLF is lost from buffer when streams are reopened. */
+	msieve_client->skip_line = FALSE;
+
+	/* Cork the stream to send the capability data as a single tcp frame
+	 *   Some naive clients break if we don't.
+	 */
+	o_stream_cork(client->output);
+
+	client_send_capabilities(client);
+	client_send_ok(client, "TLS negotiation successful.");
+
+	o_stream_uncork(client->output);
+}
+
+static void
+client_send_reply_raw(struct client *client,
+				   const char *prefix, const char *resp_code,
+				   const char *text)
+{
+	T_BEGIN {
+		string_t *line = t_str_new(256);
+
+		str_append(line, prefix);
+
+		if (resp_code != NULL) {
+			str_append(line, " (");
+			str_append(line, resp_code);
+			str_append_c(line, ')');
+		}
+
+		if ( text != NULL )	{
+			str_append_c(line, ' ');
+			managesieve_quote_append_string(line, text, TRUE);
+		}
+
+		str_append(line, "\r\n");
+
+		client_send_raw_data(client, str_data(line), str_len(line));
+	} T_END;
+}
+
+void client_send_reply_code
+(struct client *client, enum managesieve_cmd_reply reply, const char *resp_code,
+	const char *text)
+{
+	const char *prefix = "NO";
+
+	switch (reply) {
+	case MANAGESIEVE_CMD_REPLY_OK:
+		prefix = "OK";
+		break;
+	case MANAGESIEVE_CMD_REPLY_NO:
+		break;
+	case MANAGESIEVE_CMD_REPLY_BYE:
+		prefix = "BYE";
+		break;
+	}
+
+	client_send_reply_raw(client, prefix, resp_code, text);
+}
+
+void client_send_reply
+(struct client *client, enum managesieve_cmd_reply reply, const char *text)
+{
+	client_send_reply_code(client, reply, NULL, text);
+}
+
+static void
+managesieve_client_notify_disconnect
+(struct client *client, enum client_disconnect_reason reason, const char *text)
+{
+	if ( reason == CLIENT_DISCONNECT_SYSTEM_SHUTDOWN ) {
+		client_send_reply_code
+			(client, MANAGESIEVE_CMD_REPLY_BYE, "TRYLATER", text);
+	} else {
+		client_send_reply_code
+			(client, MANAGESIEVE_CMD_REPLY_BYE, NULL, text);
+	}
+}
+
+static void managesieve_login_preinit(void)
+{
+	login_set_roots = managesieve_login_settings_set_roots;
+}
+
+static void managesieve_login_init(void)
+{
+}
+
+static void managesieve_login_deinit(void)
+{
+	clients_destroy_all();
+}
+
+static struct client_vfuncs managesieve_client_vfuncs = {
+	managesieve_client_alloc,
+	managesieve_client_create,
+	managesieve_client_destroy,
+	managesieve_client_notify_auth_ready,
+	managesieve_client_notify_disconnect,
+	NULL,
+	managesieve_client_notify_starttls,
+	managesieve_client_starttls,
+	managesieve_client_input,
+	managesieve_client_auth_send_challenge,
+	managesieve_client_auth_parse_response,
+	managesieve_client_auth_result,
+	managesieve_proxy_reset,
+	managesieve_proxy_parse_line,
+	managesieve_proxy_error,
+	managesieve_proxy_get_state,
+	client_common_send_raw_data,
+	managesieve_client_input_next_cmd,
+	client_common_default_free,
+};
+
+static const struct login_binary managesieve_login_binary = {
+	.protocol = "sieve",
+	.process_name = "managesieve-login",
+	.default_port = 4190,
+
+	.client_vfuncs = &managesieve_client_vfuncs,
+	.preinit = managesieve_login_preinit,
+	.init = managesieve_login_init,
+	.deinit = managesieve_login_deinit
+};
+
+int main(int argc, char *argv[])
+{
+	return login_binary_run(&managesieve_login_binary, argc, argv);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve-login/client.h
@@ -0,0 +1,74 @@
+#ifndef CLIENT_H
+#define CLIENT_H
+
+#include "net.h"
+#include "client-common.h"
+
+/* maximum length for managesieve command line. */
+#define MAX_MANAGESIEVE_LINE 8192
+
+enum managesieve_proxy_state {
+	MSIEVE_PROXY_STATE_NONE,
+	MSIEVE_PROXY_STATE_TLS_START,
+	MSIEVE_PROXY_STATE_TLS_READY,
+	MSIEVE_PROXY_STATE_XCLIENT,
+	MSIEVE_PROXY_STATE_AUTH,
+
+	MSIEVE_PROXY_STATE_COUNT
+};
+struct managesieve_command;
+
+struct managesieve_client {
+	struct client common;
+
+	const struct managesieve_login_settings *set;
+	struct managesieve_parser *parser;
+
+	enum managesieve_proxy_state proxy_state;
+
+	const char *cmd_name;
+	struct managesieve_command *cmd;
+
+	struct istream *auth_response_input;
+
+	bool cmd_finished:1;
+	bool cmd_parsed_args:1;
+	bool skip_line:1;
+	bool auth_mech_name_parsed:1;
+
+	bool proxy_starttls:1;
+	bool proxy_sasl:1;
+	bool proxy_xclient:1;
+};
+
+bool client_skip_line(struct managesieve_client *client);
+
+enum managesieve_cmd_reply {
+	MANAGESIEVE_CMD_REPLY_OK,
+	MANAGESIEVE_CMD_REPLY_NO,
+	MANAGESIEVE_CMD_REPLY_BYE
+};
+
+void client_send_reply(struct client *client,
+				   enum managesieve_cmd_reply reply, const char *text);
+
+void client_send_reply_code(struct client *client,
+				   enum managesieve_cmd_reply reply, const char *resp_code,
+				   const char *text);
+
+#define client_send_ok(client, text) \
+	client_send_reply(client, MANAGESIEVE_CMD_REPLY_OK, text)
+#define client_send_no(client, text) \
+	client_send_reply(client, MANAGESIEVE_CMD_REPLY_NO, text)
+#define client_send_bye(client, text) \
+	client_send_reply(client, MANAGESIEVE_CMD_REPLY_BYE, text)
+
+#define client_send_okresp(client, resp_code, text) \
+	client_send_reply_code(client, MANAGESIEVE_CMD_REPLY_OK, resp_code, text)
+#define client_send_noresp(client, resp_code, text) \
+	client_send_reply_code(client, MANAGESIEVE_CMD_REPLY_NO, resp_code, text)
+#define client_send_byeresp(client, resp_code, text) \
+	client_send_reply_code(client, MANAGESIEVE_CMD_REPLY_BYE, resp_code, text)
+
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve-login/managesieve-login-settings-plugin.c
@@ -0,0 +1,223 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "buffer.h"
+#include "env-util.h"
+#include "execv-const.h"
+#include "master-service.h"
+#include "settings-parser.h"
+#include "config-parser-private.h"
+#include "managesieve-login-settings-plugin.h"
+
+#include <stddef.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include <sysexits.h>
+
+typedef enum { CAP_SIEVE, CAP_NOTIFY } capability_type_t;
+
+bool capability_dumped = FALSE;
+static char *capability_sieve = NULL;
+static char *capability_notify = NULL;
+
+static void (*next_hook_config_parser_begin)(struct config_parser_context *ctx) = NULL;
+
+static void managesieve_login_config_parser_begin(struct config_parser_context *ctx);
+
+const char *managesieve_login_settings_version = DOVECOT_ABI_VERSION;
+
+void managesieve_login_settings_init(struct module *module ATTR_UNUSED)
+{
+	next_hook_config_parser_begin = hook_config_parser_begin;
+	hook_config_parser_begin = managesieve_login_config_parser_begin;
+}
+
+void managesieve_login_settings_deinit(void)
+{
+	hook_config_parser_begin = next_hook_config_parser_begin;
+
+	if ( capability_sieve != NULL )
+		i_free(capability_sieve);
+
+	if ( capability_notify != NULL )
+		i_free(capability_notify);
+}
+
+static void capability_store(capability_type_t cap_type, const char *value)
+{
+	switch ( cap_type ) {
+	case CAP_SIEVE:
+		capability_sieve = i_strdup(value);
+		break;
+	case CAP_NOTIFY:
+		capability_notify = i_strdup(value);
+		break;
+	}
+}
+
+static void capability_parse(const char *cap_string)
+{
+	capability_type_t cap_type = CAP_SIEVE;
+	const char *p = cap_string;
+	string_t *part = t_str_new(256);
+
+	if ( cap_string == NULL || *cap_string == '\0' ) {
+		i_warning("managesieve-login: capability string is empty.");
+		return;
+	}
+
+	while ( *p != '\0' ) {
+		if ( *p == '\\' ) {
+			p++;
+			if ( *p != '\0' ) {
+				str_append_c(part, *p);
+				p++;
+			} else break;
+		} else if ( *p == ':' ) {
+			if ( strcasecmp(str_c(part), "SIEVE") == 0 )
+				cap_type = CAP_SIEVE;
+			else if ( strcasecmp(str_c(part), "NOTIFY") == 0 )
+				cap_type = CAP_NOTIFY;
+			else
+				i_warning("managesieve-login: unknown capability '%s' listed in "
+					"capability string (ignored).", str_c(part));
+			str_truncate(part, 0);
+		} else if ( *p == ',' ) {
+			capability_store(cap_type, str_c(part));
+			str_truncate(part, 0);
+		} else {
+			/* Append character, but omit leading spaces */
+			if ( str_len(part) > 0 || *p != ' ' )
+				str_append_c(part, *p);
+		}
+		p++;
+	}
+
+	if ( str_len(part) > 0 ) {
+		capability_store(cap_type, str_c(part));
+	}
+}
+
+static bool capability_dump(void)
+{
+	char buf[4096];
+	int fd[2], status = 0;
+	ssize_t ret;
+	unsigned int pos;
+	pid_t pid;
+
+	if ( getenv("DUMP_CAPABILITY") != NULL )
+		return TRUE;
+
+	if ( pipe(fd) < 0 ) {
+		i_error("managesieve-login: dump-capability pipe() failed: %m");
+		return FALSE;
+	}
+	fd_close_on_exec(fd[0], TRUE);
+	fd_close_on_exec(fd[1], TRUE);
+
+	if ( (pid = fork()) == (pid_t)-1 ) {
+		(void)close(fd[0]); (void)close(fd[1]);
+		i_error("managesieve-login: dump-capability fork() failed: %m");
+		return FALSE;
+	}
+
+	if ( pid == 0 ) {
+		const char *argv[5];
+
+		/* Child */
+		(void)close(fd[0]);
+
+		if (dup2(fd[1], STDOUT_FILENO) < 0)
+			i_fatal("managesieve-login: dump-capability dup2() failed: %m");
+
+		env_put("DUMP_CAPABILITY=1");
+
+		argv[0] = PKG_LIBEXECDIR"/managesieve";
+		argv[1] = "-k";
+		argv[2] = "-c";
+		argv[3] = master_service_get_config_path(master_service);
+		argv[4] = NULL;
+		execv_const(argv[0], argv);
+
+		i_fatal("managesieve-login: dump-capability execv(%s) failed: %m", argv[0]);
+	}
+
+	(void)close(fd[1]);
+
+	alarm(60);
+	if (wait(&status) == -1) {
+		i_error("managesieve-login: dump-capability failed: process %d got stuck",
+			(int)pid);
+		return FALSE;
+	}
+	alarm(0);
+
+	if (status != 0) {
+		(void)close(fd[0]);
+		if (WIFSIGNALED(status)) {
+			i_error("managesieve-login: dump-capability process "
+				"killed with signal %d", WTERMSIG(status));
+		} else {
+			i_error("managesieve-login: dump-capability process returned %d",
+				WIFEXITED(status) ? WEXITSTATUS(status) : status);
+		}
+		return FALSE;
+	}
+
+	pos = 0;
+	while ((ret = read(fd[0], buf + pos, sizeof(buf) - pos)) > 0)
+		pos += ret;
+
+	if (ret < 0) {
+		i_error("managesieve-login: read(dump-capability process) failed: %m");
+		(void)close(fd[0]);
+		return FALSE;
+	}
+	(void)close(fd[0]);
+
+	if (pos == 0 || buf[pos-1] != '\n') {
+		i_error("managesieve-login: dump-capability: Couldn't read capability "
+			"(got %u bytes)", pos);
+		return FALSE;
+	}
+	buf[pos-1] = '\0';
+
+	capability_parse(buf);
+
+	return TRUE;
+}
+
+static void managesieve_login_config_set
+(struct config_parser_context *ctx, const char *key, const char *value)
+{
+	config_apply_line(ctx, key, t_strdup_printf("%s=%s", key, value), NULL);
+}
+
+static void managesieve_login_config_parser_begin(struct config_parser_context *ctx)
+{	
+	const char *const *module = ctx->modules;
+
+	if ( module != NULL && *module != NULL ) {
+		while ( *module != NULL ) {
+			if ( strcmp(*module, "managesieve-login") == 0 )
+				break;
+			module++;
+		}
+		if ( *module == NULL )
+			return;
+	}
+
+	if ( !capability_dumped ) {
+		(void)capability_dump();
+		capability_dumped = TRUE;
+	}
+
+	if ( capability_sieve != NULL )
+		managesieve_login_config_set(ctx, "managesieve_sieve_capability", capability_sieve);
+
+	if ( capability_notify != NULL )
+		managesieve_login_config_set(ctx, "managesieve_notify_capability", capability_notify);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve-login/managesieve-login-settings-plugin.h
@@ -0,0 +1,9 @@
+#ifndef MANAGESIEVE_LOGIN_SETTINGS_PLUGIN_H
+#define MANAGESIEVE_LOGIN_SETTINGS_PLUGIN_H
+
+#include "lib.h"
+
+void managesieve_login_settings_init(struct module *module);
+void managesieve_login_settings_deinit(void);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve-login/managesieve-login-settings.c
@@ -0,0 +1,103 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "buffer.h"
+#include "env-util.h"
+#include "execv-const.h"
+#include "settings-parser.h"
+#include "service-settings.h"
+#include "login-settings.h"
+
+#include "pigeonhole-config.h"
+
+#include "managesieve-login-settings.h"
+
+#include <stddef.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include <sysexits.h>
+
+/* <settings checks> */
+
+static struct inet_listener_settings managesieve_login_inet_listeners_array[] = {
+    { .name = "sieve", .address = "", .port = 4190 },
+};
+static struct inet_listener_settings *managesieve_login_inet_listeners[] = {
+	&managesieve_login_inet_listeners_array[0]
+};
+static buffer_t managesieve_login_inet_listeners_buf = {
+	managesieve_login_inet_listeners, sizeof(managesieve_login_inet_listeners), { 0, }
+};
+/* </settings checks> */
+
+struct service_settings managesieve_login_settings_service_settings = {
+	.name = "managesieve-login",
+	.protocol = "sieve",
+	.type = "login",
+	.executable = "managesieve-login",
+	.user = "$default_login_user",
+	.group = "",
+	.privileged_group = "",
+	.extra_groups = "",
+	.chroot = "login",
+
+	.drop_priv_before_exec = FALSE,
+
+	.process_min_avail = 0,
+	.process_limit = 0,
+	.client_limit = 0,
+	.service_count = 1,
+	.idle_kill = 0,
+	.vsz_limit = (uoff_t)-1,
+
+	.unix_listeners = ARRAY_INIT,
+	.fifo_listeners = ARRAY_INIT,
+	.inet_listeners = { { &managesieve_login_inet_listeners_buf,
+		sizeof(managesieve_login_inet_listeners[0]) } }
+};
+
+#undef DEF
+#define DEF(type, name) \
+	{ type, #name, offsetof(struct managesieve_login_settings, name), NULL }
+
+static const struct setting_define managesieve_login_setting_defines[] = {
+	DEF(SET_STR, managesieve_implementation_string),
+	DEF(SET_STR, managesieve_sieve_capability),
+	DEF(SET_STR, managesieve_notify_capability),
+
+	SETTING_DEFINE_LIST_END
+};
+
+static const struct managesieve_login_settings managesieve_login_default_settings = {
+	.managesieve_implementation_string = DOVECOT_NAME " " PIGEONHOLE_NAME,
+	.managesieve_sieve_capability = "",
+	.managesieve_notify_capability = NULL
+};
+
+static const struct setting_parser_info *managesieve_login_setting_dependencies[] = {
+	&login_setting_parser_info,
+	NULL
+};
+
+static const struct setting_parser_info managesieve_login_setting_parser_info = {
+	.module_name = "managesieve-login",
+	.defines = managesieve_login_setting_defines,
+	.defaults = &managesieve_login_default_settings,
+
+	.type_offset = (size_t)-1,
+	.struct_size = sizeof(struct managesieve_login_settings),
+
+	.parent_offset = (size_t)-1,
+	.parent = NULL,
+
+	.dependencies = managesieve_login_setting_dependencies
+};
+
+const struct setting_parser_info *managesieve_login_settings_set_roots[] = {
+	&login_setting_parser_info,
+	&managesieve_login_setting_parser_info,
+	NULL
+};
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve-login/managesieve-login-settings.h
@@ -0,0 +1,17 @@
+#ifndef MANAGESIEVE_LOGIN_SETTINGS_H
+#define MANAGESIEVE_LOGIN_SETTINGS_H
+
+struct managesieve_login_settings {
+	const char *managesieve_implementation_string;
+	const char *managesieve_sieve_capability;
+	const char *managesieve_notify_capability;
+};
+
+extern const struct setting_parser_info *managesieve_login_settings_set_roots[];
+
+#ifdef _CONFIG_PLUGIN
+void managesieve_login_settings_init(void);
+void managesieve_login_settings_deinit(void);
+#endif
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve-login/managesieve-proxy.c
@@ -0,0 +1,563 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include <string.h>
+#include "login-common.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "safe-memset.h"
+#include "buffer.h"
+#include "base64.h"
+#include "dsasl-client.h"
+
+#include "client.h"
+#include "client-authenticate.h"
+
+#include "managesieve-quote.h"
+#include "managesieve-proxy.h"
+#include "managesieve-parser.h"
+
+typedef enum {
+	MANAGESIEVE_RESPONSE_NONE,
+	MANAGESIEVE_RESPONSE_OK,
+	MANAGESIEVE_RESPONSE_NO,
+	MANAGESIEVE_RESPONSE_BYE
+} managesieve_response_t;
+
+static const char *managesieve_proxy_state_names[MSIEVE_PROXY_STATE_COUNT] = {
+	"none", "tls-start", "tls-ready", "xclient", "auth"
+};
+
+static void proxy_free_password(struct client *client)
+{
+	if (client->proxy_password == NULL)
+		return;
+
+	safe_memset(client->proxy_password, 0, strlen(client->proxy_password));
+	i_free_and_null(client->proxy_password);
+}
+
+static void proxy_write_xclient
+(struct managesieve_client *client, string_t *str)
+{
+	str_printfa(str,
+		"XCLIENT ADDR=%s PORT=%u SESSION=%s TTL=%u\r\n",
+		net_ip2addr(&client->common.ip),
+		client->common.remote_port,
+		client_get_session_id(&client->common),
+		client->common.proxy_ttl - 1);
+}
+
+static void proxy_write_auth_data
+(const unsigned char *data, unsigned int data_len,
+	string_t *str)
+{
+	if (data_len == 0)
+		str_append(str, "\"\"");
+	else {
+		string_t *data_str = t_str_new(128);
+		base64_encode(data, data_len, data_str);
+		managesieve_quote_append_string(str, str_c(data_str), FALSE);
+	}
+}
+
+static int proxy_write_auth
+(struct managesieve_client *client, string_t *str)
+{
+	struct dsasl_client_settings sasl_set;
+	const unsigned char *output;
+	size_t len;
+	const char *mech_name, *error;
+
+	i_assert(client->common.proxy_ttl > 1);
+
+	if ( !client->proxy_sasl ) {
+		/* Prevent sending credentials to a server that has login disabled;
+		   i.e., due to the lack of TLS */
+		client_log_err(&client->common, "proxy: "
+			"Server has disabled authentication (TLS required?)");
+		return -1;
+	}
+
+	if (client->common.proxy_mech == NULL)
+		client->common.proxy_mech = &dsasl_client_mech_plain;
+
+	i_assert(client->common.proxy_sasl_client == NULL);
+	i_zero(&sasl_set);
+	sasl_set.authid = client->common.proxy_master_user != NULL ?
+		client->common.proxy_master_user : client->common.proxy_user;
+	sasl_set.authzid = client->common.proxy_user;
+	sasl_set.password = client->common.proxy_password;
+	client->common.proxy_sasl_client =
+		dsasl_client_new(client->common.proxy_mech, &sasl_set);
+	mech_name = dsasl_client_mech_get_name(client->common.proxy_mech);
+
+	str_append(str, "AUTHENTICATE ");
+	managesieve_quote_append_string(str, mech_name, FALSE);
+	if (dsasl_client_output(client->common.proxy_sasl_client,
+				&output, &len, &error) < 0) {
+		client_log_err(&client->common, t_strdup_printf(
+			"proxy: SASL mechanism %s init failed: %s",
+			mech_name, error));
+		return -1;
+	}
+	if (len > 0) {
+		str_append_c(str, ' ');
+		proxy_write_auth_data(output, len, str);
+	}
+	str_append(str, "\r\n");
+	proxy_free_password(&client->common);
+	return 0;
+}
+
+static int proxy_input_auth_challenge
+(struct managesieve_client *client, const char *line,
+	const char **challenge_r)
+{
+	struct istream *input;
+	struct managesieve_parser *parser;
+ 	const struct managesieve_arg *args;
+	const char *challenge;
+	bool fatal = FALSE;
+	int ret;
+
+	i_assert(client->common.proxy_sasl_client != NULL);
+	*challenge_r = NULL;
+
+	/* Build an input stream for the managesieve parser
+	 *  FIXME: Ugly, see proxy_input_capability().
+	 */
+	line = t_strconcat(line, "\r\n", NULL);
+	input = i_stream_create_from_data(line, strlen(line));
+	parser = managesieve_parser_create(input, MAX_MANAGESIEVE_LINE);
+	managesieve_parser_reset(parser);
+
+	(void)i_stream_read(input);
+	ret = managesieve_parser_read_args(parser, 1, 0, &args);
+
+	if ( ret >= 0 ) {
+		if ( ret > 0 && managesieve_arg_get_string(&args[0], &challenge) ) {
+			*challenge_r = t_strdup(challenge);
+		} else {
+			client_log_err(&client->common, t_strdup_printf("proxy: "
+				"Server sent invalid SASL challenge line: %s",
+				str_sanitize(line,160)));
+			fatal = TRUE;
+		}
+
+	} else if ( ret == -2 ) {
+		/* Parser needs more data (not possible on mem stream) */
+		i_unreached();
+
+	} else {
+		const char *error_str = managesieve_parser_get_error(parser, &fatal);
+		error_str = (error_str != NULL ? error_str : "unknown (bug)" );
+
+		/* Do not accept faulty server */
+		client_log_err(&client->common, t_strdup_printf("proxy: "
+			"Protocol parse error(%d) int SASL challenge line: %s (line=`%s')",
+			ret, error_str, line));
+		fatal = TRUE;
+	}
+
+
+	/* Cleanup parser */
+  managesieve_parser_destroy(&parser);
+	i_stream_destroy(&input);
+
+	/* Time to exit if greeting was not accepted */
+	if ( fatal ) return -1;
+	return 0;
+}
+
+static int proxy_write_auth_response
+(struct managesieve_client *client,
+	const char *challenge, string_t *str)
+{
+	const unsigned char *data;
+	size_t data_len;
+	const char *error;
+	int ret;
+
+	if (base64_decode(challenge, strlen(challenge), NULL, str) < 0) {
+		client_log_err(&client->common,
+			"proxy: Server sent invalid base64 data in AUTHENTICATE response");
+		return -1;
+	}
+	ret = dsasl_client_input(client->common.proxy_sasl_client,
+				 str_data(str), str_len(str), &error);
+	if (ret == 0) {
+		ret = dsasl_client_output(client->common.proxy_sasl_client,
+					  &data, &data_len, &error);
+	}
+	if (ret < 0) {
+		client_log_err(&client->common, t_strdup_printf(
+			"proxy: Server sent invalid authentication data: %s",
+			error));
+		return -1;
+	}
+	i_assert(ret == 0);
+
+	str_truncate(str, 0);
+	proxy_write_auth_data(data, data_len, str);
+	str_append(str, "\r\n");
+	return 0;
+}
+
+static managesieve_response_t proxy_read_response
+(const struct managesieve_arg *args)
+{
+	const char *response;
+
+	if ( managesieve_arg_get_atom(&args[0], &response) ) {
+		if ( strcasecmp(response, "OK") == 0 ) {
+			/* Received OK response; greeting is finished */
+			return MANAGESIEVE_RESPONSE_OK;
+
+		} else if ( strcasecmp(response, "NO") == 0 ) {
+			/* Received OK response; greeting is finished */
+			return MANAGESIEVE_RESPONSE_NO;
+
+		} else if ( strcasecmp(response, "BYE") == 0 ) {
+			/* Received OK response; greeting is finished */
+			return MANAGESIEVE_RESPONSE_BYE;
+		}
+	}
+	return MANAGESIEVE_RESPONSE_NONE;
+}
+
+static int proxy_input_capability
+(struct managesieve_client *client, const char *line,
+	managesieve_response_t *resp_r)
+{
+	struct istream *input;
+	struct managesieve_parser *parser;
+ 	const struct managesieve_arg *args;
+	const char *capability;
+	int ret;
+	bool fatal = FALSE;
+
+	*resp_r = MANAGESIEVE_RESPONSE_NONE;
+
+	/* Build an input stream for the managesieve parser
+	 *  FIXME: It would be nice if the line-wise parsing could be
+	 *    substituded by something similar to the command line interpreter.
+	 *    However, the current login_proxy structure does not make streams
+	 *    known until inside proxy_input handler.
+	 */
+	line = t_strconcat(line, "\r\n", NULL);
+	input = i_stream_create_from_data(line, strlen(line));
+	parser = managesieve_parser_create(input, MAX_MANAGESIEVE_LINE);
+	managesieve_parser_reset(parser);
+
+	/* Parse input
+	 *  FIXME: Theoretically the OK response could include a
+	 *   response code which could be rejected by the parser.
+	 */
+	(void)i_stream_read(input);
+	ret = managesieve_parser_read_args(parser, 2, 0, &args);
+
+	if ( ret == 0 ) {
+		client_log_err(&client->common, t_strdup_printf("proxy: "
+			"Remote returned with invalid capability/greeting line: %s",
+			str_sanitize(line,160)));
+		fatal = TRUE;
+	} else if ( ret > 0 ) {
+		if ( args[0].type == MANAGESIEVE_ARG_ATOM ) {
+			*resp_r = proxy_read_response(args);
+
+			if ( *resp_r == MANAGESIEVE_RESPONSE_NONE ) {
+				client_log_err(&client->common, t_strdup_printf("proxy: "
+					"Remote sent invalid response: %s",
+					str_sanitize(line,160)));
+
+				fatal = TRUE;
+			}
+		} else if ( managesieve_arg_get_string(&args[0], &capability) ) {
+			if ( strcasecmp(capability, "SASL") == 0 ) {
+				const char *sasl_mechs;
+
+				/* Check whether the server supports the SASL mechanism
+				 * we are going to use (currently only PLAIN supported).
+				 */
+				if ( ret == 2 && managesieve_arg_get_string(&args[1], &sasl_mechs) ) {
+					const char *const *mechs = t_strsplit(sasl_mechs, " ");
+
+					if ( *mechs != NULL ) {
+						/* At least one SASL mechanism is supported */
+						client->proxy_sasl = TRUE;
+					}
+
+				} else {
+					client_log_err(&client->common, "proxy: "
+		         		"Server returned erroneous SASL capability");
+					fatal = TRUE;
+				}
+
+			} else if ( strcasecmp(capability, "STARTTLS") == 0 ) {
+				client->proxy_starttls = TRUE;
+			} else if ( strcasecmp(capability, "XCLIENT") == 0 ) {
+				client->proxy_xclient = TRUE;
+			}
+
+		} else {
+			/* Do not accept faulty server */
+			client_log_err(&client->common, t_strdup_printf("proxy: "
+				"Remote returned with invalid capability/greeting line: %s",
+				str_sanitize(line,160)));
+			fatal = TRUE;
+		}
+
+	} else if ( ret == -2 ) {
+		/* Parser needs more data (not possible on mem stream) */
+		i_unreached();
+
+	} else {
+		const char *error_str = managesieve_parser_get_error(parser, &fatal);
+		error_str = (error_str != NULL ? error_str : "unknown (bug)" );
+
+		/* Do not accept faulty server */
+		client_log_err(&client->common, t_strdup_printf("proxy: "
+			"Protocol parse error(%d) in capability/greeting line: %s (line=`%s')",
+			ret, error_str, line));
+		fatal = TRUE;
+	}
+
+	/* Cleanup parser */
+	managesieve_parser_destroy(&parser);
+	i_stream_destroy(&input);
+
+	/* Time to exit if greeting was not accepted */
+	if ( fatal ) return -1;
+
+	/* Wait until greeting is received completely */
+	if ( *resp_r == MANAGESIEVE_RESPONSE_NONE ) return 1;
+
+	return 0;
+}
+
+int managesieve_proxy_parse_line(struct client *client, const char *line)
+{
+	struct managesieve_client *msieve_client =
+		(struct managesieve_client *) client;
+	struct ostream *output;
+	enum login_proxy_ssl_flags ssl_flags;
+	managesieve_response_t response = MANAGESIEVE_RESPONSE_NONE;
+	string_t *command;
+	int ret = 0;
+
+	i_assert(!client->destroyed);
+
+	output = login_proxy_get_ostream(client->login_proxy);
+	switch ( msieve_client->proxy_state ) {
+	case MSIEVE_PROXY_STATE_NONE:
+		if ( (ret=proxy_input_capability
+			(msieve_client, line, &response)) < 0 ) {
+			client_proxy_failed(client, TRUE);
+			return -1;
+		}
+
+		if ( ret == 0 ) {
+			if ( response != MANAGESIEVE_RESPONSE_OK ) {
+				client_log_err(client, "proxy: "
+					"Remote sent unexpected NO/BYE instead of capability response");
+				client_proxy_failed(client, TRUE);
+				return -1;
+			}
+
+			command = t_str_new(128);
+
+			ssl_flags = login_proxy_get_ssl_flags(client->login_proxy);
+			if ((ssl_flags & PROXY_SSL_FLAG_STARTTLS) != 0) {
+				if ( !msieve_client->proxy_starttls ) {
+					client_log_err(client, "proxy: Remote doesn't support STARTTLS");
+						client_proxy_failed(client, TRUE);
+					return -1;
+				}
+
+				str_append(command, "STARTTLS\r\n");
+				msieve_client->proxy_state = MSIEVE_PROXY_STATE_TLS_START;
+
+			} else if (msieve_client->proxy_xclient) {
+				proxy_write_xclient(msieve_client, command);
+				msieve_client->proxy_state = MSIEVE_PROXY_STATE_XCLIENT;
+
+			} else {
+				if ( proxy_write_auth(msieve_client, command) < 0 ) {
+					client_proxy_failed(client, TRUE);
+					return -1;
+				}
+				msieve_client->proxy_state = MSIEVE_PROXY_STATE_AUTH;
+			}
+
+			o_stream_nsend(output, str_data(command), str_len(command));
+		}
+		return 0;
+
+	case MSIEVE_PROXY_STATE_TLS_START:
+		if ( strncasecmp(line, "OK", 2) == 0 &&
+			( strlen(line) == 2 || line[2] == ' ' ) ) {
+
+			/* STARTTLS successful, begin TLS negotiation. */
+			if ( login_proxy_starttls(client->login_proxy) < 0 ) {
+				client_proxy_failed(client, TRUE);
+				return -1;
+			}
+
+			msieve_client->proxy_sasl = FALSE;
+			msieve_client->proxy_xclient = FALSE;
+			msieve_client->proxy_state = MSIEVE_PROXY_STATE_TLS_READY;
+			return 1;
+		}
+
+		client_log_err(client, "proxy: Remote refused STARTTLS command");
+		client_proxy_failed(client, TRUE);
+		return -1;
+
+	case MSIEVE_PROXY_STATE_TLS_READY:
+		if ( (ret=proxy_input_capability(msieve_client, line, &response)) < 0 ) {
+			client_proxy_failed(client, TRUE);
+			return -1;
+		}
+
+		if ( ret == 0 ) {
+			if ( response != MANAGESIEVE_RESPONSE_OK ) {
+				/* STARTTLS failed */
+				client_log_err(client,
+					t_strdup_printf("proxy: Remote STARTTLS failed: %s",
+						str_sanitize(line, 160)));
+				client_proxy_failed(client, TRUE);
+				return -1;
+			}
+
+			command = t_str_new(128);
+			if ( msieve_client->proxy_xclient ) {
+				proxy_write_xclient(msieve_client, command);
+				msieve_client->proxy_state = MSIEVE_PROXY_STATE_XCLIENT;
+
+			} else {
+				if ( proxy_write_auth(msieve_client, command) < 0 ) {
+					client_proxy_failed(client, TRUE);
+					return -1;
+				}
+				msieve_client->proxy_state = MSIEVE_PROXY_STATE_AUTH;
+			}
+			o_stream_nsend(output, str_data(command), str_len(command));
+		}
+		return 0;
+
+	case MSIEVE_PROXY_STATE_XCLIENT:
+		if ( strncasecmp(line, "OK", 2) == 0 &&
+			( strlen(line) == 2 || line[2] == ' ' ) ) {
+
+			command = t_str_new(128);
+			if ( proxy_write_auth(msieve_client, command) < 0 ) {
+				client_proxy_failed(client, TRUE);
+				return -1;
+			}
+			o_stream_nsend(output, str_data(command), str_len(command));
+			msieve_client->proxy_state = MSIEVE_PROXY_STATE_AUTH;
+			return 0;
+		}
+
+		client_log_err(client, t_strdup_printf(
+			"proxy: Remote XCLIENT failed: %s",
+			str_sanitize(line, 160)));
+		client_proxy_failed(client, TRUE);
+		return -1;
+
+	case MSIEVE_PROXY_STATE_AUTH:
+		/* Challenge? */
+		if ( *line == '"' ) {
+			const char *challenge;
+
+			if ( proxy_input_auth_challenge
+				(msieve_client, line, &challenge) < 0 ) {
+				client_proxy_failed(client, TRUE);
+				return -1;
+			}
+			command = t_str_new(128);
+			if ( proxy_write_auth_response
+				(msieve_client, challenge, command) < 0 ) {
+				client_proxy_failed(client, TRUE);
+				return -1;
+			}
+			o_stream_nsend(output, str_data(command), str_len(command));
+			return 0;
+		}
+
+		/* Check login status */
+		if ( strncasecmp(line, "OK", 2) == 0 &&
+			(strlen(line) == 2 || line[2] == ' ') ) {
+			string_t *str = t_str_new(128);
+
+			/* Login successful */
+
+			/* FIXME: some SASL mechanisms cause a capability response to be sent */
+
+			/* Send this line to client. */
+			str_append(str, line );
+			str_append(str, "\r\n");
+			o_stream_nsend(client->output, str_data(str), str_len(str));
+
+			(void)client_skip_line(msieve_client);
+			client_proxy_finish_destroy_client(client);
+			return 1;
+		}
+
+		/* Authentication failed */
+		if ( client->set->auth_verbose ) {
+			const char *log_line = line;
+
+			if (strncasecmp(log_line, "NO ", 3) == 0)
+				log_line += 3;
+			client_proxy_log_failure(client, log_line);
+		}
+
+		/* FIXME: properly parse and handle response codes */
+
+		/* Login failed. Send our own failure reply so client can't
+		 * figure out if user exists or not just by looking at the
+		 * reply string.
+		 */
+		client_send_no(client, AUTH_FAILED_MSG);
+
+		client->proxy_auth_failed = TRUE;
+		client_proxy_failed(client, FALSE);
+		return -1;
+
+	default:
+		/* Not supposed to happen */
+		break;
+	}
+
+	i_unreached();
+	return -1;
+}
+
+void managesieve_proxy_reset(struct client *client)
+{
+	struct managesieve_client *msieve_client =
+		(struct managesieve_client *) client;
+
+	msieve_client->proxy_starttls = FALSE;
+	msieve_client->proxy_sasl = FALSE;
+	msieve_client->proxy_xclient = FALSE;
+	msieve_client->proxy_state = MSIEVE_PROXY_STATE_NONE;
+}
+
+void managesieve_proxy_error(struct client *client, const char *text)
+{
+	client_send_reply_code(client, MANAGESIEVE_CMD_REPLY_NO, "TRYLATER", text);
+}
+
+const char *managesieve_proxy_get_state(struct client *client)
+{
+	struct managesieve_client *msieve_client =
+		(struct managesieve_client *) client;
+
+	return managesieve_proxy_state_names[msieve_client->proxy_state];
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve-login/managesieve-proxy.h
@@ -0,0 +1,10 @@
+#ifndef MANAGESIEVE_PROXY_H
+#define MANAGESIEVE_PROXY_H
+
+void managesieve_proxy_reset(struct client *client);
+int managesieve_proxy_parse_line(struct client *client, const char *line);
+
+void managesieve_proxy_error(struct client *client, const char *text);
+const char *managesieve_proxy_get_state(struct client *client);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve/Makefile.am
@@ -0,0 +1,59 @@
+settingsdir = $(dovecot_moduledir)/settings
+
+dovecot_pkglibexec_PROGRAMS = managesieve
+
+AM_CPPFLAGS = \
+	$(LIBDOVECOT_INCLUDE) \
+	$(LIBDOVECOT_SERVICE_INCLUDE) \
+	-DMODULEDIR=\""$(dovecot_moduledir)"\" \
+	-I$(top_srcdir) \
+	-I$(top_srcdir)/src/lib-sieve \
+	-I$(top_srcdir)/src/lib-managesieve
+
+libmanagesieve_settings_la_LDFLAGS = -module -avoid-version
+
+settings_LTLIBRARIES = \
+	libmanagesieve_settings.la
+
+libmanagesieve_settings_la_SOURCES = \
+	managesieve-settings.c
+
+libs = \
+	managesieve-settings.lo \
+	$(top_builddir)/src/lib-managesieve/libmanagesieve.la \
+	$(top_builddir)/src/lib-sieve/libdovecot-sieve.la
+
+managesieve_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS)
+managesieve_LDFLAGS = -export-dynamic $(BINARY_LDFLAGS)
+
+managesieve_LDADD = $(libs) $(LIBDOVECOT_STORAGE) $(LIBDOVECOT_LDA) $(LIBDOVECOT)
+
+managesieve_DEPENDENCIES = $(libs) $(LIBDOVECOT_STORAGE_DEPS) $(LIBDOVECOT_LDA_DEPS) $(LIBDOVECOT_DEPS)
+
+cmds = \
+	cmd-capability.c \
+	cmd-logout.c \
+	cmd-putscript.c \
+	cmd-getscript.c \
+	cmd-setactive.c \
+	cmd-deletescript.c \
+	cmd-listscripts.c \
+	cmd-havespace.c \
+	cmd-renamescript.c \
+	cmd-noop.c
+
+managesieve_SOURCES = \
+	$(cmds) \
+	managesieve-quota.c \
+	managesieve-client.c \
+	managesieve-commands.c \
+	managesieve-capabilities.c \
+	main.c
+
+noinst_HEADERS = \
+	managesieve-quota.h \
+	managesieve-client.h \
+	managesieve-commands.h \
+	managesieve-capabilities.h \
+	managesieve-settings.h \
+	managesieve-common.h
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve/cmd-capability.c
@@ -0,0 +1,61 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "strfuncs.h"
+#include "ostream.h"
+
+#include "sieve.h"
+
+#include "managesieve-common.h"
+#include "managesieve-commands.h"
+
+bool cmd_capability(struct client_command_context *cmd)
+{
+	struct client *client = cmd->client;
+	const char *sievecap, *notifycap;
+	unsigned int max_redirects;
+
+	/* no arguments */
+	if ( !client_read_no_args(cmd) )
+		return FALSE;
+
+	o_stream_cork(client->output);
+
+	T_BEGIN {
+		/* Get capabilities */
+		sievecap = sieve_get_capabilities(client->svinst, NULL);
+		notifycap = sieve_get_capabilities(client->svinst, "notify");
+		max_redirects = sieve_max_redirects(client->svinst);
+
+		/* Default capabilities */
+  		client_send_line(client, t_strconcat("\"IMPLEMENTATION\" \"",
+			client->set->managesieve_implementation_string, "\"", NULL));
+		client_send_line(client, t_strconcat("\"SIEVE\" \"",
+			( sievecap == NULL ? "" : sievecap ), "\"", NULL));
+
+		/* Maximum number of redirects (if limited) */
+		if ( max_redirects > 0 )
+			client_send_line(client,
+				t_strdup_printf("\"MAXREDIRECTS\" \"%u\"", max_redirects));
+
+		/* Notify methods */
+		if ( notifycap != NULL ) {
+			client_send_line(client, t_strconcat("\"NOTIFY\" \"",
+				notifycap, "\"", NULL));
+		}
+
+		/* Protocol version */
+		client_send_line(client, "\"VERSION\" \"1.0\"");
+
+		/* Finish */
+		client_send_line(client, "OK \"Capability completed.\"");
+	} T_END;
+
+	o_stream_uncork(client->output);
+
+	return TRUE;
+
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve/cmd-deletescript.c
@@ -0,0 +1,40 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve.h"
+#include "sieve-script.h"
+#include "sieve-storage.h"
+
+#include "managesieve-common.h"
+#include "managesieve-commands.h"
+
+bool cmd_deletescript(struct client_command_context *cmd)
+{
+	struct client *client = cmd->client;
+	struct sieve_storage *storage = client->storage;
+	const char *scriptname;
+	struct sieve_script *script;
+
+	/* <scrip name>*/
+	if ( !client_read_string_args(cmd, TRUE, 1, &scriptname) )
+		return FALSE;
+
+	script = sieve_storage_open_script
+		(storage, scriptname, NULL);
+	if ( script == NULL ) {
+		client_send_storage_error(client, storage);
+		return TRUE;
+	}
+
+	if ( sieve_script_delete(script, FALSE) < 0 ) {
+		client_send_storage_error(client, storage);
+	} else {
+		client->deleted_count++;
+		client_send_ok(client, "Deletescript completed.");
+	}
+
+	sieve_script_unref(&script);
+	return TRUE;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve/cmd-getscript.c
@@ -0,0 +1,141 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "ostream.h"
+#include "istream.h"
+#include "iostream.h"
+
+#include "sieve-script.h"
+#include "sieve-storage.h"
+
+#include "managesieve-common.h"
+#include "managesieve-commands.h"
+
+struct cmd_getscript_context {
+	struct client *client;
+	struct client_command_context *cmd;
+	struct sieve_storage *storage;
+	uoff_t script_size;
+
+	struct sieve_script *script;
+	struct istream *script_stream;
+
+	bool failed:1;
+};
+
+static bool cmd_getscript_finish(struct cmd_getscript_context *ctx)
+{
+	struct client *client = ctx->client;
+
+	if ( ctx->script != NULL )
+		sieve_script_unref(&ctx->script);
+
+	if ( ctx->failed ) {
+		if ( client->output->closed ) {
+			client_disconnect(client, "Disconnected");
+			return TRUE;
+		}
+
+		client_send_storage_error(client, client->storage);
+		return TRUE;
+	}
+
+	client->get_count++;
+	client->get_bytes += ctx->script_size;
+
+	client_send_line(client, "");
+	client_send_ok(client, "Getscript completed.");
+	return TRUE;
+}
+
+static bool cmd_getscript_continue(struct client_command_context *cmd)
+{
+	struct client *client = cmd->client;
+	struct cmd_getscript_context *ctx = cmd->context;
+
+	switch (o_stream_send_istream(client->output, ctx->script_stream)) {
+	case OSTREAM_SEND_ISTREAM_RESULT_FINISHED:
+		if ( ctx->script_stream->v_offset != ctx->script_size && !ctx->failed ) {
+			/* Input stream gave less data than expected */
+			sieve_storage_set_critical(ctx->storage,
+				"GETSCRIPT for script `%s' from %s got too little data: "
+				"%"PRIuUOFF_T" vs %"PRIuUOFF_T, sieve_script_name(ctx->script),
+				sieve_script_location(ctx->script), ctx->script_stream->v_offset, ctx->script_size);
+
+			client_disconnect(ctx->client, "GETSCRIPT failed");
+			ctx->failed = TRUE;
+		}
+		break;
+	case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT:
+		i_unreached();
+	case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT:
+		return FALSE;
+	case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT:
+		sieve_storage_set_critical(ctx->storage,
+			"o_stream_send_istream() failed for script `%s' from %s: %s",
+			sieve_script_name(ctx->script),
+			sieve_script_location(ctx->script),
+			i_stream_get_error(ctx->script_stream));
+		ctx->failed = TRUE;
+		break;
+	case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT:
+		client_disconnect(ctx->client,
+			io_stream_get_disconnect_reason(client->input, client->output));
+		ctx->failed = TRUE;
+		break;
+	}
+	return cmd_getscript_finish(ctx);
+}
+
+bool cmd_getscript(struct client_command_context *cmd)
+{
+	struct client *client = cmd->client;
+	struct cmd_getscript_context *ctx;
+	const char *scriptname;
+	enum sieve_error error;
+
+	/* <scriptname> */
+	if ( !client_read_string_args(cmd, TRUE, 1, &scriptname) )
+		return FALSE;
+
+	ctx = p_new(cmd->pool, struct cmd_getscript_context, 1);
+	ctx->cmd = cmd;
+	ctx->client = client;
+	ctx->storage = client->storage;
+	ctx->failed = FALSE;
+
+	ctx->script = sieve_storage_open_script
+		(client->storage, scriptname, NULL);
+	if (ctx->script == NULL) {
+		ctx->failed = TRUE;
+		return cmd_getscript_finish(ctx);
+	}
+
+	if ( sieve_script_get_stream
+		(ctx->script, &ctx->script_stream, &error) < 0 ) {
+		if ( error == SIEVE_ERROR_NOT_FOUND )
+			sieve_storage_set_error(client->storage, error, "Script does not exist.");
+		ctx->failed = TRUE;
+		return cmd_getscript_finish(ctx);
+	}
+
+	if ( sieve_script_get_size(ctx->script, &ctx->script_size) <= 0 ) {
+		sieve_storage_set_critical(ctx->storage,
+			"failed to obtain script size for script `%s' from %s",
+			sieve_script_name(ctx->script), sieve_script_location(ctx->script));
+		ctx->failed = TRUE;
+		return cmd_getscript_finish(ctx);
+	}
+
+	i_assert(ctx->script_stream->v_offset == 0);
+
+	client_send_line
+		(client, t_strdup_printf("{%"PRIuUOFF_T"}", ctx->script_size));
+
+	client->command_pending = TRUE;
+	cmd->func = cmd_getscript_continue;
+	cmd->context = ctx;
+
+	return cmd_getscript_continue(cmd);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve/cmd-havespace.c
@@ -0,0 +1,52 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "managesieve-common.h"
+#include "managesieve-commands.h"
+
+#include "sieve.h"
+#include "sieve-script.h"
+#include "sieve-storage.h"
+
+#include "managesieve-client.h"
+#include "managesieve-quota.h"
+
+bool cmd_havespace(struct client_command_context *cmd)
+{
+	struct client *client = cmd->client;
+	const struct managesieve_arg *args;
+	const char *scriptname;
+	uoff_t size;
+
+	/* <scriptname> <size> */
+	if ( !client_read_args(cmd, 2, 0, TRUE, &args) )
+	  return FALSE;
+
+	if ( !managesieve_arg_get_string(&args[0], &scriptname) ) {
+		client_send_no(client, "Invalid string for scriptname.");
+		return TRUE;
+	}
+
+	if ( !managesieve_arg_get_number(&args[1], &size) ) {
+		client_send_no(client, "Invalid scriptsize argument.");
+		return TRUE;
+	}
+
+	if ( !sieve_script_name_is_valid(scriptname) ) {
+		client_send_no(client, "Invalid script name.");
+		return TRUE;
+	}
+
+	if ( size == 0 ) {
+		client_send_no(client, "Cannot upload empty script.");
+		return TRUE;
+	}
+
+	if ( !managesieve_quota_check_all(client, scriptname, size) )
+		return TRUE;
+
+	client_send_ok(client, "Putscript would succeed.");
+	return TRUE;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve/cmd-listscripts.c
@@ -0,0 +1,57 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve.h"
+#include "sieve-storage.h"
+
+#include "managesieve-quote.h"
+
+#include "managesieve-common.h"
+#include "managesieve-commands.h"
+
+bool cmd_listscripts(struct client_command_context *cmd)
+{
+  struct client *client = cmd->client;
+	struct sieve_storage_list_context *ctx;
+	const char *scriptname;
+	bool active;
+	string_t *str;
+
+	/* no arguments */
+	if ( !client_read_no_args(cmd) )
+		return FALSE;
+
+	if ( (ctx = sieve_storage_list_init(client->storage))
+		== NULL ) {
+		client_send_storage_error(client, client->storage);
+		return TRUE;
+	}
+
+	/* FIXME: This will be quite slow for large script lists. Implement
+	 * some buffering to fix this. Wont truely be an issue with managesieve
+	 * though.
+	 */
+	while ((scriptname = sieve_storage_list_next(ctx, &active)) != NULL) {
+		T_BEGIN {
+			str = t_str_new(128);
+
+			managesieve_quote_append_string(str, scriptname, FALSE);
+
+			if ( active )
+				str_append(str, " ACTIVE");
+
+			client_send_line(client, str_c(str));
+		} T_END;
+	}
+
+	if ( sieve_storage_list_deinit(&ctx) < 0 ) {
+		client_send_storage_error(client, client->storage);
+		return TRUE;
+	}
+
+	client_send_ok(client, "Listscripts completed.");
+	return TRUE;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve/cmd-logout.c
@@ -0,0 +1,21 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "ostream.h"
+
+#include "managesieve-common.h"
+#include "managesieve-commands.h"
+
+bool cmd_logout(struct client_command_context *cmd)
+{
+	struct client *client = cmd->client;
+
+	/* no arguments */
+	if ( !client_read_no_args(cmd) )
+		return FALSE;
+
+	client_send_line(client, "OK \"Logout completed.\"");
+	client_disconnect(client, "Logged out");
+	return TRUE;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve/cmd-noop.c
@@ -0,0 +1,46 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+
+#include "managesieve-quote.h"
+
+#include "managesieve-common.h"
+#include "managesieve-commands.h"
+
+
+bool cmd_noop(struct client_command_context *cmd)
+{
+	struct client *client = cmd->client;
+	const struct managesieve_arg *args;
+	const char *text;
+	string_t *resp_code;
+
+	/* [<echo string>] */
+	if ( !client_read_args(cmd, 0, 0, FALSE, &args) )
+		return FALSE;
+
+	if ( MANAGESIEVE_ARG_IS_EOL(&args[0]) ) {
+		client_send_ok(client, "NOOP Completed");
+		return TRUE;
+	}
+
+	if ( !managesieve_arg_get_string(&args[0], &text) ) {
+		client_send_no(client, "Invalid echo tag.");
+		return TRUE;
+	}
+
+	if ( !MANAGESIEVE_ARG_IS_EOL(&args[1]) ) {
+		client_send_command_error(cmd, "Too many arguments.");
+		return TRUE;
+	}
+
+	resp_code = t_str_new(256);
+	str_append(resp_code, "TAG ");
+	managesieve_quote_append_string(resp_code, text, FALSE);
+
+	client_send_okresp(client, str_c(resp_code), "Done");
+	return TRUE;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve/cmd-putscript.c
@@ -0,0 +1,493 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* NOTE: this file also contains the checkscript command due to its obvious
+ * similarities.
+ */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "str.h"
+
+#include "sieve.h"
+#include "sieve-script.h"
+#include "sieve-storage.h"
+
+#include "managesieve-parser.h"
+
+#include "managesieve-common.h"
+#include "managesieve-client.h"
+#include "managesieve-commands.h"
+#include "managesieve-quota.h"
+
+#include <sys/time.h>
+
+struct cmd_putscript_context {
+	struct client *client;
+	struct client_command_context *cmd;
+	struct sieve_storage *storage;
+
+	struct istream *input;
+
+	const char *scriptname;
+	uoff_t script_size, max_script_size;
+
+	struct managesieve_parser *save_parser;
+	struct sieve_storage_save_context *save_ctx;
+
+	bool script_size_valid:1;
+};
+
+static void cmd_putscript_finish(struct cmd_putscript_context *ctx);
+static bool cmd_putscript_continue_script(struct client_command_context *cmd);
+
+static void client_input_putscript(struct client *client)
+{
+	struct client_command_context *cmd = &client->cmd;
+
+	i_assert(!client->destroyed);
+
+	client->last_input = ioloop_time;
+	timeout_reset(client->to_idle);
+
+	switch (i_stream_read(client->input)) {
+	case -1:
+		/* disconnected */
+		cmd_putscript_finish(cmd->context);
+		/* Reset command so that client_destroy() doesn't try to call
+		   cmd_putscript_continue_script() anymore. */
+		_client_reset_command(client);
+		client_destroy(client, "Disconnected in PUTSCRIPT/CHECKSCRIPT");
+		return;
+	case -2:
+		cmd_putscript_finish(cmd->context);
+		if (client->command_pending) {
+			/* uploaded script data, this is handled internally by
+			   mailbox_save_continue() */
+			break;
+		}
+
+		/* parameter word is longer than max. input buffer size.
+		   this is most likely an error, so skip the new data
+		   until newline is found. */
+		client->input_skip_line = TRUE;
+
+		client_send_command_error(cmd, "Too long argument.");
+		cmd->param_error = TRUE;
+		_client_reset_command(client);
+		return;
+	}
+
+	if (cmd->func(cmd)) {
+		/* command execution was finished. Note that if cmd_sync()
+		   didn't finish, we didn't get here but the input handler
+		   has already been moved. So don't do anything important
+		   here..
+
+		   reset command once again to reset cmd_sync()'s changes. */
+		_client_reset_command(client);
+
+		if (client->input_pending)
+			client_input(client);
+	}
+}
+
+static void cmd_putscript_finish(struct cmd_putscript_context *ctx)
+{
+	managesieve_parser_destroy(&ctx->save_parser);
+
+	io_remove(&ctx->client->io);
+	o_stream_set_flush_callback(ctx->client->output,
+				    client_output, ctx->client);
+
+	if (ctx->save_ctx != NULL)
+	{
+		ctx->client->input_skip_line = TRUE;
+		sieve_storage_save_cancel(&ctx->save_ctx);
+	}
+}
+
+static bool cmd_putscript_continue_cancel(struct client_command_context *cmd)
+{
+	struct cmd_putscript_context *ctx = cmd->context;
+	size_t size;
+
+	(void)i_stream_read(ctx->input);
+	(void)i_stream_get_data(ctx->input, &size);
+	i_stream_skip(ctx->input, size);
+
+	if ( cmd->client->input->closed || ctx->input->eof ||
+		ctx->input->v_offset == ctx->script_size ) {
+		cmd_putscript_finish(ctx);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static bool cmd_putscript_cancel(struct cmd_putscript_context *ctx, bool skip)
+{
+	ctx->client->input_skip_line = TRUE;
+
+	if ( !skip ) {
+		cmd_putscript_finish(ctx);
+		return TRUE;
+	}
+
+	/* we have to read the nonsynced literal so we don't treat the uploaded script
+	   as commands. */
+	ctx->client->command_pending = TRUE;
+	ctx->cmd->func = cmd_putscript_continue_cancel;
+	ctx->cmd->context = ctx;
+	return cmd_putscript_continue_cancel(ctx->cmd);
+}
+
+static bool cmd_putscript_finish_parsing(struct client_command_context *cmd)
+{
+	struct client *client = cmd->client;
+	struct cmd_putscript_context *ctx = cmd->context;
+	const struct managesieve_arg *args;
+	int ret;
+
+	/* if error occurs, the CRLF is already read. */
+	client->input_skip_line = FALSE;
+
+	/* <script literal> */
+	ret = managesieve_parser_read_args(ctx->save_parser, 0, 0, &args);
+	if (ret == -1 || client->output->closed) {
+		if (ctx->storage != NULL) {
+			const char *msg;
+			bool fatal ATTR_UNUSED;
+
+			msg = managesieve_parser_get_error(ctx->save_parser, &fatal);
+			client_send_command_error(cmd, msg);
+		}
+		cmd_putscript_finish(ctx);
+		return TRUE;
+	}
+	if (ret < 0) {
+		/* need more data */
+		return FALSE;
+	}
+
+	if ( MANAGESIEVE_ARG_IS_EOL(&args[0]) ) {
+		struct sieve_script *script;
+		bool success = TRUE;
+
+		/* Eat away the trailing CRLF */
+		client->input_skip_line = TRUE;
+
+		/* Obtain script object for uploaded script */
+		script = sieve_storage_save_get_tempscript(ctx->save_ctx);
+
+		/* Check result */
+		if ( script == NULL ) {
+			client_send_storage_error(client, ctx->storage);
+			cmd_putscript_finish(ctx);
+			return TRUE;
+		}
+
+		/* If quoted string, the size was not known until now */
+		if ( !ctx->script_size_valid ) {
+			if (sieve_script_get_size(script, &ctx->script_size) < 0) {
+				client_send_storage_error(client, ctx->storage);
+				cmd_putscript_finish(ctx);
+				return TRUE;
+			}
+			ctx->script_size_valid = TRUE;
+
+			/* Check quota; max size is already checked */
+			if ( ctx->scriptname != NULL && !managesieve_quota_check_all
+					(client, ctx->scriptname, ctx->script_size) ) {
+				cmd_putscript_finish(ctx);
+				return TRUE;
+			}
+		}
+
+		/* Try to compile script */
+		T_BEGIN {
+			struct sieve_error_handler *ehandler;
+			enum sieve_compile_flags cpflags =
+				SIEVE_COMPILE_FLAG_NOGLOBAL | SIEVE_COMPILE_FLAG_UPLOADED;
+			struct sieve_binary *sbin;
+			enum sieve_error error;
+			string_t *errors;
+
+			/* Mark this as an activation when we are replacing the active script */
+			if ( sieve_storage_save_will_activate(ctx->save_ctx) ) {
+				cpflags |= SIEVE_COMPILE_FLAG_ACTIVATED;
+			}
+
+			/* Prepare error handler */
+			errors = str_new(default_pool, 1024);
+			ehandler = sieve_strbuf_ehandler_create(client->svinst, errors, TRUE,
+				client->set->managesieve_max_compile_errors);
+
+			/* Compile */
+			if ( (sbin=sieve_compile_script
+				(script, ehandler, cpflags, &error)) == NULL ) {
+				if ( error != SIEVE_ERROR_NOT_VALID ) {
+					const char *errormsg =
+						sieve_script_get_last_error(script, &error);
+					if ( error != SIEVE_ERROR_NONE )
+						client_send_no(client, errormsg);
+					else
+						client_send_no(client, str_c(errors));
+				} else {
+					client_send_no(client, str_c(errors));
+				}
+				success = FALSE;
+			} else {
+				sieve_close(&sbin);
+
+				/* Commit to save only when this is a putscript command */
+				if ( ctx->scriptname != NULL ) {
+					ret = sieve_storage_save_commit(&ctx->save_ctx);
+
+					/* Check commit */
+					if (ret < 0) {
+						client_send_storage_error(client, ctx->storage);
+						success = FALSE;
+					}
+				}
+			}
+
+			/* Finish up */
+			cmd_putscript_finish(ctx);
+
+			/* Report result to user */
+			if ( success ) {
+				if ( ctx->scriptname != NULL ) {
+					client->put_count++;
+					client->put_bytes += ctx->script_size;
+				} else {
+					client->check_count++;
+					client->check_bytes += ctx->script_size;
+				}
+
+				if ( sieve_get_warnings(ehandler) > 0 )
+					client_send_okresp(client, "WARNINGS", str_c(errors));
+				else {
+					if ( ctx->scriptname != NULL )
+						client_send_ok(client, "PUTSCRIPT completed.");
+					else
+						client_send_ok(client, "Script checked successfully.");
+				}
+			}
+
+			sieve_error_handler_unref(&ehandler);
+			str_free(&errors);
+		} T_END;
+
+		return TRUE;
+	}
+
+	client_send_command_error(cmd, "Too many command arguments.");
+	cmd_putscript_finish(ctx);
+	return TRUE;
+}
+
+static bool cmd_putscript_continue_parsing(struct client_command_context *cmd)
+{
+	struct client *client = cmd->client;
+	struct cmd_putscript_context *ctx = cmd->context;
+	const struct managesieve_arg *args;
+	int ret;
+
+	/* if error occurs, the CRLF is already read. */
+	client->input_skip_line = FALSE;
+
+	/* <script literal> */
+	ret = managesieve_parser_read_args(ctx->save_parser, 0,
+				    MANAGESIEVE_PARSE_FLAG_STRING_STREAM, &args);
+	if (ret == -1 || client->output->closed) {
+		cmd_putscript_finish(ctx);
+		client_send_command_error(cmd, "Invalid arguments.");
+		client->input_skip_line = TRUE;
+		return TRUE;
+	}
+	if (ret < 0) {
+		/* need more data */
+		return FALSE;
+	}
+
+	/* Validate the script argument */
+	if ( !managesieve_arg_get_string_stream(args,&ctx->input) ) {
+		client_send_command_error(cmd, "Invalid arguments.");
+		return cmd_putscript_cancel(ctx, FALSE);
+	}
+
+	if ( i_stream_get_size(ctx->input, FALSE, &ctx->script_size) > 0 ) {
+		ctx->script_size_valid = TRUE;
+
+		/* Check quota */
+		if ( ctx->scriptname == NULL ) {
+			if ( !managesieve_quota_check_validsize(client, ctx->script_size) )
+				return cmd_putscript_cancel(ctx, TRUE);
+		} else {
+			if ( !managesieve_quota_check_all
+				(client, ctx->scriptname, ctx->script_size) )
+				return cmd_putscript_cancel(ctx, TRUE);
+		}
+
+	} else {
+		ctx->max_script_size = managesieve_quota_max_script_size(client);
+	}
+
+	/* save the script */
+	ctx->save_ctx = sieve_storage_save_init
+		(ctx->storage, ctx->scriptname, ctx->input);
+
+	if ( ctx->save_ctx == NULL ) {
+		/* save initialization failed */
+		client_send_storage_error(client, ctx->storage);
+		return cmd_putscript_cancel(ctx, TRUE);
+	}
+
+	/* after literal comes CRLF, if we fail make sure we eat it away */
+	client->input_skip_line = TRUE;
+
+	client->command_pending = TRUE;
+	cmd->func = cmd_putscript_continue_script;
+	return cmd_putscript_continue_script(cmd);
+}
+
+static bool cmd_putscript_continue_script(struct client_command_context *cmd)
+{
+	struct client *client = cmd->client;
+	struct cmd_putscript_context *ctx = cmd->context;
+	size_t size;
+	int ret;
+
+	if (ctx->save_ctx != NULL) {
+		for (;;) {
+			i_assert(!ctx->script_size_valid ||
+				ctx->input->v_offset <= ctx->script_size);
+			if ( ctx->max_script_size > 0 &&
+				ctx->input->v_offset > ctx->max_script_size ) {
+				(void)managesieve_quota_check_validsize(client, ctx->input->v_offset);
+				cmd_putscript_finish(ctx);
+				return TRUE;
+			}
+
+			ret = i_stream_read(ctx->input);
+			if ((ret != -1 || ctx->input->stream_errno != EINVAL ||
+				client->input->eof) &&
+				sieve_storage_save_continue(ctx->save_ctx) < 0) {
+				/* we still have to finish reading the script
+			   	  from client */
+				sieve_storage_save_cancel(&ctx->save_ctx);
+				break;
+			}
+			if (ret == -1 || ret == 0)
+        break;
+		}
+	}
+
+	if (ctx->save_ctx == NULL) {
+		(void)i_stream_read(ctx->input);
+		(void)i_stream_get_data(ctx->input, &size);
+		i_stream_skip(ctx->input, size);
+	}
+
+	if (ctx->input->eof || client->input->closed) {
+		bool failed = FALSE;
+		bool all_written = FALSE;
+
+		if ( !ctx->script_size_valid ) {
+			if ( !client->input->eof &&
+				ctx->input->stream_errno == EINVAL ) {
+				client_send_command_error(cmd, t_strdup_printf(
+					"Invalid input: %s", i_stream_get_error(ctx->input)));
+				client->input_skip_line = TRUE;
+				failed = TRUE;
+			}
+			all_written = ( ctx->input->eof && ctx->input->stream_errno == 0 );
+
+		} else {
+			all_written = ( ctx->input->v_offset == ctx->script_size );
+		}
+
+		/* finished */
+		ctx->input = NULL;
+
+		if ( !failed ) {
+			if (ctx->save_ctx == NULL) {
+				/* failed above */
+				client_send_storage_error(client, ctx->storage);
+				failed = TRUE;
+			} else if (!all_written) {
+				/* client disconnected before it finished sending the
+					 whole script. */
+				failed = TRUE;
+				sieve_storage_save_cancel(&ctx->save_ctx);
+				client_disconnect
+					(client, "EOF while appending in PUTSCRIPT/CHECKSCRIPT");
+			} else if (sieve_storage_save_finish(ctx->save_ctx) < 0) {
+				failed = TRUE;
+				client_send_storage_error(client, ctx->storage);
+			} else {
+				failed = client->input->closed;
+			}
+		}
+
+		if (failed) {
+			cmd_putscript_finish(ctx);
+			return TRUE;
+		}
+
+		/* finish */
+		client->command_pending = FALSE;
+		managesieve_parser_reset(ctx->save_parser);
+		cmd->func = cmd_putscript_finish_parsing;
+		return cmd_putscript_finish_parsing(cmd);
+	}
+
+	return FALSE;
+}
+
+static bool cmd_putscript_start
+(struct client_command_context *cmd, const char *scriptname)
+{
+	struct cmd_putscript_context *ctx;
+	struct client *client = cmd->client;
+
+	ctx = p_new(cmd->pool, struct cmd_putscript_context, 1);
+	ctx->cmd = cmd;
+	ctx->client = client;
+	ctx->storage = client->storage;
+	ctx->scriptname = scriptname;
+
+	io_remove(&client->io);
+	client->io = io_add(i_stream_get_fd(client->input), IO_READ,
+			    client_input_putscript, client);
+	/* putscript is special because we're only waiting on client input, not
+	   client output, so disable the standard output handler until we're
+	   finished */
+	o_stream_unset_flush_callback(client->output);
+
+	ctx->save_parser = managesieve_parser_create
+		(client->input, client->set->managesieve_max_line_length);
+
+	cmd->func = cmd_putscript_continue_parsing;
+	cmd->context = ctx;
+	return cmd_putscript_continue_parsing(cmd);
+
+}
+
+bool cmd_putscript(struct client_command_context *cmd)
+{
+	const char *scriptname;
+
+	/* <scriptname> */
+	if ( !client_read_string_args(cmd, FALSE, 1, &scriptname) )
+		return FALSE;
+
+	return cmd_putscript_start(cmd, scriptname);
+}
+
+bool cmd_checkscript(struct client_command_context *cmd)
+{
+	return cmd_putscript_start(cmd, NULL);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve/cmd-renamescript.c
@@ -0,0 +1,42 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve.h"
+#include "sieve-script.h"
+#include "sieve-storage.h"
+
+#include "managesieve-common.h"
+#include "managesieve-commands.h"
+
+bool cmd_renamescript(struct client_command_context *cmd)
+{
+	struct client *client = cmd->client;
+	struct sieve_storage *storage = client->storage;
+	const char *scriptname, *newname;
+	struct sieve_script *script;
+
+	/* <oldname> <newname> */
+	if (!client_read_string_args(cmd, TRUE, 2, &scriptname, &newname))
+		return FALSE;
+
+	script = sieve_storage_open_script
+		(storage, scriptname, NULL);
+	if (script == NULL) {
+		client_send_storage_error(client, storage);
+		return TRUE;
+	}
+
+	if (sieve_script_rename(script, newname) < 0) {
+		client_send_storage_error(client, storage);
+	} else {
+		client->renamed_count++;
+		client_send_ok(client, "Renamescript completed.");
+	}
+
+	sieve_script_unref(&script);
+	return TRUE;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve/cmd-setactive.c
@@ -0,0 +1,114 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve.h"
+#include "sieve-script.h"
+#include "sieve-storage.h"
+
+#include "managesieve-common.h"
+#include "managesieve-commands.h"
+
+bool cmd_setactive(struct client_command_context *cmd)
+{
+	struct client *client = cmd->client;
+	struct sieve_storage *storage = client->storage;
+	const char *scriptname;
+	struct sieve_script *script;
+	int ret;
+
+	/* <scriptname> */
+	if ( !client_read_string_args(cmd, TRUE, 1, &scriptname) )
+		return FALSE;
+
+	/* Activate, or .. */
+	if ( *scriptname != '\0' ) {
+		string_t *errors = NULL;
+		const char *errormsg = NULL;
+		bool warnings = FALSE;
+		bool success = TRUE;
+
+		script = sieve_storage_open_script
+			(storage, scriptname, NULL);
+		if ( script == NULL ) {
+			client_send_storage_error(client, storage);
+			return TRUE;
+		}
+
+		if ( sieve_script_is_active(script) <= 0 ) {
+			/* Script is first being activated; compile it again without the UPLOAD
+			 * flag.
+			 */
+			T_BEGIN {
+				struct sieve_error_handler *ehandler;
+				enum sieve_compile_flags cpflags =
+					SIEVE_COMPILE_FLAG_NOGLOBAL | SIEVE_COMPILE_FLAG_ACTIVATED;
+				struct sieve_binary *sbin;
+				enum sieve_error error;
+
+				/* Prepare error handler */
+				errors = str_new(default_pool, 1024);
+				ehandler = sieve_strbuf_ehandler_create(client->svinst, errors, TRUE,
+					client->set->managesieve_max_compile_errors);
+
+				/* Compile */
+				if ( (sbin=sieve_compile_script
+					(script, ehandler, cpflags, &error)) == NULL ) {
+					if (error != SIEVE_ERROR_NOT_VALID) {
+						errormsg = sieve_script_get_last_error(script, &error);
+						if ( error == SIEVE_ERROR_NONE )
+							errormsg = NULL;
+					}
+					success = FALSE;
+				} else {
+					sieve_close(&sbin);
+				}
+
+				warnings = ( sieve_get_warnings(ehandler) > 0 );
+				sieve_error_handler_unref(&ehandler);
+			} T_END;
+		}
+
+		/* Activate only when script is valid (or already active) */
+		if ( success ) {
+			/* Refresh activation no matter what; this can also resolve some erroneous
+			 * situations.
+			 */
+			ret = sieve_script_activate(script, (time_t)-1);
+			if ( ret < 0 ) {
+				client_send_storage_error(client, storage);
+			} else {
+				if ( warnings ) {
+					client_send_okresp(client, "WARNINGS", str_c(errors));
+				} else {
+					client_send_ok(client, ( ret > 0 ?
+						"Setactive completed." :
+						"Script is already active." ));
+				}
+			}
+		} else if ( errormsg == NULL ) {
+			client_send_no(client, str_c(errors));
+		} else {
+			client_send_no(client, errormsg);
+		}
+
+		if ( errors != NULL )
+			str_free(&errors);
+		sieve_script_unref(&script);
+
+	/* ... deactivate */
+	} else {
+		ret = sieve_storage_deactivate(storage, (time_t)-1);
+
+		if ( ret < 0 )
+			client_send_storage_error(client, storage);
+		else
+			client_send_ok(client, ( ret > 0 ?
+ 				"Active script is now deactivated." :
+				"No scripts currently active." ));
+	}
+
+	return TRUE;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve/main.c
@@ -0,0 +1,358 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "buffer.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "path-util.h"
+#include "str.h"
+#include "base64.h"
+#include "process-title.h"
+#include "restrict-access.h"
+#include "settings-parser.h"
+#include "master-interface.h"
+#include "master-service.h"
+#include "master-login.h"
+#include "mail-user.h"
+#include "mail-storage-service.h"
+
+#include "managesieve-common.h"
+#include "managesieve-commands.h"
+#include "managesieve-capabilities.h"
+
+#include <stdio.h>
+#include <unistd.h>
+
+#define IS_STANDALONE() \
+        (getenv(MASTER_IS_PARENT_ENV) == NULL)
+
+#define MANAGESIEVE_DIE_IDLE_SECS 10
+
+static bool verbose_proctitle = FALSE;
+static struct mail_storage_service_ctx *storage_service;
+static struct master_login *master_login = NULL;
+
+void (*hook_client_created)(struct client **client) = NULL;
+
+void managesieve_refresh_proctitle(void)
+{
+#define MANAGESIEVE_PROCTITLE_PREFERRED_LEN 80
+	struct client *client;
+	string_t *title = t_str_new(128);
+
+	if (!verbose_proctitle)
+		return;
+
+	str_append_c(title, '[');
+	switch (managesieve_client_count) {
+	case 0:
+		str_append(title, "idling");
+		break;
+	case 1:
+		client = managesieve_clients;
+		str_append(title, client->user->username);
+		if (client->user->conn.remote_ip != NULL) {
+			str_append_c(title, ' ');
+			str_append(title,
+				   net_ip2addr(client->user->conn.remote_ip));
+		}
+
+		if ( client->cmd.name != NULL &&
+			str_len(title) <= MANAGESIEVE_PROCTITLE_PREFERRED_LEN ) {
+			str_append_c(title, ' ');
+			str_append(title, client->cmd.name);
+		}
+		break;
+	default:
+		str_printfa(title, "%u connections", managesieve_client_count);
+		break;
+	}
+	str_append_c(title, ']');
+	process_title_set(str_c(title));
+}
+
+static void client_kill_idle(struct client *client)
+{
+	client_send_bye(client, "Server shutting down.");
+	client_destroy(client, "Server shutting down.");
+}
+
+static void managesieve_die(void)
+{
+	struct client *client, *next;
+	time_t last_io, now = time(NULL);
+	time_t stop_timestamp = now - MANAGESIEVE_DIE_IDLE_SECS;
+	unsigned int stop_msecs;
+
+	for (client = managesieve_clients; client != NULL; client = next) {
+		next = client->next;
+
+		last_io = I_MAX(client->last_input, client->last_output);
+		if (last_io <= stop_timestamp)
+			client_kill_idle(client);
+		else {
+			timeout_remove(&client->to_idle);
+			stop_msecs = (last_io - stop_timestamp) * 1000;
+			client->to_idle = timeout_add(stop_msecs,
+						      client_kill_idle, client);
+		}
+	}
+}
+
+static void client_add_input(struct client *client, const buffer_t *buf)
+{
+	struct ostream *output;
+
+	if (buf != NULL && buf->used > 0) {
+		if (!i_stream_add_data(client->input, buf->data, buf->used))
+			i_panic("Couldn't add client input to stream");
+	}
+
+	output = client->output;
+	o_stream_ref(output);
+	o_stream_cork(output);
+	if (!IS_STANDALONE())
+		client_send_ok(client, "Logged in.");
+  (void)client_input(client);
+	o_stream_uncork(output);
+	o_stream_unref(&output);
+}
+
+static int
+client_create_from_input(const struct mail_storage_service_input *input,
+			 int fd_in, int fd_out, const buffer_t *input_buf,
+			 const char **error_r)
+{
+	struct mail_storage_service_user *user;
+	struct mail_user *mail_user;
+	struct client *client;
+	struct managesieve_settings *set;
+	const char *error;
+
+	if (mail_storage_service_lookup_next(storage_service, input,
+					     &user, &mail_user, error_r) <= 0)
+		return -1;
+	restrict_access_allow_coredumps(TRUE);
+
+	set = mail_storage_service_user_get_set(user)[1];
+	if (set->verbose_proctitle)
+		verbose_proctitle = TRUE;
+
+	if (settings_var_expand(&managesieve_setting_parser_info, set, mail_user->pool,
+				mail_user_var_expand_table(mail_user), &error) <= 0) {
+		i_error("Failed to expand settings: %s", error);
+		mail_storage_service_user_unref(&user);
+		mail_user_unref(&mail_user);
+		return -1;
+	}
+
+	client = client_create
+		(fd_in, fd_out, input->session_id, mail_user, user, set);
+	T_BEGIN {
+		client_add_input(client, input_buf);
+	} T_END;
+	return 0;
+}
+
+static void main_stdio_run(const char *username)
+{
+	struct mail_storage_service_input input;
+	const char *value, *error, *input_base64;
+	buffer_t *input_buf;
+
+	i_zero(&input);
+	input.module = "managesieve";
+	input.service = "sieve";
+	input.username =  username != NULL ? username : getenv("USER");
+	if (input.username == NULL && IS_STANDALONE())
+		input.username = getlogin();
+	if (input.username == NULL)
+		i_fatal("USER environment missing");
+	if ((value = getenv("IP")) != NULL)
+		net_addr2ip(value, &input.remote_ip);
+	if ((value = getenv("LOCAL_IP")) != NULL)
+		net_addr2ip(value, &input.local_ip);
+
+	input_base64 = getenv("CLIENT_INPUT");
+	input_buf = input_base64 == NULL ? NULL :
+		t_base64_decode_str(input_base64);
+
+	if (client_create_from_input(&input, STDIN_FILENO, STDOUT_FILENO,
+				     input_buf, &error) < 0)
+		i_fatal("%s", error);
+}
+
+static void
+login_client_connected(const struct master_login_client *client,
+		       const char *username, const char *const *extra_fields)
+{
+#define MSG_BYE_INTERNAL_ERROR "BYE \""CRITICAL_MSG"\"\r\n"
+	struct mail_storage_service_input input;
+	enum mail_auth_request_flags flags = client->auth_req.flags;
+	const char *error;
+	buffer_t input_buf;
+
+	i_zero(&input);
+	input.module = "managesieve";
+	input.service = "sieve";
+	input.local_ip = client->auth_req.local_ip;
+	input.remote_ip = client->auth_req.remote_ip;
+	input.local_port = client->auth_req.local_port;
+	input.remote_port = client->auth_req.remote_port;
+	input.username = username;
+	input.userdb_fields = extra_fields;
+	input.session_id = client->session_id;
+	if ((flags & MAIL_AUTH_REQUEST_FLAG_CONN_SECURED) != 0)
+		input.conn_secured = TRUE;
+	if ((flags & MAIL_AUTH_REQUEST_FLAG_CONN_SSL_SECURED) != 0)
+		input.conn_ssl_secured = TRUE;
+
+	buffer_create_from_const_data(&input_buf, client->data,
+				 client->auth_req.data_size);
+	if (client_create_from_input(&input, client->fd, client->fd,
+				     &input_buf, &error) < 0) {
+		int fd = client->fd;
+
+		if (write(fd, MSG_BYE_INTERNAL_ERROR,
+			  strlen(MSG_BYE_INTERNAL_ERROR)) < 0) {
+			if (errno != EAGAIN && errno != EPIPE)
+				i_error("write(client) failed: %m");
+		}
+		i_error("%s", error);
+		i_close_fd(&fd);
+		master_service_client_connection_destroyed(master_service);
+	}
+}
+
+static void login_client_failed(const struct master_login_client *client,
+				const char *errormsg)
+{
+	const char *msg;
+
+	msg = t_strdup_printf("NO \"%s\"\r\n", errormsg);
+	if (write(client->fd, msg, strlen(msg)) < 0) {
+		/* ignored */
+	}
+}
+
+static void client_connected(struct master_service_connection *conn)
+{
+	/* when running standalone, we shouldn't even get here */
+	i_assert(master_login != NULL);
+
+	master_service_client_connection_accept(conn);
+	master_login_add(master_login, conn->fd);
+}
+
+int main(int argc, char *argv[])
+{
+	static const struct setting_parser_info *set_roots[] = {
+		&managesieve_setting_parser_info,
+		NULL
+	};
+	struct master_login_settings login_set;
+	enum master_service_flags service_flags = 0;
+	enum mail_storage_service_flags storage_service_flags = 0;
+	const char *username = NULL, *error = NULL;
+	int c;
+
+	i_zero(&login_set);
+	login_set.postlogin_timeout_secs = MASTER_POSTLOGIN_TIMEOUT_DEFAULT;
+
+	if (IS_STANDALONE() && getuid() == 0 &&
+	    net_getpeername(1, NULL, NULL) == 0) {
+		printf("NO \"managesieve binary must not be started from "
+		       "inetd, use managesieve-login instead.\"\n");
+		return 1;
+	}
+
+	if (IS_STANDALONE() || getenv("DUMP_CAPABILITY") != NULL) {
+		service_flags |= MASTER_SERVICE_FLAG_STANDALONE |
+			MASTER_SERVICE_FLAG_STD_CLIENT;
+	} else {
+		service_flags |= MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN;
+	}
+	if ( getenv("DUMP_CAPABILITY") != NULL )
+		service_flags |= MASTER_SERVICE_FLAG_DONT_SEND_STATS;
+
+	master_service = master_service_init("managesieve", service_flags,
+					     &argc, &argv, "t:u:");
+	while ((c = master_getopt(master_service)) > 0) {
+		switch (c) {
+		case 't':
+			if (str_to_uint(optarg, &login_set.postlogin_timeout_secs) < 0 ||
+			    login_set.postlogin_timeout_secs == 0)
+				i_fatal("Invalid -t parameter: %s", optarg);
+			break;
+		case 'u':
+			storage_service_flags |=
+				MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP;
+			username = optarg;
+			break;
+		default:
+			return FATAL_DEFAULT;
+		}
+	}
+
+	master_service_set_die_callback(master_service, managesieve_die);
+
+	/* plugins may want to add commands, so this needs to be called early */
+	commands_init();
+
+	/* Dump capabilities if requested */
+	if ( getenv("DUMP_CAPABILITY") != NULL ) {
+		managesieve_capabilities_dump();
+		commands_deinit();
+		master_service_deinit(&master_service);
+		exit(0);
+	}
+
+	if (t_abspath("auth-master", &login_set.auth_socket_path, &error) < 0)
+		i_fatal("t_abspath(%s) failed: %s", "auth-master", error);
+
+	if (argv[optind] != NULL &&
+	    t_abspath(argv[optind], &login_set.postlogin_socket_path, &error) < 0) {
+		i_fatal("t_abspath(%s) failed: %s",
+			argv[optind], error);
+	}
+
+	login_set.callback = login_client_connected;
+	login_set.failure_callback = login_client_failed;
+
+	if (!IS_STANDALONE())
+		master_login = master_login_init(master_service, &login_set);
+
+	storage_service =
+		mail_storage_service_init(master_service,
+					  set_roots, storage_service_flags);
+	master_service_init_finish(master_service);
+	/* NOTE: login_set.*_socket_path are now invalid due to data stack
+	   having been freed */
+
+	/* fake that we're running, so we know if client was destroyed
+		while handling its initial input */
+	io_loop_set_running(current_ioloop);
+
+	if (IS_STANDALONE()) {
+		T_BEGIN {
+			main_stdio_run(username);
+		} T_END;
+	} else {
+		io_loop_set_running(current_ioloop);
+	}
+
+	if (io_loop_is_running(current_ioloop))
+		master_service_run(master_service, client_connected);
+	clients_destroy_all();
+
+	if (master_login != NULL)
+		master_login_deinit(&master_login);
+	mail_storage_service_deinit(&storage_service);
+
+	commands_deinit();
+
+	master_service_deinit(&master_service);
+	return 0;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve/managesieve-capabilities.c
@@ -0,0 +1,138 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "hostpid.h"
+#include "var-expand.h"
+#include "settings-parser.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "master-service-settings-cache.h"
+
+#include "sieve.h"
+
+#include "managesieve-capabilities.h"
+
+#include <stddef.h>
+#include <unistd.h>
+
+/*
+ * Global plugin settings
+ */
+
+struct plugin_settings {
+	ARRAY(const char *) plugin_envs;
+};
+
+static const struct setting_parser_info **plugin_set_roots;
+
+#undef DEF
+#define DEF(type, name) \
+	{ type, #name, offsetof(struct plugin_settings, name), NULL }
+
+static const struct setting_define plugin_setting_defines[] = {
+	{ SET_STRLIST, "plugin", offsetof(struct plugin_settings, plugin_envs), NULL },
+
+	SETTING_DEFINE_LIST_END
+};
+
+static const struct setting_parser_info plugin_setting_parser_info = {
+	.module_name = "managesieve",
+	.defines = plugin_setting_defines,
+
+	.type_offset = (size_t)-1,
+	.struct_size = sizeof(struct plugin_settings),
+
+	.parent_offset = (size_t)-1,
+};
+
+static const struct setting_parser_info *default_plugin_set_roots[] = {
+	&plugin_setting_parser_info,
+	NULL
+};
+
+static const struct setting_parser_info **plugin_set_roots =
+	default_plugin_set_roots;
+
+static struct plugin_settings *plugin_settings_read(void)
+{
+	const char *error;
+
+	if (master_service_settings_read_simple(master_service, plugin_set_roots, &error) < 0)
+		i_fatal("Error reading configuration: %s", error);
+
+	return (struct plugin_settings *)
+		master_service_settings_get_others(master_service)[0];
+}
+
+static const char *plugin_settings_get
+	(const struct plugin_settings *set, const char *identifier)
+{
+	const char *const *envs;
+	unsigned int i, count;
+
+	if ( !array_is_created(&set->plugin_envs) )
+		return NULL;
+
+	envs = array_get(&set->plugin_envs, &count);
+	for ( i = 0; i < count; i += 2 ) {
+		if ( strcmp(envs[i], identifier) == 0 )
+			return envs[i+1];
+	}
+
+	return NULL;
+}
+
+/*
+ * Sieve environment
+ */
+
+static const char *sieve_get_setting
+(void *context, const char *identifier)
+{
+	const struct plugin_settings *set = (const struct plugin_settings *) context;
+
+  return plugin_settings_get(set, identifier);
+}
+
+static const struct sieve_callbacks sieve_callbacks = {
+	NULL,
+	sieve_get_setting
+};
+
+/*
+ * Capability dumping
+ */
+
+void managesieve_capabilities_dump(void)
+{
+	const struct plugin_settings *global_plugin_settings;
+	struct sieve_environment svenv;
+	struct sieve_instance *svinst;
+	const char *notify_cap;
+
+	/* Read plugin settings */
+
+	global_plugin_settings = plugin_settings_read();
+
+	/* Initialize Sieve engine */
+
+	memset((void*)&svenv, 0, sizeof(svenv));
+	svenv.home_dir = "/tmp";
+
+	svinst = sieve_init
+		(&svenv, &sieve_callbacks, (void *) global_plugin_settings, FALSE);
+
+	/* Dump capabilities */
+
+	notify_cap = sieve_get_capabilities(svinst, "notify");
+
+	if ( notify_cap == NULL )
+		printf("SIEVE: %s\n", sieve_get_capabilities(svinst, NULL));
+	else
+		printf("SIEVE: %s, NOTIFY: %s\n", sieve_get_capabilities(svinst, NULL),
+			sieve_get_capabilities(svinst, "notify"));
+
+	sieve_deinit(&svinst);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve/managesieve-capabilities.h
@@ -0,0 +1,6 @@
+#ifndef MANAGESIEVE_CAPABILITIES_H
+#define MANAGESIEVE_CAPABILITIES_H
+
+void managesieve_capabilities_dump(void);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve/managesieve-client.c
@@ -0,0 +1,732 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "llist.h"
+#include "str.h"
+#include "hostpid.h"
+#include "net.h"
+#include "istream.h"
+#include "ostream.h"
+#include "iostream.h"
+#include "iostream-rawlog.h"
+#include "var-expand.h"
+#include "time-util.h"
+#include "master-service.h"
+#include "mail-storage-service.h"
+#include "mail-namespace.h"
+
+#include "sieve.h"
+#include "sieve-storage.h"
+
+#include "managesieve-quote.h"
+#include "managesieve-common.h"
+#include "managesieve-commands.h"
+#include "managesieve-client.h"
+
+#include <unistd.h>
+
+extern struct mail_storage_callbacks mail_storage_callbacks;
+struct managesieve_module_register managesieve_module_register = { 0 };
+
+struct client *managesieve_clients = NULL;
+unsigned int managesieve_client_count = 0;
+
+static const char *managesieve_sieve_get_setting
+(void *context, const char *identifier)
+{
+	struct mail_user *mail_user = (struct mail_user *) context;
+
+	if ( mail_user == NULL )
+		return NULL;
+
+	return mail_user_plugin_getenv(mail_user, identifier);
+}
+
+static const struct sieve_callbacks managesieve_sieve_callbacks = {
+	NULL,
+	managesieve_sieve_get_setting
+};
+
+static void client_idle_timeout(struct client *client)
+{
+	if (client->cmd.func != NULL) {
+		client_destroy(client,
+			"Disconnected for inactivity in reading our output");
+	} else {
+		client_send_bye(client, "Disconnected for inactivity");
+		client_destroy(client, "Disconnected for inactivity");
+	}
+}
+
+static struct sieve_storage *client_get_storage
+(struct sieve_instance *svinst, struct mail_user *user, int fd_out)
+{
+	struct sieve_storage *storage;
+	enum sieve_error error;
+	const char *errormsg, *byemsg;
+
+	/* Open personal script storage */
+
+	storage = sieve_storage_create_main
+		(svinst, user, SIEVE_STORAGE_FLAG_READWRITE, &error);
+	if (storage == NULL) {
+		switch (error) {
+		case SIEVE_ERROR_NOT_POSSIBLE:
+			byemsg = "BYE \"Sieve processing is disabled for this user.\"\r\n";
+			errormsg = "Failed to open Sieve storage: "
+				"Sieve is disabled for this user";
+			break;
+		case SIEVE_ERROR_NOT_FOUND:
+			byemsg = "BYE \"This user cannot manage personal Sieve scripts.\"\r\n";
+			errormsg = "Failed to open Sieve storage: "
+				"Personal script storage disabled or not found.";
+			break;
+		default:
+			byemsg = t_strflocaltime
+				("BYE \""CRITICAL_MSG_STAMP"\"\r\n", ioloop_time);
+			errormsg = "Failed to open Sieve storage.";
+		}
+
+		if (write(fd_out, byemsg, strlen(byemsg)) < 0) {
+			if (errno != EAGAIN && errno != EPIPE)
+				i_error("write(client) failed: %m");
+		}
+		i_fatal("%s", errormsg);
+	}
+
+	return storage;
+}
+
+struct client *client_create
+(int fd_in, int fd_out, const char *session_id, struct mail_user *user,
+	struct mail_storage_service_user *service_user,
+	const struct managesieve_settings *set)
+{
+	struct client *client;
+	const char *ident;
+	struct sieve_environment svenv;
+	struct sieve_instance *svinst;
+	struct sieve_storage *storage;
+	pool_t pool;
+
+	/* Initialize Sieve */
+
+	memset((void*)&svenv, 0, sizeof(svenv));
+	svenv.username = user->username;
+	(void)mail_user_get_home(user, &svenv.home_dir);
+	svenv.base_dir = user->set->base_dir;
+	svenv.flags = SIEVE_FLAG_HOME_RELATIVE;
+
+	svinst = sieve_init
+		(&svenv, &managesieve_sieve_callbacks, (void *) user, set->mail_debug);
+
+	/* Get Sieve storage */
+
+	storage = client_get_storage(svinst, user, fd_out);
+
+	/* always use nonblocking I/O */
+	net_set_nonblock(fd_in, TRUE);
+	net_set_nonblock(fd_out, TRUE);
+
+	pool = pool_alloconly_create("managesieve client", 1024);
+	client = p_new(pool, struct client, 1);
+	client->pool = pool;
+	client->set = set;
+	client->service_user = service_user;
+	client->session_id = p_strdup(pool, session_id);
+	client->fd_in = fd_in;
+	client->fd_out = fd_out;
+	client->input = i_stream_create_fd
+		(fd_in, set->managesieve_max_line_length);
+	client->output = o_stream_create_fd(fd_out, (size_t)-1);
+
+	o_stream_set_no_error_handling(client->output, TRUE);
+	i_stream_set_name(client->input, "<managesieve client>");
+	o_stream_set_name(client->output, "<managesieve client>");
+
+	o_stream_set_flush_callback(client->output, client_output, client);
+
+	client->io = io_add_istream(client->input, client_input, client);
+	client->last_input = ioloop_time;
+	client->to_idle = timeout_add
+		(CLIENT_IDLE_TIMEOUT_MSECS, client_idle_timeout, client);
+
+	client->cmd.pool =
+		pool_alloconly_create(MEMPOOL_GROWING"client command", 1024*12);
+	client->cmd.client = client;
+	client->user = user;
+
+	if (set->rawlog_dir[0] != '\0') {
+		(void)iostream_rawlog_create(set->rawlog_dir, &client->input,
+						   &client->output);
+	}
+
+	client->parser = managesieve_parser_create
+		(client->input, set->managesieve_max_line_length);
+
+	client->svinst = svinst;
+	client->storage = storage;
+
+	ident = mail_user_get_anvil_userip_ident(client->user);
+	if (ident != NULL) {
+		master_service_anvil_send(master_service, t_strconcat(
+			"CONNECT\t", my_pid, "\tsieve/", ident, "\n", NULL));
+		client->anvil_sent = TRUE;
+	}
+
+	managesieve_client_count++;
+	DLLIST_PREPEND(&managesieve_clients, client);
+	if (hook_client_created != NULL)
+		hook_client_created(&client);
+
+	managesieve_refresh_proctitle();
+	return client;
+}
+
+static const char *client_stats(struct client *client)
+{
+	const struct var_expand_table logout_tab[] = {
+		{ 'i', dec2str(i_stream_get_absolute_offset(client->input)), "input" },
+		{ 'o', dec2str(client->output->offset), "output" },
+		{ '\0', dec2str(client->put_bytes), "put_bytes" },
+		{ '\0', dec2str(client->put_count), "put_count" },
+		{ '\0', dec2str(client->get_bytes), "get_bytes" },
+		{ '\0', dec2str(client->get_count), "get_count" },
+		{ '\0', dec2str(client->check_bytes), "check_bytes" },
+		{ '\0', dec2str(client->check_count), "check_count" },
+		{ '\0', dec2str(client->deleted_count), "deleted_count" },
+		{ '\0', dec2str(client->renamed_count), "renamed_count" },
+		{ '\0', client->session_id, "session" },
+		{ '\0', NULL, NULL }
+	};
+	const struct var_expand_table *user_tab =
+		mail_user_var_expand_table(client->user);
+	const struct var_expand_table *tab =
+		t_var_expand_merge_tables(logout_tab, user_tab);
+	string_t *str;
+	const char *error;
+
+	str = t_str_new(128);
+	if (var_expand_with_funcs(str, client->set->managesieve_logout_format,
+				  tab, mail_user_var_expand_func_table,
+				  client->user, &error) < 0) {
+		i_error("Failed to expand managesieve_logout_format=%s: %s",
+			client->set->managesieve_logout_format, error);
+	}
+	return str_c(str);
+}
+
+void client_destroy(struct client *client, const char *reason)
+{
+	bool ret;
+
+ 	i_assert(!client->handling_input);
+	i_assert(!client->destroyed);
+	client->destroyed = TRUE;
+
+	if (!client->disconnected) {
+		client->disconnected = TRUE;
+		if (reason == NULL) {
+			reason = io_stream_get_disconnect_reason(client->input,
+								 client->output);
+		}
+		i_info("%s %s", reason, client_stats(client));
+	}
+
+	if (client->command_pending) {
+		/* try to deinitialize the command */
+		i_assert(client->cmd.func != NULL);
+
+		i_stream_close(client->input);
+		o_stream_close(client->output);
+
+		client->input_pending = FALSE;
+
+		ret = client->cmd.func(&client->cmd);
+		i_assert(ret);
+	}
+
+	if (client->anvil_sent) {
+		master_service_anvil_send(master_service, t_strconcat(
+			"DISCONNECT\t", my_pid, "\tsieve/",
+			mail_user_get_anvil_userip_ident(client->user),
+			"\n", NULL));
+	}
+
+	managesieve_parser_destroy(&client->parser);
+	io_remove(&client->io);
+	timeout_remove(&client->to_idle_output);
+	timeout_remove(&client->to_idle);
+
+	/* i/ostreams are already closed at this stage, so fd can be closed */
+	net_disconnect(client->fd_in);
+	if (client->fd_in != client->fd_out)
+		net_disconnect(client->fd_out);
+
+	/* Free the user after client is already disconnected. It may start
+	   some background work like autoexpunging. */
+	mail_user_unref(&client->user);
+
+	/* free the i/ostreams after mail_user_unref(), which could trigger
+		 mail_storage_callbacks notifications that write to the ostream. */
+	i_stream_destroy(&client->input);
+	o_stream_destroy(&client->output);
+
+	sieve_storage_unref(&client->storage);
+	sieve_deinit(&client->svinst);
+
+	pool_unref(&client->cmd.pool);
+	mail_storage_service_user_unref(&client->service_user);
+
+	managesieve_client_count--;
+	DLLIST_REMOVE(&managesieve_clients, client);
+	pool_unref(&client->pool);
+
+	master_service_client_connection_destroyed(master_service);
+	managesieve_refresh_proctitle();
+}
+
+static void client_destroy_timeout(struct client *client)
+{
+	client_destroy(client, NULL);
+}
+
+void client_disconnect(struct client *client, const char *reason)
+{
+	i_assert(reason != NULL);
+
+	if (client->disconnected)
+		return;
+
+	i_info("Disconnected: %s %s", reason, client_stats(client));
+	client->disconnected = TRUE;
+	o_stream_flush(client->output);
+	o_stream_uncork(client->output);
+
+	i_stream_close(client->input);
+	o_stream_close(client->output);
+
+	timeout_remove(&client->to_idle);
+	client->to_idle = timeout_add(0, client_destroy_timeout, client);
+}
+
+void client_disconnect_with_error(struct client *client, const char *msg)
+{
+	client_send_bye(client, msg);
+	client_disconnect(client, msg);
+}
+
+int client_send_line(struct client *client, const char *data)
+{
+	struct const_iovec iov[2];
+
+	if (client->output->closed)
+		return -1;
+
+	iov[0].iov_base = data;
+	iov[0].iov_len = strlen(data);
+	iov[1].iov_base = "\r\n";
+	iov[1].iov_len = 2;
+
+	if (o_stream_sendv(client->output, iov, 2) < 0)
+		return -1;
+	client->last_output = ioloop_time;
+
+	if (o_stream_get_buffer_used_size(client->output) >=
+	    CLIENT_OUTPUT_OPTIMAL_SIZE) {
+		/* buffer full, try flushing */
+		return o_stream_flush(client->output);
+	}
+	return 1;
+}
+
+void client_send_response
+(struct client *client, const char *oknobye, const char *resp_code, const char *msg)
+{
+	string_t *str;
+
+	str = t_str_new(128);
+	str_append(str, oknobye);
+
+	if ( resp_code != NULL ) {
+		str_append(str, " (");
+		str_append(str, resp_code);
+		str_append_c(str, ')');
+	}
+
+	if ( msg != NULL ) {
+		str_append_c(str, ' ');
+		managesieve_quote_append_string(str, msg, TRUE);
+	}
+
+	client_send_line(client, str_c(str));
+}
+
+void client_send_command_error
+(struct client_command_context *cmd, const char *msg)
+{
+	struct client *client = cmd->client;
+	const char *error, *cmd_name;
+	bool fatal;
+
+	if (msg == NULL) {
+		msg = managesieve_parser_get_error(client->parser, &fatal);
+		if (fatal) {
+			client_disconnect_with_error(client, msg);
+			return;
+		}
+	}
+
+	if (cmd->name == NULL)
+		error = t_strconcat
+			("Error in MANAGESIEVE command: ", msg, NULL);
+	else {
+		cmd_name = t_str_ucase(cmd->name);
+		error = t_strconcat
+			("Error in MANAGESIEVE command ", cmd_name, ": ", msg, NULL);
+	}
+
+	client_send_no(client, error);
+
+	if (++client->bad_counter >= CLIENT_MAX_BAD_COMMANDS) {
+		client_disconnect_with_error(client,
+			"Too many invalid MANAGESIEVE commands.");
+	}
+
+	/* client_read_args() failures rely on this being set, so that the
+	   command processing is stopped even while command function returns
+	   FALSE. */
+	cmd->param_error = TRUE;
+}
+
+void client_send_storage_error
+(struct client *client, struct sieve_storage *storage)
+{
+	enum sieve_error error_code;
+	const char *error;
+
+	error = sieve_storage_get_last_error(storage, &error_code);
+
+	switch ( error_code ) {
+	case SIEVE_ERROR_TEMP_FAILURE:
+		client_send_noresp(client, "TRYLATER", error);
+		break;
+
+	case SIEVE_ERROR_NO_QUOTA:
+		client_send_noresp(client, "QUOTA", error);
+		break;
+
+	case SIEVE_ERROR_NOT_FOUND:
+		client_send_noresp(client, "NONEXISTENT", error);
+		break;
+
+	case SIEVE_ERROR_ACTIVE:
+		client_send_noresp(client, "ACTIVE", error);
+		break;
+
+	case SIEVE_ERROR_EXISTS:
+		client_send_noresp(client, "ALREADYEXISTS", error);
+		break;
+
+	case SIEVE_ERROR_NOT_POSSIBLE:
+	default:
+		client_send_no(client, error);
+		break;
+	}
+}
+
+bool client_read_args(struct client_command_context *cmd, unsigned int count,
+	unsigned int flags, bool no_more, const struct managesieve_arg **args_r)
+{
+	const struct managesieve_arg *dummy_args_r = NULL;
+	int ret;
+
+	if ( args_r == NULL ) args_r = &dummy_args_r;
+
+	i_assert(count <= INT_MAX);
+
+	ret = managesieve_parser_read_args
+		(cmd->client->parser, ( no_more ? 0 : count ), flags, args_r);
+	if ( ret >= 0 ) {
+		if ( count > 0 || no_more ) {
+			if ( ret < (int)count ) {
+				client_send_command_error(cmd, "Missing arguments.");
+				return FALSE;
+			} else if ( no_more && ret > (int)count ) {
+				client_send_command_error(cmd, "Too many arguments.");
+				return FALSE;
+			}
+		}
+
+		/* all parameters read successfully */
+		return TRUE;
+	} else if (ret == -2) {
+		/* need more data */
+		if (cmd->client->input->closed) {
+			/* disconnected */
+ 			cmd->param_error = TRUE;
+		}
+		return FALSE;
+	} else {
+		/* error */
+		client_send_command_error(cmd, NULL);
+		return FALSE;
+	}
+}
+
+bool client_read_string_args(struct client_command_context *cmd,
+			     bool no_more, unsigned int count, ...)
+{
+	const struct managesieve_arg *msieve_args;
+	va_list va;
+	const char *str;
+	unsigned int i;
+	bool result = TRUE;
+
+	if ( !client_read_args(cmd, count, 0, no_more, &msieve_args) )
+		return FALSE;
+
+	va_start(va, count);
+	for ( i = 0; i < count; i++ ) {
+		const char **ret = va_arg(va, const char **);
+
+		if ( MANAGESIEVE_ARG_IS_EOL(&msieve_args[i]) ) {
+			client_send_command_error(cmd, "Missing arguments.");
+			result = FALSE;
+			break;
+		}
+
+		if ( !managesieve_arg_get_string(&msieve_args[i], &str) ) {
+			client_send_command_error(cmd, "Invalid arguments.");
+			result = FALSE;
+			break;
+		}
+
+		if (ret != NULL)
+			*ret = str;
+	}
+	va_end(va);
+
+	return result;
+}
+
+void _client_reset_command(struct client *client)
+{
+	pool_t pool;
+	size_t size;
+
+	/* reset input idle time because command output might have taken a
+	   long time and we don't want to disconnect client immediately then */
+	client->last_input = ioloop_time;
+	timeout_reset(client->to_idle);
+
+	client->command_pending = FALSE;
+    if (client->io == NULL && !client->disconnected) {
+        i_assert(i_stream_get_fd(client->input) >= 0);
+        client->io = io_add(i_stream_get_fd(client->input),
+                    IO_READ, client_input, client);
+    }
+    o_stream_set_flush_callback(client->output, client_output, client);
+
+	pool = client->cmd.pool;
+	i_zero(&client->cmd);
+
+	p_clear(pool);
+	client->cmd.pool = pool;
+	client->cmd.client = client;
+
+	managesieve_parser_reset(client->parser);
+
+	/* if there's unread data in buffer, remember that there's input
+	   pending and we should get around to calling client_input() soon.
+	   This is mostly for APPEND/IDLE. */
+	(void)i_stream_get_data(client->input, &size);
+	if (size > 0 && !client->destroyed)
+		client->input_pending = TRUE;
+}
+
+/* Skip incoming data until newline is found,
+   returns TRUE if newline was found. */
+static bool client_skip_line(struct client *client)
+{
+	const unsigned char *data;
+	size_t i, data_size;
+
+	data = i_stream_get_data(client->input, &data_size);
+
+	for (i = 0; i < data_size; i++) {
+		if (data[i] == '\n') {
+			client->input_skip_line = FALSE;
+			i++;
+			break;
+		}
+	}
+
+	i_stream_skip(client->input, i);
+	return !client->input_skip_line;
+}
+
+static bool client_handle_input(struct client_command_context *cmd)
+{
+	struct client *client = cmd->client;
+
+	if (cmd->func != NULL) {
+		/* command is being executed - continue it */
+		if (cmd->func(cmd) || cmd->param_error) {
+			/* command execution was finished */
+			if (!cmd->param_error)
+				client->bad_counter = 0;
+			_client_reset_command(client);
+			return TRUE;
+		}
+
+		/* unfinished */
+    if (client->command_pending)
+			o_stream_set_flush_pending(client->output, TRUE);
+		return FALSE;
+	}
+
+	if (client->input_skip_line) {
+		/* we're just waiting for new line.. */
+		if (!client_skip_line(client))
+			return FALSE;
+
+		/* got the newline */
+		_client_reset_command(client);
+
+		/* pass through to parse next command */
+	}
+
+	if (cmd->name == NULL) {
+		cmd->name = managesieve_parser_read_word(client->parser);
+		if (cmd->name == NULL)
+			return FALSE; /* need more data */
+		cmd->name = p_strdup(cmd->pool, cmd->name);
+		managesieve_refresh_proctitle();
+	}
+
+	if (cmd->name[0] == '\0') {
+		/* command not given - cmd_func is already NULL. */
+	} else {
+		/* find the command function */
+		struct command *command = command_find(cmd->name);
+
+		if (command != NULL) {
+			cmd->func = command->func;
+		}
+	}
+
+	client->input_skip_line = TRUE;
+	if (cmd->func == NULL) {
+		/* unknown command */
+		client_send_command_error(cmd, "Unknown command.");
+		_client_reset_command(client);
+	} else {
+		i_assert(!client->disconnected);
+
+		client_handle_input(cmd);
+	}
+
+	return TRUE;
+}
+
+void client_input(struct client *client)
+{
+	struct client_command_context *cmd = &client->cmd;
+	bool ret;
+
+	if (client->command_pending) {
+		/* already processing one command. wait. */
+		io_remove(&client->io);
+		return;
+	}
+
+	client->input_pending = FALSE;
+	client->last_input = ioloop_time;
+	timeout_reset(client->to_idle);
+
+	switch (i_stream_read(client->input)) {
+	case -1:
+		/* disconnected */
+		client_destroy(client, NULL);
+		return;
+	case -2:
+		/* parameter word is longer than max. input buffer size.
+		   this is most likely an error, so skip the new data
+		   until newline is found. */
+		client->input_skip_line = TRUE;
+
+		client_send_command_error(cmd, "Too long argument.");
+		_client_reset_command(client);
+		break;
+	}
+
+	client->handling_input = TRUE;
+	o_stream_cork(client->output);
+	do {
+		T_BEGIN {
+			ret = client_handle_input(cmd);
+		} T_END;
+	} while (ret && !client->disconnected);
+    o_stream_uncork(client->output);
+    client->handling_input = FALSE;
+
+	if (client->command_pending)
+		client->input_pending = TRUE;
+
+	if (client->output->closed)
+		client_destroy(client, NULL);
+}
+
+int client_output(struct client *client)
+{
+	struct client_command_context *cmd = &client->cmd;
+	int ret;
+	bool finished;
+
+	client->last_output = ioloop_time;
+    timeout_reset(client->to_idle);
+	if (client->to_idle_output != NULL)
+		timeout_reset(client->to_idle_output);
+
+	if ((ret = o_stream_flush(client->output)) < 0) {
+		client_destroy(client, NULL);
+		return 1;
+	}
+
+	if (!client->command_pending)
+		return 1;
+
+	/* continue processing command */
+	o_stream_cork(client->output);
+	client->output_pending = TRUE;
+	finished = cmd->func(cmd) || cmd->param_error;
+
+	/* a bit kludgy check. normally we would want to get back to this
+	   output handler, but IDLE is a special case which has command
+	   pending but without necessarily anything to write. */
+	if (!finished && client->output_pending)
+		o_stream_set_flush_pending(client->output, TRUE);
+
+	o_stream_uncork(client->output);
+
+	if (finished) {
+		/* command execution was finished */
+		client->bad_counter = 0;
+		_client_reset_command(client);
+
+		if (client->input_pending)
+			client_input(client);
+	}
+	return ret;
+}
+
+void clients_destroy_all(void)
+{
+	while (managesieve_clients != NULL) {
+		client_send_bye(managesieve_clients, "Server shutting down.");
+		client_destroy(managesieve_clients, "Server shutting down.");
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve/managesieve-client.h
@@ -0,0 +1,145 @@
+#ifndef MANAGESIEVE_CLIENT_H
+#define MANAGESIEVE_CLIENT_H
+
+#include "managesieve-commands.h"
+
+struct client;
+struct sieve_storage;
+struct managesieve_parser;
+struct managesieve_arg;
+
+struct client_command_context {
+	struct client *client;
+
+	pool_t pool;
+	const char *name;
+
+	command_func_t *func;
+	void *context;
+
+	bool param_error:1;
+};
+
+struct managesieve_module_register {
+    unsigned int id;
+};
+
+union managesieve_module_context {
+    struct managesieve_module_register *reg;
+};
+extern struct managesieve_module_register managesieve_module_register;
+
+struct client {
+	struct client *prev, *next;
+
+	const char *session_id;
+	int fd_in, fd_out;
+	struct io *io;
+	struct istream *input;
+	struct ostream *output;
+	struct timeout *to_idle, *to_idle_output;
+
+	pool_t pool;
+	struct mail_storage_service_user *service_user;
+	const struct managesieve_settings *set;
+
+	struct mail_user *user;
+
+	struct sieve_instance *svinst;
+	struct sieve_storage *storage;
+
+	time_t last_input, last_output;
+	unsigned int bad_counter;
+
+	struct managesieve_parser *parser;
+	struct client_command_context cmd;
+
+	uoff_t put_bytes;
+	uoff_t get_bytes;
+	uoff_t check_bytes;
+	unsigned int put_count;
+	unsigned int get_count;
+	unsigned int check_count;
+	unsigned int deleted_count;
+	unsigned int renamed_count;
+
+	bool disconnected:1;
+	bool destroyed:1;
+	bool command_pending:1;
+	bool input_pending:1;
+	bool output_pending:1;
+	bool handling_input:1;
+	bool anvil_sent:1;
+	bool input_skip_line:1; /* skip all the data until we've
+					   found a new line */
+};
+
+extern struct client *managesieve_clients;
+extern unsigned int managesieve_client_count;
+
+/* Create new client with specified input/output handles. socket specifies
+   if the handle is a socket. */
+struct client *client_create
+	(int fd_in, int fd_out, const char *session_id, struct mail_user *user,
+		struct mail_storage_service_user *service_user,
+		const struct managesieve_settings *set);
+void client_destroy(struct client *client, const char *reason);
+
+void client_dump_capability(struct client *client);
+
+/* Disconnect client connection */
+void client_disconnect(struct client *client, const char *reason);
+void client_disconnect_with_error(struct client *client, const char *msg);
+
+/* Send a line of data to client. Returns 1 if ok, 0 if buffer is getting full,
+   -1 if error */
+int client_send_line(struct client *client, const char *data);
+
+void client_send_response(struct client *client,
+  const char *oknobye, const char *resp_code, const char *msg);
+
+#define client_send_ok(client, msg) \
+  client_send_response(client, "OK", NULL, msg)
+#define client_send_no(client, msg) \
+  client_send_response(client, "NO", NULL, msg)
+#define client_send_bye(client, msg) \
+  client_send_response(client, "BYE", NULL, msg)
+
+#define client_send_okresp(client, resp_code, msg) \
+  client_send_response(client, "OK", resp_code, msg)
+#define client_send_noresp(client, resp_code, msg) \
+  client_send_response(client, "NO", resp_code, msg)
+#define client_send_byeresp(cmd, resp_code, msg) \
+  client_send_response(client, "BYE", resp_code, msg)
+
+/* Send BAD command error to client. msg can be NULL. */
+void client_send_command_error(struct client_command_context *cmd,
+			       const char *msg);
+
+/* Send storage or sieve related errors to the client */
+void client_send_storage_error(struct client *client,
+             struct sieve_storage *storage);
+
+/* Read a number of arguments. Returns TRUE if everything was read or
+   FALSE if either needs more data or error occurred. */
+bool client_read_args
+	(struct client_command_context *cmd, unsigned int count, unsigned int flags,
+		bool no_more, const struct managesieve_arg **args_r);
+/* Reads a number of string arguments. ... is a list of pointers where to
+   store the arguments. */
+bool client_read_string_args
+	(struct client_command_context *cmd, bool no_more, unsigned int count, ...);
+
+static inline bool client_read_no_args
+(struct client_command_context *cmd)
+{
+	return client_read_args(cmd, 0, 0, TRUE, NULL);
+}
+
+void _client_reset_command(struct client *client);
+void client_input(struct client *client);
+int client_output(struct client *client);
+
+void clients_destroy_all(void);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve/managesieve-commands.c
@@ -0,0 +1,108 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+
+#include "managesieve-common.h"
+#include "managesieve-commands.h"
+
+
+/* Might want to combine this somewhere in a commands-common.c
+ * to avoid duplicate code
+ */
+
+static const struct command managesieve_base_commands[] = {
+	{ "CAPABILITY", cmd_capability },
+	{ "LOGOUT", cmd_logout },
+	{ "PUTSCRIPT", cmd_putscript },
+	{ "CHECKSCRIPT", cmd_checkscript },
+	{ "GETSCRIPT", cmd_getscript },
+	{ "SETACTIVE", cmd_setactive },
+	{ "DELETESCRIPT", cmd_deletescript },
+	{ "LISTSCRIPTS", cmd_listscripts },
+	{ "HAVESPACE", cmd_havespace },
+	{ "RENAMESCRIPT", cmd_renamescript },
+	{ "NOOP", cmd_noop }
+};
+
+#define MANAGESIEVE_COMMANDS_COUNT N_ELEMENTS(managesieve_base_commands)
+
+static ARRAY(struct command) managesieve_commands;
+static bool commands_unsorted;
+
+void command_register(const char *name, command_func_t *func)
+{
+	struct command cmd;
+
+	i_zero(&cmd);
+	cmd.name = name;
+	cmd.func = func;
+	array_append(&managesieve_commands, &cmd, 1);
+
+	commands_unsorted = TRUE;
+}
+
+void command_unregister(const char *name)
+{
+	const struct command *cmd;
+	unsigned int i, count;
+
+	cmd = array_get(&managesieve_commands, &count);
+	for (i = 0; i < count; i++) {
+		if (strcasecmp(cmd[i].name, name) == 0) {
+			array_delete(&managesieve_commands, i, 1);
+			return;
+		}
+	}
+
+	i_error("Trying to unregister unknown command '%s'", name);
+}
+
+void command_register_array(const struct command *cmdarr, unsigned int count)
+{
+	commands_unsorted = TRUE;
+	array_append(&managesieve_commands, cmdarr, count);
+}
+
+void command_unregister_array(const struct command *cmdarr, unsigned int count)
+{
+	while (count > 0) {
+		command_unregister(cmdarr->name);
+		count--; cmdarr++;
+	}
+}
+
+static int command_cmp(const struct command *c1, const struct command *c2)
+{
+	return strcasecmp(c1->name, c2->name);
+}
+
+static int command_bsearch(const char *name, const struct command *cmd)
+{
+	return strcasecmp(name, cmd->name);
+}
+
+struct command *command_find(const char *name)
+{
+	if (commands_unsorted) {
+		array_sort(&managesieve_commands, command_cmp);
+		commands_unsorted = FALSE;
+	}
+
+	return array_bsearch(&managesieve_commands, name, command_bsearch);
+}
+
+void commands_init(void)
+{
+	i_array_init(&managesieve_commands, 16);
+	commands_unsorted = FALSE;
+
+	command_register_array(managesieve_base_commands, MANAGESIEVE_COMMANDS_COUNT);
+}
+
+void commands_deinit(void)
+{
+	command_unregister_array(managesieve_base_commands, MANAGESIEVE_COMMANDS_COUNT);
+	array_free(&managesieve_commands);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve/managesieve-commands.h
@@ -0,0 +1,46 @@
+#ifndef MANAGESIEVE_COMMANDS_H
+#define MANAGESIEVE_COMMANDS_H
+
+struct client_command_context;
+
+#include "managesieve-parser.h"
+
+typedef bool command_func_t(struct client_command_context *cmd);
+
+struct command {
+	const char *name;
+	command_func_t *func;
+};
+
+/* Register command. Given name parameter must be permanently stored until
+   command is unregistered. */
+void command_register(const char *name, command_func_t *func);
+void command_unregister(const char *name);
+
+/* Register array of commands. */
+void command_register_array(const struct command *cmdarr, unsigned int count);
+void command_unregister_array(const struct command *cmdarr, unsigned int count);
+
+struct command *command_find(const char *name);
+
+void commands_init(void);
+void commands_deinit(void);
+
+/* MANAGESIEVE commands: */
+
+/* Non-Authenticated State */
+extern bool cmd_logout(struct client_command_context *cmd);
+extern bool cmd_capability(struct client_command_context *cmd);
+extern bool cmd_noop(struct client_command_context *cmd);
+
+/* Authenticated State */
+extern bool cmd_putscript(struct client_command_context *cmd);
+extern bool cmd_checkscript(struct client_command_context *cmd);
+extern bool cmd_getscript(struct client_command_context *cmd);
+extern bool cmd_setactive(struct client_command_context *cmd);
+extern bool cmd_deletescript(struct client_command_context *cmd);
+extern bool cmd_listscripts(struct client_command_context *cmd);
+extern bool cmd_havespace(struct client_command_context *cmd);
+extern bool cmd_renamescript(struct client_command_context *cmd);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve/managesieve-common.h
@@ -0,0 +1,30 @@
+#ifndef MANAGESIEVE_COMMON_H
+#define MANAGESIEVE_COMMON_H
+
+#include "pigeonhole-config.h"
+
+/* Disconnect client after idling this many milliseconds */
+#define CLIENT_IDLE_TIMEOUT_MSECS (60*30*1000)
+
+/* If we can't send anything to client for this long, disconnect the client */
+#define CLIENT_OUTPUT_TIMEOUT_MSECS (5*60*1000)
+
+/* Stop buffering more data into output stream after this many bytes */
+#define CLIENT_OUTPUT_OPTIMAL_SIZE 2048
+
+/* Disconnect client when it sends too many bad commands in a row */
+#define CLIENT_MAX_BAD_COMMANDS 20
+
+#define CRITICAL_MSG \
+  "Internal error occurred. Refer to server log for more information."
+#define CRITICAL_MSG_STAMP CRITICAL_MSG " [%Y-%m-%d %H:%M:%S]"
+
+#include "lib.h"
+#include "managesieve-client.h"
+#include "managesieve-settings.h"
+
+extern void (*hook_client_created)(struct client **client);
+
+void managesieve_refresh_proctitle(void);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve/managesieve-quota.c
@@ -0,0 +1,76 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "strfuncs.h"
+
+#include "sieve.h"
+#include "sieve-storage.h"
+
+#include "managesieve-client.h"
+#include "managesieve-quota.h"
+
+uint64_t managesieve_quota_max_script_size(struct client *client)
+{
+	return sieve_storage_quota_max_script_size(client->storage);
+}
+
+bool managesieve_quota_check_validsize(struct client *client, size_t size)
+{
+	uint64_t limit;
+
+	if ( !sieve_storage_quota_validsize
+		(client->storage, size, &limit) ) {
+		client_send_noresp(client, "QUOTA/MAXSIZE",
+			t_strdup_printf("Script is too large (max %llu bytes).",
+			(unsigned long long int) limit));
+
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+bool managesieve_quota_check_all
+(struct client *client, const char *scriptname, size_t size)
+{
+	enum sieve_storage_quota quota;
+	uint64_t limit;
+	int ret;
+
+	if ( (ret=sieve_storage_quota_havespace
+		(client->storage, scriptname, size, &quota, &limit)) <= 0 ) {
+		if ( ret == 0 ) {
+			switch ( quota ) {
+			case SIEVE_STORAGE_QUOTA_MAXSIZE:
+				client_send_noresp(client, "QUOTA/MAXSIZE", t_strdup_printf(
+					"Script is too large (max %llu bytes).",
+					(unsigned long long int) limit));
+				break;
+
+			case SIEVE_STORAGE_QUOTA_MAXSCRIPTS:
+				client_send_noresp(client, "QUOTA/MAXSCRIPTS", t_strdup_printf(
+					"Script count quota exceeded (max %llu scripts).",
+					(unsigned long long int) limit));
+				break;
+
+			case SIEVE_STORAGE_QUOTA_MAXSTORAGE:
+				client_send_noresp(client, "QUOTA/MAXSTORAGE", t_strdup_printf(
+					"Script storage quota exceeded (max %llu bytes).",
+					(unsigned long long int) limit));
+				break;
+
+			default:
+				client_send_noresp(client, "QUOTA", "Quota exceeded.");
+				break;
+			}
+		} else {
+			client_send_storage_error(client, client->storage);
+		}
+
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve/managesieve-quota.h
@@ -0,0 +1,11 @@
+#ifndef MANAGESIEVE_QUOTA_H
+#define MANAGESIEVE_QUOTA_H
+
+uint64_t managesieve_quota_max_script_size(struct client *client);
+
+bool managesieve_quota_check_validsize
+	(struct client *client, size_t size);
+bool managesieve_quota_check_all
+	(struct client *client, const char *scriptname, size_t size);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve/managesieve-settings.c
@@ -0,0 +1,171 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "buffer.h"
+#include "settings-parser.h"
+#include "service-settings.h"
+#include "mail-storage-settings.h"
+
+#include "pigeonhole-config.h"
+
+#include "managesieve-settings.h"
+
+#include <stddef.h>
+#include <unistd.h>
+
+static bool managesieve_settings_verify(void *_set, pool_t pool,
+				 const char **error_r);
+
+/* <settings checks> */
+static struct file_listener_settings managesieve_unix_listeners_array[] = {
+	{ "login/sieve", 0666, "", "" }
+};
+static struct file_listener_settings *managesieve_unix_listeners[] = {
+	&managesieve_unix_listeners_array[0]
+};
+static buffer_t managesieve_unix_listeners_buf = {
+	managesieve_unix_listeners, sizeof(managesieve_unix_listeners), { 0, }
+};
+/* </settings checks> */
+
+struct service_settings managesieve_settings_service_settings = {
+	.name = "managesieve",
+	.protocol = "sieve",
+	.type = "",
+	.executable = "managesieve",
+	.user = "",
+	.group = "",
+	.privileged_group = "",
+	.extra_groups = "",
+	.chroot = "",
+
+	.drop_priv_before_exec = FALSE,
+
+	.process_min_avail = 0,
+	.process_limit = 0,
+	.client_limit = 1,
+	.service_count = 1,
+	.idle_kill = 0,
+	.vsz_limit = (uoff_t)-1,
+
+	.unix_listeners = { { &managesieve_unix_listeners_buf,
+				   sizeof(managesieve_unix_listeners[0]) } },
+	.fifo_listeners = ARRAY_INIT,
+	.inet_listeners = ARRAY_INIT
+};
+
+#undef DEF
+#undef DEFLIST
+#define DEF(type, name) \
+	{ type, #name, offsetof(struct managesieve_settings, name), NULL }
+#define DEFLIST(field, name, defines) \
+	{ SET_DEFLIST, name, offsetof(struct managesieve_settings, field), defines }
+
+static struct setting_define managesieve_setting_defines[] = {
+	DEF(SET_BOOL, mail_debug),
+	DEF(SET_BOOL, verbose_proctitle),
+	DEF(SET_STR_VARS, rawlog_dir),
+
+	DEF(SET_UINT, managesieve_max_line_length),
+	DEF(SET_STR, managesieve_implementation_string),
+	DEF(SET_STR, managesieve_client_workarounds),
+	DEF(SET_STR, managesieve_logout_format),
+	DEF(SET_UINT, managesieve_max_compile_errors),
+
+
+	SETTING_DEFINE_LIST_END
+};
+
+static struct managesieve_settings managesieve_default_settings = {
+	.mail_debug = FALSE,
+	.verbose_proctitle = FALSE,
+	.rawlog_dir = "",
+
+	/* RFC-2683 recommends at least 8000 bytes. Some clients however don't
+	   break large message sets to multiple commands, so we're pretty
+	   liberal by default. */
+	.managesieve_max_line_length = 65536,
+	.managesieve_implementation_string = DOVECOT_NAME " " PIGEONHOLE_NAME,
+	.managesieve_client_workarounds = "",
+	.managesieve_logout_format = "bytes=%i/%o",
+	.managesieve_max_compile_errors = 5
+};
+
+static const struct setting_parser_info *managesieve_setting_dependencies[] = {
+	&mail_user_setting_parser_info,
+	NULL
+};
+
+const struct setting_parser_info managesieve_setting_parser_info = {
+	.module_name = "managesieve",
+	.defines = managesieve_setting_defines,
+	.defaults = &managesieve_default_settings,
+
+	.type_offset = (size_t)-1,
+	.struct_size = sizeof(struct managesieve_settings),
+
+	.parent_offset = (size_t)-1,
+	.parent = NULL,
+
+	.check_func = managesieve_settings_verify,
+	.dependencies = managesieve_setting_dependencies
+};
+
+const struct setting_parser_info *managesieve_settings_set_roots[] = {
+	&managesieve_setting_parser_info,
+	NULL
+};
+
+/* <settings checks> */
+struct managesieve_client_workaround_list {
+	const char *name;
+	enum managesieve_client_workarounds num;
+};
+
+static const struct managesieve_client_workaround_list
+	managesieve_client_workaround_list[] = {
+	{ NULL, 0 }
+};
+
+static int
+managesieve_settings_parse_workarounds(struct managesieve_settings *set,
+				const char **error_r)
+{
+	enum managesieve_client_workarounds client_workarounds = 0;
+	const struct managesieve_client_workaround_list *list;
+	const char *const *str;
+
+	str = t_strsplit_spaces(set->managesieve_client_workarounds, " ,");
+	for (; *str != NULL; str++) {
+		list = managesieve_client_workaround_list;
+		for (; list->name != NULL; list++) {
+			if (strcasecmp(*str, list->name) == 0) {
+				client_workarounds |= list->num;
+				break;
+			}
+		}
+		if (list->name == NULL) {
+			*error_r = t_strdup_printf("managesieve_client_workarounds: "
+				"Unknown workaround: %s", *str);
+			return -1;
+		}
+	}
+	set->parsed_workarounds = client_workarounds;
+	return 0;
+}
+
+
+static bool managesieve_settings_verify
+(void *_set, pool_t pool ATTR_UNUSED, const char **error_r)
+{
+	struct managesieve_settings *set = _set;
+
+	if (managesieve_settings_parse_workarounds(set, error_r) < 0)
+		return FALSE;
+	return TRUE;
+}
+
+/* </settings checks> */
+
+const char *managesieve_settings_version = DOVECOT_ABI_VERSION;
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/managesieve/managesieve-settings.h
@@ -0,0 +1,29 @@
+#ifndef MANAGESIEVE_SETTINGS_H
+#define MANAGESIEVE_SETTINGS_H
+
+struct mail_user_settings;
+
+/* <settings checks> */
+enum managesieve_client_workarounds {
+	WORKAROUND_NONE = 0x00
+};
+/* </settings checks> */
+
+struct managesieve_settings {
+	bool mail_debug;
+	bool verbose_proctitle;
+	const char *rawlog_dir;
+
+	/* managesieve: */
+	unsigned int managesieve_max_line_length;
+	const char *managesieve_implementation_string;
+	const char *managesieve_client_workarounds;
+	const char *managesieve_logout_format;
+	unsigned int managesieve_max_compile_errors;
+
+	enum managesieve_client_workarounds parsed_workarounds;
+};
+
+extern const struct setting_parser_info managesieve_setting_parser_info;
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/Makefile.am
@@ -0,0 +1,7 @@
+SUBDIRS = \
+	doveadm-sieve \
+	lda-sieve \
+	sieve-extprograms \
+	imapsieve \
+	imap-filter-sieve \
+	settings
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/doveadm-sieve/Makefile.am
@@ -0,0 +1,31 @@
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib-sieve \
+	$(LIBDOVECOT_INCLUDE) \
+	$(LIBDOVECOT_STORAGE_INCLUDE) \
+	$(LIBDOVECOT_DOVEADM_INCLUDE)
+
+doveadm_moduledir = $(dovecot_moduledir)/doveadm
+lib10_doveadm_sieve_plugin_la_LDFLAGS = -module -avoid-version
+
+doveadm_module_LTLIBRARIES = lib10_doveadm_sieve_plugin.la
+
+lib10_doveadm_sieve_plugin_la_LIBADD = \
+	$(top_builddir)/src/lib-sieve/libdovecot-sieve.la
+
+commands = \
+	doveadm-sieve-cmd-list.c \
+	doveadm-sieve-cmd-get.c \
+	doveadm-sieve-cmd-put.c \
+	doveadm-sieve-cmd-delete.c \
+	doveadm-sieve-cmd-activate.c \
+	doveadm-sieve-cmd-rename.c
+
+lib10_doveadm_sieve_plugin_la_SOURCES = \
+	$(commands) \
+	doveadm-sieve-cmd.c \
+	doveadm-sieve-sync.c \
+	doveadm-sieve-plugin.c
+
+noinst_HEADERS = \
+	doveadm-sieve-cmd.h \
+	doveadm-sieve-plugin.h
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/doveadm-sieve/doveadm-sieve-cmd-activate.c
@@ -0,0 +1,145 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "doveadm-mail.h"
+
+#include "sieve.h"
+#include "sieve-script.h"
+#include "sieve-storage.h"
+
+#include "doveadm-sieve-cmd.h"
+
+struct doveadm_sieve_activate_cmd_context {
+	struct doveadm_sieve_cmd_context ctx;
+
+	const char *scriptname;
+};
+
+static int
+cmd_sieve_activate_run(struct doveadm_sieve_cmd_context *_ctx)
+{
+	struct doveadm_sieve_activate_cmd_context *ctx =
+		(struct doveadm_sieve_activate_cmd_context *)_ctx;
+	struct sieve_storage *storage = _ctx->storage;
+	struct sieve_script *script;
+	enum sieve_error error;
+	int ret = 0;
+
+	script = sieve_storage_open_script
+		(storage, ctx->scriptname, NULL);
+	if ( script == NULL ) {
+		i_error("Failed to activate Sieve script: %s",
+			sieve_storage_get_last_error(storage, &error));
+		doveadm_sieve_cmd_failed_error(_ctx, error);
+		return -1;
+	}
+
+	if ( sieve_script_is_active(script) <= 0 ) {
+		/* Script is first being activated; compile it again without the UPLOAD
+		 * flag.
+		 */
+		struct sieve_error_handler *ehandler;
+		enum sieve_compile_flags cpflags =
+			SIEVE_COMPILE_FLAG_NOGLOBAL | SIEVE_COMPILE_FLAG_ACTIVATED;
+		struct sieve_binary *sbin;
+		enum sieve_error error;
+
+		/* Compile */
+		ehandler = sieve_master_ehandler_create(ctx->ctx.svinst, NULL, 0);
+		if ( (sbin=sieve_compile_script
+			(script, ehandler, cpflags, &error)) == NULL ) {
+			doveadm_sieve_cmd_failed_error(_ctx, error);
+			ret = -1;
+		} else {
+			sieve_close(&sbin);
+		}
+		sieve_error_handler_unref(&ehandler);
+	}
+
+	/* Activate only when script is valid (or already active) */
+	if ( ret == 0 ) {
+		/* Refresh activation no matter what; this can also resolve some erroneous
+		 * situations.
+		 */
+		ret = sieve_script_activate(script, (time_t)-1);
+		if ( ret < 0 ) {
+			i_error("Failed to activate Sieve script: %s",
+				sieve_storage_get_last_error(storage, &error));
+			doveadm_sieve_cmd_failed_error(_ctx, error);
+			ret = -1;
+		}
+	}
+
+	sieve_script_unref(&script);
+	return ret;
+}
+
+static int cmd_sieve_deactivate_run
+(struct doveadm_sieve_cmd_context *_ctx)
+{
+	struct sieve_storage *storage = _ctx->storage;
+	enum sieve_error error;
+
+	if (sieve_storage_deactivate(storage, (time_t)-1) < 0) {
+		i_error("Failed to deactivate Sieve script: %s",
+			sieve_storage_get_last_error(storage, &error));
+		doveadm_sieve_cmd_failed_error(_ctx, error);
+		return -1;
+	}
+	return 0;
+}
+
+static void cmd_sieve_activate_init
+(struct doveadm_mail_cmd_context *_ctx,
+	const char *const args[])
+{
+	struct doveadm_sieve_activate_cmd_context *ctx =
+		(struct doveadm_sieve_activate_cmd_context *)_ctx;
+
+	if (str_array_length(args) != 1)
+		doveadm_mail_help_name("sieve activate");
+	doveadm_sieve_cmd_scriptnames_check(args);
+
+	ctx->scriptname = p_strdup(ctx->ctx.ctx.pool, args[0]);
+}
+
+static struct doveadm_mail_cmd_context *
+cmd_sieve_activate_alloc(void)
+{
+	struct doveadm_sieve_activate_cmd_context *ctx;
+
+	ctx = doveadm_sieve_cmd_alloc(struct doveadm_sieve_activate_cmd_context);
+	ctx->ctx.ctx.v.init = cmd_sieve_activate_init;
+	ctx->ctx.v.run = cmd_sieve_activate_run;
+	return &ctx->ctx.ctx;
+}
+
+static struct doveadm_mail_cmd_context *
+cmd_sieve_deactivate_alloc(void)
+{
+	struct doveadm_sieve_cmd_context *ctx;
+
+	ctx = doveadm_sieve_cmd_alloc(struct doveadm_sieve_cmd_context);
+	ctx->v.run = cmd_sieve_deactivate_run;
+	return &ctx->ctx;
+}
+
+struct doveadm_cmd_ver2 doveadm_sieve_cmd_activate = {
+	.name = "sieve activate",
+	.mail_cmd = cmd_sieve_activate_alloc,
+	.usage = DOVEADM_CMD_MAIL_USAGE_PREFIX"<scriptname>",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAM('\0',"scriptname",CMD_PARAM_STR,CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+};
+
+struct doveadm_cmd_ver2 doveadm_sieve_cmd_deactivate = {
+	.name = "sieve deactivate",
+	.mail_cmd = cmd_sieve_deactivate_alloc,
+	.usage = DOVEADM_CMD_MAIL_USAGE_PREFIX,
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAMS_END
+};
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/doveadm-sieve/doveadm-sieve-cmd-delete.c
@@ -0,0 +1,117 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "doveadm-mail.h"
+
+#include "sieve.h"
+#include "sieve-script.h"
+#include "sieve-storage.h"
+
+#include "doveadm-sieve-cmd.h"
+
+struct doveadm_sieve_delete_cmd_context {
+	struct doveadm_sieve_cmd_context ctx;
+
+	ARRAY_TYPE(const_string) scriptnames;
+	bool ignore_active:1;
+};
+
+static int
+cmd_sieve_delete_run(struct doveadm_sieve_cmd_context *_ctx)
+{
+	struct doveadm_sieve_delete_cmd_context *ctx =
+		(struct doveadm_sieve_delete_cmd_context *)_ctx;
+	struct sieve_storage *storage = _ctx->storage;
+	const ARRAY_TYPE(const_string) *scriptnames = &ctx->scriptnames;
+	const char *const *namep;
+	struct sieve_script *script;
+	enum sieve_error error;
+	int ret = 0;
+
+	array_foreach(scriptnames, namep) {
+		const char *scriptname = *namep;
+		int sret = 0;
+
+		script = sieve_storage_open_script
+			(storage, scriptname, NULL);
+		if (script == NULL) {
+			sret =  -1;
+		} else {
+			if (sieve_script_delete(script, ctx->ignore_active) < 0) {
+				(void)sieve_storage_get_last_error(storage, &error);
+				sret = -1;
+			}
+			sieve_script_unref(&script);
+		}
+			
+		if (sret < 0) {
+			i_error("Failed to delete Sieve script: %s",
+				sieve_storage_get_last_error(storage, &error));
+			doveadm_sieve_cmd_failed_error(_ctx, error);
+			ret = -1;
+		}
+	}
+	return ret;
+}
+
+static void cmd_sieve_delete_init
+(struct doveadm_mail_cmd_context *_ctx,
+	const char *const args[])
+{
+	struct doveadm_sieve_delete_cmd_context *ctx =
+		(struct doveadm_sieve_delete_cmd_context *)_ctx;
+	const char *name;
+	unsigned int i;
+
+	if (args[0] == NULL)
+		doveadm_mail_help_name("sieve delete");
+	doveadm_sieve_cmd_scriptnames_check(args);
+
+	for (i = 0; args[i] != NULL; i++) {
+		name = p_strdup(ctx->ctx.ctx.pool, args[i]);
+		array_append(&ctx->scriptnames, &name, 1);
+	}
+}
+
+static bool
+cmd_sieve_delete_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c)
+{
+	struct doveadm_sieve_delete_cmd_context *ctx =
+		(struct doveadm_sieve_delete_cmd_context *)_ctx;
+
+	switch ( c ) {
+	case 'a':
+		ctx->ignore_active = TRUE;
+		break;
+	default:
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static struct doveadm_mail_cmd_context *
+cmd_sieve_delete_alloc(void)
+{
+	struct doveadm_sieve_delete_cmd_context *ctx;
+
+	ctx = doveadm_sieve_cmd_alloc(struct doveadm_sieve_delete_cmd_context);
+	ctx->ctx.ctx.getopt_args = "a";
+	ctx->ctx.ctx.v.parse_arg = cmd_sieve_delete_parse_arg;
+	ctx->ctx.ctx.v.init = cmd_sieve_delete_init;
+	ctx->ctx.v.run = cmd_sieve_delete_run;
+	p_array_init(&ctx->scriptnames, ctx->ctx.ctx.pool, 16);
+	return &ctx->ctx.ctx;
+}
+
+struct doveadm_cmd_ver2 doveadm_sieve_cmd_delete = {
+	.name = "sieve delete",
+	.mail_cmd = cmd_sieve_delete_alloc,
+	.usage = DOVEADM_CMD_MAIL_USAGE_PREFIX"[-a] <scriptname> [...]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAM('a',"ignore-active",CMD_PARAM_BOOL,0)
+DOVEADM_CMD_PARAM('\0',"scriptname",CMD_PARAM_ARRAY,CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+};
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/doveadm-sieve/doveadm-sieve-cmd-get.c
@@ -0,0 +1,83 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "doveadm-print.h"
+#include "doveadm-mail.h"
+
+#include "sieve.h"
+#include "sieve-script.h"
+#include "sieve-storage.h"
+
+#include "doveadm-sieve-cmd.h"
+
+struct doveadm_sieve_get_cmd_context {
+	struct doveadm_sieve_cmd_context ctx;
+
+	const char *scriptname;
+};
+
+static int
+cmd_sieve_get_run(struct doveadm_sieve_cmd_context *_ctx)
+{
+	struct doveadm_sieve_get_cmd_context *ctx =
+		(struct doveadm_sieve_get_cmd_context *)_ctx;
+	struct sieve_script *script;
+	struct istream *input;
+	enum sieve_error error;
+	int ret;
+
+	script = sieve_storage_open_script
+		(_ctx->storage, ctx->scriptname, &error);
+	if ( script == NULL || sieve_script_get_stream
+		(script, &input, &error) < 0 ) {
+		i_error("Failed to open Sieve script: %s",
+			sieve_storage_get_last_error(_ctx->storage, &error));
+		doveadm_sieve_cmd_failed_error(_ctx, error);
+		if (script != NULL)
+			sieve_script_unref(&script);
+		return -1;
+	}
+
+	ret = doveadm_print_istream(input);
+	sieve_script_unref(&script);
+	return ret;
+}
+
+static void cmd_sieve_get_init
+(struct doveadm_mail_cmd_context *_ctx,
+	const char *const args[])
+{
+	struct doveadm_sieve_get_cmd_context *ctx =
+		(struct doveadm_sieve_get_cmd_context *)_ctx;
+
+	if ( str_array_length(args) != 1 )
+		doveadm_mail_help_name("sieve get");
+	doveadm_sieve_cmd_scriptnames_check(args);
+
+	ctx->scriptname = p_strdup(ctx->ctx.ctx.pool, args[0]);
+
+	doveadm_print_header_simple("sieve script");
+}
+
+static struct doveadm_mail_cmd_context *
+cmd_sieve_get_alloc(void)
+{
+	struct doveadm_sieve_get_cmd_context *ctx;
+
+	ctx = doveadm_sieve_cmd_alloc(struct doveadm_sieve_get_cmd_context);
+	ctx->ctx.ctx.v.init = cmd_sieve_get_init;
+	ctx->ctx.v.run = cmd_sieve_get_run;
+	doveadm_print_init("pager");
+	return &ctx->ctx.ctx;
+}
+
+struct doveadm_cmd_ver2 doveadm_sieve_cmd_get = {
+	.name = "sieve get",
+	.mail_cmd = cmd_sieve_get_alloc,
+	.usage = DOVEADM_CMD_MAIL_USAGE_PREFIX"<scriptname>",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAM('\0',"scriptname",CMD_PARAM_STR,CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+};
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/doveadm-sieve/doveadm-sieve-cmd-list.c
@@ -0,0 +1,79 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "doveadm-print.h"
+#include "doveadm-mail.h"
+
+#include "sieve.h"
+#include "sieve-storage.h"
+
+#include "doveadm-sieve-cmd.h"
+
+static int
+cmd_sieve_list_run(struct doveadm_sieve_cmd_context *_ctx)
+{
+	struct sieve_storage *storage = _ctx->storage;
+	struct sieve_storage_list_context *lctx;
+	enum sieve_error error;
+	const char *scriptname;
+	bool active;
+
+	if ( (lctx = sieve_storage_list_init(storage))
+		== NULL ) {
+		i_error("Listing Sieve scripts failed: %s",
+			sieve_storage_get_last_error(storage, &error));
+		doveadm_sieve_cmd_failed_error(_ctx, error);
+		return -1;
+	}
+
+	while ( (scriptname=sieve_storage_list_next(lctx, &active))
+		!= NULL ) {
+		doveadm_print(scriptname);
+		if ( active )
+			doveadm_print("ACTIVE");
+		else
+			doveadm_print("");
+	}
+
+	if ( sieve_storage_list_deinit(&lctx) < 0 ) {
+		i_error("Listing Sieve scripts failed: %s",
+			sieve_storage_get_last_error(storage, &error));
+		doveadm_sieve_cmd_failed_error(_ctx, error);
+		return -1;
+	}
+	return 0;
+}
+
+static void cmd_sieve_list_init
+(struct doveadm_mail_cmd_context *_ctx ATTR_UNUSED,
+	const char *const args[] ATTR_UNUSED)
+{
+	doveadm_print_header("script", "script",
+		DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE);
+	doveadm_print_header("active", "active",
+		DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE);
+}
+
+static struct doveadm_mail_cmd_context *
+cmd_sieve_list_alloc(void)
+{
+	struct doveadm_sieve_cmd_context *ctx;
+
+	ctx = doveadm_sieve_cmd_alloc(struct doveadm_sieve_cmd_context);
+	ctx->ctx.v.init = cmd_sieve_list_init;
+	ctx->ctx.getopt_args = "s";
+	ctx->v.run = cmd_sieve_list_run;
+	doveadm_print_init(DOVEADM_PRINT_TYPE_FLOW);
+	return &ctx->ctx;
+}
+
+struct doveadm_cmd_ver2 doveadm_sieve_cmd_list = {
+	.name = "sieve list",
+	.mail_cmd = cmd_sieve_list_alloc,
+	.usage = DOVEADM_CMD_MAIL_USAGE_PREFIX,
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAMS_END
+};
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/doveadm-sieve/doveadm-sieve-cmd-put.c
@@ -0,0 +1,187 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "istream.h"
+#include "doveadm-mail.h"
+
+#include "sieve.h"
+#include "sieve-script.h"
+#include "sieve-storage.h"
+
+#include "doveadm-sieve-cmd.h"
+
+struct doveadm_sieve_put_cmd_context {
+	struct doveadm_sieve_cmd_context ctx;
+
+	const char *scriptname;
+	
+	bool activate:1;
+};
+
+static int cmd_sieve_put_run
+(struct doveadm_sieve_cmd_context *_ctx)
+{
+	struct doveadm_sieve_put_cmd_context *ctx =
+		(struct doveadm_sieve_put_cmd_context *)_ctx;
+	struct sieve_storage_save_context *save_ctx;
+	struct sieve_storage *storage = _ctx->storage;
+	struct istream *input = _ctx->ctx.cmd_input;
+	enum sieve_error error;
+	ssize_t ret;
+	bool save_failed = FALSE;
+
+	save_ctx = sieve_storage_save_init
+		(storage, ctx->scriptname, input);
+	if ( save_ctx == NULL ) {
+		i_error("Saving failed: %s",
+			sieve_storage_get_last_error(storage, &error));
+		doveadm_sieve_cmd_failed_error(_ctx, error);
+		return -1;
+	}
+
+	while ( (ret = i_stream_read(input)) > 0 || ret == -2 ) {
+		if ( sieve_storage_save_continue(save_ctx) < 0 ) {
+			save_failed = TRUE;
+			ret = -1;
+			break;
+		}
+	}
+	i_assert(ret == -1);
+
+	if ( input->stream_errno != 0 ) {
+		i_error("read(script input) failed: %s", i_stream_get_error(input));
+		doveadm_sieve_cmd_failed_error
+			(&ctx->ctx, SIEVE_ERROR_TEMP_FAILURE);
+	} else if ( save_failed ) {
+		i_error("Saving failed: %s",
+			sieve_storage_get_last_error(storage, NULL));
+		doveadm_sieve_cmd_failed_storage(&ctx->ctx, storage);
+	} else if ( sieve_storage_save_finish(save_ctx) < 0 ) {
+		i_error("Saving failed: %s",
+			sieve_storage_get_last_error(storage, NULL));
+		doveadm_sieve_cmd_failed_storage(&ctx->ctx, storage);
+	} else {
+		ret = 0;
+	}
+
+	/* Verify that script compiles */
+	if ( ret == 0 ) {
+		struct sieve_error_handler *ehandler;
+		enum sieve_compile_flags cpflags =
+			SIEVE_COMPILE_FLAG_NOGLOBAL | SIEVE_COMPILE_FLAG_UPLOADED;
+		struct sieve_script *script;
+		struct sieve_binary *sbin;
+
+		/* Obtain script object for uploaded script */
+		script = sieve_storage_save_get_tempscript(save_ctx);
+
+		/* Check result */
+		if ( script == NULL ) {
+			i_error("Saving failed: %s",
+				sieve_storage_get_last_error(storage, &error));
+			doveadm_sieve_cmd_failed_error(_ctx, error);
+			ret = -1;
+
+		} else {
+			/* Mark this as an activation when we are replacing the active script */
+			if ( ctx->activate || sieve_storage_save_will_activate(save_ctx) )
+				cpflags |= SIEVE_COMPILE_FLAG_ACTIVATED;
+
+			/* Compile */
+			ehandler = sieve_master_ehandler_create(ctx->ctx.svinst, NULL, 0);
+			if ( (sbin=sieve_compile_script
+				(script, ehandler, cpflags, &error)) == NULL ) {
+				doveadm_sieve_cmd_failed_error(_ctx, error);
+				ret = -1;
+			} else {
+				sieve_close(&sbin);
+
+				/* Script is valid; commit it to storage */
+				ret = sieve_storage_save_commit(&save_ctx);
+				if (ret < 0) {
+					i_error("Saving failed: %s",
+						sieve_storage_get_last_error(storage, &error));
+					doveadm_sieve_cmd_failed_error(_ctx, error);
+					ret = -1;
+				}
+			}
+			sieve_error_handler_unref(&ehandler);
+		}
+	}
+
+	if ( save_ctx != NULL )
+		sieve_storage_save_cancel(&save_ctx);
+
+	if ( ctx->activate && ret == 0 ) {
+		struct sieve_script *script = sieve_storage_open_script
+			(storage, ctx->scriptname, NULL);
+		if ( script == NULL ||
+			sieve_script_activate(script, (time_t)-1) < 0) {
+			i_error("Failed to activate Sieve script: %s",
+				sieve_storage_get_last_error(storage, &error));
+			doveadm_sieve_cmd_failed_error(_ctx, error);
+			ret = -1;
+		}
+	}
+
+	i_assert(input->eof);
+	return ret < 0 ? -1 : 0;
+}
+
+static void cmd_sieve_put_init
+(struct doveadm_mail_cmd_context *_ctx,
+	const char *const args[])
+{
+	struct doveadm_sieve_put_cmd_context *ctx =
+		(struct doveadm_sieve_put_cmd_context *)_ctx;
+
+	if ( str_array_length(args) != 1 )
+		doveadm_mail_help_name("sieve put");
+	doveadm_sieve_cmd_scriptnames_check(args);
+
+	ctx->scriptname = p_strdup(ctx->ctx.ctx.pool, args[0]);
+
+	doveadm_mail_get_input(_ctx);
+}
+
+static bool
+cmd_sieve_put_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c)
+{
+	struct doveadm_sieve_put_cmd_context *ctx =
+		(struct doveadm_sieve_put_cmd_context *)_ctx;
+
+	switch ( c ) {
+	case 'a':
+		ctx->activate = TRUE;
+		break;
+	default:
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static struct doveadm_mail_cmd_context *
+cmd_sieve_put_alloc(void)
+{
+	struct doveadm_sieve_put_cmd_context *ctx;
+
+	ctx = doveadm_sieve_cmd_alloc(struct doveadm_sieve_put_cmd_context);
+	ctx->ctx.ctx.getopt_args = "a";
+	ctx->ctx.ctx.v.parse_arg = cmd_sieve_put_parse_arg;
+	ctx->ctx.ctx.v.init = cmd_sieve_put_init;
+	ctx->ctx.v.run = cmd_sieve_put_run;
+	return &ctx->ctx.ctx;
+}
+
+struct doveadm_cmd_ver2 doveadm_sieve_cmd_put = {
+	.name = "sieve put",
+	.mail_cmd = cmd_sieve_put_alloc,
+	.usage = DOVEADM_CMD_MAIL_USAGE_PREFIX"[-a] <scriptname>",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAM('a',"activate",CMD_PARAM_BOOL,0)
+DOVEADM_CMD_PARAM('\0',"scriptname",CMD_PARAM_STR,CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAM('\0',"file",CMD_PARAM_ISTREAM,CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+};
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/doveadm-sieve/doveadm-sieve-cmd-rename.c
@@ -0,0 +1,83 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "doveadm-mail.h"
+
+#include "sieve.h"
+#include "sieve-script.h"
+#include "sieve-storage.h"
+
+#include "doveadm-sieve-cmd.h"
+
+struct doveadm_sieve_rename_cmd_context {
+	struct doveadm_sieve_cmd_context ctx;
+
+	const char *oldname, *newname;
+};
+
+static int
+cmd_sieve_rename_run(struct doveadm_sieve_cmd_context *_ctx)
+{
+	struct doveadm_sieve_rename_cmd_context *ctx =
+		(struct doveadm_sieve_rename_cmd_context *)_ctx;
+	struct sieve_storage *storage = _ctx->storage;
+	struct sieve_script *script;
+	enum sieve_error error;
+	int ret = 0;
+
+	script = sieve_storage_open_script
+		(storage, ctx->oldname, NULL);
+	if ( script == NULL ) {
+		i_error("Failed to rename Sieve script: %s",
+			sieve_storage_get_last_error(storage, &error));
+		doveadm_sieve_cmd_failed_error(_ctx, error);
+		ret = -1;
+	} else if ( sieve_script_rename(script, ctx->newname) < 0 ) {
+		i_error("Failed to rename Sieve script: %s",
+			sieve_storage_get_last_error(storage, &error));
+		doveadm_sieve_cmd_failed_error(_ctx, error);
+		ret = -1;
+	}
+
+	if ( script != NULL )
+		sieve_script_unref(&script);
+	return ret;
+}
+
+static void cmd_sieve_rename_init
+(struct doveadm_mail_cmd_context *_ctx,
+	const char *const args[])
+{
+	struct doveadm_sieve_rename_cmd_context *ctx =
+		(struct doveadm_sieve_rename_cmd_context *)_ctx;
+
+	if ( str_array_length(args) != 2 )
+		doveadm_mail_help_name("sieve rename");
+	doveadm_sieve_cmd_scriptnames_check(args);
+
+	ctx->oldname = p_strdup(ctx->ctx.ctx.pool, args[0]);
+	ctx->newname = p_strdup(ctx->ctx.ctx.pool, args[1]);
+}
+
+static struct doveadm_mail_cmd_context *
+cmd_sieve_rename_alloc(void)
+{
+	struct doveadm_sieve_rename_cmd_context *ctx;
+
+	ctx = doveadm_sieve_cmd_alloc(struct doveadm_sieve_rename_cmd_context);
+	ctx->ctx.ctx.v.init = cmd_sieve_rename_init;
+	ctx->ctx.v.run = cmd_sieve_rename_run;
+	return &ctx->ctx.ctx;
+}
+
+struct doveadm_cmd_ver2 doveadm_sieve_cmd_rename = {
+	.name = "sieve rename",
+	.mail_cmd = cmd_sieve_rename_alloc,
+	.usage = DOVEADM_CMD_MAIL_USAGE_PREFIX"<oldname> <newname>",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAM('\0',"oldname",CMD_PARAM_STR,CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAM('\0',"newname",CMD_PARAM_STR,CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+};
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/doveadm-sieve/doveadm-sieve-cmd.c
@@ -0,0 +1,179 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "unichar.h"
+#include "mail-storage.h"
+#include "doveadm-mail.h"
+
+#include "sieve.h"
+#include "sieve-script.h"
+#include "sieve-storage.h"
+
+#include "doveadm-sieve-cmd.h"
+
+void doveadm_sieve_cmd_failed_error
+(struct doveadm_sieve_cmd_context *ctx, enum sieve_error error)
+{
+	struct doveadm_mail_cmd_context *mctx = &ctx->ctx;
+	int exit_code = 0;
+
+	switch ( error ) {
+	case SIEVE_ERROR_NONE:
+		i_unreached();
+		return;
+	case SIEVE_ERROR_TEMP_FAILURE:
+		exit_code = EX_TEMPFAIL;
+		break;
+	case SIEVE_ERROR_NOT_POSSIBLE:
+	case SIEVE_ERROR_EXISTS:
+	case SIEVE_ERROR_NOT_VALID:
+	case SIEVE_ERROR_ACTIVE:
+		exit_code = DOVEADM_EX_NOTPOSSIBLE;
+		break;
+	case SIEVE_ERROR_BAD_PARAMS:
+		exit_code = EX_USAGE;
+		break;
+	case SIEVE_ERROR_NO_PERMISSION:
+		exit_code = EX_NOPERM;
+		break;
+	case SIEVE_ERROR_NO_QUOTA:
+		exit_code = EX_CANTCREAT;
+		break;
+	case SIEVE_ERROR_NOT_FOUND:
+		exit_code = DOVEADM_EX_NOTFOUND;
+		break;
+	default:
+		i_unreached();
+	}
+	/* tempfail overrides all other exit codes, otherwise use whatever
+	   error happened first */
+	if ( mctx->exit_code == 0 || exit_code == EX_TEMPFAIL )
+		mctx->exit_code = exit_code;
+}
+
+void doveadm_sieve_cmd_failed_storage
+(struct doveadm_sieve_cmd_context *ctx,	 struct sieve_storage *storage)
+{
+	enum sieve_error error;
+
+	(void)sieve_storage_get_last_error(storage, &error);
+	doveadm_sieve_cmd_failed_error(ctx, error);
+}
+
+static const char *doveadm_sieve_cmd_get_setting
+(void *context, const char *identifier)
+{
+	struct doveadm_sieve_cmd_context *ctx =
+		(struct doveadm_sieve_cmd_context *) context;
+
+	return mail_user_plugin_getenv(ctx->ctx.cur_mail_user, identifier);
+}
+
+static const struct sieve_callbacks sieve_callbacks = {
+	NULL,
+	doveadm_sieve_cmd_get_setting
+};
+
+static bool doveadm_sieve_cmd_parse_arg
+(struct doveadm_mail_cmd_context *_ctx ATTR_UNUSED,
+	int c ATTR_UNUSED)
+{
+	return FALSE;
+}
+
+void doveadm_sieve_cmd_scriptnames_check(const char *const args[])
+{
+	unsigned int i;
+
+	for (i = 0; args[i] != NULL; i++) {
+		if (!uni_utf8_str_is_valid(args[i])) {
+			i_fatal_status(EX_DATAERR,
+				"Sieve script name not valid UTF-8: %s", args[i]);
+		}
+		if ( !sieve_script_name_is_valid(args[i]) ) {
+			i_fatal_status(EX_DATAERR,
+				"Sieve script name not valid: %s", args[i]);
+		}
+	}
+}
+
+static int
+doveadm_sieve_cmd_run
+(struct doveadm_mail_cmd_context *_ctx,
+	struct mail_user *user)
+{
+	struct doveadm_sieve_cmd_context *ctx =
+		(struct doveadm_sieve_cmd_context *)_ctx;
+	struct sieve_environment svenv;
+	enum sieve_error error;
+	int ret;
+
+	memset((void*)&svenv, 0, sizeof(svenv));
+	svenv.username = user->username;
+	(void)mail_user_get_home(user, &svenv.home_dir);
+	svenv.base_dir = user->set->base_dir;
+	svenv.flags = SIEVE_FLAG_HOME_RELATIVE;
+
+	ctx->svinst = sieve_init
+		(&svenv, &sieve_callbacks, (void *)ctx, user->mail_debug);
+
+	ctx->storage = sieve_storage_create_main
+		(ctx->svinst, user, SIEVE_STORAGE_FLAG_READWRITE, &error);
+	if ( ctx->storage == NULL ) {
+		switch ( error ) {
+		case SIEVE_ERROR_NOT_POSSIBLE:
+			error = SIEVE_ERROR_NOT_FOUND;
+			i_error("Failed to open Sieve storage: "
+				"Sieve is disabled for this user");
+			break;
+		case SIEVE_ERROR_NOT_FOUND:
+			i_error("Failed to open Sieve storage: "
+				"User cannot manage personal Sieve scripts.");
+			break;
+		default:
+			i_error("Failed to open Sieve storage.");
+		}
+		doveadm_sieve_cmd_failed_error(ctx, error);
+		ret =  -1;
+
+	} else {
+		i_assert( ctx->v.run != NULL );
+		ret = ctx->v.run(ctx);
+		sieve_storage_unref(&ctx->storage);
+	}
+
+	sieve_deinit(&ctx->svinst);
+	return ret;
+}
+
+struct doveadm_sieve_cmd_context *
+doveadm_sieve_cmd_alloc_size(size_t size)
+{
+	struct doveadm_sieve_cmd_context *ctx;
+
+	ctx = (struct doveadm_sieve_cmd_context *)
+		doveadm_mail_cmd_alloc_size(size);
+	ctx->ctx.getopt_args = "s";
+	ctx->ctx.v.parse_arg = doveadm_sieve_cmd_parse_arg;
+	ctx->ctx.v.run = doveadm_sieve_cmd_run;
+	return ctx;
+}
+
+static struct doveadm_cmd_ver2 *doveadm_sieve_commands[] = {
+	&doveadm_sieve_cmd_list,
+	&doveadm_sieve_cmd_get,
+	&doveadm_sieve_cmd_put,
+	&doveadm_sieve_cmd_delete,
+	&doveadm_sieve_cmd_activate,
+	&doveadm_sieve_cmd_deactivate,
+	&doveadm_sieve_cmd_rename
+};
+
+void doveadm_sieve_cmds_init(void)
+{
+	unsigned int i;
+
+	for (i = 0; i < N_ELEMENTS(doveadm_sieve_commands); i++)
+		doveadm_cmd_register_ver2(doveadm_sieve_commands[i]);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/doveadm-sieve/doveadm-sieve-cmd.h
@@ -0,0 +1,43 @@
+#ifndef DOVEADM_SIEVE_CMD_H
+#define DOVEADM_SIEVE_CMD_H
+
+struct doveadm_sieve_cmd_context;
+
+struct doveadm_sieve_cmd_vfuncs {
+	/* This is the main function which performs all the work for the
+	   command. This is called once per each user. */
+	int (*run)(struct doveadm_sieve_cmd_context *ctx);
+};
+
+struct doveadm_sieve_cmd_context {
+	struct doveadm_mail_cmd_context ctx;
+
+	struct sieve_instance *svinst;
+	struct sieve_storage *storage;
+
+	struct doveadm_sieve_cmd_vfuncs v;
+};
+
+void doveadm_sieve_cmd_failed_error
+(struct doveadm_sieve_cmd_context *ctx, enum sieve_error error);
+void doveadm_sieve_cmd_failed_storage
+(struct doveadm_sieve_cmd_context *ctx,	 struct sieve_storage *storage);
+
+#define doveadm_sieve_cmd_alloc(type) \
+	(type *)doveadm_sieve_cmd_alloc_size(sizeof(type))
+struct doveadm_sieve_cmd_context *
+doveadm_sieve_cmd_alloc_size(size_t size);
+
+void doveadm_sieve_cmd_scriptnames_check(const char *const args[]);
+
+extern struct doveadm_cmd_ver2 doveadm_sieve_cmd_list;
+extern struct doveadm_cmd_ver2 doveadm_sieve_cmd_get;
+extern struct doveadm_cmd_ver2 doveadm_sieve_cmd_put;
+extern struct doveadm_cmd_ver2 doveadm_sieve_cmd_delete;
+extern struct doveadm_cmd_ver2 doveadm_sieve_cmd_activate;
+extern struct doveadm_cmd_ver2 doveadm_sieve_cmd_deactivate;
+extern struct doveadm_cmd_ver2 doveadm_sieve_cmd_rename;
+
+void doveadm_sieve_cmds_init(void);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/doveadm-sieve/doveadm-sieve-plugin.c
@@ -0,0 +1,24 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "doveadm-mail.h"
+
+#include "sieve.h"
+
+#include "doveadm-sieve-cmd.h"
+#include "doveadm-sieve-plugin.h"
+
+const char *doveadm_sieve_plugin_version = DOVECOT_ABI_VERSION;
+
+void doveadm_sieve_plugin_init(struct module *module)
+{
+	doveadm_sieve_sync_init(module);
+	doveadm_sieve_cmds_init();
+}
+
+void doveadm_sieve_plugin_deinit(void)
+{
+	/* the hooks array is freed already */
+	/*mail_storage_hooks_remove(&doveadm_sieve_mail_storage_hooks);*/
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/doveadm-sieve/doveadm-sieve-plugin.h
@@ -0,0 +1,17 @@
+#ifndef DOVEADM_SIEVE_PLUGIN_H
+#define DOVEADM_SIEVE_PLUGIN_H
+
+/*
+ * Plugin interface
+ */
+
+void doveadm_sieve_plugin_init(struct module *module);
+void doveadm_sieve_plugin_deinit(void);
+
+/*
+ * Replication
+ */
+
+void doveadm_sieve_sync_init(struct module *module);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/doveadm-sieve/doveadm-sieve-sync.c
@@ -0,0 +1,747 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "ioloop.h"
+#include "time-util.h"
+#include "istream.h"
+#include "istream-concat.h"
+#include "mail-storage-private.h"
+
+#include "sieve.h"
+#include "sieve-script.h"
+#include "sieve-storage.h"
+
+#include "doveadm-sieve-plugin.h"
+
+#define SIEVE_MAIL_CONTEXT(obj) \
+	MODULE_CONTEXT_REQUIRE(obj, sieve_storage_module)
+#define SIEVE_USER_CONTEXT(obj) \
+	MODULE_CONTEXT_REQUIRE(obj, sieve_user_module)
+
+struct sieve_mail_user {
+	union mail_user_module_context module_ctx;
+
+	struct sieve_instance *svinst;
+	struct sieve_storage *sieve_storage;
+};
+
+struct sieve_mailbox_attribute_iter {
+	struct mailbox_attribute_iter iter;
+	struct mailbox_attribute_iter *super;
+
+	struct sieve_storage_list_context *sieve_list;
+	string_t *name;
+
+	bool failed;
+	bool have_active;
+};
+
+static MODULE_CONTEXT_DEFINE_INIT(sieve_storage_module,
+				  &mail_storage_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(sieve_user_module,
+				  &mail_user_module_register);
+
+static const char *
+mail_sieve_get_setting(void *context, const char *identifier)
+{
+	struct mail_user *mail_user = context;
+
+	return mail_user_plugin_getenv(mail_user, identifier);
+}
+
+static const struct sieve_callbacks mail_sieve_callbacks = {
+	NULL,
+	mail_sieve_get_setting
+};
+
+static void mail_sieve_user_deinit(struct mail_user *user)
+{
+	struct sieve_mail_user *suser = SIEVE_USER_CONTEXT(user);
+
+	if ( suser->svinst != NULL ) {
+		if (suser->sieve_storage != NULL)
+			sieve_storage_unref(&suser->sieve_storage);
+		sieve_deinit(&suser->svinst);
+	}
+
+	suser->module_ctx.super.deinit(user);
+}
+
+static int
+mail_sieve_user_init
+(struct mail_user *user, struct sieve_storage **svstorage_r)
+{
+	struct sieve_mail_user *suser = SIEVE_USER_CONTEXT(user);
+	enum sieve_storage_flags storage_flags =
+		SIEVE_STORAGE_FLAG_READWRITE |
+		SIEVE_STORAGE_FLAG_SYNCHRONIZING;
+	struct sieve_environment svenv;
+
+	if ( suser->svinst != NULL ) {
+		*svstorage_r = suser->sieve_storage;
+		return suser->sieve_storage != NULL ? 1 : 0;
+	}
+
+	/* Delayed initialization of sieve storage until it's actually needed */
+	i_zero(&svenv);
+	svenv.username = user->username;
+	(void)mail_user_get_home(user, &svenv.home_dir);
+	svenv.base_dir = user->set->base_dir;
+	svenv.flags = SIEVE_FLAG_HOME_RELATIVE;
+
+	suser->svinst = sieve_init(&svenv, &mail_sieve_callbacks,
+				   user, user->mail_debug);
+	suser->sieve_storage = sieve_storage_create_main
+			(suser->svinst, user, storage_flags, NULL);
+
+	*svstorage_r = suser->sieve_storage;
+	return suser->sieve_storage != NULL ? 1 : 0;
+}
+
+static int sieve_attribute_unset_script(struct mail_storage *storage,
+					struct sieve_storage *svstorage,
+					const char *scriptname)
+{
+	struct sieve_script *script;
+	const char *errstr;
+	enum sieve_error error;
+	int ret = 0;
+
+	script = sieve_storage_open_script(svstorage, scriptname, NULL);
+	if (script == NULL) { 
+		ret = -1;
+	} else {
+		ret = sieve_script_delete(script, TRUE);
+		sieve_script_unref(&script);
+	}
+
+	if (ret < 0) {
+		errstr = sieve_storage_get_last_error(svstorage, &error);
+		if (error == SIEVE_ERROR_NOT_FOUND) {
+			/* already deleted, ignore */
+			return 0;
+		}
+		mail_storage_set_critical(storage,
+			"Failed to delete Sieve script '%s': %s", scriptname,
+			errstr);
+		return -1;
+	}
+	return 0;
+}
+
+static int
+sieve_attribute_set_active(struct mail_storage *storage,
+			   struct sieve_storage *svstorage,
+			   const struct mail_attribute_value *value)
+{
+	const char *scriptname;
+	struct sieve_script *script;
+	time_t last_change =
+		(value->last_change == 0 ? ioloop_time : value->last_change);
+	int ret;
+
+	if (mailbox_attribute_value_to_string(storage, value, &scriptname) < 0)
+		return -1;
+
+	if (scriptname == NULL) {
+		/* don't affect non-link active script */
+		if ((ret=sieve_storage_is_singular(svstorage)) != 0) {
+			if (ret < 0) {
+				mail_storage_set_internal_error(storage);
+				return -1;
+			}
+			return 0;
+		}
+
+		/* deactivate current script */
+		if (sieve_storage_deactivate(svstorage, last_change) < 0) {
+			mail_storage_set_critical(storage,
+				"Failed to deactivate Sieve: %s",
+				sieve_storage_get_last_error(svstorage, NULL));
+			return -1;
+		}
+		return 0;
+	}
+	i_assert(scriptname[0] == MAILBOX_ATTRIBUTE_SIEVE_DEFAULT_LINK);
+	scriptname++;
+
+	/* activate specified script */
+	script = sieve_storage_open_script(svstorage, scriptname, NULL);
+	ret = script == NULL ? -1 :
+		sieve_script_activate(script, last_change);
+	if (ret < 0) {
+		mail_storage_set_critical(storage,
+			"Failed to activate Sieve script '%s': %s", scriptname,
+			sieve_storage_get_last_error(svstorage, NULL));
+	}
+	if (script != NULL)
+		sieve_script_unref(&script);
+	sieve_storage_set_modified(svstorage, last_change);
+	return ret;
+}
+
+static int
+sieve_attribute_unset_active_script(struct mail_storage *storage,
+			   struct sieve_storage *svstorage, time_t last_change)
+{
+	int ret;
+
+	if ((ret=sieve_storage_is_singular(svstorage)) != 0) {
+		if (ret < 0)
+			mail_storage_set_internal_error(storage);
+		return ret;
+	}
+
+	if (sieve_storage_deactivate(svstorage, last_change) < 0) {
+		mail_storage_set_critical(storage,
+			"Failed to deactivate sieve: %s",
+			sieve_storage_get_last_error(svstorage, NULL));
+		return -1;
+	}
+	return 0;
+}
+
+static int
+sieve_attribute_set_active_script(struct mail_storage *storage,
+			   struct sieve_storage *svstorage,
+			   const struct mail_attribute_value *value)
+{
+	struct istream *input;
+	time_t last_change =
+		(value->last_change == 0 ? ioloop_time : value->last_change);
+
+	if (value->value != NULL) {
+		input = i_stream_create_from_data(value->value, strlen(value->value));
+	} else if (value->value_stream != NULL) {
+		input = value->value_stream;
+		i_stream_ref(input);
+	} else {
+		return sieve_attribute_unset_active_script
+			(storage, svstorage, last_change);
+	}
+	/* skip over the 'S' type */
+	i_stream_skip(input, 1);
+
+	if (sieve_storage_save_as_active
+		(svstorage, input, last_change) < 0) {
+		mail_storage_set_critical(storage,
+			"Failed to save active sieve script: %s",
+			sieve_storage_get_last_error(svstorage, NULL));
+		i_stream_unref(&input);
+		return -1;
+	}
+
+	sieve_storage_set_modified(svstorage, last_change);
+	i_stream_unref(&input);
+	return 0;
+}
+
+static int
+sieve_attribute_set_default(struct mail_storage *storage,
+			    struct sieve_storage *svstorage,
+			    const struct mail_attribute_value *value)
+{
+	const unsigned char *data;
+	size_t size;
+	ssize_t ret;
+	char type;
+
+	if (value->value != NULL) {
+		type = value->value[0];
+	} else if (value->value_stream != NULL) {
+		ret = i_stream_read_more(value->value_stream, &data, &size);
+		if (ret == -1) {
+			mail_storage_set_critical(storage, "read(%s) failed: %m",
+				i_stream_get_name(value->value_stream));
+			return -1;
+		}
+		i_assert(ret > 0);
+		type = data[0];
+	} else {
+		type = MAILBOX_ATTRIBUTE_SIEVE_DEFAULT_SCRIPT;
+	}
+	if (type == MAILBOX_ATTRIBUTE_SIEVE_DEFAULT_LINK)
+		return sieve_attribute_set_active(storage, svstorage, value);
+	if (type == MAILBOX_ATTRIBUTE_SIEVE_DEFAULT_SCRIPT)
+		return sieve_attribute_set_active_script(storage, svstorage, value);
+	mail_storage_set_error(storage, MAIL_ERROR_PARAMS,
+			       "Invalid value for default sieve attribute");
+	return -1;
+}
+
+static int
+sieve_attribute_set_sieve(struct mail_storage *storage,
+			  const char *key,
+			  const struct mail_attribute_value *value)
+{
+	struct sieve_storage *svstorage;
+	struct sieve_storage_save_context *save_ctx;
+	struct istream *input;
+	const char *scriptname;
+	int ret;
+
+	if ((ret = mail_sieve_user_init(storage->user, &svstorage)) <= 0) {
+		if (ret == 0) {
+			mail_storage_set_error(storage, MAIL_ERROR_NOTFOUND,
+					       "Sieve not enabled for user");
+		}
+		return -1;
+	}
+
+	if (strcmp(key, MAILBOX_ATTRIBUTE_SIEVE_DEFAULT) == 0)
+		return sieve_attribute_set_default(storage, svstorage, value);
+	if (!str_begins(key, MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES)) {
+		mail_storage_set_error(storage, MAIL_ERROR_NOTFOUND,
+				       "Nonexistent sieve attribute");
+		return -1;
+	}
+	scriptname = key + strlen(MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES);
+
+	if (value->value != NULL) {
+		input = i_stream_create_from_data(value->value,
+						  strlen(value->value));
+		save_ctx = sieve_storage_save_init(svstorage, scriptname, input);
+	} else if (value->value_stream != NULL) {
+		input = value->value_stream;
+		i_stream_ref(input);
+		save_ctx = sieve_storage_save_init(svstorage, scriptname, input);
+	} else {
+		return sieve_attribute_unset_script(storage, svstorage, scriptname);
+	}
+
+	if (save_ctx == NULL) {
+		/* save initialization failed */
+		mail_storage_set_critical(storage,
+			"Failed to save sieve script '%s': %s", scriptname,
+			sieve_storage_get_last_error(svstorage, NULL));
+		i_stream_unref(&input);
+		return -1;
+	}
+
+	if (value->last_change != 0)
+		sieve_storage_save_set_mtime(save_ctx, value->last_change);
+
+	ret = 0;
+	while (input->stream_errno == 0 &&
+		!i_stream_read_eof(input)) {
+		if (sieve_storage_save_continue(save_ctx) < 0) {
+			mail_storage_set_critical(storage,
+				"Failed to save sieve script '%s': %s", scriptname,
+				sieve_storage_get_last_error(svstorage, NULL));
+			ret = -1;
+			break;
+		}
+	}
+	if (input->stream_errno != 0) {
+		errno = input->stream_errno;
+		mail_storage_set_critical(storage,
+			"Saving sieve script: read(%s) failed: %m",
+			i_stream_get_name(input));
+		ret = -1;
+	}
+	i_assert(input->eof || ret < 0);
+	if (ret == 0 && sieve_storage_save_finish(save_ctx) < 0) {
+		mail_storage_set_critical(storage,
+			"Failed to save sieve script '%s': %s", scriptname,
+			sieve_storage_get_last_error(svstorage, NULL));
+		ret = -1;
+	}
+	if (ret < 0)
+		sieve_storage_save_cancel(&save_ctx);
+	else if (sieve_storage_save_commit(&save_ctx) < 0) {
+		mail_storage_set_critical(storage,
+			"Failed to save sieve script '%s': %s", scriptname,
+			sieve_storage_get_last_error(svstorage, NULL));
+		ret = -1;
+	}
+	i_stream_unref(&input);
+	return ret;
+}
+
+static int
+sieve_attribute_set(struct mailbox_transaction_context *t,
+		    enum mail_attribute_type type, const char *key,
+		    const struct mail_attribute_value *value)
+{
+	struct mail_user *user = t->box->storage->user;
+	union mailbox_module_context *sbox = SIEVE_MAIL_CONTEXT(t->box);
+
+	if (t->box->storage->user->dsyncing &&
+	    type == MAIL_ATTRIBUTE_TYPE_PRIVATE &&
+	    str_begins(key, MAILBOX_ATTRIBUTE_PREFIX_SIEVE)) {
+		time_t ts =
+			(value->last_change != 0 ? value->last_change : ioloop_time);
+
+		if (sieve_attribute_set_sieve(t->box->storage, key, value) < 0)
+			return -1;
+
+		if (user->mail_debug) {
+			const char *change;
+			if (value->last_change != 0) {
+				change = t_strflocaltime
+					("(last change: %Y-%m-%d %H:%M:%S)", value->last_change);
+			} else {
+				change = t_strflocaltime
+					("(time: %Y-%m-%d %H:%M:%S)", ioloop_time);
+			}
+			i_debug("doveadm-sieve: Assigned value for key `%s' %s",
+				key, change);
+		}
+
+		/* FIXME: set value len to sieve script size / active name
+		   length */
+		if (value->value != NULL || value->value_stream != NULL)
+			mail_index_attribute_set(t->itrans, TRUE, key, ts, 0);
+		else
+			mail_index_attribute_unset(t->itrans, TRUE, key, ts);
+		return 0;
+	}
+	return sbox->super.attribute_set(t, type, key, value);
+}
+
+static int
+sieve_attribute_retrieve_script(struct mail_storage *storage,
+			   struct sieve_storage *svstorage, struct sieve_script *script,
+			   bool add_type_prefix,
+			   struct mail_attribute_value *value_r, const char **errorstr_r)
+{
+	static char type = MAILBOX_ATTRIBUTE_SIEVE_DEFAULT_SCRIPT;
+	struct istream *input, *inputs[3];
+	const struct stat *st;
+	enum sieve_error error;
+
+	if (script == NULL)
+		*errorstr_r = sieve_storage_get_last_error(svstorage, &error);
+	else if (sieve_script_get_stream(script, &input, &error) < 0)
+		sieve_script_unref(&script);
+	
+	if (script == NULL) {
+		if (error == SIEVE_ERROR_NOT_FOUND) {
+			/* already deleted, but return the last_change */
+			(void)sieve_storage_get_last_change(svstorage,
+							    &value_r->last_change);
+			return 0;
+		}
+		*errorstr_r = sieve_storage_get_last_error(svstorage, &error);
+		return -1;
+	}
+	if (i_stream_stat(input, FALSE, &st) < 0) {
+		mail_storage_set_critical(storage,
+			"stat(%s) failed: %m", i_stream_get_name(input));
+	} else {
+		value_r->last_change = st->st_mtime;
+	}
+	if (!add_type_prefix) {
+		i_stream_ref(input);
+		value_r->value_stream = input;
+	} else {
+		inputs[0] = i_stream_create_from_data(&type, 1);
+		inputs[1] = input;
+		inputs[2] = NULL;
+		value_r->value_stream = i_stream_create_concat(inputs);
+		i_stream_unref(&inputs[0]);
+	}
+	sieve_script_unref(&script);
+	return 1;
+}
+
+static int
+sieve_attribute_get_active_script(struct mail_storage *storage,
+			   struct sieve_storage *svstorage,
+			   struct mail_attribute_value *value_r)
+{
+	struct sieve_script *script;
+	const char *errstr;
+	int ret;
+
+	if ((ret=sieve_storage_is_singular(svstorage)) <= 0) {
+		if (ret == 0 && sieve_storage_active_script_get_last_change
+			(svstorage, &value_r->last_change) < 0) {
+			ret = -1;
+		}
+		if (ret < 0)
+			mail_storage_set_internal_error(storage);
+		return ret;
+	}
+
+	if ((script=sieve_storage_active_script_open
+		(svstorage, NULL)) == NULL)
+		return 0;
+
+	if ((ret=sieve_attribute_retrieve_script
+		(storage, svstorage, script, TRUE, value_r, &errstr)) < 0) {
+		mail_storage_set_critical(storage,
+			"Failed to access active sieve script: %s", errstr);
+	}
+	return ret;
+}
+
+static int
+sieve_attribute_get_default(struct mail_storage *storage,
+			    struct sieve_storage *svstorage,
+			    struct mail_attribute_value *value_r)
+{
+	const char *scriptname;
+	int ret;
+
+	ret = sieve_storage_active_script_get_name(svstorage, &scriptname);
+	if (ret == 0)
+		return sieve_attribute_get_active_script(storage, svstorage, value_r);
+
+	if (ret > 0) {
+		value_r->value = t_strdup_printf("%c%s",
+			MAILBOX_ATTRIBUTE_SIEVE_DEFAULT_LINK, scriptname);
+		if (sieve_storage_active_script_get_last_change
+				(svstorage, &value_r->last_change) < 0)
+			ret = -1;
+	}
+	if (ret < 0)
+		mail_storage_set_internal_error(storage);
+	return ret;
+}
+
+static int
+sieve_attribute_get_sieve(struct mail_storage *storage, const char *key,
+			  struct mail_attribute_value *value_r)
+{
+	struct sieve_storage *svstorage;
+	struct sieve_script *script;
+	const char *scriptname, *errstr;
+	int ret;
+
+	if ((ret = mail_sieve_user_init(storage->user, &svstorage)) <= 0)
+		return ret;
+
+	if (strcmp(key, MAILBOX_ATTRIBUTE_SIEVE_DEFAULT) == 0)
+		return sieve_attribute_get_default(storage, svstorage, value_r);
+	if (!str_begins(key, MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES))
+		return 0;
+	if ((value_r->flags & MAIL_ATTRIBUTE_VALUE_FLAG_INT_STREAMS) == 0) {
+		mail_storage_set_error(storage, MAIL_ERROR_PARAMS,
+			"Sieve attributes are available only as streams");
+		return -1;
+	}
+	scriptname = key + strlen(MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES);
+	script = sieve_storage_open_script(svstorage, scriptname, NULL);
+	if ((ret=sieve_attribute_retrieve_script
+		(storage, svstorage, script, FALSE, value_r, &errstr)) < 0) {
+		mail_storage_set_critical(storage,
+			"Failed to access sieve script '%s': %s",
+			scriptname, errstr);
+	}
+	return ret;
+}
+
+static int
+sieve_attribute_get(struct mailbox *box,
+		    enum mail_attribute_type type, const char *key,
+		    struct mail_attribute_value *value_r)
+{
+	union mailbox_module_context *sbox = SIEVE_MAIL_CONTEXT(box);
+	struct mail_user *user = box->storage->user;
+	int ret;
+
+	if (box->storage->user->dsyncing &&
+	    type == MAIL_ATTRIBUTE_TYPE_PRIVATE &&
+	    str_begins(key, MAILBOX_ATTRIBUTE_PREFIX_SIEVE)) {
+
+		ret = sieve_attribute_get_sieve(box->storage, key, value_r);
+		if (ret >= 0 && user->mail_debug) {
+			struct tm *tm = localtime(&value_r->last_change);
+			char str[256];
+			const char *timestamp = "";
+	
+			if (strftime(str, sizeof(str),
+				" (last change: %Y-%m-%d %H:%M:%S)", tm) > 0)
+				timestamp = str;
+
+			if (ret > 0) {
+				i_debug("doveadm-sieve: Retrieved value for key `%s'%s",
+					key, timestamp);
+			} else {
+				i_debug("doveadm-sieve: Value missing for key `%s'%s",
+					key, timestamp);
+			}
+		}
+		return ret;
+	}
+	return sbox->super.attribute_get(box, type, key, value_r);
+}
+
+static int
+sieve_attribute_iter_script_init(struct sieve_mailbox_attribute_iter *siter)
+{
+	struct mail_user *user = siter->iter.box->storage->user;
+	struct sieve_storage *svstorage;
+	int ret;
+
+	if (user->mail_debug)
+		i_debug("doveadm-sieve: Iterating Sieve mailbox attributes");
+
+	if ((ret = mail_sieve_user_init(user, &svstorage)) <= 0)
+		return ret;
+
+	siter->sieve_list = sieve_storage_list_init(svstorage);
+	if (siter->sieve_list == NULL) {
+		mail_storage_set_critical(siter->iter.box->storage,
+			"Failed to iterate sieve scripts: %s",
+			sieve_storage_get_last_error(svstorage, NULL));
+		return -1;
+	}
+	siter->name = str_new(default_pool, 128);
+	str_append(siter->name, MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES);
+	return 0;
+}
+
+static struct mailbox_attribute_iter *
+sieve_attribute_iter_init(struct mailbox *box, enum mail_attribute_type type,
+			  const char *prefix)
+{
+	union mailbox_module_context *sbox = SIEVE_MAIL_CONTEXT(box);
+	struct sieve_mailbox_attribute_iter *siter;
+
+	siter = i_new(struct sieve_mailbox_attribute_iter, 1);
+	siter->iter.box = box;
+	siter->super = sbox->super.attribute_iter_init(box, type, prefix);
+
+	if (box->storage->user->dsyncing &&
+	    type == MAIL_ATTRIBUTE_TYPE_PRIVATE &&
+	    str_begins(prefix, MAILBOX_ATTRIBUTE_PREFIX_SIEVE)) {
+		if (sieve_attribute_iter_script_init(siter) < 0)
+			siter->failed = TRUE;
+	}
+	return &siter->iter;
+}
+
+static const char *
+sieve_attribute_iter_next_script(struct sieve_mailbox_attribute_iter *siter)
+{
+	struct mail_user *user = siter->iter.box->storage->user;
+	struct sieve_mail_user *suser = SIEVE_USER_CONTEXT(user);
+	struct sieve_storage *svstorage = suser->sieve_storage;
+	const char *scriptname;
+	bool active;
+	int ret;
+
+	if (siter->sieve_list == NULL)
+		return NULL;
+
+	/* Iterate through all scripts in sieve_dir */
+	while ((scriptname = sieve_storage_list_next(siter->sieve_list, &active))
+		!= NULL) {
+		if (active)
+			siter->have_active = TRUE;
+		str_truncate(siter->name, strlen(MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES));
+		str_append(siter->name, scriptname);
+		return str_c(siter->name);
+	}
+	if (sieve_storage_list_deinit(&siter->sieve_list) < 0) {
+		mail_storage_set_critical(siter->iter.box->storage,
+			"Failed to iterate sieve scripts: %s",
+			sieve_storage_get_last_error(svstorage, NULL));
+		siter->failed = TRUE;
+		return NULL;
+	}
+
+	/* Check whether active script is a proper symlink or a regular file */
+	if ((ret=sieve_storage_is_singular(svstorage)) < 0) {
+		mail_storage_set_critical(siter->iter.box->storage,
+			"Failed to iterate sieve scripts: %s",
+			sieve_storage_get_last_error(svstorage, NULL));
+		return NULL;
+	}
+
+	/* Regular file */
+	if (ret > 0)
+		return MAILBOX_ATTRIBUTE_SIEVE_DEFAULT;
+
+	/* Symlink or none active */
+	return siter->have_active ? MAILBOX_ATTRIBUTE_SIEVE_DEFAULT : NULL;
+}
+
+static const char *
+sieve_attribute_iter_next(struct mailbox_attribute_iter *iter)
+{
+	struct sieve_mailbox_attribute_iter *siter =
+		(struct sieve_mailbox_attribute_iter *)iter;
+	union mailbox_module_context *sbox = SIEVE_MAIL_CONTEXT(iter->box);
+	struct mail_user *user = iter->box->storage->user;
+	const char *key;
+
+	if (siter->sieve_list != NULL) {
+		if ((key = sieve_attribute_iter_next_script(siter)) != NULL) {
+			if (user->mail_debug) {
+				i_debug("doveadm-sieve: Iterating Sieve mailbox attribute: %s", key);
+			}
+			return key;
+		}
+	}
+	return sbox->super.attribute_iter_next(siter->super);
+}
+
+static int
+sieve_attribute_iter_deinit(struct mailbox_attribute_iter *iter)
+{
+	struct sieve_mailbox_attribute_iter *siter =
+		(struct sieve_mailbox_attribute_iter *)iter;
+	union mailbox_module_context *sbox = SIEVE_MAIL_CONTEXT(iter->box);
+	int ret = siter->failed ? -1 : 0;
+
+	if (siter->super != NULL) {
+		if (sbox->super.attribute_iter_deinit(siter->super) < 0)
+			ret = -1;
+	}
+	if (siter->sieve_list != NULL)
+		(void)sieve_storage_list_deinit(&siter->sieve_list);
+	if (siter->name != NULL)
+		str_free(&siter->name);
+	i_free(siter);
+	return ret;
+}
+
+static void
+sieve_mail_user_created(struct mail_user *user)
+{
+	struct sieve_mail_user *suser;
+	struct mail_user_vfuncs *v = user->vlast;
+
+	suser = p_new(user->pool, struct sieve_mail_user, 1);
+	suser->module_ctx.super = *v;
+	user->vlast = &suser->module_ctx.super;
+	v->deinit = mail_sieve_user_deinit;
+	MODULE_CONTEXT_SET(user, sieve_user_module, suser);
+}
+
+static void
+sieve_mailbox_allocated(struct mailbox *box)
+{
+	struct mailbox_vfuncs *v = box->vlast;
+	union mailbox_module_context *sbox;
+
+	/* attribute syncing is done via INBOX */
+	if (!box->inbox_user)
+		return;
+
+	sbox = p_new(box->pool, union mailbox_module_context, 1);
+	sbox->super = *v;
+	box->vlast = &sbox->super;
+	v->attribute_set = sieve_attribute_set;
+	v->attribute_get = sieve_attribute_get;
+	v->attribute_iter_init = sieve_attribute_iter_init;
+	v->attribute_iter_next = sieve_attribute_iter_next;
+	v->attribute_iter_deinit = sieve_attribute_iter_deinit;
+	MODULE_CONTEXT_SET_SELF(box, sieve_storage_module, sbox);
+}
+
+static struct mail_storage_hooks doveadm_sieve_mail_storage_hooks = {
+	.mail_user_created = sieve_mail_user_created,
+	.mailbox_allocated = sieve_mailbox_allocated
+};
+
+void doveadm_sieve_sync_init(struct module *module)
+{
+	mail_storage_hooks_add_forced
+		(module, &doveadm_sieve_mail_storage_hooks);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/imap-filter-sieve/Makefile.am
@@ -0,0 +1,26 @@
+imap_moduledir = $(dovecot_moduledir)
+
+imap_module_LTLIBRARIES = lib95_imap_filter_sieve_plugin.la
+
+lib95_imap_filter_sieve_plugin_la_LDFLAGS = -module -avoid-version
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib-sieve \
+	$(LIBDOVECOT_IMAP_INCLUDE) \
+	$(LIBDOVECOT_LDA_INCLUDE) \
+	$(LIBDOVECOT_INCLUDE) \
+	-DPKG_RUNDIR=\""$(rundir)"\"
+
+lib95_imap_filter_sieve_plugin_la_SOURCES = \
+	cmd-filter.c \
+	cmd-filter-sieve.c \
+	imap-filter.c \
+	imap-filter-sieve.c \
+	imap-filter-sieve-plugin.c
+lib95_imap_filter_sieve_plugin_la_LIBADD = \
+	$(top_builddir)/src/lib-sieve/libdovecot-sieve.la
+
+noinst_HEADERS = \
+	imap-filter.h \
+	imap-filter-sieve.h \
+	imap-filter-sieve-plugin.h
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/imap-filter-sieve/cmd-filter-sieve.c
@@ -0,0 +1,405 @@
+/* Copyright (c) 2017-2018 Pigeonhole authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "str.h"
+#include "istream.h"
+#include "istream-seekable.h"
+#include "ostream.h"
+#include "imap-commands.h"
+
+#include "imap-filter.h"
+#include "imap-filter-sieve.h"
+
+#define FILTER_MAX_INMEM_SIZE (1024*128)
+
+static int
+cmd_filter_sieve_compile_script(struct imap_filter_context *ctx)
+{
+	struct client_command_context *cmd = ctx->cmd;
+	struct imap_filter_sieve_context *sctx = ctx->sieve;
+	struct client *client = cmd->client;
+	string_t *errors = NULL;
+	bool have_warnings = FALSE;
+	int ret = 0;
+
+	ret = imap_filter_sieve_compile(sctx, &errors, &have_warnings);
+	if (ret >= 0 && !have_warnings)
+		return 0;
+		
+	o_stream_nsend_str(client->output,
+		t_strdup_printf("* FILTER (TAG %s) "
+				"%s {%"PRIuSIZE_T"}\r\n",
+				(ret < 0 ? "ERRORS" : "WARNINGS"),
+				cmd->tag, str_len(errors)));
+	o_stream_nsend(client->output,
+		       str_data(errors), str_len(errors));
+	o_stream_nsend_str(client->output, "\r\n");
+	
+	if (ret < 0) {
+		ctx->compile_failure = TRUE;
+		ctx->failed = TRUE;
+		return -1;
+	}
+	return 0;
+}
+
+static bool
+cmd_filter_sieve_delivery(struct client_command_context *cmd)
+{
+	struct imap_filter_context *ctx = cmd->context;
+	struct client *client = cmd->client;
+	struct imap_filter_sieve_context *sctx = ctx->sieve;
+	enum mail_error error;
+	const char *error_string;
+	int ret;
+
+	if (cmd->cancel) {
+		imap_filter_deinit(ctx);
+		return TRUE;
+	}
+
+	i_assert(sctx->filter_type == IMAP_FILTER_SIEVE_TYPE_DELIVERY);
+	ret = imap_filter_sieve_open_personal(sctx, NULL,
+					      &error, &error_string);
+	if (ret < 0) {
+		client_send_tagline(cmd,
+			imap_get_error_string(cmd, error_string, error));
+		imap_filter_deinit(ctx);
+		return TRUE;
+	}
+	if (cmd_filter_sieve_compile_script(ctx) < 0) {
+		client_send_tagline(cmd, "NO Failed to compile Sieve script");
+		client->input_skip_line = TRUE;
+		imap_filter_deinit(ctx);
+		return TRUE;
+	}
+
+	imap_parser_reset(ctx->parser);
+	cmd->func = imap_filter_search;
+	return imap_filter_search(cmd);
+}
+
+static int
+cmd_filter_sieve_script_parse_name_arg(struct imap_filter_context *ctx)
+{
+	struct client_command_context *cmd = ctx->cmd;
+	const struct imap_arg *args;
+	const char *error;
+	enum imap_parser_error parse_error;
+	int ret;
+
+	ret = imap_parser_read_args(ctx->parser, 1, 0, &args);
+	if (ret < 0) {
+		if (ret == -2)
+			return 0;
+		error = imap_parser_get_error(ctx->parser, &parse_error);
+		switch (parse_error) {
+		case IMAP_PARSE_ERROR_NONE:
+			i_unreached();
+		case IMAP_PARSE_ERROR_LITERAL_TOO_BIG:
+			client_disconnect_with_error(ctx->cmd->client, error);
+			break;
+		default:
+			client_send_command_error(ctx->cmd, error);
+			break;
+		}
+		return -1;
+	}
+
+	switch (args[0].type) {
+	case IMAP_ARG_EOL:
+		client_send_command_error(ctx->cmd, "Script name missing");
+		return -1;
+	case IMAP_ARG_LIST:
+		client_send_command_error(ctx->cmd,
+			  "Script name must be a string");
+		return -1;
+	case IMAP_ARG_NIL:
+	case IMAP_ARG_ATOM:
+	case IMAP_ARG_STRING:
+		/* we have the value already */
+		if (ctx->failed)
+			return 1;
+		ctx->script_name = p_strdup(cmd->pool,
+					    imap_arg_as_nstring(&args[0]));
+		break;
+	case IMAP_ARG_LITERAL:
+	case IMAP_ARG_LITERAL_SIZE:
+	case IMAP_ARG_LITERAL_SIZE_NONSYNC:
+		i_unreached();
+	}
+	return 1;
+}
+
+static bool
+cmd_filter_sieve_script_parse_name(struct client_command_context *cmd)
+{
+	struct imap_filter_context *ctx = cmd->context;
+	struct client *client = cmd->client;
+	struct imap_filter_sieve_context *sctx = ctx->sieve;
+	enum mail_error error;
+	const char *error_string;
+	int ret;
+
+	if (cmd->cancel) {
+		imap_filter_deinit(ctx);
+		return TRUE;
+	}
+
+	if ((ret=cmd_filter_sieve_script_parse_name_arg(ctx)) == 0)
+		return FALSE;
+	if (ret < 0) {
+		/* already sent the error to client */ ;
+		imap_filter_deinit(ctx);
+		return TRUE;
+	}
+
+	switch (sctx->filter_type) {
+	case IMAP_FILTER_SIEVE_TYPE_PERSONAL:
+		ret = imap_filter_sieve_open_personal(sctx, ctx->script_name,
+						      &error, &error_string);
+		break;
+	case IMAP_FILTER_SIEVE_TYPE_GLOBAL:
+		ret = imap_filter_sieve_open_global(sctx, ctx->script_name,
+						    &error, &error_string);
+		break;
+	case IMAP_FILTER_SIEVE_TYPE_DELIVERY:
+	case IMAP_FILTER_SIEVE_TYPE_SCRIPT:
+		i_unreached();
+	}
+	if (ret < 0) {
+		client_send_tagline(cmd,
+			imap_get_error_string(cmd, error_string, error));
+		imap_filter_deinit(ctx);
+		return TRUE;
+	}
+	if (cmd_filter_sieve_compile_script(ctx) < 0) {
+		client_send_tagline(cmd, "NO Failed to compile Sieve script");
+		client->input_skip_line = TRUE;
+		imap_filter_deinit(ctx);
+		return TRUE;
+	}
+
+	imap_parser_reset(ctx->parser);
+	cmd->func = imap_filter_search;
+	return imap_filter_search(cmd);
+}
+
+static void
+cmd_filter_sieve_compile_input(struct imap_filter_context *ctx,
+				struct istream *input)
+{
+	struct imap_filter_sieve_context *sctx = ctx->sieve;
+
+	imap_filter_sieve_open_input(sctx, input);
+	(void)cmd_filter_sieve_compile_script(ctx);
+}
+
+static int
+cmd_filter_sieve_script_read_stream(struct imap_filter_context *ctx)
+{
+	struct istream *input = ctx->script_input;
+	const unsigned char *data;
+	size_t size;
+	int ret;
+
+	while ((ret = i_stream_read_more(input, &data, &size)) > 0)
+		i_stream_skip(input, size);
+	if (input->v_offset == ctx->script_len) {
+		/* finished reading the value */
+		i_stream_seek(input, 0);
+
+		if (ctx->failed) {
+			i_stream_unref(&ctx->script_input);
+			return 1;
+		}
+
+		cmd_filter_sieve_compile_input(ctx, ctx->script_input);
+		i_stream_unref(&ctx->script_input);
+		return 1;
+	}
+	if (input->eof) {
+		/* client disconnected */
+		return -1;
+	}
+	return 0;
+}
+
+static int
+cmd_filter_sieve_script_parse_value_arg(struct imap_filter_context *ctx)
+{
+	const struct imap_arg *args;
+	const char *value, *error;
+	enum imap_parser_error parse_error;
+	struct istream *input, *inputs[2];
+	string_t *path;
+	int ret;
+
+	ret = imap_parser_read_args(ctx->parser, 1,
+				    IMAP_PARSE_FLAG_LITERAL_SIZE |
+				    IMAP_PARSE_FLAG_LITERAL8, &args);
+	if (ret < 0) {
+		if (ret == -2)
+			return 0;
+		error = imap_parser_get_error(ctx->parser, &parse_error);
+		switch (parse_error) {
+		case IMAP_PARSE_ERROR_NONE:
+			i_unreached();
+		case IMAP_PARSE_ERROR_LITERAL_TOO_BIG:
+			client_disconnect_with_error(ctx->cmd->client, error);
+			break;
+		default:
+			client_send_command_error(ctx->cmd, error);
+			break;
+		}
+		return -1;
+	}
+
+	switch (args[0].type) {
+	case IMAP_ARG_EOL:
+		client_send_command_error(ctx->cmd, "Script value missing");
+		return -1;
+	case IMAP_ARG_NIL:
+	case IMAP_ARG_ATOM:
+	case IMAP_ARG_LIST:
+		client_send_command_error(ctx->cmd,
+			  "Script value must be a string");
+		return -1;
+	case IMAP_ARG_STRING:
+		/* we have the value already */
+		if (ctx->failed)
+			return 1;
+		value = imap_arg_as_nstring(&args[0]);
+		input = i_stream_create_from_data(value, strlen(value));
+		cmd_filter_sieve_compile_input(ctx, input);
+		i_stream_unref(&input);
+		return 1;
+	case IMAP_ARG_LITERAL_SIZE:
+		o_stream_nsend(ctx->cmd->client->output, "+ OK\r\n", 6);
+		o_stream_uncork(ctx->cmd->client->output);
+		o_stream_cork(ctx->cmd->client->output);
+		/* fall through */
+	case IMAP_ARG_LITERAL_SIZE_NONSYNC:
+		ctx->script_len = imap_arg_as_literal_size(&args[0]);
+
+		inputs[0] = i_stream_create_limit(ctx->cmd->client->input,
+						  ctx->script_len);
+		inputs[1] = NULL;
+
+		path = t_str_new(128);
+		mail_user_set_get_temp_prefix(path,
+					      ctx->cmd->client->user->set);
+		ctx->script_input = i_stream_create_seekable_path(
+			inputs, FILTER_MAX_INMEM_SIZE, str_c(path));
+		i_stream_set_name(ctx->script_input,
+				  i_stream_get_name(inputs[0]));
+		i_stream_unref(&inputs[0]);
+		break;
+	case IMAP_ARG_LITERAL:
+		i_unreached();
+	}
+	return cmd_filter_sieve_script_read_stream(ctx);
+}
+
+static bool
+cmd_filter_sieve_script_parse_value(struct client_command_context *cmd)
+{
+	struct imap_filter_context *ctx = cmd->context;
+	struct client *client = cmd->client;
+	int ret;
+
+	if (cmd->cancel) {
+		imap_filter_deinit(ctx);
+		return TRUE;
+	}
+
+	if (ctx->script_input != NULL) {
+		if ((ret=cmd_filter_sieve_script_read_stream(ctx)) == 0)
+			return FALSE;
+	} else {
+		if ((ret=cmd_filter_sieve_script_parse_value_arg(ctx)) == 0)
+			return FALSE;
+	}
+
+	if (ret < 0) {
+		/* already sent the error to client */ ;
+		imap_filter_deinit(ctx);
+		return TRUE;
+	} else if (ctx->compile_failure) {
+		client_send_tagline(cmd, "NO Failed to compile Sieve script");
+		client->input_skip_line = TRUE;
+		imap_filter_deinit(ctx);
+		return TRUE;
+	}
+
+	imap_parser_reset(ctx->parser);
+	cmd->func = imap_filter_search;
+	return imap_filter_search(cmd);
+}
+
+bool cmd_filter_sieve(struct client_command_context *cmd)
+{
+	struct imap_filter_context *ctx = cmd->context;
+	struct client *client = cmd->client;
+	enum imap_filter_sieve_type type;
+	const struct imap_arg *args;
+	const char *sieve_type;
+
+	if (!client_read_args(cmd, 2, 0, &args))
+		return FALSE;
+	args++;
+
+	/* sieve-type */
+	if (IMAP_ARG_IS_EOL(args)) {
+		client_send_command_error(cmd,
+			"Missing SIEVE filter sub-type.");
+		return TRUE;
+	}
+	if (!imap_arg_get_atom(args, &sieve_type)) {
+		client_send_command_error(cmd,
+			"SIEVE filter sub-type is not an atom.");
+		return TRUE;
+	}
+	if (strcasecmp(sieve_type, "DELIVERY") == 0) {
+		type = IMAP_FILTER_SIEVE_TYPE_DELIVERY;
+	} else if (strcasecmp(sieve_type, "PERSONAL") == 0) {
+		type = IMAP_FILTER_SIEVE_TYPE_PERSONAL;
+	} else if (strcasecmp(sieve_type, "GLOBAL") == 0) {
+		type = IMAP_FILTER_SIEVE_TYPE_GLOBAL;
+	} else if (strcasecmp(sieve_type, "SCRIPT") == 0) {
+		type = IMAP_FILTER_SIEVE_TYPE_SCRIPT;
+	} else {
+		client_send_command_error(cmd, t_strdup_printf(
+			"Unknown SIEVE filter sub-type `%s'",
+			sieve_type));
+		return TRUE;
+	}
+
+	ctx->sieve = imap_filter_sieve_context_create(ctx, type);
+
+	/* we support large scripts, so read the values from client
+	   asynchronously the same way as APPEND does. */
+	client->input_lock = cmd;
+	ctx->parser = imap_parser_create(client->input, client->output,
+					 client->set->imap_max_line_length);
+	if (client->set->imap_literal_minus)
+		imap_parser_enable_literal_minus(ctx->parser);
+	o_stream_unset_flush_callback(client->output);
+
+	switch (type) {
+	case IMAP_FILTER_SIEVE_TYPE_DELIVERY:
+		cmd->func = cmd_filter_sieve_delivery;
+		break;
+	case IMAP_FILTER_SIEVE_TYPE_PERSONAL:
+		cmd->func = cmd_filter_sieve_script_parse_name;
+		break;
+	case IMAP_FILTER_SIEVE_TYPE_GLOBAL:
+		cmd->func = cmd_filter_sieve_script_parse_name;
+		break;
+	case IMAP_FILTER_SIEVE_TYPE_SCRIPT:
+		cmd->func = cmd_filter_sieve_script_parse_value;
+		break;
+	}
+	cmd->context = ctx;
+	return cmd->func(cmd);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/imap-filter-sieve/cmd-filter.c
@@ -0,0 +1,57 @@
+/* Copyright (c) 2017-2018 Pigeonhole authors, see the included COPYING file */
+
+#include "imap-common.h"
+
+#include "imap-filter.h"
+#include "imap-filter-sieve.h"
+
+static bool
+cmd_filter_parse_spec(struct imap_filter_context *ctx,
+		       const struct imap_arg **_args)
+{
+	const struct imap_arg *args = *_args;
+	struct client_command_context *cmd = ctx->cmd;
+	const char *filter_type;
+
+	/* filter-type */
+	if (IMAP_ARG_IS_EOL(args)) {
+		client_send_command_error(cmd,
+			"Missing filter type.");
+		return TRUE;
+	}
+	if (!imap_arg_get_atom(args, &filter_type)) {
+		client_send_command_error(cmd,
+			"Filter type is not an atom.");
+		return TRUE;
+	}
+	if (strcasecmp(filter_type, "SIEVE") != 0) {
+		client_send_command_error(cmd, t_strdup_printf(
+			"Unknown filter type `%s'", filter_type));
+		return TRUE;
+	}
+
+	cmd->func = cmd_filter_sieve;
+	cmd->context = ctx;
+	return cmd_filter_sieve(cmd);
+}
+
+bool cmd_filter(struct client_command_context *cmd)
+{
+	struct imap_filter_context *ctx;
+	const struct imap_arg *args;
+
+	if (!client_read_args(cmd, 1, 0, &args))
+		return FALSE;
+
+	if (!client_verify_open_mailbox(cmd))
+		return TRUE;
+
+	ctx = p_new(cmd->pool, struct imap_filter_context, 1);
+	ctx->cmd = cmd;
+
+	if (!cmd_filter_parse_spec(ctx, &args))
+		return FALSE;
+
+	imap_filter_context_free(ctx);
+	return TRUE;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/imap-filter-sieve/imap-filter-sieve-plugin.c
@@ -0,0 +1,56 @@
+/* Copyright (c) 2017-2018 Pigeonhole authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "str.h"
+
+#include "imap-filter-sieve.h"
+#include "imap-filter-sieve-plugin.h"
+
+static struct module *imap_filter_sieve_module;
+static imap_client_created_func_t *next_hook_client_created;
+
+/*
+ * Client
+ */
+
+static void imap_filter_sieve_plugin_client_created(struct client **clientp)
+{
+	struct client *client = *clientp;
+	struct mail_user *user = client->user;
+
+	if (mail_user_is_plugin_loaded(user, imap_filter_sieve_module)) {
+		client_add_capability(client, "FILTER=SIEVE");
+
+		imap_filter_sieve_client_created(client);
+	}
+
+	if (next_hook_client_created != NULL)
+		next_hook_client_created(clientp);
+}
+
+/*
+ * Plugin
+ */
+
+const char *imap_filter_sieve_plugin_version = DOVECOT_ABI_VERSION;
+const char imap_filter_sieve_plugin_binary_dependency[] = "imap";
+
+void imap_filter_sieve_plugin_init(struct module *module)
+{
+	command_register("FILTER", cmd_filter, COMMAND_FLAG_USES_SEQS);
+	command_register("UID FILTER", cmd_filter, COMMAND_FLAG_BREAKS_SEQS);
+
+	imap_filter_sieve_module = module;
+	next_hook_client_created = imap_client_created_hook_set(
+		imap_filter_sieve_plugin_client_created);
+	imap_filter_sieve_init(module);
+}
+
+void imap_filter_sieve_plugin_deinit(void)
+{
+	command_unregister("FILTER");
+	command_unregister("UID FILTER");
+
+	imap_filter_sieve_deinit();
+	imap_client_created_hook_set(next_hook_client_created);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/imap-filter-sieve/imap-filter-sieve-plugin.h
@@ -0,0 +1,16 @@
+/* Copyright (c) 2017-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#ifndef IMAP_FILTER_SIEVE_PLUGIN_H
+#define IMAP_FILTER_SIEVE_PLUGIN_H
+
+struct module;
+
+extern const char imap_filter_sieve_plugin_binary_dependency[];
+
+bool cmd_filter(struct client_command_context *cmd);
+
+void imap_filter_sieve_plugin_init(struct module *module);
+void imap_filter_sieve_plugin_deinit(void);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/imap-filter-sieve/imap-filter-sieve.c
@@ -0,0 +1,1007 @@
+/* Copyright (c) 2017-2018 Pigeonhole authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "str.h"
+#include "ioloop.h"
+#include "time-util.h"
+#include "module-context.h"
+#include "message-address.h"
+#include "mail-user.h"
+#include "mail-duplicate.h"
+#include "mail-storage-private.h"
+#include "iostream-ssl.h"
+#include "smtp-submit.h"
+#include "sieve.h"
+#include "sieve-storage.h"
+#include "sieve-script.h"
+
+#include "imap-filter-sieve.h"
+
+#define DUPLICATE_DB_NAME "lda-dupes"
+
+#define IMAP_FILTER_SIEVE_USER_CONTEXT(obj) \
+	MODULE_CONTEXT(obj, imap_filter_sieve_user_module)
+#define IMAP_FILTER_SIEVE_USER_CONTEXT_REQUIRE(obj) \
+	MODULE_CONTEXT_REQUIRE(obj, imap_filter_sieve_user_module)
+
+struct imap_filter_sieve_script {
+	struct sieve_script *script;
+	struct sieve_binary *binary;
+
+	/* Compile failed once with this error;
+	   don't try again for this transaction */
+	enum sieve_error compile_error;
+
+	/* Binary corrupt after recompile; don't recompile again */
+	bool binary_corrupt:1;
+};
+
+struct imap_filter_sieve_user {
+	union mail_user_module_context module_ctx;
+	struct client *client;
+
+	struct sieve_instance *svinst;
+	struct sieve_storage *storage;
+	struct sieve_storage *global_storage;
+
+	struct mail_duplicate_db *dup_db;
+
+	struct sieve_error_handler *master_ehandler;
+};
+
+static MODULE_CONTEXT_DEFINE_INIT(imap_filter_sieve_user_module,
+				  &mail_user_module_register);
+
+/*
+ *
+ */
+
+static const char *
+imap_filter_sieve_get_setting(void *context, const char *identifier)
+{
+	struct imap_filter_sieve_user *ifsuser = context;
+	struct mail_user *user = ifsuser->client->user;
+
+	return mail_user_plugin_getenv(user, identifier);
+}
+
+static const struct sieve_callbacks imap_filter_sieve_callbacks = {
+	NULL,
+	imap_filter_sieve_get_setting
+};
+
+static struct sieve_instance *
+imap_filter_sieve_get_svinst(struct imap_filter_sieve_context *sctx)
+{
+	struct mail_user *user = sctx->user;
+	struct imap_filter_sieve_user *ifsuser =
+		IMAP_FILTER_SIEVE_USER_CONTEXT_REQUIRE(user);
+	struct sieve_environment svenv;
+	const struct mail_storage_settings *mail_set;
+	bool debug = user->mail_debug;
+
+	if (ifsuser->svinst != NULL)
+		return ifsuser->svinst;
+
+	mail_set = mail_user_set_get_storage_set(user);
+
+	ifsuser->dup_db = mail_duplicate_db_init(user, DUPLICATE_DB_NAME);
+
+	i_zero(&svenv);
+	svenv.username = user->username;
+	(void)mail_user_get_home(user, &svenv.home_dir);
+	svenv.hostname = mail_set->hostname;
+	svenv.base_dir = user->set->base_dir;
+	svenv.flags = SIEVE_FLAG_HOME_RELATIVE;
+	svenv.location = SIEVE_ENV_LOCATION_MS;
+	svenv.delivery_phase = SIEVE_DELIVERY_PHASE_POST;
+
+	ifsuser->svinst = sieve_init(&svenv, &imap_filter_sieve_callbacks,
+				     ifsuser, debug);
+
+	ifsuser->master_ehandler = sieve_master_ehandler_create(
+		ifsuser->svinst, NULL, 0); // FIXME: prefix?
+	sieve_system_ehandler_set(ifsuser->master_ehandler);
+	sieve_error_handler_accept_infolog(ifsuser->master_ehandler, TRUE);
+	sieve_error_handler_accept_debuglog(ifsuser->master_ehandler, debug);
+
+	return ifsuser->svinst;
+}
+
+static int
+imap_filter_sieve_get_personal_storage(struct imap_filter_sieve_context *sctx,
+				       struct sieve_storage **storage_r,
+				       enum mail_error *error_code_r,
+				       const char **error_r)
+{
+	struct mail_user *user = sctx->user;
+	struct imap_filter_sieve_user *ifsuser =
+		IMAP_FILTER_SIEVE_USER_CONTEXT_REQUIRE(user);
+	enum sieve_storage_flags storage_flags = 0;
+	struct sieve_instance *svinst;
+	enum sieve_error error;
+
+	*error_code_r = MAIL_ERROR_NONE;
+	*error_r = NULL;
+
+	if (ifsuser->storage != NULL) {
+		*storage_r = ifsuser->storage;
+		return 0;
+	}
+
+	// FIXME: limit interval between retries
+
+	svinst = imap_filter_sieve_get_svinst(sctx);
+	ifsuser->storage = sieve_storage_create_main(svinst, user,
+						     storage_flags, &error);
+	if (ifsuser->storage != NULL) {
+		*storage_r = ifsuser->storage;
+		return 0;
+	}
+
+	switch (error) {
+	case SIEVE_ERROR_NOT_POSSIBLE:
+		*error_r = "Sieve processing is disabled for this user";
+		*error_code_r = MAIL_ERROR_NOTPOSSIBLE;
+		break;
+	case SIEVE_ERROR_NOT_FOUND:
+		*error_r = "Sieve script storage not accessible";
+		*error_code_r = MAIL_ERROR_NOTFOUND;
+		break;
+	default:
+		*error_r = t_strflocaltime(MAIL_ERRSTR_CRITICAL_MSG_STAMP,
+					   ioloop_time);
+		*error_code_r = MAIL_ERROR_TEMP;
+		break;
+	}
+
+	return -1;
+}
+
+static int
+imap_filter_sieve_get_global_storage(struct imap_filter_sieve_context *sctx,
+				     struct sieve_storage **storage_r,
+				     enum mail_error *error_code_r,
+				     const char **error_r)
+{
+	struct mail_user *user = sctx->user;
+	struct imap_filter_sieve_user *ifsuser =
+		IMAP_FILTER_SIEVE_USER_CONTEXT_REQUIRE(user);
+	struct sieve_instance *svinst;
+	const char *location;
+	enum sieve_error error;
+
+	*error_code_r = MAIL_ERROR_NONE;
+	*error_r = NULL;
+
+	if (ifsuser->global_storage != NULL) {
+		*storage_r = ifsuser->global_storage;
+		return 0;
+	}
+
+	svinst = imap_filter_sieve_get_svinst(sctx);
+
+	location = mail_user_plugin_getenv(user, "sieve_global");
+	if (location == NULL) {
+		sieve_sys_info(svinst, "include: sieve_global is unconfigured; "
+			"include of `:global' script is therefore not possible");
+		*error_code_r = MAIL_ERROR_NOTFOUND;
+		*error_r = "No global Sieve scripts available";
+		return -1;
+	}
+	ifsuser->global_storage =
+		sieve_storage_create(svinst, location, 0, &error);
+	if (ifsuser->global_storage != NULL) {
+		*storage_r = ifsuser->global_storage;
+		return 0;
+	}
+
+	switch (error) {
+	case SIEVE_ERROR_NOT_POSSIBLE:
+	case SIEVE_ERROR_NOT_FOUND:
+		*error_r = "No global Sieve scripts available";
+		*error_code_r = MAIL_ERROR_NOTFOUND;
+		break;
+	default:
+		*error_r = t_strflocaltime(MAIL_ERRSTR_CRITICAL_MSG_STAMP,
+					   ioloop_time);
+		*error_code_r = MAIL_ERROR_TEMP;
+		break;
+	}
+
+	return -1;
+}
+
+/*
+ *
+ */
+
+struct imap_filter_sieve_context *
+imap_filter_sieve_context_create(struct imap_filter_context *ctx,
+				 enum imap_filter_sieve_type type)
+{
+	struct client_command_context *cmd = ctx->cmd;
+	struct imap_filter_sieve_context *sctx;
+
+	sctx = p_new(cmd->pool, struct imap_filter_sieve_context, 1);
+	sctx->pool = cmd->pool;
+	sctx->filter_type = type;
+	sctx->user = ctx->cmd->client->user;
+
+	return sctx;
+}
+
+void imap_filter_sieve_context_free(struct imap_filter_sieve_context **_sctx)
+{
+	struct imap_filter_sieve_context *sctx = *_sctx;
+	struct imap_filter_sieve_script *scripts;
+	unsigned int i;
+
+	*_sctx = NULL;
+
+	if (sctx == NULL)
+		return;
+
+	scripts = sctx->scripts;
+	for (i = 0; i < sctx->scripts_count; i++) {
+		if (scripts[i].binary != NULL)
+			sieve_close(&scripts[i].binary);
+		if (scripts[i].script != NULL)
+			sieve_script_unref(&scripts[i].script);
+	}
+
+	str_free(&sctx->errors);
+}
+
+/*
+ * Error handling
+ */
+
+static struct sieve_error_handler *
+imap_filter_sieve_create_error_handler(struct imap_filter_sieve_context *sctx)
+{
+	struct sieve_instance *svinst = imap_filter_sieve_get_svinst(sctx);
+
+	/* Prepare error handler */
+	if (sctx->errors == NULL)
+		sctx->errors = str_new(default_pool, 1024);
+	else
+		str_truncate(sctx->errors, 0);
+	return sieve_strbuf_ehandler_create(svinst, sctx->errors, TRUE,
+		10 /* client->set->_max_compile_errors */);
+}
+
+/*
+ *
+ */
+
+static struct sieve_binary *
+imap_sieve_filter_open_script(struct imap_filter_sieve_context *sctx,
+			      struct sieve_script *script,
+			      enum sieve_compile_flags cpflags,
+			      struct sieve_error_handler *user_ehandler,
+			      bool recompile, enum sieve_error *error_r)
+{
+	struct mail_user *user = sctx->user;
+	struct imap_filter_sieve_user *ifsuser =
+		IMAP_FILTER_SIEVE_USER_CONTEXT_REQUIRE(user);
+	struct sieve_instance *svinst = imap_filter_sieve_get_svinst(sctx);
+	struct sieve_error_handler *ehandler;
+	struct sieve_binary *sbin;
+	const char *compile_name = "compile";
+	bool debug = user->mail_debug;
+
+	if (recompile) {
+		/* Warn */
+		sieve_sys_warning(svinst,
+			"Encountered corrupt binary: re-compiling script %s",
+			sieve_script_location(script));
+		compile_name = "re-compile";
+	} else if (debug) {
+		sieve_sys_debug(svinst,
+			"Loading script %s", sieve_script_location(script));
+	}
+
+	if (script == sctx->user_script)
+		ehandler = user_ehandler;
+	else
+		ehandler = ifsuser->master_ehandler;
+	sieve_error_handler_reset(ehandler);
+
+	/* Load or compile the sieve script */
+	if (recompile) {
+		sbin = sieve_compile_script(script, ehandler, cpflags, error_r);
+	} else {
+		sbin = sieve_open_script(script, ehandler, cpflags, error_r);
+	}
+
+	/* Handle error */
+	if (sbin == NULL) {
+		switch (*error_r) {
+		/* Script not found */
+		case SIEVE_ERROR_NOT_FOUND:
+			if (debug) {
+				sieve_sys_debug(svinst, "Script `%s' is missing for %s",
+					sieve_script_location(script), compile_name);
+			}
+			break;
+		/* Temporary failure */
+		case SIEVE_ERROR_TEMP_FAILURE:
+			sieve_sys_error(svinst,
+				"Failed to open script `%s' for %s (temporary failure)",
+				sieve_script_location(script), compile_name);
+			break;
+		/* Compile failed */
+		case SIEVE_ERROR_NOT_VALID:
+			if (script == sctx->user_script)
+				break;
+			sieve_sys_error(svinst,	"Failed to %s script `%s'",
+				compile_name, sieve_script_location(script));
+			break;
+		/* Something else */
+		default:
+			sieve_sys_error(svinst,	"Failed to open script `%s' for %s",
+				sieve_script_location(script), compile_name);
+			break;
+		}
+
+		return NULL;
+	}
+
+	if (!recompile)
+		(void)sieve_save(sbin, FALSE, NULL);
+	return sbin;
+}
+
+int imap_filter_sieve_compile(struct imap_filter_sieve_context *sctx,
+			      string_t **errors_r, bool *have_warnings_r)
+{
+	struct imap_filter_sieve_script *scripts = sctx->scripts;
+	unsigned int count = sctx->scripts_count, i;
+	struct sieve_error_handler *ehandler;
+	enum sieve_error error;
+	int ret = 0;
+
+	*errors_r = NULL;
+	*have_warnings_r = FALSE;
+
+	/* Prepare error handler */
+	ehandler = imap_filter_sieve_create_error_handler(sctx);
+
+	for (i = 0; i < count; i++) {
+		struct sieve_script *script = scripts[i].script;
+
+		i_assert(script != NULL);
+
+		scripts[i].binary =
+			imap_sieve_filter_open_script(sctx, script, 0, ehandler,
+						     FALSE, &error);
+		if (scripts[i].binary == NULL) {
+			if (error != SIEVE_ERROR_NOT_VALID) {
+				const char *errormsg =
+					sieve_script_get_last_error(script, &error);
+
+				if (error != SIEVE_ERROR_NONE) {
+					str_truncate(sctx->errors, 0);
+					str_append(sctx->errors, errormsg);
+				}
+			}
+			ret = -1;
+			break;
+		}
+	}
+
+	*have_warnings_r = (sieve_get_warnings(ehandler) > 0);
+	*errors_r = sctx->errors;
+
+	sieve_error_handler_unref(&ehandler);
+	return ret;
+}
+
+void imap_filter_sieve_open_input(struct imap_filter_sieve_context *sctx,
+				  struct istream *input)
+{
+	struct sieve_instance *svinst;
+	struct sieve_script *script;
+
+	svinst = imap_filter_sieve_get_svinst(sctx);
+	script = sieve_data_script_create_from_input(svinst, "script", input);
+
+	sctx->user_script = script;
+	sctx->scripts = p_new(sctx->pool, struct imap_filter_sieve_script, 1);
+	sctx->scripts_count = 1;
+	sctx->scripts[0].script = script;
+}
+
+int imap_filter_sieve_open_personal(struct imap_filter_sieve_context *sctx,
+				    const char *name,
+				    enum mail_error *error_code_r,
+				    const char **error_r)
+{
+	struct sieve_storage *storage;
+	struct sieve_script *script;
+	enum sieve_error error;
+
+	if (imap_filter_sieve_get_personal_storage(sctx, &storage,
+						   error_code_r, error_r) < 0)
+		return -1;
+
+	if (name == NULL)
+		script = sieve_storage_active_script_open(storage, NULL);
+	else
+		script = sieve_storage_open_script(storage, name, NULL);
+	if (script == NULL) {
+		*error_r = sieve_storage_get_last_error(storage, &error);
+
+		switch (error) {
+		case SIEVE_ERROR_NOT_FOUND:
+			*error_code_r = MAIL_ERROR_NOTFOUND;
+			break;
+		case SIEVE_ERROR_NOT_POSSIBLE:
+			*error_code_r = MAIL_ERROR_NOTPOSSIBLE;
+			break;
+		default:
+			*error_code_r = MAIL_ERROR_TEMP;
+		}
+		return -1;
+	}
+
+	sctx->user_script = script;
+	sctx->scripts = p_new(sctx->pool, struct imap_filter_sieve_script, 1);
+	sctx->scripts_count = 1;
+	sctx->scripts[0].script = script;
+	return 0;
+}
+
+int imap_filter_sieve_open_global(struct imap_filter_sieve_context *sctx,
+				  const char *name,
+				  enum mail_error *error_code_r,
+				  const char **error_r)
+{
+	struct sieve_storage *storage;
+	struct sieve_script *script;
+	enum sieve_error error;
+
+	if (imap_filter_sieve_get_global_storage(sctx, &storage,
+						 error_code_r, error_r) < 0)
+		return -1;
+
+	script = sieve_storage_open_script(storage, name, NULL);
+	if (script == NULL) {
+		*error_r = sieve_storage_get_last_error(storage, &error);
+
+		switch (error) {
+		case SIEVE_ERROR_NOT_FOUND:
+			*error_code_r = MAIL_ERROR_NOTFOUND;
+			break;
+		case SIEVE_ERROR_NOT_POSSIBLE:
+			*error_code_r = MAIL_ERROR_NOTPOSSIBLE;
+			break;
+		default:
+			*error_code_r = MAIL_ERROR_TEMP;
+		}
+		return -1;
+	}
+
+	sctx->user_script = script;
+	sctx->scripts = p_new(sctx->pool, struct imap_filter_sieve_script, 1);
+	sctx->scripts_count = 1;
+	sctx->scripts[0].script = script;
+	return 0;
+}
+
+/*
+ * Mail transmission
+ */
+
+static void *
+imap_filter_sieve_smtp_start(const struct sieve_script_env *senv,
+			     const struct smtp_address *mail_from)
+{
+	struct imap_filter_sieve_context *sctx = senv->script_context;
+	struct mail_user *user = sctx->user;
+	struct imap_filter_sieve_user *ifsuser =
+		IMAP_FILTER_SIEVE_USER_CONTEXT_REQUIRE(user);
+	const struct smtp_submit_settings *smtp_set = ifsuser->client->smtp_set;
+	struct ssl_iostream_settings ssl_set;
+
+	i_zero(&ssl_set);
+	mail_user_init_ssl_client_settings(user, &ssl_set);
+
+	return (void *)smtp_submit_init_simple(smtp_set, &ssl_set, mail_from);
+}
+
+static void
+imap_filter_sieve_smtp_add_rcpt(const struct sieve_script_env *senv ATTR_UNUSED,
+				void *handle,
+				const struct smtp_address *rcpt_to)
+{
+	struct smtp_submit *smtp_submit = handle;
+
+	smtp_submit_add_rcpt(smtp_submit, rcpt_to);
+}
+
+static struct ostream *
+imap_filter_sieve_smtp_send(const struct sieve_script_env *senv ATTR_UNUSED,
+			    void *handle)
+{
+	struct smtp_submit *smtp_submit = handle;
+
+	return smtp_submit_send(smtp_submit);
+}
+
+static void
+imap_filter_sieve_smtp_abort(const struct sieve_script_env *senv ATTR_UNUSED,
+			     void *handle)
+{
+	struct smtp_submit *smtp_submit = handle;
+
+	smtp_submit_deinit(&smtp_submit);
+}
+
+static int
+imap_filter_sieve_smtp_finish(const struct sieve_script_env *senv ATTR_UNUSED,
+			      void *handle, const char **error_r)
+{
+	struct smtp_submit *smtp_submit = handle;
+	int ret;
+
+	ret = smtp_submit_run(smtp_submit, error_r);
+	smtp_submit_deinit(&smtp_submit);
+	return ret;
+}
+
+/*
+ * Duplicate checking
+ */
+
+static bool
+imap_filter_sieve_duplicate_check(const struct sieve_script_env *senv,
+				  const void *id, size_t id_size)
+{
+	struct imap_filter_sieve_context *sctx = senv->script_context;
+	struct imap_filter_sieve_user *ifsuser =
+		IMAP_FILTER_SIEVE_USER_CONTEXT_REQUIRE(sctx->user);
+
+	return mail_duplicate_check(ifsuser->dup_db,
+		id, id_size, senv->user->username);
+}
+
+static void
+imap_filter_sieve_duplicate_mark(const struct sieve_script_env *senv,
+				 const void *id, size_t id_size, time_t time)
+{
+	struct imap_filter_sieve_context *sctx = senv->script_context;
+	struct imap_filter_sieve_user *ifsuser =
+		IMAP_FILTER_SIEVE_USER_CONTEXT_REQUIRE(sctx->user);
+
+	mail_duplicate_mark(ifsuser->dup_db,
+		id, id_size, senv->user->username, time);
+}
+
+static void
+imap_filter_sieve_duplicate_flush(const struct sieve_script_env *senv)
+{
+	struct imap_filter_sieve_context *sctx = senv->script_context;
+	struct imap_filter_sieve_user *ifsuser =
+		IMAP_FILTER_SIEVE_USER_CONTEXT_REQUIRE(sctx->user);
+
+	mail_duplicate_db_flush(ifsuser->dup_db);
+}
+
+/*
+ *
+ */
+
+static int
+imap_sieve_filter_handle_exec_status(struct imap_filter_sieve_context *sctx,
+				     struct sieve_script *script, int status,
+				     bool keep,
+				     struct sieve_exec_status *estatus)
+{
+	struct imap_filter_sieve_user *ifsuser =
+		IMAP_FILTER_SIEVE_USER_CONTEXT_REQUIRE(sctx->user);
+	struct sieve_instance *svinst = ifsuser->svinst;
+	sieve_sys_error_func_t error_func, user_error_func;
+	enum mail_error mail_error = MAIL_ERROR_NONE;
+	int ret = -1;
+
+	error_func = user_error_func = sieve_sys_error;
+
+	if (estatus != NULL && estatus->last_storage != NULL &&
+	    estatus->store_failed) {
+		(void)mail_storage_get_last_error(estatus->last_storage,
+						  &mail_error);
+
+		/* Don't bother administrator too much with benign errors */
+		if (mail_error == MAIL_ERROR_NOQUOTA) {
+			error_func = sieve_sys_info;
+			user_error_func = sieve_sys_info;
+		}
+	}
+
+	switch ( status ) {
+	case SIEVE_EXEC_FAILURE:
+		user_error_func(svinst,
+			"Execution of script %s failed",
+			sieve_script_location(script));
+		ret = -1;
+		break;
+	case SIEVE_EXEC_TEMP_FAILURE:
+		error_func(svinst,
+			"Execution of script %s was aborted "
+			"due to temporary failure",
+			sieve_script_location(script));
+		ret = -1;
+		break;
+	case SIEVE_EXEC_BIN_CORRUPT:
+		sieve_sys_error(svinst,
+			"!!BUG!!: Binary compiled from %s is still corrupt; "
+			"bailing out and reverting to default action",
+			sieve_script_location(script));
+		ret = -1;
+		break;
+	case SIEVE_EXEC_KEEP_FAILED:
+		error_func(svinst,
+			"Execution of script %s failed "
+			"with unsuccessful implicit keep",
+			sieve_script_location(script));
+		ret = -1;
+		break;
+	case SIEVE_EXEC_OK:
+		ret = (keep ? 0 : 1);
+		break;
+	}
+
+	return ret;
+}
+
+static int
+imap_sieve_filter_run_scripts(struct imap_filter_sieve_context *sctx,
+			      struct sieve_error_handler *user_ehandler,
+			      const struct sieve_message_data *msgdata,
+			      const struct sieve_script_env *scriptenv)
+{
+	struct mail_user *user = sctx->user;
+	struct imap_filter_sieve_user *ifsuser =
+		IMAP_FILTER_SIEVE_USER_CONTEXT_REQUIRE(user);
+	struct sieve_instance *svinst = ifsuser->svinst;
+	struct imap_filter_sieve_script *scripts = sctx->scripts;
+	unsigned int count = sctx->scripts_count;
+	struct sieve_multiscript *mscript;
+	struct sieve_error_handler *ehandler;
+	struct sieve_script *last_script = NULL;
+	bool user_script = FALSE, more = TRUE;
+	bool debug = user->mail_debug, keep = TRUE;
+	enum sieve_compile_flags cpflags;
+	enum sieve_execute_flags exflags;
+	enum sieve_error compile_error = SIEVE_ERROR_NONE;
+	unsigned int i;
+	int ret;
+
+	/* Start execution */
+	mscript = sieve_multiscript_start_execute(svinst, msgdata, scriptenv);
+
+	/* Execute scripts */
+	for (i = 0; i < count && more; i++) {
+		struct sieve_script *script = scripts[i].script;
+		struct sieve_binary *sbin = scripts[i].binary;
+
+		if (sbin == NULL) {
+			if (debug) {
+				sieve_sys_debug(svinst,
+					"Skipping script from `%s'",
+					sieve_script_location(script));
+			}
+			continue;
+		}
+
+		cpflags = 0;
+		exflags = SIEVE_EXECUTE_FLAG_SKIP_RESPONSES;
+
+		user_script = ( script == sctx->user_script );
+		last_script = script;
+
+		if (user_script) {
+			cpflags |= SIEVE_COMPILE_FLAG_NOGLOBAL;
+			exflags |= SIEVE_EXECUTE_FLAG_NOGLOBAL;
+			ehandler = user_ehandler;
+		} else {
+			ehandler = ifsuser->master_ehandler;
+		}
+
+		/* Execute */
+		if (debug) {
+			sieve_sys_debug(svinst,
+				"Executing script from `%s'",
+				sieve_get_source(sbin));
+		}
+		more = sieve_multiscript_run(mscript,
+			sbin, ehandler, ehandler, exflags);
+
+		if (!more) {
+			if (!scripts[i].binary_corrupt &&
+			    sieve_multiscript_status(mscript)
+				== SIEVE_EXEC_BIN_CORRUPT &&
+			    sieve_is_loaded(sbin)) {
+
+				/* Close corrupt script */
+				sieve_close(&sbin);
+
+				/* Recompile */
+				scripts[i].binary = sbin =
+					imap_sieve_filter_open_script(sctx,
+						script, cpflags, user_ehandler,
+						FALSE, &compile_error);
+				if ( sbin == NULL ) {
+					scripts[i].compile_error = compile_error;
+					break;
+				}
+
+				/* Execute again */
+				more = sieve_multiscript_run(mscript, sbin,
+					ehandler, ehandler, exflags);
+
+				/* Save new version */
+
+				if (sieve_multiscript_status(mscript)
+					== SIEVE_EXEC_BIN_CORRUPT)
+					scripts[i].binary_corrupt = TRUE;
+				else if (more)
+					(void)sieve_save(sbin, FALSE, NULL);
+			}
+		}
+	}
+
+	/* Finish execution */
+	exflags = SIEVE_EXECUTE_FLAG_SKIP_RESPONSES;
+	ehandler = (user_ehandler != NULL ?
+		user_ehandler : ifsuser->master_ehandler);
+	if (compile_error == SIEVE_ERROR_TEMP_FAILURE) {
+		ret = sieve_multiscript_tempfail(&mscript, ehandler, exflags);
+	} else {
+		ret = sieve_multiscript_finish(&mscript, ehandler, exflags,
+					       &keep);
+	}
+
+	/* Don't log additional messages about compile failure */
+	if (compile_error != SIEVE_ERROR_NONE &&
+	    ret == SIEVE_EXEC_FAILURE) {
+		sieve_sys_info(svinst,
+			"Aborted script execution sequence "
+			"with successful implicit keep");
+		return 1;
+	}
+
+	i_assert(last_script != NULL); /* at least one script is executed */
+	return imap_sieve_filter_handle_exec_status(sctx,
+		last_script, ret, keep, scriptenv->exec_status);
+}
+
+static int
+parse_address(const char *address, const struct smtp_address **addr_r)
+{
+	struct message_address *msg_addr;
+	struct smtp_address *smtp_addr;
+
+	if (message_address_parse_path(pool_datastack_create(),
+				       (const unsigned char *)address,
+				       strlen(address), &msg_addr) < 0) {
+		*addr_r = NULL;
+		return -1;
+	}
+	if (smtp_address_create_from_msg_temp(msg_addr, &smtp_addr) < 0) {
+		*addr_r = NULL;
+		return -1;
+	}
+
+	*addr_r = smtp_addr;
+	return 1;
+}
+
+static void
+imap_sieve_filter_get_msgdata(struct imap_filter_sieve_context *sctx,
+			      struct mail *mail,
+			      struct sieve_message_data *msgdata_r)
+{
+	struct sieve_instance *svinst = imap_filter_sieve_get_svinst(sctx);
+	struct mail_user *user = sctx->user;
+	const char *address, *error;
+	const struct smtp_address *mail_from, *rcpt_to;
+	struct smtp_address *user_addr;
+	int ret;
+
+	mail_from = NULL;
+	if ((ret=mail_get_special(mail, MAIL_FETCH_FROM_ENVELOPE,
+				  &address)) > 0 &&
+	    (ret=parse_address(address, &mail_from)) < 0) {
+		sieve_sys_warning(svinst,
+			"Failed to parse message FROM_ENVELOPE");
+	}
+	if (ret <= 0 &&
+	    mail_get_first_header(mail, "Return-Path",
+				  &address) > 0 &&
+	    parse_address(address, &mail_from) < 0) {
+		sieve_sys_info(svinst,
+			"Failed to parse Return-Path header");
+	}
+
+	rcpt_to = NULL;
+	if (mail_get_first_header(mail, "Delivered-To",
+				  &address) > 0 &&
+	    parse_address(address, &rcpt_to) < 0) {
+		sieve_sys_info(svinst,
+			"Failed to parse Delivered-To header");
+	}
+	if (rcpt_to == NULL) {
+		if (svinst->user_email != NULL)
+			rcpt_to = svinst->user_email;
+		else if (smtp_address_parse_username(sctx->pool, user->username,
+						     &user_addr, &error) < 0) {
+			sieve_sys_warning(svinst,
+				"Cannot obtain SMTP address from username `%s': %s",
+				user->username, error);
+		} else {
+			if (user_addr->domain == NULL)
+				user_addr->domain = svinst->domainname;
+			rcpt_to = user_addr;
+		}
+	}
+
+	// FIXME: maybe parse top Received header.
+
+	i_zero(msgdata_r);
+	msgdata_r->mail = mail;
+	msgdata_r->envelope.mail_from = mail_from;
+	msgdata_r->envelope.rcpt_to = rcpt_to;
+	msgdata_r->auth_user = user->username;
+	(void)mail_get_first_header(mail, "Message-ID", &msgdata_r->id);
+}
+
+int imap_sieve_filter_run_mail(struct imap_filter_sieve_context *sctx,
+			       struct mail *mail, string_t **errors_r,
+			       bool *have_warnings_r)
+{
+	struct sieve_instance *svinst = imap_filter_sieve_get_svinst(sctx);
+	struct mail_user *user = sctx->user;
+	struct sieve_error_handler *user_ehandler;
+	struct sieve_message_data msgdata;
+	struct sieve_script_env scriptenv;
+	struct sieve_exec_status estatus;
+	struct sieve_trace_config trace_config;
+	struct sieve_trace_log *trace_log;
+	const char *error;
+	int ret;
+
+	*errors_r = NULL;
+	*have_warnings_r = FALSE;
+
+	/* Prepare error handler */
+	user_ehandler = imap_filter_sieve_create_error_handler(sctx);
+
+	/* Initialize trace logging */
+
+	trace_log = NULL;
+	if (sieve_trace_config_get(svinst, &trace_config) >= 0) {
+		const char *tr_label = t_strdup_printf
+			("%s.%s.%u", user->username,
+				mailbox_get_vname(mail->box), mail->uid);
+		if (sieve_trace_log_open(svinst, tr_label, &trace_log) < 0)
+			i_zero(&trace_config);
+	}
+
+	T_BEGIN {
+		/* Collect necessary message data */
+
+		imap_sieve_filter_get_msgdata(sctx, mail, &msgdata);
+
+		/* Compose script execution environment */
+
+		if (sieve_script_env_init(&scriptenv, user, &error) < 0) {
+			sieve_sys_error(svinst,
+				"Failed to initialize script execution: %s",
+				error);
+			ret = -1;
+		} else {
+			scriptenv.default_mailbox = mailbox_get_vname(mail->box);
+			scriptenv.smtp_start = imap_filter_sieve_smtp_start;
+			scriptenv.smtp_add_rcpt = imap_filter_sieve_smtp_add_rcpt;
+			scriptenv.smtp_send = imap_filter_sieve_smtp_send;
+			scriptenv.smtp_abort = imap_filter_sieve_smtp_abort;
+			scriptenv.smtp_finish = imap_filter_sieve_smtp_finish;
+			scriptenv.duplicate_mark = imap_filter_sieve_duplicate_mark;
+			scriptenv.duplicate_check = imap_filter_sieve_duplicate_check;
+			scriptenv.duplicate_flush = imap_filter_sieve_duplicate_flush;
+			scriptenv.trace_log = trace_log;
+			scriptenv.trace_config = trace_config;
+			scriptenv.script_context = sctx;
+
+			i_zero(&estatus);
+			scriptenv.exec_status = &estatus;
+
+			/* Execute script(s) */
+
+			ret = imap_sieve_filter_run_scripts(sctx, user_ehandler,
+							    &msgdata, &scriptenv);
+		}
+	} T_END;
+
+	if ( trace_log != NULL )
+		sieve_trace_log_free(&trace_log);
+
+	*have_warnings_r = (sieve_get_warnings(user_ehandler) > 0);
+	*errors_r = sctx->errors;
+
+	sieve_error_handler_unref(&user_ehandler);
+
+	return ret;
+}
+
+/*
+ * User
+ */
+
+static void imap_filter_sieve_user_deinit(struct mail_user *user)
+{
+	struct imap_filter_sieve_user *ifsuser =
+		IMAP_FILTER_SIEVE_USER_CONTEXT_REQUIRE(user);
+
+	sieve_error_handler_unref(&ifsuser->master_ehandler);
+
+	if (ifsuser->storage != NULL)
+		sieve_storage_unref(&ifsuser->storage);
+	if (ifsuser->svinst != NULL)
+		sieve_deinit(&ifsuser->svinst);
+	if (ifsuser->dup_db != NULL)
+		mail_duplicate_db_deinit(&ifsuser->dup_db);
+
+	ifsuser->module_ctx.super.deinit(user);
+}
+
+static void imap_filter_sieve_user_created(struct mail_user *user)
+{
+	struct imap_filter_sieve_user *ifsuser;
+	struct mail_user_vfuncs *v = user->vlast;
+
+	ifsuser = p_new(user->pool, struct imap_filter_sieve_user, 1);
+	ifsuser->module_ctx.super = *v;
+	user->vlast = &ifsuser->module_ctx.super;
+	v->deinit = imap_filter_sieve_user_deinit;
+	MODULE_CONTEXT_SET(user, imap_filter_sieve_user_module, ifsuser);
+}
+
+/*
+ * Hooks
+ */
+
+static struct mail_storage_hooks imap_filter_sieve_mail_storage_hooks = {
+	.mail_user_created = imap_filter_sieve_user_created,
+};
+
+/*
+ * Client
+ */
+
+void imap_filter_sieve_client_created(struct client *client)
+{
+	struct imap_filter_sieve_user *ifsuser =
+		IMAP_FILTER_SIEVE_USER_CONTEXT_REQUIRE(client->user);
+
+	ifsuser->client = client;
+}
+
+/*
+ *
+ */
+
+void imap_filter_sieve_init(struct module *module)
+{
+	mail_storage_hooks_add(module, &imap_filter_sieve_mail_storage_hooks);
+}
+
+void imap_filter_sieve_deinit(void)
+{
+	mail_storage_hooks_remove(&imap_filter_sieve_mail_storage_hooks);
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/imap-filter-sieve/imap-filter-sieve.h
@@ -0,0 +1,86 @@
+#ifndef IMAP_FILTER_SIEVE_H
+#define IMAP_FILTER_SIEVE_H
+
+#include "imap-filter.h"
+
+struct imap_filter_sieve_script;
+struct imap_filter_sieve_context;
+
+enum imap_filter_sieve_type {
+	IMAP_FILTER_SIEVE_TYPE_DELIVERY,
+	IMAP_FILTER_SIEVE_TYPE_PERSONAL,
+	IMAP_FILTER_SIEVE_TYPE_GLOBAL,
+	IMAP_FILTER_SIEVE_TYPE_SCRIPT,
+};
+
+struct imap_filter_sieve_context {
+	pool_t pool;
+
+	enum imap_filter_sieve_type filter_type;
+
+	struct mail_user *user;
+
+	struct sieve_script *user_script;
+	struct imap_filter_sieve_script *scripts;
+	unsigned int scripts_count;
+
+	string_t *errors;
+
+	bool warnings:1;
+};
+
+/*
+ * FILTER Command
+ */
+
+bool cmd_filter_sieve(struct client_command_context *cmd);
+
+/*
+ * Context
+ */
+
+struct imap_filter_sieve_context *
+imap_filter_sieve_context_create(struct imap_filter_context *ctx,
+				 enum imap_filter_sieve_type type);
+void imap_filter_sieve_context_free(struct imap_filter_sieve_context **_sctx);
+
+/*
+ * Compile
+ */
+
+int imap_filter_sieve_compile(struct imap_filter_sieve_context *sctx,
+			      string_t **errors_r, bool *have_warnings_r);
+
+/*
+ * Open
+ */
+
+void imap_filter_sieve_open_input(struct imap_filter_sieve_context *sctx,
+				  struct istream *input);
+int imap_filter_sieve_open_personal(struct imap_filter_sieve_context *sctx,
+				    const char *name,
+				    enum mail_error *error_code_r,
+				    const char **error_r) ATTR_NULL(2);
+int imap_filter_sieve_open_global(struct imap_filter_sieve_context *sctx,
+				    const char *name,
+				    enum mail_error *error_code_r,
+				    const char **error_r);
+
+/*
+ * Run
+ */
+
+int imap_sieve_filter_run_mail(struct imap_filter_sieve_context *sctx,
+			       struct mail *mail, string_t **errors_r,
+			       bool *have_warnings_r);
+
+/*
+ *
+ */
+
+void imap_filter_sieve_client_created(struct client *client);
+
+void imap_filter_sieve_init(struct module *module);
+void imap_filter_sieve_deinit(void);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/imap-filter-sieve/imap-filter.c
@@ -0,0 +1,254 @@
+/* Copyright (c) 2017-2018 Pigeonhole authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "str.h"
+#include "ostream.h"
+#include "imap-resp-code.h"
+#include "imap-search-args.h"
+
+#include "imap-filter.h"
+#include "imap-filter-sieve.h"
+
+static void imap_filter_args_check(struct imap_filter_context *ctx,
+				   const struct mail_search_arg *sargs)
+{
+	for (; sargs != NULL; sargs = sargs->next) {
+		switch (sargs->type) {
+		case SEARCH_SEQSET:
+			ctx->have_seqsets = TRUE;
+			break;
+		case SEARCH_MODSEQ:
+			ctx->have_modseqs = TRUE;
+			break;
+		case SEARCH_OR:
+		case SEARCH_SUB:
+			imap_filter_args_check(ctx, sargs->value.subargs);
+			break;
+		default:
+			break;
+		}
+	}
+}
+
+static bool
+imap_filter_mail(struct client_command_context *cmd, struct mail *mail)
+{
+	struct imap_filter_context *ctx = cmd->context;
+	struct client *client = cmd->client;
+	string_t *errors = NULL;
+	bool have_warnings = FALSE;
+	int ret;
+
+	// FIXME: return fatal error status when no mail filter activity will
+	// work (e.g. when binary is corrupt)
+	ret = imap_sieve_filter_run_mail(ctx->sieve, mail,
+					 &errors, &have_warnings);
+
+	o_stream_nsend_str(client->output,
+		t_strdup_printf("* %u FILTERED (TAG %s) UID %u ",
+				mail->seq, cmd->tag, mail->uid));
+	if (ret < 0 || have_warnings) {
+		o_stream_nsend_str(client->output,
+			t_strdup_printf("%s {%"PRIuSIZE_T"}\r\n",
+					(ret < 0 ? "ERRORS" : "WARNINGS"),
+					str_len(errors)));
+		o_stream_nsend(client->output,
+			       str_data(errors), str_len(errors));
+		o_stream_nsend_str(client->output, "\r\n");
+	} else {
+		o_stream_nsend_str(client->output, "OK\r\n");
+	}
+
+	/* Handle the result */
+	if (ret < 0) {
+		/* Sieve error; keep */
+	} else {
+		if (ret > 0) {
+			/* Discard */
+			mail_update_flags(mail, MODIFY_ADD, MAIL_DELETED);
+		}
+	}
+
+	return TRUE;
+}
+
+static bool imap_filter_more(struct client_command_context *cmd)
+{
+	struct imap_filter_context *ctx = cmd->context;
+	struct mail *mail;
+	enum mailbox_sync_flags sync_flags;
+	const char *ok_reply;
+	bool tryagain, lost_data;
+
+	if (cmd->cancel) {
+		(void)imap_filter_deinit(ctx);
+		return TRUE;
+	}
+
+	while (mailbox_search_next_nonblock(ctx->search_ctx,
+					    &mail, &tryagain)) {
+		if (!imap_filter_mail(cmd, mail))
+			break;
+	}
+	if (tryagain)
+		return FALSE;
+
+	lost_data = mailbox_search_seen_lost_data(ctx->search_ctx);
+	if (imap_filter_deinit(ctx) < 0) {
+		client_send_box_error(cmd, cmd->client->mailbox);
+		return TRUE;
+	}
+
+	sync_flags = MAILBOX_SYNC_FLAG_FAST;
+	if (!cmd->uid || ctx->have_seqsets)
+		sync_flags |= MAILBOX_SYNC_FLAG_NO_EXPUNGES;
+	ok_reply = t_strdup_printf("OK %sFilter completed",
+		lost_data ? "["IMAP_RESP_CODE_EXPUNGEISSUED"] " : "");
+	return cmd_sync(cmd, sync_flags, 0, ok_reply);
+}
+
+static void imap_filter_more_callback(struct client_command_context *cmd)
+{
+	struct client *client = cmd->client;
+	bool finished;
+
+	o_stream_cork(client->output);
+	finished = command_exec(cmd);
+	o_stream_uncork(client->output);
+
+	if (!finished)
+		(void)client_handle_unfinished_cmd(cmd);
+	else
+		client_command_free(&cmd);
+	cmd_sync_delayed(client);
+
+	if (client->disconnected)
+		client_destroy(client, NULL);
+	else
+		client_continue_pending_input(client);
+}
+
+static bool
+imap_filter_start(struct imap_filter_context *ctx,
+		  struct mail_search_args *sargs)
+{
+	struct client_command_context *cmd = ctx->cmd;
+
+	imap_filter_args_check(ctx, sargs->args);
+
+	if (ctx->have_modseqs)
+		(void)client_enable(cmd->client, MAILBOX_FEATURE_CONDSTORE);
+
+	ctx->box = cmd->client->mailbox;
+	ctx->trans = mailbox_transaction_begin(ctx->box, 0,
+					       imap_client_command_get_reason(cmd));
+	ctx->sargs = sargs;
+	ctx->search_ctx = mailbox_search_init(ctx->trans, sargs, NULL, 0, NULL);
+
+	cmd->func = imap_filter_more;
+	cmd->context = ctx;
+
+	if (imap_filter_more(cmd))
+		return TRUE;
+
+	/* we may have moved onto syncing by now */
+	if (cmd->func == imap_filter_more) {
+		ctx->to = timeout_add(0, imap_filter_more_callback, cmd);
+		cmd->state = CLIENT_COMMAND_STATE_WAIT_EXTERNAL;
+	}
+	return FALSE;
+}
+
+static bool
+imap_filter_parse_search(struct imap_filter_context *ctx,
+			const struct imap_arg *args)
+{
+	struct client_command_context *cmd = ctx->cmd;
+	struct mail_search_args *sargs;
+	const char *charset;
+	int ret;
+
+	if (imap_arg_atom_equals(args, "CHARSET")) {
+		/* CHARSET specified */
+		if (!imap_arg_get_astring(&args[1], &charset)) {
+			client_send_command_error(cmd,
+				"Invalid charset argument.");
+			imap_filter_context_free(ctx);
+			return TRUE;
+		}
+		args += 2;
+	} else {
+		charset = "UTF-8";
+	}
+
+	ret = imap_search_args_build(cmd, args, charset, &sargs);
+	if (ret <= 0) {
+		imap_filter_context_free(ctx);
+		return ret < 0;
+	}
+
+	return imap_filter_start(ctx, sargs);
+}
+
+bool imap_filter_search(struct client_command_context *cmd)
+{
+	struct imap_filter_context *ctx = cmd->context;
+	const struct imap_arg *args;
+	const char *error;
+	enum imap_parser_error parse_error;
+	int ret;
+
+	ret = imap_parser_read_args(ctx->parser, 0, 0, &args);
+	if (ret < 0) {
+		if (ret == -2)
+			return FALSE;
+		error = imap_parser_get_error(ctx->parser, &parse_error);
+		switch (parse_error) {
+		case IMAP_PARSE_ERROR_NONE:
+			i_unreached();
+		case IMAP_PARSE_ERROR_LITERAL_TOO_BIG:
+			client_disconnect_with_error(ctx->cmd->client, error);
+			break;
+		default:
+			client_send_command_error(ctx->cmd, error);
+			break;
+		}
+		return TRUE;
+	}
+	return imap_filter_parse_search(ctx, args);
+}
+
+int imap_filter_deinit(struct imap_filter_context *ctx)
+{
+	int ret = 0;
+
+	o_stream_set_flush_callback(ctx->cmd->client->output,
+				    client_output, ctx->cmd->client);
+	ctx->cmd->client->input_lock = NULL;
+	imap_parser_unref(&ctx->parser);
+
+	if (ctx->search_ctx != NULL &&
+	    mailbox_search_deinit(&ctx->search_ctx) < 0)
+		ret = -1;
+
+	if (ctx->trans != NULL)
+		(void)mailbox_transaction_commit(&ctx->trans);
+
+	timeout_remove(&ctx->to);
+	if (ctx->sargs != NULL) {
+		mail_search_args_deinit(ctx->sargs);
+		mail_search_args_unref(&ctx->sargs);
+	}
+	imap_filter_context_free(ctx);
+
+	ctx->cmd->context = NULL;
+	return ret;
+}
+
+void imap_filter_context_free(struct imap_filter_context *ctx)
+{
+	imap_filter_sieve_context_free(&ctx->sieve);
+}
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/imap-filter-sieve/imap-filter.h
@@ -0,0 +1,43 @@
+#ifndef IMAP_FILTER_H
+#define IMAP_FILTER_H
+
+struct mail_duplicate_db;
+
+struct sieve_script;
+struct sieve_storage;
+struct sieve_binary;
+
+struct imap_filter_context {
+	struct client_command_context *cmd;
+	struct mailbox *box;
+	struct mailbox_transaction_context *trans;
+	struct mail_search_context *search_ctx;
+
+	struct imap_parser *parser;
+
+	struct imap_filter_sieve_context *sieve;
+	const char *script_name;
+	uoff_t script_len;
+	struct istream *script_input;
+
+	struct mail_search_args *sargs;
+
+	struct timeout *to;
+
+	bool failed:1;
+	bool compile_failure:1;
+	bool have_seqsets:1;
+	bool have_modseqs:1;
+};
+
+bool imap_filter_search(struct client_command_context *cmd);
+
+int imap_filter_deinit(struct imap_filter_context *ctx);
+
+void imap_filter_context_free(struct imap_filter_context *ctx);
+
+/* Commands */
+
+bool cmd_filter(struct client_command_context *cmd);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/imapsieve/Makefile.am
@@ -0,0 +1,40 @@
+imap_moduledir = $(dovecot_moduledir)
+sieve_plugindir = $(dovecot_moduledir)/sieve
+
+imap_module_LTLIBRARIES = lib95_imap_sieve_plugin.la
+sieve_plugin_LTLIBRARIES = lib90_sieve_imapsieve_plugin.la
+
+lib95_imap_sieve_plugin_la_LDFLAGS = -module -avoid-version
+lib90_sieve_imapsieve_plugin_la_LDFLAGS = -module -avoid-version
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib-sieve \
+	-I$(top_srcdir)/src/lib-sieve/util \
+	-I$(top_srcdir)/src/lib-sieve/plugins/environment \
+	$(LIBDOVECOT_IMAP_INCLUDE) \
+	$(LIBDOVECOT_LDA_INCLUDE) \
+	$(LIBDOVECOT_INCLUDE) \
+	-DPKG_RUNDIR=\""$(rundir)"\"
+
+lib95_imap_sieve_plugin_la_SOURCES = \
+	ext-imapsieve.c \
+	ext-imapsieve-environment.c \
+	imap-sieve.c \
+	imap-sieve-storage.c \
+	imap-sieve-plugin.c
+lib95_imap_sieve_plugin_la_LIBADD = \
+	$(top_builddir)/src/lib-sieve/libdovecot-sieve.la
+
+lib90_sieve_imapsieve_plugin_la_SOURCES = \
+	ext-imapsieve.c \
+	sieve-imapsieve-plugin.c
+lib90_sieve_imapsieve_plugin_la_CPPFLAGS = \
+	${AM_CPPFLAGS} \
+	-D__IMAPSIEVE_DUMMY
+
+noinst_HEADERS = \
+	ext-imapsieve-common.h \
+	imap-sieve.h \
+	imap-sieve-storage.h \
+	imap-sieve-plugin.h \
+	sieve-imapsieve-plugin.h
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/imapsieve/ext-imapsieve-common.h
@@ -0,0 +1,29 @@
+#ifndef EXT_IMAPSIEVE_COMMON_H
+#define EXT_IMAPSIEVE_COMMON_H
+
+#include "sieve-extensions.h"
+
+#include "imap-sieve.h"
+
+/*
+ * Extensions
+ */
+
+extern const struct sieve_extension_def imapsieve_extension;
+extern const struct sieve_extension_def imapsieve_extension_dummy;
+
+extern const struct sieve_extension_def vnd_imapsieve_extension;
+extern const struct sieve_extension_def vnd_imapsieve_extension_dummy;
+
+/*
+ * Environment items
+ */
+
+void ext_imapsieve_environment_items_register
+	(const struct sieve_extension *ext,
+		const struct sieve_runtime_env *renv);
+void ext_imapsieve_environment_vendor_items_register
+	(const struct sieve_extension *ext,
+		const struct sieve_runtime_env *renv);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/imapsieve/ext-imapsieve-environment.c
@@ -0,0 +1,176 @@
+/* Copyright (c) 2016-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "mail-storage.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-runtime.h"
+
+#include "sieve-ext-environment.h"
+
+#include "ext-imapsieve-common.h"
+
+/*
+ * Environment items
+ */
+
+/* imap.user */
+
+static const char *envit_imap_user_get_value
+(const struct sieve_runtime_env *renv,
+	const char *name ATTR_UNUSED)
+{
+	return renv->svinst->username;
+}
+
+const struct sieve_environment_item imap_user_env_item = {
+	.name = "imap.user",
+	.get_value = envit_imap_user_get_value
+};
+
+/* imap.email */
+
+static const char *envit_imap_email_get_value
+(const struct sieve_runtime_env *renv,
+	const char *name ATTR_UNUSED)
+{
+	const struct smtp_address *user_email =
+		sieve_get_user_email(renv->svinst);
+
+	if (user_email == NULL)
+		return NULL;
+	return smtp_address_encode(user_email);
+}
+
+const struct sieve_environment_item imap_email_env_item = {
+	.name = "imap.email",
+	.get_value = envit_imap_email_get_value
+};
+
+/* imap.cause */
+
+static const char *envit_imap_cause_get_value
+(const struct sieve_runtime_env *renv,
+	const char *name ATTR_UNUSED)
+{
+	const struct sieve_script_env *senv = renv->scriptenv;
+	struct imap_sieve_context *isctx =
+		(struct imap_sieve_context *)senv->script_context;
+
+	return isctx->event.cause;
+}
+
+const struct sieve_environment_item imap_cause_env_item = {
+	.name = "imap.cause",
+	.get_value = envit_imap_cause_get_value
+};
+
+/* imap.mailbox */
+
+static const char *envit_imap_mailbox_get_value
+(const struct sieve_runtime_env *renv,
+	const char *name ATTR_UNUSED)
+{
+	const struct sieve_message_data *msgdata = renv->msgdata;
+
+	return mailbox_get_vname(msgdata->mail->box);
+}
+
+const struct sieve_environment_item imap_mailbox_env_item = {
+	.name = "imap.mailbox",
+	.get_value = envit_imap_mailbox_get_value
+};
+
+
+/* imap.changedflags */
+
+static const char *envit_imap_changedflags_get_value
+(const struct sieve_runtime_env *renv,
+	const char *name ATTR_UNUSED)
+{
+	const struct sieve_script_env *senv = renv->scriptenv;
+	struct imap_sieve_context *isctx =
+		(struct imap_sieve_context *)senv->script_context;
+
+	return isctx->event.changed_flags;
+}
+
+const struct sieve_environment_item imap_changedflags_env_item = {
+	.name = "imap.changedflags",
+	.get_value = envit_imap_changedflags_get_value
+};
+
+/* vnd.dovecot.mailbox-from */
+
+static const char *envit_vnd_mailbox_from_get_value
+(const struct sieve_runtime_env *renv,
+	const char *name ATTR_UNUSED)
+{
+	const struct sieve_script_env *senv = renv->scriptenv;
+	struct imap_sieve_context *isctx =
+		(struct imap_sieve_context *)senv->script_context;
+
+	return mailbox_get_vname(isctx->event.src_mailbox);
+}
+
+const struct sieve_environment_item vnd_mailbox_from_env_item = {
+	.name = "vnd.dovecot.mailbox-from",
+	.get_value = envit_vnd_mailbox_from_get_value
+};
+
+/* vnd.dovecot.mailbox-to */
+
+static const char *envit_vnd_mailbox_to_get_value
+(const struct sieve_runtime_env *renv,
+	const char *name ATTR_UNUSED)
+{
+	const struct sieve_script_env *senv = renv->scriptenv;
+	struct imap_sieve_context *isctx =
+		(struct imap_sieve_context *)senv->script_context;
+
+	return mailbox_get_vname(isctx->event.dest_mailbox);
+}
+
+const struct sieve_environment_item vnd_mailbox_to_env_item = {
+	.name = "vnd.dovecot.mailbox-to",
+	.get_value = envit_vnd_mailbox_to_get_value
+};
+
+/*
+ * Register
+ */
+
+void ext_imapsieve_environment_items_register
+(const struct sieve_extension *ext, const struct sieve_runtime_env *renv)
+{
+	const struct sieve_extension *env_ext =
+		(const struct sieve_extension *) ext->context;
+
+	sieve_environment_item_register
+		(env_ext, renv->interp, &imap_user_env_item);
+	sieve_environment_item_register
+		(env_ext, renv->interp, &imap_email_env_item);
+	sieve_environment_item_register
+		(env_ext, renv->interp, &imap_cause_env_item);
+	sieve_environment_item_register
+		(env_ext, renv->interp, &imap_mailbox_env_item);
+	sieve_environment_item_register
+		(env_ext, renv->interp, &imap_changedflags_env_item);
+}
+
+void ext_imapsieve_environment_vendor_items_register
+(const struct sieve_extension *ext, const struct sieve_runtime_env *renv)
+{
+	const struct sieve_extension *env_ext =
+		(const struct sieve_extension *) ext->context;
+
+	sieve_environment_item_register
+		(env_ext, renv->interp, &vnd_mailbox_from_env_item);
+	sieve_environment_item_register
+		(env_ext, renv->interp, &vnd_mailbox_to_env_item);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/imapsieve/ext-imapsieve.c
@@ -0,0 +1,168 @@
+/* Copyright (c) 2016-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension imapsieve
+ * -------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 6785
+ * Implementation: full
+ * Status: experimental
+ *
+ */
+
+#include "lib.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-binary.h"
+
+#include "sieve-validator.h"
+#include "sieve-interpreter.h"
+
+#include "sieve-ext-environment.h"
+
+#include "ext-imapsieve-common.h"
+
+/*
+ * Extension
+ */
+
+static bool ext_imapsieve_load
+	(const struct sieve_extension *ext, void **context);
+static bool ext_vnd_imapsieve_load
+	(const struct sieve_extension *ext, void **context);
+static bool ext_vnd_imapsieve_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+static bool ext_imapsieve_interpreter_load
+	(const struct sieve_extension *ext,
+		const struct sieve_runtime_env *renv,
+		sieve_size_t *address ATTR_UNUSED);
+
+#ifdef __IMAPSIEVE_DUMMY
+const struct sieve_extension_def imapsieve_extension_dummy = {
+#else
+const struct sieve_extension_def imapsieve_extension = {
+#endif
+	.name = "imapsieve",
+	.load = ext_imapsieve_load,
+	.interpreter_load = ext_imapsieve_interpreter_load
+};
+
+#ifdef __IMAPSIEVE_DUMMY
+const struct sieve_extension_def vnd_imapsieve_extension_dummy = {
+#else
+const struct sieve_extension_def vnd_imapsieve_extension = {
+#endif
+	.name = "vnd.dovecot.imapsieve",
+	.load = ext_vnd_imapsieve_load,
+	.interpreter_load = ext_imapsieve_interpreter_load,
+	.validator_load = ext_vnd_imapsieve_validator_load
+};
+
+/*
+ * Context
+ */
+
+static bool ext_imapsieve_load
+(const struct sieve_extension *ext, void **context)
+{
+	*context = (void*)
+		sieve_ext_environment_require_extension(ext->svinst);
+	return TRUE;
+}
+
+static bool ext_vnd_imapsieve_load
+(const struct sieve_extension *ext, void **context)
+{
+	*context = (void*)sieve_extension_require
+#ifdef __IMAPSIEVE_DUMMY
+		(ext->svinst, &imapsieve_extension_dummy, TRUE);
+#else
+		(ext->svinst, &imapsieve_extension, TRUE);
+#endif
+	return TRUE;
+}
+
+/*
+ * Validator
+ */
+
+static bool ext_vnd_imapsieve_validator_load
+(const struct sieve_extension *ext ATTR_UNUSED,
+	struct sieve_validator *valdtr)
+{
+	const struct sieve_extension *ims_ext;
+
+	/* Load environment extension implicitly */
+
+	ims_ext = sieve_validator_extension_load_implicit
+#ifdef __IMAPSIEVE_DUMMY
+		(valdtr, imapsieve_extension_dummy.name);
+#else
+		(valdtr, imapsieve_extension.name);
+#endif
+	if ( ims_ext == NULL )
+		return FALSE;
+
+	return TRUE;
+}
+
+/*
+ * Interpreter
+ */
+
+static int ext_imapsieve_interpreter_run
+	(const struct sieve_extension *this_ext,
+		const struct sieve_runtime_env *renv,
+		void *context, bool deferred);
+
+const struct sieve_interpreter_extension
+imapsieve_interpreter_extension = {
+#ifdef __IMAPSIEVE_DUMMY
+	.ext_def = &imapsieve_extension_dummy,
+#else
+	.ext_def = &imapsieve_extension,
+#endif
+	.run = ext_imapsieve_interpreter_run
+};
+
+static bool ext_imapsieve_interpreter_load
+(const struct sieve_extension *ext ATTR_UNUSED,
+	const struct sieve_runtime_env *renv,
+	sieve_size_t *address ATTR_UNUSED)
+{
+	sieve_interpreter_extension_register(renv->interp,
+		ext, &imapsieve_interpreter_extension, NULL);
+	return TRUE;
+}
+
+#ifdef __IMAPSIEVE_DUMMY
+static int ext_imapsieve_interpreter_run
+(const struct sieve_extension *ext ATTR_UNUSED,
+	const struct sieve_runtime_env *renv,
+	void *context ATTR_UNUSED, bool deferred)
+{
+	if ( !deferred ) {
+		sieve_runtime_error(renv, NULL,
+			"the imapsieve extension cannot be used outside IMAP");
+	}
+	return SIEVE_EXEC_FAILURE;
+}
+#else
+static int ext_imapsieve_interpreter_run
+(const struct sieve_extension *ext,
+	const struct sieve_runtime_env *renv,
+	void *context ATTR_UNUSED, bool deferred ATTR_UNUSED)
+{
+	if (ext->def == &vnd_imapsieve_extension) {
+		const struct sieve_extension *ims_ext =
+			(const struct sieve_extension *)ext->context;
+		ext_imapsieve_environment_vendor_items_register(ims_ext, renv);
+	} else {
+		ext_imapsieve_environment_items_register(ext, renv);
+	}
+	return SIEVE_EXEC_OK;
+}
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/imapsieve/imap-sieve-plugin.c
@@ -0,0 +1,60 @@
+/* Copyright (c) 2016-2018 Pigeonhole authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "str.h"
+
+#include "imap-sieve.h"
+#include "imap-sieve-storage.h"
+
+#include "imap-sieve-plugin.h"
+
+static struct module *imap_sieve_module;
+static imap_client_created_func_t *next_hook_client_created;
+
+/*
+ * Client
+ */
+
+static void imap_sieve_client_created(struct client **clientp)
+{
+	struct client *client = *clientp;
+	struct mail_user *user = client->user;
+	const char *url = NULL;
+
+	if (mail_user_is_plugin_loaded(user, imap_sieve_module)) {
+		url = mail_user_plugin_getenv(user, "imapsieve_url");
+		// FIXME: parse the URL and report error if it is bad
+		if (url != NULL && strncasecmp(url, "sieve:", 6) == 0) {
+			client_add_capability(client, t_strconcat(
+				"IMAPSIEVE=", url, NULL));
+		} else {
+			url = NULL;
+		}
+
+		imap_sieve_storage_client_created(client, (url != NULL));
+	}
+
+	if (next_hook_client_created != NULL)
+		next_hook_client_created(clientp);
+}
+
+/*
+ * Plugin
+ */
+
+const char *imap_sieve_plugin_version = DOVECOT_ABI_VERSION;
+const char imap_sieve_plugin_binary_dependency[] = "imap";
+
+void imap_sieve_plugin_init(struct module *module)
+{
+	imap_sieve_module = module;
+	next_hook_client_created =
+		imap_client_created_hook_set(imap_sieve_client_created);
+	imap_sieve_storage_init(module);
+}
+
+void imap_sieve_plugin_deinit(void)
+{
+	imap_sieve_storage_deinit();
+	imap_client_created_hook_set(next_hook_client_created);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/imapsieve/imap-sieve-plugin.h
@@ -0,0 +1,11 @@
+#ifndef IMAP_SIEVE_PLUGIN_H
+#define IMAP_SIEVE_PLUGIN_H
+
+struct module;
+
+extern const char imap_sieve_plugin_binary_dependency[];
+
+void imap_sieve_plugin_init(struct module *module);
+void imap_sieve_plugin_deinit(void);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/imapsieve/imap-sieve-storage.c
@@ -0,0 +1,1249 @@
+/* Copyright (c) 2016-2018 Pigeonhole authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "array.h"
+#include "hash.h"
+#include "str.h"
+#include "istream.h"
+#include "ostream.h"
+#include "module-context.h"
+#include "mail-user.h"
+#include "mail-storage-private.h"
+#include "mailbox-attribute.h"
+#include "mailbox-list-private.h"
+#include "imap-match.h"
+#include "imap-util.h"
+
+#include "imap-sieve.h"
+#include "imap-sieve-storage.h"
+
+#define MAILBOX_ATTRIBUTE_IMAPSIEVE_SCRIPT "imapsieve/script"
+#define MAIL_SERVER_ATTRIBUTE_IMAPSIEVE_SCRIPT "imapsieve/script"
+
+#define IMAP_SIEVE_USER_CONTEXT(obj) \
+	MODULE_CONTEXT(obj, imap_sieve_user_module)
+#define IMAP_SIEVE_USER_CONTEXT_REQUIRE(obj) \
+	MODULE_CONTEXT_REQUIRE(obj, imap_sieve_user_module)
+#define IMAP_SIEVE_CONTEXT(obj) \
+	MODULE_CONTEXT(obj, imap_sieve_storage_module)
+#define IMAP_SIEVE_CONTEXT_REQUIRE(obj) \
+	MODULE_CONTEXT_REQUIRE(obj, imap_sieve_storage_module)
+#define IMAP_SIEVE_MAIL_CONTEXT(obj) \
+	MODULE_CONTEXT_REQUIRE(obj, imap_sieve_mail_module)
+
+struct imap_sieve_mailbox_rule;
+struct imap_sieve_user;
+struct imap_sieve_mailbox_event;
+struct imap_sieve_mailbox_transaction;
+struct imap_sieve_mail;
+
+enum imap_sieve_command {
+	IMAP_SIEVE_CMD_NONE = 0,
+	IMAP_SIEVE_CMD_APPEND,
+	IMAP_SIEVE_CMD_COPY,
+	IMAP_SIEVE_CMD_MOVE,
+	IMAP_SIEVE_CMD_STORE,
+	IMAP_SIEVE_CMD_OTHER
+};
+
+ARRAY_DEFINE_TYPE(imap_sieve_mailbox_rule,
+	struct imap_sieve_mailbox_rule *);
+ARRAY_DEFINE_TYPE(imap_sieve_mailbox_event,
+	struct imap_sieve_mailbox_event);
+
+HASH_TABLE_DEFINE_TYPE(imap_sieve_mailbox_rule,
+	struct imap_sieve_mailbox_rule *,
+	struct imap_sieve_mailbox_rule *);
+
+struct imap_sieve_mailbox_rule {
+	unsigned int index;
+	const char *mailbox;
+	const char *from;
+	const char *const *causes;
+	const char *before, *after;
+	const char *copy_source_after;
+};
+
+struct imap_sieve_user {
+	union mail_user_module_context module_ctx;
+	struct client *client;
+	struct imap_sieve *isieve;
+
+	enum imap_sieve_command cur_cmd;
+
+	HASH_TABLE_TYPE(imap_sieve_mailbox_rule) mbox_rules;
+	ARRAY_TYPE(imap_sieve_mailbox_rule) mbox_patterns;
+
+	bool sieve_active:1;
+	bool user_script:1;
+};
+
+struct imap_sieve_mailbox_event {
+	uint32_t dest_mail_uid, src_mail_uid;
+	unsigned int save_seq;
+
+	const char *changed_flags;
+};
+
+struct imap_sieve_mailbox_transaction {
+	pool_t pool;
+
+	union mailbox_transaction_module_context module_ctx;
+
+	struct mailbox *src_box;
+	struct mailbox_transaction_context *src_mail_trans;
+
+	ARRAY_TYPE(imap_sieve_mailbox_event) events;
+};
+
+struct imap_sieve_mail {
+	union mail_module_context module_ctx;
+
+	string_t *flags;
+};
+
+static MODULE_CONTEXT_DEFINE_INIT(imap_sieve_user_module,
+				  &mail_user_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(imap_sieve_storage_module,
+				  &mail_storage_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(imap_sieve_mail_module,
+				  &mail_module_register);
+
+static void
+imap_sieve_mailbox_rules_get(struct mail_user *user,
+	struct mailbox *dst_box, struct mailbox *src_box,
+	const char *cause,
+	ARRAY_TYPE(imap_sieve_mailbox_rule) *rules);
+
+/*
+ * Logging
+ */
+
+static inline void
+imap_sieve_debug(struct mail_user *user,
+	const char *format, ...) ATTR_FORMAT(2, 3);
+static inline void
+imap_sieve_debug(struct mail_user *user,
+	const char *format, ...)
+{
+	va_list args;
+
+	if (user->mail_debug) {
+		va_start(args, format);
+		i_debug("imapsieve: %s",
+			t_strdup_vprintf(format, args));
+		va_end(args);
+	}
+}
+
+static inline void
+imap_sieve_warning(struct mail_user *user ATTR_UNUSED,
+	const char *format, ...) ATTR_FORMAT(2, 3);
+static inline void
+imap_sieve_warning(struct mail_user *user ATTR_UNUSED,
+	const char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+	i_warning("imapsieve: %s",
+		t_strdup_vprintf(format, args));
+	va_end(args);
+}
+
+static inline void
+imap_sieve_mailbox_debug(struct mailbox *box,
+	const char *format, ...) ATTR_FORMAT(2, 3);
+static inline void
+imap_sieve_mailbox_debug(struct mailbox *box,
+	const char *format, ...)
+{
+	va_list args;
+
+	if (box->storage->user->mail_debug) {
+		va_start(args, format);
+		i_debug("imapsieve: mailbox %s: %s",
+			mailbox_get_vname(box),
+			t_strdup_vprintf(format, args));
+		va_end(args);
+	}
+}
+
+static inline void
+imap_sieve_mailbox_warning(struct mailbox *box,
+	const char *format, ...) ATTR_FORMAT(2, 3);
+static inline void
+imap_sieve_mailbox_warning(struct mailbox *box,
+	const char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+	i_warning("imapsieve: mailbox %s: %s",
+		mailbox_get_vname(box),
+		t_strdup_vprintf(format, args));
+	va_end(args);
+}
+
+static inline void
+imap_sieve_mailbox_error(struct mailbox *box,
+	const char *format, ...) ATTR_FORMAT(2, 3);
+static inline void
+imap_sieve_mailbox_error(struct mailbox *box,
+	const char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+	i_error("imapsieve: mailbox %s: %s",
+		mailbox_get_vname(box),
+		t_strdup_vprintf(format, args));
+	va_end(args);
+}
+
+/*
+ * Events
+ */
+
+static int imap_sieve_mailbox_get_script_real
+(struct mailbox *box,
+	const char **script_name_r)
+{
+	struct mail_user *user = box->storage->user;
+	struct mail_attribute_value value;
+	int ret;
+
+	*script_name_r = NULL;
+
+	/* get the name of the Sieve script from mailbox METADATA */
+	if ((ret=mailbox_attribute_get(box, MAIL_ATTRIBUTE_TYPE_SHARED,
+			MAILBOX_ATTRIBUTE_IMAPSIEVE_SCRIPT, &value)) < 0) {
+		imap_sieve_mailbox_error(box,
+			"Failed to read /shared/"
+			MAILBOX_ATTRIBUTE_IMAPSIEVE_SCRIPT" "
+			"mailbox attribute: %s",
+			mailbox_get_last_error(box, NULL));
+		return -1;
+	}
+
+	if (ret > 0) {
+		imap_sieve_mailbox_debug(box,
+			"Mailbox attribute /shared/"
+			MAILBOX_ATTRIBUTE_IMAPSIEVE_SCRIPT" "
+			"points to Sieve script `%s'", value.value);
+
+	/* if not found, get the name of the Sieve script from
+	   server METADATA */
+	} else {
+		struct mail_namespace *ns;
+		struct mailbox *inbox;
+
+		imap_sieve_mailbox_debug(box,
+			"Mailbox attribute /shared/"
+			MAILBOX_ATTRIBUTE_IMAPSIEVE_SCRIPT" "
+			"not found");
+
+		ns = mail_namespace_find_inbox(user->namespaces);
+		inbox = mailbox_alloc(ns->list, "INBOX",
+			MAILBOX_FLAG_READONLY);
+		ret = mailbox_attribute_get(inbox,
+			MAIL_ATTRIBUTE_TYPE_SHARED,
+			MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER
+			MAILBOX_ATTRIBUTE_IMAPSIEVE_SCRIPT, &value);
+
+		if (ret <= 0) {
+			if (ret < 0) {
+				imap_sieve_mailbox_error(box,
+					"Failed to read /shared/"
+					MAIL_SERVER_ATTRIBUTE_IMAPSIEVE_SCRIPT" "
+					"server attribute: %s",
+					mailbox_get_last_error(inbox, NULL));
+			} else if (ret == 0) {
+				imap_sieve_mailbox_debug(box,
+					"Server attribute /shared/"
+					MAIL_SERVER_ATTRIBUTE_IMAPSIEVE_SCRIPT" "
+					"not found");
+			}
+			mailbox_free(&inbox);
+			return ret;
+		}
+		mailbox_free(&inbox);
+
+		imap_sieve_mailbox_debug(box,
+			"Server attribute /shared/"
+			MAIL_SERVER_ATTRIBUTE_IMAPSIEVE_SCRIPT" "
+			"points to Sieve script `%s'", value.value);
+	}
+
+	*script_name_r = value.value;
+	return 1;
+}
+
+static int imap_sieve_mailbox_get_script
+(struct mailbox *box, const char **script_name_r)
+{
+	int ret;
+
+	ret = imap_sieve_mailbox_get_script_real
+		(box, script_name_r);
+	return ret;
+}
+
+static struct imap_sieve_mailbox_event *
+imap_sieve_create_mailbox_event
+(struct mailbox_transaction_context *t, struct mail *dest_mail)
+{
+	struct imap_sieve_mailbox_transaction *ismt = IMAP_SIEVE_CONTEXT_REQUIRE(t);
+	struct imap_sieve_mailbox_event *event;
+
+	if (!array_is_created(&ismt->events))
+		i_array_init(&ismt->events, 64);
+
+	event = array_append_space(&ismt->events);
+	event->save_seq = t->save_count;
+	event->dest_mail_uid = dest_mail->uid;
+	return event;
+}
+
+static void imap_sieve_add_mailbox_event
+(struct mailbox_transaction_context *t,
+	struct mail *dest_mail, struct mailbox *src_box,
+	const char *changed_flags)
+{
+	struct imap_sieve_mailbox_transaction *ismt = IMAP_SIEVE_CONTEXT_REQUIRE(t);
+	struct imap_sieve_mailbox_event *event;
+
+	i_assert(ismt->src_box == NULL || ismt->src_box == src_box);
+	ismt->src_box = src_box;
+
+	event = imap_sieve_create_mailbox_event(t, dest_mail);
+	event->changed_flags = p_strdup(ismt->pool, changed_flags);
+}
+
+static void imap_sieve_add_mailbox_copy_event
+(struct mailbox_transaction_context *t,
+	struct mail *dest_mail, struct mail *src_mail)
+{
+	struct imap_sieve_mailbox_transaction *ismt = IMAP_SIEVE_CONTEXT_REQUIRE(t);
+	struct imap_sieve_mailbox_event *event;
+
+	i_assert(ismt->src_box == NULL || ismt->src_box == src_mail->box);
+	i_assert(ismt->src_mail_trans == NULL ||
+		ismt->src_mail_trans == src_mail->transaction);
+
+	ismt->src_box = src_mail->box;
+	ismt->src_mail_trans = src_mail->transaction;
+
+	event = imap_sieve_create_mailbox_event(t, dest_mail);
+	event->src_mail_uid = src_mail->uid;
+}
+
+/*
+ * Mail
+ */
+
+static void
+imap_sieve_mail_update_flags(struct mail *_mail,
+	enum modify_type modify_type, enum mail_flags flags)
+{
+	struct mail_private *mail = (struct mail_private *)_mail;
+	struct imap_sieve_mail *ismail = IMAP_SIEVE_MAIL_CONTEXT(mail);
+	enum mail_flags old_flags, new_flags, changed_flags;
+
+	old_flags = mail_get_flags(_mail);
+	ismail->module_ctx.super.update_flags(_mail, modify_type, flags);
+	new_flags = mail_get_flags(_mail);
+
+	changed_flags = old_flags ^ new_flags;
+	if (changed_flags == 0)
+		return;
+
+	if (ismail->flags == NULL)
+		ismail->flags = str_new(default_pool, 64);
+	imap_write_flags(ismail->flags, changed_flags, NULL);
+}
+
+static void
+imap_sieve_mail_update_keywords(struct mail *_mail,
+	enum modify_type modify_type, struct mail_keywords *keywords)
+{
+	struct mail_private *mail = (struct mail_private *)_mail;
+	struct imap_sieve_mail *ismail = IMAP_SIEVE_MAIL_CONTEXT(mail);
+	const char *const *old_keywords, *const *new_keywords;
+	unsigned int i, j;
+
+	old_keywords = mail_get_keywords(_mail);
+	ismail->module_ctx.super.update_keywords(_mail, modify_type, keywords);
+	new_keywords = mail_get_keywords(_mail);
+
+	if (ismail->flags == NULL)
+		ismail->flags = str_new(default_pool, 64);
+
+	/* Removed flags */
+	for (i = 0; old_keywords[i] != NULL; i++) {
+		for (j = 0; new_keywords[j] != NULL; j++) {
+			if (strcmp(old_keywords[i], new_keywords[j]) == 0)
+				break;
+		}
+		if (new_keywords[j] == NULL) {
+			if (str_len(ismail->flags) > 0)
+				str_append_c(ismail->flags, ' ');
+			str_append(ismail->flags, old_keywords[i]);
+		}
+	}
+
+	/* Added flags */
+	for (i = 0; new_keywords[i] != NULL; i++) {
+		for (j = 0; old_keywords[j] != NULL; j++) {
+			if (strcmp(new_keywords[i], old_keywords[j]) == 0)
+				break;
+		}
+		if (old_keywords[j] == NULL) {
+			if (str_len(ismail->flags) > 0)
+				str_append_c(ismail->flags, ' ');
+			str_append(ismail->flags, new_keywords[i]);
+		}
+	}
+}
+
+static void imap_sieve_mail_close(struct mail *_mail)
+{
+	struct mail_private *mail = (struct mail_private *)_mail;
+	struct mailbox_transaction_context *t = _mail->transaction;
+	struct imap_sieve_mail *ismail = IMAP_SIEVE_MAIL_CONTEXT(mail);
+
+	if (ismail->flags != NULL && str_len(ismail->flags) > 0) {
+		if (!_mail->expunged) {
+			imap_sieve_mailbox_debug(_mail->box,
+				"FLAG event (changed flags: %s)",
+				str_c(ismail->flags));
+
+			imap_sieve_add_mailbox_event(t,
+				_mail, _mail->box, str_c(ismail->flags));
+		}
+		str_truncate(ismail->flags, 0);
+	}
+
+	ismail->module_ctx.super.close(_mail);
+}
+
+static void imap_sieve_mail_free(struct mail *_mail)
+{
+	struct mail_private *mail = (struct mail_private *)_mail;
+	struct imap_sieve_mail *ismail = IMAP_SIEVE_MAIL_CONTEXT(mail);
+	string_t *flags = ismail->flags;
+
+	ismail->module_ctx.super.free(_mail);
+
+	if (flags != NULL)
+		str_free(&flags);
+}
+
+static void imap_sieve_mail_allocated(struct mail *_mail)
+{
+	struct mail_private *mail = (struct mail_private *)_mail;
+	struct imap_sieve_mailbox_transaction *ismt =
+		IMAP_SIEVE_CONTEXT(_mail->transaction);
+	struct mail_user *user = _mail->box->storage->user;
+	struct imap_sieve_user *isuser =
+		IMAP_SIEVE_USER_CONTEXT_REQUIRE(user);
+	struct mail_vfuncs *v = mail->vlast;
+	struct imap_sieve_mail *ismail;
+
+	if (ismt == NULL || isuser->sieve_active)
+		return;
+
+	ismail = p_new(mail->pool, struct imap_sieve_mail, 1);
+	ismail->module_ctx.super = *v;
+	mail->vlast = &ismail->module_ctx.super;
+
+	v->close = imap_sieve_mail_close;
+	v->free = imap_sieve_mail_free;
+	v->update_flags = imap_sieve_mail_update_flags;
+	v->update_keywords = imap_sieve_mail_update_keywords;
+	MODULE_CONTEXT_SET(mail, imap_sieve_mail_module, ismail);
+}
+
+/*
+ * Save/copy
+ */
+
+static int
+imap_sieve_mailbox_copy(struct mail_save_context *ctx, struct mail *mail)
+{
+	struct mailbox_transaction_context *t = ctx->transaction;
+	struct mail_storage *storage = t->box->storage;
+	struct mail_user *user = storage->user;
+	struct imap_sieve_user *isuser =
+		IMAP_SIEVE_USER_CONTEXT_REQUIRE(user);
+	union mailbox_module_context *lbox =
+		IMAP_SIEVE_CONTEXT_REQUIRE(t->box);
+	struct imap_sieve_mailbox_transaction *ismt =
+		IMAP_SIEVE_CONTEXT(t);
+
+	if (lbox->super.copy(ctx, mail) < 0)
+		return -1;
+
+	if (ismt != NULL && !isuser->sieve_active &&
+		!ctx->dest_mail->expunged &&
+		(isuser->cur_cmd == IMAP_SIEVE_CMD_COPY ||
+			isuser->cur_cmd == IMAP_SIEVE_CMD_MOVE)) {
+		imap_sieve_mailbox_debug(t->box, "%s event",
+			(isuser->cur_cmd == IMAP_SIEVE_CMD_COPY ?
+				"COPY" : "MOVE"));
+		imap_sieve_add_mailbox_copy_event(t, ctx->dest_mail, mail);
+	}
+
+	return 0;
+}
+
+static int
+imap_sieve_mailbox_save_finish(struct mail_save_context *ctx)
+{
+	struct mailbox_transaction_context *t = ctx->transaction;
+	struct mailbox *box = t->box;
+	struct imap_sieve_mailbox_transaction *ismt = IMAP_SIEVE_CONTEXT(t);
+	union mailbox_module_context *lbox = IMAP_SIEVE_CONTEXT_REQUIRE(box);
+	struct mail_user *user = box->storage->user;
+	struct imap_sieve_user *isuser = IMAP_SIEVE_USER_CONTEXT_REQUIRE(user);
+	struct mail *dest_mail = ctx->copying_via_save ? NULL : ctx->dest_mail;
+
+	if (lbox->super.save_finish(ctx) < 0)
+		return -1;
+
+	if (ismt != NULL && !isuser->sieve_active &&
+		dest_mail != NULL && !dest_mail->expunged &&
+		isuser->cur_cmd == IMAP_SIEVE_CMD_APPEND) {
+
+		imap_sieve_mailbox_debug(t->box, "APPEND event");
+		imap_sieve_add_mailbox_event(t, dest_mail, box, NULL);
+	}
+	return 0;
+}
+
+/*
+ * Mailbox
+ */
+
+static struct mailbox_transaction_context *
+imap_sieve_mailbox_transaction_begin(struct mailbox *box,
+			 enum mailbox_transaction_flags flags,
+			 const char *reason)
+{
+	union mailbox_module_context *lbox = IMAP_SIEVE_CONTEXT_REQUIRE(box);
+	struct mail_user *user = box->storage->user;
+	struct imap_sieve_user *isuser = 	IMAP_SIEVE_USER_CONTEXT(user);
+	struct mailbox_transaction_context *t;
+	struct imap_sieve_mailbox_transaction *ismt;
+	pool_t pool;
+
+	/* commence parent transaction */
+	t = lbox->super.transaction_begin(box, flags, reason);
+
+	if (isuser == NULL || isuser->sieve_active ||
+		isuser->cur_cmd == IMAP_SIEVE_CMD_NONE)
+		return t;
+
+	i_assert(isuser->client != NULL);
+
+	pool = pool_alloconly_create("imap_sieve_mailbox_transaction", 1024);
+	ismt = p_new(pool, struct imap_sieve_mailbox_transaction, 1);
+	ismt->pool = pool;
+	MODULE_CONTEXT_SET(t, imap_sieve_storage_module, ismt);
+
+	return t;
+}
+
+static void
+imap_sieve_mailbox_transaction_free
+(struct imap_sieve_mailbox_transaction *ismt)
+{
+	if (array_is_created(&ismt->events))
+		array_free(&ismt->events);
+	pool_unref(&ismt->pool);
+}
+
+static void
+imap_sieve_mailbox_run_copy_source(
+	struct imap_sieve_mailbox_transaction *ismt,
+	struct imap_sieve_run *isrun,
+	const struct imap_sieve_mailbox_event *mevent,
+	struct mail **src_mail)
+{
+	struct mailbox *src_box = ismt->src_box;
+	int ret;
+
+	if (isrun == NULL)
+		return;
+
+	i_assert(ismt->src_mail_trans->box == src_box);
+
+	if (*src_mail == NULL)
+		*src_mail = mail_alloc(ismt->src_mail_trans, 0, NULL);
+
+	/* Select source message */
+	if (!mail_set_uid(*src_mail, mevent->src_mail_uid)) {
+		imap_sieve_mailbox_warning(src_box,
+			"Failed to find source message for Sieve event "
+			"(UID=%llu)", (unsigned long long)mevent->src_mail_uid);
+		return;
+	}
+
+	imap_sieve_mailbox_debug(src_box,
+		"Running copy_source_after scripts.");
+
+	/* Run scripts for source mail */
+	ret = imap_sieve_run_mail
+		(isrun, *src_mail, NULL);
+	if (ret > 0) {
+		/* Discard */
+		mail_update_flags(*src_mail, MODIFY_ADD, MAIL_DELETED);
+	}
+}
+
+static int
+imap_sieve_mailbox_transaction_run(
+	struct imap_sieve_mailbox_transaction *ismt,
+	struct mailbox *dest_box,
+	struct mail_transaction_commit_changes *changes)
+{
+	static const char *wanted_headers[] = {
+		"From", "To", "Message-ID", "Subject", "Return-Path",
+		NULL
+	};
+	struct mailbox *src_box = ismt->src_box;
+	struct mail_user *user = dest_box->storage->user;
+	struct imap_sieve_user *isuser = IMAP_SIEVE_USER_CONTEXT_REQUIRE(user);
+	const struct imap_sieve_mailbox_event *mevent;
+	struct mailbox_header_lookup_ctx *headers_ctx;
+	struct mailbox_transaction_context *st;
+	struct mailbox *sbox;
+	struct imap_sieve_run *isrun, *isrun_src;
+	struct seq_range_iter siter;
+	const char *cause, *script_name = NULL;
+	bool can_discard;
+	struct mail *mail, *src_mail = NULL;
+	int ret;
+
+	if (ismt == NULL || !array_is_created(&ismt->events)) {
+		/* Nothing to do */
+		return 0;
+	}
+
+	i_assert(isuser->client != NULL);
+
+	/* Get user script for this mailbox */
+	if (isuser->user_script && imap_sieve_mailbox_get_script
+		(dest_box, &script_name) < 0) {
+		return 0; // FIXME: some errors may warrant -1
+	}
+
+	/* Make sure IMAPSIEVE is initialized for this user */
+	if (isuser->isieve == NULL)
+		isuser->isieve = imap_sieve_init(isuser->client);
+
+	can_discard = FALSE;
+	switch (isuser->cur_cmd) {
+	case IMAP_SIEVE_CMD_APPEND:
+		cause = "APPEND";
+		can_discard = TRUE;
+		break;
+	case IMAP_SIEVE_CMD_COPY:
+	case IMAP_SIEVE_CMD_MOVE:
+		cause = "COPY";
+		can_discard = TRUE;
+		break;
+	case IMAP_SIEVE_CMD_STORE:
+	case IMAP_SIEVE_CMD_OTHER:
+		cause = "FLAG";
+		break;
+	default:
+		i_unreached();
+	}
+
+	/* Initialize execution */
+	T_BEGIN {
+		ARRAY_TYPE(imap_sieve_mailbox_rule) mbrules;
+		ARRAY_TYPE(const_string) scripts_before, scripts_after;
+		ARRAY_TYPE(const_string) scripts_copy_source;
+		struct imap_sieve_mailbox_rule *const *rule_idx;
+
+		/* Find matching rules */
+		t_array_init(&mbrules, 16);
+		imap_sieve_mailbox_rules_get
+			(user, dest_box, src_box, cause, &mbrules);
+
+		/* Apply all matched rules */
+		t_array_init(&scripts_before, 8);
+		t_array_init(&scripts_after, 8);
+		t_array_init(&scripts_copy_source, 4);
+		array_foreach(&mbrules, rule_idx) {
+			struct imap_sieve_mailbox_rule *rule = *rule_idx;
+
+			if (rule->before != NULL)
+				array_append(&scripts_before, &rule->before, 1);
+			if (rule->after != NULL)
+				array_append(&scripts_after, &rule->after, 1);
+			if (rule->copy_source_after != NULL)
+				array_append(&scripts_copy_source, &rule->copy_source_after, 1);
+		}
+		(void)array_append_space(&scripts_before);
+		(void)array_append_space(&scripts_after);
+
+		/* Initialize */
+		ret = imap_sieve_run_init
+			(isuser->isieve, dest_box, src_box, cause, script_name,
+				array_idx(&scripts_before, 0),
+				array_idx(&scripts_after, 0), &isrun);
+
+		/* Initialize source script execution */
+		isrun_src = NULL;
+		if (ret > 0 && ismt->src_mail_trans != NULL &&
+			isuser->cur_cmd == IMAP_SIEVE_CMD_COPY &&
+			array_count(&scripts_copy_source) > 0) {
+			const char *no_scripts = NULL;
+
+			(void)array_append_space(&scripts_copy_source);
+			if (imap_sieve_run_init(isuser->isieve,
+					dest_box, src_box, cause, NULL,
+					&no_scripts, array_idx(&scripts_copy_source, 0),
+					&isrun_src) <= 0)
+				isrun_src = NULL;
+		}
+	} T_END;
+
+	if (ret <= 0) {
+		// FIXME: temp fail should be handled properly
+		return 0;
+	}
+
+	/* Get synchronized view on the destination mailbox */
+	sbox = mailbox_alloc(dest_box->list, dest_box->vname, 0);
+	if (mailbox_sync(sbox, 0) < 0) {
+		mailbox_free(&sbox);
+		imap_sieve_run_deinit(&isrun);
+		if (isrun_src != NULL)
+			imap_sieve_run_deinit(&isrun_src);
+		return -1;
+	}
+
+	/* Create transaction for event messages */
+	st = mailbox_transaction_begin(sbox, 0, __func__);
+	headers_ctx = mailbox_header_lookup_init(sbox, wanted_headers);
+	mail = mail_alloc(st, 0, headers_ctx);
+	mailbox_header_lookup_unref(&headers_ctx);
+
+	/* Iterate through all events */
+	seq_range_array_iter_init(&siter, &changes->saved_uids);
+	array_foreach(&ismt->events, mevent) {
+		uint32_t uid;
+
+		/* Determine UID for saved message */
+		if (mevent->dest_mail_uid > 0 ||
+			!seq_range_array_iter_nth(&siter, mevent->save_seq, &uid))
+			uid = mevent->dest_mail_uid;
+
+		/* Select event message */
+		if (!mail_set_uid(mail, uid) || mail->expunged) {
+			/* already gone for some reason */
+			imap_sieve_mailbox_debug(sbox,
+				"Message for Sieve event gone (UID=%llu)",
+				(unsigned long long)uid);
+			continue;
+		}
+
+		/* Run scripts for this mail */
+		ret = imap_sieve_run_mail
+			(isrun, mail, mevent->changed_flags);
+
+		/* Handle the result */
+		if (ret < 0) {
+			/* Sieve error; keep */
+		} else {
+			if (ret > 0 && can_discard) {
+				/* Discard */
+				mail_update_flags(mail, MODIFY_ADD, MAIL_DELETED);
+			}
+
+			imap_sieve_mailbox_run_copy_source
+				(ismt, isrun_src, mevent, &src_mail);
+		}
+	}
+
+	/* Cleanup */
+	mail_free(&mail);
+	ret = mailbox_transaction_commit(&st);
+	if (src_mail != NULL)
+		mail_free(&src_mail);
+	imap_sieve_run_deinit(&isrun);
+	if (isrun_src != NULL)
+		imap_sieve_run_deinit(&isrun_src);
+	mailbox_free(&sbox);
+	return ret;
+}
+
+static int
+imap_sieve_mailbox_transaction_commit(
+	struct mailbox_transaction_context *t,
+	struct mail_transaction_commit_changes *changes_r)
+{
+	struct mailbox *box = t->box;
+	struct mail_user *user = box->storage->user;
+	struct imap_sieve_mailbox_transaction *ismt = IMAP_SIEVE_CONTEXT(t);
+	union mailbox_module_context *lbox = IMAP_SIEVE_CONTEXT_REQUIRE(t->box);
+	struct imap_sieve_user *isuser = IMAP_SIEVE_USER_CONTEXT_REQUIRE(user);
+	int ret = 0;
+
+	if ((lbox->super.transaction_commit(t, changes_r)) < 0)
+		ret = -1;
+	else if (ismt != NULL) {
+		isuser->sieve_active = TRUE;
+		if (imap_sieve_mailbox_transaction_run
+			(ismt, box, changes_r) < 0)
+			ret = -1;
+		isuser->sieve_active = FALSE;
+	}
+
+	if (ismt != NULL)
+		imap_sieve_mailbox_transaction_free(ismt);
+	return ret;
+}
+
+static void
+imap_sieve_mailbox_transaction_rollback(
+	struct mailbox_transaction_context *t)
+{
+	struct imap_sieve_mailbox_transaction *ismt = IMAP_SIEVE_CONTEXT(t);
+	union mailbox_module_context *lbox = IMAP_SIEVE_CONTEXT_REQUIRE(t->box);
+
+	lbox->super.transaction_rollback(t);
+
+	if (ismt != NULL)
+		imap_sieve_mailbox_transaction_free(ismt);
+}
+
+static void imap_sieve_mailbox_allocated(struct mailbox *box)
+{
+	struct mail_user *user = box->storage->user;
+	struct imap_sieve_user *isuser = IMAP_SIEVE_USER_CONTEXT_REQUIRE(user);
+	struct mailbox_vfuncs *v = box->vlast;
+	union mailbox_module_context *lbox;
+
+	if (isuser->client == NULL || isuser->sieve_active ||
+		(box->flags & MAILBOX_FLAG_READONLY) != 0)
+		return;
+
+	lbox = p_new(box->pool, union mailbox_module_context, 1);
+	lbox->super = *v;
+	box->vlast = &lbox->super;
+
+	v->copy = imap_sieve_mailbox_copy;
+	v->save_finish = imap_sieve_mailbox_save_finish;
+	v->transaction_begin = imap_sieve_mailbox_transaction_begin;
+	v->transaction_commit = imap_sieve_mailbox_transaction_commit;
+	v->transaction_rollback = imap_sieve_mailbox_transaction_rollback;
+	MODULE_CONTEXT_SET_SELF(box, imap_sieve_storage_module, lbox);
+}
+
+/*
+ * Mailbox rules
+ */
+
+static unsigned int imap_sieve_mailbox_rule_hash
+(const struct imap_sieve_mailbox_rule *rule)
+{
+	unsigned int hash = str_hash(rule->mailbox);
+
+	if (rule->from != NULL)
+		hash += str_hash(rule->from);
+	return hash;
+}
+
+static int imap_sieve_mailbox_rule_cmp
+(const struct imap_sieve_mailbox_rule *rule1,
+	const struct imap_sieve_mailbox_rule *rule2)
+{
+	int ret;
+
+	if ((ret=strcmp(rule1->mailbox, rule2->mailbox)) != 0)
+		return ret;
+	return null_strcmp(rule1->from, rule2->from);
+}
+
+static bool rule_pattern_has_wildcards(const char *pattern)
+{
+	for (; *pattern != '\0'; pattern++) {
+		if (*pattern == '%' || *pattern == '*')
+			return TRUE;
+	}
+	return FALSE;
+}
+
+static void
+imap_sieve_mailbox_rules_init(struct mail_user *user)
+{
+	struct imap_sieve_user *isuser = IMAP_SIEVE_USER_CONTEXT_REQUIRE(user);
+	string_t *identifier;
+	unsigned int i = 0;
+	size_t prefix_len;
+
+	if (hash_table_is_created(isuser->mbox_rules))
+		return;
+
+	hash_table_create(&isuser->mbox_rules, default_pool, 0,
+		imap_sieve_mailbox_rule_hash, imap_sieve_mailbox_rule_cmp);
+	i_array_init(&isuser->mbox_patterns, 8);
+
+	identifier = t_str_new(256);
+	str_append(identifier, "imapsieve_mailbox");
+	prefix_len = str_len(identifier);
+
+	for (i = 1; ; i++) {
+		struct imap_sieve_mailbox_rule *mbrule;
+		const char *setval;
+		size_t id_len;
+
+		str_truncate(identifier, prefix_len);
+		str_printfa(identifier, "%u", i);
+		id_len = str_len(identifier);
+
+		str_append(identifier, "_name");
+		setval = mail_user_plugin_getenv
+			(user, str_c(identifier));
+		if (setval == NULL || *setval == '\0')
+			break;
+		setval = t_str_trim(setval, "\t ");
+		if (strcasecmp(setval, "INBOX") == 0)
+			setval = t_str_ucase(setval);
+
+		mbrule = p_new(user->pool,
+			struct imap_sieve_mailbox_rule, 1);
+		mbrule->index = i;
+		mbrule->mailbox = p_strdup(user->pool, setval);
+
+		str_truncate(identifier, id_len);
+		str_append(identifier, "_from");
+		setval = mail_user_plugin_getenv(user, str_c(identifier));
+		if (setval != NULL && *setval != '\0') {
+			setval = t_str_trim(setval, "\t ");
+			if (strcasecmp(setval, "INBOX") == 0)
+				setval = t_str_ucase(setval);
+			mbrule->from = p_strdup(user->pool, setval);
+			if (strcmp(mbrule->from, "*") == 0)
+				mbrule->from = NULL;
+		}
+
+		if ((strcmp(mbrule->mailbox, "*") == 0 ||
+				!rule_pattern_has_wildcards(mbrule->mailbox)) &&
+			(mbrule->from == NULL ||
+				!rule_pattern_has_wildcards(mbrule->from)) &&
+			hash_table_lookup(isuser->mbox_rules, mbrule) != NULL) {
+			imap_sieve_warning(user,
+				"Duplicate static mailbox rule [%u] for mailbox `%s' "
+				"(skipped)", i, mbrule->mailbox);
+			continue;
+		}
+
+		str_truncate(identifier, id_len);
+		str_append(identifier, "_causes");
+		setval = mail_user_plugin_getenv(user, str_c(identifier));
+		if (setval != NULL && *setval != '\0') {
+			const char *const *cause;
+
+			mbrule->causes = (const char *const *)
+				p_strsplit_spaces(user->pool, setval, " \t,");
+
+			for (cause = mbrule->causes; *cause != NULL; cause++) {
+				if (!imap_sieve_event_cause_valid(*cause))
+					break;
+			}
+			if (*cause != NULL) {
+				imap_sieve_warning(user,
+					"Static mailbox rule [%u] has invalid event cause `%s' "
+					"(skipped)", i, *cause);
+				continue;
+			}
+		}
+
+		str_truncate(identifier, id_len);
+		str_append(identifier, "_before");
+		setval = mail_user_plugin_getenv(user, str_c(identifier));
+		mbrule->before = p_strdup_empty(user->pool, setval);
+
+		str_truncate(identifier, id_len);
+		str_append(identifier, "_after");
+		setval = mail_user_plugin_getenv(user, str_c(identifier));
+		mbrule->after = p_strdup_empty(user->pool, setval);
+
+		str_truncate(identifier, id_len);
+		str_append(identifier, "_copy_source_after");
+		setval = mail_user_plugin_getenv(user, str_c(identifier));
+		mbrule->copy_source_after = p_strdup_empty(user->pool, setval);
+
+		if (user->mail_debug) {
+			imap_sieve_debug(user, "Static mailbox rule [%u]: "
+				"mailbox=`%s' from=`%s' causes=(%s) => "
+				"before=%s after=%s%s",
+				mbrule->index, mbrule->mailbox,
+				(mbrule->from == NULL ? "*" : mbrule->from),
+				t_strarray_join(mbrule->causes, " "),
+				(mbrule->before == NULL ? "(none)" :
+					t_strconcat("`", mbrule->before, "'", NULL)),
+				(mbrule->after == NULL ? "(none)" :
+					t_strconcat("`", mbrule->after, "'", NULL)),
+				(mbrule->copy_source_after == NULL ? "":
+					t_strconcat(" copy_source_after=`",
+						mbrule->copy_source_after, "'", NULL)));
+		}
+
+		if ((strcmp(mbrule->mailbox, "*") == 0 ||
+				!rule_pattern_has_wildcards(mbrule->mailbox)) &&
+			(mbrule->from == NULL ||
+				!rule_pattern_has_wildcards(mbrule->from))) {
+			hash_table_insert(isuser->mbox_rules, mbrule, mbrule);
+		} else {
+			array_append(&isuser->mbox_patterns, &mbrule, 1);
+		}
+	}
+
+	if (i == 0)
+		imap_sieve_debug(user, "No static mailbox rules");
+}
+
+static bool
+imap_sieve_mailbox_rule_match_cause
+(struct imap_sieve_mailbox_rule *rule, const char *cause)
+{
+	const char *const *cp;
+
+	if (rule->causes == NULL || *rule->causes == NULL)
+		return TRUE;
+
+	for (cp = rule->causes; *cp != NULL; cp++) {
+		if (strcasecmp(cause, *cp) == 0)
+			return TRUE;
+	}
+	return FALSE;
+}
+
+static void
+imap_sieve_mailbox_rules_match_patterns(struct mail_user *user,
+	struct mailbox *dst_box, struct mailbox *src_box,
+	const char *cause,
+	ARRAY_TYPE(imap_sieve_mailbox_rule) *rules)
+{
+	struct imap_sieve_user *isuser = IMAP_SIEVE_USER_CONTEXT_REQUIRE(user);
+	struct imap_sieve_mailbox_rule *const *rule_idx;
+	struct mail_namespace *dst_ns, *src_ns;
+
+	if (array_count(&isuser->mbox_patterns) == 0)
+		return;
+
+	dst_ns = mailbox_get_namespace(dst_box);
+	src_ns = (src_box == NULL ? NULL :
+		mailbox_get_namespace(src_box));
+
+	array_foreach(&isuser->mbox_patterns, rule_idx) {
+		struct imap_sieve_mailbox_rule *rule = *rule_idx;
+		struct imap_match_glob *glob;
+
+		if (src_ns == NULL && rule->from != NULL)
+			continue;
+		if (!imap_sieve_mailbox_rule_match_cause(rule, cause))
+			continue;
+
+		if (strcmp(rule->mailbox, "*") != 0) {
+			glob = imap_match_init(pool_datastack_create(),
+				rule->mailbox, TRUE, mail_namespace_get_sep(dst_ns));
+			if (imap_match(glob, mailbox_get_vname(dst_box))
+				!= IMAP_MATCH_YES)
+				continue;
+		}
+		if (rule->from != NULL) {
+			glob = imap_match_init(pool_datastack_create(),
+				rule->from, TRUE, mail_namespace_get_sep(src_ns));
+			if (imap_match(glob, mailbox_get_vname(src_box))
+				!= IMAP_MATCH_YES)
+				continue;
+		}
+
+		imap_sieve_debug(user,
+			"Matched static mailbox rule [%u]",
+			rule->index);
+		array_append(rules, &rule, 1);
+	}
+}
+
+static void
+imap_sieve_mailbox_rules_match(struct mail_user *user,
+	const char *dst_box, const char *src_box,
+	const char *cause,
+	ARRAY_TYPE(imap_sieve_mailbox_rule) *rules)
+{
+	struct imap_sieve_user *isuser = IMAP_SIEVE_USER_CONTEXT_REQUIRE(user);
+	struct imap_sieve_mailbox_rule lookup_rule;
+	struct imap_sieve_mailbox_rule *rule;
+
+	i_zero(&lookup_rule);
+	lookup_rule.mailbox = dst_box;
+	lookup_rule.from = src_box;
+	rule = hash_table_lookup(isuser->mbox_rules, &lookup_rule);
+
+	if (rule != NULL &&
+		imap_sieve_mailbox_rule_match_cause(rule, cause)) {
+		struct imap_sieve_mailbox_rule *const *rule_idx;
+		unsigned int insert_idx = 0;
+
+		/* Insert sorted by rule index */
+		array_foreach(rules, rule_idx) {
+			if (rule->index < (*rule_idx)->index) {
+				insert_idx = array_foreach_idx(rules, rule_idx);
+				break;
+			}
+		}
+		array_insert(rules, insert_idx, &rule, 1);
+
+		imap_sieve_debug(user,
+			"Matched static mailbox rule [%u]",
+			rule->index);
+	}
+}
+
+static void
+imap_sieve_mailbox_rules_get(struct mail_user *user,
+	struct mailbox *dst_box, struct mailbox *src_box,
+	const char *cause,
+	ARRAY_TYPE(imap_sieve_mailbox_rule) *rules)
+{
+	const char *dst_name, *src_name;
+
+	imap_sieve_mailbox_rules_init(user);
+
+	imap_sieve_mailbox_rules_match_patterns
+		(user, dst_box, src_box, cause, rules);
+
+	dst_name = mailbox_get_vname(dst_box);
+	src_name = (src_box == NULL ? NULL :
+		mailbox_get_vname(src_box));
+
+	imap_sieve_mailbox_rules_match
+		(user, dst_name, src_name, cause, rules);
+	imap_sieve_mailbox_rules_match
+		(user, "*", src_name, cause, rules);
+	if (src_name != NULL) {
+		imap_sieve_mailbox_rules_match
+			(user, dst_name, NULL, cause, rules);
+		imap_sieve_mailbox_rules_match
+			(user, "*", NULL, cause, rules);
+	}
+}
+
+/*
+ * User
+ */
+
+static void imap_sieve_user_deinit(struct mail_user *user)
+{
+	struct imap_sieve_user *isuser = IMAP_SIEVE_USER_CONTEXT_REQUIRE(user);
+
+	if (isuser->isieve != NULL)
+		imap_sieve_deinit(&isuser->isieve);
+
+	if (hash_table_is_created(isuser->mbox_rules))
+		hash_table_destroy(&isuser->mbox_rules);
+	if (array_is_created(&isuser->mbox_patterns))
+		array_free(&isuser->mbox_patterns);
+
+	isuser->module_ctx.super.deinit(user);
+}
+
+static void imap_sieve_user_created(struct mail_user *user)
+{
+	struct imap_sieve_user *isuser;
+	struct mail_user_vfuncs *v = user->vlast;
+
+	isuser = p_new(user->pool, struct imap_sieve_user, 1);
+	isuser->module_ctx.super = *v;
+	user->vlast = &isuser->module_ctx.super;
+	v->deinit = imap_sieve_user_deinit;
+	MODULE_CONTEXT_SET(user, imap_sieve_user_module, isuser);
+}
+
+/*
+ * Hooks
+ */
+
+static struct mail_storage_hooks imap_sieve_mail_storage_hooks = {
+	.mail_user_created = imap_sieve_user_created,
+	.mailbox_allocated = imap_sieve_mailbox_allocated,
+	.mail_allocated = imap_sieve_mail_allocated
+};
+
+/*
+ * Commands
+ */
+
+static void imap_sieve_command_pre(struct client_command_context *cmd)
+{
+	struct client *client = cmd->client;
+	struct mail_user *user = client->user;
+	struct imap_sieve_user *isuser = 	IMAP_SIEVE_USER_CONTEXT(user);
+
+	if (isuser == NULL)
+		return;
+
+	if (strcasecmp(cmd->name, "APPEND") == 0) {
+		isuser->cur_cmd = IMAP_SIEVE_CMD_APPEND;
+	} else 	if (strcasecmp(cmd->name, "COPY") == 0 ||
+		strcasecmp(cmd->name, "UID COPY") == 0) {
+		isuser->cur_cmd = IMAP_SIEVE_CMD_COPY;
+	} else 	if (strcasecmp(cmd->name, "MOVE") == 0 ||
+		strcasecmp(cmd->name, "UID MOVE") == 0) {
+		isuser->cur_cmd = IMAP_SIEVE_CMD_MOVE;
+	} else 	if (strcasecmp(cmd->name, "STORE") == 0 ||
+		strcasecmp(cmd->name, "UID STORE") == 0) {
+		isuser->cur_cmd = IMAP_SIEVE_CMD_STORE;
+	} else {
+		isuser->cur_cmd = IMAP_SIEVE_CMD_OTHER;
+	}
+}
+
+static void imap_sieve_command_post(struct client_command_context *cmd)
+{
+	struct client *client = cmd->client;
+	struct mail_user *user = client->user;
+	struct imap_sieve_user *isuser = 	IMAP_SIEVE_USER_CONTEXT(user);
+
+	if (isuser == NULL)
+		return;
+	isuser->cur_cmd = IMAP_SIEVE_CMD_NONE;
+}
+
+/*
+ * Client
+ */
+
+void imap_sieve_storage_client_created(struct client *client,
+	bool user_script)
+{
+	struct imap_sieve_user *isuser = IMAP_SIEVE_USER_CONTEXT_REQUIRE(client->user);
+
+	isuser->client = client;
+	isuser->user_script = user_script;
+}
+
+/*
+ *
+ */
+
+void imap_sieve_storage_init(struct module *module)
+{
+	command_hook_register(imap_sieve_command_pre, imap_sieve_command_post);
+	mail_storage_hooks_add(module, &imap_sieve_mail_storage_hooks);
+}
+
+void imap_sieve_storage_deinit(void)
+{
+	mail_storage_hooks_remove(&imap_sieve_mail_storage_hooks);
+	command_hook_unregister(imap_sieve_command_pre, imap_sieve_command_post);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/imapsieve/imap-sieve-storage.h
@@ -0,0 +1,10 @@
+#ifndef IMAP_SIEVE_STORAGE_H
+#define IMAP_SIEVE_STORAGE_H
+
+void imap_sieve_storage_init(struct module *module);
+void imap_sieve_storage_deinit(void);
+
+void imap_sieve_storage_client_created(struct client *client,
+	bool user_script);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/imapsieve/imap-sieve.c
@@ -0,0 +1,782 @@
+/* Copyright (c) 2016-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "home-expand.h"
+#include "smtp-address.h"
+#include "smtp-submit.h"
+#include "mail-storage.h"
+#include "mail-user.h"
+#include "mail-duplicate.h"
+#include "iostream-ssl.h"
+#include "imap-client.h"
+#include "imap-settings.h"
+
+#include "sieve.h"
+#include "sieve-script.h"
+#include "sieve-storage.h"
+
+#include "ext-imapsieve-common.h"
+
+#include "imap-sieve.h"
+
+/*
+ * Configuration
+ */
+
+#define DUPLICATE_DB_NAME "lda-dupes"
+#define IMAP_SIEVE_MAX_USER_ERRORS 30
+
+/*
+ * IMAP Sieve
+ */
+
+struct imap_sieve {
+	pool_t pool;
+	struct client *client;
+	const char *home_dir;
+
+	struct sieve_instance *svinst;
+	struct sieve_storage *storage;
+
+	const struct sieve_extension *ext_imapsieve;
+	const struct sieve_extension *ext_vnd_imapsieve;
+
+	struct mail_duplicate_db *dup_db;
+
+	struct sieve_error_handler *master_ehandler;
+};
+
+static const char *
+mail_sieve_get_setting(void *context, const char *identifier)
+{
+	struct imap_sieve *isieve = (struct imap_sieve *)context;
+	struct mail_user *user = isieve->client->user;
+
+	return mail_user_plugin_getenv(user, identifier);
+}
+
+static const struct sieve_callbacks mail_sieve_callbacks = {
+	NULL,
+	mail_sieve_get_setting
+};
+
+
+struct imap_sieve *imap_sieve_init(struct client *client)
+{
+	struct sieve_environment svenv;
+	struct imap_sieve *isieve;
+	struct mail_user *user = client->user;
+	const struct mail_storage_settings *mail_set =
+		mail_user_set_get_storage_set(user);
+	bool debug = user->mail_debug;
+	pool_t pool;
+
+	pool = pool_alloconly_create("imap_sieve", 256);
+	isieve = p_new(pool, struct imap_sieve, 1);
+	isieve->pool = pool;
+	isieve->client = client;
+
+	isieve->dup_db = mail_duplicate_db_init(user, DUPLICATE_DB_NAME);
+
+	i_zero(&svenv);
+	svenv.username = user->username;
+	(void)mail_user_get_home(user, &svenv.home_dir);
+	svenv.hostname = mail_set->hostname;
+	svenv.base_dir = user->set->base_dir;
+	svenv.flags = SIEVE_FLAG_HOME_RELATIVE;
+	svenv.location = SIEVE_ENV_LOCATION_MS;
+	svenv.delivery_phase = SIEVE_DELIVERY_PHASE_POST;
+
+	isieve->home_dir = p_strdup(pool, svenv.home_dir);
+
+	isieve->svinst = sieve_init
+		(&svenv, &mail_sieve_callbacks, isieve, debug);
+
+	isieve->ext_imapsieve = sieve_extension_replace
+		(isieve->svinst, &imapsieve_extension, TRUE);
+	isieve->ext_vnd_imapsieve = sieve_extension_replace
+		(isieve->svinst, &vnd_imapsieve_extension, TRUE);
+
+	isieve->master_ehandler = sieve_master_ehandler_create
+		(isieve->svinst, NULL, 0); // FIXME: prefix?
+	sieve_system_ehandler_set(isieve->master_ehandler);
+	sieve_error_handler_accept_infolog(isieve->master_ehandler, TRUE);
+	sieve_error_handler_accept_debuglog(isieve->master_ehandler, debug);
+
+	return isieve;
+}
+
+void imap_sieve_deinit(struct imap_sieve **_isieve)
+{
+	struct imap_sieve *isieve = *_isieve;
+
+	*_isieve = NULL;
+
+	sieve_error_handler_unref(&isieve->master_ehandler);
+
+	if (isieve->storage != NULL)
+		sieve_storage_unref(&isieve->storage);
+	sieve_extension_unregister(isieve->ext_imapsieve);
+	sieve_extension_unregister(isieve->ext_vnd_imapsieve);
+	sieve_deinit(&isieve->svinst);
+
+	mail_duplicate_db_deinit(&isieve->dup_db);
+
+	pool_unref(&isieve->pool);
+}
+
+static int
+imap_sieve_get_storage(struct imap_sieve *isieve,
+	struct sieve_storage **storage_r)
+{
+	enum sieve_storage_flags storage_flags = 0;
+	struct mail_user *user = isieve->client->user;
+	enum sieve_error error;
+
+	if (isieve->storage != NULL) {
+		*storage_r = isieve->storage;
+		return 1;
+	}
+
+	// FIXME: limit interval between retries
+
+	isieve->storage = sieve_storage_create_main
+		(isieve->svinst, user, storage_flags, &error);
+	if (isieve->storage == NULL) {
+		if (error == SIEVE_ERROR_TEMP_FAILURE)
+			return -1;
+		return 0;
+	}
+	*storage_r = isieve->storage;
+	return 1;
+}
+
+/*
+ * Mail transmission
+ */
+
+static void *imap_sieve_smtp_start
+(const struct sieve_script_env *senv,
+	const struct smtp_address *mail_from)
+{
+	struct imap_sieve_context *isctx =
+		(struct imap_sieve_context *)senv->script_context;
+	struct imap_sieve *isieve = isctx->isieve;
+	struct mail_user *user = isieve->client->user;
+	const struct smtp_submit_settings *smtp_set = isieve->client->smtp_set;
+	struct ssl_iostream_settings ssl_set;
+	
+	i_zero(&ssl_set);
+	mail_user_init_ssl_client_settings(user, &ssl_set);
+
+	return (void *)smtp_submit_init_simple(smtp_set, &ssl_set, mail_from);
+}
+
+static void imap_sieve_smtp_add_rcpt
+(const struct sieve_script_env *senv ATTR_UNUSED, void *handle,
+	const struct smtp_address *rcpt_to)
+{
+	struct smtp_submit *smtp_submit = (struct smtp_submit *) handle;
+
+	smtp_submit_add_rcpt(smtp_submit, rcpt_to);
+}
+
+static struct ostream *imap_sieve_smtp_send
+(const struct sieve_script_env *senv ATTR_UNUSED, void *handle)
+{
+	struct smtp_submit *smtp_submit = (struct smtp_submit *) handle;
+
+	return smtp_submit_send(smtp_submit);
+}
+
+static void imap_sieve_smtp_abort
+(const struct sieve_script_env *senv ATTR_UNUSED, void *handle)
+{
+	struct smtp_submit *smtp_submit = (struct smtp_submit *) handle;
+
+	smtp_submit_deinit(&smtp_submit);
+}
+
+static int imap_sieve_smtp_finish
+(const struct sieve_script_env *senv ATTR_UNUSED, void *handle,
+	const char **error_r)
+{
+	struct smtp_submit *smtp_submit = (struct smtp_submit *) handle;
+	int ret;
+
+	ret = smtp_submit_run(smtp_submit, error_r);
+	smtp_submit_deinit(&smtp_submit);
+	return ret;
+}
+
+/*
+ * Duplicate checking
+ */
+
+static bool imap_sieve_duplicate_check
+(const struct sieve_script_env *senv, const void *id,
+	size_t id_size)
+{
+	struct imap_sieve_context *isctx =
+		(struct imap_sieve_context *)senv->script_context;
+
+	return mail_duplicate_check(isctx->isieve->dup_db,
+		id, id_size, senv->user->username);
+}
+
+static void imap_sieve_duplicate_mark
+(const struct sieve_script_env *senv, const void *id,
+	size_t id_size, time_t time)
+{
+	struct imap_sieve_context *isctx =
+		(struct imap_sieve_context *)senv->script_context;
+
+	mail_duplicate_mark(isctx->isieve->dup_db,
+		id, id_size, senv->user->username, time);
+}
+
+static void imap_sieve_duplicate_flush
+(const struct sieve_script_env *senv)
+{
+	struct imap_sieve_context *isctx =
+		(struct imap_sieve_context *)senv->script_context;
+	mail_duplicate_db_flush(isctx->isieve->dup_db);
+}
+
+/*
+ * IMAP Sieve run
+ */
+
+struct imap_sieve_run_script {
+	struct sieve_script *script;
+	struct sieve_binary *binary;
+
+	/* Compile failed once with this error;
+	   don't try again for this transaction */
+	enum sieve_error compile_error;
+
+	/* Binary corrupt after recompile; don't recompile again */
+	bool binary_corrupt:1;
+};
+
+struct imap_sieve_run {
+	pool_t pool;
+	struct imap_sieve *isieve;
+	struct mailbox *dest_mailbox, *src_mailbox;
+	char *cause;
+
+	struct sieve_error_handler *user_ehandler;
+	char *userlog;
+
+	struct sieve_script *user_script;
+	struct imap_sieve_run_script *scripts;
+	unsigned int scripts_count;
+};
+
+static void
+imap_sieve_run_init_user_log(struct imap_sieve_run *isrun)
+{
+	struct imap_sieve *isieve = isrun->isieve;
+	struct sieve_instance *svinst = isieve->svinst;
+	const char *log_path;
+
+	log_path = sieve_user_get_log_path
+		(svinst, isrun->user_script);
+	if ( log_path != NULL ) {
+		isrun->userlog = p_strdup(isrun->pool, log_path);
+		isrun->user_ehandler = sieve_logfile_ehandler_create
+			(svinst, log_path, IMAP_SIEVE_MAX_USER_ERRORS);
+	}
+}
+
+int imap_sieve_run_init(struct imap_sieve *isieve,
+	struct mailbox *dest_mailbox, struct mailbox *src_mailbox,
+	const char *cause, const char *script_name,
+	const char *const *scripts_before,
+	const char *const *scripts_after,
+	struct imap_sieve_run **isrun_r)
+{
+	struct sieve_instance *svinst = isieve->svinst;
+	struct imap_sieve_run *isrun;
+	struct sieve_storage *storage;
+	struct imap_sieve_run_script *scripts;
+	struct sieve_script *user_script;
+	const char *const *sp;
+	enum sieve_error error;
+	pool_t pool;
+	unsigned int max_len, count;
+	int ret;
+
+	/* Determine how many scripts we may run for this event */
+	max_len = 0;
+	if (scripts_before != NULL)
+		max_len += str_array_length(scripts_before);
+	if (script_name != NULL)
+		max_len++;
+	if (scripts_after != NULL)
+		max_len += str_array_length(scripts_after);
+	if (max_len == 0)
+		return 0;
+
+	/* Get storage for user script */
+	storage = NULL;
+	if (script_name != NULL && *script_name != '\0' &&
+		(ret=imap_sieve_get_storage(isieve, &storage)) < 0)
+		return ret;
+
+	/* Open all scripts */
+	count = 0;
+	pool = pool_alloconly_create("imap_sieve_run", 256);
+	scripts = p_new(pool, struct imap_sieve_run_script, max_len);
+
+	/* Admin scripts before user script */
+	if (scripts_before != NULL) {
+		for (sp = scripts_before; *sp != NULL; sp++) {
+			i_assert(count < max_len);
+			scripts[count].script = sieve_script_create_open
+				(svinst, *sp, NULL, &error);
+			if (scripts[count].script != NULL)
+				count++;
+			else if (error == SIEVE_ERROR_TEMP_FAILURE)
+				return -1;
+		}
+	}
+
+	/* The user script */
+	user_script = NULL;
+	if (storage != NULL) {
+		i_assert(count < max_len);
+		scripts[count].script = sieve_storage_open_script
+			(storage, script_name, &error);
+		if (scripts[count].script != NULL) {
+			user_script = scripts[count].script;
+			count++;
+		} else if (error == SIEVE_ERROR_TEMP_FAILURE) {
+			return -1;
+		}
+	}
+
+	/* Admin scripts after user script */
+	if (scripts_after != NULL) {
+		for (sp = scripts_after; *sp != NULL; sp++) {
+			i_assert(count < max_len);
+			scripts[count].script = sieve_script_create_open
+				(svinst, *sp, NULL, &error);
+			if (scripts[count].script != NULL)
+				count++;
+			else if (error == SIEVE_ERROR_TEMP_FAILURE)
+				return -1;
+		}
+	}
+
+	if (count == 0) {
+		/* None of the scripts could be opened */
+		pool_unref(&pool);
+		return 0;
+	}
+
+	/* Initialize */
+	isrun = p_new(pool, struct imap_sieve_run, 1);
+	isrun->pool = pool;
+	isrun->isieve = isieve;
+	isrun->dest_mailbox = dest_mailbox;
+	isrun->src_mailbox = src_mailbox;
+	isrun->cause = p_strdup(pool, cause);
+	isrun->user_script = user_script;
+	isrun->scripts = scripts;
+	isrun->scripts_count = count;
+
+	imap_sieve_run_init_user_log(isrun);
+
+	*isrun_r = isrun;
+	return 1;
+}
+
+void imap_sieve_run_deinit(struct imap_sieve_run **_isrun)
+{
+	struct imap_sieve_run *isrun = *_isrun;
+	unsigned int i;
+
+	*_isrun = NULL;
+
+	for (i = 0; i < isrun->scripts_count; i++) {
+		if (isrun->scripts[i].binary != NULL)
+			sieve_close(&isrun->scripts[i].binary);
+		if (isrun->scripts[i].script != NULL)
+			sieve_script_unref(&isrun->scripts[i].script);
+	}
+	if (isrun->user_ehandler != NULL)
+		sieve_error_handler_unref(&isrun->user_ehandler);
+
+	pool_unref(&isrun->pool);
+}
+
+static struct sieve_binary *
+imap_sieve_run_open_script(
+	struct imap_sieve_run *isrun,
+	struct sieve_script *script,
+	enum sieve_compile_flags cpflags,
+	bool recompile, enum sieve_error *error_r)
+{
+	struct imap_sieve *isieve = isrun->isieve;
+	struct sieve_instance *svinst = isieve->svinst;
+	struct mail_user *user = isieve->client->user;
+	struct sieve_error_handler *ehandler;
+	struct sieve_binary *sbin;
+	const char *compile_name = "compile";
+	bool debug = user->mail_debug;
+
+	if ( recompile ) {
+		/* Warn */
+		sieve_sys_warning(svinst,
+			"Encountered corrupt binary: re-compiling script %s",
+			sieve_script_location(script));
+		compile_name = "re-compile";
+	} else 	if ( debug ) {
+		sieve_sys_debug(svinst,
+			"Loading script %s", sieve_script_location(script));
+	}
+
+	if ( script == isrun->user_script )
+		ehandler = isrun->user_ehandler;
+	else
+		ehandler = isieve->master_ehandler;
+	sieve_error_handler_reset(ehandler);
+
+	/* Load or compile the sieve script */
+	if ( recompile ) {
+		sbin = sieve_compile_script
+			(script, ehandler, cpflags, error_r);
+	} else {
+		sbin = sieve_open_script
+			(script, ehandler, cpflags, error_r);
+	}
+
+	/* Handle error */
+	if ( sbin == NULL ) {
+		switch ( *error_r ) {
+		/* Script not found */
+		case SIEVE_ERROR_NOT_FOUND:
+			if ( debug ) {
+				sieve_sys_debug(svinst, "Script `%s' is missing for %s",
+					sieve_script_location(script), compile_name);
+			}
+			break;
+		/* Temporary failure */
+		case SIEVE_ERROR_TEMP_FAILURE:
+			sieve_sys_error(svinst,
+				"Failed to open script `%s' for %s (temporary failure)",
+				sieve_script_location(script), compile_name);
+			break;
+		/* Compile failed */
+		case SIEVE_ERROR_NOT_VALID:
+			if (script == isrun->user_script && isrun->userlog != NULL ) {
+				sieve_sys_info(svinst,
+					"Failed to %s script `%s' "
+					"(view user logfile `%s' for more information)",
+					compile_name, sieve_script_location(script),
+					isrun->userlog);
+				break;
+			}
+			sieve_sys_error(svinst,	"Failed to %s script `%s'",
+				compile_name, sieve_script_location(script));
+			break;
+		/* Something else */
+		default:
+			sieve_sys_error(svinst,	"Failed to open script `%s' for %s",
+				sieve_script_location(script), compile_name);
+			break;
+		}
+
+		return NULL;
+	}
+
+	if (!recompile)
+		(void)sieve_save(sbin, FALSE, NULL);
+	return sbin;
+}
+
+static int imap_sieve_handle_exec_status
+(struct imap_sieve_run *isrun,
+	struct sieve_script *script, int status, bool keep,
+	struct sieve_exec_status *estatus)
+	ATTR_NULL(2)
+{
+	struct imap_sieve *isieve = isrun->isieve;
+	struct sieve_instance *svinst = isieve->svinst;
+	const char *userlog_notice = "";
+	sieve_sys_error_func_t error_func, user_error_func;
+	enum mail_error mail_error = MAIL_ERROR_NONE;
+	int ret = -1;
+
+	error_func = user_error_func = sieve_sys_error;
+
+	if ( estatus != NULL && estatus->last_storage != NULL &&
+		estatus->store_failed) {
+		mail_storage_get_last_error(estatus->last_storage, &mail_error);
+
+		/* Don't bother administrator too much with benign errors */
+		if ( mail_error == MAIL_ERROR_NOQUOTA ) {
+			error_func = sieve_sys_info;
+			user_error_func = sieve_sys_info;
+		}
+	}
+
+	if ( script == isrun->user_script && isrun->userlog != NULL ) {
+		userlog_notice = t_strdup_printf
+			(" (user logfile %s may reveal additional details)",
+				isrun->userlog);
+		user_error_func = sieve_sys_info;
+	}
+
+	switch ( status ) {
+	case SIEVE_EXEC_FAILURE:
+		user_error_func(svinst,
+			"Execution of script %s failed%s",
+			sieve_script_location(script), userlog_notice);
+		ret = 0;
+		break;
+	case SIEVE_EXEC_TEMP_FAILURE:
+		error_func(svinst,
+			"Execution of script %s was aborted "
+			"due to temporary failure%s",
+			sieve_script_location(script), userlog_notice);
+		ret = -1;
+		break;
+	case SIEVE_EXEC_BIN_CORRUPT:
+		sieve_sys_error(svinst,
+			"!!BUG!!: Binary compiled from %s is still corrupt; "
+			"bailing out and reverting to default action",
+			sieve_script_location(script));
+		ret = 0;
+		break;
+	case SIEVE_EXEC_KEEP_FAILED:
+		error_func(svinst,
+			"Execution of script %s failed "
+			"with unsuccessful implicit keep%s",
+			sieve_script_location(script), userlog_notice);
+		ret = 0;
+		break;
+	case SIEVE_EXEC_OK:
+		ret = (keep ? 0 : 1);
+		break;
+	}
+
+	return ret;
+}
+
+static int imap_sieve_run_scripts
+(struct imap_sieve_run *isrun,
+	const struct sieve_message_data *msgdata,
+	const struct sieve_script_env *scriptenv)
+{
+	struct imap_sieve *isieve = isrun->isieve;
+	struct sieve_instance *svinst = isieve->svinst;
+	struct mail_user *user = isieve->client->user;
+	struct imap_sieve_run_script *scripts = isrun->scripts;
+	unsigned int count = isrun->scripts_count;
+	struct sieve_multiscript *mscript;
+	struct sieve_error_handler *ehandler;
+	struct sieve_script *last_script = NULL;
+	bool user_script = FALSE, more = TRUE;
+	bool debug = user->mail_debug, keep = TRUE;
+	enum sieve_compile_flags cpflags;
+	enum sieve_execute_flags exflags;
+	enum sieve_error compile_error = SIEVE_ERROR_NONE;
+	unsigned int i;
+	int ret;
+
+	/* Start execution */
+	mscript = sieve_multiscript_start_execute
+		(svinst, msgdata, scriptenv);
+
+	/* Execute scripts */
+	for ( i = 0; i < count && more; i++ ) {
+		struct sieve_script *script = scripts[i].script;
+		struct sieve_binary *sbin = scripts[i].binary;
+
+		cpflags = 0;
+		exflags = SIEVE_EXECUTE_FLAG_NO_ENVELOPE |
+			  SIEVE_EXECUTE_FLAG_SKIP_RESPONSES;
+
+		user_script = ( script == isrun->user_script );
+		last_script = script;
+
+		if ( user_script ) {
+			cpflags |= SIEVE_COMPILE_FLAG_NOGLOBAL;
+			exflags |= SIEVE_EXECUTE_FLAG_NOGLOBAL;
+			ehandler = isrun->user_ehandler;
+		} else {
+			cpflags |= SIEVE_COMPILE_FLAG_NO_ENVELOPE;
+			ehandler = isieve->master_ehandler;
+		}
+
+		/* Open */
+		if (sbin == NULL) {
+			if ( debug ) {
+				sieve_sys_debug(svinst,
+					"Opening script %d of %d from `%s'",
+					i+1, count, sieve_script_location(script));
+			}
+
+			/* Already known to fail */
+			if (scripts[i].compile_error != SIEVE_ERROR_NONE) {
+				compile_error = scripts[i].compile_error;
+				break;
+			}
+
+			/* Try to open/compile binary */
+			scripts[i].binary = sbin = imap_sieve_run_open_script
+				(isrun, script, cpflags, FALSE, &compile_error);
+			if ( sbin == NULL ) {
+				scripts[i].compile_error = compile_error;
+				break;
+			}
+		}
+
+		/* Execute */
+		if ( debug ) {
+			sieve_sys_debug(svinst,
+				"Executing script from `%s'",
+				sieve_get_source(sbin));
+		}
+		more = sieve_multiscript_run(mscript,
+			sbin, ehandler, ehandler, exflags);
+
+		if ( !more ) {
+			if ( !scripts[i].binary_corrupt &&
+				sieve_multiscript_status(mscript)
+					== SIEVE_EXEC_BIN_CORRUPT &&
+				sieve_is_loaded(sbin) ) {
+
+				/* Close corrupt script */
+				sieve_close(&sbin);
+
+				/* Recompile */
+				scripts[i].binary = sbin = imap_sieve_run_open_script
+					(isrun, script, cpflags, FALSE, &compile_error);
+				if ( sbin == NULL ) {
+					scripts[i].compile_error = compile_error;
+					break;
+				}
+
+				/* Execute again */
+				more = sieve_multiscript_run(mscript, sbin,
+					ehandler, ehandler, exflags);
+
+				/* Save new version */
+
+				if ( sieve_multiscript_status(mscript)
+					== SIEVE_EXEC_BIN_CORRUPT )
+					scripts[i].binary_corrupt = TRUE;
+				else if ( more )
+					(void)sieve_save(sbin, FALSE, NULL);
+			}
+		}
+	}
+
+	/* Finish execution */
+	exflags = SIEVE_EXECUTE_FLAG_NO_ENVELOPE |
+		  SIEVE_EXECUTE_FLAG_SKIP_RESPONSES;
+	ehandler = (isrun->user_ehandler != NULL ?
+		isrun->user_ehandler : isieve->master_ehandler);
+	if ( compile_error == SIEVE_ERROR_TEMP_FAILURE ) {
+		ret = sieve_multiscript_tempfail
+			(&mscript, ehandler, exflags);
+	} else {
+		ret = sieve_multiscript_finish
+			(&mscript, ehandler, exflags, &keep);
+	}
+
+	/* Don't log additional messages about compile failure */
+	if ( compile_error != SIEVE_ERROR_NONE &&
+		ret == SIEVE_EXEC_FAILURE ) {
+		sieve_sys_info(svinst,
+			"Aborted script execution sequence "
+			"with successful implicit keep");
+		return 1;
+	}
+
+	return imap_sieve_handle_exec_status
+		(isrun, last_script, ret, keep, scriptenv->exec_status);
+}
+
+int imap_sieve_run_mail
+(struct imap_sieve_run *isrun, struct mail *mail,
+	const char *changed_flags)
+{
+	struct imap_sieve *isieve = isrun->isieve;
+	struct sieve_instance *svinst = isieve->svinst;
+	struct mail_user *user = isieve->client->user;
+	struct sieve_message_data msgdata;
+	struct sieve_script_env scriptenv;
+	struct sieve_exec_status estatus;
+	struct imap_sieve_context context;
+	struct sieve_trace_config trace_config;
+	struct sieve_trace_log *trace_log;
+	const char *error;
+	int ret;
+
+	i_zero(&context);
+	context.event.dest_mailbox = isrun->dest_mailbox;
+	context.event.src_mailbox = isrun->src_mailbox;
+	context.event.cause = isrun->cause;
+	context.event.changed_flags = changed_flags;
+	context.isieve = isieve;
+
+	/* Initialize trace logging */
+
+	trace_log = NULL;
+	if ( sieve_trace_config_get(svinst, &trace_config) >= 0) {
+		const char *tr_label = t_strdup_printf
+			("%s.%s.%u", user->username,
+				mailbox_get_vname(mail->box), mail->uid);
+		if ( sieve_trace_log_open(svinst, tr_label, &trace_log) < 0 )
+			i_zero(&trace_config);
+	}
+
+	T_BEGIN {
+		/* Collect necessary message data */
+
+		i_zero(&msgdata);
+		msgdata.mail = mail;
+		msgdata.auth_user = user->username;
+		(void)mail_get_first_header
+			(msgdata.mail, "Message-ID", &msgdata.id);
+
+		/* Compose script execution environment */
+
+		if (sieve_script_env_init(&scriptenv, user, &error) < 0) {
+			sieve_sys_error(svinst,
+				"Failed to initialize script execution: %s",
+				error);
+			ret = -1;
+		} else {
+			scriptenv.default_mailbox = mailbox_get_vname(mail->box);
+			scriptenv.smtp_start = imap_sieve_smtp_start;
+			scriptenv.smtp_add_rcpt = imap_sieve_smtp_add_rcpt;
+			scriptenv.smtp_send = imap_sieve_smtp_send;
+			scriptenv.smtp_abort = imap_sieve_smtp_abort;
+			scriptenv.smtp_finish = imap_sieve_smtp_finish;
+			scriptenv.duplicate_mark = imap_sieve_duplicate_mark;
+			scriptenv.duplicate_check = imap_sieve_duplicate_check;
+			scriptenv.duplicate_flush = imap_sieve_duplicate_flush;
+			scriptenv.trace_log = trace_log;
+			scriptenv.trace_config = trace_config;
+			scriptenv.script_context = (void *)&context;
+
+			i_zero(&estatus);
+			scriptenv.exec_status = &estatus;
+
+			/* Execute script(s) */
+
+			ret = imap_sieve_run_scripts(isrun, &msgdata, &scriptenv);
+		}
+	} T_END;
+
+	if ( trace_log != NULL )
+		sieve_trace_log_free(&trace_log);
+
+	return ret;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/imapsieve/imap-sieve.h
@@ -0,0 +1,59 @@
+#ifndef IMAP_SIEVE_H
+#define IMAP_SIEVE_H
+
+struct client;
+
+/*
+ * IMAP event
+ */
+
+struct imap_sieve_event {
+	struct mailbox *dest_mailbox, *src_mailbox;
+	const char *cause;
+	const char *changed_flags;
+};
+
+struct imap_sieve_context {
+	struct imap_sieve_event event;
+
+	struct imap_sieve *isieve;
+};
+
+static inline bool
+imap_sieve_event_cause_valid(const char *cause)
+{
+	return (strcasecmp(cause, "APPEND") == 0 ||
+		strcasecmp(cause, "COPY") == 0 ||
+		strcasecmp(cause, "FLAG") == 0);
+}
+
+/*
+ * IMAP Sieve
+ */
+
+struct imap_sieve;
+
+struct imap_sieve *imap_sieve_init(struct client *client);
+void imap_sieve_deinit(struct imap_sieve **_isieve);
+
+/*
+ * IMAP Sieve run
+ */
+
+struct imap_sieve_run;
+
+int imap_sieve_run_init(struct imap_sieve *isieve,
+	struct mailbox *dest_mailbox, struct mailbox *src_mailbox,
+	const char *cause, const char *script_name,
+	const char *const *scripts_before,
+	const char *const *scripts_after,
+	struct imap_sieve_run **isrun_r)
+	ATTR_NULL(4, 5, 6);
+
+int imap_sieve_run_mail
+(struct imap_sieve_run *isrun, struct mail *mail,
+	const char *changed_flags);
+
+void imap_sieve_run_deinit(struct imap_sieve_run **_isrun);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/imapsieve/sieve-imapsieve-plugin.c
@@ -0,0 +1,64 @@
+/* Copyright (c) 2016-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-extensions.h"
+
+#include "ext-imapsieve-common.h"
+
+#include "sieve-imapsieve-plugin.h"
+
+/*
+ * Sieve plugin interface
+ */
+
+struct _plugin_context {
+	const struct sieve_extension *ext_imapsieve;
+	const struct sieve_extension *ext_vnd_imapsieve;
+};
+
+const char *sieve_imapsieve_plugin_version = PIGEONHOLE_ABI_VERSION;
+
+void sieve_imapsieve_plugin_load
+(struct sieve_instance *svinst, void **context)
+{
+	struct _plugin_context *pctx = i_new(struct _plugin_context, 1);
+
+	pctx->ext_imapsieve = sieve_extension_register
+		(svinst, &imapsieve_extension_dummy, TRUE);
+	pctx->ext_vnd_imapsieve = sieve_extension_register
+		(svinst, &vnd_imapsieve_extension_dummy, TRUE);
+
+	if ( svinst->debug ) {
+		sieve_sys_debug(svinst,
+			"Sieve imapsieve plugin for %s version %s loaded",
+			PIGEONHOLE_NAME, PIGEONHOLE_VERSION_FULL);
+	}
+
+	*context = (void *)pctx;
+}
+
+void sieve_imapsieve_plugin_unload
+(struct sieve_instance *svinst ATTR_UNUSED, void *context)
+{
+	struct _plugin_context *pctx = (struct _plugin_context *)context;
+
+	sieve_extension_unregister(pctx->ext_imapsieve);
+	sieve_extension_unregister(pctx->ext_vnd_imapsieve);
+
+	i_free(pctx);}
+
+/*
+ * Module interface
+ */
+
+void sieve_imapsieve_plugin_init(void)
+{
+	/* Nothing */
+}
+
+void sieve_imapsieve_plugin_deinit(void)
+{
+	/* Nothing */
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/imapsieve/sieve-imapsieve-plugin.h
@@ -0,0 +1,20 @@
+#ifndef SIEVE_IMAPSIEVE_PLUGIN_H
+#define SIEVE_IMAPSIEVE_PLUGIN_H
+
+/*
+ * Plugin interface
+ */
+
+void sieve_imapsieve_plugin_load
+	(struct sieve_instance *svinst, void **context);
+void sieve_imapsieve_plugin_unload
+	(struct sieve_instance *svinst, void *context);
+
+/*
+ * Module interface
+ */
+
+void sieve_imapsieve_plugin_init(void);
+void sieve_imapsieve_plugin_deinit(void);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/lda-sieve/Makefile.am
@@ -0,0 +1,21 @@
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib-sieve \
+	$(LIBDOVECOT_INCLUDE) \
+	$(LIBDOVECOT_DICT_INCLUDE) \
+	$(LIBDOVECOT_SMTP_INCLUDE) \
+	$(LIBDOVECOT_LDA_INCLUDE)
+
+lib90_sieve_plugin_la_LDFLAGS = -module -avoid-version
+
+dovecot_module_LTLIBRARIES = lib90_sieve_plugin.la
+
+lib90_sieve_plugin_la_LIBADD = \
+	$(top_builddir)/src/lib-sieve/libdovecot-sieve.la
+
+lib90_sieve_plugin_la_SOURCES = \
+	lda-sieve-log.c \
+	lda-sieve-plugin.c
+
+noinst_HEADERS = \
+	lda-sieve-log.h \
+	lda-sieve-plugin.h
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/lda-sieve/lda-sieve-log.c
@@ -0,0 +1,101 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "var-expand.h"
+#include "lda-settings.h"
+#include "mail-deliver.h"
+
+#include "sieve-error-private.h"
+
+#include "lda-sieve-log.h"
+
+struct lda_sieve_log_ehandler {
+	struct sieve_error_handler handler;
+
+	struct mail_deliver_context *mdctx;
+};
+
+static const char *ATTR_FORMAT(2, 0) lda_sieve_log_expand_message
+(struct sieve_error_handler *_ehandler,
+	const char *fmt, va_list args)
+{
+	struct lda_sieve_log_ehandler *ehandler =
+		(struct lda_sieve_log_ehandler *) _ehandler;
+	struct mail_deliver_context *mdctx = ehandler->mdctx;
+	const struct var_expand_table *table;
+	string_t *str;
+	const char *error;
+
+	table = mail_deliver_ctx_get_log_var_expand_table
+		(mdctx, t_strdup_vprintf(fmt, args));
+
+	str = t_str_new(256);
+	if (var_expand(str, mdctx->set->deliver_log_format, table, &error) <= 0) {
+		i_error("Failed to expand deliver_log_format=%s: %s",
+			mdctx->set->deliver_log_format, error);
+	}
+	return str_c(str);
+}
+
+static void ATTR_FORMAT(4, 0) lda_sieve_log_verror
+(struct sieve_error_handler *ehandler, unsigned int flags,
+	const char *location, const char *fmt, va_list args)
+{
+	sieve_direct_error(ehandler->svinst,
+		ehandler->parent, flags, location, "%s",
+		lda_sieve_log_expand_message(ehandler, fmt, args));
+}
+
+static void ATTR_FORMAT(4, 0) lda_sieve_log_vwarning
+(struct sieve_error_handler *ehandler, unsigned int flags,
+	const char *location, const char *fmt, va_list args)
+{
+	sieve_direct_warning(ehandler->svinst,
+		ehandler->parent, flags, location, "%s",
+		lda_sieve_log_expand_message(ehandler, fmt, args));
+}
+
+static void ATTR_FORMAT(4, 0) lda_sieve_log_vinfo
+(struct sieve_error_handler *ehandler, unsigned int flags,
+	const char *location, const char *fmt, va_list args)
+{
+	sieve_direct_info(ehandler->svinst,
+		ehandler->parent, flags, location, "%s",
+		lda_sieve_log_expand_message(ehandler, fmt, args));
+}
+
+static void ATTR_FORMAT(4, 0) lda_sieve_log_vdebug
+(struct sieve_error_handler *ehandler, unsigned int flags,
+	const char *location, const char *fmt, va_list args)
+{
+	sieve_direct_debug(ehandler->svinst,
+		ehandler->parent, flags, location, "%s",
+		lda_sieve_log_expand_message(ehandler, fmt, args));
+}
+
+struct sieve_error_handler *lda_sieve_log_ehandler_create
+(struct sieve_error_handler *parent,
+	struct mail_deliver_context *mdctx)
+{
+	struct lda_sieve_log_ehandler *ehandler;
+	pool_t pool;
+
+	if ( parent == NULL )
+		return NULL;
+
+	pool = pool_alloconly_create("lda_sieve_log_ehandler", 2048);
+	ehandler = p_new(pool, struct lda_sieve_log_ehandler, 1);
+	sieve_error_handler_init_from_parent(&ehandler->handler, pool, parent);
+
+	ehandler->mdctx = mdctx;
+
+	ehandler->handler.verror = lda_sieve_log_verror;
+	ehandler->handler.vwarning = lda_sieve_log_vwarning;
+	ehandler->handler.vinfo = lda_sieve_log_vinfo;
+	ehandler->handler.vdebug = lda_sieve_log_vdebug;
+
+	return &(ehandler->handler);
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/lda-sieve/lda-sieve-log.h
@@ -0,0 +1,8 @@
+#ifndef LDA_SIEVE_LOG_H
+#define LDA_SIEVE_LOG_H
+
+struct sieve_error_handler *lda_sieve_log_ehandler_create
+	(struct sieve_error_handler *parent,
+		struct mail_deliver_context *mdctx);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/lda-sieve/lda-sieve-plugin.c
@@ -0,0 +1,988 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "home-expand.h"
+#include "eacces-error.h"
+#include "smtp-address.h"
+#include "smtp-submit.h"
+#include "mail-storage.h"
+#include "mail-deliver.h"
+#include "mail-user.h"
+#include "mail-duplicate.h"
+#include "smtp-submit.h"
+#include "mail-send.h"
+#include "iostream-ssl.h"
+#include "lda-settings.h"
+
+#include "sieve.h"
+#include "sieve-script.h"
+#include "sieve-storage.h"
+
+#include "lda-sieve-log.h"
+#include "lda-sieve-plugin.h"
+
+#include <sys/stat.h>
+#include <dirent.h>
+
+/*
+ * Configuration
+ */
+
+#define LDA_SIEVE_DEFAULT_LOCATION "~/.dovecot.sieve"
+
+#define LDA_SIEVE_MAX_USER_ERRORS 30
+
+/*
+ * Global variables
+ */
+
+static deliver_mail_func_t *next_deliver_mail;
+
+/*
+ * Settings handling
+ */
+
+static const char *lda_sieve_get_setting
+(void *context, const char *identifier)
+{
+	struct mail_deliver_context *mdctx = (struct mail_deliver_context *)context;
+	const char *value = NULL;
+
+	if ( mdctx == NULL )
+		return NULL;
+
+	if ( mdctx->rcpt_user == NULL ||
+		(value=mail_user_plugin_getenv(mdctx->rcpt_user, identifier)) == NULL ) {
+		if ( strcmp(identifier, "recipient_delimiter") == 0 )
+			value = mdctx->set->recipient_delimiter;
+	}
+
+	return value;
+}
+
+static const struct sieve_callbacks lda_sieve_callbacks = {
+	NULL,
+	lda_sieve_get_setting
+};
+
+/*
+ * Mail transmission
+ */
+
+static void *lda_sieve_smtp_start
+(const struct sieve_script_env *senv,
+	const struct smtp_address *mail_from)
+{
+	struct mail_deliver_context *dctx =
+		(struct mail_deliver_context *) senv->script_context;
+	struct mail_user *user = dctx->rcpt_user;
+	struct ssl_iostream_settings ssl_set;
+	
+	i_zero(&ssl_set);
+	mail_user_init_ssl_client_settings(user, &ssl_set);
+
+	return (void *)smtp_submit_init_simple(dctx->smtp_set,
+		&ssl_set, mail_from);
+}
+
+static void lda_sieve_smtp_add_rcpt
+(const struct sieve_script_env *senv ATTR_UNUSED, void *handle,
+	const struct smtp_address *rcpt_to)
+{
+	struct smtp_submit *smtp_submit = (struct smtp_submit *) handle;
+
+	smtp_submit_add_rcpt(smtp_submit, rcpt_to);
+}
+
+static struct ostream *lda_sieve_smtp_send
+(const struct sieve_script_env *senv ATTR_UNUSED, void *handle)
+{
+	struct smtp_submit *smtp_submit = (struct smtp_submit *) handle;
+
+	return smtp_submit_send(smtp_submit);
+}
+
+static void lda_sieve_smtp_abort
+(const struct sieve_script_env *senv ATTR_UNUSED, void *handle)
+{
+	struct smtp_submit *smtp_submit = (struct smtp_submit *) handle;
+
+	smtp_submit_deinit(&smtp_submit);
+}
+
+static int lda_sieve_smtp_finish
+(const struct sieve_script_env *senv ATTR_UNUSED, void *handle,
+	const char **error_r)
+{
+	struct smtp_submit *smtp_submit = (struct smtp_submit *) handle;
+	int ret;
+
+	ret = smtp_submit_run(smtp_submit, error_r);
+	smtp_submit_deinit(&smtp_submit);
+	return ret;
+}
+
+static int lda_sieve_reject_mail
+(const struct sieve_script_env *senv,
+	const struct smtp_address *recipient, const char *reason)
+{
+	struct mail_deliver_context *dctx =
+		(struct mail_deliver_context *) senv->script_context;
+
+	return mail_send_rejection(dctx, recipient, reason);
+}
+
+/*
+ * Duplicate checking
+ */
+
+static bool lda_sieve_duplicate_check
+(const struct sieve_script_env *senv, const void *id, size_t id_size)
+{
+	struct mail_deliver_context *dctx =
+		(struct mail_deliver_context *) senv->script_context;
+
+	return mail_duplicate_check(dctx->dup_db,
+		id, id_size, senv->user->username);
+}
+
+static void lda_sieve_duplicate_mark
+(const struct sieve_script_env *senv, const void *id, size_t id_size,
+	time_t time)
+{
+	struct mail_deliver_context *dctx =
+		(struct mail_deliver_context *) senv->script_context;
+
+	mail_duplicate_mark(dctx->dup_db,
+		id, id_size, senv->user->username, time);
+}
+
+static void lda_sieve_duplicate_flush
+(const struct sieve_script_env *senv)
+{
+	struct mail_deliver_context *dctx =
+		(struct mail_deliver_context *) senv->script_context;
+	mail_duplicate_db_flush(dctx->dup_db);
+}
+
+/*
+ * Plugin implementation
+ */
+
+struct lda_sieve_run_context {
+	struct sieve_instance *svinst;
+
+	struct mail_deliver_context *mdctx;
+	const char *home_dir;
+
+	struct sieve_script **scripts;
+	unsigned int script_count;
+
+	struct sieve_script *user_script;
+	struct sieve_script *main_script;
+	struct sieve_script *discard_script;
+
+	const struct sieve_message_data *msgdata;
+	const struct sieve_script_env *scriptenv;
+
+	struct sieve_error_handler *user_ehandler;
+	struct sieve_error_handler *master_ehandler;
+	struct sieve_error_handler *action_ehandler;
+	const char *userlog;
+};
+
+static int lda_sieve_get_personal_storage
+(struct sieve_instance *svinst, struct mail_user *user,
+	struct sieve_storage **storage_r, enum sieve_error *error_r)
+{
+	*storage_r = sieve_storage_create_main(svinst, user, 0, error_r);
+	if (*storage_r == NULL) {
+		switch (*error_r) {
+		case SIEVE_ERROR_NOT_POSSIBLE:
+		case SIEVE_ERROR_NOT_FOUND:
+			break;
+		case SIEVE_ERROR_TEMP_FAILURE:
+			sieve_sys_error(svinst,
+				"Failed to access user's personal storage "
+				"(temporary failure)");
+			return -1;
+		default:
+			sieve_sys_error(svinst,
+				"Failed to access user's personal storage");
+			break;
+		}
+		return 0;
+	}
+	return 1;
+}
+
+static int lda_sieve_multiscript_get_scripts
+(struct sieve_instance *svinst, const char *label, const char *location,
+	ARRAY_TYPE(sieve_script) *scripts, enum sieve_error *error_r)
+{
+	struct sieve_script_sequence *seq;
+	struct sieve_script *script;
+	bool finished = FALSE;
+	int ret = 1;
+
+	seq = sieve_script_sequence_create(svinst, location, error_r);
+	if ( seq == NULL )
+		return ( *error_r == SIEVE_ERROR_NOT_FOUND ? 0 : -1 );
+
+	while ( ret > 0 && !finished ) {
+		script = sieve_script_sequence_next(seq, error_r);
+		if ( script == NULL ) {
+			switch ( *error_r ) {
+			case SIEVE_ERROR_NONE:
+				finished = TRUE;
+				break;
+			case SIEVE_ERROR_TEMP_FAILURE:
+				sieve_sys_error(svinst,
+					"Failed to access %s script from `%s' (temporary failure)",
+					label, location);
+				ret = -1;
+			default:
+				break;
+			}
+			continue;
+		}
+
+		array_append(scripts, &script, 1);
+	}
+
+	sieve_script_sequence_free(&seq);
+	return ret;
+}
+
+static void lda_sieve_binary_save
+(struct lda_sieve_run_context *srctx, struct sieve_binary *sbin,
+	struct sieve_script *script)
+{
+	enum sieve_error error;
+
+	/* Save binary when compiled */
+	if ( sieve_save(sbin, FALSE, &error) < 0 &&
+		error == SIEVE_ERROR_NO_PERMISSION && script != srctx->user_script ) {
+
+		/* Cannot save binary for global script */
+		sieve_sys_error(srctx->svinst,
+			"The LDA Sieve plugin does not have permission "
+			"to save global Sieve script binaries; "
+			"global Sieve scripts like `%s' need to be "
+			"pre-compiled using the sievec tool",
+			sieve_script_location(script));
+	}
+}
+
+static struct sieve_binary *lda_sieve_open
+(struct lda_sieve_run_context *srctx, struct sieve_script *script,
+	enum sieve_compile_flags cpflags, bool recompile, enum sieve_error *error_r)
+{
+	struct sieve_instance *svinst = srctx->svinst;
+	struct sieve_error_handler *ehandler;
+	struct sieve_binary *sbin;
+	bool debug = srctx->mdctx->rcpt_user->mail_debug;
+	const char *compile_name = "compile";
+
+	if ( recompile ) {
+		/* Warn */
+		sieve_sys_warning(svinst,
+			"Encountered corrupt binary: re-compiling script %s",
+			sieve_script_location(script));
+		compile_name = "re-compile";
+	} else 	if ( debug ) {
+		sieve_sys_debug(svinst,
+			"Loading script %s", sieve_script_location(script));
+	}
+
+	if ( script == srctx->user_script )
+		ehandler = srctx->user_ehandler;
+	else
+		ehandler = srctx->master_ehandler;
+
+	sieve_error_handler_reset(ehandler);
+
+	if ( recompile )
+		sbin = sieve_compile_script(script, ehandler,	cpflags, error_r);
+	else 
+		sbin = sieve_open_script(script, ehandler, cpflags, error_r);
+
+	/* Load or compile the sieve script */
+	if ( sbin == NULL ) {
+		switch ( *error_r ) {
+		/* Script not found */
+		case SIEVE_ERROR_NOT_FOUND:
+			if ( debug ) {
+				sieve_sys_debug(svinst, "Script `%s' is missing for %s",
+					sieve_script_location(script), compile_name);
+			}
+			break;
+		/* Temporary failure */
+		case SIEVE_ERROR_TEMP_FAILURE:
+			sieve_sys_error(svinst,
+				"Failed to open script `%s' for %s (temporary failure)",
+				sieve_script_location(script), compile_name);
+			break;
+		/* Compile failed */
+		case SIEVE_ERROR_NOT_VALID:
+			if (script == srctx->user_script && srctx->userlog != NULL ) {
+				sieve_sys_info(svinst,
+					"Failed to %s script `%s' "
+					"(view user logfile `%s' for more information)",
+					compile_name, sieve_script_location(script), srctx->userlog);
+				break;
+			}
+			sieve_sys_error(svinst,	"Failed to %s script `%s'",
+				compile_name, sieve_script_location(script));
+			break;
+		/* Something else */
+		default:
+			sieve_sys_error(svinst,	"Failed to open script `%s' for %s",
+				sieve_script_location(script), compile_name);
+			break;
+		}
+
+		return NULL;
+	}
+
+	if (!recompile)
+		lda_sieve_binary_save(srctx, sbin, script);
+	return sbin;
+}
+
+static int lda_sieve_handle_exec_status
+(struct lda_sieve_run_context *srctx, struct sieve_script *script, int status)
+{
+	struct sieve_instance *svinst = srctx->svinst;
+	struct mail_deliver_context *mdctx = srctx->mdctx;
+	struct sieve_exec_status *estatus = srctx->scriptenv->exec_status;
+	const char *userlog_notice = "";
+	sieve_sys_error_func_t error_func, user_error_func; 
+	enum mail_error mail_error = MAIL_ERROR_NONE;
+	int ret;
+
+	error_func = user_error_func = sieve_sys_error;
+
+	if ( estatus != NULL && estatus->last_storage != NULL &&
+		estatus->store_failed) {
+		mail_storage_get_last_error(estatus->last_storage, &mail_error);
+
+		/* Don't bother administrator too much with benign errors */
+		if ( mail_error == MAIL_ERROR_NOQUOTA ) {
+			error_func = sieve_sys_info;
+			user_error_func = sieve_sys_info;
+		}
+	}
+
+	if ( script == srctx->user_script && srctx->userlog != NULL ) {
+		userlog_notice = t_strdup_printf
+			(" (user logfile %s may reveal additional details)", srctx->userlog);
+		user_error_func = sieve_sys_info;
+	}
+
+	switch ( status ) {
+	case SIEVE_EXEC_FAILURE:
+		user_error_func(svinst,
+			"Execution of script %s failed, but implicit keep was successful%s",
+			sieve_script_location(script), userlog_notice);
+		ret = 1;
+		break;
+	case SIEVE_EXEC_TEMP_FAILURE:
+		error_func(svinst,
+			"Execution of script %s was aborted due to temporary failure%s",
+			sieve_script_location(script), userlog_notice);
+		if ( mail_error != MAIL_ERROR_TEMP && mdctx->tempfail_error == NULL ) {
+			mdctx->tempfail_error =
+				"Execution of Sieve filters was aborted due to temporary failure";
+		}
+		ret = -1;
+		break;
+	case SIEVE_EXEC_BIN_CORRUPT:
+		sieve_sys_error(svinst,
+			"!!BUG!!: Binary compiled from %s is still corrupt; "
+			"bailing out and reverting to default delivery",
+			sieve_script_location(script));
+		ret = -1;
+		break;
+	case SIEVE_EXEC_KEEP_FAILED:
+		error_func(svinst,
+			"Execution of script %s failed with unsuccessful implicit keep%s",
+			sieve_script_location(script), userlog_notice);
+		ret = -1;
+		break;
+	default:
+		ret = status > 0 ? 1 : -1;
+		break;
+	}
+
+	return ret;
+}
+
+static bool
+lda_sieve_execute_script(struct lda_sieve_run_context *srctx,
+	struct sieve_multiscript *mscript,
+	struct sieve_script *script,
+	unsigned int index, bool discard_script,
+	enum sieve_error *error_r)
+{
+	struct sieve_instance *svinst = srctx->svinst;
+	struct mail_deliver_context *mdctx = srctx->mdctx;
+	struct sieve_error_handler *exec_ehandler, *action_ehandler;
+	struct sieve_binary *sbin = NULL;
+	enum sieve_compile_flags cpflags = 0;
+	enum sieve_execute_flags exflags = 0;
+	bool debug = srctx->mdctx->rcpt_user->mail_debug;
+	bool user_script, more;
+
+	*error_r = SIEVE_ERROR_NONE;
+
+	user_script = ( script == srctx->user_script );
+
+	if ( user_script ) {
+		cpflags |= SIEVE_COMPILE_FLAG_NOGLOBAL;
+		exflags |= SIEVE_EXECUTE_FLAG_NOGLOBAL;
+		exec_ehandler = srctx->user_ehandler;
+	} else {
+		exec_ehandler = srctx->master_ehandler;
+	}
+
+	/* Open */
+
+	if ( debug ) {
+		if ( !discard_script ) {
+			sieve_sys_debug(svinst,
+				"Opening script %d of %d from `%s'",
+				index, srctx->script_count,
+				sieve_script_location(script));
+		} else {
+			sieve_sys_debug(svinst,
+				"Opening discard script from `%s'",
+				sieve_script_location(script));
+		}
+	}
+
+	sbin = lda_sieve_open(srctx, script, cpflags, FALSE, error_r);
+	if ( sbin == NULL )
+		return FALSE;
+
+	/* Execute */
+
+	if ( debug ) {
+		sieve_sys_debug(svinst,
+			"Executing script from `%s'",
+			sieve_get_source(sbin));
+	}
+
+	action_ehandler = lda_sieve_log_ehandler_create
+		(exec_ehandler, mdctx);
+	if ( !discard_script ) {
+		more = sieve_multiscript_run(mscript, sbin,
+			exec_ehandler, action_ehandler, exflags);
+	} else {
+		sieve_multiscript_run_discard(mscript, sbin,
+			exec_ehandler, action_ehandler, exflags);
+		more = FALSE;
+	}
+	sieve_error_handler_unref(&action_ehandler);
+
+	if ( !more ) {
+		if ( sieve_multiscript_status(mscript) == SIEVE_EXEC_BIN_CORRUPT &&
+			sieve_is_loaded(sbin) ) {
+			/* Close corrupt script */
+
+			sieve_close(&sbin);
+
+			/* Recompile */
+
+			sbin = lda_sieve_open(srctx, script, cpflags, TRUE, error_r);
+			if ( sbin == NULL )
+				return FALSE;
+
+			/* Execute again */
+
+			action_ehandler = lda_sieve_log_ehandler_create
+				(exec_ehandler, mdctx);
+			if ( !discard_script ) {
+				more = sieve_multiscript_run(mscript, sbin,
+					exec_ehandler, action_ehandler, exflags);
+			} else {
+				sieve_multiscript_run_discard(mscript, sbin,
+					exec_ehandler, action_ehandler, exflags);
+			}
+			sieve_error_handler_unref(&action_ehandler);
+
+			/* Save new version */
+
+			if ( sieve_multiscript_status(mscript) != SIEVE_EXEC_BIN_CORRUPT )
+				lda_sieve_binary_save(srctx, sbin, script);
+		}
+	}
+
+	sieve_close(&sbin);
+
+	return more;
+}
+
+static int lda_sieve_execute_scripts
+(struct lda_sieve_run_context *srctx)
+{
+	struct sieve_instance *svinst = srctx->svinst;
+	struct mail_deliver_context *mdctx = srctx->mdctx;
+	struct sieve_multiscript *mscript;
+	struct sieve_error_handler *exec_ehandler, *action_ehandler;
+	struct sieve_script *script, *last_script = NULL;
+	bool discard_script;
+	enum sieve_error error;
+	unsigned int i;
+	int ret;
+
+	i_assert( srctx->script_count > 0 );
+
+	/* Start execution */
+
+	mscript = sieve_multiscript_start_execute
+		(svinst, srctx->msgdata, srctx->scriptenv);
+
+	/* Execute scripts */
+
+	i = 0;
+	discard_script = FALSE;
+	error = SIEVE_ERROR_NONE;
+	for (;;) {
+		bool more;
+
+		if ( !discard_script ) {
+			/* normal script sequence */
+			i_assert( i < srctx->script_count );
+			script = srctx->scripts[i];
+			i++;
+		} else {
+			/* discard script */
+			script = srctx->discard_script;
+		}
+
+		i_assert( script != NULL );
+		last_script = script;
+
+		more = lda_sieve_execute_script(srctx, mscript,
+			script, i, discard_script, &error);
+		if ( error == SIEVE_ERROR_NOT_FOUND ) {
+			/* skip scripts which finally turn out not to exist */
+			more = TRUE;
+		}
+
+		if ( discard_script ) {
+			/* Executed discard script, which is always final */
+			break;
+		} else if ( more ) {
+			/* The "keep" action is applied; execute next script */
+			i_assert( i <= srctx->script_count );
+			if ( i == srctx->script_count ) {
+				/* End of normal script sequence */
+				break;
+			}
+		} else if ( error != SIEVE_ERROR_NONE ) {
+			break;
+		} else if ( sieve_multiscript_will_discard(mscript) &&
+			srctx->discard_script != NULL ) {
+			/* Mail is set to be discarded, but we have a discard script. */
+			discard_script = TRUE;
+		} else {
+			break;
+		}
+	}
+
+	/* Finish execution */
+	exec_ehandler = (srctx->user_ehandler != NULL ?
+		srctx->user_ehandler : srctx->master_ehandler);
+	action_ehandler = lda_sieve_log_ehandler_create
+		(exec_ehandler, mdctx);
+	if ( error == SIEVE_ERROR_TEMP_FAILURE ) {
+		ret = sieve_multiscript_tempfail
+			(&mscript, action_ehandler, 0);
+	} else {
+		ret = sieve_multiscript_finish
+			(&mscript, action_ehandler, 0, NULL);
+	}
+	sieve_error_handler_unref(&action_ehandler);
+
+	/* Don't log additional messages about compile failure */
+	if ( error != SIEVE_ERROR_NONE && ret == SIEVE_EXEC_FAILURE ) {
+		sieve_sys_info(svinst,
+			"Aborted script execution sequence with successful implicit keep");
+		return 1;
+	}
+
+	return lda_sieve_handle_exec_status(srctx, last_script, ret);
+}
+
+static int lda_sieve_find_scripts(struct lda_sieve_run_context *srctx)
+{
+	struct mail_deliver_context *mdctx = srctx->mdctx;
+	struct sieve_instance *svinst = srctx->svinst;
+	struct sieve_storage *main_storage;
+	const char *sieve_before, *sieve_after, *sieve_discard;
+	const char *setting_name;
+	enum sieve_error error;
+	ARRAY_TYPE(sieve_script) script_sequence;
+	struct sieve_script *const *scripts;
+	bool debug = mdctx->rcpt_user->mail_debug;
+	unsigned int after_index, count, i;
+	int ret = 1;
+
+	/* Find the personal script to execute */
+
+	ret = lda_sieve_get_personal_storage
+		(svinst, mdctx->rcpt_user, &main_storage, &error);
+	if ( ret == 0 && error == SIEVE_ERROR_NOT_POSSIBLE )
+		return 0;
+	if ( ret > 0 ) {
+		srctx->main_script =
+			sieve_storage_active_script_open(main_storage, &error);
+
+		if ( srctx->main_script == NULL ) {
+			switch ( error ) {
+			case SIEVE_ERROR_NOT_FOUND:
+				sieve_sys_debug(svinst,
+					"User has no active script in storage `%s'",
+					sieve_storage_location(main_storage));
+				break;
+			case SIEVE_ERROR_TEMP_FAILURE:
+				sieve_sys_error(svinst,
+					"Failed to access active Sieve script in user storage `%s' "
+					"(temporary failure)",
+					sieve_storage_location(main_storage));
+				ret = -1;
+				break;
+			default:
+				sieve_sys_error(svinst,
+					"Failed to access active Sieve script in user storage `%s'",
+					sieve_storage_location(main_storage));
+				break;
+			}
+		} else if ( !sieve_script_is_default(srctx->main_script) ) {
+			srctx->user_script = srctx->main_script;
+		}
+		sieve_storage_unref(&main_storage);
+	}
+
+	if ( debug && ret >= 0 && srctx->main_script == NULL ) {
+		sieve_sys_debug(svinst,
+			"User has no personal script");
+	}
+
+	/* Compose script array */
+
+	t_array_init(&script_sequence, 16);
+	
+	/* before */
+	if ( ret >= 0 ) {
+		i = 2;
+		setting_name = "sieve_before";
+		sieve_before = mail_user_plugin_getenv(mdctx->rcpt_user, setting_name);
+		while ( ret >= 0 && sieve_before != NULL && *sieve_before != '\0' ) {
+			ret = lda_sieve_multiscript_get_scripts(svinst, setting_name,
+				sieve_before, &script_sequence, &error);
+			if ( ret < 0 && error == SIEVE_ERROR_TEMP_FAILURE ) {
+				ret = -1;
+				break;
+			} else if (ret == 0 && debug ) {
+				sieve_sys_debug(svinst, "Location for %s not found: %s",
+					setting_name, sieve_before);
+			}
+			ret = 0;
+			setting_name = t_strdup_printf("sieve_before%u", i++);
+			sieve_before = mail_user_plugin_getenv(mdctx->rcpt_user, setting_name);
+		}
+
+		if ( ret >= 0 && debug ) {
+			scripts = array_get(&script_sequence, &count);
+			for ( i = 0; i < count; i ++ ) {
+				sieve_sys_debug(svinst,
+					"Executed before user's personal Sieve script(%d): %s",
+					i+1, sieve_script_location(scripts[i]));
+			}
+		}
+	}
+
+	/* main */
+	if ( srctx->main_script != NULL ) {
+		array_append(&script_sequence, &srctx->main_script, 1);
+
+		if ( ret >= 0 && debug ) {
+			sieve_sys_debug(svinst,
+				"Using the following location for user's Sieve script: %s",
+				sieve_script_location(srctx->main_script));
+		}
+	}
+
+	after_index = array_count(&script_sequence);
+
+	/* after */
+	if ( ret >= 0 ) {
+		i = 2;
+		setting_name = "sieve_after";
+		sieve_after = mail_user_plugin_getenv(mdctx->rcpt_user, setting_name);
+		while ( sieve_after != NULL && *sieve_after != '\0' ) {
+			ret = lda_sieve_multiscript_get_scripts(svinst, setting_name,
+				sieve_after, &script_sequence, &error);
+			if ( ret < 0 && error == SIEVE_ERROR_TEMP_FAILURE ) {
+				ret = -1;
+				break;
+			} else if (ret == 0 && debug ) {
+				sieve_sys_debug(svinst, "Location for %s not found: %s",
+					setting_name, sieve_after);
+			}
+			ret = 0;
+			setting_name = t_strdup_printf("sieve_after%u", i++);
+			sieve_after = mail_user_plugin_getenv(mdctx->rcpt_user, setting_name);
+		}
+
+		if ( ret >= 0 && debug ) {
+			scripts = array_get(&script_sequence, &count);
+			for ( i = after_index; i < count; i ++ ) {
+				sieve_sys_debug(svinst, "executed after user's Sieve script(%d): %s",
+					i+1, sieve_script_location(scripts[i]));
+			}
+		}
+	}
+
+	/* discard */
+	sieve_discard = mail_user_plugin_getenv
+		(mdctx->rcpt_user, "sieve_discard");
+	if ( sieve_discard != NULL && *sieve_discard != '\0' ) {
+		srctx->discard_script = sieve_script_create_open
+			(svinst, sieve_discard, NULL, &error);
+		if ( srctx->discard_script == NULL ) {
+			switch ( error ) {
+			case SIEVE_ERROR_NOT_FOUND:
+				if (debug ) {
+					sieve_sys_debug(svinst,
+						"Location for sieve_discard not found: %s",
+						sieve_discard);
+				}
+				break;
+			case SIEVE_ERROR_TEMP_FAILURE:
+				ret = -1;
+				break;
+			default:
+				break;
+			}
+		}
+	}
+
+	if (ret < 0) {
+		mdctx->tempfail_error =
+			"Temporarily unable to access necessary Sieve scripts";
+	}
+	srctx->scripts =
+		array_get_modifiable(&script_sequence, &srctx->script_count);
+	return ret;
+}
+
+static void
+lda_sieve_free_scripts(struct lda_sieve_run_context *srctx)
+{
+	unsigned int i;
+
+	for ( i = 0; i < srctx->script_count; i++ )
+		sieve_script_unref(&srctx->scripts[i]);
+	if ( srctx->discard_script != NULL )
+		sieve_script_unref(&srctx->discard_script);
+}
+
+static int lda_sieve_execute
+(struct lda_sieve_run_context *srctx, struct mail_storage **storage_r)
+{
+	struct mail_deliver_context *mdctx = srctx->mdctx;
+	struct sieve_instance *svinst = srctx->svinst;
+	struct sieve_message_data msgdata;
+	struct sieve_script_env scriptenv;
+	struct sieve_exec_status estatus;
+	struct sieve_trace_config trace_config;
+	struct sieve_trace_log *trace_log;
+	bool debug = mdctx->rcpt_user->mail_debug;
+	const char *error;
+	int ret;
+
+	/* Check whether there are any scripts to execute at all */
+
+	if ( srctx->script_count == 0 ) {
+		if ( debug ) {
+			sieve_sys_debug(svinst,
+				"No scripts to execute: reverting to default delivery.");
+		}
+
+		/* No error, but no delivery by this plugin either. A return value of <= 0
+		 * for a deliver plugin is is considered a failure. In deliver itself,
+		 * saved_mail and tried_default_save remain unset, meaning that deliver
+		 * will then attempt the default delivery. We return 0 to signify the lack
+		 * of a real error.
+		 */
+		return 0;
+	}
+
+	/* Initialize user error handler */
+
+	if ( srctx->user_script != NULL ) {
+		const char *log_path =
+			sieve_user_get_log_path(svinst, srctx->user_script);
+
+		if ( log_path != NULL ) {
+			srctx->userlog = log_path;
+			srctx->user_ehandler = sieve_logfile_ehandler_create
+				(svinst, srctx->userlog, LDA_SIEVE_MAX_USER_ERRORS);
+		}
+	}
+
+	/* Initialize trace logging */
+
+	trace_log = NULL;
+	if ( sieve_trace_config_get(svinst, &trace_config) >= 0 &&
+		sieve_trace_log_open(svinst, NULL, &trace_log) < 0 )
+		i_zero(&trace_config);
+
+	/* Collect necessary message data */
+
+	i_zero(&msgdata);
+
+	msgdata.mail = mdctx->src_mail;
+	msgdata.auth_user = mdctx->rcpt_user->username;
+	msgdata.envelope.mail_from = mail_deliver_get_return_address(mdctx);
+	msgdata.envelope.mail_params = &mdctx->mail_params;
+	msgdata.envelope.rcpt_to = mdctx->rcpt_to;
+	msgdata.envelope.rcpt_params = &mdctx->rcpt_params;
+	(void)mail_get_first_header(msgdata.mail, "Message-ID", &msgdata.id);
+
+	srctx->msgdata = &msgdata;
+
+	/* Compose script execution environment */
+
+	if (sieve_script_env_init(&scriptenv, mdctx->rcpt_user, &error) < 0) {
+		sieve_sys_error(svinst,
+			"Failed to initialize script execution: %s", error);
+		if ( trace_log != NULL )
+			sieve_trace_log_free(&trace_log);
+		return -1;
+	}
+
+	scriptenv.default_mailbox = mdctx->rcpt_default_mailbox;
+	scriptenv.mailbox_autocreate = mdctx->set->lda_mailbox_autocreate;
+	scriptenv.mailbox_autosubscribe = mdctx->set->lda_mailbox_autosubscribe;
+	scriptenv.smtp_start = lda_sieve_smtp_start;
+	scriptenv.smtp_add_rcpt = lda_sieve_smtp_add_rcpt;
+	scriptenv.smtp_send = lda_sieve_smtp_send;
+	scriptenv.smtp_abort = lda_sieve_smtp_abort;
+	scriptenv.smtp_finish = lda_sieve_smtp_finish;
+	scriptenv.duplicate_mark = lda_sieve_duplicate_mark;
+	scriptenv.duplicate_check = lda_sieve_duplicate_check;
+	scriptenv.duplicate_flush = lda_sieve_duplicate_flush;
+	scriptenv.reject_mail = lda_sieve_reject_mail;
+	scriptenv.script_context = (void *) mdctx;
+	scriptenv.trace_log = trace_log;
+	scriptenv.trace_config = trace_config;
+
+	i_zero(&estatus);
+	scriptenv.exec_status = &estatus;
+
+	srctx->scriptenv = &scriptenv;
+
+	/* Execute script(s) */
+
+	ret = lda_sieve_execute_scripts(srctx);
+
+	/* Record status */
+
+	mdctx->tried_default_save = estatus.tried_default_save;
+	*storage_r = estatus.last_storage;
+
+	if ( trace_log != NULL )
+		sieve_trace_log_free(&trace_log);
+
+	return ret;
+}
+
+static int lda_sieve_deliver_mail
+(struct mail_deliver_context *mdctx, struct mail_storage **storage_r)
+{
+	struct lda_sieve_run_context srctx;
+	const struct mail_storage_settings *mail_set =
+		mail_user_set_get_storage_set(mdctx->rcpt_user);
+	bool debug = mdctx->rcpt_user->mail_debug;
+	struct sieve_environment svenv;
+	int ret = 0;
+
+	/* Initialize run context */
+
+	i_zero(&srctx);
+	srctx.mdctx = mdctx;
+	(void)mail_user_get_home(mdctx->rcpt_user, &srctx.home_dir);
+
+	/* Initialize Sieve engine */
+
+	memset((void*)&svenv, 0, sizeof(svenv));
+	svenv.username = mdctx->rcpt_user->username;
+	svenv.home_dir = srctx.home_dir;
+	svenv.hostname = mail_set->hostname;
+	svenv.base_dir = mdctx->rcpt_user->set->base_dir;
+	svenv.temp_dir = mdctx->rcpt_user->set->mail_temp_dir;
+	svenv.flags = SIEVE_FLAG_HOME_RELATIVE;
+	svenv.location = SIEVE_ENV_LOCATION_MDA;
+	svenv.delivery_phase = SIEVE_DELIVERY_PHASE_DURING;
+
+	srctx.svinst = sieve_init(&svenv, &lda_sieve_callbacks, mdctx, debug);
+
+	/* Initialize master error handler */
+
+	srctx.master_ehandler =
+		sieve_master_ehandler_create(srctx.svinst, NULL, 0);
+	sieve_system_ehandler_set(srctx.master_ehandler);
+
+	sieve_error_handler_accept_infolog(srctx.master_ehandler, TRUE);
+	sieve_error_handler_accept_debuglog(srctx.master_ehandler, debug);
+
+	*storage_r = NULL;
+
+	/* Find Sieve scripts and run them */
+
+	T_BEGIN {
+		if (lda_sieve_find_scripts(&srctx) < 0)
+			ret = -1;
+		else if ( srctx.scripts == NULL )
+			ret = 0;
+		else
+			ret = lda_sieve_execute(&srctx, storage_r);
+
+		lda_sieve_free_scripts(&srctx);
+	} T_END;
+
+	/* Clean up */
+
+	if ( srctx.user_ehandler != NULL )
+		sieve_error_handler_unref(&srctx.user_ehandler);
+	sieve_error_handler_unref(&srctx.master_ehandler);
+	sieve_deinit(&srctx.svinst);
+
+	return ret;
+}
+
+/*
+ * Plugin interface
+ */
+
+const char *sieve_plugin_version = DOVECOT_ABI_VERSION;
+const char sieve_plugin_binary_dependency[] = "lda lmtp";
+
+void sieve_plugin_init(void)
+{
+	/* Hook into the delivery process */
+	next_deliver_mail = mail_deliver_hook_set(lda_sieve_deliver_mail);
+}
+
+void sieve_plugin_deinit(void)
+{
+	/* Remove hook */
+	mail_deliver_hook_set(next_deliver_mail);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/lda-sieve/lda-sieve-plugin.h
@@ -0,0 +1,11 @@
+#ifndef LDA_SIEVE_PLUGIN_H
+#define LDA_SIEVE_PLUGIN_H
+
+/*
+ * Plugin interface
+ */
+
+void sieve_plugin_init(void);
+void sieve_plugin_deinit(void);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/settings/Makefile.am
@@ -0,0 +1,12 @@
+settingsdir = $(dovecot_moduledir)/settings
+
+AM_CPPFLAGS = \
+	$(LIBDOVECOT_INCLUDE)
+
+libpigeonhole_settings_la_LDFLAGS = -module -avoid-version
+
+settings_LTLIBRARIES = \
+	libpigeonhole_settings.la
+
+libpigeonhole_settings_la_SOURCES = \
+	pigeonhole-settings.c
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/settings/pigeonhole-settings.c
@@ -0,0 +1,13 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "pigeonhole-config.h"
+#include "pigeonhole-version.h"
+
+/* This is currently just a dummy plugin that adds a Pigeonhole
+ * version banner the doveconf output.
+ */
+
+const char *pigeonhole_settings_version = DOVECOT_ABI_VERSION;
+const char *pigeonhole_settings_doveconf_banner = "Pigeonhole version "PIGEONHOLE_VERSION_FULL;
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/sieve-extprograms/Makefile.am
@@ -0,0 +1,34 @@
+sieve_plugindir = $(dovecot_moduledir)/sieve
+
+sieve_plugin_LTLIBRARIES = lib90_sieve_extprograms_plugin.la
+
+lib90_sieve_extprograms_plugin_la_LDFLAGS = -module -avoid-version
+
+AM_CPPFLAGS = \
+    -I$(top_srcdir)/src/lib-sieve \
+    -I$(top_srcdir)/src/lib-sieve/util \
+	-I$(top_srcdir)/src/lib-sieve/plugins/copy \
+	-I$(top_srcdir)/src/lib-sieve/plugins/variables \
+	$(LIBDOVECOT_INCLUDE) \
+	-DPKG_RUNDIR=\""$(rundir)"\"
+
+commands = \
+	cmd-pipe.c \
+	cmd-filter.c \
+	cmd-execute.c
+
+extensions = \
+	ext-pipe.c \
+	ext-filter.c \
+	ext-execute.c
+
+lib90_sieve_extprograms_plugin_la_SOURCES = \
+	$(commands) \
+	$(extensions) \
+	sieve-extprograms-common.c \
+	sieve-extprograms-plugin.c
+
+noinst_HEADERS = \
+	sieve-extprograms-common.h \
+	sieve-extprograms-plugin.h
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/sieve-extprograms/cmd-execute.c
@@ -0,0 +1,454 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "buffer.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "istream.h"
+#include "ostream.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-binary.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-result.h"
+
+#include "sieve-ext-variables.h"
+
+#include "sieve-extprograms-common.h"
+
+/* Execute command
+ *
+ * Syntax:
+ *   "execute" [":input" <input-data: string> / ":pipe"] 
+ *             [":output" <varname: string>]
+ *             <program-name: string> [<arguments: string-list>]
+ *
+ */
+
+static bool cmd_execute_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool cmd_execute_generate
+	(const struct sieve_codegen_env *cgenv, 
+		struct sieve_command *ctx);
+
+const struct sieve_command_def cmd_execute = {
+	.identifier = "execute",
+	.type = SCT_HYBRID,
+	.positional_args = -1, /* We check positional arguments ourselves */
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = cmd_execute_registered,
+	.validate = sieve_extprogram_command_validate,
+	.generate = cmd_execute_generate,
+};
+
+/*
+ * Tagged arguments
+ */
+
+static bool cmd_execute_validate_input_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+    struct sieve_command *cmd);
+static bool cmd_execute_generate_input_tag
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+    struct sieve_command *cmd);
+
+static bool cmd_execute_validate_output_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg, 
+		struct sieve_command *cmd);
+
+static const struct sieve_argument_def execute_input_tag = { 
+	.identifier = "input", 
+	.validate = cmd_execute_validate_input_tag,
+	.generate = cmd_execute_generate_input_tag
+};
+
+static const struct sieve_argument_def execute_pipe_tag = { 
+	.identifier = "pipe",
+	.validate = cmd_execute_validate_input_tag,
+	.generate = cmd_execute_generate_input_tag
+};
+
+static const struct sieve_argument_def execute_output_tag = { 
+	.identifier = "output", 
+	.validate = cmd_execute_validate_output_tag,
+};
+
+
+/* 
+ * Execute operation 
+ */
+
+static bool cmd_execute_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_execute_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def cmd_execute_operation = { 
+	.mnemonic = "EXECUTE",
+	.ext_def = &vnd_execute_extension, 
+	.dump = cmd_execute_operation_dump, 
+	.execute = cmd_execute_operation_execute
+};
+
+/* Codes for optional operands */
+
+enum cmd_execute_optional {
+  OPT_END,
+  OPT_INPUT,
+  OPT_OUTPUT
+};
+
+/*
+ * Tag validation
+ */
+
+static bool cmd_execute_validate_input_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+    struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *tag = *arg;
+
+	if ( (bool) cmd->data ) {
+		sieve_argument_validate_error(valdtr, *arg,
+			"multiple :input or :pipe arguments specified for the %s %s",
+			sieve_command_identifier(cmd), sieve_command_type_name(cmd));
+		return FALSE;
+	}
+
+	cmd->data = (void *) TRUE;
+
+	/* Skip tag */
+ 	*arg = sieve_ast_argument_next(*arg);
+
+	if ( sieve_argument_is(tag, execute_input_tag) ) {
+		/* Check syntax:
+		 *   :input <input-data: string>
+		 */
+		if ( !sieve_validate_tag_parameter
+			(valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING, FALSE) ) {
+			return FALSE;
+		}
+
+		/* Assign tag parameters */
+		tag->parameters = *arg;
+		*arg = sieve_ast_arguments_detach(*arg,1);
+	}
+
+	return TRUE;
+}
+
+static bool cmd_execute_validate_output_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg, 
+	struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *tag = *arg;
+	struct sieve_extprograms_config *ext_config =
+		(struct sieve_extprograms_config *) cmd->ext->context;
+
+	if ( ext_config == NULL || ext_config->var_ext == NULL ||
+		!sieve_ext_variables_is_active(ext_config->var_ext, valdtr) )	{
+		sieve_argument_validate_error(valdtr,*arg, 
+			"the %s %s only allows for the specification of an "
+			":output argument when the variables extension is active",
+			sieve_command_identifier(cmd), sieve_command_type_name(cmd));
+		return FALSE;
+	}
+
+	/* Detach the tag itself */
+	*arg = sieve_ast_arguments_detach(*arg, 1);
+	
+	if ( !sieve_variable_argument_activate(ext_config->var_ext,
+		ext_config->var_ext, valdtr, cmd, *arg, TRUE) )
+		return FALSE;
+
+	(*arg)->argument->id_code = tag->argument->id_code;
+
+	/* Skip parameter */
+	*arg = sieve_ast_argument_next(*arg);
+					
+	return TRUE;
+}		
+
+/*
+ * Command registration
+ */
+
+static bool cmd_execute_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg)
+{
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &execute_input_tag, OPT_INPUT);
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &execute_pipe_tag, OPT_INPUT);
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &execute_output_tag, OPT_OUTPUT);
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_execute_generate_input_tag
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+    struct sieve_command *cmd)
+{
+	if ( arg->parameters == NULL ) {
+		sieve_opr_omitted_emit(cgenv->sblock);
+		return TRUE;
+	}
+
+	return sieve_generate_argument_parameters(cgenv, cmd, arg);
+}
+
+static bool cmd_execute_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	sieve_operation_emit(cgenv->sblock, cmd->ext, &cmd_execute_operation);
+
+	/* Emit is_test flag */
+	sieve_binary_emit_byte(cgenv->sblock,
+		(uint8_t)( cmd->ast_node->type == SAT_TEST ? 1 : 0));
+
+	/* Generate arguments */
+	if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
+		return FALSE;
+
+	/* Emit a placeholder when the <arguments> argument is missing */
+	if ( sieve_ast_argument_next(cmd->first_positional) == NULL )
+		sieve_opr_omitted_emit(cgenv->sblock);
+
+	return TRUE;
+}
+
+/* 
+ * Code dump
+ */
+ 
+static bool cmd_execute_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{	
+	int opt_code = 0;
+	unsigned int is_test = 0;
+	
+	/* Read is_test flag */
+	if ( !sieve_binary_read_byte(denv->sblock, address, &is_test) )
+		return FALSE;
+
+	sieve_code_dumpf(denv, "EXECUTE (%s)",
+		(is_test > 0 ? "test" : "command"));
+	sieve_code_descend(denv);	
+
+	/* Dump optional operands */
+	for (;;) {
+		int opt;
+		bool opok = TRUE;
+
+		if ( (opt=sieve_action_opr_optional_dump(denv, address, &opt_code)) < 0 )
+			return FALSE;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_INPUT:
+			opok = sieve_opr_string_dump_ex(denv, address, "input", "PIPE");
+			break;	
+		case OPT_OUTPUT:
+			opok = sieve_opr_string_dump(denv, address, "output");
+			break;
+		default:
+			return FALSE;
+		}
+
+		if ( !opok ) return FALSE;
+	}
+	
+	if ( !sieve_opr_string_dump(denv, address, "program-name") )
+		return FALSE;
+
+	return sieve_opr_stringlist_dump_ex(denv, address, "arguments", "");
+}
+
+/* 
+ * Code execution
+ */
+
+static int cmd_execute_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{	
+	const struct sieve_extension *this_ext = renv->oprtn->ext;
+	struct sieve_side_effects_list *slist = NULL;
+	int opt_code = 0;
+	unsigned int is_test = 0;
+	struct sieve_stringlist *args_list = NULL;
+	string_t *pname = NULL, *input = NULL;
+	struct sieve_variable_storage *var_storage = NULL;
+	unsigned int var_index;
+	bool have_input = FALSE;
+	const char *program_name = NULL;
+	const char *const *args = NULL;
+	enum sieve_error error = SIEVE_ERROR_NONE;
+	buffer_t *outbuf = NULL;
+	struct sieve_extprogram *sprog = NULL;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* The is_test flag */
+	if ( !sieve_binary_read_byte(renv->sblock, address, &is_test) ) {
+		sieve_runtime_trace_error(renv, "invalid is_test flag");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	/* Optional operands */	
+
+	for (;;) {
+		int opt;
+
+		if ( (opt=sieve_action_opr_optional_read
+			(renv, address, &opt_code, &ret, &slist)) < 0 )
+			return ret;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_INPUT:
+			ret = sieve_opr_string_read_ex
+				(renv, address, "input", TRUE, &input, NULL);
+			have_input = TRUE;
+			break;
+		case OPT_OUTPUT:
+			ret = sieve_variable_operand_read
+				(renv, address, "output", &var_storage, &var_index);
+			break;
+		default:
+			sieve_runtime_trace_error(renv, "unknown optional operand");
+			return SIEVE_EXEC_BIN_CORRUPT;
+		}
+
+		if ( ret <= 0 ) return ret;
+	}
+
+	/* Fixed operands */
+
+	if ( (ret=sieve_extprogram_command_read_operands
+		(renv, address, &pname, &args_list)) <= 0 )
+		return ret;
+
+	program_name = str_c(pname);
+	if ( args_list != NULL &&
+		sieve_stringlist_read_all(args_list, pool_datastack_create(), &args) < 0 ) {
+		sieve_runtime_trace_error(renv, "failed to read args operand");
+		return args_list->exec_status;
+	}
+
+	/*
+	 * Perform operation
+	 */
+
+	/* Trace */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS, "execute action");
+	sieve_runtime_trace_descend(renv);
+	sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS,
+		"execute program `%s'", str_sanitize(program_name, 128));
+
+	sprog = sieve_extprogram_create
+		(this_ext, renv->scriptenv, renv->msgdata, "execute", program_name, args,
+			&error);
+	if ( sprog != NULL ) {
+		if ( var_storage != NULL ) {
+			// FIXME: limit output size
+			struct ostream *outdata;
+
+			outbuf = buffer_create_dynamic(default_pool, 1024);
+			outdata = o_stream_create_buffer(outbuf);
+			sieve_extprogram_set_output(sprog, outdata);
+			o_stream_unref(&outdata);
+		}
+
+		if ( input == NULL && have_input ) {
+			struct mail *mail = sieve_message_get_mail(renv->msgctx);
+
+			if ( sieve_extprogram_set_input_mail(sprog, mail) < 0 ) {
+				sieve_extprogram_destroy(&sprog);
+				if ( outbuf != NULL )
+					buffer_free(&outbuf);
+				return sieve_runtime_mail_error(renv, mail,
+					"execute action: failed to read input message");
+			}
+			ret = 1;
+		} else if ( input != NULL ) {
+			struct istream *indata =
+				i_stream_create_from_data(str_data(input), str_len(input));
+			sieve_extprogram_set_input(sprog, indata);
+			i_stream_unref(&indata);
+			ret = 1;
+		}
+
+		if ( ret >= 0 ) 
+			ret = sieve_extprogram_run(sprog);
+		sieve_extprogram_destroy(&sprog);
+	} else {
+		ret = -1;
+	}
+
+	if ( ret > 0 ) {
+		sieve_runtime_trace(renv,	SIEVE_TRLVL_ACTIONS,
+			"executed program successfully");
+
+		if ( var_storage != NULL ) {
+			string_t *var;
+
+			if ( sieve_variable_get_modifiable(var_storage, var_index, &var) ) {
+				str_truncate(var, 0);
+				str_append_str(var, outbuf); 
+
+				sieve_runtime_trace(renv,	SIEVE_TRLVL_ACTIONS,
+					"assigned output variable");
+			} // FIXME: handle failure
+		}
+
+	} else if ( ret < 0 ) {
+		if ( error == SIEVE_ERROR_NOT_FOUND ) {
+			sieve_runtime_error(renv, NULL,
+				"execute action: program `%s' not found",
+				str_sanitize(program_name, 80));
+		} else {
+			sieve_extprogram_exec_error(renv->ehandler,
+				sieve_runtime_get_full_command_location(renv),
+				"execute action: failed to execute to program `%s'",
+				str_sanitize(program_name, 80));
+		}
+	} else {
+		sieve_runtime_trace(renv,	SIEVE_TRLVL_ACTIONS,
+			"execute action: program indicated false result");
+	}
+
+	if ( outbuf != NULL ) 
+		buffer_free(&outbuf);
+
+	if ( is_test > 0 ) {
+		sieve_interpreter_set_test_result(renv->interp, ( ret > 0 ));
+		return SIEVE_EXEC_OK;
+	}
+	return ( ret >= 0 ? SIEVE_EXEC_OK : SIEVE_EXEC_FAILURE );
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/sieve-extprograms/cmd-filter.c
@@ -0,0 +1,244 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "buffer.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "istream.h"
+#include "ostream.h"
+#include "safe-mkstemp.h"
+#include "mail-user.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-binary.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-result.h"
+
+#include "sieve-ext-variables.h"
+
+#include "sieve-extprograms-common.h"
+
+/* Filter command
+ *
+ * Syntax:
+ *   "filter" <program-name: string> [<arguments: string-list>]
+ *
+ */
+
+static bool cmd_filter_generate
+	(const struct sieve_codegen_env *cgenv, 
+		struct sieve_command *ctx);
+
+const struct sieve_command_def cmd_filter = {
+	.identifier = "filter",
+	.type = SCT_HYBRID,
+	.positional_args = -1, /* We check positional arguments ourselves */
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = sieve_extprogram_command_validate,
+	.generate = cmd_filter_generate
+};
+
+/* 
+ * Filter operation 
+ */
+
+static bool cmd_filter_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_filter_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def cmd_filter_operation = { 
+	.mnemonic = "FILTER",
+	.ext_def = &vnd_filter_extension, 
+	.dump = cmd_filter_operation_dump, 
+	.execute = cmd_filter_operation_execute
+};
+
+/*
+ * Code generation
+ */
+
+static bool cmd_filter_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	sieve_operation_emit(cgenv->sblock, cmd->ext, &cmd_filter_operation);
+
+	/* Emit is_test flag */
+	sieve_binary_emit_byte(cgenv->sblock,
+		(uint8_t)( cmd->ast_node->type == SAT_TEST ? 1 : 0 ));
+
+	/* Generate arguments */
+	if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
+		return FALSE;
+
+	/* Emit a placeholder when the <arguments> argument is missing */
+	if ( sieve_ast_argument_next(cmd->first_positional) == NULL )
+		sieve_opr_omitted_emit(cgenv->sblock);
+
+	return TRUE;
+}
+
+/* 
+ * Code dump
+ */
+ 
+static bool cmd_filter_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{	
+	unsigned int is_test = 0;
+
+	/* Read is_test flag */
+	if ( !sieve_binary_read_byte(denv->sblock, address, &is_test) )
+		return FALSE;
+
+	sieve_code_dumpf(denv, "FILTER (%s)",
+		(is_test > 0 ? "test" : "command"));
+	sieve_code_descend(denv);		
+
+	/* Dump optional operands */
+	if ( sieve_action_opr_optional_dump(denv, address, NULL) != 0 )
+		return FALSE;
+
+	if ( !sieve_opr_string_dump(denv, address, "program-name") )
+		return FALSE;
+
+	return sieve_opr_stringlist_dump_ex(denv, address, "arguments", "");
+}
+
+/* 
+ * Code execution
+ */
+
+static int cmd_filter_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{	
+	const struct sieve_extension *this_ext = renv->oprtn->ext;
+	unsigned int is_test = 0;
+	struct sieve_stringlist *args_list = NULL;
+	enum sieve_error error = SIEVE_ERROR_NONE;
+	string_t *pname = NULL;
+	const char *program_name = NULL;
+	const char *const *args = NULL;
+	struct istream *newmsg = NULL;
+	struct sieve_extprogram *sprog;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* The is_test flag */
+
+	if ( !sieve_binary_read_byte(renv->sblock, address, &is_test) ) {
+		sieve_runtime_trace_error(renv, "invalid is_test flag");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	/* Optional operands */	
+
+	if ( sieve_action_opr_optional_read(renv, address, NULL, &ret, NULL) != 0 ) 
+		return ret;
+
+	/* Fixed operands */
+
+	if ( (ret=sieve_extprogram_command_read_operands
+		(renv, address, &pname, &args_list)) <= 0 )
+		return ret;
+
+	program_name = str_c(pname);
+	if ( args_list != NULL &&
+		sieve_stringlist_read_all(args_list, pool_datastack_create(), &args) < 0 ) {
+		sieve_runtime_trace_error(renv, "failed to read args operand");
+		return args_list->exec_status;
+	}
+
+	/*
+	 * Perform operation
+	 */
+
+	/* Trace */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS, "filter action");
+	sieve_runtime_trace_descend(renv);
+	sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS,
+		"execute program `%s'", str_sanitize(program_name, 128));
+
+	sprog = sieve_extprogram_create
+		(this_ext, renv->scriptenv, renv->msgdata, "filter", program_name, args,
+			&error);
+
+	if ( sprog != NULL ) {
+		struct mail *mail = sieve_message_get_mail(renv->msgctx);
+
+		if ( sieve_extprogram_set_input_mail(sprog, mail) < 0 ) {
+			sieve_extprogram_destroy(&sprog);
+			return sieve_runtime_mail_error(renv, mail,
+				"filter action: failed to read input message");
+		}
+		sieve_extprogram_set_output_seekable(sprog);
+		ret = sieve_extprogram_run(sprog);
+	} else {
+		ret = -1;
+	}
+
+	if ( ret > 0 )
+		newmsg = sieve_extprogram_get_output_seekable(sprog);
+	if ( sprog != NULL )
+		sieve_extprogram_destroy(&sprog);
+
+	if ( ret > 0 && newmsg != NULL ) {
+		sieve_runtime_trace(renv,	SIEVE_TRLVL_ACTIONS,
+			"executed program successfully");
+
+		i_stream_set_name(newmsg,
+			t_strdup_printf("filter %s output", program_name));
+		newmsg->blocking = TRUE;
+		if ( (ret=sieve_message_substitute(renv->msgctx, newmsg)) >= 0 ) {
+			sieve_runtime_trace(renv,	SIEVE_TRLVL_ACTIONS,
+				"changed message");
+		} else {
+			sieve_runtime_critical(renv, NULL, "filter action",
+				"filter action: failed to substitute message"); 
+		}
+
+		i_stream_unref(&newmsg);
+
+	} else if ( ret < 0 ) {
+		if ( error == SIEVE_ERROR_NOT_FOUND ) {
+			sieve_runtime_error(renv, NULL,
+				"filter action: program `%s' not found",
+				str_sanitize(program_name, 80));
+		} else {
+			sieve_extprogram_exec_error(renv->ehandler,
+				sieve_runtime_get_full_command_location(renv),
+				"filter action: failed to execute to program `%s'",
+				str_sanitize(program_name, 80));
+		}
+
+	} else {
+		sieve_runtime_trace(renv,	SIEVE_TRLVL_ACTIONS,
+			"filter action: program indicated false result");
+	}
+
+	if ( is_test > 0 ) {
+		sieve_interpreter_set_test_result(renv->interp, ( ret > 0 ));
+
+		return SIEVE_EXEC_OK;
+	}
+
+	return ( ret >= 0 ? SIEVE_EXEC_OK : SIEVE_EXEC_FAILURE );
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/sieve-extprograms/cmd-pipe.c
@@ -0,0 +1,386 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-result.h"
+
+#include "sieve-extprograms-common.h"
+
+/* Pipe command
+ *
+ * Syntax:
+ *   pipe [":copy"] [":try"] <program-name: string> [<arguments: string-list>]
+ *
+ */
+
+static bool cmd_pipe_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool cmd_pipe_generate
+	(const struct sieve_codegen_env *cgenv, 
+		struct sieve_command *ctx);
+
+const struct sieve_command_def cmd_pipe = {
+	.identifier = "pipe",
+	.type = SCT_COMMAND,
+	.positional_args = -1, /* We check positional arguments ourselves */
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = cmd_pipe_registered,
+	.validate = sieve_extprogram_command_validate,
+	.generate = cmd_pipe_generate
+};
+
+/*
+ * Tagged arguments
+ */
+
+static const struct sieve_argument_def pipe_try_tag = { 
+	.identifier = "try"
+};
+
+/* 
+ * Pipe operation 
+ */
+
+static bool cmd_pipe_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_pipe_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def cmd_pipe_operation = { 
+	.mnemonic = "PIPE",
+	.ext_def = &vnd_pipe_extension,
+	.dump = cmd_pipe_operation_dump, 
+	.execute = cmd_pipe_operation_execute
+};
+
+/* Codes for optional operands */
+
+enum cmd_pipe_optional {
+  OPT_END,
+  OPT_TRY
+};
+
+/* 
+ * Pipe action 
+ */
+
+/* Forward declarations */
+
+static int act_pipe_check_duplicate
+	(const struct sieve_runtime_env *renv, 
+		const struct sieve_action *act,
+		const struct sieve_action *act_other);
+static void act_pipe_print
+	(const struct sieve_action *action,
+		const struct sieve_result_print_env *rpenv, bool *keep);	
+static int act_pipe_commit
+	(const struct sieve_action *action,	
+		const struct sieve_action_exec_env *aenv, void *tr_context, bool *keep);
+
+/* Action object */
+
+const struct sieve_action_def act_pipe = {
+	.name = "pipe",
+	.flags = SIEVE_ACTFLAG_TRIES_DELIVER,
+	.check_duplicate = act_pipe_check_duplicate, 
+	.print = act_pipe_print,
+	.commit = act_pipe_commit
+};
+
+/* Action context information */
+		
+struct ext_pipe_action {
+	const char *program_name;
+	const char * const *args;
+	bool try;
+};
+
+/*
+ * Command registration
+ */
+
+static bool cmd_pipe_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg)
+{
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &pipe_try_tag, OPT_TRY);
+
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_pipe_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	sieve_operation_emit(cgenv->sblock, cmd->ext, &cmd_pipe_operation);
+
+	/* Generate arguments */
+	if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
+		return FALSE;
+
+	/* Emit a placeholder when the <arguments> argument is missing */
+	if ( sieve_ast_argument_next(cmd->first_positional) == NULL )
+		sieve_opr_omitted_emit(cgenv->sblock);
+
+	return TRUE;
+}
+
+/* 
+ * Code dump
+ */
+ 
+static bool cmd_pipe_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{	
+	int opt_code = 0;
+	
+	sieve_code_dumpf(denv, "PIPE");
+	sieve_code_descend(denv);	
+
+	/* Dump optional operands */
+	for (;;) {
+		int opt;
+
+		if ( (opt=sieve_action_opr_optional_dump(denv, address, &opt_code)) < 0 )
+			return FALSE;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_TRY:
+			sieve_code_dumpf(denv, "try");	
+			break;
+		default:
+			return FALSE;
+		}
+	}
+	
+	if ( !sieve_opr_string_dump(denv, address, "program-name") )
+		return FALSE;
+
+	return sieve_opr_stringlist_dump_ex(denv, address, "arguments", "");
+}
+
+/* 
+ * Code execution
+ */
+
+static int cmd_pipe_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{	
+	const struct sieve_extension *this_ext = renv->oprtn->ext;
+	struct sieve_side_effects_list *slist = NULL;
+	struct ext_pipe_action *act;
+	pool_t pool;
+	int opt_code = 0;
+	struct sieve_stringlist *args_list = NULL;
+	string_t *pname = NULL;
+	bool try = FALSE;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Optional operands */	
+
+	for (;;) {
+		int opt;
+
+		if ( (opt=sieve_action_opr_optional_read
+			(renv, address, &opt_code, &ret, &slist)) < 0 )
+			return ret;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_TRY:
+			try = TRUE;
+			break;
+		default:
+			sieve_runtime_trace_error(renv, "unknown optional operand");
+			return SIEVE_EXEC_BIN_CORRUPT;
+		}
+	}
+
+	/* Fixed operands */
+
+	if ( (ret=sieve_extprogram_command_read_operands
+		(renv, address, &pname, &args_list)) <= 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	/* Trace */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS, "pipe action");	
+
+	/* Compose action */
+
+	pool = sieve_result_pool(renv->result);
+	act = p_new(pool, struct ext_pipe_action, 1);
+
+	if ( args_list != NULL &&
+		sieve_stringlist_read_all(args_list, pool, &act->args) < 0 ) {
+		sieve_runtime_trace_error(renv, "failed to read args operand");
+		return args_list->exec_status;
+	}
+	
+	act->program_name = p_strdup(pool, str_c(pname));
+	act->try = try;
+
+	if ( sieve_result_add_action
+		(renv, this_ext, &act_pipe, slist, (void *) act, 0, TRUE) < 0 ) {
+		return SIEVE_EXEC_FAILURE;
+	}
+
+	return SIEVE_EXEC_OK;
+}
+
+/*
+ * Action
+ */
+
+/* Runtime verification */
+
+static int act_pipe_check_duplicate
+(const struct sieve_runtime_env *renv ATTR_UNUSED, 
+	const struct sieve_action *act,
+	const struct sieve_action *act_other)
+{
+	struct ext_pipe_action *new_act, *old_act;
+		
+	if ( act->context == NULL || act_other->context == NULL )
+		return 0;
+
+	new_act = (struct ext_pipe_action *) act->context;
+	old_act = (struct ext_pipe_action *) act_other->context;
+
+	if ( strcmp(new_act->program_name, old_act->program_name) == 0 ) {
+		sieve_runtime_error(renv, act->location,
+			"duplicate pipe \"%s\" action not allowed "
+			"(previously triggered one was here: %s)",
+			new_act->program_name, act_other->location);
+		return -1;
+	}
+
+	return 0;
+}
+
+/* Result printing */
+ 
+static void act_pipe_print
+(const struct sieve_action *action,	const struct sieve_result_print_env *rpenv, 
+	bool *keep ATTR_UNUSED)	
+{
+	const struct ext_pipe_action *act = 
+		(const struct ext_pipe_action *) action->context;
+
+	sieve_result_action_printf
+		( rpenv, "pipe message to external program '%s':", act->program_name);
+	
+	/* Print main method parameters */
+
+	sieve_result_printf
+		( rpenv, "    => try           : %s\n", (act->try ? "yes" : "no") );
+
+	/* FIXME: print args */
+
+	/* Finish output with an empty line */
+
+	sieve_result_printf(rpenv, "\n");
+}
+
+/* Result execution */
+
+static int act_pipe_commit
+(const struct sieve_action *action,
+	const struct sieve_action_exec_env *aenv, 
+	void *tr_context ATTR_UNUSED, bool *keep)
+{
+	const struct ext_pipe_action *act = 
+		(const struct ext_pipe_action *) action->context;
+	enum sieve_error error = SIEVE_ERROR_NONE;
+	struct mail *mail =	( action->mail != NULL ?
+		action->mail : sieve_message_get_mail(aenv->msgctx) );
+	struct sieve_extprogram *sprog;
+	int ret;
+
+	sprog = sieve_extprogram_create
+		(action->ext, aenv->scriptenv, aenv->msgdata, "pipe",
+			act->program_name, act->args, &error);
+	if ( sprog != NULL ) {
+		if ( sieve_extprogram_set_input_mail(sprog, mail) < 0 ) {
+			sieve_extprogram_destroy(&sprog);
+			return sieve_result_mail_error(aenv, mail,
+				"pipe action: failed to read input message");
+		}
+		ret = sieve_extprogram_run(sprog);
+	} else {
+		ret = -1;
+	}
+	if ( sprog != NULL )
+		sieve_extprogram_destroy(&sprog);
+
+	if ( ret > 0 ) {
+		sieve_result_global_log(aenv, "pipe action: "
+			"piped message to program `%s'", str_sanitize(act->program_name, 128));
+
+		/* Indicate that message was successfully 'forwarded' */
+		aenv->exec_status->message_forwarded = TRUE;
+	} else {
+		if ( ret < 0 ) {
+			if ( error == SIEVE_ERROR_NOT_FOUND ) {
+				sieve_result_error(aenv, "pipe action: "
+					"failed to pipe message to program: program `%s' not found",
+					str_sanitize(act->program_name, 80));						
+			} else {
+				sieve_extprogram_exec_error(aenv->ehandler, NULL,
+					"pipe action: failed to pipe message to program `%s'",
+					str_sanitize(act->program_name, 80));
+			}
+		} else {
+			sieve_extprogram_exec_error(aenv->ehandler, NULL,
+				"pipe action: failed to execute to program `%s'",
+				str_sanitize(act->program_name, 80));
+		}
+
+		if ( act->try ) return SIEVE_EXEC_OK;
+
+		return SIEVE_EXEC_FAILURE;
+	}
+
+	*keep = FALSE;
+	return SIEVE_EXEC_OK;
+}
+
+
+
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/sieve-extprograms/ext-execute.c
@@ -0,0 +1,80 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension vnd.dovecot.execute
+ * -----------------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: vendor-defined; spec-bosch-sieve-extprograms
+ * Implementation: full
+ * Status: experimental
+ *
+ */
+
+#include "lib.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-binary.h"
+
+#include "sieve-validator.h"
+#include "sieve-interpreter.h"
+
+#include "sieve-ext-copy.h"
+
+#include "sieve-extprograms-common.h"
+
+/*
+ * Extension
+ */
+
+static bool ext_execute_load(const struct sieve_extension *ext, void **context);
+static void ext_execute_unload(const struct sieve_extension *ext);
+static bool ext_execute_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+	
+const struct sieve_extension_def vnd_execute_extension = { 
+	.name = "vnd.dovecot.execute",
+	.load = ext_execute_load,
+	.unload = ext_execute_unload,
+	.validator_load = ext_execute_validator_load,
+	SIEVE_EXT_DEFINE_OPERATION(cmd_execute_operation)
+};
+
+/*
+ * Context
+ */
+
+static bool ext_execute_load(const struct sieve_extension *ext, void **context)
+{
+	if ( *context != NULL ) {
+		ext_execute_unload(ext);
+		*context = NULL;
+	}
+
+	*context = (void *)sieve_extprograms_config_init(ext);
+	return TRUE;
+}
+
+static void ext_execute_unload(const struct sieve_extension *ext)
+{
+	struct sieve_extprograms_config *ext_config = 
+		(struct sieve_extprograms_config *)ext->context;
+
+	if ( ext_config == NULL ) return;
+
+	sieve_extprograms_config_deinit(&ext_config);
+}
+
+/*
+ * Validation
+ */
+
+static bool ext_execute_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	/* Register commands */
+	sieve_validator_register_command(valdtr, ext, &cmd_execute);
+
+	return TRUE;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/sieve-extprograms/ext-filter.c
@@ -0,0 +1,80 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension vnd.dovecot.filter
+ * -----------------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: vendor-defined; spec-bosch-sieve-extprograms
+ * Implementation: full
+ * Status: experimental
+ *
+ */
+
+#include "lib.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-binary.h"
+
+#include "sieve-validator.h"
+#include "sieve-interpreter.h"
+
+#include "sieve-ext-copy.h"
+
+#include "sieve-extprograms-common.h"
+
+/*
+ * Extension
+ */
+
+static bool ext_filter_load(const struct sieve_extension *ext, void **context);
+static void ext_filter_unload(const struct sieve_extension *ext);
+static bool ext_filter_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+	
+const struct sieve_extension_def vnd_filter_extension = { 
+	.name = "vnd.dovecot.filter",
+	.load = ext_filter_load,
+	.unload = ext_filter_unload,
+	.validator_load = ext_filter_validator_load,
+	SIEVE_EXT_DEFINE_OPERATION(cmd_filter_operation),
+};
+
+/*
+ * Context
+ */
+
+static bool ext_filter_load(const struct sieve_extension *ext, void **context)
+{
+	if ( *context != NULL ) {
+		ext_filter_unload(ext);
+		*context = NULL;
+	}
+
+	*context = (void *)sieve_extprograms_config_init(ext);
+	return TRUE;
+}
+
+static void ext_filter_unload(const struct sieve_extension *ext)
+{
+	struct sieve_extprograms_config *ext_config = 
+		(struct sieve_extprograms_config *)ext->context;
+
+	if ( ext_config == NULL ) return;
+
+	sieve_extprograms_config_deinit(&ext_config);
+}
+
+/*
+ * Validation
+ */
+
+static bool ext_filter_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	/* Register commands */
+	sieve_validator_register_command(valdtr, ext, &cmd_filter);
+
+	return TRUE;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/sieve-extprograms/ext-pipe.c
@@ -0,0 +1,114 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension vnd.dovecot.pipe
+ * -----------------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: vendor-define; spec-bosch-sieve-extprograms
+ * Implementation: full
+ * Status: experimental
+ *
+ */
+
+#include "lib.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-binary.h"
+
+#include "sieve-validator.h"
+#include "sieve-interpreter.h"
+
+#include "sieve-ext-copy.h"
+
+#include "sieve-extprograms-common.h"
+
+/*
+ * Extension
+ */
+
+static bool ext_pipe_load(const struct sieve_extension *ext, void **context);
+static void ext_pipe_unload(const struct sieve_extension *ext);
+static bool ext_pipe_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+	
+const struct sieve_extension_def vnd_pipe_extension = { 
+	.name = "vnd.dovecot.pipe",
+	.load = ext_pipe_load,
+	.unload = ext_pipe_unload,
+	.validator_load = ext_pipe_validator_load,
+	SIEVE_EXT_DEFINE_OPERATION(cmd_pipe_operation),
+};
+
+/*
+ * Context
+ */
+
+static bool ext_pipe_load(const struct sieve_extension *ext, void **context)
+{
+	if ( *context != NULL ) {
+		ext_pipe_unload(ext);
+		*context = NULL;
+	}
+
+	*context = (void *)sieve_extprograms_config_init(ext);
+	return TRUE;
+}
+
+static void ext_pipe_unload(const struct sieve_extension *ext)
+{
+	struct sieve_extprograms_config *ext_config = 
+		(struct sieve_extprograms_config *)ext->context;
+
+	if ( ext_config == NULL ) return;
+
+	sieve_extprograms_config_deinit(&ext_config);
+}
+
+/*
+ * Validation
+ */
+
+static bool ext_pipe_validator_validate
+	(const struct sieve_extension *ext,
+		struct sieve_validator *valdtr, void *context,
+		struct sieve_ast_argument *require_arg,
+		bool required);
+
+const struct sieve_validator_extension pipe_validator_extension = {
+	.ext = &vnd_pipe_extension,
+	.validate = ext_pipe_validator_validate
+};
+
+static bool ext_pipe_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	/* Register commands */
+	sieve_validator_register_command(valdtr, ext, &cmd_pipe);
+
+	/* Register extension to validator */
+	sieve_validator_extension_register
+		(valdtr, ext, &pipe_validator_extension, NULL);
+
+	return TRUE;
+}
+
+static bool ext_pipe_validator_validate
+(const struct sieve_extension *ext,
+	struct sieve_validator *valdtr, void *context ATTR_UNUSED,
+	struct sieve_ast_argument *require_arg ATTR_UNUSED,
+	bool required ATTR_UNUSED)
+{
+	struct sieve_extprograms_config *ext_config =
+		(struct sieve_extprograms_config *) ext->context;
+
+	if ( ext_config != NULL && ext_config->copy_ext != NULL ) {
+		/* Register :copy command tag */
+		sieve_ext_copy_register_tag(valdtr,
+			ext_config->copy_ext, cmd_pipe.identifier);
+	}
+	return TRUE;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/sieve-extprograms/sieve-extprograms-common.c
@@ -0,0 +1,644 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "lib-signals.h"
+#include "str.h"
+#include "strfuncs.h"
+#include "str-sanitize.h"
+#include "unichar.h"
+#include "array.h"
+#include "eacces-error.h"
+#include "smtp-params.h"
+#include "istream.h"
+#include "istream-crlf.h"
+#include "istream-header-filter.h"
+#include "ostream.h"
+#include "mail-user.h"
+#include "mail-storage.h"
+
+#include "program-client.h"
+
+#include "sieve-common.h"
+#include "sieve-settings.h"
+#include "sieve-error.h"
+#include "sieve-extensions.h"
+#include "sieve-ast.h"
+#include "sieve-commands.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-runtime.h"
+#include "sieve-interpreter.h"
+
+#include "sieve-ext-copy.h"
+#include "sieve-ext-variables.h"
+
+#include "sieve-extprograms-common.h"
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+
+/*
+ * Limits
+ */
+
+#define SIEVE_EXTPROGRAMS_MAX_PROGRAM_NAME_LEN 128
+#define SIEVE_EXTPROGRAMS_MAX_PROGRAM_ARG_LEN  1024
+
+#define SIEVE_EXTPROGRAMS_DEFAULT_EXEC_TIMEOUT_SECS 10
+#define SIEVE_EXTPROGRAMS_CONNECT_TIMEOUT_MSECS 5
+
+/*
+ * Pipe Extension Context
+ */
+
+struct sieve_extprograms_config *sieve_extprograms_config_init
+(const struct sieve_extension *ext)
+{
+	struct sieve_instance *svinst = ext->svinst;
+	struct sieve_extprograms_config *ext_config;
+	const char *extname = sieve_extension_name(ext);
+	const char *bin_dir, *socket_dir, *input_eol;
+	sieve_number_t execute_timeout;
+
+	extname = strrchr(extname, '.');
+	i_assert(extname != NULL);
+	extname++;
+
+	bin_dir = sieve_setting_get
+		(svinst, t_strdup_printf("sieve_%s_bin_dir", extname));
+	socket_dir = sieve_setting_get
+		(svinst, t_strdup_printf("sieve_%s_socket_dir", extname));
+	input_eol = sieve_setting_get
+		(svinst, t_strdup_printf("sieve_%s_input_eol", extname));
+	
+	ext_config = i_new(struct sieve_extprograms_config, 1);
+	ext_config->execute_timeout = 
+		SIEVE_EXTPROGRAMS_DEFAULT_EXEC_TIMEOUT_SECS;
+
+	if ( bin_dir == NULL && socket_dir == NULL ) {
+		if ( svinst->debug ) {
+			sieve_sys_debug(svinst, "%s extension: "
+				"no bin or socket directory specified; extension is unconfigured "
+				"(both sieve_%s_bin_dir and sieve_%s_socket_dir are not set)",
+				sieve_extension_name(ext), extname, extname);
+		}
+	} else {
+		ext_config->bin_dir = i_strdup(bin_dir);
+		ext_config->socket_dir = i_strdup(socket_dir);
+
+		if (sieve_setting_get_duration_value
+			(svinst, t_strdup_printf("sieve_%s_exec_timeout", extname),
+				&execute_timeout)) {
+			ext_config->execute_timeout = execute_timeout;
+		}
+
+		ext_config->default_input_eol = SIEVE_EXTPROGRAMS_EOL_CRLF;
+		if (input_eol != NULL && strcasecmp(input_eol, "lf") == 0)
+			ext_config->default_input_eol = SIEVE_EXTPROGRAMS_EOL_LF;
+	}
+
+	if ( sieve_extension_is(ext, vnd_pipe_extension) ) 
+		ext_config->copy_ext = sieve_ext_copy_get_extension(ext->svinst);
+	if ( sieve_extension_is(ext, vnd_execute_extension) ) 
+		ext_config->var_ext = sieve_ext_variables_get_extension(ext->svinst);
+	return ext_config;
+}
+
+void sieve_extprograms_config_deinit
+(struct sieve_extprograms_config **ext_config)
+{
+	if ( *ext_config == NULL )
+		return;
+
+	i_free((*ext_config)->bin_dir);
+	i_free((*ext_config)->socket_dir);
+	i_free((*ext_config));
+
+	*ext_config = NULL;
+}
+
+/*
+ * Program name and arguments
+ */
+
+bool sieve_extprogram_name_is_valid(string_t *name)
+{
+	ARRAY_TYPE(unichars) uni_name;
+	unsigned int count, i;
+	const unichar_t *name_chars;
+	size_t namelen = str_len(name);
+
+	/* Check minimum length */
+	if ( namelen == 0 )
+		return FALSE;
+
+	/* Check worst-case maximum length */
+	if ( namelen > SIEVE_EXTPROGRAMS_MAX_PROGRAM_NAME_LEN * 4 )
+		return FALSE;
+
+	/* Intialize array for unicode characters */
+	t_array_init(&uni_name, namelen * 4);
+
+	/* Convert UTF-8 to UCS4/UTF-32 */
+	if ( uni_utf8_to_ucs4_n(str_data(name), namelen, &uni_name) < 0 )
+		return FALSE;
+	name_chars = array_get(&uni_name, &count);
+
+	/* Check true maximum length */
+	if ( count > SIEVE_EXTPROGRAMS_MAX_PROGRAM_NAME_LEN )
+		return FALSE;
+
+	/* Scan name for invalid characters
+	 *   FIXME: compliance with Net-Unicode Definition (Section 2 of
+	 *          RFC 5198) is not checked fully and no normalization
+	 *          is performed.
+	 */
+	for ( i = 0; i < count; i++ ) {
+
+		/* 0000-001F; [CONTROL CHARACTERS] */
+		if ( name_chars[i] <= 0x001f )
+			return FALSE;
+
+		/* 002F; SLASH */
+		if ( name_chars[i] == 0x002f )
+			return FALSE;
+
+		/* 007F; DELETE */
+		if ( name_chars[i] == 0x007f )
+			return FALSE;
+
+		/* 0080-009F; [CONTROL CHARACTERS] */
+		if ( name_chars[i] >= 0x0080 && name_chars[i] <= 0x009f )
+			return FALSE;
+
+		/* 00FF */
+		if ( name_chars[i] == 0x00ff )
+			return FALSE;
+
+		/* 2028; LINE SEPARATOR */
+		/* 2029; PARAGRAPH SEPARATOR */
+		if ( name_chars[i] == 0x2028 || name_chars[i] == 0x2029 )
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+bool sieve_extprogram_arg_is_valid(string_t *arg)
+{
+	const unsigned char *chars;
+	unsigned int i;
+
+	/* Check maximum length */
+	if ( str_len(arg) > SIEVE_EXTPROGRAMS_MAX_PROGRAM_ARG_LEN )
+		return FALSE;
+
+	/* Check invalid characters */
+	chars = str_data(arg);
+	for ( i = 0; i < str_len(arg); i++ ) {
+		/* 0010; CR */
+		if ( chars[i] == 0x0D )
+			return FALSE;
+
+		/* 0010; LF */
+		if ( chars[i] == 0x0A )
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+struct _arg_validate_context {
+	struct sieve_validator *valdtr;
+	struct sieve_command *cmd;
+};
+
+static int _arg_validate
+(void *context, struct sieve_ast_argument *item)
+{
+	struct _arg_validate_context *actx = (struct _arg_validate_context *) context;
+
+	if ( sieve_argument_is_string_literal(item) ) {
+		string_t *arg = sieve_ast_argument_str(item);
+
+		if ( !sieve_extprogram_arg_is_valid(arg) ) {
+			sieve_argument_validate_error(actx->valdtr, item,
+				"%s %s: specified external program argument `%s' is invalid",
+				sieve_command_identifier(actx->cmd), sieve_command_type_name(actx->cmd),
+				str_sanitize(str_c(arg), 128));
+
+			return -1;
+		}
+	}
+
+	return 1;
+}
+
+bool sieve_extprogram_command_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *arg = cmd->first_positional;
+	struct sieve_ast_argument *stritem;
+	struct _arg_validate_context actx;
+	string_t *program_name;
+
+	if ( arg == NULL ) {
+		sieve_command_validate_error(valdtr, cmd, 
+			"the %s %s expects at least one positional argument, but none was found", 
+			sieve_command_identifier(cmd), sieve_command_type_name(cmd));
+		return FALSE;
+	}
+
+	/* <program-name: string> argument */
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "program-name", 1, SAAT_STRING) ) {
+		return FALSE;
+	}
+	
+	if ( !sieve_validator_argument_activate(valdtr, cmd, arg, FALSE) )
+		return FALSE;
+
+	/* Variables are not allowed */
+	if ( !sieve_argument_is_string_literal(arg) ) {
+		sieve_argument_validate_error(valdtr, arg, 
+			"the %s %s requires a constant string "
+			"for its program-name argument",
+			sieve_command_identifier(cmd), sieve_command_type_name(cmd));
+		return FALSE;
+	}
+
+	/* Check program name */
+	program_name = sieve_ast_argument_str(arg);
+	if ( !sieve_extprogram_name_is_valid(program_name) ) {
+ 		sieve_argument_validate_error(valdtr, arg,
+			"%s %s: invalid program name '%s'",
+			sieve_command_identifier(cmd), sieve_command_type_name(cmd),
+			str_sanitize(str_c(program_name), 80));
+		return FALSE;
+	}
+
+	/* Optional <arguments: string-list> argument */
+
+	arg = sieve_ast_argument_next(arg);
+	if ( arg == NULL )
+		return TRUE;
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "arguments", 2, SAAT_STRING_LIST) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, cmd, arg, FALSE) )
+		return FALSE;
+
+	/* Check arguments */
+	actx.valdtr = valdtr;
+	actx.cmd = cmd;
+	stritem = arg;
+	if ( sieve_ast_stringlist_map
+		(&stritem, (void *)&actx, _arg_validate) <= 0 ) {
+		return FALSE;
+	}
+
+	if ( sieve_ast_argument_next(arg) != NULL ) {
+		sieve_command_validate_error(valdtr, cmd, 
+			"the %s %s expects at most two positional arguments, but more were found", 
+			sieve_command_identifier(cmd), sieve_command_type_name(cmd));
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+/*
+ * Common command operands
+ */
+
+int sieve_extprogram_command_read_operands
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+	string_t **pname_r, struct sieve_stringlist **args_list_r)
+{	
+	string_t *arg;
+	int ret;
+
+	/*
+	 * Read fixed operands
+	 */
+
+	if ( (ret=sieve_opr_string_read
+		(renv, address, "program-name", pname_r)) <= 0 )
+		return ret;
+
+	if ( (ret=sieve_opr_stringlist_read_ex
+		(renv, address, "arguments", TRUE, args_list_r)) <= 0 )
+		return ret;
+		
+	/*
+	 * Check operands
+	 */
+
+	arg = NULL;
+	while ( *args_list_r != NULL &&
+		(ret=sieve_stringlist_next_item(*args_list_r, &arg)) > 0 ) {
+		if ( !sieve_extprogram_arg_is_valid(arg) ) {
+			sieve_runtime_error(renv, NULL,
+				"specified :args item `%s' is invalid",
+				str_sanitize(str_c(arg), 128));
+			return SIEVE_EXEC_FAILURE;
+		}
+	}
+
+	if ( ret < 0 ) {
+		sieve_runtime_trace_error(renv, "invalid args-list item");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	return SIEVE_EXEC_OK;
+}
+
+/*
+ * Running external programs
+ */
+
+struct sieve_extprogram {
+	struct sieve_instance *svinst;
+	const struct sieve_extprograms_config *ext_config;
+
+	const struct sieve_script_env *scriptenv;
+	struct program_client_settings set;
+	struct program_client *program_client;
+};
+
+void sieve_extprogram_exec_error
+(struct sieve_error_handler *ehandler, const char *location,
+	const char *fmt, ...)
+{
+	char str[256];
+	struct tm *tm;
+	const char *timestamp;
+
+	tm = localtime(&ioloop_time);
+
+	timestamp =
+		( strftime(str, sizeof(str), " [%Y-%m-%d %H:%M:%S]", tm) > 0 ? str : "" );
+
+	va_list args;
+	va_start(args, fmt);
+
+	T_BEGIN {
+		sieve_error(ehandler, location,
+			"%s: refer to server log for more information.%s",
+			t_strdup_vprintf(fmt, args), timestamp);
+	} T_END;
+
+	va_end(args);
+}
+
+/* API */
+
+struct sieve_extprogram *sieve_extprogram_create
+(const struct sieve_extension *ext, const struct sieve_script_env *senv,
+	const struct sieve_message_data *msgdata, const char *action,
+	const char *program_name, const char * const *args,
+	enum sieve_error *error_r)
+{
+	struct sieve_instance *svinst = ext->svinst;
+	struct sieve_extprograms_config *ext_config =
+		(struct sieve_extprograms_config *) ext->context;
+	const struct smtp_address *sender, *recipient, *orig_recipient;
+	struct sieve_extprogram *sprog;
+	const char *path = NULL;
+	struct stat st;
+	bool fork = FALSE;
+	int ret;
+
+	if ( svinst->debug ) {
+		sieve_sys_debug(svinst, "action %s: "
+			"running program: %s", action, program_name);
+	}
+
+	if ( ext_config == NULL ||
+		(ext_config->bin_dir == NULL && ext_config->socket_dir == NULL) ) {
+		sieve_sys_error(svinst, "action %s: "
+			"failed to execute program `%s': "
+			"vnd.dovecot.%s extension is unconfigured", action, program_name, action);
+		*error_r = SIEVE_ERROR_NOT_FOUND;
+		return NULL;
+	}
+
+	/* Try socket first */
+	if ( ext_config->socket_dir != NULL ) {
+		path = t_strconcat(senv->user->set->base_dir, "/",
+			ext_config->socket_dir, "/", program_name, NULL);
+		if ( (ret=stat(path, &st)) < 0 ) {
+			switch ( errno ) {
+			case ENOENT:
+				if ( svinst->debug ) {
+					sieve_sys_debug(svinst, "action %s: "
+						"socket path `%s' for program `%s' not found",
+						action, path, program_name);
+				}
+				break;
+			case EACCES:
+				sieve_sys_error(svinst, "action %s: "
+					"failed to stat socket: %s", action, eacces_error_get("stat", path));
+				*error_r = SIEVE_ERROR_NO_PERMISSION;
+				return NULL;
+			default:
+				sieve_sys_error(svinst, "action %s: "
+					"failed to stat socket `%s': %m", action, path);
+				*error_r = SIEVE_ERROR_TEMP_FAILURE;
+				return NULL;
+			}
+			path = NULL;
+		} else if ( !S_ISSOCK(st.st_mode) ) {
+			sieve_sys_error(svinst, "action %s: "
+				"socket path `%s' for program `%s' is not a socket",
+				action, path, program_name);
+			*error_r = SIEVE_ERROR_NOT_POSSIBLE;
+			return NULL;
+		}
+	}
+		
+	/* Try executable next */
+	if ( path == NULL && ext_config->bin_dir != NULL ) {
+		fork = TRUE;
+		path = t_strconcat(ext_config->bin_dir, "/", program_name, NULL);
+		if ( (ret=stat(path, &st)) < 0 ) {
+			switch ( errno ) {
+			case ENOENT:
+				if ( svinst->debug ) {
+					sieve_sys_debug(svinst, "action %s: "
+						"executable path `%s' for program `%s' not found",
+						action, path, program_name);
+				}
+				*error_r = SIEVE_ERROR_NOT_FOUND;
+				break;
+			case EACCES:
+				sieve_sys_error(svinst, "action %s: "
+					"failed to stat program: %s", action, eacces_error_get("stat", path));
+				*error_r = SIEVE_ERROR_NO_PERMISSION;
+				break;
+			default:
+				sieve_sys_error(svinst, "action %s: "
+					"failed to stat program `%s': %m", action, path);
+				*error_r = SIEVE_ERROR_TEMP_FAILURE;
+				break;
+			}
+
+			return NULL;
+		} else if ( !S_ISREG(st.st_mode) ) {
+			sieve_sys_error(svinst, "action %s: "
+				"executable `%s' for program `%s' is not a regular file",
+				action, path, program_name);
+			*error_r = SIEVE_ERROR_NOT_POSSIBLE;
+			return NULL;
+		} else if ( (st.st_mode & S_IWOTH) != 0 ) {
+			sieve_sys_error(svinst, "action %s: "
+				"executable `%s' for program `%s' is world-writable",
+				action, path, program_name);
+			*error_r = SIEVE_ERROR_NO_PERMISSION;
+			return NULL;
+		}
+	}
+
+	/* None found ? */
+	if ( path == NULL ) {
+		sieve_sys_error(svinst, "action %s: "
+			"program `%s' not found", action, program_name);
+		*error_r = SIEVE_ERROR_NOT_FOUND;
+		return NULL;
+	}
+
+	sprog = i_new(struct sieve_extprogram, 1);
+	sprog->svinst = ext->svinst;
+	sprog->ext_config = ext_config;
+	sprog->scriptenv = senv;
+
+	sprog->set.client_connect_timeout_msecs =
+		SIEVE_EXTPROGRAMS_CONNECT_TIMEOUT_MSECS;
+	sprog->set.input_idle_timeout_msecs =
+		ext_config->execute_timeout * 1000;
+	restrict_access_init(&sprog->set.restrict_set);
+	if (senv->user->uid != 0)
+		sprog->set.restrict_set.uid = senv->user->uid;
+	if (senv->user->gid != 0)
+		sprog->set.restrict_set.gid = senv->user->gid;
+	sprog->set.debug = svinst->debug;
+
+	if ( fork ) {
+		sprog->program_client =
+			program_client_local_create(path, args, &sprog->set);
+	} else {
+		sprog->program_client =
+			program_client_unix_create(path, args, &sprog->set, FALSE);
+	}
+
+	if ( svinst->username != NULL )
+		program_client_set_env(sprog->program_client, "USER", svinst->username);
+	if ( svinst->home_dir != NULL )
+		program_client_set_env(sprog->program_client, "HOME", svinst->home_dir);
+	if ( svinst->hostname != NULL )
+		program_client_set_env(sprog->program_client, "HOST", svinst->hostname);
+
+	sender = msgdata->envelope.mail_from;
+	recipient = msgdata->envelope.rcpt_to;
+	orig_recipient = NULL;
+	if ( msgdata->envelope.rcpt_params != NULL )
+		orig_recipient = msgdata->envelope.rcpt_params->orcpt.addr;
+
+	if ( !smtp_address_isnull(sender) ) {
+		program_client_set_env(sprog->program_client, "SENDER",
+				       smtp_address_encode(sender));
+	}
+	if ( !smtp_address_isnull(recipient) ) {
+		program_client_set_env(sprog->program_client, "RECIPIENT",
+				       smtp_address_encode(recipient));
+	}
+	if ( !smtp_address_isnull(orig_recipient) ) {
+		program_client_set_env(sprog->program_client, "ORIG_RECIPIENT",
+				       smtp_address_encode(orig_recipient));
+	}
+
+	return sprog;
+}
+
+void sieve_extprogram_destroy(struct sieve_extprogram **_sprog)
+{
+	struct sieve_extprogram *sprog = *_sprog;
+
+	program_client_destroy(&sprog->program_client);
+	i_free(sprog);
+	*_sprog = NULL;
+}
+
+/* I/0 */
+
+void sieve_extprogram_set_output
+(struct sieve_extprogram *sprog, struct ostream *output)
+{
+	program_client_set_output(sprog->program_client, output);
+}
+
+void sieve_extprogram_set_input
+(struct sieve_extprogram *sprog, struct istream *input)
+{
+	switch (sprog->ext_config->default_input_eol) {
+	case SIEVE_EXTPROGRAMS_EOL_LF:
+		input = i_stream_create_lf(input);
+		break;
+	case SIEVE_EXTPROGRAMS_EOL_CRLF:
+		input = i_stream_create_crlf(input);
+		break;
+	default:
+		i_unreached();
+	}
+
+	program_client_set_input(sprog->program_client, input);
+
+	i_stream_unref(&input);
+}
+
+void sieve_extprogram_set_output_seekable
+(struct sieve_extprogram *sprog)
+{
+	string_t *prefix;
+	prefix = t_str_new(128);
+	mail_user_set_get_temp_prefix(prefix, sprog->scriptenv->user->set);
+
+	program_client_set_output_seekable(sprog->program_client, str_c(prefix));
+}
+
+struct istream *sieve_extprogram_get_output_seekable
+(struct sieve_extprogram *sprog)
+{
+	return program_client_get_output_seekable(sprog->program_client);
+}
+
+int sieve_extprogram_set_input_mail
+(struct sieve_extprogram *sprog, struct mail *mail)
+{
+	struct istream *input;
+
+	if (mail_get_stream(mail, NULL, NULL, &input) < 0)
+		return -1;
+
+	sieve_extprogram_set_input(sprog, input);
+	return 1;
+}
+
+int sieve_extprogram_run(struct sieve_extprogram *sprog)
+{
+	return program_client_run(sprog->program_client);
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/sieve-extprograms/sieve-extprograms-common.h
@@ -0,0 +1,107 @@
+#ifndef SIEVE_EXTPROGRAMS_COMMON_H
+#define SIEVE_EXTPROGRAMS_COMMON_H
+
+#include "sieve-common.h"
+
+/*
+ * Extension configuration
+ */
+
+enum sieve_extprograms_eol {
+	SIEVE_EXTPROGRAMS_EOL_CRLF = 0,
+	SIEVE_EXTPROGRAMS_EOL_LF
+};
+
+struct sieve_extprograms_config {
+	const struct sieve_extension *copy_ext;
+	const struct sieve_extension *var_ext;
+
+	char *socket_dir;
+	char *bin_dir;
+
+	enum sieve_extprograms_eol default_input_eol;
+
+	unsigned int execute_timeout;
+};
+
+struct sieve_extprograms_config *sieve_extprograms_config_init
+	(const struct sieve_extension *ext);
+void sieve_extprograms_config_deinit
+	(struct sieve_extprograms_config **ext_config);
+
+/*
+ * Extensions
+ */
+
+extern const struct sieve_extension_def vnd_pipe_extension;
+extern const struct sieve_extension_def vnd_filter_extension;
+extern const struct sieve_extension_def vnd_execute_extension;
+
+/* 
+ * Commands 
+ */
+
+extern const struct sieve_command_def cmd_pipe;
+extern const struct sieve_command_def cmd_filter;
+extern const struct sieve_command_def cmd_execute;
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def cmd_pipe_operation;
+extern const struct sieve_operation_def cmd_filter_operation;
+extern const struct sieve_operation_def cmd_execute_operation;
+
+/*
+ * Program name and arguments
+ */
+
+bool sieve_extprogram_arg_is_valid(string_t *arg);
+bool sieve_extprogram_name_is_valid(string_t *name);
+
+/*
+ * Command validation
+ */
+
+bool sieve_extprogram_command_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+
+/*
+ * Common command operands
+ */
+
+int sieve_extprogram_command_read_operands
+	(const struct sieve_runtime_env *renv, sieve_size_t *address,
+		string_t **pname_r, struct sieve_stringlist **args_list_r);
+
+/*
+ * Running external programs
+ */
+
+void sieve_extprogram_exec_error
+	(struct sieve_error_handler *ehandler, const char *location,
+		const char *fmt, ...) ATTR_FORMAT(3, 4);
+
+struct sieve_extprogram *sieve_extprogram_create
+	(const struct sieve_extension *ext, const struct sieve_script_env *senv,
+		const struct sieve_message_data *msgdata, const char *action,
+		const char *program_name, const char * const *args,
+		enum sieve_error *error_r);
+void sieve_extprogram_destroy(struct sieve_extprogram **_sprog);
+
+void sieve_extprogram_set_output
+	(struct sieve_extprogram *sprog, struct ostream *output);
+void sieve_extprogram_set_output_seekable
+	(struct sieve_extprogram *sprog);
+struct istream *sieve_extprogram_get_output_seekable
+	(struct sieve_extprogram *sprog);
+
+void sieve_extprogram_set_input
+	(struct sieve_extprogram *sprog, struct istream *input);
+int sieve_extprogram_set_input_mail
+	(struct sieve_extprogram *sprog, struct mail *mail);
+
+int sieve_extprogram_run(struct sieve_extprogram *sprog);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/sieve-extprograms/sieve-extprograms-plugin.c
@@ -0,0 +1,67 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-extensions.h"
+
+#include "sieve-extprograms-common.h"
+#include "sieve-extprograms-plugin.h"
+
+/*
+ * Sieve plugin interface
+ */
+
+struct _plugin_context {
+	const struct sieve_extension *ext_pipe;
+	const struct sieve_extension *ext_filter;
+	const struct sieve_extension *ext_execute;		
+};
+
+const char *sieve_extprograms_plugin_version = PIGEONHOLE_ABI_VERSION;
+
+void sieve_extprograms_plugin_load
+(struct sieve_instance *svinst, void **context)
+{
+	struct _plugin_context *pctx = i_new(struct _plugin_context, 1);
+
+	pctx->ext_pipe = sieve_extension_register
+		(svinst, &vnd_pipe_extension, FALSE);
+	pctx->ext_filter = sieve_extension_register
+		(svinst, &vnd_filter_extension, FALSE);
+	pctx->ext_execute = sieve_extension_register
+		(svinst, &vnd_execute_extension, FALSE);
+
+	if ( svinst->debug ) {
+		sieve_sys_debug(svinst, "Sieve Extprograms plugin for %s version %s loaded",
+			PIGEONHOLE_NAME, PIGEONHOLE_VERSION_FULL);
+	}
+
+	*context = (void *)pctx;
+}
+
+void sieve_extprograms_plugin_unload
+(struct sieve_instance *svinst ATTR_UNUSED, void *context)
+{
+	struct _plugin_context *pctx = (struct _plugin_context *)context;
+
+	sieve_extension_unregister(pctx->ext_pipe);
+	sieve_extension_unregister(pctx->ext_filter);
+	sieve_extension_unregister(pctx->ext_execute);
+
+	i_free(pctx);
+}
+
+/*
+ * Module interface
+ */
+
+void sieve_extprograms_plugin_init(void)
+{
+	/* Nothing */
+}
+
+void sieve_extprograms_plugin_deinit(void)
+{
+	/* Nothing */
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/plugins/sieve-extprograms/sieve-extprograms-plugin.h
@@ -0,0 +1,20 @@
+#ifndef SIEVE_EXTPROGRAMS_PLUGIN_H
+#define SIEVE_EXTPROGRAMS_PLUGIN_H
+
+/*
+ * Plugin interface
+ */
+
+void sieve_extprograms_plugin_load
+	(struct sieve_instance *svinst, void **context);
+void sieve_extprograms_plugin_unload
+	(struct sieve_instance *svinst, void *context);
+
+/*
+ * Module interface
+ */
+
+void sieve_extprograms_plugin_init(void);
+void sieve_extprograms_plugin_deinit(void);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/sieve-tools/Makefile.am
@@ -0,0 +1,59 @@
+bin_PROGRAMS = sievec sieve-dump sieve-test sieve-filter
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib-sieve \
+	-I$(top_srcdir)/src/lib-sieve-tool \
+	-I$(srcdir)/debug \
+	$(LIBDOVECOT_INCLUDE) \
+	$(LIBDOVECOT_SERVICE_INCLUDE)
+
+libs = \
+	$(top_builddir)/src/lib-sieve/libdovecot-sieve.la \
+	$(top_builddir)/src/lib-sieve-tool/libsieve-tool.la
+
+libs_ldadd = $(libs) $(LIBDOVECOT_STORAGE) $(LIBDOVECOT_LDA) $(LIBDOVECOT)
+libs_deps = $(libs) $(LIBDOVECOT_STORAGE_DEPS) $(LIBDOVECOT_LDA_DEPS) $(LIBDOVECOT_DEPS)
+
+# Sieve Compile Tool
+
+sievec_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS)
+sievec_LDFLAGS = -export-dynamic $(BINARY_LDFLAGS)
+sievec_LDADD = $(libs_ldadd)
+sievec_DEPENDENCIES = $(libs_deps)
+
+sievec_SOURCES = \
+	sievec.c
+
+# Sieve Dump Tool
+
+sieve_dump_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS)
+sieve_dump_LDFLAGS = -export-dynamic $(BINARY_LDFLAGS)
+sieve_dump_LDADD = $(libs_ldadd)
+sieve_dump_DEPENDENCIES = $(libs_deps)
+
+sieve_dump_SOURCES = \
+	sieve-dump.c
+
+# Sieve Test Tool
+
+sieve_test_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS)
+sieve_test_LDFLAGS = -export-dynamic $(BINARY_LDFLAGS)
+sieve_test_LDADD = $(libs_ldadd)
+sieve_test_DEPENDENCIES = $(libs_deps)
+
+sieve_test_SOURCES = \
+	sieve-test.c
+
+## Unfinished tools
+
+# Sieve Filter Tool
+
+sieve_filter_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS)
+sieve_filter_LDFLAGS = -export-dynamic $(BINARY_LDFLAGS)
+sieve_filter_LDADD = $(libs_ldadd)
+sieve_filter_DEPENDENCIES = $(libs_deps)
+
+sieve_filter_SOURCES = \
+	sieve-filter.c
+
+noinst_HEADERS =
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/sieve-tools/sieve-dump.c
@@ -0,0 +1,97 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "mail-storage-service.h"
+#include "mail-user.h"
+
+#include "sieve.h"
+#include "sieve-extensions.h"
+#include "sieve-tool.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sysexits.h>
+
+/*
+ * Print help
+ */
+
+static void print_help(void)
+{
+	printf(
+"Usage: sieve-dump [-c <config-file>] [-D] [-h] [-P <plugin>] [-x <extensions>]\n"
+"                  <sieve-binary> [<out-file>]\n"
+	);
+}
+
+/*
+ * Tool implementation
+ */
+
+int main(int argc, char **argv)
+{
+	struct sieve_instance *svinst;
+	struct sieve_binary *sbin;
+	const char *binfile, *outfile;
+	bool hexdump = FALSE;
+	int exit_status = EXIT_SUCCESS;
+	int c;
+
+	sieve_tool = sieve_tool_init("sieve-dump", &argc, &argv, "DhP:x:", FALSE);
+
+	outfile = NULL;
+
+	while ((c = sieve_tool_getopt(sieve_tool)) > 0) {
+		switch (c) {
+		case 'h':
+			/* produce hexdump */
+			hexdump = TRUE;
+			break;
+		default:
+			print_help();
+			i_fatal_status(EX_USAGE, "Unknown argument: %c", c);
+			break;
+		}
+	}
+
+	if ( optind < argc ) {
+		binfile = argv[optind++];
+	} else {
+		print_help();
+		i_fatal_status(EX_USAGE, "Missing <script-file> argument");
+	}
+
+	if ( optind < argc ) {
+		outfile = argv[optind++];
+	}
+
+	/* Finish tool initialization */
+	svinst = sieve_tool_init_finish(sieve_tool, FALSE, TRUE);
+
+        /* Enable debug extension */
+        sieve_enable_debug_extension(svinst);
+
+	/* Dump binary */
+	sbin = sieve_load(svinst, binfile, NULL);
+	if ( sbin != NULL ) {
+		sieve_tool_dump_binary_to(sbin, outfile == NULL ? "-" : outfile, hexdump);
+
+		sieve_close(&sbin);
+	} else {
+		i_error("failed to load binary: %s", binfile);
+		exit_status = EXIT_FAILURE;
+	}
+
+	sieve_tool_deinit(&sieve_tool);
+
+	return exit_status;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/sieve-tools/sieve-filter.c
@@ -0,0 +1,582 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "lib-signals.h"
+#include "ioloop.h"
+#include "env-util.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "ostream.h"
+#include "array.h"
+#include "mail-namespace.h"
+#include "mail-storage.h"
+#include "mail-search-build.h"
+
+#include "sieve.h"
+#include "sieve-extensions.h"
+#include "sieve-binary.h"
+
+#include "sieve-tool.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <sysexits.h>
+
+/*
+ * Print help
+ */
+
+static void print_help(void)
+{
+	printf(
+"Usage: sieve-filter [-c <config-file>] [-C] [-D] [-e] [-m <default-mailbox>]\n"
+"                    [-P <plugin>] [-q <output-mailbox>] [-Q <mail-command>]\n"
+"                    [-s <script-file>] [-u <user>] [-v] [-W] [-x <extensions>]\n"
+"                    <script-file> <source-mailbox> [<discard-action>]\n"
+	);
+}
+
+enum sieve_filter_discard_action {
+	SIEVE_FILTER_DACT_KEEP,        /* Keep discarded messages in source folder */
+	SIEVE_FILTER_DACT_MOVE,        /* Move discarded messages to Trash folder */
+	SIEVE_FILTER_DACT_DELETE,      /* Flag discarded messages as \DELETED */
+	SIEVE_FILTER_DACT_EXPUNGE      /* Expunge discarded messages */
+};
+
+struct sieve_filter_data {
+	enum sieve_filter_discard_action discard_action;
+	struct mailbox *move_mailbox;
+
+	struct sieve_script_env *senv;
+	struct sieve_binary *main_sbin;
+	struct sieve_error_handler *ehandler;
+
+	bool execute:1;
+	bool source_write:1;
+	bool default_move:1;
+};
+
+struct sieve_filter_context {
+	const struct sieve_filter_data *data;
+
+	struct mailbox_transaction_context *move_trans;
+
+	struct ostream *teststream;
+};
+
+static int filter_message
+(struct sieve_filter_context *sfctx, struct mail *mail)
+{
+	struct sieve_error_handler *ehandler = sfctx->data->ehandler;
+	struct sieve_script_env *senv = sfctx->data->senv;
+	struct sieve_exec_status estatus;
+	struct sieve_binary *sbin;
+	struct sieve_message_data msgdata;
+	bool execute = sfctx->data->execute;
+	bool source_write = sfctx->data->source_write;
+	const char *subject, *date;
+	uoff_t size = 0;
+	int ret;
+
+	/* Initialize execution status */
+	i_zero(&estatus);
+	senv->exec_status = &estatus;
+
+	/* Collect necessary message data */
+	i_zero(&msgdata);
+	msgdata.mail = mail;
+	msgdata.auth_user = senv->user->username;
+	(void)mail_get_first_header(mail, "Message-ID", &msgdata.id);
+
+	sieve_tool_get_envelope_data
+		(&msgdata, mail, NULL, NULL, NULL);
+
+	if ( mail_get_virtual_size(mail, &size) < 0 ) {
+		if ( mail->expunged )
+			return 1;
+
+		sieve_error(ehandler, NULL, "failed to obtain message size; "
+			"skipping this message (id=%s)",
+			( msgdata.id == NULL ? "none" : msgdata.id ));
+		return 0;
+	}
+
+	if ( mail_get_first_header(mail, "date", &date) <= 0 )
+		date = "";
+	if ( mail_get_first_header(mail, "subject", &subject) <= 0 )
+		subject = "";
+
+	/* Single script */
+	sbin = sfctx->data->main_sbin;
+
+	/* Execute script */
+	if ( execute ) {
+		struct sieve_error_handler *action_ehandler;
+
+		sieve_info(ehandler, NULL,
+			"filtering: [%s; %"PRIuUOFF_T" bytes] `%s'", date, size,
+			str_sanitize(subject, 40));
+
+		action_ehandler = sieve_prefix_ehandler_create
+			(ehandler, NULL, t_strdup_printf("msgid=%s",
+				( msgdata.id == NULL ? "unspecified" : msgdata.id )));
+		ret = sieve_execute(sbin, &msgdata, senv,
+				ehandler, action_ehandler, 0, NULL);
+		sieve_error_handler_unref(&action_ehandler);
+
+	} else {
+		o_stream_nsend_str(sfctx->teststream,
+			t_strdup_printf(">> Filtering message:\n\n"
+				"  ID:      %s\n"
+			  "  Date:    %s\n"
+        "  Size:    %"PRIuUOFF_T" bytes\n"
+				"  Subject: %s\n", ( msgdata.id == NULL ? "none" : msgdata.id ),
+				date, size, str_sanitize(subject, 40)));
+
+		ret = sieve_test
+			(sbin, &msgdata, senv, ehandler, sfctx->teststream, 0, NULL);
+	}
+
+	/* Handle message in source folder */
+	if ( ret > 0 ) {
+		struct mailbox *move_box = sfctx->data->move_mailbox;
+		enum sieve_filter_discard_action discard_action =
+			sfctx->data->discard_action;
+
+		if ( !source_write ) {
+			/* READ-ONLY; Do nothing */
+
+		} else if ( estatus.keep_original  ) {
+			/* Explicitly `stored' in source box; just keep it there */
+			sieve_info(ehandler, NULL, "message kept in source mailbox");
+
+		} else if ( estatus.message_saved ) {
+			sieve_info(ehandler, NULL,
+				"message expunged from source mailbox upon successful move");
+
+			if ( execute )
+				mail_expunge(mail);
+
+		} else {
+
+			switch ( discard_action ) {
+			/* Leave it there */
+			case SIEVE_FILTER_DACT_KEEP:
+				sieve_info(ehandler, NULL, "message left in source mailbox");
+				break;
+			/* Move message to indicated folder */
+			case SIEVE_FILTER_DACT_MOVE:
+				sieve_info(ehandler, NULL,
+					"message in source mailbox moved to mailbox '%s'",
+					mailbox_get_name(move_box));
+
+				if ( execute && move_box != NULL ) {
+					struct mailbox_transaction_context *t = sfctx->move_trans;
+					struct mail_save_context *save_ctx;
+
+					save_ctx = mailbox_save_alloc(t);
+
+					if ( mailbox_copy(&save_ctx, mail) < 0 ) {
+						enum mail_error error;
+						const char *errstr;
+
+						errstr = mail_storage_get_last_error
+							(mailbox_get_storage(move_box), &error);
+
+						sieve_error(ehandler, NULL,
+							"failed to move message to mailbox %s: %s",
+							mailbox_get_name(move_box), errstr);
+						return -1;
+				    }
+
+					mail_expunge(mail);
+				}
+				break;
+			/* Flag message as \DELETED */
+			case SIEVE_FILTER_DACT_DELETE:
+				sieve_info(ehandler, NULL, "message flagged as deleted in source mailbox");
+				if ( execute )
+					mail_update_flags(mail, MODIFY_ADD, MAIL_DELETED);
+				break;
+			/* Expunge the message immediately */
+			case SIEVE_FILTER_DACT_EXPUNGE:
+				sieve_info(ehandler, NULL, "message expunged from source mailbox");
+				if ( execute )
+					mail_expunge(mail);
+				break;
+			/* Unknown */
+			default:
+				i_unreached();
+				break;
+			}
+		}
+	}
+
+	switch ( ret ) {
+	case SIEVE_EXEC_OK:
+		break;
+	case SIEVE_EXEC_BIN_CORRUPT:
+		sieve_error(ehandler, NULL, "sieve script binary is corrupt");
+		return -1;
+	case SIEVE_EXEC_FAILURE:
+	case SIEVE_EXEC_TEMP_FAILURE:
+		if ( source_write && execute && sfctx->data->default_move &&
+			!estatus.keep_original &&	estatus.message_saved ) {
+			/* The implicit keep action moved message to default mailbox, so
+			   the source message still needs to be expunged */
+			sieve_error(ehandler, NULL,
+				"sieve script execution failed for this message; "
+				"message moved to default mailbox");
+			mail_expunge(mail);
+			return 0;
+		}
+		/* Fall through */
+	case SIEVE_EXEC_KEEP_FAILED:
+		sieve_error(ehandler, NULL,
+			"sieve script execution failed for this message; "
+			"message left in source mailbox");
+		return 0;
+	}
+
+	return 1;
+}
+
+/* FIXME: introduce this into Dovecot */
+static void mail_search_build_add_flags
+(struct mail_search_args *args, enum mail_flags flags, bool not)
+{
+	struct mail_search_arg *arg;
+
+	arg = p_new(args->pool, struct mail_search_arg, 1);
+	arg->type = SEARCH_FLAGS;
+	arg->value.flags = flags;
+	arg->match_not = not;
+
+	arg->next = args->args;
+	args->args = arg;
+}
+
+static int filter_mailbox
+(const struct sieve_filter_data *sfdata, struct mailbox *src_box)
+{
+	struct sieve_filter_context sfctx;
+	struct mailbox *move_box = sfdata->move_mailbox;
+	struct sieve_error_handler *ehandler = sfdata->ehandler;
+	struct mail_search_args *search_args;
+	struct mailbox_transaction_context *t;
+	struct mail_search_context *search_ctx;
+	struct mail *mail;
+	int ret = 1;
+
+	/* Sync source mailbox */
+
+	if ( mailbox_sync(src_box, MAILBOX_SYNC_FLAG_FULL_READ) < 0 ) {
+		sieve_error(ehandler, NULL, "failed to sync source mailbox");
+		return -1;
+	}
+
+	/* Initialize */
+
+	i_zero(&sfctx);
+	sfctx.data = sfdata;
+
+	/* Create test stream */
+	if ( !sfdata->execute ) {
+		sfctx.teststream = o_stream_create_fd(1, 0);
+		o_stream_set_no_error_handling(sfctx.teststream, TRUE);
+	}
+
+	/* Start move mailbox transaction */
+
+	if ( move_box != NULL ) {
+		sfctx.move_trans = mailbox_transaction_begin
+			(move_box, MAILBOX_TRANSACTION_FLAG_EXTERNAL,
+			 "sieve_filter_data move_box");
+	}
+
+	/* Search non-deleted messages in the source folder */
+
+	search_args = mail_search_build_init();
+	mail_search_build_add_flags(search_args, MAIL_DELETED, TRUE);
+
+	t = mailbox_transaction_begin(src_box, 0,
+				      "sieve_filter_data src_box");
+	search_ctx = mailbox_search_init(t, search_args, NULL, 0, NULL);
+	mail_search_args_unref(&search_args);
+
+	/* Iterate through all requested messages */
+
+	while ( ret >= 0 && mailbox_search_next(search_ctx, &mail) ) {
+		ret = filter_message(&sfctx, mail);
+	}
+
+	/* Cleanup */
+
+	if ( mailbox_search_deinit(&search_ctx) < 0 ) {
+		ret = -1;
+	}
+
+	if ( sfctx.move_trans != NULL ) {
+		if ( mailbox_transaction_commit(&sfctx.move_trans) < 0 ) {
+			ret = -1;
+		}
+	}
+
+	if ( mailbox_transaction_commit(&t) < 0 ) {
+		ret = -1;
+	}
+
+	if ( sfctx.teststream != NULL )
+		o_stream_destroy(&sfctx.teststream);
+
+	if ( ret < 0 ) return ret;
+
+	/* Sync mailbox */
+
+	if ( sfdata->execute ) {
+		if ( mailbox_sync(src_box, MAILBOX_SYNC_FLAG_FULL_WRITE) < 0 ) {
+			sieve_error(ehandler, NULL, "failed to sync source mailbox");
+			return -1;
+		}
+	}
+
+	return ret;
+}
+
+/*
+ * Tool implementation
+ */
+
+int main(int argc, char **argv)
+{
+	struct sieve_instance *svinst;
+	ARRAY_TYPE (const_string) scriptfiles;
+	const char *scriptfile,	*src_mailbox, *dst_mailbox, *move_mailbox;
+	struct sieve_filter_data sfdata;
+	enum sieve_filter_discard_action discard_action = SIEVE_FILTER_DACT_KEEP;
+	struct mail_user *mail_user;
+	struct sieve_binary *main_sbin;
+	struct sieve_script_env scriptenv;
+	struct sieve_error_handler *ehandler;
+	bool force_compile, execute, source_write, verbose, default_move;
+	struct mail_namespace *ns;
+	struct mailbox *src_box = NULL, *move_box = NULL;
+	enum mailbox_flags open_flags = MAILBOX_FLAG_IGNORE_ACLS;
+	enum mail_error error;
+	const char *errstr;
+	int c;
+
+	sieve_tool = sieve_tool_init("sieve-filter", &argc, &argv,
+		"m:s:x:P:u:q:Q:DCevW", FALSE);
+
+	t_array_init(&scriptfiles, 16);
+
+	/* Parse arguments */
+	dst_mailbox = move_mailbox = NULL;
+	force_compile = execute = source_write = default_move = FALSE;
+	verbose = FALSE;	
+	while ((c = sieve_tool_getopt(sieve_tool)) > 0) {
+		switch (c) {
+		case 'm':
+			/* default mailbox (keep box) */
+			dst_mailbox = optarg;
+			break;
+		case 's':
+			/* scriptfile executed before main script */
+			{
+				const char *file;
+
+				file = t_strdup(optarg);
+				array_append(&scriptfiles, &file, 1);
+
+				/* FIXME: */
+				i_fatal_status(EX_USAGE,
+					"The -s argument is currently NOT IMPLEMENTED");
+			}
+			break;
+		case 'q':
+			i_fatal_status(EX_USAGE,
+				"The -q argument is currently NOT IMPLEMENTED");
+			break;
+		case 'Q':
+			i_fatal_status(EX_USAGE,
+				"The -Q argument is currently NOT IMPLEMENTED");
+			break;
+		case 'e':
+			/* execution mode */
+			execute = TRUE;
+			break;
+		case 'C':
+			/* force script compile */
+			force_compile = TRUE;
+			break;
+		case 'W':
+			/* enable source mailbox write */
+			source_write = TRUE;
+			break;
+		case 'v':
+			/* enable verbose output */
+			verbose = TRUE;
+			break;
+		default:
+			/* unrecognized option */
+			print_help();
+			i_fatal_status(EX_USAGE, "Unknown argument: %c", c);
+			break;
+		}
+	}
+
+	/* Script file argument */
+	if ( optind < argc ) {
+		scriptfile = t_strdup(argv[optind++]);
+	} else {
+		print_help();
+		i_fatal_status(EX_USAGE, "Missing <script-file> argument");
+	}
+
+	/* Source mailbox argument */
+	if ( optind < argc ) {
+		src_mailbox = t_strdup(argv[optind++]);
+	} else {
+		print_help();
+		i_fatal_status(EX_USAGE, "Missing <source-mailbox> argument");
+	}
+
+	/* Source action argument */
+	if ( optind < argc ) {
+		const char *srcact = argv[optind++];
+
+		if ( strcmp(srcact, "keep") == 0 ) {
+			discard_action = SIEVE_FILTER_DACT_KEEP;
+		} else if ( strcmp(srcact, "move") == 0 ) {
+			discard_action = SIEVE_FILTER_DACT_MOVE;
+			if ( optind < argc ) {
+				move_mailbox = t_strdup(argv[optind++]);
+			} else {
+				print_help();
+				i_fatal_status(EX_USAGE,
+					"Invalid <discard-action> argument: "
+					"the `move' action requires mailbox argument");
+			}
+		} else if ( strcmp(srcact, "delete") == 0 ) {
+			discard_action = SIEVE_FILTER_DACT_DELETE;
+		} else if ( strcmp(srcact, "expunge") == 0 ) {
+			discard_action = SIEVE_FILTER_DACT_EXPUNGE;
+		} else {
+			print_help();
+			i_fatal_status(EX_USAGE, "Invalid <discard-action> argument");
+		}
+	}
+
+	if ( optind != argc ) {
+		print_help();
+		i_fatal_status(EX_USAGE, "Unknown argument: %s", argv[optind]);
+	}
+
+	if ( dst_mailbox == NULL ) {
+		dst_mailbox = src_mailbox;
+	} else {
+		/* The (implicit) keep action will move the message */
+		default_move = TRUE;
+	}
+
+	/* Finish tool initialization */
+	svinst = sieve_tool_init_finish(sieve_tool, TRUE, FALSE);
+
+	/* Enable debug extension */
+	sieve_enable_debug_extension(svinst);
+
+	/* Create error handler */
+	ehandler = sieve_stderr_ehandler_create(svinst, 0);
+	sieve_system_ehandler_set(ehandler);
+	sieve_error_handler_accept_infolog(ehandler, verbose);
+	sieve_error_handler_accept_debuglog(ehandler, svinst->debug);
+
+	/* Compile main sieve script */
+	if ( force_compile ) {
+		main_sbin = sieve_tool_script_compile(svinst, scriptfile, NULL);
+		if ( main_sbin != NULL )
+			(void) sieve_save(main_sbin, TRUE, NULL);
+	} else {
+		main_sbin = sieve_tool_script_open(svinst, scriptfile);
+	}
+
+	/* Initialize mail user */
+	mail_user = sieve_tool_get_mail_user(sieve_tool);
+
+	/* Open the source mailbox */
+
+	ns = mail_namespace_find(mail_user->namespaces, src_mailbox);
+	if ( ns == NULL )
+		i_fatal("Unknown namespace for source mailbox '%s'", src_mailbox);
+
+	if ( !source_write || !execute )
+		open_flags |= MAILBOX_FLAG_READONLY;
+
+	src_box = mailbox_alloc(ns->list, src_mailbox, open_flags);
+	if ( mailbox_open(src_box) < 0 ) {
+		i_fatal("Couldn't open source mailbox '%s': %s",
+			src_mailbox, mailbox_get_last_error(src_box, &error));
+	}
+
+	/* Open move box if necessary */
+
+	if ( execute && discard_action == SIEVE_FILTER_DACT_MOVE &&
+		move_mailbox != NULL ) {
+		ns = mail_namespace_find(mail_user->namespaces, move_mailbox);
+		if ( ns == NULL )
+			i_fatal("Unknown namespace for mailbox '%s'", move_mailbox);
+
+		move_box = mailbox_alloc(ns->list, move_mailbox, open_flags);
+		if ( mailbox_open(move_box) < 0 ) {
+			i_fatal("Couldn't open mailbox '%s': %s",
+				move_mailbox, mailbox_get_last_error(move_box, &error));
+		}
+
+		if ( mailbox_backends_equal(src_box, move_box) ) {
+			i_fatal("Source mailbox and mailbox for move action are identical.");
+		}
+	}
+
+	/* Compose script environment */
+	if (sieve_script_env_init(&scriptenv, mail_user, &errstr) < 0)
+		i_fatal("Failed to initialize script execution: %s", errstr);
+	scriptenv.mailbox_autocreate = FALSE;
+	scriptenv.default_mailbox = dst_mailbox;
+
+	/* Compose filter context */
+	i_zero(&sfdata);
+	sfdata.senv = &scriptenv;
+	sfdata.discard_action = discard_action;
+	sfdata.move_mailbox = move_box;
+	sfdata.main_sbin = main_sbin;
+	sfdata.ehandler = ehandler;
+	sfdata.execute = execute;
+	sfdata.source_write = source_write;
+	sfdata.default_move = default_move;
+
+	/* Apply Sieve filter to all messages found */
+	(void) filter_mailbox(&sfdata, src_box);
+
+	/* Close the source mailbox */
+	if ( src_box != NULL )
+		mailbox_free(&src_box);
+
+	/* Close the move mailbox */
+	if ( move_box != NULL )
+		mailbox_free(&move_box);
+
+	/* Close the script binary */
+	if ( main_sbin != NULL )
+		sieve_close(&main_sbin);
+
+	/* Cleanup error handler */
+	sieve_error_handler_unref(&ehandler);
+
+	sieve_tool_deinit(&sieve_tool);
+
+	return 0;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/sieve-tools/sieve-test.c
@@ -0,0 +1,462 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "lib-signals.h"
+#include "ioloop.h"
+#include "env-util.h"
+#include "str.h"
+#include "ostream.h"
+#include "array.h"
+#include "mail-namespace.h"
+#include "mail-storage.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "mail-storage-service.h"
+
+#include "sieve.h"
+#include "sieve-binary.h"
+#include "sieve-extensions.h"
+
+#include "sieve-tool.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <sysexits.h>
+
+
+/*
+ * Configuration
+ */
+
+#define DEFAULT_SENDMAIL_PATH "/usr/lib/sendmail"
+
+/*
+ * Print help
+ */
+
+static void print_help(void)
+{
+	printf(
+"Usage: sieve-test [-a <orig-recipient-address] [-c <config-file>]\n"
+"                  [-C] [-D] [-d <dump-filename>] [-e]\n"
+"                  [-f <envelope-sender>] [-l <mail-location>]\n"
+"                  [-m <default-mailbox>] [-P <plugin>]\n"
+"                  [-r <recipient-address>] [-s <script-file>]\n"
+"                  [-t <trace-file>] [-T <trace-option>] [-x <extensions>]\n"
+"                  <script-file> <mail-file>\n"
+	);
+}
+
+/*
+ * Dummy SMTP session
+ */
+
+static void *sieve_smtp_start
+(const struct sieve_script_env *senv ATTR_UNUSED,
+	const struct smtp_address *mail_from)
+{
+	struct ostream *output;
+
+	i_info("sending message from <%s>:",
+		smtp_address_encode(mail_from));
+
+	output = o_stream_create_fd(STDOUT_FILENO, (size_t)-1);
+	o_stream_set_no_error_handling(output, TRUE);
+	return (void*)output;
+}
+
+static void sieve_smtp_add_rcpt
+(const struct sieve_script_env *senv ATTR_UNUSED,
+	void *handle ATTR_UNUSED,
+	const struct smtp_address *rcpt_to)
+{
+	printf("\nRECIPIENT: %s\n",
+		smtp_address_encode(rcpt_to));
+}
+
+static struct ostream *sieve_smtp_send
+(const struct sieve_script_env *senv ATTR_UNUSED,
+	void *handle)
+{
+	printf("START MESSAGE:\n");
+
+	return (struct ostream *)handle;
+}
+
+static void sieve_smtp_abort
+(const struct sieve_script_env *senv ATTR_UNUSED,
+	void *handle)
+{
+	struct ostream *output = (struct ostream *)handle;
+
+	printf("#### ABORT MESSAGE ####\n\n");
+	o_stream_unref(&output);
+}
+
+static int sieve_smtp_finish
+(const struct sieve_script_env *senv ATTR_UNUSED,
+	void *handle, const char **error_r ATTR_UNUSED)
+{
+	struct ostream *output = (struct ostream *)handle;
+
+	printf("END MESSAGE\n\n");
+	o_stream_unref(&output);
+	return 1;
+}
+
+/*
+ * Dummy duplicate check implementation
+ */
+
+static bool duplicate_check
+(const struct sieve_script_env *senv, const void *id ATTR_UNUSED,
+	size_t id_size ATTR_UNUSED)
+{
+	i_info("checked duplicate for user %s.\n", senv->user->username);
+	return 0;
+}
+
+static void duplicate_mark
+(const struct sieve_script_env *senv, const void *id ATTR_UNUSED,
+	size_t id_size ATTR_UNUSED, time_t time ATTR_UNUSED)
+{
+	i_info("marked duplicate for user %s.\n", senv->user->username);
+}
+
+/*
+ * Tool implementation
+ */
+
+int main(int argc, char **argv)
+{
+	struct sieve_instance *svinst;
+	ARRAY_TYPE (const_string) scriptfiles;
+	const char *scriptfile, *mailbox, *dumpfile, *tracefile, *mailfile,
+		*mailloc, *errstr;
+	struct smtp_address *rcpt_to, *final_rcpt_to, *mail_from;
+	struct sieve_trace_config trace_config;
+	struct mail *mail;
+	struct sieve_binary *main_sbin, *sbin = NULL;
+	struct sieve_message_data msgdata;
+	struct sieve_script_env scriptenv;
+	struct sieve_exec_status estatus;
+	struct sieve_error_handler *ehandler, *action_ehandler;
+	struct ostream *teststream = NULL;
+	struct sieve_trace_log *trace_log = NULL;
+	bool force_compile = FALSE, execute = FALSE;
+	int exit_status = EXIT_SUCCESS;
+	int ret, c;
+
+	sieve_tool = sieve_tool_init
+		("sieve-test", &argc, &argv, "r:a:f:m:d:l:s:eCt:T:DP:x:u:", FALSE);
+
+	ehandler = action_ehandler = NULL;
+	t_array_init(&scriptfiles, 16);
+
+	/* Parse arguments */
+	mailbox = dumpfile = tracefile = mailloc = NULL;
+	mail_from = final_rcpt_to = rcpt_to = NULL;
+	i_zero(&trace_config);
+	trace_config.level = SIEVE_TRLVL_ACTIONS;
+	while ((c = sieve_tool_getopt(sieve_tool)) > 0) {
+		switch (c) {
+		case 'r':
+			/* final recipient address */
+			if (smtp_address_parse_mailbox(pool_datastack_create(), optarg,
+				SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART,
+				&final_rcpt_to, &errstr) < 0) {
+				i_fatal("Invalid -r parameter: %s", errstr);
+			}
+			break;
+		case 'a':
+			/* original recipient address */
+			if (smtp_address_parse_mailbox(pool_datastack_create(), optarg,
+				SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART,
+				&rcpt_to, &errstr) < 0) {
+				i_fatal("Invalid -a parameter: %s", errstr);
+			}
+			break;
+		case 'f':
+			/* envelope sender address */
+			if (smtp_address_parse_mailbox(pool_datastack_create(), optarg,
+				0, &mail_from, &errstr) < 0) {
+				i_fatal("Invalid -f parameter: %s", errstr);
+			}
+			break;
+		case 'm':
+			/* default mailbox (keep box) */
+			mailbox = optarg;
+			break;
+		case 'l':
+			/* mail location */
+			mailloc = optarg;
+			break;
+		case 't':
+			/* trace file */
+			tracefile = optarg;
+			break;
+			/* trace options */
+		case 'T':
+			sieve_tool_parse_trace_option(&trace_config, optarg);
+			break;
+		case 'd':
+			/* dump file */
+			dumpfile = optarg;
+			break;
+		case 's':
+			/* scriptfile executed before main script */
+			{
+				const char *file;
+
+				file = t_strdup(optarg);
+				array_append(&scriptfiles, &file, 1);
+			}
+			break;
+			/* execution mode */
+		case 'e':
+			execute = TRUE;
+			break;
+			/* force script compile */
+		case 'C':
+			force_compile = TRUE;
+			break;
+		default:
+			/* unrecognized option */
+			print_help();
+			i_fatal_status(EX_USAGE, "Unknown argument: %c", c);
+			break;
+		}
+	}
+
+	if ( optind < argc ) {
+		scriptfile = argv[optind++];
+	} else {
+		print_help();
+		i_fatal_status(EX_USAGE, "Missing <script-file> argument");
+	}
+
+	if ( optind < argc ) {
+		mailfile = argv[optind++];
+	} else {
+		print_help();
+		i_fatal_status(EX_USAGE, "Missing <mail-file> argument");
+	}
+
+	if (optind != argc) {
+		print_help();
+		i_fatal_status(EX_USAGE, "Unknown argument: %s", argv[optind]);
+	}
+
+	/* Finish tool initialization */
+	svinst = sieve_tool_init_finish(sieve_tool, mailloc == NULL, FALSE);
+
+	/* Enable debug extension */
+	sieve_enable_debug_extension(svinst);
+
+	/* Create error handler */
+	ehandler = sieve_stderr_ehandler_create(svinst, 0);
+	sieve_system_ehandler_set(ehandler);
+	sieve_error_handler_accept_infolog(ehandler, TRUE);
+	sieve_error_handler_accept_debuglog(ehandler, svinst->debug);
+
+	/* Compile main sieve script */
+	if ( force_compile ) {
+		main_sbin = sieve_tool_script_compile(svinst, scriptfile, NULL);
+		if ( main_sbin != NULL )
+			(void) sieve_save(main_sbin, TRUE, NULL);
+	} else {
+		main_sbin = sieve_tool_script_open(svinst, scriptfile);
+	}
+
+	if ( main_sbin == NULL ) {
+		exit_status = EXIT_FAILURE;
+	} else {
+		/* Dump script */
+		sieve_tool_dump_binary_to(main_sbin, dumpfile, FALSE);
+
+		/* Obtain mail namespaces from -l argument */
+		if ( mailloc != NULL ) {
+			sieve_tool_init_mail_user(sieve_tool, mailloc);
+		}
+
+		/* Initialize raw mail object */
+		mail = sieve_tool_open_file_as_mail(sieve_tool, mailfile);
+
+		if ( mailbox == NULL )
+			mailbox = "INBOX";
+
+		/* Collect necessary message data */
+		i_zero(&msgdata);
+		msgdata.mail = mail;
+		msgdata.auth_user = sieve_tool_get_username(sieve_tool);
+		(void)mail_get_first_header(mail, "Message-ID", &msgdata.id);
+
+		sieve_tool_get_envelope_data(&msgdata, mail,
+			mail_from, rcpt_to, final_rcpt_to);
+
+		/* Create streams for test and trace output */
+
+		if ( !execute ) {
+			action_ehandler = NULL;
+			teststream = o_stream_create_fd(1, 0);
+			o_stream_set_no_error_handling(teststream, TRUE);
+		} else {
+			action_ehandler = sieve_prefix_ehandler_create
+				(ehandler, NULL, t_strdup_printf("msgid=%s",
+					( msgdata.id == NULL ? "unspecified" : msgdata.id )));
+		}
+
+		if ( tracefile != NULL ) {
+			(void)sieve_trace_log_create(svinst,
+				(strcmp(tracefile, "-") == 0 ? NULL : tracefile),
+				&trace_log);
+		}
+
+		/* Compose script environment */
+		if (sieve_script_env_init(&scriptenv,
+			sieve_tool_get_mail_user(sieve_tool), &errstr) < 0)
+			i_fatal("Failed to initialize script execution: %s", errstr);
+
+		scriptenv.default_mailbox = mailbox;
+		scriptenv.smtp_start = sieve_smtp_start;
+		scriptenv.smtp_add_rcpt = sieve_smtp_add_rcpt;
+		scriptenv.smtp_send = sieve_smtp_send;
+		scriptenv.smtp_abort = sieve_smtp_abort;
+		scriptenv.smtp_finish = sieve_smtp_finish;
+		scriptenv.duplicate_mark = duplicate_mark;
+		scriptenv.duplicate_check = duplicate_check;
+		scriptenv.trace_log = trace_log;
+		scriptenv.trace_config = trace_config;
+
+		i_zero(&estatus);
+		scriptenv.exec_status = &estatus;
+
+		/* Run the test */
+		ret = 1;
+		if ( array_count(&scriptfiles) == 0 ) {
+			/* Single script */
+			sbin = main_sbin;
+			main_sbin = NULL;
+
+			/* Execute/Test script */
+			if ( execute ) {
+				ret = sieve_execute(sbin, &msgdata, &scriptenv,
+					ehandler, action_ehandler, 0, NULL);
+			} else {
+				ret = sieve_test(sbin, &msgdata, &scriptenv,
+					ehandler, teststream, 0, NULL);
+			}
+		} else {
+			/* Multiple scripts */
+			const char *const *sfiles;
+			unsigned int i, count;
+			struct sieve_multiscript *mscript;
+			bool more = TRUE;
+			int result;
+
+			if ( execute )
+				mscript = sieve_multiscript_start_execute
+					(svinst, &msgdata, &scriptenv);
+			else
+				mscript = sieve_multiscript_start_test
+					(svinst, &msgdata, &scriptenv, teststream);
+
+			/* Execute scripts sequentially */
+			sfiles = array_get(&scriptfiles, &count);
+			for ( i = 0; i < count && more; i++ ) {
+				if ( teststream != NULL )
+					o_stream_nsend_str(teststream,
+						t_strdup_printf("\n## Executing script: %s\n", sfiles[i]));
+
+				/* Close previous script */
+				if ( sbin != NULL )
+					sieve_close(&sbin);
+
+				/* Compile sieve script */
+				if ( force_compile ) {
+					sbin = sieve_tool_script_compile(svinst, sfiles[i], sfiles[i]);
+					if ( sbin != NULL )
+						(void) sieve_save(sbin, FALSE, NULL);
+				} else {
+					sbin = sieve_tool_script_open(svinst, sfiles[i]);
+				}
+
+				if ( sbin == NULL ) {
+					ret = SIEVE_EXEC_FAILURE;
+					break;
+				}
+
+				/* Execute/Test script */
+				more = sieve_multiscript_run(mscript, sbin,
+					ehandler, action_ehandler, 0);
+			}
+
+			/* Execute/Test main script */
+			if ( more && ret > 0 ) {
+				if ( teststream != NULL )
+					o_stream_nsend_str(teststream,
+						t_strdup_printf("## Executing script: %s\n", scriptfile));
+
+				/* Close previous script */
+				if ( sbin != NULL )
+					sieve_close(&sbin);
+
+				sbin = main_sbin;
+				main_sbin = NULL;
+
+				(void)sieve_multiscript_run(mscript, sbin,
+					ehandler, ehandler, 0);
+			}
+
+			result = sieve_multiscript_finish(&mscript, ehandler, 0, NULL);
+
+			ret = ret > 0 ? result : ret;
+		}
+
+		/* Run */
+		switch ( ret ) {
+		case SIEVE_EXEC_OK:
+			i_info("final result: success");
+			break;
+		case SIEVE_EXEC_BIN_CORRUPT:
+			i_info("corrupt binary deleted.");
+			i_unlink_if_exists(sieve_binary_path(sbin));
+			/* fall through */
+		case SIEVE_EXEC_FAILURE:
+			i_info("final result: failed; resolved with successful implicit keep");
+			exit_status = EXIT_FAILURE;
+			break;
+		case SIEVE_EXEC_TEMP_FAILURE:
+			i_info("final result: temporary failure");
+			exit_status = EXIT_FAILURE;
+			break;
+		case SIEVE_EXEC_KEEP_FAILED:
+			i_info("final result: utter failure");
+			exit_status = EXIT_FAILURE;
+			break;
+		}
+
+		if ( teststream != NULL )
+			o_stream_destroy(&teststream);
+		if ( trace_log != NULL )
+			sieve_trace_log_free(&trace_log);
+
+		/* Cleanup remaining binaries */
+		if ( sbin != NULL )
+			sieve_close(&sbin);
+		if ( main_sbin != NULL )
+			sieve_close(&main_sbin);
+	}
+
+	/* Cleanup error handler */
+	if (action_ehandler != NULL)
+		sieve_error_handler_unref(&action_ehandler);
+	sieve_error_handler_unref(&ehandler);
+
+	sieve_tool_deinit(&sieve_tool);
+
+	return exit_status;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/sieve-tools/sievec.c
@@ -0,0 +1,157 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "mail-storage-service.h"
+#include "mail-user.h"
+
+#include "sieve.h"
+#include "sieve-extensions.h"
+#include "sieve-script.h"
+#include "sieve-tool.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <dirent.h>
+#include <sysexits.h>
+
+/*
+ * Print help
+ */
+
+static void print_help(void)
+{
+	printf(
+"Usage: sievec  [-c <config-file>] [-d] [-D] [-P <plugin>] [-x <extensions>] \n"
+"              <script-file> [<out-file>]\n"
+	);
+}
+
+/*
+ * Tool implementation
+ */
+
+int main(int argc, char **argv)
+{
+	struct sieve_instance *svinst;
+	struct stat st;
+	struct sieve_binary *sbin;
+	bool dump = FALSE;
+	const char *scriptfile, *outfile;
+	int exit_status = EXIT_SUCCESS;
+	int c;
+
+	sieve_tool = sieve_tool_init("sievec", &argc, &argv, "DdP:x:u:", FALSE);
+
+	outfile = NULL;
+	while ((c = sieve_tool_getopt(sieve_tool)) > 0) {
+		switch (c) {
+		case 'd':
+			/* dump file */
+			dump = TRUE;
+			break;
+		default:
+			print_help();
+			i_fatal_status(EX_USAGE, "Unknown argument: %c", c);
+			break;
+		}
+	}
+
+	if ( optind < argc ) {
+		scriptfile = argv[optind++];
+	} else {
+		print_help();
+		i_fatal_status(EX_USAGE, "Missing <script-file> argument");
+	}
+
+	if ( optind < argc ) {
+		outfile = argv[optind++];
+	} else if ( dump ) {
+		outfile = "-";
+	}
+
+	svinst = sieve_tool_init_finish(sieve_tool, FALSE, TRUE);
+
+	/* Enable debug extension */
+	sieve_enable_debug_extension(svinst);
+
+	if ( stat(scriptfile, &st) == 0 && S_ISDIR(st.st_mode) ) {
+		/* Script directory */
+		DIR *dirp;
+		struct dirent *dp;
+
+		/* Sanity checks on some of the arguments */
+
+		if ( dump )
+			i_fatal_status(EX_USAGE,
+				"the -d option is not allowed when scriptfile is a directory.");
+
+		if ( outfile != NULL )
+			i_fatal_status(EX_USAGE,
+				"the outfile argument is not allowed when scriptfile is a directory.");
+
+		/* Open the directory */
+		if ( (dirp = opendir(scriptfile)) == NULL )
+			i_fatal("opendir(%s) failed: %m", scriptfile);
+
+		/* Compile each sieve file */
+		for (;;) {
+
+			errno = 0;
+			if ( (dp = readdir(dirp)) == NULL ) {
+				if ( errno != 0 )
+					i_fatal("readdir(%s) failed: %m", scriptfile);
+				break;
+			}
+
+			if ( sieve_script_file_has_extension(dp->d_name) ) {
+				const char *file;
+
+				if ( scriptfile[strlen(scriptfile)-1] == '/' )
+					file = t_strconcat(scriptfile, dp->d_name, NULL);
+				else
+					file = t_strconcat(scriptfile, "/", dp->d_name, NULL);
+
+				sbin = sieve_tool_script_compile(svinst, file, NULL);
+
+				if ( sbin != NULL ) {
+					sieve_save(sbin, TRUE, NULL);
+					sieve_close(&sbin);
+				}
+			}
+		}
+
+		/* Close the directory */
+		if ( closedir(dirp) < 0 )
+			i_fatal("closedir(%s) failed: %m", scriptfile);
+	} else {
+		/* Script file (i.e. not a directory)
+		 *
+		 *   NOTE: For consistency, stat errors are handled here as well
+		 */
+		sbin = sieve_tool_script_compile(svinst, scriptfile, NULL);
+
+		if ( sbin != NULL ) {
+			if ( dump )
+				sieve_tool_dump_binary_to(sbin, outfile, FALSE);
+			else {	
+				sieve_save_as(sbin, outfile, TRUE, 0600, NULL);
+			}
+
+			sieve_close(&sbin);
+		} else {
+			exit_status = EXIT_FAILURE;
+		}
+	}
+
+	sieve_tool_deinit(&sieve_tool);
+
+	return exit_status;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/Makefile.am
@@ -0,0 +1,74 @@
+noinst_PROGRAMS = testsuite
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib-sieve \
+	-I$(top_srcdir)/src/lib-sieve/util \
+	-I$(top_srcdir)/src/lib-sieve/plugins/variables \
+	-I$(top_srcdir)/src/lib-sieve-tool \
+	$(LIBDOVECOT_INCLUDE) \
+	$(LIBDOVECOT_SERVICE_INCLUDE)
+
+testsuite_LDFLAGS = -export-dynamic
+
+libs = \
+	$(top_builddir)/src/lib-sieve/libdovecot-sieve.la \
+	$(top_builddir)/src/lib-sieve-tool/libsieve-tool.la
+
+testsuite_LDADD = $(libs) $(LIBDOVECOT_STORAGE) $(LIBDOVECOT_LDA) $(LIBDOVECOT)
+testsuite_DEPENDENCIES = $(libs) $(LIBDOVECOT_STORAGE_DEPS) $(LIBDOVECOT_LDA_DEPS) $(LIBDOVECOT_DEPS)
+
+commands = \
+	cmd-test.c \
+	cmd-test-fail.c \
+	cmd-test-config.c \
+	cmd-test-set.c \
+	cmd-test-result.c \
+	cmd-test-message.c \
+	cmd-test-mailbox.c \
+	cmd-test-binary.c \
+	cmd-test-imap-metadata.c
+
+tests = \
+	tst-test-script-compile.c \
+	tst-test-script-run.c \
+	tst-test-multiscript.c \
+	tst-test-error.c \
+	tst-test-result-action.c \
+	tst-test-result-execute.c
+
+testsuite_SOURCES = \
+	testsuite-common.c \
+	testsuite-settings.c \
+	testsuite-objects.c \
+	testsuite-substitutions.c \
+	testsuite-variables.c \
+	testsuite-arguments.c \
+	testsuite-message.c \
+	testsuite-log.c \
+	testsuite-script.c \
+	testsuite-result.c \
+	testsuite-smtp.c \
+	testsuite-mailstore.c \
+	testsuite-binary.c \
+	$(commands) \
+	$(tests) \
+	ext-testsuite.c \
+	testsuite.c
+
+noinst_HEADERS = \
+	testsuite-common.h \
+	testsuite-settings.h \
+	testsuite-objects.h \
+	testsuite-substitutions.h \
+	testsuite-variables.h \
+	testsuite-arguments.h \
+	testsuite-message.h \
+	testsuite-log.h \
+	testsuite-script.h \
+	testsuite-result.h \
+	testsuite-smtp.h \
+	testsuite-mailstore.h \
+	testsuite-binary.h
+
+clean-local:
+	-rm -rf test.out.*
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/cmd-test-binary.c
@@ -0,0 +1,207 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+
+#include "testsuite-common.h"
+#include "testsuite-binary.h"
+#include "testsuite-script.h"
+
+/*
+ * Commands
+ */
+
+static bool cmd_test_binary_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_test_binary_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+/* Test_binary_load command
+ *
+ * Syntax:
+ *   test_binary_load <binary-name: string>
+ */
+
+const struct sieve_command_def cmd_test_binary_load = {
+	.identifier = "test_binary_load",
+	.type = SCT_COMMAND,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = cmd_test_binary_validate,
+	.generate = cmd_test_binary_generate
+};
+
+/* Test_binary_save command
+ *
+ * Syntax:
+ *   test_binary_save <binary-name: string>
+ */
+
+const struct sieve_command_def cmd_test_binary_save = {
+	.identifier = "test_binary_save",
+	.type = SCT_COMMAND,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = cmd_test_binary_validate,
+	.generate = cmd_test_binary_generate,
+};
+
+/*
+ * Operations
+ */
+
+static bool cmd_test_binary_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_test_binary_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+/* test_binary_create operation */
+
+const struct sieve_operation_def test_binary_load_operation = {
+	.mnemonic = "TEST_BINARY_LOAD",
+	.ext_def = &testsuite_extension,
+	.code = TESTSUITE_OPERATION_TEST_BINARY_LOAD,
+	.dump = cmd_test_binary_operation_dump,
+	.execute = cmd_test_binary_operation_execute
+};
+
+/* test_binary_delete operation */
+
+const struct sieve_operation_def test_binary_save_operation = {
+	.mnemonic = "TEST_BINARY_SAVE",
+	.ext_def = &testsuite_extension,
+	.code = TESTSUITE_OPERATION_TEST_BINARY_SAVE,
+	.dump = cmd_test_binary_operation_dump,
+	.execute = cmd_test_binary_operation_execute
+};
+
+/*
+ * Validation
+ */
+
+static bool cmd_test_binary_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *arg = cmd->first_positional;
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "binary-name", 1, SAAT_STRING) ) {
+		return FALSE;
+	}
+
+	return sieve_validator_argument_activate(valdtr, cmd, arg, FALSE);
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_test_binary_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	/* Emit operation */
+	if ( sieve_command_is(cmd, cmd_test_binary_load) )
+		sieve_operation_emit(cgenv->sblock, cmd->ext, &test_binary_load_operation);
+	else if ( sieve_command_is(cmd, cmd_test_binary_save) )
+		sieve_operation_emit(cgenv->sblock, cmd->ext, &test_binary_save_operation);
+	else
+		i_unreached();
+
+ 	/* Generate arguments */
+	if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
+		return FALSE;
+
+	return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_test_binary_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "%s:", sieve_operation_mnemonic(denv->oprtn));
+
+	sieve_code_descend(denv);
+
+	return sieve_opr_string_dump(denv, address, "binary-name");
+}
+
+/*
+ * Intepretation
+ */
+
+static int cmd_test_binary_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	const struct sieve_operation *oprtn = renv->oprtn;
+	string_t *binary_name = NULL;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Binary Name */
+
+	if ( (ret=sieve_opr_string_read(renv, address, "binary-name", &binary_name))
+		<= 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	if ( sieve_operation_is(oprtn, test_binary_load_operation) ) {
+		struct sieve_binary *sbin = testsuite_binary_load(str_c(binary_name));
+
+		if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
+			sieve_runtime_trace(renv, 0, "testsuite: test_binary_load command");
+			sieve_runtime_trace_descend(renv);
+			sieve_runtime_trace(renv, 0, "load binary `%s'", str_c(binary_name));
+		}
+
+		if ( sbin != NULL ) {
+			testsuite_script_set_binary(renv, sbin);
+
+			sieve_binary_unref(&sbin);
+		} else {
+			sieve_sys_error(testsuite_sieve_instance,
+				"failed to load binary %s", str_c(binary_name));
+			return SIEVE_EXEC_FAILURE;
+		}
+
+	} else if ( sieve_operation_is(oprtn, test_binary_save_operation) ) {
+		struct sieve_binary *sbin = testsuite_script_get_binary(renv);
+
+		if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
+			sieve_runtime_trace(renv, 0, "testsuite: test_binary_save command");
+			sieve_runtime_trace_descend(renv);
+			sieve_runtime_trace(renv, 0, "save binary `%s'", str_c(binary_name));
+		}
+
+		if ( sbin != NULL )
+			testsuite_binary_save(sbin, str_c(binary_name));
+		else {
+			sieve_sys_error(testsuite_sieve_instance,
+				"no compiled binary to save as %s", str_c(binary_name));
+			return SIEVE_EXEC_FAILURE;
+		}
+	} else {
+		i_unreached();
+	}
+
+	return SIEVE_EXEC_OK;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/cmd-test-config.c
@@ -0,0 +1,478 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-settings.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+
+#include "testsuite-common.h"
+#include "testsuite-settings.h"
+
+/*
+ * Commands
+ */
+
+static bool cmd_test_config_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+/* Test_config_set command
+ *
+ * Syntax:
+ *   test_config_set <setting: string> <value: string>
+ */
+
+static bool cmd_test_config_set_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+
+const struct sieve_command_def cmd_test_config_set = {
+	.identifier = "test_config_set",
+	.type = SCT_COMMAND,
+	.positional_args = 2,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = cmd_test_config_set_validate,
+	.generate = cmd_test_config_generate
+};
+
+/* Test_config_unset command
+ *
+ * Syntax:
+ *   test_config_unset <setting: string>
+ */
+
+static bool cmd_test_config_unset_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+
+const struct sieve_command_def cmd_test_config_unset = {
+	.identifier = "test_config_unset",
+	.type = SCT_COMMAND,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = cmd_test_config_unset_validate,
+	.generate = cmd_test_config_generate,
+};
+
+/* Test_config_reload command
+ *
+ * Syntax:
+ *   test_config_reload [:extension <extension: string>]
+ */
+
+static bool cmd_test_config_reload_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg);
+
+const struct sieve_command_def cmd_test_config_reload = {
+	.identifier = "test_config_reload",
+	.type = SCT_COMMAND,
+	.positional_args = 0,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = cmd_test_config_reload_registered,
+	.generate = cmd_test_config_generate
+};
+
+/*
+ * Command tags
+ */
+
+/* Forward declarations */
+
+static bool cmd_test_config_reload_validate_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+
+/* Argument objects */
+
+static const struct sieve_argument_def test_config_reload_extension_tag = {
+	.identifier = "extension",
+	.validate = cmd_test_config_reload_validate_tag,
+};
+
+/* Codes for optional arguments */
+
+enum cmd_test_config_optional {
+	OPT_END,
+	OPT_EXTENSION
+};
+
+/*
+ * Operations
+ */
+
+/* Test_config_set operation */
+
+static bool cmd_test_config_set_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_test_config_set_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def test_config_set_operation = {
+	.mnemonic = "TEST_CONFIG_SET",
+	.ext_def = &testsuite_extension,
+	.code = TESTSUITE_OPERATION_TEST_CONFIG_SET,
+	.dump = cmd_test_config_set_operation_dump,
+	.execute = cmd_test_config_set_operation_execute
+};
+
+/* Test_config_unset operation */
+
+static bool cmd_test_config_unset_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_test_config_unset_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def test_config_unset_operation = {
+	.mnemonic = "TEST_CONFIG_UNSET",
+	.ext_def = &testsuite_extension,
+	.code = TESTSUITE_OPERATION_TEST_CONFIG_UNSET,
+	.dump = cmd_test_config_unset_operation_dump,
+	.execute = cmd_test_config_unset_operation_execute
+};
+
+/* Test_config_read operation */
+
+static bool cmd_test_config_reload_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_test_config_reload_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def test_config_reload_operation = {
+	.mnemonic = "TEST_CONFIG_RELOAD",
+	.ext_def = &testsuite_extension,
+	.code = TESTSUITE_OPERATION_TEST_CONFIG_RELOAD,
+	.dump = cmd_test_config_reload_operation_dump,
+	.execute = cmd_test_config_reload_operation_execute
+};
+
+/*
+ * Tag validation
+ */
+
+static bool cmd_test_config_reload_validate_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *tag = *arg;
+
+	/* Detach the tag itself */
+	*arg = sieve_ast_arguments_detach(*arg,1);
+
+	/* Check syntax:
+	 *   :extension <extension: string>
+	 */
+	if ( !sieve_validate_tag_parameter
+		(valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING, TRUE) ) {
+		return FALSE;
+	}
+
+	/* Skip parameter */
+	*arg = sieve_ast_argument_next(*arg);
+
+	return TRUE;
+}
+
+/*
+ * Command registration
+ */
+
+static bool cmd_test_config_reload_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg)
+{
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &test_config_reload_extension_tag, OPT_EXTENSION);
+
+	return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+static bool cmd_test_config_set_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *arg = cmd->first_positional;
+
+	/* Check syntax:
+	 *   <setting: string> <value: string>
+	 */
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "setting", 1, SAAT_STRING) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, cmd, arg, FALSE) )
+		return FALSE;
+
+	arg = sieve_ast_argument_next(arg);
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "value", 2, SAAT_STRING) ) {
+		return FALSE;
+	}
+
+	return sieve_validator_argument_activate(valdtr, cmd, arg, FALSE);
+}
+
+static bool cmd_test_config_unset_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *arg = cmd->first_positional;
+
+	/* Check syntax:
+	 *   <setting: string>
+	 */
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "setting", 1, SAAT_STRING) ) {
+		return FALSE;
+	}
+
+	return sieve_validator_argument_activate(valdtr, cmd, arg, FALSE);
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_test_config_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	if ( sieve_command_is(cmd, cmd_test_config_set) )
+		sieve_operation_emit
+			(cgenv->sblock, cmd->ext, &test_config_set_operation);
+	else if ( sieve_command_is(cmd, cmd_test_config_unset) )
+		sieve_operation_emit
+			(cgenv->sblock, cmd->ext, &test_config_unset_operation);
+	else if ( sieve_command_is(cmd, cmd_test_config_reload) )
+		sieve_operation_emit
+			(cgenv->sblock, cmd->ext, &test_config_reload_operation);
+	else
+		i_unreached();
+
+ 	/* Generate arguments */
+	if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
+		return FALSE;
+
+	return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_test_config_set_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "TEST_CONFIG_SET:");
+
+	sieve_code_descend(denv);
+
+	return sieve_opr_string_dump(denv, address, "setting") &&
+		sieve_opr_string_dump(denv, address, "value");
+}
+
+static bool cmd_test_config_unset_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "TEST_CONFIG_UNSET:");
+
+	sieve_code_descend(denv);
+
+	return
+		sieve_opr_string_dump(denv, address, "setting");
+}
+
+static bool cmd_test_config_reload_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	int opt_code = 0;
+
+	sieve_code_dumpf(denv, "TEST_CONFIG_RELOAD:");
+	sieve_code_descend(denv);
+
+	/* Dump optional operands */
+
+	for (;;) {
+		int opt;
+		bool opok = TRUE;
+
+		if ( (opt=sieve_opr_optional_dump(denv, address, &opt_code)) < 0 )
+			return FALSE;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_EXTENSION:
+			opok = sieve_opr_string_dump(denv, address, "extensions");
+			break;
+		default:
+			opok = FALSE;
+			break;
+		}
+
+		if ( !opok ) return FALSE;
+	}
+
+	return TRUE;
+}
+
+/*
+ * Interpretation
+ */
+
+static int cmd_test_config_set_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	string_t *setting;
+	string_t *value;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Setting */
+	if ( (ret=sieve_opr_string_read(renv, address, "setting", &setting)) <= 0 )
+		return ret;
+
+	/* Value */
+	if ( (ret=sieve_opr_string_read(renv, address, "value", &value)) <= 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
+		sieve_runtime_trace(renv, 0,
+			"testsuite: test_config_set command");
+		sieve_runtime_trace_descend(renv);
+		sieve_runtime_trace(renv, 0, "set config `%s' = `%s'",
+			str_c(setting), str_c(value));
+	}
+
+	testsuite_setting_set(str_c(setting), str_c(value));
+
+	return SIEVE_EXEC_OK;
+}
+
+static int cmd_test_config_unset_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	string_t *setting;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Setting */
+	if ( (ret=sieve_opr_string_read(renv, address, "setting", &setting)) <= 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
+		sieve_runtime_trace(renv, 0,
+			"testsuite: test_config_unset command");
+		sieve_runtime_trace_descend(renv);
+		sieve_runtime_trace(renv, 0, "unset config `%s'", str_c(setting));
+	}
+
+	testsuite_setting_unset(str_c(setting));
+
+	return SIEVE_EXEC_OK;
+}
+
+static int cmd_test_config_reload_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	const struct sieve_extension *ext;
+	int opt_code = 0;
+	string_t *extension = NULL;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Optional operands */
+	for (;;) {
+		int opt;
+
+		if ( (opt=sieve_opr_optional_read(renv, address, &opt_code)) < 0 )
+			return SIEVE_EXEC_BIN_CORRUPT;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_EXTENSION:
+			ret = sieve_opr_string_read(renv, address, "extension", &extension);
+			break;
+		default:
+			sieve_runtime_trace_error(renv, "unknown optional operand");
+			ret = SIEVE_EXEC_BIN_CORRUPT;
+		}
+
+		if ( ret <= 0 ) return ret;
+	}
+
+	/*
+	 * Perform operation
+	 */
+
+	if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
+		sieve_runtime_trace(renv, 0,
+			"testsuite: test_config_reload command");
+		sieve_runtime_trace_descend(renv);
+	}
+
+	if ( extension == NULL ) {
+		if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
+			sieve_runtime_trace(renv, 0,
+				"reload configuration for sieve engine");
+		}
+
+		sieve_settings_load(renv->svinst);
+
+	} else {
+		if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
+			sieve_runtime_trace(renv, 0,
+				"reload configuration for extension `%s'",
+				str_c(extension));
+		}
+
+		ext = sieve_extension_get_by_name(renv->svinst, str_c(extension));
+		if ( ext == NULL ) {
+			testsuite_test_failf("test_config_reload: "
+				"unknown extension '%s'", str_c(extension));
+			return SIEVE_EXEC_OK;
+		}
+
+		sieve_extension_reload(ext);
+	}
+
+	return SIEVE_EXEC_OK;
+}
+
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/cmd-test-fail.c
@@ -0,0 +1,150 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+
+#include "testsuite-common.h"
+
+/*
+ * Test_fail command
+ *
+ * Syntax:
+ *   test_fail <reason: string>
+ */
+
+static bool cmd_test_fail_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_test_fail_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def cmd_test_fail = {
+	.identifier = "test_fail",
+	.type = SCT_COMMAND,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = cmd_test_fail_validate,
+	.generate = cmd_test_fail_generate
+};
+
+/*
+ * Test operation
+ */
+
+static bool cmd_test_fail_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_test_fail_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def test_fail_operation = {
+	.mnemonic = "TEST_FAIL",
+	.ext_def = &testsuite_extension,
+	.code = TESTSUITE_OPERATION_TEST_FAIL,
+	.dump = cmd_test_fail_operation_dump,
+	.execute = cmd_test_fail_operation_execute
+};
+
+/*
+ * Validation
+ */
+
+static bool cmd_test_fail_validate
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *arg = cmd->first_positional;
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "reason", 1, SAAT_STRING) ) {
+		return FALSE;
+	}
+
+	return sieve_validator_argument_activate(valdtr, cmd, arg, FALSE);
+}
+
+/*
+ * Code generation
+ */
+
+static inline struct testsuite_generator_context *
+	_get_generator_context(struct sieve_generator *gentr)
+{
+	return (struct testsuite_generator_context *)
+		sieve_generator_extension_get_context(gentr, testsuite_ext);
+}
+
+static bool cmd_test_fail_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	struct testsuite_generator_context *genctx =
+		_get_generator_context(cgenv->gentr);
+
+	sieve_operation_emit(cgenv->sblock, cmd->ext, &test_fail_operation);
+
+	/* Generate arguments */
+	if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
+		return FALSE;
+
+	sieve_jumplist_add(genctx->exit_jumps,
+		sieve_binary_emit_offset(cgenv->sblock, 0));
+
+	return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_test_fail_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	unsigned int pc;
+	sieve_offset_t offset;
+
+	sieve_code_dumpf(denv, "TEST_FAIL:");
+	sieve_code_descend(denv);
+
+	if ( !sieve_opr_string_dump(denv, address, "reason") )
+		return FALSE;
+
+	sieve_code_mark(denv);
+	pc = *address;
+	if ( sieve_binary_read_offset(denv->sblock, address, &offset) )
+		sieve_code_dumpf(denv, "offset: %d [%08x]", offset, pc + offset);
+	else
+		return FALSE;
+
+	return TRUE;
+}
+
+/*
+ * Intepretation
+ */
+
+static int cmd_test_fail_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	string_t *reason;
+	int ret;
+
+	if ( (ret=sieve_opr_string_read(renv, address, "reason", &reason)) <= 0 )
+		return ret;
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS,
+		"testsuite: test_fail command; FAIL current test");
+
+	testsuite_test_fail(reason);
+
+	return sieve_interpreter_program_jump(renv->interp, TRUE, TRUE);
+}
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/cmd-test-imap-metadata.c
@@ -0,0 +1,284 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+
+#include "testsuite-common.h"
+#include "testsuite-mailstore.h"
+
+/*
+ * Commands
+ */
+
+static bool cmd_test_imap_metadata_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool cmd_test_imap_metadata_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_test_imap_metadata_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+/* Test_mailbox_create command
+ *
+ * Syntax:
+ *   test_imap_metadata_set
+ *     <mailbox: string> <annotation: string> <value:string>
+ */
+
+const struct sieve_command_def cmd_test_imap_metadata_set = {
+	.identifier = "test_imap_metadata_set",
+	.type = SCT_COMMAND,
+	.positional_args = 2,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = cmd_test_imap_metadata_registered,
+	.validate = cmd_test_imap_metadata_validate,
+	.generate = cmd_test_imap_metadata_generate,
+};
+
+/*
+ * Command tags
+ */
+
+static bool cmd_test_imap_metadata_validate_mailbox_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+
+static const struct sieve_argument_def test_imap_metadata_mailbox_tag = {
+	.identifier = "mailbox",
+	.validate = cmd_test_imap_metadata_validate_mailbox_tag
+};
+
+/*
+ * Operations
+ */
+
+static bool cmd_test_imap_metadata_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_test_imap_metadata_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+/* Test_mailbox_create operation */
+
+const struct sieve_operation_def test_imap_metadata_set_operation = {
+	.mnemonic = "TEST_IMAP_METADATA_SET",
+	.ext_def = &testsuite_extension,
+	.code = TESTSUITE_OPERATION_TEST_IMAP_METADATA_SET,
+	.dump = cmd_test_imap_metadata_operation_dump,
+	.execute = cmd_test_imap_metadata_operation_execute
+};
+
+/* Codes for optional arguments */
+
+enum cmd_vacation_optional {
+	OPT_END,
+	OPT_MAILBOX
+};
+
+/*
+ * Tag validation
+ */
+
+static bool cmd_test_imap_metadata_validate_mailbox_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *tag = *arg;
+
+	/* Delete this tag */
+	*arg = sieve_ast_arguments_detach(*arg, 1);
+
+	/* Check syntax:
+	 *   :mailbox string
+	 */
+	if ( !sieve_validate_tag_parameter
+		(valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING, FALSE) ) {
+		return FALSE;
+	}
+
+	/* Skip parameter */
+	*arg = sieve_ast_argument_next(*arg);
+	return TRUE;
+}
+
+/*
+ * Command registration
+ */
+
+static bool cmd_test_imap_metadata_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg)
+{
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &test_imap_metadata_mailbox_tag, OPT_MAILBOX);
+	return TRUE;
+}
+
+
+/*
+ * Validation
+ */
+
+static bool cmd_test_imap_metadata_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *arg = cmd->first_positional;
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "annotation", 2, SAAT_STRING) )
+		return FALSE;
+
+	if (!sieve_validator_argument_activate(valdtr, cmd, arg, FALSE))
+		return FALSE;
+
+	arg = sieve_ast_argument_next(arg);
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "value", 3, SAAT_STRING) )
+		return FALSE;
+
+	if (!sieve_validator_argument_activate(valdtr, cmd, arg, FALSE))
+		return FALSE;
+	return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_test_imap_metadata_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	/* Emit operation */
+	if ( sieve_command_is(cmd, cmd_test_imap_metadata_set) )
+		sieve_operation_emit
+			(cgenv->sblock, cmd->ext, &test_imap_metadata_set_operation);
+	else
+		i_unreached();
+
+ 	/* Generate arguments */
+	if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
+		return FALSE;
+	return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_test_imap_metadata_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	int opt_code = 0;
+
+	sieve_code_dumpf(denv, "%s:", sieve_operation_mnemonic(denv->oprtn));
+	sieve_code_descend(denv);
+
+	/* Dump optional operands */
+
+	for (;;) {
+		int opt;
+		bool opok = TRUE;
+
+		if ( (opt=sieve_opr_optional_dump(denv, address, &opt_code)) < 0 )
+			return FALSE;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_MAILBOX:
+			opok = sieve_opr_string_dump(denv, address, "mailbox");
+			break;
+		default:
+			return FALSE;
+		}
+
+		if ( !opok ) return FALSE;
+	}	
+
+	return 
+		( sieve_opr_string_dump(denv, address, "annotation") &&
+			sieve_opr_string_dump(denv, address, "value") );
+}
+
+/*
+ * Intepretation
+ */
+
+static int cmd_test_imap_metadata_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	const struct sieve_operation *oprtn = renv->oprtn;
+	int opt_code = 0;
+	string_t *mailbox = NULL, *annotation = NULL, *value = NULL;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Optional operands */
+
+	for (;;) {
+		int opt;
+
+		if ( (opt=sieve_opr_optional_read(renv, address, &opt_code)) < 0 )
+			return SIEVE_EXEC_BIN_CORRUPT;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_MAILBOX:
+			ret = sieve_opr_string_read(renv, address, "mailbox", &mailbox);
+			break;
+		default:
+			sieve_runtime_trace_error(renv, "unknown optional operand");
+			ret = SIEVE_EXEC_BIN_CORRUPT;
+		}
+
+		if ( ret <= 0 ) return ret;
+	}
+
+	/* Fixed operands */
+
+	if ( (ret=sieve_opr_string_read
+		(renv, address, "annotation", &annotation)) <= 0 )
+		return ret;
+	if ( (ret=sieve_opr_string_read
+		(renv, address, "value", &value)) <= 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	if ( sieve_operation_is(oprtn, test_imap_metadata_set_operation) ) {
+		if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
+			sieve_runtime_trace(renv, 0, "testsuite/test_imap_metadata_set command");
+			sieve_runtime_trace_descend(renv);
+			if (mailbox == NULL) {
+				sieve_runtime_trace(renv, 0,
+					"set server annotation `%s'", str_c(annotation));
+			} else {
+				sieve_runtime_trace(renv, 0,
+					"set annotation `%s' for mailbox `%s'",
+					str_c(annotation), str_c(mailbox));
+			}
+		}
+
+		if (testsuite_mailstore_set_imap_metadata
+			(( mailbox == NULL ? NULL : str_c(mailbox) ),
+				str_c(annotation), str_c(value)) < 0)
+			return SIEVE_EXEC_FAILURE;
+	}
+
+	return SIEVE_EXEC_OK;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/cmd-test-mailbox.c
@@ -0,0 +1,188 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+
+#include "testsuite-common.h"
+#include "testsuite-mailstore.h"
+
+/*
+ * Commands
+ */
+
+static bool cmd_test_mailbox_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_test_mailbox_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+/* Test_mailbox_create command
+ *
+ * Syntax:
+ *   test_mailbox_create <mailbox: string>
+ */
+
+const struct sieve_command_def cmd_test_mailbox_create = {
+	.identifier = "test_mailbox_create",
+	.type = SCT_COMMAND,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = cmd_test_mailbox_validate,
+	.generate = cmd_test_mailbox_generate
+};
+
+/* Test_mailbox_delete command
+ *
+ * Syntax:
+ *   test_mailbox_create <mailbox: string>
+ */
+
+const struct sieve_command_def cmd_test_mailbox_delete = {
+	.identifier = "test_mailbox_delete",
+	.type = SCT_COMMAND,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = cmd_test_mailbox_validate,
+	.generate = cmd_test_mailbox_generate
+};
+
+/*
+ * Operations
+ */
+
+static bool cmd_test_mailbox_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_test_mailbox_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+/* Test_mailbox_create operation */
+
+const struct sieve_operation_def test_mailbox_create_operation = {
+	.mnemonic = "TEST_MAILBOX_CREATE",
+	.ext_def = &testsuite_extension,
+	.code = TESTSUITE_OPERATION_TEST_MAILBOX_CREATE,
+	.dump = cmd_test_mailbox_operation_dump,
+	.execute = cmd_test_mailbox_operation_execute
+};
+
+/* Test_mailbox_delete operation */
+
+const struct sieve_operation_def test_mailbox_delete_operation = {
+	.mnemonic = "TEST_MAILBOX_DELETE",
+	.ext_def = &testsuite_extension,
+	.code = TESTSUITE_OPERATION_TEST_MAILBOX_DELETE,
+	.dump = cmd_test_mailbox_operation_dump,
+	.execute = cmd_test_mailbox_operation_execute
+};
+
+/*
+ * Validation
+ */
+
+static bool cmd_test_mailbox_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *arg = cmd->first_positional;
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "mailbox", 1, SAAT_STRING) ) {
+		return FALSE;
+	}
+
+	return sieve_validator_argument_activate(valdtr, cmd, arg, FALSE);
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_test_mailbox_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	/* Emit operation */
+	if ( sieve_command_is(cmd, cmd_test_mailbox_create) )
+		sieve_operation_emit
+			(cgenv->sblock, cmd->ext, &test_mailbox_create_operation);
+	else if ( sieve_command_is(cmd, cmd_test_mailbox_delete) )
+		sieve_operation_emit
+			(cgenv->sblock, cmd->ext, &test_mailbox_delete_operation);
+	else
+		i_unreached();
+
+ 	/* Generate arguments */
+	if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
+		return FALSE;
+
+	return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_test_mailbox_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "%s:", sieve_operation_mnemonic(denv->oprtn));
+
+	sieve_code_descend(denv);
+
+	return sieve_opr_string_dump(denv, address, "mailbox");
+}
+
+
+/*
+ * Intepretation
+ */
+
+static int cmd_test_mailbox_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	const struct sieve_operation *oprtn = renv->oprtn;
+	string_t *mailbox = NULL;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Index */
+
+	if ( (ret=sieve_opr_string_read(renv, address, "mailbox", &mailbox)) <= 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	if ( sieve_operation_is(oprtn, test_mailbox_create_operation) ) {
+		if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
+			sieve_runtime_trace(renv, 0, "testsuite/test_mailbox_create command");
+			sieve_runtime_trace_descend(renv);
+			sieve_runtime_trace(renv, 0, "create mailbox `%s'", str_c(mailbox));
+		}
+
+		testsuite_mailstore_mailbox_create(renv, str_c(mailbox));
+	} else {
+		if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
+			sieve_runtime_trace(renv, 0, "testsuite/test_mailbox_delete command");
+			sieve_runtime_trace_descend(renv);
+			sieve_runtime_trace(renv, 0, "delete mailbox `%s'", str_c(mailbox));
+		}
+
+		/* FIXME: implement */
+		testsuite_test_failf("test_mailbox_delete: NOT IMPLEMENTED");
+	}
+
+	return SIEVE_EXEC_OK;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/cmd-test-message.c
@@ -0,0 +1,517 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "istream.h"
+#include "mail-storage.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-message.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+
+#include "testsuite-common.h"
+#include "testsuite-smtp.h"
+#include "testsuite-mailstore.h"
+
+/*
+ * Commands
+ */
+
+/* Test_message command
+ *
+ * Syntax:
+ *   test_message ( :smtp / :mailbox <mailbox: string> ) <index: number>
+ */
+
+static bool cmd_test_message_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool cmd_test_message_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_test_message_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def cmd_test_message = {
+	.identifier = "test_message",
+	.type = SCT_HYBRID,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = cmd_test_message_registered,
+	.validate = cmd_test_message_validate,
+	.generate = cmd_test_message_generate
+};
+
+/* Test_message_print command
+ *
+ * Syntax:
+ *   test_message_print
+ */
+
+static bool cmd_test_message_print_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+const struct sieve_command_def cmd_test_message_print = {
+	.identifier = "test_message_print",
+	.type = SCT_COMMAND,
+	.positional_args = 0,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.generate = cmd_test_message_print_generate
+};
+
+/*
+ * Operations
+ */
+
+/* Test_message_smtp operation */
+
+static bool cmd_test_message_smtp_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_test_message_smtp_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def test_message_smtp_operation = {
+	.mnemonic = "TEST_MESSAGE_SMTP",
+	.ext_def = &testsuite_extension,
+	.code = TESTSUITE_OPERATION_TEST_MESSAGE_SMTP,
+	.dump = cmd_test_message_smtp_operation_dump,
+	.execute = cmd_test_message_smtp_operation_execute
+};
+
+/* Test_message_mailbox operation */
+
+static bool cmd_test_message_mailbox_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_test_message_mailbox_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def test_message_mailbox_operation = {
+	.mnemonic = "TEST_MESSAGE_MAILBOX",
+	.ext_def = &testsuite_extension,
+	.code = TESTSUITE_OPERATION_TEST_MESSAGE_MAILBOX,
+	.dump = cmd_test_message_mailbox_operation_dump,
+	.execute = cmd_test_message_mailbox_operation_execute
+};
+
+/* Test_message_print operation */
+
+static bool cmd_test_message_print_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_test_message_print_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def test_message_print_operation = {
+	.mnemonic = "TEST_MESSAGE_PRINT",
+	.ext_def = &testsuite_extension,
+	.code = TESTSUITE_OPERATION_TEST_MESSAGE_PRINT,
+	.dump = cmd_test_message_print_operation_dump,
+	.execute = cmd_test_message_print_operation_execute
+};
+
+/*
+ * Compiler context data
+ */
+
+enum test_message_source {
+	MSG_SOURCE_SMTP,
+	MSG_SOURCE_MAILBOX,
+	MSG_SOURCE_LAST
+};
+
+const struct sieve_operation_def *test_message_operations[] = {
+	&test_message_smtp_operation,
+	&test_message_mailbox_operation
+};
+
+struct cmd_test_message_context_data {
+	enum test_message_source msg_source;
+	const char *folder;
+};
+
+#define CMD_TEST_MESSAGE_ERROR_DUP_TAG \
+	"exactly one of the ':smtp' or ':folder' tags must be specified " \
+	"for the test_message command, but more were found"
+
+/*
+ * Command tags
+ */
+
+static bool cmd_test_message_validate_smtp_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+static bool cmd_test_message_validate_folder_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+
+static const struct sieve_argument_def test_message_smtp_tag = {
+	.identifier = "smtp",
+	.validate = cmd_test_message_validate_smtp_tag
+};
+
+static const struct sieve_argument_def test_message_folder_tag = {
+	.identifier = "folder",
+	.validate = cmd_test_message_validate_folder_tag
+};
+
+static bool cmd_test_message_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg)
+{
+	/* Register our tags */
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &test_message_folder_tag, 0);
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &test_message_smtp_tag, 0);
+
+	return TRUE;
+}
+
+static struct cmd_test_message_context_data *cmd_test_message_validate_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	struct cmd_test_message_context_data *ctx_data =
+		(struct cmd_test_message_context_data *) cmd->data;
+
+	if ( ctx_data != NULL ) {
+		sieve_argument_validate_error
+			(valdtr, *arg, CMD_TEST_MESSAGE_ERROR_DUP_TAG);
+		return NULL;
+	}
+
+	ctx_data = p_new
+		(sieve_command_pool(cmd), struct cmd_test_message_context_data, 1);
+	cmd->data = ctx_data;
+
+	/* Delete this tag */
+	*arg = sieve_ast_arguments_detach(*arg, 1);
+
+	return ctx_data;
+}
+
+static bool cmd_test_message_validate_smtp_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	struct cmd_test_message_context_data *ctx_data =
+		cmd_test_message_validate_tag(valdtr, arg, cmd);
+
+	/* Return value is NULL on error */
+	if ( ctx_data == NULL ) return FALSE;
+
+	/* Assign chosen message source */
+	ctx_data->msg_source = MSG_SOURCE_SMTP;
+
+	return TRUE;
+}
+
+static bool cmd_test_message_validate_folder_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *tag = *arg;
+	struct cmd_test_message_context_data *ctx_data =
+		cmd_test_message_validate_tag(valdtr, arg, cmd);
+
+	/* Return value is NULL on error */
+	if ( ctx_data == NULL ) return FALSE;
+
+	/* Assign chose message source */
+	ctx_data->msg_source = MSG_SOURCE_MAILBOX;
+
+	/* Check syntax:
+	 *   :folder string
+	 */
+	if ( !sieve_validate_tag_parameter
+		(valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING, FALSE) ) {
+		return FALSE;
+	}
+
+	/* Skip parameter */
+	*arg = sieve_ast_argument_next(*arg);
+
+	return TRUE;
+}
+
+/*
+ * Validation
+ */
+
+static bool cmd_test_message_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *arg = cmd->first_positional;
+
+	if ( cmd->data == NULL ) {
+		sieve_command_validate_error(valdtr, cmd,
+			"the test_message command requires either the :smtp or the :mailbox tag "
+			"to be specified");
+		return FALSE;
+	}
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "index", 1, SAAT_NUMBER) ) {
+		return FALSE;
+	}
+
+	return sieve_validator_argument_activate(valdtr, cmd, arg, FALSE);
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_test_message_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	struct cmd_test_message_context_data *ctx_data =
+		(struct cmd_test_message_context_data *) cmd->data;
+
+	i_assert( ctx_data->msg_source < MSG_SOURCE_LAST );
+
+	/* Emit operation */
+	sieve_operation_emit(cgenv->sblock, cmd->ext,
+		test_message_operations[ctx_data->msg_source]);
+
+	/* Emit is_test flag */
+	sieve_binary_emit_byte(cgenv->sblock,
+		( cmd->ast_node->type == SAT_TEST ? 1 : 0));
+
+ 	/* Generate arguments */
+	if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
+		return FALSE;
+
+	return TRUE;
+}
+
+static bool cmd_test_message_print_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	/* Emit operation */
+	sieve_operation_emit
+		(cgenv->sblock, cmd->ext, &test_message_print_operation);
+	return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_test_message_smtp_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	unsigned int is_test;
+
+	if ( !sieve_binary_read_byte(denv->sblock, address, &is_test) )
+		return FALSE;
+
+	sieve_code_dumpf(denv, "TEST_MESSAGE_SMTP (%s):",
+		( is_test > 0 ? "TEST" : "COMMAND" ));
+
+	sieve_code_descend(denv);
+
+	return sieve_opr_number_dump(denv, address, "index");
+}
+
+static bool cmd_test_message_mailbox_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	unsigned int is_test;
+
+	if ( !sieve_binary_read_byte(denv->sblock, address, &is_test) )
+		return FALSE;
+
+	sieve_code_dumpf(denv, "TEST_MESSAGE_MAILBOX (%s):",
+		( is_test > 0 ? "TEST" : "COMMAND" ));
+
+	sieve_code_descend(denv);
+
+	return
+		sieve_opr_string_dump(denv, address, "folder") &&
+		sieve_opr_number_dump(denv, address, "index");
+}
+
+static bool cmd_test_message_print_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address ATTR_UNUSED)
+{
+	sieve_code_dumpf(denv, "TEST_MESSAGE_PRINT");
+
+	return TRUE;
+}
+
+
+/*
+ * Intepretation
+ */
+
+static int cmd_test_message_smtp_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	sieve_number_t msg_index;
+	unsigned int is_test = 0;
+	bool result;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Is test */
+
+	if ( !sieve_binary_read_byte(renv->sblock, address, &is_test) ) {
+		sieve_runtime_trace_error(renv, "invalid is_test flag");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	/* Index */
+
+	if ( (ret=sieve_opr_number_read(renv, address, "index", &msg_index)) <= 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	if ( is_test > 0 ) {
+		if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_TESTS) ) {
+			sieve_runtime_trace(renv, 0,
+				"testsuite: test_message test");
+			sieve_runtime_trace_descend(renv);
+			sieve_runtime_trace(renv, 0,
+				"check and retrieve smtp message [index=%llu]",
+				(unsigned long long)msg_index);
+		}
+	} else {
+		if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
+			sieve_runtime_trace(renv, 0,
+				"testsuite: test_message command");
+			sieve_runtime_trace_descend(renv);
+			sieve_runtime_trace(renv, 0,
+				"retrieve smtp message [index=%llu]",
+				(unsigned long long)msg_index);
+		}
+	}
+
+	result = testsuite_smtp_get(renv, msg_index);
+
+	if ( is_test > 0 ) {
+		sieve_interpreter_set_test_result(renv->interp, result);
+		return SIEVE_EXEC_OK;
+	}
+
+	if ( !result ) {
+		testsuite_test_failf("no outgoing SMTP message with index %llu",
+			(unsigned long long)msg_index);
+	}
+
+	return SIEVE_EXEC_OK;
+}
+
+static int cmd_test_message_mailbox_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	string_t *folder;
+	sieve_number_t msg_index;
+	unsigned int is_test = 0;
+	bool result;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Is test */
+	if ( !sieve_binary_read_byte(renv->sblock, address, &is_test) ) {
+		sieve_runtime_trace_error(renv, "invalid is_test flag");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	/* Folder */
+	if ( (ret=sieve_opr_string_read(renv, address, "folder", &folder)) <= 0 )
+		return ret;
+
+	/* Index */
+	if ( (ret=sieve_opr_number_read(renv, address, "index", &msg_index)) <= 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	if ( is_test > 0 ) {
+		if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_TESTS) ) {
+			sieve_runtime_trace(renv, 0,
+				"testsuite: test_message test");
+			sieve_runtime_trace_descend(renv);
+			sieve_runtime_trace(renv, 0,
+				"check and retrieve mailbox message [mailbox=`%s' index=%llu]",
+				str_c(folder), (unsigned long long)msg_index);
+		}
+	} else {
+		if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
+			sieve_runtime_trace(renv, 0,
+				"testsuite: test_message command");
+			sieve_runtime_trace_descend(renv);
+			sieve_runtime_trace(renv, 0,
+				"retrieve mailbox message [mailbox=`%s' index=%llu]",
+				str_c(folder), (unsigned long long)msg_index);
+		}
+	}
+
+	result = testsuite_mailstore_mail_index(renv, str_c(folder), msg_index);
+
+	if ( is_test > 0 ) {
+		sieve_interpreter_set_test_result(renv->interp, result);
+		return SIEVE_EXEC_OK;
+	}
+
+	if ( !result )
+		testsuite_test_failf("no message in folder '%s' with index %llu",
+			str_c(folder), (unsigned long long)msg_index);
+
+	return SIEVE_EXEC_OK;
+}
+
+static int cmd_test_message_print_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED)
+{
+	struct mail *mail = sieve_message_get_mail(renv->msgctx);
+	struct istream *input;
+	const unsigned char *data;
+	size_t size;
+
+	if (mail_get_stream(mail, NULL, NULL, &input) < 0) {
+		sieve_runtime_error(renv, NULL,
+			"test_message_print: failed to read current message");
+		return SIEVE_EXEC_OK;
+	}
+
+	printf("\n--MESSAGE: \n");
+
+	/* Pipe the message to the outgoing SMTP transport */
+	while (i_stream_read_more(input, &data, &size) > 0) {
+		ssize_t wret;
+
+		if ( (wret=write(1, data, size)) <= 0 )
+			break;
+		i_stream_skip(input, wret);
+	}
+	printf("\n--MESSAGE--\n");
+
+	return SIEVE_EXEC_OK;
+}
+
+
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/cmd-test-result.c
@@ -0,0 +1,132 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-script.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+#include "sieve.h"
+
+#include "testsuite-common.h"
+#include "testsuite-result.h"
+#include "testsuite-smtp.h"
+
+/*
+ * Commands
+ */
+
+static bool cmd_test_result_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+/* Test_result_reset command
+ *
+ * Syntax:
+ *   test_result_reset
+ */
+
+const struct sieve_command_def cmd_test_result_reset = {
+	.identifier = "test_result_reset",
+	.type = SCT_COMMAND,
+	.positional_args = 0,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.generate = cmd_test_result_generate
+};
+
+/* Test_result_print command
+ *
+ * Syntax:
+ *   test_result_print
+ */
+
+const struct sieve_command_def cmd_test_result_print = {
+	.identifier = "test_result_print",
+	.type = SCT_COMMAND,
+	.positional_args = 0,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.generate = cmd_test_result_generate
+};
+
+/*
+ * Operations
+ */
+
+/* test_result_reset */
+
+static int cmd_test_result_reset_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def test_result_reset_operation = {
+	.mnemonic = "TEST_RESULT_RESET",
+	.ext_def = &testsuite_extension,
+	.code = TESTSUITE_OPERATION_TEST_RESULT_RESET,
+	.execute = cmd_test_result_reset_operation_execute
+};
+
+/* test_result_print */
+
+static int cmd_test_result_print_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def test_result_print_operation = {
+	.mnemonic = "TEST_RESULT_PRINT",
+	.ext_def = &testsuite_extension,
+	.code = TESTSUITE_OPERATION_TEST_RESULT_PRINT,
+	.execute = cmd_test_result_print_operation_execute
+};
+
+/*
+ * Code generation
+ */
+
+static bool cmd_test_result_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	if ( sieve_command_is(cmd, cmd_test_result_reset) )
+		sieve_operation_emit(cgenv->sblock, cmd->ext, &test_result_reset_operation);
+	else if ( sieve_command_is(cmd, cmd_test_result_print) )
+		sieve_operation_emit(cgenv->sblock, cmd->ext, &test_result_print_operation);
+	else
+		i_unreached();
+
+	return TRUE;
+}
+
+/*
+ * Intepretation
+ */
+
+static int cmd_test_result_reset_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED)
+{
+	sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS,
+			"testsuite: test_result_reset command; reset script result");
+
+	testsuite_result_reset(renv);
+	testsuite_smtp_reset();
+
+	return SIEVE_EXEC_OK;
+}
+
+static int cmd_test_result_print_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED)
+{
+	sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS,
+			"testsuite: test_result_print command; print script result ");
+
+	testsuite_result_print(renv);
+
+	return SIEVE_EXEC_OK;
+}
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/cmd-test-set.c
@@ -0,0 +1,161 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "str-sanitize.h"
+#include "istream.h"
+#include "istream-header-filter.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-code-dumper.h"
+#include "sieve-result.h"
+
+#include "testsuite-common.h"
+#include "testsuite-objects.h"
+
+#include <stdio.h>
+
+/*
+ * Test_set command
+ *
+ * Syntax
+ *   test_set <testsuite object (member): string> <value: string>
+ */
+
+static bool cmd_test_set_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_test_set_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+const struct sieve_command_def cmd_test_set = {
+	.identifier = "test_set",
+	.type = SCT_COMMAND,
+	.positional_args = 2,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = cmd_test_set_validate,
+	.generate = cmd_test_set_generate
+};
+
+/*
+ * Test_set operation
+ */
+
+static bool cmd_test_set_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_test_set_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def test_set_operation = {
+	.mnemonic = "TEST_SET",
+	.ext_def = &testsuite_extension,
+	.code = TESTSUITE_OPERATION_TEST_SET,
+	.dump = cmd_test_set_operation_dump,
+	.execute = cmd_test_set_operation_execute
+};
+
+/*
+ * Validation
+ */
+
+static bool cmd_test_set_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *arg = cmd->first_positional;
+
+	/* Check arguments */
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "object", 1, SAAT_STRING) ) {
+		return FALSE;
+	}
+
+	if ( !testsuite_object_argument_activate(valdtr, arg, cmd) )
+		return FALSE;
+
+	arg = sieve_ast_argument_next(arg);
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "value", 2, SAAT_STRING) ) {
+		return FALSE;
+	}
+
+	return sieve_validator_argument_activate(valdtr, cmd, arg, FALSE);
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_test_set_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	sieve_operation_emit(cgenv->sblock, cmd->ext, &test_set_operation);
+
+	/* Generate arguments */
+	return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_test_set_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "TEST SET:");
+	sieve_code_descend(denv);
+
+	return
+		testsuite_object_dump(denv, address) &&
+		sieve_opr_string_dump(denv, address, "value");
+}
+
+/*
+ * Intepretation
+ */
+
+static int cmd_test_set_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	struct testsuite_object tobj;
+	string_t *value;
+	int member_id;
+	int ret;
+
+	if ( !testsuite_object_read_member
+		(renv->sblock, address, &tobj, &member_id) ) {
+		sieve_runtime_trace_error(renv, "invalid testsuite object member");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	if ( (ret=sieve_opr_string_read(renv, address, "string", &value)) <= 0 )
+		return ret;
+
+	if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
+		sieve_runtime_trace(renv, 0, "testsuite: test_set command");
+		sieve_runtime_trace_descend(renv);
+		sieve_runtime_trace(renv, 0,
+			"set test parameter '%s' = \"%s\"",
+				testsuite_object_member_name(&tobj, member_id), str_c(value));
+	}
+
+	if ( tobj.def == NULL || tobj.def->set_member == NULL ) {
+		sieve_runtime_trace_error(renv, "unimplemented testsuite object");
+		return SIEVE_EXEC_FAILURE;
+	}
+
+	tobj.def->set_member(renv, member_id, value);
+	return SIEVE_EXEC_OK;
+}
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/cmd-test.c
@@ -0,0 +1,183 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+
+#include "testsuite-common.h"
+
+/*
+ * Test command
+ *
+ * Syntax:
+ *   test <test-name: string> <block>
+ */
+
+static bool cmd_test_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_test_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *md);
+
+const struct sieve_command_def cmd_test = {
+	.identifier = "test",
+	.type = SCT_COMMAND,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = TRUE,
+	.block_required = TRUE,
+	.validate = cmd_test_validate,
+	.generate = cmd_test_generate,
+};
+
+/*
+ * Test operations
+ */
+
+/* Test operation */
+
+static bool cmd_test_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_test_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def test_operation = {
+	.mnemonic = "TEST",
+	.ext_def = &testsuite_extension,
+	.code = TESTSUITE_OPERATION_TEST,
+	.dump = cmd_test_operation_dump,
+	.execute = cmd_test_operation_execute
+};
+
+/* Test_finish operation */
+
+static int cmd_test_finish_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def test_finish_operation = {
+	.mnemonic = "TEST-FINISH",
+	.ext_def = &testsuite_extension,
+	.code = TESTSUITE_OPERATION_TEST_FINISH,
+	.execute = cmd_test_finish_operation_execute
+};
+
+/*
+ * Validation
+ */
+
+static bool cmd_test_validate
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *arg = cmd->first_positional;
+
+ 	/* Check valid command placement */
+	if ( !sieve_command_is_toplevel(cmd) )
+	{
+		sieve_command_validate_error(valdtr, cmd,
+			"tests cannot be nested: test command must be issued at top-level");
+		return FALSE;
+	}
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "test-name", 1, SAAT_STRING) ) {
+		return FALSE;
+	}
+
+	return sieve_validator_argument_activate(valdtr, cmd, arg, FALSE);
+}
+
+/*
+ * Code generation
+ */
+
+static inline struct testsuite_generator_context *
+_get_generator_context(struct sieve_generator *gentr)
+{
+	return (struct testsuite_generator_context *)
+		sieve_generator_extension_get_context(gentr, testsuite_ext);
+}
+
+static bool cmd_test_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+	struct testsuite_generator_context *genctx =
+		_get_generator_context(cgenv->gentr);
+
+	sieve_operation_emit(cgenv->sblock, cmd->ext, &test_operation);
+
+	/* Generate arguments */
+	if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
+		return FALSE;
+
+	/* Prepare jumplist */
+	sieve_jumplist_reset(genctx->exit_jumps);
+
+	/* Test body */
+	if ( !sieve_generate_block(cgenv, cmd->ast_node) )
+		return FALSE;
+
+	sieve_operation_emit(cgenv->sblock, cmd->ext, &test_finish_operation);
+
+	/* Resolve exit jumps to this point */
+	sieve_jumplist_resolve(genctx->exit_jumps);
+
+	return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_test_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "TEST:");
+	sieve_code_descend(denv);
+
+	return
+		sieve_opr_string_dump(denv, address, "test name");
+}
+
+/*
+ * Intepretation
+ */
+
+static int cmd_test_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	string_t *test_name;
+	int ret;
+
+	if ( (ret=sieve_opr_string_read(renv, address, "test name", &test_name))
+		<= 0 )
+		return ret;
+
+	sieve_runtime_trace_sep(renv);
+	sieve_runtime_trace(renv, SIEVE_TRLVL_NONE,
+		"** Testsuite test start: \"%s\"", str_c(test_name));
+
+	testsuite_test_start(test_name);
+	return SIEVE_EXEC_OK;
+}
+
+static int cmd_test_finish_operation_execute
+(const struct sieve_runtime_env *renv ATTR_UNUSED,
+	sieve_size_t *address ATTR_UNUSED)
+{
+	sieve_runtime_trace(renv, SIEVE_TRLVL_NONE,
+		"** Testsuite test end");
+	sieve_runtime_trace_sep(renv);
+
+	testsuite_test_succeed(NULL);
+	return SIEVE_EXEC_OK;
+}
+
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/ext-testsuite.c
@@ -0,0 +1,175 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension testsuite
+ * -------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: vendor-specific
+ *   (FIXME: provide specification for test authors)
+ *
+ */
+
+/*
+ * Purpose: This custom extension is used to add sieve commands and tests that
+ *          act the Sieve engine and on the test suite itself. This practically
+ *          provides the means to completely control and thereby test the Sieve
+ *          compiler and interpreter. This extension transforms the basic Sieve
+ *          language into something much more powerful and suitable to perform
+ *          complex self-test operations. Of course, this extension is only
+ *          available (as vnd.dovecot.testsuite) when the sieve engine is used
+ *          from within the testsuite commandline tool. Test scripts have the
+ *          extension .svtest by convention to distinguish them from any normal
+ *          sieve scripts that may reside in the same directory.
+ *
+ * WARNING: Although this code can serve as an example on how to write
+ *          extensions to the Sieve interpreter, it is generally _NOT_ to be
+ *          used as a source for ideas on new Sieve extensions. Many of the
+ *          commands and tests that this extension introduces conflict with the
+ *          goal and the implied restrictions of the Sieve language. These
+ *          restrictions were put in place with good reason. Therefore, do
+ *          _NOT_ export functionality provided by this testsuite extension to
+ *          your custom extensions that are to be put to general use.
+ */
+
+#include <stdio.h>
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+
+#include "testsuite-common.h"
+#include "testsuite-variables.h"
+#include "testsuite-arguments.h"
+
+/*
+ * Operations
+ */
+
+const struct sieve_operation_def *testsuite_operations[] = {
+	&test_operation,
+	&test_finish_operation,
+	&test_fail_operation,
+	&test_config_set_operation,
+	&test_config_unset_operation,
+	&test_config_reload_operation,
+	&test_set_operation,
+	&test_script_compile_operation,
+	&test_script_run_operation,
+	&test_multiscript_operation,
+	&test_error_operation,
+	&test_result_action_operation,
+	&test_result_execute_operation,
+	&test_result_reset_operation,
+	&test_result_print_operation,
+	&test_message_smtp_operation,
+	&test_message_mailbox_operation,
+	&test_message_print_operation,
+	&test_mailbox_create_operation,
+	&test_mailbox_delete_operation,
+	&test_binary_load_operation,
+	&test_binary_save_operation,
+	&test_imap_metadata_set_operation
+};
+
+/*
+ * Operands
+ */
+
+const struct sieve_operand_def *testsuite_operands[] = {
+	&testsuite_object_operand,
+	&testsuite_substitution_operand,
+	&testsuite_namespace_operand
+};
+
+/*
+ * Extension
+ */
+
+/* Forward declarations */
+
+static bool ext_testsuite_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+static bool ext_testsuite_generator_load
+	(const struct sieve_extension *ext, const struct sieve_codegen_env *cgenv);
+static bool ext_testsuite_interpreter_load
+	(const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+		sieve_size_t *address);
+static bool ext_testsuite_binary_load
+	(const struct sieve_extension *ext, struct sieve_binary *sbin);
+
+/* Extension object */
+
+const struct sieve_extension_def testsuite_extension = {
+	.name = "vnd.dovecot.testsuite",
+	.validator_load = ext_testsuite_validator_load,
+	.generator_load = ext_testsuite_generator_load,
+	.interpreter_load = ext_testsuite_interpreter_load,
+	.binary_load = ext_testsuite_binary_load,
+	SIEVE_EXT_DEFINE_OPERATIONS(testsuite_operations),
+	SIEVE_EXT_DEFINE_OPERANDS(testsuite_operands)
+};
+
+/* Extension implementation */
+
+static bool ext_testsuite_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	sieve_validator_register_command(valdtr, ext, &cmd_test);
+	sieve_validator_register_command(valdtr, ext, &cmd_test_fail);
+	sieve_validator_register_command(valdtr, ext, &cmd_test_config_set);
+	sieve_validator_register_command(valdtr, ext, &cmd_test_config_unset);
+	sieve_validator_register_command(valdtr, ext, &cmd_test_config_reload);
+	sieve_validator_register_command(valdtr, ext, &cmd_test_set);
+	sieve_validator_register_command(valdtr, ext, &cmd_test_result_print);
+	sieve_validator_register_command(valdtr, ext, &cmd_test_result_reset);
+	sieve_validator_register_command(valdtr, ext, &cmd_test_message);
+	sieve_validator_register_command(valdtr, ext, &cmd_test_message_print);
+	sieve_validator_register_command(valdtr, ext, &cmd_test_mailbox_create);
+	sieve_validator_register_command(valdtr, ext, &cmd_test_mailbox_delete);
+	sieve_validator_register_command(valdtr, ext, &cmd_test_binary_load);
+	sieve_validator_register_command(valdtr, ext, &cmd_test_binary_save);
+	sieve_validator_register_command(valdtr, ext, &cmd_test_imap_metadata_set);
+
+	sieve_validator_register_command(valdtr, ext, &tst_test_script_compile);
+	sieve_validator_register_command(valdtr, ext, &tst_test_script_run);
+	sieve_validator_register_command(valdtr, ext, &tst_test_multiscript);
+	sieve_validator_register_command(valdtr, ext, &tst_test_error);
+	sieve_validator_register_command(valdtr, ext, &tst_test_result_action);
+	sieve_validator_register_command(valdtr, ext, &tst_test_result_execute);
+
+/*	sieve_validator_argument_override(valdtr, SAT_VAR_STRING, ext,
+		&testsuite_string_argument);*/
+
+	testsuite_variables_init(ext, valdtr);
+
+	return testsuite_validator_context_initialize(valdtr);
+}
+
+static bool ext_testsuite_generator_load
+(const struct sieve_extension *ext, const struct sieve_codegen_env *cgenv)
+{
+	return testsuite_generator_context_initialize(cgenv->gentr, ext);
+}
+
+static bool ext_testsuite_interpreter_load
+(const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+	sieve_size_t *address ATTR_UNUSED)
+{
+	return testsuite_interpreter_context_initialize(renv->interp, ext);
+}
+
+static bool ext_testsuite_binary_load
+(const struct sieve_extension *ext ATTR_UNUSED, struct sieve_binary *sbin ATTR_UNUSED)
+{
+	return TRUE;
+}
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/testsuite-arguments.c
@@ -0,0 +1,190 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-ast.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-dump.h"
+
+#include "testsuite-common.h"
+#include "testsuite-substitutions.h"
+#include "testsuite-arguments.h"
+
+#include <ctype.h>
+
+/*
+ * Testsuite string argument
+ */
+
+static bool arg_testsuite_string_validate
+	(struct sieve_validator *validator, struct sieve_ast_argument **arg,
+		struct sieve_command *context);
+
+const struct sieve_argument_def testsuite_string_argument = {
+	.identifier = "@testsuite-string",
+	.validate = arg_testsuite_string_validate,
+	.generate = sieve_arg_catenated_string_generate,
+};
+
+static bool arg_testsuite_string_validate
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	enum { ST_NONE, ST_OPEN, ST_SUBSTITUTION, ST_PARAM, ST_CLOSE } state =
+		ST_NONE;
+	pool_t pool = sieve_ast_pool((*arg)->ast);
+	struct sieve_arg_catenated_string *catstr = NULL;
+	string_t *str = sieve_ast_argument_str(*arg);
+	const char *p, *strstart, *substart = NULL;
+	const char *strval = (const char *) str_data(str);
+	const char *strend = strval + str_len(str);
+	bool result = TRUE;
+	string_t *subs_name = t_str_new(256);
+	string_t *subs_param = t_str_new(256);
+
+	T_BEGIN {
+		/* Initialize substitution structure */
+
+		p = strval;
+		strstart = p;
+		while ( result && p < strend ) {
+			switch ( state ) {
+
+			/* Nothing found yet */
+			case ST_NONE:
+				if ( *p == '%' ) {
+					substart = p;
+					state = ST_OPEN;
+					str_truncate(subs_name, 0);
+					str_truncate(subs_param, 0);
+				}
+				p++;
+				break;
+
+			/* Got '%' */
+			case ST_OPEN:
+				if ( *p == '{' ) {
+					state = ST_SUBSTITUTION;
+					p++;
+				} else
+					state = ST_NONE;
+				break;
+
+			/* Got '%{' */
+			case ST_SUBSTITUTION:
+				state = ST_PARAM;
+
+				while ( *p != '}' && *p != ':' ) {
+					if ( !i_isalnum(*p) ) {
+						state = ST_NONE;
+						break;
+					}
+					str_append_c(subs_name, *p);
+					p++;
+				}
+				break;
+
+			/* Got '%{name' */
+			case ST_PARAM:
+				if ( *p == ':' ) {
+					p++;
+					while ( *p != '}' ) {
+						str_append_c(subs_param, *p);
+						p++;
+					}
+				}
+				state = ST_CLOSE;
+				break;
+
+			/* Finished parsing param, expecting '}' */
+			case ST_CLOSE:
+				if ( *p == '}' ) {
+					struct sieve_ast_argument *strarg;
+
+					/* We now know that the substitution is valid */
+
+					if ( catstr == NULL ) {
+						catstr = sieve_arg_catenated_string_create(*arg);
+					}
+
+					/* Add the substring that is before the substitution to the
+					 * variable-string AST.
+					 */
+					if ( substart > strstart ) {
+						string_t *newstr = str_new(pool, substart - strstart);
+						str_append_data(newstr, strstart, substart - strstart);
+
+						strarg = sieve_ast_argument_string_create_raw
+							((*arg)->ast, newstr, (*arg)->source_line);
+						sieve_arg_catenated_string_add_element(catstr, strarg);
+
+						/* Give other substitution extensions a chance to do their work */
+						if ( !sieve_validator_argument_activate_super
+							(valdtr, cmd, strarg, FALSE) ) {
+							result = FALSE;
+							break;
+						}
+					}
+
+					strarg = testsuite_substitution_argument_create
+						(valdtr, (*arg)->ast, (*arg)->source_line, str_c(subs_name),
+							str_c(subs_param));
+
+					if ( strarg != NULL )
+						sieve_arg_catenated_string_add_element(catstr, strarg);
+					else {
+						sieve_argument_validate_error(valdtr, *arg,
+							"unknown testsuite substitution type '%s'", str_c(subs_name));
+					}
+
+					strstart = p + 1;
+					substart = strstart;
+
+					p++;
+				}
+
+				/* Finished, reset for the next substitution */
+				state = ST_NONE;
+			}
+		}
+	} T_END;
+
+	/* Bail out early if substitution is invalid */
+	if ( !result ) return FALSE;
+
+	/* Check whether any substitutions were found */
+	if ( catstr == NULL ) {
+		/* No substitutions in this string, pass it on to any other substution
+		 * extension.
+		 */
+		return sieve_validator_argument_activate_super(valdtr, cmd, *arg, TRUE);
+	}
+
+	/* Add the final substring that comes after the last substitution to the
+	 * variable-string AST.
+	 */
+	if ( strend > strstart ) {
+		struct sieve_ast_argument *strarg;
+		string_t *newstr = str_new(pool, strend - strstart);
+		str_append_data(newstr, strstart, strend - strstart);
+
+		strarg = sieve_ast_argument_string_create_raw
+			((*arg)->ast, newstr, (*arg)->source_line);
+		sieve_arg_catenated_string_add_element(catstr, strarg);
+
+		/* Give other substitution extensions a chance to do their work */
+		if ( !sieve_validator_argument_activate_super
+			(valdtr, cmd, strarg, FALSE) )
+			return FALSE;
+	}
+
+	return TRUE;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/testsuite-arguments.h
@@ -0,0 +1,6 @@
+#ifndef TESTSUITE_ARGUMENTS_H
+#define TESTSUITE_ARGUMENTS_H
+
+extern const struct sieve_argument_def testsuite_string_argument;
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/testsuite-binary.c
@@ -0,0 +1,82 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "mempool.h"
+#include "imem.h"
+#include "array.h"
+#include "strfuncs.h"
+#include "unlink-directory.h"
+
+#include "sieve.h"
+#include "sieve-common.h"
+#include "sieve-script.h"
+#include "sieve-binary.h"
+#include "sieve-error.h"
+
+#include "testsuite-common.h"
+#include "testsuite-binary.h"
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+/*
+ * State
+ */
+
+static char *testsuite_binary_tmp = NULL;
+
+/*
+ * Initialization
+ */
+
+void testsuite_binary_init(void)
+{
+	testsuite_binary_tmp = i_strconcat
+		(testsuite_tmp_dir_get(), "/binaries", NULL);
+
+	if ( mkdir(testsuite_binary_tmp, 0700) < 0 ) {
+		i_fatal("failed to create temporary directory '%s': %m.",
+			testsuite_binary_tmp);
+	}
+}
+
+void testsuite_binary_deinit(void)
+{
+	const char *error;
+
+	if ( unlink_directory(testsuite_binary_tmp, UNLINK_DIRECTORY_FLAG_RMDIR, &error) < 0 ) {
+		i_warning("failed to remove temporary directory '%s': %s.",
+			testsuite_binary_tmp, error);
+	}
+
+	i_free(testsuite_binary_tmp);
+}
+
+void testsuite_binary_reset(void)
+{
+	testsuite_binary_init();
+	testsuite_binary_deinit();
+}
+
+/*
+ * Binary Access
+ */
+
+bool testsuite_binary_save(struct sieve_binary *sbin, const char *name)
+{
+	return ( sieve_save_as(sbin, t_strdup_printf
+		("%s/%s", testsuite_binary_tmp, sieve_binfile_from_name(name)), TRUE,
+			0600, NULL) > 0 );
+}
+
+struct sieve_binary *testsuite_binary_load(const char *name)
+{
+	struct sieve_instance *svinst = testsuite_sieve_instance;
+
+	return sieve_load(svinst, t_strdup_printf
+		("%s/%s", testsuite_binary_tmp, sieve_binfile_from_name(name)), NULL);
+}
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/testsuite-binary.h
@@ -0,0 +1,17 @@
+#ifndef TESTSUITE_BINARY_H
+#define TESTSUITE_BINARY_H
+
+#include "sieve-common.h"
+
+void testsuite_binary_init(void);
+void testsuite_binary_deinit(void);
+void testsuite_binary_reset(void);
+
+/*
+ * Binary Access
+ */
+
+bool testsuite_binary_save(struct sieve_binary *sbin, const char *name);
+struct sieve_binary *testsuite_binary_load(const char *name);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/testsuite-common.c
@@ -0,0 +1,314 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "string.h"
+#include "ostream.h"
+#include "hash.h"
+#include "mail-storage.h"
+#include "env-util.h"
+#include "unlink-directory.h"
+
+#include "mail-raw.h"
+
+#include "sieve-common.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+#include "sieve-commands.h"
+#include "sieve-extensions.h"
+#include "sieve-binary.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+#include "sieve-dump.h"
+
+#include "testsuite-common.h"
+#include "testsuite-settings.h"
+#include "testsuite-objects.h"
+#include "testsuite-log.h"
+#include "testsuite-script.h"
+#include "testsuite-binary.h"
+#include "testsuite-result.h"
+#include "testsuite-smtp.h"
+
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+/*
+ * Global data
+ */
+
+struct sieve_instance *testsuite_sieve_instance = NULL;
+char *testsuite_test_path = NULL;
+
+/* Test context */
+
+static string_t *test_name;
+static unsigned int test_index;
+static unsigned int test_failures;
+
+/* Extension */
+
+const struct sieve_extension *testsuite_ext;
+
+/*
+ * Validator context
+ */
+
+bool testsuite_validator_context_initialize(struct sieve_validator *valdtr)
+{
+	pool_t pool = sieve_validator_pool(valdtr);
+	struct testsuite_validator_context *ctx =
+		p_new(pool, struct testsuite_validator_context, 1);
+
+	/* Setup object registry */
+	ctx->object_registrations = sieve_validator_object_registry_create(valdtr);
+	testsuite_register_core_objects(ctx);
+
+	sieve_validator_extension_set_context(valdtr, testsuite_ext, ctx);
+
+	return TRUE;
+}
+
+struct testsuite_validator_context *testsuite_validator_context_get
+(struct sieve_validator *valdtr)
+{
+	return (struct testsuite_validator_context *)
+		sieve_validator_extension_get_context(valdtr, testsuite_ext);
+}
+
+/*
+ * Generator context
+ */
+
+bool testsuite_generator_context_initialize
+(struct sieve_generator *gentr, const struct sieve_extension *this_ext)
+{
+	pool_t pool = sieve_generator_pool(gentr);
+	struct sieve_binary_block *sblock = sieve_generator_get_block(gentr);
+	struct testsuite_generator_context *ctx =
+		p_new(pool, struct testsuite_generator_context, 1);
+
+	/* Setup exit jumplist */
+	ctx->exit_jumps = sieve_jumplist_create(pool, sblock);
+
+	sieve_generator_extension_set_context(gentr, this_ext, ctx);
+
+	return TRUE;
+}
+
+/*
+ * Interpreter context
+ */
+
+static void testsuite_interpreter_free
+(const struct sieve_extension *ext ATTR_UNUSED,
+	struct sieve_interpreter *interp ATTR_UNUSED, void *context)
+{
+	struct testsuite_interpreter_context *ctx =
+		(struct testsuite_interpreter_context *)context;
+
+	if ( ctx->compiled_script != NULL )
+		sieve_binary_unref(&ctx->compiled_script);
+}
+
+const struct sieve_interpreter_extension
+testsuite_interpreter_ext = {
+	.ext_def = &testsuite_extension,
+	.free = testsuite_interpreter_free,
+};
+
+bool testsuite_interpreter_context_initialize
+(struct sieve_interpreter *interp, const struct sieve_extension *this_ext)
+{
+	pool_t pool = sieve_interpreter_pool(interp);
+	struct testsuite_interpreter_context *ctx =
+		p_new(pool, struct testsuite_interpreter_context, 1);
+
+	sieve_interpreter_extension_register
+		(interp, this_ext, &testsuite_interpreter_ext, ctx);
+	return TRUE;
+}
+
+struct testsuite_interpreter_context *testsuite_interpreter_context_get
+(struct sieve_interpreter *interp, const struct sieve_extension *this_ext)
+{
+	struct testsuite_interpreter_context *ctx =
+		sieve_interpreter_extension_get_context(interp, this_ext);
+
+	return ctx;
+}
+
+/*
+ * Test context
+ */
+
+static void testsuite_test_context_init(void)
+{
+	test_name = str_new(default_pool, 128);
+	test_index = 0;
+	test_failures = 0;
+}
+
+void testsuite_test_start(string_t *name)
+{
+	str_truncate(test_name, 0);
+	str_append_str(test_name, name);
+
+	test_index++;
+}
+
+void testsuite_test_fail(string_t *reason)
+{
+	testsuite_test_fail_cstr(str_c(reason));
+}
+
+void testsuite_test_failf(const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+
+	testsuite_test_fail_cstr(t_strdup_vprintf(fmt, args));
+
+	va_end(args);
+}
+
+void testsuite_test_fail_cstr(const char *reason)
+{
+	if ( str_len(test_name) == 0 ) {
+		if ( reason == NULL || *reason == '\0' )
+			printf("%2d: Test FAILED\n", test_index);
+		else
+			printf("%2d: Test FAILED: %s\n", test_index, reason);
+	} else {
+		if ( reason == NULL || *reason == '\0' )
+			printf("%2d: Test '%s' FAILED\n", test_index, str_c(test_name));
+		else
+			printf("%2d: Test '%s' FAILED: %s\n", test_index,
+				str_c(test_name), reason);
+	}
+
+	str_truncate(test_name, 0);
+
+	test_failures++;
+}
+
+void testsuite_testcase_fail(const char *reason)
+{
+	if ( reason == NULL || *reason == '\0' )
+		printf("XX: Test CASE FAILED\n");
+	else
+		printf("XX: Test CASE FAILED: %s\n", reason);
+
+	test_failures++;
+}
+
+void testsuite_test_succeed(string_t *reason)
+{
+	if ( str_len(test_name) == 0 ) {
+		if ( reason == NULL || str_len(reason) == 0 )
+			printf("%2d: Test SUCCEEDED\n", test_index);
+		else
+			printf("%2d: Test SUCCEEDED: %s\n", test_index, str_c(reason));
+	} else {
+		if ( reason == NULL || str_len(reason) == 0 )
+			printf("%2d: Test '%s' SUCCEEDED\n", test_index, str_c(test_name));
+		else
+			printf("%2d: Test '%s' SUCCEEDED: %s\n", test_index,
+				str_c(test_name), str_c(reason));
+	}
+	str_truncate(test_name, 0);
+}
+
+static void testsuite_test_context_deinit(void)
+{
+	str_free(&test_name);
+}
+
+bool testsuite_testcase_result(void)
+{
+	if ( test_failures > 0 ) {
+		printf("\nFAIL: %d of %d tests failed.\n\n", test_failures, test_index);
+		return FALSE;
+	}
+
+	printf("\nPASS: %d tests succeeded.\n\n", test_index);
+	return TRUE;
+}
+
+/*
+ * Testsuite temporary directory
+ */
+
+static char *testsuite_tmp_dir;
+
+static void testsuite_tmp_dir_init(void)
+{
+	testsuite_tmp_dir = i_strdup_printf
+		("/tmp/dsieve-testsuite.%s.%s", dec2str(time(NULL)), dec2str(getpid()));
+
+	if ( mkdir(testsuite_tmp_dir, 0700) < 0 ) {
+		i_fatal("failed to create temporary directory '%s': %m.",
+			testsuite_tmp_dir);
+	}
+}
+
+static void testsuite_tmp_dir_deinit(void)
+{
+	const char *error;
+
+	if ( unlink_directory(testsuite_tmp_dir, UNLINK_DIRECTORY_FLAG_RMDIR, &error) < 0 )
+		i_warning("failed to remove temporary directory '%s': %s.",
+			testsuite_tmp_dir, error);
+
+	i_free(testsuite_tmp_dir);
+}
+
+const char *testsuite_tmp_dir_get(void)
+{
+	return testsuite_tmp_dir;
+}
+
+/*
+ * Main testsuite init/deinit
+ */
+
+void testsuite_init
+(struct sieve_instance *svinst, const char *test_path, bool log_stdout)
+{
+	testsuite_sieve_instance = svinst;
+
+	testsuite_test_context_init();
+	testsuite_log_init(log_stdout);
+	testsuite_tmp_dir_init();
+
+	testsuite_script_init();
+	testsuite_binary_init();
+	testsuite_smtp_init();
+
+	testsuite_ext = sieve_extension_register
+		(svinst, &testsuite_extension, TRUE);
+
+
+	testsuite_test_path = i_strdup(test_path);
+}
+
+void testsuite_deinit(void)
+{
+	i_free(testsuite_test_path);
+
+	testsuite_smtp_deinit();
+	testsuite_binary_deinit();
+	testsuite_script_deinit();
+
+	testsuite_tmp_dir_deinit();
+	testsuite_log_deinit();
+	testsuite_test_context_deinit();
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/testsuite-common.h
@@ -0,0 +1,186 @@
+#ifndef TESTSUITE_COMMON_H
+#define TESTSUITE_COMMON_H
+
+#include "sieve-common.h"
+
+#include "sieve-tool.h"
+
+/*
+ * Global data
+ */
+
+extern struct sieve_instance *testsuite_sieve_instance;
+
+extern const struct sieve_extension_def testsuite_extension;
+
+extern const struct sieve_extension *testsuite_ext;
+
+extern const struct sieve_script_env *testsuite_scriptenv;
+
+extern char *testsuite_test_path;
+
+
+/*
+ * Validator context
+ */
+
+struct testsuite_validator_context {
+	struct sieve_validator_object_registry *object_registrations;
+};
+
+bool testsuite_validator_context_initialize(struct sieve_validator *valdtr);
+struct testsuite_validator_context *testsuite_validator_context_get
+	(struct sieve_validator *valdtr);
+
+/*
+ * Generator context
+ */
+
+struct testsuite_generator_context {
+	struct sieve_jumplist *exit_jumps;
+};
+
+bool testsuite_generator_context_initialize
+	(struct sieve_generator *gentr, const struct sieve_extension *this_ext);
+
+/*
+ * Interpreter context
+ */
+
+struct testsuite_interpreter_context {
+	struct sieve_binary *compiled_script;
+};
+
+bool testsuite_interpreter_context_initialize
+	(struct sieve_interpreter *interp, const struct sieve_extension *this_ext);
+struct testsuite_interpreter_context *testsuite_interpreter_context_get
+	(struct sieve_interpreter *interp, const struct sieve_extension *this_ext);
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def cmd_test;
+extern const struct sieve_command_def cmd_test_fail;
+extern const struct sieve_command_def cmd_test_config_set;
+extern const struct sieve_command_def cmd_test_config_unset;
+extern const struct sieve_command_def cmd_test_config_reload;
+extern const struct sieve_command_def cmd_test_set;
+extern const struct sieve_command_def cmd_test_result_reset;
+extern const struct sieve_command_def cmd_test_result_print;
+extern const struct sieve_command_def cmd_test_message;
+extern const struct sieve_command_def cmd_test_message_print;
+extern const struct sieve_command_def cmd_test_mailbox;
+extern const struct sieve_command_def cmd_test_mailbox_create;
+extern const struct sieve_command_def cmd_test_mailbox_delete;
+extern const struct sieve_command_def cmd_test_binary_load;
+extern const struct sieve_command_def cmd_test_binary_save;
+extern const struct sieve_command_def cmd_test_imap_metadata_set;
+
+/*
+ * Tests
+ */
+
+extern const struct sieve_command_def tst_test_script_compile;
+extern const struct sieve_command_def tst_test_script_run;
+extern const struct sieve_command_def tst_test_multiscript;
+extern const struct sieve_command_def tst_test_error;
+extern const struct sieve_command_def tst_test_result_action;
+extern const struct sieve_command_def tst_test_result_execute;
+
+/*
+ * Operations
+ */
+
+enum testsuite_operation_code {
+	TESTSUITE_OPERATION_TEST,
+	TESTSUITE_OPERATION_TEST_FINISH,
+	TESTSUITE_OPERATION_TEST_FAIL,
+	TESTSUITE_OPERATION_TEST_CONFIG_SET,
+	TESTSUITE_OPERATION_TEST_CONFIG_UNSET,
+	TESTSUITE_OPERATION_TEST_CONFIG_RELOAD,
+	TESTSUITE_OPERATION_TEST_SET,
+	TESTSUITE_OPERATION_TEST_SCRIPT_COMPILE,
+	TESTSUITE_OPERATION_TEST_SCRIPT_RUN,
+	TESTSUITE_OPERATION_TEST_MULTISCRIPT,
+	TESTSUITE_OPERATION_TEST_ERROR,
+	TESTSUITE_OPERATION_TEST_RESULT_ACTION,
+	TESTSUITE_OPERATION_TEST_RESULT_EXECUTE,
+	TESTSUITE_OPERATION_TEST_RESULT_RESET,
+	TESTSUITE_OPERATION_TEST_RESULT_PRINT,
+	TESTSUITE_OPERATION_TEST_MESSAGE_SMTP,
+	TESTSUITE_OPERATION_TEST_MESSAGE_MAILBOX,
+	TESTSUITE_OPERATION_TEST_MESSAGE_PRINT,
+	TESTSUITE_OPERATION_TEST_MAILBOX_CREATE,
+	TESTSUITE_OPERATION_TEST_MAILBOX_DELETE,
+	TESTSUITE_OPERATION_TEST_BINARY_LOAD,
+	TESTSUITE_OPERATION_TEST_BINARY_SAVE,
+	TESTSUITE_OPERATION_TEST_IMAP_METADATA_SET
+};
+
+extern const struct sieve_operation_def test_operation;
+extern const struct sieve_operation_def test_finish_operation;
+extern const struct sieve_operation_def test_fail_operation;
+extern const struct sieve_operation_def test_config_set_operation;
+extern const struct sieve_operation_def test_config_unset_operation;
+extern const struct sieve_operation_def test_config_reload_operation;
+extern const struct sieve_operation_def test_set_operation;
+extern const struct sieve_operation_def test_script_compile_operation;
+extern const struct sieve_operation_def test_script_run_operation;
+extern const struct sieve_operation_def test_multiscript_operation;
+extern const struct sieve_operation_def test_error_operation;
+extern const struct sieve_operation_def test_result_action_operation;
+extern const struct sieve_operation_def test_result_execute_operation;
+extern const struct sieve_operation_def test_result_reset_operation;
+extern const struct sieve_operation_def test_result_print_operation;
+extern const struct sieve_operation_def test_message_smtp_operation;
+extern const struct sieve_operation_def test_message_mailbox_operation;
+extern const struct sieve_operation_def test_message_print_operation;
+extern const struct sieve_operation_def test_mailbox_create_operation;
+extern const struct sieve_operation_def test_mailbox_delete_operation;
+extern const struct sieve_operation_def test_binary_load_operation;
+extern const struct sieve_operation_def test_binary_save_operation;
+extern const struct sieve_operation_def test_imap_metadata_set_operation;
+
+/*
+ * Operands
+ */
+
+extern const struct sieve_operand_def testsuite_object_operand;
+extern const struct sieve_operand_def testsuite_substitution_operand;
+
+enum testsuite_operand_code {
+	TESTSUITE_OPERAND_OBJECT,
+	TESTSUITE_OPERAND_SUBSTITUTION,
+	TESTSUITE_OPERAND_NAMESPACE
+};
+
+/*
+ * Test context
+ */
+
+void testsuite_test_start(string_t *name);
+void testsuite_test_fail(string_t *reason);
+void testsuite_test_failf(const char *fmt, ...) ATTR_FORMAT(1, 2);
+void testsuite_test_fail_cstr(const char *reason);
+
+void testsuite_test_succeed(string_t *reason);
+
+void testsuite_testcase_fail(const char *reason);
+bool testsuite_testcase_result(void);
+
+/*
+ * Testsuite temporary directory
+ */
+
+const char *testsuite_tmp_dir_get(void);
+
+/*
+ * Testsuite init/deinit
+ */
+
+void testsuite_init
+	(struct sieve_instance *svinst, const char *test_path, bool log_stdout);
+void testsuite_deinit(void);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/testsuite-log.c
@@ -0,0 +1,291 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-error-private.h"
+
+#include "testsuite-common.h"
+#include "testsuite-log.h"
+
+/*
+ * Configuration
+ */
+
+bool _testsuite_log_stdout = FALSE;
+
+/*
+ * Testsuite log error handlers
+ */
+
+struct sieve_error_handler *testsuite_log_ehandler = NULL;
+struct sieve_error_handler *testsuite_log_main_ehandler = NULL;
+
+struct _testsuite_log_message {
+	const char *location;
+	const char *message;
+};
+
+static pool_t _testsuite_logmsg_pool = NULL;
+ARRAY(struct _testsuite_log_message) _testsuite_log_errors;
+ARRAY(struct _testsuite_log_message) _testsuite_log_warnings;
+ARRAY(struct _testsuite_log_message) _testsuite_log_messages;
+
+static inline void ATTR_FORMAT(3, 0) _testsuite_stdout_vlog
+(const char *prefix, const char *location, const char *fmt,
+	va_list args)
+{
+	if ( _testsuite_log_stdout ) {
+		va_list args_copy;
+
+		VA_COPY(args_copy, args);
+		if ( location == NULL || *location == '\0' )
+			fprintf(stdout,
+				"LOG: %s: %s\n", prefix, t_strdup_vprintf(fmt, args_copy));
+		else
+			fprintf(stdout,
+				"LOG: %s: %s: %s\n", prefix, location, t_strdup_vprintf(fmt, args_copy));
+		va_end(args_copy);
+	}
+}
+
+static void ATTR_FORMAT(4, 0) _testsuite_log_verror
+(struct sieve_error_handler *ehandler ATTR_UNUSED,
+	unsigned int flags ATTR_UNUSED, const char *location, const char *fmt,
+	va_list args)
+{
+	pool_t pool = _testsuite_logmsg_pool;
+	struct _testsuite_log_message msg;
+
+	_testsuite_stdout_vlog("error", location, fmt, args);
+
+	msg.location = p_strdup(pool, location);
+	msg.message = p_strdup_vprintf(pool, fmt, args);
+
+	array_append(&_testsuite_log_errors, &msg, 1);
+}
+
+static void ATTR_FORMAT(4, 0) _testsuite_log_main_verror
+(struct sieve_error_handler *ehandler ATTR_UNUSED,
+	unsigned int flags ATTR_UNUSED, const char *location, const char *fmt,
+	va_list args)
+{
+	if ( location == NULL || *location == '\0' )
+		fprintf(stderr,
+			"error: %s\n", t_strdup_vprintf(fmt, args));
+	else
+		fprintf(stderr,
+			"%s: error: %s\n", location, t_strdup_vprintf(fmt, args));
+}
+
+static void ATTR_FORMAT(4, 0) _testsuite_log_vwarning
+(struct sieve_error_handler *ehandler ATTR_UNUSED,
+	unsigned int flags ATTR_UNUSED, const char *location, const char *fmt,
+	va_list args)
+{
+	pool_t pool = _testsuite_logmsg_pool;
+	struct _testsuite_log_message msg;
+
+	_testsuite_stdout_vlog("warning", location, fmt, args);
+
+	msg.location = p_strdup(pool, location);
+	msg.message = p_strdup_vprintf(pool, fmt, args);
+
+	array_append(&_testsuite_log_warnings, &msg, 1);
+}
+
+static void ATTR_FORMAT(4, 0) _testsuite_log_vinfo
+(struct sieve_error_handler *ehandler ATTR_UNUSED,
+	unsigned int flags ATTR_UNUSED, const char *location, const char *fmt,
+	va_list args)
+{
+	pool_t pool = _testsuite_logmsg_pool;
+	struct _testsuite_log_message msg;
+
+	_testsuite_stdout_vlog("info", location, fmt, args);
+
+	msg.location = p_strdup(pool, location);
+	msg.message = p_strdup_vprintf(pool, fmt, args);
+
+	array_append(&_testsuite_log_messages, &msg, 1);
+}
+
+
+static void ATTR_FORMAT(4, 0) _testsuite_log_vdebug
+(struct sieve_error_handler *ehandler ATTR_UNUSED,
+	unsigned int flags ATTR_UNUSED, const char *location, const char *fmt,
+	va_list args)
+{
+	_testsuite_stdout_vlog("debug", location, fmt, args);
+}
+
+static struct sieve_error_handler *_testsuite_log_ehandler_create(void)
+{
+	pool_t pool;
+	struct sieve_error_handler *ehandler;
+
+	pool = pool_alloconly_create
+		("testsuite_log_ehandler", sizeof(struct sieve_error_handler));
+	ehandler = p_new(pool, struct sieve_error_handler, 1);
+	sieve_error_handler_init(ehandler, testsuite_sieve_instance, pool, 0);
+
+	ehandler->verror = _testsuite_log_verror;
+	ehandler->vwarning = _testsuite_log_vwarning;
+	ehandler->vinfo = _testsuite_log_vinfo;
+	ehandler->vdebug = _testsuite_log_vdebug;
+
+	return ehandler;
+}
+
+static struct sieve_error_handler *_testsuite_log_main_ehandler_create(void)
+{
+	pool_t pool;
+	struct sieve_error_handler *ehandler;
+
+	pool = pool_alloconly_create
+		("testsuite_log_main_ehandler", sizeof(struct sieve_error_handler));
+	ehandler = p_new(pool, struct sieve_error_handler, 1);
+	sieve_error_handler_init(ehandler, testsuite_sieve_instance, pool, 0);
+
+	ehandler->verror = _testsuite_log_main_verror;
+	ehandler->vwarning = _testsuite_log_vwarning;
+	ehandler->vinfo = _testsuite_log_vinfo;
+	ehandler->vdebug = _testsuite_log_vdebug;
+
+	return ehandler;
+}
+
+/*
+ *
+ */
+
+void testsuite_log_clear_messages(void)
+{
+	if ( _testsuite_logmsg_pool != NULL ) {
+		if ( array_count(&_testsuite_log_errors) == 0 )
+			return;
+		pool_unref(&_testsuite_logmsg_pool);
+	}
+
+	_testsuite_logmsg_pool = pool_alloconly_create
+		("testsuite_log_messages", 8192);
+
+	p_array_init(&_testsuite_log_errors, _testsuite_logmsg_pool, 128);
+	p_array_init(&_testsuite_log_warnings, _testsuite_logmsg_pool, 128);
+	p_array_init(&_testsuite_log_messages, _testsuite_logmsg_pool, 128);
+
+	sieve_error_handler_reset(testsuite_log_ehandler);
+}
+
+/*
+ *
+ */
+
+void testsuite_log_init(bool log_stdout)
+{
+	_testsuite_log_stdout = log_stdout;
+
+	testsuite_log_ehandler = _testsuite_log_ehandler_create();
+	sieve_error_handler_accept_infolog(testsuite_log_ehandler, TRUE);
+	sieve_error_handler_accept_debuglog(testsuite_log_ehandler, TRUE);
+
+	testsuite_log_main_ehandler = _testsuite_log_main_ehandler_create();
+	sieve_error_handler_accept_infolog(testsuite_log_main_ehandler, TRUE);
+	sieve_error_handler_accept_debuglog(testsuite_log_main_ehandler, TRUE);
+
+	sieve_system_ehandler_set(testsuite_log_ehandler);
+
+	testsuite_log_clear_messages();
+}
+
+void testsuite_log_deinit(void)
+{
+	sieve_error_handler_unref(&testsuite_log_ehandler);
+	sieve_error_handler_unref(&testsuite_log_main_ehandler);
+
+	pool_unref(&_testsuite_logmsg_pool);
+}
+
+/*
+ * Log stringlist
+ */
+
+/* Forward declarations */
+
+static int testsuite_log_stringlist_next_item
+	(struct sieve_stringlist *_strlist, string_t **str_r);
+static void testsuite_log_stringlist_reset
+	(struct sieve_stringlist *_strlist);
+
+/* Stringlist object */
+
+struct testsuite_log_stringlist {
+	struct sieve_stringlist strlist;
+
+	int pos, index;
+};
+
+struct sieve_stringlist *testsuite_log_stringlist_create
+(const struct sieve_runtime_env *renv, int index)
+{
+	struct testsuite_log_stringlist *strlist;
+
+	strlist = t_new(struct testsuite_log_stringlist, 1);
+	strlist->strlist.runenv = renv;
+	strlist->strlist.exec_status = SIEVE_EXEC_OK;
+	strlist->strlist.next_item = testsuite_log_stringlist_next_item;
+	strlist->strlist.reset = testsuite_log_stringlist_reset;
+
+ 	strlist->index = index;
+	strlist->pos = 0;
+
+	return &strlist->strlist;
+}
+
+static int testsuite_log_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+	struct testsuite_log_stringlist *strlist =
+		(struct testsuite_log_stringlist *) _strlist;
+	const struct _testsuite_log_message *msg;
+	int pos;
+
+	*str_r = NULL;
+
+	if ( strlist->pos < 0 )
+		return 0;
+
+	if ( strlist->index > 0 ) {
+		pos = strlist->index - 1;
+		strlist->pos = -1;
+	} else {
+		pos = strlist->pos++;
+	}
+
+	if ( pos >= (int) array_count(&_testsuite_log_errors) ) {
+		strlist->pos = -1;
+		return 0;
+	}
+
+	msg = array_idx(&_testsuite_log_errors, (unsigned int) pos);
+
+	*str_r = t_str_new_const(msg->message, strlen(msg->message));
+	return 1;
+}
+
+static void testsuite_log_stringlist_reset
+(struct sieve_stringlist *_strlist)
+{
+	struct testsuite_log_stringlist *strlist =
+		(struct testsuite_log_stringlist *) _strlist;
+
+	strlist->pos = 0;
+}
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/testsuite-log.h
@@ -0,0 +1,25 @@
+#ifndef TESTSUITE_LOG_H
+#define TESTSUITE_LOG_H
+
+#include "sieve-common.h"
+
+extern struct sieve_error_handler *testsuite_log_ehandler;
+extern struct sieve_error_handler *testsuite_log_main_ehandler;
+
+/*
+ * Initialization
+ */
+
+void testsuite_log_init(bool log_stdout);
+void testsuite_log_deinit(void);
+
+/*
+ * Access
+ */
+
+void testsuite_log_clear_messages(void);
+
+struct sieve_stringlist *testsuite_log_stringlist_create
+	(const struct sieve_runtime_env *renv, int index);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/testsuite-mailstore.c
@@ -0,0 +1,292 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "mempool.h"
+#include "imem.h"
+#include "array.h"
+#include "strfuncs.h"
+#include "str-sanitize.h"
+#include "path-util.h"
+#include "unlink-directory.h"
+#include "env-util.h"
+#include "mail-namespace.h"
+#include "mail-storage.h"
+#include "imap-metadata.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-interpreter.h"
+
+#include "testsuite-message.h"
+#include "testsuite-common.h"
+#include "testsuite-smtp.h"
+
+#include "testsuite-mailstore.h"
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+/*
+ * Forward declarations
+ */
+
+static void testsuite_mailstore_close(void);
+
+/*
+ * State
+ */
+
+static struct mail_user *testsuite_mailstore_user = NULL;
+
+static char *testsuite_mailstore_location = NULL;
+static char *testsuite_mailstore_attrs = NULL;
+
+static char *testsuite_mailstore_folder = NULL;
+static struct mailbox *testsuite_mailstore_box = NULL;
+static struct mailbox_transaction_context *testsuite_mailstore_trans = NULL;
+static struct mail *testsuite_mailstore_mail = NULL;
+
+/*
+ * Initialization
+ */
+
+void testsuite_mailstore_init(void)
+{
+	struct mail_user *mail_user_dovecot, *mail_user;
+	struct mail_namespace *ns;
+	struct mail_namespace_settings *ns_set;
+	struct mail_storage_settings *mail_set;
+	const char *tmpdir, *error, *cwd;
+
+	tmpdir = testsuite_tmp_dir_get();
+	testsuite_mailstore_location =
+		i_strconcat(tmpdir, "/mailstore", NULL);
+	testsuite_mailstore_attrs =
+		i_strconcat(tmpdir, "/mail-attrs.dict", NULL);
+
+	if ( mkdir(testsuite_mailstore_location, 0700) < 0 ) {
+		i_fatal("failed to create temporary directory '%s': %m.",
+			testsuite_mailstore_location);
+	}
+	
+	mail_user_dovecot = sieve_tool_get_mail_user(sieve_tool);
+	mail_user = mail_user_alloc(NULL, "testsuite-mail-user@example.org",
+		mail_user_dovecot->set_info, mail_user_dovecot->unexpanded_set);
+	mail_user->autocreated = TRUE;
+	if (t_get_working_dir(&cwd, &error) < 0)
+		i_fatal("Failed to get working directory: %s", error);
+	mail_user_set_home(mail_user, cwd);
+	if (mail_user_init(mail_user, &error) < 0)
+		i_fatal("Testsuite user initialization failed: %s", error);
+
+	ns_set = p_new(mail_user->pool, struct mail_namespace_settings, 1);
+	ns_set->location = testsuite_mailstore_location;
+	ns_set->separator = ".";
+
+	ns = mail_namespaces_init_empty(mail_user);
+	ns->flags |= NAMESPACE_FLAG_INBOX_USER;
+	ns->set = ns_set;
+	/* absolute paths are ok with raw storage */
+	mail_set = p_new(mail_user->pool, struct mail_storage_settings, 1);
+	*mail_set = *ns->mail_set;
+	mail_set->mail_location = p_strconcat
+		(mail_user->pool, "maildir:", testsuite_mailstore_location, NULL);
+	mail_set->mail_attribute_dict = p_strconcat
+		(mail_user->pool, "file:", testsuite_mailstore_attrs, NULL);
+	ns->mail_set = mail_set;
+
+	if (mail_storage_create(ns, "maildir", 0, &error) < 0)
+		i_fatal("Couldn't create testsuite storage: %s", error);
+	if (mail_namespaces_init_finish(ns, &error) < 0)
+		i_fatal("Couldn't create testsuite namespace: %s", error);
+
+	testsuite_mailstore_user = mail_user;
+}
+
+void testsuite_mailstore_deinit(void)
+{
+	const char *error;
+
+	testsuite_mailstore_close();
+
+	if ( unlink_directory(testsuite_mailstore_location, UNLINK_DIRECTORY_FLAG_RMDIR, &error) < 0 ) {
+		i_warning("failed to remove temporary directory '%s': %s.",
+			testsuite_mailstore_location, error);
+	}
+
+	i_free(testsuite_mailstore_location);
+	i_free(testsuite_mailstore_attrs);
+	mail_user_unref(&testsuite_mailstore_user);
+}
+
+void testsuite_mailstore_reset(void)
+{
+}
+
+/*
+ * Mail user
+ */
+
+struct mail_user *testsuite_mailstore_get_user(void)
+{
+	if (testsuite_mailstore_user == NULL)
+		return sieve_tool_get_mail_user(sieve_tool);
+	return testsuite_mailstore_user;
+}
+
+/*
+ * Mailbox Access
+ */
+
+bool testsuite_mailstore_mailbox_create
+(const struct sieve_runtime_env *renv ATTR_UNUSED, const char *folder)
+{
+	struct mail_user *mail_user = testsuite_mailstore_user;
+	struct mail_namespace *ns = mail_user->namespaces;
+	struct mailbox *box;
+
+	box = mailbox_alloc(ns->list, folder, 0);
+
+	if ( mailbox_create(box, NULL, FALSE) < 0 ) {
+		mailbox_free(&box);
+		return FALSE;
+	}
+
+	mailbox_free(&box);
+
+	return TRUE;
+}
+
+static void testsuite_mailstore_close(void)
+{
+	if ( testsuite_mailstore_mail != NULL )
+		mail_free(&testsuite_mailstore_mail);
+
+	if ( testsuite_mailstore_trans != NULL )
+		mailbox_transaction_rollback(&testsuite_mailstore_trans);
+
+	if ( testsuite_mailstore_box != NULL )
+		mailbox_free(&testsuite_mailstore_box);
+
+	if ( testsuite_mailstore_folder != NULL )
+		i_free(testsuite_mailstore_folder);
+}
+
+static struct mail *testsuite_mailstore_open(const char *folder)
+{
+	enum mailbox_flags flags =
+		MAILBOX_FLAG_SAVEONLY | MAILBOX_FLAG_POST_SESSION;
+	struct mail_user *mail_user = testsuite_mailstore_user;
+	struct mail_namespace *ns = mail_user->namespaces;
+	struct mailbox *box;
+	struct mailbox_transaction_context *t;
+
+	if ( testsuite_mailstore_mail == NULL ) {
+		testsuite_mailstore_close();
+	} else if ( testsuite_mailstore_folder != NULL
+		&& strcmp(folder, testsuite_mailstore_folder) != 0  ) {
+		testsuite_mailstore_close();
+	} else {
+		return testsuite_mailstore_mail;
+	}
+
+	box = mailbox_alloc(ns->list, folder, flags);
+	if ( mailbox_open(box) < 0 ) {
+		sieve_sys_error(testsuite_sieve_instance,
+			"testsuite: failed to open mailbox '%s'", folder);
+		mailbox_free(&box);
+		return NULL;
+	}
+
+	/* Sync mailbox */
+
+	if ( mailbox_sync(box, MAILBOX_SYNC_FLAG_FULL_READ) < 0 ) {
+		sieve_sys_error(testsuite_sieve_instance,
+			"testsuite: failed to sync mailbox '%s'", folder);
+		mailbox_free(&box);
+		return NULL;
+	}
+
+	/* Start transaction */
+
+	t = mailbox_transaction_begin(box, 0, __func__);
+
+	testsuite_mailstore_folder = i_strdup(folder);
+	testsuite_mailstore_box = box;
+	testsuite_mailstore_trans = t;
+	testsuite_mailstore_mail = mail_alloc(t, 0, NULL);
+
+	return testsuite_mailstore_mail;
+}
+
+bool testsuite_mailstore_mail_index
+(const struct sieve_runtime_env *renv, const char *folder, unsigned int index)
+{
+	struct mail *mail = testsuite_mailstore_open(folder);
+
+	if ( mail == NULL )
+		return FALSE;
+
+	mail_set_seq(mail, index+1);
+	testsuite_message_set_mail(renv, mail);
+
+	return TRUE;
+}
+
+/*
+ * IMAP metadata
+ */
+
+int testsuite_mailstore_set_imap_metadata
+(const char *mailbox, const char *annotation, const char *value)
+{
+	struct imap_metadata_transaction *imtrans;
+	struct mail_attribute_value avalue;
+	struct mailbox *box;
+	enum mail_error error_code;
+	const char *error;
+	int ret;
+
+	if ( !imap_metadata_verify_entry_name(annotation, &error) ) {
+		sieve_sys_error(testsuite_sieve_instance,
+			"testsuite: imap metadata: "
+			"specified annotation name `%s' is invalid: %s",
+			str_sanitize(annotation, 256), error);
+		return -1;
+	}
+
+	if ( mailbox != NULL ) {
+		struct mail_namespace *ns;
+		ns = mail_namespace_find
+			(testsuite_mailstore_user->namespaces, mailbox);
+		box = mailbox_alloc(ns->list, mailbox, 0);
+		imtrans = imap_metadata_transaction_begin(box);
+	} else {
+		box = NULL;
+		imtrans = imap_metadata_transaction_begin_server
+			(testsuite_mailstore_user);
+	}
+
+	i_zero(&avalue);
+	avalue.value = value;
+	if ((ret=imap_metadata_set(imtrans, annotation, &avalue)) < 0) {
+		error = imap_metadata_transaction_get_last_error
+			(imtrans, &error_code);
+		imap_metadata_transaction_rollback(&imtrans);
+	} else {
+		ret = imap_metadata_transaction_commit
+			(&imtrans, &error_code, &error);
+	}
+	if ( box != NULL )
+		mailbox_free(&box);
+
+	if ( ret < 0 ) {
+		sieve_sys_error(testsuite_sieve_instance,
+			"testsuite: imap metadata: "
+			"failed to assign annotation `%s': %s",
+			str_sanitize(annotation, 256), error);
+		return -1;
+	}
+	return 0;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/testsuite-mailstore.h
@@ -0,0 +1,40 @@
+#ifndef TESTSUITE_MAILSTORE_H
+#define TESTSUITE_MAILSTORE_H
+
+#include "lib.h"
+
+#include "sieve-common.h"
+
+/*
+ * Initialization
+ */
+
+void testsuite_mailstore_init(void);
+void testsuite_mailstore_deinit(void);
+void testsuite_mailstore_reset(void);
+
+/*
+ * Mail user
+ */
+
+struct mail_user *testsuite_mailstore_get_user(void);
+
+/*
+ * Mailbox Access
+ */
+
+bool testsuite_mailstore_mailbox_create
+	(const struct sieve_runtime_env *renv ATTR_UNUSED, const char *folder);
+
+bool testsuite_mailstore_mail_index
+	(const struct sieve_runtime_env *renv, const char *folder,
+		unsigned int index);
+
+/*
+ * IMAP metadata
+ */
+
+int testsuite_mailstore_set_imap_metadata
+	(const char *mailbox, const char *annotation, const char *value);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/testsuite-message.c
@@ -0,0 +1,259 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "istream.h"
+#include "smtp-params.h"
+#include "message-address.h"
+#include "mail-storage.h"
+#include "master-service.h"
+
+#include "sieve-common.h"
+#include "sieve-address.h"
+#include "sieve-error.h"
+#include "sieve-message.h"
+#include "sieve-interpreter.h"
+
+#include "sieve-tool.h"
+
+#include "testsuite-common.h"
+#include "testsuite-message.h"
+
+/*
+ * Testsuite message environment
+ */
+
+struct sieve_message_data testsuite_msgdata;
+static struct smtp_params_rcpt testsuite_rcpt_params;
+
+static struct mail *testsuite_mail;
+
+static const char *_default_message_data =
+"From: sender@example.com\n"
+"To: recipient@example.org\n"
+"Subject: Frop!\n"
+"\n"
+"Friep!\n";
+
+static struct smtp_address *env_mail_from = NULL;
+static struct smtp_address *env_rcpt_to = NULL;
+static struct smtp_address *env_orig_rcpt_to = NULL;
+static char *env_auth = NULL;
+
+pool_t message_pool;
+
+static const struct smtp_address *
+testsuite_message_get_address(struct mail *mail, const char *header)
+{
+	struct message_address *addr;
+	const char *str;
+
+	if (mail_get_first_header(mail, header, &str) <= 0)
+		return NULL;
+	addr = message_address_parse(pool_datastack_create(),
+	             (const unsigned char *)str,
+	             strlen(str), 1, 0);
+	if ( addr == NULL ||
+		addr->mailbox == NULL || *addr->mailbox == '\0' )
+		return NULL;
+	return smtp_address_create_temp(addr->mailbox, addr->domain);
+}
+
+static void testsuite_message_set_data(struct mail *mail)
+{
+	const struct smtp_address *recipient = NULL, *sender = NULL;
+
+	i_free(env_mail_from);
+	i_free(env_rcpt_to);
+	i_free(env_orig_rcpt_to);
+	i_free(env_auth);
+
+	env_mail_from = env_rcpt_to = env_orig_rcpt_to = NULL;
+	env_auth = NULL;
+
+	/*
+	 * Collect necessary message data
+	 */
+
+	/* Get recipient address */
+	recipient = testsuite_message_get_address(mail, "Envelope-To");
+	if ( recipient == NULL )
+		recipient = testsuite_message_get_address(mail, "To");
+	if ( recipient == NULL )
+		recipient = SMTP_ADDRESS_LITERAL("recipient", "example.com");
+
+	/* Get sender address */
+	sender = testsuite_message_get_address(mail, "Return-path");
+	if ( sender == NULL )
+		sender = testsuite_message_get_address(mail, "Sender");
+	if ( sender == NULL )
+		sender = testsuite_message_get_address(mail, "From");
+	if ( sender == NULL )
+		sender = SMTP_ADDRESS_LITERAL("sender", "example.com");
+
+	env_mail_from = smtp_address_clone(default_pool, sender);
+	env_rcpt_to = smtp_address_clone(default_pool, recipient);
+	env_orig_rcpt_to = smtp_address_clone(default_pool, recipient);
+
+	i_zero(&testsuite_msgdata);
+	testsuite_msgdata.mail = mail;
+	testsuite_msgdata.auth_user = sieve_tool_get_username(sieve_tool);
+	testsuite_msgdata.envelope.mail_from = env_mail_from;
+	testsuite_msgdata.envelope.rcpt_to = env_rcpt_to;
+
+	i_zero(&testsuite_rcpt_params);
+	testsuite_rcpt_params.orcpt.addr = env_orig_rcpt_to;
+
+	testsuite_msgdata.envelope.rcpt_params = &testsuite_rcpt_params;
+
+	(void)mail_get_first_header(mail, "Message-ID", &testsuite_msgdata.id);
+}
+
+void testsuite_message_init(void)
+{
+	message_pool = pool_alloconly_create("testsuite_message", 6096);
+
+	string_t *default_message = str_new(message_pool, 1024);
+	str_append(default_message, _default_message_data);
+
+	testsuite_mail = sieve_tool_open_data_as_mail(sieve_tool, default_message);
+	testsuite_message_set_data(testsuite_mail);
+}
+
+void testsuite_message_set_string
+(const struct sieve_runtime_env *renv, string_t *message)
+{
+	sieve_message_context_reset(renv->msgctx);
+
+	testsuite_mail = sieve_tool_open_data_as_mail(sieve_tool, message);
+	testsuite_message_set_data(testsuite_mail);
+}
+
+void testsuite_message_set_file
+(const struct sieve_runtime_env *renv, const char *file_path)
+{
+	sieve_message_context_reset(renv->msgctx);
+
+	testsuite_mail = sieve_tool_open_file_as_mail(sieve_tool, file_path);
+	testsuite_message_set_data(testsuite_mail);
+}
+
+void testsuite_message_set_mail
+(const struct sieve_runtime_env *renv, struct mail *mail)
+{
+	sieve_message_context_reset(renv->msgctx);
+
+	testsuite_message_set_data(mail);
+}
+
+void testsuite_message_deinit(void)
+{
+	i_free(env_mail_from);
+	i_free(env_rcpt_to);
+	i_free(env_orig_rcpt_to);
+	i_free(env_auth);
+	pool_unref(&message_pool);
+}
+
+void testsuite_envelope_set_sender_address
+(const struct sieve_runtime_env *renv,
+	const struct smtp_address *address)
+{
+	sieve_message_context_reset(renv->msgctx);
+
+	i_free(env_mail_from);
+
+	env_mail_from = smtp_address_clone(default_pool, address);
+	testsuite_msgdata.envelope.mail_from = env_mail_from;
+}
+
+void testsuite_envelope_set_sender
+(const struct sieve_runtime_env *renv, const char *value)
+{
+	struct smtp_address *address = NULL;
+	const char *error;
+
+	if (smtp_address_parse_path(pool_datastack_create(), value,
+		SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY |
+		SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL,
+		&address, &error) < 0) {
+		sieve_sys_error(testsuite_sieve_instance,
+			"testsuite: envelope sender address "
+			"`%s' is invalid: %s", value, error);
+	}
+	testsuite_envelope_set_sender_address(renv, address);
+}
+
+void testsuite_envelope_set_recipient_address
+(const struct sieve_runtime_env *renv,
+	const struct smtp_address *address)
+{
+	sieve_message_context_reset(renv->msgctx);
+
+	i_free(env_rcpt_to);
+	i_free(env_orig_rcpt_to);
+
+	env_rcpt_to = smtp_address_clone(default_pool, address);
+	env_orig_rcpt_to = smtp_address_clone(default_pool, address);
+	testsuite_msgdata.envelope.rcpt_to = env_rcpt_to;
+	testsuite_rcpt_params.orcpt.addr = env_orig_rcpt_to;
+}
+
+void testsuite_envelope_set_recipient
+(const struct sieve_runtime_env *renv, const char *value)
+{
+	struct smtp_address *address = NULL;
+	const char *error;
+
+	if (smtp_address_parse_path(pool_datastack_create(), value,
+		SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART |
+		SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL,
+		&address, &error) < 0) {
+		sieve_sys_error(testsuite_sieve_instance,
+			"testsuite: envelope recipient address "
+			"`%s' is invalid: %s", value, error);
+	}
+	testsuite_envelope_set_recipient_address(renv, address);
+}
+
+void testsuite_envelope_set_orig_recipient_address
+(const struct sieve_runtime_env *renv,
+	const struct smtp_address *address)
+{
+	sieve_message_context_reset(renv->msgctx);
+
+	i_free(env_orig_rcpt_to);
+
+	env_orig_rcpt_to = smtp_address_clone(default_pool, address);
+	testsuite_rcpt_params.orcpt.addr = env_orig_rcpt_to;
+}
+
+void testsuite_envelope_set_orig_recipient
+(const struct sieve_runtime_env *renv, const char *value)
+{
+	struct smtp_address *address = NULL;
+	const char *error;
+
+	if (smtp_address_parse_path(pool_datastack_create(), value,
+		SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART |
+		SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL,
+		&address, &error) < 0) {
+		sieve_sys_error(testsuite_sieve_instance,
+			"testsuite: envelope recipient address "
+			"`%s' is invalid: %s", value, error);
+	}
+	testsuite_envelope_set_orig_recipient_address(renv, address);
+}
+
+void testsuite_envelope_set_auth_user
+(const struct sieve_runtime_env *renv, const char *value)
+{
+	sieve_message_context_reset(renv->msgctx);
+
+	i_free(env_auth);
+
+	env_auth = i_strdup(value);
+	testsuite_msgdata.auth_user = env_auth;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/testsuite-message.h
@@ -0,0 +1,43 @@
+#ifndef TESTSUITE_MESSAGE_H
+#define TESTSUITE_MESSAGE_H
+
+#include "lib.h"
+#include "master-service.h"
+
+#include "sieve-common.h"
+#include "sieve-tool.h"
+
+extern struct sieve_message_data testsuite_msgdata;
+
+void testsuite_message_init(void);
+void testsuite_message_deinit(void);
+
+void testsuite_message_set_string
+	(const struct sieve_runtime_env *renv, string_t *message);
+void testsuite_message_set_file
+	(const struct sieve_runtime_env *renv, const char *file_path);
+void testsuite_message_set_mail
+	(const struct sieve_runtime_env *renv, struct mail *mail);
+
+void testsuite_envelope_set_sender_address
+	(const struct sieve_runtime_env *renv,
+		const struct smtp_address *address);
+void testsuite_envelope_set_sender
+	(const struct sieve_runtime_env *renv, const char *value);
+
+void testsuite_envelope_set_recipient_address
+	(const struct sieve_runtime_env *renv,
+		const struct smtp_address *address);
+void testsuite_envelope_set_recipient
+	(const struct sieve_runtime_env *renv, const char *value);
+
+void testsuite_envelope_set_orig_recipient_address
+	(const struct sieve_runtime_env *renv,
+		const struct smtp_address *address);
+void testsuite_envelope_set_orig_recipient
+	(const struct sieve_runtime_env *renv, const char *value);
+
+void testsuite_envelope_set_auth_user
+	(const struct sieve_runtime_env *renv, const char *value);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/testsuite-objects.c
@@ -0,0 +1,369 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "string.h"
+#include "ostream.h"
+#include "hash.h"
+#include "mail-storage.h"
+
+#include "sieve.h"
+#include "sieve-code.h"
+#include "sieve-commands.h"
+#include "sieve-extensions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+
+#include "testsuite-common.h"
+#include "testsuite-objects.h"
+#include "testsuite-message.h"
+
+/*
+ * Testsuite core objects
+ */
+
+enum testsuite_object_code {
+	TESTSUITE_OBJECT_MESSAGE,
+	TESTSUITE_OBJECT_ENVELOPE
+};
+
+const struct testsuite_object_def *testsuite_core_objects[] = {
+	&message_testsuite_object, &envelope_testsuite_object
+};
+
+const unsigned int testsuite_core_objects_count =
+	N_ELEMENTS(testsuite_core_objects);
+
+/*
+ * Testsuite object registry
+ */
+
+static inline struct sieve_validator_object_registry *_get_object_registry
+(struct sieve_validator *valdtr)
+{
+	struct testsuite_validator_context *ctx =
+		testsuite_validator_context_get(valdtr);
+
+	return ctx->object_registrations;
+}
+
+void testsuite_object_register
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	const struct testsuite_object_def *tobj_def)
+{
+	struct sieve_validator_object_registry *regs = _get_object_registry(valdtr);
+
+	sieve_validator_object_registry_add(regs, ext, &tobj_def->obj_def);
+}
+
+static const struct testsuite_object *testsuite_object_create
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+	const char *identifier)
+{
+	struct sieve_validator_object_registry *regs = _get_object_registry(valdtr);
+	struct sieve_object object;
+	struct testsuite_object *tobj;
+
+	if ( !sieve_validator_object_registry_find(regs, identifier, &object) )
+		return NULL;
+
+	tobj = p_new(sieve_command_pool(cmd), struct testsuite_object, 1);
+	tobj->object = object;
+	tobj->def = (const struct testsuite_object_def *) object.def;
+
+  return tobj;
+}
+
+void testsuite_register_core_objects
+(struct testsuite_validator_context *ctx)
+{
+	struct sieve_validator_object_registry *regs = ctx->object_registrations;
+	unsigned int i;
+
+	/* Register core testsuite objects */
+	for ( i = 0; i < testsuite_core_objects_count; i++ ) {
+		const struct testsuite_object_def *tobj_def = testsuite_core_objects[i];
+
+		sieve_validator_object_registry_add
+			(regs, testsuite_ext, &tobj_def->obj_def);
+	}
+}
+
+/*
+ * Testsuite object code
+ */
+
+const struct sieve_operand_class sieve_testsuite_object_operand_class =
+	{ "testsuite object" };
+
+static const struct sieve_extension_objects core_testsuite_objects =
+	SIEVE_EXT_DEFINE_OBJECTS(testsuite_core_objects);
+
+const struct sieve_operand_def testsuite_object_operand = {
+	.name = "testsuite-object",
+	.ext_def = &testsuite_extension,
+	.code = TESTSUITE_OPERAND_OBJECT,
+	.class = &sieve_testsuite_object_operand_class,
+	.interface = &core_testsuite_objects
+};
+
+static void testsuite_object_emit
+(struct sieve_binary_block *sblock, const struct testsuite_object *tobj,
+	int member_id)
+{
+	sieve_opr_object_emit(sblock, tobj->object.ext, tobj->object.def);
+
+	if ( tobj->def != NULL && tobj->def->get_member_id != NULL ) {
+		(void) sieve_binary_emit_byte(sblock, (unsigned char) member_id);
+	}
+}
+
+bool testsuite_object_read
+(struct sieve_binary_block *sblock, sieve_size_t *address,
+	struct testsuite_object *tobj)
+{
+	struct sieve_operand oprnd;
+
+	if ( !sieve_operand_read(sblock, address, NULL, &oprnd) )
+		return FALSE;
+
+	if ( !sieve_opr_object_read_data
+		(sblock, &oprnd, &sieve_testsuite_object_operand_class, address,
+			&tobj->object) )
+		return FALSE;
+
+	tobj->def = (const struct testsuite_object_def *) tobj->object.def;
+	i_assert(tobj->def != NULL);
+	return TRUE;
+}
+
+bool testsuite_object_read_member
+(struct sieve_binary_block *sblock, sieve_size_t *address,
+	struct testsuite_object *tobj, int *member_id_r)
+{
+	if ( !testsuite_object_read(sblock, address, tobj) )
+		return FALSE;
+
+	*member_id_r = -1;
+	if ( tobj->def->get_member_id != NULL ) {
+		if ( !sieve_binary_read_code(sblock, address, member_id_r) )
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+const char *testsuite_object_member_name
+(const struct testsuite_object *object, int member_id)
+{
+	const struct testsuite_object_def *obj_def = object->def;
+	const char *member = NULL;
+
+	if ( obj_def->get_member_id != NULL ) {
+		if ( obj_def->get_member_name != NULL )
+			member = obj_def->get_member_name(member_id);
+	} else
+		return obj_def->obj_def.identifier;
+
+	if ( member == NULL )
+		return t_strdup_printf("%s.%d", obj_def->obj_def.identifier, member_id);
+
+	return t_strdup_printf("%s.%s", obj_def->obj_def.identifier, member);
+}
+
+bool testsuite_object_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	struct testsuite_object object;
+	int member_id;
+
+	sieve_code_mark(denv);
+
+	if ( !testsuite_object_read_member
+		(denv->sblock, address, &object, &member_id) )
+		return FALSE;
+
+	sieve_code_dumpf(denv, "%s: %s",
+		sieve_testsuite_object_operand_class.name,
+		testsuite_object_member_name(&object, member_id));
+
+	return TRUE;
+}
+
+/*
+ * Testsuite object argument
+ */
+
+static bool arg_testsuite_object_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+		struct sieve_command *cmd);
+
+const struct sieve_argument_def testsuite_object_argument = {
+	.identifier = "testsuite-object",
+	.generate = arg_testsuite_object_generate
+};
+
+struct testsuite_object_argctx {
+	const struct testsuite_object *object;
+	int member;
+};
+
+bool testsuite_object_argument_activate
+(struct sieve_validator *valdtr, struct sieve_ast_argument *arg,
+	struct sieve_command *cmd)
+{
+	const char *objname = sieve_ast_argument_strc(arg);
+	const struct testsuite_object *tobj;
+	int member_id;
+	const char *member;
+	struct testsuite_object_argctx *ctx;
+
+	/* Parse the object specifier */
+
+	member = strchr(objname, '.');
+	if ( member != NULL ) {
+		objname = t_strdup_until(objname, member);
+		member++;
+	}
+
+	/* Find the object */
+
+	tobj = testsuite_object_create(valdtr, cmd, objname);
+	if ( tobj == NULL ) {
+		sieve_argument_validate_error(valdtr, arg,
+			"unknown testsuite object '%s'", objname);
+		return FALSE;
+	}
+
+	/* Find the object member */
+
+	member_id = -1;
+	if ( member != NULL ) {
+		if ( tobj->def == NULL || tobj->def->get_member_id == NULL ||
+			(member_id=tobj->def->get_member_id(member)) == -1 ) {
+			sieve_argument_validate_error(valdtr, arg,
+				"member '%s' does not exist for testsuite object '%s'", member, objname);
+			return FALSE;
+		}
+	}
+
+	/* Assign argument context */
+
+	ctx = p_new(sieve_command_pool(cmd), struct testsuite_object_argctx, 1);
+	ctx->object = tobj;
+	ctx->member = member_id;
+
+	arg->argument = sieve_argument_create
+		(arg->ast, &testsuite_object_argument, testsuite_ext, 0);
+	arg->argument->data = (void *) ctx;
+
+	return TRUE;
+}
+
+static bool arg_testsuite_object_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+	struct sieve_command *cmd ATTR_UNUSED)
+{
+	struct testsuite_object_argctx *ctx =
+		(struct testsuite_object_argctx *) arg->argument->data;
+
+	testsuite_object_emit(cgenv->sblock, ctx->object, ctx->member);
+
+	return TRUE;
+}
+
+/*
+ * Testsuite core object implementation
+ */
+
+static bool tsto_message_set_member
+	(const struct sieve_runtime_env *renv, int id, string_t *value);
+
+static int tsto_envelope_get_member_id(const char *identifier);
+static const char *tsto_envelope_get_member_name(int id);
+static bool tsto_envelope_set_member
+	(const struct sieve_runtime_env *renv, int id, string_t *value);
+
+const struct testsuite_object_def message_testsuite_object = {
+	SIEVE_OBJECT("message",
+		&testsuite_object_operand, TESTSUITE_OBJECT_MESSAGE),
+	.set_member = tsto_message_set_member
+};
+
+const struct testsuite_object_def envelope_testsuite_object = {
+	SIEVE_OBJECT("envelope",
+		&testsuite_object_operand, TESTSUITE_OBJECT_ENVELOPE),
+	.get_member_id = tsto_envelope_get_member_id,
+	.get_member_name = tsto_envelope_get_member_name,
+	.set_member = tsto_envelope_set_member
+};
+
+enum testsuite_object_envelope_field {
+	TESTSUITE_OBJECT_ENVELOPE_FROM,
+	TESTSUITE_OBJECT_ENVELOPE_TO,
+	TESTSUITE_OBJECT_ENVELOPE_ORIG_TO,
+	TESTSUITE_OBJECT_ENVELOPE_AUTH_USER
+};
+
+static bool tsto_message_set_member
+(const struct sieve_runtime_env *renv, int id, string_t *value)
+{
+	if ( id != -1 ) return FALSE;
+
+	testsuite_message_set_string(renv, value);
+
+	return TRUE;
+}
+
+static int tsto_envelope_get_member_id(const char *identifier)
+{
+	if ( strcasecmp(identifier, "from") == 0 )
+		return TESTSUITE_OBJECT_ENVELOPE_FROM;
+	if ( strcasecmp(identifier, "to") == 0 )
+		return TESTSUITE_OBJECT_ENVELOPE_TO;
+	if ( strcasecmp(identifier, "orig_to") == 0 )
+		return TESTSUITE_OBJECT_ENVELOPE_ORIG_TO;
+	if ( strcasecmp(identifier, "auth") == 0 )
+		return TESTSUITE_OBJECT_ENVELOPE_AUTH_USER;
+
+	return -1;
+}
+
+static const char *tsto_envelope_get_member_name(int id)
+{
+	switch ( id ) {
+	case TESTSUITE_OBJECT_ENVELOPE_FROM:
+		return "from";
+	case TESTSUITE_OBJECT_ENVELOPE_TO:
+		return "to";
+	case TESTSUITE_OBJECT_ENVELOPE_ORIG_TO:
+		return "orig_to";
+	case TESTSUITE_OBJECT_ENVELOPE_AUTH_USER:
+		return "auth";
+	}
+
+	return NULL;
+}
+
+static bool tsto_envelope_set_member
+(const struct sieve_runtime_env *renv, int id, string_t *value)
+{
+	switch ( id ) {
+	case TESTSUITE_OBJECT_ENVELOPE_FROM:
+		testsuite_envelope_set_sender(renv, str_c(value));
+		return TRUE;
+	case TESTSUITE_OBJECT_ENVELOPE_TO:
+		testsuite_envelope_set_recipient(renv, str_c(value));
+		return TRUE;
+	case TESTSUITE_OBJECT_ENVELOPE_ORIG_TO:
+		testsuite_envelope_set_orig_recipient(renv, str_c(value));
+		return TRUE;
+	case TESTSUITE_OBJECT_ENVELOPE_AUTH_USER:
+		testsuite_envelope_set_auth_user(renv, str_c(value));
+		return TRUE;
+	}
+
+	return FALSE;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/testsuite-objects.h
@@ -0,0 +1,83 @@
+#ifndef TESTSUITE_OBJECTS_H
+#define TESTSUITE_OBJECTS_H
+
+#include "sieve-common.h"
+#include "sieve-objects.h"
+
+#include "testsuite-common.h"
+
+/*
+ * Testsuite object operand
+ */
+
+struct testsuite_object_operand_interface {
+	struct sieve_extension_objects testsuite_objects;
+};
+
+extern const struct sieve_operand_class testsuite_object_oprclass;
+
+/*
+ * Testsuite object access
+ */
+
+struct testsuite_object_def {
+	struct sieve_object_def obj_def;
+
+	int (*get_member_id)(const char *identifier);
+	const char *(*get_member_name)(int id);
+
+	bool (*set_member)
+		(const struct sieve_runtime_env *renv, int id, string_t *value);
+	string_t *(*get_member)
+		(const struct sieve_runtime_env *renv, int id);
+};
+
+struct testsuite_object {
+	struct sieve_object object;
+
+	const struct testsuite_object_def *def;
+};
+
+/*
+ * Testsuite object registration
+ */
+
+void testsuite_register_core_objects
+	(struct testsuite_validator_context *ctx);
+void testsuite_object_register
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		const struct testsuite_object_def *tobj_def);
+
+/*
+ * Testsuite object argument
+ */
+
+bool testsuite_object_argument_activate
+	(struct sieve_validator *valdtr, struct sieve_ast_argument *arg,
+		struct sieve_command *cmd);
+
+/*
+ * Testsuite object code
+ */
+
+bool testsuite_object_read
+  (struct sieve_binary_block *sblock, sieve_size_t *address,
+		struct testsuite_object *tobj);
+bool testsuite_object_read_member
+  (struct sieve_binary_block *sblock, sieve_size_t *address,
+		struct testsuite_object *tobj, int *member_id_r);
+
+bool testsuite_object_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+
+const char *testsuite_object_member_name
+	(const struct testsuite_object *object, int member_id);
+
+/*
+ * Testsuite core objects
+ */
+
+extern const struct testsuite_object_def message_testsuite_object;
+extern const struct testsuite_object_def envelope_testsuite_object;
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/testsuite-result.c
@@ -0,0 +1,178 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "ostream.h"
+#include "str.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-stringlist.h"
+#include "sieve-actions.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+
+#include "testsuite-common.h"
+#include "testsuite-log.h"
+#include "testsuite-message.h"
+
+#include "testsuite-result.h"
+
+static struct sieve_result *_testsuite_result;
+
+void testsuite_result_init(void)
+{
+	struct sieve_instance *svinst = testsuite_sieve_instance;
+
+	_testsuite_result = sieve_result_create
+		(svinst, &testsuite_msgdata, testsuite_scriptenv);
+}
+
+void testsuite_result_deinit(void)
+{
+	if ( _testsuite_result != NULL ) {
+		sieve_result_unref(&_testsuite_result);
+	}
+}
+
+void testsuite_result_reset
+(const struct sieve_runtime_env *renv)
+{
+	struct sieve_instance *svinst = testsuite_sieve_instance;
+
+	if ( _testsuite_result != NULL ) {
+		sieve_result_unref(&_testsuite_result);
+	}
+
+	_testsuite_result = sieve_result_create
+		(svinst, &testsuite_msgdata, testsuite_scriptenv);
+	sieve_interpreter_set_result(renv->interp, _testsuite_result);
+}
+
+struct sieve_result *testsuite_result_get(void)
+{
+	return _testsuite_result;
+}
+
+struct sieve_result_iterate_context *testsuite_result_iterate_init(void)
+{
+	if ( _testsuite_result == NULL )
+		return NULL;
+
+	return sieve_result_iterate_init(_testsuite_result);
+}
+
+bool testsuite_result_execute(const struct sieve_runtime_env *renv)
+{
+	int ret;
+
+	if ( _testsuite_result == NULL ) {
+		sieve_runtime_error(renv, NULL,
+			"testsuite: trying to execute result, but no result evaluated yet");
+		return FALSE;
+	}
+
+	testsuite_log_clear_messages();
+
+	/* Execute the result */
+	ret=sieve_result_execute
+		(_testsuite_result, NULL, testsuite_log_ehandler, 0);
+
+	return ( ret > 0 );
+}
+
+void testsuite_result_print
+(const struct sieve_runtime_env *renv)
+{
+	struct ostream *out;
+
+	out = o_stream_create_fd(1, 0);
+	o_stream_set_no_error_handling(out, TRUE);
+
+	o_stream_nsend_str(out, "\n--");
+	sieve_result_print(_testsuite_result, renv->scriptenv, out, NULL);
+	o_stream_nsend_str(out, "--\n\n");
+
+	o_stream_destroy(&out);
+}
+
+/*
+ * Result stringlist
+ */
+
+/* Forward declarations */
+
+static int testsuite_result_stringlist_next_item
+	(struct sieve_stringlist *_strlist, string_t **str_r);
+static void testsuite_result_stringlist_reset
+	(struct sieve_stringlist *_strlist);
+
+/* Stringlist object */
+
+struct testsuite_result_stringlist {
+	struct sieve_stringlist strlist;
+
+	struct sieve_result_iterate_context *result_iter;
+	int pos, index;
+};
+
+struct sieve_stringlist *testsuite_result_stringlist_create
+(const struct sieve_runtime_env *renv, int index)
+{
+	struct testsuite_result_stringlist *strlist;
+
+	strlist = t_new(struct testsuite_result_stringlist, 1);
+	strlist->strlist.runenv = renv;
+	strlist->strlist.exec_status = SIEVE_EXEC_OK;
+	strlist->strlist.next_item = testsuite_result_stringlist_next_item;
+	strlist->strlist.reset = testsuite_result_stringlist_reset;
+
+	strlist->result_iter = testsuite_result_iterate_init();
+ 	strlist->index = index;
+	strlist->pos = 0;
+
+	return &strlist->strlist;
+}
+
+static int testsuite_result_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+	struct testsuite_result_stringlist *strlist =
+		(struct testsuite_result_stringlist *) _strlist;
+	const struct sieve_action *action;
+	const char *act_name;
+	bool keep;
+
+	*str_r = NULL;
+
+	if ( strlist->index > 0 && strlist->pos > 0 )
+		return 0;
+
+	do {
+		if ( (action=sieve_result_iterate_next(strlist->result_iter, &keep))
+			== NULL )
+			return 0;
+
+		strlist->pos++;
+	} while ( strlist->pos < strlist->index );
+
+	if ( keep )
+		act_name = "keep";
+	else
+		act_name = ( action == NULL || action->def == NULL ||
+			action->def->name == NULL ) ? "" : action->def->name;
+
+	*str_r = t_str_new_const(act_name, strlen(act_name));
+	return 1;
+}
+
+static void testsuite_result_stringlist_reset
+(struct sieve_stringlist *_strlist)
+{
+	struct testsuite_result_stringlist *strlist =
+		(struct testsuite_result_stringlist *) _strlist;
+
+	strlist->result_iter = testsuite_result_iterate_init();
+	strlist->pos = 0;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/testsuite-result.h
@@ -0,0 +1,22 @@
+#ifndef TESTSUITE_RESULT_H
+#define TESTSUITE_RESULT_H
+
+void testsuite_result_init(void);
+void testsuite_result_deinit(void);
+
+void testsuite_result_reset
+	(const struct sieve_runtime_env *renv);
+
+struct sieve_result *testsuite_result_get(void);
+
+struct sieve_result_iterate_context *testsuite_result_iterate_init(void);
+
+bool testsuite_result_execute(const struct sieve_runtime_env *renv);
+
+void testsuite_result_print
+	(const struct sieve_runtime_env *renv ATTR_UNUSED);
+
+struct sieve_stringlist *testsuite_result_stringlist_create
+	(const struct sieve_runtime_env *renv, int index);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/testsuite-script.c
@@ -0,0 +1,238 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve.h"
+#include "sieve-common.h"
+#include "sieve-script.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-runtime-trace.h"
+#include "sieve-result.h"
+
+#include "testsuite-common.h"
+#include "testsuite-settings.h"
+#include "testsuite-log.h"
+#include "testsuite-smtp.h"
+#include "testsuite-result.h"
+
+#include "testsuite-script.h"
+
+/*
+ * Tested script environment
+ */
+
+void testsuite_script_init(void)
+{
+}
+
+void testsuite_script_deinit(void)
+{
+}
+
+static struct sieve_binary *_testsuite_script_compile
+(const struct sieve_runtime_env *renv, const char *script)
+{
+	struct sieve_instance *svinst = testsuite_sieve_instance;
+	struct sieve_binary *sbin;
+	const char *script_path;
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "compile script `%s'", script);
+
+	script_path = sieve_file_script_get_dirpath(renv->script);
+	if ( script_path == NULL )
+		return NULL;
+
+	script_path = t_strconcat(script_path, "/", script, NULL);
+
+	if ( (sbin = sieve_compile
+		(svinst, script_path, NULL, testsuite_log_ehandler, 0, NULL)) == NULL )
+		return NULL;
+
+	return sbin;
+}
+
+bool testsuite_script_compile
+(const struct sieve_runtime_env *renv, const char *script)
+{
+	struct testsuite_interpreter_context *ictx =
+		testsuite_interpreter_context_get(renv->interp, testsuite_ext);
+	struct sieve_binary *sbin;
+
+	i_assert(ictx != NULL);
+	testsuite_log_clear_messages();
+
+	if ( (sbin=_testsuite_script_compile(renv, script)) == NULL )
+		return FALSE;
+
+	if ( ictx->compiled_script != NULL ) {
+		sieve_binary_unref(&ictx->compiled_script);
+	}
+
+	ictx->compiled_script = sbin;
+	return TRUE;
+}
+
+bool testsuite_script_is_subtest(const struct sieve_runtime_env *renv)
+{
+	struct testsuite_interpreter_context *ictx =
+		testsuite_interpreter_context_get(renv->interp, testsuite_ext);
+
+	i_assert(ictx != NULL);
+	if ( ictx->compiled_script == NULL )
+		return FALSE;
+
+	return ( sieve_binary_extension_get_index
+		(ictx->compiled_script, testsuite_ext) >= 0 );
+}
+
+bool testsuite_script_run(const struct sieve_runtime_env *renv)
+{
+	const struct sieve_script_env *senv = renv->scriptenv;
+	struct testsuite_interpreter_context *ictx =
+		testsuite_interpreter_context_get(renv->interp, testsuite_ext);
+	struct sieve_script_env scriptenv;
+	struct sieve_result *result;
+	struct sieve_interpreter *interp;
+	const char *error;
+	int ret;
+
+	i_assert(ictx != NULL);
+
+	if ( ictx->compiled_script == NULL ) {
+		sieve_runtime_error(renv, NULL,
+			"testsuite: trying to run script, but no script compiled yet");
+		return FALSE;
+	}
+
+	testsuite_log_clear_messages();
+
+	/* Compose script execution environment */
+	if (sieve_script_env_init(&scriptenv, senv->user, &error) < 0) {
+		sieve_runtime_error(renv, NULL,
+			"testsuite: failed to initialize script execution: %s",
+			error);
+		return FALSE;
+	}
+	scriptenv.default_mailbox = "INBOX";
+	scriptenv.smtp_start = testsuite_smtp_start;
+	scriptenv.smtp_add_rcpt = testsuite_smtp_add_rcpt;
+	scriptenv.smtp_send = testsuite_smtp_send;
+	scriptenv.smtp_abort = testsuite_smtp_abort;
+	scriptenv.smtp_finish = testsuite_smtp_finish;
+	scriptenv.duplicate_mark = NULL;
+	scriptenv.duplicate_check = NULL;
+	scriptenv.trace_log = renv->scriptenv->trace_log;
+	scriptenv.trace_config = renv->scriptenv->trace_config;
+
+	result = testsuite_result_get();
+
+	/* Execute the script */
+	interp=sieve_interpreter_create(ictx->compiled_script,
+		NULL, renv->msgdata, &scriptenv, testsuite_log_ehandler, 0);
+
+	if ( interp == NULL )
+		return FALSE;
+
+	ret = sieve_interpreter_run(interp, result);
+
+	sieve_interpreter_free(&interp);
+
+	return ( ret > 0 || sieve_binary_extension_get_index
+                (ictx->compiled_script, testsuite_ext) >= 0 );
+}
+
+struct sieve_binary *testsuite_script_get_binary(const struct sieve_runtime_env *renv)
+{
+	struct testsuite_interpreter_context *ictx =
+		testsuite_interpreter_context_get(renv->interp, testsuite_ext);
+
+	i_assert(ictx != NULL);
+	return ictx->compiled_script;
+}
+
+void testsuite_script_set_binary
+(const struct sieve_runtime_env *renv, struct sieve_binary *sbin)
+{
+	struct testsuite_interpreter_context *ictx =
+		testsuite_interpreter_context_get(renv->interp, testsuite_ext);
+
+	i_assert(ictx != NULL);
+
+	if ( ictx->compiled_script != NULL ) {
+		sieve_binary_unref(&ictx->compiled_script);
+	}
+
+	ictx->compiled_script = sbin;
+	sieve_binary_ref(sbin);
+}
+
+/*
+ * Multiscript
+ */
+
+bool testsuite_script_multiscript
+(const struct sieve_runtime_env *renv, ARRAY_TYPE (const_string) *scriptfiles)
+{
+	struct sieve_instance *svinst = testsuite_sieve_instance;
+	const struct sieve_script_env *senv = renv->scriptenv;
+	struct sieve_script_env scriptenv;
+	struct sieve_multiscript *mscript;
+	const char *const *scripts;
+	const char *error;
+	unsigned int count, i;
+	bool more = TRUE;
+	bool result = TRUE;
+
+	testsuite_log_clear_messages();
+
+	/* Compose script execution environment */
+	if (sieve_script_env_init(&scriptenv, senv->user, &error) < 0) {
+		sieve_runtime_error(renv, NULL,
+			"testsuite: failed to initialize script execution: %s",
+			error);
+		return FALSE;
+	}
+	scriptenv.default_mailbox = "INBOX";
+	scriptenv.smtp_start = testsuite_smtp_start;
+	scriptenv.smtp_add_rcpt = testsuite_smtp_add_rcpt;
+	scriptenv.smtp_send = testsuite_smtp_send;
+	scriptenv.smtp_abort = testsuite_smtp_abort;
+	scriptenv.smtp_finish = testsuite_smtp_finish;
+	scriptenv.duplicate_mark = NULL;
+	scriptenv.duplicate_check = NULL;
+	scriptenv.trace_log = renv->scriptenv->trace_log;
+	scriptenv.trace_config = renv->scriptenv->trace_config;
+
+	/* Start execution */
+
+	mscript = sieve_multiscript_start_execute(svinst, renv->msgdata, &scriptenv);
+
+	/* Execute scripts before main script */
+
+	scripts = array_get(scriptfiles, &count);
+
+	for ( i = 0; i < count && more; i++ ) {
+		struct sieve_binary *sbin = NULL;
+		const char *script = scripts[i];
+
+		/* Open */
+		if ( (sbin=_testsuite_script_compile(renv, script)) == NULL ) {
+			result = FALSE;
+			break;
+		}
+
+		/* Execute */
+
+		sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "run script `%s'", script);
+
+		more = sieve_multiscript_run(mscript, sbin,
+			testsuite_log_ehandler, testsuite_log_ehandler, 0);
+
+		sieve_close(&sbin);
+	}
+
+	return ( sieve_multiscript_finish
+		(&mscript, testsuite_log_ehandler, 0, NULL) > 0	&& result );
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/testsuite-script.h
@@ -0,0 +1,22 @@
+#ifndef TESTSUITE_SCRIPT_H
+#define TESTSUITE_SCRIPT_H
+
+#include "sieve-common.h"
+
+void testsuite_script_init(void);
+void testsuite_script_deinit(void);
+
+bool testsuite_script_is_subtest(const struct sieve_runtime_env *renv);
+
+bool testsuite_script_compile
+	(const struct sieve_runtime_env *renv, const char *script);
+bool testsuite_script_run
+	(const struct sieve_runtime_env *renv);
+bool testsuite_script_multiscript
+	(const struct sieve_runtime_env *renv,
+		ARRAY_TYPE (const_string) *scriptfiles);
+
+struct sieve_binary *testsuite_script_get_binary(const struct sieve_runtime_env *renv);
+void testsuite_script_set_binary(const struct sieve_runtime_env *renv, struct sieve_binary *sbin);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/testsuite-settings.c
@@ -0,0 +1,96 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "hash.h"
+#include "imem.h"
+#include "strfuncs.h"
+#include "mail-user.h"
+
+#include "sieve-common.h"
+
+#include "testsuite-common.h"
+#include "testsuite-mailstore.h"
+#include "testsuite-settings.h"
+
+struct testsuite_setting {
+	char *identifier;
+	char *value;
+};
+
+static HASH_TABLE(const char *, struct testsuite_setting *) settings;
+
+static const char *testsuite_setting_get
+	(void *context, const char *identifier);
+
+void testsuite_settings_init(void)
+{
+	hash_table_create(&settings, default_pool, 0, str_hash, strcmp);
+
+	sieve_tool_set_setting_callback(sieve_tool, testsuite_setting_get, NULL);
+}
+
+void testsuite_settings_deinit(void)
+{
+	struct hash_iterate_context *itx =
+		hash_table_iterate_init(settings);
+	const char *key;
+	struct testsuite_setting *setting;
+
+	while ( hash_table_iterate(itx, settings, &key, &setting) ) {
+		i_free(setting->identifier);
+		i_free(setting->value);
+		i_free(setting);
+	}
+
+	hash_table_iterate_deinit(&itx);
+
+	hash_table_destroy(&settings);
+}
+
+static const char *testsuite_setting_get
+(void *context ATTR_UNUSED, const char *identifier)
+{
+	struct testsuite_setting *setting;
+	struct mail_user *user;
+
+	setting = hash_table_lookup(settings, identifier);
+	if ( setting != NULL )
+		return setting->value;
+
+	user = testsuite_mailstore_get_user();
+	if ( user == NULL )
+		return NULL;
+	return mail_user_plugin_getenv(user, identifier);
+}
+
+void testsuite_setting_set(const char *identifier, const char *value)
+{
+	struct testsuite_setting *setting =
+		hash_table_lookup(settings, identifier);
+
+	if ( setting != NULL ) {
+		i_free(setting->value);
+		setting->value = i_strdup(value);
+	} else {
+		setting = i_new(struct testsuite_setting, 1);
+		setting->identifier = i_strdup(identifier);
+		setting->value = i_strdup(value);
+
+		hash_table_insert(settings, identifier, setting);
+	}
+}
+
+void testsuite_setting_unset(const char *identifier)
+{
+	struct testsuite_setting *setting =
+		hash_table_lookup(settings, identifier);
+
+	if ( setting != NULL ) {
+		i_free(setting->identifier);
+		i_free(setting->value);
+		i_free(setting);
+
+		hash_table_remove(settings, identifier);
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/testsuite-settings.h
@@ -0,0 +1,12 @@
+#ifndef TESTSUITE_SETTINGS_H
+#define TESTSUITE_SETTINGS_H
+
+#include "sieve-common.h"
+
+void testsuite_settings_init(void);
+void testsuite_settings_deinit(void);
+
+void testsuite_setting_set(const char *identifier, const char *value);
+void testsuite_setting_unset(const char *identifier);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/testsuite-smtp.c
@@ -0,0 +1,176 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "ostream.h"
+#include "unlink-directory.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-interpreter.h"
+
+#include "testsuite-message.h"
+#include "testsuite-common.h"
+#include "testsuite-smtp.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+struct testsuite_smtp_message {
+	const struct smtp_address *envelope_from, *envelope_to;
+	const char *file;
+};
+
+static pool_t testsuite_smtp_pool;
+static const char *testsuite_smtp_tmp;
+static ARRAY(struct testsuite_smtp_message) testsuite_smtp_messages;
+
+/*
+ * Initialize
+ */
+
+void testsuite_smtp_init(void)
+{
+	pool_t pool;
+
+	testsuite_smtp_pool = pool = pool_alloconly_create("testsuite_smtp", 8192);
+
+	testsuite_smtp_tmp = p_strconcat
+		(pool, testsuite_tmp_dir_get(), "/smtp", NULL);
+
+	if ( mkdir(testsuite_smtp_tmp, 0700) < 0 ) {
+		i_fatal("failed to create temporary directory '%s': %m.",
+			testsuite_smtp_tmp);
+	}
+
+	p_array_init(&testsuite_smtp_messages, pool, 16);
+}
+
+void testsuite_smtp_deinit(void)
+{
+	const char *error;
+
+	if ( unlink_directory(testsuite_smtp_tmp, UNLINK_DIRECTORY_FLAG_RMDIR, &error) < 0 )
+		i_warning("failed to remove temporary directory '%s': %s.",
+			testsuite_smtp_tmp, error);
+
+	pool_unref(&testsuite_smtp_pool);
+}
+
+void testsuite_smtp_reset(void)
+{
+	testsuite_smtp_deinit();
+	testsuite_smtp_init();
+}
+
+/*
+ * Simulated SMTP out
+ */
+
+struct testsuite_smtp {
+	char *msg_file;
+	struct smtp_address *mail_from;
+	struct ostream *output;
+};
+
+void *testsuite_smtp_start
+(const struct sieve_script_env *senv ATTR_UNUSED,
+	const struct smtp_address *mail_from)
+{
+	struct testsuite_smtp *smtp;
+	unsigned int smtp_count = array_count(&testsuite_smtp_messages);
+	int fd;
+
+	smtp = i_new(struct testsuite_smtp, 1);
+
+	smtp->msg_file = i_strdup_printf("%s/%d.eml", testsuite_smtp_tmp, smtp_count);
+	smtp->mail_from = smtp_address_clone(default_pool, mail_from);
+	
+	if ( (fd=open(smtp->msg_file, O_WRONLY | O_CREAT, 0600)) < 0 ) {
+		i_fatal("failed create tmp file for SMTP simulation: open(%s) failed: %m",
+			smtp->msg_file);
+	}
+
+	smtp->output = o_stream_create_fd_autoclose(&fd, (size_t)-1);
+
+	return (void *) smtp;
+}
+
+void testsuite_smtp_add_rcpt
+(const struct sieve_script_env *senv ATTR_UNUSED,
+	void *handle, const struct smtp_address *rcpt_to)
+{
+	struct testsuite_smtp *smtp = (struct testsuite_smtp *) handle;
+	struct testsuite_smtp_message *msg;
+
+	msg = array_append_space(&testsuite_smtp_messages);
+
+	msg->file = p_strdup(testsuite_smtp_pool, smtp->msg_file);
+	msg->envelope_from = smtp_address_clone(testsuite_smtp_pool, smtp->mail_from);
+	msg->envelope_to = smtp_address_clone(testsuite_smtp_pool, rcpt_to);
+}
+
+struct ostream *testsuite_smtp_send
+(const struct sieve_script_env *senv ATTR_UNUSED, void *handle)
+{
+	struct testsuite_smtp *smtp = (struct testsuite_smtp *) handle;
+
+	return smtp->output;
+}
+
+void testsuite_smtp_abort
+(const struct sieve_script_env *senv ATTR_UNUSED,
+	void *handle)
+{
+	struct testsuite_smtp *smtp = (struct testsuite_smtp *) handle;
+
+	o_stream_ignore_last_errors(smtp->output);
+	o_stream_unref(&smtp->output);
+	i_unlink(smtp->msg_file);
+	i_free(smtp->msg_file);
+	i_free(smtp->mail_from);
+	i_free(smtp);
+}
+
+int testsuite_smtp_finish
+(const struct sieve_script_env *senv ATTR_UNUSED,
+	void *handle, const char **error_r ATTR_UNUSED)
+{
+	struct testsuite_smtp *smtp = (struct testsuite_smtp *) handle;
+	int ret = 1;
+
+	if (o_stream_finish(smtp->output) < 0) {
+		i_error("write(%s) failed: %s", smtp->msg_file,
+			o_stream_get_error(smtp->output));
+		ret = -1;
+	}
+	o_stream_unref(&smtp->output);
+	i_free(smtp->msg_file);
+	i_free(smtp->mail_from);
+	i_free(smtp);
+	return ret;
+}
+
+/*
+ * Access
+ */
+
+bool testsuite_smtp_get
+(const struct sieve_runtime_env *renv, unsigned int index)
+{
+	const struct testsuite_smtp_message *smtp_msg;
+
+	if ( index >= array_count(&testsuite_smtp_messages) )
+		return FALSE;
+
+	smtp_msg = array_idx(&testsuite_smtp_messages, index);
+
+	testsuite_message_set_file(renv, smtp_msg->file);
+	testsuite_envelope_set_sender_address(renv, smtp_msg->envelope_from);
+	testsuite_envelope_set_recipient_address(renv, smtp_msg->envelope_to);
+
+	return TRUE;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/testsuite-smtp.h
@@ -0,0 +1,35 @@
+#ifndef TESTSUITE_SMTP_H
+#define TESTSUITE_SMTP_H
+
+void testsuite_smtp_init(void);
+void testsuite_smtp_deinit(void);
+void testsuite_smtp_reset(void);
+
+/*
+ * Simulated SMTP out
+ */
+
+void *testsuite_smtp_start
+	(const struct sieve_script_env *senv ATTR_UNUSED,
+		const struct smtp_address *mail_from);
+void testsuite_smtp_add_rcpt
+	(const struct sieve_script_env *senv ATTR_UNUSED,
+		void *handle, const struct smtp_address *rcpt_to);
+struct ostream *testsuite_smtp_send
+	(const struct sieve_script_env *senv ATTR_UNUSED,
+		void *handle);
+void testsuite_smtp_abort
+	(const struct sieve_script_env *senv ATTR_UNUSED,
+		void *handle);
+int testsuite_smtp_finish
+	(const struct sieve_script_env *senv ATTR_UNUSED,
+		void *handle, const char **error_r);
+
+/*
+ * Access
+ */
+
+bool testsuite_smtp_get
+	(const struct sieve_runtime_env *renv, unsigned int index);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/testsuite-substitutions.c
@@ -0,0 +1,253 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve.h"
+#include "sieve-code.h"
+#include "sieve-commands.h"
+#include "sieve-binary.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "testsuite-common.h"
+#include "testsuite-substitutions.h"
+
+/*
+ * Forward declarations
+ */
+
+void testsuite_opr_substitution_emit
+	(struct sieve_binary_block *sblock, const struct testsuite_substitution *tsub,
+		const char *param);
+
+/*
+ * Testsuite substitutions
+ */
+
+/* FIXME: make this extendible */
+
+enum {
+	TESTSUITE_SUBSTITUTION_FILE,
+};
+
+static const struct testsuite_substitution_def testsuite_file_substitution;
+
+static const struct testsuite_substitution_def *substitutions[] = {
+	&testsuite_file_substitution,
+};
+
+static const unsigned int substitutions_count = N_ELEMENTS(substitutions);
+
+static inline const struct testsuite_substitution_def *
+testsuite_substitution_get
+(unsigned int code)
+{
+	if ( code >= substitutions_count )
+		return NULL;
+
+	return substitutions[code];
+}
+
+static const struct testsuite_substitution *testsuite_substitution_create
+(struct sieve_ast *ast, const char *identifier)
+{
+	unsigned int i;
+
+	for ( i = 0; i < substitutions_count; i++ ) {
+		if ( strcasecmp(substitutions[i]->obj_def.identifier, identifier) == 0 ) {
+			const struct testsuite_substitution_def *tsub_def = substitutions[i];
+			struct testsuite_substitution *tsub;
+
+			tsub = p_new(sieve_ast_pool(ast), struct testsuite_substitution, 1);
+			tsub->object.def = &tsub_def->obj_def;
+			tsub->object.ext = testsuite_ext;
+			tsub->def = tsub_def;
+
+			return tsub;
+		}
+	}
+
+	return NULL;
+}
+
+/*
+ * Substitution argument
+ */
+
+static bool arg_testsuite_substitution_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+		struct sieve_command *context);
+
+struct _testsuite_substitution_context {
+	const struct testsuite_substitution *tsub;
+	const char *param;
+};
+
+const struct sieve_argument_def testsuite_substitution_argument = {
+	.identifier = "@testsuite-substitution",
+	.generate = arg_testsuite_substitution_generate
+};
+
+struct sieve_ast_argument *testsuite_substitution_argument_create
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_ast *ast,
+	unsigned int source_line, const char *substitution, const char *param)
+{
+	const struct testsuite_substitution *tsub;
+	struct _testsuite_substitution_context *tsctx;
+	struct sieve_ast_argument *arg;
+	pool_t pool;
+
+	tsub = testsuite_substitution_create(ast, substitution);
+	if ( tsub == NULL )
+		return NULL;
+
+	arg = sieve_ast_argument_create(ast, source_line);
+	arg->type = SAAT_STRING;
+
+	pool = sieve_ast_pool(ast);
+	tsctx = p_new(pool, struct _testsuite_substitution_context, 1);
+	tsctx->tsub = tsub;
+	tsctx->param = p_strdup(pool, param);
+
+	arg->argument = sieve_argument_create
+		(ast, &testsuite_substitution_argument, testsuite_ext, 0);
+	arg->argument->data = (void *) tsctx;
+
+	return arg;
+}
+
+static bool arg_testsuite_substitution_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+	struct sieve_command *context ATTR_UNUSED)
+{
+	struct _testsuite_substitution_context *tsctx =
+		(struct _testsuite_substitution_context *) arg->argument->data;
+
+	testsuite_opr_substitution_emit(cgenv->sblock, tsctx->tsub, tsctx->param);
+
+	return TRUE;
+}
+
+/*
+ * Substitution operand
+ */
+
+static bool opr_substitution_dump
+	(const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+		sieve_size_t *address);
+static int opr_substitution_read_value
+	(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+		sieve_size_t *address, string_t **str);
+
+const struct sieve_opr_string_interface testsuite_substitution_interface = {
+	opr_substitution_dump,
+	opr_substitution_read_value
+};
+
+const struct sieve_operand_def testsuite_substitution_operand = {
+	.name = "test-substitution",
+	.ext_def = &testsuite_extension,
+	.code = TESTSUITE_OPERAND_SUBSTITUTION,
+	.class = &string_class,
+	.interface = &testsuite_substitution_interface
+};
+
+void testsuite_opr_substitution_emit
+(struct sieve_binary_block *sblock, const struct testsuite_substitution *tsub,
+	const char *param)
+{
+	/* Default variable storage */
+	(void) sieve_operand_emit
+		(sblock, testsuite_ext, &testsuite_substitution_operand);
+	(void) sieve_binary_emit_unsigned(sblock, tsub->object.def->code);
+	(void) sieve_binary_emit_cstring(sblock, param);
+}
+
+static bool opr_substitution_dump
+(const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+	sieve_size_t *address)
+{
+	unsigned int code = 0;
+	const struct testsuite_substitution_def *tsub;
+	string_t *param;
+
+	if ( !sieve_binary_read_unsigned(denv->sblock, address, &code) )
+		return FALSE;
+
+	tsub = testsuite_substitution_get(code);
+	if ( tsub == NULL )
+		return FALSE;
+
+	if ( !sieve_binary_read_string(denv->sblock, address, &param) )
+		return FALSE;
+
+	if ( oprnd->field_name != NULL )
+		sieve_code_dumpf(denv, "%s: TEST_SUBS %%{%s:%s}",
+			oprnd->field_name, tsub->obj_def.identifier, str_c(param));
+	else
+		sieve_code_dumpf(denv, "TEST_SUBS %%{%s:%s}",
+			tsub->obj_def.identifier, str_c(param));
+	return TRUE;
+}
+
+static int opr_substitution_read_value
+(const struct sieve_runtime_env *renv,
+	const struct sieve_operand *oprnd ATTR_UNUSED, sieve_size_t *address,
+	string_t **str_r)
+{
+	const struct testsuite_substitution_def *tsub;
+	unsigned int code = 0;
+	string_t *param;
+
+	if ( !sieve_binary_read_unsigned(renv->sblock, address, &code) )
+		return SIEVE_EXEC_BIN_CORRUPT;
+
+	tsub = testsuite_substitution_get(code);
+	if ( tsub == NULL )
+		return SIEVE_EXEC_FAILURE;
+
+	/* Parameter str can be NULL if we are requested to only skip and not
+	 * actually read the argument.
+	 */
+	if ( str_r == NULL ) {
+		if ( !sieve_binary_read_string(renv->sblock, address, NULL) )
+			return SIEVE_EXEC_BIN_CORRUPT;
+
+		return SIEVE_EXEC_OK;
+	}
+
+	if ( !sieve_binary_read_string(renv->sblock, address, &param) )
+		return SIEVE_EXEC_BIN_CORRUPT;
+
+	if ( !tsub->get_value(str_c(param), str_r) )
+		return SIEVE_EXEC_FAILURE;
+
+	return SIEVE_EXEC_OK;
+}
+
+/*
+ * Testsuite substitution definitions
+ */
+
+static bool testsuite_file_substitution_get_value
+	(const char *param, string_t **result);
+
+static const struct testsuite_substitution_def
+testsuite_file_substitution = {
+	SIEVE_OBJECT("file",
+		&testsuite_substitution_operand,
+		TESTSUITE_SUBSTITUTION_FILE),
+	.get_value = testsuite_file_substitution_get_value
+};
+
+static bool testsuite_file_substitution_get_value
+(const char *param, string_t **result)
+{
+	*result = t_str_new(256);
+
+	str_printfa(*result, "[FILE: %s]", param);
+	return TRUE;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/testsuite-substitutions.h
@@ -0,0 +1,23 @@
+#ifndef TESTSUITE_SUBSTITUTIONS_H
+#define TESTSUITE_SUBSTITUTIONS_H
+
+#include "sieve-common.h"
+#include "sieve-objects.h"
+
+struct testsuite_substitution_def {
+	struct sieve_object_def obj_def;
+
+	bool (*get_value)(const char *param, string_t **result);
+};
+
+struct testsuite_substitution {
+	struct sieve_object object;
+
+	const struct testsuite_substitution_def *def;
+};
+
+struct sieve_ast_argument *testsuite_substitution_argument_create
+	(struct sieve_validator *valdtr, struct sieve_ast *ast,
+		unsigned int source_line, const char *substitution, const char *param);
+
+#endif
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/testsuite-variables.c
@@ -0,0 +1,183 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-ast.h"
+#include "sieve-binary.h"
+#include "sieve-code.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "sieve-ext-variables.h"
+
+#include "testsuite-common.h"
+#include "testsuite-variables.h"
+
+/*
+ *
+ */
+
+static const struct sieve_extension *testsuite_ext_variables = NULL;
+
+/*
+ *
+ */
+
+bool testsuite_varnamespace_validate
+	(struct sieve_validator *valdtr, const struct sieve_variables_namespace *nspc,
+		struct sieve_ast_argument *arg, struct sieve_command *cmd,
+		ARRAY_TYPE(sieve_variable_name) *var_name, void **var_data,
+		bool assignment);
+bool testsuite_varnamespace_generate
+	(const struct sieve_codegen_env *cgenv,
+		const struct sieve_variables_namespace *nspc,
+		struct sieve_ast_argument *arg, struct sieve_command *cmd, void *var_data);
+bool testsuite_varnamespace_dump_variable
+	(const struct sieve_dumptime_env *denv,
+		const struct sieve_variables_namespace *nspc,
+		const struct sieve_operand *oprnd, sieve_size_t *address);
+int testsuite_varnamespace_read_variable
+	(const struct sieve_runtime_env *renv,
+		const struct sieve_variables_namespace *nspc,
+		const struct sieve_operand *oprnd, sieve_size_t *address, string_t **str_r);
+
+static const struct sieve_variables_namespace_def testsuite_namespace = {
+	SIEVE_OBJECT("tst", &testsuite_namespace_operand, 0),
+	testsuite_varnamespace_validate,
+	testsuite_varnamespace_generate,
+	testsuite_varnamespace_dump_variable,
+	testsuite_varnamespace_read_variable
+};
+
+bool testsuite_varnamespace_validate
+(struct sieve_validator *valdtr,
+	const struct sieve_variables_namespace *nspc ATTR_UNUSED,
+	struct sieve_ast_argument *arg, struct sieve_command *cmd ATTR_UNUSED,
+	ARRAY_TYPE(sieve_variable_name) *var_name, void **var_data,
+	bool assignment)
+{
+	struct sieve_ast *ast = arg->ast;
+	const struct sieve_variable_name *name_element;
+	const char *variable;
+
+	/* Check variable name */
+
+	if ( array_count(var_name) != 2 ) {
+		sieve_argument_validate_error(valdtr, arg,
+			"testsuite: invalid variable name within testsuite namespace: "
+			"encountered sub-namespace");
+		return FALSE;
+ 	}
+
+	name_element = array_idx(var_name, 1);
+	if ( name_element->num_variable >= 0 ) {
+		sieve_argument_validate_error(valdtr, arg,
+			"testsuite: invalid variable name within testsuite namespace 'tst.%d': "
+			"encountered numeric variable name", name_element->num_variable);
+		return FALSE;
+	}
+
+	variable = str_c(name_element->identifier);
+
+	if ( assignment ) {
+		sieve_argument_validate_error(valdtr, arg,
+			"testsuite: cannot assign to testsuite variable 'tst.%s'", variable);
+		return FALSE;
+	}
+
+	*var_data = (void *) p_strdup(sieve_ast_pool(ast), variable);
+
+	return TRUE;
+}
+
+bool testsuite_varnamespace_generate
+(const struct sieve_codegen_env *cgenv,
+	const struct sieve_variables_namespace *nspc,
+	struct sieve_ast_argument *arg ATTR_UNUSED,
+	struct sieve_command *cmd ATTR_UNUSED, void *var_data)
+{
+	const struct sieve_extension *this_ext = SIEVE_OBJECT_EXTENSION(nspc);
+	const char *variable = (const char *) var_data;
+
+	if ( this_ext == NULL )
+		return FALSE;
+
+	sieve_variables_opr_namespace_variable_emit
+		(cgenv->sblock, testsuite_ext_variables, this_ext, &testsuite_namespace);
+	sieve_binary_emit_cstring(cgenv->sblock, variable);
+
+	return TRUE;
+}
+
+bool testsuite_varnamespace_dump_variable
+(const struct sieve_dumptime_env *denv,
+	const struct sieve_variables_namespace *nspc ATTR_UNUSED,
+	const struct sieve_operand *oprnd, sieve_size_t *address)
+{
+	string_t *var_name;
+
+	if ( !sieve_binary_read_string(denv->sblock, address, &var_name) )
+		return FALSE;
+
+	if ( oprnd->field_name != NULL )
+		sieve_code_dumpf(denv, "%s: VAR ${tst.%s}",
+			oprnd->field_name, str_c(var_name));
+	else
+		sieve_code_dumpf(denv, "VAR ${tst.%s}",
+			str_c(var_name));
+
+	return TRUE;
+}
+
+int testsuite_varnamespace_read_variable
+(const struct sieve_runtime_env *renv,
+	const struct sieve_variables_namespace *nspc ATTR_UNUSED,
+	const struct sieve_operand *oprnd, sieve_size_t *address,
+	string_t **str_r)
+{
+	string_t *var_name;
+
+	if ( !sieve_binary_read_string(renv->sblock, address, &var_name) ) {
+		sieve_runtime_trace_operand_error(renv, oprnd,
+			"testsuite variable operand corrupt: invalid name");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	if ( str_r != NULL ) {
+		if ( strcmp(str_c(var_name), "path") == 0 )
+			*str_r = t_str_new_const(testsuite_test_path, strlen(testsuite_test_path));
+		else
+			*str_r = NULL;
+	}
+	return SIEVE_EXEC_OK;
+}
+
+
+/*
+ * Namespace registration
+ */
+
+static const struct sieve_extension_objects testsuite_namespaces =
+	SIEVE_VARIABLES_DEFINE_NAMESPACE(testsuite_namespace);
+
+const struct sieve_operand_def testsuite_namespace_operand = {
+	.name = "testsuite-namespace",
+	.ext_def = &testsuite_extension,
+	.code = TESTSUITE_OPERAND_NAMESPACE,
+	.class =  &sieve_variables_namespace_operand_class,
+	.interface = &testsuite_namespaces
+};
+
+void testsuite_variables_init
+(const struct sieve_extension *this_ext, struct sieve_validator *valdtr)
+{
+	testsuite_ext_variables = sieve_ext_variables_get_extension(this_ext->svinst);
+
+	sieve_variables_namespace_register
+		(testsuite_ext_variables, valdtr, this_ext, &testsuite_namespace);
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/testsuite-variables.h
@@ -0,0 +1,11 @@
+#ifndef TESTSUITE_VARIABLES_H
+#define TESTSUITE_VARIABLES_H
+
+extern const struct sieve_operand_def testsuite_namespace_operand;
+
+void testsuite_variables_init
+	(const struct sieve_extension *this_ext, struct sieve_validator *valdtr);
+
+#endif
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/testsuite.c
@@ -0,0 +1,244 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "lib-signals.h"
+#include "ioloop.h"
+#include "env-util.h"
+#include "ostream.h"
+#include "hostpid.h"
+#include "path-util.h"
+
+#include "sieve.h"
+#include "sieve-extensions.h"
+#include "sieve-script.h"
+#include "sieve-binary.h"
+#include "sieve-result.h"
+#include "sieve-interpreter.h"
+
+#include "sieve-tool.h"
+
+#include "testsuite-common.h"
+#include "testsuite-log.h"
+#include "testsuite-settings.h"
+#include "testsuite-result.h"
+#include "testsuite-message.h"
+#include "testsuite-smtp.h"
+#include "testsuite-mailstore.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <sysexits.h>
+
+const struct sieve_script_env *testsuite_scriptenv;
+
+/*
+ * Configuration
+ */
+
+#define DEFAULT_SENDMAIL_PATH "/usr/lib/sendmail"
+
+/*
+ * Testsuite execution
+ */
+
+static void print_help(void)
+{
+	printf(
+"Usage: testsuite [-D] [-E] [-d <dump-filename>]\n"
+"                 [-t <trace-filename>] [-T <trace-option>]\n"
+"                 [-P <plugin>] [-x <extensions>]\n"
+"                 <scriptfile>\n"
+	);
+}
+
+static int testsuite_run
+(struct sieve_binary *sbin, const struct sieve_message_data *msgdata,
+	const struct sieve_script_env *senv, struct sieve_error_handler *ehandler)
+{
+	struct sieve_interpreter *interp;
+	struct sieve_result *result;
+	int ret = 0;
+
+	/* Create the interpreter */
+	if ( (interp=sieve_interpreter_create
+		(sbin, NULL, msgdata, senv, ehandler, 0)) == NULL )
+		return SIEVE_EXEC_BIN_CORRUPT;
+
+	/* Run the interpreter */
+	result = testsuite_result_get();
+	sieve_result_ref(result);
+	ret = sieve_interpreter_run(interp, result);
+	sieve_result_unref(&result);
+
+	/* Free the interpreter */
+	sieve_interpreter_free(&interp);
+
+	return ret;
+}
+
+int main(int argc, char **argv)
+{
+	struct sieve_instance *svinst;
+	const char *scriptfile, *dumpfile, *tracefile;
+	struct sieve_trace_config trace_config;
+	struct sieve_binary *sbin;
+	const char *sieve_dir, *cwd, *error;
+	bool log_stdout = FALSE;
+	int ret, c;
+
+	sieve_tool = sieve_tool_init
+		("testsuite", &argc, &argv, "d:t:T:EDP:", TRUE);
+
+	/* Parse arguments */
+	dumpfile = tracefile = NULL;
+	i_zero(&trace_config);
+	trace_config.level = SIEVE_TRLVL_ACTIONS;
+	while ((c = sieve_tool_getopt(sieve_tool)) > 0) {
+		switch (c) {
+		case 'd':
+			/* destination address */
+			dumpfile = optarg;
+			break;
+		case 't':
+			/* trace file */
+			tracefile = optarg;
+			break;
+		case 'T':
+			sieve_tool_parse_trace_option(&trace_config, optarg);
+			break;
+		case 'E':
+			log_stdout = TRUE;
+			break;
+		default:
+			print_help();
+			i_fatal_status(EX_USAGE,
+				"Unknown argument: %c", c);
+			break;
+		}
+	}
+
+	if ( optind < argc ) {
+		scriptfile = t_strdup(argv[optind++]);
+	} else {
+		print_help();
+		i_fatal_status(EX_USAGE, "Missing <scriptfile> argument");
+	}
+
+	if (optind != argc) {
+		print_help();
+		i_fatal_status(EX_USAGE, "Unknown argument: %s", argv[optind]);
+	}
+
+	// FIXME: very very ugly
+	master_service_parse_option(master_service,
+		'o', "postmaster_address=postmaster@example.com");
+
+	if (t_get_working_dir(&cwd, &error) < 0)
+		i_fatal("Failed to get working directory: %s", error);
+	/* Initialize mail user */
+	sieve_tool_set_homedir(sieve_tool, cwd);
+
+	/* Initialize settings environment */
+	testsuite_settings_init();
+
+	/* Currently needed for include (FIXME) */
+	sieve_dir = strrchr(scriptfile, '/');
+	if ( sieve_dir == NULL )
+		sieve_dir= "./";
+	else {
+		sieve_dir = t_strdup_until(scriptfile, sieve_dir+1);
+	}
+
+	testsuite_setting_set
+		("sieve_dir", t_strconcat(sieve_dir, "included", NULL));
+	testsuite_setting_set
+		("sieve_global_dir", t_strconcat(sieve_dir, "included-global", NULL));
+
+	/* Finish testsuite initialization */
+	svinst = sieve_tool_init_finish(sieve_tool, FALSE, FALSE);
+	testsuite_init(svinst, sieve_dir, log_stdout);
+
+	printf("Test case: %s:\n\n", scriptfile);
+
+	/* Compile sieve script */
+	if ( (sbin = sieve_compile
+		(svinst, scriptfile, NULL, testsuite_log_main_ehandler, 0, NULL))
+			!= NULL ) {
+		struct sieve_trace_log *trace_log = NULL;
+		struct sieve_script_env scriptenv;
+
+		/* Dump script */
+		sieve_tool_dump_binary_to(sbin, dumpfile, FALSE);
+
+		if ( tracefile != NULL ) {
+			(void)sieve_trace_log_create(svinst,
+				(strcmp(tracefile, "-") == 0 ? NULL : tracefile),
+				&trace_log);
+		}
+
+		testsuite_mailstore_init();
+		testsuite_message_init();
+
+		if (sieve_script_env_init(&scriptenv,
+			testsuite_mailstore_get_user(), &error) < 0)
+			i_fatal("Failed to initialize script execution: %s", error);
+
+		scriptenv.default_mailbox = "INBOX";
+		scriptenv.smtp_start = testsuite_smtp_start;
+		scriptenv.smtp_add_rcpt = testsuite_smtp_add_rcpt;
+		scriptenv.smtp_send = testsuite_smtp_send;
+		scriptenv.smtp_abort = testsuite_smtp_abort;
+		scriptenv.smtp_finish = testsuite_smtp_finish;
+		scriptenv.trace_log = trace_log;
+		scriptenv.trace_config = trace_config;
+
+		testsuite_scriptenv = &scriptenv;
+
+		testsuite_result_init();
+
+		/* Run the test */
+		ret = testsuite_run
+			(sbin, &testsuite_msgdata, &scriptenv, testsuite_log_main_ehandler);
+
+		switch ( ret ) {
+		case SIEVE_EXEC_OK:
+			break;
+		case SIEVE_EXEC_FAILURE:
+		case SIEVE_EXEC_KEEP_FAILED:
+		case SIEVE_EXEC_TEMP_FAILURE:
+			testsuite_testcase_fail("test script execution aborted due to error");
+			break;
+		case SIEVE_EXEC_BIN_CORRUPT:
+			testsuite_testcase_fail("compiled test script binary is corrupt");
+			break;
+		}
+
+		sieve_close(&sbin);
+
+		/* De-initialize message environment */
+		testsuite_message_deinit();
+		testsuite_mailstore_deinit();
+		testsuite_result_deinit();
+
+		if ( trace_log != NULL )
+			sieve_trace_log_free(&trace_log);
+
+		testsuite_scriptenv = NULL;
+	} else {
+		testsuite_testcase_fail("failed to compile testcase script");
+	}
+
+	/* De-initialize testsuite */
+	testsuite_deinit();
+	testsuite_settings_deinit();
+
+	sieve_tool_deinit(&sieve_tool);
+
+	if ( !testsuite_testcase_result() )
+		return EXIT_FAILURE;
+
+	return EXIT_SUCCESS;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/tst-test-error.c
@@ -0,0 +1,273 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-script.h"
+#include "sieve-commands.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+#include "testsuite-common.h"
+#include "testsuite-log.h"
+
+/*
+ * Test_error command
+ *
+ * Syntax:
+ *   test [MATCH-TYPE] [COMPARATOR] [:index number] <key-list: string-list>
+ */
+
+static bool tst_test_error_registered
+	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool tst_test_error_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool tst_test_error_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def tst_test_error = {
+	.identifier = "test_error",
+	.type = SCT_TEST,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = tst_test_error_registered,
+	.validate = tst_test_error_validate,
+	.generate = tst_test_error_generate
+};
+
+/*
+ * Operation
+ */
+
+static bool tst_test_error_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_test_error_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def test_error_operation = {
+	.mnemonic = "TEST_ERROR",
+	.ext_def = &testsuite_extension,
+	.code = TESTSUITE_OPERATION_TEST_ERROR,
+	.dump = tst_test_error_operation_dump,
+	.execute = tst_test_error_operation_execute
+};
+
+/*
+ * Tagged arguments
+ */
+
+/* NOTE: This will be merged with the date-index extension when it is
+ * implemented.
+ */
+
+static bool tst_test_error_validate_index_tag
+	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+
+static const struct sieve_argument_def test_error_index_tag = {
+	.identifier = "index",
+	.validate = tst_test_error_validate_index_tag
+};
+
+enum tst_test_error_optional {
+	OPT_INDEX = SIEVE_MATCH_OPT_LAST,
+};
+
+
+/*
+ * Argument implementation
+ */
+
+static bool tst_test_error_validate_index_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *tag = *arg;
+
+	/* Detach the tag itself */
+	*arg = sieve_ast_arguments_detach(*arg,1);
+
+	/* Check syntax:
+	 *   :index number
+	 */
+	if ( !sieve_validate_tag_parameter
+		(valdtr, cmd, tag, *arg, NULL, 0, SAAT_NUMBER, FALSE) ) {
+		return FALSE;
+	}
+
+	/* Skip parameter */
+	*arg = sieve_ast_argument_next(*arg);
+	return TRUE;
+}
+
+
+/*
+ * Command registration
+ */
+
+static bool tst_test_error_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg)
+{
+	/* The order of these is not significant */
+	sieve_comparators_link_tag(valdtr, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR);
+	sieve_match_types_link_tags(valdtr, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
+
+	sieve_validator_register_tag
+		(valdtr, cmd_reg, ext, &test_error_index_tag, OPT_INDEX);
+
+	return TRUE;
+}
+
+/*
+ * Validation
+ */
+
+static bool tst_test_error_validate
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *tst)
+{
+	struct sieve_ast_argument *arg = tst->first_positional;
+	struct sieve_comparator cmp_default =
+		SIEVE_COMPARATOR_DEFAULT(i_octet_comparator);
+	struct sieve_match_type mcht_default =
+		SIEVE_COMPARATOR_DEFAULT(is_match_type);
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "key list", 2, SAAT_STRING_LIST) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+		return FALSE;
+
+	/* Validate the key argument to a specified match type */
+	return sieve_match_type_validate
+		(valdtr, tst, arg, &mcht_default, &cmp_default);
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_test_error_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
+{
+	sieve_operation_emit(cgenv->sblock, tst->ext, &test_error_operation);
+
+	/* Generate arguments */
+	return sieve_generate_arguments(cgenv, tst, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_test_error_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	int opt_code = 0;
+
+	sieve_code_dumpf(denv, "TEST_ERROR:");
+	sieve_code_descend(denv);
+
+	/* Handle any optional arguments */
+	for (;;) {
+		int opt;
+
+		if ( (opt=sieve_match_opr_optional_dump(denv, address, &opt_code))
+			< 0 )
+			return FALSE;
+
+		if ( opt == 0 ) break;
+
+		if ( opt_code == OPT_INDEX ) {
+			if ( !sieve_opr_number_dump(denv, address, "index") )
+				return FALSE;
+		} else {
+			return FALSE;
+		}
+	}
+
+	return sieve_opr_stringlist_dump(denv, address, "key list");
+}
+
+/*
+ * Intepretation
+ */
+
+static int tst_test_error_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	int opt_code = 0;
+	struct sieve_comparator cmp = SIEVE_COMPARATOR_DEFAULT(i_octet_comparator);
+	struct sieve_match_type mcht = SIEVE_COMPARATOR_DEFAULT(is_match_type);
+	struct sieve_stringlist *value_list, *key_list;
+	int index = -1;
+	int match, ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Read optional operands */
+	for (;;) {
+		sieve_number_t number;
+		int opt;
+
+		if ( (opt=sieve_match_opr_optional_read
+			(renv, address, &opt_code, &ret, &cmp, &mcht)) < 0 )
+			return ret;
+
+		if ( opt == 0 ) break;
+
+		if ( opt_code == OPT_INDEX ) {
+			if ( (ret=sieve_opr_number_read(renv, address, "index", &number)) <= 0 )
+				return ret;
+			index = (int) number;
+		} else {
+			sieve_runtime_trace_error(renv, "invalid optional operand");
+			return SIEVE_EXEC_BIN_CORRUPT;
+		}
+	}
+
+	/* Read key-list */
+	if ( (ret=sieve_opr_stringlist_read(renv, address, "key_list", &key_list))
+		<= 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	if ( index > 0 )
+		sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+			"testsuite: test_error test; match error message [index=%d]", index);
+	else
+		sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+			"testsuite: test_error test; match error messages");
+
+	/* Create value stringlist */
+	value_list = testsuite_log_stringlist_create(renv, index);
+
+	/* Perform match */
+	if ( (match=sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret)) < 0 )
+		return ret;
+
+	/* Set test result for subsequent conditional jump */
+	sieve_interpreter_set_test_result(renv->interp, match > 0);
+	return SIEVE_EXEC_OK;
+}
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/tst-test-multiscript.c
@@ -0,0 +1,155 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-script.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+#include "sieve.h"
+
+#include "testsuite-common.h"
+#include "testsuite-script.h"
+
+/*
+ * Test_multiscript command
+ *
+ * Syntax:
+ *   test_multiscript <scripts: string-list>
+ */
+
+static bool tst_test_multiscript_validate
+	(struct sieve_validator *validator, struct sieve_command *cmd);
+static bool tst_test_multiscript_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *tst);
+
+const struct sieve_command_def tst_test_multiscript = {
+	.identifier = "test_multiscript",
+	.type = SCT_TEST,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = tst_test_multiscript_validate,
+	.generate = tst_test_multiscript_generate,
+};
+
+/*
+ * Operation
+ */
+
+static bool tst_test_multiscript_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_test_multiscript_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def test_multiscript_operation = {
+	.mnemonic = "TEST_MULTISCRIPT",
+	.ext_def = &testsuite_extension,
+	.code = TESTSUITE_OPERATION_TEST_MULTISCRIPT,
+	.dump = tst_test_multiscript_operation_dump,
+	.execute = tst_test_multiscript_operation_execute
+};
+
+/*
+ * Validation
+ */
+
+static bool tst_test_multiscript_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+	struct sieve_ast_argument *arg = tst->first_positional;
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "scripts", 1, SAAT_STRING_LIST) ) {
+		return FALSE;
+	}
+
+	return sieve_validator_argument_activate(valdtr, tst, arg, FALSE);
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_test_multiscript_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
+{
+	sieve_operation_emit(cgenv->sblock, tst->ext, &test_multiscript_operation);
+
+	/* Generate arguments */
+	return sieve_generate_arguments(cgenv, tst, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_test_multiscript_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "TEST_MULTISCRIPT:");
+	sieve_code_descend(denv);
+
+	if ( !sieve_opr_stringlist_dump(denv, address, "scripts") )
+		return FALSE;
+
+	return TRUE;
+}
+
+/*
+ * Intepretation
+ */
+
+static int tst_test_multiscript_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	struct sieve_stringlist *scripts_list;
+	string_t *script_name;
+	ARRAY_TYPE (const_string) scriptfiles;
+	bool result = TRUE;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	if ( (ret=sieve_opr_stringlist_read(renv, address, "scripts", &scripts_list))
+		<= 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+		"testsuite: test_multiscript test");
+	sieve_runtime_trace_descend(renv);
+
+	t_array_init(&scriptfiles, 16);
+
+	script_name = NULL;
+	while ( result &&
+		(ret=sieve_stringlist_next_item(scripts_list, &script_name)) > 0 ) {
+		const char *script = t_strdup(str_c(script_name));
+
+		array_append(&scriptfiles, &script, 1);
+	}
+
+	result = result && (ret >= 0) &&
+		testsuite_script_multiscript(renv, &scriptfiles);
+
+	/* Set result */
+	sieve_interpreter_set_test_result(renv->interp, result);
+
+	return SIEVE_EXEC_OK;
+}
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/tst-test-result-action.c
@@ -0,0 +1,268 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-script.h"
+#include "sieve-commands.h"
+#include "sieve-actions.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-result.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+#include "testsuite-common.h"
+#include "testsuite-result.h"
+
+/*
+ * test_result_action command
+ *
+ * Syntax:
+ *   test_result_action [MATCH-TYPE] [COMPARATOR] [:index number]
+ *     <key-list: string-list>
+ */
+
+static bool tst_test_result_action_registered
+	(struct sieve_validator *validator, const struct sieve_extension *ext,
+		struct sieve_command_registration *cmd_reg);
+static bool tst_test_result_action_validate
+	(struct sieve_validator *validator, struct sieve_command *cmd);
+static bool tst_test_result_action_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def tst_test_result_action = {
+	.identifier = "test_result_action",
+	.type = SCT_TEST,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = tst_test_result_action_registered,
+	.validate = tst_test_result_action_validate,
+	.generate = tst_test_result_action_generate
+};
+
+/*
+ * Operation
+ */
+
+static bool tst_test_result_action_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_test_result_action_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def test_result_action_operation = {
+	.mnemonic = "TEST_RESULT_ACTION",
+	.ext_def = &testsuite_extension,
+	.code = TESTSUITE_OPERATION_TEST_RESULT_ACTION,
+	.dump = tst_test_result_action_operation_dump,
+	.execute = tst_test_result_action_operation_execute
+};
+
+/*
+ * Tagged arguments
+ */
+
+/* FIXME: merge this with the test_error version of this tag */
+
+static bool tst_test_result_action_validate_index_tag
+	(struct sieve_validator *validator, struct sieve_ast_argument **arg,
+		struct sieve_command *cmd);
+
+static const struct sieve_argument_def test_result_action_index_tag = {
+	.identifier = "index",
+	.validate = tst_test_result_action_validate_index_tag
+};
+
+enum tst_test_result_action_optional {
+	OPT_INDEX = SIEVE_MATCH_OPT_LAST,
+};
+
+/*
+ * Argument implementation
+ */
+
+static bool tst_test_result_action_validate_index_tag
+(struct sieve_validator *validator, struct sieve_ast_argument **arg,
+	struct sieve_command *cmd)
+{
+	struct sieve_ast_argument *tag = *arg;
+
+	/* Detach the tag itself */
+	*arg = sieve_ast_arguments_detach(*arg,1);
+
+	/* Check syntax:
+	 *   :index number
+	 */
+	if ( !sieve_validate_tag_parameter
+		(validator, cmd, tag, *arg, NULL, 0, SAAT_NUMBER, FALSE) ) {
+		return FALSE;
+	}
+
+	/* Skip parameter */
+	*arg = sieve_ast_argument_next(*arg);
+	return TRUE;
+}
+
+
+/*
+ * Command registration
+ */
+
+static bool tst_test_result_action_registered
+(struct sieve_validator *validator, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg)
+{
+	/* The order of these is not significant */
+	sieve_comparators_link_tag(validator, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR);
+	sieve_match_types_link_tags(validator, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
+
+	sieve_validator_register_tag
+		(validator, cmd_reg, ext, &test_result_action_index_tag, OPT_INDEX);
+
+	return TRUE;
+}
+
+/*
+ * Validation
+ */
+
+static bool tst_test_result_action_validate
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *tst)
+{
+	struct sieve_ast_argument *arg = tst->first_positional;
+	struct sieve_comparator cmp_default =
+		SIEVE_COMPARATOR_DEFAULT(i_octet_comparator);
+	struct sieve_match_type mcht_default =
+		SIEVE_COMPARATOR_DEFAULT(is_match_type);
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "key list", 2, SAAT_STRING_LIST) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+		return FALSE;
+
+	/* Validate the key argument to a specified match type */
+	return sieve_match_type_validate
+		(valdtr, tst, arg, &mcht_default, &cmp_default);
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_test_result_action_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
+{
+	sieve_operation_emit(cgenv->sblock, tst->ext, &test_result_action_operation);
+
+	/* Generate arguments */
+	return sieve_generate_arguments(cgenv, tst, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_test_result_action_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	int opt_code = 0;
+
+	sieve_code_dumpf(denv, "TEST_RESULT_ACTION:");
+	sieve_code_descend(denv);
+
+	/* Handle any optional arguments */
+	for (;;) {
+		int opt;
+
+		if ( (opt=sieve_match_opr_optional_dump(denv, address, &opt_code))
+			< 0 )
+			return FALSE;
+
+		if ( opt == 0 ) break;
+
+		if ( opt_code == OPT_INDEX ) {
+			if ( !sieve_opr_number_dump(denv, address, "index") )
+				return FALSE;
+		} else {
+			return FALSE;
+		}
+	}
+
+	return sieve_opr_stringlist_dump(denv, address, "key list");
+}
+
+/*
+ * Intepretation
+ */
+
+static int tst_test_result_action_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	int opt_code = 0;
+	struct sieve_comparator cmp = SIEVE_COMPARATOR_DEFAULT(i_octet_comparator);
+	struct sieve_match_type mcht = SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+	struct sieve_stringlist *value_list, *key_list;
+	int index = 0;
+	int match, ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Read optional operands */
+	for (;;) {
+		sieve_number_t number;
+		int opt;
+
+		if ( (opt=sieve_match_opr_optional_read
+			(renv, address, &opt_code, &ret, &cmp, &mcht)) < 0 )
+			return ret;
+
+		if ( opt == 0 ) break;
+
+		if ( opt_code == OPT_INDEX ) {
+			if ( (ret=sieve_opr_number_read(renv, address, "index", &number)) <= 0 )
+				return ret;
+			index = (int) number;
+		} else {
+			sieve_runtime_trace_error(renv, "invalid optional operand");
+			return SIEVE_EXEC_BIN_CORRUPT;
+		}
+	}
+
+	/* Read key-list */
+	if ( (ret=sieve_opr_stringlist_read(renv, address, "key-list", &key_list))
+		<= 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+		"testsuite: test_result_action test; match result name (index: %d)", index);
+
+	/* Create value stringlist */
+	value_list = testsuite_result_stringlist_create(renv, index);
+
+	/* Perform match */
+	if ( (match=sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret)) < 0 )
+		return ret;
+
+	/* Set test result for subsequent conditional jump */
+	sieve_interpreter_set_test_result(renv->interp, match > 0);
+	return SIEVE_EXEC_OK;
+}
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/tst-test-result-execute.c
@@ -0,0 +1,96 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-script.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+#include "sieve.h"
+
+#include "testsuite-common.h"
+#include "testsuite-result.h"
+
+/*
+ * Test_result_execute command
+ *
+ * Syntax:
+ *   test_result_execute
+ */
+
+static bool tst_test_result_execute_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+const struct sieve_command_def tst_test_result_execute = {
+	.identifier = "test_result_execute",
+	.type = SCT_TEST,
+	.positional_args = 0,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.generate = tst_test_result_execute_generate
+};
+
+/*
+ * Operation
+ */
+
+static int tst_test_result_execute_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def test_result_execute_operation = {
+	.mnemonic = "TEST_RESULT_EXECUTE",
+	.ext_def = &testsuite_extension,
+	.code = TESTSUITE_OPERATION_TEST_RESULT_EXECUTE,
+	.execute = tst_test_result_execute_operation_execute
+};
+
+/*
+ * Code generation
+ */
+
+static bool tst_test_result_execute_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
+{
+	sieve_operation_emit(cgenv->sblock, tst->ext, &test_result_execute_operation);
+
+	return TRUE;
+}
+
+/*
+ * Intepretation
+ */
+
+static int tst_test_result_execute_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED)
+{
+	bool result = TRUE;
+
+	/*
+	 * Perform operation
+	 */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+		"testsuite: test_result_execute test");
+
+	result = testsuite_result_execute(renv);
+
+	if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_TESTS) ) {
+		sieve_runtime_trace_descend(renv);
+		sieve_runtime_trace(renv, 0, "execution of result %s",
+			( result ? "succeeded" : "failed" ));
+	}
+
+	/* Set result */
+	sieve_interpreter_set_test_result(renv->interp, result);
+
+	return SIEVE_EXEC_OK;
+}
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/tst-test-script-compile.c
@@ -0,0 +1,144 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-script.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+#include "sieve.h"
+
+#include "testsuite-common.h"
+#include "testsuite-script.h"
+
+/*
+ * Test_script_compile command
+ *
+ * Syntax:
+ *   test_script_compile <scriptpath: string>
+ */
+
+static bool tst_test_script_compile_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool tst_test_script_compile_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+const struct sieve_command_def tst_test_script_compile = {
+	.identifier = "test_script_compile",
+	.type = SCT_TEST,
+	.positional_args = 1,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.validate = tst_test_script_compile_validate,
+	.generate = tst_test_script_compile_generate
+};
+
+/*
+ * Operation
+ */
+
+static bool tst_test_script_compile_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_test_script_compile_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def test_script_compile_operation = {
+	.mnemonic = "TEST_SCRIPT_COMPILE",
+	.ext_def = &testsuite_extension,
+	.code = TESTSUITE_OPERATION_TEST_SCRIPT_COMPILE,
+	.dump = tst_test_script_compile_operation_dump,
+	.execute = tst_test_script_compile_operation_execute
+};
+
+/*
+ * Validation
+ */
+
+static bool tst_test_script_compile_validate
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *tst)
+{
+	struct sieve_ast_argument *arg = tst->first_positional;
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "script", 1, SAAT_STRING) ) {
+		return FALSE;
+	}
+
+	return sieve_validator_argument_activate(valdtr, tst, arg, FALSE);
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_test_script_compile_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
+{
+	sieve_operation_emit(cgenv->sblock, tst->ext, &test_script_compile_operation);
+
+	/* Generate arguments */
+	return sieve_generate_arguments(cgenv, tst, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_test_script_compile_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "TEST_SCRIPT_COMPILE:");
+	sieve_code_descend(denv);
+
+	if ( !sieve_opr_string_dump(denv, address, "script-name") )
+		return FALSE;
+
+	return TRUE;
+}
+
+/*
+ * Intepretation
+ */
+
+static int tst_test_script_compile_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	string_t *script_name;
+	bool result = TRUE;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	if ( (ret=sieve_opr_string_read(renv, address, "script-name", &script_name))
+		<= 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_TESTS) ) {
+		sieve_runtime_trace(renv, 0, "testsuite: test_script_compile test");
+		sieve_runtime_trace_descend(renv);
+	}
+
+	/* Attempt script compile */
+
+	result = testsuite_script_compile(renv, str_c(script_name));
+
+	/* Set result */
+	sieve_interpreter_set_test_result(renv->interp, result);
+
+	return SIEVE_EXEC_OK;
+}
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/src/testsuite/tst-test-script-run.c
@@ -0,0 +1,198 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-script.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+#include "sieve.h"
+
+#include "testsuite-common.h"
+#include "testsuite-script.h"
+#include "testsuite-result.h"
+
+/*
+ * Test_script_run command
+ *
+ * Syntax:
+ *   test_script_run
+ */
+
+static bool tst_test_script_run_registered
+(struct sieve_validator *validator, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg);
+static bool tst_test_script_run_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+const struct sieve_command_def tst_test_script_run = {
+	.identifier = "test_script_run",
+	.type = SCT_TEST,
+	.positional_args = 0,
+	.subtests = 0,
+	.block_allowed = FALSE,
+	.block_required = FALSE,
+	.registered = tst_test_script_run_registered,
+	.generate = tst_test_script_run_generate
+};
+
+/*
+ * Operation
+ */
+
+static bool tst_test_script_run_operation_dump
+	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_test_script_run_operation_execute
+	(const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def test_script_run_operation = {
+	.mnemonic = "TEST_SCRIPT_RUN",
+	.ext_def = &testsuite_extension,
+	.code = TESTSUITE_OPERATION_TEST_SCRIPT_RUN,
+	.dump = tst_test_script_run_operation_dump,
+	.execute = tst_test_script_run_operation_execute
+};
+
+/*
+ * Tagged arguments
+ */
+
+/* Codes for optional arguments */
+
+enum cmd_vacation_optional {
+	OPT_END,
+	OPT_APPEND_RESULT
+};
+
+/* Tags */
+
+static const struct sieve_argument_def append_result_tag = {
+	.identifier = "append_result"
+};
+
+static bool tst_test_script_run_registered
+(struct sieve_validator *validator, const struct sieve_extension *ext,
+	struct sieve_command_registration *cmd_reg)
+{
+	sieve_validator_register_tag
+		(validator, cmd_reg, ext, &append_result_tag, OPT_APPEND_RESULT);
+
+	return TRUE;
+}
+
+
+/*
+ * Code generation
+ */
+
+static bool tst_test_script_run_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
+{
+	sieve_operation_emit(cgenv->sblock, tst->ext, &test_script_run_operation);
+
+	return sieve_generate_arguments(cgenv, tst, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_test_script_run_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	int opt_code = 0;
+
+	sieve_code_dumpf(denv, "TEST_SCRIPT_RUN");
+	sieve_code_descend(denv);
+
+	/* Dump optional operands */
+	for (;;) {
+		int opt;
+
+		if ( (opt=sieve_opr_optional_dump(denv, address, &opt_code)) < 0 )
+			return FALSE;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_APPEND_RESULT:
+			sieve_code_dumpf(denv, "append_result");
+			break;
+		default:
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+
+/*
+ * Intepretation
+ */
+
+static int tst_test_script_run_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	bool append_result = FALSE;
+	int opt_code = 0;
+	bool result = TRUE;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Optional operands */
+	for (;;) {
+		int opt;
+
+		if ( (opt=sieve_opr_optional_read(renv, address, &opt_code)) < 0 )
+			return SIEVE_EXEC_BIN_CORRUPT;
+
+		if ( opt == 0 ) break;
+
+		switch ( opt_code ) {
+		case OPT_APPEND_RESULT:
+			append_result = TRUE;
+			break;
+		default:
+			sieve_runtime_trace_error(renv,
+				"unknown optional operand");
+			return SIEVE_EXEC_BIN_CORRUPT;
+		}
+	}
+
+	/*
+	 * Perform operation
+	 */
+
+	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+		"testsuite: run compiled script [append_result=%s]",
+		( append_result ? "yes" : "no" ));
+
+	/* Reset result object */
+	if ( !append_result )
+		testsuite_result_reset(renv);
+
+	/* Run script */
+	result = testsuite_script_run(renv);
+
+	if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_TESTS) ) {
+		sieve_runtime_trace_descend(renv);
+		sieve_runtime_trace(renv, 0, "execution of script %s",
+			( result ? "succeeded" : "failed" ));
+	}
+
+	/* Indicate test status */
+	sieve_interpreter_set_test_result(renv->interp, result);
+
+	return SIEVE_EXEC_OK;
+}
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/stamp.h.in
@@ -0,0 +1 @@
+// empty file
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/comparators/i-ascii-casemap.svtest
@@ -0,0 +1,39 @@
+require "vnd.dovecot.testsuite";
+
+test_set "message" text:
+From: stephan@example.org
+Cc: frop@example.com
+To: test@dovecot.example.net
+X-A: This is a TEST header
+Subject: Test Message
+
+Test!
+.
+;
+
+test "i;ascii-casemap :contains (1)" {
+	if not header :contains :comparator "i;ascii-casemap" "X-A" "TEST" {
+		test_fail "should have matched";
+	}
+}
+
+test "i;ascii-casemap :contains (2)" {
+	if not header :contains :comparator "i;ascii-casemap" "X-A" "test" {
+		test_fail "should have matched";
+	}
+}
+
+test "i;ascii-casemap :matches (1)" {
+	if not header :matches :comparator "i;ascii-casemap" "X-A" "This*TEST*r" {
+		test_fail "should have matched";
+	}
+}
+
+test "i;ascii-casemap :matches (2)" {
+	if not header :matches :comparator "i;ascii-casemap" "X-A" "ThIs*tEsT*R" {
+		test_fail "should have matched";
+	}
+}
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/comparators/i-octet.svtest
@@ -0,0 +1,37 @@
+require "vnd.dovecot.testsuite";
+
+test_set "message" text:
+From: stephan@example.org
+Cc: frop@example.com
+To: test@dovecot.example.net
+X-A: This is a TEST header
+Subject: Test Message
+
+Test!
+.
+;
+
+test "i;octet :contains" {
+	if not header :contains :comparator "i;octet" "X-A" "TEST" {
+		test_fail "should have matched";
+	}
+}
+
+test "i;octet not :contains" {
+	if header :contains :comparator "i;octet" "X-A" "test" {
+		test_fail "should not have matched";
+	}
+}
+
+test "i;octet :matches" {
+	if not header :matches :comparator "i;octet" "X-A" "This*TEST*r" {
+		test_fail "should have matched";
+	}
+}
+
+test "i;octet not :matches" {
+	if header :matches :comparator "i;octet" "X-A" "ThIs*tEsT*R" {
+		test_fail "should not have matched";
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/compile.svtest
@@ -0,0 +1,16 @@
+require "vnd.dovecot.testsuite";
+
+# Just test whether valid scripts will compile without problems
+
+test "Trivial" {
+	if not test_script_compile "trivial.sieve" {
+		test_fail "could not compile";
+	}
+}
+
+test "Redirect" {
+	if not test_script_compile "redirect.sieve" {
+		test_fail "could not compile";
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/errors.svtest
@@ -0,0 +1,381 @@
+require "vnd.dovecot.testsuite";
+
+require "relational";
+require "comparator-i;ascii-numeric";
+
+/*
+ * Errors triggered in the compiled scripts are pretty reduntant over the
+ * tested commands, but we want to be thorough.
+ */
+
+/*
+ * Lexer errors
+ */
+
+test "Lexer errors (FIXME: count only)" {
+	if test_script_compile "errors/lexer.sieve" {
+		test_fail "compile should have failed.";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "9" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+/*
+ * Parser errors
+ */
+
+test "Parser errors (FIXME: count only)" {
+	if test_script_compile "errors/parser.sieve" {
+		test_fail "compile should have failed.";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "9" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+/*
+ * Header test
+ */
+
+test "Header errors" {
+	if test_script_compile "errors/header.sieve" {
+		test_fail "compile should have failed.";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "10" {
+		test_fail "wrong number of errors reported";
+	}
+
+	if not test_error :index 1 :matches
+		"unknown * ':all' for * header test *" {
+		test_fail "error 1 is invalid";
+	}
+
+	if not test_error :index 2 :matches
+		"*header test * string list * 1 (header names), but * number *" {
+		test_fail "error 2 is invalid";
+	}
+
+	if not test_error :index 3 :matches
+		"*header test * string list * 2 (key list), * number *" {
+		test_fail "error 3 is invalid";
+	}
+
+	if not test_error :index 4 :matches
+		"unknown tagged argument ':tag' for the header test *" {
+		test_fail "error 4 is invalid";
+	}
+
+	if not test_error :index 5 :matches
+		"* header test requires 2 *, but 1 *" {
+		test_fail "error 5 is invalid";
+	}
+
+	if not test_error :index 6 :matches
+		"* header test requires 2 *, but 0 *" {
+		test_fail "error 6 is invalid";
+	}
+
+	if not test_error :index 7 :matches
+		"*header test accepts no sub-tests* specified*" {
+		test_fail "error 7 is invalid";
+	}
+
+	if not test_error :index 8 :matches
+		"* use test 'header' * command*" {
+		test_fail "error 8 is invalid";
+	}
+
+	if not test_error :index 9 :matches
+		"* use test 'header' * command*" {
+		test_fail "error 9 is invalid";
+	}
+
+	if test_error :index 4 :contains "radish" {
+		test_fail "error test matched nonsense";
+	}
+}
+
+/*
+ * Address test
+ */
+
+
+test "Address errors" {
+	if test_script_compile "errors/address.sieve" {
+		test_fail "compile should have failed.";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "9" {
+		test_fail "wrong number of errors reported";
+	}
+
+	if not test_error :index 1 :matches
+		"*unknown * ':nonsense' * address test*" {
+		test_fail "error 1 is invalid";
+	}
+
+	if not test_error :index 2 :matches
+		"*address test expects *string list * 1 (header list),* number * found*" {
+		test_fail "error 2 is invalid";
+	}
+
+	if not test_error :index 3 :matches
+		"*address test expects *string list * 2 (key list),* number * found*" {
+		test_fail "error 3 is invalid";
+	}
+
+	if not test_error :index 4 :matches
+		"*unexpected *':is' * address test*" {
+		test_fail "error 4 is invalid";
+	}
+
+	if not test_error :index 5 :matches
+		"*address test * 2 positional arg*, but 1*" {
+		test_fail "error 5 is invalid";
+	}
+
+	if not test_error :index 6 :matches
+		"*address test * 2 positional arg*, but 0*" {
+		test_fail "error 6 is invalid";
+	}
+
+	if not test_error :index 7 :matches
+		"*'frop' *not allowed *address test*" {
+		test_fail "error 7 is invalid";
+	}
+
+	if not test_error :index 8 :matches
+		"*'frop' *not allowed *address test*" {
+		test_fail "error 8 is invalid";
+	}
+
+	if test_error :index 23 :contains "radish" {
+		test_fail "error test matched nonsense";
+	}
+}
+
+/*
+ * If command
+ */
+
+test "If errors (FIXME: count only)" {
+	if test_script_compile "errors/if.sieve" {
+		test_fail "compile should have failed.";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "12" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+/*
+ * Require command
+ */
+
+test "Require errors (FIXME: count only)" {
+	if test_script_compile "errors/require.sieve" {
+		test_fail "compile should have failed.";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "15" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+/*
+ * Size test
+ */
+
+test "Size errors (FIXME: count only)" {
+	if test_script_compile "errors/size.sieve" {
+		test_fail "compile should have failed.";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "7" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+/*
+ * Envelope test
+ */
+
+test "Envelope errors (FIXME: count only)" {
+	if test_script_compile "errors/envelope.sieve" {
+		test_fail "compile should have failed.";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "3" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+/*
+ * Stop command
+ */
+
+test "Stop errors (FIXME: count only)" {
+	if test_script_compile "errors/stop.sieve" {
+		test_fail "compile should have failed.";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "9" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+/*
+ * Keep command
+ */
+
+test "Keep errors (FIXME: count only)" {
+	if test_script_compile "errors/keep.sieve" {
+		test_fail "compile should have failed.";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "3" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+/*
+ * COMPARATOR errors
+ */
+
+test "COMPARATOR errors (FIXME: count only)" {
+	if test_script_compile "errors/comparator.sieve" {
+		test_fail "compile should have failed.";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "6" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+/*
+ * ADDRESS-PART errors
+ */
+
+test "ADDRESS-PART errors (FIXME: count only)" {
+	if test_script_compile "errors/address-part.sieve" {
+		test_fail "compile should have failed.";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "3" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+/*
+ * MATCH-TYPE errors
+ */
+
+test "MATCH-TYPE errors (FIXME: count only)" {
+	if test_script_compile "errors/match-type.sieve" {
+		test_fail "compile should have failed.";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "2" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+/*
+ * Encoded-character errors
+ */
+
+test "Encoded-character errors (FIXME: count only)" {
+	if test_script_compile "errors/encoded-character.sieve" {
+		test_fail "compile should have failed.";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "3" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+/*
+ * Outgoing address errors
+ */
+
+test "Outgoing address errors (FIXME: count only)" {
+	if test_script_compile "errors/out-address.sieve" {
+		test_fail "compile should have failed.";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "16" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+/*
+ * Tagged argument errors
+ */
+
+test "Tagged argument errors (FIXME: count only)" {
+	if test_script_compile "errors/tag.sieve" {
+		test_fail "compile should have failed.";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "3" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+/*
+ * Typos
+ */
+
+test "Typos" {
+	if test_script_compile "errors/typos.sieve" {
+		test_fail "compile should have failed.";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "6" {
+		test_fail "wrong number of errors reported";
+	}
+
+	if not test_error :index 1 :matches
+		"missing semicolon * fileinto *" {
+		test_fail "error 1 is invalid";
+	}
+
+	if not test_error :index 2 :matches
+		"*fileinto command * no *tests* specified*" {
+		test_fail "error 2 is invalid";
+	}
+
+	if not test_error :index 3 :matches
+		"missing semicolon * fileinto *" {
+		test_fail "error 3 is invalid";
+	}
+
+	if not test_error :index 4 :matches
+		"*address test requires 2 * 0 * specified" {
+		test_fail "error 4 is invalid";
+	}
+
+	if not test_error :index 5 :matches
+		"missing colon *matches* tag * address test" {
+		test_fail "error 5 is invalid";
+	}
+}
+
+
+/*
+ * Unsupported language features
+ */
+
+test "Unsupported language features (FIXME: count only)" {
+	if test_script_compile "errors/unsupported.sieve" {
+		test_fail "compile should have failed.";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "3" {
+		test_fail "wrong number of errors reported";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/errors/address-part.sieve
@@ -0,0 +1,17 @@
+/*
+ * Address part errors
+ *
+ * Total errors: 2 (+1 = 3)
+ */
+
+# Duplicate address part (1)
+if address :all :comparator "i;octet" :domain "from" "STEPHAN" {
+
+	# Duplicate address part (2)
+	if address :domain :localpart :comparator "i;octet" "from" "friep.example.com" {
+		keep;
+	}
+
+	stop;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/errors/address.sieve
@@ -0,0 +1,71 @@
+require "comparator-i;ascii-numeric";
+
+/*
+ * Address test errors
+ *
+ * Total count: 8 (+1 = 9)
+ */
+
+/*
+ * Command structure
+ */
+
+# Invalid tag
+if address :nonsense :comparator "i;ascii-casemap" :localpart "From" "nico" {
+	discard;
+}
+
+# Invalid first argument
+if address :is :comparator "i;ascii-numeric" :localpart 45 "nico" {
+	discard;
+}
+
+# Invalid second argument
+if address :is :comparator "i;ascii-numeric" :localpart "From" 45 {
+	discard;
+}
+
+# Invalid second argument
+if address :comparator "i;ascii-numeric" :localpart "From" :is {
+	discard;
+}
+
+# Missing second argument
+if address :is :comparator "i;ascii-numeric" :localpart "From" {
+	discard;
+}
+
+# Missing arguments
+if address :is :comparator "i;ascii-numeric" :localpart {
+	discard;
+}
+
+# Not an error
+if address :localpart :is :comparator "i;ascii-casemap" "from" ["frop", "frop"] {
+	discard;
+}
+
+/*
+ * Specified headers must contain addresses
+ */
+
+# Invalid header
+if address :is "frop" "frml" {
+	keep;
+}
+
+# Not an error
+if address :is "reply-to" "frml" {
+	keep;
+}
+
+# Invalid header (#2)
+if address :is ["to", "frop"] "frml" {
+	keep;
+}
+
+# Not an error
+if address :is ["to", "reply-to"] "frml" {
+	keep;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/errors/comparator.sieve
@@ -0,0 +1,21 @@
+/*
+ * Address part errors
+ *
+ * Total errors: 5 (+1 = 6)
+ */
+
+# 1: No argument
+if address :comparator { }
+
+# 2: Number argument
+if address :comparator 1 "from" "frop" { }
+
+# 3: String list argument
+if address :comparator ["a", "b"] "from" "frop" { }
+
+# 4: Unknown tag
+if address :comparator :frop "from" "frop" { }
+
+# 5: Known tag
+if address :comparator :all "from" "frop" { }
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/errors/encoded-character.sieve
@@ -0,0 +1,23 @@
+/*
+ * Encoded-character errors
+ *
+ * Total errors: 2 (+1 = 3)
+ */
+
+require "encoded-character";
+require "fileinto";
+
+# Invalid unicode character (1)
+fileinto "INBOX.${unicode:200000}";
+
+# Not an error
+fileinto "INBOX.${unicode:200000";
+
+# Invalid unicode character (2)
+fileinto "INBOX.${Unicode:DF01}";
+
+# Not an error
+fileinto "INBOX.${Unicode:DF01";
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/errors/envelope.sieve
@@ -0,0 +1,23 @@
+/*
+ * Envelope test errors
+ *
+ * Total errors: 2 (+1 = 3)
+ */
+
+require "envelope";
+
+# Not an error
+if envelope :is "to" "frop@example.org" {
+}
+
+# Unknown envelope part (1)
+if envelope :is "frop" "frop@example.org" {
+}
+
+# Not an error
+if envelope :is ["to","from"] "frop@example.org" {
+}
+
+# Unknown envelope part (2)
+if envelope :is ["to","frop"] "frop@example.org" {
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/errors/header.sieve
@@ -0,0 +1,57 @@
+require "comparator-i;ascii-numeric";
+
+/*
+ * Compile errors for the header test
+ *
+ * Total errors: 9 (+1 validation failed msg = 10)
+ */
+
+# Unknown tagged argument
+if header :all :comparator "i;ascii-casemap" "From" "nico" {
+	keep;
+}
+
+# Wrong first argument
+if header :is :comparator "i;ascii-numeric" 45 "nico" {
+	keep;
+}
+
+# Wrong second argument
+if header :is :comparator "i;ascii-numeric" "From" 45 {
+	discard;
+}
+
+# Wrong second argument
+if header :is :comparator "i;ascii-numeric" "From" :tag {
+	stop;
+}
+
+# Missing second argument
+if header :is :comparator "i;ascii-numeric" "From" {
+	stop;
+}
+
+# Missing arguments
+if header :is :comparator "i;ascii-numeric" {
+	keep;
+}
+
+# Not an error
+if header :is :comparator "i;ascii-casemap" "frop" ["frop", "frop"] {
+	discard;
+}
+
+# Spurious sub-test
+if header "frop" "frop" true {
+	discard;
+}
+
+# Test used as command with block
+header "frop" "frop" {
+    discard;
+}
+
+# Test used as command
+header "frop" "frop";
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/errors/if.sieve
@@ -0,0 +1,78 @@
+/*
+ * If command errors
+ *
+ * Total errors: 11 (+1 = 12)
+ */
+
+# Spurious argument
+if "frop" true {}
+
+# Spurious argument
+elsif "frop" true {}
+
+# Spurious string list
+if [ "false", "false", "false" ] false {
+	stop;
+}
+
+# No block
+if true;
+
+# No test
+if {
+	keep;
+}
+
+# Spurious test list
+if ( false, false, true ) {
+	keep;
+}
+
+stop;
+
+# If-less else
+else {
+	keep;
+}
+
+# Not an error
+if true {
+	keep;
+}
+
+stop;
+
+# If-less if structure (should produce only one error)
+elsif true {
+	keep;
+}
+elsif true {
+	keep;
+}
+else {
+}
+
+# Elsif after else
+if true {
+	keep;
+} else {
+	stop;
+} elsif true {
+	stop;
+}
+
+# If used as test
+if if true {
+}
+
+# Else if instead of elsif
+
+if true {
+	stop;
+} else if false {
+	keep;
+}
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/errors/keep.sieve
@@ -0,0 +1,14 @@
+/*
+ * Keep errors
+ *
+ * Total erors: 2 (+1 = 3)
+ */
+
+# Spurious string argument
+keep "frop";
+
+# Spurious test
+keep true;
+
+# Not an error
+keep;
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/errors/lexer.sieve
@@ -0,0 +1,68 @@
+/*
+ * Lexer tests
+ *
+ * Total errors: 8 (+1 = 9)
+ */
+
+/*
+ * Number limits
+ */
+
+# 1: Number too large
+if size :under 18446744073709551617 {
+	stop;
+}
+
+# 2: Number too large
+if size :under 18446744073709551616 {
+	stop;
+}
+
+# 3: Number too large
+if size :over 180143985094819840k {
+	stop;
+}
+
+# 4: Number too large
+if size :over 1006622342342296M {
+	stop;
+}
+
+# 5: Number too large
+if size :over 34359738368G {
+	stop;
+}
+
+# 6: Number far too large
+if size :over 49834598293485814273947921734981723971293741923 {
+	stop;
+}
+
+# Not an error
+if size :under 18446744073709551615 {
+	stop;
+}
+
+# Not an error
+if size :under 18446744073709551614 {
+	stop;
+}
+
+# Not an error
+if size :under 800G {
+	stop;
+}
+
+/*
+ * Identifier limits
+ */
+
+# 7: Identifier too long
+if this_is_a_rediculously_long_test_name {
+	stop;
+}
+
+# 8: Identifier way too long
+if test :this_is_an_even_more_rediculously_long_tagged_argument_name {
+	stop;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/errors/match-type.sieve
@@ -0,0 +1,7 @@
+require "comparator-i;ascii-numeric";
+
+if header :contains :comparator "i;ascii-numeric" "from" "friep.example.com" {
+	keep;
+}
+
+keep;
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/errors/out-address.sieve
@@ -0,0 +1,33 @@
+require "vacation";
+
+# Error
+
+redirect "@wrong.example.com";
+redirect "error";
+redirect "error@";
+redirect "Stephan Bosch error@example.org";
+redirect "Stephan Bosch <error@example.org";
+redirect " more error @  example.com  ";
+redirect "@";
+redirect "<>";
+redirect "Error <";
+redirect "Error <stephan";
+redirect "Error <stephan@";
+redirect "stephan@example.org,tss@example.net";
+redirect "stephan@example.org,%&^&!!~";
+redirect "rüdiger@example.com";
+
+vacation :from "Error" "Ik ben er niet.";
+
+# Ok
+
+redirect "Ok Good <stephan@example.org>";
+redirect "ok@example.com";
+redirect " more  @  example.com  ";
+
+redirect ".japanese@example.com";
+redirect "japanese.@example.com";
+redirect "japanese...localpart@example.com";
+redirect "..japanese...localpart..@example.com";
+
+vacation :from "good@voorbeeld.nl.example.com" "Ik ben weg!";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/errors/parser.sieve
@@ -0,0 +1,78 @@
+/*
+ * Parser errors
+ *
+ * Total errors: 8 (+1 = 9)
+ */
+
+# Too many arguments (1)
+frop :this "is" "a" 2 :long "argument" "list" :and :it :should "fail" :during "parsing" :but "it" "should" "be"
+	"recoverable" "." :this "is" "a" 2 :long "argument" "list" :and :it :should "fail" :during "parsing" :but
+	"it" "should" "be" "recoverable" {
+	stop;
+}
+
+# Garbage argument (2)
+friep $$$;
+
+# Deep block nesting (1)
+if true { if true { if true { if true { if true { if true { if true { if true {
+	if true { if true { if true { if true { if true { if true { if true { if true {
+		if true { if true { if true { if true { if true { if true { if true { if true {
+			if true { if true {	if true { if true {	if true { if true { if true { if true {
+				if true { if true { if true { if true { if true { if true { if true { if true {
+					stop;
+				} } } } } } } }
+			} } } } } } } }
+		} } } } } } } }
+	} } } } } } } }
+} } } } } } } }
+
+# Deepest block and too deep test (list) nesting (1)
+if true { if true { if true { if true { if true { if true { if true { if true {
+	if true { if true { if true { if true { if true { if true { if true { if true {
+		if true { if true { if true { if true { if true { if true { if true { if true {
+			if true { if true {	if true { if true {	if true { if true {
+				if
+					anyof ( anyof ( anyof ( anyof ( anyof ( anyof ( anyof ( anyof (
+					anyof ( anyof ( anyof ( anyof ( anyof ( anyof ( anyof ( anyof (
+					anyof ( anyof ( anyof ( anyof ( anyof ( anyof ( anyof ( anyof (
+					anyof ( anyof ( anyof ( anyof ( anyof ( anyof ( anyof ( anyof (
+					anyof ( anyof ( anyof ( anyof ( anyof ( anyof ( anyof ( anyof (
+						true
+					))))))))
+					))))))))
+					))))))))
+					))))))))
+					))))))))
+				{
+					stop;
+				}
+			} } } } } }
+		} } } } } } } }
+	} } } } } } } }
+} } } } } } } }
+
+# Deepest block and too deep test nesting (1)
+if true { if true { if true { if true { if true { if true { if true { if true {
+	if true { if true { if true { if true { if true { if true { if true { if true {
+		if true { if true { if true { if true { if true { if true { if true { if true {
+			if true { if true {	if true { if true {	if true { if true {
+				if
+					not not not not not not not not
+					not not not not not not not not
+					not not not not not not not not
+					not not not not not not not not
+					not not not not not not not not false
+				{
+					stop;
+				}
+			} } } } } }
+		} } } } } } } }
+	} } } } } } } }
+} } } } } } } }
+
+
+# Garbage command; test wether previous errors were resolved (2)
+frop $$$$;
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/errors/require.sieve
@@ -0,0 +1,42 @@
+/*
+ * Require errors
+ *
+ * Total errors: 11 (+1 = 12)
+ */
+
+# Not an error
+require "fileinto";
+
+# Missing argument
+require;
+
+# Too many arguments
+require "fileinto" "vacation";
+
+# Invalid argument
+require 45;
+
+# Invalid extensions (3 errors)
+require ["_frop", "_friep", "_frml"];
+
+# Core commands required
+require ["redirect", "keep", "discard"];
+
+# Invalid arguments
+require "dovecot.test" true;
+
+# Invalid extension
+require "_frop";
+
+# Spurious command block
+require "fileinto" {
+  keep;
+}
+
+# Nested require
+if true {
+  require "relional";
+}
+
+# Require after other command than require
+require "copy";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/errors/size.sieve
@@ -0,0 +1,47 @@
+/*
+ * Size test errors
+ *
+ * Total errors: 6 (+1 = 7)
+ */
+
+# Used as command (1)
+size :under 23;
+
+# Missing argument (2)
+if size {
+}
+
+# Missing :over/:under (3)
+if size 45 {
+	discard;
+}
+
+# No error
+if size :over 34K {
+	stop;
+}
+
+# No error
+if size :under 34M {
+	stop;
+}
+
+# Conflicting tags (4)
+if size :under :over 34 {
+	keep;
+}
+
+# Duplicate tags (5)
+if size :over :over 45M {
+	stop;
+}
+
+# Wrong argument order (6)
+if size 34M :over {
+	stop;
+}
+
+# No error; but worthy of a warning
+if size :under 0 {
+	stop;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/errors/stop.sieve
@@ -0,0 +1,33 @@
+/*
+ * Stop command errors
+ *
+ * Total errors: 7 (+1 = 8)
+ */
+
+# Spurious string argument
+stop "frop";
+
+# Spurious number argument
+stop 13;
+
+# Spurious string list argument
+stop [ "frop", "frop" ];
+
+# Spurious test
+stop true;
+
+# Spurious test list
+stop ( true, false );
+
+# Spurious command block
+stop {
+  keep;
+}
+
+# Spurious argument and test
+stop "frop" true {
+  stop;
+}
+
+# Not an error
+stop;
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/errors/tag.sieve
@@ -0,0 +1,16 @@
+/*
+ * Tag errors
+ *
+ * Total errors: 2 (+1 = 3)
+ */
+
+# Unknown tag (1)
+if envelope :isnot :comparator "i;ascii-casemap" :localpart "From" "nico" {
+	discard;
+}
+
+# Spurious tag (1)
+if true :comparator "i;ascii-numeric" {
+  	keep;
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/errors/typos.sieve
@@ -0,0 +1,29 @@
+/*
+ * This test is primarily meant to check the compiler's handling of typos
+ * at various locations.
+ */
+
+require "fileinto";
+
+/*
+ * Missing semicolon
+ */
+
+fileinto "frop"
+keep;
+
+/* Other situations */
+
+fileinto "frup"
+true;
+
+fileinto "friep"
+snot;
+
+/*
+ * Forgot tag colon
+ */
+
+if address matches "from" "*frop*" {
+	stop;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/errors/unsupported.sieve
@@ -0,0 +1,30 @@
+/*
+ * Handling of unsupported language features.
+ *
+ *   Total errors: 3 (+1 = 4)
+ */
+
+require "variables";
+require "include";
+require "regex";
+
+/*
+ * Unsupported use of variables
+ */
+
+/* Comparator argument */
+
+set "comp" "i;ascii-numeric";
+
+if address :comparator "${comp}" "from" "stephan@example.org" {
+	stop;
+}
+
+/* Included script */
+
+set "script" "blacklist";
+
+include "${blacklist}";
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/recover.svtest
@@ -0,0 +1,50 @@
+require "vnd.dovecot.testsuite";
+
+require "relational";
+require "comparator-i;ascii-numeric";
+
+/*
+ * Test parser's recover capability
+ */
+
+/*
+ * Commands
+ */
+
+/* Missing semicolon */
+
+test "Missing semicolons" {
+	if test_script_compile "recover/commands-semicolon.sieve" {
+		test_fail "compile should have failed.";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "3" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+/* End of block recovery*/
+
+test "Missing semicolon at end of block" {
+	if test_script_compile "recover/commands-endblock.sieve" {
+		test_fail "compile should have failed.";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "4" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+/*
+ * Tests
+ */
+
+test "Spurious comma at end of test list" {
+	if test_script_compile "recover/tests-endcomma.sieve" {
+		test_fail "compile should have failed.";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "3" {
+		test_fail "wrong number of errors reported";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/recover/commands-endblock.sieve
@@ -0,0 +1,27 @@
+if true {
+	if true {
+		# Missing semicolon
+		keep
+	}
+}
+
+if true {
+	# Erroneous syntax
+	keep,
+	keep
+}
+
+if true {
+	if anyof(true,true,false) {
+		keep;
+	}
+}
+
+if true {
+	if anyof(true,true,false) {
+		keep;
+		# Missing semicolon
+		discard
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/recover/commands-semicolon.sieve
@@ -0,0 +1,16 @@
+
+keep;
+
+discard;
+
+# Missing semicolon
+keep
+
+redirect "frop@nl.example.com";
+
+discard;
+
+# Missing semicolon
+keep
+
+redirect "frml@nl.example.com";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/recover/tests-endcomma.sieve
@@ -0,0 +1,17 @@
+if true {
+	if true {
+		# Spurious comma
+		if anyof(true,true,true,) {
+		}
+	}
+}
+
+if true {
+	if anyof(true,true) {
+		# Spurious comma
+		if anyof(true,true,true,) {
+			if anyof(true,true,true) {
+			}
+		}
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/redirect.sieve
@@ -0,0 +1,23 @@
+# Test various white space occurrences
+redirect "stephan@example.org";
+redirect " stephan@example.org";
+redirect "stephan @example.org";
+redirect "stephan@ example.org";
+redirect "stephan@example.org ";
+redirect " stephan @ example.org ";
+redirect "Stephan Bosch<stephan@example.org>";
+redirect " Stephan Bosch<stephan@example.org>";
+redirect "Stephan Bosch <stephan@example.org>";
+redirect "Stephan Bosch< stephan@example.org>";
+redirect "Stephan Bosch<stephan @example.org>";
+redirect "Stephan Bosch<stephan@ example.org>";
+redirect "Stephan Bosch<stephan@example.org >";
+redirect "Stephan Bosch<stephan@example.org> ";
+redirect "  Stephan Bosch  <  stephan  @  example.org  > ";
+
+# Test address syntax
+redirect "\"Stephan Bosch\"@example.org";
+redirect "Stephan.Bosch@eXamPle.oRg";
+redirect "Stephan.Bosch@example.org";
+redirect "Stephan Bosch <stephan@example.org>";
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/trivial.sieve
@@ -0,0 +1,17 @@
+# Commands must be case-insensitive
+keep;
+Keep;
+KEEP;
+discard;
+DisCaRD;
+
+# Tags must be case-insensitive
+if size :UNDER 34 {
+}
+
+if header :Is "from" "tukker@example.com" {
+}
+
+# Numbers must be case-insensitive
+if anyof( size :UNDER 34m, size :oVeR 50M ) {
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/warnings.svtest
@@ -0,0 +1,8 @@
+require "vnd.dovecot.testsuite";
+
+test "EOF Warnings" {
+	if not test_script_compile "warnings/eof.sieve" {
+		test_fail "compile should have succeeded.";
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/warnings/eof.sieve
@@ -0,0 +1,2 @@
+keep;
+# Final comment without newline
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/compile/warnings/invalid-headers.sieve
@@ -0,0 +1,14 @@
+# Header test
+if header "from:" "frop@example.org" {
+	stop;
+}
+
+# Address test
+if address "from:" "frop@example.org" {
+	stop;
+}
+
+# Exists test
+if exists "from:" {
+	stop;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/control-if.svtest
@@ -0,0 +1,292 @@
+require "vnd.dovecot.testsuite";
+
+/*
+ * ## RFC 5228, Section 3.1. Control if (page 21) ##
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: test@dovecot.example.net
+Cc: friep@example.com
+Subject: Test
+
+Test!
+.
+;
+
+/*
+ * Basic functionality
+ */
+
+/* "The semantics are similar to those of any of the many other
+ *  programming languages these control structures appear in.  When the
+ *  interpreter sees an "if", it evaluates the test associated with it.
+ *  If the test is true, it executes the block associated with it.
+ *
+ *  If the test of the "if" is false, it evaluates the test of the first
+ *  "elsif" (if any).  If the test of "elsif" is true, it runs the
+ *  elsif's block.  An elsif may be followed by an elsif, in which case,
+ *  the interpreter repeats this process until it runs out of elsifs.
+ *
+ *  When the interpreter runs out of elsifs, there may be an "else" case.
+ *  If there is, and none of the if or elsif tests were true, the
+ *  interpreter runs the else's block.
+ *
+ *  This provides a way of performing exactly one of the blocks in the
+ *  chain.
+ * "
+ */
+
+/*
+ * TEST: Basic functionality: if true/false
+ */
+
+test "Basic functionality: if true/false" {
+	/* Static */
+	if true {
+		/* Correct */
+	} else {
+		test_fail "executed wrong alternative for static true";
+	}
+
+	if false {
+		test_fail "executed wrong alternative for static false";
+	} else {
+		/* Correct */
+	}
+
+	/* Dynamic */
+	if exists "to" {
+		/* Correct */
+	} else {
+		test_fail "executed wrong alternative for dynamic true";
+	}
+
+	if exists "flierp" {
+		test_fail "executed wrong alternative for dynamic false";
+	} else {
+		/* Correct */
+	}
+}
+
+/*
+ * TEST: Basic functionality: if not true/false
+ */
+
+test "Basic functionality: if not true/false" {
+	/* Static */
+	if not true {
+		test_fail "executed wrong alternative for static not true";
+	} else {
+		/* Correct */
+	}
+
+	if not false {
+		/* Correct */
+	} else {
+		test_fail "executed wrong alternative for static not false";
+	}
+
+	/* Dynamic */
+	if not exists "to" {
+		test_fail "executed wrong alternative for dynamic not true";
+	} else {
+		/* Correct */
+	}
+
+	if not exists "flierp" {
+		/* Correct */
+	} else {
+		test_fail "executed wrong alternative for dynamic not false";
+	}
+}
+
+/*
+ * TEST: Basic functionality: elseif true/false
+ */
+
+test "Basic functionality: elseif true/false" {
+	/* Static */
+	if true {
+		/* Correct */
+	} elsif true {
+		test_fail "executed wrong alternative for static true-true (elsif)";
+	} else {
+		test_fail "executed wrong alternative for static true-true (else)";
+	}
+
+	if true {
+		/* Correct */
+	} elsif false {
+		test_fail "executed wrong alternative for static true-false (elsif)";
+	} else {
+		test_fail "executed wrong alternative for static true-false (else)";
+	}
+
+	if false {
+		test_fail "executed wrong alternative for static false-true (if)";
+	} elsif true {
+		/* Correct */
+	} else {
+		test_fail "executed wrong alternative for static false-false (else)";
+	}
+
+	if false {
+		test_fail "executed wrong alternative for static false-false (if)";
+	} elsif false {
+		test_fail "executed wrong alternative for static false-false (elsif)";
+	} else {
+		/* Correct */
+	}
+
+	/* Dynamic */
+	if address :is "from" "stephan@example.org" {
+		/* Correct */
+	} elsif address :contains "from" "stephan" {
+		test_fail "executed wrong alternative for dynamic true-true (elsif)";
+	} else {
+		test_fail "executed wrong alternative for dynamic true-true (else)";
+	}
+
+	if address :is "from" "stephan@example.org" {
+		/* Correct */
+	} elsif address :is "from" "frop@example.com" {
+		test_fail "executed wrong alternative for dynamic true-false (elsif)";
+	} else {
+		test_fail "executed wrong alternative for dynamic true-false (else)";
+	}
+
+	if address :is "from" "tss@example.net" {
+		test_fail "executed wrong alternative for dynamic false-true (if)";
+	} elsif address :is "from" "stephan@example.org" {
+		/* Correct */
+	} else {
+		test_fail "executed wrong alternative for dynamic false-true(else)";
+	}
+
+	if address :is "from" "tss@example.net" {
+		test_fail "executed wrong alternative for dynamic false-false (if)";
+	} elsif address :is "to" "stephan@example.org" {
+		test_fail "executed wrong alternative for dynamic false-false (elsif)";
+	} else {
+		/* Correct */
+	}
+
+	/* Static/Dynamic */
+
+	if true {
+		/* Correct */
+	} elsif address :contains "from" "stephan" {
+		test_fail "executed wrong alternative for first-static true-true (elsif)";
+	} else {
+		test_fail "executed wrong alternative for first-static true-true (else)";
+	}
+
+	if address :is "from" "stephan@example.org" {
+		/* Correct */
+	} elsif true {
+		test_fail "executed wrong alternative for second-static true-true (elsif)";
+	} else {
+		test_fail "executed wrong alternative for second-static true-true (else)";
+	}
+
+	if true {
+		/* Correct */
+	} elsif address :is "from" "frop@example.com" {
+		test_fail "executed wrong alternative for first-static true-false (elsif)";
+	} else {
+		test_fail "executed wrong alternative for first-static true-false (else)";
+	}
+
+	if address :is "from" "stephan@example.org" {
+		/* Correct */
+	} elsif false {
+		test_fail "executed wrong alternative for second-static true-false (elsif)";
+	} else {
+		test_fail "executed wrong alternative for second-static true-false (else)";
+	}
+
+	if false {
+		test_fail "executed wrong alternative for first-static false-true (if)";
+	} elsif address :is "from" "stephan@example.org" {
+		/* Correct */
+	} else {
+		test_fail "executed wrong alternative for first-static false-true(else)";
+	}
+
+	if address :is "from" "tss@example.net" {
+		test_fail "executed wrong alternative for second-static false-true (if)";
+	} elsif true {
+		/* Correct */
+	} else {
+		test_fail "executed wrong alternative for second-static false-true(else)";
+	}
+
+	if false {
+		test_fail "executed wrong alternative for first-static false-false (if)";
+	} elsif address :is "to" "stephan@example.org" {
+		test_fail "executed wrong alternative for first-static false-false (elsif)";
+	} else {
+		/* Correct */
+	}
+
+	if address :is "from" "tss@example.net" {
+		test_fail "executed wrong alternative for second-static false-false (if)";
+	} elsif false {
+		test_fail "executed wrong alternative for second-static false-false (elsif)";
+	} else {
+		/* Correct */
+	}
+}
+
+/*
+ * TEST: Basic functionality: nesting
+ */
+
+test "Basic functionality: nesting" {
+	/* Static */
+	if true {
+		if true {
+			if false {
+				test_fail "chose wrong static outcome: true->true->false";
+			} else {
+				/* Correct */
+			}
+		} else {
+			test_fail "chose wrong static outcome: true->false";
+		}
+	} elsif true {
+		if false {
+			test_fail "chose wrong static outcome: false->true->false";
+		} elsif true {
+			test_fail "chose wrong static outcome: false->true->true";
+		}
+	} else {
+		test_fail "chose wrong static outcome: false->false";
+	}
+
+	/* Dynamic */
+
+	if exists "to" {
+		if exists "from" {
+			if exists "friep" {
+				test_fail "chose wrong dynamic outcome: true->true->false";
+			} else {
+				/* Correct */
+			}
+		} else {
+			test_fail "chose wrong dynamic outcome: true->false";
+		}
+	} elsif exists "cc" {
+		if exists "frop" {
+			test_fail "chose wrong dynamic outcome: false->true->false";
+		} elsif exists "from" {
+			test_fail "chose wrong dynamic outcome: false->true->true";
+		}
+	} else {
+		test_fail "chose wrong dynamic outcome: false->false";
+	}
+}
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/control-stop.svtest
@@ -0,0 +1,29 @@
+require "vnd.dovecot.testsuite";
+
+/*
+ * ## RFC 5228, Section 3.3. Control stop (page 22) ##
+ */
+
+/*
+ * TEST: End processing
+ */
+
+/* "The "stop" action ends all processing.
+ * "
+ */
+
+test "End processing" {
+	stop;
+
+	test_fail "continued after stop";
+}
+
+/*
+ * TEST: Implicit keep
+ */
+
+/* "If the implicit keep has not been cancelled, then it is taken.
+ * "
+ */
+
+/* FIXME */
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/deprecated/imapflags/errors.svtest
@@ -0,0 +1,24 @@
+require "vnd.dovecot.testsuite";
+
+require "comparator-i;ascii-numeric";
+require "relational";
+
+test "Deprecated imapflags extension used with imap4flags" {
+	if test_script_compile "errors/conflict.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "2" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+test "Deprecated imapflags extension used with imap4flags (ihave)" {
+	if test_script_compile "errors/conflict-ihave.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "3" {
+		test_fail "wrong number of errors reported";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/deprecated/imapflags/errors/conflict-ihave.sieve
@@ -0,0 +1,6 @@
+require "imap4flags";
+require "ihave";
+
+if ihave "imapflags" {
+	addflags "Frop";
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/deprecated/imapflags/errors/conflict.sieve
@@ -0,0 +1,4 @@
+require "imapflags";
+require "imap4flags";
+
+addflag "\\flagged";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/deprecated/imapflags/execute.svtest
@@ -0,0 +1,92 @@
+require "vnd.dovecot.testsuite";
+require "fileinto";
+require "imap4flags";
+require "relational";
+require "comparator-i;ascii-numeric";
+require "mailbox";
+
+test_set "message" text:
+From: Henry von Flockenstoffen <henry@example.com>
+To: Dieter von Ausburg <dieter@example.com>
+Subject: Test message.
+
+Test message.
+.
+;
+
+test "Mark / Unmark" {
+	if not test_script_compile "execute/mark.sieve" {
+		test_fail "script compile failed";
+	}
+
+	if not test_script_run {
+		test_fail "script execute failed";
+	}
+
+	if not test_result_execute {
+		test_fail "failed to execute first result";
+	}
+
+	test_result_reset;
+
+	test_message :folder "Marked" 0;
+
+	if not hasflag "\\flagged" {
+		test_fail "message not marked";
+	}
+
+	test_result_reset;
+
+	test_message :folder "Unmarked" 0;
+
+	if hasflag "\\flagged" {
+		test_fail "message not unmarked";
+	}
+}
+
+test_result_reset;
+test "Setflag / Addflag / Removeflag" {
+	if not test_script_compile "execute/flags.sieve" {
+		test_fail "script compile failed";
+	}
+
+	if not test_script_run {
+		test_fail "script execute failed";
+	}
+
+	if not test_result_execute {
+		test_fail "failed to execute first result";
+	}
+
+	test_result_reset;
+
+	test_message :folder "Set" 0;
+
+	if not hasflag "\\draft" {
+		test_fail "flag not set";
+	}
+
+	test_result_reset;
+
+	test_message :folder "Add" 0;
+
+	if not hasflag "\\draft" {
+		test_fail "flag not retained";
+	}
+
+	if not hasflag "\\flagged" {
+		test_fail "flag not added";
+	}
+
+	test_result_reset;
+
+	test_message :folder "Remove" 0;
+
+	if not hasflag "\\flagged" {
+		test_fail "flag not retained";
+	}
+
+	if hasflag "\\draft" {
+		test_fail "flag not removed";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/deprecated/imapflags/execute/flags.sieve
@@ -0,0 +1,12 @@
+require "imapflags";
+require "fileinto";
+require "mailbox";
+
+setflag "\\draft";
+fileinto :create "Set";
+
+addflag "\\flagged";
+fileinto :create "Add";
+
+removeflag "\\draft";
+fileinto :create "Remove";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/deprecated/imapflags/execute/mark.sieve
@@ -0,0 +1,11 @@
+require "imapflags";
+require "fileinto";
+require "mailbox";
+
+mark;
+
+fileinto :create "Marked";
+
+unmark;
+
+fileinto :create "Unmarked";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/deprecated/notify/basic.svtest
@@ -0,0 +1,59 @@
+require "vnd.dovecot.testsuite";
+require "notify";
+require "body";
+
+test "Execute" {
+	/* Test to catch runtime segfaults */
+	notify
+		:message "This is probably very important"
+		:low
+		:method "mailto"
+		:options ["stephan@example.com", "stephan@example.org"];
+
+	if not test_result_execute {
+		test_fail "Execute failed";
+	}
+}
+
+test_result_reset;
+
+test_set "message" text:
+To: user@example.com
+From: stephan@example.org
+Subject: Mail
+
+Test!
+.
+;
+
+test "Substitutions" {
+	notify
+		:message "$from$: $subject$"
+		:options "stephan@example.com";
+	if not test_result_execute {
+		test_fail "Execute failed";
+	}
+	test_message :smtp 0;
+	if not body :contains "stephan@example.org: Mail" {
+		test_fail "Substitution failed";
+	}
+}
+
+test_result_reset;
+
+test_set "message" text:
+To: user@example.com
+
+Test!
+.
+;
+
+test "Empty substitutions" {
+	notify
+		:message "$from$: $subject$"
+		:options "stephan@example.com";
+	if not test_result_execute {
+		test_fail "Execute failed";
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/deprecated/notify/denotify.svtest
@@ -0,0 +1,279 @@
+require "vnd.dovecot.testsuite";
+require "notify";
+require "envelope";
+
+/*
+ * Denotify all
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test "Denotify All" {
+	notify :options "timo@example.com";
+	notify :options "stephan@dovecot.example.net";
+	notify :options "postmaster@frop.example.org";
+	denotify;
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	if test_message :smtp 0 {
+		test_fail "no notifications should have been sent";
+	}
+}
+
+/*
+ * Denotify First
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test "Denotify ID First" {
+	/* #1 */
+	notify :options "timo@example.com" :id "aap";
+
+	/* #2 */
+	notify :options "stephan@dovecot.example.net" :id "noot";
+
+	/* #3 */
+	notify :options "postmaster@frop.example.org" :id "mies";
+
+	denotify :is "aap";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	if not test_message :smtp 0 {
+		test_fail "two notifications should have been sent (#2 missing)";
+	}
+
+	if not envelope "to" "stephan@dovecot.example.net" {
+		test_fail "message #2 unexpectedly missing from output";
+	}
+
+	if not test_message :smtp 1 {
+		test_fail "two notifications should have been sent (#3 missing)";
+	}
+
+	if not envelope "to" "postmaster@frop.example.org" {
+		test_fail "message #3 unexpectedly missing from output";
+	}
+
+	if test_message :smtp 2 {
+		test_fail "too many notifications sent";
+	}
+}
+
+/*
+ * Denotify Middle
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test "Denotify ID Middle" {
+	/* #1 */
+	notify :options "timo@example.com" :id "aap";
+
+	/* #2 */
+	notify :options "stephan@dovecot.example.net" :id "noot";
+
+	/* #3 */
+	notify :options "postmaster@frop.example.org" :id "mies";
+
+	denotify :is "noot";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	if not test_message :smtp 0 {
+		test_fail "two notifications should have been sent (#1 missing)";
+	}
+
+	if not envelope "to" "timo@example.com" {
+		test_fail "message #1 unexpectedly missing from output";
+	}
+
+	if not test_message :smtp 1 {
+		test_fail "two notifications should have been sent (#3 missing)";
+	}
+
+	if not envelope "to" "postmaster@frop.example.org" {
+		test_fail "message #3 unexpectedly missing from output";
+	}
+
+	if test_message :smtp 2 {
+		test_fail "too many notifications sent";
+	}
+}
+
+/*
+ * Denotify Last
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test "Denotify ID Last" {
+	/* #1 */
+	notify :options "timo@example.com" :id "aap";
+
+	/* #2 */
+	notify :options "stephan@dovecot.example.net" :id "noot";
+
+	/* #3 */
+	notify :options "postmaster@frop.example.org" :id "mies";
+
+	denotify :is "mies";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	if not test_message :smtp 0 {
+		test_fail "two notifications should have been sent (#1 missing)";
+	}
+
+	if not envelope "to" "timo@example.com" {
+		test_fail "message #1 unexpectedly missing from output";
+	}
+
+	if not test_message :smtp 1 {
+		test_fail "two notifications should have been sent (#2 missing)";
+	}
+
+	if not envelope "to" "stephan@dovecot.example.net" {
+		test_fail "message #2 unexpectedly missing from output";
+	}
+
+	if test_message :smtp 2 {
+		test_fail "too many notifications sent";
+	}
+}
+
+
+/*
+ * Denotify Matching
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test "Denotify Matching" {
+	/* #1 */
+	notify :options "timo@example.com" :id "frop";
+
+	/* #2 */
+	notify :options "stephan@dovecot.example.net" :id "noot";
+
+	/* #3 */
+	notify :options "postmaster@frop.example.org" :id "friep";
+
+	denotify :matches "fr*";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	if not test_message :smtp 0 {
+		test_fail "one notification should have been sent";
+	}
+
+	if not envelope "to" "stephan@dovecot.example.net" {
+		test_fail "message #2 unexpectedly missing from output";
+	}
+
+	if test_message :smtp 1 {
+		test_fail "too many notifications sent";
+	}
+}
+
+
+/*
+ * Denotify Matching
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test "Denotify Matching Importance" {
+	/* #1 */
+	notify :options "timo@example.com" :id "frop" :low;
+
+	/* #2 */
+	notify :options "stephan@dovecot.example.net" :id "frml" :high;
+
+	/* #3 */
+	notify :options "postmaster@frop.example.org" :id "friep" :low;
+
+	denotify :matches "fr*" :low;
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	if not test_message :smtp 0 {
+		test_fail "one notification should have been sent";
+	}
+
+	if not envelope "to" "stephan@dovecot.example.net" {
+		test_fail "message #2 unexpectedly missing from output";
+	}
+
+	if test_message :smtp 1 {
+		test_fail "too many notifications sent";
+	}
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/deprecated/notify/errors.svtest
@@ -0,0 +1,33 @@
+require "vnd.dovecot.testsuite";
+require "comparator-i;ascii-numeric";
+require "relational";
+
+test "Invalid :options argument (FIXME: count only)" {
+	if test_script_compile "errors/options.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "3" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+test "Deprecated notify extension used with enotify" {
+	if test_script_compile "errors/conflict.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "3" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+test "Deprecated notify extension used with enotify (ihave)" {
+	if test_script_compile "errors/conflict-ihave.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "3" {
+		test_fail "wrong number of errors reported";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/deprecated/notify/errors/conflict-ihave.sieve
@@ -0,0 +1,8 @@
+require "enotify";
+require "ihave";
+
+# 1: Conflict
+if ihave "notify" {
+	# 2: Syntax wrong for enotify (and not skipped in compile)
+	notify :options "frop@frop.example.org";
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/deprecated/notify/errors/conflict.sieve
@@ -0,0 +1,4 @@
+require "enotify";
+require "notify";
+
+notify :options "frop@frop.example.org";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/deprecated/notify/errors/options.sieve
@@ -0,0 +1,11 @@
+require "notify";
+
+# 1: empty option
+notify :options "";
+
+# 2: invalid address syntax
+notify :options "frop#frop.example.org";
+
+# Valid
+notify :options "frop@frop.example.org";
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/deprecated/notify/execute.svtest
@@ -0,0 +1,25 @@
+require "vnd.dovecot.testsuite";
+require "relational";
+
+
+/*
+ * Execution testing (currently just meant to trigger any segfaults)
+ */
+
+test "Duplicate recipients" {
+	if not test_script_compile "execute/duplicates.sieve" {
+		test_fail "script compile failed";
+	}
+
+	if not test_script_run {
+		test_fail "script execute failed";
+	}
+
+	if test_result_action :count "ne" "2" {
+		test_fail "second notify action was discarded entirely";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/deprecated/notify/execute/duplicates.sieve
@@ -0,0 +1,4 @@
+require "notify";
+
+notify :message "Incoming stupidity." :options ["stephan@example.org", "stephan@friep.example.com", "idiot@example.org"];
+notify :message "There it is." :options ["tss@example.net", "stephan@example.org", "idiot@example.org", "nico@frop.example.org", "stephan@friep.example.com"];
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/deprecated/notify/mailto.svtest
@@ -0,0 +1,317 @@
+require "vnd.dovecot.testsuite";
+
+require "notify";
+require "body";
+require "relational";
+require "comparator-i;ascii-numeric";
+
+/*
+ * Simple test
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test "Simple" {
+	notify :method "mailto" :options "stephan@example.org";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not header :matches "Auto-Submitted" "auto-generated*" {
+		test_fail "auto-submitted header set inappropriately";
+	}
+
+	if not exists "X-Sieve" {
+		test_fail "x-sieve header missing from outgoing message";
+	}
+}
+
+/*
+ * Multiple recipients
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test "Multiple recipients" {
+	notify :options ["timo@example.com","stephan@dovecot.example.net","postmaster@frop.example.org"];
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not address :is "to" "timo@example.com" {
+		test_fail "first To address missing";
+	}
+
+	test_message :smtp 1;
+
+	if not address :is "to" "stephan@dovecot.example.net" {
+		test_fail "second To address missing";
+	}
+
+	if not header :matches "Auto-Submitted" "auto-generated*" {
+		test_fail "auto-submitted header not found for second message";
+	}
+
+	test_message :smtp 2;
+
+	if not address :is "to" "postmaster@frop.example.org" {
+		test_fail "third To address missing";
+	}
+
+	if not header :matches "Auto-Submitted" "auto-generated*" {
+		test_fail "auto-submitted header not found for third message";
+	}
+
+	if not address :count "eq" :comparator "i;ascii-numeric" "to" "3" {
+		test_fail "wrong number of recipients in To header";
+	}
+
+	if not address :count "eq" :comparator "i;ascii-numeric" "cc" "0" {
+		test_fail "too many recipients in Cc header";
+	}
+}
+
+/*
+ * Duplicate recipients
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test "Duplicate recipients" {
+	notify :options ["timo@example.com", "stephan@dovecot.example.net", "stephan@dovecot.example.net"];
+	notify :options ["timo@example.com", "stephan@example.org"];
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 2;
+
+	if address "To" "stephan@dovecot.example.net" {
+		test_fail "duplicate recipient not removed from first message";
+	}
+
+	if address "To" "timo@example.com" {
+		test_fail "duplicate recipient not removed from second message";
+	}
+}
+
+/*
+ * Notifying on automated messages
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Auto-submitted: auto-notify
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test "Notifying on automated messages" {
+	notify :options "stephan@example.org";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	if test_message :smtp 0 {
+		test_fail "notified of auto-submitted message";
+	}
+}
+
+test_result_reset;
+
+test_set "message" text:
+To: nico@frop.example.org
+From: stephan@example.org
+Subject: Test
+
+Test. Test
+Frop!
+.
+;
+
+test "Body; Singular Message" {
+	notify :low :id "frop" :options "stephan@example.org"
+    	:message text:
+Received interesting message:
+
+$text$
+
+You have been notified.
+.
+;
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not body :raw :contains "Received interesting message" {
+		test_fail "notification has no heading";
+	}
+
+	if not body :raw :contains "You have been notified" {
+		test_fail "notification has no footer";
+	}
+
+	if not allof(
+		body :raw :contains "Test. Test",
+		body :raw :contains "Frop" ) {
+		test_fail "notification has no original message";
+	}
+}
+
+test_result_reset;
+
+test_set "message" text:
+To: nico@frop.example.org
+From: stephan@example.org
+Subject: Test
+
+Test. Test
+Frop!
+.
+;
+
+test "Body; $text[maxsize]$" {
+	notify :low :id "frop" :options "sirius@example.org"
+    	:message text:
+Received interesting message:
+
+$text[5]$
+
+You have been notified.
+.
+;
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not body :raw :contains "Received interesting message" {
+		test_fail "notification has no heading";
+	}
+
+	if not body :raw :contains "You have been notified" {
+		test_fail "notification has no footer";
+	}
+
+	if anyof(
+		body :raw :contains "Test. Test",
+		body :raw :contains "Frop" ) {
+		test_fail "original message in notification is not truncated";
+	}
+
+	if not body :raw :contains "Test." {
+		test_fail "notification does not contain the required message";
+	}
+}
+
+test_result_reset;
+
+test_set "message" text:
+From: Whomever <whoever@example.com>
+To: Someone <someone@example.com>
+Date: Sat, 10 Oct 2009 00:30:04 +0200
+Subject: whatever
+Content-Type: multipart/mixed; boundary=outer
+
+This is a multi-part message in MIME format.
+
+--outer
+Content-Type: multipart/alternative; boundary=inner
+
+This is a nested multi-part message in MIME format.
+
+--inner
+Content-Type: application/sieve; charset="us-ascii"
+
+keep;
+
+--inner
+Content-Type: text/plain; charset="us-ascii"
+
+Friep!
+
+--inner--
+
+This is the end of the inner MIME multipart.
+
+--outer
+Content-Type: message/rfc822
+
+From: Someone Else
+Subject: hello request
+
+Please say Hello
+
+--outer--
+
+This is the end of the outer MIME multipart.
+.
+;
+
+test "Body; Multipart Message" {
+	notify :low :id "frop" :options "stephan@example.org"
+    	:message text:
+Received interesting message:
+
+$text$
+
+You have been notified.
+.
+;
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not body :raw :contains "Friep!" {
+		test_fail "notification has incorrect content";
+	}
+}
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/execute/actions.svtest
@@ -0,0 +1,80 @@
+require "vnd.dovecot.testsuite";
+require "relational";
+require "comparator-i;ascii-numeric";
+
+test_set "message" text:
+To: nico@frop.example.org
+From: stephan@example.org
+Subject: Test
+
+Test.
+.
+;
+
+test_mailbox_create "INBOX.VB";
+test_mailbox_create "INBOX.backup";
+
+test "Fileinto" {
+	if not test_script_compile "actions/fileinto.sieve" {
+		test_fail "script compile failed";
+	}
+
+	if not test_script_run {
+		test_fail "script run failed";
+	}
+
+	if not test_result_action :count "eq" :comparator "i;ascii-numeric" "3" {
+		test_fail "wrong number of actions in result";
+	}
+
+	if not test_result_action :index 1 "store" {
+		test_fail "first action is not 'store'";
+	}
+
+	if not test_result_action :index 2 "store" {
+		test_fail "second action is not 'store'";
+	}
+
+	if not test_result_action :index 3 "keep" {
+		test_fail "third action is not 'keep'";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed";
+	}
+}
+
+test "Redirect" {
+	if not test_script_compile "actions/redirect.sieve" {
+		test_fail "compile failed";
+	}
+
+	if not test_script_run {
+		test_fail "execute failed";
+	}
+
+	if not test_result_action :count "eq" :comparator "i;ascii-numeric" "4" {
+		test_fail "wrong number of actions in result";
+	}
+
+	if not test_result_action :index 1 "redirect" {
+		test_fail "first action is not 'redirect'";
+	}
+
+	if not test_result_action :index 2 "keep" {
+		test_fail "second action is not 'keep'";
+	}
+
+	if not test_result_action :index 3 "redirect" {
+		test_fail "third action is not 'redirect'";
+	}
+
+	if not test_result_action :index 4 "redirect" {
+		test_fail "fourth action is not 'redirect'";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed";
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/execute/actions/fileinto.sieve
@@ -0,0 +1,17 @@
+require "fileinto";
+
+/* Three store actions */
+
+if address :contains "to" "frop.example" {
+	/* #1 */
+	fileinto "INBOX.VB";
+}
+
+/* #2 */
+fileinto "INBOX.backup";
+
+/* #3 */
+keep;
+
+/* Duplicate of keep */
+fileinto "INBOX";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/execute/actions/redirect.sieve
@@ -0,0 +1,17 @@
+if address :contains "to" "frop.example" {
+	/* #1 */
+	redirect "stephan@example.com";
+
+	/* #2 */
+	keep;
+}
+
+/* #3 */
+redirect "stephan@example.org";
+
+/* #4 */
+redirect "nico@example.nl";
+
+/* Duplicates */
+redirect "Stephan Bosch <stephan@example.com>";
+keep;
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/execute/address-normalize.svtest
@@ -0,0 +1,46 @@
+require "vnd.dovecot.testsuite";
+require "envelope";
+
+test_set "message" text:
+From: tss@example.net
+To: stephan@example.org
+Subject: Frop!
+
+Frop!
+.
+;
+
+test_set "envelope.from" "timo@example.net";
+test_set "envelope.to" "\"sirius\"@example.org";
+
+/*
+ * Mail address normalization - redirect
+ */
+
+test "Mail address normalization - redirect" {
+	redirect "\"S[r]us\"@example.net";
+	redirect "\"Sirius\"@example.net";
+	redirect "\"Stephan Bosch\" <\"S.Bosch\"@example.net>";
+
+	if not test_result_execute {
+		test_fail "failed to execute redirect";
+	}
+
+	test_message :smtp 0;
+
+	if not envelope :is "to" "\"S[r]us\"@example.net" {
+		test_fail "envelope recipient incorrect";
+	}
+
+	test_message :smtp 1;
+
+	if not envelope :is "to" "Sirius@example.net" {
+		test_fail "envelope recipient incorrect";
+	}
+
+	test_message :smtp 2;
+
+	if not envelope :is "to" "S.Bosch@example.net" {
+		test_fail "envelope recipient incorrect";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/execute/errors.svtest
@@ -0,0 +1,135 @@
+require "vnd.dovecot.testsuite";
+
+require "relational";
+require "comparator-i;ascii-numeric";
+require "fileinto";
+
+test "Action conflicts: reject <-> fileinto" {
+	if not test_script_compile "errors/conflict-reject-fileinto.sieve" {
+		test_fail "compile failed";
+	}
+
+	if test_script_run {
+		test_fail "execution should have failed";
+	}
+
+	if test_error :count "gt" :comparator "i;ascii-numeric" "1" {
+		test_fail "too many runtime errors reported";
+	}
+}
+
+test "Action conflicts: reject <-> keep" {
+	if not test_script_compile "errors/conflict-reject-keep.sieve" {
+		test_fail "compile failed";
+	}
+
+	if test_script_run {
+		test_fail "execution should have failed";
+	}
+
+	if test_error :count "gt" :comparator "i;ascii-numeric" "1" {
+		test_fail "too many runtime errors reported";
+	}
+}
+
+test "Action conflicts: reject <-> redirect" {
+	if not test_script_compile "errors/conflict-reject-redirect.sieve" {
+		test_fail "compile failed";
+	}
+
+	if test_script_run {
+		test_fail "execution should have failed";
+	}
+
+	if test_error :count "gt" :comparator "i;ascii-numeric" "1" {
+		test_fail "too many runtime errors reported";
+	}
+}
+
+test "Action limit" {
+	if not test_script_compile "errors/actions-limit.sieve" {
+		test_fail "compile failed";
+	}
+
+	if test_script_run {
+		test_fail "execution should have failed";
+	}
+
+	if test_error :count "gt" :comparator "i;ascii-numeric" "1" {
+		test_fail "too many runtime errors reported";
+	}
+
+	if not test_error :index 1 :contains "total number of actions exceeds policy limit"{
+		test_fail "unexpected error reported";
+	}
+}
+
+test "Redirect limit" {
+	if not test_script_compile "errors/redirect-limit.sieve" {
+		test_fail "compile failed";
+	}
+
+	if test_script_run {
+		test_fail "execution should have failed";
+	}
+
+	if test_error :count "gt" :comparator "i;ascii-numeric" "1" {
+		test_fail "too many runtime errors reported";
+	}
+
+	if not test_error :index 1 :contains "number of redirect actions exceeds policy limit"{
+		test_fail "unexpected error reported";
+	}
+}
+
+test "Fileinto missing folder" {
+	if not test_script_compile "errors/fileinto.sieve" {
+		test_fail "compile failed";
+	}
+
+	test_mailbox_create "INBOX";
+
+	if not test_script_run {
+		test_fail "execution failed";
+	}
+
+	if test_result_execute {
+		test_fail "execution of result should have failed";
+	}
+
+	if test_error :count "gt" :comparator "i;ascii-numeric" "1" {
+		test_fail "too many runtime errors reported";
+	}
+
+	if not allof (
+		test_error :index 1 :contains "failed to store into mailbox",
+		test_error :index 1 :contains "exist",
+		test_error :index 1 :contains "FROP") {
+		test_fail "unexpected error reported";
+	}
+}
+
+test "Fileinto invalid folder name" {
+	if not test_script_compile "errors/fileinto-invalid-name.sieve" {
+		test_fail "compile failed";
+	}
+
+	if not test_script_run {
+		test_fail "execution failed";
+	}
+
+	if test_result_execute {
+		test_fail "execution of result should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "1" {
+		test_fail "wrong number of runtime errors reported";
+	}
+
+	if not allof (
+		test_error :index 1 :contains "failed to store into mailbox",
+		test_error :index 1 :contains "name") {
+		test_fail "unexpected error reported";
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/execute/errors/action-duplicates.sieve
@@ -0,0 +1,4 @@
+require "reject";
+
+reject "Message is not appreciated.";
+reject "No, really, it is not appreciated.";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/execute/errors/actions-limit.sieve
@@ -0,0 +1,35 @@
+require "fileinto";
+
+fileinto "box1";
+fileinto "box2";
+fileinto "box3";
+fileinto "box4";
+fileinto "box5";
+fileinto "box6";
+fileinto "box7";
+fileinto "box8";
+fileinto "box9";
+fileinto "box10";
+fileinto "box11";
+fileinto "box12";
+fileinto "box13";
+fileinto "box14";
+fileinto "box15";
+fileinto "box16";
+fileinto "box17";
+fileinto "box18";
+fileinto "box19";
+fileinto "box20";
+fileinto "box21";
+fileinto "box22";
+fileinto "box23";
+fileinto "box24";
+fileinto "box25";
+fileinto "box26";
+fileinto "box27";
+fileinto "box28";
+redirect "address1@example.com";
+redirect "address2@example.com";
+redirect "address3@example.com";
+redirect "address4@example.com";
+keep;
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/execute/errors/conflict-reject-fileinto.sieve
@@ -0,0 +1,5 @@
+require "reject";
+require "fileinto";
+
+reject "No nonsense in my mailbox.";
+fileinto "Spam";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/execute/errors/conflict-reject-keep.sieve
@@ -0,0 +1,4 @@
+require "reject";
+
+reject "I am not interested in your nonsense.";
+keep;
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/execute/errors/conflict-reject-redirect.sieve
@@ -0,0 +1,4 @@
+require "reject";
+
+reject "I am not interested in your nonsense.";
+redirect "frop@example.com";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/execute/errors/fileinto-invalid-name.sieve
@@ -0,0 +1,5 @@
+require "fileinto";
+require "mailbox";
+
+fileinto :create "foo//somedomain/org";
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/execute/errors/fileinto.sieve
@@ -0,0 +1,3 @@
+require "fileinto";
+
+fileinto "FROP";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/execute/errors/redirect-limit.sieve
@@ -0,0 +1,5 @@
+redirect "address1@example.com";
+redirect "address2@example.com";
+redirect "address3@example.com";
+redirect "address4@example.com";
+redirect "address5@example.com";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/execute/examples.svtest
@@ -0,0 +1,115 @@
+require "vnd.dovecot.testsuite";
+
+/* Compile and execute all example scripts to trigger
+ * any Segfaults. No message is set and no results are checked.
+ */
+
+test "Elvey example" {
+	if not test_script_compile "../../examples/elvey.sieve" {
+		test_fail "could not compile";
+	}
+
+	test_binary_save "elvey";
+	test_binary_load "elvey";
+
+	if not test_script_run { }
+}
+
+test "M. Johnson example" {
+	if not test_script_compile "../../examples/mjohnson.sieve" {
+		test_fail "could not compile";
+	}
+
+	test_binary_save "mjohnson";
+	test_binary_load "mjohnson";
+
+	if not test_script_run { }
+}
+
+test "RFC 3028 example" {
+	if not test_script_compile "../../examples/rfc3028.sieve" {
+		test_fail "could not compile";
+	}
+
+	test_binary_save "rfc3028";
+	test_binary_load "rfc3028";
+
+	if not test_script_run { }
+}
+
+test "Sieve examples" {
+	if not test_script_compile "../../examples/sieve_examples.sieve" {
+		test_fail "could not compile";
+	}
+
+	test_binary_save "sieve_examples";
+	test_binary_load "sieve_examples";
+
+	if not test_script_run { }
+}
+
+test "Vivil example" {
+	if not test_script_compile "../../examples/vivil.sieve" {
+		test_fail "could not compile";
+	}
+
+	test_binary_save "vivil";
+	test_binary_load "vivil";
+
+	if not test_script_run { }
+}
+
+test "Jerry example" {
+	if not test_script_compile "../../examples/jerry.sieve" {
+		test_fail "could not compile";
+	}
+
+	test_binary_save "jerry";
+	test_binary_load "jerry";
+
+	if not test_script_run { }
+}
+
+test "M. Klose example" {
+	if not test_script_compile "../../examples/mklose.sieve" {
+		test_fail "could not compile";
+	}
+
+	test_binary_save "mklose";
+	test_binary_load "mklose";
+
+	if not test_script_run { }
+}
+
+test "Sanjay example" {
+	if not test_script_compile "../../examples/sanjay.sieve" {
+		test_fail "could not compile";
+	}
+
+	test_binary_save "sanjay";
+	test_binary_load "sanjay";
+
+	if not test_script_run { }
+}
+
+test "Relational (RFC5231) example" {
+	if not test_script_compile "../../examples/relational.rfc5231.sieve" {
+		test_fail "could not compile";
+	}
+
+	test_binary_save "relational";
+	test_binary_load "relational";
+
+	if not test_script_run { }
+}
+
+test "Subaddress (RFC5233) example" {
+	if not test_script_compile "../../examples/subaddress.rfc5233.sieve" {
+		test_fail "could not compile";
+	}
+
+	test_binary_save "subaddress";
+	test_binary_load "subaddress";
+
+	if not test_script_run { }
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/execute/mailstore.svtest
@@ -0,0 +1,84 @@
+require "vnd.dovecot.testsuite";
+require "fileinto";
+require "variables";
+require "mailbox";
+
+set "message1" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: First message
+
+Frop
+.
+;
+
+set "message2" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Second message
+
+Frop
+.
+;
+
+set "message3" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Third message
+
+Frop
+.
+;
+
+test "Duplicates" {
+	test_set "message" "${message1}";
+
+	fileinto :create "Folder";
+	fileinto :create "Folder";
+
+	if not test_result_execute {
+		test_fail "failed to execute first result";
+	}
+
+	test_result_reset;
+
+	test_set "message" "${message2}";
+
+	fileinto :create "Folder";
+	fileinto :create "Folder";
+
+	if not test_result_execute {
+		test_fail "failed to execute second result";
+	}
+
+	test_result_reset;
+
+	test_set "message" "${message3}";
+
+	fileinto :create "Folder";
+	fileinto :create "Folder";
+
+	if not test_result_execute {
+		test_fail "failed to execute third result";
+	}
+
+	test_message :folder "Folder" 0;
+
+	if not header :is "subject" "First message" {
+		test_fail "first message incorrect";
+	}
+
+	test_message :folder "Folder" 1;
+
+	if not header :is "subject" "Second message" {
+		test_fail "first message incorrect";
+	}
+
+	test_message :folder "Folder" 2;
+
+	if not header :is "subject" "Third message" {
+		test_fail "first message incorrect";
+	}
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/execute/smtp.svtest
@@ -0,0 +1,348 @@
+require "vnd.dovecot.testsuite";
+require "envelope";
+
+test_set "message" text:
+From: stephan@example.org
+To: tss@example.net
+Subject: Frop!
+
+Frop!
+.
+;
+test_set "envelope.from" "sirius@example.org";
+test_set "envelope.to" "timo@example.net";
+
+test "Redirect" {
+	redirect "cras@example.net";
+
+	if not test_result_execute {
+		test_fail "failed to execute redirect";
+	}
+
+	test_message :smtp 0;
+
+	if not address :is "to" "tss@example.net" {
+		test_fail "to address incorrect (strange forward)";
+	}
+
+	if not address :is "from" "stephan@example.org" {
+		test_fail "from address incorrect (strange forward)";
+	}
+
+	if not envelope :is "to" "cras@example.net" {
+		test_fail "envelope recipient incorrect";
+	}
+
+	if not envelope :is "from" "sirius@example.org" {
+		test_fail "envelope sender incorrect";
+	}
+
+	if not header :contains "x-sieve-redirected-from"
+		"timo@example.net" {
+		test_fail "x-sieve-redirected-from header is incorrect";
+	}
+}
+
+test_result_reset;
+test_set "message" text:
+From: stephan@example.org
+To: tss@example.net
+Subject: Frop!
+
+Frop!
+.
+;
+test_set "envelope.from" "<>";
+test_set "envelope.to" "timo@example.net";
+
+test "Redirect from <>" {
+	redirect "cras@example.net";
+
+	if not test_result_execute {
+		test_fail "failed to execute redirect";
+	}
+
+	test_message :smtp 0;
+
+	if envelope :is "from" "sirius@example.org" {
+		test_fail "envelope sender incorrect (not changed)";
+	}
+
+	if not envelope :is "from" "" {
+		test_fail "envelope sender incorrect";
+	}
+
+	if not header :contains "x-sieve-redirected-from"
+		"timo@example.net" {
+		test_fail "x-sieve-redirected-from header is incorrect";
+	}
+}
+
+test_result_reset;
+test_set "message" text:
+From: stephan@example.org
+To: tss@example.net
+Subject: Frop!
+
+Frop!
+.
+;
+test_set "envelope.from" "sirius@example.org";
+test_set "envelope.to" "timo@example.net";
+
+test_config_set "sieve_redirect_envelope_from" " recipient ";
+test_config_reload;
+
+test "Redirect from [recipient]" {
+	redirect "cras@example.net";
+
+	if not test_result_execute {
+		test_fail "failed to execute redirect";
+	}
+
+	test_message :smtp 0;
+
+	if envelope :is "from" "sirius@example.org" {
+		test_fail "envelope sender incorrect (not changed)";
+	}
+
+	if not envelope :is "from" "timo@example.net" {
+		test_fail "envelope sender incorrect";
+	}
+
+	if not header :contains "x-sieve-redirected-from"
+		"timo@example.net" {
+		test_fail "x-sieve-redirected-from header is incorrect";
+	}
+}
+
+test_result_reset;
+test_set "message" text:
+From: stephan@example.org
+To: tss@example.net
+Subject: Frop!
+
+Frop!
+.
+;
+test_set "envelope.from" "sirius@example.org";
+test_set "envelope.to" "timo@example.net";
+test_set "envelope.orig_to" "tss@example.net";
+
+test_config_set "sieve_redirect_envelope_from" "orig_recipient ";
+test_config_reload;
+
+test "Redirect from [original recipient]" {
+	redirect "cras@example.net";
+
+	if not test_result_execute {
+		test_fail "failed to execute redirect";
+	}
+
+	test_message :smtp 0;
+
+	if envelope :is "from" "sirius@example.org" {
+		test_fail "envelope sender incorrect (not changed)";
+	}
+
+	if not envelope :is "from" "tss@example.net" {
+		test_fail "envelope sender incorrect";
+	}
+
+	if not header :contains "x-sieve-redirected-from"
+		"timo@example.net" {
+		test_fail "x-sieve-redirected-from header is incorrect";
+	}
+}
+
+test_result_reset;
+test_set "message" text:
+From: stephan@example.org
+To: tss@example.net
+Subject: Frop!
+
+Frop!
+.
+;
+test_set "envelope.from" "sirius@example.org";
+test_set "envelope.to" "timo@example.net";
+test_set "envelope.orig_to" "tss@example.net";
+
+test_config_set "sieve_redirect_envelope_from" "<backscatter@example.net> ";
+test_config_reload;
+
+test "Redirect from [<explicit>]" {
+	redirect "cras@example.net";
+
+	if not test_result_execute {
+		test_fail "failed to execute redirect";
+	}
+
+	test_message :smtp 0;
+
+	if envelope :is "from" "sirius@example.org" {
+		test_fail "envelope sender incorrect (not changed)";
+	}
+
+	if not envelope :is "from" "backscatter@example.net" {
+		test_fail "envelope sender incorrect";
+	}
+
+	if not header :contains "x-sieve-redirected-from"
+		"timo@example.net" {
+		test_fail "x-sieve-redirected-from header is incorrect";
+	}
+}
+
+test_result_reset;
+test_set "message" text:
+From: stephan@example.org
+To: tss@example.net
+Subject: Frop!
+
+Frop!
+.
+;
+test_set "envelope.from" "sirius@example.org";
+test_set "envelope.to" "timo@example.net";
+
+test_config_set "sieve_redirect_envelope_from" "<>";
+test_config_reload;
+
+test "Redirect from [<>]" {
+	redirect "cras@example.net";
+
+	if not test_result_execute {
+		test_fail "failed to execute redirect";
+	}
+
+	test_message :smtp 0;
+
+	if envelope :is "from" "sirius@example.org" {
+		test_fail "envelope sender incorrect (not changed)";
+	}
+
+	if not envelope :is "from" "" {
+		test_fail "envelope sender incorrect";
+	}
+
+	if not header :contains "x-sieve-redirected-from"
+		"timo@example.net" {
+		test_fail "x-sieve-redirected-from header is incorrect";
+	}
+}
+
+test_result_reset;
+test_set "message" text:
+From: stephan@example.org
+To: tss@example.net
+Subject: Frop!
+
+Frop!
+.
+;
+test_set "envelope.from" "<>";
+test_set "envelope.to" "timo@example.net";
+test_set "envelope.orig_to" "tss@example.net";
+
+test_config_set "sieve_redirect_envelope_from" "<backscatter@example.net>";
+test_config_reload;
+
+test "Redirect from <> with [<explicit>]" {
+	redirect "cras@example.net";
+
+	if not test_result_execute {
+		test_fail "failed to execute redirect";
+	}
+
+	test_message :smtp 0;
+
+	if envelope :is "from" "backscatter@example.net" {
+		test_fail "envelope sender incorrect (erroneously changed)";
+	}
+
+	if not envelope :is "from" "" {
+		test_fail "envelope sender incorrect";
+	}
+
+	if not header :contains "x-sieve-redirected-from"
+		"timo@example.net" {
+		test_fail "x-sieve-redirected-from header is incorrect";
+	}
+}
+
+test_result_reset;
+test_set "message" text:
+From: stephan@example.org
+To: tss@example.net
+Subject: Frop!
+
+Frop!
+.
+;
+test_set "envelope.from" "sirius@example.org";
+test_set "envelope.to" "timo@example.net";
+test_set "envelope.orig_to" "tss@example.net";
+
+test_config_set "sieve_redirect_envelope_from" "user_email";
+test_config_reload;
+
+test "Redirect from [user email - fallback default]" {
+	redirect "cras@example.net";
+
+	if not test_result_execute {
+		test_fail "failed to execute redirect";
+	}
+
+	test_message :smtp 0;
+
+	if not envelope :is "from" "timo@example.net" {
+		test_fail "envelope sender incorrect";
+	}
+
+	if not header :contains "x-sieve-redirected-from"
+		"timo@example.net" {
+		test_fail "x-sieve-redirected-from header is incorrect";
+	}
+}
+
+test_result_reset;
+test_set "message" text:
+From: stephan@example.org
+To: tss@example.net
+Subject: Frop!
+
+Frop!
+.
+;
+test_set "envelope.from" "sirius@example.org";
+test_set "envelope.to" "timo@example.net";
+test_set "envelope.orig_to" "tss@example.net";
+
+test_config_set "sieve_redirect_envelope_from" "user_email";
+test_config_set "sieve_user_email" "t.sirainen@example.net";
+test_config_reload;
+
+test "Redirect from [user email]" {
+	redirect "cras@example.net";
+
+	if not test_result_execute {
+		test_fail "failed to execute redirect";
+	}
+
+	test_message :smtp 0;
+
+	if envelope :is "from" "sirius@example.org" {
+		test_fail "envelope sender incorrect (not changed)";
+	}
+
+	if not envelope :is "from" "t.sirainen@example.net" {
+		test_fail "envelope sender incorrect";
+	}
+
+	if not header :contains "x-sieve-redirected-from"
+		"t.sirainen@example.net" {
+		test_fail "x-sieve-redirected-from header is incorrect";
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/body/basic.svtest
@@ -0,0 +1,97 @@
+require "vnd.dovecot.testsuite";
+require "relational";
+require "comparator-i;ascii-numeric";
+
+require "body";
+
+test_set "message" text:
+From: stephan@example.org
+To: tss@example.net
+Subject: Test message.
+
+Test!
+
+.
+;
+
+/* Empty line
+ *
+ *  RFC 5173:
+ *    'The body test matches content in the body of an email message, that
+ *     is, anything following the first empty line after the header.  (The
+ *     empty line itself, if present, is not considered to be part of the
+ *     body.)'
+ */
+test "The empty line" {
+
+	if not body :raw :is text:
+Test!
+
+.
+	{
+		test_fail "invalid message body extracted (1)";
+	}
+
+	if body :raw :is text:
+
+Test!
+
+.
+	{
+		test_fail "invalid message body extracted (2)";
+	}
+
+	if body :raw :is "Test"
+	{
+		test_fail "body test matches nonsense (3)";
+	}
+}
+
+/* Default comparator and match type
+ *
+ *  RFC 5173:
+ *    'The COMPARATOR and MATCH-TYPE keyword parameters are defined in
+ *     [SIEVE].  As specified in Sections 2.7.1 and 2.7.3 of [SIEVE], the
+ *     default COMPARATOR is "i;ascii-casemap" and the default MATCH-TYPE is
+ *     ":is".'
+ */
+
+test "Defaults" {
+	if anyof ( body :raw "Test", body :raw "*Test*" ) {
+		test_fail "default match type is not :is as is required";
+	}
+
+	if allof( not body :raw :contains "tesT", body :raw :contains "Test" ) {
+		test_fail "default comparator is not i;ascii-casemap as is required";
+	}
+}
+
+/* No body
+ *
+ *  RFC 5173:
+ *    'If a message consists of a header only, not followed by an empty line,
+ *     then that set is empty and all "body" tests return false, including
+ *     those that test for an empty string.  (This is similar to how the
+ *     "header" test always fails when the named header fields aren't present.)'
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: tss@example.net
+Subject: No body is here!
+.
+;
+
+test "No body" {
+	if body :raw :contains "" {
+		test_fail "matched against non-existent body (:contains \"\")";
+	}
+
+	if body :raw :is "" {
+		test_fail "matched against non-existent body (:is \"\")";
+	}
+
+	if body :raw :matches "*" {
+		test_fail "matched against non-existent body (:matches \"*\")";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/body/content.svtest
@@ -0,0 +1,332 @@
+require "vnd.dovecot.testsuite";
+require "relational";
+require "comparator-i;ascii-numeric";
+
+require "body";
+
+/*
+ *
+ */
+
+test_set "message" text:
+From: justin@example.com
+To: carl@example.nl
+Subject: Frop
+Content-Type: multipart/mixed; boundary=donkey
+
+This is a multi-part message in MIME format.
+
+--donkey
+Content-Type: text/plain
+
+Plain Text
+
+--donkey
+Content-Type: text/stupid
+
+Stupid Text
+
+--donkey
+Content-Type: text/plain/stupid
+
+Plain Stupid Text
+
+--donkey--
+.
+;
+
+/*
+ * RFC5173, Section 5.2:
+ *  If an individual content type begins or ends with a '/' (slash) or
+ *  contains multiple slashes, then it matches no content types.
+ *  ...
+ */
+
+test "Basic Match" {
+	if not body :content "text/plain" :matches "Plain Text*" {
+		test_fail "failed to match (1)";
+	}
+
+	if not body :content "text/plain" :contains "" {
+		test_fail "failed to match (2)";
+	}
+
+	if not body :content "text/stupid" :contains "" {
+		test_fail "failed to match (3)";
+	}
+}
+
+test "Begin Slash" {
+	if body :content "/plain" :contains "" {
+		test_fail "matched :content \"/plain\"";
+	}
+}
+
+test "End Slash" {
+	if body :content "text/" :contains "" {
+		test_fail "matched :content \"text/\"";
+	}
+}
+
+test "Double Slash" {
+	if body :content "text/plain/stupid" :contains "" {
+		test_fail "matched :content \"text/plain/stupid\"";
+	}
+}
+
+/*
+ *
+ */
+
+test_set "message" text:
+From: justin@example.com
+To: carl@example.nl
+Subject: Frop
+Content-Type: multipart/mixed; boundary=limit
+
+This is a multi-part message in MIME format.
+
+--limit
+Content-Type: text/plain
+
+This is a text message.
+
+--limit
+Content-Type: text/html
+
+<html><body>This is HTML</body></html>
+
+--limit
+Content-Type: application/sieve
+
+keep;
+
+--limit--
+.
+;
+
+/* RFC5173, Section 5.2:
+ *  ...
+ *  Otherwise, if it contains a slash, then it specifies a full
+ *  <type>/<subtype> pair, and matches only that specific content type.
+ *  If it is the empty string, all MIME content types are matched.
+ *  Otherwise, it specifies a <type> only, and any subtype of that type
+ *  matches it.
+ */
+
+test "Full Content Type" {
+	if not body :content "text/plain" :matches "This is a text message.*" {
+		test_fail "failed to match text/plain content";
+	}
+
+	if body :content "text/plain" :matches "<html><body>This is HTML</body></html>*" {
+		test_fail "erroneously matched text/html content";
+	}
+
+	if not body :content "text/html" :matches "<html><body>This is HTML</body></html>*" {
+		test_fail "failed to match text/html content";
+	}
+
+	if body :content "text/html" :matches "This is a text message.*" {
+		test_fail "erroneously matched text/plain content";
+	}
+
+	if body :content "text/html" :matches "This is HTML*" {
+		test_fail "body :content test matched plain text";
+	}
+}
+
+test "Empty Content Type" {
+	if not body :content "" :matches "This is a text message.*" {
+		test_fail "failed to match text/plain content";
+	}
+
+	if not body :content "" :matches "<html><body>This is HTML</body></html>*" {
+		test_fail "failed to match text/html content";
+	}
+
+	if not body :content "" :matches "keep;*" {
+		test_fail "failed to match application/sieve content";
+	}
+
+	if body :content "" :matches "*blurdybloop*" {
+		test_fail "body :content \"\" test matches nonsense";
+	}
+}
+
+test "Main Content Type" {
+	if not body :content "text" :matches "This is a text message.*" {
+		test_fail "failed to match text/plain content";
+	}
+
+	if not body :content "text" :matches "<html><body>This is HTML</body></html>*" {
+		test_fail "failed to match text/html content";
+	}
+
+	if body :content "text" :matches "keep;*" {
+		test_fail "erroneously matched application/sieve content";
+	}
+}
+
+/*
+ *
+ */
+
+test_set "message" text:
+From: Whomever <whoever@example.com>
+To: Someone <someone@example.com>
+Date: Sat, 10 Oct 2009 00:30:04 +0200
+Subject: whatever
+Content-Type: multipart/mixed; boundary=outer
+
+This is a multi-part message in MIME format.
+
+--outer
+Content-Type: multipart/alternative; boundary=inner
+
+This is a nested multi-part message in MIME format.
+
+--inner
+Content-Type: text/plain; charset="us-ascii"
+
+Hello
+
+--inner
+Content-Type: text/html; charset="us-ascii"
+
+<html><body>Hello</body></html>
+
+--inner--
+
+This is the end of the inner MIME multipart.
+
+--outer
+Content-Type: message/rfc822
+
+From: Someone Else
+Subject: Hello, this is an elaborate request for you to finally say hello
+ already!
+
+Please say Hello
+
+--outer--
+
+This is the end of the outer MIME multipart.
+.
+;
+
+/* RFC5173, Section 5.2:
+ *
+ *  The search for MIME parts matching the :content specification is
+ *  recursive and automatically descends into multipart and
+ *  message/rfc822 MIME parts.  All MIME parts with matching types are
+ *  searched for the key strings.  The test returns true if any
+ *  combination of a searched MIME part and key-list argument match.
+ */
+
+test "Nested Search" {
+	if not body :content "text/plain" :matches "Hello*" {
+		test_fail "failed to match text/plain content";
+	}
+
+	if body :content "text/plain" :matches "<html><body>Hello</body></html>*" {
+		test_fail "erroneously matched text/html content";
+	}
+
+	if not body :content "text/html" :matches "<html><body>Hello</body></html>*" {
+		test_fail "failed to match text/html content";
+	}
+
+	if body :content "text/html" :matches "Hello*" {
+		test_fail "erroneously matched text/plain content";
+	}
+
+	if not body :content "text" :contains "html" {
+		test_fail "failed match text content (1)";
+	}
+
+	if not body :content "text" :contains "hello" {
+		test_fail "failed match text content (2)";
+	}
+
+	if not body :content "text/plain" :contains "please say hello" {
+		test_fail "failed match nested message content as text/plain";
+	}
+
+	if not body :content "text" :contains "please say hello" {
+		test_fail "failed match nested message content as text/*";
+	}
+
+	if not body :content "text" :count "eq" :comparator "i;ascii-numeric" "3" {
+		test_fail "matched wrong number of \"text/*\" body parts";
+	}
+}
+
+/* RFC5173, Section 5.2:
+ *
+ *  If the :content specification matches a multipart MIME part, only the
+ *  prologue and epilogue sections of the part will be searched for the
+ *  key strings, treating the entire prologue and the entire epilogue as
+ *  separate strings; the contents of nested parts are only searched if
+ *  their respective types match the :content specification.
+ *
+ */
+
+test "Multipart Content" {
+	if not body :content "multipart" :contains
+		"This is a multi-part message in MIME format" {
+		test_fail "missed first multipart body part";
+	}
+
+	if not body :content "multipart" :contains
+		"This is a nested multi-part message in MIME format" {
+		test_fail "missed second multipart body part";
+	}
+
+	if not body :content "multipart" :contains
+		"This is the end of the inner MIME multipart" {
+		test_fail "missed third multipart body part";
+	}
+
+	if not body :content "multipart" :contains
+		"This is the end of the outer MIME multipart." {
+		test_fail "missed fourth multipart body part";
+	}
+
+	if body :content "multipart" :contains "--inner" {
+		test_fail "inner boundary is part of match";
+	}
+
+	if body :content "multipart" :contains "--outer" {
+		test_fail "outer boundary is part of match";
+	}
+}
+
+/* RFC5173, Section 5.2:
+ *
+ *  If the :content specification matches a message/rfc822 MIME part,
+ *  only the header of the nested message will be searched for the key
+ *  strings, treating the header as a single string; the contents of the
+ *  nested message body parts are only searched if their content type
+ *  matches the :content specification.
+ */
+
+test "Content-Type: message/rfc822" {
+	if not body :content "message/rfc822" :contains
+		"From: Someone Else" {
+		test_fail "missed raw message/rfc822 from header";
+	}
+
+	if not body :content "message/rfc822" :is text:
+From: Someone Else
+Subject: Hello, this is an elaborate request for you to finally say hello
+ already!
+.
+	{
+		test_fail "header content does not match exactly";
+	}
+}
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/body/errors.svtest
@@ -0,0 +1,19 @@
+require "vnd.dovecot.testsuite";
+
+require "relational";
+require "comparator-i;ascii-numeric";
+
+/*
+ * Invalid syntax
+ */
+
+test "Invalid Syntax" {
+        if test_script_compile "errors/syntax.sieve" {
+                test_fail "compile should have failed";
+        }
+
+        if not test_error :count "eq" :comparator "i;ascii-numeric" "12" {
+                test_fail "wrong number of errors reported";
+        }
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/body/errors/syntax.sieve
@@ -0,0 +1,38 @@
+require "body";
+
+# 1: No key list
+if body { }
+
+# 2: Number 
+if body 3 { }
+
+# OK: String
+if body "frop" { }
+
+# 3: To many arguments
+if body "frop" "friep" { }
+
+# 4: Unknown tag
+if body :frop { }
+
+# 5: Unknown tag with valid key
+if body :friep "frop" { }
+
+# 6: Content without argument
+if body :content { }
+
+# 7: Content without key argument
+if body :content "frop" { }
+
+# 8: Content with number argument
+if body :content 3 "frop" { }
+
+# 9: Content with unknown tag
+if body :content :frml "frop" { }
+
+# 10: Content with known tag
+if body :content :contains "frop" {  }
+
+# 11: Duplicate transform
+if body :content "frop" :raw "frop" {  }
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/body/match-values.svtest
@@ -0,0 +1,55 @@
+require "vnd.dovecot.testsuite";
+
+require "body";
+require "variables";
+
+test_set "message" text:
+From: stephan@example.org
+To: s.bosch@twente.example.net
+Subject: Body test
+
+The big bad body test.
+.
+;
+
+# Test whether body test ignores match values
+test "Match values disabled" {
+	if not body :raw :matches "The * bad * test*" {
+		test_fail "should have matched";
+	}
+
+	if anyof (
+		string :is "${1}" "big",
+		string :is "${2}" "body",
+		not string :is "${0}" "",
+		not string :is "${1}" "",
+		not string :is "${2}" "") {
+		test_fail "match values not disabled";
+	}
+}
+
+test "Match values re-enabled" {
+	if not header :matches "from" "*@*" {
+		test_fail "should have matched";
+	}
+
+	if anyof (
+		not string :is "${0}" "stephan@example.org",
+		not string :is "${1}" "stephan",
+		not string :is "${2}" "example.org" ) {
+		test_fail "match values not re-enabled properly.";
+	}
+}
+
+test "Match values retained" {
+	if not body :raw :matches "The * bad * test*" {
+		test_fail "should have matched";
+	}
+
+	if anyof (
+		not string :is "${0}" "stephan@example.org",
+		not string :is "${1}" "stephan",
+		not string :is "${2}" "example.org" ) {
+		test_fail "match values not retained after body test.";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/body/raw.svtest
@@ -0,0 +1,85 @@
+require "vnd.dovecot.testsuite";
+require "body";
+
+test_set "message" text:
+From: Whomever <whoever@example.com>
+To: Someone <someone@example.com>
+Date: Sat, 10 Oct 2009 00:30:04 +0200
+Subject: whatever
+Content-Type: multipart/mixed; boundary=outer
+
+This is a multi-part message in MIME format.
+
+--outer
+Content-Type: multipart/alternative; boundary=inner
+
+This is a nested multi-part message in MIME format.
+
+--inner
+Content-Type: text/plain; charset="us-ascii"
+
+Hello
+
+--inner
+Content-Type: text/html; charset="us-ascii"
+
+<html><body>Hello</body></html>
+
+--inner--
+
+This is the end of the inner MIME multipart.
+
+--outer
+Content-Type: message/rfc822
+
+From: Someone Else
+Subject: hello request
+
+Please say Hello
+
+--outer--
+
+This is the end of the outer MIME multipart.
+.
+;
+
+/*
+ *
+ * RFC 5173:
+ *  The ":raw" transform matches against the entire undecoded body of a
+ *  message as a single item.
+ *
+ *  If the specified body-transform is ":raw", the [MIME] structure of
+ *  the body is irrelevant.  The implementation MUST NOT remove any
+ *  transfer encoding from the message, MUST NOT refuse to filter
+ *  messages with syntactic errors (unless the environment it is part of
+ *  rejects them outright), and MUST treat multipart boundaries or the
+ *  MIME headers of enclosed body parts as part of the content being
+ *  matched against, instead of MIME structures to interpret.
+ */
+
+test "Multipart Boundaries" {
+	if not body :raw :contains "--inner" {
+		test_fail "Raw body does not contain '--inner'";
+	}
+
+	if not body :raw :contains "--outer" {
+		test_fail "Raw body does not contain '--outer'";
+	}
+}
+
+test "Multipart Headers" {
+	if not body :raw :contains "boundary=inner" {
+		test_fail "Raw body does not contain 'boundary=inner'";
+	}
+
+	if not body :raw :contains "rfc822" {
+		test_fail "Raw body does not contain 'rfc822'";
+	}
+}
+
+test "Multipart Content" {
+	if not body :raw :contains "<html><body>Hello</body></html>" {
+		test_fail "Raw body does not contain '<html><body>Hello</body></html>'";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/body/text.svtest
@@ -0,0 +1,225 @@
+require "vnd.dovecot.testsuite";
+require "relational";
+require "comparator-i;ascii-numeric";
+
+require "body";
+
+/*
+ *
+ */
+
+test_set "message" text:
+From: justin@example.com
+To: carl@example.nl
+Subject: Frop
+Content-Type: multipart/mixed; boundary=donkey
+
+This is a multi-part message in MIME format.
+
+--donkey
+Content-Type: text/plain
+
+Plain Text
+
+--donkey
+Content-Type: text/stupid
+
+Stupid Text
+
+--donkey
+Content-Type: text/plain/stupid
+
+Plain Stupid Text
+
+--donkey--
+.
+;
+
+test "Basic Match" {
+	if not body :text :contains "Plain Text" {
+		test_fail "failed to match (1)";
+	}
+
+	if not body :text :contains "Stupid Text" {
+		test_fail "failed to match (2)";
+	}
+}
+
+test "Double Slash" {
+	if body :text :contains "Plain Stupid Text" {
+		test_fail "matched \"text/plain/stupid\"";
+	}
+}
+
+/*
+ *
+ */
+
+test_set "message" text:
+From: justin@example.com
+To: carl@example.nl
+Subject: Frop
+Content-Type: multipart/mixed; boundary=limit
+
+This is a multi-part message in MIME format.
+
+--limit
+Content-Type: text/plain
+
+This is a text message.
+
+--limit
+Content-Type: text/html
+
+<html><body>This is HTML</body></html>
+
+--limit
+Content-Type: application/sieve
+
+keep;
+
+--limit--
+.
+;
+
+test "Full Content Type" {
+	if not body :text :contains "This is a text message" {
+		test_fail "failed to match text/plain content";
+	}
+
+	if not body :text :contains "This is HTML" {
+		test_fail "failed to match text/html content";
+	}
+
+	if body :text :contains "<html>" {
+		test_fail "erroneously matched text/html markup";
+	}
+
+	if body :text :contains "keep;" {
+		test_fail "body :text test matched non-text content";
+	}
+}
+
+/*
+ *
+ */
+
+test_set "message" text:
+From: Whomever <whoever@example.com>
+To: Someone <someone@example.com>
+Date: Sat, 10 Oct 2009 00:30:04 +0200
+Subject: whatever
+Content-Type: multipart/mixed; boundary=outer
+
+This is a multi-part message in MIME format.
+
+--outer
+Content-Type: multipart/alternative; boundary=inner
+
+This is a nested multi-part message in MIME format.
+
+--inner
+Content-Type: text/plain; charset="us-ascii"
+
+Hello
+
+--inner
+Content-Type: text/html; charset="us-ascii"
+
+<html><body>HTML Hello</body></html>
+
+--inner
+Content-Type: application/xhtml+xml; charset="us-ascii"
+
+<html><body>XHTML Hello</body></html>
+
+--inner--
+
+This is the end of the inner MIME multipart.
+
+--outer
+Content-Type: message/rfc822
+
+From: Someone Else
+Subject: Hello, this is an elaborate request for you to finally say hello
+ already!
+
+Please say Hello
+
+--outer--
+
+This is the end of the outer MIME multipart.
+.
+;
+
+/* RFC5173, Section 5.2:
+ *
+ *  The search for MIME parts matching the :content specification is
+ *  recursive and automatically descends into multipart and
+ *  message/rfc822 MIME parts.  All MIME parts with matching types are
+ *  searched for the key strings.  The test returns true if any
+ *  combination of a searched MIME part and key-list argument match.
+ */
+
+test "Nested Search" {
+	if not body :text :contains "Hello" {
+		test_fail "failed to match text/plain content";
+	}
+	if not body :text :contains "HTML Hello" {
+		test_fail "failed to match text/html content";
+	}
+	if not body :text :contains "XHTML Hello" {
+		test_fail "failed to match application/xhtml+xml content";
+	}
+	if body :text :contains ["<html>", "body"] {
+		test_fail "erroneously matched text/html markup";
+	}
+	if not body :text :contains "Please say Hello" {
+		test_fail "failed to match message/rfc822 body";
+	}
+	if body :text :contains "MIME" {
+		test_fail "erroneously matched multipart prologue/epilogue text";
+	}
+}
+
+/*
+ * Broken/Empty parts
+ */
+
+test_set "message" text:
+From: Whomever <whoever@example.com>
+To: Someone <someone@example.com>
+Date: Sat, 10 Oct 2009 00:30:04 +0200
+Subject: whatever
+Content-Type: multipart/mixed; boundary=outer
+
+This is a multi-part message in MIME format.
+
+--outer
+Content-Type: text/html
+
+--outer
+Content-Type: text/html; charset=utf-8
+Content-Transfer-Encoding: multipart/related
+Content-Disposition: inline
+
+<html><body>Please say Hello</body></html>
+
+--outer--
+
+This is the end of the outer MIME multipart.
+.
+;
+
+test "Nested Search" {
+	if body :text :contains "Hello" {
+		test_fail "Cannot match empty/broken part";
+	}
+	if body :text :contains ["<html>", "body"] {
+		test_fail "erroneously matched text/html markup";
+	}
+	if body :text :contains "MIME" {
+		test_fail "erroneously matched multipart prologue/epilogue text";
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/date/basic.svtest
@@ -0,0 +1,73 @@
+require "vnd.dovecot.testsuite";
+require "date";
+require "variables";
+require "relational";
+
+test_set "message" text:
+From: stephan@example.org
+To: sirius@friep.example.com
+Subject: Frop!
+Date: Mon, 20 Jul 2009 21:44:43 +0300
+Delivery-Date: Mon, 22 Jul 2009 23:30:14 +0300
+Invalid-Date: Moo, 34 Juul 3060 25:30:42 +6600
+Wanna date?
+.
+;
+
+test "Defaults" {
+	if not date :originalzone "date" "std11" "mon, 20 jul 2009 21:44:43 +0300" {
+		test_fail "default comparator is not i;ascii-casemap";
+	}
+
+	if anyof ( date "date" "std11" "Mon", date "date" "std11" "*") {
+		test_fail "default match type appears to be :contains or :matches";
+	}
+}
+
+test "Count" {
+	if not date :count "eq" "date" "date" "1" {
+		test_fail "count of existing date header field is not 1";
+	}
+
+	if not date :count "eq" "resent-date" "date" "0" {
+		test_fail "count of non-existent date header field is not 0";
+	}
+}
+
+test "Invalid" {
+	if date :matches "invalid-date" "std11" "*" {
+		test_fail "matched invalid date: ${0}";
+	}
+}
+
+test "Comparison" {
+	if not date :originalzone :is "delivery-date" "date" "2009-07-22" {
+		if date :originalzone :matches "delivery-date" "date" "*" { set "date" "${1}"; }
+		test_fail "date is invalid: ${date}";
+	}
+	if not date :originalzone :value "ge" "delivery-date" "date" "2009-07-22" {
+		test_fail "date comparison ge failed equal";
+	}
+
+	if not date :originalzone :value "ge" "delivery-date" "date" "2009-07-21" {
+		test_fail "date comparison ge failed greater";
+	}
+
+	if anyof (not date :originalzone :value "ge" "delivery-date" "date" "2009-06-22",
+		not date :originalzone :value "ge" "date" "date" "2006-07-22" ) {
+		test_fail "date comparison ge failed much greater";
+	}
+
+	if not date :originalzone :value "le" "delivery-date" "date" "2009-07-22" {
+		test_fail "date comparison le failed equal";
+	}
+
+	if not date :originalzone :value "le" "delivery-date" "date" "2009-07-23" {
+		test_fail "date comparison le failed less";
+	}
+
+	if anyof (not date :originalzone :value "le" "delivery-date" "date" "2009-09-22",
+		not date :originalzone :value "le" "date" "date" "2012-07-22" ) {
+		test_fail "date comparison ge failed much less";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/date/date-parts.svtest
@@ -0,0 +1,120 @@
+require "vnd.dovecot.testsuite";
+require "date";
+require "variables";
+
+test_set "message" text:
+From: stephan@example.org
+To: sirius@friep.example.com
+Subject: Frop!
+Date: Mon, 20 Jul 2009 21:44:43 +0300
+Delivery-Date: Mon, 22 Jul 2009 23:30:14 +0300
+
+Wanna date?
+.
+;
+
+/* "year"      => the year, "0000" .. "9999". */
+test "Year" {
+	if not date :originalzone "date" "year" "2009" {
+		test_fail "failed to extract year part";
+	}
+}
+
+/* "month"     => the month, "01" .. "12". */
+test "Month" {
+	if not date :originalzone "date" "month" "07" {
+		test_fail "failed to extract month part";
+	}
+}
+
+/* "day"       => the day, "01" .. "31". */
+test "Day" {
+	if not date :originalzone "date" "day" "20" {
+		test_fail "failed to extract day part";
+	}
+}
+
+/* "date"      => the date in "yyyy-mm-dd" format. */
+test "Date" {
+	if not date :originalzone "date" "date" "2009-07-20" {
+		test_fail "failed to extract date part";
+	}
+}
+
+/* "julian"    => the Modified Julian Day, that is, the date
+              expressed as an integer number of days since
+              00:00 UTC on November 17, 1858 (using the Gregorian
+              calendar).  This corresponds to the regular
+              Julian Day minus 2400000.5.  */
+test "Julian" {
+	if not date :originalzone "date" "julian" "55032" {
+		if date :matches :originalzone "date" "julian" "*" { }
+		test_fail "failed to extract julian part: ${0}";
+	}
+	if not date :originalzone "delivery-date" "julian" "55034" {
+		if date :matches :originalzone "delivery-date" "julian" "*" { }
+		test_fail "failed to extract julian part: ${0}";
+	}
+}
+
+/* "hour"      => the hour, "00" .. "23". */
+test "Hour" {
+	if not date :originalzone "date" "hour" "21" {
+		test_fail "failed to extract hour part";
+	}
+}
+
+/* "minute"    => the minute, "00" .. "59". */
+test "Minute" {
+	if not date :originalzone "date" "minute" "44" {
+		test_fail "failed to extract minute part";
+	}
+}
+
+/* "second"    => the second, "00" .. "60". */
+test "Second" {
+	if not date :originalzone "date" "second" "43" {
+		test_fail "failed to extract second part";
+	}
+}
+
+/* "time"      => the time in "hh:mm:ss" format. */
+test "Time" {
+	if not date :originalzone "date" "time" "21:44:43" {
+		test_fail "failed to extract time part";
+	}
+}
+
+/* "iso8601"   => the date and time in restricted ISO 8601 format. */
+test "ISO8601" {
+	if not date :originalzone "date" "iso8601" "2009-07-20T21:44:43+03:00" {
+		test_fail "failed to extract iso8601 part";
+	}
+}
+
+/* "std11"     => the date and time in a format appropriate
+                  for use in a Date: header field [RFC2822]. */
+test "STD11" {
+	if not date :originalzone "date" "std11" "Mon, 20 Jul 2009 21:44:43 +0300" {
+		test_fail "failed to extract std11 part";
+	}
+}
+
+/* "zone"      => the time zone in use.  */
+test "zone" {
+	if not date :originalzone "date" "zone" "+0300" {
+		test_fail "failed to extract zone part";
+	}
+
+	if not date :zone "+0200" "date" "zone" "+0200" {
+		test_fail "failed to extract zone part";
+	}
+}
+
+/* "weekday"   => the day of the week expressed as an integer between
+                  "0" and "6". "0" is Sunday, "1" is Monday, etc. */
+test "Weekday" {
+	if not date :originalzone "date" "weekday" "1" {
+		test_fail "failed to extract weekday part";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/date/zones.svtest
@@ -0,0 +1,76 @@
+require "vnd.dovecot.testsuite";
+require "date";
+require "variables";
+
+/* Extract local timezone first */
+test "Local-Zone" {
+	if not currentdate :matches "zone" "*" {
+		test_fail "matches '*' failed for zone part.";
+	}
+	set "local_zone" "${0}";
+}
+
+/* FIXME: using variables somehow fails here */
+if string "${local_zone}" "+0200" {
+test_set "message" text:
+From: stephan@example.org
+To: sirius@friep.example.com
+Subject: Frop!
+Date: Mon, 20 Jul 2009 21:44:43 +0300
+Delivery-Date: Mon, 23 Jul 2009 05:30:14 +0800
+
+Wanna date?
+.
+;
+} else {
+test_set "message" text:
+From: stephan@example.org
+To: sirius@friep.example.com
+Subject: Frop!
+Date: Mon, 20 Jul 2009 21:44:43 +0300
+Delivery-Date: Mon, 22 Jul 2009 23:30:14 +0200
+
+Wanna date?
+.
+;
+}
+
+test "Specified Zone" {
+	if not date :zone "+0200" "date" "zone" "+0200" {
+		if date :matches :zone "+0200" "date" "zone" "*" {}
+		test_fail "zone is incorrect: ${0}";
+	}
+
+	if not date :zone "+0200" "date" "time" "20:44:43" {
+		test_fail "zone is not applied";
+	}
+}
+
+test "Original Zone" {
+	if not date :originalzone "date" "zone" "+0300" {
+		if date :matches :originalzone "date" "zone" "*" {}
+		test_fail "zone is incorrect: ${0}";
+	}
+
+	if not date :originalzone "date" "time" "21:44:43" {
+		test_fail "time should be left untouched";
+	}
+}
+
+test "Local Zone Shift" {
+	if anyof (
+			allof (
+				string "${local_zone}" "+0200",
+				date "delivery-date" "iso8601" "2009-07-23T05:30:14+08:00"),
+			allof (
+				not string "${local_zone}" "+0200",
+				date "delivery-date" "iso8601" "2009-07-22T23:30:14+02:00")) {
+
+		if date :matches "delivery-date" "iso8601" "*"
+			{ set "a" "${0}"; }
+		if date :originalzone :matches "delivery-date" "iso8601" "*"
+			{ set "b" "${0}"; }
+
+		test_fail "time not shifted to local zone: ${b} => ${a}";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/duplicate/errors.svtest
@@ -0,0 +1,54 @@
+require "vnd.dovecot.testsuite";
+
+require "relational";
+require "comparator-i;ascii-numeric";
+
+/*
+ * Invalid syntax
+ */
+
+test "Invalid Syntax" {
+        if test_script_compile "errors/syntax.sieve" {
+                test_fail "compile should have failed";
+        }
+
+        if not test_error :count "eq" :comparator "i;ascii-numeric" "17" {
+                test_fail "wrong number of errors reported";
+        }
+}
+
+test "Invalid Syntax (vnd)" {
+        if test_script_compile "errors/syntax-vnd.sieve" {
+                test_fail "compile should have failed";
+        }
+
+        if not test_error :count "eq" :comparator "i;ascii-numeric" "5" {
+                test_fail "wrong number of errors reported";
+        }
+}
+
+/*
+ * Extension conflict
+ */
+
+test "Extension conflict" {
+        if test_script_compile "errors/conflict.sieve" {
+                test_fail "compile should have failed";
+        }
+
+        if not test_error :count "eq" :comparator "i;ascii-numeric" "2" {
+                test_fail "wrong number of errors reported";
+        }
+}
+
+test "Extension conflict (vnd first)" {
+        if test_script_compile "errors/conflict-vnd.sieve" {
+                test_fail "compile should have failed";
+        }
+
+        if not test_error :count "eq" :comparator "i;ascii-numeric" "2" {
+                test_fail "wrong number of errors reported";
+        }
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/duplicate/errors/conflict-vnd.sieve
@@ -0,0 +1,4 @@
+require "vnd.dovecot.duplicate";
+require "duplicate";
+
+if duplicate { keep; }
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/duplicate/errors/conflict.sieve
@@ -0,0 +1,4 @@
+require "duplicate";
+require "vnd.dovecot.duplicate";
+
+if duplicate { keep; }
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/duplicate/errors/syntax-vnd.sieve
@@ -0,0 +1,19 @@
+require "vnd.dovecot.duplicate";
+
+# Used as a command
+duplicate;
+
+# Used with no argument (not an error)
+if duplicate {}
+
+# Used with string argument
+if duplicate "frop" { }
+
+# Used with numer argument
+if duplicate 23423 { }
+
+# Used with numer argument
+if duplicate ["frop"] { }
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/duplicate/errors/syntax.sieve
@@ -0,0 +1,54 @@
+require "duplicate";
+
+# Used as a command
+duplicate;
+
+# Used with no argument (not an error)
+if duplicate {}
+
+# Used with string argument
+if duplicate "frop" { }
+
+# Used with numner argument
+if duplicate 23423 { }
+
+# Used with numer argument
+if duplicate ["frop"] { }
+
+# Used with unknown tag
+if duplicate :test "frop" { }
+
+# Bad :header parameter
+if duplicate :header 23 {}
+
+# Bad :uniqueid parameter
+if duplicate :uniqueid 23 {}
+
+# Bad :handle parameter
+if duplicate :handle ["a", "b", "c"] {}
+
+# Bad seconds parameter
+if duplicate :seconds "a" {}
+
+# Missing :header parameter
+if duplicate :header {}
+
+# Missing :uniqueid parameter
+if duplicate :uniqueid {}
+
+# Missing :handle parameter
+if duplicate :handle {}
+
+# Missing seconds parameter
+if duplicate :seconds {}
+
+# :last with a parameter
+if duplicate :last "frop" {}
+
+# :last as :seconds parameter
+if duplicate :seconds :last {}
+
+# Conflicting tags
+if duplicate :header "X-Frop" :uniqueid "FROP!" { }
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/duplicate/execute-vnd.svtest
@@ -0,0 +1,20 @@
+require "vnd.dovecot.testsuite";
+require "vnd.dovecot.duplicate";
+
+test "Run" {
+	if duplicate {
+		test_fail "test erroneously reported a duplicate";
+	}
+
+	if duplicate :handle "handle" {
+		test_fail "test with name erroneously reported a duplicate";
+	}
+
+	if duplicate {
+		test_fail "test erroneously reported a duplicate";
+	}
+
+	if duplicate :handle "handle" {
+		test_fail "test with name erroneously reported a duplicate";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/duplicate/execute.svtest
@@ -0,0 +1,41 @@
+require "vnd.dovecot.testsuite";
+require "duplicate";
+
+# Simple execution tests; no duplicate verification can be tested yet.
+test "Run" {
+	if duplicate {
+		test_fail "test erroneously reported a duplicate";
+	}
+
+	if duplicate :handle "handle" {
+		test_fail "test with :handle erroneously reported a duplicate";
+	}
+
+	if duplicate {
+		test_fail "test erroneously reported a duplicate";
+	}
+
+	if duplicate :handle "handle" {
+		test_fail "test with :handle erroneously reported a duplicate";
+	}
+
+	if duplicate :header "X-frop" {
+		test_fail "test with :header erroneously reported a duplicate";
+	}
+
+	if duplicate :uniqueid "FROP!" {
+		test_fail "test with :uniqueid erroneously reported a duplicate";
+	}
+
+	if duplicate :seconds 90 {
+		test_fail "test with :seconds erroneously reported a duplicate";
+	}
+
+	if duplicate :seconds 90 :last {
+		test_fail "test with :seconds :last erroneously reported a duplicate";
+	}
+
+	if duplicate :last {
+		test_fail "test with :seconds :last erroneously reported a duplicate";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/editheader/addheader.svtest
@@ -0,0 +1,833 @@
+require "vnd.dovecot.testsuite";
+require "encoded-character";
+require "variables";
+require "fileinto";
+require "mailbox";
+require "body";
+
+require "editheader";
+
+set "message" text:
+From: stephan@example.com
+To: timo@example.com
+Subject: Frop!
+
+Frop!
+
+.
+;
+
+test_set "message" "${message}";
+test "Addheader - first" {
+	if size :over 76 {
+		test_fail "original message is longer than 76 bytes?!";
+	}
+
+	addheader "X-Some-Header" "Header content";
+
+	if not size :over 76 {
+		test_fail "mail is not larger";
+	}
+
+	if size :over 107 {
+		test_fail "mail is too large";
+	}
+
+	if size :under 107 {
+		test_fail "mail is too small";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained";
+	}
+
+	if not exists "x-some-header" {
+		test_fail "header not added";
+	}
+
+	if not header :is "x-some-header" "Header content" {
+		test_fail "wrong content added";
+	}
+
+	redirect "frop@example.com";
+	fileinto :create "folder1";
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	if not test_message :folder "folder1" 0 {
+		test_fail "message not stored";
+	}
+
+	if not size :over 76 {
+		test_fail "stored mail is not larger";
+	}
+
+	if size :over 107 {
+		test_fail "stored mail is too large";
+	}
+
+	if size :under 100 {
+		test_fail "stored mail is too small";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in stored mail";
+	}
+
+	if not exists "x-some-header" {
+		test_fail "header not in stored mail";
+	}
+
+	if not header :is "x-some-header" "Header content" {
+		test_fail "wrong content in stored mail ";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in stored mail";
+	}
+
+	if not test_message :smtp 0 {
+		test_fail "message not redirected";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in redirected mail";
+	}
+
+	if not exists "x-some-header" {
+		test_fail "header not in redirected mail";
+	}
+
+	if not header :is "x-some-header" "Header content" {
+		test_fail "wrong content in redirected mail ";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in redirected mail";
+	}
+}
+
+test_result_reset;
+test_set "message" "${message}";
+test "Addheader - first (two)" {
+	if size :over 76 {
+		test_fail "original message is longer than 76 bytes?!";
+	}
+
+	addheader "X-Some-Header" "Header content";
+	addheader "X-Some-Other-Header" "More header content";
+
+	if not size :over 76 {
+		test_fail "mail is not larger";
+	}
+
+	if size :over 149 {
+		test_fail "mail is too large";
+	}
+
+	if size :under 149 {
+		test_fail "mail is too small";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained";
+	}
+
+	if not exists "x-some-header" {
+		test_fail "header #1 not added";
+	}
+
+	if not header :is "x-some-header" "Header content" {
+		test_fail "wrong content added #1";
+	}
+
+	if not exists "x-some-other-header" {
+		test_fail "header #2 not added";
+	}
+
+	if not header :is "x-some-other-header" "More header content" {
+		test_fail "wrong content added #2";
+	}
+
+	redirect "frop@example.com";
+	fileinto :create "folder2";
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	if not test_message :folder "folder2" 0 {
+		test_fail "message not stored";
+	}
+
+	if not size :over 76 {
+		test_fail "stored mail is not larger";
+	}
+
+	if size :over 149 {
+		test_fail "stored mail is too large";
+	}
+
+	if size :under 100 {
+		test_fail "stored mail is too small";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in stored mail";
+	}
+
+	if not exists "x-some-header" {
+		test_fail "header #1 not in stored mail";
+	}
+
+	if not header :is "x-some-header" "Header content" {
+		test_fail "wrong content #1 in stored mail ";
+	}
+
+	if not exists "x-some-other-header" {
+		test_fail "header #2 not in stored mail";
+	}
+
+	if not header :is "x-some-other-header" "More header content" {
+		test_fail "wrong content #2 in stored mail";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in stored mail";
+	}
+
+	if not test_message :smtp 0 {
+		test_fail "message not redirected";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in redirected mail";
+	}
+
+	if not exists "x-some-header" {
+		test_fail "header not in redirected mail";
+	}
+
+	if not header :is "x-some-header" "Header content" {
+		test_fail "wrong content in redirected mail ";
+	}
+
+	if not exists "x-some-other-header" {
+		test_fail "header #2 not in redirected mail";
+	}
+
+	if not header :is "x-some-other-header" "More header content" {
+		test_fail "wrong content #2 in redirected mail";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in redirected mail";
+	}
+}
+
+test_result_reset;
+test_set "message" "${message}";
+test "Addheader - last" {
+	if size :over 76 {
+		test_fail "original message is longer than 76 bytes?!";
+	}
+
+	addheader :last "X-Some-Header" "Header content";
+
+	if not size :over 76 {
+		test_fail "mail is not larger";
+	}
+
+	if size :over 107 {
+		test_fail "mail is too large";
+	}
+
+	if size :under 107 {
+		test_fail "mail is too small";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained";
+	}
+
+	if not exists "x-some-header" {
+		test_fail "header not added";
+	}
+
+	if not header :is "x-some-header" "Header content" {
+		test_fail "wrong content added";
+	}
+
+	redirect "frop@example.com";
+	fileinto :create "folder3";
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	if not test_message :folder "folder3" 0 {
+		test_fail "message not stored";
+	}
+
+	if not size :over 76 {
+		test_fail "stored mail is not larger";
+	}
+
+	if size :over 107 {
+		test_fail "stored mail is too large";
+	}
+
+	if size :under 100 {
+		test_fail "stored mail is too small";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in stored mail";
+	}
+
+	if not exists "x-some-header" {
+		test_fail "header not in stored mail";
+	}
+
+	if not header :is "x-some-header" "Header content" {
+		test_fail "wrong content in stored mail ";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in stored mail";
+	}
+
+	if not test_message :smtp 0 {
+		test_fail "message not redirected";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in redirected mail";
+	}
+
+	if not exists "x-some-header" {
+		test_fail "header not in redirected mail";
+	}
+
+	if not header :is "x-some-header" "Header content" {
+		test_fail "wrong content in redirected mail ";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in redirected mail";
+	}
+}
+
+test_result_reset;
+test_set "message" "${message}";
+test "Addheader - last (two)" {
+	if size :over 76 {
+		test_fail "original message is longer than 76 bytes?!";
+	}
+
+	addheader :last "X-Some-Header" "Header content";
+	addheader "X-Some-Other-Header" "More header content";
+
+	if not size :over 76 {
+		test_fail "mail is not larger";
+	}
+
+	if size :over 149 {
+		test_fail "mail is too large";
+	}
+
+	if size :under 149 {
+		test_fail "mail is too small";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained";
+	}
+
+	if not exists "x-some-header" {
+		test_fail "header #1 not added";
+	}
+
+	if not header :is "x-some-header" "Header content" {
+		test_fail "wrong content added #1";
+	}
+
+	if not exists "x-some-other-header" {
+		test_fail "header #2 not added";
+	}
+
+	if not header :is "x-some-other-header" "More header content" {
+		test_fail "wrong content added #2";
+	}
+
+	redirect "frop@example.com";
+	fileinto :create "folder4";
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	if not test_message :folder "folder4" 0 {
+		test_fail "message not stored";
+	}
+
+	if not size :over 76 {
+		test_fail "stored mail is not larger";
+	}
+
+	if size :over 149 {
+		test_fail "stored mail is too large";
+	}
+
+	if size :under 100 {
+		test_fail "stored mail is too small";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in stored mail";
+	}
+
+	if not exists "x-some-header" {
+		test_fail "header #1 not in stored mail";
+	}
+
+	if not header :is "x-some-header" "Header content" {
+		test_fail "wrong content #1 in stored mail";
+	}
+
+	if not exists "x-some-other-header" {
+		test_fail "header #2 not in stored mail";
+	}
+
+	if not header :is "x-some-other-header" "More header content" {
+		test_fail "wrong content #2 in stored mail";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in stored mail";
+	}
+
+	if not test_message :smtp 0 {
+		test_fail "message not redirected";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in redirected mail";
+	}
+
+	if not exists "x-some-header" {
+		test_fail "header #1 not in redirected mail";
+	}
+
+	if not header :is "x-some-header" "Header content" {
+		test_fail "wrong content #1 in redirected mail ";
+	}
+
+	if not exists "x-some-other-header" {
+		test_fail "header #2 not in redirected mail";
+	}
+
+	if not header :is "x-some-other-header" "More header content" {
+		test_fail "wrong content #2 in redirected mail";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in redirected mail";
+	}
+}
+
+test_result_reset;
+test_set "message" "${message}";
+test "Addheader - framed" {
+	if size :over 76 {
+		test_fail "original message is longer than 76 bytes?!";
+	}
+
+	addheader "X-Some-Header-first" "Header content first";
+	addheader :last "X-Some-Header-last" "Header content last";
+
+	if not size :over 76 {
+		test_fail "mail is not larger";
+	}
+
+	if size :over 160 {
+		test_fail "mail is too large";
+	}
+
+	if size :under 160 {
+		test_fail "mail is too small";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained";
+	}
+
+	if not exists "x-some-header-first" {
+		test_fail "first header not added";
+	}
+
+	if not exists "x-some-header-last" {
+		test_fail "last header not added";
+	}
+
+	if not header :is "x-some-header-first" "Header content first" {
+		test_fail "wrong first content added";
+	}
+
+	if not header :is "x-some-header-last" "Header content last" {
+		test_fail "wrong last content added";
+	}
+
+	redirect "frop@example.com";
+	fileinto :create "folder5";
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	if not test_message :folder "folder5" 0 {
+		test_fail "message not stored";
+	}
+
+	if not size :over 76 {
+		test_fail "stored mail is not larger";
+	}
+
+	if size :over 160 {
+		test_fail "stored mail is too large";
+	}
+
+	if size :under 152 {
+		test_fail "stored mail is too small";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in stored mail";
+	}
+
+	if not exists "x-some-header-first" {
+		test_fail "first header not in stored mail";
+	}
+
+	if not exists "x-some-header-last" {
+		test_fail "last header not in stored mail";
+	}
+
+	if not header :is "x-some-header-first" "Header content first" {
+		test_fail "wrong first header content in stored mail ";
+	}
+
+	if not header :is "x-some-header-last" "Header content last" {
+		test_fail "wrong last header content in stored mail ";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in stored mail";
+	}
+
+	if not test_message :smtp 0 {
+		test_fail "message not redirected";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in redirected mail";
+	}
+
+	if not exists "x-some-header-first" {
+		test_fail "first header not in redirected mail";
+	}
+
+	if not exists "x-some-header-last" {
+		test_fail "last header not in redirected mail";
+	}
+
+	if not header :is "x-some-header-first" "Header content first" {
+		test_fail "wrong first header content in redirected mail ";
+	}
+
+	if not header :is "x-some-header-last" "Header content last" {
+		test_fail "wrong last header content in redirected mail ";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in redirected mail";
+	}
+}
+
+/*
+ * Addheader - folded
+ */
+
+test_result_reset;
+test_set "message" "${message}";
+test "Addheader - folded" {
+	set "before"
+		"This is very long header content, folded to fit inside multiple header lines. This may cause problems, so that is why it is tested here.";
+	set "after"
+		"This is somewhat longer header content, folded to fit inside multiple header lines. This may cause problems, so that is why it is tested here.";
+
+	addheader :last "X-Some-Header-first" "${before}";
+	addheader :last "X-Some-Header-last" "${after}";
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained";
+	}
+
+	if not exists "x-some-header-first" {
+		test_fail "first header not added";
+	}
+
+	if not exists "x-some-header-last" {
+		test_fail "last header not added";
+	}
+
+	if not header :is "x-some-header-first" "${before}" {
+		test_fail "wrong first content added";
+	}
+
+	if not header :is "x-some-header-last" "${after}" {
+		test_fail "wrong last content added";
+	}
+
+	redirect "frop@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	if not test_message :smtp 0 {
+		test_fail "message not redirected";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in redirected mail";
+	}
+
+	if not exists "x-some-header-first" {
+		test_fail "first header not in redirected mail";
+	}
+
+	if not exists "x-some-header-last" {
+		test_fail "last header not in redirected mail";
+	}
+
+	if not header :is "x-some-header-first" "${before}" {
+		test_fail "wrong first header content in redirected mail ";
+	}
+
+	if not header :is "x-some-header-last" "${after}" {
+		test_fail "wrong last header content in redirected mail ";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in redirected mail";
+	}
+}
+
+/*
+ * Addheader - newlines
+ */
+
+test_result_reset;
+test_set "message" "${message}";
+test "Addheader - newlines" {
+	set "before" text:
+This is very long header content
+ containing newlines. This may
+ cause some problems, so that
+ is why it is tested here.
+.
+;
+
+	set "after" text:
+This is somewhat longer header content
+ containing newlines. This may
+ cause some problems, so that
+ is why it is tested here.
+.
+;
+
+	set "before_out"
+		"This is very long header content containing newlines. This may cause some problems, so that is why it is tested here.";
+
+	set "after_out"
+		"This is somewhat longer header content containing newlines. This may cause some problems, so that is why it is tested here.";
+
+	addheader "X-Some-Header-first" "${before}";
+	addheader :last "X-Some-Header-last" "${after}";
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained";
+	}
+
+	if not exists "x-some-header-first" {
+		test_fail "first header not added";
+	}
+
+	if not exists "x-some-header-last" {
+		test_fail "last header not added";
+	}
+
+	if not header :is "x-some-header-first" "${before_out}" {
+		if header :matches "x-some-header-first" "*" {}
+		test_fail "wrong first content added: `${0}`";
+	}
+
+	if not header :is "x-some-header-last" "${after_out}" {
+		if header :matches "x-some-header-last" "*" {}
+		test_fail "wrong last content added: `${0}`";
+	}
+
+	redirect "frop@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	if not test_message :smtp 0 {
+		test_fail "message not redirected";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in redirected mail";
+	}
+
+	if not exists "x-some-header-first" {
+		test_fail "first header not in redirected mail";
+	}
+
+	if not exists "x-some-header-last" {
+		test_fail "last header not in redirected mail";
+	}
+
+	if not header :is "x-some-header-first" "${before_out}" {
+		if header :matches "x-some-header-first" "*" {}
+		test_fail "wrong first header content in redirected mail: `${0}`";
+	}
+
+	if not header :is "x-some-header-last" "${after_out}" {
+		if header :matches "x-some-header-last" "*" {}
+		test_fail "wrong last header content in redirected mail: `${0}`";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in redirected mail";
+	}
+}
+
+test_result_reset;
+test_set "message" "${message}";
+test "Addheader - implicit keep" {
+	if size :over 76 {
+		test_fail "original message is longer than 76 bytes?!";
+	}
+
+	addheader "X-Some-Header" "Header content";
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	if not test_message :folder "INBOX" 0 {
+		test_fail "message not stored";
+	}
+
+	if not size :over 76 {
+		test_fail "stored mail is not larger";
+	}
+
+	if size :over 107 {
+		test_fail "stored mail is too large";
+	}
+
+	if size :under 100 {
+		test_fail "stored mail is too small";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in stored message";
+	}
+
+	if not exists "x-some-header" {
+		test_fail "header not added to stored message";
+	}
+
+	if not header :is "x-some-header" "Header content" {
+		test_fail "wrong content added to stored message";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in stored mail";
+	}
+}
+
+test_set "message" "${message}";
+test "Addheader - UTF 8" {
+	if size :over 76 {
+		test_fail "original message is longer than 76 bytes?!";
+	}
+
+	addheader "X-Some-Header" "Это тест!";
+	fileinto :create "folder6";
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	if not test_message :folder "folder6" 0 {
+		test_fail "message not stored";
+	}
+
+	if not exists "x-some-header" {
+		test_fail "header not added to stored message";
+	}
+
+	if not header :is "x-some-header" "Это тест!" {
+		if header :matches "x-some-header" "*" {}
+		test_fail "Bel character not retained: `${0}`";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in stored mail";
+	}
+}
+
+test_result_reset;
+
+test_set "message" "${message}";
+test "Addheader - devious characters" {
+	if size :over 76 {
+		test_fail "original message is longer than 76 bytes?!";
+	}
+
+	addheader "X-Some-Header" "Ring my ${hex:07}!";
+	fileinto :create "folder7";
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	if not test_message :folder "folder7" 0 {
+		test_fail "message not stored";
+	}
+
+	if not exists "x-some-header" {
+		test_fail "header not added to stored message";
+	}
+
+	if header :is "x-some-header" "Ring my !" {
+		if header :matches "x-some-header" "*" {}
+		test_fail "Bel character not retained: `${0}`";
+	}
+
+	if not header :is "x-some-header" "Ring my ${hex:07}!" {
+		if header :matches "x-some-header" "*" {}
+		test_fail "Incorrect header value: `${0}`";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in stored mail";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/editheader/alternating.svtest
@@ -0,0 +1,181 @@
+require "vnd.dovecot.testsuite";
+require "variables";
+require "fileinto";
+require "mailbox";
+require "body";
+
+require "editheader";
+
+set "message" text:
+From: stephan@example.com
+To: timo@example.com
+Subject: Frop!
+
+Frop!
+
+.
+;
+
+
+test_set "message" "${message}";
+test "Alternating - add; delete" {
+	addheader "X-Some-Header" "Header content";
+
+	if not exists "x-some-header" {
+		test_fail "header not added";
+	}
+
+	if not header :is "x-some-header" "Header content" {
+		test_fail "wrong content added";
+	}
+
+	redirect "frop@example.com";
+
+	deleteheader "X-Some-Header";
+
+	if exists "x-some-header" {
+		test_fail "header not deleted";
+	}
+
+	fileinto :create "folder1";
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	/* redirected message */
+
+	if not test_message :smtp 0 {
+		test_fail "message not redirected";
+	}
+
+	if not exists "x-some-header" {
+		test_fail "added header not in redirected mail";
+	}
+
+	if not header :is "x-some-header" "Header content" {
+		test_fail "wrong content in redirected mail ";
+	}
+
+	/* stored message message */
+
+	if not test_message :folder "folder1" 0 {
+		test_fail "message not stored";
+	}
+
+	if exists "x-some-header" {
+		test_fail "added header still present stored mail";
+	}
+}
+
+test_result_reset;
+
+test_set "message" "${message}";
+test "Alternating - delete; add" {
+	deleteheader "Subject";
+
+	if exists "subject" {
+		test_fail "header not deleted";
+	}
+
+	redirect "frop@example.com";
+
+	addheader "Subject" "Friep!";
+
+	if not exists "subject" {
+		test_fail "header not added";
+	}
+
+	if not header :is "subject" "Friep!" {
+		test_fail "wrong content added";
+	}
+
+	fileinto :create "folder2";
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	/* redirected message */
+
+	if not test_message :smtp 0 {
+		test_fail "message not redirected";
+	}
+
+	if exists "subject" {
+		test_fail "deleted header still present redirected mail";
+	}
+
+	/* stored message message */
+
+	if not test_message :folder "folder2" 0 {
+		test_fail "message not stored";
+	}
+
+	if not exists "subject" {
+		test_fail "added header not in stored mail";
+	}
+
+	if not header :is "subject" "Friep!" {
+		test_fail "wrong content in redirected mail ";
+	}
+}
+
+test_result_reset;
+
+test_set "message" "${message}";
+test "Alternating - add :last; delete any" {
+	addheader :last "X-Some-Header" "Header content";
+
+	if not exists "x-some-header" {
+		test_fail "header not added";
+	}
+
+	if not header :is "x-some-header" "Header content" {
+		test_fail "wrong content added";
+	}
+
+	redirect "frop@example.com";
+
+	deleteheader "X-Some-Other-Header";
+
+	if not exists "x-some-header" {
+		test_fail "header somehow deleted";
+	}
+
+	fileinto :create "folder3";
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	/* redirected message */
+
+	if not test_message :smtp 0 {
+		test_fail "message not redirected";
+	}
+
+	if not exists "x-some-header" {
+		test_fail "added header not in redirected mail";
+	}
+
+	if not header :is "x-some-header" "Header content" {
+		test_fail "wrong content in redirected mail ";
+	}
+
+	/* stored message message */
+
+	if not test_message :folder "folder3" 0 {
+		test_fail "message not stored";
+	}
+
+	if not exists "x-some-header" {
+		test_fail "added header lost in stored mail";
+	}
+
+	if not header :is "x-some-header" "Header content" {
+		test_fail "wrong content in stored mail ";
+	}
+
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/editheader/deleteheader.svtest
@@ -0,0 +1,1115 @@
+require "vnd.dovecot.testsuite";
+require "variables";
+require "fileinto";
+require "mailbox";
+require "body";
+
+require "editheader";
+
+set "message" text:
+X-A: Onzinnige informatie
+X-B: kun je maar beter
+X-C: niet via e-mail versturen
+From: stephan@example.com
+X-D: en daarom is het nuttig
+To: timo@example.com
+Subject: Frop!
+X-A: dit terstond te verwijderen,
+X-B: omdat dit anders
+X-C: alleen maar schijfruimte verspilt.
+
+Frop!
+
+.
+;
+
+test_set "message" "${message}";
+test "Deleteheader - nonexistent" {
+	if size :over 288 {
+		test_fail "original message is longer than 288 bytes?!";
+	}
+
+	if size :under 288 {
+		test_fail "original message is shorter than 288 bytes?!";
+	}
+
+	deleteheader "X-Z";
+
+	if size :under 288 {
+		test_fail "message is shorter than original";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained";
+	}
+
+	if not header :is "X-B" "omdat dit anders" {
+		test_fail "original X-B header not retained";
+	}
+
+	if not header :is "X-C" "niet via e-mail versturen" {
+		test_fail "original X-C header not retained";
+	}
+
+	redirect "frop@example.com";
+	fileinto :create "folder1";
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	if not test_message :folder "folder1" 0 {
+		test_fail "message not stored";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in stored mail";
+	}
+
+	if not header :is "X-B" "omdat dit anders" {
+		test_fail "original X-B header not retained in stored mail";
+	}
+
+	if not header :is "X-C" "niet via e-mail versturen" {
+		test_fail "original X-C header not retained in stored mail";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in stored mail";
+	}
+
+	if not test_message :smtp 0 {
+		test_fail "message not redirected";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in redirected mail";
+	}
+
+	if not header :is "X-B" "omdat dit anders" {
+		test_fail "original X-B header not retained in redirected mail";
+	}
+
+	if not header :is "X-C" "niet via e-mail versturen" {
+		test_fail "original X-C header not retained in redirected mail";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in redirected mail";
+	}
+}
+
+test_set "message" "${message}";
+test "Deleteheader - nonexistent (match)" {
+	if size :over 288 {
+		test_fail "original message is longer than 288 bytes?!";
+	}
+
+	if size :under 288 {
+		test_fail "original message is shorter than 288 bytes?!";
+	}
+
+	deleteheader :matches "X-Z" "*frop*";
+
+	if size :under 288 {
+		test_fail "message is shorter than original";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained";
+	}
+
+	if not header :is "X-B" "omdat dit anders" {
+		test_fail "original X-B header not retained";
+	}
+
+	if not header :is "X-C" "niet via e-mail versturen" {
+		test_fail "original X-C header not retained";
+	}
+
+	redirect "frop@example.com";
+	fileinto :create "folder1b";
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	if not test_message :folder "folder1b" 0 {
+		test_fail "message not stored";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in stored mail";
+	}
+
+	if not header :is "X-B" "omdat dit anders" {
+		test_fail "original X-B header not retained in stored mail";
+	}
+
+	if not header :is "X-C" "niet via e-mail versturen" {
+		test_fail "original X-C header not retained in stored mail";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in stored mail";
+	}
+
+	if not test_message :smtp 0 {
+		test_fail "message not redirected";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in redirected mail";
+	}
+
+	if not header :is "X-B" "omdat dit anders" {
+		test_fail "original X-B header not retained in redirected mail";
+	}
+
+	if not header :is "X-C" "niet via e-mail versturen" {
+		test_fail "original X-C header not retained in redirected mail";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in redirected mail";
+	}
+}
+
+test_result_reset;
+test_set "message" "${message}";
+test "Deleteheader - one" {
+	if size :over 288 {
+		test_fail "original message is longer than 288 bytes?!";
+	}
+
+	if size :under 288 {
+		test_fail "original message is shorter than 288 bytes?!";
+	}
+
+	deleteheader "X-D";
+
+	if not size :under 288 {
+		test_fail "edited message is not shorter";
+	}
+
+	if size :over 258 {
+		test_fail "edited message is too long";
+	}
+
+	if size :under 258 {
+		test_fail "edited message is too short";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained";
+	}
+
+	if not header :is "X-B" "omdat dit anders" {
+		test_fail "original X-B header not retained";
+	}
+
+	if not header :is "X-C" "niet via e-mail versturen" {
+		test_fail "original X-C header not retained";
+	}
+
+	if exists "X-D" {
+		test_fail "X-D header not deleted";
+	}
+
+	redirect "frop@example.com";
+	fileinto :create "folder2";
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	if not test_message :folder "folder2" 0 {
+		test_fail "message not stored";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in stored mail";
+	}
+
+	if not header :is "X-B" "omdat dit anders" {
+		test_fail "original X-B header not retained in stored mail";
+	}
+
+	if not header :is "X-C" "niet via e-mail versturen" {
+		test_fail "original X-C header not retained in stored mail";
+	}
+
+	if exists "X-D" {
+		test_fail "X-D header not deleted in stored mail";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in stored mail";
+	}
+
+	if not test_message :smtp 0 {
+		test_fail "message not redirected";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in redirected mail";
+	}
+
+	if not header :is "X-B" "omdat dit anders" {
+		test_fail "original X-B header not retained in redirected mail";
+	}
+
+	if not header :is "X-C" "niet via e-mail versturen" {
+		test_fail "original X-C header not retained in redirected mail";
+	}
+
+	if exists "X-D" {
+		test_fail "X-D header not deleted in redirected mail";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in redirected mail";
+	}
+}
+
+test_result_reset;
+test_set "message" "${message}";
+test "Deleteheader - two (first)" {
+	if size :over 288 {
+		test_fail "original message is longer than 288 bytes?!";
+	}
+
+	if size :under 288 {
+		test_fail "original message is shorter than 288 bytes?!";
+	}
+
+	deleteheader "X-A";
+
+	if not size :under 288 {
+		test_fail "edited message is not shorter";
+	}
+
+	if size :over 226 {
+		test_fail "edited message is too long";
+	}
+
+	if size :under 226 {
+		test_fail "edited message is too short";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained";
+	}
+
+	if not header :is "X-B" "omdat dit anders" {
+		test_fail "original X-B header not retained";
+	}
+
+	if not header :is "X-C" "niet via e-mail versturen" {
+		test_fail "original X-C header not retained";
+	}
+
+	if exists "X-A" {
+		test_fail "X-A header not deleted";
+	}
+
+	redirect "frop@example.com";
+	fileinto :create "folder3";
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	if not test_message :folder "folder3" 0 {
+		test_fail "message not stored";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in stored mail";
+	}
+
+	if not header :is "X-B" "omdat dit anders" {
+		test_fail "original X-B header not retained in stored mail";
+	}
+
+	if not header :is "X-C" "niet via e-mail versturen" {
+		test_fail "original X-C header not retained in stored mail";
+	}
+
+	if exists "X-A" {
+		test_fail "X-A header not deleted in stored mail";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in stored mail";
+	}
+
+	if not test_message :smtp 0 {
+		test_fail "message not redirected";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in redirected mail";
+	}
+
+	if not header :is "X-B" "omdat dit anders" {
+		test_fail "original X-B header not retained in redirected mail";
+	}
+
+	if not header :is "X-C" "niet via e-mail versturen" {
+		test_fail "original X-C header not retained in redirected mail";
+	}
+
+	if exists "X-A" {
+		test_fail "X-A header not deleted in redirected mail";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in redirected mail";
+	}
+}
+
+test_result_reset;
+test_set "message" "${message}";
+test "Deleteheader - two (last)" {
+	if size :over 288 {
+		test_fail "original message is longer than 288 bytes?!";
+	}
+
+	if size :under 288 {
+		test_fail "original message is shorter than 288 bytes?!";
+	}
+
+	deleteheader "X-C";
+
+	if not size :under 288 {
+		test_fail "edited message is not shorter";
+	}
+
+	if size :over 215 {
+		test_fail "edited message is too long";
+	}
+
+	if size :under 215 {
+		test_fail "edited message is too short";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained";
+	}
+
+	if not header :is "X-A" "dit terstond te verwijderen," {
+		test_fail "original X-A header not retained";
+	}
+
+	if not header :is "X-B" "omdat dit anders" {
+		test_fail "original X-B header not retained";
+	}
+
+	if exists "X-C" {
+		test_fail "X-C header not deleted";
+	}
+
+	redirect "frop@example.com";
+	fileinto :create "folder4";
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	if not test_message :folder "folder4" 0 {
+		test_fail "message not stored";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in stored mail";
+	}
+
+	if not header :is "X-A" "dit terstond te verwijderen," {
+		test_fail "original X-A header not retained in stored mail";
+	}
+
+	if not header :is "X-B" "omdat dit anders" {
+		test_fail "original X-B header not retained in stored mail";
+	}
+
+	if exists "X-C" {
+		test_fail "X-C header not deleted in stored mail";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in stored mail";
+	}
+
+	if not test_message :smtp 0 {
+		test_fail "message not redirected";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in redirected mail";
+	}
+
+	if not header :is "X-A" "dit terstond te verwijderen," {
+		test_fail "original X-A header not retained in redirected mail";
+	}
+
+	if not header :is "X-B" "omdat dit anders" {
+		test_fail "original X-B header not retained in redirected mail";
+	}
+
+	if exists "X-C" {
+		test_fail "X-C header not deleted in redirected mail";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in redirected mail";
+	}
+}
+
+test_result_reset;
+test_set "message" "${message}";
+test "Deleteheader - :index" {
+	if size :over 288 {
+		test_fail "original message is longer than 288 bytes?!";
+	}
+
+	if size :under 288 {
+		test_fail "original message is shorter than 288 bytes?!";
+	}
+
+	deleteheader :index 1 "X-A";
+	deleteheader :index 2 "X-C";
+
+	if not size :under 288 {
+		test_fail "edited message is not shorter";
+	}
+
+	if size :over 220 {
+		test_fail "edited message is too long";
+	}
+
+	if size :under 220 {
+		test_fail "edited message is too short";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained";
+	}
+
+	if not header :is "X-A" "dit terstond te verwijderen," {
+		test_fail "original X-A (2) header not retained";
+	}
+
+	if not header :is "X-C" "niet via e-mail versturen" {
+		test_fail "original X-C (1) header not retained";
+	}
+
+	if header :is "X-A" "Onzinnige informatie" {
+		test_fail "original X-A (1) header not deleted";
+	}
+
+	if header :is "X-C" "alleen maar schijfruimte verspilt." {
+		test_fail "original X-C (2) header not deleted";
+	}
+
+	redirect "frop@example.com";
+	fileinto :create "folder5";
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	if not test_message :folder "folder5" 0 {
+		test_fail "message not stored";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in stored mail";
+	}
+
+	if not header :is "X-A" "dit terstond te verwijderen," {
+		test_fail "original X-A (2) header not retained in stored mail";
+	}
+
+	if not header :is "X-C" "niet via e-mail versturen" {
+		test_fail "original X-C (1) header not retained in stored mail";
+	}
+
+	if header :is "X-A" "Onzinnige informatie" {
+		test_fail "original X-A (1) header not deleted in stored mail";
+	}
+
+	if header :is "X-C" "alleen maar schijfruimte verspilt." {
+		test_fail "original X-C (2) header not deleted in stored mail";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in stored mail";
+	}
+
+	if not test_message :smtp 0 {
+		test_fail "message not redirected";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in redirected mail";
+	}
+
+	if not header :is "X-A" "dit terstond te verwijderen," {
+		test_fail "original X-A (2) header not retained redirected mail";
+	}
+
+	if not header :is "X-C" "niet via e-mail versturen" {
+		test_fail "original X-B (1) header not retained redirected mail";
+	}
+
+	if header :is "X-A" "Onzinnige informatie" {
+		test_fail "original X-A (1) header not deleted redirected mail";
+	}
+
+	if header :is "X-C" "alleen maar schijfruimte verspilt." {
+		test_fail "original X-B (2) header not deleted redirected mail";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in redirected mail";
+	}
+}
+
+test_result_reset;
+test_set "message" "${message}";
+test "Deleteheader - :index :last" {
+	if size :over 288 {
+		test_fail "original message is longer than 288 bytes?!";
+	}
+
+	if size :under 288 {
+		test_fail "original message is shorter than 288 bytes?!";
+	}
+
+	deleteheader :index 1 :last "X-A";
+	deleteheader :last :index 2 "X-C";
+
+	if size :over 221 {
+		test_fail "edited message is too long";
+	}
+
+	if size :under 221 {
+		test_fail "edited message is too short";
+	}
+
+	if not size :under 288 {
+		test_fail "edited message is not shorter";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained";
+	}
+
+	if not header :is "X-A" "Onzinnige informatie" {
+		test_fail "original X-A (1) header not retained";
+	}
+
+	if not header :is "X-C" "alleen maar schijfruimte verspilt." {
+		test_fail "original X-C (2) header not retained";
+	}
+
+	if header :is "X-A" "dit terstond te verwijderen," {
+		test_fail "original X-A (2) header not deleted";
+	}
+
+	if header :is "X-C" "niet via e-mail versturen" {
+		test_fail "original X-C (1) header not deleted";
+	}
+
+	redirect "frop@example.com";
+	fileinto :create "folder6";
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	if not test_message :folder "folder6" 0 {
+		test_fail "message not stored";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in stored mail";
+	}
+
+	if not header :is "X-A" "Onzinnige informatie" {
+		test_fail "original X-A (1) header not retained in stored mail";
+	}
+
+	if not header :is "X-C" "alleen maar schijfruimte verspilt." {
+		test_fail "original X-C (2) header not retained in stored mail";
+	}
+
+	if header :is "X-A" "dit terstond te verwijderen," {
+		test_fail "original X-A (2) header not deleted in stored mail";
+	}
+
+	if header :is "X-C" "niet via e-mail versturen" {
+		test_fail "original X-C (1) header not deleted in stored mail";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in stored mail";
+	}
+
+	if not test_message :smtp 0 {
+		test_fail "message not redirected";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in redirected mail";
+	}
+
+	if header :is "X-A" "dit terstond te verwijderen," {
+		test_fail "original X-A (2) header not deleted redirected mail";
+	}
+
+	if header :is "X-C" "niet via e-mail versturen" {
+		test_fail "original X-B (1) header not deleted redirected mail";
+	}
+
+	if not header :is "X-A" "Onzinnige informatie" {
+		test_fail "original X-A (1) header not retained redirected mail";
+	}
+
+	if not header :is "X-C" "alleen maar schijfruimte verspilt." {
+		test_fail "original X-B (2) header not retained redirected mail";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in redirected mail";
+	}
+}
+
+test_result_reset;
+test_set "message" "${message}";
+test "Deleteheader - implicit keep" {
+	deleteheader "X-D";
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	if not test_message :folder "INBOX" 0 {
+		test_fail "message not stored";
+	}
+
+	if not header :is "subject" "Frop!" {
+		test_fail "original subject header not retained in stored mail";
+	}
+
+	if not header :is "X-B" "omdat dit anders" {
+		test_fail "original X-B header not retained in stored mail";
+	}
+
+	if not header :is "X-C" "niet via e-mail versturen" {
+		test_fail "original X-C header not retained in stored mail";
+	}
+
+	if exists "X-D" {
+		test_fail "X-D header not deleted in stored mail";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in stored mail";
+	}
+}
+
+/*
+ *
+ */
+
+test_result_reset;
+
+test_set "message" text:
+X-A: Dit is een klein verhaaltje
+X-B: om te testen of de correcte
+X-C: informatie wordt herkend en
+X-D: verwijderd. Zo valt goed te
+X-A: zien dat het allemaal werkt
+X-B: zoals het bedoeld is. Alles
+X-C: wordt in een keer getest op
+X-D: een wijze die efficient die
+X-A: problemen naar voren brengt
+X-B: die bij dit nieuwe deel van
+X-C: de programmatuur naar voren
+X-D: kunnen komen. Zo werkt het!
+
+Frop!
+.
+;
+
+test "Deleteheader - :matches" {
+	if size :over 417 {
+		test_fail "original message is longer than 417 bytes?!";
+	}
+
+	if size :under 417 {
+		test_fail "original message is shorter than 417 bytes?!";
+	}
+
+	deleteheader :matches "X-A" "*klein*";
+	deleteheader :matches "X-B" "*bedoeld*";
+	deleteheader :matches "X-C" "*programmatuur*";
+	deleteheader :contains "X-D" ["verwijderd", "!"];
+
+	if not size :under 417 {
+		test_fail "edited message is not shorter";
+	}
+
+	if size :over 247 {
+		test_fail "edited message is too long";
+	}
+
+	if size :under 247 {
+		test_fail "edited message is too short";
+	}
+
+	if not header :is "X-A" "zien dat het allemaal werkt" {
+		test_fail "original X-A (2) header not retained";
+	}
+
+	if not header :is "X-A" "problemen naar voren brengt" {
+		test_fail "original X-A (3) header not retained";
+	}
+
+	if not header :is "X-B" "om te testen of de correcte" {
+		test_fail "original X-B (1) header not retained";
+	}
+
+	if not header :is "X-B" "die bij dit nieuwe deel van" {
+		test_fail "original X-B (3) header not retained";
+	}
+
+	if not header :is "X-C" "informatie wordt herkend en" {
+		test_fail "original X-C (1) header not retained";
+	}
+
+	if not header :is "X-C" "wordt in een keer getest op" {
+		test_fail "original X-C (2) header not retained";
+	}
+
+	if not header :is "X-D" "een wijze die efficient die" {
+		test_fail "original X-C (2) header not retained";
+	}
+
+	if header :is "X-A" "Dit is een klein verhaaltje" {
+		test_fail "original X-A (1) header not deleted";
+	}
+
+	if header :is "X-B" "zoals het bedoeld is. Alles" {
+		test_fail "original X-B (2) header not deleted";
+	}
+
+	if header :is "X-C" "de programmatuur naar voren" {
+		test_fail "original X-C (3) header not deleted";
+	}
+
+	if header :is "X-D" "verwijderd. Zo valt goed te" {
+		test_fail "original X-C (1) header not deleted";
+	}
+
+	if header :is "X-D" "kunnen komen. Zo werkt het!" {
+		test_fail "original X-C (3) header not deleted";
+	}
+
+	redirect "frop@example.com";
+	fileinto :create "folder7";
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	if not test_message :folder "folder7" 0 {
+		test_fail "message not stored";
+	}
+
+	if not header :is "X-A" "zien dat het allemaal werkt" {
+		test_fail "original X-A (2) header not retained in stored mail";
+	}
+
+	if not header :is "X-A" "problemen naar voren brengt" {
+		test_fail "original X-A (3) header not retained in stored mail";
+	}
+
+	if not header :is "X-B" "om te testen of de correcte" {
+		test_fail "original X-B (1) header not retained in stored mail";
+	}
+
+	if not header :is "X-B" "die bij dit nieuwe deel van" {
+		test_fail "original X-B (3) header not retained in stored mail";
+	}
+
+	if not header :is "X-C" "informatie wordt herkend en" {
+		test_fail "original X-C (1) header not retained in stored mail";
+	}
+
+	if not header :is "X-C" "wordt in een keer getest op" {
+		test_fail "original X-C (2) header not retained in stored mail";
+	}
+
+	if not header :is "X-D" "een wijze die efficient die" {
+		test_fail "original X-C (2) header not retained in stored mail";
+	}
+
+	if header :is "X-A" "Dit is een klein verhaaltje" {
+		test_fail "original X-A (1) header not deleted in stored mail";
+	}
+
+	if header :is "X-B" "zoals het bedoeld is. Alles" {
+		test_fail "original X-B (2) header not deleted in stored mail";
+	}
+
+	if header :is "X-C" "de programmatuur naar voren" {
+		test_fail "original X-C (3) header not deleted in stored mail";
+	}
+
+	if header :is "X-D" "verwijderd. Zo valt goed te" {
+		test_fail "original X-C (1) header not deleted in stored mail";
+	}
+
+	if header :is "X-D" "kunnen komen. Zo werkt het!" {
+		test_fail "original X-C (3) header not deleted in stored mail";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in stored mail";
+	}
+
+	if not test_message :smtp 0 {
+		test_fail "message not redirected";
+	}
+
+	if not header :is "X-A" "zien dat het allemaal werkt" {
+		test_fail "original X-A (2) header not retained in redirected mail";
+	}
+
+	if not header :is "X-A" "problemen naar voren brengt" {
+		test_fail "original X-A (3) header not retained in redirected mail";
+	}
+
+	if not header :is "X-B" "om te testen of de correcte" {
+		test_fail "original X-B (1) header not retained in redirected mail";
+	}
+
+	if not header :is "X-B" "die bij dit nieuwe deel van" {
+		test_fail "original X-B (3) header not retained in redirected mail";
+	}
+
+	if not header :is "X-C" "informatie wordt herkend en" {
+		test_fail "original X-C (1) header not retained in redirected mail";
+	}
+
+	if not header :is "X-C" "wordt in een keer getest op" {
+		test_fail "original X-C (2) header not retained in redirected mail";
+	}
+
+	if not header :is "X-D" "een wijze die efficient die" {
+		test_fail "original X-C (2) header not retained in redirected mail";
+	}
+
+	if header :is "X-A" "Dit is een klein verhaaltje" {
+		test_fail "original X-A (1) header not deleted in redirected mail";
+	}
+
+	if header :is "X-B" "zoals het bedoeld is. Alles" {
+		test_fail "original X-B (2) header not deleted in redirected mail";
+	}
+
+	if header :is "X-C" "de programmatuur naar voren" {
+		test_fail "original X-C (3) header not deleted in redirected mail";
+	}
+
+	if header :is "X-D" "verwijderd. Zo valt goed te" {
+		test_fail "original X-C (1) header not deleted in redirected mail";
+	}
+
+	if header :is "X-D" "kunnen komen. Zo werkt het!" {
+		test_fail "original X-C (3) header not deleted in redirected mail";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in redirected mail";
+	}
+}
+
+
+/*
+ *
+ */
+
+set "message2" text:
+X-A: Long folded header to test removal of folded
+ headers from a message. This is the top header.
+X-B: First intermittent unfolded header
+X-A: Long folded header to test removal of folded
+ headers from a message. This is the middle header.
+X-B: Second intermittent unfolded header
+X-A: Long folded header to test removal of folded
+ headers from a message. This is the bottom header,
+ which concludes the header of this message.
+
+Frop!
+.
+;
+
+test_result_reset;
+test_set "message" "${message2}";
+test "Deleteheader - folded" {
+	deleteheader "X-A";
+
+	if exists "X-A" {
+		test_fail "original X-A (1) header not deleted";
+	}
+
+	if not header :is "X-B" "First intermittent unfolded header" {
+		test_fail "original X-B (2) header not retained";
+	}
+
+	if not header :is "X-B" "Second intermittent unfolded header" {
+		test_fail "original X-B (2) header not retained";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in redirected mail";
+	}
+
+	redirect "frop@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	if not test_message :smtp 0 {
+		test_fail "message not redirected";
+	}
+
+	if exists "X-A" {
+		test_fail "original X-A (1) header not deleted in redirected mail";
+	}
+
+	if not header :is "X-B" "First intermittent unfolded header" {
+		test_fail "original X-B (2) header not retained in redirected mail";
+	}
+
+	if not header :is "X-B" "Second intermittent unfolded header" {
+		test_fail "original X-B (2) header not retained in redirected mail";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in redirected mail";
+	}
+}
+
+test_result_reset;
+test_set "message" "${message2}";
+test "Deleteheader - folded (match)" {
+	deleteheader :matches "X-A" "*header*";
+
+	if exists "X-A" {
+		test_fail "original X-A (1) header not deleted";
+	}
+
+	if not header :is "X-B" "First intermittent unfolded header" {
+		test_fail "original X-B (2) header not retained";
+	}
+
+	if not header :is "X-B" "Second intermittent unfolded header" {
+		test_fail "original X-B (2) header not retained";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in redirected mail";
+	}
+
+	redirect "frop@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	if not test_message :smtp 0 {
+		test_fail "message not redirected";
+	}
+
+	if exists "X-A" {
+		test_fail "original X-A (1) header not deleted in redirected mail";
+	}
+
+	if not header :is "X-B" "First intermittent unfolded header" {
+		test_fail "original X-B (2) header not retained in redirected mail";
+	}
+
+	if not header :is "X-B" "Second intermittent unfolded header" {
+		test_fail "original X-B (2) header not retained in redirected mail";
+	}
+
+	if not body :matches "Frop!*" {
+		test_fail "body not retained in redirected mail";
+	}
+}
+
+
+/*
+ * TEST: Ignoring whitespace
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.com
+Subject:         Help
+X-A:     Text
+X-B: Text
+
+Text
+.
+;
+
+test "Ignoring whitespace" {
+	deleteheader :is "subject" "Help";
+	deleteheader :is "x-a" "Text";
+	deleteheader :is "x-b" "Text";
+
+	if exists "subject" {
+		test_fail "subject header not deleted";
+	}
+
+	if exists "x-a" {
+		test_fail "x-a header not deleted";
+	}
+
+	if exists "x-b" {
+		test_fail "x-b header not deleted";
+	}
+}
+
+/*
+ * TEST: Interaction with body test
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.com
+Subject: Hoppa
+
+Text
+.
+;
+
+test "Interaction with body test" {
+	addheader "X-Frop" "frop";
+	
+	if body "!TEST!" {}
+
+	deleteheader "subject";
+
+	if exists "subject" {
+		test_fail "subject header not deleted";
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/editheader/errors.svtest
@@ -0,0 +1,164 @@
+require "vnd.dovecot.testsuite";
+require "comparator-i;ascii-numeric";
+require "relational";
+require "variables";
+
+require "editheader";
+
+test "Invalid field-name" {
+	if test_script_compile "errors/field-name.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "5" {
+		test_fail "wrong number of errors reported";
+	}
+
+	if not test_error :index 1 :matches "*field name*X-field:*invalid*" {
+		test_fail "wrong error reported (1)";
+	}
+
+	if not test_error :index 2 :matches "*field name*X field*invalid*" {
+		test_fail "wrong error reported (2)";
+	}
+
+	if not test_error :index 3 :matches "*field name*X-field:*invalid*" {
+		test_fail "wrong error reported (3)";
+	}
+
+	if not test_error :index 4 :matches "*field name*X field*invalid*" {
+		test_fail "wrong error reported (4)";
+	}
+}
+
+test "Invalid field-name at runtime " {
+	if not test_script_compile "errors/field-name-runtime.sieve" {
+		test_fail "compile failed";
+	}
+
+	if test_script_run {
+		test_fail "run should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "1" {
+		test_fail "wrong number of errors reported";
+	}
+
+	if not test_error :matches "*field name*X-field:*invalid*" {
+		test_fail "wrong error reported";
+	}
+}
+
+test "Invalid field value" {
+	if test_script_compile "errors/field-value.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "2" {
+		test_fail "wrong number of errors reported";
+	}
+
+	if not test_error :index 1 :matches "*value*Woah*invalid*" {
+		test_fail "wrong error reported (1): ${0}";
+	}
+}
+
+test "Command syntax (FIXME: count only)" {
+	if test_script_compile "errors/command-syntax.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "10" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+/*
+ * TEST - Size limit
+ */
+
+test "Size limit" {
+	if not test_script_compile "errors/size-limit.sieve" {
+		test_fail "compile should have succeeded";
+	}
+
+	test_config_set "sieve_editheader_max_header_size" "1024";
+	test_config_reload :extension "editheader";
+
+	if test_script_compile "errors/size-limit.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "2" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+
+/*
+ * TEST - Size limit at runtime
+ */
+
+test_config_set "sieve_editheader_max_header_size" "";
+test_config_reload :extension "editheader";
+
+test "Size limit at runtime" {
+	if not test_script_compile "errors/size-limit-runtime.sieve" {
+		test_fail "compile should have succeeded";
+	}
+
+	if not test_script_run {
+		test_fail "run failed";
+	}
+
+	test_config_set "sieve_editheader_max_header_size" "1024";
+	test_config_reload :extension "editheader";
+
+	if not test_script_compile "errors/size-limit-runtime.sieve" {
+		test_fail "compile should have succeeded";
+	}
+
+	if test_script_run {
+		test_fail "run should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "1" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+/*
+ * TEST - Implicit keep at runtime error
+ */
+
+test_set "message" text:
+From: stephan@example.com
+To: tss@example.com
+Subject: Frop
+
+Frop!
+.
+;
+
+test "Implicit keep at runtime error" {
+	if not test_script_compile "errors/runtime-error.sieve" {
+		test_fail "compile failed";
+	}
+
+	if not test_script_run {
+		test_fail "run failed";
+	}
+
+	if test_result_execute {
+		test_fail "result execution should have failed";
+	}
+
+	if not test_message :folder "INBOX" 0 {
+		test_fail "message not stored (no implicit keep)";
+	}
+
+	if exists "X-Frop" {
+		test_fail "implicit keep message has editheader changes";
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/editheader/errors/command-syntax.sieve
@@ -0,0 +1,42 @@
+require "editheader";
+
+/* "addheader" [":last"] <field-name: string> <value: string>
+ */
+
+# 1: missing field name and value
+addheader;
+
+# 2: missing value
+addheader "x-frop";
+
+# 3: value not a string; number
+addheader "x-frop" 2;
+
+# 4: value not a string; list
+addheader "x-frop" ["frop"];
+
+# 5: strange tag
+addheader :tag "x-frop" "frop";
+
+/* "deleteheader" [":index" <fieldno: number> [":last"]]
+ *                  [COMPARATOR] [MATCH-TYPE]
+ *                  <field-name: string>
+ *                  [<value-patterns: string-list>]
+ */
+
+# 6: missing field name
+deleteheader;
+
+# 7: :last tag without index
+deleteheader :last "x-frop";
+
+# 8: :index tag with string argument
+deleteheader :index "frop" "x-frop";
+
+# OK: match type without value patterns
+deleteheader :matches "x-frop";
+
+# 9: value patterns not a string(list)
+deleteheader "x-frop" 1;
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/editheader/errors/field-name-runtime.sieve
@@ -0,0 +1,6 @@
+require "editheader";
+require "variables";
+
+set "header" "X-field:";
+
+addheader "${header}" "Frop";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/editheader/errors/field-name.sieve
@@ -0,0 +1,19 @@
+require "editheader";
+
+# Ok
+addheader "X-field" "Frop";
+
+# Invalid ':'
+addheader "X-field:" "Frop";
+
+# Invalid ' '
+addheader "X field" "Frop";
+
+# Ok
+deleteheader "X-field";
+
+# Invalid ':'
+deleteheader "X-field:";
+
+# Invalid ' '
+deleteheader "X field";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/editheader/errors/field-value.sieve
@@ -0,0 +1,15 @@
+require "editheader";
+require "encoded-character";
+
+# Ok
+addheader "X-field" "Frop";
+
+# Ok
+addheader "X-field" "Frop
+Frml";
+
+# Invalid 'BELL'; but not an error
+addheader "X-field" "Yeah${hex:07}!";
+
+# Invalid 'NUL'
+addheader "X-field" "Woah${hex:00}!";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/editheader/errors/runtime-error.sieve
@@ -0,0 +1,6 @@
+require "editheader";
+require "fileinto";
+
+addheader "X-Frop" "Friep";
+
+fileinto "Rediculous.non-existent.folder";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/editheader/errors/size-limit-runtime.sieve
@@ -0,0 +1,46 @@
+require "editheader";
+require "variables";
+
+set "blob" text:
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+.
+;
+
+addheader "x-frop" "${blob}";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/editheader/errors/size-limit.sieve
@@ -0,0 +1,43 @@
+require "editheader";
+
+addheader "x-frop" text:
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+.
+;
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/editheader/execute.svtest
@@ -0,0 +1,45 @@
+require "vnd.dovecot.testsuite";
+require "include";
+require "variables";
+
+/*
+ * Multi script
+ */
+
+test_set "message" text:
+From: idiot@example.com
+To: idiot@example.org
+Subject: Frop!
+
+Frop.
+.
+;
+
+test_result_reset;
+test "Multi script" {
+	if not test_multiscript [
+		"execute/multiscript-before.sieve",
+		"execute/multiscript-personal.sieve",
+		"execute/multiscript-after.sieve"
+	] {
+		test_fail "failed to run all scripts";
+	}
+
+	test_message :folder "INBOX" 0;
+
+	if not header "subject" "Frop!" {
+		test_fail "keep not executed.";
+	}
+
+	if not header "X-Before" "before" {
+		test_fail "No X-Before header";
+	}
+
+	if not header "X-Personal" "personal" {
+		test_fail "No X-Personal header";
+	}
+
+	if not header "X-After" "after" {
+		test_fail "No X-After header";
+	}	
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/editheader/execute/multiscript-after.sieve
@@ -0,0 +1,4 @@
+require "editheader";
+
+addheader "X-After" "after";
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/editheader/execute/multiscript-before.sieve
@@ -0,0 +1,4 @@
+require "editheader";
+
+addheader "X-Before" "before";
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/editheader/execute/multiscript-personal.sieve
@@ -0,0 +1,4 @@
+require "editheader";
+
+addheader "X-Personal" "personal";
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/editheader/protected.svtest
@@ -0,0 +1,150 @@
+require "vnd.dovecot.testsuite";
+
+require "variables";
+
+require "editheader";
+
+set "message" text:
+Received: by example.com (Postfix, from userid 202)
+	id 32A131WFW23QWE4; Mon, 21 Nov 2011 05:25:26 +0200 (EET)
+Delivery-date: Mon, 21 Nov 2011 04:26:04 +0100
+Auto-Submitted: yes
+X-Friep: frop 3
+Subject: Frop!
+From: stephan@example.com
+To: tss@example.com
+
+Frop!
+.
+;
+
+test_set "message" "${message}";
+test "Default protected" {
+	if not exists "received" {
+		test_fail "received header did not exist in the first place";
+	}
+
+	if not exists "auto-submitted" {
+		test_fail "auto-submitted header did not exist in the first place";
+	}
+
+	deleteheader "received";
+	deleteheader "auto-submitted";
+	deleteheader "subject";
+
+	if not exists "received" {
+		test_fail "protected received header was deleted";
+	}
+
+	if not exists "auto-submitted" {
+		test_fail "protected auto-submitted header was deleted";
+	}
+
+	if exists "subject" {
+		test_fail "subject header cannot be protected, but it was not deleted";
+	}
+}
+
+test_config_set "sieve_editheader_protected" "subject delivery-date x-frop";
+test_config_reload :extension "editheader";
+
+test_set "message" "${message}";
+test "Configured protected" {
+	if not exists "delivery-date" {
+		test_fail "received header did not exist in the first place";
+	}
+
+	if not exists "subject" {
+		test_fail "received header did not exist in the first place";
+	}
+
+	if exists "x-frop" {
+		test_fail "x-frop header already present";
+	}
+
+	deleteheader "delivery-date";
+	deleteheader "subject";
+	addheader "x-frop" "Frop!";
+
+	if not exists "delivery-date" {
+		test_fail "protected delivery-date header was deleted";
+	}
+
+	if exists "subject" {
+		test_fail "subject header cannot be protected, but it was not deleted";
+	}
+
+	if exists "x-frop" {
+		test_fail "protected x-frop header was added";
+	}
+}
+
+test_config_set "sieve_editheader_protected" "";
+test_config_set "sieve_editheader_forbid_add" "subject x-frop";
+test_config_set "sieve_editheader_forbid_delete" "subject x-friep";
+test_config_reload :extension "editheader";
+
+test_set "message" "${message}";
+test "Configured forbid_add/forbid_delete" {
+	if not exists "delivery-date" {
+		test_fail "received header did not exist in the first place";
+	}
+
+	if not exists "subject" {
+		test_fail "received header did not exist in the first place";
+	}
+
+	if not exists "x-friep" {
+		test_fail "x-friep header did not exist in the first place";
+	}
+
+	if exists "x-frop" {
+		test_fail "x-frop header already present";
+	}
+
+	deleteheader "delivery-date";
+	deleteheader "subject";
+	deleteheader "x-friep";
+
+	if exists "delivery-date" {
+		test_fail "unprotected delivery-date header was not deleted";
+	}
+
+	if exists "subject" {
+		test_fail "subject header cannot be protected, but it was not deleted";
+	}
+
+	if not exists "x-friep" {
+		test_fail "protected x-friep header was deleted";
+	}
+
+	addheader "delivery-date" "Yesterday";
+	addheader "subject" "Fropfrop!";
+	addheader "x-frop" "Frop!";
+	addheader "received" text:
+by sieve.example.com (My little Sieve script)
+id 3jhl22khhf23f; Mon, 24 Aug 2015 04:11:54 -0600;
+.
+;
+	addheader "auto-submitted" "no way";
+
+	if not header "delivery-date" "Yesterday" {
+		test_fail "unprotected delivery-date header was not added";
+	}
+
+	if not header "subject" "Fropfrop!" {
+		test_fail "subject header cannot be protected, but it was not added";
+	}
+
+	if exists "x-frop" {
+		test_fail "protected x-frop header was added";
+	}
+
+	if not header :contains "received" "sieve.example.com" {
+		test_fail "received header was not added";
+	}
+
+	if not header "auto-submitted" "no way" {
+		test_fail "autosubmitted header was not added";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/editheader/utf8.svtest
@@ -0,0 +1,97 @@
+require "vnd.dovecot.testsuite";
+
+require "encoded-character";
+require "variables";
+require "editheader";
+
+test_set "message" text:
+Subject: Frop!
+From: stephan@example.com
+To: stephan@example.com
+
+Frop!
+.
+;
+
+test "UTF8 - add; get" {
+	set "comment" "Ein unerh${unicode:00F6}rt gro${unicode:00DF}er Test";
+
+	addheader "Comment" "${comment}";
+
+	if not exists "comment" {
+		test_fail "header not added";
+	}
+
+	if not header :is "comment" "${comment}" {
+		test_fail "wrong content added/retrieved";
+	}
+
+	redirect "frop@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	/* redirected message */
+
+	if not test_message :smtp 0 {
+		test_fail "message not redirected";
+	}
+
+	if not exists "comment" {
+		test_fail "header not added in redirected mail";
+	}
+
+	if not header :is "comment" "${comment}" {
+		test_fail "wrong content added/retrieved from redirected mail";
+	}
+}
+
+test_result_reset;
+
+test_set "message" text:
+Subject: Frop!
+Comment: Ein =?utf-8?q?unerh=C3=B6rt_gro=C3=9Fer?= Test
+X-Spam: no
+From: stephan@example.com
+To: stephan@example.com
+
+Frop!
+.
+;
+
+test "UTF8 - existing; delete other; get" {
+	set "comment" "Ein unerh${unicode:00F6}rt gro${unicode:00DF}er Test";
+
+	deleteheader "x-spam";
+
+	if not exists "comment" {
+		test_fail "header not present";
+	}
+
+	if not header :is "comment" "${comment}" {
+		test_fail "wrong content retrieved";
+	}
+
+	redirect "frop@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	/* redirected message */
+
+	if not test_message :smtp 0 {
+		test_fail "message not redirected";
+	}
+
+	if not exists "comment" {
+		test_fail "header not present in redirected mail";
+	}
+
+	if not header :is "comment" "${comment}" {
+		test_fail "wrong content retrieved from redirected mail";
+	}
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/encoded-character.svtest
@@ -0,0 +1,180 @@
+require "vnd.dovecot.testsuite";
+
+require "encoded-character";
+require "variables";
+
+test "HEX equality one" {
+	if not string "${hex:42}" "B" {
+		test_fail "failed to match the string 'B'";
+	}
+
+	if string "${hex:42}" "b" {
+		test_fail "matched nonsense";
+	}
+
+	if string "${hex:42}" "" {
+		test_fail "substitution failed";
+	}
+}
+
+test "HEX equality one middle" {
+	if not string " ${hex:42} " " B " {
+		test_fail "failed to match the string ' B '";
+	}
+
+	if string " ${hex:42} " " b " {
+		test_fail "matched nonsense";
+	}
+
+	if string " ${hex:42} " "  " {
+		test_fail "substitution failed";
+	}
+}
+
+test "HEX equality one begin" {
+	if not string "${hex:42} " "B " {
+		test_fail "failed to match the string 'B '";
+	}
+
+	if string "${hex:42} " " b" {
+		test_fail "matched nonsense";
+	}
+
+	if string "${hex:42} " " " {
+		test_fail "substitution failed";
+	}
+}
+
+test "HEX equality one end" {
+	if not string " ${hex:42}" " B" {
+		test_fail "failed to match the string ' B'";
+	}
+
+	if string " ${hex:42}" " b " {
+		test_fail "matched nonsense";
+	}
+
+	if string " ${hex:42}" " " {
+		test_fail "substitution failed";
+	}
+}
+
+test "HEX equality two triple" {
+	if not string "${hex:42 61 64}${hex: 61 73 73}" "Badass" {
+		test_fail "failed to match the string 'Badass'";
+	}
+
+	if string "${hex:42 61 64}${hex: 61 73 73}" "Sadass" {
+		test_fail "matched nonsense";
+	}
+
+	if string "${hex:42 61 64}${hex: 61 73 73}" "" {
+		test_fail "substitution failed";
+	}
+}
+
+test "HEX equality braindead" {
+	if not string "${hex:42 72 61 69 6E 64 65 61 64}" "Braindead" {
+		test_fail "failed to match the string 'Braindead'";
+	}
+
+	if string "${hex:42 72 61 69 6E 64 65 61 64}" "Brian Nut" {
+		test_fail "matched nonsense";
+	}
+}
+
+test "Syntax errors" {
+	if anyof( not string "$" "${hex:24}", not string "$ " "${hex:24} ", not string " $" " ${hex:24}" ) {
+		test_fail "loose $ handled inappropriately";
+	}
+
+	if anyof( not string "${" "${hex:24}{", not string "a${" "a${hex:24}{", not string "${a" "${hex:24}{a" ) {
+		test_fail "loose ${ handled inappropriately";
+	}
+
+	if anyof( not string "${}" "${hex:24}{}", not string "b${}" "b${hex:24}{}", not string "${}b" "${hex:24}{}b" ) {
+		test_fail "entirely missing content handled inappropriately";
+	}
+
+	if not string "${:}" "${hex:24}{:}" {
+		test_fail "missing content handled inappropriately";
+	}
+
+	if not string "${hex:}" "${hex:24}{hex:}" {
+		test_fail "missing hex content handled inappropriately";
+	}
+
+	if not string "${unicode:}" "${hex:24}{unicode:}" {
+		test_fail "missing unicode content handled inappropriately";
+	}
+
+	if not string "${hex:sss}" "${hex:24}{hex:sss}" {
+		test_fail "erroneous hex content handled inappropriately";
+	}
+
+	if not string "${unicode:ttt}" "${hex:24}{unicode:ttt}" {
+		test_fail "erroneous unicode content handled inappropriately";
+	}
+
+	if not string "${hex:aa aa" "${hex:24}{hex:aa aa" {
+		test_fail "unterminated hex content handled inappropriately";
+	}
+
+	if not string "${unicode: aaaa aaaa" "${hex:24}{unicode: aaaa aaaa" {
+		test_fail "unterminated unicode content handled inappropriately";
+	}
+}
+
+/*
+ * RFC Examples
+ */
+
+test "RFC Examples" {
+	if not string "$${hex:40}" "$@" {
+		test_fail "failed RFC example 1";
+	}
+
+	if not string "${hex: 40 }" "@" {
+		test_fail "failed RFC example 2";
+	}
+
+	if not string "${HEX: 40}" "@" {
+		test_fail "failed RFC example 3";
+	}
+
+	if not string "${hex:40" "${hex:40" {
+		test_fail "failed RFC example 4";
+	}
+
+	if not string "${hex:400}" "${hex:400}" {
+		test_fail "failed RFC example 5";
+	}
+
+	if not string "${hex:4${hex:30}}" "${hex: 24}{hex:40}" {
+		test_fail "failed RFC example 6";
+	}
+
+	if not string "${unicode:40}" "@" {
+		test_fail "failed RFC example 7";
+	}
+
+	if not string "${ unicode:40}" "${ unicode:40}" {
+		test_fail "failed RFC example 8";
+	}
+
+	if not string "${UNICODE:40}" "@" {
+		test_fail "failed RFC example 9";
+	}
+
+	if not string "${UnICoDE:0000040}" "@" {
+		test_fail "failed RFC example 10";
+	}
+
+	if not string "${Unicode:40}" "@" {
+		test_fail "failed RFC example 11";
+	}
+
+	if not string "${Unicode:Cool}" "${Unicode:Cool}" {
+		test_fail "failed RFC example 12";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/enotify/basic.svtest
@@ -0,0 +1,15 @@
+require "vnd.dovecot.testsuite";
+require "enotify";
+
+test "Execute" {
+	/* Test to catch runtime segfaults */
+	if valid_notify_method
+		"mailto:stephan@example.com" {
+
+		/* Test to catch runtime segfaults */
+		notify
+			:message "This is probably very important"
+			:importance "1"
+			"mailto:stephan@example.com%2cstephan@example.org?subject=Important%20message%20received";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/enotify/encodeurl.svtest
@@ -0,0 +1,11 @@
+require "vnd.dovecot.testsuite";
+require "variables";
+require "enotify";
+
+test "Encode Simple" {
+	set :encodeurl "url_data" "\\frop\\&fruts/^@";
+
+	if not string :is :comparator "i;octet" "${url_data}" "%5Cfrop%5C%26fruts%2F%5E%40" {
+		test_fail "url data encoded incorrectly '${url_data}'";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/enotify/errors.svtest
@@ -0,0 +1,45 @@
+require "vnd.dovecot.testsuite";
+require "comparator-i;ascii-numeric";
+require "relational";
+
+require "enotify";
+
+test "Invalid URI (FIXME: count only)" {
+	if test_script_compile "errors/uri.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "2" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+test "Invalid mailto URI (FIXME: count only)" {
+	if test_script_compile "errors/uri-mailto.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "7" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+test "Invalid mailto :from address (FIXME: count only)" {
+	if test_script_compile "errors/from-mailto.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "3" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+test "Invalid :options argument (FIXME: count only)" {
+	if test_script_compile "errors/options.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "6" {
+		test_fail "wrong number of errors reported";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/enotify/errors/from-mailto.sieve
@@ -0,0 +1,7 @@
+require "enotify";
+
+# 1: Invalid from address
+notify :from "stephan#example.org" "mailto:stephan@example.com";
+
+# 2: Empty from address
+notify :from "" "mailto:stephan@example.com";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/enotify/errors/options.sieve
@@ -0,0 +1,18 @@
+require "enotify";
+
+# 1: empty option
+notify :options "" "mailto:stephan@example.org";
+
+# 2: invalid option name syntax
+notify :options "frop" "mailto:stephan@example.org";
+
+# 3: invalid option name syntax
+notify :options "_frop=" "mailto:stephan@example.org";
+
+# 4: invalid option name syntax
+notify :options "=frop" "mailto:stephan@example.org";
+
+# 5: invalid value
+notify :options "frop=frml
+frop" "mailto:stephan@example.org";
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/enotify/errors/uri-mailto.sieve
@@ -0,0 +1,20 @@
+require "enotify";
+
+# 1: Invalid character in to part
+notify "mailto:stephan@example.org;?header=frop";
+
+# 2: Invalid character in hname
+notify "mailto:stephan@example.org?header<=frop";
+
+# 3: Invalid character in hvalue
+notify "mailto:stephan@example.org?header=fr>op";
+
+# 4: Invalid header name
+notify "mailto:stephan@example.org?header:=frop";
+
+# 5: Invalid recipient
+notify "mailto:stephan%23example.org";
+
+# 6: Invalid to header recipient
+notify "mailto:stephan@example.org?to=nico%23frop.example.org";
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/enotify/errors/uri.sieve
@@ -0,0 +1,5 @@
+require "enotify";
+
+# 1: Invalid url scheme
+notify "snailto:stephan@example.org";
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/enotify/execute.svtest
@@ -0,0 +1,99 @@
+require "vnd.dovecot.testsuite";
+require "relational";
+
+
+/*
+ * Execution testing (currently just meant to trigger any segfaults)
+ */
+
+test "RFC Example 1" {
+	if not test_script_compile "execute/draft-rfc-ex1.sieve" {
+		test_fail "script compile failed";
+	}
+
+	if not test_script_run {
+		test_fail "script run failed";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed";
+	}
+}
+
+test "RFC Example 2" {
+	if not test_script_compile "execute/draft-rfc-ex2.sieve" {
+		test_fail "script compile failed";
+	}
+
+	if not test_script_run {
+		test_fail "script execute failed";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed";
+	}
+}
+
+/* tel: not supported
+test "RFC Example 3" {
+	if not test_script_compile "execute/draft-rfc-ex3.sieve" {
+		test_fail "script compile failed";
+	}
+
+	if not test_script_run {
+		test_fail "script execute failed";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed";
+	}
+}
+*/
+
+/* tel: and xmmp: not supported
+test "RFC Example 5" {
+	if not test_script_compile "execute/draft-rfc-ex5.sieve" {
+		test_fail "script compile failed";
+	}
+
+	if not test_script_run {
+		test_fail "script execute failed";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed";
+	}
+}
+*/
+
+test "RFC Example 6" {
+	if not test_script_compile "execute/draft-rfc-ex6.sieve" {
+		test_fail "script compile failed";
+	}
+
+	if not test_script_run {
+		test_fail "script execute failed";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed";
+	}
+}
+
+test "Duplicate recipients" {
+	if not test_script_compile "execute/duplicates.sieve" {
+		test_fail "script compile failed";
+	}
+
+	if not test_script_run {
+		test_fail "script execute failed";
+	}
+
+	if test_result_action :count "ne" "2" {
+		test_fail "second notify action was discarded entirely";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/enotify/execute/draft-rfc-ex1.sieve
@@ -0,0 +1,26 @@
+require ["enotify", "fileinto", "variables"];
+
+if header :contains "from" "boss@example.org" {
+	notify :importance "1"
+		:message "This is probably very important"
+		"mailto:alm@example.com";
+	# Don't send any further notifications
+	stop;
+}
+
+if header :contains "to" "sievemailinglist@example.org" {
+	# :matches is used to get the value of the Subject header
+	if header :matches "Subject" "*" {
+		set "subject" "${1}";
+	}
+
+	# :matches is used to get the value of the From header
+	if header :matches "From" "*" {
+		set "from" "${1}";
+	}
+
+	notify :importance "3"
+		:message "[SIEVE] ${from}: ${subject}"
+		"mailto:alm@example.com";
+	fileinto "INBOX.sieve";
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/enotify/execute/draft-rfc-ex2.sieve
@@ -0,0 +1,22 @@
+require ["enotify", "fileinto", "variables", "envelope"];
+
+if header :matches "from" "*@*.example.org" {
+	# :matches is used to get the MAIL FROM address
+	if envelope :all :matches "from" "*" {
+		set "env_from" " [really: ${1}]";
+	}
+
+	# :matches is used to get the value of the Subject header
+	if header :matches "Subject" "*" {
+		set "subject" "${1}";
+	}
+
+	# :matches is used to get the address from the From header
+	if address :matches :all "from" "*" {
+		set "from_addr" "${1}";
+	}
+
+	notify :message "${from_addr}${env_from}: ${subject}"
+		"mailto:alm@example.com";
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/enotify/execute/draft-rfc-ex3.sieve
@@ -0,0 +1,31 @@
+require ["enotify", "variables"];
+
+set "notif_method"
+	"xmpp:tim@example.com?message;subject=SIEVE;body=You%20got%20mail";
+
+if header :contains "subject" "Your dog" {
+	set "notif_method" "tel:+14085551212";
+}
+
+if header :contains "to" "sievemailinglist@example.org" {
+	set "notif_method" "";
+}
+
+if not string :is "${notif_method}" "" {
+	notify "${notif_method}";
+}
+
+if header :contains "from" "boss@example.org" {
+	# :matches is used to get the value of the Subject header
+	if header :matches "Subject" "*" {
+		set "subject" "${1}";
+	}
+
+	# don't need high importance notification for
+	# a 'for your information'
+	if not header :contains "subject" "FYI:" {
+		notify :importance "1" :message "BOSS: ${subject}"
+			"tel:+14085551212";
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/enotify/execute/draft-rfc-ex5.sieve
@@ -0,0 +1,11 @@
+require ["enotify"];
+
+if notify_method_capability
+	"xmpp:tim@example.com?message;subject=SIEVE"
+	"Online"
+	"yes" {
+	notify :importance "1" :message "You got mail"
+		"xmpp:tim@example.com?message;subject=SIEVE";
+} else {
+	notify :message "You got mail" "tel:+14085551212";
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/enotify/execute/draft-rfc-ex6.sieve
@@ -0,0 +1,5 @@
+require ["enotify", "variables"];
+
+set :encodeurl "body_param" "Safe body&evil=evilbody";
+
+notify "mailto:tim@example.com?body=${body_param}";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/enotify/execute/duplicates.sieve
@@ -0,0 +1,4 @@
+require "enotify";
+
+notify :message "Incoming stupidity." "mailto:stephan@example.org%2cstephan@friep.example.com%2cidiot@example.org";
+notify :message "There it is." "mailto:tss@example.net%2cstephan@example.org%2cidiot@example.org%2cnico@frop.example.org%2cstephan@friep.example.com";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/enotify/mailto.svtest
@@ -0,0 +1,541 @@
+require "vnd.dovecot.testsuite";
+require "enotify";
+require "relational";
+require "envelope";
+require "variables";
+require "comparator-i;ascii-numeric";
+
+/*
+ * Simple test
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test "Simple" {
+	notify "mailto:stephan@example.org";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not header :matches "Auto-Submitted" "auto-notified*" {
+		test_fail "auto-submitted header set inappropriately";
+	}
+
+	if not exists "X-Sieve" {
+		test_fail "x-sieve header missing from outgoing message";
+	}
+
+	if anyof (
+		not header :matches "x-priority" "3 *",
+		not header "importance" "normal") {
+
+		test_fail "default priority is not normal";
+	}
+}
+
+/*
+ * Multiple recipients
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test "Multiple recipients" {
+	notify "mailto:timo@example.com%2cstephan@dovecot.example.net?cc=postmaster@frop.example.org&subject=Frop%20received";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not address :is "to" "timo@example.com" {
+		test_fail "first To address missing";
+	}
+
+	if not address :is "to" "stephan@dovecot.example.net" {
+		test_fail "second To address missing";
+	}
+
+	if not address :is "cc" "postmaster@frop.example.org" {
+		test_fail "first Cc address missing";
+	}
+
+	if not address :count "eq" :comparator "i;ascii-numeric" "to" "2" {
+		test_fail "too many recipients in To header";
+	}
+
+	if not address :count "eq" :comparator "i;ascii-numeric" "cc" "1" {
+		test_fail "too many recipients in Cc header";
+	}
+
+	if not header "subject" "Frop received" {
+		test_fail "subject header set incorrectly";
+	}
+
+	test_message :smtp 1;
+
+	if not header :matches "Auto-Submitted" "auto-notified*" {
+		test_fail "auto-submitted header not found for second message";
+	}
+
+	test_message :smtp 2;
+
+	if not header :matches "Auto-Submitted" "auto-notified*" {
+		test_fail "auto-submitted header not found for third message";
+	}
+}
+
+/*
+ * Duplicate recipients
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test "Duplicate recipients" {
+	notify "mailto:timo@example.com%2cstephan@dovecot.example.net?cc=stephan@dovecot.example.net";
+	notify "mailto:stephan@example.org?cc=timo@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if address "Cc" "stephan@dovecot.example.net" {
+		test_fail "duplicate recipient not removed from first message";
+	}
+
+	test_message :smtp 1;
+
+	if address "Cc" "timo@example.com" {
+		test_fail "duplicate recipient not removed from second message";
+	}
+}
+
+
+/*
+ * Notifying on automated messages
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Auto-submitted: auto-notify
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test "Notifying on automated messages" {
+	notify "mailto:stephan@example.org?cc=timo@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	if test_message :smtp 0 {
+		test_fail "notified of auto-submitted message";
+	}
+}
+
+/*
+ * Envelope
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test_result_reset;
+
+test_set "envelope.from" "sirius@example.org";
+test_set "envelope.to" "bertus@frop.example.org";
+
+test "Envelope" {
+	notify "mailto:stephan@example.org?cc=timo@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not envelope :localpart :is "from" "postmaster" {
+		test_fail "envelope sender set incorrectly";
+	}
+
+	if not envelope :is "to" "stephan@example.org" {
+		test_fail "envelope sender set incorrectly";
+	}
+
+	test_message :smtp 1;
+
+	if not envelope :localpart :is "from" "postmaster" {
+		test_fail "envelope sender set incorrectly";
+	}
+
+	if not envelope :is "to" "timo@example.com" {
+		test_fail "envelope sender set incorrectly";
+	}
+}
+
+/*
+ * Envelope :from
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test_set "envelope.from" "sirius@example.org";
+test_set "envelope.to" "bertus@frop.example.org";
+
+test_result_reset;
+
+test "Envelope :from" {
+	notify :from "nico@frop.example.org"
+		"mailto:stephan@example.org?cc=timo@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not envelope :is "from" "nico@frop.example.org" {
+		test_fail "envelope sender set incorrectly";
+	}
+
+	if not envelope :is "to" "stephan@example.org" {
+		test_fail "envelope sender set incorrectly";
+	}
+
+	test_message :smtp 1;
+
+	if not envelope :is "from" "nico@frop.example.org" {
+		test_fail "envelope sender set incorrectly";
+	}
+
+	if not envelope :is "to" "timo@example.com" {
+		test_fail "envelope sender set incorrectly";
+	}
+}
+
+/*
+ * Envelope <>
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test_set "envelope.from" "<>";
+test_set "envelope.to" "bertus@frop.example.org";
+
+test_result_reset;
+
+test "Envelope <>" {
+	notify :from "nico@frop.example.org"
+		"mailto:stephan@example.org?cc=timo@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not envelope :is "from" "" {
+		test_fail "envelope sender set incorrectly";
+	}
+
+	if not envelope :is "to" "stephan@example.org" {
+		test_fail "envelope recipient set incorrectly";
+	}
+
+	test_message :smtp 1;
+
+	if not envelope :is "from" "" {
+		test_fail "envelope sender set incorrectly";
+	}
+
+	if not envelope :is "to" "timo@example.com" {
+		test_fail "envelope recipient set incorrectly";
+	}
+}
+
+/*
+ * Envelope config - sender
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test_set "envelope.from" "sirius@example.org";
+test_set "envelope.to" "bertus@frop.example.org";
+
+test_config_set "sieve_notify_mailto_envelope_from"
+	"sender";
+test_config_reload :extension "enotify";
+test_result_reset;
+
+test "Envelope config - sender" {
+	notify :from "nico@frop.example.org"
+		"mailto:stephan@example.org?cc=timo@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not header :is "from" "nico@frop.example.org" {
+		test_fail "from set incorrectly";
+	}
+
+	if not envelope :is "from" "sirius@example.org" {
+		test_fail "envelope sender set incorrectly";
+	}
+
+	if not envelope :is "to" "stephan@example.org" {
+		test_fail "envelope recipient set incorrectly";
+	}
+
+	test_message :smtp 1;
+
+	if not header :is "from" "nico@frop.example.org" {
+		test_fail "from set incorrectly";
+	}
+
+	if not envelope :is "from" "sirius@example.org" {
+		test_fail "envelope sender set incorrectly";
+	}
+
+	if not envelope :is "to" "timo@example.com" {
+		test_fail "envelope recipient set incorrectly";
+	}
+}
+
+/*
+ * Envelope config - recipient
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test_set "envelope.from" "sirius@example.org";
+test_set "envelope.to" "bertus@frop.example.org";
+
+test_config_set "sieve_notify_mailto_envelope_from"
+	"recipient";
+test_config_reload :extension "enotify";
+test_result_reset;
+
+test "Envelope config - recipient" {
+	notify :from "nico@frop.example.org"
+		"mailto:stephan@example.org?cc=timo@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not header :is "from" "nico@frop.example.org" {
+		test_fail "from set incorrectly";
+	}
+
+	if not envelope :is "from" "bertus@frop.example.org" {
+		test_fail "envelope sender set incorrectly";
+	}
+
+	if not envelope :is "to" "stephan@example.org" {
+		test_fail "envelope recipient set incorrectly";
+	}
+
+	test_message :smtp 1;
+
+	if not header :is "from" "nico@frop.example.org" {
+		test_fail "from set incorrectly";
+	}
+
+	if not envelope :is "from" "bertus@frop.example.org" {
+		test_fail "envelope sender set incorrectly";
+	}
+
+	if not envelope :is "to" "timo@example.com" {
+		test_fail "envelope recipient set incorrectly";
+	}
+}
+
+/*
+ * Envelope config - user_email
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test_set "envelope.from" "sirius@example.org";
+test_set "envelope.to" "bertus@frop.example.org";
+
+test_config_set "sieve_notify_mailto_envelope_from"
+	"user_email";
+test_config_set "sieve_user_email" "b.wortel@example.org";
+test_config_reload;
+test_config_reload :extension "enotify";
+test_result_reset;
+
+test "Envelope config - user_email" {
+	notify :from "nico@frop.example.org"
+		"mailto:stephan@example.org?cc=timo@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not header :is "from" "nico@frop.example.org" {
+		test_fail "from set incorrectly";
+	}
+
+	if not envelope :is "from" "b.wortel@example.org" {
+		test_fail "envelope sender set incorrectly";
+	}
+
+	if not envelope :is "to" "stephan@example.org" {
+		test_fail "envelope recipient set incorrectly";
+	}
+
+	test_message :smtp 1;
+
+	if not header :is "from" "nico@frop.example.org" {
+		test_fail "from set incorrectly";
+	}
+
+	if not envelope :is "from" "b.wortel@example.org" {
+		test_fail "envelope sender set incorrectly";
+	}
+
+	if not envelope :is "to" "timo@example.com" {
+		test_fail "envelope recipient set incorrectly";
+	}
+}
+
+/*
+ * UTF-8 addresses
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test "UTF-8 address" {
+	set "to" "=?utf-8?q?G=C3=BCnther?= M. Karotte <g.m.karotte@example.com>";
+	set "cc" "Dieter T. =?utf-8?q?Stoppelr=C3=BCbe?= <d.t.stoppelruebe@example.com>";
+
+	set :encodeurl "to_enc" "${to}";
+	set :encodeurl "cc_enc" "${cc}";
+
+	notify "mailto:?to=${to_enc}&cc=${cc_enc}";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	set "expected" "Günther M. Karotte <g.m.karotte@example.com>";
+	if not header :is "to" "${expected}" {
+		if header :matches "to" "*" { set "decoded" "${1}"; }
+
+		test_fail text:
+to header is not encoded/decoded properly:
+expected: ${expected}
+decoded: ${decoded}
+.
+;
+	}
+
+	set "expected" "Dieter T. Stoppelrübe <d.t.stoppelruebe@example.com>";
+	if not header :is "cc" "${expected}" {
+		if header :matches "cc" "*" { set "decoded" "${1}"; }
+
+		test_fail text:
+to header is not encoded/decoded properly:
+expected: ${expected}
+decoded: ${decoded}
+.
+;
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/enotify/notify_method_capability.svtest
@@ -0,0 +1,12 @@
+require "vnd.dovecot.testsuite";
+require "enotify";
+
+test "Mailto" {
+	if not notify_method_capability :is "mailto:stephan@example.org" "online" "maybe" {
+		test_fail "test should have matched";
+	}
+
+	if notify_method_capability :is "mailto:stephan@example.org" "online" "yes" {
+		test_fail "test should not have matched";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/enotify/valid_notify_method.svtest
@@ -0,0 +1,31 @@
+require "vnd.dovecot.testsuite";
+
+require "enotify";
+
+test "Mailto: invalid header name" {
+	if valid_notify_method
+		"mailto:stephan@example.org?header:=frop" {
+		test_fail "invalid uri accepted";
+	}
+}
+
+test "Mailto: invalid recipient" {
+	if valid_notify_method
+		"mailto:stephan%23example.org" {
+		test_fail "invalid uri accepted";
+	}
+}
+
+test "Mailto: invalid to header recipient" {
+	if valid_notify_method
+		"mailto:stephan@example.org?to=nico%23frop.example.org" {
+		test_fail "invalid uri accepted";
+	}
+}
+
+test "Mailto: valid URI" {
+	if not valid_notify_method
+		"mailto:stephan@example.org" {
+		test_fail "valid uri denied";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/envelope.svtest
@@ -0,0 +1,244 @@
+require "vnd.dovecot.testsuite";
+
+require "envelope";
+
+/*
+ * Empty envelope addresses
+ */
+
+/* RFC 5228, Section 5.4: The null reverse-path is matched against as the empty
+ * string, regardless of the ADDRESS-PART argument specified.
+ */
+
+test "Envelope - from empty" {
+	/* Return_path: "" */
+
+	test_set "envelope.from" "";
+
+	if not envelope :all :is "from" "" {
+		test_fail "failed to (:all :is)-match a \"\" return path";
+	}
+
+	if not envelope :all :contains "from" "" {
+		test_fail "failed to (:all :contains)-match a \"\" return path";
+	}
+
+	if not envelope :domain :is "from" "" {
+		test_fail "failed to (:domain :is)-match a \"\" return path";
+	}
+
+	if not envelope :domain :contains "from" "" {
+		test_fail "failed to (:domain :contains)-match a \"\" return path";
+	}
+
+	/* Return path: <> */
+
+	test_set "envelope.from" "<>";
+
+	if not envelope :all :is "from" "" {
+		test_fail "failed to (:all :is)-match a <> return path";
+	}
+
+	if not envelope :all :contains "from" "" {
+		test_fail "failed to (:all :contains)-match a <> return path";
+	}
+
+	if not envelope :domain :is "from" "" {
+		test_fail "failed to (:domain :is)-match a <> return path";
+	}
+
+	if not envelope :domain :contains "from" "" {
+		test_fail "failed to (:domain :contains)-match a <> return path";
+	}
+
+	if envelope :all :is "from" "nico@frop.example.org" {
+		test_fail "envelope test matches nonsense";
+	}
+}
+
+/*
+ * Invalid envelope addresses
+ */
+
+test "Envelope - invalid paths" {
+	/* Return_path: "hutsefluts" */
+
+	test_set "envelope.from" "hutsefluts@";
+	test_set "envelope.to" "knurft@";
+
+	if envelope :all :is "from" "hutsefluts@" {
+		test_fail ":all address part matched syntactically incorrect reverse path";
+	}
+	if envelope :all :is "to" "knurft@" {
+		test_fail ":all address part matched syntactically incorrect forward path";
+	}
+}
+
+/*
+ * Syntax errors
+ */
+
+test "Envelope - syntax errors" {
+	/* Control */
+	test_set "envelope.from" "<stephan@example.org>";
+	if not envelope :all :is "from" "stephan@example.org" {
+		test_fail "correct control test failed";
+	}
+
+	# Duplicate <
+	test_set "envelope.from" "<<stephan@example.org>";
+	if envelope :all :is "from" "stephan@example.org" {
+		test_fail "failed to recognize syntax error (1)";
+	}
+
+	# Spurious >
+	test_set "envelope.from" "stephan@example.org>";
+	if envelope :all :is "from" "stephan@example.org" {
+		test_fail "failed to recognize syntax error (2)";
+	}
+
+	# Missing >
+	test_set "envelope.from" "<stephan@example.org";
+	if envelope :all :is "from" "stephan@example.org" {
+		test_fail "failed to recognize syntax error (3)";
+	}
+
+	# No @
+	test_set "envelope.from" "<stephan example.org>";
+	if envelope :domain :contains "from" "example" {
+		test_fail "failed to recognize syntax error (4)";
+	}
+
+	# Duplicate @
+	test_set "envelope.from" "<stephan@@example.org>";
+	if envelope :domain :contains "from" "example" {
+		test_fail "failed to recognize syntax error (5)";
+	}
+}
+
+/*
+ * Ignoring source routes
+ */
+
+test "Envelope - source route" {
+	/* Single */
+	test_set "envelope.from" "<@cola.example.org:stephan@example.org>";
+	if not envelope :localpart :is "from" "stephan" {
+		test_fail "parsing path with source route (single) failed";
+	}
+
+	/* Dual */
+	test_set "envelope.from" "<@cola.example.org,@mx.utwente.nl:stephan@example.org>";
+	if not envelope :localpart :is "from" "stephan" {
+		test_fail "parsing path with source route (dual) failed";
+	}
+
+	/* Multiple */
+	test_set "envelope.from" "<@cola.example.org,@mx.utwente.nl,@smtp.example.net:stephan@example.org>";
+	if not envelope :localpart :is "from" "stephan" {
+		test_fail "parsing path with source route (multiple) failed";
+	}
+}
+
+test "Envelope - source route errors" {
+	test_set "envelope.to" "<cola.example.org:stephan@example.org>";
+	if envelope :domain :contains "to" "" {
+		test_fail "parsing syntactically incorrect path should have failed (1)";
+	}
+
+	test_set "envelope.to" "<@.example.org:stephan@example.org>";
+	if envelope :domain :contains "to" "" {
+		test_fail "parsing syntactically incorrect path should have failed (2)";
+	}
+
+	test_set "envelope.to" "<@cola..nl:stephan@example.org>";
+	if envelope :domain :contains "to" "" {
+		test_fail "parsing syntactically incorrect path should have failed (3)";
+	}
+
+	test_set "envelope.to" "<@cola.example.orgstephan@example.org>";
+	if envelope :domain :contains "to" "" {
+		test_fail "parsing syntactically incorrect path should have failed (4)";
+	}
+
+	test_set "envelope.to" "<@cola.example.org@mx.utwente.nl:stephan@example.org>";
+	if envelope :domain :contains "to" "" {
+		test_fail "parsing syntactically incorrect path should have failed (5)";
+	}
+
+	test_set "envelope.to" "<@cola.example.org,mx.utwente.nl:stephan@example.org>";
+	if envelope :domain :contains "to" "" {
+		test_fail "parsing syntactically incorrect path should have failed (6)";
+	}
+
+	test_set "envelope.to" "<@cola.example.org,@mx.utwente.nl,stephan@example.org>";
+	if envelope :domain :contains "to" "" {
+		test_fail "parsing syntactically incorrect path should have failed (7)";
+	}
+}
+
+test "Envelope - local part only" {
+	test_set "envelope.to" "<MAILER-DAEMON>";
+	if not envelope :is "to" "MAILER-DAEMON" {
+		test_fail "failed to parse local_part only path";
+	}
+
+	test_set "envelope.to" "MAILER-DAEMON@";
+	if envelope :is "to" "MAILER-DAEMON" {
+		test_fail "parsing syntactically incorrect path with missing domain";
+	}
+
+	test_set "envelope.to" "<MAILER-DAEMON>";
+	if not envelope :is "to" "MAILER-DAEMON" {
+		test_fail "failed to parse local_part only path with angle brackets";
+	}
+}
+
+test "Envelope - Japanese localpart" {
+	test_set "envelope.to" ".japanese@example.com";
+	if not envelope :localpart :is "to" ".japanese" {
+		test_fail "failed to parse japanese local_part (1)";
+	}
+
+	test_set "envelope.to" "japanese.@example.com";
+	if not envelope :localpart :is "to" "japanese." {
+		test_fail "failed to parse japanese local_part (2)";
+	}
+
+	test_set "envelope.to" "japanese...localpart@example.com";
+	if not envelope :localpart :is "to" "japanese...localpart" {
+		test_fail "failed to parse japanese local_part (3)";
+	}
+
+	test_set "envelope.to" "..japanese...localpart..@example.com";
+	if not envelope :localpart :is "to" "..japanese...localpart.." {
+		test_fail "failed to parse japanese local_part (4)";
+	}
+}
+
+test "Envelope - Non-standard hostnames" {
+	test_set "envelope.to" "japanese@_example.com";
+	if not envelope :domain :is "to" "_example.com" {
+		test_fail "failed to parse non-standard domain (1)";
+	}
+
+	test_set "envelope.to" "japanese@ex_ample.com";
+	if not envelope :domain :is "to" "ex_ample.com" {
+		test_fail "failed to parse non-standard domain (2)";
+	}
+
+	test_set "envelope.to" "japanese@example_.com";
+	if not envelope :domain :is "to" "example_.com" {
+		test_fail "failed to parse non-standard domain (3)";
+	}
+
+	test_set "envelope.to" "japanese@-example.com";
+	if not envelope :domain :is "to" "-example.com" {
+		test_fail "failed to parse non-standard domain (4)";
+	}
+
+	test_set "envelope.to" "japanese@example-.com";
+	if not envelope :domain :is "to" "example-.com" {
+		test_fail "failed to parse non-standard domain (5)";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/environment/basic.svtest
@@ -0,0 +1,33 @@
+require "vnd.dovecot.testsuite";
+require "environment";
+require "variables";
+
+test "Name" {
+	if not environment :contains "name" "pigeonhole" {
+		if environment :matches "name" "*" { set "env_name" "${1}"; }
+
+		test_fail "name environment returned invalid value(1): ${env_name}";
+	}
+
+	if not environment :contains "name" "sieve" {
+		if environment :matches "name" "*" { set "env_name" "${1}"; }
+
+		test_fail "name environment returned invalid value(2): ${env_name}";
+	}
+
+	if environment :contains "name" "cyrus" {
+		test_fail "something is definitely wrong here";
+	}
+
+	if not environment :is :comparator "i;octet" "name" "Pigeonhole Sieve" {
+		test_fail "name environment does not match exactly with what is expected";
+	}
+}
+
+test "Location" {
+	if not environment "location" "MS" {
+		test_fail "wrong testsuite environment location";
+	}
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/environment/rfc.svtest
@@ -0,0 +1,28 @@
+require "vnd.dovecot.testsuite";
+require "environment";
+require "relational";
+
+test "Non-existent" {
+	if environment :contains "nonsense" "" {
+		test_fail "matched unknown environment item";
+	}
+}
+
+test "Exists" {
+	if not environment :contains "version" "" {
+		test_fail "failed to match known environment item";
+	}
+}
+
+test "Count" {
+	if anyof (
+			environment :count "eq" "nonsense" "0",
+			environment :count "eq" "nonsense" "1"
+		) {
+		test_fail "count should not match unknown environment item";
+	}
+
+	if not environment :count "eq" "location" "1" {
+		test_fail "count of non-empty environment should be 1";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/ihave/errors.svtest
@@ -0,0 +1,19 @@
+require "vnd.dovecot.testsuite";
+
+require "relational";
+require "comparator-i;ascii-numeric";
+
+test "Error command" {
+	if not test_script_compile "errors/error.sieve" {
+		test_fail "compile failed";
+	}
+
+	if test_script_run {
+		test_fail "execution should have failed";
+	}
+
+	if test_error :count "gt" :comparator "i;ascii-numeric" "1" {
+		test_fail "too many runtime errors reported";
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/ihave/errors/error.sieve
@@ -0,0 +1,3 @@
+require "ihave";
+
+error "Something failed.";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/ihave/execute.svtest
@@ -0,0 +1,23 @@
+require "vnd.dovecot.testsuite";
+
+/*
+ * Execution testing (currently just meant to trigger any segfaults)
+ */
+
+test "Basic" {
+	if not test_script_compile "execute/ihave.sieve" {
+		test_fail "script compile failed";
+	}
+
+	if not test_script_run {
+		test_fail "script run failed";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed";
+	}
+
+        test_binary_save "ihave-basic";
+        test_binary_load "ihave-basic";
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/ihave/execute/ihave.sieve
@@ -0,0 +1,7 @@
+require "ihave";
+
+if ihave "nonsense-extension" {
+	nonsense_command "Frop!";
+}
+
+redirect "frop@example.com";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/ihave/restrictions.svtest
@@ -0,0 +1,14 @@
+require "vnd.dovecot.testsuite";
+require "ihave";
+
+test "Restricted: encoded-character" {
+	if ihave "encoded-character" {
+		test_fail "encoded-character extension is incompatible with ihave";
+	}
+}
+
+test "Restricted: variables" {
+	if ihave "variables" {
+		test_fail "variables extension is incompatible with ihave";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/imap4flags/basic.svtest
@@ -0,0 +1,332 @@
+require "vnd.dovecot.testsuite";
+
+require "imap4flags";
+require "relational";
+require "variables";
+require "comparator-i;ascii-numeric";
+
+/*
+ * Basic functionality tests
+ */
+
+test "Hasflag empty" {
+	if hasflag "\\Seen" {
+		test_fail "hasflag sees initial \\seen flag were there should be none";
+	}
+	if hasflag "\\draft" {
+		test_fail "hasflag sees initial \\draft flag were there should be none";
+	}
+	if hasflag "\\recent" {
+		test_fail "hasflag sees initial \\recent flag were there should be none";
+	}
+	if hasflag "\\flagged" {
+		test_fail "hasflag sees initial \\flagged flag were there should be none";
+	}
+	if hasflag "\\answered" {
+		test_fail "hasflag sees initial \\answered flag were there should be none";
+	}
+	if hasflag "\\deleted" {
+		test_fail "hasflag sees initial \\deleted flag were there should be none";
+	}
+
+	if hasflag :comparator "i;ascii-numeric" :count "ge" "1" {
+		test_fail "hasflag sees initial flags were there should be none";
+	}
+}
+
+test "Setflag; Hasflag one" {
+	setflag "\\seen";
+
+	if not hasflag "\\Seen" {
+		test_fail "flag not set of hasflag fails to see it";
+	}
+
+	if not hasflag :comparator "i;ascii-numeric" :count "eq" "1" {
+		test_fail "flag not set of hasflag fails to see it";
+	}
+
+	if hasflag "$Nonsense" {
+		test_fail "hasflag sees other flag that the one set";
+	}
+}
+
+test "Hasflag; duplicates" {
+	set "Flags" "A B C D E F A B C D E F";
+
+	if hasflag :comparator "i;ascii-numeric" :count "gt" "Flags" "6" {
+		test_fail "hasflag must ignore duplicates";
+	}
+
+	if not hasflag :comparator "i;ascii-numeric" :count "eq" "Flags" "6" {
+		test_fail "hasflag :count gives strange results";
+	}
+}
+
+test "Flag operations" {
+	setflag "A";
+
+	if not hasflag "A" {
+		test_fail "hasflag misses set flag";
+	}
+
+	if hasflag :comparator "i;ascii-numeric" :count "gt" "1" {
+		test_fail "hasflag sees more than one flag";
+	}
+
+	addflag "B";
+
+	if not hasflag "B" {
+		test_fail "flag \"B\" not added";
+	}
+
+	if not hasflag "A" {
+		test_fail "flag \"A\" not retained";
+	}
+
+	if hasflag :comparator "i;ascii-numeric" :count "gt" "2" {
+		test_fail "hasflag sees more than two flags";
+	}
+
+	addflag ["C", "D", "E F"];
+
+	if not hasflag :comparator "i;ascii-numeric" :count "eq" "6" {
+		test_fail "hasflag sees more than two flags";
+	}
+
+	removeflag ["D"];
+
+	if not hasflag :comparator "i;ascii-numeric" :count "eq" "5" {
+		test_fail "hasflag sees more than two flags";
+	}
+
+	if hasflag "D" {
+		test_fail "removed flag still present";
+	}
+
+	set "var" "G";
+	addflag "${var}";
+
+	if not hasflag "G" {
+		test_fail "flag \"G\" not added";
+	}
+
+	if not hasflag "A" {
+		test_fail "flag \"A\" not retained";
+	}
+
+	if not hasflag :comparator "i;ascii-numeric" :count "eq" "6" {
+		test_fail "hasflag sees something other than six flags";
+	}
+}
+
+test "Variable flag operations" {
+	setflag "frop" "A";
+
+	if not hasflag "frop" "A" {
+		test_fail "hasflag misses set flag";
+	}
+
+	if hasflag :comparator "i;ascii-numeric" :count "gt" "frop" "1" {
+		test_fail "hasflag sees more than one flag";
+	}
+
+	addflag "frop" "B";
+
+	if not hasflag "frop" "B" {
+		test_fail "flag \"B\" not added";
+	}
+
+	if not hasflag "frop" "A" {
+		test_fail "flag \"A\" not retained";
+	}
+
+	if hasflag :comparator "i;ascii-numeric" :count "gt" "frop" "2" {
+		test_fail "hasflag sees more than two flags";
+	}
+
+	addflag "frop" ["C", "D", "E F"];
+
+	if not hasflag :comparator "i;ascii-numeric" :count "eq" "frop" "6" {
+		test_fail "hasflag sees something other than six flags";
+	}
+
+	removeflag "frop" ["D"];
+
+	if not hasflag :comparator "i;ascii-numeric" :count "eq" "frop" "5" {
+		test_fail "hasflag sees something other than five flags";
+	}
+
+	if hasflag "frop" "D" {
+		test_fail "removed flag still present";
+	}
+
+	set "var" "G";
+	addflag "frop" "${var}";
+
+	if not hasflag "frop" "G" {
+		test_fail "flag \"G\" not added";
+	}
+
+	if not hasflag "frop" "A" {
+		test_fail "flag \"A\" not retained";
+	}
+
+	if not hasflag :comparator "i;ascii-numeric" :count "eq" "frop" "6" {
+		test_fail "hasflag sees something other than six flags";
+	}
+}
+
+test "Setflag; string list" {
+	setflag ["A B", "C D"];
+
+	if not hasflag "A" {
+		test_fail "hasflag misses A flag";
+	}
+
+	if not hasflag "B" {
+		test_fail "hasflag misses B flag";
+	}
+
+	if not hasflag "C" {
+		test_fail "hasflag misses C flag";
+	}
+
+	if not hasflag "D" {
+		test_fail "hasflag misses D flag";
+	}
+
+	if hasflag :comparator "i;ascii-numeric" :count "ne" "4" {
+		test_fail "hasflag sees incorrect number of flags";
+	}
+}
+
+test "Removal: one" {
+	setflag "\\seen";
+
+	if not hasflag "\\seen" {
+		test_fail "hasflag misses set flag";
+	}
+
+	removeflag "\\seen";
+
+	if hasflag "\\seen" {
+		test_fail "flag not removed";
+	}
+
+	if not hasflag :comparator "i;ascii-numeric" :count "eq" "0" {
+		test_fail "flags are still set";
+	}
+}
+
+test "Removal: first" {
+	setflag "$frop \\seen";
+
+	if not allof ( hasflag "\\seen", hasflag "$frop" ) {
+		test_fail "hasflag misses set flags";
+	}
+
+	removeflag "$frop";
+
+	if not hasflag "\\seen" {
+		test_fail "wrong flag removed";
+	}
+
+	if hasflag "$frop" {
+		test_fail "flag not removed";
+	}
+
+	if not hasflag :comparator "i;ascii-numeric" :count "eq" "1" {
+		test_fail "more than one flag remains set";
+	}
+}
+
+test "Removal: last" {
+	setflag "\\seen $friep";
+
+	if not allof ( hasflag "\\seen", hasflag "$friep" ) {
+		test_fail "hasflag misses set flags";
+	}
+
+	removeflag "$friep";
+
+	if not hasflag "\\seen" {
+		test_fail "wrong flag removed";
+	}
+
+	if hasflag "$friep" {
+		test_fail "flag not removed";
+	}
+
+	if not hasflag :comparator "i;ascii-numeric" :count "eq" "1" {
+		test_fail "more than one flag remains set";
+	}
+}
+
+test "Removal: middle" {
+	setflag "\\seen $friep \\flagged";
+
+	if not allof ( hasflag "\\flagged", hasflag "\\seen", hasflag "$friep" ) {
+		test_fail "hasflag misses set flags";
+	}
+
+	removeflag "$friep";
+
+	if not allof ( hasflag "\\seen", hasflag "\\flagged" ) {
+		test_fail "wrong flag removed";
+	}
+
+	if hasflag "$friep" {
+		test_fail "flag not removed";
+	}
+
+	if not hasflag :comparator "i;ascii-numeric" :count "eq" "2" {
+		test_fail "more than two flags remain set";
+	}
+}
+
+test "Removal: duplicates" {
+	setflag "\\seen $friep $friep \\flagged $friep";
+
+	if not allof ( hasflag "\\flagged", hasflag "\\seen", hasflag "$friep" ) {
+		test_fail "hasflag misses set flags";
+	}
+
+	removeflag "$friep";
+
+	if not allof ( hasflag "\\seen", hasflag "\\flagged" ) {
+		test_fail "wrong flag removed";
+	}
+
+	if hasflag "$friep" {
+		test_fail "flag not removed";
+	}
+
+	if not hasflag :comparator "i;ascii-numeric" :count "eq" "2" {
+		test_fail "more than two flags remain set";
+	}
+}
+
+test "Removal: whitespace" {
+	setflag "   \\seen     $friep      $friep   \\flagged    $friep   ";
+
+	if not allof ( hasflag "\\flagged", hasflag "\\seen", hasflag "$friep" ) {
+		test_fail "hasflag misses set flags";
+	}
+
+	removeflag "$friep";
+
+	if not allof ( hasflag "\\seen", hasflag "\\flagged" ) {
+		test_fail "wrong flag removed";
+	}
+
+	if hasflag "$friep" {
+		test_fail "flag not removed";
+	}
+
+	if not hasflag :comparator "i;ascii-numeric" :count "eq" "2" {
+		test_fail "more than two flags remain set";
+	}
+}
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/imap4flags/execute.svtest
@@ -0,0 +1,68 @@
+require "vnd.dovecot.testsuite";
+require "imap4flags";
+require "relational";
+
+
+/*
+ * Execution testing
+ */
+
+test_mailbox_create "INBOX.Junk";
+test_mailbox_create "INBOX.Nonsense";
+
+test "Flags Side Effect" {
+	if not test_script_compile "execute/flags-side-effect.sieve" {
+		test_fail "script compile failed";
+	}
+
+	if not test_script_run {
+		test_fail "script execute failed";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed";
+	}
+
+	test_result_reset;
+
+	if not test_message :folder "INBOX.Junk" 0 {
+		test_fail "message not stored in INBOX.Junk";
+	}
+
+	if not hasflag :count "eq" "1" {
+		test_fail "invalid number of flags for message in INBOX.Junk";
+	}
+
+	if not hasflag :is "NONSENSE" {
+		test_fail "invalid flag set for message in INBOX.Junk";
+	}
+
+	test_result_reset;
+
+	if not test_message :folder "INBOX" 0 {
+		test_fail "message not stored in INBOX";
+	}
+
+	if not hasflag :count "eq" "1" {
+		test_fail "invalid number of flags for message in INBOX";
+	}
+
+	if not hasflag :is "\\seen" {
+		test_fail "invalid flag set for message in INBOX";
+	}
+
+	test_result_reset;
+
+	if not test_message :folder "INBOX.Nonsense" 0 {
+		test_fail "message not stored in INBOX.Nonsense";
+	}
+
+	if not hasflag :count "eq" "1" {
+		test_fail "invalid number of flags for message in Inbox.Nonsense";
+	}
+
+	if not hasflag :is "IMPLICIT" {
+		test_fail "invalid flag set for message in Inbox.Nonsene";
+	}
+
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/imap4flags/execute/flags-side-effect.sieve
@@ -0,0 +1,18 @@
+require "imap4flags";
+require "fileinto";
+
+/*
+ * When keep/fileinto is used multiple times in a script and duplicate
+ * message elimination is performed, the last flag list value MUST win.
+ */
+
+setflag "IMPLICIT";
+
+fileinto :flags "\\Seen \\Draft" "INBOX.Junk";
+fileinto :flags "NONSENSE" "INBOX.Junk";
+
+keep;
+keep :flags "\\Seen";
+
+fileinto :flags "\\Seen" "Inbox.Nonsense";
+fileinto "Inbox.Nonsense";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/imap4flags/flagstore.svtest
@@ -0,0 +1,146 @@
+require "vnd.dovecot.testsuite";
+require "fileinto";
+require "imap4flags";
+require "relational";
+require "comparator-i;ascii-numeric";
+require "mailbox";
+
+test_set "message" text:
+From: Henry von Flockenstoffen <henry@example.com>
+To: Dieter von Ausburg <dieter@example.com>
+Subject: Test message.
+
+Test message.
+.
+;
+
+test "Basic" {
+	if hasflag :comparator "i;ascii-numeric" :count "ge" "1" {
+		test_fail "some flags or keywords are already set";
+	}
+
+	setflag "$label1 \\answered";
+
+	fileinto :create "Uninteresting";
+
+	if not test_result_execute {
+		test_fail "failed to execute first result";
+	}
+
+	test_result_reset;
+
+	setflag "\\draft \\seen Junk";
+
+	fileinto "Uninteresting";
+
+	if not test_result_execute {
+		test_fail "failed to execute second result";
+	}
+
+	test_result_reset;
+
+	fileinto :flags "\\flagged" "Uninteresting";
+
+	if not test_result_execute {
+		test_fail "failed to execute third result";
+	}
+
+	test_result_reset;
+
+	test_message :folder "Uninteresting" 0;
+
+	if not hasflag "$label1 \\answered" {
+		test_fail "flags not stored for first message";
+	}
+
+	if not hasflag :comparator "i;ascii-numeric" :count "eq" "2" {
+		test_fail "invalid number of flags set for first message";
+	}
+
+	test_result_reset;
+
+	test_message :folder "Uninteresting" 1;
+
+	if not hasflag "\\draft \\seen Junk" {
+		test_fail "flags not stored for second message";
+	}
+
+	if not hasflag :comparator "i;ascii-numeric" :count "eq" "3" {
+		test_fail "invalid number of flags set for second message";
+	}
+
+	test_result_reset;
+
+	test_message :folder "Uninteresting" 2;
+
+	if not hasflag "\\flagged" {
+		test_fail "flags not stored for third message";
+	}
+
+	if not hasflag :comparator "i;ascii-numeric" :count "eq" "1" {
+		test_fail "invalid number of flags set for third message";
+	}
+}
+
+test_result_reset;
+test_set "message" text:
+From: Henry von Flockenstoffen <henry@example.com>
+To: Dieter von Ausburg <dieter@example.com>
+Subject: Test message.
+
+Test message.
+.
+;
+
+test "Flag changes between stores" {
+	if hasflag :comparator "i;ascii-numeric" :count "ge" "1" {
+		test_fail "some flags or keywords are already set";
+	}
+
+	setflag "$label1 \\answered";
+	fileinto :create "FolderA";
+
+	setflag "$label2";
+	fileinto :create "FolderB";
+
+	fileinto :create :flags "\\seen \\draft \\flagged" "FolderC";
+
+	if not test_result_execute {
+		test_fail "failed to execute first result";
+	}
+
+	test_result_reset;
+	test_message :folder "FolderA" 0;
+
+	if not hasflag "\\answered $label1" {
+		test_fail "flags not stored for first message";
+	}
+
+	if not hasflag :comparator "i;ascii-numeric" :count "eq" "2" {
+		test_fail "invalid number of flags set for first message";
+	}
+
+	test_result_reset;
+	test_message :folder "FolderB" 0;
+
+	if not hasflag "$label2" {
+		test_fail "flag not stored for second message";
+	}
+
+	if not hasflag :comparator "i;ascii-numeric" :count "eq" "1" {
+		test_fail "invalid number of flags set for second message";
+	}
+
+	test_result_reset;
+	test_message :folder "FolderC" 0;
+
+	if not hasflag "\\seen \\flagged \\draft" {
+		test_fail "flags not stored for third message";
+	}
+
+	if not hasflag :comparator "i;ascii-numeric" :count "eq" "3" {
+		test_fail "invalid number of flags set for third message";
+	}
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/imap4flags/flagstring.svtest
@@ -0,0 +1,82 @@
+require "vnd.dovecot.testsuite";
+require "imap4flags";
+require "variables";
+
+test "Duplicates: setflag" {
+	setflag "flags" "\\seen \\seen";
+
+	if not string "${flags}" "\\seen" {
+		test_fail "duplicate \\seen flag item not removed (1)";
+	}
+
+	setflag "flags" "\\seen $frop \\seen";
+
+	if not string "${flags}" "\\seen $frop" {
+		test_fail "duplicate \\seen flag item not removed (2)";
+	}
+
+	setflag "flags" "\\seen $frop $frop \\seen";
+
+	if not string "${flags}" "\\seen $frop" {
+		test_fail "duplicate \\seen flag item not removed (3)";
+	}
+
+	setflag "flags" "$frop \\seen $frop \\seen";
+
+	if not string "${flags}" "$frop \\seen" {
+		test_fail "duplicate \\seen flag item not removed (4)";
+	}
+
+	setflag "flags" "$frop \\seen \\seen \\seen \\seen $frop $frop $frop \\seen";
+
+	if not string "${flags}" "$frop \\seen" {
+		test_fail "duplicate \\seen flag item not removed (5)";
+	}
+}
+
+test "Duplicates: addflag" {
+	setflag "flags" "";
+	addflag "flags" "\\seen \\seen";
+
+	if not string "${flags}" "\\seen" {
+		test_fail "duplicate \\seen flag item not removed (1)";
+	}
+
+	setflag "flags" "";
+	addflag "flags" "\\seen $frop \\seen";
+
+	if not string "${flags}" "\\seen $frop" {
+		test_fail "duplicate \\seen flag item not removed (2)";
+	}
+
+	setflag "flags" "";
+	addflag "flags" "\\seen $frop $frop \\seen";
+
+	if not string "${flags}" "\\seen $frop" {
+		test_fail "duplicate \\seen flag item not removed (3)";
+	}
+
+	setflag "flags" "";
+	addflag "flags" "$frop \\seen $frop \\seen";
+
+	if not string "${flags}" "$frop \\seen" {
+		test_fail "duplicate \\seen flag item not removed (4)";
+	}
+
+	setflag "flags" "";
+	addflag "flags" "$frop \\seen \\seen \\seen \\seen $frop $frop $frop \\seen";
+
+	if not string "${flags}" "$frop \\seen" {
+		test_fail "duplicate \\seen flag item not removed (5)";
+	}
+
+	setflag "flags" "$frop \\seen";
+	addflag "flags" "\\seen \\seen \\seen $frop $frop $frop \\seen";
+
+	if not string "${flags}" "$frop \\seen" {
+		test_fail "duplicate \\seen flag item not removed (6)";
+	}
+}
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/imap4flags/hasflag.svtest
@@ -0,0 +1,91 @@
+require "vnd.dovecot.testsuite";
+
+require "imap4flags";
+require "relational";
+require "variables";
+require "comparator-i;ascii-numeric";
+
+/*
+ * Generic tests
+ */
+
+test "Ignoring \"\"" {
+	setflag "";
+
+	if hasflag "" {
+		test_fail "hasflag fails to ignore empty string";
+	}
+}
+
+/*
+ * Variables
+ */
+
+test "Multiple variables" {
+	setflag "A" "Aflag";
+	setflag "B" "Bflag";
+	setflag "C" "Cflag";
+
+	if not hasflag ["a", "b", "c"] ["Bflag"] {
+		test_fail "hasflag failed to match multiple flags variables";
+	}
+}
+
+/*
+ * RFC examples
+ */
+
+test "RFC hasflag example - :is" {
+	setflag "A B";
+
+	if not hasflag ["b","A"] {
+		test_fail "list representation did not match";
+	}
+
+	if not hasflag :is "b A" {
+		test_fail "string representation did not match";
+	}
+}
+
+test "RFC hasflag example - :contains variable" {
+	set "MyVar" "NonJunk Junk gnus-forward $Forwarded NotJunk JunkRecorded $Junk $NotJunk";
+
+	if not hasflag :contains "MyVar" "Junk" {
+		test_fail "failed true example 1";
+	}
+
+	if not hasflag :contains "MyVar" "forward" {
+		test_fail "failed true example 2";
+	}
+
+	if not hasflag :contains "MyVar" ["label", "forward"] {
+		test_fail "failed true example 3";
+	}
+
+	if not hasflag :contains "MyVar" ["junk", "forward"] {
+		test_fail "failed true example 4";
+	}
+
+	if not hasflag :contains "MyVar" "junk forward" {
+		test_fail "failed true example 4 (rewrite 1)";
+	}
+
+	if not hasflag :contains "MyVar" "forward junk" {
+		test_fail "failed true example 4 (rewrite 2)";
+	}
+
+	if hasflag :contains "MyVar" "label" {
+		test_fail "failed false example 1";
+	}
+
+	if hasflag :contains "MyVar" ["label1", "label2"] {
+		test_fail "failed false example 2";
+	}
+}
+
+test "RFC hasflag example - :count variable" {
+	set "MyFlags" "A B";
+	if not hasflag :count "ge" :comparator "i;ascii-numeric" "MyFlags" "2" {
+		test_fail "failed count \"ge\" comparison";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/imap4flags/multiscript.svtest
@@ -0,0 +1,55 @@
+require "vnd.dovecot.testsuite";
+require "imap4flags";
+require "relational";
+require "comparator-i;ascii-numeric";
+require "mailbox";
+require "fileinto";
+
+test "Segfault Trigger 1" {
+
+	if not test_multiscript [
+		"multiscript/group-spam.sieve",
+		"multiscript/spam.sieve",
+		"multiscript/sent-store.sieve"]
+	{
+		test_fail "failed multiscript execution";
+	}
+}
+
+test_set "message" text:
+From: Henry von Flockenstoffen <henry@example.com>
+To: Dieter von Ausburg <dieter@example.com>
+Subject: Test message.
+
+Test message.
+.
+;
+
+test "Internal Flags" {
+	if hasflag :comparator "i;ascii-numeric" :count "ge" "1" {
+ 		test_fail "some flags or keywords are already set";
+	}
+
+	if not test_multiscript [
+		"multiscript/setflag.sieve",
+		"multiscript/fileinto.sieve"]
+	{
+		test_fail "failed multiscript execution";
+	}
+
+	test_result_reset;
+	test_message :folder "folder" 0;
+
+	if not hasflag "\\answered" {
+		test_fail "\\answered flag not stored for message";
+	}
+
+	if not hasflag "$label1" {
+		test_fail "$label1 keyword not stored for message";
+ 	}
+
+	if not hasflag :comparator "i;ascii-numeric" :count "eq" "2" {
+		test_fail "invalid number of flags set for message";
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/imap4flags/multiscript/fileinto.sieve
@@ -0,0 +1,4 @@
+require "fileinto";
+require "mailbox";
+
+fileinto :create "folder";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/imap4flags/multiscript/group-spam.sieve
@@ -0,0 +1,14 @@
+require ["fileinto", "variables", "envelope"];
+
+if header :contains "X-Group-Mail" ["Yes", "YES", "1"] {
+	if header :contains "X-Spam-Flag" ["Yes", "YES", "1"] {
+		if envelope :matches :localpart "to" "*" {
+			fileinto "group/${1}/SPAM"; stop;
+		}
+	}
+	if address :is ["To"] "sales@florist.ru" {
+		fileinto "group/info/Orders";
+	}
+	stop;
+}
+keep;
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/imap4flags/multiscript/sent-store.sieve
@@ -0,0 +1,7 @@
+require ["imap4flags"];
+
+if header :contains "X-Set-Seen" ["Yes", "YES", "1"] {
+	setflag "\\Seen";
+}
+
+keep;
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/imap4flags/multiscript/setflag.sieve
@@ -0,0 +1,3 @@
+require "imap4flags";
+
+setflag "$label1 \\answered";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/imap4flags/multiscript/spam.sieve
@@ -0,0 +1,8 @@
+require ["fileinto"];
+
+if header :contains "X-Spam-Flag" ["Yes", "YES", "1"] {
+  fileinto "SPAM";
+}
+keep;
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/errors.svtest
@@ -0,0 +1,149 @@
+require "vnd.dovecot.testsuite";
+
+require "relational";
+require "comparator-i;ascii-numeric";
+
+/*
+ * Generic include errors
+ */
+
+test "Generic" {
+	if test_script_compile "errors/generic.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "3" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+test "Circular - direct" {
+	if test_script_compile "errors/circular-1.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "3" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+test "Circular - one intermittent" {
+	if test_script_compile "errors/circular-2.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "4" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+test "Circular - two intermittent" {
+	if test_script_compile "errors/circular-3.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "5" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+/*
+ * Using global without variables required
+ */
+
+test "Variables inactive" {
+	if test_script_compile "errors/variables-inactive.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "3" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+/*
+ * Generic variables errors
+ */
+
+test "Variables" {
+	if test_script_compile "errors/variables.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "3" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+/*
+ * Global variable namespace
+ */
+
+test "Global Namespace" {
+	if test_script_compile "errors/global-namespace.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "4" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+/*
+ * Invalid script names
+ */
+
+test "Invalid Script Names" {
+	if test_script_compile "errors/scriptname.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "8" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+/* Include limit */
+
+test "Include limit" {
+	test_config_set "sieve_include_max_includes" "3";
+	test_config_reload :extension "include";
+
+	if test_script_compile "errors/include-limit.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "2" {
+		test_fail "wrong number of errors reported";
+	}
+
+	test_config_set "sieve_include_max_includes" "255";
+	test_config_reload :extension "include";
+
+	if not test_script_compile "errors/include-limit.sieve" {
+		test_fail "compile should have succeeded";
+	}
+}
+
+/* Depth limit */
+
+test "Depth limit" {
+	test_config_set "sieve_include_max_nesting_depth" "2";
+	test_config_reload :extension "include";
+
+	if test_script_compile "errors/depth-limit.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "4" {
+		test_fail "wrong number of errors reported";
+	}
+
+	test_config_set "sieve_include_max_nesting_depth" "10";
+	test_config_reload :extension "include";
+
+	if not test_script_compile "errors/depth-limit.sieve" {
+		test_fail "compile should have succeeded";
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/errors/action-conflicts.sieve
@@ -0,0 +1,4 @@
+require "include";
+
+include "action-fileinto";
+include "action-reject";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/errors/circular-1.sieve
@@ -0,0 +1,5 @@
+require "include";
+
+discard;
+
+include "circular-one";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/errors/circular-2.sieve
@@ -0,0 +1,5 @@
+require "include";
+
+discard;
+
+include "circular-two";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/errors/circular-3.sieve
@@ -0,0 +1,5 @@
+require "include";
+
+discard;
+
+include "circular-three";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/errors/depth-limit.sieve
@@ -0,0 +1,3 @@
+require "include";
+
+include :personal "depth-limit-1";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/errors/generic.sieve
@@ -0,0 +1,7 @@
+require "include";
+
+# Non-existent sieve script
+include "frop.sieve";
+
+# Use of / in script names
+include "../frop.sieve";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/errors/global-namespace.sieve
@@ -0,0 +1,13 @@
+require "variables";
+require "include";
+
+# Invalid namespace
+set "globl.var" "frop";
+
+# Sub-namespace
+set "global.env.0" "12";
+
+# Invalid variable name
+set "global.12" "porf";
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/errors/include-limit.sieve
@@ -0,0 +1,6 @@
+require "include";
+
+include "rfc-ex1-always_allow";
+include "rfc-ex2-spam_filter_script";
+include "rfc-ex1-mailing_lists";
+include "rfc-ex1-spam_tests";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/errors/scriptname.sieve
@@ -0,0 +1,25 @@
+require "variables";
+require "include";
+require "encoded-character";
+
+# Slash
+include "../frop";
+
+# More slashes
+include "../../james/sieve/vacation";
+
+# 0000-001F; [CONTROL CHARACTERS]
+include "idiotic${unicode: 001a}";
+
+# 007F; DELETE
+include "idiotic${unicode: 007f}";
+
+# 0080-009F; [CONTROL CHARACTERS]
+include "idiotic${unicode: 0085}";
+
+# 2028; LINE SEPARATOR
+include "idiotic${unicode: 2028}";
+
+# 2029; PARAGRAPH SEPARATOR
+include "idiotic${unicode: 2029}";
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/errors/variables-inactive.sieve
@@ -0,0 +1,7 @@
+require "include";
+require "fileinto";
+
+global "friep";
+global "frop";
+
+fileinto "Frop";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/errors/variables.sieve
@@ -0,0 +1,23 @@
+require "include";
+require "variables";
+
+# Duplicate global declaration (not an error)
+global "frml";
+global "frml";
+
+keep;
+
+# Global after command not being require or global (not an error)
+global "friep";
+
+# DEPRECATED: import/export after command not being require or import/export
+export "friep";
+import "friep";
+
+# Marking local variable as global
+set "frutsels" "frop";
+global "frutsels";
+set "frutsels" "frop";
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/execute.svtest
@@ -0,0 +1,68 @@
+require "vnd.dovecot.testsuite";
+require "include";
+require "variables";
+
+test_set "message" text:
+From: idiot@example.com
+To: idiot@example.org
+Subject: Frop!
+
+Frop.
+.
+;
+
+test "Actions Fileinto" {
+	test_mailbox_create "aaaa";
+	test_mailbox_create "bbbb";
+
+	if not test_script_compile "execute/actions-fileinto.sieve" {
+		test_fail "failed to compile sieve script";
+	}
+
+	test_binary_save "actions-fileinto";
+	test_binary_load "actions-fileinto";
+
+	if not test_script_run {
+		test_fail "failed to execute sieve script";
+	}
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	test_message :folder "aaaa" 0;
+
+	if not header "subject" "Frop!" {
+		test_fail "fileinto \"aaaa\" not executed.";
+	}
+
+	test_message :folder "bbbb" 0;
+
+	if not header "subject" "Frop!" {
+		test_fail "fileinto \"bbbb\" not executed.";
+	}
+}
+
+test "Namespace - file" {
+	if not test_script_compile "execute/namespace.sieve" {
+		test_fail "failed to compile sub-test";
+	}
+
+	if not test_script_run {
+		test_fail "failed to execute sub-test";
+	}
+}
+
+test "Namespace - dict" {
+	test_config_set "sieve" "dict:file:${tst.path}/included/namespace.dict";
+	test_config_set "sieve_global" "dict:file:${tst.path}/included-global/namespace.dict";
+	test_config_reload :extension "include";
+
+	if not test_script_compile "execute/namespace.sieve" {
+		test_fail "failed to compile sub-test";
+	}
+	
+	if not test_script_run {
+		test_fail "failed to execute sub-test";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/execute/actions-fileinto.sieve
@@ -0,0 +1,5 @@
+require "include";
+
+include "actions-fileinto1";
+include "actions-fileinto2";
+include "actions-fileinto3";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/execute/namespace.sieve
@@ -0,0 +1,26 @@
+require "vnd.dovecot.testsuite";
+require "include";
+require "variables";
+
+set "global.a" "none";
+include :personal "namespace";
+
+if string "${global.a}" "none" {
+	test_fail "personal script not executed";
+}
+
+if not string "${global.a}" "personal" {
+	test_fail "executed global instead of personal script: ${global.a}";
+}
+
+set "global.a" "none";
+include :global "namespace";
+
+if string "{global.a}" "none" {
+	test_fail "global script not executed";
+}
+
+if not string "${global.a}" "global" {
+	test_fail "executed personal instead of global script: ${global.a}";
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/execute/optional.sieve
@@ -0,0 +1,5 @@
+require "include";
+
+include :optional "optional-1";
+include :optional "optional-2";
+include :optional "optional-3";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included-global/namespace.dict
@@ -0,0 +1,4 @@
+priv/sieve/name/namespace
+1
+priv/sieve/data/1
+require ["variables", "include"]; set "global.a" "global";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included-global/namespace.sieve
@@ -0,0 +1,4 @@
+require "include";
+require "variables";
+
+set "global.a" "global";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included-global/rfc-ex1-spam_tests.sieve
@@ -0,0 +1,7 @@
+require ["reject"];
+
+if anyof (header :contains "Subject" "$$",
+	header :contains "Subject" "Make money")
+{
+	reject "Not wanted";
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/action-fileinto.sieve
@@ -0,0 +1,3 @@
+require "fileinto";
+
+fileinto "frop";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/action-reject.sieve
@@ -0,0 +1,3 @@
+require "reject";
+
+reject "Ik heb geen zin in die rommel.";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/actions-fileinto1.sieve
@@ -0,0 +1,3 @@
+require "fileinto";
+
+fileinto "aaaa";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/actions-fileinto2.sieve
@@ -0,0 +1,4 @@
+require "fileinto";
+
+fileinto "bbbb";
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/actions-fileinto3.sieve
@@ -0,0 +1,3 @@
+require "fileinto";
+
+fileinto "aaaa";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/circular-one.sieve
@@ -0,0 +1,5 @@
+require "include";
+
+keep;
+
+include "circular-one";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/circular-three-2.sieve
@@ -0,0 +1,3 @@
+require "include";
+
+include "circular-three-3";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/circular-three-3.sieve
@@ -0,0 +1,3 @@
+require "include";
+
+include "circular-three.sieve";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/circular-three.sieve
@@ -0,0 +1,7 @@
+require "include";
+
+keep;
+
+include "circular-three-2";
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/circular-two-2.sieve
@@ -0,0 +1,3 @@
+require "include";
+
+include "circular-two.sieve";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/circular-two.sieve
@@ -0,0 +1,7 @@
+require "include";
+
+keep;
+
+include "circular-two-2";
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/depth-limit-1.sieve
@@ -0,0 +1,3 @@
+require "include";
+
+include :personal "depth-limit-2";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/depth-limit-2.sieve
@@ -0,0 +1,3 @@
+require "include";
+
+include :personal "depth-limit-3";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/depth-limit-3.sieve
@@ -0,0 +1 @@
+keep;
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/namespace.dict
@@ -0,0 +1,4 @@
+priv/sieve/name/namespace
+1
+priv/sieve/data/1
+require ["variables", "include"]; set "global.a" "personal";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/namespace.sieve
@@ -0,0 +1,4 @@
+require "include";
+require "variables";
+
+set "global.a" "personal";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/once-1.sieve
@@ -0,0 +1,9 @@
+require "include";
+require "variables";
+
+global "result";
+
+set "result" "${result} ONE";
+
+return;
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/once-2.sieve
@@ -0,0 +1,12 @@
+require "include";
+require "variables";
+
+global "result";
+
+set "result" "${result} TWO";
+
+keep;
+
+include :once "once-1";
+
+return;
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/once-3.sieve
@@ -0,0 +1,3 @@
+require "include";
+
+include "once-4";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/once-4.sieve
@@ -0,0 +1,3 @@
+require "include";
+
+include :once "once-3";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/optional-1.sieve
@@ -0,0 +1,9 @@
+require "include";
+require "variables";
+
+global "result";
+
+set "result" "${result} ONE";
+
+return;
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/optional-2.sieve
@@ -0,0 +1,9 @@
+require "include";
+require "variables";
+
+global "result";
+
+set "result" "${result} TWO";
+
+keep;
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/rfc-ex1-always_allow.sieve
@@ -0,0 +1,8 @@
+if header :is "From" "boss@example.com"
+{
+	keep;
+}
+elsif header :is "From" "ceo@example.com"
+{
+	keep;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/rfc-ex1-mailing_lists.sieve
@@ -0,0 +1,10 @@
+require ["fileinto"];
+
+if header :is "Sender" "owner-ietf-mta-filters@imc.example.com"
+{
+	fileinto "lists.sieve";
+}
+elsif header :is "Sender" "owner-ietf-imapext@imc.example.com"
+{
+	fileinto "lists.imapext";
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/rfc-ex1-spam_tests.sieve
@@ -0,0 +1,10 @@
+require ["reject"];
+
+if header :contains "Subject" "XXXX"
+{
+	reject "Not wanted";
+}
+elsif header :is "From" "money@example.com"
+{
+	reject "Not wanted";
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/rfc-ex2-spam_filter_script.sieve
@@ -0,0 +1,8 @@
+require ["variables", "include"];
+global ["test", "test_mailbox"];
+
+if header :contains "Subject" "${test}"
+{
+	set "test_mailbox" "spam-${test}";
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/twice-1.sieve
@@ -0,0 +1,7 @@
+require "include";
+require "variables";
+
+global "result";
+
+set "result" "${result} TWO";
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/twice-2.sieve
@@ -0,0 +1,8 @@
+require "include";
+require "variables";
+
+global "result";
+
+set "result" "${result} THREE";
+
+include "twice-1";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/variables-included1.sieve
@@ -0,0 +1,7 @@
+require "include";
+require "variables";
+
+global ["value1"];
+global ["result1"];
+
+set "result1" "${value1} ${global.value2}";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/variables-included2.sieve
@@ -0,0 +1,6 @@
+require "include";
+require "variables";
+
+global ["value3", "value4"];
+
+set "global.result2" "${value3} ${value4}";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/included/variables-included3.sieve
@@ -0,0 +1,8 @@
+require "include";
+require "variables";
+
+global "result1";
+global "result2";
+global "result";
+
+set "result" "${result1} ${result2}";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/once.svtest
@@ -0,0 +1,24 @@
+require "vnd.dovecot.testsuite";
+require "include";
+require "variables";
+
+global "result";
+
+set "result" "";
+
+test "Included Once" {
+	include "once-1";
+	include "once-2";
+
+	if string "${result}" " ONE TWO ONE" {
+		test_fail "duplicate included :once script";
+	}
+
+	if not string "${result}" " ONE TWO" {
+		test_fail "unexpected result value: ${result}";
+	}
+}
+
+test "Included Once recursive" {
+	include "once-3";
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/optional.svtest
@@ -0,0 +1,40 @@
+require "vnd.dovecot.testsuite";
+require "include";
+require "variables";
+
+global "result";
+set "result" "";
+
+test "Included Optional" {
+	include :optional "optional-1";
+	include :optional "optional-2";
+
+	if not string "${result}" " ONE TWO" {
+		test_fail "unexpected result value: ${result}";
+	}
+
+	# missing
+	include :optional "optional-3";
+
+	if not string "${result}" " ONE TWO" {
+		test_fail "unexpected result value after missing script: ${result}";
+	}
+}
+
+
+test "Included Optional - Binary" {
+ 	if not test_script_compile "execute/optional.sieve" {
+		test_fail "failed to compile sieve script";
+	}
+
+	test_binary_save "optional";
+	test_binary_load "optional";
+
+	if not test_script_run {
+		test_fail "failed to execute sieve script";
+	}
+
+	if not string "${result}" " ONE TWO" {
+		test_fail "unexpected result value: ${result}";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/rfc-ex1-default.sieve
@@ -0,0 +1,6 @@
+require ["include"];
+
+include :personal "rfc-ex1-always_allow";
+include :global "rfc-ex1-spam_tests";
+include :personal "rfc-ex1-spam_tests";
+include :personal "rfc-ex1-mailing_lists";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/rfc-ex2-default.sieve
@@ -0,0 +1,21 @@
+require ["variables", "include", "relational", "fileinto"];
+global "test";
+global "test_mailbox";
+
+# The included script may contain repetitive code that is
+# effectively a subroutine that can be factored out.
+set "test" "$$";
+include "rfc-ex2-spam_filter_script";
+
+set "test" "Make money";
+include "rfc-ex2-spam_filter_script";
+
+# Message will be filed according to the test that matched last.
+if string :count "eq" "${test_mailbox}" "1"
+{
+	fileinto "INBOX${test_mailbox}";
+	stop;
+}
+
+# If nothing matched, the message is implicitly kept.
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/rfc.svtest
@@ -0,0 +1,13 @@
+require "vnd.dovecot.testsuite";
+
+test "RFC example 1" {
+	if not test_script_compile "rfc-ex1-default.sieve" {
+		test_fail "failed to compile sieve script";
+	}
+}
+
+test "RFC example 2" {
+	if not test_script_compile "rfc-ex2-default.sieve" {
+		test_fail "failed to compile sieve script";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/twice.svtest
@@ -0,0 +1,20 @@
+require "vnd.dovecot.testsuite";
+require "include";
+require "variables";
+
+global "result";
+
+set "result" "ONE";
+
+test "Twice included" {
+	include "twice-1";
+	include "twice-2";
+
+	if string "${result}" "ONE TWO THREE" {
+		test_fail "duplicate include failed";
+	}
+
+	if not string "${result}" "ONE TWO THREE TWO" {
+		test_fail "unexpected result: ${result}";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/include/variables.svtest
@@ -0,0 +1,29 @@
+require "vnd.dovecot.testsuite";
+
+require "include";
+require "variables";
+
+global ["value1", "value2"];
+set "value1" "Works";
+set "value2" "fine.";
+
+global ["value3", "value4"];
+set "value3" "Yeah";
+set "value4" "it does.";
+
+include "variables-included1";
+include "variables-included2";
+include "variables-included3";
+
+global "result";
+
+test "Basic" {
+	if not string :is "${result}" "Works fine. Yeah it does." {
+		test_fail "invalid result: ${result}";
+	}
+
+	if string :is "${result}" "nonsense" {
+		test_fail "string test succeeds inappropriately";
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/index/basic.svtest
@@ -0,0 +1,93 @@
+require "vnd.dovecot.testsuite";
+require "index";
+require "date";
+require "variables";
+require "subaddress";
+
+test_set "message" text:
+To: first@friep.example.com
+X-A: First
+Received: from mx.example.com (127.0.0.13) by mx.example.org
+ (127.0.0.12) with Macrosoft SMTP Server (TLS) id 1.2.3.4;
+ Wed, 12 Nov 2014 18:18:31 +0100
+To: second@friep.example.com
+From: stephan@example.org
+Received: from mx.example.com (127.0.0.13) by mx.example.org
+ (127.0.0.12) with Macrosoft SMTP Server (TLS) id 1.2.3.4;
+ Wed, 12 Nov 2014 18:18:30 +0100
+X-A: Second
+To: third@friep.example.com
+X-A: Third
+Received: from mx.example.com (127.0.0.13) by mx.example.org
+ (127.0.0.12) with Macrosoft SMTP Server (TLS) id 1.2.3.4;
+ Wed, 12 Nov 2014 18:18:29 +0100
+Subject: Frop!
+X-A: Fourth
+To: fourth@friep.example.com
+Received: from mx.example.com (127.0.0.13) by mx.example.org
+ (127.0.0.12) with Macrosoft SMTP Server (TLS) id 1.2.3.4;
+ Wed, 12 Nov 2014 18:18:28 +0100
+
+Frop
+.
+;
+
+test "Header :index" {
+	if not header :index 3 "x-a" "Third" {
+		test_fail "wrong header retrieved";
+	}
+
+	if header :index 3 "x-a" ["First", "Second", "Fourth"] {
+		test_fail "other header retrieved";
+	}
+}
+
+test "Header :index :last" {
+	if not header :index 3 :last "x-a" "Second" {
+		test_fail "wrong header retrieved";
+	}
+
+	if header :index 3 :last "x-a" ["First", "Third", "Fourth"] {
+		test_fail "other header retrieved";
+	}
+}
+
+test "Address :index" {
+	if not address :localpart :index 2 "to" "second" {
+		test_fail "wrong header retrieved";
+	}
+
+	if address :localpart :index 2 "to" ["first", "third", "fourth"] {
+		test_fail "other header retrieved";
+	}
+}
+
+test "Address :index :last" {
+	if not address :localpart :index 2 :last "to" "third" {
+		test_fail "wrong header retrieved";
+	}
+
+	if address :localpart :index 2 :last "to" ["first", "second", "fourth"] {
+		test_fail "other header retrieved";
+	}
+}
+
+test "Date :index" {
+	if not date :index 1 "received" "second" "31" {
+		test_fail "wrong header retrieved";
+	}
+
+	if date :index 1 "received" "second" ["30", "29", "28"] {
+		test_fail "other header retrieved";
+	}
+}
+
+test "Date :index :last" {
+	if not date :index 1 :last "received" "second" "28"{
+		test_fail "wrong header retrieved";
+	}
+
+	if date :index 1 :last "received" "second" ["31", "30", "29"] {
+		test_fail "other header retrieved";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/index/errors.svtest
@@ -0,0 +1,20 @@
+require "vnd.dovecot.testsuite";
+
+require "relational";
+require "comparator-i;ascii-numeric";
+
+/*
+ * Invalid syntax
+ */
+
+test "Invalid Syntax" {
+        if test_script_compile "errors/syntax.sieve" {
+                test_fail "compile should have failed";
+        }
+
+        if not test_error :count "eq" :comparator "i;ascii-numeric" "5" {
+                test_fail "wrong number of errors reported";
+        }
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/index/errors/syntax.sieve
@@ -0,0 +1,20 @@
+require "date";
+require "index";
+
+# Not an error
+if header :last :index 2 "to" "ok" { }
+
+# Not an error
+if header :index 444 :last "to" "ok" { }
+
+# 1: missing argument
+if header :index "to" "ok" {}
+
+# 2: missing argument
+if header :index :last "to" "ok" {}
+
+# 3: erroneous string argument
+if header :index "frop" "to" "ok" {}
+
+# 4: last without index
+if header :last "to" "ok" {}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/mailbox/execute.svtest
@@ -0,0 +1,80 @@
+require "vnd.dovecot.testsuite";
+require "mailbox";
+require "fileinto";
+
+test "MailboxExists - None exist" {
+	if mailboxexists "frop" {
+		test_fail "mailboxexists confirms existance of unknown folder";
+	}
+}
+
+test_mailbox_create "frop";
+test_mailbox_create "friep";
+
+test "MailboxExists - Not all exist" {
+	if mailboxexists ["frop", "friep", "frml"] {
+		test_fail "mailboxexists confirms existance of unknown folder";
+	}
+}
+
+test_mailbox_create "frml";
+
+test "MailboxExists - One exists" {
+	if not mailboxexists ["frop"] {
+		test_fail "mailboxexists fails to recognize folder";
+	}
+}
+
+test "MailboxExists - All exist" {
+	if not mailboxexists ["frop", "friep", "frml"] {
+		test_fail "mailboxexists fails to recognize folders";
+	}
+}
+
+test ":Create" {
+	if mailboxexists "created" {
+		test_fail "mailbox exists already";
+	}
+
+	test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Frop 1
+
+Frop!
+.
+	;
+
+	fileinto :create "created";
+
+	if not test_result_execute {
+		test_fail "execution of result failed";
+	}
+
+	if not mailboxexists "created" {
+		test_fail "mailbox somehow not created";
+	}
+
+	test_result_reset;
+
+	test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Frop 2
+
+Frop!
+.
+	;
+
+	fileinto "created";
+
+	if not test_result_execute {
+		test_fail "execution of result failed second time";
+	}
+
+	test_message :folder "created" 0;
+
+	if not header :is "subject" "Frop 1" {
+		test_fail "incorrect message read back from mail store";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/metadata/errors.svtest
@@ -0,0 +1,18 @@
+require "vnd.dovecot.testsuite";
+
+require "relational";
+require "comparator-i;ascii-numeric";
+
+/*
+ * Invalid syntax
+ */
+
+test "Invalid Syntax" {
+        if test_script_compile "errors/syntax.sieve" {
+                test_fail "compile should have failed";
+        }
+
+        if not test_error :count "eq" :comparator "i;ascii-numeric" "27" {
+                test_fail "wrong number of errors reported";
+        }
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/metadata/errors/syntax.sieve
@@ -0,0 +1,48 @@
+require "mboxmetadata";
+require "servermetadata";
+
+# 1-4: Used as a command
+metadata;
+metadataexists;
+servermetadata;
+servermetadataexists;
+
+# 5-8: Used with no argument
+if metadata {}
+if metadataexists {}
+if servermetadata {}
+if servermetadataexists {}
+
+# 9-10: Used with one string argument
+if metadata "frop" { }
+if servermetadata "frop" { }
+if metadataexists "frop" { }
+
+# Used with one number argument
+if metadata 13123123 { }
+if servermetadata 123123 { }
+if metadataexists 123123 { }
+if servermetadataexists 123123 {}
+
+# Used with one string list argument
+if metadata ["frop"] { }
+if servermetadata ["frop"] { }
+if metadataexists ["frop"] { }
+
+# Used with unknown tag
+if metadata :frop "frop" { }
+if servermetadata :frop "frop" { }
+if metadataexists :frop "frop" { }
+if servermetadataexists :frop "frop" {}
+
+# Invalid arguments
+if metadata "/private/frop" "friep" {}
+if servermetadata "INBOX" "/private/frop" "friep" {}
+if metadataexists 23 "/private/frop" {}
+if servermetadataexists "INBOX" "/private/frop" {}
+
+# Invalid annotations
+if metadata "INBOX" "frop" "friep" {}
+if servermetadata "frop" "friep" {}
+if metadataexists "INBOX" ["/private/frop", "/friep"] { }
+if servermetadataexists ["/private/frop", "/friep", "/private/friep"] { }
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/metadata/execute.svtest
@@ -0,0 +1,145 @@
+require "vnd.dovecot.testsuite";
+require "mboxmetadata";
+require "servermetadata";
+require "fileinto";
+
+test "MetadataExists - None exist" {
+	if metadataexists "INBOX" "/private/frop" {
+		test_fail "metadataexists confirms existence of unknown annotation";
+	}
+}
+
+test_imap_metadata_set :mailbox "INBOX" "/private/frop" "FROP!";
+test_imap_metadata_set :mailbox "INBOX" "/private/friep" "FRIEP!";
+
+test "MetadataExists - Not all exist" {
+	if metadataexists "INBOX"
+		["/private/frop", "/private/friep", "/private/frml"] {
+		test_fail "metadataexists confirms existence of unknown annotation";
+	}
+}
+
+test_imap_metadata_set :mailbox "INBOX" "/private/friep" "FRIEP!";
+test_imap_metadata_set :mailbox "INBOX" "/private/frml" "FRML!";
+
+test "MetadataExists - One exists" {
+	if not metadataexists "INBOX" ["/private/frop"] {
+		test_fail "metadataexists fails to recognize annotation";
+	}
+}
+
+test "MetadataExists - All exist" {
+	if not metadataexists "INBOX"
+		["/private/frop", "/private/friep", "/private/frml"] {
+		test_fail "metadataexists fails to recognize annotations";
+	}
+}
+
+test "MetadataExists - Invalid" {
+	if metadataexists "INBOX"
+		["/shared/frop", "/friep", "/private/frml"] {
+		test_fail "metadataexists accepted invalid annotation name";
+	}
+}
+
+test "Metadata" {
+	if not metadata :is "INBOX" "/private/frop" "FROP!" {
+		test_fail "invalid metadata value for /private/frop";
+	}
+	if metadata :is "INBOX" "/private/frop" "Hutsefluts" {
+		test_fail "unexpected match for /private/frop";
+	}
+
+	if not metadata :is "INBOX" "/private/friep" "FRIEP!" {
+		test_fail "invalid metadata value for /private/friep";
+	}
+	if metadata :is "INBOX" "/private/friep" "Hutsefluts" {
+		test_fail "unexpected match for /private/friep";
+	}
+
+	if not metadata :is "INBOX" "/private/frml" "FRML!" {
+		test_fail "invalid metadata value for /private/frml";
+	}
+	if metadata :is "INBOX" "/private/frml" "Hutsefluts" {
+		test_fail "unexpected match for /private/frml";
+	}
+}
+
+test "Metadata - Invalid" {
+	if metadata :contains "INBOX" "/frop" "" {
+		test_fail "erroneously found a value for \"/frop\"";
+	}
+}
+
+test "ServermetadataExists - None exist" {
+	if servermetadataexists "/private/frop" {
+		test_fail "servermetadataexists confirms existence of unknown annotation";
+	}
+}
+
+# currently not possible to test servermetadata
+if false {
+
+test_imap_metadata_set "/private/frop" "FROP!";
+test_imap_metadata_set "/private/friep" "FRIEP!";
+
+test "ServermetadataExists - Not all exist" {
+	if servermetadataexists 
+		["/private/frop", "/private/friep", "/private/frml"] {
+		test_fail "metadataexists confirms existence of unknown annotation";
+	}
+}
+
+test_imap_metadata_set "/private/friep" "FRIEP!";
+test_imap_metadata_set "/private/frml" "FRML!";
+
+test "ServermetadataExists - One exists" {
+	if not servermetadataexists ["/private/frop"] {
+		test_fail "servermetadataexists fails to recognize annotation";
+	}
+}
+
+test "ServermetadataExists - All exist" {
+	if not servermetadataexists
+		["/private/frop", "/private/friep", "/private/frml"] {
+		test_fail "servermetadataexists fails to recognize annotations";
+	}
+}
+
+test "ServermetadataExists - Invalid" {
+	if servermetadataexists
+		["frop", "/private/friep", "/private/frml"] {
+		test_fail "servermetadataexists accepted invalid annotation name";
+	}
+}
+
+test "Servermetadata" {
+	if not servermetadata :is "/private/frop" "FROP!" {
+		test_fail "invalid servermetadata value for /private/frop";
+	}
+	if servermetadata :is "/private/frop" "Hutsefluts" {
+		test_fail "unexpected match for /private/frop";
+	}
+
+	if not servermetadata :is "/private/friep" "FRIEP!" {
+		test_fail "invalid servermetadata value for /private/friep";
+	}
+	if servermetadata :is "/private/friep" "Hutsefluts" {
+		test_fail "unexpected match for /private/friep";
+	}
+
+	if not servermetadata :is "/private/frml" "FRML!" {
+		test_fail "invalid servermetadata value for /private/frml";
+	}
+	if servermetadata :is "/private/frml" "Hutsefluts" {
+		test_fail "unexpected match for /private/frml";
+	}
+}
+
+test "Servermetadata - Invalid" {
+	if servermetadata :contains "/frop" "" {
+		test_fail "erroneously found a value for \"/frop\"";
+	}
+}
+
+} #disabled
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/mime/address.svtest
@@ -0,0 +1,281 @@
+require "vnd.dovecot.testsuite";
+require "mime";
+require "foreverypart";
+
+/*
+ * Basic functionionality
+ */
+
+test_set "message" text:
+From: stephan@example.com
+To: nico@nl.example.com, harry@de.example.com
+cc: Timo <tss(no spam)@fi.iki>
+Subject: Frobnitzm
+
+Test.
+.
+;
+
+test "Basic functionality" {
+	/* Must match */
+	if not address :mime :anychild :contains ["to", "from"] "harry" {
+		test_fail "failed to match address (1)";
+	}
+
+	if not address :mime :anychild :contains ["to", "from"] "de.example" {
+		test_fail "failed to match address (2)";
+	}
+
+	if not address :mime :anychild :matches "to" "*@*.example.com" {
+		test_fail "failed to match address (3)";
+	}
+
+	if not address :mime :anychild :is "to" "harry@de.example.com" {
+		test_fail "failed to match address (4)";
+	}
+
+	/* Must not match */
+	if address :mime :anychild :is ["to", "from"] "nonsense@example.com" {
+		test_fail "matches erroneous address";
+	}
+
+	/* Match first key */
+	if not address :mime :anychild :contains ["to"] ["nico", "fred", "henk"] {
+		test_fail "failed to match first key";
+	}
+
+	/* Match second key */
+	if not address :mime :anychild :contains ["to"] ["fred", "nico", "henk"] {
+		test_fail "failed to match second key";
+	}
+
+	/* Match last key */
+	if not address :mime :anychild :contains ["to"] ["fred", "henk", "nico"] {
+		test_fail "failed to match last key";
+	}
+
+	/* First header */
+	if not address :mime :anychild :contains
+		["to", "from"] ["fred", "nico", "henk"] {
+		test_fail "failed to match first header";
+	}
+
+	/* Second header */
+	if not address :mime :anychild :contains
+		["from", "to"] ["fred", "nico", "henk"] {
+		test_fail "failed to match second header";
+	}
+
+	/* Comment */
+	if not address :mime :anychild :is "cc" "tss@fi.iki" {
+		test_fail "failed to ignore comment in address";
+	}
+}
+
+/*
+ * Basic functionionality - foreverypart
+ */
+
+test "Basic functionality - foreverypart" {
+	foreverypart {
+		/* Must match */
+		if not address :mime :anychild :contains ["to", "from"] "harry" {
+			test_fail "failed to match address (1)";
+		}
+
+		if not address :mime :anychild :contains ["to", "from"] "de.example" {
+			test_fail "failed to match address (2)";
+		}
+
+		if not address :mime :anychild :matches "to" "*@*.example.com" {
+			test_fail "failed to match address (3)";
+		}
+
+		if not address :mime :anychild :is "to" "harry@de.example.com" {
+			test_fail "failed to match address (4)";
+		}
+
+		/* Must not match */
+		if address :mime :anychild :is ["to", "from"] "nonsense@example.com" {
+			test_fail "matches erroneous address";
+		}
+
+		/* Match first key */
+		if not address :mime :anychild :contains ["to"] ["nico", "fred", "henk"] {
+			test_fail "failed to match first key";
+		}
+
+		/* Match second key */
+		if not address :mime :anychild :contains ["to"] ["fred", "nico", "henk"] {
+			test_fail "failed to match second key";
+		}
+
+		/* Match last key */
+		if not address :mime :anychild :contains ["to"] ["fred", "henk", "nico"] {
+			test_fail "failed to match last key";
+		}
+
+		/* First header */
+		if not address :mime :anychild :contains
+			["to", "from"] ["fred", "nico", "henk"] {
+			test_fail "failed to match first header";
+		}
+
+		/* Second header */
+		if not address :mime :anychild :contains
+			["from", "to"] ["fred", "nico", "henk"] {
+			test_fail "failed to match second header";
+		}
+
+		/* Comment */
+		if not address :mime :anychild :is "cc" "tss@fi.iki" {
+			test_fail "failed to ignore comment in address";
+		}
+	}
+}
+
+/*
+ * Address headers
+ */
+
+test_set "message" text:
+From: stephan@friep.frop
+To: henk@tukkerland.ex
+CC: ivo@boer.ex
+Bcc: joop@hooibaal.ex
+Sender: s.bosch@friep.frop
+Resent-From: ivo@boer.ex
+Resent-To: idioot@dombo.ex
+Subject: Berichtje
+
+Test.
+.
+;
+
+test "Address headers" {
+	if not address :mime :anychild "from" "stephan@friep.frop" {
+		test_fail "from header not recognized";
+	}
+
+	if not address :mime :anychild "to" "henk@tukkerland.ex" {
+		test_fail "to header not recognized";
+	}
+
+	if not address :mime :anychild "cc" "ivo@boer.ex" {
+		test_fail "cc header not recognized";
+	}
+
+	if not address :mime :anychild "bcc" "joop@hooibaal.ex" {
+		test_fail "bcc header not recognized";
+	}
+
+	if not address :mime :anychild "sender" "s.bosch@friep.frop" {
+		test_fail "sender header not recognized";
+	}
+
+	if not address :mime :anychild "resent-from" "ivo@boer.ex" {
+		test_fail "resent-from header not recognized";
+	}
+
+	if not address :mime :anychild "resent-to" "idioot@dombo.ex" {
+		test_fail "resent-to header not recognized";
+	}
+}
+
+/*
+ * Address headers - foreverypart
+ */
+
+test "Address headers - foreverypart" {
+	foreverypart {
+		if not address :mime :anychild "from" "stephan@friep.frop" {
+			test_fail "from header not recognized";
+		}
+
+		if not address :mime :anychild "to" "henk@tukkerland.ex" {
+			test_fail "to header not recognized";
+		}
+
+		if not address :mime :anychild "cc" "ivo@boer.ex" {
+			test_fail "cc header not recognized";
+		}
+
+		if not address :mime :anychild "bcc" "joop@hooibaal.ex" {
+			test_fail "bcc header not recognized";
+		}
+
+		if not address :mime :anychild "sender" "s.bosch@friep.frop" {
+			test_fail "sender header not recognized";
+		}
+
+		if not address :mime :anychild "resent-from" "ivo@boer.ex" {
+			test_fail "resent-from header not recognized";
+		}
+
+		if not address :mime :anychild "resent-to" "idioot@dombo.ex" {
+			test_fail "resent-to header not recognized";
+		}
+	}
+}
+
+/*
+ * Multipart anychild
+ */
+
+test_set "message" text:
+From: Hendrik <hendrik@example.com>
+To: Harrie <harrie@example.com>
+Date: Sat, 11 Oct 2010 00:31:44 +0200
+Subject: Harrie is een prutser
+Content-Type: multipart/mixed; boundary=AA
+CC: AA@example.com
+
+This is a multi-part message in MIME format.
+--AA
+Content-Type: multipart/mixed; boundary=BB
+CC: BB@example.com
+
+This is a multi-part message in MIME format.
+--BB
+Content-Type: text/plain; charset="us-ascii"
+CC: CC@example.com
+
+Hello
+
+--BB
+Content-Type: text/plain; charset="us-ascii"
+CC: DD@example.com
+
+Hello again
+
+--BB--
+This is the end of MIME multipart.
+
+--AA
+Content-Type: text/plain; charset="us-ascii"
+CC: EE@example.com
+
+And again
+
+--AA--
+This is the end of  MIME multipart.
+.
+;
+
+test "Multipart anychild" {
+	if not address :mime :anychild :localpart "Cc" "AA" {
+		test_fail "AA Cc repient does not exist";
+	}
+	if not address :mime :anychild :localpart "Cc" "BB" {
+		test_fail "BB Cc repient does not exist";
+	}
+	if not address :mime :anychild :localpart "Cc" "CC" {
+		test_fail "CC Cc repient does not exist";
+	}
+	if not address :mime :anychild :localpart "Cc" "DD" {
+		test_fail "DD Cc repient does not exist";
+	}
+	if not address :mime :anychild :localpart "Cc" "EE" {
+		test_fail "EE Cc repient does not exist";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/mime/calendar-example.svtest
@@ -0,0 +1,129 @@
+require "vnd.dovecot.testsuite";
+require "mime";
+require "foreverypart";
+require "editheader";
+require "relational";
+require "variables";
+
+# Example from RFC 6047, Section 2.5:
+test_set "message" text:
+From: user1@example.com
+To: user2@example.com
+Subject: Phone Conference
+Mime-Version: 1.0
+Date: Wed, 07 May 2008 21:30:25 +0400
+Message-ID: <4821E731.5040506@laptop1.example.com>
+Content-Type: text/calendar; method=REQUEST; charset=UTF-8
+Content-Transfer-Encoding: quoted-printable
+
+BEGIN:VCALENDAR
+PRODID:-//Example/ExampleCalendarClient//EN
+METHOD:REQUEST
+VERSION:2.0
+BEGIN:VEVENT
+ORGANIZER:mailto:user1@example.com
+ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:mailto:user1@example.com
+ATTENDEE;RSVP=YES;CUTYPE=INDIVIDUAL:mailto:user2@example.com
+DTSTAMP:20080507T170000Z
+DTSTART:20080701T160000Z
+DTEND:20080701T163000Z
+SUMMARY:Phone call to discuss your last visit
+DESCRIPTION:=D1=82=D1=8B =D0=BA=D0=B0=D0=BA - =D0=B4=D0=BE=D0=
+ =B2=D0=BE=D0=BB=D0=B5=D0=BD =D0=BF=D0=BE=D0=B5=D0=B7=D0=B4=D0=BA=D0
+ =BE=D0=B9?
+UID:calsvr.example.com-8739701987387998
+SEQUENCE:0
+STATUS:TENTATIVE
+END:VEVENT
+END:VCALENDAR
+.
+;
+
+test "Calendar only" {
+	foreverypart {
+		if allof(
+			header :mime :count "eq" "Content-Type" "1",
+			header :mime :contenttype "Content-Type" "text/calendar",
+			header :mime :param "method" :matches "Content-Type" "*",
+			header :mime :param "charset" :is "Content-Type" "UTF-8" ) {
+			addheader "X-ICAL" "${1}";
+			break;
+		}
+	}
+
+	if not header "x-ical" "request" {
+		test_fail "Failed to parse message correctly";
+	}
+}
+
+# Modified example
+test_set "message" text:
+From: user1@example.com
+To: user2@example.com
+Subject: Phone Conference
+Mime-Version: 1.0
+Date: Wed, 07 May 2008 21:30:25 +0400
+Message-ID: <4821E731.5040506@laptop1.example.com>
+Content-Type: multipart/mixed; boundary=AA
+
+This is a multi-part message in MIME format.
+
+--AA
+Content-Type: text/plain
+
+Hello,
+
+I'd like to discuss your last visit. A tentative meeting schedule is
+attached.
+
+Regards,
+
+User1
+
+--AA
+Content-Type: text/calendar; method=REQUEST; charset=UTF-8
+Content-Transfer-Encoding: quoted-printable
+
+BEGIN:VCALENDAR
+PRODID:-//Example/ExampleCalendarClient//EN
+METHOD:REQUEST
+VERSION:2.0
+BEGIN:VEVENT
+ORGANIZER:mailto:user1@example.com
+ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:mailto:user1@example.com
+ATTENDEE;RSVP=YES;CUTYPE=INDIVIDUAL:mailto:user2@example.com
+DTSTAMP:20080507T170000Z
+DTSTART:20080701T160000Z
+DTEND:20080701T163000Z
+SUMMARY:Phone call to discuss your last visit
+DESCRIPTION:=D1=82=D1=8B =D0=BA=D0=B0=D0=BA - =D0=B4=D0=BE=D0=
+ =B2=D0=BE=D0=BB=D0=B5=D0=BD =D0=BF=D0=BE=D0=B5=D0=B7=D0=B4=D0=BA=D0
+ =BE=D0=B9?
+UID:calsvr.example.com-8739701987387998
+SEQUENCE:0
+STATUS:TENTATIVE
+END:VEVENT
+END:VCALENDAR
+
+--AA--
+.
+;
+
+test "Multipart message" {
+	foreverypart {
+		if allof(
+			header :mime :count "eq" "Content-Type" "1",
+			header :mime :contenttype "Content-Type" "text/calendar",
+			header :mime :param "method" :matches "Content-Type" "*",
+			header :mime :param "charset" :is "Content-Type" "UTF-8" ) {
+			addheader "X-ICAL" "${1}";
+			break;
+		}
+	}
+
+	if not header "x-ical" "request" {
+		test_fail "Failed to parse message correctly";
+	}
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/mime/content-header.svtest
@@ -0,0 +1,161 @@
+require "vnd.dovecot.testsuite";
+require "relational";
+require "mime";
+
+test_set "message" text:
+From: stephan@example.com
+To: timo@example.com
+Subject: Frop
+Content-Type: text/plain
+
+Frop
+.
+;
+
+test "Simple Content-Type :type" {
+	if not header :mime :type "content-type" "text" {
+		test_fail "wrong type extracted";
+	}
+}
+
+test "Simple Content-Type :subype" {
+	if not header :mime :subtype "content-type" "plain" {
+		test_fail "wrong subtype extracted";
+	}
+}
+
+test "Simple Content-Type :contenttype" {
+	if not header :mime :contenttype "content-type" "text/plain" {
+		test_fail "wrong contenttype extracted";
+	}
+}
+
+test_set "message" text:
+From: stephan@example.com
+To: timo@example.com
+Subject: Frop
+Content-Type: text/calendar; method=request; charset=UTF-8;
+
+Frop
+.
+;
+
+test "Advanced Content-Type :type" {
+	if not header :mime :type "content-type" "text" {
+		test_fail "wrong type extracted";
+	}
+}
+
+test "Advanced Content-Type :subype" {
+	if not header :mime :subtype "content-type" "calendar" {
+		test_fail "wrong subtype extracted";
+	}
+}
+
+test "Advanced Content-Type :contenttype" {
+	if not header :mime :contenttype "content-type" "text/calendar" {
+		test_fail "wrong contenttype extracted";
+	}
+}
+
+test "Advanced Content-Type :param" {
+	if not header :mime :param "method" "content-type" "request" {
+		test_fail "wrong method param extracted";
+	}
+
+	if not header :mime :param "charset" "content-type" "UTF-8" {
+		test_fail "wrong charset param extracted";
+	}
+
+	if not header :mime :param ["method", "charset"]
+		"content-type" "request" {
+		test_fail "wrong method param extracted";
+	}
+
+	if not header :mime :param ["method", "charset"]
+		"content-type" "UTF-8" {
+		test_fail "wrong charset param extracted";
+	}
+
+	if not header :count "eq" :mime :param ["method", "charset"]
+		"content-type" "2" {
+		test_fail "wrong number of parameters";
+	}
+}
+
+test_set "message" text:
+From: stephan@example.com
+To: timo@example.com
+Subject: Frop
+Content-Type: application/x-stuff;
+	title*0*=us-ascii'en'This%20is%20even%20more%20;
+	title*1*=%2A%2A%2Afun%2A%2A%2A%20;
+	title*2="isn't it!"
+
+Frop
+.
+;
+
+test "Encoded Content-Type :param" {
+	if not header :mime :param "title" "content-type"
+		"This is even more ***fun*** isn't it!" {
+		test_fail "wrong method param extracted";
+	}
+}
+
+test_set "message" text:
+From: stephan@example.com
+To: timo@example.com
+Subject: Frop
+Content-Type: image/png
+Content-Disposition: inline; filename="frop.exe"; title="Frop!"
+
+Frop
+.
+;
+
+test "Content-Disposition :type" {
+	if not header :mime :type "content-disposition" "inline" {
+		test_fail "wrong type extracted";
+	}
+}
+
+test "Content-Disposition :subype" {
+	if not header :mime :subtype "content-disposition" "" {
+		test_fail "wrong subtype extracted";
+	}
+}
+
+test "Content-Disposition :contenttype" {
+	if not header :mime :contenttype "content-disposition" "inline" {
+		test_fail "wrong contenttype extracted";
+	}
+}
+
+test "Content-Disposition :param" {
+	if not header :mime :param "filename" "content-disposition" "frop.exe" {
+		test_fail "wrong filename param extracted";
+	}
+
+	if not header :mime :param "title" "content-disposition" "Frop!" {
+		test_fail "wrong title param extracted";
+	}
+
+	if not header :mime :param ["filename", "title"]
+		"content-disposition" "frop.exe" {
+		test_fail "wrong filename param extracted";
+	}
+
+	if not header :mime :param ["filename", "title"]
+		"content-disposition" "Frop!" {
+		test_fail "wrong title param extracted";
+	}
+
+	if not header :count "eq" :mime :param ["filename", "title"]
+		"content-disposition" "2" {
+		test_fail "wrong number of parameters";
+	}
+
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/mime/errors.svtest
@@ -0,0 +1,162 @@
+require "vnd.dovecot.testsuite";
+
+require "relational";
+require "comparator-i;ascii-numeric";
+
+test "Foreverypart command" {
+	if test_script_compile "errors/foreverypart.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if test_error :count "ne" :comparator "i;ascii-numeric" "12" {
+		test_fail "incorrect number of compile errors reported";
+	}
+}
+
+test "Break command" {
+	if test_script_compile "errors/break.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if test_error :count "ne" :comparator "i;ascii-numeric" "21" {
+		test_fail "incorrect number of compile errors reported";
+	}
+}
+
+test "Header test with :mime tag" {
+	if test_script_compile "errors/header-mime-tag.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if test_error :count "ne" :comparator "i;ascii-numeric" "10" {
+		test_fail "incorrect number of compile errors reported";
+	}
+}
+
+test "Address test with :mime tag" {
+	if test_script_compile "errors/address-mime-tag.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if test_error :count "ne" :comparator "i;ascii-numeric" "6" {
+		test_fail "incorrect number of compile errors reported";
+	}
+}
+
+test "Exists test with :mime tag" {
+	if test_script_compile "errors/exists-mime-tag.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if test_error :count "ne" :comparator "i;ascii-numeric" "6" {
+		test_fail "incorrect number of compile errors reported";
+	}
+}
+
+test "Limits" {
+	if test_script_compile "errors/limits.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if test_error :count "ne" :comparator "i;ascii-numeric" "2" {
+		test_fail "incorrect number of compile errors reported";
+	}
+}
+
+test_set "message" text:
+From: Whomever <whoever@example.com>
+To: Someone <someone@example.com>
+Date: Sat, 10 Oct 2009 00:30:04 +0200
+Subject: whatever
+Content-Type: multipart/mixed; boundary=AA
+
+This is a multi-part message in MIME format.
+
+--AA
+Content-Type: multipart/alternative; boundary=BB
+
+This is a multi-part message in MIME format.
+
+--BB
+Content-Type: multipart/alternative; boundary=CC
+
+This is a multi-part message in MIME format.
+
+--CC
+Content-Type: multipart/alternative; boundary=DD
+
+This is a multi-part message in MIME format.
+
+--DD
+Content-Type: multipart/alternative; boundary=EE
+
+This is a nested multi-part message in MIME format.
+
+--EE
+Content-Type: text/plain; charset="us-ascii"
+
+Hello
+
+--EE--
+
+This is the end of the inner MIME multipart.
+
+--DD--
+
+This is the end of the MIME multipart.
+
+--CC--
+
+This is the end of the MIME multipart.
+
+--BB--
+
+This is the end of the MIME multipart.
+
+--AA--
+
+This is the end of the MIME multipart.
+.
+;
+
+test "Limits - include" {
+	if not test_script_compile "errors/limits-include.sieve" {
+		test_fail "script compile failed";
+	}
+
+	if test_script_run {
+		test_fail "script run should have failed";
+	}
+}
+
+test "Extracttext" {
+	if test_script_compile "errors/extracttext.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if test_error :count "ne" :comparator "i;ascii-numeric" "11" {
+		test_fail "incorrect number of compile errors reported";
+	}
+}
+
+test "Extracttext - without variables" {
+	if test_script_compile "errors/extracttext-novar.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if test_error :count "ne" :comparator "i;ascii-numeric" "2" {
+		test_fail "incorrect number of compile errors reported";
+	}
+}
+
+test "Extracttext - without foreverypart" {
+	if test_script_compile "errors/extracttext-nofep.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if test_error :count "ne" :comparator "i;ascii-numeric" "2" {
+		test_fail "incorrect number of compile errors reported";
+	}
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/mime/errors/address-mime-tag.sieve
@@ -0,0 +1,38 @@
+require "mime";
+
+## Address
+
+# No error
+if address :contains :mime "To" "frop@example.com" {
+	discard;
+}
+
+# No error
+if address :anychild :contains :mime "To" "frop@example.com" {
+	discard;
+}
+
+# 1: Bare anychild option
+if address :anychild "To" "frop@example.com" {
+	discard;
+}
+
+# 2: Inappropriate option
+if address :mime :anychild :type "To" "frop@example.com" {
+	discard;
+}
+
+# 3: Inappropriate option
+if address :mime :anychild :subtype "To" "frop@example.com" {
+	discard;
+}
+
+# 4: Inappropriate option
+if address :mime :anychild :contenttype "To" "frop@example.com" {
+	discard;
+}
+
+# 5: Inappropriate option
+if address :mime :anychild :param "frop" "To" "frop@example.com" {
+	discard;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/mime/errors/break.sieve
@@ -0,0 +1,157 @@
+require "foreverypart";
+
+foreverypart :name "frop" {
+	# 1: Spurious tag
+	break :tag;
+
+	# 2: Spurious tests
+	break true;
+
+	# 3: Spurious tests
+	break anyof(true, false);
+
+	# 4: Bare string
+	break "frop";
+
+	# 5: Bare string-list
+	break ["frop", "friep"];
+
+	# 6: Several bad arguments
+	break 13 ["frop", "friep"];
+
+	# 7: Spurious additional tag
+	break :name "frop" :friep;
+
+	# 8: Spurious additional string
+	break :name "frop" "friep";
+
+	# 9: Bad name
+	break :name 13;
+
+	# 10: Bad name
+	break :name ["frop", "friep"];
+
+	# No error
+	break;
+
+	# No error
+	break :name "frop";
+
+	# No error
+	if exists "frop" {
+		break;
+	}
+
+	# No error
+	if exists "frop" {
+		break :name "frop";
+	}
+
+	# No error	
+	foreverypart {
+		break :name "frop";
+	}
+
+	# No error
+	foreverypart :name "friep" {
+		break :name "frop";
+	}
+
+	# No error
+	foreverypart :name "friep" {
+		break :name "friep";
+	}
+
+	# No error
+	foreverypart :name "friep" {
+		break;
+	}
+
+	# No error	
+	foreverypart {
+		if exists "frop" {
+			break :name "frop";
+		}
+	}
+
+	# No error
+	foreverypart :name "friep" {
+		if exists "frop" {
+			break :name "frop";
+		}
+	}
+
+	# No error
+	foreverypart :name "friep" {
+		if exists "frop" {
+			break :name "friep";
+		}
+	}
+
+	# No error
+	foreverypart :name "friep" {
+		if exists "frop" {
+			break;
+		}
+	}
+}
+
+# 11: Outside loop
+break; 
+
+# 12: Outside loop
+if exists "frop" {
+	break;
+}
+
+# 13: Outside loop
+break :name "frop";
+
+# 14: Outside loop
+if exists "frop" {
+	break :name "frop";
+}
+
+# 15: Bad name
+foreverypart {
+	break :name "frop";
+}
+
+# 16: Bad name
+foreverypart {
+	if exists "frop" {
+		break :name "frop";
+	}
+}
+
+# 17: Bad name
+foreverypart :name "friep" {
+	break :name "frop";
+}
+
+# 18: Bad name
+foreverypart :name "friep" {
+	if exists "frop" {
+		break :name "frop";
+	}
+}
+
+# 19: Bad name
+foreverypart :name "friep" {
+	foreverypart :name "frop" {
+		break :name "frml";
+	}
+}
+
+# 20: Bad name
+foreverypart :name "friep" {
+	foreverypart :name "frop" {
+		if exists "frop" {
+			break :name "frml";
+		}
+	}
+}
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/mime/errors/exists-mime-tag.sieve
@@ -0,0 +1,43 @@
+require "mime";
+
+## Exists
+
+# No error
+if exists :mime "To" {
+	discard;
+}
+
+# No error
+if exists :anychild :mime "To" {
+	discard;
+}
+
+# 1: Inappropriate option
+if exists :anychild "To" {
+	discard;
+}
+
+# 2: Inappropriate option
+if exists :mime :type "To" {
+	discard;
+}
+
+# 3: Inappropriate option
+if exists :mime :subtype "To" {
+	discard;
+}
+
+# 4: Inappropriate option
+if exists :mime :contenttype "To" {
+	discard;
+}
+
+# 5: Inappropriate option
+if exists :mime :param ["frop", "friep"] "To" {
+	discard;
+}
+
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/mime/errors/extracttext-nofep.sieve
@@ -0,0 +1,4 @@
+require "extracttext";
+require "variables";
+
+keep;
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/mime/errors/extracttext-novar.sieve
@@ -0,0 +1,6 @@
+require "extracttext";
+require "foreverypart";
+
+foreverypart {
+	extracttext "frop";
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/mime/errors/extracttext.sieve
@@ -0,0 +1,42 @@
+require "extracttext";
+require "variables";
+require "foreverypart";
+
+# 1: Used outside foreverypart
+extracttext :first 10 "data";
+
+foreverypart {
+	# 2: Missing arguments
+	extracttext;
+	
+	# 3: Bad arguments
+	extracttext 1;
+
+	# 4: Bad arguments
+	extracttext ["frop", "friep"];
+
+	# 5: Unknown tag
+	extracttext :frop "frop";
+
+	# 6: Invalid variable name
+	extracttext "${frop}";
+
+	# Not an error
+	extracttext "\n\a\m\e";
+
+	# 7: Trying to assign match variable
+	extracttext "0";
+
+	# Not an error
+	extracttext :lower "frop";
+
+	# 8: Bad ":first" tag
+	extracttext :first "frop";
+
+	# 9: Bad ":first" tag
+	extracttext :first "frop" "friep";
+
+	# 10: Bad ":first" tag
+	extracttext :first ["frop", "friep"] "frml";
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/mime/errors/foreverypart.sieve
@@ -0,0 +1,45 @@
+require "foreverypart";
+
+# 1: No block
+foreverypart;
+
+# 2: Spurious tag
+foreverypart :tag { }
+
+# 3: Spurious tests
+foreverypart true { }
+
+# 4: Spurious tests
+foreverypart anyof(true, false) { }
+
+# 5: Bare string
+foreverypart "frop" { }
+
+# 6: Bare string-list
+foreverypart ["frop", "friep"] { }
+
+# 7: Several bad arguments
+foreverypart 13 ["frop", "friep"] { }
+
+# 8: Spurious additional tag
+foreverypart :name "frop" :friep { }
+
+# 9: Spurious additional string
+foreverypart :name "frop" "friep" { }
+
+# 10: Bad name
+foreverypart :name 13 { }
+
+# 11: Bad name
+foreverypart :name ["frop", "friep"] { }
+
+# No error
+foreverypart { keep; }
+
+# No error
+foreverypart :name "frop" { keep; }
+
+# No error
+foreverypart :name "frop" { foreverypart { keep; } }
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/mime/errors/header-mime-tag.sieve
@@ -0,0 +1,100 @@
+require "mime";
+
+## Header
+
+# No error
+if header :contains :mime "Content-Type" "text/plain" {
+	discard;
+}
+
+# No error
+if header :mime :type "Content-Type" "text" {
+	discard;
+}
+
+# No error
+if header :mime :subtype "Content-Type" "plain" {
+	discard;
+}
+
+# No error
+if header :mime :contenttype "Content-Type" "text/plain" {
+	discard;
+}
+
+# No error
+if header :mime :param ["frop", "friep"] "Content-Type" "frml" {
+	discard;
+}
+
+# No error
+if header :anychild :contains :mime "Content-Type" "text/plain" {
+	discard;
+}
+
+# No error
+if header :mime :anychild :type "Content-Type" "text" {
+	discard;
+}
+
+# No error
+if header :mime :subtype :anychild "Content-Type" "plain" {
+	discard;
+}
+
+# No error
+if header :anychild :mime :contenttype "Content-Type" "text/plain" {
+	discard;
+}
+
+# No error
+if header :mime :param ["frop", "friep"] :anychild "Content-Type" "frml" {
+	discard;
+}
+
+# 1: Bare anychild option
+if header :anychild "Content-Type" "frml" {
+	discard;
+}
+
+# 2: Bare mime option
+if header :type "Content-Type" "frml" {
+	discard;
+}
+
+# 3: Bare mime option
+if header :subtype "Content-Type" "frml" {
+	discard;
+}
+
+# 4: Bare mime option
+if header :contenttype "Content-Type" "frml" {
+	discard;
+}
+
+# 5: Bare mime option
+if header :param "frop" "Content-Type" "frml" {
+	discard;
+}
+
+# 6: Multiple option tags
+if header :mime :type :subtype "Content-Type" "frml" {
+	discard;
+}
+
+# 7: Bad param argument
+if header :mime :param 13 "Content-Type" "frml" {
+	discard;
+}
+
+# 8: Missing param argument
+if header :mime :param :anychild "Content-Type" "frml" {
+	discard;
+}
+
+# 9: Missing param argument
+if header :mime :param :frop "Content-Type" "frml" {
+	discard;
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/mime/errors/limits-include.sieve
@@ -0,0 +1,6 @@
+require "foreverypart";
+require "include";
+
+foreverypart :name "frop" {
+	include "include-loop-2";
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/mime/errors/limits.sieve
@@ -0,0 +1,13 @@
+require "foreverypart";
+
+foreverypart :name "frop" {
+	foreverypart :name "friep" {
+		foreverypart :name "frml" {
+			foreverypart {
+				foreverypart {
+					break;
+				}
+			}
+		}
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/mime/execute.svtest
@@ -0,0 +1,82 @@
+require "vnd.dovecot.testsuite";
+
+/*
+ * Execution testing (currently just meant to trigger any segfaults)
+ */
+
+test_set "message" text:
+From: Whomever <whoever@example.com>
+To: Someone <someone@example.com>
+Date: Sat, 10 Oct 2009 00:30:04 +0200
+Subject: whatever
+Content-Type: multipart/mixed; boundary=outer
+
+This is a multi-part message in MIME format.
+
+--outer
+Content-Type: multipart/alternative; boundary=inner
+
+This is a nested multi-part message in MIME format.
+
+--inner
+Content-Type: text/plain; charset="us-ascii"
+
+Hello
+
+--inner
+Content-Type: text/html; charset="us-ascii"
+
+<html><body>Hello</body></html>
+
+--inner--
+
+This is the end of the inner MIME multipart.
+
+--outer
+Content-Type: message/rfc822
+
+From: Someone Else
+Subject: Hello, this is an elaborate request for you to finally say hello
+ already!
+
+Please say Hello
+
+--outer--
+
+This is the end of the outer MIME multipart.
+.
+;
+
+test "Basic - foreverypart" {
+	if not test_script_compile "execute/foreverypart.sieve" {
+		test_fail "script compile failed";
+	}
+
+	if not test_script_run {
+		test_fail "script run failed";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed";
+	}
+
+	test_binary_save "ihave-basic";
+	test_binary_load "ihave-basic";
+}
+
+test "Basic - mime" {
+	if not test_script_compile "execute/mime.sieve" {
+		test_fail "script compile failed";
+	}
+
+	if not test_script_run {
+		test_fail "script run failed";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed";
+	}
+
+	test_binary_save "ihave-basic";
+	test_binary_load "ihave-basic";
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/mime/execute/foreverypart.sieve
@@ -0,0 +1,14 @@
+require "foreverypart";
+require "variables";
+
+foreverypart {
+	foreverypart {
+		foreverypart {
+			foreverypart {
+				set "a" "a${a}";
+			}
+		}
+	}
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/mime/execute/mime.sieve
@@ -0,0 +1,69 @@
+require "mime";
+require "foreverypart";
+require "variables";
+
+if header :contains :mime "Content-Type" "text/plain" {
+	discard;
+}
+if header :mime :type "Content-Type" "text" {
+	discard;
+}
+if header :mime :subtype "Content-Type" "plain" {
+	discard;
+}
+if header :mime :contenttype "Content-Type" "text/plain" {
+	discard;
+}
+if header :mime :param ["frop", "friep"] "Content-Type" "frml" {
+	discard;
+}
+if header :anychild :contains :mime "Content-Type" "text/plain" {
+	discard;
+}
+if header :mime :anychild :type "Content-Type" "text" {
+	discard;
+}
+if header :mime :subtype :anychild "Content-Type" "plain" {
+	discard;
+}
+if header :anychild :mime :contenttype "Content-Type" "text/plain" {
+	discard;
+}
+if header :mime :param ["frop", "friep"] :anychild "Content-Type" "frml" {
+	discard;
+}
+
+foreverypart {
+	foreverypart {
+		if header :contains :mime "Content-Type" "text/plain" {
+			discard;
+		}
+		if header :mime :type "Content-Type" "text" {
+			discard;
+		}
+		if header :mime :subtype "Content-Type" "plain" {
+			discard;
+		}
+		if header :mime :contenttype "Content-Type" "text/plain" {
+			discard;
+		}
+		if header :mime :param ["frop", "friep"] "Content-Type" "frml" {
+			discard;
+		}
+		if header :anychild :contains :mime "Content-Type" "text/plain" {
+			discard;
+		}
+		if header :mime :anychild :type "Content-Type" "text" {
+			discard;
+		}
+		if header :mime :subtype :anychild "Content-Type" "plain" {
+			discard;
+		}
+		if header :anychild :mime :contenttype "Content-Type" "text/plain" {
+			discard;
+		}
+		if header :mime :param ["frop", "friep"] :anychild "Content-Type" "frml" {
+			discard;
+		}
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/mime/exists.svtest
@@ -0,0 +1,237 @@
+require "vnd.dovecot.testsuite";
+require "mime";
+require "foreverypart";
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@vestingbar.bl
+Subject: Test message
+Date: Wed, 29 Jul 2009 18:21:44 +0300
+X-Spam-Status: Not Spam
+Resent-To: nico@frop.example.com
+
+Test!
+.
+;
+
+/*
+ * One header
+ */
+
+test "One header" {
+	if not exists :mime :anychild "from" {
+		test_fail "exists test missed from header";
+	}
+
+	if exists :mime :anychild "x-nonsense" {
+		test_fail "exists test found non-existent header";
+	}
+}
+
+/*
+ * One header - foreverypart
+ */
+
+test "One header - foreverypart" {
+	foreverypart {
+		if not exists :mime :anychild "from" {
+			test_fail "exists test missed from header";
+		}
+
+		if exists :mime :anychild "x-nonsense" {
+			test_fail "exists test found non-existent header";
+		}
+	}
+}
+
+/*
+ * Two headers
+ */
+
+test "Two headers" {
+	if not exists :mime :anychild ["from","to"] {
+		test_fail "exists test missed from or to header";
+	}
+
+	if exists :mime :anychild ["from","x-nonsense"] {
+		test_fail "exists test found non-existent header (1)";
+	}
+
+	if exists :mime :anychild ["x-nonsense","to"] {
+		test_fail "exists test found non-existent header (2)";
+	}
+
+	if exists :mime :anychild ["x-nonsense","x-nonsense2"] {
+		test_fail "exists test found non-existent header (3)";
+	}
+}
+
+/*
+ * Two headers - foreverypart
+ */
+
+test "Two headers - foreverypart" {
+	foreverypart {
+		if not exists :mime :anychild ["from","to"] {
+			test_fail "exists test missed from or to header";
+		}
+
+		if exists :mime :anychild ["from","x-nonsense"] {
+			test_fail "exists test found non-existent header (1)";
+		}
+
+		if exists :mime :anychild ["x-nonsense","to"] {
+			test_fail "exists test found non-existent header (2)";
+		}
+
+		if exists :mime :anychild ["x-nonsense","x-nonsense2"] {
+			test_fail "exists test found non-existent header (3)";
+		}
+	}
+}
+
+/*
+ * Three headers
+ */
+
+test "Three headers" {
+	if not exists :mime :anychild ["Subject","date","resent-to"] {
+		test_fail "exists test missed subject, date or resent-to header";
+	}
+
+	if exists :mime :anychild ["x-nonsense","date","resent-to"] {
+		test_fail "exists test found non-existent header (1)";
+	}
+
+	if exists :mime :anychild ["subject", "x-nonsense","resent-to"] {
+		test_fail "exists test found non-existent header (2)";
+	}
+
+	if exists :mime :anychild ["subject","date","x-nonsense"] {
+		test_fail "exists test found non-existent header (3)";
+	}
+
+	if exists :mime :anychild ["subject", "x-nonsense","x-nonsense2"] {
+		test_fail "exists test found non-existent header (4)";
+	}
+
+	if exists :mime :anychild ["x-nonsense","date","x-nonsense2"] {
+		test_fail "exists test found non-existent header (5)";
+	}
+
+	if exists :mime :anychild ["x-nonsense","x-nonsense2","resent-to"] {
+		test_fail "exists test found non-existent header (6)";
+	}
+
+	if exists :mime :anychild ["x-nonsense","x-nonsense2","x-nonsense3"] {
+		test_fail "exists test found non-existent header (7)";
+	}
+}
+
+/*
+ * Three headers - foreverypart
+ */
+
+test "Three headers - foreverypart " {
+	foreverypart {
+		if not exists :mime :anychild ["Subject","date","resent-to"] {
+			test_fail "exists test missed subject, date or resent-to header";
+		}
+
+		if exists :mime :anychild ["x-nonsense","date","resent-to"] {
+			test_fail "exists test found non-existent header (1)";
+		}
+
+		if exists :mime :anychild ["subject", "x-nonsense","resent-to"] {
+			test_fail "exists test found non-existent header (2)";
+		}
+
+		if exists :mime :anychild ["subject","date","x-nonsense"] {
+			test_fail "exists test found non-existent header (3)";
+		}
+
+		if exists :mime :anychild ["subject", "x-nonsense","x-nonsense2"] {
+			test_fail "exists test found non-existent header (4)";
+		}
+
+		if exists :mime :anychild ["x-nonsense","date","x-nonsense2"] {
+			test_fail "exists test found non-existent header (5)";
+		}
+
+		if exists :mime :anychild ["x-nonsense","x-nonsense2","resent-to"] {
+			test_fail "exists test found non-existent header (6)";
+		}
+
+		if exists :mime :anychild ["x-nonsense","x-nonsense2","x-nonsense3"] {
+			test_fail "exists test found non-existent header (7)";
+		}
+	}
+}
+
+/*
+ * Multipart anychild
+ */
+
+test_set "message" text:
+From: Hendrik <hendrik@example.com>
+To: Harrie <harrie@example.com>
+Date: Sat, 11 Oct 2010 00:31:44 +0200
+Subject: Harrie is een prutser
+Content-Type: multipart/mixed; boundary=AA
+X-Test1: AA
+
+This is a multi-part message in MIME format.
+--AA
+Content-Type: multipart/mixed; boundary=BB
+X-Test2: BB
+
+This is a multi-part message in MIME format.
+--BB
+Content-Type: text/plain; charset="us-ascii"
+X-Test3: CC
+
+Hello
+
+--BB
+Content-Type: text/plain; charset="us-ascii"
+X-Test4: DD
+
+Hello again
+
+--BB--
+This is the end of MIME multipart.
+
+--AA
+Content-Type: text/plain; charset="us-ascii"
+X-Test5: EE
+
+And again
+
+--AA--
+This is the end of  MIME multipart.
+.
+;
+
+test "Multipart anychild" {
+	if not exists :mime :anychild "X-Test1" {
+		test_fail "X-Test1 header does exist";
+	}
+	if not exists :mime :anychild "X-Test2" {
+		test_fail "X-Test2 header does exist";
+	}
+	if not exists :mime :anychild "X-Test3" {
+		test_fail "X-Test3 header does exist";
+	}
+	if not exists :mime :anychild "X-Test4" {
+		test_fail "X-Test4 header does exist";
+	}
+	if not exists :mime :anychild "X-Test5" {
+		test_fail "X-Test5 header does exist";
+	}
+	if not exists :mime :anychild
+			["X-Test1", "X-Test2", "X-Test3", "X-Test4", "X-Test5"] {
+		test_fail "Not all headers exist";
+	}
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/mime/extracttext.svtest
@@ -0,0 +1,143 @@
+require "vnd.dovecot.testsuite";
+require "foreverypart";
+require "variables";
+require "extracttext";
+
+test_set "message" text:
+From: Hendrik <hendrik@example.com>
+To: Harrie <harrie@example.com>
+Date: Sat, 11 Oct 2010 00:31:44 +0200
+Subject: Harrie is een prutser
+Content-Type: multipart/mixed; boundary=AA
+
+This is a multi-part message in MIME format.
+--AA
+Content-Type: multipart/mixed; boundary=BB
+
+This is a multi-part message in MIME format.
+--BB
+Content-Type: text/plain; charset="us-ascii"
+
+This is the first message part containing
+plain text. 
+
+--BB
+Content-Type: text/plain; charset="us-ascii"
+
+This is another plain text message part.
+
+--BB--
+This is the end of MIME multipart.
+
+--AA
+Content-Type: text/html; charset="us-ascii"
+
+<html>
+<body>This is a piece of HTML text.</body>
+</html>
+
+--AA--
+This is the end of  MIME multipart.
+.
+;
+
+test "Basic" {
+	set "a" "a";
+	foreverypart {
+		extracttext "b";
+		if string "${a}" "aaa" {
+			if not string :contains "${b}" "first" {
+				test_fail "bad content extracted: ${b}";
+			}
+		} elsif string "${a}" "aaaa" {
+			if not string :contains "${b}" "another" {
+				test_fail "bad content extracted: ${b}";
+			}
+		} elsif string "${a}" "aaaaa" {
+			if not string :contains "${b}" "HTML text" {
+				test_fail "bad content extracted: ${b}";
+			}
+			if string :contains "${b}" "<html>" {
+				test_fail "content extracted html: ${b}";
+			}
+		}
+		set "a" "a${a}";
+	}
+	if not string "${a}" "aaaaaa" {
+		set :length "parts" "${a}";
+		test_fail "bad number of parts parsed: ${parts}"; 
+	}
+}
+
+test_set "message" text:
+From: <stephan@example.com>
+To: <frop@example.com>
+Subject: Frop!
+
+FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP!
+FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP!
+FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP!
+FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP!
+FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP!
+FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP!
+FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP!
+FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP!
+FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP!
+FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP!
+FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP!
+FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP! FROP!
+.
+;
+
+test "First - less" {
+	foreverypart {
+		extracttext :first 20 "data";
+		if not string "${data}" "FROP! FROP! FROP! FR" {
+			test_fail "Bad data extracted";
+		}
+
+		extracttext :length :first 100 "data_len";
+		if not string "${data_len}" "100" {
+			test_fail "Bad number of bytes extracted";
+		}
+	}
+}
+
+test_set "message" text:
+From: <stephan@example.com>
+To: <frop@example.com>
+Subject: Frop!
+
+FROP! FROP! FROP! FROP!
+.
+;
+
+test "First - more" {
+	foreverypart {
+		extracttext :first 100 "data";
+		if not string :matches "${data}" "FROP! FROP! FROP! FROP!*" {
+			test_fail "Bad data extracted";
+		}
+	}
+}
+
+test_set "message" text:
+From: <stephan@example.com>
+To: <frop@example.com>
+Subject: Frop!
+
+FROP! FROP! FROP! FROP!
+.
+;
+
+test "Modifier" {
+	foreverypart {
+		extracttext :lower :upperfirst "data";
+		if not string :matches "${data}" "Frop! frop! frop! frop!*" {
+			test_fail "Bad data extracted";
+		}
+	}
+}
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/mime/foreverypart.svtest
@@ -0,0 +1,178 @@
+require "vnd.dovecot.testsuite";
+require "relational";
+require "foreverypart";
+require "mime";
+require "variables";
+require "include";
+
+test_set "message" text:
+From: Hendrik <hendrik@example.com>
+To: Harrie <harrie@example.com>
+Date: Sat, 11 Oct 2010 00:31:44 +0200
+Subject: Harrie is een prutser
+Content-Type: multipart/mixed; boundary=AA
+X-Test: AA
+
+This is a multi-part message in MIME format.
+--AA
+Content-Type: multipart/mixed; boundary=BB
+X-Test: BB
+
+This is a multi-part message in MIME format.
+--BB
+Content-Type: text/plain; charset="us-ascii"
+X-Test: CC
+
+Hello
+
+--BB
+Content-Type: text/plain; charset="us-ascii"
+X-Test: DD
+
+Hello again
+
+--BB--
+This is the end of MIME multipart.
+
+--AA
+Content-Type: text/plain; charset="us-ascii"
+X-Test: EE
+
+And again
+
+--AA--
+This is the end of  MIME multipart.
+.
+;
+
+test "Single loop" {
+	set "a" "a";
+	foreverypart {
+		set :length "la" "${a}";
+
+		if string "${a}" "a" {
+			if not header :mime "X-Test" "AA" {
+				test_fail "wrong header extracted (${la})";
+			}
+		} elsif string "${a}" "aa" {
+			if not header :mime "X-Test" "BB" {
+				test_fail "wrong header extracted (${la})";
+			}
+		} elsif string "${a}" "aaa" {
+			if not header :mime "X-Test" "CC" {
+				test_fail "wrong header extracted (${la})";
+			}
+		} elsif string "${a}" "aaaa" {
+			if not header :mime "X-Test" "DD" {
+				test_fail "wrong header extracted (${la})";
+			}
+		} elsif string "${a}" "aaaaa" {
+			if not header :mime "X-Test" "EE" {
+				test_fail "wrong header extracted (${la})";
+			}
+		}
+		set "a" "a${a}";
+	}
+}
+
+test "Double loop" {
+	set "a" "a";
+	foreverypart {
+		set :length "la" "${a}";
+
+		if string "${a}" "a" {
+			if not header :mime "X-Test" "AA" {
+				test_fail "wrong header extracted (${la})";
+			}
+		} elsif string "${a}" "aaaaaa" {
+			if not header :mime "X-Test" "BB" {
+				test_fail "wrong header extracted (${la})";
+			}
+		} elsif string "${a}" "aaaaaaaaa" {
+			if not header :mime "X-Test" "CC" {
+				test_fail "wrong header extracted (${la})";
+			}
+		} elsif string "${a}" "aaaaaaaaaa" {
+			if not header :mime "X-Test" "DD" {
+				test_fail "wrong header extracted (${la})";
+			}
+		} elsif string "${a}" "aaaaaaaaaaa" {
+			if not header :mime "X-Test" "EE" {
+				test_fail "wrong header extracted (${la})";
+			}
+		}
+
+		set "a" "a${a}";
+
+		foreverypart {
+			set :length "la" "${a}";
+
+			if string "${a}" "aa" {
+				if not header :mime "X-Test" "BB" {
+					test_fail "wrong header extracted (${la})";
+				}
+			} elsif string "${a}" "aaa" {
+				if not header :mime "X-Test" "CC" {
+					test_fail "wrong header extracted (${la})";
+				}
+			} elsif string "${a}" "aaaa" {
+				if not header :mime "X-Test" "DD" {
+					test_fail "wrong header extracted (${la})";
+				}
+			} elsif string "${a}" "aaaaa" {
+				if not header :mime "X-Test" "EE" {
+					test_fail "wrong header extracted (${la})";
+				}
+			} elsif string "${a}" "aaaaaaa" {
+				if not header :mime "X-Test" "CC" {
+					test_fail "wrong header extracted (${la})";
+				}
+			} elsif string "${a}" "aaaaaaaa" {
+				if not header :mime "X-Test" "DD" {
+					test_fail "wrong header extracted (${la})";
+				}
+			}
+			set "a" "a${a}";
+		}
+	}
+}
+
+test "Double loop - include" {
+	global "in";
+	global "error";
+	set "in" "a";
+	foreverypart {
+		set :length "la" "${in}";
+
+		if string "${in}" "in" {
+			if not header :mime "X-Test" "AA" {
+				test_fail "wrong header extracted (${la})";
+			}
+		} elsif string "${in}" "aaaaaa" {
+			if not header :mime "X-Test" "BB" {
+				test_fail "wrong header extracted (${la})";
+			}
+		} elsif string "${in}" "aaaaaaaaa" {
+			if not header :mime "X-Test" "CC" {
+				test_fail "wrong header extracted (${la})";
+			}
+		} elsif string "${in}" "aaaaaaaaaa" {
+			if not header :mime "X-Test" "DD" {
+				test_fail "wrong header extracted (${la})";
+			}
+		} elsif string "${in}" "aaaaaaaaaaa" {
+			if not header :mime "X-Test" "EE" {
+				test_fail "wrong header extracted (${la})";
+			}
+		}
+
+		set "in" "a${in}";
+
+		include "include-foreverypart";
+
+		if not string "${error}" "" {
+			test_fail "INCLUDED: ${error}";
+		}
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/mime/header.svtest
@@ -0,0 +1,444 @@
+require "vnd.dovecot.testsuite";
+require "variables";
+require "foreverypart";
+require "mime";
+
+/*
+ * Basic functionality
+ */
+
+test_set "message" text:
+From: stephan@example.com
+To: nico@nl.example.com, harry@de.example.com
+Subject: Frobnitzm
+Comments: This is nonsense.
+Keywords: nonsense, strange, testing
+X-Spam: Yes
+
+Test.
+.
+;
+
+test "Basic functionality" {
+	/* Must match */
+	if not header :mime :anychild :contains ["Subject", "Comments"] "Frobnitzm" {
+		test_fail "failed to match header (1)";
+	}
+
+	if not header :mime :anychild :contains ["Subject", "Comments"] "nonsense" {
+		test_fail "failed to match header(2)";
+	}
+
+	if not header :mime :anychild :matches "Keywords" "*, strange, *" {
+		test_fail "failed to match header (3)";
+	}
+
+	if not header :mime :anychild :is "Comments" "This is nonsense." {
+		test_fail "failed to match header (4)";
+	}
+
+	/* Must not match */
+	if header :mime :anychild ["subject", "comments", "keywords"] "idiotic" {
+		test_fail "matched nonsense";
+	}
+
+	/* Match first key */
+	if not header :mime :anychild :contains ["keywords"] ["strange", "snot", "vreemd"] {
+		test_fail "failed to match first key";
+	}
+
+	/* Match second key */
+	if not header :mime :anychild :contains ["keywords"] ["raar", "strange", "vreemd"] {
+		test_fail "failed to match second key";
+	}
+
+	/* Match last key */
+	if not header :mime :anychild :contains ["keywords"] ["raar", "snot", "strange"] {
+		test_fail "failed to match last key";
+	}
+
+	/* First header */
+	if not header :mime :anychild :contains ["keywords", "subject"]
+		["raar", "strange", "vreemd"] {
+		test_fail "failed to match first header";
+	}
+
+	/* Second header */
+	if not header :mime :anychild :contains ["subject", "keywords"]
+		["raar", "strange", "vreemd"] {
+		test_fail "failed to match second header";
+	}
+}
+
+/*
+ * Basic functionality - foreverypart
+ */
+
+test "Basic functionality - foreverypart" {
+	foreverypart {
+		/* Must match */
+		if not header :mime :anychild :contains ["Subject", "Comments"] "Frobnitzm" {
+			test_fail "failed to match header (1)";
+		}
+
+		if not header :mime :anychild :contains ["Subject", "Comments"] "nonsense" {
+			test_fail "failed to match header(2)";
+		}
+
+		if not header :mime :anychild :matches "Keywords" "*, strange, *" {
+			test_fail "failed to match header (3)";
+		}
+
+		if not header :mime :anychild :is "Comments" "This is nonsense." {
+			test_fail "failed to match header (4)";
+		}
+
+		/* Must not match */
+		if header :mime :anychild ["subject", "comments", "keywords"] "idiotic" {
+			test_fail "matched nonsense";
+		}
+
+		/* Match first key */
+		if not header :mime :anychild :contains ["keywords"] ["strange", "snot", "vreemd"] {
+			test_fail "failed to match first key";
+		}
+
+		/* Match second key */
+		if not header :mime :anychild :contains ["keywords"] ["raar", "strange", "vreemd"] {
+			test_fail "failed to match second key";
+		}
+
+		/* Match last key */
+		if not header :mime :anychild :contains ["keywords"] ["raar", "snot", "strange"] {
+			test_fail "failed to match last key";
+		}
+
+		/* First header */
+		if not header :mime :anychild :contains ["keywords", "subject"]
+			["raar", "strange", "vreemd"] {
+			test_fail "failed to match first header";
+		}
+
+		/* Second header */
+		if not header :mime :anychild :contains ["subject", "keywords"]
+			["raar", "strange", "vreemd"] {
+			test_fail "failed to match second header";
+		}
+	}
+}
+
+/*
+ * Matching empty key
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.com
+X-Caffeine: C8H10N4O2
+Subject: I need coffee!
+Comments:
+
+Text
+.
+;
+
+test "Matching empty key" {
+	if header :mime :anychild :is "X-Caffeine" "" {
+		test_fail ":is-matched non-empty header with empty string";
+	}
+
+	if not header :mime :anychild :contains "X-Caffeine" "" {
+		test_fail "failed to match existing header with empty string";
+	}
+
+	if not header :mime :anychild :is "comments" "" {
+		test_fail "failed to match empty header :mime :anychild with empty string";
+	}
+
+	if header :mime :anychild :contains "X-Nonsense" "" {
+		test_fail ":contains-matched non-existent header with empty string";
+	}
+}
+
+/*
+ * Matching empty key - foreverypart
+ */
+
+test "Matching empty key - foreverypart" {
+	foreverypart {
+		if header :mime :anychild :is "X-Caffeine" "" {
+			test_fail ":is-matched non-empty header with empty string";
+		}
+
+		if not header :mime :anychild :contains "X-Caffeine" "" {
+			test_fail "failed to match existing header with empty string";
+		}
+
+		if not header :mime :anychild :is "comments" "" {
+			test_fail "failed to match empty header :mime :anychild with empty string";
+		}
+
+		if header :mime :anychild :contains "X-Nonsense" "" {
+			test_fail ":contains-matched non-existent header with empty string";
+		}
+	}
+}
+
+/*
+ * Ignoring whitespace
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.com
+Subject:         Help
+X-A:     Text
+X-B: Text   
+
+Text
+.
+;
+
+test "Ignoring whitespace" {
+	if not header :mime :anychild :is "x-a" "Text" {
+		if header :mime :anychild :matches "x-a" "*" {
+			set "header" "${1}"; 
+		}
+		test_fail "header :mime :anychild test does not strip leading whitespace (header=`${header}`)";
+	}
+
+	if not header :mime :anychild :is "x-b" "Text" {
+		if header :mime :anychild :matches "x-b" "*" {
+			set "header" "${1}"; 
+		}
+		test_fail "header :mime :anychild test does not strip trailing whitespace (header=`${header}`)";
+	}
+
+	if not header :mime :anychild :is "subject" "Help" {
+		if header :mime :anychild :matches "subject" "*" {
+			set "header" "${1}"; 
+		}
+		test_fail "header :mime :anychild test does not strip both leading and trailing whitespace (header=`${header}`)";
+	}
+}
+
+/*
+ * Ignoring whitespace - foreverypart
+ */
+
+test "Ignoring whitespace - foreverypart" {
+	foreverypart {
+		if not header :mime :anychild :is "x-a" "Text" {
+			if header :mime :anychild :matches "x-a" "*" {
+				set "header" "${1}"; 
+			}
+			test_fail "header :mime :anychild test does not strip leading whitespace (header=`${header}`)";
+		}
+
+		if not header :mime :anychild :is "x-b" "Text" {
+			if header :mime :anychild :matches "x-b" "*" {
+				set "header" "${1}"; 
+			}
+			test_fail "header :mime :anychild test does not strip trailing whitespace (header=`${header}`)";
+		}
+
+		if not header :mime :anychild :is "subject" "Help" {
+			if header :mime :anychild :matches "subject" "*" {
+				set "header" "${1}"; 
+			}
+			test_fail "header :mime :anychild test does not strip both leading and trailing whitespace (header=`${header}`)";
+		}
+	}
+}
+
+/*
+ * Absent or empty header
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.com
+CC: harry@nonsense.ex
+Subject:
+Comments:
+
+Text
+.
+;
+
+test "Absent or empty header" {
+	if not header :mime :anychild :matches "Cc" "?*" {
+		test_fail "CC header is not absent or empty";
+	}
+
+	if header :mime :anychild :matches "Subject" "?*" {
+		test_fail "Subject header is empty, but matched otherwise";
+	}
+
+	if header :mime :anychild :matches "Comment" "?*" {
+		test_fail "Comment header is empty, but matched otherwise";
+	}
+}
+
+/*
+ * Absent or empty header - foreverypart
+ */
+
+test "Absent or empty header - foreverypart" {
+	foreverypart {
+		if not header :mime :anychild :matches "Cc" "?*" {
+			test_fail "CC header is not absent or empty";
+		}
+
+		if header :mime :anychild :matches "Subject" "?*" {
+			test_fail "Subject header is empty, but matched otherwise";
+		}
+
+		if header :mime :anychild :matches "Comment" "?*" {
+			test_fail "Comment header is empty, but matched otherwise";
+		}
+	}
+}
+
+
+/*
+ * Invalid header name
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.com
+Subject: Valid message
+X-Multiline: This is a multi-line
+ header body, which should be
+ unfolded correctly.
+
+Text
+.
+;
+
+test "Invalid header name" {
+	if header :mime :anychild :contains "subject:" "" {
+		test_fail "matched invalid header name";
+	}
+
+	if header :mime :anychild :contains "to!" "" {
+		test_fail "matched invalid header name";
+	}
+}
+
+/*
+ * Invalid header name - foreverypart
+ */
+
+test "Invalid header name - foreverypart" {
+	foreverypart {
+		if header :mime :anychild :contains "subject:" "" {
+			test_fail "matched invalid header name";
+		}
+
+		if header :mime :anychild :contains "to!" "" {
+			test_fail "matched invalid header name";
+		}
+	}
+}
+
+/*
+ * Folded headers
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.com
+Subject: Not enough space on a line!
+X-Multiline: This is a multi-line
+ header body, which should be
+ unfolded correctly.
+
+Text
+.
+;
+
+test "Folded headers" {
+	if not header :mime :anychild :is "x-multiline"
+		"This is a multi-line header body, which should be unfolded correctly." {
+		test_fail "failed to properly unfold folded header.";
+	}
+}
+
+/*
+ * Folded headers - foreverypart
+ */
+
+test "Folded headers - foreverypart" {
+	foreverypart {
+		if not header :mime :anychild :is "x-multiline"
+			"This is a multi-line header body, which should be unfolded correctly." {
+			test_fail "failed to properly unfold folded header.";
+		}
+	}
+}
+
+/*
+ * Multipart anychild
+ */
+
+test_set "message" text:
+From: Hendrik <hendrik@example.com>
+To: Harrie <harrie@example.com>
+Date: Sat, 11 Oct 2010 00:31:44 +0200
+Subject: Harrie is een prutser
+Content-Type: multipart/mixed; boundary=AA
+X-Test: AA
+
+This is a multi-part message in MIME format.
+--AA
+Content-Type: multipart/mixed; boundary=BB
+X-Test: BB
+
+This is a multi-part message in MIME format.
+--BB
+Content-Type: text/plain; charset="us-ascii"
+X-Test: CC
+
+Hello
+
+--BB
+Content-Type: text/plain; charset="us-ascii"
+X-Test: DD
+
+Hello again
+
+--BB--
+This is the end of MIME multipart.
+
+--AA
+Content-Type: text/plain; charset="us-ascii"
+X-Test: EE
+
+And again
+
+--AA--
+This is the end of  MIME multipart.
+.
+;
+
+test "Multipart anychild" {
+	if not header :mime :anychild "X-Test" "AA" {
+		test_fail "No AA";
+	}
+	if not header :mime :anychild "X-Test" "BB" {
+		test_fail "No BB";
+	}
+	if not header :mime :anychild "X-Test" "CC" {
+		test_fail "No CC";
+	}
+	if not header :mime :anychild "X-Test" "DD" {
+		test_fail "No DD";
+	}
+	if not header :mime :anychild "X-Test" "EE" {
+		test_fail "No EE";
+	}
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/mime/included/include-foreverypart.sieve
@@ -0,0 +1,44 @@
+require "include";
+require "foreverypart";
+require "mime";
+require "variables";
+
+global "in";
+global "error";
+
+foreverypart {
+	set :length "la" "${in}";
+
+	if string "${in}" "aa" {
+		if not header :mime "X-Test" "BB" {
+			set "error" "wrong header extracted (${la})";
+			return;
+		}
+	} elsif string "${in}" "aaa" {
+		if not header :mime "X-Test" "CC" {
+			set "error" "wrong header extracted (${la})";
+			return;
+		}
+	} elsif string "${in}" "aaaa" {
+		if not header :mime "X-Test" "DD" {
+			set "error" "wrong header extracted (${la})";
+			return;
+		}
+	} elsif string "${in}" "aaaaa" {
+		if not header :mime "X-Test" "EE" {
+			set "error" "wrong header extracted (${la})";
+			return;
+		}
+	} elsif string "${in}" "aaaaaaa" {
+		if not header :mime "X-Test" "CC" {
+			set "error" "wrong header extracted (${la})";
+			return;
+		}
+	} elsif string "${in}" "aaaaaaaa" {
+		if not header :mime "X-Test" "DD" {
+			set "error" "wrong header extracted (${la})";
+			return;
+		}
+	}
+	set "in" "a${in}";
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/mime/included/include-loop-2.sieve
@@ -0,0 +1,6 @@
+require "foreverypart";
+require "include";
+
+foreverypart :name "friep" {
+	include "include-loop-3";
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/mime/included/include-loop-3.sieve
@@ -0,0 +1,6 @@
+require "foreverypart";
+require "include";
+
+foreverypart :name "frml" {
+	include "include-loop-4";
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/mime/included/include-loop-4.sieve
@@ -0,0 +1,6 @@
+require "foreverypart";
+require "include";
+
+foreverypart {
+	include "include-loop-5";
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/mime/included/include-loop-5.sieve
@@ -0,0 +1,9 @@
+require "foreverypart";
+require "include";
+require "mime";
+
+foreverypart {
+	if header :mime :subtype "content-type" "plain" {
+		break;
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/regex/basic.svtest
@@ -0,0 +1,51 @@
+require "vnd.dovecot.testsuite";
+
+require "regex";
+require "variables";
+
+test_set "message" text:
+From: stephan+sieve@friep.example.com
+To: tss@example.net, nico@nl.example.com, sirius@fi.example.com
+Subject: Test
+
+Test message.
+.
+;
+
+test "Basic example" {
+	if not address :regex :comparator "i;ascii-casemap" "from" [
+		"stephan(\\+.*)?@it\\.example\\.com",
+		"stephan(\\+.*)?@friep\\.example\\.com"
+		] {
+		test_fail "failed to match";
+	}
+}
+
+test "No values" {
+	if header :regex "cc" [".*\\.com", ".*\\.nl"] {
+		test_fail "matched inappropriately";
+	}
+}
+
+
+test "More values" {
+	if address :regex "to" [".*\\.uk", ".*\\.nl", ".*\\.tk"] {
+		test_fail "matched inappropriately";
+	}
+
+	if not address :regex "to" [".*\\.uk", ".*\\.nl", ".*\\.tk", ".*fi\\..*"] {
+		test_fail "failed to match last";
+	}
+}
+
+test "Variable regex" {
+	set "regex" "stephan[+](sieve)@friep.example.com";
+
+	if not header :regex "from" "${regex}" {
+		test_fail "failed to match variable regex";
+	}
+
+	if not string "${1}" "sieve" {
+		test_fail "failed to extract proper match value from variable regex";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/regex/errors.svtest
@@ -0,0 +1,29 @@
+require "vnd.dovecot.testsuite";
+
+require "relational";
+require "comparator-i;ascii-numeric";
+
+test "Compile errors" {
+	if test_script_compile "errors/compile.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "5" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+test "Runtime errors" {
+	if not test_script_compile "errors/runtime.sieve" {
+		test_fail "failed to compile";
+	}
+
+	if not test_script_run {
+		test_fail "script should have run fine";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "1" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/regex/errors/compile.sieve
@@ -0,0 +1,25 @@
+require "regex";
+require "comparator-i;ascii-numeric";
+require "envelope";
+
+if address :regex :comparator "i;ascii-numeric" "from" "sirius(\\+.*)?@friep\\.example\\.com" {
+	keep;
+	stop;
+}
+
+if address :regex "from" "sirius(+\\+.*)?@friep\\.example\\.com" {
+	keep;
+	stop;
+}
+
+if header :regex "from" "sirius(\\+.*)?@friep\\.ex[]ample.com" {
+    keep;
+    stop;
+}
+
+if envelope :regex "from" "sirius(\\+.*)?@friep\\.ex[]ample.com" {
+    keep;
+    stop;
+}
+
+discard;
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/regex/errors/runtime.sieve
@@ -0,0 +1,9 @@
+require "regex";
+require "variables";
+require "fileinto";
+
+set "regex" "[";
+
+if header :regex "to" "${regex}" {
+	fileinto "frop";
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/regex/match-values.svtest
@@ -0,0 +1,72 @@
+require "vnd.dovecot.testsuite";
+
+require "regex";
+require "variables";
+
+test_set "message" text:
+From: Andy Howell <AndyHowell@example.com>
+Sender: antlr-interest-bounces@ant.example.com
+To: Stephan Bosch <stephan@example.org>
+Subject: [Dovecot] Sieve regex match problem
+
+Hi,
+
+I is broken.
+.
+;
+
+test "Basic match values 1" {
+	if header :regex ["Sender"] ["([^-@]*)-([^-@]*)(-bounces)?@ant.example.com"] {
+
+		if not string :is "${1}" "antlr" {
+			test_fail "first match value is not correct";
+		}
+
+		if not string :is "${2}" "interest" {
+			test_fail "second match value is not correct";
+		}
+
+		if not string :is "${3}" "-bounces" {
+			test_fail "third match value is not correct";
+		}
+
+		if string :is "${4}" "-bounces" {
+			test_fail "fourth match contains third value";
+		}
+	} else {
+		test_fail "failed to match";
+	}
+}
+
+test "Basic match values 2" {
+	if header :regex ["Sender"] ["(.*>[ \\t]*,?[ \\t]*)?([^-@]*)-([^-@]*)(-bounces)?@ant.example.com"] {
+
+		if not string :is "${1}" "" {
+			test_fail "first match value is not correct: ${1}";
+		}
+
+		if not string :is "${2}" "antlr" {
+			test_fail "second match value is not correct: ${2}";
+		}
+
+		if not string :is "${3}" "interest" {
+			test_fail "third match value is not correct: ${3}";
+		}
+
+		if not string :is "${4}" "-bounces" {
+			test_fail "fourth match value is not correct: ${4}";
+		}
+
+		if string :is "${5}" "-bounces" {
+			test_fail "fifth match contains fourth value: ${5}";
+		}
+	} else {
+		test_fail "failed to match";
+	}
+}
+
+
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/reject/execute.svtest
@@ -0,0 +1,34 @@
+require "vnd.dovecot.testsuite";
+require "relational";
+require "comparator-i;ascii-numeric";
+
+test_set "message" text:
+To: nico@frop.example.org
+From: stephan@example.org
+Subject: Test
+
+Test.
+.
+;
+
+test "Execute" {
+	if not test_script_compile "execute/basic.sieve" {
+		test_fail "script compile failed";
+	}
+
+	if not test_script_run {
+		test_fail "script run failed";
+	}
+
+	if not test_result_action :count "eq" :comparator "i;ascii-numeric" "1" {
+		test_fail "invalid number of actions in result";
+	}
+
+	if not test_result_action :index 1 "reject" {
+		test_fail "reject action missing from result";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/reject/execute/basic.sieve
@@ -0,0 +1,8 @@
+require "reject";
+
+if address :contains "to" "frop.example" {
+	reject "Don't send unrequested messages.";
+	stop;
+}
+
+keep;
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/reject/smtp.svtest
@@ -0,0 +1,56 @@
+require "vnd.dovecot.testsuite";
+require "envelope";
+require "reject";
+
+test_set "message" text:
+From: stephan@example.org
+To: tss@example.net
+Subject: Frop!
+
+Frop!
+.
+;
+
+test_set "envelope.from" "sirius@example.org";
+test_set "envelope.to" "timo@example.net";
+
+test "Basic" {
+	reject "I don't want your mail";
+
+	if not test_result_execute {
+        test_fail "failed to execute reject";
+    }
+
+    test_message :smtp 0;
+
+    if not address :is "to" "sirius@example.org" {
+        test_fail "to address incorrect";
+    }
+
+    if not header :contains "from" "Postmaster" {
+        test_fail "from address incorrect";
+    }
+
+	if not envelope :is "to" "sirius@example.org" {
+		test_fail "envelope recipient incorrect";
+	}
+
+	if not envelope :is "from" "" {
+		test_fail "envelope sender not null";
+	}
+}
+
+test_result_reset;
+test_set "envelope.from" "<>";
+
+test "Null Sender" {
+	reject "I don't want your mail";
+
+	if not test_result_execute {
+		test_fail "failed to execute reject";
+	}
+
+	if test_message :smtp 0 {
+		test_fail "reject sent message to NULL sender";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/relational/basic.svtest
@@ -0,0 +1,178 @@
+require "vnd.dovecot.testsuite";
+
+require "relational";
+require "comparator-i;ascii-numeric";
+
+/*
+ * Test message
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Cc: frop@example.org
+CC: timo@example.org
+X-Spam-Score: 300
+X-Nonsense: 1000
+X-Nonsense: 20
+X-Alpha: abcdzyx
+X-Count: a
+X-Count: b
+X-Count: c
+X-Count: d
+X-Count: e
+X-Count: f
+X-Count: g
+X-Count: h
+X-Count: i
+X-Count: j
+X-Count: k
+X-Count: l
+X-Count: m
+X-Count: n
+X-Count: o
+X-Count: p
+X-Count: q
+X-Count: r
+X-Count: s
+X-Count: t
+X-Count: u
+X-Count: v
+X-Count: w
+X-Count: x
+X-Count: y
+X-Count: z
+Subject: Test
+Comment:
+
+Test!
+.
+;
+
+/*
+ * Empty strings
+ */
+
+test "Value \"\" eq 40 (vs)" {
+	if header :value "eq" :comparator "i;ascii-numeric" "comment" "40" {
+		test_fail ":value matched empty string with i;ascii-numeric";
+	}
+
+	if header :value "gt" :comparator "i;ascii-numeric" "x-spam-score" "" {
+		test_fail ":value 300 exceeded empty string with i;ascii-numeric";
+	}
+
+	if header :value "gt" :comparator "i;ascii-numeric" "x-spam-score" "" {
+		test_fail ":count exceeded empty string with i;ascii-numeric";
+	}
+}
+
+/*
+ * Match type :value
+ */
+
+test "Value 300 eq 2" {
+	if header :value "eq" :comparator "i;ascii-numeric" "x-spam-score" "2" {
+		test_fail "should not have matched";
+	}
+}
+
+test "Value 300 lt 2" {
+	if header :value "lt" :comparator "i;ascii-numeric" "x-spam-score" "2" {
+		test_fail "should not have matched";
+	}
+}
+
+test "Value 300 le 300" {
+	if not header :value "le" :comparator "i;ascii-numeric" "x-spam-score" "300" {
+		test_fail "should have matched";
+	}
+}
+
+test "Value 300 le 302" {
+	if not header :value "le" :comparator "i;ascii-numeric" "x-spam-score" "302" {
+		test_fail "should have matched";
+	}
+}
+
+test "Value 302 le 00302" {
+	if not header :value "le" :comparator "i;ascii-numeric" "x-spam-score" "00302" {
+		test_fail "should have matched";
+	}
+}
+
+test "Value {1000,20} le 300" {
+	if not header :value "le" :comparator "i;ascii-numeric" "x-nonsense" "300" {
+		test_fail "should have matched";
+	}
+}
+
+test "Value {1000,20} lt 3" {
+	if header :value "lt" :comparator "i;ascii-numeric" "x-nonsense" "3" {
+		test_fail "should not have matched";
+	}
+}
+
+test "Value {1000,20} gt 3000" {
+	if header :value "gt" :comparator "i;ascii-numeric" "x-nonsense" "3000" {
+		test_fail "should not have matched";
+	}
+}
+
+test "Value {1000,20} gt {3000,30}" {
+	if not header :value "gt" :comparator "i;ascii-numeric" "x-nonsense" ["3000","30"] {
+		test_fail "should have matched";
+	}
+}
+
+test "Value {1000,20} lt {3, 19})" {
+	if header :value "lt" :comparator "i;ascii-numeric" "x-nonsense" ["3","19"] {
+		test_fail "should not have matched";
+	}
+}
+
+test "Value {1000,20} gt {3000,1001}" {
+	if header :value "gt" :comparator "i;ascii-numeric" "x-nonsense" ["3000","1001"] {
+		test_fail "should not have matched";
+	}
+}
+
+test "Value abcdzyz gt aaaaaaa" {
+	if not header :value "gt" :comparator "i;octet" "x-alpha" "aaaaaaa" {
+		test_fail "should have matched";
+	}
+}
+
+/*
+ * Match type :count
+ */
+
+test "Count 2 ne 2" {
+	if header :count "ne" :comparator "i;ascii-numeric" "cc" "2" {
+		test_fail "should not have matched";
+	}
+}
+
+test "Count 2 ge 2" {
+	if not header :count "ge" :comparator "i;ascii-numeric" "cc" "2" {
+		test_fail "should have matched";
+	}
+}
+
+test "Count 2 ge 002" {
+	if not header :count "ge" :comparator "i;ascii-numeric" "cc" "002" {
+		test_fail "should have matched";
+	}
+}
+
+test "Count 26 lt {4,5,6,10,20}" {
+	if header :count "lt" :comparator "i;ascii-numeric" "x-count" ["4","5","6","10","20"] {
+		test_fail "should not have matched";
+	}
+}
+
+test "Count 26 lt {4,5,6,10,20,100}" {
+	if not header :count "lt" :comparator "i;ascii-numeric" "x-count" ["4","5","6","10","20","100"] {
+		test_fail "should have matched";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/relational/comparators.svtest
@@ -0,0 +1,258 @@
+require "vnd.dovecot.testsuite";
+require "variables";
+require "relational";
+require "comparator-i;ascii-numeric";
+
+/*
+ * Comparator i;octet
+ */
+
+test "i;octet" {
+	if not string :comparator "i;octet" :value "eq" "" "" {
+		test_fail "not '' eq ''";
+	}
+
+	if not string :comparator "i;octet" :value "gt" "a" "" {
+		test_fail "not 'a' gt ''";
+	}
+
+	if not string :comparator "i;octet" :value "lt" "" "a" {
+		test_fail "not '' lt 'a'";
+	}
+
+	if not string :comparator "i;octet" :value "gt" "ab" "a" {
+		test_fail "not 'ab' gt 'a'";
+	}
+
+	if not string :comparator "i;octet" :value "lt" "a" "ab" {
+		test_fail "not 'a' lt 'ab'";
+	}
+
+	if not string :comparator "i;octet" :value "gt" "ba" "ab" {
+		test_fail "not 'ba' gt 'ab'";
+	}
+
+	if not string :comparator "i;octet" :value "lt" "ab" "ba" {
+		test_fail "not 'ab' lt 'ba'";
+	}
+
+	if not string :comparator "i;octet" :value "eq" "abcd" "abcd" {
+		test_fail "not 'abcd' eq 'abcd'";
+	}
+
+	if not string :comparator "i;octet" :value "lt" "abcce" "abcde" {
+		test_fail "not 'abcce' lt 'abcde'";
+	}
+
+	if not string :comparator "i;octet" :value "gt" "abcde" "abcce" {
+		test_fail "not 'abcde' gt 'abcce'";
+	}
+
+	if not string :comparator "i;octet" :value "lt" "abcce" "abcd" {
+		test_fail "not 'abcce' lt 'abcd'";
+	}
+
+	if not string :comparator "i;octet" :value "gt" "abcd" "abcce" {
+		test_fail "not 'abcd' gt 'abcce'";
+	}
+
+	if not string :comparator "i;octet" :value "lt" "Z" "b" {
+		test_fail "not 'Z' lt 'b'";
+	}
+}
+
+/*
+ * Comparator i;ascii-casemap
+ */
+
+test "i;ascii-casemap" {
+	if not string :comparator "i;ascii-casemap" :value "eq" "" "" {
+		test_fail "not '' eq ''";
+	}
+
+	if not string :comparator "i;ascii-casemap" :value "gt" "a" "" {
+		test_fail "not 'a' gt ''";
+	}
+
+	if not string :comparator "i;ascii-casemap" :value "lt" "" "a" {
+		test_fail "not '' lt 'a'";
+	}
+
+	if not string :comparator "i;ascii-casemap" :value "gt" "ab" "a" {
+		test_fail "not 'ab' gt 'a'";
+	}
+
+	if not string :comparator "i;ascii-casemap" :value "lt" "a" "ab" {
+		test_fail "not 'a' lt 'ab'";
+	}
+
+	if not string :comparator "i;ascii-casemap" :value "gt" "ba" "ab" {
+		test_fail "not 'ba' gt 'ab'";
+	}
+
+	if not string :comparator "i;ascii-casemap" :value "lt" "ab" "ba" {
+		test_fail "not 'ab' lt 'ba'";
+	}
+
+	if not string :comparator "i;ascii-casemap" :value "eq" "abcd" "abcd" {
+		test_fail "not 'abcd' eq 'abcd'";
+	}
+
+	if not string :comparator "i;ascii-casemap" :value "lt" "abcce" "abcde" {
+		test_fail "not 'abcce' lt 'abcde'";
+	}
+
+	if not string :comparator "i;ascii-casemap" :value "gt" "abcde" "abcce" {
+		test_fail "not 'abcde' gt 'abcce'";
+	}
+
+	if not string :comparator "i;ascii-casemap" :value "lt" "abcce" "abcd" {
+		test_fail "not 'abcce' lt 'abcd'";
+	}
+
+	if not string :comparator "i;ascii-casemap" :value "gt" "abcd" "abcce" {
+		test_fail "not 'abcd' gt 'abcce'";
+	}
+
+	if not string :comparator "i;ascii-casemap" :value "gt" "Z" "b" {
+		test_fail "not 'Z' gt 'b'";
+	}
+}
+
+/*
+ * Comparator i;ascii-numeric
+ */
+
+test "i;ascii-numeric" {
+	/* Non-digit characters; equality */
+
+	if not string :comparator "i;ascii-numeric" :value "eq" "" "" {
+		test_fail "not '' eq ''";
+	}
+
+	if not string :comparator "i;ascii-numeric" :value "eq" "a" "" {
+		test_fail "not 'a' eq ''";
+	}
+
+	if not string :comparator "i;ascii-numeric" :value "eq" "" "a" {
+		test_fail "not '' eq 'a'";
+	}
+
+	if not string :comparator "i;ascii-numeric" :value "eq" "a" "b" {
+		test_fail "not 'a' eq 'b'";
+	}
+
+	if not string :comparator "i;ascii-numeric" :value "eq" "b" "a" {
+		test_fail "not 'b' eq 'a'";
+	}
+
+	if string :comparator "i;ascii-numeric" :value "eq" "a" "0" {
+		test_fail "'a' eq '0'";
+	}
+
+	if string :comparator "i;ascii-numeric" :value "eq" "0" "a" {
+		test_fail "'0' eq 'a'";
+	}
+
+	if not string :comparator "i;ascii-numeric" :value "ne" "a" "0" {
+		test_fail "not 'a' ne '0'";
+	}
+
+	if not string :comparator "i;ascii-numeric" :value "ne" "0" "a" {
+		test_fail "not '0' ne 'a'";
+	}
+
+	/* Non-digit characters; comparison */
+
+	if string :comparator "i;ascii-numeric" :value "lt" "a" "0" {
+		test_fail "'a' lt '0'";
+	}
+
+	if not string :comparator "i;ascii-numeric" :value "lt" "0" "a" {
+		test_fail "not '0' lt 'a'";
+	}
+
+	if not string :comparator "i;ascii-numeric" :value "gt" "a" "0" {
+		test_fail "not 'a' gt '0'";
+	}
+
+	if string :comparator "i;ascii-numeric" :value "gt" "0" "a" {
+		test_fail "'0' gt 'a'";
+	}
+
+	if not string :comparator "i;ascii-numeric" :value "ge" "a" "0" {
+		test_fail "not 'a' ge '0'";
+	}
+
+	if string :comparator "i;ascii-numeric" :value "ge" "0" "a" {
+		test_fail "'0' ge 'a'";
+	}
+
+	if string :comparator "i;ascii-numeric" :value "le" "a" "0" {
+		test_fail "'a' le '0'";
+	}
+
+	if not string :comparator "i;ascii-numeric" :value "le" "0" "a" {
+		test_fail "not '0' le 'a'";
+	}
+
+	if not string :comparator "i;ascii-numeric" :value "eq" "0" "0" {
+		test_fail "not '0' eq '0'";
+	}
+
+	/* Digit characters; basic comparison */
+
+	if not string :comparator "i;ascii-numeric" :value "eq" "2" "2" {
+		test_fail "not '2' eq '2'";
+	}
+
+	if not string :comparator "i;ascii-numeric" :value "gt" "2" "1" {
+		test_fail "not '2' gt '1'";
+	}
+
+	if not string :comparator "i;ascii-numeric" :value "lt" "1" "2" {
+		test_fail "not '1' lt '2'";
+	}
+
+	if not string :comparator "i;ascii-numeric" :value "lt" "65535" "65635" {
+		test_fail "not '65535' lt '65635'";
+	}
+
+	if not string :comparator "i;ascii-numeric" :value "gt" "65635" "65535" {
+		test_fail "not '65635' gt '65535'";
+	}
+
+	/* Digit characters; leading zeros */
+
+	if not string :comparator "i;ascii-numeric" :value "eq" "0" "000" {
+		test_fail "not '0' eq '000'";
+	}
+
+	if not string :comparator "i;ascii-numeric" :value "eq" "000" "0" {
+		test_fail "not '0' eq '000'";
+	}
+
+	if not string :comparator "i;ascii-numeric" :value "eq" "02" "0002" {
+		test_fail "not '02' eq '0002'";
+	}
+
+	if not string :comparator "i;ascii-numeric" :value "eq" "0002" "02" {
+		test_fail "not '0002' eq '02'";
+	}
+
+	if not string :comparator "i;ascii-numeric" :value "gt" "2" "001" {
+		test_fail "not '2' gt '001'";
+	}
+
+	if not string :comparator "i;ascii-numeric" :value "lt" "001" "2" {
+		test_fail "not '001' lt '2'";
+	}
+
+	if not string :comparator "i;ascii-numeric" :value "gt" "002" "1" {
+		test_fail "not '002' gt '1'";
+	}
+
+	if not string :comparator "i;ascii-numeric" :value "lt" "1" "002" {
+		test_fail "not '1' lt '002'";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/relational/errors.svtest
@@ -0,0 +1,15 @@
+require "vnd.dovecot.testsuite";
+
+# A bit awkward to test the extension with itself
+require "relational";
+require "comparator-i;ascii-numeric";
+
+test "Validation errors" {
+	if test_script_compile "errors/validation.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if test_error :count "ne" "3" {
+		test_fail "wrong number of errors reported";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/relational/errors/validation.sieve
@@ -0,0 +1,11 @@
+require "relational";
+
+# Not a valid relation (1)
+if header :value "gr" "from" "ah" {
+	keep;
+}
+
+# Not a valid relation (1)
+if header :count "lf" "from" "eek" {
+	keep;
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/relational/rfc.svtest
@@ -0,0 +1,71 @@
+require "vnd.dovecot.testsuite";
+
+require "relational";
+require "comparator-i;ascii-numeric";
+
+test_set "message" text:
+Received: ...
+Received: ...
+Subject: example
+To: foo@example.com, baz@example.com
+CC: qux@example.com
+
+RFC Example
+.
+;
+
+test "Example 1" {
+	# The test:
+
+	if not address :count "ge" :comparator "i;ascii-numeric"
+		["to", "cc"] ["3"] {
+
+		test_fail "should have counted three addresses";
+	}
+
+    # would evaluate to true, and the test
+
+	if anyof (
+			address :count "ge" :comparator "i;ascii-numeric"
+				["to"] ["3"],
+			address :count "ge" :comparator "i;ascii-numeric"
+				["cc"] ["3"]
+	) {
+
+		test_fail "should not have counted three addresses";
+	}
+
+	# would evaluate to false.
+
+	# To check the number of received fields in the header, the following
+	# test may be used:
+
+	if header :count "ge" :comparator "i;ascii-numeric"
+ 		["received"] ["3"] {
+
+		test_fail "should not have counted three received headers";
+	}
+
+	# This would evaluate to false.  But
+
+	if not header :count "ge" :comparator "i;ascii-numeric"
+		["received", "subject"] ["3"] {
+
+		test_fail "should have counted three headers";
+	}
+
+	# would evaluate to true.
+
+	# The test:
+
+	if header :count "ge" :comparator "i;ascii-numeric"
+		["to", "cc"] ["3"] {
+
+		test_fail "should not have counted three to or cc headers";
+	}
+
+	# will always evaluate to false on an RFC 2822 compliant message
+	# [RFC2822], since a message can have at most one "to" field and at
+	# most one "cc" field.  This test counts the number of fields, not the
+	# number of addresses.
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/spamvirustest/errors.svtest
@@ -0,0 +1,15 @@
+require "vnd.dovecot.testsuite";
+
+require "comparator-i;ascii-numeric";
+require "relational";
+
+test "Syntax errors" {
+	if test_script_compile "errors/syntax-errors.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "5" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/spamvirustest/errors/syntax-errors.sieve
@@ -0,0 +1,19 @@
+require "spamtest";
+require "virustest";
+
+# Value not a string
+if spamtest 3 {
+}
+
+# Value not a string
+if virustest 3 {
+}
+
+# Missing value argument
+if spamtest :matches :comparator "i;ascii-casemap" {
+}
+
+# Inappropriate :percent argument
+if spamtest :percent "3" {
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/spamvirustest/spamtest.svtest
@@ -0,0 +1,276 @@
+require "vnd.dovecot.testsuite";
+require "spamtest";
+require "relational";
+require "comparator-i;ascii-numeric";
+require "variables";
+
+/*
+ * Value
+ */
+
+test_set "message" text:
+From: legitimate@example.com
+To: victim@dovecot.example.net
+Subject: Not spammish
+X-SpamCheck: No, score=-1.6 required=5.0 autolearn=no version=3.2.5
+X-SpamCheck1: No, score=0.0 required=5.0 autolearn=no version=3.2.5
+X-SpamCheck2: No, score=1.0 required=5.0 autolearn=no version=3.2.5
+X-SpamCheck3: No, score=4.0 required=5.0 autolearn=no version=3.2.5
+X-SpamCheck4: Yes, score=5.0 required=5.0 autolearn=no version=3.2.5
+X-SpamCheck5: Yes, score=7.6 required=5.0 autolearn=no version=3.2.5
+
+Test!
+.
+;
+
+test_config_set "sieve_spamtest_status_header"
+	"X-SpamCheck:[ \\ta-zA-Z]+, score=(-?[0-9]+.[0-9]+)";
+test_config_set "sieve_spamtest_max_header"
+	"X-SpamCheck:[ \\ta-zA-Z]+, score=-?[0-9]+.[0-9]+ required=(-?[0-9]+.[0-9]+)";
+test_config_set "sieve_spamtest_status_type" "score";
+test_config_reload :extension "spamtest";
+
+test "Value: subzero" {
+	if spamtest :is "0" {
+		test_fail "spamtest not configured or test failed";
+	}
+
+	if not spamtest :is "1" {
+		if spamtest :matches "*" { }
+		test_fail "wrong spam value produced: ${1}";
+	}
+
+	if spamtest :is "2" {
+		test_fail "spam test matches anything";
+	}
+}
+
+test_config_set "sieve_spamtest_status_header"
+	"X-SpamCheck1:[ \\ta-zA-Z]+, score=(-?[0-9]+.[0-9]+)";
+test_config_reload :extension "spamtest";
+
+test "Value: zero" {
+	if spamtest :is "0" {
+		test_fail "spamtest not configured or test failed";
+	}
+
+	if not spamtest :is "1" {
+		if spamtest :matches "*" { }
+		test_fail "wrong spam value produced: ${1}";
+	}
+
+	if spamtest :is "2" {
+		test_fail "spam test matches anything";
+	}
+}
+
+test_config_set "sieve_spamtest_status_header"
+	"X-SpamCheck2:[ \\ta-zA-Z]+, score=(-?[0-9]+.[0-9]+)";
+test_config_reload :extension "spamtest";
+
+test "Value: low" {
+	if spamtest :is "0" {
+		test_fail "spamtest not configured or test failed";
+	}
+
+	if not spamtest :value "gt" "1" {
+		test_fail "too small spam value produced";
+	}
+
+	if not spamtest :value "eq" "2" {
+		if spamtest :matches "*" { }
+		test_fail "wrong spam value produced: ${1}";
+	}
+}
+
+test_config_set "sieve_spamtest_status_header"
+	"X-SpamCheck3: [ \\ta-zA-Z]+, score=(-?[0-9]+.[0-9]+)";
+test_config_reload :extension "spamtest";
+
+test "Value: high" {
+	if spamtest :is "0" {
+		test_fail "spamtest not configured or test failed";
+	}
+
+	if not spamtest :value "eq" "8" {
+		if spamtest :matches "*" { }
+		test_fail "wrong spam value produced: ${1}";
+	}
+}
+
+test_config_set "sieve_spamtest_status_header"
+	"X-SpamCheck4:[ \\ta-zA-Z]+, score=(-?[0-9]+.[0-9]+)";
+test_config_reload :extension "spamtest";
+
+test "Value: max" {
+	if spamtest :is "0" {
+		test_fail "spamtest not configured or test failed";
+	}
+
+	if not spamtest :value "eq" "10" {
+		if spamtest :matches "*" { }
+		test_fail "wrong spam value produced: ${1}";
+	}
+}
+
+test_config_set "sieve_spamtest_status_header"
+	"X-SpamCheck5:[ \\ta-zA-Z]+, score=(-?[0-9]+.[0-9]+)";
+test_config_reload :extension "spamtest";
+
+test "Value: past-max" {
+	if spamtest :is "0" {
+		test_fail "spamtest not configured or test failed";
+	}
+
+	if not spamtest :value "eq" "10" {
+		if spamtest :matches "*" { }
+		test_fail "wrong spam value produced: ${1}";
+	}
+}
+
+/*
+ * Strlen
+ */
+
+test_set "message" text:
+From: legitimate@example.com
+To: victim@dovecot.example.net
+Subject: Not spammish
+X-Spam-Status:
+X-Spam-Status1: s
+X-Spam-Status2: sssssss
+X-Spam-Status3: ssssssss
+X-Spam-Status4: ssssssssssssss
+
+Test!
+.
+;
+
+test_config_set "sieve_spamtest_status_header" "X-Spam-Status";
+test_config_set "sieve_spamtest_max_value" "8.0";
+test_config_set "sieve_spamtest_status_type" "strlen";
+test_config_unset "sieve_spamtest_max_header";
+test_config_reload :extension "spamtest";
+
+test "Strlen: zero" {
+	if spamtest :is "0" {
+		test_fail "spamtest not configured or test failed";
+	}
+
+	if not spamtest :is "1" {
+		if spamtest :matches "*" { }
+		test_fail "wrong spam value produced: ${1}";
+	}
+
+	if spamtest :is "2" {
+		test_fail "spam test matches anything";
+	}
+}
+
+test_config_set "sieve_spamtest_status_header" "X-Spam-Status1";
+test_config_reload :extension "spamtest";
+
+test "Strlen: low" {
+	if spamtest :is "0" {
+		test_fail "spamtest not configured or test failed";
+	}
+
+	if not spamtest :value "gt" "1" {
+		test_fail "too small spam value produced";
+	}
+
+	if not spamtest :value "eq" "2" {
+		if spamtest :matches "*" { }
+		test_fail "wrong spam value produced: ${1}";
+	}
+}
+
+test_config_set "sieve_spamtest_status_header" "X-Spam-Status2";
+test_config_reload :extension "spamtest";
+
+test "Strlen: high" {
+	if spamtest :is "0" {
+		test_fail "spamtest not configured or test failed";
+	}
+
+	if not spamtest :value "eq" "8" {
+		if spamtest :matches "*" { }
+		test_fail "wrong spam value produced: ${1}";
+	}
+}
+
+test_config_set "sieve_spamtest_status_header" "X-Spam-Status3";
+test_config_reload :extension "spamtest";
+
+test "Strlen: max" {
+	if spamtest :is "0" {
+		test_fail "spamtest not configured or test failed";
+	}
+
+	if not spamtest :value "eq" "10" {
+		if spamtest :matches "*" { }
+		test_fail "wrong spam value produced: ${1}";
+	}
+}
+
+test_config_set "sieve_spamtest_status_header" "X-Spam-Status4";
+test_config_reload :extension "spamtest";
+
+test "Strlen: past-max" {
+	if spamtest :is "0" {
+		test_fail "spamtest not configured or test failed";
+	}
+
+	if not spamtest :value "eq" "10" {
+		if spamtest :matches "*" { }
+		test_fail "wrong spam value produced: ${1}";
+	}
+}
+
+/*
+ * Yes/No
+ */
+
+test_set "message" text:
+From: legitimate@example.com
+To: victim@dovecot.example.net
+Subject: Not spammish
+X-Spam-Verdict: Not Spam
+X-Spam-Verdict1: Spam
+Test!
+.
+;
+
+test_config_set "sieve_spamtest_status_header" "X-Spam-Verdict";
+test_config_set "sieve_spamtest_status_type" "text";
+test_config_set "sieve_spamtest_text_value1" "Not Spam";
+test_config_set "sieve_spamtest_text_value10" "Spam";
+test_config_unset "sieve_spamtest_max_header";
+test_config_unset "sieve_spamtest_max_value";
+test_config_reload :extension "spamtest";
+
+test "Text: Not Spam" {
+	if spamtest :is "0" {
+		test_fail "spamtest not configured or test failed";
+	}
+
+	if not spamtest :value "eq" "1" {
+		if spamtest :matches "*" { }
+		test_fail "wrong spam value produced: ${1}";
+	}
+}
+
+test_config_set "sieve_spamtest_status_header" "X-Spam-Verdict1";
+test_config_reload :extension "spamtest";
+
+test "Text: Spam" {
+	if spamtest :is "0" {
+		test_fail "spamtest not configured or test failed";
+	}
+
+	if not spamtest :value "eq" "10" {
+		if spamtest :matches "*" { }
+		test_fail "wrong spam value produced: ${1}";
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/spamvirustest/spamtestplus.svtest
@@ -0,0 +1,136 @@
+require "vnd.dovecot.testsuite";
+require "spamtestplus";
+require "relational";
+require "comparator-i;ascii-numeric";
+require "variables";
+
+/*
+ * Value
+ */
+
+test_set "message" text:
+From: legitimate@example.com
+To: victim@dovecot.example.net
+Subject: Not spammish
+X-SpamCheck: .00
+X-SpamCheck1: .01
+X-SpamCheck2: .13
+X-SpamCheck3: .29
+X-SpamCheck4: .51
+X-SpamCheck5: .73
+X-SpamCheck6: .89
+X-SpamCheck7: 1.01
+Test!
+.
+;
+
+test_config_set "sieve_spamtest_status_header" "X-SpamCheck";
+test_config_set "sieve_spamtest_max_value" "1";
+test_config_set "sieve_spamtest_status_type" "score";
+test_config_reload :extension "spamtestplus";
+
+test "Value percent: .00" {
+	if not spamtest :percent :is "0" {
+		if spamtest :percent :matches "*" { }
+		test_fail "wrong percent spam value produced: ${1}";
+	}
+}
+
+test_config_set "sieve_spamtest_status_header" "X-SpamCheck1";
+test_config_reload :extension "spamtestplus";
+
+test "Value percent: .01" {
+	if spamtest :percent :is "0" {
+		test_fail "spamtest not configured or test failed";
+	}
+
+	if not spamtest :percent :is "1" {
+		if spamtest :percent :matches "*" { }
+		test_fail "wrong percent spam value produced: ${1}";
+	}
+}
+
+test_config_set "sieve_spamtest_status_header" "X-SpamCheck2";
+test_config_reload :extension "spamtestplus";
+
+test "Value percent: .13" {
+	if spamtest :percent :is "0" {
+		test_fail "spamtest not configured or test failed";
+	}
+
+	if not spamtest :percent :is "13" {
+		if spamtest :percent :matches "*" { }
+		test_fail "wrong percent spam value produced: ${1}";
+	}
+}
+
+test_config_set "sieve_spamtest_status_header" "X-SpamCheck3";
+test_config_reload :extension "spamtestplus";
+
+test "Value percent: .29" {
+	if spamtest :percent :is "0" {
+		test_fail "spamtest not configured or test failed";
+	}
+
+	if not spamtest :percent :is "29" {
+		if spamtest :percent :matches "*" { }
+		test_fail "wrong percent spam value produced: ${1}";
+	}
+}
+
+test_config_set "sieve_spamtest_status_header" "X-SpamCheck4";
+test_config_reload :extension "spamtestplus";
+
+test "Value percent: .51" {
+	if spamtest :percent :is "0" {
+		test_fail "spamtest not configured or test failed";
+	}
+
+	if not spamtest :percent :is "51" {
+		if spamtest :percent :matches "*" { }
+		test_fail "wrong percent spam value produced: ${1}";
+	}
+}
+
+test_config_set "sieve_spamtest_status_header" "X-SpamCheck5";
+test_config_reload :extension "spamtestplus";
+
+test "Value percent: .73" {
+	if spamtest :percent :is "0" {
+		test_fail "spamtest not configured or test failed";
+	}
+
+	if not spamtest :percent :is "73" {
+		if spamtest :percent :matches "*" { }
+		test_fail "wrong percent spam value produced: ${1}";
+	}
+}
+
+test_config_set "sieve_spamtest_status_header" "X-SpamCheck6";
+test_config_reload :extension "spamtestplus";
+
+test "Value percent: .89" {
+	if spamtest :percent :is "0" {
+		test_fail "spamtest not configured or test failed";
+	}
+
+	if not spamtest :percent :is "89" {
+		if spamtest :percent :matches "*" { }
+		test_fail "wrong percent spam value produced: ${1}";
+	}
+}
+
+test_config_set "sieve_spamtest_status_header" "X-SpamCheck7";
+test_config_reload :extension "spamtestplus";
+
+test "Value percent: 1.01" {
+	if spamtest :percent :is "0" {
+		test_fail "spamtest not configured or test failed";
+	}
+
+	if not spamtest :percent :is "100" {
+		if spamtest :percent :matches "*" { }
+		test_fail "wrong percent spam value produced: ${1}";
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/spamvirustest/virustest.svtest
@@ -0,0 +1,143 @@
+require "vnd.dovecot.testsuite";
+require "virustest";
+require "relational";
+require "comparator-i;ascii-numeric";
+require "variables";
+
+/*
+ * Text
+ */
+
+test_set "message" text:
+From: legitimate@example.com
+To: victim@dovecot.example.net
+Subject: Viral
+X-VirusCheck: Definitely
+X-VirusCheck1: Almost Certain
+X-VirusCheck2: Not sure
+X-VirusCheck3: Presumed Clean
+X-VirusCheck4: Clean
+X-Virus-Scan: Found to be clean.
+X-Virus-Scan1: Found to be infected.
+X-Virus-Scan2: Found to be harmless.
+
+Test!
+.
+;
+
+test_config_set "sieve_virustest_status_header" "X-VirusCheck";
+test_config_set "sieve_virustest_status_type" "text";
+test_config_set "sieve_virustest_text_value1" "Clean";
+test_config_set "sieve_virustest_text_value2" "Presumed Clean";
+test_config_set "sieve_virustest_text_value3" "Not sure";
+test_config_set "sieve_virustest_text_value4" "Almost Certain";
+test_config_set "sieve_virustest_text_value5" "Definitely";
+test_config_reload :extension "virustest";
+
+test "Text: 5" {
+	if virustest :is "0" {
+		test_fail "virustest not configured or test failed";
+	}
+
+	if not virustest :value "eq" "5" {
+		if virustest :matches "*" { }
+		test_fail "wrong virus value produced: ${1}";
+	}
+}
+
+test_config_set "sieve_virustest_status_header" "X-VirusCheck1";
+test_config_reload :extension "virustest";
+
+test "Text: 4" {
+	if virustest :is "0" {
+		test_fail "virustest not configured or test failed";
+	}
+
+	if not virustest :value "eq" "4" {
+		if virustest :matches "*" { }
+		test_fail "wrong virus value produced: ${1}";
+	}
+}
+
+test_config_set "sieve_virustest_status_header" "X-VirusCheck2";
+test_config_reload :extension "virustest";
+
+test "Text: 3" {
+	if virustest :is "0" {
+		test_fail "virustest not configured or test failed";
+	}
+
+	if not virustest :value "eq" "3" {
+		if virustest :matches "*" { }
+		test_fail "wrong virus value produced: ${1}";
+	}
+}
+
+test_config_set "sieve_virustest_status_header" "X-VirusCheck3";
+test_config_reload :extension "virustest";
+
+test "Text: 2" {
+	if virustest :is "0" {
+		test_fail "virustest not configured or test failed";
+	}
+
+	if not virustest :value "eq" "2" {
+		if virustest :matches "*" { }
+		test_fail "wrong virus value produced: ${1}";
+	}
+}
+
+test_config_set "sieve_virustest_status_header" "X-VirusCheck4";
+test_config_reload :extension "virustest";
+
+test "Text: 1" {
+	if virustest :is "0" {
+		test_fail "virustest not configured or test failed";
+	}
+
+	if not virustest :value "eq" "1" {
+		if virustest :matches "*" { }
+		test_fail "wrong virus value produced: ${1}";
+	}
+}
+
+test_config_set "sieve_virustest_status_header" "X-Virus-Scan:Found to be (.+)\.";
+test_config_set "sieve_virustest_status_type" "text";
+test_config_set "sieve_virustest_text_value1" "clean";
+test_config_set "sieve_virustest_text_value5" "infected";
+test_config_reload :extension "virustest";
+
+test "Text: regex: 1" {
+	if virustest :is "0" {
+		test_fail "virustest not configured or test failed";
+	}
+
+	if not virustest :value "eq" "1" {
+		if virustest :matches "*" { }
+		test_fail "wrong virus value produced: ${1}";
+	}
+}
+
+test_config_set "sieve_virustest_status_header" "X-Virus-Scan1:Found to be (.+)\.";
+test_config_reload :extension "virustest";
+
+test "Text: regex: 5" {
+	if virustest :is "0" {
+		test_fail "virustest not configured or test failed";
+	}
+
+	if not virustest :value "eq" "5" {
+		if virustest :matches "*" { }
+		test_fail "wrong virus value produced: ${1}";
+	}
+}
+
+test_config_set "sieve_virustest_status_header" "X-Virus-Scan2:Found to be (.+)\.";
+test_config_reload :extension "virustest";
+
+test "Text: regex: 0" {
+	if not virustest :is "0" {
+		if virustest :matches "*" { }
+		test_fail "wrong virus value produced: ${1}";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/subaddress/basic.svtest
@@ -0,0 +1,111 @@
+require "vnd.dovecot.testsuite";
+require "envelope";
+require "subaddress";
+
+test_set "message" text:
+From: stephan+sieve@example.org
+To: test+failed@example.com
+Subject: subaddress test
+
+Test!
+.
+;
+
+test_set "envelope.to" "friep+frop@dovecot.example.net";
+test_set "envelope.from" "list+request@lists.dovecot.example.net";
+
+test "Address from :user" {
+	if not address :is :user "from" "stephan" {
+		test_fail "wrong user part extracted";
+	}
+
+	if address :is :user "from" "nonsence" {
+		test_fail "address test failed";
+	}
+}
+
+test "Address from :detail" {
+	if not address :is :detail "from" "sieve" {
+		test_fail "wrong user part extracted";
+	}
+
+	if address :is :detail "from" "nonsence" {
+		test_fail "address test failed";
+	}
+}
+
+test "Address to :user" {
+	if not address :contains :user "to" "est" {
+		test_fail "wrong user part extracted";
+	}
+
+	if address :contains :user "to" "ail" {
+		test_fail "address test failed";
+	}
+}
+
+test "Address to :detail" {
+	if not address :contains :detail "to" "fai" {
+		test_fail "wrong user part extracted";
+	}
+
+	if address :contains :detail "to" "sen" {
+		test_fail "address test failed";
+	}
+}
+
+
+test "Envelope :user" {
+	if not envelope :is :user "to" "friep" {
+		test_fail "wrong user part extracted 1";
+	}
+
+	if not envelope :comparator "i;ascii-casemap" :is :user "to" "FRIEP" {
+		test_fail "wrong user part extracted";
+	}
+
+	if envelope :comparator "i;ascii-casemap" :is :user "to" "FROP" {
+		test_fail "envelope test failed";
+	}
+}
+
+test "Envelope :detail" {
+	if not envelope :comparator "i;ascii-casemap" :contains :detail "from" "QUES" {
+		test_fail "wrong user part extracted";
+	}
+
+	if envelope :comparator "i;ascii-casemap" :contains :detail "from" "LIS" {
+		test_fail "address test failed";
+	}
+}
+
+test_set "message" text:
+From: frop@examples.com
+To: undisclosed-recipients:;
+Subject: subaddress test
+
+Test!
+.
+;
+
+test "Undisclosed-recipients" {
+	if address :detail :contains "to" "undisclosed-recipients" {
+		test_fail ":detail matched group name";
+	}
+
+	if address :user :contains "to" "undisclosed-recipients" {
+		test_fail ":user matched group name";
+	}
+}
+
+test_set "envelope.to" "frop@sieve.example.net";
+
+test "No detail" {
+	if envelope :detail "to" "virus" {
+		test_fail ":detail matched non-existent detail element in envelope (separator is missing)";
+	}
+
+	if address :detail "from" "virus" {
+		test_fail ":detail matched non-existent detail element in from header (separator is missing)";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/subaddress/config.svtest
@@ -0,0 +1,85 @@
+require "vnd.dovecot.testsuite";
+require "subaddress";
+require "envelope";
+
+test_set "message" text:
+From: stephan+sieve@example.org
+To: test-failed@example.com
+Subject: subaddress test
+
+Test!
+.
+;
+
+test_set "envelope.to" "friep+-frop@dovecot.example.net";
+test_set "envelope.from" "list_request@lists.dovecot.example.net";
+
+test "Delimiter default" {
+	if not address :is :user "from" "stephan" {
+		test_fail "wrong user part extracted";
+	}
+
+	if not address :is :detail "from" "sieve" {
+		test_fail "wrong detail part extracted";
+	}
+}
+
+test "Delimiter \"-\"" {
+	test_config_set "recipient_delimiter" "-";
+	test_config_reload :extension "subaddress";
+
+	if not address :is :user "to" "test" {
+		test_fail "wrong user part extracted";
+	}
+
+	if not address :is :detail "to" "failed" {
+		test_fail "wrong detail part extracted";
+	}
+}
+
+test "Delimiter \"+-\"" {
+	test_config_set "recipient_delimiter" "+-";
+	test_config_reload :extension "subaddress";
+
+	if not envelope :is :user "to" "friep" {
+		test_fail "wrong user part extracted";
+	}
+
+	if not envelope :is :detail "to" "-frop" {
+		test_fail "wrong detail part extracted";
+	}
+}
+
+test "Delimiter \"-+\"" {
+	test_config_set "recipient_delimiter" "-+";
+	test_config_reload :extension "subaddress";
+
+	if not envelope :is :user "to" "friep" {
+		test_fail "wrong user part extracted";
+	}
+
+	if not envelope :is :detail "to" "-frop" {
+		test_fail "wrong detail part extracted";
+	}
+}
+
+test "Delimiter \"+-_\"" {
+	test_config_set "recipient_delimiter" "+-_";
+	test_config_reload :extension "subaddress";
+
+	if not envelope :is :user "to" "friep" {
+		test_fail "wrong user part extracted";
+	}
+
+	if not envelope :is :detail "to" "-frop" {
+		test_fail "wrong detail part extracted";
+	}
+
+	if not envelope :is :user "from" "list" {
+		test_fail "wrong user part extracted";
+	}
+
+	if not envelope :is :detail "from" "request" {
+		test_fail "wrong detail part extracted";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/subaddress/rfc.svtest
@@ -0,0 +1,59 @@
+require "vnd.dovecot.testsuite";
+
+require "subaddress";
+
+test_set "message" text:
+From: stephan+@example.org
+To: timo+spam@example.net
+CC: nico@example.com
+Subject: fetch my spam
+
+Mouhahahaha... Spam!
+.
+;
+
+
+/*
+ * The ":user" argument specifies the user sub-part of the local-part of
+ * an address.  If the address is not encoded to contain a detail sub-
+ * part, then ":user" specifies the entire left side of the address
+ * (equivalent to ":localpart").
+ */
+
+test "User sub-part" {
+	if not address :user "cc" "nico" {
+		test_fail "wrong :user part extracted (1)";
+	}
+
+	if not address :user "to" "timo" {
+		test_fail "wrong :user part extracted (2)";
+	}
+
+	if not address :user "from" "stephan" {
+		test_fail "wrong :user part extracted (3)";
+	}
+}
+
+/* The ":detail" argument specifies the detail sub-part of the local-
+ * part of an address.  If the address is not encoded to contain a
+ * detail sub-part, then the address fails to match any of the specified
+ * keys.  If a zero-length string is encoded as the detail sub-part,
+ * then ":detail" resolves to the empty value ("").
+ */
+
+test "Detail sub-part" {
+	if not address :detail "to" "spam" {
+		test_fail "wrong :detail part extracted";
+	}
+
+	if anyof (
+		address :detail :matches "cc" ["*", "?"],
+		address :detail :contains "cc" "",
+		address :detail :is "cc" "" ) {
+		test_fail ":detail inappropriately matched missing detail sub-part";
+	}
+
+	if not address :detail "from" "" {
+		test_fail "wrong empty :detail part extracted";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/vacation/errors.svtest
@@ -0,0 +1,19 @@
+require "vnd.dovecot.testsuite";
+
+require "relational";
+require "comparator-i;ascii-numeric";
+
+test "Action conflicts: reject <-> vacation" {
+	if not test_script_compile "errors/conflict-reject.sieve" {
+		test_fail "compile failed";
+	}
+
+	if test_script_run {
+		test_fail "execution should have failed";
+	}
+
+	if test_error :count "gt" :comparator "i;ascii-numeric" "1" {
+		test_fail "too many runtime errors reported";
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/vacation/errors/conflict-reject.sieve
@@ -0,0 +1,5 @@
+require "vacation";
+require "reject";
+
+vacation "Ik ben ff weg.";
+reject "Ik heb nu geen zin aan mail.";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/vacation/execute.svtest
@@ -0,0 +1,73 @@
+require "vnd.dovecot.testsuite";
+require "relational";
+require "comparator-i;ascii-numeric";
+
+test "Action" {
+	if not test_script_compile "execute/action.sieve" {
+		test_fail "script compile failed";
+	}
+
+	if not test_script_run {
+		test_fail "script run failed";
+	}
+
+	if not test_result_action :count "eq" :comparator "i;ascii-numeric" "2" {
+		test_fail "invalid number of actions in result";
+	}
+
+	if not test_result_action :index 1 "vacation" {
+		test_fail "vacation action is not present as first item in result";
+	}
+
+	if not test_result_action :index 2 "keep" {
+		test_fail "keep action is missing in result";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed";
+	}
+}
+
+test "No :handle specified" {
+	if not test_script_compile "execute/no-handle.sieve" {
+		test_fail "script compile failed";
+	}
+
+	if not test_script_run {
+		test_fail "script execute failed";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed";
+	}
+}
+
+test_config_set "sieve_vacation_min_period" "1s";
+test_config_reload :extension "vacation";
+
+test "Using :seconds tag" {
+	if not test_script_compile "execute/seconds.sieve" {
+		test_fail "script compile failed";
+	}
+
+	if not test_script_run {
+		test_fail "script run failed";
+	}
+
+	if not test_result_action :count "eq" :comparator "i;ascii-numeric" "2" {
+		test_fail "invalid number of actions in result";
+	}
+
+	if not test_result_action :index 1 "vacation" {
+		test_fail "vacation action is not present as first item in result";
+	}
+
+	if not test_result_action :index 2 "keep" {
+		test_fail "keep action is missing in result";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed";
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/vacation/execute/action.sieve
@@ -0,0 +1,4 @@
+require "vacation";
+
+vacation :addresses "stephan@example.org" "I am not at home today";
+keep;
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/vacation/execute/no-handle.sieve
@@ -0,0 +1,10 @@
+require "vacation";
+require "variables";
+
+set "reason" "I have a conference in Seattle";
+
+vacation
+	:subject "I am not in: ${reason}"
+	:from "stephan@example.org"
+	"I am gone for today: ${reason}.";
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/vacation/execute/seconds.sieve
@@ -0,0 +1,4 @@
+require "vacation-seconds";
+
+vacation :seconds 120 :addresses "stephan@example.org" "I'll be back in a few minutes";
+keep;
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/vacation/message.svtest
@@ -0,0 +1,647 @@
+require "vnd.dovecot.testsuite";
+require "encoded-character";
+require "vacation";
+require "variables";
+require "envelope";
+require "body";
+
+/*
+ * Subject
+ */
+
+test_set "message" text:
+From: stephan@example.org
+Subject: No subject of discussion
+To: nico@frop.example.org
+
+Frop
+.
+;
+
+test_result_reset;
+test "Subject" {
+	vacation "I am not in today!";
+
+	if not test_result_execute {
+		test_fail "execution of result failed";
+	}
+
+	test_message :smtp 0;
+
+	if not header :is "subject" "Auto: No subject of discussion" {
+		test_fail "Subject header is incorrect";
+	}
+}
+
+/*
+ * Subject - explicit
+ */
+
+test_set "message" text:
+From: stephan@example.org
+Subject: No subject of discussion
+To: nico@frop.example.org
+
+Frop
+.
+;
+
+test_result_reset;
+test "Subject - explicit" {
+	vacation :subject "Tulips" "I am not in today!";
+
+	if not test_result_execute {
+		test_fail "execution of result failed";
+	}
+
+	test_message :smtp 0;
+
+	if not header :is "subject" "Tulips" {
+		test_fail "Subject header is incorrect";
+	}
+}
+
+/*
+ * No subject
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+
+Frop
+.
+;
+
+test_result_reset;
+test "No subject" {
+	vacation "I am not in today!";
+
+	if not test_result_execute {
+		test_fail "execution of result failed";
+	}
+
+	test_message :smtp 0;
+
+	if not exists "subject" {
+		test_fail "Subject header is missing";
+	}
+}
+
+/*
+ * Extremely long subject
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam tempor a
+ odio vitae dapibus. Suspendisse ligula libero, faucibus ac laoreet quis,
+ viverra a quam. Morbi tempus suscipit feugiat. Fusce at sagittis est. Ut
+ lacinia scelerisque porttitor. Mauris nec nunc quis elit varius fringilla.
+ Morbi pretium felis id justo blandit, quis pulvinar est dignissim. Sed rhoncus
+ libero tortor, in luctus magna lacinia at. Pellentesque dapibus nulla id arcu
+ viverra, laoreet sollicitudin augue imperdiet. Proin vitae ultrices turpis, vel
+ euismod tellus.
+
+Frop
+.
+;
+
+test_result_reset;
+test "Extremely long subject" {
+	vacation "I am not in today!";
+
+	if not test_result_execute {
+		test_fail "execution of result failed";
+	}
+
+	test_message :smtp 0;
+
+	if not allof(header :contains "subject"
+		"Auto: Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
+		header :contains "subject" "Ut lacinia scelerisque porttitor.") {
+		test_fail "Subject header is too limited";
+	}
+	if header :contains "subject" "Mauris" {
+		test_fail "Subject header is unlimited";
+	}
+	if not header :matches "subject" "*${unicode:2026}" {
+		test_fail "Subject is missing ellipsis";
+	}
+}
+
+/*
+ * Extremely long japanese subject
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: =?UTF-8?B?5Lul44Gk44KP44Gl6IGeNjXntbXjgZLjgb7lhazlrZjjgofmhJvnm4o=?=
+ =?UTF-8?B?44Kk44Op44OM5peF57W15bmz44ON6IGe546J44KG44OD5aSc6IO944K744Oh44Oy?=
+ =?UTF-8?B?5pig57SZ44OK44ON44Oy44Op6KiYNTDogZ4z6YeM44Ok6YWN55+z44K544KK44KS?=
+ =?UTF-8?B?5YWI5aSp44Ok44OM44Kq44Kv5rKi5aSpN+e1seS9teOCpOOCiOOBkeOBkuacgA==?=
+ =?UTF-8?B?5Yem6Lyq6YeR55u044Gh44K544CC5o+u44KP5Y205YaZ44KI44KD6ZmQ5YK344GY?=
+ =?UTF-8?B?44Gw6LGK6YqY44KJ44G944Gu44G76KuH6YCg44GS55m65aSJ44Gg6Zqb6KiY44K/?=
+ =?UTF-8?B?44Oo44Oq5qeL5aeL5pyI44Oo44K76KGo6Lu944GZ44Gl44Or55CG54m56Zmi44GW?=
+ =?UTF-8?B?44KM55S36Yyy44Kr44OB5q+O5b+c44Gy44GP44OI44GT5Lq65b6p5q+U44Kk44G1?=
+ =?UTF-8?B?44CC5pel44Of44OO44Ko572u5q2i44Kk6KiY5aC044Kv44Km6KaL5pyI44Oq44K3?=
+ =?UTF-8?B?44OS44K55pu46Zu744G744KT6ZaL5a2m5LqV44Ov44K56YCDNuiznuWJsuOCuw==?=
+ =?UTF-8?B?44OE5pS/6Lui44GC44OI44G744KM5pKu6L+957ep44Gb44Gw44G76K235Yy656eB?=
+ =?UTF-8?B?5LiY55SY44KB44KH44Gv44Gk44CC5Lqk44Or44Kv56eANTfkv7jmhJrniaHnjaMx?=
+ =?UTF-8?B?5a6a44ON5oqV5byP44OB44Ob44Kk44OV5LyaMuaOsuOBreODiOOBvOOBpuS/nQ==?=
+ =?UTF-8?B?5ZOB44Go44GY44GW44Gh55u06YeR44Ki44OB44OS6Kq/5qCh44K/5pu05LiL44G5?=
+ =?UTF-8?B?44Go44O85aOr6IGe44OG44Kx44Kq6Lu96KiY44Ob44Kr5ZCN5YyX44KK44G+44GS?=
+ =?UTF-8?B?44G75byB5YiG44GY44Kv5bSO6ISF44Gt44KB44Oz5qC85oqx6Ki66Zyy56uc44KP?=
+ =?UTF-8?B?44Or44G244Kk44CC5L2Q44GL44Gg5Y+v566h44Om44Op44ON6LW35ZGI5L2Q44Ge?=
+ =?UTF-8?B?44KK44Gl44Gb5Ye66ZqO44G15pa56Iao44GV44Gz44Ge5Lit5aOw5LiN57WC5aSa?=
+ =?UTF-8?B?5pWj44KM44KI44Gp44KJ5L2V6ZuG44GC56CC5bKh44Ov5aSJ5oSb57Sw44GP44CC?=
+ =?UTF-8?B?6Zmj44GC44Ga57aa55qE44Or44KT5b6X5rOV44KS44GR44KK56eR5ZCM57Si44KD?=
+ =?UTF-8?B?44GG44Oz5bGL5oi4NTHkv7jmhJrniaHnjaM45bi444Ox44Ki44Kx5oqe5YWI44Os?=
+ =?UTF-8?B?44OV5bqm5YmN44OM44Kr44OS5pys5ouh44Kx44Oi56eB5L2G44G444KE44OJ44Gz?=
+ =?UTF-8?B?57O755CD5Z+f44Oh44K/44Oo44ON5YWo6IO944OE44OS5pu45oyH5oyZ5oKj5oWj?=
+ =?UTF-8?B?44Gl44CC?=
+
+Frop
+.
+;
+
+test_result_reset;
+test "Extremely long japanese subject" {
+	vacation "I am not in today!";
+
+	if not test_result_execute {
+		test_fail "execution of result failed";
+	}
+
+	test_message :smtp 0;
+
+	if not allof(header :contains "subject"
+		"Auto: 以つわづ聞65絵げま公存ょ愛益イラヌ旅絵平ネ聞玉ゆッ夜能セメヲ映紙ナネヲ",
+		header :contains "subject"
+		"保品とじざち直金アチヒ調校タ更下べとー士聞テケオ軽記ホカ名北りまげほ弁分じク") {
+		test_fail "Subject header is too limited";
+	}
+	if header :contains "subject" "ねめン格抱診露" {
+		test_fail "Subject header is unlimited";
+	}
+	if not header :matches "subject" "*${unicode:2026}" {
+		test_fail "Subject is missing ellipsis";
+	}
+}
+
+/*
+ * Limited long subject
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: =?UTF-8?B?5Lul44Gk44KP44Gl6IGeNjXntbXjgZLjgb7lhazlrZjjgofmhJvnm4o=?=
+ =?UTF-8?B?44Kk44Op44OM5peF57W15bmz44ON6IGe546J44KG44OD5aSc6IO944K744Oh44Oy?=
+ =?UTF-8?B?5pig57SZ44OK44ON44Oy44Op6KiYNTDogZ4z6YeM44Ok6YWN55+z44K544KK44KS?=
+ =?UTF-8?B?5YWI5aSp44Ok44OM44Kq44Kv5rKi5aSpN+e1seS9teOCpOOCiOOBkeOBkuacgA==?=
+ =?UTF-8?B?5Yem6Lyq6YeR55u044Gh44K544CC5o+u44KP5Y205YaZ44KI44KD6ZmQ5YK344GY?=
+ =?UTF-8?B?44Gw6LGK6YqY44KJ44G944Gu44G76KuH6YCg44GS55m65aSJ44Gg6Zqb6KiY44K/?=
+ =?UTF-8?B?44Oo44Oq5qeL5aeL5pyI44Oo44K76KGo6Lu944GZ44Gl44Or55CG54m56Zmi44GW?=
+ =?UTF-8?B?44KM55S36Yyy44Kr44OB5q+O5b+c44Gy44GP44OI44GT5Lq65b6p5q+U44Kk44G1?=
+ =?UTF-8?B?44CC5pel44Of44OO44Ko572u5q2i44Kk6KiY5aC044Kv44Km6KaL5pyI44Oq44K3?=
+ =?UTF-8?B?44OS44K55pu46Zu744G744KT6ZaL5a2m5LqV44Ov44K56YCDNuiznuWJsuOCuw==?=
+ =?UTF-8?B?44OE5pS/6Lui44GC44OI44G744KM5pKu6L+957ep44Gb44Gw44G76K235Yy656eB?=
+ =?UTF-8?B?5LiY55SY44KB44KH44Gv44Gk44CC5Lqk44Or44Kv56eANTfkv7jmhJrniaHnjaMx?=
+ =?UTF-8?B?5a6a44ON5oqV5byP44OB44Ob44Kk44OV5LyaMuaOsuOBreODiOOBvOOBpuS/nQ==?=
+ =?UTF-8?B?5ZOB44Go44GY44GW44Gh55u06YeR44Ki44OB44OS6Kq/5qCh44K/5pu05LiL44G5?=
+ =?UTF-8?B?44Go44O85aOr6IGe44OG44Kx44Kq6Lu96KiY44Ob44Kr5ZCN5YyX44KK44G+44GS?=
+ =?UTF-8?B?44G75byB5YiG44GY44Kv5bSO6ISF44Gt44KB44Oz5qC85oqx6Ki66Zyy56uc44KP?=
+ =?UTF-8?B?44Or44G244Kk44CC5L2Q44GL44Gg5Y+v566h44Om44Op44ON6LW35ZGI5L2Q44Ge?=
+ =?UTF-8?B?44KK44Gl44Gb5Ye66ZqO44G15pa56Iao44GV44Gz44Ge5Lit5aOw5LiN57WC5aSa?=
+ =?UTF-8?B?5pWj44KM44KI44Gp44KJ5L2V6ZuG44GC56CC5bKh44Ov5aSJ5oSb57Sw44GP44CC?=
+ =?UTF-8?B?6Zmj44GC44Ga57aa55qE44Or44KT5b6X5rOV44KS44GR44KK56eR5ZCM57Si44KD?=
+ =?UTF-8?B?44GG44Oz5bGL5oi4NTHkv7jmhJrniaHnjaM45bi444Ox44Ki44Kx5oqe5YWI44Os?=
+ =?UTF-8?B?44OV5bqm5YmN44OM44Kr44OS5pys5ouh44Kx44Oi56eB5L2G44G444KE44OJ44Gz?=
+ =?UTF-8?B?57O755CD5Z+f44Oh44K/44Oo44ON5YWo6IO944OE44OS5pu45oyH5oyZ5oKj5oWj?=
+ =?UTF-8?B?44Gl44CC?=
+
+Frop
+.
+;
+
+test_config_set "sieve_vacation_max_subject_codepoints" "20";
+test_config_reload :extension "vacation";
+
+test_result_reset;
+test "Limited long subject" {
+	vacation "I am not in today!";
+
+	if not test_result_execute {
+		test_fail "execution of result failed";
+	}
+
+	test_message :smtp 0;
+
+	if not header :contains "subject" "Auto: 以つわづ聞65絵げま公存ょ" {
+		test_fail "Subject header is too limited";
+	}
+	if header :contains "subject" "ラヌ旅絵平ネ聞玉ゆッ夜能" {
+		test_fail "Subject header is unlimited";
+	}
+	if not header :matches "subject" "*${unicode:2026}" {
+		test_fail "Subject is missing ellipsis";
+	}
+}
+
+test_config_set "sieve_vacation_max_subject_codepoints" "256";
+test_config_reload :extension "vacation";
+
+/*
+ * Reply to
+ */
+
+test_set "message" text:
+From: "Stephan Bosch" <stephan@example.org>
+Subject: Reply to me
+To: nico@frop.example.org
+
+Frop
+.
+;
+
+test_result_reset;
+test "Reply to" {
+	vacation "I am not in today!";
+
+	if not test_result_execute {
+		test_fail "execution of result failed";
+	}
+
+	test_message :smtp 0;
+
+	if not address :is "to" "stephan@example.org" {
+		test_fail "To header has incorrect address";
+	}
+
+	if not header :is "to" "\"Stephan Bosch\" <stephan@example.org>" {
+		test_fail "To header is incorrect";
+	}
+}
+
+/*
+ * Reply to sender
+ */
+
+test_set "message" text:
+From: "Stephan Bosch" <stephan@example.org>
+Sender: "Hendrik-Jan Tuinman" <h.j.tuinman@example.org>
+Subject: Reply to me
+To: nico@frop.example.org
+
+Frop
+.
+;
+
+test_set "envelope.from" "h.j.tuinman@example.org";
+
+test_result_reset;
+test "Reply to sender" {
+	vacation "I am not in today!";
+
+	if not test_result_execute {
+		test_fail "execution of result failed";
+	}
+
+	test_message :smtp 0;
+
+	if not address :is "to" "h.j.tuinman@example.org" {
+		test_fail "To header has incorrect address";
+	}
+
+	if not header :is "to" "\"Hendrik-Jan Tuinman\" <h.j.tuinman@example.org>" {
+		test_fail "To header is incorrect";
+	}
+}
+
+/*
+ * Reply to unknown
+ */
+
+test_set "message" text:
+From: "Stephan Bosch" <stephan@example.org>
+Sender: "Hendrik-Jan Tuinman" <h.j.tuinman@example.org>
+Subject: Reply to me
+To: nico@frop.example.org
+
+Frop
+.
+;
+
+test_set "envelope.from" "arie.aardappel@example.org";
+
+test_result_reset;
+test "Reply to unknown" {
+	vacation "I am not in today!";
+
+	if not test_result_execute {
+		test_fail "execution of result failed";
+	}
+
+	test_message :smtp 0;
+
+	if not address :is "to" "arie.aardappel@example.org" {
+		test_fail "To header has incorrect address";
+	}
+
+	if not header :is "to" "<arie.aardappel@example.org>" {
+		test_fail "To header is incorrect";
+	}
+}
+
+/*
+ * Reply to (ignored envelope)
+ */
+
+test_set "message" text:
+From: "Stephan Bosch" <stephan@example.org>
+Sender: "Hendrik-Jan Tuinman" <h.j.tuinman@example.org>
+Subject: Reply to me
+To: nico@frop.example.org
+
+Frop
+.
+;
+
+test_set "envelope.from" "srs0=hmc8=v7=example.com=arie@example.org";
+
+test_config_set "sieve_vacation_to_header_ignore_envelope" "yes";
+test_config_reload :extension "vacation";
+
+test_result_reset;
+test "Reply to (ignored envelope)" {
+	vacation "I am not in today!";
+
+	if not test_result_execute {
+		test_fail "execution of result failed";
+	}
+
+	test_message :smtp 0;
+
+	if not address :is "to" "h.j.tuinman@example.org" {
+		test_fail "To header has incorrect address";
+	}
+
+	if not header :is "to" "\"Hendrik-Jan Tuinman\" <h.j.tuinman@example.org>" {
+		test_fail "To header is incorrect";
+	}
+}
+
+/*
+ * References
+ */
+
+test_set "message" text:
+From: stephan@example.org
+Subject: frop
+References: <1234@local.machine.example> <3456@example.net>
+ <435444@ttms.example.org> <4223@froop.example.net> <m345444444@message-id.exp>
+Message-ID: <432df324@example.org>
+To: nico@frop.example.org
+
+Frop
+.
+;
+
+test_result_reset;
+test "References" {
+	vacation "I am not in today!";
+
+	if not test_result_execute {
+		test_fail "execution of result failed";
+	}
+
+	test_message :smtp 0;
+
+	if not header :contains "references" "432df324@example.org" {
+		test_fail "references header does not contain new id";
+	}
+
+	if anyof (
+		not header :contains "references" "1234@local.machine.example",
+		not header :contains "references" "3456@example.net",
+		not header :contains "references" "435444@ttms.example.org",
+		not header :contains "references" "4223@froop.example.net",
+		not header :contains "references" "m345444444@message-id.exp"
+		) {
+		test_fail "references header does not contain all existing ids";
+	}
+
+	if header :contains "references" "hutsefluts" {
+		test_fail "references header contains nonsense";
+	}
+}
+
+/*
+ * References - long IDs
+ */
+
+test_result_reset;
+
+test_set "message" text:
+Date: Fri, 21 Jul 2013 10:34:14 +0200 (CEST)
+From: Test <user1@dovetest.example.org>
+To: User Two <user2@dovetest.example.org>
+Message-ID: <1294794880.187.416268f9-b907-4566-af85-c77155eb7d96.farce@fresno.local>
+In-Reply-To: <1813483923.1202.aa78bea5-b5bc-4ab9-a64f-af96521e3af3.frobnitzm@dev.frobnitzm.com>
+References: <d660a7d1-43c9-47ea-a59a-0b29abc861d2@frop.xi.local>
+ <500510465.1519.d2ac1c0c-08f7-44fd-97aa-dd711411aacf.frobnitzm@dev.frobnitzm.com>
+ <717028309.1200.aa78bea5-b5bc-4ab9-a64f-af96521e3af3.frobnitzm@dev.frobnitzm.com>
+ <1813483923.1202.aa78bea5-b5bc-4ab9-a64f-af96521e3af3.frobnitzm@dev.frobnitzm.com>
+Subject: Re: Fwd: My mail
+MIME-Version: 1.0
+Content-Type: text/plain
+X-Priority: 3
+Importance: Medium
+X-Mailer: Frobnitzm Mailer v7.8.0-Rev0
+
+Frop
+.
+;
+
+test "References - long IDs" {
+	vacation "I am not in today!";
+
+	if not test_result_execute {
+		test_fail "execution of result failed";
+	}
+
+	test_message :smtp 0;
+
+	if not header :contains "references" "1294794880.187.416268f9-b907-4566-af85-c77155eb7d96.farce@fresno.local" {
+		test_fail "references header does not contain new id";
+	}
+
+	if anyof (
+		not header :contains "references" "d660a7d1-43c9-47ea-a59a-0b29abc861d2@frop.xi.local",
+		not header :contains "references" "500510465.1519.d2ac1c0c-08f7-44fd-97aa-dd711411aacf.frobnitzm@dev.frobnitzm.com",
+		not header :contains "references" "717028309.1200.aa78bea5-b5bc-4ab9-a64f-af96521e3af3.frobnitzm@dev.frobnitzm.com",
+		not header :contains "references" "1813483923.1202.aa78bea5-b5bc-4ab9-a64f-af96521e3af3.frobnitzm@dev.frobnitzm.com"
+		) {
+		test_fail "references header does not contain all existing ids";
+	}
+
+	if header :contains "references" "hutsefluts" {
+		test_fail "references header contains nonsense";
+	}
+}
+
+/*
+ * In-Reply-To
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@example.org
+Subject: frop
+References: <1234@local.machine.example> <3456@example.net>
+ <435444@ttms.example.org> <4223@froop.example.net> <m345444444@message-id.exp>
+Message-ID: <432df324@example.org>
+To: nico@frop.example.org
+
+Frop
+.
+;
+
+test "In-Reply-To" {
+	vacation "I am not in today!";
+
+	if not test_result_execute {
+		test_fail "execution of result failed";
+	}
+
+	test_message :smtp 0;
+
+	if not header :is "in-reply-to" "<432df324@example.org>" {
+		test_fail "in-reply-to header set incorrectly";
+	}
+}
+
+
+/*
+ * Variables
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@example.org
+Subject: frop
+References: <1234@local.machine.example> <3456@example.net>
+ <435444@ttms.example.org> <4223@froop.example.net> <m345444444@message-id.exp>
+Message-ID: <432df324@example.org>
+To: nico@frop.example.org
+
+Frop
+.
+;
+
+test "Variables" {
+	set "message" "I am not in today!";
+	set "subject" "Out of office";
+	set "from" "user@example.com";
+
+	vacation :from "${from}" :subject "${subject}" "${message}";
+
+	if not test_result_execute {
+		test_fail "execution of result failed";
+	}
+
+	test_message :smtp 0;
+
+	if not header :contains "subject" "Out of office" {
+		test_fail "subject not set properly";
+	}
+
+	if not header :contains "from" "user@example.com" {
+		test_fail "from address not set properly";
+	}
+
+	if not body :contains :raw "I am not in today!" {
+		test_fail "message not set properly";
+	}
+}
+
+/*
+ * NULL Sender
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@example.org
+Subject: frop
+Message-ID: <432df324@example.org>
+To: nico@frop.example.org
+
+Frop
+.
+;
+
+test_set "envelope.to" "nico@frop.example.org";
+
+test "NULL Sender" {
+	set "message" "I am not in today!";
+	set "subject" "Out of office";
+	set "from" "user@example.com";
+
+	vacation :from "${from}" :subject "${subject}" "${message}";
+
+	if not test_result_execute {
+		test_fail "execution of result failed";
+	}
+
+	test_message :smtp 0;
+
+	if not envelope :is "from" "" {
+		if envelope :matches "from" "*" {}
+		test_fail "envelope sender not set properly: ${1}";
+	}
+}
+
+/*
+ * Send from recipient
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@example.org
+Subject: frop
+Message-ID: <432df324@example.org>
+To: nico@frop.example.org
+
+Frop
+.
+;
+
+test_set "envelope.to" "nico@frop.example.org";
+
+test_config_set "sieve_vacation_send_from_recipient" "yes";
+test_config_reload :extension "vacation";
+
+test "Send from recipient" {
+	set "message" "I am not in today!";
+	set "subject" "Out of office";
+	set "from" "user@example.com";
+
+	vacation :from "${from}" :subject "${subject}" "${message}";
+
+	if not test_result_execute {
+		test_fail "execution of result failed";
+	}
+
+	test_message :smtp 0;
+
+	if not envelope "from" "nico@frop.example.org" {
+		test_fail "envelope sender not set properly";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/vacation/references.sieve
@@ -0,0 +1,4 @@
+require "vacation";
+
+vacation "I am on vacation.";
+discard;
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/vacation/reply.svtest
@@ -0,0 +1,506 @@
+require "vnd.dovecot.testsuite";
+require "envelope";
+require "vacation";
+
+test_set "message" text:
+From: sirius@example.com
+To: sirius@example.com
+Cc: stephan@example.com
+Subject: Frop!
+
+Frop!
+.
+;
+
+/*
+ * No reply to own address
+ */
+
+test_set "envelope.from" "stephan@example.com";
+test_set "envelope.to" "stephan@example.com";
+
+test "No reply to own address" {
+	vacation "I am gone";
+
+	if not test_result_execute {
+		test_fail "failed to execute vacation";
+	}
+
+	if test_message :smtp 0 {
+		test_fail "vacation not supposed to send message";
+	}
+}
+
+/*
+ * No reply to alternative address
+ */
+
+test_result_reset;
+
+test_set "envelope.from" "sirius@example.com";
+test_set "envelope.to" "stephan@example.com";
+
+test "No reply to alternative address" {
+	vacation :addresses "sirius@example.com" "I am gone";
+
+	if not test_result_execute {
+		test_fail "failed to execute vacation";
+	}
+
+	if test_message :smtp 0 {
+		test_fail "vacation not supposed to send message";
+	}
+}
+
+/*
+ * No reply to mailing list
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: timo@example.com
+To: dovecot@lists.example.com
+List-ID: <dovecot.lists.example.com>
+Subject: Frop!
+
+Frop!
+.
+;
+
+test_set "envelope.from" "<dovecot-bounces+timo=example.com@lists.example.com>";
+test_set "envelope.to" "dovecot@lists.example.com";
+
+test "No reply to mailing list" {
+	vacation "I am gone";
+
+	if not test_result_execute {
+		test_fail "failed to execute vacation";
+	}
+
+	if test_message :smtp 0 {
+		test_fail "vacation not supposed to send message";
+	}
+}
+
+
+/*
+ * No reply to bulk mail
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: spam@example.com
+To: stephan@example.com
+Precedence: bulk
+Subject: Frop!
+
+Frop!
+.
+;
+
+test_set "envelope.from" "spam@example.com";
+test_set "envelope.to" "stephan@example.com";
+
+test "No reply to bulk mail" {
+	vacation "I am gone";
+
+	if not test_result_execute {
+		test_fail "failed to execute vacation";
+	}
+
+	if test_message :smtp 0 {
+		test_fail "vacation not supposed to send message";
+	}
+}
+
+/*
+ * No reply to auto-submitted mail
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: spam@example.com
+To: stephan@example.com
+Auto-submitted: yes
+Subject: Frop!
+
+Frop!
+.
+;
+
+test_set "envelope.from" "spam@example.com";
+test_set "envelope.to" "stephan@example.com";
+
+test "No reply to auto-submitted mail" {
+	vacation "I am gone";
+
+	if not test_result_execute {
+		test_fail "failed to execute vacation";
+	}
+
+	if test_message :smtp 0 {
+		test_fail "vacation not supposed to send message";
+	}
+}
+
+/*
+ * No reply to Microsoft X-Auto-Response-Suppress - All
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: spam@example.com
+To: stephan@example.com
+X-Auto-Response-Suppress: All
+Subject: Frop!
+
+Frop!
+.
+;
+
+test_set "envelope.from" "spam@example.com";
+test_set "envelope.to" "stephan@example.com";
+
+test "No reply to Microsoft X-Auto-Response-Suppress - All" {
+	vacation "I am gone";
+
+	if not test_result_execute {
+		test_fail "failed to execute vacation";
+	}
+
+	if test_message :smtp 0 {
+		test_fail "vacation not supposed to send message";
+	}
+}
+
+/*
+ * No reply to Microsoft X-Auto-Response-Suppress - OOF
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: spam@example.com
+To: stephan@example.com
+X-Auto-Response-Suppress: OOF
+Subject: Frop!
+
+Frop!
+.
+;
+
+test_set "envelope.from" "spam@example.com";
+test_set "envelope.to" "stephan@example.com";
+
+test "No reply to Microsoft X-Auto-Response-Suppress - OOF" {
+	vacation "I am gone";
+
+	if not test_result_execute {
+		test_fail "failed to execute vacation";
+	}
+
+	if test_message :smtp 0 {
+		test_fail "vacation not supposed to send message";
+	}
+}
+
+/*
+ * No reply to Microsoft X-Auto-Response-Suppress - DR,OOF,RN
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: spam@example.com
+To: stephan@example.com
+X-Auto-Response-Suppress: DR, OOF,	RN
+Subject: Frop!
+
+Frop!
+.
+;
+
+test_set "envelope.from" "spam@example.com";
+test_set "envelope.to" "stephan@example.com";
+
+test "No reply to Microsoft X-Auto-Response-Suppress - DR,OOF,RN" {
+	vacation "I am gone";
+
+	if not test_result_execute {
+		test_fail "failed to execute vacation";
+	}
+
+	if test_message :smtp 0 {
+		test_fail "vacation not supposed to send message";
+	}
+}
+
+/*
+ * No reply to system address
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: dovecot@lists.example.com
+To: stephan@example.com
+Subject: Frop!
+
+Frop!
+.
+;
+
+test_set "envelope.from" "dovecot-request@lists.example.com";
+test_set "envelope.to" "stephan@example.com";
+
+test "No reply to system address" {
+	vacation "I am gone";
+
+	if not test_result_execute {
+		test_fail "failed to execute vacation";
+	}
+
+	if test_message :smtp 0 {
+		test_fail "vacation not supposed to send message";
+	}
+}
+
+/*
+ * No reply to implicitly delivered message
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: timo@example.com
+To: all@example.com
+Subject: Frop!
+
+Frop!
+.
+;
+
+test_set "envelope.from" "timo@example.com";
+test_set "envelope.to" "stephan@example.com";
+
+test_config_set "sieve_user_email" "jason@example.com";
+test_config_reload;
+
+test "No reply for implicitly delivered message" {
+	vacation "I am gone";
+
+	if not test_result_execute {
+		test_fail "failed to execute vacation";
+	}
+
+	if test_message :smtp 0 {
+		test_fail "vacation not supposed to send message";
+	}
+}
+
+/*
+ * No reply to original recipient
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: timo@example.com
+To: all@example.com
+Subject: Frop!
+
+Frop!
+.
+;
+
+test_set "envelope.from" "timo@example.com";
+test_set "envelope.to" "stephan@example.com";
+test_set "envelope.orig_to" "all@example.com";
+
+test "No reply for original recipient" {
+	vacation "I am gone";
+
+	if not test_result_execute {
+		test_fail "failed to execute vacation";
+	}
+
+	if test_message :smtp 0 {
+		test_fail "vacation not supposed to send message";
+	}
+}
+
+/*
+ * Reply for normal mail
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: timo@example.com
+To: stephan@example.com
+Subject: Frop!
+Auto-submitted: no
+Precedence: normal
+X-Auto-Response-Suppress: None
+
+Frop!
+.
+;
+
+test_set "envelope.from" "timo@example.com";
+test_set "envelope.to" "stephan@example.com";
+
+test "Reply for normal mail" {
+	vacation "I am gone";
+
+	if not test_result_execute {
+		test_fail "failed to execute vacation";
+	}
+
+	if not test_message :smtp 0 {
+		test_fail "vacation did not reply";
+	}
+}
+
+/*
+ * Reply for :addresses
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: timo@example.com
+To: all@example.com
+Subject: Frop!
+
+Frop!
+.
+;
+
+test_set "envelope.from" "timo@example.com";
+test_set "envelope.to" "stephan@example.com";
+
+test "Reply for :addresses" {
+	vacation :addresses "all@example.com" "I am gone";
+
+	if not test_result_execute {
+		test_fail "failed to execute vacation";
+	}
+
+	if not test_message :smtp 0 {
+		test_fail "vacation did not reply";
+	}
+}
+
+/*
+ * Reply for original recipient
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: timo@example.com
+To: all@example.com
+Subject: Frop!
+
+Frop!
+.
+;
+
+test_set "envelope.from" "timo@example.com";
+test_set "envelope.to" "stephan@example.com";
+test_set "envelope.orig_to" "all@example.com";
+
+test_config_set "sieve_vacation_use_original_recipient" "yes";
+test_config_reload :extension "vacation";
+
+test "Reply for original recipient" {
+	vacation "I am gone";
+
+	if not test_result_execute {
+		test_fail "failed to execute vacation";
+	}
+
+	if not test_message :smtp 0 {
+		test_fail "vacation did not reply";
+	}
+}
+
+/*
+ * Reply for user's explicitly configured email address
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: timo@example.com
+To: user@example.com
+Subject: Frop!
+
+Frop!
+.
+;
+
+test_set "envelope.from" "timo@example.com";
+test_set "envelope.to" "jibberish@example.com";
+test_set "envelope.orig_to" "even-more-jibberish@example.com";
+
+test_config_set "sieve_user_email" "user@example.com";
+test_config_reload;
+
+test "Reply for user's explicitly configured email address" {
+	vacation "I am gone";
+
+	if not test_result_execute {
+		test_fail "failed to execute vacation";
+	}
+
+	if not test_message :smtp 0 {
+		test_fail "vacation did not reply";
+	}
+
+	if not address "from" "user@example.com" {
+		test_fail "mail not sent from user's email address";
+	}
+}
+
+/*
+ * Reply for any recipient
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: timo@example.com
+To: all@example.com
+Subject: Frop!
+
+Frop!
+.
+;
+
+test_set "envelope.from" "timo@example.com";
+test_set "envelope.to" "stephan@example.com";
+
+test_config_set "sieve_vacation_dont_check_recipient" "yes";
+test_config_reload :extension "vacation";
+
+test "Reply for any recipient" {
+	vacation "I am gone";
+
+	if not test_result_execute {
+		test_fail "failed to execute vacation";
+	}
+
+	if not test_message :smtp 0 {
+		test_fail "vacation did not reply";
+	}
+}
+
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/vacation/smtp.svtest
@@ -0,0 +1,157 @@
+require "vnd.dovecot.testsuite";
+require "envelope";
+require "vacation";
+require "variables";
+
+test_set "message" text:
+From: stephan@example.org
+To: tss@example.net
+Subject: Frop!
+
+Frop!
+.
+;
+
+test_set "envelope.from" "sirius@example.org";
+test_set "envelope.to" "timo@example.net";
+
+test "Basic" {
+	vacation :addresses "tss@example.net" :from "Timo Sirainen <sirainen@example.net>" "I am gone";
+
+	if not test_result_execute {
+		test_fail "failed to execute vacation";
+	}
+
+	test_message :smtp 0;
+
+	if not address :is "to" "sirius@example.org" {
+		test_fail "to address incorrect";
+	}
+
+	if not address :is "from" "sirainen@example.net" {
+		test_fail "from address incorrect";
+	}
+
+	if not envelope :is "to" "sirius@example.org" {
+		test_fail "envelope recipient incorrect";
+	}
+
+	if not envelope :is "from" "" {
+		test_fail "envelope sender not null";
+	}
+}
+
+test_result_reset;
+test_set "envelope.from" "<>";
+
+test "Null Sender" {
+	vacation :addresses "tss@example.net" "I am gone";
+
+	if not test_result_execute {
+		test_fail "failed to execute vacation";
+	}
+
+	if test_message :smtp 0 {
+		test_fail "reject sent message to NULL sender";
+	}
+}
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@example.org
+To: timo@example.net
+Cc: stephan@friep.example.com
+Subject: Frop!
+
+Frop!
+.
+;
+
+test_set "envelope.from" "sirius@example.org";
+test_set "envelope.to" "timo@example.net";
+
+test "Envelope.to == To" {
+	vacation "I am gone";
+
+	if not test_result_execute {
+		test_fail "failed to execute vacation";
+	}
+
+	test_message :smtp 0;
+
+	if not address :is "from" "timo@example.net" {
+		test_fail "from address incorrect";
+	}
+
+	if not envelope :is "from" "" {
+		test_fail "envelope sender not null";
+	}
+}
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@example.org
+To: tss@example.net
+Cc: stephan@friep.example.com
+Subject: Frop!
+
+Frop!
+.
+;
+
+test_set "envelope.from" "sirius@example.org";
+test_set "envelope.to" "timo@example.net";
+
+test "Envelope.to != To" {
+	vacation :addresses "tss@example.net" "I am gone";
+
+	if not test_result_execute {
+		test_fail "failed to execute vacation";
+	}
+
+	test_message :smtp 0;
+
+	if not address :is "from" "tss@example.net" {
+		test_fail "from address incorrect";
+	}
+
+	if not envelope :is "from" "" {
+		test_fail "envelope sender not null";
+	}
+}
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@example.org
+To: tss@example.net
+Cc: colleague@example.net
+Subject: Frop!
+
+Frop!
+.
+;
+
+test_set "envelope.from" "sirius@example.org";
+test_set "envelope.to" "colleague@example.net";
+
+test "Cc" {
+	vacation "I am gone";
+
+	if not test_result_execute {
+		test_fail "failed to execute vacation";
+	}
+
+	test_message :smtp 0;
+
+	if not address :is "from" "colleague@example.net" {
+		if address :matches "from" "*" { }
+		test_fail "from address incorrect: ${1}";
+	}
+
+	if not envelope :is "from" "" {
+		test_fail "envelope sender not null";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/vacation/utf-8.svtest
@@ -0,0 +1,168 @@
+require "vnd.dovecot.testsuite";
+require "vacation";
+require "variables";
+
+test_set "message" text:
+From: stephan@example.org
+Subject: frop
+References: <1234@local.machine.example> <3456@example.net>
+ <435444@ttms.com> <4223@froop.example.net> <m345444444@message-id.exp>
+Message-ID: <432df324@example.org>
+To: nico@frop.example.org
+
+Frop
+.
+;
+
+test "UTF-8 Subject" {
+	/* Trigger vacation response with rediculous Russian subject */
+	vacation :subject "Auto: Я могу есть стекло, оно мне не вредит."
+		"I am not in today";
+
+	/* Execute Sieve result (sending message to dummy SMTP) */
+	if not test_result_execute {
+		test_fail "execution of result failed";
+	}
+
+	/* Retrieve message from dummy SMTP and set it as the active message under
+	 * test.
+	 */
+	test_message :smtp 0;
+
+	set "expected" "Auto: Я могу есть стекло, оно мне не вредит.";
+	if not header :is "subject" "${expected}" {
+		if header :matches "subject" "*" { set "subject" "${1}"; }
+
+		test_fail text:
+subject header is not encoded/decoded properly:
+expected: ${expected}
+decoded: ${subject}
+.
+;
+	}
+}
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@example.org
+Subject: frop
+References: <1234@local.machine.example> <3456@example.net>
+ <435444@ttms.com> <4223@froop.example.net> <m345444444@message-id.exp>
+Message-ID: <432df324@example.org>
+To: nico@frop.example.org
+
+Frop
+.
+;
+
+
+test "MIME Encoded Subject" {
+	/* Trigger vacation response with rediculous Russian subject */
+	vacation :subject "=?utf-8?b?w4TDlsOc?= sadasd"
+		"I am not in today";
+
+	/* Execute Sieve result (sending message to dummy SMTP) */
+	if not test_result_execute {
+		test_fail "execution of result failed";
+	}
+
+	/* Retrieve message from dummy SMTP and set it as the active message under
+	 * test.
+	 */
+	test_message :smtp 0;
+
+	set "expected" "ÄÖÜ sadasd";
+	if not header :is "subject" "${expected}" {
+		if header :matches "subject" "*" { set "subject" "${1}"; }
+
+		test_fail text:
+subject header is not encoded/decoded properly:
+expected: ${expected}
+decoded: ${subject}
+.
+;
+	}
+}
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@example.org
+Subject: frop
+Message-ID: <432df324@example.org>
+To: <g.m.karotte@example.com>
+
+Frop
+.
+;
+
+
+test "MIME Encoded From" {
+	vacation :subject "Frop"
+		:from "=?utf-8?q?G=C3=BCnther?= M. Karotte <g.m.karotte@example.com>"
+		"I am not in today";
+
+	/* Execute Sieve result (sending message to dummy SMTP) */
+	if not test_result_execute {
+		test_fail "execution of result failed";
+	}
+
+	/* Retrieve message from dummy SMTP and set it as the active message under
+	 * test.
+	 */
+	test_message :smtp 0;
+
+	set "expected" "Günther M. Karotte <g.m.karotte@example.com>";
+	if not header :is "from" "${expected}" {
+		if header :matches "from" "*" { set "decoded" "${1}"; }
+
+		test_fail text:
+from header is not encoded/decoded properly:
+expected: ${expected}
+decoded: ${decoded}
+.
+;
+	}
+}
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@example.org
+Subject: frop
+Message-ID: <432df324@example.org>
+To: <g.m.karotte@example.com>
+
+Frop
+.
+;
+
+
+test "MIME Encoded From - UTF-8 in phrase" {
+	vacation :subject "Frop"
+		:from "Günther M. Karotte <g.m.karotte@example.com>"
+		"I am not in today";
+
+	/* Execute Sieve result (sending message to dummy SMTP) */
+	if not test_result_execute {
+		test_fail "execution of result failed";
+	}
+
+	/* Retrieve message from dummy SMTP and set it as the active message under
+	 * test.
+	 */
+	test_message :smtp 0;
+
+	set "expected" "Günther M. Karotte <g.m.karotte@example.com>";
+	if not header :is "from" "${expected}" {
+		if header :matches "from" "*" { set "decoded" "${1}"; }
+
+		test_fail text:
+from header is not encoded/decoded properly:
+expected: ${expected}
+decoded: ${decoded}
+.
+;
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/variables/basic.svtest
@@ -0,0 +1,223 @@
+require "vnd.dovecot.testsuite";
+require "variables";
+
+test_set "message" text:
+From: stephan@example.org
+To: test@example.com
+Subject: Variables test
+
+Testing variables...
+.
+;
+
+/*
+ * Substitution syntax
+ */
+
+test "Unknown variables" {
+	set "q" "a";
+	set "qw" "bb";
+	set "qwe" "ccc";
+	set "qwer" "dddd";
+	set "qwert" "ccc";
+
+	if anyof (
+		not string "[${qwerty}]" "[]",
+		not string "[${20}]" "[]"
+	) {
+		test_fail "unknown variable not substituted with empty string";
+	}
+}
+
+test "One pass" {
+	set "something" "value";
+	set "s" "$";
+
+	if string "${s}{something}" "value" {
+		test_fail "somehow variable string is scanned multiple times";
+	}
+
+	if not string :matches "${s}{something}" "?{something}" {
+		test_fail "unexpected result";
+	}
+}
+
+test "Syntax errors" {
+	set "s" "$";
+	set "variable" "nonsense";
+
+	if anyof (
+		not string "$" "${s}",
+		not string "${" "${s}{",
+		not string "${a" "${s}{a",
+		not string "${$}" "${s}{$}",
+		not string "${%%%%}" "${s}{%%%%}",
+		not string "${0.s}" "${s}{0.s}",
+		not string "&%${}!" "&%${s}{}!",
+		not string "${doh!}" "${s}{doh!}" )
+	{
+		test_fail "variables substitution changed substring not matching variable-ref";
+	}
+}
+
+test "RFC syntax examples" {
+	# The variable "company" holds the value "ACME".  No other variables
+    # are set.
+	set "company" "ACME";
+
+	# "${full}"         => the empty string
+	if not string :is "${full}" "" {
+		test_fail "unknown variable did not yield empty string";
+	}
+
+	# "${company}"      => "ACME"
+	if not string :is "${company}" "ACME" {
+		test_fail "assigned variable did not get substituted";
+	}
+
+	# "${BAD${Company}" => "${BADACME"
+	if not string :is "${BAD${Company}" "${BADACME" {
+		test_fail "'BADACME' test did not yield expected result";
+	}
+
+	#"${President, ${Company} Inc.}"
+	#                        => "${President, ACME Inc.}"
+	if not string "${President, ${Company} Inc.}"
+		"${President, ACME Inc.}" {
+		test_fail "'Company president' test did not yield expected result";
+	}
+}
+
+/*
+ * Variable assignments
+ */
+
+test "Basic assignment" {
+	set "test" "Value";
+
+	if not string :is "${test}" "Value" {
+		test_fail "variable assignment failed";
+	}
+
+	if string :is "${test}" "value" {
+		test_fail "string test failed";
+	}
+}
+
+test "Assignment overwritten" {
+	set "test" "Value";
+	set "test" "More";
+
+	if not string :is "${test}" "More" {
+		test_fail "variable assignment failed";
+	}
+
+	if string :is "${test}" "Value" {
+		test_fail "value not overwritten";
+	}
+
+	if string :is "${test}" "nonsense" {
+		test_fail "string test failed";
+	}
+}
+
+test "Two assignments" {
+	set "test" "Value";
+	set "test2" "More";
+
+	if not string :is "${test}" "Value" {
+		test_fail "variable assignment failed";
+	}
+
+	if string :is "${test}" "More" {
+		test_fail "assignments to different variables overlap";
+	}
+
+	if string :is "${test}" "nonsense" {
+		test_fail "string test failed";
+	}
+}
+
+test "Variables case-insensitive" {
+	set "VeRyElAboRATeVaRIABLeName" "interesting value";
+
+	if not string "${veryelaboratevariablename}" "interesting value" {
+		test_fail "variable names are case sensitive (lower case try)";
+	}
+
+	if not string "${VERYELABORATEVARIABLENAME}" "interesting value" {
+		test_fail "variable names are case sensitive (upper case try)";
+	}
+}
+
+test "RFC set command example" {
+	set "honorific"  "Mr";
+	set "first_name" "Wile";
+	set "last_name"  "Coyote";
+	set "vacation" text:
+Dear ${HONORIFIC} ${last_name},
+I'm out, please leave a message after the meep.
+.
+;
+	if not string :is :comparator "i;octet" "${VAcaTION}" text:
+Dear Mr Coyote,
+I'm out, please leave a message after the meep.
+.
+	{
+		test_fail "failed to set variable correctly: ${VAcaTION}";
+	}
+}
+
+/*
+ * Variable substitution
+ */
+
+test "Multi-line string substitution" {
+	set "name" "Stephan Bosch";
+	set "address" "stephan@example.org";
+	set "subject" "Test message";
+
+	set "message" text: # Message with substitutions
+From: ${name} <${address}>
+To: Bertus van Asseldonk <b.vanasseldonk@nl.example.com>
+Subject: ${subject}
+
+This is a test message.
+.
+;
+	if not string :is "${message}" text:
+From: Stephan Bosch <stephan@example.org>
+To: Bertus van Asseldonk <b.vanasseldonk@nl.example.com>
+Subject: Test message
+
+This is a test message.
+.
+	{
+		test_fail "variable substitution failed";
+	}
+}
+
+test "Multiple substitutions" {
+	set "a" "the monkey";
+	set "b" "a nut";
+	set "c" "the fish";
+	set "d" "on fire";
+	set "e" "eats";
+	set "f" "is";
+
+	if not string :is "${a} ${e} ${b}" "the monkey eats a nut" {
+		test_fail "variable substitution failed (1)";
+	}
+
+	if not string :is "${c} ${f} ${d}" "the fish is on fire" {
+		test_fail "variable substitution failed (2)";
+	}
+
+	set :upperfirst "sentence" "${a} ${e} ${b}";
+
+	if not string :is "${sentence}" "The monkey eats a nut" {
+		test_fail "modified variable substitution failed";
+	}
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/variables/errors.svtest
@@ -0,0 +1,34 @@
+require "vnd.dovecot.testsuite";
+
+require "comparator-i;ascii-numeric";
+require "relational";
+
+test "Invalid namespaces (FIXME: count only)" {
+	if test_script_compile "errors/namespace.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "5" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+test "Invalid set command invocations (FIXME: count only)" {
+	if test_script_compile "errors/set.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "7" {
+		test_fail "wrong number of errors reported";
+	}
+}
+
+test "Limits (FIXME: count only)" {
+	if test_script_compile "errors/limits.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "6" {
+		test_fail "wrong number of errors reported";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/variables/errors/limits.sieve
@@ -0,0 +1,287 @@
+require "variables";
+
+# Not an error (0)
+set "var123456789012345678901234567890" "value";
+
+# Exceed the maximum variable name length (1)
+set "var123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" "value";
+
+# Must yield unknown namespace error (no limit exceeded) (1)
+set "namespace.sub.sub.variable" "value";
+
+# Must yield unknown namespace error (exceeds element limit) (1)
+set "namespace.sub.sub.sub.variable" "value";
+
+# Not an error (0)
+if string "${32}" "value" {
+	stop;
+}
+
+# Exceed the maximum match value index (1)
+if string "${33}" "value" {
+	stop;
+}
+
+# Exceed the maximum number of declared variables (1!)
+set "var001" "value";
+set "var002" "value";
+set "var003" "value";
+set "var004" "value";
+set "var005" "value";
+set "var006" "value";
+set "var007" "value";
+set "var008" "value";
+set "var009" "value";
+set "var010" "value";
+set "var011" "value";
+set "var012" "value";
+set "var013" "value";
+set "var014" "value";
+set "var015" "value";
+set "var016" "value";
+set "var017" "value";
+set "var018" "value";
+set "var019" "value";
+set "var020" "value";
+set "var021" "value";
+set "var022" "value";
+set "var023" "value";
+set "var024" "value";
+set "var025" "value";
+set "var026" "value";
+set "var027" "value";
+set "var028" "value";
+set "var029" "value";
+set "var030" "value";
+set "var031" "value";
+set "var032" "value";
+set "var033" "value";
+set "var034" "value";
+set "var035" "value";
+set "var036" "value";
+set "var037" "value";
+set "var038" "value";
+set "var039" "value";
+set "var040" "value";
+set "var041" "value";
+set "var042" "value";
+set "var043" "value";
+set "var044" "value";
+set "var045" "value";
+set "var046" "value";
+set "var047" "value";
+set "var048" "value";
+set "var049" "value";
+set "var050" "value";
+set "var051" "value";
+set "var052" "value";
+set "var053" "value";
+set "var054" "value";
+set "var055" "value";
+set "var056" "value";
+set "var057" "value";
+set "var058" "value";
+set "var059" "value";
+set "var060" "value";
+set "var061" "value";
+set "var062" "value";
+set "var063" "value";
+set "var064" "value";
+set "var065" "value";
+set "var066" "value";
+set "var067" "value";
+set "var068" "value";
+set "var069" "value";
+set "var070" "value";
+set "var071" "value";
+set "var072" "value";
+set "var073" "value";
+set "var074" "value";
+set "var075" "value";
+set "var076" "value";
+set "var077" "value";
+set "var078" "value";
+set "var079" "value";
+set "var080" "value";
+set "var081" "value";
+set "var082" "value";
+set "var083" "value";
+set "var084" "value";
+set "var085" "value";
+set "var086" "value";
+set "var087" "value";
+set "var088" "value";
+set "var089" "value";
+set "var090" "value";
+set "var091" "value";
+set "var092" "value";
+set "var093" "value";
+set "var094" "value";
+set "var095" "value";
+set "var096" "value";
+set "var097" "value";
+set "var098" "value";
+set "var099" "value";
+
+set "var100" "value";
+set "var101" "value";
+set "var102" "value";
+set "var103" "value";
+set "var104" "value";
+set "var105" "value";
+set "var106" "value";
+set "var107" "value";
+set "var108" "value";
+set "var109" "value";
+set "var110" "value";
+set "var111" "value";
+set "var112" "value";
+set "var113" "value";
+set "var114" "value";
+set "var115" "value";
+set "var116" "value";
+set "var117" "value";
+set "var118" "value";
+set "var119" "value";
+set "var120" "value";
+set "var121" "value";
+set "var122" "value";
+set "var123" "value";
+set "var124" "value";
+set "var125" "value";
+set "var126" "value";
+set "var127" "value";
+set "var128" "value";
+set "var129" "value";
+set "var130" "value";
+set "var131" "value";
+set "var132" "value";
+set "var133" "value";
+set "var134" "value";
+set "var135" "value";
+set "var136" "value";
+set "var137" "value";
+set "var138" "value";
+set "var139" "value";
+set "var140" "value";
+set "var141" "value";
+set "var142" "value";
+set "var143" "value";
+set "var144" "value";
+set "var145" "value";
+set "var146" "value";
+set "var147" "value";
+set "var148" "value";
+set "var149" "value";
+set "var150" "value";
+set "var151" "value";
+set "var152" "value";
+set "var153" "value";
+set "var154" "value";
+set "var155" "value";
+set "var156" "value";
+set "var157" "value";
+set "var158" "value";
+set "var159" "value";
+set "var160" "value";
+set "var161" "value";
+set "var162" "value";
+set "var163" "value";
+set "var164" "value";
+set "var165" "value";
+set "var166" "value";
+set "var167" "value";
+set "var168" "value";
+set "var169" "value";
+set "var170" "value";
+set "var171" "value";
+set "var172" "value";
+set "var173" "value";
+set "var174" "value";
+set "var175" "value";
+set "var176" "value";
+set "var177" "value";
+set "var178" "value";
+set "var179" "value";
+set "var180" "value";
+set "var181" "value";
+set "var182" "value";
+set "var183" "value";
+set "var184" "value";
+set "var185" "value";
+set "var186" "value";
+set "var187" "value";
+set "var188" "value";
+set "var189" "value";
+set "var190" "value";
+set "var191" "value";
+set "var192" "value";
+set "var193" "value";
+set "var194" "value";
+set "var195" "value";
+set "var196" "value";
+set "var197" "value";
+set "var198" "value";
+set "var199" "value";
+set "var200" "value";
+
+set "var201" "value";
+set "var202" "value";
+set "var203" "value";
+set "var204" "value";
+set "var205" "value";
+set "var206" "value";
+set "var207" "value";
+set "var208" "value";
+set "var209" "value";
+set "var210" "value";
+set "var211" "value";
+set "var212" "value";
+set "var213" "value";
+set "var214" "value";
+set "var215" "value";
+set "var216" "value";
+set "var217" "value";
+set "var218" "value";
+set "var219" "value";
+set "var220" "value";
+set "var221" "value";
+set "var222" "value";
+set "var223" "value";
+set "var224" "value";
+set "var225" "value";
+set "var226" "value";
+set "var227" "value";
+set "var228" "value";
+set "var229" "value";
+set "var230" "value";
+set "var231" "value";
+set "var232" "value";
+set "var233" "value";
+set "var234" "value";
+set "var235" "value";
+set "var236" "value";
+set "var237" "value";
+set "var238" "value";
+set "var239" "value";
+set "var240" "value";
+set "var241" "value";
+set "var242" "value";
+set "var243" "value";
+set "var244" "value";
+set "var245" "value";
+set "var246" "value";
+set "var247" "value";
+set "var248" "value";
+set "var249" "value";
+set "var250" "value";
+set "var251" "value";
+set "var252" "value";
+set "var253" "value";
+set "var254" "value";
+set "var255" "value";
+set "var256" "value";
+set "var257" "value";
+set "var258" "value";
+set "var259" "value";
+set "var260" "value";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/variables/errors/namespace.sieve
@@ -0,0 +1,8 @@
+require "variables";
+require "fileinto";
+
+set "namespace.frop" "value";
+set "complex.struct.frop" "value";
+
+fileinto "${namespace.frop}";
+fileinto "${complex.struct.frop}";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/variables/errors/set.sieve
@@ -0,0 +1,19 @@
+require "variables";
+
+# Invalid variable name
+set "${frop}" "frop";
+set "...." "frop";
+set "name." "frop";
+set ".name" "frop";
+
+# Not an error
+set "\n\a\m\e" "frop";
+
+# Trying to assign match variable;
+set "0" "frop";
+
+# Not an error
+set :UPPER "name" "frop";
+
+# Invalid tag
+set :inner "name" "frop";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/variables/match.svtest
@@ -0,0 +1,365 @@
+require "vnd.dovecot.testsuite";
+
+require "variables";
+
+/*
+ * RFC compliance
+ */
+
+# Test acceptance of leading zeroes
+test "RFC - leading zeroes" {
+	if not string :matches "frop:frup:frop" "*:*:*" {
+		test_fail "failed to match";
+	}
+
+	if not string :is "${0000002}" "frup" {
+		test_fail "incorrect match value (0000002): ${0000002}";
+	}
+}
+
+# Test non-greedyness
+test "RFC - not greedy" {
+	if not string :matches "frop.......frop.........frop...." "?*frop*" {
+		test_fail "failed to match";
+	}
+
+	if not string :is "${1}${2}${3}" "frop................frop...." {
+		test_fail "incorrect match values: ${1}${2}${3}";
+	}
+}
+
+# Index out of range
+test "RFC - index out of range" {
+	if not string :matches "test" "*" {
+		test_fail "failed to match (impossible)";
+	}
+
+	if not string :is "${2}" "" {
+		test_fail "incorrect match value: '${2}'";
+	}
+}
+
+# Index 0
+test "RFC - index 0" {
+	if not string :matches "a b c d e f g" "? ? ? ? ? ? ?" {
+		test_fail "failed to match";
+	}
+
+	if not string :is "${0}" "a b c d e f g" {
+        test_fail "incorrect match value: ${0}";
+    }
+}
+
+# Test short-circuit
+test "RFC - test short-circuit" {
+	if not anyof (
+		string :matches "a b c d e f g" "? ?",
+		string :matches "puk pok puk pok" "pu*ok",
+		string :matches "snot kip snot" "snot*snot"
+	) {
+		test_fail "failed to match any";
+	}
+
+	if string :is "${1}" " kip " {
+		test_fail "did not short-circuit test execution or intented test failed.";
+	}
+
+	if not string :is "${1}" "k pok puk p" {
+		test_fail "incorrect match value: ${1}";
+	}
+}
+
+# Test overwriting only on match
+test "RFC - values overwrite" {
+	set "sentence1" "the cat jumps off the table";
+	set "sentence2" "the dog barks at the cat in the alley";
+
+	if not string :matches "${sentence1}" "the * jumps off the *" {
+		test_fail "failed to match first sentence";
+	}
+
+	if not string :is "${1}:${2}" "cat:table" {
+		test_fail "invalid match values";
+	}
+
+	if string :matches "${sentence2}" "the * barks at the * in the store" {
+		test_fail "should not have matched second sentence";
+	}
+
+	if not string :is "${1}:${2}" "cat:table" {
+		test_fail "should have preserved match values";
+	}
+
+	if not string :matches "${sentence2}" "the * barks at the * in the alley" {
+		test_fail "failed to match the second sentence (second time)";
+	}
+
+	if not string :is "${1}:${2}" "dog:cat" {
+		test_fail "should have overwritten match values";
+	}
+}
+
+test "RFC - example" {
+	test_set "message" text:
+Subject: [acme-users] [fwd] version 1.0 is out
+List-Id: Dovecot Mailing List <dovecot@dovecot.example.net>
+To: coyote@ACME.Example.COM
+Fom: stephan@example.org
+
+Test message.
+.
+;
+	if header :matches "List-ID" "*<*@*" {
+		if not string "INBOX.lists.${2}" "INBOX.lists.dovecot" {
+			test_fail "incorrect match value: INBOX.lists.${2}";
+		}
+	} else {
+		test_fail "failed to match list header";
+	}
+
+	# Imagine the header
+	# Subject: [acme-users] [fwd] version 1.0 is out
+	if header :matches "Subject" "[*] *" {
+		# ${1} will hold "acme-users",
+		# ${2} will hold "[fwd] version 1.0 is out"
+
+		if anyof (
+			not string "${1}" "acme-users",
+			not string "${2}" "[fwd] version 1.0 is out"
+		) {
+			test_fail "invalid match values: ${1} ${2}";
+		}
+	} else {
+		test_fail "failed to match subject";
+	}
+
+	# Imagine the header
+	# To: coyote@ACME.Example.COM
+	if address :matches ["To", "Cc"] ["coyote@**.com",
+		"wile@**.com"] {
+		# ${0} is the matching address
+		# ${1} is always the empty string
+		# ${2} is part of the domain name ("ACME.Example")
+
+		if anyof (
+			not string "${0}" "coyote@ACME.Example.COM",
+			not string "${1}" "",
+			not string "${2}" "ACME.Example"
+		) {
+			test_fail "invalid match values: ${0}, ${1}, ${2}";
+		}
+	} else {
+		# Control wouldn't reach this block if any match was
+		# successful, so no match variables are set at this
+ 		# point.
+
+		test_fail "failed to match to address";
+ 	}
+
+	if anyof (true, address :domain :matches "To" "*.com") {
+		# The second test is never evaluated, so there are
+		# still no match variables set.
+
+		/* FIXME: not compliant */
+	}
+}
+
+/*
+ * Generic tests
+ */
+
+set "match1" "Test of general stupidity";
+
+test "Begin" {
+	if not string :matches "${match1}" "Test of *" {
+		test_fail "should have matched";
+	}
+
+	if not string :is "${1}" "general stupidity" {
+		test_fail "match value incorrect";
+	}
+}
+
+test "Begin no match" {
+	if string :matches "${match1}" "of *" {
+		test_fail "should not have matched";
+	}
+}
+
+set "match2" "toptoptop";
+
+test "End" {
+	if not string :matches "${match2}" "*top" {
+		test_fail "should have matched";
+	}
+
+	if not string :is "${1}" "toptop" {
+		test_fail "match value incorrect";
+	}
+}
+
+set "match3" "ik ben een tukker met grote oren en een lelijke broek.";
+
+test "Multiple" {
+	if not string :matches "${match3}" "ik ben * met * en *." {
+		test_fail "should have matched";
+	}
+
+	set "line" "Hij is ${1} met ${2} en ${3}!";
+
+	if not string :is "${line}"
+		"Hij is een tukker met grote oren en een lelijke broek!" {
+		test_fail "match values incorrect: ${line}";
+	}
+}
+
+set "match4" "beter van niet?";
+
+test "Escape" {
+	if not string :matches "${match4}" "*\\?" {
+		test_fail "should have matched";
+	}
+
+	if not string :is "${1}" "beter van niet" {
+		test_fail "match value incorrect: ${1}";
+	}
+}
+
+set "match5" "The quick brown fox jumps over the lazy dog.";
+
+test "Alphabet ?" {
+	if not string :matches "${match5}" "T?? ????? ????? ?o? ?u??? o?er ?he ???? ?o?." {
+		test_fail "should have matched";
+	}
+
+	set "alphabet" "${22}${8}${6}${25}${2}${13}${26}${1}${5}${15}${7}${21}${16}${12}${10}${17}${3}${9}${18}${20}${4}${19}${11}${14}${24}${23}";
+
+	if not string :is "${alphabet}" "abcdefghijklmnopqrstuvwxyz" {
+		test_fail "match values incorrect: ${alphabet}";
+	}
+
+	if string :matches "${match5}" "T?? ????? ?w??? ?o? ?u??? o?er ?he ???? ?o?." {
+		test_fail "should not have matched";
+	}
+}
+
+set "match6" "zero:one:zero|three;one;zero/five";
+
+test "Words sep ?" {
+
+	if not string :matches "${match6}" "*one?zero?five" {
+		test_fail "should have matched";
+	}
+
+	if not string :is "${1}${2}${3}" "zero:one:zero|three;;/" {
+		test_fail "incorrect match values: ${1} ${2} ${3}";
+	}
+}
+
+set "match7" "frop";
+
+test "Letters begin ?" {
+	if not string :matches "${match7}" "??op" {
+		test_fail "should have matched";
+	}
+
+	set "val" "${0}:${1}:${2}:${3}:";
+
+	if not string :is "${val}" "frop:f:r::" {
+		test_fail "incorrect match values: ${val}";
+	}
+}
+
+test "Letters end ?" {
+    if not string :matches "${match7}" "fr??" {
+        test_fail "should have matched";
+    }
+
+    set "val" "${0}:${1}:${2}:${3}:";
+
+    if not string :is "${val}" "frop:o:p::" {
+        test_fail "incorrect match values: ${val}";
+    }
+}
+
+set "match8" "klopfropstroptop";
+
+test "Letters words *? - 1" {
+	if not string :matches "${match8}" "*fr??*top" {
+		test_fail "should have matched";
+	}
+
+	set "val" ":${0}:${1}:${2}:${3}:${4}:${5}:";
+
+	if not string :is "${val}" ":klopfropstroptop:klop:o:p:strop::" {
+		test_fail "incorrect match values: ${val}";
+	}
+}
+
+test "Letters words *? - 2" {
+	if not string :matches "${match8}" "?*fr??*top" {
+		test_fail "should have matched";
+	}
+
+	set "val" ":${0}:${1}:${2}:${3}:${4}:${5}:${6}:";
+
+	if not string :is "${val}" ":klopfropstroptop:k:lop:o:p:strop::" {
+		test_fail "incorrect match values: ${val}";
+	}
+}
+
+test "Letters words *? backtrack" {
+	if not string :matches "${match8}" "*?op" {
+		test_fail "should have matched";
+	}
+
+	set "val" ":${0}:${1}:${2}:${3}:${4}:";
+
+	if not string :is "${val}" ":klopfropstroptop:klopfropstrop:t:::" {
+		test_fail "incorrect match values: ${val}";
+	}
+}
+
+test "Letters words *? first" {
+	if not string :matches "${match8}" "*?op*" {
+		test_fail "failed to match";
+	}
+
+	set "val" ":${0}:${1}:${2}:${3}:${4}:";
+
+	if not string :is "${val}" ":klopfropstroptop:k:l:fropstroptop::" {
+		test_fail "incorrect match values: ${val}";
+	}
+}
+
+/*
+ * Specific tests
+ */
+
+test_set "message" text:
+Return-path: <stephan@xi.example.org>
+Envelope-to: stephan@xi.example.org
+Delivery-date: Sun, 01 Feb 2009 11:29:57 +0100
+Received: from stephan by xi.example.org with local (Exim 4.69)
+	(envelope-from <stephan@xi.example.org>)
+	id 1LTZaP-0007h3-2e
+	for stephan@xi.example.org; Sun, 01 Feb 2009 11:29:57 +0100
+From: Dovecot Debian Builder <stephan.example.org@xi.example.org>
+To: stephan@xi.example.org
+Subject: Log for failed build of dovecot_2:1.2.alpha5-0~auto+159 (dist=hardy)
+Message-Id: <E1LTZaP-0007h3-2e@xi.example.org>
+Date: Sun, 01 Feb 2009 11:29:57 +0100
+
+Automatic build of dovecot_1.2.alpha5-0~auto+159 on xi by sbuild/i386 0.57.7
+.
+;
+
+test "Match combined" {
+	if not header :matches "subject" "Log for ?* build of *" {
+		test_fail "failed to match";
+	}
+
+	if not string "${1}${2}" "failed" {
+		test_fail "incorrect match values: ${1}${2}";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/variables/modifiers.svtest
@@ -0,0 +1,160 @@
+require "vnd.dovecot.testsuite";
+require "variables";
+require "encoded-character";
+
+/*
+ * Modifiers
+ */
+
+test "Modifier :lower" {
+	set :lower "test" "VaLuE";
+
+	if not string :is "${test}" "value" {
+		test_fail "modified variable assignment failed";
+	}
+}
+
+test "Modifiers :lower :upperfirst" {
+	set :lower :upperfirst "test" "vAlUe";
+
+	if string :is "${test}" "value" {
+		test_fail "modifiers applied with wrong precedence";
+	}
+
+	if not string :is "${test}" "Value" {
+		test_fail "modified variable assignment failed";
+	}
+}
+
+test "Modifiers :upperfirst :lower" {
+	set :upperfirst :lower "test" "vAlUe";
+
+	if string :is "${test}" "value" {
+		test_fail "modifiers applied with wrong precedence";
+	}
+
+	if not string :is "${test}" "Value" {
+		test_fail "modified variable assignment failed";
+	}
+}
+
+test "Modifier :upper" {
+	set :upper "test" "vAlUe";
+
+	if not string :is "${test}" "VALUE" {
+		test_fail "modified variable assignment failed";
+	}
+}
+
+test "Modifiers :upper :lowerfirst" {
+	set :upper :lowerfirst "test" "VaLuE";
+
+	if string :is "${test}" "VALUE" {
+		test_fail "modifiers applied with wrong precedence";
+	}
+
+	if not string :is "${test}" "vALUE" {
+		test_fail "modified variable assignment failed";
+	}
+}
+
+test "Modifiers :lowerfirst :upper" {
+	set :lowerfirst :upper "test" "VaLuE";
+
+	if string :is "${test}" "VALUE" {
+		test_fail "modifiers applied with wrong precedence";
+	}
+
+	if not string :is "${test}" "vALUE" {
+		test_fail "modified variable assignment failed";
+	}
+}
+
+test "Modifier :length (empty)" {
+	set :length "test" "";
+
+	if not string :is "${test}" "0" {
+		test_fail "modified variable assignment failed";
+	}
+}
+
+test "Modifier :length (simple)" {
+	set :length "test" "VaLuE";
+
+	if not string :is "${test}" "5" {
+		test_fail "modified variable assignment failed";
+	}
+}
+
+test "Modifier :length (elaborate)" {
+	set "a" "abcdefghijklmnopqrstuvwxyz";
+	set "b" "1234567890";
+	set :length "test" " ${a}:${b}  ";
+
+	if not string :is "${test}" "40" {
+		test_fail "modified variable assignment failed";
+	}
+}
+
+test "Modifier :quotewildcard" {
+	set :quotewildcard "test" "^^***??**^^";
+
+	if not string :is "${test}" "^^\\*\\*\\*\\?\\?\\*\\*^^" {
+		test_fail "modified variable assignment failed";
+	}
+}
+
+test "Modifier :length :quotewildcard" {
+	set :length :quotewildcard "test" "^^***??**^^";
+
+	if string :is "${test}" "11" {
+		test_fail "modifiers applied with wrong precedence";
+	}
+
+	if not string :is "${test}" "18" {
+		test_fail "modified variable assignment failed";
+	}
+}
+
+test "RFC examples" {
+	set "a" "juMBlEd lETteRS";             # => "juMBlEd lETteRS"
+	if not string "${a}" "juMBlEd lETteRS" {
+		test_fail "modified assignment failed (1): ${a}";
+	}
+
+	set :length "b" "${a}";                # => "15"
+	if not string "${b}" "15" {
+		test_fail "modified assignment failed (2): ${a}";
+	}
+
+	set :lower "b" "${a}";                 #  => "jumbled letters"
+	if not string "${b}" "jumbled letters" {
+		test_fail "modified assignment failed (3): ${a}";
+	}
+
+    set :upperfirst "b" "${a}";            # => "JuMBlEd lETteRS"
+	if not string "${b}" "JuMBlEd lETteRS" {
+		test_fail "modified assignment failed (4): ${a}";
+	}
+
+	set :upperfirst :lower "b" "${a}";     # => "Jumbled letters"
+	if not string "${b}" "Jumbled letters" {
+		test_fail "modified assignment failed (5): ${a}";
+	}
+
+	set :quotewildcard "b" "Rock*";        # => "Rock\*"
+	if not string "${b}" "Rock\\*" {
+		test_fail "modified assignment failed (6): ${a}";
+	}
+}
+
+/* RFC mentions `characters' and not octets */
+
+test "Modifier :length utf8" {
+	set "a" "Das ist ${unicode: 00fc}berhaupt nicht m${unicode: 00f6}glich.";
+
+	set :length "b" "${a}";
+    if not string "${b}" "32" {
+        test_fail "incorrect number of unicode characters reported: ${b}/32";
+    }
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/variables/quoting.svtest
@@ -0,0 +1,36 @@
+require "vnd.dovecot.testsuite";
+
+require "variables";
+require "encoded-character";
+
+test "Encodings - RFC examples" {
+	set "s" "$";
+	set "foo" "bar";
+
+	# "${fo\o}"  => ${foo}  => the expansion of variable foo.
+	if not string :is "${fo\o}" "bar" {
+		test_fail "failed 'the expansion of variable foo (${s}{fo\\o})'";
+	}
+
+	# "${fo\\o}" => ${fo\o} => illegal identifier => left verbatim.
+	if not string :is "${fo\\o}" "${s}{fo\\o}" {
+		test_fail "failed 'illegal identifier => left verbatim'";
+	}
+
+	# "\${foo}"  => ${foo}  => the expansion of variable foo.
+	if not string "\${foo}" "bar" {
+		test_fail "failed 'the expansion of variable foo (\\${s}{foo})'";
+	}
+
+	# "\\${foo}" => \${foo} => a backslash character followed by the
+	#                          expansion of variable foo.
+	if not string "\\${foo}" "\\bar" {
+		test_fail "failed 'a backslash character followed by expansion of variable foo";
+	}
+
+	set "name" "Ethelbert";
+	if not string "dear${hex:20 24 7b 4e}ame}" "dear Ethelbert" {
+		test_fail "failed 'dear Ethelbert' example";
+    }
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/variables/regex.svtest
@@ -0,0 +1,35 @@
+require "vnd.dovecot.testsuite";
+
+require "regex";
+require "variables";
+
+# Test overwriting only on match
+test "RFC - values overwrite" {
+	set "sentence1" "the cat jumps off the table";
+	set "sentence2" "the dog barks at the cat in the alley";
+
+	if not string :regex "${sentence1}" "the (.*) jumps off the (.*)" {
+		test_fail "failed to match first sentence";
+	}
+
+	if not string :is "${1}:${2}" "cat:table" {
+		test_fail "invalid match values";
+	}
+
+	if string :regex "${sentence2}" "the (.*) barks at the (.*) in the store" {
+		test_fail "should not have matched second sentence";
+	}
+
+	if not string :is "${1}:${2}" "cat:table" {
+		test_fail "should have preserved match values";
+	}
+
+	if not string :regex "${sentence2}" "the (.*) barks at the (.*) in the alley" {
+		test_fail "failed to match the second sentence (second time)";
+	}
+
+	if not string :is "${1}:${2}" "dog:cat" {
+		test_fail "should have overwritten match values";
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/variables/string.svtest
@@ -0,0 +1,37 @@
+require "vnd.dovecot.testsuite";
+
+require "relational";
+require "comparator-i;ascii-numeric";
+
+require "variables";
+
+test "String - :count" {
+	if not string :count "eq" :comparator "i;ascii-numeric" ["a", "b", "c"] "3" {
+		test_fail "string test failed :count match";
+	}
+}
+
+test "String - :count \"\"" {
+	if not string :count "eq" :comparator "i;ascii-numeric" ["a", "", "c"] "2" {
+		test_fail "string test failed :count match";
+	}
+}
+
+test "RFC example" {
+	set "state" "${state} pending";
+
+	if not string :matches " ${state} " "* pending *" {
+    	# the above test always succeeds
+
+		test_fail "test should have matched: \" ${state} \"";
+	}
+}
+
+test "No whitespace stripping" {
+	set "vara" "      value       ";
+	set "varb" "value";
+
+	if not string :is :comparator "i;octet" "${vara}" "      ${varb}       " {
+		test_fail "string test seems to have stripped white space";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/vnd.dovecot/debug/execute.svtest
@@ -0,0 +1,6 @@
+require "vnd.dovecot.testsuite";
+require "vnd.dovecot.debug";
+
+test "Basic" {
+	debug_log "logging basic message.";
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/vnd.dovecot/environment/basic.svtest
@@ -0,0 +1,29 @@
+require "vnd.dovecot.testsuite";
+require "vnd.dovecot.environment";
+require "variables";
+
+test "default-mailbox" {
+	if not environment :is "vnd.dovecot.default-mailbox" "INBOX" {
+		if environment :matches "vnd.dovecot.default-mailbox" "*" { set "env" "${1}"; }
+
+		test_fail "vnd.dovecot.default-mailbox environment returned invalid value(1): `${env}'";
+	}
+}
+
+test "username" {
+	if not environment :contains "vnd.dovecot.username" "" {
+		test_fail "vnd.dovecot.username environment does not exist";
+	}
+}
+
+test_config_set "sieve_env_display_name" "Jan Jansen";
+test_config_reload :extension "vnd.dovecot.environment";
+
+test "config" {
+	if not environment :contains "vnd.dovecot.config.display_name" "" {
+		test_fail "vnd.dovecot.config.display_name environment does not exist";
+	}
+	if not environment :is "vnd.dovecot.config.display_name" "Jan Jansen" {
+		test_fail "vnd.dovecot.config.display_name environment has wrong value";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/vnd.dovecot/environment/variables.svtest
@@ -0,0 +1,18 @@
+require "vnd.dovecot.testsuite";
+require "vnd.dovecot.environment";
+require "variables";
+require "relational";
+
+test "default_mailbox" {
+	if not string "${env.vnd.dovecot.default_mailbox}" "INBOX" {
+		test_fail "The env.vnd.dovecot.default_mailbox variable returned invalid value: `${env.vnd.dovecot.default_mailbox}'";
+	}
+}
+
+test "username" {
+	set :length "userlen" "${env.vnd.dovecot.username}";
+	if not string :value "ge" "${userlen}" "1" {
+		test_fail "The env.vnd.dovecot.username variable is empty or does not exist";
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/vnd.dovecot/report/errors.svtest
@@ -0,0 +1,13 @@
+require "vnd.dovecot.testsuite";
+require "comparator-i;ascii-numeric";
+require "relational";
+
+test "Invalid syntax (FIXME: count only)" {
+	if test_script_compile "errors/syntax.sieve" {
+		test_fail "compile should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "9" {
+		test_fail "wrong number of errors reported";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/vnd.dovecot/report/errors/syntax.sieve
@@ -0,0 +1,28 @@
+require "vnd.dovecot.report";
+
+# 1: Too few arguments
+report;
+
+# 2: Too few arguments
+report "abuse";
+
+# 3: Too few arguments
+report "abuse" "Message is spam.";
+
+# Not an error
+report "abuse" "Message is spam." "frop@example.com";
+
+# 4: Bad arguments
+report "abuse" "Message is spam." 1;
+
+# 5: Bad tag
+report :frop "abuse" "Message is spam." "frop@example.com";
+
+# 6: Bad sub-test
+report "abuse" "Message is spam." "frop@example.com" frop;
+
+# 7: Bad block
+report "abuse" "Message is spam." "frop@example.com" { }
+
+# 8: Bad feedback type
+report "?????" "Message is spam." "frop@example.com";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/extensions/vnd.dovecot/report/execute.svtest
@@ -0,0 +1,269 @@
+require "vnd.dovecot.testsuite";
+require "vnd.dovecot.report";
+require "relational";
+require "comparator-i;ascii-numeric";
+require "body";
+require "variables";
+
+/*
+ * Simple test
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test "Simple" {
+	report "abuse" "This message is spam!" "abuse@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not body :raw :contains "This message is spam!" {
+		test_fail "report does not contain user text";
+	}
+
+	if not body :raw :contains "Klutsefluts" {
+		test_fail "report does not contain message body";
+	}
+}
+
+/*
+ * Simple - :headers_only test
+ */
+
+test_result_reset;
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test "Simple - :headers_only" {
+	report :headers_only "abuse"
+		"This message is spam!" "abuse@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not body :raw :contains "This message is spam!" {
+		test_fail "report does not contain user text";
+	}
+
+	if body :raw :contains "Klutsefluts" {
+		test_fail "report contains message body";
+	}
+}
+
+/*
+ * Configuration
+ */
+
+set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+/* default */
+
+test_set "message" "${message}";
+test_set "envelope.from" "from@example.com";
+test_set "envelope.to" "to@example.com";
+test_set "envelope.orig_to" "orig_to@example.com";
+
+test_result_reset;
+
+test "Configuration - from default" {
+	report "abuse" "This message is spam!" "abuse@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not address :localpart "from" "postmaster" {
+		test_fail "not sent from postmaster";
+	}
+}
+
+/* from sender */
+
+test_set "message" "${message}";
+test_set "envelope.from" "from@example.com";
+test_set "envelope.to" "to@example.com";
+test_set "envelope.orig_to" "orig_to@example.com";
+
+test_config_set "sieve_report_from" "sender";
+test_config_reload :extension "vnd.dovecot.report";
+test_result_reset;
+
+test "Configuration - from sender" {
+	report "abuse" "This message is spam!" "abuse@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not address :localpart "from" "from" {
+		test_fail "not sent from sender";
+	}
+}
+
+/* from recipient */
+
+test_set "message" "${message}";
+test_set "envelope.from" "from@example.com";
+test_set "envelope.to" "to@example.com";
+test_set "envelope.orig_to" "orig_to@example.com";
+
+test_config_set "sieve_report_from" "recipient";
+test_config_reload :extension "vnd.dovecot.report";
+test_result_reset;
+
+test "Configuration - from recipient" {
+	report "abuse" "This message is spam!" "abuse@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not address :localpart "from" "to" {
+		test_fail "not sent from recipient";
+	}
+}
+
+/* from original recipient */
+
+test_set "message" "${message}";
+test_set "envelope.from" "from@example.com";
+test_set "envelope.to" "to@example.com";
+test_set "envelope.orig_to" "orig_to@example.com";
+
+test_config_set "sieve_report_from" "orig_recipient";
+test_config_reload :extension "vnd.dovecot.report";
+test_result_reset;
+
+test "Configuration - from original recipient" {
+	report "abuse" "This message is spam!" "abuse@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not address :localpart "from" "orig_to" {
+		test_fail "not sent from original recipient";
+	}
+}
+
+/* from user email */
+
+test_set "message" "${message}";
+test_set "envelope.from" "from@example.com";
+test_set "envelope.to" "to@example.com";
+test_set "envelope.orig_to" "orig_to@example.com";
+
+test_config_set "sieve_report_from" "user_email";
+test_config_set "sieve_user_email" "user@example.com";
+test_config_reload;
+test_config_reload :extension "vnd.dovecot.report";
+test_result_reset;
+
+test "Configuration - from user email" {
+	report "abuse" "This message is spam!" "abuse@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not address :localpart "from" "user" {
+		test_fail "not sent from user email";
+	}
+}
+
+/* explicit */
+
+test_set "message" "${message}";
+test_set "envelope.from" "from@example.com";
+test_set "envelope.to" "to@example.com";
+test_set "envelope.orig_to" "orig_to@example.com";
+
+test_config_set "sieve_report_from" "<frop@example.com>";
+test_config_reload :extension "vnd.dovecot.report";
+test_result_reset;
+
+test "Configuration - explicit" {
+	report "abuse" "This message is spam!" "abuse@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not address :localpart "from" "frop" {
+		test_fail "not sent from explicit address";
+	}
+}
+
+/*
+ * Reporting-User
+ */
+
+/* sieve_user_email */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+Subject: Frop!
+
+Klutsefluts.
+.
+;
+
+test_set "envelope.orig_to" "orig_to@example.com";
+
+test_config_set "sieve_user_email" "newuser@example.com";
+test_config_reload;
+test_result_reset;
+
+test "Reporting-User - sieve_user_email" {
+	report "abuse" "This message is spam!" "abuse@example.com";
+
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not body :raw :contains "Dovecot-Reporting-User: <newuser@example.com>" {
+		test_fail "Reporting-User field is wrong.";
+	}
+}
\ No newline at end of file
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/lexer.svtest
@@ -0,0 +1,39 @@
+require "vnd.dovecot.testsuite";
+require "variables";
+
+/* Test conformance to RFC 5228 - 2.4.2. Strings */
+
+set "text" text: # Comment
+Line 1
+.Line 2
+..Line 3
+.Line 4
+Line 5
+.
+;
+
+set "quoted"
+"Line 1
+.Line 2
+.Line 3
+.Line 4
+Line 5
+";
+
+test "String Literal" {
+	if not string :is "${text}" "${quoted}" {
+		test_fail "lexer messed-up dot stuffing";
+	}
+
+	if string :is "${text}" "" {
+		test_fail "variable substitution failed";
+	}
+}
+
+test "Unknown Escapes" {
+	if not string :is "\a\a\a\a\a" "aaaaa" {
+		test_fail "unknown quoted string escape sequences are handled inappropriately";
+	}
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/match-types/contains.svtest
@@ -0,0 +1,81 @@
+require "vnd.dovecot.testsuite";
+
+test_set "message" text:
+From: stephan@example.org
+Cc: frop@example.com
+To: test@dovecot.example.net
+X-Bullshit: f fr fro frop frob frobn frobnitzn
+Subject: Test Message
+Comment:
+
+Test!
+.
+;
+
+# Match tests
+
+test "Match empty" {
+	if not header :contains "x-bullshit" "" {
+		test_fail "contains tests fails to match \"\" against non-empty string";
+	}
+
+	if not header :contains "comment" "" {
+		test_fail "contains tests fails to match \"\" against empty string";
+	}
+}
+
+test "Match full" {
+	if not address :contains "from" "stephan@example.org" {
+		test_fail "should have matched";
+	}
+}
+
+test "Match begin" {
+	if not address :contains "from" "stephan" {
+		test_fail "should have matched";
+	}
+}
+
+test "Match end" {
+	if not address :contains "from" "example.org" {
+		test_fail "should have matched";
+	}
+}
+
+test "Match middle" {
+	if not address :contains "from" "@" {
+		test_fail "should have matched";
+	}
+}
+
+test "Match similar beginnings" {
+	if not header :contains "x-bullshit" "frobnitzn" {
+		test_fail "should have matched";
+	}
+}
+
+test "Match case-insensitive" {
+	if not address :contains :comparator "i;ascii-casemap" "from" "EXAMPLE" {
+		test_fail "match fails to apply correct comparator";
+	}
+
+	if not address :contains "from" "EXAMPLE" {
+		test_fail "default comparator is wrong";
+	}
+}
+
+# Non-match tests
+
+test "No match full (typo)" {
+	if address :contains "to" "frob@example.com" {
+		test_fail "should not have matched";
+	}
+}
+
+test "No match end (typo)" {
+	if header :contains "x-bullshit" "frobnitzm" {
+		test_fail "should not have matched";
+	}
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/match-types/is.svtest
@@ -0,0 +1,22 @@
+require "vnd.dovecot.testsuite";
+
+test_set "message" text:
+From: Stephan Bosch <stephan@example.org>
+To: nico@frop.example.org
+Subject: Test message
+Comment:
+
+Test!
+
+.
+;
+
+test "Empty key" {
+	if header :is "from" "" {
+		test_fail "erroneously matched empty key against non-empty string";
+	}
+
+	if not header :is "comment" "" {
+		test_fail "failed to match empty string";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/match-types/matches.svtest
@@ -0,0 +1,241 @@
+require "vnd.dovecot.testsuite";
+
+test_set "message" text:
+From: stephan+sieve@friep.example.com
+To: sirius@example.org
+To: nico@frop.example.org
+Cc: me@example.com
+Cc: timo@dovecot.example.com
+X-Hufter: TRUE
+Subject: make your money very fast!!!
+X-Spam-Score: **********
+X-Bullshit: 33333???a
+Message-ID: <90a02fe01fc25e131d0e9c4c45975894@example.com>
+Comment:
+X-Subject: Log for successful build of Dovecot.
+
+Het werkt!
+.
+;
+
+/*
+ * General conformance testing
+ */
+
+test "Empty string" {
+	if not header :matches "comment" "" {
+		test_fail "failed to match \"\" against \"\"";
+	}
+
+	if not header :matches "comment" "*" {
+		test_fail "failed to match \"\" against \"*\"";
+	}
+
+	if header :matches "comment" "?" {
+		test_fail "inappropriately matched \"\" against \"?\"";
+	}
+}
+
+test "Multiple '*'" {
+	if not address :matches "from" "*@fri*p*examp*.com" {
+		test_fail "should have matched";
+	}
+
+	if address :matches "from" "*@f*pex*mple.com" {
+		test_fail "should not have matched";
+	}
+}
+
+test "End '*'" {
+	if not address :matches "from" "stephan+sieve@friep.*" {
+		test_fail "should have matched";
+	}
+
+	if address :matches "from" "stepan+sieve@friep.*" {
+		test_fail "should not have matched";
+	}
+}
+
+test "Begin '*'" {
+	if not address :matches "from" "*+sieve@friep.example.com" {
+		test_fail "should have matched";
+	}
+
+	if address :matches "from" "*+sieve@friep.example.om" {
+		test_fail "should not have matched";
+	}
+}
+
+test "Middle '?'" {
+	if not address :matches "from" "stephan+sieve?friep.example.com" {
+		test_fail "should have matched";
+	}
+
+	if address :matches "from" "stephan+sieve?fiep.example.com" {
+		test_fail "should not have matched";
+	}
+}
+
+test "Begin '?'" {
+	if not address :matches "from" "?tephan+sieve@friep.example.com" {
+		test_fail "should have matched";
+	}
+
+	if address :matches "from" "?tephan+sievefriep.example.com" {
+		test_fail "should not have matched";
+	}
+}
+
+test "End '?'" {
+	if not address :matches "from" "stephan+sieve@friep.example.co?" {
+		test_fail "should have matched";
+	}
+
+	if address :matches "from" "sephan+sieve@friep.example.co?" {
+		test_fail "should not have matched";
+	}
+}
+
+test "Multiple '?'" {
+	if not address :matches "from" "?t?phan?sieve?fri?p.exampl?.co?" {
+		test_fail "should have matched";
+	}
+
+	if address :matches "from" "?t?phan?sieve?fiep.exam?le.co?" {
+		test_fail "should not have matched";
+	}
+}
+
+test "Escaped '?'" {
+	if not header :matches "x-bullshit" "33333\\?\\?\\??" {
+		test_fail "should have matched";
+	}
+
+	if header :matches "x-bullshit" "33333\\?\\?\\?" {
+		test_fail "should not have matched";
+	}
+}
+
+test "Escaped '?' following '*'" {
+	if not header :matches "x-bullshit" "33333*\\?\\??" {
+		test_fail "should have matched";
+	}
+
+}
+
+test "Escaped '?' directly following initial '*'" {
+	if not header :matches "X-Bullshit" "*\\?\\?\\?a" {
+		test_fail "should have matched";
+	}
+}
+
+test "Escaped '?' following initial '*'" {
+	if not header :matches "x-bullshit" "*3333\\?\\?\\?a" {
+		test_fail "should have matched";
+	}
+}
+
+test "Escaped '*' with active '*' at the end" {
+	if not header :matches "x-spam-score" "\\*\\*\\*\\*\\**" {
+		test_fail "should have matched";
+	}
+}
+
+test "All escaped '*'" {
+	if not header :matches "x-spam-score" "\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*" {
+		test_fail "should have matched";
+	}
+
+	if header :matches "x-spam-score" "\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*" {
+		test_fail "should not have matched";
+	}
+}
+
+test "Middle not escaped '*'" {
+	if not header :matches "x-spam-score" "\\*\\*\\***\\*\\*" {
+		test_fail "should have matched";
+	}
+}
+
+test "Escaped '*' alternating with '?'" {
+	if not header :matches "x-spam-score" "\\*?\\*?\\*?\\*?\\*?" {
+		test_fail "should have matched";
+	}
+
+	if header :matches "x-spam-score" "\\*?\\*?\\*?\\*?\\*??" {
+		test_fail "should not have matched";
+	}
+}
+
+test "All escaped" {
+	if header :matches "x-bullshit" "\\*3333\\?\\?\\?a" {
+		test_fail "should not have matched";
+	}
+
+
+	if header :matches "x-bullshit" "33333\\?\\?\\?aa" {
+		test_fail "should not have matched";
+	}
+
+	if header :matches "x-bullshit" "\\f3333\\?\\?\\?a" {
+		test_fail "should not have matched";
+	}
+}
+
+test "Put '*' directly before '?'" {
+	if header :matches "x-subject" "Log for *??????????? build of *" {
+		test_fail "should not have matched";
+	}
+
+	if not header :matches "x-subject" "Log for *?????????? build of *" {
+		test_fail "should have matched";
+	}
+
+	if not header :matches "x-subject" "Log for *? build of *" {
+		test_fail "should have matched";
+	}
+}
+
+test "Put '?' directly before '*'" {
+	if header :matches "x-subject" "Log for ???????????* build of *" {
+		test_fail "should not have matched";
+	}
+
+	if not header :matches "x-subject" "Log for ??????????* build of *" {
+		test_fail "should have matched";
+	}
+
+	if not header :matches "x-subject" "Log for ?* build of *" {
+		test_fail "should have matched";
+	}
+}
+
+test "Fixed beginning" {
+	if not header :matches "subject" "make your *" {
+		test_fail "should have matched";
+	}
+}
+
+test "Fixed end" {
+	if not header :matches "subject" "* very fast!!!" {
+		test_fail "should have matched";
+	}
+
+	if header :matches "subject" "* very fast!!" {
+		test_fail "should not have matched";
+	}
+}
+
+test "Fixed string" {
+	if not address :matches "to" "sirius@example.org" {
+		test_fail "should have matched";
+	}
+
+	if address :matches "to" "example.org" {
+		test_fail "should not have matched";
+	}
+
+	if address :matches "to" "sirius" {
+		test_fail "should not have matched";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/multiscript/basic.svtest
@@ -0,0 +1,91 @@
+require "vnd.dovecot.testsuite";
+
+test_set "message" text:
+From: stephan@example.org
+Message-ID: <frop33333333333333333@frutsens.example.nl>
+To: nico@frop.example.org
+Subject: Frop.
+
+Friep.
+.
+;
+
+test "Append" {
+	if not allof (
+		test_script_compile "fileinto-inbox.sieve",
+		test_script_run ){
+		test_fail "failed to compile and run first script";
+	}
+
+	if not allof (
+		test_script_compile "vacation.sieve",
+		test_script_run :append_result ) {
+		test_fail "failed to compile and run second script";
+	}
+
+	if not allof (
+		test_script_compile "notify.sieve",
+		test_script_run :append_result ) {
+		test_fail "failed to compile and run third script";
+	}
+
+	if not test_result_action :index 1 "store" {
+		test_fail "first action is not 'store'";
+	}
+
+	if not test_result_action :index 2 "vacation" {
+		test_fail "second action is not 'vacation'";
+	}
+
+	if not test_result_action :index 3 "notify" {
+		test_fail "third action is not 'notify'";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed";
+	}
+}
+
+test "Sequential Execute" {
+	if not allof (
+		test_script_compile "fileinto-inbox.sieve",
+		test_script_run ) {
+		test_fail "failed to compile and run first script";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed after first script";
+	}
+
+	if not allof (
+		test_script_compile "vacation.sieve",
+		test_script_run :append_result ) {
+		test_fail "failed to compile and run second script";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed after second script";
+	}
+
+	if not allof (
+		test_script_compile "notify.sieve",
+		test_script_run :append_result ) {
+		test_fail "failed to compile and run third script";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed after third script";
+	}
+
+	if not test_result_action :index 1 "store" {
+		test_fail "first action is not 'store'";
+	}
+
+	if not test_result_action :index 2 "vacation" {
+		test_fail "second action is not 'vacation'";
+	}
+
+	if not test_result_action :index 3 "notify" {
+		test_fail "third action is not 'notify'";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/multiscript/conflicts.svtest
@@ -0,0 +1,100 @@
+require "vnd.dovecot.testsuite";
+
+test_set "message" text:
+From: stephan@example.org
+Message-ID: <frop33333333333333333@nl.example.com>
+To: nico@frop.example.org
+Subject: Frop.
+
+Friep.
+.
+;
+
+test "Graceful Conflicts" {
+	if not allof (
+		test_script_compile "fileinto-inbox.sieve",
+		test_script_run ){
+		test_fail "failed to compile and run first script";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed after first script";
+	}
+
+	if not allof (
+		test_script_compile "reject-1.sieve",
+		test_script_run :append_result ) {
+		test_fail "failed to compile and run second script";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed after second script";
+	}
+
+	if not allof (
+		test_script_compile "reject-2.sieve",
+		test_script_run :append_result ) {
+		test_fail "failed to compile and run third script";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed after third script";
+	}
+
+	if not test_result_action :index 1 "store" {
+		test_result_print;
+		test_fail "first action is not 'store'";
+	}
+
+	if not test_result_action :index 2 "reject" {
+		test_result_print;
+		test_fail "first reject action not retained";
+	}
+
+	if test_result_action :index 3 "reject" {
+		test_result_print;
+		test_fail "second reject action not discarded";
+	}
+
+}
+
+test "Duplicates" {
+	if not allof (
+		test_script_compile "fileinto-inbox.sieve",
+		test_script_run ){
+		test_fail "failed to compile and run first script";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed after first script";
+	}
+
+	if not allof (
+		test_script_compile "fileinto-inbox.sieve",
+		test_script_run :append_result ) {
+		test_fail "failed to compile and run second script";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed after second script";
+	}
+
+	if not allof (
+		test_script_compile "keep.sieve",
+		test_script_run :append_result ) {
+		test_fail "failed to compile and run third script";
+	}
+
+	if not test_result_execute {
+		test_fail "result execute failed after third script";
+	}
+
+	if not test_result_action :index 1 "keep" {
+		test_fail "first action is not 'keep'";
+	}
+
+	if test_result_action :index 2 "store" {
+		test_fail "fileinto action not discarded";
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/multiscript/fileinto-frop.sieve
@@ -0,0 +1,3 @@
+require "fileinto";
+
+fileinto "frop";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/multiscript/fileinto-inbox.sieve
@@ -0,0 +1,4 @@
+require "fileinto";
+
+fileinto "INBOX";
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/multiscript/keep.sieve
@@ -0,0 +1 @@
+keep;
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/multiscript/notify.sieve
@@ -0,0 +1,3 @@
+require "enotify";
+
+notify "mailto:stephan@example.org";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/multiscript/reject-1.sieve
@@ -0,0 +1,3 @@
+require "reject";
+
+reject "Message is not wanted.";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/multiscript/reject-2.sieve
@@ -0,0 +1,3 @@
+require "reject";
+
+reject "Will not accept this nonsense.";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/multiscript/vacation.sieve
@@ -0,0 +1,3 @@
+require "vacation";
+
+vacation "I am not home";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/bin/addheader
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+echo "$1: $2"
+cat
+
+exit 0
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/bin/big
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+N="0123456701234567012345670123456701234567012345670123456701234567"
+N="$N$N$N$N$N$N$N$N$N$N$N$N$N$N$N$N"
+echo -n "$N$N"
+
+exit 0
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/bin/cat
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+cat
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/bin/cat-stdin
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+cat /dev/stdin
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/bin/crlf
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+tr -s '\r' '#'
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/bin/env
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+eval echo -n "\${$1}"
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/bin/frame
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+echo -n "FRAMED $1{ "
+cat
+echo -n " }"
+
+exit 0
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/bin/modify
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+echo "X-Frop: Extra header"
+cat
+echo
+echo "Extra body content!"
+
+exit 0
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/bin/program
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+cat > /dev/null
+
+exit 0
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/bin/replace
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+cat > /dev/null
+
+echo "From: hatseflat@example.com"
+echo "To: frutsel@example.org"
+echo "Subject: replacement message"
+echo
+echo "Replaced!"
+
+
+exit 0
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/bin/sleep10
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+sleep 10
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/bin/sleep2
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+sleep 2
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/bin/spamc
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+echo 'X-Spam-Status: Yes, score=66.5/5.0 tests=CONTAINS_LARGE_ROOSTER'
+cat
+
+exit 0
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/bin/stderr
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+echo "========================================" 1>&2
+echo "Test shell script successfully executed!" 1>&2
+echo 1>&2
+echo "Arguments: $1 $2" 1>&2
+echo 1>&2
+echo "Environment:" 1>&2
+env 1>&2
+echo 1>&2
+echo "Message:" 1>&2
+cat 1>&2
+echo "========================================" 1>&2
+echo 1>&2
+
+echo "Subject: frop!"
+echo "From: stephan@example.org"
+echo "To: tss@example.com"
+echo
+echo "Frop!"
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/errors.svtest
@@ -0,0 +1,32 @@
+require "vnd.dovecot.testsuite";
+
+require "relational";
+require "comparator-i;ascii-numeric";
+
+/*
+ * Invalid program names
+ */
+
+test "Invalid Program Names" {
+        if test_script_compile "errors/programname.sieve" {
+                test_fail "compile should have failed";
+        }
+
+        if not test_error :count "eq" :comparator "i;ascii-numeric" "8" {
+                test_fail "wrong number of errors reported";
+        }
+}
+
+/*
+ * Invalid arguments
+ */
+
+test "Invalid Arguments" {
+        if test_script_compile "errors/arguments.sieve" {
+                test_fail "compile should have failed";
+        }
+
+        if not test_error :count "eq" :comparator "i;ascii-numeric" "2" {
+                test_fail "wrong number of errors reported";
+        }
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/errors/arguments.sieve
@@ -0,0 +1,5 @@
+require "vnd.dovecot.pipe";
+
+pipe :args "aaaa
+	aaaa" "frop";
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/errors/programname.sieve
@@ -0,0 +1,25 @@
+require "variables";
+require "encoded-character";
+require "vnd.dovecot.pipe";
+
+# Slash
+pipe "../frop";
+
+# More slashes
+pipe "../../james/sieve/vacation";
+
+# 0000-001F; [CONTROL CHARACTERS]
+pipe "idiotic${unicode: 001a}";
+
+# 007F; DELETE
+pipe "idiotic${unicode: 007f}";
+
+# 0080-009F; [CONTROL CHARACTERS]
+pipe "idiotic${unicode: 0085}";
+
+# 2028; LINE SEPARATOR
+pipe "idiotic${unicode: 2028}";
+
+# 2029; PARAGRAPH SEPARATOR
+pipe "idiotic${unicode: 2029}";
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/execute/command.svtest
@@ -0,0 +1,27 @@
+require "vnd.dovecot.testsuite";
+require "vnd.dovecot.execute";
+require "variables";
+
+test_config_set "sieve_execute_bin_dir" "${tst.path}/../bin";
+test_config_reload :extension "vnd.dovecot.execute";
+
+test "Basic" {
+	execute "program";
+}
+
+test "Input message" {
+	execute :pipe "program";
+}
+
+test "Input string" {
+	execute :input "DATA" "program";
+}
+
+test "Input variable" {
+	set "DATA" "DATA";
+	execute :input "${DATA}" "program";
+}
+
+test "Output variable" {
+	execute :output "DATA" "program";
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/execute/errors.svtest
@@ -0,0 +1,53 @@
+require "vnd.dovecot.testsuite";
+
+require "relational";
+require "comparator-i;ascii-numeric";
+
+test_config_set "sieve_execute_bin_dir" "${tst.path}/../bin";
+test_config_reload :extension "vnd.dovecot.execute";
+
+/*
+ * Command syntax
+ */
+
+test "Command syntax" {
+        if test_script_compile "errors/syntax.sieve" {
+                test_fail "compile should have failed";
+        }
+
+        if not test_error :count "eq" :comparator "i;ascii-numeric" "13" {
+                test_fail "wrong number of errors reported";
+        }
+}
+
+/*
+ * Variables
+ */
+
+test "Variables" {
+        if test_script_compile "errors/variables.sieve" {
+                test_fail "compile should have failed";
+        }
+
+        if not test_error :count "eq" :comparator "i;ascii-numeric" "2" {
+                test_fail "wrong number of errors reported";
+        }
+}
+
+/*
+ * Unknown program
+ */
+
+test "Unknown program" {
+        if not test_script_compile "errors/unknown-program.sieve" {
+                test_fail "compile should have succeeded";
+        }
+
+	if test_script_run {
+                test_fail "execution should have failed";
+	}
+
+        if not test_error :count "eq" :comparator "i;ascii-numeric" "1" {
+                test_fail "wrong number of errors reported";
+        }
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/execute/errors/syntax.sieve
@@ -0,0 +1,38 @@
+require "vnd.dovecot.execute";
+
+# 1: error: no arguments
+execute;
+
+# 2: error: numeric argument
+execute 1;
+
+# 3: error: tag argument
+execute :frop;
+
+# 4: error: numeric second argument
+execute "sdfd" 1;
+
+# 5: error: stringlist first argument
+execute ["sdfd","werwe"] "sdfs";
+
+# 6: error: too many arguments
+execute "sdfs" "sdfd" "werwe";
+
+# 7: error: inappropriate :copy argument
+execute :copy "234234" ["324234", "23423"];
+
+# 8: error: invalid :input argument; missing parameter
+execute :input "frop";
+
+# 9: error: invalid :input argument; invalid parameter
+execute :input 1 "frop";
+
+# 10: error: invalid :input argument; invalid parameter
+execute :input ["23423","21342"] "frop";
+
+# 11: error: invalid :input argument; invalid parameter
+execute :input :friep "frop";
+
+# 12: error: :output not allowed without variables extension
+execute :output "${frop}" "frop";
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/execute/errors/unknown-program.sieve
@@ -0,0 +1,3 @@
+require "vnd.dovecot.execute";
+
+execute "unknown";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/execute/errors/variables.sieve
@@ -0,0 +1,7 @@
+require "vnd.dovecot.execute";
+require "variables";
+
+# 1: invalid variable name
+execute :output "wqwe-aeqwe" "frop";
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/execute/execute.svtest
@@ -0,0 +1,177 @@
+require "vnd.dovecot.testsuite";
+require "vnd.dovecot.execute";
+require "vnd.dovecot.debug";
+require "variables";
+require "relational";
+require "environment";
+require "encoded-character";
+
+test_set "message" text:
+From: stephan@example.com
+To: pipe@example.net
+Subject: Frop!
+
+Frop!
+.
+;
+
+test_config_set "sieve_execute_bin_dir" "${tst.path}/../bin";
+test_config_reload :extension "vnd.dovecot.execute";
+test_result_reset;
+
+test "Execute - bare" {
+	execute "program";
+}
+
+test_result_reset;
+test "Execute - i/-" {
+	execute :input "FROP" "frame";
+}
+
+test_result_reset;
+test "Execute - -/o" {
+	execute :output "out" "frame";
+
+	if not string "${out}" "FRAMED {  }" {
+		test_fail "wrong string returned: ${out}";
+	}
+}
+
+test_result_reset;
+test "Execute - i/o" {
+	execute :input "FROP" :output "out" "frame";
+
+	if not string "${out}" "FRAMED { FROP }" {
+		test_fail "wrong string returned: ${out}";
+	}
+}
+
+test_result_reset;
+test "Execute - i/o and arguments" {
+	execute :input "FROP" :output "out" "frame" ["FRIEP "];
+
+	if not string "${out}" "FRAMED FRIEP { FROP }" {
+		test_fail "wrong string returned: ${out}";
+	}
+}
+
+test_result_reset;
+test "Execute - pipe" {
+	execute :pipe :output "msg" "cat";
+
+	if not string :contains "${msg}" "Subject: Frop!" {
+		test_fail "wrong string returned: ${out}";
+	}
+}
+
+test_result_reset;
+test "Execute - pipe /dev/stdin" {
+	execute :pipe :output "msg" "cat-stdin";
+
+	if not string :contains "${msg}" "Subject: Frop!" {
+		test_fail "wrong string returned: ${out}";
+	}
+}
+
+test_result_reset;
+test "Execute - env" {
+	test_set "envelope.from" "stephan@sub.example.com";
+	test_set "envelope.to" "stephan@sub.example.net";
+	test_set "envelope.orig_to" "all@sub.example.net";
+
+	execute :output "out" "env" "SENDER";
+	if not string :is "${out}" "stephan@sub.example.com" {
+		test_fail "wrong SENDER env returned: '${out}'";
+	}
+
+	execute :output "out" "env" "RECIPIENT";
+	if not string :is "${out}" "stephan@sub.example.net" {
+		test_fail "wrong RECIPIENT env returned: '${out}'";
+	}
+
+	execute :output "out" "env" "ORIG_RECIPIENT";
+	if not string :is "${out}" "all@sub.example.net" {
+		test_fail "wrong ORIG_RECIPIENT env returned: '${out}'";
+	}
+
+	execute :output "out" "env" "HOST";
+	if not environment :is "host" "${out}" {
+		test_fail "wrong HOST env returned: '${out}'";
+	}
+
+	execute :output "out" "env" "HOME";
+	if string :count "eq" "${out}" "0" {
+		test_fail "empty HOME env returned";
+	}
+
+	execute :output "out" "env" "USER";
+	if string :count "eq" "${out}" "0" {
+		test_fail "empty USER env returned";
+	}
+}
+
+test_result_reset;
+test "Execute - used as test" {
+	if execute :pipe :output "msg" "dog" {
+		test_fail "execute action indicated success with invalid program";
+	}
+
+	if not execute :pipe :output "msg" "cat" {
+		test_fail "execute action indicated failure with valid program";
+	}
+
+	if not string :contains "${msg}" "Subject: Frop!" {
+		test_fail "wrong string returned: ${out}";
+	}
+}
+	
+test_config_set "sieve_execute_input_eol" "crlf";
+test_config_reload :extension "vnd.dovecot.execute";
+test_result_reset;
+set "out" "";
+
+test "Execute - CRLF" {
+	execute
+		:input "FROP${hex:0A}FRIEP${hex:0a}"
+		:output "out"
+		"crlf";
+
+	if not string "${out}" "FROP#${hex:0A}FRIEP#${hex:0a}" {
+		test_fail "wrong string returned: '${out}'";
+	}
+}
+
+test_config_set "sieve_execute_input_eol" "lf";
+test_config_reload :extension "vnd.dovecot.execute";
+test_result_reset;
+set "out" "";
+
+test "Execute - LF" {
+	execute
+		:input "FROP${hex:0D 0A}FRIEP${hex:0d 0a}"
+		:output "out"
+		"crlf";
+
+	if not string "${out}" "FROP${hex:0A}FRIEP${hex:0a}" {
+		test_fail "wrong string returned: '${out}'";
+	}
+}
+
+set "D" "0123456701234567012345670123456701234567012345670123456701234567";
+set "D" "${D}${D}${D}${D}${D}${D}${D}${D}${D}${D}${D}${D}${D}${D}${D}${D}";
+set "data" "${D}${D}";
+
+test_config_set "sieve_execute_input_eol" "crlf";
+test_config_reload :extension "vnd.dovecot.execute";
+test_result_reset;
+set "out" "";
+
+test "Execute - big" {
+	execute
+		:output "out"
+		"big";
+
+	if not string "${out}" "${data}" {
+		test_fail "wrong string returned: '${out}'";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/filter/command.svtest
@@ -0,0 +1,10 @@
+require "vnd.dovecot.testsuite";
+require "vnd.dovecot.filter";
+require "variables";
+
+test_config_set "sieve_filter_bin_dir" "${tst.path}/../bin";
+test_config_reload :extension "vnd.dovecot.filter";
+
+test "Basic" {
+	filter "program";
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/filter/errors.svtest
@@ -0,0 +1,39 @@
+require "vnd.dovecot.testsuite";
+
+require "relational";
+require "comparator-i;ascii-numeric";
+
+/*
+ * Command syntax
+ */
+
+test_config_set "sieve_filter_bin_dir" "${tst.path}/../bin";
+test_config_reload :extension "vnd.dovecot.filter";
+
+test "Command syntax" {
+        if test_script_compile "errors/syntax.sieve" {
+                test_fail "compile should have failed";
+        }
+
+        if not test_error :count "eq" :comparator "i;ascii-numeric" "8" {
+                test_fail "wrong number of errors reported";
+        }
+}
+
+/*
+ * Unknown program
+ */
+
+test "Unknown program" {
+        if not test_script_compile "errors/unknown-program.sieve" {
+                test_fail "compile should have succeeded";
+        }
+
+        if test_script_run {
+                test_fail "execution should have failed";
+        }
+
+        if not test_error :count "eq" :comparator "i;ascii-numeric" "1" {
+                test_fail "wrong number of errors reported";
+        }
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/filter/errors/syntax.sieve
@@ -0,0 +1,22 @@
+require "vnd.dovecot.filter";
+
+# 1: error: no arguments
+filter;
+
+# 2: error: numeric argument
+filter 1;
+
+# 3: error: tag argument
+filter :frop;
+
+# 4: error: numeric second argument
+filter "sdfd" 1;
+
+# 5: error: stringlist first argument
+filter ["sdfd","werwe"] "sdfs";
+
+# 6: error: too many arguments
+filter "sdfd" "werwe" "sdfs";
+
+# 7: error: inappropriate :copy argument
+filter :try :copy "234234" ["324234", "23423"];
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/filter/errors/unknown-program.sieve
@@ -0,0 +1,3 @@
+require "vnd.dovecot.filter";
+
+filter "unknown";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/filter/execute.svtest
@@ -0,0 +1,213 @@
+require "vnd.dovecot.testsuite";
+require "vnd.dovecot.filter";
+require "vnd.dovecot.debug";
+require "variables";
+require "editheader";
+require "spamtest";
+require "body";
+require "fileinto";
+require "mailbox";
+
+test_set "message" text:
+From: stephan@example.com
+To: pipe@example.net
+Subject: Frop!
+
+Frop!
+.
+;
+
+test_config_set "sieve_filter_bin_dir" "${tst.path}/../bin";
+test_config_reload :extension "vnd.dovecot.filter";
+test_result_reset;
+
+test_result_reset;
+test "Replace" {
+	if header :contains "subject" "replacement" {
+		test_fail "message already replaced";
+	}
+
+	filter "replace";
+
+	if not header :contains "subject" "replacement" {
+		test_fail "message not replaced";
+	}
+}
+
+test_result_reset;
+test "Used as test" {
+	if filter "nonsense" {
+		test_fail "filter action indicated success with invalid program";
+	}
+
+	if not filter "replace" {
+		test_fail "filter action indicated failure with valid program";
+	}
+
+	if not header :contains "subject" "replacement" {
+		test_fail "message not replaced; filter not actually executed";
+	}
+}	
+
+test_result_reset;
+test "Modify" {
+	if anyof (
+		body :contains "extra",
+		exists "x-frop") {
+		test_fail "message already modified";
+	}
+
+	if not header "subject" "Frop!" {
+		test_fail "message is wrong";
+	}
+
+	filter "modify";
+
+	if not header "subject" "Frop!" {
+		test_fail "message replaced erroneously";
+	}
+
+	if not header :contains "x-frop" "extra" {
+		test_fail "message header not modified";
+	}
+
+	if not body :contains "Extra" {
+		test_fail "message body not modified";
+	}
+}
+
+test_result_reset;
+test "Editheader" {
+	if anyof ( exists "X-A", exists "X-B", exists "X-C", exists "X-D",
+		exists "X-E") {
+		test_fail "message already modified";
+	}
+
+	addheader "X-A" "1";
+	if not header "X-A" "1" {
+		test_fail "X-A header missing";
+	}
+
+	fileinto :create "A";
+
+	filter "addheader" ["X-B", "2"];
+	if not header "X-B" "2" {
+		test_fail "X-B header missing";
+	}
+
+	fileinto :create "B";
+
+	addheader "X-C" "3";
+	if not header "X-C" "3" {
+		test_fail "X-C header missing";
+	}
+
+	fileinto :create "C";
+
+	filter "addheader" ["X-D", "4"];
+	if not header "X-D" "4" {
+		test_fail "X-D header missing";
+	}
+
+	fileinto :create "D";
+
+	addheader "X-E" "5";
+	if not header "X-E" "5" {
+		test_fail "X-E header missing";
+	}
+
+	fileinto :create "E";
+
+	if not test_result_execute {
+		test_fail "failed to execute result";
+	}
+
+	test_message :folder "A" 0;
+
+	if not header "X-A" "1" {
+		test_fail "X-A header missing";
+	}
+	if anyof (
+		header "X-B" "2", header "X-C" "3",
+		header "X-D" "4", header "X-E" "5") {
+		test_fail "X-B, X-C, X-D or X-E header found";
+	}
+
+	test_message :folder "B" 0;
+
+	if not header "X-B" "2" {
+		test_fail "X-B header missing";
+	}
+	if anyof (
+		header "X-C" "3", header "X-D" "4", header "X-E" "5") {
+		test_fail "X-C, X-D or X-E header found";
+	}
+
+	test_message :folder "C" 0;
+
+	if not header "X-C" "3" {
+		test_fail "X-C header missing";
+	}
+	if anyof (header "X-D" "4", header "X-E" "5") {
+		test_fail "X-D or X-E header found";
+	}
+
+	test_message :folder "D" 0;
+
+	if not header "X-D" "4" {
+		test_fail "X-D header missing";
+	}
+	if anyof (header "X-E" "5") {
+		test_fail "X-E header found";
+	}
+
+	test_message :folder "E" 0;
+
+	if not header "X-A" "1" {
+		test_fail "X-A header missing in final message";
+	}
+	if not header "X-B" "2" {
+		test_fail "X-B header missing in final message";
+	}
+	if not header "X-C" "3" {
+		test_fail "X-C header missing in final message";
+	}
+	if not header "X-D" "4" {
+		test_fail "X-D header missing in final message";
+	}
+	if not header "X-E" "5" {
+		test_fail "X-E header missing in final message";
+	}
+}
+
+test_config_set "sieve_spamtest_status_header"
+	"X-Spam-Status: [^,]*, score=(-?[[:digit:]]+\\.[[:digit:]]).*";
+test_config_set "sieve_spamtest_max_value" "10";
+test_config_set "sieve_spamtest_status_type" "score";
+test_config_reload :extension "spamtest";
+
+test_result_reset;
+test "Spamtest" {
+	if exists "x-spam-status" {
+		test_fail "message already modified";
+	}
+
+	if not header "subject" "Frop!" {
+		test_fail "message is wrong";
+	}
+
+	filter "spamc";
+
+	if not exists "x-spam-status" {
+		test_fail "x-spam-score header not added";
+	}
+
+	if spamtest :is "0" {
+		test_fail "spamtest not configured or test failed";
+	}
+
+	if not spamtest :is "10" {
+		test_fail "spamtest yields incorrect value";
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/pipe/command.svtest
@@ -0,0 +1,10 @@
+require "vnd.dovecot.testsuite";
+require "vnd.dovecot.pipe";
+
+test_config_set "sieve_pipe_bin_dir" "${tst.path}/../bin";
+test_config_reload :extension "vnd.dovecot.pipe";
+
+test "Basic" {
+	pipe "program";
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/pipe/errors.svtest
@@ -0,0 +1,95 @@
+require "vnd.dovecot.testsuite";
+require "variables";
+
+require "relational";
+require "comparator-i;ascii-numeric";
+
+/*
+ * Command syntax
+ */
+
+test "Command syntax" {
+        if test_script_compile "errors/syntax.sieve" {
+                test_fail "compile should have failed";
+        }
+
+        if not test_error :count "eq" :comparator "i;ascii-numeric" "8" {
+                test_fail "wrong number of errors reported";
+        }
+}
+
+/* Unknown program */
+
+test_set "message" text:
+From: stephan@example.com
+To: pipe@example.net
+Subject: Frop!
+
+Frop!
+.
+;
+
+test_config_set "sieve_pipe_bin_dir" "${tst.path}/../bin";
+test_config_reload :extension "vnd.dovecot.pipe";
+test_result_reset;
+
+test "Unknown program" {
+	if not test_script_compile "errors/unknown-program.sieve" {
+		test_fail "compile failed";
+	}
+
+	if not test_script_run {
+		test_fail "execute failed";
+	}
+
+	if test_result_execute {
+		test_fail "pipe should have failed";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "1" {
+		test_fail "wrong number of errors reported";
+	}
+
+	if not test_error :index 1 :contains "failed to pipe" {
+		test_fail "wrong error reported";
+	}
+}
+
+/* Timeout */
+
+test_set "message" text:
+From: stephan@example.com
+To: pipe@example.net
+Subject: Frop!
+
+Frop!
+.
+;
+
+test_config_set "sieve_pipe_bin_dir" "${tst.path}/../bin";
+test_config_set "sieve_pipe_exec_timeout" "1s";
+test_config_reload :extension "vnd.dovecot.pipe";
+test_result_reset;
+
+test "Timeout" {
+	if not test_script_compile "errors/timeout.sieve" {
+		test_fail "compile failed";
+	}
+
+	if not test_script_run {
+		test_fail "execute failed";
+	}
+
+	if test_result_execute {
+		test_fail "pipe should have timed out";
+	}
+
+	if not test_error :count "eq" :comparator "i;ascii-numeric" "1" {
+  	test_fail "wrong number of errors reported";
+  }    
+
+	if not test_error :index 1 :contains "failed to pipe" {
+  	test_fail "wrong error reported";
+  }    
+
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/pipe/errors/syntax.sieve
@@ -0,0 +1,22 @@
+require "vnd.dovecot.pipe";
+
+# 1: error: no arguments
+pipe;
+
+# 2: error: numeric argument
+pipe 1;
+
+# 3: error: tag argument
+pipe :frop;
+
+# 4: error: numeric second argument
+pipe "sdfd" 1;
+
+# 5: error: stringlist first argument
+pipe ["sdfd","werwe"] "sdfs";
+
+# 6: error: too many arguments
+pipe "sdfd" "werwe" "sdfs";
+
+# 7: error: inappropriate :copy argument
+pipe :try :copy "234234" ["324234", "23423"];
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/pipe/errors/timeout.sieve
@@ -0,0 +1,3 @@
+require "vnd.dovecot.pipe";
+
+pipe "sleep10";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/pipe/errors/unknown-program.sieve
@@ -0,0 +1,3 @@
+require "vnd.dovecot.pipe";
+
+pipe "unknown";
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/plugins/extprograms/pipe/execute.svtest
@@ -0,0 +1,56 @@
+require "vnd.dovecot.testsuite";
+require "vnd.dovecot.pipe";
+require "vnd.dovecot.debug";
+require "variables";
+
+test_set "message" text:
+From: stephan@example.com
+To: pipe@example.net
+Subject: Frop!
+
+Frop!
+.
+;
+
+/* Basic pipe */
+
+test_config_set "sieve_pipe_bin_dir" "${tst.path}/../bin";
+test_config_reload :extension "vnd.dovecot.pipe";
+test_result_reset;
+
+test "Pipe" {
+	pipe "stderr" ["ONE", "TWO"];
+	
+	if not test_result_execute {
+		test_fail "failed to pipe message to script";
+	}	
+}
+
+/* Timeout */
+
+test_config_set "sieve_pipe_bin_dir" "${tst.path}/../bin";
+test_config_set "sieve_pipe_exec_timeout" "3s";
+test_config_reload :extension "vnd.dovecot.pipe";
+test_result_reset;
+
+test "Timeout 3s" {
+	pipe "sleep2";
+	
+	if not test_result_execute {
+		test_fail "failed to pipe message to script";
+	}	
+}
+
+test_result_reset;
+test_config_set "sieve_pipe_bin_dir" "${tst.path}/../bin";
+test_config_set "sieve_pipe_exec_timeout" "0";
+test_config_reload :extension "vnd.dovecot.pipe";
+test_result_reset;
+
+test "Timeout infinite" {
+	pipe "sleep2";
+	
+	if not test_result_execute {
+		test_fail "failed to pipe message to script";
+	}	
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/test-address.svtest
@@ -0,0 +1,434 @@
+require "vnd.dovecot.testsuite";
+
+/*
+ * ## RFC 5228, Section 5.1. Test address (page 26) ##
+ */
+
+/*
+ * TEST: Basic functionionality
+ */
+
+/* "The "address" test matches Internet addresses in structured headers
+ *  that contain addresses.  It returns true if any header contains any
+ *  key in the specified part of the address, as modified by the
+ *  comparator and the match keyword.  Whether there are other addresses
+ *  present in the header doesn't affect this test; this test does not
+ *  provide any way to determine whether an address is the only address
+ *  in a header.
+ *
+ *  Like envelope and header, this test returns true if any combination
+ *  of the header-list and key-list arguments match and returns false
+ *  otherwise.
+ * "
+ */
+
+test_set "message" text:
+From: stephan@example.com
+To: nico@nl.example.com, harry@de.example.com
+cc: Timo <tss(no spam)@fi.iki>
+Subject: Frobnitzm
+
+Test.
+.
+;
+
+test "Basic functionality" {
+	/* Must match */
+	if not address :contains ["to", "from"] "harry" {
+		test_fail "failed to match address (1)";
+	}
+
+	if not address :contains ["to", "from"] "de.example" {
+		test_fail "failed to match address (2)";
+	}
+
+	if not address :matches "to" "*@*.example.com" {
+		test_fail "failed to match address (3)";
+	}
+
+	if not address :is "to" "harry@de.example.com" {
+		test_fail "failed to match address (4)";
+	}
+
+	/* Must not match */
+	if address :is ["to", "from"] "nonsense@example.com" {
+		test_fail "matches erroneous address";
+	}
+
+	/* Match first key */
+	if not address :contains ["to"] ["nico", "fred", "henk"] {
+		test_fail "failed to match first key";
+	}
+
+	/* Match second key */
+	if not address :contains ["to"] ["fred", "nico", "henk"] {
+		test_fail "failed to match second key";
+	}
+
+	/* Match last key */
+	if not address :contains ["to"] ["fred", "henk", "nico"] {
+		test_fail "failed to match last key";
+	}
+
+	/* First header */
+	if not address :contains ["to", "from"] ["fred", "nico", "henk"] {
+		test_fail "failed to match first header";
+	}
+
+	/* Second header */
+	if not address :contains ["from", "to"] ["fred", "nico", "henk"] {
+		test_fail "failed to match second header";
+	}
+
+	/* Comment */
+	if not address :is "cc" "tss@fi.iki" {
+		test_fail "failed to ignore comment in address";
+	}
+}
+
+/*
+ * TEST: Case-sensitivity
+ */
+
+/* "Internet email addresses [RFC 2822] have the somewhat awkward characteristic
+ *  that the local-part to the left of the at-sign is considered case sensitive,
+ *  and the domain-part to the right of the at-sign is case insensitive. The
+ *  "address" command does not deal with this itself, but provides the
+ *  ADDRESS-PART argument for allowing users to deal with it.
+ * "
+ */
+
+test_set "message" text:
+From: stephan@example.com
+To: Nico@nl.example.com, harry@DE.EXAMPLE.COM
+Subject: Case-sensitivity
+
+Test.
+.
+;
+
+
+test "Case-sensitivity" {
+	/* Default: i;ascii-casemap */
+
+	if not address :is ["to", "from"] "nico@nl.example.com" {
+		test_fail "address comparator is i;octet by default (1)";
+	}
+
+	if not address :is ["to", "from"] "harry@de.example.com" {
+		test_fail "address comparator is i;octet by default (2)";
+	}
+
+	if not address :is ["to", "from"] "STEPHAN@example.com" {
+		test_fail "address comparator is i;octet by default (3)";
+	}
+
+	if not address :is :localpart ["to"] "nico" {
+		test_fail "address comparator is i;octet by default (4)";
+	}
+
+	/* Match case-sensitively */
+
+	if not address :is :comparator "i;octet" ["to"] "Nico@nl.example.com" {
+		test_fail "failed to match case-sensitive address (1)";
+	}
+
+	if not address :is :comparator "i;octet" ["to"] "harry@DE.EXAMPLE.COM" {
+		test_fail "failed to match case-sensitive address (2)";
+	}
+
+	if address :is :comparator "i;octet" ["to"] "harry@de.example.com" {
+		test_fail "failed to notice case difference in address with i;octet (1)";
+	}
+
+	if address :is :comparator "i;octet" ["from"] "STEPHAN@example.com" {
+		test_fail "failed to notice case difference in address with i;octet (2)";
+	}
+
+	if not address :is :localpart :comparator "i;octet" ["to"] "Nico" {
+		test_fail "failed to match case-sensitive localpart";
+	}
+
+	if address :is :localpart :comparator "i;octet" ["to"] "nico" {
+		test_fail "failed to notice case difference in local_part with i;octet";
+	}
+
+	if not address :is :domain :comparator "i;octet" ["to"] "DE.EXAMPLE.COM" {
+		test_fail "failed to match case-sensitive localpart";
+	}
+
+	if address :is :domain :comparator "i;octet" ["to"] "de.example.com" {
+		test_fail "failed to notice case difference in domain with i;octet";
+	}
+}
+
+/*
+ * TEST: Phrase part, comments and group names
+ */
+
+/* "The address primitive never acts on the phrase part of an email
+ *  address or on comments within that address.  It also never acts on
+ *  group names, ...
+ * "
+ */
+
+test_set "message" text:
+From: Stephan Bosch <stephan(the author)@example.com>
+To: Nico Thalens <nico@nl.example.com>, Harry Becker <harry@de.example.com>
+cc: tukkers: henk@tukkerland.ex, theo@tukkerland.ex, frits@tukkerland.ex;
+Subject: Frobnitzm
+
+Test.
+.
+;
+
+test "Phrase part, comments and group names" {
+	if address :contains :all :comparator "i;ascii-casemap"
+		["to","from"] ["Bosch", "Thalens", "Becker"] {
+		test_fail "matched phrase part";
+	}
+
+	if address :contains :all :comparator "i;ascii-casemap" "from" "author" {
+		test_fail "matched comment";
+	}
+
+
+	if address :contains :all :comparator "i;ascii-casemap" ["cc"] ["tukkers"] {
+		test_fail "matched group name";
+	}
+}
+
+
+/*
+ * TEST: Group addresses
+ */
+
+/* "... although it does act on the addresses within the group
+ *  construct.
+ * "
+ */
+
+test_set "message" text:
+From: stephan@friep.frop
+To: undisclosed-recipients:;
+cc: tukkers: henk@tukkerland.ex, theo@tukkerland.ex, frits@tukkerland.ex;
+Subject: Invalid addresses
+
+Test.
+.
+;
+
+test "Group addresses" {
+	if not address :is :domain ["cc"] ["tukkerland.ex"] {
+		test_fail "failed to match group address (1)";
+	}
+
+	if not address :is :localpart ["cc"] ["henk"] {
+		test_fail "failed to match group address (2)";
+	}
+
+	if not address :is :localpart ["cc"] ["theo"] {
+		test_fail "failed to match group address (3)";
+	}
+
+	if not address :is :localpart ["cc"] ["frits"] {
+		test_fail "failed to match group address (4)";
+	}
+}
+
+/*
+ * TEST: Address headers
+ */
+
+/* "Implementations MUST restrict the address test to headers that
+ *  contain addresses, but MUST include at least From, To, Cc, Bcc,
+ *  Sender, Resent-From, and Resent-To, and it SHOULD include any other
+ *  header that utilizes an "address-list" structured header body.
+ * "
+ */
+
+test_set "message" text:
+From: stephan@friep.frop
+To: henk@tukkerland.ex
+CC: ivo@boer.ex
+Bcc: joop@hooibaal.ex
+Sender: s.bosch@friep.frop
+Resent-From: ivo@boer.ex
+Resent-To: idioot@dombo.ex
+Subject: Berichtje
+
+Test.
+.
+;
+
+
+test "Address headers" {
+	if not address "from" "stephan@friep.frop" {
+		test_fail "from header not recognized";
+	}
+
+	if not address "to" "henk@tukkerland.ex" {
+		test_fail "to header not recognized";
+	}
+
+	if not address "cc" "ivo@boer.ex" {
+		test_fail "cc header not recognized";
+	}
+
+	if not address "bcc" "joop@hooibaal.ex" {
+		test_fail "bcc header not recognized";
+	}
+
+	if not address "sender" "s.bosch@friep.frop" {
+		test_fail "sender header not recognized";
+	}
+
+	if not address "resent-from" "ivo@boer.ex" {
+		test_fail "resent-from header not recognized";
+	}
+
+	if not address "resent-to" "idioot@dombo.ex" {
+		test_fail "resent-to header not recognized";
+	}
+}
+
+/* ## RFC 5228, Section 2.7.4. Comparisons against Addresses (page 16) ## */
+
+/*
+ * TEST: Invalid addresses
+ */
+
+/*
+ * "If an address is not syntactically valid, then it will not be matched
+ *  by tests specifying ":localpart" or ":domain".
+ * "
+ */
+
+test_set "message" text:
+From: stephan@
+To: @example.org
+Cc: nonsense
+Resent-To:
+Bcc: nico@frop.example.com, @example.org
+Resent-Cc:<jürgen@example.com>
+Subject: Invalid addresses
+
+Test.
+.
+;
+
+test "Invalid addresses" {
+	if address :localpart "from" "stephan" {
+		test_fail ":localpart matched invalid address";
+	}
+
+	if address :localpart "resent-cc" "jürgen" {
+		test_fail ":localpart matched invalid UTF-8 address";
+	}
+
+	if address :domain "to" "example.org" {
+		test_fail ":domain matched invalid address";
+	}
+
+	if address :domain "resent-cc" "example.com" {
+		test_fail ":domain matched invalid UTF-8 address";
+	}
+
+	if not address :is :all "resent-to" "" {
+		test_fail ":all failed to match empty address";
+	}
+
+	if not address :is :all "cc" "nonsense" {
+		test_fail ":all failed to match invalid address";
+	}
+
+	if not address :is :all "resent-cc" "<jürgen@example.com>" {
+		test_fail ":all failed to match invalid UTF-8 address";
+	}
+
+	if address :is :localpart "bcc" "" {
+		test_fail ":localpart matched invalid address";
+	}
+
+	if address :is :domain "cc" "example.org" {
+		test_fail ":domain matched invalid address";
+	}
+}
+
+/*
+ * TEST: Default address part
+ */
+
+/* "If an optional address-part is omitted, the default is ":all".
+ * "
+ */
+
+test_set "message" text:
+From: stephan@example.com
+To: nico@nl.example.com, harry@de.example.com
+Subject: Frobnitzm
+
+Test.
+.
+;
+
+test "Default address part" {
+	if not address :is :comparator "i;ascii-casemap" "from" "stephan@example.com"
+		{
+		test_fail "invalid default address part (1)";
+	}
+
+	if not address :is :comparator "i;ascii-casemap" "to"
+		["harry@de.example.com"] {
+		test_fail "invalid default address part (2)";
+	}
+}
+
+/*
+ * TEST: Mime encoding of '@' in display name
+ */
+
+test_set "message" text:
+From: "Frop <frop@example.org>"
+To: =?UTF-8?B?RnJpZXBAZnJvcA0K?=
+	<friep@example.com>
+Subject: Test
+
+Frop!
+.
+;
+
+
+test "Mime encoding of '@' in display name" {
+        # Relevant sieve rule:
+
+        if not address :is "To" 
+			["friep@example.com"] {
+                test_fail "Invalid address extracted";
+        }
+}
+
+/*
+ * TEST: Erroneous mime encoding
+ */
+
+test_set "message" text:
+From: "William Wallace <william@scotsmen.ex>"
+To: "=?UTF-8?B?IkR1bWIgTWFpbGVyIg==?="
+	<horde@lists.scotsmen.ex>
+Subject: Test
+
+Frop!
+.
+;
+
+
+test "Erroneous mime encoding" {
+        # Relevant sieve rule:
+
+        if not address :is ["To","CC"] ["horde@lists.scotsmen.ex","archers@lists.scotsmen.ex"] {
+                test_fail "Failed to match improperly encoded address headers";
+        }
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/test-allof.svtest
@@ -0,0 +1,446 @@
+require "vnd.dovecot.testsuite";
+
+/*
+ * ## RFC 5228, Section 5.2. Test allof (page 27) ##
+ */
+
+/* "The "allof" test performs a logical AND on the tests supplied to it.
+ *
+ *  Example:  allof (false, false)  =>   false
+ *            allof (false, true)   =>   false
+ *            allof (true,  true)   =>   true
+ *
+ *  The allof test takes as its argument a test-list.
+ * "
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: test@dovecot.example.net
+cc: stephan@idiot.ex
+Subject: Test
+
+Test!
+.
+;
+
+/*
+ * TEST: Basic functionality: static
+ */
+
+test "Basic functionality: static" {
+	if allof ( true ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong single outcome: false";
+	}
+
+	if allof ( false ) {
+		test_fail "chose wrong single outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( true, true, true ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong all-true outcome: false";
+	}
+
+	if allof ( false, false, false ) {
+		test_fail "chose wrong all-false outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( true, false, false ) {
+		test_fail "chose wrong first-true outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( false, true, false ) {
+		test_fail "chose wrong second-true outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( false, false, true ) {
+		test_fail "chose wrong last-true outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( false, true, true ) {
+		test_fail "chose wrong first-false outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( true, false, true ) {
+		test_fail "chose wrong second-false outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( true, true, false ) {
+		test_fail "chose wrong last-false outcome: true";
+	} else {
+		/* Correct */
+	}
+}
+
+/*
+ * TEST: Basic functionality: dynamic
+ */
+
+test "Basic functionality: dynamic" {
+	if allof ( exists "from" ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong single outcome: false";
+	}
+
+	if allof ( exists "friep" ) {
+		test_fail "chose wrong single outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( exists "from", exists "to", exists "cc" ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong all-true outcome: false";
+	}
+
+	if allof ( exists "friep", exists "frop", exists "frml" ) {
+		test_fail "chose wrong all-false outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( exists "to", exists "frop", exists "frml" ) {
+		test_fail "chose wrong first-true outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( exists "friep", exists "from", exists "frml" ) {
+		test_fail "chose wrong second-true outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( exists "friep", exists "frop", exists "cc" ) {
+		test_fail "chose wrong last-true outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( exists "friep", exists "from", exists "cc" ) {
+		test_fail "chose wrong first-false outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( exists "to", exists "frop", exists "cc" ) {
+		test_fail "chose wrong second-false outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( exists "to", exists "from", exists "frml" ) {
+		test_fail "chose wrong last-false outcome: true";
+	} else {
+		/* Correct */
+	}
+}
+
+/*
+ * TEST: Basic functionality: static/dynamic
+ */
+
+test "Basic functionality: static/dynamic" {
+	/* All true */
+
+	if allof ( true, exists "to", exists "cc" ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong all-true first-static outcome: false";
+	}
+
+	if allof ( exists "from", true, exists "cc" ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong all-true second-static outcome: false";
+	}
+
+	if allof ( exists "from", exists "to", true ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong all-true third-static outcome: false";
+	}
+
+	/* All false */
+
+	if allof ( false, exists "frop", exists "frml" ) {
+		test_fail "chose wrong all-false first-static outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( exists "friep", false, exists "frml" ) {
+		test_fail "chose wrong all-false second-static outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( exists "friep", exists "frop", false ) {
+		test_fail "chose wrong all-false third-static outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	/* First true */
+
+	if allof ( true, exists "frop", exists "frml" ) {
+		test_fail "chose wrong first-true first-static outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( exists "to", false, exists "frml" ) {
+		test_fail "chose wrong first-true second-static outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( exists "to", exists "frop", false ) {
+		test_fail "chose wrong first-true third-static outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	/* Second true */
+
+	if allof ( false, exists "from", exists "frml" ) {
+		test_fail "chose wrong second-true first-static outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( exists "friep", true, exists "frml" ) {
+		test_fail "chose wrong second-true second-static outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( exists "friep", exists "from", false ) {
+		test_fail "chose wrong second-true third-static outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	/* Last true */
+
+	if allof ( false, exists "frop", exists "cc" ) {
+		test_fail "chose wrong last-true first-static outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( exists "friep", false, exists "cc" ) {
+		test_fail "chose wrong last-true second-static outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( exists "friep", exists "frop", true ) {
+		test_fail "chose wrong last-true third-static outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	/* First false */
+
+	if allof ( false, exists "from", exists "cc" ) {
+		test_fail "chose wrong first-false first-static outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( exists "friep", true, exists "cc" ) {
+		test_fail "chose wrong first-false second-static outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( exists "friep", exists "from", true ) {
+		test_fail "chose wrong first-false third-static outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	/* Second false */
+
+	if allof ( true, exists "frop", exists "cc" ) {
+		test_fail "chose wrong second-false first-static outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( exists "to", false, exists "cc" ) {
+		test_fail "chose wrong second-false second-static outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( exists "to", exists "frop", true ) {
+		test_fail "chose wrong second-false third-static outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	/* Last false */
+
+	if allof ( true, exists "from", exists "frml" ) {
+		test_fail "chose wrong last-false first-static outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( exists "to", true, exists "frml" ) {
+		test_fail "chose wrong last-false second-static outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( exists "to", exists "from", false ) {
+		test_fail "chose wrong last-false last-static outcome: true";
+	} else {
+		/* Correct */
+	}
+}
+
+/*
+ * TEST: Basic functionality: nesting
+ */
+
+test "Basic functionality: nesting" {
+	/* Static */
+
+	if allof ( allof(true, true), allof(true, true) ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong static nesting ((true, true),(true,true)) outcome: false";
+	}
+
+	if allof ( allof(false, true), allof(true, true) ) {
+		test_fail "chose wrong static nesting ((false, true),(true,true)) outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( allof(true, false), allof(true, true) ) {
+		test_fail "chose wrong static nesting ((true,false),(true,true)) outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( allof(true, true), allof(false, true) ) {
+		test_fail "chose wrong static nesting ((true, true),(false,true)) outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( allof(true, true), allof(true, false) ) {
+		test_fail "chose wrong static nesting ((true, true),(true,false)) outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( allof(true, false), allof(true, false) ) {
+		test_fail "chose wrong static nesting ((true, false),(true,false)) outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	/* Dynamic */
+
+	if allof ( allof(exists "to", exists "from"), allof(exists "cc", exists "subject") ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong dynamic nesting ((true, true),(true,true)) outcome: false";
+	}
+
+	if allof ( allof(exists "frop", exists "from"), allof(exists "cc", exists "subject") ) {
+		test_fail "chose wrong dynamic nesting ((false, true),(true,true)) outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( allof(exists "to", exists "friep"), allof(exists "cc", exists "subject") ) {
+		test_fail "chose wrong dynamic nesting ((true,false),(true,true)) outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( allof(exists "to", exists "from"), allof(exists "frml", exists "subject") ) {
+		test_fail "chose wrong dynamic nesting ((true, true),(false,true)) outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( allof(exists "to", exists "from"), allof(exists "cc", exists "fruts") ) {
+		test_fail "chose wrong dynamic nesting ((true, true),(true,false)) outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( allof(exists "to", exists "friep"), allof(exists "cc", exists "fruts") ) {
+		test_fail "chose wrong dynamic nesting ((true, false),(true,false)) outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	/* Static/Dynamic */
+
+	if allof ( allof(exists "to", true), allof(true, exists "subject") ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong static/dynamic nesting ((true, true),(true,true)) outcome: false";
+	}
+
+	if allof ( allof(false, exists "from"), allof(exists "cc", exists "subject") ) {
+		test_fail "chose wrong static/dynamic nesting ((false, true),(true,true)) outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( allof(exists "to", false), allof(exists "cc", exists "subject") ) {
+		test_fail "chose wrong static/dynamic nesting ((true,false),(true,true)) outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( allof(exists "to", exists "from"), allof(false, exists "subject") ) {
+		test_fail "chose wrong static/dynamic nesting ((true, true),(false,true)) outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( allof(exists "to", exists "from"), allof(exists "cc", false) ) {
+		test_fail "chose wrong static/dynamic nesting ((true, true),(true,false)) outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if allof ( allof(exists "to", false), allof(true, exists "fruts") ) {
+		test_fail "chose wrong static/dynamic nesting ((true, false),(true,false)) outcome: true";
+	} else {
+		/* Correct */
+	}
+
+}
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/test-anyof.svtest
@@ -0,0 +1,445 @@
+require "vnd.dovecot.testsuite";
+
+/*
+ * ## RFC 5228, Section 5.3. Test anyof (page 27) ##
+ */
+
+/* "The "anyof" test performs a logical OR on the tests supplied to it.
+ *
+ *  Example:  anyof (false, false)  =>   false
+ *            anyof (false, true)   =>   true
+ *            anyof (true,  true)   =>   true
+ * "
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: test@dovecot.example.net
+cc: stephan@idiot.ex
+Subject: Test
+
+Test!
+.
+;
+
+/*
+ * TEST: Basic functionality: static
+ */
+
+test "Basic functionality: static" {
+	if anyof ( true ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong single outcome: false";
+	}
+
+	if anyof ( false ) {
+		test_fail "chose wrong single outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if anyof ( true, true, true ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong all-true outcome: false";
+	}
+
+	if anyof ( false, false, false ) {
+		test_fail "chose wrong all-false outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if anyof ( true, false, false ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong first-true outcome: false";
+	}
+
+	if anyof ( false, true, false ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong second-true outcome: false";
+	}
+
+	if anyof ( false, false, true ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong last-true outcome: false";
+	}
+
+	if anyof ( false, true, true ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong first-false outcome: false";
+	}
+
+	if anyof ( true, false, true ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong second-false outcome: false";
+	}
+
+	if anyof ( true, true, false ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong last-false outcome: false";
+	}
+}
+
+/*
+ * TEST: Basic functionality: dynamic
+ */
+
+test "Basic functionality: dynamic" {
+	if anyof ( exists "from" ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong single outcome: false";
+	}
+
+	if anyof ( exists "friep" ) {
+		test_fail "chose wrong single outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if anyof ( exists "from", exists "to", exists "cc" ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong all-true outcome: false";
+	}
+
+	if anyof ( exists "friep", exists "frop", exists "frml" ) {
+		test_fail "chose wrong all-false outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if anyof ( exists "to", exists "frop", exists "frml" ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong first-true outcome: false";
+	}
+
+	if anyof ( exists "friep", exists "from", exists "frml" ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong second-true outcome: false";
+	}
+
+	if anyof ( exists "friep", exists "frop", exists "cc" ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong last-true outcome: false";
+	}
+
+	if anyof ( exists "friep", exists "from", exists "cc" ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong first-false outcome: false";
+	}
+
+	if anyof ( exists "to", exists "frop", exists "cc" ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong second-false outcome: false";
+	}
+
+	if anyof ( exists "to", exists "from", exists "frml" ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong last-false outcome: false";
+	}
+}
+
+/*
+ * TEST: Basic functionality: static/dynamic
+ */
+
+test "Basic functionality: static/dynamic" {
+	/* All true */
+
+	if anyof ( true, exists "to", exists "cc" ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong all-true first-static outcome: false";
+	}
+
+	if anyof ( exists "from", true, exists "cc" ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong all-true second-static outcome: false";
+	}
+
+	if anyof ( exists "from", exists "to", true ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong all-true third-static outcome: false";
+	}
+
+	/* All false */
+
+	if anyof ( false, exists "frop", exists "frml" ) {
+		test_fail "chose wrong all-false first-static outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if anyof ( exists "friep", false, exists "frml" ) {
+		test_fail "chose wrong all-false second-static outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if anyof ( exists "friep", exists "frop", false ) {
+		test_fail "chose wrong all-false third-static outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	/* First true */
+
+	if anyof ( true, exists "frop", exists "frml" ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong first-true first-static outcome: false";
+	}
+
+	if anyof ( exists "to", false, exists "frml" ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong first-true second-static outcome: false";
+	}
+
+	if anyof ( exists "to", exists "frop", false ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong first-true third-static outcome: false";
+	}
+
+	/* Second true */
+
+	if anyof ( false, exists "from", exists "frml" ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong second-true first-static outcome: false";
+	}
+
+	if anyof ( exists "friep", true, exists "frml" ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong second-true second-static outcome: false";
+	}
+
+	if anyof ( exists "friep", exists "from", false ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong second-true third-static outcome: false";
+	}
+
+	/* Last true */
+
+	if anyof ( false, exists "frop", exists "cc" ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong last-true first-static outcome: false";
+	}
+
+	if anyof ( exists "friep", false, exists "cc" ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong last-true second-static outcome: false";
+	}
+
+	if anyof ( exists "friep", exists "frop", true ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong last-true third-static outcome: false";
+	}
+
+	/* First false */
+
+	if anyof ( false, exists "from", exists "cc" ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong first-false first-static outcome: false";
+	}
+
+	if anyof ( exists "friep", true, exists "cc" ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong first-false second-static outcome: false";
+	}
+
+	if anyof ( exists "friep", exists "from", true ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong first-false third-static outcome: false";
+	}
+
+	/* Second false */
+
+	if anyof ( true, exists "frop", exists "cc" ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong second-false first-static outcome: false";
+	}
+
+	if anyof ( exists "to", false, exists "cc" ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong second-false second-static outcome: false";
+	}
+
+	if anyof ( exists "to", exists "frop", true ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong second-false third-static outcome: false";
+	}
+
+	/* Third false */
+
+	if anyof ( true, exists "from", exists "frml" ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong last-false first-static outcome: false";
+	}
+
+	if anyof ( exists "to", true, exists "frml" ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong last-false second-static outcome: false";
+	}
+
+	if anyof ( exists "to", exists "from", false ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong last-false third-static outcome: false";
+	}
+}
+
+/*
+ * TEST: Basic functionality: nesting
+ */
+
+test "Basic functionality: nesting" {
+	/* Static */
+
+	if anyof ( anyof(false, false), anyof(false, false) ) {
+		test_fail "chose wrong static nesting ((false, false),(false,false)) outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if anyof ( anyof(true, false), anyof(false, false) ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong static nesting ((true, false),(false,false)) outcome: false";
+	}
+
+	if anyof ( anyof(false, true), anyof(false, false) ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong static nesting ((false, true),(false,false)) outcome: false";
+	}
+
+	if anyof ( anyof(false, false), anyof(true, false) ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong static nesting ((false, false),(true,false)) outcome: false";
+	}
+
+	if anyof ( anyof(false, false), anyof(false, true) ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong static nesting ((false, false),(false,true)) outcome: false";
+	}
+
+	if anyof ( anyof(true, false), anyof(false, true) ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong static nesting ((true, false),(false,true)) outcome: false";
+	}
+
+	/* Dynamic */
+
+	if anyof ( anyof(exists "frop", exists "friep"), anyof(exists "frml", exists "fruts") ) {
+		test_fail "chose wrong dynamic nesting ((false, false),(false,false)) outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if anyof ( anyof(exists "to", exists "friep"), anyof(exists "frml", exists "fruts") ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong dynamic nesting ((true, false),(false,false)) outcome: false";
+	}
+
+	if anyof ( anyof(exists "frop", exists "from"), anyof(exists "frml", exists "fruts") ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong dynamic nesting ((false, true),(false,false)) outcome: false";
+	}
+
+	if anyof ( anyof(exists "frop", exists "friep"), anyof(exists "cc", exists "fruts") ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong dynamic nesting ((false, false),(true,false)) outcome: false";
+	}
+
+	if anyof ( anyof(exists "frop", exists "friep"), anyof(exists "frml", exists "subject") ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong dynamic nesting ((false, false),(false,true)) outcome: false";
+	}
+
+	if anyof ( anyof(exists "to", exists "friep"), anyof(exists "frml", exists "subject") ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong dynamic nesting ((true, false),(false,true)) outcome: false";
+	}
+
+	/* Static/Dynamic */
+
+	if anyof ( anyof(false, exists "friep"), anyof(exists "frml", exists "fruts") ) {
+		test_fail "chose wrong static/dynamic nesting ((false, false),(false,false)) outcome: true";
+	} else {
+		/* Correct */
+	}
+
+	if anyof ( anyof(exists "to", false), anyof(exists "frml", exists "fruts") ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong static/dynamic nesting ((true, false),(false,false)) outcome: false";
+	}
+
+	if anyof ( anyof(exists "frop", exists "from"), anyof(false, exists "fruts") ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong static/dynamic nesting ((false, true),(false,false)) outcome: false";
+	}
+
+	if anyof ( anyof(exists "frop", exists "friep"), anyof(exists "cc", false) ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong static/dynamic nesting ((false, false),(true,false)) outcome: false";
+	}
+
+	if anyof ( anyof(exists "frop", exists "friep"), anyof(exists "frml", true) ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong static/dynamic nesting ((false, false),(false,true)) outcome: false";
+	}
+
+	if anyof ( anyof(true, exists "friep"), anyof(false, exists "subject") ) {
+		/* Correct */
+	} else {
+		test_fail "chose wrong dynamic nesting ((true, false),(false,true)) outcome: false";
+	}
+
+}
+
+
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/test-exists.svtest
@@ -0,0 +1,93 @@
+require "vnd.dovecot.testsuite";
+
+/* "The "exists" test is true if the headers listed in the header-names
+ *  argument exist within the message.  All of the headers must exist or
+ *  the test is false.
+ * "
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@vestingbar.bl
+Subject: Test message
+Date: Wed, 29 Jul 2009 18:21:44 +0300
+X-Spam-Status: Not Spam
+Resent-To: nico@frop.example.com
+
+Test!
+.
+;
+
+/*
+ * TEST: One header
+ */
+
+test "One header" {
+	if not exists "from" {
+		test_fail "exists test missed from header";
+	}
+
+	if exists "x-nonsense" {
+		test_fail "exists test found non-existent header";
+	}
+}
+
+/*
+ * TEST: Two headers
+ */
+
+test "Two headers" {
+	if not exists ["from","to"] {
+		test_fail "exists test missed from or to header";
+	}
+
+	if exists ["from","x-nonsense"] {
+		test_fail "exists test found non-existent header (1)";
+	}
+
+	if exists ["x-nonsense","to"] {
+		test_fail "exists test found non-existent header (2)";
+	}
+
+	if exists ["x-nonsense","x-nonsense2"] {
+		test_fail "exists test found non-existent header (3)";
+	}
+}
+
+/*
+ * TEST: Three headers
+ */
+
+test "Three headers" {
+	if not exists ["Subject","date","resent-to"] {
+		test_fail "exists test missed subject, date or resent-to header";
+	}
+
+	if exists ["x-nonsense","date","resent-to"] {
+		test_fail "exists test found non-existent header (1)";
+	}
+
+	if exists ["subject", "x-nonsense","resent-to"] {
+		test_fail "exists test found non-existent header (2)";
+	}
+
+	if exists ["subject","date","x-nonsense"] {
+		test_fail "exists test found non-existent header (3)";
+	}
+
+	if exists ["subject", "x-nonsense","x-nonsense2"] {
+		test_fail "exists test found non-existent header (4)";
+	}
+
+	if exists ["x-nonsense","date","x-nonsense2"] {
+		test_fail "exists test found non-existent header (5)";
+	}
+
+	if exists ["x-nonsense","x-nonsense2","resent-to"] {
+		test_fail "exists test found non-existent header (6)";
+	}
+
+	if exists ["x-nonsense","x-nonsense2","x-nonsense3"] {
+		test_fail "exists test found non-existent header (7)";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/test-header.svtest
@@ -0,0 +1,280 @@
+require "vnd.dovecot.testsuite";
+require "variables";
+
+/*
+ * ## RFC 5228, Section 5.7. Test header (page 29) ##
+ */
+
+/*
+ * TEST: Basic functionality
+ */
+
+/* "The "header" test evaluates to true if the value of any of the named
+ *  headers, ignoring leading and trailing whitespace, matches any key.
+ *  The type of match is specified by the optional match argument, which
+ *  defaults to ":is" if not specified, as specified in section 2.6.
+ *
+ *  Like address and envelope, this test returns true if any combination
+ *  of the header-names list and key-list arguments match and returns
+ *  false otherwise.
+ * "
+ */
+
+test_set "message" text:
+From: stephan@example.com
+To: nico@nl.example.com, harry@de.example.com
+Subject: Frobnitzm
+Comments: This is nonsense.
+Keywords: nonsense, strange, testing
+X-Spam: Yes
+
+Test.
+.
+;
+
+test "Basic functionality" {
+	/* Must match */
+	if not header :contains ["Subject", "Comments"] "Frobnitzm" {
+		test_fail "failed to match header (1)";
+	}
+
+	if not header :contains ["Subject", "Comments"] "nonsense" {
+		test_fail "failed to match header(2)";
+	}
+
+	if not header :matches "Keywords" "*, strange, *" {
+		test_fail "failed to match header (3)";
+	}
+
+	if not header :is "Comments" "This is nonsense." {
+		test_fail "failed to match header (4)";
+	}
+
+	/* Must not match */
+	if header ["subject", "comments", "keywords"] "idiotic" {
+		test_fail "matched nonsense";
+	}
+
+	/* Match first key */
+	if not header :contains ["keywords"] ["strange", "snot", "vreemd"] {
+		test_fail "failed to match first key";
+	}
+
+	/* Match second key */
+	if not header :contains ["keywords"] ["raar", "strange", "vreemd"] {
+		test_fail "failed to match second key";
+	}
+
+	/* Match last key */
+	if not header :contains ["keywords"] ["raar", "snot", "strange"] {
+		test_fail "failed to match last key";
+	}
+
+	/* First header */
+	if not header :contains ["keywords", "subject"]
+		["raar", "strange", "vreemd"] {
+		test_fail "failed to match first header";
+	}
+
+	/* Second header */
+	if not header :contains ["subject", "keywords"]
+		["raar", "strange", "vreemd"] {
+		test_fail "failed to match second header";
+	}
+}
+
+/*
+ * TEST: Matching empty key
+ */
+
+/* "If a header listed in the header-names argument exists, it contains
+ *  the empty key ("").  However, if the named header is not present, it
+ *  does not match any key, including the empty key.  So if a message
+ *  contained the header
+ *
+ *          X-Caffeine: C8H10N4O2
+ *
+ *  these tests on that header evaluate as follows:
+ *
+ *          header :is ["X-Caffeine"] [""]         => false
+ *          header :contains ["X-Caffeine"] [""]   => true
+ * "
+ */
+
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.com
+X-Caffeine: C8H10N4O2
+Subject: I need coffee!
+Comments:
+
+Text
+.
+;
+
+test "Matching empty key" {
+	if header :is "X-Caffeine" "" {
+		test_fail ":is-matched non-empty header with empty string";
+	}
+
+	if not header :contains "X-Caffeine" "" {
+		test_fail "failed to match existing header with empty string";
+	}
+
+	if not header :is "comments" "" {
+		test_fail "failed to match empty header with empty string";
+	}
+
+	if header :contains "X-Nonsense" "" {
+		test_fail ":contains-matched non-existent header with empty string";
+	}
+}
+
+/*
+ * TEST: Ignoring whitespace
+ */
+
+/* "The "header" test evaluates to true if the value of any of the named
+ *  headers, ignoring leading and trailing whitespace, matches any key.
+ *  ...
+ * "
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.com
+Subject:         Help
+X-A:     Text
+X-B: Text   
+
+Text
+.
+;
+
+test "Ignoring whitespace" {
+	if not header :is "x-a" "Text" {
+		if header :matches "x-a" "*" {
+			set "header" "${1}"; 
+		}
+		test_fail "header test does not strip leading whitespace (header=`${header}`)";
+	}
+
+	if not header :is "x-b" "Text" {
+		if header :matches "x-b" "*" {
+			set "header" "${1}"; 
+		}
+		test_fail "header test does not strip trailing whitespace (header=`${header}`)";
+	}
+
+	if not header :is "subject" "Help" {
+		if header :matches "subject" "*" {
+			set "header" "${1}"; 
+		}
+		test_fail "header test does not strip both leading and trailing whitespace (header=`${header}`)";
+	}
+}
+
+/*
+ * TEST: Absent or empty header
+ */
+
+/* "Testing whether a given header is either absent or doesn't contain
+ *  any non-whitespace characters can be done using a negated "header"
+ *  test:
+ *
+ *          not header :matches "Cc" "?*"
+ * "
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.com
+CC: harry@nonsense.ex
+Subject:
+Comments:
+
+Text
+.
+;
+
+test "Absent or empty header" {
+	if not header :matches "Cc" "?*" {
+		test_fail "CC header is not absent or empty";
+	}
+
+	if header :matches "Subject" "?*" {
+		test_fail "Subject header is empty, but matched otherwise";
+	}
+
+	if header :matches "Comment" "?*" {
+		test_fail "Comment header is empty, but matched otherwise";
+	}
+}
+
+/*
+ * ## RFC 5228, Section 2.4.2.2. Headers (page 9)
+ */
+
+/*
+ * TEST: Invalid header name
+ */
+
+/* "A header name never contains a colon.  The "From" header refers to a
+ *  line beginning "From:" (or "From   :", etc.).  No header will match
+ *  the string "From:" due to the trailing colon.
+ *
+ *  Similarly, no header will match a syntactically invalid header name.
+ *  An implementation MUST NOT cause an error for syntactically invalid
+ *  header names in tests.
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.com
+Subject: Valid message
+X-Multiline: This is a multi-line
+ header body, which should be
+ unfolded correctly.
+
+Text
+.
+;
+
+test "Invalid header name" {
+	if header :contains "subject:" "" {
+		test_fail "matched invalid header name";
+	}
+
+	if header :contains "to!" "" {
+		test_fail "matched invalid header name";
+	}
+}
+
+/*
+ * TEST: Folded headers
+ */
+
+/* "Header lines are unfolded as described in [RFC 2822] section 2.2.3.
+ *  ...
+ * "
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.com
+Subject: Not enough space on a line!
+X-Multiline: This is a multi-line
+ header body, which should be
+ unfolded correctly.
+
+Text
+.
+;
+
+test "Folded header" {
+	if not header :is "x-multiline"
+		"This is a multi-line header body, which should be unfolded correctly." {
+		test_fail "failed to properly unfold folded header.";
+	}
+}
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/test-size.svtest
@@ -0,0 +1,74 @@
+require "vnd.dovecot.testsuite";
+
+/*
+ * ## RFC 5228, Section 5.9. Test size (page 29) ##
+ */
+
+/*
+ * TEST: Basic functionality
+ */
+
+/* "The "size" test deals with the size of a message.  It takes either a
+ *  tagged argument of ":over" or ":under", followed by a number
+ *  representing the size of the message.
+ *
+ *  If the argument is ":over", and the size of the message is greater
+ *  than the number provided, the test is true; otherwise, it is false.
+
+ *  If the argument is ":under", and the size of the message is less than
+ *  the number provided, the test is true; otherwise, it is false.
+ * "
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.com
+Subject:         Help        
+X-A:     Text
+X-B: Text            
+X-Multiline: This is a multi-line
+ header body, which should be
+ unfolded correctly.
+
+Text
+
+.
+;
+
+test "Basic functionality" {
+	if not size :under 1000 {
+		test_fail "size test produced unexpected result (1)";
+	}
+
+	if size :under 10 {
+		test_fail "size test produced unexpected result (2)";
+	}
+
+	if not size :over 10 {
+		test_fail "size test produced unexpected result (3)";
+	}
+
+	if size :over 1000 {
+		test_fail "size test produced unexpected result (4)";
+	}
+}
+
+/*
+ * TEST: Exact size
+ */
+
+/* "Note that for a message that is exactly 4,000 octets, the message is
+ *  neither ":over" nor ":under" 4000 octets.
+ * "
+ */
+
+test "Exact size" {
+	if size :under 221 {
+		test_fail "size :under matched exact limit";
+	}
+
+	if size :over 221 {
+		test_fail "size :over matched exact limit";
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/tests/testsuite.svtest
@@ -0,0 +1,75 @@
+require "vnd.dovecot.testsuite";
+require "envelope";
+
+/* Test message environment */
+
+test "Message Environment" {
+	test_set "message" text:
+From: sirius@example.org
+To: nico@frop.example.com
+Subject: Frop!
+
+Frop!
+.
+	;
+
+	if not header :contains "from" "example.org" {
+		test_fail "message data not set properly.";
+	}
+
+	test_set "message" text:
+From: nico@frop.example.com
+To: stephan@nl.example.com
+Subject: Friep!
+
+Friep!
+.
+	;
+
+	if not header :is "from" "nico@frop.example.com" {
+    	test_fail "message data not set properly.";
+	}
+
+	keep;
+}
+
+/* Test envelope environment */
+
+test "Envelope Environment" {
+	test_set "envelope.from" "stephan@hutsefluts.example.net";
+
+	if not envelope :is "from" "stephan@hutsefluts.example.net" {
+		test_fail "envelope.from data not set properly (1).";
+	}
+
+	test_set "envelope.to" "news@example.org";
+
+	if not envelope :is "to" "news@example.org" {
+		test_fail "envelope.to data not set properly (1).";
+	}
+
+	test_set "envelope.auth" "sirius";
+
+	if not envelope :is "auth" "sirius" {
+		test_fail "envelope.auth data not set properly (1).";
+	}
+
+	test_set "envelope.from" "stephan@example.org";
+
+	if not envelope :is "from" "stephan@example.org" {
+		test_fail "envelope.from data not reset properly (2).";
+	}
+
+	test_set "envelope.to" "past-news@example.org";
+
+	if not envelope :is "to" "past-news@example.org" {
+		test_fail "envelope.to data not reset properly (2).";
+	}
+
+	test_set "envelope.auth" "zilla";
+
+	if not envelope :is "auth" "zilla" {
+		test_fail "envelope.auth data not reset properly (2).";
+	}
+}
+
--- /dev/null
+++ dovecot-2.3.4.1/pigeonhole/update-version.sh
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+SRCDIR="${1:-`pwd`}"
+BUILDDIR="${2:-`pwd`}"
+VERSION_H="pigeonhole-version.h"
+VERSION_HT="pigeonhole-version.h.tmp"
+
+abspath()
+{ #$1 the path
+  #$2 1 -> SRCDIR || 2 -> BUILDDIR
+	old=`pwd`
+	cd "${1}"
+	if [ ${2} -eq 1 ]; then
+		SRCDIR=`pwd`
+	else
+		BUILDDIR=`pwd`
+	fi
+	cd "$old"
+}
+
+abspath "${SRCDIR}" 1
+abspath "${BUILDDIR}" 2
+
+# when using a different BUILDDIR just copy from SRCDIR, if there is no .git
+if [ "${BUILDDIR}" != "${SRCDIR}" ]; then
+	if [ ! -d "${SRCDIR}/.git" ]  && [ -f "${SRCDIR}/${VERSION_H}" ]; then
+		cmp -s "${SRCDIR}/${VERSION_H}" "${BUILDDIR}/${VERSION_H}"
+		if [ $? -ne 0 ]; then
+			cp "${SRCDIR}/${VERSION_H}" "${BUILDDIR}/${VERSION_H}"
+			exit 0
+		fi
+	fi
+fi
+
+# Don't generate dovecot-version.h if the source tree has no .git dir but
+# a dovecot-version.h. This may be the result of a release/nightly tarball.
+[ ! -d "${SRCDIR}/.git" ] && [ -f "${BUILDDIR}/${VERSION_H}" ] && exit 0
+
+# Lets generate the dovecot-version.h
+[ -f "${BUILDDIR}/${VERSION_HT}" ] && rm -f "${BUILDDIR}/${VERSION_HT}"
+if true; then
+	GITID=`git --git-dir ${SRCDIR}/.git rev-parse --short HEAD`
+	cat > "${BUILDDIR}/${VERSION_HT}" <<EOF
+#ifndef PIGEONHOLE_VERSION_H
+#define PIGEONHOLE_VERSION_H
+
+#define PIGEONHOLE_VERSION_FULL PIGEONHOLE_VERSION" (${GITID})"
+
+#endif /* PIGEONHOLE_VERSION_H */
+EOF
+else
+	cat > "${BUILDDIR}/${VERSION_HT}" <<EOF
+#ifndef PIGEONHOLE_VERSION_H
+#define PIGEONHOLE_VERSION_H
+
+#define PIGEONHOLE_VERSION_FULL PIGEONHOLE_VERSION
+
+#endif /* PIGEONHOLE_VERSION_H */
+EOF
+fi
+
+cmp -s "${BUILDDIR}/${VERSION_H}" "${BUILDDIR}/${VERSION_HT}" && \
+	rm -f "${BUILDDIR}/${VERSION_HT}" || \
+	mv -f "${BUILDDIR}/${VERSION_HT}" "${BUILDDIR}/${VERSION_H}"
