有好几种从Unix程序发电子邮件的方法。根据不同情况最好的选择有所不同,所以我将提供两个方法。还有第三种方法,这里没有说道,是连接本地主机的SMTP (译者注:SMTP:Simple Mail Transfer Protocol简单邮件传输协议)端口并直接使用SMTP协议,参见RFC 821(译者注:RFC:Request For Comments)。
简单方法:/bin/mail
对于简单应用,执行‘mail’程序已经是足够了(通常是‘/bin/mail’,但一些系统上有可能是‘/usr/bin/mail’)。
*警告:*UCB Mail程序的一些版本甚至在非交互模式下也会执行在消息体(message body)中以‘~!’或‘~|’打头的行所表示的命令。这可能有安全上的风险。
象这样执行:‘mail -s '标题' 收件人地址 ...’,程序将把标准输入作为消息体,并提供缺省得消息头(其中包括已设定的标题),然后传递整个消息给‘sendmail’ 进行投递。
这个范例程序在本地主机上发送一封测试消息给‘root’:
#include
#define MAILPROG "/bin/mail"
int main()
{
FILE *mail = popen(MAILPROG " -s 'Test Message' root", "w");
if (!mail)
{
perror("popen");
exit(1);
}
fprintf(mail, "This is a test./n");
if (pclose(mail))
{
fprintf(stderr, "mail failed!/n");
exit(1);
}
}
如果要发送的正文已经保存在一个文件中,那么可以这样做:
system(MAILPROG " -s 'file contents' root
这个方法可以扩展到更复杂的情况,但是得当心很多潜在的危险(pitfalls):
· 如果使用system()或popen(),你必须非常当心将参数括起来从而保护它们不被 错误的进行文件名匹配替换或单词分割。
· 基于用户设置数据来构造命令行是缓冲区越界错误和其它安全漏洞的普遍原因。
· 这种方法不允许设定CC:(译者注:CC:Carbon Copy 抄送)或 BCC:(译者注: BCC:Blind Carbon Copy:盲送,指投递地址不在消息中出现的抄送)的收件人。 (一些/bin/mail程序的版本允许,其它则不允许)
直接启动邮件传输代理:/usr/bin/sendmail
‘mail’程序是“邮件用户代理”(Mail User Agent)的一个例子,它旨在供用户执行以收发电子邮件,但它并不负责实际的传输。一个用来传输邮件的程序被称为“邮件传输代理”(MTA),而在Unix系统普遍能找到的邮件传输代理被称为 ‘sendmail’。也有其它在使用的邮件传输代理,比如‘MMDF’,但这些程序通常包括一个程序来模拟‘sendmail’的普通做法。
历史上,‘sendmail’通常在‘/usr/lib’里找到,但现在的趋势是将应用库程序从 ‘/usr/lib’挪出,并挪入比如‘/usr/sbin’或‘/usr/libexec’等目录。结果是,一般总是以绝对路径启动‘sendmail’程序,而路径是由系统决定的。
为了了解‘sendmail’程序怎样工作,通常需要了解一下“信封”(envelope)的概念。这非常类似书面信件;信封上定义这个消息投递给谁,并注明由谁发出( 为了报告错误的目的)。在信封中包含的是“消息头”和“消息体”,之间由一个空行隔开。消息头的格式主要在RFC 822中提供;并且参见MIME 的RFC文档。( 译者注:MIME的文档包括:RFC1521,RFC1652)
有两种主要的方法使用‘sendmail’程序以生成一个消息:要么信封的收件人能被显式的提供,要么‘sendmail’程序可被指示从消息头中推理出它们。两种方法都有优缺点。
5.2.2.1. 显式提供信封内容
消息的信封内容可在命令行上简单的设定。它的缺点在于邮件地址可能包含的字符会造成‘system()’和‘popen() ’程序可观的以外出错(grief),比如单引号,被括起的字符串等等。传递这些指令给shell程序并成功解释可以预见潜在的危险。(可以将命令中任何一个单引号替换成单引号、反斜杠、单引号、单引号的顺序组合,然后再将整个地址括上单引号。可怕,呃?)
以上的一些不愉快可以通过避开使用‘system()’或‘popen()’函数并求助于‘ fork()’和‘exec()’函数而避免。这有时不管怎样也是需要的;比如,用户自定义的对于SIGCHLD信号的处理函数通常会打断‘pclose()’函数从而影响到或大或小的范围。
这里是一个范例程序:
#include
#include
#include
#include
#include
#include
/* #include
#ifndef _PATH_SENDMAIL
#define _PATH_SENDMAIL "/usr/lib/sendmail"
#endif
/* -oi 意味着 "不要视‘ .’为消息的终止符"
* 删除这个选项 ,"--" 如果使用sendmail 8版以前的版本 (并希望没有人
* 曾经使用过一个以减号开头的收件人地址)
* 你也许希望加 -oem (report errors by mail,以邮件方式报告错误)
*/
#define SENDMAIL_OPTS "-oi","--"
/* 下面是一个返回数组中的成员数的宏 */
#define countof(a) ((sizeof(a))/sizeof((a)[0]))
/* 发送由FD所包含以读操作打开的文件之内容至设定的收件人;前提是这
* 个文件中包含RFC822定义的消息头和消息体,收件人列表由NULL指针
* 标志结束;如果发现错误则返回-1,否则返回sendmail的返回值(它使用
*
*/
int send_message(int fd, const char **recipients)
{
static const char *argv_init[] = { _PATH_SENDMAIL, SENDMAIL_OPTS };
const char **argvec = NULL;
int num_recip = 0;
pid_t pid;
int rc;
int status;
/* 计算收件人数目 */
while (recipients[num_recip])
++num_recip;
if (!num_recip)
return 0; /* 视无收件人为成功 */
/* 分配空间给参数矢量 */
argvec = malloc((sizeof char*) * (num_recip+countof(argv_init)+1));
if (!argvec)
return -1;
/* 初始化参数矢量 */
memcpy(argvec, argv_init, sizeof(argv_init));
memcpy(argvec+countof(argv_init),
recipients, num_recip*sizeof(char*));
argvec[num_recip + countof(argv_init)] = NULL;
/* 需要在此增加一些信号阻塞 */
/* 产生子进程 */
switch (pid = fork())
{
case 0: /* 子进程 */
/* 建立管道 */
if (fd != STDIN_FILENO)
dup2(fd, STDIN_FILENO);
/* 其它地方已定义 -- 关闭所有>=参数的文件描述符对应的参数 */
closeall(3);
/* 发送: */
execv(_PATH_SENDMAIL, argvec);
_exit(EX_OSFILE);
default: /* 父进程 */
free(argvec);
rc = waitpid(pid, &status, 0);
if (rc < 0)
return -1;
if (WIFEXITED(status))
return WEXITSTATUS(status);
return -1;
case -1: /* 错误 */
free(argvec);
return -1;
}
}
允许sendmail程序推理出收件人
‘sendmail’的‘-t’选项指令‘sendmail’程序处理消息的头信息,并使用所有包含收件人(即:‘To:’,‘Cc:’和‘Bcc:’)的头信息建立收件人列表。它的优点在于简化了‘sendmail’的命令行,但也使得设置在消息头信息中所列以外的收件人成为不可能。(这通常不是一个问题)
作为一个范例,以下这个程序将标准输入作为一个文件以MIME附件方式发送给设定的收件人。为简洁起见略去了一些错误检查。这个程序需要调用‘metamail’ 分发程序包的‘mimecode’程序。
#include
#include
#include
/* #include
#ifndef _PATH_SENDMAIL
#define _PATH_SENDMAIL "/usr/lib/sendmail"
#endif
#define SENDMAIL_OPTS "-oi"
#define countof(a) ((sizeof(a))/sizeof((a)[0]))
char tfilename[L_tmpnam];
char command[128+L_tmpnam];
void cleanup(void)
{
unlink(tfilename);
}
int main(int argc, char **argv)
{
FILE *msg;
int i;
if (argc < 2)
{
fprintf(stderr, "usage: %s recipients.../n", argv[0]);
exit(2);
}
if (tmpnam(tfilename) == NULL
|| (msg = fopen(tfilename,"w")) == NULL)
exit(2);
atexit(cleanup);
fclose(msg);
msg = fopen(tfilename,"a");
if (!msg)
exit(2);
/* 建立收件人列表 */
fprintf(msg, "To: %s", argv[1]);
for (i = 2; i < argc; i++)
fprintf(msg, ",/n/t%s", argv[i]);
fputc('/n',msg);
/* 标题 */
fprintf(msg, "Subject: file sent by mail/n");
/* sendmail程序会自动添加 From:, Date:, Message-ID: 等消息头信息 */
/* MIME的处理 */
fprintf(msg, "MIME-Version: 1.0/n");
fprintf(msg, "Content-Type: application/octet-stream/n");
fprintf(msg, "Content-Transfer-Encoding: base64/n");
/* 消息头结束,加一个空行 */
fputc('/n',msg);
fclose(msg);
/* 执行编码程序 */
sprintf(command, "mimencode -b >>%s", tfilename);
if (system(command))
exit(1);
/* 执行信使程序 */
sprintf(command, "%s %s -t <%s",
_PATH_SENDMAIL, SENDMAIL_OPTS, tfilename);
if (system(command))
exit(1);
return 0;
}