🧼C, C++/네트워크

c/c++ 메일 전송(smtp프로토콜) | 이론과 실습

Mawile 2021. 12. 25.
728x90

🎄 개요

 안녕하세요! 오랜만에 c++ 네트워크글이 돌아왔습니다...

이번에는 저가 엄청나게 유익한 내용을 들고왔는데요..

 

흔히 여러분들중에서 키로거사용경험이 있으신분들은 smtp를 이용한 메일전송을 많이보셨을겁니다.

이번 포스팅에서는 c++환경에서 아무 상용라이브러리의 도움없이 smtp프로토콜을 이용하여,

메일을 전송하는 방법에 관하여 토론하는 시간을 가져보도록 하겠습니다.

 

 

🎄 목차

- SMTP 프로토콜이란?

- SMTP 프로토콜의 내부구조

- 구글의 SMTP 서버 이용하기

- C++20을 이용한 메일전송 프로그램 만들기 (Unauthorized id[:비검증된 계정])

- 나만의 SMTP 서버 만들기

- C++20을 이용한 메일전송 프로그램 만들기 (Authorized id[:검증된 계정])

- 마치며...

 

 

🖲️ SMTP 프로토콜이란?

인터넷에서 이메일을 보내기 위해 이용되는 프로토콜이다. 사용하는 TCP 포트번호는 25번이다. 상대 서버를 지시하기 위해서 DNS의 MX레코드가 사용된다. RFC2821에 따라 규정되어 있다. 메일 서버간의 송수신뿐만 아니라, 메일 클라이언트에서 메일 서버로 메일을 보낼 때에도 사용되는 경우가 많다.

 

SMTP는 텍스트 기반의 프로토콜로서 요구/응답 메시지뿐 아니라 모든 문자가 7bit ASCII로 되어있어야 한다고 규정되어 있다. 이 때문에 문자 표현에 8비트 이상의 코드를 사용하는 언어나 첨부파일과 자주 사용되는 각종 바이너리는 마임(MIME)이라고 불리는 방식으로 7비트로 변환되어 전달된다.

 

SMTP는 메시지를 생성하는 방법을 규정하지 않는다. 메시지 생성을 위하여 로컬 편집이나 단순한 전자 우편 응용이 사용된다. 메시지가 생성되면 호출된 SMTP가 메시지를 받고 TCP를 이용하여 다른 호스트의 SMTP에게 전달한다.

(위키피디아 참조)

 

🖲️ SMTP 프로토콜의 내부구조

사실 smtp의 패킷교환을 까보면은 생각보다 어려운점은 없습니다.

다음은 smtp의 패킷교환 내용의 예입니다.

S가 서버(수신자) 이고, C가 클라이언트(송신자) 입니다.

예시 1.
S : 220 www.example.com ESMTP Postfix
C : HELO mydomain.com
S : 250 Hello mydomain.com
C : MAIL FROM: <sender@mydomain.com>
S : 250 ok
C : RCPT TO: <friend@example.com>
S : 250 ok
C : DATA
S : 354 End data with <CR><LF>.<CR><LF>
C : Subject: test message
C : From: sender@mydomain.com
C : To: friend@example.com
C :
C : Hello,
C : This is a test
C : Goodbye.
C : .
S : 250 Ok: queued as 12345
C : quit
S : 221 Bye

 

예시 2.
S: 220 smtp.example.com ESMTP Postfix
C: HELO relay.example.com
S: 250 smtp.example.com, I am glad to meet you
C: MAIL FROM:<bob@example.com>
S: 250 Ok
C: RCPT TO:<alice@example.com>
S: 250 Ok
C: RCPT TO:<theboss@example.com>
S: 250 Ok
C: DATA
S: 354 End data with <CR><LF>.<CR><LF>
C: From: "Bob Example" <bob@example.com>
C: To: Alice Example <alice@example.com>
C: Cc: theboss@example.com
C: Date: Tue, 15 January 2008 16:02:43 -0500
C: Subject: Test message
C: 
C: Hello Alice.
C: This is a test message with 5 header fields and 4 lines in the message body.
C: Your friend,
C: Bob
C: .
S: 250 Ok: queued as 12345
C: QUIT
S: 221 Bye

 

