From f5505c87e0b338a579691675cd80ce3617027247 Mon Sep 17 00:00:00 2001 From: Stephan Bosch <stephan@rename-it.nl> Date: Tue, 4 Mar 2014 22:28:56 +0100 Subject: [PATCH] lib-sieve: Upgraded "vnd.dovecot.duplicate" to "duplicate". Backwards compatibility is provided for vnd.dovecot.duplicate. Still need to fix the constraint that it must not track duplicates from failed Sieve executions. --- Makefile.am | 5 +- README | 4 +- configure.ac | 2 +- doc/extensions/duplicate.txt | 47 ++ doc/extensions/vnd.dovecot.duplicate.txt | 58 -- .../draft-ietf-appsawg-sieve-duplicate-03.txt | 728 ++++++++++++++++++ src/lib-sieve/Makefile.am | 2 +- src/lib-sieve/plugins/Makefile.am | 1 + .../{vnd.dovecot => }/duplicate/Makefile.am | 2 +- .../duplicate/ext-duplicate-common.c | 71 +- .../duplicate/ext-duplicate-common.h | 3 +- .../plugins/duplicate/ext-duplicate.c | 107 +++ .../duplicate/tst-duplicate.c | 110 ++- src/lib-sieve/plugins/vnd.dovecot/Makefile.am | 3 +- .../plugins/vnd.dovecot/debug/cmd-debug-log.c | 2 +- .../vnd.dovecot/debug/ext-debug-common.h | 2 +- .../plugins/vnd.dovecot/debug/ext-debug.c | 2 +- .../vnd.dovecot/duplicate/ext-duplicate.c | 50 -- src/lib-sieve/sieve-extensions.c | 18 +- src/plugins/sieve-extprograms/cmd-execute.c | 2 +- src/plugins/sieve-extprograms/cmd-filter.c | 2 +- src/plugins/sieve-extprograms/cmd-pipe.c | 2 +- src/plugins/sieve-extprograms/ext-execute.c | 2 +- src/plugins/sieve-extprograms/ext-filter.c | 2 +- src/plugins/sieve-extprograms/ext-pipe.c | 4 +- .../sieve-extprograms-common.c | 4 +- .../sieve-extprograms-common.h | 6 +- .../sieve-extprograms-plugin.c | 6 +- tests/extensions/duplicate/errors.svtest | 54 ++ .../duplicate/errors/conflict-vnd.sieve | 4 + .../duplicate/errors/conflict.sieve | 4 + .../errors/syntax-vnd.sieve} | 0 .../extensions/duplicate/errors/syntax.sieve | 54 ++ .../execute-vnd.svtest} | 0 tests/extensions/duplicate/execute.svtest | 41 + .../vnd.dovecot/duplicate/errors.svtest | 18 - 36 files changed, 1210 insertions(+), 212 deletions(-) create mode 100644 doc/extensions/duplicate.txt delete mode 100644 doc/extensions/vnd.dovecot.duplicate.txt create mode 100644 doc/rfc/draft-ietf-appsawg-sieve-duplicate-03.txt rename src/lib-sieve/plugins/{vnd.dovecot => }/duplicate/Makefile.am (92%) rename src/lib-sieve/plugins/{vnd.dovecot => }/duplicate/ext-duplicate-common.c (77%) rename src/lib-sieve/plugins/{vnd.dovecot => }/duplicate/ext-duplicate-common.h (85%) create mode 100644 src/lib-sieve/plugins/duplicate/ext-duplicate.c rename src/lib-sieve/plugins/{vnd.dovecot => }/duplicate/tst-duplicate.c (73%) delete mode 100644 src/lib-sieve/plugins/vnd.dovecot/duplicate/ext-duplicate.c create mode 100644 tests/extensions/duplicate/errors.svtest create mode 100644 tests/extensions/duplicate/errors/conflict-vnd.sieve create mode 100644 tests/extensions/duplicate/errors/conflict.sieve rename tests/extensions/{vnd.dovecot/duplicate/errors/syntax.sieve => duplicate/errors/syntax-vnd.sieve} (100%) create mode 100644 tests/extensions/duplicate/errors/syntax.sieve rename tests/extensions/{vnd.dovecot/duplicate/execute.svtest => duplicate/execute-vnd.svtest} (100%) create mode 100644 tests/extensions/duplicate/execute.svtest delete mode 100644 tests/extensions/vnd.dovecot/duplicate/errors.svtest diff --git a/Makefile.am b/Makefile.am index 91429aec5..015403d68 100644 --- a/Makefile.am +++ b/Makefile.am @@ -136,9 +136,10 @@ test_cases = \ tests/extensions/editheader/utf8.svtest \ tests/extensions/editheader/protected.svtest \ tests/extensions/editheader/errors.svtest \ + tests/extensions/duplicate/errors.svtest \ + tests/extensions/duplicate/execute.svtest \ + tests/extensions/duplicate/execute-vnd.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 d4e68b452..1b826c534 100644 --- a/README +++ b/README @@ -120,6 +120,7 @@ following list outlines the implementation status of each supported extension: mailbox (RFC 5490; Section 3): fully supported (v0.1.10+), but ACL permissions are not verified for mailboxexists. include (RFC 6609): fully supported (v0.4.0+) + duplicate (draft v03): fully supported (v0.4.3+). regex (draft v08; not latest version): almost fully supported, but UTF-8 is not supported. @@ -135,9 +136,6 @@ following list outlines the implementation status of each supported extension: vnd.dovecot.debug (v0.3.0+): Allows logging debug messages - vnd.dovecot.duplicate (v0.3.1+): - Allows detecting duplicate message deliveries based on message ID and - other criteria. vnd.dovecot.pipe (v0.4.0+; sieve_extprograms plugin): Implements piping messages to a pre-defined set of external programs vnd.dovecot.filter (v0.4.0+; sieve_extprograms plugin): diff --git a/configure.ac b/configure.ac index 2adf4d843..b3761c665 100644 --- a/configure.ac +++ b/configure.ac @@ -122,9 +122,9 @@ 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/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/extensions/duplicate.txt b/doc/extensions/duplicate.txt new file mode 100644 index 000000000..e98e089db --- /dev/null +++ b/doc/extensions/duplicate.txt @@ -0,0 +1,47 @@ +Duplicate Extension + +Relevant specifications +======================= + + doc/rfc/draft-ietf-appsawg-sieve-duplicate-03.txt + +Description +=========== + +The duplicate extension augments the Sieve filtering implementation with a test +to verify whether the evaluated string value was seen before in an earlier +execution of the Sieve script. The main application for this new test is +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/rfc/draft-ietf-appsawg-sieve-duplicate-03.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 internet draft, but +for backwards compatibility the original extension is still supported. + +Configuration +============= + +The "duplicate" extension is not enabled by default. + +The following configuration settings are used: + +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_extensions = +vnd.dovecot.duplicate + + sieve_duplicate_default_period = 1h + sieve_duplicate_max_period = 1d +}d diff --git a/doc/extensions/vnd.dovecot.duplicate.txt b/doc/extensions/vnd.dovecot.duplicate.txt deleted file mode 100644 index 59ff7599e..000000000 --- a/doc/extensions/vnd.dovecot.duplicate.txt +++ /dev/null @@ -1,58 +0,0 @@ -Vnd.dovecot.duplicate Extension - -Relevant specifications -======================= - - doc/rfc/spec-bosch-sieve-duplicate.txt - -Description -=========== - -The vnd.dovecot.duplicate extension augments the Sieve filtering implementation -with a test to verify whether the evaluated string value was seen before in an -earlier execution of the Sieve script. The main application for this new test is -detecting and handling duplicate message deliveries, e.g. as 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 by adding it to the `sieve_extensions' or the -`sieve_global_extensions' setting. - -The following configuration settings are used: - -sieve_duplicate_default_period = 12h -sieve_duplicate_max_period = 2d - 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_extensions = +vnd.dovecot.duplicate - - sieve_duplicate_default_period = 1h - sieve_duplicate_max_period = 1d -} diff --git a/doc/rfc/draft-ietf-appsawg-sieve-duplicate-03.txt b/doc/rfc/draft-ietf-appsawg-sieve-duplicate-03.txt new file mode 100644 index 000000000..1100934c7 --- /dev/null +++ b/doc/rfc/draft-ietf-appsawg-sieve-duplicate-03.txt @@ -0,0 +1,728 @@ + + + +APPSAWG S. Bosch +Internet-Draft March 3, 2014 +Intended status: Standards Track +Expires: September 4, 2014 + + + Sieve Email Filtering: Detecting Duplicate Deliveries + draft-ietf-appsawg-sieve-duplicate-03 + +Abstract + + This document defines a new test command "duplicate" for the "Sieve" + email filtering 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 or + other parts of the message. + +Status of this Memo + + This Internet-Draft is submitted in full conformance with the + provisions of BCP 78 and BCP 79. + + Internet-Drafts are working documents of the Internet Engineering + Task Force (IETF). Note that other groups may also distribute + working documents as Internet-Drafts. The list of current Internet- + Drafts is at http://datatracker.ietf.org/drafts/current/. + + Internet-Drafts are draft documents valid for a maximum of six months + and may be updated, replaced, or obsoleted by other documents at any + time. It is inappropriate to use Internet-Drafts as reference + material or to cite them other than as "work in progress." + + This Internet-Draft will expire on September 4, 2014. + +Copyright Notice + + Copyright (c) 2014 IETF Trust and the persons identified as the + document authors. All rights reserved. + + This document is subject to BCP 78 and the IETF Trust's Legal + Provisions Relating to IETF Documents + (http://trustee.ietf.org/license-info) in effect on the date of + publication of this document. Please review these documents + carefully, as they describe your rights and restrictions with respect + + + +Bosch Expires September 4, 2014 [Page 1] + +Internet-Draft Sieve: Detecting Duplicate Deliveries March 2014 + + + to this document. Code Components extracted from this document must + include Simplified BSD License text as described in Section 4.e of + the Trust Legal Provisions and are provided without warranty as + described in the Simplified BSD License. + + +Table of Contents + + 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 3 + 2. Conventions Used in This Document . . . . . . . . . . . . . . 3 + 3. Test "duplicate" . . . . . . . . . . . . . . . . . . . . . . . 3 + 3.1. Interaction with Other Sieve Extensions . . . . . . . . . 8 + 4. Sieve Capability Strings . . . . . . . . . . . . . . . . . . . 8 + 5. Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 + 5.1. Example 1 . . . . . . . . . . . . . . . . . . . . . . . . 8 + 5.2. Example 2 . . . . . . . . . . . . . . . . . . . . . . . . 8 + 5.3. Example 3 . . . . . . . . . . . . . . . . . . . . . . . . 9 + 5.4. Example 4 . . . . . . . . . . . . . . . . . . . . . . . . 10 + 6. Security Considerations . . . . . . . . . . . . . . . . . . . 11 + 7. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 11 + 8. Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . 11 + 9. References . . . . . . . . . . . . . . . . . . . . . . . . . . 12 + 9.1. Normative References . . . . . . . . . . . . . . . . . . . 12 + 9.2. Informative References . . . . . . . . . . . . . . . . . . 12 + Author's Address . . . . . . . . . . . . . . . . . . . . . . . . . 13 + + + + + + + + + + + + + + + + + + + + + + + + + + +Bosch Expires September 4, 2014 [Page 2] + +Internet-Draft Sieve: Detecting Duplicate Deliveries March 2014 + + +1. Introduction + + This document specifies an extension to the Sieve filtering language + defined by RFC 5228 [SIEVE]. It adds a test to track whether or not + a text string was seen before by the delivery agent in an earlier + execution of the Sieve script. This can be used to detect and handle + duplicate message deliveries. + + Duplicate deliveries are a common side-effect of being subscribed to + a mailing list. For example, if a member of the list decides to + reply to both the user and the mailing list itself, the user will + often get one copy of the message directly and another through the + mailing list. Also, if someone cross-posts over several mailing + lists to which the user is subscribed, the user will likely receive a + copy from each of those lists. In another scenario, the user has + several redirected mail addresses all pointing to his main mail + account. If one of the user's contacts sends the message to more + than one of those addresses, the user will likely receive more than a + single copy. Using the "duplicate" extension, users have the means + to detect and handle such duplicates, e.g. by discarding them, + marking them as "seen", or putting them in a special folder. + + Duplicate messages are normally detected using the Message-ID header + field, which is required to be unique for each message. However, the + "duplicate" test is flexible enough to use different criteria for + defining what makes a message a duplicate, for example on the subject + line or parts of the message body. Other applications of this new + test command are also possible, as long as the tracked unique value + is a string. + + +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" [":handle" <handle: string>] + [":header" <header-name: string> / + ":uniqueid" <value: string>] + [":seconds" <timeout: number>] [":last"] + + + +Bosch Expires September 4, 2014 [Page 3] + +Internet-Draft Sieve: Detecting Duplicate Deliveries March 2014 + + + In its basic form, the "duplicate" test keeps track of which messages + were seen before by this test during an earlier Sieve execution. + Messages are by default identified by their message ID as contained + in the Message-ID header. The "duplicate" test evaluates to "true" + when the message was seen before and it evaluates to "false" when it + was not. + + As a side-effect, the "duplicate" test adds the message ID to an + internal duplicate tracking list once the Sieve execution finishes + successfully. This way, the same test will evaluate to "true" during + the next Sieve execution. Note that this side-effect is performed + only when the "duplicate" test is actually evaluated. If the + "duplicate" test is nested in a control structure or it is not the + first item of an "allof" or "anyof" test list, its evaluation depends + on the result of preceding tests, which may produce unexpected + results. + + Implementations MUST only update the internal duplicate tracking list + when the Sieve script execution finishes successfully. If failing + script executions add the message ID to the duplicate tracking list, + all "duplicate" tests in the Sieve script would erroneously yield + "true" for the next delivery attempt of the same message, which can + -- depending on the action taken for a duplicate -- easily lead to + discarding the message without further notice. + + However, deferring the definitive modification of the tracking list + to the end of a successful Sieve script execution is not without + problems. It can cause a race condition when a duplicate message is + delivered in parallel before the tracking list is updated. This way, + a duplicate message could be missed by the "duplicate" test. More + complex implementations could use a locking mechanism to prevent this + problem. But, irrespective of what implementation is chosen, + situations in which the "duplicate" test erroneously yields "true" + MUST be prevented. + + The "duplicate" test MUST only check for duplicates amongst message + ID values encountered in previous executions of the Sieve script; it + MUST NOT consider ID values encountered earlier in the current Sieve + script execution as potential duplicates. This means that all + "duplicate" tests in a Sieve script execution, including those + located in scripts included using the "include" [INCLUDE] extension, + MUST always yield the same result if the arguments are identical. + + Implementations SHOULD limit the number of entries in the duplicate + tracking list. When limiting the number of entries, implementations + SHOULD discard the oldest ones first. + + Also, implementations SHOULD let entries in the tracking list expire + + + +Bosch Expires September 4, 2014 [Page 4] + +Internet-Draft Sieve: Detecting Duplicate Deliveries March 2014 + + + after a short period of time. The user can explicitly control the + length of this expiration time by means of the ":seconds" argument, + which accepts an integer value specifying the timeout value in + seconds. If the ":seconds" argument is omitted, an appropriate + default value MUST be used. A default expiration time of around 7 + days is usually appropriate. Sites SHOULD impose a maximum limit on + the expiration time. If that limit is exceeded by the ":seconds" + argument, the maximum value MUST silently be substituted; exceeding + the limit MUST NOT produce an error. If the ":seconds" argument is + zero, the "duplicate" test MUST yield "false" unconditionally. + + When the ":last" argument is omitted, the expiration time for entries + in the duplicate tracking list MUST be measured relative to the + moment at which the entry was first created; i.e., at the end of the + successful script execution during which "duplicate" test returned + "false" for a message with that particular message ID value. This + means that subsequent duplicate messages have no influence on the + time at which the entry in the duplicate tracking list finally + expires. + + In contrast, when the ":last" argument is specified, the expiration + time MUST be measured relative to the last script execution during + which the "duplicate" test was used to check the entry's message ID + value. This effectively means that the entry in the duplicate + tracking will not expire while duplicate messages with the + corresponding message ID keep being delivered within intervals + smaller than the expiration time. + + By default, the content of the message's Message-ID header field is + used as the unique ID for duplicate tracking. For more complex + applications, the "duplicate" test can also be used to detect + duplicate deliveries based on other message text. Then, the tracked + unique ID can be an arbitrary string value extracted from the + message. By adding the ":header" argument with a message header + field name, the content of the specified header field can be used as + the tracked unique ID instead of the default Message-ID header. + Alternatively, the tracked unique ID can be specified explicitly + using the ":uniqueid" argument. The ":header" and ":uniqueid" + arguments are mutually exclusive and specifying both for a single + "duplicate" test command MUST trigger an error. + + The syntax rules for the header name parameter of the ":header" + argument are specified in Section 2.4.2.2 of RFC 5228 [SIEVE]. Note + that implementations MUST NOT trigger an error for an invalid header + name. Instead, the "duplicate" test MUST yield "false" + unconditionally in this case. The parameter of the ":uniqueid" + argument can be any string. + + + + +Bosch Expires September 4, 2014 [Page 5] + +Internet-Draft Sieve: Detecting Duplicate Deliveries March 2014 + + + The Messsage-ID header field is assumed to be globally unique as + required in Section 3.6.4 of RFC 5322 [IMAIL]. In practice, this + assumption may not aways prove to be true. The "duplicate" tests + does not deal with this situation implicitly, which means that false + duplicates may be detected in this case. However, the user can + address such situations by specifying an alternative means of message + identification using the ":header" or the ":uniqueid" argument. + + If the tracked unique ID value is extracted directly from a message + header field, i.e., when the ":uniqueid" argument is not used, the + following operations MUST be performed before the actual duplicate + verification: + + o Unfold the header line as described in [IMAIL] Section 2.2.3. (see + also Section 2.4.2.2 of RFC 5228 [SIEVE]). + + o If possible, convert the header value to Unicode, encoded as UTF-8 + (see Section 2.7.2 of RFC 5228 [SIEVE]). If conversion is not + possible, the value is left unchanged. + + o Trim leading and trailing whitespace from the header value (see + Section 2.2 of RFC 5228 [SIEVE]). + + Note that these rules also apply to the Message-ID header field used + by the basic "duplicate" test without a ":header" or ":uniqueid" + argument. When the ":uniqueid" argument is used, such normalization + concerns are the responsibility of the user. + + If the header field specified using the ":header" argument exists + multiple times in the message, only the first occurrence MUST be used + for duplicate tracking. If the specified header field is not present + in the message, the "duplicate" test MUST yield "false" + unconditionally. In that case the duplicate tracking list is left + unmodified by this test, since no unique ID value is available. The + same rules apply with respect to the Message-ID header field for the + basic "duplicate" test without a ":header" or ":uniqueid" argument, + since that header field could also be missing or occur multiple + times. + + The string parameter of the ":uniqueid" argument can be composed from + arbitrary text extracted from the message using the "variables" + [VARIABLES] extension. To extract text from the message body, the + "foreverypart" and "extracttext" [SIEVE-MIME] extensions need to be + used as well. This provides the user with detailed control over what + identifies a message as a duplicate. + + The tracked unique ID value MUST be matched case-sensitively, + irrespective of whether it originates from a header or is specified + + + +Bosch Expires September 4, 2014 [Page 6] + +Internet-Draft Sieve: Detecting Duplicate Deliveries March 2014 + + + explicitly using the ":uniqueid" argument. To achieve case- + insensitive behavior, the "set" command added by the "variables" + [VARIABLES] extension can be used in combination with the ":uniqueid" + argument to normalize the tracked unique ID value to upper or lower + case. + + The "duplicate" test MUST track a unique ID value independent of its + source. This means that it does not matter whether values are + obtained from the message ID header, from an arbitrary header + specified using the ":header" argument or explicitly from the + ":uniqueid" argument. For example, the following three examples are + equivalent and match the same entry in the duplicate tracking list: + + require "duplicate"; + if duplicate { + discard; + } + + + require "duplicate"; + if duplicate :header "message-id" { + discard; + } + + + require ["duplicate", "variables"]; + if header :matches "message-id" "*" { + if duplicate :uniqueid "${0}" { + discard; + } + } + + The ":handle" argument can be used to override this default behavior. + The ":handle" argument separates a "duplicate" test from other + duplicate tests with a different or omitted ":handle" argument. + Using the ":handle" argument, unrelated "duplicate" tests can be + prevented from interfering with each other: a message is only + recognized as a duplicate when the tracked unique ID was seen before + in an earlier script execution by a "duplicate" test with the same + ":handle" argument. + + NOTE: The necessary mechanism to track duplicate messages is very + similar to the mechanism that is needed for tracking duplicate + responses for the "vacation" [VACATION] action. One way to implement + the necessary mechanism for the "duplicate" test is therefore to + store a hash of the tracked unique ID and, if provided, the ":handle" + argument. + + + + +Bosch Expires September 4, 2014 [Page 7] + +Internet-Draft Sieve: Detecting Duplicate Deliveries March 2014 + + +3.1. Interaction with Other Sieve Extensions + + The "duplicate" test does not support either the "index" + [DATE-INDEX], or "mime" [SIEVE-MIME] extensions directly, meaning + that none of the ":index", ":mime" or associated arguments are added + to the "duplicate" test when these extensions are active. The + ":uniqueid" argument can be used in combination with the "variables" + [VARIABLES] extension to achieve the same result indirectly. + + Normally, Sieve scripts are executed at final delivery. However, + with the "imapsieve" [IMAPSIEVE] extension, Sieve scripts are invoked + when the IMAP [IMAP] server performs operations on the message store, + e.g. when messages are uploaded, flagged, or moved to another + location. The "duplicate" test is devised for use at final delivery + and the semantics in the "imapsieve" context are left undefined. + Therefore it is NOT RECOMMENDED to allow the "duplicate" test to be + used in the context of "imapsieve". + + +4. Sieve Capability Strings + + A Sieve implementation that defines the "duplicate" test command will + advertise the capability string "duplicate". + + +5. Examples + +5.1. Example 1 + + In this basic example, message duplicates are detected by tracking + the Message-ID header. Duplicate deliveries are stored in a special + folder contained in the user's Trash folder. If the folder does not + exist, it is created automatically using the "mailbox" [MAILBOX] + extension. 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 ["duplicate", "fileinto", "mailbox"]; + + if duplicate { + fileinto :create "Trash/Duplicate"; + } + +5.2. Example 2 + + This example shows a more complex use of the "duplicate" test. The + user gets network alerts from a set of remote automated monitoring + systems. Several notifications can be received about the same event + + + +Bosch Expires September 4, 2014 [Page 8] + +Internet-Draft Sieve: Detecting Duplicate Deliveries March 2014 + + + from different monitoring systems. The Message-ID of these messages + is different, because these are all distinct messages from different + senders. To avoid being notified more than a single time about the + same event the user writes the following script: + + require ["duplicate", "variables", "imap4flags", + "fileinto"]; + + if header :matches "subject" "ALERT: *" { + if duplicate :seconds 60 :uniqueid "${1}" { + setflag "\\seen"; + } + fileinto "Alerts"; + } + + The subjects of the notification message are structured with a + predictable pattern which includes a description of the event. In + the script above, the "duplicate" test is used to detect duplicate + alert events. The message subject is matched against a pattern and + the event description is extracted using the "variables" [VARIABLES] + extension. If a message with that event in the subject was received + before, but more than a minute ago, it is not detected as a duplicate + due to the specified ":seconds" argument. In the the event of a + duplicate, the message is marked as "seen" using the "imap4flags" + [IMAP4FLAGS] extension. All alert messages are put into the "Alerts" + mailbox irrespective of whether those messages are duplicates or not. + +5.3. Example 3 + + This example shows how the "duplicate" test can be used to limit the + frequency of notifications sent using the "enotify" [NOTIFY] + extension. Consider the following scenario: a mail user receives + XMPP notifications [NOTIFY-XMPP] about new mail through Sieve, but + sometimes a single contact sends many messages in a short period of + time. Now the user wants to prevent being notified of all of those + messages. The user wants to be notified about messages from each + person at most once per 30 minutes and writes the following script: + + require ["variables", "envelope", "enotify", "duplicate"]; + + if envelope :matches "from" "*" { set "sender" "${1}"; } + if header :matches "subject" "*" { set "subject" "${1}"; } + + if not duplicate :seconds 1800 :uniqueid "${sender}" + { + notify :message "[SIEVE] ${sender}: ${subject}" + "xmpp:user@im.example.com"; + } + + + +Bosch Expires September 4, 2014 [Page 9] + +Internet-Draft Sieve: Detecting Duplicate Deliveries March 2014 + + + The example shown above uses the message envelope sender rather than + the Message-ID header as the unique ID for duplicate tracking. + + The example can be extended to allow more messages from the same + sender in close succession as long as the discussed subject is + different. This can be achieved as follows: + + require ["variables", "envelope", "enotify", "duplicate"]; + + if envelope :matches "from" "*" { set "sender" "${1}"; } + if header :matches "subject" "*" { set "subject" "${1}"; } + + # account for 'Re:' prefix + if string :comparator "i;ascii-casemap" + :matches "${subject}" "Re:*" + { + set "subject" "${1}"; + } + if not duplicate :seconds 1800 + :uniqueid "${sender} ${subject}" + { + notify :message "[SIEVE] ${sender}: ${subject}" + "xmpp:user@im.example.com"; + } + + This uses a combination of the message envelope sender and the + subject of the message as the unique ID for duplicate tracking. + +5.4. Example 4 + + For this example, the mail user uses the "duplicate" test for two + separate applications: for discarding duplicate events from a + notification system and to mark certain follow-up messages in a + software support mailing as "seen" using the "imap4flags" + [IMAP4FLAGS] extension. + + The two "duplicate" tests in the following example each use a + different header to identify messages. However, these "X-Event-ID" + and "X-Ticket-ID headers can have similar values in this case (e.g. + both based on a time stamp), meaning that one "duplicate" test can + erroneously detect duplicates based on ID values tracked by the + other. Therefore, the user wants to prevent the second "duplicate" + test from matching ID values tracked by the first "duplicate" test + and vice versa. This is achieved by specifying different ":handle" + arguments for these tests. + + + + + + +Bosch Expires September 4, 2014 [Page 10] + +Internet-Draft Sieve: Detecting Duplicate Deliveries March 2014 + + + require ["duplicate", "imap4flags"]; + + if duplicate :header "X-Event-ID" :handle "notifier" { + discard; + } + if allof ( + duplicate :header "X-Ticket-ID" :handle "support", + address "to" "support@example.com", + header :contains "subject" "fileserver") + { + setflag "\\seen"; + } + + +6. Security Considerations + + A flood of unique messages could cause the list of tracked message ID + values to grow indefinitely. Implementations SHOULD apply limits on + the number and lifespan of entries in that list. + + +7. IANA Considerations + + The following template specifies the IANA registration of the Sieve + extension specified in this document: + + To: iana@iana.org + Subject: Registration of new Sieve extension + + Capability name: duplicate + Description: Adds test 'duplicate' that can be used to test + whether a particular message is a duplicate; + i.e., whether a copy of it was seen before by the + delivery agent that is executing the Sieve + script. + RFC number: this RFC + Contact address: Sieve mailing list <sieve@ietf.org> + + This information should be added to the list of sieve extensions + given on http://www.iana.org/assignments/sieve-extensions. + + +8. Acknowledgements + + Thanks to Cyrus Daboo, Arnt Gulbrandsen, Tony Hansen, Kristin Hubner, + Alexey Melnikov, Subramanian Moonesamy, Tom Petch, Hector Santos, + Robert Sparks, and Aaron Stone for reviews and suggestions. With + special thanks to Ned Freed for his guidance and support. + + + +Bosch Expires September 4, 2014 [Page 11] + +Internet-Draft Sieve: Detecting Duplicate Deliveries March 2014 + + +9. References + +9.1. Normative References + + [DATE-INDEX] + Freed, N., "Sieve Email Filtering: Date and Index + Extensions", RFC 5260, July 2008. + + [IMAIL] Resnick, P., Ed., "Internet Message Format", RFC 5322, + October 2008. + + [IMAPSIEVE] + Leiba, B., "Support for Internet Message Access Protocol + (IMAP) Events in Sieve", RFC 6785, November 2012. + + [INCLUDE] Daboo, C. and A. Stone, "Sieve Email Filtering: Include + Extension", RFC 6609, May 2012. + + [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. + + [SIEVE-MIME] + Hansen, T. and C. Daboo, "Sieve Email Filtering: MIME Part + Tests, Iteration, Extraction, Replacement, and Enclosure", + RFC 5703, October 2009. + + [VARIABLES] + Homme, K., "Sieve Email Filtering: Variables Extension", + RFC 5229, January 2008. + +9.2. Informative References + + [IMAP] Crispin, M., "INTERNET MESSAGE ACCESS PROTOCOL - VERSION + 4rev1", RFC 3501, March 2003. + + [IMAP4FLAGS] + Melnikov, A., "Sieve Email Filtering: Imap4flags + Extension", RFC 5232, January 2008. + + [MAILBOX] Melnikov, A., "The Sieve Mail-Filtering Language -- + Extensions for Checking Mailbox Status and Accessing + Mailbox Metadata", RFC 5490, March 2009. + + [NOTIFY] Melnikov, A., Leiba, B., Segmuller, W., and T. Martin, + + + +Bosch Expires September 4, 2014 [Page 12] + +Internet-Draft Sieve: Detecting Duplicate Deliveries March 2014 + + + "Sieve Email Filtering: Extension for Notifications", + RFC 5435, January 2009. + + [NOTIFY-XMPP] + Saint-Andre, P. and A. Melnikov, "Sieve Notification + Mechanism: Extensible Messaging and Presence Protocol + (XMPP)", RFC 5437, January 2009. + + [VACATION] + Showalter, T. and N. Freed, "Sieve Email Filtering: + Vacation Extension", RFC 5230, January 2008. + + +Author's Address + + Stephan Bosch + Enschede + NL + + Email: stephan@rename-it.nl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Bosch Expires September 4, 2014 [Page 13] + diff --git a/src/lib-sieve/Makefile.am b/src/lib-sieve/Makefile.am index 0f8f15619..d6007ca91 100644 --- a/src/lib-sieve/Makefile.am +++ b/src/lib-sieve/Makefile.am @@ -68,8 +68,8 @@ plugins = \ $(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)/vnd.dovecot/debug/libsieve_ext_debug.la \ - $(extdir)/vnd.dovecot/duplicate/libsieve_ext_duplicate.la \ $(unfinished_plugins) libdovecot_sieve_la_DEPENDENCIES = \ diff --git a/src/lib-sieve/plugins/Makefile.am b/src/lib-sieve/plugins/Makefile.am index a26c4a820..915ada271 100644 --- a/src/lib-sieve/plugins/Makefile.am +++ b/src/lib-sieve/plugins/Makefile.am @@ -21,6 +21,7 @@ SUBDIRS = \ spamvirustest \ ihave \ editheader \ + duplicate \ vnd.dovecot \ $(UNFINISHED) diff --git a/src/lib-sieve/plugins/vnd.dovecot/duplicate/Makefile.am b/src/lib-sieve/plugins/duplicate/Makefile.am similarity index 92% rename from src/lib-sieve/plugins/vnd.dovecot/duplicate/Makefile.am rename to src/lib-sieve/plugins/duplicate/Makefile.am index 3d72ae223..7469b987f 100644 --- a/src/lib-sieve/plugins/vnd.dovecot/duplicate/Makefile.am +++ b/src/lib-sieve/plugins/duplicate/Makefile.am @@ -1,7 +1,7 @@ noinst_LTLIBRARIES = libsieve_ext_duplicate.la AM_CPPFLAGS = \ - -I$(srcdir)/../../.. \ + -I$(srcdir)/../.. \ $(LIBDOVECOT_INCLUDE) tests = \ diff --git a/src/lib-sieve/plugins/vnd.dovecot/duplicate/ext-duplicate-common.c b/src/lib-sieve/plugins/duplicate/ext-duplicate-common.c similarity index 77% rename from src/lib-sieve/plugins/vnd.dovecot/duplicate/ext-duplicate-common.c rename to src/lib-sieve/plugins/duplicate/ext-duplicate-common.c index 602d5ed2e..bdc0dcb2f 100644 --- a/src/lib-sieve/plugins/vnd.dovecot/duplicate/ext-duplicate-common.c +++ b/src/lib-sieve/plugins/duplicate/ext-duplicate-common.c @@ -72,6 +72,7 @@ struct act_duplicate_mark_data { const char *handle; unsigned int period; unsigned char hash[MD5_RESULTLEN]; + unsigned int last:1; }; static void act_duplicate_mark_print @@ -97,15 +98,18 @@ static void act_duplicate_mark_print { 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 duplicate with handle: %s", - str_sanitize(data->handle, 128)); + sieve_result_action_printf(rpenv, "track%s duplicate with handle: %s", + last, str_sanitize(data->handle, 128)); } else { - sieve_result_action_printf(rpenv, "track duplicate"); + sieve_result_action_printf(rpenv, "track%s duplicate", last); } } +// FIXME: at commit phase the sieve script is still not guaranteed to finish +// successfully. We need a new final stage in Sieve result execution. static int act_duplicate_mark_commit (const struct sieve_action *action, const struct sieve_action_exec_env *aenv, @@ -131,6 +135,7 @@ static int act_duplicate_mark_commit struct ext_duplicate_handle { const char *handle; + unsigned int last:1; unsigned int duplicate:1; }; @@ -141,18 +146,40 @@ struct ext_duplicate_context { unsigned int 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) + 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; - static const char *id = "sieve duplicate"; struct act_duplicate_mark_data *act; - struct md5_context ctx; if ( !sieve_action_duplicate_check_available(senv) || value == NULL ) return 0; @@ -175,7 +202,8 @@ int ext_duplicate_check } 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 ) + if ( strcmp(record->handle, str_c(handle)) == 0 && + record->last == last ) return ( record->duplicate ? 1 : 0 ); } } @@ -186,29 +214,31 @@ int ext_duplicate_check if (handle != NULL) act->handle = p_strdup(result_pool, str_c(handle)); act->period = period; + act->last = last; /* Create hash */ - md5_init(&ctx); - md5_update(&ctx, id, strlen(id)); - if (handle != NULL) { - md5_update(&ctx, "h-", 2); - md5_update(&ctx, str_c(handle), str_len(handle)); - } else { - md5_update(&ctx, "default", 7); - } - md5_update(&ctx, value, value_len); - md5_final(&ctx, act->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 ( sieve_result_add_action - (renv, NULL, &act_duplicate_mark, NULL, (void *) act, 0, FALSE) < 0 ) - return -1; + 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 ) { @@ -223,6 +253,7 @@ int ext_duplicate_check 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; } diff --git a/src/lib-sieve/plugins/vnd.dovecot/duplicate/ext-duplicate-common.h b/src/lib-sieve/plugins/duplicate/ext-duplicate-common.h similarity index 85% rename from src/lib-sieve/plugins/vnd.dovecot/duplicate/ext-duplicate-common.h rename to src/lib-sieve/plugins/duplicate/ext-duplicate-common.h index 2ca6ce4b4..af1883de0 100644 --- a/src/lib-sieve/plugins/vnd.dovecot/duplicate/ext-duplicate-common.h +++ b/src/lib-sieve/plugins/duplicate/ext-duplicate-common.h @@ -21,6 +21,7 @@ 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 @@ -40,6 +41,6 @@ extern const struct sieve_operation_def tst_duplicate_operation; int ext_duplicate_check (const struct sieve_runtime_env *renv, string_t *handle, - const char *value, size_t value_len, sieve_number_t period); + const char *value, size_t value_len, sieve_number_t period, bool last); #endif /* EXT_DUPLICATE_COMMON_H */ diff --git a/src/lib-sieve/plugins/duplicate/ext-duplicate.c b/src/lib-sieve/plugins/duplicate/ext-duplicate.c new file mode 100644 index 000000000..0e440849f --- /dev/null +++ b/src/lib-sieve/plugins/duplicate/ext-duplicate.c @@ -0,0 +1,107 @@ +/* Copyright (c) 2002-2014 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_extension_validate + (const struct sieve_extension *ext, struct sieve_validator *valdtr, + void *context, struct sieve_ast_argument *require_arg); + +const struct sieve_validator_extension duplicate_validator_extension = { + &vnd_duplicate_extension, + ext_duplicate_validator_extension_validate, + NULL +}; + +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_extension_validate +(const struct sieve_extension *ext, struct sieve_validator *valdtr, + void *context ATTR_UNUSED, struct sieve_ast_argument *require_arg) +{ + const struct sieve_extension *ext_dupl; + + if ( (ext_dupl=sieve_extension_get_by_name + (ext->svinst, "duplicate")) != NULL ) { + + /* Check for conflict with duplicate extension */ + if ( sieve_validator_extension_loaded(valdtr, ext_dupl) ) { + 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; +} + diff --git a/src/lib-sieve/plugins/vnd.dovecot/duplicate/tst-duplicate.c b/src/lib-sieve/plugins/duplicate/tst-duplicate.c similarity index 73% rename from src/lib-sieve/plugins/vnd.dovecot/duplicate/tst-duplicate.c rename to src/lib-sieve/plugins/duplicate/tst-duplicate.c index 2e4de5a57..921f17108 100644 --- a/src/lib-sieve/plugins/vnd.dovecot/duplicate/tst-duplicate.c +++ b/src/lib-sieve/plugins/duplicate/tst-duplicate.c @@ -17,10 +17,10 @@ /* Duplicate test * * Syntax: - * Usage: "duplicate" [":seconds" <timeout: number>] + * Usage: "duplicate" [":handle" <handle: string>] * [":header" <header-name: string> / - * ":value" <value: string>] - * [":handle" <handle: string>] + * ":uniqueid" <value: string>] + * [":seconds" <timeout: number>] [":last"] */ static bool tst_duplicate_registered @@ -64,8 +64,15 @@ static const struct sieve_argument_def duplicate_header_tag = { NULL, NULL, NULL }; +static const struct sieve_argument_def duplicate_uniqueid_tag = { + "uniqueid", + NULL, + tst_duplicate_validate_string_tag, + NULL, NULL, NULL +}; + static const struct sieve_argument_def duplicate_value_tag = { - "value", + "value", /* vnd.dovecot.duplicate (deprecated) */ NULL, tst_duplicate_validate_string_tag, NULL, NULL, NULL @@ -78,13 +85,19 @@ static const struct sieve_argument_def duplicate_handle_tag = { NULL, NULL, NULL }; +static const struct sieve_argument_def duplicate_last_tag = { + "last", + NULL, NULL, NULL, NULL, NULL +}; + /* Codes for optional arguments */ enum tst_duplicate_optional { OPT_END, OPT_SECONDS, OPT_HEADER, - OPT_VALUE, + OPT_UNIQUEID, + OPT_LAST, OPT_HANDLE }; @@ -143,7 +156,6 @@ static bool tst_duplicate_validate_number_tag /* Skip parameter */ *arg = sieve_ast_argument_next(*arg); - return TRUE; } @@ -151,6 +163,7 @@ 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 */ @@ -166,19 +179,31 @@ static bool tst_duplicate_validate_string_tag return FALSE; } - if ((bool)cmd->data == TRUE) { + if ( (bool)cmd->data ) { sieve_argument_validate_error(valdtr, *arg, - "conflicting :header and :value arguments specified " - "for the duplicate test"); - return TRUE; + "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; + 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) ) { - cmd->data = (void*)TRUE; + i_assert(sieve_extension_is(ext, vnd_duplicate_extension)); + cmd->data = (void *)TRUE; + } else { + i_unreached(); } /* Skip parameter */ @@ -197,9 +222,16 @@ static bool tst_duplicate_registered sieve_validator_register_tag (valdtr, cmd_reg, ext, &duplicate_seconds_tag, OPT_SECONDS); sieve_validator_register_tag - (valdtr, cmd_reg, ext, &duplicate_header_tag, OPT_HEADER); + (valdtr, cmd_reg, ext, &duplicate_last_tag, OPT_LAST); sieve_validator_register_tag - (valdtr, cmd_reg, ext, &duplicate_value_tag, OPT_VALUE); + (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; @@ -227,6 +259,7 @@ static bool tst_duplicate_generate 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"); @@ -247,11 +280,17 @@ static bool tst_duplicate_operation_dump 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_VALUE: - opok = sieve_opr_string_dump(denv, address, "value"); + 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"); @@ -277,11 +316,11 @@ static int tst_duplicate_operation_execute const struct ext_duplicate_config *config = (const struct ext_duplicate_config *) ext->context; int opt_code = 0; - string_t *handle = NULL, *header = NULL, *value = NULL; + 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 duplicate = FALSE; + bool last = FALSE, duplicate = FALSE; int ret; /* @@ -302,11 +341,18 @@ static int tst_duplicate_operation_execute 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_VALUE: - ret = sieve_opr_string_read(renv, address, "value", &value); + 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); @@ -328,22 +374,28 @@ static int tst_duplicate_operation_execute sieve_runtime_trace_descend(renv); /* Get value */ - if (header != NULL) { - if (mail_get_first_header(renv->msgdata->mail, str_c(header), &val) > 0) + if ( uniqueid != NULL ) { + val = str_c(uniqueid); + val_len = str_len(uniqueid); + } else { + if ( header == NULL ) { + ret = mail_get_first_header_utf8 + (renv->msgdata->mail, "Message-ID", &val); + } else { + ret = mail_get_first_header_utf8 + (renv->msgdata->mail, str_c(header), &val); + } + + if ( ret > 0 ) val_len = strlen(val); - } else if (value != NULL) { - val = str_c(value); - val_len = str_len(value); - } else if (renv->msgdata->id != NULL) { - val = renv->msgdata->id; - val_len = strlen(renv->msgdata->id); } /* Check duplicate */ if (val == NULL) { duplicate = FALSE; } else { - if ((ret=ext_duplicate_check(renv, handle, val, val_len, seconds)) < 0) + if ((ret=ext_duplicate_check + (renv, handle, val, val_len, seconds, last)) < 0) return SIEVE_EXEC_FAILURE; duplicate = ( ret > 0 ); } diff --git a/src/lib-sieve/plugins/vnd.dovecot/Makefile.am b/src/lib-sieve/plugins/vnd.dovecot/Makefile.am index 200940813..3c46867ff 100644 --- a/src/lib-sieve/plugins/vnd.dovecot/Makefile.am +++ b/src/lib-sieve/plugins/vnd.dovecot/Makefile.am @@ -1,3 +1,2 @@ -SUBDIRS = debug duplicate - +SUBDIRS = debug diff --git a/src/lib-sieve/plugins/vnd.dovecot/debug/cmd-debug-log.c b/src/lib-sieve/plugins/vnd.dovecot/debug/cmd-debug-log.c index 4229cf192..585aa9bd6 100644 --- a/src/lib-sieve/plugins/vnd.dovecot/debug/cmd-debug-log.c +++ b/src/lib-sieve/plugins/vnd.dovecot/debug/cmd-debug-log.c @@ -50,7 +50,7 @@ static int cmd_debug_log_operation_execute const struct sieve_operation_def debug_log_operation = { "debug_log", - &debug_extension, + &vnd_debug_extension, 0, cmd_debug_log_operation_dump, cmd_debug_log_operation_execute diff --git a/src/lib-sieve/plugins/vnd.dovecot/debug/ext-debug-common.h b/src/lib-sieve/plugins/vnd.dovecot/debug/ext-debug-common.h index 0d69de04c..5e7690ed8 100644 --- a/src/lib-sieve/plugins/vnd.dovecot/debug/ext-debug-common.h +++ b/src/lib-sieve/plugins/vnd.dovecot/debug/ext-debug-common.h @@ -8,7 +8,7 @@ * Extensions */ -extern const struct sieve_extension_def debug_extension; +extern const struct sieve_extension_def vnd_debug_extension; /* * Commands diff --git a/src/lib-sieve/plugins/vnd.dovecot/debug/ext-debug.c b/src/lib-sieve/plugins/vnd.dovecot/debug/ext-debug.c index 0af87771a..6d2eb26bf 100644 --- a/src/lib-sieve/plugins/vnd.dovecot/debug/ext-debug.c +++ b/src/lib-sieve/plugins/vnd.dovecot/debug/ext-debug.c @@ -39,7 +39,7 @@ static bool ext_debug_interpreter_load const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED); -const struct sieve_extension_def debug_extension = { +const struct sieve_extension_def vnd_debug_extension = { .name = "vnd.dovecot.debug", .validator_load = ext_debug_validator_load, .interpreter_load = ext_debug_interpreter_load, diff --git a/src/lib-sieve/plugins/vnd.dovecot/duplicate/ext-duplicate.c b/src/lib-sieve/plugins/vnd.dovecot/duplicate/ext-duplicate.c deleted file mode 100644 index acbc22f73..000000000 --- a/src/lib-sieve/plugins/vnd.dovecot/duplicate/ext-duplicate.c +++ /dev/null @@ -1,50 +0,0 @@ -/* Copyright (c) 2002-2014 Pigeonhole authors, see the included COPYING file - */ - -/* Extension vnd.dovecot.duplicate - * ------------------------------- - * - * Authors: Stephan Bosch - * Specification: vendor-defined; 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 = { - .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_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/sieve-extensions.c b/src/lib-sieve/sieve-extensions.c index eebb8cb44..c1aaf9a6d 100644 --- a/src/lib-sieve/sieve-extensions.c +++ b/src/lib-sieve/sieve-extensions.c @@ -99,12 +99,11 @@ extern const struct sieve_extension_def spamtestplus_extension; extern const struct sieve_extension_def virustest_extension; extern const struct sieve_extension_def ihave_extension; extern const struct sieve_extension_def editheader_extension; -extern const struct sieve_extension_def mboxmetadata_extension; -extern const struct sieve_extension_def servermetadata_extension; +extern const struct sieve_extension_def duplicate_extension; /* vnd.dovecot. */ -extern const struct sieve_extension_def debug_extension; -extern const struct sieve_extension_def duplicate_extension; +extern const struct sieve_extension_def vnd_debug_extension; +extern const struct sieve_extension_def vnd_duplicate_extension; /* * List of native extensions @@ -131,7 +130,7 @@ const struct sieve_extension_def *sieve_core_extensions[] = { &relational_extension, ®ex_extension, &imap4flags_extension, ©_extension, &include_extension, &body_extension, &variables_extension, &enotify_extension, &environment_extension, - &mailbox_extension, &date_extension, &ihave_extension, + &mailbox_extension, &date_extension, &ihave_extension, &duplicate_extension }; const unsigned int sieve_core_extensions_count = @@ -147,7 +146,7 @@ const struct sieve_extension_def *sieve_extra_extensions[] = { &virustest_extension, &editheader_extension, /* vnd.dovecot. */ - &debug_extension, &duplicate_extension + &vnd_debug_extension }; const unsigned int sieve_extra_extensions_count = @@ -162,7 +161,8 @@ extern const struct sieve_extension_def notify_extension; const struct sieve_extension_def *sieve_deprecated_extensions[] = { &imapflags_extension, - ¬ify_extension + ¬ify_extension, + &vnd_duplicate_extension }; const unsigned int sieve_deprecated_extensions_count = @@ -175,6 +175,8 @@ const unsigned int sieve_deprecated_extensions_count = #ifdef HAVE_SIEVE_UNFINISHED extern const struct sieve_extension_def ereject_extension; +extern const struct sieve_extension_def mboxmetadata_extension; +extern const struct sieve_extension_def servermetadata_extension; const struct sieve_extension_def *sieve_unfinished_extensions[] = { &ereject_extension, &mboxmetadata_extension, &servermetadata_extension @@ -696,7 +698,7 @@ const struct sieve_extension *sieve_get_address_part_extension void sieve_enable_debug_extension(struct sieve_instance *svinst) { - (void) sieve_extension_register(svinst, &debug_extension, TRUE); + (void) sieve_extension_register(svinst, &vnd_debug_extension, TRUE); } /* diff --git a/src/plugins/sieve-extprograms/cmd-execute.c b/src/plugins/sieve-extprograms/cmd-execute.c index a4f346e80..a15a26cad 100644 --- a/src/plugins/sieve-extprograms/cmd-execute.c +++ b/src/plugins/sieve-extprograms/cmd-execute.c @@ -105,7 +105,7 @@ static int cmd_execute_operation_execute (const struct sieve_runtime_env *renv, sieve_size_t *address); const struct sieve_operation_def cmd_execute_operation = { - "EXECUTE", &execute_extension, + "EXECUTE", &vnd_execute_extension, 0, cmd_execute_operation_dump, cmd_execute_operation_execute diff --git a/src/plugins/sieve-extprograms/cmd-filter.c b/src/plugins/sieve-extprograms/cmd-filter.c index 5b7382e94..e1484db9d 100644 --- a/src/plugins/sieve-extprograms/cmd-filter.c +++ b/src/plugins/sieve-extprograms/cmd-filter.c @@ -62,7 +62,7 @@ static int cmd_filter_operation_execute (const struct sieve_runtime_env *renv, sieve_size_t *address); const struct sieve_operation_def cmd_filter_operation = { - "FILTER", &filter_extension, + "FILTER", &vnd_filter_extension, 0, cmd_filter_operation_dump, cmd_filter_operation_execute diff --git a/src/plugins/sieve-extprograms/cmd-pipe.c b/src/plugins/sieve-extprograms/cmd-pipe.c index a73ec25b4..11bb1439a 100644 --- a/src/plugins/sieve-extprograms/cmd-pipe.c +++ b/src/plugins/sieve-extprograms/cmd-pipe.c @@ -67,7 +67,7 @@ static int cmd_pipe_operation_execute (const struct sieve_runtime_env *renv, sieve_size_t *address); const struct sieve_operation_def cmd_pipe_operation = { - "PIPE", &pipe_extension, 0, + "PIPE", &vnd_pipe_extension, 0, cmd_pipe_operation_dump, cmd_pipe_operation_execute }; diff --git a/src/plugins/sieve-extprograms/ext-execute.c b/src/plugins/sieve-extprograms/ext-execute.c index 855502b17..1ea2999c2 100644 --- a/src/plugins/sieve-extprograms/ext-execute.c +++ b/src/plugins/sieve-extprograms/ext-execute.c @@ -33,7 +33,7 @@ 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 execute_extension = { +const struct sieve_extension_def vnd_execute_extension = { .name = "vnd.dovecot.execute", .load = ext_execute_load, .unload = ext_execute_unload, diff --git a/src/plugins/sieve-extprograms/ext-filter.c b/src/plugins/sieve-extprograms/ext-filter.c index c1a4212ba..886cc4346 100644 --- a/src/plugins/sieve-extprograms/ext-filter.c +++ b/src/plugins/sieve-extprograms/ext-filter.c @@ -33,7 +33,7 @@ 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 filter_extension = { +const struct sieve_extension_def vnd_filter_extension = { .name = "vnd.dovecot.filter", .load = ext_filter_load, .unload = ext_filter_unload, diff --git a/src/plugins/sieve-extprograms/ext-pipe.c b/src/plugins/sieve-extprograms/ext-pipe.c index 47eebdfb1..cc1de8527 100644 --- a/src/plugins/sieve-extprograms/ext-pipe.c +++ b/src/plugins/sieve-extprograms/ext-pipe.c @@ -33,7 +33,7 @@ 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 pipe_extension = { +const struct sieve_extension_def vnd_pipe_extension = { .name = "vnd.dovecot.pipe", .load = ext_pipe_load, .unload = ext_pipe_unload, @@ -75,7 +75,7 @@ static bool ext_pipe_validator_extension_validate void *context, struct sieve_ast_argument *require_arg); const struct sieve_validator_extension pipe_validator_extension = { - &pipe_extension, + &vnd_pipe_extension, ext_pipe_validator_extension_validate, NULL }; diff --git a/src/plugins/sieve-extprograms/sieve-extprograms-common.c b/src/plugins/sieve-extprograms/sieve-extprograms-common.c index 36b19faca..6912640e7 100644 --- a/src/plugins/sieve-extprograms/sieve-extprograms-common.c +++ b/src/plugins/sieve-extprograms/sieve-extprograms-common.c @@ -96,9 +96,9 @@ struct sieve_extprograms_config *sieve_extprograms_config_init } } - if ( sieve_extension_is(ext, pipe_extension) ) + if ( sieve_extension_is(ext, vnd_pipe_extension) ) ext_config->copy_ext = sieve_ext_copy_get_extension(ext->svinst); - if ( sieve_extension_is(ext, execute_extension) ) + if ( sieve_extension_is(ext, vnd_execute_extension) ) ext_config->var_ext = sieve_ext_variables_get_extension(ext->svinst); return ext_config; } diff --git a/src/plugins/sieve-extprograms/sieve-extprograms-common.h b/src/plugins/sieve-extprograms/sieve-extprograms-common.h index c865670ef..03850decb 100644 --- a/src/plugins/sieve-extprograms/sieve-extprograms-common.h +++ b/src/plugins/sieve-extprograms/sieve-extprograms-common.h @@ -29,9 +29,9 @@ void sieve_extprograms_config_deinit * Extensions */ -extern const struct sieve_extension_def pipe_extension; -extern const struct sieve_extension_def filter_extension; -extern const struct sieve_extension_def execute_extension; +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 diff --git a/src/plugins/sieve-extprograms/sieve-extprograms-plugin.c b/src/plugins/sieve-extprograms/sieve-extprograms-plugin.c index ee445a9b5..9fb58c97f 100644 --- a/src/plugins/sieve-extprograms/sieve-extprograms-plugin.c +++ b/src/plugins/sieve-extprograms/sieve-extprograms-plugin.c @@ -24,11 +24,11 @@ void sieve_extprograms_plugin_load struct _plugin_context *pctx = i_new(struct _plugin_context, 1); pctx->ext_pipe = sieve_extension_register - (svinst, &pipe_extension, FALSE); + (svinst, &vnd_pipe_extension, FALSE); pctx->ext_filter = sieve_extension_register - (svinst, &filter_extension, FALSE); + (svinst, &vnd_filter_extension, FALSE); pctx->ext_execute = sieve_extension_register - (svinst, &execute_extension, FALSE); + (svinst, &vnd_execute_extension, FALSE); if ( svinst->debug ) { sieve_sys_debug(svinst, "Sieve Extprograms plugin for %s version %s loaded", diff --git a/tests/extensions/duplicate/errors.svtest b/tests/extensions/duplicate/errors.svtest new file mode 100644 index 000000000..108a0f06e --- /dev/null +++ b/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"; + } +} + + diff --git a/tests/extensions/duplicate/errors/conflict-vnd.sieve b/tests/extensions/duplicate/errors/conflict-vnd.sieve new file mode 100644 index 000000000..1c133df18 --- /dev/null +++ b/tests/extensions/duplicate/errors/conflict-vnd.sieve @@ -0,0 +1,4 @@ +require "vnd.dovecot.duplicate"; +require "duplicate"; + +if duplicate { keep; } diff --git a/tests/extensions/duplicate/errors/conflict.sieve b/tests/extensions/duplicate/errors/conflict.sieve new file mode 100644 index 000000000..aa9b038f3 --- /dev/null +++ b/tests/extensions/duplicate/errors/conflict.sieve @@ -0,0 +1,4 @@ +require "duplicate"; +require "vnd.dovecot.duplicate"; + +if duplicate { keep; } diff --git a/tests/extensions/vnd.dovecot/duplicate/errors/syntax.sieve b/tests/extensions/duplicate/errors/syntax-vnd.sieve similarity index 100% rename from tests/extensions/vnd.dovecot/duplicate/errors/syntax.sieve rename to tests/extensions/duplicate/errors/syntax-vnd.sieve diff --git a/tests/extensions/duplicate/errors/syntax.sieve b/tests/extensions/duplicate/errors/syntax.sieve new file mode 100644 index 000000000..a561cfb32 --- /dev/null +++ b/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!" { } + + diff --git a/tests/extensions/vnd.dovecot/duplicate/execute.svtest b/tests/extensions/duplicate/execute-vnd.svtest similarity index 100% rename from tests/extensions/vnd.dovecot/duplicate/execute.svtest rename to tests/extensions/duplicate/execute-vnd.svtest diff --git a/tests/extensions/duplicate/execute.svtest b/tests/extensions/duplicate/execute.svtest new file mode 100644 index 000000000..9e060ffbb --- /dev/null +++ b/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"; + } +} diff --git a/tests/extensions/vnd.dovecot/duplicate/errors.svtest b/tests/extensions/vnd.dovecot/duplicate/errors.svtest deleted file mode 100644 index 769ca64d9..000000000 --- a/tests/extensions/vnd.dovecot/duplicate/errors.svtest +++ /dev/null @@ -1,18 +0,0 @@ -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"; - } -} -- GitLab