forked from d--j/go-milter
-
Notifications
You must be signed in to change notification settings - Fork 0
/
macro.go
327 lines (294 loc) · 10.6 KB
/
macro.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
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
package milter
import (
"fmt"
"strings"
"sync"
"time"
"unicode"
)
type MacroStage = byte
const (
StageConnect MacroStage = iota // SMFIM_CONNECT
StageHelo // SMFIM_HELO
StageMail // SMFIM_ENVFROM
StageRcpt // SMFIM_ENVRCPT
StageData // SMFIM_DATA
StageEOM // SMFIM_EOM
StageEOH // SMFIM_EOH
StageEndMarker // is used for command level macros for Abort, Unknown and Header commands
StageNotFoundMarker // identifies that a macro was not found
)
type MacroName = string
// Macros that have good support between MTAs like sendmail and Postfix
const (
MacroMTAVersion MacroName = "v" // MTA Version (and MTA name in case of Postfix)
MacroMTAFQDN MacroName = "j" // MTA fully qualified domain name
MacroDaemonName MacroName = "{daemon_name}" // name of the daemon of the MTA. E.g. MTA-v4 or smtpd or anything the user configured.
MacroDaemonAddr MacroName = "{daemon_addr}" // Local server IP address
MacroDaemonPort MacroName = "{daemon_port}" // Local server TCP port
MacroIfName MacroName = "{if_name}" // Interface name of the interface the MTA is accepting the SMTP connection
MacroIfAddr MacroName = "{if_addr}" // IP address of the interface the MTA is accepting the SMTP connection
MacroTlsVersion MacroName = "{tls_version}" // TLS version in use (set after STARTTLS or when SMTPS is used)
MacroCipher MacroName = "{cipher}" // Cipher suite used (set after STARTTLS or when SMTPS is used)
MacroCipherBits MacroName = "{cipher_bits}" // Strength of the cipher suite in bits (set after STARTTLS or when SMTPS is used)
MacroCertSubject MacroName = "{cert_subject}" // Validated client cert's subject information (only when mutual TLS is in use)
MacroCertIssuer MacroName = "{cert_issuer}" // Validated client cert's issuer information (only when mutual TLS is in use)
MacroClientAddr MacroName = "{client_addr}" // Remote client IP address
MacroClientPort MacroName = "{client_port}" // Remote client TCP port
MacroClientPTR MacroName = "{client_ptr}" // Client name from address → name lookup
MacroClientName MacroName = "{client_name}" // Remote client hostname
MacroClientConnections MacroName = "{client_connections}" // Connection concurrency for this client
MacroQueueId MacroName = "i" // The queue ID for this message. Some MTAs only assign a Queue ID after the DATA command (Postfix)
MacroAuthType MacroName = "{auth_type}" // The used authentication method (LOGIN, DIGEST-MD5, etc)
MacroAuthAuthen MacroName = "{auth_authen}" // The username of the authenticated user
MacroAuthSsf MacroName = "{auth_ssf}" // The key length (in bits) of the used encryption layer (TLS) – if any
MacroAuthAuthor MacroName = "{auth_author}" // The optional overwrite username for this message
MacroMailMailer MacroName = "{mail_mailer}" // the delivery agent for this MAIL FROM (e.g. esmtp, lmtp)
MacroMailHost MacroName = "{mail_host}" // the domain part of the MAIL FROM address
MacroMailAddr MacroName = "{mail_addr}" // the MAIL FROM address (only the address without <>)
MacroRcptMailer MacroName = "{rcpt_mailer}" // MacroRcptMailer holds the delivery agent for the current RCPT TO address
MacroRcptHost MacroName = "{rcpt_host}" // The domain part of the RCPT TO address
MacroRcptAddr MacroName = "{rcpt_addr}" // the RCPT TO address (only the address without <>)
)
// Macros that do not have good cross-MTA support. Only usable with sendmail as MTA.
const (
MacroRFC1413AuthInfo MacroName = "_"
MacroHopCount MacroName = "c"
MacroSenderHostName MacroName = "s"
MacroProtocolUsed MacroName = "r"
MacroMTAPid MacroName = "p"
MacroDateRFC822Origin MacroName = "a"
MacroDateRFC822Current MacroName = "b"
MacroDateANSICCurrent MacroName = "d"
MacroDateSecondsCurrent MacroName = "t"
)
type macroRequests [][]MacroName
type Macros interface {
Get(name MacroName) string
GetEx(name MacroName) (value string, ok bool)
}
// MacroBag is a default implementation of the Macros interface.
// A MacroBag is safe for concurrent use by multiple goroutines.
// It has special handling for the date related macros and can be copied.
//
// The zero value of MacroBag is invalid. Use [NewMacroBag] to create an empty MacroBag.
type MacroBag struct {
macros map[MacroName]string
mutex sync.RWMutex
currentDate, headerDate time.Time
}
func NewMacroBag() *MacroBag {
return &MacroBag{
macros: make(map[MacroName]string),
}
}
func (m *MacroBag) Get(name MacroName) string {
v, _ := m.GetEx(name)
return v
}
func (m *MacroBag) GetEx(name MacroName) (value string, ok bool) {
m.mutex.RLock()
defer m.mutex.RUnlock()
value, ok = m.macros[name]
if !ok {
switch name {
case MacroDateRFC822Origin:
if !m.headerDate.IsZero() {
ok = true
value = m.headerDate.Format(time.RFC822Z)
}
case MacroDateRFC822Current, MacroDateSecondsCurrent, MacroDateANSICCurrent:
ok = true
current := m.currentDate
if current.IsZero() {
current = time.Now()
}
switch name {
case MacroDateRFC822Current:
value = current.Format(time.RFC822Z)
case MacroDateSecondsCurrent:
value = fmt.Sprintf("%d", current.Unix())
case MacroDateANSICCurrent:
value = current.Format(time.ANSIC)
}
}
}
return
}
func (m *MacroBag) Set(name MacroName, value string) {
m.mutex.Lock()
defer m.mutex.Unlock()
m.macros[name] = value
}
// Copy copies the macros to a new MacroBag.
// The time.Time values set by [MacroBag.SetCurrentDate] and [MacroBag.SetHeaderDate] do not get copied.
func (m *MacroBag) Copy() *MacroBag {
m.mutex.Lock()
defer m.mutex.Unlock()
macros := make(map[MacroName]string)
for k, v := range m.macros {
macros[k] = v
}
return &MacroBag{macros: macros}
}
func (m *MacroBag) SetCurrentDate(date time.Time) {
m.mutex.Lock()
defer m.mutex.Unlock()
m.currentDate = date
}
func (m *MacroBag) SetHeaderDate(date time.Time) {
m.mutex.Lock()
defer m.mutex.Unlock()
m.headerDate = date
}
var _ Macros = &MacroBag{}
type macrosStages struct {
byStages []map[MacroName]string
}
func newMacroStages() *macrosStages {
return ¯osStages{
byStages: make([]map[MacroName]string, StageEndMarker+1),
}
}
func (s *macrosStages) GetMacroEx(name MacroName) (value string, stageFound MacroStage) {
i := StageEndMarker
for {
if s.byStages[i] != nil {
if v, ok := s.byStages[i][name]; ok {
return v, i
}
}
if i == StageConnect {
return "", StageNotFoundMarker
}
i--
}
}
func (s *macrosStages) SetMacro(stage MacroStage, name MacroName, value string) {
if len(s.byStages) < int(stage) {
panic(fmt.Sprintf("tried to set macro in invalid stage %v", stage))
}
if s.byStages[stage] == nil {
s.byStages[stage] = make(map[MacroName]string)
}
s.byStages[stage][name] = value
}
func (s *macrosStages) SetStageMap(stage MacroStage, kv map[MacroName]string) {
if len(s.byStages) < int(stage) {
panic(fmt.Sprintf("tried to set invalid stage %v", stage))
}
s.byStages[stage] = make(map[MacroName]string)
for k, v := range kv {
s.byStages[stage][k] = v
}
}
func (s *macrosStages) SetStage(stage MacroStage, kv ...string) {
if len(kv)%2 != 0 {
panic(fmt.Sprintf("kv needs to have an even amount of entries, not %d", len(kv)))
}
if len(s.byStages) < int(stage) {
panic(fmt.Sprintf("tried to set invalid stage %v", stage))
}
s.byStages[stage] = make(map[MacroName]string)
k := ""
for i, str := range kv {
if i%2 == 0 {
k = str
} else {
s.byStages[stage][k] = str
}
}
}
func (s *macrosStages) DelMacro(stage MacroStage, name MacroName) {
if s.byStages[stage] == nil {
return
}
delete(s.byStages[stage], name)
if len(s.byStages[stage]) == 0 {
s.byStages[stage] = nil
}
}
func (s *macrosStages) DelStage(stage MacroStage) {
s.byStages[stage] = nil
}
func (s *macrosStages) DelStageAndAbove(stage MacroStage) {
var stages []MacroStage
switch stage {
case StageConnect:
stages = []MacroStage{StageConnect, StageHelo, StageMail, StageRcpt, StageData, StageEOH, StageEOM, StageEndMarker}
case StageHelo:
stages = []MacroStage{StageHelo, StageMail, StageRcpt, StageData, StageEOH, StageEOM, StageEndMarker}
case StageMail:
stages = []MacroStage{StageMail, StageRcpt, StageData, StageEOH, StageEOM, StageEndMarker}
case StageRcpt:
stages = []MacroStage{StageRcpt, StageData, StageEOH, StageEOM, StageEndMarker}
case StageData:
stages = []MacroStage{StageData, StageEOH, StageEOM, StageEndMarker}
case StageEOH:
stages = []MacroStage{StageEOH, StageEOM, StageEndMarker}
case StageEOM:
stages = []MacroStage{StageEOM, StageEndMarker}
case StageEndMarker:
stages = []MacroStage{StageEndMarker}
}
for _, st := range stages {
s.byStages[st] = nil
}
}
// macroReader is a read-only Macros compatible view of its macroStages
type macroReader struct {
macrosStages *macrosStages
}
func (r *macroReader) GetEx(name MacroName) (val string, ok bool) {
if r == nil || r.macrosStages == nil {
return "", false
}
var stage MacroStage
val, stage = r.macrosStages.GetMacroEx(name)
ok = stage <= StageEndMarker // StageEndMarker is for command-level macros
return
}
func (r *macroReader) Get(name MacroName) string {
v, _ := r.GetEx(name)
return v
}
var _ Macros = ¯oReader{}
func parseRequestedMacros(str string) []string {
return removeEmpty(strings.FieldsFunc(str, func(r rune) bool {
return unicode.IsSpace(r) || r == ','
}))
}
func removeEmpty(str []string) []string {
if len(str) == 0 {
return []string{}
}
indexesToKeep := make([]int, 0, len(str))
for i, s := range str {
if len(s) > 0 {
indexesToKeep = append(indexesToKeep, i)
}
}
r := make([]string, 0, len(indexesToKeep))
for _, index := range indexesToKeep {
r = append(r, str[index])
}
return r
}
func removeDuplicates(str []string) []string {
if len(str) == 0 {
return []string{}
}
found := make(map[string]bool, len(str))
indexesToKeep := make([]int, 0, len(str))
for i, v := range str {
if !found[v] {
indexesToKeep = append(indexesToKeep, i)
found[v] = true
}
}
noDuplicates := make([]string, len(indexesToKeep))
for i, index := range indexesToKeep {
noDuplicates[i] = str[index]
}
return noDuplicates
}