흠.. 예시를 보고나니 smtp가 성립되는 과정이 감잡히시나요?

클라이언트쪽에서 서버쪽으로 특정 명령어패킷을 날리면 서버는 해당 명령어패킷을 받고 분석한뒤, 다시 클라이언트로 패킷을 날려서 그에대한 반응(response)을 알려줍니다.

 

예를들어서 클라이언트측에서 "mail from: <메일전송자의 이메일주소>" 라는 패킷을 보내면, 서버에서는 해당 패킷을 받고, "메일전송자의 이메일주소를 통해 주소반환확립을 하라는거군!" 이라고 알아듣는겁니다.

 

이때 mail, helo ,ehlo, rcpt, data 등등의 코드를 우리는 SMTP reply code 라고합니다.

저가 이러한 SMTP reply code 와 서버가 잘못된 입력패킷을 받았을때 뱉어내는 오류코드들은 아래 사이트에서 확인해주시기 바랍니다. (해당사이트 내용이 워낙 정리가 잘되어있어서 저도 html 로컬파일로 다운로드하고 소장중 ㅎㅎ)

https://www.greenend.org.uk/rjk/tech/smtpreplies.html

 

SMTP reply codes

252 Cannot VRFY user, but will accept message and attempt delivery

www.greenend.org.uk

 

이제부터 우리가 먼저 알아볼 smtp의 패킷교환의 흐름은 다음과 같습니다.

ehlo\r\n
(만약 smtp서버가 auth login을 지원한다면)
auth login\r\n
전송자id(base64암호화)\r\n
전송자비밀번호(base64암호화)\r\n
(아니라면)
mail from: <전송자이메일>\r\n
rcpt to: <수신자이메일>\r\n
data\r\n
Subject:제목\r\n\r\n
내용\r\n
.\r\n
quit\r\n

 

자.. 이제 smtp의 흐름은 알아보았습니다...

만약에 우리가 smtp클라이언트를 완성했다고해도 메일서버가 없으면 아무것도못합니다.

이제 간단하게 구글의 smtp서버를 열어보겠습니다!

 

 

🖲️ 구글의 SMTP 서버 이용하기

먼저 구글을 이용해서 smtp서버를 이용하기 위해서는 gmail로 들어가주시면 됩니다.

gmail로 들어가시면은 위에 톱니바퀴를 클릭한후, 모든 설정 보기를 클릭해줍니다.

 

그다음 POP 활성화IMAP 사용 을 클릭한후, 변경사항 저장을 눌러서 맞추어줍니다.

구글의 smtp서버를 사용할 준비가 되었습니다.

이제 C++20을 통해 실제 smtp프로토콜을 이용한 나만의 메일전송클래스를 만들어보도록 하겠습니다.

 

🖲️ C++20을 이용한 메일전송 프로그램 만들기 (Unauthorized id[:비검증된 계정])

MySMTPClient.h
#pragma once
#pragma comment(lib, "ws2_32")
#pragma warning(disable:4996)

#include <iostream>
#include <fstream>

#include <WinSock2.h>
#include <WS2tcpip.h>
#include <Windows.h>

namespace Mawi1e {
	class MySMTPClient {
	public:
		MySMTPClient();

		void SetServerPort(const int);
		void SetSMTPServer(const std::string&);
		void SetDNSAddress(const std::string&);
		void SetEmailTo(const std::string&);
		void SetEmailFrom(const std::string&);
		void SetSubject(const std::string&);
		void SetMessage(const std::string&);

		void Transport();

	private:
		SOCKET Connect();
		void Close(SOCKET);

	private:
		std::string m_SMTPServer, m_DNSAddress, m_EmailTo, m_EmailFrom, m_Subject, m_Message;
		int m_ServerPort;

	};
}

이제 우리가 SMTP프로토콜을 이용하여 메일을 전송할 클래스입니다.

실제 패킷전송은 Transport함수에서 이루어집니다.

 

MySMTPClient.cpp
#include "MySMTPClient.h"

namespace Mawi1e {
	MySMTPClient::MySMTPClient() { }

