프로그래밍/메일서버 만들기

1. SMTP (Simple Mail Transfer Protocol) 원리 이해 / Telnet SMTP 테스트 메일 전송

리블스톤 2025. 4. 10. 19:00
Intro


개인 공부 차원에서 SMTP 메일서버를 하나 제작해보려고 한다. 보안이 적용되지 않는 심플한 메일서버는 원리 이해와 Telnet을 사용하면 즉시 구현이 가능하므로, 여기서는 SMTP를 이용한 웹메일 서비스 및 POP3, IMAP을 지원하는 메일서버를 만들어 보려고 한다.

 

언어: Python(3.x) 또는 Rust

 

SMTP란?

 

이미 넷상에서 많이 찾아볼 수 있는 개념이지만, 공부를 위해 스스로 정리해 보자면, Simple Mail Transfer Protocol이다. Protocol이라는 개념에서 볼 수 있다시피, 메일 전송을 위한 일종의 규약이다. Simple이기에, 단순한 텍스트 기반 프로토콜이므로, 만일 비디오나 오디오 등의 파일 전송 등의 프로토콜은 ESMTP(Extended SMTP)라는 프로토콜을 사용한다.

 

규약이 중요한 이유는 간단하다. 우리가 이 신호를 받아서 읽을 수 있는 형태로 변환하려면 우선 어떤 식으로 메일을 보내야 할 지를 정해야 하기 때문이겠다.

 

그렇다면 이 프로토콜의 내용물을 살펴보자.

 

1. 이 프로토콜은 일반적으로 25/tcp, 587/tcp 포트를 사용한다. 예외적으로 SMTPS(SMTP Secure)은 465/tcp 포트를 사용한다.

2. 구성요소는 기본적으로 3개의 헤더(?)로 이루어져 있다. MAIL, RCPT, DATA. 이 요소를 어떻게 사용하는지는 이제 실전으로 들어가서 다루어보도록 하자.

 

Telnet

백문이 불여일견. 필자는 우선 Telnet이라는 것을 통해 SMTP를 시도해 볼 생각이다. 다만 일반적인 메일서버는 스팸 필터링을 위한 검증 절차가 있으므로, Telnet같은 보안에 취약한 도구로 통신을 시도하면 차단당할 가능성이 높더라.

 

필자는 내부 서버로 라즈베리파이 5 8gb RAM 모델을 사용중이다. Raspberry pi OS라는 운영체제인데, debian 운영체제 기반이라 debian linux 명령어를 사용한다.

 

raspberrypi OS에서 telnet을 설치해 보자.

sudo apt-get update
sudo apt-get install telnet -y

 

지체없이 telnet을 들어가보....기 전에, 우선 소개해야 할 웹사이트가 하나 있다.

상기했듯이, 기존의 상용 메일서버는 telnet을 통한 SMTP를 좋아하지 않는다. 암호화가 안 되어있는 상태라서 그런가, 아니면 앞서 말한 것처럼 알 수 없는 소스로부터의 요청이라서 그런가, 암튼 무언가의 이유 때문에 막아놓는 것 같다.

 

때문에 필자는 tempmail.org 이라는 서비스를 이용해 볼 것이다. 이 서비스는 임의의 메일 주소를 생성해 사용할 수 있게 해 준다. 단순 접속 후 잠시 기다리면 아래와 같은 창에 내가 쓸 임시 메일이 생성된다.

Tempmail.org 사이트의 정경.

 

 

내 임시 메일주소는 sivej69040@exclussi.com이다. 따라서 우리는 exclussi.com이라는 도메인에 메일로 통신을 시도해 볼 것이다.

telnet <도메인 주소> <포트>
> telnet exclussi.com 25

실패작

 

첫 번째 시도는 당연하게도 실패. 이는 필자가 도메인에 냅다 통신을 걸어버려서 그렇다. 일반적으로 메일 통신을 위한 서브도메인이 따로 있다. nslookup을 통해 메일서버를 탐색해보자.

nslookup
set type=mx
exclussi.com

 

set type=mx로 메일서버를 탐색하고, 도메인을 입력한다.

nslookup으로 mx 서브도메인을 탐색하는 모습

서브도메인을 찾았다. 보통은 mx1, mx2 같은 형식으로 서브도메인을 운영중이지만, 이곳은 단순하게 mail.exclussi.com. 으로 운영되는 것으로 보아 그렇게 많은 메일서버를 운영중이진 않는 것으로 보인다. (아니면 임시도메인이라 그럴지도.)

 

찾아낸 도메인으로 다시 통신을 시도해보자.

mx 도메인으로 통신을 성공한 모습.

성공이다.

그렇다면, 이제 메일을 보내 볼 차례이다.

 

우선 HELO를 통해 내가 어디서 왔는지를 알려보자.

HELO leeblog.xyz

필자가 가진 leeblog.xyz라는 도메인이 있으므로, 이를 이용해 내 정보를 넘겨준다. 여담이지만, 해당 정보는 임의로 조작이 가능하므로, 굳이 신뢰할 필요는 없다.

 

그 후, MAIL FROM, RCPT TO, DATA 등을 통해, 내가 보낼 메일을 구성한다. 구성은 다음과 같다.

MAIL FROM: <My mail address>
RCPT TO: <recipient mail address>
DATA
<Contents>
.

여기서 DATA는 실제 메일을 보낼 내용의 스타트를 끊는다. 이후에 작성되는 내용은 "."을 단독으로 사용해서 끝내기 전 까지 포함된다. 즉, DATA ~~~ . 으로 랩핑이 된다고 볼 수 있다.

DATA 내부에서는 제목과 수신인, 참조인등을 구분하기 위해 세부적인 방법을 사용한다. 이에 대한 규약이 있는지는 모르겠으나, 이 tempmail에서는 Subject, From, To 등의 구문으로 구분해 주는 것 같다.

 

아래는 내가 통신을 시도한 내용이다.

 

HELO leeblog.xyz
MAIL FROM: leeblestone@leeblog.xyz
RCPT TO: sivej69040@exclussi.com
DATA
From: "leeblestone" <leeblestone@leeblog.xyz>
To: "exclussi" <sivej69040@exclussi.com>
Subject: Another Test

Hi there
This is Leeblestone!
Nice to meet you

.

 

Telnet 통신 내역 콜론을 빠뜨려서 간혹 501 에러가 보인다.

 

아래는 통신 결과물이다. tempmail의 메일함에서 확인할 수 있다.

 

결론

 

우선 이것저것 건드리며 테스트를 해 보아야겠지만, 평문화 된 통신에 관해서는 코드로 구현하는 게 크게 어려워 보이진 않는다. 구분자와 헤더를 적절하게 구분하여 정규식으로 처리가 가능한 수준이므로, 해당 통신에서 중요하게 구현해야 할 건 아래와 같이 구분될 듯 하다.

 

1. 암호화 통신 규약

2. 암호화를 시켜 DB로 저장해보기

3. queue가 많아지면, 이에 대한 순서를 정렬하는 방법.

 

정도일 것 같다.