github.com/ArminBerberovic/gophish@v0.9.0/imap/imap.go (about)

     1  package imap
     2  
     3  // Functionality taken from https://github.com/jprobinson/eazye
     4  
     5  import (
     6  	"bytes"
     7  	"crypto/tls"
     8  	"fmt"
     9  	"strconv"
    10  	"time"
    11  
    12  	log "github.com/gophish/gophish/logger"
    13  	"github.com/gophish/gophish/models"
    14  	"github.com/jordan-wright/email"
    15  	"github.com/mxk/go-imap/imap"
    16  )
    17  
    18  // Client interface for IMAP interactions
    19  type Client interface {
    20  	Close(expunge bool) (cmd *imap.Command, err error)
    21  	Login(username, password string) (cmd *imap.Command, err error)
    22  	Logout(timeout time.Duration) (cmd *imap.Command, err error)
    23  	Select(mbox string, readonly bool) (cmd *imap.Command, err error)
    24  	UIDFetch(seq *imap.SeqSet, items ...string) (cmd *imap.Command, err error)
    25  	UIDSearch(spec ...imap.Field) (cmd *imap.Command, err error)
    26  	UIDStore(seq *imap.SeqSet, item string, value imap.Field) (cmd *imap.Command, err error)
    27  }
    28  
    29  // Email represents an email.Email with an included IMAP UID
    30  type Email struct {
    31  	UID uint32 `json:"uid"`
    32  	*email.Email
    33  }
    34  
    35  // Mailbox holds onto the credentials and other information
    36  // needed for connecting to an IMAP server.
    37  type Mailbox struct {
    38  	Host   string
    39  	TLS    bool
    40  	User   string
    41  	Pwd    string
    42  	Folder string
    43  	// Read only mode, false (original logic) if not initialized
    44  	ReadOnly bool
    45  }
    46  
    47  // GetAll will pull all emails from the email folder and return them as a list.
    48  func (mbox *Mailbox) GetAll(markAsRead, delete bool) ([]Email, error) {
    49  	// call chan, put 'em in a list, return
    50  	var emails []Email
    51  	responses, err := mbox.GenerateAll(markAsRead, delete)
    52  	if err != nil {
    53  		return emails, err
    54  	}
    55  
    56  	for resp := range responses {
    57  		if resp.Err != nil {
    58  			return emails, resp.Err
    59  		}
    60  		emails = append(emails, resp.Email)
    61  	}
    62  
    63  	return emails, nil
    64  }
    65  
    66  // GenerateAll will find all emails in the email folder and pass them along to the responses channel.
    67  func (mbox *Mailbox) GenerateAll(markAsRead, delete bool) (chan Response, error) {
    68  	return mbox.generateMail("ALL", nil, markAsRead, delete)
    69  }
    70  
    71  // GetUnread will find all unread emails in the folder and return them as a list.
    72  func (mbox *Mailbox) GetUnread(markAsRead, delete bool) ([]Email, error) {
    73  	// call chan, put 'em in a list, return
    74  	var emails []Email
    75  
    76  	responses, err := mbox.GenerateUnread(markAsRead, delete)
    77  	if err != nil {
    78  		return emails, err
    79  	}
    80  
    81  	for resp := range responses {
    82  		if resp.Err != nil {
    83  			return emails, resp.Err
    84  		}
    85  		emails = append(emails, resp.Email)
    86  	}
    87  
    88  	return emails, nil
    89  }
    90  
    91  // GenerateUnread will find all unread emails in the folder and pass them along to the responses channel.
    92  func (mbox *Mailbox) GenerateUnread(markAsRead, delete bool) (chan Response, error) {
    93  	return mbox.generateMail("UNSEEN", nil, markAsRead, delete)
    94  }
    95  
    96  // MarkAsUnread will set the UNSEEN flag on a supplied slice of UIDs
    97  func (mbox *Mailbox) MarkAsUnread(uids []uint32) error {
    98  	client, err := mbox.newClient()
    99  	if err != nil {
   100  		return err
   101  	}
   102  	defer func() {
   103  		client.Close(true)
   104  		client.Logout(30 * time.Second)
   105  	}()
   106  	for _, u := range uids {
   107  		err := alterEmail(client, u, "\\SEEN", false)
   108  		if err != nil {
   109  			return err //return on first failure
   110  		}
   111  	}
   112  	return nil
   113  
   114  }
   115  
   116  // DeleteEmails will delete emails from the supplied slice of UIDs
   117  func (mbox *Mailbox) DeleteEmails(uids []uint32) error {
   118  	client, err := mbox.newClient()
   119  	if err != nil {
   120  		return err
   121  	}
   122  	defer func() {
   123  		client.Close(true)
   124  		client.Logout(30 * time.Second)
   125  	}()
   126  	for _, u := range uids {
   127  		err := deleteEmail(client, u)
   128  		if err != nil {
   129  			return err //return on first failure
   130  		}
   131  	}
   132  	return nil
   133  
   134  }
   135  
   136  // Validate validates supplied IMAP model by connecting to the server
   137  func Validate(s *models.IMAP) error {
   138  
   139  	err := s.Validate()
   140  	if err != nil {
   141  		log.Error(err)
   142  		return err
   143  	}
   144  
   145  	s.Host = s.Host + ":" + strconv.Itoa(int(s.Port)) // Append port
   146  	mailServer := Mailbox{
   147  		Host:   s.Host,
   148  		TLS:    s.TLS,
   149  		User:   s.Username,
   150  		Pwd:    s.Password,
   151  		Folder: s.Folder}
   152  
   153  	client, err := mailServer.newClient()
   154  	if err != nil {
   155  		log.Error(err.Error())
   156  	} else {
   157  		client.Close(true)
   158  		client.Logout(30 * time.Second)
   159  	}
   160  	return err
   161  }
   162  
   163  // Response is a helper struct to wrap the email responses and possible errors.
   164  type Response struct {
   165  	Email Email
   166  	Err   error
   167  }
   168  
   169  // newClient will initiate a new IMAP connection with the given creds.
   170  func (mbox *Mailbox) newClient() (*imap.Client, error) {
   171  	var client *imap.Client
   172  	var err error
   173  	if mbox.TLS {
   174  		client, err = imap.DialTLS(mbox.Host, new(tls.Config))
   175  		if err != nil {
   176  			return client, err
   177  		}
   178  	} else {
   179  		client, err = imap.Dial(mbox.Host)
   180  		if err != nil {
   181  			return client, err
   182  		}
   183  	}
   184  
   185  	_, err = client.Login(mbox.User, mbox.Pwd)
   186  	if err != nil {
   187  		return client, err
   188  	}
   189  
   190  	_, err = imap.Wait(client.Select(mbox.Folder, mbox.ReadOnly))
   191  	if err != nil {
   192  		return client, err
   193  	}
   194  
   195  	return client, nil
   196  }
   197  
   198  const dateFormat = "02-Jan-2006"
   199  
   200  // findEmails will run a find the UIDs of any emails that match the search.:
   201  func findEmails(client Client, search string, since *time.Time) (*imap.Command, error) {
   202  	var specs []imap.Field
   203  	if len(search) > 0 {
   204  		specs = append(specs, search)
   205  	}
   206  
   207  	if since != nil {
   208  		sinceStr := since.Format(dateFormat)
   209  		specs = append(specs, "SINCE", sinceStr)
   210  	}
   211  
   212  	// get headers and UID for UnSeen message in src inbox...
   213  	cmd, err := imap.Wait(client.UIDSearch(specs...))
   214  	if err != nil {
   215  		return &imap.Command{}, fmt.Errorf("uid search failed: %s", err)
   216  	}
   217  	return cmd, nil
   218  }
   219  
   220  const GenerateBufferSize = 100
   221  
   222  func (mbox *Mailbox) generateMail(search string, since *time.Time, markAsRead, delete bool) (chan Response, error) {
   223  	responses := make(chan Response, GenerateBufferSize)
   224  	client, err := mbox.newClient()
   225  	if err != nil {
   226  		close(responses)
   227  		return responses, fmt.Errorf("failed to create IMAP connection: %s", err)
   228  	}
   229  
   230  	go func() {
   231  		defer func() {
   232  			client.Close(true)
   233  			client.Logout(30 * time.Second)
   234  			close(responses)
   235  		}()
   236  
   237  		var cmd *imap.Command
   238  		// find all the UIDs
   239  		cmd, err = findEmails(client, search, since)
   240  		if err != nil {
   241  			responses <- Response{Err: err}
   242  			return
   243  		}
   244  		// gotta fetch 'em all
   245  		getEmails(client, cmd, markAsRead, delete, responses)
   246  	}()
   247  
   248  	return responses, nil
   249  }
   250  
   251  func getEmails(client Client, cmd *imap.Command, markAsRead, delete bool, responses chan Response) {
   252  	seq := &imap.SeqSet{}
   253  	msgCount := 0
   254  	for _, rsp := range cmd.Data {
   255  		for _, uid := range rsp.SearchResults() {
   256  			msgCount++
   257  			seq.AddNum(uid)
   258  		}
   259  	}
   260  
   261  	if seq.Empty() {
   262  		return
   263  	}
   264  
   265  	fCmd, err := imap.Wait(client.UIDFetch(seq, "INTERNALDATE", "BODY[]", "UID", "RFC822.HEADER"))
   266  	if err != nil {
   267  		responses <- Response{Err: fmt.Errorf("unable to perform uid fetch: %s", err)}
   268  		return
   269  	}
   270  
   271  	var email Email
   272  	for _, msgData := range fCmd.Data {
   273  		msgFields := msgData.MessageInfo().Attrs
   274  
   275  		// make sure is a legit response before we attempt to parse it
   276  		// deal with unsolicited FETCH responses containing only flags
   277  		// I'm lookin' at YOU, Gmail!
   278  		// http://mailman13.u.washington.edu/pipermail/imap-protocol/2014-October/002355.html
   279  		// http://stackoverflow.com/questions/26262472/gmail-imap-is-sometimes-returning-bad-results-for-fetch
   280  		if _, ok := msgFields["RFC822.HEADER"]; !ok {
   281  			continue
   282  		}
   283  
   284  		email, err = NewEmail(msgFields)
   285  		if err != nil {
   286  			responses <- Response{Err: fmt.Errorf("unable to parse email: %s", err)}
   287  			return
   288  		}
   289  
   290  		responses <- Response{Email: email}
   291  
   292  		if !markAsRead {
   293  			err = removeSeen(client, imap.AsNumber(msgFields["UID"]))
   294  			if err != nil {
   295  				responses <- Response{Err: fmt.Errorf("unable to remove seen flag: %s", err)}
   296  				return
   297  			}
   298  		}
   299  
   300  		if delete {
   301  			err = deleteEmail(client, imap.AsNumber(msgFields["UID"]))
   302  			if err != nil {
   303  				responses <- Response{Err: fmt.Errorf("unable to delete email: %s", err)}
   304  				return
   305  			}
   306  		}
   307  	}
   308  	return
   309  }
   310  
   311  func deleteEmail(client Client, UID uint32) error {
   312  	return alterEmail(client, UID, "\\DELETED", true)
   313  }
   314  
   315  func removeSeen(client Client, UID uint32) error {
   316  	return alterEmail(client, UID, "\\SEEN", false)
   317  }
   318  
   319  func alterEmail(client Client, UID uint32, flag string, plus bool) error {
   320  	flg := "-FLAGS"
   321  	if plus {
   322  		flg = "+FLAGS"
   323  	}
   324  	fSeq := &imap.SeqSet{}
   325  	fSeq.AddNum(UID)
   326  	_, err := imap.Wait(client.UIDStore(fSeq, flg, flag))
   327  	if err != nil {
   328  		return err
   329  	}
   330  
   331  	return nil
   332  }
   333  
   334  // NewEmail will parse an imap.FieldMap into an Email. This
   335  // will expect the message to container the internaldate and the body with
   336  // all headers included.
   337  func NewEmail(msgFields imap.FieldMap) (Email, error) {
   338  
   339  	rawBody := imap.AsBytes(msgFields["BODY[]"])
   340  
   341  	rawBodyStream := bytes.NewReader(rawBody)
   342  	em, err := email.NewEmailFromReader(rawBodyStream) // Parse with @jordanwright's library
   343  	if err != nil {
   344  		return Email{}, err
   345  	}
   346  	iem := Email{
   347  		Email: em,
   348  		UID:   imap.AsNumber(msgFields["UID"]),
   349  	}
   350  
   351  	return iem, err
   352  }