diff --git a/store/account.go b/store/account.go index 3ce57f0..e665776 100644 --- a/store/account.go +++ b/store/account.go @@ -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 { e := part.Envelope sent := e.Date diff --git a/webapi/webapi.go b/webapi/webapi.go index 16e1b82..1e5bda6 100644 --- a/webapi/webapi.go +++ b/webapi/webapi.go @@ -64,7 +64,8 @@ type Message struct { To []NameAddress CC []NameAddress // 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 // Optional Reply-To header, where the recipient is asked to send replies to. diff --git a/webapisrv/server.go b/webapisrv/server.go index 6f74c63..a5afc1b 100644 --- a/webapisrv/server.go +++ b/webapisrv/server.go @@ -615,7 +615,7 @@ func (s server) Send(ctx context.Context, req webapi.SendRequest) (resp webapi.S from, fromPath := froms[0], fromPaths[0] to, toPaths := xparseAddresses(m.To) cc, ccPaths := xparseAddresses(m.CC) - _, bccPaths := xparseAddresses(m.BCC) + bcc, bccPaths := xparseAddresses(m.BCC) recipients := append(append(toPaths, ccPaths...), bccPaths...) 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("Cc", cc) + // We prepend Bcc headers to the message when adding to the Sent mailbox. if m.Subject != "" { xcheckcontrol(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) 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{ CreateSeq: modseq, ModSeq: modseq, diff --git a/webapisrv/server_test.go b/webapisrv/server_test.go index 5fb3886..1120caa 100644 --- a/webapisrv/server_test.go +++ b/webapisrv/server_test.go @@ -382,7 +382,6 @@ func TestServer(t *testing.T) { msgRes, err := client.MessageGet(ctxbg, webapi.MessageGetRequest{MsgID: 1}) tcheckf(t, err, "remove suppressed address") sentMsg := sendReq.Message - sentMsg.BCC = []webapi.NameAddress{} // todo: the Sent message should contain the BCC. for webmail too. sentMsg.Date = msgRes.Message.Date sentMsg.HTML += "\n" tcompare(t, msgRes.Message, sentMsg) diff --git a/webmail/api.go b/webmail/api.go index 01a944d..88eedc3 100644 --- a/webmail/api.go +++ b/webmail/api.go @@ -286,7 +286,8 @@ func xrandom(ctx context.Context, n int) []byte { // Bcc message header. // // 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) { // 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) } + var bccAddrs []message.NameAddress for _, s := range m.Bcc { addr, err := parseAddress(s) xcheckuserf(ctx, err, "parsing Bcc address") + bccAddrs = append(bccAddrs, addr) recipients = append(recipients, addr.Address) } @@ -446,6 +449,7 @@ func (w Webmail) MessageSubmit(ctx context.Context, m SubmitMessage) { } xc.HeaderAddrs("To", toAddrs) xc.HeaderAddrs("Cc", ccAddrs) + // We prepend Bcc headers to the message when adding to the Sent mailbox. if m.Subject != "" { xc.Subject(m.Subject) } @@ -750,6 +754,17 @@ func (w Webmail) MessageSubmit(ctx context.Context, m SubmitMessage) { 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{ CreateSeq: modseq, ModSeq: modseq, diff --git a/webmail/api.json b/webmail/api.json index d6958bc..5b9a71d 100644 --- a/webmail/api.json +++ b/webmail/api.json @@ -101,7 +101,7 @@ }, { "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": [ { "Name": "m", diff --git a/webmail/api.ts b/webmail/api.ts index 2b8c9a3..931d3c5 100644 --- a/webmail/api.ts +++ b/webmail/api.ts @@ -715,7 +715,8 @@ export class Client { // Bcc message header. // // 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 { const fn: string = "MessageSubmit" const paramTypes: string[][] = [["SubmitMessage"]] diff --git a/webmail/api_test.go b/webmail/api_test.go index 7919514..f1b551e 100644 --- a/webmail/api_test.go +++ b/webmail/api_test.go @@ -424,7 +424,7 @@ func TestAPI(t *testing.T) { tcompare(t, len(l), 0) tcompare(t, full, true) l, full = api.CompleteRecipient(ctx, "cc2") - tcompare(t, l, []string{"mjl cc2 "}) + tcompare(t, l, []string{"mjl cc2 ", "mjl bcc2 "}) tcompare(t, full, true) // RecipientSecurity diff --git a/webmail/msg.js b/webmail/msg.js index 94c14d1..57dda5d 100644 --- a/webmail/msg.js +++ b/webmail/msg.js @@ -457,7 +457,8 @@ var api; // Bcc message header. // // 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) { const fn = "MessageSubmit"; const paramTypes = [["SubmitMessage"]]; diff --git a/webmail/text.js b/webmail/text.js index ab98a22..30cf66e 100644 --- a/webmail/text.js +++ b/webmail/text.js @@ -457,7 +457,8 @@ var api; // Bcc message header. // // 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) { const fn = "MessageSubmit"; const paramTypes = [["SubmitMessage"]]; diff --git a/webmail/webmail.js b/webmail/webmail.js index f34889e..ed96552 100644 --- a/webmail/webmail.js +++ b/webmail/webmail.js @@ -457,7 +457,8 @@ var api; // Bcc message header. // // 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) { const fn = "MessageSubmit"; const paramTypes = [["SubmitMessage"]];