From 3843cea95910840a3974ff7442a392b534167640 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Fri, 30 Oct 2015 23:44:00 -0600 Subject: [PATCH] letsencrypt: Allow (but warn about) empty emails --- caddy/letsencrypt/letsencrypt.go | 4 ---- caddy/letsencrypt/storage.go | 10 +++++++++- caddy/letsencrypt/storage_test.go | 15 +++++++++++++++ caddy/letsencrypt/user.go | 17 +++++++++++++---- 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/caddy/letsencrypt/letsencrypt.go b/caddy/letsencrypt/letsencrypt.go index d131fbb8..f7a5fd54 100644 --- a/caddy/letsencrypt/letsencrypt.go +++ b/caddy/letsencrypt/letsencrypt.go @@ -156,10 +156,6 @@ func groupConfigsByEmail(configs []server.Config) (map[string][]*server.Config, continue } 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]) } return initMap, nil diff --git a/caddy/letsencrypt/storage.go b/caddy/letsencrypt/storage.go index 6826e930..81c0aaea 100644 --- a/caddy/letsencrypt/storage.go +++ b/caddy/letsencrypt/storage.go @@ -48,12 +48,18 @@ func (s Storage) Users() string { // User gets the account folder for the user with email. func (s Storage) User(email string) string { + if email == "" { + email = emptyEmail + } return filepath.Join(s.Users(), email) } // UserRegFile gets the path to the registration file for // the user with the given email address. func (s Storage) UserRegFile(email string) string { + if email == "" { + email = emptyEmail + } fileName := emailUsername(email) if fileName == "" { fileName = "registration" @@ -64,7 +70,9 @@ func (s Storage) UserRegFile(email string) string { // UserKeyFile gets the path to the private key file for // the user with the given email address. func (s Storage) UserKeyFile(email string) string { - // TODO: Read the KeyFile property in the registration file instead? + if email == "" { + email = emptyEmail + } fileName := emailUsername(email) if fileName == "" { fileName = "private" diff --git a/caddy/letsencrypt/storage_test.go b/caddy/letsencrypt/storage_test.go index 67368669..5107c32a 100644 --- a/caddy/letsencrypt/storage_test.go +++ b/caddy/letsencrypt/storage_test.go @@ -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 { 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) { @@ -61,6 +72,10 @@ func TestEmailUsername(t *testing.T) { input: "@foobar.com", expect: "foobar.com", }, + { + input: emptyEmail, + expect: emptyEmail, + }, } { if actual := emailUsername(test.input); actual != test.expect { t.Errorf("Test %d: Expected username to be '%s' but was '%s'", i, test.expect, actual) diff --git a/caddy/letsencrypt/user.go b/caddy/letsencrypt/user.go index ff4d6acb..0e7a8b45 100644 --- a/caddy/letsencrypt/user.go +++ b/caddy/letsencrypt/user.go @@ -115,6 +115,8 @@ func newUser(email string) (User, error) { // getEmail does everything it can to obtain an email // address from the user to use for TLS for cfg. If it // 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 { // First try the tls directive from the Caddyfile leEmail := cfg.TLS.LetsEncryptEmail @@ -124,7 +126,6 @@ func getEmail(cfg server.Config) string { } if leEmail == "" { // 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()) if err == nil { var mostRecent os.FileInfo @@ -143,9 +144,13 @@ func getEmail(cfg server.Config) string { } if leEmail == "" { // 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) - 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(" ") + 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 leEmail, err = reader.ReadString('\n') if err != nil { @@ -169,7 +174,7 @@ func promptUserAgreement(agreementURL string, changed bool) bool { 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') if err != nil { return false @@ -182,3 +187,7 @@ func promptUserAgreement(agreementURL string, changed bool) bool { // stdin is used to read the user's input if prompted; // this is changed by tests during tests. 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"