Repository: certbot/certbot

Issue:
Unclear error about invalid email in non-interactive mode
If an invalid email address is specified in the non-interactive mode, it is not clear from the error message what's wrong:

```
# certbot --non-interactive --agree-tos --email=qwe@qwe.qwe register
You should register before running non-interactively, or provide --agree-tos and --email <email_address> flags
```

Increase the verbosity with `-vvv` makes sense:

```
2016-07-13 13:21:01,448:DEBUG:acme.client:Received response <Response [400]> (headers: {'Content-Length': '123', 'Boulder-Request-Id': 'jSUozaFbKZV7fCbfIJvfLG6-J9WxYEcl3osWRXnKS0E', 'Expires': 'Wed, 13 Jul 2016 07:21:01 GMT', 'Server': 'nginx', 'Connection': 'close', 'Pragma': 'no-cache', 'Cache-Control': 'max-age=0, no-cache, no-store', 'Date': 'Wed, 13 Jul 2016 07:21:01 GMT', 'Content-Type': 'application/problem+json', 'Replay-Nonce': 'wDx_-VvU_PzjfRWovewncePYqQ4frdUw3dC29B5LKbc'}): '{\n  "type": "urn:acme:error:invalidEmail",\n  "detail": "DNS problem: NXDOMAIN looking up MX for qwe.qwe",\n  "status": 400\n}'
2016-07-13 13:21:01,450:DEBUG:certbot.main:Exiting abnormally:
Traceback (most recent call last):
  File "/usr/local/psa/var/modules/letsencrypt/venv.X5yGo/bin/certbot", line 11, in <module>
    sys.exit(main())
  File "/usr/local/psa/var/modules/letsencrypt/venv.X5yGo/lib/python2.7/site-packages/certbot/main.py", line 744, in main
    return config.func(config, plugins)
  File "/usr/local/psa/var/modules/letsencrypt/venv.X5yGo/lib/python2.7/site-packages/certbot/main.py", line 388, in register
    _determine_account(config)
  File "/usr/local/psa/var/modules/letsencrypt/venv.X5yGo/lib/python2.7/site-packages/certbot/main.py", line 345, in _determine_account
    config, account_storage, tos_cb=_tos_cb)
  File "/usr/local/psa/var/modules/letsencrypt/venv.X5yGo/lib/python2.7/site-packages/certbot/client.py", line 120, in register
    regr = perform_registration(acme, config)
  File "/usr/local/psa/var/modules/letsencrypt/venv.X5yGo/lib/python2.7/site-packages/certbot/client.py", line 153, in perform_registration
    config.namespace.email = display_ops.get_email(invalid=True)
  File "/usr/local/psa/var/modules/letsencrypt/venv.X5yGo/lib/python2.7/site-packages/certbot/display/ops.py", line 52, in get_email
    raise errors.MissingCommandlineFlag(msg)
MissingCommandlineFlag: You should register before running non-interactively, or provide --agree-tos and --email <email_address> flags
```

In the interactive mode an email address is asked again:

```
There seem to be problems with that address. Enter email address (used for urgent notices and lost key recovery)
```

This is much better, but I would expect the detailed error `DNS problem: NXDOMAIN looking up MX for qwe.qwe` to be displayed.



Discussion:

Resolving patch:
diff --git a/certbot/client.py b/certbot/client.py
--- a/certbot/client.py
+++ b/certbot/client.py
@@ -150,8 +150,14 @@ def perform_registration(acme, config):
         return acme.register(messages.NewRegistration.from_data(email=config.email))
     except messages.Error as e:
         if e.typ == "urn:acme:error:invalidEmail":
-            config.namespace.email = display_ops.get_email(invalid=True)
-            return perform_registration(acme, config)
+            if config.noninteractive_mode:
+                msg = ("The ACME server believes %s is an invalid email address. "
+                       "Please ensure it is a valid email and attempt "
+                       "registration again." % config.email)
+                raise errors.Error(msg)
+            else:
+                config.namespace.email = display_ops.get_email(invalid=True)
+                return perform_registration(acme, config)
         else:
             raise
 
diff --git a/certbot/display/ops.py b/certbot/display/ops.py
--- a/certbot/display/ops.py
+++ b/certbot/display/ops.py
@@ -48,7 +48,7 @@ def get_email(invalid=False, optional=True):
                 invalid_prefix + msg if invalid else msg)
         except errors.MissingCommandlineFlag:
             msg = ("You should register before running non-interactively, "
-                   "or provide --agree-tos and --email <email_address> flags")
+                   "or provide --agree-tos and --email <email_address> flags.")
             raise errors.MissingCommandlineFlag(msg)
 
         if code != display_util.OK:


Test patch:
diff --git a/certbot/tests/client_test.py b/certbot/tests/client_test.py
--- a/certbot/tests/client_test.py
+++ b/certbot/tests/client_test.py
@@ -63,6 +63,7 @@ def test_it(self):
     @mock.patch("certbot.client.display_ops.get_email")
     def test_email_retry(self, _rep, mock_get_email):
         from acme import messages
+        self.config.noninteractive_mode = False
         msg = "DNS problem: NXDOMAIN looking up MX for example.com"
         mx_err = messages.Error(detail=msg, typ="urn:acme:error:invalidEmail")
         with mock.patch("certbot.client.acme_client.Client") as mock_client:
@@ -70,6 +71,15 @@ def test_email_retry(self, _rep, mock_get_email):
             self._call()
             self.assertEqual(mock_get_email.call_count, 1)
 
+    @mock.patch("certbot.account.report_new_account")
+    def test_email_invalid_noninteractive(self, _rep):
+        from acme import messages
+        msg = "DNS problem: NXDOMAIN looking up MX for example.com"
+        mx_err = messages.Error(detail=msg, typ="urn:acme:error:invalidEmail")
+        with mock.patch("certbot.client.acme_client.Client") as mock_client:
+            mock_client().register.side_effect = [mx_err, mock.MagicMock()]
+            self.assertRaises(errors.Error, self._call)
+
     def test_needs_email(self):
         self.config.email = None
         self.assertRaises(errors.Error, self._call)