smpt.sendmail thow x509: certificate signed by unknown authority on windows - go

I try send mail using smtp package on windows host, but I got this error "x509: certificate signed by unknown authority".
Certificate that using mail server is valid.
How can I understand that this is due to the fact that the certificate store is configured incorrectly.
But I didn't find any information on how to do it.
package main
import (
"crypto/tls"
"fmt"
"log"
"net"
"net/mail"
"net/smtp"
)
// StartTLS Email Example
func main() {
from := mail.Address{"", "examplt#email.com"}
to := mail.Address{"", "alhaos#gmail.com"}
subj := "This is the email subject"
body := "This is an example body.\n With two lines."
// Setup headers
headers := make(map[string]string)
headers["From"] = from.String()
headers["To"] = to.String()
headers["Subject"] = subj
// Setup message
message := ""
for k, v := range headers {
message += fmt.Sprintf("%s: %s\r\n", k, v)
}
message += "\r\n" + body
// Connect to the SMTP Server
servername := "MyServer:587"
host, _, _ := net.SplitHostPort(servername)
auth := smtp.PlainAuth("", "username", "pass", host)
// TLS config
tlsconfig := &tls.Config{
// InsecureSkipVerify: true,
ServerName: host,
}
c, err := smtp.Dial(servername)
if err != nil {
log.Panic(err)
}
c.StartTLS(tlsconfig)
// Auth
if err = c.Auth(auth); err != nil {
log.Panic(err)
}
// To && From
if err = c.Mail(from.Address); err != nil {
log.Panic(err)
}
if err = c.Rcpt(to.Address); err != nil {
log.Panic(err)
}
// Data
w, err := c.Data()
if err != nil {
log.Panic(err)
}
_, err = w.Write([]byte(message))
if err != nil {
log.Panic(err)
}
err = w.Close()
if err != nil {
log.Panic(err)
}
c.Quit()
}

Related

SMTP client using a remote SOCKS5/proxy in Go

I am trying to create an SMTP client that uses a proxy connection, SOCKS5.
When I use a local host proxy the code successfully creates an SMTP client.
When I try to use a remote proxy, I am getting a TTL expired. I am also getting an EOF error when trying to use a different proxy connection.
I have set up a proxy server in my localhost, socks5://dante:maluki#127.0.0.1:1080
I have also set up an identical proxy server on my remote VM, socks5://dante:maluki#35.242.186.23:1080
package main
import (
"errors"
"log"
"net"
"net/smtp"
"net/url"
"time"
"golang.org/x/net/idna"
"golang.org/x/net/proxy"
)
const (
smtpTimeout = time.Second * 60
smtpPort = ":25"
)
func init() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
}
func main() {
// The code works when I use a localhost proxy
// socks5://dante:maluki#127.0.0.1:1080
client, err := newSMTPClient("gmail.com", "socks5://dante:maluki#35.242.186.23:1080")
if err != nil {
log.Println(err)
return
}
log.Println(client)
}
// establishProxyConnection connects to the address on the named network address
// via proxy protocol
func establishProxyConnection(addr, proxyURI string) (net.Conn, error) {
// return socks.Dial(proxyURI)("tcp", addr)
u, err := url.Parse(proxyURI)
if err != nil {
log.Println(err)
return nil, err
}
var iface proxy.Dialer
if u.User != nil {
auth := proxy.Auth{}
auth.User = u.User.Username()
auth.Password, _ = u.User.Password()
iface, err = proxy.SOCKS5("tcp", u.Host, &auth, &net.Dialer{Timeout: 30 * time.Second})
if err != nil {
log.Println(err)
return nil, err
}
} else {
iface, err = proxy.SOCKS5("tcp", u.Host, nil, proxy.FromEnvironment())
if err != nil {
log.Println(err)
return nil, err
}
}
dialfunc := iface.Dial
return dialfunc("tcp", addr)
}
// newSMTPClient generates a new available SMTP client
func newSMTPClient(domain, proxyURI string) (*smtp.Client, error) {
domain = domainToASCII(domain)
mxRecords, err := net.LookupMX(domain)
if err != nil {
log.Println(err)
return nil, err
}
if len(mxRecords) == 0 {
return nil, errors.New("No MX records found")
}
// Attempt to connect to SMTP servers
for _, r := range mxRecords {
// Simplified to make the code short
addr := r.Host + smtpPort
c, err := dialSMTP(addr, proxyURI)
if err != nil {
log.Println(err)
continue
}
return c, err
}
return nil, errors.New("failed to created smtp.Client")
}
// dialSMTP is a timeout wrapper for smtp.Dial. It attempts to dial an
// SMTP server (socks5 proxy supported) and fails with a timeout if timeout is reached while
// attempting to establish a new connection
func dialSMTP(addr, proxyURI string) (*smtp.Client, error) {
// Channel holding the new smtp.Client or error
ch := make(chan interface{}, 1)
// Dial the new smtp connection
go func() {
var conn net.Conn
var err error
conn, err = establishProxyConnection(addr, proxyURI)
if err != nil {
log.Println(err)
}
if err != nil {
ch <- err
return
}
host, _, err := net.SplitHostPort(addr)
if err != nil {
log.Println(err)
}
client, err := smtp.NewClient(conn, host)
log.Println(client)
if err != nil {
log.Println(err)
ch <- err
return
}
ch <- client
}()
// Retrieve the smtp client from our client channel or timeout
select {
case res := <-ch:
switch r := res.(type) {
case *smtp.Client:
return r, nil
case error:
return nil, r
default:
return nil, errors.New("Unexpected response dialing SMTP server")
}
case <-time.After(smtpTimeout):
return nil, errors.New("Timeout connecting to mail-exchanger")
}
}
// domainToASCII converts any internationalized domain names to ASCII
// reference: https://en.wikipedia.org/wiki/Punycode
func domainToASCII(domain string) string {
asciiDomain, err := idna.ToASCII(domain)
if err != nil {
return domain
}
return asciiDomain
}

