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