	void MySMTPClient::SetServerPort(const int serverPort) { m_ServerPort = serverPort; }
	void MySMTPClient::SetSMTPServer(const std::string& smtpServer) { m_SMTPServer = smtpServer; }
	void MySMTPClient::SetDNSAddress(const std::string& dnsAddress) { m_DNSAddress = dnsAddress; }
	void MySMTPClient::SetEmailTo(const std::string& mailTo) { m_EmailTo = mailTo; }
	void MySMTPClient::SetEmailFrom(const std::string& mailFrom) { m_EmailFrom = mailFrom; }
	void MySMTPClient::SetSubject(const std::string& subject) { m_Subject = subject; }
	void MySMTPClient::SetMessage(const std::string& message) { m_Message = message; }

	void MySMTPClient::Transport() {
		/** --------------------------------------------------------------------------------
		[                                  smtp 패킷전송                                   ]
		-------------------------------------------------------------------------------- **/


		std::ofstream report;
		SOCKET servSocket;
		char recvBuffer[0x200], sendBuffer[0x200];
		int recvBytes;

		/** --------------------------------------------------------------------------------
		[                                   패킷내용기록                                   ]
		-------------------------------------------------------------------------------- **/
		report.open("SMTP.txt");
		if (report.fail()) { throw std::runtime_error("SMTP.txt failed."); }
		if (!report.is_open()) { throw std::runtime_error("SMTP.txt can not open the file."); }

		servSocket = Connect();

		/** --------------------------------------------------------------------------------
		[                                      ehlo                                        ]
		-------------------------------------------------------------------------------- **/
		recvBytes = recv(servSocket, recvBuffer, sizeof(recvBuffer), 0);
		recvBuffer[recvBytes] = '\0';
		report << recvBuffer;
		sprintf(sendBuffer, "ehlo %s\r\n", m_DNSAddress.c_str());
		report << sendBuffer;
		send(servSocket, sendBuffer, (int)strlen(sendBuffer), 0);

		/** --------------------------------------------------------------------------------
		[                                      mail                                        ]
		-------------------------------------------------------------------------------- **/
		recvBytes = recv(servSocket, recvBuffer, sizeof(recvBuffer), 0);
		recvBuffer[recvBytes] = '\0';
		report << recvBuffer;
		sprintf(sendBuffer, "mail from:<%s>\r\n", m_EmailFrom.c_str());
		report << sendBuffer;
		send(servSocket, sendBuffer, (int)strlen(sendBuffer), 0);

		/** --------------------------------------------------------------------------------
		[                                      rcpt                                        ]
		-------------------------------------------------------------------------------- **/
		recvBytes = recv(servSocket, recvBuffer, sizeof(recvBuffer), 0);
		recvBuffer[recvBytes] = '\0';
		report << recvBuffer;
		sprintf(sendBuffer, "rcpt to:<%s>\r\n", m_EmailTo.c_str());
		report << sendBuffer;
		send(servSocket, sendBuffer, (int)strlen(sendBuffer), 0);

		/** --------------------------------------------------------------------------------
		[                                      data                                        ]
		-------------------------------------------------------------------------------- **/
		recvBytes = recv(servSocket, recvBuffer, sizeof(recvBuffer), 0);
		recvBuffer[recvBytes] = '\0';
		report << recvBuffer;
		sprintf(sendBuffer, "data\r\n");
		report << sendBuffer;
		send(servSocket, sendBuffer, (int)strlen(sendBuffer), 0);

		/** --------------------------------------------------------------------------------
		[                                     subject                                      ]
		-------------------------------------------------------------------------------- **/
		recvBytes = recv(servSocket, recvBuffer, sizeof(recvBuffer), 0);
		recvBuffer[recvBytes] = '\0';
		report << recvBuffer;
		sprintf(sendBuffer, "To:%s\nFrom:%s\nSubject:%s\r\n\r\n%s\r\n.\r\n",
			m_EmailTo.c_str(), m_EmailFrom.c_str(), m_Subject.c_str(), m_Message.c_str());
		report << sendBuffer;
		send(servSocket, sendBuffer, (int)strlen(sendBuffer), 0);

		/** --------------------------------------------------------------------------------
		[                                      quit                                        ]
		-------------------------------------------------------------------------------- **/
		recvBytes = recv(servSocket, recvBuffer, sizeof(recvBuffer), 0);
		recvBuffer[recvBytes] = '\0';
		report << recvBuffer;
		sprintf(sendBuffer, "quit\r\n");
		report << sendBuffer;
		send(servSocket, sendBuffer, (int)strlen(sendBuffer), 0);


		recvBytes = recv(servSocket, recvBuffer, sizeof(recvBuffer), 0);
		recvBuffer[recvBytes] = '\0';
		report << recvBuffer;

		Close(servSocket);

		report.close();
	}