Custom VerifyPeerCertificate in the crypto/tls package

I'm trying to write a custom VerifyPeerCertificate to get the certificate even if CN and FQDN do not match.
I'm new to golang, so I'm trying to modify some code that I've found, and make it work but without any success.
So here is my code :
package main
import (
"fmt"
"log"
"crypto/tls"
"crypto/x509"
)
func main() {
customVerify := func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
roots := x509.NewCertPool()
for _, rawCert := range rawCerts {
c, _ := x509.ParseCertificate(rawCert)
roots.AddCert(c)
}
cert, _ := x509.ParseCertificate(rawCerts[0])
fmt.Println("subject name is : ",cert.Subject.CommonName)
opts := x509.VerifyOptions{
DNSName: cert.Subject.CommonName,
Roots: roots,
}
if _, err := cert.Verify(opts); err != nil {
panic("failed to verify certificate: " + err.Error())
return err
}
return nil
}
log.SetFlags(log.Lshortfile)
conf := &tls.Config{
InsecureSkipVerify: true,
VerifyPeerCertificate: customVerify,
}
conn, err := tls.Dial("tcp", "127.0.0.1:9007", conf)
if err != nil {
log.Println(err)
return
}
defer conn.Close()
n, err := conn.Write([]byte("hello\n"))
if err != nil {
log.Println(n, err)
return
}
buf := make([]byte, 100)
n, err = conn.Read(buf)
if err != nil {
log.Println(n, err)
return
}
println(string(buf[:n]))
}
I'm trying to get the certificate of a local server.
when I try to run the code, I'm getting this error :
root#mymachine:~/Tproject# go run test.go
subject name is : dssdemo
test.go:50: remote error: tls: bad certificate
I've tried to mimic the example_Certificate_Verify
Can someone help me with this ?
Thank you in advance.
Edit:
Mutual HTTPS is causing the : test.go:50: remote error: tls: bad certificate
But still, Is it possible to somehow return the server certificate ?
This custom verification ignore all verfications:
func ipSCert(host, port string) ([]*x509.Certificate, string, error) {
var ipcertchain []*x509.Certificate
customVerify := func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
roots := x509.NewCertPool()
for _, rawCert := range rawCerts {
c, _ := x509.ParseCertificate(rawCert)
certItem, _ := x509.ParseCertificate(rawCert)
ipcertchain = append(ipcertchain, certItem)
roots.AddCert(c)
}
return nil
}
log.SetFlags(log.Lshortfile)
d := &net.Dialer{
Timeout: time.Duration(TimeoutSeconds) * time.Second,
}
cs, err := cipherSuite()
if err != nil {
return []*x509.Certificate{&x509.Certificate{}}, "", err
}
conf := &tls.Config{
InsecureSkipVerify: true,
VerifyPeerCertificate: customVerify,
CipherSuites: cs,
MaxVersion: tlsVersion(),
}
conn, err := tls.DialWithDialer(d, "tcp", host+":"+port, conf)
if err != nil {
return nil, "", err
}
conn.Close()
return ipcertchain, host, nil
}

