diff --git a/INSTALL b/INSTALL
index 43afdb3c9db7449a0dcf196052c2d99b330b1cbe..052ada22722c64e85855dd09d1b628638217ddf8 100644
--- a/INSTALL
+++ b/INSTALL
@@ -260,15 +260,25 @@ 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..)
    Path to a script file or a directory containing script files that need to be
-   executed before the user's script. If the path points to a directory, all the
-   Sieve scripts contained therein (with the proper .sieve extension) are
-   executed. The order of execution is determined by the file names, using a
-   normal 8bit per-character comparison. 
+   executed before the user's personal script. If the 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 file or directory paths can be specified by appending an
+   increasing number. The Sieve scripts found from these paths 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 =
-   Identical to sieve_before, only the specified scripts are executed after the
-   user's script (only when keep is still in effect!). 
+ 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 
@@ -302,17 +312,29 @@ For example:
 
 plugin {
 ...
-   # Scripts executed before the user's script.
+   # 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
 
-   # Scripts executed after the user's script (if keep is still in effect)
+   # User-specific scripts executed before the user's personal script.
+   #   E.g. a vacation script managed through a non-ManageSieve GUI.
+   sieve_before2 = /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_after = /var/lib/dovecot/sieve/after.d/
+   sieve_after2 = /var/lib/dovecot/sieve/after.d/
 }
 
-IMPORTANT: Be sure to manually pre-compile the scripts specified by sieve_before 
-and sieve_after using the sievec tool, as explained in the README file.
+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.
+In that case be sure to manually pre-compile those scripts using the sievec
+tool, as explained in the README file.
 
 Sieve Interpreter - Extension Configuration
 -------------------------------------------
@@ -324,7 +346,8 @@ Sieve Interpreter - Extension Configuration
   to delete and add header fields.
 
   The editheader extension requires explicit configuration and is not enabled
-  for use by default. Refer to doc/editheader.txt for configuration information.
+  for use by default. Refer to doc/extensions/editheader.txt for configuration
+  information.
 
 - Vacation extension:
 
@@ -332,8 +355,8 @@ Sieve Interpreter - Extension Configuration
   automatic replies to incoming email messages.
 
   The vacation extension is available by default, but it has its own specific
-  configuration options. Refer to doc/vacation.txt for settings specific to the
-  vacation extension.
+  configuration options. Refer to doc/extensions/vacation.txt for settings
+  specific to the vacation extension.
 
 - Include extension:
 
@@ -341,8 +364,8 @@ Sieve Interpreter - Extension Configuration
   into another.
 
   The include extension is available by default, but it has its own specific
-  configuration options. Refer to doc/include.txt for settings specific to the
-  include extension.
+  configuration options. Refer to doc/extensions/include.txt for settings
+  specific to the include extension.
 
 - Spamtest and Virustest extensions:
 
@@ -357,7 +380,7 @@ Sieve Interpreter - Extension Configuration
 
   The spamtest, spamtestplus and virustest extensions require explicit
   configuration and are not enabled for use by default. Refer to
-  doc/spamtest-virustest.txt for configuration information.
+  doc/extensions/spamtest-virustest.txt for configuration information.
 
 Sieve Interpreter - Migration from CMUSieve (Dovecot v1.0/v1.1)
 ---------------------------------------------------------------
diff --git a/Makefile.am b/Makefile.am
index a3daf563fa0291fb7d2e7f9dfb465ebf6ee5e9aa..e8cdca0acecbd7bb40067dcb346ac7fe9277b524 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -133,6 +133,8 @@ test_cases = \
 	tests/extensions/editheader/protected.svtest \
 	tests/extensions/editheader/errors.svtest \
 	tests/extensions/vnd.dovecot/debug/execute.svtest \
+	tests/extensions/vnd.dovecot/duplicate/errors.svtest \
+	tests/extensions/vnd.dovecot/duplicate/execute.svtest \
 	tests/deprecated/notify/basic.svtest \
 	tests/deprecated/notify/mailto.svtest \
 	tests/deprecated/notify/errors.svtest \
diff --git a/README b/README
index 179048a81f0f3c1256be3ed57cb6592b7d2bcd57..6e2ed9e456eaa9fb85df82511ccfe83872b4f665 100644
--- a/README
+++ b/README
@@ -209,11 +209,11 @@ scripts using the sievec command line tool. For example:
 
 sievec /var/lib/dovecot/sieve/global/
 
-This is 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.
+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
 ===========================
diff --git a/configure.in b/configure.in
index c075b061e0497ab7a07e2199a0a065d1b1f38c0f..34a7f586c590384244d10ea342af652cfefc103f 100644
--- a/configure.in
+++ b/configure.in
@@ -97,6 +97,7 @@ doc/Makefile
 doc/man/Makefile
 doc/example-config/Makefile
 doc/example-config/conf.d/Makefile
+doc/rfc/Makefile
 src/Makefile
 src/lib-sieve/Makefile
 src/lib-sieve/plugins/Makefile
@@ -121,6 +122,7 @@ src/lib-sieve/plugins/ihave/Makefile
 src/lib-sieve/plugins/editheader/Makefile
 src/lib-sieve/plugins/vnd.dovecot/Makefile
 src/lib-sieve/plugins/vnd.dovecot/debug/Makefile
+src/lib-sieve/plugins/vnd.dovecot/duplicate/Makefile
 src/lib-sieve-tool/Makefile
 src/lib-sievestorage/Makefile
 src/lib-managesieve/Makefile
diff --git a/doc/Makefile.am b/doc/Makefile.am
index 20ce3278bb15532185958ec43016ebd13b13a3d2..58b1b6ebbec79f904725a84b741b1be42b30da37 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -1,15 +1,14 @@
-SUBDIRS = man example-config
+SUBDIRS = man example-config rfc
 
 docfiles = \
-	vacation.txt \
-	spamtest-virustest.txt
+	script-location-dict.txt
 
 if BUILD_DOCS
 sieve_doc_DATA = $(docfiles)
 endif
 
 EXTRA_DIST = \
-	rfc \
 	devel \
+	extensions \
 	$(docfiles)
 
