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

完整代码下载:

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。

因工作需要在Linux环境中用C++编写个发送邮件的程序,着实费了点周折,最终得以满意解决,现将历程与成果与大家分享! 一、刚开始网上一通逛搜,发现Linux环境下,发邮件使用较多的方法是libesmtp包,网上也有示例,按照相关章的指引,很容易就实现的邮件的发送,但问题是不知道如何实现SSL。 二、发现libesmtp文件中有个smtp_starttls_set_ctx接口,似乎是可以解决ssl问题的,逛搜libesmtp解决SSL发送邮件的解决办法,几乎无任何信息,后来下载了个libesmtp的源代码包libesmtp-1.0.6.tar.bz2,内含examples示例目录,可以直接编译成功,但似乎是只支持tls邮件发送,而不支持ssl邮件的发送,百思不得其解。 三、接着寻找别的解决办法,在CSDN搜到一个csmtp说可以解决SSL邮件发送问题的资源,但下载需要50积分,说心里话能解决问题50积分也是值得的,但没有呀,提供资源者还比较仁义,告知资来源于https://www.codeproject.com,于是乎在codeproject找到了csmtp的资源,有两个版本,v2.4版本包CSmtp_v2_4_ssl.zip,v1.8版本分为window(CSmtp_v1_8a.zip)和linux(CSmtp_v1_8b.zip)两个包。 四、为了能省点精力,就直接用版较低的linux版吧,解压后发现有makefile文件,可直接编译通过,一般的邮件能发送成功,但可惜的是v1.8版本也不支持ssl协议。 五、其实从包的名字上就能看出来v2.4版本开始支持 ssl协议,但v2.4并不分windows版本和linux版本,是否能支持linux呢,查看源代码发现有对linux的支持,只是包内没有makefile文件,似乎没有在linux目录下编译过,于是编写了个makefile文件尝试编译,竞然编通过,而且发送文件成功,经过测试可以支持ssl邮件的发送,因暂无需求tls未做测试。 六、现将程序重新打包成csmtp_v2.4_linux.tar文件,与大家分享,文中所提到的相关资源包都一并打包到资源中了。 最后感谢原创christopher w. backen提供的代码资源!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值