Created
January 5, 2020 09:39
-
-
Save murakmii/939b6b9eef4ab040e471ae69bca9ecb6 to your computer and use it in GitHub Desktop.
SMTPでお喋り
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bufio" | |
"crypto/tls" | |
"encoding/base64" | |
"fmt" | |
"strconv" | |
"strings" | |
) | |
const ( | |
gmailUsername = "<username>" | |
gmailPassword = "<password>" | |
sendTo = "<mail-address>" | |
) | |
type ( | |
smtpClient struct { | |
conn *tls.Conn | |
reader *bufio.Reader | |
handler smtpReplyHandler | |
} | |
smtpReply struct { | |
Code int | |
Messages []string | |
} | |
smtpReplyHandler func(*smtpReply) []byte | |
) | |
func connectToSMTPServer(host string, handler smtpReplyHandler) (*smtpClient, error) { | |
conn, err := tls.Dial("tcp", host+":465", &tls.Config{}) | |
if err != nil { | |
return nil, fmt.Errorf("failed to connect smtp server: %s", err) | |
} | |
return &smtpClient{ | |
conn: conn, | |
reader: bufio.NewReader(conn), | |
handler: handler, | |
}, nil | |
} | |
func (sc *smtpClient) startTransaction() error { | |
var continued *smtpReply | |
for { | |
rawReply, err := sc.reader.ReadString('\n') | |
if err != nil { | |
return fmt.Errorf("failed to read reply from smtp server: %s", err) | |
} | |
reply, multi, err := parseReply(strings.TrimSuffix(rawReply, "\r\n")) | |
if err != nil { | |
return fmt.Errorf("smtp server replied wrong: %s", err) | |
} | |
if continued != nil { | |
if continued.Code != reply.Code { | |
return fmt.Errorf("smtp server replied wrong(code mismatch: %d <=> %d)", continued.Code, reply.Code) | |
} | |
reply.Messages = append(continued.Messages, reply.Messages...) | |
} | |
if multi { | |
continued = reply | |
continue | |
} else { | |
continued = nil | |
} | |
fmt.Printf("[server] --> [client] %s\n", reply) | |
if reply.Code == 221 { | |
fmt.Printf("smtp server replied 221. transaction finished\n") | |
return nil | |
} | |
msg := sc.handler(reply) | |
if len(msg) == 0 { | |
continue | |
} | |
fmt.Printf("[server] <-- [client] %s", string(msg)) | |
if _, err = sc.conn.Write(msg); err != nil { | |
return fmt.Errorf("failed to write: %s", err) | |
} | |
} | |
} | |
func (sc *smtpClient) close() error { | |
return sc.conn.Close() | |
} | |
func parseReply(rawReply string) (*smtpReply, bool, error) { | |
code, err := strconv.ParseInt(rawReply[0:3], 10, 31) | |
if err != nil { | |
return nil, false, fmt.Errorf("invalid code: %s", err) | |
} | |
if code < 100 || code > 599 { | |
return nil, false, fmt.Errorf("unsupported code: %d", code) | |
} | |
reply := &smtpReply{ | |
Code: int(code), | |
Messages: strings.Split(rawReply[4:], " "), | |
} | |
multi := rawReply[3] == '-' | |
return reply, multi, nil | |
} | |
func (sr *smtpReply) String() string { | |
return fmt.Sprintf("%d %s", sr.Code, strings.Join(sr.Messages, " ")) | |
} | |
func main() { | |
cmdWhenOK := []string{ | |
"AUTH PLAIN\r\n", | |
fmt.Sprintf("MAIL FROM:<%[email protected]>\r\n", gmailUsername), | |
fmt.Sprintf("RCPT TO:<%s>\r\n", sendTo), | |
"DATA\r\n", | |
"QUIT\r\n", | |
} | |
client, err := connectToSMTPServer("smtp.gmail.com", func(reply *smtpReply) []byte { | |
switch reply.Code { | |
case 220: | |
return []byte("EHLO gmail.com\r\n") | |
case 235, 250: | |
msg := cmdWhenOK[0] | |
cmdWhenOK = cmdWhenOK[1:] | |
return []byte(msg) | |
case 334: | |
auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("\x00%s\x00%s", gmailUsername, gmailPassword))) | |
return []byte(auth + "\r\n") | |
case 354: | |
mail := fmt.Sprintf(`From: %[email protected] | |
To: %s | |
Subject: test from go | |
Hello! | |
. | |
`, gmailUsername, sendTo) | |
return []byte(strings.ReplaceAll(mail, "\n", "\r\n")) | |
default: | |
return []byte("QUIT\r\n") | |
} | |
}) | |
if err != nil { | |
panic(err) | |
} | |
defer client.close() | |
if err := client.startTransaction(); err != nil { | |
panic(err) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment