mirror of
https://github.com/certbot/certbot.git
synced 2026-08-01 08:08:00 +02:00
Merge pull request #126 from kuba/logging
Refactor logging, "pythonic" DialogHandler
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
"""Apache Configuration based off of Augeas Configurator."""
|
"""Apache Configuration based off of Augeas Configurator."""
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
import re
|
import re
|
||||||
@@ -15,7 +16,6 @@ from letsencrypt.client import CONFIG
|
|||||||
from letsencrypt.client import crypto_util
|
from letsencrypt.client import crypto_util
|
||||||
from letsencrypt.client import errors
|
from letsencrypt.client import errors
|
||||||
from letsencrypt.client import le_util
|
from letsencrypt.client import le_util
|
||||||
from letsencrypt.client import logger
|
|
||||||
|
|
||||||
|
|
||||||
# Configurator should be turned into a Singleton
|
# Configurator should be turned into a Singleton
|
||||||
@@ -229,13 +229,13 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
|
|
||||||
if len(path["cert_file"]) == 0 or len(path["cert_key"]) == 0:
|
if len(path["cert_file"]) == 0 or len(path["cert_key"]) == 0:
|
||||||
# Throw some "can't find all of the directives error"
|
# Throw some "can't find all of the directives error"
|
||||||
logger.warn(("Cannot find a cert or key directive in %s"
|
logging.warn(
|
||||||
% vhost.path))
|
"Cannot find a cert or key directive in %s", vhost.path)
|
||||||
logger.warn("VirtualHost was not modified")
|
logging.warn("VirtualHost was not modified")
|
||||||
# Presumably break here so that the virtualhost is not modified
|
# Presumably break here so that the virtualhost is not modified
|
||||||
return False
|
return False
|
||||||
|
|
||||||
logger.info("Deploying Certificate to VirtualHost %s" % vhost.filep)
|
logging.info("Deploying Certificate to VirtualHost %s", vhost.filep)
|
||||||
|
|
||||||
self.aug.set(path["cert_file"][0], cert)
|
self.aug.set(path["cert_file"][0], cert)
|
||||||
self.aug.set(path["cert_key"][0], key)
|
self.aug.set(path["cert_key"][0], key)
|
||||||
@@ -524,15 +524,15 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
if not check_ssl_loaded():
|
if not check_ssl_loaded():
|
||||||
logger.info("Loading mod_ssl into Apache Server")
|
logging.info("Loading mod_ssl into Apache Server")
|
||||||
enable_mod("ssl")
|
enable_mod("ssl")
|
||||||
|
|
||||||
# Check for Listen 443
|
# Check for Listen 443
|
||||||
# Note: This could be made to also look for ip:443 combo
|
# Note: This could be made to also look for ip:443 combo
|
||||||
# TODO: Need to search only open directives and IfMod mod_ssl.c
|
# TODO: Need to search only open directives and IfMod mod_ssl.c
|
||||||
if len(self.find_directive(case_i("Listen"), "443")) == 0:
|
if len(self.find_directive(case_i("Listen"), "443")) == 0:
|
||||||
logger.debug("No Listen 443 directive found")
|
logging.debug("No Listen 443 directive found")
|
||||||
logger.debug("Setting the Apache Server to Listen on port 443")
|
logging.debug("Setting the Apache Server to Listen on port 443")
|
||||||
path = self._add_dir_to_ifmodssl(
|
path = self._add_dir_to_ifmodssl(
|
||||||
get_aug_path(self.location["listen"]), "Listen", "443")
|
get_aug_path(self.location["listen"]), "Listen", "443")
|
||||||
self.save_notes += "Added Listen 443 directive to %s\n" % path
|
self.save_notes += "Added Listen 443 directive to %s\n" % path
|
||||||
@@ -554,15 +554,15 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
tup = addr.partition(":")
|
tup = addr.partition(":")
|
||||||
if tup[0] == "_default_":
|
if tup[0] == "_default_":
|
||||||
if not self.is_name_vhost(default_addr):
|
if not self.is_name_vhost(default_addr):
|
||||||
logger.debug(("Setting all VirtualHosts on "
|
logging.debug("Setting all VirtualHosts on %s to be "
|
||||||
"%s to be name based vhosts" % default_addr))
|
"name based vhosts", default_addr)
|
||||||
self.add_name_vhost(default_addr)
|
self.add_name_vhost(default_addr)
|
||||||
|
|
||||||
# No default addresses... so set each one individually
|
# No default addresses... so set each one individually
|
||||||
for addr in vhost.addrs:
|
for addr in vhost.addrs:
|
||||||
if not self.is_name_vhost(addr):
|
if not self.is_name_vhost(addr):
|
||||||
logger.debug(("Setting VirtualHost at %s "
|
logging.debug("Setting VirtualHost at %s to be a name "
|
||||||
"to be a name based virtual host" % addr))
|
"based virtual host", addr)
|
||||||
self.add_name_vhost(addr)
|
self.add_name_vhost(addr)
|
||||||
|
|
||||||
def _get_ifmod(self, aug_conf_path, mod):
|
def _get_ifmod(self, aug_conf_path, mod):
|
||||||
@@ -703,7 +703,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
# validChars = re.compile("[a-zA-Z0-9.*?_-/]*")
|
# validChars = re.compile("[a-zA-Z0-9.*?_-/]*")
|
||||||
# matchObj = validChars.match(arg)
|
# matchObj = validChars.match(arg)
|
||||||
# if matchObj.group() != arg:
|
# if matchObj.group() != arg:
|
||||||
# logger.error("Error: Invalid regexp characters in %s" % arg)
|
# logging.error("Error: Invalid regexp characters in %s", arg)
|
||||||
# return []
|
# return []
|
||||||
|
|
||||||
# Standardize the include argument based on server root
|
# Standardize the include argument based on server root
|
||||||
@@ -769,7 +769,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
new_file.write(line)
|
new_file.write(line)
|
||||||
new_file.write("</IfModule>\n")
|
new_file.write("</IfModule>\n")
|
||||||
except IOError:
|
except IOError:
|
||||||
logger.fatal("Error writing/reading to file in make_vhost_ssl")
|
logging.fatal("Error writing/reading to file in make_vhost_ssl")
|
||||||
sys.exit(49)
|
sys.exit(49)
|
||||||
finally:
|
finally:
|
||||||
orig_file.close()
|
orig_file.close()
|
||||||
@@ -803,7 +803,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
vh_p = self.aug.match(("/files%s//* [label()=~regexp('%s')]" %
|
vh_p = self.aug.match(("/files%s//* [label()=~regexp('%s')]" %
|
||||||
(ssl_fp, case_i('VirtualHost'))))
|
(ssl_fp, case_i('VirtualHost'))))
|
||||||
if len(vh_p) != 1:
|
if len(vh_p) != 1:
|
||||||
logger.error("Error: should only be one vhost in %s" % avail_fp)
|
logging.error("Error: should only be one vhost in %s", avail_fp)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
self.add_dir(vh_p[0], "SSLCertificateFile",
|
self.add_dir(vh_p[0], "SSLCertificateFile",
|
||||||
@@ -813,7 +813,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
self.add_dir(vh_p[0], "Include", self.location["ssl_options"])
|
self.add_dir(vh_p[0], "Include", self.location["ssl_options"])
|
||||||
|
|
||||||
# Log actions and create save notes
|
# Log actions and create save notes
|
||||||
logger.info("Created an SSL vhost at %s" % ssl_fp)
|
logging.info("Created an SSL vhost at %s", ssl_fp)
|
||||||
self.save_notes += 'Created ssl vhost at %s\n' % ssl_fp
|
self.save_notes += 'Created ssl vhost at %s\n' % ssl_fp
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
@@ -832,7 +832,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
if (self.is_name_vhost(nonssl_vhost.addrs[i]) and
|
if (self.is_name_vhost(nonssl_vhost.addrs[i]) and
|
||||||
not self.is_name_vhost(ssl_addrs[i])):
|
not self.is_name_vhost(ssl_addrs[i])):
|
||||||
self.add_name_vhost(ssl_addrs[i])
|
self.add_name_vhost(ssl_addrs[i])
|
||||||
logger.info("Enabling NameVirtualHosts on " + ssl_addrs[i])
|
logging.info("Enabling NameVirtualHosts on %s", ssl_addrs[i])
|
||||||
need_to_save = True
|
need_to_save = True
|
||||||
|
|
||||||
if need_to_save:
|
if need_to_save:
|
||||||
@@ -862,7 +862,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
general_v = self._general_vhost(ssl_vhost)
|
general_v = self._general_vhost(ssl_vhost)
|
||||||
if general_v is None:
|
if general_v is None:
|
||||||
# Add virtual_server with redirect
|
# Add virtual_server with redirect
|
||||||
logger.debug(
|
logging.debug(
|
||||||
"Did not find http version of ssl virtual host... creating")
|
"Did not find http version of ssl virtual host... creating")
|
||||||
return self.create_redirect_vhost(ssl_vhost)
|
return self.create_redirect_vhost(ssl_vhost)
|
||||||
else:
|
else:
|
||||||
@@ -870,10 +870,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
exists, code = self.existing_redirect(general_v)
|
exists, code = self.existing_redirect(general_v)
|
||||||
if exists:
|
if exists:
|
||||||
if code == 0:
|
if code == 0:
|
||||||
logger.debug("Redirect already added")
|
logging.debug("Redirect already added")
|
||||||
return True, general_v
|
return True, general_v
|
||||||
else:
|
else:
|
||||||
logger.debug("Unknown redirect exists for this vhost")
|
logging.debug("Unknown redirect exists for this vhost")
|
||||||
return False, general_v
|
return False, general_v
|
||||||
# Add directives to server
|
# Add directives to server
|
||||||
self.add_dir(general_v.path, "RewriteEngine", "On")
|
self.add_dir(general_v.path, "RewriteEngine", "On")
|
||||||
@@ -986,7 +986,7 @@ LogLevel warn \n\
|
|||||||
# Write out file
|
# Write out file
|
||||||
with open(redirect_filepath, 'w') as redirect_fd:
|
with open(redirect_filepath, 'w') as redirect_fd:
|
||||||
redirect_fd.write(redirect_file)
|
redirect_fd.write(redirect_file)
|
||||||
logger.info("Created redirect file: " + redirect_filename)
|
logging.info("Created redirect file: %s", redirect_filename)
|
||||||
|
|
||||||
self.aug.load()
|
self.aug.load()
|
||||||
# Make a new vhost data structure and add it to the lists
|
# Make a new vhost data structure and add it to the lists
|
||||||
@@ -1115,8 +1115,8 @@ LogLevel warn \n\
|
|||||||
|
|
||||||
# Can be removed once find directive can return ordered results
|
# Can be removed once find directive can return ordered results
|
||||||
if len(cert_path) != 1 or len(key_path) != 1:
|
if len(cert_path) != 1 or len(key_path) != 1:
|
||||||
logger.error(("Too many cert or key directives in vhost "
|
logging.error("Too many cert or key directives in vhost %s",
|
||||||
"%s" % vhost.filep))
|
vhost.filep)
|
||||||
sys.exit(40)
|
sys.exit(40)
|
||||||
|
|
||||||
cert = os.path.abspath(self.aug.get(cert_path[0]))
|
cert = os.path.abspath(self.aug.get(cert_path[0]))
|
||||||
@@ -1166,7 +1166,7 @@ LogLevel warn \n\
|
|||||||
self.register_file_creation(False, enabled_path)
|
self.register_file_creation(False, enabled_path)
|
||||||
os.symlink(vhost.filep, enabled_path)
|
os.symlink(vhost.filep, enabled_path)
|
||||||
vhost.enabled = True
|
vhost.enabled = True
|
||||||
logger.info("Enabling available site: %s" % vhost.filep)
|
logging.info("Enabling available site: %s", vhost.filep)
|
||||||
self.save_notes += 'Enabled site %s\n' % vhost.filep
|
self.save_notes += 'Enabled site %s\n' % vhost.filep
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
@@ -1283,14 +1283,14 @@ LogLevel warn \n\
|
|||||||
stderr=subprocess.PIPE)
|
stderr=subprocess.PIPE)
|
||||||
text = proc.communicate()
|
text = proc.communicate()
|
||||||
except (OSError, ValueError):
|
except (OSError, ValueError):
|
||||||
logger.fatal("Unable to run /usr/sbin/apache2ctl configtest")
|
logging.fatal("Unable to run /usr/sbin/apache2ctl configtest")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if proc.returncode != 0:
|
if proc.returncode != 0:
|
||||||
# Enter recovery routine...
|
# Enter recovery routine...
|
||||||
logger.error("Configtest failed")
|
logging.error("Configtest failed")
|
||||||
logger.error(text[0])
|
logging.error(text[0])
|
||||||
logger.error(text[1])
|
logging.error(text[1])
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@@ -1378,8 +1378,8 @@ LogLevel warn \n\
|
|||||||
|
|
||||||
# Do weak validation that challenge is of expected type
|
# Do weak validation that challenge is of expected type
|
||||||
if not ("list_sni_tuple" in chall_dict and "dvsni_key" in chall_dict):
|
if not ("list_sni_tuple" in chall_dict and "dvsni_key" in chall_dict):
|
||||||
logger.fatal("Incorrect parameter given to Apache DVSNI challenge")
|
logging.fatal("Incorrect parameter given to Apache DVSNI challenge")
|
||||||
logger.fatal("Chall dict: " + str(chall_dict))
|
logging.fatal("Chall dict: %s", chall_dict)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
addresses = []
|
addresses = []
|
||||||
@@ -1387,10 +1387,10 @@ LogLevel warn \n\
|
|||||||
for tup in chall_dict["list_sni_tuple"]:
|
for tup in chall_dict["list_sni_tuple"]:
|
||||||
vhost = self.choose_virtual_host(tup[0])
|
vhost = self.choose_virtual_host(tup[0])
|
||||||
if vhost is None:
|
if vhost is None:
|
||||||
logger.error(("No vhost exists with servername "
|
logging.error(
|
||||||
"or alias of: %s" % tup[0]))
|
"No vhost exists with servername or alias of: %s", tup[0])
|
||||||
logger.error("No _default_:443 vhost exists")
|
logging.error("No _default_:443 vhost exists")
|
||||||
logger.error("Please specify servernames in the Apache config")
|
logging.error("Please specify servernames in the Apache config")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# TODO - @jdkasten review this code to make sure it makes sense
|
# TODO - @jdkasten review this code to make sure it makes sense
|
||||||
@@ -1559,8 +1559,8 @@ def enable_mod(mod_name):
|
|||||||
stdout=open("/dev/null", 'w'),
|
stdout=open("/dev/null", 'w'),
|
||||||
stderr=open("/dev/null", 'w'))
|
stderr=open("/dev/null", 'w'))
|
||||||
except (OSError, subprocess.CalledProcessError) as err:
|
except (OSError, subprocess.CalledProcessError) as err:
|
||||||
logger.error("Error enabling mod_" + mod_name)
|
logging.error("Error enabling mod_%s", mod_name)
|
||||||
logger.error("Exception: %s" % str(err))
|
logging.error("Exception: %s", err)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
@@ -1583,9 +1583,9 @@ def check_ssl_loaded():
|
|||||||
stderr=open(
|
stderr=open(
|
||||||
"/dev/null", 'w')).communicate()[0]
|
"/dev/null", 'w')).communicate()[0]
|
||||||
except (OSError, ValueError):
|
except (OSError, ValueError):
|
||||||
logger.error(
|
logging.error(
|
||||||
"Error accessing %s for loaded modules!" % CONFIG.APACHE_CTL)
|
"Error accessing %s for loaded modules!", CONFIG.APACHE_CTL)
|
||||||
logger.error("This may be caused by an Apache Configuration Error")
|
logging.error("This may be caused by an Apache Configuration Error")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if "ssl_module" in proc:
|
if "ssl_module" in proc:
|
||||||
@@ -1608,14 +1608,14 @@ def apache_restart():
|
|||||||
|
|
||||||
if proc.returncode != 0:
|
if proc.returncode != 0:
|
||||||
# Enter recovery routine...
|
# Enter recovery routine...
|
||||||
logger.error("Configtest failed")
|
logging.error("Configtest failed")
|
||||||
logger.error(text[0])
|
logging.error(text[0])
|
||||||
logger.error(text[1])
|
logging.error(text[1])
|
||||||
return False
|
return False
|
||||||
|
|
||||||
except (OSError, ValueError):
|
except (OSError, ValueError):
|
||||||
logger.fatal(("Apache Restart Failed - "
|
logging.fatal(
|
||||||
"Please Check the Configuration"))
|
"Apache Restart Failed - Please Check the Configuration")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@@ -1714,8 +1714,6 @@ def main():
|
|||||||
"""Main function used for quick testing purposes"""
|
"""Main function used for quick testing purposes"""
|
||||||
|
|
||||||
config = ApacheConfigurator()
|
config = ApacheConfigurator()
|
||||||
logger.setLogger(logger.FileLogger(sys.stdout))
|
|
||||||
logger.setLogLevel(logger.DEBUG)
|
|
||||||
|
|
||||||
# for v in config.vhosts:
|
# for v in config.vhosts:
|
||||||
# print v.filep
|
# print v.filep
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"""Class of Augeas Configurators."""
|
"""Class of Augeas Configurators."""
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import shutil
|
import shutil
|
||||||
@@ -9,7 +10,6 @@ import augeas
|
|||||||
from letsencrypt.client import CONFIG
|
from letsencrypt.client import CONFIG
|
||||||
from letsencrypt.client import configurator
|
from letsencrypt.client import configurator
|
||||||
from letsencrypt.client import le_util
|
from letsencrypt.client import le_util
|
||||||
from letsencrypt.client import logger
|
|
||||||
|
|
||||||
|
|
||||||
class AugeasConfigurator(configurator.Configurator):
|
class AugeasConfigurator(configurator.Configurator):
|
||||||
@@ -61,9 +61,9 @@ class AugeasConfigurator(configurator.Configurator):
|
|||||||
# As aug.get may return null
|
# As aug.get may return null
|
||||||
if lens_path and lens in lens_path:
|
if lens_path and lens in lens_path:
|
||||||
# Strip off /augeas/files and /error
|
# Strip off /augeas/files and /error
|
||||||
logger.error('There has been an error in parsing the file: '
|
logging.error('There has been an error in parsing the file: %s',
|
||||||
'%s' % path[13:len(path) - 6])
|
path[13:len(path) - 6])
|
||||||
logger.error(self.aug.get(path + '/message'))
|
logging.error(self.aug.get(path + '/message'))
|
||||||
|
|
||||||
def save(self, title=None, temporary=False):
|
def save(self, title=None, temporary=False):
|
||||||
"""Saves all changes to the configuration files.
|
"""Saves all changes to the configuration files.
|
||||||
@@ -90,14 +90,14 @@ class AugeasConfigurator(configurator.Configurator):
|
|||||||
except (RuntimeError, IOError):
|
except (RuntimeError, IOError):
|
||||||
# Check for the root of save problems
|
# Check for the root of save problems
|
||||||
new_errs = self.aug.match("/augeas//error")
|
new_errs = self.aug.match("/augeas//error")
|
||||||
# logger.error("During Save - " + mod_conf)
|
# logging.error("During Save - %s", mod_conf)
|
||||||
# Only print new errors caused by recent save
|
# Only print new errors caused by recent save
|
||||||
for err in new_errs:
|
for err in new_errs:
|
||||||
if err not in ex_errs:
|
if err not in ex_errs:
|
||||||
logger.error("Unable to save file - "
|
logging.error(
|
||||||
"%s" % err[13:len(err)-6])
|
"Unable to save file - %s", err[13:len(err) - 6])
|
||||||
logger.error("Attempted Save Notes")
|
logging.error("Attempted Save Notes")
|
||||||
logger.error(self.save_notes)
|
logging.error(self.save_notes)
|
||||||
# Erase Save Notes
|
# Erase Save Notes
|
||||||
self.save_notes = ""
|
self.save_notes = ""
|
||||||
return False
|
return False
|
||||||
@@ -117,7 +117,7 @@ class AugeasConfigurator(configurator.Configurator):
|
|||||||
valid, message = self.check_tempfile_saves(save_files)
|
valid, message = self.check_tempfile_saves(save_files)
|
||||||
|
|
||||||
if not valid:
|
if not valid:
|
||||||
logger.fatal(message)
|
logging.fatal(message)
|
||||||
# What is the protocol in this situation?
|
# What is the protocol in this situation?
|
||||||
# This shouldn't happen if the challenge codebase is correct
|
# This shouldn't happen if the challenge codebase is correct
|
||||||
return False
|
return False
|
||||||
@@ -153,8 +153,8 @@ class AugeasConfigurator(configurator.Configurator):
|
|||||||
result = self._recover_checkpoint(self.direc["temp"])
|
result = self._recover_checkpoint(self.direc["temp"])
|
||||||
if result != 0:
|
if result != 0:
|
||||||
# We have a partial or incomplete recovery
|
# We have a partial or incomplete recovery
|
||||||
logger.fatal("Incomplete or failed recovery for "
|
logging.fatal("Incomplete or failed recovery for %s",
|
||||||
"%s" % self.direc["temp"])
|
self.direc["temp"])
|
||||||
sys.exit(67)
|
sys.exit(67)
|
||||||
# Remember to reload Augeas
|
# Remember to reload Augeas
|
||||||
self.aug.load()
|
self.aug.load()
|
||||||
@@ -168,24 +168,24 @@ class AugeasConfigurator(configurator.Configurator):
|
|||||||
try:
|
try:
|
||||||
rollback = int(rollback)
|
rollback = int(rollback)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
logger.error("Rollback argument must be a positive integer")
|
logging.error("Rollback argument must be a positive integer")
|
||||||
# Sanity check input
|
# Sanity check input
|
||||||
if rollback < 1:
|
if rollback < 1:
|
||||||
logger.error("Rollback argument must be a positive integer")
|
logging.error("Rollback argument must be a positive integer")
|
||||||
return
|
return
|
||||||
|
|
||||||
backups = os.listdir(self.direc["backup"])
|
backups = os.listdir(self.direc["backup"])
|
||||||
backups.sort()
|
backups.sort()
|
||||||
|
|
||||||
if len(backups) < rollback:
|
if len(backups) < rollback:
|
||||||
logger.error(("Unable to rollback %d checkpoints, only "
|
logging.error("Unable to rollback %d checkpoints, only %d exist",
|
||||||
"%d exist") % (rollback, len(backups)))
|
rollback, len(backups))
|
||||||
|
|
||||||
while rollback > 0 and backups:
|
while rollback > 0 and backups:
|
||||||
cp_dir = self.direc["backup"] + backups.pop()
|
cp_dir = self.direc["backup"] + backups.pop()
|
||||||
result = self._recover_checkpoint(cp_dir)
|
result = self._recover_checkpoint(cp_dir)
|
||||||
if result != 0:
|
if result != 0:
|
||||||
logger.fatal("Failed to load checkpoint during rollback")
|
logging.fatal("Failed to load checkpoint during rollback")
|
||||||
sys.exit(39)
|
sys.exit(39)
|
||||||
rollback -= 1
|
rollback -= 1
|
||||||
|
|
||||||
@@ -262,7 +262,7 @@ class AugeasConfigurator(configurator.Configurator):
|
|||||||
if filename not in existing_filepaths:
|
if filename not in existing_filepaths:
|
||||||
# Tag files with index so multiple files can
|
# Tag files with index so multiple files can
|
||||||
# have the same filename
|
# have the same filename
|
||||||
logger.debug("Creating backup of %s" % filename)
|
logging.debug("Creating backup of %s", filename)
|
||||||
shutil.copy2(filename, os.path.join(
|
shutil.copy2(filename, os.path.join(
|
||||||
cp_dir, os.path.basename(filename) + "_" + str(idx)))
|
cp_dir, os.path.basename(filename) + "_" + str(idx)))
|
||||||
op_fd.write(filename + '\n')
|
op_fd.write(filename + '\n')
|
||||||
@@ -295,7 +295,7 @@ class AugeasConfigurator(configurator.Configurator):
|
|||||||
path)
|
path)
|
||||||
except (IOError, OSError):
|
except (IOError, OSError):
|
||||||
# This file is required in all checkpoints.
|
# This file is required in all checkpoints.
|
||||||
logger.error("Unable to recover files from %s" % cp_dir)
|
logging.error("Unable to recover files from %s", cp_dir)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
# Remove any newly added files if they exist
|
# Remove any newly added files if they exist
|
||||||
@@ -304,7 +304,7 @@ class AugeasConfigurator(configurator.Configurator):
|
|||||||
try:
|
try:
|
||||||
shutil.rmtree(cp_dir)
|
shutil.rmtree(cp_dir)
|
||||||
except OSError:
|
except OSError:
|
||||||
logger.error("Unable to remove directory: %s" % cp_dir)
|
logging.error("Unable to remove directory: %s", cp_dir)
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
@@ -354,7 +354,7 @@ class AugeasConfigurator(configurator.Configurator):
|
|||||||
for file_path in files:
|
for file_path in files:
|
||||||
new_fd.write("%s\n" % file_path)
|
new_fd.write("%s\n" % file_path)
|
||||||
except (IOError, OSError):
|
except (IOError, OSError):
|
||||||
logger.error("ERROR: Unable to register file creation")
|
logging.error("ERROR: Unable to register file creation")
|
||||||
|
|
||||||
def recovery_routine(self):
|
def recovery_routine(self):
|
||||||
"""Revert all previously modified files.
|
"""Revert all previously modified files.
|
||||||
@@ -373,8 +373,8 @@ class AugeasConfigurator(configurator.Configurator):
|
|||||||
# We have a partial or incomplete recovery
|
# We have a partial or incomplete recovery
|
||||||
# Not as egregious
|
# Not as egregious
|
||||||
# TODO: Additional tests? recovery
|
# TODO: Additional tests? recovery
|
||||||
logger.fatal("Incomplete or failed recovery for %s" %
|
logging.fatal("Incomplete or failed recovery for %s",
|
||||||
self.direc["progress"])
|
self.direc["progress"])
|
||||||
sys.exit(68)
|
sys.exit(68)
|
||||||
|
|
||||||
# Need to reload configuration after these changes take effect
|
# Need to reload configuration after these changes take effect
|
||||||
@@ -403,13 +403,12 @@ class AugeasConfigurator(configurator.Configurator):
|
|||||||
if os.path.lexists(path):
|
if os.path.lexists(path):
|
||||||
os.remove(path)
|
os.remove(path)
|
||||||
else:
|
else:
|
||||||
logger.warn((
|
logging.warn(
|
||||||
"File: %s - Could not be found to be deleted\n"
|
"File: %s - Could not be found to be deleted\n"
|
||||||
"Program was probably shut down unexpectedly, "
|
"Program was probably shut down unexpectedly, ")
|
||||||
"in which case this is not a problem") % path)
|
|
||||||
except (IOError, OSError):
|
except (IOError, OSError):
|
||||||
logger.fatal(
|
logging.fatal(
|
||||||
"Unable to remove filepaths contained within %s" % file_list)
|
"Unable to remove filepaths contained within %s", file_list)
|
||||||
sys.exit(41)
|
sys.exit(41)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@@ -441,12 +440,12 @@ class AugeasConfigurator(configurator.Configurator):
|
|||||||
shutil.move(changes_since_tmp_path, changes_since_path)
|
shutil.move(changes_since_tmp_path, changes_since_path)
|
||||||
|
|
||||||
except (IOError, OSError):
|
except (IOError, OSError):
|
||||||
logger.error("Unable to finalize checkpoint - adding title")
|
logging.error("Unable to finalize checkpoint - adding title")
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
os.rename(cp_dir, final_dir)
|
os.rename(cp_dir, final_dir)
|
||||||
except OSError:
|
except OSError:
|
||||||
logger.error("Unable to finalize checkpoint, %s -> %s" %
|
logging.error(
|
||||||
(cp_dir, final_dir))
|
"Unable to finalize checkpoint, %s -> %s", cp_dir, final_dir)
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"""ACME challenge."""
|
"""ACME challenge."""
|
||||||
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from letsencrypt.client import CONFIG
|
from letsencrypt.client import CONFIG
|
||||||
from letsencrypt.client import logger
|
|
||||||
|
|
||||||
|
|
||||||
class Challenge(object):
|
class Challenge(object):
|
||||||
@@ -93,8 +93,8 @@ def _find_smart_path(challenges, combos):
|
|||||||
combo_total = 0
|
combo_total = 0
|
||||||
|
|
||||||
if not best_combo:
|
if not best_combo:
|
||||||
logger.fatal("Client does not support any combination of "
|
logging.fatal("Client does not support any combination of "
|
||||||
"challenges to satisfy ACME server")
|
"challenges to satisfy ACME server")
|
||||||
sys.exit(22)
|
sys.exit(22)
|
||||||
|
|
||||||
return best_combo
|
return best_combo
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import collections
|
import collections
|
||||||
import csv
|
import csv
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import socket
|
import socket
|
||||||
@@ -21,7 +22,6 @@ from letsencrypt.client import crypto_util
|
|||||||
from letsencrypt.client import display
|
from letsencrypt.client import display
|
||||||
from letsencrypt.client import errors
|
from letsencrypt.client import errors
|
||||||
from letsencrypt.client import le_util
|
from letsencrypt.client import le_util
|
||||||
from letsencrypt.client import logger
|
|
||||||
|
|
||||||
|
|
||||||
# it's weird to point to chocolate servers via raw IPv6 addresses, and
|
# it's weird to point to chocolate servers via raw IPv6 addresses, and
|
||||||
@@ -65,9 +65,6 @@ class Client(object):
|
|||||||
self.privkey = privkey
|
self.privkey = privkey
|
||||||
self._validate_csr_key_cli() # TODO: catch exceptions
|
self._validate_csr_key_cli() # TODO: catch exceptions
|
||||||
|
|
||||||
# Logger needs to be initialized before Configurator
|
|
||||||
self.init_logger()
|
|
||||||
|
|
||||||
# TODO: Can probably figure out which configurator to use
|
# TODO: Can probably figure out which configurator to use
|
||||||
# without special packaging based on system info Command
|
# without special packaging based on system info Command
|
||||||
# line arg or client function to discover
|
# line arg or client function to discover
|
||||||
@@ -178,8 +175,8 @@ class Client(object):
|
|||||||
try:
|
try:
|
||||||
return self.is_expected_msg(auth_dict, "authorization")
|
return self.is_expected_msg(auth_dict, "authorization")
|
||||||
except:
|
except:
|
||||||
logger.fatal("Failed Authorization procedure - "
|
logging.fatal(
|
||||||
"cleaning up challenges")
|
"Failed Authorization procedure - cleaning up challenges")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
finally:
|
finally:
|
||||||
self.cleanup_challenges(chal_objs)
|
self.cleanup_challenges(chal_objs)
|
||||||
@@ -193,7 +190,7 @@ class Client(object):
|
|||||||
:rtype: dict
|
:rtype: dict
|
||||||
|
|
||||||
"""
|
"""
|
||||||
logger.info("Preparing and sending CSR..")
|
logging.info("Preparing and sending CSR...")
|
||||||
return self.send_and_receive_expected(
|
return self.send_and_receive_expected(
|
||||||
acme.certificate_request(csr_der, self.privkey.pem), "certificate")
|
acme.certificate_request(csr_der, self.privkey.pem), "certificate")
|
||||||
|
|
||||||
@@ -303,24 +300,23 @@ class Client(object):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
elif response["type"] == "error":
|
elif response["type"] == "error":
|
||||||
logger.error("%s: %s - More Info: %s" %
|
logging.error(
|
||||||
(response["error"],
|
"%s: %s - More Info: %s", response["error"],
|
||||||
response.get("message", ""),
|
response.get("message", ""), response.get("moreInfo", ""))
|
||||||
response.get("moreInfo", "")))
|
|
||||||
raise errors.LetsEncryptClientError(response["error"])
|
raise errors.LetsEncryptClientError(response["error"])
|
||||||
|
|
||||||
elif response["type"] == "defer":
|
elif response["type"] == "defer":
|
||||||
logger.info("Waiting for %d seconds..." % delay)
|
logging.info("Waiting for %d seconds...", delay)
|
||||||
time.sleep(delay)
|
time.sleep(delay)
|
||||||
response = self.send(acme.status_request(response["token"]))
|
response = self.send(acme.status_request(response["token"]))
|
||||||
else:
|
else:
|
||||||
logger.fatal("Received unexpected message")
|
logging.fatal("Received unexpected message")
|
||||||
logger.fatal("Expected: %s" % expected)
|
logging.fatal("Expected: %s" % expected)
|
||||||
logger.fatal("Received: " + response)
|
logging.fatal("Received: " + response)
|
||||||
sys.exit(33)
|
sys.exit(33)
|
||||||
|
|
||||||
logger.error("Server has deferred past the max of %d seconds" %
|
logging.error(
|
||||||
(rounds * delay))
|
"Server has deferred past the max of %d seconds", rounds * delay)
|
||||||
|
|
||||||
def list_certs_keys(self):
|
def list_certs_keys(self):
|
||||||
"""List trusted Let's Encrypt certificates."""
|
"""List trusted Let's Encrypt certificates."""
|
||||||
@@ -328,7 +324,7 @@ class Client(object):
|
|||||||
certs = []
|
certs = []
|
||||||
|
|
||||||
if not os.path.isfile(list_file):
|
if not os.path.isfile(list_file):
|
||||||
logger.info(
|
logging.info(
|
||||||
"You don't have any certificates saved from letsencrypt")
|
"You don't have any certificates saved from letsencrypt")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -399,8 +395,8 @@ class Client(object):
|
|||||||
cert_fd.write(
|
cert_fd.write(
|
||||||
crypto_util.b64_cert_to_pem(certificate_dict["certificate"]))
|
crypto_util.b64_cert_to_pem(certificate_dict["certificate"]))
|
||||||
cert_fd.close()
|
cert_fd.close()
|
||||||
logger.info("Server issued certificate; certificate written to %s" %
|
logging.info(
|
||||||
cert_file)
|
"Server issued certificate; certificate written to %s", cert_file)
|
||||||
|
|
||||||
if certificate_dict.get("chain", None):
|
if certificate_dict.get("chain", None):
|
||||||
chain_fd, chain_fn = le_util.unique_file(CONFIG.CHAIN_PATH, 0o644)
|
chain_fd, chain_fn = le_util.unique_file(CONFIG.CHAIN_PATH, 0o644)
|
||||||
@@ -408,7 +404,7 @@ class Client(object):
|
|||||||
chain_fd.write(crypto_util.b64_cert_to_pem(cert))
|
chain_fd.write(crypto_util.b64_cert_to_pem(cert))
|
||||||
chain_fd.close()
|
chain_fd.close()
|
||||||
|
|
||||||
logger.info("Cert chain written to %s" % chain_fn)
|
logging.info("Cert chain written to %s", chain_fn)
|
||||||
|
|
||||||
# This expects a valid chain file
|
# This expects a valid chain file
|
||||||
cert_chain_abspath = os.path.abspath(chain_fn)
|
cert_chain_abspath = os.path.abspath(chain_fn)
|
||||||
@@ -420,7 +416,7 @@ class Client(object):
|
|||||||
cert_chain_abspath)
|
cert_chain_abspath)
|
||||||
# Enable any vhost that was issued to, but not enabled
|
# Enable any vhost that was issued to, but not enabled
|
||||||
if not host.enabled:
|
if not host.enabled:
|
||||||
logger.info("Enabling Site " + host.filep)
|
logging.info("Enabling Site %s", host.filep)
|
||||||
self.config.enable_site(host)
|
self.config.enable_site(host)
|
||||||
|
|
||||||
# sites may have been enabled / final cleanup
|
# sites may have been enabled / final cleanup
|
||||||
@@ -464,7 +460,7 @@ class Client(object):
|
|||||||
:param dict challenges: challenges from a challenge message
|
:param dict challenges: challenges from a challenge message
|
||||||
|
|
||||||
"""
|
"""
|
||||||
logger.info("Cleaning up challenges...")
|
logging.info("Cleaning up challenges...")
|
||||||
for chall in challenges:
|
for chall in challenges:
|
||||||
if chall["type"] in CONFIG.CONFIG_CHALLENGES:
|
if chall["type"] in CONFIG.CONFIG_CHALLENGES:
|
||||||
self.config.cleanup()
|
self.config.cleanup()
|
||||||
@@ -484,7 +480,7 @@ class Client(object):
|
|||||||
path = challenge.gen_challenge_path(
|
path = challenge.gen_challenge_path(
|
||||||
challenge_msg["challenges"], challenge_msg.get("combinations", []))
|
challenge_msg["challenges"], challenge_msg.get("combinations", []))
|
||||||
|
|
||||||
logger.info("Performing the following challenges:")
|
logging.info("Performing the following challenges:")
|
||||||
|
|
||||||
# Every indices element is a list of integers referring to which
|
# Every indices element is a list of integers referring to which
|
||||||
# challenges in the master list the challenge object satisfies
|
# challenges in the master list the challenge object satisfies
|
||||||
@@ -507,8 +503,8 @@ class Client(object):
|
|||||||
for index in indices[i]:
|
for index in indices[i]:
|
||||||
responses[index] = response
|
responses[index] = response
|
||||||
|
|
||||||
logger.info("Configured Apache for challenges; " +
|
logging.info(
|
||||||
"waiting for verification...")
|
"Configured Apache for challenges; waiting for verification...")
|
||||||
|
|
||||||
return responses, challenge_objs
|
return responses, challenge_objs
|
||||||
|
|
||||||
@@ -528,9 +524,10 @@ class Client(object):
|
|||||||
idx = 0
|
idx = 0
|
||||||
|
|
||||||
if encrypt:
|
if encrypt:
|
||||||
logger.error("Unfortunately securely storing the certificates/"
|
logging.error(
|
||||||
"keys is not yet available. Stay tuned for the "
|
"Unfortunately securely storing the certificates/"
|
||||||
"next update!")
|
"keys is not yet available. Stay tuned for the "
|
||||||
|
"next update!")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if os.path.isfile(list_file):
|
if os.path.isfile(list_file):
|
||||||
@@ -566,9 +563,8 @@ class Client(object):
|
|||||||
"""
|
"""
|
||||||
for ssl_vh in vhost:
|
for ssl_vh in vhost:
|
||||||
success, redirect_vhost = self.config.enable_redirect(ssl_vh)
|
success, redirect_vhost = self.config.enable_redirect(ssl_vh)
|
||||||
# pylint: disable=maybe-no-member
|
logging.info(
|
||||||
logger.info("\nRedirect vhost: " + redirect_vhost.filep +
|
"\nRedirect vhost: %s - %s ", redirect_vhost.filep, success)
|
||||||
" - " + str(success))
|
|
||||||
# If successful, make sure redirect site is enabled
|
# If successful, make sure redirect site is enabled
|
||||||
if success:
|
if success:
|
||||||
self.config.enable_site(redirect_vhost)
|
self.config.enable_site(redirect_vhost)
|
||||||
@@ -615,20 +611,20 @@ class Client(object):
|
|||||||
chall = challenges[index]
|
chall = challenges[index]
|
||||||
|
|
||||||
if chall["type"] == "dvsni":
|
if chall["type"] == "dvsni":
|
||||||
logger.info(" DVSNI challenge for name %s." % name)
|
logging.info(" DVSNI challenge for name %s.", name)
|
||||||
sni_satisfies.append(index)
|
sni_satisfies.append(index)
|
||||||
sni_todo.append((str(name), str(chall["r"]),
|
sni_todo.append((str(name), str(chall["r"]),
|
||||||
str(chall["nonce"])))
|
str(chall["nonce"])))
|
||||||
|
|
||||||
elif chall["type"] == "recoveryToken":
|
elif chall["type"] == "recoveryToken":
|
||||||
logger.info("\tRecovery Token Challenge for name: %s." % name)
|
logging.info("\tRecovery Token Challenge for name: %s.", name)
|
||||||
challenge_obj_indices.append(index)
|
challenge_obj_indices.append(index)
|
||||||
challenge_objs.append({
|
challenge_objs.append({
|
||||||
type: "recoveryToken",
|
type: "recoveryToken",
|
||||||
})
|
})
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.fatal("Challenge not currently supported")
|
logging.fatal("Challenge not currently supported")
|
||||||
sys.exit(82)
|
sys.exit(82)
|
||||||
|
|
||||||
if sni_todo:
|
if sni_todo:
|
||||||
@@ -640,7 +636,7 @@ class Client(object):
|
|||||||
"dvsni_key": self.privkey,
|
"dvsni_key": self.privkey,
|
||||||
})
|
})
|
||||||
challenge_obj_indices.append(sni_satisfies)
|
challenge_obj_indices.append(sni_satisfies)
|
||||||
logger.debug(sni_todo)
|
logging.debug(sni_todo)
|
||||||
|
|
||||||
return challenge_objs, challenge_obj_indices
|
return challenge_objs, challenge_obj_indices
|
||||||
|
|
||||||
@@ -663,7 +659,7 @@ class Client(object):
|
|||||||
key_f.write(key_pem)
|
key_f.write(key_pem)
|
||||||
key_f.close()
|
key_f.close()
|
||||||
|
|
||||||
logger.info("Generating key: %s" % key_filename)
|
logging.info("Generating key: %s", key_filename)
|
||||||
|
|
||||||
self.privkey = Client.Key(key_filename, key_pem)
|
self.privkey = Client.Key(key_filename, key_pem)
|
||||||
|
|
||||||
@@ -678,7 +674,7 @@ class Client(object):
|
|||||||
csr_f.write(csr_pem)
|
csr_f.write(csr_pem)
|
||||||
csr_f.close()
|
csr_f.close()
|
||||||
|
|
||||||
logger.info("Creating CSR: %s" % csr_filename)
|
logging.info("Creating CSR: %s", csr_filename)
|
||||||
|
|
||||||
self.csr = Client.CSR(csr_filename, csr_der, "der")
|
self.csr = Client.CSR(csr_filename, csr_der, "der")
|
||||||
elif self.csr.type != "der":
|
elif self.csr.type != "der":
|
||||||
@@ -726,22 +722,14 @@ class Client(object):
|
|||||||
sanity_check_names(names)
|
sanity_check_names(names)
|
||||||
|
|
||||||
if not names:
|
if not names:
|
||||||
logger.fatal("No domain names were found in your apache config")
|
logging.fatal("No domain names were found in your apache config")
|
||||||
logger.fatal("Either specify which names you would like "
|
logging.fatal("Either specify which names you would like "
|
||||||
"letsencrypt to validate or add server names "
|
"letsencrypt to validate or add server names "
|
||||||
"to your virtual hosts")
|
"to your virtual hosts")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
return names
|
return names
|
||||||
|
|
||||||
def init_logger(self):
|
|
||||||
if self.use_curses:
|
|
||||||
logger.setLogger(logger.NcursesLogger())
|
|
||||||
logger.setLogLevel(logger.INFO)
|
|
||||||
else:
|
|
||||||
logger.setLogger(logger.FileLogger(sys.stdout))
|
|
||||||
logger.setLogLevel(logger.INFO)
|
|
||||||
|
|
||||||
|
|
||||||
def remove_cert_key(cert):
|
def remove_cert_key(cert):
|
||||||
"""Remove certificate key.
|
"""Remove certificate key.
|
||||||
@@ -778,7 +766,7 @@ def sanity_check_names(names):
|
|||||||
"""
|
"""
|
||||||
for name in names:
|
for name in names:
|
||||||
if not is_hostname_sane(name):
|
if not is_hostname_sane(name):
|
||||||
logger.fatal(repr(name) + " is an impossible hostname")
|
logging.fatal("%r is an impossible hostname", name)
|
||||||
sys.exit(81)
|
sys.exit(81)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""Let's Encrypt client crypto utility functions"""
|
"""Let's Encrypt client crypto utility functions"""
|
||||||
import binascii
|
import binascii
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import logging
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from Crypto import Random
|
from Crypto import Random
|
||||||
@@ -12,7 +13,6 @@ import M2Crypto
|
|||||||
|
|
||||||
from letsencrypt.client import CONFIG
|
from letsencrypt.client import CONFIG
|
||||||
from letsencrypt.client import le_util
|
from letsencrypt.client import le_util
|
||||||
from letsencrypt.client import logger
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: All of these functions need unit tests
|
# TODO: All of these functions need unit tests
|
||||||
@@ -53,7 +53,7 @@ def create_sig(msg, key_str, nonce=None, nonce_len=CONFIG.NONCE_SIZE):
|
|||||||
hashed = Crypto.Hash.SHA256.new(msg_with_nonce)
|
hashed = Crypto.Hash.SHA256.new(msg_with_nonce)
|
||||||
signature = Crypto.Signature.PKCS1_v1_5.new(key).sign(hashed)
|
signature = Crypto.Signature.PKCS1_v1_5.new(key).sign(hashed)
|
||||||
|
|
||||||
logger.debug('%s signed as %s' % (msg_with_nonce, signature))
|
logging.debug('%s signed as %s', msg_with_nonce, signature)
|
||||||
|
|
||||||
n_bytes = binascii.unhexlify(leading_zeros(hex(key.n)[2:].rstrip("L")))
|
n_bytes = binascii.unhexlify(leading_zeros(hex(key.n)[2:].rstrip("L")))
|
||||||
e_bytes = binascii.unhexlify(leading_zeros(hex(key.e)[2:].rstrip("L")))
|
e_bytes = binascii.unhexlify(leading_zeros(hex(key.e)[2:].rstrip("L")))
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
"""Logging utilities."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import dialog
|
||||||
|
|
||||||
|
from letsencrypt.client import display
|
||||||
|
|
||||||
|
|
||||||
|
class DialogHandler(logging.Handler):
|
||||||
|
"""Logging handler using dialog info box.
|
||||||
|
|
||||||
|
:ivar int height: Height of the info box (without padding).
|
||||||
|
:ivar int width: Width of the info box (without padding).
|
||||||
|
:ivar list lines: Lines to be displayed in the info box.
|
||||||
|
:ivar d: Instance of :class:`dialog.Dialog`.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
PADDING_HEIGHT = 2
|
||||||
|
PADDING_WIDTH = 4
|
||||||
|
|
||||||
|
def __init__(self, level=logging.NOTSET, height=display.HEIGHT,
|
||||||
|
width=display.WIDTH - 4, d=None):
|
||||||
|
# Handler not new-style -> no super
|
||||||
|
logging.Handler.__init__(self, level)
|
||||||
|
self.height = height
|
||||||
|
self.width = width
|
||||||
|
# "dialog" collides with module name... pylint: disable=invalid-name
|
||||||
|
self.d = dialog.Dialog() if d is None else d
|
||||||
|
self.lines = []
|
||||||
|
|
||||||
|
def emit(self, record):
|
||||||
|
"""Emit message to a dialog info box.
|
||||||
|
|
||||||
|
Only show the last (self.height) lines; note that lines can wrap
|
||||||
|
at self.width, so a single line could actually be multiple
|
||||||
|
lines.
|
||||||
|
|
||||||
|
"""
|
||||||
|
for line in (record.msg % record.args).splitlines():
|
||||||
|
# check for lines that would wrap
|
||||||
|
cur_out = line
|
||||||
|
while len(cur_out) > self.width:
|
||||||
|
# find first space before self.width chars into cur_out
|
||||||
|
last_space_pos = cur_out.rfind(' ', 0, self.width)
|
||||||
|
|
||||||
|
if last_space_pos == -1:
|
||||||
|
# no spaces, just cut them off at whatever
|
||||||
|
self.lines.append(cur_out[0:self.width])
|
||||||
|
cur_out = cur_out[self.width:]
|
||||||
|
else:
|
||||||
|
# cut off at last space
|
||||||
|
self.lines.append(cur_out[0:last_space_pos])
|
||||||
|
cur_out = cur_out[last_space_pos + 1:]
|
||||||
|
if cur_out != '':
|
||||||
|
self.lines.append(cur_out)
|
||||||
|
|
||||||
|
# show last 16 lines
|
||||||
|
content = '\n'.join(self.lines[-self.height:])
|
||||||
|
|
||||||
|
# add the padding around the box
|
||||||
|
self.d.infobox(
|
||||||
|
content, self.height + self.PADDING_HEIGHT,
|
||||||
|
self.width + self.PADDING_WIDTH)
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
"""Logger."""
|
|
||||||
import logger
|
|
||||||
import textwrap
|
|
||||||
import time
|
|
||||||
|
|
||||||
import dialog
|
|
||||||
|
|
||||||
from letsencrypt.client import display
|
|
||||||
from letsencrypt.client import errors
|
|
||||||
|
|
||||||
|
|
||||||
class Singleton(object):
|
|
||||||
_instance = None
|
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
|
||||||
if not cls._instance:
|
|
||||||
cls._instance = super(
|
|
||||||
Singleton, cls).__new__(cls, *args, **kwargs)
|
|
||||||
return cls._instance
|
|
||||||
|
|
||||||
# log levels
|
|
||||||
TRACE = 5
|
|
||||||
DEBUG = 4
|
|
||||||
INFO = 3
|
|
||||||
WARN = 2
|
|
||||||
ERROR = 1
|
|
||||||
FATAL = 0
|
|
||||||
NONE = -1
|
|
||||||
|
|
||||||
|
|
||||||
class Logger(Singleton):
|
|
||||||
debugLevelStr = {TRACE: 'TRACE', DEBUG: 'DEBUG', INFO: 'INFO',
|
|
||||||
WARN: 'WARN', ERROR: 'ERROR', FATAL: 'FATAL'}
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.level = INFO
|
|
||||||
|
|
||||||
def debugLevel(self, level=DEBUG):
|
|
||||||
return self.debugLevelStr[level]
|
|
||||||
|
|
||||||
def log(self, level, data):
|
|
||||||
raise errors.LetsEncryptClientError("No Logger defined")
|
|
||||||
|
|
||||||
def timefmt(self, t=None):
|
|
||||||
if t is None:
|
|
||||||
t = time.time()
|
|
||||||
return time.strftime("%b %d %Y %H:%M:%S",
|
|
||||||
time.localtime(t)) + ('%.03f' % (t - int(t)))[1:]
|
|
||||||
|
|
||||||
|
|
||||||
class FileLogger(Logger):
|
|
||||||
|
|
||||||
def __init__(self, outfile):
|
|
||||||
self.outfile = outfile
|
|
||||||
|
|
||||||
def log(self, level, data):
|
|
||||||
msg = "%s [%s] %s" % (self.timefmt(), self.debugLevel(level), data)
|
|
||||||
wm = textwrap.fill(msg, 80)
|
|
||||||
self.outfile.write("%s\n" % wm)
|
|
||||||
|
|
||||||
|
|
||||||
class NcursesLogger(Logger):
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
firstmessage="",
|
|
||||||
height=display.HEIGHT,
|
|
||||||
width=display.WIDTH-4):
|
|
||||||
self.lines = []
|
|
||||||
self.all_content = ""
|
|
||||||
self.d = dialog.Dialog()
|
|
||||||
self.height = height
|
|
||||||
self.width = width
|
|
||||||
self.add(firstmessage)
|
|
||||||
|
|
||||||
'''
|
|
||||||
Only show the last (self.height) lines;
|
|
||||||
note that lines can wrap at self.width, so
|
|
||||||
a single line could actually be multiple lines
|
|
||||||
'''
|
|
||||||
def add(self, s):
|
|
||||||
self.all_content += s
|
|
||||||
|
|
||||||
for line in s.splitlines():
|
|
||||||
# check for lines that would wrap
|
|
||||||
cur_out = line
|
|
||||||
while len(cur_out) > self.width:
|
|
||||||
|
|
||||||
# find first space before self.width chars into cur_out
|
|
||||||
last_space_pos = cur_out.rfind(' ', 0, self.width)
|
|
||||||
|
|
||||||
if (last_space_pos == -1):
|
|
||||||
# no spaces, just cut them off at whatever
|
|
||||||
self.lines.append(cur_out[0:self.width])
|
|
||||||
cur_out = cur_out[self.width:]
|
|
||||||
else:
|
|
||||||
# cut off at last space
|
|
||||||
self.lines.append(cur_out[0:last_space_pos])
|
|
||||||
cur_out = cur_out[last_space_pos+1:]
|
|
||||||
if cur_out != '':
|
|
||||||
self.lines.append(cur_out)
|
|
||||||
|
|
||||||
# show last 16 lines
|
|
||||||
self.content = '\n'.join(self.lines[-self.height:])
|
|
||||||
self.show()
|
|
||||||
|
|
||||||
def show(self):
|
|
||||||
# add the padding around the box
|
|
||||||
self.d.infobox(self.content, self.height+2, self.width+4)
|
|
||||||
|
|
||||||
def log(self, level, data):
|
|
||||||
self.add(str(data) + "\n")
|
|
||||||
|
|
||||||
|
|
||||||
log_instance = None
|
|
||||||
|
|
||||||
|
|
||||||
def setLogger(log_inst):
|
|
||||||
global log_instance
|
|
||||||
log_instance = log_inst
|
|
||||||
|
|
||||||
|
|
||||||
def setLogLevel(log_level):
|
|
||||||
global log_instance
|
|
||||||
log_instance.level = log_level
|
|
||||||
|
|
||||||
|
|
||||||
def log(level, data):
|
|
||||||
global log_instance
|
|
||||||
if level <= log_instance.level:
|
|
||||||
log_instance.log(level, data)
|
|
||||||
|
|
||||||
|
|
||||||
def trace(data):
|
|
||||||
log(TRACE, data)
|
|
||||||
|
|
||||||
|
|
||||||
def debug(data):
|
|
||||||
log(DEBUG, data)
|
|
||||||
|
|
||||||
|
|
||||||
def info(data):
|
|
||||||
log(INFO, data)
|
|
||||||
|
|
||||||
|
|
||||||
def warn(data):
|
|
||||||
log(WARN, data)
|
|
||||||
|
|
||||||
|
|
||||||
def error(data):
|
|
||||||
log(ERROR, data)
|
|
||||||
|
|
||||||
|
|
||||||
def fatal(data):
|
|
||||||
log(FATAL, data)
|
|
||||||
|
|
||||||
|
|
||||||
def none(data):
|
|
||||||
# Uh...what?
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# Unit test/example usage:
|
|
||||||
|
|
||||||
# Set the logging type you want to use (stdout logging):
|
|
||||||
# logger.setLogger(FileLogger(sys.stdout))
|
|
||||||
setLogger(NcursesLogger())
|
|
||||||
|
|
||||||
# Set the most verbose you want to log
|
|
||||||
# (TRACE, DEBUG, INFO, WARN, ERROR, FATAL, NONE)
|
|
||||||
setLogLevel(logger.TRACE)
|
|
||||||
|
|
||||||
# Log a message:
|
|
||||||
# logger.log(logger.INFO, "logger!")
|
|
||||||
|
|
||||||
time.sleep(0.01)
|
|
||||||
info(("This is a long line, it's pretty long, butitalso hasbig wordsthat "
|
|
||||||
"areprobably hardtobreak oninan easywayforthe ncurseslib, "
|
|
||||||
"sowhatdoes itdo then?"))
|
|
||||||
info("aa " + "a"*70 + "B")
|
|
||||||
|
|
||||||
for i in range(20):
|
|
||||||
info("iteration #%d/20" % i)
|
|
||||||
time.sleep(0.3)
|
|
||||||
|
|
||||||
# Alternatively, use
|
|
||||||
error("errrrr")
|
|
||||||
|
|
||||||
trace("some trace data: %d - %f - %s" % (5, 8.3, 'cows'))
|
|
||||||
@@ -66,7 +66,7 @@ class NginxConfigurator(augeas_configurator.AugeasConfigurator):
|
|||||||
# # validChars = re.compile("[a-zA-Z0-9.*?_-/]*")
|
# # validChars = re.compile("[a-zA-Z0-9.*?_-/]*")
|
||||||
# # matchObj = validChars.match(arg)
|
# # matchObj = validChars.match(arg)
|
||||||
# # if matchObj.group() != arg:
|
# # if matchObj.group() != arg:
|
||||||
# # logger.error("Error: Invalid regexp characters in %s" % arg)
|
# # logging.error("Error: Invalid regexp characters in %s", arg)
|
||||||
# # return []
|
# # return []
|
||||||
|
|
||||||
# # Standardize the include argument based on server root
|
# # Standardize the include argument based on server root
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ from letsencrypt.client import apache_configurator
|
|||||||
from letsencrypt.client import CONFIG
|
from letsencrypt.client import CONFIG
|
||||||
from letsencrypt.client import display
|
from letsencrypt.client import display
|
||||||
from letsencrypt.client import errors
|
from letsencrypt.client import errors
|
||||||
from letsencrypt.client import logger
|
|
||||||
|
|
||||||
|
|
||||||
UBUNTU_CONFIGS = pkg_resources.resource_filename(
|
UBUNTU_CONFIGS = pkg_resources.resource_filename(
|
||||||
@@ -24,8 +23,6 @@ class TwoVhost80Test(unittest.TestCase):
|
|||||||
"""Test two standard well configured HTTP vhosts."""
|
"""Test two standard well configured HTTP vhosts."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
logger.setLogger(logger.FileLogger(sys.stdout))
|
|
||||||
logger.setLogLevel(logger.INFO)
|
|
||||||
display.set_display(display.NcursesDisplay())
|
display.set_display(display.NcursesDisplay())
|
||||||
|
|
||||||
self.temp_dir = os.path.join(
|
self.temp_dir = os.path.join(
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
"""Tests for letsencrypt.client.log."""
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
|
||||||
|
class DialogHandlerTest(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.d = mock.MagicMock() # pylint: disable=invalid-name
|
||||||
|
|
||||||
|
from letsencrypt.client.log import DialogHandler
|
||||||
|
self.handler = DialogHandler(height=2, width=6, d=self.d)
|
||||||
|
self.handler.PADDING_HEIGHT = 2
|
||||||
|
self.handler.PADDING_WIDTH = 4
|
||||||
|
|
||||||
|
def test_adds_padding(self):
|
||||||
|
self.handler.emit(mock.MagicMock())
|
||||||
|
self.d.infobox.assert_called_once_with(mock.ANY, 4, 10)
|
||||||
|
|
||||||
|
def test_args_in_msg_get_replaced(self):
|
||||||
|
assert len('123456') <= self.handler.width
|
||||||
|
self.handler.emit(mock.MagicMock(msg='123%s', args=(456,)))
|
||||||
|
self.d.infobox.assert_called_once_with('123456', mock.ANY, mock.ANY)
|
||||||
|
|
||||||
|
def test_wraps_nospace_is_greedy(self):
|
||||||
|
assert len('1234567') > self.handler.width
|
||||||
|
self.handler.emit(mock.MagicMock(msg='1234567'))
|
||||||
|
self.d.infobox.assert_called_once_with('123456\n7', mock.ANY, mock.ANY)
|
||||||
|
|
||||||
|
def test_wraps_at_whitespace(self):
|
||||||
|
assert len('123 567') > self.handler.width
|
||||||
|
self.handler.emit(mock.MagicMock(msg='123 567'))
|
||||||
|
self.d.infobox.assert_called_once_with('123\n567', mock.ANY, mock.ANY)
|
||||||
|
|
||||||
|
def test_only_last_lines_are_printed(self):
|
||||||
|
assert len('a\nb\nc'.split()) > self.handler.height
|
||||||
|
self.handler.emit(mock.MagicMock(msg='a\n\nb\nc'))
|
||||||
|
self.d.infobox.assert_called_once_with('b\nc', mock.ANY, mock.ANY)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
"""Parse command line and call the appropriate functions."""
|
"""Parse command line and call the appropriate functions."""
|
||||||
import argparse
|
import argparse
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@@ -8,10 +9,7 @@ from letsencrypt.client import apache_configurator
|
|||||||
from letsencrypt.client import CONFIG
|
from letsencrypt.client import CONFIG
|
||||||
from letsencrypt.client import client
|
from letsencrypt.client import client
|
||||||
from letsencrypt.client import display
|
from letsencrypt.client import display
|
||||||
from letsencrypt.client import logger
|
from letsencrypt.client import log
|
||||||
|
|
||||||
logger.setLogger(logger.FileLogger(sys.stdout))
|
|
||||||
logger.setLogLevel(logger.INFO)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -55,20 +53,26 @@ def main():
|
|||||||
"HTTP and HTTPS.")
|
"HTTP and HTTPS.")
|
||||||
parser.add_argument("-e", "--agree-eula", dest="eula", action="store_true",
|
parser.add_argument("-e", "--agree-eula", dest="eula", action="store_true",
|
||||||
help="Skip the end user license agreement screen.")
|
help="Skip the end user license agreement screen.")
|
||||||
parser.add_argument("-t", "--text", dest="curses", action="store_false",
|
parser.add_argument("-t", "--text", dest="use_curses", action="store_false",
|
||||||
help="Use the text output instead of the curses UI.")
|
help="Use the text output instead of the curses UI.")
|
||||||
parser.add_argument("--test", dest="test", action="store_true",
|
parser.add_argument("--test", dest="test", action="store_true",
|
||||||
help="Run in test mode.")
|
help="Run in test mode.")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Set up logging
|
||||||
|
logger = logging.getLogger()
|
||||||
|
logger.setLevel(logging.INFO) # TODO: --log
|
||||||
|
if args.use_curses:
|
||||||
|
logger.addHandler(log.DialogHandler())
|
||||||
|
|
||||||
# Enforce '--privkey' is set along with '--csr'.
|
# Enforce '--privkey' is set along with '--csr'.
|
||||||
if args.csr and not args.privkey:
|
if args.csr and not args.privkey:
|
||||||
parser.error("private key file (--privkey) must be specified along{0} "
|
parser.error("private key file (--privkey) must be specified along{0} "
|
||||||
"with the certificate signing request file (--csr)"
|
"with the certificate signing request file (--csr)"
|
||||||
.format(os.linesep))
|
.format(os.linesep))
|
||||||
|
|
||||||
if args.curses:
|
if args.use_curses:
|
||||||
display.set_display(display.NcursesDisplay())
|
display.set_display(display.NcursesDisplay())
|
||||||
else:
|
else:
|
||||||
display.set_display(display.FileDisplay(sys.stdout))
|
display.set_display(display.FileDisplay(sys.stdout))
|
||||||
@@ -93,7 +97,7 @@ def main():
|
|||||||
else:
|
else:
|
||||||
csr = client.Client.CSR(args.csr[0], args.csr[1], "pem")
|
csr = client.Client.CSR(args.csr[0], args.csr[1], "pem")
|
||||||
|
|
||||||
acme = client.Client(server, csr, privkey, args.curses)
|
acme = client.Client(server, csr, privkey, args.use_curses)
|
||||||
if args.revoke:
|
if args.revoke:
|
||||||
acme.list_certs_keys()
|
acme.list_certs_keys()
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user