zoukankan      html  css  js  c++  java
  • 利用SMTP发送Mail详解

     利用SMTP发送Mail详解

    分类: VC++程式开发 技术杂谈 2010-01-09 11:02 4135人阅读 评论(0) 收藏 举报

    服务器headerdatesocket网络2010

    在以前接触的项目中,一直都是在做网站时用到了发送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 命令进行查看:

    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就结束了, 由于水平有限, 有很多地方可能讲不够透彻!!!

     

     

  • 相关阅读:
    OK335x mksd.sh hacking
    Qt jsoncpp 对象拷贝、删除、函数调用 demo
    OK335xS 256M 512M nand flash make ubifs hacking
    Qt QScrollArea and layout in code
    JsonCpp Documentation
    Qt 4.8.5 jsoncpp lib
    Oracle数据库生成UUID
    freemarker得到数组的长度
    FreeMarker中if标签内的判断条件
    freemarker语法
  • 原文地址:https://www.cnblogs.com/ylsoo/p/4300074.html
Copyright © 2011-2022 走看看