Skip to content

Commit

Permalink
in webmail & webapisrv, store bcc header in sent messages
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mjl- committed Apr 16, 2024
1 parent abd098e commit c9451d4
Show file tree
Hide file tree
Showing 11 changed files with 42 additions and 11 deletions.
2 changes: 1 addition & 1 deletion store/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion webapi/webapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
14 changes: 13 additions & 1 deletion webapisrv/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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...)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
1 change: 0 additions & 1 deletion webapisrv/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
17 changes: 16 additions & 1 deletion webmail/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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\(

Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion webmail/api.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 2 additions & 1 deletion webmail/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
const fn: string = "MessageSubmit"
const paramTypes: string[][] = [["SubmitMessage"]]
Expand Down
2 changes: 1 addition & 1 deletion webmail/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 <[email protected]>"})
tcompare(t, l, []string{"mjl cc2 <[email protected]>", "mjl bcc2 <[email protected]>"})
tcompare(t, full, true)

// RecipientSecurity
Expand Down
3 changes: 2 additions & 1 deletion webmail/msg.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"]];
Expand Down
3 changes: 2 additions & 1 deletion webmail/text.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"]];
Expand Down
3 changes: 2 additions & 1 deletion webmail/webmail.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"]];
Expand Down

0 comments on commit c9451d4

Please sign in to comment.