From c27359ab4a803ea6625ec9a5d9d3ab5ac5c6e946 Mon Sep 17 00:00:00 2001 From: Quentin DAUCHY <quentindauchy@evil.educ.wifi> Date: Tue, 15 Dec 2015 16:48:09 +0100 Subject: [PATCH] Add mails library --- .gitlab-ci.yml | 7 +- mails/README.md | 45 ++++++++++ mails/TODO.md | 2 + mails/email.go | 206 +++++++++++++++++++++++++++++++++++++++++++++ mails/mail_test.go | 106 +++++++++++++++++++++++ mails/testImg.gif | Bin 0 -> 13974 bytes 6 files changed, 364 insertions(+), 2 deletions(-) create mode 100644 mails/README.md create mode 100644 mails/TODO.md create mode 100644 mails/email.go create mode 100644 mails/mail_test.go create mode 100644 mails/testImg.gif diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e4e719e..c3cc499 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -9,6 +9,7 @@ Unit tests: - strong # Disable this build on small runners services: - "lesterpig/mongo:latest" # Use this light version of mongo + - "lesterpig/postfix:latest" #artifacts: # Waiting GitLab 8.2.1... # paths: # - "coverage.html" @@ -16,6 +17,7 @@ Unit tests: - "go get gopkg.in/mgo.v2" - "go test -coverprofile auth.part -v ./auth" - "go test -coverprofile mgdb.part -v ./mgdb" + - "go test -coverprofile mails.part -v ./mails" - "echo 'mode: set' *part > c.out" - "grep -h -v 'mode: set' *part >> c.out" - "go tool cover -html=c.out -o coverage.html" @@ -26,7 +28,8 @@ ARM tests: - arm script: - "go get gopkg.in/mgo.v2" - - "go test -cover -short -run 'Test[^M][^o][^n][^g][^o]' -v ./..." + - "go test -cover -short -v ./auth" + - "go test -cover -short -v ./mgdb" Code lint: stage: test @@ -38,4 +41,4 @@ Code lint: - "go get github.com/alecthomas/gometalinter" - "go get gopkg.in/mgo.v2" - "gometalinter --install" - - "gometalinter -t --deadline=60s ./..." + - "gometalinter -t --deadline=100s -j1 ./..." diff --git a/mails/README.md b/mails/README.md new file mode 100644 index 0000000..4226c1d --- /dev/null +++ b/mails/README.md @@ -0,0 +1,45 @@ +### DFSS - Mails lib ### + +This library is designed to wrap the smtp library of go. + +## Initiating a connection to a server ## + +To start a connection to a server, create a CustomClient via NewCustomClient +This takes : +- A sender (ex : qdauchy@insa-rennes.fr) +- A host (ex : mailhost.insa-rennes.fr) +- A port (ex : 587) +- A user (ex : qdauchy) +- A password + +This requires the server to have TLS + +## Using the connection ## + +The connection that has been created can then be used to send one or several mails + +Using Send requires : +- A slice of receivers +- A subject +- A message +- A (possibly empty) slice of extensions +- A (possibly empty) slice of filenames. This slice must be of the same length as the extensions one. + +## Closing the connection ## + +Finally, close the connection using Close. + +## Example ### + +Refer to the doc's to see the library in practice + +## Testing the library ## + +The testing file uses the following variables to set up the tests : +DFSS_TEST_MAIL_SENDER +DFSS_TEST_MAIL_HOST +DFSS_TEST_MAIL_PORT +DFSS_TEST_MAIL_USER +DFSS_TEST_MAIL_PASSWORD +DFSS_TEST_MAIL_RCPT1 +DFSS_TEST_MAIL_RCPT2 diff --git a/mails/TODO.md b/mails/TODO.md new file mode 100644 index 0000000..d007783 --- /dev/null +++ b/mails/TODO.md @@ -0,0 +1,2 @@ +Modify the header so that the spam score is lower (9.986 is cutting it close) +Document diff --git a/mails/email.go b/mails/email.go new file mode 100644 index 0000000..6dc595e --- /dev/null +++ b/mails/email.go @@ -0,0 +1,206 @@ +// Package mails provides a simple interface with the smtp library +package mails + +import ( + "bytes" + "crypto/rand" + "crypto/tls" + "encoding/base64" + "errors" + "fmt" + "io" + "io/ioutil" + "mime/multipart" + "net/smtp" + "net/textproto" + "strings" + "time" +) + +// CustomClient : +// Modelizes the constants of a connection : an actual client, and a sender +type CustomClient struct { + sender string + client *smtp.Client +} + +const rfc2822 = "Fri 18 Dec 2015 10:01:17 -0606" // used to format time to rfc2822. Not accurate but fmt can't see a , + +// NewCustomClient starts up a custom client. +func NewCustomClient(sender, host, port, username, password string) (*CustomClient, error) { + + // Connect to the server. Type of connection is smtp.Client + connection, err := smtp.Dial(host + ":" + port) + if err != nil { + return nil, err + } + + // Check that the server does implement TLS + if ok, _ := connection.Extension("StartTLS"); !ok { + return nil, errors.New("Connection failed : mail server doesn't support TLS") + } + + // Start tls + if err := connection.StartTLS(&tls.Config{InsecureSkipVerify: true, ServerName: host}); err != nil { + return nil, err + } + + // Authenticate to the server if it is supported + if ok, _ := connection.Extension("AUTH"); ok { + auth := smtp.PlainAuth(username, username, password, host) + if err := connection.Auth(auth); err != nil { + return nil, err + } + } + + return &CustomClient{sender, connection}, nil +} + +// Send a mail with the custom client. Returns nil on success. +func (c *CustomClient) Send(receivers []string, subject, message string, extensions, filenames []string) error { + // Keep the connection in a local variable for ease of access + connection := c.client + + boundary := randomBoundary() + header := createHeader(c.sender, subject, boundary) + + // Encode the message in base64 ONCE + base64Message := base64.StdEncoding.EncodeToString([]byte(message)) + + for _, receiver := range receivers { + + // Set the sender + if err := connection.Mail(c.sender); err != nil { + return err + } + + // Set the receiver. This modifies the header in the current instance + if err := connection.Rcpt(receiver); err != nil { + return err + } + + // Set the message : header, then message encoded in base64, then attachments + var localBuffer bytes.Buffer + if err := createFullMessage(&localBuffer, receiver, c.sender, header, base64Message, extensions, filenames, boundary); err != nil { + return err + } + + // Send it. Data returns a writer to which one can write to write the message itself + emailWriter, err := connection.Data() + if err != nil { + return err + } + + _, err = fmt.Fprintf(emailWriter, localBuffer.String()) + if err != nil { + return err + } + err = emailWriter.Close() + if err != nil { + return err + } + + // Reset the envellope + err = connection.Reset() + if err != nil { + return err + } + } + + return nil +} + +// Close the connection of CustomClient +func (c *CustomClient) Close() error { + return c.client.Close() +} + +// Creates the header for all messages +func createHeader(sender, subject, boundary string) string { + var buffer bytes.Buffer + fmt.Fprintf(&buffer, "From: %s\r\n", sender) + fmt.Fprintf(&buffer, "MIME-Version: 1.0\r\n") + fmt.Fprintf(&buffer, "Subject: %s\r\n", subject) + // Replace the first space with a comma and a space to conform to rfc2822 + fmt.Fprintf(&buffer, "Date: %s%s", strings.Replace(time.Now().UTC().Format(rfc2822), " ", ", ", 1), "\r\n") + fmt.Fprintf(&buffer, "Content-Type: multipart/mixed; boundary=\"%s\"; charset=\"UTF-8\"\r\n", boundary) + fmt.Fprintf(&buffer, "To: ") + return buffer.String() +} + +// Create the full message for a single receiver +func createFullMessage(b *bytes.Buffer, receiver, sender, globalHeader, base64Message string, extensions, filenames []string, boundary string) error { + fmt.Fprintf(b, "%s%s\r\n", globalHeader, receiver) + + writer := multipart.NewWriter(b) + if err := writer.SetBoundary(boundary); err != nil { + return err + } + // Set the message + if err := createText(writer, base64Message); err != nil { + return err + } + + // Set attachments. Here for now because the boundaries are wanted unique + for index, value := range filenames { + if err := createAttachment(writer, extensions[index], value); err != nil { + return err + } + } + if err := writer.Close(); err != nil { + return err + } + + return nil +} + +// Create an attachment with a certain extension +func createAttachment(writer *multipart.Writer, extension, path string) error { + // Create a header + newHeader := make(textproto.MIMEHeader) + newHeader.Add("Content-Type", extension) + newHeader.Add("Content-Transfer-Encoding", "base64") + newHeader.Add("Content-Disposition", "attachment; filename="+path+";") + + // Create a writer for the file + output, err := writer.CreatePart(newHeader) + if err != nil { + return err + } + + // Write the file to the message + data, err := ioutil.ReadFile(path) + if err != nil { + return err + } + fmt.Fprintf(output, base64.StdEncoding.EncodeToString([]byte(data))) + + return nil +} + +// Creates the equivalent of the message wrapped in a boundary. The message is expected to have been encoded via base64 +func createText(writer *multipart.Writer, message string) error { + // Create the mime header for the message + mimeHeaderMessage := make(textproto.MIMEHeader) + mimeHeaderMessage.Add("Content-Transfer-Encoding", "base64") + mimeHeaderMessage.Add("Content-Type", "text/plain; charset=\"UTF-8\"") + + // Set the message + output, err := writer.CreatePart(mimeHeaderMessage) + if err != nil { + return err + } + + fmt.Fprintf(output, "%s", message) + return nil +} + +// Totally copied from go stl +func randomBoundary() string { + var buf [30]byte + _, err := io.ReadFull(rand.Reader, buf[:]) + if err != nil { + panic(err) + } + return fmt.Sprintf("%x", buf[:]) +} diff --git a/mails/mail_test.go b/mails/mail_test.go new file mode 100644 index 0000000..b04f106 --- /dev/null +++ b/mails/mail_test.go @@ -0,0 +1,106 @@ +package mails + +import ( + "fmt" + "os" + "testing" +) + +var client *CustomClient +var err error +var rcpt1 string +var rcpt2 string + +func TestMain(m *testing.M) { + // Setup is based on environment variables + sender := os.Getenv("DFSS_TEST_MAIL_SENDER") + host := os.Getenv("DFSS_TEST_MAIL_HOST") + port := os.Getenv("DFSS_TEST_MAIL_PORT") + username := os.Getenv("DFSS_TEST_MAIL_USER") + password := os.Getenv("DFSS_TEST_MAIL_PASSWORD") + rcpt1 = os.Getenv("DFSS_TEST_MAIL_RCPT1") + rcpt2 = os.Getenv("DFSS_TEST_MAIL_RCPT2") + client, err = NewCustomClient(sender, host, port, username, password) + if err != nil { + fmt.Println(err) + } + + code := m.Run() + + err = client.Close() + if err != nil { + fmt.Println(err) + } + os.Exit(code) +} + +func TestSingleMail(t *testing.T) { + err = client.Send([]string{rcpt1}, "TestSingleMail", "Gros espoirs!", []string{}, []string{}) + if err != nil { + t.Fatal(err) + } +} + +func TestDoubleMail(t *testing.T) { + err = client.Send([]string{rcpt1, rcpt2}, "TestDoubleMail", "Gros espoirs!", []string{}, []string{}) + if err != nil { + t.Fatal(err) + } +} + +func TestRuneMail(t *testing.T) { + err = client.Send([]string{rcpt1}, "TestRuneMail", "测试", []string{}, []string{}) + if err != nil { + t.Fatal(err) + } +} + +func TestAttachmentMailText(t *testing.T) { + err = client.Send([]string{rcpt1}, "TestAttachmentMailText", "What would make a good attachment?", []string{"text/plain"}, []string{"mail_test.go"}) + if err != nil { + t.Fatal(err) + } +} + +func TestAttachmentMailImage(t *testing.T) { + err = client.Send([]string{rcpt1}, "TestAttachmentMailImage", "What would make a good attachment?", []string{"image/gif"}, []string{"testImg.gif"}) + if err != nil { + t.Fatal(err) + } +} + +func ExampleCustomClient() { + + // Create a connection + client, err := NewCustomClient("qdauchy@insa-rennes.fr", "mailhost.insa-rennes.fr", "587", "qdauchy", "notreallymypass") + if err != nil { + fmt.Println(err) + } + + // Some reused variables + receiver1 := "lbonniot@insa-rennes.fr" + receiver2 := "qdauchy@insa-rennes.fr" + receiver3 := "tclaverie@insa-rennes.fr" + subject := "Mail example" + message := `Hello, this is a mail example. It's not like the cactus is going + to be jealous or anything...` + + // Send a first mail, without attachments + err = client.Send([]string{receiver1, receiver2}, subject, message, []string{}, []string{}) + if err != nil { + fmt.Println(err) + } + + // Send a second mail, with some attachments + err = client.Send([]string{receiver1, receiver3}, subject, message, []string{"text/plain", "image/gif"}, []string{"email.go", "testImg.gif"}) + if err != nil { + fmt.Println(err) + } + + // Close the connection + err = client.Close() + if err != nil { + fmt.Println(err) + } + +} diff --git a/mails/testImg.gif b/mails/testImg.gif new file mode 100644 index 0000000000000000000000000000000000000000..c161ddeb013501d84d9b7414973869333f373a61 GIT binary patch literal 13974 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 YH^x{!HBH^P!E|uwK*yhdSS;ZFf4l69+yDRo literal 0 HcmV?d00001 -- GitLab