From 512e02c837347f7ef821925ee61452645c97adaf Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 24 Jun 2015 11:46:39 -0700 Subject: [PATCH] Added reporter messages for failed challenges. --- letsencrypt/auth_handler.py | 87 +++++++++++++++++++++++++++++++++++++ letsencrypt/reporter.py | 23 +++++++--- 2 files changed, 103 insertions(+), 7 deletions(-) diff --git a/letsencrypt/auth_handler.py b/letsencrypt/auth_handler.py index d7d590878..502b0b76d 100644 --- a/letsencrypt/auth_handler.py +++ b/letsencrypt/auth_handler.py @@ -3,12 +3,15 @@ import itertools import logging import time +import zope.component + from acme import challenges from acme import messages from letsencrypt import achallenges from letsencrypt import constants from letsencrypt import errors +from letsencrypt import interfaces class AuthHandler(object): @@ -193,6 +196,7 @@ class AuthHandler(object): updated for _, updated in failed_achalls) if all_failed_achalls: + _report_failed_challs(all_failed_achalls) raise errors.FailedChallenges(all_failed_achalls) dom_to_check -= comp_domains @@ -480,3 +484,86 @@ def is_preferred(offered_challb, satisfied, different=True): return False return True + + +_ERROR_HELP_COMMON = ( + 'To fix these errors, please make sure that your domain name was entered ' + 'correctly and the DNS A/AAAA record(s) for that domain contains the ' + 'right IP address.' +) + + +_ERROR_HELP = { + 'connection' : + _ERROR_HELP_COMMON + ' Additionally, please check that your computer ' + 'has publicly routable IP address and no firewalls are preventing the ' + 'server from communicating with the client.', + 'dnssec' : + _ERROR_HELP_COMMON + ' Additionally, if you have DNSSEC enabled for ' + 'your domain, please ensure the signature is valid.', + 'malformed' : + 'To fix these errors, please make sure that you did not provide any ' + 'invalid information to the client and try running Let\'s Encrypt ' + 'again.', + 'serverInternal' : + 'Unfortunately, an error on the ACME server prevented you from completing ' + 'authorization. Please try again later.', + 'tls' : + _ERROR_HELP_COMMON + ' Additionally, please check that you have an up ' + 'to date TLS configuration that allows the server to communicate with ' + 'the Let\'s Encrypt client.', + 'unauthorized' : _ERROR_HELP_COMMON, + 'unknownHost' : + 'To fix these errors, please make sure that your domain name was ' + 'entered correctly.', +} + + +def _report_failed_challs(failed_achalls): + """Notifies the user about failed challenges. + + :param set failed_achalls: A set of failed + :class:`letsencrypt.achallenges.AnnotatedChallenge`. + + """ + problems = dict() + for achall in failed_achalls: + if achall.error: + problems.setdefault(achall.error.typ, []).append(achall) + + reporter = zope.component.getUtility(interfaces.IReporter) + for achalls in problems.itervalues(): + reporter.add_message(_generate_failed_chall_msg(achalls), 1, True) + + +def _generate_failed_chall_msg(failed_achalls): + """Creates a user friendly error message about failed challenges. + + :param list failed_achalls: A list of failed + :class:`letsencrypt.achallenges.AnnotatedChallenge` with the same error + type. + + :returns: A formatted error message for the client. + :rtype: str + + """ + typ = failed_achalls[0].error.typ + msg = [ + 'The following \'{0}\' errors were reported by the server:'.format(typ) + ] + + problems = dict() + for achall in failed_achalls: + problems.setdefault(achall.error.description, []).append(achall.domain) + for problem in problems: + domains = problems[problem] + domains.sort() + msg.append('\n\nDomains: ') + msg.append(', '.join(domains)) + msg.append('\nError: {0}'.format(problem)) + + if typ in _ERROR_HELP: + msg.append('\n\n') + msg.append(_ERROR_HELP[typ]) + + return "".join(msg) diff --git a/letsencrypt/reporter.py b/letsencrypt/reporter.py index 045c1befa..3045a7e19 100644 --- a/letsencrypt/reporter.py +++ b/letsencrypt/reporter.py @@ -46,9 +46,10 @@ class Reporter(object): printed if the program exits abnormally. """ - assert self.HIGH_PRIORITY <= priority <= self.LOW_PRIORITY - self.messages.put(self._msg_type(priority, msg, on_crash)) - logging.info("Reporting to user: %s", msg) + if msg: + assert self.HIGH_PRIORITY <= priority <= self.LOW_PRIORITY + self.messages.put(self._msg_type(priority, msg, on_crash)) + logging.info("Reporting to user: %s", msg) def atexit_print_messages(self, pid=os.getpid()): """Function to be registered with atexit to print messages. @@ -66,7 +67,8 @@ class Reporter(object): If there is an unhandled exception, only messages for which ``on_crash`` is ``True`` are printed. -""" + + """ bold_on = False if not self.messages.empty(): no_exception = sys.exc_info()[0] is None @@ -74,14 +76,21 @@ class Reporter(object): if bold_on: print self._BOLD print 'IMPORTANT NOTES:' - wrapper = textwrap.TextWrapper(initial_indent=' - ', - subsequent_indent=(' ' * 3)) + first_wrapper = textwrap.TextWrapper( + initial_indent=' - ', subsequent_indent=(' ' * 3)) + next_wrapper = textwrap.TextWrapper( + initial_indent=first_wrapper.subsequent_indent, + subsequent_indent=first_wrapper.subsequent_indent) while not self.messages.empty(): msg = self.messages.get() if no_exception or msg.on_crash: if bold_on and msg.priority > self.HIGH_PRIORITY: sys.stdout.write(self._RESET) bold_on = False - print wrapper.fill(msg.text) + lines = msg.text.splitlines() + print first_wrapper.fill(lines[0]) + if len(lines) > 1: + print "\n".join( + next_wrapper.fill(line) for line in lines[1:]) if bold_on: sys.stdout.write(self._RESET)