Go Golang SMTP Script. Need to add BCC header

I am using this well known blank canvas for SMTP in Go.
I need to add in Bcc Blank Carbon Copy address to it but I have tried lots of things and nothing I am trying works which is strange...
I have tried adding in "headers["Bcc"] = "someemail#address.com" I am sure it is an easy modification.
Thanks in Advance..
package main
import (
"fmt"
"log"
"net"
"net/mail"
"net/smtp"
"crypto/tls"
)
func main() {
from := mail.Address{"", "username#example.tld"}
to := mail.Address{"", "username#anotherexample.tld"}
subj := "This is the email subject"
body := "This is an example body.\n With two lines."
headers := make(map[string]string)
headers["From"] = from.String()
headers["To"] = to.String()
headers["Subject"] = subj
message := ""
for k,v := range headers {
message += fmt.Sprintf("%s: %s\r\n", k, v)
}
message += "\r\n" + body
servername := "smtp.example.tld:465"
host, _, _ := net.SplitHostPort(servername)
auth := smtp.PlainAuth("","username#example.tld", "password", host)
tlsconfig := &tls.Config {
InsecureSkipVerify: true,
ServerName: host,
}
conn, err := tls.Dial("tcp", servername, tlsconfig)
if err != nil {
log.Panic(err)
}
c, err := smtp.NewClient(conn, host)
if err != nil {
log.Panic(err)
}
if err = c.Auth(auth); err != nil {
log.Panic(err)
}
if err = c.Mail(from.Address); err != nil {
log.Panic(err)
}
if err = c.Rcpt(to.Address); err != nil {
log.Panic(err)
}
w, err := c.Data()
if err != nil {
log.Panic(err)
}
_, err = w.Write([]byte(message))
if err != nil {
log.Panic(err)
}
err = w.Close()
if err != nil {
log.Panic(err)
}
c.Quit()
}
See the following segment from the smtp#SendMail package docs
The msg parameter should be an RFC 822-style email with headers first, a blank line, and then the message body. The lines of msg should be CRLF terminated. The msg headers should usually include fields such as "From", "To", "Subject", and "Cc". Sending "Bcc" messages is accomplished by including an email address in the to parameter but not including it in the msg headers.
In other words, don't add them in the headers, just to the recepient list.
In your example boilerplate code, you would add a call to c.Rcpt(...) for each email in the bcc list, and that's it. Nothing to add to headers.
You need to add a RCPT line for the Bcc address too, for example:
if err = c.Rcpt("someemail#address.com"); err != nil {
log.Panic(err)
}
as user10753492 pointed out, the standard doesn't include the concepts of carbon copies. You'd need to add one RCPT TO for each recipient and then add bcc: sample#email.com to the body. The client will take care of the rest.

Send email with attachments in golang

