in webmail & webapisrv, store bcc header in sent messages

when sending a message with bcc's, prepend the bcc header to the message we
store in the sent folder. still not in the message we send to the recipients.
This commit is contained in:
Mechiel Lukkien 2024-04-16 17:57:46 +02:00
parent abd098e8c0
commit c9451d4d06
No known key found for this signature in database
11 changed files with 42 additions and 11 deletions

View file

@ -1405,7 +1405,7 @@ func (a *Account) DeliverMessage(log mlog.Log, tx *bstore.Tx, m *Message, msgFil
} }
} }
// todo: perhaps we should match the recipients based on smtp submission and a matching message-id? we now miss the addresses in bcc's. for webmail, we could insert the recipients directly. // todo: perhaps we should match the recipients based on smtp submission and a matching message-id? we now miss the addresses in bcc's if the mail client doesn't save a message that includes the bcc header in the sent mailbox.
if mb.Sent && part != nil && part.Envelope != nil { if mb.Sent && part != nil && part.Envelope != nil {
e := part.Envelope e := part.Envelope
sent := e.Date sent := e.Date

View file

@ -64,7 +64,8 @@ type Message struct {
To []NameAddress To []NameAddress
CC []NameAddress CC []NameAddress
// For submissions, BCC addressees receive the message but are not added to the // For submissions, BCC addressees receive the message but are not added to the
// message headers. For incoming messages, this is typically empty. // headers of the outgoing message. Only the message saved the to the Sent mailbox
// gets the Bcc header prepended. For incoming messages, this is typically empty.
BCC []NameAddress BCC []NameAddress
// Optional Reply-To header, where the recipient is asked to send replies to. // Optional Reply-To header, where the recipient is asked to send replies to.

View file

@ -615,7 +615,7 @@ func (s server) Send(ctx context.Context, req webapi.SendRequest) (resp webapi.S
from, fromPath := froms[0], fromPaths[0] from, fromPath := froms[0], fromPaths[0]
to, toPaths := xparseAddresses(m.To) to, toPaths := xparseAddresses(m.To)
cc, ccPaths := xparseAddresses(m.CC) cc, ccPaths := xparseAddresses(m.CC)
_, bccPaths := xparseAddresses(m.BCC) bcc, bccPaths := xparseAddresses(m.BCC)
recipients := append(append(toPaths, ccPaths...), bccPaths...) recipients := append(append(toPaths, ccPaths...), bccPaths...)
addresses := append(append(m.To, m.CC...), m.BCC...) addresses := append(append(m.To, m.CC...), m.BCC...)
@ -714,6 +714,7 @@ func (s server) Send(ctx context.Context, req webapi.SendRequest) (resp webapi.S
} }
xc.HeaderAddrs("To", to) xc.HeaderAddrs("To", to)
xc.HeaderAddrs("Cc", cc) xc.HeaderAddrs("Cc", cc)
// We prepend Bcc headers to the message when adding to the Sent mailbox.
if m.Subject != "" { if m.Subject != "" {
xcheckcontrol(m.Subject) xcheckcontrol(m.Subject)
xc.Subject(m.Subject) xc.Subject(m.Subject)
@ -1037,6 +1038,17 @@ func (s server) Send(ctx context.Context, req webapi.SendRequest) (resp webapi.S
modseq, err := acc.NextModSeq(tx) modseq, err := acc.NextModSeq(tx)
xcheckf(err, "next modseq") xcheckf(err, "next modseq")
// If there were bcc headers, prepend those to the stored message only, before the
// DKIM signature. The DKIM-signature oversigns the bcc header, so this stored message
// won't validate with DKIM anymore, which is fine.
if len(bcc) > 0 {
var sb strings.Builder
xbcc := message.NewComposer(&sb, 100*1024, smtputf8)
xbcc.HeaderAddrs("Bcc", bcc)
xbcc.Flush()
msgPrefix = sb.String() + msgPrefix
}
sentm := store.Message{ sentm := store.Message{
CreateSeq: modseq, CreateSeq: modseq,
ModSeq: modseq, ModSeq: modseq,

View file

@ -382,7 +382,6 @@ func TestServer(t *testing.T) {
msgRes, err := client.MessageGet(ctxbg, webapi.MessageGetRequest{MsgID: 1}) msgRes, err := client.MessageGet(ctxbg, webapi.MessageGetRequest{MsgID: 1})
tcheckf(t, err, "remove suppressed address") tcheckf(t, err, "remove suppressed address")
sentMsg := sendReq.Message sentMsg := sendReq.Message
sentMsg.BCC = []webapi.NameAddress{} // todo: the Sent message should contain the BCC. for webmail too.
sentMsg.Date = msgRes.Message.Date sentMsg.Date = msgRes.Message.Date
sentMsg.HTML += "\n" sentMsg.HTML += "\n"
tcompare(t, msgRes.Message, sentMsg) tcompare(t, msgRes.Message, sentMsg)

View file

@ -286,7 +286,8 @@ func xrandom(ctx context.Context, n int) []byte {
// Bcc message header. // Bcc message header.
// //
// If a Sent mailbox is configured, messages are added to it after submitting // If a Sent mailbox is configured, messages are added to it after submitting
// to the delivery queue. // to the delivery queue. If Bcc addresses were present, a header is prepended
// to the message stored in the Sent mailbox.
func (w Webmail) MessageSubmit(ctx context.Context, m SubmitMessage) { func (w Webmail) MessageSubmit(ctx context.Context, m SubmitMessage) {
// Similar between ../smtpserver/server.go:/submit\( and ../webmail/api.go:/MessageSubmit\( and ../webapisrv/server.go:/Send\( // Similar between ../smtpserver/server.go:/submit\( and ../webmail/api.go:/MessageSubmit\( and ../webapisrv/server.go:/Send\(
@ -343,9 +344,11 @@ func (w Webmail) MessageSubmit(ctx context.Context, m SubmitMessage) {
recipients = append(recipients, addr.Address) recipients = append(recipients, addr.Address)
} }
var bccAddrs []message.NameAddress
for _, s := range m.Bcc { for _, s := range m.Bcc {
addr, err := parseAddress(s) addr, err := parseAddress(s)
xcheckuserf(ctx, err, "parsing Bcc address") xcheckuserf(ctx, err, "parsing Bcc address")
bccAddrs = append(bccAddrs, addr)
recipients = append(recipients, addr.Address) recipients = append(recipients, addr.Address)
} }
@ -446,6 +449,7 @@ func (w Webmail) MessageSubmit(ctx context.Context, m SubmitMessage) {
} }
xc.HeaderAddrs("To", toAddrs) xc.HeaderAddrs("To", toAddrs)
xc.HeaderAddrs("Cc", ccAddrs) xc.HeaderAddrs("Cc", ccAddrs)
// We prepend Bcc headers to the message when adding to the Sent mailbox.
if m.Subject != "" { if m.Subject != "" {
xc.Subject(m.Subject) xc.Subject(m.Subject)
} }
@ -750,6 +754,17 @@ func (w Webmail) MessageSubmit(ctx context.Context, m SubmitMessage) {
xcheckf(ctx, err, "next modseq") xcheckf(ctx, err, "next modseq")
} }
// If there were bcc headers, prepend those to the stored message only, before the
// DKIM signature. The DKIM-signature oversigns the bcc header, so this stored
// message won't validate with DKIM anymore, which is fine.
if len(bccAddrs) > 0 {
var sb strings.Builder
xbcc := message.NewComposer(&sb, 100*1024, smtputf8)
xbcc.HeaderAddrs("Bcc", bccAddrs)
xbcc.Flush()
msgPrefix = sb.String() + msgPrefix
}
sentm := store.Message{ sentm := store.Message{
CreateSeq: modseq, CreateSeq: modseq,
ModSeq: modseq, ModSeq: modseq,

View file

@ -101,7 +101,7 @@
}, },
{ {
"Name": "MessageSubmit", "Name": "MessageSubmit",
"Docs": "MessageSubmit sends a message by submitting it the outgoing email queue. The\nmessage is sent to all addresses listed in the To, Cc and Bcc addresses, without\nBcc message header.\n\nIf a Sent mailbox is configured, messages are added to it after submitting\nto the delivery queue.", "Docs": "MessageSubmit sends a message by submitting it the outgoing email queue. The\nmessage is sent to all addresses listed in the To, Cc and Bcc addresses, without\nBcc message header.\n\nIf a Sent mailbox is configured, messages are added to it after submitting\nto the delivery queue. If Bcc addresses were present, a header is prepended\nto the message stored in the Sent mailbox.",
"Params": [ "Params": [
{ {
"Name": "m", "Name": "m",

View file

@ -715,7 +715,8 @@ export class Client {
// Bcc message header. // Bcc message header.
// //
// If a Sent mailbox is configured, messages are added to it after submitting // If a Sent mailbox is configured, messages are added to it after submitting
// to the delivery queue. // to the delivery queue. If Bcc addresses were present, a header is prepended
// to the message stored in the Sent mailbox.
async MessageSubmit(m: SubmitMessage): Promise<void> { async MessageSubmit(m: SubmitMessage): Promise<void> {
const fn: string = "MessageSubmit" const fn: string = "MessageSubmit"
const paramTypes: string[][] = [["SubmitMessage"]] const paramTypes: string[][] = [["SubmitMessage"]]

View file

@ -424,7 +424,7 @@ func TestAPI(t *testing.T) {
tcompare(t, len(l), 0) tcompare(t, len(l), 0)
tcompare(t, full, true) tcompare(t, full, true)
l, full = api.CompleteRecipient(ctx, "cc2") l, full = api.CompleteRecipient(ctx, "cc2")
tcompare(t, l, []string{"mjl cc2 <mjl+cc2@mox.example>"}) tcompare(t, l, []string{"mjl cc2 <mjl+cc2@mox.example>", "mjl bcc2 <mjl+bcc2@mox.example>"})
tcompare(t, full, true) tcompare(t, full, true)
// RecipientSecurity // RecipientSecurity

View file

@ -457,7 +457,8 @@ var api;
// Bcc message header. // Bcc message header.
// //
// If a Sent mailbox is configured, messages are added to it after submitting // If a Sent mailbox is configured, messages are added to it after submitting
// to the delivery queue. // to the delivery queue. If Bcc addresses were present, a header is prepended
// to the message stored in the Sent mailbox.
async MessageSubmit(m) { async MessageSubmit(m) {
const fn = "MessageSubmit"; const fn = "MessageSubmit";
const paramTypes = [["SubmitMessage"]]; const paramTypes = [["SubmitMessage"]];

View file

@ -457,7 +457,8 @@ var api;
// Bcc message header. // Bcc message header.
// //
// If a Sent mailbox is configured, messages are added to it after submitting // If a Sent mailbox is configured, messages are added to it after submitting
// to the delivery queue. // to the delivery queue. If Bcc addresses were present, a header is prepended
// to the message stored in the Sent mailbox.
async MessageSubmit(m) { async MessageSubmit(m) {
const fn = "MessageSubmit"; const fn = "MessageSubmit";
const paramTypes = [["SubmitMessage"]]; const paramTypes = [["SubmitMessage"]];

View file

@ -457,7 +457,8 @@ var api;
// Bcc message header. // Bcc message header.
// //
// If a Sent mailbox is configured, messages are added to it after submitting // If a Sent mailbox is configured, messages are added to it after submitting
// to the delivery queue. // to the delivery queue. If Bcc addresses were present, a header is prepended
// to the message stored in the Sent mailbox.
async MessageSubmit(m) { async MessageSubmit(m) {
const fn = "MessageSubmit"; const fn = "MessageSubmit";
const paramTypes = [["SubmitMessage"]]; const paramTypes = [["SubmitMessage"]];