電子郵件已經是現代人幾乎都會使用的網路服務了,有了電子郵件人與人在傳遞訊息上更為便利、即時,軟體程式也可以藉由電子郵件來即時通知使用者目前軟體運行時遇到的狀況。
這邊有些術語要先介紹一下,一封電子郵件在Unix-like系統中,從發送到接收的流程是以下這樣:
- 使用者在MUA(Mail User Agent)軟體(例如:Mozilla ThunderBird、Microsoft Outlook或各式網頁信箱)上撰寫郵件。
- MUA透過簡單郵件傳輸協定(SMTP, Simple Mail Transfer Protocol),將使用者撰寫的郵件傳給某個MTA(Mail Transfer Agent)。這邊的MTA有點像是當地的郵局(在此以
localmail
來表示這個MTA的主機名稱),用來處理當地居民的郵件收發業務,或是處理接收自或發送至其它MTA主機的郵件。 - 如果郵件的收件者(例如:peter@localmail)也是在當地,則會直接再由該當地的MTA,指派給當地的MDA(Mail Delivery Agent)來處理,MDA就像是郵局中的人員,負責分類當地的信件,並投遞至Unix-like系統內的信箱(inbox,或稱mailbox)。MDA會包含在MTA的軟體中,不過MTA也可以改將郵件交給procmail等專門的MDA軟體來處理,就像是郵局把郵件交給外聘人員來處理一樣。但如果郵件的收件者(例如:peter@gmail.com)不是在當地,MTA則會根據該收件者郵件地址內的網域名稱,透過DNS來查詢其MX(Mail Exchanger)記錄所指定的MX主機的網域名稱,當地的MTA會再透過SMTP將郵件轉發給該網域名稱所對應的MX主機。在郵件中使用的網域名稱可以設定多筆DNS的MX記錄來指定多個MX主機的網域名稱,每筆MX記錄的優先權不同,數值愈小的會愈先被當地的MTA嘗試寄送,如果不成功才會再嘗試下一個MX主機。如果所有MX記錄的MX主機都無法成功寄送,MTA會將郵件寄送給A記錄所指定的MX主機。MX其實就是MTA,只是它因為在這個發送郵件的過程中扮演不同的角色(MX的角色就像集運站、轉運站),而有不同的稱呼。無論如何,正常的話,最終該封郵件會到達目的地的MTA,也就是收受地的郵局,然後再被收受地的MDA處理。
- 使用者操作MUA軟體,透過POP3(Post Office Protocol)或是IMAP(Internet Message Access Protocol)協定向MAA(Mail Access Agent)軟體(例如:dovecot、Cyrus IMAP)驗證身份,並取得(或刪除)儲存在Unix-like系統內的信箱中的郵件。
至於MRA(Mail Retrieval Agent,例如:fetchmail),它雖然並不是標準電子郵件架構中的一環,但還是經常會被提到。它可以透過POP3或是IMAP等協定向郵件伺服器取得郵件,並儲存至Unix-like系統內的信箱,或是交給MUA來處理。此外,它也可以透過SMTP將郵件發送給遠端的MTA。我們可以很簡單地把MRA當成是簡易的郵件客戶端(僅剛好符合郵件的收發需求),適合被其它程式呼叫調用;而MUA則當成是功能完整或是功能比較多的郵件客戶端(除了基本的郵件收發功能外,還擁有多帳號管理、圖形化介面等功能),適合大眾使用鍵盤和滑鼠來操作。
架設MTA伺服器
Unix-like作業系統中常用的MTA伺服器程式為「Sendmail」和「Postfix」兩種,後者比較新,也是現在主流的MTA伺服器程式。
安裝Postfix
要在Ubuntu Server上安裝Postfix,可以直接在終端機中執行以下指令:
安裝Postfix的過程中會有一些需要手動設定的項目。首先要設定Postfix的一般類型,在此可以直接選擇Internet Site
。
接著要設定郵件伺服器的名稱,也就是電子郵件地址中,小老鼠@
後的部份。此處雖然以magiclen-server
為例,不過如果要公開在網際網路上使用,還是得填入完整網域名稱(FQDN, Fully Qualified Domain Name),也就是將一般的網域名稱在結尾處,加上半形句號.
,以示結尾,並設定該網域的DNS的A記錄、PTR記錄和TXT(SPF)記錄,主機名稱也要用一樣的完整網域名稱,而且主機IP必須不能是浮動(dynamic)的或是住宅代理(residential proxy)的,以免被某些郵件伺服器拒絕接收。
Postfix使用STMP來接收郵件,TCP連接埠為25,可以使用以下指令來查看Postfix是否有確實安裝成功。
ss
指令可以顯示出Socket相關的資訊。-l
參數可以只顯示正在監聽中的連線。若使用ss
指令時都沒給任何參數的話,會忽略掉監聽中的連線。-t
參數可以只顯示TCP連線。-n
參數可以讓連接埠數字直接被輸出,而不是用一個名稱代替。-p
參數可以顯示佔用連線的行程。
如上圖,如果有看到TCP有在監聽連接埠25,就表示Postfix安裝成功了!
設定Postfix
Postfix的主設定檔為/etc/postfix/main.cf
。它預設的模樣長成這樣:
比較重要的設定項目有:
myhostname
:設定郵件伺服器的名稱,也就是自己的電子郵件地址中,小老鼠@
後的部份。這個設定項目只能夠填寫一個主要使用的伺服器名稱,如果需要有多個的話,要添加至mydestination
設定項目中。inet_interfaces
:設定SMTP要監聽的IP位址。如果有多個的話要使用逗號和(或)空白隔開,
。如果設為all
,表示要監聽所有網路介面。如果這個郵件伺服器只是要用在本地端,可設定為lo
。mydestination
:設定允許接收的收件者伺服器的名稱,也就是自己的電子郵件地址中,小老鼠@
後的部份。如果有多個的話要使用逗號和(或)空白隔開,
。如果使用$myhostname
,表示要把myhostname
作為變數串接進來,例如:$myhostname, mail.$myhostname
,即magiclen-server, mail.magiclen-server
。mynetworks
:設定允許將這台郵件伺服器作為中繼(replay)伺服器的IP子網路。如果有多個的話要使用空白隔開[]
括起來,例如:[2001:240:587::]/64
。relay_domains
:設定允許中繼的收件者伺服器的名稱(寄信的郵件伺服器IP必須要在mynetworks
中才有作用)。如果有多個的話要使用空白隔開mydestination
的值)。relayhost
:設定郵件如果不是當地郵件,且需要轉發給MX主機處理時,就先忽略收件者電子郵件中的網域名稱,直接將郵件轉發給這個設定項目所設定的MX主機位址或是主機名稱。此外,MX主機位址或是主機名稱後還可以再加上冒號:
和連接埠來設定非預設的SMTP連接埠。如果是使用位址(例如:an.ip.add.ress
),就必須使用中括號[]
括起來(例如:[an.ip.add.ress]
)。用位址的話就不會去進行DNS的MX記錄查找,但單純用主機名稱(例如:mailhost
)的話還是會。所以如果不想要讓主機名稱去查詢DNS的MX記錄,就把它加上中括號吧!
對於目的地為當地的電子郵件,可以在當地的郵件伺服器主機上設定/etc/aliases
這個設定檔,來制定使用者別名。它預設的模樣長成這樣:
這項別名設定表示要將寄給postmaster@magiclen-server
的郵件,轉寄到root@magiclen-server
。轉寄的對象可以是一個以上,用逗號,
分隔不同的使用者名稱。例如:
別名也常被拿來當群組功能使用。
由於/etc/postfix/main.cf
中,alias_maps
這個設定項目的預設值為hash:/etc/aliases
,因此若要讓/etc/aliases
這個檔案成功套用設定,還需要使用以下的postalias
指令,來產生Postfix的主設定檔/etc/postfix/main.cf
能讀取的設定檔(.db檔):
或者也可以用newaliases
指令,自動去讀取Postfix設定檔的alias_database
欄位的設定,因此不需要在執行newaliases
指令的時候再透過參數傳入要讀取的alias原始設定檔。
如果要讓轉寄的別名自己也把郵件保留下來,那就要轉寄給自己。例如:
轉寄的對象也可以用小老鼠@
來指定其它郵件伺服器的地址哦!例如:
另外,每個使用者還可以在其家目錄下撰寫.forward
檔,來指定收到郵件後,要自動轉寄給哪些使用者或是其它郵件伺服器的地址,並同時把原本的郵件保留下來。如果有多個轉寄對象,要用換行字元來分隔。
對於目的地非當地的電子郵件,如果要其將導給當地的使用者,可以在Postfix的主設定檔/etc/postfix/main.cf
中藉由virtual_alias_domains
設定項目,將要進行別名化的網域名稱列出來,如果有多個網域名稱,要使用逗號和(或)空白隔開,
。例如:
接著新增或編輯/etc/postfix/virtual
這個檔案,使其內容如下:
postmaster@magiclen.org peter info@mail.magiclen.org gerda sales@magiclen.org petra @example.com jim
以上內容,表示如果郵件目的地的電子郵件地址是postmaster@magiclen.org
時,就寄給當地的peter
使用者;如果郵件目的地的電子郵件地址是info@mail.magiclen.org
時,就寄給當地的gerda
使用者;如果郵件目的地的電子郵件地址是sales@magiclen.org
時,就寄給當地的petra
使用者;最後如果郵件目的地的電子郵件地址的網域名稱是example.com
時,就寄給當地的jim
使用者。
使用以下的postmap
指令,來產生Postfix的主設定檔/etc/postfix/main.cf
能讀取的設定檔(.db檔):
在Postfix的主設定檔/etc/postfix/main.cf
中,將virtual_alias_maps
項目設定為:
如此一來Postfix就可以讀取到/etc/postfix/virtual.db
了。
設定好後使用以下指令來讓Postfix重新載入設定檔:
postfix
指令可以用來管理Postfix。其提供許多子命令,常用的有以下幾個:
status
:檢查Postfix行程的運作狀態。check
:檢查Postfix相關的檔案和目錄之擁有者和權限是否有設定正確。start
:執行Postfix。執行前會自動進行以上的check
步驟。stop
:關閉Postfix。flush
:強制將郵件佇列中的郵件寄出。正常情況下,郵件佇列中的郵件會每隔一段時間才會寄送。reload
:重新載入設定檔。修改Postfix的設定檔後,如果要套用新的設定就執行這個子命令吧!
測試收發郵件
既然都架設好Postfix了,當然是要實際試一下啦!
我們可以使用telnet
指令來與TCP的連接埠25進行TCP連線,直接手打SMTP的內容。指令如下:
成功連上的話,會出現以下畫面:
接著用以下訊息,來設定郵件的寄件者:
輸入以下訊息,來設定郵件的收件者:
由於我們現在是要測試Postfix,收件者最好填入本地端的電子郵件地址。
然後輸入data
指令,說明我們要開始傳送郵件的內容了。
以下是一個簡單的郵件內容範例:
Subject: A Test Mail Hello, It works? regards, Magic Len
若已經輸入完整封郵件了,想要跳出因使用data
指令所進入的訊息內容模式,需再使用單獨的一行來傳送.
。
如此一來這封郵件就會跑到Postfix的郵件佇列中,等待被寄出。
若要結束SMTP連線,輸入quit
指令。結束SMTP連線之後,若剛才郵件收件者是填本地端的電子郵件地址,正常應該會看到You have mail in /var/mail/USER
的訊息。例如郵件收件者是magiclen@localhost
,在/var/mail/magiclen
檔案中就會存有magiclen
這個使用者收到的信件內容。
/var/mail/magiclen
檔案的內容如下:
如此一來就表示Postfix的郵件收發功能是正常的了!
mail指令
GNU mail是十分方便的MTA郵件發送與查看MDA所接收到的郵件的軟體,有了它,我們就不需要自己使用telnet
指令來手動輸入SMTP訊息。Ubuntu Server的mail
指令工具包含在mailutils
套件中,可以使用以下指令來安裝:
若要使用mail
指令來發送信件,指令用法如下:
在輸入完郵件內容之後,按下Ctrl + d來送出。如果送不出去,可以先用Enter跳下一行,再按Ctrl + d。
若要使用mail
指令來查看信件,指令用法如下:
直接按下Enter鍵即可閱讀郵件。
查看Postfix的郵件佇列
postqueue
指令可以查看Postfix尚未成功寄出的郵件,用法如下:
postqueue
指令的-p
參數可以將佇列中的郵件以傳統Sendmail的mailq
指令的輸出格式來輸出。
Postfix的日誌(Log)
預設的Postfix的Log檔為/var/log/mail.log
和/var/log/mail.err
。可以用rsyslog的設定檔/etc/rsyslog.d/50-default.conf
來設定mail.*
和mail.err
所使用的Log檔案路徑。
架設MAA伺服器
Dovecot
是普遍使用的MAA伺服器軟體,可以架設出支援POP3和IMAP的伺服器。另外Dovecot本身也有MDA的功能,可以使用Sieve腳本語言來處理收到的郵件。
安裝Dovecot
要在Ubuntu Server上安裝Dovecot,可以直接在終端機中執行以下指令:
設定Dovecot
作為MAA──啟動POP3和IMAP
POP3與IMAP
POP3使用的TCP連接埠為110,藉由這個協定,使用者可以將郵件伺服器上的新郵件都下載下來,通常郵件被下載之後,就會在伺服器中被刪除。使用SSL加密的POP3s的TCP連接埠為995。
IMAP使用的TCP連接埠為143,藉由這個協定,使用者可以瀏覽郵件伺服器上儲存的郵件,並只下載想要讀取的郵件。使用SSL加密的IMAPs的TCP連接埠為993。
設定檔修改
Dovecot預設會自動開啟所有已安裝的通訊協定支援功能,使以下指令可以查看到底支援哪些通訊協定:
如果要手動更改要開啟的通訊協定的話,可以在Dovecot的主設定檔/etc/dovecot/dovecot.conf
中,把以下這行用井字號#
註解掉:
並加上:
例如只開啟POP3的話,設定方式如下:
在啟用POP3或IMAP的同時會自動啟用其SSL加密的通道(POP3s和IMAPs),如果要停用的話,可以在Dovecot的主設定檔/etc/dovecot/dovecot.conf
中,將ssl
項目設定為no
。
設定好後使用以下指令來讓Dovecot重新載入設定檔:
使用以下指令可以查看Dovecot正在監聽哪些連接埠:
剛才提到的doveadm
指令可以用來管理Dovecot。其提供許多子命令,常用的有以下幾個:
reload
:重新載入設定檔。stop
:停止運行Dovecot。雖然有停止功能,但是如果要執行Dovecot,就不能用doveadm
指令了。可用sudo systemctl start dovecot
來執行Dovecot。who
:顯示誰正在登入Dovecot伺服器。kick
:將指定的正在連線的IP位址或是使用者名稱強制斷線。
作為MDA──Sieve腳本語言
Sieve
Sieve(RFC 5228)是一個專門用來處理E-mail訊息的程式腳本語言,能夠過濾、分類、排序郵件,也可以用來作自動郵件回覆。
設定檔修改
在Postfix的主設定檔/etc/postfix/main.cf
中,將mailbox_command
項目設定為:
就能夠讓Postfix將Dovecot作為MDA了(此處是系統使用者的用法,不過Dovecot也支援虛擬使用者的用法,本篇文章就不提了)!雖然這邊Dovecot用的名稱是LDA(Local Delivery Agent),但MDA和LDA可以說是一樣的東西。
由於預設的信箱是儲存在/var/mail
目錄,因此/usr/lib/dovecot/dovecot-lda
這支程式必須要對/var/mail
目錄擁有存取權限。/var/mail
目錄結構的權限可以使用以下指令來查看:
從上圖可以看得出來,/var/mail
目錄可以透過root
使用者或是mail
這個群組來進行存取。如果讓/usr/lib/dovecot/dovecot-lda
這支程式獲得root權限可能會有安全上的疑慮,因此我們就利用SGID,讓它有mail
群組的權限吧!
首先用以下指令,將/usr/lib/dovecot/dovecot-lda
的群組改為mail
。
接著用以下指令,替/usr/lib/dovecot/dovecot-lda
設定SGID。
不過由於Dovecot沒有root權限,因此無法去存取root使用者的信箱。這部份可自行利用Postfix的「alias」功能,來將root使用者的郵件轉寄給一般使用者。
接著為了要讓Dovecot能夠使用Sieve,需要去編輯/etc/dovecot/conf.d/15-lda.conf
檔案,將檔案內容修改如下:
lda_mailbox_autocreate = yes lda_mailbox_autosubscribe = yes protocol lda { mail_plugins = $mail_plugins sieve }
lda_mailbox_autocreate
項目可以讓Dovecot自動建立使用者信箱。lda_mailbox_autosubscribe
項目可以讓Dovecot自動去訂閱其自動建立出來的使用者信箱。mail_plugins
項目可以引入多個Dovecot的插件,在此多引入了sieve
。
接著編輯/etc/dovecot/conf.d/90-sieve.conf
檔案,內容如下:
plugin { sieve = ~/.dovecot.sieve sieve_dir = ~/sieve sieve_default = /var/lib/dovecot/sieve/default.sieve sieve_global_dir = /var/lib/dovecot/sieve }
sieve
項目指定了每個使用者可以在哪裡自訂它們的Sieve腳本。如果有多個Sieve腳本檔案,sieve_dir
項目則是定義了每個使用者應該要在哪個目錄放置這些腳本檔案。
sieve_default
項目指定了預設的Sieve腳本。如果有多個Sieve腳本檔案,sieve_global_dir
項目則是定義了應該要在哪個目錄放置這些腳本檔案。
設定好後使用以下指令來重啟Dovecot:
Sieve腳本的撰寫方式
Sieve腳本的語法非常容易理解,它由三個部份組成:Control、Test和Action。Control用來控制程式的流程,影響程式應該要執行哪些敘述。Test則是與Control搭配使用,進行條件判斷,看要執行哪些Action。以下是Sieve腳本的語法範例:
# comment require ["extension-name"]; # include multiple extensions by their names /* This is multi line comment */ if <condition> { # if is a control command, <condition> can be formed by a test command (or more) action1; action2; ... } elsif <condition> { # elsif is a control command action1; action2; ... } else { # else is a control command action1; action2; ... }
if、elsif、else這些Control命令很簡單,就不多說了。首先來看看條件式<condition>
吧!
header
是一個經常被用在條件式中的Test命令,它可以用來判斷郵件的標頭資料是否符合某個規則,格式如下:
header [MATCH-TYPE] [ADDRESS-PART] [COMPARATOR] <header-names: 字串或字串陣列> <key-list: 字串或字串陣列>
MATCH-TYPE
的格式如下(雙引號內的字串):
:is
用來進行絕對的匹配,目標字串必須要跟樣本一模一樣。:contains
用來進行部份的匹配,目標字串中的某段子字串必須要跟樣本一模一樣。:matches
用來進行wildcard的匹配,可以使用星號*
來表示任意數量的任意字元,或是用問號?
來表示數量最多為一個的任意字元。
ADDRESS-PART
的格式如下(雙引號內的字串):
ADDRESS-PART
專門用在匹配電子郵件地址的時候。:localpart
表示小老鼠@
左邊的部份;:domain
表示小老鼠@
右邊的部份;:all
表示全部,此為預設值。如果匹配對象並非電子郵件地址,就不會套用這個設定(也就是會使用:all
)。
COMPARATOR
的格式如下:
標準的comparator-name
有兩種:i;octet
簡單進行位元組比對,所以字元會區分英文字母大小寫;i;ascii-casemap
不區分英文字母大小寫的比對,此為預設值。如果要使用其它的comparator-name
,會需要額外的擴充元件。
舉例來說,header :contains "subject" "lunch"
可以測試郵件的主旨(subject)是否有包含lunch
這個字串(不分大小寫)。header :contains ["X-Caffeine", "X-MagicLen"] [""]
可以測試郵件的標頭是否有X-Caffeine
或X-MagicLen
欄位。header :matches "Cc" "?*"
可以測試郵件的副本(cc)是否有至少一個任意字元。
舉一個header
命令搭配if
的例子:
if header :contains "subject" "money" { discard; }
以上程式,會判斷郵件的主旨是否有包含money
這個字串(不分大小寫)。如果有的話就執行discard
這個Action。discard
會捨棄掉該封郵件。
其它常用的Test命令如下:
address
類似header
命令,但address
只負責判斷屬於電子郵件信箱格式的標頭欄位。
allof
使用AND邏輯,結合多個Test命令的敘述。
格式如下:
allof (Test 1, Test 2, ...)
anyof
使用OR邏輯,結合多個Test命令的敘述。
格式如下:
anyof (Test 1, Test 2, ...)
exists
判斷某個標頭欄位是否存在。
格式如下:
exists <header-names: 字串或字串陣列>
與header
命令不同的是,exists
的header-names
如果使用字串陣列的話,表示所有在陣列中的標頭欄位都要存在,這個測試才會回傳true
。
size
用來判斷郵件的大小是否大於或是小於某個值。
格式如下:
size <":over" / ":under"> <limit: 數值>
舉例來說,size :over 500K
可以判斷郵件的大小是否大於500KB。
true
回傳true
。
false
回傳false
。
not
將測試敘述回傳的結果反向(NOT邏輯)。
格式如下:
not Test
其它常用的Action命令如下:
keep
擁有與discard
命令的相反功能,可以將郵件保留下來。為Sieve腳本預設會執行的指令,不用明寫沒關係。
reject
功能類似discard
,但reject
命令可以加上捨棄郵件的原因。這個命令必須要引入reject
這個擴充元件才能使用。
格式如下:
reject <reason: 字串>
stop
用來結束郵件的處理。為Sieve腳本預設會執行的指令,不用明寫沒關係,除非是想拿來進行流程控制。
fileinto
用來指定郵件要放置的信箱。這個命令必須要引入fileinto
這個擴充元件才能使用。
格式如下:
fileinto <mailbox: 字串>;
例如:
fileinto "INBOX.harassment";
redirect
用來轉寄郵件給其它電子郵件地址。
格式如下:
redirect <address: 字串>;
Sieve自動回信
要用Sieve腳本來完成自動回信功能,可以引入vacation
這個擴充套件。引入之後就可以使用vacation
這個Action命令,格式如下:
vacation [:days 數值] [:subject 字串] [:from 字串] [:addresses 字串或字串陣列] [:mime] [:handle 字串] <reason: 字串>
:days
用來設定郵件多久沒有回覆的時候(幾天),就要使用自動回覆功能。:subject
用來指定自動回覆時郵件所使用的主旨。:from
用來指定自動回覆時郵件的寄件者。:addresses
用來指定要觀察的郵件收件者的電子郵件地址(觀察是否有被開啟)。:mime
用來表示<reason: 字串>
中,除了有郵件內容之外,還有定義內容的MIME標頭。:handle
可以用來設定一個名稱,使得不同的vacation
命令(參數不同)可以被當作相同的vacation
命令來被追蹤。<reason: 字串>
就是設定自動回覆的郵件的內容啦!
設定別的MDA
「Procmail」也是一個常用的獨立MDA,可以根據郵件的發信人、主題、長度以及關鍵字,進行分類整理的工作。經常用來處理病毒、垃圾信的過濾。
安裝Procmail
要在Ubuntu Server上安裝Procmail,可以直接在終端機中執行以下指令:
在Postfix的主設定檔/etc/postfix/main.cf
中,將mailbox_command
項目設定為:
就能夠讓Postfix將Procmail作為MDA了!
設定Procmail
Procmail的設定檔分為系統預設的/etc/procmailrc
,和可供每個使用者分別覆蓋系統預設值的~/.procmailrc
。這兩個檔案如果不存在的話要自行建立哦!至於這些設定檔的設定方式,以後有機會再說吧!如果您有興趣或是需要的話可以參考以下連結: