qmail源代码分析之qmail-smtpd.c

qmail 总览

tcpserver MUA
| |
V V
qmail-smtpd qmail-inject
| |
+---------qmail-queue<-----------+
|
|
qmail-send
|
+------------+------------+
| |
V V
qmail-rspawn qmail-lspawn
| |
V V
qmail-remote qmail-local
| |
| |
V V
INTERNET <----qmail-pop3d
|
|
vchkpw
|
|
qmail-popup
|
|
tcpserver--+

[/code:1:d803669f6f]



qmail-smtpd.c源代码分析(去掉了所有include)

qmail-smtpd是由tcpserver或由tcp-env启动。tcpserver负责监听端口,如果指定了-x rule.cbd,tcpserver会先决断是断开连接还是启动qmail子进程。如果没有指定-x参数启动tcpserver,那么直接启动qmail-smtpd.启动qmail-smtpd之前将来自网络的数据连接重定向到qmail-smtpd的fd0,fd1.还会初始化一些qmail-smtpd需要的环境变量,如TCPREMOTEIP.
tcp-env只会初始化qmail-smtpd的环境变量,不负责监听端口及重定向网络连接。所以tcp-env要和inetd配合使用。当然,由于初始化环境变量的工作tcpserver也会作,所以没有必要tcpserver和tcp-env配合使用.

qmail-smtpd完成邮件smtp命令的接收,并调用相应的处理程序。
检查mail 中的地址是否在control/badmailfrom中定义(MAIL命令)
检查是否设置了RELAYCLIENT环境变量或 rcpt 中的地址是否是control/rcpthosts中定义(RCPT命令)
需要明确的是qmail-smtpd只是简单的接收邮件内容传送给qmail-queue,并不对邮件进行转发(DATA命令)。
当然还要向qmail-queue传送mailfrom,mailto

[code:1:d803669f6f]
#define MAXHOPS 100
unsigned int databytes = 0; //邮件最大长度:0=无限
int timeout = 1200; //默认超时20分钟


//向网络写,超时值为control/timeoutsmtpd指定的值。没有这个文件则取默认值20分钟
int safewrite(fd,buf,len) int fd; char *buf; int len;
{
int r;
r = timeoutwrite(timeout,fd,buf,len);
if (r <= 0) _exit(1);
return r;
}

char ssoutbuf[512];
substdio ssout = SUBSTDIO_FDBUF(safewrite,1,ssoutbuf,sizeof ssoutbuf);

void flush() { substdio_flush(%26amp;ssout); }
void out(s) char *s; { substdio_puts(%26amp;ssout,s); }

//错误处理函数
void die_read() { _exit(1); }
void die_alarm() { out("451 timeout (#4.4.2)\r\n"); flush(); _exit(1); }
void die_nomem() { out("421 out of memory (#4.3.0)\r\n"); flush(); _exit(1); }
void die_control() { out("421 unable to read controls (#4.3.0)\r\n"); flush(); _exit(1); }
void die_ipme() { out("421 unable to figure out my IP addresses (#4.3.0)\r\n"); flush(); _exit(1); }
void straynewline() { out("451 See http://pobox.com/~djb/docs/smtplf.html.\r\n"); flush(); _exit(1); }

void err_bmf() { out("553 sorry, your envelope sender is in my badmailfrom list (#5.7.1)\r\n"); }
void err_nogateway() { out("553 sorry, that domain isn't in my list of allowed rcpthosts (#5.7.1)\r\n"); }
void err_unimpl() { out("502 unimplemented (#5.5.1)\r\n"); }
void err_syntax() { out("555 syntax error (#5.5.4)\r\n"); }
void err_wantmail() { out("503 MAIL first (#5.5.1)\r\n"); }
void err_wantrcpt() { out("503 RCPT first (#5.5.1)\r\n"); }
void err_noop() { out("250 ok\r\n"); }
void err_vrfy() { out("252 send some mail, i'll try my best\r\n"); }
void err_qqt() { out("451 qqt failure (#4.3.0)\r\n"); }


stralloc greeting = {0};

//输出提示信息*code
void smtp_greet(code) char *code;
{
substdio_puts(%26amp;ssout,code);
substdio_put(%26amp;ssout,greeting.s,greeting.len);
}


void smtp_help()
{
out("214 qmail home page: http://pobox.com/~djb/qmail.html\r\n");
}

void smtp_quit()
{
smtp_greet("221 "); out("\r\n"); flush(); _exit(0);
}

char *remoteip; //远端ip地址
char *remotehost; //远端主机名
char *remoteinfo; //远端信息
char *local; //本地主机
char *relayclient; //是否检查rcpthosts文件

stralloc helohost = {0};
char *fakehelo; /* pointer into helohost, or 0 */