diff --git a/doc/example-config/conf.d/90-sieve.conf b/doc/example-config/conf.d/90-sieve.conf
index c3df478d19a62c61b002c48db97dda627b15e869..35a9f5d36352643f22a4a762d834353efdb0acad 100644
--- a/doc/example-config/conf.d/90-sieve.conf
+++ b/doc/example-config/conf.d/90-sieve.conf
@@ -28,14 +28,20 @@ plugin {
   # Path to a script file or a directory containing script files that need to be
   # executed before the user's script. If the path points to a directory, all
   # the Sieve scripts contained therein (with the proper .sieve extension) are
-  # executed. The order of execution is determined by the file names, using a
-  # normal 8bit per-character comparison. 
+  # executed. The order of execution within a directory is determined by the
+  # file names, using a normal 8bit per-character comparison. Multiple script
+  # file or directory paths can be specified by appending an increasing number.
   #sieve_before =
+  #sieve_before2 =
+  #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!). 
+  # user's script (only when keep is still in effect!). Multiple script file or
+  # directory paths 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
diff --git a/doc/editheader.txt b/doc/extensions/editheader.txt
similarity index 100%
rename from doc/editheader.txt
rename to doc/extensions/editheader.txt
diff --git a/doc/include.txt b/doc/extensions/include.txt
similarity index 100%
rename from doc/include.txt
rename to doc/extensions/include.txt
diff --git a/doc/spamtest-virustest.txt b/doc/extensions/spamtest-virustest.txt
similarity index 100%
rename from doc/spamtest-virustest.txt
rename to doc/extensions/spamtest-virustest.txt
diff --git a/doc/vacation.txt b/doc/extensions/vacation.txt
similarity index 100%
rename from doc/vacation.txt
rename to doc/extensions/vacation.txt
diff --git a/doc/extensions/vnd.dovecot.duplicate.txt b/doc/extensions/vnd.dovecot.duplicate.txt
new file mode 100644
index 0000000000000000000000000000000000000000..0d27cc9116bd3b49d91fe00abedbf5e011bb5074
--- /dev/null
+++ b/doc/extensions/vnd.dovecot.duplicate.txt
@@ -0,0 +1,56 @@
+Vnd.dovecot.duplicate Extension
+
+Relevant specifications
+=======================
+
+	doc/rfc/spec-bosch-sieve-duplicate.txt
+
+Description
+===========
+
+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. The vnd.dovecot.duplicate
+extension augments the Sieve filtering implementation with a test to verify
+whether a message was received earlier already based on its Message-ID. This can
+be used to prevent duplicate deliveries, e.g. caused by mailinglists when people
+reply both to the mailinglist and the user directly.
+
+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.
+
+Refer to doc/rfc/spec-bosch-sieve-duplicate.txt for a specification of the Sieve
+language extension.
+
+Implementation Status
+---------------------
+
+The "vnd.dovecot.duplicate" Sieve language extension is vendor-specific with
+draft status and its 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 "vnd.dovecot.duplicate" extension is not enabled by default and thus it
+needs to be enabled explicitly.
+
+The following configuration settings are used:
+
+sieve_duplicate_period = 1d
+  This option specifies after what period of time Message-IDs are purged from
+  the duplicate 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_extensions = +vnd.dovecot.duplicate
+
+  sieve_duplicate_period = 6h
+}
diff --git a/doc/rfc/Makefile.am b/doc/rfc/Makefile.am
new file mode 100644
index 0000000000000000000000000000000000000000..682bd724a596199c6b7d135248748afe83beaa08
--- /dev/null
+++ b/doc/rfc/Makefile.am
@@ -0,0 +1,32 @@
+docfiles = \
+	body.rfc5173.txt
+	collation.rfc4790.txt
+	copy.rfc3894.txt
+	draft-degener-sieve-multiscript-00.txt
+	draft-duerst-mailto-bis-05.txt
+	draft-ietf-sieve-include-05.txt
+	draft-murchison-sieve-regex-07.txt
+	editheader.rfc5293.txt
+	environment.rfc5183.txt
+	ihave.rfc5463.txt
+	imail.rfc2822.txt
+	imap4flags.rfc5232.txt
+	mailto.rfc2368.txt
+	managesieve.rfc5804.txt
+	notify-mailto.rfc5436.txt
+	notify.rfc5435.txt
+	reject-ereject.rfc5429.txt
+	relational.rfc5231.txt
+	sieve.rfc5228.txt
+	spamvirustest.rfc5235.txt
+	spec-bosch-sieve-duplicate.txt
+	subaddress.rfc5233.txt
+	uri.rfc3986.txt
+	utf-8.rfc3629.txt
+	vacation.rfc5230.txt
+	vacation-seconds.rfc6131.txt
+	variables.rfc5229.txt
+
+EXTRA_DIST = \
+	$(docfiles)
+
diff --git a/doc/rfc/spec-bosch-sieve-duplicate.txt b/doc/rfc/spec-bosch-sieve-duplicate.txt
new file mode 100644
index 0000000000000000000000000000000000000000..95512457fb93d58e324d4acbff28fb094a61505f
--- /dev/null
+++ b/doc/rfc/spec-bosch-sieve-duplicate.txt
@@ -0,0 +1,224 @@
+
+
+
+Pigeonhole Project                                              S. Bosch
+                                                       February 25, 2012
+
+
+         Sieve Email Filtering: Detecting Duplicate Deliveries
+
+Abstract
+
+   This document defines a new vendor-defined test command "duplicate"
+   for the "Sieve" email filtering language that tests whether an e-mail
+   message is a duplicate, i.e. whether it was seen before by the
+   delivery agent.  Users can use this new test to remove duplicate
+   deliveries commonly caused by mailing list subscriptions or mail
+   account aliases.
+
+
+Table of Contents
+
+   1.  Introduction  . . . . . . . . . . . . . . . . . . . . . . . . . 2
+   2.  Conventions Used in This Document . . . . . . . . . . . . . . . 2
+   3.  Test "duplicate"  . . . . . . . . . . . . . . . . . . . . . . . 2
+   4.  Sieve Capability Strings  . . . . . . . . . . . . . . . . . . . 3
+   5.  Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
+   6.  Security Considerations . . . . . . . . . . . . . . . . . . . . 3
+   7.  Normative References  . . . . . . . . . . . . . . . . . . . . . 3
+   Author's Address  . . . . . . . . . . . . . . . . . . . . . . . . . 3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Bosch                                                           [Page 1]
+
+                  Sieve: Detecting Duplicate Deliveries    February 2012
+
+
+1.  Introduction
+
+   This is an extension to the Sieve filtering language defined by RFC
+   5228 [SIEVE].  It adds a test to determine whether a message was seen
+   before by the delivery agent based on the Message-ID header.
+
+   Duplicate deliveries are a common side-effect of being subscribed to
+   a mailing list.  If a member of the list decides to reply to both the
+   user and the mailing list itself, the user will get a copy of the
+   message directly and through mailing list.  In another scenario, the
+   user has several aliases for his mail account and one of his contacts
+   sends the message to multiple addresses that eventually map to the
+   same account.  Using the vnd.dovecot.duplicate extension, users have
+   the means to detect such duplicates and deal with these
+   appropriately, e.g. by discarding them.
+
+   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 and GUI-based Sieve editors.
+
+
+2.  Conventions Used in This Document
+
+   The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+   "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+   document are to be interpreted as described in [KEYWORDS].
+
+   Conventions for notations are as in [SIEVE] Section 1.1, including
+   use of the "Usage:" label for the definition of action and tagged
+   arguments syntax.
+
+
+3.  Test "duplicate"
+
+   Usage: "duplicate" [<name: string>]
+
+   The "duplicate" test keeps track of which Message-ID values were seen
+   before by this test in an earlier delivery operation.  It evaluates
+   to "true" when the Message-ID header of the current message was seen
+   before.  If it is not known, the test evaluates to "false" and the
+   Message-ID is added to a persistent internal tracking list.
+   Implementations SHOULD limit the number of messages that are tracked
+   and SHOULD let Message-ID entries expire after some short period of
+   time.
+
+   Using the "name" argument, the duplicate test can be employed for
+   multiple independent purposes.  Only when the Message-ID was seen
+   before in an earlier script execution by a duplicate test with the
+
+
+
+Bosch                                                           [Page 2]
+
+                  Sieve: Detecting Duplicate Deliveries    February 2012
+
+
+   same "name" argument, it is recognized as a duplicate.
+
+
+4.  Sieve Capability Strings
+
+   A Sieve implementation that defines the "duplicate" test command will
+   advertise the capability string "vnd.dovecot.duplicate".
+
+
+5.  Example
+
+   In this example, duplicate deliveries are stored in a special folder
+   contained in the user's Trash folder.  If the folder does not exist,
+   it is created.  This way, the user has a chance to recover messages
+   when necessary.  Messages that are not recognized as duplicates are
+   stored in the user's inbox as normal.
+
+   require ["vnd.dovecot.duplicate", "fileinto", "mailbox"];
+
+   if duplicate {
+           fileinto :create "Trash/Duplicate";
+   }
+
+
+6.  Security Considerations
+
+   A flood of unique messages could cause the list of tracked Message-
+   IDs to grow indefinitely.  Implementations therefore SHOULD implement
+   limits on the number and lifespan of entries in that list.
+
+
+7.  Normative References
+
+   [KEYWORDS]
+              Bradner, S., "Key words for use in RFCs to Indicate
+              Requirement Levels", BCP 14, RFC 2119, March 1997.
+
+   [SIEVE]    Guenther, P. and T. Showalter, "Sieve: An Email Filtering
+              Language", RFC 5228, January 2008.
+
+
+
+
+
+
+
+
+
+
+
+
+Bosch                                                           [Page 3]
+
+                  Sieve: Detecting Duplicate Deliveries    February 2012
+
+
+Author's Address
+
+   Stephan Bosch
+   Enschede
+   NL
+
+   Email: stephan@rename-it.nl
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Bosch                                                           [Page 4]
+
diff --git a/doc/rfc/xml/reference.KEYWORDS.xml b/doc/rfc/xml/reference.KEYWORDS.xml
new file mode 100644
index 0000000000000000000000000000000000000000..26056ea06af17e18a4640cb0d17c5518854a9fad
--- /dev/null
+++ b/doc/rfc/xml/reference.KEYWORDS.xml
@@ -0,0 +1,44 @@
+<?xml version='1.0' encoding='UTF-8'?>
+
+<reference anchor='KEYWORDS'>
+
+<front>
+<title abbrev='RFC Key Words'>Key words for use in RFCs to Indicate Requirement Levels</title>
+<author initials='S.' surname='Bradner' fullname='Scott Bradner'>
+<organization>Harvard University</organization>
+<address>
+<postal>
+<street>1350 Mass. Ave.</street>
+<street>Cambridge</street>
+<street>MA 02138</street></postal>
+<phone>- +1 617 495 3864</phone>
+<email>sob@harvard.edu</email></address></author>
+<date year='1997' month='March' />
+<area>General</area>
+<keyword>keyword</keyword>
+<abstract>
+<t>
+   In many standards track documents several words are used to signify
+   the requirements in the specification.  These words are often
+   capitalized.  This document defines these words as they should be
+   interpreted in IETF documents.  Authors who follow these guidelines
+   should incorporate this phrase near the beginning of their document:
+
+<list>
+<t>
+      The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL
+      NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED",  "MAY", and
+      "OPTIONAL" in this document are to be interpreted as described in
+      RFC 2119.
+</t></list></t>
+<t>
+   Note that the force of these words is modified by the requirement
+   level of the document in which they are used.
+</t></abstract></front>
+
+<seriesInfo name='BCP' value='14' />
+<seriesInfo name='RFC' value='2119' />
+<format type='TXT' octets='4723' target='http://www.rfc-editor.org/rfc/rfc2119.txt' />
+<format type='HTML' octets='17491' target='http://xml.resource.org/public/rfc/html/rfc2119.html' />
+<format type='XML' octets='5777' target='http://xml.resource.org/public/rfc/xml/rfc2119.xml' />
+</reference>
diff --git a/doc/rfc/xml/reference.SIEVE.xml b/doc/rfc/xml/reference.SIEVE.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6e2ecab496fa4e897ec1511e3b086004c86bb02a
--- /dev/null
+++ b/doc/rfc/xml/reference.SIEVE.xml
@@ -0,0 +1,17 @@
+<?xml version='1.0' encoding='UTF-8'?>
+
+<reference anchor='SIEVE'>
+
+<front>
+<title>Sieve: An Email Filtering Language</title>
+<author initials='P.' surname='Guenther' fullname='P. Guenther'>
+<organization /></author>
+<author initials='T.' surname='Showalter' fullname='T. Showalter'>
+<organization /></author>
+<date year='2008' month='January' />
+<abstract>
+<t>This document describes a language for filtering email messages at time of final delivery.  It is designed to be implementable on either a mail client or mail server.  It is meant to be extensible, simple, and independent of access protocol, mail architecture, and operating system.  It is suitable for running on a mail server where users may not be allowed to execute arbitrary programs, such as on black box Internet Message Access Protocol (IMAP) servers, as the base language has no variables, loops, or ability to shell out to external programs. [STANDARDS-TRACK]</t></abstract></front>
+
+<seriesInfo name='RFC' value='5228' />
+<format type='TXT' octets='87531' target='http://www.rfc-editor.org/rfc/rfc5228.txt' />
+</reference>
diff --git a/doc/rfc/xml/spec-bosch-sieve-duplicate.xml b/doc/rfc/xml/spec-bosch-sieve-duplicate.xml
new file mode 100644
index 0000000000000000000000000000000000000000..26ceb5be00a61e637b7f5de68d1f620e0c552c93
--- /dev/null
+++ b/doc/rfc/xml/spec-bosch-sieve-duplicate.xml
@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding="US-ASCII"?>
+
+<!DOCTYPE rfc SYSTEM "rfc2629.dtd">
+
+<?xml-stylesheet type='text/xsl' href='rfc2629.xslt' ?>
+<!-- used by XSLT processors -->
+<?rfc strict="yes" ?>
+<?rfc toc="yes"?>
+<?rfc tocdepth="4"?>
+<?rfc symrefs="yes"?>
+<?rfc sortrefs="yes" ?>
+<?rfc compact="yes" ?>
+<?rfc subcompact="no" ?>
+<?rfc private="Pigeonhole Project" ?>
+
+<rfc category="info" docName="spec-bosch-sieve-pipe">
+<!-- ***** FRONT MATTER ***** -->
+
+<front>
+<title abbrev="Sieve: Detecting Duplicate Deliveries">
+Sieve Email Filtering: Detecting Duplicate Deliveries
+</title>
+
+<author fullname="Stephan Bosch" initials="S." surname="Bosch">
+  <organization/>
+  <address>
+    <postal>
+      <street></street>
+      <city>Enschede</city>
+      <country>NL</country>
+    </postal>
+    <email>stephan@rename-it.nl</email>
+  </address>
+</author>
+
+<date/>
+
+<area>General</area>
+<workgroup>Pigeonhole Project</workgroup>
+<keyword>sieve</keyword>
+<keyword>duplicate deliveries</keyword>
+
+<abstract>
+<t>
+This document defines a new vendor-defined test command "duplicate" for the
+"Sieve" email filtering language that tests whether an e-mail message is a
+duplicate, i.e. whether it was seen before by the delivery agent. Users can use
+this new test to remove duplicate deliveries commonly caused by mailing list
+subscriptions or mail account aliases. 
+</t>
+</abstract>
+</front>
+
+<middle>
+
+<section title="Introduction">
+<t>This is an extension to the Sieve filtering language defined by
+<xref target="SIEVE">RFC 5228</xref>. It adds a test to determine whether a
+message was seen before by the delivery agent based on the Message-ID header.
+</t>
+
+<t>Duplicate deliveries are a common side-effect of being subscribed to a
+mailing list. If a member of the list decides to reply to both the user and the
+mailing list itself, the user will get a copy of the message directly and
+through mailing list. In another scenario, the user has several aliases for his
+mail account and one of his contacts sends the message to multiple addresses
+that eventually map to the same account. Using the vnd.dovecot.duplicate
+extension, users have the means to detect such duplicates and deal with these
+appropriately, e.g. by discarding them.</t>
+
+<t>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 and GUI-based Sieve editors.
+</t>
+</section>
+
+<section title="Conventions Used in This Document">
+<t>The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+document are to be interpreted as described in <xref target="KEYWORDS"/>.</t>
+<t>Conventions for notations are as in <xref target="SIEVE"/> Section 1.1,
+including use of the "Usage:" label for the definition of action and tagged
+arguments syntax.</t>
+</section>
+
+<section title="Test &quot;duplicate&quot;">
+<?rfc needLines="3" ?>
+<figure>
+<artwork><![CDATA[
+Usage: "duplicate" [<name: string>]
+]]></artwork>
+</figure>
+
+<t>The "duplicate" test keeps track of which Message-ID values were seen before
+by this test in an earlier delivery operation. It evaluates to "true" when the
+Message-ID header of the current message was seen before. If it is not known,
+the test evaluates to "false" and the Message-ID is added to a persistent
+internal tracking list. Implementations SHOULD limit the number of messages that
+are tracked and SHOULD let Message-ID entries expire after some short period of
+time.
+</t>
+
+<t>Using the "name" argument, the duplicate test can be employed for multiple
+independent purposes. Only when the Message-ID was seen before in an earlier
+script execution by a duplicate test with the same "name" argument, it is
+recognized as a duplicate.
+</t>
+</section>
+
+<section title="Sieve Capability Strings">
+<t>A Sieve implementation that defines the "duplicate" test command 
+will advertise the capability string "vnd.dovecot.duplicate".
+</t>
+</section>
+
+<section title="Example">
+<t>In this example, duplicate deliveries are stored in a special folder
+contained in the user's Trash folder. If the folder does not exist, it is
+created. This way, the user has a chance to recover messages when necessary.
+Messages that are not recognized as duplicates are stored in the user's inbox as
+normal.
+</t>
+
+<?rfc needLines="7" ?>
+<figure>
+<artwork><![CDATA[
+require ["vnd.dovecot.duplicate", "fileinto", "mailbox"];
+
+if duplicate {
+        fileinto :create "Trash/Duplicate";
+}
+]]></artwork>
+</figure>
+
+</section>
+
+<section anchor="Security" title="Security Considerations">
+<t>A flood of unique messages could cause the list of tracked Message-IDs to 
+grow indefinitely. Implementations therefore SHOULD implement limits on the 
+number and lifespan of entries in that list.</t>
+</section>
+</middle>
+
+<!--  *****BACK MATTER ***** -->
+
+<back>
+<!-- References split into informative and normative -->
+
+<references title="Normative References">
+  <?rfc include="reference.KEYWORDS.xml"?>
+  <?rfc include="reference.SIEVE.xml"?>
+</references>
+
+<!--<references title="Informative References">
+  
+</references> -->
+</back>
+
+</rfc>
diff --git a/src/lib-managesieve/managesieve-parser.c b/src/lib-managesieve/managesieve-parser.c
index 4906f714938fcba174044e6031ee11c76318c364..d05888a5f9aef6b301d345f697fdddc85ab5e639 100644
--- a/src/lib-managesieve/managesieve-parser.c
+++ b/src/lib-managesieve/managesieve-parser.c
@@ -636,7 +636,7 @@ static ssize_t quoted_string_istream_read(struct istream_private *stream)
 		if (ret <= 0 && (ret != -2 || stream->skip == 0)) {
 			if ( stream->istream.eof && stream->istream.stream_errno == 0 ) {
 				stream->istream.eof = 0;
-				stream->istream.stream_errno = EPROTO;
+				stream->istream.stream_errno = EIO;
 			} else {
 				stream->istream.stream_errno = stream->parent->stream_errno;
 				stream->istream.eof = stream->parent->eof;
@@ -673,7 +673,7 @@ static ssize_t quoted_string_istream_read(struct istream_private *stream)
 			if ( !IS_QUOTED_SPECIAL(data[i]) ) {
 				qsstream->parser->error =
 					"Escaped quoted-string character is not a QUOTED-SPECIAL.";
-				stream->istream.stream_errno = EPROTO;
+				stream->istream.stream_errno = EIO;
 				ret = -1;
 				break;
 			}
@@ -682,7 +682,7 @@ static ssize_t quoted_string_istream_read(struct istream_private *stream)
 
 		if ( (data[i] & 0x80) == 0 && ( data[i] == '\r' || data[i] == '\n' ) ) {
 			qsstream->parser->error = "String contains invalid character.";
-			stream->istream.stream_errno = EPROTO;
+			stream->istream.stream_errno = EIO;
 			ret = -1;
 			break;
 		}
diff --git a/src/lib-sieve-tool/mail-raw.c b/src/lib-sieve-tool/mail-raw.c
index b72bb36344eb44690bedee7f64c8294df397e7b4..eec30a75961dc0468fa8bacec812a37aba3c1cf0 100644
--- a/src/lib-sieve-tool/mail-raw.c
+++ b/src/lib-sieve-tool/mail-raw.c
@@ -115,7 +115,7 @@ static struct istream *mail_raw_create_stream
 	if (ret > 0 && size >= 5 && memcmp(data, "From ", 5) == 0) {
 		/* skip until the first LF */
 		i_stream_skip(input, 5);
-		while ((ret = i_stream_read_data(input, &data, &size, 0)) > 0) {
+		while ( i_stream_read_data(input, &data, &size, 0) > 0 ) {
 			for (i = 0; i < size; i++) {
 				if (data[i] == '\n')
 					break;
@@ -228,7 +228,7 @@ struct mail_raw *mail_raw_open_file
 {
 	struct mail_raw *mailr;
 	struct istream *input = NULL;
-	time_t mtime;
+	time_t mtime = (time_t)-1;
 	const char *sender = NULL;
 	
 	if ( path == NULL || strcmp(path, "-") == 0 ) {
diff --git a/src/lib-sieve/Makefile.am b/src/lib-sieve/Makefile.am
index 5d4c163676282c875de2d7136c1779a3b7fbed57..115463c9f4fd35a4cf334b079e530c48ee6387ad 100644
--- a/src/lib-sieve/Makefile.am
+++ b/src/lib-sieve/Makefile.am
@@ -67,6 +67,7 @@ plugins = \
 	$(extdir)/ihave/libsieve_ext_ihave.la \
 	$(extdir)/editheader/libsieve_ext_editheader.la \
 	$(extdir)/vnd.dovecot/debug/libsieve_ext_debug.la \
+	$(extdir)/vnd.dovecot/duplicate/libsieve_ext_duplicate.la \
 	$(unfinished_plugins)
 
 libdovecot_sieve_la_DEPENDENCIES = $(plugins)
diff --git a/src/lib-sieve/edit-mail.c b/src/lib-sieve/edit-mail.c
index 73e3c17be28979997ae2157f0b1cbb402f504251..ef4c80bc5bb22fa0d22724ad5258e25cb78175c0 100644
--- a/src/lib-sieve/edit-mail.c
+++ b/src/lib-sieve/edit-mail.c
@@ -657,21 +657,23 @@ static int edit_mail_headers_parse
 	}
 
 	/* Insert header field index items in main list */
-	if ( edmail->header_fields_appended != NULL ) {
-		if ( edmail->header_fields_appended->prev != NULL ) {
-			edmail->header_fields_appended->prev->next = head;
-			head->prev = edmail->header_fields_appended->prev;
-		}
+	if ( head != NULL && tail != NULL ) {
+		if ( edmail->header_fields_appended != NULL ) {
+			if ( edmail->header_fields_appended->prev != NULL ) {
+				edmail->header_fields_appended->prev->next = head;
+				head->prev = edmail->header_fields_appended->prev;
+			}
 
-		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;
+			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 */
diff --git a/src/lib-sieve/mcht-matches.c b/src/lib-sieve/mcht-matches.c
index 6ad8f6dc35a42434750dce2dc3a46da0aae62a25..1cee70a57b0f8a2b772d2f094cfff824921de130 100644
--- a/src/lib-sieve/mcht-matches.c
+++ b/src/lib-sieve/mcht-matches.c
@@ -111,7 +111,6 @@ static int mcht_matches_match_key
 	vp = val;                   /* Value pointer */
 	kp = key;                   /* Key pointer */
 	wp = key;                   /* Wildcard (key) pointer */
-	pvp = val;                  /* Previous value Pointer */
 
 	/* Start match values list if requested */
 	if ( (mvalues = sieve_match_values_start(mctx->runenv)) != NULL ) {
diff --git a/src/lib-sieve/plugins/body/ext-body-common.c b/src/lib-sieve/plugins/body/ext-body-common.c
index 024608800676338a123d48e2651c39b3a176f136..d28121b4bc22220181aa75ad3be5ad242d5982a9 100644
--- a/src/lib-sieve/plugins/body/ext-body-common.c
+++ b/src/lib-sieve/plugins/body/ext-body-common.c
@@ -328,6 +328,7 @@ static bool ext_body_parts_add_missing
 				}
 	
 				/* Save bodies only if we have a wanted content-type */
+				i_assert( body_part != NULL );
 				save_body = _is_wanted_content_type
 					(content_types, body_part->content_type);
 				continue;
@@ -336,9 +337,10 @@ static bool ext_body_parts_add_missing
 			/* Encountered the empty line that indicates the end of the headers and 
 			 * the start of the body
 			 */
-			if ( block.hdr->eoh )
+			if ( block.hdr->eoh ) {
+				i_assert( body_part != NULL );
 				body_part->have_body = TRUE;
-			else if ( header_part != NULL ) {
+			} else if ( header_part != NULL ) {
 				/* Save message/rfc822 header as part content */
 				if ( block.hdr->continued ) {
 					buffer_append(ctx->tmp_buffer, block.hdr->value, block.hdr->value_len);
@@ -363,6 +365,8 @@ static bool ext_body_parts_add_missing
 				block.hdr->use_full_value = TRUE;
 				continue;
 			}
+
+			i_assert( body_part != NULL );
 		
 			/* Parse the content type from the Content-type header */
 			T_BEGIN {
@@ -488,11 +492,14 @@ static bool ext_body_get_raw
 		i_stream_skip(input, hdr_size.physical_size);
 
 		/* Read raw message body */
-		while ( (ret = i_stream_read_data(input, &data, &size, 0)) > 0 ) {	
+		while ( (ret=i_stream_read_data(input, &data, &size, 0)) > 0 ) {	
 			buffer_append(buf, data, size);
 
 			i_stream_skip(input, size);
 		}
+
+		if ( ret == -1 && input->stream_errno != 0 )
+			return FALSE;
 	} else {
 		buf = ctx->raw_body;	
 	}
diff --git a/src/lib-sieve/plugins/editheader/ext-editheader-common.c b/src/lib-sieve/plugins/editheader/ext-editheader-common.c
index 52e99acfd12d42fbca0f67b22dd39ac573eb170c..a49edffca1fef274c86386614df511120a02d952 100644
--- a/src/lib-sieve/plugins/editheader/ext-editheader-common.c
+++ b/src/lib-sieve/plugins/editheader/ext-editheader-common.c
@@ -52,8 +52,7 @@ static struct ext_editheader_header *ext_editheader_config_header_find
 bool ext_editheader_load
 (const struct sieve_extension *ext, void **context)
 {
-	struct ext_editheader_config *ext_config =
-		(struct ext_editheader_config *) *context;
+	struct ext_editheader_config *ext_config;
 	struct sieve_instance *svinst = ext->svinst;
 	const char *protected;
 	size_t max_header_size;
diff --git a/src/lib-sieve/plugins/imap4flags/tag-flags.c b/src/lib-sieve/plugins/imap4flags/tag-flags.c
index 45b82e990a9808020a4dcf22e1817e2c9184f978..06b73e0e3ee753fdc4e85a56632c996f78e6dfec 100644
--- a/src/lib-sieve/plugins/imap4flags/tag-flags.c
+++ b/src/lib-sieve/plugins/imap4flags/tag-flags.c
@@ -299,6 +299,9 @@ static int seff_flags_do_read_context
 		}
 	}
 	
+	if ( ret < 0 )
+		return flag_list->exec_status;
+
 	*se_context = (void *) ctx;
 	
 	return SIEVE_EXEC_OK;
diff --git a/src/lib-sieve/plugins/include/cmd-include.c b/src/lib-sieve/plugins/include/cmd-include.c
index 23193387bb17717e4a6fd5492fe85bb0a615abea..d2741605973f85b795e39ec071be37bad6580b3a 100644
--- a/src/lib-sieve/plugins/include/cmd-include.c
+++ b/src/lib-sieve/plugins/include/cmd-include.c
@@ -279,8 +279,7 @@ static bool cmd_include_validate
 		ctx_data->script = script;
 	}
 	
-	arg = sieve_ast_arguments_detach(arg, 1);
-	
+	(void)sieve_ast_arguments_detach(arg, 1);
 	return TRUE;
 }
 
diff --git a/src/lib-sieve/plugins/notify/ext-notify-common.c b/src/lib-sieve/plugins/notify/ext-notify-common.c
index d3a9f6df373b78918329a6d22a06ec2488141cdd..e7b6f9265761bb916c5d5a9f98f985673300919c 100644
--- a/src/lib-sieve/plugins/notify/ext-notify-common.c
+++ b/src/lib-sieve/plugins/notify/ext-notify-common.c
@@ -3,6 +3,7 @@
 
 #include "lib.h"
 #include "str.h"
+#include "istream.h"
 #include "rfc822-parser.h"
 #include "message-parser.h"
 #include "message-decoder.h"
@@ -158,7 +159,7 @@ static buffer_t *cmd_notify_extract_body_text
 	struct message_block block, decoded;
 	struct istream *input;
 	bool is_text, save_body;
-	int ret;
+	int ret = 1;
 	
 	/* Return cached result if available */
 	mctx = ext_notify_get_message_context(this_ext, renv->msgctx);
@@ -171,7 +172,7 @@ static buffer_t *cmd_notify_extract_body_text
 
 	/* Get the message stream */
 	if ( mail_get_stream(renv->msgdata->mail, NULL, NULL, &input) < 0 )
-		return FALSE;
+		return NULL;
 			
 	/* Initialize body decoder */
 	decoder = message_decoder_init(FALSE);
@@ -179,7 +180,7 @@ static buffer_t *cmd_notify_extract_body_text
 	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 ) {		
+	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);
@@ -222,6 +223,9 @@ static buffer_t *cmd_notify_extract_body_text
 	(void)message_parser_deinit(&parser, &parts);
 	message_decoder_deinit(&decoder);
 	
+	if ( ret < 0 && input->stream_errno != 0 )
+		return NULL;
+
 	/* Return status */
 	return mctx->body_text;
 }
diff --git a/src/lib-sieve/plugins/variables/ext-variables-modifiers.c b/src/lib-sieve/plugins/variables/ext-variables-modifiers.c
index d37683d26307154c3b32d5f20fce35ecc57baaa7..12270429bd05002a98d8c096773b1de788c02d14 100644
--- a/src/lib-sieve/plugins/variables/ext-variables-modifiers.c
+++ b/src/lib-sieve/plugins/variables/ext-variables-modifiers.c
@@ -220,7 +220,7 @@ bool mod_upper_modify(string_t *in, string_t **result)
 	str_append_str(*result, in);
 
 	content = str_c_modifiable(*result);
-	content = str_ucase(content);
+	(void)str_ucase(content);
 	
 	return TRUE;
 }
@@ -233,7 +233,7 @@ bool mod_lower_modify(string_t *in, string_t **result)
 	str_append_str(*result, in);
 
 	content = str_c_modifiable(*result);
-	content = str_lcase(content);
+	(void)str_lcase(content);
 
 	return TRUE;
 }
diff --git a/src/lib-sieve/plugins/variables/ext-variables-namespaces.c b/src/lib-sieve/plugins/variables/ext-variables-namespaces.c
index 9a85ed4e7276be25c8c389141808bc0a1aa45eea..03684179da4626de1bdd6cc907c46a33fabb5360 100644
--- a/src/lib-sieve/plugins/variables/ext-variables-namespaces.c
+++ b/src/lib-sieve/plugins/variables/ext-variables-namespaces.c
@@ -90,7 +90,7 @@ bool ext_variables_namespace_argument_activate
 	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;
+	void *var_data = NULL;
 
 	nspc = ext_variables_namespace_create_instance
 		(this_ext, valdtr, cmd, str_c(name_element->identifier));
diff --git a/src/lib-sieve/plugins/vnd.dovecot/Makefile.am b/src/lib-sieve/plugins/vnd.dovecot/Makefile.am
index ecb5e1301a3c2979289ec5455e73090c82d69c54..2009408137963b5791aa58ec31b180fe30a2d0e0 100644
--- a/src/lib-sieve/plugins/vnd.dovecot/Makefile.am
+++ b/src/lib-sieve/plugins/vnd.dovecot/Makefile.am
@@ -1,3 +1,3 @@
-SUBDIRS = debug
+SUBDIRS = debug duplicate
 
 
diff --git a/src/lib-sieve/plugins/vnd.dovecot/duplicate/Makefile.am b/src/lib-sieve/plugins/vnd.dovecot/duplicate/Makefile.am
new file mode 100644
index 0000000000000000000000000000000000000000..d50e0c4ddfbb90b29167301a7c7846170bb18897
--- /dev/null
+++ b/src/lib-sieve/plugins/vnd.dovecot/duplicate/Makefile.am
@@ -0,0 +1,20 @@
+noinst_LTLIBRARIES = libsieve_ext_duplicate.la
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib-sieve \
+	$(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
+
diff --git a/src/lib-sieve/plugins/vnd.dovecot/duplicate/ext-duplicate-common.c b/src/lib-sieve/plugins/vnd.dovecot/duplicate/ext-duplicate-common.c
new file mode 100644
index 0000000000000000000000000000000000000000..d538ccd26d9c3a59f2cf0a8bbb84e6b141e3c6fa
--- /dev/null
+++ b/src/lib-sieve/plugins/vnd.dovecot/duplicate/ext-duplicate-common.c
@@ -0,0 +1,120 @@
+/* Copyright (c) 2002-2012 Sieve duplicate Plugin authors, see the included
+ * COPYING file.
+ */
+
+#include "lib.h"
+#include "md5.h"
+#include "ioloop.h"
+#include "str.h"
+
+#include "sieve-common.h"
+#include "sieve-settings.h"
+#include "sieve-error.h"
+#include "sieve-extensions.h"
+#include "sieve-code.h"
+#include "sieve-runtime.h"
+#include "sieve-actions.h"
+#include "sieve-result.h"
+
+#include "ext-duplicate-common.h"
+
+/*
+ * Extension configuration
+ */
+
+#define EXT_DUPLICATE_DEFAULT_PERIOD (1*24*60*60)
+ 
+struct ext_duplicate_config {
+	unsigned int period;
+};
+
+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 period;
+
+	if ( *context != NULL )
+		ext_duplicate_unload(ext);
+
+	if ( !sieve_setting_get_duration_value
+		(svinst, "sieve_duplicate_period", &period) ) {
+		period = EXT_DUPLICATE_DEFAULT_PERIOD;
+	}
+
+	config = i_new(struct ext_duplicate_config, 1);
+	config->period = 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 checking
+ */
+
+struct ext_duplicate_context {
+	unsigned int duplicate:1;
+};
+
+bool ext_duplicate_check
+(const struct sieve_runtime_env *renv, string_t *name)
+{
+	const struct sieve_extension *this_ext = renv->oprtn->ext;
+	const struct sieve_script_env *senv = renv->scriptenv;
+	struct ext_duplicate_context *rctx;
+	pool_t pool;		
+
+	/* Get context; find out whether duplicate was checked earlier */
+	rctx = (struct ext_duplicate_context *) 
+		sieve_result_extension_get_context(renv->result, this_ext); 
+
+	if ( rctx != NULL ) {
+		/* Already checked for duplicate */
+		return rctx->duplicate;
+	}
+
+	/* Create context */
+	pool = sieve_result_pool(renv->result);
+	rctx = p_new(pool, struct ext_duplicate_context, 1);
+	sieve_result_extension_set_context(renv->result, this_ext, (void *)rctx);
+
+	/* Lookup duplicate */
+	if ( sieve_action_duplicate_check_available(senv)
+		&& renv->msgdata->id != NULL ) {
+		static const char *id = "sieve duplicate";
+		struct ext_duplicate_config *ext_config =
+			(struct ext_duplicate_config *) this_ext->context;
+		unsigned char dupl_hash[MD5_RESULTLEN];
+		struct md5_context ctx;
+
+		/* Create hash */
+		md5_init(&ctx);
+		md5_update(&ctx, id, strlen(id));
+		if (name != NULL)
+			md5_update(&ctx, str_c(name), str_len(name));
+		md5_update(&ctx, renv->msgdata->id, strlen(renv->msgdata->id));
+		md5_final(&ctx, dupl_hash);
+
+		/* Check duplicate */
+		rctx->duplicate = sieve_action_duplicate_check
+			(senv, dupl_hash, sizeof(dupl_hash));
+
+		/* Create/refresh entry */
+		sieve_action_duplicate_mark
+			(senv, dupl_hash, sizeof(dupl_hash), ioloop_time + ext_config->period);
+	}
+
+	return rctx->duplicate;	
+}
+	
diff --git a/src/lib-sieve/plugins/vnd.dovecot/duplicate/ext-duplicate-common.h b/src/lib-sieve/plugins/vnd.dovecot/duplicate/ext-duplicate-common.h
new file mode 100644
index 0000000000000000000000000000000000000000..fc44bfab326a1a20989b1380a0a3a37b3271b2bd
--- /dev/null
+++ b/src/lib-sieve/plugins/vnd.dovecot/duplicate/ext-duplicate-common.h
@@ -0,0 +1,40 @@
+/* Copyright (c) 2002-2012 Sieve duplicate Plugin authors, see the included
+ * COPYING file.
+ */
+
+#ifndef __EXT_DUPLICATE_COMMON_H
+#define __EXT_DUPLICATE_COMMON_H
+
+#include "sieve-common.h"
+
+/*
+ * Extension
+ */
+
+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;
+
+/* 
+ * Tests
+ */
+
+extern const struct sieve_command_def tst_duplicate;
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def tst_duplicate_operation;
+
+/*
+ * Duplicate checking
+ */
+
+bool ext_duplicate_check
+	(const struct sieve_runtime_env *renv, string_t *name);
+
+#endif /* EXT_DUPLICATE_COMMON_H */
diff --git a/src/lib-sieve/plugins/vnd.dovecot/duplicate/ext-duplicate.c b/src/lib-sieve/plugins/vnd.dovecot/duplicate/ext-duplicate.c
new file mode 100644
index 0000000000000000000000000000000000000000..4b134c15962d08ff87192c3882a65a0daf5c244b
--- /dev/null
+++ b/src/lib-sieve/plugins/vnd.dovecot/duplicate/ext-duplicate.c
@@ -0,0 +1,53 @@
+/* Copyright (c) 2002-2012 Sieve duplicate Plugin authors, see the included
+ * COPYING file.
+ */
+
+/* Extension vnd.dovecot.duplicate
+ * -------------------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: spec-bosch-sieve-duplicate
+ * Implementation: full
+ * 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"
+
+/* 
+ * Extension 
+ */
+
+static bool ext_duplicate_validator_load
+	(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+	
+const struct sieve_extension_def duplicate_extension = { 
+	"vnd.dovecot.duplicate",
+	ext_duplicate_load,
+	ext_duplicate_unload,
+	ext_duplicate_validator_load,
+	NULL, NULL, NULL, NULL, NULL,
+	SIEVE_EXT_DEFINE_OPERATION(tst_duplicate_operation),
+	SIEVE_EXT_DEFINE_NO_OPERANDS
+};
+
+/*
+ * Validation
+ */
+
+static bool ext_duplicate_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+	/* Register duplicate test */
+	sieve_validator_register_command(valdtr, ext, &tst_duplicate);
+
+	return TRUE;
+}
diff --git a/src/lib-sieve/plugins/vnd.dovecot/duplicate/tst-duplicate.c b/src/lib-sieve/plugins/vnd.dovecot/duplicate/tst-duplicate.c
new file mode 100644
index 0000000000000000000000000000000000000000..c65688f74e3c0d0a3e039f4c9cf86a35039aa1f9
--- /dev/null
+++ b/src/lib-sieve/plugins/vnd.dovecot/duplicate/tst-duplicate.c
@@ -0,0 +1,151 @@
+/* Copyright (c) 2002-2012 Sieve duplicate Plugin authors, see the included
+ * COPYING file.
+ */
+
+#include "lib.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:
+ *   "duplicate" [<name: string>]
+ *
+ */
+
+static bool tst_duplicate_validate
+	(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool tst_duplicate_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def tst_duplicate = {
+	"duplicate",
+	SCT_TEST,
+	-1, /* We check positional arguments ourselves */
+	0, FALSE, FALSE,
+	NULL,	NULL,
+	tst_duplicate_validate,
+	NULL,
+	tst_duplicate_generate,
+	NULL,
+};
+
+/* 
+ * 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 = { 
+	"DUPLICATE", &duplicate_extension, 
+	0,
+	tst_duplicate_operation_dump,
+	tst_duplicate_operation_execute
+};
+
+/* 
+ * Validation 
+ */
+
+static bool tst_duplicate_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd) 
+{ 	
+	struct sieve_ast_argument *arg = cmd->first_positional;
+	
+	if ( arg == NULL )
+		return TRUE;
+
+	if ( !sieve_validate_positional_argument
+		(valdtr, cmd, arg, "name", 1, SAAT_STRING) ) {
+		return FALSE;
+	}
+	
+	return sieve_validator_argument_activate(valdtr, cmd, arg, FALSE);
+}
+
+/*
+ * 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;
+
+	/* Emit a placeholder when the <name> argument is missing */
+	if ( cmd->first_positional == NULL )
+		sieve_opr_omitted_emit(cgenv->sblock);
+
+	return TRUE;
+}
+
+/* 
+ * Code dump
+ */
+ 
+static bool tst_duplicate_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	sieve_code_dumpf(denv, "DUPLICATE");
+	sieve_code_descend(denv);
+	
+	return sieve_opr_string_dump_ex(denv, address, "name", "");
+}
+
+/* 
+ * Code execution
+ */
+
+static int tst_duplicate_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED)
+{	
+	string_t *name = NULL;
+	bool duplicate = FALSE;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Read rejection reason */
+	if ( (ret=sieve_opr_string_read_ex(renv, address, "name", TRUE, &name, NULL))
+		<= 0 )
+		return ret;
+
+	/*
+	 * Perform operation
+	 */
+
+	/* Trace */
+	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "duplicate test");
+	sieve_runtime_trace_descend(renv);
+
+	/* Check duplicate */
+	if ( (duplicate=ext_duplicate_check(renv, name)) ) {
+		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;
+}
+
diff --git a/src/lib-sieve/sieve-address.c b/src/lib-sieve/sieve-address.c
index 5234b53cbb86ccee7f4695d16e9b7fe328518da0..d88b9cf6363228152eb0daab421cde39210fda4a 100644
--- a/src/lib-sieve/sieve-address.c
+++ b/src/lib-sieve/sieve-address.c
@@ -348,7 +348,7 @@ static int parse_mailbox(struct sieve_message_address_parser *ctx)
 		return ret;
 	} 
 
-	if ((ret = parse_addr_spec(ctx)) < 0)
+	if (parse_addr_spec(ctx) < 0)
 		return -1;
 
 	if (*ctx->parser.data != '>') {
@@ -367,8 +367,6 @@ static bool parse_mailbox_address
 (struct sieve_message_address_parser *ctx, const unsigned char *address, 
 	unsigned int addr_size)
 {
-	int ret;
-	
 	/* Initialize parser */
 	
 	rfc822_parser_init(&ctx->parser, address, addr_size, NULL);
@@ -382,9 +380,8 @@ static bool parse_mailbox_address
 		return FALSE;
 	}
 	
-	if ((ret = parse_mailbox(ctx)) < 0) {
+	if (parse_mailbox(ctx) < 0)
 		return FALSE;
-	}
 
 	if (ctx->parser.data != ctx->parser.end) {
 		if ( *ctx->parser.data == ',' ) 
@@ -715,7 +712,7 @@ static int path_parse_domain
 			str_append_c(parser->str, *parser->data);
 			parser->data++;
 
-			if ( (ret=path_skip_white_space(parser)) <= 0 )
+			if ( path_skip_white_space(parser) <= 0 )
 				return -1;
 		}
 	}
@@ -739,10 +736,10 @@ static int path_skip_source_route(struct sieve_envelope_address_parser *parser)
 		parser->data++;
 	
 		for (;;) {
-			if ( (ret=path_skip_white_space(parser)) <= 0 )
+			if ( path_skip_white_space(parser) <= 0 )
 				return -1;	
 
-			if ( (ret=path_parse_domain(parser, TRUE)) <= 0 )
+			if ( path_parse_domain(parser, TRUE) <= 0 )
 				return -1;	
 
 			if ( (ret=path_skip_white_space(parser)) <= 0 )
@@ -753,7 +750,7 @@ static int path_skip_source_route(struct sieve_envelope_address_parser *parser)
 				break;
 			parser->data++;
 
-			if ( (ret=path_skip_white_space(parser)) <= 0 )
+			if ( path_skip_white_space(parser) <= 0 )
 				return -1;
 
 			if ( *parser->data != '@' )
@@ -835,7 +832,7 @@ static int path_parse_local_part(struct sieve_envelope_address_parser *parser)
 			str_append_c(parser->str, *parser->data);
 				parser->data++;
 	
-			if ( (ret=path_skip_white_space(parser)) <= 0 )
+			if ( path_skip_white_space(parser) <= 0 )
 				return -1;
 		}
 	}
@@ -860,7 +857,7 @@ static int path_parse_mailbox(struct sieve_envelope_address_parser *parser)
 
 	parser->data++;
 
-	if ( (ret=path_skip_white_space(parser)) <= 0 )
+	if ( path_skip_white_space(parser) <= 0 )
 		return -1;
 
 	return path_parse_domain(parser, FALSE);
@@ -881,7 +878,7 @@ static int path_parse(struct sieve_envelope_address_parser *parser)
 		parser->data++;
 		brackets = TRUE;
 
-		if ( (ret=path_skip_white_space(parser)) <= 0 ) 
+		if ( path_skip_white_space(parser) <= 0 ) 
 			return -1;
 
 		/* Null path? */
@@ -892,7 +889,7 @@ static int path_parse(struct sieve_envelope_address_parser *parser)
 	}
 
 	/*  [ A-d-l ":" ] Mailbox */
-	if ( (ret=path_skip_source_route(parser)) <= 0 )
+	if ( path_skip_source_route(parser) <= 0 )
 		return -1;
 
 	if ( (ret=path_parse_mailbox(parser)) < 0 )
diff --git a/src/lib-sieve/sieve-ast.c b/src/lib-sieve/sieve-ast.c
index a4edee2bdd09d5410530e0251caab54c5999ab16..bcacde530f4525184e0f84bc94a2f6a3f270032c 100644
--- a/src/lib-sieve/sieve-ast.c
+++ b/src/lib-sieve/sieve-ast.c
@@ -924,16 +924,18 @@ static void sieve_ast_unparse_stringlist
 			printf("  ");	
 
 		stritem = sieve_ast_strlist_first(strlist);
-		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("  ");
+		if ( stritem != NULL ) {
 			sieve_ast_unparse_string(sieve_ast_strlist_str(stritem));
-		  stritem = sieve_ast_strlist_next(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 {
diff --git a/src/lib-sieve/sieve-ast.h b/src/lib-sieve/sieve-ast.h
index 67b9ca01c06330ee63c6550567d8525d30c5bf63..0da2b984a67b4fd4127271360272c530e64518b2 100644
--- a/src/lib-sieve/sieve-ast.h
+++ b/src/lib-sieve/sieve-ast.h
@@ -340,10 +340,8 @@ struct sieve_ast_argument *sieve_ast_stringlist_join
 #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) == NULL ? SAAT_NONE : (argument)->type)
-#define sieve_ast_argument_line(argument) \
-	((argument) == NULL ? 0 : (argument)->source_line)
+#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))
diff --git a/src/lib-sieve/sieve-binary-file.c b/src/lib-sieve/sieve-binary-file.c
index f03bdd1cb8fbc6fd037200cf5e18cfb5ef1a003c..98624ec037c4348c727e26c400a9a8f3e5bf8718 100644
--- a/src/lib-sieve/sieve-binary-file.c
+++ b/src/lib-sieve/sieve-binary-file.c
@@ -284,7 +284,7 @@ int sieve_binary_save
 		if ( sbin->svinst->debug ) {
 			sieve_sys_debug(sbin->svinst, "binary save: not saving binary %s, "
 				"because it is already stored", path);
-		}		
+		}
 		return 0;
 	}
 
diff --git a/src/lib-sieve/sieve-commands.h b/src/lib-sieve/sieve-commands.h
index 8e7d38d090f6ad2ef8d4c3d715bc05eeeb5c27d1..6d73c1dff5dc483b20dbb656bf1684105a007c48 100644
--- a/src/lib-sieve/sieve-commands.h
+++ b/src/lib-sieve/sieve-commands.h
@@ -163,7 +163,7 @@ struct sieve_command {
 	( sieve_command_def_type_name((cmd)->def) )	
 
 #define sieve_commands_equal(cmd1, cmd2) \
-	( (cmd1)->def == (cmd2)->def )
+	( (cmd1) != NULL && (cmd2) != NULL && (cmd1)->def == (cmd2)->def )
 
 /* Context API */
 
diff --git a/src/lib-sieve/sieve-extensions.c b/src/lib-sieve/sieve-extensions.c
index 81fc8e167fa25607221f9e2e97a408d8e39312f0..fd5a24f1bf87445008e0e0b02b2f92458feb400c 100644
--- a/src/lib-sieve/sieve-extensions.c
+++ b/src/lib-sieve/sieve-extensions.c
@@ -108,6 +108,7 @@ extern const struct sieve_extension_def editheader_extension;
 
 /* vnd.dovecot. */
 extern const struct sieve_extension_def debug_extension;
+extern const struct sieve_extension_def duplicate_extension;
 
 /*
  * List of native extensions
@@ -150,7 +151,7 @@ const struct sieve_extension_def *sieve_extra_extensions[] = {
 	&virustest_extension, &editheader_extension,
 
 	/* vnd.dovecot. */
-	&debug_extension
+	&debug_extension, &duplicate_extension
 };
 
 const unsigned int sieve_extra_extensions_count =
diff --git a/src/lib-sieve/sieve-script-file.c b/src/lib-sieve/sieve-script-file.c
index 1ba30ef22f6f84e49ce39e807f5bfe0504b21ca6..1fc5bd5941af7f23fd9754327f5343234b424c8d 100644
--- a/src/lib-sieve/sieve-script-file.c
+++ b/src/lib-sieve/sieve-script-file.c
@@ -219,7 +219,7 @@ static int sieve_file_script_create
 		if ( success ) {
 			if ( _script->bin_dir != NULL ) {
 				binpath = sieve_binfile_from_name(name);
-				binpath =	t_strconcat(_script->bin_dir, "/", binpath, NULL);
+				binpath = t_strconcat(_script->bin_dir, "/", binpath, NULL);
 			} else {
 				binpath = sieve_binfile_from_name(basename);
 				if ( *dirpath != '\0' )
@@ -232,7 +232,7 @@ static int sieve_file_script_create
 			script->filename = p_strdup(pool, filename);
 			script->dirpath = p_strdup(pool, dirpath);
 			script->binpath = p_strdup(pool, binpath);
-			
+
 			if ( script->script.name == NULL ||
 				strcmp(script->script.name, basename) == 0 )
 				script->script.location = script->path;
@@ -345,7 +345,7 @@ static int sieve_file_script_binary_save
 {
 	struct sieve_file_script *script = (struct sieve_file_script *)_script;
 
-	if ( sieve_script_setup_bindir(_script, 0700) < 0 )
+	if ( _script->bin_dir != NULL && sieve_script_setup_bindir(_script, 0700) < 0 )
 		return -1;
 
 	return sieve_binary_save(sbin, script->binpath, update,
diff --git a/src/lib-sieve/sieve-script.c b/src/lib-sieve/sieve-script.c
index b5017d604b840a1d820d34bcf0ec5998470b8b49..32771f42fb1b55477174405751324a36d9e23e4c 100644
--- a/src/lib-sieve/sieve-script.c
+++ b/src/lib-sieve/sieve-script.c
@@ -205,7 +205,8 @@ struct sieve_script *sieve_script_init
 		sieve_critical(svinst, ehandler, NULL,
 			"failed to access sieve script", "failed to parse script location: %s",
 			parse_error);
-		*error_r = SIEVE_ERROR_TEMP_FAIL;
+		if ( error_r != NULL )
+			*error_r = SIEVE_ERROR_TEMP_FAIL;
 		return NULL;
 	}
 
@@ -261,6 +262,9 @@ struct sieve_script *sieve_script_create
 		} T_END;
 	}
 
+	if ( script_class == NULL )
+		return NULL;
+
 	script = script_class->v.alloc();
 	if ( sieve_script_init
 		(script, svinst, script_class, data, name, ehandler, error_r) == NULL ) {
diff --git a/src/lib-sieve/sieve-smtp.c b/src/lib-sieve/sieve-smtp.c
index dac8a28bce5d021fecc42cc055323397d3bc31df..7391c41c5279d943851b5b89561ee5b18d1821fd 100644
--- a/src/lib-sieve/sieve-smtp.c
+++ b/src/lib-sieve/sieve-smtp.c
@@ -29,6 +29,6 @@ bool sieve_smtp_close
     if ( senv->smtp_open == NULL || senv->smtp_close == NULL )
         return NULL;
 
-    return senv->smtp_close(senv->script_context, handle);
+    return senv->smtp_close(senv, handle);
 }
 
diff --git a/src/lib-sieve/sieve-validator.c b/src/lib-sieve/sieve-validator.c
index 55fece31a967a0c9c360728fc9726b43a27b18c7..9647751fd666f3ca5d503b54a0b3c5bea9cd2162 100644
--- a/src/lib-sieve/sieve-validator.c
+++ b/src/lib-sieve/sieve-validator.c
@@ -818,6 +818,8 @@ bool sieve_validate_positional_argument
 	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) ) 
@@ -891,7 +893,7 @@ static bool sieve_validate_command_arguments
 	arg = sieve_ast_argument_first(cmd->ast_node);
 		
 	/* Visit tagged and optional arguments */
-	while ( sieve_ast_argument_type(arg) == SAAT_TAG ) {
+	while ( arg != NULL && sieve_ast_argument_type(arg) == SAAT_TAG ) {
 		struct sieve_ast_argument *parg;
 		void *arg_data = NULL; 
 		struct sieve_tag_registration *tag_reg = 
@@ -1180,8 +1182,6 @@ static bool sieve_validate_command_context
 
 		/* Identifier = "" when the command was previously marked as unknown */
 		if ( *(cmd_def->identifier) != '\0' ) {
-			struct sieve_command *cmd;
-
 			if ( (cmd_def->type == SCT_COMMAND && ast_type == SAT_TEST)
 				|| (cmd_def->type == SCT_TEST && ast_type == SAT_COMMAND) ) {
 				sieve_validator_error(
@@ -1192,7 +1192,7 @@ static bool sieve_validate_command_context
 			 	return FALSE;
 			} 
 			 
-			cmd_node->command = cmd = 
+			cmd_node->command =
 				sieve_command_create(cmd_node, cmd_reg->ext, cmd_def, cmd_reg); 
 		} else {
 			return FALSE;
@@ -1216,7 +1216,7 @@ 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->command;
+	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;
 	
diff --git a/src/lib-sieve/sieve.c b/src/lib-sieve/sieve.c
index b6c2f24e424d0e6bba62bf4e53286a0d535faea6..cfb3e7d3ea12ed37a0d8102f875707bf1d706aef 100644
--- a/src/lib-sieve/sieve.c
+++ b/src/lib-sieve/sieve.c
@@ -6,6 +6,7 @@
 #include "istream.h"
 #include "buffer.h"
 #include "eacces-error.h"
+#include "home-expand.h"
 
 #include "sieve-limits.h"
 #include "sieve-settings.h"
@@ -406,6 +407,9 @@ 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);
 }
 
@@ -712,6 +716,26 @@ struct sieve_directory *sieve_directory_open
 	if ( error_r != NULL )
 		*error_r = SIEVE_ERROR_NONE;
 
+	if ( (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_sys_error(svinst,
+				"sieve dir path %s is relative to home directory, "
+				"but home directory is not available.", path);
+			if ( error_r != NULL )
+				*error_r = SIEVE_ERROR_TEMP_FAIL;
+			return NULL;
+		}
+	}
+
 	/* Specified path can either be a regular file or a directory */
 	if ( stat(path, &st) != 0 ) {
 		switch ( errno ) {
diff --git a/src/lib-sievestorage/sieve-storage-quota.c b/src/lib-sievestorage/sieve-storage-quota.c
index f081ed51b4acad5b493566c2c900290badce46be..0989b504d474322cb7f3f4ba743321644981550a 100644
--- a/src/lib-sievestorage/sieve-storage-quota.c
+++ b/src/lib-sievestorage/sieve-storage-quota.c
@@ -123,11 +123,10 @@ int sieve_storage_quota_havespace
 		if ( storage->max_storage > 0 ) { 
 			const char *path;
 			struct stat st;
-			int ret;
 
 			path = t_strconcat(storage->dir, "/", dp->d_name, NULL);
 		
-			if ( (ret=stat(path, &st)) < 0 ) {
+			if ( stat(path, &st) < 0 ) {
 				i_warning
 					("sieve-storage: quota: stat(%s) failed: %m", path);
 				continue;
diff --git a/src/managesieve-login/client-authenticate.c b/src/managesieve-login/client-authenticate.c
index c2c7e3ad60d34a9300385e64cb11a9bf8d9faf6a..e53914015c1e44339cb4b7424bc21d37822766c8 100644
--- a/src/managesieve-login/client-authenticate.c
+++ b/src/managesieve-login/client-authenticate.c
@@ -221,7 +221,7 @@ static int managesieve_client_auth_read_response
 	if ( ret == 0 ) return 0;
 
 	if ( msieve_client->auth_response_input->stream_errno != 0 ) {
-		if ( msieve_client->auth_response_input->stream_errno == EPROTO ) {
+		if ( msieve_client->auth_response_input->stream_errno == EIO ) {
 			error = managesieve_parser_get_error(msieve_client->parser, &fatal);
 			if (error != NULL ) {
 				if (fatal) {
diff --git a/src/managesieve-login/managesieve-login-settings-plugin.c b/src/managesieve-login/managesieve-login-settings-plugin.c
index 24dc0c632cbf4c5b1eec767343d092d0d2a58528..aab26820275863de80a695697a07afebbca567e5 100644
--- a/src/managesieve-login/managesieve-login-settings-plugin.c
+++ b/src/managesieve-login/managesieve-login-settings-plugin.c
@@ -105,7 +105,7 @@ static void capability_parse(const char *cap_string)
 static bool capability_dump(void)
 {
 	char buf[4096];
-	int fd[2], status;
+	int fd[2], status = 0;
 	ssize_t ret;
 	unsigned int pos;
 	pid_t pid;
diff --git a/src/managesieve/cmd-putscript.c b/src/managesieve/cmd-putscript.c
index 10145972ea9554435d90e2169a6c0467145e8d5a..4218eda322aea63dc1958f58f6b17f00e329200b 100644
--- a/src/managesieve/cmd-putscript.c
+++ b/src/managesieve/cmd-putscript.c
@@ -371,7 +371,7 @@ static bool cmd_putscript_continue_script(struct client_command_context *cmd)
 		bool all_written = FALSE;
 
 		if ( ctx->script_size == 0 ) {
-			if ( ctx->input->stream_errno == EPROTO ) {
+			if ( ctx->input->stream_errno == EIO ) {
 				bool fatal;
 				const char *parse_error;
 
diff --git a/src/plugins/lda-sieve/lda-sieve-plugin.c b/src/plugins/lda-sieve/lda-sieve-plugin.c
index dd41984a145fc19578f624a3b3222508761acd85..de43a9a31078babe78dfd960f2bd715629ca2819 100644
--- a/src/plugins/lda-sieve/lda-sieve-plugin.c
+++ b/src/plugins/lda-sieve/lda-sieve-plugin.c
@@ -502,10 +502,8 @@ static int lda_sieve_multiscript_execute
 
 		/* Open */
 
-		if ( (sbin=lda_sieve_open(srctx, script, cpflags, &error)) == NULL ) {
-			ret = ( error == SIEVE_ERROR_NOT_FOUND ? 0 : -1 );
+		if ( (sbin=lda_sieve_open(srctx, script, cpflags, &error)) == NULL )
 			break;
-		}
 
 		/* Execute */
 
@@ -525,10 +523,8 @@ static int lda_sieve_multiscript_execute
 				/* Recompile */
 
 				sbin=lda_sieve_recompile(srctx, script, cpflags, &error);
-				if ( sbin	== NULL ) {
-					ret = ( error == SIEVE_ERROR_NOT_FOUND ? 0 : -1 );
+				if ( sbin == NULL )
 					break;
-				}
 
 				/* Execute again */
 
@@ -564,7 +560,9 @@ static int lda_sieve_deliver_mail
 	struct sieve_exec_status estatus;
 	struct sieve_error_handler *master_ehandler;
 	const char *user_location, *default_location, *sieve_before, *sieve_after;
+	const char *setting_name;
 	ARRAY_TYPE(sieve_scripts) script_sequence;
+	unsigned int after_index;
 	bool debug = mdctx->dest_user->mail_debug;
 	enum sieve_error error;
 	int ret = 0;
@@ -653,21 +651,27 @@ static int lda_sieve_deliver_mail
 
 		t_array_init(&script_sequence, 16);
 
-		sieve_before = mail_user_plugin_getenv(mdctx->dest_user, "sieve_before");
-		if ( sieve_before != NULL && *sieve_before != '\0' ) {
-			if ( lda_sieve_multiscript_get_scripts(svinst, "sieve_before",
-				sieve_before, master_ehandler, &script_sequence) == 0 && debug ) {
-				sieve_sys_debug(svinst, "sieve_before location not found: %s",
-					sieve_before);
+		i = 2;
+		setting_name = "sieve_before";
+		sieve_before = mail_user_plugin_getenv(mdctx->dest_user, setting_name);
+		while ( sieve_before != NULL && *sieve_before != '\0' ) {
+			if ( lda_sieve_multiscript_get_scripts
+				(svinst, setting_name, sieve_before, master_ehandler,
+					&script_sequence) == 0 && debug ) {
+				sieve_sys_debug(svinst, "%s location not found: %s",
+					setting_name, sieve_before);
 			}
+			setting_name = t_strdup_printf("sieve_before%u", i++);
+			sieve_before = mail_user_plugin_getenv(mdctx->dest_user, setting_name);
+		}
 
-			if ( debug ) {
-				scripts = array_get(&script_sequence, &count);
-				for ( i = 0; i < count; i ++ ) {
-					sieve_sys_debug(svinst, "executed before user's Sieve script(%d): %s",
-						i+1, sieve_script_location(scripts[i]));
-				}				
-			}
+		if ( 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]));
+			}				
 		}
 
 		if ( srctx.main_script != NULL ) {
@@ -680,23 +684,27 @@ static int lda_sieve_deliver_mail
 			}
 		}
 
-		sieve_after = mail_user_plugin_getenv(mdctx->dest_user, "sieve_after");
-		if ( sieve_after != NULL && *sieve_after != '\0' ) {
-			unsigned int after_index = array_count(&script_sequence);
+		after_index = array_count(&script_sequence);
 
-			if ( lda_sieve_multiscript_get_scripts(svinst, "sieve_after",
+		i = 2;
+		setting_name = "sieve_after";
+		sieve_after = mail_user_plugin_getenv(mdctx->dest_user, setting_name);
+		while ( sieve_after != NULL && *sieve_after != '\0' ) {
+			if ( lda_sieve_multiscript_get_scripts(svinst, setting_name,
 				sieve_after, master_ehandler, &script_sequence) == 0 && debug ) {
-				sieve_sys_debug(svinst, "sieve_after location not found: %s",
-					sieve_after);
-			}
-
-			if ( 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]));
-				}				
+				sieve_sys_debug(svinst, "%s location not found: %s",
+					setting_name, sieve_after);
 			}
+			setting_name = t_strdup_printf("sieve_after%u", i++);
+			sieve_after = mail_user_plugin_getenv(mdctx->dest_user, setting_name);
+		}
+	
+		if ( 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]));
+			}				
 		}
 
 		srctx.scripts =
diff --git a/src/sieve-tools/sieve-dump.c b/src/sieve-tools/sieve-dump.c
index 2825c5e707296f99a626a07dca372a84e6d0b47d..2d6e8c24224e57599328f5ef941670874ec17946 100644
--- a/src/sieve-tools/sieve-dump.c
+++ b/src/sieve-tools/sieve-dump.c
@@ -48,7 +48,7 @@ int main(int argc, char **argv)
 
 	sieve_tool = sieve_tool_init("sieve-dump", &argc, &argv, "hP:x:", FALSE);
 		
-	binfile = outfile = NULL;
+	outfile = NULL;
 
 	while ((c = sieve_tool_getopt(sieve_tool)) > 0) {
 		switch (c) {
diff --git a/src/sieve-tools/sieve-filter.c b/src/sieve-tools/sieve-filter.c
index 32c14bd92d30ae502b8ccbbbd01b3a14af002785..16a07334ad98fa01001e03d7c0d5e2b053614a18 100644
--- a/src/sieve-tools/sieve-filter.c
+++ b/src/sieve-tools/sieve-filter.c
@@ -368,8 +368,7 @@ int main(int argc, char **argv)
 	t_array_init(&scriptfiles, 16);
 
 	/* Parse arguments */
-	scriptfile =  NULL;
-	src_mailbox = dst_mailbox = move_mailbox = NULL;
+	dst_mailbox = move_mailbox = NULL;
 	force_compile = execute = source_write = verbose = FALSE;
 	while ((c = sieve_tool_getopt(sieve_tool)) > 0) {
 		switch (c) {
diff --git a/src/sieve-tools/sieve-test.c b/src/sieve-tools/sieve-test.c
index b69ccd4018cd6716a899be222c5544bbd32421dc..da08d83ff92a568fe27eecf6de1b98ced3dd3eac 100644
--- a/src/sieve-tools/sieve-test.c
+++ b/src/sieve-tools/sieve-test.c
@@ -127,8 +127,8 @@ int main(int argc, char **argv)
 	t_array_init(&scriptfiles, 16);
 
 	/* Parse arguments */
-	scriptfile = recipient = final_recipient = sender = mailbox = dumpfile =
-		tracefile = mailfile = mailloc = NULL;
+	recipient = final_recipient = sender = mailbox = dumpfile =
+		tracefile = mailloc = NULL;
 	memset(&tr_config, 0, sizeof(tr_config));
 	tr_config.level = SIEVE_TRLVL_ACTIONS;
 	while ((c = sieve_tool_getopt(sieve_tool)) > 0) {
@@ -269,7 +269,7 @@ int main(int argc, char **argv)
 		
 		/* Compose script environment */
 		memset(&scriptenv, 0, sizeof(scriptenv));
-		scriptenv.default_mailbox = "INBOX";
+		scriptenv.default_mailbox = mailbox;
 		scriptenv.user = sieve_tool_get_mail_user(sieve_tool);
 		scriptenv.postmaster_address = "postmaster@example.com";
 		scriptenv.smtp_open = sieve_smtp_open;
diff --git a/src/sieve-tools/sievec.c b/src/sieve-tools/sievec.c
index 8f0e38cd4a2c802be169a93a1f09bd076002a5fb..59bd756acbe848053fa6ad806cc1a8aaf86cef94 100644
--- a/src/sieve-tools/sievec.c
+++ b/src/sieve-tools/sievec.c
@@ -52,7 +52,7 @@ int main(int argc, char **argv)
 
 	sieve_tool = sieve_tool_init("sievec", &argc, &argv, "DdP:x:u:", FALSE);
 
-	scriptfile = outfile = NULL;
+	outfile = NULL;
 	while ((c = sieve_tool_getopt(sieve_tool)) > 0) {
 		switch (c) {
 		case 'd':
diff --git a/src/testsuite/cmd-test-message.c b/src/testsuite/cmd-test-message.c
index 4cd4b66fd8c12a2018c84e0c330d53f738432d1b..59f7104239ff629cda2409b8001c168b6856783c 100644
--- a/src/testsuite/cmd-test-message.c
+++ b/src/testsuite/cmd-test-message.c
@@ -486,7 +486,6 @@ static int cmd_test_message_print_operation_execute
 	struct istream *input;
 	const unsigned char *data;
 	size_t size;
-	int ret;
 
 	if (mail_get_stream(mail, NULL, NULL, &input) < 0) {
 		sieve_runtime_error(renv, NULL,
@@ -497,7 +496,7 @@ static int cmd_test_message_print_operation_execute
 	printf("\n--MESSAGE: \n");
 
 	/* Pipe the message to the outgoing SMTP transport */
-	while ((ret=i_stream_read_data(input, &data, &size, 0)) > 0) {
+	while (i_stream_read_data(input, &data, &size, 0) > 0) {
 		ssize_t wret;
 
 		if ( (wret=write(1, data, size)) <= 0 )
diff --git a/src/testsuite/testsuite.c b/src/testsuite/testsuite.c
index 9590116612cb63fc218477b3b33207b477c300e0..0d68c6910ab6477d90ec45884717b09c98c5d741 100644
--- a/src/testsuite/testsuite.c
+++ b/src/testsuite/testsuite.c
@@ -95,7 +95,7 @@ int main(int argc, char **argv)
 		("testsuite", &argc, &argv, "d:t:T:EDP:", TRUE);
 
 	/* Parse arguments */
-	scriptfile = dumpfile = tracefile = NULL;
+	dumpfile = tracefile = NULL;
 	memset(&tr_config, 0, sizeof(tr_config));
 	tr_config.level = SIEVE_TRLVL_ACTIONS;
 	while ((c = sieve_tool_getopt(sieve_tool)) > 0) {
diff --git a/tests/extensions/vnd.dovecot/duplicate/errors.svtest b/tests/extensions/vnd.dovecot/duplicate/errors.svtest
new file mode 100644
index 0000000000000000000000000000000000000000..62d7374d48c94abb63e379f3803089a4b4ff2229
--- /dev/null
+++ b/tests/extensions/vnd.dovecot/duplicate/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" "4" {
+                test_fail "wrong number of errors reported";
+        }
+}
diff --git a/tests/extensions/vnd.dovecot/duplicate/errors/syntax.sieve b/tests/extensions/vnd.dovecot/duplicate/errors/syntax.sieve
new file mode 100644
index 0000000000000000000000000000000000000000..5ecfb65f435fee8e3410a0544385e709da488b65
--- /dev/null
+++ b/tests/extensions/vnd.dovecot/duplicate/errors/syntax.sieve
@@ -0,0 +1,16 @@
+require "vnd.dovecot.duplicate";
+
+# Used as a command
+duplicate;
+
+# Used with string argument (not an error)
+if duplicate "frop" { }
+
+# Used with numer argument
+if duplicate 23423 { }
+
+# Used with numer argument
+if duplicate ["frop"] { }
+
+
+
diff --git a/tests/extensions/vnd.dovecot/duplicate/execute.svtest b/tests/extensions/vnd.dovecot/duplicate/execute.svtest
new file mode 100644
index 0000000000000000000000000000000000000000..00e9cf1bc1bda7afdacb79bb012b02416fdef9da
--- /dev/null
+++ b/tests/extensions/vnd.dovecot/duplicate/execute.svtest
@@ -0,0 +1,12 @@
+require "vnd.dovecot.testsuite";
+require "vnd.dovecot.duplicate";
+
+test "Run" {
+	if duplicate {
+		test_fail "test erroneously reported a duplicate";
+	}
+
+	if duplicate "name" {
+		test_fail "test with name erroneously reported a duplicate";
+	}
+}