技术宅的结界

 找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 4233|回复: 57
收起左侧

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

  [复制链接]

995

主题

2207

帖子

5万

积分

用户组: 管理员

一只技术宅

UID
1
精华
197
威望
261 点
宅币
16463 个
贡献
32443 次
宅之契约
0 份
在线时间
1565 小时
注册时间
2014-1-26
发表于 2016-1-2 11:20:35 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有帐号?立即注册→加入我们

x
呼。。终于完成了。

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

效果图:
20160102114945.png

下面是通过SMTP和服务器交互的内容。想看?回帖后才能看到。
游客,如果您要查看本帖隐藏内容请回复
为了写这个东西,我需要解决以下三个问题:

写好它以后,大家可以看看我的DEMO,使用我定义的接口进行SMTP协议邮件发送过程。这用起来还是非常方便的。
[C] 纯文本查看 复制代码
#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:
[C] 纯文本查看 复制代码
//=============================================================================
//作者: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[MaxLine + 1];//添加一个行结尾
	size_t	cbRecv;

	char	szSeparator[MaxLine + 1];//不同MIME块之间的分隔行
	size_t	cbSeparator;//分隔行的长度

	//最后状态
	uint32_t uLastStatus;
	char	 pLastRecvStr[MaxLine + 1];

	//错误处理,这个必须设置
	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,		//用户名,如[QQ号]
	const char	*pPassword,		//密码,如[QQ密码]
	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,		//用户名,如[QQ号]
	const char	*pPassword,		//密码,如[QQ密码]
	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:回帖后可见。
游客,如果您要查看本帖隐藏内容请回复
SRC: smtpmail.7z (9.22 KB, 下载次数: 50)

本帖被以下淘专辑推荐:

0

主题

3

帖子

29

积分

用户组: 初·技术宅

UID
1155
精华
0
威望
1 点
宅币
24 个
贡献
0 次
宅之契约
0 份
在线时间
2 小时
注册时间
2015-9-27
发表于 2016-1-2 11:28:25 | 显示全部楼层
必须支持啊,又学了一点知识

34

主题

133

帖子

6938

积分

用户组: 管理员

UID
77
精华
11
威望
112 点
宅币
6397 个
贡献
129 次
宅之契约
0 份
在线时间
90 小时
注册时间
2014-2-22
发表于 2016-1-2 11:28:28 | 显示全部楼层
真是屌得不行!

34

主题

133

帖子

6938

积分

用户组: 管理员

UID
77
精华
11
威望
112 点
宅币
6397 个
贡献
129 次
宅之契约
0 份
在线时间
90 小时
注册时间
2014-2-22
发表于 2016-1-2 20:30:53 | 显示全部楼层
经测试,只支持QQ邮箱,对126邮箱不支持,其他未知。

995

主题

2207

帖子

5万

积分

用户组: 管理员

一只技术宅

UID
1
精华
197
威望
261 点
宅币
16463 个
贡献
32443 次
宅之契约
0 份
在线时间
1565 小时
注册时间
2014-1-26
 楼主| 发表于 2016-1-2 22:02:16 | 显示全部楼层
美俪女神 发表于 2016-1-2 20:30
经测试,只支持QQ邮箱,对126邮箱不支持,其他未知。

试试gmail邮箱吧。。

995

主题

2207

帖子

5万

积分

用户组: 管理员

一只技术宅

UID
1
精华
197
威望
261 点
宅币
16463 个
贡献
32443 次
宅之契约
0 份
在线时间
1565 小时
注册时间
2014-1-26
 楼主| 发表于 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连接

0

主题

31

帖子

89

积分

用户组: 小·技术宅

UID
1457
精华
0
威望
2 点
宅币
54 个
贡献
0 次
宅之契约
0 份
在线时间
5 小时
注册时间
2016-1-29
发表于 2016-1-30 22:17:08 | 显示全部楼层
想看看代码

0

主题

76

帖子

6735

积分

用户组: 真·技术宅

UID
604
精华
0
威望
1 点
宅币
804 个
贡献
5853 次
宅之契约
0 份
在线时间
97 小时
注册时间
2014-12-20
发表于 2016-1-31 10:34:18 | 显示全部楼层
真是来得太及时了.. 以后终于可以自动交作业了

1

主题

6

帖子

44

积分

用户组: 初·技术宅

