forked from gophish/gophish
/
smtp.go
240 lines (222 loc) · 6.05 KB
/
smtp.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
package models
import (
"crypto/tls"
"errors"
"net/mail"
"os"
"strconv"
"strings"
"time"
"github.com/gophish/gomail"
log "github.com/gophish/gophish/logger"
"github.com/gophish/gophish/mailer"
"github.com/jinzhu/gorm"
)
// Dialer is a wrapper around a standard gomail.Dialer in order
// to implement the mailer.Dialer interface. This allows us to better
// separate the mailer package as opposed to forcing a connection
// between mailer and gomail.
type Dialer struct {
*gomail.Dialer
}
// Dial wraps the gomail dialer's Dial command
func (d *Dialer) Dial() (mailer.Sender, error) {
return d.Dialer.Dial()
}
// SMTP contains the attributes needed to handle the sending of campaign emails
type SMTP struct {
Id int64 `json:"id" gorm:"column:id; primary_key:yes"`
UserId int64 `json:"-" gorm:"column:user_id"`
Interface string `json:"interface_type" gorm:"column:interface_type"`
Name string `json:"name"`
Host string `json:"host"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
FromAddress string `json:"from_address"`
IgnoreCertErrors bool `json:"ignore_cert_errors"`
Headers []Header `json:"headers"`
ModifiedDate time.Time `json:"modified_date"`
}
// Header contains the fields and methods for a sending profile to have
// custom headers
type Header struct {
Id int64 `json:"-"`
SMTPId int64 `json:"-"`
Key string `json:"key"`
Value string `json:"value"`
}
// ErrFromAddressNotSpecified is thrown when there is no "From" address
// specified in the SMTP configuration
var ErrFromAddressNotSpecified = errors.New("No From Address specified")
// ErrHostNotSpecified is thrown when there is no Host specified
// in the SMTP configuration
var ErrHostNotSpecified = errors.New("No SMTP Host specified")
// ErrInvalidHost indicates that the SMTP server string is invalid
var ErrInvalidHost = errors.New("Invalid SMTP server address")
// TableName specifies the database tablename for Gorm to use
func (s SMTP) TableName() string {
return "smtp"
}
// Validate ensures that SMTP configs/connections are valid
func (s *SMTP) Validate() error {
switch {
case s.FromAddress == "":
return ErrFromAddressNotSpecified
case s.Host == "":
return ErrHostNotSpecified
}
_, err := mail.ParseAddress(s.FromAddress)
if err != nil {
return err
}
// Make sure addr is in host:port format
hp := strings.Split(s.Host, ":")
if len(hp) > 2 {
return ErrInvalidHost
} else if len(hp) < 2 {
hp = append(hp, "25")
}
_, err = strconv.Atoi(hp[1])
if err != nil {
return ErrInvalidHost
}
return err
}
// GetDialer returns a dialer for the given SMTP profile
func (s *SMTP) GetDialer() (mailer.Dialer, error) {
// Setup the message and dial
hp := strings.Split(s.Host, ":")
if len(hp) < 2 {
hp = append(hp, "25")
}
// Any issues should have been caught in validation, but we'll
// double check here.
port, err := strconv.Atoi(hp[1])
if err != nil {
log.Error(err)
return nil, err
}
d := gomail.NewDialer(hp[0], port, s.Username, s.Password)
d.TLSConfig = &tls.Config{
ServerName: s.Host,
InsecureSkipVerify: s.IgnoreCertErrors,
}
hostname, err := os.Hostname()
if err != nil {
log.Error(err)
hostname = "localhost"
}
d.LocalName = hostname
return &Dialer{d}, err
}
// GetSMTPs returns the SMTPs owned by the given user.
func GetSMTPs(uid int64) ([]SMTP, error) {
ss := []SMTP{}
err := db.Where("user_id=?", uid).Find(&ss).Error
if err != nil {
log.Error(err)
return ss, err
}
for i := range ss {
err = db.Where("smtp_id=?", ss[i].Id).Find(&ss[i].Headers).Error
if err != nil && err != gorm.ErrRecordNotFound {
log.Error(err)
return ss, err
}
}
return ss, nil
}
// GetSMTP returns the SMTP, if it exists, specified by the given id and user_id.
func GetSMTP(id int64, uid int64) (SMTP, error) {
s := SMTP{}
err := db.Where("user_id=? and id=?", uid, id).Find(&s).Error
if err != nil {
log.Error(err)
}
err = db.Where("smtp_id=?", s.Id).Find(&s.Headers).Error
if err != nil && err != gorm.ErrRecordNotFound {
log.Error(err)
return s, err
}
return s, err
}
// GetSMTPByName returns the SMTP, if it exists, specified by the given name and user_id.
func GetSMTPByName(n string, uid int64) (SMTP, error) {
s := SMTP{}
err := db.Where("user_id=? and name=?", uid, n).Find(&s).Error
if err != nil {
log.Error(err)
return s, err
}
err = db.Where("smtp_id=?", s.Id).Find(&s.Headers).Error
if err != nil && err != gorm.ErrRecordNotFound {
log.Error(err)
}
return s, err
}
// PostSMTP creates a new SMTP in the database.
func PostSMTP(s *SMTP) error {
err := s.Validate()
if err != nil {
log.Error(err)
return err
}
// Insert into the DB
err = db.Save(s).Error
if err != nil {
log.Error(err)
}
// Save custom headers
for i := range s.Headers {
s.Headers[i].SMTPId = s.Id
err := db.Save(&s.Headers[i]).Error
if err != nil {
log.Error(err)
return err
}
}
return err
}
// PutSMTP edits an existing SMTP in the database.
// Per the PUT Method RFC, it presumes all data for a SMTP is provided.
func PutSMTP(s *SMTP) error {
err := s.Validate()
if err != nil {
log.Error(err)
return err
}
err = db.Where("id=?", s.Id).Save(s).Error
if err != nil {
log.Error(err)
}
// Delete all custom headers, and replace with new ones
err = db.Where("smtp_id=?", s.Id).Delete(&Header{}).Error
if err != nil && err != gorm.ErrRecordNotFound {
log.Error(err)
return err
}
for i := range s.Headers {
s.Headers[i].SMTPId = s.Id
err := db.Save(&s.Headers[i]).Error
if err != nil {
log.Error(err)
return err
}
}
return err
}
// DeleteSMTP deletes an existing SMTP in the database.
// An error is returned if a SMTP with the given user id and SMTP id is not found.
func DeleteSMTP(id int64, uid int64) error {
// Delete all custom headers
err = db.Where("smtp_id=?", id).Delete(&Header{}).Error
if err != nil {
log.Error(err)
return err
}
err = db.Where("user_id=?", uid).Delete(SMTP{Id: id}).Error
if err != nil {
log.Error(err)
}
return err
}