Browse Source

Began work on emailing service #1456

Torkel Ödegaard 10 years ago
parent
commit
3f5ab189cd

+ 13 - 0
docker/blocks/smtp/Dockerfile

@@ -0,0 +1,13 @@
+FROM centos:centos7
+MAINTAINER Przemyslaw Ozgo <linux@ozgo.info>
+
+RUN \
+    yum update -y && \
+    yum install -y net-snmp net-snmp-utils && \
+    yum clean all
+
+COPY bootstrap.sh /tmp/bootstrap.sh
+
+EXPOSE 161
+
+ENTRYPOINT ["/tmp/bootstrap.sh"]

+ 27 - 0
docker/blocks/smtp/bootstrap.sh

@@ -0,0 +1,27 @@
+#!/bin/sh
+
+set -u
+
+# User params
+USER_PARAMS=$@
+
+# Internal params
+RUN_CMD="snmpd -f ${USER_PARAMS}"
+
+#######################################
+# Echo/log function
+# Arguments:
+#   String: value to log
+#######################################
+log() {
+  if [[ "$@" ]]; then echo "[`date +'%Y-%m-%d %T'`] $@";
+  else echo; fi
+}
+
+# Launch
+log $RUN_CMD
+$RUN_CMD
+
+# Exit immidiately in case of any errors or when we have interactive terminal
+if [[ $? != 0 ]] || test -t 0; then exit $?; fi
+log

+ 4 - 0
docker/blocks/smtp/fig

@@ -0,0 +1,4 @@
+snmpd:
+  build: blocks/snmpd
+  ports:
+    - "161:161"

+ 2 - 0
main.go

