// ------------------------------------------------------ // 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") } } }