Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to connect and send emails from exchange server in Go using starttls?

I'm working on porting some existing Python code to Go. One bit is in charge of sending emails through Exchange server (SMTP + STARTTLS). Existing (simplified) code looks like this:

import smtplib
client = smtplib.SMTP("exchangeserver.com")
client.starttls()
client.login('user', 'password')
client.sendmail('[email protected]', 
    ['[email protected]', '[email protected]'], 
    '..message..')

I'd like to do the same thing with Go, help is appreciated - thanks.

like image 600
sa125 Avatar asked Dec 25 '22 12:12

sa125


2 Answers

Edited:

Here is an example using the tls connection setup. I don't have one to test against, so it fails on my end connecting, but I suspect this is what you need to make it work

package main

import (
  "crypto/tls"
  "fmt"
  "log"
  "net/smtp"
)

func main() {
  var (
    host     = "smtp.google.com"
    port     = 587
    from     = "[email protected]"
    password = "baz"
    to       = []string{"[email protected]"}
    msg      = []byte("This is my message")
    auth     = smtp.PlainAuth("", from, password, "smtp.gmail.com")
  )
  serverAddr := fmt.Sprintf("%s:%d", host, port)

  conn, err := tls.Dial("tcp", serverAddr, nil)
  if err != nil {
    log.Printf("Error Dialing %s\n", err)
    return
  }

  client, err := smtp.NewClient(conn, host)
  if err != nil {
    log.Printf("Error SMTP connection: %s\n", err)
    return
  }

  if ok, _ := client.Extension("AUTH"); ok {
    if err := client.Auth(auth); err != nil {
      log.Printf("Error during AUTH %s\n", err)
      return
    }
  }

  if err := client.Mail(from); err != nil {
    log.Printf("Error: %s\n", err)
    return
  }

  for _, addr := range to {
    if err := client.Rcpt(addr); err != nil {
      log.Printf("Error: %s\n", err)
      return
    }
  }

  w, err := client.Data()
  if err != nil {
    log.Printf("Error: %s\n", err)
    return
  }

  _, err = w.Write(msg)
  if err != nil {
    log.Printf("Error: %s\n", err)
    return

  }

  err = w.Close()
  if err != nil {
    log.Printf("Error: %s\n", err)
    return

  }

  client.Quit()
}

I think something like this might work. You'll have to make the appropriate changes for server, user/password, etc. Let me know if you need more help.

package main

import (
  "fmt"
  "log"
  "net/smtp"
)

func main() {
  to := "[email protected]"
  from := "[email protected]"
  password := "myPassword"
  subject := "subject line of email"
  msg := "a one-line email message"

  emailTemplate := `To: %s
Subject: %s

%s
`
  body := fmt.Sprintf(emailTemplate, to, subject, msg)
  auth := smtp.PlainAuth("", from, password, "smtp.gmail.com")
  err := smtp.SendMail(
    "smtp.gmail.com:587",
    auth,
    from,
    []string{to},
    []byte(body),
  )
  if err != nil {
    log.Fatal(err)
  }
}
like image 156
Cory LaNou Avatar answered Dec 28 '22 08:12

Cory LaNou


To expand on Cory LaNou's answer, you can use StartTLS on a normal net.Conn. You need to define a tls.Config and then use that to upgrade the connection to use TLS.

The example below is based on Cory's answer and also steals some code from the crypto/tls test files for creating the RSA cert and private key. In production you'd obviously replace these with real certs and keys.

It may require some customization of the variables, as well as some changes to the tls.Config to suit your environment.

package main

import (
    "crypto/rsa"
    "crypto/tls"
    "encoding/hex"
    "fmt"
    "log"
    "math/big"
    "net"
    "net/smtp"
)