	SOCKET MySMTPClient::Connect() {
		/** --------------------------------------------------------------------------------
		[                                    서버 접속                                     ]
		-------------------------------------------------------------------------------- **/


		WSADATA wsa;
		SOCKET servSocket;
		SOCKADDR_IN servAddrIn;
		hostent* host;
		int result;

		result = WSAStartup(MAKEWORD(2, 2), &wsa);
		if (result != 0) {
			throw std::runtime_error("WSAStartup");
		}

		servSocket = socket(PF_INET, SOCK_STREAM, 0);
		if (servSocket == INVALID_SOCKET) {
			throw std::runtime_error("socket");
		}

		memset(&servAddrIn, 0x00, sizeof(servAddrIn));

		host = gethostbyname(m_SMTPServer.c_str());
		if (host == NULL)
		{
			throw std::runtime_error("gethostbyname");
		}

		memcpy(&(servAddrIn.sin_addr), host->h_addr, host->h_length);
		servAddrIn.sin_family = host->h_addrtype;
		servAddrIn.sin_port = htons(m_ServerPort);

		result = connect(servSocket, (SOCKADDR*)&servAddrIn, sizeof(servAddrIn));
		if (result == INVALID_SOCKET) {
			throw std::runtime_error("connect");
		}

		return servSocket;
	}

	void MySMTPClient::Close(SOCKET servSocket) {
		closesocket(servSocket);
		WSACleanup();
	}
}

저가 중간중간에 주석을 달아놓았습니다.

솔직히 TCP/IP 공부해보신분들은 아시겠지만,

진짜 TCP프로토콜과 사용법이 거의 중첩되다보니 설명할것이 크게 없네요..

혹시 궁금한부분이 있으시다면 댓글로 질문주세요!

 

main.cpp
#include "MySMTPClient.h"

int main() {
	try {
		Mawi1e::MySMTPClient smtpServer;

		smtpServer.SetDNSAddress("google.co.kr");
		smtpServer.SetEmailFrom("송신자이메일@gmail.com");
		smtpServer.SetEmailTo("수신자이메일@gmail.com");
		smtpServer.SetMessage("Hello There~ I am THE MAWILE ~!");
		smtpServer.SetServerPort(25);
		smtpServer.SetSMTPServer("smtp.google.com");
		smtpServer.SetSubject("Mawi1e's Test!");

		smtpServer.Transport();
	}
	catch (const std::exception& e) {
		MessageBoxA(0, e.what(), "", MB_OK);
	}
}

이제 해당 프로그램을 컴파일하고 실행시켰을때, 패킷로그를 기록한 SMTP.txt 파일의 내용을 확인해보겠습니다.

 

실행화면
[SMTP.txt]

220 mx.google.com ESMTP (보안상생략) - gsmtp
ehlo google.co.kr
250-mx.google.com at your service, [(보안상생략)]
250-SIZE 157286400
250-8BITMIME
250-STARTTLS
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-CHUNKING
250 SMTPUTF8
mail from:<(보안상생략)>
250 2.1.0 OK (보안상생략) - gsmtp
rcpt to:<(보안상생략)>
250 2.1.5 OK (보안상생략) - gsmtp
data
354  Go ahead (보안상생략) - gsmtp
To:(보안상생략)
From:(보안상생략)
Subject:Mawi1e's Test!