void dohelo(arg) char *arg; {
if (!stralloc_copys(%26amp;helohost,arg)) die_nomem();
if (!stralloc_0(%26amp;helohost)) die_nomem();
//fakehelo变量,如果helo 参数指定的主机名与TCPREMOTEHOST环境变量中的主机名不同则
//fakehelo的值为helo命令的参数指定的主机名.如果两者相同则fekehelo为NULL;
//data命令处理程式用到这个变量
fakehelo = case_diffs(remotehost,helohost.s) ? helohost.s : 0;
}

int liphostok = 0;

stralloc liphost = {0};

int bmfok = 0;
stralloc bmf = {0};
struct constmap mapbmf;
void setup()
{
char *x;
unsigned long u;

if (control_init() == -1) die_control(); //control/me
//读入欢迎信息greeting,如果不存在则从me文件复制
if (control_rldef(%26amp;greeting,"control/smtpgreeting",1,(char *) 0) != 1)
die_control();
//读入localiphost,如果文件不存在则从me文件复制
liphostok = control_rldef(%26amp;liphost,"control/localiphost",1,(char *) 0);
if (liphostok == -1) die_control();

//读control/timeoutsmtpd存入timeout,用于控制超时的情况.
if (control_readint(%26amp;timeout,"control/timeoutsmtpd") == -1) die_control();
if (timeout <= 0) timeout = 1;

if (rcpthosts_init() == -1) die_control();

//读入badmailfrom文件存入 bmf
bmfok = control_readfile(%26amp;bmf,"control/badmailfrom",0);
if (bmfok == -1) die_control();
if (bmfok)
if (!constmap_init(%26amp;mapbmf,bmf.s,bmf.len,0)) die_nomem();

//读入databytes文件存入 databytes,如果该文件不存在,则将
//databytes的值设为0.

if (control_readint(%26amp;databytes,"control/databytes") == -1) die_control();
x = env_get("DATABYTES");
if (x) { scan_ulong(x,%26amp;u); databytes = u; }
if (!(databytes + 1)) --databytes;

//取tcp-environ环境变量,如果环境变量没有设置,将它的值设置为unknow.
//这些信息来自tcpserver,或tcp-env之类的程式
remoteip = env_get("TCPREMOTEIP");
if (!remoteip) remoteip = "unknown";
local = env_get("TCPLOCALHOST");
if (!local) local = env_get("TCPLOCALIP");
if (!local) local = "unknown";
remotehost = env_get("TCPREMOTEHOST");
if (!remotehost) remotehost = "unknown";
remoteinfo = env_get("TCPREMOTEINFO");

//从环境变量RELAYCLIENT读入.
//如果RELAYCLIENT变量没有设置那么relayclient将会是NULL.
relayclient = env_get("RELAYCLIENT");
dohelo(remotehost);
}


stralloc addr = {0}; /* will be 0-terminated, if addrparse returns 1 */


//对命令参数arg进行邮件地址分析
//并将分离出的email地址存入全局缓存addr
//成功返回值为1,失败返回0
int addrparse(arg)
char *arg;
{
int i;
char ch;
char terminator;
struct ip_address ip;
int flagesc;
int flagquoted;

//分离出邮件地址
//例如: arg="",或 arg=": [email protected] "
//执行下面这段程式后arg="[email protected]"
terminator = '>';
i = str_chr(arg,'<');
if (arg[i])
arg += i + 1;
else { /* partner should go read rfc 821 */
terminator = ' ';
arg += str_chr(arg,':');
if (*arg == ':') ++arg;
while (*arg == ' ') ++arg;
}

/* strip source route */
if (*arg == '@') while (*arg) if (*arg++ == ':') break;

if (!stralloc_copys(%26amp;addr,"")) die_nomem();
flagesc = 0;
flagquoted = 0;
for (i = 0;ch = arg[i];++i) { /* copy arg to addr, stripping quotes */
if (flagesc) {
if (!stralloc_append(%26amp;addr,%26amp;ch)) die_nomem();
flagesc = 0;
}
else {
if (!flagquoted %26amp;%26amp; (ch == terminator)) break;
switch(ch) {
case '\\': flagesc = 1; break;
case '"': flagquoted = !flagquoted; break;
default: if (!stralloc_append(%26amp;addr,%26amp;ch)) die_nomem();
}
}
}

/* could check for termination failure here, but why bother? */
if (!stralloc_append(%26amp;addr,"")) die_nomem();

//将ip地址转换为主机名:
//如 test@[10.0.6.21] 转换为 [email protected]
//依据是control/localiphost文件中有host.mydomain.org
if (liphostok) {
i = byte_rchr(addr.s,addr.len,'@');
if (i < addr.len) /* if not, partner should go read rfc 821 */
if (addr.s[i + 1] == '[')//比较是否是用[]括起来的IP地址
if (!addr.s[i + 1 + ip_scanbracket(addr.s + i + 1,%26amp;ip)])
if (ipme_is(%26amp;ip)) {
addr.len = i + 1;
if (!stralloc_cat(%26amp;addr,%26amp;liphost)) die_nomem();
if (!stralloc_0(%26amp;addr)) die_nomem();
}
}
if (addr.len > 900) return 0; //地址太长,出错返回
return 1;//成功返回
}
//简单的垃圾邮件检查
//检查全局缓冲区addr中的地址是否有在badmailfrom中定义,
//如果有则返回 1,否则返回 0.
int bmfcheck()
{
int j;
if (!bmfok) return 0;
if (constmap(%26amp;mapbmf,addr.s,addr.len - 1)) return 1;
j = byte_rchr(addr.s,addr.len,'@');
if (j < addr.len)
if (constmap(%26amp;mapbmf,addr.s + j,addr.len - j - 1)) return 1;
return 0;
}


