您的位置:首页 > 运维架构 > Linux

linux下基于SMTP协议的C++邮件客户端

2015-10-14 21:52 751 查看
完整代码下载:
https://github.com/WaPonX/SMTPMail

在网络中使用SMTP登陆的时候,需要将代码转换成base64编码。
下面这个函数是从网上抄的:
#include <string>

std::string Base64Encode(const std::string& src) {
using std::string;
int i, j, srcLen = src.length();
string dst(srcLen / 3 * 4 + 4, 0);
for(i = 0, j= 0; i <=srcLen - 3; i+=3, j+=4) {
dst[j] = (src[i] & 0xFC) >> 2;
dst[j+1] = ((src[i] & 0x03) << 4) + ((src[i+1] & 0xF0) >> 4);
dst[j+2] = ((src[i+1] & 0x0F) << 2) + ((src[i+2] & 0xC0) >> 6);
dst[j+3] = src[i+2] & 0x3F;
}
if( srcLen % 3 == 1 ) {
dst[j] = (src[i] & 0xFC) >> 2;
dst[j+1] = ((src[i] & 0x03) << 4);
dst[j+2] = 64;
dst[j+3] = 64;
j += 4;
}
else if( srcLen % 3 == 2 ) {
dst[j] = (src[i] & 0xFC) >> 2;
dst[j+1] = ((src[i] & 0x03) << 4) + ((src[i+1] & 0xF0) >> 4);
dst[j+2] = ((src[i+1] & 0x0F) << 2);
dst[j+3] = 64;
j+=4;
}

static unsigned char *base64 =
(unsigned char*)("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=");
for(i = 0; i < j; ++i) {    //map 6 bit value to base64 ASCII character
dst[i] = base64[(int)dst[i]];
}

return dst;
}


使用这个函数就可以完成base64转码
在linux下你可以使用如下命令:
echo string | base64
将string替换成你想要转换的字符串,输出的就是对应的base64编码。
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string>
#include <time.h>

namespace Mail {
bool OpenLogFile(int &log) {
if ( (log = open("./mail.log", O_WRONLY | O_APPEND)) == -1) {
printf("open log file error!\n");
printf("please, make sure have a file called mail.log in the path\n");

return false;
}
return true;
}

std::string GetTime() {
time_t tt = time(NULL);

return std::string(ctime(&tt));
}
void log(const char *str, size_t) {
static int log = -1;
if (log < 0) {
if (!OpenLogFile(log)) {
return ;
}
}
std::string res(GetTime());
res =  res + str + "\n";
write (log, res.c_str(), res.length());
}

}	// namespace Mail


对上面的三个函数进行说明:
1.
bool OpenLogFile(int &log);

这个函数包装了打开日志文件这个功能,使用了O_WRONLY | O_APPEND,因为只需要写,不需要读而且每次都要写到文件的最后。
2.
std::string GetTime();

这个函数为获取系统当前的时间,并按string类型返回。
3.
void log(const char *str, size_t);

这个函数就是将错误信息写入日志文件的函数,为什么第二个参数没有命名?一开始我是有命名的,后来发现这个参数没用,所以就不命名了。这样的做法可以避免编译器报错。是一个小技巧。

#include "base64encode.h"
#include "log.h"
#include "smtpmail.h"

#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>

namespace {
const std::string SMTPPORT("25");
}

namespace Mail {
SMTPMail::SMTPMail (const String &username,
const String &password,
const String &host) :
_islogin(false),
_reuseaddr(true),
_fd(-1),
_paddrfilter(InitAddrInfoFilter()),
_username(username),
_password(Base64Encode(password)),
_host(host){
//_paddrfilter = InitAddrInfoFilter();
}

SMTPMail::~SMTPMail() {
if (_fd > 0) {
close(_fd);
}
if (_paddrfilter != NULL) {
freeaddrinfo(_paddrfilter);
}
}

SMTPMail::AddrType *SMTPMail::InitAddrInfoFilter() {
AddrType *p = new AddrType;
if (p == NULL) {
return NULL;
}
memset(p, 0, sizeof(AddrType));
//p->ai_flags = AI_CANONNAME;
p->ai_family = AF_UNSPEC;
p->ai_socktype = SOCK_STREAM;
struct addrinfo *res = NULL;
int n = getaddrinfo(_host.c_str(), SMTPPORT.c_str(), p, &res);
if (n != 0) {
const char *str = gai_strerror(n);
log(str, strlen(str));
return NULL;
}
if (!Socket(res)) {
freeaddrinfo(res);
const char *str = strerror(errno);
log(str, strlen(str));
return NULL;
}
_islogin = true;

return p;
}

bool SMTPMail::Socket(AddrType *res) {
while (res != NULL) {
_fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (_fd > 0) {
if (setsockopt(_fd, SOL_SOCKET, SO_REUSEADDR, &_reuseaddr, sizeof(_reuseaddr))  == 0) {
if (connect(_fd, res->ai_addr, sizeof(struct sockaddr)) == 0){
return true;
}
}
close(_fd);
_fd = -1;
}
res = res->ai_next;
}

return false;
}

bool SMTPMail::Login() {
auto Check = [] (const String &res,
const String &mode) -> void {
if (res.substr(0, 3) != mode) {
log(res.c_str(), res.length());
}
};

Send(String("HELO "+ _username +"\r\n"));
Check(Recv(), "250");

Send(String("auth login\r\n"));
Check(Recv(), "334");

Send(Base64Encode(_username) + "\r\n");
Check(Recv(), "334");

Send(String(_password + "\r\n"));
Check(Recv(), "334");

return true;
}

void SMTPMail::SendEmail(const String &to, const String &text) {
if(false == _islogin) {
return;
}
if (!Login()) {
return ;
}
Send(String("MAIL FROM: <" + _username + ">\r\n"));
Recv();

Send(String("RCPT TO: <" + to + ">\r\n"));
Recv();

Send(String("DATA\r\n"));
Recv();

Send(String("to:" + to + "\r\n" + text + "\r\n.\r\n"));
Recv();

Send(String("QUIT\r\n"));
Recv();
}

void SMTPMail::Send(const String &msg) {
if(send(_fd, msg.c_str(), msg.length(), MSG_CONFIRM) < 0) {
const char *str = "send error\n";
log(str, strlen(str));
}
}

SMTPMail::String SMTPMail::Recv() {
const size_t buflen = 512;
static char buf[buflen];
if (recv(_fd, buf, buflen, 0) < 0) {
const char *str = "recv error\n";
log(str, strlen(str));

return String("");
}

return String(buf);
}
}


在这段代码中我只挑一部分函数进行说明,毕竟有的函数很简单,没必要说明:

1.

SMTPMail::AddrType *SMTPMail::InitAddrInfoFilter();
这个函数是为了初始化AddrInfo类型的过滤器。因为是动态分配内存,而且是初始化成员,为了能在内存分配失败的时候更加容易的添加异常处理,我讲这部分代码独立出来。

需要注意的是AddrType是我自己typedef出来的一个类型:

typedef struct addrinfo AddrType;
struct addrinfo类型在删除的时候需要调用freeaddrinfo函数进行删除。

其实,这个做法还有一个更加明智的代替方法:
bool SMTPMail::Socket(AddrType *res)


使用智能指针。并且为这个智能智能指针定义自己的删除器。

具体做法请自行google

2.
bool SMTPMail::Socket(AddrType *res);


这个函数将尝试连接getaddrinfo返回的所有可用的地址,使用setsockopt函数设置端口可以复用,并连接。

如果成功,则返回true。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: