in smtp submission, if a fromid is present in the mailfrom command, use it when queueing

it's the responsibility of the sender to use unique fromid's.
we do check if that's the case, and return an error if not.

also make it more clear that "unique smtp mail from addresses" map to the
"FromIDLoginAddresses" account config field.

based on feedback from cuu508 for #31, thanks!
This commit is contained in:
Mechiel Lukkien
2024-04-28 13:18:25 +02:00
parent 32cf6500bd
commit 8cc795b2ec
12 changed files with 126 additions and 19 deletions

View File

@ -2097,8 +2097,20 @@ func (c *conn) submit(ctx context.Context, recvHdrFor func(string) string, msgWr
xcheckf(err, "parsing login address")
useFromID := slices.Contains(accConf.ParsedFromIDLoginAddresses, loginAddr)
var localpartBase string
var fromID string
var genFromID bool
if useFromID {
localpartBase = strings.SplitN(string(c.mailFrom.Localpart), confDom.LocalpartCatchallSeparator, 2)[0]
// With submission, user can bring their own fromid.
t := strings.SplitN(string(c.mailFrom.Localpart), confDom.LocalpartCatchallSeparator, 2)
localpartBase = t[0]
if len(t) == 2 {
fromID = t[1]
if fromID != "" && len(c.recipients) > 1 {
xsmtpServerErrorf(codes{smtp.C554TransactionFailed, smtp.SeProto5TooManyRcpts3}, "cannot send to multiple recipients with chosen fromid")
}
} else {
genFromID = true
}
}
now := time.Now()
qml := make([]queue.Msg, len(c.recipients))
@ -2116,9 +2128,10 @@ func (c *conn) submit(ctx context.Context, recvHdrFor func(string) string, msgWr
}
fp := *c.mailFrom
var fromID string
if useFromID {
fromID = xrandomID(16)
if genFromID {
fromID = xrandomID(16)
}
fp.Localpart = smtp.Localpart(localpartBase + confDom.LocalpartCatchallSeparator + fromID)
}
@ -2142,7 +2155,11 @@ func (c *conn) submit(ctx context.Context, recvHdrFor func(string) string, msgWr
}
// todo: it would be good to have a limit on messages (count and total size) a user has in the queue. also/especially with futurerelease. ../rfc/4865:387
if err := queue.Add(ctx, c.log, c.account.Name, dataFile, qml...); err != nil {
if err := queue.Add(ctx, c.log, c.account.Name, dataFile, qml...); err != nil && errors.Is(err, queue.ErrFromID) && !genFromID {
// todo: should we return this error during the "rcpt to" command?
// secode is not an exact match, but seems closest.
xsmtpServerErrorf(errCodes(smtp.C554TransactionFailed, smtp.SeAddr1SenderSyntax7, err), "bad fromid in smtp mail from address: %s", err)
} else if err != nil {
// Aborting the transaction is not great. But continuing and generating DSNs will
// probably result in errors as well...
metricSubmission.WithLabelValues("queueerror").Inc()

View File

@ -1898,3 +1898,48 @@ test email
ts.smtpErr(err, &smtpclient.Error{Permanent: true, Code: smtp.C554TransactionFailed, Secode: smtp.SeMsg6Other0})
})
}
// FromID can be specified during submission, but must be unique, with single recipient.
func TestUniqueFromID(t *testing.T) {
ts := newTestServer(t, filepath.FromSlash("../testdata/smtpfromid/mox.conf"), dns.MockResolver{})
defer ts.close()
ts.user = "mjl+fromid@mox.example"
ts.pass = password0
ts.submission = true
extraMsg := strings.ReplaceAll(`From: <mjl@mox.example>
To: <remote@example.org>
Subject: test
test email
`, "\n", "\r\n")
// Specify our own unique id when queueing.
ts.run(func(err error, client *smtpclient.Client) {
tcheck(t, err, "init client")
mailFrom := "mjl+unique@mox.example"
rcptTo := "mjl@mox.example"
err = client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(extraMsg)), strings.NewReader(extraMsg), true, true, false)
ts.smtpErr(err, nil)
})
// But we can only use it once.
ts.run(func(err error, client *smtpclient.Client) {
tcheck(t, err, "init client")
mailFrom := "mjl+unique@mox.example"
rcptTo := "mjl@mox.example"
err = client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(extraMsg)), strings.NewReader(extraMsg), true, true, false)
ts.smtpErr(err, &smtpclient.Error{Permanent: true, Code: smtp.C554TransactionFailed, Secode: smtp.SeAddr1SenderSyntax7})
})
// We cannot use our own fromid with multiple recipients.
ts.run(func(err error, client *smtpclient.Client) {
tcheck(t, err, "init client")
mailFrom := "mjl+unique2@mox.example"
rcptTo := []string{"mjl@mox.example", "mjl@mox.example"}
_, err = client.DeliverMultiple(ctxbg, mailFrom, rcptTo, int64(len(extraMsg)), strings.NewReader(extraMsg), true, true, false)
ts.smtpErr(err, &smtpclient.Error{Permanent: true, Code: smtp.C554TransactionFailed, Secode: smtp.SeProto5TooManyRcpts3})
})
}