qmail对邮件的处理过程,由前端smtpd收下邮件,通过零时进程qmail-queue将邮件入队。qmail-queue首先在pid目录下创建一个inode,以这个inode的序列号作为该mail message的id,随后将该inode硬连接到mess目录下,写入邮件的内容,并在intd目录下创建一个同名的文件,写入MAIL FROM和RCPT TO等信息后又将该文件link到todo目录下。至此,整个邮件的入队过程完成。qmail-send不断扫描todo目录,如果有新邮件,它将todo文件中的MAIL FROM写入到info目录下的同名文中,RCPT TO分别写入到local和remote目录下,删除todo下的文件随后将该id加入到remote或者local的内存队列中.(预处理完成)。qmail在内存中有四条队列,分别为前面所说的两条,和一条标示已经完成一份邮件处理的队列以及一条文件系统发生错误时才会被使用到的队列. job对应一个local或者remote下保存rcpt to的文件,rcpt to的地址以T作为开头,当往该用户成功投递后qmail就将T改为D标示投递成功或者永久性失败.
qmail-send不断从remote或者local的内存队列中取下一个id,给它分配一个对应的job,并从相应的info, local或者remote目录下取到MAIL FROM和RCPT TO并将它写到管道的buffer中,最后才被写入管道. 管道是qmail-send用来与两个长驻进程rspawn和lspawn通信的方式.rspawn或者lspawn收到来自qmail-send的投递信息后创建一个新的进程去投递,rspawn或者lspawn通过wait这个进程结束时的exit code来确定投递的成功与否.然后再通过管道将结果返回给qmail-send. qmail-send检查投递的结果,如果成功就如上面所说的将rcpt标示为D,否则确定是要重试还是多次尝试失败放弃。当一个job中所有的RCPT TO都被处理完后,qmail-send将相应的local或者remote目录下的文件删除,当一个msgid对应的local或者remote下的 rcpt to全被处理完时(即这几个文件已经不存在了),则该id会被加入到完成队列中去. qmail-send也会不断检测这个完成队列,取下一个id,通过检测bounce目录下是否有对应的文件来决定是否需要bounce,并删除info目录下的对应文件最后提交给qmail-clean将mess目录下的文件删除。
这些应该就是一封邮件在qmail中处理的大致过程,当然qmail-send 并不像我所说的那样线性地执行下来的,它是通过一个循环不断地执行前面所说的那五个函数来实现
[1.qmail全部控制文件列表(按照相关性排列) ]
qmail-inject
defaultdomain
defaulthost
idhost
plusdomain
qmail-qmqpc
qmqpservers
qmail-remote
helohost
smtproutes
timeoutconnect
timeoutremote
qmail-send
bouncefrom
bouncehost
concurrencylocal
concurrencyremote
doublebouncehost
doublebounceto
envnoathost
locals
me
percenthack
queuelifetime
virtualdomains
qmail-smtpd
badmailfrom
databytes
localiphost
rcpthosts
morercpthosts
timeoutsmtpd
smtpgreeting
-----------------------------------------------------------------
[2.各控制文件的作用 ]
2.1 qmail-inject
qmail-inject接受本地邮件消息后检测邮件头是否符合RFC822标准,并传送给qmail-queue排进队列
defaultdomain
用来向那些没有使用正确的格式的邮件中增加邮件地址的,默认情况下,
如果主机地址中没有结束符".",qmail-inject将给主机名后添加defaultdomain的内容,如果该文
件不存在,将使用me文件中的值代替.
例如:defaultdomain值为linuxfane.com
本地产生了一封发送给的邮件,那么qmail-inject将用linuxfane.com来补全这个
后缀,变成
defaulthost
用来定义主机名,用来向地址中不含主机名的邮件补足邮件地址的。类似于defaultdomain。
例如:defaulthost值为linuxfane.com
那么发送给 rainbow的消息,将被qmail-inject用defaulthost的值来补足为,
以便于qmail-send快速分辨是否属于本地地址。
idhost
用来指定邮件中Message-ID:头字段的主机名
例如:idhost值为linuxfane.com
那么邮件头中如下:
Message-ID:<>;
如果idhost值为rainbow.linuxfane.com
那么邮件头中如下:
Message-ID:<>;
plusdomain
用来将其值添加在任何一个以一个+号结尾的地址
_________________________________________________________________
2.2 qmail-qmqpc
qmail-qmqpc是快速邮件排队协议(QMQP)使用的程序,QMQP是一个QMAIL专用协议,用在工作站级QMAIL
服务器向中心服务器发送队列。
qmqpservers
用来指定qmail-qmqpc使用的上级服务器
通常该功能应用很少,我认为qmail-qmqpc用法就是代替qmail-queue来直接传给上级服务器。
_________________________________________________________________
2.3 qmail-remote
qmail-remote通过SMTP协议向远程主机传送邮件。
helohost
该文件用来指定qmail-remote程序与远程邮件主机SMTP会话中的主机名。
smtproutes
用来指定到固定目的的静态SMTP路线。
例子:smtproutes内容如下
rainbow.linuxfane.com:mail8.rainbow.jp
:fw.rainbow.linuxfane.com:8088
第一行的存在会将发给rainbow.linuxfane.com的邮件重定向到mail8.rainbow.jp主机
第二行的存在会将任何不符合之前行条件的邮件重定向到fw.rainbow.linuxfane.com的8088端口,让
邮件安全的穿越防火墙
qmmail-remote将按照smtproutes中的行序来进行处理
timeoutconnect
用来指定qmail-remote尝试同一个远程邮件主机建立一个SMTP会话的时间。默认值状态下如果超出60
秒没有得到远程邮件主机的回应,那么将断开连接。
timeoutremote
用来指定qmail-remote在一个SMTP连接已经建立后,远程邮件主机的每一个回应的时间数。默认值为
1200秒。
_________________________________________________________________
2.4 qmail-send
qmail-send用来检测队列中每一个邮件的状态,并调用相应的程序进行处理。例如一个目的为本地的
邮件将会被qmail-send调用qmail-lspawn处理。
bouncefrom | bouncehost
默认情况下,退回给原始发送方的邮件的发件人地址为,如果你想修改
MAILER-DAEMON这段,那么就修改bouncefrom文件吧。
如果想修改中HOSTNAME这段,那么就修改bouncehost文件。
例如:bouncefrom内容为SERVER-MANAGER | bouncehost文件内容为mail8.rainbow.jp
那么一封因为邮件地址不存在而被退回的通知邮件的发件人将是
concurrencylocal
用来决定qmail可以同时运行的本地投递进程的数目,默认值是10,标准安装的qmail最大可设值是120。
安装的时候可以通过修改conf-spawn来将其变为255以下任何数目。
concurrencyremote
用来决定qmail可以同时运行的远程投递进程的数目,默认值是20,就是说,qmail可以在同一时间内
同时发送20封邮件,标准安装的qmail最大可设值是120。安装的时候可以通过修改conf-spawn来将其
变为255以下任何数目。
doublebouncehost | doublebounceto
这两个控制文件类似于bouncefrom和bouncehost的关系,也是doublebouncehost指定主机名,
doublebounceto指定用户名。
他们的功能是为一个被退回两次的邮件制定一条出路。
例如:doublebouncehost内容为rainbow.linuxfane.com | doublebounceto内容为rainbow
那么一个被退回2次的邮件将被发送给
没有这两个文件的时候,那么发送给文件中的域名
envnoathost
qmail-send用来给没有主机名的邮件收件人制定一个主机名的,通常这个文件并没有必要,qmail-send
总是使用me文件中的域名。
locals
用来为qmail指明位于本地的邮件地址。qmail-send利用此文件来判断收件人地址是否是一个本地地址。
例如:locals文件中写有rainbow.linuxfane.com
那么qmail-send检测到队列中有发送给@rainbow.linuxfane.com的邮件的时候,就会调用qmail-lspawn
来进行本地投送。
me
如果没有me文件,那么你的qmail将拒绝执行。me文件用来指定本地邮件服务器的主机名。
以上列出的许多控制文件在没有被创建的时候,qmail都会用me中的值来代替。
percenthack
在UUCP协议中带有%并在该文件中列出的地址将会被转换为一个标准的DNS格式主机名。
queuelifetime
用来指定一个邮件在队列中的最大存活时间,默认是604800秒,当一个邮件达到这个时间线后,qmail将
最后一次尝试发送这封邮件,如果依然失败,会将其从队列中删除。
virtualdomains
让qmail接受本地邮件以外,还接受该文件中指定的域或者邮件地址,就是常说的虚拟域支持。
_________________________________________________________________
2.5 qmail-smtpd
qmail-smtpd接受远程主机的邮件并转交给队列处理程序qmail-queue来处理。
badmailfrom
用来指定不喜欢的发件人的邮件地址或者是域名
例如:badmailfrom中写有
@hotmail.com
如果给该邮件主机送信时,将得到code 553,告知其是一个不受欢迎的发件人。
而所有来自hotmail.com的发件人也将得到同样的code 553。
databytes
用来指定该邮件服务器可以接受邮件的最大字节数。
例如:databytes指定为8000000
那么如果有人给该主机发送的邮件字节数超过了这个商业上默认的最大容忍限度,将得到code 552,告知
其邮件大小超出了该邮件主机所能容忍的限度。
恐怖的是,默认状态下,这个文件并没有,如果有人用你服务器上的两个不存在的地址作为FROM/TO发一
个100M的邮件
locals
指定邮件本地地址,不存在,qmail-send就假定me文件的值为邮件主机的唯一可用本地邮件主机
localiphost
用来为使用ip来标示目的主机的邮件消息指定本地主机名的
例如:rainbow.linuxfane.com的ip地址为198.17.1.2 | localiphost内容为rainbow.linuxfane.com
那么一封发送给]的邮件的地址会被转换为
rcpthosts | morercpthosts
这两个文件用来指定qmail可以接收的除本地主机名外的其他域名
他们的作用是相同的,不过因为rcpthosts中的域名不能超过50行,所以才出现了morercpthosts文件
timeoutsmtpd
用来指定qmail-smtpd等待远程SMTP主机发送数据的时间线。默认的情况下,如果连接建立后1200秒内没有
接受到远程SMTP主机的任何信息,那么将关闭这个连接。
smtpgreeting
用来指定SMTP连接的欢迎标志
例如:smtpgreeting值为 welcome connect to out open relay mail server - ad.rainbow.linuxfane.com
那么建立一个smtp连接的时候将会看到如下信息:
Trying 198.17.x.x...
Connected to ad.rainbow.linuxfane.com.
Escape characteris ^].
220 welcome connect to out open relay mail server - ad.rainbow.linuxfane.com ESMTP
3.SMTP在TCP协议25号端口监听连接请求
4.连接和发送过程:
a.建立TCP连接
b.客户端发送HELO命令以标识发件人自己的身份,然后客户端发送MAIL命令
服务器端正希望以OK作为响应,表明准备接收
c.客户端发送RCPT命令,以标识该电子邮件的计划接收人,可以有多个RCPT行
服务器端则表示是否愿意为收件人接受邮件
d.协商结束,发送邮件,用命令DATA发送
e. 以.表示结束输入内容一起发送出去
f.结束此次发送,用QUIT命令退出。
qmail外发循环变换ip
安装qmail-1.03-bind-interface patch补丁,新建/var/qmail/control/bindroutes文件,内容为
:124.193.110.70
该ip即为qmail外发使用ip,不需重启qmail
循环变换,脚本
#!/bin/sh
while [ 1 ]
do
for i in "192.168.7.157" "192.168.7.158" "192.168.7.242"
do
echo ":$i">/var/qmail/control/bindroutes
sleep 1
done
done
每1秒变换一次ip,不足:有可能,当bindroutes里的内容无效时,qmail会用一个固定的ip外发邮件,所以,当echo没有完全写入bindroutes文件中时,qmail会用一个固定的ip发邮件,所以,有可能会有一个ip的外发频率比较高。
Qmail邮件队列工作原理
1.概述
以下是qmail的数据流简图
qmail-smtpd --- >>qmail-queue --->> qmail-send <<--- qmail-rspawn <<--- qmail- remote
/ |
qmail-inject _/ qmail-clean \_ qmail-lspawn <<--- qmail-local
qmail中,每一条消息都发送到中央队列等待发送,由qmail-queue进程控制。它在以下情况被调用:
1、当产生本地消息时,qmail-inject进程调用qmail-queue。
2、qmail-smtpd准备SMTP协议下的投递邮件任务时调用它。
3、向前(forwarded)发送邮件时,qmail-local调用它。
4、退回邮件时,qmail-send调用它。
每封邮件接着由qmail-lspawn 和qmail-rspawn协助qmail-sned进程完成投递,最后由qmail-clean清除邮件队列。这四个进程是系统由始至终都在运行的,十分重要。
qmail的队列被设计成很强的鲁棒性,并假定基础的文件系统也是强健的。所有的cleanups清除队列操作都由qmail-send独立控制,无须人为干预。详细请看第六部分。
2. 队列结构
队列里的每条消息都由唯一的号码标识,假定某条消息是的标识码是457。队列被组织成几个目录,每个目录都可能包含和这条消息相关的文件:
mess/457: 消息正文
todo/457: 信封: 消息的来源地址和目的地址。实际是指向intd/457的链接。
intd/457: 信封,由qmail-queue生成。
info/457: 信封上的发送者地址,预处理后生成。
local/457: 本地接受地址,预处理后生成。
remote/457: 远程接受地址,预处理后生成。
bounce/457: 传输错误信息。
以下是一条消息所有可能的状态。“+”号表示该目录下文件存在,“-”表示该目录下文件不存在,“?”表示未知。
S1. -mess -intd -todo -info -local -remote -bounce
S2. +mess -intd -todo -info -local -remote -bounce
S3. +mess +intd -todo -info -local -remote -bounce
S4. +mess ?intd +todo ?info ?local ?remote -bounce (queued,表示已经进入队列)
S5. +mess -intd -todo +info ?local ?remote ?bounce (preprocessed,表示已通过预处理)
重点:如果消息457存在,那么它也对应一个inode结点号457。
3. 消息如何进入队列?
添加一条消息到队列里,qmail-queue首先创建一个单独的目录,命名为 pid/。然后在这个目录下为这条消息创建一个独立文件。文件系统为这个文件分配唯一的标识,暂时记做457。qmail-queue监视这个文件标识,并保证这条消息进入状态S1。
qmail-queue接着把pid/ 更名为mess/457,进入状态S2。然后写一些相关的信息到mess/457目录下,创建目录intd/457,进入状态S3,并在intd/457下写入相关的信封信息。
最后,qmail-queue创建一个指向 intd/457 的链接 :todo/457 ,进入状态S4。到此为止,这条消息已经被处理到队列中,等待qmail-send的处理。
qmail-queue在处理任何文件之前,会启动一个24小时的超时记时器。一旦等待处理超过24小时,qmail-queue将中止当前进程。
我的理解:
qmail-queue首先在pid目录下创建一个inode(暂记为123)(S1),以这个inode的序列号作为该mail message的id,随后将该inode硬连接到mess目录下(S2),写入邮件的内容,并在intd目录下创建一个同名的文件,写入MAIL FROM和RCPT TO等信息(S3)后又将该文件link到todo目录下。至此,整个邮件的入队过程完成(S4),等待qmail-send的处理。 qmail-send不断扫描todo目录,如果有新邮件,它将todo文件中的MAIL FROM写入到info目录下的同名文中,RCPT TO分别写入到local和remote目录下,删除todo下的文件随后将该id加入到remote或者local的内存队列中(S5).(预处理完成)
4. 队列中的消息如何预处理
一旦消息进入等待队列,qmail-send必须判断哪些是要投递给本地接收者,哪些是要投递给远程接收者。它也有可能重写某些接受者地址。
当qmail-send检测到 todo/457 目录,得知消息457处于状态S4。于是,它先删除已经存在的(假如)info/457, local/457 和 remote/457目录。然后读取整个todo/457目录,生成 info/457 、 local/457 或者 remote/457 目录。完成这些操作后,删除 intd/457目录。这时,该条消息仍然处于S4状态。最后,qmail-send删除 todo/457 目录,进入S5状态。到此为止,这条消息已成功的完成预处理。
5. 预处理后,消息如何被投递。
处在S5状态的消息将被做如下处理。每个 local/457 和 remote/457 目录下的邮件地址将被标记NOT DONE 或 DONE:
DONE: 表示该消息已经被成功投递,或者是投递时遇到了永久性错误。这时,qmail-send将会停止对该消息所示的地址继续进行投递任务。
NOT DONE:如果已经有投递任务在尝试,该标记表示投递人物遇到暂时性错误。这种情况下,qmail-send会随后继续尝试投递。
qmail-send 会在空闲的时间尝试给标记了NOT DONE 的地址发送邮件。如果邮件被成功处理,qmail-send将在该地址标记DONE。如果遇到永久性错误,它首先发送一条错误通知到bounce/457目录(如果该目录不存在,就自动创建),然后将邮件地址标记为DONE。
注意:bounce/457被设计成不强健的。
qmail-send会在任意的时刻处理 bounce/457目录 ,可能如下:
1)从 bounce/457 和 mess/457目录创建一条新的bounce消息
2)删除bounce/457目录。
当local/457中所有的邮件地址都被标记DONE后,qmail-send将删除local/457。同样的,remote/457也做此处理。随后,qmail-send删除队列里的邮件:
第一步、如果 bounce/457存在,qmail-send按照上述的方法处理。
第二步、如果 bounce/457已删除,qmail-send删除 info/457目录,进入状态S2;然后删除 mess/457 ,进入状态S1。
6. Cleanups清除操作。
如果在qmail-queue处理邮件队列时,或者qmail-send正删除邮件时,系统因故障崩溃,当时处理的邮件就会处在S2或者S3状态。
如果qmail-send检查到有邮件处在S2或者S3状态(不是正在删除的邮件),而mess/457的创建时间已经过了36小时,它就相续删除 intd/457 (如果存在)和 mess/457。此时,正在处理该邮件的qmail-queue将被挂起。
同样的,如果qmail-send发现 pid/ 目录下的文件创建时间超过36小时,也将删除该目录。
如果在qmail-send投递邮件时发生系统崩溃故障,Cleanups则无须运行。最遭情况下,邮件可能会被投递两次。其实,在分布式邮件系统上,目前并没有一种有效的方法来降低邮件重复投递的可能。不妨试想一下,如果在接受方的邮件服务器响应之前,投递邮件的SMTP链接发生断路错误,该怎么办?显然,用户必须考虑到这种最坏情况,并再次发送邮件。同样的,如果在qmail-send进程为邮件做DONE标记之前,计算机崩溃了。那么,重启后的新进程必须再次发送该邮件。通常的方法是,利用日志文件。反正,删除重复邮件是接受方的事情,无须顾虑。
7. 更多
目前,info/457的存在有两种作用:
1、它记录了邮件信封中,发送人的信息。
2、守护进程通过它的修改时间来判断该邮件在队列中是否超时。
将来,随着系统改进, info/457 可能会存储更多的信息。任何改变都是向后兼容的,并将由版本号标识。
当qiaml-queue成功的将一条邮件加入到队列中时,它将启动一个由qmail-send设定的触发器。该触发器的工作机制是:命名管道锁触发-- lock/trigger 。在扫描 todo/ 之前,qmail-send 打开 lock/trigger下的管道O_NDELAY 设定可读。qiaml-queue通过向O_NDELAY写一个字节来触发qmail-send。这使得 locks/trigger 可读并唤醒qmail-send进程。在再次扫描 todo/ 之前,qmail-send 先关闭lock/trigger再打开它。
qmail系统中有9个核心程序:
qmail-smtpd:负责接收来自远程主机的邮件消息并将它们传送给qmail-queue处理。
mail-inject:程序是用来接收本地产生的邮件消息并传送给qmail-queue程序。
qmail-queue:程序处理他们发来的邮件,移进邮件队列以便发送。一旦消息被成功的放在邮件队列中,就调用qmail-send程序来处理他。
qmail-send:检查邮件队列中每一个消息状态,前一次邮件请求失败的消息被识别,并决定它是临时失败还是永久的,临时失败会再次投递,永久失败将被送递到mail-clean程序,被删除掉。qmail-clean是用来清除永久失败的的邮件消息的。
qmail-send调用了qmail-lspawn 和qmail-rspawn程序。qmail-send判断邮件是发给谁的,发给本地的,就交给qmail-lspawn程序,再由qmail-local投送到本地邮件服务器。要是确定为远程主机,就调用qmail-rspawn程序,qmail-rspawn为每一个邮件消息的接受方决定目的的邮件服务器,再调用qmail-remote程序发送。
qmail的进程
qmail-send splogger qmail qmail-lspawn qmail-rspawn qmail-clean
qmail在任何时候都要求有五个程序在后台运行。这些核心的程序允许qmail扫描新邮件,并将他们发送到适当的目的地。
qmail实用工具程序
除了以上五个qmail进程外,进程和本地用户在处理和传输消息的过程中,都还会用到几个其他的qmail实用工具程序。如下:
bouncesaying 允许用户将消息转发给程序或者程序不可用时反弹回信息
condredirect 允许用户将消息转发给程序,然后把消息转发到另一个地址
except 过去常用来修改一个调用程序的退出编码
forward 将消息转发给一个备用的邮件地址
maildir2mbox 将在maildir格式邮箱中的消息转换到一个标准的sendmail邮箱中
maildirwatch 产生一个maildir格式的邮箱
maildirwatch 监控一个用户的邮件目录并报告所有的新消息
preline 在将消息转发给相关程序前预处理这些消息
qbiff 当新消息到达后向用户终端显示器上写一个通知
qmail-clean 从qmail邮件队列中永远的移出无法到达的消息
qmail-inject 将新消息插入到qmail邮件队列中
qmail-local 向本地邮件系统上的用户投递消息
qmail-lspawn 由qmail-send调用以便向qmail-local程序转发消息
qmail-newmrh 用来从morercpthosts文件中创建qmail数据库
qmail-newn 从一个包含系统用户名和邮件名的ASCII文件表建立一个qmail用户文件
qmail-pop3d 接受pop3连接以便让用户阅读他们的邮箱
qmail-popup 用来验证pop3连接中的用户ID和密码
qmail-pw2u 将UNIX系统的用户id和密码转换成qmail-newn能用的一个表结构
qmail-qmtpd 接受远程主机发来的qmtp连接请求
qmail-qread 产生一个显示当前qmail邮件队列里消息的报告
qmail-qstat 产生一个报告来显示当前处在qmail投送状态下等待发送的消息数目
qmail-queue 将邮件消息排队等待投送
qmail-remote 将消息投送给远程邮件用户
qmail-rspawn 由qmail-send调用来向qmail-remote转发消息
qmail-send 尝试投送qmail邮件队列里的邮件消息
qmail-showctl 显示当前的qmail系统配置情况
qmail-smtpd 接收来自远程主机的smtp连接请求
qmail-start 初始化qmail的启动脚本
qmail-tcpok 用来清空qmail-remote中用到的tcp超时记录表
qmail-tcpto 显示当前的tcp超时记录表
qreceipt 用来会应对要求受到进行确认的消息
sendmail 用来将sendmail程序的功能复制到mta程序中
splogger 将消息插入系统登陆程序中
tcp-env 将网络连接的信息转换成unix环境变量