certbot/letsencrypt/scripts/main.py

215 lines
7 KiB
Python
Raw Normal View History

#!/usr/bin/env python
2014-11-21 15:06:13 -05:00
"""Parse command line and call the appropriate functions."""
import argparse
import logging
import os
import sys
2014-12-17 06:02:15 -05:00
import zope.component
2015-01-24 08:12:45 -05:00
import zope.interface
2014-12-17 06:02:15 -05:00
2014-11-21 15:06:13 -05:00
from letsencrypt.client import CONFIG
from letsencrypt.client import client
from letsencrypt.client import display
2014-12-17 09:27:21 -05:00
from letsencrypt.client import interfaces
from letsencrypt.client import log
2014-12-21 06:37:29 -05:00
from letsencrypt.client import revoker
2014-12-19 18:49:29 -05:00
from letsencrypt.client.apache import configurator
2015-01-24 08:12:45 -05:00
def main(): # pylint: disable=too-many-statements
2014-11-26 21:30:42 -05:00
"""Command line argument parsing and main script execution."""
if not os.geteuid() == 0:
2014-11-26 21:30:42 -05:00
sys.exit(
"{0}Root is required to run letsencrypt. Please use sudo.{0}"
.format(os.linesep))
parser = argparse.ArgumentParser(
description="An ACME client that can update Apache configurations.")
parser.add_argument("-d", "--domains", dest="domains", metavar="DOMAIN",
nargs="+")
parser.add_argument("-s", "--server", dest="server",
help="The ACME CA server address.")
parser.add_argument("-p", "--privkey", dest="privkey", type=read_file,
2014-11-26 21:30:42 -05:00
help="Path to the private key file for certificate "
"generation.")
parser.add_argument("-b", "--rollback", dest="rollback", type=int,
default=0, metavar="N",
help="Revert configuration N number of checkpoints.")
2014-11-26 21:30:42 -05:00
parser.add_argument("-k", "--revoke", dest="revoke", action="store_true",
help="Revoke a certificate.")
parser.add_argument("-v", "--view-config-changes",
dest="view_config_changes",
2014-11-26 21:30:42 -05:00
action="store_true",
help="View checkpoints and associated configuration "
"changes.")
parser.add_argument("-r", "--redirect", dest="redirect",
action="store_const", const=True,
help="Automatically redirect all HTTP traffic to HTTPS "
"for the newly authenticated vhost.")
parser.add_argument("-n", "--no-redirect", dest="redirect",
action="store_const", const=False,
help="Skip the HTTPS redirect question, allowing both "
"HTTP and HTTPS.")
parser.add_argument("-e", "--agree-tos", dest="eula", action="store_true",
2014-11-26 21:30:42 -05:00
help="Skip the end user license agreement screen.")
parser.add_argument("-t", "--text", dest="use_curses", action="store_false",
2014-11-26 21:30:42 -05:00
help="Use the text output instead of the curses UI.")
parser.add_argument("--test", dest="test", action="store_true",
help="Run in test mode.")
args = parser.parse_args()
# Set up logging
logger = logging.getLogger()
2014-12-21 06:37:29 -05:00
logger.setLevel(logging.INFO)
if args.use_curses:
logger.addHandler(log.DialogHandler())
2014-12-17 06:02:15 -05:00
displayer = display.NcursesDisplay()
else:
2014-12-17 06:02:15 -05:00
displayer = display.FileDisplay(sys.stdout)
zope.component.provideUtility(displayer)
2014-12-21 06:37:29 -05:00
installer = determine_installer()
2014-12-21 19:32:01 -05:00
server = CONFIG.ACME_SERVER if args.server is None else args.server
2014-12-21 06:37:29 -05:00
if args.revoke:
revoc = revoker.Revoker(server, installer)
revoc.list_certs_keys()
sys.exit()
if args.rollback > 0:
2014-12-21 06:37:29 -05:00
rollback(installer, args.rollback)
sys.exit()
if args.view_config_changes:
view_config_changes(installer)
sys.exit()
if not args.eula:
display_eula()
2014-12-21 06:37:29 -05:00
# Use the same object if possible
if interfaces.IAuthenticator.providedBy(installer):
auth = installer
else:
auth = determine_authenticator()
2014-11-25 03:43:18 -05:00
2014-12-17 09:27:21 -05:00
domains = choose_names(installer) if args.domains is None else args.domains
# Prepare for init of Client
if args.privkey is None:
2014-12-17 09:27:21 -05:00
privkey = client.init_key()
else:
privkey = client.Client.Key(args.privkey[0], args.privkey[1])
acme = client.Client(server, privkey, auth, installer)
2014-12-17 09:27:21 -05:00
2014-12-21 06:37:29 -05:00
# Validate the key and csr
client.validate_key_csr(privkey)
2014-12-21 06:37:29 -05:00
cert_file, chain_file = acme.obtain_certificate(domains)
acme.deploy_certificate(domains, privkey, cert_file, chain_file)
acme.enhance_config(domains, args.redirect)
2014-12-17 09:27:21 -05:00
def display_eula():
"""Displays the end user agreement."""
with open('EULA') as eula_file:
2014-12-22 00:41:12 -05:00
if not zope.component.getUtility(interfaces.IDisplay).generic_yesno(
2014-12-17 09:27:21 -05:00
eula_file.read(), "Agree", "Cancel"):
sys.exit(0)
def choose_names(installer):
"""Display screen to select domains to validate.
:param installer: An installer object
:type installer: :class:`letsencrypt.client.interfaces.IInstaller`
"""
# This function adds all names
# found within the config to self.names
# Then filters them based on user selection
2014-12-22 00:41:12 -05:00
code, names = zope.component.getUtility(
interfaces.IDisplay).filter_names(get_all_names(installer))
2014-12-17 09:27:21 -05:00
if code == display.OK and names:
return names
else:
2014-12-17 09:27:21 -05:00
sys.exit(0)
def get_all_names(installer):
"""Return all valid names in the configuration.
:param installer: An installer object
:type installer: :class:`letsencrypt.client.interfaces.IInstaller`
"""
names = list(installer.get_all_names())
client.sanity_check_names(names)
if not names:
logging.fatal("No domain names were found in your installation")
logging.fatal("Either specify which names you would like "
"letsencrypt to validate or add server names "
"to your virtual hosts")
sys.exit(1)
return names
# This should be controlled by commandline parameters
def determine_authenticator():
"""Returns a valid IAuthenticator."""
2015-01-24 08:12:45 -05:00
return configurator.ApacheConfigurator()
2014-12-17 09:27:21 -05:00
def determine_installer():
2015-01-24 08:12:45 -05:00
"""Returns a valid IInstaller."""
return configurator.ApacheConfigurator()
2014-11-26 21:30:42 -05:00
def read_file(filename):
"""Returns the given file's contents with universal new line support.
2014-11-29 21:07:52 -05:00
:param str filename: Filename
2014-11-30 07:10:23 -05:00
:returns: A tuple of filename and its contents
:rtype: tuple
:raises argparse.ArgumentTypeError: File does not exist or is not readable.
"""
try:
2014-12-17 03:14:27 -05:00
return filename, open(filename, 'rU').read()
except IOError as exc:
raise argparse.ArgumentTypeError(exc.strerror)
def rollback(installer, checkpoints):
2014-11-26 21:30:42 -05:00
"""Revert configuration the specified number of checkpoints.
:param installer: Installer object
:type installer: :class:`letsencrypt.client.interfaces.IInstaller`
2014-11-29 21:07:52 -05:00
:param int checkpoints: Number of checkpoints to revert.
2014-11-26 21:30:42 -05:00
"""
installer.rollback_checkpoints(checkpoints)
installer.restart()
2014-11-26 21:30:42 -05:00
def view_config_changes(installer):
"""View checkpoints and associated configuration changes.
:param installer: Installer object
:type installer: :class:`letsencrypt.client.interfaces.IInstaller`
"""
installer.view_config_changes()
2014-11-26 21:30:42 -05:00
if __name__ == "__main__":
main()