Commit 3843cea9 authored by Matthew Holt's avatar Matthew Holt

letsencrypt: Allow (but warn about) empty emails

parent e99b3af0
...@@ -156,10 +156,6 @@ func groupConfigsByEmail(configs []server.Config) (map[string][]*server.Config, ...@@ -156,10 +156,6 @@ func groupConfigsByEmail(configs []server.Config) (map[string][]*server.Config,
continue continue
} }
leEmail := getEmail(configs[i]) leEmail := getEmail(configs[i])
if leEmail == "" {
// TODO: This may not be an error; just a poor choice by the user
return nil, errors.New("must have email address to serve HTTPS without existing certificate and key")
}
initMap[leEmail] = append(initMap[leEmail], &configs[i]) initMap[leEmail] = append(initMap[leEmail], &configs[i])
} }
return initMap, nil return initMap, nil
......
...@@ -48,12 +48,18 @@ func (s Storage) Users() string { ...@@ -48,12 +48,18 @@ func (s Storage) Users() string {
// User gets the account folder for the user with email. // User gets the account folder for the user with email.
func (s Storage) User(email string) string { func (s Storage) User(email string) string {
if email == "" {
email = emptyEmail
}
return filepath.Join(s.Users(), email) return filepath.Join(s.Users(), email)
} }
// UserRegFile gets the path to the registration file for // UserRegFile gets the path to the registration file for
// the user with the given email address. // the user with the given email address.
func (s Storage) UserRegFile(email string) string { func (s Storage) UserRegFile(email string) string {
if email == "" {
email = emptyEmail
}
fileName := emailUsername(email) fileName := emailUsername(email)
if fileName == "" { if fileName == "" {
fileName = "registration" fileName = "registration"
...@@ -64,7 +70,9 @@ func (s Storage) UserRegFile(email string) string { ...@@ -64,7 +70,9 @@ func (s Storage) UserRegFile(email string) string {
// UserKeyFile gets the path to the private key file for // UserKeyFile gets the path to the private key file for
// the user with the given email address. // the user with the given email address.
func (s Storage) UserKeyFile(email string) string { func (s Storage) UserKeyFile(email string) string {
// TODO: Read the KeyFile property in the registration file instead? if email == "" {
email = emptyEmail
}
fileName := emailUsername(email) fileName := emailUsername(email)
if fileName == "" { if fileName == "" {
fileName = "private" fileName = "private"
......
...@@ -35,6 +35,17 @@ func TestStorage(t *testing.T) { ...@@ -35,6 +35,17 @@ func TestStorage(t *testing.T) {
if expected, actual := filepath.Join("letsencrypt", "users", "me@example.com", "me.key"), storage.UserKeyFile("me@example.com"); actual != expected { if expected, actual := filepath.Join("letsencrypt", "users", "me@example.com", "me.key"), storage.UserKeyFile("me@example.com"); actual != expected {
t.Errorf("Expected UserKeyFile() to return '%s' but got '%s'", expected, actual) t.Errorf("Expected UserKeyFile() to return '%s' but got '%s'", expected, actual)
} }
// Test with empty emails
if expected, actual := filepath.Join("letsencrypt", "users", emptyEmail), storage.User(emptyEmail); actual != expected {
t.Errorf("Expected User(\"\") to return '%s' but got '%s'", expected, actual)
}
if expected, actual := filepath.Join("letsencrypt", "users", emptyEmail, emptyEmail+".json"), storage.UserRegFile(""); actual != expected {
t.Errorf("Expected UserRegFile(\"\") to return '%s' but got '%s'", expected, actual)
}
if expected, actual := filepath.Join("letsencrypt", "users", emptyEmail, emptyEmail+".key"), storage.UserKeyFile(""); actual != expected {
t.Errorf("Expected UserKeyFile(\"\") to return '%s' but got '%s'", expected, actual)
}
} }
func TestEmailUsername(t *testing.T) { func TestEmailUsername(t *testing.T) {
...@@ -61,6 +72,10 @@ func TestEmailUsername(t *testing.T) { ...@@ -61,6 +72,10 @@ func TestEmailUsername(t *testing.T) {
input: "@foobar.com", input: "@foobar.com",
expect: "foobar.com", expect: "foobar.com",
}, },
{
input: emptyEmail,
expect: emptyEmail,
},
} { } {
if actual := emailUsername(test.input); actual != test.expect { if actual := emailUsername(test.input); actual != test.expect {
t.Errorf("Test %d: Expected username to be '%s' but was '%s'", i, test.expect, actual) t.Errorf("Test %d: Expected username to be '%s' but was '%s'", i, test.expect, actual)
......
...@@ -115,6 +115,8 @@ func newUser(email string) (User, error) { ...@@ -115,6 +115,8 @@ func newUser(email string) (User, error) {
// getEmail does everything it can to obtain an email // getEmail does everything it can to obtain an email
// address from the user to use for TLS for cfg. If it // address from the user to use for TLS for cfg. If it
// cannot get an email address, it returns empty string. // cannot get an email address, it returns empty string.
// (It will warn the user of the consequences of an
// empty email.)
func getEmail(cfg server.Config) string { func getEmail(cfg server.Config) string {
// First try the tls directive from the Caddyfile // First try the tls directive from the Caddyfile
leEmail := cfg.TLS.LetsEncryptEmail leEmail := cfg.TLS.LetsEncryptEmail
...@@ -124,7 +126,6 @@ func getEmail(cfg server.Config) string { ...@@ -124,7 +126,6 @@ func getEmail(cfg server.Config) string {
} }
if leEmail == "" { if leEmail == "" {
// Then try to get most recent user email ~/.caddy/users file // Then try to get most recent user email ~/.caddy/users file
// TODO: Probably better to open the user's json file and read the email out of there...
userDirs, err := ioutil.ReadDir(storage.Users()) userDirs, err := ioutil.ReadDir(storage.Users())
if err == nil { if err == nil {
var mostRecent os.FileInfo var mostRecent os.FileInfo
...@@ -143,9 +144,13 @@ func getEmail(cfg server.Config) string { ...@@ -143,9 +144,13 @@ func getEmail(cfg server.Config) string {
} }
if leEmail == "" { if leEmail == "" {
// Alas, we must bother the user and ask for an email address // Alas, we must bother the user and ask for an email address
// TODO/BUG: This doesn't work when Caddyfile is piped into caddy
reader := bufio.NewReader(stdin) reader := bufio.NewReader(stdin)
fmt.Print("Email address: ") // TODO: More explanation probably, and show ToS? fmt.Println("Your sites will be served over HTTPS automatically using Let's Encrypt.")
fmt.Println("By continuing, you agree to the Let's Encrypt Subscriber Agreement at:")
fmt.Println(" <TODO: link>")
fmt.Println("Please enter your email address so you can recover your account if needed.")
fmt.Println("You can leave it blank, but you lose the ability to recover your account.")
fmt.Print("Email address: ")
var err error var err error
leEmail, err = reader.ReadString('\n') leEmail, err = reader.ReadString('\n')
if err != nil { if err != nil {
...@@ -169,7 +174,7 @@ func promptUserAgreement(agreementURL string, changed bool) bool { ...@@ -169,7 +174,7 @@ func promptUserAgreement(agreementURL string, changed bool) bool {
fmt.Print("Do you agree to the terms? (y/n): ") fmt.Print("Do you agree to the terms? (y/n): ")
} }
reader := bufio.NewReader(stdin) // TODO/BUG: This doesn't work when Caddyfile is piped into caddy reader := bufio.NewReader(stdin)
answer, err := reader.ReadString('\n') answer, err := reader.ReadString('\n')
if err != nil { if err != nil {
return false return false
...@@ -182,3 +187,7 @@ func promptUserAgreement(agreementURL string, changed bool) bool { ...@@ -182,3 +187,7 @@ func promptUserAgreement(agreementURL string, changed bool) bool {
// stdin is used to read the user's input if prompted; // stdin is used to read the user's input if prompted;
// this is changed by tests during tests. // this is changed by tests during tests.
var stdin = io.ReadWriter(os.Stdin) var stdin = io.ReadWriter(os.Stdin)
// The name of the folder for accounts where the email
// address was not provided; default 'username' if you will.
const emptyEmail = "default"
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment