Abstract
この投稿では,C++ から Email (Gmail) の送信を行う.C++ からメールを送信するには,socket 通信で接続を確立し,OpenSSL 等の暗号化ライブラリ通して通信する必要がある.socket 通信まで正常に動作させられれば,暗号化通信は socket を OpenSSL でラップするだけである.その後,メールサーバーと定型文をやり取りすることで,メールが送信される.本投稿ではメールサーバとして Gmail を利用する.Gmail をプログラムから利用するにあたり,予め Gmail の「安全性の低いアプリの許可:」を「有効」にする必要がある.また,2 段階認証を使用している場合は,本プログラム用にアプリケーションパスワードを発行しておく必要がある.
Method
1) Socket 通信の確立.
2) OpenSSL による暗号化通信.
2) OpenSSL による暗号化通信.
1), 2) の実装部を下記に示します.
./socket.hpp
./socket.cpp
3) Gmail サーバとの通信../socket.hpp
#pragma once
#include <string>
#include <unistd.h> //close()に必要
#include <netdb.h> //freeaddrinfo()
#include <openssl/ssl.h>
namespace sstd{ class sockSSL; }
class sstd::sockSSL{
private:
// for Socket
int sock;
std::string hostNameOrAddress; //ex: "www.google.co.jp"
std::string service; //ex: "http" or "80" (80 is a service number of HTTP.)
// for OpenSSL
SSL_CTX *ctx;
SSL *ssl;
public:
sockSSL(const std::string& hostNameOrAddress_in, const std::string& service_in){
this->hostNameOrAddress = hostNameOrAddress_in;
this->service = service_in;
}
~sockSSL(){ close(); }
bool open();
int send (const std::string& msg);
int send_withPrint(const std::string& msg);
// RETURN VALUES (https://www.openssl.org/docs/man1.0.2/ssl/SSL_write.html)
// The following return values can occur:
// > 0: The write operation was successful, the return value is the number of bytes actually written to the TLS/SSL connection.
// <= 0: The write operation was not successful, because either the connection was closed, an error occurred or action must be taken by the calling process. Call SSL_get_error() with the return value ret to find out the reason.
// SSLv2 (deprecated) does not support a shutdown alert protocol, so it can only be detected, whether the underlying connection was closed. It cannot be checked, why the closure happened.
// Old documentation indicated a difference between 0 and -1, and that -1 was retryable. You should instead call SSL_get_error() to find out if it's retryable.
std::string recv(bool& result);
void close(); // 接続を切断する
};
./socket.cpp
#include "socket.hpp"
#include <sstd/sstd.hpp>
bool sstd::sockSSL::open(){
struct addrinfo info;
info.ai_flags = 0;
info.ai_family = AF_UNSPEC; // allow IPv4 or IPv6
info.ai_socktype = SOCK_STREAM; // SOCK_STREAM: TCP, SOCK_DGRAM: UDP
info.ai_protocol = 0;
struct addrinfo *pInfo;
int ret = getaddrinfo(this->hostNameOrAddress.c_str(), this->service.c_str(), &info, &pInfo );
if(ret!=0){ sstd::pdbg("ERROR: getaddrinfo() was failed: %s\n",gai_strerror(ret)); return false; }
struct addrinfo *rp; // for repeat
for(rp=pInfo; rp!=NULL; rp=rp->ai_next){
this->sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if(this->sock!=-1){
if(::connect(this->sock, rp->ai_addr, rp->ai_addrlen)==0){ break; }
sstd::pdbg("ERROR: connect() was failed.\n");
::close(this->sock);
}else{
sstd::pdbg("ERROR: socket() was failed.\n");
}
}
if(rp==NULL){
sstd::pdbg("ERROR: All connection was failed.\n");
freeaddrinfo(pInfo);
return false;
}
freeaddrinfo(pInfo);
// - init OpenSSL ------------------------------------------
// init
SSL_load_error_strings();
SSL_library_init();
// settings
// this->ctx = SSL_CTX_new( SSLv2_client_method()); // SSLver2だけを使用する
// this->ctx = SSL_CTX_new( SSLv3_client_method()); // SSLver3だけを使用する
// this->ctx = SSL_CTX_new( TLSv1_client_method()); // TLSver1だけを使用する
this->ctx = SSL_CTX_new(SSLv23_client_method()); // SSLv2,SSLv3,TLSv1すべてだけを使用する
this->ssl = SSL_new(this->ctx);
// beginning of the SSL connection
SSL_set_fd(this->ssl, this->sock); // throwing socket to OpenSSL
SSL_connect(this->ssl);
// ----------------------------------- end of init OpenSSL -
return true;
}
int sstd::sockSSL::send(const std::string& msg){
return SSL_write(this->ssl, msg.c_str(), msg.length());
}
int sstd::sockSSL::send_withPrint(const std::string& msg){
printf("Sending Data >> %s", msg.c_str());
return send(msg);
}
std::string sstd::sockSSL::recv(bool& result){
result=true;
std::string recvMsg;
char buf[1024*1024];
for(int previous_readSize=0, readSize=1;
previous_readSize != readSize;
previous_readSize = readSize )
{
memset(buf, 0, sizeof(buf));
readSize = SSL_read(this->ssl, buf, sizeof(buf)-1);
if(readSize<=0){ break; }
buf[readSize] = '\0';
recvMsg += buf;
}
if(recvMsg.size()==0){ result=false; }
return recvMsg;
}
void sstd::sockSSL::close(){
// finalize of OpenSSL
SSL_shutdown(this->ssl); // correspond to SSL_connect()
SSL_free (this->ssl); // correspond to SSL_new()
SSL_CTX_free(this->ctx); // correspond to SSL_CTX_new()
// disconnection of the line
::close(this->sock);
}
3) の実装部を下記に示します.
./sendMail.hpp
./sendMail.cpp
./sendMail.hpp
#pragma once
struct sMail{
std::string usr; // usr name
std::string domain; // domain
std::string pass; // password
// std::string from; // メールの送信元。[usr]より自動生成
std::string to; // メールの送信先
std::string subject; // メールの件名
std::string data; // メールの本文(今回はHTMLmailなので、HTMLを記述)
};
bool sendMail (struct sMail& mail);
bool sendMail_withPrint (struct sMail& mail);
bool sendMail_of_HTML (struct sMail& mail);
bool sendMail_of_HTML_withPrint(struct sMail& mail);
./sendMail.cpp
#include <sstd/sstd.hpp>
#include "socket.hpp"
#include "sendMail.hpp"
bool sendMail(struct sMail& mail){
sstd::sockSSL sock(sstd::ssprintf("smtp.%s", mail.domain.c_str()).c_str(), "465");
bool ret;
if(!sock.open()){return false;} sock.recv(ret); if(!ret){return false;}
if( sock.send("EHLO localhost\r\n" )<=0){return false;} sock.recv(ret); if(!ret){return false;}
if( sock.send("AUTH LOGIN\r\n" )<=0){return false;} sock.recv(ret); if(!ret){return false;}
if( sock.send(sstd::base64_encode(mail.usr)+"\r\n" )<=0){return false;} sock.recv(ret); if(!ret){return false;} // mail User ID
if( sock.send(sstd::base64_encode(mail.pass)+"\r\n" )<=0){return false;} sock.recv(ret); if(!ret){return false;} // mail pass
if( sock.send("MAIL FROM: <"+mail.usr+'@'+mail.domain+">\r\n")<=0){return false;} sock.recv(ret); if(!ret){return false;}
if( sock.send("RCPT TO: <"+mail.to+">\r\n" )<=0){return false;} sock.recv(ret); if(!ret){return false;}
if( sock.send("DATA\r\n" )<=0){return false;} sock.recv(ret); if(!ret){return false;}
std::string buf;
buf = "Subject: =?UTF-8?B?"+sstd::base64_encode(mail.subject)+"?=\r\n";
buf += "Mime-Version: 1.0;\r\n";
buf += "Content-Type: text/plain; charset=\"UTF-8\";\r\n";
buf += "Content-Transfer-Encoding: 7bit;\r\n";
buf += "\r\n";
buf += mail.data+"\r\n";
buf += ".\r\n";
if( sock.send(buf )<=0){return false;} sock.recv(ret); if(!ret){return false;}
return true;
}
bool sendMail_withPrint(struct sMail& mail){ 〜 省略 〜 }
bool sendMail_of_HTML(struct sMail& mail){
sstd::sockSSL sock(sstd::ssprintf("smtp.%s", mail.domain.c_str()).c_str(), "465");
bool ret;
if(!sock.open()){return false;} sock.recv(ret); if(!ret){return false;}
if( sock.send("EHLO localhost\r\n" )<=0){return false;} sock.recv(ret); if(!ret){return false;}
if( sock.send("AUTH LOGIN\r\n" )<=0){return false;} sock.recv(ret); if(!ret){return false;}
if( sock.send(sstd::base64_encode(mail.usr)+"\r\n" )<=0){return false;} sock.recv(ret); if(!ret){return false;} // mail User ID
if( sock.send(sstd::base64_encode(mail.pass)+"\r\n" )<=0){return false;} sock.recv(ret); if(!ret){return false;} // mail pass
if( sock.send("MAIL FROM: <"+mail.usr+'@'+mail.domain+">\r\n")<=0){return false;} sock.recv(ret); if(!ret){return false;}
if( sock.send("RCPT TO: <"+mail.to+">\r\n" )<=0){return false;} sock.recv(ret); if(!ret){return false;}
if( sock.send("DATA\r\n" )<=0){return false;} sock.recv(ret); if(!ret){return false;}
std::string buf;
buf = "Subject: =?UTF-8?B?"+sstd::base64_encode(mail.subject)+"?=\r\n";
buf += "Mime-Version: 1.0;\r\n";
buf += "Content-Type: text/html; charset=\"UTF-8\";\r\n";
buf += "Content-Transfer-Encoding: 7bit;\r\n";
buf += "\r\n";
buf += mail.data+"\r\n";
buf += ".\r\n";
if( sock.send(buf )<=0){return false;} sock.recv(ret); if(!ret){return false;}
return true;
}
bool sendMail_of_HTML_withPrint(struct sMail& mail){ 〜 省略 〜 }
Implementation
上記実装を用いて,実際に Email を送信するコードを示す.
./main.cpp
./main.cpp
#include <sstd/sstd.hpp>
#include "./sstd_socket/sendMail.hpp"
int main(){
struct sMail mail;
mail.usr = "usrName";
mail.domain = "gmail.com";
mail.pass = "passWord";
mail.to = "usrName@domain";
mail.subject = "TESTING_NOW_テスト投稿_マルチバイトコードも自由自在!!";
{
mail.data = "This is a test of plain text mail.\r\n";
mail.data += "\r\n";
mail.data += "これは,プレーンテキストメールの送信テストです.<br/>\r\n";
mail.data += "■■■ <b><u>タイトル</u></b> ■■■<br/>\r\n";
mail.data += "<ul>\r\n";
mail.data += "<li>項目 1. </li>\r\n";
mail.data += "<li>項目 2. </li>\r\n";
mail.data += "</ul>\r\n";
mail.data += "<br/>\r\n";
mail.data += "<hr/>\r\n";
mail.data += "<br/>\r\n";
mail.data += "abc あいう<br/>\r\n";
mail.data += "<br/>\r\n";
if(!sendMail (mail)){ sstd::pdbg("ERROR: sendMail() was failed.\n"); }
// if(!sendMail_withPrint(mail)){ sstd::pdbg("ERROR: sendMail_withPrint() was failed.\n"); }
}
{
mail.data = "This is a test of HTML mail.<br/>";
mail.data = "<br/>";
mail.data += "これは,HTML メールの送信テストです.<br/>";
mail.data += "■■■ <b><u>タイトル</u></b> ■■■<br/>";
mail.data += "<ul>";
mail.data += "<li>項目 1. </li>";
mail.data += "<li>項目 2. </li>";
mail.data += "</ul>";
mail.data += "<br/>";
mail.data += "<hr/>";
mail.data += "<br/>";
mail.data += "abc あいう<br/>";
mail.data += "<br/>";
// if(!sendMail_of_HTML (mail)){ sstd::pdbg("ERROR: sendMail_of_HTML() was failed.\n"); }
if(!sendMail_of_HTML_withPrint(mail)){ sstd::pdbg("ERROR: sendMail_of_HTML_withPrint() was failed.\n"); }
}
return 0;
}
Results
実行環境
実行結果
受信トレイの様子
メール本文 (plain text mail)
メール本文 (HTML mail)
・Ubuntu 16.04 LTS
実行結果
$ ./exe 220 smtp.gmail.com ESMTP XXXXXXXXXXXXXXXXXXX - gsmtp Sending Data >> EHLO localhost 250-smtp.gmail.com at your service, [XXX.XXX.XXX.XXX] 250-SIZE 35882577 250-8BITMIME 250-AUTH LOGIN PLAIN XOAUTH2 PLAIN-CLIENTTOKEN OAUTHBEARER XOAUTH 250-ENHANCEDSTATUSCODES 250-PIPELINING 250-CHUNKING 250 SMTPUTF8 Sending Data >> AUTH LOGIN 334 VXNlcm5hbWU6 Sending Data >> [base64Encoded_usrName] 334 UGFzc3dvcmQ6 Sending Data >> [base64Encoded_passWord] 235 2.7.0 Accepted Sending Data >> MAIL FROM: <[usrName]@[domain]> 250 2.1.0 OK XXXXXXXXXXXXXXXXXXX - gsmtp Sending Data >> RCPT TO: <[mail.to]> 250 2.1.5 OK XXXXXXXXXXXXXXXXXXX - gsmtp Sending Data >> DATA 354 Go ahead XXXXXXXXXXXXXXXXXXX - gsmtp Sending Data >> Subject: =?UTF-8?B?VEVTVElOR19OT1df44OG44K544OI5oqV56i/X+ODnuODq+ODgeODkOOCpOODiOOCs+ODvOODieOCguiHqueUseiHquWcqCEh?= Mime-Version: 1.0; Content-Type: text/html; charset="UTF-8"; Content-Transfer-Encoding: 7bit; <br/>これは,HTML メールの送信テストです.<br/>■■■ <b><u>タイトル</u></b> ■■■<br/><ul><li>項目 1. </li><li>項目 2. </li></ul><br/><hr/><br/>abc あいう<br/><br/> . 250 2.0.0 OK 1522244454 XXXXXXXXXXXXXXXXXXX - gsmtp
受信トレイの様子
メール本文 (plain text mail)
メール本文 (HTML mail)
Summary
plain text 及び HTML 形式のメールを送信する C++ コードを開発した.
Future work
添付ファイルに対応する.
Appendix
本投稿で使用したコード全体を,下記の URL に置いておく.UNIX 環境であれば,make するだけで,実験できる.現状では openssl-1.0.2n.tar.gz を同封しているが,古くなった場合は,新しい openssl と置き換える必要がある.ファイル名が "openssl*.tar.gz" であれば,makefile の設定により自動で展開しコンパイルされる.



0 件のコメント:
コメントを投稿