本文共 16114 字,大约阅读时间需要 53 分钟。
在以前接触的项目中,一直都是在做网站时用到了发送mail 的功能,在asp 和.net 中都有相关的发送mail 的类, 实现起来非常简单。最近这段时间因工作需要在C++ 中使用发送mail 的功能,上网搜了一大堆资料,终于得以实现,总结自己开发过程中碰到的一些问题,希望对需的人有所帮助, 由于能力有限, 文中不免有些误解之处, 望大家能指正!!
其实,使用C++ 发送mail 也是很简的事, 只需要了解一点SMTP 协议和socket 编程就OK 了, 网络上也有很多高人写好的mail 类源码,有兴趣的朋友可以下载看看。(其实网上的很多代码都有问题,缺乏高质量和稳定性,特别是网络这一块的处理,可以尝试使用质量较高的libcurl库中的smtp模块接口来实现) 1. SMTP 常用命令简介 1). SMTP 常用命令 HELO/EHLO 向服务器标识用户身份 MAIL 初始化邮件传输 mail from: RCPT 标识单个的邮件接收人;常在MAIL 命令后面 可有多个rcpt to: DATA 在单个或多个RCPT 命令后,表示所有的邮件接收人已标识,并初始化数据传输,以. 结束。 VRFY 用于验证指定的用户/ 邮箱是否存在;由于安全方面的原因,服务器常禁止此命令 EXPN 验证给定的邮箱列表是否存在,扩充邮箱列表,也常被禁用 HELP 查询服务器支持什么命令 NOOP 无操作,服务器应响应OK QUIT 结束会话 RSET 重置会话,当前传输被取消 如你对SMTP 命令不了解,可以用telnet 命令登陆到smtp 服务器用help 命令进行查看: Normal 0 7.8 磅 0 2 false false false MicrosoftInternetExplorer4 220 tdcsw.maintek.corpnet.asus ESMTP Sendmail 8.13.8/8.13.8; Sat, 9 Jan 2010 10: 45:09 +0800 help 214-2.0.0 This is sendmail 214-2.0.0 Topics: 214-2.0.0 HELO EHLO MAIL RCPT DATA 214-2.0.0 RSET NOOP QUIT HELP VRFY 214-2.0.0 EXPN VERB ETRN DSN AUTH 214-2.0.0 STARTTLS 214-2.0.0 For more info use "HELP <topic>". 214-2.0.0 To report bugs in the implementation see 214-2.0.0 http://www.sendmail.org/email-addresses.html 214-2.0.0 For local information send email to Postmaster at your site. 214 2.0.0 End of HELP info 2).SMTP 返回码含义 * 邮件服务返回代码含义 * 500 格式错误,命令不可识别(此错误也包括命令行过长) * 501 参数格式错误 * 502 命令不可实现 * 503 错误的命令序列 * 504 命令参数不可实现 * 211 系统状态或系统帮助响应 * 214 帮助信息 * 220 服务就绪 * 221 服务关闭传输信道 * 421 服务未就绪,关闭传输信道(当必须关闭时,此应答可以作为对任何命令的响应) * 250 要求的邮件操作完成 * 251 用户非本地,将转发向 * 450 要求的邮件操作未完成,邮箱不可用(例如,邮箱忙) * 550 要求的邮件操作未完成,邮箱不可用(例如,邮箱未找到,或不可访问) * 451 放弃要求的操作;处理过程中出错 * 551 用户非本地,请尝试 * 452 系统存储不足,要求的操作未执行 * 552 过量的存储分配,要求的操作未执行 * 553 邮箱名不可用,要求的操作未执行(例如邮箱格式错误) * 354 开始邮件输入,以. 结束 * 554 操作失败 * 535 用户验证失败 * 235 用户验证成功 * 334 等待用户输入验证信息 for next connection>; 3) SMTP 命令应用 我们下需使用telnet 命令实现smtp 邮件的发送,具体操作如下: 220 tdcsw.com ESMTP Sendmail 8.13.8/8.13.8; Wed, 23 Dec 2009 18 :18:18 +0800 HELO tdcsw 250 tdcsw.com Hello x-128-101-1-240.ahc.umn.edu [128.101.1.240], pleased to meet you MAIL FROM:lily@tdcsw.com 250 2.1.0 lily@tdcsw.com... Sender ok RCPR TO:sam@163.com 250 2.1.5 carven@tdcsw.pegatroncorp.com... Recipient ok DATA 354 Enter mail, end with "." on a line by itself SUBJECT:HELLO HI: HAR are you? . 250 2.0.0 nBNAIIG4000507 Message accepted for delivery quit 221 2.0.0 tdcsw.maintek.corpnet.asus closing connection Connection to host lost. 2. 用C++ 实现Mail 发送 为了便于理解, 在此就不封装Mail 类了, 而是以过程式函数方式给出. 1). 首先需要建立TCP 套接字, 连接端口依服务器而定,SMTP 服务默认端口为25, 我们以 默认端口为例 WSADATA wsaData; int SockFD; WSAStartup(MAKEWORD(2,2), &wsaData); SockFD = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); ServAddr.sin_family = AF_INET; ServAddr.sin_addr.s_addr = inet_addr (“192.168.1.1”); //192.168.1.1 为服务器地址 ServAddr.sin_port = htons(25); connect(SockFD, (struct sockaddr *)&ServAddr, sizeof(ServAddr)); 2). 发送SMTP 命令及数据 const char HEADER[] = "HELO smtpSrv\r\n" "MAIL FROM: sender@126.com\r\n" "RCPT TO: recv@gmail.com\r\n" "DATA\r\n" "FROM: sender@126.com\r\n" "TO: recv@gmail.com\r\n" "SUBJECT: this is a test\r\n" "Date: Fri, 8 Jan 2010 16:12:30\r\n" "X-Mailer: shadowstar's mailer\r\n" "MIME-Version: 1.0\r\n" "Content-type: text/plain\r\n\r\n"; //send HEADER send(SockFD, HEADER, strlen(HEADER), 0); const char CONTENT[]="this is content.\r\n"; //send CONTENT send(SockFD, CONTENT, strlen(CONTENT), 0); send(SockFD, ".\r\n", strlen(".\r\n"), 0); //end send(SockFD, "QUIT\r\n", strlen("QUIT\r\n"), 0); //quit mail 发送的功能基本上就完成了, 当然, 如果是应用的话还是需要很多改动的地方的, 比如说添加附件等. 3). 附件功能 要使用SMTP 发送附件, 需要对SMTP 头信息进行说明, 改变Content-type 及为每一段正文添加BOUNDARY 名, 示例如下: "DATA\r\n" "FROM: sender@126.com\r\n" "TO: recv@gmail.com\r\n" "SUBJECT: this is a test\r\n" "Date: Fri, 8 Jan 2010 16:12:30\r\n" "X-Mailer: shadowstar's mailer\r\n" "MIME-Version: 1.0\r\n" "Content-type: multipart/mixed; boundary=\"#BOUNDARY#\"\r\n\r\n"; // 正文 "--#BOUNDARY#\r\n" "Content-Type: text/plain; charset=gb2312\r\n" "Content-Transfer-Encoding: quoted-printable\r\n" 邮件正文………. // 附件 "\r\n--#BOUNDARY#\r\n" "Content-Type: application/octet-stream; name=att.txt\r\n" "Content-Disposition: attachment; filename=att.txt\r\n" "Content-Transfer-Encoding: base64\r\n" "\r\n" 附件正信息(base64 编码)….. Base64 编码函数在网络上很容易找到, 这里就不给出源码了, 如需要支持HTML 格式而又不知道如何写这些头信息, 可以用outlook 或foxmail 写一封支持HTML 格式的mail, 查看其原文信息, 依照相同的格式发送就行了. 4). 实现抄送及密送 在SMTP 命令集中并没有RCPT CC 或RCPT BCC 相关命令, 那要如何来实现抄送和密送功能呢? 在网络上找到这样一句话: “ 所有的接收者协商都通过RCPT TO 命令来实现,如果是BCC ,则协商发送后在对方接收时被删掉信封接收者”, 开始一直不明白这句话是什么意思? 后来通看查看foxmail 的邮件原文发现: Date: Wed, 6 Jan 2010 12:11:48 +0800 From: "carven_li" < carven_li @smtp.com> To: "carven" <carven@smtp.com> Cc: "sam" <sam@smtp.com>, "yoyo" <yoyo@smtp.com> BCC: "clara" <clara@tsmtp.com> Subject: t X-mailer: Foxmail 5.0 [cn] Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=====001_Dragon237244850520_=====" 才恍然大悟, 所谓的” 协商” 应该就是指发送方在Data 中指定哪些为CC, 哪些为BCC, 默认情况下什么都不写, 只发送第一个RCPT TO 的mail, 其他的都被过滤掉. 3. SMTP身份认证 SMTP身份认证方式有很多种, 每种认证方式验证发送的信息都有点细微的差别, 这里我主要介绍下LOGIN,PLAIN及NTLM三种简单的认证方式, 附带CRAM-MD5和DIGEST-MD5方式(验证没通过, 不知道问题出在哪了? 有待高人帮忙解决!). 要进行身份认证, 先要知道当前SMTP服务器支持哪些认证方式, 在ESMTP中有个与HELO命令相同功能的命令EHLO可以得到当前服务器支持的认证方式(有些服务器无返回信息, 可能服务器端作了限制). 1) LOGIN认证方式 LOGIN认证方式是基于明文传输的, 因此没什么安全性可言, 如信息被截获, 那么用户名和密码也就泄露了. 认证过程如下: AUTH LOGIN 334 VXNlcm5hbWU6 //服务器返回信息, Base64编码的Username: bXlOYW1l //输入用户名, 也需Base64编码 334 UGFzc3dvcmQ6 //服务器返回信息, Base64编码的Password:: bXlQYXNzd29yZA== //输入密码, 也需Base64编码 235 2.0.0 OK Authenticated // 535 5.7.0 authentication failed 2). NTLM认证方式 NTLM认证方式过程与LOGIN认证方式是一模一样的, 只需将AUTH LOGN改成AUTH NTLM.就行了. 3). PLAIN认证方式 PLAIN认证方式消息过过程与LOGIN和NTLM有所不同, 其格式为: “NULL+UserName+NULL+Password”, 其中NULL为C语言中的’\0’, 不方便使用命令行测试, 因此下面给出C++代码来实现: char szSend[] = "$user$pwd"; size_t n = stlen(szSend); for(int i=0; i<n; i++) if(szSend[i] == '$') szSend[i] = '\0'; char szMsg[512] base64_encode(szSend, n, szMsg); send (skt, szMsg, strlen(szMsg), 0); 4). CRAM-MD5认证方式 前面所介绍的三种方式, 都是将用户名和密码经过BASE64编码后直接发送到服务器端的, BASE64编码并不是一种安全的加密算法, 其所有信息都可能通过反编码, 没有什么安全性可言. 而CRAM-MD5方式与前三种不同, 它是基于Challenge/Response的方式, 其中Challenge是由服务器产生的, 每次连接产生的Challenge都不同, 而Response是由用户名,密码,Challenge组合而成的, 具体格式如下: response=base64_encode(username : H_MAC(challenge, password)) H_MAC是Keyed MD5算法(见http://www.faqs.org/rfcs/rfc2195.html), 先由challenge和password生成16位的散列码, 将其转换成16进制32个字节的字符串数组digest(即以%02x输出), 再对(username+空格+digest[32])进行base64编码,就是要发送的response了. 另外, 在http://www.net-track.ch/opensource/cmd5/提供了SMTP CRAM-MD5认证源码, 可用于测试CRAM-MD5认证, 但不知道是不是我这边测试的SendMail服务器配置有问题, 测试时一直不能通过. 5). DIGEST-MD5认证方式 DIGEST-MD5认证也是Challenge/Response的方式, 与CRAM-MD5相比, 它的Challenge信息更多, 其Response计算方式也非常复杂, 我在测试时也是以认证失败而告终, 只是将在网上找到的资料整理于此, 能为后来研究的人多提供点资料, 或者有兴趣的朋友们可以和我一起讨论下. 我们先看下DIGEST-MD5认证发送响应信息: DIGEST-MD5服务器格式说明(见RFC 2831 Digest SASL Mechanism Mai 2000): digest-challenge = 1 # (Reich | Nonce | qop-Optionen | schal | MAXBUF | charset Algorithmus | Chiffre-opts | auth-param) realm = "Reich" "=" < "> Reich-Wert <"> Reich-Wert = qdstr-val Nonce = "Nonce" "=" < "> Nonce-Wert <"> Nonce-Wert = qdstr-val qop-options = "qop" "=" < "> qop-Liste <"> qop-list = 1 # qop-Wert qop-Wert = "auth" | "auth-int" | "auth-conf" | Token stale = "veraltete" "=" "true" MAXBUF = "MAXBUF" "=" MAXBUF-Wert MAXBUF-Wert = 1 * DIGIT charset = "charset" = "" UTF-8 " algorithm = "Algorithmus" "=" "md5-sess" Chiffre-opts = "Chiffre" "=" < "> 1 # Null-Wert <"> Chiffre-value = "3des" | "des" | "RC4-40" | "RC4" | "RC4-56" | Token auth-param = Token "=" (token | quoted-string) DIGEST-MD5客户端响应格式说明(见RFC 2831 Digest SASL Mechanism Mai 2000): digest-response = 1 # (Benutzername | Reich | Nonce | cnonce | Nonce-count | qop | digest-uri | Antwort | MAXBUF | charset | Chiffre | authzid | auth-param) username = "username" = "<"> username-Wert < "> Benutzernamen-Wert = qdstr-val cnonce = "cnonce" "=" < "> cnonce-Wert <"> cnonce-Wert = qdstr-val Nonce-count = "nc" "=" nc-Wert nc-Wert = 8LHEX qop = "qop" "=" qop-Wert digest-uri = "digest-uri" = "<"> digest-uri-value < "> digest-uri-value = serv-type "/" host [ "/" serv-name] //eg: smtp/mail3.example.com/example.com serv-type = 1 * ALPHA //www for web-service, ftp for ftp-dienst, SMTP for mail-versand-service … host = 1 * (ALPHA | DIGIT | "-" | ".") serv-name = host response = "Antwort" "=" Response-Wert response-value = 32LHEX LHEX = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "a" | "b" | "c" | "d" | "e" | "f" cipher = "Chiffre" "=" Null-Wert authzid = "authzid" "=" < "> authzid-Wert <"> authzid-Wert = qdstr-val 其各字段具体含义见相关文档, 这里只介始几个需要用到的字段是如何产生的, C/S响应示例如下: S: realm="elwood.innosoft.com",nonce="OA6MG9tEQGm2hh",qop="auth", algorithm=md5-sess,charset=utf-8 C: charset=utf-8,username="chris",realm="elwood.innosoft.com", nonce="OA6MG9tEQGm2hh",nc=00000001,cnonce="OA6MHXh6VqTrRk", digest-uri="imap/elwood.innosoft.com", response=d388dad90d4bbd760a152321f2143af7,qop=auth S: rspauth=ea40f60335c427b5527b84dbabcdfffd The password in this example was "secret". 从这个示例可以看出, 客户端返回的信息比服务器端发送过来的多了以下几个: username, nc, cnonce, digest-uri和respone username就不用说了, nc是8位长的16进制数字符串,统计客户端使用nonce发出请求的次数(包含当前请求),例示我们可以设为”00000001”, cnonce是是用了4个随机数组成一个8位长16进制的字符串,digest-uri是由在realm前加上请求类型(如http, smtp等), response是一个32位长的16进制数组, 计算公式如下: If the "qop" value is "auth" or "auth-int": request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" nc-value ":" unq(cnonce-value) ":" unq(qop-value) ":" H(A2) ) <"> If the "qop" directive is not present (this construction is for compatibility with RFC 2069): request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) ) > <"> See below for the definitions for A1 and A2. Read more: http://www.faqs.org/rfcs/rfc2617.html#ixzz0c4s8ck3F KD(secret,data)表示分类算法,其中data指数据,secret表示采用的方法.如果表示校验和算法时,data要写成H(data);而unq(X)表示将带引号字符串的引号去掉。 对于"MD5" 和"MD5-sess" 算法: H(data) = MD5(data) 和 KD(secret, data) = H(concat(secret, ":", data)) 如果"algorithm"指定为"MD5"或没有指定,A1计算方式如下: A1 = unq(username-value) ":" unq(realm-value) ":" passwd //Password为用户密码 如果"algorithm"指定为"MD5-sess", 则需要nonce和cnonce的参与: A1 = H(unq(username-value) ":" unq(realm-value) ":" passwd ) ":" unq(nonce-value) ":" unq(cnonce-value) 如果"qop"没有指定或指定为"auth", A2计算方式如下: A2 = Method ":" digest-uri-value 如果"qop"没有指定或指定为"auth int", A2计算方式如下: A2 = Method ":" digest-uri-value ":" H(entity-body) Method是http请求时的方法(post,get), 由于英文水平比较差, 很多都看不明白, 有兴趣的朋友可以自己去看看原文(http://www.faqs.org/rfcs/rfc2617.html), 这里还提供了DIGEST验证的代码: File "digcalc.h": #define HASHLEN 16 typedef char HASH[HASHLEN]; #define HASHHEXLEN 32 typedef char HASHHEX[HASHHEXLEN+1]; #define IN #define OUT /* calculate H(A1) as per HTTP Digest spec */ void DigestCalcHA1( IN char * pszAlg, IN char * pszUserName, IN char * pszRealm, IN char * pszPassword, IN char * pszNonce, IN char * pszCNonce, OUT HASHHEX SessionKey ); /* calculate request-digest/response-digest as per HTTP Digest spec */ void DigestCalcResponse( IN HASHHEX HA1, /* H(A1) */ IN char * pszNonce, /* nonce from server */ IN char * pszNonceCount, /* 8 hex digits */ IN char * pszCNonce, /* client nonce */ IN char * pszQop, /* qop-value: "", "auth", "auth-int" */ IN char * pszMethod, /* method from the request */ IN char * pszDigestUri, /* requested URL */ IN HASHHEX HEntity, /* H(entity body) if qop="auth-int" */ OUT HASHHEX Response /* request-digest or response-digest */ ); File "digcalc.c": #include <global.h> #include <md5.h> #include <string.h> #include "digcalc.h" void CvtHex( IN HASH Bin, OUT HASHHEX Hex ) { unsigned short i; unsigned char j; for (i = 0; i < HASHLEN; i++) { j = (Bin[i] >> 4) & 0xf; if (j <= 9) Hex[i*2] = (j + '0'); else Hex[i*2] = (j + 'a' - 10); j = Bin[i] & 0xf; if (j <= 9) Hex[i*2+1] = (j + '0'); else Hex[i*2+1] = (j + 'a' - 10); }; Hex[HASHHEXLEN] = '\0'; }; /* calculate H(A1) as per spec */ void DigestCalcHA1( IN char * pszAlg, IN char * pszUserName, IN char * pszRealm, IN char * pszPassword, IN char * pszNonce, IN char * pszCNonce, OUT HASHHEX SessionKey ) { MD5_CTX Md5Ctx; HASH HA1; MD5Init(&Md5Ctx); MD5Update(&Md5Ctx, pszUserName, strlen(pszUserName)); MD5Update(&Md5Ctx, ":", 1); MD5Update(&Md5Ctx, pszRealm, strlen(pszRealm)); MD5Update(&Md5Ctx, ":", 1); MD5Update(&Md5Ctx, pszPassword, strlen(pszPassword)); MD5Final(HA1, &Md5Ctx); if (stricmp(pszAlg, "md5-sess") == 0) { MD5Init(&Md5Ctx); MD5Update(&Md5Ctx, HA1, HASHLEN); MD5Update(&Md5Ctx, ":", 1); MD5Update(&Md5Ctx, pszNonce, strlen(pszNonce)); MD5Update(&Md5Ctx, ":", 1); MD5Update(&Md5Ctx, pszCNonce, strlen(pszCNonce)); MD5Final(HA1, &Md5Ctx); }; CvtHex(HA1, SessionKey); }; /* calculate request-digest/response-digest as per HTTP Digest spec */ void DigestCalcResponse( IN HASHHEX HA1, /* H(A1) */ IN char * pszNonce, /* nonce from server */ IN char * pszNonceCount, /* 8 hex digits */ IN char * pszCNonce, /* client nonce */ IN char * pszQop, /* qop-value: "", "auth", "auth-int" */ IN char * pszMethod, /* method from the request */ IN char * pszDigestUri, /* requested URL */ IN HASHHEX HEntity, /* H(entity body) if qop="auth-int" */ OUT HASHHEX Response /* request-digest or response-digest */ ) { MD5_CTX Md5Ctx; HASH HA2; HASH RespHash; HASHHEX HA2Hex; // calculate H(A2) MD5Init(&Md5Ctx); MD5Update(&Md5Ctx, pszMethod, strlen(pszMethod)); MD5Update(&Md5Ctx, ":", 1); MD5Update(&Md5Ctx, pszDigestUri, strlen(pszDigestUri)); if (stricmp(pszQop, "auth-int") == 0) { MD5Update(&Md5Ctx, ":", 1); MD5Update(&Md5Ctx, HEntity, HASHHEXLEN); }; MD5Final(HA2, &Md5Ctx); CvtHex(HA2, HA2Hex); // calculate response MD5Init(&Md5Ctx); MD5Update(&Md5Ctx, HA1, HASHHEXLEN); MD5Update(&Md5Ctx, ":", 1); MD5Update(&Md5Ctx, pszNonce, strlen(pszNonce)); MD5Update(&Md5Ctx, ":", 1); if (*pszQop) { MD5Update(&Md5Ctx, pszNonceCount, strlen(pszNonceCount)); MD5Update(&Md5Ctx, ":", 1); MD5Update(&Md5Ctx, pszCNonce, strlen(pszCNonce)); MD5Update(&Md5Ctx, ":", 1); MD5Update(&Md5Ctx, pszQop, strlen(pszQop)); MD5Update(&Md5Ctx, ":", 1); }; MD5Update(&Md5Ctx, HA2Hex, HASHHEXLEN); MD5Final(RespHash, &Md5Ctx); CvtHex(RespHash, Response); }; File "digtest.c": #include <stdio.h> #include "digcalc.h" void main(int argc, char ** argv) { char * pszNonce = "dcd98b7102dd2f0e8b11d0f600bfb0c093"; char * pszCNonce = "0a4f113b"; char * pszUser = "Mufasa"; char * pszRealm = "testrealm@host.com"; char * pszPass = "Circle Of Life"; char * pszAlg = "md5"; char szNonceCount[9] = "00000001"; char * pszMethod = "GET"; char * pszQop = "auth"; char * pszURI = "/dir/index.html"; HASHHEX HA1; HASHHEX HA2 = ""; HASHHEX Response; DigestCalcHA1(pszAlg, pszUser, pszRealm, pszPass, pszNonce, pszCNonce, HA1); DigestCalcResponse(HA1, pszNonce, szNonceCount, pszCNonce, pszQop, pszMethod, pszURI, HA2, Response); printf("Response = %s\n", Response); }; 到这里,关于使用SMTP发送mail就结束了, 由于水平有限, 有很多地方可能讲不够透彻!!! 上面这个牛人这么牛逼但是Sendmail提示身份验证失败原因是犯下saslauthd服务没有开启的错误,也转载一下,呵呵: 问题描述: SendMail安装成功并已启动,利用foxmail可以收发Mail, 只是当选中“SMTP服务器需要身份验证”是,发送mail总是验证失败, 使用 telnet登陆smtp服务器,输入ehlo ip返回信息如下: 250-ENHANCEDSTATUSCODES 250-PIPELINING 250-8BITMIME 250-SIZE 250-DSN 250-ETRN 250-AUTH DIGEST-MD5 CRAM-MD5 LOGIN PLAIN 250-DELIVERBY 250 HELP 从返回信息上可以SMTP服务器是可以通过DIGEST-MD5,CRAM-MD5,LOGIN PLAIN这几种方式验证的,在foxmail的帮助文档中也可以找到它 是支持这些验证方式的,但是我输入: auth login\r\n 334 VXNlcm5hbWU6 //响应正确的 Y2FydmVu //编码过的用户名 334 UGFzc3dvcmQ6 //返回也没问题 Y2FydmVuMTIz //编码过的蜜码 535 5.7.0 authentication failed //这里就出问题了 用这密码登陆自己的SMTP服务器是没问题的啊,終于在网上看到一篇讲sendmail认证的文章 (http://www.wangchao.net.cn/bbsdetail_1417288.html),对sendmail作了如下改动: 修改/etc/mail/sendmail.mc文件: 第42行和43行,把最前面的dnl删除,变成: TRUST_AUTH_MECH(`EXTERNAL DIGEST-MD5 CRAM-MD5 LOGIN PLAIN')dnl define(`confAUTH_MECHANISMS', `EXTERNAL GSSAPI DIGEST-MD5 CRAM-MD5 LOGIN PLAIN')dnl 第84 行DAEMON_OPTIONS(`Port=smtp,Addr=127.0.0.1, Name=MTA')dnl把里面的127.0.0.1改成0.0.0.0, 把mc文件编译成sendmail的配置,运行m4 sendmail.mc > sendmail.cf; /etc/init.d/sendmail restart(重新启动sendmail) 然后用telnet试了下,发现还是同样的问题. 难道还有什么没配置好? 再仔细看看这篇文,上面还说到了需要smtpd.conf和启动/etc/init.d/saslauthd, 第一次看也没注意到这两个问题,反正还没弄好,也 就试试吧,用ls /usr/lib/sasl2发现Sendmail.conf和smtpd.conf都存在,且内容也是 pwcheck_method:saslauthd. 不会是saslauthd服务没有启动吧? chkconfig --list | grep "saslauthd" saslauthd服务还真没有启动,由于sendmail不是我配置的,本人对于sendmail也不熟,也不知道需要些什么服务,只有照着网上说的做 了。 telnet测试如下: auth login 334 VXNlcm5hbWU6 Y2FydmVu 334 UGFzc3dvcmQ6 Y2FydmVuMTIz 235 2.0.0 OK Authenticated 用foxmail发送也不再有问题了, saslauthd服务没有开启,害得我弄了老半天!!!