0xAA55 发表于 2016-1-2 11:20:35

【C】使用SMTP协议发送电子邮件源码,支持附件,支持OpenSSL加密,支持STARTTLS

呼。。终于完成了。

有关的内容大家可以参考我用Java写的轻量级电子邮件类smtpmail
其实这个C语言版的也差不多。区别在于,这个不需要自己保存数字签名文件,它反正就这么就能用。

效果图:


下面是通过SMTP和服务器交互的内容。想看?回帖后才能看到。**** Hidden Message *****为了写这个东西,我需要解决以下三个问题:
[*]Base64编码。参考资料:http://www.0xaa55.com/thread-1212-1-1.html
[*]UTF-8编码。参考资料:http://www.0xaa55.com/thread-1676-1-1.html
[*]OpenSSL。这个的编译搞得我很头疼:编译它需要安装ActivePerl。然后将其编译为x64的动态库DLL的时候,它总是出问题,NASM有个文件编译不通过。而如果设置了no-asm编译选项的话,它最后链接又通不过——缺少符号。最后我把它编译成静态库,勉强能用。


写好它以后,大家可以看看我的DEMO,使用我定义的接口进行SMTP协议邮件发送过程。这用起来还是非常方便的。#include<stdio.h>
#include<WinSock2.h>
#include"smtpmail.h"

#define DEMO_SERVER                "smtp.qq.com",465
#define DEMO_ACCOUNT        "username","password"
#define DEMO_FROMTO                "username@qq.com","receiver@qq.com"

//=============================================================================
//函数:SendTestMail
//描述:测试函数,展示发送邮件
//-----------------------------------------------------------------------------
sm_exception_t SendTestMail()
{
        return sm_SendTextMail(sm_SSL,DEMO_SERVER,DEMO_ACCOUNT,DEMO_FROMTO,
                "这是测试邮件,来自smtpmail",
                "这是测试邮件的内容。");
}

//=============================================================================
//函数:SendTestMailWithAttachment
//描述:测试函数,展示发送带附件的邮件
//-----------------------------------------------------------------------------
sm_exception_t SendTestMailWithAttachment()
{
        smtpmail_t sm;
        sm_exception_t ex;
        FILE*fp = NULL;

        sm_Init(&sm, sm_SSL);

        ex = sm_CatchException(&sm);
        if(ex)
        {
                sm_Cleanup(&sm);
                if(fp)
                        fclose(fp);
                return ex;
        }

        sm_StartSendMail(&sm,DEMO_SERVER,DEMO_ACCOUNT,DEMO_FROMTO,
                "这是测试邮件,来自smtpmail,测试附件",
                sm_false);

        fp = fopen("attachment.gif","rb");
        sm_SendAttachment(&sm, "image/gif", fp, "name", "filename.gif");
        fclose(fp);fp=NULL;

        sm_StartSendPlainText(&sm);
        sm_SendPlainText(&sm, "这个邮件有一个GIF附件。");
        sm_EndSendPlainText(&sm);

        sm_FinishSendMail(&sm);
        sm_Cleanup(&sm);

        return sme_OK;
}

int main(int argc,char**argv)
{
#        ifdef _MSC_VER
        {
                WSADATA wsaData;
                WSAStartup(WINSOCK_VERSION,&wsaData);
        }
#        endif // _MSC_VER
       
        fputs(sm_GetExceptionString(SendTestMail()),stderr);
        fputs(sm_GetExceptionString(SendTestMailWithAttachment()),stderr);
        return 0;
}


参考资料:
http://www.0xaa55.com/thread-1212-1-1.html
http://www.0xaa55.com/thread-1671-1-1.html
http://www.0xaa55.com/thread-1676-1-1.html
RFC 2821 (SMTP)
RFC 3207 (STARTTLS)
RFC 6409 (Port 587)
https://en.wikipedia.org/wiki/SMTPS

