forked from emersion/go-milter
-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathresponse.go
More file actions
164 lines (148 loc) · 6.55 KB
/
response.go
File metadata and controls
164 lines (148 loc) · 6.55 KB
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
package milter
import (
"fmt"
"strings"
"github.com/d--j/go-milter/internal/wire"
"github.com/d--j/go-milter/milterutil"
)
// Response represents a response structure returned by callback
// handlers to indicate how the milter server should proceed
type Response struct {
code wire.Code
data []byte
}
// Response returns message instance with data
func (r *Response) Response() *wire.Message {
return &wire.Message{Code: r.code, Data: r.data}
}
// Continue returns false if the MTA should stop sending events for this transaction, true otherwise.
// If the Response is for a RCPT TO event, this function will return true if the MTA should accept this recipient.
// A [RespDiscard] Response will return false because the MTA should end sending events for the current
// SMTP transaction to this milter.
func (r *Response) Continue() bool {
switch wire.ActionCode(r.code) {
case wire.ActAccept, wire.ActDiscard, wire.ActReject, wire.ActTempFail, wire.ActReplyCode:
return false
default:
return true
}
}
// String returns a string representation of this response.
// Can be used for logging purposes.
// This method will always return a logfmt compatible string.
// We try to not alter the output of this method arbitrarily – but we do not make any guaranties.
//
// It sometimes internally examines the bytes that will be sent over the wire with the parsing code
// of the client part of this library. This is not the most performant implementation, so
// you might opt to not use this method when your code needs to be performant.
func (r *Response) String() string {
switch wire.ActionCode(r.code) {
case wire.ActContinue:
return "response=continue"
case wire.ActAccept:
return "response=accept"
case wire.ActDiscard:
return "response=discard"
case wire.ActReject:
return "response=reject"
case wire.ActTempFail:
return "response=temp_fail"
case wire.ActSkip:
return "response=skip"
case wire.ActProgress:
return "response=progress"
case wire.ActReplyCode:
act, err := parseAction(r.Response())
if err != nil {
return fmt.Sprintf("response=invalid code=%d data_len=%d data=%q", r.code, len(r.data), r.data)
}
action := "temp_fail"
if act.SMTPCode > 499 {
action = "reject"
}
return fmt.Sprintf("response=reply_code action=%s code=%d reason=%q", action, act.SMTPCode, act.SMTPReply)
}
// Users of the library do not really see modification Response objects.
// This is just for completeness’ sake
act, err := parseModifyAct(r.Response())
if err == nil {
switch act.Type {
case ActionAddRcpt:
if act.RcptArgs != "" {
return fmt.Sprintf("response=add_rcpt rcpt=%q args=%q", act.Rcpt, act.RcptArgs)
}
return fmt.Sprintf("response=add_rcpt rcpt=%q", act.Rcpt)
case ActionDelRcpt:
return fmt.Sprintf("response=del_rcpt rcpt=%q", act.Rcpt)
case ActionQuarantine:
return fmt.Sprintf("response=quarantine reason=%q", act.Reason)
case ActionReplaceBody:
return fmt.Sprintf("response=replace_body len=%d", len(act.Body))
case ActionChangeFrom:
if act.FromArgs != "" {
return fmt.Sprintf("response=change_from from=%q args=%q", act.From, act.FromArgs)
}
return fmt.Sprintf("response=change_from from=%q", act.From)
case ActionAddHeader:
return fmt.Sprintf("response=add_header name=%q value=%q", act.HeaderName, act.HeaderValue)
case ActionChangeHeader:
return fmt.Sprintf("response=change_header name=%q value=%q index=%d", act.HeaderName, act.HeaderValue, act.HeaderIndex)
case ActionInsertHeader:
return fmt.Sprintf("response=insert_header name=%q value=%q index=%d", act.HeaderName, act.HeaderValue, act.HeaderIndex)
}
}
return fmt.Sprintf("response=unknown code=%d data_len=%d data=%q", r.code, len(r.data), r.data)
}
// newResponse generates a new Response suitable for [wire.WritePacket]
func newResponse(code wire.Code, data []byte) *Response {
return &Response{code, data}
}
// newResponseStr generates a new [Response] with string payload (null-byte terminated)
func newResponseStr(code wire.Code, data string) (*Response, error) {
if len(data) > int(DataSize64K)-1 { // space for null-bytes
return nil, fmt.Errorf("milter: invalid data length: %d > %d", len(data), int(DataSize64K)-1)
}
if strings.ContainsRune(data, 0) {
return nil, fmt.Errorf("milter: invalid data: cannot contain null-bytes")
}
return newResponse(code, []byte(data+"\x00")), nil
}
// RejectWithCodeAndReason stops processing and tells the client the error code and reason to send
//
// smtpCode must be between 400 and 599, otherwise this method will return an error.
// See [milterutil.FormatResponse] for the rules on the reason string.
func RejectWithCodeAndReason(smtpCode uint16, reason string) (*Response, error) {
if smtpCode < 400 || smtpCode > 599 {
return nil, fmt.Errorf("milter: invalid code %d", smtpCode)
}
data, err := milterutil.FormatResponse(smtpCode, reason)
if err != nil {
return nil, err
}
return newResponseStr(wire.Code(wire.ActReplyCode), data)
}
// Define standard responses with no data
var (
// RespAccept signals to the MTA that the current transaction should be accepted.
// No more events get sent to the milter after this response.
RespAccept = &Response{code: wire.Code(wire.ActAccept)}
// RespContinue signals to the MTA that the current transaction should continue
RespContinue = &Response{code: wire.Code(wire.ActContinue)}
// RespDiscard signals to the MTA that the current transaction should be silently discarded.
// No more events get sent to the milter after this response.
RespDiscard = &Response{code: wire.Code(wire.ActDiscard)}
// RespReject signals to the MTA that the current transaction should be rejected with a hard rejection.
// No more events get sent to the milter after this response.
RespReject = &Response{code: wire.Code(wire.ActReject)}
// RespTempFail signals to the MTA that the current transaction should be rejected with a temporary error code.
// The sending MTA might try to deliver the same message again at a later time.
// No more events get sent to the milter after this response.
RespTempFail = &Response{code: wire.Code(wire.ActTempFail)}
// RespSkip signals to the MTA that transaction should continue and that the MTA
// does not need to send more events of the same type. This response only makes sense/is possible as
// a return value of [Milter.RcptTo], [Milter.Header] and [Milter.BodyChunk].
// It can only be used with milter protocol v6 and later.
RespSkip = &Response{code: wire.Code(wire.ActSkip)}
)
// respProgress signals to the MTA that the milter does progress and prevents the MTA to quit the connection
var respProgress = &Response{code: wire.Code(wire.ActProgress)}