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 件のコメント:
コメントを投稿