UID
1660
精华
0
威望
2 点
宅币
34 个
贡献
0 次
宅之契约
0 份
在线时间
6 小时
注册时间
2016-5-5
发表于 2016-5-5 13:36:27 | 显示全部楼层
呜 正好遇到这个问题了

0

主题

3

帖子

12

积分

用户组: 初·技术宅

UID
1692
精华
0
威望
1 点
宅币
7 个
贡献
0 次
宅之契约
0 份
在线时间
0 小时
注册时间
2016-5-14
发表于 2016-5-14 22:41:30 | 显示全部楼层
回复 Down完整代码包~

0

主题

25

帖子

52

积分

用户组: 小·技术宅

UID
1741
精华
0
威望
2 点
宅币
23 个
贡献
0 次
宅之契约
0 份
在线时间
4 小时
注册时间
2016-6-3
发表于 2016-6-4 02:48:50 | 显示全部楼层
后生可畏啊~!

0

主题

1

帖子

8

积分

用户组: 初·技术宅

UID
2064
精华
0
威望
1 点
宅币
5 个
贡献
0 次
宅之契约
0 份
在线时间
0 小时
注册时间
2016-11-14
发表于 2016-11-14 16:05:06 | 显示全部楼层
必须支持啊,又学了一点知识

0

主题

24

帖子

54

积分

用户组: 小·技术宅

UID
2067
精华
0
威望
1 点
宅币
28 个
贡献
0 次
宅之契约
0 份
在线时间
0 小时
注册时间
2016-11-17
发表于 2016-11-17 10:25:11 | 显示全部楼层
支持    !!

23

主题

63

帖子

1532

积分

用户组: 管理员

UID
1043
精华
7
威望
29 点
宅币
1349 个
贡献
27 次
宅之契约
0 份
在线时间
252 小时
注册时间
2015-8-15
发表于 2017-1-13 22:27:14 | 显示全部楼层
必须支持一下
flowers for Broken spirits - a woman turned into stake will hold the world in the basin of fire.

0

主题

1

帖子

9

积分

用户组: 初·技术宅

UID
2217
精华
0
威望
0 点
宅币
8 个
贡献
0 次
宅之契约
0 份
在线时间
0 小时
注册时间
2017-2-6
发表于 2017-2-6 03:49:15 | 显示全部楼层
刚好需要这功能..下载来研究看看

7

主题

18

帖子

210

积分

用户组: 中·技术宅

UID
536
精华
1
威望
16 点
宅币
146 个
贡献
9 次
宅之契约
0 份
在线时间
3 小时
注册时间
2014-11-6
发表于 2017-2-15 14:23:20 | 显示全部楼层
美俪女神 发表于 2016-1-2 20:30
经测试,只支持QQ邮箱,对126邮箱不支持,其他未知。

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

0

主题

18

帖子

48

积分

用户组: 初·技术宅

UID
2268
精华
0
威望
1 点
宅币
28 个
贡献
0 次
宅之契约
0 份
在线时间
2 小时
注册时间
2017-2-21
发表于 2017-2-27 12:37:10 | 显示全部楼层
感谢楼主分享哈

0

主题

1

帖子

8

积分

用户组: 初·技术宅

UID
2309
精华
0
威望
1 点
宅币
5 个
贡献
0 次
宅之契约
0 份
在线时间
0 小时
注册时间
2017-3-7
发表于 2017-3-7 21:02:22 | 显示全部楼层
真的是太神拉~~

0

主题

1

帖子

9

积分

用户组: 初·技术宅

UID
2473
精华
0
威望
0 点
宅币
8 个
贡献
0 次
宅之契约
0 份
在线时间
0 小时
注册时间
2017-5-6
发表于 2017-5-6 10:53:25 | 显示全部楼层
特别需要,万分感谢

0

主题

1

帖子

8

积分

用户组: 初·技术宅

UID
2502
精华
0
威望
1 点
宅币
5 个
贡献
0 次
宅之契约
0 份
在线时间
0 小时
注册时间
2017-5-14
发表于 2017-5-14 00:11:29 | 显示全部楼层
。。急用。。感谢楼主

本版积分规则

QQ|申请友链|Archiver|手机版|小黑屋|技术宅的结界 ( 滇ICP备16008837号|网站地图

GMT+8, 2018-9-24 00:06 , Processed in 0.139957 second(s), 48 queries , Gzip On.

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表