Skip to content

Commit

Permalink
Merge pull request #207 from openinfradev/release
Browse files Browse the repository at this point in the history
20231114 release to main (v3.1.2)
  • Loading branch information
ktkfree authored Nov 14, 2023
2 parents b9462b4 + f0b1597 commit 62aeb14
Show file tree
Hide file tree
Showing 2 changed files with 234 additions and 6 deletions.
238 changes: 233 additions & 5 deletions internal/mail/smtp.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
package mail

import (
"bytes"
"crypto/tls"
"embed"
"errors"
"fmt"
"io"
"net"
"net/smtp"
"strings"
"time"

"github.com/openinfradev/tks-api/pkg/log"
"github.com/spf13/viper"
"gopkg.in/gomail.v2"
Expand Down Expand Up @@ -44,11 +53,7 @@ func (s *SmtpMailer) SendMail() error {
s.client.SetHeader("Subject", s.message.Subject)
s.client.SetBody("text/html", s.message.Body)

d := gomail.NewDialer(s.Host, s.Port, s.Username, s.Password)
if s.Port == 25 || s.Port == 587 || s.Port == 2587 {
d.SSL = false
}

d := NewDialer(s.Host, s.Port, s.Username, s.Password)
if err := d.DialAndSend(s.client); err != nil {
log.Errorf("failed to send email, %v", err)
return err
Expand Down Expand Up @@ -132,3 +137,226 @@ func NewSmtpMailer(m *MessageInfo) *SmtpMailer {

return mailer
}

// smtp 25 NonTLS Support
type unencryptedAuth struct {
smtp.Auth
}

func (a unencryptedAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
s := *server
s.TLS = true
return a.Auth.Start(&s)
}

// type, function override
var (
netDialTimeout = net.DialTimeout
tlsClient = tls.Client
smtpNewClient = func(conn net.Conn, host string) (smtpClient, error) {
return smtp.NewClient(conn, host)
}
)

type Dialer struct {
Host string
Port int
Username string
Password string
Auth smtp.Auth
SSL bool
TLSConfig *tls.Config
LocalName string
}

func NewDialer(host string, port int, username, password string) *Dialer {
return &Dialer{
Host: host,
Port: port,
Username: username,
Password: password,
SSL: port == 465,
}
}

func (d *Dialer) DialAndSend(m ...*gomail.Message) error {
s, err := d.Dial()
if err != nil {
return err
}
defer s.Close()

return gomail.Send(s, m...)
}

func addr(host string, port int) string {
return fmt.Sprintf("%s:%d", host, port)
}

func (d *Dialer) Dial() (gomail.SendCloser, error) {
conn, err := netDialTimeout("tcp", addr(d.Host, d.Port), 10*time.Second)
if err != nil {
return nil, err
}

if d.SSL {
conn = tlsClient(conn, d.tlsConfig())
}

c, err := smtpNewClient(conn, d.Host)
if err != nil {
return nil, err
}

if d.LocalName != "" {
if err := c.Hello(d.LocalName); err != nil {
return nil, err
}
}

if !d.SSL {
if ok, _ := c.Extension("STARTTLS"); ok {
if err := c.StartTLS(d.tlsConfig()); err != nil {
c.Close()
return nil, err
}
}
}

if d.Auth == nil && d.Username != "" {
if ok, auths := c.Extension("AUTH"); ok {
if strings.Contains(auths, "CRAM-MD5") {
d.Auth = smtp.CRAMMD5Auth(d.Username, d.Password)
} else if strings.Contains(auths, "LOGIN") &&
!strings.Contains(auths, "PLAIN") {
d.Auth = &loginAuth{
username: d.Username,
password: d.Password,
host: d.Host,
}
} else {
// NonTLS SMTP 25 support
d.Auth = unencryptedAuth{
smtp.PlainAuth(
"",
d.Username,
d.Password,
d.Host,
),
}
//d.Auth = smtp.PlainAuth("", d.Username, d.Password, d.Host)
}
}
}

if d.Auth != nil {
if err = c.Auth(d.Auth); err != nil {
c.Close()
return nil, err
}
}

return &smtpSender{c, d}, nil
}

func (d *Dialer) tlsConfig() *tls.Config {
if d.TLSConfig == nil {
return &tls.Config{ServerName: d.Host}
}
return d.TLSConfig
}

type smtpClient interface {
Hello(string) error
Extension(string) (bool, string)
StartTLS(*tls.Config) error
Auth(smtp.Auth) error
Mail(string) error
Rcpt(string) error
Data() (io.WriteCloser, error)
Quit() error
Close() error
}

type loginAuth struct {
username string
password string
host string
}

func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
if !server.TLS {
advertised := false
for _, mechanism := range server.Auth {
if mechanism == "LOGIN" {
advertised = true
break
}
}
if !advertised {
return "", nil, errors.New("gomail: unencrypted connection")
}
}
if server.Name != a.host {
return "", nil, errors.New("gomail: wrong host name")
}
return "LOGIN", nil, nil
}

func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if !more {
return nil, nil
}

switch {
case bytes.Equal(fromServer, []byte("Username:")):
return []byte(a.username), nil
case bytes.Equal(fromServer, []byte("Password:")):
return []byte(a.password), nil
default:
return nil, fmt.Errorf("gomail: unexpected server challenge: %s", fromServer)
}
}

type smtpSender struct {
smtpClient
d *Dialer
}

func (c *smtpSender) Send(from string, to []string, msg io.WriterTo) error {
if err := c.Mail(from); err != nil {
if err == io.EOF {
// This is probably due to a timeout, so reconnect and try again.
sc, derr := c.d.Dial()
if derr == nil {
if s, ok := sc.(*smtpSender); ok {
*c = *s
return c.Send(from, to, msg)
}
}
}
return err
}

for _, addr := range to {
if err := c.Rcpt(addr); err != nil {
return err
}
}

w, err := c.Data()
if err != nil {
return err
}

if _, err = msg.WriteTo(w); err != nil {
w.Close()
return err
}

return w.Close()
}

func (c *smtpSender) Close() error {
return c.Quit()
}
2 changes: 1 addition & 1 deletion pkg/api-client/api-client.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ func (c *ApiClientImpl) callWithBody(prefix string, method string, path string,
res.Body.Close()
}()

if res.StatusCode%100 != 2 {
if res.StatusCode < 200 || res.StatusCode >= 300 {
var restError httpErrors.RestError
if err := json.Unmarshal(body, &restError); err != nil {
return nil, fmt.Errorf("Invalid http status. failed to unmarshal body : %s", err)
Expand Down

0 comments on commit 62aeb14

Please sign in to comment.