-
-
Notifications
You must be signed in to change notification settings - Fork 18
/
smtp_client.go
142 lines (121 loc) · 4.76 KB
/
smtp_client.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
package truemail
import (
"net"
"net/smtp"
"time"
)
// SMTP request configuration. Provides connection/request settings for SMTP client
type SmtpRequestConfiguration struct {
VerifierDomain, VerifierEmail, TargetEmail, TargetServerAddress string
TargetServerPortNumber, ConnectionTimeout, ResponseTimeout int
}
// smtpRequestConfiguration builder. Creates SMTP request configuration with settings from configuration
func newSmtpRequestConfiguration(config *Configuration, targetEmail, targetServerAddress string) *SmtpRequestConfiguration {
return &SmtpRequestConfiguration{
VerifierDomain: config.VerifierDomain,
VerifierEmail: config.VerifierEmail,
TargetEmail: targetEmail,
TargetServerAddress: targetServerAddress,
TargetServerPortNumber: config.SmtpPort,
ConnectionTimeout: config.ConnectionTimeout,
ResponseTimeout: config.ResponseTimeout,
}
}
// SMTP response structure. Includes RCPTTO successful request marker
// and SMTP client error pointers slice
type SmtpResponse struct {
Rcptto bool
Errors []*SmtpClientError
}
// SMTP request structure. Includes attempts count, target email & host address,
// pointers to SMTP request configuration and SMTP response
type SmtpRequest struct {
Attempts int
Email, Host string
Configuration *SmtpRequestConfiguration
Response *SmtpResponse
}
// SMTP validation client interface
type client interface {
runSession() bool
sessionError() *SmtpClientError
}
// SMTP client structure. Provides possibility to interact with target SMTP server
type smtpClient struct {
verifierDomain, verifierEmail, targetEmail, targetServerAddress, networkProtocol string
targetServerPortNumber int
connectionTimeout, responseTimeout time.Duration
client *smtp.Client
err *SmtpClientError
}
// smtpClient builder. Creates SMTP client with settings from smtpRequestConfiguration
func newSmtpClient(config *SmtpRequestConfiguration) *smtpClient {
return &smtpClient{
verifierDomain: config.VerifierDomain,
verifierEmail: config.VerifierEmail,
targetEmail: config.TargetEmail,
targetServerAddress: config.TargetServerAddress,
targetServerPortNumber: config.TargetServerPortNumber,
networkProtocol: tcpTransportLayer,
connectionTimeout: time.Duration(config.ConnectionTimeout) * time.Second,
responseTimeout: time.Duration(config.ResponseTimeout) * time.Second,
}
}
// smtpClient methods
// Initializes SMTP client connection with connection timeout
func (smtpClient *smtpClient) initConnection() (net.Conn, error) {
targetAddress := serverWithPortNumber(smtpClient.targetServerAddress, smtpClient.targetServerPortNumber)
connection, error := net.DialTimeout(smtpClient.networkProtocol, targetAddress, smtpClient.connectionTimeout)
return connection, error
}
// interface implementation
// Returns pointer to current SMTP client custom error
func (smtpClient *smtpClient) sessionError() *SmtpClientError {
return smtpClient.err
}
// Runs SMTP session with target mail server. Assigns smtpClient.error
// for failure case and return false. Otherwise returns true
func (smtpClient *smtpClient) runSession() bool {
var err error
connection, err := smtpClient.initConnection()
if err != nil {
smtpClient.err = &SmtpClientError{isConnection: true, err: err}
return false
}
closeConnection := func() {
connection.Close()
smtpClient.err = &SmtpClientError{isResponseTimeout: true, err: err}
}
client, err := smtp.NewClient(connection, smtpClient.targetServerAddress)
// Handle error case when SMTP server responded with non 220 status
if err != nil {
closeConnection()
smtpClient.err = &SmtpClientError{isSmtpServiceReady: true, err: err}
return false
}
smtpClient.client = client
defer client.Close()
timerHello := time.AfterFunc(smtpClient.responseTimeout, closeConnection)
err = client.Hello(smtpClient.verifierDomain)
if err != nil {
smtpClient.err = &SmtpClientError{isHello: true, err: err}
return false
}
defer timerHello.Stop()
timerMailFrom := time.AfterFunc(smtpClient.responseTimeout, closeConnection)
err = client.Mail(smtpClient.verifierEmail)
if err != nil {
smtpClient.err = &SmtpClientError{isMailFrom: true, err: err}
return false
}
defer timerMailFrom.Stop()
timerRcptTo := time.AfterFunc(smtpClient.responseTimeout, closeConnection)
err = client.Rcpt(smtpClient.targetEmail)
if err != nil {
smtpClient.err = &SmtpClientError{isRecptTo: true, err: err}
return false
}
defer timerRcptTo.Stop()
// TODO: What about client.Quit() ?
return true
}