Hello There~ I am THE MAWILE ~!
.
250 2.0.0 OK  (보안상생략) - gsmtp
quit
221 2.0.0 closing connection (보안상생략) - gsmtp

와우! 모두 error code가 뜨지않고 잘 전송되었습니다.

이제 실제 gmail로 가서 확인해보죠!

와우....

저는 스팸함에 들어있었습니다.

 

흠... 근데 위에 "주의해야 할 메일입니다." 라는 메세지가 매우 거슬립니다.

해당 메세지가 뜨는이유는 발송된 이메일이 검증된 이메일이 아니기때문입니다.

이메일을 검증해주려면 smtp패킷교환단계에서 auth login이라는 명령을 서버쪽으로 보내야하지만, 저가 이번에 이용한 구글서버에는 없더라구요. (여러분도 직접 확인이 가능하십니다. SMTP.txt의 내용을 확인해보세요.)

250-SIZE 157286400
250-8BITMIME
250-STARTTLS
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-CHUNKING
250 SMTPUTF8

 

그래서 저는 저만의 SMTP 서버를 만들어보려고 합니다.

 

🖲️ 나만의 SMTP 서버 만들기

 

우선 Windows Features 를 들어가신후, 인터넷 정보 서비스에 가서 위와같이 체크해줍니다.

체크해주면 프로그램이 깔리겠죠? 다 깔릴때까지 기다려주세요.

 

 

그러면 IIS 관리자에 "SMTP 전자 메일" 이라는 항목이 생겼을겁니다. 들어가줍니다.

 

 

그리고, 저는 그냥 호스트는 localhost로 했습니다.

포트는 반드시 25로 해주셔야하구요. 인증 설정은 Windows를 선택하신후, 적용을 눌러줍니다.

 

이제 MailEnable을 설치해주세요!

MailEnable 설치

http://www.mailenable.com/download.asp

 

Download | MailEnable

Adds collaboration, sharing and enterprise features (like clustering, database connectivity, remote administration, etc).

www.mailenable.com

 

저는 설치가 다되서 안뜨는데, 그냥 다 설치&Next해주시구요, 어떤 입력칸들이 뜰텐데 다음과 같습니다.

Name 자신의 이메일
Company 아무거나
Post Office Name 자신의 메일서버 아이디(*중요)
Password 자신의 메일서버 비밀번호(*중요)
Domain Name 아까 IIS 관리자에서 여러분이 서술하신 SMTP 서버주소를 입력합니다..(저는 localhost로 했습니다.)
DNS Host(s) 그냥 그대로 놔두세요.
SMTP Port 아까 IIS 관리자에서 여러분이 서술하신 SMTP 서버포트를 입력합니다..(저는 25로 했습니다.)

이제 제어판으로 가봅시다!

 

제어판 -> 시스템 및 보안 -> Windows 방화벽 -> 고급 설정 -> 인바운드 규칙로 들어가신다음,

새규칙 -> 포트 -> 특정 로컬 포트(아까 설정한 포트번호:25) -> 연결허용 -> 다 체크(도메인, 개인, 공용) -> 이름, 설명 -> 마침

 

이제 제어판설정까지 끝나셨다면 cmd로 smtp서버가 25번포트로 열려있는지 확인해봅시다!

netstat -ano | find ":25"

굿!! 25번포트로 서버가 열려있습니다.

 

MailEnable 세팅

MailEnable Management -> Messageing Manager -> Post Offices -> (자기아이디:저는 귀찮아서 DEFAULT로했습니다.)

-> Mailboxes -> New Mailbox.

 

위와 같이 눌러주시면은 이러한 대화창이 뜰것입니다.

 

Mailbox TypeADMIN으로 해주시구요. 자신의 아이디, 비밀번호를 입력한뒤 OK를 눌러줍니다.

 

 

속성 -> Web Admin 으로 들어가신뒤 모두 체크해주세요.

 

MailEnable 어드민계정 로그인

아래링크로가서 로그인해주세요.

http://localhost/MEWebMail

 

 

흠... 굿!!! 아주 잘되네요!!

저의 서버가 완성되었습니다!!

 

이제 저의 서버주소는 다음과같이 설정될겁니다.

