文章目录
前言
写SMTP的最初目的仅仅是为了自动给自己发邮件而已,其实Python、Java等很多语言都有现成的包直接用就可以了,非常简单,甚至Linux下直接apt install一个mail相关的包就可以直接发邮件了。刚才开始觉得用Python密码明文太不安全,Java反编译太容易防君子不防小人。最终选了C实现,然而后来才发发现直接硬编码邮件账号密码到执行文件里反编译个密码还是非常简单的事,而且范了编码的大忌了,有空再对密码做加密处理吧。这里就当是了解一下SMTP协议的一个过程。
SMTP
SMTP模型
+----------+ +----------+
+------+ | | | |
| User |<-->| | SMTP | |
+------+ | Client- |Commands/Replies| Server- |
+------+ | SMTP |<-------------->| SMTP | +------+
| File |<-->| | and Mail | |<-->| File |
|System| | | | | |System|
+------+ +----------+ +----------+ +------+
SMTP client SMTP server
SMTP事务
SMTP事务分为三个步骤:
- 以MAIL 命令开始给出发送人
MAIL <SP> FROM:<reverse-path> <CRLF>
- 随后一个或多个RCPT命令,提供接收者的邮箱
RCPT <SP> TO:<forward-path> <CRLF>
- 然后DATA 命令给出邮件数据
DATA <CRLF>
<CRLF>:回车换行,即\r\n
<SP>: 空格字符
<CRLF>.<CRLF>: 邮件结束标志
SMTP命令
以下是SMTP命令:
HELO <SP> <domain> <CRLF>
MAIL <SP> FROM:<reverse-path> <CRLF>
RCPT <SP> TO:<forward-path> <CRLF>
DATA <CRLF>
RSET <CRLF>
SEND <SP> FROM:<reverse-path> <CRLF>
SOML <SP> FROM:<reverse-path> <CRLF>
SAML <SP> FROM:<reverse-path> <CRLF>
VRFY <SP> <string> <CRLF>
EXPN <SP> <string> <CRLF>
HELP [<SP> <string>] <CRLF>
NOOP <CRLF>
QUIT <CRLF>
TURN <CRLF>
HELO <SP> <domain> <CRLF>:打开传输通道,如"HELO smtp.163.com\r\n"
QUIT <CRLF> :关闭传输通道: “QUIT \r\n”
会话的第一个命令必须是HELO,会话最后一个命令必须是QUIT命令,NOOP, HELP, EXPN, 和VRFY命令可以在会话中任何地方使用
RESET (RSET):终止当前邮件事务,已经存储的发送者、接收者、邮件数据信息都被丢弃,清除缓存区
SEND (SEND): 初始化邮件事务,邮件数据被转发到一个或多个终端。
SEND AND MAIL (SAML): 初始化邮件事务,邮件数据被转发到一个或多个终端或邮箱。
VERIFY (VRFY): 验证邮箱是否存在,如果参数是用户名,则返回一个全名(如果存在)。
NOOP (NOOP): 这个命令指示服务器收到命令后不用回复OK。
…
必须的命令就几个HELO/MAIL/RCPT/DATA,外加一个鉴权的AUTH LOGIN,我在163试了一下RESET命令没实现?其它的命令还没测试过
再试试RSETRESET (RSET)
命令的返回值
I: intermediate
S: success
E: error
如连接,返回220代表连接成功,返回554代表连接出错,其它命令同理参考以下命令对应的值。
Command-Reply Sequences:
CONNECTION ESTABLISHMENT
S: 220
E: 554
EHLO or HELO
S: 250
E: 504, 550
MAIL
S: 250
E: 552, 451, 452, 550, 553, 503
RCPT
S: 250, 251 (but see section 3.4 for discussion of 251 and 551)
E: 550, 551, 552, 553, 450, 451, 452, 503, 550
DATA
I: 354 -> data -> S: 250
E: 552, 554, 451, 452
E: 451, 554, 503
RSET
S: 250
VRFY
S: 250, 251, 252
E: 550, 551, 553, 502, 504
EXPN
S: 250, 252
E: 550, 500, 502, 504
HELP
S: 211, 214
E: 502, 504
NOOP
S: 250
QUIT
S: 221
纯手动发送Email
开启邮箱smtp服务
首先从网页登录邮箱,在设置里打开SMTP服务,并设置客户端授权码
使用telnet连接登录smtp
telnet smtp.163.com 25
HELO smtp.163.com
直接MAIL FROM 开始邮件事务: 提示要鉴权,RFC 821文档中似乎并没有提到鉴权
不过想想也合理,不鉴权怎么知道是谁发的邮件,SMTP 认证也能为了使用户避免受到更多的垃圾邮件的侵扰。
AUTH LOGIN
“dXNlcm5hbWU6"是BASE64编码后的"username:”
"UGFzc3dvcmQ6"是BASE64编码后的 “Password:”
坑:为何鉴权失败?反复确认了账号、授权密码都没问题,smtp也开了,可就是登录失败。。。
原因:用户名不能带后缀,如邮箱 test@163.com 的用户名应该是test而不是全称,test经过base64编码后是dGVzdA==,即为这里要输入的用户名。
Note:用户名和密码都需要经过BASE64编码后再输入到Telnet会话框中(懒得自己编码的直接百度”Base64在线编码解码“)
登录成功
开始邮件事务
登录成功后,开始邮件事务
MAIL FROM:<xxx@163.com>
RCPT TO:<xxx@qq.com>
DATA
From: zbc<xxx@163.com>
TO: zbc<xxx@qq.com>
Subject: test
Mime-Version: 1.0
Content-Type:text/plain; charset=utf-8
Content-Transfer-Encoding: base64
dGVzdCBib2R5
Message-ID: <xxxxxxxxx.xxx@163.com>
Date: Date: Sun, 16 Jun 2019 15:08:37 +0800 (CST)
.
注意,最后一行单独一个"."表示结束
发送成功后收到的邮件,主体内容(DATA命令后面的内容)格式还有点错误,不过发成功了,有时间再研究MIME其它文档
RFC 821(1982年比我还老)似乎已经被RFC 2821替代了
C代码实现SMTP发送邮件
已经实现了普通文本邮件的发送。
未完待续
TODO:
- 附件的发送
- 适配Windows
makefile
CC_FLAG= -g
GCC=gcc
#GCC=x86_64-w64-mingw32-gcc
smtp.exe:main.o smtp.o base64.o
${GCC} ${CC_FLAG} -o smtp main.o smtp.o base64.o
main.o:main.c
${GCC} ${CC_FLAG} -c main.c -o main.o
smtp.o:smtp.h smtp.c
${GCC} ${CC_FLAG} -c smtp.c -o smtp.o
base64.o:lib/base64.h lib/base64.c
${GCC} ${CC_FLAG} -c lib/base64.c -o base64.o
.PHONY:clean
clean:
rm -f *.o smtp
reference: https://blog.csdn.net/qq_26093511/article/details/78836087
BASE64编码: https://datatracker.ietf.org/doc/html/rfc4648
lib/base64.h
/*base64.h*/
#ifndef _BASE64_H
#define _BASE64_H
#include <stdlib.h>
#include <string.h>
unsigned char *base64_encode(unsigned char *str);
unsigned char *base64_decode(unsigned char *code);
#endif
lib/base64.c
/*base64.c*/
#include "base64.h"
unsigned char *base64_encode(unsigned char *str)
{
long len;
long str_len;
unsigned char *res;
int i,j;
// The Base64 Alphabet
unsigned char *base64_table="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
// result str length
str_len=strlen(str);
if(str_len % 3 == 0)
len=str_len/3*4;
else
len=(str_len/3+1)*4;
res=malloc(sizeof(unsigned char)*len+1);
res[len]='\0';
//以3个8位字符为一组进行编码
for(i=0, j=0;i<len-2;j+=3,i+=4)
{
res[i]=base64_table[str[j]>>2]; //取出第一个字符的前6位并找出对应的结果字符
res[i+1]=base64_table[(str[j]&0x3)<<4 | (str[j+1]>>4)]; //将第一个字符的后位与第二个字符的前4位进行组合并找到对应的结果字符
res[i+2]=base64_table[(str[j+1]&0xf)<<2 | (str[j+2]>>6)]; //将第二个字符的后4位与第三个字符的前2位组合并找出对应的结果字符
res[i+3]=base64_table[str[j+2]&0x3f]; //取出第三个字符的后6位并找出结果字符
}
switch(str_len % 3)
{
case 1:
res[i-2]='=';
res[i-1]='=';
break;
case 2:
res[i-1]='=';
break;
}
return res;
}
unsigned char *base64_decode(unsigned char *code)
{
//根据base64表,以字符值为数组下标找到对应的十进制数据
int table[]={
0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,62,0,0,0,
63,52,53,54,55,56,57,58,
59,60,61,0,0,0,0,0,0,0,0,
1,2,3,4,5,6,7,8,9,10,11,12,
13,14,15,16,17,18,19,20,21,
22,23,24,25,0,0,0,0,0,0,26,
27,28,29,30,31,32,33,34,35,
36,37,38,39,40,41,42,43,44,
45,46,47,48,49,50,51
};
long len;
long str_len;
unsigned char *res;
//result str length
len=strlen(code);
//判断编码后的字符串后是否有=
if(strstr(code,"=="))
str_len=len/4*3-2;
else if(strstr(code,"="))
str_len=len/4*3-1;
else
str_len=len/4*3;
res=malloc(sizeof(unsigned char)*str_len+1);
res[str_len]='\0';
//以4个字符为一位进行解码
for(int i=0, j=0;i < len-2;j+=3,i+=4)
{
res[j]=((unsigned char)table[code[i]])<<2 | (((unsigned char)table[code[i+1]])>>4); //取出第一个字符对应base64表的十进制数的前6位与第二个字符对应base64表的十进制数的后2位进行组合
res[j+1]=(((unsigned char)table[code[i+1]])<<4) | (((unsigned char)table[code[i+2]])>>2); //取出第二个字符对应base64表的十进制数的后4位与第三个字符对应bas464表的十进制数的后4位进行组合
res[j+2]=(((unsigned char)table[code[i+2]])<<6) | ((unsigned char)table[code[i+3]]); //取出第三个字符对应base64表的十进制数的后2位与第4个字符进行组合
}
return res;
}
stmp.h
/**
* @file smtp.h
*
*/
#ifndef SMTP_H_
#define SMTP_H_
#define SMTP_BUFFER_SIZE 1024
/**
* @struct smtp
*/
struct smtp
{
const char* domain;
const char* user_name;
const char* password;
const char* subject;
const char* content;
const char** to;
// char** to;
int to_len;
char ** cc;
int cc_len;
char ** attachment;
int file_count;
int status;
int socket;
char buffer[SMTP_BUFFER_SIZE];
char* cmd;
char* data;
};
#ifdef __cplusplus
extern "C"
{
#endif
/**
* 使用smtp协议发送邮件。不做参数检查,外部自己处理好。
* @param domain 域名
* @param port 端口号
* @param user_name 用户名
* @param password 密码
* @param subject 标题
* @param content 邮件内容
* @param to 发送目标
* @param to_len 有多少个目标
* @return 如果发送成功返回0,否则返回一个正数表示错误原因。
*
* @par Sample Code:
* @code
* smtp_send("smtp.163.com",25,"xxx@163.com","mypassword","email-subject","email-content",
* "xxx@gmail.com");
* @code
*
*/
int smtp_send(const char* domain,int port,const char* user_name,const char* password,
const char* subject,const char* content,const char** to,int to_len,const char** cc, int cc_len);
#ifdef __cplusplus
}
#endif /* end of extern "C" */
#endif /* SMTP_H_ */
smtp.c
/**
* @file smtp.c
*
*
*/
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/socket.h>
// #include <winsock.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <strings.h>
#include<math.h>
#include "smtp.h"
#include "lib/base64.h"
/**
* 返回的错误码
*/
enum SMTP_ERROR
{
// success
SMTP_ERROR_OK,
// create socket fail!
SMTP_ERROR_SOCKET,
// connect socket fail
SMTP_ERROR_CONNECT,
// can not find domain
SMTP_ERROR_DOMAIN,
// server error
SMTP_ERROR_READ,
SMTP_ERROR_WRITE,
// server status error
SMTP_ERROR_SERVER_STATUS
};
/**
*去前后空白符
*/
void trim(char* str){
if(str==NULL)
return;
char *begin=str;
while(*begin&&(unsigned char)*begin<=32) begin++;
if(!*begin){
*str=0;
return;
}
while(*str++=*begin++);
str-=2;
while((unsigned char)*str--<=32);
*(str+2)=0;
}
/* 功 能:将str字符串中的oldstr字符串替换为newstr字符串
* 参 数:str:操作目标 oldstr:被替换者 newstr:替换者
* 返回值:返回替换之后的字符串
* 版 本: V0.2
*/
char *strrpc(char *str,char *oldstr,char *newstr){
char bstr[strlen(str)];//转换缓冲区
memset(bstr,0,sizeof(bstr));
for(int i = 0;i < strlen(str);i++){
if(!strncmp(str+i,oldstr,strlen(oldstr))){//查找目标字符串
strcat(bstr,newstr);
i += strlen(oldstr) - 1;
}else{
strncat(bstr,str + i,1);//保存一字节进缓冲区
}
}
strcpy(str,bstr);
return str;
}
/**
* @enum smtp 状态
*/
enum SMTP_STATUS
{
SMTP_STATUS_NULL, //!< SMTP_STATUS_NULL
SMTP_STATUS_EHLO, //!< SMTP_STATUS_EHLO
SMTP_STATUS_AUTH, //!< SMTP_STATUS_AUTH
SMTP_STATUS_SEND, //!< SMTP_STATUS_SEND
SMTP_STATUS_QUIT, //!< SMTP_STATUS_QUIT
SMTP_STATUS_MAX //!< SMTP_STATUS_MAX
};
/**
* 读取smtp服务器响应并简单解析出响应状态和响应参数
* @param sm smtp指针
* @return 读取正常返回0,否则返回正数表示错误原因
*/
static int smtp_read(struct smtp* sm)
{
for(;;)
{
int size = recv(sm->socket,sm->buffer,SMTP_BUFFER_SIZE - 1,0);
if(size == -1)
{
if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) continue;
}
// 套结字错误或者关闭
if(size <= 0) break;
sm->buffer[size] = 0;
printf("SERVER: %s\n",sm->buffer);
// 不确定这个是否找不到,smtp协议一般都会在响应状态后跟随参数
sm->cmd = sm->buffer;
char* p = strchr(sm->buffer,' ');
if(p)
{
*p = '\0';
sm->data = p + 1;
}
return 0;
}
printf("smtp_read() 接收信息错误\n");
return SMTP_ERROR_READ;
}
/**
* 向服务器发送信息
* @param fd 套结字
* @param buffer 要发送的数据
* @param buffer_size 要发送的数据长度
* @return 如果成功反送返回0,否则返回正数表示错误原因
*/
int smtp_write(int fd,const char* buffer)
{
int size = strlen(buffer);
for(int send_num = 0;send_num < size; )
{
int error = send(fd,&buffer[send_num],size - send_num,0);
if(error < 0)
{
printf("发送数据错误 errno = %d size = %d send_num = %d",errno,size, send_num);
if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) continue;
return SMTP_ERROR_WRITE;
}
else send_num += error;
}
return 0;
}
/**
* 分割接收到的数据,主要是区分base64编码结果。同时sm->data节点会被修改
* @param sm
* @return 返回原来的sm->data。
*/
static char* explode(struct smtp* sm)
{
char* old = sm->data;
char* p = old;
while(*p)
{
if((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') ||
*p == '+' || *p == '/' || *p == '=')
{
p++;
}
else
{
sm->data = p;
*p = '\0';
break;
}
}
return old;
}
static int hello(struct smtp* sm)
{
// 发送HELO命令
char buffer[256];
memset(buffer, 0 , sizeof(buffer));
int size = sprintf(buffer,"HELO %s\r\n",sm->domain);
if(smtp_write(sm->socket,buffer)) return SMTP_ERROR_WRITE;
// 服务器应该正常返回250
if(smtp_read(sm) || strcmp(sm->cmd,"250")) return SMTP_ERROR_READ;
sm->status = SMTP_STATUS_AUTH;
return 0;
}
static int auth(struct smtp* sm)
{
// 发送AUTH命令,第一次接到的数据应该是base64编码后的Username,如果不是直接返回
// 然后第二次应该是base64后的Password
if(smtp_write(sm->socket,"AUTH LOGIN\r\n")) return SMTP_ERROR_WRITE;
if(smtp_read(sm) || strcmp(sm->cmd,"334")) return SMTP_ERROR_READ;
// username:
char* p = explode(sm);
char buffer[256];
char* BASE64_USERNAME = base64_decode(p);
int size = strlen(BASE64_USERNAME);
strcpy(buffer, BASE64_USERNAME);
free(BASE64_USERNAME);
if(size < 0) return SMTP_ERROR_SERVER_STATUS;
buffer[size] = 0;
if(strcasecmp(buffer,"username:")) return SMTP_ERROR_SERVER_STATUS;
char* username = base64_encode(sm->user_name);
size = strlen(username);
// strcat(buffer, p2);
strcpy(buffer, username);
free(username);
if(size < 0 || size + 2 > 256) return SMTP_ERROR_WRITE;
buffer[size++] = '\r';
buffer[size++] = '\n';
buffer[size] = '\0';
if(smtp_write(sm->socket,buffer)) return SMTP_ERROR_WRITE;
if(smtp_read(sm) || strcmp(sm->cmd,"334")) return SMTP_ERROR_READ;
// Password:
p = explode(sm);
char* p2 = base64_decode(p);
size = strlen(p2);
strcpy(buffer, p2);
if(size < 0) return SMTP_ERROR_SERVER_STATUS;
buffer[size] = 0;
// if(strcasecmp(buffer,"password:")) return SMTP_ERROR_SERVER_STATUS;
if(strcasecmp(buffer,"Password:")) return SMTP_ERROR_SERVER_STATUS;
char* psw = base64_encode(sm->password);
// email password
strcpy(buffer, psw);
size = strlen(psw);
if(size < 0 || size + 2 > 256) return SMTP_ERROR_WRITE;
buffer[size++] = '\r';
buffer[size++] = '\n';
buffer[size] = '\0';
if(smtp_write(sm->socket,buffer)) return SMTP_ERROR_WRITE;
if(smtp_read(sm) || strcmp(sm->cmd,"235")) return SMTP_ERROR_READ;
sm->status = SMTP_STATUS_SEND;
return 0;
}
/**
* 生成smtp格式的时间字符串:Wed, 30 Jan 2019 22:45:26 +0800 (CST)
* 这里直接返回在堆栈上的缓冲区,意味着只能立刻使用,一旦堆栈有变
* 动就不能在使用了。
* @param buffer
* @return 返回 buffer
*/
static char* smtp_time(char* buffer)
{
time_t t2;
time(&t2);
struct tm t;
localtime_r(&t2,&t);
// localtime_s(&t2,&t);
static char* week[] = {"Sun" , "Mon" , "Tue" , "Wed" , "Thu" , "Fri" , "Sat"};
static char* month[] = {"Jan" , "Feb" , "Mar" , "Apr" , "May" , "Jun" , "Jul" , "Aug" , "Sep" , "Oct" , "Nov" , "Dec"};
// C Date: Wed, 30 Jan 2019 05:48:24 --84199186
// C Date: Wed, 30 Jan 2019 22:45:26 +0800 (CST)
sprintf(buffer,"%s, %02d %s %04d %02d:%02d:%02d %s%02d00 (CST)",
// Linux
week[t.tm_wday],t.tm_mday,month[t.tm_mon], t.tm_year + 1900,t.tm_hour,t.tm_min,t.tm_sec,t.tm_gmtoff >= 0? "+":"-",abs(t.tm_gmtoff / 3600));
// TODO: Windows
// week[t.tm_wday],t.tm_mday,month[t.tm_mon], t.tm_year + 1900,t.tm_hour,t.tm_min,t.tm_sec,t.__tm_gmtoff >= 0? "+":"-",abs(t.__tm_gmtoff / 3600)); // C99
return buffer;
}
static int send_mail(struct smtp* sm)
{
// MAIL FROM
char buffer[256];
int size = sprintf(buffer,"MAIL FROM: <%s>\r\n",sm->user_name);
if(smtp_write(sm->socket,buffer)) return SMTP_ERROR_WRITE;
if(smtp_read(sm) || strcmp(sm->cmd,"250")) return SMTP_ERROR_READ;
// RCPT TO
int i;
for(i = 0; i < sm->to_len; i++)
{
size = sprintf(buffer,"RCPT TO: <%s>\r\n",sm->to[i]);
if(smtp_write(sm->socket,buffer)) return SMTP_ERROR_WRITE;
if(smtp_read(sm) || strcmp(sm->cmd,"250")) return SMTP_ERROR_READ;
}
// DATA,最后一行是 "\r\n.\r\n" 表示邮件结束
if(smtp_write(sm->socket,"DATA\r\n")) return SMTP_ERROR_WRITE;
if(smtp_read(sm) || strcmp(sm->cmd,"354")) return SMTP_ERROR_READ;
// 分配足够大缓冲区存储邮件头,唯一不确定的就是群发数量,这里先统计一下发送目标占用的字节大小
int to_size = 0;
for(i = 0; i < sm->to_len; i++) to_size += strlen(sm->to[i]);
char header[to_size + 512 + strlen(sm->user_name)];
// From
char * from = (char*)malloc(sizeof(char)*(strlen("From: %s<%s>\r\n")+strlen(sm->user_name)+strlen(sm->user_name)));
int pos = sprintf(from,"From: %s<%s>\r\n",sm->user_name,sm->user_name);
//sprintf(&header[pos],"From: %s<%s>\r\n",sm->user_name,sm->user_name);
// int pos = strlen("MIME-Version: 1.0\r\nContent-Type: text/html\r\n");
memcpy(header,from,pos);
// To:
for(i = 0; i < sm->to_len; i++)
{
pos += sprintf(&header[pos],"To: %s\r\n",sm->to[i]);
}
// CC: TODO
if(sm->cc != NULL && sm->cc_len > 0)
{
for(i = 0; i < sm->cc_len; i++)
{
pos += sprintf(&header[pos],"Cc: %s\r\n",sm->cc[i]);
}
}
// Subject:
pos += sprintf(&header[pos],"Subject: %s\r\n",sm->subject);
// char mime_version[] = "Mime-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\n";
char mime_version[] = "Mime-Version: 1.0\r\nContent-Type: text/html; charset=utf-8\r\n";
pos += sprintf(&header[pos], mime_version);
// Content-Transfer-Encoding: base64
pos += sprintf(&header[pos],"Content-Transfer-Encoding: base64\r\n");
pos += sprintf(&header[pos],"Message-ID: <%ld.%s>\r\n",time(NULL),sm->user_name);
char date[50];
pos += sprintf(&header[pos],"Date: %s\r\n\r\n",smtp_time(date));
free(from);
if(smtp_write(sm->socket,header)) return SMTP_ERROR_WRITE;
if(smtp_write(sm->socket,sm->content)) return SMTP_ERROR_WRITE;
if(smtp_write(sm->socket,"\r\n.\r\n")) return SMTP_ERROR_WRITE;
if(smtp_read(sm) || strcmp(sm->cmd,"250")) return SMTP_ERROR_READ;
sm->status = SMTP_STATUS_QUIT;
return 0;
}
static int quit(struct smtp* sm)
{
// if(smtp_write(sm->socket,"QUIT \r\n",strlen("QUIT \r\n"))) return SMTP_ERROR_WRITE;
if(smtp_write(sm->socket,"QUIT \r\n")) return SMTP_ERROR_WRITE;
if(smtp_read(sm) || strcmp(sm->cmd,"221")) return SMTP_ERROR_READ;
sm->status = SMTP_STATUS_NULL;
return 0;
}
typedef int (*SMTP_FUN)(struct smtp*);
static const SMTP_FUN smtp_fun[SMTP_STATUS_MAX] = {NULL,hello,auth,send_mail,quit};
int smtp_send(const char* domain,int port,const char* user_name,const char* password,
const char* subject,const char* content,const char** to,int to_len,const char** cc, int cc_len)
{
struct hostent* host = gethostbyname(domain);
if(!host)
{
printf("domain can not find!\n");
return SMTP_ERROR_DOMAIN;
}
if(host->h_addrtype != AF_INET)
{
// Linux
// if(host->h_addrtype == AF_INET6) printf("ipv6 is not support!\n");
// Windows
if(host->h_addrtype == AF_INET) printf("ipv6 is not support!\n");
else printf("address type is not support %d\n ",host->h_addrtype);
return SMTP_ERROR_DOMAIN;
}
int sock_fd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == sock_fd)
{
printf("can not create socket!\n");
return SMTP_ERROR_SOCKET;
}
// 连接到服务器
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr = *(struct in_addr*)host->h_addr_list[0];
memset(local.sin_zero,0,sizeof(local.sin_zero));
if(-1 == connect(sock_fd,(struct sockaddr*)&local,sizeof(local)))
{
printf("can not connect socket!\n");
return SMTP_ERROR_CONNECT;
}
printf("connect ok ,ip address %s \n",inet_ntoa(local.sin_addr));
struct smtp sm = {.domain=domain,.user_name=user_name,.password=password,.subject=subject,
.content=base64_encode(content),.status=SMTP_STATUS_EHLO,.socket=sock_fd,.to=to,
.to_len=to_len,.cc=cc,.cc_len=cc_len};
// free(sm.content);
// 这里应该是服务器欢迎信息,如果状态不是220,直接返回
if(smtp_read(&sm) || strcmp(sm.cmd,"220")) return SMTP_ERROR_READ;
while(sm.status != SMTP_STATUS_NULL)
{
int error = smtp_fun[sm.status](&sm);
if(error)
{
printf("error = %d\n",error);
return error;
}
}
close(sock_fd);
return 0;
}
main.c
/**
* @file main.c
*
* Author: zbc
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <getopt.h>
#include <string.h>
#include "smtp.h"
//返回一个 char *arr[], size为返回数组的长度
char **explode(char sep, const char *str, int *size)
{
int count = 0, i;
for(i = 0; i < strlen(str); i++)
{
if (str[i] == sep)
{
count ++;
}
}
char **ret = calloc(++count, sizeof(char *));
//int lastindex = -1;
int lastindex = -1;
int j = 0;
for(i = 0; i < strlen(str); i++)
{
if (str[i] == sep)
{
if ((i - lastindex -1) > 0)
{
ret[j] = calloc(i - lastindex, sizeof(char)); //分配子串长度+1的内存空间
memcpy(ret[j], str + lastindex + 1, i - lastindex - 1);
// if(j==0)
// {
// memcpy(ret[j], str + lastindex , i - lastindex);
// }
// else{
// memcpy(ret[j], str + lastindex + 1 , i - lastindex -1 );
// }
j++;
}
lastindex = i;
}
}
//处理最后一个子串
//if (lastindex <= strlen(str) )
if (lastindex <= (int)strlen(str) )
{
if ((strlen(str) - 1 - lastindex) > 0)
{
ret[j] = calloc(strlen(str) - lastindex, sizeof(char));
memcpy(ret[j], str + lastindex + 1, strlen(str) - 1 - lastindex);
j++;
}
}
*size = j;
return ret;
}
void help_info()
{
printf(
"Usage: ./smtp [Option]\n"
" -h --help show this\n"
" -m --show Display sending info\n"
" -d --domain smtp server address[smtp.163.com]\n"
" -u --user username:Sender Email\n"
" -p --password \n"
" -t --to Receiving Email list\n"
" -c --cc Carbon Copy Email list\n"
" -s --subject subject\n"
" -b --content email body partion\n"
" -f --attachment filname list\n"
);
exit(0);
}
//reference https://blog.csdn.net/zhaoyong26/article/details/54574398
void get_option(int argc, char **argv, struct smtp* sm)
{
char *cmd = argv[0];
int flag = 0;
/**
d:domain
//username
f:from
p:password
t:to
c:cc
s:subject
b:content
a:attachment
**/
/**
char *domain = NULL;
char *from = NULL;
char *password = NULL;
char *to = NULL;
char *cc = NULL;
char *subject = NULL;
char *content = NULL;
char *attachment = NULL;
**/
while (1) {
int option_index = 0;
struct option long_options[] =
{
{"help" , 0, 0, 'h'}, //0代表没有参数
{"domain" , 1, 0, 'd'},
//{"from" , 1, 0, 'f'},
{"user" , 1, 0, 'u'},
{"password" , 1, 0, 'p'},
{"to" , 1, 0, 't'},
{"cc" , 1, 0, 'c'},
{"subject" , 1, 0, 's'},
{"attachment" , 1, 0, 'f'},
{"content" , 1, 0, 'b'}, //1代表有参数
{"show" , 0, 0, 'm'}
};
int c;
c = getopt_long(argc, argv, "h:d:u:p:t:c:s:f:b:m",long_options, &option_index); //注意这里的冒号,有冒号就需要加参数值,没有冒号就不用加参数值
if (c == -1)
break;
switch (c)
{
case 'h':
//printf("help->\n\t%s",help_info);
help_info();
break;
case 'd':
// printf("domain: %s\n", optarg);
// domain = (char*)calloc(strlen(optarg),sizeof(char));
sm->domain = optarg;
break;
case 'u':
sm->user_name = optarg;
break;
case 'p':
sm->password = optarg;
break;
case 't':
{
int to_len;
sm->to = explode(',', optarg, &to_len);
sm->to_len = to_len;
break;
}
case 'c':
{
int cc_len;
// sm->cc = explode(',', optarg, &cc_len);
(*sm).cc = explode(',', optarg, &cc_len);
// sm->cc_len = cc_len;
(*sm).cc_len = cc_len;
break;
}
case 'f':
{
int count;
sm->attachment = explode(',', optarg, &count);
sm->file_count = count;
// printf("\\\\TODO:Unable to send attachment\n");
// exit(1);
break;
}
case 's':
sm->subject = optarg;
break;
case 'b':
sm->content = optarg;
break;
case 'm':
flag = 1;
break;
default:
// printf("this is default!\n");
break;
}
}
if(sm->user_name == NULL){
// help_info();
fprintf(stderr,"[Error] user cannot be empty.\n");
exit(1);
}
if(sm->password == NULL){
// help_info();
fprintf(stderr,"[Error] password cannot be empty.\n");
exit(1);
}
if(sm->to == NULL){
// printf("[Error] Receive Email address cannot be empty.\n");
fprintf(stderr,"[Error] Receive Email address cannot be empty.\n");
exit(1);
}
if(sm->subject == NULL){
fprintf(stderr,"[Error] Subject cannot be empty.\n");
exit(1);
}
//打印邮件信息
if(flag == 1)
{
if(sm->domain != NULL){
printf("domain:%s\n",sm->domain);
//free(domain);
//domain = NULL;
}
if(sm->user_name != NULL){
printf("from:%s\n",sm->user_name);
}
if(sm->password != NULL){
// printf("password:%s\n",sm->password);
printf("password:******\n");
}
if(sm->to != NULL)
{
printf("To: ");
for(int i = 0; i < sm->to_len; i++)
{
printf("<%s>",sm->to[i]);
if(i+1 == sm->to_len)
printf("\n");
else
printf(", ");
}
}
if(sm->cc != NULL)
{
printf("CC: ");
for(int i = 0; i < sm->cc_len; i++)
{
printf("<%s>",sm->cc[i]);
if(i+1 == sm->cc_len)
printf("\n");
else
printf(", ");
}
}
if(sm->attachment != NULL)
{
printf("attachment: ");
for(int i = 0; i < sm->file_count; i++)
{
printf("<%s>",sm->attachment[i]);
if(i+1 == sm->file_count)
printf("\n");
else
printf(", ");
}
}
}
if(sm->subject != NULL)
{
printf("subject:%s\n", sm->subject);
}
return;
}
int main(int argc,char** argv)
{
struct smtp sm = {};
sm.domain = "smtp.163.com";
sm.cc = NULL;
// sm.cc_len = NULL;
int to_len = 2;
/**
d:domain
//username
p:password
f:from
t:to
c:cc
s:subject
b:content
a:attachment
**/
printf("\n\n\n----------------------------------\n");
get_option(argc , argv, &sm);
smtp_send(sm.domain,25,sm.user_name,sm.password,sm.subject,sm.content,sm.to,sm.to_len, sm.cc, sm.cc_len);
printf("\n\n\n----------------end------------------\n");
return EXIT_SUCCESS;
}
reference
[1]. https://datatracker.ietf.org/doc/html/rfc821
[2]. https://datatracker.ietf.org/doc/html/rfc2821