完整的代码:
smtpmail.h://=============================================================================
//作者:0xAA55
//作者网站:http://www.0xaa55.com/
//本源码完全免费开放,大家可以随意拿去使用、修改、用于任何用途。
//但是,由于本源码带来的任何损失,均与本作者无关。请保留此信息。
//-----------------------------------------------------------------------------
#ifndef _SMTP_MAIL_SENDER_
#define _SMTP_MAIL_SENDER_

#include<stdint.h>
#include<setjmp.h>
#include<Winsock2.h>
#include<openssl/ssl.h>

typedef enum
{
        sm_false = 0,
        sm_true
}sm_bool_t,*sm_bool_p;

//=============================================================================
//smtpmail的异常号码。用于追踪错误。
typedef enum
{
        sme_OK = 0,                                //没错
        sme_InvalidParam,                //参数错误
        sme_NoEnoughMemory,                //内存不足
        sme_ServerNotFound,                //没找到服务器
        sme_InitSSLFailed,                //初始化OpenSSL失败
        sme_SSLHandShakeErr,        //OpenSSL握手失败
        sme_ConnectFail,                //连接失败
        sme_SendFail,                        //发送失败
        sme_RecvFail,                        //接收失败
        sme_IncorrectData,                //接收到了奇怪的数据
        sme_BadStatusCode,                //服务器返回不好的状态码,不能再合作下去了
        sme_STARTTLS_Failed,        //服务器不支持STARTTLS,但是用户要求必须支持
        sme_LoginFailed,                //登录失败,错误的用户名和密码
        sme_SetFromToError,                //设置收件人和发件人失败
        sme_SendMailFail,                //最后服务器返回“发送邮件失败”
        sme_ReadFileError,                //无法读取文件(无法发送附件)
}sm_exception_t,*sm_exception_p;

//=============================================================================
//加密方式
typedef enum
{
        sm_Plain = 0,                        //发送明文,不加密
        sm_STARTTLS_Or_Plain,        //明文连接,如果服务器支持STARTTLS就加密,否则发送明文
        sm_STARTTLS,                        //明文连接,服务器必须支持STARTTLS否则失败
        sm_SSL                                        //SSL加密
}sm_sectype_t,*sm_sectype_p;

typedef struct smtpmail_s smtpmail_t,*smtpmail_p;
typedef void(*Send_f)(smtpmail_p,const void*pBuf,size_t);
typedef size_t(*Recv_f)(smtpmail_p,void*pBuf,size_t);

#define MaxLine 255 /* 假定从服务器返回的字符串每行最大字符数 */

struct smtpmail_s
{
        //传输工具
        SOCKET Socket;
        Send_f pfnSend;
        Recv_f pfnRecv;

        //安全模式
        sm_sectype_t SecureMode;

        SSL_CTX        *pCTX;
        SSL                *pSSL;

        //接收数据缓冲区
        char        RecvBuf;//添加一个行结尾
        size_t        cbRecv;

        char        szSeparator;//不同MIME块之间的分隔行
        size_t        cbSeparator;//分隔行的长度

        //最后状态
        uint32_t uLastStatus;
        char       pLastRecvStr;

        //错误处理,这个必须设置
        jmp_buf        OnError;
};

//=============================================================================
//函数:sm_Init
//描述:初始化邮件发送器
//-----------------------------------------------------------------------------
void sm_Init
(
        smtpmail_p ps,
        sm_sectype_t SecureMode//安全模式
);

//=============================================================================
//函数:sm_CatchException
//描述:捕获异常处理。必须在调用sm_Init后直接使用。
//    一旦出现异常,程序将从这里返回。切记返回完后调用sm_Cleanup进行清理。
//-----------------------------------------------------------------------------
#define sm_CatchException(ps) (sm_exception_t)setjmp((ps)->OnError)

