SMTP郵件傳輸協議發送郵件和附件


在以前接觸的項目中,一直都是在做網站時用到了發送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服務沒有開啟,害得我弄了老半天!!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM