Dovecot Plugin - sieve

最後更新: 2018-12-26

介紹

Sieve 是一種 script language 來, 它是專為處理 E-Mail 而設的.

而且它具有防止帳戶執行任何指令的好處, 所以很安全.

rfc5228

目錄

補充

 * ISPConfig 係透過 cron jobs 去發動修改的

 


Enable sieve plugin support for dovecot-lmtp

 

Install

apt-get install dovecot-sieve dovecot-managesieved

Configure

/etc/dovecot/conf.d/15-lda.conf

protocol lda {
  # Space separated list of plugins to load (default is global mail_plugins).
  mail_plugins = $mail_plugins sieve
}

/etc/dovecot/conf.d/20-lmtp.conf

protocol lmtp {
  postmaster_address = [email protected]
  mail_plugins = $mail_plugins
}

/etc/dovecot/conf.d/90-sieve.conf

plugin {
   # Per-user sieve settings
   sieve_dir = /%Lh/sieve
   # sieve=/var/vmail/%d/%n/.sieve
   sieve = /%Lh/sieve/dovecot.sieve

   # Global setting
   sieve_global_path = /var/lib/dovecot/sieve/default.sieve
   sieve_global_dir = /var/lib/dovecot/sieve/
}

 


managesieve

 

Package: dovecot-pigeonhole

Port: 4190/TCP

Config

# Default: 0.0.0.0:4190
protocols = ... sieve

# Service 改 listen 127.0.0.1
service managesieve-login {
    inet_listener sieve {
        # Default: 0.0.0.0
        address = 127.0.0.1
        port = 4190
    }
}

# 派信時發動 sieve
protocol lmtp {
    mail_plugins = $mail_plugins sieve
    ...
}

Test

telnet localhost 4190

"IMPLEMENTATION" "Dovecot Pigeonhole"
"SIEVE" "fileinto reject envelope encoded-character vacation subaddress comparator-i; ...
"NOTIFY" "mailto"
"SASL" "PLAIN LOGIN"
"VERSION" "1.0"
OK "Dovecot ready."

Log

2022-06-13 10:26:50 managesieve-login: Info: Disconnected: Too many invalid commands. (no auth attempts): rip=127.0.0.1, lip=127.0.0.1, secured

 


Script Compiling

 

雖然 sieve 是 script 來, 但為了提升執行速度,

它會在第一次執時時 compile 成 binary 的格式存放

P.S.

當 Script 有問題而不能執行時, E-Mail 會被直接放入 Mailbox

log

Dec 31 15:27:17 vm dovecot: lda([email protected]): 
 sieve: Failed to compile script `/var/vmail/datahunter.org/postmaster/sieve' 
 (view user logfile `/var/vmail/datahunter.org/postmaster/sieve.log' for more information)

 


自動回覆(autoresponder) - vacation

 

回覆次數:

:days 是用來設定 n 天內同一個寄件者來信時, sieve 不再執行 auto reply

  • min: 1
  • default: 7

會保存回覆過的記錄: .dovecot.lda-dupes

dovecot.sieve:

# 亦可以寫成 require ["vacation"];
require "vacation";
if true
{
  vacation
   :subject "text string"
   :days 1
   # 只有 "To" Email1, Email2 才會解發自動回覆, 不填即所有人都有
   :addresses ["Email1", "Email2"]  
   "要自動回覆的內容, 
可以多行, 但別縮排
直到遇到它
";

  # Default action 係會 keep 的, 所以不用加 "keep"
  #keep;
}

收信後會 compile 成 dovecot.svbin

P.S.

# 有時段的回覆

#### Autoreply
if allof(currentdate :value "ge" "iso8601" "2022-08-20T00:00:00",
         currentdate :value "le" "iso8601" "2022-09-15T00:00:00") {
    ....
}

 


Filter Spam Rule

 

dovecot.sieve

# fileinto: for putting mail into a imap folder
# mailbox: for creating imap folder if not exists
require ["fileinto", "mailbox"];

# rule:[Move Spam to Junk Folder]
if header :matches ["X-Spam-Flag"] ["YES"] {
    #### 對於 hit 中的 mail 處理

    #1 Keep this mail in INBOX.
    #keep;      <-- 沒有 stop, reject, discard 的話, 它就是 Deault "keep"

    #2 move it to 'Junk', and stop
    fileinto :create "Junk";

    #3 If you ensure they are spam, you can discard it.
    #discard;

    # 不執行之後的 sieve rule, 跳出 sieve
    stop;
}

