SuperMail更新-V0.8

[| 2010/09/28 00:51]
1,修正:在弹出修改/添加配置的框后,假如未提交任何数据就关闭框,不再刷新页面。
2,修正:初步解决邮件转发环路。
3,新:用户设置目标邮箱后发送通知邮件。
4,添加新域名,采取直接vps接收方式,避免一些邮件被qq企业邮箱当成垃圾邮件过滤掉。避免用qq邮箱向supermail地址发送邮件时被提示地址不存在。
今天准备上线SuperMail,发现后台程序有两个问题,一个问题是相对路径问题,这个好解决,在程序中设置一下工作路径就可以了。还有一个就是权限问题。

背景:/var/spool/mail/a 这个是邮箱,权限为660,属于a用户,mail组
程序的运行者:b,mail组。

b用户把a邮箱中的邮件复制到临时文件中后,调用mailbox类的clear方法将a邮箱清空。结果a邮箱的权限变成775,拥有者b,所属组b。猜测clear是把a邮箱删除,重新建立了一下。粗看了下mailbox源码,貌似不是这样的,时间原因,没有细究。(疑点:将/var/spool/mail/的umask设置成0177后a邮箱还是变成775,不知为何)

当sendmail给a邮箱投递信件的时候,发现a邮箱所有者非a,于是chmod所有者为a,并将权限改成600(邮箱权限默认是660的),结果导致b用户无法读取a邮箱。

导致这个问题的根本原因是clear方法的实现不当,所以决定重载clear函数,改为使用truncate函数将文件截短为0。遇到另一问题:如何取得a邮箱文件描述符。因为程序逻辑是mbox(mailbox的派生类)将a邮箱加锁,复制内容,清空,解锁。复制内容后,清空前是不能解锁邮箱的。复制内容后是mbox的私有成员_file拥有a邮箱的锁,假如这个时候通过open获得a邮箱的一个描述符,是无法操作a邮箱的(无法获得锁),_file又不能释放锁。所以只能设法获得_file,用来截短文件。查了下,mailbox类中没有方法可以获得私有的_file。尝试在mailbox的派生类中调用self._file.truncate(0),居然成功了。。看来Python中派生类中是可以使用父类的私有成员的。不过这么实现感觉比较山寨。期待更好的方法。

注意doctype的重要性

[| 2010/09/26 01:11]
今天用tipswindown,结果在ie中死活显示不正确,找了所有可能出毛病的地方,都找不到,实在郁闷死了。最后突然想起来页头doctype没加,加上:<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">后,好了。
做了这一周多,终于把邮件自动派发器的前后台都做出来了,不过只是一个demo。功能还很不完善。

后台采用python作为处理程序,前台设置页面使用php。用户配置数据库使用sqlite3。

做的过程中接触了不少新技术,学到了很多新东西。

codeigniter使用sqlite数据库

[| 2010/09/24 16:35]
今天做邮件转发程序的web页面,要读取配置数据库,由于配置数据库使用的是sqlite,所以要用php读取sqlite。

php使用sqlite有两种方式,一种是pdo驱动,一种是用php_sqlite模块。

首先试了试php_sqlite模块,结果发现centos没有这个模块,下了一个试着编译了一下,乱七八糟,这个方法太不普适。放弃。

然后使用pdo方法。用$test=new PDO('sqlite:test.db'),获得pdo连接后,可以使用了。

然后要在codeigniter里使用sqlite数据库,所以查了下文档,原来codeigniter 1.7.*集成的sqlite模块不支持sqlite3,而为了对php4的兼容性,没有包含pdo模块。还好官网有pdo驱动,按说明下载、配置好后发现报“SQLSTATE[HY000] [14] unable to open database file”这个错误。折腾了一下午。发现直接用:php test.php可以打开数据库,但是用web服务器就不行。看来要么是权限问题,要么是配置文件的不同。用两种方式打印了phpinfo,发现没有什么问题。疑惑了好长时间。。

突然想起来,数据库所在的目录树中有一层是没有给web服务器访问权限的。。怪不得,把权限放开,正常了。

Python中删除对象属性