//检查全局缓存addr中的邮件地址是否要进行转发(依据control/rcpthosts文件)
//可以进行转发返回1
//拒绝转发返回0
int addrallowed()
{
int r;
r = rcpthosts(addr.s,str_len(addr.s));
if (r == -1) die_control();
return r;
}


int seenmail = 0;
int flagbarf; /* defined if seenmail */
stralloc mailfrom = {0};
stralloc rcptto = {0};

void smtp_helo(arg) char *arg;
{
smtp_greet("250 "); out("\r\n");
seenmail = 0; dohelo(arg);
}
void smtp_ehlo(arg) char *arg;
{
out(accept_buf);
out("\r\n");
}

//data 命令解释程式
//完成向qmail-queue投递邮件
void smtp_data() {
int hops;
unsigned long qp;
char *qqx;

if (!seenmail) { err_wantmail(); return; } //如果没有执行过mail命令,出错返回
if (!rcptto.len) { err_wantrcpt(); return; } //如果没有执行rcpt命令,出错返回
seenmail = 0; //将mail命令标志失效,
//databytes 邮件最大长度,如果没有指定那么它的值将是0
if (databytes) bytestooverflow = databytes + 1;
if (qmail_open(%26amp;qqt) == -1) { err_qqt(); return; }//建立子进程执行qmail-queue
qp = qmail_qp(%26amp;qqt); //qp 为qmail-queue process缩写,it's a process id.
out("354 go ahead\r\n");

//向新建立的进程传送邮件头
received(%26amp;qqt,"SMTP",local,remoteip,remotehost,remoteinfo,fakehelo);
blast(%26amp;hops);
hops = (hops >= MAXHOPS);
if (hops) qmail_fail(%26amp;qqt);
//向qmail-queue传送邮件头信息.
//如果[email protected][email protected]发送邮件,那么向qmail-queue传送的字符串将是
// [email protected]"> [email protected]@hg.org
qmail_from(%26amp;qqt,mailfrom.s);
qmail_put(%26amp;qqt,rcptto.s,rcptto.len);

qqx = qmail_close(%26amp;qqt);
if (!*qqx) { acceptmessage(qp); return; }//如果接收成功
if (hops) { out("554 too many hops, this message is looping (#5.4.6)\r\n"); return; }
if (databytes) if (!bytestooverflow) { out("552 sorry, that message size exceeds my databytes limit (#5.3.4)\r\n"); return; }
if (*qqx == 'D') out("554 "); else out("451 ");

out(qqx + 1);
out("\r\n");
}


//smtp命令处理函数表
struct commands smtpcommands[] = {
{ "rcpt", smtp_rcpt, 0 }
, { "mail", smtp_mail, 0 }
, { "data", smtp_data, flush } //建立子进程执行qamil-queue,并向其传送邮件.
, { "quit", smtp_quit, flush }
, { "helo", smtp_helo, flush }
, { "ehlo", smtp_ehlo, flush }
, { "rset", smtp_rset, 0 }
, { "help", smtp_help, flush }
, { "noop", err_noop, flush } //实际上未实现的命令
, { "vrfy", err_vrfy, flush } //实际上未实现的命令
, { 0, err_unimpl, flush } //命令错误
} ;



/*
qmail-smtpd 是由tcpserver,或tcp-env之类的程式启动
tcpserver,tcp-env将来自网络的连接重定向到qmail-smtpd的标准输入及标准输出.这些程式建立一些环境变量(如TCPREMOTEHOST,TCPREMOTEIP)将由setup()函数使用
*/
void main()
{
sig_pipeignore();//忽略信号.
if (chdir(auto_qmail) == -1) die_control();//改变当前目录到 /var/qmail.
setup();//读控制文件及相应的环境变量.
if (ipme_init() != 1) die_ipme(); //取本地接口的IP地址:
smtp_greet("220 "); //显示欢迎信息.
out(" ESMTP\r\n");
//从标准输入(网络连接)读入smtp命令.
if (commands(%26amp;ssin,%26amp;smtpcommands) == 0) die_read();
die_nomem();
}

你可能感兴趣的:(mail)