-
Notifications
You must be signed in to change notification settings - Fork 8
/
maildir.go
195 lines (178 loc) · 5.5 KB
/
maildir.go
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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
package maildir_processor
import (
"fmt"
"github.com/flashmob/go-guerrilla/backends"
"github.com/flashmob/go-guerrilla/mail"
"github.com/flashmob/go-guerrilla/response"
_ "github.com/sloonz/go-maildir"
"github.com/flashmob/go-maildir"
"os"
"os/user"
"strconv"
"strings"
"sync"
)
const MailDirFilePerms = 0600
type maildirConfig struct {
// maildir_path may contain a [user] placeholder. This will be substituted at run time
// eg /home/[user]/Maildir will get substituted to /home/test/Maildir for [email protected]
Path string `json:"maildir_path"`
// This is a string holding user to group/id mappings - in other words, the recipient table
// Each record separated by ","
// Records have the following format: <username>=<id>:<group>
// use -1 for <id> & <group> if you want to ignore these, otherwise get these numbers from /etc/passwd
// Example: "test=1002:2003,guerrilla=1001:1001"
UserMap string `json:"maildir_user_map"`
}
type MailDir struct {
userMap map[string][]int
dirs map[string]*maildir.Maildir
config *maildirConfig
}
// check to see if we have configured
func (m *MailDir) checkUsers(rcpt []mail.Address, mailDirs map[string]*maildir.Maildir) bool {
for i := range rcpt {
if _, ok := mailDirs[rcpt[i].User]; !ok {
return false
}
}
return true
}
var mdirMux sync.Mutex
// initDirs creates the mail dir folders if they haven't been created already
func (m *MailDir) initDirs() error {
if m.dirs == nil {
m.dirs = make(map[string]*maildir.Maildir, 0)
}
// initialize some maildirs
mdirMux.Lock()
defer mdirMux.Unlock()
for str, ids := range m.userMap {
path := strings.Replace(m.config.Path, "[user]", str, 1)
if mdir, err := maildir.NewWithPerm(path, true, MailDirFilePerms, ids[0], ids[1]); err == nil {
m.dirs[str] = mdir
} else {
backends.Log().WithError(err).Error("could not create Maildir. Please check the config")
return err
}
}
return nil
}
func (m *MailDir) validateRcpt(addr *mail.Address) backends.RcptError {
u := strings.ToLower(addr.User)
mdir, ok := m.dirs[u]
if !ok {
return backends.NoSuchUser
}
if _, err := os.Stat(mdir.Path); err != nil {
return backends.StorageNotAvailable
}
return nil
}
func newMailDir(config *maildirConfig) (*MailDir, error) {
m := &MailDir{}
m.config = config
m.userMap = usermap(m.config.UserMap)
if strings.Index(m.config.Path, "~/") == 0 {
// expand the ~/ to home dir
usr, err := user.Current()
if err != nil {
backends.Log().WithError(err).Error("could not expand ~/ to homedir")
return nil, err
}
m.config.Path = usr.HomeDir + m.config.Path[1:]
}
if err := m.initDirs(); err != nil {
return nil, err
}
return m, nil
}
// usermap parses the usermap config strings and returns the result in a map
// Example: "test=1002:2003,guerrilla=1001:1001"
// test and guerrilla are usernames
// number 1002 is the uid, 2003 is gid
func usermap(usermap string) (ret map[string][]int) {
ret = make(map[string][]int, 0)
users := strings.Split(usermap, ",")
for i := range users {
u := strings.Split(users[i], "=")
if len(u) != 2 {
return
}
ids := strings.Split(u[1], ":")
if len(ids) != 2 {
return
}
n := make([]int, 0)
ret[u[0]] = n
for k := range ids {
s, _ := strconv.Atoi(ids[k])
ret[u[0]] = append(ret[u[0]], s)
}
}
return
}
var Processor = func() backends.Decorator {
// The following initialization is run when the program first starts
// config will be populated by the initFunc
var (
m *MailDir
)
// initFunc is an initializer function which is called when our processor gets created.
// It gets called for every worker
initializer := backends.InitializeWith(func(backendConfig backends.BackendConfig) error {
configType := backends.BaseConfig(&maildirConfig{})
bcfg, err := backends.Svc.ExtractConfig(backendConfig, configType)
if err != nil {
return err
}
c := bcfg.(*maildirConfig)
m, err = newMailDir(c)
if err != nil {
return err
}
return nil
})
// register our initializer
backends.Svc.AddInitializer(initializer)
return func(c backends.Processor) backends.Processor {
// The function will be called on each email transaction.
// On success, it forwards to the next step in the processor call-stack,
// or returns with an error if failed
return backends.ProcessWith(func(e *mail.Envelope, task backends.SelectTask) (backends.Result, error) {
if task == backends.TaskValidateRcpt {
// Check the recipients for each RCPT command.
// This is called each time a recipient is added,
// validate only the _last_ recipient that was appended
if size := len(e.RcptTo); size > 0 {
if err := m.validateRcpt(&e.RcptTo[size-1]); err != nil {
backends.Log().WithError(backends.NoSuchUser).Info("recipient not configured: ", e.RcptTo[size-1].User)
return backends.NewResult(
response.Canned.FailRcptCmd),
backends.NoSuchUser
}
}
return c.Process(e, task)
} else if task == backends.TaskSaveMail {
for i := range e.RcptTo {
u := strings.ToLower(e.RcptTo[i].User)
mdir, ok := m.dirs[u]
if !ok {
// no such user
continue
}
if filename, err := mdir.CreateMail(e.NewReader()); err != nil {
backends.Log().WithError(err).Error("Could not save email")
return backends.NewResult(fmt.Sprintf("554 Error: could not save email for [%s]", u)), err
} else {
backends.Log().Debug("saved email as", filename)
}
}
// continue to the next Processor in the decorator chain
return c.Process(e, task)
} else {
return c.Process(e, task)
}
})
}
}