[| 2010/09/23 19:24]
今天下午把dismail的数据库模块和邮件发送模块都做好,并调试好了。正在高兴,发现派发出去的信件里带了长长的一大串无用信息。想清理一下header。结果遍查Message类方法,没有对应的,只有添加header属性的函数,没有删除的。网上搜索了很长时间,毫无结果。一度想改写标准库,或者自己处理一下mbox文件,不过考虑到兼容性和稳定性,没有那样弄。

突然看到一篇文章说,可以用messageObject[key]的方式来访问header的字段,试了一下,果然可以。于是想到既然可以这样访问,能不能用这种方式去掉指定的属性呢?查了下手册,发现del方法,一试,好用。爽,问题解决了。用keys函数获取header中的所有字段,然后用正则筛选一下,删除掉无用的。邮件头大为精简了。
今天发现原来python中内置了mailbox类,可以解析linux系统邮箱。并附带了详细的说明,汗,我花好几天事件去看源码,python文档里几句话就说明白了。

由于现在还是python 2.5及以下版本的天下,所以我用的python手册也是2.5版本的,在里面查到mailbox模块中有mbox类,专门解析mbox格式存储的邮箱,准备拿来用。结果去开发环境上一跑,显示没有该模块,百思不得其解。后来查了一下,原来linux上python还是2.4版本,2.4版本中的mailbox模块是很弱小的。由于程序要在2.4平台上运行,所以要考虑问题的解决方法。

假如自己实现mbox类的话,太过麻烦,并且对于2.5平台上来说这个工作毫无用处。于是决定利用2.5版本上现有的mailbox模块。

把2.5版本的mailbox.py拷贝到当前目录下,改名为mailbox_v25.py。

在程序中使用如下方法,判断版本是不是2.5以下,假如是的话,导入mailbox_v25.py,假如不是的话,导入系统默认模块即可。
if (int)(map(str,sys.version_info)[1])<5:
import mailbox_v25 as mailbox
else:
import mailbox

ok,问题解决。以后假如版本升级,不再支持python2.4,那么可以移除mailbox_v25.py以精简包。
前几天突然想做一个信件分析并自动分发的程序。先给服务器设置了域名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的原理说的很少,大部分都是讲配置的。期待有深度分析的文章出现~~
由于邮件是串行放在文件里的,如何把一个一个的邮件分拆开就成了一个学问,想了一天,好多方案,都不大好。无语了,下了mailx的源码,研究一下,看看它是怎么分析出邮件数的。
今天搞到了个qq企业邮箱的邀请码,于是把我的域名邮箱迁移到了企业邮箱上,突发奇想,想让我的vps也当当邮件服务器,设一个二级域名的mx记录上去。

然后突然疑惑了,smtp是用来发邮件给其他服务器的,那接受其他服务器发过来的邮件怎么办呢?貌似pop3是接邮件的,但是一查又怪怪的。

后来终于搞懂了,smtp是服务器间传送邮件用的。pop3/imap是用户从服务器上取邮件用的。我只是想收邮件,并不用Foxmail等软件从服务器上取,所以不用装pop3服务器。

所以系统自带的sendmail就可以了。

sendmail默认是只监听本地端口的,这时只能向外网发邮件,不能接收外网发过来的邮件,我们需要修改配置文件。

在CentOS下/etc/mail/sendmail.mc,修改此文件,把DAEMON_OPTIONS(`Port=smtp,Addr=127.0.0.1, Name=MTA') 注释掉。

然后运行:m4 /etc/mail/sendmail.mc < sendmail.cf

这个时候可能会提示找不到*****cf文件。原因是没有装sendmail-cf这个软件包,装上即可

然后重启sendmail。

这个时候美滋滋的试着发邮件给vps,结果被退信了:said: 550 5.7.1 ... Relaying denied (in reply to RCPT TO command)

查了下,是由于/etc/mail/local-host-names 里没有配置服务器的域名,比如我想接收**@abc.com,那么在这个里面就要写上:abc.com

然后重启服务器。

再发,还是不行,告知User unknown 。这个问题是由于收件人不存在造成的,以收件人的名字新建一个用户即可。

再试,成功了。

还有一个地方,就是/etc/mail/access,这个文件是规定哪些ip可以使用这个smtp服务器发邮件,我并不想远程使用smtp发邮件,并且为了安全性考虑,这个文件保持不动,这样就只能localhost账户发送邮件出去了。

分页: 1/2 第一页 1 2 下页 最后页 [ 显示模式: 摘要 | 列表 ]