大家好,又见面了,我是你们的朋友全栈君。
应用:分布式评测系统中检测到连接断开后向管理员发送邮件。
一、 认证方式
ESMTP(Extension SMTP)即认证的邮件传输方式,是邮件服务器系统为了限制非本系统的正式用户利用本系统散发垃圾邮件或其他不当行
为而开设的一项安全认证服务。它与传统的SMTP方式相比,主要的不同有两点:
1) 支持8-bit MIME格式的编码。
2) 支持用户身份的验证。
多了一道用户身份的验证手续,验证之后的邮件发送过程与传统的SMTP方式一致。为了方便用户的使用,绝大多数的ESMTP服务器都继承了
POP3服务器的帐号和密码设置体系,也就是说收发邮件都用同一个帐号和密码。
根据[RFC 2554]规范,SMTP的认证功能主要是增加了AUTH命令。AUTH命令有多种用法,而且有多种认证机制。AUTH支持的认证机制主要有LOGIN,CRAM-MD5[注1]等。LOGIN应该是大多数免费邮件服务器都支持的,网易与新浪都支持。下面主要针对LOGIN方式进行介绍,其它方式请根据相应的RFC 规范进行修改。
几乎所有的ESMTP服务器都继承了POP3服务器的账号和密码设置体系,也就是说收发邮件用相同的账号和密码。当然,也可以用不同的账号和密码,但那样无论是电子邮件服务提供商的维护还是用户的使用都会很麻烦,故而很少采用。
LOGIN 方式口令-应答过程如下(S:表示服务器返回,C:表示客户端发送)
1. C: AUTH LOGIN
2. S: 334 dXNlcm5hbWU6 // dXNlcm5hbWU6是username:的BASE64编码
3. C: dXNlcm5hbWU6
4. S: 334 cGFzc3dvcmQ6 // cGFzc3dvcmQ6是password:的BASE64编码
5. C: cGFzc3dvcmQ6
6. S: 235 Authentication successful.
(1). 为客户端向服务器发送认证指令。
(2). 服务端返回base64编码串,成功码为334。编码字符串解码后为”username:”,说明要求客户端发送用户名。
(3). 客户端发送用base64编码的用户名,此处为”username:”。
(4). 服务端返回base64编码串,成功码为334。编码字符串解码后为”password:”,说明要求客户端发送用户口令。
(5). 客户端发送用base64编码的口令,此处为”password:”。
(6). 成功后,服务端返回码为235,表示认证成功可以发送邮件了
二:BASE64编码原理
Base64编码其实是将3个8位字节转换为4个6位字节,( 3*8 = 4*6 = 24 ) 这4个六位字节 其实仍然是8位,只不过高两位被设置为0. 当一个字
节只有6位有效时,它的取值空间为0~63。
事实上,0~63之间的ASCII码有许多不可见字符,所以应该再做一个映射,映射表为
‘A‘ ~ ‘Z‘ ? ASCII(0 ~ 25)
‘a’ ~ ‘z‘ ? ASCII(26 ~ 51)
‘0’ ~ ‘9‘ ? ASCII(52 ~ 61)
‘+‘ ? ASCII(62)
‘/‘ ? ASCII(63)
这样就可以将3个8位字节,转换为4个可见字符。
具体的字节拆分方法为:(图(画得不好,领会精神 :-))
aaaaaabb ccccdddd eeffffff //abcdef其实就是1或0,为了看的清楚就用abcdef代替
00aaaaaa 00bbcccc 00ddddee 00ffffff
注:上面的三个字节位原文,下面四个字节为Base64编码,其前两位均为0。
这样拆分的时候,原文的字节数量应该是3的倍数,当这个条件不能满足时,用全零字节补足,转化时Base64编码用=号代替,这就是为什么有
些Base64编码以一个或两个等号结束的原因,但等号最多有两个,因为:如果F(origin)代表原文的字节数,F(remain)代表余数,则
F(remain) = F(origin) MOD 3 成立。
所以F(remain)的可能取值为0,1,2.
如果设 n = [F(origin) – F(remain)] / 3
当F(remain) = 0 时,恰好转换为4*n个字节的Base64编码。
当F(remain) = 1 时,由于一个原文字节可以拆分为属于两个Base64编码的字节,为了让Base64编码是4的倍数,所以应该为补2个等号。
当F(remain) = 2 时,由于两个原文字节可以拆分为属于3个Base64编码的字节,同理,应该补上一个等号。
三、发送步骤
1)获得邮件服务器的地址。调用gethostbyname(),将smtp.sina.com作为参数传入,保存返回的 hostent结构的地址,用它的h_addr_list[0]来初始化sockaddr_in结构的sin_addr.S_un.S_addr成员,端口写成25,这是邮件服务标准端口。
2)TCP Socket连接。
3)使用SMTP来通信了。该通信是个同步的过程,遵守一发一收的规则,连接上后先接收服务器的反馈信息,然后发送“HELO [信息]/r/n”表明身份,命令EHLO和后面的信息要有空格,信息可以什么信息都不加,接收后继续发送“AUTH LOGIN/r/n”,接收后发送用户名(即邮箱地址中@前边的部分),再发送密码,此时服务器返回是否验证成功的信息,若成功则返回代码为235的信息,否则返回535,注意有可能由于服务器的繁忙而导致验证失败,并不是用户名和密码的问题,所以失败后要继续从头开始,连接-发送-接收-验证,另外,用户名和密码采用base64编码。
验证后就可以发送具体的邮件信息了。首先发送发件人,“MAIL FROM: <用户名@邮件服务器>/r/n”。其次发送收件人,这个可是要起作用的,发送 “RCPT TO: <目的邮箱>”,要发送给几个人,就发送几个“RCPT TO: <目的邮箱>”,然后发送“DATA/r/n”表示要发送具体数据了,数据格式为:邮件头+邮件体
邮件头:
From: “想要显示的发件人”<想要显示的邮箱名>/r/n
To: <想要显示的收件人邮箱地址>/r/n
Subject: 主题名/r/n/r/n 此处的两个/r/n表示邮件头结束
邮件体:
邮件内容
结束标志为/r/n./r/n
将这些信息组成一个字符串发送出去就可以了,最后发送“QUIT /r/n”断开连接。
至此,邮件发送程序便编写完成了。
4)断开TCP连接。
四、关键代码
void sendemail(char *mailsrv, char *mailfrom, char *mailto, char *body)
{
int sockfd = 0,i=0;
struct sockaddr_in their_addr = {0};
char buf[1500] = {0};
char rbuf[1500] = {0};
char login[128] = {0};
char pass[128] = {0};
struct hostent *hp = gethostbyname(mailsrv);
memset(&their_addr, 0, sizeof(their_addr));
their_addr.sin_family = AF_INET;
their_addr.sin_port = htons(25);
their_addr.sin_addr.s_addr = *(int*)(*hp->h_addr_list);
sockfd = open_socket((struct sockaddr *)&their_addr);
memset(rbuf,0,1500);
while(recv(sockfd, rbuf, 1500, 0) == 0)
{
printf(“reconnect…/n”);
sleep(2);
close(sockfd);
sockfd = open_socket((struct sockaddr *)&their_addr);
memset(rbuf,0,1500);
}
printf(“%d.%s/n”,i++, rbuf);
//1.EHLO
memset(buf, 0, 1500);
sprintf(buf, “EHLO caoli/r/n”);
send(sockfd, buf, strlen(buf), 0);
memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf(“%d.%s/n”,i++, rbuf);
//2.AUTH LOGIN
memset(buf, 0, 1500);
sprintf(buf, “AUTH LOGIN/r/n”);
send(sockfd, buf, strlen(buf), 0);
printf(“%s/n”, buf);
memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf(“%d.%s/n”,i++, rbuf);
//3.USER
memset(buf, 0, 1500);
sprintf(buf,”caoli”);
memset(login, 0, 128);
base64(login, buf, strlen(buf));
sprintf(buf, “%s/r/n”, login);
send(sockfd, buf, strlen(buf), 0);
printf(“%s/n”, buf);
memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf(“%d.%s/n”,i++, rbuf);
//4.PASSWD
sprintf(buf, “33060808”);
memset(pass, 0, 128);
base64(pass, buf, strlen(buf));
sprintf(buf, “%s/r/n”, pass);
send(sockfd, buf, strlen(buf), 0);
printf(“%s/n”, buf);
memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf(“%d.%s/n”,i++, rbuf);
//5.MAIL FROM
memset(buf, 0, 1500);
sprintf(buf, “MAIL FROM:<%s>/r/n”,mailfrom);
send(sockfd, buf, strlen(buf), 0);
memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf(“%d.%s/n”,i++, rbuf);
//6.MAIL TO …(multiple destination)
sprintf(buf, “RCPT TO:<%s>/r/n”, mailto);
send(sockfd, buf, strlen(buf), 0);
memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf(“%d:%d.%s/n”,__LINE__,i++, rbuf);
/*
sprintf(buf, “RCPT TO:starryheavens@hotmail.com/r/n”);
send(sockfd, buf, strlen(buf), 0);
memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf(“%d.%s/n”,i++, rbuf);
*/
//7.DATA
sprintf(buf, “DATA/r/n”);
send(sockfd, buf, strlen(buf), 0);
memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf(“%d.%s/n”,i++, rbuf);
//8.CONTENT
sprintf(buf, “%s/r/n./r/n”, body);
send(sockfd, buf, strlen(buf), 0);
memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf(“%d.%s/n”,i++, rbuf);
//9.QUIT
sprintf(buf, “QUIT/r/n”);
send(sockfd, buf, strlen(buf), 0);
memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf(“%d.%s/n”,i++, rbuf);
close(sockfd);
return;
}
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/131877.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...