Here is the code:
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"net/url"
"os"
"os/user"
"path/filepath"
"golang.org/x/net/context"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"google.golang.org/api/gmail/v1"
"encoding/base64"
"io/ioutil"
)
// getClient uses a Context and Config to retrieve a Token
// then generate a Client. It returns the generated Client.
func getClient(ctx context.Context, config *oauth2.Config, configFileName string) *http.Client {
cacheFile, err := tokenCacheFile(configFileName)
if err != nil {
log.Fatalf("Unable to get path to cached credential file. %v", err)
}
tok, err := tokenFromFile(cacheFile)
if err != nil {
tok = getTokenFromWeb(config)
saveToken(cacheFile, tok)
}
return config.Client(ctx, tok)
}
// getTokenFromWeb uses Config to request a Token.
// It returns the retrieved Token.
func getTokenFromWeb(config *oauth2.Config) *oauth2.Token {
authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
fmt.Printf("Go to the following link in your browser then type the " +
"authorization code: \n%v\n", authURL)
var code string
if _, err := fmt.Scan(&code); err != nil {
log.Fatalf("Unable to read authorization code %v", err)
}
tok, err := config.Exchange(oauth2.NoContext, code)
if err != nil {
log.Fatalf("Unable to retrieve token from web %v", err)
}
return tok
}
// tokenCacheFile generates credential file path/filename.
// It returns the generated credential path/filename.
func tokenCacheFile(filename string) (string, error) {
usr, err := user.Current()
if err != nil {
return "", err
}
tokenCacheDir := filepath.Join(usr.HomeDir, ".credentials")
os.MkdirAll(tokenCacheDir, 0700)
return filepath.Join(tokenCacheDir,
url.QueryEscape(filename)), err
}
// tokenFromFile retrieves a Token from a given file path.
// It returns the retrieved Token and any read error encountered.
func tokenFromFile(file string) (*oauth2.Token, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
t := &oauth2.Token{}
err = json.NewDecoder(f).Decode(t)
defer f.Close()
return t, err
}
// saveToken uses a file path to create a file and store the
// token in it.
func saveToken(file string, token *oauth2.Token) {
fmt.Printf("Saving credential file to: %s\n", file)
f, err := os.Create(file)
if err != nil {
log.Fatalf("Unable to cache oauth token: %v", err)
}
defer f.Close()
json.NewEncoder(f).Encode(token)
}
func main() {
// Use oauth2.NoContext if there isn't a good context to pass in.
//ctx := context.TODO()
ctx := context.Background()
b, err := ioutil.ReadFile("client_secret.json")
if err != nil {
log.Fatalf("Unable to read client secret file: %v", err)
}
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/gmail-go-quickstart.json
sendConfig, err := google.ConfigFromJSON(b, gmail.GmailSendScope)
if err != nil {
log.Fatalf("Unable to parse client secret file to config: %v", err)
}
sendClient := getClient(ctx, sendConfig, "send.json")
sendService, err := gmail.New(sendClient)
if err != nil {
log.Fatalf("Unable to retrieve gmail Client %v", err)
}
if err := SendEmail(ctx, sendService, "jane1988#gmail.com"); err != nil {
log.Fatalf("failed to send email: %v", err)
}
}
func SendEmail(ctx context.Context, svc *gmail.Service, email string) error {
header := make(map[string]string)
header["To"] = email
header["Subject"] = "hello there"
header["MIME-Version"] = "1.0"
header["Content-Type"] = `text/html; charset="utf-8"`
header["Content-Transfer-Encoding"] = "base64"
var msg string
for k, v := range header {
msg += fmt.Sprintf("%s: %s\n", k, v)
}
msg += "\n" + "Hello, Gmail!"
gmsg := gmail.Message{
Raw: encodeWeb64String([]byte(msg)),
}
_, err := svc.Users.Messages.Send("me", &gmsg).Do()
return err
}
func encodeWeb64String(b []byte) string {
s := base64.URLEncoding.EncodeToString(b)
var i = len(s) - 1
for s[i] == '=' {
i--
}
return s[0 : i + 1]
}
This works perfectly, but without attachments. How can I attach files to the mail?
Maybe you can try change the header Content-Type to multipart/mixed (RFC 2046, Section 5.1.3) or multipart/alternative (RFC 2046, Section 5.1.4) and check how to use Content-Disposition: attachment; filename=<your file here.ext>.

How to send email through Gmail Go SDK?