이름@localhost
저같은 경우는 -> DEFAULT@localhost

 

 

🖲️ C++20을 이용한 메일전송 프로그램 만들기 (Authorized id[:검증된 계정])

자.... 이제 저의 서버가 만들어졌습니다.

일단 검증된계정을 통한 메일전송 프로그램은 아까 만들었던 비검증된 계정을 통해 만들었던 메일전송 프로그램에서 이어서 시작할겁니다.

 

MySMTPClient.h
namespace Mawi1e {
	class MySMTPClient {
	public:
		MySMTPClient();

		void SetServerPort(const int);
		void SetSMTPServer(const std::string&);
		void SetDNSAddress(const std::string&);
		void SetEmailTo(const std::string&);
		void SetEmailFrom(const std::string&);
		void SetSubject(const std::string&);
		void SetMessage(const std::string&);

		void SetID(const std::string&);
		void SetPassword(const std::string&);

		void Transport();

	private:
		SOCKET Connect();
		void Close(SOCKET);
		std::string Base64Encoding(const std::string&);

	private:
		std::string m_SMTPServer, m_DNSAddress, m_EmailTo, m_EmailFrom, m_Subject, m_Message;
		std::string m_Password, m_ID;
		int m_ServerPort;

	};
}

 

우선 크게 달라진점은 3개의 함수와 2개의 변수가 추가되었습니다.

자신의 ID, 비밀번호를 지정하는 함수와 base64인코딩을 해주는 함수입니다.

 

base64인코딩이 왜 필요하지?

smtp 패킷교류를 할때 auth 라고해서 자신의 이메일인지 입증해야하는 절차가 있습니다.

그때 필요합니다.

 

MySMTPClient.cpp
void MySMTPClient::SetID(const std::string& password) { m_Password = password; }
void MySMTPClient::SetPassword(const std::string& id) { m_ID = id; }

우선 위 2함수를 추가시켜주세요.

 

std::string MySMTPClient::Base64Encoding(const std::string& in) {
	const std::string BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	std::string output;
	int value, value_b;

	value = 0; value_b = -6;
	for (unsigned char i : in) {
		value = (value << 8) + i;
		value_b += 8;

		while (value_b >= 0) {
			output.push_back(BASE64[(value >> value_b) & 0x3F]);
			value_b -= 6;
		}
	}

	if (value_b > -6) {
		output.push_back(BASE64[((value << 8) >> (value_b + 8)) & 0x3F]);
	}

	while (output.size() % 4) {
		output.push_back('=');
	}

	return output;
}

Base64인코딩을 하는과정은

우선 24비트 버퍼에 위쪽부터 한 바이트씩 세 바이트를 집어넣습니다.

그리고 남은 바이트가 3바이트 미만이라면,

버퍼의 남은 부분은 0으로 채워넣게 됩니다. 그리고, 버퍼의 위쪽부터 6비트씩 잘라 그 값을 읽어,

다음에 정렬된 64개 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"의 문자 중에서 읽은 값 번째 문자를 골라 출력합니다.

 

만약 입력된 바이트가 하나라면 출력 중 두 개만이 사용되고 나머지 둘은 "="으로 패딩되며, 입력된 바이트가 둘이라면 출력 중 세 개 만이 사용되고 나머지 하나는 "="으로 패딩되게 됩니다.

이것은 원본으로 되돌릴 때 원본에는 없던 비트가 생기는 것을 방지하기 위함입니다. 이 과정을 입력 데이터가 끝날 때까지 반복하면 인코딩이 됩니다. (위키피디아 참조)

 

