|
|
|
package smtpserver
|
|
|
|
|
|
|
|
import (
|
|
|
|
"b612.me/starlog"
|
|
|
|
"b612.me/startext"
|
|
|
|
"bytes"
|
|
|
|
"crypto/tls"
|
|
|
|
"fmt"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"html"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"mime"
|
|
|
|
"mime/quotedprintable"
|
|
|
|
"net/mail"
|
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/emersion/go-smtp"
|
|
|
|
)
|
|
|
|
|
|
|
|
var addr string
|
|
|
|
var user, pass string
|
|
|
|
var allowAnyuser bool
|
|
|
|
var output string
|
|
|
|
var domain string
|
|
|
|
var cert, key string
|
|
|
|
|
|
|
|
var Cmd = &cobra.Command{
|
|
|
|
Use: "smtps",
|
|
|
|
Short: "smtp server",
|
|
|
|
Long: "smtp server",
|
|
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
|
|
run()
|
|
|
|
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
Cmd.Flags().StringVarP(&addr, "addr", "a", "0.0.0.0:25", "smtp server listen address")
|
|
|
|
Cmd.Flags().StringVarP(&user, "user", "u", "admin", "smtp server username")
|
|
|
|
Cmd.Flags().StringVarP(&pass, "pass", "p", "admin", "smtp server password")
|
|
|
|
Cmd.Flags().BoolVarP(&allowAnyuser, "allow-anyuser", "A", false, "allow any user")
|
|
|
|
Cmd.Flags().StringVarP(&output, "output", "o", "", "output mail to html")
|
|
|
|
Cmd.Flags().StringVarP(&domain, "domain", "d", "localhost", "smtp server domain")
|
|
|
|
Cmd.Flags().StringVarP(&cert, "cert", "c", "", "smtp server cert(TLS)")
|
|
|
|
Cmd.Flags().StringVarP(&key, "key", "k", "", "smtp server key(TLS)")
|
|
|
|
}
|
|
|
|
|
|
|
|
type backend struct{}
|
|
|
|
|
|
|
|
func (bkd *backend) NewSession(c *smtp.Conn) (smtp.Session, error) {
|
|
|
|
return &session{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type session struct {
|
|
|
|
username string
|
|
|
|
password string
|
|
|
|
to string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *session) AuthPlain(username, password string) error {
|
|
|
|
s.username = username
|
|
|
|
s.password = password
|
|
|
|
starlog.Printf("username:%s,password:%s\n", username, password)
|
|
|
|
if allowAnyuser {
|
|
|
|
return nil
|
|
|
|
} else {
|
|
|
|
if username != user || password != pass {
|
|
|
|
return smtp.ErrAuthFailed
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *session) Mail(from string, opts *smtp.MailOptions) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *session) Rcpt(to string, opts *smtp.RcptOptions) error {
|
|
|
|
s.to += to + ";"
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *session) Data(r io.Reader) error {
|
|
|
|
mailData, err := ioutil.ReadAll(r)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
msg, err := mail.ReadMessage(bytes.NewReader(mailData))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
header := msg.Header
|
|
|
|
subject := header.Get("Subject")
|
|
|
|
to := header.Get("To")
|
|
|
|
cc := header.Get("Cc")
|
|
|
|
from := header.Get("From") // 获取发件人
|
|
|
|
date := header.Get("Date")
|
|
|
|
body, err := ioutil.ReadAll(msg.Body)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var bodyStr string
|
|
|
|
|
|
|
|
var d = new(mime.WordDecoder)
|
|
|
|
{
|
|
|
|
subject, err = d.DecodeHeader(subject)
|
|
|
|
if err != nil {
|
|
|
|
starlog.Errorf("Decode subject %s error:%s\n", subject, err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
bodyStr, err = bodyDecode(string(body))
|
|
|
|
if err != nil {
|
|
|
|
starlog.Errorf("Decode body %s error:%s\n", string(body), err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if startext.IsGBK([]byte(bodyStr)) {
|
|
|
|
tmp, err := startext.GBK2UTF8([]byte(bodyStr))
|
|
|
|
if err == nil {
|
|
|
|
bodyStr = string(tmp)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
starlog.Println("From:", from)
|
|
|
|
starlog.Println("Subject:", subject)
|
|
|
|
starlog.Println("To ALL:", s.to)
|
|
|
|
starlog.Println("To:", to)
|
|
|
|
starlog.Println("Cc:", cc)
|
|
|
|
starlog.Println("Body:", bodyStr)
|
|
|
|
if output != "" {
|
|
|
|
path := fmt.Sprintf("%s/%s_%s.html", output, subject, time.Now().Format("2006_01_02_15_04_05_"))
|
|
|
|
html := fmt.Sprintf(`<html><head>
|
|
|
|
<meta charset="utf-8">
|
|
|
|
<title>%s</title>
|
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<h2>%s</h2>
|
|
|
|
<hr>
|
|
|
|
<p>auth user:<strong> %s </strong></p>
|
|
|
|
<p>auth pass:<strong> %s </strong></p>
|
|
|
|
<hr>
|
|
|
|
<p>Date:<strong> %v </strong></p>
|
|
|
|
<p>From:<strong> %s </strong></p>
|
|
|
|
<p>To All:<strong> %s </strong></p>
|
|
|
|
<p>To:<strong> %s </strong></p>
|
|
|
|
<p>Cc:<strong> %s </strong></p>
|
|
|
|
<hr>
|
|
|
|
<br />
|
|
|
|
<br />
|
|
|
|
%s
|
|
|
|
</body>
|
|
|
|
</html>`, html.EscapeString(subject), html.EscapeString(subject), html.EscapeString(s.username), html.EscapeString(s.password),
|
|
|
|
date, html.EscapeString(from), html.EscapeString(s.to), html.EscapeString(to), html.EscapeString(cc), bodyStr)
|
|
|
|
os.WriteFile(path, []byte(html), 0644)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *session) Reset() {}
|
|
|
|
|
|
|
|
func (s *session) Logout() error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func run() {
|
|
|
|
var err error
|
|
|
|
s := smtp.NewServer(&backend{})
|
|
|
|
if cert != "" && key != "" {
|
|
|
|
var config tls.Config
|
|
|
|
config.Certificates = make([]tls.Certificate, 1)
|
|
|
|
config.Certificates[0], err = tls.LoadX509KeyPair(cert, key)
|
|
|
|
if err != nil {
|
|
|
|
starlog.Errorln("failed to load cert:", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
s.TLSConfig = &config
|
|
|
|
s.Addr = addr
|
|
|
|
s.Domain = domain
|
|
|
|
s.AllowInsecureAuth = true
|
|
|
|
s.Debug = os.Stdout
|
|
|
|
starlog.Infoln("Starting TLS-SMTP server at", addr)
|
|
|
|
starlog.Errorln(s.ListenAndServeTLS())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
s.Addr = addr
|
|
|
|
s.Domain = domain
|
|
|
|
s.AllowInsecureAuth = true
|
|
|
|
s.Debug = os.Stdout
|
|
|
|
|
|
|
|
starlog.Infoln("Starting SMTP server at", addr)
|
|
|
|
starlog.Errorln(s.ListenAndServe())
|
|
|
|
}
|
|
|
|
|
|
|
|
func bodyDecode(encoded string) (string, error) {
|
|
|
|
encoded = strings.Replace(encoded, "=\r\n", "", -1) // 对于Windows系统
|
|
|
|
encoded = strings.Replace(encoded, "=\n", "", -1) // 对于UNIX/Linux系统
|
|
|
|
|
|
|
|
// 创建一个新的Quoted-Printable阅读器
|
|
|
|
reader := quotedprintable.NewReader(strings.NewReader(encoded))
|
|
|
|
|
|
|
|
// 读取并解码整个内容
|
|
|
|
decoded, err := ioutil.ReadAll(reader)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("Error decoding string:", err)
|
|
|
|
return string(decoded), err
|
|
|
|
}
|
|
|
|
return string(decoded), nil
|
|
|
|
}
|