在以前接觸的項目中,一直都是在做網站時用到了發送mail 的功能,在asp 和.net 中都有相關的發送mail 的類, 實現起來非常簡單。最近這段時間因工作需要在C++ 中使用發送mail 的功能,上網搜了一大堆資料,終於得以實現,總結自己開發過程中碰到的一些問題,希望對需的人有所幫助, 由於能力有限, 文中不免有些誤解之處, 望大家能指正!!
其實,使用C++ 發送mail 也是很簡的事, 只需要了解一點SMTP 協議和socket 編程就OK 了, 網絡上也有很多高人寫好的mail 類源碼,有興趣的朋友可以下載看看.
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服務沒有開啟,害得我弄了老半天!!!