void MySMTPClient::Transport() {
	/** --------------------------------------------------------------------------------
	[                                  smtp 패킷전송                                   ]
	-------------------------------------------------------------------------------- **/


	std::ofstream report;
	SOCKET servSocket;
	char recvBuffer[0x200], sendBuffer[0x200];
	int recvBytes;

	/** --------------------------------------------------------------------------------
	[                                   패킷내용기록                                   ]
	-------------------------------------------------------------------------------- **/
	report.open("SMTP.txt");
	if (report.fail()) { throw std::runtime_error("SMTP.txt failed."); }
	if (!report.is_open()) { throw std::runtime_error("SMTP.txt can not open the file."); }

	servSocket = Connect();

	/** --------------------------------------------------------------------------------
	[                                      ehlo                                        ]
	-------------------------------------------------------------------------------- **/
	recvBytes = recv(servSocket, recvBuffer, sizeof(recvBuffer), 0);
	recvBuffer[recvBytes] = '\0';
	report << recvBuffer;
	sprintf(sendBuffer, "ehlo %s\r\n", m_DNSAddress.c_str());
	report << sendBuffer;
	send(servSocket, sendBuffer, (int)strlen(sendBuffer), 0);

	/** --------------------------------------------------------------------------------
	[                                      auth                                        ]
	-------------------------------------------------------------------------------- **/
	recvBytes = recv(servSocket, recvBuffer, sizeof(recvBuffer), 0);
	recvBuffer[recvBytes] = '\0';
	report << recvBuffer;
	sprintf(sendBuffer, "auth login\r\n");
	report << sendBuffer;
	send(servSocket, sendBuffer, (int)strlen(sendBuffer), 0);

	recvBytes = recv(servSocket, recvBuffer, sizeof(recvBuffer), 0);
	recvBuffer[recvBytes] = '\0';
	report << recvBuffer;
	sprintf(sendBuffer, "%s\r\n", Base64Encoding(m_ID).c_str());
	report << sendBuffer;
	send(servSocket, sendBuffer, (int)strlen(sendBuffer), 0);

	recvBytes = recv(servSocket, recvBuffer, sizeof(recvBuffer), 0);
	recvBuffer[recvBytes] = '\0';
	report << recvBuffer;
	sprintf(sendBuffer, "%s\r\n", Base64Encoding(m_Password).c_str());
	report << sendBuffer;
	send(servSocket, sendBuffer, (int)strlen(sendBuffer), 0);

	/** --------------------------------------------------------------------------------
	[                                      mail                                        ]
	-------------------------------------------------------------------------------- **/
	recvBytes = recv(servSocket, recvBuffer, sizeof(recvBuffer), 0);
	recvBuffer[recvBytes] = '\0';
	report << recvBuffer;
	sprintf(sendBuffer, "mail from:<%s>\r\n", m_EmailFrom.c_str());
	report << sendBuffer;
	send(servSocket, sendBuffer, (int)strlen(sendBuffer), 0);

	/** --------------------------------------------------------------------------------
	[                                      rcpt                                        ]
	-------------------------------------------------------------------------------- **/
	recvBytes = recv(servSocket, recvBuffer, sizeof(recvBuffer), 0);
	recvBuffer[recvBytes] = '\0';
	report << recvBuffer;
	sprintf(sendBuffer, "rcpt to:<%s>\r\n", m_EmailTo.c_str());
	report << sendBuffer;
	send(servSocket, sendBuffer, (int)strlen(sendBuffer), 0);

	/** --------------------------------------------------------------------------------
	[                                      data                                        ]
	-------------------------------------------------------------------------------- **/
	recvBytes = recv(servSocket, recvBuffer, sizeof(recvBuffer), 0);
	recvBuffer[recvBytes] = '\0';
	report << recvBuffer;
	sprintf(sendBuffer, "data\r\n");
	report << sendBuffer;
	send(servSocket, sendBuffer, (int)strlen(sendBuffer), 0);

	/** --------------------------------------------------------------------------------
	[                                     subject                                      ]
	-------------------------------------------------------------------------------- **/
	recvBytes = recv(servSocket, recvBuffer, sizeof(recvBuffer), 0);
	recvBuffer[recvBytes] = '\0';
	report << recvBuffer;
	sprintf(sendBuffer, "To:%s\nFrom:%s\nSubject:%s\r\n\r\n%s\r\n.\r\n",
		m_EmailTo.c_str(), m_EmailFrom.c_str(), m_Subject.c_str(), m_Message.c_str());
	report << sendBuffer;
	send(servSocket, sendBuffer, (int)strlen(sendBuffer), 0);

	/** --------------------------------------------------------------------------------
	[                                      quit                                        ]
	-------------------------------------------------------------------------------- **/
	recvBytes = recv(servSocket, recvBuffer, sizeof(recvBuffer), 0);
	recvBuffer[recvBytes] = '\0';
	report << recvBuffer;
	sprintf(sendBuffer, "quit\r\n");
	report << sendBuffer;
	send(servSocket, sendBuffer, (int)strlen(sendBuffer), 0);


	recvBytes = recv(servSocket, recvBuffer, sizeof(recvBuffer), 0);
	recvBuffer[recvBytes] = '\0';
	report << recvBuffer;

	Close(servSocket);

	report.close();
}

 

