From e09ca00978d79aea67b83b36529355c3101bf958 Mon Sep 17 00:00:00 2001 From: Stephan Bosch <stephan@rename-it.nl> Date: Thu, 20 Sep 2012 00:22:36 +0200 Subject: [PATCH] lib-sieve: Finished implementation of include extension. Added support for the :optional tag of the include command. Changed Sieve script API in the process; opening the script is now available as a separate step. --- Makefile.am | 1 + README | 6 +- TODO | 2 - doc/rfc/draft-ietf-sieve-include-05.txt | 784 ----------------- doc/rfc/include.rfc6609.txt | 787 ++++++++++++++++++ src/lib-sieve/plugins/include/cmd-include.c | 89 +- .../plugins/include/ext-include-binary.c | 112 ++- .../plugins/include/ext-include-binary.h | 15 +- .../plugins/include/ext-include-common.c | 98 ++- .../plugins/include/ext-include-common.h | 11 +- src/lib-sieve/plugins/include/ext-include.c | 3 + src/lib-sieve/sieve-lexer.c | 4 +- src/lib-sieve/sieve-script-dict.c | 63 +- src/lib-sieve/sieve-script-file.c | 16 +- src/lib-sieve/sieve-script-private.h | 25 +- src/lib-sieve/sieve-script.c | 185 ++-- src/lib-sieve/sieve-script.h | 24 +- src/lib-sieve/sieve.c | 8 +- src/lib-sievestorage/sieve-storage-script.c | 18 +- src/managesieve/cmd-getscript.c | 5 +- src/plugins/lda-sieve/lda-sieve-plugin.c | 4 +- .../extensions/include/execute/optional.sieve | 5 + .../include/included/optional-1.sieve | 9 + .../include/included/optional-2.sieve | 9 + tests/extensions/include/optional.svtest | 40 + 25 files changed, 1263 insertions(+), 1060 deletions(-) delete mode 100644 doc/rfc/draft-ietf-sieve-include-05.txt create mode 100644 doc/rfc/include.rfc6609.txt create mode 100644 tests/extensions/include/execute/optional.sieve create mode 100644 tests/extensions/include/included/optional-1.sieve create mode 100644 tests/extensions/include/included/optional-2.sieve create mode 100644 tests/extensions/include/optional.svtest diff --git a/Makefile.am b/Makefile.am index 6e9141691..a4d359fa6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -76,6 +76,7 @@ test_cases = \ tests/extensions/include/variables.svtest \ tests/extensions/include/once.svtest \ tests/extensions/include/twice.svtest \ + tests/extensions/include/optional.svtest \ tests/extensions/include/rfc.svtest \ tests/extensions/include/execute.svtest \ tests/extensions/imap4flags/basic.svtest \ diff --git a/README b/README index 4e3c00ab0..8d9f345ce 100644 --- a/README +++ b/README @@ -109,8 +109,7 @@ following list outlines the implementation status of each supported extension: relational (RFC 5231): fully supported. imap4flags (RFC 5232): fully supported. subaddress (RFC 5233): fully supported, but with limited configurability. - spamtest and virustest (RFC 5235): fully supported (v0.1.16+), but - currently considered experimental. + spamtest and virustest (RFC 5235): fully supported (v0.1.16+). date (RFC 5260; Section 4): fully supported (v0.1.12+). editheader (RFC 5293): fully supported (v0.3.0+). reject (RFC 5429; Section 2.2): fully supported. @@ -121,8 +120,7 @@ following list outlines the implementation status of each supported extension: ihave (RFC 5463): fully supported (v0.2.4+). mailbox (RFC 5490; Section 3): fully supported (v0.1.10+), but ACL permissions are not verified for mailboxexists. - include (draft v05; not latest version): almost fully supported, but - interaction with ManageSieve is not in accordance with specification. + include (RFC 6609): fully supported (v0.4.0+) regex (draft v08; not latest version): almost fully supported, but UTF-8 is not supported. diff --git a/TODO b/TODO index 392b856ae..4f9684c9a 100644 --- a/TODO +++ b/TODO @@ -21,8 +21,6 @@ Next (mostly in order of descending priority/precedence): of script storage like LDAP or SQL database. - Implement read/write script storages for using ManageSieve with dict database -* Update include extension to latest draft (v13 currently): - - Implement :optional tag. * Implement index extension * Add normalize() method to comparators to normalize the string before matching (for efficiency). diff --git a/doc/rfc/draft-ietf-sieve-include-05.txt b/doc/rfc/draft-ietf-sieve-include-05.txt deleted file mode 100644 index ada0bc86c..000000000 --- a/doc/rfc/draft-ietf-sieve-include-05.txt +++ /dev/null @@ -1,784 +0,0 @@ - - - -Network Working Group C. Daboo -Internet-Draft A. Stone -Intended status: Standards Track July 12, 2010 -Expires: January 13, 2011 - - - Sieve Email Filtering: Include Extension - draft-ietf-sieve-include-05 - -Abstract - - The Sieve Email Filtering "include" extension permits users to - include one Sieve script inside another. This can make managing - large scripts or multiple sets of scripts much easier, and allows a - site and its users to build up libraries of scripts. Users are able - to include their own personal scripts or site-wide scripts. - -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 January 13, 2011. - -Copyright Notice - - Copyright (c) 2010 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 - 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. - - - -Daboo & Stone Expires January 13, 2011 [Page 1] - -Internet-Draft Sieve Extension: Include July 2010 - - - This document may contain material from IETF Documents or IETF - Contributions published or made publicly available before November - 10, 2008. The person(s) controlling the copyright in some of this - material may not have granted the IETF Trust the right to allow - modifications of such material outside the IETF Standards Process. - Without obtaining an adequate license from the person(s) controlling - the copyright in such materials, this document may not be modified - outside the IETF Standards Process, and derivative works of it may - not be created outside the IETF Standards Process, except to format - it for publication as an RFC or to translate it into languages other - than English. - - -Table of Contents - - 1. Introduction and Overview . . . . . . . . . . . . . . . . . . 5 - 2. Conventions Used in This Document . . . . . . . . . . . . . . 5 - 3. Include Extension . . . . . . . . . . . . . . . . . . . . . . 5 - 3.1. General Considerations . . . . . . . . . . . . . . . . . . 5 - 3.2. Control Structure include . . . . . . . . . . . . . . . . 6 - 3.3. Control Structure return . . . . . . . . . . . . . . . . . 10 - 3.4. Interaction with Variables . . . . . . . . . . . . . . . . 10 - 3.4.1. Control Structure global . . . . . . . . . . . . . . . 10 - 3.4.2. Variables Namespace global . . . . . . . . . . . . . . 12 - 4. Security Considerations . . . . . . . . . . . . . . . . . . . 12 - 5. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 13 - 5.1. "include" Extension Registration . . . . . . . . . . . . . 13 - 6. References . . . . . . . . . . . . . . . . . . . . . . . . . . 13 - 6.1. Normative References . . . . . . . . . . . . . . . . . . . 13 - 6.2. Informative References . . . . . . . . . . . . . . . . . . 13 - Appendix A. Acknowledgments . . . . . . . . . . . . . . . . . . . 13 - Authors' Addresses . . . . . . . . . . . . . . . . . . . . . . . . 13 - - - - - - - - - - - - - - - - - - - -Daboo & Stone Expires January 13, 2011 [Page 2] - -Internet-Draft Sieve Extension: Include July 2010 - - -Change History (to be removed prior to publication as an RFC) - - Changes from ietf-04 to ietf-05: - - a. Integrate review from Barry Leiba. - - Changes from ietf-03 to ietf-04: - - a. No changes. - - Changes from ietf-02 to ietf-03: - - a. Setting a variable then calling global on it is an error - (something like 'use strict'). - - b. Specify that the 'global' keyword is only available when - 'variables' has also been required. - - c. Uploading a script that includes a nonexistent script is not an - error at upload time. - - Changes from ietf-01 to ietf-02: - - a. Require that script names must be constant strings, not subject - to variable expansion. - - b. Try the phrase immediate script instead of current script. - - c. Clarify that "global 'varname'" and "global.varname" refer to the - same variable. - - d. Drop the requirement the global keywords come after require and - before anything else. - - Changes from ietf-00 to ietf-01: - - a. Replaced import/export with global. - - b. Added :once modifier to include. - - c. Added global namespace to see if it holds water. - - Changes from daboo-06 to ietf-00: - - a. None - - Changes from -05 to -06: - - - - -Daboo & Stone Expires January 13, 2011 [Page 3] - -Internet-Draft Sieve Extension: Include July 2010 - - - a. Aaron Stone joins as author. - - b. Removed | characters from the script examples. - - c. Updated draft references to published RFCs. - - Changes from -04 to -05: - - a. Fixed examples. - - b. Relaxed requirement that imported/exported variables be set - before being used. - - Changes from -03 to -04: - - a. Fixed missing 2119 definitions. - - b. Defined interaction with variables through use of import and - export commands. - - Changes from -02 to -03: - - a. Refreshing expired draft (updated for nits). - - b. Syntax -> Usage. - - c. Updated to 3028bis reference. - - Changes from -01 to -02: - - a. Minor formatting changes only - refreshing expired draft. - - Changes from -00 to -01: - - a. Added IPR boiler plate. - - b. Re-ordered sections at start to conform to RFC style. - - c. Moved recursion comment into General Considerations section. - - d. Switched to using optional parameter to indicate personal vs - global. - - e. Explicitly state that an error occurs when a missing script is - included. - - - - - - -Daboo & Stone Expires January 13, 2011 [Page 4] - -Internet-Draft Sieve Extension: Include July 2010 - - -1. Introduction and Overview - - It's convenient to be able to break SIEVE [RFC5228] scripts down into - smaller components which can be reused in a variety of different - circumstances. For example, users may want to have a default script - and a special 'vacation' script, the latter being activated when the - user goes on vacation. In that case the default actions should - continue to be run, but a vacation command should be executed first. - One option is to edit the default script to add or remove the - vacation command as needed. Another is to have a vacation script - that simply has a vacation command and then includes the default - script. - - -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 [RFC2119]. - - Conventions for notations are as in SIEVE [RFC5228] Section 1.1. - - The following key phrases are used to describe scripts and script - execution: - - script - a valid Sieve script. - - script execution - an instance of a Sieve interpreter invoked for a given message - delivery, starting with the user's active script and continuing - through any included scripts until the message is delivered. - - immediate script - the individual Sieve script file being executed. - - including script - the individual Sieve script file that had an include statement - which included the immediate script. - - -3. Include Extension - -3.1. General Considerations - - Sieve implementations that implement the "include", "return", and - "global" commands described below have an identifier of "include" for - use with the capability mechanism. If any of the "include", - - - -Daboo & Stone Expires January 13, 2011 [Page 5] - -Internet-Draft Sieve Extension: Include July 2010 - - - "return", or "global" commands are used in a script, the "include" - capability MUST be listed in the "require" statement in that script. - - Sieve implementations need to track the use of actions in included - scripts so that implicit "keep" behavior can be properly determined - based on whether any actions have executed in any script. - - Sieve implementations are allowed to limit the total number of nested - included scripts, but MUST provide for a total of at least three - levels of nested scripts including the top-level script. An error - MUST be generated either when the script is uploaded to the Sieve - repository, or when the script is executed, if any nesting limit is - exceeded. If such an error is detected whilst processing a Sieve - script, an implicit "keep" action MUST be executed to prevent loss of - any messages. - - Sieve implementations MUST ensure that recursive includes are not - possible. For example, if script "A" includes script "B", and script - "B" includes script "A" an error MUST be generated either when the - script is uploaded to the Sieve repository, or when the script is - executed. If such an error is detected whilst processing a Sieve - script, an implicit "keep" action MUST be executed to prevent loss of - any messages. - - Sieve implementations MUST generate an error at execution time if an - included script does not exist. Implementations MUST NOT generate - errors for scripts missing at upload time, as this would force an - upload ordering requirement upon script authors / generators. - - If the Sieve "variables" extension [RFC5229] is present, an issue - arises with the "scope" of variables defined in scripts that may - include each other. For example, if a script defines the variable - "${status}" with one particular meaning or usage, and another defines - "${status}" with a different meaning, then if one script includes the - other there is an issue as to which "${status}" is being referenced. - To solve this problem, Sieve implementations MUST follow the scoping - rules defined in Section 3.4 and support the "global" command defined - there. - -3.2. Control Structure include - - Usage: include [LOCATION] [ONCE] <value: string> - - LOCATION = ":personal" / ":global" - - ONCE = ":once" - - The "include" command takes an optional "location" parameter, an - - - -Daboo & Stone Expires January 13, 2011 [Page 6] - -Internet-Draft Sieve Extension: Include July 2010 - - - optional ":once" parameter, and a single string argument representing - the name of the script to include for processing at that point. It - is RECOMMENDED that implementations restrict script names according - to [I-D.ietf-sieve-managesieve] Section 1.7. Implementations MUST - NOT allow variables to be expanded into the names of Sieve scripts; - in other words, the value MUST be a constant string as defined in - VARIABLES [RFC5229], Section 3. - - The "location" parameter MUST default to ":personal" if not - specified. The "location" has the following meanings: - - :personal - Indicates that the named script is stored in the user's own - personal (private) Sieve repository. - - :global - Indicates that the named script is stored in a site-wide Sieve - repository, accessible to all users of the Sieve system. - - The ":once" parameter tells the interpreter only to include the named - script if it has not already been included at any other point during - script execution. If the script has already been included, - processing continues immediately following the include command. - Implementations MUST NOT generate an error if an "include :once" - command names a script whose inclusion would be recursive; in this - case, the script MUST be considered previously included and therefore - "include :once" will not include it again. - - Note: It is RECOMMENDED that script authors / generators use this - parameter only when including a script that performs general duties - such as declaring global variables and making sanity checks of the - environment. - - The included script MUST be a valid Sieve script, including having - necessary "require" statements for all optional capabilities used by - the script. The scope of a "require" statement in an included script - is for the immediate script only, not the including script. For - example, if script "A" includes script "B", and script "B" uses the - "fileinto" extension, script "B" must have a "require" statement for - "fileinto", irrespective of whether script "A" has one. In addition, - if script "A" does not have a "require" statement for "fileinto", - "fileinto" cannot be used anywhere in script "A", even after - inclusion of script "B". - - A "stop" command in an included script MUST stop all script - processing, including the processing of the scripts that include the - immediate one. The "return" command (described below) stops - processing of the immediate script only, and allows the scripts that - - - -Daboo & Stone Expires January 13, 2011 [Page 7] - -Internet-Draft Sieve Extension: Include July 2010 - - - include it to continue. - - Examples: - - The user has four scripts stored in their personal repository: - - "default" - - This is the default active script that includes several others. - - - require ["include"]; - - include :personal "always_allow"; - include :global "spam_tests"; - include :personal "spam_tests"; - include :personal "mailing_lists"; - - Personal script "always_allow" - - This script special-cases some correspondent email addresses and - makes sure any message containing those addresses are always kept. - - - if header :is "From" "boss@example.com" - { - keep; - } - elsif header :is "From" "ceo@example.com" - { - keep; - } - - Personal script "spam_tests" - - This script does some user-specific spam tests to catch spam - messages not caught by the site-wide spam tests. - - - require ["reject"]; - - if header :contains "Subject" "XXXX" - { - reject; - } - elsif header :is "From" "money@example.com" - { - reject; - - - -Daboo & Stone Expires January 13, 2011 [Page 8] - -Internet-Draft Sieve Extension: Include July 2010 - - - } - - Personal script "mailing_lists" - - This script looks for messages from different mailing lists and - files each into a mailbox specific to the mailing list. - - - require ["fileinto"]; - - if header :is "List-ID" "owner-ietf-mta-filters@imc.org" - { - fileinto "lists.sieve"; - } - elsif header :is "List-ID" "owner-ietf-imapext@imc.org" - { - fileinto "lists.imapext"; - } - - There is one script stored in the global repository: - - Site script "spam_tests" - - This script does some site-wide spam tests which any user at the - site can include in their own scripts at a suitable point. The - script content is kept up to date by the site administrator. - - - require ["reject"]; - - if anyof (header :contains "Subject" "$$", - header :contains "Subject" "Make money") - { - reject; - } - - The "include" command may appear anywhere in the script where a - control structure is legal. - - Example: - - require ["include"]; - - if anyof (header :contains "Subject" "$$", - header :contains "Subject" "Make money") - { - include "my_reject_script"; - } - - - -Daboo & Stone Expires January 13, 2011 [Page 9] - -Internet-Draft Sieve Extension: Include July 2010 - - -3.3. Control Structure return - - Usage: return - - The "return" command stops processing of the immediately included - script only and returns processing control to the script which - includes it. If used in the main script (i.e., not in an included - script), it has the same effect as the "stop" command, including the - appropriate "keep" action if no other actions have been executed up - to that point. - -3.4. Interaction with Variables - - In order to avoid problems of variables in an included script - "overwriting" those from the script that includes it, this - specification requires that all variables defined in a script MUST be - kept "private" to the immediate script by default - that is, they are - not "visible" to other scripts. This ensures that two script authors - cannot inadvertently cause problems by choosing the same name for a - variable. - - However, sometimes there is a need to make a variable defined in one - script available to others. This specification defines the new - command "global" to declare that a variable is shared among scripts. - Effectively, two namespaces are defined: one local to the immediate - script, and another shared among all scripts. Implementations MUST - allow a non-global variable to have the same name as a global - variable but have no interaction between them. - -3.4.1. Control Structure global - - Usage: global <value: string-list> - - The "global" command contains a string list argument that defines one - or more names of variables to be stored in the global variable space. - - The "global" command is only available when the script has both - "include" and "variables" in its require line. If the "global" - command appears when only "include" or only "variables" has been - required, an error MUST be generated when the script is uploaded. - - If a "global" command is given the name of a variable that has - previously been defined in the immediate script with "set", an error - MUST be generated either when the script is uploaded or at execution - time. - - If a "global" command lists a variable that has not been defined in - the global namespace, the name of the variable is now marked as - - - -Daboo & Stone Expires January 13, 2011 [Page 10] - -Internet-Draft Sieve Extension: Include July 2010 - - - global, and any subsequent "set" command will set the value of the - variable in global scope. - - A variable has global scope in all scripts that have declared it with - the "global" command. If a script uses that variable name without - declaring it global, the name specifies a separate, non-global - variable within that script. - - Interpretation of a string containing a variable marked as global, - but without any value set, SHALL behave as any other access to an - unknown variable, as specified in VARIABLES [RFC5229], Section 3 - (i.e., evaluates to an empty string). - - Example: - - require ["variables", "include"]; - global "test"; - global "test-mailbox"; - - # The included script may contain repetitive code that is - # effectively a subroutine that can be factored out. - set "test" "$$" - include "spam_filter_script"; - - set "test" "Make money" - include "spam_filter_script"; - - # Message will be filed according to the test that matched last. - if string :count "${test-mailbox}" "1" - { - fileinto "INBOX${test-mailbox}"; - stop; - } - - # If nothing matched, the message is implicitly kept. - - Active script - - - require ["variables", "include"]; - global ["test", "test-mailbox"]; - - if header :contains "Subject" "${test}" - { - set "test-mailbox" "spam-${test}; - } - - spam_filter_script - - - -Daboo & Stone Expires January 13, 2011 [Page 11] - -Internet-Draft Sieve Extension: Include July 2010 - - -3.4.2. Variables Namespace global - - In addition to the "global" command, this document defines the - variables namespace "global", as specified in VARIABLES [RFC5229], - Section 3. - - Example: - - require ["variables", "include"]; - - set "global.i_am_on_vacation" "1"; - - Variables declared global and variables accessed via the global - namespace MUST be one and the same. In the following example script, - we see the variable "i_am_on_vacation" used in a "global" command, - and again with the "global." namespace. Consider these as two - syntaxes with identical meaning. - - Example: - - require ["variables", "include"]; - global "i_am_on_vacation"; - - set "global.i_am_on_vacation" "1"; - - if string :is "${i_am_on_vacation}" "1" - { - vacation "It's true, I am on vacation." - } - - -4. Security Considerations - - Sieve implementations MUST ensure adequate security for the global - script repository to prevent unauthorized changes to global scripts. - - Sieve implementations MUST ensure that script names are checked for - validity and proper permissions prior to inclusion, in order to - prevent a malicious user from gaining acess to files accessible to - the mail server software that should not be accessible to the user. - - Beyond these, the "include" extension does not raise any security - considerations that are not present in the base SIEVE [RFC5228] - document and the VARIABLES [RFC5229] extension. - - - - - - - -Daboo & Stone Expires January 13, 2011 [Page 12] - -Internet-Draft Sieve Extension: Include July 2010 - - -5. IANA Considerations - - The following template specifies the IANA registration of the Sieve - extension specified in this document: - -5.1. "include" Extension Registration - - Capability name: include - Description: adds the "include" command to execute other Sieve - scripts, and the "global" command and "global" variables - namespace to access variables shared among included scripts. - RFC number: this RFC - Contact address: the Sieve discussion list <ietf-mta-filters@imc.org> - - -6. References - -6.1. Normative References - - [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate - Requirement Levels", BCP 14, RFC 2119, March 1997. - - [RFC5228] Guenther, P. and T. Showalter, "Sieve: An Email Filtering - Language", RFC 5228, January 2008. - - [RFC5229] Homme, K., "Sieve Email Filtering: Variables Extension", - RFC 5229, January 2008. - -6.2. Informative References - - [I-D.ietf-sieve-managesieve] - Melnikov, A. and T. Martin, "A Protocol for Remotely - Managing Sieve Scripts", draft-ietf-sieve-managesieve-09 - (work in progress), January 2009. - - -Appendix A. Acknowledgments - - Thanks to Ken Murchison, Rob Siemborski, Alexey Melnikov, Marc Mutz, - Kjetil Torgrim Homme, Stephan Bosch, Arnt Gulbrandsen, Barry Leiba, - and Jeffrey Hutzelman for comments and corrections. - - - - - - - - - - -Daboo & Stone Expires January 13, 2011 [Page 13] - -Internet-Draft Sieve Extension: Include July 2010 - - -Authors' Addresses - - Cyrus Daboo - - Email: cyrus@daboo.name - - - Aaron Stone - - Email: aaron@serendipity.cx - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Daboo & Stone Expires January 13, 2011 [Page 14] - diff --git a/doc/rfc/include.rfc6609.txt b/doc/rfc/include.rfc6609.txt new file mode 100644 index 000000000..08620a5b2 --- /dev/null +++ b/doc/rfc/include.rfc6609.txt @@ -0,0 +1,787 @@ + + + + + + +Internet Engineering Task Force (IETF) C. Daboo +Request for Comments: 6609 Apple, Inc. +Category: Standards Track A. Stone +ISSN: 2070-1721 Serendipity + May 2012 + + + Sieve Email Filtering: Include Extension + +Abstract + + The Sieve Email Filtering "include" extension permits users to + include one Sieve script inside another. This can make managing + large scripts or multiple sets of scripts much easier, and allows a + site and its users to build up libraries of scripts. Users are able + to include their own personal scripts or site-wide scripts. + +Status of This Memo + + This is an Internet Standards Track document. + + This document is a product of the Internet Engineering Task Force + (IETF). It represents the consensus of the IETF community. It has + received public review and has been approved for publication by the + Internet Engineering Steering Group (IESG). Further information on + Internet Standards is available in Section 2 of RFC 5741. + + Information about the current status of this document, any errata, + and how to provide feedback on it may be obtained at + http://www.rfc-editor.org/info/rfc6609. + +Copyright Notice + + Copyright (c) 2012 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 + 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. + + + + + + +Daboo & Stone Standards Track [Page 1] + +RFC 6609 Sieve Extension: Include May 2012 + + +Table of Contents + + 1. Introduction and Overview .......................................2 + 2. Conventions Used in This Document ...............................2 + 3. Include Extension ...............................................3 + 3.1. General Considerations .....................................3 + 3.2. Control Structure "include" ................................4 + 3.3. Control Structure "return" .................................7 + 3.4. Interaction with the "variables" Extension .................8 + 3.4.1. Control Structure "global" ..........................8 + 3.4.2. Variables Namespace global .........................10 + 3.5. Interaction with Other Extensions .........................11 + 4. Security Considerations ........................................12 + 5. IANA Considerations ............................................12 + 6. References .....................................................13 + 6.1. Normative References ......................................13 + 6.2. Informative References ....................................13 + Appendix A. Acknowledgments .......................................14 + +1. Introduction and Overview + + It's convenient to be able to break Sieve [RFC5228] scripts down into + smaller components that can be reused in a variety of different + circumstances. For example, users may want to have a default script + and a special 'vacation' script, the latter being activated when the + user goes on vacation. In that case, the default actions should + continue to be run, but a vacation command should be executed first. + One option is to edit the default script to add or remove the + vacation command as needed. Another is to have a vacation script + that simply has a vacation command and then includes the default + script. + + This document defines the Sieve Email Filtering "include" extension, + which permits users to include one Sieve script inside another. + +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 [RFC2119]. + + Conventions for notations are as in Sieve [RFC5228], Section 1.1. + + + + + + + + + +Daboo & Stone Standards Track [Page 2] + +RFC 6609 Sieve Extension: Include May 2012 + + + The following key phrases are used to describe scripts and script + execution: + + script + a valid Sieve script. + + script execution + an instance of a Sieve interpreter invoked for a given message + delivery, starting with the user's active script and continuing + through any included scripts until the final disposition of the + message (e.g., delivered, forwarded, discarded, rejected, etc.). + + immediate script + the individual Sieve script file being executed. + + including script + the individual Sieve script file that had an include statement + that included the immediate script. + +3. Include Extension + +3.1. General Considerations + + Sieve implementations that implement the "include", "return", and + "global" commands described below have an identifier of "include" for + use with the capability mechanism. If any of the "include", + "return", or "global" commands are used in a script, the "include" + capability MUST be listed in the "require" statement in that script. + + Sieve implementations need to track the use of actions in included + scripts so that implicit "keep" behavior can be properly determined + based on whether any actions have executed in any script. + + Sieve implementations are allowed to limit the total number of nested + included scripts, but MUST provide for a total of at least three + levels of nested scripts including the top-level script. An error + MUST be generated either when the script is uploaded to the Sieve + repository, or when the script is executed, if any nesting limit is + exceeded. If such an error is detected whilst processing a Sieve + script, an implicit "keep" action MUST be executed to prevent loss of + any messages. + + Sieve implementations MUST NOT allow recursive script inclusion. + Both direct recursion, where script A includes script A (itself), and + indirect recursion, where script A includes script B which includes + script A once again, are prohibited. + + + + + +Daboo & Stone Standards Track [Page 3] + +RFC 6609 Sieve Extension: Include May 2012 + + + Sieve implementations MUST generate an error at execution time if an + included script is a recursive inclusion. Implementations MUST NOT + generate errors for recursive includes at upload time, as this would + force an upload ordering requirement upon script authors and + generators. + + Sieve implementations MUST generate an error at execution time if an + included script does not exist, except when the ":optional" parameter + is specified. Implementations MUST NOT generate errors for scripts + missing at upload time, as this would force an upload ordering + requirement upon script authors and generators. + + If the Sieve "variables" extension [RFC5229] is present, an issue + arises with the "scope" of variables defined in scripts that may + include each other. For example, if a script defines the variable + "${status}" with one particular meaning or usage, and another defines + "${status}" with a different meaning, then if one script includes the + other there is an issue as to which "${status}" is being referenced. + To solve this problem, Sieve implementations MUST follow the scoping + rules defined in Section 3.4 and support the "global" command defined + there. + +3.2. Control Structure "include" + + Usage: include [LOCATION] [":once"] [":optional"] <value: string> + + LOCATION = ":personal" / ":global" + + The "include" command takes an optional "location" parameter, an + optional ":once" parameter, an optional ":optional" parameter, and a + single string argument representing the name of the script to include + for processing at that point. Implementations MUST restrict script + names according to ManageSieve [RFC5804], Section 1.6. The script + name argument MUST be a constant string as defined in [RFC5229], + Section 3; implementations MUST NOT expand variables in the script + name argument. + + The "location" parameter MUST default to ":personal" if not + specified. The "location" parameter MUST NOT be specified more than + once. The "location" has the following meanings: + + :personal + Indicates that the named script is stored in the user's own + personal (private) Sieve repository. + + :global + Indicates that the named script is stored in a site-wide Sieve + repository, accessible to all users of the Sieve system. + + + +Daboo & Stone Standards Track [Page 4] + +RFC 6609 Sieve Extension: Include May 2012 + + + The ":once" parameter tells the interpreter only to include the named + script if it has not already been included at any other point during + script execution. If the script has already been included, + processing continues immediately following the "include" command. + Implementations MUST NOT generate an error if an "include :once" + command names a script whose inclusion would be recursive; in this + case, the script MUST be considered previously included, and + therefore "include :once" will not include it again. + + Note: It is RECOMMENDED that script authors and generators use the + ":once" parameter only when including a script that performs general + duties such as declaring global variables and making sanity checks of + the environment. + + The ":optional" parameter indicates that the script may be missing. + Ordinarily, an implementation MUST generate an error during execution + if an "include" command specifies a script that does not exist. When + ":optional" is specified, implementations MUST NOT generate an error + for a missing script, and MUST continue as if the "include" command + had not been present. + + The included script MUST be a valid Sieve script. Implementations + MUST validate that each script has its own "require" statements for + all optional capabilities used by that script. The scope of a + "require" statement is the script in which it immediately appears, + and neither inherits nor passes on capabilities to other scripts + during the course of execution. + + A "stop" command in an included script MUST stop all script + processing, including the processing of the scripts that include the + immediate one. The "return" command (described below) stops + processing of the immediate script only, and allows the scripts that + include it to continue. + + The "include" command MAY appear anywhere in a script where a control + structure is legal, and MAY be used within another control structure, + e.g., an "if" block. + + + + + + + + + + + + + + +Daboo & Stone Standards Track [Page 5] + +RFC 6609 Sieve Extension: Include May 2012 + + + Examples: + + The user has four scripts stored in their personal repository: + + "default" + + This is the default active script that includes several others. + + require ["include"]; + + include :personal "always_allow"; + include :global "spam_tests"; + include :personal "spam_tests"; + include :personal "mailing_lists"; + + Personal script "always_allow" + + This script special-cases some correspondent email addresses and + makes sure any message containing those addresses is always kept. + + if address :is "from" "boss@example.com" + { + keep; + } + elsif address :is "from" "ceo@example.com" + { + keep; + } + + Personal script "spam_tests" (uses "reject" [RFC5429]) + + This script does some user-specific spam tests to catch spam + messages not caught by the site-wide spam tests. + + require ["reject"]; + + if header :contains "Subject" "XXXX" + { + reject "Subject XXXX is unacceptable."; + } + elsif address :is "from" "money@example.com" + { + reject "Mail from this sender is unwelcome."; + } + + + + + + + +Daboo & Stone Standards Track [Page 6] + +RFC 6609 Sieve Extension: Include May 2012 + + + Personal script "mailing_lists" + + This script looks for messages from different mailing lists and + files each into a mailbox specific to the mailing list. + + require ["fileinto"]; + + if header :is "List-ID" "sieve.ietf.org" + { + fileinto "lists.sieve"; + } + elsif header :is "List-ID" "ietf-imapext.imc.org" + { + fileinto "lists.imapext"; + } + + There is one script stored in the global repository: + + Site script "spam_tests" (uses "reject" [RFC5429]) + + This script does some site-wide spam tests that any user at the + site can include in their own scripts at a suitable point. The + script content is kept up to date by the site administrator. + + require ["reject"]; + + if anyof (header :contains "Subject" "$$", + header :contains "Subject" "Make money") + { + reject "No thank you."; + } + +3.3. Control Structure "return" + + Usage: return + + The "return" command stops processing of the immediately included + script only and returns processing control to the script that + includes it. If used in the main script (i.e., not in an included + script), it has the same effect as the "stop" command, including the + appropriate "keep" action if no other actions have been executed up + to that point. + + + + + + + + + +Daboo & Stone Standards Track [Page 7] + +RFC 6609 Sieve Extension: Include May 2012 + + +3.4. Interaction with the "variables" Extension + + In order to avoid problems of variables in an included script + "overwriting" those from the script that includes it, this + specification requires that all variables defined in a script MUST be + kept "private" to the immediate script by default -- that is, they + are not "visible" to other scripts. This ensures that two script + authors cannot inadvertently cause problems by choosing the same name + for a variable. + + However, sometimes there is a need to make a variable defined in one + script available to others. This specification defines the new + command "global" to declare that a variable is shared among scripts. + Effectively, two namespaces are defined: one local to the immediate + script, and another shared among all scripts. Implementations MUST + allow a non-global variable to have the same name as a global + variable but have no interaction between them. + +3.4.1. Control Structure "global" + + Usage: global <value: string-list> + + The "global" command accepts a string list argument that defines one + or more names of variables to be stored in the global variable space. + Each name MUST be a constant string and conform to the syntax of + variable-name as defined in the "variables" extension document + [RFC5229], Section 3. Match variables cannot be specified, and + namespace prefixes are not allowed. An invalid name MUST be detected + as a syntax error. + + The "global" command is only available when the script has both + "include" and "variables" in its require line. If the "global" + command appears when only "include" or only "variables" has been + required, an error MUST be generated when the script is uploaded. + + If a "global" command is given the name of a variable that has + previously been defined in the immediate script with "set", an error + MUST be generated either when the script is uploaded or at execution + time. + + If a "global" command lists a variable that has not been defined in + the "global" namespace, the name of the variable is now marked as + global, and any subsequent "set" command will set the value of the + variable in global scope. + + + + + + + +Daboo & Stone Standards Track [Page 8] + +RFC 6609 Sieve Extension: Include May 2012 + + + A variable has global scope in all scripts that have declared it with + the "global" command. If a script uses that variable name without + declaring it global, the name specifies a separate, non-global + variable within that script. + + Interpretation of a string containing a variable marked as global, + but without any value set, SHALL behave as any other access to an + unknown variable, as specified in the "variables" extension document + [RFC5229], Section 3 (i.e., evaluates to an empty string). + + Example: + + The active script + + The included script may contain repetitive code that is + effectively a subroutine that can be factored out. In this + script, the test that matches last will leave its value in the + test_mailbox variable, and the top-level script will file the + message into that mailbox. If no tests matched, the message will + be implicitly kept in the INBOX. + + require ["fileinto", "include", "variables", "relational"]; + global "test"; + global "test_mailbox"; + + set "test" "$$"; + include "subject_tests"; + + set "test" "Make money"; + include "subject_tests"; + + if string :count "eq" "${test_mailbox}" "1" + { + fileinto "${test_mailbox}"; + stop; + } + + + + + + + + + + + + + + + +Daboo & Stone Standards Track [Page 9] + +RFC 6609 Sieve Extension: Include May 2012 + + + Personal script "subject_tests" + + This script performs a number of tests against the message, sets + the global test_mailbox variable with a folder to file the message + into, and then falls back to the top-level script. + + + require ["include", "variables"]; + global ["test", "test_mailbox"]; + + if header :contains "Subject" "${test}" + { + set "test_mailbox" "spam-${test}"; + } + +3.4.2. Variables Namespace global + + In addition to the "global" command, this document defines the + variables namespace "global", in accordance with the "variables" + extension document [RFC5229], Section 3. The "global" namespace has + no sub-namespaces (e.g., 'set "global.data.from" "me@example.com";' + is not allowed). The variable-name part MUST be a valid identifier + (e.g., 'set "global.12" "value";' is not valid because "12" is not a + valid identifier). + + Note that the "variables" extension document [RFC5229], Section 3 + suggests that extensions should define a namespace that is the same + as its capability string (in this case, "include" rather than + "global"). Nevertheless, references to the "global" namespace + without a prior require statement for the "include" extension MUST + cause an error. + + Example: + + require ["variables", "include"]; + + set "global.i_am_on_vacation" "1"; + + Variables declared global and variables accessed via the "global" + namespace MUST each be one and the same. In the following example + script, we see the variable "i_am_on_vacation" used in a "global" + command, and again with the "global" namespace. Consider these as + two syntaxes with identical meaning. + + + + + + + + +Daboo & Stone Standards Track [Page 10] + +RFC 6609 Sieve Extension: Include May 2012 + + + Example: + + require ["variables", "include", "vacation"]; + global "i_am_on_vacation"; + + set "global.i_am_on_vacation" "1"; + + if string :is "${i_am_on_vacation}" "1" + { + vacation "It's true, I am on vacation."; + } + +3.5. Interaction with Other Extensions + + When "include" is used with the "editheader" extension [RFC5293], any + changes made to headers in a script MUST be propagated both to and + from included scripts. By way of example, if a script deletes one + header and adds another, then includes a second script, the included + script MUST NOT see the removed header, and MUST see the added + header. Likewise, if the included script adds or removes a header, + upon returning to the including script, subsequent actions MUST see + the added headers and MUST NOT see the removed headers. + + When "include" is used with the MIME extension [RFC5703] + "foreverypart" control structure, the included script MUST be + presented with the current MIME part as though it were the entire + message. A script SHALL NOT have any special control over the + control structure it was included from. The "break" command in an + included script is not valid on its own and may not terminate a + "foreverypart" iteration in another script. The included script can + use "return" to transfer control back to the including script. A + global variable can be used to convey results to the including + script. A "stop" in an included script, even within a "foreverypart" + loop, still halts all script execution, per Section 3.2. + + When "include" is used with the "reject" extension [RFC5429], calling + "reject" or "ereject" at any time sets the reject action on the + message, and continues script execution. Apropos of the MIME + extension, if an included script sees only a portion of the message + and calls a reject, it is the entire message and not the single MIME + part that carries the rejection. + + + + + + + + + + +Daboo & Stone Standards Track [Page 11] + +RFC 6609 Sieve Extension: Include May 2012 + + +4. Security Considerations + + Sieve implementations MUST ensure adequate security for the global + script repository to prevent unauthorized changes to global scripts. + For example, a site policy might enable only certain users with + administrative privileges to modify the global scripts. Sites are + advised against allowing all users to have write access to the sites' + global scripts. + + Sieve implementations MUST ensure that script names are checked for + validity and proper permissions prior to inclusion, in order to + prevent a malicious user from gaining access to files accessible to + the mail server software that should not be accessible to the user. + + Sieve implementations MUST ensure that script names are safe for use + with their storage system. An error MUST be generated either when + the script is uploaded or at execution time for a script including a + name that could be used as a vector to attack the storage system. By + way of example, the following include commands should be considered + hostile: 'include "./../..//etc/passwd"', 'include "foo$(`rm + star`)"'. + + Beyond these, the "include" extension does not raise any security + considerations that are not discussed in the base Sieve [RFC5228] + document and the "variables" extension document [RFC5229]. + +5. 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: include + Description: adds the "include" command to execute other Sieve + scripts, the "return" action from an included + script, and the "global" command and "global" + variables namespace to access variables shared + among included scripts. + RFC number: this RFC + Contact address: the Sieve discussion list <sieve@ietf.org> + + This information has been added to IANA's "Sieve Extensions" registry + (http://www.iana.org). + + + + + + +Daboo & Stone Standards Track [Page 12] + +RFC 6609 Sieve Extension: Include May 2012 + + +6. References + +6.1. Normative References + + [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [RFC5228] Guenther, P., Ed., and T. Showalter, Ed., "Sieve: An Email + Filtering Language", RFC 5228, January 2008. + + [RFC5229] Homme, K., "Sieve Email Filtering: Variables Extension", + RFC 5229, January 2008. + + [RFC5804] Melnikov, A., Ed., and T. Martin, "A Protocol for Remotely + Managing Sieve Scripts", RFC 5804, July 2010. + +6.2. Informative References + + [RFC5293] Degener, J. and P. Guenther, "Sieve Email Filtering: + Editheader Extension", RFC 5293, August 2008. + + [RFC5429] Stone, A., Ed., "Sieve Email Filtering: Reject and + Extended Reject Extensions", RFC 5429, March 2009. + + [RFC5703] Hansen, T. and C. Daboo, "Sieve Email Filtering: MIME Part + Tests, Iteration, Extraction, Replacement, and Enclosure", + RFC 5703, October 2009. + + + + + + + + + + + + + + + + + + + + + + + + +Daboo & Stone Standards Track [Page 13] + +RFC 6609 Sieve Extension: Include May 2012 + + +Appendix A. Acknowledgments + + Thanks to Stephan Bosch, Ned Freed, Arnt Gulbrandsen, Tony Hansen, + Kjetil Torgrim Homme, Jeffrey Hutzelman, Barry Leiba, Alexey + Melnikov, Ken Murchison, Marc Mutz, and Rob Siemborski, for comments + and corrections. + +Authors' Addresses + + Cyrus Daboo + Apple Inc. + 1 Infinite Loop + Cupertino, CA 95014 + USA + + EMail: cyrus@daboo.name + URI: http://www.apple.com/ + + + Aaron Stone + Serendipity + 1817 California St. #104 + San Francisco, CA 94109 + USA + + EMail: aaron@serendipity.cx + + + + + + + + + + + + + + + + + + + + + + + + + +Daboo & Stone Standards Track [Page 14] + diff --git a/src/lib-sieve/plugins/include/cmd-include.c b/src/lib-sieve/plugins/include/cmd-include.c index 516cb6844..f9e54ec57 100644 --- a/src/lib-sieve/plugins/include/cmd-include.c +++ b/src/lib-sieve/plugins/include/cmd-include.c @@ -23,7 +23,7 @@ * Include command * * Syntax: - * include [LOCATION] <value: string> + * include [LOCATION] [":once"] [":optional"] <value: string> * * [LOCATION]: * ":personal" / ":global" @@ -74,11 +74,11 @@ const struct sieve_operation_def include_operation = { struct cmd_include_context_data { enum ext_include_script_location location; - bool location_assigned; - - bool include_once; struct sieve_script *script; + enum ext_include_flags flags; + + unsigned int location_assigned:1; }; /* @@ -103,17 +103,25 @@ static const struct sieve_argument_def include_global_tag = { NULL, NULL, NULL }; -static bool cmd_include_validate_once_tag +static bool cmd_include_validate_boolean_tag (struct sieve_validator *valdtr, struct sieve_ast_argument **arg, struct sieve_command *cmd); static const struct sieve_argument_def include_once_tag = { "once", NULL, - cmd_include_validate_once_tag, + cmd_include_validate_boolean_tag, + NULL, NULL, NULL +}; + +static const struct sieve_argument_def include_optional_tag = { + "optional", + NULL, + cmd_include_validate_boolean_tag, NULL, NULL, NULL }; + /* * Tag validation */ @@ -147,14 +155,17 @@ static bool cmd_include_validate_location_tag return TRUE; } -static bool cmd_include_validate_once_tag +static bool cmd_include_validate_boolean_tag (struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_ast_argument **arg, struct sieve_command *cmd) { struct cmd_include_context_data *ctx_data = (struct cmd_include_context_data *) cmd->data; - ctx_data->include_once = TRUE; + if ( sieve_argument_is(*arg, include_once_tag) ) + ctx_data->flags |= EXT_INCLUDE_FLAG_ONCE; + else + ctx_data->flags |= EXT_INCLUDE_FLAG_OPTIONAL; /* Delete this tag (for now) */ *arg = sieve_ast_arguments_detach(*arg, 1); @@ -173,6 +184,7 @@ static bool cmd_include_registered sieve_validator_register_tag(valdtr, cmd_reg, ext, &include_personal_tag, 0); sieve_validator_register_tag(valdtr, cmd_reg, ext, &include_global_tag, 0); sieve_validator_register_tag(valdtr, cmd_reg, ext, &include_once_tag, 0); + sieve_validator_register_tag(valdtr, cmd_reg, ext, &include_optional_tag, 0); return TRUE; } @@ -204,7 +216,7 @@ static bool cmd_include_validate struct sieve_script *script; const char *script_location, *script_name; enum sieve_error error = SIEVE_ERROR_NONE; - bool include = TRUE; + int ret; /* Check argument */ if ( !sieve_validate_positional_argument @@ -251,33 +263,47 @@ static bool cmd_include_validate (this_ext->svinst, script_location, script_name, sieve_validator_error_handler(valdtr), &error); - if ( script == NULL ) { + ret = 0; + if ( script != NULL ) + ret = sieve_script_open(script, &error); + + if ( script == NULL || ret < 0 ) { if ( error != SIEVE_ERROR_NOT_FOUND ) { + if ( script != NULL ) + sieve_script_unref(&script); return FALSE; + + /* Not found */ } else { enum sieve_compile_flags cpflags = sieve_validator_compile_flags(valdtr); - if ( (cpflags & SIEVE_COMPILE_FLAG_UPLOADED) != 0 ) { + if ( (ctx_data->flags & EXT_INCLUDE_FLAG_OPTIONAL) != 0 ) { + /* :optional */ + + } else if ( (cpflags & SIEVE_COMPILE_FLAG_UPLOADED) != 0 ) { + /* Script is being uploaded */ sieve_argument_validate_warning(valdtr, arg, "included %s script '%s' does not exist (ignored during upload)", ext_include_script_location_name(ctx_data->location), str_sanitize(script_name, 80)); - include = FALSE; + ctx_data->flags |= EXT_INCLUDE_FLAG_MISSING_AT_UPLOAD; + } else { + /* Should have existed */ sieve_argument_validate_error(valdtr, arg, "included %s script '%s' does not exist", ext_include_script_location_name(ctx_data->location), str_sanitize(script_name, 80)); + if ( script != NULL ) + sieve_script_unref(&script); return FALSE; } } } - if ( include ) { - ext_include_ast_link_included_script(cmd->ext, cmd->ast_node->ast, script); - ctx_data->script = script; - } + ext_include_ast_link_included_script(cmd->ext, cmd->ast_node->ast, script); + ctx_data->script = script; (void)sieve_ast_arguments_detach(arg, 1); return TRUE; @@ -293,26 +319,20 @@ static bool cmd_include_generate struct cmd_include_context_data *ctx_data = (struct cmd_include_context_data *) cmd->data; const struct ext_include_script_info *included; - unsigned int flags = ctx_data->include_once; int ret; - /* Upon upload ctx_data->script may be NULL if the script was not found. We - * don't emit any code for this include command in that case. + /* Compile (if necessary) and include the script into the binary. + * This yields the id of the binary block containing the compiled byte code. */ - if ( ctx_data->script != NULL ) { - /* Compile (if necessary) and include the script into the binary. - * This yields the id of the binary block containing the compiled byte code. - */ - if ( (ret=ext_include_generate_include - (cgenv, cmd, ctx_data->location, ctx_data->script, &included, - ctx_data->include_once)) < 0 ) - return FALSE; - - if ( ret > 0 ) { - (void)sieve_operation_emit(cgenv->sblock, cmd->ext, &include_operation); - (void)sieve_binary_emit_unsigned(cgenv->sblock, included->id); - (void)sieve_binary_emit_byte(cgenv->sblock, flags); - } + if ( (ret=ext_include_generate_include + (cgenv, cmd, ctx_data->location, ctx_data->flags, ctx_data->script, + &included)) < 0 ) + return FALSE; + + if ( ret > 0 ) { + (void)sieve_operation_emit(cgenv->sblock, cmd->ext, &include_operation); + (void)sieve_binary_emit_unsigned(cgenv->sblock, included->id); + (void)sieve_binary_emit_byte(cgenv->sblock, ctx_data->flags); } return TRUE; @@ -371,7 +391,8 @@ static int opc_include_execute return SIEVE_EXEC_BIN_CORRUPT; } - return ext_include_execute_include(renv, include_id, flags & 0x01); + return ext_include_execute_include + (renv, include_id, (enum ext_include_flags)flags); } diff --git a/src/lib-sieve/plugins/include/ext-include-binary.c b/src/lib-sieve/plugins/include/ext-include-binary.c index acf2e32a2..38a893625 100644 --- a/src/lib-sieve/plugins/include/ext-include-binary.c +++ b/src/lib-sieve/plugins/include/ext-include-binary.c @@ -122,16 +122,18 @@ struct ext_include_binary_context *ext_include_binary_init */ const struct ext_include_script_info *ext_include_binary_script_include -(struct ext_include_binary_context *binctx, struct sieve_script *script, - enum ext_include_script_location location, struct sieve_binary_block *inc_block) +(struct ext_include_binary_context *binctx, + enum ext_include_script_location location, enum ext_include_flags flags, + struct sieve_script *script, struct sieve_binary_block *inc_block) { pool_t pool = sieve_binary_pool(binctx->binary); struct ext_include_script_info *incscript; incscript = p_new(pool, struct ext_include_script_info, 1); incscript->id = array_count(&binctx->include_index)+1; - incscript->script = script; incscript->location = location; + incscript->flags = flags; + incscript->script = script; incscript->block = inc_block; /* Unreferenced on binary_free */ @@ -220,9 +222,15 @@ static bool ext_include_binary_save for ( i = 0; i < script_count; i++ ) { struct ext_include_script_info *incscript = scripts[i]; - sieve_binary_emit_unsigned(sblock, sieve_binary_block_get_id(incscript->block)); + if ( incscript->block != NULL ) { + sieve_binary_emit_unsigned + (sblock, sieve_binary_block_get_id(incscript->block)); + } else { + sieve_binary_emit_unsigned(sblock, 0); + } sieve_binary_emit_byte(sblock, incscript->location); sieve_binary_emit_cstring(sblock, sieve_script_name(incscript->script)); + sieve_binary_emit_byte(sblock, incscript->flags); sieve_script_binary_write_metadata(incscript->script, sblock); } @@ -267,17 +275,19 @@ static bool ext_include_binary_open /* Read dependencies */ for ( i = 0; i < depcount; i++ ) { unsigned int inc_block_id; - struct sieve_binary_block *inc_block; - unsigned int location; + struct sieve_binary_block *inc_block = NULL; + unsigned int location, flags; string_t *script_name; const char *script_location; struct sieve_script *script; + enum sieve_error error; int ret; if ( !sieve_binary_read_unsigned(sblock, &offset, &inc_block_id) || !sieve_binary_read_byte(sblock, &offset, &location) || - !sieve_binary_read_string(sblock, &offset, &script_name) ) { + !sieve_binary_read_string(sblock, &offset, &script_name) || + !sieve_binary_read_byte(sblock, &offset, &flags) ) { /* Binary is corrupt, recompile */ sieve_sys_error(svinst, "include: failed to read included script " @@ -286,7 +296,8 @@ static bool ext_include_binary_open return FALSE; } - if ( (inc_block=sieve_binary_block_get(sbin, inc_block_id)) == NULL ) { + if ( inc_block_id != 0 && + (inc_block=sieve_binary_block_get(sbin, inc_block_id)) == NULL ) { sieve_sys_error(svinst, "include: failed to find block %d for included script " "from dependency block %d of binary %s", inc_block_id, block_id, @@ -304,23 +315,59 @@ static bool ext_include_binary_open return FALSE; } - /* Can we find/open the script dependency ? */ + /* Can we find the script dependency ? */ script_location = ext_include_get_script_location (ext, location, str_c(script_name)); - if ( script_location == NULL || (script=sieve_script_create - (ext->svinst, script_location, str_c(script_name), NULL, NULL)) == NULL ) - { + if ( script_location == NULL ) { /* No, recompile */ return FALSE; } - if ( (ret=sieve_script_binary_read_metadata(script, sblock, &offset)) - < 0 ) { + /* Can we open the script dependency ? */ + script = sieve_script_create + (ext->svinst, script_location, str_c(script_name), NULL, &error); + if ( script == NULL ) { + /* No, recompile */ + return FALSE; + } + if ( sieve_script_open(script, &error) < 0 ) { + if ( error != SIEVE_ERROR_NOT_FOUND ) { + /* No, recompile */ + return FALSE; + } + + if ( (flags & EXT_INCLUDE_FLAG_OPTIONAL) == 0 && + (flags & EXT_INCLUDE_FLAG_MISSING_AT_UPLOAD) == 0) { + /* Not supposed to be missing, recompile */ + if ( svinst->debug ) { + sieve_sys_debug(svinst, + "include: script '%s' contained in binary %s is now missing, " + "so recompile", str_c(script_name), sieve_binary_path(sbin)); + } + return FALSE; + } + + } else if (inc_block == NULL) { + /* Script exists, but it is missing from the binary, recompile no matter + * what. + */ + if ( svinst->debug ) { + sieve_sys_debug(svinst, + "include: script '%s' is missing in binary %s, but is now available, " + "so recompile", str_c(script_name), sieve_binary_path(sbin)); + } + return FALSE; + } + + /* Can we read script metadata ? */ + if ( (ret=sieve_script_binary_read_metadata + (script, sblock, &offset)) < 0 ) { /* Binary is corrupt, recompile */ sieve_sys_error(svinst, "include: dependency block %d of binary %s " "contains invalid script metadata for script %s", block_id, sieve_binary_path(sbin), sieve_script_location(script)); + sieve_script_unref(&script); return FALSE; } @@ -328,7 +375,7 @@ static bool ext_include_binary_open binctx->outdated = TRUE; (void)ext_include_binary_script_include - (binctx, script, location, inc_block); + (binctx, location, flags, script, inc_block); sieve_script_unref(&script); } @@ -363,7 +410,8 @@ static void ext_include_binary_free /* Release references to all included script objects */ hctx = hash_table_iterate_init(binctx->included_scripts); - while ( hash_table_iterate(hctx, binctx->included_scripts, &script, &incscript) ) + while ( hash_table_iterate + (hctx, binctx->included_scripts, &script, &incscript) ) sieve_script_unref(&incscript->script); hash_table_iterate_deinit(&hctx); @@ -391,23 +439,31 @@ bool ext_include_binary_dump return FALSE; hctx = hash_table_iterate_init(binctx->included_scripts); - while ( hash_table_iterate(hctx, binctx->included_scripts, &script, &incscript) ) { - unsigned int block_id = sieve_binary_block_get_id(incscript->block); + while ( hash_table_iterate + (hctx, binctx->included_scripts, &script, &incscript) ) { - sieve_binary_dump_sectionf(denv, "Included %s script '%s' (block: %d)", - ext_include_script_location_name(incscript->location), - sieve_script_name(incscript->script), block_id); + if ( incscript->block == NULL ) { + sieve_binary_dump_sectionf(denv, "Included %s script '%s' (MISSING)", + ext_include_script_location_name(incscript->location), + sieve_script_name(incscript->script)); - denv->sblock = incscript->block; - denv->cdumper = sieve_code_dumper_create(denv); + } else { + unsigned int block_id = sieve_binary_block_get_id(incscript->block); - if ( denv->cdumper == NULL ) - return FALSE; + sieve_binary_dump_sectionf(denv, "Included %s script '%s' (block: %d)", + ext_include_script_location_name(incscript->location), + sieve_script_name(incscript->script), block_id); - sieve_code_dumper_run(denv->cdumper); - sieve_code_dumper_free(&(denv->cdumper)); - } + denv->sblock = incscript->block; + denv->cdumper = sieve_code_dumper_create(denv); + if ( denv->cdumper == NULL ) + return FALSE; + + sieve_code_dumper_run(denv->cdumper); + sieve_code_dumper_free(&(denv->cdumper)); + } + } hash_table_iterate_deinit(&hctx); return TRUE; diff --git a/src/lib-sieve/plugins/include/ext-include-binary.h b/src/lib-sieve/plugins/include/ext-include-binary.h index 448ae9673..0d9ca0b09 100644 --- a/src/lib-sieve/plugins/include/ext-include-binary.h +++ b/src/lib-sieve/plugins/include/ext-include-binary.h @@ -30,18 +30,19 @@ struct sieve_variable_scope_binary *ext_include_binary_get_global_scope */ struct ext_include_script_info { - unsigned int id; + unsigned int id; - struct sieve_script *script; - enum ext_include_script_location location; + struct sieve_script *script; + enum ext_include_flags flags; + enum ext_include_script_location location; - struct sieve_binary_block *block; + struct sieve_binary_block *block; }; const struct ext_include_script_info *ext_include_binary_script_include - (struct ext_include_binary_context *binctx, struct sieve_script *script, - enum ext_include_script_location location, - struct sieve_binary_block *block); + (struct ext_include_binary_context *binctx, + enum ext_include_script_location location, enum ext_include_flags flags, + struct sieve_script *script, struct sieve_binary_block *inc_block); bool ext_include_binary_script_is_included (struct ext_include_binary_context *binctx, struct sieve_script *script, const struct ext_include_script_info **script_info_r); diff --git a/src/lib-sieve/plugins/include/ext-include-common.c b/src/lib-sieve/plugins/include/ext-include-common.c index 28e044cf4..abdab4631 100644 --- a/src/lib-sieve/plugins/include/ext-include-common.c +++ b/src/lib-sieve/plugins/include/ext-include-common.c @@ -454,8 +454,9 @@ struct sieve_variable_storage *ext_include_interpreter_get_global_variables int ext_include_generate_include (const struct sieve_codegen_env *cgenv, struct sieve_command *cmd, - enum ext_include_script_location location, struct sieve_script *script, - const struct ext_include_script_info **included_r, bool once) + enum ext_include_script_location location, enum ext_include_flags flags, + struct sieve_script *script, + const struct ext_include_script_info **included_r) { const struct sieve_extension *this_ext = cmd->ext; struct ext_include_context *ext_ctx = @@ -489,7 +490,7 @@ int ext_include_generate_include } /* Check for circular include */ - if ( !once ) { + if ( (flags & EXT_INCLUDE_FLAG_ONCE) == 0 ) { pctx = ctx; while ( pctx != NULL ) { if ( sieve_script_equals(pctx->script, script) ) { @@ -517,7 +518,6 @@ int ext_include_generate_include /* Is the script already compiled into the current binary? */ if ( !ext_include_binary_script_is_included(binctx, script, &included) ) { - struct sieve_binary_block *inc_block; const char *script_name = sieve_script_name(script); enum sieve_compile_flags cpflags = cgenv->flags; @@ -532,55 +532,65 @@ int ext_include_generate_include /* No, allocate a new block in the binary and mark the script as included. */ - inc_block = sieve_binary_block_create(sbin); - included = ext_include_binary_script_include - (binctx, script, location, inc_block); - - /* Parse */ - if ( (ast = sieve_parse(script, ehandler, NULL)) == NULL ) { - sieve_command_generate_error(gentr, cmd, - "failed to parse included script '%s'", str_sanitize(script_name, 80)); - return -1; - } + if ( !sieve_script_is_open(script) ) { + i_assert((flags & EXT_INCLUDE_FLAG_MISSING_AT_UPLOAD) != 0 || + (flags & EXT_INCLUDE_FLAG_OPTIONAL) != 0); + included = ext_include_binary_script_include + (binctx, location, flags, script, NULL); + result = 0; + + } else { + struct sieve_binary_block *inc_block = sieve_binary_block_create(sbin); + + included = ext_include_binary_script_include + (binctx, location, flags, script, inc_block); + + /* Parse */ + if ( (ast = sieve_parse(script, ehandler, NULL)) == NULL ) { + sieve_command_generate_error(gentr, cmd, + "failed to parse included script '%s'", str_sanitize(script_name, 80)); + return -1; + } - /* Included scripts inherit global variable scope */ - (void)ext_include_create_ast_context(this_ext, ast, cmd->ast_node->ast); + /* Included scripts inherit global variable scope */ + (void)ext_include_create_ast_context(this_ext, ast, cmd->ast_node->ast); - if ( location == EXT_INCLUDE_LOCATION_GLOBAL ) + if ( location == EXT_INCLUDE_LOCATION_GLOBAL ) cpflags &= ~SIEVE_RUNTIME_FLAG_NOGLOBAL; - else + else cpflags |= SIEVE_RUNTIME_FLAG_NOGLOBAL; - /* Validate */ - if ( !sieve_validate(ast, ehandler, cpflags, NULL) ) { - sieve_command_generate_error(gentr, cmd, - "failed to validate included script '%s'", - str_sanitize(script_name, 80)); - sieve_ast_unref(&ast); - return -1; - } + /* Validate */ + if ( !sieve_validate(ast, ehandler, cpflags, NULL) ) { + sieve_command_generate_error(gentr, cmd, + "failed to validate included script '%s'", + str_sanitize(script_name, 80)); + sieve_ast_unref(&ast); + return -1; + } + + /* Generate + * + * FIXME: It might not be a good idea to recurse code generation for + * included scripts. + */ + subgentr = sieve_generator_create(ast, ehandler, cpflags); + ext_include_initialize_generator_context(cmd->ext, subgentr, ctx, script); + + if ( sieve_generator_run(subgentr, &inc_block) == NULL ) { + sieve_command_generate_error(gentr, cmd, + "failed to generate code for included script '%s'", + str_sanitize(script_name, 80)); + result = -1; + } - /* Generate - * - * FIXME: It might not be a good idea to recurse code generation for - * included scripts. - */ - subgentr = sieve_generator_create(ast, ehandler, cpflags); - ext_include_initialize_generator_context(cmd->ext, subgentr, ctx, script); + sieve_generator_free(&subgentr); - if ( sieve_generator_run(subgentr, &inc_block) == NULL ) { - sieve_command_generate_error(gentr, cmd, - "failed to generate code for included script '%s'", - str_sanitize(script_name, 80)); - result = -1; + /* Cleanup */ + sieve_ast_unref(&ast); } - - sieve_generator_free(&subgentr); - - /* Cleanup */ - sieve_ast_unref(&ast); } - + if ( result > 0 ) *included_r = included; return result; diff --git a/src/lib-sieve/plugins/include/ext-include-common.h b/src/lib-sieve/plugins/include/ext-include-common.h index 20763ed07..53b1131f0 100644 --- a/src/lib-sieve/plugins/include/ext-include-common.h +++ b/src/lib-sieve/plugins/include/ext-include-common.h @@ -21,6 +21,12 @@ struct ext_include_binary_context; * Types */ +enum ext_include_flags { // stored in one byte + EXT_INCLUDE_FLAG_ONCE = 0x01, + EXT_INCLUDE_FLAG_OPTIONAL = 0x02, + EXT_INCLUDE_FLAG_MISSING_AT_UPLOAD = 0x04 +}; + enum ext_include_script_location { EXT_INCLUDE_LOCATION_PERSONAL, EXT_INCLUDE_LOCATION_GLOBAL, @@ -144,8 +150,9 @@ void ext_include_register_generator_context int ext_include_generate_include (const struct sieve_codegen_env *cgenv, struct sieve_command *cmd, - enum ext_include_script_location location, struct sieve_script *script, - const struct ext_include_script_info **included_r, bool once); + enum ext_include_script_location location, + enum ext_include_flags flags, struct sieve_script *script, + const struct ext_include_script_info **included_r); /* Interpreter context */ diff --git a/src/lib-sieve/plugins/include/ext-include.c b/src/lib-sieve/plugins/include/ext-include.c index 6471edda3..9172d5d01 100644 --- a/src/lib-sieve/plugins/include/ext-include.c +++ b/src/lib-sieve/plugins/include/ext-include.c @@ -64,6 +64,8 @@ static bool ext_include_binary_load const struct sieve_extension_def include_extension = { .name = "include", + .version = 1, + .load = ext_include_load, .unload = ext_include_unload, .validator_load = ext_include_validator_load, @@ -72,6 +74,7 @@ const struct sieve_extension_def include_extension = { .binary_load = ext_include_binary_load, .binary_dump = ext_include_binary_dump, .code_dump = ext_include_code_dump, + SIEVE_EXT_DEFINE_OPERATIONS(ext_include_operations) }; diff --git a/src/lib-sieve/sieve-lexer.c b/src/lib-sieve/sieve-lexer.c index 20af2c83f..e7527fe7e 100644 --- a/src/lib-sieve/sieve-lexer.c +++ b/src/lib-sieve/sieve-lexer.c @@ -71,8 +71,7 @@ const struct sieve_lexer *sieve_lexer_create const struct stat *st; /* Open script as stream */ - stream = sieve_script_open(script, error_r); - if ( stream == NULL ) + if ( sieve_script_get_stream(script, &stream, error_r) < 0 ) return NULL; /* Check script size */ @@ -121,7 +120,6 @@ void sieve_lexer_free(const struct sieve_lexer **lexer) i_stream_unref(&scanner->input); - sieve_script_close(scanner->script); sieve_script_unref(&scanner->script); sieve_error_handler_unref(&scanner->ehandler); diff --git a/src/lib-sieve/sieve-script-dict.c b/src/lib-sieve/sieve-script-dict.c index 489c6bbab..1ea1fd224 100644 --- a/src/lib-sieve/sieve-script-dict.c +++ b/src/lib-sieve/sieve-script-dict.c @@ -49,7 +49,18 @@ static struct sieve_script *sieve_dict_script_alloc(void) return &script->script; } -static int sieve_dict_script_create +static void sieve_dict_script_free(struct sieve_script *_script) +{ + struct sieve_dict_script *script = (struct sieve_dict_script *)_script; + + if ( script->dict != NULL ) + dict_deinit(&script->dict); + + if ( script->data_pool != NULL ) + pool_unref(&script->data_pool); +} + +static int sieve_dict_script_open (struct sieve_script *_script, const char *data, const char *const *options, enum sieve_error *error_r) { @@ -154,19 +165,9 @@ static int sieve_dict_script_create return 0; } -static void sieve_dict_script_destroy(struct sieve_script *_script) -{ - struct sieve_dict_script *script = (struct sieve_dict_script *)_script; - - if ( script->dict != NULL ) - dict_deinit(&script->dict); - - if ( script->data_pool != NULL ) - pool_unref(&script->data_pool); -} - -static struct istream *sieve_dict_script_open -(struct sieve_script *_script, enum sieve_error *error_r) +static int sieve_dict_script_get_stream +(struct sieve_script *_script, struct istream **stream_r, + enum sieve_error *error_r) { struct sieve_dict_script *script = (struct sieve_dict_script *)_script; struct sieve_instance *svinst = _script->svinst; @@ -194,21 +195,11 @@ static struct istream *sieve_dict_script_open "not found at path %s", script->data_id, name, path); } *error_r = SIEVE_ERROR_TEMP_FAIL; - return NULL; + return -1; } - return i_stream_create_from_data(script->data, strlen(script->data)); -} - -static void sieve_dict_script_close(struct sieve_script *_script) -{ - struct sieve_dict_script *script = (struct sieve_dict_script *)_script; - - pool_unref(&script->data_pool); - - script->data_pool = NULL; - script->data_id = NULL; - script->data = NULL; + *stream_r = i_stream_create_from_data(script->data, strlen(script->data)); + return 0; } static int sieve_dict_script_binary_read_metadata @@ -270,18 +261,18 @@ static int sieve_dict_script_binary_save static bool sieve_dict_script_equals (const struct sieve_script *_script, const struct sieve_script *_other) { - struct sieve_dict_script *script = (struct sieve_dict_script *)_script; - struct sieve_dict_script *other = (struct sieve_dict_script *)_other; + struct sieve_dict_script *script = (struct sieve_dict_script *)_script; + struct sieve_dict_script *other = (struct sieve_dict_script *)_other; - if ( script == NULL || other == NULL ) - return FALSE; + if ( script == NULL || other == NULL ) + return FALSE; - if ( strcmp(script->dict_uri, other->dict_uri) != 0 ) + if ( strcmp(script->dict_uri, other->dict_uri) != 0 ) return FALSE; i_assert( _script->name != NULL && _other->name != NULL ); - return ( strcmp(_script->name, _other->name) == 0 ); + return ( strcmp(_script->name, _other->name) == 0 ); } @@ -289,11 +280,11 @@ const struct sieve_script sieve_dict_script = { .driver_name = SIEVE_DICT_SCRIPT_DRIVER_NAME, .v = { sieve_dict_script_alloc, - sieve_dict_script_create, - sieve_dict_script_destroy, + sieve_dict_script_free, sieve_dict_script_open, - sieve_dict_script_close, + + sieve_dict_script_get_stream, sieve_dict_script_binary_read_metadata, sieve_dict_script_binary_write_metadata, diff --git a/src/lib-sieve/sieve-script-file.c b/src/lib-sieve/sieve-script-file.c index d7c184f81..2e3bf60d7 100644 --- a/src/lib-sieve/sieve-script-file.c +++ b/src/lib-sieve/sieve-script-file.c @@ -113,7 +113,7 @@ static struct sieve_script *sieve_file_script_alloc(void) return &script->script; } -static int sieve_file_script_create +static int sieve_file_script_open (struct sieve_script *_script, const char *path, const char *const *options, enum sieve_error *error_r) { @@ -248,8 +248,9 @@ static int sieve_file_script_create return ( success ? 0 : -1 ); } -static struct istream *sieve_file_script_open -(struct sieve_script *_script, enum sieve_error *error_r) +static int sieve_file_script_get_stream +(struct sieve_script *_script, struct istream **stream_r, + enum sieve_error *error_r) { struct sieve_file_script *script = (struct sieve_file_script *)_script; struct sieve_instance *svinst = _script->svinst; @@ -261,7 +262,7 @@ static struct istream *sieve_file_script_open if ( (fd=open(script->path, O_RDONLY)) < 0 ) { sieve_file_script_handle_error(_script, script->path, name, error_r); - return NULL; + return -1; } if ( fstat(fd, &st) != 0 ) { @@ -292,7 +293,8 @@ static struct istream *sieve_file_script_open } } - return result; + *stream_r = result; + return 0; } static int sieve_file_script_get_size @@ -356,11 +358,11 @@ const struct sieve_script sieve_file_script = { .driver_name = SIEVE_FILE_SCRIPT_DRIVER_NAME, .v = { sieve_file_script_alloc, - sieve_file_script_create, NULL, sieve_file_script_open, - NULL, + + sieve_file_script_get_stream, sieve_file_script_binary_read_metadata, NULL, diff --git a/src/lib-sieve/sieve-script-private.h b/src/lib-sieve/sieve-script-private.h index e317ff0d7..955885226 100644 --- a/src/lib-sieve/sieve-script-private.h +++ b/src/lib-sieve/sieve-script-private.h @@ -12,17 +12,16 @@ struct sieve_script_vfuncs { struct sieve_script *(*alloc)(void); - int (*create) - (struct sieve_script *script, const char *data, const char *const *options, - enum sieve_error *error_r); - void (*destroy) - (struct sieve_script *script); + void (*destroy)(struct sieve_script *script); - struct istream *(*open) - (struct sieve_script *script, enum sieve_error *error_r); - void (*close) - (struct sieve_script *script); + int (*open) + (struct sieve_script *script, const char *data, + const char *const *options, enum sieve_error *error_r); + int (*get_stream) + (struct sieve_script *script, struct istream **stream_r, + enum sieve_error *error_r); + int (*binary_read_metadata) (struct sieve_script *_script, struct sieve_binary_block *sblock, sieve_size_t *offset); @@ -53,18 +52,20 @@ struct sieve_script { struct sieve_error_handler *ehandler; const char *name; + const char *data; const char *location; const char *bin_dir; /* Stream */ struct istream *stream; + + unsigned int open:1; }; -struct sieve_script *sieve_script_init +void sieve_script_init (struct sieve_script *script, struct sieve_instance *svinst, const struct sieve_script *script_class, const char *data, - const char *name, struct sieve_error_handler *ehandler, - enum sieve_error *error_r); + const char *name, struct sieve_error_handler *ehandler); int sieve_script_setup_bindir (struct sieve_script *script, mode_t mode); diff --git a/src/lib-sieve/sieve-script.c b/src/lib-sieve/sieve-script.c index 82558674f..e627125f2 100644 --- a/src/lib-sieve/sieve-script.c +++ b/src/lib-sieve/sieve-script.c @@ -180,51 +180,19 @@ static bool sieve_script_location_parse return TRUE; } -struct sieve_script *sieve_script_init +void sieve_script_init (struct sieve_script *script, struct sieve_instance *svinst, - const struct sieve_script *script_class, const char *data, const char *name, - struct sieve_error_handler *ehandler, enum sieve_error *error_r) + const struct sieve_script *script_class, const char *data, + const char *name, struct sieve_error_handler *ehandler) { - enum sieve_error error; - const char *const *options = NULL; - const char *location = NULL, *parse_error = NULL; - - if ( error_r != NULL ) - *error_r = SIEVE_ERROR_NONE; - script->script_class = script_class; script->refcount = 1; script->svinst = svinst; - script->ehandler = ehandler; - + script->data = p_strdup_empty(script->pool, data); script->name = p_strdup_empty(script->pool, name); - if ( !sieve_script_location_parse - (script, data, &location, &options, &parse_error) ) { - sieve_critical(svinst, ehandler, NULL, - "failed to access sieve script", "failed to parse script location: %s", - parse_error); - if ( error_r != NULL ) - *error_r = SIEVE_ERROR_TEMP_FAIL; - return NULL; - } - - if ( script->v.create(script, location, options, &error) < 0 ) { - - if ( error_r == NULL ) { - if ( error == SIEVE_ERROR_NOT_FOUND ) - sieve_error(ehandler, script->name, "sieve script does not exist"); - } else { - *error_r = error; - } - return NULL; - } - - i_assert( script->location != NULL ); - sieve_error_handler_ref(ehandler); - return script; } struct sieve_script *sieve_script_create @@ -257,36 +225,109 @@ struct sieve_script *sieve_script_create else script_class = NULL; - if ( script_class == NULL ) - i_error("Unknown sieve script driver module: %s", driver); + if ( script_class == NULL ) { + sieve_sys_error(svinst, + "Unknown sieve script driver module: %s", driver); + } } T_END; } - if ( script_class == NULL ) + if ( script_class == NULL ) { + if ( error_r != NULL ) + *error_r = SIEVE_ERROR_TEMP_FAIL; return NULL; + } script = script_class->v.alloc(); - if ( sieve_script_init - (script, svinst, script_class, data, name, ehandler, error_r) == NULL ) { - pool_unref(&script->pool); - return NULL; + sieve_script_init(script, svinst, script_class, data, name, ehandler); + return script; +} + +int sieve_script_open +(struct sieve_script *script, enum sieve_error *error_r) +{ + struct sieve_instance *svinst = script->svinst; + struct sieve_error_handler *ehandler = script->ehandler; + enum sieve_error error; + const char *const *options = NULL; + const char *location = NULL, *parse_error = NULL; + + if ( error_r != NULL ) + *error_r = SIEVE_ERROR_NONE; + + if ( script->open ) + return 0; + + if ( !sieve_script_location_parse + (script, script->data, &location, &options, &parse_error) ) { + sieve_critical(svinst, ehandler, NULL, + "failed to access sieve script", "failed to parse script location: %s", + parse_error); + if ( error_r != NULL ) + *error_r = SIEVE_ERROR_TEMP_FAIL; + return -1; + } + + if ( script->v.open(script, location, options, &error) < 0 ) { + if ( error_r == NULL ) { + if ( error == SIEVE_ERROR_NOT_FOUND ) + sieve_error(ehandler, script->name, "sieve script does not exist"); + } else { + *error_r = error; + } + return -1; } + i_assert( script->location != NULL ); + i_assert( script->name != NULL ); + script->open = TRUE; + return 0; +} + +int sieve_script_open_as +(struct sieve_script *script, const char *name, enum sieve_error *error_r) +{ + if ( sieve_script_open(script, error_r) < 0 ) + return -1; + + /* override name */ + script->name = p_strdup(script->pool, name); + return 0; +} + +struct sieve_script *sieve_script_create_open +(struct sieve_instance *svinst, const char *location, const char *name, + struct sieve_error_handler *ehandler, enum sieve_error *error_r) +{ + struct sieve_script *script; + + script = sieve_script_create(svinst, location, name, ehandler, error_r); + if ( script == NULL ) + return NULL; + + if ( sieve_script_open(script, error_r) < 0 ) { + sieve_script_unref(&script); + return NULL; + } + return script; } -struct sieve_script *sieve_script_create_as +struct sieve_script *sieve_script_create_open_as (struct sieve_instance *svinst, const char *location, const char *name, struct sieve_error_handler *ehandler, enum sieve_error *error_r) { struct sieve_script *script; - if ( (script=sieve_script_create(svinst, location, NULL, ehandler, error_r)) - == NULL ) + script = sieve_script_create(svinst, location, name, ehandler, error_r); + if ( script == NULL ) return NULL; - /* override name */ - script->name = p_strdup(script->pool, name); + if ( sieve_script_open_as(script, name, error_r) < 0 ) { + sieve_script_unref(&script); + return NULL; + } + return script; } @@ -338,6 +379,7 @@ struct sieve_instance *sieve_script_svinst(const struct sieve_script *script) int sieve_script_get_size(struct sieve_script *script, uoff_t *size_r) { + struct istream *stream; int ret; if ( script->v.get_size != NULL ) { @@ -346,31 +388,42 @@ int sieve_script_get_size(struct sieve_script *script, uoff_t *size_r) } /* Try getting size from the stream */ - if ( script->stream == NULL && sieve_script_open(script, NULL) == NULL ) + if ( script->stream == NULL && + sieve_script_get_stream(script, &stream, NULL) < 0 ) return -1; return i_stream_get_size(script->stream, TRUE, size_r); } +bool sieve_script_is_open(const struct sieve_script *script) +{ + return script->open; +} + /* * Stream management */ -struct istream *sieve_script_open -(struct sieve_script *script, enum sieve_error *error_r) +int sieve_script_get_stream +(struct sieve_script *script, struct istream **stream_r, + enum sieve_error *error_r) { enum sieve_error error; + int ret; if ( error_r != NULL ) *error_r = SIEVE_ERROR_NONE; - if ( script->stream == NULL ) { - T_BEGIN { - script->stream = script->v.open(script, &error); - } T_END; + if ( script->stream != NULL ) { + *stream_r = script->stream; + return 0; } - if ( script->stream == NULL ) { + T_BEGIN { + ret = script->v.get_stream(script, &script->stream, &error); + } T_END; + + if ( ret < 0 ) { if ( error_r == NULL ) { if ( error == SIEVE_ERROR_NOT_FOUND ) { sieve_error(script->ehandler, script->name, @@ -379,23 +432,11 @@ struct istream *sieve_script_open } else { *error_r = error; } + return -1; } - return script->stream; -} - -void sieve_script_close(struct sieve_script *script) -{ - if ( script->stream != NULL ) - return; - - i_stream_unref(&script->stream); - - if ( script->v.close != NULL ) { - T_BEGIN { - script->v.close(script); - } T_END; - } + *stream_r = script->stream; + return 0; } /* @@ -425,6 +466,8 @@ bool sieve_script_equals unsigned int sieve_script_hash(const struct sieve_script *script) { + i_assert( script->name != NULL ); + return str_hash(script->name); } diff --git a/src/lib-sieve/sieve-script.h b/src/lib-sieve/sieve-script.h index 93a3d93d4..1d9a42518 100644 --- a/src/lib-sieve/sieve-script.h +++ b/src/lib-sieve/sieve-script.h @@ -26,13 +26,22 @@ ARRAY_DEFINE_TYPE(sieve_scripts, struct sieve_script *); struct sieve_script *sieve_script_create (struct sieve_instance *svinst, const char *location, const char *name, struct sieve_error_handler *ehandler, enum sieve_error *error_r); -struct sieve_script *sieve_script_create_as - (struct sieve_instance *svinst, const char *location, const char *name, - struct sieve_error_handler *ehandler, enum sieve_error *error_r); void sieve_script_ref(struct sieve_script *script); void sieve_script_unref(struct sieve_script **script); +int sieve_script_open + (struct sieve_script *script, enum sieve_error *error_r); +int sieve_script_open_as + (struct sieve_script *script, const char *name, enum sieve_error *error_r); + +struct sieve_script *sieve_script_create_open + (struct sieve_instance *svinst, const char *location, const char *name, + struct sieve_error_handler *ehandler, enum sieve_error *error_r); +struct sieve_script *sieve_script_create_open_as + (struct sieve_instance *svinst, const char *location, const char *name, + struct sieve_error_handler *ehandler, enum sieve_error *error_r); + /* * Accessors */ @@ -41,6 +50,8 @@ const char *sieve_script_name(const struct sieve_script *script); const char *sieve_script_location(const struct sieve_script *script); struct sieve_instance *sieve_script_svinst(const struct sieve_script *script); +bool sieve_script_is_open(const struct sieve_script *script); + /* * Saving/loading Sieve binaries */ @@ -61,10 +72,9 @@ int sieve_script_binary_save * Stream management */ -struct istream *sieve_script_open - (struct sieve_script *script, enum sieve_error *error_r); -void sieve_script_close(struct sieve_script *script); - +int sieve_script_get_stream + (struct sieve_script *script, struct istream **stream_r, + enum sieve_error *error_r); int sieve_script_get_size(struct sieve_script *script, uoff_t *size_r); /* diff --git a/src/lib-sieve/sieve.c b/src/lib-sieve/sieve.c index 3d60dc7aa..8e310c685 100644 --- a/src/lib-sieve/sieve.c +++ b/src/lib-sieve/sieve.c @@ -255,7 +255,7 @@ struct sieve_binary *sieve_compile struct sieve_script *script; struct sieve_binary *sbin; - if ( (script = sieve_script_create + if ( (script = sieve_script_create_open (svinst, script_location, script_name, ehandler, error_r)) == NULL ) return NULL; @@ -375,10 +375,8 @@ struct sieve_binary *sieve_open struct sieve_binary *sbin; /* First open the scriptfile itself */ - script = sieve_script_create - (svinst, script_location, script_name, ehandler, error_r); - - if ( script == NULL ) { + if ( (script=sieve_script_create_open + (svinst, script_location, script_name, ehandler, error_r)) == NULL ) { /* Failed */ return NULL; } diff --git a/src/lib-sievestorage/sieve-storage-script.c b/src/lib-sievestorage/sieve-storage-script.c index 7314addcb..70583c180 100644 --- a/src/lib-sievestorage/sieve-storage-script.c +++ b/src/lib-sievestorage/sieve-storage-script.c @@ -63,18 +63,18 @@ struct sieve_script *sieve_storage_script_init_from_path st_script->file.script.pool = pool; st_script->storage = storage; - if ( sieve_script_init + sieve_script_init (&st_script->file.script, storage->svinst, &sieve_file_script, path, - scriptname, sieve_storage_get_error_handler(storage), &error) != NULL ) { - return &st_script->file.script; - } - - pool_unref(&pool); + scriptname, sieve_storage_get_error_handler(storage)); - if ( error == SIEVE_ERROR_NOT_FOUND ) - sieve_storage_set_error(storage, error, "Script does not exist."); + if ( sieve_script_open(&st_script->file.script, &error) < 0 ) { + if ( error == SIEVE_ERROR_NOT_FOUND ) + sieve_storage_set_error(storage, error, "Script does not exist."); + pool_unref(&pool); + return NULL; + } - return NULL; + return &st_script->file.script; } struct sieve_script *sieve_storage_script_init diff --git a/src/managesieve/cmd-getscript.c b/src/managesieve/cmd-getscript.c index d5aae5c8e..232fe1a90 100644 --- a/src/managesieve/cmd-getscript.c +++ b/src/managesieve/cmd-getscript.c @@ -105,9 +105,8 @@ bool cmd_getscript(struct client_command_context *cmd) return cmd_getscript_finish(ctx); } - ctx->script_stream = sieve_script_open(ctx->script, &error); - - if ( ctx->script_stream == NULL ) { + if ( sieve_script_get_stream + (ctx->script, &ctx->script_stream, &error) < 0 ) { if ( error == SIEVE_ERROR_NOT_FOUND ) sieve_storage_set_error(client->storage, error, "Script does not exist."); ctx->failed = TRUE; diff --git a/src/plugins/lda-sieve/lda-sieve-plugin.c b/src/plugins/lda-sieve/lda-sieve-plugin.c index d8f3c5b86..797acd2bd 100644 --- a/src/plugins/lda-sieve/lda-sieve-plugin.c +++ b/src/plugins/lda-sieve/lda-sieve-plugin.c @@ -605,7 +605,7 @@ static int lda_sieve_deliver_mail user_location = lda_sieve_get_personal_location(svinst, mdctx->dest_user); if ( user_location != NULL ) { - srctx.user_script = sieve_script_create_as + srctx.user_script = sieve_script_create_open_as (svinst, user_location, "main script", master_ehandler, &error); if ( srctx.user_script == NULL ) { @@ -629,7 +629,7 @@ static int lda_sieve_deliver_mail if ( srctx.user_script == NULL ) { default_location = lda_sieve_get_default_location(mdctx->dest_user); if ( default_location != NULL ) { - srctx.main_script = sieve_script_create_as + srctx.main_script = sieve_script_create_open_as (svinst, default_location, "main script", master_ehandler, &error); if ( srctx.main_script == NULL && error == SIEVE_ERROR_NOT_FOUND && diff --git a/tests/extensions/include/execute/optional.sieve b/tests/extensions/include/execute/optional.sieve new file mode 100644 index 000000000..a6ad479fe --- /dev/null +++ b/tests/extensions/include/execute/optional.sieve @@ -0,0 +1,5 @@ +require "include"; + +include :optional "optional-1"; +include :optional "optional-2"; +include :optional "optional-3"; diff --git a/tests/extensions/include/included/optional-1.sieve b/tests/extensions/include/included/optional-1.sieve new file mode 100644 index 000000000..288d1412c --- /dev/null +++ b/tests/extensions/include/included/optional-1.sieve @@ -0,0 +1,9 @@ +require "include"; +require "variables"; + +global "result"; + +set "result" "${result} ONE"; + +return; + diff --git a/tests/extensions/include/included/optional-2.sieve b/tests/extensions/include/included/optional-2.sieve new file mode 100644 index 000000000..11920f5e4 --- /dev/null +++ b/tests/extensions/include/included/optional-2.sieve @@ -0,0 +1,9 @@ +require "include"; +require "variables"; + +global "result"; + +set "result" "${result} TWO"; + +keep; + diff --git a/tests/extensions/include/optional.svtest b/tests/extensions/include/optional.svtest new file mode 100644 index 000000000..345f8309b --- /dev/null +++ b/tests/extensions/include/optional.svtest @@ -0,0 +1,40 @@ +require "vnd.dovecot.testsuite"; +require "include"; +require "variables"; + +global "result"; +set "result" ""; + +test "Included Optional" { + include :optional "optional-1"; + include :optional "optional-2"; + + if not string "${result}" " ONE TWO" { + test_fail "unexpected result value: ${result}"; + } + + # missing + include :optional "optional-3"; + + if not string "${result}" " ONE TWO" { + test_fail "unexpected result value after missing script: ${result}"; + } +} + + +test "Included Optional - Binary" { + if not test_script_compile "execute/optional.sieve" { + test_fail "failed to compile sieve script"; + } + + test_binary_save "optional"; + test_binary_load "optional"; + + if not test_script_run { + test_fail "failed to execute sieve script"; + } + + if not string "${result}" " ONE TWO" { + test_fail "unexpected result value: ${result}"; + } +} -- GitLab