【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 ***** 必须支持啊,又学了一点知识 真是屌得不行! 经测试,只支持QQ邮箱,对126邮箱不支持,其他未知。 美俪女神 发表于 2016-1-2 20:30
经测试,只支持QQ邮箱,对126邮箱不支持,其他未知。
试试gmail邮箱吧。。 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连接
想看看代码 真是来得太及时了.. 以后终于可以自动交作业了:lol 呜 正好遇到这个问题了 回复 Down完整代码包~ 后生可畏啊~! 必须支持啊,又学了一点知识 支持 !! 必须支持一下 刚好需要这功能..下载来研究看看 美俪女神 发表于 2016-1-2 20:30
经测试,只支持QQ邮箱,对126邮箱不支持,其他未知。
126需要手动配置邮箱支持的协议 感谢楼主分享哈 真的是太神拉~~ 特别需要,万分感谢 。。急用。。感谢楼主