@@ -16,6 +16,7 @@ import (
 	"github.com/grafana/grafana/pkg/plugins"
 	"github.com/grafana/grafana/pkg/search"
 	"github.com/grafana/grafana/pkg/services/eventpublisher"
+	"github.com/grafana/grafana/pkg/services/mailer"
 	"github.com/grafana/grafana/pkg/services/sqlstore"
 	"github.com/grafana/grafana/pkg/setting"
 	"github.com/grafana/grafana/pkg/social"
@@ -56,6 +57,7 @@ func main() {
 	social.NewOAuthService()
 	eventpublisher.Init()
 	plugins.Init()
+	mailer.Init()
 
 	if setting.ReportingEnabled {
 		go metrics.StartUsageReportLoop()

+ 7 - 0
pkg/api/apikey.go

@@ -24,6 +24,13 @@ func GetApiKeys(c *middleware.Context) Response {
 		}
 	}
 
+	bus.Dispatch(&m.SendEmailCommand{
+		To:      []string{"torkel@raintank.io"},
+		From:    "grafana@test.com",
+		Subject: "Test from Grafana2",
+		Body:    "Body! hej hoppas allt är bra",
+	})
+
 	return Json(200, result)
 }
 

+ 24 - 0
pkg/models/emails.go

@@ -0,0 +1,24 @@
+package models
+
+type SendEmailCommand struct {
+	To      []string
+	From    string
+	Subject string
+	Body    string
+	Type    string
+	Massive bool
+	Info    string
+}
+
+// create mail content
+func (m *SendEmailCommand) Content() string {
+	// set mail type
+	contentType := "text/plain; charset=UTF-8"
+	if m.Type == "html" {
+		contentType = "text/html; charset=UTF-8"
+	}
+
+	// create mail content
+	content := "From: " + m.From + "\r\nSubject: " + m.Subject + "\r\nContent-Type: " + contentType + "\r\n\r\n" + m.Body
+	return content
+}

+ 243 - 0
pkg/services/mailer/mailer.go

@@ -0,0 +1,243 @@
+package mailer
+
+import (
+	"crypto/tls"
+	"fmt"
+	"net"
+	"net/mail"
+	"net/smtp"
+	"os"
+	"strings"
+
+	"github.com/grafana/grafana/pkg/bus"
+	"github.com/grafana/grafana/pkg/log"
+	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/setting"
+)
+
+var mailQueue chan *m.SendEmailCommand
+
+func Init() {
+	bus.AddHandler("email", handleEmailCommand)
+
+	mailQueue = make(chan *m.SendEmailCommand, 10)
+
+	setting.Smtp = setting.SmtpSettings{
+		Host:        "smtp.gmail.com:587",
+		User:        "torkel.odegaard@gmail.com",
+		Password:    "peslpwstnnloiksq",
+		FromAddress: "grafana@grafana.org",
+	}
+
+	go processMailQueue()
+}
+
+func processMailQueue() {
+	for {
+		select {
+		case msg := <-mailQueue:
+			num, err := buildAndSend(msg)
+			tos := strings.Join(msg.To, "; ")
+			info := ""
+			if err != nil {
+				if len(msg.Info) > 0 {
+					info = ", info: " + msg.Info
+				}
+				log.Error(4, fmt.Sprintf("Async sent email %d succeed, not send emails: %s%s err: %s", num, tos, info, err))
+			} else {
+				log.Trace(fmt.Sprintf("Async sent email %d succeed, sent emails: %s%s", num, tos, info))
+			}
+		}
+	}
+}
+
+func encodeRFC2047(text string) string {
+	// use mail's rfc2047 to encode any string
+	addr := mail.Address{Address: text}
+	return strings.Trim(addr.String(), " <>")
+}
+
+func handleEmailCommand(cmd *m.SendEmailCommand) error {
+	log.Info("Sending on queue")
+	mailQueue <- cmd
+	return nil
+}
+
+func sendToSmtpServer(recipients []string, msgContent []byte) error {
+	host, port, err := net.SplitHostPort(setting.Smtp.Host)
+	if err != nil {
+		return err
+	}
+
+	tlsconfig := &tls.Config{
+		InsecureSkipVerify: setting.Smtp.SkipVerify,
+		ServerName:         host,
+	}
+
+	if setting.Smtp.CertFile != "" {
+		cert, err := tls.LoadX509KeyPair(setting.Smtp.CertFile, setting.Smtp.KeyFile)
+		if err != nil {
+			return err
+		}
+		tlsconfig.Certificates = []tls.Certificate{cert}
+	}
+
+	conn, err := net.Dial("tcp", net.JoinHostPort(host, port))
+	if err != nil {
+		return err
+	}
+	defer conn.Close()
+
+	isSecureConn := false
+	// Start TLS directly if the port ends with 465 (SMTPS protocol)
+	if strings.HasSuffix(port, "465") {
+		conn = tls.Client(conn, tlsconfig)
+		isSecureConn = true
+	}
+
+	client, err := smtp.NewClient(conn, host)
+	if err != nil {
+		return err
+	}
+
+	hostname, err := os.Hostname()
+	if err != nil {
+		return err
+	}
+
+	if err = client.Hello(hostname); err != nil {
+		return err
+	}
+
+	// If not using SMTPS, alway use STARTTLS if available
+	hasStartTLS, _ := client.Extension("STARTTLS")
+	if !isSecureConn && hasStartTLS {
+		if err = client.StartTLS(tlsconfig); err != nil {
+			return err
+		}
+	}
+
+	canAuth, options := client.Extension("AUTH")
+
+	if canAuth && len(setting.Smtp.User) > 0 {
+		var auth smtp.Auth
+
+		if strings.Contains(options, "CRAM-MD5") {
+			auth = smtp.CRAMMD5Auth(setting.Smtp.User, setting.Smtp.Password)
+		} else if strings.Contains(options, "PLAIN") {
+			auth = smtp.PlainAuth("", setting.Smtp.User, setting.Smtp.Password, host)
+		}
+
+		if auth != nil {
+			if err = client.Auth(auth); err != nil {
+				return err
+			}
+		}
+	}
+
+	if fromAddress, err := mail.ParseAddress(setting.Smtp.FromAddress); err != nil {
+		return err
+	} else {
+		if err = client.Mail(fromAddress.Address); err != nil {
+			return err
+		}
+	}
+
+	for _, rec := range recipients {
+		if err = client.Rcpt(rec); err != nil {
+			return err
+		}
+	}
+
+	w, err := client.Data()
+	if err != nil {
+		return err
+	}
+	if _, err = w.Write([]byte(msgContent)); err != nil {
+		return err
+	}
+
+	if err = w.Close(); err != nil {
+		return err
+	}
+
+	return client.Quit()
+	// smtpServer := "smtp.gmail.com"
+	// auth := smtp.PlainAuth(
+	// 	"",
+	// 	"torkel.odegaard@gmail.com",
+	// 	"peslpwstnnloiksq",
+	// 	smtpServer,
+	// )
+	//
+	// from := mail.Address{Name: "test", Address: "torkel@test.com"}
+	// to := mail.Address{Name: "Torkel Ödegaard", Address: "torkel@raintank.io"}
+	// title := "Message from Grafana"
+	//
+	// body := "Testing email sending"
+	//
+	// header := make(map[string]string)
+	// header["From"] = from.String()
+	// header["To"] = to.String()
+	// header["Subject"] = encodeRFC2047(title)
+	// header["MIME-Version"] = "1.0"
+	// header["Content-Type"] = "text/plain; charset=\"utf-8\""
+	// header["Content-Transfer-Encoding"] = "base64"
+	//
+	// message := ""
+	// for k, v := range header {
+	// 	message += fmt.Sprintf("%s: %s\r\n", k, v)
+	// }
+	// message += "\r\n" + base64.StdEncoding.EncodeToString([]byte(body))
+	//
+	// // Connect to the server, authenticate, set the sender and recipient,
+	// // and send the email all in one step.
+	// err := smtp.SendMail(
+	// 	smtpServer+":587",
+	// 	auth,
+	// 	from.Address,
+	// 	[]string{to.Address},
+	// 	[]byte(message),
+	// )
+	// if err != nil {
+	// 	log.Info("Failed to send email: %v", err)
+	// }
+	// kkkk
+}
+
+func buildAndSend(msg *m.SendEmailCommand) (int, error) {
+	log.Trace("Sending mails to: %s", strings.Join(msg.To, "; "))
+
+	// get message body
+	content := msg.Content()
+
+	if len(msg.To) == 0 {
+		return 0, fmt.Errorf("empty receive emails")
+	} else if len(msg.Body) == 0 {
+		return 0, fmt.Errorf("empty email body")
+	}
+
+	if msg.Massive {
+		// send mail to multiple emails one by one
+		num := 0
+		for _, to := range msg.To {
+			body := []byte("To: " + to + "\r\n" + content)
+			err := sendToSmtpServer([]string{to}, body)
+			if err != nil {
+				return num, err
+			}
+			num++
+		}
+		return num, nil
+	} else {
+		body := []byte("To: " + strings.Join(msg.To, ";") + "\r\n" + content)
+
+		// send to multiple emails in one message
+		err := sendToSmtpServer(msg.To, body)
+		if err != nil {
+			return 0, err
+		} else {
+			return 1, nil
+		}
+	}
+}

+ 3 - 0
pkg/setting/setting.go

@@ -114,6 +114,9 @@ var (
 
 	ReportingEnabled  bool
 	GoogleAnalyticsId string
+
+	// SMTP email settings
+	Smtp SmtpSettings
 )
 
 type CommandLineArgs struct {

+ 11 - 0
pkg/setting/setting_smtp.go

@@ -0,0 +1,11 @@
+package setting
+
+type SmtpSettings struct {
+	Host        string
+	User        string
+	Password    string
+	CertFile    string
+	KeyFile     string
+	FromAddress string
+	SkipVerify  bool
+}