2023-01-30 16:27:06 +03:00
package queue
import (
"bufio"
"fmt"
"os"
"time"
"github.com/mjl-/mox/dns"
"github.com/mjl-/mox/dsn"
"github.com/mjl-/mox/message"
"github.com/mjl-/mox/mlog"
"github.com/mjl-/mox/mox-"
"github.com/mjl-/mox/smtp"
"github.com/mjl-/mox/store"
)
func queueDSNFailure ( log * mlog . Log , m Msg , remoteMTA dsn . NameIP , secodeOpt , errmsg string ) {
const subject = "mail delivery failed"
message := fmt . Sprintf ( `
Delivery has failed permanently for your email to :
% s
No further deliveries will be attempted .
Error during the last delivery attempt :
% s
` , m . Recipient ( ) . XString ( m . SMTPUTF8 ) , errmsg )
queueDSN ( log , m , remoteMTA , secodeOpt , errmsg , true , nil , subject , message )
}
func queueDSNDelay ( log * mlog . Log , m Msg , remoteMTA dsn . NameIP , secodeOpt , errmsg string , retryUntil time . Time ) {
const subject = "mail delivery delayed"
message := fmt . Sprintf ( `
Delivery has been delayed of your email to :
% s
Next attempts to deliver : in 4 hours , 8 hours and 16 hours .
If these attempts all fail , you will receive a notice .
Error during the last delivery attempt :
% s
` , m . Recipient ( ) . XString ( false ) , errmsg )
queueDSN ( log , m , remoteMTA , secodeOpt , errmsg , false , & retryUntil , subject , message )
}
// We only queue DSNs for delivery failures for emails submitted by authenticated
// users. So we are delivering to local users. ../rfc/5321:1466
// ../rfc/5321:1494
// ../rfc/7208:490
// todo future: when we implement relaying, we should be able to send DSNs to non-local users. and possibly specify a null mailfrom. ../rfc/5321:1503
func queueDSN ( log * mlog . Log , m Msg , remoteMTA dsn . NameIP , secodeOpt , errmsg string , permanent bool , retryUntil * time . Time , subject , textBody string ) {
kind := "delayed delivery"
if permanent {
kind = "failure"
}
qlog := func ( text string , err error ) {
log . Errorx ( "queue dsn: " + text + ": sender will not be informed about dsn" , err , mlog . Field ( "sender" , m . Sender ( ) . XString ( m . SMTPUTF8 ) ) , mlog . Field ( "kind" , kind ) )
}
msgf , err := os . Open ( m . MessagePath ( ) )
if err != nil {
qlog ( "opening queued message" , err )
return
}
msgr := store . FileMsgReader ( m . MsgPrefix , msgf )
2023-02-16 15:22:00 +03:00
defer func ( ) {
err := msgr . Close ( )
log . Check ( err , "closing message reader after queuing dsn" )
} ( )
2023-01-30 16:27:06 +03:00
headers , err := message . ReadHeaders ( bufio . NewReader ( msgr ) )
if err != nil {
qlog ( "reading headers of queued message" , err )
return
}
var action dsn . Action
var status string
if permanent {
status = "5."
action = dsn . Failed
} else {
action = dsn . Delayed
status = "4."
}
if secodeOpt != "" {
status += secodeOpt
} else {
status += "0.0"
}
diagCode := errmsg
if ! dsn . HasCode ( diagCode ) {
diagCode = status + " " + errmsg
}
dsnMsg := & dsn . Message {
2023-07-23 18:56:39 +03:00
SMTPUTF8 : m . SMTPUTF8 ,
From : smtp . Path { Localpart : "postmaster" , IPDomain : dns . IPDomain { Domain : mox . Conf . Static . HostnameDomain } } ,
To : m . Sender ( ) ,
Subject : subject ,
References : m . MessageID ,
TextBody : textBody ,
2023-01-30 16:27:06 +03:00
ReportingMTA : mox . Conf . Static . HostnameDomain . ASCII ,
ArrivalDate : m . Queued ,
Recipients : [ ] dsn . Recipient {
{
FinalRecipient : m . Recipient ( ) ,
Action : action ,
Status : status ,
RemoteMTA : remoteMTA ,
DiagnosticCode : diagCode ,
LastAttemptDate : * m . LastAttempt ,
WillRetryUntil : retryUntil ,
} ,
} ,
Original : headers ,
}
msgData , err := dsnMsg . Compose ( log , m . SMTPUTF8 )
if err != nil {
qlog ( "composing dsn" , err )
return
}
msgData = append ( msgData , [ ] byte ( "Return-Path: <" + dsnMsg . From . XString ( m . SMTPUTF8 ) + ">\r\n" ) ... )
mailbox := "Inbox"
acc , err := store . OpenAccount ( m . SenderAccount )
if err != nil {
acc , err = store . OpenAccount ( mox . Conf . Static . Postmaster . Account )
if err != nil {
qlog ( "looking up postmaster account after sender account was not found" , err )
return
}
mailbox = mox . Conf . Static . Postmaster . Mailbox
}
defer func ( ) {
2023-02-16 15:22:00 +03:00
err := acc . Close ( )
log . Check ( err , "queue dsn: closing account" , mlog . Field ( "sender" , m . Sender ( ) . XString ( m . SMTPUTF8 ) ) , mlog . Field ( "kind" , kind ) )
2023-01-30 16:27:06 +03:00
} ( )
msgFile , err := store . CreateMessageTemp ( "queue-dsn" )
if err != nil {
qlog ( "creating temporary message file" , err )
return
}
defer func ( ) {
if msgFile != nil {
2023-02-16 15:22:00 +03:00
err := os . Remove ( msgFile . Name ( ) )
log . Check ( err , "removing message file" , mlog . Field ( "path" , msgFile . Name ( ) ) )
err = msgFile . Close ( )
log . Check ( err , "closing message file" )
2023-01-30 16:27:06 +03:00
}
} ( )
2023-08-11 15:07:49 +03:00
msgWriter := message . NewWriter ( msgFile )
2023-01-30 16:27:06 +03:00
if _ , err := msgWriter . Write ( msgData ) ; err != nil {
qlog ( "writing dsn message" , err )
return
}
msg := & store . Message {
Received : time . Now ( ) ,
Size : msgWriter . Size ,
MsgPrefix : [ ] byte { } ,
}
acc . WithWLock ( func ( ) {
if err := acc . DeliverMailbox ( log , mailbox , msg , msgFile , true ) ; err != nil {
qlog ( "delivering dsn to mailbox" , err )
return
}
} )
2023-02-16 15:22:00 +03:00
err = msgFile . Close ( )
log . Check ( err , "closing dsn file" )
2023-01-30 16:27:06 +03:00
msgFile = nil
}