//=============================================================================
//函数:sm_StartSendMail
//描述:开始发送正文。调用前必须先调用sm_CatchException。
//-----------------------------------------------------------------------------
void sm_StartSendMail
(
        smtpmail_p        ps,
        const char        *pszServerAddr,        //服务器地址,如“smtp.qq.com”
        uint16_t        uPort,                        //服务器端口,如“465”
        const char        *pUsername,                //用户名,如
        const char        *pPassword,                //密码,如
        const char        *pMailFrom,                //发件人,如QQ号@qq.com
        const char        *pRcptTo,                //收件人,如xxx@xx.com
        const char        *pSubject,                //主题,如“测试邮件”
        sm_bool_t        bHaveAttachment        //是否打算发送附件
);

//=============================================================================
//函数:sm_StartSendMimeData
//      sm_StartSendMimeDataAsAttachment
//      sm_SendMimeData
//      sm_SendTextMimeData
//      sm_EndSendMimeData
//描述:发送MIME内容。
//有关MIME类型的资料:
//http://www.sitepoint.com/web-foundations/mime-types-complete-list/
//-----------------------------------------------------------------------------
void sm_StartSendMimeData(smtpmail_p ps,const char*pszMimeType);
void sm_StartSendMimeDataAsAttachment
(
        smtpmail_p ps,
        const char*pszMimeType,
        const char*pszName,                //附件名
        const char*pszFilename        //附件文件名
);
void sm_SendMimeData(smtpmail_p ps,const void*pszData,size_t cbData);
void sm_SendTextMimeData(smtpmail_p ps,const char*pszText);
void sm_EndSendMimeData(smtpmail_p ps);

#define sm_StartSendPlainText(ps) sm_StartSendMimeData(ps, "text/plain")
#define sm_SendPlainText(ps, pszText) sm_SendTextMimeData(ps, pszText)
#define sm_EndSendPlainText(ps) sm_EndSendMimeData(ps)

#define sm_StartSendHTML(ps) sm_StartSendMimeData(ps, "text/html")
#define sm_SendHTML(ps,pszText) sm_SendMimeData(ps, pszText)
#define sm_EndSendHTML(ps) sm_EndSendMimeData(ps)

//=============================================================================
//函数:sm_SendAttachment
//描述:发送附件
//-----------------------------------------------------------------------------
void sm_SendAttachment
(
        smtpmail_p ps,
        const char*pszMimeType,        //MIME类型
        FILE*fp,                                //已经打开的文件
        const char*pName,                //附件名
        const char*pFilename        //文件名,不含路径
);

//=============================================================================
//函数:sm_FinishSendMail
//描述:完成邮件的发送(最后一步)
//-----------------------------------------------------------------------------
void sm_FinishSendMail(smtpmail_p ps);

//=============================================================================
//函数:sm_Cleanup
//描述:用完smtpmail后,清理内存。
//-----------------------------------------------------------------------------
void sm_Cleanup(smtpmail_p ps);

//=============================================================================
//函数:sm_SendTextMail
//描述:一步完成——发送纯文本邮件
//-----------------------------------------------------------------------------
sm_exception_t sm_SendTextMail
(
        sm_sectype_t SecureMode,        //安全模式
        const char        *pszServerAddr,        //服务器地址,如“smtp.qq.com”
        uint16_t        uPort,                        //服务器端口,如“465”
        const char        *pUsername,                //用户名,如
        const char        *pPassword,                //密码,如
        const char        *pMailFrom,                //发件人,如QQ号@qq.com
        const char        *pRcptTo,                //收件人,如xxx@xx.com
        const char        *pSubject,                //主题,如“测试邮件”
        const char        *pText                        //纯文本内容
);

//=============================================================================
//函数:sm_GetExceptionString
//描述:取得用于描述异常的字符串
//-----------------------------------------------------------------------------
char*sm_GetExceptionString(sm_exception_t e);

#endif // !_SMTP_MAIL_SENDER_smtpmail.c:回帖后可见。**** Hidden Message *****SRC:
不过由于论坛上传附件大小限制的关系,这个附件只包含了核心的代码。而OpenSSL则并没有包含在里面,所以不能编译通过。
完整的源码包请回帖后查看并下载。**** Hidden Message *****

郭先生 发表于 2016-1-2 11:28:25