I'm trying to send a new email through the gmail package . However the Message type which is required by the send method is poorly documented. Most of the fields seem used to actually parse/read emails. The only field which makes sense (at some degree) for the send method is Payload of type MessagePart though I can't figure it out how to generate the MessagePartBody as it seems to be a kind of mime type. Below is the code I have so far.
func (em *Email) SendMessage(cl *Client) error {
config.ClientId = cl.Username
config.ClientSecret = cl.Password
t := &oauth.Transport{
Config: config,
Transport: http.DefaultTransport,
}
var tk oauth.Token
err := json.Unmarshal([]byte(cl.Meta), &tk)
t.Token = &tk
if err != nil {
log.Errorf("meta %v, err %v", cl.Meta, err)
return err
}
gmailService, err := gmail.New(t.Client())
if err != nil {
log.Error(err)
return err
}
p := gmail.MessagePart{}
p.Headers = append(p.Headers, &gmail.MessagePartHeader{
Name: "From",
Value: em.FromEmail,
})
p.Headers = append(p.Headers, &gmail.MessagePartHeader{
Name: "To",
Value: em.ToEmail,
})
p.Headers = append(p.Headers, &gmail.MessagePartHeader{
Name: "Subject",
Value: em.Subject,
})
emsg := base64.StdEncoding.EncodeToString(em.Message)
log.Info(emsg)
msg := gmail.Message{
Payload: &p,
Raw: "",
}
_, err = gmailService.Users.Messages.Send("me", &msg).Do()
if err != nil {
log.Error(err)
return err
}
return err
}
The "REST" API is even more confusing. It requires an uploadType param (WTF to upload) and a raw field which I guess is the raw message which requires a format provided by messages.get. Why would you send a message from your inbox which literally would be a 'resend' as your are on the receipt list ? Am I the only one who thinks this API(or at least the documentation) is just crap ?
It was a bit tricky but here is how you can send emails through the GMAIL API
import(
"code.google.com/p/goauth2/oauth"
"code.google.com/p/google-api-go-client/gmail/v1"
log "github.com/golang/glog"
"encoding/base64"
"encoding/json"
"net/mail"
"strings"
)
type Email struct {
FromName, FromEmail, ToName, ToEmail, Subject string
Message string
}
func (em *Email) SendMessage(cl *Client) error {
config.ClientId = cl.Username //oauth clientID
config.ClientSecret = cl.Password //oauth client secret
t := &oauth.Transport{
Config: config,
Transport: http.DefaultTransport,
}
var tk oauth.Token
err := json.Unmarshal([]byte(cl.Meta), &tk)
t.Token = &tk
if err != nil {
log.Errorf("meta %v, err %v", cl.Meta, err)
return err
}
gmailService, err := gmail.New(t.Client())
if err != nil {
log.Error(err)
return err
}
from := mail.Address{em.FromName, em.FromEmail}
to := mail.Address{em.ToName, em.ToEmail}
header := make(map[string]string)
header["From"] = from.String()
header["To"] = to.String()
header["Subject"] = encodeRFC2047(em.Subject)
header["MIME-Version"] = "1.0"
header["Content-Type"] = "text/html; charset=\"utf-8\""
header["Content-Transfer-Encoding"] = "base64"
var msg string
for k, v := range header {
msg += fmt.Sprintf("%s: %s\r\n", k, v)
}
msg += "\r\n" + em.Message
gmsg := gmail.Message{
Raw: encodeWeb64String([]byte(msg)),
}
_, err = gmailService.Users.Messages.Send("me", &gmsg).Do()
if err != nil {
log.Errorf("em %v, err %v", gmsg, err)
return err
}
return err
}
func encodeRFC2047(s string) string {
// use mail's rfc2047 to encode any string
addr := mail.Address{s, ""}
return strings.Trim(addr.String(), " <>")
}
func encodeWeb64String(b []byte) string {
s := base64.URLEncoding.EncodeToString(b)
var i = len(s) - 1
for s[i] == '=' {
i--
}
return s[0 : i+1]
}
Similar to #hey 's answer, but I tidied it up, and allowed the email to put newlines in the email body through \n and show up correctly on the email client. Also, #hey is not using the new supported Gmail API. Here is the final code:
import (
"encoding/base64"
"golang.org/x/net/context"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"google.golang.org/api/gmail/v1"
"encoding/json"
"net/mail"
)
type Email struct {
FromName string
FromEmail string
ToName string
ToEmail string
Subject string
Message string
}
func (em *Email) sendMailFromEmail() error {
b, err := ioutil.ReadFile("credentials.json")
if err != nil {
log.Fatalf("Unable to read client secret file: %v", err)
}
// If modifying these scopes, delete your previously saved token.json.
config, err := google.ConfigFromJSON(b, gmail.GmailSendScope)
if err != nil {
log.Fatalf("Unable to parse client secret file to config: %v", err)
}
cl := getClientMail(config)
gmailService, err := gmail.New(cl)
if err != nil {
log.Fatalf("Unable to retrieve Gmail client: %v", err)
}
from := mail.Address{em.FromName, em.FromEmail}
to := mail.Address{em.ToName, em.ToEmail}
header := make(map[string]string)
header["From"] = from.String()
header["To"] = to.String()
header["Subject"] = em.Subject
header["MIME-Version"] = "1.0"
header["Content-Type"] = "text/plain; charset=\"utf-8\""
header["Content-Transfer-Encoding"] = "base64"
var msg string
for k, v := range header {
msg += fmt.Sprintf("%s: %s\r\n", k, v)
}
msg += "\r\n" + em.Message
gmsg := gmail.Message{
Raw: base64.RawURLEncoding.EncodeToString([]byte(msg)),
}
_, err = gmailService.Users.Messages.Send("me", &gmsg).Do()
if err != nil {
log.Printf("em %v, err %v", gmsg, err)
return err
}
return err
}
I did not include the following functions: getClient, getTokenFromWeb, tokenFromFile, and saveToken. You can find them, and learn how to enable the Gmail API through this tutorial by Google.

Resources