// Package cmd is a wrapper library to send mail package cmd import ( "crypto/tls" "errors" "fmt" "net" "net/mail" "net/smtp" "os" "strings" ) // Send is a wrapper for smtp.SendMail() which also supports sending via unix sockets. // Unix sockets must be set as unix:/path/to/socket // It does not support authentication. func Send(addr string, from string, to []string, msg []byte) error { socketPath, isSocket := socketAddress(addr) fromAddress, err := mail.ParseAddress(from) if err != nil { return fmt.Errorf("invalid from address: %s", from) } if len(to) == 0 { return fmt.Errorf("no To addresses specified") } if err := validateLine(fromAddress.Address); err != nil { return err } for _, recipient := range to { if err := validateLine(recipient); err != nil { return err } } if !isSocket { return sendMail(addr, nil, fromAddress.Address, to, msg) } conn, err := net.Dial("unix", socketPath) if err != nil { return fmt.Errorf("error connecting to %s", addr) } client, err := smtp.NewClient(conn, "") if err != nil { _ = conn.Close() return err } defer func() { _ = client.Close() }() if err := client.Mail(fromAddress.Address); err != nil { return fmt.Errorf("error setting sender: %w", err) } for _, a := range to { if err := client.Rcpt(a); err != nil { return err } } wc, err := client.Data() if err != nil { return err } if _, err := wc.Write(msg); err != nil { return err } if err := wc.Close(); err != nil { return err } return client.Quit() } func sendMail(addr string, a smtp.Auth, from string, to []string, msg []byte) error { c, err := smtp.Dial(addr) if err != nil { return err } defer func() { _ = c.Close() }() // Use the local hostname for EHLO/HELO as required by RFC 5321. // Fall back to "localhost" if the hostname cannot be determined. localHostname, err := os.Hostname() if err != nil { localHostname = "localhost" } if err = c.Hello(localHostname); err != nil { return err } if ok, _ := c.Extension("STARTTLS"); ok { config := &tls.Config{ServerName: addr, InsecureSkipVerify: true} // #nosec if err = c.StartTLS(config); err != nil { return err } } if a != nil { if ok, _ := c.Extension("AUTH"); !ok { return errors.New("smtp: server doesn't support AUTH") } if err = c.Auth(a); err != nil { return err } } if err = c.Mail(from); err != nil { return err } for _, addr := range to { if err = c.Rcpt(addr); err != nil { return err } } w, err := c.Data() if err != nil { return err } _, err = w.Write(msg) if err != nil { return err } err = w.Close() if err != nil { return err } return c.Quit() } // validateLine checks to see if a line has CR or LF as per RFC 5321. func validateLine(line string) error { if strings.ContainsAny(line, "\n\r") { return errors.New("smtp: A line must not contain CR or LF") } return nil }