forked from caddyserver/caddy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
filestorage.go
239 lines (215 loc) · 7.06 KB
/
filestorage.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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
package caddytls
import (
"github.com/mholt/caddy"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"strings"
)
// storageBasePath is the root path in which all TLS/ACME assets are
// stored. Do not change this value during the lifetime of the program.
var storageBasePath = filepath.Join(caddy.AssetsPath(), "acme")
// FileStorageCreator creates a new Storage instance backed by the local
// disk. The resulting Storage instance is guaranteed to be non-nil if
// there is no error. This can be used by "middleware" implementations that
// may want to proxy the disk storage.
func FileStorageCreator(caURL *url.URL) (Storage, error) {
return FileStorage(filepath.Join(storageBasePath, caURL.Host)), nil
}
// FileStorage is a root directory and facilitates forming file paths derived
// from it. It is used to get file paths in a consistent, cross- platform way
// for persisting ACME assets on the file system.
type FileStorage string
// sites gets the directory that stores site certificate and keys.
func (s FileStorage) sites() string {
return filepath.Join(string(s), "sites")
}
// site returns the path to the folder containing assets for domain.
func (s FileStorage) site(domain string) string {
domain = strings.ToLower(domain)
return filepath.Join(s.sites(), domain)
}
// siteCertFile returns the path to the certificate file for domain.
func (s FileStorage) siteCertFile(domain string) string {
domain = strings.ToLower(domain)
return filepath.Join(s.site(domain), domain+".crt")
}
// siteKeyFile returns the path to domain's private key file.
func (s FileStorage) siteKeyFile(domain string) string {
domain = strings.ToLower(domain)
return filepath.Join(s.site(domain), domain+".key")
}
// siteMetaFile returns the path to the domain's asset metadata file.
func (s FileStorage) siteMetaFile(domain string) string {
domain = strings.ToLower(domain)
return filepath.Join(s.site(domain), domain+".json")
}
// users gets the directory that stores account folders.
func (s FileStorage) users() string {
return filepath.Join(string(s), "users")
}
// user gets the account folder for the user with email
func (s FileStorage) user(email string) string {
if email == "" {
email = emptyEmail
}
email = strings.ToLower(email)
return filepath.Join(s.users(), email)
}
// emailUsername returns the username portion of an email address (part before
// '@') or the original input if it can't find the "@" symbol.
func emailUsername(email string) string {
at := strings.Index(email, "@")
if at == -1 {
return email
} else if at == 0 {
return email[1:]
}
return email[:at]
}
// userRegFile gets the path to the registration file for the user with the
// given email address.
func (s FileStorage) userRegFile(email string) string {
if email == "" {
email = emptyEmail
}
email = strings.ToLower(email)
fileName := emailUsername(email)
if fileName == "" {
fileName = "registration"
}
return filepath.Join(s.user(email), fileName+".json")
}
// userKeyFile gets the path to the private key file for the user with the
// given email address.
func (s FileStorage) userKeyFile(email string) string {
if email == "" {
email = emptyEmail
}
email = strings.ToLower(email)
fileName := emailUsername(email)
if fileName == "" {
fileName = "private"
}
return filepath.Join(s.user(email), fileName+".key")
}
// readFile abstracts a simple ioutil.ReadFile, making sure to return an
// ErrStorageNotFound instance when the file is not found.
func (s FileStorage) readFile(file string) ([]byte, error) {
byts, err := ioutil.ReadFile(file)
if os.IsNotExist(err) {
return nil, ErrStorageNotFound
}
return byts, err
}
// SiteExists implements Storage.SiteExists by checking for the presence of
// cert and key files.
func (s FileStorage) SiteExists(domain string) bool {
_, err := os.Stat(s.siteCertFile(domain))
if err != nil {
return false
}
_, err = os.Stat(s.siteKeyFile(domain))
if err != nil {
return false
}
return true
}
// LoadSite implements Storage.LoadSite by loading it from disk. If it is not
// present, the ErrStorageNotFound error instance is returned.
func (s FileStorage) LoadSite(domain string) (*SiteData, error) {
var err error
siteData := new(SiteData)
siteData.Cert, err = s.readFile(s.siteCertFile(domain))
if err == nil {
siteData.Key, err = s.readFile(s.siteKeyFile(domain))
}
if err == nil {
siteData.Meta, err = s.readFile(s.siteMetaFile(domain))
}
return siteData, err
}
// StoreSite implements Storage.StoreSite by writing it to disk. The base
// directories needed for the file are automatically created as needed.
func (s FileStorage) StoreSite(domain string, data *SiteData) error {
err := os.MkdirAll(s.site(domain), 0700)
if err != nil {
return err
}
err = ioutil.WriteFile(s.siteCertFile(domain), data.Cert, 0600)
if err == nil {
err = ioutil.WriteFile(s.siteKeyFile(domain), data.Key, 0600)
}
if err == nil {
err = ioutil.WriteFile(s.siteMetaFile(domain), data.Meta, 0600)
}
return err
}
// DeleteSite implements Storage.DeleteSite by deleting just the cert from
// disk. If it is not present, the ErrStorageNotFound error instance is
// returned.
func (s FileStorage) DeleteSite(domain string) error {
err := os.Remove(s.siteCertFile(domain))
if os.IsNotExist(err) {
return ErrStorageNotFound
}
return err
}
// LockRegister implements Storage.LockRegister by just returning true because
// it is not a multi-server storage implementation.
func (s FileStorage) LockRegister(domain string) (bool, error) {
return true, nil
}
// UnlockRegister implements Storage.UnlockRegister as a no-op because it is
// not a multi-server storage implementation.
func (s FileStorage) UnlockRegister(domain string) error {
return nil
}
// LoadUser implements Storage.LoadUser by loading it from disk. If it is not
// present, the ErrStorageNotFound error instance is returned.
func (s FileStorage) LoadUser(email string) (*UserData, error) {
var err error
userData := new(UserData)
userData.Reg, err = s.readFile(s.userRegFile(email))
if err == nil {
userData.Key, err = s.readFile(s.userKeyFile(email))
}
return userData, err
}
// StoreUser implements Storage.StoreUser by writing it to disk. The base
// directories needed for the file are automatically created as needed.
func (s FileStorage) StoreUser(email string, data *UserData) error {
err := os.MkdirAll(s.user(email), 0700)
if err != nil {
return err
}
err = ioutil.WriteFile(s.userRegFile(email), data.Reg, 0600)
if err == nil {
err = ioutil.WriteFile(s.userKeyFile(email), data.Key, 0600)
}
return err
}
// MostRecentUserEmail implements Storage.MostRecentUserEmail by finding the
// most recently written sub directory in the users' directory. It is named
// after the email address. This corresponds to the most recent call to
// StoreUser.
func (s FileStorage) MostRecentUserEmail() string {
userDirs, err := ioutil.ReadDir(s.users())
if err != nil {
return ""
}
var mostRecent os.FileInfo
for _, dir := range userDirs {
if !dir.IsDir() {
continue
}
if mostRecent == nil || dir.ModTime().After(mostRecent.ModTime()) {
mostRecent = dir
}
}
if mostRecent != nil {
return mostRecent.Name()
}
return ""
}