82 lines
2.2 KiB
Go
82 lines
2.2 KiB
Go
package accounts
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"crypto/tls"
|
||
|
|
"encoding/json"
|
||
|
|
"fmt"
|
||
|
|
"net"
|
||
|
|
"net/smtp"
|
||
|
|
"strconv"
|
||
|
|
|
||
|
|
"gomail/internal/crypto"
|
||
|
|
"gomail/internal/db"
|
||
|
|
)
|
||
|
|
|
||
|
|
// sendViaSMTP delivers an outgoing message through a linked account's own
|
||
|
|
// SMTP settings — stdlib net/smtp, same choice as internal/queue's
|
||
|
|
// MXDeliverer, so outbound for both "GoMail relays for me" and "I'm using my
|
||
|
|
// own IMAP+SMTP provider" stay on the same dependency-free foundation.
|
||
|
|
func sendViaSMTP(account *db.LinkedAccount, mk *crypto.MasterKey, msg *OutgoingMessage) error {
|
||
|
|
plain, err := crypto.Decrypt(mk, account.ID, "linked-account-cred", account.CredentialEnc)
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("decrypting stored credential: %w", err)
|
||
|
|
}
|
||
|
|
var cred IMAPCredential // same password shape reused for the paired SMTP auth
|
||
|
|
if err := json.Unmarshal(plain, &cred); err != nil {
|
||
|
|
return fmt.Errorf("parsing stored credential: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
addr := net.JoinHostPort(account.SMTPHost, strconv.Itoa(account.SMTPPort))
|
||
|
|
conn, err := net.DialTimeout("tcp", addr, dialTimeout)
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("dial %s: %w", addr, err)
|
||
|
|
}
|
||
|
|
defer conn.Close()
|
||
|
|
|
||
|
|
if account.SMTPTLS == "implicit" {
|
||
|
|
// See provider_imap.go's connect() comment — same gap, same reason.
|
||
|
|
conn = tls.Client(conn, &tls.Config{ServerName: account.SMTPHost, InsecureSkipVerify: true})
|
||
|
|
}
|
||
|
|
|
||
|
|
client, err := smtp.NewClient(conn, account.SMTPHost)
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("SMTP handshake: %w", err)
|
||
|
|
}
|
||
|
|
defer client.Close()
|
||
|
|
|
||
|
|
if account.SMTPTLS == "starttls" {
|
||
|
|
if ok, _ := client.Extension("STARTTLS"); ok {
|
||
|
|
if err := client.StartTLS(&tls.Config{ServerName: account.SMTPHost}); err != nil {
|
||
|
|
return fmt.Errorf("STARTTLS: %w", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
auth := smtp.PlainAuth("", account.EmailAddress, cred.Password, account.SMTPHost)
|
||
|
|
if err := client.Auth(auth); err != nil {
|
||
|
|
return fmt.Errorf("SMTP auth: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := client.Mail(account.EmailAddress); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
for _, to := range msg.To {
|
||
|
|
if err := client.Rcpt(to); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
w, err := client.Data()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
raw := buildRFC5322(account.EmailAddress, msg)
|
||
|
|
if _, err := w.Write(raw); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if err := w.Close(); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return client.Quit()
|
||
|
|
}
|