[go] Best way use smtp via Proxy

package main

import (
	"encoding/base64"
	"fmt"
	"log"
	"math/rand"
	"net"
	"net/smtp"
	"os"
	"strings"
	"time"

	"github.com/pkg/errors"
	"golang.org/x/net/proxy"
)

type SocksSMTP struct {
	ProxyUserName string
	ProxyPassword string
	ProxyAddress  string

	Address  string
	UserName string
	Password string
}

func BaseEncode(s string) string {
	return fmt.Sprintf("=?UTF-8?B?%s?=", base64.StdEncoding.EncodeToString([]byte(s)))
}

func (s *SocksSMTP) CreateClient() (*smtp.Client, error) {
	var auth *proxy.Auth
	if s.ProxyUserName != "" {
		auth = &proxy.Auth{
			User:     s.ProxyUserName,
			Password: s.ProxyPassword,
		}
	}
	dialer, err := proxy.SOCKS5("tcp", s.ProxyAddress, auth, proxy.Direct)
	if err != nil {
		return nil, errors.Wrap(err, "Create SOCKS5")
	}
	conn, err := dialer.Dial("tcp", s.Address)
	if err != nil {
		return nil, errors.Wrap(err, "Dial")
	}

	host, _, _ := net.SplitHostPort(s.Address)
	c, err := smtp.NewClient(conn, host)

	return c, err
}

type _PlainAuth struct {
	identity, username, password string
	host                         string
}

func PlainAuth(identity, username, password string,
	host string) smtp.Auth {
	return &_PlainAuth{identity, username, password,
		host}
}

func (a *_PlainAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
	if server.Name != a.host {
		return "", nil, errors.New("wrong host name")
	}
	resp := []byte(a.identity + "\x00" + a.username + "\x00" + a.password)
	return "PLAIN", resp, nil
}

func (a *_PlainAuth) Next(from_server []byte, more bool) ([]byte, error) {
	if more {
		// We've already sent everything.
		return nil, errors.New("unexpected server challenge")
	}
	return nil, nil
}

// 发送文本
func (s *SocksSMTP) Send(subjct string, from_name string, to []string, body string) error {
	c, err := s.CreateClient()
	if err != nil {
		return errors.Wrap(err, "CreateClient")
	}
	defer c.Close()

	if err = c.Hello("localhost"); err != nil {
		return errors.Wrap(err, "hello")
	}

	host, _, err := net.SplitHostPort(s.Address)
	if err != nil {
		return err
	}

	if ok, _ := c.Extension("STARTTLS"); ok {
		fmt.Println("NEED TLS")
		// TODO
	}

	auth := PlainAuth("", s.UserName, s.Password, host)
	if err = c.Auth(auth); err != nil {
		return errors.Wrap(err, "Auth")
	}

	// 设置发件人
	if err = c.Mail(s.UserName); err != nil {
		return errors.Wrap(err, "Set Sender")
	}

	// 设置接收人
	for _, addr := range to {
		if err = c.Rcpt(addr); err != nil {
			return errors.Wrap(err, "Set Receiver")
		}
	}
	w, err := c.Data()
	if err != nil {
		return errors.Wrap(err, "Client.Data")
	}

	hostname, err := os.Hostname()
	if err != nil {
		return errors.Wrap(err, "Get hostname")
	}
	headers := map[string]string{}
	headers["Subject"] = BaseEncode(subjct)
	headers["To"] = strings.Join(to, ", ")
	headers["From"] = from_name
	headers["MIME-Version"] = "1.0"
	headers["Content-Type"] = "text/plain; charset=UTF-8"
	headers["Message-ID"] = fmt.Sprintf("<%f.%d@%s>", rand.Float64(),
		time.Now().UnixNano(), hostname)

	msg := ""
	for k, v := range headers {
		msg += fmt.Sprintf("%s: %s\r\n", k, v)
	}
	msg += fmt.Sprintf("\r\n" + body)

	if _, err = w.Write([]byte(msg)); err != nil {
		return errors.Wrap(err, "Writer msg")
	}
	if err = w.Close(); err != nil {
		return err
	}

	return c.Quit()
}

func main() {
	s := &SocksSMTP{
		ProxyUserName: "",
		ProxyPassword: "",
		ProxyAddress:  "",

		Address:  "<host>:<port>",
		UserName: "",
		Password: "",
	}

	err := s.Send("测试 from socks5", "from@xx.com", []string{"to@xx.com"}, "sockssmtp测试")
	if err != nil {
		log.Fatalln(err)
	}
}