그다음, auth를 추가해줍니다.

auth에 대한 메세지패킷의 교환흐름은 다음과 같습니다.

ehlo -> auth => id(base64) -> password(base64) -> mail...

이제 마지막으로 main함수쪽을 변경해줍시다!

 

main.cpp
#include "MySMTPClient.h"

int main() {
	try {
		Mawi1e::MySMTPClient smtpServer;

		smtpServer.SetDNSAddress("");
		smtpServer.SetEmailFrom("DEFAULT@localhost");
		smtpServer.SetEmailTo("수신자이메일@gmail.com");
		smtpServer.SetMessage("Hello There~ I am THE MAWILE from my own server ~!");
		smtpServer.SetServerPort(25);
		smtpServer.SetSMTPServer("localhost");
		smtpServer.SetSubject("Mawi1e's Test from my own server!");

		smtpServer.SetID("DEFAULT");
		smtpServer.SetPassword("DEFAULT");

		smtpServer.Transport();
	}
	catch (const std::exception& e) {
		MessageBoxA(0, e.what(), "", MB_OK);
	}
}

저는 다음과 같이 적어주었지만,

EmailFrom이나 SMTPServer, ID, Password는 여러분이 정하신걸로 각자 하셔야합니다.

 

실행

실행해주면은??

 

우선 실행해주면 SMTP.txt 로그파일이 나왔습니다.

오류가 걸렸는지, 잘 작동하는지 확인해봅시다!

[SMTP.txt]

220 (---) ESMTP MailEnable Service, Version: 10.37-- ready at (---)
ehlo 
250-(---), this server offers 4 extensions
250-AUTH LOGIN
250-SIZE 40960000
250-HELP
250 AUTH=LOGIN
auth login
334 VXNlcm5hbWU6
REVGQVVMVA==
334 UGFzc3dvcmQ6
REVGQVVMVA==
235 Authenticated
mail from:<DEFAULT@localhost>
250 Requested mail action okay, completed
rcpt to:<(---)>
250 Requested mail action okay, completed
data
354 Start mail input; end with <CRLF>.<CRLF>
To:(---)
From:DEFAULT@localhost
Subject:Mawi1e's Test from my own server!

Hello There~ I am THE MAWILE from my own server ~!
.
250 Requested mail action okay, completed
quit
221 Service closing transmission channel

 

굿!! 아주잘 됩니다. 아무 error code없이 잘 전송이 되었습니다.

이제 수신자이메일로 가서 메일이 왔는지 확인해볼게요!

 

메일 도착!

 

깔끔하게 왔습니다.

전처럼 노란색메세지도 안뜹니다.. 굿...!

저는 스팸함에 도착했긴했는데, 확실히 인증된 이메일로 도착한걸 볼수가있죠..

 

 

🖲️ 마치며...

이번 시간에는 smtp프로토콜을 이용한 메일전송 프로그램을 a부터 z까지 아무라이브러리의 도움없이 직접 다 만들어보았습니다.

다음에는 http프로토콜에 관한 강좌를 써보까 생각중입니다.

 

사실 저가 http관련 글은 많은데 smtp관련해서 명확한 글을 찾기가 힘들었습니다. 대부분 외부라이브러리사용이나 파이썬이 많더라구요. 그래서 저는 윈속2만 써서 a부터 z까지 직접 다 만들어보고 싶었습니다.

궁금한 부분이 있으시면 댓글로 질문주세요!

 

그럼 안녕~!!~!!

 


728x90

댓글