package main import ( "bytes" "crypto/tls" "encoding/json" "fmt" "html/template" "log" "net" "net/http" "net/smtp" "strings" "github.com/spf13/viper" ) //Request struct type SendMailRequest struct { from string to []string subject string body string } // NewSendMailRequest creates a new instance to manage send mail func NewSendMailRequest(from string, to []string, subject string) *SendMailRequest { return &SendMailRequest{ from: from, to: to, subject: subject, } } // Execute processes the actual email sending func (m *SendMailRequest) Execute() error { mime := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n" from := "From: " + m.from + "\n" subject := "Subject: " + m.subject + "\n" msg := []byte(from + subject + mime + "\n" + m.body) // Connect to the SMTP Server smtpServerAddr := viper.GetString("SMTP_SERVER_ADDR") smtpServerHost, _, _ := net.SplitHostPort(smtpServerAddr) smtpClientAuth := smtp.PlainAuth("", viper.GetString("SMTP_CLIENT_USERNAME"), viper.GetString("SMTP_CLIENT_PASSWORD"), smtpServerHost) // TLS config tlsconfig := &tls.Config{ InsecureSkipVerify: true, ServerName: smtpServerHost, } // Important: call tls.Dial instead of smtp.Dial for smtp servers running on 465. // On port 465 ssl connection is required from the very beginning (no starttls) conn, err := tls.Dial("tcp", smtpServerAddr, tlsconfig) if err != nil { return fmt.Errorf("failed initiating smtp connection (%s)", err) } smtpClient, err := smtp.NewClient(conn, smtpServerHost) if err != nil { return fmt.Errorf("failed creating the smtp client (%s)", err) } defer smtpClient.Quit() if err = smtpClient.Auth(smtpClientAuth); err != nil { return fmt.Errorf("failed authenticating to smtp server (%s)", err) } // Initialize a mail transaction err = smtpClient.Mail(m.from) if err != nil { return fmt.Errorf("failed issuing MAIL command (%s)", err) } // Set recipents for _, recipient := range m.to { err = smtpClient.Rcpt(recipient) if err != nil { return fmt.Errorf("failed issuing RCPT command (%s)", err) } } smtpWriter, err := smtpClient.Data() if err != nil { return fmt.Errorf("failed issuing DATA command (%s)", err) } _, err = smtpWriter.Write([]byte(msg)) if err != nil { return fmt.Errorf("failed sending mail content (%s)", err) } err = smtpWriter.Close() if err != nil { return fmt.Errorf("failed close smtp client (%s)", err) } return nil } // ParseTemplate parses template and bing data and process the email sending func (m *SendMailRequest) ParseTemplate(templateFileName string, data interface{}) error { emailTpl, err := template.ParseFiles(templateFileName) if err != nil { return err } buf := new(bytes.Buffer) err = emailTpl.Execute(buf, data) if err != nil { return err } m.body = buf.String() return nil } // SendMail handles HTTP request to send email func SendMail(httpResp http.ResponseWriter, httpReq *http.Request) { AllowedOriginDomain := viper.GetString("ALLOWED_ORIGIN_DOMAIN") AllowedOrigins := map[string]bool{ fmt.Sprintf("http://%s", AllowedOriginDomain): true, fmt.Sprintf("https://%s", AllowedOriginDomain): true, fmt.Sprintf("http://www.%s", AllowedOriginDomain): true, fmt.Sprintf("https://www.%s", AllowedOriginDomain): true, } if len(httpReq.Header["Origin"]) == 0 || len(httpReq.Header["Referer"]) == 0 { rawHeader, _ := json.Marshal(httpReq.Header) log.Println("request with unexpected headers:", string(rawHeader)) httpResp.WriteHeader(http.StatusForbidden) return } reqOrigin := httpReq.Header["Origin"][0] if _, domainFound := AllowedOrigins[reqOrigin]; !domainFound { log.Println("Not allowed origin:", reqOrigin) httpResp.WriteHeader(http.StatusForbidden) return } httpReq.ParseForm() contactRequest := ContactRequest{ Name: httpReq.FormValue("name"), Email: strings.TrimSpace(httpReq.FormValue("email")), Organization: httpReq.FormValue("organization"), Subject: httpReq.FormValue("subject"), Message: httpReq.FormValue("message"), RequestTarget: httpReq.FormValue("target"), } var recipients []string switch contactRequest.RequestTarget { case "demo": recipients = []string{contactRequest.Email, viper.GetString("CONTACT_REPLY_CC_EMAIL")} case "contact": recipients = []string{viper.GetString("CONTACT_REPLY_CC_EMAIL")} default: log.Println("Not allowed request type:", contactRequest.RequestTarget) httpResp.WriteHeader(http.StatusForbidden) return } userData, _ := json.Marshal(contactRequest) log.Println("New Request:", string(userData)) templateData := struct { Name string Email string Organization string Subject string Message string DemoURL string }{ Name: contactRequest.Name, Email: contactRequest.Email, Organization: contactRequest.Organization, Subject: contactRequest.Subject, Message: contactRequest.Message, DemoURL: viper.GetString("DEMO_URL"), } contactResponse := ContactResponse{} contactEmail := viper.GetString("CONTACT_REPLY_EMAIL") sendMailReq := NewSendMailRequest( contactEmail, recipients, contactRequest.Subject, ) err := error(nil) if contactRequest.RequestTarget == "demo" { err = sendMailReq.ParseTemplate(viper.GetString("TEMPLATE_DEMO_REQUEST_REPLY"), templateData) } else { err = sendMailReq.ParseTemplate(viper.GetString("TEMPLATE_CONTACT_REQUEST_REPLY"), templateData) } if err == nil { err := sendMailReq.Execute() if err != nil { log.Printf("error: %s", err.Error()) contactResponse.Status = "error" contactResponse.Message = fmt.Sprintf("An internal error occurred, please try later or send us an email at %s.", viper.GetString("CONTACT_REPLY_CC_EMAIL")) } else { contactResponse.Status = "success" if contactRequest.RequestTarget == "demo" { contactResponse.Message = "Thank you, if you supplied a correct email address then an email should have been sent to you." } else { contactResponse.Message = "Thank you, if you supplied a correct email address then we'll process your request within the next 48 hours." } } } else { log.Printf("error: %s", err.Error()) contactResponse.Status = "error" contactResponse.Message = "Invalid request, please review your input and try again." } refererURL := strings.Split(httpReq.Header["Referer"][0], "?")[0] respRawData, _ := json.Marshal(contactResponse) httpResp.Header().Set("Location", fmt.Sprintf("%s?status=%s&message=%s", refererURL, contactResponse.Status, contactResponse.Message)) httpResp.WriteHeader(http.StatusSeeOther) httpResp.Header().Set("Content-Type", "application/json; charset=UTF-8") httpResp.Write(respRawData) }