Spam hali ham trendda bo’lgani va zararli dasturlar serverdan «axlat tashuvchi» sifatida foydalanish uchun veb-saytlarni egallab olishda davom etayotgani sababli, ba’zi ma’lumotlar markazlari va uy internet provayderlari chiquvchi ulanishlarni 25-port bilan cheklaydi. Albatta, siz shifrlangan 465-port orqali tashqi SMTP xizmatidan foydalanishingiz mumkin, ammo mening holatimda bu unchalik foydali bo’lmadi. Menga faqat serverdagi barcha virtual mashinalardan hisobotlar va bildirishnomalar hamda ularning yetkazib berilishi kafolati kerak edi. Bir oz o’ylab ko’rgandan so’ng, ularni olishning eng qiziqarli usuli hozirgi sevimli messenjerim Telegram orqali ekanligiga qaror qildim.
Mavzuni Google’da qidirganimdan so’ng, deyarli darhol smtp2tg loyihasiga duch keldim.
smpt2tg Go tilida yozilgan, shuning uchun avval uni o’rnatishingiz kerak. Birinchi urinishim uni paket menejeri orqali o’rnatish edi, bu esa qo’shimcha yarim soat vaqtni behuda sarfladi. Buning bunday ishlamasligini anglab, o’rnatgan barcha fayllarimni o’chirib tashladim va eng oddiy yo’lni tanladim.
wget https://golang.org/dl/go1.15.6.linux-amd64.tar.gz
tar -C /usr/local -xzf go1.15.6.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
echo "export PATH=$PATH:/usr/local/go/bin" >> ~/.bashrc Endi tilimizni to’g’ri joyga qo’yganimizga ishonch hosil qilaylik. :)