Notes

1) "X-Spam-Flag" 係 Amavisd 加上去的, Value 得 YES / NO

 


If

 

if ... {
   ...
} elsif ... {
      ...
} else {
     keep;
}

 


match 的方式

 

":is"

an absolute match

 * The empty key ("") only ":is" matches with the empty value.

":contains"

A sub-string match, useful if we know a part of a string

":matches"

A wildcard match using the characters "*" and "?"

The entire value must be matched.

  • "?" for one unknown character and the asterisk
  • "*" for zero or more unknown characters

e.g.

if header :matches "subject" ["*test*1234*", "*test*5678*"] {
    ...
}

 *  "?" and "*" may be escaped as "\\?" and "\\*"

 

":regex"

  • .
  • ?
  • *
  • +
  • [a-z]
  • [^ ]
  • {n,m}
  • ^
  • $
  • |
  • ( )

 


Action

 

  • redirect
  • copy
  • fileinto
  • reject
  • discard

"redirect" extension

# One email one line.
redirect "[email protected]";
redirect "[email protected]";

"redirect" 後自己係沒有 copy 的

redirect 人數限制

cat /var/vmail/Mydomain/MyUser/.sieve.log

main script: line 8: error: number of redirect actions exceeds policy limit (5 > 4).

Setting

# 當 over 時, 全部人都不 forward
sieve_max_redirects = 10

防 loop 死

if allof (header :contains "X-Sieve-Redirected-From" "me@domain", exists "X-Sieve")
{
        discard;
}

P.S.

redirect 後會加了 "X-Sieve" header

X-Sieve: Pigeonhole Sieve 0.5.8 (b7b03ba2)
X-Sieve-Redirected-From: recipient@domain

server log

先在 t1 時間完成後再在 t2 時間在 u2@ 派信

t1 dovecot: lda(u1@DOMAIN): sieve: msgid=<ID@u22>: forwarded to <u2@DOMAIN>
t1 dovecot: lda(u1@DOMAIN): sieve: msgid=<ID@u22>: stored mail into mailbox 'INBOX'
t2 dovecot: lda(u2@DOMAIN): sieve: msgid=<ID@u22>: stored mail into mailbox 'INBOX'

discarded duplicate forward

# user1 -> user2 log

... dovecot: lda(u1@domain): sieve: msgid=...: discarded duplicate forward to <u2@domain>

已經有此 Message ID 存在

Config

plugin {
 ...
 # Default: 12h
 sieve_redirect_duplicate_period = 300
}

 * The period is specified in s(econds), unless followed by a d(ay), h(our) or m(inute)

 * If an account forwards many messages, it may be necessary to lower this setting.
    (To prevent the ~/.dovecot.lda-dupes database file from growing to an impractical size)

vmail: /home/vmail/DOMAIN/USER

它的作用: halt potential mail loops

Source Code: pigeonhole/src/lib-sieve/cmd-redirect.c

"copy"

defined in RFC 3894 adds the ":copy" parameter to permit "redirect" to take effect

    without cancelling the default action of saving the message to the "INBOX".

e.g.

if allof (header :contains "to" "me@domain")
{
        redirect :copy "another@domain";
}

"fileinto"

require ["fileinto", "reject"];

e.g.

fileinto ".trash";

"reject"

reject "彈回不收";

"discard"

將信件直接 Delete, 不是放入垃圾筒 (入垃圾筒係透過 fileinto 實現)

 

 


Dovecot 與 Sieve

 

dovecot 是其中一個用 sieve 去處理 E-Mail 的 E-Mail Server

設定如下

plugin {
  # 限制 Source 檔大小
  sieve_max_script_size = 1M
  
  sieve_max_actions = 32
  
  sieve_max_redirects = 4
  
  sieve = /var/sieve-scripts/%u.sieve
}

global sieve:

sieve_global_dir = /var/vmail/sieve
sieve_global_path = /var/vmail/sieve/dovecot.sieve

如果用户有(存在) 自己 sieve 文件,  那 Gobal 的將不會被執行 !!

 



Plugin - include

 

注意:

只有 Dovecot v2.x 支援此 plugin

