1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
|
package main
import "html/template"
// ============================================================================
// Account identity
// ============================================================================
//
// A mailweb process serves exactly one account. Running a second account means
// running a second process, with its own database and its own port; there is no
// mode in which one instance holds two.
//
// That is not a limitation left for later, it is what the schema says. Messages
// are keyed (mailbox, uid, uidvalidity), which is the only thing that uniquely
// identifies a message on an IMAP server — and it identifies it only on *that*
// server. Two accounts share the key space: both have an INBOX, both hand out
// UID 1 in it. Merging them into one database means either adding an account
// column to that key and a predicate to every query that touches it, or
// silently losing whichever message arrived second. Separate databases make the
// isolation structural instead: a query cannot reach another account's mail
// because it is not in the file.
//
// What remains is telling the instances apart. Two mailwebs look identical —
// same layout, same routes, same words — so the account each one serves is
// named in every rendering it produces. Without that, the only difference
// between two windows is the port in the address bar, and the text renderings
// do not even have that.
// myAddress is the account owner's primary address: the one excluded from the
// contacts listing, the one sender for whom no spam report is offered, and the
// one a reply comes from.
//
// It defaults to --imap-user and is overridden with --my-address, which matters
// when the login name is not the address mail arrives at — a distinction every
// server makes and some accounts use.
//
// It is a var rather than a constant because it was a constant, and the
// constant is what stopped mailweb from serving any account but one.
var myAddress string
// myAddresses is every address that is this account, including myAddress.
//
// One mailbox commonly receives mail at several addresses — a forwarding alias,
// a shared or role account — so this is a set rather than a string. A single
// address was enough while the question was only which contact to hide from a
// listing, and stops being enough the moment a reply has to decide who else was
// on a thread.
//
// Getting it wrong is not cosmetic, and it is not cosmetic in either direction.
//
// Too few: reply-all takes the recipients of the message being answered and
// removes the account itself; an alias mailweb does not recognise as its own
// survives that filter, so the reply is addressed back to the mailbox it was
// sent from. That is a copy of the account owner's own letter arriving in their
// inbox, and — worse — an address in the To: header that everyone else on the
// thread sees and will reply to in turn.
//
// Too many is the quieter failure, and the one actually found here: an address
// listed that is *not* this account is hidden from the contacts listing and
// silently subtracted from every reply-all. A real correspondent stops
// appearing, and mail meant for them stops being addressed to them, with
// nothing anywhere reporting either. This author's own unit named two addresses
// of another domain that the mailbox merely receives mail *about* — they appear
// as recipients on hundreds of messages and as the sender of none, which is
// what an alias never looks like.
//
// The test to apply is whether the mailbox sends *as* the address, not whether
// mail addressed to it arrives here.
//
// Repeat --my-address to add one. The first given (or --imap-user) stays
// primary; the rest are recognised but never used as a sender.
var myAddresses = map[string]bool{}
// isMyAddress reports whether an address is this account.
//
// Every site that asks "is this me" must ask here, so that adding an alias to
// the command line takes effect everywhere at once rather than in whichever
// places remembered to check the set instead of the single address.
func isMyAddress(address string) bool {
address = contactAddress(address)
if address == "" {
return false
}
return address == myAddress || myAddresses[address]
}
// isSelfMail reports whether a message went from this account to nobody but
// this account: a note to self.
//
// Such a message is the one case where the account's own address is worth
// treating as a correspondent rather than as noise. Everywhere else the address
// is subtracted — from the contacts listing, from the recipients of a reply —
// because it says nothing: every message in the mirror involves this account,
// so listing it as a contact would name the one address that is on all of them.
// A message it sent to itself is the exception, since there the account is the
// whole of the conversation and subtracting it leaves nothing at all.
//
// The test is deliberately strict: every recipient must be this account, in To,
// Cc and Bcc alike. A message from the account to itself *and* to somebody else
// is an ordinary thread that happens to be copied here, and a reply to it must
// still not carry the account's own address — see deriveRecipientSets, and
// "Choosing who a draft goes to" in mailweb(7). Only "nobody but me" makes the
// exception safe; "me somewhere among the recipients" would re-introduce the
// bug --my-address exists to prevent.
//
// A message with no recipients at all is not self-mail. It names nobody, which
// is not the same as naming this account, and it comes up in practice: mail
// addressed entirely by Bcc arrives with an empty To.
func isSelfMail(fromAddr string, to, cc, bcc []addr) bool {
if _, from := parseFromAddr(fromAddr); !isMyAddress(from) {
return false
}
n := 0
for _, list := range [][]addr{to, cc, bcc} {
for _, a := range list {
if !isMyAddress(a.Address) {
return false
}
n++
}
}
return n > 0
}
// accountName is the short label identifying this instance in its own output:
// window titles, and the header of every text rendering.
//
// It is separate from myAddress because the two answer different questions. An
// address is matched against mail and must be exact; a label is read by a human
// glancing at a browser tab, and "qualle" is more use there than the address it
// stands for. Defaults to --imap-user, so an instance that sets neither still
// says something true.
var accountName string
// sentMailbox is the name of the mailbox holding sent mail, which is how the
// contact view decides that a message went out rather than came in.
//
// Servers do not agree on the name: mailbox.org says "Sent", strato says "Sent
// Items", others "Sent Messages". Getting it wrong is not cosmetic. The
// direction is what the contact view prints as `->` or `<-`, and what the text
// rendering states in words, so a mailbox that is not recognised makes every
// message the account sent read as one the correspondent sent to it — mailweb
// asserting, in its own voice, that somebody wrote something they did not.
//
// The special-use attribute would answer this without being told, and is what
// appending uses (see findSentMailbox), but that costs a LIST round-trip per
// request and the answer is needed while rendering rows. A flag resolved once
// at startup is the same answer without the traffic.
var sentMailbox string
// accountTmplFuncs makes the label available to HTML templates as {{account}}.
//
// It is a function rather than a field on each template's data because the
// label belongs to the process, not to any one page: threading it through would
// mean touching every view struct, including the two in forge.go that have no
// struct at all — one renders a bare slice, the other an anonymous type. The
// closure reads the var when the template executes, which is always after main
// has set it and before any request has been served.
var accountTmplFuncs = template.FuncMap{
"account": func() string { return accountName },
}
|