必须支持啊,又学了一点知识

Golden Blonde 发表于 2016-1-2 11:28:28

真是屌得不行!

Golden Blonde 发表于 2016-1-2 20:30:53

经测试,只支持QQ邮箱,对126邮箱不支持,其他未知。

0xAA55 发表于 2016-1-2 22:02:16

美俪女神 发表于 2016-1-2 20:30
经测试,只支持QQ邮箱,对126邮箱不支持,其他未知。

试试gmail邮箱吧。。

0xAA55 发表于 2016-1-15 22:13:13

587端口+STARTTLS邮件机制:(正常情况)
1、用户连接到smtp.qq.com:587
2、服务器返回:“220 欢迎!qq邮箱”
3、用户发送:“ehlo smtp.qq.com”
4、服务器返回:(“250-”开头的功能列表,验证什么的)
5、用户发送:“STARTTLS”
6、服务器返回:“220 OK”
7、用户初始化OpenSSL,然后SSL_connect
8、用户通过SSL发送:“ehlo smtp.qq.com”
9、服务器通过SSL返回:(“250-”开头的功能列表,验证什么的,但是没有“STARTTLS”)
10、用户通过SSL发送:“AUTH LOGIN”
11、服务器通过SSL返回:“334 VXNlcm5hbWU6”
12、用户通过SSL发送:(Base64编码的用户名,QQ邮箱的话,就是QQ号了)
13、服务器通过SSL返回:“334 UGFzc3dvcmQ6”
14、用户通过SSL发送:(Base64编码的用户名,QQ邮箱的话,就是QQ密码了)
15、服务器通过SSL返回:“235 验证成功。”
16、用户通过SSL发送:“MAIL FROM:<发件人@qq.com>”(发件人不能乱填,没有经过允许的话不认的)
17、服务器通过SSL返回:“250 设置发件人成功。”
18、用户通过SSL发送:“RCPT TO:<收件人>”(收件人不能完全等于发件人)
19、服务器通过SSL返回:“250 设置收件人成功。”
20、用户通过SSL发送:“DATA”
21、服务器通过SSL返回:“354用<cr><lf>.<cr><lf>结束
22、用户通过SSL发送:(邮件内容,用“\r\n.\r\n”来表示结束信号)
23、服务器通过SSL返回:“250 已加入队列。”
24、用户通过SSL发送:“QUIT”
25、服务器通过SSL返回:“221 Bye”
26、服务器断开SSL连接
27、服务器断开socket连接

搬砖工 发表于 2016-1-30 22:17:08

想看看代码

0x01810 发表于 2016-1-31 10:34:18

真是来得太及时了.. 以后终于可以自动交作业了:lol

诹访子 发表于 2016-5-5 13:36:27

呜 正好遇到这个问题了

雪樱汐 发表于 2016-5-14 22:41:30

回复 Down完整代码包~

誓不回头 发表于 2016-6-4 02:48:50

后生可畏啊~!

zh_flor 发表于 2016-11-14 16:05:06

必须支持啊,又学了一点知识

jasonchen 发表于 2016-11-17 10:25:11

支持    !!

唐凌 发表于 2017-1-13 22:27:14

必须支持一下

kongqi1 发表于 2017-2-6 03:49:15

刚好需要这功能..下载来研究看看

def 发表于 2017-2-15 14:23:20

美俪女神 发表于 2016-1-2 20:30
经测试,只支持QQ邮箱,对126邮箱不支持,其他未知。

126需要手动配置邮箱支持的协议

tx7790 发表于 2017-2-27 12:37:10

感谢楼主分享哈

happy09033 发表于 2017-3-7 21:02:22

真的是太神拉~~

mageart 发表于 2017-5-6 10:53:25

特别需要,万分感谢

古河面包店 发表于 2017-5-14 00:11:29

。。急用。。感谢楼主
页: [1] 2 3 4 5
查看完整版本: 【C】使用SMTP协议发送电子邮件源码,支持附件,支持OpenSSL加密,支持STARTTLS