-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathmain.go
246 lines (233 loc) · 9.2 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
package main
import (
"bytes"
"crypto/tls"
"fmt"
"io"
"mime"
"net"
"os"
"regexp"
"strings"
"time"
"github.com/jhillyerd/enmime"
//"github.com/mileusna/spf"
"blitiri.com.ar/go/spf"
"github.com/sirupsen/logrus" // 引入logrus包
"github.com/yumusb/go-smtp"
)
func main() {
// 设置logrus为JSON格式
logrus.SetFormatter(&logrus.JSONFormatter{})
logrus.SetOutput(os.Stdout)
logrus.SetLevel(logrus.InfoLevel)
// 加载配置
err := LoadConfig("config.yml")
if err != nil {
logrus.Fatalf("Error loading config: %v", err)
}
logrus.Infof("Telegram Chat ID: %s", CONFIG.Telegram.ChatID)
//spf.DNSServer = "1.1.1.1:53"
be := &Backend{}
// Plain SMTP server with STARTTLS support
plainServer := smtp.NewServer(be)
plainServer.Addr = CONFIG.SMTP.ListenAddress
plainServer.Domain = GetEnv("MXDOMAIN", "localhost")
plainServer.WriteTimeout = 10 * time.Second
plainServer.ReadTimeout = 10 * time.Second
plainServer.MaxMessageBytes = 1024 * 1024
plainServer.MaxRecipients = 50
plainServer.AllowInsecureAuth = false // Change to true if you want to allow plain auth before STARTTLS (not recommended)
// Attempt to load TLS configuration for STARTTLS and SMTPS
cer, err := tls.LoadX509KeyPair(CONFIG.SMTP.CertFile, CONFIG.SMTP.KeyFile)
if err != nil {
logrus.Warnf("Loading TLS certificate failed: %v", err)
logrus.Infof("Starting plainServer only at %s", CONFIG.SMTP.ListenAddress)
// Start only the plain SMTP server with STARTTLS in a new goroutine
if err := plainServer.ListenAndServe(); err != nil {
logrus.Fatal(err)
}
} else {
// Certificate loaded successfully, configure STARTTLS
plainServer.TLSConfig = &tls.Config{Certificates: []tls.Certificate{cer}}
// SMTPS server (TLS only)
tlsServer := smtp.NewServer(be)
tlsServer.Addr = CONFIG.SMTP.ListenAddressTls
tlsServer.Domain = GetEnv("MXDOMAIN", "localhost")
tlsServer.WriteTimeout = 10 * time.Second
tlsServer.ReadTimeout = 10 * time.Second
tlsServer.MaxMessageBytes = 1024 * 1024
tlsServer.MaxRecipients = 50
tlsServer.AllowInsecureAuth = false
tlsServer.TLSConfig = &tls.Config{Certificates: []tls.Certificate{cer}}
// Start the plain SMTP server with STARTTLS in a new goroutine
go func() {
logrus.Infof("Starting plainServer at %s", CONFIG.SMTP.ListenAddress)
if err := plainServer.ListenAndServe(); err != nil {
logrus.Fatal(err)
}
}()
// Start the SMTPS server (TLS only)
logrus.Infof("Starting tlsServer at %s", CONFIG.SMTP.ListenAddressTls)
if err := tlsServer.ListenAndServeTLS(); err != nil {
logrus.Fatal(err)
}
}
}
func SPFCheck(s *Session) *smtp.SMTPError {
remoteHost, _, err := net.SplitHostPort(s.remoteIP)
if err != nil {
logrus.Warn("parse remote addr failed")
return &smtp.SMTPError{Code: 550, EnhancedCode: smtp.EnhancedCode{5, 1, 0}, Message: "Invalid remote address"}
}
remoteIP := net.ParseIP(remoteHost)
s.spfResult, err = spf.CheckHostWithSender(remoteIP, s.remoteclientHostname, s.from)
if err != nil {
logrus.Warnf("SPF check Result: %v - UUID: %s", err, s.UUID)
//return &smtp.SMTPError{Code: 550, EnhancedCode: smtp.EnhancedCode{5, 7, 0}, Message: "SPF check failed"}
}
logrus.Infof("SPF Result: %v - Domain: %s, Remote IP: %s, Sender: %s - UUID: %s", s.spfResult, getDomainFromEmail(s.from), remoteHost, s.from, s.UUID)
switch s.spfResult {
case spf.None:
logrus.Warnf("SPF Result: NONE - No SPF record found for domain %s. Rejecting email.", getDomainFromEmail(s.from))
return &smtp.SMTPError{Code: 450, EnhancedCode: smtp.EnhancedCode{5, 0, 0}, Message: "SPF check softfail (no SPF record)"}
case spf.Neutral:
logrus.Infof("SPF Result: NEUTRAL - Domain %s neither permits nor denies sending mail from IP %s", getDomainFromEmail(s.from), s.remoteIP)
case spf.Pass:
logrus.Infof("SPF Result: PASS - SPF check passed for domain %s, email is legitimate", getDomainFromEmail(s.from))
case spf.Fail:
logrus.Warnf("SPF Result: FAIL - SPF check failed for domain %s, mail from IP %s is unauthorized", getDomainFromEmail(s.from), s.remoteIP)
return &smtp.SMTPError{Code: 550, EnhancedCode: smtp.EnhancedCode{5, 7, 0}, Message: "SPF check failed"}
case spf.SoftFail:
logrus.Warnf("SPF Result: SOFTFAIL - SPF check soft failed for domain %s, email is suspicious", getDomainFromEmail(s.from))
return &smtp.SMTPError{Code: 450, EnhancedCode: smtp.EnhancedCode{5, 0, 1}, Message: "SPF check softfail"}
case spf.TempError:
logrus.Warnf("SPF Result: TEMPERROR - Temporary SPF error occurred for domain %s, retry might succeed", getDomainFromEmail(s.from))
return &smtp.SMTPError{Code: 451, EnhancedCode: smtp.EnhancedCode{4, 0, 0}, Message: "Temporary SPF check error"}
case spf.PermError:
logrus.Warnf("SPF Result: PERMERROR - Permanent SPF error for domain %s, SPF record is invalid", getDomainFromEmail(s.from))
return &smtp.SMTPError{Code: 550, EnhancedCode: smtp.EnhancedCode{5, 1, 2}, Message: "SPF check permanent error"}
}
return nil // SPF 检查通过,返回 nil
}
func (s *Session) Data(r io.Reader) error {
buf := new(bytes.Buffer)
_, err := buf.ReadFrom(r)
if err != nil {
return fmt.Errorf("error reading data: %v", err)
}
data := buf.Bytes()
env, err := enmime.ReadEnvelope(bytes.NewReader(data))
if err != nil {
logrus.Errorf("Failed to parse email: %v - UUID: %s", err, s.UUID)
return err
}
logrus.Infof("Received email: From=%s HeaderTo=%s ParsedTo=%v Subject=%s - UUID: %s",
env.GetHeader("From"),
env.GetHeader("To"),
s.to,
env.GetHeader("Subject"),
s.UUID)
var attachments []string
for _, attachment := range env.Attachments {
disposition := attachment.Header.Get("Content-Disposition")
if disposition != "" {
_, params, _ := mime.ParseMediaType(disposition)
if filename, ok := params["filename"]; ok {
attachments = append(attachments, filename)
}
}
}
parsedContent := fmt.Sprintf(
"📧 New Email Notification\n"+
"=================================\n"+
"📤 From: %s\n"+
"📬 To: %s\n"+
"---------------------------------\n"+
"🔍 SPF Status: %s\n"+
"📝 Subject: %s\n"+
"📅 Date: %s\n"+
"📄 Content-Type: %s\n"+
"=================================\n\n"+
"✉️ Email Body:\n\n%s\n\n"+
"=================================\n"+
"📎 Attachments:\n%s\n"+
"=================================\n"+
"🔑 UUID: %s",
s.from,
strings.Join(s.to, ", "),
s.spfResult,
env.GetHeader("Subject"),
env.GetHeader("Date"),
getPrimaryContentType(env.GetHeader("Content-Type")),
env.Text,
strings.Join(attachments, "\n"),
s.UUID,
)
parsedTitle := fmt.Sprintf("📬 New Email: %s", env.GetHeader("Subject"))
s.msgId = env.GetHeader("Message-ID")
if s.msgId == "" {
s.msgId = env.GetHeader("Message-Id")
}
sender := extractEmails(env.GetHeader("From"))
recipient := getFirstMatchingEmail(s.to)
if !strings.EqualFold(sender, CONFIG.SMTP.PrivateEmail) && !strings.Contains(recipient, "_at_") && !regexp.MustCompile(`^(\w|-)+@.+$`).MatchString(recipient) {
// 验证收件人的规则
logrus.Warnf("不符合规则的收件人,需要是 random@qq.com、ran-dom@qq.com,当前为 %s - UUID: %s", recipient, s.UUID)
return &smtp.SMTPError{
Code: 550,
EnhancedCode: smtp.EnhancedCode{5, 1, 0},
Message: "Invalid recipient",
}
}
var outsite2private bool
outsite2private = false
if CONFIG.SMTP.PrivateEmail != "" {
formattedSender := ""
targetAddress := ""
if strings.EqualFold(sender, CONFIG.SMTP.PrivateEmail) && strings.Contains(recipient, "_at_") {
// 来自私密邮箱,需要将邮件转发到目标邮箱
originsenderEmail, selfsenderEmail := parseEmails(recipient)
targetAddress = originsenderEmail
formattedSender = selfsenderEmail
outsite2private = false
logrus.Infof("Private 2 outside, ([%s] → [%s]) changed to ([%s] → [%s]) - UUID: %s", sender, recipient, formattedSender, targetAddress, s.UUID)
} else if strings.EqualFold(sender, CONFIG.SMTP.PrivateEmail) && !strings.Contains(recipient, "_at_") {
// 来自私密邮箱,但目标邮箱写的有问题
logrus.Infof("not need forward, from %s to %s - UUID: %s", sender, recipient, s.UUID)
// 不需要转发,但是可能需要通知给用户。
return nil
} else {
// 来自非私密邮箱,需要将邮件转发到私密邮箱
domain := getDomainFromEmail(recipient)
formattedSender = fmt.Sprintf("%s_%s@%s",
strings.ReplaceAll(strings.ReplaceAll(sender, "@", "_at_"), ".", "_"),
strings.Split(recipient, "@")[0],
domain)
targetAddress = CONFIG.SMTP.PrivateEmail
logrus.Infof("Outside 2 private, ([%s] → [%s]) changed to ([%s] → [%s]) - UUID: %s", sender, recipient, formattedSender, targetAddress, s.UUID)
outsite2private = true
}
go forwardEmailToTargetAddress(data, formattedSender, targetAddress, s)
if outsite2private {
if CONFIG.Telegram.ChatID != "" {
go sendToTelegramBot(parsedContent, s.UUID)
if CONFIG.Telegram.SendEML {
go sendRawEMLToTelegram(data, env.GetHeader("Subject"), s.UUID)
} else {
logrus.Info("Telegram EML is disabled.")
}
} else {
logrus.Info("Telegram is disabled.")
}
if CONFIG.Webhook.Enabled {
go sendWebhook(CONFIG.Webhook, parsedTitle, parsedContent, s.UUID)
} else {
logrus.Info("Webhook is disabled.")
}
}
} else {
logrus.Info("Email forwarder is disabled.")
}
return nil
}