美文网首页
SMTP邮件传输协议发送邮件和附件

SMTP邮件传输协议发送邮件和附件

作者: 贰爷 | 来源:发表于2020-09-27 17:21 被阅读0次

    在以前接触的项目中,一直都是在做网站时用到了发送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>;

    1. 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.

    1. 用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, 其他的都被过滤掉.

    1. 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[] = "userpwd";
    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服务没有开启,害得我弄了老半天!!!

    相关文章

      网友评论

          本文标题:SMTP邮件传输协议发送邮件和附件

          本文链接:https://www.haomeiwen.com/subject/sharrktx.html