2022-01-06 09:00:24 +01:00
package smtp
2020-05-20 14:28:08 +02:00
import (
"crypto/tls"
2023-12-05 19:01:03 +02:00
"errors"
2021-04-28 09:53:33 +02:00
"net"
"net/smtp"
2022-04-27 01:01:45 +02:00
"github.com/zitadel/logging"
2022-02-16 16:49:17 +01:00
2022-04-27 01:01:45 +02:00
"github.com/zitadel/zitadel/internal/notification/channels"
"github.com/zitadel/zitadel/internal/notification/messages"
2023-12-08 16:30:55 +02:00
"github.com/zitadel/zitadel/internal/zerrors"
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 {
2023-08-29 09:08:24 +02:00
smtpClient * smtp . Client
senderAddress string
senderName string
replyToAddress string
2020-05-20 14:28:08 +02:00
}
2023-10-10 15:20:53 +02:00
func InitChannel ( cfg * Config ) ( * Email , error ) {
client , err := cfg . SMTP . connectToSMTP ( cfg . Tls )
2020-05-20 14:28:08 +02:00
if err != nil {
2022-05-13 14:13:07 +02:00
logging . New ( ) . WithError ( err ) . Error ( "could not connect to smtp" )
2020-05-20 14:28:08 +02:00
return nil , err
}
2022-03-07 14:22:37 +01:00
logging . New ( ) . Debug ( "successfully initialized smtp email channel" )
2020-05-20 14:28:08 +02:00
return & Email {
2023-08-29 09:08:24 +02:00
smtpClient : client ,
2023-10-10 15:20:53 +02:00
senderName : cfg . FromName ,
senderAddress : cfg . From ,
replyToAddress : cfg . ReplyToAddress ,
2020-05-20 14:28:08 +02:00
} , 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 {
2023-12-08 16:30:55 +02:00
return zerrors . ThrowInternal ( nil , "EMAIL-s8JLs" , "message is not EmailMessage" )
2020-05-20 14:28:08 +02:00
}
2022-01-06 09:00:24 +01:00
if emailMsg . Content == "" || emailMsg . Subject == "" || len ( emailMsg . Recipients ) == 0 {
2023-12-08 16:30:55 +02:00
return zerrors . 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-01-06 09:00:24 +01:00
}
2022-02-16 16:49:17 +01:00
emailMsg . SenderEmail = email . senderAddress
emailMsg . SenderName = email . senderName
2023-08-29 09:08:24 +02:00
emailMsg . ReplyToAddress = email . replyToAddress
2020-05-20 14:28:08 +02:00
// To && From
if err := email . smtpClient . Mail ( emailMsg . SenderEmail ) ; err != nil {
2023-12-08 16:30:55 +02:00
return zerrors . ThrowInternalf ( err , "EMAIL-s3is3" , "could not set sender: %v" , emailMsg . SenderEmail )
2020-05-20 14:28:08 +02:00
}
for _ , recp := range append ( append ( emailMsg . Recipients , emailMsg . CC ... ) , emailMsg . BCC ... ) {
if err := email . smtpClient . Rcpt ( recp ) ; err != nil {
2023-12-08 16:30:55 +02:00
return zerrors . ThrowInternalf ( err , "EMAIL-s4is4" , "could not set recipient: %v" , recp )
2020-05-20 14:28:08 +02:00
}
}
// Data
w , err := email . smtpClient . Data ( )
if err != nil {
return err
}
2023-03-29 00:09:06 +02:00
content , err := emailMsg . GetContent ( )
if err != nil {
return err
}
_ , err = w . Write ( [ ] byte ( content ) )
2020-05-20 14:28:08 +02: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 {
2023-12-08 16:30:55 +02:00
return nil , zerrors . ThrowInternal ( err , "EMAIL-spR56" , "could not split host and port for connect to smtp" )
2020-05-20 14:28:08 +02:00
}
if ! tlsRequired {
2024-04-11 09:16:10 +02:00
client , err = smtpConfig . getSMTPClient ( )
2020-05-20 14:28:08 +02:00
} else {
2024-04-11 09:16:10 +02:00
client , err = smtpConfig . getSMTPClientWithTls ( host )
2020-05-20 14:28:08 +02:00
}
if err != nil {
return nil , err
}
err = smtpConfig . smtpAuth ( client , host )
if err != nil {
return nil , err
}
return client , nil
}
2024-04-11 09:16:10 +02:00
func ( smtpConfig SMTP ) getSMTPClient ( ) ( * smtp . Client , error ) {
2020-05-20 14:28:08 +02:00
client , err := smtp . Dial ( smtpConfig . Host )
if err != nil {
2023-12-08 16:30:55 +02:00
return nil , zerrors . ThrowInternal ( err , "EMAIL-skwos" , "could not make smtp dial" )
2020-05-20 14:28:08 +02:00
}
return client , nil
}
2024-04-11 09:16:10 +02:00
func ( smtpConfig SMTP ) getSMTPClientWithTls ( host string ) ( * smtp . Client , error ) {
2020-05-20 14:28:08 +02:00
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..." )
2024-04-11 09:16:10 +02:00
return smtpConfig . getSMTPClientWithStartTls ( host )
2021-04-28 09:53:33 +02:00
}
2020-05-20 14:28:08 +02:00
if err != nil {
2023-12-08 16:30:55 +02:00
return nil , zerrors . 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 {
2023-12-08 16:30:55 +02:00
return nil , zerrors . ThrowInternal ( err , "EMAIL-skwi4" , "could not create smtp client" )
2020-05-20 14:28:08 +02:00
}
return client , err
}
2024-04-11 09:16:10 +02:00
func ( smtpConfig SMTP ) getSMTPClientWithStartTls ( host string ) ( * smtp . Client , error ) {
client , err := smtpConfig . getSMTPClient ( )
2021-04-28 09:53:33 +02:00
if err != nil {
return nil , err
}
if err := client . StartTLS ( & tls . Config {
ServerName : host ,
} ) ; err != nil {
2023-12-08 16:30:55 +02:00
return nil , zerrors . ThrowInternal ( err , "EMAIL-guvsQ" , "could not start tls" )
2021-04-28 09:53:33 +02:00
}
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
2024-04-30 09:31:07 +02:00
err := client . Auth ( PlainOrLoginAuth ( smtpConfig . User , smtpConfig . Password , host ) )
2023-03-29 00:09:06 +02:00
if err != nil {
2023-12-08 16:30:55 +02:00
return zerrors . ThrowInternalf ( err , "EMAIL-s9kfs" , "could not add smtp auth for user %s" , smtpConfig . User )
2023-03-29 00:09:06 +02:00
}
return nil
2020-05-20 14:28:08 +02:00
}