2022-01-06 08:00:24 +00:00
package smtp
2020-05-20 12:28:08 +00:00
import (
"crypto/tls"
2021-04-28 07:53:33 +00:00
"net"
"net/smtp"
2022-02-16 15:49:17 +00:00
"github.com/pkg/errors"
2022-04-26 23:01:45 +00:00
"github.com/zitadel/logging"
2022-02-16 15:49:17 +00:00
2022-04-26 23:01:45 +00:00
caos_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/notification/channels"
"github.com/zitadel/zitadel/internal/notification/messages"
2020-05-20 12:28:08 +00:00
)
2022-01-06 08:00:24 +00:00
var _ channels . NotificationChannel = ( * Email ) ( nil )
2020-05-20 12:28:08 +00:00
type Email struct {
2023-08-29 07:08:24 +00:00
smtpClient * smtp . Client
senderAddress string
senderName string
replyToAddress string
2020-05-20 12:28:08 +00:00
}
2023-10-10 13:20:53 +00:00
func InitChannel ( cfg * Config ) ( * Email , error ) {
client , err := cfg . SMTP . connectToSMTP ( cfg . Tls )
2020-05-20 12:28:08 +00:00
if err != nil {
2022-05-13 12:13:07 +00:00
logging . New ( ) . WithError ( err ) . Error ( "could not connect to smtp" )
2020-05-20 12:28:08 +00:00
return nil , err
}
2022-03-07 13:22:37 +00:00
logging . New ( ) . Debug ( "successfully initialized smtp email channel" )
2020-05-20 12:28:08 +00:00
return & Email {
2023-08-29 07:08:24 +00:00
smtpClient : client ,
2023-10-10 13:20:53 +00:00
senderName : cfg . FromName ,
senderAddress : cfg . From ,
replyToAddress : cfg . ReplyToAddress ,
2020-05-20 12:28:08 +00:00
} , nil
}
2022-01-06 08:00:24 +00:00
func ( email * Email ) HandleMessage ( message channels . Message ) error {
2020-05-20 12:28:08 +00:00
defer email . smtpClient . Close ( )
2022-01-06 08:00:24 +00:00
emailMsg , ok := message . ( * messages . Email )
2020-05-20 12:28:08 +00:00
if ! ok {
return caos_errs . ThrowInternal ( nil , "EMAIL-s8JLs" , "message is not EmailMessage" )
}
2022-01-06 08:00:24 +00: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 ) )
}
2022-02-16 15:49:17 +00:00
emailMsg . SenderEmail = email . senderAddress
emailMsg . SenderName = email . senderName
2023-08-29 07:08:24 +00:00
emailMsg . ReplyToAddress = email . replyToAddress
2020-05-20 12:28:08 +00: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
}
2023-03-28 22:09:06 +00:00
content , err := emailMsg . GetContent ( )
if err != nil {
return err
}
_ , err = w . Write ( [ ] byte ( content ) )
2020-05-20 12:28:08 +00:00
if err != nil {
return err
}
err = w . Close ( )
if err != nil {
return err
}
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 07:53:33 +00:00
return nil , caos_errs . ThrowInternal ( err , "EMAIL-skwos" , "could not make smtp dial" )
2020-05-20 12:28:08 +00: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 07:53:33 +00: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 12:28:08 +00:00
if err != nil {
2021-04-28 07:53:33 +00:00
return nil , caos_errs . ThrowInternal ( err , "EMAIL-sl39s" , "could not make tls dial" )
2020-05-20 12:28:08 +00:00
}
client , err := smtp . NewClient ( conn , host )
if err != nil {
2021-04-28 07:53:33 +00:00
return nil , caos_errs . ThrowInternal ( err , "EMAIL-skwi4" , "could not create smtp client" )
2020-05-20 12:28:08 +00:00
}
return client , err
}
2021-04-28 07:53:33 +00: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 12:28:08 +00:00
func ( smtpConfig SMTP ) smtpAuth ( client * smtp . Client , host string ) error {
if ! smtpConfig . HasAuth ( ) {
return nil
}
// Auth
2023-08-29 15:37:30 +00:00
auth := unencryptedAuth {
smtp . PlainAuth ( "" , smtpConfig . User , smtpConfig . Password , host ) ,
}
2020-05-20 12:28:08 +00:00
err := client . Auth ( auth )
2023-03-28 22:09:06 +00:00
if err != nil {
return caos_errs . ThrowInternalf ( err , "EMAIL-s9kfs" , "could not add smtp auth for user %s" , smtpConfig . User )
}
return nil
2020-05-20 12:28:08 +00:00
}