Фейковый SMTP-сервер на bash

Фейковый SMTP-сервер на bashДля чего вообще нужен фейковый SMTP-сервер? Конечно же, для разработки. Когда вы разрабатываете веб-приложение, вам, возможно, надо будет отправлять какую-то электронную почту. Например, со ссылкой на подтверждение регистрации нового пользователя. И использовать для этого реальные адреса может быть проблематично, если не используется полноценный почтовый сервер, так как почта, отправленная с сервера, на котором ведется разработка, или с виртуальной машины, доставлена скорее всего не будет. К тому же, если при отправке формируются реальные адреса, скажем, из базы, то отправлять наружу такие письма никак нельзя. Эту проблему можно решить достаточно просто.

Для этого нам может пригодиться почтовый сервер, который никуда отправлять почту не будет, он будет работать как нормальный почтовый сервер, но фактически будет складывать отправленные через него письма в директорию в виде файлов, либо просто выводить на экран. Реализовать его можно, например, на bash.

Требования

Каким требованиям должен удовлетворять такой сервер:

  • Слушать порт 25/tcp, на котором работает обычно SMTP-сервер
  • Принимать само письмо и записывать его в файл, либо на терминал
  • Поддерживать команды протокола SMTP

В принципе, этого будет достаточно. Если какой-то функционал будет нужен дополнительно, его можно добавить позже.

Протокол SMTP

Сам по себе протокол SMTP очень простой, но реализовывать его целиком мы не будем. Нам нужны будут только следующие команды:

HELO имя/IP-адрес Начало сессии
MAIL FROM: отправитель Отправитель письма
RCPT TO: получатель Может быть несколько таких заголовков
DATA Начало ввода данных
. (точка) Окончание ввода данных (Строка, состоящая из одного символа)
QUIT Окончание сессии

Не очень много команд, но, в принципе, их уже достаточно, чтобы можно было сэмулировать работу почтового сервера.

Фейковый SMTP-сервер

Это будет достаточно небольшой скрипт. Его первая задача — открыть порт 25/tcp и слушать его. Как только на этом порту будет введена команда, ее надо обработать и соответствующим образом прореагировать.

Для того, чтобы запустить скрипт, нам понадобятся права пользователя root, поскольку порт 25 является привелегированным, а также установленный пакет netcat.

Сам скрипт:

#!/bin/bash

# Адрес, на котором будет слушаться порт
LISTEN="192.168.0.6"

S=./tmp
[ -p $S ] || mkfifo $S

start_smtp(){
(tail -f $S) | ( netcat -l -p 25 $LISTEN ) | (
    # Баннер почтового сервера
    echo "220 fake smtp server" > $S
    # Разделителем значений будет перевод строки
    # Это нужно для считывания текста целыми строками
    IFS='
'
    # Флаг режима ввода данных
    DATA_INPUT=0
    while read
    do
    echo $REPLY
    if [ $DATA_INPUT -eq 0 ]
    then
        case $REPLY in
            HELO*)
                # Здесь и далее ответ на команды клиента
                echo "250 bash SMTP stub" > $S
            ;;
            DATA*)
                # После ввода команды DATA включаем режим ввода данных
                echo "354 Start mail input; end with ." > $S
                DATA_INPUT=1
            ;;
            QUIT*)
                # Окончание сессии
                echo "221 2.0.0 Bye" > $S
                # Находим запущенные нами процессы tail и netstat и убиваем их
                # Без окончания этих процессов следующая сессия не запустится 
                TAIL_PID=$(ps ax | grep "tail -f $S" | grep -v grep | cut -d' ' -f 1)
                NETCAT_PID=$(ps ax | grep "netcat -l -p 25" | grep -v grep | cut -d' ' -f 1)
                kill -9 $TAIL_PID $NETCAT_PID
                # Выход в главную программу
                return
            ;;
            MAIL\ FROM*)
                echo "250 2.1.0 OK" > $S
            ;;
            RCPT\ TO*)
                echo "250 2.1.0 OK" > $S
            ;;
            *)
                echo "502 5.5.2 Error: command not recognized" > $S
            ;;
        esac
    else
        # Ввод данных пока не встретится "."
        case $REPLY in
            .*)
                echo "250 2.1.0 OK" > $S
                DATA_INPUT=0
            ;;
        esac
    fi
    done
)

}

while true
do
echo "*** Starting session"
start_smtp
done

Вот такой небольшой скрипт. Информация, которую передает клиент, выводится на экран терминала, в котором запущен скрипт. При желании ее можно перенаправить в файл лога.