github.com/gophish/gophish@v0.12.2-0.20230915144530-8e7929441393/models/smtp.go (about) 1 package models 2 3 import ( 4 "crypto/tls" 5 "errors" 6 "net/mail" 7 "os" 8 "regexp" 9 "strconv" 10 "strings" 11 "time" 12 13 "github.com/gophish/gomail" 14 "github.com/gophish/gophish/dialer" 15 log "github.com/gophish/gophish/logger" 16 "github.com/gophish/gophish/mailer" 17 "github.com/jinzhu/gorm" 18 ) 19 20 // Dialer is a wrapper around a standard gomail.Dialer in order 21 // to implement the mailer.Dialer interface. This allows us to better 22 // separate the mailer package as opposed to forcing a connection 23 // between mailer and gomail. 24 type Dialer struct { 25 *gomail.Dialer 26 } 27 28 // Dial wraps the gomail dialer's Dial command 29 func (d *Dialer) Dial() (mailer.Sender, error) { 30 return d.Dialer.Dial() 31 } 32 33 // SMTP contains the attributes needed to handle the sending of campaign emails 34 type SMTP struct { 35 Id int64 `json:"id" gorm:"column:id; primary_key:yes"` 36 UserId int64 `json:"-" gorm:"column:user_id"` 37 Interface string `json:"interface_type" gorm:"column:interface_type"` 38 Name string `json:"name"` 39 Host string `json:"host"` 40 Username string `json:"username,omitempty"` 41 Password string `json:"password,omitempty"` 42 FromAddress string `json:"from_address"` 43 IgnoreCertErrors bool `json:"ignore_cert_errors"` 44 Headers []Header `json:"headers"` 45 ModifiedDate time.Time `json:"modified_date"` 46 } 47 48 // Header contains the fields and methods for a sending profile to have 49 // custom headers 50 type Header struct { 51 Id int64 `json:"-"` 52 SMTPId int64 `json:"-"` 53 Key string `json:"key"` 54 Value string `json:"value"` 55 } 56 57 // ErrFromAddressNotSpecified is thrown when there is no "From" address 58 // specified in the SMTP configuration 59 var ErrFromAddressNotSpecified = errors.New("No From Address specified") 60 61 // ErrInvalidFromAddress is thrown when the SMTP From field in the sending 62 // profiles containes a value that is not an email address 63 var ErrInvalidFromAddress = errors.New("Invalid SMTP From address because it is not an email address") 64 65 // ErrHostNotSpecified is thrown when there is no Host specified 66 // in the SMTP configuration 67 var ErrHostNotSpecified = errors.New("No SMTP Host specified") 68 69 // ErrInvalidHost indicates that the SMTP server string is invalid 70 var ErrInvalidHost = errors.New("Invalid SMTP server address") 71 72 // TableName specifies the database tablename for Gorm to use 73 func (s SMTP) TableName() string { 74 return "smtp" 75 } 76 77 // Validate ensures that SMTP configs/connections are valid 78 func (s *SMTP) Validate() error { 79 switch { 80 case s.FromAddress == "": 81 return ErrFromAddressNotSpecified 82 case s.Host == "": 83 return ErrHostNotSpecified 84 case !validateFromAddress(s.FromAddress): 85 return ErrInvalidFromAddress 86 } 87 _, err := mail.ParseAddress(s.FromAddress) 88 if err != nil { 89 return err 90 } 91 // Make sure addr is in host:port format 92 hp := strings.Split(s.Host, ":") 93 if len(hp) > 2 { 94 return ErrInvalidHost 95 } else if len(hp) < 2 { 96 hp = append(hp, "25") 97 } 98 _, err = strconv.Atoi(hp[1]) 99 if err != nil { 100 return ErrInvalidHost 101 } 102 return err 103 } 104 105 // validateFromAddress validates 106 func validateFromAddress(email string) bool { 107 r, _ := regexp.Compile("^([a-zA-Z0-9_\\-\\.]+)@([a-zA-Z0-9_\\-\\.]+)\\.([a-zA-Z]{2,18})$") 108 return r.MatchString(email) 109 } 110 111 // GetDialer returns a dialer for the given SMTP profile 112 func (s *SMTP) GetDialer() (mailer.Dialer, error) { 113 // Setup the message and dial 114 hp := strings.Split(s.Host, ":") 115 if len(hp) < 2 { 116 hp = append(hp, "25") 117 } 118 host := hp[0] 119 // Any issues should have been caught in validation, but we'll 120 // double check here. 121 port, err := strconv.Atoi(hp[1]) 122 if err != nil { 123 log.Error(err) 124 return nil, err 125 } 126 dialer := dialer.Dialer() 127 d := gomail.NewWithDialer(dialer, host, port, s.Username, s.Password) 128 d.TLSConfig = &tls.Config{ 129 ServerName: host, 130 InsecureSkipVerify: s.IgnoreCertErrors, 131 } 132 hostname, err := os.Hostname() 133 if err != nil { 134 log.Error(err) 135 hostname = "localhost" 136 } 137 d.LocalName = hostname 138 return &Dialer{d}, err 139 } 140 141 // GetSMTPs returns the SMTPs owned by the given user. 142 func GetSMTPs(uid int64) ([]SMTP, error) { 143 ss := []SMTP{} 144 err := db.Where("user_id=?", uid).Find(&ss).Error 145 if err != nil { 146 log.Error(err) 147 return ss, err 148 } 149 for i := range ss { 150 err = db.Where("smtp_id=?", ss[i].Id).Find(&ss[i].Headers).Error 151 if err != nil && err != gorm.ErrRecordNotFound { 152 log.Error(err) 153 return ss, err 154 } 155 } 156 return ss, nil 157 } 158 159 // GetSMTP returns the SMTP, if it exists, specified by the given id and user_id. 160 func GetSMTP(id int64, uid int64) (SMTP, error) { 161 s := SMTP{} 162 err := db.Where("user_id=? and id=?", uid, id).Find(&s).Error 163 if err != nil { 164 log.Error(err) 165 return s, err 166 } 167 err = db.Where("smtp_id=?", s.Id).Find(&s.Headers).Error 168 if err != nil && err != gorm.ErrRecordNotFound { 169 log.Error(err) 170 return s, err 171 } 172 return s, err 173 } 174 175 // GetSMTPByName returns the SMTP, if it exists, specified by the given name and user_id. 176 func GetSMTPByName(n string, uid int64) (SMTP, error) { 177 s := SMTP{} 178 err := db.Where("user_id=? and name=?", uid, n).Find(&s).Error 179 if err != nil { 180 log.Error(err) 181 return s, err 182 } 183 err = db.Where("smtp_id=?", s.Id).Find(&s.Headers).Error 184 if err != nil && err != gorm.ErrRecordNotFound { 185 log.Error(err) 186 } 187 return s, err 188 } 189 190 // PostSMTP creates a new SMTP in the database. 191 func PostSMTP(s *SMTP) error { 192 err := s.Validate() 193 if err != nil { 194 log.Error(err) 195 return err 196 } 197 // Insert into the DB 198 err = db.Save(s).Error 199 if err != nil { 200 log.Error(err) 201 } 202 // Save custom headers 203 for i := range s.Headers { 204 s.Headers[i].SMTPId = s.Id 205 err := db.Save(&s.Headers[i]).Error 206 if err != nil { 207 log.Error(err) 208 return err 209 } 210 } 211 return err 212 } 213 214 // PutSMTP edits an existing SMTP in the database. 215 // Per the PUT Method RFC, it presumes all data for a SMTP is provided. 216 func PutSMTP(s *SMTP) error { 217 err := s.Validate() 218 if err != nil { 219 log.Error(err) 220 return err 221 } 222 err = db.Where("id=?", s.Id).Save(s).Error 223 if err != nil { 224 log.Error(err) 225 } 226 // Delete all custom headers, and replace with new ones 227 err = db.Where("smtp_id=?", s.Id).Delete(&Header{}).Error 228 if err != nil && err != gorm.ErrRecordNotFound { 229 log.Error(err) 230 return err 231 } 232 // Save custom headers 233 for i := range s.Headers { 234 s.Headers[i].SMTPId = s.Id 235 err := db.Save(&s.Headers[i]).Error 236 if err != nil { 237 log.Error(err) 238 return err 239 } 240 } 241 return err 242 } 243 244 // DeleteSMTP deletes an existing SMTP in the database. 245 // An error is returned if a SMTP with the given user id and SMTP id is not found. 246 func DeleteSMTP(id int64, uid int64) error { 247 // Delete all custom headers 248 err := db.Where("smtp_id=?", id).Delete(&Header{}).Error 249 if err != nil { 250 log.Error(err) 251 return err 252 } 253 err = db.Where("user_id=?", uid).Delete(SMTP{Id: id}).Error 254 if err != nil { 255 log.Error(err) 256 } 257 return err 258 }