sendmail与mail工作原理及代码分析
[| 2010/09/19 20:24]
前几天突然想做一个信件分析并自动分发的程序。先给服务器设置了域名MX记录,开放了端口,设置了收信专用账户。然后准备开始分析邮件。
发现邮件在服务器上是在一个文件中连续存储的。打开/var/spool/mail/username可以发现里面顺序存储了很多邮件。那么把这些邮件拆开就成了问题。Mail命令可以分析并分拆邮件,但是mail命令有自己的命令行,用shell的方法对其操作很麻烦。
然后决定看一下邮件的协议,看看邮件的规范是什么,从中找出特征值来分隔。后来发现各个邮件服务器发送的邮件形状各异,很难找到一些共性。而邮件规范RFC822并没有定义太严格的邮件格式,只是定义邮件头和邮件正文由换行隔开。(
)。qq,163的邮件都是中规中矩的,很好识别。但是学校的邮件服务器发送的邮件就惨不忍睹。想了一天,也没有想出比较好的方法。
上面只是问题一,还有一个问题就是文件同步互斥问题。如何保证读到的文件的完整性、sendmail在写入文件的策略是什么,都不太清楚。
由于有了上面的问题,想到系统内置的mail命令应该是比较完美解决这些问题的,于是决定看一下mail的实现方法。
首先查到mail命令从属的软件包是mailx。版本是:mailx-8.1.1-44.2.2。从网上找到mailx-8.1.1-44.2.2.src.rpm 下载下来。运行:rpm -ivh mailx-8.1.1-44.2.2.src.rpm。这样,源码就被解压到了/usr/src/redhat/下。该文件夹下有两个目录,一个是SOURCE,一个是SPECS。其中SOURCE下就是补丁和源码。SPECS里是打包rpm的一个工具文件。
到SOURCE目录下,发现有一个mailx-8.1.1.tar.gz文件,一堆补丁,和一个.c文件。注意要先打补丁,然后再编译、查看源码。我刚开始的时候直接解开tar.gz开始弄,发现里面的Makefile都没法通过编译。自己写Makefile编译后还有bug。这是因为没有打补丁的缘故。
下面说一下打补丁。这个时候要借助SPECS里面的文件。首先把tar.gz解开。解出的mailx-8.1.1文件夹和补丁们放在同一个目录下。然后去mailx.spec里看,有一句:Source1: flock.c。说明SOURCE下的flock.c文件是后来补的源文件,需要放到mailx-8.1.1里面。下面接着是:
Patch0: mailx-8.1.1.debian.patch
Patch1: mailx-8.1.1.security.patch
Patch2: mailx-8.1.1.nolock.patch
Patch3: mailx-8.1.1.debian2.patch
Patch4: mailx-noroot.patch
Patch5: mailx-nopanic.patch
Patch6: mailx-nullchar.patch
Patch7: mailx-8.1.1-fhs.patch
Patch8: mailx-8.1.1-environ.patch
Patch9: mailx-8.1.1-siglj.patch
Patch10: mailx-8.1.1-bug15728.patch
Patch11: mailx-8.1.1-bug10074.patch
Patch12: mailx-8.1.1-uidcheck.patch
Patch13: mailx-8.1.1-flock.patch
Patch14: mailx-8.1.1-nostrip.patch
Patch15: mailx-8.1.1-ctime.patch
Patch16: mailx-8.1.1-bug134837.patch
Patch17: mailx-8.1.1-manpage-fix.patch
Patch18: mailx-8.1.1-manfix.patch
Patch19: mailx-8.1.1-display.patch
Patch20: mailx-8.1.1-bug44798.patch
Patch21: mailx-8.1.1-bug58672.patch
Patch22: mailx-8.1.1-reproblem.patch
Patch23: mailx-8.1.1-mbproblem.patch
Patch24: mailx-8.1.1-unread.patch
这个是打补丁的顺序。需要用:patch -p0 < *****.patch 挨个执行一遍。(-p0具体含义可查patch用法,和目录层级有关系。假如把补丁文件和源文件放到一起就是-p1)
这么多补丁要是手动去打的话很累。我直接写了个脚本,按列表挨个执行一遍打补丁操作。
打完补丁后再去Makefile里看,会发现现在Makefile已经完整了,直接make就可以编译。这时要注意,通过打补丁,有的源文件已经不再被需要。dotlock.c被no_dot_lock.c替换了。在读源码的时候要注意这一点。假如想使用dotlock.c的话可以修改Makefile,不过还要注意dotlock.c在包含头文件的时候#include "extern.h"和#include "rcv.h"这两句反了,要对调一下才能通过编译。
现在开始读源码,从main.c开始,先是进行一些校验活动,临时文件的创建,参数读取。在265行if (setfile(ef) < 0)调用setfile开始对系统邮箱进行处理。
去lex.c找到setfile,也是一些初始化、校验过程后。156行setptr(ibuf);调用setptr函数,这个函数是分析系统邮箱的过程。
去fio.c,找到setptr函数。115到151行是重点之所在。判断逻辑是:假如一行的开头是From并且后面是一个空格,那么这是一封邮件的开始。向下移动到第一个空行。到达邮件正文段。直到遇到下一封邮件头。
在mail处理系统邮箱的时候会用flock/fcntl把文件加锁,解决了互斥问题。
这里要注意,邮件开头是From 空格 其他值,而不是邮件头里的From字段,两者相差一个冒号。我刚开始以为mail规定From字段必须在第一行,跑去查RFC822,结果那里说不强制次序。分析收到的邮件发现确实都是From开头的。过了好一会才发现并不是From字段。。。
下面又会疑惑了,这个From开头的行是哪里来的?邮件的发送方并没有发送这个字段。况且规范并没有规定这个,即使有的邮件服务器这样发送,一定会有不太规范的邮件服务器不这样发送。这样会出问题。经过分析有可能是sendmail在收邮件的时候给加上的。
下载sendmail的源码。阅读README,里面讲到mail.local文件夹下是负责向用户系统邮箱投递邮件的模块。进去后发现mail.local.c文件。在784行,(void) fprintf(fp, "From %s %s", from, ctime(&tval)); 显然,sendmail在投递邮件的时候会先写一行,结构是:From 空格 寄信人 空格 时间。这就保证了每封邮件都是From 空格开始了。我们可以放心的用这一特征来分析了。
mail.local.c里还可以发现,在写入文件前也是加了锁的。所以我们在读邮箱时也要加锁,假如加锁成功,那么可以保证我们分析文件与sendmail投递互斥进行。
不过需要注意一点。假如分析邮件的过程比较长,那么需要把邮件先移动到一个临时文件里然后分析。避免长时间加锁邮箱导致sendmail发信队列积压。并且移动完后一定不要忘了给邮箱解锁。
以上就是这几天阅读代码的一个小总结,欢迎拍砖。网上关于sendmail和mail的原理说的很少,大部分都是讲配置的。期待有深度分析的文章出现~~
发现邮件在服务器上是在一个文件中连续存储的。打开/var/spool/mail/username可以发现里面顺序存储了很多邮件。那么把这些邮件拆开就成了问题。Mail命令可以分析并分拆邮件,但是mail命令有自己的命令行,用shell的方法对其操作很麻烦。
然后决定看一下邮件的协议,看看邮件的规范是什么,从中找出特征值来分隔。后来发现各个邮件服务器发送的邮件形状各异,很难找到一些共性。而邮件规范RFC822并没有定义太严格的邮件格式,只是定义邮件头和邮件正文由换行隔开。(
)。qq,163的邮件都是中规中矩的,很好识别。但是学校的邮件服务器发送的邮件就惨不忍睹。想了一天,也没有想出比较好的方法。
上面只是问题一,还有一个问题就是文件同步互斥问题。如何保证读到的文件的完整性、sendmail在写入文件的策略是什么,都不太清楚。
由于有了上面的问题,想到系统内置的mail命令应该是比较完美解决这些问题的,于是决定看一下mail的实现方法。
首先查到mail命令从属的软件包是mailx。版本是:mailx-8.1.1-44.2.2。从网上找到mailx-8.1.1-44.2.2.src.rpm 下载下来。运行:rpm -ivh mailx-8.1.1-44.2.2.src.rpm。这样,源码就被解压到了/usr/src/redhat/下。该文件夹下有两个目录,一个是SOURCE,一个是SPECS。其中SOURCE下就是补丁和源码。SPECS里是打包rpm的一个工具文件。
到SOURCE目录下,发现有一个mailx-8.1.1.tar.gz文件,一堆补丁,和一个.c文件。注意要先打补丁,然后再编译、查看源码。我刚开始的时候直接解开tar.gz开始弄,发现里面的Makefile都没法通过编译。自己写Makefile编译后还有bug。这是因为没有打补丁的缘故。
下面说一下打补丁。这个时候要借助SPECS里面的文件。首先把tar.gz解开。解出的mailx-8.1.1文件夹和补丁们放在同一个目录下。然后去mailx.spec里看,有一句:Source1: flock.c。说明SOURCE下的flock.c文件是后来补的源文件,需要放到mailx-8.1.1里面。下面接着是:
Patch0: mailx-8.1.1.debian.patch
Patch1: mailx-8.1.1.security.patch
Patch2: mailx-8.1.1.nolock.patch
Patch3: mailx-8.1.1.debian2.patch
Patch4: mailx-noroot.patch
Patch5: mailx-nopanic.patch
Patch6: mailx-nullchar.patch
Patch7: mailx-8.1.1-fhs.patch
Patch8: mailx-8.1.1-environ.patch
Patch9: mailx-8.1.1-siglj.patch
Patch10: mailx-8.1.1-bug15728.patch
Patch11: mailx-8.1.1-bug10074.patch
Patch12: mailx-8.1.1-uidcheck.patch
Patch13: mailx-8.1.1-flock.patch
Patch14: mailx-8.1.1-nostrip.patch
Patch15: mailx-8.1.1-ctime.patch
Patch16: mailx-8.1.1-bug134837.patch
Patch17: mailx-8.1.1-manpage-fix.patch
Patch18: mailx-8.1.1-manfix.patch
Patch19: mailx-8.1.1-display.patch
Patch20: mailx-8.1.1-bug44798.patch
Patch21: mailx-8.1.1-bug58672.patch
Patch22: mailx-8.1.1-reproblem.patch
Patch23: mailx-8.1.1-mbproblem.patch
Patch24: mailx-8.1.1-unread.patch
这个是打补丁的顺序。需要用:patch -p0 < *****.patch 挨个执行一遍。(-p0具体含义可查patch用法,和目录层级有关系。假如把补丁文件和源文件放到一起就是-p1)
这么多补丁要是手动去打的话很累。我直接写了个脚本,按列表挨个执行一遍打补丁操作。
打完补丁后再去Makefile里看,会发现现在Makefile已经完整了,直接make就可以编译。这时要注意,通过打补丁,有的源文件已经不再被需要。dotlock.c被no_dot_lock.c替换了。在读源码的时候要注意这一点。假如想使用dotlock.c的话可以修改Makefile,不过还要注意dotlock.c在包含头文件的时候#include "extern.h"和#include "rcv.h"这两句反了,要对调一下才能通过编译。
现在开始读源码,从main.c开始,先是进行一些校验活动,临时文件的创建,参数读取。在265行if (setfile(ef) < 0)调用setfile开始对系统邮箱进行处理。
去lex.c找到setfile,也是一些初始化、校验过程后。156行setptr(ibuf);调用setptr函数,这个函数是分析系统邮箱的过程。
去fio.c,找到setptr函数。115到151行是重点之所在。判断逻辑是:假如一行的开头是From并且后面是一个空格,那么这是一封邮件的开始。向下移动到第一个空行。到达邮件正文段。直到遇到下一封邮件头。
在mail处理系统邮箱的时候会用flock/fcntl把文件加锁,解决了互斥问题。
这里要注意,邮件开头是From 空格 其他值,而不是邮件头里的From字段,两者相差一个冒号。我刚开始以为mail规定From字段必须在第一行,跑去查RFC822,结果那里说不强制次序。分析收到的邮件发现确实都是From开头的。过了好一会才发现并不是From字段。。。
下面又会疑惑了,这个From开头的行是哪里来的?邮件的发送方并没有发送这个字段。况且规范并没有规定这个,即使有的邮件服务器这样发送,一定会有不太规范的邮件服务器不这样发送。这样会出问题。经过分析有可能是sendmail在收邮件的时候给加上的。
下载sendmail的源码。阅读README,里面讲到mail.local文件夹下是负责向用户系统邮箱投递邮件的模块。进去后发现mail.local.c文件。在784行,(void) fprintf(fp, "From %s %s", from, ctime(&tval)); 显然,sendmail在投递邮件的时候会先写一行,结构是:From 空格 寄信人 空格 时间。这就保证了每封邮件都是From 空格开始了。我们可以放心的用这一特征来分析了。
mail.local.c里还可以发现,在写入文件前也是加了锁的。所以我们在读邮箱时也要加锁,假如加锁成功,那么可以保证我们分析文件与sendmail投递互斥进行。
不过需要注意一点。假如分析邮件的过程比较长,那么需要把邮件先移动到一个临时文件里然后分析。避免长时间加锁邮箱导致sendmail发信队列积压。并且移动完后一定不要忘了给邮箱解锁。
以上就是这几天阅读代码的一个小总结,欢迎拍砖。网上关于sendmail和mail的原理说的很少,大部分都是讲配置的。期待有深度分析的文章出现~~
Anoymous
2011/06/05 06:42
看不到
分页: 1/1 1