adjust auto-retry, fix banned response code, rewrite email composing to enmime, add more logs
This commit is contained in:
@@ -39,7 +39,7 @@ func (b *Bot) SetSendmail(sendmail func(string, string, string) error) {
|
|||||||
func (b *Bot) Sendmail(eventID id.EventID, from, to, data string) (bool, error) {
|
func (b *Bot) Sendmail(eventID id.EventID, from, to, data string) (bool, error) {
|
||||||
err := b.sendmail(from, to, data)
|
err := b.sendmail(from, to, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.HasPrefix(err.Error(), "45") {
|
if strings.HasPrefix(err.Error(), "4") {
|
||||||
b.log.Debug("email %s (from=%s to=%s) was added to the queue: %v", eventID, from, to, err)
|
b.log.Debug("email %s (from=%s to=%s) was added to the queue: %v", eventID, from, to, err)
|
||||||
return true, b.enqueueEmail(eventID.String(), from, to, data)
|
return true, b.enqueueEmail(eventID.String(), from, to, data)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package smtp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -14,6 +13,13 @@ import (
|
|||||||
"gitlab.com/etke.cc/postmoogle/utils"
|
"gitlab.com/etke.cc/postmoogle/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ErrBanned returned to banned hosts
|
||||||
|
var ErrBanned = &smtp.SMTPError{
|
||||||
|
Code: 554,
|
||||||
|
EnhancedCode: smtp.EnhancedCode{5, 5, 4},
|
||||||
|
Message: "please, don't bother me anymore, kupo.",
|
||||||
|
}
|
||||||
|
|
||||||
type mailServer struct {
|
type mailServer struct {
|
||||||
bot matrixbot
|
bot matrixbot
|
||||||
log *logger.Logger
|
log *logger.Logger
|
||||||
@@ -24,17 +30,19 @@ type mailServer struct {
|
|||||||
func (m *mailServer) Login(state *smtp.ConnectionState, username, password string) (smtp.Session, error) {
|
func (m *mailServer) Login(state *smtp.ConnectionState, username, password string) (smtp.Session, error) {
|
||||||
m.log.Debug("Login state=%+v username=%+v", state, username)
|
m.log.Debug("Login state=%+v username=%+v", state, username)
|
||||||
if m.bot.IsBanned(state.RemoteAddr) {
|
if m.bot.IsBanned(state.RemoteAddr) {
|
||||||
return nil, errors.New("please, don't bother me anymore")
|
return nil, ErrBanned
|
||||||
}
|
}
|
||||||
|
|
||||||
if !utils.AddressValid(username) {
|
if !utils.AddressValid(username) {
|
||||||
|
m.log.Debug("address %s is invalid", username)
|
||||||
m.bot.Ban(state.RemoteAddr)
|
m.bot.Ban(state.RemoteAddr)
|
||||||
return nil, errors.New("please, provide an email address")
|
return nil, ErrBanned
|
||||||
}
|
}
|
||||||
|
|
||||||
if !m.bot.AllowAuth(username, password) {
|
if !m.bot.AllowAuth(username, password) {
|
||||||
|
m.log.Debug("username=%s or password=<redacted> is invalid", username)
|
||||||
m.bot.Ban(state.RemoteAddr)
|
m.bot.Ban(state.RemoteAddr)
|
||||||
return nil, errors.New("email or password is invalid")
|
return nil, ErrBanned
|
||||||
}
|
}
|
||||||
|
|
||||||
return &outgoingSession{
|
return &outgoingSession{
|
||||||
@@ -51,7 +59,7 @@ func (m *mailServer) Login(state *smtp.ConnectionState, username, password strin
|
|||||||
func (m *mailServer) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session, error) {
|
func (m *mailServer) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session, error) {
|
||||||
m.log.Debug("AnonymousLogin state=%+v", state)
|
m.log.Debug("AnonymousLogin state=%+v", state)
|
||||||
if m.bot.IsBanned(state.RemoteAddr) {
|
if m.bot.IsBanned(state.RemoteAddr) {
|
||||||
return nil, errors.New("please, don't bother me anymore")
|
return nil, ErrBanned
|
||||||
}
|
}
|
||||||
|
|
||||||
return &incomingSession{
|
return &incomingSession{
|
||||||
|
|||||||
@@ -35,8 +35,9 @@ type incomingSession struct {
|
|||||||
func (s *incomingSession) Mail(from string, opts smtp.MailOptions) error {
|
func (s *incomingSession) Mail(from string, opts smtp.MailOptions) error {
|
||||||
sentry.GetHubFromContext(s.ctx).Scope().SetTag("from", from)
|
sentry.GetHubFromContext(s.ctx).Scope().SetTag("from", from)
|
||||||
if !utils.AddressValid(from) {
|
if !utils.AddressValid(from) {
|
||||||
|
s.log.Debug("address %s is invalid", from)
|
||||||
s.ban(s.addr)
|
s.ban(s.addr)
|
||||||
return errors.New("please, provide email address")
|
return ErrBanned
|
||||||
}
|
}
|
||||||
s.from = from
|
s.from = from
|
||||||
s.log.Debug("mail from %s, options: %+v", from, opts)
|
s.log.Debug("mail from %s, options: %+v", from, opts)
|
||||||
@@ -56,20 +57,20 @@ func (s *incomingSession) Rcpt(to string) error {
|
|||||||
if !domainok {
|
if !domainok {
|
||||||
s.log.Debug("wrong domain of %s", to)
|
s.log.Debug("wrong domain of %s", to)
|
||||||
s.ban(s.addr)
|
s.ban(s.addr)
|
||||||
return smtp.ErrAuthRequired
|
return ErrBanned
|
||||||
}
|
}
|
||||||
|
|
||||||
roomID, ok := s.getRoomID(utils.Mailbox(to))
|
roomID, ok := s.getRoomID(utils.Mailbox(to))
|
||||||
if !ok {
|
if !ok {
|
||||||
s.log.Debug("mapping for %s not found", to)
|
s.log.Debug("mapping for %s not found", to)
|
||||||
s.ban(s.addr)
|
s.ban(s.addr)
|
||||||
return smtp.ErrAuthRequired
|
return ErrBanned
|
||||||
}
|
}
|
||||||
|
|
||||||
validations := s.getFilters(roomID)
|
validations := s.getFilters(roomID)
|
||||||
if !validateEmail(s.from, s.to, s.log, validations) {
|
if !validateEmail(s.from, s.to, s.log, validations) {
|
||||||
s.ban(s.addr)
|
s.ban(s.addr)
|
||||||
return smtp.ErrAuthRequired
|
return ErrBanned
|
||||||
}
|
}
|
||||||
|
|
||||||
s.log.Debug("mail to %s", to)
|
s.log.Debug("mail to %s", to)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/emersion/go-msgauth/dkim"
|
"github.com/emersion/go-msgauth/dkim"
|
||||||
"gitlab.com/etke.cc/go/secgen"
|
"github.com/jhillyerd/enmime"
|
||||||
"maunium.net/go/mautrix/event"
|
"maunium.net/go/mautrix/event"
|
||||||
"maunium.net/go/mautrix/format"
|
"maunium.net/go/mautrix/format"
|
||||||
"maunium.net/go/mautrix/id"
|
"maunium.net/go/mautrix/id"
|
||||||
@@ -143,75 +143,33 @@ func (e *Email) Content(threadID id.EventID, options *ContentOptions) *event.Con
|
|||||||
|
|
||||||
// Compose converts the email object to a string (to be used for delivery via SMTP) and possibly DKIM-signs it
|
// Compose converts the email object to a string (to be used for delivery via SMTP) and possibly DKIM-signs it
|
||||||
func (e *Email) Compose(privkey string) string {
|
func (e *Email) Compose(privkey string) string {
|
||||||
|
mail := enmime.Builder().
|
||||||
|
From("", e.From).
|
||||||
|
To("", e.To).
|
||||||
|
Header("Message-Id", e.MessageID).
|
||||||
|
Subject(e.Subject).
|
||||||
|
Text([]byte(e.Text)).
|
||||||
|
HTML([]byte(e.HTML))
|
||||||
|
if e.InReplyTo != "" {
|
||||||
|
mail = mail.Header("In-Reply-To", e.InReplyTo)
|
||||||
|
}
|
||||||
|
if e.References != "" {
|
||||||
|
mail = mail.Header("References", e.References)
|
||||||
|
}
|
||||||
|
|
||||||
|
root, err := mail.Build()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("cannot compose email: %v", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
var data strings.Builder
|
var data strings.Builder
|
||||||
|
err = root.Encode(&data)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("cannot encode email: %v", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
domain := strings.SplitN(e.From, "@", 2)[1]
|
domain := strings.SplitN(e.From, "@", 2)[1]
|
||||||
boundaryName := "postmoogle" + secgen.Password(32)
|
|
||||||
boundary := "--" + boundaryName
|
|
||||||
|
|
||||||
data.WriteString("MIME-Version: 1.0")
|
|
||||||
data.WriteString("\r\n")
|
|
||||||
|
|
||||||
data.WriteString("Content-Type: multipart/alternative; boundary=")
|
|
||||||
data.WriteString(boundaryName)
|
|
||||||
data.WriteString("\r\n")
|
|
||||||
|
|
||||||
data.WriteString("Content-Transfer-Encoding: 8BIT")
|
|
||||||
data.WriteString("\r\n")
|
|
||||||
|
|
||||||
data.WriteString("From: ")
|
|
||||||
data.WriteString(e.From)
|
|
||||||
data.WriteString("\r\n")
|
|
||||||
|
|
||||||
data.WriteString("To: ")
|
|
||||||
data.WriteString(e.To)
|
|
||||||
data.WriteString("\r\n")
|
|
||||||
|
|
||||||
data.WriteString("Message-Id: ")
|
|
||||||
data.WriteString(e.MessageID)
|
|
||||||
data.WriteString("\r\n")
|
|
||||||
|
|
||||||
data.WriteString("Date: ")
|
|
||||||
data.WriteString(e.Date)
|
|
||||||
data.WriteString("\r\n")
|
|
||||||
|
|
||||||
if e.InReplyTo != "" {
|
|
||||||
data.WriteString("In-Reply-To: ")
|
|
||||||
data.WriteString(e.InReplyTo)
|
|
||||||
data.WriteString("\r\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.References != "" {
|
|
||||||
data.WriteString("References: ")
|
|
||||||
data.WriteString(e.References)
|
|
||||||
data.WriteString("\r\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
data.WriteString("Subject: ")
|
|
||||||
data.WriteString(e.Subject)
|
|
||||||
data.WriteString("\r\n")
|
|
||||||
data.WriteString("\r\n")
|
|
||||||
|
|
||||||
data.WriteString(boundary)
|
|
||||||
data.WriteString("\r\n")
|
|
||||||
data.WriteString("Content-Type: text/html; charset=\"UTF-8\"")
|
|
||||||
data.WriteString("\r\n")
|
|
||||||
data.WriteString(e.HTML)
|
|
||||||
data.WriteString("\r\n")
|
|
||||||
data.WriteString("\r\n")
|
|
||||||
|
|
||||||
data.WriteString(boundary)
|
|
||||||
data.WriteString("\r\n")
|
|
||||||
data.WriteString("Content-Type: text/plain; charset=\"UTF-8\"")
|
|
||||||
data.WriteString("\r\n")
|
|
||||||
data.WriteString(e.Text)
|
|
||||||
data.WriteString("\r\n")
|
|
||||||
data.WriteString("\r\n")
|
|
||||||
|
|
||||||
data.WriteString(boundary)
|
|
||||||
data.WriteString("--")
|
|
||||||
data.WriteString("\r\n")
|
|
||||||
|
|
||||||
return e.sign(domain, privkey, data)
|
return e.sign(domain, privkey, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user