功能:

在 sieve script 上 include 其他 sieve script

用法:

The "include" command may appear anywhere in the script where a control structure is legal.

Example:

dovecot.sieve:

require ["include"];
include :global   "admin";
include :personal "managesieve";

global 與 personal

  • global: sieve_global_dir = /var/vmail/sieve
  • personal: sieve_global_path = /var/vmail/sieve/dovecot.sieve

/etc/dovecot/dovecot.conf:

plugin {
  # Directory for :personal include scripts. The default is to use home directory.
  sieve_dir = %h/sieve

  # Directory for :global include scripts (not to be confused with sieve_global_path).
  # If unset, the include fails.
  sieve_global_dir = /etc/dovecot/sieve/
}

Usage: return

The "return" command stops processing of the currently 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.

Roundcube Plugin:

// List of reserved script names (without extension).
// Scripts listed here will be not presented to the user.
// Can not create
$rcmail_config['managesieve_filename_exceptions'] = array('admin');

/var/log/sieve.log

Aug 20 17:48:02 lda(test@receive): Error: sieve: failed to open script //var/vmail/receive/test//sieve/dovecot.sieve 
(view user logfile //var/vmail/receive/test//sieve/dovecot.sieve.log for more information)
Aug 20 17:48:02 lda(test@receive): Info: msgid=<ID@SENDER>: saved mail to INBOX

 


Plugin - address

 

* address 支援的 feild 有 "from", "to"

(對比起 header , address 額外支援 ":localpart", ":domain")

# 某 Domain

if address :is :domain "to" "example.com"
{
    fileinto "examplecom";
}

# 某些人

if address :is ["from", "sender"] ["[email protected]", "[email protected]", "[email protected]"]
{
    fileinto "friends";
}

 


Plugin - header

 

空格

Each header line is allowed to have whitespace nearly anywhere in the line

Extra spaces between the header name and the ":" in a header field are ignored.

"From:XXX" 等價 "From    :    XXX"

所以 header 的關鍵是 ":"

某人

if header :regex ["from"] ["test@domain$"] {
    discard;
    stop;
}

* 預設不分有小寫

to 與 cc

if header :contains ["to", "cc"]
 [
  "user1@domain",
  "user2@domain",
 ]
 {
  redirect "usera@domain";
  redirect "userb@domain";
  stop;
 }

 


Plugin - date

 

Extension 功能

- provides a new date test to extract and match date/time information from structured header fields

- provides a currentdate test that operates

The type of match defaults to ":is" and the default comparator is "i;ascii-casemap"

Usage:

date [<":zone" <time-zone: string>> / ":originalzone"]
                 [COMPARATOR] [MATCH-TYPE] <header-name: string>
                 <date-part: string> <key-list: string-list>

Zone and Originalzone Arguments

:originalzone

argument specifies that the time zone offset originally in the extracted date-time value should be retained.

:zone

specifies a specific time zone offset that the date-time value is to be shifted to prior to testing. 

 * If both the :zone and :originalzone arguments are omitted, the local time zone MUST be used.

 * It is an error to specify both :zone and :originalzone.

Time format:

  • "date"      =>  "yyyy-mm-dd"
  • "hour"      =>  "00" .. "23".
  • "minute"  =>  "00" .. "59"

Example

[1]

require ["date", "relational", "fileinto"];
if allof(header :is "from" "[email protected]",
    date :value "ge" :originalzone "date" "hour" "09",
    date :value "lt" :originalzone "date" "hour" "17")
{ fileinto "urgent"; }

[2]

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."; }

 


Tips

 

sieve file link

dovecot.sieve  --soft_link-->  managesieve.sieve

":is" vs ":contains"

不知為何 discard 不到 system@ 的 "Info: " 來信

# rule:[Delete eRO mail]
if allof (header :contains "subject" "Info: ", header :is "from" "[email protected]")
{
        discard;
}

原因: From 不是純 email address, 所以不能用 ":is" 去匹配

From: Alert System <[email protected]>

Case insensitively

在 ":is", ":contains", ":matches" 及 ":regex" 後加上 ":comparator" tag

:comparator "i;ascii-casemap"

mapping uppercase characters to lowercase. This is a case-insensitive comparison.

Notes

 * Folder names are case-sensitive

 


Doc

Creative Commons license icon Creative Commons license icon