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  }