zabbix-helpers/check_email_delivery_go/email-check.go

586 lines
20 KiB
Go

// ------------------------------------------------------
// Email sender and receiver for checking mail transport
// Author: Sergey Kalinin, 2022
//-------------------------------------------------------
package main
import (
"crypto/tls"
"encoding/json"
"errors"
"flag"
"fmt"
"log"
"net"
"net/http"
"net/smtp"
"os"
"strconv"
"time"
"github.com/adubkov/go-zabbix"
"github.com/emersion/go-imap"
"github.com/emersion/go-imap/client"
"github.com/google/uuid"
)
type SmtpSession struct {
smtpUser string
smtpPassword string
smtpServer string
smtpPort string
}
type ImapSession struct {
imapPassword string
imapServer string
imapUser string
imapPort string
}
type ZabbixSession struct {
zabbixUser string
zabbixPassword string
zabbixServer string
zabbixPort int
zabbixHost string
}
type MailMessage struct {
mailFrom string
mailTo string
mailSubject string
mailMessage string
messageUUID string
}
type loginAuth struct {
username, password string
}
func LoginAuth(username, password string) smtp.Auth {
return &loginAuth{username, password}
}
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
return "LOGIN", []byte(a.username), nil
}
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if more {
switch string(fromServer) {
case "Username:":
return []byte(a.username), nil
case "Password:":
return []byte(a.password), nil
default:
return nil, errors.New("Unknown from server")
}
}
return nil, nil
}
func sendEmail(smtpSession *SmtpSession, message *MailMessage) bool {
// Receiver email address.
to := []string{
message.mailTo,
}
serverName := smtpSession.smtpServer + ":" + smtpSession.smtpPort
// fmt.Println(serverName, smtpSession.smtpUser, smtpSession.smtpPassword)
conn, err := net.Dial("tcp", serverName)
if err != nil {
log.Println(err)
}
c, err := smtp.NewClient(conn, smtpSession.smtpServer)
if err != nil {
log.Println(err)
}
tlsconfig := &tls.Config{
ServerName: smtpSession.smtpServer,
}
if err = c.StartTLS(tlsconfig); err != nil {
log.Println(err)
}
auth := LoginAuth(smtpSession.smtpUser, smtpSession.smtpPassword)
if err = c.Auth(auth); err != nil {
log.Println(err)
}
msg := []byte("From: " + message.mailFrom + "\r\n" +
"To: " + message.mailTo + "\r\n" +
"Subject: " + message.mailSubject + "\r\n\r\n" +
message.mailMessage + "\r\n")
// Sending email.
err = smtp.SendMail(serverName, auth, message.mailFrom, to, msg)
if err != nil {
fmt.Println(err)
return false
}
// fmt.Println("Email Sent!")
if err != nil {
log.Fatal(err)
return false
} else {
log.Println("Email", message.messageUUID, "from", message.mailFrom, "sent successfully via", smtpSession.smtpServer)
return true
}
}
var httpClient = &http.Client{
Timeout: 10 * time.Second,
}
func genMessageUID() string {
id := uuid.New().String()
return id
}
func getVaultData(vaultAddr string, vaultToken string, vaultSecretName string) string {
customTransport := &(*http.DefaultTransport.(*http.Transport))
customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
client := &http.Client{Transport: customTransport}
// client := &http.Client{}
req, _ := http.NewRequest("GET", vaultAddr, nil)
req.Header.Add("Accept", "application/json")
req.Header.Add("X-Vault-Token", vaultToken)
resp, err := client.Do(req)
if err != nil {
log.Println("Errored when sending request to the Vault server")
}
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
secret := result["data"].(map[string]interface{})["data"].(map[string]interface{})[vaultSecretName]
return fmt.Sprint(secret)
}
func zabbixSender(zabbixSession *ZabbixSession, zabbixItem string, zabbixItemValue string) bool {
var (
metrics []*zabbix.Metric
err error
)
metrics = append(metrics, zabbix.NewMetric(zabbixSession.zabbixHost, zabbixItem, zabbixItemValue, time.Now().Unix()))
// metrics = append(metrics, zabbix.NewMetric(zabbixSession.zabbixHost, "status", "OK"))
// Create instance of Packet class
packet := zabbix.NewPacket(metrics)
// fmt.Println(zabbix_host, metric_name, metric_value, metrics)
// Send packet to zabbix
z := zabbix.NewSender(zabbixSession.zabbixServer, zabbixSession.zabbixPort)
res, err := z.Send(packet)
log.Println(zabbixSession.zabbixServer, zabbixSession.zabbixPort, zabbixSession.zabbixHost, zabbixItem, zabbixItemValue, metrics, zabbixSession)
log.Println("Result", string(res))
if err != nil {
log.Println("Sending to zabbix should have failed:", err)
}
return true
}
func receiveEmail(imapSession *ImapSession, message *MailMessage) bool {
serverName := imapSession.imapServer + ":" + imapSession.imapPort
log.Println("Connecting to server", serverName, "...")
// Connect to server
c, err := client.DialTLS(serverName, nil)
if err != nil {
log.Fatal(err)
}
log.Println("Connected")
log.Println("Trying to logged in to imap with username", imapSession.imapUser, "...")
// Don't forget to logout
defer c.Logout()
// Login
if err := c.Login(imapSession.imapUser, imapSession.imapPassword); err != nil {
log.Fatal(err)
return false
}
log.Println(imapSession.imapUser, "logged in")
// Select INBOX
_, err = c.Select("INBOX", false)
if err != nil {
log.Fatal(err)
}
log.Println("Select the INBOX:")
// Get the last 4 messages
// from := uint32(1)
// to := mbox.Messages
// if mbox.Messages > 10 {
// // We're using unsigned integers here, only subtract if the result is > 0
// from = mbox.Messages - 10
// }
// seqset := new(imap.SeqSet)
// seqset.AddRange(from, to)
// messages := make(chan *imap.Message, 10)
// done := make(chan error, 1)
// go func() {
// done <- c.Fetch(seqset, []imap.FetchItem{imap.FetchEnvelope, imap.FetchUid}, messages)
// }()
// // var uid uint32
// log.Println("Search message with subject", message.mailSubject, "in last messages:")
// for msg := range messages {
// log.Println(msg.Uid)
// if msg.Envelope.Subject == message.mailSubject {
// log.Println("Message was found:", msg.Envelope.Subject, msg.Envelope.MessageId, msg.Uid)
// // uid = msg.Uid
// // item := imap.FormatFlagsOp(imap.AddFlags, true)
// // flags := []interface{}{imap.DeletedFlag}
// // if err := c.Store(seqset, item, flags, nil); err != nil {
// // log.Fatal(err)
// // } else {
// // log.Println("Message with UID", mbox.Messages, "was marked for deletion")
// // }
// }
// }
// if err := <-done; err != nil {
// log.Fatal(err)
// return false
// }
// -----------------------
// We will delete the last message
// if mbox.Messages == 0 {
// log.Fatal("No message in mailbox")
// }
// seqset.Clear()
// seqSet := new(imap.SeqSet)
// seqSet.AddNum(uid)
// // First mark the message as deleted
// item := imap.FormatFlagsOp(imap.AddFlags, true)
// flags := []interface{}{imap.DeletedFlag}
// if err := c.Store(seqSet, item, flags, nil); err != nil {
// log.Fatal(err)
// } else {
// log.Println("Last", mbox.Messages, "messages was marked for deletion")
// }
// Then delete it
// if err := c.Expunge(nil); err != nil {
// log.Fatal(err)
// }
// log.Println("Last messages has been deleted")
log.Println("Search message with subject", message.mailSubject, "in last messages...")
criteria := imap.NewSearchCriteria()
criteria.Text = []string{message.mailSubject}
ids, err := c.Search(criteria)
if err != nil {
log.Fatal(err)
}
log.Println("Total messages :", ids)
if len(ids) > 0 {
seqset := new(imap.SeqSet)
seqset.AddNum(ids...)
messages := make(chan *imap.Message, 10)
done := make(chan error, 1)
go func() {
done <- c.Fetch(seqset, []imap.FetchItem{imap.FetchEnvelope}, messages)
}()
// log.Println("Unseen messages:")
for msg := range messages {
log.Println("Message was found:", msg.Envelope.Subject, msg.Envelope.MessageId, msg.Uid)
// log.Println("* " + msg.Envelope.Subject)
}
if err := <-done; err != nil {
log.Fatal(err)
return false
}
item := imap.FormatFlagsOp(imap.AddFlags, true)
flags := []interface{}{imap.DeletedFlag}
if err := c.Store(seqset, item, flags, nil); err != nil {
log.Fatal(err)
return false
} else {
log.Println("The message with subject", message.mailSubject, "was marked for deletion")
}
}
// Then delete it
log.Println("Delete marked messages...")
if err := c.Expunge(nil); err != nil {
log.Fatal(err)
} else {
log.Println("All marked messages was deleted")
}
log.Println("Done!")
return true
}
func main() {
var (
vaultAddress string
vaultToken string
vaultEmailSecretPath string
vaultZabbixSecretPath string
vaultPath string
message MailMessage
smtpSession SmtpSession
imapSession ImapSession
zabbixSession ZabbixSession
sendMessage bool
receiveMessage bool
operationDelay int
useZabbixSender bool
zabbixApi bool
err error
msgSendResult bool
msgReceiveResult bool
direction string
)
// Genarate uniq ID
message.messageUUID = genMessageUID()
// ----------------------------------------------------------
// Read command line options and and settins the variables
flag.StringVar(&message.mailFrom, "mail-from", "", "Mail sender address 'From' (MAIL_FROM)")
flag.StringVar(&message.mailTo, "mail-to", "", "Mail receiver address 'From' (MAIL_TO)")
flag.StringVar(&message.mailSubject, "mail-subject", "", "Mail message subject (MAIL_SUBJECT)")
flag.StringVar(&message.mailMessage, "mail-message", "", "Mail message body (MAIL_MESSAGE)")
flag.StringVar(&smtpSession.smtpServer, "smtp-server", "", "SMTP server address (SMTP_SERVER)")
flag.StringVar(&smtpSession.smtpPort, "smtp-port", "587", "SMTP server port (SMTP_PORT)")
flag.StringVar(&smtpSession.smtpUser, "smtp-user", "", "SMTP user (SMTP_USER)")
flag.StringVar(&smtpSession.smtpPassword, "smtp-password", "", "SMTP user password (SMTP_PASSWORD)")
flag.StringVar(&imapSession.imapServer, "imap-server", "", "IMAP server address (IMAP_SERVER)")
flag.StringVar(&imapSession.imapPort, "imap-port", "993", "IMAP server port (IMAP_PORT)")
flag.StringVar(&imapSession.imapUser, "imap-user", "", "IMAP user (IMAP_USER)")
flag.StringVar(&imapSession.imapPassword, "imap-password", "", "IMAP user password (IMAP_PASSWORD)")
flag.StringVar(&vaultAddress, "vault-address", "", "Hashicorp (c) vault address (VAULT_ADDRESS)")
flag.StringVar(&vaultToken, "vault-token", "", "Vault access token (VAULT_TOKEN)")
flag.StringVar(&vaultEmailSecretPath, "vault-email-secret-path", "", "Path to Vault email secret, where 'full Vault path' = 'vault-address' + 'vault-email-secret-path'")
flag.StringVar(&vaultZabbixSecretPath, "vault-zabbix-secret-path", "", "Path to Vault zabbix secret, where 'full Vault path' = 'vault-address' + 'vault-zabbix-secret-path'")
flag.BoolVar(&sendMessage, "send", false, "Sending message is ON")
flag.BoolVar(&receiveMessage, "receive", false, "receive message is ON")
flag.IntVar(&operationDelay, "delay", 5, "Pause is required between operations")
flag.BoolVar(&useZabbixSender, "use-zabbix-sender", false, "Send metrics or discovery data into zabbix via zabbix-sender")
flag.BoolVar(&zabbixApi, "zabbix-api", false, "Send metrics or discovery data into zabbix via Zabbix API (not implemented yet)")
flag.StringVar(&zabbixSession.zabbixServer, "zabbix-server", "", "Zabbix server address (ZABBIX_SERVER)")
flag.IntVar(&zabbixSession.zabbixPort, "zabbix-port", 10051, "Zabbix server port (ZABBIX_PORT)")
flag.StringVar(&zabbixSession.zabbixUser, "zabbix-api-user", "", "Zabbix server API user (ZABBIX_USER)")
flag.StringVar(&zabbixSession.zabbixPassword, "zabbix-api-password", "", "Zabbix server API password (ZABBIX_PASSWORD)")
flag.StringVar(&zabbixSession.zabbixHost, "zabbix-host", "", "Zabbix monitoring host name")
flag.StringVar(&direction, "direction", "local", "Direction of email checking. Must be an: 'local', 'incoming', 'outgoing'.")
flag.Parse()
if os.Getenv("VAULT_ADDRESS") != "" {
vaultAddress = os.Getenv("VAULT_ADDRESS")
}
if vaultAddress != "" && vaultToken == "" && os.Getenv("VAULT_TOKEN") == "" {
fmt.Println("If You setting VAULT_ADDRES, You must sure environment variables `VAULT_TOKEN`, or used with '-vault-token' argument")
os.Exit(1)
} else if vaultToken == "" && os.Getenv("VAULT_TOKEN") != "" {
vaultToken = os.Getenv("VAULT_TOKEN")
}
if message.mailFrom == "" && os.Getenv("MAIL_FROM") == "" {
fmt.Println("Make sure environment variables `MAIL_FROM`, or used with '-mail-from' argument")
os.Exit(1)
} else if message.mailFrom == "" && os.Getenv("MAIL_FROM") != "" {
message.mailFrom = os.Getenv("MAIL_FROM")
}
if message.mailTo == "" && os.Getenv("MAIL_TO") == "" {
fmt.Println("Make sure environment variables `MAIL_TO`, or used with '-mail-to' argument")
os.Exit(1)
} else if message.mailTo == "" && os.Getenv("MAIL_TO") != "" {
message.mailTo = os.Getenv("MAIL_TO")
}
if message.mailSubject == "" && os.Getenv("MAIL_SUBJECT") == "" {
message.mailSubject = message.messageUUID
// fmt.Println("Make sure environment variables `MAIL_SUBJECT`, or used with '-mail-subject' argument")
// os.Exit(1)
} else if message.mailSubject == "" && os.Getenv("MAIL_SUBJECT") != "" {
message.mailFrom = os.Getenv("MAIL_SUBJECT")
}
if message.mailMessage == "" && os.Getenv("MAIL_MESSAGE") == "" {
message.mailMessage = message.messageUUID
// fmt.Println("Make sure environment variables `MAIL_MESSAGE`, or used with '-mail-message' argument")
// os.Exit(1)
} else if message.mailMessage == "" && os.Getenv("MAIL_MESSAGE") != "" {
message.mailMessage = os.Getenv("MAIL_MESSAGE")
}
if smtpSession.smtpUser == "" && os.Getenv("SMTP_USER") == "" {
fmt.Println("Make sure environment variables `SMTP_USER`, or used with '-smtp-user' argument")
os.Exit(1)
} else if smtpSession.smtpUser == "" && os.Getenv("SMTP_USER") != "" {
smtpSession.smtpServer = os.Getenv("SMTP_USER")
}
if smtpSession.smtpPassword == "" && os.Getenv("SMTP_PASSWORD") == "" && vaultAddress == "" {
fmt.Println("Make sure environment variables `SMTP_PASSWORD`, or used with '-smtp-password' argument")
os.Exit(1)
} else if smtpSession.smtpPassword == "" && os.Getenv("SMTP_PASSWORD") != "" {
smtpSession.smtpPassword = os.Getenv("SMTP_PASSWORD")
} else if vaultAddress != "" && smtpSession.smtpUser != "" && smtpSession.smtpPassword == "" {
vaultPath = vaultAddress + vaultEmailSecretPath
smtpSession.smtpPassword = getVaultData(vaultPath, vaultToken, smtpSession.smtpUser)
// fmt.Println(smtpUser, smtpPassword)
}
if smtpSession.smtpServer == "" && os.Getenv("SMTP_SERVER") == "" {
fmt.Println("Make sure environment variables `SMTP_SERVER`, or used with '-smtp-server' argument")
os.Exit(1)
} else if smtpSession.smtpServer == "" && os.Getenv("SMTP_SERVER") != "" {
smtpSession.smtpServer = os.Getenv("SMTP_SERVER")
}
if imapSession.imapUser == "" && os.Getenv("IMAP_USER") == "" {
fmt.Println("Make sure environment variables `IMAP_USER`, or used with '-imap-user' argument")
os.Exit(1)
} else if imapSession.imapUser == "" && os.Getenv("IMAP_USER") != "" {
imapSession.imapServer = os.Getenv("IMAP_USER")
}
if imapSession.imapPassword == "" && os.Getenv("IMAP_PASSWORD") == "" && vaultAddress == "" {
fmt.Println("Make sure environment variables `IMAP_PASSWORD`, or used with '-imap-password' argument")
os.Exit(1)
} else if imapSession.imapUser == "" && os.Getenv("IMAP_PASSWORD") != "" {
imapSession.imapPassword = os.Getenv("IMAP_PASSWORD")
} else if vaultAddress != "" && imapSession.imapUser != "" && imapSession.imapPassword == "" {
// secret := append("")
vaultPath = vaultAddress + vaultEmailSecretPath
imapSession.imapPassword = getVaultData(vaultPath, vaultToken, imapSession.imapUser)
// fmt.Println(imapUser, imapPassword)
}
if imapSession.imapServer == "" && os.Getenv("IMAP_SERVER") == "" {
fmt.Println("Make sure environment variables `IMAP_SERVER`, or used with '-imap-server' argument")
os.Exit(1)
} else if imapSession.imapServer == "" && os.Getenv("IMAP_SERVER") != "" {
imapSession.imapServer = os.Getenv("IMAP_SERVER")
}
if zabbixApi || useZabbixSender {
if zabbixSession.zabbixServer == "" && os.Getenv("ZABBIX_SERVER") == "" {
fmt.Println("Make sure environment variables `ZABBIX_SERVER`, or used with '-zabbix-server' argument")
os.Exit(1)
} else if zabbixSession.zabbixServer == "" && os.Getenv("ZABBIX_SERVER") != "" {
zabbixSession.zabbixServer = os.Getenv("ZABBIX_SERVER")
}
if os.Getenv("ZABBIX_PORT") != "" {
fmt.Println("Make sure environment variables `ZABBIX_PORT`, or used with '-zabbix-port' argument")
os.Exit(1)
if zabbixSession.zabbixPort, err = strconv.Atoi(os.Getenv("ZABBIX_PORT")); err != nil {
log.Println(zabbixSession.zabbixPort, "Zabbix port value error")
}
}
if zabbixSession.zabbixHost == "" && os.Getenv("ZABBIX_HOST") == "" {
fmt.Println("Make sure environment variables `ZABBIX_HOST`, or used with '-zabbix-host' argument")
os.Exit(1)
} else if zabbixSession.zabbixHost == "" && os.Getenv("ZABBIX_HOST") != "" {
zabbixSession.zabbixHost = os.Getenv("ZABBIX_HOST")
}
}
if zabbixApi {
if zabbixSession.zabbixUser == "" && os.Getenv("ZABBIX_USER") == "" {
fmt.Println("Make sure environment variables `ZABBIX_USER`, or used with '-zabbix-api-user' argument")
os.Exit(1)
} else if zabbixSession.zabbixUser == "" && os.Getenv("ZABBIX_USER") != "" {
zabbixSession.zabbixUser = os.Getenv("ZABBIX_USER")
}
if zabbixSession.zabbixPassword == "" && os.Getenv("ZABBIX_PASSWORD") == "" {
fmt.Println("Make sure environment variables `ZABBIX_PASSWORD`, or used with '-zabbix-api-password' argument")
os.Exit(1)
} else if vaultAddress != "" && zabbixSession.zabbixUser != "" && zabbixSession.zabbixPassword == "" {
// secret := append("")
vaultPath = vaultAddress + vaultZabbixSecretPath
zabbixSession.zabbixPassword = getVaultData(vaultPath, vaultToken, zabbixSession.zabbixUser)
// fmt.Println(imapUser, imapPassword)
} else if zabbixSession.zabbixPassword == "" && os.Getenv("ZABBIX_PASSWORD") != "" {
zabbixSession.zabbixUser = os.Getenv("ZABBIX_USER")
}
}
fmt.Println(useZabbixSender)
// ----------------------------------------------------------
// Run the operations
// Send message
if sendMessage {
log.Println("Send email message with UUID:", message.messageUUID)
msgSendResult = sendEmail(&smtpSession, &message)
// Send result into zabbix
if useZabbixSender {
if msgSendResult {
zabbixSender(&zabbixSession, `email.smtp_send.`+direction+`.status`, "OK")
} else {
zabbixSender(&zabbixSession, `email.smtp_send.`+direction+`.status`, "CRITICAL")
}
}
}
// When using both "send" and "receive",
// a 10-second pause between operations is required
if sendMessage && receiveMessage {
time.Sleep(time.Duration(operationDelay) * time.Second)
}
// Receive message
if receiveMessage {
log.Println("Read email message with UUID:", message.messageUUID, "via", imapSession.imapServer)
msgReceiveResult = receiveEmail(&imapSession, &message)
// Send result into zabbix
if useZabbixSender {
if msgReceiveResult {
zabbixSender(&zabbixSession, `email.imap_receive.`+direction+`.status`, "OK")
} else {
zabbixSender(&zabbixSession, `email.imap_receive.`+direction+`.status`, "CRITICAL")
}
}
}
// Send result into zabbix
if useZabbixSender {
if msgSendResult && msgReceiveResult {
zabbixSender(&zabbixSession, `email.delivery.`+direction+`.status`, "OK")
} else {
zabbixSender(&zabbixSession, `email.delivery.`+direction+`.status`, "CRITICAL")
}
}
}