parent
831c1f45ac
commit
f5b63355b9
@ -61,10 +61,10 @@ First go to [spamhaus](https://check.spamhaus.org/) and check your domain name a
|
||||
|
||||
Or
|
||||
|
||||
`docker run -p 25:25 -p 80:80 -p 443:443 -p 110:110 -p 465:465 -v $(pwd)/config:/work/config ghcr.io/jinnrry/pmail:latest`
|
||||
`docker run -p 25:25 -p 80:80 -p 443:443 -p 110:110 -p 465:465 -p 995:995 -v $(pwd)/config:/work/config ghcr.io/jinnrry/pmail:latest`
|
||||
|
||||
> [!IMPORTANT]
|
||||
> If your server has a firewall turned on, you need to open ports 25, 80, 110, 443, 465
|
||||
> If your server has a firewall turned on, you need to open ports 25, 80, 110, 443, 465, 995
|
||||
|
||||
## 3、Configuration
|
||||
|
||||
|
||||
@ -65,10 +65,10 @@ PMail是一个追求极简部署流程、极致资源占用的个人域名邮箱
|
||||
|
||||
或者
|
||||
|
||||
`docker run -p 25:25 -p 80:80 -p 443:443 -p 110:110 -p 465:465 -v $(pwd)/config:/work/config ghcr.io/jinnrry/pmail:latest`
|
||||
`docker run -p 25:25 -p 80:80 -p 443:443 -p 110:110 -p 465:465 -p 995:995 -v $(pwd)/config:/work/config ghcr.io/jinnrry/pmail:latest`
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 如果你服务器开启了防火墙,你需要放行25、80、110、443、465这五个端口
|
||||
> 如果你服务器开启了防火墙,你需要打开25、80、110、443、465、995端口
|
||||
|
||||
## 3、配置
|
||||
|
||||
|
||||
@ -39,7 +39,7 @@ type Config struct {
|
||||
//go:embed tables/*
|
||||
var tableConfig embed.FS
|
||||
|
||||
const Version = "2.3.6"
|
||||
const Version = "2.3.7"
|
||||
|
||||
const DBTypeMySQL = "mysql"
|
||||
const DBTypeSQLite = "sqlite"
|
||||
|
||||
@ -5,7 +5,7 @@ go 1.21
|
||||
require (
|
||||
github.com/Jinnrry/gopop v0.0.0-20231113115125-fbdf52ae39ea
|
||||
github.com/alexedwards/scs/mysqlstore v0.0.0-20230327161757-10d4299e3b24
|
||||
github.com/alexedwards/scs/sqlite3store v0.0.0-20230327161757-10d4299e3b24
|
||||
github.com/alexedwards/scs/sqlite3store v0.0.0-20231113091146-cef4b05350c8
|
||||
github.com/alexedwards/scs/v2 v2.5.1
|
||||
github.com/emersion/go-message v0.18.0
|
||||
github.com/emersion/go-msgauth v0.6.6
|
||||
@ -21,7 +21,7 @@ require (
|
||||
modernc.org/sqlite v1.24.0
|
||||
)
|
||||
|
||||
replace github.com/alexedwards/scs/sqlite3store v0.0.0-20230327161757-10d4299e3b24 => github.com/Jinnrry/scs/sqlite3store v0.0.0-20230803080525-914f01e0d379
|
||||
replace github.com/emersion/go-smtp v0.20.1 => github.com/jinnrry/go-smtp v0.20.1
|
||||
|
||||
require (
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
|
||||
@ -4,6 +4,9 @@ github.com/Jinnrry/scs/sqlite3store v0.0.0-20230803080525-914f01e0d379 h1:i6LB/3
|
||||
github.com/Jinnrry/scs/sqlite3store v0.0.0-20230803080525-914f01e0d379/go.mod h1:Iyk7S76cxGaiEX/mSYmTZzYehp4KfyylcLaV3OnToss=
|
||||
github.com/alexedwards/scs/mysqlstore v0.0.0-20230327161757-10d4299e3b24 h1:1jXpX7IE/zuf9FZQJpqZNepXqW8mq6NLzplHDCA43HY=
|
||||
github.com/alexedwards/scs/mysqlstore v0.0.0-20230327161757-10d4299e3b24/go.mod h1:ShejCOaSJCEjCWjc7YBrgy2xd0Kp+wiyBdzTNQrAGn4=
|
||||
github.com/alexedwards/scs/sqlite3store v0.0.0-20230327161757-10d4299e3b24/go.mod h1:Iyk7S76cxGaiEX/mSYmTZzYehp4KfyylcLaV3OnToss=
|
||||
github.com/alexedwards/scs/sqlite3store v0.0.0-20231113091146-cef4b05350c8 h1:mnXnnXEjn8QIyv4KCN0+IjDlXA64qdq2hIVOmfNFeuY=
|
||||
github.com/alexedwards/scs/sqlite3store v0.0.0-20231113091146-cef4b05350c8/go.mod h1:Iyk7S76cxGaiEX/mSYmTZzYehp4KfyylcLaV3OnToss=
|
||||
github.com/alexedwards/scs/v2 v2.5.1 h1:EhAz3Kb3OSQzD8T+Ub23fKsiuvE0GzbF5Lgn0uTwM3Y=
|
||||
github.com/alexedwards/scs/v2 v2.5.1/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
@ -22,8 +25,6 @@ github.com/emersion/go-msgauth v0.6.6 h1:buv5lL8v/3v4RpHnQFS2IPhE3nxSRX+AxnrEJbD
|
||||
github.com/emersion/go-msgauth v0.6.6/go.mod h1:A+/zaz9bzukLM6tRWRgJ3BdrBi+TFKTvQ3fGMFOI9SM=
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-smtp v0.20.1 h1:kW8Nkzomjk6W1ebfUYy6wAYThukCzD9MFOkFnragOHc=
|
||||
github.com/emersion/go-smtp v0.20.1/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
||||
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwoTQyw0fIM5xv1HF+Y+3ZijDR839WMulgxCcUY=
|
||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||
@ -44,6 +45,8 @@ github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbu
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jinnrry/go-smtp v0.20.1 h1:coy2+Ch7+ptr3J4rHwE+YVY25eaXuUbi4PNHchfc6ro=
|
||||
github.com/jinnrry/go-smtp v0.20.1/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
||||
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
||||
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
|
||||
@ -110,7 +110,7 @@ func StartWithTLS() {
|
||||
instanceTls.MaxMessageBytes = 1024 * 1024
|
||||
instanceTls.MaxRecipients = 50
|
||||
// force TLS for auth
|
||||
instanceTls.AllowInsecureAuth = false
|
||||
instanceTls.AllowInsecureAuth = true
|
||||
// Load the certificate and key
|
||||
cer, err := tls.LoadX509KeyPair(config.Instance.SSLPublicKeyPath, config.Instance.SSLPrivateKeyPath)
|
||||
if err != nil {
|
||||
|
||||
@ -154,25 +154,24 @@ func Send(ctx *context.Context, e *parsemail.Email) (error, map[string]error) {
|
||||
as.WaitProcess(func(p any) {
|
||||
|
||||
err := smtp.SendMail("", domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Warnf("SMTP Send Error! Error:%+v", err)
|
||||
} else {
|
||||
log.WithContext(ctx).Infof("SMTP Send Success !")
|
||||
}
|
||||
|
||||
// 重新选取证书域名
|
||||
// 使用其他方式发送
|
||||
if err != nil {
|
||||
if errors.Is(err, smtp.NoSupportSTARTTLSError) {
|
||||
err = smtp.SendMailWithTls("", domain.mxHost+":465", nil, e.From.EmailAddress, buildAddress(tos), b)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Warnf("Unsafe! %s Server Not Support SMTPS & STARTTLS", domain.domain)
|
||||
err = smtp.SendMailUnsafe("", domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b)
|
||||
}
|
||||
}
|
||||
|
||||
// 证书错误,从新选取证书发送
|
||||
if certificateErr, ok := err.(*tls.CertificateVerificationError); ok {
|
||||
if hostnameErr, is := certificateErr.Err.(x509.HostnameError); is {
|
||||
if hostnameErr.Certificate != nil {
|
||||
certificateHostName := hostnameErr.Certificate.DNSNames
|
||||
// smtps发送失败,尝试smtp
|
||||
// 重新选取证书发送
|
||||
err = smtp.SendMail(domainMatch(domain.domain, certificateHostName), domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Warnf("SMTP Send Error! Error:%+v", err)
|
||||
} else {
|
||||
log.WithContext(ctx).Infof("SMTP Send Success !")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -208,6 +207,10 @@ func buildAddress(u []*parsemail.User) []string {
|
||||
}
|
||||
|
||||
func domainMatch(domain string, dnsNames []string) string {
|
||||
if len(dnsNames) == 0 {
|
||||
return domain
|
||||
}
|
||||
|
||||
secondMatch := ""
|
||||
|
||||
for _, name := range dnsNames {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package send
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"os"
|
||||
"pmail/config"
|
||||
@ -49,3 +50,26 @@ func TestSend(t *testing.T) {
|
||||
}
|
||||
Send(nil, e)
|
||||
}
|
||||
|
||||
func TestSendSohu(t *testing.T) {
|
||||
testInit()
|
||||
e := &parsemail.Email{
|
||||
From: &parsemail.User{
|
||||
Name: "发送人",
|
||||
EmailAddress: "j@jinnrry.com",
|
||||
},
|
||||
To: []*parsemail.User{
|
||||
{"jinnrry@sohu.com", "名"},
|
||||
},
|
||||
Subject: "插件测试",
|
||||
Text: []byte("这是Text"),
|
||||
HTML: []byte("<div>这是Html</div>"),
|
||||
}
|
||||
Send(nil, e)
|
||||
}
|
||||
|
||||
func Test_domainMatch(t *testing.T) {
|
||||
domain := domainMatch("qq.com", nil)
|
||||
|
||||
fmt.Println(domain)
|
||||
}
|
||||
|
||||
@ -33,6 +33,8 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var NoSupportSTARTTLSError = errors.New("smtp: server doesn't support STARTTLS")
|
||||
|
||||
// A Client represents a client connection to an SMTP server.
|
||||
type Client struct {
|
||||
// Text is the textproto.Conn used by the Client. It is exported to allow for
|
||||
@ -417,7 +419,7 @@ func SendMail(domain string, addr string, a smtp.Auth, from string, to []string,
|
||||
return err
|
||||
}
|
||||
if ok, _ := c.Extension("STARTTLS"); !ok {
|
||||
return errors.New("smtp: server doesn't support STARTTLS")
|
||||
return NoSupportSTARTTLSError
|
||||
}
|
||||
|
||||
var config *tls.Config
|
||||
@ -461,6 +463,56 @@ func SendMail(domain string, addr string, a smtp.Auth, from string, to []string,
|
||||
return c.Quit()
|
||||
}
|
||||
|
||||
// SendMailUnsafe 无TLS加密的邮件发送方式
|
||||
func SendMailUnsafe(domain string, addr string, a smtp.Auth, from string, to []string, msg []byte) error {
|
||||
if err := validateLine(from); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, recp := range to {
|
||||
if err := validateLine(recp); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
c, err := Dial(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Close()
|
||||
if err = c.hello(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if a != nil && c.ext != nil {
|
||||
if _, ok := c.ext["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()
|
||||
}
|
||||
|
||||
// Extension reports whether an extension is support by the server.
|
||||
// The extension name is case-insensitive. If the extension is supported,
|
||||
// Extension also returns a string that contains any parameters the
|
||||
|
||||
Loading…
Reference in New Issue
Block a user