You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
star/smtpserver/smtp.go

213 lines
5.0 KiB
Go

8 months ago
package smtpserver
import (
"b612.me/starlog"
8 months ago
"b612.me/startext"
8 months ago
"bytes"
8 months ago
"crypto/tls"
8 months ago
"fmt"
"github.com/spf13/cobra"
8 months ago
"html"
8 months ago
"io"
"io/ioutil"
8 months ago
"mime"
"mime/quotedprintable"
8 months ago
"net/mail"
"os"
8 months ago
"strings"
"time"
8 months ago
"github.com/emersion/go-smtp"
)
var addr string
var user, pass string
var allowAnyuser bool
var output string
var domain string
8 months ago
var cert, key string
8 months ago
var Cmd = &cobra.Command{
8 months ago
Use: "smtps",
8 months ago
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")
8 months ago
Cmd.Flags().StringVarP(&cert, "cert", "c", "", "smtp server cert(TLS)")
Cmd.Flags().StringVarP(&key, "key", "k", "", "smtp server key(TLS)")
8 months ago
}
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")
8 months ago
to := header.Get("To")
8 months ago
cc := header.Get("Cc")
from := header.Get("From") // 获取发件人
8 months ago
date := header.Get("Date")
8 months ago
body, err := ioutil.ReadAll(msg.Body)
if err != nil {
return err
}
8 months ago
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)
8 months ago
if output != "" {
8 months ago
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)
8 months ago
os.WriteFile(path, []byte(html), 0644)
}
return nil
}
func (s *session) Reset() {}
func (s *session) Logout() error {
return nil
}
func run() {
8 months ago
var err error
8 months ago
s := smtp.NewServer(&backend{})
8 months ago
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
}
8 months ago
s.Addr = addr
s.Domain = domain
s.AllowInsecureAuth = true
s.Debug = os.Stdout
starlog.Infoln("Starting SMTP server at", addr)
starlog.Errorln(s.ListenAndServe())
}
8 months ago
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
}