Keling, bu masalaga qaramlik paketlarini qo’shamiz.
go get github.com/veqryn/go-email/email
go get github.com/spf13/viper
go get gopkg.in/telegram-bot-api.v4
go get github.com/ircop/smtp2tg Dastur uchun konfiguratsiya yaratish kerak
Siz uni /etc/smtp2tg.toml fayliga joylashtirishingiz mumkin
[bot]
token = "ключ телеграм бота"
[receivers]
"*" = "id чата/канала/группы для wildcard"
"[email protected]" = "id чата/канала/группы для конкретного адреса"
[smtp]
listen = "0.0.0.0:25"
name = "ex.uz"
[logging]
file = "/var/log/smtp2tg.log"
debug = false Endi siz dasturni ishga tushirishingiz mumkin
/root/go/bin/smtp2tg -c /etc/smtp2tg.toml & 
Ushbu SMTP serveri tomonidan qabul qilingan har qanday pochta konfiguratsiyaga muvofiq qabul qiluvchiga mos keladigan identifikatorga yoki «barchasini ushlab olish» qoidasiga muvofiq yo’naltiriladi.
Agar sizga haqiqiy pochta qutisi kerak bo’lsa, shunchaki domeningizning MX yozuvini smtp2tg ishlayotgan serverga yo’naltiring. Biroq, mening maqsadim serverimdan istalgan yo’nalishda chiqayotgan barcha pochta xabarlarini ushlab qolish edi. 25-port baribir men uchun bloklanganligi sababli, men darhol gipervizorga dnat qoidasini qo’shdim.
iptables -t nat -I PREROUTING -p tcp -d 0.0.0.0/0 --dport 25 -j DNAT --to-destination IP_СЕРВЕРА:25 Endi bu dastur qabul qiluvchining manzilida qaysi MX hal qilinganidan qat’i nazar, hamma narsani ushlaydi.
Men hozir konsolni yopishim mumkin edi, lekin men bir bezovta qiluvchi xususiyatni topdim. Elektron pochtani tahlil qilishda smtp2tg kontent turini ko’rsatadigan sarlavhani qidiradi. Masalan, PHPdagi mail() funktsiyasi sukut bo’yicha elektron pochtada bunday kontentni o’z ichiga olmaydi va shu sababli ba’zi kontent bizga yetib bormasligi, «bo’sh konvert» sifatida filtrlanishi mumkin.
Men bu sozlamadan umuman mamnun emas edim, shuning uchun Go bilan umuman tanish bo’lmaganim uchun biror narsani tuzatishga kirishdim. :) Mening harakatlarim natijasini quyida ko’rish mumkin. Bu dasturning biroz o’zgartirilgan versiyasi bo’lib, endi u elektron pochta mavzusini va jo’natuvchining manzilini ham ko’rsatadi.
<pre class="wp-block-syntaxhighlighter-code">package main
import (
"os"
"strconv"
"strings"
"flag"
"bytes"
"log"
"net"
"gopkg.in/telegram-bot-api.v4"
"github.com/spf13/viper"
"github.com/veqryn/go-email/email"
"github.com/ircop/smtp2tg/smtpd"
)
var receivers map[string]string
var bot *tgbotapi.BotAPI
var debug bool
func main() {
configFilePath := flag.String("c", "./smtp2tg.toml", "Config file location")
//pidFilePath := flag.String("p", "/var/run/smtp2tg.pid", "Pid file location")
flag.Parse()
// Load & parse config
viper.SetConfigFile(*configFilePath)
err := viper.ReadInConfig()
if( err != nil ) {
log.Fatal(err.Error())
}
// Logging
logfile := viper.GetString("logging.file")
if( logfile == "" ) {
log.Println("No logging.file defined in config, outputting to stdout")
} else {
lf, err := os.OpenFile(logfile, os.O_APPEND | os.O_CREATE | os.O_RDWR, 0666)
if( err != nil ) {
log.Fatal(err.Error())
}
log.SetOutput(lf)
}
// Debug?
debug = viper.GetBool("logging.debug")
receivers = viper.GetStringMapString("receivers")
if( receivers["*"] == "" ) {
log.Fatal("No wildcard receiver (*) found in config.")
}
var token string = viper.GetString("bot.token")
if( token == "" ) {
log.Fatal("No bot.token defined in config")
}
var listen string = viper.GetString("smtp.listen")
var name string = viper.GetString("smtp.name")
if( listen == "" ) {
log.Fatal("No smtp.listen defined in config.")
}
if( name == "" ) {
log.Fatal("No smtp.name defined in config.")
}
// Initialize TG bot
bot, err = tgbotapi.NewBotAPI( token )
if( err != nil ) {
log.Fatal(err.Error())
}
log.Printf("Bot authorized as %s", bot.Self.UserName )
log.Printf("Initializing smtp server on %s...", listen)
// Initialize SMTP server
err_ := smtpd.ListenAndServe(listen, mailHandler, "mail2tg", "", debug)
if( err_ != nil ) {
log.Fatal(err_.Error())
}
}
func mailHandler(origin net.Addr, from string, to []string, data []byte) {
from = strings.Trim(from, " ")
to[0] = strings.Trim(to[0], " ")
to[0] = strings.Trim(to[0], "<")
to[0] = strings.Trim(to[0], ">")
msg, err := email.ParseMessage(bytes.NewReader(data))
if( err != nil ) {
log.Printf("[MAIL ERROR]: %s", err.Error())
return
}
subject := msg.Header.Get("Subject")
myBytes := msg.Body
log.Printf("Received mail from '%s' for '%s' with subject '%s'", from, to[0], subject)
// Find receivers and send to TG
var tgid string
if( receivers[to[0]] != "" ) {
tgid = receivers[to[0]]
} else {
tgid = receivers["*"]
}
textMsgs := msg.MessagesContentTypePrefix("text")
images := msg.MessagesContentTypePrefix("image")
if len(textMsgs) == 0 && len(images) == 0 {
if len(myBytes) == 0 {
log.Printf("mail doesn't contain text or image")
return
}
}
log.Printf("Relaying message to: %v", tgid)
i, err := strconv.ParseInt(tgid, 10, 64)
if( err != nil ) {
log.Printf("[ERROR]: wrong telegram id: not int64")
return
}
if len(textMsgs) > 0 {
bodyStr := "📬 from: "+from + " 💬 "+ subject + " 💬\r\n\r\n" + string(textMsgs[0].Body)
tgMsg := tgbotapi.NewMessage(i, bodyStr)
tgMsg.ParseMode = tgbotapi.ModeMarkdown
_, err = bot.Send(tgMsg)
if err != nil {
log.Printf("[ERROR]: telegram message send: '%s'", err.Error())
return
}
} else if len(myBytes) > 0 {
bodyStr := "📬 from: "+from + " 💬 "+ subject + " 💬\r\n\r\n" +string(myBytes)
tgMsg := tgbotapi.NewMessage(i, bodyStr)
tgMsg.ParseMode = tgbotapi.ModeMarkdown
_, err = bot.Send(tgMsg)
if err != nil {
log.Printf("[ERROR]: telegram message send: '%s'", err.Error())
return
}
}
// TODO Better to use 'sendMediaGroup' to send all attachments as a
// single message, but go telegram api has not implemented it yet
// https://github.com/go-telegram-bot-api/telegram-bot-api/issues/143
for _, part := range msg.MessagesContentTypePrefix("image") {
_, params, err := part.Header.ContentDisposition()
if err != nil {
log.Printf("[ERROR]: content disposition parse: '%s'", err.Error())
return
}
text := params["filename"]
tgFile := tgbotapi.FileBytes{Name: text, Bytes: part.Body}
tgMsg := tgbotapi.NewPhotoUpload(i, tgFile)
tgMsg.Caption = text
// It's not a separate message, so disable notification
tgMsg.DisableNotification = true
_, err = bot.Send(tgMsg)
if err != nil {
log.Printf("[ERROR]: telegram photo send: '%s'", err.Error())
return
}
}
}
</pre> Nima uchun ishlab chiquvchi dastlab bularning barchasi keraksiz deb qaror qilganini bilmayman, lekin asl kod faqat elektron pochtaning asosiy qismini yetkazib berish uchun mo’ljallangan edi.
Ushbu kod quyidagicha yig’iladi:
mkdir ~/smtp2tg/
vi ~/smtp2tg/main.go Yuqoridagi kodni main.go ga joylashtiring va «go build» ni ishga tushiring
Biz uni ishga tushiramiz, tekshiramiz va shunga o’xshash narsani olamiz:

Qolgan narsa uni ishga tushirish (masalan, rc.local da?) ga qo’yishdir.
CentOS 7 da ushbu jarayonni demonizatsiya qilishning munosib usulini topsam, ehtimol ushbu maqolani tugataman. Hozircha, «boricha» deganlaridek, buni sizning hukmingizga qoldiraman.
