2022-01-06 09:00:24 +01:00
package smtp
2020-05-20 14:28:08 +02:00
import (
"crypto/tls"
2021-04-28 09:53:33 +02:00
"net"
"net/smtp"
2022-01-06 09:00:24 +01:00
"github.com/caos/zitadel/internal/notification/messages"
2020-05-20 14:28:08 +02:00
"github.com/caos/logging"
caos_errs "github.com/caos/zitadel/internal/errors"
2022-01-06 09:00:24 +01:00
"github.com/caos/zitadel/internal/notification/channels"
2021-04-28 09:53:33 +02:00
"github.com/pkg/errors"
2020-05-20 14:28:08 +02:00
)
2022-01-06 09:00:24 +01:00
var _ channels . NotificationChannel = ( * Email ) ( nil )
2020-05-20 14:28:08 +02:00
type Email struct {
smtpClient * smtp . Client
}
2022-01-06 09:00:24 +01:00
func InitSMTPChannel ( config EmailConfig ) ( * Email , error ) {
2020-05-20 14:28:08 +02:00
client , err := config . SMTP . connectToSMTP ( config . Tls )
if err != nil {
return nil , err
}
2022-01-06 09:00:24 +01:00
logging . Log ( "NOTIF-4n4Ih" ) . Debug ( "successfully initialized smtp email channel" )
2020-05-20 14:28:08 +02:00
return & Email {
smtpClient : client ,
} , nil
}
2022-01-06 09:00:24 +01:00
func ( email * Email ) HandleMessage ( message channels . Message ) error {
2020-05-20 14:28:08 +02:00
defer email . smtpClient . Close ( )
2022-01-06 09:00:24 +01:00
emailMsg , ok := message . ( * messages . Email )
2020-05-20 14:28:08 +02:00
if ! ok {
return caos_errs . ThrowInternal ( nil , "EMAIL-s8JLs" , "message is not EmailMessage" )
}
2022-01-06 09:00:24 +01:00
if emailMsg . Content == "" || emailMsg . Subject == "" || len ( emailMsg . Recipients ) == 0 {
return caos_errs . ThrowInternalf ( nil , "EMAIL-zGemZ" , "subject, recipients and content must be set but got subject %s, recipients length %d and content length %d" , emailMsg . Subject , len ( emailMsg . Recipients ) , len ( emailMsg . Content ) )
}
2020-05-20 14:28:08 +02:00
// To && From
if err := email . smtpClient . Mail ( emailMsg . SenderEmail ) ; err != nil {
return caos_errs . ThrowInternalf ( err , "EMAIL-s3is3" , "could not set sender: %v" , emailMsg . SenderEmail )
}
for _ , recp := range append ( append ( emailMsg . Recipients , emailMsg . CC ... ) , emailMsg . BCC ... ) {
if err := email . smtpClient . Rcpt ( recp ) ; err != nil {
return caos_errs . ThrowInternalf ( err , "EMAIL-s4is4" , "could not set recipient: %v" , recp )
}
}
// Data
w , err := email . smtpClient . Data ( )
if err != nil {
return err
}
_ , err = w . Write ( [ ] byte ( emailMsg . GetContent ( ) ) )
if err != nil {
return err
}
err = w . Close ( )
if err != nil {
return err
}
defer logging . LogWithFields ( "EMAI-a1c87ec8" ) . Debug ( "email sent" )
return email . smtpClient . Quit ( )
}
func ( smtpConfig SMTP ) connectToSMTP ( tlsRequired bool ) ( client * smtp . Client , err error ) {
host , _ , err := net . SplitHostPort ( smtpConfig . Host )
if err != nil {
return nil , caos_errs . ThrowInternal ( err , "EMAIL-spR56" , "could not split host and port for connect to smtp" )
}
if ! tlsRequired {
client , err = smtpConfig . getSMPTClient ( )
} else {
client , err = smtpConfig . getSMPTClientWithTls ( host )
}
if err != nil {
return nil , err
}
err = smtpConfig . smtpAuth ( client , host )
if err != nil {
return nil , err
}
return client , nil
}
func ( smtpConfig SMTP ) getSMPTClient ( ) ( * smtp . Client , error ) {
client , err := smtp . Dial ( smtpConfig . Host )
if err != nil {
2021-04-28 09:53:33 +02:00
return nil , caos_errs . ThrowInternal ( err , "EMAIL-skwos" , "could not make smtp dial" )
2020-05-20 14:28:08 +02:00
}
return client , nil
}
func ( smtpConfig SMTP ) getSMPTClientWithTls ( host string ) ( * smtp . Client , error ) {
conn , err := tls . Dial ( "tcp" , smtpConfig . Host , & tls . Config { } )
2021-04-28 09:53:33 +02:00
if errors . As ( err , & tls . RecordHeaderError { } ) {
logging . Log ( "MAIN-xKIzT" ) . OnError ( err ) . Warn ( "could not connect using normal tls. trying starttls instead..." )
return smtpConfig . getSMPTClientWithStartTls ( host )
}
2020-05-20 14:28:08 +02:00
if err != nil {
2021-04-28 09:53:33 +02:00
return nil , caos_errs . ThrowInternal ( err , "EMAIL-sl39s" , "could not make tls dial" )
2020-05-20 14:28:08 +02:00
}
client , err := smtp . NewClient ( conn , host )
if err != nil {
2021-04-28 09:53:33 +02:00
return nil , caos_errs . ThrowInternal ( err , "EMAIL-skwi4" , "could not create smtp client" )
2020-05-20 14:28:08 +02:00
}
return client , err
}
2021-04-28 09:53:33 +02:00
func ( smtpConfig SMTP ) getSMPTClientWithStartTls ( host string ) ( * smtp . Client , error ) {
client , err := smtpConfig . getSMPTClient ( )
if err != nil {
return nil , err
}
if err := client . StartTLS ( & tls . Config {
ServerName : host ,
} ) ; err != nil {
return nil , caos_errs . ThrowInternal ( err , "EMAIL-guvsQ" , "could not start tls" )
}
return client , nil
}
2020-05-20 14:28:08 +02:00
func ( smtpConfig SMTP ) smtpAuth ( client * smtp . Client , host string ) error {
if ! smtpConfig . HasAuth ( ) {
return nil
}
// Auth
auth := smtp . PlainAuth ( "" , smtpConfig . User , smtpConfig . Password , host )
err := client . Auth ( auth )
2021-04-28 09:53:33 +02:00
logging . Log ( "EMAIL-s9kfs" ) . WithField ( "smtp user" , smtpConfig . User ) . OnError ( err ) . Debug ( "could not add smtp auth" )
2020-05-20 14:28:08 +02:00
return err
}