<commit_before>"""Certbot main entry point."""
# pylint: disable=too-many-lines

from contextlib import contextmanager
import functools
import logging.handlers
import sys
from typing import Generator
from typing import IO
from typing import Iterable
from typing import List
from typing import Optional
from typing import Tuple
from typing import Union

import configobj
import josepy as jose
import zope.component
import zope.interface

from acme import errors as acme_errors
import certbot
from certbot import configuration
from certbot import crypto_util
from certbot import errors
from certbot import interfaces
from certbot import util
from certbot._internal import account
from certbot._internal import cert_manager
from certbot._internal import cli
from certbot._internal import client
from certbot._internal import constants
from certbot._internal import eff
from certbot._internal import hooks
from certbot._internal import log
from certbot._internal import renewal
from certbot._internal import reporter
from certbot._internal import snap_config
from certbot._internal import storage
from certbot._internal import updater
from certbot._internal.display import obj as display_obj
from certbot._internal.display import util as internal_display_util
from certbot._internal.plugins import disco as plugins_disco
from certbot._internal.plugins import selection as plug_sel
from certbot.compat import filesystem
from certbot.compat import misc
from certbot.compat import os
from certbot.display import ops as display_ops
from certbot.display import util as display_util
from certbot.plugins import enhancements

USER_CANCELLED = ("User chose to cancel the operation and may "
                  "reinvoke the client.")


logger = logging.getLogger(__name__)


def _suggest_donation_if_appropriate(config):
    """Potentially suggest a donation to support Certbot.

    :param config: Configuration object
    :type config: configuration.NamespaceConfig

    :returns: `None`
    :rtype: None

    """
    # don't prompt for donation if:
    # - renewing
    # - using the staging server (--staging or --dry-run)
    # - running with --quiet (display fd won't be available during atexit calls #8995)
    assert config.verb != "renew"
    if config.staging or config.quiet:
        return
    util.atexit_register(
        display_util.notification,
        "If you like Certbot, please consider supporting our work by:\n"
        " * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate\n"
        " * Donating to EFF:                    https://eff.org/donate-le",
        pause=False
    )


def _get_and_save_cert(le_client, config, domains=None, certname=None, lineage=None):
    """Authenticate and enroll certificate.

    This method finds the relevant lineage, figures out what to do with it,
    then performs that action. Includes calls to hooks, various reports,
    checks, and requests for user input.

    :param config: Configuration object
    :type config: configuration.NamespaceConfig

    :param domains: List of domain names to get a certificate. Defaults to `None`
    :type domains: `list` of `str`

    :param certname: Name of new certificate. Defaults to `None`
    :type certname: str

    :param lineage: Certificate lineage object. Defaults to `None`
    :type lineage: storage.RenewableCert

    :returns: the issued certificate or `None` if doing a dry run
    :rtype: storage.RenewableCert or None

    :raises errors.Error: if certificate could not be obtained

    """
    hooks.pre_hook(config)
    try:
        if lineage is not None:
            # Renewal, where we already know the specific lineage we're
            # interested in
            display_util.notify(
                "{action} for {domains}".format(
                    action="Simulating renewal of an existing certificate"
                            if config.dry_run else "Renewing an existing certificate",
                    domains=internal_display_util.summarize_domain_list(domains or lineage.names())
                )
            )
            renewal.renew_cert(config, domains, le_client, lineage)
        else:
            # TREAT AS NEW REQUEST
            assert domains is not None
            display_util.notify(
                "{action} for {domains}".format(
                    action="Simulating a certificate request" if config.dry_run else
                           "Requesting a certificate",
                    domains=internal_display_util.summarize_domain_list(domains)
                )
            )
            lineage = le_client.obtain_and_enroll_certificate(domains, certname)
            if lineage is False:
                raise errors.Error("Certificate could not be obtained")
            if lineage is not None:
                hooks.deploy_hook(config, lineage.names(), lineage.live_dir)
    finally:
        hooks.post_hook(config)

    return lineage


def _handle_unexpected_key_type_migration(config: configuration.NamespaceConfig,
                                          cert: storage.RenewableCert) -> None:
    """
    This function ensures that the user will not implicitly migrate an existing key
    from one type to another in the situation where a certificate for that lineage
    already exist and they have not provided explicitly --key-type and --cert-name.
    :param config: Current configuration provided by the client
    :param cert: Matching certificate that could be renewed
    """
    if not cli.set_by_cli("key_type") or not cli.set_by_cli("certname"):

        new_key_type = config.key_type.upper()
        cur_key_type = cert.private_key_type.upper()

        if new_key_type != cur_key_type:
            msg = ('Are you trying to change the key type of the certificate named {0} '
                   'from {1} to {2}? Please provide both --cert-name and --key-type on '
                   'the command line confirm the change you are trying to make.')
            msg = msg.format(cert.lineagename, cur_key_type, new_key_type)
            raise errors.Error(msg)


def _handle_subset_cert_request(config: configuration.NamespaceConfig,
                                domains: List[str],
                                cert: storage.RenewableCert
                                ) -> Tuple[str, Optional[storage.RenewableCert]]:
    """Figure out what to do if a previous cert had a subset of the names now requested

    :param config: Configuration object
    :type config: configuration.NamespaceConfig

    :param domains: List of domain names
    :type domains: `list` of `str`

    :param cert: Certificate object
    :type cert: storage.RenewableCert

    :returns: Tuple of (str action, cert_or_None) as per _find_lineage_for_domains_and_certname
              action can be: "newcert" | "renew" | "reinstall"
    :rtype: `tuple` of `str`
<commit_msg>fix typo in error message (#9047)<commit_after>"""Certbot main entry point."""
# pylint: disable=too-many-lines

from contextlib import contextmanager
import functools
import logging.handlers
import sys
from typing import Generator
from typing import IO
from typing import Iterable
from typing import List
from typing import Optional
from typing import Tuple
from typing import Union

import configobj
import josepy as jose
import zope.component
import zope.interface

from acme import errors as acme_errors
import certbot
from certbot import configuration
from certbot import crypto_util
from certbot import errors
from certbot import interfaces
from certbot import util
from certbot._internal import account
from certbot._internal import cert_manager
from certbot._internal import cli
from certbot._internal import client
from certbot._internal import constants
from certbot._internal import eff
from certbot._internal import hooks
from certbot._internal import log
from certbot._internal import renewal
from certbot._internal import reporter
from certbot._internal import snap_config
from certbot._internal import storage
from certbot._internal import updater
from certbot._internal.display import obj as display_obj
from certbot._internal.display import util as internal_display_util
from certbot._internal.plugins import disco as plugins_disco
from certbot._internal.plugins import selection as plug_sel
from certbot.compat import filesystem
from certbot.compat import misc
from certbot.compat import os
from certbot.display import ops as display_ops
from certbot.display import util as display_util
from certbot.plugins import enhancements

USER_CANCELLED = ("User chose to cancel the operation and may "
                  "reinvoke the client.")


logger = logging.getLogger(__name__)


def _suggest_donation_if_appropriate(config):
    """Potentially suggest a donation to support Certbot.

    :param config: Configuration object
    :type config: configuration.NamespaceConfig

    :returns: `None`
    :rtype: None

    """
    # don't prompt for donation if:
    # - renewing
    # - using the staging server (--staging or --dry-run)
    # - running with --quiet (display fd won't be available during atexit calls #8995)
    assert config.verb != "renew"
    if config.staging or config.quiet:
        return
    util.atexit_register(
        display_util.notification,
        "If you like Certbot, please consider supporting our work by:\n"
        " * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate\n"
        " * Donating to EFF:                    https://eff.org/donate-le",
        pause=False
    )


def _get_and_save_cert(le_client, config, domains=None, certname=None, lineage=None):
    """Authenticate and enroll certificate.

    This method finds the relevant lineage, figures out what to do with it,
    then performs that action. Includes calls to hooks, various reports,
    checks, and requests for user input.

    :param config: Configuration object
    :type config: configuration.NamespaceConfig

    :param domains: List of domain names to get a certificate. Defaults to `None`
    :type domains: `list` of `str`

    :param certname: Name of new certificate. Defaults to `None`
    :type certname: str

    :param lineage: Certificate lineage object. Defaults to `None`
    :type lineage: storage.RenewableCert

    :returns: the issued certificate or `None` if doing a dry run
    :rtype: storage.RenewableCert or None

    :raises errors.Error: if certificate could not be obtained

    """
    hooks.pre_hook(config)
    try:
        if lineage is not None:
            # Renewal, where we already know the specific lineage we're
            # interested in
            display_util.notify(
                "{action} for {domains}".format(
                    action="Simulating renewal of an existing certificate"
                            if config.dry_run else "Renewing an existing certificate",
                    domains=internal_display_util.summarize_domain_list(domains or lineage.names())
                )
            )
            renewal.renew_cert(config, domains, le_client, lineage)
        else:
            # TREAT AS NEW REQUEST
            assert domains is not None
            display_util.notify(
                "{action} for {domains}".format(
                    action="Simulating a certificate request" if config.dry_run else
                           "Requesting a certificate",
                    domains=internal_display_util.summarize_domain_list(domains)
                )
            )
            lineage = le_client.obtain_and_enroll_certificate(domains, certname)
            if lineage is False:
                raise errors.Error("Certificate could not be obtained")
            if lineage is not None:
                hooks.deploy_hook(config, lineage.names(), lineage.live_dir)
    finally:
        hooks.post_hook(config)

    return lineage


def _handle_unexpected_key_type_migration(config: configuration.NamespaceConfig,
                                          cert: storage.RenewableCert) -> None:
    """
    This function ensures that the user will not implicitly migrate an existing key
    from one type to another in the situation where a certificate for that lineage
    already exist and they have not provided explicitly --key-type and --cert-name.
    :param config: Current configuration provided by the client
    :param cert: Matching certificate that could be renewed
    """
    if not cli.set_by_cli("key_type") or not cli.set_by_cli("certname"):

        new_key_type = config.key_type.upper()
        cur_key_type = cert.private_key_type.upper()

        if new_key_type != cur_key_type:
            msg = ('Are you trying to change the key type of the certificate named {0} '
                   'from {1} to {2}? Please provide both --cert-name and --key-type on '
                   'the command line to confirm the change you are trying to make.')
            msg = msg.format(cert.lineagename, cur_key_type, new_key_type)
            raise errors.Error(msg)


def _handle_subset_cert_request(config: configuration.NamespaceConfig,
                                domains: List[str],
                                cert: storage.RenewableCert
                                ) -> Tuple[str, Optional[storage.RenewableCert]]:
    """Figure out what to do if a previous cert had a subset of the names now requested

    :param config: Configuration object
    :type config: configuration.NamespaceConfig

    :param domains: List of domain names
    :type domains: `list` of `str`

    :param cert: Certificate object
    :type cert: storage.RenewableCert

    :returns: Tuple of (str action, cert_or_None) as per _find_lineage_for_domains_and_certname
              action can be: "newcert" | "renew" | "reinstall"
    :rtype: `tuple` of `str`

{'fraction_non_alphanumeric': 0.0647345809364305, 'fraction_numerical': 0.0013107114250345882, 'mean_word_length': 2.807596340449127, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 7, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2115710', 'n_tokens_mistral': 3684, 'n_tokens_neox': 3483, 'n_words': 1332}