#!/usr/bin/env python """Parse command line and call the appropriate functions. .. todo:: Sanity check all input. Be sure to avoid shell code etc... """ import argparse import logging import os import pkg_resources import sys import confargparse import zope.component import letsencrypt from letsencrypt.client import configuration from letsencrypt.client import client from letsencrypt.client import errors from letsencrypt.client import interfaces from letsencrypt.client import le_util from letsencrypt.client import log from letsencrypt.client import standalone_authenticator as standalone from letsencrypt.client.apache import configurator from letsencrypt.client.display import util as display_util from letsencrypt.client.display import ops as display_ops def create_parser(): """Create parser.""" parser = confargparse.ConfArgParser( description="letsencrypt client %s" % letsencrypt.__version__) add = parser.add_argument config_help = lambda name: interfaces.IConfig[name].__doc__ add("-d", "--domains", metavar="DOMAIN", nargs="+") add("-s", "--server", default="letsencrypt-demo.org:443", help=config_help("server")) add("-k", "--authkey", type=read_file, help="Path to the authorized key file") add("-B", "--rsa-key-size", type=int, default=2048, metavar="N", help=config_help("rsa_key_size")) add("-R", "--revoke", action="store_true", help="Revoke a certificate from a menu.") add("--revoke-certificate", dest="rev_cert", type=read_file, help="Revoke a specific certificate.") add("--revoke-key", dest="rev_key", type=read_file, help="Revoke all certs generated by the provided authorized key.") add("-b", "--rollback", type=int, default=0, metavar="N", help="Revert configuration N number of checkpoints.") add("-v", "--view-config-changes", action="store_true", help="View checkpoints and associated configuration changes.") # TODO: resolve - assumes binary logic while client.py assumes ternary. add("-r", "--redirect", action="store_true", help="Automatically redirect all HTTP traffic to HTTPS for the newly " "authenticated vhost.") add("--no-confirm", dest="no_confirm", action="store_true", help="Turn off confirmation screens, currently used for --revoke") add("-e", "--agree-tos", dest="eula", action="store_true", help="Skip the end user license agreement screen.") add("-t", "--text", dest="use_curses", action="store_false", help="Use the text output instead of the curses UI.") add("--config-dir", default="/etc/letsencrypt", help=config_help("config_dir")) add("--work-dir", default="/var/lib/letsencrypt", help=config_help("work_dir")) add("--backup-dir", default="/var/lib/letsencrypt/backups", help=config_help("backup_dir")) add("--key-dir", default="/etc/letsencrypt/keys", help=config_help("key_dir")) add("--cert-dir", default="/etc/letsencrypt/certs", help=config_help("cert_dir")) add("--le-vhost-ext", default="-le-ssl.conf", help=config_help("le_vhost_ext")) add("--cert-path", default="/etc/letsencrypt/certs/cert-letsencrypt.pem", help=config_help("cert_path")) add("--chain-path", default="/etc/letsencrypt/certs/chain-letsencrypt.pem", help=config_help("chain_path")) add("--apache-server-root", default="/etc/apache2", help=config_help("apache_server_root")) add("--apache-mod-ssl-conf", default="/etc/letsencrypt/options-ssl.conf", help=config_help("apache_mod_ssl_conf")) add("--apache-ctl", default="apache2ctl", help=config_help("apache_ctl")) add("--apache-enmod", default="a2enmod", help=config_help("apache_enmod")) add("--apache-init-script", default="/etc/init.d/apache2", help=config_help("apache_init_script")) return parser def main(): # pylint: disable=too-many-branches, too-many-statements """Command line argument parsing and main script execution.""" # note: arg parser internally handles --help (and exits afterwards) args = create_parser().parse_args() config = configuration.NamespaceConfig(args) # note: check is done after arg parsing as --help should work w/o root also. if not os.geteuid() == 0: sys.exit( "{0}Root is required to run letsencrypt. Please use sudo.{0}" .format(os.linesep)) # Set up logging logger = logging.getLogger() logger.setLevel(logging.INFO) if args.use_curses: logger.addHandler(log.DialogHandler()) displayer = display_util.NcursesDisplay() else: displayer = display_util.FileDisplay(sys.stdout) zope.component.provideUtility(displayer) if args.view_config_changes: client.view_config_changes(config) sys.exit() if args.revoke or args.rev_cert is not None or args.rev_key is not None: client.revoke(config, args.no_confirm, args.rev_cert, args.rev_key) sys.exit() if args.rollback > 0: client.rollback(args.rollback, config) sys.exit() if not args.eula: display_eula() all_auths = [ configurator.ApacheConfigurator(config), standalone.StandaloneAuthenticator(), ] try: auth = client.determine_authenticator(all_auths) except errors.LetsEncryptClientError: logging.critical("No authentication mechanisms were found on your " "system.") sys.exit(1) if auth is None: sys.exit(0) # Use the same object if possible if interfaces.IInstaller.providedBy(auth): # pylint: disable=no-member installer = auth else: # This is simple and avoids confusion right now. installer = None if args.domains is None: doms = display_ops.choose_names(installer) else: doms = args.domains if not doms: sys.exit(0) # Prepare for init of Client if args.authkey is None: authkey = client.init_key(args.rsa_key_size, config.key_dir) else: authkey = le_util.Key(args.authkey[0], args.authkey[1]) acme = client.Client(config, authkey, auth, installer) # Validate the key and csr client.validate_key_csr(authkey) # This more closely mimics the capabilities of the CLI # It should be possible for reconfig only, install-only, no-install # I am not sure the best way to handle all of the unimplemented abilities, # but this code should be safe on all environments. cert_file = None if auth is not None: cert_file, chain_file = acme.obtain_certificate(doms) if installer is not None and cert_file is not None: acme.deploy_certificate(doms, authkey, cert_file, chain_file) if installer is not None: acme.enhance_config(doms, args.redirect) def display_eula(): """Displays the end user agreement.""" eula = pkg_resources.resource_string("letsencrypt", "EULA") if not zope.component.getUtility(interfaces.IDisplay).yesno( eula, "Agree", "Cancel"): sys.exit(0) def read_file(filename): """Returns the given file's contents with universal new line support. :param str filename: Filename :returns: A tuple of filename and its contents :rtype: tuple :raises argparse.ArgumentTypeError: File does not exist or is not readable. """ try: return filename, open(filename, "rU").read() except IOError as exc: raise argparse.ArgumentTypeError(exc.strerror) if __name__ == "__main__": main()