func main() {

    var (
        host     = "smtp.myexchange.com"
        port     = 587
        from     = "[email protected]"
        password = "password"
        to       = []string{"[email protected]"}
        msg      = []byte("This is my message")
        auth     = smtp.PlainAuth("", from, password, "smtp.myexchange.com")
    )

    conf := new(tls.Config)
    conf.Certificates = make([]tls.Certificate, 1)
    conf.Certificates[0].Certificate = [][]byte{testRSACertificate}
    conf.Certificates[0].PrivateKey = testRSAPrivateKey
    conf.CipherSuites = []uint16{tls.TLS_RSA_WITH_RC4_128_SHA}
    conf.InsecureSkipVerify = true
    conf.MinVersion = tls.VersionSSL30
    conf.MaxVersion = tls.VersionTLS10
    serverAddr := fmt.Sprintf("%s:%d", host, port)

    conn, err := net.Dial("tcp", serverAddr)
    if err != nil {
        log.Printf("Error Dialing %s\n", err)
        return
    }

    client, err := smtp.NewClient(conn, host)
    if err != nil {
        log.Printf("Error SMTP connection: %s\n", err)
        return
    }

    if err = client.StartTLS(conf); err != nil {
        log.Printf("Error performing StartTLS: %s\n", err)
        return
    }

    if ok, _ := client.Extension("AUTH"); ok {
        if err := client.Auth(auth); err != nil {
            log.Printf("Error during AUTH %s\n", err)
            return
        }
    }

    if err := client.Mail(from); err != nil {
        log.Printf("Error: %s\n", err)
        return
    }

    for _, addr := range to {
        if err := client.Rcpt(addr); err != nil {
            log.Printf("Error: %s\n", err)
            return
        }
    }

    w, err := client.Data()
    if err != nil {
        log.Printf("Error: %s\n", err)
        return
    }

    _, err = w.Write(msg)
    if err != nil {
        log.Printf("Error: %s\n", err)
        return

    }

    err = w.Close()
    if err != nil {
        log.Printf("Error: %s\n", err)
        return

    }

    client.Quit()
}

// Code below from http://golang.org/src/pkg/crypto/tls/handshake_server_test.go

func bigFromString(s string) *big.Int {
    ret := new(big.Int)
    ret.SetString(s, 10)
    return ret
}

func fromHex(s string) []byte {
    b, _ := hex.DecodeString(s)
    return b
}

var testRSACertificate = fromHex("308202b030820219a00302010202090085b0bba48a7fb8ca300d06092a864886f70d01010505003045310b3009060355040613024155311330110603550408130a536f6d652d53746174653121301f060355040a1318496e7465726e6574205769646769747320507479204c7464301e170d3130303432343039303933385a170d3131303432343039303933385a3045310b3009060355040613024155311330110603550408130a536f6d652d53746174653121301f060355040a1318496e7465726e6574205769646769747320507479204c746430819f300d06092a864886f70d010101050003818d0030818902818100bb79d6f517b5e5bf4610d0dc69bee62b07435ad0032d8a7a4385b71452e7a5654c2c78b8238cb5b482e5de1f953b7e62a52ca533d6fe125c7a56fcf506bffa587b263fb5cd04d3d0c921964ac7f4549f5abfef427100fe1899077f7e887d7df10439c4a22edb51c97ce3c04c3b326601cfafb11db8719a1ddbdb896baeda2d790203010001a381a73081a4301d0603551d0e04160414b1ade2855acfcb28db69ce2369ded3268e18883930750603551d23046e306c8014b1ade2855acfcb28db69ce2369ded3268e188839a149a4473045310b3009060355040613024155311330110603550408130a536f6d652d53746174653121301f060355040a1318496e7465726e6574205769646769747320507479204c746482090085b0bba48a7fb8ca300c0603551d13040530030101ff300d06092a864886f70d010105050003818100086c4524c76bb159ab0c52ccf2b014d7879d7a6475b55a9566e4c52b8eae12661feb4f38b36e60d392fdf74108b52513b1187a24fb301dbaed98b917ece7d73159db95d31d78ea50565cd5825a2d5a5f33c4b6d8c97590968c0f5298b5cd981f89205ff2a01ca31b9694dda9fd57e970e8266d71999b266e3850296c90a7bdd9")

var testRSAPrivateKey = &rsa.PrivateKey{
    PublicKey: rsa.PublicKey{
        N: bigFromString("131650079503776001033793877885499001334664249354723305978524647182322416328664556247316495448366990052837680518067798333412266673813370895702118944398081598789828837447552603077848001020611640547221687072142537202428102790818451901395596882588063427854225330436740647715202971973145151161964464812406232198521"),
        E: 65537,
    },
    D: bigFromString("29354450337804273969007277378287027274721892607543397931919078829901848876371746653677097639302788129485893852488285045793268732234230875671682624082413996177431586734171663258657462237320300610850244186316880055243099640544518318093544057213190320837094958164973959123058337475052510833916491060913053867729"),
    Primes: []*big.Int{
        bigFromString("11969277782311800166562047708379380720136961987713178380670422671426759650127150688426177829077494755200794297055316163155755835813760102405344560929062149"),
        bigFromString("10998999429884441391899182616418192492905073053684657075974935218461686523870125521822756579792315215543092255516093840728890783887287417039645833477273829"),
    },
}

Playground

like image 38
Intermernet Avatar answered Dec 28 '22 08:12

Intermernet