RFC 5260
Sieve Email Filtering: Date and Index Extensions
Sieve Email Filtering: Date and Index Extensions ๐
Okay bestie, so this is RFC 5260 ๐, written by N. Freed at Sun Microsystems ๐ธ. It's a Standards Track doc, so it's an actual Internet standard and not just a suggestion โจ. It came out in July 2008 ๐ . The header doesn't list anything it obsoletes or updates, so it's standing alone ๐ .
Status of This Memo ๐
This one is an Internet standards track protocol for the Internet community, and it's literally asking for discussion and suggestions for improvements ๐ซถ. If you want to know where this protocol stands in the official standardization state, check the current edition of the "Internet Official Protocol Standards" (STD 1) ๐. Distribution of this memo is unlimited, so share the tea ๐.
Abstract โจ
This doc is all about the "date" and "index" extensions to the Sieve email filtering language ๐. The "date" extension lets Sieve test date and time values in a bunch of different ways ๐ฐ๏ธ. The "index" extension lets you limit header and address tests to specific instances of header fields when those fields get repeated, which is so useful ๐ฆ.
Table of Contents ๐ง
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 2
2. Conventions Used in This Document . . . . . . . . . . . . . . 2
3. Capability Identifiers . . . . . . . . . . . . . . . . . . . . 3
4. Date Test . . . . . . . . . . . . . . . . . . . . . . . . . . 3
4.1. Zone and Originalzone Arguments . . . . . . . . . . . . . 4
4.2. Date-part Argument . . . . . . . . . . . . . . . . . . . . 4
4.3. Comparator Interactions with Date-part Arguments . . . . . 5
4.4. Examples . . . . . . . . . . . . . . . . . . . . . . . . . 6
5. Currentdate Test . . . . . . . . . . . . . . . . . . . . . . . 6
5.1. Examples . . . . . . . . . . . . . . . . . . . . . . . . . 6
6. Index Extension . . . . . . . . . . . . . . . . . . . . . . . 7
6.1. Example . . . . . . . . . . . . . . . . . . . . . . . . . 8
7. Security Considerations . . . . . . . . . . . . . . . . . . . 8
8. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 9
9. References . . . . . . . . . . . . . . . . . . . . . . . . . . 9
9.1. Normative References . . . . . . . . . . . . . . . . . . . 9
9.2. Informative References . . . . . . . . . . . . . . . . . . 10
Appendix A. Julian Date Conversions . . . . . . . . . . . . . . . 11
Appendix B. Acknowledgements . . . . . . . . . . . . . . . . . . 12
1. Introduction โจ
Okay so, Sieve [RFC5228] is a language for filtering email messages at or around the time of final delivery ๐ฌ. It's designed so it can be implemented on either a mail client or a mail server ๐ป. It's meant to be extensible, simple, and independent of access protocol, mail architecture, and operating system ๐ง . It's also perfect for a mail server where users might not be allowed to run arbitrary programs, like on black box Internet Message Access Protocol [RFC3501] servers ๐ฅ๏ธ. Why? Because it doesn't have user-controlled loops or the ability to run external programs ๐ โโ๏ธ.
The "date" extension adds a new date test that pulls out and matches date/time info from structured header fields ๐๏ธ. It's kind of similar in concept to the address test from [RFC5228], which does similar stuff on addresses in header fields ๐ .
The "date" extension also adds a currentdate test, which looks at the date and time when the Sieve script actually runs โฐ.
Here's the thing though ๐ค. Some header fields with date/time info, like Received:, naturally show up more than once in a single header. So it's really useful to be able to restrict the date test to some subset of the fields that are there. For example, you might want to apply a date test to the last (earliest) Received: field ๐. Also, it might be useful to apply similar restrictions to the header or address tests from [RFC5228] too ๐.
That's why this spec also defines an "index" extension ๐. It adds two extra tagged arguments, :index and :last, to the header, address, and date tests. If they're present, they say which occurrence of the named header field gets tested ๐ฆ.
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 RFC 2119 [RFC2119] ๐ซถ.
The terms used to describe the different parts of the Sieve language are taken from Section 1.1 of [RFC5228] ๐. Section 2 of that same document covers the basic Sieve language syntax and semantics ๐งฉ. The date-time syntactic element, which is defined using ABNF notation [RFC5234] in [RFC3339], is also used here ๐ธ.
3. Capability Identifiers ๐ท๏ธ
The capability strings for the two extensions in this document are date and index ๐.
4. Date Test ๐๏ธ
Here's the usage:
Usage: date [<":zone" <time-zone: string>> / ":originalzone"]
[COMPARATOR] [MATCH-TYPE] <header-name: string>
<date-part: string> <key-list: string-list>
The date test matches date/time info that comes from headers containing [RFC2822] date-time values ๐. It pulls the date/time info out of the header, shifts it to the specified time zone, and figures out the value of the given date-part ๐ฐ๏ธ. The test returns true if the resulting string matches any of the strings in the key-list, depending on what the comparator and match keywords say ๐ฏ.
The test returns false no matter what in any of these cases ๐ โโ๏ธ:
- the specified header field doesn't exist
- the field exists but doesn't have a syntactically valid date-time spec
- the date-time isn't valid according to the calendar rules (like January 32nd, or February 29 in a non-leap year) ๐
- the resulting string doesn't match any key-list value
The match type defaults to :is, and the default comparator is i;ascii-casemap โจ.
Unlike the header and address tests, the date test can only be applied to a single header field at a time ๐. If there are multiple header fields with the same name, only the first one found gets used. (But heads up, this behavior can be changed with the "index" extension below ๐ฅ.) These restrictions keep the test simple and the meaning clear ๐.
The "relational" extension [RFC5231] adds a match type called :count ๐ข. The count for a date test is 1 if the specified field exists and contains a valid date, and 0 otherwise ๐.
Implementations MUST support extraction of RFC 2822 date-time info that either makes up the entire header field (like in a standard Date: header field), or appears at the end of a header field after a semicolon (like in a standard Received: header field). ๐จ
Implementations MAY support extraction of date and time info in RFC 2822 or other formats that show up in other positions in header field content ๐. If a field has more than one date or time value, the last one that appears SHOULD be used ๐ซถ.
4.1. Zone and Originalzone Arguments ๐
The :originalzone argument says the time zone offset that was originally in the extracted date-time value should be kept ๐งณ. The :zone argument says a specific time zone offset that the date-time value should be shifted to before testing ๐. It's an error to specify both :zone and :originalzone ๐
โโ๏ธ.
The value of time-zone MUST be an offset relative to UTC with this syntax:
time-zone = ( "+" / "-" ) 4DIGIT
The "+" or "-" tells you whether the time of day is ahead of (east of) or behind (west of) UTC ๐งญ. The first two digits are the number of hours difference from Universal Time, and the last two digits are the number of minutes difference from Universal Time โฑ๏ธ. This matches the RFC 2822 format for time zone offsets, not the ISO 8601 format, so keep that in mind ๐.
If both the:zoneand:originalzonearguments are left out, the local time zone MUST be used. ๐
4.2. Date-part Argument ๐งฉ
The date-part argument says which part of the resulting date/time value to match against the key-list ๐ฏ. These are the possible case-insensitive values:
"year" => the year, "0000" .. "9999".
"month" => the month, "01" .. "12".
"day" => the day, "01" .. "31".
"date" => the date in "yyyy-mm-dd" format.
"julian" => the Modified Julian Day, that is, the date
expressed as an integer number of days since
00:00 UTC on November 17, 1858 (using the Gregorian
calendar). This corresponds to the regular
Julian Day minus 2400000.5. Sample routines to
convert to and from modified Julian dates are
given in Appendix A.
"hour" => the hour, "00" .. "23".
"minute" => the minute, "00" .. "59".
"second" => the second, "00" .. "60".
"time" => the time in "hh:mm:ss" format.
"iso8601" => the date and time in restricted ISO 8601 format.
"std11" => the date and time in a format appropriate
for use in a Date: header field [RFC2822].
"zone" => the time zone in use. If the user specified a
time zone with ":zone", "zone" will
contain that value. If :originalzone is specified
this value will be the original zone specified
in the date-time value. If neither argument is
specified the value will be the server's default
time zone in offset format "+hhmm" or "-hhmm". An
offset of 0 (Zulu) always has a positive sign.
"weekday" => the day of the week expressed as an integer between
"0" and "6". "0" is Sunday, "1" is Monday, etc.
The restricted ISO 8601 format comes from the date-time ABNF production in [RFC3339], Section 5.6 ๐ธ. It has these extra rules: the letters "T" and "Z" MUST be in upper case, and a time zone offset of zero MUST be represented by "Z" and not "+00:00" โจ.
4.3. Comparator Interactions with Date-part Arguments ๐
Not every comparator plays nicely with every date-part argument ๐. In general, the date-parts can be compared and tested for equality with either i;ascii-casemap (the default) or i;octet, but there are two exceptions ๐
:
julianis an integer, and it might or might not have leading zeros ๐ข. Because of that,i;ascii-numericis almost certainly the best comparator to use with it ๐.std11is there so you can get date/time values in a format that's good for email header fields ๐. The wide range of possible std11 syntaxes (which implementations of this extension are free to use when composing a std11 string) makes this format a poor pick for comparisons ๐ฌ. Still, if you have to compare it, it's case-insensitive, so you'll need to usei;ascii-casemap๐ซ .
The "year", "month", "day", "hour", "minute", "second", and "weekday" date-parts all use fixed-width string representations of integers ๐ง. So they can be compared with i;octet, i;ascii-casemap, and i;ascii-numeric, and you get equivalent results with each one ๐ซถ.
"date" and "time" also use fixed-width string representations of integers ๐
. They can be compared with i;octet and i;ascii-casemap โจ. But i;ascii-numeric can't be used with them, because it doesn't allow for non-digit characters ๐
โโ๏ธ.
Part 1 of 2 wraps here ๐ฆ, and part 2 is coming up with the rest of the good stuff ๐๐ซถ
4.4. Examples โจ
okay so the Date: field is the sender's own claim about when they made the message, and you can check it ๐ฐ๏ธ like, "wait, did they send this during work hours or nah?" ๐คจ
require ["date", "relational", "fileinto"];
if allof(header :is "from" "boss@example.com",
date :value "ge" :originalzone "date" "hour" "09",
date :value "lt" :originalzone "date" "hour" "17")
{ fileinto "urgent"; }
and testing the first Received: field tells you when the message actually landed in your local system, which is way more trustworthy bc the sender didn't write it ๐
require ["date", "relational", "fileinto"];
if anyof(date :is "received" "weekday" "0",
date :is "received" "weekday" "6")
{ fileinto "weekend"; }
5. Currentdate Test ๐ธ
Usage:
Usage: currentdate [":zone" <time-zone: string>]
[COMPARATOR] [MATCH-TYPE]
<date-part: string>
<key-list: string-list>
the currentdate test is basically the date test's cooler sibling ๐ except instead of digging up a date from the message header, it checks the current date and time. The ":zone" argument and the date-part arguments work exactly the same as they do in the date test, so no new vocab to learn ๐ง
Heads up, though: every currentdate test in a single Sieve script MUST refer to the same point in time during execution of the script. No one test gets to be in 2009 while another is in 2026 ๐
Also, the :count value of a currentdate test is always 1. Always. No exceptions ๐
5.1. Examples ๐
The simplest use of currentdate is an action that only fires at certain times โฐ Like, a user might want messages sent to their pager after business hours and on weekends:
require ["date", "relational"];
if anyof(currentdate :is "weekday" "0",
currentdate :is "weekday" "6",
currentdate :value "lt" "hour" "09",
currentdate :value "ge" "hour" "17")
{ redirect "pager@example.com"; }
currentdate can also set up vacation [RFC5230] responses ahead of time and then turn them off automatically ๐๏ธ so you don't have to remember to do it yourself:
require ["date", "relational", "vacation"];
if allof(currentdate :value "ge" "date" "2007-06-30",
currentdate :value "le" "date" "2007-07-07")
{ vacation :days 7 "I'm away during the first week in July."; }
currentdate is also friends with the variables extension ๐ซถ so you can pass time-dependent values into other tests and actions. This Sieve script drops messages into a folder named after the current month and year ๐:
require ["date", "variables", "fileinto"];
if currentdate :matches "month" "*" { set "month" "${1}"; }
if currentdate :matches "year" "*" { set "year" "${1}"; }
fileinto "${month}-${year}";
And finally, currentdate pairs up with the editheader extension to stamp a header field with the date and time info ๐๏ธ
require ["variables", "date", "editheader"];
if currentdate :matches "std11" "*"
{addheader "Processing-date" "${0}";}
6. Index Extension ๐ฆ
The "index" extension, if it's specified, adds optional :index and :last arguments to the header, address, and date tests. Here's the lineup ๐
Syntax:
Syntax: date [":index" <fieldno: number> [":last"]]
[<":zone" <time-zone: string>> / ":originalzone"]
[COMPARATOR] [MATCH-TYPE] <header-name: string>
<date-part: string> <key-list: string-list>
Syntax: header [":index" <fieldno: number> [":last"]]
[COMPARATOR] [MATCH-TYPE]
<header-names: string-list> <key-list: string-list>
Syntax: address [":index" <fieldno: number> [":last"]]
[ADDRESS-PART] [COMPARATOR] [MATCH-TYPE]
<header-list: string-list> <key-list: string-list>
So if you specify :index <fieldno>, the matching attempts are limited to that one header field, with fieldno starting at 1 for the first named header field ๐ฏ Add :last on top of that and the count runs backwards, so 1 means the last named header field, 2 means the second to last, and so on ๐ Specifying :last without :index is an error, so don't do that ๐ โโ๏ธ
Quick reminder: :index only counts separate header fields, NOT multiple occurrences inside one single field. So you can't use :index to grab one specific address out of an address list that lives inside a single header field ๐
Both header and address let you name more than one header field. When you do, all the named header fields get counted in the order you list them in the header-list ๐
6.1. Example ๐งโโ๏ธ
Mail delivery can involve multiple hops, which means the Received: field holding info about when a message first entered the local administrative domain might be the second or later field in the message โ๏ธ As long as that field offset stays consistent, you can test it:
# Implement the Internet-Draft cutoff date check assuming the
# second Received: field specifies when the message first
# entered the local email infrastructure.
require ["date", "relational", "index"];
if date :value "gt" :index 2 :zone "-0500" "received"
"iso8601" "2007-02-26T09:00:00-05:00",
{ redirect "aftercutoff@example.org"; }
7. Security Considerations ๐
Okay this part is seriously important, bestie ๐จ The stuff defined here, just like the base Sieve specification, works on message header information that can be forged super easily ๐ญ Some fields are way more reliable than others though. For example, the Date: field is usually put in by the message sender and can be changed at any point. Meanwhile, the uppermost Received: field is usually added by the local mail system, so it's really hard for the sender or some middleman to fake ๐ต๏ธโโ๏ธ
Using the currentdate test makes script behavior inherently less predictable and harder to analyze. That could cause problems for systems that rely on script analysis to spot sketchy scripts ๐
Every security consideration in the base Sieve specification also applies to these extensions ๐ก๏ธ
8. IANA Considerations ๐
The following templates specify the IANA registrations for the two Sieve extensions in this document ๐
To: iana@iana.org
Subject: Registration of new Sieve extensions
Capability name: date
Description: The "date" extension gives Sieve the ability
to test date and time values.
RFC number: RFC 5260
Contact address: Sieve discussion list <ietf-mta-filters@imc.org>
Capability name: index
Description: The "index" extension provides a means to
limit header and address tests to specific
instances when more than one field of a
given type is present.
RFC number: RFC 5260
Contact address: Sieve discussion list <ietf-mta-filters@imc.org>
9. References ๐
9.1. Normative References ๐
- [CALGO199] Tantzen, R., "Algorithm 199: Conversions Between Calendar Date and Julian Day Number", Collected Algorithms from CACM 199.
- [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, March 1997.
- [RFC2822] Resnick, P., "Internet Message Format", RFC 2822, April 2001.
- [RFC3339] Klyne, G., Ed. and C. Newman, "Date and Time on the Internet: Timestamps", RFC 3339, July 2002.
- [RFC5228] Guenther, P. and T. Showalter, "Sieve: An Email Filtering Language", RFC 5228, January 2008.
- [RFC5231] Segmuller, W. and B. Leiba, "Sieve Email Filtering: Relational Extension", RFC 5231, January 2008.
- [RFC5234] Crocker, D. and P. Overell, "Augmented BNF for Syntax Specifications: ABNF", STD 68, RFC 5234, January 2008.
9.2. Informative References ๐
- [RFC3501] Crispin, M., "INTERNET MESSAGE ACCESS PROTOCOL - VERSION 4rev1", RFC 3501, March 2003.
- [RFC5230] Showalter, T. and N. Freed, "Sieve Email Filtering: Vacation Extension", RFC 5230, January 2008.
Appendix A. Julian Date Conversions ๐งฎ
Here are some C routines that show how to translate day/month/year info to and from modified Julian dates โจ They're straightforward translations of the Algol routines in CACM Algorithm 199 [CALGO199], so credit where it's due ๐ซถ
First up, given the day, month, and year, jday returns the modified Julian date:
int jday(int year, int month, int day)
{
int j, c, ya;
if (month > 2)
month -= 3;
else
{
month += 9;
year--;
}
c = year / 100;
ya = year - c * 100;
return (c * 146097 / 4 + ya * 1461 / 4 + (month * 153 + 2) / 5 +
day + 1721119);
}
And then, given j, the modified Julian date, jdate gives you back the day, month, and year ๐
void jdate(int j, int *year, int *month, int *day)
{
int y, m, d;
j -= 1721119;
y = (j * 4 - 1) / 146097;
j = j * 4 - y * 146097 - 1;
d = j / 4;
j = (d * 4 + 3) / 1461;
d = d * 4 - j * 1461 + 3;
d = (d + 4) / 4;
m = (d * 5 - 3) / 153;
d = d * 5 - m * 153 - 3;
*day = (d + 5) / 5;
*year = y * 100 + j;
if (m < 10)
*month = m + 3;
else
{
*month = m - 9;
*year += 1;
}
}
Appendix B. Acknowledgements ๐
Dave Cridland wrote the text describing the proper comparators to use with different date-parts, so huge thanks to him ๐ Cyrus Daboo, Frank Ellerman, Alexey Melnikov, Chris Newman, Dilyan Palauzov, and Aaron Stone also gave helpful suggestions and corrections ๐
Author's Address ๐๏ธ
Ned Freed
Sun Microsystems
800 Royal Oaks
Monrovia, CA 91016-6347
USA
Phone: +1 909 457 4293
EMail: ned.freed@mrochek.com
Full Copyright Statement โ๏ธ
Copyright (C) The IETF Trust (2008).
This document is subject to the rights, licenses and restrictions contained in BCP 78, and except as set forth therein, the authors retain all their rights.
This document and the information contained herein are provided on an "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY, THE IETF TRUST AND THE INTERNET ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
Intellectual Property ๐ฆ
The IETF takes no position regarding the validity or scope of any Intellectual Property Rights or other rights that might be claimed to pertain to the implementation or use of the technology described in this document, or the extent to which any license under such rights might or might not be available. It also doesn't represent that it has made any independent effort to identify any such rights. Information on the procedures with respect to rights in RFC documents can be found in BCP 78 and BCP 79.
Copies of IPR disclosures made to the IETF Secretariat and any assurances of licenses to be made available, or the result of an attempt made to obtain a general license or permission for the use of such proprietary rights by implementers or users of this specification, can be obtained from the IETF on-line IPR repository at http://www.ietf.org/ipr.
The IETF invites any interested party to bring to its attention any copyrights, patents or patent applications, or other proprietary rights that may cover technology that may be required to implement this standard. Please address the information to the IETF at ietf-ipr@ietf.org. ๐ โจ
๐ฆ where this page comes from
- ietf: rfc5260 2008-07
- ๐ girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms