[
  {
    "path": ".gitignore",
    "content": ".pc/"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Certbot change log\n\nCertbot adheres to [Semantic Versioning](https://semver.org/).\n\n## 1.3.0 - 2020-03-03\n\n### Added\n\n* Added certbot.ocsp Certbot's API. The certbot.ocsp module can be used to \n  determine the OCSP status of certificates.\n* Don't verify the existing certificate in HTTP01Response.simple_verify, for \n  compatibility with the real-world ACME challenge checks.\n\n### Changed\n\n* Certbot will now renew certificates early if they have been revoked according\n  to OCSP.\n* Fix acme module warnings when response Content-Type includes params (e.g. charset).\n* Fixed issue where webroot plugin would incorrectly raise `Read-only file system` \n  error when creating challenge directories (issue #7165).\n\n### Fixed\n\n*\n\nMore details about these changes can be found on our GitHub repo.\n\n## 1.2.0 - 2020-02-04\n\n### Added\n\n* Added support for Cloudflare's limited-scope API Tokens\n* Added support for `$hostname` in nginx `server_name` directive\n\n### Changed\n\n* Add directory field to error message when field is missing.\n* If MD5 hasher is not available, try it in non-security mode (fix for FIPS systems) -- [#1948](https://github.com/certbot/certbot/issues/1948)\n* Disable old SSL versions and ciphersuites and remove `SSLCompression off` setting to follow Mozilla recommendations in Apache.\n* Remove ECDHE-RSA-AES128-SHA from NGINX ciphers list now that Windows 2008 R2 and Windows 7 are EOLed\n* Support for Python 3.4 has been removed.\n\n### Fixed\n\n* Fix collections.abc imports for Python 3.9.\n* Fix Apache plugin to use less restrictive umask for making the challenge directory when a restrictive umask was set when certbot was started.\n\nMore details about these changes can be found on our GitHub repo.\n\n## 1.1.0 - 2020-01-14\n\n### Added\n\n*\n\n### Changed\n\n* Removed the fallback introduced with 0.34.0 in `acme` to retry a POST-as-GET\n  request as a GET request when the targeted ACME CA server seems to not support\n  POST-as-GET requests.\n* certbot-auto no longer supports architectures other than x86_64 on RHEL 6\n  based systems. Existing certbot-auto installations affected by this will\n  continue to work, but they will no longer receive updates. To install a\n  newer version of Certbot on these systems, you should update your OS.\n* Support for Python 3.4 in Certbot and its ACME library is deprecated and will be\n  removed in the next release of Certbot. certbot-auto users on x86_64 systems running\n  RHEL 6 or derivatives will be asked to enable Software Collections (SCL) repository\n  so Python 3.6 can be installed. certbot-auto can enable the SCL repo for you on CentOS 6\n  while users on other RHEL 6 based systems will be asked to do this manually.\n\n### Fixed\n\n*\n\nMore details about these changes can be found on our GitHub repo.\n\n## 1.0.0 - 2019-12-03\n\n### Added\n\n*\n\n### Removed\n\n* The `docs` extras for the `certbot-apache` and `certbot-nginx` packages\n  have been removed.\n\n### Changed\n\n* certbot-auto has deprecated support for systems using OpenSSL 1.0.1 that are\n  not running on x86-64. This primarily affects RHEL 6 based systems.\n* Certbot's `config_changes` subcommand has been removed\n* `certbot.plugins.common.TLSSNI01` has been removed.\n* Deprecated attributes related to the TLS-SNI-01 challenge in\n  `acme.challenges` and `acme.standalone`\n  have been removed.\n* The functions `certbot.client.view_config_changes`,\n  `certbot.main.config_changes`,\n  `certbot.plugins.common.Installer.view_config_changes`,\n  `certbot.reverter.Reverter.view_config_changes`, and\n  `certbot.util.get_systemd_os_info` have been removed\n* Certbot's `register --update-registration` subcommand has been removed\n* When possible, default to automatically configuring the webserver so all requests\n  redirect to secure HTTPS access. This is mostly relevant when running Certbot\n  in non-interactive mode. Previously, the default was to not redirect all requests.\n\n### Fixed\n\n*\n\nMore details about these changes can be found on our GitHub repo.\n\n## 0.40.1 - 2019-11-05\n\n### Changed\n\n* Added back support for Python 3.4 to Certbot components and certbot-auto due\n  to a bug when requiring Python 2.7 or 3.5+ on RHEL 6 based systems.\n\nMore details about these changes can be found on our GitHub repo.\n\n## 0.40.0 - 2019-11-05\n\n### Added\n\n*\n\n### Changed\n\n* We deprecated support for Python 3.4 in Certbot and its ACME library. Support\n  for Python 3.4 will be removed in the next major release of Certbot.\n  certbot-auto users on RHEL 6 based systems will be asked to enable Software\n  Collections (SCL) repository so Python 3.6 can be installed. certbot-auto can\n  enable the SCL repo for you on CentOS 6 while users on other RHEL 6 based\n  systems will be asked to do this manually.\n* `--server` may now be combined with `--dry-run`. Certbot will, as before, use the\n  staging server instead of the live server when `--dry-run` is used.\n* `--dry-run` now requests fresh authorizations every time, fixing the issue\n  where it was prone to falsely reporting success.\n* Updated certbot-dns-google to depend on newer versions of\n  google-api-python-client and oauth2client.\n* The OS detection logic again uses distro library for Linux OSes\n* certbot.plugins.common.TLSSNI01 has been deprecated and will be removed in a\n  future release.\n* CLI flags --tls-sni-01-port and --tls-sni-01-address have been removed.\n* The values tls-sni and tls-sni-01 for the --preferred-challenges flag are no\n  longer accepted.\n* Removed the flags: `--agree-dev-preview`, `--dialog`, and `--apache-init-script`\n* acme.standalone.BaseRequestHandlerWithLogging and\n  acme.standalone.simple_tls_sni_01_server have been deprecated and will be\n  removed in a future release of the library.\n* certbot-dns-rfc2136 now use TCP to query SOA records.\n\n### Fixed\n\n*\n\nMore details about these changes can be found on our GitHub repo.\n\n## 0.39.0 - 2019-10-01\n\n### Added\n\n* Support for Python 3.8 was added to Certbot and all of its components.\n* Support for CentOS 8 was added to certbot-auto.\n\n### Changed\n\n* Don't send OCSP requests for expired certificates\n* Return to using platform.linux_distribution instead of distro.linux_distribution in OS fingerprinting for Python < 3.8\n* Updated the Nginx plugin's TLS configuration to keep support for some versions of IE11.\n\n### Fixed\n\n* Fixed OS detection in the Apache plugin on RHEL 6.\n\nMore details about these changes can be found on our GitHub repo.\n\n## 0.38.0 - 2019-09-03\n\n### Added\n\n* Disable session tickets for Nginx users when appropriate.\n\n### Changed\n\n* If Certbot fails to rollback your server configuration, the error message\n  links to the Let's Encrypt forum. Change the link to the Help category now\n  that the Server category has been closed.\n* Replace platform.linux_distribution with distro.linux_distribution as a step\n  towards Python 3.8 support in Certbot.\n\n### Fixed\n\n* Fixed OS detection in the Apache plugin on Scientific Linux.\n\nMore details about these changes can be found on our GitHub repo.\n\n## 0.37.2 - 2019-08-21\n\n* Stop disabling TLS session tickets in Nginx as it caused TLS failures on\n  some systems.\n\nMore details about these changes can be found on our GitHub repo.\n\n## 0.37.1 - 2019-08-08\n\n### Fixed\n\n* Stop disabling TLS session tickets in Apache as it caused TLS failures on\n  some systems.\n\nMore details about these changes can be found on our GitHub repo.\n\n## 0.37.0 - 2019-08-07\n\n### Added\n\n* Turn off session tickets for apache plugin by default\n* acme: Authz deactivation added to `acme` module.\n\n### Changed\n\n* Follow updated Mozilla recommendations for Nginx ssl_protocols, ssl_ciphers,\n  and ssl_prefer_server_ciphers\n\n### Fixed\n\n* Fix certbot-auto failures on RHEL 8.\n\nMore details about these changes can be found on our GitHub repo.\n\n## 0.36.0 - 2019-07-11\n\n### Added\n\n* Turn off session tickets for nginx plugin by default\n* Added missing error types from RFC8555 to acme\n\n### Changed\n\n* Support for Ubuntu 14.04 Trusty has been removed.\n* Update the 'manage your account' help to be more generic.\n* The error message when Certbot's Apache plugin is unable to modify your\n  Apache configuration has been improved.\n* Certbot's config_changes subcommand has been deprecated and will be\n  removed in a future release.\n* `certbot config_changes` no longer accepts a --num parameter.\n* The functions `certbot.plugins.common.Installer.view_config_changes` and\n  `certbot.reverter.Reverter.view_config_changes` have been deprecated and will\n  be removed in a future release.\n\n### Fixed\n\n* Replace some unnecessary platform-specific line separation.\n\nMore details about these changes can be found on our GitHub repo.\n\n## 0.35.1 - 2019-06-10\n\n### Fixed\n\n* Support for specifying an authoritative base domain in our dns-rfc2136 plugin\n  has been removed. This feature was added in our last release but had a bug\n  which caused the plugin to fail so the feature has been removed until it can\n  be added properly.\n\nDespite us having broken lockstep, we are continuing to release new versions of\nall Certbot components during releases for the time being, however, the only\npackage with changes other than its version number was:\n\n* certbot-dns-rfc2136\n\nMore details about these changes can be found on our GitHub repo.\n\n## 0.35.0 - 2019-06-05\n\n### Added\n\n* dns_rfc2136 plugin now supports explicitly specifying an authoritative\n  base domain for cases when the automatic method does not work (e.g.\n  Split horizon DNS)\n\n### Changed\n\n*\n\n### Fixed\n\n* Renewal parameter `webroot_path` is always saved, avoiding some regressions\n  when `webroot` authenticator plugin is invoked with no challenge to perform.\n* Certbot now accepts OCSP responses when an explicit authorized\n  responder, different from the issuer, is used to sign OCSP\n  responses.\n* Scripts in Certbot hook directories are no longer executed when their\n  filenames end in a tilde.\n\nDespite us having broken lockstep, we are continuing to release new versions of\nall Certbot components during releases for the time being, however, the only\npackage with changes other than its version number was:\n\n* certbot\n* certbot-dns-rfc2136\n\nMore details about these changes can be found on our GitHub repo.\n\n## 0.34.2 - 2019-05-07\n\n### Fixed\n\n* certbot-auto no longer writes a check_permissions.py script at the root\n  of the filesystem.\n\nDespite us having broken lockstep, we are continuing to release new versions of\nall Certbot components during releases for the time being, however, the only\nchanges in this release were to certbot-auto.\n\nMore details about these changes can be found on our GitHub repo.\n\n## 0.34.1 - 2019-05-06\n\n### Fixed\n\n* certbot-auto no longer prints a blank line when there are no permissions\n  problems.\n\nDespite us having broken lockstep, we are continuing to release new versions of\nall Certbot components during releases for the time being, however, the only\nchanges in this release were to certbot-auto.\n\nMore details about these changes can be found on our GitHub repo.\n\n## 0.34.0 - 2019-05-01\n\n### Changed\n\n* Apache plugin now tries to restart httpd on Fedora using systemctl if a\n  configuration test error is detected. This has to be done due to the way\n  Fedora now generates the self signed certificate files upon first\n  restart.\n* Updated Certbot and its plugins to improve the handling of file system permissions\n  on Windows as a step towards adding proper Windows support to Certbot.\n* Updated urllib3 to 1.24.2 in certbot-auto.\n* Removed the fallback introduced with 0.32.0 in `acme` to retry a challenge response\n  with a `keyAuthorization` if sending the response without this field caused a\n  `malformed` error to be received from the ACME server.\n* Linode DNS plugin now supports api keys created from their new panel\n  at [cloud.linode.com](https://cloud.linode.com)\n\n### Fixed\n\n* Fixed Google DNS Challenge issues when private zones exist\n* Adding a warning noting that future versions of Certbot will automatically configure the\n  webserver so that all requests redirect to secure HTTPS access. You can control this\n  behavior and disable this warning with the --redirect and --no-redirect flags.\n* certbot-auto now prints warnings when run as root with insecure file system\n  permissions. If you see these messages, you should fix the problem by\n  following the instructions at\n  https://community.letsencrypt.org/t/certbot-auto-deployment-best-practices/91979/,\n  however, these warnings can be disabled as necessary with the flag\n  --no-permissions-check.\n* `acme` module uses now a POST-as-GET request to retrieve the registration\n  from an ACME v2 server\n* Convert the tsig algorithm specified in the certbot_dns_rfc2136 configuration file to\n  all uppercase letters before validating. This makes the value in the config case\n  insensitive.\n\nDespite us having broken lockstep, we are continuing to release new versions of\nall Certbot components during releases for the time being, however, the only\npackage with changes other than its version number was:\n\n* acme\n* certbot\n* certbot-apache\n* certbot-dns-cloudflare\n* certbot-dns-cloudxns\n* certbot-dns-digitalocean\n* certbot-dns-dnsimple\n* certbot-dns-dnsmadeeasy\n* certbot-dns-gehirn\n* certbot-dns-google\n* certbot-dns-linode\n* certbot-dns-luadns\n* certbot-dns-nsone\n* certbot-dns-ovh\n* certbot-dns-rfc2136\n* certbot-dns-route53\n* certbot-dns-sakuracloud\n* certbot-nginx\n\nMore details about these changes can be found on our GitHub repo.\n\n## 0.33.1 - 2019-04-04\n\n### Fixed\n\n* A bug causing certbot-auto to print warnings or crash on some RHEL based\n  systems has been resolved.\n\nDespite us having broken lockstep, we are continuing to release new versions of\nall Certbot components during releases for the time being, however, the only\nchanges in this release were to certbot-auto.\n\nMore details about these changes can be found on our GitHub repo.\n\n## 0.33.0 - 2019-04-03\n\n### Added\n\n* Fedora 29+ is now supported by certbot-auto. Since Python 2.x is on a deprecation\n  path in Fedora, certbot-auto will install and use Python 3.x on Fedora 29+.\n* CLI flag `--https-port` has been added for Nginx plugin exclusively, and replaces\n  `--tls-sni-01-port`. It defines the HTTPS port the Nginx plugin will use while\n  setting up a new SSL vhost. By default the HTTPS port is 443.\n\n### Changed\n\n* Support for TLS-SNI-01 has been removed from all official Certbot plugins.\n* Attributes related to the TLS-SNI-01 challenge in `acme.challenges` and `acme.standalone`\n  modules are deprecated and will be removed soon.\n* CLI flags `--tls-sni-01-port` and `--tls-sni-01-address` are now no-op, will\n  generate a deprecation warning if used, and will be removed soon.\n* Options `tls-sni` and `tls-sni-01` in `--preferred-challenges` flag are now no-op,\n  will generate a deprecation warning if used, and will be removed soon.\n* CLI flag `--standalone-supported-challenges` has been removed.\n\n### Fixed\n\n* Certbot uses the Python library cryptography for OCSP when cryptography>=2.5\n  is installed. We fixed a bug in Certbot causing it to interpret timestamps in\n  the OCSP response as being in the local timezone rather than UTC.\n* Issue causing the default CentOS 6 TLS configuration to ignore some of the\n  HTTPS VirtualHosts created by Certbot. mod_ssl loading is now moved to main\n  http.conf for this environment where possible.\n\nDespite us having broken lockstep, we are continuing to release new versions of\nall Certbot components during releases for the time being, however, the only\npackage with changes other than its version number was:\n\n* acme\n* certbot\n* certbot-apache\n* certbot-nginx\n\nMore details about these changes can be found on our GitHub repo.\n\n## 0.32.0 - 2019-03-06\n\n### Added\n\n* If possible, Certbot uses built-in support for OCSP from recent cryptography\n  versions instead of the OpenSSL binary: as a consequence Certbot does not need\n  the OpenSSL binary to be installed anymore if cryptography>=2.5 is installed.\n\n### Changed\n\n* Certbot and its acme module now depend on josepy>=1.1.0 to avoid printing the\n  warnings described at https://github.com/certbot/josepy/issues/13.\n* Apache plugin now respects CERTBOT_DOCS environment variable when adding\n  command line defaults.\n* The running of manual plugin hooks is now always included in Certbot's log\n  output.\n* Tests execution for certbot, certbot-apache and certbot-nginx packages now relies on pytest.\n* An ACME CA server may return a \"Retry-After\" HTTP header on authorization polling, as\n  specified in the ACME protocol, to indicate when the next polling should occur. Certbot now\n  reads this header if set and respect its value.\n* The `acme` module avoids sending the `keyAuthorization` field in the JWS\n  payload when responding to a challenge as the field is not included in the\n  current ACME protocol. To ease the migration path for ACME CA servers,\n  Certbot and its `acme` module will first try the request without the\n  `keyAuthorization` field but will temporarily retry the request with the\n  field included if a `malformed` error is received. This fallback will be\n  removed in version 0.34.0.\n\nDespite us having broken lockstep, we are continuing to release new versions of\nall Certbot components during releases for the time being, however, the only\npackage with changes other than its version number was:\n\n* acme\n* certbot\n* certbot-apache\n* certbot-nginx\n\nMore details about these changes can be found on our GitHub repo.\n\n## 0.31.0 - 2019-02-07\n\n### Added\n\n* Avoid reprocessing challenges that are already validated\n  when a certificate is issued.\n* Support for initiating (but not solving end-to-end) TLS-ALPN-01 challenges\n  with the `acme` module.\n\n### Changed\n\n* Certbot's official Docker images are now based on Alpine Linux 3.9 rather\n  than 3.7. The new version comes with OpenSSL 1.1.1.\n* Lexicon-based DNS plugins are now fully compatible with Lexicon 3.x (support\n  on 2.x branch is maintained).\n* Apache plugin now attempts to configure all VirtualHosts matching requested\n  domain name instead of only a single one when answering the HTTP-01 challenge.\n\n### Fixed\n\n* Fixed accessing josepy contents through acme.jose when the full acme.jose\n  path is used.\n* Clarify behavior for deleting certs as part of revocation.\n\nDespite us having broken lockstep, we are continuing to release new versions of\nall Certbot components during releases for the time being, however, the only\npackage with changes other than its version number was:\n\n* acme\n* certbot\n* certbot-apache\n* certbot-dns-cloudxns\n* certbot-dns-dnsimple\n* certbot-dns-dnsmadeeasy\n* certbot-dns-gehirn\n* certbot-dns-linode\n* certbot-dns-luadns\n* certbot-dns-nsone\n* certbot-dns-ovh\n* certbot-dns-sakuracloud\n\nMore details about these changes can be found on our GitHub repo.\n\n## 0.30.2 - 2019-01-25\n\n### Fixed\n\n* Update the version of setuptools pinned in certbot-auto to 40.6.3 to\n  solve installation problems on newer OSes.\n\nDespite us having broken lockstep, we are continuing to release new versions of\nall Certbot components during releases for the time being, however, this\nrelease only affects certbot-auto.\n\nMore details about these changes can be found on our GitHub repo.\n\n## 0.30.1 - 2019-01-24\n\n### Fixed\n\n* Always download the pinned version of pip in pipstrap to address breakages\n* Rename old,default.conf to old-and-default.conf to address commas in filenames\n  breaking recent versions of pip.\n* Add VIRTUALENV_NO_DOWNLOAD=1 to all calls to virtualenv to address breakages\n  from venv downloading the latest pip\n\nDespite us having broken lockstep, we are continuing to release new versions of\nall Certbot components during releases for the time being, however, the only\npackage with changes other than its version number was:\n\n* certbot-apache\n\nMore details about these changes can be found on our GitHub repo.\n\n## 0.30.0 - 2019-01-02\n\n### Added\n\n* Added the `update_account` subcommand for account management commands.\n\n### Changed\n\n* Copied account management functionality from the `register` subcommand\n  to the `update_account` subcommand.\n* Marked usage `register --update-registration` for deprecation and\n  removal in a future release.\n\n### Fixed\n\n* Older modules in the josepy library can now be accessed through acme.jose\n  like it could in previous versions of acme. This is only done to preserve\n  backwards compatibility and support for doing this with new modules in josepy\n  will not be added. Users of the acme library should switch to using josepy\n  directly if they haven't done so already.\n\nDespite us having broken lockstep, we are continuing to release new versions of\nall Certbot components during releases for the time being, however, the only\npackage with changes other than its version number was:\n\n* acme\n\nMore details about these changes can be found on our GitHub repo.\n\n## 0.29.1 - 2018-12-05\n\n### Added\n\n*\n\n### Changed\n\n*\n\n### Fixed\n\n* The default work and log directories have been changed back to\n  /var/lib/letsencrypt and /var/log/letsencrypt respectively.\n\nDespite us having broken lockstep, we are continuing to release new versions of\nall Certbot components during releases for the time being, however, the only\npackage with changes other than its version number was:\n\n* certbot\n\nMore details about these changes can be found on our GitHub repo.\n\n## 0.29.0 - 2018-12-05\n\n### Added\n\n* Noninteractive renewals with `certbot renew` (those not started from a\n  terminal) now randomly sleep 1-480 seconds before beginning work in\n  order to spread out load spikes on the server side.\n* Added External Account Binding support in cli and acme library.\n  Command line arguments --eab-kid and --eab-hmac-key added.\n\n### Changed\n\n* Private key permissioning changes: Renewal preserves existing group mode\n  & gid of previous private key material. Private keys for new\n  lineages (i.e. new certs, not renewed) default to 0o600.\n\n### Fixed\n\n* Update code and dependencies to clean up Resource and Deprecation Warnings.\n* Only depend on imgconverter extension for Sphinx >= 1.6\n\nDespite us having broken lockstep, we are continuing to release new versions of\nall Certbot components during releases for the time being, however, the only\npackage with changes other than its version number was:\n\n* acme\n* certbot\n* certbot-apache\n* certbot-dns-cloudflare\n* certbot-dns-digitalocean\n* certbot-dns-google\n* certbot-nginx\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/milestone/62?closed=1\n\n## 0.28.0 - 2018-11-7\n\n### Added\n\n* `revoke` accepts `--cert-name`, and doesn't accept both `--cert-name` and `--cert-path`.\n* Use the ACMEv2 newNonce endpoint when a new nonce is needed, and newNonce is available in the directory.\n\n### Changed\n\n* Removed documentation mentions of `#letsencrypt` IRC on Freenode.\n* Write README to the base of (config-dir)/live directory\n* `--manual` will explicitly warn users that earlier challenges should remain in place when setting up subsequent challenges.\n* Warn when using deprecated acme.challenges.TLSSNI01\n* Log warning about TLS-SNI deprecation in Certbot\n* Stop preferring TLS-SNI in the Apache, Nginx, and standalone plugins\n* OVH DNS plugin now relies on Lexicon>=2.7.14 to support HTTP proxies\n* Default time the Linode plugin waits for DNS changes to propagate is now 1200 seconds.\n\n### Fixed\n\n* Match Nginx parser update in allowing variable names to start with `${`.\n* Fix ranking of vhosts in Nginx so that all port-matching vhosts come first\n* Correct OVH integration tests on machines without internet access.\n* Stop caching the results of ipv6_info in http01.py\n* Test fix for Route53 plugin to prevent boto3 making outgoing connections.\n* The grammar used by Augeas parser in Apache plugin was updated to fix various parsing errors.\n* The CloudXNS, DNSimple, DNS Made Easy, Gehirn, Linode, LuaDNS, NS1, OVH, and\n  Sakura Cloud DNS plugins are now compatible with Lexicon 3.0+.\n\nDespite us having broken lockstep, we are continuing to release new versions of\nall Certbot components during releases for the time being, however, the only\npackage with changes other than its version number was:\n\n* acme\n* certbot\n* certbot-apache\n* certbot-dns-cloudxns\n* certbot-dns-dnsimple\n* certbot-dns-dnsmadeeasy\n* certbot-dns-gehirn\n* certbot-dns-linode\n* certbot-dns-luadns\n* certbot-dns-nsone\n* certbot-dns-ovh\n* certbot-dns-route53\n* certbot-dns-sakuracloud\n* certbot-nginx\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/milestone/59?closed=1\n\n## 0.27.1 - 2018-09-06\n\n### Fixed\n\n* Fixed parameter name in OpenSUSE overrides for default parameters in the\n  Apache plugin. Certbot on OpenSUSE works again.\n\nDespite us having broken lockstep, we are continuing to release new versions of\nall Certbot components during releases for the time being, however, the only\npackage with changes other than its version number was:\n\n* certbot-apache\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/milestone/60?closed=1\n\n## 0.27.0 - 2018-09-05\n\n### Added\n\n* The Apache plugin now accepts the parameter --apache-ctl which can be\n  used to configure the path to the Apache control script.\n\n### Changed\n\n* When using `acme.client.ClientV2` (or\n `acme.client.BackwardsCompatibleClientV2` with an ACME server that supports a\n newer version of the ACME protocol), an `acme.errors.ConflictError` will be\n raised if you try to create an ACME account with a key that has already been\n used. Previously, a JSON parsing error was raised in this scenario when using\n the library with Let's Encrypt's ACMEv2 endpoint.\n\n### Fixed\n\n* When Apache is not installed, Certbot's Apache plugin no longer prints\n  messages about being unable to find apachectl to the terminal when the plugin\n  is not selected.\n* If you're using the Apache plugin with the --apache-vhost-root flag set to a\n  directory containing a disabled virtual host for the domain you're requesting\n  a certificate for, the virtual host will now be temporarily enabled if\n  necessary to pass the HTTP challenge.\n* The documentation for the Certbot package can now be built using Sphinx 1.6+.\n* You can now call `query_registration` without having to first call\n  `new_account` on `acme.client.ClientV2` objects.\n* The requirement of `setuptools>=1.0` has been removed from `certbot-dns-ovh`.\n* Names in certbot-dns-sakuracloud's tests have been updated to refer to Sakura\n  Cloud rather than NS1 whose plugin certbot-dns-sakuracloud was based on.\n\nDespite us having broken lockstep, we are continuing to release new versions of\nall Certbot components during releases for the time being, however, the only\npackage with changes other than its version number was:\n\n* acme\n* certbot\n* certbot-apache\n* certbot-dns-ovh\n* certbot-dns-sakuracloud\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/milestone/57?closed=1\n\n## 0.26.1 - 2018-07-17\n\n### Fixed\n\n* Fix a bug that was triggered when users who had previously manually set `--server` to get ACMEv2 certs tried to renew ACMEv1 certs.\n\nDespite us having broken lockstep, we are continuing to release new versions of all Certbot components during releases for the time being, however, the only package with changes other than its version number was:\n\n* certbot\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/milestone/58?closed=1\n\n## 0.26.0 - 2018-07-11\n\n### Added\n\n* A new security enhancement which we're calling AutoHSTS has been added to\n  Certbot's Apache plugin. This enhancement configures your webserver to send a\n  HTTP Strict Transport Security header with a low max-age value that is slowly\n  increased over time. The max-age value is not increased to a large value\n  until you've successfully managed to renew your certificate. This enhancement\n  can be requested with the --auto-hsts flag.\n* New official DNS plugins have been created for Gehirn Infrastructure Service,\n  Linode, OVH, and Sakura Cloud. These plugins can be found on our Docker Hub\n  page at https://hub.docker.com/u/certbot and on PyPI.\n* The ability to reuse ACME accounts from Let's Encrypt's ACMEv1 endpoint on\n  Let's Encrypt's ACMEv2 endpoint has been added.\n* Certbot and its components now support Python 3.7.\n* Certbot's install subcommand now allows you to interactively choose which\n  certificate to install from the list of certificates managed by Certbot.\n* Certbot now accepts the flag `--no-autorenew` which causes any obtained\n  certificates to not be automatically renewed when it approaches expiration.\n* Support for parsing the TLS-ALPN-01 challenge has been added back to the acme\n  library.\n\n### Changed\n\n* Certbot's default ACME server has been changed to Let's Encrypt's ACMEv2\n  endpoint. By default, this server will now be used for both new certificate\n  lineages and renewals.\n* The Nginx plugin is no longer marked labeled as an \"Alpha\" version.\n* The `prepare` method of Certbot's plugins is no longer called before running\n  \"Updater\" enhancements that are run on every invocation of `certbot renew`.\n\nDespite us having broken lockstep, we are continuing to release new versions of\nall Certbot components during releases for the time being, however, the only\npackages with functional changes were:\n\n* acme\n* certbot\n* certbot-apache\n* certbot-dns-gehirn\n* certbot-dns-linode\n* certbot-dns-ovh\n* certbot-dns-sakuracloud\n* certbot-nginx\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/milestone/55?closed=1\n\n## 0.25.1 - 2018-06-13\n\n### Fixed\n\n* TLS-ALPN-01 support has been removed from our acme library. Using our current\n  dependencies, we are unable to provide a correct implementation of this\n  challenge so we decided to remove it from the library until we can provide\n  proper support.\n* Issues causing test failures when running the tests in the acme package with\n  pytest<3.0 has been resolved.\n* certbot-nginx now correctly depends on acme>=0.25.0.\n\nDespite us having broken lockstep, we are continuing to release new versions of\nall Certbot components during releases for the time being, however, the only\npackages with changes other than their version number were:\n\n* acme\n* certbot-nginx\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/milestone/56?closed=1\n\n## 0.25.0 - 2018-06-06\n\n### Added\n\n* Support for the ready status type was added to acme. Without this change,\n  Certbot and acme users will begin encountering errors when using Let's\n  Encrypt's ACMEv2 API starting on June 19th for the staging environment and\n  July 5th for production. See\n  https://community.letsencrypt.org/t/acmev2-order-ready-status/62866 for more\n  information.\n* Certbot now accepts the flag --reuse-key which will cause the same key to be\n  used in the certificate when the lineage is renewed rather than generating a\n  new key.\n* You can now add multiple email addresses to your ACME account with Certbot by\n  providing a comma separated list of emails to the --email flag.\n* Support for Let's Encrypt's upcoming TLS-ALPN-01 challenge was added to acme.\n  For more information, see\n  https://community.letsencrypt.org/t/tls-alpn-validation-method/63814/1.\n* acme now supports specifying the source address to bind to when sending\n  outgoing connections. You still cannot specify this address using Certbot.\n* If you run Certbot against Let's Encrypt's ACMEv2 staging server but don't\n  already have an account registered at that server URL, Certbot will\n  automatically reuse your staging account from Let's Encrypt's ACMEv1 endpoint\n  if it exists.\n* Interfaces were added to Certbot allowing plugins to be called at additional\n  points. The `GenericUpdater` interface allows plugins to perform actions\n  every time `certbot renew` is run, regardless of whether any certificates are\n  due for renewal, and the `RenewDeployer` interface allows plugins to perform\n  actions when a certificate is renewed. See `certbot.interfaces` for more\n  information.\n\n### Changed\n\n* When running Certbot with --dry-run and you don't already have a staging\n  account, the created account does not contain an email address even if one\n  was provided to avoid expiration emails from Let's Encrypt's staging server.\n* certbot-nginx does a better job of automatically detecting the location of\n  Nginx's configuration files when run on BSD based systems.\n* acme now requires and uses pytest when running tests with setuptools with\n  `python setup.py test`.\n* `certbot config_changes` no longer waits for user input before exiting.\n\n### Fixed\n\n* Misleading log output that caused users to think that Certbot's standalone\n  plugin failed to bind to a port when performing a challenge has been\n  corrected.\n* An issue where certbot-nginx would fail to enable HSTS if the server block\n  already had an `add_header` directive has been resolved.\n* certbot-nginx now does a better job detecting the server block to base the\n  configuration for TLS-SNI challenges on.\n\nDespite us having broken lockstep, we are continuing to release new versions of\nall Certbot components during releases for the time being, however, the only\npackages with functional changes were:\n\n* acme\n* certbot\n* certbot-apache\n* certbot-nginx\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/milestone/54?closed=1\n\n## 0.24.0 - 2018-05-02\n\n### Added\n\n* certbot now has an enhance subcommand which allows you to configure security\n  enhancements like HTTP to HTTPS redirects, OCSP stapling, and HSTS without\n  reinstalling a certificate.\n* certbot-dns-rfc2136 now allows the user to specify the port to use to reach\n  the DNS server in its credentials file.\n* acme now parses the wildcard field included in authorizations so it can be\n  used by users of the library.\n\n### Changed\n\n* certbot-dns-route53 used to wait for each DNS update to propagate before\n  sending the next one, but now it sends all updates before waiting which\n  speeds up issuance for multiple domains dramatically.\n* Certbot's official Docker images are now based on Alpine Linux 3.7 rather\n  than 3.4 because 3.4 has reached its end-of-life.\n* We've doubled the time Certbot will spend polling authorizations before\n  timing out.\n* The level of the message logged when Certbot is being used with\n  non-standard paths warning that crontabs for renewal included in Certbot\n  packages from OS package managers may not work has been reduced. This stops\n  the message from being written to stderr every time `certbot renew` runs.\n\n### Fixed\n\n* certbot-auto now works with Python 3.6.\n\nDespite us having broken lockstep, we are continuing to release new versions of\nall Certbot components during releases for the time being, however, the only\npackages with changes other than their version number were:\n\n* acme\n* certbot\n* certbot-apache\n* certbot-dns-digitalocean (only style improvements to tests)\n* certbot-dns-rfc2136\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/milestone/52?closed=1\n\n## 0.23.0 - 2018-04-04\n\n### Added\n\n* Support for OpenResty was added to the Nginx plugin.\n\n### Changed\n\n* The timestamps in Certbot's logfiles now use the system's local time zone\n  rather than UTC.\n* Certbot's DNS plugins that use Lexicon now rely on Lexicon>=2.2.1 to be able\n  to create and delete multiple TXT records on a single domain.\n* certbot-dns-google's test suite now works without an internet connection.\n\n### Fixed\n\n* Removed a small window that if during which an error occurred, Certbot\n  wouldn't clean up performed challenges.\n* The parameters `default` and `ipv6only` are now removed from `listen`\n  directives when creating a new server block in the Nginx plugin.\n* `server_name` directives enclosed in quotation marks in Nginx are now properly\n  supported.\n* Resolved an issue preventing the Apache plugin from starting Apache when it's\n  not currently running on RHEL and Gentoo based systems.\n\nDespite us having broken lockstep, we are continuing to release new versions of\nall Certbot components during releases for the time being, however, the only\npackages with changes other than their version number were:\n\n* certbot\n* certbot-apache\n* certbot-dns-cloudxns\n* certbot-dns-dnsimple\n* certbot-dns-dnsmadeeasy\n* certbot-dns-google\n* certbot-dns-luadns\n* certbot-dns-nsone\n* certbot-dns-rfc2136\n* certbot-nginx\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/milestone/50?closed=1\n\n## 0.22.2 - 2018-03-19\n\n### Fixed\n\n* A type error introduced in 0.22.1 that would occur during challenge cleanup\n  when a Certbot plugin raises an exception while trying to complete the\n  challenge was fixed.\n\nDespite us having broken lockstep, we are continuing to release new versions of\nall Certbot components during releases for the time being, however, the only\npackages with changes other than their version number were:\n\n* certbot\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/milestone/53?closed=1\n\n## 0.22.1 - 2018-03-19\n\n### Changed\n\n* The ACME server used with Certbot's --dry-run and --staging flags is now\n  Let's Encrypt's ACMEv2 staging server which allows people to also test ACMEv2\n  features with these flags.\n\n### Fixed\n\n* The HTTP Content-Type header is now set to the correct value during\n  certificate revocation with new versions of the ACME protocol.\n* When using Certbot with Let's Encrypt's ACMEv2 server, it would add a blank\n  line to the top of chain.pem and between the certificates in fullchain.pem\n  for each lineage. These blank lines have been removed.\n* Resolved a bug that caused Certbot's --allow-subset-of-names flag not to\n  work.\n* Fixed a regression in acme.client.Client that caused the class to not work\n  when it was initialized without a ClientNetwork which is done by some of the\n  other projects using our ACME library.\n\nDespite us having broken lockstep, we are continuing to release new versions of\nall Certbot components during releases for the time being, however, the only\npackages with changes other than their version number were:\n\n* acme\n* certbot\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/milestone/51?closed=1\n\n## 0.22.0 - 2018-03-07\n\n### Added\n\n* Support for obtaining wildcard certificates and a newer version of the ACME\n  protocol such as the one implemented by Let's Encrypt's upcoming ACMEv2\n  endpoint was added to Certbot and its ACME library. Certbot still works with\n  older ACME versions and will automatically change the version of the protocol\n  used based on the version the ACME CA implements.\n* The Apache and Nginx plugins are now able to automatically install a wildcard\n  certificate to multiple virtual hosts that you select from your server\n  configuration.\n* The `certbot install` command now accepts the `--cert-name` flag for\n  selecting a certificate.\n* `acme.client.BackwardsCompatibleClientV2` was added to Certbot's ACME library\n  which automatically handles most of the differences between new and old ACME\n  versions. `acme.client.ClientV2` is also available for people who only want\n  to support one version of the protocol or want to handle the differences\n  between versions themselves.\n* certbot-auto now supports the flag --install-only which has the script\n  install Certbot and its dependencies and exit without invoking Certbot.\n* Support for issuing a single certificate for a wildcard and base domain was\n  added to our Google Cloud DNS plugin. To do this, we now require your API\n  credentials have additional permissions, however, your credentials will\n  already have these permissions unless you defined a custom role with fewer\n  permissions than the standard DNS administrator role provided by Google.\n  These permissions are also only needed for the case described above so it\n  will continue to work for existing users. For more information about the\n  permissions changes, see the documentation in the plugin.\n\n### Changed\n\n* We have broken lockstep between our ACME library, Certbot, and its plugins.\n  This means that the different components do not need to be the same version\n  to work together like they did previously. This makes packaging easier\n  because not every piece of Certbot needs to be repackaged to ship a change to\n  a subset of its components.\n* Support for Python 2.6 and Python 3.3 has been removed from ACME, Certbot,\n  Certbot's plugins, and certbot-auto. If you are using certbot-auto on a RHEL\n  6 based system, it will walk you through the process of installing Certbot\n  with Python 3 and refuse to upgrade to a newer version of Certbot until you\n  have done so.\n* Certbot's components now work with older versions of setuptools to simplify\n  packaging for EPEL 7.\n\n### Fixed\n\n* Issues caused by Certbot's Nginx plugin adding multiple ipv6only directives\n  has been resolved.\n* A problem where Certbot's Apache plugin would add redundant include\n  directives for the TLS configuration managed by Certbot has been fixed.\n* Certbot's webroot plugin now properly deletes any directories it creates.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/milestone/48?closed=1\n\n## 0.21.1 - 2018-01-25\n\n### Fixed\n\n* When creating an HTTP to HTTPS redirect in Nginx, we now ensure the Host\n  header of the request is set to an expected value before redirecting users to\n  the domain found in the header. The previous way Certbot configured Nginx\n  redirects was a potential security issue which you can read more about at\n  https://community.letsencrypt.org/t/security-issue-with-redirects-added-by-certbots-nginx-plugin/51493.\n* Fixed a problem where Certbot's Apache plugin could fail HTTP-01 challenges\n  if basic authentication is configured for the domain you request a\n  certificate for.\n* certbot-auto --no-bootstrap now properly tries to use Python 3.4 on RHEL 6\n  based systems rather than Python 2.6.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/milestone/49?closed=1\n\n## 0.21.0 - 2018-01-17\n\n### Added\n\n* Support for the HTTP-01 challenge type was added to our Apache and Nginx\n  plugins. For those not aware, Let's Encrypt disabled the TLS-SNI-01 challenge\n  type which was what was previously being used by our Apache and Nginx plugins\n  last week due to a security issue. For more information about Let's Encrypt's\n  change, click\n  [here](https://community.letsencrypt.org/t/2018-01-11-update-regarding-acme-tls-sni-and-shared-hosting-infrastructure/50188).\n  Our Apache and Nginx plugins will automatically switch to use HTTP-01 so no\n  changes need to be made to your Certbot configuration, however, you should\n  make sure your server is accessible on port 80 and isn't behind an external\n  proxy doing things like redirecting all traffic from HTTP to HTTPS. HTTP to\n  HTTPS redirects inside Apache and Nginx are fine.\n* IPv6 support was added to the Nginx plugin.\n* Support for automatically creating server blocks based on the default server\n  block was added to the Nginx plugin.\n* The flags --delete-after-revoke and --no-delete-after-revoke were added\n  allowing users to control whether the revoke subcommand also deletes the\n  certificates it is revoking.\n\n### Changed\n\n* We deprecated support for Python 2.6 and Python 3.3 in Certbot and its ACME\n  library. Support for these versions of Python will be removed in the next\n  major release of Certbot. If you are using certbot-auto on a RHEL 6 based\n  system, it will guide you through the process of installing Python 3.\n* We split our implementation of JOSE (Javascript Object Signing and\n  Encryption) out of our ACME library and into a separate package named josepy.\n  This package is available on [PyPI](https://pypi.python.org/pypi/josepy) and\n  on [GitHub](https://github.com/certbot/josepy).\n* We updated the ciphersuites used in Apache to the new [values recommended by\n  Mozilla](https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29).\n  The major change here is adding ChaCha20 to the list of supported\n  ciphersuites.\n\n### Fixed\n\n* An issue with our Apache plugin on Gentoo due to differences in their\n  apache2ctl command have been resolved.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/milestone/47?closed=1\n\n## 0.20.0 - 2017-12-06\n\n### Added\n\n* Certbot's ACME library now recognizes URL fields in challenge objects in\n  preparation for Let's Encrypt's new ACME endpoint. The value is still\n  accessible in our ACME library through the name \"uri\".\n\n### Changed\n\n* The Apache plugin now parses some distro specific Apache configuration files\n  on non-Debian systems allowing it to get a clearer picture on the running\n  configuration. Internally, these changes were structured so that external\n  contributors can easily write patches to make the plugin work in new Apache\n  configurations.\n* Certbot better reports network failures by removing information about\n  connection retries from the error output.\n* An unnecessary question when using Certbot's webroot plugin interactively has\n  been removed.\n\n### Fixed\n\n* Certbot's NGINX plugin no longer sometimes incorrectly reports that it was\n  unable to deploy a HTTP->HTTPS redirect when requesting Certbot to enable a\n  redirect for multiple domains.\n* Problems where the Apache plugin was failing to find directives and\n  duplicating existing directives on openSUSE have been resolved.\n* An issue running the test shipped with Certbot and some our DNS plugins with\n  older versions of mock have been resolved.\n* On some systems, users reported strangely interleaved output depending on\n  when stdout and stderr were flushed. This problem was resolved by having\n  Certbot regularly flush these streams.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/milestone/44?closed=1\n\n## 0.19.0 - 2017-10-04\n\n### Added\n\n* Certbot now has renewal hook directories where executable files can be placed\n  for Certbot to run with the renew subcommand. Pre-hooks, deploy-hooks, and\n  post-hooks can be specified in the renewal-hooks/pre, renewal-hooks/deploy,\n  and renewal-hooks/post directories respectively in Certbot's configuration\n  directory (which is /etc/letsencrypt by default). Certbot will automatically\n  create these directories when it is run if they do not already exist.\n* After revoking a certificate with the revoke subcommand, Certbot will offer\n  to delete the lineage associated with the certificate. When Certbot is run\n  with --non-interactive, it will automatically try to delete the associated\n  lineage.\n* When using Certbot's Google Cloud DNS plugin on Google Compute Engine, you no\n  longer have to provide a credential file to Certbot if you have configured\n  sufficient permissions for the instance which Certbot can automatically\n  obtain using Google's metadata service.\n\n### Changed\n\n* When deleting certificates interactively using the delete subcommand, Certbot\n  will now allow you to select multiple lineages to be deleted at once.\n* Certbot's Apache plugin no longer always parses Apache's sites-available on\n  Debian based systems and instead only parses virtual hosts included in your\n  Apache configuration. You can provide an additional directory for Certbot to\n  parse using the command line flag --apache-vhost-root.\n\n### Fixed\n\n* The plugins subcommand can now be run without root access.\n* certbot-auto now includes a timeout when updating itself so it no longer\n  hangs indefinitely when it is unable to connect to the external server.\n* An issue where Certbot's Apache plugin would sometimes fail to deploy a\n  certificate on Debian based systems if mod_ssl wasn't already enabled has\n  been resolved.\n* A bug in our Docker image where the certificates subcommand could not report\n  if certificates maintained by Certbot had been revoked has been fixed.\n* Certbot's RFC 2136 DNS plugin (for use with software like BIND) now properly\n  performs DNS challenges when the domain being verified contains a CNAME\n  record.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/milestone/43?closed=1\n\n## 0.18.2 - 2017-09-20\n\n### Fixed\n\n* An issue where Certbot's ACME module would raise an AttributeError trying to\n  create self-signed certificates when used with pyOpenSSL 17.3.0 has been\n  resolved. For Certbot users with this version of pyOpenSSL, this caused\n  Certbot to crash when performing a TLS SNI challenge or when the Nginx plugin\n  tried to create an SSL server block.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/milestone/46?closed=1\n\n## 0.18.1 - 2017-09-08\n\n### Fixed\n\n* If certbot-auto was running as an unprivileged user and it upgraded from\n  0.17.0 to 0.18.0, it would crash with a permissions error and would need to\n  be run again to successfully complete the upgrade. This has been fixed and\n  certbot-auto should upgrade cleanly to 0.18.1.\n* Certbot usually uses \"certbot-auto\" or \"letsencrypt-auto\" in error messages\n  and the User-Agent string instead of \"certbot\" when you are using one of\n  these wrapper scripts. Proper detection of this was broken with Certbot's new\n  installation path in /opt in 0.18.0 but this problem has been resolved.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/milestone/45?closed=1\n\n## 0.18.0 - 2017-09-06\n\n### Added\n\n* The Nginx plugin now configures Nginx to use 2048-bit Diffie-Hellman\n  parameters. Java 6 clients do not support Diffie-Hellman parameters larger\n  than 1024 bits, so if you need to support these clients you will need to\n  manually modify your Nginx configuration after using the Nginx installer.\n\n### Changed\n\n* certbot-auto now installs Certbot in directories under `/opt/eff.org`. If you\n  had an existing installation from certbot-auto, a symlink is created to the\n  new directory. You can configure certbot-auto to use a different path by\n  setting the environment variable VENV_PATH.\n* The Nginx plugin can now be selected in Certbot's interactive output.\n* Output verbosity of renewal failures when running with `--quiet` has been\n  reduced.\n* The default revocation reason shown in Certbot help output now is a human\n  readable string instead of a numerical code.\n* Plugin selection is now included in normal terminal output.\n\n### Fixed\n\n* A newer version of ConfigArgParse is now installed when using certbot-auto\n  causing values set to false in a Certbot INI configuration file to be handled\n  intuitively. Setting a boolean command line flag to false is equivalent to\n  not including it in the configuration file at all.\n* New naming conventions preventing certbot-auto from installing OS\n  dependencies on Fedora 26 have been resolved.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/milestone/42?closed=1\n\n## 0.17.0 - 2017-08-02\n\n### Added\n\n* Support in our nginx plugin for modifying SSL server blocks that do\n  not contain certificate or key directives.\n* A `--max-log-backups` flag to allow users to configure or even completely\n  disable Certbot's built in log rotation.\n* A `--user-agent-comment` flag to allow people who build tools around Certbot\n  to differentiate their user agent string by adding a comment to its default\n  value.\n\n### Changed\n\n* Due to some awesome work by\n  [cryptography project](https://github.com/pyca/cryptography), compilation can\n  now be avoided on most systems when using certbot-auto. This eliminates many\n  problems people have had in the past such as running out of memory, having\n  invalid headers/libraries, and changes to the OS packages on their system\n  after compilation breaking Certbot.\n* The `--renew-hook` flag has been hidden in favor of `--deploy-hook`. This new\n  flag works exactly the same way except it is always run when a certificate is\n  issued rather than just when it is renewed.\n* We have started printing deprecation warnings in certbot-auto for\n  experimentally supported systems with OS packages available.\n* A certificate lineage's name is included in error messages during renewal.\n\n### Fixed\n\n* Encoding errors that could occur when parsing error messages from the ACME\n  server containing Unicode have been resolved.\n* certbot-auto no longer prints misleading messages about there being a newer\n  pip version available when installation fails.\n* Certbot's ACME library now properly extracts domains from critical SAN\n  extensions.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.17.0+is%3Aclosed\n\n## 0.16.0 - 2017-07-05\n\n### Added\n\n* A plugin for performing DNS challenges using dynamic DNS updates as defined\n  in RFC 2316. This plugin is packaged separately from Certbot and is available\n  at https://pypi.python.org/pypi/certbot-dns-rfc2136. It supports Python 2.6,\n  2.7, and 3.3+. At this time, there isn't a good way to install this plugin\n  when using certbot-auto, but this should change in the near future.\n* Plugins for performing DNS challenges for the providers\n  [DNS Made Easy](https://pypi.python.org/pypi/certbot-dns-dnsmadeeasy) and\n  [LuaDNS](https://pypi.python.org/pypi/certbot-dns-luadns). These plugins are\n  packaged separately from Certbot and support Python 2.7 and 3.3+. Currently,\n  there isn't a good way to install these plugins when using certbot-auto,\n  but that should change soon.\n* Support for performing TLS-SNI-01 challenges when using the manual plugin.\n* Automatic detection of Arch Linux in the Apache plugin providing better\n  default settings for the plugin.\n\n### Changed\n\n* The text of the interactive question about whether a redirect from HTTP to\n  HTTPS should be added by Certbot has been rewritten to better explain the\n  choices to the user.\n* Simplified HTTP challenge instructions in the manual plugin.\n\n### Fixed\n\n* Problems performing a dry run when using the Nginx plugin have been fixed.\n* Resolved an issue where certbot-dns-digitalocean's test suite would sometimes\n  fail when ran using Python 3.\n* On some systems, previous versions of certbot-auto would error out with a\n  message about a missing hash for setuptools. This has been fixed.\n* A bug where Certbot would sometimes not print a space at the end of an\n  interactive prompt has been resolved.\n* Nonfatal tracebacks are no longer shown in rare cases where Certbot\n  encounters an exception trying to close its TCP connection with the ACME\n  server.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.16.0+is%3Aclosed\n\n## 0.15.0 - 2017-06-08\n\n### Added\n\n* Plugins for performing DNS challenges for popular providers. Like the Apache\n  and Nginx plugins, these plugins are packaged separately and not included in\n  Certbot by default. So far, we have plugins for\n  [Amazon Route 53](https://pypi.python.org/pypi/certbot-dns-route53),\n  [Cloudflare](https://pypi.python.org/pypi/certbot-dns-cloudflare),\n  [DigitalOcean](https://pypi.python.org/pypi/certbot-dns-digitalocean), and\n  [Google Cloud](https://pypi.python.org/pypi/certbot-dns-google) which all\n  work on Python 2.6, 2.7, and 3.3+. Additionally, we have plugins for\n  [CloudXNS](https://pypi.python.org/pypi/certbot-dns-cloudxns),\n  [DNSimple](https://pypi.python.org/pypi/certbot-dns-dnsimple),\n  [NS1](https://pypi.python.org/pypi/certbot-dns-nsone) which work on Python\n  2.7 and 3.3+ (and not 2.6). Currently, there isn't a good way to install\n  these plugins when using `certbot-auto`, but that should change soon.\n* IPv6 support in the standalone plugin. When performing a challenge, the\n  standalone plugin automatically handles listening for IPv4/IPv6 traffic based\n  on the configuration of your system.\n* A mechanism for keeping your Apache and Nginx SSL/TLS configuration up to\n  date. When the Apache or Nginx plugins are used, they place SSL/TLS\n  configuration options in the root of Certbot's config directory\n  (`/etc/letsencrypt` by default). Now when a new version of these plugins run\n  on your system, they will automatically update the file to the newest\n  version if it is unmodified. If you manually modified the file, Certbot will\n  display a warning giving you a path to the updated file which you can use as\n  a reference to manually update your modified copy.\n* `--http-01-address` and `--tls-sni-01-address` flags for controlling the\n  address Certbot listens on when using the standalone plugin.\n* The command `certbot certificates` that lists certificates managed by Certbot\n  now performs additional validity checks to notify you if your files have\n  become corrupted.\n\n### Changed\n\n* Messages custom hooks print to `stdout` are now displayed by Certbot when not\n  running in `--quiet` mode.\n* `jwk` and `alg` fields in JWS objects have been moved into the protected\n  header causing Certbot to more closely follow the latest version of the ACME\n  spec.\n\n### Fixed\n\n* Permissions on renewal configuration files are now properly preserved when\n  they are updated.\n* A bug causing Certbot to display strange defaults in its help output when\n  using Python <= 2.7.4 has been fixed.\n* Certbot now properly handles mixed case domain names found in custom CSRs.\n* A number of poorly worded prompts and error messages.\n\n### Removed\n\n* Support for OpenSSL 1.0.0 in `certbot-auto` has been removed as we now pin a\n  newer version of `cryptography` which dropped support for this version.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.15.0+is%3Aclosed\n\n## 0.14.2 - 2017-05-25\n\n### Fixed\n\n* Certbot 0.14.0 included a bug where Certbot would create a temporary log file\n(usually in /tmp) if the program exited during argument parsing. If a user\nprovided -h/--help/help, --version, or an invalid command line argument,\nCertbot would create this temporary log file. This was especially bothersome to\ncertbot-auto users as certbot-auto runs `certbot --version` internally to see\nif the script needs to upgrade causing it to create at least one of these files\non every run. This problem has been resolved.\n\nMore details about this change can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.14.2+is%3Aclosed\n\n## 0.14.1 - 2017-05-16\n\n### Fixed\n\n* Certbot now works with configargparse 0.12.0.\n* Issues with the Apache plugin and Augeas 1.7+ have been resolved.\n* A problem where the Nginx plugin would fail to install certificates on\nsystems that had the plugin's SSL/TLS options file from 7+ months ago has been\nfixed.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.14.1+is%3Aclosed\n\n## 0.14.0 - 2017-05-04\n\n### Added\n\n* Python 3.3+ support for all Certbot packages. `certbot-auto` still currently\nonly supports Python 2, but the `acme`, `certbot`, `certbot-apache`, and\n`certbot-nginx` packages on PyPI now fully support Python 2.6, 2.7, and 3.3+.\n* Certbot's Apache plugin now handles multiple virtual hosts per file.\n* Lockfiles to prevent multiple versions of Certbot running simultaneously.\n\n### Changed\n\n* When converting an HTTP virtual host to HTTPS in Apache, Certbot only copies\nthe virtual host rather than the entire contents of the file it's contained\nin.\n* The Nginx plugin now includes SSL/TLS directives in a separate file located\nin Certbot's configuration directory rather than copying the contents of the\nfile into every modified `server` block.\n\n### Fixed\n\n* Ensure logging is configured before parts of Certbot attempt to log any\nmessages.\n* Support for the `--quiet` flag in `certbot-auto`.\n* Reverted a change made in a previous release to make the `acme` and `certbot`\npackages always depend on `argparse`. This dependency is conditional again on\nthe user's Python version.\n* Small bugs in the Nginx plugin such as properly handling empty `server`\nblocks and setting `server_names_hash_bucket_size` during challenges.\n\nAs always, a more complete list of changes can be found on GitHub:\nhttps://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.14.0+is%3Aclosed\n\n## 0.13.0 - 2017-04-06\n\n### Added\n\n* `--debug-challenges` now pauses Certbot after setting up challenges for debugging.\n* The Nginx parser can now handle all valid directives in configuration files.\n* Nginx ciphersuites have changed to Mozilla Intermediate.\n* `certbot-auto --no-bootstrap` provides the option to not install OS dependencies.\n\n### Fixed\n\n* `--register-unsafely-without-email` now respects `--quiet`.\n* Hyphenated renewal parameters are now saved in renewal config files.\n* `--dry-run` no longer persists keys and csrs.\n* Certbot no longer hangs when trying to start Nginx in Arch Linux.\n* Apache rewrite rules no longer double-encode characters.\n\nA full list of changes is available on GitHub:\nhttps://github.com/certbot/certbot/issues?q=is%3Aissue%20milestone%3A0.13.0%20is%3Aclosed%20\n\n## 0.12.0 - 2017-03-02\n\n### Added\n\n* Certbot now allows non-camelcase Apache VirtualHost names.\n* Certbot now allows more log messages to be silenced.\n\n### Fixed\n\n* Fixed a regression around using `--cert-name` when getting new certificates\n\nMore information about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/issues?q=is%3Aissue%20milestone%3A0.12.0\n\n## 0.11.1 - 2017-02-01\n\n### Fixed\n\n* Resolved a problem where Certbot would crash while parsing command line\narguments in some cases.\n* Fixed a typo.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/pulls?q=is%3Apr%20milestone%3A0.11.1%20is%3Aclosed\n\n## 0.11.0 - 2017-02-01\n\n### Added\n\n* When using the standalone plugin while running Certbot interactively\nand a required port is bound by another process, Certbot will give you\nthe option to retry to grab the port rather than immediately exiting.\n* You are now able to deactivate your account with the Let's Encrypt\nserver using the `unregister` subcommand.\n* When revoking a certificate using the `revoke` subcommand, you now\nhave the option to provide the reason the certificate is being revoked\nto Let's Encrypt with `--reason`.\n\n### Changed\n\n* Providing `--quiet` to `certbot-auto` now silences package manager output.\n\n### Removed\n\n* Removed the optional `dnspython` dependency in our `acme` package.\nNow the library does not support client side verification of the DNS\nchallenge.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.11.0+is%3Aclosed\n\n## 0.10.2 - 2017-01-25\n\n### Added\n\n* If Certbot receives a request with a `badNonce` error, it now\nautomatically retries the request. Since nonces from Let's Encrypt expire,\nthis helps people performing the DNS challenge with the `manual` plugin\nwho may have to wait an extended period of time for their DNS changes to\npropagate.\n\n### Fixed\n\n* Certbot now saves the `--preferred-challenges` values for renewal. Previously\nthese values were discarded causing a different challenge type to be used when\nrenewing certs in some cases.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.10.2+is%3Aclosed\n\n## 0.10.1 - 2017-01-13\n\n### Fixed\n\n* Resolve problems where when asking Certbot to update a certificate at\nan existing path to include different domain names, the old names would\ncontinue to be used.\n* Fix issues successfully running our unit test suite on some systems.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.10.1+is%3Aclosed\n\n## 0.10.0 - 2017-01-11\n\n## Added\n\n* Added the ability to customize and automatically complete DNS and HTTP\ndomain validation challenges with the manual plugin. The flags\n`--manual-auth-hook` and `--manual-cleanup-hook` can now be provided\nwhen using the manual plugin to execute commands provided by the user to\nperform and clean up challenges provided by the CA. This is best used in\ncomplicated setups where the DNS challenge must be used or Certbot's\nexisting plugins cannot be used to perform HTTP challenges. For more\ninformation on how this works, see `certbot --help manual`.\n* Added a `--cert-name` flag for specifying the name to use for the\ncertificate in Certbot's configuration directory. Using this flag in\ncombination with `-d/--domains`, a user can easily request a new\ncertificate with different domains and save it with the name provided by\n`--cert-name`. Additionally, `--cert-name` can be used to select a\ncertificate with the `certonly` and `run` subcommands so a full list of\ndomains in the certificate does not have to be provided.\n* Added subcommand `certificates` for listing the certificates managed by\nCertbot and their properties.\n* Added the `delete` subcommand for removing certificates managed by Certbot\nfrom the configuration directory.\n* Certbot now supports requesting internationalized domain names (IDNs).\n* Hooks provided to Certbot are now saved to be reused during renewal.\nIf you run Certbot with `--pre-hook`, `--renew-hook`, or `--post-hook`\nflags when obtaining a certificate, the provided commands will\nautomatically be saved and executed again when renewing the certificate.\nA pre-hook and/or post-hook can also be given to the `certbot renew`\ncommand either on the command line or in a [configuration\nfile](https://certbot.eff.org/docs/using.html#configuration-file) to run\nan additional command before/after any certificate is renewed. Hooks\nwill only be run if a certificate is renewed.\n* Support Busybox in certbot-auto.\n\n### Changed\n\n* Recategorized `-h/--help` output to improve documentation and\ndiscoverability.\n\n### Removed\n\n* Removed the ncurses interface. This change solves problems people\nwere having on many systems, reduces the number of Certbot\ndependencies, and simplifies our code. Certbot's only interface now is\nthe text interface which was available by providing `-t/--text` to\nearlier versions of Certbot.\n\n### Fixed\n\n* Many small bug fixes.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.10.0is%3Aclosed\n\n## 0.9.3 - 2016-10-13\n\n### Added\n\n* The Apache plugin uses information about your OS to help determine the\nlayout of your Apache configuration directory. We added a patch to\nensure this code behaves the same way when testing on different systems\nas the tests were failing in some cases.\n\n### Changed\n\n* Certbot adopted more conservative behavior about reporting a needed port as\nunavailable when using the standalone plugin.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/milestone/27?closed=1\n\n## 0.9.2 - 2016-10-12\n\n### Added\n\n* Certbot stopped requiring that all possibly required ports are available when\nusing the standalone plugin. It now only verifies that the ports are available\nwhen they are necessary.\n\n### Fixed\n\n* Certbot now verifies that our optional dependencies version matches what is\nrequired by Certbot.\n* Certnot now properly copies the `ssl on;` directives as necessary when\nperforming domain validation in the Nginx plugin.\n* Fixed problem where symlinks were becoming files when they were\npackaged, causing errors during testing and OS packaging.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/milestone/26?closed=1\n\n## 0.9.1 - 2016-10-06\n\n### Fixed\n\n* Fixed a bug that was introduced in version 0.9.0 where the command\nline flag -q/--quiet wasn't respected in some cases.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/milestone/25?closed=1\n\n## 0.9.0 - 2016-10-05\n\n### Added\n\n* Added an alpha version of the Nginx plugin. This plugin fully automates the\nprocess of obtaining and installing certificates with Nginx.\nAdditionally, it is able to automatically configure security\nenhancements such as an HTTP to HTTPS redirect and OCSP stapling. To use\nthis plugin, you must have the `certbot-nginx` package installed (which\nis installed automatically when using `certbot-auto`) and provide\n`--nginx` on the command line. This plugin is still in its early stages\nso we recommend you use it with some caution and make sure you have a\nbackup of your Nginx configuration.\n* Added support for the `DNS` challenge in the `acme` library and `DNS` in\nCertbot's `manual` plugin. This allows you to create DNS records to\nprove to Let's Encrypt you control the requested domain name. To use\nthis feature, include `--manual --preferred-challenges dns` on the\ncommand line.\n* Certbot now helps with enabling Extra Packages for Enterprise Linux (EPEL) on\nCentOS 6 when using `certbot-auto`. To use `certbot-auto` on CentOS 6,\nthe EPEL repository has to be enabled. `certbot-auto` will now prompt\nusers asking them if they would like the script to enable this for them\nautomatically. This is done without prompting users when using\n`letsencrypt-auto` or if `-n/--non-interactive/--noninteractive` is\nincluded on the command line.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.9.0+is%3Aclosed\n\n## 0.8.1 - 2016-06-14\n\n### Added\n\n* Certbot now preserves a certificate's common name when using `renew`.\n* Certbot now saves webroot values for renewal when they are entered interactively.\n* Certbot now gracefully reports that the Apache plugin isn't usable when Augeas is not installed.\n* Added experimental support for Mageia has been added to `certbot-auto`.\n\n### Fixed\n\n* Fixed problems with an invalid user-agent string on OS X.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.8.1+\n\n## 0.8.0 - 2016-06-02\n\n### Added\n\n* Added the `register` subcommand which can be used to register an account\nwith the Let's Encrypt CA.\n* You can now run `certbot register --update-registration` to\nchange the e-mail address associated with your registration.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.8.0+\n\n## 0.7.0 - 2016-05-27\n\n### Added\n\n* Added `--must-staple` to request certificates from Let's Encrypt\nwith the OCSP must staple extension.\n* Certbot now automatically configures OSCP stapling for Apache.\n* Certbot now allows requesting certificates for domains found in the common name\nof a custom CSR.\n\n### Fixed\n\n* Fixed a number of miscellaneous bugs\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/issues?q=milestone%3A0.7.0+is%3Aissue\n\n## 0.6.0 - 2016-05-12\n\n### Added\n\n* Versioned the datetime dependency in setup.py.\n\n### Changed\n\n* Renamed the client from `letsencrypt` to `certbot`.\n\n### Fixed\n\n* Fixed a small json deserialization error.\n* Certbot now preserves domain order in generated CSRs.\n* Fixed some minor bugs.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/certbot/certbot/issues?q=is%3Aissue%20milestone%3A0.6.0%20is%3Aclosed%20\n\n## 0.5.0 - 2016-04-05\n\n### Added\n\n* Added the ability to use the webroot plugin interactively.\n* Added the flags --pre-hook, --post-hook, and --renew-hook which can be used with\nthe renew subcommand to register shell commands to run in response to\nrenewal events. Pre-hook commands will be run before any certs are\nrenewed, post-hook commands will be run after any certs are renewed,\nand renew-hook commands will be run after each cert is renewed. If no\ncerts are due for renewal, no command is run.\n* Added a -q/--quiet flag which silences all output except errors.\n* Added an --allow-subset-of-domains flag which can be used with the renew\ncommand to prevent renewal failures for a subset of the requested\ndomains from causing the client to exit.\n\n### Changed\n\n* Certbot now uses renewal configuration files. In /etc/letsencrypt/renewal\nby default, these files can be used to control what parameters are\nused when renewing a specific certificate.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/letsencrypt/letsencrypt/issues?q=milestone%3A0.5.0+is%3Aissue\n\n## 0.4.2 - 2016-03-03\n\n### Fixed\n\n* Resolved problems encountered when compiling letsencrypt\nagainst the new OpenSSL release.\n* Fixed problems encountered when using `letsencrypt renew` with configuration files\nfrom the private beta.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/letsencrypt/letsencrypt/issues?q=is%3Aissue+milestone%3A0.4.2\n\n## 0.4.1 - 2016-02-29\n\n### Fixed\n\n* Fixed Apache parsing errors encountered with some configurations.\n* Fixed Werkzeug dependency problems encountered on some Red Hat systems.\n* Fixed bootstrapping failures when using letsencrypt-auto with --no-self-upgrade.\n* Fixed problems with parsing renewal config files from private beta.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/letsencrypt/letsencrypt/issues?q=is:issue+milestone:0.4.1\n\n## 0.4.0 - 2016-02-10\n\n### Added\n\n* Added the verb/subcommand `renew` which can be used to renew your existing\ncertificates as they approach expiration. Running `letsencrypt renew`\nwill examine all existing certificate lineages and determine if any are\nless than 30 days from expiration. If so, the client will use the\nsettings provided when you previously obtained the certificate to renew\nit. The subcommand finishes by printing a summary of which renewals were\nsuccessful, failed, or not yet due.\n* Added a `--dry-run` flag to help with testing configuration\nwithout affecting production rate limits. Currently supported by the\n`renew` and `certonly` subcommands, providing `--dry-run` on the command\nline will obtain certificates from the staging server without saving the\nresulting certificates to disk.\n* Added major improvements to letsencrypt-auto. This script\nhas been rewritten to include full support for Python 2.6, the ability\nfor letsencrypt-auto to update itself, and improvements to the\nstability, security, and performance of the script.\n* Added support for Apache 2.2 to the Apache plugin.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/letsencrypt/letsencrypt/issues?q=is%3Aissue+milestone%3A0.4.0\n\n## 0.3.0 - 2016-01-27\n\n### Added\n\n* Added a non-interactive mode which can be enabled by including `-n` or\n`--non-interactive` on the command line. This can be used to guarantee\nthe client will not prompt when run automatically using cron/systemd.\n* Added preparation for the new letsencrypt-auto script. Over the past\ncouple months, we've been working on increasing the reliability and\nsecurity of letsencrypt-auto. A number of changes landed in this\nrelease to prepare for the new version of this script.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/letsencrypt/letsencrypt/issues?q=is%3Aissue+milestone%3A0.3.0\n\n## 0.2.0 - 2016-01-14\n\n### Added\n\n* Added Apache plugin support for non-Debian based systems. Support has been\nadded for modern Red Hat based systems such as Fedora 23, Red Hat 7,\nand CentOS 7 running Apache 2.4. In theory, this plugin should be\nable to be configured to run on any Unix-like OS running Apache 2.4.\n* Relaxed PyOpenSSL version requirements. This adds support for systems\nwith PyOpenSSL versions 0.13 or 0.14.\n* Improved error messages from the client.\n\n### Fixed\n\n* Resolved issues with the Apache plugin enabling an HTTP to HTTPS\nredirect on some systems.\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/letsencrypt/letsencrypt/issues?q=is%3Aissue+milestone%3A0.2.0\n\n## 0.1.1 - 2015-12-15\n\n### Added\n\n* Added a check that avoids attempting to issue for unqualified domain names like\n\"localhost\".\n\n### Fixed\n\n* Fixed a confusing UI path that caused some users to repeatedly renew\ntheir certs while experimenting with the client, in some cases hitting\nissuance rate limits.\n* Fixed numerous Apache configuration parser problems\n* Fixed --webroot permission handling for non-root users\n\nMore details about these changes can be found on our GitHub repo:\nhttps://github.com/letsencrypt/letsencrypt/issues?q=milestone%3A0.1.1\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "Certbot ACME Client\nCopyright (c) Electronic Frontier Foundation and others\nLicensed Apache Version 2.0\n\nThe nginx plugin incorporates code from nginxparser\nCopyright (c) 2014 Fatih Erikli\nLicensed MIT\n\n\nText of Apache License\n======================\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n\nText of MIT License\n===================\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include README.rst\ninclude CHANGELOG.md\ninclude LICENSE.txt\nrecursive-include docs *\nrecursive-include examples *\nrecursive-include certbot/tests/testdata *\nrecursive-include tests *.py\ninclude certbot/ssl-dhparams.pem\nglobal-exclude __pycache__\nglobal-exclude *.py[cod]\n"
  },
  {
    "path": "PKG-INFO",
    "content": "Metadata-Version: 2.1\nName: certbot\nVersion: 1.3.0\nSummary: ACME client\nHome-page: https://github.com/letsencrypt/letsencrypt\nAuthor: Certbot Project\nAuthor-email: client-dev@letsencrypt.org\nLicense: Apache License 2.0\nDescription: .. This file contains a series of comments that are used to include sections of this README in other files. Do not modify these comments unless you know what you are doing. tag:intro-begin\n        \n        Certbot is part of EFF’s effort to encrypt the entire Internet. Secure communication over the Web relies on HTTPS, which requires the use of a digital certificate that lets browsers verify the identity of web servers (e.g., is that really google.com?). Web servers obtain their certificates from trusted third parties called certificate authorities (CAs). Certbot is an easy-to-use client that fetches a certificate from Let’s Encrypt—an open certificate authority launched by the EFF, Mozilla, and others—and deploys it to a web server.\n        \n        Anyone who has gone through the trouble of setting up a secure website knows what a hassle getting and maintaining a certificate is. Certbot and Let’s Encrypt can automate away the pain and let you turn on and manage HTTPS with simple commands. Using Certbot and Let's Encrypt is free, so there’s no need to arrange payment.\n        \n        How you use Certbot depends on the configuration of your web server. The best way to get started is to use our `interactive guide <https://certbot.eff.org>`_. It generates instructions based on your configuration settings. In most cases, you’ll need `root or administrator access <https://certbot.eff.org/faq/#does-certbot-require-root-administrator-privileges>`_ to your web server to run Certbot.\n        \n        Certbot is meant to be run directly on your web server, not on your personal computer. If you’re using a hosted service and don’t have direct access to your web server, you might not be able to use Certbot. Check with your hosting provider for documentation about uploading certificates or using certificates issued by Let’s Encrypt.\n        \n        Certbot is a fully-featured, extensible client for the Let's\n        Encrypt CA (or any other CA that speaks the `ACME\n        <https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md>`_\n        protocol) that can automate the tasks of obtaining certificates and\n        configuring webservers to use them. This client runs on Unix-based operating\n        systems.\n        \n        To see the changes made to Certbot between versions please refer to our\n        `changelog <https://github.com/certbot/certbot/blob/master/certbot/CHANGELOG.md>`_.\n        \n        Until May 2016, Certbot was named simply ``letsencrypt`` or ``letsencrypt-auto``,\n        depending on install method. Instructions on the Internet, and some pieces of the\n        software, may still refer to this older name.\n        \n        Contributing\n        ------------\n        \n        If you'd like to contribute to this project please read `Developer Guide\n        <https://certbot.eff.org/docs/contributing.html>`_.\n        \n        This project is governed by `EFF's Public Projects Code of Conduct <https://www.eff.org/pages/eppcode>`_.\n        \n        .. _installation:\n        \n        How to run the client\n        ---------------------\n        \n        The easiest way to install and run Certbot is by visiting `certbot.eff.org`_,\n        where you can find the correct instructions for many web server and OS\n        combinations.  For more information, see `Get Certbot\n        <https://certbot.eff.org/docs/install.html>`_.\n        \n        .. _certbot.eff.org: https://certbot.eff.org/\n        \n        Understanding the client in more depth\n        --------------------------------------\n        \n        To understand what the client is doing in detail, it's important to\n        understand the way it uses plugins.  Please see the `explanation of\n        plugins <https://certbot.eff.org/docs/using.html#plugins>`_ in\n        the User Guide.\n        \n        Links\n        =====\n        \n        .. Do not modify this comment unless you know what you're doing. tag:links-begin\n        \n        Documentation: https://certbot.eff.org/docs\n        \n        Software project: https://github.com/certbot/certbot\n        \n        Notes for developers: https://certbot.eff.org/docs/contributing.html\n        \n        Main Website: https://certbot.eff.org\n        \n        Let's Encrypt Website: https://letsencrypt.org\n        \n        Community: https://community.letsencrypt.org\n        \n        ACME spec: http://ietf-wg-acme.github.io/acme/\n        \n        ACME working area in github: https://github.com/ietf-wg-acme/acme\n        \n        |build-status| |container|\n        \n        .. |build-status| image:: https://travis-ci.com/certbot/certbot.svg?branch=master\n           :target: https://travis-ci.com/certbot/certbot\n           :alt: Travis CI status\n        \n        .. |container| image:: https://quay.io/repository/letsencrypt/letsencrypt/status\n           :target: https://quay.io/repository/letsencrypt/letsencrypt\n           :alt: Docker Repository on Quay.io\n        \n        .. Do not modify this comment unless you know what you're doing. tag:links-end\n        \n        System Requirements\n        ===================\n        \n        See https://certbot.eff.org/docs/install.html#system-requirements.\n        \n        .. Do not modify this comment unless you know what you're doing. tag:intro-end\n        \n        .. Do not modify this comment unless you know what you're doing. tag:features-begin\n        \n        Current Features\n        =====================\n        \n        * Supports multiple web servers:\n        \n          - apache/2.x\n          - nginx/0.8.48+\n          - webroot (adds files to webroot directories in order to prove control of\n            domains and obtain certs)\n          - standalone (runs its own simple webserver to prove you control a domain)\n          - other server software via `third party plugins <https://certbot.eff.org/docs/using.html#third-party-plugins>`_\n        \n        * The private key is generated locally on your system.\n        * Can talk to the Let's Encrypt CA or optionally to other ACME\n          compliant services.\n        * Can get domain-validated (DV) certificates.\n        * Can revoke certificates.\n        * Adjustable RSA key bit-length (2048 (default), 4096, ...).\n        * Can optionally install a http -> https redirect, so your site effectively\n          runs https only (Apache only)\n        * Fully automated.\n        * Configuration changes are logged and can be reverted.\n        * Supports an interactive text UI, or can be driven entirely from the\n          command line.\n        * Free and Open Source Software, made with Python.\n        \n        .. Do not modify this comment unless you know what you're doing. tag:features-end\n        \n        For extensive documentation on using and contributing to Certbot, go to https://certbot.eff.org/docs. If you would like to contribute to the project or run the latest code from git, you should read our `developer guide <https://certbot.eff.org/docs/contributing.html>`_.\n        \nPlatform: UNKNOWN\nClassifier: Development Status :: 5 - Production/Stable\nClassifier: Environment :: Console\nClassifier: Environment :: Console :: Curses\nClassifier: Intended Audience :: System Administrators\nClassifier: License :: OSI Approved :: Apache Software License\nClassifier: Operating System :: POSIX :: Linux\nClassifier: Programming Language :: Python\nClassifier: Programming Language :: Python :: 2\nClassifier: Programming Language :: Python :: 2.7\nClassifier: Programming Language :: Python :: 3\nClassifier: Programming Language :: Python :: 3.5\nClassifier: Programming Language :: Python :: 3.6\nClassifier: Programming Language :: Python :: 3.7\nClassifier: Programming Language :: Python :: 3.8\nClassifier: Topic :: Internet :: WWW/HTTP\nClassifier: Topic :: Security\nClassifier: Topic :: System :: Installation/Setup\nClassifier: Topic :: System :: Networking\nClassifier: Topic :: System :: Systems Administration\nClassifier: Topic :: Utilities\nRequires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*\nProvides-Extra: dev3\nProvides-Extra: docs\nProvides-Extra: dev\n"
  },
  {
    "path": "README.rst",
    "content": ".. This file contains a series of comments that are used to include sections of this README in other files. Do not modify these comments unless you know what you are doing. tag:intro-begin\n\nCertbot is part of EFF’s effort to encrypt the entire Internet. Secure communication over the Web relies on HTTPS, which requires the use of a digital certificate that lets browsers verify the identity of web servers (e.g., is that really google.com?). Web servers obtain their certificates from trusted third parties called certificate authorities (CAs). Certbot is an easy-to-use client that fetches a certificate from Let’s Encrypt—an open certificate authority launched by the EFF, Mozilla, and others—and deploys it to a web server.\n\nAnyone who has gone through the trouble of setting up a secure website knows what a hassle getting and maintaining a certificate is. Certbot and Let’s Encrypt can automate away the pain and let you turn on and manage HTTPS with simple commands. Using Certbot and Let's Encrypt is free, so there’s no need to arrange payment.\n\nHow you use Certbot depends on the configuration of your web server. The best way to get started is to use our `interactive guide <https://certbot.eff.org>`_. It generates instructions based on your configuration settings. In most cases, you’ll need `root or administrator access <https://certbot.eff.org/faq/#does-certbot-require-root-administrator-privileges>`_ to your web server to run Certbot.\n\nCertbot is meant to be run directly on your web server, not on your personal computer. If you’re using a hosted service and don’t have direct access to your web server, you might not be able to use Certbot. Check with your hosting provider for documentation about uploading certificates or using certificates issued by Let’s Encrypt.\n\nCertbot is a fully-featured, extensible client for the Let's\nEncrypt CA (or any other CA that speaks the `ACME\n<https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md>`_\nprotocol) that can automate the tasks of obtaining certificates and\nconfiguring webservers to use them. This client runs on Unix-based operating\nsystems.\n\nTo see the changes made to Certbot between versions please refer to our\n`changelog <https://github.com/certbot/certbot/blob/master/certbot/CHANGELOG.md>`_.\n\nUntil May 2016, Certbot was named simply ``letsencrypt`` or ``letsencrypt-auto``,\ndepending on install method. Instructions on the Internet, and some pieces of the\nsoftware, may still refer to this older name.\n\nContributing\n------------\n\nIf you'd like to contribute to this project please read `Developer Guide\n<https://certbot.eff.org/docs/contributing.html>`_.\n\nThis project is governed by `EFF's Public Projects Code of Conduct <https://www.eff.org/pages/eppcode>`_.\n\n.. _installation:\n\nHow to run the client\n---------------------\n\nThe easiest way to install and run Certbot is by visiting `certbot.eff.org`_,\nwhere you can find the correct instructions for many web server and OS\ncombinations.  For more information, see `Get Certbot\n<https://certbot.eff.org/docs/install.html>`_.\n\n.. _certbot.eff.org: https://certbot.eff.org/\n\nUnderstanding the client in more depth\n--------------------------------------\n\nTo understand what the client is doing in detail, it's important to\nunderstand the way it uses plugins.  Please see the `explanation of\nplugins <https://certbot.eff.org/docs/using.html#plugins>`_ in\nthe User Guide.\n\nLinks\n=====\n\n.. Do not modify this comment unless you know what you're doing. tag:links-begin\n\nDocumentation: https://certbot.eff.org/docs\n\nSoftware project: https://github.com/certbot/certbot\n\nNotes for developers: https://certbot.eff.org/docs/contributing.html\n\nMain Website: https://certbot.eff.org\n\nLet's Encrypt Website: https://letsencrypt.org\n\nCommunity: https://community.letsencrypt.org\n\nACME spec: http://ietf-wg-acme.github.io/acme/\n\nACME working area in github: https://github.com/ietf-wg-acme/acme\n\n|build-status| |container|\n\n.. |build-status| image:: https://travis-ci.com/certbot/certbot.svg?branch=master\n   :target: https://travis-ci.com/certbot/certbot\n   :alt: Travis CI status\n\n.. |container| image:: https://quay.io/repository/letsencrypt/letsencrypt/status\n   :target: https://quay.io/repository/letsencrypt/letsencrypt\n   :alt: Docker Repository on Quay.io\n\n.. Do not modify this comment unless you know what you're doing. tag:links-end\n\nSystem Requirements\n===================\n\nSee https://certbot.eff.org/docs/install.html#system-requirements.\n\n.. Do not modify this comment unless you know what you're doing. tag:intro-end\n\n.. Do not modify this comment unless you know what you're doing. tag:features-begin\n\nCurrent Features\n=====================\n\n* Supports multiple web servers:\n\n  - apache/2.x\n  - nginx/0.8.48+\n  - webroot (adds files to webroot directories in order to prove control of\n    domains and obtain certs)\n  - standalone (runs its own simple webserver to prove you control a domain)\n  - other server software via `third party plugins <https://certbot.eff.org/docs/using.html#third-party-plugins>`_\n\n* The private key is generated locally on your system.\n* Can talk to the Let's Encrypt CA or optionally to other ACME\n  compliant services.\n* Can get domain-validated (DV) certificates.\n* Can revoke certificates.\n* Adjustable RSA key bit-length (2048 (default), 4096, ...).\n* Can optionally install a http -> https redirect, so your site effectively\n  runs https only (Apache only)\n* Fully automated.\n* Configuration changes are logged and can be reverted.\n* Supports an interactive text UI, or can be driven entirely from the\n  command line.\n* Free and Open Source Software, made with Python.\n\n.. Do not modify this comment unless you know what you're doing. tag:features-end\n\nFor extensive documentation on using and contributing to Certbot, go to https://certbot.eff.org/docs. If you would like to contribute to the project or run the latest code from git, you should read our `developer guide <https://certbot.eff.org/docs/contributing.html>`_.\n"
  },
  {
    "path": "certbot/__init__.py",
    "content": "\"\"\"Certbot client.\"\"\"\n\n# version number like 1.2.3a0, must have at least 2 parts, like 1.2\n__version__ = '1.3.0'\n"
  },
  {
    "path": "certbot/_internal/__init__.py",
    "content": "\"\"\"\nModules internal to Certbot.\n\nThis package contains modules that are not considered part of Certbot's public\nAPI. They may be changed without updating Certbot's major version.\n\"\"\"\n"
  },
  {
    "path": "certbot/_internal/account.py",
    "content": "\"\"\"Creates ACME accounts for server.\"\"\"\nimport datetime\nimport functools\nimport hashlib\nimport logging\nimport shutil\nimport socket\n\nfrom cryptography.hazmat.primitives import serialization\nimport josepy as jose\nimport pyrfc3339\nimport pytz\nimport six\nimport zope.component\n\nfrom acme import fields as acme_fields\nfrom acme import messages\nfrom certbot import errors\nfrom certbot import interfaces\nfrom certbot import util\nfrom certbot._internal import constants\nfrom certbot.compat import os\n\nlogger = logging.getLogger(__name__)\n\n\nclass Account(object):\n    \"\"\"ACME protocol registration.\n\n    :ivar .RegistrationResource regr: Registration Resource\n    :ivar .JWK key: Authorized Account Key\n    :ivar .Meta: Account metadata\n    :ivar str id: Globally unique account identifier.\n\n    \"\"\"\n\n    class Meta(jose.JSONObjectWithFields):\n        \"\"\"Account metadata\n\n        :ivar datetime.datetime creation_dt: Creation date and time (UTC).\n        :ivar str creation_host: FQDN of host, where account has been created.\n\n        .. note:: ``creation_dt`` and ``creation_host`` are useful in\n            cross-machine migration scenarios.\n\n        \"\"\"\n        creation_dt = acme_fields.RFC3339Field(\"creation_dt\")\n        creation_host = jose.Field(\"creation_host\")\n\n    def __init__(self, regr, key, meta=None):\n        self.key = key\n        self.regr = regr\n        self.meta = self.Meta(\n            # pyrfc3339 drops microseconds, make sure __eq__ is sane\n            creation_dt=datetime.datetime.now(\n                tz=pytz.UTC).replace(microsecond=0),\n            creation_host=socket.getfqdn()) if meta is None else meta\n\n        # try MD5, else use MD5 in non-security mode (e.g. for FIPS systems / RHEL)\n        try:\n            hasher = hashlib.md5()\n        except ValueError:\n            hasher = hashlib.new('md5', usedforsecurity=False) # type: ignore\n\n        hasher.update(self.key.key.public_key().public_bytes(\n            encoding=serialization.Encoding.PEM,\n            format=serialization.PublicFormat.SubjectPublicKeyInfo)\n        )\n\n        self.id = hasher.hexdigest()\n        # Implementation note: Email? Multiple accounts can have the\n        # same email address. Registration URI? Assigned by the\n        # server, not guaranteed to be stable over time, nor\n        # canonical URI can be generated. ACME protocol doesn't allow\n        # account key (and thus its fingerprint) to be updated...\n\n    @property\n    def slug(self):\n        \"\"\"Short account identification string, useful for UI.\"\"\"\n        return \"{1}@{0} ({2})\".format(pyrfc3339.generate(\n            self.meta.creation_dt), self.meta.creation_host, self.id[:4])\n\n    def __repr__(self):\n        return \"<{0}({1}, {2}, {3})>\".format(\n            self.__class__.__name__, self.regr, self.id, self.meta)\n\n    def __eq__(self, other):\n        return (isinstance(other, self.__class__) and\n                self.key == other.key and self.regr == other.regr and\n                self.meta == other.meta)\n\n\ndef report_new_account(config):\n    \"\"\"Informs the user about their new ACME account.\"\"\"\n    reporter = zope.component.queryUtility(interfaces.IReporter)\n    if reporter is None:\n        return\n    reporter.add_message(\n        \"Your account credentials have been saved in your Certbot \"\n        \"configuration directory at {0}. You should make a secure backup \"\n        \"of this folder now. This configuration directory will also \"\n        \"contain certificates and private keys obtained by Certbot \"\n        \"so making regular backups of this folder is ideal.\".format(\n            config.config_dir),\n        reporter.MEDIUM_PRIORITY)\n\n\nclass AccountMemoryStorage(interfaces.AccountStorage):\n    \"\"\"In-memory account storage.\"\"\"\n\n    def __init__(self, initial_accounts=None):\n        self.accounts = initial_accounts if initial_accounts is not None else {}\n\n    def find_all(self):\n        return list(six.itervalues(self.accounts))\n\n    def save(self, account, client):\n        if account.id in self.accounts:\n            logger.debug(\"Overwriting account: %s\", account.id)\n        self.accounts[account.id] = account\n\n    def load(self, account_id):\n        try:\n            return self.accounts[account_id]\n        except KeyError:\n            raise errors.AccountNotFound(account_id)\n\nclass RegistrationResourceWithNewAuthzrURI(messages.RegistrationResource):\n    \"\"\"A backwards-compatible RegistrationResource with a new-authz URI.\n\n       Hack: Certbot versions pre-0.11.1 expect to load\n       new_authzr_uri as part of the account. Because people\n       sometimes switch between old and new versions, we will\n       continue to write out this field for some time so older\n       clients don't crash in that scenario.\n    \"\"\"\n    new_authzr_uri = jose.Field('new_authzr_uri')\n\nclass AccountFileStorage(interfaces.AccountStorage):\n    \"\"\"Accounts file storage.\n\n    :ivar .IConfig config: Client configuration\n\n    \"\"\"\n    def __init__(self, config):\n        self.config = config\n        util.make_or_verify_dir(config.accounts_dir, 0o700, self.config.strict_permissions)\n\n    def _account_dir_path(self, account_id):\n        return self._account_dir_path_for_server_path(account_id, self.config.server_path)\n\n    def _account_dir_path_for_server_path(self, account_id, server_path):\n        accounts_dir = self.config.accounts_dir_for_server_path(server_path)\n        return os.path.join(accounts_dir, account_id)\n\n    @classmethod\n    def _regr_path(cls, account_dir_path):\n        return os.path.join(account_dir_path, \"regr.json\")\n\n    @classmethod\n    def _key_path(cls, account_dir_path):\n        return os.path.join(account_dir_path, \"private_key.json\")\n\n    @classmethod\n    def _metadata_path(cls, account_dir_path):\n        return os.path.join(account_dir_path, \"meta.json\")\n\n    def _find_all_for_server_path(self, server_path):\n        accounts_dir = self.config.accounts_dir_for_server_path(server_path)\n        try:\n            candidates = os.listdir(accounts_dir)\n        except OSError:\n            return []\n\n        accounts = []\n        for account_id in candidates:\n            try:\n                accounts.append(self._load_for_server_path(account_id, server_path))\n            except errors.AccountStorageError:\n                logger.debug(\"Account loading problem\", exc_info=True)\n\n        if not accounts and server_path in constants.LE_REUSE_SERVERS:\n            # find all for the next link down\n            prev_server_path = constants.LE_REUSE_SERVERS[server_path]\n            prev_accounts = self._find_all_for_server_path(prev_server_path)\n            # if we found something, link to that\n            if prev_accounts:\n                try:\n                    self._symlink_to_accounts_dir(prev_server_path, server_path)\n                except OSError:\n                    return []\n            accounts = prev_accounts\n        return accounts\n\n    def find_all(self):\n        return self._find_all_for_server_path(self.config.server_path)\n\n    def _symlink_to_account_dir(self, prev_server_path, server_path, account_id):\n        prev_account_dir = self._account_dir_path_for_server_path(account_id, prev_server_path)\n        new_account_dir = self._account_dir_path_for_server_path(account_id, server_path)\n        os.symlink(prev_account_dir, new_account_dir)\n\n    def _symlink_to_accounts_dir(self, prev_server_path, server_path):\n        accounts_dir = self.config.accounts_dir_for_server_path(server_path)\n        if os.path.islink(accounts_dir):\n            os.unlink(accounts_dir)\n        else:\n            os.rmdir(accounts_dir)\n        prev_account_dir = self.config.accounts_dir_for_server_path(prev_server_path)\n        os.symlink(prev_account_dir, accounts_dir)\n\n    def _load_for_server_path(self, account_id, server_path):\n        account_dir_path = self._account_dir_path_for_server_path(account_id, server_path)\n        if not os.path.isdir(account_dir_path): # isdir is also true for symlinks\n            if server_path in constants.LE_REUSE_SERVERS:\n                prev_server_path = constants.LE_REUSE_SERVERS[server_path]\n                prev_loaded_account = self._load_for_server_path(account_id, prev_server_path)\n                # we didn't error so we found something, so create a symlink to that\n                accounts_dir = self.config.accounts_dir_for_server_path(server_path)\n                # If accounts_dir isn't empty, make an account specific symlink\n                if os.listdir(accounts_dir):\n                    self._symlink_to_account_dir(prev_server_path, server_path, account_id)\n                else:\n                    self._symlink_to_accounts_dir(prev_server_path, server_path)\n                return prev_loaded_account\n            raise errors.AccountNotFound(\n                \"Account at %s does not exist\" % account_dir_path)\n\n        try:\n            with open(self._regr_path(account_dir_path)) as regr_file:\n                regr = messages.RegistrationResource.json_loads(regr_file.read())\n            with open(self._key_path(account_dir_path)) as key_file:\n                key = jose.JWK.json_loads(key_file.read())\n            with open(self._metadata_path(account_dir_path)) as metadata_file:\n                meta = Account.Meta.json_loads(metadata_file.read())\n        except IOError as error:\n            raise errors.AccountStorageError(error)\n\n        return Account(regr, key, meta)\n\n    def load(self, account_id):\n        return self._load_for_server_path(account_id, self.config.server_path)\n\n    def save(self, account, client):\n        self._save(account, client, regr_only=False)\n\n    def save_regr(self, account, acme):\n        \"\"\"Save the registration resource.\n\n        :param Account account: account whose regr should be saved\n\n        \"\"\"\n        self._save(account, acme, regr_only=True)\n\n    def delete(self, account_id):\n        \"\"\"Delete registration info from disk\n\n        :param account_id: id of account which should be deleted\n\n        \"\"\"\n        account_dir_path = self._account_dir_path(account_id)\n        if not os.path.isdir(account_dir_path):\n            raise errors.AccountNotFound(\n                \"Account at %s does not exist\" % account_dir_path)\n        # Step 1: Delete account specific links and the directory\n        self._delete_account_dir_for_server_path(account_id, self.config.server_path)\n\n        # Step 2: Remove any accounts links and directories that are now empty\n        if not os.listdir(self.config.accounts_dir):\n            self._delete_accounts_dir_for_server_path(self.config.server_path)\n\n    def _delete_account_dir_for_server_path(self, account_id, server_path):\n        link_func = functools.partial(self._account_dir_path_for_server_path, account_id)\n        nonsymlinked_dir = self._delete_links_and_find_target_dir(server_path, link_func)\n        shutil.rmtree(nonsymlinked_dir)\n\n    def _delete_accounts_dir_for_server_path(self, server_path):\n        link_func = self.config.accounts_dir_for_server_path\n        nonsymlinked_dir = self._delete_links_and_find_target_dir(server_path, link_func)\n        os.rmdir(nonsymlinked_dir)\n\n    def _delete_links_and_find_target_dir(self, server_path, link_func):\n        \"\"\"Delete symlinks and return the nonsymlinked directory path.\n\n        :param str server_path: file path based on server\n        :param callable link_func: callable that returns possible links\n            given a server_path\n\n        :returns: the final, non-symlinked target\n        :rtype: str\n\n        \"\"\"\n        dir_path = link_func(server_path)\n\n        # does an appropriate directory link to me? if so, make sure that's gone\n        reused_servers = {}\n        for k in constants.LE_REUSE_SERVERS:\n            reused_servers[constants.LE_REUSE_SERVERS[k]] = k\n\n        # is there a next one up?\n        possible_next_link = True\n        while possible_next_link:\n            possible_next_link = False\n            if server_path in reused_servers:\n                next_server_path = reused_servers[server_path]\n                next_dir_path = link_func(next_server_path)\n                if os.path.islink(next_dir_path) and os.readlink(next_dir_path) == dir_path:\n                    possible_next_link = True\n                    server_path = next_server_path\n                    dir_path = next_dir_path\n\n        # if there's not a next one up to delete, then delete me\n        # and whatever I link to\n        while os.path.islink(dir_path):\n            target = os.readlink(dir_path)\n            os.unlink(dir_path)\n            dir_path = target\n\n        return dir_path\n\n    def _save(self, account, acme, regr_only):\n        account_dir_path = self._account_dir_path(account.id)\n        util.make_or_verify_dir(account_dir_path, 0o700, self.config.strict_permissions)\n        try:\n            with open(self._regr_path(account_dir_path), \"w\") as regr_file:\n                regr = account.regr\n                # If we have a value for new-authz, save it for forwards\n                # compatibility with older versions of Certbot. If we don't\n                # have a value for new-authz, this is an ACMEv2 directory where\n                # an older version of Certbot won't work anyway.\n                if hasattr(acme.directory, \"new-authz\"):\n                    regr = RegistrationResourceWithNewAuthzrURI(\n                        new_authzr_uri=acme.directory.new_authz,\n                        body={},\n                        uri=regr.uri)\n                else:\n                    regr = messages.RegistrationResource(\n                        body={},\n                        uri=regr.uri)\n                regr_file.write(regr.json_dumps())\n            if not regr_only:\n                with util.safe_open(self._key_path(account_dir_path),\n                                    \"w\", chmod=0o400) as key_file:\n                    key_file.write(account.key.json_dumps())\n                with open(self._metadata_path(\n                        account_dir_path), \"w\") as metadata_file:\n                    metadata_file.write(account.meta.json_dumps())\n        except IOError as error:\n            raise errors.AccountStorageError(error)\n"
  },
  {
    "path": "certbot/_internal/auth_handler.py",
    "content": "\"\"\"ACME AuthHandler.\"\"\"\nimport datetime\nimport logging\nimport time\n\nimport zope.component\n\nfrom acme import challenges\nfrom acme import errors as acme_errors\nfrom acme import messages\nfrom acme.magic_typing import Dict\nfrom acme.magic_typing import List\nfrom acme.magic_typing import Tuple\nfrom certbot import achallenges\nfrom certbot import errors\nfrom certbot import interfaces\nfrom certbot._internal import error_handler\n\nlogger = logging.getLogger(__name__)\n\n\nclass AuthHandler(object):\n    \"\"\"ACME Authorization Handler for a client.\n\n    :ivar auth: Authenticator capable of solving\n        :class:`~acme.challenges.Challenge` types\n    :type auth: :class:`certbot.interfaces.IAuthenticator`\n\n    :ivar acme.client.BackwardsCompatibleClientV2 acme_client: ACME client API.\n\n    :ivar account: Client's Account\n    :type account: :class:`certbot._internal.account.Account`\n\n    :ivar list pref_challs: sorted user specified preferred challenges\n        type strings with the most preferred challenge listed first\n\n    \"\"\"\n    def __init__(self, auth, acme_client, account, pref_challs):\n        self.auth = auth\n        self.acme = acme_client\n\n        self.account = account\n        self.pref_challs = pref_challs\n\n    def handle_authorizations(self, orderr, best_effort=False, max_retries=30):\n        \"\"\"\n        Retrieve all authorizations, perform all challenges required to validate\n        these authorizations, then poll and wait for the authorization to be checked.\n        :param acme.messages.OrderResource orderr: must have authorizations filled in\n        :param bool best_effort: if True, not all authorizations need to be validated (eg. renew)\n        :param int max_retries: maximum number of retries to poll authorizations\n        :returns: list of all validated authorizations\n        :rtype: List\n\n        :raises .AuthorizationError: If unable to retrieve all authorizations\n        \"\"\"\n        authzrs = orderr.authorizations[:]\n        if not authzrs:\n            raise errors.AuthorizationError('No authorization to handle.')\n\n        # Retrieve challenges that need to be performed to validate authorizations.\n        achalls = self._choose_challenges(authzrs)\n        if not achalls:\n            return authzrs\n\n        # Starting now, challenges will be cleaned at the end no matter what.\n        with error_handler.ExitHandler(self._cleanup_challenges, achalls):\n            # To begin, let's ask the authenticator plugin to perform all challenges.\n            try:\n                resps = self.auth.perform(achalls)\n\n                # If debug is on, wait for user input before starting the verification process.\n                logger.info('Waiting for verification...')\n                config = zope.component.getUtility(interfaces.IConfig)\n                if config.debug_challenges:\n                    notify = zope.component.getUtility(interfaces.IDisplay).notification\n                    notify('Challenges loaded. Press continue to submit to CA. '\n                           'Pass \"-v\" for more info about challenges.', pause=True)\n            except errors.AuthorizationError as error:\n                logger.critical('Failure in setting up challenges.')\n                logger.info('Attempting to clean up outstanding challenges...')\n                raise error\n            # All challenges should have been processed by the authenticator.\n            assert len(resps) == len(achalls), 'Some challenges have not been performed.'\n\n            # Inform the ACME CA server that challenges are available for validation.\n            for achall, resp in zip(achalls, resps):\n                self.acme.answer_challenge(achall.challb, resp)\n\n            # Wait for authorizations to be checked.\n            self._poll_authorizations(authzrs, max_retries, best_effort)\n\n            # Keep validated authorizations only. If there is none, no certificate can be issued.\n            authzrs_validated = [authzr for authzr in authzrs\n                                 if authzr.body.status == messages.STATUS_VALID]\n            if not authzrs_validated:\n                raise errors.AuthorizationError('All challenges have failed.')\n\n            return authzrs_validated\n\n    def deactivate_valid_authorizations(self, orderr):\n        # type: (messages.OrderResource) -> Tuple[List, List]\n        \"\"\"\n        Deactivate all `valid` authorizations in the order, so that they cannot be re-used\n        in subsequent orders.\n        :param messages.OrderResource orderr: must have authorizations filled in\n        :returns: tuple of list of successfully deactivated authorizations, and\n                  list of unsuccessfully deactivated authorizations.\n        :rtype: tuple\n        \"\"\"\n        to_deactivate = [authzr for authzr in orderr.authorizations\n                         if authzr.body.status == messages.STATUS_VALID]\n        deactivated = []\n        failed = []\n\n        for authzr in to_deactivate:\n            try:\n                authzr = self.acme.deactivate_authorization(authzr)\n                deactivated.append(authzr)\n            except acme_errors.Error as e:\n                failed.append(authzr)\n                logger.debug('Failed to deactivate authorization %s: %s', authzr.uri, e)\n\n        return (deactivated, failed)\n\n    def _poll_authorizations(self, authzrs, max_retries, best_effort):\n        \"\"\"\n        Poll the ACME CA server, to wait for confirmation that authorizations have their challenges\n        all verified. The poll may occur several times, until all authorizations are checked\n        (valid or invalid), or after a maximum of retries.\n        \"\"\"\n        authzrs_to_check = {index: (authzr, None)\n                            for index, authzr in enumerate(authzrs)}\n        authzrs_failed_to_report = []\n        # Give an initial second to the ACME CA server to check the authorizations\n        sleep_seconds = 1\n        for _ in range(max_retries):\n            # Wait for appropriate time (from Retry-After, initial wait, or no wait)\n            if sleep_seconds > 0:\n                time.sleep(sleep_seconds)\n            # Poll all updated authorizations.\n            authzrs_to_check = {index: self.acme.poll(authzr) for index, (authzr, _)\n                                in authzrs_to_check.items()}\n            # Update the original list of authzr with the updated authzrs from server.\n            for index, (authzr, _) in authzrs_to_check.items():\n                authzrs[index] = authzr\n\n            # Gather failed authorizations\n            authzrs_failed = [authzr for authzr, _ in authzrs_to_check.values()\n                              if authzr.body.status == messages.STATUS_INVALID]\n            for authzr_failed in authzrs_failed:\n                logger.warning('Challenge failed for domain %s',\n                               authzr_failed.body.identifier.value)\n            # Accumulating all failed authzrs to build a consolidated report\n            # on them at the end of the polling.\n            authzrs_failed_to_report.extend(authzrs_failed)\n\n            # Extract out the authorization already checked for next poll iteration.\n            # Poll may stop here because there is no pending authorizations anymore.\n            authzrs_to_check = {index: (authzr, resp) for index, (authzr, resp)\n                                in authzrs_to_check.items()\n                                if authzr.body.status == messages.STATUS_PENDING}\n            if not authzrs_to_check:\n                # Polling process is finished, we can leave the loop\n                break\n\n            # Be merciful with the ACME server CA, check the Retry-After header returned,\n            # and wait this time before polling again in next loop iteration.\n            # From all the pending authorizations, we take the greatest Retry-After value\n            # to avoid polling an authorization before its relevant Retry-After value.\n            retry_after = max(self.acme.retry_after(resp, 3)\n                              for _, resp in authzrs_to_check.values())\n            sleep_seconds = (retry_after - datetime.datetime.now()).total_seconds()\n\n        # In case of failed authzrs, create a report to the user.\n        if authzrs_failed_to_report:\n            _report_failed_authzrs(authzrs_failed_to_report, self.account.key)\n            if not best_effort:\n                # Without best effort, having failed authzrs is critical and fail the process.\n                raise errors.AuthorizationError('Some challenges have failed.')\n\n        if authzrs_to_check:\n            # Here authzrs_to_check is still not empty, meaning we exceeded the max polling attempt.\n            raise errors.AuthorizationError('All authorizations were not finalized by the CA.')\n\n    def _choose_challenges(self, authzrs):\n        \"\"\"\n        Retrieve necessary and pending challenges to satisfy server.\n        NB: Necessary and already validated challenges are not retrieved,\n        as they can be reused for a certificate issuance.\n        \"\"\"\n        pending_authzrs = [authzr for authzr in authzrs\n                           if authzr.body.status != messages.STATUS_VALID]\n        achalls = []  # type: List[achallenges.AnnotatedChallenge]\n        if pending_authzrs:\n            logger.info(\"Performing the following challenges:\")\n        for authzr in pending_authzrs:\n            authzr_challenges = authzr.body.challenges\n            if self.acme.acme_version == 1:\n                combinations = authzr.body.combinations\n            else:\n                combinations = tuple((i,) for i in range(len(authzr_challenges)))\n\n            path = gen_challenge_path(\n                authzr_challenges,\n                self._get_chall_pref(authzr.body.identifier.value),\n                combinations)\n\n            achalls.extend(self._challenge_factory(authzr, path))\n\n        return achalls\n\n    def _get_chall_pref(self, domain):\n        \"\"\"Return list of challenge preferences.\n\n        :param str domain: domain for which you are requesting preferences\n\n        \"\"\"\n        chall_prefs = []\n        # Make sure to make a copy...\n        plugin_pref = self.auth.get_chall_pref(domain)\n        if self.pref_challs:\n            plugin_pref_types = set(chall.typ for chall in plugin_pref)\n            for typ in self.pref_challs:\n                if typ in plugin_pref_types:\n                    chall_prefs.append(challenges.Challenge.TYPES[typ])\n            if chall_prefs:\n                return chall_prefs\n            raise errors.AuthorizationError(\n                \"None of the preferred challenges \"\n                \"are supported by the selected plugin\")\n        chall_prefs.extend(plugin_pref)\n        return chall_prefs\n\n    def _cleanup_challenges(self, achalls):\n        \"\"\"Cleanup challenges.\n\n        :param achalls: annotated challenges to cleanup\n        :type achalls: `list` of :class:`certbot.achallenges.AnnotatedChallenge`\n\n        \"\"\"\n        logger.info(\"Cleaning up challenges\")\n        self.auth.cleanup(achalls)\n\n    def _challenge_factory(self, authzr, path):\n        \"\"\"Construct Namedtuple Challenges\n\n        :param messages.AuthorizationResource authzr: authorization\n\n        :param list path: List of indices from `challenges`.\n\n        :returns: achalls, list of challenge type\n            :class:`certbot.achallenges.Indexed`\n        :rtype: list\n\n        :raises .errors.Error: if challenge type is not recognized\n\n        \"\"\"\n        achalls = []\n\n        for index in path:\n            challb = authzr.body.challenges[index]\n            achalls.append(challb_to_achall(\n                challb, self.account.key, authzr.body.identifier.value))\n\n        return achalls\n\n\ndef challb_to_achall(challb, account_key, domain):\n    \"\"\"Converts a ChallengeBody object to an AnnotatedChallenge.\n\n    :param .ChallengeBody challb: ChallengeBody\n    :param .JWK account_key: Authorized Account Key\n    :param str domain: Domain of the challb\n\n    :returns: Appropriate AnnotatedChallenge\n    :rtype: :class:`certbot.achallenges.AnnotatedChallenge`\n\n    \"\"\"\n    chall = challb.chall\n    logger.info(\"%s challenge for %s\", chall.typ, domain)\n\n    if isinstance(chall, challenges.KeyAuthorizationChallenge):\n        return achallenges.KeyAuthorizationAnnotatedChallenge(\n            challb=challb, domain=domain, account_key=account_key)\n    elif isinstance(chall, challenges.DNS):\n        return achallenges.DNS(challb=challb, domain=domain)\n    raise errors.Error(\n        \"Received unsupported challenge of type: {0}\".format(chall.typ))\n\n\ndef gen_challenge_path(challbs, preferences, combinations):\n    \"\"\"Generate a plan to get authority over the identity.\n\n    .. todo:: This can be possibly be rewritten to use resolved_combinations.\n\n    :param tuple challbs: A tuple of challenges\n        (:class:`acme.messages.Challenge`) from\n        :class:`acme.messages.AuthorizationResource` to be\n        fulfilled by the client in order to prove possession of the\n        identifier.\n\n    :param list preferences: List of challenge preferences for domain\n        (:class:`acme.challenges.Challenge` subclasses)\n\n    :param tuple combinations: A collection of sets of challenges from\n        :class:`acme.messages.Challenge`, each of which would\n        be sufficient to prove possession of the identifier.\n\n    :returns: tuple of indices from ``challenges``.\n    :rtype: tuple\n\n    :raises certbot.errors.AuthorizationError: If a\n        path cannot be created that satisfies the CA given the preferences and\n        combinations.\n\n    \"\"\"\n    if combinations:\n        return _find_smart_path(challbs, preferences, combinations)\n    return _find_dumb_path(challbs, preferences)\n\n\ndef _find_smart_path(challbs, preferences, combinations):\n    \"\"\"Find challenge path with server hints.\n\n    Can be called if combinations is included. Function uses a simple\n    ranking system to choose the combo with the lowest cost.\n\n    \"\"\"\n    chall_cost = {}\n    max_cost = 1\n    for i, chall_cls in enumerate(preferences):\n        chall_cost[chall_cls] = i\n        max_cost += i\n\n    # max_cost is now equal to sum(indices) + 1\n\n    best_combo = None\n    # Set above completing all of the available challenges\n    best_combo_cost = max_cost\n\n    combo_total = 0\n    for combo in combinations:\n        for challenge_index in combo:\n            combo_total += chall_cost.get(challbs[\n                challenge_index].chall.__class__, max_cost)\n\n        if combo_total < best_combo_cost:\n            best_combo = combo\n            best_combo_cost = combo_total\n\n        combo_total = 0\n\n    if not best_combo:\n        _report_no_chall_path(challbs)\n\n    return best_combo\n\n\ndef _find_dumb_path(challbs, preferences):\n    \"\"\"Find challenge path without server hints.\n\n    Should be called if the combinations hint is not included by the\n    server. This function either returns a path containing all\n    challenges provided by the CA or raises an exception.\n\n    \"\"\"\n    path = []\n    for i, challb in enumerate(challbs):\n        # supported is set to True if the challenge type is supported\n        supported = next((True for pref_c in preferences\n                          if isinstance(challb.chall, pref_c)), False)\n        if supported:\n            path.append(i)\n        else:\n            _report_no_chall_path(challbs)\n\n    return path\n\n\ndef _report_no_chall_path(challbs):\n    \"\"\"Logs and raises an error that no satisfiable chall path exists.\n\n    :param challbs: challenges from the authorization that can't be satisfied\n\n    \"\"\"\n    msg = (\"Client with the currently selected authenticator does not support \"\n           \"any combination of challenges that will satisfy the CA.\")\n    if len(challbs) == 1 and isinstance(challbs[0].chall, challenges.DNS01):\n        msg += (\n            \" You may need to use an authenticator \"\n            \"plugin that can do challenges over DNS.\")\n    logger.critical(msg)\n    raise errors.AuthorizationError(msg)\n\n\n_ERROR_HELP_COMMON = (\n    \"To fix these errors, please make sure that your domain name was entered \"\n    \"correctly and the DNS A/AAAA record(s) for that domain contain(s) the \"\n    \"right IP address.\")\n\n\n_ERROR_HELP = {\n    \"connection\":\n        _ERROR_HELP_COMMON + \" Additionally, please check that your computer \"\n        \"has a publicly routable IP address and that no firewalls are preventing \"\n        \"the server from communicating with the client. If you're using the \"\n        \"webroot plugin, you should also verify that you are serving files \"\n        \"from the webroot path you provided.\",\n    \"dnssec\":\n        _ERROR_HELP_COMMON + \" Additionally, if you have DNSSEC enabled for \"\n        \"your domain, please ensure that the signature is valid.\",\n    \"malformed\":\n        \"To fix these errors, please make sure that you did not provide any \"\n        \"invalid information to the client, and try running Certbot \"\n        \"again.\",\n    \"serverInternal\":\n        \"Unfortunately, an error on the ACME server prevented you from completing \"\n        \"authorization. Please try again later.\",\n    \"tls\":\n        _ERROR_HELP_COMMON + \" Additionally, please check that you have an \"\n        \"up-to-date TLS configuration that allows the server to communicate \"\n        \"with the Certbot client.\",\n    \"unauthorized\": _ERROR_HELP_COMMON,\n    \"unknownHost\": _ERROR_HELP_COMMON,\n}\n\n\ndef _report_failed_authzrs(failed_authzrs, account_key):\n    \"\"\"Notifies the user about failed authorizations.\"\"\"\n    problems = {}  # type: Dict[str, List[achallenges.AnnotatedChallenge]]\n    failed_achalls = [challb_to_achall(challb, account_key, authzr.body.identifier.value)\n                      for authzr in failed_authzrs for challb in authzr.body.challenges\n                      if challb.error]\n\n    for achall in failed_achalls:\n        problems.setdefault(achall.error.typ, []).append(achall)\n\n    reporter = zope.component.getUtility(interfaces.IReporter)\n    for achalls in problems.values():\n        reporter.add_message(_generate_failed_chall_msg(achalls), reporter.MEDIUM_PRIORITY)\n\n\ndef _generate_failed_chall_msg(failed_achalls):\n    \"\"\"Creates a user friendly error message about failed challenges.\n\n    :param list failed_achalls: A list of failed\n        :class:`certbot.achallenges.AnnotatedChallenge` with the same error\n        type.\n\n    :returns: A formatted error message for the client.\n    :rtype: str\n\n    \"\"\"\n    error = failed_achalls[0].error\n    typ = error.typ\n    if messages.is_acme_error(error):\n        typ = error.code\n    msg = [\"The following errors were reported by the server:\"]\n\n    for achall in failed_achalls:\n        msg.append(\"\\n\\nDomain: %s\\nType:   %s\\nDetail: %s\" % (\n            achall.domain, typ, achall.error.detail))\n\n    if typ in _ERROR_HELP:\n        msg.append(\"\\n\\n\")\n        msg.append(_ERROR_HELP[typ])\n\n    return \"\".join(msg)\n"
  },
  {
    "path": "certbot/_internal/cert_manager.py",
    "content": "\"\"\"Tools for managing certificates.\"\"\"\nimport datetime\nimport logging\nimport re\nimport traceback\n\nimport pytz\nimport zope.component\n\nfrom acme.magic_typing import List\nfrom certbot import crypto_util\nfrom certbot import errors\nfrom certbot import interfaces\nfrom certbot import ocsp\nfrom certbot import util\nfrom certbot._internal import storage\nfrom certbot.compat import os\nfrom certbot.display import util as display_util\n\nlogger = logging.getLogger(__name__)\n\n###################\n# Commands\n###################\n\ndef update_live_symlinks(config):\n    \"\"\"Update the certificate file family symlinks to use archive_dir.\n\n    Use the information in the config file to make symlinks point to\n    the correct archive directory.\n\n    .. note:: This assumes that the installation is using a Reverter object.\n\n    :param config: Configuration.\n    :type config: :class:`certbot._internal.configuration.NamespaceConfig`\n\n    \"\"\"\n    for renewal_file in storage.renewal_conf_files(config):\n        storage.RenewableCert(renewal_file, config, update_symlinks=True)\n\ndef rename_lineage(config):\n    \"\"\"Rename the specified lineage to the new name.\n\n    :param config: Configuration.\n    :type config: :class:`certbot._internal.configuration.NamespaceConfig`\n\n    \"\"\"\n    disp = zope.component.getUtility(interfaces.IDisplay)\n\n    certname = get_certnames(config, \"rename\")[0]\n\n    new_certname = config.new_certname\n    if not new_certname:\n        code, new_certname = disp.input(\n            \"Enter the new name for certificate {0}\".format(certname),\n            flag=\"--updated-cert-name\", force_interactive=True)\n        if code != display_util.OK or not new_certname:\n            raise errors.Error(\"User ended interaction.\")\n\n    lineage = lineage_for_certname(config, certname)\n    if not lineage:\n        raise errors.ConfigurationError(\"No existing certificate with name \"\n            \"{0} found.\".format(certname))\n    storage.rename_renewal_config(certname, new_certname, config)\n    disp.notification(\"Successfully renamed {0} to {1}.\"\n        .format(certname, new_certname), pause=False)\n\ndef certificates(config):\n    \"\"\"Display information about certs configured with Certbot\n\n    :param config: Configuration.\n    :type config: :class:`certbot._internal.configuration.NamespaceConfig`\n    \"\"\"\n    parsed_certs = []\n    parse_failures = []\n    for renewal_file in storage.renewal_conf_files(config):\n        try:\n            renewal_candidate = storage.RenewableCert(renewal_file, config)\n            crypto_util.verify_renewable_cert(renewal_candidate)\n            parsed_certs.append(renewal_candidate)\n        except Exception as e:  # pylint: disable=broad-except\n            logger.warning(\"Renewal configuration file %s produced an \"\n                           \"unexpected error: %s. Skipping.\", renewal_file, e)\n            logger.debug(\"Traceback was:\\n%s\", traceback.format_exc())\n            parse_failures.append(renewal_file)\n\n    # Describe all the certs\n    _describe_certs(config, parsed_certs, parse_failures)\n\ndef delete(config):\n    \"\"\"Delete Certbot files associated with a certificate lineage.\"\"\"\n    certnames = get_certnames(config, \"delete\", allow_multiple=True)\n    for certname in certnames:\n        storage.delete_files(config, certname)\n        disp = zope.component.getUtility(interfaces.IDisplay)\n        disp.notification(\"Deleted all files relating to certificate {0}.\"\n            .format(certname), pause=False)\n\n###################\n# Public Helpers\n###################\n\ndef lineage_for_certname(cli_config, certname):\n    \"\"\"Find a lineage object with name certname.\"\"\"\n    configs_dir = cli_config.renewal_configs_dir\n    # Verify the directory is there\n    util.make_or_verify_dir(configs_dir, mode=0o755)\n    try:\n        renewal_file = storage.renewal_file_for_certname(cli_config, certname)\n    except errors.CertStorageError:\n        return None\n    try:\n        return storage.RenewableCert(renewal_file, cli_config)\n    except (errors.CertStorageError, IOError):\n        logger.debug(\"Renewal conf file %s is broken.\", renewal_file)\n        logger.debug(\"Traceback was:\\n%s\", traceback.format_exc())\n        return None\n\ndef domains_for_certname(config, certname):\n    \"\"\"Find the domains in the cert with name certname.\"\"\"\n    lineage = lineage_for_certname(config, certname)\n    return lineage.names() if lineage else None\n\ndef find_duplicative_certs(config, domains):\n    \"\"\"Find existing certs that match the given domain names.\n\n    This function searches for certificates whose domains are equal to\n    the `domains` parameter and certificates whose domains are a subset\n    of the domains in the `domains` parameter. If multiple certificates\n    are found whose names are a subset of `domains`, the one whose names\n    are the largest subset of `domains` is returned.\n\n    If multiple certificates' domains are an exact match or equally\n    sized subsets, which matching certificates are returned is\n    undefined.\n\n    :param config: Configuration.\n    :type config: :class:`certbot._internal.configuration.NamespaceConfig`\n    :param domains: List of domain names\n    :type domains: `list` of `str`\n\n    :returns: lineages representing the identically matching cert and the\n        largest subset if they exist\n    :rtype: `tuple` of `storage.RenewableCert` or `None`\n\n    \"\"\"\n    def update_certs_for_domain_matches(candidate_lineage, rv):\n        \"\"\"Return cert as identical_names_cert if it matches,\n           or subset_names_cert if it matches as subset\n        \"\"\"\n        # TODO: Handle these differently depending on whether they are\n        #       expired or still valid?\n        identical_names_cert, subset_names_cert = rv\n        candidate_names = set(candidate_lineage.names())\n        if candidate_names == set(domains):\n            identical_names_cert = candidate_lineage\n        elif candidate_names.issubset(set(domains)):\n            # This logic finds and returns the largest subset-names cert\n            # in the case where there are several available.\n            if subset_names_cert is None:\n                subset_names_cert = candidate_lineage\n            elif len(candidate_names) > len(subset_names_cert.names()):\n                subset_names_cert = candidate_lineage\n        return (identical_names_cert, subset_names_cert)\n\n    return _search_lineages(config, update_certs_for_domain_matches, (None, None))\n\ndef _archive_files(candidate_lineage, filetype):\n    \"\"\" In order to match things like:\n        /etc/letsencrypt/archive/example.com/chain1.pem.\n\n        Anonymous functions which call this function are eventually passed (in a list) to\n        `match_and_check_overlaps` to help specify the acceptable_matches.\n\n        :param `.storage.RenewableCert` candidate_lineage: Lineage whose archive dir is to\n            be searched.\n        :param str filetype: main file name prefix e.g. \"fullchain\" or \"chain\".\n\n        :returns: Files in candidate_lineage's archive dir that match the provided filetype.\n        :rtype: list of str or None\n    \"\"\"\n    archive_dir = candidate_lineage.archive_dir\n    pattern = [os.path.join(archive_dir, f) for f in os.listdir(archive_dir)\n                    if re.match(\"{0}[0-9]*.pem\".format(filetype), f)]\n    if pattern:\n        return pattern\n    return None\n\ndef _acceptable_matches():\n    \"\"\" Generates the list that's passed to match_and_check_overlaps. Is its own function to\n    make unit testing easier.\n\n    :returns: list of functions\n    :rtype: list\n    \"\"\"\n    return [lambda x: x.fullchain_path, lambda x: x.cert_path,\n            lambda x: _archive_files(x, \"cert\"), lambda x: _archive_files(x, \"fullchain\")]\n\ndef cert_path_to_lineage(cli_config):\n    \"\"\" If config.cert_path is defined, try to find an appropriate value for config.certname.\n\n    :param `configuration.NamespaceConfig` cli_config: parsed command line arguments\n\n    :returns: a lineage name\n    :rtype: str\n\n    :raises `errors.Error`: If the specified cert path can't be matched to a lineage name.\n    :raises `errors.OverlappingMatchFound`: If the matched lineage's archive is shared.\n    \"\"\"\n    acceptable_matches = _acceptable_matches()\n    match = match_and_check_overlaps(cli_config, acceptable_matches,\n            lambda x: cli_config.cert_path[0], lambda x: x.lineagename)\n    return match[0]\n\ndef match_and_check_overlaps(cli_config, acceptable_matches, match_func, rv_func):\n    \"\"\" Searches through all lineages for a match, and checks for duplicates.\n    If a duplicate is found, an error is raised, as performing operations on lineages\n    that have their properties incorrectly duplicated elsewhere is probably a bad idea.\n\n    :param `configuration.NamespaceConfig` cli_config: parsed command line arguments\n    :param list acceptable_matches: a list of functions that specify acceptable matches\n    :param function match_func: specifies what to match\n    :param function rv_func: specifies what to return\n\n    \"\"\"\n    def find_matches(candidate_lineage, return_value, acceptable_matches):\n        \"\"\"Returns a list of matches using _search_lineages.\"\"\"\n        acceptable_matches = [func(candidate_lineage) for func in acceptable_matches]\n        acceptable_matches_rv = []  # type: List[str]\n        for item in acceptable_matches:\n            if isinstance(item, list):\n                acceptable_matches_rv += item\n            else:\n                acceptable_matches_rv.append(item)\n        match = match_func(candidate_lineage)\n        if match in acceptable_matches_rv:\n            return_value.append(rv_func(candidate_lineage))\n        return return_value\n\n    matched = _search_lineages(cli_config, find_matches, [], acceptable_matches)\n    if not matched:\n        raise errors.Error(\"No match found for cert-path {0}!\".format(cli_config.cert_path[0]))\n    elif len(matched) > 1:\n        raise errors.OverlappingMatchFound()\n    return matched\n\n\ndef human_readable_cert_info(config, cert, skip_filter_checks=False):\n    \"\"\" Returns a human readable description of info about a RenewableCert object\"\"\"\n    certinfo = []\n    checker = ocsp.RevocationChecker()\n\n    if config.certname and cert.lineagename != config.certname and not skip_filter_checks:\n        return \"\"\n    if config.domains and not set(config.domains).issubset(cert.names()):\n        return \"\"\n    now = pytz.UTC.fromutc(datetime.datetime.utcnow())\n\n    reasons = []\n    if cert.is_test_cert:\n        reasons.append('TEST_CERT')\n    if cert.target_expiry <= now:\n        reasons.append('EXPIRED')\n    elif checker.ocsp_revoked(cert):\n        reasons.append('REVOKED')\n\n    if reasons:\n        status = \"INVALID: \" + \", \".join(reasons)\n    else:\n        diff = cert.target_expiry - now\n        if diff.days == 1:\n            status = \"VALID: 1 day\"\n        elif diff.days < 1:\n            status = \"VALID: {0} hour(s)\".format(diff.seconds // 3600)\n        else:\n            status = \"VALID: {0} days\".format(diff.days)\n\n    valid_string = \"{0} ({1})\".format(cert.target_expiry, status)\n    certinfo.append(\"  Certificate Name: {0}\\n\"\n                    \"    Domains: {1}\\n\"\n                    \"    Expiry Date: {2}\\n\"\n                    \"    Certificate Path: {3}\\n\"\n                    \"    Private Key Path: {4}\".format(\n                         cert.lineagename,\n                         \" \".join(cert.names()),\n                         valid_string,\n                         cert.fullchain,\n                         cert.privkey))\n    return \"\".join(certinfo)\n\ndef get_certnames(config, verb, allow_multiple=False, custom_prompt=None):\n    \"\"\"Get certname from flag, interactively, or error out.\n    \"\"\"\n    certname = config.certname\n    if certname:\n        certnames = [certname]\n    else:\n        disp = zope.component.getUtility(interfaces.IDisplay)\n        filenames = storage.renewal_conf_files(config)\n        choices = [storage.lineagename_for_filename(name) for name in filenames]\n        if not choices:\n            raise errors.Error(\"No existing certificates found.\")\n        if allow_multiple:\n            if not custom_prompt:\n                prompt = \"Which certificate(s) would you like to {0}?\".format(verb)\n            else:\n                prompt = custom_prompt\n            code, certnames = disp.checklist(\n                prompt, choices, cli_flag=\"--cert-name\", force_interactive=True)\n            if code != display_util.OK:\n                raise errors.Error(\"User ended interaction.\")\n        else:\n            if not custom_prompt:\n                prompt = \"Which certificate would you like to {0}?\".format(verb)\n            else:\n                prompt = custom_prompt\n\n            code, index = disp.menu(\n                prompt, choices, cli_flag=\"--cert-name\", force_interactive=True)\n\n            if code != display_util.OK or index not in range(0, len(choices)):\n                raise errors.Error(\"User ended interaction.\")\n            certnames = [choices[index]]\n    return certnames\n\n###################\n# Private Helpers\n###################\n\ndef _report_lines(msgs):\n    \"\"\"Format a results report for a category of single-line renewal outcomes\"\"\"\n    return \"  \" + \"\\n  \".join(str(msg) for msg in msgs)\n\ndef _report_human_readable(config, parsed_certs):\n    \"\"\"Format a results report for a parsed cert\"\"\"\n    certinfo = []\n    for cert in parsed_certs:\n        certinfo.append(human_readable_cert_info(config, cert))\n    return \"\\n\".join(certinfo)\n\ndef _describe_certs(config, parsed_certs, parse_failures):\n    \"\"\"Print information about the certs we know about\"\"\"\n    out = []  # type: List[str]\n\n    notify = out.append\n\n    if not parsed_certs and not parse_failures:\n        notify(\"No certs found.\")\n    else:\n        if parsed_certs:\n            match = \"matching \" if config.certname or config.domains else \"\"\n            notify(\"Found the following {0}certs:\".format(match))\n            notify(_report_human_readable(config, parsed_certs))\n        if parse_failures:\n            notify(\"\\nThe following renewal configurations \"\n               \"were invalid:\")\n            notify(_report_lines(parse_failures))\n\n    disp = zope.component.getUtility(interfaces.IDisplay)\n    disp.notification(\"\\n\".join(out), pause=False, wrap=False)\n\ndef _search_lineages(cli_config, func, initial_rv, *args):\n    \"\"\"Iterate func over unbroken lineages, allowing custom return conditions.\n\n    Allows flexible customization of return values, including multiple\n    return values and complex checks.\n\n    :param `configuration.NamespaceConfig` cli_config: parsed command line arguments\n    :param function func: function used while searching over lineages\n    :param initial_rv: initial return value of the function (any type)\n\n    :returns: Whatever was specified by `func` if a match is found.\n    \"\"\"\n    configs_dir = cli_config.renewal_configs_dir\n    # Verify the directory is there\n    util.make_or_verify_dir(configs_dir, mode=0o755)\n\n    rv = initial_rv\n    for renewal_file in storage.renewal_conf_files(cli_config):\n        try:\n            candidate_lineage = storage.RenewableCert(renewal_file, cli_config)\n        except (errors.CertStorageError, IOError):\n            logger.debug(\"Renewal conf file %s is broken. Skipping.\", renewal_file)\n            logger.debug(\"Traceback was:\\n%s\", traceback.format_exc())\n            continue\n        rv = func(candidate_lineage, rv, *args)\n    return rv\n"
  },
  {
    "path": "certbot/_internal/cli/__init__.py",
    "content": "\"\"\"Certbot command line argument & config processing.\"\"\"\n# pylint: disable=too-many-lines\nfrom __future__ import print_function\nimport logging\nimport logging.handlers\nimport argparse\nimport sys\nimport certbot._internal.plugins.selection as plugin_selection\nfrom certbot._internal.plugins import disco as plugins_disco\n\nfrom acme.magic_typing import Optional\n\n# pylint: disable=ungrouped-imports\nimport certbot\nfrom certbot._internal import constants\n\nimport certbot.plugins.enhancements as enhancements\n\n\nfrom certbot._internal.cli.cli_constants import (\n    LEAUTO,\n    old_path_fragment,\n    new_path_prefix,\n    cli_command,\n    SHORT_USAGE,\n    COMMAND_OVERVIEW,\n    HELP_AND_VERSION_USAGE,\n    ARGPARSE_PARAMS_TO_REMOVE,\n    EXIT_ACTIONS,\n    ZERO_ARG_ACTIONS,\n    VAR_MODIFIERS\n)\n\nfrom certbot._internal.cli.cli_utils import (\n    _Default,\n    read_file,\n    flag_default,\n    config_help,\n    HelpfulArgumentGroup,\n    CustomHelpFormatter,\n    _DomainsAction,\n    add_domains,\n    CaseInsensitiveList,\n    _user_agent_comment_type,\n    _EncodeReasonAction,\n    parse_preferred_challenges,\n    _PrefChallAction,\n    _DeployHookAction,\n    _RenewHookAction,\n    nonnegative_int\n)\n\n# These imports depend on cli_constants and cli_utils.\nfrom certbot._internal.cli.report_config_interaction import report_config_interaction\nfrom certbot._internal.cli.verb_help import VERB_HELP, VERB_HELP_MAP\nfrom certbot._internal.cli.group_adder import _add_all_groups\nfrom certbot._internal.cli.subparsers import _create_subparsers\nfrom certbot._internal.cli.paths_parser import _paths_parser\nfrom certbot._internal.cli.plugins_parsing import _plugins_parsing\n\n# These imports depend on some or all of the submodules for cli.\nfrom certbot._internal.cli.helpful import HelpfulArgumentParser\n# pylint: enable=ungrouped-imports\n\n\nlogger = logging.getLogger(__name__)\n\n\n# Global, to save us from a lot of argument passing within the scope of this module\nhelpful_parser = None  # type: Optional[HelpfulArgumentParser]\n\n\ndef prepare_and_parse_args(plugins, args, detect_defaults=False):\n    \"\"\"Returns parsed command line arguments.\n\n    :param .PluginsRegistry plugins: available plugins\n    :param list args: command line arguments with the program name removed\n\n    :returns: parsed command line arguments\n    :rtype: argparse.Namespace\n\n    \"\"\"\n\n    helpful = HelpfulArgumentParser(args, plugins, detect_defaults)\n    _add_all_groups(helpful)\n\n    # --help is automatically provided by argparse\n    helpful.add(\n        None, \"-v\", \"--verbose\", dest=\"verbose_count\", action=\"count\",\n        default=flag_default(\"verbose_count\"), help=\"This flag can be used \"\n        \"multiple times to incrementally increase the verbosity of output, \"\n        \"e.g. -vvv.\")\n    helpful.add(\n        None, \"-t\", \"--text\", dest=\"text_mode\", action=\"store_true\",\n        default=flag_default(\"text_mode\"), help=argparse.SUPPRESS)\n    helpful.add(\n        None, \"--max-log-backups\", type=nonnegative_int,\n        default=flag_default(\"max_log_backups\"),\n        help=\"Specifies the maximum number of backup logs that should \"\n             \"be kept by Certbot's built in log rotation. Setting this \"\n             \"flag to 0 disables log rotation entirely, causing \"\n             \"Certbot to always append to the same log file.\")\n    helpful.add(\n        [None, \"automation\", \"run\", \"certonly\", \"enhance\"],\n        \"-n\", \"--non-interactive\", \"--noninteractive\",\n        dest=\"noninteractive_mode\", action=\"store_true\",\n        default=flag_default(\"noninteractive_mode\"),\n        help=\"Run without ever asking for user input. This may require \"\n              \"additional command line flags; the client will try to explain \"\n              \"which ones are required if it finds one missing\")\n    helpful.add(\n        [None, \"register\", \"run\", \"certonly\", \"enhance\"],\n        constants.FORCE_INTERACTIVE_FLAG, action=\"store_true\",\n        default=flag_default(\"force_interactive\"),\n        help=\"Force Certbot to be interactive even if it detects it's not \"\n             \"being run in a terminal. This flag cannot be used with the \"\n             \"renew subcommand.\")\n    helpful.add(\n        [None, \"run\", \"certonly\", \"certificates\", \"enhance\"],\n        \"-d\", \"--domains\", \"--domain\", dest=\"domains\",\n        metavar=\"DOMAIN\", action=_DomainsAction,\n        default=flag_default(\"domains\"),\n        help=\"Domain names to apply. For multiple domains you can use \"\n             \"multiple -d flags or enter a comma separated list of domains \"\n             \"as a parameter. The first domain provided will be the \"\n             \"subject CN of the certificate, and all domains will be \"\n             \"Subject Alternative Names on the certificate. \"\n             \"The first domain will also be used in \"\n             \"some software user interfaces and as the file paths for the \"\n             \"certificate and related material unless otherwise \"\n             \"specified or you already have a certificate with the same \"\n             \"name. In the case of a name collision it will append a number \"\n             \"like 0001 to the file path name. (default: Ask)\")\n    helpful.add(\n        [None, \"run\", \"certonly\", \"register\"],\n        \"--eab-kid\", dest=\"eab_kid\",\n        metavar=\"EAB_KID\",\n        help=\"Key Identifier for External Account Binding\"\n    )\n    helpful.add(\n        [None, \"run\", \"certonly\", \"register\"],\n        \"--eab-hmac-key\", dest=\"eab_hmac_key\",\n        metavar=\"EAB_HMAC_KEY\",\n        help=\"HMAC key for External Account Binding\"\n    )\n    helpful.add(\n        [None, \"run\", \"certonly\", \"manage\", \"delete\", \"certificates\",\n         \"renew\", \"enhance\"], \"--cert-name\", dest=\"certname\",\n        metavar=\"CERTNAME\", default=flag_default(\"certname\"),\n        help=\"Certificate name to apply. This name is used by Certbot for housekeeping \"\n             \"and in file paths; it doesn't affect the content of the certificate itself. \"\n             \"To see certificate names, run 'certbot certificates'. \"\n             \"When creating a new certificate, specifies the new certificate's name. \"\n             \"(default: the first provided domain or the name of an existing \"\n             \"certificate on your system for the same domains)\")\n    helpful.add(\n        [None, \"testing\", \"renew\", \"certonly\"],\n        \"--dry-run\", action=\"store_true\", dest=\"dry_run\",\n        default=flag_default(\"dry_run\"),\n        help=\"Perform a test run of the client, obtaining test (invalid) certificates\"\n             \" but not saving them to disk. This can currently only be used\"\n             \" with the 'certonly' and 'renew' subcommands. \\nNote: Although --dry-run\"\n             \" tries to avoid making any persistent changes on a system, it \"\n             \" is not completely side-effect free: if used with webserver authenticator plugins\"\n             \" like apache and nginx, it makes and then reverts temporary config changes\"\n             \" in order to obtain test certificates, and reloads webservers to deploy and then\"\n             \" roll back those changes.  It also calls --pre-hook and --post-hook commands\"\n             \" if they are defined because they may be necessary to accurately simulate\"\n             \" renewal. --deploy-hook commands are not called.\")\n    helpful.add(\n        [\"register\", \"automation\"], \"--register-unsafely-without-email\", action=\"store_true\",\n        default=flag_default(\"register_unsafely_without_email\"),\n        help=\"Specifying this flag enables registering an account with no \"\n             \"email address. This is strongly discouraged, because in the \"\n             \"event of key loss or account compromise you will irrevocably \"\n             \"lose access to your account. You will also be unable to receive \"\n             \"notice about impending expiration or revocation of your \"\n             \"certificates. Updates to the Subscriber Agreement will still \"\n             \"affect you, and will be effective 14 days after posting an \"\n             \"update to the web site.\")\n    helpful.add(\n        [\"register\", \"update_account\", \"unregister\", \"automation\"], \"-m\", \"--email\",\n        default=flag_default(\"email\"),\n        help=config_help(\"email\"))\n    helpful.add([\"register\", \"update_account\", \"automation\"], \"--eff-email\", action=\"store_true\",\n                default=flag_default(\"eff_email\"), dest=\"eff_email\",\n                help=\"Share your e-mail address with EFF\")\n    helpful.add([\"register\", \"update_account\", \"automation\"], \"--no-eff-email\",\n                action=\"store_false\", default=flag_default(\"eff_email\"), dest=\"eff_email\",\n                help=\"Don't share your e-mail address with EFF\")\n    helpful.add(\n        [\"automation\", \"certonly\", \"run\"],\n        \"--keep-until-expiring\", \"--keep\", \"--reinstall\",\n        dest=\"reinstall\", action=\"store_true\", default=flag_default(\"reinstall\"),\n        help=\"If the requested certificate matches an existing certificate, always keep the \"\n             \"existing one until it is due for renewal (for the \"\n             \"'run' subcommand this means reinstall the existing certificate). (default: Ask)\")\n    helpful.add(\n        \"automation\", \"--expand\", action=\"store_true\", default=flag_default(\"expand\"),\n        help=\"If an existing certificate is a strict subset of the requested names, \"\n             \"always expand and replace it with the additional names. (default: Ask)\")\n    helpful.add(\n        \"automation\", \"--version\", action=\"version\",\n        version=\"%(prog)s {0}\".format(certbot.__version__),\n        help=\"show program's version number and exit\")\n    helpful.add(\n        [\"automation\", \"renew\"],\n        \"--force-renewal\", \"--renew-by-default\", dest=\"renew_by_default\",\n        action=\"store_true\", default=flag_default(\"renew_by_default\"),\n        help=\"If a certificate \"\n             \"already exists for the requested domains, renew it now, \"\n             \"regardless of whether it is near expiry. (Often \"\n             \"--keep-until-expiring is more appropriate). Also implies \"\n             \"--expand.\")\n    helpful.add(\n        \"automation\", \"--renew-with-new-domains\", dest=\"renew_with_new_domains\",\n        action=\"store_true\", default=flag_default(\"renew_with_new_domains\"),\n        help=\"If a \"\n             \"certificate already exists for the requested certificate name \"\n             \"but does not match the requested domains, renew it now, \"\n             \"regardless of whether it is near expiry.\")\n    helpful.add(\n        \"automation\", \"--reuse-key\", dest=\"reuse_key\",\n        action=\"store_true\", default=flag_default(\"reuse_key\"),\n        help=\"When renewing, use the same private key as the existing \"\n             \"certificate.\")\n\n    helpful.add(\n        [\"automation\", \"renew\", \"certonly\"],\n        \"--allow-subset-of-names\", action=\"store_true\",\n        default=flag_default(\"allow_subset_of_names\"),\n        help=\"When performing domain validation, do not consider it a failure \"\n             \"if authorizations can not be obtained for a strict subset of \"\n             \"the requested domains. This may be useful for allowing renewals for \"\n             \"multiple domains to succeed even if some domains no longer point \"\n             \"at this system. This option cannot be used with --csr.\")\n    helpful.add(\n        \"automation\", \"--agree-tos\", dest=\"tos\", action=\"store_true\",\n        default=flag_default(\"tos\"),\n        help=\"Agree to the ACME Subscriber Agreement (default: Ask)\")\n    helpful.add(\n        [\"unregister\", \"automation\"], \"--account\", metavar=\"ACCOUNT_ID\",\n        default=flag_default(\"account\"),\n        help=\"Account ID to use\")\n    helpful.add(\n        \"automation\", \"--duplicate\", dest=\"duplicate\", action=\"store_true\",\n        default=flag_default(\"duplicate\"),\n        help=\"Allow making a certificate lineage that duplicates an existing one \"\n             \"(both can be renewed in parallel)\")\n    helpful.add(\n        \"automation\", \"--os-packages-only\", action=\"store_true\",\n        default=flag_default(\"os_packages_only\"),\n        help=\"(certbot-auto only) install OS package dependencies and then stop\")\n    helpful.add(\n        \"automation\", \"--no-self-upgrade\", action=\"store_true\",\n        default=flag_default(\"no_self_upgrade\"),\n        help=\"(certbot-auto only) prevent the certbot-auto script from\"\n             \" upgrading itself to newer released versions (default: Upgrade\"\n             \" automatically)\")\n    helpful.add(\n        \"automation\", \"--no-bootstrap\", action=\"store_true\",\n        default=flag_default(\"no_bootstrap\"),\n        help=\"(certbot-auto only) prevent the certbot-auto script from\"\n             \" installing OS-level dependencies (default: Prompt to install \"\n             \" OS-wide dependencies, but exit if the user says 'No')\")\n    helpful.add(\n        \"automation\", \"--no-permissions-check\", action=\"store_true\",\n        default=flag_default(\"no_permissions_check\"),\n        help=\"(certbot-auto only) skip the check on the file system\"\n             \" permissions of the certbot-auto script\")\n    helpful.add(\n        [\"automation\", \"renew\", \"certonly\", \"run\"],\n        \"-q\", \"--quiet\", dest=\"quiet\", action=\"store_true\",\n        default=flag_default(\"quiet\"),\n        help=\"Silence all output except errors. Useful for automation via cron.\"\n             \" Implies --non-interactive.\")\n    # overwrites server, handled in HelpfulArgumentParser.parse_args()\n    helpful.add([\"testing\", \"revoke\", \"run\"], \"--test-cert\", \"--staging\",\n        dest=\"staging\", action=\"store_true\", default=flag_default(\"staging\"),\n        help=\"Use the staging server to obtain or revoke test (invalid) certificates; equivalent\"\n             \" to --server \" + constants.STAGING_URI)\n    helpful.add(\n        \"testing\", \"--debug\", action=\"store_true\", default=flag_default(\"debug\"),\n        help=\"Show tracebacks in case of errors, and allow certbot-auto \"\n             \"execution on experimental platforms\")\n    helpful.add(\n        [None, \"certonly\", \"run\"], \"--debug-challenges\", action=\"store_true\",\n        default=flag_default(\"debug_challenges\"),\n        help=\"After setting up challenges, wait for user input before \"\n             \"submitting to CA\")\n    helpful.add(\n        \"testing\", \"--no-verify-ssl\", action=\"store_true\",\n        help=config_help(\"no_verify_ssl\"),\n        default=flag_default(\"no_verify_ssl\"))\n    helpful.add(\n        [\"testing\", \"standalone\", \"manual\"], \"--http-01-port\", type=int,\n        dest=\"http01_port\",\n        default=flag_default(\"http01_port\"), help=config_help(\"http01_port\"))\n    helpful.add(\n        [\"testing\", \"standalone\"], \"--http-01-address\",\n        dest=\"http01_address\",\n        default=flag_default(\"http01_address\"), help=config_help(\"http01_address\"))\n    helpful.add(\n        [\"testing\", \"nginx\"], \"--https-port\", type=int,\n        default=flag_default(\"https_port\"),\n        help=config_help(\"https_port\"))\n    helpful.add(\n        \"testing\", \"--break-my-certs\", action=\"store_true\",\n        default=flag_default(\"break_my_certs\"),\n        help=\"Be willing to replace or renew valid certificates with invalid \"\n             \"(testing/staging) certificates\")\n    helpful.add(\n        \"security\", \"--rsa-key-size\", type=int, metavar=\"N\",\n        default=flag_default(\"rsa_key_size\"), help=config_help(\"rsa_key_size\"))\n    helpful.add(\n        \"security\", \"--must-staple\", action=\"store_true\",\n        dest=\"must_staple\", default=flag_default(\"must_staple\"),\n        help=config_help(\"must_staple\"))\n    helpful.add(\n        [\"security\", \"enhance\"],\n        \"--redirect\", action=\"store_true\", dest=\"redirect\",\n        default=flag_default(\"redirect\"),\n        help=\"Automatically redirect all HTTP traffic to HTTPS for the newly \"\n             \"authenticated vhost. (default: Ask)\")\n    helpful.add(\n        \"security\", \"--no-redirect\", action=\"store_false\", dest=\"redirect\",\n        default=flag_default(\"redirect\"),\n        help=\"Do not automatically redirect all HTTP traffic to HTTPS for the newly \"\n             \"authenticated vhost. (default: Ask)\")\n    helpful.add(\n        [\"security\", \"enhance\"],\n        \"--hsts\", action=\"store_true\", dest=\"hsts\", default=flag_default(\"hsts\"),\n        help=\"Add the Strict-Transport-Security header to every HTTP response.\"\n             \" Forcing browser to always use SSL for the domain.\"\n             \" Defends against SSL Stripping.\")\n    helpful.add(\n        \"security\", \"--no-hsts\", action=\"store_false\", dest=\"hsts\",\n        default=flag_default(\"hsts\"), help=argparse.SUPPRESS)\n    helpful.add(\n        [\"security\", \"enhance\"],\n        \"--uir\", action=\"store_true\", dest=\"uir\", default=flag_default(\"uir\"),\n        help='Add the \"Content-Security-Policy: upgrade-insecure-requests\"'\n             ' header to every HTTP response. Forcing the browser to use'\n             ' https:// for every http:// resource.')\n    helpful.add(\n        \"security\", \"--no-uir\", action=\"store_false\", dest=\"uir\", default=flag_default(\"uir\"),\n        help=argparse.SUPPRESS)\n    helpful.add(\n        \"security\", \"--staple-ocsp\", action=\"store_true\", dest=\"staple\",\n        default=flag_default(\"staple\"),\n        help=\"Enables OCSP Stapling. A valid OCSP response is stapled to\"\n        \" the certificate that the server offers during TLS.\")\n    helpful.add(\n        \"security\", \"--no-staple-ocsp\", action=\"store_false\", dest=\"staple\",\n        default=flag_default(\"staple\"), help=argparse.SUPPRESS)\n    helpful.add(\n        \"security\", \"--strict-permissions\", action=\"store_true\",\n        default=flag_default(\"strict_permissions\"),\n        help=\"Require that all configuration files are owned by the current \"\n             \"user; only needed if your config is somewhere unsafe like /tmp/\")\n    helpful.add(\n        [\"manual\", \"standalone\", \"certonly\", \"renew\"],\n        \"--preferred-challenges\", dest=\"pref_challs\",\n        action=_PrefChallAction, default=flag_default(\"pref_challs\"),\n        help='A sorted, comma delimited list of the preferred challenge to '\n             'use during authorization with the most preferred challenge '\n             'listed first (Eg, \"dns\" or \"http,dns\"). '\n             'Not all plugins support all challenges. See '\n             'https://certbot.eff.org/docs/using.html#plugins for details. '\n             'ACME Challenges are versioned, but if you pick \"http\" rather '\n             'than \"http-01\", Certbot will select the latest version '\n             'automatically.')\n    helpful.add(\n        \"renew\", \"--pre-hook\",\n        help=\"Command to be run in a shell before obtaining any certificates.\"\n        \" Intended primarily for renewal, where it can be used to temporarily\"\n        \" shut down a webserver that might conflict with the standalone\"\n        \" plugin. This will only be called if a certificate is actually to be\"\n        \" obtained/renewed. When renewing several certificates that have\"\n        \" identical pre-hooks, only the first will be executed.\")\n    helpful.add(\n        \"renew\", \"--post-hook\",\n        help=\"Command to be run in a shell after attempting to obtain/renew\"\n        \" certificates. Can be used to deploy renewed certificates, or to\"\n        \" restart any servers that were stopped by --pre-hook. This is only\"\n        \" run if an attempt was made to obtain/renew a certificate. If\"\n        \" multiple renewed certificates have identical post-hooks, only\"\n        \" one will be run.\")\n    helpful.add(\"renew\", \"--renew-hook\",\n                action=_RenewHookAction, help=argparse.SUPPRESS)\n    helpful.add(\n        \"renew\", \"--no-random-sleep-on-renew\", action=\"store_false\",\n        default=flag_default(\"random_sleep_on_renew\"), dest=\"random_sleep_on_renew\",\n        help=argparse.SUPPRESS)\n    helpful.add(\n        \"renew\", \"--deploy-hook\", action=_DeployHookAction,\n        help='Command to be run in a shell once for each successfully'\n        ' issued certificate. For this command, the shell variable'\n        ' $RENEWED_LINEAGE will point to the config live subdirectory'\n        ' (for example, \"/etc/letsencrypt/live/example.com\") containing'\n        ' the new certificates and keys; the shell variable'\n        ' $RENEWED_DOMAINS will contain a space-delimited list of'\n        ' renewed certificate domains (for example, \"example.com'\n        ' www.example.com\"')\n    helpful.add(\n        \"renew\", \"--disable-hook-validation\",\n        action=\"store_false\", dest=\"validate_hooks\",\n        default=flag_default(\"validate_hooks\"),\n        help=\"Ordinarily the commands specified for\"\n        \" --pre-hook/--post-hook/--deploy-hook will be checked for\"\n        \" validity, to see if the programs being run are in the $PATH,\"\n        \" so that mistakes can be caught early, even when the hooks\"\n        \" aren't being run just yet. The validation is rather\"\n        \" simplistic and fails if you use more advanced shell\"\n        \" constructs, so you can use this switch to disable it.\"\n        \" (default: False)\")\n    helpful.add(\n        \"renew\", \"--no-directory-hooks\", action=\"store_false\",\n        default=flag_default(\"directory_hooks\"), dest=\"directory_hooks\",\n        help=\"Disable running executables found in Certbot's hook directories\"\n        \" during renewal. (default: False)\")\n    helpful.add(\n        \"renew\", \"--disable-renew-updates\", action=\"store_true\",\n        default=flag_default(\"disable_renew_updates\"), dest=\"disable_renew_updates\",\n        help=\"Disable automatic updates to your server configuration that\"\n        \" would otherwise be done by the selected installer plugin, and triggered\"\n        \" when the user executes \\\"certbot renew\\\", regardless of if the certificate\"\n        \" is renewed. This setting does not apply to important TLS configuration\"\n        \" updates.\")\n    helpful.add(\n        \"renew\", \"--no-autorenew\", action=\"store_false\",\n        default=flag_default(\"autorenew\"), dest=\"autorenew\",\n        help=\"Disable auto renewal of certificates.\")\n\n    # Populate the command line parameters for new style enhancements\n    enhancements.populate_cli(helpful.add)\n\n    _create_subparsers(helpful)\n    _paths_parser(helpful)\n    # _plugins_parsing should be the last thing to act upon the main\n    # parser (--help should display plugin-specific options last)\n    _plugins_parsing(helpful, plugins)\n\n    if not detect_defaults:\n        global helpful_parser # pylint: disable=global-statement\n        helpful_parser = helpful\n    return helpful.parse_args()\n\n\ndef set_by_cli(var):\n    \"\"\"\n    Return True if a particular config variable has been set by the user\n    (CLI or config file) including if the user explicitly set it to the\n    default.  Returns False if the variable was assigned a default value.\n    \"\"\"\n    detector = set_by_cli.detector  # type: ignore\n    if detector is None and helpful_parser is not None:\n        # Setup on first run: `detector` is a weird version of config in which\n        # the default value of every attribute is wrangled to be boolean-false\n        plugins = plugins_disco.PluginsRegistry.find_all()\n        # reconstructed_args == sys.argv[1:], or whatever was passed to main()\n        reconstructed_args = helpful_parser.args + [helpful_parser.verb]\n        detector = set_by_cli.detector = prepare_and_parse_args(  # type: ignore\n            plugins, reconstructed_args, detect_defaults=True)\n        # propagate plugin requests: eg --standalone modifies config.authenticator\n        detector.authenticator, detector.installer = (  # type: ignore\n            plugin_selection.cli_plugin_requests(detector))\n\n    if not isinstance(getattr(detector, var), _Default):\n        logger.debug(\"Var %s=%s (set by user).\", var, getattr(detector, var))\n        return True\n\n    for modifier in VAR_MODIFIERS.get(var, []):\n        if set_by_cli(modifier):\n            logger.debug(\"Var %s=%s (set by user).\",\n                var, VAR_MODIFIERS.get(var, []))\n            return True\n\n    return False\n\n\n# static housekeeping var\n# functions attributed are not supported by mypy\n# https://github.com/python/mypy/issues/2087\nset_by_cli.detector = None  # type: ignore\n\n\ndef has_default_value(option, value):\n    \"\"\"Does option have the default value?\n\n    If the default value of option is not known, False is returned.\n\n    :param str option: configuration variable being considered\n    :param value: value of the configuration variable named option\n\n    :returns: True if option has the default value, otherwise, False\n    :rtype: bool\n\n    \"\"\"\n    if helpful_parser is not None:\n        return (option in helpful_parser.defaults and\n                helpful_parser.defaults[option] == value)\n    return False\n\n\ndef option_was_set(option, value):\n    \"\"\"Was option set by the user or does it differ from the default?\n\n    :param str option: configuration variable being considered\n    :param value: value of the configuration variable named option\n\n    :returns: True if the option was set, otherwise, False\n    :rtype: bool\n\n    \"\"\"\n    return set_by_cli(option) or not has_default_value(option, value)\n\n\ndef argparse_type(variable):\n    \"\"\"Return our argparse type function for a config variable (default: str)\"\"\"\n    # pylint: disable=protected-access\n    if helpful_parser is not None:\n        for action in helpful_parser.parser._actions:\n            if action.type is not None and action.dest == variable:\n                return action.type\n    return str\n"
  },
  {
    "path": "certbot/_internal/cli/cli_constants.py",
    "content": "\"\"\"Certbot command line constants\"\"\"\nimport sys\n\nfrom certbot.compat import os\n\n# For help strings, figure out how the user ran us.\n# When invoked from letsencrypt-auto, sys.argv[0] is something like:\n# \"/home/user/.local/share/certbot/bin/certbot\"\n# Note that this won't work if the user set VENV_PATH or XDG_DATA_HOME before\n# running letsencrypt-auto (and sudo stops us from seeing if they did), so it\n# should only be used for purposes where inability to detect letsencrypt-auto\n# fails safely\n\nLEAUTO = \"letsencrypt-auto\"\nif \"CERTBOT_AUTO\" in os.environ:\n    # if we're here, this is probably going to be certbot-auto, unless the\n    # user saved the script under a different name\n    LEAUTO = os.path.basename(os.environ[\"CERTBOT_AUTO\"])\n\nold_path_fragment = os.path.join(\".local\", \"share\", \"letsencrypt\")\nnew_path_prefix = os.path.abspath(os.path.join(os.sep, \"opt\",\n                                               \"eff.org\", \"certbot\", \"venv\"))\nif old_path_fragment in sys.argv[0] or sys.argv[0].startswith(new_path_prefix):\n    cli_command = LEAUTO\nelse:\n    cli_command = \"certbot\"\n\n\n# Argparse's help formatting has a lot of unhelpful peculiarities, so we want\n# to replace as much of it as we can...\n\n# This is the stub to include in help generated by argparse\nSHORT_USAGE = \"\"\"\n  {0} [SUBCOMMAND] [options] [-d DOMAIN] [-d DOMAIN] ...\n\nCertbot can obtain and install HTTPS/TLS/SSL certificates.  By default,\nit will attempt to use a webserver both for obtaining and installing the\ncertificate. \"\"\".format(cli_command)\n\n# This section is used for --help and --help all ; it needs information\n# about installed plugins to be fully formatted\nCOMMAND_OVERVIEW = \"\"\"The most common SUBCOMMANDS and flags are:\n\nobtain, install, and renew certificates:\n    (default) run   Obtain & install a certificate in your current webserver\n    certonly        Obtain or renew a certificate, but do not install it\n    renew           Renew all previously obtained certificates that are near expiry\n    enhance         Add security enhancements to your existing configuration\n   -d DOMAINS       Comma-separated list of domains to obtain a certificate for\n\n  %s\n  --standalone      Run a standalone webserver for authentication\n  %s\n  --webroot         Place files in a server's webroot folder for authentication\n  --manual          Obtain certificates interactively, or using shell script hooks\n\n   -n               Run non-interactively\n  --test-cert       Obtain a test certificate from a staging server\n  --dry-run         Test \"renew\" or \"certonly\" without saving any certificates to disk\n\nmanage certificates:\n    certificates    Display information about certificates you have from Certbot\n    revoke          Revoke a certificate (supply --cert-name or --cert-path)\n    delete          Delete a certificate (supply --cert-name)\n\nmanage your account:\n    register        Create an ACME account\n    unregister      Deactivate an ACME account\n    update_account  Update an ACME account\n  --agree-tos       Agree to the ACME server's Subscriber Agreement\n   -m EMAIL         Email address for important account notifications\n\"\"\"\n\n# This is the short help for certbot --help, where we disable argparse\n# altogether\nHELP_AND_VERSION_USAGE = \"\"\"\nMore detailed help:\n\n  -h, --help [TOPIC]    print this message, or detailed help on a topic;\n                        the available TOPICS are:\n\n   all, automation, commands, paths, security, testing, or any of the\n   subcommands or plugins (certonly, renew, install, register, nginx,\n   apache, standalone, webroot, etc.)\n  -h all                print a detailed help page including all topics\n  --version             print the version number\n\"\"\"\n\n# These argparse parameters should be removed when detecting defaults.\nARGPARSE_PARAMS_TO_REMOVE = (\"const\", \"nargs\", \"type\",)\n\n\n# These sets are used when to help detect options set by the user.\nEXIT_ACTIONS = set((\"help\", \"version\",))\n\n\nZERO_ARG_ACTIONS = set((\"store_const\", \"store_true\",\n                        \"store_false\", \"append_const\", \"count\",))\n\n\n# Maps a config option to a set of config options that may have modified it.\n# This dictionary is used recursively, so if A modifies B and B modifies C,\n# it is determined that C was modified by the user if A was modified.\nVAR_MODIFIERS = {\"account\": set((\"server\",)),\n                 \"renew_hook\": set((\"deploy_hook\",)),\n                 \"server\": set((\"dry_run\", \"staging\",)),\n                 \"webroot_map\": set((\"webroot_path\",))}\n"
  },
  {
    "path": "certbot/_internal/cli/cli_utils.py",
    "content": "\"\"\"Certbot command line util function\"\"\"\nimport argparse\nimport copy\n\nimport zope.interface.interface  # pylint: disable=unused-import\n\nfrom acme import challenges\nfrom certbot import interfaces\nfrom certbot import util\nfrom certbot import errors\nfrom certbot.compat import os\nfrom certbot._internal import constants\n\n\nclass _Default(object):\n    \"\"\"A class to use as a default to detect if a value is set by a user\"\"\"\n\n    def __bool__(self):\n        return False\n\n    def __eq__(self, other):\n        return isinstance(other, _Default)\n\n    def __hash__(self):\n        return id(_Default)\n\n    def __nonzero__(self):\n        return self.__bool__()\n\n\ndef read_file(filename, mode=\"rb\"):\n    \"\"\"Returns the given file's contents.\n\n    :param str filename: path to file\n    :param str mode: open mode (see `open`)\n\n    :returns: absolute path of filename and its contents\n    :rtype: tuple\n\n    :raises argparse.ArgumentTypeError: File does not exist or is not readable.\n\n    \"\"\"\n    try:\n        filename = os.path.abspath(filename)\n        with open(filename, mode) as the_file:\n            contents = the_file.read()\n        return filename, contents\n    except IOError as exc:\n        raise argparse.ArgumentTypeError(exc.strerror)\n\n\ndef flag_default(name):\n    \"\"\"Default value for CLI flag.\"\"\"\n    # XXX: this is an internal housekeeping notion of defaults before\n    # argparse has been set up; it is not accurate for all flags.  Call it\n    # with caution.  Plugin defaults are missing, and some things are using\n    # defaults defined in this file, not in constants.py :(\n    return copy.deepcopy(constants.CLI_DEFAULTS[name])\n\n\ndef config_help(name, hidden=False):\n    \"\"\"Extract the help message for an `.IConfig` attribute.\"\"\"\n    if hidden:\n        return argparse.SUPPRESS\n    field = interfaces.IConfig.__getitem__(name)  # type: zope.interface.interface.Attribute\n    return field.__doc__\n\n\nclass HelpfulArgumentGroup(object):\n    \"\"\"Emulates an argparse group for use with HelpfulArgumentParser.\n\n    This class is used in the add_group method of HelpfulArgumentParser.\n    Command line arguments can be added to the group, but help\n    suppression and default detection is applied by\n    HelpfulArgumentParser when necessary.\n\n    \"\"\"\n    def __init__(self, helpful_arg_parser, topic):\n        self._parser = helpful_arg_parser\n        self._topic = topic\n\n    def add_argument(self, *args, **kwargs):\n        \"\"\"Add a new command line argument to the argument group.\"\"\"\n        self._parser.add(self._topic, *args, **kwargs)\n\n\nclass CustomHelpFormatter(argparse.HelpFormatter):\n    \"\"\"This is a clone of ArgumentDefaultsHelpFormatter, with bugfixes.\n\n    In particular we fix https://bugs.python.org/issue28742\n    \"\"\"\n\n    def _get_help_string(self, action):\n        helpstr = action.help\n        if '%(default)' not in action.help and '(default:' not in action.help:\n            if action.default != argparse.SUPPRESS:\n                defaulting_nargs = [argparse.OPTIONAL, argparse.ZERO_OR_MORE]\n                if action.option_strings or action.nargs in defaulting_nargs:\n                    helpstr += ' (default: %(default)s)'\n        return helpstr\n\n\nclass _DomainsAction(argparse.Action):\n    \"\"\"Action class for parsing domains.\"\"\"\n\n    def __call__(self, parser, namespace, domain, option_string=None):\n        \"\"\"Just wrap add_domains in argparseese.\"\"\"\n        add_domains(namespace, domain)\n\n\ndef add_domains(args_or_config, domains):\n    \"\"\"Registers new domains to be used during the current client run.\n\n    Domains are not added to the list of requested domains if they have\n    already been registered.\n\n    :param args_or_config: parsed command line arguments\n    :type args_or_config: argparse.Namespace or\n        configuration.NamespaceConfig\n    :param str domain: one or more comma separated domains\n\n    :returns: domains after they have been normalized and validated\n    :rtype: `list` of `str`\n\n    \"\"\"\n    validated_domains = []\n    for domain in domains.split(\",\"):\n        domain = util.enforce_domain_sanity(domain.strip())\n        validated_domains.append(domain)\n        if domain not in args_or_config.domains:\n            args_or_config.domains.append(domain)\n\n    return validated_domains\n\n\nclass CaseInsensitiveList(list):\n    \"\"\"A list that will ignore case when searching.\n\n    This class is passed to the `choices` argument of `argparse.add_arguments`\n    through the `helpful` wrapper. It is necessary due to special handling of\n    command line arguments by `set_by_cli` in which the `type_func` is not applied.\"\"\"\n    def __contains__(self, element):\n        return super(CaseInsensitiveList, self).__contains__(element.lower())\n\n\ndef _user_agent_comment_type(value):\n    if \"(\" in value or \")\" in value:\n        raise argparse.ArgumentTypeError(\"may not contain parentheses\")\n    return value\n\n\nclass _EncodeReasonAction(argparse.Action):\n    \"\"\"Action class for parsing revocation reason.\"\"\"\n\n    def __call__(self, parser, namespace, reason, option_string=None):\n        \"\"\"Encodes the reason for certificate revocation.\"\"\"\n        code = constants.REVOCATION_REASONS[reason.lower()]\n        setattr(namespace, self.dest, code)\n\n\ndef parse_preferred_challenges(pref_challs):\n    \"\"\"Translate and validate preferred challenges.\n\n    :param pref_challs: list of preferred challenge types\n    :type pref_challs: `list` of `str`\n\n    :returns: validated list of preferred challenge types\n    :rtype: `list` of `str`\n\n    :raises errors.Error: if pref_challs is invalid\n\n    \"\"\"\n    aliases = {\"dns\": \"dns-01\", \"http\": \"http-01\"}\n    challs = [c.strip() for c in pref_challs]\n    challs = [aliases.get(c, c) for c in challs]\n\n    unrecognized = \", \".join(name for name in challs\n                             if name not in challenges.Challenge.TYPES)\n    if unrecognized:\n        raise errors.Error(\n            \"Unrecognized challenges: {0}\".format(unrecognized))\n    return challs\n\n\nclass _PrefChallAction(argparse.Action):\n    \"\"\"Action class for parsing preferred challenges.\"\"\"\n\n    def __call__(self, parser, namespace, pref_challs, option_string=None):\n        try:\n            challs = parse_preferred_challenges(pref_challs.split(\",\"))\n        except errors.Error as error:\n            raise argparse.ArgumentError(self, str(error))\n        namespace.pref_challs.extend(challs)\n\n\nclass _DeployHookAction(argparse.Action):\n    \"\"\"Action class for parsing deploy hooks.\"\"\"\n\n    def __call__(self, parser, namespace, values, option_string=None):\n        renew_hook_set = namespace.deploy_hook != namespace.renew_hook\n        if renew_hook_set and namespace.renew_hook != values:\n            raise argparse.ArgumentError(\n                self, \"conflicts with --renew-hook value\")\n        namespace.deploy_hook = namespace.renew_hook = values\n\n\nclass _RenewHookAction(argparse.Action):\n    \"\"\"Action class for parsing renew hooks.\"\"\"\n\n    def __call__(self, parser, namespace, values, option_string=None):\n        deploy_hook_set = namespace.deploy_hook is not None\n        if deploy_hook_set and namespace.deploy_hook != values:\n            raise argparse.ArgumentError(\n                self, \"conflicts with --deploy-hook value\")\n        namespace.renew_hook = values\n\n\ndef nonnegative_int(value):\n    \"\"\"Converts value to an int and checks that it is not negative.\n\n    This function should used as the type parameter for argparse\n    arguments.\n\n    :param str value: value provided on the command line\n\n    :returns: integer representation of value\n    :rtype: int\n\n    :raises argparse.ArgumentTypeError: if value isn't a non-negative integer\n\n    \"\"\"\n    try:\n        int_value = int(value)\n    except ValueError:\n        raise argparse.ArgumentTypeError(\"value must be an integer\")\n\n    if int_value < 0:\n        raise argparse.ArgumentTypeError(\"value must be non-negative\")\n    return int_value\n"
  },
  {
    "path": "certbot/_internal/cli/group_adder.py",
    "content": "\"\"\"This module contains a function to add the groups of arguments for the help\ndisplay\"\"\"\nfrom certbot._internal.cli import VERB_HELP\n\n\ndef _add_all_groups(helpful):\n    helpful.add_group(\"automation\", description=\"Flags for automating execution & other tweaks\")\n    helpful.add_group(\"security\", description=\"Security parameters & server settings\")\n    helpful.add_group(\"testing\",\n        description=\"The following flags are meant for testing and integration purposes only.\")\n    helpful.add_group(\"paths\", description=\"Flags for changing execution paths & servers\")\n    helpful.add_group(\"manage\",\n        description=\"Various subcommands and flags are available for managing your certificates:\",\n        verbs=[\"certificates\", \"delete\", \"renew\", \"revoke\", \"update_symlinks\"])\n\n    # VERBS\n    for verb, docs in VERB_HELP:\n        name = docs.get(\"realname\", verb)\n        helpful.add_group(name, description=docs[\"opts\"])\n"
  },
  {
    "path": "certbot/_internal/cli/helpful.py",
    "content": "\"\"\"Certbot command line argument parser\"\"\"\nfrom __future__ import print_function\nimport argparse\nimport copy\nimport glob\nimport sys\nimport configargparse\nimport six\nimport zope.component\nimport zope.interface\n\nfrom zope.interface import interfaces as zope_interfaces\n\n# pylint: disable=unused-import, no-name-in-module\nfrom acme.magic_typing import Any, Dict, Optional\n# pylint: enable=unused-import, no-name-in-module\n\nfrom certbot import crypto_util\nfrom certbot import errors\nfrom certbot import interfaces\nfrom certbot import util\nfrom certbot.compat import os\nfrom certbot._internal import constants\nfrom certbot._internal import hooks\n\nfrom certbot.display import util as display_util\n\nfrom certbot._internal.cli import (\n    SHORT_USAGE,\n    CustomHelpFormatter,\n    flag_default,\n    VERB_HELP,\n    VERB_HELP_MAP,\n    COMMAND_OVERVIEW,\n    HELP_AND_VERSION_USAGE,\n    _Default,\n    add_domains,\n    EXIT_ACTIONS,\n    ZERO_ARG_ACTIONS,\n    ARGPARSE_PARAMS_TO_REMOVE,\n    HelpfulArgumentGroup\n)\n\n\nclass HelpfulArgumentParser(object):\n    \"\"\"Argparse Wrapper.\n\n    This class wraps argparse, adding the ability to make --help less\n    verbose, and request help on specific subcategories at a time, eg\n    'certbot --help security' for security options.\n\n    \"\"\"\n    def __init__(self, args, plugins, detect_defaults=False):\n        from certbot._internal import main\n        self.VERBS = {\n            \"auth\": main.certonly,\n            \"certonly\": main.certonly,\n            \"run\": main.run,\n            \"install\": main.install,\n            \"plugins\": main.plugins_cmd,\n            \"register\": main.register,\n            \"update_account\": main.update_account,\n            \"unregister\": main.unregister,\n            \"renew\": main.renew,\n            \"revoke\": main.revoke,\n            \"rollback\": main.rollback,\n            \"everything\": main.run,\n            \"update_symlinks\": main.update_symlinks,\n            \"certificates\": main.certificates,\n            \"delete\": main.delete,\n            \"enhance\": main.enhance,\n        }\n\n        # Get notification function for printing\n        try:\n            self.notify = zope.component.getUtility(\n                interfaces.IDisplay).notification\n        except zope_interfaces.ComponentLookupError:\n            self.notify = display_util.NoninteractiveDisplay(\n                sys.stdout).notification\n\n\n        # List of topics for which additional help can be provided\n        HELP_TOPICS = [\"all\", \"security\", \"paths\", \"automation\", \"testing\"]\n        HELP_TOPICS += list(self.VERBS) + self.COMMANDS_TOPICS + [\"manage\"]\n\n        plugin_names = list(plugins)\n        self.help_topics = HELP_TOPICS + plugin_names + [None]  # type: ignore\n\n        self.detect_defaults = detect_defaults\n        self.args = args\n\n        if self.args and self.args[0] == 'help':\n            self.args[0] = '--help'\n\n        self.determine_verb()\n        help1 = self.prescan_for_flag(\"-h\", self.help_topics)\n        help2 = self.prescan_for_flag(\"--help\", self.help_topics)\n        if isinstance(help1, bool) and isinstance(help2, bool):\n            self.help_arg = help1 or help2\n        else:\n            self.help_arg = help1 if isinstance(help1, six.string_types) else help2\n\n        short_usage = self._usage_string(plugins, self.help_arg)\n\n        self.visible_topics = self.determine_help_topics(self.help_arg)\n\n        # elements are added by .add_group()\n        self.groups = {}  # type: Dict[str, argparse._ArgumentGroup]\n        # elements are added by .parse_args()\n        self.defaults = {}  # type: Dict[str, Any]\n\n        self.parser = configargparse.ArgParser(\n            prog=\"certbot\",\n            usage=short_usage,\n            formatter_class=CustomHelpFormatter,\n            args_for_setting_config_path=[\"-c\", \"--config\"],\n            default_config_files=flag_default(\"config_files\"),\n            config_arg_help_message=\"path to config file (default: {0})\".format(\n                \" and \".join(flag_default(\"config_files\"))))\n\n        # This is the only way to turn off overly verbose config flag documentation\n        self.parser._add_config_file_help = False\n\n    # Help that are synonyms for --help subcommands\n    COMMANDS_TOPICS = [\"command\", \"commands\", \"subcommand\", \"subcommands\", \"verbs\"]\n\n    def _list_subcommands(self):\n        longest = max(len(v) for v in VERB_HELP_MAP)\n\n        text = \"The full list of available SUBCOMMANDS is:\\n\\n\"\n        for verb, props in sorted(VERB_HELP):\n            doc = props.get(\"short\", \"\")\n            text += '{0:<{length}}     {1}\\n'.format(verb, doc, length=longest)\n\n        text += \"\\nYou can get more help on a specific subcommand with --help SUBCOMMAND\\n\"\n        return text\n\n    def _usage_string(self, plugins, help_arg):\n        \"\"\"Make usage strings late so that plugins can be initialised late\n\n        :param plugins: all discovered plugins\n        :param help_arg: False for none; True for --help; \"TOPIC\" for --help TOPIC\n        :rtype: str\n        :returns: a short usage string for the top of --help TOPIC)\n        \"\"\"\n        if \"nginx\" in plugins:\n            nginx_doc = \"--nginx           Use the Nginx plugin for authentication & installation\"\n        else:\n            nginx_doc = \"(the certbot nginx plugin is not installed)\"\n        if \"apache\" in plugins:\n            apache_doc = \"--apache          Use the Apache plugin for authentication & installation\"\n        else:\n            apache_doc = \"(the certbot apache plugin is not installed)\"\n\n        usage = SHORT_USAGE\n        if help_arg is True:\n            self.notify(usage + COMMAND_OVERVIEW % (apache_doc, nginx_doc) + HELP_AND_VERSION_USAGE)\n            sys.exit(0)\n        elif help_arg in self.COMMANDS_TOPICS:\n            self.notify(usage + self._list_subcommands())\n            sys.exit(0)\n        elif help_arg == \"all\":\n            # if we're doing --help all, the OVERVIEW is part of the SHORT_USAGE at\n            # the top; if we're doing --help someothertopic, it's OT so it's not\n            usage += COMMAND_OVERVIEW % (apache_doc, nginx_doc)\n        else:\n            custom = VERB_HELP_MAP.get(help_arg, {}).get(\"usage\", None)\n            usage = custom if custom else usage\n\n        return usage\n\n    def remove_config_file_domains_for_renewal(self, parsed_args):\n        \"\"\"Make \"certbot renew\" safe if domains are set in cli.ini.\"\"\"\n        # Works around https://github.com/certbot/certbot/issues/4096\n        if self.verb == \"renew\":\n            for source, flags in self.parser._source_to_settings.items(): # pylint: disable=protected-access\n                if source.startswith(\"config_file\") and \"domains\" in flags:\n                    parsed_args.domains = _Default() if self.detect_defaults else []\n\n    def parse_args(self):\n        \"\"\"Parses command line arguments and returns the result.\n\n        :returns: parsed command line arguments\n        :rtype: argparse.Namespace\n\n        \"\"\"\n        parsed_args = self.parser.parse_args(self.args)\n        parsed_args.func = self.VERBS[self.verb]\n        parsed_args.verb = self.verb\n\n        self.remove_config_file_domains_for_renewal(parsed_args)\n\n        if self.detect_defaults:\n            return parsed_args\n\n        self.defaults = dict((key, copy.deepcopy(self.parser.get_default(key)))\n                             for key in vars(parsed_args))\n\n        # Do any post-parsing homework here\n\n        if self.verb == \"renew\":\n            if parsed_args.force_interactive:\n                raise errors.Error(\n                    \"{0} cannot be used with renew\".format(\n                        constants.FORCE_INTERACTIVE_FLAG))\n            parsed_args.noninteractive_mode = True\n\n        if parsed_args.force_interactive and parsed_args.noninteractive_mode:\n            raise errors.Error(\n                \"Flag for non-interactive mode and {0} conflict\".format(\n                    constants.FORCE_INTERACTIVE_FLAG))\n\n        if parsed_args.staging or parsed_args.dry_run:\n            self.set_test_server(parsed_args)\n\n        if parsed_args.csr:\n            self.handle_csr(parsed_args)\n\n        if parsed_args.must_staple:\n            parsed_args.staple = True\n\n        if parsed_args.validate_hooks:\n            hooks.validate_hooks(parsed_args)\n\n        if parsed_args.allow_subset_of_names:\n            if any(util.is_wildcard_domain(d) for d in parsed_args.domains):\n                raise errors.Error(\"Using --allow-subset-of-names with a\"\n                                   \" wildcard domain is not supported.\")\n\n        if parsed_args.hsts and parsed_args.auto_hsts:\n            raise errors.Error(\n                \"Parameters --hsts and --auto-hsts cannot be used simultaneously.\")\n\n        return parsed_args\n\n    def set_test_server(self, parsed_args):\n        \"\"\"We have --staging/--dry-run; perform sanity check and set config.server\"\"\"\n\n        # Flag combinations should produce these results:\n        #                             | --staging      | --dry-run   |\n        # ------------------------------------------------------------\n        # | --server acme-v02         | Use staging    | Use staging |\n        # | --server acme-staging-v02 | Use staging    | Use staging |\n        # | --server <other>          | Conflict error | Use <other> |\n\n        default_servers = (flag_default(\"server\"), constants.STAGING_URI)\n\n        if parsed_args.staging and parsed_args.server not in default_servers:\n            raise errors.Error(\"--server value conflicts with --staging\")\n\n        if parsed_args.server in default_servers:\n            parsed_args.server = constants.STAGING_URI\n\n        if parsed_args.dry_run:\n            if self.verb not in [\"certonly\", \"renew\"]:\n                raise errors.Error(\"--dry-run currently only works with the \"\n                                   \"'certonly' or 'renew' subcommands (%r)\" % self.verb)\n            parsed_args.break_my_certs = parsed_args.staging = True\n            if glob.glob(os.path.join(parsed_args.config_dir, constants.ACCOUNTS_DIR, \"*\")):\n                # The user has a prod account, but might not have a staging\n                # one; we don't want to start trying to perform interactive registration\n                parsed_args.tos = True\n                parsed_args.register_unsafely_without_email = True\n\n    def handle_csr(self, parsed_args):\n        \"\"\"Process a --csr flag.\"\"\"\n        if parsed_args.verb != \"certonly\":\n            raise errors.Error(\"Currently, a CSR file may only be specified \"\n                               \"when obtaining a new or replacement \"\n                               \"via the certonly command. Please try the \"\n                               \"certonly command instead.\")\n        if parsed_args.allow_subset_of_names:\n            raise errors.Error(\"--allow-subset-of-names cannot be used with --csr\")\n\n        csrfile, contents = parsed_args.csr[0:2]\n        typ, csr, domains = crypto_util.import_csr_file(csrfile, contents)\n\n        # This is not necessary for webroot to work, however,\n        # obtain_certificate_from_csr requires parsed_args.domains to be set\n        for domain in domains:\n            add_domains(parsed_args, domain)\n\n        if not domains:\n            # TODO: add CN to domains instead:\n            raise errors.Error(\n                \"Unfortunately, your CSR %s needs to have a SubjectAltName for every domain\"\n                % parsed_args.csr[0])\n\n        parsed_args.actual_csr = (csr, typ)\n\n        csr_domains = {d.lower() for d in domains}\n        config_domains = set(parsed_args.domains)\n        if csr_domains != config_domains:\n            raise errors.ConfigurationError(\n                \"Inconsistent domain requests:\\nFrom the CSR: {0}\\nFrom command line/config: {1}\"\n                .format(\", \".join(csr_domains), \", \".join(config_domains)))\n\n\n    def determine_verb(self):\n        \"\"\"Determines the verb/subcommand provided by the user.\n\n        This function works around some of the limitations of argparse.\n\n        \"\"\"\n        if \"-h\" in self.args or \"--help\" in self.args:\n            # all verbs double as help arguments; don't get them confused\n            self.verb = \"help\"\n            return\n\n        for i, token in enumerate(self.args):\n            if token in self.VERBS:\n                verb = token\n                if verb == \"auth\":\n                    verb = \"certonly\"\n                if verb == \"everything\":\n                    verb = \"run\"\n                self.verb = verb\n                self.args.pop(i)\n                return\n\n        self.verb = \"run\"\n\n    def prescan_for_flag(self, flag, possible_arguments):\n        \"\"\"Checks cli input for flags.\n\n        Check for a flag, which accepts a fixed set of possible arguments, in\n        the command line; we will use this information to configure argparse's\n        help correctly.  Return the flag's argument, if it has one that matches\n        the sequence @possible_arguments; otherwise return whether the flag is\n        present.\n\n        \"\"\"\n        if flag not in self.args:\n            return False\n        pos = self.args.index(flag)\n        try:\n            nxt = self.args[pos + 1]\n            if nxt in possible_arguments:\n                return nxt\n        except IndexError:\n            pass\n        return True\n\n    def add(self, topics, *args, **kwargs):\n        \"\"\"Add a new command line argument.\n\n        :param topics: str or [str] help topic(s) this should be listed under,\n                       or None for options that don't fit under a specific\n                       topic which will only be shown in \"--help all\" output.\n                       The first entry determines where the flag lives in the\n                       \"--help all\" output (None -> \"optional arguments\").\n        :param list *args: the names of this argument flag\n        :param dict **kwargs: various argparse settings for this argument\n\n        \"\"\"\n\n        if isinstance(topics, list):\n            # if this flag can be listed in multiple sections, try to pick the one\n            # that the user has asked for help about\n            topic = self.help_arg if self.help_arg in topics else topics[0]\n        else:\n            topic = topics  # there's only one\n\n        if self.detect_defaults:\n            kwargs = self.modify_kwargs_for_default_detection(**kwargs)\n\n        if self.visible_topics[topic]:\n            if topic in self.groups:\n                group = self.groups[topic]\n                group.add_argument(*args, **kwargs)\n            else:\n                self.parser.add_argument(*args, **kwargs)\n        else:\n            kwargs[\"help\"] = argparse.SUPPRESS\n            self.parser.add_argument(*args, **kwargs)\n\n    def modify_kwargs_for_default_detection(self, **kwargs):\n        \"\"\"Modify an arg so we can check if it was set by the user.\n\n        Changes the parameters given to argparse when adding an argument\n        so we can properly detect if the value was set by the user.\n\n        :param dict kwargs: various argparse settings for this argument\n\n        :returns: a modified versions of kwargs\n        :rtype: dict\n\n        \"\"\"\n        action = kwargs.get(\"action\", None)\n        if action not in EXIT_ACTIONS:\n            kwargs[\"action\"] = (\"store_true\" if action in ZERO_ARG_ACTIONS else\n                                \"store\")\n            kwargs[\"default\"] = _Default()\n            for param in ARGPARSE_PARAMS_TO_REMOVE:\n                kwargs.pop(param, None)\n\n        return kwargs\n\n    def add_deprecated_argument(self, argument_name, num_args):\n        \"\"\"Adds a deprecated argument with the name argument_name.\n\n        Deprecated arguments are not shown in the help. If they are used\n        on the command line, a warning is shown stating that the\n        argument is deprecated and no other action is taken.\n\n        :param str argument_name: Name of deprecated argument.\n        :param int nargs: Number of arguments the option takes.\n\n        \"\"\"\n        util.add_deprecated_argument(\n            self.parser.add_argument, argument_name, num_args)\n\n    def add_group(self, topic, verbs=(), **kwargs):\n        \"\"\"Create a new argument group.\n\n        This method must be called once for every topic, however, calls\n        to this function are left next to the argument definitions for\n        clarity.\n\n        :param str topic: Name of the new argument group.\n        :param str verbs: List of subcommands that should be documented as part of\n                          this help group / topic\n\n        :returns: The new argument group.\n        :rtype: `HelpfulArgumentGroup`\n\n        \"\"\"\n        if self.visible_topics[topic]:\n            self.groups[topic] = self.parser.add_argument_group(topic, **kwargs)\n            if self.help_arg:\n                for v in verbs:\n                    self.groups[topic].add_argument(v, help=VERB_HELP_MAP[v][\"short\"])\n        return HelpfulArgumentGroup(self, topic)\n\n    def add_plugin_args(self, plugins):\n        \"\"\"\n\n        Let each of the plugins add its own command line arguments, which\n        may or may not be displayed as help topics.\n\n        \"\"\"\n        for name, plugin_ep in six.iteritems(plugins):\n            parser_or_group = self.add_group(name,\n                                             description=plugin_ep.long_description)\n            plugin_ep.plugin_cls.inject_parser_options(parser_or_group, name)\n\n    def determine_help_topics(self, chosen_topic):\n        \"\"\"\n\n        The user may have requested help on a topic, return a dict of which\n        topics to display. @chosen_topic has prescan_for_flag's return type\n\n        :returns: dict\n\n        \"\"\"\n        # topics maps each topic to whether it should be documented by\n        # argparse on the command line\n        if chosen_topic == \"auth\":\n            chosen_topic = \"certonly\"\n        if chosen_topic == \"everything\":\n            chosen_topic = \"run\"\n        if chosen_topic == \"all\":\n            # Addition of condition closes #6209 (removal of duplicate route53 option).\n            return {t: t != 'certbot-route53:auth' for t in self.help_topics}\n        elif not chosen_topic:\n            return {t: False for t in self.help_topics}\n        return {t: t == chosen_topic for t in self.help_topics}\n"
  },
  {
    "path": "certbot/_internal/cli/paths_parser.py",
    "content": "\"\"\"This is a module that adds configuration to the argument parser regarding\npaths for certificates\"\"\"\nfrom certbot.compat import os\nfrom certbot._internal.cli import (\n    read_file,\n    flag_default,\n    config_help\n)\n\n\ndef _paths_parser(helpful):\n    add = helpful.add\n    verb = helpful.verb\n    if verb == \"help\":\n        verb = helpful.help_arg\n\n    cph = \"Path to where certificate is saved (with auth --csr), installed from, or revoked.\"\n    sections = [\"paths\", \"install\", \"revoke\", \"certonly\", \"manage\"]\n    if verb == \"certonly\":\n        add(sections, \"--cert-path\", type=os.path.abspath,\n            default=flag_default(\"auth_cert_path\"), help=cph)\n    elif verb == \"revoke\":\n        add(sections, \"--cert-path\", type=read_file, required=False, help=cph)\n    else:\n        add(sections, \"--cert-path\", type=os.path.abspath, help=cph)\n\n    section = \"paths\"\n    if verb in (\"install\", \"revoke\"):\n        section = verb\n    # revoke --key-path reads a file, install --key-path takes a string\n    add(section, \"--key-path\",\n        type=((verb == \"revoke\" and read_file) or os.path.abspath),\n        help=\"Path to private key for certificate installation \"\n             \"or revocation (if account key is missing)\")\n\n    default_cp = None\n    if verb == \"certonly\":\n        default_cp = flag_default(\"auth_chain_path\")\n    add([\"paths\", \"install\"], \"--fullchain-path\", default=default_cp, type=os.path.abspath,\n        help=\"Accompanying path to a full certificate chain (certificate plus chain).\")\n    add(\"paths\", \"--chain-path\", default=default_cp, type=os.path.abspath,\n        help=\"Accompanying path to a certificate chain.\")\n    add(\"paths\", \"--config-dir\", default=flag_default(\"config_dir\"),\n        help=config_help(\"config_dir\"))\n    add(\"paths\", \"--work-dir\", default=flag_default(\"work_dir\"),\n        help=config_help(\"work_dir\"))\n    add(\"paths\", \"--logs-dir\", default=flag_default(\"logs_dir\"),\n        help=\"Logs directory.\")\n    add(\"paths\", \"--server\", default=flag_default(\"server\"),\n        help=config_help(\"server\"))\n"
  },
  {
    "path": "certbot/_internal/cli/plugins_parsing.py",
    "content": "\"\"\"This is a module that handles parsing of plugins for the argument parser\"\"\"\nfrom certbot._internal.cli import flag_default\n\n\ndef _plugins_parsing(helpful, plugins):\n    # It's nuts, but there are two \"plugins\" topics.  Somehow this works\n    helpful.add_group(\n        \"plugins\", description=\"Plugin Selection: Certbot client supports an \"\n        \"extensible plugins architecture. See '%(prog)s plugins' for a \"\n        \"list of all installed plugins and their names. You can force \"\n        \"a particular plugin by setting options provided below. Running \"\n        \"--help <plugin_name> will list flags specific to that plugin.\")\n\n    helpful.add(\"plugins\", \"--configurator\", default=flag_default(\"configurator\"),\n                help=\"Name of the plugin that is both an authenticator and an installer.\"\n                \" Should not be used together with --authenticator or --installer. \"\n                \"(default: Ask)\")\n    helpful.add(\"plugins\", \"-a\", \"--authenticator\", default=flag_default(\"authenticator\"),\n                help=\"Authenticator plugin name.\")\n    helpful.add(\"plugins\", \"-i\", \"--installer\", default=flag_default(\"installer\"),\n                help=\"Installer plugin name (also used to find domains).\")\n    helpful.add([\"plugins\", \"certonly\", \"run\", \"install\"],\n                \"--apache\", action=\"store_true\", default=flag_default(\"apache\"),\n                help=\"Obtain and install certificates using Apache\")\n    helpful.add([\"plugins\", \"certonly\", \"run\", \"install\"],\n                \"--nginx\", action=\"store_true\", default=flag_default(\"nginx\"),\n                help=\"Obtain and install certificates using Nginx\")\n    helpful.add([\"plugins\", \"certonly\"], \"--standalone\", action=\"store_true\",\n                default=flag_default(\"standalone\"),\n                help='Obtain certificates using a \"standalone\" webserver.')\n    helpful.add([\"plugins\", \"certonly\"], \"--manual\", action=\"store_true\",\n                default=flag_default(\"manual\"),\n                help=\"Provide laborious manual instructions for obtaining a certificate\")\n    helpful.add([\"plugins\", \"certonly\"], \"--webroot\", action=\"store_true\",\n                default=flag_default(\"webroot\"),\n                help=\"Obtain certificates by placing files in a webroot directory.\")\n    helpful.add([\"plugins\", \"certonly\"], \"--dns-cloudflare\", action=\"store_true\",\n                default=flag_default(\"dns_cloudflare\"),\n                help=(\"Obtain certificates using a DNS TXT record (if you are \"\n                      \"using Cloudflare for DNS).\"))\n    helpful.add([\"plugins\", \"certonly\"], \"--dns-cloudxns\", action=\"store_true\",\n                default=flag_default(\"dns_cloudxns\"),\n                help=(\"Obtain certificates using a DNS TXT record (if you are \"\n                     \"using CloudXNS for DNS).\"))\n    helpful.add([\"plugins\", \"certonly\"], \"--dns-digitalocean\", action=\"store_true\",\n                default=flag_default(\"dns_digitalocean\"),\n                help=(\"Obtain certificates using a DNS TXT record (if you are \"\n                      \"using DigitalOcean for DNS).\"))\n    helpful.add([\"plugins\", \"certonly\"], \"--dns-dnsimple\", action=\"store_true\",\n                default=flag_default(\"dns_dnsimple\"),\n                help=(\"Obtain certificates using a DNS TXT record (if you are \"\n                      \"using DNSimple for DNS).\"))\n    helpful.add([\"plugins\", \"certonly\"], \"--dns-dnsmadeeasy\", action=\"store_true\",\n                default=flag_default(\"dns_dnsmadeeasy\"),\n                help=(\"Obtain certificates using a DNS TXT record (if you are \"\n                      \"using DNS Made Easy for DNS).\"))\n    helpful.add([\"plugins\", \"certonly\"], \"--dns-gehirn\", action=\"store_true\",\n                default=flag_default(\"dns_gehirn\"),\n                help=(\"Obtain certificates using a DNS TXT record \"\n                      \"(if you are using Gehirn Infrastructure Service for DNS).\"))\n    helpful.add([\"plugins\", \"certonly\"], \"--dns-google\", action=\"store_true\",\n                default=flag_default(\"dns_google\"),\n                help=(\"Obtain certificates using a DNS TXT record (if you are \"\n                      \"using Google Cloud DNS).\"))\n    helpful.add([\"plugins\", \"certonly\"], \"--dns-linode\", action=\"store_true\",\n                default=flag_default(\"dns_linode\"),\n                help=(\"Obtain certificates using a DNS TXT record (if you are \"\n                      \"using Linode for DNS).\"))\n    helpful.add([\"plugins\", \"certonly\"], \"--dns-luadns\", action=\"store_true\",\n                default=flag_default(\"dns_luadns\"),\n                help=(\"Obtain certificates using a DNS TXT record (if you are \"\n                      \"using LuaDNS for DNS).\"))\n    helpful.add([\"plugins\", \"certonly\"], \"--dns-nsone\", action=\"store_true\",\n                default=flag_default(\"dns_nsone\"),\n                help=(\"Obtain certificates using a DNS TXT record (if you are \"\n                      \"using NS1 for DNS).\"))\n    helpful.add([\"plugins\", \"certonly\"], \"--dns-ovh\", action=\"store_true\",\n                default=flag_default(\"dns_ovh\"),\n                help=(\"Obtain certificates using a DNS TXT record (if you are \"\n                      \"using OVH for DNS).\"))\n    helpful.add([\"plugins\", \"certonly\"], \"--dns-rfc2136\", action=\"store_true\",\n                default=flag_default(\"dns_rfc2136\"),\n                help=\"Obtain certificates using a DNS TXT record (if you are using BIND for DNS).\")\n    helpful.add([\"plugins\", \"certonly\"], \"--dns-route53\", action=\"store_true\",\n                default=flag_default(\"dns_route53\"),\n                help=(\"Obtain certificates using a DNS TXT record (if you are using Route53 for \"\n                      \"DNS).\"))\n    helpful.add([\"plugins\", \"certonly\"], \"--dns-sakuracloud\", action=\"store_true\",\n                default=flag_default(\"dns_sakuracloud\"),\n                help=(\"Obtain certificates using a DNS TXT record \"\n                     \"(if you are using Sakura Cloud for DNS).\"))\n\n    # things should not be reorder past/pre this comment:\n    # plugins_group should be displayed in --help before plugin\n    # specific groups (so that plugins_group.description makes sense)\n\n    helpful.add_plugin_args(plugins)\n"
  },
  {
    "path": "certbot/_internal/cli/report_config_interaction.py",
    "content": "\"\"\"This is a module that reports config option interaction that should be\nchecked by set_by_cli\"\"\"\nimport six\n\nfrom certbot._internal.cli import VAR_MODIFIERS\n\n\ndef report_config_interaction(modified, modifiers):\n    \"\"\"Registers config option interaction to be checked by set_by_cli.\n\n    This function can be called by during the __init__ or\n    add_parser_arguments methods of plugins to register interactions\n    between config options.\n\n    :param modified: config options that can be modified by modifiers\n    :type modified: iterable or str (string_types)\n    :param modifiers: config options that modify modified\n    :type modifiers: iterable or str (string_types)\n\n    \"\"\"\n    if isinstance(modified, six.string_types):\n        modified = (modified,)\n    if isinstance(modifiers, six.string_types):\n        modifiers = (modifiers,)\n\n    for var in modified:\n        VAR_MODIFIERS.setdefault(var, set()).update(modifiers)\n"
  },
  {
    "path": "certbot/_internal/cli/subparsers.py",
    "content": "\"\"\"This module creates subparsers for the argument parser\"\"\"\nfrom certbot import interfaces\nfrom certbot._internal import constants\n\nfrom certbot._internal.cli import (\n    flag_default,\n    read_file,\n    CaseInsensitiveList,\n    _user_agent_comment_type,\n    _EncodeReasonAction\n)\n\n\ndef _create_subparsers(helpful):\n    from certbot._internal.client import sample_user_agent  # avoid import loops\n    helpful.add(\n        None, \"--user-agent\", default=flag_default(\"user_agent\"),\n        help='Set a custom user agent string for the client. User agent strings allow '\n             'the CA to collect high level statistics about success rates by OS, '\n             'plugin and use case, and to know when to deprecate support for past Python '\n             \"versions and flags. If you wish to hide this information from the Let's \"\n             'Encrypt server, set this to \"\". '\n             '(default: {0}). The flags encoded in the user agent are: '\n             '--duplicate, --force-renew, --allow-subset-of-names, -n, and '\n             'whether any hooks are set.'.format(sample_user_agent()))\n    helpful.add(\n        None, \"--user-agent-comment\", default=flag_default(\"user_agent_comment\"),\n        type=_user_agent_comment_type,\n        help=\"Add a comment to the default user agent string. May be used when repackaging Certbot \"\n             \"or calling it from another tool to allow additional statistical data to be collected.\"\n             \" Ignored if --user-agent is set. (Example: Foo-Wrapper/1.0)\")\n    helpful.add(\"certonly\",\n                \"--csr\", default=flag_default(\"csr\"), type=read_file,\n                help=\"Path to a Certificate Signing Request (CSR) in DER or PEM format.\"\n                \" Currently --csr only works with the 'certonly' subcommand.\")\n    helpful.add(\"revoke\",\n                \"--reason\", dest=\"reason\",\n                choices=CaseInsensitiveList(sorted(constants.REVOCATION_REASONS,\n                                                   key=constants.REVOCATION_REASONS.get)),\n                action=_EncodeReasonAction, default=flag_default(\"reason\"),\n                help=\"Specify reason for revoking certificate. (default: unspecified)\")\n    helpful.add(\"revoke\",\n                \"--delete-after-revoke\", action=\"store_true\",\n                default=flag_default(\"delete_after_revoke\"),\n                help=\"Delete certificates after revoking them, along with all previous and later \"\n                \"versions of those certificates.\")\n    helpful.add(\"revoke\",\n                \"--no-delete-after-revoke\", action=\"store_false\",\n                dest=\"delete_after_revoke\",\n                default=flag_default(\"delete_after_revoke\"),\n                help=\"Do not delete certificates after revoking them. This \"\n                     \"option should be used with caution because the 'renew' \"\n                     \"subcommand will attempt to renew undeleted revoked \"\n                     \"certificates.\")\n    helpful.add(\"rollback\",\n                \"--checkpoints\", type=int, metavar=\"N\",\n                default=flag_default(\"rollback_checkpoints\"),\n                help=\"Revert configuration N number of checkpoints.\")\n    helpful.add(\"plugins\",\n                \"--init\", action=\"store_true\", default=flag_default(\"init\"),\n                help=\"Initialize plugins.\")\n    helpful.add(\"plugins\",\n                \"--prepare\", action=\"store_true\", default=flag_default(\"prepare\"),\n                help=\"Initialize and prepare plugins.\")\n    helpful.add(\"plugins\",\n                \"--authenticators\", action=\"append_const\", dest=\"ifaces\",\n                default=flag_default(\"ifaces\"),\n                const=interfaces.IAuthenticator, help=\"Limit to authenticator plugins only.\")\n    helpful.add(\"plugins\",\n                \"--installers\", action=\"append_const\", dest=\"ifaces\",\n                default=flag_default(\"ifaces\"),\n                const=interfaces.IInstaller, help=\"Limit to installer plugins only.\")\n"
  },
  {
    "path": "certbot/_internal/cli/verb_help.py",
    "content": "\"\"\"This module contain help information for verbs supported by certbot\"\"\"\nfrom certbot.compat import os\nfrom certbot._internal.cli import (\n    SHORT_USAGE,\n    flag_default\n)\n\n# The attributes here are:\n# short: a string that will be displayed by \"certbot -h commands\"\n# opts:  a string that heads the section of flags with which this command is documented,\n#        both for \"certbot -h SUBCOMMAND\" and \"certbot -h all\"\n# usage: an optional string that overrides the header of \"certbot -h SUBCOMMAND\"\nVERB_HELP = [\n    (\"run (default)\", {\n        \"short\": \"Obtain/renew a certificate, and install it\",\n        \"opts\": \"Options for obtaining & installing certificates\",\n        \"usage\": SHORT_USAGE.replace(\"[SUBCOMMAND]\", \"\"),\n        \"realname\": \"run\"\n    }),\n    (\"certonly\", {\n        \"short\": \"Obtain or renew a certificate, but do not install it\",\n        \"opts\": \"Options for modifying how a certificate is obtained\",\n        \"usage\": (\"\\n\\n  certbot certonly [options] [-d DOMAIN] [-d DOMAIN] ...\\n\\n\"\n                  \"This command obtains a TLS/SSL certificate without installing it anywhere.\")\n    }),\n    (\"renew\", {\n        \"short\": \"Renew all certificates (or one specified with --cert-name)\",\n        \"opts\": (\"The 'renew' subcommand will attempt to renew all\"\n                 \" certificates (or more precisely, certificate lineages) you have\"\n                 \" previously obtained if they are close to expiry, and print a\"\n                 \" summary of the results. By default, 'renew' will reuse the options\"\n                 \" used to create obtain or most recently successfully renew each\"\n                 \" certificate lineage. You can try it with `--dry-run` first. For\"\n                 \" more fine-grained control, you can renew individual lineages with\"\n                 \" the `certonly` subcommand. Hooks are available to run commands\"\n                 \" before and after renewal; see\"\n                 \" https://certbot.eff.org/docs/using.html#renewal for more\"\n                 \" information on these.\"),\n        \"usage\": \"\\n\\n  certbot renew [--cert-name CERTNAME] [options]\\n\\n\"\n    }),\n    (\"certificates\", {\n        \"short\": \"List certificates managed by Certbot\",\n        \"opts\": \"List certificates managed by Certbot\",\n        \"usage\": (\"\\n\\n  certbot certificates [options] ...\\n\\n\"\n                  \"Print information about the status of certificates managed by Certbot.\")\n    }),\n    (\"delete\", {\n        \"short\": \"Clean up all files related to a certificate\",\n        \"opts\": \"Options for deleting a certificate\",\n        \"usage\": \"\\n\\n  certbot delete --cert-name CERTNAME\\n\\n\"\n    }),\n    (\"revoke\", {\n        \"short\": \"Revoke a certificate specified with --cert-path or --cert-name\",\n        \"opts\": \"Options for revocation of certificates\",\n        \"usage\": \"\\n\\n  certbot revoke [--cert-path /path/to/fullchain.pem | \"\n        \"--cert-name example.com] [options]\\n\\n\"\n    }),\n    (\"register\", {\n        \"short\": \"Register for account with Let's Encrypt / other ACME server\",\n        \"opts\": \"Options for account registration\",\n        \"usage\": \"\\n\\n  certbot register --email user@example.com [options]\\n\\n\"\n    }),\n    (\"update_account\", {\n        \"short\": \"Update existing account with Let's Encrypt / other ACME server\",\n        \"opts\": \"Options for account modification\",\n        \"usage\": \"\\n\\n  certbot update_account --email updated_email@example.com [options]\\n\\n\"\n    }),\n    (\"unregister\", {\n        \"short\": \"Irrevocably deactivate your account\",\n        \"opts\": \"Options for account deactivation.\",\n        \"usage\": \"\\n\\n  certbot unregister [options]\\n\\n\"\n    }),\n    (\"install\", {\n        \"short\": \"Install an arbitrary certificate in a server\",\n        \"opts\": \"Options for modifying how a certificate is deployed\",\n        \"usage\": \"\\n\\n  certbot install --cert-path /path/to/fullchain.pem \"\n        \" --key-path /path/to/private-key [options]\\n\\n\"\n    }),\n    (\"rollback\", {\n        \"short\": \"Roll back server conf changes made during certificate installation\",\n        \"opts\": \"Options for rolling back server configuration changes\",\n        \"usage\": \"\\n\\n  certbot rollback --checkpoints 3 [options]\\n\\n\"\n    }),\n    (\"plugins\", {\n        \"short\": \"List plugins that are installed and available on your system\",\n        \"opts\": 'Options for the \"plugins\" subcommand',\n        \"usage\": \"\\n\\n  certbot plugins [options]\\n\\n\"\n    }),\n    (\"update_symlinks\", {\n        \"short\": \"Recreate symlinks in your /etc/letsencrypt/live/ directory\",\n        \"opts\": (\"Recreates certificate and key symlinks in {0}, if you changed them by hand \"\n                 \"or edited a renewal configuration file\".format(\n                  os.path.join(flag_default(\"config_dir\"), \"live\"))),\n        \"usage\": \"\\n\\n  certbot update_symlinks [options]\\n\\n\"\n    }),\n    (\"enhance\", {\n        \"short\": \"Add security enhancements to your existing configuration\",\n        \"opts\": (\"Helps to harden the TLS configuration by adding security enhancements \"\n                 \"to already existing configuration.\"),\n        \"usage\": \"\\n\\n  certbot enhance [options]\\n\\n\"\n    }),\n]\n\n\n# VERB_HELP is a list in order to preserve order, but a dict is sometimes useful\nVERB_HELP_MAP = dict(VERB_HELP)\n"
  },
  {
    "path": "certbot/_internal/client.py",
    "content": "\"\"\"Certbot client API.\"\"\"\nimport datetime\nimport logging\nimport platform\n\nfrom cryptography.hazmat.backends import default_backend\n# See https://github.com/pyca/cryptography/issues/4275\nfrom cryptography.hazmat.primitives.asymmetric.rsa import generate_private_key  # type: ignore\nimport josepy as jose\nimport OpenSSL\nimport zope.component\n\nfrom acme import client as acme_client\nfrom acme import crypto_util as acme_crypto_util\nfrom acme import errors as acme_errors\nfrom acme import messages\nfrom acme.magic_typing import List\nfrom acme.magic_typing import Optional\nimport certbot\nfrom certbot import crypto_util\nfrom certbot import errors\nfrom certbot import interfaces\nfrom certbot import util\nfrom certbot._internal import account\nfrom certbot._internal import auth_handler\nfrom certbot._internal import cli\nfrom certbot._internal import constants\nfrom certbot._internal import eff\nfrom certbot._internal import error_handler\nfrom certbot._internal import storage\nfrom certbot._internal.display import enhancements\nfrom certbot._internal.plugins import selection as plugin_selection\nfrom certbot.compat import os\nfrom certbot.display import ops as display_ops\n\nlogger = logging.getLogger(__name__)\n\n\ndef acme_from_config_key(config, key, regr=None):\n    \"Wrangle ACME client construction\"\n    # TODO: Allow for other alg types besides RS256\n    net = acme_client.ClientNetwork(key, account=regr, verify_ssl=(not config.no_verify_ssl),\n                                    user_agent=determine_user_agent(config))\n    return acme_client.BackwardsCompatibleClientV2(net, key, config.server)\n\n\ndef determine_user_agent(config):\n    \"\"\"\n    Set a user_agent string in the config based on the choice of plugins.\n    (this wasn't knowable at construction time)\n\n    :returns: the client's User-Agent string\n    :rtype: `str`\n    \"\"\"\n\n    # WARNING: To ensure changes are in line with Certbot's privacy\n    # policy, talk to a core Certbot team member before making any\n    # changes here.\n    if config.user_agent is None:\n        ua = (\"CertbotACMEClient/{0} ({1}; {2}{8}) Authenticator/{3} Installer/{4} \"\n              \"({5}; flags: {6}) Py/{7}\")\n        if os.environ.get(\"CERTBOT_DOCS\") == \"1\":\n            cli_command = \"certbot(-auto)\"\n            os_info = \"OS_NAME OS_VERSION\"\n            python_version = \"major.minor.patchlevel\"\n        else:\n            cli_command = cli.cli_command\n            os_info = util.get_os_info_ua()\n            python_version = platform.python_version()\n        ua = ua.format(certbot.__version__, cli_command, os_info,\n                       config.authenticator, config.installer, config.verb,\n                       ua_flags(config), python_version,\n                       \"; \" + config.user_agent_comment if config.user_agent_comment else \"\")\n    else:\n        ua = config.user_agent\n    return ua\n\ndef ua_flags(config):\n    \"Turn some very important CLI flags into clues in the user agent.\"\n    if isinstance(config, DummyConfig):\n        return \"FLAGS\"\n    flags = []\n    if config.duplicate:\n        flags.append(\"dup\")\n    if config.renew_by_default:\n        flags.append(\"frn\")\n    if config.allow_subset_of_names:\n        flags.append(\"asn\")\n    if config.noninteractive_mode:\n        flags.append(\"n\")\n    hook_names = (\"pre\", \"post\", \"renew\", \"manual_auth\", \"manual_cleanup\")\n    hooks = [getattr(config, h + \"_hook\") for h in hook_names]\n    if any(hooks):\n        flags.append(\"hook\")\n    return \" \".join(flags)\n\nclass DummyConfig(object):\n    \"Shim for computing a sample user agent.\"\n    def __init__(self):\n        self.authenticator = \"XXX\"\n        self.installer = \"YYY\"\n        self.user_agent = None\n        self.verb = \"SUBCOMMAND\"\n\n    def __getattr__(self, name):\n        \"Any config properties we might have are None.\"\n        return None\n\ndef sample_user_agent():\n    \"Document what this Certbot's user agent string will be like.\"\n\n    return determine_user_agent(DummyConfig())\n\n\ndef register(config, account_storage, tos_cb=None):\n    \"\"\"Register new account with an ACME CA.\n\n    This function takes care of generating fresh private key,\n    registering the account, optionally accepting CA Terms of Service\n    and finally saving the account. It should be called prior to\n    initialization of `Client`, unless account has already been created.\n\n    :param .IConfig config: Client configuration.\n\n    :param .AccountStorage account_storage: Account storage where newly\n        registered account will be saved to. Save happens only after TOS\n        acceptance step, so any account private keys or\n        `.RegistrationResource` will not be persisted if `tos_cb`\n        returns ``False``.\n\n    :param tos_cb: If ACME CA requires the user to accept a Terms of\n        Service before registering account, client action is\n        necessary. For example, a CLI tool would prompt the user\n        acceptance. `tos_cb` must be a callable that should accept\n        `.RegistrationResource` and return a `bool`: ``True`` iff the\n        Terms of Service present in the contained\n        `.Registration.terms_of_service` is accepted by the client, and\n        ``False`` otherwise. ``tos_cb`` will be called only if the\n        client action is necessary, i.e. when ``terms_of_service is not\n        None``. This argument is optional, if not supplied it will\n        default to automatic acceptance!\n\n    :raises certbot.errors.Error: In case of any client problems, in\n        particular registration failure, or unaccepted Terms of Service.\n    :raises acme.errors.Error: In case of any protocol problems.\n\n    :returns: Newly registered and saved account, as well as protocol\n        API handle (should be used in `Client` initialization).\n    :rtype: `tuple` of `.Account` and `acme.client.Client`\n\n    \"\"\"\n    # Log non-standard actions, potentially wrong API calls\n    if account_storage.find_all():\n        logger.info(\"There are already existing accounts for %s\", config.server)\n    if config.email is None:\n        if not config.register_unsafely_without_email:\n            msg = (\"No email was provided and \"\n                   \"--register-unsafely-without-email was not present.\")\n            logger.warning(msg)\n            raise errors.Error(msg)\n        if not config.dry_run:\n            logger.info(\"Registering without email!\")\n\n    # If --dry-run is used, and there is no staging account, create one with no email.\n    if config.dry_run:\n        config.email = None\n\n    # Each new registration shall use a fresh new key\n    rsa_key = generate_private_key(\n            public_exponent=65537,\n            key_size=config.rsa_key_size,\n            backend=default_backend())\n    key = jose.JWKRSA(key=jose.ComparableRSAKey(rsa_key))\n    acme = acme_from_config_key(config, key)\n    # TODO: add phone?\n    regr = perform_registration(acme, config, tos_cb)\n\n    acc = account.Account(regr, key)\n    account.report_new_account(config)\n    account_storage.save(acc, acme)\n\n    eff.handle_subscription(config)\n\n    return acc, acme\n\n\ndef perform_registration(acme, config, tos_cb):\n    \"\"\"\n    Actually register new account, trying repeatedly if there are email\n    problems\n\n    :param acme.client.Client client: ACME client object.\n    :param .IConfig config: Client configuration.\n    :param Callable tos_cb: a callback to handle Term of Service agreement.\n\n    :returns: Registration Resource.\n    :rtype: `acme.messages.RegistrationResource`\n    \"\"\"\n\n    eab_credentials_supplied = config.eab_kid and config.eab_hmac_key\n    if eab_credentials_supplied:\n        account_public_key = acme.client.net.key.public_key()\n        eab = messages.ExternalAccountBinding.from_data(account_public_key=account_public_key,\n                                                        kid=config.eab_kid,\n                                                        hmac_key=config.eab_hmac_key,\n                                                        directory=acme.client.directory)\n    else:\n        eab = None\n\n    if acme.external_account_required():\n        if not eab_credentials_supplied:\n            msg = (\"Server requires external account binding.\"\n                   \" Please use --eab-kid and --eab-hmac-key.\")\n            raise errors.Error(msg)\n\n    try:\n        newreg = messages.NewRegistration.from_data(email=config.email,\n                                                    external_account_binding=eab)\n        return acme.new_account_and_tos(newreg, tos_cb)\n    except messages.Error as e:\n        if e.code == \"invalidEmail\" or e.code == \"invalidContact\":\n            if config.noninteractive_mode:\n                msg = (\"The ACME server believes %s is an invalid email address. \"\n                       \"Please ensure it is a valid email and attempt \"\n                       \"registration again.\" % config.email)\n                raise errors.Error(msg)\n            config.email = display_ops.get_email(invalid=True)\n            return perform_registration(acme, config, tos_cb)\n        raise\n\n\nclass Client(object):\n    \"\"\"Certbot's client.\n\n    :ivar .IConfig config: Client configuration.\n    :ivar .Account account: Account registered with `register`.\n    :ivar .AuthHandler auth_handler: Authorizations handler that will\n        dispatch DV challenges to appropriate authenticators\n        (providing `.IAuthenticator` interface).\n    :ivar .IAuthenticator auth: Prepared (`.IAuthenticator.prepare`)\n        authenticator that can solve ACME challenges.\n    :ivar .IInstaller installer: Installer.\n    :ivar acme.client.BackwardsCompatibleClientV2 acme: Optional ACME\n        client API handle. You might already have one from `register`.\n\n    \"\"\"\n\n    def __init__(self, config, account_, auth, installer, acme=None):\n        \"\"\"Initialize a client.\"\"\"\n        self.config = config\n        self.account = account_\n        self.auth = auth\n        self.installer = installer\n\n        # Initialize ACME if account is provided\n        if acme is None and self.account is not None:\n            acme = acme_from_config_key(config, self.account.key, self.account.regr)\n        self.acme = acme\n\n        if auth is not None:\n            self.auth_handler = auth_handler.AuthHandler(\n                auth, self.acme, self.account, self.config.pref_challs)\n        else:\n            self.auth_handler = None\n\n    def obtain_certificate_from_csr(self, csr, orderr=None):\n        \"\"\"Obtain certificate.\n\n        :param .util.CSR csr: PEM-encoded Certificate Signing\n            Request. The key used to generate this CSR can be different\n            than `authkey`.\n        :param acme.messages.OrderResource orderr: contains authzrs\n\n        :returns: certificate and chain as PEM byte strings\n        :rtype: tuple\n\n        \"\"\"\n        if self.auth_handler is None:\n            msg = (\"Unable to obtain certificate because authenticator is \"\n                   \"not set.\")\n            logger.warning(msg)\n            raise errors.Error(msg)\n        if self.account.regr is None:\n            raise errors.Error(\"Please register with the ACME server first.\")\n\n        logger.debug(\"CSR: %s\", csr)\n\n        if orderr is None:\n            orderr = self._get_order_and_authorizations(csr.data, best_effort=False)\n\n        deadline = datetime.datetime.now() + datetime.timedelta(seconds=90)\n        orderr = self.acme.finalize_order(orderr, deadline)\n        cert, chain = crypto_util.cert_and_chain_from_fullchain(orderr.fullchain_pem)\n        return cert.encode(), chain.encode()\n\n    def obtain_certificate(self, domains, old_keypath=None):\n        \"\"\"Obtains a certificate from the ACME server.\n\n        `.register` must be called before `.obtain_certificate`\n\n        :param list domains: domains to get a certificate\n\n        :returns: certificate as PEM string, chain as PEM string,\n            newly generated private key (`.util.Key`), and DER-encoded\n            Certificate Signing Request (`.util.CSR`).\n        :rtype: tuple\n\n        \"\"\"\n\n        # We need to determine the key path, key PEM data, CSR path,\n        # and CSR PEM data.  For a dry run, the paths are None because\n        # they aren't permanently saved to disk.  For a lineage with\n        # --reuse-key, the key path and PEM data are derived from an\n        # existing file.\n\n        if old_keypath is not None:\n            # We've been asked to reuse a specific existing private key.\n            # Therefore, we'll read it now and not generate a new one in\n            # either case below.\n            #\n            # We read in bytes here because the type of `key.pem`\n            # created below is also bytes.\n            with open(old_keypath, \"rb\") as f:\n                keypath = old_keypath\n                keypem = f.read()\n            key = util.Key(file=keypath, pem=keypem) # type: Optional[util.Key]\n            logger.info(\"Reusing existing private key from %s.\", old_keypath)\n        else:\n            # The key is set to None here but will be created below.\n            key = None\n\n        # Create CSR from names\n        if self.config.dry_run:\n            key = key or util.Key(file=None,\n                                  pem=crypto_util.make_key(self.config.rsa_key_size))\n            csr = util.CSR(file=None, form=\"pem\",\n                           data=acme_crypto_util.make_csr(\n                               key.pem, domains, self.config.must_staple))\n        else:\n            key = key or crypto_util.init_save_key(self.config.rsa_key_size,\n                                                   self.config.key_dir)\n            csr = crypto_util.init_save_csr(key, domains, self.config.csr_dir)\n\n        orderr = self._get_order_and_authorizations(csr.data, self.config.allow_subset_of_names)\n        authzr = orderr.authorizations\n        auth_domains = set(a.body.identifier.value for a in authzr)\n        successful_domains = [d for d in domains if d in auth_domains]\n\n        # allow_subset_of_names is currently disabled for wildcard\n        # certificates. The reason for this and checking allow_subset_of_names\n        # below is because successful_domains == domains is never true if\n        # domains contains a wildcard because the ACME spec forbids identifiers\n        # in authzs from containing a wildcard character.\n        if self.config.allow_subset_of_names and successful_domains != domains:\n            if not self.config.dry_run:\n                os.remove(key.file)\n                os.remove(csr.file)\n            return self.obtain_certificate(successful_domains)\n        else:\n            cert, chain = self.obtain_certificate_from_csr(csr, orderr)\n            return cert, chain, key, csr\n\n    def _get_order_and_authorizations(self, csr_pem, best_effort):\n        # type: (str, bool) -> List[messages.OrderResource]\n        \"\"\"Request a new order and complete its authorizations.\n\n        :param str csr_pem: A CSR in PEM format.\n        :param bool best_effort: True if failing to complete all\n            authorizations should not raise an exception\n\n        :returns: order resource containing its completed authorizations\n        :rtype: acme.messages.OrderResource\n\n        \"\"\"\n        try:\n            orderr = self.acme.new_order(csr_pem)\n        except acme_errors.WildcardUnsupportedError:\n            raise errors.Error(\"The currently selected ACME CA endpoint does\"\n                               \" not support issuing wildcard certificates.\")\n\n        # For a dry run, ensure we have an order with fresh authorizations\n        if orderr and self.config.dry_run:\n            deactivated, failed = self.auth_handler.deactivate_valid_authorizations(orderr)\n            if deactivated:\n                logger.debug(\"Recreating order after authz deactivations\")\n                orderr = self.acme.new_order(csr_pem)\n            if failed:\n                logger.warning(\"Certbot was unable to obtain fresh authorizations for every domain\"\n                               \". The dry run will continue, but results may not be accurate.\")\n\n        authzr = self.auth_handler.handle_authorizations(orderr, best_effort)\n        return orderr.update(authorizations=authzr)\n    def obtain_and_enroll_certificate(self, domains, certname):\n        \"\"\"Obtain and enroll certificate.\n\n        Get a new certificate for the specified domains using the specified\n        authenticator and installer, and then create a new renewable lineage\n        containing it.\n\n        :param domains: domains to request a certificate for\n        :type domains: `list` of `str`\n        :param certname: requested name of lineage\n        :type certname: `str` or `None`\n\n        :returns: A new :class:`certbot._internal.storage.RenewableCert` instance\n            referred to the enrolled cert lineage, False if the cert could not\n            be obtained, or None if doing a successful dry run.\n\n        \"\"\"\n        cert, chain, key, _ = self.obtain_certificate(domains)\n\n        if (self.config.config_dir != constants.CLI_DEFAULTS[\"config_dir\"] or\n                self.config.work_dir != constants.CLI_DEFAULTS[\"work_dir\"]):\n            logger.info(\n                \"Non-standard path(s), might not work with crontab installed \"\n                \"by your operating system package manager\")\n\n        new_name = self._choose_lineagename(domains, certname)\n\n        if self.config.dry_run:\n            logger.debug(\"Dry run: Skipping creating new lineage for %s\",\n                        new_name)\n            return None\n        return storage.RenewableCert.new_lineage(\n            new_name, cert,\n            key.pem, chain,\n            self.config)\n\n    def _choose_lineagename(self, domains, certname):\n        \"\"\"Chooses a name for the new lineage.\n\n        :param domains: domains in certificate request\n        :type domains: `list` of `str`\n        :param certname: requested name of lineage\n        :type certname: `str` or `None`\n\n        :returns: lineage name that should be used\n        :rtype: str\n\n        \"\"\"\n        if certname:\n            return certname\n        elif util.is_wildcard_domain(domains[0]):\n            # Don't make files and directories starting with *.\n            return domains[0][2:]\n        return domains[0]\n\n    def save_certificate(self, cert_pem, chain_pem,\n                         cert_path, chain_path, fullchain_path):\n        \"\"\"Saves the certificate received from the ACME server.\n\n        :param str cert_pem:\n        :param str chain_pem:\n        :param str cert_path: Candidate path to a certificate.\n        :param str chain_path: Candidate path to a certificate chain.\n        :param str fullchain_path: Candidate path to a full cert chain.\n\n        :returns: cert_path, chain_path, and fullchain_path as absolute\n            paths to the actual files\n        :rtype: `tuple` of `str`\n\n        :raises IOError: If unable to find room to write the cert files\n\n        \"\"\"\n        for path in cert_path, chain_path, fullchain_path:\n            util.make_or_verify_dir(os.path.dirname(path), 0o755, self.config.strict_permissions)\n\n\n        cert_file, abs_cert_path = _open_pem_file('cert_path', cert_path)\n\n        try:\n            cert_file.write(cert_pem)\n        finally:\n            cert_file.close()\n        logger.info(\"Server issued certificate; certificate written to %s\",\n                    abs_cert_path)\n\n        chain_file, abs_chain_path =\\\n                _open_pem_file('chain_path', chain_path)\n        fullchain_file, abs_fullchain_path =\\\n                _open_pem_file('fullchain_path', fullchain_path)\n\n        _save_chain(chain_pem, chain_file)\n        _save_chain(cert_pem + chain_pem, fullchain_file)\n\n        return abs_cert_path, abs_chain_path, abs_fullchain_path\n\n    def deploy_certificate(self, domains, privkey_path,\n                           cert_path, chain_path, fullchain_path):\n        \"\"\"Install certificate\n\n        :param list domains: list of domains to install the certificate\n        :param str privkey_path: path to certificate private key\n        :param str cert_path: certificate file path (optional)\n        :param str chain_path: chain file path\n\n        \"\"\"\n        if self.installer is None:\n            logger.warning(\"No installer specified, client is unable to deploy\"\n                           \"the certificate\")\n            raise errors.Error(\"No installer available\")\n\n        chain_path = None if chain_path is None else os.path.abspath(chain_path)\n\n        msg = (\"Unable to install the certificate\")\n        with error_handler.ErrorHandler(self._recovery_routine_with_msg, msg):\n            for dom in domains:\n                self.installer.deploy_cert(\n                    domain=dom, cert_path=os.path.abspath(cert_path),\n                    key_path=os.path.abspath(privkey_path),\n                    chain_path=chain_path,\n                    fullchain_path=fullchain_path)\n                self.installer.save()  # needed by the Apache plugin\n\n            self.installer.save(\"Deployed ACME Certificate\")\n\n        msg = (\"We were unable to install your certificate, \"\n               \"however, we successfully restored your \"\n               \"server to its prior configuration.\")\n        with error_handler.ErrorHandler(self._rollback_and_restart, msg):\n            # sites may have been enabled / final cleanup\n            self.installer.restart()\n\n    def enhance_config(self, domains, chain_path, ask_redirect=True):\n        \"\"\"Enhance the configuration.\n\n        :param list domains: list of domains to configure\n        :param chain_path: chain file path\n        :type chain_path: `str` or `None`\n\n        :raises .errors.Error: if no installer is specified in the\n            client.\n\n        \"\"\"\n        if self.installer is None:\n            logger.warning(\"No installer is specified, there isn't any \"\n                           \"configuration to enhance.\")\n            raise errors.Error(\"No installer available\")\n\n        enhanced = False\n        enhancement_info = (\n            (\"hsts\", \"ensure-http-header\", \"Strict-Transport-Security\"),\n            (\"redirect\", \"redirect\", None),\n            (\"staple\", \"staple-ocsp\", chain_path),\n            (\"uir\", \"ensure-http-header\", \"Upgrade-Insecure-Requests\"),)\n        supported = self.installer.supported_enhancements()\n\n        for config_name, enhancement_name, option in enhancement_info:\n            config_value = getattr(self.config, config_name)\n            if enhancement_name in supported:\n                if ask_redirect:\n                    if config_name == \"redirect\" and config_value is None:\n                        config_value = enhancements.ask(enhancement_name)\n                        if not config_value:\n                            logger.warning(\"Future versions of Certbot will automatically \"\n                                \"configure the webserver so that all requests redirect to secure \"\n                                \"HTTPS access. You can control this behavior and disable this \"\n                                \"warning with the --redirect and --no-redirect flags.\")\n                if config_value:\n                    self.apply_enhancement(domains, enhancement_name, option)\n                    enhanced = True\n            elif config_value:\n                logger.warning(\n                    \"Option %s is not supported by the selected installer. \"\n                    \"Skipping enhancement.\", config_name)\n\n        msg = (\"We were unable to restart web server\")\n        if enhanced:\n            with error_handler.ErrorHandler(self._rollback_and_restart, msg):\n                self.installer.restart()\n\n    def apply_enhancement(self, domains, enhancement, options=None):\n        \"\"\"Applies an enhancement on all domains.\n\n        :param list domains: list of ssl_vhosts (as strings)\n        :param str enhancement: name of enhancement, e.g. ensure-http-header\n        :param str options: options to enhancement, e.g. Strict-Transport-Security\n\n            .. note:: When more `options` are needed, make options a list.\n\n        :raises .errors.PluginError: If Enhancement is not supported, or if\n            there is any other problem with the enhancement.\n\n\n        \"\"\"\n        msg = (\"We were unable to set up enhancement %s for your server, \"\n               \"however, we successfully installed your certificate.\"\n               % (enhancement))\n        with error_handler.ErrorHandler(self._recovery_routine_with_msg, msg):\n            for dom in domains:\n                try:\n                    self.installer.enhance(dom, enhancement, options)\n                except errors.PluginEnhancementAlreadyPresent:\n                    if enhancement == \"ensure-http-header\":\n                        logger.warning(\"Enhancement %s was already set.\",\n                                options)\n                    else:\n                        logger.warning(\"Enhancement %s was already set.\",\n                                enhancement)\n                except errors.PluginError:\n                    logger.warning(\"Unable to set enhancement %s for %s\",\n                            enhancement, dom)\n                    raise\n\n            self.installer.save(\"Add enhancement %s\" % (enhancement))\n\n    def _recovery_routine_with_msg(self, success_msg):\n        \"\"\"Calls the installer's recovery routine and prints success_msg\n\n        :param str success_msg: message to show on successful recovery\n\n        \"\"\"\n        self.installer.recovery_routine()\n        reporter = zope.component.getUtility(interfaces.IReporter)\n        reporter.add_message(success_msg, reporter.HIGH_PRIORITY)\n\n    def _rollback_and_restart(self, success_msg):\n        \"\"\"Rollback the most recent checkpoint and restart the webserver\n\n        :param str success_msg: message to show on successful rollback\n\n        \"\"\"\n        logger.critical(\"Rolling back to previous server configuration...\")\n        reporter = zope.component.getUtility(interfaces.IReporter)\n        try:\n            self.installer.rollback_checkpoints()\n            self.installer.restart()\n        except:\n            reporter.add_message(\n                \"An error occurred and we failed to restore your config and \"\n                \"restart your server. Please post to \"\n                \"https://community.letsencrypt.org/c/help \"\n                \"with details about your configuration and this error you received.\",\n                reporter.HIGH_PRIORITY)\n            raise\n        reporter.add_message(success_msg, reporter.HIGH_PRIORITY)\n\n\ndef validate_key_csr(privkey, csr=None):\n    \"\"\"Validate Key and CSR files.\n\n    Verifies that the client key and csr arguments are valid and correspond to\n    one another. This does not currently check the names in the CSR due to\n    the inability to read SANs from CSRs in python crypto libraries.\n\n    If csr is left as None, only the key will be validated.\n\n    :param privkey: Key associated with CSR\n    :type privkey: :class:`certbot.util.Key`\n\n    :param .util.CSR csr: CSR\n\n    :raises .errors.Error: when validation fails\n\n    \"\"\"\n    # TODO: Handle all of these problems appropriately\n    # The client can eventually do things like prompt the user\n    # and allow the user to take more appropriate actions\n\n    # Key must be readable and valid.\n    if privkey.pem and not crypto_util.valid_privkey(privkey.pem):\n        raise errors.Error(\"The provided key is not a valid key\")\n\n    if csr:\n        if csr.form == \"der\":\n            csr_obj = OpenSSL.crypto.load_certificate_request(\n                OpenSSL.crypto.FILETYPE_ASN1, csr.data)\n            cert_buffer = OpenSSL.crypto.dump_certificate_request(\n                OpenSSL.crypto.FILETYPE_PEM, csr_obj\n            )\n            csr = util.CSR(csr.file, cert_buffer, \"pem\")\n\n        # If CSR is provided, it must be readable and valid.\n        if csr.data and not crypto_util.valid_csr(csr.data):\n            raise errors.Error(\"The provided CSR is not a valid CSR\")\n\n        # If both CSR and key are provided, the key must be the same key used\n        # in the CSR.\n        if csr.data and privkey.pem:\n            if not crypto_util.csr_matches_pubkey(\n                    csr.data, privkey.pem):\n                raise errors.Error(\"The key and CSR do not match\")\n\n\ndef rollback(default_installer, checkpoints, config, plugins):\n    \"\"\"Revert configuration the specified number of checkpoints.\n\n    :param int checkpoints: Number of checkpoints to revert.\n\n    :param config: Configuration.\n    :type config: :class:`certbot.interfaces.IConfig`\n\n    \"\"\"\n    # Misconfigurations are only a slight problems... allow the user to rollback\n    installer = plugin_selection.pick_installer(\n        config, default_installer, plugins, question=\"Which installer \"\n        \"should be used for rollback?\")\n\n    # No Errors occurred during init... proceed normally\n    # If installer is None... couldn't find an installer... there shouldn't be\n    # anything to rollback\n    if installer is not None:\n        installer.rollback_checkpoints(checkpoints)\n        installer.restart()\n\ndef _open_pem_file(cli_arg_path, pem_path):\n    \"\"\"Open a pem file.\n\n    If cli_arg_path was set by the client, open that.\n    Otherwise, uniquify the file path.\n\n    :param str cli_arg_path: the cli arg name, e.g. cert_path\n    :param str pem_path: the pem file path to open\n\n    :returns: a tuple of file object and its absolute file path\n\n    \"\"\"\n    if cli.set_by_cli(cli_arg_path):\n        return util.safe_open(pem_path, chmod=0o644, mode=\"wb\"),\\\n            os.path.abspath(pem_path)\n    uniq = util.unique_file(pem_path, 0o644, \"wb\")\n    return uniq[0], os.path.abspath(uniq[1])\n\ndef _save_chain(chain_pem, chain_file):\n    \"\"\"Saves chain_pem at a unique path based on chain_path.\n\n    :param str chain_pem: certificate chain in PEM format\n    :param str chain_file: chain file object\n\n    \"\"\"\n    try:\n        chain_file.write(chain_pem)\n    finally:\n        chain_file.close()\n\n    logger.info(\"Cert chain written to %s\", chain_file.name)\n"
  },
  {
    "path": "certbot/_internal/configuration.py",
    "content": "\"\"\"Certbot user-supplied configuration.\"\"\"\nimport copy\n\nfrom six.moves.urllib import parse\nimport zope.interface\n\nfrom certbot import errors\nfrom certbot import interfaces\nfrom certbot import util\nfrom certbot._internal import constants\nfrom certbot.compat import misc\nfrom certbot.compat import os\n\n\n@zope.interface.implementer(interfaces.IConfig)\nclass NamespaceConfig(object):\n    \"\"\"Configuration wrapper around :class:`argparse.Namespace`.\n\n    For more documentation, including available attributes, please see\n    :class:`certbot.interfaces.IConfig`. However, note that\n    the following attributes are dynamically resolved using\n    :attr:`~certbot.interfaces.IConfig.work_dir` and relative\n    paths defined in :py:mod:`certbot._internal.constants`:\n\n      - `accounts_dir`\n      - `csr_dir`\n      - `in_progress_dir`\n      - `key_dir`\n      - `temp_checkpoint_dir`\n\n    And the following paths are dynamically resolved using\n    :attr:`~certbot.interfaces.IConfig.config_dir` and relative\n    paths defined in :py:mod:`certbot._internal.constants`:\n\n      - `default_archive_dir`\n      - `live_dir`\n      - `renewal_configs_dir`\n\n    :ivar namespace: Namespace typically produced by\n        :meth:`argparse.ArgumentParser.parse_args`.\n    :type namespace: :class:`argparse.Namespace`\n\n    \"\"\"\n\n    def __init__(self, namespace):\n        object.__setattr__(self, 'namespace', namespace)\n\n        self.namespace.config_dir = os.path.abspath(self.namespace.config_dir)\n        self.namespace.work_dir = os.path.abspath(self.namespace.work_dir)\n        self.namespace.logs_dir = os.path.abspath(self.namespace.logs_dir)\n\n        # Check command line parameters sanity, and error out in case of problem.\n        check_config_sanity(self)\n\n    def __getattr__(self, name):\n        return getattr(self.namespace, name)\n\n    def __setattr__(self, name, value):\n        setattr(self.namespace, name, value)\n\n    @property\n    def server_path(self):\n        \"\"\"File path based on ``server``.\"\"\"\n        parsed = parse.urlparse(self.namespace.server)\n        return (parsed.netloc + parsed.path).replace('/', os.path.sep)\n\n    @property\n    def accounts_dir(self):  # pylint: disable=missing-function-docstring\n        return self.accounts_dir_for_server_path(self.server_path)\n\n    def accounts_dir_for_server_path(self, server_path):\n        \"\"\"Path to accounts directory based on server_path\"\"\"\n        server_path = misc.underscores_for_unsupported_characters_in_path(server_path)\n        return os.path.join(\n            self.namespace.config_dir, constants.ACCOUNTS_DIR, server_path)\n\n    @property\n    def backup_dir(self):  # pylint: disable=missing-function-docstring\n        return os.path.join(self.namespace.work_dir, constants.BACKUP_DIR)\n\n    @property\n    def csr_dir(self):  # pylint: disable=missing-function-docstring\n        return os.path.join(self.namespace.config_dir, constants.CSR_DIR)\n\n    @property\n    def in_progress_dir(self):  # pylint: disable=missing-function-docstring\n        return os.path.join(self.namespace.work_dir, constants.IN_PROGRESS_DIR)\n\n    @property\n    def key_dir(self):  # pylint: disable=missing-function-docstring\n        return os.path.join(self.namespace.config_dir, constants.KEY_DIR)\n\n    @property\n    def temp_checkpoint_dir(self):  # pylint: disable=missing-function-docstring\n        return os.path.join(\n            self.namespace.work_dir, constants.TEMP_CHECKPOINT_DIR)\n\n    def __deepcopy__(self, _memo):\n        # Work around https://bugs.python.org/issue1515 for py26 tests :( :(\n        # https://travis-ci.org/letsencrypt/letsencrypt/jobs/106900743#L3276\n        new_ns = copy.deepcopy(self.namespace)\n        return type(self)(new_ns)\n\n    @property\n    def default_archive_dir(self):  # pylint: disable=missing-function-docstring\n        return os.path.join(self.namespace.config_dir, constants.ARCHIVE_DIR)\n\n    @property\n    def live_dir(self):  # pylint: disable=missing-function-docstring\n        return os.path.join(self.namespace.config_dir, constants.LIVE_DIR)\n\n    @property\n    def renewal_configs_dir(self):  # pylint: disable=missing-function-docstring\n        return os.path.join(\n            self.namespace.config_dir, constants.RENEWAL_CONFIGS_DIR)\n\n    @property\n    def renewal_hooks_dir(self):\n        \"\"\"Path to directory with hooks to run with the renew subcommand.\"\"\"\n        return os.path.join(self.namespace.config_dir,\n                            constants.RENEWAL_HOOKS_DIR)\n\n    @property\n    def renewal_pre_hooks_dir(self):\n        \"\"\"Path to the pre-hook directory for the renew subcommand.\"\"\"\n        return os.path.join(self.renewal_hooks_dir,\n                            constants.RENEWAL_PRE_HOOKS_DIR)\n\n    @property\n    def renewal_deploy_hooks_dir(self):\n        \"\"\"Path to the deploy-hook directory for the renew subcommand.\"\"\"\n        return os.path.join(self.renewal_hooks_dir,\n                            constants.RENEWAL_DEPLOY_HOOKS_DIR)\n\n    @property\n    def renewal_post_hooks_dir(self):\n        \"\"\"Path to the post-hook directory for the renew subcommand.\"\"\"\n        return os.path.join(self.renewal_hooks_dir,\n                            constants.RENEWAL_POST_HOOKS_DIR)\n\n\ndef check_config_sanity(config):\n    \"\"\"Validate command line options and display error message if\n    requirements are not met.\n\n    :param config: IConfig instance holding user configuration\n    :type args: :class:`certbot.interfaces.IConfig`\n\n    \"\"\"\n    # Port check\n    if config.http01_port == config.https_port:\n        raise errors.ConfigurationError(\n            \"Trying to run http-01 and https-port \"\n            \"on the same port ({0})\".format(config.https_port))\n\n    # Domain checks\n    if config.namespace.domains is not None:\n        for domain in config.namespace.domains:\n            # This may be redundant, but let's be paranoid\n            util.enforce_domain_sanity(domain)\n"
  },
  {
    "path": "certbot/_internal/constants.py",
    "content": "\"\"\"Certbot constants.\"\"\"\nimport logging\n\nimport pkg_resources\n\nfrom acme import challenges\nfrom certbot.compat import misc\nfrom certbot.compat import os\n\nSETUPTOOLS_PLUGINS_ENTRY_POINT = \"certbot.plugins\"\n\"\"\"Setuptools entry point group name for plugins.\"\"\"\n\nOLD_SETUPTOOLS_PLUGINS_ENTRY_POINT = \"letsencrypt.plugins\"\n\"\"\"Plugins Setuptools entry point before rename.\"\"\"\n\nCLI_DEFAULTS = dict(\n    config_files=[\n        os.path.join(misc.get_default_folder('config'), 'cli.ini'),\n        # http://freedesktop.org/wiki/Software/xdg-user-dirs/\n        os.path.join(os.environ.get(\"XDG_CONFIG_HOME\", \"~/.config\"),\n                     \"letsencrypt\", \"cli.ini\"),\n    ],\n\n    # Main parser\n    verbose_count=-int(logging.INFO / 10),\n    text_mode=False,\n    max_log_backups=1000,\n    noninteractive_mode=False,\n    force_interactive=False,\n    domains=[],\n    certname=None,\n    dry_run=False,\n    register_unsafely_without_email=False,\n    email=None,\n    eff_email=None,\n    reinstall=False,\n    expand=False,\n    renew_by_default=False,\n    renew_with_new_domains=False,\n    autorenew=True,\n    allow_subset_of_names=False,\n    tos=False,\n    account=None,\n    duplicate=False,\n    os_packages_only=False,\n    no_self_upgrade=False,\n    no_permissions_check=False,\n    no_bootstrap=False,\n    quiet=False,\n    staging=False,\n    debug=False,\n    debug_challenges=False,\n    no_verify_ssl=False,\n    http01_port=challenges.HTTP01Response.PORT,\n    http01_address=\"\",\n    https_port=443,\n    break_my_certs=False,\n    rsa_key_size=2048,\n    must_staple=False,\n    redirect=None,\n    auto_hsts=False,\n    hsts=None,\n    uir=None,\n    staple=None,\n    strict_permissions=False,\n    pref_challs=[],\n    validate_hooks=True,\n    directory_hooks=True,\n    reuse_key=False,\n    disable_renew_updates=False,\n    random_sleep_on_renew=True,\n    eab_hmac_key=None,\n    eab_kid=None,\n\n    # Subparsers\n    num=None,\n    user_agent=None,\n    user_agent_comment=None,\n    csr=None,\n    reason=0,\n    delete_after_revoke=None,\n    rollback_checkpoints=1,\n    init=False,\n    prepare=False,\n    ifaces=None,\n\n    # Path parsers\n    auth_cert_path=\"./cert.pem\",\n    auth_chain_path=\"./chain.pem\",\n    key_path=None,\n    config_dir=misc.get_default_folder('config'),\n    work_dir=misc.get_default_folder('work'),\n    logs_dir=misc.get_default_folder('logs'),\n    server=\"https://acme-v02.api.letsencrypt.org/directory\",\n\n    # Plugins parsers\n    configurator=None,\n    authenticator=None,\n    installer=None,\n    apache=False,\n    nginx=False,\n    standalone=False,\n    manual=False,\n    webroot=False,\n    dns_cloudflare=False,\n    dns_cloudxns=False,\n    dns_digitalocean=False,\n    dns_dnsimple=False,\n    dns_dnsmadeeasy=False,\n    dns_gehirn=False,\n    dns_google=False,\n    dns_linode=False,\n    dns_luadns=False,\n    dns_nsone=False,\n    dns_ovh=False,\n    dns_rfc2136=False,\n    dns_route53=False,\n    dns_sakuracloud=False\n\n)\nSTAGING_URI = \"https://acme-staging-v02.api.letsencrypt.org/directory\"\n\n# The set of reasons for revoking a certificate is defined in RFC 5280 in\n# section 5.3.1. The reasons that users are allowed to submit are restricted to\n# those accepted by the ACME server implementation. They are listed in\n# `letsencrypt.boulder.revocation.reasons.go`.\nREVOCATION_REASONS = {\n    \"unspecified\": 0,\n    \"keycompromise\": 1,\n    \"affiliationchanged\": 3,\n    \"superseded\": 4,\n    \"cessationofoperation\": 5}\n\n\"\"\"Defaults for CLI flags and `.IConfig` attributes.\"\"\"\n\nQUIET_LOGGING_LEVEL = logging.WARNING\n\"\"\"Logging level to use in quiet mode.\"\"\"\n\nRENEWER_DEFAULTS = dict(\n    renewer_enabled=\"yes\",\n    renew_before_expiry=\"30 days\",\n    # This value should ensure that there is never a deployment delay by\n    # default.\n    deploy_before_expiry=\"99 years\",\n)\n\"\"\"Defaults for renewer script.\"\"\"\n\nARCHIVE_DIR = \"archive\"\n\"\"\"Archive directory, relative to `IConfig.config_dir`.\"\"\"\n\nCONFIG_DIRS_MODE = 0o755\n\"\"\"Directory mode for ``.IConfig.config_dir`` et al.\"\"\"\n\nACCOUNTS_DIR = \"accounts\"\n\"\"\"Directory where all accounts are saved.\"\"\"\n\nLE_REUSE_SERVERS = {\n    os.path.normpath('acme-v02.api.letsencrypt.org/directory'):\n        os.path.normpath('acme-v01.api.letsencrypt.org/directory'),\n    os.path.normpath('acme-staging-v02.api.letsencrypt.org/directory'):\n        os.path.normpath('acme-staging.api.letsencrypt.org/directory')\n}\n\"\"\"Servers that can reuse accounts from other servers.\"\"\"\n\nBACKUP_DIR = \"backups\"\n\"\"\"Directory (relative to `IConfig.work_dir`) where backups are kept.\"\"\"\n\nCSR_DIR = \"csr\"\n\"\"\"See `.IConfig.csr_dir`.\"\"\"\n\nIN_PROGRESS_DIR = \"IN_PROGRESS\"\n\"\"\"Directory used before a permanent checkpoint is finalized (relative to\n`IConfig.work_dir`).\"\"\"\n\nKEY_DIR = \"keys\"\n\"\"\"Directory (relative to `IConfig.config_dir`) where keys are saved.\"\"\"\n\nLIVE_DIR = \"live\"\n\"\"\"Live directory, relative to `IConfig.config_dir`.\"\"\"\n\nTEMP_CHECKPOINT_DIR = \"temp_checkpoint\"\n\"\"\"Temporary checkpoint directory (relative to `IConfig.work_dir`).\"\"\"\n\nRENEWAL_CONFIGS_DIR = \"renewal\"\n\"\"\"Renewal configs directory, relative to `IConfig.config_dir`.\"\"\"\n\nRENEWAL_HOOKS_DIR = \"renewal-hooks\"\n\"\"\"Basename of directory containing hooks to run with the renew command.\"\"\"\n\nRENEWAL_PRE_HOOKS_DIR = \"pre\"\n\"\"\"Basename of directory containing pre-hooks to run with the renew command.\"\"\"\n\nRENEWAL_DEPLOY_HOOKS_DIR = \"deploy\"\n\"\"\"Basename of directory containing deploy-hooks to run with the renew command.\"\"\"\n\nRENEWAL_POST_HOOKS_DIR = \"post\"\n\"\"\"Basename of directory containing post-hooks to run with the renew command.\"\"\"\n\nFORCE_INTERACTIVE_FLAG = \"--force-interactive\"\n\"\"\"Flag to disable TTY checking in IDisplay.\"\"\"\n\nEFF_SUBSCRIBE_URI = \"https://supporters.eff.org/subscribe/certbot\"\n\"\"\"EFF URI used to submit the e-mail address of users who opt-in.\"\"\"\n\nSSL_DHPARAMS_DEST = \"ssl-dhparams.pem\"\n\"\"\"Name of the ssl_dhparams file as saved in `IConfig.config_dir`.\"\"\"\n\nSSL_DHPARAMS_SRC = pkg_resources.resource_filename(\n    \"certbot\", \"ssl-dhparams.pem\")\n\"\"\"Path to the nginx ssl_dhparams file found in the Certbot distribution.\"\"\"\n\nUPDATED_SSL_DHPARAMS_DIGEST = \".updated-ssl-dhparams-pem-digest.txt\"\n\"\"\"Name of the hash of the updated or informed ssl_dhparams as saved in `IConfig.config_dir`.\"\"\"\n\nALL_SSL_DHPARAMS_HASHES = [\n    '9ba6429597aeed2d8617a7705b56e96d044f64b07971659382e426675105654b',\n]\n\"\"\"SHA256 hashes of the contents of all versions of SSL_DHPARAMS_SRC\"\"\"\n"
  },
  {
    "path": "certbot/_internal/display/__init__.py",
    "content": "\"\"\"Certbot display utilities.\"\"\"\n"
  },
  {
    "path": "certbot/_internal/display/completer.py",
    "content": "\"\"\"Provides Tab completion when prompting users for a path.\"\"\"\nimport glob\n\n# readline module is not available on all systems\ntry:\n    import readline\nexcept ImportError:\n    import certbot._internal.display.dummy_readline as readline  # type: ignore\n\n\nclass Completer(object):\n    \"\"\"Provides Tab completion when prompting users for a path.\n\n    This class is meant to be used with readline to provide Tab\n    completion for users entering paths. The complete method can be\n    passed to readline.set_completer directly, however, this function\n    works best as a context manager. For example:\n\n    with Completer():\n        raw_input()\n\n    In this example, Tab completion will be available during the call to\n    raw_input above, however, readline will be restored to its previous\n    state when exiting the body of the with statement.\n\n    \"\"\"\n\n    def __init__(self):\n        self._iter = self._original_completer = self._original_delims = None\n\n    def complete(self, text, state):\n        \"\"\"Provides path completion for use with readline.\n\n        :param str text: text to offer completions for\n        :param int state: which completion to return\n\n        :returns: possible completion for text or ``None`` if all\n            completions have been returned\n        :rtype: str\n\n        \"\"\"\n        if state == 0:\n            self._iter = glob.iglob(text + '*')\n        return next(self._iter, None)\n\n    def __enter__(self):\n        self._original_completer = readline.get_completer()\n        self._original_delims = readline.get_completer_delims()\n\n        readline.set_completer(self.complete)\n        readline.set_completer_delims(' \\t\\n;')\n\n        # readline can be implemented using GNU readline, pyreadline or libedit\n        # which have different configuration syntax\n        if readline.__doc__ is not None and 'libedit' in readline.__doc__:\n            readline.parse_and_bind('bind ^I rl_complete')\n        else:\n            readline.parse_and_bind('tab: complete')\n\n    def __exit__(self, unused_type, unused_value, unused_traceback):\n        readline.set_completer_delims(self._original_delims)\n        readline.set_completer(self._original_completer)\n"
  },
  {
    "path": "certbot/_internal/display/dummy_readline.py",
    "content": "\"\"\"A dummy module with no effect for use on systems without readline.\"\"\"\n\n\ndef get_completer():\n    \"\"\"An empty implementation of readline.get_completer.\"\"\"\n\n\ndef get_completer_delims():\n    \"\"\"An empty implementation of readline.get_completer_delims.\"\"\"\n\n\ndef parse_and_bind(unused_command):\n    \"\"\"An empty implementation of readline.parse_and_bind.\"\"\"\n\n\ndef set_completer(unused_function=None):\n    \"\"\"An empty implementation of readline.set_completer.\"\"\"\n\n\ndef set_completer_delims(unused_delims):\n    \"\"\"An empty implementation of readline.set_completer_delims.\"\"\"\n"
  },
  {
    "path": "certbot/_internal/display/enhancements.py",
    "content": "\"\"\"Certbot Enhancement Display\"\"\"\nimport logging\n\nimport zope.component\n\nfrom certbot import errors\nfrom certbot import interfaces\nfrom certbot.display import util as display_util\n\nlogger = logging.getLogger(__name__)\n\n# Define a helper function to avoid verbose code\nutil = zope.component.getUtility\n\n\ndef ask(enhancement):\n    \"\"\"Display the enhancement to the user.\n\n    :param str enhancement: One of the\n        :const:`~certbot.plugins.enhancements.ENHANCEMENTS` enhancements\n\n    :returns: True if feature is desired, False otherwise\n    :rtype: bool\n\n    :raises .errors.Error: if the enhancement provided is not supported\n\n    \"\"\"\n    try:\n        # Call the appropriate function based on the enhancement\n        return DISPATCH[enhancement]()\n    except KeyError:\n        logger.error(\"Unsupported enhancement given to ask(): %s\", enhancement)\n        raise errors.Error(\"Unsupported Enhancement\")\n\n\ndef redirect_by_default():\n    \"\"\"Determines whether the user would like to redirect to HTTPS.\n\n    :returns: True if redirect is desired, False otherwise\n    :rtype: bool\n\n    \"\"\"\n    choices = [\n        (\"No redirect\", \"Make no further changes to the webserver configuration.\"),\n        (\"Redirect\", \"Make all requests redirect to secure HTTPS access. \"\n            \"Choose this for new sites, or if you're confident your site works on HTTPS. \"\n            \"You can undo this change by editing your web server's configuration.\"),\n    ]\n\n    code, selection = util(interfaces.IDisplay).menu(\n        \"Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.\",\n        choices, default=1,\n        cli_flag=\"--redirect / --no-redirect\", force_interactive=True)\n\n    if code != display_util.OK:\n        return False\n\n    return selection == 1\n\n\nDISPATCH = {\n    \"redirect\": redirect_by_default\n}\n"
  },
  {
    "path": "certbot/_internal/eff.py",
    "content": "\"\"\"Subscribes users to the EFF newsletter.\"\"\"\nimport logging\n\nimport requests\nimport zope.component\n\nfrom certbot import interfaces\nfrom certbot._internal import constants\n\nlogger = logging.getLogger(__name__)\n\n\ndef handle_subscription(config):\n    \"\"\"High level function to take care of EFF newsletter subscriptions.\n\n    The user may be asked if they want to sign up for the newsletter if\n    they have not already specified.\n\n    :param .IConfig config: Client configuration.\n\n    \"\"\"\n    if config.email is None:\n        if config.eff_email:\n            _report_failure(\"you didn't provide an e-mail address\")\n        return\n    if config.eff_email is None:\n        config.eff_email = _want_subscription()\n    if config.eff_email:\n        subscribe(config.email)\n\n\ndef _want_subscription():\n    \"\"\"Does the user want to be subscribed to the EFF newsletter?\n\n    :returns: True if we should subscribe the user, otherwise, False\n    :rtype: bool\n\n    \"\"\"\n    prompt = (\n        'Would you be willing to share your email address with the '\n        \"Electronic Frontier Foundation, a founding partner of the Let's \"\n        'Encrypt project and the non-profit organization that develops '\n        \"Certbot? We'd like to send you email about our work encrypting \"\n        \"the web, EFF news, campaigns, and ways to support digital freedom. \")\n    display = zope.component.getUtility(interfaces.IDisplay)\n    return display.yesno(prompt, default=False)\n\n\ndef subscribe(email):\n    \"\"\"Subscribe the user to the EFF mailing list.\n\n    :param str email: the e-mail address to subscribe\n\n    \"\"\"\n    url = constants.EFF_SUBSCRIBE_URI\n    data = {'data_type': 'json',\n            'email': email,\n            'form_id': 'eff_supporters_library_subscribe_form'}\n    logger.debug('Sending POST request to %s:\\n%s', url, data)\n    _check_response(requests.post(url, data=data))\n\n\ndef _check_response(response):\n    \"\"\"Check for errors in the server's response.\n\n    If an error occurred, it will be reported to the user.\n\n    :param requests.Response response: the server's response to the\n        subscription request\n\n    \"\"\"\n    logger.debug('Received response:\\n%s', response.content)\n    try:\n        response.raise_for_status()\n        if not response.json()['status']:\n            _report_failure('your e-mail address appears to be invalid')\n    except requests.exceptions.HTTPError:\n        _report_failure()\n    except (ValueError, KeyError):\n        _report_failure('there was a problem with the server response')\n\n\ndef _report_failure(reason=None):\n    \"\"\"Notify the user of failing to sign them up for the newsletter.\n\n    :param reason: a phrase describing what the problem was\n        beginning with a lowercase letter and no closing punctuation\n    :type reason: `str` or `None`\n\n    \"\"\"\n    msg = ['We were unable to subscribe you the EFF mailing list']\n    if reason is not None:\n        msg.append(' because ')\n        msg.append(reason)\n    msg.append('. You can try again later by visiting https://act.eff.org.')\n    reporter = zope.component.getUtility(interfaces.IReporter)\n    reporter.add_message(''.join(msg), reporter.LOW_PRIORITY)\n"
  },
  {
    "path": "certbot/_internal/error_handler.py",
    "content": "\"\"\"Registers functions to be called if an exception or signal occurs.\"\"\"\nimport functools\nimport logging\nimport signal\nimport traceback\n\nfrom acme.magic_typing import Any\nfrom acme.magic_typing import Callable\nfrom acme.magic_typing import Dict\nfrom acme.magic_typing import List\nfrom acme.magic_typing import Union\nfrom certbot import errors\nfrom certbot.compat import os\n\nlogger = logging.getLogger(__name__)\n\n\n# _SIGNALS stores the signals that will be handled by the ErrorHandler. These\n# signals were chosen as their default handler terminates the process and could\n# potentially occur from inside Python. Signals such as SIGILL were not\n# included as they could be a sign of something devious and we should terminate\n# immediately.\nif os.name != \"nt\":\n    _SIGNALS = [signal.SIGTERM]\n    for signal_code in [signal.SIGHUP, signal.SIGQUIT,\n                        signal.SIGXCPU, signal.SIGXFSZ]:\n        # Adding only those signals that their default action is not Ignore.\n        # This is platform-dependent, so we check it dynamically.\n        if signal.getsignal(signal_code) != signal.SIG_IGN:\n            _SIGNALS.append(signal_code)\nelse:\n    # POSIX signals are not implemented natively in Windows, but emulated from the C runtime.\n    # As consumed by CPython, most of handlers on theses signals are useless, in particular\n    # SIGTERM: for instance, os.kill(pid, signal.SIGTERM) will call TerminateProcess, that stops\n    # immediately the process without calling the attached handler. Besides, non-POSIX signals\n    # (CTRL_C_EVENT and CTRL_BREAK_EVENT) are implemented in a console context to handle the\n    # CTRL+C event to a process launched from the console. Only CTRL_C_EVENT has a reliable\n    # behavior in fact, and maps to the handler to SIGINT. However in this case, a\n    # KeyboardInterrupt is raised, that will be handled by ErrorHandler through the context manager\n    # protocol. Finally, no signal on Windows is electable to be handled using ErrorHandler.\n    #\n    # Refs: https://stackoverflow.com/a/35792192, https://maruel.ca/post/python_windows_signal,\n    # https://docs.python.org/2/library/os.html#os.kill,\n    # https://www.reddit.com/r/Python/comments/1dsblt/windows_command_line_automation_ctrlc_question\n    _SIGNALS = []\n\n\nclass ErrorHandler(object):\n    \"\"\"Context manager for running code that must be cleaned up on failure.\n\n    The context manager allows you to register functions that will be called\n    when an exception (excluding SystemExit) or signal is encountered.\n    Usage::\n\n        handler = ErrorHandler(cleanup1_func, *cleanup1_args, **cleanup1_kwargs)\n        handler.register(cleanup2_func, *cleanup2_args, **cleanup2_kwargs)\n\n        with handler:\n            do_something()\n\n    Or for one cleanup function::\n\n        with ErrorHandler(func, args, kwargs):\n            do_something()\n\n    If an exception is raised out of do_something, the cleanup functions will\n    be called in last in first out order. Then the exception is raised.\n    Similarly, if a signal is encountered, the cleanup functions are called\n    followed by the previously received signal handler.\n\n    Each registered cleanup function is called exactly once. If a registered\n    function raises an exception, it is logged and the next function is called.\n    Signals received while the registered functions are executing are\n    deferred until they finish.\n\n    \"\"\"\n    def __init__(self, func, *args, **kwargs):\n        self.call_on_regular_exit = False\n        self.body_executed = False\n        self.funcs = []  # type: List[Callable[[], Any]]\n        self.prev_handlers = {}  # type: Dict[int, Union[int, None, Callable]]\n        self.received_signals = []  # type: List[int]\n        if func is not None:\n            self.register(func, *args, **kwargs)\n\n    def __enter__(self):\n        self.body_executed = False\n        self._set_signal_handlers()\n\n    def __exit__(self, exec_type, exec_value, trace):\n        self.body_executed = True\n        retval = False\n        # SystemExit is ignored to properly handle forks that don't exec\n        if exec_type is SystemExit:\n            return retval\n        if exec_type is None:\n            if not self.call_on_regular_exit:\n                return retval\n        elif exec_type is errors.SignalExit:\n            logger.debug(\"Encountered signals: %s\", self.received_signals)\n            retval = True\n        else:\n            logger.debug(\"Encountered exception:\\n%s\", \"\".join(\n                traceback.format_exception(exec_type, exec_value, trace)))\n\n        self._call_registered()\n        self._reset_signal_handlers()\n        self._call_signals()\n        return retval\n\n    def register(self, func, *args, **kwargs):\n        # type: (Callable, *Any, **Any) -> None\n        \"\"\"Sets func to be run with the given arguments during cleanup.\n\n        :param function func: function to be called in case of an error\n\n        \"\"\"\n        self.funcs.append(functools.partial(func, *args, **kwargs))\n\n    def _call_registered(self):\n        \"\"\"Calls all registered functions\"\"\"\n        logger.debug(\"Calling registered functions\")\n        while self.funcs:\n            try:\n                self.funcs[-1]()\n            except Exception:  # pylint: disable=broad-except\n                logger.error(\"Encountered exception during recovery: \", exc_info=True)\n            self.funcs.pop()\n\n    def _set_signal_handlers(self):\n        \"\"\"Sets signal handlers for signals in _SIGNALS.\"\"\"\n        for signum in _SIGNALS:\n            prev_handler = signal.getsignal(signum)\n            # If prev_handler is None, the handler was set outside of Python\n            if prev_handler is not None:\n                self.prev_handlers[signum] = prev_handler\n                signal.signal(signum, self._signal_handler)\n\n    def _reset_signal_handlers(self):\n        \"\"\"Resets signal handlers for signals in _SIGNALS.\"\"\"\n        for signum in self.prev_handlers:\n            signal.signal(signum, self.prev_handlers[signum])\n        self.prev_handlers.clear()\n\n    def _signal_handler(self, signum, unused_frame):\n        \"\"\"Replacement function for handling received signals.\n\n        Store the received signal. If we are executing the code block in\n        the body of the context manager, stop by raising signal exit.\n\n        :param int signum: number of current signal\n\n        \"\"\"\n        self.received_signals.append(signum)\n        if not self.body_executed:\n            raise errors.SignalExit\n\n    def _call_signals(self):\n        \"\"\"Finally call the deferred signals.\"\"\"\n        for signum in self.received_signals:\n            logger.debug(\"Calling signal %s\", signum)\n            os.kill(os.getpid(), signum)\n\nclass ExitHandler(ErrorHandler):\n    \"\"\"Context manager for running code that must be cleaned up.\n\n    Subclass of ErrorHandler, with the same usage and parameters.\n    In addition to cleaning up on all signals, also cleans up on\n    regular exit.\n    \"\"\"\n    def __init__(self, func, *args, **kwargs):\n        ErrorHandler.__init__(self, func, *args, **kwargs)\n        self.call_on_regular_exit = True\n"
  },
  {
    "path": "certbot/_internal/hooks.py",
    "content": "\"\"\"Facilities for implementing hooks that call shell commands.\"\"\"\nfrom __future__ import print_function\n\nimport logging\nfrom subprocess import PIPE\nfrom subprocess import Popen\n\nfrom acme.magic_typing import List\nfrom acme.magic_typing import Set\nfrom certbot import errors\nfrom certbot import util\nfrom certbot.compat import filesystem\nfrom certbot.compat import os\nfrom certbot.plugins import util as plug_util\n\nlogger = logging.getLogger(__name__)\n\n\ndef validate_hooks(config):\n    \"\"\"Check hook commands are executable.\"\"\"\n    validate_hook(config.pre_hook, \"pre\")\n    validate_hook(config.post_hook, \"post\")\n    validate_hook(config.deploy_hook, \"deploy\")\n    validate_hook(config.renew_hook, \"renew\")\n\n\ndef _prog(shell_cmd):\n    \"\"\"Extract the program run by a shell command.\n\n    :param str shell_cmd: command to be executed\n\n    :returns: basename of command or None if the command isn't found\n    :rtype: str or None\n\n    \"\"\"\n    if not util.exe_exists(shell_cmd):\n        plug_util.path_surgery(shell_cmd)\n        if not util.exe_exists(shell_cmd):\n            return None\n    return os.path.basename(shell_cmd)\n\n\ndef validate_hook(shell_cmd, hook_name):\n    \"\"\"Check that a command provided as a hook is plausibly executable.\n\n    :raises .errors.HookCommandNotFound: if the command is not found\n    \"\"\"\n    if shell_cmd:\n        cmd = shell_cmd.split(None, 1)[0]\n        if not _prog(cmd):\n            path = os.environ[\"PATH\"]\n            if os.path.exists(cmd):\n                msg = \"{1}-hook command {0} exists, but is not executable.\".format(cmd, hook_name)\n            else:\n                msg = \"Unable to find {2}-hook command {0} in the PATH.\\n(PATH is {1})\".format(\n                    cmd, path, hook_name)\n\n            raise errors.HookCommandNotFound(msg)\n\n\ndef pre_hook(config):\n    \"\"\"Run pre-hooks if they exist and haven't already been run.\n\n    When Certbot is running with the renew subcommand, this function\n    runs any hooks found in the config.renewal_pre_hooks_dir (if they\n    have not already been run) followed by any pre-hook in the config.\n    If hooks in config.renewal_pre_hooks_dir are run and the pre-hook in\n    the config is a path to one of these scripts, it is not run twice.\n\n    :param configuration.NamespaceConfig config: Certbot settings\n\n    \"\"\"\n    if config.verb == \"renew\" and config.directory_hooks:\n        for hook in list_hooks(config.renewal_pre_hooks_dir):\n            _run_pre_hook_if_necessary(hook)\n\n    cmd = config.pre_hook\n    if cmd:\n        _run_pre_hook_if_necessary(cmd)\n\n\nexecuted_pre_hooks = set()  # type: Set[str]\n\n\ndef _run_pre_hook_if_necessary(command):\n    \"\"\"Run the specified pre-hook if we haven't already.\n\n    If we've already run this exact command before, a message is logged\n    saying the pre-hook was skipped.\n\n    :param str command: pre-hook to be run\n\n    \"\"\"\n    if command in executed_pre_hooks:\n        logger.info(\"Pre-hook command already run, skipping: %s\", command)\n    else:\n        _run_hook(\"pre-hook\", command)\n        executed_pre_hooks.add(command)\n\n\ndef post_hook(config):\n    \"\"\"Run post-hooks if defined.\n\n    This function also registers any executables found in\n    config.renewal_post_hooks_dir to be run when Certbot is used with\n    the renew subcommand.\n\n    If the verb is renew, we delay executing any post-hooks until\n    :func:`run_saved_post_hooks` is called. In this case, this function\n    registers all hooks found in config.renewal_post_hooks_dir to be\n    called followed by any post-hook in the config. If the post-hook in\n    the config is a path to an executable in the post-hook directory, it\n    is not scheduled to be run twice.\n\n    :param configuration.NamespaceConfig config: Certbot settings\n\n    \"\"\"\n\n    cmd = config.post_hook\n    # In the \"renew\" case, we save these up to run at the end\n    if config.verb == \"renew\":\n        if config.directory_hooks:\n            for hook in list_hooks(config.renewal_post_hooks_dir):\n                _run_eventually(hook)\n        if cmd:\n            _run_eventually(cmd)\n    # certonly / run\n    elif cmd:\n        _run_hook(\"post-hook\", cmd)\n\n\npost_hooks = []  # type: List[str]\n\n\ndef _run_eventually(command):\n    \"\"\"Registers a post-hook to be run eventually.\n\n    All commands given to this function will be run exactly once in the\n    order they were given when :func:`run_saved_post_hooks` is called.\n\n    :param str command: post-hook to register to be run\n\n    \"\"\"\n    if command not in post_hooks:\n        post_hooks.append(command)\n\n\ndef run_saved_post_hooks():\n    \"\"\"Run any post hooks that were saved up in the course of the 'renew' verb\"\"\"\n    for cmd in post_hooks:\n        _run_hook(\"post-hook\", cmd)\n\n\ndef deploy_hook(config, domains, lineage_path):\n    \"\"\"Run post-issuance hook if defined.\n\n    :param configuration.NamespaceConfig config: Certbot settings\n    :param domains: domains in the obtained certificate\n    :type domains: `list` of `str`\n    :param str lineage_path: live directory path for the new cert\n\n    \"\"\"\n    if config.deploy_hook:\n        _run_deploy_hook(config.deploy_hook, domains,\n                         lineage_path, config.dry_run)\n\n\ndef renew_hook(config, domains, lineage_path):\n    \"\"\"Run post-renewal hooks.\n\n    This function runs any hooks found in\n    config.renewal_deploy_hooks_dir followed by any renew-hook in the\n    config. If the renew-hook in the config is a path to a script in\n    config.renewal_deploy_hooks_dir, it is not run twice.\n\n    If Certbot is doing a dry run, no hooks are run and messages are\n    logged saying that they were skipped.\n\n    :param configuration.NamespaceConfig config: Certbot settings\n    :param domains: domains in the obtained certificate\n    :type domains: `list` of `str`\n    :param str lineage_path: live directory path for the new cert\n\n    \"\"\"\n    executed_dir_hooks = set()\n    if config.directory_hooks:\n        for hook in list_hooks(config.renewal_deploy_hooks_dir):\n            _run_deploy_hook(hook, domains, lineage_path, config.dry_run)\n            executed_dir_hooks.add(hook)\n\n    if config.renew_hook:\n        if config.renew_hook in executed_dir_hooks:\n            logger.info(\"Skipping deploy-hook '%s' as it was already run.\",\n                        config.renew_hook)\n        else:\n            _run_deploy_hook(config.renew_hook, domains,\n                             lineage_path, config.dry_run)\n\n\ndef _run_deploy_hook(command, domains, lineage_path, dry_run):\n    \"\"\"Run the specified deploy-hook (if not doing a dry run).\n\n    If dry_run is True, command is not run and a message is logged\n    saying that it was skipped. If dry_run is False, the hook is run\n    after setting the appropriate environment variables.\n\n    :param str command: command to run as a deploy-hook\n    :param domains: domains in the obtained certificate\n    :type domains: `list` of `str`\n    :param str lineage_path: live directory path for the new cert\n    :param bool dry_run: True iff Certbot is doing a dry run\n\n    \"\"\"\n    if dry_run:\n        logger.warning(\"Dry run: skipping deploy hook command: %s\",\n                       command)\n        return\n\n    os.environ[\"RENEWED_DOMAINS\"] = \" \".join(domains)\n    os.environ[\"RENEWED_LINEAGE\"] = lineage_path\n    _run_hook(\"deploy-hook\", command)\n\n\ndef _run_hook(cmd_name, shell_cmd):\n    \"\"\"Run a hook command.\n\n    :param str cmd_name: the user facing name of the hook being run\n    :param shell_cmd: shell command to execute\n    :type shell_cmd: `list` of `str` or `str`\n\n    :returns: stderr if there was any\"\"\"\n    err, _ = execute(cmd_name, shell_cmd)\n    return err\n\n\ndef execute(cmd_name, shell_cmd):\n    \"\"\"Run a command.\n\n    :param str cmd_name: the user facing name of the hook being run\n    :param shell_cmd: shell command to execute\n    :type shell_cmd: `list` of `str` or `str`\n\n    :returns: `tuple` (`str` stderr, `str` stdout)\"\"\"\n    logger.info(\"Running %s command: %s\", cmd_name, shell_cmd)\n\n    # universal_newlines causes Popen.communicate()\n    # to return str objects instead of bytes in Python 3\n    cmd = Popen(shell_cmd, shell=True, stdout=PIPE,\n                stderr=PIPE, universal_newlines=True)\n    out, err = cmd.communicate()\n    base_cmd = os.path.basename(shell_cmd.split(None, 1)[0])\n    if out:\n        logger.info('Output from %s command %s:\\n%s', cmd_name, base_cmd, out)\n    if cmd.returncode != 0:\n        logger.error('%s command \"%s\" returned error code %d',\n                     cmd_name, shell_cmd, cmd.returncode)\n    if err:\n        logger.error('Error output from %s command %s:\\n%s', cmd_name, base_cmd, err)\n    return err, out\n\n\ndef list_hooks(dir_path):\n    \"\"\"List paths to all hooks found in dir_path in sorted order.\n\n    :param str dir_path: directory to search\n\n    :returns: `list` of `str`\n    :rtype: sorted list of absolute paths to executables in dir_path\n\n    \"\"\"\n    allpaths = (os.path.join(dir_path, f) for f in os.listdir(dir_path))\n    hooks = [path for path in allpaths if filesystem.is_executable(path) and not path.endswith('~')]\n    return sorted(hooks)\n"
  },
  {
    "path": "certbot/_internal/lock.py",
    "content": "\"\"\"Implements file locks compatible with Linux and Windows for locking files and directories.\"\"\"\nimport errno\nimport logging\n\nfrom acme.magic_typing import Optional\nfrom certbot import errors\nfrom certbot.compat import filesystem\nfrom certbot.compat import os\n\ntry:\n    import fcntl\nexcept ImportError:\n    import msvcrt\n    POSIX_MODE = False\nelse:\n    POSIX_MODE = True\n\n\n\nlogger = logging.getLogger(__name__)\n\n\ndef lock_dir(dir_path):\n    # type: (str) -> LockFile\n    \"\"\"Place a lock file on the directory at dir_path.\n\n    The lock file is placed in the root of dir_path with the name\n    .certbot.lock.\n\n    :param str dir_path: path to directory\n\n    :returns: the locked LockFile object\n    :rtype: LockFile\n\n    :raises errors.LockError: if unable to acquire the lock\n\n    \"\"\"\n    return LockFile(os.path.join(dir_path, '.certbot.lock'))\n\n\nclass LockFile(object):\n    \"\"\"\n    Platform independent file lock system.\n    LockFile accepts a parameter, the path to a file acting as a lock. Once the LockFile,\n    instance is created, the associated file is 'locked from the point of view of the OS,\n    meaning that if another instance of Certbot try at the same time to acquire the same lock,\n    it will raise an Exception. Calling release method will release the lock, and make it\n    available to every other instance.\n    Upon exit, Certbot will also release all the locks.\n    This allows us to protect a file or directory from being concurrently accessed\n    or modified by two Certbot instances.\n    LockFile is platform independent: it will proceed to the appropriate OS lock mechanism\n    depending on Linux or Windows.\n    \"\"\"\n    def __init__(self, path):\n        # type: (str) -> None\n        \"\"\"\n        Create a LockFile instance on the given file path, and acquire lock.\n        :param str path: the path to the file that will hold a lock\n        \"\"\"\n        self._path = path\n        mechanism = _UnixLockMechanism if POSIX_MODE else _WindowsLockMechanism\n        self._lock_mechanism = mechanism(path)\n\n        self.acquire()\n\n    def __repr__(self):\n        # type: () -> str\n        repr_str = '{0}({1}) <'.format(self.__class__.__name__, self._path)\n        if self.is_locked():\n            repr_str += 'acquired>'\n        else:\n            repr_str += 'released>'\n        return repr_str\n\n    def acquire(self):\n        # type: () -> None\n        \"\"\"\n        Acquire the lock on the file, forbidding any other Certbot instance to acquire it.\n        :raises errors.LockError: if unable to acquire the lock\n        \"\"\"\n        self._lock_mechanism.acquire()\n\n    def release(self):\n        # type: () -> None\n        \"\"\"\n        Release the lock on the file, allowing any other Certbot instance to acquire it.\n        \"\"\"\n        self._lock_mechanism.release()\n\n    def is_locked(self):\n        # type: () -> bool\n        \"\"\"\n        Check if the file is currently locked.\n        :return: True if the file is locked, False otherwise\n        \"\"\"\n        return self._lock_mechanism.is_locked()\n\n\nclass _BaseLockMechanism(object):\n    def __init__(self, path):\n        # type: (str) -> None\n        \"\"\"\n        Create a lock file mechanism for Unix.\n        :param str path: the path to the lock file\n        \"\"\"\n        self._path = path\n        self._fd = None  # type: Optional[int]\n\n    def is_locked(self):\n        # type: () -> bool\n        \"\"\"Check if lock file is currently locked.\n        :return: True if the lock file is locked\n        :rtype: bool\n        \"\"\"\n        return self._fd is not None\n\n    def acquire(self):  # pylint: disable=missing-function-docstring\n        pass  # pragma: no cover\n\n    def release(self):  # pylint: disable=missing-function-docstring\n        pass  # pragma: no cover\n\n\nclass _UnixLockMechanism(_BaseLockMechanism):\n    \"\"\"\n    A UNIX lock file mechanism.\n    This lock file is released when the locked file is closed or the\n    process exits. It cannot be used to provide synchronization between\n    threads. It is based on the lock_file package by Martin Horcicka.\n    \"\"\"\n    def acquire(self):\n        # type: () -> None\n        \"\"\"Acquire the lock.\"\"\"\n        while self._fd is None:\n            # Open the file\n            fd = filesystem.open(self._path, os.O_CREAT | os.O_WRONLY, 0o600)\n            try:\n                self._try_lock(fd)\n                if self._lock_success(fd):\n                    self._fd = fd\n            finally:\n                # Close the file if it is not the required one\n                if self._fd is None:\n                    os.close(fd)\n\n    def _try_lock(self, fd):\n        # type: (int) -> None\n        \"\"\"\n        Try to acquire the lock file without blocking.\n        :param int fd: file descriptor of the opened file to lock\n        \"\"\"\n        try:\n            fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)\n        except IOError as err:\n            if err.errno in (errno.EACCES, errno.EAGAIN):\n                logger.debug('A lock on %s is held by another process.', self._path)\n                raise errors.LockError('Another instance of Certbot is already running.')\n            raise\n\n    def _lock_success(self, fd):\n        # type: (int) -> bool\n        \"\"\"\n        Did we successfully grab the lock?\n        Because this class deletes the locked file when the lock is\n        released, it is possible another process removed and recreated\n        the file between us opening the file and acquiring the lock.\n        :param int fd: file descriptor of the opened file to lock\n        :returns: True if the lock was successfully acquired\n        :rtype: bool\n        \"\"\"\n        # Normally os module should not be imported in certbot codebase except in certbot.compat\n        # for the sake of compatibility over Windows and Linux.\n        # We make an exception here, since _lock_success is private and called only on Linux.\n        from os import stat, fstat  # pylint: disable=os-module-forbidden\n        try:\n            stat1 = stat(self._path)\n        except OSError as err:\n            if err.errno == errno.ENOENT:\n                return False\n            raise\n\n        stat2 = fstat(fd)\n        # If our locked file descriptor and the file on disk refer to\n        # the same device and inode, they're the same file.\n        return stat1.st_dev == stat2.st_dev and stat1.st_ino == stat2.st_ino\n\n    def release(self):\n        # type: () -> None\n        \"\"\"Remove, close, and release the lock file.\"\"\"\n        # It is important the lock file is removed before it's released,\n        # otherwise:\n        #\n        # process A: open lock file\n        # process B: release lock file\n        # process A: lock file\n        # process A: check device and inode\n        # process B: delete file\n        # process C: open and lock a different file at the same path\n        try:\n            os.remove(self._path)\n        finally:\n            # Following check is done to make mypy happy: it ensure that self._fd, marked\n            # as Optional[int] is effectively int to make it compatible with os.close signature.\n            if self._fd is None:  # pragma: no cover\n                raise TypeError('Error, self._fd is None.')\n            try:\n                os.close(self._fd)\n            finally:\n                self._fd = None\n\n\nclass _WindowsLockMechanism(_BaseLockMechanism):\n    \"\"\"\n    A Windows lock file mechanism.\n    By default on Windows, acquiring a file handler gives exclusive access to the process\n    and results in an effective lock. However, it is possible to explicitly acquire the\n    file handler in shared access in terms of read and write, and this is done by os.open\n    and io.open in Python. So an explicit lock needs to be done through the call of\n    msvcrt.locking, that will lock the first byte of the file. In theory, it is also\n    possible to access a file in shared delete access, allowing other processes to delete an\n    opened file. But this needs also to be done explicitly by all processes using the Windows\n    low level APIs, and Python does not do it. As of Python 3.7 and below, Python developers\n    state that deleting a file opened by a process from another process is not possible with\n    os.open and io.open.\n    Consequently, mscvrt.locking is sufficient to obtain an effective lock, and the race\n    condition encountered on Linux is not possible on Windows, leading to a simpler workflow.\n    \"\"\"\n    def acquire(self):\n        \"\"\"Acquire the lock\"\"\"\n        open_mode = os.O_RDWR | os.O_CREAT | os.O_TRUNC\n\n        fd = None\n        try:\n            # Under Windows, filesystem.open will raise directly an EACCES error\n            # if the lock file is already locked.\n            fd = filesystem.open(self._path, open_mode, 0o600)\n            msvcrt.locking(fd, msvcrt.LK_NBLCK, 1)\n        except (IOError, OSError) as err:\n            if fd:\n                os.close(fd)\n            # Anything except EACCES is unexpected. Raise directly the error in that case.\n            if err.errno != errno.EACCES:\n                raise\n            logger.debug('A lock on %s is held by another process.', self._path)\n            raise errors.LockError('Another instance of Certbot is already running.')\n\n        self._fd = fd\n\n    def release(self):\n        \"\"\"Release the lock.\"\"\"\n        try:\n            msvcrt.locking(self._fd, msvcrt.LK_UNLCK, 1)\n            os.close(self._fd)\n\n            try:\n                os.remove(self._path)\n            except OSError as e:\n                # If the lock file cannot be removed, it is not a big deal.\n                # Likely another instance is acquiring the lock we just released.\n                logger.debug(str(e))\n        finally:\n            self._fd = None\n"
  },
  {
    "path": "certbot/_internal/log.py",
    "content": "\"\"\"Logging utilities for Certbot.\n\nThe best way to use this module is through `pre_arg_parse_setup` and\n`post_arg_parse_setup`. `pre_arg_parse_setup` configures a minimal\nterminal logger and ensures a detailed log is written to a secure\ntemporary file if Certbot exits before `post_arg_parse_setup` is called.\n`post_arg_parse_setup` relies on the parsed command line arguments and\ndoes the full logging setup with terminal and rotating file handling as\nconfigured by the user. Any logged messages before\n`post_arg_parse_setup` is called are sent to the rotating file handler.\nSpecial care is taken by both methods to ensure all errors are logged\nand properly flushed before program exit.\n\n\"\"\"\nfrom __future__ import print_function\n\nimport functools\nimport logging\nimport logging.handlers\nimport shutil\nimport sys\nimport tempfile\nimport traceback\n\nfrom acme import messages\nfrom certbot import errors\nfrom certbot import util\nfrom certbot._internal import constants\nfrom certbot.compat import os\n\n# Logging format\nCLI_FMT = \"%(message)s\"\nFILE_FMT = \"%(asctime)s:%(levelname)s:%(name)s:%(message)s\"\n\n\nlogger = logging.getLogger(__name__)\n\n\ndef pre_arg_parse_setup():\n    \"\"\"Setup logging before command line arguments are parsed.\n\n    Terminal logging is setup using\n    `certbot._internal.constants.QUIET_LOGGING_LEVEL` so Certbot is as quiet as\n    possible. File logging is setup so that logging messages are\n    buffered in memory. If Certbot exits before `post_arg_parse_setup`\n    is called, these buffered messages are written to a temporary file.\n    If Certbot doesn't exit, `post_arg_parse_setup` writes the messages\n    to the normal log files.\n\n    This function also sets `logging.shutdown` to be called on program\n    exit which automatically flushes logging handlers and\n    `sys.excepthook` to properly log/display fatal exceptions.\n\n    \"\"\"\n    temp_handler = TempHandler()\n    temp_handler.setFormatter(logging.Formatter(FILE_FMT))\n    temp_handler.setLevel(logging.DEBUG)\n    memory_handler = MemoryHandler(temp_handler)\n\n    stream_handler = ColoredStreamHandler()\n    stream_handler.setFormatter(logging.Formatter(CLI_FMT))\n    stream_handler.setLevel(constants.QUIET_LOGGING_LEVEL)\n\n    root_logger = logging.getLogger()\n    root_logger.setLevel(logging.DEBUG)  # send all records to handlers\n    root_logger.addHandler(memory_handler)\n    root_logger.addHandler(stream_handler)\n\n    # logging.shutdown will flush the memory handler because flush() and\n    # close() are explicitly called\n    util.atexit_register(logging.shutdown)\n    sys.excepthook = functools.partial(\n        pre_arg_parse_except_hook, memory_handler,\n        debug='--debug' in sys.argv, log_path=temp_handler.path)\n\n\ndef post_arg_parse_setup(config):\n    \"\"\"Setup logging after command line arguments are parsed.\n\n    This function assumes `pre_arg_parse_setup` was called earlier and\n    the root logging configuration has not been modified. A rotating\n    file logging handler is created and the buffered log messages are\n    sent to that handler. Terminal logging output is set to the level\n    requested by the user.\n\n    :param certbot.interface.IConfig config: Configuration object\n\n    \"\"\"\n    file_handler, file_path = setup_log_file_handler(\n        config, 'letsencrypt.log', FILE_FMT)\n    logs_dir = os.path.dirname(file_path)\n\n    root_logger = logging.getLogger()\n    memory_handler = stderr_handler = None\n    for handler in root_logger.handlers:\n        if isinstance(handler, ColoredStreamHandler):\n            stderr_handler = handler\n        elif isinstance(handler, MemoryHandler):\n            memory_handler = handler\n    msg = 'Previously configured logging handlers have been removed!'\n    assert memory_handler is not None and stderr_handler is not None, msg\n\n    root_logger.addHandler(file_handler)\n    root_logger.removeHandler(memory_handler)\n    temp_handler = memory_handler.target  # pylint: disable=no-member\n    memory_handler.setTarget(file_handler)  # pylint: disable=no-member\n    memory_handler.flush(force=True)  # pylint: disable=unexpected-keyword-arg\n    memory_handler.close()\n    temp_handler.close()\n\n    if config.quiet:\n        level = constants.QUIET_LOGGING_LEVEL\n    else:\n        level = -config.verbose_count * 10\n    stderr_handler.setLevel(level)\n    logger.debug('Root logging level set at %d', level)\n    logger.info('Saving debug log to %s', file_path)\n\n    sys.excepthook = functools.partial(\n        post_arg_parse_except_hook, debug=config.debug, log_path=logs_dir)\n\n\ndef setup_log_file_handler(config, logfile, fmt):\n    \"\"\"Setup file debug logging.\n\n    :param certbot.interface.IConfig config: Configuration object\n    :param str logfile: basename for the log file\n    :param str fmt: logging format string\n\n    :returns: file handler and absolute path to the log file\n    :rtype: tuple\n\n    \"\"\"\n    # TODO: logs might contain sensitive data such as contents of the\n    # private key! #525\n    util.set_up_core_dir(config.logs_dir, 0o700, config.strict_permissions)\n    log_file_path = os.path.join(config.logs_dir, logfile)\n    try:\n        handler = logging.handlers.RotatingFileHandler(\n            log_file_path, maxBytes=2 ** 20,\n            backupCount=config.max_log_backups)\n    except IOError as error:\n        raise errors.Error(util.PERM_ERR_FMT.format(error))\n    # rotate on each invocation, rollover only possible when maxBytes\n    # is nonzero and backupCount is nonzero, so we set maxBytes as big\n    # as possible not to overrun in single CLI invocation (1MB).\n    handler.doRollover()  # TODO: creates empty letsencrypt.log.1 file\n    handler.setLevel(logging.DEBUG)\n    handler_formatter = logging.Formatter(fmt=fmt)\n    handler.setFormatter(handler_formatter)\n    return handler, log_file_path\n\n\nclass ColoredStreamHandler(logging.StreamHandler):\n    \"\"\"Sends colored logging output to a stream.\n\n    If the specified stream is not a tty, the class works like the\n    standard `logging.StreamHandler`. Default red_level is\n    `logging.WARNING`.\n\n    :ivar bool colored: True if output should be colored\n    :ivar bool red_level: The level at which to output\n\n    \"\"\"\n    def __init__(self, stream=None):\n        super(ColoredStreamHandler, self).__init__(stream)\n        self.colored = (sys.stderr.isatty() if stream is None else\n                        stream.isatty())\n        self.red_level = logging.WARNING\n\n    def format(self, record):\n        \"\"\"Formats the string representation of record.\n\n        :param logging.LogRecord record: Record to be formatted\n\n        :returns: Formatted, string representation of record\n        :rtype: str\n\n        \"\"\"\n        out = super(ColoredStreamHandler, self).format(record)\n        if self.colored and record.levelno >= self.red_level:\n            return ''.join((util.ANSI_SGR_RED, out, util.ANSI_SGR_RESET))\n        return out\n\n\nclass MemoryHandler(logging.handlers.MemoryHandler):\n    \"\"\"Buffers logging messages in memory until the buffer is flushed.\n\n    This differs from `logging.handlers.MemoryHandler` in that flushing\n    only happens when flush(force=True) is called.\n\n    \"\"\"\n    def __init__(self, target=None, capacity=10000):\n        # capacity doesn't matter because should_flush() is overridden\n        super(MemoryHandler, self).__init__(capacity, target=target)\n\n    def close(self):\n        \"\"\"Close the memory handler, but don't set the target to None.\"\"\"\n        # This allows the logging module which may only have a weak\n        # reference to the target handler to properly flush and close it.\n        target = self.target\n        super(MemoryHandler, self).close()\n        self.target = target\n\n    def flush(self, force=False):  # pylint: disable=arguments-differ\n        \"\"\"Flush the buffer if force=True.\n\n        If force=False, this call is a noop.\n\n        :param bool force: True if the buffer should be flushed.\n\n        \"\"\"\n        # This method allows flush() calls in logging.shutdown to be a\n        # noop so we can control when this handler is flushed.\n        if force:\n            super(MemoryHandler, self).flush()\n\n    def shouldFlush(self, record):\n        \"\"\"Should the buffer be automatically flushed?\n\n        :param logging.LogRecord record: log record to be considered\n\n        :returns: False because the buffer should never be auto-flushed\n        :rtype: bool\n\n        \"\"\"\n        return False\n\n\nclass TempHandler(logging.StreamHandler):\n    \"\"\"Safely logs messages to a temporary file.\n\n    The file is created with permissions 600. If no log records are sent\n    to this handler, the temporary file is deleted when the handler is\n    closed.\n\n    :ivar str path: file system path to the temporary log file\n\n    \"\"\"\n    def __init__(self):\n        self._workdir = tempfile.mkdtemp()\n        self.path = os.path.join(self._workdir, 'log')\n        stream = util.safe_open(self.path, mode='w', chmod=0o600)\n        super(TempHandler, self).__init__(stream)\n        self._delete = True\n\n    def emit(self, record):\n        \"\"\"Log the specified logging record.\n\n        :param logging.LogRecord record: Record to be formatted\n\n        \"\"\"\n        self._delete = False\n        super(TempHandler, self).emit(record)\n\n    def close(self):\n        \"\"\"Close the handler and the temporary log file.\n\n        The temporary log file is deleted if it wasn't used.\n\n        \"\"\"\n        self.acquire()\n        try:\n            # StreamHandler.close() doesn't close the stream to allow a\n            # stream like stderr to be used\n            self.stream.close()\n            if self._delete:\n                shutil.rmtree(self._workdir)\n            self._delete = False\n            super(TempHandler, self).close()\n        finally:\n            self.release()\n\n\ndef pre_arg_parse_except_hook(memory_handler, *args, **kwargs):\n    \"\"\"A simple wrapper around post_arg_parse_except_hook.\n\n    The additional functionality provided by this wrapper is the memory\n    handler will be flushed before Certbot exits. This allows us to\n    write logging messages to a temporary file if we crashed before\n    logging was fully configured.\n\n    Since sys.excepthook isn't called on SystemExit exceptions, the\n    memory handler will not be flushed in this case which prevents us\n    from creating temporary log files when argparse exits because a\n    command line argument was invalid or -h, --help, or --version was\n    provided on the command line.\n\n    :param MemoryHandler memory_handler: memory handler to flush\n    :param tuple args: args for post_arg_parse_except_hook\n    :param dict kwargs: kwargs for post_arg_parse_except_hook\n\n    \"\"\"\n    try:\n        post_arg_parse_except_hook(*args, **kwargs)\n    finally:\n        # flush() is called here so messages logged during\n        # post_arg_parse_except_hook are also flushed.\n        memory_handler.flush(force=True)\n\n\ndef post_arg_parse_except_hook(exc_type, exc_value, trace, debug, log_path):\n    \"\"\"Logs fatal exceptions and reports them to the user.\n\n    If debug is True, the full exception and traceback is shown to the\n    user, otherwise, it is suppressed. sys.exit is always called with a\n    nonzero status.\n\n    :param type exc_type: type of the raised exception\n    :param BaseException exc_value: raised exception\n    :param traceback trace: traceback of where the exception was raised\n    :param bool debug: True if the traceback should be shown to the user\n    :param str log_path: path to file or directory containing the log\n\n    \"\"\"\n    exc_info = (exc_type, exc_value, trace)\n    # constants.QUIET_LOGGING_LEVEL or higher should be used to\n    # display message the user, otherwise, a lower level like\n    # logger.DEBUG should be used\n    if debug or not issubclass(exc_type, Exception):\n        assert constants.QUIET_LOGGING_LEVEL <= logging.ERROR\n        logger.error('Exiting abnormally:', exc_info=exc_info)\n    else:\n        logger.debug('Exiting abnormally:', exc_info=exc_info)\n        if issubclass(exc_type, errors.Error):\n            sys.exit(exc_value)\n        logger.error('An unexpected error occurred:')\n        if messages.is_acme_error(exc_value):\n            # Remove the ACME error prefix from the exception\n            _, _, exc_str = str(exc_value).partition(':: ')\n            logger.error(exc_str)\n        else:\n            traceback.print_exception(exc_type, exc_value, None)\n    exit_with_log_path(log_path)\n\n\ndef exit_with_log_path(log_path):\n    \"\"\"Print a message about the log location and exit.\n\n    The message is printed to stderr and the program will exit with a\n    nonzero status.\n\n    :param str log_path: path to file or directory containing the log\n\n    \"\"\"\n    msg = 'Please see the '\n    if os.path.isdir(log_path):\n        msg += 'logfiles in {0} '.format(log_path)\n    else:\n        msg += \"logfile '{0}' \".format(log_path)\n    msg += 'for more details.'\n    sys.exit(msg)\n"
  },
  {
    "path": "certbot/_internal/main.py",
    "content": "\"\"\"Certbot main entry point.\"\"\"\n# pylint: disable=too-many-lines\nfrom __future__ import print_function\n\nimport functools\nimport logging.handlers\nimport sys\n\nimport configobj\nimport josepy as jose\nimport zope.component\n\nfrom acme import errors as acme_errors\nfrom acme.magic_typing import Union\nimport certbot\nfrom certbot import crypto_util\nfrom certbot import errors\nfrom certbot import interfaces\nfrom certbot import util\nfrom certbot._internal import account\nfrom certbot._internal import cert_manager\nfrom certbot._internal import cli\nfrom certbot._internal import client\nfrom certbot._internal import configuration\nfrom certbot._internal import constants\nfrom certbot._internal import eff\nfrom certbot._internal import hooks\nfrom certbot._internal import log\nfrom certbot._internal import renewal\nfrom certbot._internal import reporter\nfrom certbot._internal import storage\nfrom certbot._internal import updater\nfrom certbot._internal.plugins import disco as plugins_disco\nfrom certbot._internal.plugins import selection as plug_sel\nfrom certbot.compat import filesystem\nfrom certbot.compat import misc\nfrom certbot.compat import os\nfrom certbot.display import ops as display_ops\nfrom certbot.display import util as display_util\nfrom certbot.plugins import enhancements\n\nUSER_CANCELLED = (\"User chose to cancel the operation and may \"\n                  \"reinvoke the client.\")\n\n\nlogger = logging.getLogger(__name__)\n\n\ndef _suggest_donation_if_appropriate(config):\n    \"\"\"Potentially suggest a donation to support Certbot.\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :returns: `None`\n    :rtype: None\n\n    \"\"\"\n    assert config.verb != \"renew\"\n    if config.staging:\n        # --dry-run implies --staging\n        return\n    reporter_util = zope.component.getUtility(interfaces.IReporter)\n    msg = (\"If you like Certbot, please consider supporting our work by:\\n\\n\"\n           \"Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate\\n\"\n           \"Donating to EFF:                    https://eff.org/donate-le\\n\\n\")\n    reporter_util.add_message(msg, reporter_util.LOW_PRIORITY)\n\ndef _report_successful_dry_run(config):\n    \"\"\"Reports on successful dry run\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :returns: `None`\n    :rtype: None\n\n    \"\"\"\n    reporter_util = zope.component.getUtility(interfaces.IReporter)\n    assert config.verb != \"renew\"\n    reporter_util.add_message(\"The dry run was successful.\",\n                              reporter_util.HIGH_PRIORITY, on_crash=False)\n\n\ndef _get_and_save_cert(le_client, config, domains=None, certname=None, lineage=None):\n    \"\"\"Authenticate and enroll certificate.\n\n    This method finds the relevant lineage, figures out what to do with it,\n    then performs that action. Includes calls to hooks, various reports,\n    checks, and requests for user input.\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param domains: List of domain names to get a certificate. Defaults to `None`\n    :type domains: `list` of `str`\n\n    :param certname: Name of new certificate. Defaults to `None`\n    :type certname: str\n\n    :param lineage: Certificate lineage object. Defaults to `None`\n    :type lineage: storage.RenewableCert\n\n    :returns: the issued certificate or `None` if doing a dry run\n    :rtype: storage.RenewableCert or None\n\n    :raises errors.Error: if certificate could not be obtained\n\n    \"\"\"\n    hooks.pre_hook(config)\n    try:\n        if lineage is not None:\n            # Renewal, where we already know the specific lineage we're\n            # interested in\n            logger.info(\"Renewing an existing certificate\")\n            renewal.renew_cert(config, domains, le_client, lineage)\n        else:\n            # TREAT AS NEW REQUEST\n            assert domains is not None\n            logger.info(\"Obtaining a new certificate\")\n            lineage = le_client.obtain_and_enroll_certificate(domains, certname)\n            if lineage is False:\n                raise errors.Error(\"Certificate could not be obtained\")\n            if lineage is not None:\n                hooks.deploy_hook(config, lineage.names(), lineage.live_dir)\n    finally:\n        hooks.post_hook(config)\n\n    return lineage\n\n\ndef _handle_subset_cert_request(config, domains, cert):\n    \"\"\"Figure out what to do if a previous cert had a subset of the names now requested\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param domains: List of domain names\n    :type domains: `list` of `str`\n\n    :param cert: Certificate object\n    :type cert: storage.RenewableCert\n\n    :returns: Tuple of (str action, cert_or_None) as per _find_lineage_for_domains_and_certname\n              action can be: \"newcert\" | \"renew\" | \"reinstall\"\n    :rtype: `tuple` of `str`\n\n    \"\"\"\n    existing = \", \".join(cert.names())\n    question = (\n        \"You have an existing certificate that contains a portion of \"\n        \"the domains you requested (ref: {0}){br}{br}It contains these \"\n        \"names: {1}{br}{br}You requested these names for the new \"\n        \"certificate: {2}.{br}{br}Do you want to expand and replace this existing \"\n        \"certificate with the new certificate?\"\n    ).format(cert.configfile.filename,\n             existing,\n             \", \".join(domains),\n             br=os.linesep)\n    if config.expand or config.renew_by_default or zope.component.getUtility(\n            interfaces.IDisplay).yesno(question, \"Expand\", \"Cancel\",\n                                       cli_flag=\"--expand\",\n                                       force_interactive=True):\n        return \"renew\", cert\n    reporter_util = zope.component.getUtility(interfaces.IReporter)\n    reporter_util.add_message(\n        \"To obtain a new certificate that contains these names without \"\n        \"replacing your existing certificate for {0}, you must use the \"\n        \"--duplicate option.{br}{br}\"\n        \"For example:{br}{br}{1} --duplicate {2}\".format(\n            existing,\n            sys.argv[0], \" \".join(sys.argv[1:]),\n            br=os.linesep\n        ),\n        reporter_util.HIGH_PRIORITY)\n    raise errors.Error(USER_CANCELLED)\n\n\ndef _handle_identical_cert_request(config, lineage):\n    \"\"\"Figure out what to do if a lineage has the same names as a previously obtained one\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param lineage: Certificate lineage object\n    :type lineage: storage.RenewableCert\n\n    :returns: Tuple of (str action, cert_or_None) as per _find_lineage_for_domains_and_certname\n              action can be: \"newcert\" | \"renew\" | \"reinstall\"\n    :rtype: `tuple` of `str`\n\n    \"\"\"\n    if not lineage.ensure_deployed():\n        return \"reinstall\", lineage\n    if renewal.should_renew(config, lineage):\n        return \"renew\", lineage\n    if config.reinstall:\n        # Set with --reinstall, force an identical certificate to be\n        # reinstalled without further prompting.\n        return \"reinstall\", lineage\n    question = (\n        \"You have an existing certificate that has exactly the same \"\n        \"domains or certificate name you requested and isn't close to expiry.\"\n        \"{br}(ref: {0}){br}{br}What would you like to do?\"\n    ).format(lineage.configfile.filename, br=os.linesep)\n\n    if config.verb == \"run\":\n        keep_opt = \"Attempt to reinstall this existing certificate\"\n    elif config.verb == \"certonly\":\n        keep_opt = \"Keep the existing certificate for now\"\n    choices = [keep_opt,\n               \"Renew & replace the cert (limit ~5 per 7 days)\"]\n\n    display = zope.component.getUtility(interfaces.IDisplay)\n    response = display.menu(question, choices,\n                            default=0, force_interactive=True)\n    if response[0] == display_util.CANCEL:\n        # TODO: Add notification related to command-line options for\n        #       skipping the menu for this case.\n        raise errors.Error(\n            \"Operation canceled. You may re-run the client.\")\n    if response[1] == 0:\n        return \"reinstall\", lineage\n    elif response[1] == 1:\n        return \"renew\", lineage\n    raise AssertionError('This is impossible')\n\n\ndef _find_lineage_for_domains(config, domains):\n    \"\"\"Determine whether there are duplicated names and how to handle\n    them (renew, reinstall, newcert, or raising an error to stop\n    the client run if the user chooses to cancel the operation when\n    prompted).\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param domains: List of domain names\n    :type domains: `list` of `str`\n\n    :returns: Two-element tuple containing desired new-certificate behavior as\n              a string token (\"reinstall\", \"renew\", or \"newcert\"), plus either\n              a RenewableCert instance or `None` if renewal shouldn't occur.\n    :rtype: `tuple` of `str` and :class:`storage.RenewableCert` or `None`\n\n    :raises errors.Error: If the user would like to rerun the client again.\n\n    \"\"\"\n    # Considering the possibility that the requested certificate is\n    # related to an existing certificate.  (config.duplicate, which\n    # is set with --duplicate, skips all of this logic and forces any\n    # kind of certificate to be obtained with renewal = False.)\n    if config.duplicate:\n        return \"newcert\", None\n    # TODO: Also address superset case\n    ident_names_cert, subset_names_cert = cert_manager.find_duplicative_certs(config, domains)\n    # XXX ^ schoen is not sure whether that correctly reads the systemwide\n    # configuration file.\n    if ident_names_cert is None and subset_names_cert is None:\n        return \"newcert\", None\n\n    if ident_names_cert is not None:\n        return _handle_identical_cert_request(config, ident_names_cert)\n    elif subset_names_cert is not None:\n        return _handle_subset_cert_request(config, domains, subset_names_cert)\n    return None, None\n\ndef _find_cert(config, domains, certname):\n    \"\"\"Finds an existing certificate object given domains and/or a certificate name.\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param domains: List of domain names\n    :type domains: `list` of `str`\n\n    :param certname: Name of certificate\n    :type certname: str\n\n    :returns: Two-element tuple of a boolean that indicates if this function should be\n              followed by a call to fetch a certificate from the server, and either a\n              RenewableCert instance or None.\n    :rtype: `tuple` of `bool` and :class:`storage.RenewableCert` or `None`\n\n    \"\"\"\n    action, lineage = _find_lineage_for_domains_and_certname(config, domains, certname)\n    if action == \"reinstall\":\n        logger.info(\"Keeping the existing certificate\")\n    return (action != \"reinstall\"), lineage\n\ndef _find_lineage_for_domains_and_certname(config, domains, certname):\n    \"\"\"Find appropriate lineage based on given domains and/or certname.\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param domains: List of domain names\n    :type domains: `list` of `str`\n\n    :param certname: Name of certificate\n    :type certname: str\n\n    :returns: Two-element tuple containing desired new-certificate behavior as\n              a string token (\"reinstall\", \"renew\", or \"newcert\"), plus either\n              a RenewableCert instance or None if renewal should not occur.\n\n    :rtype: `tuple` of `str` and :class:`storage.RenewableCert` or `None`\n\n    :raises errors.Error: If the user would like to rerun the client again.\n\n    \"\"\"\n    if not certname:\n        return _find_lineage_for_domains(config, domains)\n    lineage = cert_manager.lineage_for_certname(config, certname)\n    if lineage:\n        if domains:\n            if set(cert_manager.domains_for_certname(config, certname)) != set(domains):\n                _ask_user_to_confirm_new_names(config, domains, certname,\n                    lineage.names()) # raises if no\n                return \"renew\", lineage\n        # unnecessarily specified domains or no domains specified\n        return _handle_identical_cert_request(config, lineage)\n    elif domains:\n        return \"newcert\", None\n    raise errors.ConfigurationError(\"No certificate with name {0} found. \"\n        \"Use -d to specify domains, or run certbot certificates to see \"\n        \"possible certificate names.\".format(certname))\n\ndef _get_added_removed(after, before):\n    \"\"\"Get lists of items removed from `before`\n    and a lists of items added to `after`\n    \"\"\"\n    added = list(set(after) - set(before))\n    removed = list(set(before) - set(after))\n    added.sort()\n    removed.sort()\n    return added, removed\n\ndef _format_list(character, strings):\n    \"\"\"Format list with given character\n    \"\"\"\n    if not strings:\n        formatted = \"{br}(None)\"\n    else:\n        formatted = \"{br}{ch} \" + \"{br}{ch} \".join(strings)\n    return formatted.format(\n        ch=character,\n        br=os.linesep\n    )\n\ndef _ask_user_to_confirm_new_names(config, new_domains, certname, old_domains):\n    \"\"\"Ask user to confirm update cert certname to contain new_domains.\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param new_domains: List of new domain names\n    :type new_domains: `list` of `str`\n\n    :param certname: Name of certificate\n    :type certname: str\n\n    :param old_domains: List of old domain names\n    :type old_domains: `list` of `str`\n\n    :returns: None\n    :rtype: None\n\n    :raises errors.ConfigurationError: if cert name and domains mismatch\n\n    \"\"\"\n    if config.renew_with_new_domains:\n        return\n\n    added, removed = _get_added_removed(new_domains, old_domains)\n\n    msg = (\"You are updating certificate {0} to include new domain(s): {1}{br}{br}\"\n           \"You are also removing previously included domain(s): {2}{br}{br}\"\n           \"Did you intend to make this change?\".format(\n               certname,\n               _format_list(\"+\", added),\n               _format_list(\"-\", removed),\n               br=os.linesep))\n    obj = zope.component.getUtility(interfaces.IDisplay)\n    if not obj.yesno(msg, \"Update cert\", \"Cancel\", default=True):\n        raise errors.ConfigurationError(\"Specified mismatched cert name and domains.\")\n\ndef _find_domains_or_certname(config, installer, question=None):\n    \"\"\"Retrieve domains and certname from config or user input.\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param installer: Installer object\n    :type installer: interfaces.IInstaller\n\n    :param `str` question: Overriding default question to ask the user if asked\n        to choose from domain names.\n\n    :returns: Two-part tuple of domains and certname\n    :rtype: `tuple` of list of `str` and `str`\n\n    :raises errors.Error: Usage message, if parameters are not used correctly\n\n    \"\"\"\n    domains = None\n    certname = config.certname\n    # first, try to get domains from the config\n    if config.domains:\n        domains = config.domains\n    # if we can't do that but we have a certname, get the domains\n    # with that certname\n    elif certname:\n        domains = cert_manager.domains_for_certname(config, certname)\n\n    # that certname might not have existed, or there was a problem.\n    # try to get domains from the user.\n    if not domains:\n        domains = display_ops.choose_names(installer, question)\n\n    if not domains and not certname:\n        raise errors.Error(\"Please specify --domains, or --installer that \"\n                           \"will help in domain names autodiscovery, or \"\n                           \"--cert-name for an existing certificate name.\")\n\n    return domains, certname\n\n\ndef _report_new_cert(config, cert_path, fullchain_path, key_path=None):\n    \"\"\"Reports the creation of a new certificate to the user.\n\n    :param cert_path: path to certificate\n    :type cert_path: str\n\n    :param fullchain_path: path to full chain\n    :type fullchain_path: str\n\n    :param key_path: path to private key, if available\n    :type key_path: str\n\n    :returns: `None`\n    :rtype: None\n\n    \"\"\"\n    if config.dry_run:\n        _report_successful_dry_run(config)\n        return\n\n    assert cert_path and fullchain_path, \"No certificates saved to report.\"\n\n    expiry = crypto_util.notAfter(cert_path).date()\n    reporter_util = zope.component.getUtility(interfaces.IReporter)\n    # Print the path to fullchain.pem because that's what modern webservers\n    # (Nginx and Apache2.4) will want.\n\n    verbswitch = ' with the \"certonly\" option' if config.verb == \"run\" else \"\"\n    privkey_statement = 'Your key file has been saved at:{br}{0}{br}'.format(\n            key_path, br=os.linesep) if key_path else \"\"\n    # XXX Perhaps one day we could detect the presence of known old webservers\n    # and say something more informative here.\n    msg = ('Congratulations! Your certificate and chain have been saved at:{br}'\n           '{0}{br}{1}'\n           'Your cert will expire on {2}. To obtain a new or tweaked version of this '\n           'certificate in the future, simply run {3} again{4}. '\n           'To non-interactively renew *all* of your certificates, run \"{3} renew\"'\n           .format(fullchain_path, privkey_statement, expiry, cli.cli_command, verbswitch,\n               br=os.linesep))\n    reporter_util.add_message(msg, reporter_util.MEDIUM_PRIORITY)\n\n\ndef _determine_account(config):\n    \"\"\"Determine which account to use.\n\n    If ``config.account`` is ``None``, it will be updated based on the\n    user input. Same for ``config.email``.\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :returns: Account and optionally ACME client API (biproduct of new\n        registration).\n    :rtype: tuple of :class:`certbot._internal.account.Account` and :class:`acme.client.Client`\n\n    :raises errors.Error: If unable to register an account with ACME server\n\n    \"\"\"\n    def _tos_cb(terms_of_service):\n        if config.tos:\n            return True\n        msg = (\"Please read the Terms of Service at {0}. You \"\n               \"must agree in order to register with the ACME \"\n               \"server at {1}\".format(\n                   terms_of_service, config.server))\n        obj = zope.component.getUtility(interfaces.IDisplay)\n        result = obj.yesno(msg, \"Agree\", \"Cancel\",\n                         cli_flag=\"--agree-tos\", force_interactive=True)\n        if not result:\n            raise errors.Error(\n                \"Registration cannot proceed without accepting \"\n                \"Terms of Service.\")\n        return None\n\n    account_storage = account.AccountFileStorage(config)\n    acme = None\n\n    if config.account is not None:\n        acc = account_storage.load(config.account)\n    else:\n        accounts = account_storage.find_all()\n        if len(accounts) > 1:\n            acc = display_ops.choose_account(accounts)\n        elif len(accounts) == 1:\n            acc = accounts[0]\n        else:  # no account registered yet\n            if config.email is None and not config.register_unsafely_without_email:\n                config.email = display_ops.get_email()\n            try:\n                acc, acme = client.register(\n                    config, account_storage, tos_cb=_tos_cb)\n            except errors.MissingCommandlineFlag:\n                raise\n            except errors.Error:\n                logger.debug(\"\", exc_info=True)\n                raise errors.Error(\n                    \"Unable to register an account with ACME server\")\n\n    config.account = acc.id\n    return acc, acme\n\n\ndef _delete_if_appropriate(config):\n    \"\"\"Does the user want to delete their now-revoked certs? If run in non-interactive mode,\n    deleting happens automatically.\n\n    :param config: parsed command line arguments\n    :type config: interfaces.IConfig\n\n    :returns: `None`\n    :rtype: None\n\n    :raises errors.Error: If anything goes wrong, including bad user input, if an overlapping\n        archive dir is found for the specified lineage, etc ...\n    \"\"\"\n    display = zope.component.getUtility(interfaces.IDisplay)\n    reporter_util = zope.component.getUtility(interfaces.IReporter)\n\n    attempt_deletion = config.delete_after_revoke\n    if attempt_deletion is None:\n        msg = (\"Would you like to delete the cert(s) you just revoked, along with all earlier and \"\n            \"later versions of the cert?\")\n        attempt_deletion = display.yesno(msg, yes_label=\"Yes (recommended)\", no_label=\"No\",\n                force_interactive=True, default=True)\n\n    if not attempt_deletion:\n        reporter_util.add_message(\"Not deleting revoked certs.\", reporter_util.LOW_PRIORITY)\n        return\n\n    # config.cert_path must have been set\n    # config.certname may have been set\n    assert config.cert_path\n\n    if not config.certname:\n        config.certname = cert_manager.cert_path_to_lineage(config)\n\n    # don't delete if the archive_dir is used by some other lineage\n    archive_dir = storage.full_archive_path(\n            configobj.ConfigObj(storage.renewal_file_for_certname(config, config.certname)),\n            config, config.certname)\n    try:\n        cert_manager.match_and_check_overlaps(config, [lambda x: archive_dir],\n            lambda x: x.archive_dir, lambda x: x)\n    except errors.OverlappingMatchFound:\n        msg = ('Not deleting revoked certs due to overlapping archive dirs. More than '\n                'one lineage is using {0}'.format(archive_dir))\n        reporter_util.add_message(''.join(msg), reporter_util.MEDIUM_PRIORITY)\n        return\n    except Exception as e:\n        msg = ('config.default_archive_dir: {0}, config.live_dir: {1}, archive_dir: {2},'\n        'original exception: {3}')\n        msg = msg.format(config.default_archive_dir, config.live_dir, archive_dir, e)\n        raise errors.Error(msg)\n\n    cert_manager.delete(config)\n\n\ndef _init_le_client(config, authenticator, installer):\n    \"\"\"Initialize Let's Encrypt Client\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param authenticator: Acme authentication handler\n    :type authenticator: interfaces.IAuthenticator\n    :param installer: Installer object\n    :type installer: interfaces.IInstaller\n\n    :returns: client: Client object\n    :rtype: client.Client\n\n    \"\"\"\n    if authenticator is not None:\n        # if authenticator was given, then we will need account...\n        acc, acme = _determine_account(config)\n        logger.debug(\"Picked account: %r\", acc)\n        # XXX\n        #crypto_util.validate_key_csr(acc.key)\n    else:\n        acc, acme = None, None\n\n    return client.Client(config, acc, authenticator, installer, acme=acme)\n\n\ndef unregister(config, unused_plugins):\n    \"\"\"Deactivate account on server\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param unused_plugins: List of plugins (deprecated)\n    :type unused_plugins: `list` of `str`\n\n    :returns: `None`\n    :rtype: None\n\n    \"\"\"\n    account_storage = account.AccountFileStorage(config)\n    accounts = account_storage.find_all()\n    reporter_util = zope.component.getUtility(interfaces.IReporter)\n\n    if not accounts:\n        return \"Could not find existing account to deactivate.\"\n    yesno = zope.component.getUtility(interfaces.IDisplay).yesno\n    prompt = (\"Are you sure you would like to irrevocably deactivate \"\n              \"your account?\")\n    wants_deactivate = yesno(prompt, yes_label='Deactivate', no_label='Abort',\n                             default=True)\n\n    if not wants_deactivate:\n        return \"Deactivation aborted.\"\n\n    acc, acme = _determine_account(config)\n    cb_client = client.Client(config, acc, None, None, acme=acme)\n\n    # delete on boulder\n    cb_client.acme.deactivate_registration(acc.regr)\n    account_files = account.AccountFileStorage(config)\n    # delete local account files\n    account_files.delete(config.account)\n\n    reporter_util.add_message(\"Account deactivated.\", reporter_util.MEDIUM_PRIORITY)\n    return None\n\n\ndef register(config, unused_plugins):\n    \"\"\"Create accounts on the server.\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param unused_plugins: List of plugins (deprecated)\n    :type unused_plugins: `list` of `str`\n\n    :returns: `None` or a string indicating and error\n    :rtype: None or str\n\n    \"\"\"\n    # Portion of _determine_account logic to see whether accounts already\n    # exist or not.\n    account_storage = account.AccountFileStorage(config)\n    accounts = account_storage.find_all()\n\n    if accounts:\n        # TODO: add a flag to register a duplicate account (this will\n        #       also require extending _determine_account's behavior\n        #       or else extracting the registration code from there)\n        return (\"There is an existing account; registration of a \"\n                \"duplicate account with this command is currently \"\n                \"unsupported.\")\n    # _determine_account will register an account\n    _determine_account(config)\n    return None\n\n\ndef update_account(config, unused_plugins):\n    \"\"\"Modify accounts on the server.\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param unused_plugins: List of plugins (deprecated)\n    :type unused_plugins: `list` of `str`\n\n    :returns: `None` or a string indicating and error\n    :rtype: None or str\n\n    \"\"\"\n    # Portion of _determine_account logic to see whether accounts already\n    # exist or not.\n    account_storage = account.AccountFileStorage(config)\n    accounts = account_storage.find_all()\n    reporter_util = zope.component.getUtility(interfaces.IReporter)\n    add_msg = lambda m: reporter_util.add_message(m, reporter_util.MEDIUM_PRIORITY)\n\n    if not accounts:\n        return \"Could not find an existing account to update.\"\n    if config.email is None:\n        if config.register_unsafely_without_email:\n            return (\"--register-unsafely-without-email provided, however, a \"\n                    \"new e-mail address must\\ncurrently be provided when \"\n                    \"updating a registration.\")\n        config.email = display_ops.get_email(optional=False)\n\n    acc, acme = _determine_account(config)\n    cb_client = client.Client(config, acc, None, None, acme=acme)\n    # We rely on an exception to interrupt this process if it didn't work.\n    acc_contacts = ['mailto:' + email for email in config.email.split(',')]\n    prev_regr_uri = acc.regr.uri\n    acc.regr = cb_client.acme.update_registration(acc.regr.update(\n        body=acc.regr.body.update(contact=acc_contacts)))\n    # A v1 account being used as a v2 account will result in changing the uri to\n    # the v2 uri. Since it's the same object on disk, put it back to the v1 uri\n    # so that we can also continue to use the account object with acmev1.\n    acc.regr = acc.regr.update(uri=prev_regr_uri)\n    account_storage.save_regr(acc, cb_client.acme)\n    eff.handle_subscription(config)\n    add_msg(\"Your e-mail address was updated to {0}.\".format(config.email))\n    return None\n\ndef _install_cert(config, le_client, domains, lineage=None):\n    \"\"\"Install a cert\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param le_client: Client object\n    :type le_client: client.Client\n\n    :param domains: List of domains\n    :type domains: `list` of `str`\n\n    :param lineage: Certificate lineage object. Defaults to `None`\n    :type lineage: storage.RenewableCert\n\n    :returns: `None`\n    :rtype: None\n\n    \"\"\"\n    path_provider = lineage if lineage else config\n    assert path_provider.cert_path is not None\n\n    le_client.deploy_certificate(domains, path_provider.key_path,\n        path_provider.cert_path, path_provider.chain_path, path_provider.fullchain_path)\n    le_client.enhance_config(domains, path_provider.chain_path)\n\n\ndef install(config, plugins):\n    \"\"\"Install a previously obtained cert in a server.\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param plugins: List of plugins\n    :type plugins: `list` of `str`\n\n    :returns: `None`\n    :rtype: None\n\n    \"\"\"\n    # XXX: Update for renewer/RenewableCert\n    # FIXME: be consistent about whether errors are raised or returned from\n    # this function ...\n\n    try:\n        installer, _ = plug_sel.choose_configurator_plugins(config, plugins, \"install\")\n    except errors.PluginSelectionError as e:\n        return str(e)\n\n    custom_cert = (config.key_path and config.cert_path)\n    if not config.certname and not custom_cert:\n        certname_question = \"Which certificate would you like to install?\"\n        config.certname = cert_manager.get_certnames(\n            config, \"install\", allow_multiple=False,\n            custom_prompt=certname_question)[0]\n\n    if not enhancements.are_supported(config, installer):\n        raise errors.NotSupportedError(\"One ore more of the requested enhancements \"\n                                       \"are not supported by the selected installer\")\n    # If cert-path is defined, populate missing (ie. not overridden) values.\n    # Unfortunately this can't be done in argument parser, as certificate\n    # manager needs the access to renewal directory paths\n    if config.certname:\n        config = _populate_from_certname(config)\n    elif enhancements.are_requested(config):\n        # Preflight config check\n        raise errors.ConfigurationError(\"One or more of the requested enhancements \"\n                                        \"require --cert-name to be provided\")\n\n    if config.key_path and config.cert_path:\n        _check_certificate_and_key(config)\n        domains, _ = _find_domains_or_certname(config, installer)\n        le_client = _init_le_client(config, authenticator=None, installer=installer)\n        _install_cert(config, le_client, domains)\n    else:\n        raise errors.ConfigurationError(\"Path to certificate or key was not defined. \"\n            \"If your certificate is managed by Certbot, please use --cert-name \"\n            \"to define which certificate you would like to install.\")\n\n    if enhancements.are_requested(config):\n        # In the case where we don't have certname, we have errored out already\n        lineage = cert_manager.lineage_for_certname(config, config.certname)\n        enhancements.enable(lineage, domains, installer, config)\n\n    return None\n\ndef _populate_from_certname(config):\n    \"\"\"Helper function for install to populate missing config values from lineage\n    defined by --cert-name.\"\"\"\n\n    lineage = cert_manager.lineage_for_certname(config, config.certname)\n    if not lineage:\n        return config\n    if not config.key_path:\n        config.namespace.key_path = lineage.key_path\n    if not config.cert_path:\n        config.namespace.cert_path = lineage.cert_path\n    if not config.chain_path:\n        config.namespace.chain_path = lineage.chain_path\n    if not config.fullchain_path:\n        config.namespace.fullchain_path = lineage.fullchain_path\n    return config\n\ndef _check_certificate_and_key(config):\n    if not os.path.isfile(filesystem.realpath(config.cert_path)):\n        raise errors.ConfigurationError(\"Error while reading certificate from path \"\n                                        \"{0}\".format(config.cert_path))\n    if not os.path.isfile(filesystem.realpath(config.key_path)):\n        raise errors.ConfigurationError(\"Error while reading private key from path \"\n                                        \"{0}\".format(config.key_path))\ndef plugins_cmd(config, plugins):\n    \"\"\"List server software plugins.\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param plugins: List of plugins\n    :type plugins: `list` of `str`\n\n    :returns: `None`\n    :rtype: None\n\n    \"\"\"\n    logger.debug(\"Expected interfaces: %s\", config.ifaces)\n\n    ifaces = [] if config.ifaces is None else config.ifaces\n    filtered = plugins.visible().ifaces(ifaces)\n    logger.debug(\"Filtered plugins: %r\", filtered)\n\n    notify = functools.partial(zope.component.getUtility(\n        interfaces.IDisplay).notification, pause=False)\n    if not config.init and not config.prepare:\n        notify(str(filtered))\n        return\n\n    filtered.init(config)\n    verified = filtered.verify(ifaces)\n    logger.debug(\"Verified plugins: %r\", verified)\n\n    if not config.prepare:\n        notify(str(verified))\n        return\n\n    verified.prepare()\n    available = verified.available()\n    logger.debug(\"Prepared plugins: %s\", available)\n    notify(str(available))\n\n\ndef enhance(config, plugins):\n    \"\"\"Add security enhancements to existing configuration\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param plugins: List of plugins\n    :type plugins: `list` of `str`\n\n    :returns: `None`\n    :rtype: None\n\n    \"\"\"\n    supported_enhancements = [\"hsts\", \"redirect\", \"uir\", \"staple\"]\n    # Check that at least one enhancement was requested on command line\n    oldstyle_enh = any([getattr(config, enh) for enh in supported_enhancements])\n    if not enhancements.are_requested(config) and not oldstyle_enh:\n        msg = (\"Please specify one or more enhancement types to configure. To list \"\n               \"the available enhancement types, run:\\n\\n%s --help enhance\\n\")\n        logger.warning(msg, sys.argv[0])\n        raise errors.MisconfigurationError(\"No enhancements requested, exiting.\")\n\n    try:\n        installer, _ = plug_sel.choose_configurator_plugins(config, plugins, \"enhance\")\n    except errors.PluginSelectionError as e:\n        return str(e)\n\n    if not enhancements.are_supported(config, installer):\n        raise errors.NotSupportedError(\"One ore more of the requested enhancements \"\n                                       \"are not supported by the selected installer\")\n\n    certname_question = (\"Which certificate would you like to use to enhance \"\n                         \"your configuration?\")\n    config.certname = cert_manager.get_certnames(\n        config, \"enhance\", allow_multiple=False,\n        custom_prompt=certname_question)[0]\n    cert_domains = cert_manager.domains_for_certname(config, config.certname)\n    if config.noninteractive_mode:\n        domains = cert_domains\n    else:\n        domain_question = (\"Which domain names would you like to enable the \"\n                           \"selected enhancements for?\")\n        domains = display_ops.choose_values(cert_domains, domain_question)\n        if not domains:\n            raise errors.Error(\"User cancelled the domain selection. No domains \"\n                               \"defined, exiting.\")\n\n    lineage = cert_manager.lineage_for_certname(config, config.certname)\n    if not config.chain_path:\n        config.chain_path = lineage.chain_path\n    if oldstyle_enh:\n        le_client = _init_le_client(config, authenticator=None, installer=installer)\n        le_client.enhance_config(domains, config.chain_path, ask_redirect=False)\n    if enhancements.are_requested(config):\n        enhancements.enable(lineage, domains, installer, config)\n\n    return None\n\n\ndef rollback(config, plugins):\n    \"\"\"Rollback server configuration changes made during install.\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param plugins: List of plugins\n    :type plugins: `list` of `str`\n\n    :returns: `None`\n    :rtype: None\n\n    \"\"\"\n    client.rollback(config.installer, config.checkpoints, config, plugins)\n\ndef update_symlinks(config, unused_plugins):\n    \"\"\"Update the certificate file family symlinks\n\n    Use the information in the config file to make symlinks point to\n    the correct archive directory.\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param unused_plugins: List of plugins (deprecated)\n    :type unused_plugins: `list` of `str`\n\n    :returns: `None`\n    :rtype: None\n\n    \"\"\"\n    cert_manager.update_live_symlinks(config)\n\ndef rename(config, unused_plugins):\n    \"\"\"Rename a certificate\n\n    Use the information in the config file to rename an existing\n    lineage.\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param unused_plugins: List of plugins (deprecated)\n    :type unused_plugins: `list` of `str`\n\n    :returns: `None`\n    :rtype: None\n\n    \"\"\"\n    cert_manager.rename_lineage(config)\n\ndef delete(config, unused_plugins):\n    \"\"\"Delete a certificate\n\n    Use the information in the config file to delete an existing\n    lineage.\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param unused_plugins: List of plugins (deprecated)\n    :type unused_plugins: `list` of `str`\n\n    :returns: `None`\n    :rtype: None\n\n    \"\"\"\n    cert_manager.delete(config)\n\ndef certificates(config, unused_plugins):\n    \"\"\"Display information about certs configured with Certbot\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param unused_plugins: List of plugins (deprecated)\n    :type unused_plugins: `list` of `str`\n\n    :returns: `None`\n    :rtype: None\n\n    \"\"\"\n    cert_manager.certificates(config)\n\n# TODO: coop with renewal config\ndef revoke(config, unused_plugins):\n    \"\"\"Revoke a previously obtained certificate.\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param unused_plugins: List of plugins (deprecated)\n    :type unused_plugins: `list` of `str`\n\n    :returns: `None` or string indicating error in case of error\n    :rtype: None or str\n\n    \"\"\"\n    # For user-agent construction\n    config.installer = config.authenticator = None\n\n    if config.cert_path is None and config.certname:\n        config.cert_path = storage.cert_path_for_cert_name(config, config.certname)\n    elif not config.cert_path or (config.cert_path and config.certname):\n        # intentionally not supporting --cert-path & --cert-name together,\n        # to avoid dealing with mismatched values\n        raise errors.Error(\"Error! Exactly one of --cert-path or --cert-name must be specified!\")\n\n    if config.key_path is not None:  # revocation by cert key\n        logger.debug(\"Revoking %s using cert key %s\",\n                     config.cert_path[0], config.key_path[0])\n        crypto_util.verify_cert_matches_priv_key(config.cert_path[0], config.key_path[0])\n        key = jose.JWK.load(config.key_path[1])\n        acme = client.acme_from_config_key(config, key)\n    else:  # revocation by account key\n        logger.debug(\"Revoking %s using Account Key\", config.cert_path[0])\n        acc, _ = _determine_account(config)\n        acme = client.acme_from_config_key(config, acc.key, acc.regr)\n    cert = crypto_util.pyopenssl_load_certificate(config.cert_path[1])[0]\n    logger.debug(\"Reason code for revocation: %s\", config.reason)\n    try:\n        acme.revoke(jose.ComparableX509(cert), config.reason)\n        _delete_if_appropriate(config)\n    except acme_errors.ClientError as e:\n        return str(e)\n\n    display_ops.success_revocation(config.cert_path[0])\n    return None\n\n\ndef run(config, plugins):\n    \"\"\"Obtain a certificate and install.\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param plugins: List of plugins\n    :type plugins: `list` of `str`\n\n    :returns: `None`\n    :rtype: None\n\n    \"\"\"\n    # TODO: Make run as close to auth + install as possible\n    # Possible difficulties: config.csr was hacked into auth\n    try:\n        installer, authenticator = plug_sel.choose_configurator_plugins(config, plugins, \"run\")\n    except errors.PluginSelectionError as e:\n        return str(e)\n\n    # Preflight check for enhancement support by the selected installer\n    if not enhancements.are_supported(config, installer):\n        raise errors.NotSupportedError(\"One ore more of the requested enhancements \"\n                                       \"are not supported by the selected installer\")\n\n    # TODO: Handle errors from _init_le_client?\n    le_client = _init_le_client(config, authenticator, installer)\n\n    domains, certname = _find_domains_or_certname(config, installer)\n    should_get_cert, lineage = _find_cert(config, domains, certname)\n\n    new_lineage = lineage\n    if should_get_cert:\n        new_lineage = _get_and_save_cert(le_client, config, domains,\n            certname, lineage)\n\n    cert_path = new_lineage.cert_path if new_lineage else None\n    fullchain_path = new_lineage.fullchain_path if new_lineage else None\n    key_path = new_lineage.key_path if new_lineage else None\n    _report_new_cert(config, cert_path, fullchain_path, key_path)\n\n    _install_cert(config, le_client, domains, new_lineage)\n\n    if enhancements.are_requested(config) and new_lineage:\n        enhancements.enable(new_lineage, domains, installer, config)\n\n    if lineage is None or not should_get_cert:\n        display_ops.success_installation(domains)\n    else:\n        display_ops.success_renewal(domains)\n\n    _suggest_donation_if_appropriate(config)\n    return None\n\n\ndef _csr_get_and_save_cert(config, le_client):\n    \"\"\"Obtain a cert using a user-supplied CSR\n\n    This works differently in the CSR case (for now) because we don't\n    have the privkey, and therefore can't construct the files for a lineage.\n    So we just save the cert & chain to disk :/\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param client: Client object\n    :type client: client.Client\n\n    :returns: `cert_path` and `fullchain_path` as absolute paths to the actual files\n    :rtype: `tuple` of `str`\n\n    \"\"\"\n    csr, _ = config.actual_csr\n    cert, chain = le_client.obtain_certificate_from_csr(csr)\n    if config.dry_run:\n        logger.debug(\n            \"Dry run: skipping saving certificate to %s\", config.cert_path)\n        return None, None\n    cert_path, _, fullchain_path = le_client.save_certificate(\n        cert, chain, os.path.normpath(config.cert_path),\n        os.path.normpath(config.chain_path), os.path.normpath(config.fullchain_path))\n    return cert_path, fullchain_path\n\ndef renew_cert(config, plugins, lineage):\n    \"\"\"Renew & save an existing cert. Do not install it.\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param plugins: List of plugins\n    :type plugins: `list` of `str`\n\n    :param lineage: Certificate lineage object\n    :type lineage: storage.RenewableCert\n\n    :returns: `None`\n    :rtype: None\n\n    :raises errors.PluginSelectionError: MissingCommandlineFlag if supplied parameters do not pass\n\n    \"\"\"\n    try:\n        # installers are used in auth mode to determine domain names\n        installer, auth = plug_sel.choose_configurator_plugins(config, plugins, \"certonly\")\n    except errors.PluginSelectionError as e:\n        logger.info(\"Could not choose appropriate plugin: %s\", e)\n        raise\n    le_client = _init_le_client(config, auth, installer)\n\n    renewed_lineage = _get_and_save_cert(le_client, config, lineage=lineage)\n\n    notify = zope.component.getUtility(interfaces.IDisplay).notification\n    if installer is None:\n        notify(\"new certificate deployed without reload, fullchain is {0}\".format(\n               lineage.fullchain), pause=False)\n    else:\n        # In case of a renewal, reload server to pick up new certificate.\n        # In principle we could have a configuration option to inhibit this\n        # from happening.\n        # Run deployer\n        updater.run_renewal_deployer(config, renewed_lineage, installer)\n        installer.restart()\n        notify(\"new certificate deployed with reload of {0} server; fullchain is {1}\".format(\n               config.installer, lineage.fullchain), pause=False)\n\ndef certonly(config, plugins):\n    \"\"\"Authenticate & obtain cert, but do not install it.\n\n    This implements the 'certonly' subcommand.\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param plugins: List of plugins\n    :type plugins: `list` of `str`\n\n    :returns: `None`\n    :rtype: None\n\n    :raises errors.Error: If specified plugin could not be used\n\n    \"\"\"\n    # SETUP: Select plugins and construct a client instance\n    try:\n        # installers are used in auth mode to determine domain names\n        installer, auth = plug_sel.choose_configurator_plugins(config, plugins, \"certonly\")\n    except errors.PluginSelectionError as e:\n        logger.info(\"Could not choose appropriate plugin: %s\", e)\n        raise\n\n    le_client = _init_le_client(config, auth, installer)\n\n    if config.csr:\n        cert_path, fullchain_path = _csr_get_and_save_cert(config, le_client)\n        _report_new_cert(config, cert_path, fullchain_path)\n        _suggest_donation_if_appropriate(config)\n        return\n\n    domains, certname = _find_domains_or_certname(config, installer)\n    should_get_cert, lineage = _find_cert(config, domains, certname)\n\n    if not should_get_cert:\n        notify = zope.component.getUtility(interfaces.IDisplay).notification\n        notify(\"Certificate not yet due for renewal; no action taken.\", pause=False)\n        return\n\n    lineage = _get_and_save_cert(le_client, config, domains, certname, lineage)\n\n    cert_path = lineage.cert_path if lineage else None\n    fullchain_path = lineage.fullchain_path if lineage else None\n    key_path = lineage.key_path if lineage else None\n    _report_new_cert(config, cert_path, fullchain_path, key_path)\n    _suggest_donation_if_appropriate(config)\n\ndef renew(config, unused_plugins):\n    \"\"\"Renew previously-obtained certificates.\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param unused_plugins: List of plugins (deprecated)\n    :type unused_plugins: `list` of `str`\n\n    :returns: `None`\n    :rtype: None\n\n    \"\"\"\n    try:\n        renewal.handle_renewal_request(config)\n    finally:\n        hooks.run_saved_post_hooks()\n\n\ndef make_or_verify_needed_dirs(config):\n    \"\"\"Create or verify existence of config, work, and hook directories.\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :returns: `None`\n    :rtype: None\n\n    \"\"\"\n    util.set_up_core_dir(config.config_dir, constants.CONFIG_DIRS_MODE, config.strict_permissions)\n    util.set_up_core_dir(config.work_dir, constants.CONFIG_DIRS_MODE, config.strict_permissions)\n\n    hook_dirs = (config.renewal_pre_hooks_dir,\n                 config.renewal_deploy_hooks_dir,\n                 config.renewal_post_hooks_dir,)\n    for hook_dir in hook_dirs:\n        util.make_or_verify_dir(hook_dir, strict=config.strict_permissions)\n\n\ndef set_displayer(config):\n    \"\"\"Set the displayer\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :returns: `None`\n    :rtype: None\n\n    \"\"\"\n    if config.quiet:\n        config.noninteractive_mode = True\n        displayer = display_util.NoninteractiveDisplay(open(os.devnull, \"w\")) \\\n        # type: Union[None, display_util.NoninteractiveDisplay, display_util.FileDisplay]\n    elif config.noninteractive_mode:\n        displayer = display_util.NoninteractiveDisplay(sys.stdout)\n    else:\n        displayer = display_util.FileDisplay(sys.stdout,\n                                             config.force_interactive)\n    zope.component.provideUtility(displayer)\n\n\ndef main(cli_args=None):\n    \"\"\"Run Certbot.\n\n    :param cli_args: command line to Certbot, defaults to ``sys.argv[1:]``\n    :type cli_args: `list` of `str`\n\n    :returns: value for `sys.exit` about the exit status of Certbot\n    :rtype: `str` or `int` or `None`\n\n    \"\"\"\n    if not cli_args:\n        cli_args = sys.argv[1:]\n\n    log.pre_arg_parse_setup()\n\n    plugins = plugins_disco.PluginsRegistry.find_all()\n    logger.debug(\"certbot version: %s\", certbot.__version__)\n    # do not log `config`, as it contains sensitive data (e.g. revoke --key)!\n    logger.debug(\"Arguments: %r\", cli_args)\n    logger.debug(\"Discovered plugins: %r\", plugins)\n\n    # note: arg parser internally handles --help (and exits afterwards)\n    args = cli.prepare_and_parse_args(plugins, cli_args)\n    config = configuration.NamespaceConfig(args)\n    zope.component.provideUtility(config)\n\n    # On windows, shell without administrative right cannot create symlinks required by certbot.\n    # So we check the rights before continuing.\n    misc.raise_for_non_administrative_windows_rights()\n\n    try:\n        log.post_arg_parse_setup(config)\n        make_or_verify_needed_dirs(config)\n    except errors.Error:\n        # Let plugins_cmd be run as un-privileged user.\n        if config.func != plugins_cmd:  # pylint: disable=comparison-with-callable\n            raise\n\n    set_displayer(config)\n\n    # Reporter\n    report = reporter.Reporter(config)\n    zope.component.provideUtility(report)\n    util.atexit_register(report.print_messages)\n\n    return config.func(config, plugins)\n"
  },
  {
    "path": "certbot/_internal/plugins/__init__.py",
    "content": "\"\"\"Certbot plugins.\"\"\"\n"
  },
  {
    "path": "certbot/_internal/plugins/disco.py",
    "content": "\"\"\"Utilities for plugins discovery and selection.\"\"\"\nimport collections\nimport itertools\nimport logging\n\nimport pkg_resources\nimport six\nimport zope.interface\nimport zope.interface.verify\n\nfrom acme.magic_typing import Dict\nfrom certbot import errors\nfrom certbot import interfaces\nfrom certbot._internal import constants\n\ntry:\n    # Python 3.3+\n    from collections.abc import Mapping\nexcept ImportError:  # pragma: no cover\n    from collections import Mapping\n\nlogger = logging.getLogger(__name__)\n\n\nclass PluginEntryPoint(object):\n    \"\"\"Plugin entry point.\"\"\"\n\n    PREFIX_FREE_DISTRIBUTIONS = [\n        \"certbot\",\n        \"certbot-apache\",\n        \"certbot-dns-cloudflare\",\n        \"certbot-dns-cloudxns\",\n        \"certbot-dns-digitalocean\",\n        \"certbot-dns-dnsimple\",\n        \"certbot-dns-dnsmadeeasy\",\n        \"certbot-dns-gehirn\",\n        \"certbot-dns-google\",\n        \"certbot-dns-linode\",\n        \"certbot-dns-luadns\",\n        \"certbot-dns-nsone\",\n        \"certbot-dns-ovh\",\n        \"certbot-dns-rfc2136\",\n        \"certbot-dns-route53\",\n        \"certbot-dns-sakuracloud\",\n        \"certbot-nginx\",\n    ]\n    \"\"\"Distributions for which prefix will be omitted.\"\"\"\n\n    # this object is mutable, don't allow it to be hashed!\n    __hash__ = None  # type: ignore\n\n    def __init__(self, entry_point):\n        self.name = self.entry_point_to_plugin_name(entry_point)\n        self.plugin_cls = entry_point.load()\n        self.entry_point = entry_point\n        self._initialized = None\n        self._prepared = None\n\n    @classmethod\n    def entry_point_to_plugin_name(cls, entry_point):\n        \"\"\"Unique plugin name for an ``entry_point``\"\"\"\n        if entry_point.dist.key in cls.PREFIX_FREE_DISTRIBUTIONS:\n            return entry_point.name\n        return entry_point.dist.key + \":\" + entry_point.name\n\n    @property\n    def description(self):\n        \"\"\"Description of the plugin.\"\"\"\n        return self.plugin_cls.description\n\n    @property\n    def description_with_name(self):\n        \"\"\"Description with name. Handy for UI.\"\"\"\n        return \"{0} ({1})\".format(self.description, self.name)\n\n    @property\n    def long_description(self):\n        \"\"\"Long description of the plugin.\"\"\"\n        try:\n            return self.plugin_cls.long_description\n        except AttributeError:\n            return self.description\n\n    @property\n    def hidden(self):\n        \"\"\"Should this plugin be hidden from UI?\"\"\"\n        return getattr(self.plugin_cls, \"hidden\", False)\n\n    def ifaces(self, *ifaces_groups):\n        \"\"\"Does plugin implements specified interface groups?\"\"\"\n        return not ifaces_groups or any(\n            all(iface.implementedBy(self.plugin_cls)\n                for iface in ifaces)\n            for ifaces in ifaces_groups)\n\n    @property\n    def initialized(self):\n        \"\"\"Has the plugin been initialized already?\"\"\"\n        return self._initialized is not None\n\n    def init(self, config=None):\n        \"\"\"Memoized plugin initialization.\"\"\"\n        if not self.initialized:\n            self.entry_point.require()  # fetch extras!\n            self._initialized = self.plugin_cls(config, self.name)\n        return self._initialized\n\n    def verify(self, ifaces):\n        \"\"\"Verify that the plugin conforms to the specified interfaces.\"\"\"\n        assert self.initialized\n        for iface in ifaces:  # zope.interface.providedBy(plugin)\n            try:\n                zope.interface.verify.verifyObject(iface, self.init())\n            except zope.interface.exceptions.BrokenImplementation as error:\n                if iface.implementedBy(self.plugin_cls):\n                    logger.debug(\n                        \"%s implements %s but object does not verify: %s\",\n                        self.plugin_cls, iface.__name__, error, exc_info=True)\n                return False\n        return True\n\n    @property\n    def prepared(self):\n        \"\"\"Has the plugin been prepared already?\"\"\"\n        if not self.initialized:\n            logger.debug(\".prepared called on uninitialized %r\", self)\n        return self._prepared is not None\n\n    def prepare(self):\n        \"\"\"Memoized plugin preparation.\"\"\"\n        assert self.initialized\n        if self._prepared is None:\n            try:\n                self._initialized.prepare()\n            except errors.MisconfigurationError as error:\n                logger.debug(\"Misconfigured %r: %s\", self, error, exc_info=True)\n                self._prepared = error\n            except errors.NoInstallationError as error:\n                logger.debug(\n                    \"No installation (%r): %s\", self, error, exc_info=True)\n                self._prepared = error\n            except errors.PluginError as error:\n                logger.debug(\"Other error:(%r): %s\", self, error, exc_info=True)\n                self._prepared = error\n            else:\n                self._prepared = True\n        return self._prepared\n\n    @property\n    def misconfigured(self):\n        \"\"\"Is plugin misconfigured?\"\"\"\n        return isinstance(self._prepared, errors.MisconfigurationError)\n\n    @property\n    def problem(self):\n        \"\"\"Return the Exception raised during plugin setup, or None if all is well\"\"\"\n        if isinstance(self._prepared, Exception):\n            return self._prepared\n        return None\n\n    @property\n    def available(self):\n        \"\"\"Is plugin available, i.e. prepared or misconfigured?\"\"\"\n        return self._prepared is True or self.misconfigured\n\n    def __repr__(self):\n        return \"PluginEntryPoint#{0}\".format(self.name)\n\n    def __str__(self):\n        lines = [\n            \"* {0}\".format(self.name),\n            \"Description: {0}\".format(self.plugin_cls.description),\n            \"Interfaces: {0}\".format(\", \".join(\n                iface.__name__ for iface in zope.interface.implementedBy(\n                    self.plugin_cls))),\n            \"Entry point: {0}\".format(self.entry_point),\n        ]\n\n        if self.initialized:\n            lines.append(\"Initialized: {0}\".format(self.init()))\n            if self.prepared:\n                lines.append(\"Prep: {0}\".format(self.prepare()))\n\n        return \"\\n\".join(lines)\n\n\nclass PluginsRegistry(Mapping):\n    \"\"\"Plugins registry.\"\"\"\n\n    def __init__(self, plugins):\n        # plugins are sorted so the same order is used between runs.\n        # This prevents deadlock caused by plugins acquiring a lock\n        # and ensures at least one concurrent Certbot instance will run\n        # successfully.\n\n        # Pylint checks for super init, but also claims the super\n        # has no __init__member\n        self._plugins = collections.OrderedDict(sorted(six.iteritems(plugins)))\n\n    @classmethod\n    def find_all(cls):\n        \"\"\"Find plugins using setuptools entry points.\"\"\"\n        plugins = {}  # type: Dict[str, PluginEntryPoint]\n        entry_points = itertools.chain(\n            pkg_resources.iter_entry_points(\n                constants.SETUPTOOLS_PLUGINS_ENTRY_POINT),\n            pkg_resources.iter_entry_points(\n                constants.OLD_SETUPTOOLS_PLUGINS_ENTRY_POINT),)\n        for entry_point in entry_points:\n            plugin_ep = PluginEntryPoint(entry_point)\n            assert plugin_ep.name not in plugins, (\n                \"PREFIX_FREE_DISTRIBUTIONS messed up\")\n            if interfaces.IPluginFactory.providedBy(plugin_ep.plugin_cls):\n                plugins[plugin_ep.name] = plugin_ep\n            else:  # pragma: no cover\n                logger.warning(\n                    \"%r does not provide IPluginFactory, skipping\", plugin_ep)\n        return cls(plugins)\n\n    def __getitem__(self, name):\n        return self._plugins[name]\n\n    def __iter__(self):\n        return iter(self._plugins)\n\n    def __len__(self):\n        return len(self._plugins)\n\n    def init(self, config):\n        \"\"\"Initialize all plugins in the registry.\"\"\"\n        return [plugin_ep.init(config) for plugin_ep\n                in six.itervalues(self._plugins)]\n\n    def filter(self, pred):\n        \"\"\"Filter plugins based on predicate.\"\"\"\n        return type(self)(dict((name, plugin_ep) for name, plugin_ep\n                               in six.iteritems(self._plugins) if pred(plugin_ep)))\n\n    def visible(self):\n        \"\"\"Filter plugins based on visibility.\"\"\"\n        return self.filter(lambda plugin_ep: not plugin_ep.hidden)\n\n    def ifaces(self, *ifaces_groups):\n        \"\"\"Filter plugins based on interfaces.\"\"\"\n        return self.filter(lambda p_ep: p_ep.ifaces(*ifaces_groups))\n\n    def verify(self, ifaces):\n        \"\"\"Filter plugins based on verification.\"\"\"\n        return self.filter(lambda p_ep: p_ep.verify(ifaces))\n\n    def prepare(self):\n        \"\"\"Prepare all plugins in the registry.\"\"\"\n        return [plugin_ep.prepare() for plugin_ep in six.itervalues(self._plugins)]\n\n    def available(self):\n        \"\"\"Filter plugins based on availability.\"\"\"\n        return self.filter(lambda p_ep: p_ep.available)\n        # successfully prepared + misconfigured\n\n    def find_init(self, plugin):\n        \"\"\"Find an initialized plugin.\n\n        This is particularly useful for finding a name for the plugin\n        (although `.IPluginFactory.__call__` takes ``name`` as one of\n        the arguments, ``IPlugin.name`` is not part of the interface)::\n\n          # plugin is an instance providing IPlugin, initialized\n          # somewhere else in the code\n          plugin_registry.find_init(plugin).name\n\n        Returns ``None`` if ``plugin`` is not found in the registry.\n\n        \"\"\"\n        # use list instead of set because PluginEntryPoint is not hashable\n        candidates = [plugin_ep for plugin_ep in six.itervalues(self._plugins)\n                      if plugin_ep.initialized and plugin_ep.init() is plugin]\n        assert len(candidates) <= 1\n        if candidates:\n            return candidates[0]\n        return None\n\n    def __repr__(self):\n        return \"{0}({1})\".format(\n            self.__class__.__name__, ','.join(\n                repr(p_ep) for p_ep in six.itervalues(self._plugins)))\n\n    def __str__(self):\n        if not self._plugins:\n            return \"No plugins\"\n        return \"\\n\\n\".join(str(p_ep) for p_ep in six.itervalues(self._plugins))\n"
  },
  {
    "path": "certbot/_internal/plugins/manual.py",
    "content": "\"\"\"Manual authenticator plugin\"\"\"\nimport zope.component\nimport zope.interface\n\nfrom acme import challenges\nfrom acme.magic_typing import Dict\nfrom certbot import achallenges  # pylint: disable=unused-import\nfrom certbot import errors\nfrom certbot import interfaces\nfrom certbot import reverter\nfrom certbot._internal import hooks\nfrom certbot.compat import os\nfrom certbot.plugins import common\n\n\n@zope.interface.implementer(interfaces.IAuthenticator)\n@zope.interface.provider(interfaces.IPluginFactory)\nclass Authenticator(common.Plugin):\n    \"\"\"Manual authenticator\n\n    This plugin allows the user to perform the domain validation\n    challenge(s) themselves. This either be done manually by the user or\n    through shell scripts provided to Certbot.\n\n    \"\"\"\n\n    description = 'Manual configuration or run your own shell scripts'\n    hidden = True\n    long_description = (\n        'Authenticate through manual configuration or custom shell scripts. '\n        'When using shell scripts, an authenticator script must be provided. '\n        'The environment variables available to this script depend on the '\n        'type of challenge. $CERTBOT_DOMAIN will always contain the domain '\n        'being authenticated. For HTTP-01 and DNS-01, $CERTBOT_VALIDATION '\n        'is the validation string, and $CERTBOT_TOKEN is the filename of the '\n        'resource requested when performing an HTTP-01 challenge. An additional '\n        'cleanup script can also be provided and can use the additional variable '\n        '$CERTBOT_AUTH_OUTPUT which contains the stdout output from the auth script.')\n    _DNS_INSTRUCTIONS = \"\"\"\\\nPlease deploy a DNS TXT record under the name\n{domain} with the following value:\n\n{validation}\n\nBefore continuing, verify the record is deployed.\"\"\"\n    _HTTP_INSTRUCTIONS = \"\"\"\\\nCreate a file containing just this data:\n\n{validation}\n\nAnd make it available on your web server at this URL:\n\n{uri}\n\"\"\"\n    _SUBSEQUENT_CHALLENGE_INSTRUCTIONS = \"\"\"\n(This must be set up in addition to the previous challenges; do not remove,\nreplace, or undo the previous challenge tasks yet.)\n\"\"\"\n    _SUBSEQUENT_DNS_CHALLENGE_INSTRUCTIONS = \"\"\"\n(This must be set up in addition to the previous challenges; do not remove,\nreplace, or undo the previous challenge tasks yet. Note that you might be\nasked to create multiple distinct TXT records with the same name. This is\npermitted by DNS standards.)\n\"\"\"\n\n    def __init__(self, *args, **kwargs):\n        super(Authenticator, self).__init__(*args, **kwargs)\n        self.reverter = reverter.Reverter(self.config)\n        self.reverter.recovery_routine()\n        self.env = dict() \\\n        # type: Dict[achallenges.KeyAuthorizationAnnotatedChallenge, Dict[str, str]]\n        self.subsequent_dns_challenge = False\n        self.subsequent_any_challenge = False\n\n    @classmethod\n    def add_parser_arguments(cls, add):\n        add('auth-hook',\n            help='Path or command to execute for the authentication script')\n        add('cleanup-hook',\n            help='Path or command to execute for the cleanup script')\n        add('public-ip-logging-ok', action='store_true',\n            help='Automatically allows public IP logging (default: Ask)')\n\n    def prepare(self):  # pylint: disable=missing-function-docstring\n        if self.config.noninteractive_mode and not self.conf('auth-hook'):\n            raise errors.PluginError(\n                'An authentication script must be provided with --{0} when '\n                'using the manual plugin non-interactively.'.format(\n                    self.option_name('auth-hook')))\n        self._validate_hooks()\n\n    def _validate_hooks(self):\n        if self.config.validate_hooks:\n            for name in ('auth-hook', 'cleanup-hook'):\n                hook = self.conf(name)\n                if hook is not None:\n                    hook_prefix = self.option_name(name)[:-len('-hook')]\n                    hooks.validate_hook(hook, hook_prefix)\n\n    def more_info(self):  # pylint: disable=missing-function-docstring\n        return (\n            'This plugin allows the user to customize setup for domain '\n            'validation challenges either through shell scripts provided by '\n            'the user or by performing the setup manually.')\n\n    def get_chall_pref(self, domain):\n        # pylint: disable=unused-argument,missing-function-docstring\n        return [challenges.HTTP01, challenges.DNS01]\n\n    def perform(self, achalls):  # pylint: disable=missing-function-docstring\n        self._verify_ip_logging_ok()\n        if self.conf('auth-hook'):\n            perform_achall = self._perform_achall_with_script\n        else:\n            perform_achall = self._perform_achall_manually\n\n        responses = []\n        for achall in achalls:\n            perform_achall(achall)\n            responses.append(achall.response(achall.account_key))\n        return responses\n\n    def _verify_ip_logging_ok(self):\n        if not self.conf('public-ip-logging-ok'):\n            cli_flag = '--{0}'.format(self.option_name('public-ip-logging-ok'))\n            msg = ('NOTE: The IP of this machine will be publicly logged as '\n                   \"having requested this certificate. If you're running \"\n                   'certbot in manual mode on a machine that is not your '\n                   \"server, please ensure you're okay with that.\\n\\n\"\n                   'Are you OK with your IP being logged?')\n            display = zope.component.getUtility(interfaces.IDisplay)\n            if display.yesno(msg, cli_flag=cli_flag, force_interactive=True):\n                setattr(self.config, self.dest('public-ip-logging-ok'), True)\n            else:\n                raise errors.PluginError('Must agree to IP logging to proceed')\n\n    def _perform_achall_with_script(self, achall):\n        env = dict(CERTBOT_DOMAIN=achall.domain,\n                   CERTBOT_VALIDATION=achall.validation(achall.account_key))\n        if isinstance(achall.chall, challenges.HTTP01):\n            env['CERTBOT_TOKEN'] = achall.chall.encode('token')\n        else:\n            os.environ.pop('CERTBOT_TOKEN', None)\n        os.environ.update(env)\n        _, out = self._execute_hook('auth-hook')\n        env['CERTBOT_AUTH_OUTPUT'] = out.strip()\n        self.env[achall] = env\n\n    def _perform_achall_manually(self, achall):\n        validation = achall.validation(achall.account_key)\n        if isinstance(achall.chall, challenges.HTTP01):\n            msg = self._HTTP_INSTRUCTIONS.format(\n                achall=achall, encoded_token=achall.chall.encode('token'),\n                port=self.config.http01_port,\n                uri=achall.chall.uri(achall.domain), validation=validation)\n        else:\n            assert isinstance(achall.chall, challenges.DNS01)\n            msg = self._DNS_INSTRUCTIONS.format(\n                domain=achall.validation_domain_name(achall.domain),\n                validation=validation)\n        if isinstance(achall.chall, challenges.DNS01):\n            if self.subsequent_dns_challenge:\n                # 2nd or later dns-01 challenge\n                msg += self._SUBSEQUENT_DNS_CHALLENGE_INSTRUCTIONS\n            self.subsequent_dns_challenge = True\n        elif self.subsequent_any_challenge:\n            # 2nd or later challenge of another type\n            msg += self._SUBSEQUENT_CHALLENGE_INSTRUCTIONS\n        display = zope.component.getUtility(interfaces.IDisplay)\n        display.notification(msg, wrap=False, force_interactive=True)\n        self.subsequent_any_challenge = True\n\n    def cleanup(self, achalls):  # pylint: disable=missing-function-docstring\n        if self.conf('cleanup-hook'):\n            for achall in achalls:\n                env = self.env.pop(achall)\n                if 'CERTBOT_TOKEN' not in env:\n                    os.environ.pop('CERTBOT_TOKEN', None)\n                os.environ.update(env)\n                self._execute_hook('cleanup-hook')\n        self.reverter.recovery_routine()\n\n    def _execute_hook(self, hook_name):\n        return hooks.execute(self.option_name(hook_name), self.conf(hook_name))\n"
  },
  {
    "path": "certbot/_internal/plugins/null.py",
    "content": "\"\"\"Null plugin.\"\"\"\nimport logging\n\nimport zope.component\nimport zope.interface\n\nfrom certbot import interfaces\nfrom certbot.plugins import common\n\nlogger = logging.getLogger(__name__)\n\n\n@zope.interface.implementer(interfaces.IInstaller)\n@zope.interface.provider(interfaces.IPluginFactory)\nclass Installer(common.Plugin):\n    \"\"\"Null installer.\"\"\"\n\n    description = \"Null Installer\"\n    hidden = True\n\n    # pylint: disable=missing-function-docstring\n\n    def prepare(self):\n        pass  # pragma: no cover\n\n    def more_info(self):\n        return \"Installer that doesn't do anything (for testing).\"\n\n    def get_all_names(self):\n        return []\n\n    def deploy_cert(self, domain, cert_path, key_path,\n                    chain_path=None, fullchain_path=None):\n        pass  # pragma: no cover\n\n    def enhance(self, domain, enhancement, options=None):\n        pass  # pragma: no cover\n\n    def supported_enhancements(self):\n        return []\n\n    def save(self, title=None, temporary=False):\n        pass  # pragma: no cover\n\n    def rollback_checkpoints(self, rollback=1):\n        pass  # pragma: no cover\n\n    def recovery_routine(self):\n        pass  # pragma: no cover\n\n    def config_test(self):\n        pass  # pragma: no cover\n\n    def restart(self):\n        pass  # pragma: no cover\n"
  },
  {
    "path": "certbot/_internal/plugins/selection.py",
    "content": "\"\"\"Decide which plugins to use for authentication & installation\"\"\"\nfrom __future__ import print_function\n\nimport logging\n\nimport six\nimport zope.component\n\nfrom certbot import errors\nfrom certbot import interfaces\nfrom certbot.compat import os\nfrom certbot.display import util as display_util\n\nlogger = logging.getLogger(__name__)\nz_util = zope.component.getUtility\n\ndef pick_configurator(\n        config, default, plugins,\n        question=\"How would you like to authenticate and install \"\n                 \"certificates?\"):\n    \"\"\"Pick configurator plugin.\"\"\"\n    return pick_plugin(\n        config, default, plugins, question,\n        (interfaces.IAuthenticator, interfaces.IInstaller))\n\n\ndef pick_installer(config, default, plugins,\n                   question=\"How would you like to install certificates?\"):\n    \"\"\"Pick installer plugin.\"\"\"\n    return pick_plugin(\n        config, default, plugins, question, (interfaces.IInstaller,))\n\n\ndef pick_authenticator(\n        config, default, plugins, question=\"How would you \"\n        \"like to authenticate with the ACME CA?\"):\n    \"\"\"Pick authentication plugin.\"\"\"\n    return pick_plugin(\n        config, default, plugins, question, (interfaces.IAuthenticator,))\n\ndef get_unprepared_installer(config, plugins):\n    \"\"\"\n    Get an unprepared interfaces.IInstaller object.\n\n    :param certbot.interfaces.IConfig config: Configuration\n    :param certbot._internal.plugins.disco.PluginsRegistry plugins:\n        All plugins registered as entry points.\n\n    :returns: Unprepared installer plugin or None\n    :rtype: IPlugin or None\n    \"\"\"\n\n    _, req_inst = cli_plugin_requests(config)\n    if not req_inst:\n        return None\n    installers = plugins.filter(lambda p_ep: p_ep.name == req_inst)\n    installers.init(config)\n    installers = installers.verify((interfaces.IInstaller,))\n    if len(installers) > 1:\n        raise errors.PluginSelectionError(\n            \"Found multiple installers with the name %s, Certbot is unable to \"\n            \"determine which one to use. Skipping.\" % req_inst)\n    if installers:\n        inst = list(installers.values())[0]\n        logger.debug(\"Selecting plugin: %s\", inst)\n        return inst.init(config)\n    raise errors.PluginSelectionError(\n        \"Could not select or initialize the requested installer %s.\" % req_inst)\n\ndef pick_plugin(config, default, plugins, question, ifaces):\n    \"\"\"Pick plugin.\n\n    :param certbot.interfaces.IConfig: Configuration\n    :param str default: Plugin name supplied by user or ``None``.\n    :param certbot._internal.plugins.disco.PluginsRegistry plugins:\n        All plugins registered as entry points.\n    :param str question: Question to be presented to the user in case\n        multiple candidates are found.\n    :param list ifaces: Interfaces that plugins must provide.\n\n    :returns: Initialized plugin.\n    :rtype: IPlugin\n\n    \"\"\"\n    if default is not None:\n        # throw more UX-friendly error if default not in plugins\n        filtered = plugins.filter(lambda p_ep: p_ep.name == default)\n    else:\n        if config.noninteractive_mode:\n            # it's really bad to auto-select the single available plugin in\n            # non-interactive mode, because an update could later add a second\n            # available plugin\n            raise errors.MissingCommandlineFlag(\n                \"Missing command line flags. For non-interactive execution, \"\n                \"you will need to specify a plugin on the command line.  Run \"\n                \"with '--help plugins' to see a list of options, and see \"\n                \"https://eff.org/letsencrypt-plugins for more detail on what \"\n                \"the plugins do and how to use them.\")\n\n        filtered = plugins.visible().ifaces(ifaces)\n\n    filtered.init(config)\n    verified = filtered.verify(ifaces)\n    verified.prepare()\n    prepared = verified.available()\n\n    if len(prepared) > 1:\n        logger.debug(\"Multiple candidate plugins: %s\", prepared)\n        plugin_ep = choose_plugin(list(six.itervalues(prepared)), question)\n        if plugin_ep is None:\n            return None\n        return plugin_ep.init()\n    elif len(prepared) == 1:\n        plugin_ep = list(prepared.values())[0]\n        logger.debug(\"Single candidate plugin: %s\", plugin_ep)\n        if plugin_ep.misconfigured:\n            return None\n        return plugin_ep.init()\n    else:\n        logger.debug(\"No candidate plugin\")\n        return None\n\n\ndef choose_plugin(prepared, question):\n    \"\"\"Allow the user to choose their plugin.\n\n    :param list prepared: List of `~.PluginEntryPoint`.\n    :param str question: Question to be presented to the user.\n\n    :returns: Plugin entry point chosen by the user.\n    :rtype: `~.PluginEntryPoint`\n\n    \"\"\"\n    opts = [plugin_ep.description_with_name +\n            (\" [Misconfigured]\" if plugin_ep.misconfigured else \"\")\n            for plugin_ep in prepared]\n    names = set(plugin_ep.name for plugin_ep in prepared)\n\n    while True:\n        disp = z_util(interfaces.IDisplay)\n        if \"CERTBOT_AUTO\" in os.environ and names == set((\"apache\", \"nginx\")):\n            # The possibility of being offered exactly apache and nginx here\n            # is new interactivity brought by https://github.com/certbot/certbot/issues/4079,\n            # so set apache as a default for those kinds of non-interactive use\n            # (the user will get a warning to set --non-interactive or --force-interactive)\n            apache_idx = [n for n, p in enumerate(prepared) if p.name == \"apache\"][0]\n            code, index = disp.menu(question, opts, default=apache_idx)\n        else:\n            code, index = disp.menu(question, opts, force_interactive=True)\n\n        if code == display_util.OK:\n            plugin_ep = prepared[index]\n            if plugin_ep.misconfigured:\n                z_util(interfaces.IDisplay).notification(\n                    \"The selected plugin encountered an error while parsing \"\n                    \"your server configuration and cannot be used. The error \"\n                    \"was:\\n\\n{0}\".format(plugin_ep.prepare()), pause=False)\n            else:\n                return plugin_ep\n        else:\n            return None\n\nnoninstaller_plugins = [\"webroot\", \"manual\", \"standalone\", \"dns-cloudflare\", \"dns-cloudxns\",\n                        \"dns-digitalocean\", \"dns-dnsimple\", \"dns-dnsmadeeasy\", \"dns-gehirn\",\n                        \"dns-google\", \"dns-linode\", \"dns-luadns\", \"dns-nsone\", \"dns-ovh\",\n                        \"dns-rfc2136\", \"dns-route53\", \"dns-sakuracloud\"]\n\ndef record_chosen_plugins(config, plugins, auth, inst):\n    \"Update the config entries to reflect the plugins we actually selected.\"\n    config.authenticator = plugins.find_init(auth).name if auth else None\n    config.installer = plugins.find_init(inst).name if inst else None\n    logger.info(\"Plugins selected: Authenticator %s, Installer %s\",\n         config.authenticator, config.installer)\n\n\ndef choose_configurator_plugins(config, plugins, verb):\n    \"\"\"\n    Figure out which configurator we're going to use, modifies\n    config.authenticator and config.installer strings to reflect that choice if\n    necessary.\n\n    :raises errors.PluginSelectionError if there was a problem\n\n    :returns: (an `IAuthenticator` or None, an `IInstaller` or None)\n    :rtype: tuple\n    \"\"\"\n\n    req_auth, req_inst = cli_plugin_requests(config)\n    installer_question = None\n\n    if verb == \"enhance\":\n        installer_question = (\"Which installer would you like to use to \"\n                              \"configure the selected enhancements?\")\n\n    # Which plugins do we need?\n    if verb == \"run\":\n        need_inst = need_auth = True\n        from certbot._internal.cli import cli_command\n        if req_auth in noninstaller_plugins and not req_inst:\n            msg = ('With the {0} plugin, you probably want to use the \"certonly\" command, eg:{1}'\n                   '{1}    {2} certonly --{0}{1}{1}'\n                   '(Alternatively, add a --installer flag. See https://eff.org/letsencrypt-plugins'\n                   '{1} and \"--help plugins\" for more information.)'.format(\n                       req_auth, os.linesep, cli_command))\n\n            raise errors.MissingCommandlineFlag(msg)\n    else:\n        need_inst = need_auth = False\n    if verb == \"certonly\":\n        need_auth = True\n    elif verb in (\"install\", \"enhance\"):\n        need_inst = True\n        if config.authenticator:\n            logger.warning(\"Specifying an authenticator doesn't make sense when \"\n                           \"running Certbot with verb \\\"%s\\\"\", verb)\n    # Try to meet the user's request and/or ask them to pick plugins\n    authenticator = installer = None\n    if verb == \"run\" and req_auth == req_inst:\n        # Unless the user has explicitly asked for different auth/install,\n        # only consider offering a single choice\n        authenticator = installer = pick_configurator(config, req_inst, plugins)\n    else:\n        if need_inst or req_inst:\n            installer = pick_installer(config, req_inst, plugins, installer_question)\n        if need_auth:\n            authenticator = pick_authenticator(config, req_auth, plugins)\n    logger.debug(\"Selected authenticator %s and installer %s\", authenticator, installer)\n\n    # Report on any failures\n    if need_inst and not installer:\n        diagnose_configurator_problem(\"installer\", req_inst, plugins)\n    if need_auth and not authenticator:\n        diagnose_configurator_problem(\"authenticator\", req_auth, plugins)\n\n    record_chosen_plugins(config, plugins, authenticator, installer)\n    return installer, authenticator\n\n\ndef set_configurator(previously, now):\n    \"\"\"\n    Setting configurators multiple ways is okay, as long as they all agree\n    :param str previously: previously identified request for the installer/authenticator\n    :param str requested: the request currently being processed\n    \"\"\"\n    if not now:\n        # we're not actually setting anything\n        return previously\n    if previously:\n        if previously != now:\n            msg = \"Too many flags setting configurators/installers/authenticators {0} -> {1}\"\n            raise errors.PluginSelectionError(msg.format(repr(previously), repr(now)))\n    return now\n\n\ndef cli_plugin_requests(config):\n    \"\"\"\n    Figure out which plugins the user requested with CLI and config options\n\n    :returns: (requested authenticator string or None, requested installer string or None)\n    :rtype: tuple\n    \"\"\"\n    req_inst = req_auth = config.configurator\n    req_inst = set_configurator(req_inst, config.installer)\n    req_auth = set_configurator(req_auth, config.authenticator)\n\n    if config.nginx:\n        req_inst = set_configurator(req_inst, \"nginx\")\n        req_auth = set_configurator(req_auth, \"nginx\")\n    if config.apache:\n        req_inst = set_configurator(req_inst, \"apache\")\n        req_auth = set_configurator(req_auth, \"apache\")\n    if config.standalone:\n        req_auth = set_configurator(req_auth, \"standalone\")\n    if config.webroot:\n        req_auth = set_configurator(req_auth, \"webroot\")\n    if config.manual:\n        req_auth = set_configurator(req_auth, \"manual\")\n    if config.dns_cloudflare:\n        req_auth = set_configurator(req_auth, \"dns-cloudflare\")\n    if config.dns_cloudxns:\n        req_auth = set_configurator(req_auth, \"dns-cloudxns\")\n    if config.dns_digitalocean:\n        req_auth = set_configurator(req_auth, \"dns-digitalocean\")\n    if config.dns_dnsimple:\n        req_auth = set_configurator(req_auth, \"dns-dnsimple\")\n    if config.dns_dnsmadeeasy:\n        req_auth = set_configurator(req_auth, \"dns-dnsmadeeasy\")\n    if config.dns_gehirn:\n        req_auth = set_configurator(req_auth, \"dns-gehirn\")\n    if config.dns_google:\n        req_auth = set_configurator(req_auth, \"dns-google\")\n    if config.dns_linode:\n        req_auth = set_configurator(req_auth, \"dns-linode\")\n    if config.dns_luadns:\n        req_auth = set_configurator(req_auth, \"dns-luadns\")\n    if config.dns_nsone:\n        req_auth = set_configurator(req_auth, \"dns-nsone\")\n    if config.dns_ovh:\n        req_auth = set_configurator(req_auth, \"dns-ovh\")\n    if config.dns_rfc2136:\n        req_auth = set_configurator(req_auth, \"dns-rfc2136\")\n    if config.dns_route53:\n        req_auth = set_configurator(req_auth, \"dns-route53\")\n    if config.dns_sakuracloud:\n        req_auth = set_configurator(req_auth, \"dns-sakuracloud\")\n    logger.debug(\"Requested authenticator %s and installer %s\", req_auth, req_inst)\n    return req_auth, req_inst\n\n\ndef diagnose_configurator_problem(cfg_type, requested, plugins):\n    \"\"\"\n    Raise the most helpful error message about a plugin being unavailable\n\n    :param str cfg_type: either \"installer\" or \"authenticator\"\n    :param str requested: the plugin that was requested\n    :param .PluginsRegistry plugins: available plugins\n\n    :raises error.PluginSelectionError: if there was a problem\n    \"\"\"\n\n    if requested:\n        if requested not in plugins:\n            msg = \"The requested {0} plugin does not appear to be installed\".format(requested)\n        else:\n            msg = (\"The {0} plugin is not working; there may be problems with \"\n                   \"your existing configuration.\\nThe error was: {1!r}\"\n                   .format(requested, plugins[requested].problem))\n    elif cfg_type == \"installer\":\n        from certbot._internal.cli import cli_command\n        msg = ('Certbot doesn\\'t know how to automatically configure the web '\n          'server on this system. However, it can still get a certificate for '\n          'you. Please run \"{0} certonly\" to do so. You\\'ll need to '\n          'manually configure your web server to use the resulting '\n          'certificate.').format(cli_command)\n    else:\n        msg = \"{0} could not be determined or is not installed\".format(cfg_type)\n    raise errors.PluginSelectionError(msg)\n"
  },
  {
    "path": "certbot/_internal/plugins/standalone.py",
    "content": "\"\"\"Standalone Authenticator.\"\"\"\nimport collections\nimport logging\nimport socket\n# https://github.com/python/typeshed/blob/master/stdlib/2and3/socket.pyi\nfrom socket import errno as socket_errors  # type: ignore\n\nimport OpenSSL  # pylint: disable=unused-import\nimport six\nimport zope.interface\n\nfrom acme import challenges\nfrom acme import standalone as acme_standalone\nfrom acme.magic_typing import DefaultDict\nfrom acme.magic_typing import Dict\nfrom acme.magic_typing import Set\nfrom acme.magic_typing import Tuple\nfrom acme.magic_typing import TYPE_CHECKING\nfrom certbot import achallenges\nfrom certbot import errors\nfrom certbot import interfaces\nfrom certbot.plugins import common\n\nlogger = logging.getLogger(__name__)\n\nif TYPE_CHECKING:\n    ServedType = DefaultDict[\n        acme_standalone.BaseDualNetworkedServers,\n        Set[achallenges.KeyAuthorizationAnnotatedChallenge]\n    ]\n\nclass ServerManager(object):\n    \"\"\"Standalone servers manager.\n\n    Manager for `ACMEServer` and `ACMETLSServer` instances.\n\n    `certs` and `http_01_resources` correspond to\n    `acme.crypto_util.SSLSocket.certs` and\n    `acme.crypto_util.SSLSocket.http_01_resources` respectively. All\n    created servers share the same certificates and resources, so if\n    you're running both TLS and non-TLS instances, HTTP01 handlers\n    will serve the same URLs!\n\n    \"\"\"\n    def __init__(self, certs, http_01_resources):\n        self._instances = {}  # type: Dict[int, acme_standalone.BaseDualNetworkedServers]\n        self.certs = certs\n        self.http_01_resources = http_01_resources\n\n    def run(self, port, challenge_type, listenaddr=\"\"):\n        \"\"\"Run ACME server on specified ``port``.\n\n        This method is idempotent, i.e. all calls with the same pair of\n        ``(port, challenge_type)`` will reuse the same server.\n\n        :param int port: Port to run the server on.\n        :param challenge_type: Subclass of `acme.challenges.Challenge`,\n            currently only `acme.challenge.HTTP01`.\n        :param str listenaddr: (optional) The address to listen on. Defaults to all addrs.\n\n        :returns: DualNetworkedServers instance.\n        :rtype: ACMEServerMixin\n\n        \"\"\"\n        assert challenge_type == challenges.HTTP01\n        if port in self._instances:\n            return self._instances[port]\n\n        address = (listenaddr, port)\n        try:\n            servers = acme_standalone.HTTP01DualNetworkedServers(\n                address, self.http_01_resources)\n        except socket.error as error:\n            raise errors.StandaloneBindError(error, port)\n\n        servers.serve_forever()\n\n        # if port == 0, then random free port on OS is taken\n        # both servers, if they exist, have the same port\n        real_port = servers.getsocknames()[0][1]\n        self._instances[real_port] = servers\n        return servers\n\n    def stop(self, port):\n        \"\"\"Stop ACME server running on the specified ``port``.\n\n        :param int port:\n\n        \"\"\"\n        instance = self._instances[port]\n        for sockname in instance.getsocknames():\n            logger.debug(\"Stopping server at %s:%d...\",\n                         *sockname[:2])\n        instance.shutdown_and_server_close()\n        del self._instances[port]\n\n    def running(self):\n        \"\"\"Return all running instances.\n\n        Once the server is stopped using `stop`, it will not be\n        returned.\n\n        :returns: Mapping from ``port`` to ``servers``.\n        :rtype: tuple\n\n        \"\"\"\n        return self._instances.copy()\n\n\n@zope.interface.implementer(interfaces.IAuthenticator)\n@zope.interface.provider(interfaces.IPluginFactory)\nclass Authenticator(common.Plugin):\n    \"\"\"Standalone Authenticator.\n\n    This authenticator creates its own ephemeral TCP listener on the\n    necessary port in order to respond to incoming http-01\n    challenges from the certificate authority. Therefore, it does not\n    rely on any existing server program.\n    \"\"\"\n\n    description = \"Spin up a temporary webserver\"\n\n    def __init__(self, *args, **kwargs):\n        super(Authenticator, self).__init__(*args, **kwargs)\n\n        self.served = collections.defaultdict(set)  # type: ServedType\n\n        # Stuff below is shared across threads (i.e. servers read\n        # values, main thread writes). Due to the nature of CPython's\n        # GIL, the operations are safe, c.f.\n        # https://docs.python.org/2/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe\n        self.certs = {}  # type: Dict[bytes, Tuple[OpenSSL.crypto.PKey, OpenSSL.crypto.X509]]\n        self.http_01_resources = set() \\\n        # type: Set[acme_standalone.HTTP01RequestHandler.HTTP01Resource]\n\n        self.servers = ServerManager(self.certs, self.http_01_resources)\n\n    @classmethod\n    def add_parser_arguments(cls, add):\n        pass  # No additional argument for the standalone plugin parser\n\n    def more_info(self):  # pylint: disable=missing-function-docstring\n        return(\"This authenticator creates its own ephemeral TCP listener \"\n               \"on the necessary port in order to respond to incoming \"\n               \"http-01 challenges from the certificate authority. Therefore, \"\n               \"it does not rely on any existing server program.\")\n\n    def prepare(self):  # pylint: disable=missing-function-docstring\n        pass\n\n    def get_chall_pref(self, domain):\n        # pylint: disable=unused-argument,missing-function-docstring\n        return [challenges.HTTP01]\n\n    def perform(self, achalls):  # pylint: disable=missing-function-docstring\n        return [self._try_perform_single(achall) for achall in achalls]\n\n    def _try_perform_single(self, achall):\n        while True:\n            try:\n                return self._perform_single(achall)\n            except errors.StandaloneBindError as error:\n                _handle_perform_error(error)\n\n    def _perform_single(self, achall):\n        servers, response = self._perform_http_01(achall)\n        self.served[servers].add(achall)\n        return response\n\n    def _perform_http_01(self, achall):\n        port = self.config.http01_port\n        addr = self.config.http01_address\n        servers = self.servers.run(port, challenges.HTTP01, listenaddr=addr)\n        response, validation = achall.response_and_validation()\n        resource = acme_standalone.HTTP01RequestHandler.HTTP01Resource(\n            chall=achall.chall, response=response, validation=validation)\n        self.http_01_resources.add(resource)\n        return servers, response\n\n    def cleanup(self, achalls):  # pylint: disable=missing-function-docstring\n        # reduce self.served and close servers if no challenges are served\n        for unused_servers, server_achalls in self.served.items():\n            for achall in achalls:\n                if achall in server_achalls:\n                    server_achalls.remove(achall)\n        for port, servers in six.iteritems(self.servers.running()):\n            if not self.served[servers]:\n                self.servers.stop(port)\n\n\ndef _handle_perform_error(error):\n    if error.socket_error.errno == socket_errors.EACCES:\n        raise errors.PluginError(\n            \"Could not bind TCP port {0} because you don't have \"\n            \"the appropriate permissions (for example, you \"\n            \"aren't running this program as \"\n            \"root).\".format(error.port))\n    if error.socket_error.errno == socket_errors.EADDRINUSE:\n        display = zope.component.getUtility(interfaces.IDisplay)\n        msg = (\n            \"Could not bind TCP port {0} because it is already in \"\n            \"use by another process on this system (such as a web \"\n            \"server). Please stop the program in question and \"\n            \"then try again.\".format(error.port))\n        should_retry = display.yesno(msg, \"Retry\",\n                                     \"Cancel\", default=False)\n        if not should_retry:\n            raise errors.PluginError(msg)\n    else:\n        raise error\n"
  },
  {
    "path": "certbot/_internal/plugins/webroot.py",
    "content": "\"\"\"Webroot plugin.\"\"\"\nimport argparse\nimport collections\nimport json\nimport logging\n\nimport six\nimport zope.component\nimport zope.interface\n\nfrom acme import challenges\nfrom acme.magic_typing import DefaultDict\nfrom acme.magic_typing import Dict\nfrom acme.magic_typing import List\nfrom acme.magic_typing import Set\nfrom certbot import achallenges  # pylint: disable=unused-import\nfrom certbot import errors\nfrom certbot import interfaces\nfrom certbot._internal import cli\nfrom certbot.compat import filesystem\nfrom certbot.compat import os\nfrom certbot.display import ops\nfrom certbot.display import util as display_util\nfrom certbot.plugins import common\nfrom certbot.plugins import util\nfrom certbot.util import safe_open\n\nlogger = logging.getLogger(__name__)\n\n\n@zope.interface.implementer(interfaces.IAuthenticator)\n@zope.interface.provider(interfaces.IPluginFactory)\nclass Authenticator(common.Plugin):\n    \"\"\"Webroot Authenticator.\"\"\"\n\n    description = \"Place files in webroot directory\"\n\n    MORE_INFO = \"\"\"\\\nAuthenticator plugin that performs http-01 challenge by saving\nnecessary validation resources to appropriate paths on the file\nsystem. It expects that there is some other HTTP server configured\nto serve all files under specified web root ({0}).\"\"\"\n\n    def more_info(self):  # pylint: disable=missing-function-docstring\n        return self.MORE_INFO.format(self.conf(\"path\"))\n\n    @classmethod\n    def add_parser_arguments(cls, add):\n        add(\"path\", \"-w\", default=[], action=_WebrootPathAction,\n            help=\"public_html / webroot path. This can be specified multiple \"\n                 \"times to handle different domains; each domain will have \"\n                 \"the webroot path that preceded it.  For instance: `-w \"\n                 \"/var/www/example -d example.com -d www.example.com -w \"\n                 \"/var/www/thing -d thing.net -d m.thing.net` (default: Ask)\")\n        add(\"map\", default={}, action=_WebrootMapAction,\n            help=\"JSON dictionary mapping domains to webroot paths; this \"\n                 \"implies -d for each entry. You may need to escape this from \"\n                 \"your shell. E.g.: --webroot-map \"\n                 '\\'{\"eg1.is,m.eg1.is\":\"/www/eg1/\", \"eg2.is\":\"/www/eg2\"}\\' '\n                 \"This option is merged with, but takes precedence over, -w / \"\n                 \"-d entries. At present, if you put webroot-map in a config \"\n                 \"file, it needs to be on a single line, like: webroot-map = \"\n                 '{\"example.com\":\"/var/www\"}.')\n\n    def get_chall_pref(self, domain):  # pragma: no cover\n        # pylint: disable=unused-argument,missing-function-docstring\n        return [challenges.HTTP01]\n\n    def __init__(self, *args, **kwargs):\n        super(Authenticator, self).__init__(*args, **kwargs)\n        self.full_roots = {}  # type: Dict[str, str]\n        self.performed = collections.defaultdict(set) \\\n            # type: DefaultDict[str, Set[achallenges.KeyAuthorizationAnnotatedChallenge]]\n        # stack of dirs successfully created by this authenticator\n        self._created_dirs = []  # type: List[str]\n\n    def prepare(self):  # pylint: disable=missing-function-docstring\n        pass\n\n    def perform(self, achalls):  # pylint: disable=missing-function-docstring\n        self._set_webroots(achalls)\n\n        self._create_challenge_dirs()\n\n        return [self._perform_single(achall) for achall in achalls]\n\n    def _set_webroots(self, achalls):\n        if self.conf(\"path\"):\n            webroot_path = self.conf(\"path\")[-1]\n            logger.info(\"Using the webroot path %s for all unmatched domains.\",\n                        webroot_path)\n            for achall in achalls:\n                self.conf(\"map\").setdefault(achall.domain, webroot_path)\n        else:\n            known_webroots = list(set(six.itervalues(self.conf(\"map\"))))\n            for achall in achalls:\n                if achall.domain not in self.conf(\"map\"):\n                    new_webroot = self._prompt_for_webroot(achall.domain,\n                                                           known_webroots)\n                    # Put the most recently input\n                    # webroot first for easy selection\n                    try:\n                        known_webroots.remove(new_webroot)\n                    except ValueError:\n                        pass\n                    known_webroots.insert(0, new_webroot)\n                    self.conf(\"map\")[achall.domain] = new_webroot\n\n    def _prompt_for_webroot(self, domain, known_webroots):\n        webroot = None\n\n        while webroot is None:\n            if known_webroots:\n                # Only show the menu if we have options for it\n                webroot = self._prompt_with_webroot_list(domain, known_webroots)\n                if webroot is None:\n                    webroot = self._prompt_for_new_webroot(domain)\n            else:\n                # Allow prompt to raise PluginError instead of looping forever\n                webroot = self._prompt_for_new_webroot(domain, True)\n\n        return webroot\n\n    def _prompt_with_webroot_list(self, domain, known_webroots):\n        display = zope.component.getUtility(interfaces.IDisplay)\n        path_flag = \"--\" + self.option_name(\"path\")\n\n        while True:\n            code, index = display.menu(\n                \"Select the webroot for {0}:\".format(domain),\n                [\"Enter a new webroot\"] + known_webroots,\n                cli_flag=path_flag, force_interactive=True)\n            if code == display_util.CANCEL:\n                raise errors.PluginError(\n                    \"Every requested domain must have a \"\n                    \"webroot when using the webroot plugin.\")\n            return None if index == 0 else known_webroots[index - 1]  # code == display_util.OK\n\n    def _prompt_for_new_webroot(self, domain, allowraise=False):  # pylint: no-self-use\n        code, webroot = ops.validated_directory(\n            _validate_webroot,\n            \"Input the webroot for {0}:\".format(domain),\n            force_interactive=True)\n        if code == display_util.CANCEL:\n            if not allowraise:\n                return None\n            raise errors.PluginError(\n                \"Every requested domain must have a \"\n                \"webroot when using the webroot plugin.\")\n        return _validate_webroot(webroot)  # code == display_util.OK\n\n    def _create_challenge_dirs(self):\n        path_map = self.conf(\"map\")\n        if not path_map:\n            raise errors.PluginError(\n                \"Missing parts of webroot configuration; please set either \"\n                \"--webroot-path and --domains, or --webroot-map. Run with \"\n                \" --help webroot for examples.\")\n        for name, path in path_map.items():\n            self.full_roots[name] = os.path.join(path, challenges.HTTP01.URI_ROOT_PATH)\n            logger.debug(\"Creating root challenges validation dir at %s\",\n                         self.full_roots[name])\n\n            # Change the permissions to be writable (GH #1389)\n            # Umask is used instead of chmod to ensure the client can also\n            # run as non-root (GH #1795)\n            old_umask = os.umask(0o022)\n            try:\n                # We ignore the last prefix in the next iteration,\n                # as it does not correspond to a folder path ('/' or 'C:')\n                for prefix in sorted(util.get_prefixes(self.full_roots[name])[:-1], key=len):\n                    if os.path.isdir(prefix):\n                        # Don't try to create directory if it already exists, as some filesystems\n                        # won't reliably raise EEXIST or EISDIR if directory exists.\n                        continue\n                    try:\n                        # Set owner as parent directory if possible, apply mode for Linux/Windows.\n                        # For Linux, this is coupled with the \"umask\" call above because\n                        # os.mkdir's \"mode\" parameter may not always work:\n                        # https://docs.python.org/3/library/os.html#os.mkdir\n                        filesystem.mkdir(prefix, 0o755)\n                        self._created_dirs.append(prefix)\n                        try:\n                            filesystem.copy_ownership_and_apply_mode(\n                                path, prefix, 0o755, copy_user=True, copy_group=True)\n                        except (OSError, AttributeError) as exception:\n                            logger.info(\"Unable to change owner and uid of webroot directory\")\n                            logger.debug(\"Error was: %s\", exception)\n                    except OSError as exception:\n                        raise errors.PluginError(\n                            \"Couldn't create root for {0} http-01 \"\n                            \"challenge responses: {1}\".format(name, exception))\n            finally:\n                os.umask(old_umask)\n\n    def _get_validation_path(self, root_path, achall):  # pylint: no-self-use\n        return os.path.join(root_path, achall.chall.encode(\"token\"))\n\n    def _perform_single(self, achall):\n        response, validation = achall.response_and_validation()\n\n        root_path = self.full_roots[achall.domain]\n        validation_path = self._get_validation_path(root_path, achall)\n        logger.debug(\"Attempting to save validation to %s\", validation_path)\n\n        # Change permissions to be world-readable, owner-writable (GH #1795)\n        old_umask = os.umask(0o022)\n\n        try:\n            with safe_open(validation_path, mode=\"wb\", chmod=0o644) as validation_file:\n                validation_file.write(validation.encode())\n        finally:\n            os.umask(old_umask)\n\n        self.performed[root_path].add(achall)\n        return response\n\n    def cleanup(self, achalls):  # pylint: disable=missing-function-docstring\n        for achall in achalls:\n            root_path = self.full_roots.get(achall.domain, None)\n            if root_path is not None:\n                validation_path = self._get_validation_path(root_path, achall)\n                logger.debug(\"Removing %s\", validation_path)\n                os.remove(validation_path)\n                self.performed[root_path].remove(achall)\n\n        not_removed = []  # type: List[str]\n        while self._created_dirs:\n            path = self._created_dirs.pop()\n            try:\n                os.rmdir(path)\n            except OSError as exc:\n                not_removed.insert(0, path)\n                logger.info(\"Challenge directory %s was not empty, didn't remove\", path)\n                logger.debug(\"Error was: %s\", exc)\n        self._created_dirs = not_removed\n        logger.debug(\"All challenges cleaned up\")\n\n\nclass _WebrootMapAction(argparse.Action):\n    \"\"\"Action class for parsing webroot_map.\"\"\"\n\n    def __call__(self, parser, namespace, webroot_map, option_string=None):\n        for domains, webroot_path in six.iteritems(json.loads(webroot_map)):\n            webroot_path = _validate_webroot(webroot_path)\n            namespace.webroot_map.update(\n                (d, webroot_path) for d in cli.add_domains(namespace, domains))\n\n\nclass _WebrootPathAction(argparse.Action):\n    \"\"\"Action class for parsing webroot_path.\"\"\"\n\n    def __init__(self, *args, **kwargs):\n        super(_WebrootPathAction, self).__init__(*args, **kwargs)\n        self._domain_before_webroot = False\n\n    def __call__(self, parser, namespace, webroot_path, option_string=None):\n        if self._domain_before_webroot:\n            raise errors.PluginError(\n                \"If you specify multiple webroot paths, \"\n                \"one of them must precede all domain flags\")\n\n        if namespace.webroot_path:\n            # Apply previous webroot to all matched\n            # domains before setting the new webroot path\n            prev_webroot = namespace.webroot_path[-1]\n            for domain in namespace.domains:\n                namespace.webroot_map.setdefault(domain, prev_webroot)\n        elif namespace.domains:\n            self._domain_before_webroot = True\n\n        namespace.webroot_path.append(_validate_webroot(webroot_path))\n\n\ndef _validate_webroot(webroot_path):\n    \"\"\"Validates and returns the absolute path of webroot_path.\n\n    :param str webroot_path: path to the webroot directory\n\n    :returns: absolute path of webroot_path\n    :rtype: str\n\n    \"\"\"\n    if not os.path.isdir(webroot_path):\n        raise errors.PluginError(webroot_path + \" does not exist or is not a directory\")\n\n    return os.path.abspath(webroot_path)\n"
  },
  {
    "path": "certbot/_internal/renewal.py",
    "content": "\"\"\"Functionality for autorenewal and associated juggling of configurations\"\"\"\nfrom __future__ import print_function\n\nimport copy\nimport itertools\nimport logging\nimport random\nimport sys\nimport time\nimport traceback\n\nimport OpenSSL\nimport six\nimport zope.component\n\nfrom acme.magic_typing import List\nfrom certbot import crypto_util\nfrom certbot import errors\nfrom certbot import interfaces\nfrom certbot import util\nfrom certbot._internal import cli\nfrom certbot._internal import hooks\nfrom certbot._internal import storage\nfrom certbot._internal import updater\nfrom certbot._internal.plugins import disco as plugins_disco\nfrom certbot.compat import os\n\nlogger = logging.getLogger(__name__)\n\n# These are the items which get pulled out of a renewal configuration\n# file's renewalparams and actually used in the client configuration\n# during the renewal process. We have to record their types here because\n# the renewal configuration process loses this information.\nSTR_CONFIG_ITEMS = [\"config_dir\", \"logs_dir\", \"work_dir\", \"user_agent\",\n                    \"server\", \"account\", \"authenticator\", \"installer\",\n                    \"renew_hook\", \"pre_hook\", \"post_hook\", \"http01_address\"]\nINT_CONFIG_ITEMS = [\"rsa_key_size\", \"http01_port\"]\nBOOL_CONFIG_ITEMS = [\"must_staple\", \"allow_subset_of_names\", \"reuse_key\",\n                     \"autorenew\"]\n\nCONFIG_ITEMS = set(itertools.chain(\n    BOOL_CONFIG_ITEMS, INT_CONFIG_ITEMS, STR_CONFIG_ITEMS, ('pref_challs',)))\n\n\ndef _reconstitute(config, full_path):\n    \"\"\"Try to instantiate a RenewableCert, updating config with relevant items.\n\n    This is specifically for use in renewal and enforces several checks\n    and policies to ensure that we can try to proceed with the renewal\n    request. The config argument is modified by including relevant options\n    read from the renewal configuration file.\n\n    :param configuration.NamespaceConfig config: configuration for the\n        current lineage\n    :param str full_path: Absolute path to the configuration file that\n        defines this lineage\n\n    :returns: the RenewableCert object or None if a fatal error occurred\n    :rtype: `storage.RenewableCert` or NoneType\n\n    \"\"\"\n    try:\n        renewal_candidate = storage.RenewableCert(full_path, config)\n    except (errors.CertStorageError, IOError):\n        logger.warning(\"\", exc_info=True)\n        logger.warning(\"Renewal configuration file %s is broken. Skipping.\", full_path)\n        logger.debug(\"Traceback was:\\n%s\", traceback.format_exc())\n        return None\n    if \"renewalparams\" not in renewal_candidate.configuration:\n        logger.warning(\"Renewal configuration file %s lacks \"\n                       \"renewalparams. Skipping.\", full_path)\n        return None\n    renewalparams = renewal_candidate.configuration[\"renewalparams\"]\n    if \"authenticator\" not in renewalparams:\n        logger.warning(\"Renewal configuration file %s does not specify \"\n                       \"an authenticator. Skipping.\", full_path)\n        return None\n    # Now restore specific values along with their data types, if\n    # those elements are present.\n    try:\n        restore_required_config_elements(config, renewalparams)\n        _restore_plugin_configs(config, renewalparams)\n    except (ValueError, errors.Error) as error:\n        logger.warning(\n            \"An error occurred while parsing %s. The error was %s. \"\n            \"Skipping the file.\", full_path, str(error))\n        logger.debug(\"Traceback was:\\n%s\", traceback.format_exc())\n        return None\n\n    try:\n        config.domains = [util.enforce_domain_sanity(d)\n                          for d in renewal_candidate.names()]\n    except errors.ConfigurationError as error:\n        logger.warning(\"Renewal configuration file %s references a cert \"\n                       \"that contains an invalid domain name. The problem \"\n                       \"was: %s. Skipping.\", full_path, error)\n        return None\n\n    return renewal_candidate\n\n\ndef _restore_webroot_config(config, renewalparams):\n    \"\"\"\n    webroot_map is, uniquely, a dict, and the general-purpose configuration\n    restoring logic is not able to correctly parse it from the serialized\n    form.\n    \"\"\"\n    if \"webroot_map\" in renewalparams and not cli.set_by_cli(\"webroot_map\"):\n        config.webroot_map = renewalparams[\"webroot_map\"]\n    # To understand why webroot_path and webroot_map processing are not mutually exclusive,\n    # see https://github.com/certbot/certbot/pull/7095\n    if \"webroot_path\" in renewalparams and not cli.set_by_cli(\"webroot_path\"):\n        wp = renewalparams[\"webroot_path\"]\n        if isinstance(wp, six.string_types):  # prior to 0.1.0, webroot_path was a string\n            wp = [wp]\n        config.webroot_path = wp\n\n\ndef _restore_plugin_configs(config, renewalparams):\n    \"\"\"Sets plugin specific values in config from renewalparams\n\n    :param configuration.NamespaceConfig config: configuration for the\n        current lineage\n    :param configobj.Section renewalparams: Parameters from the renewal\n        configuration file that defines this lineage\n\n    \"\"\"\n    # Now use parser to get plugin-prefixed items with correct types\n    # XXX: the current approach of extracting only prefixed items\n    #      related to the actually-used installer and authenticator\n    #      works as long as plugins don't need to read plugin-specific\n    #      variables set by someone else (e.g., assuming Apache\n    #      configurator doesn't need to read webroot_ variables).\n    # Note: if a parameter that used to be defined in the parser is no\n    #      longer defined, stored copies of that parameter will be\n    #      deserialized as strings by this logic even if they were\n    #      originally meant to be some other type.\n    plugin_prefixes = []  # type: List[str]\n    if renewalparams[\"authenticator\"] == \"webroot\":\n        _restore_webroot_config(config, renewalparams)\n    else:\n        plugin_prefixes.append(renewalparams[\"authenticator\"])\n\n    if renewalparams.get(\"installer\") is not None:\n        plugin_prefixes.append(renewalparams[\"installer\"])\n\n    for plugin_prefix in set(plugin_prefixes):\n        plugin_prefix = plugin_prefix.replace('-', '_')\n        for config_item, config_value in six.iteritems(renewalparams):\n            if config_item.startswith(plugin_prefix + \"_\") and not cli.set_by_cli(config_item):\n                # Values None, True, and False need to be treated specially,\n                # As their types aren't handled correctly by configobj\n                if config_value in (\"None\", \"True\", \"False\"):\n                    # bool(\"False\") == True\n                    # pylint: disable=eval-used\n                    setattr(config, config_item, eval(config_value))\n                else:\n                    cast = cli.argparse_type(config_item)\n                    setattr(config, config_item, cast(config_value))\n\n\ndef restore_required_config_elements(config, renewalparams):\n    \"\"\"Sets non-plugin specific values in config from renewalparams\n\n    :param configuration.NamespaceConfig config: configuration for the\n        current lineage\n    :param configobj.Section renewalparams: parameters from the renewal\n        configuration file that defines this lineage\n\n    \"\"\"\n\n    required_items = itertools.chain(\n        ((\"pref_challs\", _restore_pref_challs),),\n        six.moves.zip(BOOL_CONFIG_ITEMS, itertools.repeat(_restore_bool)),\n        six.moves.zip(INT_CONFIG_ITEMS, itertools.repeat(_restore_int)),\n        six.moves.zip(STR_CONFIG_ITEMS, itertools.repeat(_restore_str)))\n    for item_name, restore_func in required_items:\n        if item_name in renewalparams and not cli.set_by_cli(item_name):\n            value = restore_func(item_name, renewalparams[item_name])\n            setattr(config, item_name, value)\n\n\ndef _restore_pref_challs(unused_name, value):\n    \"\"\"Restores preferred challenges from a renewal config file.\n\n    If value is a `str`, it should be a single challenge type.\n\n    :param str unused_name: option name\n    :param value: option value\n    :type value: `list` of `str` or `str`\n\n    :returns: converted option value to be stored in the runtime config\n    :rtype: `list` of `str`\n\n    :raises errors.Error: if value can't be converted to a bool\n\n    \"\"\"\n    # If pref_challs has only one element, configobj saves the value\n    # with a trailing comma so it's parsed as a list. If this comma is\n    # removed by the user, the value is parsed as a str.\n    value = [value] if isinstance(value, six.string_types) else value\n    return cli.parse_preferred_challenges(value)\n\n\ndef _restore_bool(name, value):\n    \"\"\"Restores a boolean key-value pair from a renewal config file.\n\n    :param str name: option name\n    :param str value: option value\n\n    :returns: converted option value to be stored in the runtime config\n    :rtype: bool\n\n    :raises errors.Error: if value can't be converted to a bool\n\n    \"\"\"\n    lowercase_value = value.lower()\n    if lowercase_value not in (\"true\", \"false\"):\n        raise errors.Error(\n            \"Expected True or False for {0} but found {1}\".format(name, value))\n    return lowercase_value == \"true\"\n\n\ndef _restore_int(name, value):\n    \"\"\"Restores an integer key-value pair from a renewal config file.\n\n    :param str name: option name\n    :param str value: option value\n\n    :returns: converted option value to be stored in the runtime config\n    :rtype: int\n\n    :raises errors.Error: if value can't be converted to an int\n\n    \"\"\"\n    if name == \"http01_port\" and value == \"None\":\n        logger.info(\"updating legacy http01_port value\")\n        return cli.flag_default(\"http01_port\")\n\n    try:\n        return int(value)\n    except ValueError:\n        raise errors.Error(\"Expected a numeric value for {0}\".format(name))\n\n\ndef _restore_str(unused_name, value):\n    \"\"\"Restores a string key-value pair from a renewal config file.\n\n    :param str unused_name: option name\n    :param str value: option value\n\n    :returns: converted option value to be stored in the runtime config\n    :rtype: str or None\n\n    \"\"\"\n    return None if value == \"None\" else value\n\n\ndef should_renew(config, lineage):\n    \"Return true if any of the circumstances for automatic renewal apply.\"\n    if config.renew_by_default:\n        logger.debug(\"Auto-renewal forced with --force-renewal...\")\n        return True\n    if lineage.should_autorenew():\n        logger.info(\"Cert is due for renewal, auto-renewing...\")\n        return True\n    if config.dry_run:\n        logger.info(\"Cert not due for renewal, but simulating renewal for dry run\")\n        return True\n    logger.info(\"Cert not yet due for renewal\")\n    return False\n\n\ndef _avoid_invalidating_lineage(config, lineage, original_server):\n    \"Do not renew a valid cert with one from a staging server!\"\n    # Some lineages may have begun with --staging, but then had production certs\n    # added to them\n    with open(lineage.cert) as the_file:\n        contents = the_file.read()\n    latest_cert = OpenSSL.crypto.load_certificate(\n        OpenSSL.crypto.FILETYPE_PEM, contents)\n    # all our test certs are from happy hacker fake CA, though maybe one day\n    # we should test more methodically\n    now_valid = \"fake\" not in repr(latest_cert.get_issuer()).lower()\n\n    if util.is_staging(config.server):\n        if not util.is_staging(original_server) or now_valid:\n            if not config.break_my_certs:\n                names = \", \".join(lineage.names())\n                raise errors.Error(\n                    \"You've asked to renew/replace a seemingly valid certificate with \"\n                    \"a test certificate (domains: {0}). We will not do that \"\n                    \"unless you use the --break-my-certs flag!\".format(names))\n\n\ndef renew_cert(config, domains, le_client, lineage):\n    \"Renew a certificate lineage.\"\n    renewal_params = lineage.configuration[\"renewalparams\"]\n    original_server = renewal_params.get(\"server\", cli.flag_default(\"server\"))\n    _avoid_invalidating_lineage(config, lineage, original_server)\n    if not domains:\n        domains = lineage.names()\n    # The private key is the existing lineage private key if reuse_key is set.\n    # Otherwise, generate a fresh private key by passing None.\n    new_key = os.path.normpath(lineage.privkey) if config.reuse_key else None\n    new_cert, new_chain, new_key, _ = le_client.obtain_certificate(domains, new_key)\n    if config.dry_run:\n        logger.debug(\"Dry run: skipping updating lineage at %s\",\n                    os.path.dirname(lineage.cert))\n    else:\n        prior_version = lineage.latest_common_version()\n        # TODO: Check return value of save_successor\n        lineage.save_successor(prior_version, new_cert, new_key.pem, new_chain, config)\n        lineage.update_all_links_to(lineage.latest_common_version())\n\n    hooks.renew_hook(config, domains, lineage.live_dir)\n\n\ndef report(msgs, category):\n    \"Format a results report for a category of renewal outcomes\"\n    lines = (\"%s (%s)\" % (m, category) for m in msgs)\n    return \"  \" + \"\\n  \".join(lines)\n\ndef _renew_describe_results(config, renew_successes, renew_failures,\n                            renew_skipped, parse_failures):\n\n    out = []  # type: List[str]\n    notify = out.append\n    disp = zope.component.getUtility(interfaces.IDisplay)\n\n    def notify_error(err):\n        \"\"\"Notify and log errors.\"\"\"\n        notify(str(err))\n        logger.error(err)\n\n    if config.dry_run:\n        notify(\"** DRY RUN: simulating 'certbot renew' close to cert expiry\")\n        notify(\"**          (The test certificates below have not been saved.)\")\n    notify(\"\")\n    if renew_skipped:\n        notify(\"The following certs are not due for renewal yet:\")\n        notify(report(renew_skipped, \"skipped\"))\n    if not renew_successes and not renew_failures:\n        notify(\"No renewals were attempted.\")\n        if (config.pre_hook is not None or\n                config.renew_hook is not None or config.post_hook is not None):\n            notify(\"No hooks were run.\")\n    elif renew_successes and not renew_failures:\n        notify(\"Congratulations, all renewals succeeded. The following certs \"\n               \"have been renewed:\")\n        notify(report(renew_successes, \"success\"))\n    elif renew_failures and not renew_successes:\n        notify_error(\"All renewal attempts failed. The following certs could \"\n               \"not be renewed:\")\n        notify_error(report(renew_failures, \"failure\"))\n    elif renew_failures and renew_successes:\n        notify(\"The following certs were successfully renewed:\")\n        notify(report(renew_successes, \"success\") + \"\\n\")\n        notify_error(\"The following certs could not be renewed:\")\n        notify_error(report(renew_failures, \"failure\"))\n\n    if parse_failures:\n        notify(\"\\nAdditionally, the following renewal configurations \"\n               \"were invalid: \")\n        notify(report(parse_failures, \"parsefail\"))\n\n    if config.dry_run:\n        notify(\"** DRY RUN: simulating 'certbot renew' close to cert expiry\")\n        notify(\"**          (The test certificates above have not been saved.)\")\n\n    disp.notification(\"\\n\".join(out), wrap=False)\n\n\ndef handle_renewal_request(config):\n    \"\"\"Examine each lineage; renew if due and report results\"\"\"\n\n    # This is trivially False if config.domains is empty\n    if any(domain not in config.webroot_map for domain in config.domains):\n        # If more plugins start using cli.add_domains,\n        # we may want to only log a warning here\n        raise errors.Error(\"Currently, the renew verb is capable of either \"\n                           \"renewing all installed certificates that are due \"\n                           \"to be renewed or renewing a single certificate specified \"\n                           \"by its name. If you would like to renew specific \"\n                           \"certificates by their domains, use the certonly command \"\n                           \"instead. The renew verb may provide other options \"\n                           \"for selecting certificates to renew in the future.\")\n\n    if config.certname:\n        conf_files = [storage.renewal_file_for_certname(config, config.certname)]\n    else:\n        conf_files = storage.renewal_conf_files(config)\n\n    renew_successes = []\n    renew_failures = []\n    renew_skipped = []\n    parse_failures = []\n\n    # Noninteractive renewals include a random delay in order to spread\n    # out the load on the certificate authority servers, even if many\n    # users all pick the same time for renewals.  This delay precedes\n    # running any hooks, so that side effects of the hooks (such as\n    # shutting down a web service) aren't prolonged unnecessarily.\n    apply_random_sleep = not sys.stdin.isatty() and config.random_sleep_on_renew\n\n    for renewal_file in conf_files:\n        disp = zope.component.getUtility(interfaces.IDisplay)\n        disp.notification(\"Processing \" + renewal_file, pause=False)\n        lineage_config = copy.deepcopy(config)\n        lineagename = storage.lineagename_for_filename(renewal_file)\n\n        # Note that this modifies config (to add back the configuration\n        # elements from within the renewal configuration file).\n        try:\n            renewal_candidate = _reconstitute(lineage_config, renewal_file)\n        except Exception as e:  # pylint: disable=broad-except\n            logger.warning(\"Renewal configuration file %s (cert: %s) \"\n                           \"produced an unexpected error: %s. Skipping.\",\n                           renewal_file, lineagename, e)\n            logger.debug(\"Traceback was:\\n%s\", traceback.format_exc())\n            parse_failures.append(renewal_file)\n            continue\n\n        try:\n            if renewal_candidate is None:\n                parse_failures.append(renewal_file)\n            else:\n                # XXX: ensure that each call here replaces the previous one\n                zope.component.provideUtility(lineage_config)\n                renewal_candidate.ensure_deployed()\n                from certbot._internal import main\n                plugins = plugins_disco.PluginsRegistry.find_all()\n                if should_renew(lineage_config, renewal_candidate):\n                    # Apply random sleep upon first renewal if needed\n                    if apply_random_sleep:\n                        sleep_time = random.uniform(1, 60 * 8)\n                        logger.info(\"Non-interactive renewal: random delay of %s seconds\",\n                                    sleep_time)\n                        time.sleep(sleep_time)\n                        # We will sleep only once this day, folks.\n                        apply_random_sleep = False\n\n                    # domains have been restored into lineage_config by reconstitute\n                    # but they're unnecessary anyway because renew_cert here\n                    # will just grab them from the certificate\n                    # we already know it's time to renew based on should_renew\n                    # and we have a lineage in renewal_candidate\n                    main.renew_cert(lineage_config, plugins, renewal_candidate)\n                    renew_successes.append(renewal_candidate.fullchain)\n                else:\n                    expiry = crypto_util.notAfter(renewal_candidate.version(\n                        \"cert\", renewal_candidate.latest_common_version()))\n                    renew_skipped.append(\"%s expires on %s\" % (renewal_candidate.fullchain,\n                                         expiry.strftime(\"%Y-%m-%d\")))\n                # Run updater interface methods\n                updater.run_generic_updaters(lineage_config, renewal_candidate,\n                                             plugins)\n\n        except Exception as e:  # pylint: disable=broad-except\n            # obtain_cert (presumably) encountered an unanticipated problem.\n            logger.warning(\"Attempting to renew cert (%s) from %s produced an \"\n                           \"unexpected error: %s. Skipping.\", lineagename,\n                               renewal_file, e)\n            logger.debug(\"Traceback was:\\n%s\", traceback.format_exc())\n            renew_failures.append(renewal_candidate.fullchain)\n\n    # Describe all the results\n    _renew_describe_results(config, renew_successes, renew_failures,\n                            renew_skipped, parse_failures)\n\n    if renew_failures or parse_failures:\n        raise errors.Error(\"{0} renew failure(s), {1} parse failure(s)\".format(\n            len(renew_failures), len(parse_failures)))\n\n    # Windows installer integration tests rely on handle_renewal_request behavior here.\n    # If the text below changes, these tests will need to be updated accordingly.\n    logger.debug(\"no renewal failures\")\n"
  },
  {
    "path": "certbot/_internal/reporter.py",
    "content": "\"\"\"Collects and displays information to the user.\"\"\"\nfrom __future__ import print_function\n\nimport collections\nimport logging\nimport sys\nimport textwrap\n\nfrom six.moves import queue  # type: ignore\nimport zope.interface\n\nfrom certbot import interfaces\nfrom certbot import util\n\nlogger = logging.getLogger(__name__)\n\n\n@zope.interface.implementer(interfaces.IReporter)\nclass Reporter(object):\n    \"\"\"Collects and displays information to the user.\n\n    :ivar `queue.PriorityQueue` messages: Messages to be displayed to\n        the user.\n\n    \"\"\"\n\n    HIGH_PRIORITY = 0\n    \"\"\"High priority constant. See `add_message`.\"\"\"\n    MEDIUM_PRIORITY = 1\n    \"\"\"Medium priority constant. See `add_message`.\"\"\"\n    LOW_PRIORITY = 2\n    \"\"\"Low priority constant. See `add_message`.\"\"\"\n\n    _msg_type = collections.namedtuple('ReporterMsg', 'priority text on_crash')\n\n    def __init__(self, config):\n        self.messages = queue.PriorityQueue()\n        self.config = config\n\n    def add_message(self, msg, priority, on_crash=True):\n        \"\"\"Adds msg to the list of messages to be printed.\n\n        :param str msg: Message to be displayed to the user.\n\n        :param int priority: One of `HIGH_PRIORITY`, `MEDIUM_PRIORITY`,\n            or `LOW_PRIORITY`.\n\n        :param bool on_crash: Whether or not the message should be\n            printed if the program exits abnormally.\n\n        \"\"\"\n        assert self.HIGH_PRIORITY <= priority <= self.LOW_PRIORITY\n        self.messages.put(self._msg_type(priority, msg, on_crash))\n        logger.debug(\"Reporting to user: %s\", msg)\n\n    def print_messages(self):\n        \"\"\"Prints messages to the user and clears the message queue.\n\n        If there is an unhandled exception, only messages for which\n        ``on_crash`` is ``True`` are printed.\n\n        \"\"\"\n        bold_on = False\n        if not self.messages.empty():\n            no_exception = sys.exc_info()[0] is None\n            bold_on = sys.stdout.isatty()\n            if not self.config.quiet:\n                if bold_on:\n                    print(util.ANSI_SGR_BOLD)\n                print('IMPORTANT NOTES:')\n            first_wrapper = textwrap.TextWrapper(\n                initial_indent=' - ',\n                subsequent_indent=(' ' * 3),\n                break_long_words=False,\n                break_on_hyphens=False)\n            next_wrapper = textwrap.TextWrapper(\n                initial_indent=first_wrapper.subsequent_indent,\n                subsequent_indent=first_wrapper.subsequent_indent,\n                break_long_words=False,\n                break_on_hyphens=False)\n        while not self.messages.empty():\n            msg = self.messages.get()\n            if self.config.quiet:\n                # In --quiet mode, we only print high priority messages that\n                # are flagged for crash cases\n                if not (msg.priority == self.HIGH_PRIORITY and msg.on_crash):\n                    continue\n            if no_exception or msg.on_crash:\n                if bold_on and msg.priority > self.HIGH_PRIORITY:\n                    if not self.config.quiet:\n                        sys.stdout.write(util.ANSI_SGR_RESET)\n                        bold_on = False\n                lines = msg.text.splitlines()\n                print(first_wrapper.fill(lines[0]))\n                if len(lines) > 1:\n                    print(\"\\n\".join(\n                        next_wrapper.fill(line) for line in lines[1:]))\n        if bold_on and not self.config.quiet:\n            sys.stdout.write(util.ANSI_SGR_RESET)\n"
  },
  {
    "path": "certbot/_internal/storage.py",
    "content": "\"\"\"Renewable certificates storage.\"\"\"\nimport datetime\nimport glob\nimport logging\nimport re\nimport shutil\nimport stat\n\nimport configobj\nimport parsedatetime\nimport pytz\nimport six\n\nimport certbot\nfrom certbot import crypto_util\nfrom certbot import errors\nfrom certbot import interfaces\nfrom certbot import ocsp\nfrom certbot import util\nfrom certbot._internal import cli\nfrom certbot._internal import constants\nfrom certbot._internal import error_handler\nfrom certbot._internal.plugins import disco as plugins_disco\nfrom certbot.compat import filesystem\nfrom certbot.compat import os\nfrom certbot.plugins import common as plugins_common\n\nlogger = logging.getLogger(__name__)\n\nALL_FOUR = (\"cert\", \"privkey\", \"chain\", \"fullchain\")\nREADME = \"README\"\nCURRENT_VERSION = util.get_strict_version(certbot.__version__)\nBASE_PRIVKEY_MODE = 0o600\n\n\ndef renewal_conf_files(config):\n    \"\"\"Build a list of all renewal configuration files.\n\n    :param certbot.interfaces.IConfig config: Configuration object\n\n    :returns: list of renewal configuration files\n    :rtype: `list` of `str`\n\n    \"\"\"\n    result = glob.glob(os.path.join(config.renewal_configs_dir, \"*.conf\"))\n    result.sort()\n    return result\n\ndef renewal_file_for_certname(config, certname):\n    \"\"\"Return /path/to/certname.conf in the renewal conf directory\"\"\"\n    path = os.path.join(config.renewal_configs_dir, \"{0}.conf\".format(certname))\n    if not os.path.exists(path):\n        raise errors.CertStorageError(\"No certificate found with name {0} (expected \"\n            \"{1}).\".format(certname, path))\n    return path\n\n\ndef cert_path_for_cert_name(config, cert_name):\n    \"\"\" If `--cert-name` was specified, but you need a value for `--cert-path`.\n\n    :param `configuration.NamespaceConfig` config: parsed command line arguments\n    :param str cert_name: cert name.\n\n    \"\"\"\n    cert_name_implied_conf = renewal_file_for_certname(config, cert_name)\n    fullchain_path = configobj.ConfigObj(cert_name_implied_conf)[\"fullchain\"]\n    with open(fullchain_path) as f:\n        cert_path = (fullchain_path, f.read())\n    return cert_path\n\n\ndef config_with_defaults(config=None):\n    \"\"\"Merge supplied config, if provided, on top of builtin defaults.\"\"\"\n    defaults_copy = configobj.ConfigObj(constants.RENEWER_DEFAULTS)\n    defaults_copy.merge(config if config is not None else configobj.ConfigObj())\n    return defaults_copy\n\n\ndef add_time_interval(base_time, interval, textparser=parsedatetime.Calendar()):\n    \"\"\"Parse the time specified time interval, and add it to the base_time\n\n    The interval can be in the English-language format understood by\n    parsedatetime, e.g., '10 days', '3 weeks', '6 months', '9 hours', or\n    a sequence of such intervals like '6 months 1 week' or '3 days 12\n    hours'. If an integer is found with no associated unit, it is\n    interpreted by default as a number of days.\n\n    :param datetime.datetime base_time: The time to be added with the interval.\n    :param str interval: The time interval to parse.\n\n    :returns: The base_time plus the interpretation of the time interval.\n    :rtype: :class:`datetime.datetime`\"\"\"\n\n    if interval.strip().isdigit():\n        interval += \" days\"\n\n    # try to use the same timezone, but fallback to UTC\n    tzinfo = base_time.tzinfo or pytz.UTC\n\n    return textparser.parseDT(interval, base_time, tzinfo=tzinfo)[0]\n\n\ndef write_renewal_config(o_filename, n_filename, archive_dir, target, relevant_data):\n    \"\"\"Writes a renewal config file with the specified name and values.\n\n    :param str o_filename: Absolute path to the previous version of config file\n    :param str n_filename: Absolute path to the new destination of config file\n    :param str archive_dir: Absolute path to the archive directory\n    :param dict target: Maps ALL_FOUR to their symlink paths\n    :param dict relevant_data: Renewal configuration options to save\n\n    :returns: Configuration object for the new config file\n    :rtype: configobj.ConfigObj\n\n    \"\"\"\n    config = configobj.ConfigObj(o_filename)\n    config[\"version\"] = certbot.__version__\n    config[\"archive_dir\"] = archive_dir\n    for kind in ALL_FOUR:\n        config[kind] = target[kind]\n\n    if \"renewalparams\" not in config:\n        config[\"renewalparams\"] = {}\n        config.comments[\"renewalparams\"] = [\"\",\n                                            \"Options used in \"\n                                            \"the renewal process\"]\n\n    config[\"renewalparams\"].update(relevant_data)\n\n    for k in config[\"renewalparams\"].keys():\n        if k not in relevant_data:\n            del config[\"renewalparams\"][k]\n\n    if \"renew_before_expiry\" not in config:\n        default_interval = constants.RENEWER_DEFAULTS[\"renew_before_expiry\"]\n        config.initial_comment = [\"renew_before_expiry = \" + default_interval]\n\n    # TODO: add human-readable comments explaining other available\n    #       parameters\n    logger.debug(\"Writing new config %s.\", n_filename)\n\n    # Ensure that the file exists\n    open(n_filename, 'a').close()\n\n    # Copy permissions from the old version of the file, if it exists.\n    if os.path.exists(o_filename):\n        current_permissions = stat.S_IMODE(os.lstat(o_filename).st_mode)\n        filesystem.chmod(n_filename, current_permissions)\n\n    with open(n_filename, \"wb\") as f:\n        config.write(outfile=f)\n    return config\n\n\ndef rename_renewal_config(prev_name, new_name, cli_config):\n    \"\"\"Renames cli_config.certname's config to cli_config.new_certname.\n\n    :param .NamespaceConfig cli_config: parsed command line\n        arguments\n    \"\"\"\n    prev_filename = renewal_filename_for_lineagename(cli_config, prev_name)\n    new_filename = renewal_filename_for_lineagename(cli_config, new_name)\n    if os.path.exists(new_filename):\n        raise errors.ConfigurationError(\"The new certificate name \"\n            \"is already in use.\")\n    try:\n        filesystem.replace(prev_filename, new_filename)\n    except OSError:\n        raise errors.ConfigurationError(\"Please specify a valid filename \"\n            \"for the new certificate name.\")\n\n\ndef update_configuration(lineagename, archive_dir, target, cli_config):\n    \"\"\"Modifies lineagename's config to contain the specified values.\n\n    :param str lineagename: Name of the lineage being modified\n    :param str archive_dir: Absolute path to the archive directory\n    :param dict target: Maps ALL_FOUR to their symlink paths\n    :param .NamespaceConfig cli_config: parsed command line\n        arguments\n\n    :returns: Configuration object for the updated config file\n    :rtype: configobj.ConfigObj\n\n    \"\"\"\n    config_filename = renewal_filename_for_lineagename(cli_config, lineagename)\n    temp_filename = config_filename + \".new\"\n\n    # If an existing tempfile exists, delete it\n    if os.path.exists(temp_filename):\n        os.unlink(temp_filename)\n\n    # Save only the config items that are relevant to renewal\n    values = relevant_values(vars(cli_config.namespace))\n    write_renewal_config(config_filename, temp_filename, archive_dir, target, values)\n    filesystem.replace(temp_filename, config_filename)\n\n    return configobj.ConfigObj(config_filename)\n\n\ndef get_link_target(link):\n    \"\"\"Get an absolute path to the target of link.\n\n    :param str link: Path to a symbolic link\n\n    :returns: Absolute path to the target of link\n    :rtype: str\n\n    :raises .CertStorageError: If link does not exists.\n\n    \"\"\"\n    try:\n        target = os.readlink(link)\n    except OSError:\n        raise errors.CertStorageError(\n            \"Expected {0} to be a symlink\".format(link))\n\n    if not os.path.isabs(target):\n        target = os.path.join(os.path.dirname(link), target)\n    return os.path.abspath(target)\n\ndef _write_live_readme_to(readme_path, is_base_dir=False):\n    prefix = \"\"\n    if is_base_dir:\n        prefix = \"[cert name]/\"\n    with open(readme_path, \"w\") as f:\n        logger.debug(\"Writing README to %s.\", readme_path)\n        f.write(\"This directory contains your keys and certificates.\\n\\n\"\n                \"`{prefix}privkey.pem`  : the private key for your certificate.\\n\"\n                \"`{prefix}fullchain.pem`: the certificate file used in most server software.\\n\"\n                \"`{prefix}chain.pem`    : used for OCSP stapling in Nginx >=1.3.7.\\n\"\n                \"`{prefix}cert.pem`     : will break many server configurations, and \"\n                                    \"should not be used\\n\"\n                \"                 without reading further documentation (see link below).\\n\\n\"\n                \"WARNING: DO NOT MOVE OR RENAME THESE FILES!\\n\"\n                \"         Certbot expects these files to remain in this location in order\\n\"\n                \"         to function properly!\\n\\n\"\n                \"We recommend not moving these files. For more information, see the Certbot\\n\"\n                \"User Guide at https://certbot.eff.org/docs/using.html#where-are-my-\"\n                                    \"certificates.\\n\".format(prefix=prefix))\n\n\ndef _relevant(namespaces, option):\n    \"\"\"\n    Is this option one that could be restored for future renewal purposes?\n\n    :param namespaces: plugin namespaces for configuration options\n    :type namespaces: `list` of `str`\n    :param str option: the name of the option\n\n    :rtype: bool\n    \"\"\"\n    from certbot._internal import renewal\n\n    return (option in renewal.CONFIG_ITEMS or\n            any(option.startswith(namespace) for namespace in namespaces))\n\n\ndef relevant_values(all_values):\n    \"\"\"Return a new dict containing only items relevant for renewal.\n\n    :param dict all_values: The original values.\n\n    :returns: A new dictionary containing items that can be used in renewal.\n    :rtype dict:\n\n    \"\"\"\n    plugins = plugins_disco.PluginsRegistry.find_all()\n    namespaces = [plugins_common.dest_namespace(plugin) for plugin in plugins]\n\n    rv = dict(\n        (option, value)\n        for option, value in six.iteritems(all_values)\n        if _relevant(namespaces, option) and cli.option_was_set(option, value))\n    # We always save the server value to help with forward compatibility\n    # and behavioral consistency when versions of Certbot with different\n    # server defaults are used.\n    rv[\"server\"] = all_values[\"server\"]\n    return rv\n\ndef lineagename_for_filename(config_filename):\n    \"\"\"Returns the lineagename for a configuration filename.\n    \"\"\"\n    if not config_filename.endswith(\".conf\"):\n        raise errors.CertStorageError(\n            \"renewal config file name must end in .conf\")\n    return os.path.basename(config_filename[:-len(\".conf\")])\n\ndef renewal_filename_for_lineagename(config, lineagename):\n    \"\"\"Returns the lineagename for a configuration filename.\n    \"\"\"\n    return os.path.join(config.renewal_configs_dir, lineagename) + \".conf\"\n\ndef _relpath_from_file(archive_dir, from_file):\n    \"\"\"Path to a directory from a file\"\"\"\n    return os.path.relpath(archive_dir, os.path.dirname(from_file))\n\ndef full_archive_path(config_obj, cli_config, lineagename):\n    \"\"\"Returns the full archive path for a lineagename\n\n    Uses cli_config to determine archive path if not available from config_obj.\n\n    :param configobj.ConfigObj config_obj: Renewal conf file contents (can be None)\n    :param configuration.NamespaceConfig cli_config: Main config file\n    :param str lineagename: Certificate name\n    \"\"\"\n    if config_obj and \"archive_dir\" in config_obj:\n        return config_obj[\"archive_dir\"]\n    return os.path.join(cli_config.default_archive_dir, lineagename)\n\ndef _full_live_path(cli_config, lineagename):\n    \"\"\"Returns the full default live path for a lineagename\"\"\"\n    return os.path.join(cli_config.live_dir, lineagename)\n\ndef delete_files(config, certname):\n    \"\"\"Delete all files related to the certificate.\n\n    If some files are not found, ignore them and continue.\n    \"\"\"\n    renewal_filename = renewal_file_for_certname(config, certname)\n    # file exists\n    full_default_archive_dir = full_archive_path(None, config, certname)\n    full_default_live_dir = _full_live_path(config, certname)\n    try:\n        renewal_config = configobj.ConfigObj(renewal_filename)\n    except configobj.ConfigObjError:\n        # config is corrupted\n        logger.warning(\"Could not parse %s. You may wish to manually \"\n            \"delete the contents of %s and %s.\", renewal_filename,\n            full_default_live_dir, full_default_archive_dir)\n        raise errors.CertStorageError(\n            \"error parsing {0}\".format(renewal_filename))\n    finally:\n        # we couldn't read it, but let's at least delete it\n        # if this was going to fail, it already would have.\n        os.remove(renewal_filename)\n        logger.debug(\"Removed %s\", renewal_filename)\n\n    # cert files and (hopefully) live directory\n    # it's not guaranteed that the files are in our default storage\n    # structure. so, first delete the cert files.\n    directory_names = set()\n    for kind in ALL_FOUR:\n        link = renewal_config.get(kind)\n        try:\n            os.remove(link)\n            logger.debug(\"Removed %s\", link)\n        except OSError:\n            logger.debug(\"Unable to delete %s\", link)\n        directory = os.path.dirname(link)\n        directory_names.add(directory)\n\n    # if all four were in the same directory, and the only thing left\n    # is the README file (or nothing), delete that directory.\n    # this will be wrong in very few but some cases.\n    if len(directory_names) == 1:\n        # delete the README file\n        directory = directory_names.pop()\n        readme_path = os.path.join(directory, README)\n        try:\n            os.remove(readme_path)\n            logger.debug(\"Removed %s\", readme_path)\n        except OSError:\n            logger.debug(\"Unable to delete %s\", readme_path)\n        # if it's now empty, delete the directory\n        try:\n            os.rmdir(directory) # only removes empty directories\n            logger.debug(\"Removed %s\", directory)\n        except OSError:\n            logger.debug(\"Unable to remove %s; may not be empty.\", directory)\n\n    # archive directory\n    try:\n        archive_path = full_archive_path(renewal_config, config, certname)\n        shutil.rmtree(archive_path)\n        logger.debug(\"Removed %s\", archive_path)\n    except OSError:\n        logger.debug(\"Unable to remove %s\", archive_path)\n\n\nclass RenewableCert(interfaces.RenewableCert):\n    \"\"\"Renewable certificate.\n\n    Represents a lineage of certificates that is under the management of\n    Certbot, indicated by the existence of an associated renewal\n    configuration file.\n\n    Note that the notion of \"current version\" for a lineage is\n    maintained on disk in the structure of symbolic links, and is not\n    explicitly stored in any instance variable in this object. The\n    RenewableCert object is able to determine information about the\n    current (or other) version by accessing data on disk, but does not\n    inherently know any of this information except by examining the\n    symbolic links as needed. The instance variables mentioned below\n    point to symlinks that reflect the notion of \"current version\" of\n    each managed object, and it is these paths that should be used when\n    configuring servers to use the certificate managed in a lineage.\n    These paths are normally within the \"live\" directory, and their\n    symlink targets -- the actual cert files -- are normally found\n    within the \"archive\" directory.\n\n    :ivar str cert: The path to the symlink representing the current\n        version of the certificate managed by this lineage.\n    :ivar str privkey: The path to the symlink representing the current\n        version of the private key managed by this lineage.\n    :ivar str chain: The path to the symlink representing the current version\n        of the chain managed by this lineage.\n    :ivar str fullchain: The path to the symlink representing the\n        current version of the fullchain (combined chain and cert)\n        managed by this lineage.\n    :ivar configobj.ConfigObj configuration: The renewal configuration\n        options associated with this lineage, obtained from parsing the\n        renewal configuration file and/or systemwide defaults.\n\n    \"\"\"\n    def __init__(self, config_filename, cli_config, update_symlinks=False):\n        \"\"\"Instantiate a RenewableCert object from an existing lineage.\n\n        :param str config_filename: the path to the renewal config file\n            that defines this lineage.\n        :param .NamespaceConfig: parsed command line arguments\n\n        :raises .CertStorageError: if the configuration file's name didn't end\n            in \".conf\", or the file is missing or broken.\n\n        \"\"\"\n        self.cli_config = cli_config\n        self._lineagename = lineagename_for_filename(config_filename)\n\n        # self.configuration should be used to read parameters that\n        # may have been chosen based on default values from the\n        # systemwide renewal configuration; self.configfile should be\n        # used to make and save changes.\n        try:\n            self.configfile = configobj.ConfigObj(config_filename)\n        except configobj.ConfigObjError:\n            raise errors.CertStorageError(\n                \"error parsing {0}\".format(config_filename))\n        # TODO: Do we actually use anything from defaults and do we want to\n        #       read further defaults from the systemwide renewal configuration\n        #       file at this stage?\n        self.configuration = config_with_defaults(self.configfile)\n\n        if not all(x in self.configuration for x in ALL_FOUR):\n            raise errors.CertStorageError(\n                \"renewal config file {0} is missing a required \"\n                \"file reference\".format(self.configfile))\n\n        conf_version = self.configuration.get(\"version\")\n        if (conf_version is not None and\n                util.get_strict_version(conf_version) > CURRENT_VERSION):\n            logger.info(\n                \"Attempting to parse the version %s renewal configuration \"\n                \"file found at %s with version %s of Certbot. This might not \"\n                \"work.\", conf_version, config_filename, certbot.__version__)\n\n        self.cert = self.configuration[\"cert\"]\n        self.privkey = self.configuration[\"privkey\"]\n        self.chain = self.configuration[\"chain\"]\n        self.fullchain = self.configuration[\"fullchain\"]\n        self.live_dir = os.path.dirname(self.cert)\n\n        self._fix_symlinks()\n        if update_symlinks:\n            self._update_symlinks()\n        self._check_symlinks()\n\n    @property\n    def key_path(self):\n        \"\"\"Duck type for self.privkey\"\"\"\n        return self.privkey\n\n    @property\n    def cert_path(self):\n        \"\"\"Duck type for self.cert\"\"\"\n        return self.cert\n\n    @property\n    def chain_path(self):\n        \"\"\"Duck type for self.chain\"\"\"\n        return self.chain\n\n    @property\n    def fullchain_path(self):\n        \"\"\"Duck type for self.fullchain\"\"\"\n        return self.fullchain\n\n    @property\n    def lineagename(self):\n        \"\"\"Name given to the certificate lineage.\n\n        :rtype: str\n\n        \"\"\"\n        return self._lineagename\n\n    @property\n    def target_expiry(self):\n        \"\"\"The current target certificate's expiration datetime\n\n        :returns: Expiration datetime of the current target certificate\n        :rtype: :class:`datetime.datetime`\n        \"\"\"\n        return crypto_util.notAfter(self.current_target(\"cert\"))\n\n    @property\n    def archive_dir(self):\n        \"\"\"Returns the default or specified archive directory\"\"\"\n        return full_archive_path(self.configuration,\n            self.cli_config, self.lineagename)\n\n    def relative_archive_dir(self, from_file):\n        \"\"\"Returns the default or specified archive directory as a relative path\n\n        Used for creating symbolic links.\n        \"\"\"\n        return _relpath_from_file(self.archive_dir, from_file)\n\n    @property\n    def is_test_cert(self):\n        \"\"\"Returns true if this is a test cert from a staging server.\"\"\"\n        server = self.configuration[\"renewalparams\"].get(\"server\", None)\n        if server:\n            return util.is_staging(server)\n        return False\n\n    def _check_symlinks(self):\n        \"\"\"Raises an exception if a symlink doesn't exist\"\"\"\n        for kind in ALL_FOUR:\n            link = getattr(self, kind)\n            if not os.path.islink(link):\n                raise errors.CertStorageError(\n                    \"expected {0} to be a symlink\".format(link))\n            target = get_link_target(link)\n            if not os.path.exists(target):\n                raise errors.CertStorageError(\"target {0} of symlink {1} does \"\n                                              \"not exist\".format(target, link))\n\n    def _update_symlinks(self):\n        \"\"\"Updates symlinks to use archive_dir\"\"\"\n        for kind in ALL_FOUR:\n            link = getattr(self, kind)\n            previous_link = get_link_target(link)\n            new_link = os.path.join(self.relative_archive_dir(link),\n                os.path.basename(previous_link))\n\n            os.unlink(link)\n            os.symlink(new_link, link)\n\n    def _consistent(self):\n        \"\"\"Are the files associated with this lineage self-consistent?\n\n        :returns: Whether the files stored in connection with this\n            lineage appear to be correct and consistent with one\n            another.\n        :rtype: bool\n\n        \"\"\"\n        # Each element must be referenced with an absolute path\n        for x in (self.cert, self.privkey, self.chain, self.fullchain):\n            if not os.path.isabs(x):\n                logger.debug(\"Element %s is not referenced with an \"\n                             \"absolute path.\", x)\n                return False\n\n        # Each element must exist and be a symbolic link\n        for x in (self.cert, self.privkey, self.chain, self.fullchain):\n            if not os.path.islink(x):\n                logger.debug(\"Element %s is not a symbolic link.\", x)\n                return False\n        for kind in ALL_FOUR:\n            link = getattr(self, kind)\n            target = get_link_target(link)\n\n            # Each element's link must point within the cert lineage's\n            # directory within the official archive directory\n            if not os.path.samefile(os.path.dirname(target), self.archive_dir):\n                logger.debug(\"Element's link does not point within the \"\n                             \"cert lineage's directory within the \"\n                             \"official archive directory. Link: %s, \"\n                             \"target directory: %s, \"\n                             \"archive directory: %s. If you've specified \"\n                             \"the archive directory in the renewal configuration \"\n                             \"file, you may need to update links by running \"\n                             \"certbot update_symlinks.\",\n                             link, os.path.dirname(target), self.archive_dir)\n                return False\n\n            # The link must point to a file that exists\n            if not os.path.exists(target):\n                logger.debug(\"Link %s points to file %s that does not exist.\",\n                             link, target)\n                return False\n\n            # The link must point to a file that follows the archive\n            # naming convention\n            pattern = re.compile(r\"^{0}([0-9]+)\\.pem$\".format(kind))\n            if not pattern.match(os.path.basename(target)):\n                logger.debug(\"%s does not follow the archive naming \"\n                             \"convention.\", target)\n                return False\n\n            # It is NOT required that the link's target be a regular\n            # file (it may itself be a symlink). But we should probably\n            # do a recursive check that ultimately the target does\n            # exist?\n        # XXX: Additional possible consistency checks (e.g.\n        #      cryptographic validation of the chain being a chain,\n        #      the chain matching the cert, and the cert matching\n        #      the subject key)\n        # XXX: All four of the targets are in the same directory\n        #      (This check is redundant with the check that they\n        #      are all in the desired directory!)\n        #      len(set(os.path.basename(self.current_target(x)\n        #      for x in ALL_FOUR))) == 1\n        return True\n\n    def _fix(self):\n        \"\"\"Attempt to fix defects or inconsistencies in this lineage.\n\n        .. todo:: Currently unimplemented.\n\n        \"\"\"\n        # TODO: Figure out what kinds of fixes are possible.  For\n        #       example, checking if there is a valid version that\n        #       we can update the symlinks to.  (Maybe involve\n        #       parsing keys and certs to see if they exist and\n        #       if a key corresponds to the subject key of a cert?)\n\n    # TODO: In general, the symlink-reading functions below are not\n    #       cautious enough about the possibility that links or their\n    #       targets may not exist.  (This shouldn't happen, but might\n    #       happen as a result of random tampering by a sysadmin, or\n    #       filesystem errors, or crashes.)\n\n    def _previous_symlinks(self):\n        \"\"\"Returns the kind and path of all symlinks used in recovery.\n\n        :returns: list of (kind, symlink) tuples\n        :rtype: list\n\n        \"\"\"\n        previous_symlinks = []\n        for kind in ALL_FOUR:\n            link_dir = os.path.dirname(getattr(self, kind))\n            link_base = \"previous_{0}.pem\".format(kind)\n            previous_symlinks.append((kind, os.path.join(link_dir, link_base)))\n\n        return previous_symlinks\n\n    def _fix_symlinks(self):\n        \"\"\"Fixes symlinks in the event of an incomplete version update.\n\n        If there is no problem with the current symlinks, this function\n        has no effect.\n\n        \"\"\"\n        previous_symlinks = self._previous_symlinks()\n        if all(os.path.exists(link[1]) for link in previous_symlinks):\n            for kind, previous_link in previous_symlinks:\n                current_link = getattr(self, kind)\n                if os.path.lexists(current_link):\n                    os.unlink(current_link)\n                os.symlink(os.readlink(previous_link), current_link)\n\n        for _, link in previous_symlinks:\n            if os.path.exists(link):\n                os.unlink(link)\n\n    def current_target(self, kind):\n        \"\"\"Returns full path to which the specified item currently points.\n\n        :param str kind: the lineage member item (\"cert\", \"privkey\",\n            \"chain\", or \"fullchain\")\n\n        :returns: The path to the current version of the specified\n            member.\n        :rtype: str or None\n\n        \"\"\"\n        if kind not in ALL_FOUR:\n            raise errors.CertStorageError(\"unknown kind of item\")\n        link = getattr(self, kind)\n        if not os.path.exists(link):\n            logger.debug(\"Expected symlink %s for %s does not exist.\",\n                         link, kind)\n            return None\n        return get_link_target(link)\n\n    def current_version(self, kind):\n        \"\"\"Returns numerical version of the specified item.\n\n        For example, if kind is \"chain\" and the current chain link\n        points to a file named \"chain7.pem\", returns the integer 7.\n\n        :param str kind: the lineage member item (\"cert\", \"privkey\",\n            \"chain\", or \"fullchain\")\n\n        :returns: the current version of the specified member.\n        :rtype: int\n\n        \"\"\"\n        if kind not in ALL_FOUR:\n            raise errors.CertStorageError(\"unknown kind of item\")\n        pattern = re.compile(r\"^{0}([0-9]+)\\.pem$\".format(kind))\n        target = self.current_target(kind)\n        if target is None or not os.path.exists(target):\n            logger.debug(\"Current-version target for %s \"\n                         \"does not exist at %s.\", kind, target)\n            target = \"\"\n        matches = pattern.match(os.path.basename(target))\n        if matches:\n            return int(matches.groups()[0])\n        logger.debug(\"No matches for target %s.\", kind)\n        return None\n\n    def version(self, kind, version):\n        \"\"\"The filename that corresponds to the specified version and kind.\n\n        .. warning:: The specified version may not exist in this\n           lineage. There is no guarantee that the file path returned\n           by this method actually exists.\n\n        :param str kind: the lineage member item (\"cert\", \"privkey\",\n            \"chain\", or \"fullchain\")\n        :param int version: the desired version\n\n        :returns: The path to the specified version of the specified member.\n        :rtype: str\n\n        \"\"\"\n        if kind not in ALL_FOUR:\n            raise errors.CertStorageError(\"unknown kind of item\")\n        where = os.path.dirname(self.current_target(kind))\n        return os.path.join(where, \"{0}{1}.pem\".format(kind, version))\n\n    def available_versions(self, kind):\n        \"\"\"Which alternative versions of the specified kind of item exist?\n\n        The archive directory where the current version is stored is\n        consulted to obtain the list of alternatives.\n\n        :param str kind: the lineage member item (\n            ``cert``, ``privkey``, ``chain``, or ``fullchain``)\n\n        :returns: all of the version numbers that currently exist\n        :rtype: `list` of `int`\n\n        \"\"\"\n        if kind not in ALL_FOUR:\n            raise errors.CertStorageError(\"unknown kind of item\")\n        where = os.path.dirname(self.current_target(kind))\n        files = os.listdir(where)\n        pattern = re.compile(r\"^{0}([0-9]+)\\.pem$\".format(kind))\n        matches = [pattern.match(f) for f in files]\n        return sorted([int(m.groups()[0]) for m in matches if m])\n\n    def newest_available_version(self, kind):\n        \"\"\"Newest available version of the specified kind of item?\n\n        :param str kind: the lineage member item (``cert``,\n            ``privkey``, ``chain``, or ``fullchain``)\n\n        :returns: the newest available version of this member\n        :rtype: int\n\n        \"\"\"\n        return max(self.available_versions(kind))\n\n    def latest_common_version(self):\n        \"\"\"Newest version for which all items are available?\n\n        :returns: the newest available version for which all members\n            (``cert, ``privkey``, ``chain``, and ``fullchain``) exist\n        :rtype: int\n\n        \"\"\"\n        # TODO: this can raise CertStorageError if there is no version overlap\n        #       (it should probably return None instead)\n        # TODO: this can raise a spurious AttributeError if the current\n        #       link for any kind is missing (it should probably return None)\n        versions = [self.available_versions(x) for x in ALL_FOUR]\n        return max(n for n in versions[0] if all(n in v for v in versions[1:]))\n\n    def next_free_version(self):\n        \"\"\"Smallest version newer than all full or partial versions?\n\n        :returns: the smallest version number that is larger than any\n            version of any item currently stored in this lineage\n        :rtype: int\n\n        \"\"\"\n        # TODO: consider locking/mutual exclusion between updating processes\n        # This isn't self.latest_common_version() + 1 because we don't want\n        # collide with a version that might exist for one file type but not\n        # for the others.\n        return max(self.newest_available_version(x) for x in ALL_FOUR) + 1\n\n    def ensure_deployed(self):\n        \"\"\"Make sure we've deployed the latest version.\n\n        :returns: False if a change was needed, True otherwise\n        :rtype: bool\n\n        May need to recover from rare interrupted / crashed states.\"\"\"\n\n        if self.has_pending_deployment():\n            logger.warning(\"Found a new cert /archive/ that was not linked to in /live/; \"\n                        \"fixing...\")\n            self.update_all_links_to(self.latest_common_version())\n            return False\n        return True\n\n\n    def has_pending_deployment(self):\n        \"\"\"Is there a later version of all of the managed items?\n\n        :returns: ``True`` if there is a complete version of this\n            lineage with a larger version number than the current\n            version, and ``False`` otherwise\n        :rtype: bool\n\n        \"\"\"\n        # TODO: consider whether to assume consistency or treat\n        #       inconsistent/consistent versions differently\n        smallest_current = min(self.current_version(x) for x in ALL_FOUR)\n        return smallest_current < self.latest_common_version()\n\n    def _update_link_to(self, kind, version):\n        \"\"\"Make the specified item point at the specified version.\n\n        (Note that this method doesn't verify that the specified version\n        exists.)\n\n        :param str kind: the lineage member item (\"cert\", \"privkey\",\n            \"chain\", or \"fullchain\")\n        :param int version: the desired version\n\n        \"\"\"\n        if kind not in ALL_FOUR:\n            raise errors.CertStorageError(\"unknown kind of item\")\n        link = getattr(self, kind)\n        filename = \"{0}{1}.pem\".format(kind, version)\n        # Relative rather than absolute target directory\n        target_directory = os.path.dirname(os.readlink(link))\n        # TODO: it could be safer to make the link first under a temporary\n        #       filename, then unlink the old link, then rename the new link\n        #       to the old link; this ensures that this process is able to\n        #       create symlinks.\n        # TODO: we might also want to check consistency of related links\n        #       for the other corresponding items\n        os.unlink(link)\n        os.symlink(os.path.join(target_directory, filename), link)\n\n    def update_all_links_to(self, version):\n        \"\"\"Change all member objects to point to the specified version.\n\n        :param int version: the desired version\n\n        \"\"\"\n        with error_handler.ErrorHandler(self._fix_symlinks):\n            previous_links = self._previous_symlinks()\n            for kind, link in previous_links:\n                os.symlink(self.current_target(kind), link)\n\n            for kind in ALL_FOUR:\n                self._update_link_to(kind, version)\n\n            for _, link in previous_links:\n                os.unlink(link)\n\n    def names(self):\n        \"\"\"What are the subject names of this certificate?\n\n        :returns: the subject names\n        :rtype: `list` of `str`\n        :raises .CertStorageError: if could not find cert file.\n\n        \"\"\"\n        target = self.current_target(\"cert\")\n        if target is None:\n            raise errors.CertStorageError(\"could not find cert file\")\n        with open(target) as f:\n            return crypto_util.get_names_from_cert(f.read())\n\n    def ocsp_revoked(self, version):\n        \"\"\"Is the specified cert version revoked according to OCSP?\n\n        Also returns True if the cert version is declared as revoked\n        according to OCSP. If OCSP status could not be determined, False\n        is returned.\n\n        :param int version: the desired version number\n\n        :returns: True if the certificate is revoked, otherwise, False\n        :rtype: bool\n\n        \"\"\"\n        cert_path = self.version(\"cert\", version)\n        chain_path = self.version(\"chain\", version)\n        # While the RevocationChecker should return False if it failed to\n        # determine the OCSP status, let's ensure we don't crash Certbot by\n        # catching all exceptions here.\n        try:\n            return ocsp.RevocationChecker().ocsp_revoked_by_paths(cert_path,\n                                                                  chain_path)\n        except Exception as e:  # pylint: disable=broad-except\n            logger.warning(\n                \"An error occurred determining the OCSP status of %s.\",\n                cert_path)\n            logger.debug(str(e))\n            return False\n\n    def autorenewal_is_enabled(self):\n        \"\"\"Is automatic renewal enabled for this cert?\n\n        If autorenew is not specified, defaults to True.\n\n        :returns: True if automatic renewal is enabled\n        :rtype: bool\n\n        \"\"\"\n        return (\"autorenew\" not in self.configuration[\"renewalparams\"] or\n                self.configuration[\"renewalparams\"].as_bool(\"autorenew\"))\n\n    def should_autorenew(self):\n        \"\"\"Should we now try to autorenew the most recent cert version?\n\n        This is a policy question and does not only depend on whether\n        the cert is expired. (This considers whether autorenewal is\n        enabled, whether the cert is revoked, and whether the time\n        interval for autorenewal has been reached.)\n\n        Note that this examines the numerically most recent cert version,\n        not the currently deployed version.\n\n        :returns: whether an attempt should now be made to autorenew the\n            most current cert version in this lineage\n        :rtype: bool\n\n        \"\"\"\n        if self.autorenewal_is_enabled():\n            # Consider whether to attempt to autorenew this cert now\n\n            # Renewals on the basis of revocation\n            if self.ocsp_revoked(self.latest_common_version()):\n                logger.debug(\"Should renew, certificate is revoked.\")\n                return True\n\n            # Renews some period before expiry time\n            default_interval = constants.RENEWER_DEFAULTS[\"renew_before_expiry\"]\n            interval = self.configuration.get(\"renew_before_expiry\", default_interval)\n            expiry = crypto_util.notAfter(self.version(\n                \"cert\", self.latest_common_version()))\n            now = pytz.UTC.fromutc(datetime.datetime.utcnow())\n            if expiry < add_time_interval(now, interval):\n                logger.debug(\"Should renew, less than %s before certificate \"\n                             \"expiry %s.\", interval,\n                             expiry.strftime(\"%Y-%m-%d %H:%M:%S %Z\"))\n                return True\n        return False\n\n    @classmethod\n    def new_lineage(cls, lineagename, cert, privkey, chain, cli_config):\n        \"\"\"Create a new certificate lineage.\n\n        Attempts to create a certificate lineage -- enrolled for\n        potential future renewal -- with the (suggested) lineage name\n        lineagename, and the associated cert, privkey, and chain (the\n        associated fullchain will be created automatically). Optional\n        configurator and renewalparams record the configuration that was\n        originally used to obtain this cert, so that it can be reused\n        later during automated renewal.\n\n        Returns a new RenewableCert object referring to the created\n        lineage. (The actual lineage name, as well as all the relevant\n        file paths, will be available within this object.)\n\n        :param str lineagename: the suggested name for this lineage\n            (normally the current cert's first subject DNS name)\n        :param str cert: the initial certificate version in PEM format\n        :param str privkey: the private key in PEM format\n        :param str chain: the certificate chain in PEM format\n        :param .NamespaceConfig cli_config: parsed command line\n            arguments\n\n        :returns: the newly-created RenewalCert object\n        :rtype: :class:`storage.renewableCert`\n\n        \"\"\"\n\n        # Examine the configuration and find the new lineage's name\n        for i in (cli_config.renewal_configs_dir, cli_config.default_archive_dir,\n                  cli_config.live_dir):\n            if not os.path.exists(i):\n                filesystem.makedirs(i, 0o700)\n                logger.debug(\"Creating directory %s.\", i)\n        config_file, config_filename = util.unique_lineage_name(\n            cli_config.renewal_configs_dir, lineagename)\n        base_readme_path = os.path.join(cli_config.live_dir, README)\n        if not os.path.exists(base_readme_path):\n            _write_live_readme_to(base_readme_path, is_base_dir=True)\n\n        # Determine where on disk everything will go\n        # lineagename will now potentially be modified based on which\n        # renewal configuration file could actually be created\n        lineagename = lineagename_for_filename(config_filename)\n        archive = full_archive_path(None, cli_config, lineagename)\n        live_dir = _full_live_path(cli_config, lineagename)\n        if os.path.exists(archive):\n            config_file.close()\n            raise errors.CertStorageError(\n                \"archive directory exists for \" + lineagename)\n        if os.path.exists(live_dir):\n            config_file.close()\n            raise errors.CertStorageError(\n                \"live directory exists for \" + lineagename)\n        filesystem.mkdir(archive)\n        filesystem.mkdir(live_dir)\n        logger.debug(\"Archive directory %s and live \"\n                     \"directory %s created.\", archive, live_dir)\n\n        # Put the data into the appropriate files on disk\n        target = {kind: os.path.join(live_dir, kind + \".pem\") for kind in ALL_FOUR}\n        archive_target = {kind: os.path.join(archive, kind + \"1.pem\") for kind in ALL_FOUR}\n        for kind in ALL_FOUR:\n            os.symlink(_relpath_from_file(archive_target[kind], target[kind]), target[kind])\n        with open(target[\"cert\"], \"wb\") as f:\n            logger.debug(\"Writing certificate to %s.\", target[\"cert\"])\n            f.write(cert)\n        with util.safe_open(archive_target[\"privkey\"], \"wb\", chmod=BASE_PRIVKEY_MODE) as f:\n            logger.debug(\"Writing private key to %s.\", target[\"privkey\"])\n            f.write(privkey)\n            # XXX: Let's make sure to get the file permissions right here\n        with open(target[\"chain\"], \"wb\") as f:\n            logger.debug(\"Writing chain to %s.\", target[\"chain\"])\n            f.write(chain)\n        with open(target[\"fullchain\"], \"wb\") as f:\n            # assumes that OpenSSL.crypto.dump_certificate includes\n            # ending newline character\n            logger.debug(\"Writing full chain to %s.\", target[\"fullchain\"])\n            f.write(cert + chain)\n\n        # Write a README file to the live directory\n        readme_path = os.path.join(live_dir, README)\n        _write_live_readme_to(readme_path)\n\n        # Document what we've done in a new renewal config file\n        config_file.close()\n\n        # Save only the config items that are relevant to renewal\n        values = relevant_values(vars(cli_config.namespace))\n\n        new_config = write_renewal_config(config_filename, config_filename, archive,\n            target, values)\n        return cls(new_config.filename, cli_config)\n\n    def save_successor(self, prior_version, new_cert,\n                       new_privkey, new_chain, cli_config):\n        \"\"\"Save new cert and chain as a successor of a prior version.\n\n        Returns the new version number that was created.\n\n        .. note:: this function does NOT update links to deploy this\n                  version\n\n        :param int prior_version: the old version to which this version\n            is regarded as a successor (used to choose a privkey, if the\n            key has not changed, but otherwise this information is not\n            permanently recorded anywhere)\n        :param bytes new_cert: the new certificate, in PEM format\n        :param bytes new_privkey: the new private key, in PEM format,\n            or ``None``, if the private key has not changed\n        :param bytes new_chain: the new chain, in PEM format\n        :param .NamespaceConfig cli_config: parsed command line\n            arguments\n\n        :returns: the new version number that was created\n        :rtype: int\n\n        \"\"\"\n        # XXX: assumes official archive location rather than examining links\n        # XXX: consider using os.open for availability of os.O_EXCL\n        # XXX: ensure file permissions are correct; also create directories\n        #      if needed (ensuring their permissions are correct)\n        # Figure out what the new version is and hence where to save things\n\n        self.cli_config = cli_config\n        target_version = self.next_free_version()\n        target = {kind: os.path.join(self.archive_dir, \"{0}{1}.pem\".format(kind, target_version))\n                  for kind in ALL_FOUR}\n\n        old_privkey = os.path.join(\n            self.archive_dir, \"privkey{0}.pem\".format(prior_version))\n\n        # Distinguish the cases where the privkey has changed and where it\n        # has not changed (in the latter case, making an appropriate symlink\n        # to an earlier privkey version)\n        if new_privkey is None:\n            # The behavior below keeps the prior key by creating a new\n            # symlink to the old key or the target of the old key symlink.\n            if os.path.islink(old_privkey):\n                old_privkey = os.readlink(old_privkey)\n            else:\n                old_privkey = \"privkey{0}.pem\".format(prior_version)\n            logger.debug(\"Writing symlink to old private key, %s.\", old_privkey)\n            os.symlink(old_privkey, target[\"privkey\"])\n        else:\n            with util.safe_open(target[\"privkey\"], \"wb\", chmod=BASE_PRIVKEY_MODE) as f:\n                logger.debug(\"Writing new private key to %s.\", target[\"privkey\"])\n                f.write(new_privkey)\n            # Preserve gid and (mode & MASK_FOR_PRIVATE_KEY_PERMISSIONS)\n            # from previous privkey in this lineage.\n            mode = filesystem.compute_private_key_mode(old_privkey, BASE_PRIVKEY_MODE)\n            filesystem.copy_ownership_and_apply_mode(\n                old_privkey, target[\"privkey\"], mode, copy_user=False, copy_group=True)\n\n        # Save everything else\n        with open(target[\"cert\"], \"wb\") as f:\n            logger.debug(\"Writing certificate to %s.\", target[\"cert\"])\n            f.write(new_cert)\n        with open(target[\"chain\"], \"wb\") as f:\n            logger.debug(\"Writing chain to %s.\", target[\"chain\"])\n            f.write(new_chain)\n        with open(target[\"fullchain\"], \"wb\") as f:\n            logger.debug(\"Writing full chain to %s.\", target[\"fullchain\"])\n            f.write(new_cert + new_chain)\n\n        symlinks = dict((kind, self.configuration[kind]) for kind in ALL_FOUR)\n        # Update renewal config file\n        self.configfile = update_configuration(\n            self.lineagename, self.archive_dir, symlinks, cli_config)\n        self.configuration = config_with_defaults(self.configfile)\n\n        return target_version\n"
  },
  {
    "path": "certbot/_internal/updater.py",
    "content": "\"\"\"Updaters run at renewal\"\"\"\nimport logging\n\nfrom certbot import errors\nfrom certbot import interfaces\nfrom certbot._internal.plugins import selection as plug_sel\nimport certbot.plugins.enhancements as enhancements\n\nlogger = logging.getLogger(__name__)\n\ndef run_generic_updaters(config, lineage, plugins):\n    \"\"\"Run updaters that the plugin supports\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param lineage: Certificate lineage object\n    :type lineage: storage.RenewableCert\n\n    :param plugins: List of plugins\n    :type plugins: `list` of `str`\n\n    :returns: `None`\n    :rtype: None\n    \"\"\"\n    if config.dry_run:\n        logger.debug(\"Skipping updaters in dry-run mode.\")\n        return\n    try:\n        installer = plug_sel.get_unprepared_installer(config, plugins)\n    except errors.Error as e:\n        logger.warning(\"Could not choose appropriate plugin for updaters: %s\", e)\n        return\n    if installer:\n        _run_updaters(lineage, installer, config)\n        _run_enhancement_updaters(lineage, installer, config)\n\ndef run_renewal_deployer(config, lineage, installer):\n    \"\"\"Helper function to run deployer interface method if supported by the used\n    installer plugin.\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n\n    :param lineage: Certificate lineage object\n    :type lineage: storage.RenewableCert\n\n    :param installer: Installer object\n    :type installer: interfaces.IInstaller\n\n    :returns: `None`\n    :rtype: None\n    \"\"\"\n    if config.dry_run:\n        logger.debug(\"Skipping renewal deployer in dry-run mode.\")\n        return\n\n    if not config.disable_renew_updates and isinstance(installer,\n                                                       interfaces.RenewDeployer):\n        installer.renew_deploy(lineage)\n    _run_enhancement_deployers(lineage, installer, config)\n\ndef _run_updaters(lineage, installer, config):\n    \"\"\"Helper function to run the updater interface methods if supported by the\n    used installer plugin.\n\n    :param lineage: Certificate lineage object\n    :type lineage: storage.RenewableCert\n\n    :param installer: Installer object\n    :type installer: interfaces.IInstaller\n\n    :returns: `None`\n    :rtype: None\n    \"\"\"\n    if not config.disable_renew_updates:\n        if isinstance(installer, interfaces.GenericUpdater):\n            installer.generic_updates(lineage)\n\ndef _run_enhancement_updaters(lineage, installer, config):\n    \"\"\"Iterates through known enhancement interfaces. If the installer implements\n    an enhancement interface and the enhance interface has an updater method, the\n    updater method gets run.\n\n    :param lineage: Certificate lineage object\n    :type lineage: storage.RenewableCert\n\n    :param installer: Installer object\n    :type installer: interfaces.IInstaller\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n    \"\"\"\n\n    if config.disable_renew_updates:\n        return\n    for enh in enhancements._INDEX:  # pylint: disable=protected-access\n        if isinstance(installer, enh[\"class\"]) and enh[\"updater_function\"]:\n            getattr(installer, enh[\"updater_function\"])(lineage)\n\n\ndef _run_enhancement_deployers(lineage, installer, config):\n    \"\"\"Iterates through known enhancement interfaces. If the installer implements\n    an enhancement interface and the enhance interface has an deployer method, the\n    deployer method gets run.\n\n    :param lineage: Certificate lineage object\n    :type lineage: storage.RenewableCert\n\n    :param installer: Installer object\n    :type installer: interfaces.IInstaller\n\n    :param config: Configuration object\n    :type config: interfaces.IConfig\n    \"\"\"\n\n    if config.disable_renew_updates:\n        return\n    for enh in enhancements._INDEX:  # pylint: disable=protected-access\n        if isinstance(installer, enh[\"class\"]) and enh[\"deployer_function\"]:\n            getattr(installer, enh[\"deployer_function\"])(lineage)\n"
  },
  {
    "path": "certbot/achallenges.py",
    "content": "\"\"\"Client annotated ACME challenges.\n\nPlease use names such as ``achall`` to distinguish from variables \"of type\"\n:class:`acme.challenges.Challenge` (denoted by ``chall``)\nand :class:`.ChallengeBody` (denoted by ``challb``)::\n\n  from acme import challenges\n  from acme import messages\n  from certbot import achallenges\n\n  chall = challenges.DNS(token='foo')\n  challb = messages.ChallengeBody(chall=chall)\n  achall = achallenges.DNS(chall=challb, domain='example.com')\n\nNote, that all annotated challenges act as a proxy objects::\n\n  achall.token == challb.token\n\n\"\"\"\nimport logging\n\nimport josepy as jose\n\nfrom acme import challenges\n\nlogger = logging.getLogger(__name__)\n\n\n\nclass AnnotatedChallenge(jose.ImmutableMap):\n    \"\"\"Client annotated challenge.\n\n    Wraps around server provided challenge and annotates with data\n    useful for the client.\n\n    :ivar challb: Wrapped `~.ChallengeBody`.\n\n    \"\"\"\n    __slots__ = ('challb',)\n    acme_type = NotImplemented\n\n    def __getattr__(self, name):\n        return getattr(self.challb, name)\n\n\nclass KeyAuthorizationAnnotatedChallenge(AnnotatedChallenge):\n    \"\"\"Client annotated `KeyAuthorizationChallenge` challenge.\"\"\"\n    __slots__ = ('challb', 'domain', 'account_key')\n\n    def response_and_validation(self, *args, **kwargs):\n        \"\"\"Generate response and validation.\"\"\"\n        return self.challb.chall.response_and_validation(\n            self.account_key, *args, **kwargs)\n\n\nclass DNS(AnnotatedChallenge):\n    \"\"\"Client annotated \"dns\" ACME challenge.\"\"\"\n    __slots__ = ('challb', 'domain')\n    acme_type = challenges.DNS\n"
  },
  {
    "path": "certbot/compat/__init__.py",
    "content": "\"\"\"\nCompatibility layer to run certbot both on Linux and Windows.\n\nThis package contains all logic that needs to be implemented specifically for Linux and for Windows.\nThen the rest of certbot code relies on this module to be platform agnostic.\n\"\"\"\n"
  },
  {
    "path": "certbot/compat/_path.py",
    "content": "\"\"\"\nThis compat module wraps os.path to forbid some functions.\n\nisort:skip_file\n\"\"\"\n# pylint: disable=function-redefined\nfrom __future__ import absolute_import\n\n# First round of wrapping: we import statically all public attributes exposed by the os.path\n# module. This allows in particular to have pylint, mypy, IDEs be aware that most of os.path\n# members are available in certbot.compat.path.\nfrom os.path import *  # type: ignore  # pylint: disable=wildcard-import,unused-wildcard-import,os-module-forbidden\n\n# Second round of wrapping: we import dynamically all attributes from the os.path module that have\n# not yet been imported by the first round (static star import).\nimport os.path as std_os_path  # pylint: disable=os-module-forbidden\nimport sys as std_sys\n\nourselves = std_sys.modules[__name__]\nfor attribute in dir(std_os_path):\n    # Check if the attribute does not already exist in our module. It could be internal attributes\n    # of the module (__name__, __doc__), or attributes from standard os.path already imported with\n    # `from os.path import *`.\n    if not hasattr(ourselves, attribute):\n        setattr(ourselves, attribute, getattr(std_os_path, attribute))\n\n# Clean all remaining importables that are not from the core os.path module.\ndel ourselves, std_os_path, std_sys\n\n\n# Function os.path.realpath is broken on some versions of Python for Windows.\ndef realpath(*unused_args, **unused_kwargs):\n    \"\"\"Method os.path.realpath() is forbidden\"\"\"\n    raise RuntimeError('Usage of os.path.realpath() is forbidden. '\n                       'Use certbot.compat.filesystem.realpath() instead.')\n"
  },
  {
    "path": "certbot/compat/filesystem.py",
    "content": "\"\"\"Compat module to handle files security on Windows and Linux\"\"\"\nfrom __future__ import absolute_import\n\nimport errno\nimport os  # pylint: disable=os-module-forbidden\nimport stat\n\nfrom acme.magic_typing import List\nfrom acme.magic_typing import Tuple  # pylint: disable=unused-import\nfrom acme.magic_typing import Union  # pylint: disable=unused-import\n\ntry:\n    import ntsecuritycon\n    import win32security\n    import win32con\n    import win32api\n    import win32file\n    import pywintypes\n    import winerror\n    # pylint: enable=import-error\nexcept ImportError:\n    POSIX_MODE = True\nelse:\n    POSIX_MODE = False\n\n\ndef chmod(file_path, mode):\n    # type: (str, int) -> None\n    \"\"\"\n    Apply a POSIX mode on given file_path:\n        * for Linux, the POSIX mode will be directly applied using chmod,\n        * for Windows, the POSIX mode will be translated into a Windows DACL that make sense for\n          Certbot context, and applied to the file using kernel calls.\n\n    The definition of the Windows DACL that correspond to a POSIX mode, in the context of Certbot,\n    is explained at https://github.com/certbot/certbot/issues/6356 and is implemented by the\n    method _generate_windows_flags().\n\n    :param str file_path: Path of the file\n    :param int mode: POSIX mode to apply\n    \"\"\"\n    if POSIX_MODE:\n        os.chmod(file_path, mode)\n    else:\n        _apply_win_mode(file_path, mode)\n\n\n# One could ask why there is no copy_ownership() function, or even a reimplementation\n# of os.chown() that would modify the ownership of file without touching the mode itself.\n# This is because on Windows, it would require recalculating the existing DACL against\n# the new owner, since the DACL is composed of ACEs that targets a specific user, not dynamically\n# the current owner of a file. This action would be necessary to keep consistency between\n# the POSIX mode applied to the file and the current owner of this file.\n# Since copying and editing arbitrary DACL is very difficult, and since we actually know\n# the mode to apply at the time the owner of a file should change, it is easier to just\n# change the owner, then reapply the known mode, as copy_ownership_and_apply_mode() does.\ndef copy_ownership_and_apply_mode(src, dst, mode, copy_user, copy_group):\n    # type: (str, str, int, bool, bool) -> None\n    \"\"\"\n    Copy ownership (user and optionally group on Linux) from the source to the\n    destination, then apply given mode in compatible way for Linux and Windows.\n    This replaces the os.chown command.\n    :param str src: Path of the source file\n    :param str dst: Path of the destination file\n    :param int mode: Permission mode to apply on the destination file\n    :param bool copy_user: Copy user if `True`\n    :param bool copy_group: Copy group if `True` on Linux (has no effect on Windows)\n    \"\"\"\n    if POSIX_MODE:\n        stats = os.stat(src)\n        user_id = stats.st_uid if copy_user else -1\n        group_id = stats.st_gid if copy_group else -1\n        # On Windows, os.chown does not exist. This is checked through POSIX_MODE value,\n        # but MyPy/PyLint does not know it and raises an error here on Windows.\n        # We disable specifically the check to fix the issue.\n        os.chown(dst, user_id, group_id)\n    elif copy_user:\n        # There is no group handling in Windows\n        _copy_win_ownership(src, dst)\n\n    chmod(dst, mode)\n\n\ndef check_mode(file_path, mode):\n    # type: (str, int) -> bool\n    \"\"\"\n    Check if the given mode matches the permissions of the given file.\n    On Linux, will make a direct comparison, on Windows, mode will be compared against\n    the security model.\n    :param str file_path: Path of the file\n    :param int mode: POSIX mode to test\n    :rtype: bool\n    :return: True if the POSIX mode matches the file permissions\n    \"\"\"\n    if POSIX_MODE:\n        return stat.S_IMODE(os.stat(file_path).st_mode) == mode\n\n    return _check_win_mode(file_path, mode)\n\n\ndef check_owner(file_path):\n    # type: (str) -> bool\n    \"\"\"\n    Check if given file is owned by current user.\n    :param str file_path: File path to check\n    :rtype: bool\n    :return: True if given file is owned by current user, False otherwise.\n    \"\"\"\n    if POSIX_MODE:\n        # On Windows, os.getuid does not exist. This is checked through POSIX_MODE value,\n        # but MyPy/PyLint does not know it and raises an error here on Windows.\n        # We disable specifically the check to fix the issue.\n        return os.stat(file_path).st_uid == os.getuid()  # type: ignore\n\n    # Get owner sid of the file\n    security = win32security.GetFileSecurity(file_path, win32security.OWNER_SECURITY_INFORMATION)\n    user = security.GetSecurityDescriptorOwner()\n\n    # Compare sids\n    return _get_current_user() == user\n\n\ndef check_permissions(file_path, mode):\n    # type: (str, int) -> bool\n    \"\"\"\n    Check if given file has the given mode and is owned by current user.\n    :param str file_path: File path to check\n    :param int mode: POSIX mode to check\n    :rtype: bool\n    :return: True if file has correct mode and owner, False otherwise.\n    \"\"\"\n    return check_owner(file_path) and check_mode(file_path, mode)\n\n\ndef open(file_path, flags, mode=0o777):  # pylint: disable=redefined-builtin\n    # type: (str, int, int) -> int\n    \"\"\"\n    Wrapper of original os.open function, that will ensure on Windows that given mode\n    is correctly applied.\n    :param str file_path: The file path to open\n    :param int flags: Flags to apply on file while opened\n    :param int mode: POSIX mode to apply on file when opened,\n        Python defaults will be applied if ``None``\n    :returns: the file descriptor to the opened file\n    :rtype: int\n    :raise: OSError(errno.EEXIST) if the file already exists and os.O_CREAT & os.O_EXCL are set,\n            OSError(errno.EACCES) on Windows if the file already exists and is a directory, and\n                os.O_CREAT is set.\n    \"\"\"\n    if POSIX_MODE:\n        # On Linux, invoke os.open directly.\n        return os.open(file_path, flags, mode)\n\n    # Windows: handle creation of the file atomically with proper permissions.\n    if flags & os.O_CREAT:\n        # If os.O_EXCL is set, we will use the \"CREATE_NEW\", that will raise an exception if\n        # file exists, matching the API contract of this bit flag. Otherwise, we use\n        # \"CREATE_ALWAYS\" that will always create the file whether it exists or not.\n        disposition = win32con.CREATE_NEW if flags & os.O_EXCL else win32con.CREATE_ALWAYS\n\n        attributes = win32security.SECURITY_ATTRIBUTES()\n        security = attributes.SECURITY_DESCRIPTOR\n        user = _get_current_user()\n        dacl = _generate_dacl(user, mode)\n        # We set second parameter to 0 (`False`) to say that this security descriptor is\n        # NOT constructed from a default mechanism, but is explicitly set by the user.\n        # See https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-setsecuritydescriptorowner  # pylint: disable=line-too-long\n        security.SetSecurityDescriptorOwner(user, 0)\n        # We set first parameter to 1 (`True`) to say that this security descriptor contains\n        # a DACL. Otherwise second and third parameters are ignored.\n        # We set third parameter to 0 (`False`) to say that this security descriptor is\n        # NOT constructed from a default mechanism, but is explicitly set by the user.\n        # See https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-setsecuritydescriptordacl  # pylint: disable=line-too-long\n        security.SetSecurityDescriptorDacl(1, dacl, 0)\n\n        handle = None\n        try:\n            handle = win32file.CreateFile(file_path, win32file.GENERIC_READ,\n                                          win32file.FILE_SHARE_READ & win32file.FILE_SHARE_WRITE,\n                                          attributes, disposition, 0, None)\n        except pywintypes.error as err:\n            # Handle native windows errors into python errors to be consistent with the API\n            # of os.open in the situation of a file already existing or locked.\n            if err.winerror == winerror.ERROR_FILE_EXISTS:\n                raise OSError(errno.EEXIST, err.strerror)\n            if err.winerror == winerror.ERROR_SHARING_VIOLATION:\n                raise OSError(errno.EACCES, err.strerror)\n            raise err\n        finally:\n            if handle:\n                handle.Close()\n\n        # At this point, the file that did not exist has been created with proper permissions,\n        # so os.O_CREAT and os.O_EXCL are not needed anymore. We remove them from the flags to\n        # avoid a FileExists exception before calling os.open.\n        return os.open(file_path, flags ^ os.O_CREAT ^ os.O_EXCL)\n\n    # Windows: general case, we call os.open, let exceptions be thrown, then chmod if all is fine.\n    handle = os.open(file_path, flags)\n    chmod(file_path, mode)\n    return handle\n\n\ndef makedirs(file_path, mode=0o777):\n    # type: (str, int) -> None\n    \"\"\"\n    Rewrite of original os.makedirs function, that will ensure on Windows that given mode\n    is correctly applied.\n    :param str file_path: The file path to open\n    :param int mode: POSIX mode to apply on leaf directory when created, Python defaults\n                     will be applied if ``None``\n    \"\"\"\n    if POSIX_MODE:\n        return os.makedirs(file_path, mode)\n\n    orig_mkdir_fn = os.mkdir\n    try:\n        # As we know that os.mkdir is called internally by os.makedirs, we will swap the function in\n        # os module for the time of makedirs execution on Windows.\n        os.mkdir = mkdir  # type: ignore\n        return os.makedirs(file_path, mode)\n    finally:\n        os.mkdir = orig_mkdir_fn\n\n\ndef mkdir(file_path, mode=0o777):\n    # type: (str, int) -> None\n    \"\"\"\n    Rewrite of original os.mkdir function, that will ensure on Windows that given mode\n    is correctly applied.\n    :param str file_path: The file path to open\n    :param int mode: POSIX mode to apply on directory when created, Python defaults\n                     will be applied if ``None``\n    \"\"\"\n    if POSIX_MODE:\n        return os.mkdir(file_path, mode)\n\n    attributes = win32security.SECURITY_ATTRIBUTES()\n    security = attributes.SECURITY_DESCRIPTOR\n    user = _get_current_user()\n    dacl = _generate_dacl(user, mode)\n    security.SetSecurityDescriptorOwner(user, False)\n    security.SetSecurityDescriptorDacl(1, dacl, 0)\n\n    try:\n        win32file.CreateDirectory(file_path, attributes)\n    except pywintypes.error as err:\n        # Handle native windows error into python error to be consistent with the API\n        # of os.mkdir in the situation of a directory already existing.\n        if err.winerror == winerror.ERROR_ALREADY_EXISTS:\n            raise OSError(errno.EEXIST, err.strerror, file_path, err.winerror)\n        raise err\n\n    return None\n\n\ndef replace(src, dst):\n    # type: (str, str) -> None\n    \"\"\"\n    Rename a file to a destination path and handles situations where the destination exists.\n    :param str src: The current file path.\n    :param str dst: The new file path.\n    \"\"\"\n    if hasattr(os, 'replace'):\n        # Use replace if possible. On Windows, only Python >= 3.5 is supported\n        # so we can assume that os.replace() is always available for this platform.\n        getattr(os, 'replace')(src, dst)\n    else:\n        # Otherwise, use os.rename() that behaves like os.replace() on Linux.\n        os.rename(src, dst)\n\n\ndef realpath(file_path):\n    # type: (str) -> str\n    \"\"\"\n    Find the real path for the given path. This method resolves symlinks, including\n    recursive symlinks, and is protected against symlinks that creates an infinite loop.\n    \"\"\"\n    original_path = file_path\n\n    if POSIX_MODE:\n        path = os.path.realpath(file_path)\n        if os.path.islink(path):\n            # If path returned by realpath is still a link, it means that it failed to\n            # resolve the symlink because of a loop.\n            # See realpath code: https://github.com/python/cpython/blob/master/Lib/posixpath.py\n            raise RuntimeError('Error, link {0} is a loop!'.format(original_path))\n        return path\n\n    inspected_paths = []  # type: List[str]\n    while os.path.islink(file_path):\n        link_path = file_path\n        file_path = os.readlink(file_path)\n        if not os.path.isabs(file_path):\n            file_path = os.path.join(os.path.dirname(link_path), file_path)\n        if file_path in inspected_paths:\n            raise RuntimeError('Error, link {0} is a loop!'.format(original_path))\n        inspected_paths.append(file_path)\n\n    return os.path.abspath(file_path)\n\n\n# On Windows is_executable run from an unprivileged shell may claim that a path is\n# executable when it is excutable only if run from a privileged shell. This result\n# is due to the fact that GetEffectiveRightsFromAcl calculate effective rights\n# without taking into consideration if the target user has currently required the\n# elevated privileges or not. However this is not a problem since certbot always\n# requires to be run under a privileged shell, so the user will always benefit\n# from the highest (privileged one) set of permissions on a given file.\ndef is_executable(path):\n    # type: (str) -> bool\n    \"\"\"\n    Is path an executable file?\n    :param str path: path to test\n    :return: True if path is an executable file\n    :rtype: bool\n    \"\"\"\n    if POSIX_MODE:\n        return os.path.isfile(path) and os.access(path, os.X_OK)\n\n    return _win_is_executable(path)\n\n\ndef has_world_permissions(path):\n    # type: (str) -> bool\n    \"\"\"\n    Check if everybody/world has any right (read/write/execute) on a file given its path\n    :param str path: path to test\n    :return: True if everybody/world has any right to the file\n    :rtype: bool\n    \"\"\"\n    if POSIX_MODE:\n        return bool(stat.S_IMODE(os.stat(path).st_mode) & stat.S_IRWXO)\n\n    security = win32security.GetFileSecurity(path, win32security.DACL_SECURITY_INFORMATION)\n    dacl = security.GetSecurityDescriptorDacl()\n\n    return bool(dacl.GetEffectiveRightsFromAcl({\n        'TrusteeForm': win32security.TRUSTEE_IS_SID,\n        'TrusteeType': win32security.TRUSTEE_IS_USER,\n        'Identifier': win32security.ConvertStringSidToSid('S-1-1-0'),\n    }))\n\n\ndef compute_private_key_mode(old_key, base_mode):\n    # type: (str, int) -> int\n    \"\"\"\n    Calculate the POSIX mode to apply to a private key given the previous private key\n    :param str old_key: path to the previous private key\n    :param int base_mode: the minimum modes to apply to a private key\n    :return: the POSIX mode to apply\n    :rtype: int\n    \"\"\"\n    if POSIX_MODE:\n        # On Linux, we keep read/write/execute permissions\n        # for group and read permissions for everybody.\n        old_mode = (stat.S_IMODE(os.stat(old_key).st_mode) &\n                    (stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH))\n        return base_mode | old_mode\n\n    # On Windows, the mode returned by os.stat is not reliable,\n    # so we do not keep any permission from the previous private key.\n    return base_mode\n\n\ndef has_same_ownership(path1, path2):\n    # type: (str, str) -> bool\n    \"\"\"\n    Return True if the ownership of two files given their respective path is the same.\n    On Windows, ownership is checked against owner only, since files do not have a group owner.\n    :param str path1: path to the first file\n    :param str path2: path to the second file\n    :return: True if both files have the same ownership, False otherwise\n    :rtype: bool\n\n    \"\"\"\n    if POSIX_MODE:\n        stats1 = os.stat(path1)\n        stats2 = os.stat(path2)\n        return (stats1.st_uid, stats1.st_gid) == (stats2.st_uid, stats2.st_gid)\n\n    security1 = win32security.GetFileSecurity(path1, win32security.OWNER_SECURITY_INFORMATION)\n    user1 = security1.GetSecurityDescriptorOwner()\n\n    security2 = win32security.GetFileSecurity(path2, win32security.OWNER_SECURITY_INFORMATION)\n    user2 = security2.GetSecurityDescriptorOwner()\n\n    return user1 == user2\n\n\ndef has_min_permissions(path, min_mode):\n    # type: (str, int) -> bool\n    \"\"\"\n    Check if a file given its path has at least the permissions defined by the given minimal mode.\n    On Windows, group permissions are ignored since files do not have a group owner.\n    :param str path: path to the file to check\n    :param int min_mode: the minimal permissions expected\n    :return: True if the file matches the minimal permissions expectations, False otherwise\n    :rtype: bool\n    \"\"\"\n    if POSIX_MODE:\n        st_mode = os.stat(path).st_mode\n        return st_mode == st_mode | min_mode\n\n    # Resolve symlinks, to get a consistent result with os.stat on Linux,\n    # that follows symlinks by default.\n    path = realpath(path)\n\n    # Get owner sid of the file\n    security = win32security.GetFileSecurity(\n        path, win32security.OWNER_SECURITY_INFORMATION | win32security.DACL_SECURITY_INFORMATION)\n    user = security.GetSecurityDescriptorOwner()\n    dacl = security.GetSecurityDescriptorDacl()\n    min_dacl = _generate_dacl(user, min_mode)\n\n    for index in range(min_dacl.GetAceCount()):\n        min_ace = min_dacl.GetAce(index)\n\n        # On a given ACE, index 0 is the ACE type, 1 is the permission mask, and 2 is the SID.\n        # See: http://timgolden.me.uk/pywin32-docs/PyACL__GetAce_meth.html\n        mask = min_ace[1]\n        user = min_ace[2]\n\n        effective_mask = dacl.GetEffectiveRightsFromAcl({\n            'TrusteeForm': win32security.TRUSTEE_IS_SID,\n            'TrusteeType': win32security.TRUSTEE_IS_USER,\n            'Identifier': user,\n        })\n\n        if effective_mask != effective_mask | mask:\n            return False\n\n    return True\n\n\ndef _win_is_executable(path):\n    if not os.path.isfile(path):\n        return False\n\n    security = win32security.GetFileSecurity(path, win32security.DACL_SECURITY_INFORMATION)\n    dacl = security.GetSecurityDescriptorDacl()\n\n    mode = dacl.GetEffectiveRightsFromAcl({\n        'TrusteeForm': win32security.TRUSTEE_IS_SID,\n        'TrusteeType': win32security.TRUSTEE_IS_USER,\n        'Identifier': _get_current_user(),\n    })\n\n    return mode & ntsecuritycon.FILE_GENERIC_EXECUTE == ntsecuritycon.FILE_GENERIC_EXECUTE\n\n\ndef _apply_win_mode(file_path, mode):\n    \"\"\"\n    This function converts the given POSIX mode into a Windows ACL list, and applies it to the\n    file given its path. If the given path is a symbolic link, it will resolved to apply the\n    mode on the targeted file.\n    \"\"\"\n    file_path = realpath(file_path)\n    # Get owner sid of the file\n    security = win32security.GetFileSecurity(file_path, win32security.OWNER_SECURITY_INFORMATION)\n    user = security.GetSecurityDescriptorOwner()\n\n    # New DACL, that will overwrite existing one (including inherited permissions)\n    dacl = _generate_dacl(user, mode)\n\n    # Apply the new DACL\n    security.SetSecurityDescriptorDacl(1, dacl, 0)\n    win32security.SetFileSecurity(file_path, win32security.DACL_SECURITY_INFORMATION, security)\n\n\ndef _generate_dacl(user_sid, mode):\n    analysis = _analyze_mode(mode)\n\n    # Get standard accounts from \"well-known\" sid\n    # See the list here:\n    # https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems\n    system = win32security.ConvertStringSidToSid('S-1-5-18')\n    admins = win32security.ConvertStringSidToSid('S-1-5-32-544')\n    everyone = win32security.ConvertStringSidToSid('S-1-1-0')\n\n    # New dacl, without inherited permissions\n    dacl = win32security.ACL()\n\n    # If user is already system or admins, any ACE defined here would be superseded by\n    # the full control ACE that will be added after.\n    if user_sid not in [system, admins]:\n        # Handle user rights\n        user_flags = _generate_windows_flags(analysis['user'])\n        if user_flags:\n            dacl.AddAccessAllowedAce(win32security.ACL_REVISION, user_flags, user_sid)\n\n    # Handle everybody rights\n    everybody_flags = _generate_windows_flags(analysis['all'])\n    if everybody_flags:\n        dacl.AddAccessAllowedAce(win32security.ACL_REVISION, everybody_flags, everyone)\n\n    # Handle administrator rights\n    full_permissions = _generate_windows_flags({'read': True, 'write': True, 'execute': True})\n    dacl.AddAccessAllowedAce(win32security.ACL_REVISION, full_permissions, system)\n    dacl.AddAccessAllowedAce(win32security.ACL_REVISION, full_permissions, admins)\n\n    return dacl\n\n\ndef _analyze_mode(mode):\n    return {\n        'user': {\n            'read': mode & stat.S_IRUSR,\n            'write': mode & stat.S_IWUSR,\n            'execute': mode & stat.S_IXUSR,\n        },\n        'all': {\n            'read': mode & stat.S_IROTH,\n            'write': mode & stat.S_IWOTH,\n            'execute': mode & stat.S_IXOTH,\n        },\n    }\n\n\ndef _copy_win_ownership(src, dst):\n    security_src = win32security.GetFileSecurity(src, win32security.OWNER_SECURITY_INFORMATION)\n    user_src = security_src.GetSecurityDescriptorOwner()\n\n    security_dst = win32security.GetFileSecurity(dst, win32security.OWNER_SECURITY_INFORMATION)\n    # Second parameter indicates, if `False`, that the owner of the file is not provided by some\n    # default mechanism, but is explicitly set instead. This is obviously what we are doing here.\n    security_dst.SetSecurityDescriptorOwner(user_src, False)\n\n    win32security.SetFileSecurity(dst, win32security.OWNER_SECURITY_INFORMATION, security_dst)\n\n\ndef _generate_windows_flags(rights_desc):\n    # Some notes about how each POSIX right is interpreted.\n    #\n    # For the rights read and execute, we have a pretty bijective relation between\n    # POSIX flags and their generic counterparts on Windows, so we use them directly\n    # (respectively ntsecuritycon.FILE_GENERIC_READ and ntsecuritycon.FILE_GENERIC_EXECUTE).\n    #\n    # But ntsecuritycon.FILE_GENERIC_WRITE does not correspond to what one could expect from a\n    # write access on Linux: for Windows, FILE_GENERIC_WRITE does not include delete, move or\n    # rename. This is something that requires ntsecuritycon.FILE_ALL_ACCESS.\n    # So to reproduce the write right as POSIX, we will apply ntsecuritycon.FILE_ALL_ACCESS\n    # subtracted of the rights corresponding to POSIX read and POSIX execute.\n    #\n    # Finally, having read + write + execute gives a ntsecuritycon.FILE_ALL_ACCESS,\n    # so a \"Full Control\" on the file.\n    #\n    # A complete list of the rights defined on NTFS can be found here:\n    # https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc783530(v=ws.10)#permissions-for-files-and-folders\n    flag = 0\n    if rights_desc['read']:\n        flag = flag | ntsecuritycon.FILE_GENERIC_READ\n    if rights_desc['write']:\n        flag = flag | (ntsecuritycon.FILE_ALL_ACCESS\n                       ^ ntsecuritycon.FILE_GENERIC_READ\n                       ^ ntsecuritycon.FILE_GENERIC_EXECUTE)\n    if rights_desc['execute']:\n        flag = flag | ntsecuritycon.FILE_GENERIC_EXECUTE\n\n    return flag\n\n\ndef _check_win_mode(file_path, mode):\n    # Resolve symbolic links\n    file_path = realpath(file_path)\n    # Get current dacl file\n    security = win32security.GetFileSecurity(file_path, win32security.OWNER_SECURITY_INFORMATION\n                                             | win32security.DACL_SECURITY_INFORMATION)\n    dacl = security.GetSecurityDescriptorDacl()\n\n    # Get current file owner sid\n    user = security.GetSecurityDescriptorOwner()\n\n    if not dacl:\n        # No DACL means full control to everyone\n        # This is not a deterministic permissions set.\n        return False\n\n    # Calculate the target dacl\n    ref_dacl = _generate_dacl(user, mode)\n\n    return _compare_dacls(dacl, ref_dacl)\n\n\ndef _compare_dacls(dacl1, dacl2):\n    \"\"\"\n    This method compare the two given DACLs to check if they are identical.\n    Identical means here that they contains the same set of ACEs in the same order.\n    \"\"\"\n    return ([dacl1.GetAce(index) for index in range(dacl1.GetAceCount())] ==\n            [dacl2.GetAce(index) for index in range(dacl2.GetAceCount())])\n\n\ndef _get_current_user():\n    \"\"\"\n    Return the pySID corresponding to the current user.\n    \"\"\"\n    # We craft the account_name ourselves instead of calling for instance win32api.GetUserNameEx,\n    # because this function returns nonsense values when Certbot is run under NT AUTHORITY\\SYSTEM.\n    # To run Certbot under NT AUTHORITY\\SYSTEM, you can open a shell using the instructions here:\n    # https://blogs.technet.microsoft.com/ben_parker/2010/10/27/how-do-i-run-powershell-execommand-prompt-as-the-localsystem-account-on-windows-7/\n    account_name = r\"{0}\\{1}\".format(win32api.GetDomainName(), win32api.GetUserName())\n    # LookupAccountName() expects the system name as first parameter. By passing None to it,\n    # we instruct Windows to first search the matching account in the machine local accounts,\n    # then into the primary domain accounts, if the machine has joined a domain, then finally\n    # into the trusted domains accounts. This is the preferred lookup mechanism to use in Windows\n    # if there is no reason to use a specific lookup mechanism.\n    # See https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-lookupaccountnamea\n    return win32security.LookupAccountName(None, account_name)[0]\n"
  },
  {
    "path": "certbot/compat/misc.py",
    "content": "\"\"\"\nThis compat module handles various platform specific calls that do not fall into one\nparticular category.\n\"\"\"\nfrom __future__ import absolute_import\n\nimport select\nimport sys\n\nfrom certbot import errors\nfrom certbot.compat import os\n\ntry:\n    from win32com.shell import shell as shellwin32\n    POSIX_MODE = False\nexcept ImportError:  # pragma: no cover\n    POSIX_MODE = True\n\n\n\n# For Linux: define OS specific standard binary directories\nSTANDARD_BINARY_DIRS = [\"/usr/sbin\", \"/usr/local/bin\", \"/usr/local/sbin\"] if POSIX_MODE else []\n\n\ndef raise_for_non_administrative_windows_rights():\n    # type: () -> None\n    \"\"\"\n    On Windows, raise if current shell does not have the administrative rights.\n    Do nothing on Linux.\n\n    :raises .errors.Error: If the current shell does not have administrative rights on Windows.\n    \"\"\"\n    if not POSIX_MODE and shellwin32.IsUserAnAdmin() == 0:  # pragma: no cover\n        raise errors.Error('Error, certbot must be run on a shell with administrative rights.')\n\n\ndef readline_with_timeout(timeout, prompt):\n    # type: (float, str) -> str\n    \"\"\"\n    Read user input to return the first line entered, or raise after specified timeout.\n\n    :param float timeout: The timeout in seconds given to the user.\n    :param str prompt: The prompt message to display to the user.\n\n    :returns: The first line entered by the user.\n    :rtype: str\n\n    \"\"\"\n    try:\n        # Linux specific\n        #\n        # Call to select can only be done like this on UNIX\n        rlist, _, _ = select.select([sys.stdin], [], [], timeout)\n        if not rlist:\n            raise errors.Error(\n                \"Timed out waiting for answer to prompt '{0}'\".format(prompt))\n        return rlist[0].readline()\n    except OSError:\n        # Windows specific\n        #\n        # No way with select to make a timeout to the user input on Windows,\n        # as select only supports socket in this case.\n        # So no timeout on Windows for now.\n        return sys.stdin.readline()\n\n\nWINDOWS_DEFAULT_FOLDERS = {\n    'config': 'C:\\\\Certbot',\n    'work': 'C:\\\\Certbot\\\\lib',\n    'logs': 'C:\\\\Certbot\\\\log',\n}\nLINUX_DEFAULT_FOLDERS = {\n    'config': '/etc/letsencrypt',\n    'work': '/var/lib/letsencrypt',\n    'logs': '/var/log/letsencrypt',\n}\n\n\ndef get_default_folder(folder_type):\n    # type: (str) -> str\n    \"\"\"\n    Return the relevant default folder for the current OS\n\n    :param str folder_type: The type of folder to retrieve (config, work or logs)\n\n    :returns: The relevant default folder.\n    :rtype: str\n\n    \"\"\"\n    if os.name != 'nt':\n        # Linux specific\n        return LINUX_DEFAULT_FOLDERS[folder_type]\n    # Windows specific\n    return WINDOWS_DEFAULT_FOLDERS[folder_type]\n\n\ndef underscores_for_unsupported_characters_in_path(path):\n    # type: (str) -> str\n    \"\"\"\n    Replace unsupported characters in path for current OS by underscores.\n    :param str path: the path to normalize\n    :return: the normalized path\n    :rtype: str\n    \"\"\"\n    if os.name != 'nt':\n        # Linux specific\n        return path\n\n    # Windows specific\n    drive, tail = os.path.splitdrive(path)\n    return drive + tail.replace(':', '_')\n"
  },
  {
    "path": "certbot/compat/os.py",
    "content": "\"\"\"\nThis compat modules is a wrapper of the core os module that forbids usage of specific operations\n(e.g. chown, chmod, getuid) that would be harmful to the Windows file security model of Certbot.\nThis module is intended to replace standard os module throughout certbot projects (except acme).\n\nisort:skip_file\n\"\"\"\n# pylint: disable=function-redefined\nfrom __future__ import absolute_import\n\n# First round of wrapping: we import statically all public attributes exposed by the os module\n# This allows in particular to have pylint, mypy, IDEs be aware that most of os members are\n# available in certbot.compat.os.\nfrom os import *  # type: ignore  # pylint: disable=wildcard-import,unused-wildcard-import,redefined-builtin,os-module-forbidden\n\n# Second round of wrapping: we import dynamically all attributes from the os module that have not\n# yet been imported by the first round (static import). This covers in particular the case of\n# specific python 3.x versions where not all public attributes are in the special __all__ of os,\n# and so not in `from os import *`.\nimport os as std_os  # pylint: disable=os-module-forbidden\nimport sys as std_sys\n\nourselves = std_sys.modules[__name__]\nfor attribute in dir(std_os):\n    # Check if the attribute does not already exist in our module. It could be internal attributes\n    # of the module (__name__, __doc__), or attributes from standard os already imported with\n    # `from os import *`.\n    if not hasattr(ourselves, attribute):\n        setattr(ourselves, attribute, getattr(std_os, attribute))\n\n# Import our internal path module, then allow certbot.compat.os.path\n# to behave as a module (similarly to os.path).\nfrom certbot.compat import _path as path  # type: ignore  # pylint: disable=wrong-import-position\nstd_sys.modules[__name__ + '.path'] = path\n\n# Clean all remaining importables that are not from the core os module.\ndel ourselves, std_os, std_sys\n\n\n# Chmod is the root of all evil for our security model on Windows. With the default implementation\n# of os.chmod on Windows, almost all bits on mode will be ignored, and only a general RO or RW will\n# be applied. The DACL, the inner mechanism to control file access on Windows, will stay on its\n# default definition, giving effectively at least read permissions to any one, as the default\n# permissions on root path will be inherit by the file (as NTFS state), and root path can be read\n# by anyone. So the given mode needs to be translated into a secured and not inherited DACL that\n# will be applied to this file using filesystem.chmod, calling internally the win32security\n# module to construct and apply the DACL. Complete security model to translate a POSIX mode into\n# a suitable DACL on Windows for Certbot can be found here:\n# https://github.com/certbot/certbot/issues/6356\n# Basically, it states that appropriate permissions will be set for the owner, nothing for the\n# group, appropriate permissions for the \"Everyone\" group, and all permissions to the\n# \"Administrators\" group + \"System\" user, as they can do everything anyway.\ndef chmod(*unused_args, **unused_kwargs):\n    \"\"\"Method os.chmod() is forbidden\"\"\"\n    raise RuntimeError('Usage of os.chmod() is forbidden. '\n                       'Use certbot.compat.filesystem.chmod() instead.')\n\n\n# Because uid is not a concept on Windows, chown is useless. In fact, it is not even available\n# on Python for Windows. So to be consistent on both platforms for Certbot, this method is\n# always forbidden.\ndef chown(*unused_args, **unused_kwargs):\n    \"\"\"Method os.chown() is forbidden\"\"\"\n    raise RuntimeError('Usage of os.chown() is forbidden.'\n                       'Use certbot.compat.filesystem.copy_ownership_and_apply_mode() instead.')\n\n\n# The os.open function on Windows has the same effect as a call to os.chown concerning the file\n# modes: these modes lack the correct control over the permissions given to the file. Instead,\n# filesystem.open invokes the Windows native API `CreateFile` to ensure that permissions are\n# atomically set in case of file creation, or invokes filesystem.chmod to properly set the\n# permissions for the other cases.\ndef open(*unused_args, **unused_kwargs):\n    \"\"\"Method os.open() is forbidden\"\"\"\n    raise RuntimeError('Usage of os.open() is forbidden. '\n                       'Use certbot.compat.filesystem.open() instead.')\n\n\n# Very similarly to os.open, os.mkdir has the same effects on Windows and creates an unsecured\n# folder. So a similar mitigation to security.chmod is provided on this platform.\ndef mkdir(*unused_args, **unused_kwargs):\n    \"\"\"Method os.mkdir() is forbidden\"\"\"\n    raise RuntimeError('Usage of os.mkdir() is forbidden. '\n                       'Use certbot.compat.filesystem.mkdir() instead.')\n\n\n# As said above, os.makedirs would call the original os.mkdir function recursively on Windows,\n# creating the same flaws for every actual folder created. This method is modified to ensure\n# that our modified os.mkdir is called on Windows, by monkey patching temporarily the mkdir method\n# on the original os module, executing the modified logic to correctly protect newly created\n# folders, then restoring original mkdir method in the os module.\ndef makedirs(*unused_args, **unused_kwargs):\n    \"\"\"Method os.makedirs() is forbidden\"\"\"\n    raise RuntimeError('Usage of os.makedirs() is forbidden. '\n                       'Use certbot.compat.filesystem.makedirs() instead.')\n\n\n# Because of the blocking strategy on file handlers on Windows, rename does not behave as expected\n# with POSIX systems: an exception will be raised if dst already exists.\ndef rename(*unused_args, **unused_kwargs):\n    \"\"\"Method os.rename() is forbidden\"\"\"\n    raise RuntimeError('Usage of os.rename() is forbidden. '\n                       'Use certbot.compat.filesystem.replace() instead.')\n\n\n# Behavior of os.replace is consistent between Windows and Linux. However, it is not supported on\n# Python 2.x. So, as for os.rename, we forbid it in favor of filesystem.replace.\ndef replace(*unused_args, **unused_kwargs):\n    \"\"\"Method os.replace() is forbidden\"\"\"\n    raise RuntimeError('Usage of os.replace() is forbidden. '\n                       'Use certbot.compat.filesystem.replace() instead.')\n\n\n# Results given by os.access are inconsistent or partial on Windows, because this platform is not\n# following the POSIX approach.\ndef access(*unused_args, **unused_kwargs):\n    \"\"\"Method os.access() is forbidden\"\"\"\n    raise RuntimeError('Usage of os.access() is forbidden. '\n                       'Use certbot.compat.filesystem.check_mode() or '\n                       'certbot.compat.filesystem.is_executable() instead.')\n\n\n# On Windows os.stat call result is inconsistent, with a lot of flags that are not set or\n# meaningless. We need to use specialized functions from the certbot.compat.filesystem module.\ndef stat(*unused_args, **unused_kwargs):\n    \"\"\"Method os.stat() is forbidden\"\"\"\n    raise RuntimeError('Usage of os.stat() is forbidden. '\n                       'Use certbot.compat.filesystem functions instead '\n                       '(eg. has_min_permissions, has_same_ownership).')\n\n\n# Method os.fstat has the same problem than os.stat, since it is the same function,\n# but accepting a file descriptor instead of a path.\ndef fstat(*unused_args, **unused_kwargs):\n    \"\"\"Method os.stat() is forbidden\"\"\"\n    raise RuntimeError('Usage of os.fstat() is forbidden. '\n                       'Use certbot.compat.filesystem functions instead '\n                       '(eg. has_min_permissions, has_same_ownership).')\n"
  },
  {
    "path": "certbot/crypto_util.py",
    "content": "\"\"\"Certbot client crypto utility functions.\n\n.. todo:: Make the transition to use PSS rather than PKCS1_v1_5 when the server\n    is capable of handling the signatures.\n\n\"\"\"\nimport hashlib\nimport logging\nimport warnings\n\n# See https://github.com/pyca/cryptography/issues/4275\nfrom cryptography import x509  # type: ignore\nfrom cryptography.exceptions import InvalidSignature\nfrom cryptography.hazmat.backends import default_backend\nfrom cryptography.hazmat.primitives.asymmetric.ec import ECDSA\nfrom cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey\nfrom cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15\nfrom cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey\nfrom OpenSSL import crypto\nfrom OpenSSL import SSL  # type: ignore\nimport pyrfc3339\nimport six\nimport zope.component\n\nfrom acme import crypto_util as acme_crypto_util\nfrom acme.magic_typing import IO  # pylint: disable=unused-import\nfrom certbot import errors\nfrom certbot import interfaces\nfrom certbot import util\nfrom certbot.compat import os\n\nlogger = logging.getLogger(__name__)\n\n\n# High level functions\ndef init_save_key(key_size, key_dir, keyname=\"key-certbot.pem\"):\n    \"\"\"Initializes and saves a privkey.\n\n    Inits key and saves it in PEM format on the filesystem.\n\n    .. note:: keyname is the attempted filename, it may be different if a file\n        already exists at the path.\n\n    :param int key_size: RSA key size in bits\n    :param str key_dir: Key save directory.\n    :param str keyname: Filename of key\n\n    :returns: Key\n    :rtype: :class:`certbot.util.Key`\n\n    :raises ValueError: If unable to generate the key given key_size.\n\n    \"\"\"\n    try:\n        key_pem = make_key(key_size)\n    except ValueError as err:\n        logger.error(\"\", exc_info=True)\n        raise err\n\n    config = zope.component.getUtility(interfaces.IConfig)\n    # Save file\n    util.make_or_verify_dir(key_dir, 0o700, config.strict_permissions)\n    key_f, key_path = util.unique_file(\n        os.path.join(key_dir, keyname), 0o600, \"wb\")\n    with key_f:\n        key_f.write(key_pem)\n    logger.debug(\"Generating key (%d bits): %s\", key_size, key_path)\n\n    return util.Key(key_path, key_pem)\n\n\ndef init_save_csr(privkey, names, path):\n    \"\"\"Initialize a CSR with the given private key.\n\n    :param privkey: Key to include in the CSR\n    :type privkey: :class:`certbot.util.Key`\n\n    :param set names: `str` names to include in the CSR\n\n    :param str path: Certificate save directory.\n\n    :returns: CSR\n    :rtype: :class:`certbot.util.CSR`\n\n    \"\"\"\n    config = zope.component.getUtility(interfaces.IConfig)\n\n    csr_pem = acme_crypto_util.make_csr(\n        privkey.pem, names, must_staple=config.must_staple)\n\n    # Save CSR\n    util.make_or_verify_dir(path, 0o755, config.strict_permissions)\n    csr_f, csr_filename = util.unique_file(\n        os.path.join(path, \"csr-certbot.pem\"), 0o644, \"wb\")\n    with csr_f:\n        csr_f.write(csr_pem)\n    logger.debug(\"Creating CSR: %s\", csr_filename)\n\n    return util.CSR(csr_filename, csr_pem, \"pem\")\n\n\n# WARNING: the csr and private key file are possible attack vectors for TOCTOU\n# We should either...\n# A. Do more checks to verify that the CSR is trusted/valid\n# B. Audit the parsing code for vulnerabilities\n\ndef valid_csr(csr):\n    \"\"\"Validate CSR.\n\n    Check if `csr` is a valid CSR for the given domains.\n\n    :param str csr: CSR in PEM.\n\n    :returns: Validity of CSR.\n    :rtype: bool\n\n    \"\"\"\n    try:\n        req = crypto.load_certificate_request(\n            crypto.FILETYPE_PEM, csr)\n        return req.verify(req.get_pubkey())\n    except crypto.Error:\n        logger.debug(\"\", exc_info=True)\n        return False\n\n\ndef csr_matches_pubkey(csr, privkey):\n    \"\"\"Does private key correspond to the subject public key in the CSR?\n\n    :param str csr: CSR in PEM.\n    :param str privkey: Private key file contents (PEM)\n\n    :returns: Correspondence of private key to CSR subject public key.\n    :rtype: bool\n\n    \"\"\"\n    req = crypto.load_certificate_request(\n        crypto.FILETYPE_PEM, csr)\n    pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, privkey)\n    try:\n        return req.verify(pkey)\n    except crypto.Error:\n        logger.debug(\"\", exc_info=True)\n        return False\n\n\ndef import_csr_file(csrfile, data):\n    \"\"\"Import a CSR file, which can be either PEM or DER.\n\n    :param str csrfile: CSR filename\n    :param str data: contents of the CSR file\n\n    :returns: (`crypto.FILETYPE_PEM`,\n               util.CSR object representing the CSR,\n               list of domains requested in the CSR)\n    :rtype: tuple\n\n    \"\"\"\n    PEM = crypto.FILETYPE_PEM\n    load = crypto.load_certificate_request\n    try:\n        # Try to parse as DER first, then fall back to PEM.\n        csr = load(crypto.FILETYPE_ASN1, data)\n    except crypto.Error:\n        try:\n            csr = load(PEM, data)\n        except crypto.Error:\n            raise errors.Error(\"Failed to parse CSR file: {0}\".format(csrfile))\n\n    domains = _get_names_from_loaded_cert_or_req(csr)\n    # Internally we always use PEM, so re-encode as PEM before returning.\n    data_pem = crypto.dump_certificate_request(PEM, csr)\n    return PEM, util.CSR(file=csrfile, data=data_pem, form=\"pem\"), domains\n\n\ndef make_key(bits):\n    \"\"\"Generate PEM encoded RSA key.\n\n    :param int bits: Number of bits, at least 1024.\n\n    :returns: new RSA key in PEM form with specified number of bits\n    :rtype: str\n\n    \"\"\"\n    assert bits >= 1024  # XXX\n    key = crypto.PKey()\n    key.generate_key(crypto.TYPE_RSA, bits)\n    return crypto.dump_privatekey(crypto.FILETYPE_PEM, key)\n\n\ndef valid_privkey(privkey):\n    \"\"\"Is valid RSA private key?\n\n    :param str privkey: Private key file contents in PEM\n\n    :returns: Validity of private key.\n    :rtype: bool\n\n    \"\"\"\n    try:\n        return crypto.load_privatekey(\n            crypto.FILETYPE_PEM, privkey).check()\n    except (TypeError, crypto.Error):\n        return False\n\n\ndef verify_renewable_cert(renewable_cert):\n    \"\"\"For checking that your certs were not corrupted on disk.\n\n    Several things are checked:\n        1. Signature verification for the cert.\n        2. That fullchain matches cert and chain when concatenated.\n        3. Check that the private key matches the certificate.\n\n    :param renewable_cert: cert to verify\n    :type renewable_cert: certbot.interfaces.RenewableCert\n\n    :raises errors.Error: If verification fails.\n    \"\"\"\n    verify_renewable_cert_sig(renewable_cert)\n    verify_fullchain(renewable_cert)\n    verify_cert_matches_priv_key(renewable_cert.cert_path, renewable_cert.key_path)\n\n\ndef verify_renewable_cert_sig(renewable_cert):\n    \"\"\"Verifies the signature of a RenewableCert object.\n\n    :param renewable_cert: cert to verify\n    :type renewable_cert: certbot.interfaces.RenewableCert\n\n    :raises errors.Error: If signature verification fails.\n    \"\"\"\n    try:\n        with open(renewable_cert.chain_path, 'rb') as chain_file:  # type: IO[bytes]\n            chain = x509.load_pem_x509_certificate(chain_file.read(), default_backend())\n        with open(renewable_cert.cert_path, 'rb') as cert_file:  # type: IO[bytes]\n            cert = x509.load_pem_x509_certificate(cert_file.read(), default_backend())\n        pk = chain.public_key()\n        with warnings.catch_warnings():\n            verify_signed_payload(pk, cert.signature, cert.tbs_certificate_bytes,\n                                  cert.signature_hash_algorithm)\n    except (IOError, ValueError, InvalidSignature) as e:\n        error_str = \"verifying the signature of the cert located at {0} has failed. \\\n                Details: {1}\".format(renewable_cert.cert_path, e)\n        logger.exception(error_str)\n        raise errors.Error(error_str)\n\n\ndef verify_signed_payload(public_key, signature, payload, signature_hash_algorithm):\n    \"\"\"Check the signature of a payload.\n\n    :param RSAPublicKey/EllipticCurvePublicKey public_key: the public_key to check signature\n    :param bytes signature: the signature bytes\n    :param bytes payload: the payload bytes\n    :param cryptography.hazmat.primitives.hashes.HashAlgorithm\n           signature_hash_algorithm: algorithm used to hash the payload\n\n    :raises InvalidSignature: If signature verification fails.\n    :raises errors.Error: If public key type is not supported\n    \"\"\"\n    with warnings.catch_warnings():\n        warnings.simplefilter(\"ignore\")\n        if isinstance(public_key, RSAPublicKey):\n            # https://github.com/python/typeshed/blob/master/third_party/2/cryptography/hazmat/primitives/asymmetric/rsa.pyi\n            verifier = public_key.verifier(  # type: ignore\n                signature, PKCS1v15(), signature_hash_algorithm\n            )\n            verifier.update(payload)\n            verifier.verify()\n        elif isinstance(public_key, EllipticCurvePublicKey):\n            verifier = public_key.verifier(\n                signature, ECDSA(signature_hash_algorithm)\n            )\n            verifier.update(payload)\n            verifier.verify()\n        else:\n            raise errors.Error(\"Unsupported public key type\")\n\n\ndef verify_cert_matches_priv_key(cert_path, key_path):\n    \"\"\" Verifies that the private key and cert match.\n\n    :param str cert_path: path to a cert in PEM format\n    :param str key_path: path to a private key file\n\n    :raises errors.Error: If they don't match.\n    \"\"\"\n    try:\n        context = SSL.Context(SSL.SSLv23_METHOD)\n        context.use_certificate_file(cert_path)\n        context.use_privatekey_file(key_path)\n        context.check_privatekey()\n    except (IOError, SSL.Error) as e:\n        error_str = \"verifying the cert located at {0} matches the \\\n                private key located at {1} has failed. \\\n                Details: {2}\".format(cert_path,\n                        key_path, e)\n        logger.exception(error_str)\n        raise errors.Error(error_str)\n\n\ndef verify_fullchain(renewable_cert):\n    \"\"\" Verifies that fullchain is indeed cert concatenated with chain.\n\n    :param renewable_cert: cert to verify\n    :type renewable_cert: certbot.interfaces.RenewableCert\n\n    :raises errors.Error: If cert and chain do not combine to fullchain.\n    \"\"\"\n    try:\n        with open(renewable_cert.chain_path) as chain_file:  # type: IO[str]\n            chain = chain_file.read()\n        with open(renewable_cert.cert_path) as cert_file:  # type: IO[str]\n            cert = cert_file.read()\n        with open(renewable_cert.fullchain_path) as fullchain_file:  # type: IO[str]\n            fullchain = fullchain_file.read()\n        if (cert + chain) != fullchain:\n            error_str = \"fullchain does not match cert + chain for {0}!\"\n            error_str = error_str.format(renewable_cert.lineagename)\n            raise errors.Error(error_str)\n    except IOError as e:\n        error_str = \"reading one of cert, chain, or fullchain has failed: {0}\".format(e)\n        logger.exception(error_str)\n        raise errors.Error(error_str)\n    except errors.Error as e:\n        raise e\n\n\ndef pyopenssl_load_certificate(data):\n    \"\"\"Load PEM/DER certificate.\n\n    :raises errors.Error:\n\n    \"\"\"\n\n    openssl_errors = []\n\n    for file_type in (crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1):\n        try:\n            return crypto.load_certificate(file_type, data), file_type\n        except crypto.Error as error:  # TODO: other errors?\n            openssl_errors.append(error)\n    raise errors.Error(\"Unable to load: {0}\".format(\",\".join(\n        str(error) for error in openssl_errors)))\n\n\ndef _load_cert_or_req(cert_or_req_str, load_func,\n                      typ=crypto.FILETYPE_PEM):\n    try:\n        return load_func(typ, cert_or_req_str)\n    except crypto.Error:\n        logger.error(\"\", exc_info=True)\n        raise\n\n\ndef _get_sans_from_cert_or_req(cert_or_req_str, load_func,\n                               typ=crypto.FILETYPE_PEM):\n    # pylint: disable=protected-access\n    return acme_crypto_util._pyopenssl_cert_or_req_san(_load_cert_or_req(\n        cert_or_req_str, load_func, typ))\n\n\ndef get_sans_from_cert(cert, typ=crypto.FILETYPE_PEM):\n    \"\"\"Get a list of Subject Alternative Names from a certificate.\n\n    :param str cert: Certificate (encoded).\n    :param typ: `crypto.FILETYPE_PEM` or `crypto.FILETYPE_ASN1`\n\n    :returns: A list of Subject Alternative Names.\n    :rtype: list\n\n    \"\"\"\n    return _get_sans_from_cert_or_req(\n        cert, crypto.load_certificate, typ)\n\n\ndef _get_names_from_cert_or_req(cert_or_req, load_func, typ):\n    loaded_cert_or_req = _load_cert_or_req(cert_or_req, load_func, typ)\n    return _get_names_from_loaded_cert_or_req(loaded_cert_or_req)\n\n\ndef _get_names_from_loaded_cert_or_req(loaded_cert_or_req):\n    # pylint: disable=protected-access\n    return acme_crypto_util._pyopenssl_cert_or_req_all_names(loaded_cert_or_req)\n\n\ndef get_names_from_cert(csr, typ=crypto.FILETYPE_PEM):\n    \"\"\"Get a list of domains from a cert, including the CN if it is set.\n\n    :param str cert: Certificate (encoded).\n    :param typ: `crypto.FILETYPE_PEM` or `crypto.FILETYPE_ASN1`\n\n    :returns: A list of domain names.\n    :rtype: list\n\n    \"\"\"\n    return _get_names_from_cert_or_req(\n        csr, crypto.load_certificate, typ)\n\n\ndef dump_pyopenssl_chain(chain, filetype=crypto.FILETYPE_PEM):\n    \"\"\"Dump certificate chain into a bundle.\n\n    :param list chain: List of `crypto.X509` (or wrapped in\n        :class:`josepy.util.ComparableX509`).\n\n    \"\"\"\n    # XXX: returns empty string when no chain is available, which\n    # shuts up RenewableCert, but might not be the best solution...\n    return acme_crypto_util.dump_pyopenssl_chain(chain, filetype)\n\n\ndef notBefore(cert_path):\n    \"\"\"When does the cert at cert_path start being valid?\n\n    :param str cert_path: path to a cert in PEM format\n\n    :returns: the notBefore value from the cert at cert_path\n    :rtype: :class:`datetime.datetime`\n\n    \"\"\"\n    return _notAfterBefore(cert_path, crypto.X509.get_notBefore)\n\n\ndef notAfter(cert_path):\n    \"\"\"When does the cert at cert_path stop being valid?\n\n    :param str cert_path: path to a cert in PEM format\n\n    :returns: the notAfter value from the cert at cert_path\n    :rtype: :class:`datetime.datetime`\n\n    \"\"\"\n    return _notAfterBefore(cert_path, crypto.X509.get_notAfter)\n\n\ndef _notAfterBefore(cert_path, method):\n    \"\"\"Internal helper function for finding notbefore/notafter.\n\n    :param str cert_path: path to a cert in PEM format\n    :param function method: one of ``crypto.X509.get_notBefore``\n        or ``crypto.X509.get_notAfter``\n\n    :returns: the notBefore or notAfter value from the cert at cert_path\n    :rtype: :class:`datetime.datetime`\n\n    \"\"\"\n    # pylint: disable=redefined-outer-name\n    with open(cert_path) as f:\n        x509 = crypto.load_certificate(crypto.FILETYPE_PEM,\n                                               f.read())\n    # pyopenssl always returns bytes\n    timestamp = method(x509)\n    reformatted_timestamp = [timestamp[0:4], b\"-\", timestamp[4:6], b\"-\",\n                             timestamp[6:8], b\"T\", timestamp[8:10], b\":\",\n                             timestamp[10:12], b\":\", timestamp[12:]]\n    timestamp_str = b\"\".join(reformatted_timestamp)\n    # pyrfc3339 uses \"native\" strings. That is, bytes on Python 2 and unicode\n    # on Python 3\n    if six.PY3:\n        timestamp_str = timestamp_str.decode('ascii')\n    return pyrfc3339.parse(timestamp_str)\n\n\ndef sha256sum(filename):\n    \"\"\"Compute a sha256sum of a file.\n\n    NB: In given file, platform specific newlines characters will be converted\n    into their equivalent unicode counterparts before calculating the hash.\n\n    :param str filename: path to the file whose hash will be computed\n\n    :returns: sha256 digest of the file in hexadecimal\n    :rtype: str\n    \"\"\"\n    sha256 = hashlib.sha256()\n    with open(filename, 'r') as file_d:\n        sha256.update(file_d.read().encode('UTF-8'))\n    return sha256.hexdigest()\n\ndef cert_and_chain_from_fullchain(fullchain_pem):\n    \"\"\"Split fullchain_pem into cert_pem and chain_pem\n\n    :param str fullchain_pem: concatenated cert + chain\n\n    :returns: tuple of string cert_pem and chain_pem\n    :rtype: tuple\n\n    \"\"\"\n    cert = crypto.dump_certificate(crypto.FILETYPE_PEM,\n        crypto.load_certificate(crypto.FILETYPE_PEM, fullchain_pem)).decode()\n    chain = fullchain_pem[len(cert):].lstrip()\n    return (cert, chain)\n"
  },
  {
    "path": "certbot/display/__init__.py",
    "content": "\"\"\"Certbot display utilities.\"\"\"\n"
  },
  {
    "path": "certbot/display/ops.py",
    "content": "\"\"\"Contains UI methods for LE user operations.\"\"\"\nimport logging\n\nimport zope.component\n\nfrom certbot import errors\nfrom certbot import interfaces\nfrom certbot import util\nfrom certbot.compat import misc\nfrom certbot.compat import os\nfrom certbot.display import util as display_util\n\nlogger = logging.getLogger(__name__)\n\n# Define a helper function to avoid verbose code\nz_util = zope.component.getUtility\n\n\ndef get_email(invalid=False, optional=True):\n    \"\"\"Prompt for valid email address.\n\n    :param bool invalid: True if an invalid address was provided by the user\n    :param bool optional: True if the user can use\n        --register-unsafely-without-email to avoid providing an e-mail\n\n    :returns: e-mail address\n    :rtype: str\n\n    :raises errors.Error: if the user cancels\n\n    \"\"\"\n    invalid_prefix = \"There seem to be problems with that address. \"\n    msg = \"Enter email address (used for urgent renewal and security notices)\"\n    unsafe_suggestion = (\"\\n\\nIf you really want to skip this, you can run \"\n                         \"the client with --register-unsafely-without-email \"\n                         \"but make sure you then backup your account key from \"\n                         \"{0}\\n\\n\".format(os.path.join(\n                             misc.get_default_folder('config'), 'accounts')))\n    if optional:\n        if invalid:\n            msg += unsafe_suggestion\n            suggest_unsafe = False\n        else:\n            suggest_unsafe = True\n    else:\n        suggest_unsafe = False\n\n    while True:\n        try:\n            code, email = z_util(interfaces.IDisplay).input(\n                invalid_prefix + msg if invalid else msg,\n                force_interactive=True)\n        except errors.MissingCommandlineFlag:\n            msg = (\"You should register before running non-interactively, \"\n                   \"or provide --agree-tos and --email <email_address> flags.\")\n            raise errors.MissingCommandlineFlag(msg)\n\n        if code != display_util.OK:\n            if optional:\n                raise errors.Error(\n                    \"An e-mail address or \"\n                    \"--register-unsafely-without-email must be provided.\")\n            raise errors.Error(\"An e-mail address must be provided.\")\n        if util.safe_email(email):\n            return email\n        if suggest_unsafe:\n            msg += unsafe_suggestion\n            suggest_unsafe = False  # add this message at most once\n\n        invalid = bool(email)\n\n\ndef choose_account(accounts):\n    \"\"\"Choose an account.\n\n    :param list accounts: Containing at least one\n        :class:`~certbot._internal.account.Account`\n\n    \"\"\"\n    # Note this will get more complicated once we start recording authorizations\n    labels = [acc.slug for acc in accounts]\n\n    code, index = z_util(interfaces.IDisplay).menu(\n        \"Please choose an account\", labels, force_interactive=True)\n    if code == display_util.OK:\n        return accounts[index]\n    return None\n\ndef choose_values(values, question=None):\n    \"\"\"Display screen to let user pick one or multiple values from the provided\n    list.\n\n    :param list values: Values to select from\n\n    :returns: List of selected values\n    :rtype: list\n    \"\"\"\n    code, items = z_util(interfaces.IDisplay).checklist(\n        question, tags=values, force_interactive=True)\n    if code == display_util.OK and items:\n        return items\n    return []\n\ndef choose_names(installer, question=None):\n    \"\"\"Display screen to select domains to validate.\n\n    :param installer: An installer object\n    :type installer: :class:`certbot.interfaces.IInstaller`\n\n    :param `str` question: Overriding default question to ask the user if asked\n        to choose from domain names.\n\n    :returns: List of selected names\n    :rtype: `list` of `str`\n\n    \"\"\"\n    if installer is None:\n        logger.debug(\"No installer, picking names manually\")\n        return _choose_names_manually()\n\n    domains = list(installer.get_all_names())\n    names = get_valid_domains(domains)\n\n    if not names:\n        return _choose_names_manually(\n            \"No names were found in your configuration files. \")\n\n    code, names = _filter_names(names, question)\n    if code == display_util.OK and names:\n        return names\n    return []\n\n\ndef get_valid_domains(domains):\n    \"\"\"Helper method for choose_names that implements basic checks\n     on domain names\n\n    :param list domains: Domain names to validate\n    :return: List of valid domains\n    :rtype: list\n    \"\"\"\n    valid_domains = []\n    for domain in domains:\n        try:\n            valid_domains.append(util.enforce_domain_sanity(domain))\n        except errors.ConfigurationError:\n            continue\n    return valid_domains\n\ndef _sort_names(FQDNs):\n    \"\"\"Sort FQDNs by SLD (and if many, by their subdomains)\n\n    :param list FQDNs: list of domain names\n\n    :returns: Sorted list of domain names\n    :rtype: list\n    \"\"\"\n    return sorted(FQDNs, key=lambda fqdn: fqdn.split('.')[::-1][1:])\n\n\ndef _filter_names(names, override_question=None):\n    \"\"\"Determine which names the user would like to select from a list.\n\n    :param list names: domain names\n\n    :returns: tuple of the form (`code`, `names`) where\n        `code` - str display exit code\n        `names` - list of names selected\n    :rtype: tuple\n\n    \"\"\"\n    #Sort by domain first, and then by subdomain\n    sorted_names = _sort_names(names)\n    if override_question:\n        question = override_question\n    else:\n        question = \"Which names would you like to activate HTTPS for?\"\n    code, names = z_util(interfaces.IDisplay).checklist(\n        question, tags=sorted_names, cli_flag=\"--domains\", force_interactive=True)\n    return code, [str(s) for s in names]\n\n\ndef _choose_names_manually(prompt_prefix=\"\"):\n    \"\"\"Manually input names for those without an installer.\n\n    :param str prompt_prefix: string to prepend to prompt for domains\n\n    :returns: list of provided names\n    :rtype: `list` of `str`\n\n    \"\"\"\n    code, input_ = z_util(interfaces.IDisplay).input(\n        prompt_prefix +\n        \"Please enter in your domain name(s) (comma and/or space separated) \",\n        cli_flag=\"--domains\", force_interactive=True)\n\n    if code == display_util.OK:\n        invalid_domains = dict()\n        retry_message = \"\"\n        try:\n            domain_list = display_util.separate_list_input(input_)\n        except UnicodeEncodeError:\n            domain_list = []\n            retry_message = (\n                \"Internationalized domain names are not presently \"\n                \"supported.{0}{0}Would you like to re-enter the \"\n                \"names?{0}\").format(os.linesep)\n\n        for i, domain in enumerate(domain_list):\n            try:\n                domain_list[i] = util.enforce_domain_sanity(domain)\n            except errors.ConfigurationError as e:\n                invalid_domains[domain] = str(e)\n\n        if invalid_domains:\n            retry_message = (\n                \"One or more of the entered domain names was not valid:\"\n                \"{0}{0}\").format(os.linesep)\n            for domain in invalid_domains:\n                retry_message = retry_message + \"{1}: {2}{0}\".format(\n                    os.linesep, domain, invalid_domains[domain])\n            retry_message = retry_message + (\n                \"{0}Would you like to re-enter the names?{0}\").format(\n                    os.linesep)\n\n        if retry_message:\n            # We had error in input\n            retry = z_util(interfaces.IDisplay).yesno(retry_message,\n                                                      force_interactive=True)\n            if retry:\n                return _choose_names_manually()\n        else:\n            return domain_list\n    return []\n\n\ndef success_installation(domains):\n    \"\"\"Display a box confirming the installation of HTTPS.\n\n    :param list domains: domain names which were enabled\n\n    \"\"\"\n    z_util(interfaces.IDisplay).notification(\n        \"Congratulations! You have successfully enabled {0}{1}{1}\"\n        \"You should test your configuration at:{1}{2}\".format(\n            _gen_https_names(domains),\n            os.linesep,\n            os.linesep.join(_gen_ssl_lab_urls(domains))),\n        pause=False)\n\n\ndef success_renewal(domains):\n    \"\"\"Display a box confirming the renewal of an existing certificate.\n\n    :param list domains: domain names which were renewed\n\n    \"\"\"\n    z_util(interfaces.IDisplay).notification(\n        \"Your existing certificate has been successfully renewed, and the \"\n        \"new certificate has been installed.{1}{1}\"\n        \"The new certificate covers the following domains: {0}{1}{1}\"\n        \"You should test your configuration at:{1}{2}\".format(\n            _gen_https_names(domains),\n            os.linesep,\n            os.linesep.join(_gen_ssl_lab_urls(domains))),\n        pause=False)\n\ndef success_revocation(cert_path):\n    \"\"\"Display a box confirming a certificate has been revoked.\n\n    :param list cert_path: path to certificate which was revoked.\n\n    \"\"\"\n    z_util(interfaces.IDisplay).notification(\n        \"Congratulations! You have successfully revoked the certificate \"\n        \"that was located at {0}{1}{1}\".format(\n            cert_path,\n            os.linesep),\n        pause=False)\n\n\ndef _gen_ssl_lab_urls(domains):\n    \"\"\"Returns a list of urls.\n\n    :param list domains: Each domain is a 'str'\n\n    \"\"\"\n    return [\"https://www.ssllabs.com/ssltest/analyze.html?d=%s\" % dom for dom in domains]\n\n\ndef _gen_https_names(domains):\n    \"\"\"Returns a string of the https domains.\n\n    Domains are formatted nicely with ``https://`` prepended to each.\n\n    :param list domains: Each domain is a 'str'\n\n    \"\"\"\n    if len(domains) == 1:\n        return \"https://{0}\".format(domains[0])\n    elif len(domains) == 2:\n        return \"https://{dom[0]} and https://{dom[1]}\".format(dom=domains)\n    elif len(domains) > 2:\n        return \"{0}{1}{2}\".format(\n            \", \".join(\"https://%s\" % dom for dom in domains[:-1]),\n            \", and https://\",\n            domains[-1])\n\n    return \"\"\n\n\ndef _get_validated(method, validator, message, default=None, **kwargs):\n    if default is not None:\n        try:\n            validator(default)\n        except errors.Error as error:\n            logger.debug('Encountered invalid default value \"%s\" when prompting for \"%s\"',\n                         default,\n                         message,\n                         exc_info=True)\n            raise AssertionError('Invalid default \"{0}\"'.format(default))\n\n    while True:\n        code, raw = method(message, default=default, **kwargs)\n        if code == display_util.OK:\n            try:\n                validator(raw)\n                return code, raw\n            except errors.Error as error:\n                logger.debug('Validator rejected \"%s\" when prompting for \"%s\"',\n                             raw,\n                             message,\n                             exc_info=True)\n                zope.component.getUtility(interfaces.IDisplay).notification(str(error), pause=False)\n        else:\n            return code, raw\n\n\ndef validated_input(validator, *args, **kwargs):\n    \"\"\"Like `~certbot.interfaces.IDisplay.input`, but with validation.\n\n    :param callable validator: A method which will be called on the\n        supplied input. If the method raises an `errors.Error`, its\n        text will be displayed and the user will be re-prompted.\n    :param list `*args`: Arguments to be passed to `~certbot.interfaces.IDisplay.input`.\n    :param dict `**kwargs`: Arguments to be passed to `~certbot.interfaces.IDisplay.input`.\n    :return: as `~certbot.interfaces.IDisplay.input`\n    :rtype: tuple\n    \"\"\"\n    return _get_validated(zope.component.getUtility(interfaces.IDisplay).input,\n                          validator, *args, **kwargs)\n\n\ndef validated_directory(validator, *args, **kwargs):\n    \"\"\"Like `~certbot.interfaces.IDisplay.directory_select`, but with validation.\n\n    :param callable validator: A method which will be called on the\n        supplied input. If the method raises an `errors.Error`, its\n        text will be displayed and the user will be re-prompted.\n    :param list `*args`: Arguments to be passed to `~certbot.interfaces.IDisplay.directory_select`.\n    :param dict `**kwargs`: Arguments to be passed to\n        `~certbot.interfaces.IDisplay.directory_select`.\n    :return: as `~certbot.interfaces.IDisplay.directory_select`\n    :rtype: tuple\n    \"\"\"\n    return _get_validated(zope.component.getUtility(interfaces.IDisplay).directory_select,\n                          validator, *args, **kwargs)\n"
  },
  {
    "path": "certbot/display/util.py",
    "content": "\"\"\"Certbot display.\"\"\"\nimport logging\nimport sys\nimport textwrap\n\nimport zope.interface\n\nfrom certbot import errors\nfrom certbot import interfaces\nfrom certbot._internal import constants\nfrom certbot._internal.display import completer\nfrom certbot.compat import misc\nfrom certbot.compat import os\n\nlogger = logging.getLogger(__name__)\n\nWIDTH = 72\n\n# Display exit codes\nOK = \"ok\"\n\"\"\"Display exit code indicating user acceptance.\"\"\"\n\nCANCEL = \"cancel\"\n\"\"\"Display exit code for a user canceling the display.\"\"\"\n\nHELP = \"help\"\n\"\"\"Display exit code when for when the user requests more help. (UNUSED)\"\"\"\n\nESC = \"esc\"\n\"\"\"Display exit code when the user hits Escape (UNUSED)\"\"\"\n\n# Display constants\nSIDE_FRAME = (\"- \" * 39) + \"-\"\n\"\"\"Display boundary (alternates spaces, so when copy-pasted, markdown doesn't interpret\nit as a heading)\"\"\"\n\ndef _wrap_lines(msg):\n    \"\"\"Format lines nicely to 80 chars.\n\n    :param str msg: Original message\n\n    :returns: Formatted message respecting newlines in message\n    :rtype: str\n\n    \"\"\"\n    lines = msg.splitlines()\n    fixed_l = []\n\n    for line in lines:\n        fixed_l.append(textwrap.fill(\n            line,\n            80,\n            break_long_words=False,\n            break_on_hyphens=False))\n\n    return '\\n'.join(fixed_l)\n\n\ndef input_with_timeout(prompt=None, timeout=36000.0):\n    \"\"\"Get user input with a timeout.\n\n    Behaves the same as six.moves.input, however, an error is raised if\n    a user doesn't answer after timeout seconds. The default timeout\n    value was chosen to place it just under 12 hours for users following\n    our advice and running Certbot twice a day.\n\n    :param str prompt: prompt to provide for input\n    :param float timeout: maximum number of seconds to wait for input\n\n    :returns: user response\n    :rtype: str\n\n    :raises errors.Error if no answer is given before the timeout\n\n    \"\"\"\n    # use of sys.stdin and sys.stdout to mimic six.moves.input based on\n    # https://github.com/python/cpython/blob/baf7bb30a02aabde260143136bdf5b3738a1d409/Lib/getpass.py#L129\n    if prompt:\n        sys.stdout.write(prompt)\n        sys.stdout.flush()\n\n    line = misc.readline_with_timeout(timeout, prompt)\n\n    if not line:\n        raise EOFError\n    return line.rstrip('\\n')\n\n\n@zope.interface.implementer(interfaces.IDisplay)\nclass FileDisplay(object):\n    \"\"\"File-based display.\"\"\"\n    # see https://github.com/certbot/certbot/issues/3915\n\n    def __init__(self, outfile, force_interactive):\n        super(FileDisplay, self).__init__()\n        self.outfile = outfile\n        self.force_interactive = force_interactive\n        self.skipped_interaction = False\n\n    def notification(self, message, pause=True,\n                     wrap=True, force_interactive=False):\n        \"\"\"Displays a notification and waits for user acceptance.\n\n        :param str message: Message to display\n        :param bool pause: Whether or not the program should pause for the\n            user's confirmation\n        :param bool wrap: Whether or not the application should wrap text\n        :param bool force_interactive: True if it's safe to prompt the user\n            because it won't cause any workflow regressions\n\n        \"\"\"\n        if wrap:\n            message = _wrap_lines(message)\n        self.outfile.write(\n            \"{line}{frame}{line}{msg}{line}{frame}{line}\".format(\n                line='\\n', frame=SIDE_FRAME, msg=message))\n        self.outfile.flush()\n        if pause:\n            if self._can_interact(force_interactive):\n                input_with_timeout(\"Press Enter to Continue\")\n            else:\n                logger.debug(\"Not pausing for user confirmation\")\n\n    def menu(self, message, choices, ok_label=None, cancel_label=None,  # pylint: disable=unused-argument\n             help_label=None, default=None,  # pylint: disable=unused-argument\n             cli_flag=None, force_interactive=False, **unused_kwargs):\n        \"\"\"Display a menu.\n\n        .. todo:: This doesn't enable the help label/button (I wasn't sold on\n           any interface I came up with for this). It would be a nice feature\n\n        :param str message: title of menu\n        :param choices: Menu lines, len must be > 0\n        :type choices: list of tuples (tag, item) or\n            list of descriptions (tags will be enumerated)\n        :param default: default value to return (if one exists)\n        :param str cli_flag: option used to set this value with the CLI\n        :param bool force_interactive: True if it's safe to prompt the user\n            because it won't cause any workflow regressions\n\n        :returns: tuple of (`code`, `index`) where\n            `code` - str display exit code\n            `index` - int index of the user's selection\n\n        :rtype: tuple\n\n        \"\"\"\n        if self._return_default(message, default, cli_flag, force_interactive):\n            return OK, default\n\n        self._print_menu(message, choices)\n\n        code, selection = self._get_valid_int_ans(len(choices))\n\n        return code, selection - 1\n\n    def input(self, message, default=None,\n              cli_flag=None, force_interactive=False, **unused_kwargs):\n        \"\"\"Accept input from the user.\n\n        :param str message: message to display to the user\n        :param default: default value to return (if one exists)\n        :param str cli_flag: option used to set this value with the CLI\n        :param bool force_interactive: True if it's safe to prompt the user\n            because it won't cause any workflow regressions\n\n        :returns: tuple of (`code`, `input`) where\n            `code` - str display exit code\n            `input` - str of the user's input\n        :rtype: tuple\n\n        \"\"\"\n        if self._return_default(message, default, cli_flag, force_interactive):\n            return OK, default\n\n        # Trailing space must be added outside of _wrap_lines to be preserved\n        message = _wrap_lines(\"%s (Enter 'c' to cancel):\" % message) + \" \"\n        ans = input_with_timeout(message)\n\n        if ans in (\"c\", \"C\"):\n            return CANCEL, \"-1\"\n        return OK, ans\n\n    def yesno(self, message, yes_label=\"Yes\", no_label=\"No\", default=None,\n              cli_flag=None, force_interactive=False, **unused_kwargs):\n        \"\"\"Query the user with a yes/no question.\n\n        Yes and No label must begin with different letters, and must contain at\n        least one letter each.\n\n        :param str message: question for the user\n        :param str yes_label: Label of the \"Yes\" parameter\n        :param str no_label: Label of the \"No\" parameter\n        :param default: default value to return (if one exists)\n        :param str cli_flag: option used to set this value with the CLI\n        :param bool force_interactive: True if it's safe to prompt the user\n            because it won't cause any workflow regressions\n\n        :returns: True for \"Yes\", False for \"No\"\n        :rtype: bool\n\n        \"\"\"\n        if self._return_default(message, default, cli_flag, force_interactive):\n            return default\n\n        message = _wrap_lines(message)\n\n        self.outfile.write(\"{0}{frame}{msg}{0}{frame}\".format(\n            os.linesep, frame=SIDE_FRAME + os.linesep, msg=message))\n        self.outfile.flush()\n\n        while True:\n            ans = input_with_timeout(\"{yes}/{no}: \".format(\n                yes=_parens_around_char(yes_label),\n                no=_parens_around_char(no_label)))\n\n            # Couldn't get pylint indentation right with elif\n            # elif doesn't matter in this situation\n            if (ans.startswith(yes_label[0].lower()) or\n                    ans.startswith(yes_label[0].upper())):\n                return True\n            if (ans.startswith(no_label[0].lower()) or\n                    ans.startswith(no_label[0].upper())):\n                return False\n\n    def checklist(self, message, tags, default=None,\n                  cli_flag=None, force_interactive=False, **unused_kwargs):\n        \"\"\"Display a checklist.\n\n        :param str message: Message to display to user\n        :param list tags: `str` tags to select, len(tags) > 0\n        :param default: default value to return (if one exists)\n        :param str cli_flag: option used to set this value with the CLI\n        :param bool force_interactive: True if it's safe to prompt the user\n            because it won't cause any workflow regressions\n\n        :returns: tuple of (`code`, `tags`) where\n            `code` - str display exit code\n            `tags` - list of selected tags\n        :rtype: tuple\n\n        \"\"\"\n        if self._return_default(message, default, cli_flag, force_interactive):\n            return OK, default\n\n        while True:\n            self._print_menu(message, tags)\n\n            code, ans = self.input(\"Select the appropriate numbers separated \"\n                                   \"by commas and/or spaces, or leave input \"\n                                   \"blank to select all options shown\",\n                                   force_interactive=True)\n\n            if code == OK:\n                if not ans.strip():\n                    ans = \" \".join(str(x) for x in range(1, len(tags)+1))\n                indices = separate_list_input(ans)\n                selected_tags = self._scrub_checklist_input(indices, tags)\n                if selected_tags:\n                    return code, selected_tags\n                self.outfile.write(\n                    \"** Error - Invalid selection **%s\" % os.linesep)\n                self.outfile.flush()\n            else:\n                return code, []\n\n    def _return_default(self, prompt, default, cli_flag, force_interactive):\n        \"\"\"Should we return the default instead of prompting the user?\n\n        :param str prompt: prompt for the user\n        :param default: default answer to prompt\n        :param str cli_flag: command line option for setting an answer\n            to this question\n        :param bool force_interactive: if interactivity is forced by the\n            IDisplay call\n\n        :returns: True if we should return the default without prompting\n        :rtype: bool\n\n        \"\"\"\n        # assert_valid_call(prompt, default, cli_flag, force_interactive)\n        if self._can_interact(force_interactive):\n            return False\n        if default is None:\n            msg = \"Unable to get an answer for the question:\\n{0}\".format(prompt)\n            if cli_flag:\n                msg += (\n                    \"\\nYou can provide an answer on the \"\n                    \"command line with the {0} flag.\".format(cli_flag))\n            raise errors.Error(msg)\n        logger.debug(\n            \"Falling back to default %s for the prompt:\\n%s\",\n            default, prompt)\n        return True\n\n    def _can_interact(self, force_interactive):\n        \"\"\"Can we safely interact with the user?\n\n        :param bool force_interactive: if interactivity is forced by the\n            IDisplay call\n\n        :returns: True if the display can interact with the user\n        :rtype: bool\n\n        \"\"\"\n        if (self.force_interactive or force_interactive or\n                sys.stdin.isatty() and self.outfile.isatty()):\n            return True\n        if not self.skipped_interaction:\n            logger.warning(\n                \"Skipped user interaction because Certbot doesn't appear to \"\n                \"be running in a terminal. You should probably include \"\n                \"--non-interactive or %s on the command line.\",\n                constants.FORCE_INTERACTIVE_FLAG)\n            self.skipped_interaction = True\n        return False\n\n    def directory_select(self, message, default=None, cli_flag=None,\n                         force_interactive=False, **unused_kwargs):\n        \"\"\"Display a directory selection screen.\n\n        :param str message: prompt to give the user\n        :param default: default value to return (if one exists)\n        :param str cli_flag: option used to set this value with the CLI\n        :param bool force_interactive: True if it's safe to prompt the user\n            because it won't cause any workflow regressions\n\n        :returns: tuple of the form (`code`, `string`) where\n            `code` - display exit code\n            `string` - input entered by the user\n\n        \"\"\"\n        with completer.Completer():\n            return self.input(message, default, cli_flag, force_interactive)\n\n    def _scrub_checklist_input(self, indices, tags):\n        \"\"\"Validate input and transform indices to appropriate tags.\n\n        :param list indices: input\n        :param list tags: Original tags of the checklist\n\n        :returns: valid tags the user selected\n        :rtype: :class:`list` of :class:`str`\n\n        \"\"\"\n        # They should all be of type int\n        try:\n            indices = [int(index) for index in indices]\n        except ValueError:\n            return []\n\n        # Remove duplicates\n        indices = list(set(indices))\n\n        # Check all input is within range\n        for index in indices:\n            if index < 1 or index > len(tags):\n                return []\n        # Transform indices to appropriate tags\n        return [tags[index - 1] for index in indices]\n\n    def _print_menu(self, message, choices):\n        \"\"\"Print a menu on the screen.\n\n        :param str message: title of menu\n        :param choices: Menu lines\n        :type choices: list of tuples (tag, item) or\n            list of descriptions (tags will be enumerated)\n\n        \"\"\"\n        # Can take either tuples or single items in choices list\n        if choices and isinstance(choices[0], tuple):\n            choices = [\"%s - %s\" % (c[0], c[1]) for c in choices]\n\n        # Write out the message to the user\n        self.outfile.write(\n            \"{new}{msg}{new}\".format(new=os.linesep, msg=message))\n        self.outfile.write(SIDE_FRAME + os.linesep)\n\n        # Write out the menu choices\n        for i, desc in enumerate(choices, 1):\n            msg = \"{num}: {desc}\".format(num=i, desc=desc)\n            self.outfile.write(_wrap_lines(msg))\n\n            # Keep this outside of the textwrap\n            self.outfile.write(os.linesep)\n\n        self.outfile.write(SIDE_FRAME + os.linesep)\n        self.outfile.flush()\n\n    def _get_valid_int_ans(self, max_):\n        \"\"\"Get a numerical selection.\n\n        :param int max: The maximum entry (len of choices), must be positive\n\n        :returns: tuple of the form (`code`, `selection`) where\n            `code` - str display exit code ('ok' or cancel')\n            `selection` - int user's selection\n        :rtype: tuple\n\n        \"\"\"\n        selection = -1\n        if max_ > 1:\n            input_msg = (\"Select the appropriate number \"\n                         \"[1-{max_}] then [enter] (press 'c' to \"\n                         \"cancel): \".format(max_=max_))\n        else:\n            input_msg = (\"Press 1 [enter] to confirm the selection \"\n                         \"(press 'c' to cancel): \")\n        while selection < 1:\n            ans = input_with_timeout(input_msg)\n            if ans.startswith(\"c\") or ans.startswith(\"C\"):\n                return CANCEL, -1\n            try:\n                selection = int(ans)\n                if selection < 1 or selection > max_:\n                    selection = -1\n                    raise ValueError\n\n            except ValueError:\n                self.outfile.write(\n                    \"{0}** Invalid input **{0}\".format(os.linesep))\n                self.outfile.flush()\n\n        return OK, selection\n\n\ndef assert_valid_call(prompt, default, cli_flag, force_interactive):\n    \"\"\"Verify that provided arguments is a valid IDisplay call.\n\n    :param str prompt: prompt for the user\n    :param default: default answer to prompt\n    :param str cli_flag: command line option for setting an answer\n        to this question\n    :param bool force_interactive: if interactivity is forced by the\n        IDisplay call\n\n    \"\"\"\n    msg = \"Invalid IDisplay call for this prompt:\\n{0}\".format(prompt)\n    if cli_flag:\n        msg += (\"\\nYou can set an answer to \"\n                \"this prompt with the {0} flag\".format(cli_flag))\n    assert default is not None or force_interactive, msg\n\n\n@zope.interface.implementer(interfaces.IDisplay)\nclass NoninteractiveDisplay(object):\n    \"\"\"An iDisplay implementation that never asks for interactive user input\"\"\"\n\n    def __init__(self, outfile, *unused_args, **unused_kwargs):\n        super(NoninteractiveDisplay, self).__init__()\n        self.outfile = outfile\n\n    def _interaction_fail(self, message, cli_flag, extra=\"\"):\n        \"Error out in case of an attempt to interact in noninteractive mode\"\n        msg = \"Missing command line flag or config entry for this setting:\\n\"\n        msg += message\n        if extra:\n            msg += \"\\n\" + extra\n        if cli_flag:\n            msg += \"\\n\\n(You can set this with the {0} flag)\".format(cli_flag)\n        raise errors.MissingCommandlineFlag(msg)\n\n    def notification(self, message, pause=False, wrap=True, **unused_kwargs):  # pylint: disable=unused-argument\n        \"\"\"Displays a notification without waiting for user acceptance.\n\n        :param str message: Message to display to stdout\n        :param bool pause: The NoninteractiveDisplay waits for no keyboard\n        :param bool wrap: Whether or not the application should wrap text\n\n        \"\"\"\n        if wrap:\n            message = _wrap_lines(message)\n        self.outfile.write(\n            \"{line}{frame}{line}{msg}{line}{frame}{line}\".format(\n                line=os.linesep, frame=SIDE_FRAME, msg=message))\n        self.outfile.flush()\n\n    def menu(self, message, choices, ok_label=None, cancel_label=None,\n             help_label=None, default=None, cli_flag=None, **unused_kwargs):\n        # pylint: disable=unused-argument\n        \"\"\"Avoid displaying a menu.\n\n        :param str message: title of menu\n        :param choices: Menu lines, len must be > 0\n        :type choices: list of tuples (tag, item) or\n            list of descriptions (tags will be enumerated)\n        :param int default: the default choice\n        :param dict kwargs: absorbs various irrelevant labelling arguments\n\n        :returns: tuple of (`code`, `index`) where\n            `code` - str display exit code\n            `index` - int index of the user's selection\n        :rtype: tuple\n        :raises errors.MissingCommandlineFlag: if there was no default\n\n        \"\"\"\n        if default is None:\n            self._interaction_fail(message, cli_flag, \"Choices: \" + repr(choices))\n\n        return OK, default\n\n    def input(self, message, default=None, cli_flag=None, **unused_kwargs):\n        \"\"\"Accept input from the user.\n\n        :param str message: message to display to the user\n\n        :returns: tuple of (`code`, `input`) where\n            `code` - str display exit code\n            `input` - str of the user's input\n        :rtype: tuple\n        :raises errors.MissingCommandlineFlag: if there was no default\n\n        \"\"\"\n        if default is None:\n            self._interaction_fail(message, cli_flag)\n        return OK, default\n\n    def yesno(self, message, yes_label=None, no_label=None,  # pylint: disable=unused-argument\n              default=None, cli_flag=None, **unused_kwargs):\n        \"\"\"Decide Yes or No, without asking anybody\n\n        :param str message: question for the user\n        :param dict kwargs: absorbs yes_label, no_label\n\n        :raises errors.MissingCommandlineFlag: if there was no default\n        :returns: True for \"Yes\", False for \"No\"\n        :rtype: bool\n\n        \"\"\"\n        if default is None:\n            self._interaction_fail(message, cli_flag)\n        return default\n\n    def checklist(self, message, tags, default=None,\n                  cli_flag=None, **unused_kwargs):\n        \"\"\"Display a checklist.\n\n        :param str message: Message to display to user\n        :param list tags: `str` tags to select, len(tags) > 0\n        :param dict kwargs: absorbs default_status arg\n\n        :returns: tuple of (`code`, `tags`) where\n            `code` - str display exit code\n            `tags` - list of selected tags\n        :rtype: tuple\n\n        \"\"\"\n        if default is None:\n            self._interaction_fail(message, cli_flag, \"? \".join(tags))\n        return OK, default\n\n    def directory_select(self, message, default=None,\n                         cli_flag=None, **unused_kwargs):\n        \"\"\"Simulate prompting the user for a directory.\n\n        This function returns default if it is not ``None``, otherwise,\n        an exception is raised explaining the problem. If cli_flag is\n        not ``None``, the error message will include the flag that can\n        be used to set this value with the CLI.\n\n        :param str message: prompt to give the user\n        :param default: default value to return (if one exists)\n        :param str cli_flag: option used to set this value with the CLI\n\n        :returns: tuple of the form (`code`, `string`) where\n            `code` - int display exit code\n            `string` - input entered by the user\n\n        \"\"\"\n        return self.input(message, default, cli_flag)\n\n\ndef separate_list_input(input_):\n    \"\"\"Separate a comma or space separated list.\n\n    :param str input_: input from the user\n\n    :returns: strings\n    :rtype: list\n\n    \"\"\"\n    no_commas = input_.replace(\",\", \" \")\n    # Each string is naturally unicode, this causes problems with M2Crypto SANs\n    # TODO: check if above is still true when M2Crypto is gone ^\n    return [str(string) for string in no_commas.split()]\n\n\ndef _parens_around_char(label):\n    \"\"\"Place parens around first character of label.\n\n    :param str label: Must contain at least one character\n\n    \"\"\"\n    return \"({first}){rest}\".format(first=label[0], rest=label[1:])\n"
  },
  {
    "path": "certbot/errors.py",
    "content": "\"\"\"Certbot client errors.\"\"\"\n\n\nclass Error(Exception):\n    \"\"\"Generic Certbot client error.\"\"\"\n\n\nclass AccountStorageError(Error):\n    \"\"\"Generic `.AccountStorage` error.\"\"\"\n\n\nclass AccountNotFound(AccountStorageError):\n    \"\"\"Account not found error.\"\"\"\n\n\nclass ReverterError(Error):\n    \"\"\"Certbot Reverter error.\"\"\"\n\n\nclass SubprocessError(Error):\n    \"\"\"Subprocess handling error.\"\"\"\n\n\nclass CertStorageError(Error):\n    \"\"\"Generic `.CertStorage` error.\"\"\"\n\n\nclass HookCommandNotFound(Error):\n    \"\"\"Failed to find a hook command in the PATH.\"\"\"\n\n\nclass SignalExit(Error):\n    \"\"\"A Unix signal was received while in the ErrorHandler context manager.\"\"\"\n\nclass OverlappingMatchFound(Error):\n    \"\"\"Multiple lineages matched what should have been a unique result.\"\"\"\n\nclass LockError(Error):\n    \"\"\"File locking error.\"\"\"\n\n\n# Auth Handler Errors\nclass AuthorizationError(Error):\n    \"\"\"Authorization error.\"\"\"\n\n\nclass FailedChallenges(AuthorizationError):\n    \"\"\"Failed challenges error.\n\n    :ivar set failed_achalls: Failed `.AnnotatedChallenge` instances.\n\n    \"\"\"\n    def __init__(self, failed_achalls):\n        assert failed_achalls\n        self.failed_achalls = failed_achalls\n        super(FailedChallenges, self).__init__()\n\n    def __str__(self):\n        return \"Failed authorization procedure. {0}\".format(\n            \", \".join(\n                \"{0} ({1}): {2}\".format(achall.domain, achall.typ, achall.error)\n                for achall in self.failed_achalls if achall.error is not None))\n\n\n# Plugin Errors\nclass PluginError(Error):\n    \"\"\"Certbot Plugin error.\"\"\"\n\n\nclass PluginEnhancementAlreadyPresent(Error):\n    \"\"\" Enhancement was already set \"\"\"\n\n\nclass PluginSelectionError(Error):\n    \"\"\"A problem with plugin/configurator selection or setup\"\"\"\n\n\nclass NoInstallationError(PluginError):\n    \"\"\"Certbot No Installation error.\"\"\"\n\n\nclass MisconfigurationError(PluginError):\n    \"\"\"Certbot Misconfiguration error.\"\"\"\n\n\nclass NotSupportedError(PluginError):\n    \"\"\"Certbot Plugin function not supported error.\"\"\"\n\n\nclass PluginStorageError(PluginError):\n    \"\"\"Certbot Plugin Storage error.\"\"\"\n\n\nclass StandaloneBindError(Error):\n    \"\"\"Standalone plugin bind error.\"\"\"\n\n    def __init__(self, socket_error, port):\n        super(StandaloneBindError, self).__init__(\n            \"Problem binding to port {0}: {1}\".format(port, socket_error))\n        self.socket_error = socket_error\n        self.port = port\n\n\nclass ConfigurationError(Error):\n    \"\"\"Configuration sanity error.\"\"\"\n\n# NoninteractiveDisplay iDisplay plugin error:\n\nclass MissingCommandlineFlag(Error):\n    \"\"\"A command line argument was missing in noninteractive usage\"\"\"\n"
  },
  {
    "path": "certbot/interfaces.py",
    "content": "\"\"\"Certbot client interfaces.\"\"\"\nimport abc\n\nimport six\nimport zope.interface\n\n# pylint: disable=no-self-argument,no-method-argument,inherit-non-class\n\n\n@six.add_metaclass(abc.ABCMeta)\nclass AccountStorage(object):\n    \"\"\"Accounts storage interface.\"\"\"\n\n    @abc.abstractmethod\n    def find_all(self):  # pragma: no cover\n        \"\"\"Find all accounts.\n\n        :returns: All found accounts.\n        :rtype: list\n\n        \"\"\"\n        raise NotImplementedError()\n\n    @abc.abstractmethod\n    def load(self, account_id):  # pragma: no cover\n        \"\"\"Load an account by its id.\n\n        :raises .AccountNotFound: if account could not be found\n        :raises .AccountStorageError: if account could not be loaded\n\n        \"\"\"\n        raise NotImplementedError()\n\n    @abc.abstractmethod\n    def save(self, account, client):  # pragma: no cover\n        \"\"\"Save account.\n\n        :raises .AccountStorageError: if account could not be saved\n\n        \"\"\"\n        raise NotImplementedError()\n\n\nclass IPluginFactory(zope.interface.Interface):\n    \"\"\"IPlugin factory.\n\n    Objects providing this interface will be called without satisfying\n    any entry point \"extras\" (extra dependencies) you might have defined\n    for your plugin, e.g (excerpt from ``setup.py`` script)::\n\n      setup(\n          ...\n          entry_points={\n              'certbot.plugins': [\n                  'name=example_project.plugin[plugin_deps]',\n              ],\n          },\n          extras_require={\n              'plugin_deps': ['dep1', 'dep2'],\n          }\n      )\n\n    Therefore, make sure such objects are importable and usable without\n    extras. This is necessary, because CLI does the following operations\n    (in order):\n\n      - loads an entry point,\n      - calls `inject_parser_options`,\n      - requires an entry point,\n      - creates plugin instance (`__call__`).\n\n    \"\"\"\n\n    description = zope.interface.Attribute(\"Short plugin description\")\n\n    def __call__(config, name):  # pylint: disable=signature-differs\n        \"\"\"Create new `IPlugin`.\n\n        :param IConfig config: Configuration.\n        :param str name: Unique plugin name.\n\n        \"\"\"\n\n    def inject_parser_options(parser, name):\n        \"\"\"Inject argument parser options (flags).\n\n        1. Be nice and prepend all options and destinations with\n        `~.common.option_namespace` and `~common.dest_namespace`.\n\n        2. Inject options (flags) only. Positional arguments are not\n        allowed, as this would break the CLI.\n\n        :param ArgumentParser parser: (Almost) top-level CLI parser.\n        :param str name: Unique plugin name.\n\n        \"\"\"\n\n\nclass IPlugin(zope.interface.Interface):\n    \"\"\"Certbot plugin.\"\"\"\n\n    def prepare():  # type: ignore\n        \"\"\"Prepare the plugin.\n\n        Finish up any additional initialization.\n\n        :raises .PluginError:\n            when full initialization cannot be completed.\n        :raises .MisconfigurationError:\n            when full initialization cannot be completed. Plugin will\n            be displayed on a list of available plugins.\n        :raises .NoInstallationError:\n            when the necessary programs/files cannot be located. Plugin\n            will NOT be displayed on a list of available plugins.\n        :raises .NotSupportedError:\n            when the installation is recognized, but the version is not\n            currently supported.\n\n        \"\"\"\n\n    def more_info():  # type: ignore\n        \"\"\"Human-readable string to help the user.\n\n        Should describe the steps taken and any relevant info to help the user\n        decide which plugin to use.\n\n        :rtype str:\n\n        \"\"\"\n\n\nclass IAuthenticator(IPlugin):\n    \"\"\"Generic Certbot Authenticator.\n\n    Class represents all possible tools processes that have the\n    ability to perform challenges and attain a certificate.\n\n    \"\"\"\n\n    def get_chall_pref(domain):\n        \"\"\"Return `collections.Iterable` of challenge preferences.\n\n        :param str domain: Domain for which challenge preferences are sought.\n\n        :returns: `collections.Iterable` of challenge types (subclasses of\n            :class:`acme.challenges.Challenge`) with the most\n            preferred challenges first. If a type is not specified, it means the\n            Authenticator cannot perform the challenge.\n        :rtype: `collections.Iterable`\n\n        \"\"\"\n\n    def perform(achalls):\n        \"\"\"Perform the given challenge.\n\n        :param list achalls: Non-empty (guaranteed) list of\n            :class:`~certbot.achallenges.AnnotatedChallenge`\n            instances, such that it contains types found within\n            :func:`get_chall_pref` only.\n\n        :returns: `collections.Iterable` of ACME\n            :class:`~acme.challenges.ChallengeResponse` instances corresponding to each provided\n            :class:`~acme.challenges.Challenge`.\n        :rtype: :class:`collections.Iterable` of\n            :class:`acme.challenges.ChallengeResponse`,\n            where responses are required to be returned in\n            the same order as corresponding input challenges\n\n        :raises .PluginError: If some or all challenges cannot be performed\n\n        \"\"\"\n\n    def cleanup(achalls):\n        \"\"\"Revert changes and shutdown after challenges complete.\n\n        This method should be able to revert all changes made by\n        perform, even if perform exited abnormally.\n\n        :param list achalls: Non-empty (guaranteed) list of\n            :class:`~certbot.achallenges.AnnotatedChallenge`\n            instances, a subset of those previously passed to :func:`perform`.\n\n        :raises PluginError: if original configuration cannot be restored\n\n        \"\"\"\n\n\nclass IConfig(zope.interface.Interface):\n    \"\"\"Certbot user-supplied configuration.\n\n    .. warning:: The values stored in the configuration have not been\n        filtered, stripped or sanitized.\n\n    \"\"\"\n    server = zope.interface.Attribute(\"ACME Directory Resource URI.\")\n    email = zope.interface.Attribute(\n        \"Email used for registration and recovery contact. Use comma to \"\n        \"register multiple emails, ex: u1@example.com,u2@example.com. \"\n        \"(default: Ask).\")\n    rsa_key_size = zope.interface.Attribute(\"Size of the RSA key.\")\n    must_staple = zope.interface.Attribute(\n        \"Adds the OCSP Must Staple extension to the certificate. \"\n        \"Autoconfigures OCSP Stapling for supported setups \"\n        \"(Apache version >= 2.3.3 ).\")\n\n    config_dir = zope.interface.Attribute(\"Configuration directory.\")\n    work_dir = zope.interface.Attribute(\"Working directory.\")\n\n    accounts_dir = zope.interface.Attribute(\n        \"Directory where all account information is stored.\")\n    backup_dir = zope.interface.Attribute(\"Configuration backups directory.\")\n    csr_dir = zope.interface.Attribute(\n        \"Directory where newly generated Certificate Signing Requests \"\n        \"(CSRs) are saved.\")\n    in_progress_dir = zope.interface.Attribute(\n        \"Directory used before a permanent checkpoint is finalized.\")\n    key_dir = zope.interface.Attribute(\"Keys storage.\")\n    temp_checkpoint_dir = zope.interface.Attribute(\n        \"Temporary checkpoint directory.\")\n\n    no_verify_ssl = zope.interface.Attribute(\n        \"Disable verification of the ACME server's certificate.\")\n\n    http01_port = zope.interface.Attribute(\n        \"Port used in the http-01 challenge. \"\n        \"This only affects the port Certbot listens on. \"\n        \"A conforming ACME server will still attempt to connect on port 80.\")\n\n    http01_address = zope.interface.Attribute(\n        \"The address the server listens to during http-01 challenge.\")\n\n    https_port = zope.interface.Attribute(\n        \"Port used to serve HTTPS. \"\n        \"This affects which port Nginx will listen on after a LE certificate \"\n        \"is installed.\")\n\n    pref_challs = zope.interface.Attribute(\n        \"Sorted user specified preferred challenges\"\n        \"type strings with the most preferred challenge listed first\")\n\n    allow_subset_of_names = zope.interface.Attribute(\n        \"When performing domain validation, do not consider it a failure \"\n        \"if authorizations can not be obtained for a strict subset of \"\n        \"the requested domains. This may be useful for allowing renewals for \"\n        \"multiple domains to succeed even if some domains no longer point \"\n        \"at this system. This is a boolean\")\n\n    strict_permissions = zope.interface.Attribute(\n        \"Require that all configuration files are owned by the current \"\n        \"user; only needed if your config is somewhere unsafe like /tmp/.\"\n        \"This is a boolean\")\n\n    disable_renew_updates = zope.interface.Attribute(\n        \"If updates provided by installer enhancements when Certbot is being run\"\n        \" with \\\"renew\\\" verb should be disabled.\")\n\nclass IInstaller(IPlugin):\n    \"\"\"Generic Certbot Installer Interface.\n\n    Represents any server that an X509 certificate can be placed.\n\n    It is assumed that :func:`save` is the only method that finalizes a\n    checkpoint. This is important to ensure that checkpoints are\n    restored in a consistent manner if requested by the user or in case\n    of an error.\n\n    Using :class:`certbot.reverter.Reverter` to implement checkpoints,\n    rollback, and recovery can dramatically simplify plugin development.\n\n    \"\"\"\n\n    def get_all_names():  # type: ignore\n        \"\"\"Returns all names that may be authenticated.\n\n        :rtype: `collections.Iterable` of `str`\n\n        \"\"\"\n\n    def deploy_cert(domain, cert_path, key_path, chain_path, fullchain_path):\n        \"\"\"Deploy certificate.\n\n        :param str domain: domain to deploy certificate file\n        :param str cert_path: absolute path to the certificate file\n        :param str key_path: absolute path to the private key file\n        :param str chain_path: absolute path to the certificate chain file\n        :param str fullchain_path: absolute path to the certificate fullchain\n            file (cert plus chain)\n\n        :raises .PluginError: when cert cannot be deployed\n\n        \"\"\"\n\n    def enhance(domain, enhancement, options=None):\n        \"\"\"Perform a configuration enhancement.\n\n        :param str domain: domain for which to provide enhancement\n        :param str enhancement: An enhancement as defined in\n            :const:`~certbot.plugins.enhancements.ENHANCEMENTS`\n        :param options: Flexible options parameter for enhancement.\n            Check documentation of\n            :const:`~certbot.plugins.enhancements.ENHANCEMENTS`\n            for expected options for each enhancement.\n\n        :raises .PluginError: If Enhancement is not supported, or if\n            an error occurs during the enhancement.\n\n        \"\"\"\n\n    def supported_enhancements():  # type: ignore\n        \"\"\"Returns a `collections.Iterable` of supported enhancements.\n\n        :returns: supported enhancements which should be a subset of\n            :const:`~certbot.plugins.enhancements.ENHANCEMENTS`\n        :rtype: :class:`collections.Iterable` of :class:`str`\n\n        \"\"\"\n\n    def save(title=None, temporary=False):\n        \"\"\"Saves all changes to the configuration files.\n\n        Both title and temporary are needed because a save may be\n        intended to be permanent, but the save is not ready to be a full\n        checkpoint.\n\n        It is assumed that at most one checkpoint is finalized by this\n        method. Additionally, if an exception is raised, it is assumed a\n        new checkpoint was not finalized.\n\n        :param str title: The title of the save. If a title is given, the\n            configuration will be saved as a new checkpoint and put in a\n            timestamped directory. `title` has no effect if temporary is true.\n\n        :param bool temporary: Indicates whether the changes made will\n            be quickly reversed in the future (challenges)\n\n        :raises .PluginError: when save is unsuccessful\n\n        \"\"\"\n\n    def rollback_checkpoints(rollback=1):\n        \"\"\"Revert `rollback` number of configuration checkpoints.\n\n        :raises .PluginError: when configuration cannot be fully reverted\n\n        \"\"\"\n\n    def recovery_routine():  # type: ignore\n        \"\"\"Revert configuration to most recent finalized checkpoint.\n\n        Remove all changes (temporary and permanent) that have not been\n        finalized. This is useful to protect against crashes and other\n        execution interruptions.\n\n        :raises .errors.PluginError: If unable to recover the configuration\n\n        \"\"\"\n\n    def config_test():  # type: ignore\n        \"\"\"Make sure the configuration is valid.\n\n        :raises .MisconfigurationError: when the config is not in a usable state\n\n        \"\"\"\n\n    def restart():  # type: ignore\n        \"\"\"Restart or refresh the server content.\n\n        :raises .PluginError: when server cannot be restarted\n\n        \"\"\"\n\n\nclass IDisplay(zope.interface.Interface):\n    \"\"\"Generic display.\"\"\"\n    # see https://github.com/certbot/certbot/issues/3915\n\n    def notification(message, pause, wrap=True, force_interactive=False):\n        \"\"\"Displays a string message\n\n        :param str message: Message to display\n        :param bool pause: Whether or not the application should pause for\n            confirmation (if available)\n        :param bool wrap: Whether or not the application should wrap text\n        :param bool force_interactive: True if it's safe to prompt the user\n            because it won't cause any workflow regressions\n\n        \"\"\"\n\n    def menu(message, choices, ok_label=None,\n             cancel_label=None, help_label=None,\n             default=None, cli_flag=None, force_interactive=False):\n        \"\"\"Displays a generic menu.\n\n        When not setting force_interactive=True, you must provide a\n        default value.\n\n        :param str message: message to display\n\n        :param choices: choices\n        :type choices: :class:`list` of :func:`tuple` or :class:`str`\n\n        :param str ok_label: label for OK button (UNUSED)\n        :param str cancel_label: label for Cancel button (UNUSED)\n        :param str help_label: label for Help button (UNUSED)\n        :param int default: default (non-interactive) choice from the menu\n        :param str cli_flag: to automate choice from the menu, eg \"--keep\"\n        :param bool force_interactive: True if it's safe to prompt the user\n            because it won't cause any workflow regressions\n\n        :returns: tuple of (`code`, `index`) where\n            `code` - str display exit code\n            `index` - int index of the user's selection\n\n        :raises errors.MissingCommandlineFlag: if called in non-interactive\n            mode without a default set\n\n        \"\"\"\n\n    def input(message, default=None, cli_args=None, force_interactive=False):\n        \"\"\"Accept input from the user.\n\n        When not setting force_interactive=True, you must provide a\n        default value.\n\n        :param str message: message to display to the user\n        :param str default: default (non-interactive) response to prompt\n        :param bool force_interactive: True if it's safe to prompt the user\n            because it won't cause any workflow regressions\n\n        :returns: tuple of (`code`, `input`) where\n            `code` - str display exit code\n            `input` - str of the user's input\n        :rtype: tuple\n\n        :raises errors.MissingCommandlineFlag: if called in non-interactive\n            mode without a default set\n\n        \"\"\"\n\n    def yesno(message, yes_label=\"Yes\", no_label=\"No\", default=None,\n              cli_args=None, force_interactive=False):\n        \"\"\"Query the user with a yes/no question.\n\n        Yes and No label must begin with different letters.\n\n        When not setting force_interactive=True, you must provide a\n        default value.\n\n        :param str message: question for the user\n        :param str default: default (non-interactive) choice from the menu\n        :param str cli_flag: to automate choice from the menu, eg \"--redirect / --no-redirect\"\n        :param bool force_interactive: True if it's safe to prompt the user\n            because it won't cause any workflow regressions\n\n        :returns: True for \"Yes\", False for \"No\"\n        :rtype: bool\n\n        :raises errors.MissingCommandlineFlag: if called in non-interactive\n            mode without a default set\n\n        \"\"\"\n\n    def checklist(message, tags, default=None, cli_args=None, force_interactive=False):\n        \"\"\"Allow for multiple selections from a menu.\n\n        When not setting force_interactive=True, you must provide a\n        default value.\n\n        :param str message: message to display to the user\n        :param list tags: where each is of type :class:`str` len(tags) > 0\n        :param str default: default (non-interactive) state of the checklist\n        :param str cli_flag: to automate choice from the menu, eg \"--domains\"\n        :param bool force_interactive: True if it's safe to prompt the user\n            because it won't cause any workflow regressions\n\n        :returns: tuple of the form (code, list_tags) where\n            `code` - int display exit code\n            `list_tags` - list of str tags selected by the user\n        :rtype: tuple\n\n        :raises errors.MissingCommandlineFlag: if called in non-interactive\n            mode without a default set\n\n        \"\"\"\n\n    def directory_select(self, message, default=None,\n                         cli_flag=None, force_interactive=False):\n        \"\"\"Display a directory selection screen.\n\n        When not setting force_interactive=True, you must provide a\n        default value.\n\n        :param str message: prompt to give the user\n        :param default: the default value to return, if one exists, when\n            using the NoninteractiveDisplay\n        :param str cli_flag: option used to set this value with the CLI,\n            if one exists, to be included in error messages given by\n            NoninteractiveDisplay\n        :param bool force_interactive: True if it's safe to prompt the user\n            because it won't cause any workflow regressions\n\n        :returns: tuple of the form (`code`, `string`) where\n            `code` - int display exit code\n            `string` - input entered by the user\n\n        \"\"\"\n\n\nclass IReporter(zope.interface.Interface):\n    \"\"\"Interface to collect and display information to the user.\"\"\"\n\n    HIGH_PRIORITY = zope.interface.Attribute(\n        \"Used to denote high priority messages\")\n    MEDIUM_PRIORITY = zope.interface.Attribute(\n        \"Used to denote medium priority messages\")\n    LOW_PRIORITY = zope.interface.Attribute(\n        \"Used to denote low priority messages\")\n\n    def add_message(self, msg, priority, on_crash=True):\n        \"\"\"Adds msg to the list of messages to be printed.\n\n        :param str msg: Message to be displayed to the user.\n\n        :param int priority: One of HIGH_PRIORITY, MEDIUM_PRIORITY, or\n            LOW_PRIORITY.\n\n        :param bool on_crash: Whether or not the message should be printed if\n            the program exits abnormally.\n\n        \"\"\"\n\n    def print_messages(self):\n        \"\"\"Prints messages to the user and clears the message queue.\"\"\"\n\n\n@six.add_metaclass(abc.ABCMeta)\nclass RenewableCert(object):\n    \"\"\"Interface to a certificate lineage.\"\"\"\n\n    @abc.abstractproperty\n    def cert_path(self):\n        \"\"\"Path to the certificate file.\n\n        :rtype: str\n\n        \"\"\"\n\n    @abc.abstractproperty\n    def key_path(self):\n        \"\"\"Path to the private key file.\n\n        :rtype: str\n\n        \"\"\"\n\n    @abc.abstractproperty\n    def chain_path(self):\n        \"\"\"Path to the certificate chain file.\n\n        :rtype: str\n\n        \"\"\"\n\n    @abc.abstractproperty\n    def fullchain_path(self):\n        \"\"\"Path to the full chain file.\n\n        The full chain is the certificate file plus the chain file.\n\n        :rtype: str\n\n        \"\"\"\n\n    @abc.abstractproperty\n    def lineagename(self):\n        \"\"\"Name given to the certificate lineage.\n\n        :rtype: str\n\n        \"\"\"\n\n    @abc.abstractmethod\n    def names(self):\n        \"\"\"What are the subject names of this certificate?\n\n        :returns: the subject names\n        :rtype: `list` of `str`\n        :raises .CertStorageError: if could not find cert file.\n\n        \"\"\"\n\n# Updater interfaces\n#\n# When \"certbot renew\" is run, Certbot will iterate over each lineage and check\n# if the selected installer for that lineage is a subclass of each updater\n# class. If it is and the update of that type is configured to be run for that\n# lineage, the relevant update function will be called for it. These functions\n# are never called for other subcommands, so if an installer wants to perform\n# an update during the run or install subcommand, it should do so when\n# :func:`IInstaller.deploy_cert` is called.\n\n@six.add_metaclass(abc.ABCMeta)\nclass GenericUpdater(object):\n    \"\"\"Interface for update types not currently specified by Certbot.\n\n    This class allows plugins to perform types of updates that Certbot hasn't\n    defined (yet).\n\n    To make use of this interface, the installer should implement the interface\n    methods, and interfaces.GenericUpdater.register(InstallerClass) should\n    be called from the installer code.\n\n    The plugins implementing this enhancement are responsible of handling\n    the saving of configuration checkpoints as well as other calls to\n    interface methods of `interfaces.IInstaller` such as prepare() and restart()\n    \"\"\"\n\n    @abc.abstractmethod\n    def generic_updates(self, lineage, *args, **kwargs):\n        \"\"\"Perform any update types defined by the installer.\n\n        If an installer is a subclass of the class containing this method, this\n        function will always be called when \"certbot renew\" is run. If the\n        update defined by the installer should be run conditionally, the\n        installer needs to handle checking the conditions itself.\n\n        This method is called once for each lineage.\n\n        :param lineage: Certificate lineage object\n        :type lineage: RenewableCert\n\n        \"\"\"\n\n\n@six.add_metaclass(abc.ABCMeta)\nclass RenewDeployer(object):\n    \"\"\"Interface for update types run when a lineage is renewed\n\n    This class allows plugins to perform types of updates that need to run at\n    lineage renewal that Certbot hasn't defined (yet).\n\n    To make use of this interface, the installer should implement the interface\n    methods, and interfaces.RenewDeployer.register(InstallerClass) should\n    be called from the installer code.\n    \"\"\"\n\n    @abc.abstractmethod\n    def renew_deploy(self, lineage, *args, **kwargs):\n        \"\"\"Perform updates defined by installer when a certificate has been renewed\n\n        If an installer is a subclass of the class containing this method, this\n        function will always be called when a certficate has been renewed by\n        running \"certbot renew\". For example if a plugin needs to copy a\n        certificate over, or change configuration based on the new certificate.\n\n        This method is called once for each lineage renewed\n\n        :param lineage: Certificate lineage object\n        :type lineage: RenewableCert\n\n        \"\"\"\n"
  },
  {
    "path": "certbot/main.py",
    "content": "\"\"\"Certbot main public entry point.\"\"\"\nfrom certbot._internal import main as internal_main\n\n\ndef main(cli_args=None):\n    \"\"\"Run Certbot.\n\n    :param cli_args: command line to Certbot, defaults to ``sys.argv[1:]``\n    :type cli_args: `list` of `str`\n\n    :returns: value for `sys.exit` about the exit status of Certbot\n    :rtype: `str` or `int` or `None`\n\n    \"\"\"\n    return internal_main.main(cli_args)\n"
  },
  {
    "path": "certbot/ocsp.py",
    "content": "\"\"\"Tools for checking certificate revocation.\"\"\"\nfrom datetime import datetime\nfrom datetime import timedelta\nimport logging\nimport re\nfrom subprocess import PIPE\nfrom subprocess import Popen\n\nfrom cryptography import x509\nfrom cryptography.exceptions import InvalidSignature\nfrom cryptography.exceptions import UnsupportedAlgorithm\nfrom cryptography.hazmat.backends import default_backend\n# See https://github.com/pyca/cryptography/issues/4275\nfrom cryptography.hazmat.primitives import hashes  # type: ignore\nfrom cryptography.hazmat.primitives import serialization\nimport pytz\nimport requests\n\nfrom acme.magic_typing import Optional\nfrom acme.magic_typing import Tuple\nfrom certbot import crypto_util\nfrom certbot import errors\nfrom certbot import util\nfrom certbot.interfaces import RenewableCert  # pylint: disable=unused-import\n\ntry:\n    # Only cryptography>=2.5 has ocsp module\n    # and signature_hash_algorithm attribute in OCSPResponse class\n    from cryptography.x509 import ocsp  # pylint: disable=ungrouped-imports\n    getattr(ocsp.OCSPResponse, 'signature_hash_algorithm')\nexcept (ImportError, AttributeError):  # pragma: no cover\n    ocsp = None  # type: ignore\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass RevocationChecker(object):\n    \"\"\"This class figures out OCSP checking on this system, and performs it.\"\"\"\n\n    def __init__(self, enforce_openssl_binary_usage=False):\n        self.broken = False\n        self.use_openssl_binary = enforce_openssl_binary_usage or not ocsp\n\n        if self.use_openssl_binary:\n            if not util.exe_exists(\"openssl\"):\n                logger.info(\"openssl not installed, can't check revocation\")\n                self.broken = True\n                return\n\n            # New versions of openssl want -header var=val, old ones want -header var val\n            test_host_format = Popen([\"openssl\", \"ocsp\", \"-header\", \"var\", \"val\"],\n                                     stdout=PIPE, stderr=PIPE, universal_newlines=True)\n            _out, err = test_host_format.communicate()\n            if \"Missing =\" in err:\n                self.host_args = lambda host: [\"Host=\" + host]\n            else:\n                self.host_args = lambda host: [\"Host\", host]\n\n    def ocsp_revoked(self, cert):\n        # type: (RenewableCert) -> bool\n        \"\"\"Get revoked status for a particular cert version.\n\n        .. todo:: Make this a non-blocking call\n\n        :param `.interfaces.RenewableCert` cert: Certificate object\n        :returns: True if revoked; False if valid or the check failed or cert is expired.\n        :rtype: bool\n\n        \"\"\"\n        return self.ocsp_revoked_by_paths(cert.cert_path, cert.chain_path)\n\n    def ocsp_revoked_by_paths(self, cert_path, chain_path):\n        # type: (str, str) -> bool\n        \"\"\"Performs the OCSP revocation check\n\n        :param str cert_path: Certificate filepath\n        :param str chain_path: Certificate chain filepath\n\n        :returns: True if revoked; False if valid or the check failed or cert is expired.\n        :rtype: bool\n\n        \"\"\"\n        if self.broken:\n            return False\n\n        # Let's Encrypt doesn't update OCSP for expired certificates,\n        # so don't check OCSP if the cert is expired.\n        # https://github.com/certbot/certbot/issues/7152\n        now = pytz.UTC.fromutc(datetime.utcnow())\n        if crypto_util.notAfter(cert_path) <= now:\n            return False\n\n        url, host = _determine_ocsp_server(cert_path)\n        if not host or not url:\n            return False\n\n        if self.use_openssl_binary:\n            return self._check_ocsp_openssl_bin(cert_path, chain_path, host, url)\n        return _check_ocsp_cryptography(cert_path, chain_path, url)\n\n    def _check_ocsp_openssl_bin(self, cert_path, chain_path, host, url):\n        # type: (str, str, str, str) -> bool\n        # jdkasten thanks \"Bulletproof SSL and TLS - Ivan Ristic\" for documenting this!\n        cmd = [\"openssl\", \"ocsp\",\n               \"-no_nonce\",\n               \"-issuer\", chain_path,\n               \"-cert\", cert_path,\n               \"-url\", url,\n               \"-CAfile\", chain_path,\n               \"-verify_other\", chain_path,\n               \"-trust_other\",\n               \"-header\"] + self.host_args(host)\n        logger.debug(\"Querying OCSP for %s\", cert_path)\n        logger.debug(\" \".join(cmd))\n        try:\n            output, err = util.run_script(cmd, log=logger.debug)\n        except errors.SubprocessError:\n            logger.info(\"OCSP check failed for %s (are we offline?)\", cert_path)\n            return False\n        return _translate_ocsp_query(cert_path, output, err)\n\n\ndef _determine_ocsp_server(cert_path):\n    # type: (str) -> Tuple[Optional[str], Optional[str]]\n    \"\"\"Extract the OCSP server host from a certificate.\n\n    :param str cert_path: Path to the cert we're checking OCSP for\n    :rtype tuple:\n    :returns: (OCSP server URL or None, OCSP server host or None)\n\n    \"\"\"\n    with open(cert_path, 'rb') as file_handler:\n        cert = x509.load_pem_x509_certificate(file_handler.read(), default_backend())\n    try:\n        extension = cert.extensions.get_extension_for_class(x509.AuthorityInformationAccess)\n        ocsp_oid = x509.AuthorityInformationAccessOID.OCSP\n        descriptions = [description for description in extension.value\n                        if description.access_method == ocsp_oid]\n\n        url = descriptions[0].access_location.value\n    except (x509.ExtensionNotFound, IndexError):\n        logger.info(\"Cannot extract OCSP URI from %s\", cert_path)\n        return None, None\n\n    url = url.rstrip()\n    host = url.partition(\"://\")[2].rstrip(\"/\")\n\n    if host:\n        return url, host\n    logger.info(\"Cannot process OCSP host from URL (%s) in cert at %s\", url, cert_path)\n    return None, None\n\n\ndef _check_ocsp_cryptography(cert_path, chain_path, url):\n    # type: (str, str, str) -> bool\n    # Retrieve OCSP response\n    with open(chain_path, 'rb') as file_handler:\n        issuer = x509.load_pem_x509_certificate(file_handler.read(), default_backend())\n    with open(cert_path, 'rb') as file_handler:\n        cert = x509.load_pem_x509_certificate(file_handler.read(), default_backend())\n    builder = ocsp.OCSPRequestBuilder()\n    builder = builder.add_certificate(cert, issuer, hashes.SHA1())\n    request = builder.build()\n    request_binary = request.public_bytes(serialization.Encoding.DER)\n    try:\n        response = requests.post(url, data=request_binary,\n                                 headers={'Content-Type': 'application/ocsp-request'})\n    except requests.exceptions.RequestException:\n        logger.info(\"OCSP check failed for %s (are we offline?)\", cert_path, exc_info=True)\n        return False\n    if response.status_code != 200:\n        logger.info(\"OCSP check failed for %s (HTTP status: %d)\", cert_path, response.status_code)\n        return False\n\n    response_ocsp = ocsp.load_der_ocsp_response(response.content)\n\n    # Check OCSP response validity\n    if response_ocsp.response_status != ocsp.OCSPResponseStatus.SUCCESSFUL:\n        logger.error(\"Invalid OCSP response status for %s: %s\",\n                     cert_path, response_ocsp.response_status)\n        return False\n\n    # Check OCSP signature\n    try:\n        _check_ocsp_response(response_ocsp, request, issuer, cert_path)\n    except UnsupportedAlgorithm as e:\n        logger.error(str(e))\n    except errors.Error as e:\n        logger.error(str(e))\n    except InvalidSignature:\n        logger.error('Invalid signature on OCSP response for %s', cert_path)\n    except AssertionError as error:\n        logger.error('Invalid OCSP response for %s: %s.', cert_path, str(error))\n    else:\n        # Check OCSP certificate status\n        logger.debug(\"OCSP certificate status for %s is: %s\",\n                     cert_path, response_ocsp.certificate_status)\n        return response_ocsp.certificate_status == ocsp.OCSPCertStatus.REVOKED\n\n    return False\n\n\ndef _check_ocsp_response(response_ocsp, request_ocsp, issuer_cert, cert_path):\n    \"\"\"Verify that the OCSP is valid for serveral criteria\"\"\"\n    # Assert OCSP response corresponds to the certificate we are talking about\n    if response_ocsp.serial_number != request_ocsp.serial_number:\n        raise AssertionError('the certificate in response does not correspond '\n                             'to the certificate in request')\n\n    # Assert signature is valid\n    _check_ocsp_response_signature(response_ocsp, issuer_cert, cert_path)\n\n    # Assert issuer in response is the expected one\n    if (not isinstance(response_ocsp.hash_algorithm, type(request_ocsp.hash_algorithm))\n            or response_ocsp.issuer_key_hash != request_ocsp.issuer_key_hash\n            or response_ocsp.issuer_name_hash != request_ocsp.issuer_name_hash):\n        raise AssertionError('the issuer does not correspond to issuer of the certificate.')\n\n    # In following checks, two situations can occur:\n    #   * nextUpdate is set, and requirement is thisUpdate < now < nextUpdate\n    #   * nextUpdate is not set, and requirement is thisUpdate < now\n    # NB1: We add a validity period tolerance to handle clock time inconsistencies,\n    #      value is 5 min like for OpenSSL.\n    # NB2: Another check is to verify that thisUpdate is not too old, it is optional\n    #      for OpenSSL, so we do not do it here.\n    # See OpenSSL implementation as a reference:\n    # https://github.com/openssl/openssl/blob/ef45aa14c5af024fcb8bef1c9007f3d1c115bd85/crypto/ocsp/ocsp_cl.c#L338-L391\n    now = datetime.utcnow()  # thisUpdate/nextUpdate are expressed in UTC/GMT time zone\n    if not response_ocsp.this_update:\n        raise AssertionError('param thisUpdate is not set.')\n    if response_ocsp.this_update > now + timedelta(minutes=5):\n        raise AssertionError('param thisUpdate is in the future.')\n    if response_ocsp.next_update and response_ocsp.next_update < now - timedelta(minutes=5):\n        raise AssertionError('param nextUpdate is in the past.')\n\n\ndef _check_ocsp_response_signature(response_ocsp, issuer_cert, cert_path):\n    \"\"\"Verify an OCSP response signature against certificate issuer or responder\"\"\"\n    if response_ocsp.responder_name == issuer_cert.subject:\n        # Case where the OCSP responder is also the certificate issuer\n        logger.debug('OCSP response for certificate %s is signed by the certificate\\'s issuer.',\n                     cert_path)\n        responder_cert = issuer_cert\n    else:\n        # Case where the OCSP responder is not the certificate issuer\n        logger.debug('OCSP response for certificate %s is delegated to an external responder.',\n                     cert_path)\n\n        responder_certs = [cert for cert in response_ocsp.certificates\n                           if cert.subject == response_ocsp.responder_name]\n        if not responder_certs:\n            raise AssertionError('no matching responder certificate could be found')\n\n        # We suppose here that the ACME server support only one certificate in the OCSP status\n        # request. This is currently the case for LetsEncrypt servers.\n        # See https://github.com/letsencrypt/boulder/issues/2331\n        responder_cert = responder_certs[0]\n\n        if responder_cert.issuer != issuer_cert.subject:\n            raise AssertionError('responder certificate is not signed '\n                                 'by the certificate\\'s issuer')\n\n        try:\n            extension = responder_cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage)\n            delegate_authorized = x509.oid.ExtendedKeyUsageOID.OCSP_SIGNING in extension.value\n        except (x509.ExtensionNotFound, IndexError):\n            delegate_authorized = False\n        if not delegate_authorized:\n            raise AssertionError('responder is not authorized by issuer to sign OCSP responses')\n\n        # Following line may raise UnsupportedAlgorithm\n        chosen_hash = responder_cert.signature_hash_algorithm\n        # For a delegate OCSP responder, we need first check that its certificate is effectively\n        # signed by the certificate issuer.\n        crypto_util.verify_signed_payload(issuer_cert.public_key(), responder_cert.signature,\n                                          responder_cert.tbs_certificate_bytes, chosen_hash)\n\n    # Following line may raise UnsupportedAlgorithm\n    chosen_hash = response_ocsp.signature_hash_algorithm\n    # We check that the OSCP response is effectively signed by the responder\n    # (an authorized delegate one or the certificate issuer itself).\n    crypto_util.verify_signed_payload(responder_cert.public_key(), response_ocsp.signature,\n                                      response_ocsp.tbs_response_bytes, chosen_hash)\n\n\ndef _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors):\n    \"\"\"Parse openssl's weird output to work out what it means.\"\"\"\n\n    states = (\"good\", \"revoked\", \"unknown\")\n    patterns = [r\"{0}: (WARNING.*)?{1}\".format(cert_path, s) for s in states]\n    good, revoked, unknown = (re.search(p, ocsp_output, flags=re.DOTALL) for p in patterns)\n\n    warning = good.group(1) if good else None\n\n    if (\"Response verify OK\" not in ocsp_errors) or (good and warning) or unknown:\n        logger.info(\"Revocation status for %s is unknown\", cert_path)\n        logger.debug(\"Uncertain output:\\n%s\\nstderr:\\n%s\", ocsp_output, ocsp_errors)\n        return False\n    elif good and not warning:\n        return False\n    elif revoked:\n        warning = revoked.group(1)\n        if warning:\n            logger.info(\"OCSP revocation warning: %s\", warning)\n        return True\n    else:\n        logger.warning(\"Unable to properly parse OCSP output: %s\\nstderr:%s\",\n                       ocsp_output, ocsp_errors)\n        return False\n"
  },
  {
    "path": "certbot/plugins/__init__.py",
    "content": "\"\"\"Certbot plugins.\"\"\"\n"
  },
  {
    "path": "certbot/plugins/common.py",
    "content": "\"\"\"Plugin common functions.\"\"\"\nimport logging\nimport re\nimport shutil\nimport sys\nimport tempfile\nimport warnings\n\nfrom josepy import util as jose_util\nimport pkg_resources\nimport zope.interface\n\nfrom acme.magic_typing import List\nfrom certbot import achallenges  # pylint: disable=unused-import\nfrom certbot import crypto_util\nfrom certbot import errors\nfrom certbot import interfaces\nfrom certbot import reverter\nfrom certbot._internal import constants\nfrom certbot.compat import filesystem\nfrom certbot.compat import os\nfrom certbot.plugins.storage import PluginStorage\n\nlogger = logging.getLogger(__name__)\n\n\ndef option_namespace(name):\n    \"\"\"ArgumentParser options namespace (prefix of all options).\"\"\"\n    return name + \"-\"\n\n\ndef dest_namespace(name):\n    \"\"\"ArgumentParser dest namespace (prefix of all destinations).\"\"\"\n    return name.replace(\"-\", \"_\") + \"_\"\n\n\nprivate_ips_regex = re.compile(\n    r\"(^127\\.0\\.0\\.1)|(^10\\.)|(^172\\.1[6-9]\\.)|\"\n    r\"(^172\\.2[0-9]\\.)|(^172\\.3[0-1]\\.)|(^192\\.168\\.)\")\nhostname_regex = re.compile(\n    r\"^(([a-z0-9]|[a-z0-9][a-z0-9\\-]*[a-z0-9])\\.)*[a-z]+$\", re.IGNORECASE)\n\n\n@zope.interface.implementer(interfaces.IPlugin)\nclass Plugin(object):\n    \"\"\"Generic plugin.\"\"\"\n    # provider is not inherited, subclasses must define it on their own\n    # @zope.interface.provider(interfaces.IPluginFactory)\n\n    def __init__(self, config, name):\n        self.config = config\n        self.name = name\n\n    @jose_util.abstractclassmethod\n    def add_parser_arguments(cls, add):\n        \"\"\"Add plugin arguments to the CLI argument parser.\n\n        NOTE: If some of your flags interact with others, you can\n        use cli.report_config_interaction to register this to ensure\n        values are correctly saved/overridable during renewal.\n\n        :param callable add: Function that proxies calls to\n            `argparse.ArgumentParser.add_argument` prepending options\n            with unique plugin name prefix.\n\n        \"\"\"\n\n    @classmethod\n    def inject_parser_options(cls, parser, name):\n        \"\"\"Inject parser options.\n\n        See `~.IPlugin.inject_parser_options` for docs.\n\n        \"\"\"\n        # dummy function, doesn't check if dest.startswith(self.dest_namespace)\n        def add(arg_name_no_prefix, *args, **kwargs):\n            return parser.add_argument(\n                \"--{0}{1}\".format(option_namespace(name), arg_name_no_prefix),\n                *args, **kwargs)\n        return cls.add_parser_arguments(add)\n\n    @property\n    def option_namespace(self):\n        \"\"\"ArgumentParser options namespace (prefix of all options).\"\"\"\n        return option_namespace(self.name)\n\n    def option_name(self, name):\n        \"\"\"Option name (include plugin namespace).\"\"\"\n        return self.option_namespace + name\n\n    @property\n    def dest_namespace(self):\n        \"\"\"ArgumentParser dest namespace (prefix of all destinations).\"\"\"\n        return dest_namespace(self.name)\n\n    def dest(self, var):\n        \"\"\"Find a destination for given variable ``var``.\"\"\"\n        # this should do exactly the same what ArgumentParser(arg),\n        # does to \"arg\" to compute \"dest\"\n        return self.dest_namespace + var.replace(\"-\", \"_\")\n\n    def conf(self, var):\n        \"\"\"Find a configuration value for variable ``var``.\"\"\"\n        return getattr(self.config, self.dest(var))\n\n\nclass Installer(Plugin):\n    \"\"\"An installer base class with reverter and ssl_dhparam methods defined.\n\n    Installer plugins do not have to inherit from this class.\n\n    \"\"\"\n    def __init__(self, *args, **kwargs):\n        super(Installer, self).__init__(*args, **kwargs)\n        self.storage = PluginStorage(self.config, self.name)\n        self.reverter = reverter.Reverter(self.config)\n\n    def add_to_checkpoint(self, save_files, save_notes, temporary=False):\n        \"\"\"Add files to a checkpoint.\n\n        :param set save_files: set of filepaths to save\n        :param str save_notes: notes about changes during the save\n        :param bool temporary: True if the files should be added to a\n            temporary checkpoint rather than a permanent one. This is\n            usually used for changes that will soon be reverted.\n\n        :raises .errors.PluginError: when unable to add to checkpoint\n\n        \"\"\"\n        if temporary:\n            checkpoint_func = self.reverter.add_to_temp_checkpoint\n        else:\n            checkpoint_func = self.reverter.add_to_checkpoint\n\n        try:\n            checkpoint_func(save_files, save_notes)\n        except errors.ReverterError as err:\n            raise errors.PluginError(str(err))\n\n    def finalize_checkpoint(self, title):\n        \"\"\"Timestamp and save changes made through the reverter.\n\n        :param str title: Title describing checkpoint\n\n        :raises .errors.PluginError: when an error occurs\n\n        \"\"\"\n        try:\n            self.reverter.finalize_checkpoint(title)\n        except errors.ReverterError as err:\n            raise errors.PluginError(str(err))\n\n    def recovery_routine(self):\n        \"\"\"Revert all previously modified files.\n\n        Reverts all modified files that have not been saved as a checkpoint\n\n        :raises .errors.PluginError: If unable to recover the configuration\n\n        \"\"\"\n        try:\n            self.reverter.recovery_routine()\n        except errors.ReverterError as err:\n            raise errors.PluginError(str(err))\n\n    def revert_temporary_config(self):\n        \"\"\"Rollback temporary checkpoint.\n\n        :raises .errors.PluginError: when unable to revert config\n\n        \"\"\"\n        try:\n            self.reverter.revert_temporary_config()\n        except errors.ReverterError as err:\n            raise errors.PluginError(str(err))\n\n    def rollback_checkpoints(self, rollback=1):\n        \"\"\"Rollback saved checkpoints.\n\n        :param int rollback: Number of checkpoints to revert\n\n        :raises .errors.PluginError: If there is a problem with the input or\n            the function is unable to correctly revert the configuration\n\n        \"\"\"\n        try:\n            self.reverter.rollback_checkpoints(rollback)\n        except errors.ReverterError as err:\n            raise errors.PluginError(str(err))\n\n    @property\n    def ssl_dhparams(self):\n        \"\"\"Full absolute path to ssl_dhparams file.\"\"\"\n        return os.path.join(self.config.config_dir, constants.SSL_DHPARAMS_DEST)\n\n    @property\n    def updated_ssl_dhparams_digest(self):\n        \"\"\"Full absolute path to digest of updated ssl_dhparams file.\"\"\"\n        return os.path.join(self.config.config_dir, constants.UPDATED_SSL_DHPARAMS_DIGEST)\n\n    def install_ssl_dhparams(self):\n        \"\"\"Copy Certbot's ssl_dhparams file into the system's config dir if required.\"\"\"\n        return install_version_controlled_file(\n            self.ssl_dhparams,\n            self.updated_ssl_dhparams_digest,\n            constants.SSL_DHPARAMS_SRC,\n            constants.ALL_SSL_DHPARAMS_HASHES)\n\n\nclass Addr(object):\n    r\"\"\"Represents an virtual host address.\n\n    :param str addr: addr part of vhost address\n    :param str port: port number or \\*, or \"\"\n\n    \"\"\"\n    def __init__(self, tup, ipv6=False):\n        self.tup = tup\n        self.ipv6 = ipv6\n\n    @classmethod\n    def fromstring(cls, str_addr):\n        \"\"\"Initialize Addr from string.\"\"\"\n        if str_addr.startswith('['):\n            # ipv6 addresses starts with [\n            endIndex = str_addr.rfind(']')\n            host = str_addr[:endIndex + 1]\n            port = ''\n            if len(str_addr) > endIndex + 2 and str_addr[endIndex + 1] == ':':\n                port = str_addr[endIndex + 2:]\n            return cls((host, port), ipv6=True)\n        else:\n            tup = str_addr.partition(':')\n            return cls((tup[0], tup[2]))\n\n    def __str__(self):\n        if self.tup[1]:\n            return \"%s:%s\" % self.tup\n        return self.tup[0]\n\n    def normalized_tuple(self):\n        \"\"\"Normalized representation of addr/port tuple\n        \"\"\"\n        if self.ipv6:\n            return (self.get_ipv6_exploded(), self.tup[1])\n        return self.tup\n\n    def __eq__(self, other):\n        if isinstance(other, self.__class__):\n            # compare normalized to take different\n            # styles of representation into account\n            return self.normalized_tuple() == other.normalized_tuple()\n\n        return False\n\n    def __hash__(self):\n        return hash(self.tup)\n\n    def get_addr(self):\n        \"\"\"Return addr part of Addr object.\"\"\"\n        return self.tup[0]\n\n    def get_port(self):\n        \"\"\"Return port.\"\"\"\n        return self.tup[1]\n\n    def get_addr_obj(self, port):\n        \"\"\"Return new address object with same addr and new port.\"\"\"\n        return self.__class__((self.tup[0], port), self.ipv6)\n\n    def _normalize_ipv6(self, addr):\n        \"\"\"Return IPv6 address in normalized form, helper function\"\"\"\n        addr = addr.lstrip(\"[\")\n        addr = addr.rstrip(\"]\")\n        return self._explode_ipv6(addr)\n\n    def get_ipv6_exploded(self):\n        \"\"\"Return IPv6 in normalized form\"\"\"\n        if self.ipv6:\n            return \":\".join(self._normalize_ipv6(self.tup[0]))\n        return \"\"\n\n    def _explode_ipv6(self, addr):\n        \"\"\"Explode IPv6 address for comparison\"\"\"\n        result = ['0', '0', '0', '0', '0', '0', '0', '0']\n        addr_list = addr.split(\":\")\n        if len(addr_list) > len(result):\n            # too long, truncate\n            addr_list = addr_list[0:len(result)]\n        append_to_end = False\n        for i, block in enumerate(addr_list):\n            if not block:\n                # encountered ::, so rest of the blocks should be\n                # appended to the end\n                append_to_end = True\n                continue\n            if len(block) > 1:\n                # remove leading zeros\n                block = block.lstrip(\"0\")\n            if not append_to_end:\n                result[i] = str(block)\n            else:\n                # count the location from the end using negative indices\n                result[i-len(addr_list)] = str(block)\n        return result\n\n\nclass ChallengePerformer(object):\n    \"\"\"Abstract base for challenge performers.\n\n    :ivar configurator: Authenticator and installer plugin\n    :ivar achalls: Annotated challenges\n    :vartype achalls: `list` of `.KeyAuthorizationAnnotatedChallenge`\n    :ivar indices: Holds the indices of challenges from a larger array\n        so the user of the class doesn't have to.\n    :vartype indices: `list` of `int`\n\n    \"\"\"\n\n    def __init__(self, configurator):\n        self.configurator = configurator\n        self.achalls = []  # type: List[achallenges.KeyAuthorizationAnnotatedChallenge]\n        self.indices = []  # type: List[int]\n\n    def add_chall(self, achall, idx=None):\n        \"\"\"Store challenge to be performed when perform() is called.\n\n        :param .KeyAuthorizationAnnotatedChallenge achall: Annotated\n            challenge.\n        :param int idx: index to challenge in a larger array\n\n        \"\"\"\n        self.achalls.append(achall)\n        if idx is not None:\n            self.indices.append(idx)\n\n    def perform(self):\n        \"\"\"Perform all added challenges.\n\n        :returns: challenge responses\n        :rtype: `list` of `acme.challenges.KeyAuthorizationChallengeResponse`\n\n\n        \"\"\"\n        raise NotImplementedError()\n\n\ndef install_version_controlled_file(dest_path, digest_path, src_path, all_hashes):\n    \"\"\"Copy a file into an active location (likely the system's config dir) if required.\n\n       :param str dest_path: destination path for version controlled file\n       :param str digest_path: path to save a digest of the file in\n       :param str src_path: path to version controlled file found in distribution\n       :param list all_hashes: hashes of every released version of the file\n    \"\"\"\n    current_hash = crypto_util.sha256sum(src_path)\n\n    def _write_current_hash():\n        with open(digest_path, \"w\") as f:\n            f.write(current_hash)\n\n    def _install_current_file():\n        shutil.copyfile(src_path, dest_path)\n        _write_current_hash()\n\n    # Check to make sure options-ssl.conf is installed\n    if not os.path.isfile(dest_path):\n        _install_current_file()\n        return\n    # there's already a file there. if it's up to date, do nothing. if it's not but\n    # it matches a known file hash, we can update it.\n    # otherwise, print a warning once per new version.\n    active_file_digest = crypto_util.sha256sum(dest_path)\n    if active_file_digest == current_hash: # already up to date\n        return\n    if active_file_digest in all_hashes: # safe to update\n        _install_current_file()\n    else:  # has been manually modified, not safe to update\n        # did they modify the current version or an old version?\n        if os.path.isfile(digest_path):\n            with open(digest_path, \"r\") as f:\n                saved_digest = f.read()\n            # they modified it after we either installed or told them about this version, so return\n            if saved_digest == current_hash:\n                return\n        # there's a new version but we couldn't update the file, or they deleted the digest.\n        # save the current digest so we only print this once, and print a warning\n        _write_current_hash()\n        logger.warning(\"%s has been manually modified; updated file \"\n            \"saved to %s. We recommend updating %s for security purposes.\",\n            dest_path, src_path, dest_path)\n\n\n# test utils used by certbot_apache/certbot_nginx (hence\n# \"pragma: no cover\") TODO: this might quickly lead to dead code (also\n# c.f. #383)\n\ndef dir_setup(test_dir, pkg):  # pragma: no cover\n    \"\"\"Setup the directories necessary for the configurator.\"\"\"\n    def expanded_tempdir(prefix):\n        \"\"\"Return the real path of a temp directory with the specified prefix\n\n        Some plugins rely on real paths of symlinks for working correctly. For\n        example, certbot-apache uses real paths of configuration files to tell\n        a virtual host from another. On systems where TMP itself is a symbolic\n        link, (ex: OS X) such plugins will be confused. This function prevents\n        such a case.\n        \"\"\"\n        return filesystem.realpath(tempfile.mkdtemp(prefix))\n\n    temp_dir = expanded_tempdir(\"temp\")\n    config_dir = expanded_tempdir(\"config\")\n    work_dir = expanded_tempdir(\"work\")\n\n    filesystem.chmod(temp_dir, constants.CONFIG_DIRS_MODE)\n    filesystem.chmod(config_dir, constants.CONFIG_DIRS_MODE)\n    filesystem.chmod(work_dir, constants.CONFIG_DIRS_MODE)\n\n    test_configs = pkg_resources.resource_filename(\n        pkg, os.path.join(\"testdata\", test_dir))\n\n    shutil.copytree(\n        test_configs, os.path.join(temp_dir, test_dir), symlinks=True)\n\n    return temp_dir, config_dir, work_dir\n\n\n# This class takes a similar approach to the cryptography project to deprecate attributes\n# in public modules. See the _ModuleWithDeprecation class here:\n# https://github.com/pyca/cryptography/blob/91105952739442a74582d3e62b3d2111365b0dc7/src/cryptography/utils.py#L129\nclass _TLSSNI01DeprecationModule(object):\n    \"\"\"\n    Internal class delegating to a module, and displaying warnings when\n    attributes related to TLS-SNI-01 are accessed.\n    \"\"\"\n    def __init__(self, module):\n        self.__dict__['_module'] = module\n\n    def __getattr__(self, attr):\n        if attr == 'TLSSNI01':\n            warnings.warn('TLSSNI01 is deprecated and will be removed soon.',\n                          DeprecationWarning, stacklevel=2)\n        return getattr(self._module, attr)\n\n    def __setattr__(self, attr, value):  # pragma: no cover\n        setattr(self._module, attr, value)\n\n    def __delattr__(self, attr):  # pragma: no cover\n        delattr(self._module, attr)\n\n    def __dir__(self):  # pragma: no cover\n        return ['_module'] + dir(self._module)\n\n\n# Patching ourselves to warn about TLS-SNI challenge deprecation and removal.\nsys.modules[__name__] = _TLSSNI01DeprecationModule(sys.modules[__name__])\n"
  },
  {
    "path": "certbot/plugins/dns_common.py",
    "content": "\"\"\"Common code for DNS Authenticator Plugins.\"\"\"\n\nimport abc\nimport logging\nfrom time import sleep\n\nimport configobj\nimport zope.interface\n\nfrom acme import challenges\nfrom certbot import errors\nfrom certbot import interfaces\nfrom certbot.compat import filesystem\nfrom certbot.compat import os\nfrom certbot.display import ops\nfrom certbot.display import util as display_util\nfrom certbot.plugins import common\n\nlogger = logging.getLogger(__name__)\n\n\n@zope.interface.implementer(interfaces.IAuthenticator)\n@zope.interface.provider(interfaces.IPluginFactory)\nclass DNSAuthenticator(common.Plugin):\n    \"\"\"Base class for DNS  Authenticators\"\"\"\n\n    def __init__(self, config, name):\n        super(DNSAuthenticator, self).__init__(config, name)\n\n        self._attempt_cleanup = False\n\n    @classmethod\n    def add_parser_arguments(cls, add, default_propagation_seconds=10):  # pylint: disable=arguments-differ\n        add('propagation-seconds',\n            default=default_propagation_seconds,\n            type=int,\n            help='The number of seconds to wait for DNS to propagate before asking the ACME server '\n                 'to verify the DNS record.')\n\n    def get_chall_pref(self, unused_domain):  # pylint: disable=missing-function-docstring\n        return [challenges.DNS01]\n\n    def prepare(self): # pylint: disable=missing-function-docstring\n        pass\n\n    def perform(self, achalls): # pylint: disable=missing-function-docstring\n        self._setup_credentials()\n\n        self._attempt_cleanup = True\n\n        responses = []\n        for achall in achalls:\n            domain = achall.domain\n            validation_domain_name = achall.validation_domain_name(domain)\n            validation = achall.validation(achall.account_key)\n\n            self._perform(domain, validation_domain_name, validation)\n            responses.append(achall.response(achall.account_key))\n\n        # DNS updates take time to propagate and checking to see if the update has occurred is not\n        # reliable (the machine this code is running on might be able to see an update before\n        # the ACME server). So: we sleep for a short amount of time we believe to be long enough.\n        logger.info(\"Waiting %d seconds for DNS changes to propagate\",\n                    self.conf('propagation-seconds'))\n        sleep(self.conf('propagation-seconds'))\n\n        return responses\n\n    def cleanup(self, achalls):  # pylint: disable=missing-function-docstring\n        if self._attempt_cleanup:\n            for achall in achalls:\n                domain = achall.domain\n                validation_domain_name = achall.validation_domain_name(domain)\n                validation = achall.validation(achall.account_key)\n\n                self._cleanup(domain, validation_domain_name, validation)\n\n    @abc.abstractmethod\n    def _setup_credentials(self):  # pragma: no cover\n        \"\"\"\n        Establish credentials, prompting if necessary.\n        \"\"\"\n        raise NotImplementedError()\n\n    @abc.abstractmethod\n    def _perform(self, domain, validation_name, validation):  # pragma: no cover\n        \"\"\"\n        Performs a dns-01 challenge by creating a DNS TXT record.\n\n        :param str domain: The domain being validated.\n        :param str validation_domain_name: The validation record domain name.\n        :param str validation: The validation record content.\n        :raises errors.PluginError: If the challenge cannot be performed\n        \"\"\"\n        raise NotImplementedError()\n\n    @abc.abstractmethod\n    def _cleanup(self, domain, validation_name, validation):  # pragma: no cover\n        \"\"\"\n        Deletes the DNS TXT record which would have been created by `_perform_achall`.\n\n        Fails gracefully if no such record exists.\n\n        :param str domain: The domain being validated.\n        :param str validation_domain_name: The validation record domain name.\n        :param str validation: The validation record content.\n        \"\"\"\n        raise NotImplementedError()\n\n    def _configure(self, key, label):\n        \"\"\"\n        Ensure that a configuration value is available.\n\n        If necessary, prompts the user and stores the result.\n\n        :param str key: The configuration key.\n        :param str label: The user-friendly label for this piece of information.\n        \"\"\"\n\n        configured_value = self.conf(key)\n        if not configured_value:\n            new_value = self._prompt_for_data(label)\n\n            setattr(self.config, self.dest(key), new_value)\n\n    def _configure_file(self, key, label, validator=None):\n        \"\"\"\n        Ensure that a configuration value is available for a path.\n\n        If necessary, prompts the user and stores the result.\n\n        :param str key: The configuration key.\n        :param str label: The user-friendly label for this piece of information.\n        \"\"\"\n\n        configured_value = self.conf(key)\n        if not configured_value:\n            new_value = self._prompt_for_file(label, validator)\n\n            setattr(self.config, self.dest(key), os.path.abspath(os.path.expanduser(new_value)))\n\n    def _configure_credentials(self, key, label, required_variables=None, validator=None):\n        \"\"\"\n        As `_configure_file`, but for a credential configuration file.\n\n        If necessary, prompts the user and stores the result.\n\n        Always stores absolute paths to avoid issues during renewal.\n\n        :param str key: The configuration key.\n        :param str label: The user-friendly label for this piece of information.\n        :param dict required_variables: Map of variable which must be present to error to display.\n        :param callable validator: A method which will be called to validate the\n            `CredentialsConfiguration` resulting from the supplied input after it has been validated\n            to contain the `required_variables`. Should throw a `~certbot.errors.PluginError` to\n            indicate any issue.\n        \"\"\"\n\n        def __validator(filename):\n            configuration = CredentialsConfiguration(filename, self.dest)\n\n            if required_variables:\n                configuration.require(required_variables)\n\n            if validator:\n                validator(configuration)\n\n        self._configure_file(key, label, __validator)\n\n        credentials_configuration = CredentialsConfiguration(self.conf(key), self.dest)\n        if required_variables:\n            credentials_configuration.require(required_variables)\n\n        if validator:\n            validator(credentials_configuration)\n\n        return credentials_configuration\n\n    @staticmethod\n    def _prompt_for_data(label):\n        \"\"\"\n        Prompt the user for a piece of information.\n\n        :param str label: The user-friendly label for this piece of information.\n        :returns: The user's response (guaranteed non-empty).\n        :rtype: str\n        \"\"\"\n\n        def __validator(i):\n            if not i:\n                raise errors.PluginError('Please enter your {0}.'.format(label))\n\n        code, response = ops.validated_input(\n            __validator,\n            'Input your {0}'.format(label),\n            force_interactive=True)\n\n        if code == display_util.OK:\n            return response\n        raise errors.PluginError('{0} required to proceed.'.format(label))\n\n    @staticmethod\n    def _prompt_for_file(label, validator=None):\n        \"\"\"\n        Prompt the user for a path.\n\n        :param str label: The user-friendly label for the file.\n        :param callable validator: A method which will be called to validate the supplied input\n            after it has been validated to be a non-empty path to an existing file. Should throw a\n            `~certbot.errors.PluginError` to indicate any issue.\n        :returns: The user's response (guaranteed to exist).\n        :rtype: str\n        \"\"\"\n\n        def __validator(filename):\n            if not filename:\n                raise errors.PluginError('Please enter a valid path to your {0}.'.format(label))\n\n            filename = os.path.expanduser(filename)\n\n            validate_file(filename)\n\n            if validator:\n                validator(filename)\n\n        code, response = ops.validated_directory(\n            __validator,\n            'Input the path to your {0}'.format(label),\n            force_interactive=True)\n\n        if code == display_util.OK:\n            return response\n        raise errors.PluginError('{0} required to proceed.'.format(label))\n\n\nclass CredentialsConfiguration(object):\n    \"\"\"Represents a user-supplied filed which stores API credentials.\"\"\"\n\n    def __init__(self, filename, mapper=lambda x: x):\n        \"\"\"\n        :param str filename: A path to the configuration file.\n        :param callable mapper: A transformation to apply to configuration key names\n        :raises errors.PluginError: If the file does not exist or is not a valid format.\n        \"\"\"\n        validate_file_permissions(filename)\n\n        try:\n            self.confobj = configobj.ConfigObj(filename)\n        except configobj.ConfigObjError as e:\n            logger.debug(\"Error parsing credentials configuration: %s\", e, exc_info=True)\n            raise errors.PluginError(\"Error parsing credentials configuration: {0}\".format(e))\n\n        self.mapper = mapper\n\n    def require(self, required_variables):\n        \"\"\"Ensures that the supplied set of variables are all present in the file.\n\n        :param dict required_variables: Map of variable which must be present to error to display.\n        :raises errors.PluginError: If one or more are missing.\n        \"\"\"\n        messages = []\n\n        for var in required_variables:\n            if not self._has(var):\n                messages.append('Property \"{0}\" not found (should be {1}).'\n                                .format(self.mapper(var), required_variables[var]))\n            elif not self._get(var):\n                messages.append('Property \"{0}\" not set (should be {1}).'\n                                .format(self.mapper(var), required_variables[var]))\n\n        if messages:\n            raise errors.PluginError(\n                'Missing {0} in credentials configuration file {1}:\\n * {2}'.format(\n                        'property' if len(messages) == 1 else 'properties',\n                        self.confobj.filename,\n                        '\\n * '.join(messages)\n                    )\n            )\n\n    def conf(self, var):\n        \"\"\"Find a configuration value for variable `var`, as transformed by `mapper`.\n\n        :param str var: The variable to get.\n        :returns: The value of the variable.\n        :rtype: str\n        \"\"\"\n\n        return self._get(var)\n\n    def _has(self, var):\n        return self.mapper(var) in self.confobj\n\n    def _get(self, var):\n        return self.confobj.get(self.mapper(var))\n\n\ndef validate_file(filename):\n    \"\"\"Ensure that the specified file exists.\"\"\"\n\n    if not os.path.exists(filename):\n        raise errors.PluginError('File not found: {0}'.format(filename))\n\n    if os.path.isdir(filename):\n        raise errors.PluginError('Path is a directory: {0}'.format(filename))\n\n\ndef validate_file_permissions(filename):\n    \"\"\"Ensure that the specified file exists and warn about unsafe permissions.\"\"\"\n\n    validate_file(filename)\n\n    if filesystem.has_world_permissions(filename):\n        logger.warning('Unsafe permissions on credentials configuration file: %s', filename)\n\n\ndef base_domain_name_guesses(domain):\n    \"\"\"Return a list of progressively less-specific domain names.\n\n    One of these will probably be the domain name known to the DNS provider.\n\n    :Example:\n\n    >>> base_domain_name_guesses('foo.bar.baz.example.com')\n    ['foo.bar.baz.example.com', 'bar.baz.example.com', 'baz.example.com', 'example.com', 'com']\n\n    :param str domain: The domain for which to return guesses.\n    :returns: The a list of less specific domain names.\n    :rtype: list\n    \"\"\"\n\n    fragments = domain.split('.')\n    return ['.'.join(fragments[i:]) for i in range(0, len(fragments))]\n"
  },
  {
    "path": "certbot/plugins/dns_common_lexicon.py",
    "content": "\"\"\"Common code for DNS Authenticator Plugins built on Lexicon.\"\"\"\nimport logging\n\nfrom requests.exceptions import HTTPError\nfrom requests.exceptions import RequestException\n\nfrom acme.magic_typing import Any\nfrom acme.magic_typing import Dict\nfrom acme.magic_typing import Union\nfrom certbot import errors\nfrom certbot.plugins import dns_common\n\n# Lexicon is not declared as a dependency in Certbot itself,\n# but in the Certbot plugins backed by Lexicon.\n# So we catch import error here to allow this module to be\n# always importable, even if it does not make sense to use it\n# if Lexicon is not available, obviously.\ntry:\n    from lexicon.config import ConfigResolver\nexcept ImportError:\n    ConfigResolver = None  # type: ignore\n\nlogger = logging.getLogger(__name__)\n\n\nclass LexiconClient(object):\n    \"\"\"\n    Encapsulates all communication with a DNS provider via Lexicon.\n    \"\"\"\n\n    def __init__(self):\n        self.provider = None\n\n    def add_txt_record(self, domain, record_name, record_content):\n        \"\"\"\n        Add a TXT record using the supplied information.\n\n        :param str domain: The domain to use to look up the managed zone.\n        :param str record_name: The record name (typically beginning with '_acme-challenge.').\n        :param str record_content: The record content (typically the challenge validation).\n        :raises errors.PluginError: if an error occurs communicating with the DNS Provider API\n        \"\"\"\n        self._find_domain_id(domain)\n\n        try:\n            self.provider.create_record(type='TXT', name=record_name, content=record_content)\n        except RequestException as e:\n            logger.debug('Encountered error adding TXT record: %s', e, exc_info=True)\n            raise errors.PluginError('Error adding TXT record: {0}'.format(e))\n\n    def del_txt_record(self, domain, record_name, record_content):\n        \"\"\"\n        Delete a TXT record using the supplied information.\n\n        :param str domain: The domain to use to look up the managed zone.\n        :param str record_name: The record name (typically beginning with '_acme-challenge.').\n        :param str record_content: The record content (typically the challenge validation).\n        :raises errors.PluginError: if an error occurs communicating with the DNS Provider  API\n        \"\"\"\n        try:\n            self._find_domain_id(domain)\n        except errors.PluginError as e:\n            logger.debug('Encountered error finding domain_id during deletion: %s', e,\n                         exc_info=True)\n            return\n\n        try:\n            self.provider.delete_record(type='TXT', name=record_name, content=record_content)\n        except RequestException as e:\n            logger.debug('Encountered error deleting TXT record: %s', e, exc_info=True)\n\n    def _find_domain_id(self, domain):\n        \"\"\"\n        Find the domain_id for a given domain.\n\n        :param str domain: The domain for which to find the domain_id.\n        :raises errors.PluginError: if the domain_id cannot be found.\n        \"\"\"\n\n        domain_name_guesses = dns_common.base_domain_name_guesses(domain)\n\n        for domain_name in domain_name_guesses:\n            try:\n                if hasattr(self.provider, 'options'):\n                    # For Lexicon 2.x\n                    self.provider.options['domain'] = domain_name\n                else:\n                    # For Lexicon 3.x\n                    self.provider.domain = domain_name\n\n                self.provider.authenticate()\n\n                return  # If `authenticate` doesn't throw an exception, we've found the right name\n            except HTTPError as e:\n                result = self._handle_http_error(e, domain_name)\n\n                if result:\n                    raise result\n            except Exception as e:  # pylint: disable=broad-except\n                result = self._handle_general_error(e, domain_name)\n\n                if result:\n                    raise result  # pylint: disable=raising-bad-type\n\n        raise errors.PluginError('Unable to determine zone identifier for {0} using zone names: {1}'\n                                 .format(domain, domain_name_guesses))\n\n    def _handle_http_error(self, e, domain_name):\n        return errors.PluginError('Error determining zone identifier for {0}: {1}.'\n                                  .format(domain_name, e))\n\n    def _handle_general_error(self, e, domain_name):\n        if not str(e).startswith('No domain found'):\n            return errors.PluginError('Unexpected error determining zone identifier for {0}: {1}'\n                                      .format(domain_name, e))\n        return None\n\n\ndef build_lexicon_config(lexicon_provider_name, lexicon_options, provider_options):\n    # type: (str, Dict, Dict) -> Union[ConfigResolver, Dict]\n    \"\"\"\n    Convenient function to build a Lexicon 2.x/3.x config object.\n    :param str lexicon_provider_name: the name of the lexicon provider to use\n    :param dict lexicon_options: options specific to lexicon\n    :param dict provider_options: options specific to provider\n    :return: configuration to apply to the provider\n    :rtype: ConfigurationResolver or dict\n    \"\"\"\n    config = {'provider_name': lexicon_provider_name}  # type: Dict[str, Any]\n    config.update(lexicon_options)\n    if not ConfigResolver:\n        # Lexicon 2.x\n        config.update(provider_options)\n    else:\n        # Lexicon 3.x\n        provider_config = {}\n        provider_config.update(provider_options)\n        config[lexicon_provider_name] = provider_config\n        config = ConfigResolver().with_dict(config).with_env()\n\n    return config\n"
  },
  {
    "path": "certbot/plugins/dns_test_common.py",
    "content": "\"\"\"Base test class for DNS authenticators.\"\"\"\n\nimport configobj\nimport josepy as jose\nimport mock\nimport six\n\nfrom acme import challenges\nfrom certbot import achallenges\nfrom certbot.compat import filesystem\nfrom certbot.tests import acme_util\nfrom certbot.tests import util as test_util\n\nDOMAIN = 'example.com'\nKEY = jose.JWKRSA.load(test_util.load_vector(\"rsa512_key.pem\"))\n\n\nclass BaseAuthenticatorTest(object):\n    \"\"\"\n    A base test class to reduce duplication between test code for DNS Authenticator Plugins.\n\n    Assumes:\n     * That subclasses also subclass unittest.TestCase\n     * That the authenticator is stored as self.auth\n    \"\"\"\n\n    achall = achallenges.KeyAuthorizationAnnotatedChallenge(\n        challb=acme_util.DNS01, domain=DOMAIN, account_key=KEY)\n\n    def test_more_info(self):\n        self.assertTrue(isinstance(self.auth.more_info(), six.string_types))  # pylint: disable=no-member\n\n    def test_get_chall_pref(self):\n        self.assertEqual(self.auth.get_chall_pref(None), [challenges.DNS01])  # pylint: disable=no-member\n\n    def test_parser_arguments(self):\n        m = mock.MagicMock()\n        self.auth.add_parser_arguments(m)  # pylint: disable=no-member\n\n        m.assert_any_call('propagation-seconds', type=int, default=mock.ANY, help=mock.ANY)\n\n\ndef write(values, path):\n    \"\"\"Write the specified values to a config file.\n\n    :param dict values: A map of values to write.\n    :param str path: Where to write the values.\n    \"\"\"\n\n    config = configobj.ConfigObj()\n\n    for key in values:\n        config[key] = values[key]\n\n    with open(path, \"wb\") as f:\n        config.write(outfile=f)\n\n    filesystem.chmod(path, 0o600)\n"
  },
  {
    "path": "certbot/plugins/dns_test_common_lexicon.py",
    "content": "\"\"\"Base test class for DNS authenticators built on Lexicon.\"\"\"\n\nimport josepy as jose\nimport mock\nfrom requests.exceptions import HTTPError\nfrom requests.exceptions import RequestException\n\nfrom certbot import errors\nfrom certbot.plugins import dns_test_common\nfrom certbot.tests import util as test_util\n\nDOMAIN = 'example.com'\nKEY = jose.JWKRSA.load(test_util.load_vector(\"rsa512_key.pem\"))\n\n# These classes are intended to be subclassed/mixed in, so not all members are defined.\n# pylint: disable=no-member\n\nclass BaseLexiconAuthenticatorTest(dns_test_common.BaseAuthenticatorTest):\n\n    def test_perform(self):\n        self.auth.perform([self.achall])\n\n        expected = [mock.call.add_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY)]\n        self.assertEqual(expected, self.mock_client.mock_calls)\n\n    def test_cleanup(self):\n        self.auth._attempt_cleanup = True  # _attempt_cleanup | pylint: disable=protected-access\n        self.auth.cleanup([self.achall])\n\n        expected = [mock.call.del_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY)]\n        self.assertEqual(expected, self.mock_client.mock_calls)\n\n\nclass BaseLexiconClientTest(object):\n    DOMAIN_NOT_FOUND = Exception('No domain found')\n    GENERIC_ERROR = RequestException\n    LOGIN_ERROR = HTTPError('400 Client Error: ...')\n    UNKNOWN_LOGIN_ERROR = HTTPError('500 Surprise! Error: ...')\n\n    record_prefix = \"_acme-challenge\"\n    record_name = record_prefix + \".\" + DOMAIN\n    record_content = \"bar\"\n\n    def test_add_txt_record(self):\n        self.client.add_txt_record(DOMAIN, self.record_name, self.record_content)\n\n        self.provider_mock.create_record.assert_called_with(type='TXT',\n                                                            name=self.record_name,\n                                                            content=self.record_content)\n\n    def test_add_txt_record_try_twice_to_find_domain(self):\n        self.provider_mock.authenticate.side_effect = [self.DOMAIN_NOT_FOUND, '']\n\n        self.client.add_txt_record(DOMAIN, self.record_name, self.record_content)\n\n        self.provider_mock.create_record.assert_called_with(type='TXT',\n                                                            name=self.record_name,\n                                                            content=self.record_content)\n\n    def test_add_txt_record_fail_to_find_domain(self):\n        self.provider_mock.authenticate.side_effect = [self.DOMAIN_NOT_FOUND,\n                                                       self.DOMAIN_NOT_FOUND,\n                                                       self.DOMAIN_NOT_FOUND,]\n\n        self.assertRaises(errors.PluginError,\n                          self.client.add_txt_record,\n                          DOMAIN, self.record_name, self.record_content)\n\n    def test_add_txt_record_fail_to_authenticate(self):\n        self.provider_mock.authenticate.side_effect = self.LOGIN_ERROR\n\n        self.assertRaises(errors.PluginError,\n                          self.client.add_txt_record,\n                          DOMAIN, self.record_name, self.record_content)\n\n    def test_add_txt_record_fail_to_authenticate_with_unknown_error(self):\n        self.provider_mock.authenticate.side_effect = self.UNKNOWN_LOGIN_ERROR\n\n        self.assertRaises(errors.PluginError,\n                          self.client.add_txt_record,\n                          DOMAIN, self.record_name, self.record_content)\n\n    def test_add_txt_record_error_finding_domain(self):\n        self.provider_mock.authenticate.side_effect = self.GENERIC_ERROR\n\n        self.assertRaises(errors.PluginError,\n                          self.client.add_txt_record,\n                          DOMAIN, self.record_name, self.record_content)\n\n    def test_add_txt_record_error_adding_record(self):\n        self.provider_mock.create_record.side_effect = self.GENERIC_ERROR\n\n        self.assertRaises(errors.PluginError,\n                          self.client.add_txt_record,\n                          DOMAIN, self.record_name, self.record_content)\n\n    def test_del_txt_record(self):\n        self.client.del_txt_record(DOMAIN, self.record_name, self.record_content)\n\n        self.provider_mock.delete_record.assert_called_with(type='TXT',\n                                                            name=self.record_name,\n                                                            content=self.record_content)\n\n    def test_del_txt_record_fail_to_find_domain(self):\n        self.provider_mock.authenticate.side_effect = [self.DOMAIN_NOT_FOUND,\n                                                       self.DOMAIN_NOT_FOUND,\n                                                       self.DOMAIN_NOT_FOUND, ]\n\n        self.client.del_txt_record(DOMAIN, self.record_name, self.record_content)\n\n    def test_del_txt_record_fail_to_authenticate(self):\n        self.provider_mock.authenticate.side_effect = self.LOGIN_ERROR\n\n        self.client.del_txt_record(DOMAIN, self.record_name, self.record_content)\n\n    def test_del_txt_record_fail_to_authenticate_with_unknown_error(self):\n        self.provider_mock.authenticate.side_effect = self.UNKNOWN_LOGIN_ERROR\n\n        self.client.del_txt_record(DOMAIN, self.record_name, self.record_content)\n\n    def test_del_txt_record_error_finding_domain(self):\n        self.provider_mock.authenticate.side_effect = self.GENERIC_ERROR\n\n        self.client.del_txt_record(DOMAIN, self.record_name, self.record_content)\n\n    def test_del_txt_record_error_deleting_record(self):\n        self.provider_mock.delete_record.side_effect = self.GENERIC_ERROR\n\n        self.client.del_txt_record(DOMAIN, self.record_name, self.record_content)\n"
  },
  {
    "path": "certbot/plugins/enhancements.py",
    "content": "\"\"\"New interface style Certbot enhancements\"\"\"\nimport abc\n\nimport six\n\nfrom acme.magic_typing import Any\nfrom acme.magic_typing import Dict\nfrom acme.magic_typing import List\nfrom certbot._internal import constants\n\nENHANCEMENTS = [\"redirect\", \"ensure-http-header\", \"ocsp-stapling\"]\n\"\"\"List of possible :class:`certbot.interfaces.IInstaller`\nenhancements.\n\nList of expected options parameters:\n- redirect: None\n- ensure-http-header: name of header (i.e. Strict-Transport-Security)\n- ocsp-stapling: certificate chain file path\n\n\"\"\"\n\ndef enabled_enhancements(config):\n    \"\"\"\n    Generator to yield the enabled new style enhancements.\n\n    :param config: Configuration.\n    :type config: :class:`certbot.interfaces.IConfig`\n    \"\"\"\n    for enh in _INDEX:\n        if getattr(config, enh[\"cli_dest\"]):\n            yield enh\n\ndef are_requested(config):\n    \"\"\"\n    Checks if one or more of the requested enhancements are those of the new\n    enhancement interfaces.\n\n    :param config: Configuration.\n    :type config: :class:`certbot.interfaces.IConfig`\n    \"\"\"\n    return any(enabled_enhancements(config))\n\ndef are_supported(config, installer):\n    \"\"\"\n    Checks that all of the requested enhancements are supported by the\n    installer.\n\n    :param config: Configuration.\n    :type config: :class:`certbot.interfaces.IConfig`\n\n    :param installer: Installer object\n    :type installer: interfaces.IInstaller\n\n    :returns: If all the requested enhancements are supported by the installer\n    :rtype: bool\n    \"\"\"\n    for enh in enabled_enhancements(config):\n        if not isinstance(installer, enh[\"class\"]):\n            return False\n    return True\n\ndef enable(lineage, domains, installer, config):\n    \"\"\"\n    Run enable method for each requested enhancement that is supported.\n\n    :param lineage: Certificate lineage object\n    :type lineage: certbot.interfaces.RenewableCert\n\n    :param domains: List of domains in certificate to enhance\n    :type domains: str\n\n    :param installer: Installer object\n    :type installer: interfaces.IInstaller\n\n    :param config: Configuration.\n    :type config: :class:`certbot.interfaces.IConfig`\n    \"\"\"\n    for enh in enabled_enhancements(config):\n        getattr(installer, enh[\"enable_function\"])(lineage, domains)\n\ndef populate_cli(add):\n    \"\"\"\n    Populates the command line flags for certbot._internal.cli.HelpfulParser\n\n    :param add: Add function of certbot._internal.cli.HelpfulParser\n    :type add: func\n    \"\"\"\n    for enh in _INDEX:\n        add(enh[\"cli_groups\"], enh[\"cli_flag\"], action=enh[\"cli_action\"],\n            dest=enh[\"cli_dest\"], default=enh[\"cli_flag_default\"],\n            help=enh[\"cli_help\"])\n\n\n@six.add_metaclass(abc.ABCMeta)\nclass AutoHSTSEnhancement(object):\n    \"\"\"\n    Enhancement interface that installer plugins can implement in order to\n    provide functionality that configures the software to have a\n    'Strict-Transport-Security' with initially low max-age value that will\n    increase over time.\n\n    The plugins implementing new style enhancements are responsible of handling\n    the saving of configuration checkpoints as well as calling possible restarts\n    of managed software themselves. For update_autohsts method, the installer may\n    have to call prepare() to finalize the plugin initialization.\n\n    Methods:\n        enable_autohsts is called when the header is initially installed using a\n        low max-age value.\n\n        update_autohsts is called every time when Certbot is run using 'renew'\n        verb. The max-age value should be increased over time using this method.\n\n        deploy_autohsts is called for every lineage that has had its certificate\n        renewed. A long HSTS max-age value should be set here, as we should be\n        confident that the user is able to automatically renew their certificates.\n\n\n    \"\"\"\n\n    @abc.abstractmethod\n    def update_autohsts(self, lineage, *args, **kwargs):\n        \"\"\"\n        Gets called for each lineage every time Certbot is run with 'renew' verb.\n        Implementation of this method should increase the max-age value.\n\n        :param lineage: Certificate lineage object\n        :type lineage: certbot.interfaces.RenewableCert\n\n        .. note:: prepare() method inherited from `interfaces.IPlugin` might need\n            to be called manually within implementation of this interface method\n            to finalize the plugin initialization.\n        \"\"\"\n\n    @abc.abstractmethod\n    def deploy_autohsts(self, lineage, *args, **kwargs):\n        \"\"\"\n        Gets called for a lineage when its certificate is successfully renewed.\n        Long max-age value should be set in implementation of this method.\n\n        :param lineage: Certificate lineage object\n        :type lineage: certbot.interfaces.RenewableCert\n        \"\"\"\n\n    @abc.abstractmethod\n    def enable_autohsts(self, lineage, domains, *args, **kwargs):\n        \"\"\"\n        Enables the AutoHSTS enhancement, installing\n        Strict-Transport-Security header with a low initial value to be increased\n        over the subsequent runs of Certbot renew.\n\n        :param lineage: Certificate lineage object\n        :type lineage: certbot.interfaces.RenewableCert\n\n        :param domains: List of domains in certificate to enhance\n        :type domains: `list` of `str`\n        \"\"\"\n\n# This is used to configure internal new style enhancements in Certbot. These\n# enhancement interfaces need to be defined in this file. Please do not modify\n# this list from plugin code.\n_INDEX = [\n    {\n        \"name\": \"AutoHSTS\",\n        \"cli_help\": \"Gradually increasing max-age value for HTTP Strict Transport \"+\n                    \"Security security header\",\n        \"cli_flag\": \"--auto-hsts\",\n        \"cli_flag_default\": constants.CLI_DEFAULTS[\"auto_hsts\"],\n        \"cli_groups\": [\"security\", \"enhance\"],\n        \"cli_dest\": \"auto_hsts\",\n        \"cli_action\": \"store_true\",\n        \"class\": AutoHSTSEnhancement,\n        \"updater_function\": \"update_autohsts\",\n        \"deployer_function\": \"deploy_autohsts\",\n        \"enable_function\": \"enable_autohsts\"\n    }\n]  # type: List[Dict[str, Any]]\n"
  },
  {
    "path": "certbot/plugins/storage.py",
    "content": "\"\"\"Plugin storage class.\"\"\"\nimport json\nimport logging\n\nfrom acme.magic_typing import Any\nfrom acme.magic_typing import Dict\nfrom certbot import errors\nfrom certbot.compat import filesystem\nfrom certbot.compat import os\n\nlogger = logging.getLogger(__name__)\n\n\nclass PluginStorage(object):\n    \"\"\"Class implementing storage functionality for plugins\"\"\"\n\n    def __init__(self, config, classkey):\n        \"\"\"Initializes PluginStorage object storing required configuration\n        options.\n\n        :param .configuration.NamespaceConfig config: Configuration object\n        :param str classkey: class name to use as root key in storage file\n\n        \"\"\"\n\n        self._config = config\n        self._classkey = classkey\n        self._initialized = False\n        self._data = None\n        self._storagepath = None\n\n    def _initialize_storage(self):\n        \"\"\"Initializes PluginStorage data and reads current state from the disk\n        if the storage json exists.\"\"\"\n\n        self._storagepath = os.path.join(self._config.config_dir, \".pluginstorage.json\")\n        self._load()\n        self._initialized = True\n\n    def _load(self):\n        \"\"\"Reads PluginStorage content from the disk to a dict structure\n\n        :raises .errors.PluginStorageError: when unable to open or read the file\n        \"\"\"\n        data = dict()  # type: Dict[str, Any]\n        filedata = \"\"\n        try:\n            with open(self._storagepath, 'r') as fh:\n                filedata = fh.read()\n        except IOError as e:\n            errmsg = \"Could not read PluginStorage data file: {0} : {1}\".format(\n                self._storagepath, str(e))\n            if os.path.isfile(self._storagepath):\n                # Only error out if file exists, but cannot be read\n                logger.error(errmsg)\n                raise errors.PluginStorageError(errmsg)\n        try:\n            data = json.loads(filedata)\n        except ValueError:\n            if not filedata:\n                logger.debug(\"Plugin storage file %s was empty, no values loaded\",\n                             self._storagepath)\n            else:\n                errmsg = \"PluginStorage file {0} is corrupted.\".format(\n                    self._storagepath)\n                logger.error(errmsg)\n                raise errors.PluginStorageError(errmsg)\n        self._data = data\n\n    def save(self):\n        \"\"\"Saves PluginStorage content to disk\n\n        :raises .errors.PluginStorageError: when unable to serialize the data\n            or write it to the filesystem\n        \"\"\"\n        if not self._initialized:\n            errmsg = \"Unable to save, no values have been added to PluginStorage.\"\n            logger.error(errmsg)\n            raise errors.PluginStorageError(errmsg)\n\n        try:\n            serialized = json.dumps(self._data)\n        except TypeError as e:\n            errmsg = \"Could not serialize PluginStorage data: {0}\".format(\n                str(e))\n            logger.error(errmsg)\n            raise errors.PluginStorageError(errmsg)\n        try:\n            with os.fdopen(filesystem.open(\n                    self._storagepath,\n                    os.O_WRONLY | os.O_CREAT | os.O_TRUNC,\n                    0o600), 'w') as fh:\n                fh.write(serialized)\n        except IOError as e:\n            errmsg = \"Could not write PluginStorage data to file {0} : {1}\".format(\n                self._storagepath, str(e))\n            logger.error(errmsg)\n            raise errors.PluginStorageError(errmsg)\n\n    def put(self, key, value):\n        \"\"\"Put configuration value to PluginStorage\n\n        :param str key: Key to store the value to\n        :param value: Data to store\n        \"\"\"\n        if not self._initialized:\n            self._initialize_storage()\n\n        if not self._classkey in self._data.keys():\n            self._data[self._classkey] = dict()\n        self._data[self._classkey][key] = value\n\n    def fetch(self, key):\n        \"\"\"Get configuration value from PluginStorage\n\n        :param str key: Key to get value from the storage\n\n        :raises KeyError: If the key doesn't exist in the storage\n        \"\"\"\n        if not self._initialized:\n            self._initialize_storage()\n\n        return self._data[self._classkey][key]\n"
  },
  {
    "path": "certbot/plugins/util.py",
    "content": "\"\"\"Plugin utilities.\"\"\"\nimport logging\n\nfrom certbot import util\nfrom certbot.compat import os\nfrom certbot.compat.misc import STANDARD_BINARY_DIRS\n\nlogger = logging.getLogger(__name__)\n\n\ndef get_prefixes(path):\n    \"\"\"Retrieves all possible path prefixes of a path, in descending order\n    of length. For instance,\n        (linux) /a/b/c returns ['/a/b/c', '/a/b', '/a', '/']\n        (windows) C:\\\\a\\\\b\\\\c returns ['C:\\\\a\\\\b\\\\c', 'C:\\\\a\\\\b', 'C:\\\\a', 'C:']\n    :param str path: the path to break into prefixes\n\n    :returns: all possible path prefixes of given path in descending order\n    :rtype: `list` of `str`\n    \"\"\"\n    prefix = os.path.normpath(path)\n    prefixes = []\n    while prefix:\n        prefixes.append(prefix)\n        prefix, _ = os.path.split(prefix)\n        # break once we hit the root path\n        if prefix == prefixes[-1]:\n            break\n    return prefixes\n\n\ndef path_surgery(cmd):\n    \"\"\"Attempt to perform PATH surgery to find cmd\n\n    Mitigates https://github.com/certbot/certbot/issues/1833\n\n    :param str cmd: the command that is being searched for in the PATH\n\n    :returns: True if the operation succeeded, False otherwise\n    \"\"\"\n    path = os.environ[\"PATH\"]\n    added = []\n    for d in STANDARD_BINARY_DIRS:\n        if d not in path:\n            path += os.pathsep + d\n            added.append(d)\n\n    if any(added):\n        logger.debug(\"Can't find %s, attempting PATH mitigation by adding %s\",\n                     cmd, os.pathsep.join(added))\n        os.environ[\"PATH\"] = path\n\n    if util.exe_exists(cmd):\n        return True\n    expanded = \" expanded\" if any(added) else \"\"\n    logger.debug(\"Failed to find executable %s in%s PATH: %s\", cmd,\n                 expanded, path)\n    return False\n"
  },
  {
    "path": "certbot/reverter.py",
    "content": "\"\"\"Reverter class saves configuration checkpoints and allows for recovery.\"\"\"\nimport csv\nimport glob\nimport logging\nimport shutil\nimport sys\nimport time\nimport traceback\n\nimport six\n\nfrom certbot import errors\nfrom certbot import util\nfrom certbot._internal import constants\nfrom certbot.compat import filesystem\nfrom certbot.compat import os\n\nlogger = logging.getLogger(__name__)\n\n\nclass Reverter(object):\n    \"\"\"Reverter Class - save and revert configuration checkpoints.\n\n    This class can be used by the plugins, especially Installers, to\n    undo changes made to the user's system. Modifications to files and\n    commands to do undo actions taken by the plugin should be registered\n    with this class before the action is taken.\n\n    Once a change has been registered with this class, there are three\n    states the change can be in. First, the change can be a temporary\n    change. This should be used for changes that will soon be reverted,\n    such as config changes for the purpose of solving a challenge.\n    Changes are added to this state through calls to\n    :func:`~add_to_temp_checkpoint` and reverted when\n    :func:`~revert_temporary_config` or :func:`~recovery_routine` is\n    called.\n\n    The second state a change can be in is in progress. These changes\n    are not temporary, however, they also have not been finalized in a\n    checkpoint. A change must become in progress before it can be\n    finalized. Changes are added to this state through calls to\n    :func:`~add_to_checkpoint` and reverted when\n    :func:`~recovery_routine` is called.\n\n    The last state a change can be in is finalized in a checkpoint. A\n    change is put into this state by first becoming an in progress\n    change and then calling :func:`~finalize_checkpoint`. Changes\n    in this state can be reverted through calls to\n    :func:`~rollback_checkpoints`.\n\n    As a final note, creating new files and registering undo commands\n    are handled specially and use the methods\n    :func:`~register_file_creation` and :func:`~register_undo_command`\n    respectively. Both of these methods can be used to create either\n    temporary or in progress changes.\n\n    .. note:: Consider moving everything over to CSV format.\n\n    :param config: Configuration.\n    :type config: :class:`certbot.interfaces.IConfig`\n\n    \"\"\"\n    def __init__(self, config):\n        self.config = config\n\n        util.make_or_verify_dir(\n            config.backup_dir, constants.CONFIG_DIRS_MODE, self.config.strict_permissions)\n\n    def revert_temporary_config(self):\n        \"\"\"Reload users original configuration files after a temporary save.\n\n        This function should reinstall the users original configuration files\n        for all saves with temporary=True\n\n        :raises .ReverterError: when unable to revert config\n\n        \"\"\"\n        if os.path.isdir(self.config.temp_checkpoint_dir):\n            try:\n                self._recover_checkpoint(self.config.temp_checkpoint_dir)\n            except errors.ReverterError:\n                # We have a partial or incomplete recovery\n                logger.critical(\n                    \"Incomplete or failed recovery for %s\",\n                    self.config.temp_checkpoint_dir,\n                )\n                raise errors.ReverterError(\"Unable to revert temporary config\")\n\n    def rollback_checkpoints(self, rollback=1):\n        \"\"\"Revert 'rollback' number of configuration checkpoints.\n\n        :param int rollback: Number of checkpoints to reverse. A str num will be\n           cast to an integer. So \"2\" is also acceptable.\n\n        :raises .ReverterError:\n            if there is a problem with the input or if the function is\n            unable to correctly revert the configuration checkpoints\n\n        \"\"\"\n        try:\n            rollback = int(rollback)\n        except ValueError:\n            logger.error(\"Rollback argument must be a positive integer\")\n            raise errors.ReverterError(\"Invalid Input\")\n        # Sanity check input\n        if rollback < 0:\n            logger.error(\"Rollback argument must be a positive integer\")\n            raise errors.ReverterError(\"Invalid Input\")\n\n        backups = os.listdir(self.config.backup_dir)\n        backups.sort()\n\n        if not backups:\n            logger.warning(\n                \"Certbot hasn't modified your configuration, so rollback \"\n                \"isn't available.\")\n        elif len(backups) < rollback:\n            logger.warning(\"Unable to rollback %d checkpoints, only %d exist\",\n                           rollback, len(backups))\n\n        while rollback > 0 and backups:\n            cp_dir = os.path.join(self.config.backup_dir, backups.pop())\n            try:\n                self._recover_checkpoint(cp_dir)\n            except errors.ReverterError:\n                logger.critical(\"Failed to load checkpoint during rollback\")\n                raise errors.ReverterError(\n                    \"Unable to load checkpoint during rollback\")\n            rollback -= 1\n\n    def add_to_temp_checkpoint(self, save_files, save_notes):\n        \"\"\"Add files to temporary checkpoint.\n\n        :param set save_files: set of filepaths to save\n        :param str save_notes: notes about changes during the save\n\n        \"\"\"\n        self._add_to_checkpoint_dir(\n            self.config.temp_checkpoint_dir, save_files, save_notes)\n\n    def add_to_checkpoint(self, save_files, save_notes):\n        \"\"\"Add files to a permanent checkpoint.\n\n        :param set save_files: set of filepaths to save\n        :param str save_notes: notes about changes during the save\n\n        \"\"\"\n        # Check to make sure we are not overwriting a temp file\n        self._check_tempfile_saves(save_files)\n        self._add_to_checkpoint_dir(\n            self.config.in_progress_dir, save_files, save_notes)\n\n    def _add_to_checkpoint_dir(self, cp_dir, save_files, save_notes):\n        \"\"\"Add save files to checkpoint directory.\n\n        :param str cp_dir: Checkpoint directory filepath\n        :param set save_files: set of files to save\n        :param str save_notes: notes about changes made during the save\n\n        :raises IOError: if unable to open cp_dir + FILEPATHS file\n        :raises .ReverterError: if unable to add checkpoint\n\n        \"\"\"\n        util.make_or_verify_dir(\n            cp_dir, constants.CONFIG_DIRS_MODE, self.config.strict_permissions)\n\n        op_fd, existing_filepaths = self._read_and_append(\n            os.path.join(cp_dir, \"FILEPATHS\"))\n\n        idx = len(existing_filepaths)\n\n        for filename in save_files:\n            # No need to copy/index already existing files\n            # The oldest copy already exists in the directory...\n            if filename not in existing_filepaths:\n                # Tag files with index so multiple files can\n                # have the same filename\n                logger.debug(\"Creating backup of %s\", filename)\n                try:\n                    shutil.copy2(filename, os.path.join(\n                        cp_dir, os.path.basename(filename) + \"_\" + str(idx)))\n                    op_fd.write('{0}\\n'.format(filename))\n                # http://stackoverflow.com/questions/4726260/effective-use-of-python-shutil-copy2\n                except IOError:\n                    op_fd.close()\n                    logger.error(\n                        \"Unable to add file %s to checkpoint %s\",\n                        filename, cp_dir)\n                    raise errors.ReverterError(\n                        \"Unable to add file {0} to checkpoint \"\n                        \"{1}\".format(filename, cp_dir))\n                idx += 1\n        op_fd.close()\n\n        with open(os.path.join(cp_dir, \"CHANGES_SINCE\"), \"a\") as notes_fd:\n            notes_fd.write(save_notes)\n\n    def _read_and_append(self, filepath):\n        \"\"\"Reads the file lines and returns a file obj.\n\n        Read the file returning the lines, and a pointer to the end of the file.\n\n        \"\"\"\n        # Open up filepath differently depending on if it already exists\n        if os.path.isfile(filepath):\n            op_fd = open(filepath, \"r+\")\n            lines = op_fd.read().splitlines()\n        else:\n            lines = []\n            op_fd = open(filepath, \"w\")\n\n        return op_fd, lines\n\n    def _recover_checkpoint(self, cp_dir):\n        \"\"\"Recover a specific checkpoint.\n\n        Recover a specific checkpoint provided by cp_dir\n        Note: this function does not reload augeas.\n\n        :param str cp_dir: checkpoint directory file path\n\n        :raises errors.ReverterError: If unable to recover checkpoint\n\n        \"\"\"\n        # Undo all commands\n        if os.path.isfile(os.path.join(cp_dir, \"COMMANDS\")):\n            self._run_undo_commands(os.path.join(cp_dir, \"COMMANDS\"))\n        # Revert all changed files\n        if os.path.isfile(os.path.join(cp_dir, \"FILEPATHS\")):\n            try:\n                with open(os.path.join(cp_dir, \"FILEPATHS\")) as paths_fd:\n                    filepaths = paths_fd.read().splitlines()\n                    for idx, path in enumerate(filepaths):\n                        shutil.copy2(os.path.join(\n                            cp_dir,\n                            os.path.basename(path) + \"_\" + str(idx)), path)\n            except (IOError, OSError):\n                # This file is required in all checkpoints.\n                logger.error(\"Unable to recover files from %s\", cp_dir)\n                raise errors.ReverterError(\n                    \"Unable to recover files from %s\" % cp_dir)\n\n        # Remove any newly added files if they exist\n        self._remove_contained_files(os.path.join(cp_dir, \"NEW_FILES\"))\n\n        try:\n            shutil.rmtree(cp_dir)\n        except OSError:\n            logger.error(\"Unable to remove directory: %s\", cp_dir)\n            raise errors.ReverterError(\n                \"Unable to remove directory: %s\" % cp_dir)\n\n    def _run_undo_commands(self, filepath):\n        \"\"\"Run all commands in a file.\"\"\"\n        # NOTE: csv module uses native strings. That is, bytes on Python 2 and\n        # unicode on Python 3\n        # It is strongly advised to set newline = '' on Python 3 with CSV,\n        # and it fixes problems on Windows.\n        kwargs = {'newline': ''} if sys.version_info[0] > 2 else {}\n        with open(filepath, 'r', **kwargs) as csvfile:  # type: ignore\n            csvreader = csv.reader(csvfile)\n            for command in reversed(list(csvreader)):\n                try:\n                    util.run_script(command)\n                except errors.SubprocessError:\n                    logger.error(\n                        \"Unable to run undo command: %s\", \" \".join(command))\n\n    def _check_tempfile_saves(self, save_files):\n        \"\"\"Verify save isn't overwriting any temporary files.\n\n        :param set save_files: Set of files about to be saved.\n\n        :raises certbot.errors.ReverterError:\n            when save is attempting to overwrite a temporary file.\n\n        \"\"\"\n        protected_files = []\n\n        # Get temp modified files\n        temp_path = os.path.join(self.config.temp_checkpoint_dir, \"FILEPATHS\")\n        if os.path.isfile(temp_path):\n            with open(temp_path, \"r\") as protected_fd:\n                protected_files.extend(protected_fd.read().splitlines())\n\n        # Get temp new files\n        new_path = os.path.join(self.config.temp_checkpoint_dir, \"NEW_FILES\")\n        if os.path.isfile(new_path):\n            with open(new_path, \"r\") as protected_fd:\n                protected_files.extend(protected_fd.read().splitlines())\n\n        # Verify no save_file is in protected_files\n        for filename in protected_files:\n            if filename in save_files:\n                raise errors.ReverterError(\n                    \"Attempting to overwrite challenge \"\n                    \"file - %s\" % filename)\n\n    def register_file_creation(self, temporary, *files):\n        r\"\"\"Register the creation of all files during certbot execution.\n\n        Call this method before writing to the file to make sure that the\n        file will be cleaned up if the program exits unexpectedly.\n        (Before a save occurs)\n\n        :param bool temporary: If the file creation registry is for\n            a temp or permanent save.\n        :param \\*files: file paths (str) to be registered\n\n        :raises certbot.errors.ReverterError: If\n            call does not contain necessary parameters or if the file creation\n            is unable to be registered.\n\n        \"\"\"\n        # Make sure some files are provided... as this is an error\n        # Made this mistake in my initial implementation of apache.dvsni.py\n        if not files:\n            raise errors.ReverterError(\"Forgot to provide files to registration call\")\n\n        cp_dir = self._get_cp_dir(temporary)\n\n        # Append all new files (that aren't already registered)\n        new_fd = None\n        try:\n            new_fd, ex_files = self._read_and_append(os.path.join(cp_dir, \"NEW_FILES\"))\n\n            for path in files:\n                if path not in ex_files:\n                    new_fd.write(\"{0}\\n\".format(path))\n        except (IOError, OSError):\n            logger.error(\"Unable to register file creation(s) - %s\", files)\n            raise errors.ReverterError(\n                \"Unable to register file creation(s) - {0}\".format(files))\n        finally:\n            if new_fd is not None:\n                new_fd.close()\n\n    def register_undo_command(self, temporary, command):\n        \"\"\"Register a command to be run to undo actions taken.\n\n        .. warning:: This function does not enforce order of operations in terms\n            of file modification vs. command registration.  All undo commands\n            are run first before all normal files are reverted to their previous\n            state.  If you need to maintain strict order, you may create\n            checkpoints before and after the the command registration. This\n            function may be improved in the future based on demand.\n\n        :param bool temporary: Whether the command should be saved in the\n            IN_PROGRESS or TEMPORARY checkpoints.\n        :param command: Command to be run.\n        :type command: list of str\n\n        \"\"\"\n        commands_fp = os.path.join(self._get_cp_dir(temporary), \"COMMANDS\")\n        command_file = None\n        # It is strongly advised to set newline = '' on Python 3 with CSV,\n        # and it fixes problems on Windows.\n        kwargs = {'newline': ''} if sys.version_info[0] > 2 else {}\n        try:\n            if os.path.isfile(commands_fp):\n                command_file = open(commands_fp, \"a\", **kwargs)  # type: ignore\n            else:\n                command_file = open(commands_fp, \"w\", **kwargs)  # type: ignore\n\n            csvwriter = csv.writer(command_file)\n            csvwriter.writerow(command)\n\n        except (IOError, OSError):\n            logger.error(\"Unable to register undo command\")\n            raise errors.ReverterError(\n                \"Unable to register undo command.\")\n        finally:\n            if command_file is not None:\n                command_file.close()\n\n    def _get_cp_dir(self, temporary):\n        \"\"\"Return the proper reverter directory.\"\"\"\n        if temporary:\n            cp_dir = self.config.temp_checkpoint_dir\n        else:\n            cp_dir = self.config.in_progress_dir\n\n        util.make_or_verify_dir(\n            cp_dir, constants.CONFIG_DIRS_MODE, self.config.strict_permissions)\n\n        return cp_dir\n\n    def recovery_routine(self):\n        \"\"\"Revert configuration to most recent finalized checkpoint.\n\n        Remove all changes (temporary and permanent) that have not been\n        finalized. This is useful to protect against crashes and other\n        execution interruptions.\n\n        :raises .errors.ReverterError: If unable to recover the configuration\n\n        \"\"\"\n        # First, any changes found in IConfig.temp_checkpoint_dir are removed,\n        # then IN_PROGRESS changes are removed The order is important.\n        # IN_PROGRESS is unable to add files that are already added by a TEMP\n        # change.  Thus TEMP must be rolled back first because that will be the\n        # 'latest' occurrence of the file.\n        self.revert_temporary_config()\n        if os.path.isdir(self.config.in_progress_dir):\n            try:\n                self._recover_checkpoint(self.config.in_progress_dir)\n            except errors.ReverterError:\n                # We have a partial or incomplete recovery\n                logger.critical(\"Incomplete or failed recovery for IN_PROGRESS \"\n                             \"checkpoint - %s\",\n                             self.config.in_progress_dir)\n                raise errors.ReverterError(\n                    \"Incomplete or failed recovery for IN_PROGRESS checkpoint \"\n                    \"- %s\" % self.config.in_progress_dir)\n\n    def _remove_contained_files(self, file_list):\n        \"\"\"Erase all files contained within file_list.\n\n        :param str file_list: file containing list of file paths to be deleted\n\n        :returns: Success\n        :rtype: bool\n\n        :raises certbot.errors.ReverterError: If\n            all files within file_list cannot be removed\n\n        \"\"\"\n        # Check to see that file exists to differentiate can't find file_list\n        # and can't remove filepaths within file_list errors.\n        if not os.path.isfile(file_list):\n            return False\n        try:\n            with open(file_list, \"r\") as list_fd:\n                filepaths = list_fd.read().splitlines()\n                for path in filepaths:\n                    # Files are registered before they are added... so\n                    # check to see if file exists first\n                    if os.path.lexists(path):\n                        os.remove(path)\n                    else:\n                        logger.warning(\n                            \"File: %s - Could not be found to be deleted\\n\"\n                            \" - Certbot probably shut down unexpectedly\",\n                            path)\n        except (IOError, OSError):\n            logger.critical(\n                \"Unable to remove filepaths contained within %s\", file_list)\n            raise errors.ReverterError(\n                \"Unable to remove filepaths contained within \"\n                \"{0}\".format(file_list))\n\n        return True\n\n    def finalize_checkpoint(self, title):\n        \"\"\"Finalize the checkpoint.\n\n        Timestamps and permanently saves all changes made through the use\n        of :func:`~add_to_checkpoint` and :func:`~register_file_creation`\n\n        :param str title: Title describing checkpoint\n\n        :raises certbot.errors.ReverterError: when the\n            checkpoint is not able to be finalized.\n\n        \"\"\"\n        # Check to make sure an \"in progress\" directory exists\n        if not os.path.isdir(self.config.in_progress_dir):\n            return\n\n        changes_since_path = os.path.join(self.config.in_progress_dir, \"CHANGES_SINCE\")\n        changes_since_tmp_path = os.path.join(self.config.in_progress_dir, \"CHANGES_SINCE.tmp\")\n\n        if not os.path.exists(changes_since_path):\n            logger.info(\"Rollback checkpoint is empty (no changes made?)\")\n            with open(changes_since_path, 'w') as f:\n                f.write(\"No changes\\n\")\n\n        # Add title to self.config.in_progress_dir CHANGES_SINCE\n        try:\n            with open(changes_since_tmp_path, \"w\") as changes_tmp:\n                changes_tmp.write(\"-- %s --\\n\" % title)\n                with open(changes_since_path, \"r\") as changes_orig:\n                    changes_tmp.write(changes_orig.read())\n\n        # Move self.config.in_progress_dir to Backups directory\n            shutil.move(changes_since_tmp_path, changes_since_path)\n        except (IOError, OSError):\n            logger.error(\"Unable to finalize checkpoint - adding title\")\n            logger.debug(\"Exception was:\\n%s\", traceback.format_exc())\n            raise errors.ReverterError(\"Unable to add title\")\n\n        # rename the directory as a timestamp\n        self._timestamp_progress_dir()\n\n    def _checkpoint_timestamp(self):\n        \"Determine the timestamp of the checkpoint, enforcing monotonicity.\"\n        timestamp = str(time.time())\n        others = glob.glob(os.path.join(self.config.backup_dir, \"[0-9]*\"))\n        others = [os.path.basename(d) for d in others]\n        others.append(timestamp)\n        others.sort()\n        if others[-1] != timestamp:\n            timetravel = str(float(others[-1]) + 1)\n            logger.warning(\"Current timestamp %s does not correspond to newest reverter \"\n                \"checkpoint; your clock probably jumped. Time travelling to %s\",\n                timestamp, timetravel)\n            timestamp = timetravel\n        elif len(others) > 1 and others[-2] == timestamp:\n            # It is possible if the checkpoints are made extremely quickly\n            # that will result in a name collision.\n            logger.debug(\"Race condition with timestamp %s, incrementing by 0.01\", timestamp)\n            timetravel = str(float(others[-1]) + 0.01)\n            timestamp = timetravel\n        return timestamp\n\n    def _timestamp_progress_dir(self):\n        \"\"\"Timestamp the checkpoint.\"\"\"\n        # It is possible save checkpoints faster than 1 per second resulting in\n        # collisions in the naming convention.\n\n        for _ in six.moves.range(2):\n            timestamp = self._checkpoint_timestamp()\n            final_dir = os.path.join(self.config.backup_dir, timestamp)\n            try:\n                filesystem.replace(self.config.in_progress_dir, final_dir)\n                return\n            except OSError:\n                logger.warning(\"Extreme, unexpected race condition, retrying (%s)\", timestamp)\n\n        # After 10 attempts... something is probably wrong here...\n        logger.error(\n            \"Unable to finalize checkpoint, %s -> %s\",\n            self.config.in_progress_dir, final_dir)\n        raise errors.ReverterError(\n            \"Unable to finalize checkpoint renaming\")\n"
  },
  {
    "path": "certbot/ssl-dhparams.pem",
    "content": "-----BEGIN DH PARAMETERS-----\nMIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz\n+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a\n87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7\nYdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi\n7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD\nssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==\n-----END DH PARAMETERS-----\n"
  },
  {
    "path": "certbot/tests/__init__.py",
    "content": "\"\"\"Utilities for running Certbot tests\"\"\"\n"
  },
  {
    "path": "certbot/tests/acme_util.py",
    "content": "\"\"\"ACME utilities for testing.\"\"\"\nimport datetime\n\nimport josepy as jose\nimport six\n\nfrom acme import challenges\nfrom acme import messages\nfrom certbot._internal import auth_handler\nfrom certbot.tests import util\n\nJWK = jose.JWK.load(util.load_vector('rsa512_key.pem'))\nKEY = util.load_rsa_private_key('rsa512_key.pem')\n\n# Challenges\nHTTP01 = challenges.HTTP01(\n    token=b\"evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA\")\nDNS01 = challenges.DNS01(token=b\"17817c66b60ce2e4012dfad92657527a\")\nDNS01_2 = challenges.DNS01(token=b\"cafecafecafecafecafecafe0feedbac\")\n\nCHALLENGES = [HTTP01, DNS01]\n\n\ndef gen_combos(challbs):\n    \"\"\"Generate natural combinations for challbs.\"\"\"\n    # completing a single DV challenge satisfies the CA\n    return tuple((i,) for i, _ in enumerate(challbs))\n\n\ndef chall_to_challb(chall, status):\n    \"\"\"Return ChallengeBody from Challenge.\"\"\"\n    kwargs = {\n        \"chall\": chall,\n        \"uri\": chall.typ + \"_uri\",\n        \"status\": status,\n    }\n\n    if status == messages.STATUS_VALID:\n        kwargs.update({\"validated\": datetime.datetime.now()})\n\n    return messages.ChallengeBody(**kwargs)\n\n\n# Pending ChallengeBody objects\nHTTP01_P = chall_to_challb(HTTP01, messages.STATUS_PENDING)\nDNS01_P = chall_to_challb(DNS01, messages.STATUS_PENDING)\nDNS01_P_2 = chall_to_challb(DNS01_2, messages.STATUS_PENDING)\n\nCHALLENGES_P = [HTTP01_P, DNS01_P]\n\n\n# AnnotatedChallenge objects\nHTTP01_A = auth_handler.challb_to_achall(HTTP01_P, JWK, \"example.com\")\nDNS01_A = auth_handler.challb_to_achall(DNS01_P, JWK, \"example.org\")\nDNS01_A_2 = auth_handler.challb_to_achall(DNS01_P_2, JWK, \"esimerkki.example.org\")\n\nACHALLENGES = [HTTP01_A, DNS01_A]\n\n\ndef gen_authzr(authz_status, domain, challs, statuses, combos=True):\n    \"\"\"Generate an authorization resource.\n\n    :param authz_status: Status object\n    :type authz_status: :class:`acme.messages.Status`\n    :param list challs: Challenge objects\n    :param list statuses: status of each challenge object\n    :param bool combos: Whether or not to add combinations\n\n    \"\"\"\n    challbs = tuple(\n        chall_to_challb(chall, status)\n        for chall, status in six.moves.zip(challs, statuses)\n    )\n    authz_kwargs = {\n        \"identifier\": messages.Identifier(\n            typ=messages.IDENTIFIER_FQDN, value=domain),\n        \"challenges\": challbs,\n    }\n    if combos:\n        authz_kwargs.update({\"combinations\": gen_combos(challbs)})\n    if authz_status == messages.STATUS_VALID:\n        authz_kwargs.update({\n            \"status\": authz_status,\n            \"expires\": datetime.datetime.now() + datetime.timedelta(days=31),\n        })\n    else:\n        authz_kwargs.update({\n            \"status\": authz_status,\n        })\n\n    return messages.AuthorizationResource(\n        uri=\"https://trusted.ca/new-authz-resource\",\n        body=messages.Authorization(**authz_kwargs)\n    )\n"
  },
  {
    "path": "certbot/tests/testdata/README",
    "content": "The following command has been used to generate test keys:\n\n\tfor x in 256 512 2048; do openssl genrsa -out rsa${k}_key.pem $k; done\n\nand for the CSR PEM (Certificate Signing Request):\n\n\t\topenssl req -new -out csr-Xsans_X.pem -key rsa512_key.pem [-config csr-Xsans_X.conf | -subj '/CN=example.com'] [-outform DER > csr_X.der]\n\nand for the certificate:\n\n  openssl req -new -out cert_X.pem -key rsaX_key.pem -subj '/CN=example.com' -x509 [-outform DER > cert_X.der]"
  },
  {
    "path": "certbot/tests/testdata/cert-5sans_512.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIICkTCCAjugAwIBAgIJAJNbfABWQ8bbMA0GCSqGSIb3DQEBCwUAMHkxCzAJBgNV\nBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNp\nc2NvMScwJQYDVQQKDB5FbGVjdHJvbmljIEZyb250aWVyIEZvdW5kYXRpb24xFDAS\nBgNVBAMMC2V4YW1wbGUuY29tMB4XDTE2MDYwOTIzMDEzNloXDTE2MDcwOTIzMDEz\nNloweTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM\nDVNhbiBGcmFuY2lzY28xJzAlBgNVBAoMHkVsZWN0cm9uaWMgRnJvbnRpZXIgRm91\nbmRhdGlvbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0BAQEFAANL\nADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+6QWE\n30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABo4GlMIGiMB0GA1UdDgQWBBQmz8jt\nS9eUsuQlA1gkjwTAdNWXijAfBgNVHSMEGDAWgBQmz8jtS9eUsuQlA1gkjwTAdNWX\nijAMBgNVHRMEBTADAQH/MFIGA1UdEQRLMEmCDWEuZXhhbXBsZS5jb22CDWIuZXhh\nbXBsZS5jb22CDWMuZXhhbXBsZS5jb22CDWQuZXhhbXBsZS5jb22CC2V4YW1wbGUu\nY29tMA0GCSqGSIb3DQEBCwUAA0EAVXmZxB+IJdgFvY2InOYeytTD1QmouDZRtj/T\nH/HIpSdsfO7qr4d/ZprI2IhLRxp2S4BiU5Qc5HUkeADcpNd06A==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "certbot/tests/testdata/cert-nosans_nistp256.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIBoDCCAUYCCQDCnzfUZ7TQdDAKBggqhkjOPQQDAjBYMQswCQYDVQQGEwJVUzER\nMA8GA1UECAwITWljaGlnYW4xEjAQBgNVBAcMCUFubiBBcmJvcjEMMAoGA1UECgwD\nRUZGMRQwEgYDVQQDDAtleGFtcGxlLmNvbTAeFw0xODA1MTUxNzIyMzlaFw0xODA2\nMTQxNzIyMzlaMFgxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhNaWNoaWdhbjESMBAG\nA1UEBwwJQW5uIEFyYm9yMQwwCgYDVQQKDANFRkYxFDASBgNVBAMMC2V4YW1wbGUu\nY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPPl0JauSZukvAUWv4l5VNLAY\nQXhuPXYQBf4dVET3s0E5q9ZCbSe+pNUbko9F+TFkuc7XVjQPsfkDbh0I9nD0tzAK\nBggqhkjOPQQDAgNIADBFAiEAv8S2GXmWJqZ+j3DBfm72E1YK+HkOf+TOUHsbVR+O\nZ1oCIFWNt1SPdIgRp4QAyzVk2pcTF8jDNajEMLWETDtxgRvM\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "certbot/tests/testdata/cert-san_512.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIICFjCCAcCgAwIBAgICBTkwDQYJKoZIhvcNAQELBQAwdzELMAkGA1UEBhMCVVMx\nETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3IxKzApBgNVBAoM\nIlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDASBgNVBAMMC2V4\nYW1wbGUuY29tMB4XDTE0MTIxMTIyMzQ0NVoXDTE0MTIxODIyMzQ0NVowdzELMAkG\nA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3Ix\nKzApBgNVBAoMIlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDAS\nBgNVBAMMC2V4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKx1c7RR\n7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79vukFhN9HFoHZiUvOjm0c\n+pVE6K+EdE/twuUCAwEAAaM2MDQwCQYDVR0TBAIwADAnBgNVHREEIDAeggtleGFt\ncGxlLmNvbYIPd3d3LmV4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA0EASuvNKFTF\nnTJsvnSXn52f4BMZJJ2id/kW7+r+FJRm+L20gKQ1aqq8d3e/lzRUrv5SMf1TAOe7\nRDjyGMKy5ZgM2w==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "certbot/tests/testdata/cert_2048.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDSjCCAjKgAwIBAgIJAIYLtIQHBBG0MA0GCSqGSIb3DQEBCwUAMDoxCzAJBgNV\nBAYTAkNBMQswCQYDVQQIDAJPTjEQMA4GA1UEBwwHVG9yb250bzEMMAoGA1UECgwD\nRUZGMB4XDTE3MDUyOTA3NDIwMVoXDTQ4MDMzMDA3NDIwMVowOjELMAkGA1UEBhMC\nQ0ExCzAJBgNVBAgMAk9OMRAwDgYDVQQHDAdUb3JvbnRvMQwwCgYDVQQKDANFRkYw\nggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDm1WIecnHjL4FsJvxDP27G\nyeqnXKc41HsRP9cv4z+NDjE94mDgva5ndieiA9xZ0Sh7LXtZcGDcpGop+D7s+oh0\napV6idIJ9eEPUegYlGxOFJQnZ8re6hD7MaAlNZEVhZrwJvrGy6rTFpi3DaNokGn7\nr3s2nrQ9aziljkWRp1PnTBnRNgOdi3c1IB2f4+2PdykjihxlnYUuI4Wf5QU5pFx6\n0a2mdTVDC+bKAP22IvuQnnkHgJYYS/oMxFCT9QR4xQRPOx7U2RWVrFDVMJ3mIB8F\nOW6JXfQSmaZZr46xclbEIr4QQ6RcPWvcJ1cCV1idFjEmufi52sV7r1Bf3nCJFk1f\nAgMBAAGjUzBRMB0GA1UdDgQWBBSdJ++M23AW3LkFD7LKhsH7gL6/2jAfBgNVHSME\nGDAWgBSdJ++M23AW3LkFD7LKhsH7gL6/2jAPBgNVHRMBAf8EBTADAQH/MA0GCSqG\nSIb3DQEBCwUAA4IBAQCV5kSt1HTFzUPdBvxT455YrLd3jIsRt1pRNuGjVaUYIRxh\nvds8NN1Z8h/8Cdzz8NVkIdCuYb2lFaDjs3zNVUQxCyVcH7xVyPwFI85NR27+HPRv\nxzz2rwzST+NKYst6ZBg086BKjqFtxs16lpU/TD6tOJqg86TBbfP6gib/ocGeER2D\nHEEik69FjmUCziT6uXyYW5y1PxD15UWO3RWoTpao0vGtTPceTeeuO05PVeCUlx8X\nYXg9zoVWBba0GF+qQJ67zT5nvfc2KJcgnWRIRr/90YXzBf+FdFVuC4xFHINBI1OJ\n5XBLJOv61Zu+Du/nmlBVcb8KL/Vd2oZyfoH+0oCN\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "certbot/tests/testdata/cert_512.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIB3jCCAYigAwIBAgICBTkwDQYJKoZIhvcNAQELBQAwdzELMAkGA1UEBhMCVVMx\nETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3IxKzApBgNVBAoM\nIlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDASBgNVBAMMC2V4\nYW1wbGUuY29tMB4XDTE0MTIxMTIyMzQ0NVoXDTE0MTIxODIyMzQ0NVowdzELMAkG\nA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3Ix\nKzApBgNVBAoMIlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDAS\nBgNVBAMMC2V4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKx1c7RR\n7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79vukFhN9HFoHZiUvOjm0c\n+pVE6K+EdE/twuUCAwEAATANBgkqhkiG9w0BAQsFAANBAC24z0IdwIVKSlntksll\nvr6zJepBH5fMndfk3XJp10jT6VE+14KNtjh02a56GoraAvJAT5/H67E8GvJ/ocNn\nB/o=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "certbot/tests/testdata/cert_512_bad.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIICYzCCAg2gAwIBAgIJAPvqv4TcAtuFMA0GCSqGSIb3DQEBCwUAMIGMMQswCQYD\nVQQGEwJDQTEQMA4GA1UECAwHT250YXJpbzEQMA4GA1UEBwwHVG9yb250bzEMMAoG\nA1UECgwDRUZGMRYwFAYDVQQLDA1UZWNoIFByb2plY3RzMQ4wDAYDVQQDDAVZb21u\nYTEjMCEGCSqGSIb3DQEJARYUeW9tbmEubmFzc2VyQGVmZi5vcmcwHhcNMTcwMzI0\nMjIzMjUxWhcNNDgwMTI0MjIzMjUxWjCBjDELMAkGA1UEBhMCQ0ExEDAOBgNVBAgM\nB09udGFyaW8xEDAOBgNVBAcMB1Rvcm9udG8xDDAKBgNVBAoMA0VGRjEWMBQGA1UE\nCwwNVGVjaCBQcm9qZWN0czEOMAwGA1UEAwwFWW9tbmExIzAhBgkqhkiG9w0BCQEW\nFHlvbW5hLm5hc3NlckBlZmYub3JnMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKx1\nc7RR7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79vukFhN9HFoHZiUvO\njm0c+pVE6K+EdE/twuUCAwEAAaNQME4wHQYDVR0OBBYEFCbPyO1L15Sy5CUDWCSP\nBMB01ZeKMB8GA1UdIwQYMBaAFCbPyO1L15Sy5CUDWCSPBMB01ZeKMAwGA1UdEwQF\nMAMBAf8wDQYJKoZIhvcNAQELBQADQQAeWDdcrJOolFHr3m8TrlDJ/Ca4SfJya2jb\nK1wahbX83sC42834HbDOQASGBhoLYDhC1cMPbKDDjMbR9rjYuf7T\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "certbot/tests/testdata/cert_fullchain_2048.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDSjCCAjKgAwIBAgIJAIYLtIQHBBG0MA0GCSqGSIb3DQEBCwUAMDoxCzAJBgNV\nBAYTAkNBMQswCQYDVQQIDAJPTjEQMA4GA1UEBwwHVG9yb250bzEMMAoGA1UECgwD\nRUZGMB4XDTE3MDUyOTA3NDIwMVoXDTQ4MDMzMDA3NDIwMVowOjELMAkGA1UEBhMC\nQ0ExCzAJBgNVBAgMAk9OMRAwDgYDVQQHDAdUb3JvbnRvMQwwCgYDVQQKDANFRkYw\nggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDm1WIecnHjL4FsJvxDP27G\nyeqnXKc41HsRP9cv4z+NDjE94mDgva5ndieiA9xZ0Sh7LXtZcGDcpGop+D7s+oh0\napV6idIJ9eEPUegYlGxOFJQnZ8re6hD7MaAlNZEVhZrwJvrGy6rTFpi3DaNokGn7\nr3s2nrQ9aziljkWRp1PnTBnRNgOdi3c1IB2f4+2PdykjihxlnYUuI4Wf5QU5pFx6\n0a2mdTVDC+bKAP22IvuQnnkHgJYYS/oMxFCT9QR4xQRPOx7U2RWVrFDVMJ3mIB8F\nOW6JXfQSmaZZr46xclbEIr4QQ6RcPWvcJ1cCV1idFjEmufi52sV7r1Bf3nCJFk1f\nAgMBAAGjUzBRMB0GA1UdDgQWBBSdJ++M23AW3LkFD7LKhsH7gL6/2jAfBgNVHSME\nGDAWgBSdJ++M23AW3LkFD7LKhsH7gL6/2jAPBgNVHRMBAf8EBTADAQH/MA0GCSqG\nSIb3DQEBCwUAA4IBAQCV5kSt1HTFzUPdBvxT455YrLd3jIsRt1pRNuGjVaUYIRxh\nvds8NN1Z8h/8Cdzz8NVkIdCuYb2lFaDjs3zNVUQxCyVcH7xVyPwFI85NR27+HPRv\nxzz2rwzST+NKYst6ZBg086BKjqFtxs16lpU/TD6tOJqg86TBbfP6gib/ocGeER2D\nHEEik69FjmUCziT6uXyYW5y1PxD15UWO3RWoTpao0vGtTPceTeeuO05PVeCUlx8X\nYXg9zoVWBba0GF+qQJ67zT5nvfc2KJcgnWRIRr/90YXzBf+FdFVuC4xFHINBI1OJ\n5XBLJOv61Zu+Du/nmlBVcb8KL/Vd2oZyfoH+0oCN\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIDSjCCAjKgAwIBAgIJAIYLtIQHBBG0MA0GCSqGSIb3DQEBCwUAMDoxCzAJBgNV\nBAYTAkNBMQswCQYDVQQIDAJPTjEQMA4GA1UEBwwHVG9yb250bzEMMAoGA1UECgwD\nRUZGMB4XDTE3MDUyOTA3NDIwMVoXDTQ4MDMzMDA3NDIwMVowOjELMAkGA1UEBhMC\nQ0ExCzAJBgNVBAgMAk9OMRAwDgYDVQQHDAdUb3JvbnRvMQwwCgYDVQQKDANFRkYw\nggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDm1WIecnHjL4FsJvxDP27G\nyeqnXKc41HsRP9cv4z+NDjE94mDgva5ndieiA9xZ0Sh7LXtZcGDcpGop+D7s+oh0\napV6idIJ9eEPUegYlGxOFJQnZ8re6hD7MaAlNZEVhZrwJvrGy6rTFpi3DaNokGn7\nr3s2nrQ9aziljkWRp1PnTBnRNgOdi3c1IB2f4+2PdykjihxlnYUuI4Wf5QU5pFx6\n0a2mdTVDC+bKAP22IvuQnnkHgJYYS/oMxFCT9QR4xQRPOx7U2RWVrFDVMJ3mIB8F\nOW6JXfQSmaZZr46xclbEIr4QQ6RcPWvcJ1cCV1idFjEmufi52sV7r1Bf3nCJFk1f\nAgMBAAGjUzBRMB0GA1UdDgQWBBSdJ++M23AW3LkFD7LKhsH7gL6/2jAfBgNVHSME\nGDAWgBSdJ++M23AW3LkFD7LKhsH7gL6/2jAPBgNVHRMBAf8EBTADAQH/MA0GCSqG\nSIb3DQEBCwUAA4IBAQCV5kSt1HTFzUPdBvxT455YrLd3jIsRt1pRNuGjVaUYIRxh\nvds8NN1Z8h/8Cdzz8NVkIdCuYb2lFaDjs3zNVUQxCyVcH7xVyPwFI85NR27+HPRv\nxzz2rwzST+NKYst6ZBg086BKjqFtxs16lpU/TD6tOJqg86TBbfP6gib/ocGeER2D\nHEEik69FjmUCziT6uXyYW5y1PxD15UWO3RWoTpao0vGtTPceTeeuO05PVeCUlx8X\nYXg9zoVWBba0GF+qQJ67zT5nvfc2KJcgnWRIRr/90YXzBf+FdFVuC4xFHINBI1OJ\n5XBLJOv61Zu+Du/nmlBVcb8KL/Vd2oZyfoH+0oCN\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "certbot/tests/testdata/cli.ini",
    "content": "agree-dev-preview = True\n"
  },
  {
    "path": "certbot/tests/testdata/csr-6sans_512.conf",
    "content": "[req]\ndistinguished_name = req_distinguished_name\nreq_extensions = v3_req\n\n[req_distinguished_name]\nC=US\nC_default = US\nST=Michigan\nST_default=Michigan\nL=Ann Arbor\nL_default=Ann Arbor\nO=EFF\nO_default=EFF\nOU=University of Michigan\nOU_default=University of Michigan\nCN=example.com\nCN_default=example.com\n\n\n[ v3_req ]\nsubjectAltName = @alt_names\n\n[alt_names]\nDNS.1 = example.com\nDNS.2 = example.org\nDNS.3 = example.net\nDNS.4 = example.info\nDNS.5 = subdomain.example.com\nDNS.6 = other.subdomain.example.com"
  },
  {
    "path": "certbot/tests/testdata/csr-6sans_512.pem",
    "content": "-----BEGIN CERTIFICATE REQUEST-----\nMIIBuzCCAWUCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIw\nEAYDVQQHDAlBbm4gQXJib3IxDDAKBgNVBAoMA0VGRjEfMB0GA1UECwwWVW5pdmVy\nc2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG\n9w0BAQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3f\np580rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABoIGGMIGDBgkqhkiG\n9w0BCQ4xdjB0MHIGA1UdEQRrMGmCC2V4YW1wbGUuY29tggtleGFtcGxlLm9yZ4IL\nZXhhbXBsZS5uZXSCDGV4YW1wbGUuaW5mb4IVc3ViZG9tYWluLmV4YW1wbGUuY29t\nghtvdGhlci5zdWJkb21haW4uZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADQQA+\nsU6T30n3SsdnHlj0Va8eECOWK7Lf8nUfxxgjPMQ7BoU8gbAnGfDmOlwDronTRqf1\nMe+nlYJU4TX1OiX10DYu\n-----END CERTIFICATE REQUEST-----\n"
  },
  {
    "path": "certbot/tests/testdata/csr-nonames_512.pem",
    "content": "-----BEGIN CERTIFICATE REQUEST-----\nMIH/MIGqAgEAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwXDANBgkqhkiG9w0BAQEF\nAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+\n6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABoAAwDQYJKoZIhvcNAQELBQAD\nQQBt9XLSZ9DGfWcGGaBUTCiSY7lWBegpNlCeo8pK3ydWmKpjcza+j7lF5paph2LH\nlKWVQ8+xwYMscGWK0NApHGco\n-----END CERTIFICATE REQUEST-----\n"
  },
  {
    "path": "certbot/tests/testdata/csr-nosans_512.conf",
    "content": "[req]\ndistinguished_name = req_distinguished_name\n\n[req_distinguished_name]\nC=US\nC_default = US\nST=Michigan\nST_default=Michigan\nL=Ann Arbor\nL_default=Ann Arbor\nO=EFF\nO_default=EFF\nOU=University of Michigan\nOU_default=University of Michigan\nCN=example.com\nCN_default=example.com"
  },
  {
    "path": "certbot/tests/testdata/csr-nosans_512.pem",
    "content": "-----BEGIN CERTIFICATE REQUEST-----\nMIIBMzCB3gIBADB5MQswCQYDVQQGEwJVUzERMA8GA1UECAwITWljaGlnYW4xEjAQ\nBgNVBAcMCUFubiBBcmJvcjEMMAoGA1UECgwDRUZGMR8wHQYDVQQLDBZVbml2ZXJz\naXR5IHBmIE1pY2hpZ2FuMRQwEgYDVQQDDAtleGFtcGxlLmNvbTBcMA0GCSqGSIb3\nDQEBAQUAA0sAMEgCQQCsdXO0Ue0f3a5wUkP838db0Cx1GxS4dQEEEOUfA2VF3d+n\nnzSu/b7pBYTfRxaB2YlLzo5tHPqVROivhHRP7cLlAgMBAAGgADANBgkqhkiG9w0B\nAQsFAANBAG06jIPvSC6wiGLy7sUTaEX4UCE6Cztp3vh/uXN7Q++CGn6KiXNs/BRW\neFlcFPbvxbVG/ZZFR5aPs+Oy6RhqOjg=\n-----END CERTIFICATE REQUEST-----\n"
  },
  {
    "path": "certbot/tests/testdata/csr-nosans_nistp256.pem",
    "content": "-----BEGIN CERTIFICATE REQUEST-----\nMIIBFDCBugIBADBYMQswCQYDVQQGEwJVUzERMA8GA1UECAwITWljaGlnYW4xEjAQ\nBgNVBAcMCUFubiBBcmJvcjEMMAoGA1UECgwDRUZGMRQwEgYDVQQDDAtleGFtcGxl\nLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDz5dCWrkmbpLwFFr+JeVTSw\nGEF4bj12EAX+HVRE97NBOavWQm0nvqTVG5KPRfkxZLnO11Y0D7H5A24dCPZw9Leg\nADAKBggqhkjOPQQDAgNJADBGAiEAuoZHrYA5sy2DRTdLAxJTBNHKFFKbtaGt+QaJ\nA62qa8sCIQCUkSgSAiNaEnJ7r5fKphdjeORHqhpl6flYkLE3lGmGdg==\n-----END CERTIFICATE REQUEST-----\n"
  },
  {
    "path": "certbot/tests/testdata/csr-san_512.pem",
    "content": "-----BEGIN CERTIFICATE REQUEST-----\nMIIBbjCCARgCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIw\nEAYDVQQHDAlBbm4gQXJib3IxDDAKBgNVBAoMA0VGRjEfMB0GA1UECwwWVW5pdmVy\nc2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG\n9w0BAQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3f\np580rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABoDowOAYJKoZIhvcN\nAQkOMSswKTAnBgNVHREEIDAeggtleGFtcGxlLmNvbYIPd3d3LmV4YW1wbGUuY29t\nMA0GCSqGSIb3DQEBCwUAA0EAZGBM8J1rRs7onFgtc76mOeoT1c3v0ZsEmxQfb2Wy\ntmReY6X1N4cs38D9VSow+VMRu2LWkKvzS7RUFSaTaeQz1A==\n-----END CERTIFICATE REQUEST-----\n"
  },
  {
    "path": "certbot/tests/testdata/csr_512.pem",
    "content": "-----BEGIN CERTIFICATE REQUEST-----\nMIIBFTCBwAIBADBbMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh\nMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRQwEgYDVQQDDAtFeGFt\ncGxlLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQCsdXO0Ue0f3a5wUkP838db\n0Cx1GxS4dQEEEOUfA2VF3d+nnzSu/b7pBYTfRxaB2YlLzo5tHPqVROivhHRP7cLl\nAgMBAAGgADANBgkqhkiG9w0BAQsFAANBAAceUlq4La8qaiK0DeDP3M19BIVzMmz2\noemG2fOvPiwNCB90ctSWQ6bMpUMV85ShcFi31C5vlntPfztehhq6YuE=\n-----END CERTIFICATE REQUEST-----\n"
  },
  {
    "path": "certbot/tests/testdata/nistp256_key.pem",
    "content": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIOvXH384CyNNv2lfxvjc7hg2f7ScYoLvlk/VpINLJlGBoAoGCCqGSM49\nAwEHoUQDQgAEPPl0JauSZukvAUWv4l5VNLAYQXhuPXYQBf4dVET3s0E5q9ZCbSe+\npNUbko9F+TFkuc7XVjQPsfkDbh0I9nD0tw==\n-----END EC PRIVATE KEY-----\n"
  },
  {
    "path": "certbot/tests/testdata/ocsp_certificate.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIGYDCCBEigAwIBAgIKcjrC4hZcebbtODANBgkqhkiG9w0BAQsFADBRMQswCQYD\nVQQGEwJOTzEdMBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIzAhBgNVBAMM\nGkJ1eXBhc3MgQ2xhc3MgMiBUZXN0NCBDQSA1MB4XDTE5MDUxMjE1NTgyMVoXDTE5\nMTEwODIyNTkwMFowADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK9P\nb+YhJPypm4ui+AZUHPrJ6IsB9R/6Wvgec2G/GuW/UNQFktIhU10HOHAbiJeYLqNZ\n1Cia8JD6NXXGbprOjIbZWvjulYTaLSlClcK0H7HZrcgrK60OeIGEtur27ga68RML\nhs1FG7TNyWVysifOtwW9Oo1mZQQtxViiE2Yb+Q4QqIxitnbrnFmKrVJSUHVXi8/I\nBK1yLrJiRBZMIw0wvAWcWEG2Gpp9PAbemlb11Zx8sm/RSGh7u60rmETbB2Pu941s\nXJCSQRtq5yKdtjIJTIgbe12SPkknqTqa3aUh7hgho0IymlDSeeocL60SUiUAsPEr\nQRWleodOR1ChXz5mFokCAwEAAaOCAokwggKFMAkGA1UdEwQCMAAwHwYDVR0jBBgw\nFoAUd9nQBpFm2N0ZJo1JrNowL2p7YrEwHQYDVR0OBBYEFExS23I6sLCeO6KIxzoc\ntr9s+HmiMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB\nBQUHAwIwIAYDVR0gBBkwFzALBglghEIBGgEAAgcwCAYGZ4EMAQIBMEIGA1UdHwQ7\nMDkwN6A1oDOGMWh0dHA6Ly9jcmwudGVzdDQuYnV5cGFzcy5uby9jcmwvQlBDbGFz\nczJUNENBNS5jcmwwIQYDVR0RAQH/BBcwFYITYnV5cGFzcy5wYWNhbGlzLm5ldDB4\nBggrBgEFBQcBAQRsMGowKQYIKwYBBQUHMAGGHWh0dHA6Ly9vY3NwLnRlc3Q0LmJ1\neXBhc3MuY29tMD0GCCsGAQUFBzAChjFodHRwOi8vY3J0LnRlc3Q0LmJ1eXBhc3Mu\nbm8vY3J0L0JQQ2xhc3MyVDRDQTUuY2VyMIIBBAYKKwYBBAHWeQIEAgSB9QSB8gDw\nAHYAsMyD5aX5fWuvfAnMKEkEhyrH6IsTLGNQt8b9JuFsbHcAAAFqrMQ/cQAABAMA\nRzBFAiEA1oWB4c6q7+tqGA4HhLNACOemr9c2aIUuWxeQE7/PlSYCIEolZ7pWVs1J\nVyQW/AqeuXGB7qScwUgLh9C1uOJoeRe6AHYAsMyD5aX5fWuvfAnMKEkEhyrH6IsT\nLGNQt8b9JuFsbHcAAAFqrMQ/cQAABAMARzBFAiAoLaNvIwMDifsDAXJBsAKHlYx7\nQPLXL8onYKm8f+Sf1wIhAMepo2GX84UR7WtooqzkBZLG+PaBy1zMuUAG6mwnroF9\nMA0GCSqGSIb3DQEBCwUAA4ICAQAPWLdjNS5lLL5SEtghYebtDmNj2968NYSDvb1L\n1/uFwg3LCVRR1Xb3z1Hc/sc1W0IFXU0zOqEQiuP8jkVP7UqkaWuK5Eu0eP0zPI83\nWBZM0+eBwxwzIMK/Q7fYKTu1+vg/FlH0WhtV43DQSik66366zvPi2Tfag9IPvRei\nDOjbSOBF0o4er2oCrtI0lK5YrHOdWtD7xwQIuA606P9ucuufMf+JcmduRJsVZ2Zu\n3K32SMDdAnyjvQWZNbt1ex3G8vuFQEi690UBhPcha/SO8QvLS89wcaLJnyMIWdv7\n54cbw+fa1nLKM7qph6Mk1yb0qpomPqLmKw4T6WX36c0vDlFSpexJLGgWDFqLUxPN\nqV7cJz4mi1qaYfdWXRrnyU4bl55pHTTgEzbohV7apsmytkCe1uFNrpcTh8jzAhGN\nPQqarX9UoESR56B/ufbBGlBWi0pkV49BFks6Ue0GVKo7djoxuV6+SsmYSE+6MNPv\nIUsm54TSnwxjA8WyG7pl14g1hkGFQ4NRYJMiVqK3DMABaPxVmT7NRxUQQiM0mmM7\nEKNzLBeWHJF5ecdDR1MiIF3ayn+RiZb0r8aSQBMLwN1YwUZw+hSYz1eCd7bHN1gC\n1ksxP61f8LBz0SwDoyOTr8wY++wqF26KfoYuKQ3LjLeHvuUtL3EMnAhiyuej8ZOZ\n22spng==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "certbot/tests/testdata/ocsp_issuer_certificate.pem",
    "content": "22spng==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIGMzCCBBugAwIBAgIJMvsa+ZFQCj8nMA0GCSqGSIb3DQEBCwUAMFQxCzAJBgNV\nBAYTAk5PMR0wGwYDVQQKDBRCdXlwYXNzIEFTLTk4MzE2MzMyNzEmMCQGA1UEAwwd\nQnV5cGFzcyBDbGFzcyAyIFRlc3Q0IFJvb3QgQ0EwHhcNMTcwMjEzMTY1MjQ2WhcN\nMjcwMjEzMTY1MjQ2WjBRMQswCQYDVQQGEwJOTzEdMBsGA1UECgwUQnV5cGFzcyBB\nUy05ODMxNjMzMjcxIzAhBgNVBAMMGkJ1eXBhc3MgQ2xhc3MgMiBUZXN0NCBDQSA1\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAi/vpgO2sbUQZsoxWd6us\nQvT/59kvw5ehoJABBXFs1J1AV1/K2hjhDXit/sNGKjzDvkfE9PJqXMnhKpPFkUzC\nz/NmDK++d6aRflnDvJrxlPVpp0QGbe3qOErByFjWiHoobuVItlpRO/BaBdlgGvmQ\nLeZFBXs/ZrLNFUKBcE+DZIyJH7vy2EB5dNNVn2mx0n+371InpKsYUaHNlxPpp+uj\nTOL+e4OjWTBwDaI7rVzpavozb8SPzFxjpxLLVH/j+8VPwoe3lmxr8ATyI178iRdA\nuxYfaKURSfu7PWjnDNTnq26E3pwW3E5zUbsADgUMh/PzoJAcszL1eHGUQaAGBP85\nPlLmHr+nsPMHXOUyl7Ts6KGkZlvjnVshKwUxYAqjAC7/BY0iI0xc406NK9heeVDk\nNiFA8/To6mQ09vO/TBxQtkfNk2yuxiixa101peSg4/+E4VhwYv6MJxS/oVqBd2d3\nwemYW/JUVeJg9wXGq1e/c09/UjGwUGwU9s5LNFEgj4v1tcvWnONzWNXkyMrs5g4e\nU8L/DQ3XgNrcA9zrfFq0cQhSJonj/VI/jbBYyB2yEuQAIjAN6eDIOoLmHGIIvZtE\n0LL5jaZC3W518jB1OF7QSvaFtaFl0VqDy6LMXL50elMVC+hr9KpDnN0t8gaSiPyZ\nwEC9SMdQ7SLVOUK1Xdh3dh0CAwEAAaOCAQkwggEFMA8GA1UdEwEB/wQFMAMBAf8w\nHwYDVR0jBBgwFoAU0aT+MaGsc75ZynH0up0oH+tVHh4wHQYDVR0OBBYEFHfZ0AaR\nZtjdGSaNSazaMC9qe2KxMA4GA1UdDwEB/wQEAwIBBjAgBgNVHSAEGTAXMAsGCWCE\nQgEaAQACBzAIBgZngQwBAgEwRQYDVR0fBD4wPDA6oDigNoY0aHR0cDovL2NybC50\nZXN0NC5idXlwYXNzLm5vL2NybC9CUENsYXNzMlQ0Um9vdENBLmNybDA5BggrBgEF\nBQcBAQQtMCswKQYIKwYBBQUHMAGGHWh0dHA6Ly9vY3NwLnRlc3Q0LmJ1eXBhc3Mu\nY29tMA0GCSqGSIb3DQEBCwUAA4ICAQBOgxedV31NCpZQRc8yFxoqQNgBnY1UeH/h\n/s/9fGQzyGnTWZldEi5MGJKF6ulcYnklitlg/jic9au3xSoqP/i2smUHByX2wMrC\nmDpLCwio2x2p/0Wscj5asqzJE2cCWqob2iHxo36nsr3Jdd2GIlzhZ0wm8rMZxsQG\nFgbgHYIer79S+PIdHoZuUnCJhsJ+1PRUmm2t7vcmZpu8l4CeL0XJX98l2L8kbBds\nMGo1EazGAEirZnSfQKCARhUcEdavsKl067+irsGGcK4+L78Vl9S1/QPfKG30L5fv\nnM1X1qAdhsbjwVdrhLkjpzabT0icsW6W17HLh8UBYdA7k4GclA6h+mNrXAt7JAeZ\nPzMFq0I7vVJNEdolZHTVCqT0sdJiTj+phS1ztK86Wb1R/5d5B1VSb789zSdJfrwV\nppXgPtZq5x3GQi6ooteWyuWj3cBcNu9TU1D8u1F0XI5gw4Y0VpxlDxysUgFQJlo4\nVYmMpgr442o/35UgwzkIC7x/6dkvMZvM4jYB5JZJXjynR35XawXB/hzybermJ8BB\nDsY0MCOwxhpsTbyEC4wfxZ08B4JtORkToOt4OWuejovsr68Ht6ytOPj7dquoPPNM\n9eGNSp94nEIiZ2n75ZMg0gIQArXU9OCV6B2TXxB7w2YB0y0teDgVhoM3IY/ltqJ/\nPJrUUjM8OQ==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "certbot/tests/testdata/ocsp_responder_certificate.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIEpjCCAo6gAwIBAgINARMIGYlEsD1LTt6D7zANBgkqhkiG9w0BAQsFADBRMQsw\nCQYDVQQGEwJOTzEdMBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIzAhBgNV\nBAMMGkJ1eXBhc3MgQ2xhc3MgMiBUZXN0NCBDQSA1MB4XDTE5MDQwNTEwMDAwMFoX\nDTE5MDcwNDEwMDAwMFowSTELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3Mg\nQVMtOTgzMTYzMzI3MRswGQYDVQQDDBJCdXlwYXNzIFRlc3Q0IE9DU1AwggEiMA0G\nCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKGF+kYNd1fbhYT7Vf9xouZlx+4w45\nY5EowPoaSKFo4uUDDxkj4PwmMiH4w9Q2bGrCbZRrDrvlNVY/kwzLu4CIk6Ip0dgm\nVZGNFB3Xo9nai7rI5pn/YVvVnDIQXh1LRbekzLVyHvhRgMpRb19xN/iYsxaOJDph\n8eAgbTKf6eitvfbvn/zXHj4KGKycuULI4+mwlfV3uioT4ulbT7PTVJetgi/XXFDO\nxMjbqx6I1ZMmzKJ6LNaFlfx6GdZsaLRDCidHzGp8Fm4ZdV+UPvMZcVDQO6rvQ3wU\niGyCqgfE5e0aFvfeLoBPBtaoT0Ht1CvGdTfVet6PXrF6gh40fdEH5Ob5AgMBAAGj\ngYQwgYEwCQYDVR0TBAIwADAfBgNVHSMEGDAWgBR32dAGkWbY3RkmjUms2jAvanti\nsTAdBgNVHQ4EFgQU3VlR+sSIVpmXklieP7IlpVUcXIowDgYDVR0PAQH/BAQDAgeA\nMBMGA1UdJQQMMAoGCCsGAQUFBwMJMA8GCSsGAQUFBzABBQQCBQAwDQYJKoZIhvcN\nAQELBQADggIBAFBRLVsBadNFAoFi0HOrfxYsiqggZGJLlgxGyi/0NBIgduG4kcpM\nTHvplwBwMQEqyp5511pSEbLPAFj8EqC5c46hXZXmT49xlfRvr2Bo+qtTPV9szuWr\n8muEIejwRrkATpqWPZWR2zVTXfB90mU2oGuRvxUVmnW4v+FrCChJo7+9yTocZJKx\np4vxYfPMeggomdGAAUz94+0ppSjOLDzs3MA8uOcR0zJ2Y7UHb7PBf/HiM3GO2uKB\nsRgdDaGIf/PNpav0xJ/abGNNNwvXzHiMgqqImsuv/JoncPQWbClNurhXpdN7xt9C\nHcLX2AdggabcogjWm4guBFuFTsL1i0l8Bsu/6iPJ7ddCeANfYzf7h6AcQq12uFl3\n070F29DtPh8D3FPWgRZZsxoANFjXErxfj4a4+DR+jhhkb9YM/wI0vCOM7W6PKxVn\nZK5kHGOQTcQMj7RCX52gEf27M33zC7HVam+kKhGvwq7D9Bs5hZclzcbjpR4eIxT7\ntzuiy5VpPh1DRLPrphPUB4xsA1dy6zbkg8OqddG6NxD++ja/iZyzSB3SeWyO02qA\nQoK2FzDasxpZ9rT3ioAcms3wVNe4lcd4OP8gHZONuat/gvxk6OZvAld6cnIrQZYB\nTbu89ZWvhsyI3p4YC/15pUvA95j9Y0te+G+CF22Eoyb+rtz6mMletnUB\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "certbot/tests/testdata/os-release",
    "content": "NAME=\"SystemdOS\"\nVERSION=\"42.42.42 LTS, Unreal\"\nID=systemdos\nID_LIKE=\"something nonexistent debian\"\nVERSION_ID=\"42\"\nHOME_URL=\"http://www.example.com/\"\nSUPPORT_URL=\"http://help.example.com/\"\n"
  },
  {
    "path": "certbot/tests/testdata/rsa2048_key.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDm1WIecnHjL4Fs\nJvxDP27GyeqnXKc41HsRP9cv4z+NDjE94mDgva5ndieiA9xZ0Sh7LXtZcGDcpGop\n+D7s+oh0apV6idIJ9eEPUegYlGxOFJQnZ8re6hD7MaAlNZEVhZrwJvrGy6rTFpi3\nDaNokGn7r3s2nrQ9aziljkWRp1PnTBnRNgOdi3c1IB2f4+2PdykjihxlnYUuI4Wf\n5QU5pFx60a2mdTVDC+bKAP22IvuQnnkHgJYYS/oMxFCT9QR4xQRPOx7U2RWVrFDV\nMJ3mIB8FOW6JXfQSmaZZr46xclbEIr4QQ6RcPWvcJ1cCV1idFjEmufi52sV7r1Bf\n3nCJFk1fAgMBAAECggEAJkhbVntagfgd+cbZbXm2sIdKQGlwXk92/Zxd3tZMcuNY\nrU+/C2bJ5uTEm+0R/V9f3FXlsCagGde2t7ExFnJScSRAGCuFRxudMMI/wNvUvnpR\nO9vN3HxrRo2rZqBkqHIZCR0d2Bxs/0cvGqTLZgsVWKV4xM07TThcE7DtvsNGegRn\nWFxfsRcRypkIvZoba1HagvCituRBEa07R7mQp8kRhP9ZeRq3bZws9qBmqzj1cylG\nq8QA4Foq7sK8P78bpIhrcOFBDAr+Vr1ZGY6u01J0w13MUtl6iIx4VCjQKt4NkzsK\ndj2q+GAMwhReR2ZS42o8LiyGpwusj+dKIFfFekgK2QKBgQD4wwmRDgvt85brQTNF\nTkhui0eToz5oXt8mVDb58nwkpojFQOv87ZyNsEqm7S0t/3RtEViVio2aymTMsrz4\n21vRq46dvhINQ3DoMok6xIchEOEgMeonOilkURWtrMjD/Kn297Asv7zOqI5BCNiP\n3FFcRqf+CaqbhnOgMkcI5z6b7QKBgQDtjM1otFFHyS7ctyLRuMeFyxWUSbWHvi8U\nxjUW256c6wpQ2DBLSVB61VQjfrSjkZ5DJVFGnbw42HxSDafL11mzTbY1vDbgtgLK\nYiuVHG7OYZJTLaZoM68BseX4xHN8FztnvvP1ttuk5oFb+vD8q6ODZSEawRd3PvtX\nD7RtNouc+wKBgQDiwBWGTUF+gt18T5BGilbnvLlf0Btg06mgrH74UpnqZoqhEs6J\nXKWpWZqSkfruxL4BdSBEH2l4QSiklgA+7uTBOBnlm42k3WaboQUJtn5eG5651AXV\n/+Qe9vJFvwu56iObZKcIAzY9QdN5YHDWoULgU99pZrJG1cWrrmilqvOc+QKBgQCB\niOslslY0N+926eJxzDn4qkJtJzh2+e1AfcjLWx0F4mEwroK/Ow5IvPVxmZE1NJ3B\nbaMBR9gwg1RfhhS+4gKG9NRsPuMJ7BZfd+LeH7AImEorU1RPtAc1fGW0HqP+wchi\nDU2I6pqhNBTMLG2myo2Sg93mce6y1sRFuEmh2EGPawKBgQC3uUEdjQekXaxXfYHi\n1Dk3Ht1a9t8XxwoCVRqicE7lqlwDtS2y9lHAeUP7JNy8ZGNjx8srRZpkYVMztugo\nEcw26UA7FbNqJP5OPkGjfiFqtOq70h9vlfLdiAPmoqyOx//RkgiNXt9m5xcDzzdB\n7EtBK59KSiQkB8fHtooy7Ipiiw==\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "certbot/tests/testdata/rsa256_key.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIGrAgEAAiEAm2Fylv+Uz7trgTW8EBHP3FQSMeZs2GNQ6VRo1sIVJEkCAwEAAQIh\nAJT0BA/xD01dFCAXzSNyj9nfSZa3NpqzJZZn/eOm7vghAhEAzUVNZn4lLLBD1R6N\nE8TKNQIRAMHHyn3O5JeY36lwKwkUlEUCEAliRauN0L0+QZuYjfJ9aJECEGx4dru3\nrTPCyighdqWNlHUCEQCiLjlwSRtWgmMBudCkVjzt\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "certbot/tests/testdata/rsa512_key.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIBOgIBAAJBAKx1c7RR7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79\nvukFhN9HFoHZiUvOjm0c+pVE6K+EdE/twuUCAwEAAQJAMbrEnJCrQe8YqAbw1/Bn\nelAzIamndfE3U8bTavf9sgFpS4HL83rhd6PDbvx81ucaJAT/5x048fM/nFl4fzAc\nmQIhAOF/a9o3EIsDKEmUl+Z1OaOiUxDF3kqWSmALEsmvDhwXAiEAw8ljV5RO/rUp\nZu2YMDFq3MKpyyMgBIJ8CxmGRc6gCmMCIGRQzkcmhfqBrhOFwkmozrqIBRIKJIjj\n8TRm2LXWZZ2DAiAqVO7PztdNpynugUy4jtbGKKjBrTSNBRGA7OHlUgm0dQIhALQq\n6oGU29Vxlvt3k0vmiRKU4AVfLyNXIGtcWcNG46h/\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "certbot/tests/testdata/sample-archive/cert1.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIE1DCCA7ygAwIBAgITAPoz/CBluNQV/Eh9F+CS6dSxEDANBgkqhkiG9w0BAQsF\nADAfMR0wGwYDVQQDDBRoYXBweSBoYWNrZXIgZmFrZSBDQTAeFw0xNjAyMDIyMzQ5\nMDBaFw0xNjA1MDIyMzQ5MDBaMBQxEjAQBgNVBAMTCWlzbm90Lm9yZzCCASIwDQYJ\nKoZIhvcNAQEBBQADggEPADCCAQoCggEBALyudqLKcIdWZ5VaK1fuhlEDbZtvs2E+\nslm4dmSS1nFve7MdlZ69K0gdtnhkiPQ0wGQTligeDZ8fY8iL87GZO0tp5f7S+QJN\nNYCiYw6j4qp5JBy/zG22kJz1Quu7/vXMYLzLvK6x6YixiWAWyqqvlUVBLS1r4W3h\nA5Z+F1EIsXeyz7TJe3lAzIWAAxpfH9OviIz2rEDotuCdU771USLLNSw4qJojNlTx\nUpZG6lGFs8KGb8tqROXknaMKE4PvN3SITixSUTFbktt1Wz60moWbNdLMKvgkzuUP\nr4viO2P4SO5slNAY0ZeEssPpVAelN3EvrAcEZtoKmG5fnQDVo8uVag0CAwEAAaOC\nAhIwggIOMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB\nBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUqhI4u6aaPrcYQnmypxV8Tap8\nL54wHwYDVR0jBBgwFoAU+3hPEvlgFYMsnxd/NBmzLjbqQYkweAYIKwYBBQUHAQEE\nbDBqMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5zdGFnaW5nLXgxLmxldHNlbmNy\neXB0Lm9yZy8wMwYIKwYBBQUHMAKGJ2h0dHA6Ly9jZXJ0LnN0YWdpbmcteDEubGV0\nc2VuY3J5cHQub3JnLzAUBgNVHREEDTALgglpc25vdC5vcmcwgf4GA1UdIASB9jCB\n8zAIBgZngQwBAgEwgeYGCysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRw\nOi8vY3BzLmxldHNlbmNyeXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENl\ncnRpZmljYXRlIG1heSBvbmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFy\ndGllcyBhbmQgb25seSBpbiBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRl\nIFBvbGljeSBmb3VuZCBhdCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0\nb3J5LzANBgkqhkiG9w0BAQsFAAOCAQEAbAhX6FfQwELayneY4l5RvYSdw/Jj5CRy\nKzrM7ISld7x9YPpxX6Pmht/YyMhLWrtxvFUR2+RNhSIYB8IjQEjmKjvR7UNeiUve\njzPEAuTg/9m3i0FJpPHc2aKGzlLFQCMm5/RrvnXI6ljIcyhocLvMiN46iexcExI2\nEse3w8GoH6wARYKxU/QBexfoXQLgtAbYzNRE6EgKWtB+txV+7+d2MgbhCEit5VwU\n+ydT8inp9URsA7iKM03hDdGOBysddkrm1/yEhVy/Oo6bT9WMAUHVvz61hHekWcSf\nrAQ6BayubvWOUx06eTowXr1gln/rl+WXOxcsJeag127NuhmHOCXZxQ==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "certbot/tests/testdata/sample-archive/chain1.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDETCCAfmgAwIBAgIJAJzxkS6o1QkIMA0GCSqGSIb3DQEBCwUAMB8xHTAbBgNV\nBAMMFGhhcHB5IGhhY2tlciBmYWtlIENBMB4XDTE1MDQwNzIzNTAzOFoXDTI1MDQw\nNDIzNTAzOFowHzEdMBsGA1UEAwwUaGFwcHkgaGFja2VyIGZha2UgQ0EwggEiMA0G\nCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCCkd5mgXFErJ3F2M0E9dw+Ta/md5i\n8TDId01HberAApqmydG7UZYF3zLTSzNjlNSOmtybvrSGUnZ9r9tSQcL8VM6WUOM8\ntnIpiIjEA2QkBycMwvRmZ/B2ltPdYs/R9BqNwO1g18GDZrHSzUYtNKNeFI6Glamj\n7GK2Vr0SmiEamlNIR5ktAFsEErzf/d4jCF7sosMsJpMCm1p58QkP4LHLShVLXDa8\nBMfVoI+ipYcA08iNUFkgW8VWDclIDxcysa0psDDtMjX3+4aPkE/cefmP+1xOfUuD\nHOGV8XFynsP4EpTfVOZr0/g9gYQ7ZArqXX7GTQkFqduwPm/w5qxSPTarAgMBAAGj\nUDBOMB0GA1UdDgQWBBT7eE8S+WAVgyyfF380GbMuNupBiTAfBgNVHSMEGDAWgBT7\neE8S+WAVgyyfF380GbMuNupBiTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUA\nA4IBAQAd9Da+Zv+TjMv7NTAmliqnWHY6d3UxEZN3hFEJ58IQVHbBZVZdW7zhRktB\nvR05Kweac0HJeK91TKmzvXl21IXLvh0gcNLU/uweD3no/snfdB4OoFompljThmgl\nzBqiqWoKBJQrLCA8w5UB+ReomRYd/EYXF/6TAfzm6hr//Xt5mPiUHPdvYt75lMAo\nvRxLSbF8TSQ6b7BYxISWjPgFASNNqJNHEItWsmQMtAjjwzb9cs01XH9pChVAWn9L\noeMKa+SlHSYrWG93+EcrIH/dGU76uNOiaDzBSKvaehG53h25MHuO1anNICJvZovW\nrFo4Uv1EnkKJm3vJFe50eJGhEKlx\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "certbot/tests/testdata/sample-archive/fullchain1.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIE1DCCA7ygAwIBAgITAPoz/CBluNQV/Eh9F+CS6dSxEDANBgkqhkiG9w0BAQsF\nADAfMR0wGwYDVQQDDBRoYXBweSBoYWNrZXIgZmFrZSBDQTAeFw0xNjAyMDIyMzQ5\nMDBaFw0xNjA1MDIyMzQ5MDBaMBQxEjAQBgNVBAMTCWlzbm90Lm9yZzCCASIwDQYJ\nKoZIhvcNAQEBBQADggEPADCCAQoCggEBALyudqLKcIdWZ5VaK1fuhlEDbZtvs2E+\nslm4dmSS1nFve7MdlZ69K0gdtnhkiPQ0wGQTligeDZ8fY8iL87GZO0tp5f7S+QJN\nNYCiYw6j4qp5JBy/zG22kJz1Quu7/vXMYLzLvK6x6YixiWAWyqqvlUVBLS1r4W3h\nA5Z+F1EIsXeyz7TJe3lAzIWAAxpfH9OviIz2rEDotuCdU771USLLNSw4qJojNlTx\nUpZG6lGFs8KGb8tqROXknaMKE4PvN3SITixSUTFbktt1Wz60moWbNdLMKvgkzuUP\nr4viO2P4SO5slNAY0ZeEssPpVAelN3EvrAcEZtoKmG5fnQDVo8uVag0CAwEAAaOC\nAhIwggIOMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB\nBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUqhI4u6aaPrcYQnmypxV8Tap8\nL54wHwYDVR0jBBgwFoAU+3hPEvlgFYMsnxd/NBmzLjbqQYkweAYIKwYBBQUHAQEE\nbDBqMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5zdGFnaW5nLXgxLmxldHNlbmNy\neXB0Lm9yZy8wMwYIKwYBBQUHMAKGJ2h0dHA6Ly9jZXJ0LnN0YWdpbmcteDEubGV0\nc2VuY3J5cHQub3JnLzAUBgNVHREEDTALgglpc25vdC5vcmcwgf4GA1UdIASB9jCB\n8zAIBgZngQwBAgEwgeYGCysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRw\nOi8vY3BzLmxldHNlbmNyeXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENl\ncnRpZmljYXRlIG1heSBvbmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFy\ndGllcyBhbmQgb25seSBpbiBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRl\nIFBvbGljeSBmb3VuZCBhdCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0\nb3J5LzANBgkqhkiG9w0BAQsFAAOCAQEAbAhX6FfQwELayneY4l5RvYSdw/Jj5CRy\nKzrM7ISld7x9YPpxX6Pmht/YyMhLWrtxvFUR2+RNhSIYB8IjQEjmKjvR7UNeiUve\njzPEAuTg/9m3i0FJpPHc2aKGzlLFQCMm5/RrvnXI6ljIcyhocLvMiN46iexcExI2\nEse3w8GoH6wARYKxU/QBexfoXQLgtAbYzNRE6EgKWtB+txV+7+d2MgbhCEit5VwU\n+ydT8inp9URsA7iKM03hDdGOBysddkrm1/yEhVy/Oo6bT9WMAUHVvz61hHekWcSf\nrAQ6BayubvWOUx06eTowXr1gln/rl+WXOxcsJeag127NuhmHOCXZxQ==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIDETCCAfmgAwIBAgIJAJzxkS6o1QkIMA0GCSqGSIb3DQEBCwUAMB8xHTAbBgNV\nBAMMFGhhcHB5IGhhY2tlciBmYWtlIENBMB4XDTE1MDQwNzIzNTAzOFoXDTI1MDQw\nNDIzNTAzOFowHzEdMBsGA1UEAwwUaGFwcHkgaGFja2VyIGZha2UgQ0EwggEiMA0G\nCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCCkd5mgXFErJ3F2M0E9dw+Ta/md5i\n8TDId01HberAApqmydG7UZYF3zLTSzNjlNSOmtybvrSGUnZ9r9tSQcL8VM6WUOM8\ntnIpiIjEA2QkBycMwvRmZ/B2ltPdYs/R9BqNwO1g18GDZrHSzUYtNKNeFI6Glamj\n7GK2Vr0SmiEamlNIR5ktAFsEErzf/d4jCF7sosMsJpMCm1p58QkP4LHLShVLXDa8\nBMfVoI+ipYcA08iNUFkgW8VWDclIDxcysa0psDDtMjX3+4aPkE/cefmP+1xOfUuD\nHOGV8XFynsP4EpTfVOZr0/g9gYQ7ZArqXX7GTQkFqduwPm/w5qxSPTarAgMBAAGj\nUDBOMB0GA1UdDgQWBBT7eE8S+WAVgyyfF380GbMuNupBiTAfBgNVHSMEGDAWgBT7\neE8S+WAVgyyfF380GbMuNupBiTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUA\nA4IBAQAd9Da+Zv+TjMv7NTAmliqnWHY6d3UxEZN3hFEJ58IQVHbBZVZdW7zhRktB\nvR05Kweac0HJeK91TKmzvXl21IXLvh0gcNLU/uweD3no/snfdB4OoFompljThmgl\nzBqiqWoKBJQrLCA8w5UB+ReomRYd/EYXF/6TAfzm6hr//Xt5mPiUHPdvYt75lMAo\nvRxLSbF8TSQ6b7BYxISWjPgFASNNqJNHEItWsmQMtAjjwzb9cs01XH9pChVAWn9L\noeMKa+SlHSYrWG93+EcrIH/dGU76uNOiaDzBSKvaehG53h25MHuO1anNICJvZovW\nrFo4Uv1EnkKJm3vJFe50eJGhEKlx\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "certbot/tests/testdata/sample-archive/privkey1.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8rnaiynCHVmeV\nWitX7oZRA22bb7NhPrJZuHZkktZxb3uzHZWevStIHbZ4ZIj0NMBkE5YoHg2fH2PI\ni/OxmTtLaeX+0vkCTTWAomMOo+KqeSQcv8xttpCc9ULru/71zGC8y7yusemIsYlg\nFsqqr5VFQS0ta+Ft4QOWfhdRCLF3ss+0yXt5QMyFgAMaXx/Tr4iM9qxA6LbgnVO+\n9VEiyzUsOKiaIzZU8VKWRupRhbPChm/LakTl5J2jChOD7zd0iE4sUlExW5LbdVs+\ntJqFmzXSzCr4JM7lD6+L4jtj+EjubJTQGNGXhLLD6VQHpTdxL6wHBGbaCphuX50A\n1aPLlWoNAgMBAAECggEAfKKWFWS6PnwSAnNErFoQeZVVItb/XB5JO8EA2+CvLNFi\nmefR/MCixYlzDkYCvaXW7ISPrMJlZxYaGNBx0oAQzfkPB2wfNqj/zY/29SXGxast\n8puzk0mEb1oHsaZGfeFaiXvfkFpPlI8J2uJTT7qaVNv/1sArciSv9QonpsyiRhlB\nyqT49juNVoR1tJHyXzkkRfHKTG8OlJd4kuFOl3fM9dTFPQ/ft0kTNAQ/B4SFvSwF\nRJsbLbsbFGsUdV9ekE6UX6oWD/Ah707rvgtCyS0Bc+0O3t2EKwmm3RXPRUMHCVxE\nbKdTxRB4etbjMVXMuVhB8Y4GbfrtMCy+qxZQ6znCAQKBgQDr7bcYAZVZp/nBMVB+\nlBO9w73J6lnEWm6bZ9728KlGAKETaRhxZQSi6TN6MWwNwnk6rinyz4uVwVr9ZRCs\nWkB1TbvW0JNcWdr3YClwsKXAt8X22bjGe0LagDJHG6r1TPS+MdovOS2M6IMaxlbT\nrzFhSJ8ojLX3tqnOsmc7YAFLjQKBgQDMu8E9hoJt82lQzOGrjHmGzGEu2GLx9WKO\ne4nkj335kX6fIhMMqSXBFbTJZwXoYvk5J8ZnaARbYG0m5nxDCwRjX5HWa8q0B2Po\nta53w01sKKznzlPjUhsdhEthun7MCFfLZpgvcZ9xVzOXo3/Zfn2+RrsPSjrVDqBy\nhj+k5mW4gQKBgHFWKf3LTO7cBdvsD8ou4mjn7nVgMi1kb/wR4wdnxzmMtdR4STi4\nGYkVVBhgQ5M8mDY7UoWFdH3FfCt8cI0Lcimn5ROl8RSNSeZKeL3c7lNtNRmHr/8R\nWaVTrlOAlBjxFiWEF1dWNW6ah9jF7RIV+DfOxj6ZkhTk2CAmjfb1AMpFAoGABf96\nKdNG/vGipDtcYSo8ZTaXoke0nmISARqdb5TEnAsnKoJVDInoEUARi9T411YO9x2z\nMlRZzFOG3xzhhxVLi53BKAcAaUXOJ4MrGVcfbYvDhQcGbiJ5qOO3UaWlEVUtPUhE\nLR+nDCsB1+9yT2zlQi3QTSJflt5W1QQZ2TrmwAECgYEAvQ7+sTcHs1K9yKj7koEu\nA19FbMA0IwvrVRcV/VqmlsoW6e6wW2YND+GtaDbKdD0aBPivqLJwpNFrsRA+W0iB\nvzmML6sKhhL+j7tjSgq+iQdBkKz0j9PyReuhe9CRnljMmyun+4qKEk0KUvxBrjPY\nSkn+ML18qyUoEPnmbpfHxCs=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "certbot/tests/testdata/sample-renewal-ancient.conf",
    "content": "cert = MAGICDIR/live/sample-renewal-ancient/cert.pem\nprivkey = MAGICDIR/live/sample-renewal-ancient/privkey.pem\nchain = MAGICDIR/live/sample-renewal-ancient/chain.pem\nfullchain = MAGICDIR/live/sample-renewal-ancient/fullchain.pem\nrenew_before_expiry = 1 year\n\n# Options and defaults used in the renewal process\n[renewalparams]\nno_self_upgrade = False\napache_enmod = a2enmod\nno_verify_ssl = False\nifaces = None\napache_dismod = a2dismod\nregister_unsafely_without_email = False\napache_handle_modules = True\nuir = None\ninstaller = None\nnginx_ctl = nginx\nconfig_dir = MAGICDIR\ntext_mode = False\nfunc = <function obtain_cert at 0x7f093a163c08>\nstaging = True\nprepare = False\nwork_dir = /var/lib/letsencrypt\ntos = False\ninit = False\nhttp01_port = 80\nduplicate = False\nnoninteractive_mode = True\nkey_path = None\nnginx = False\nnginx_server_root = /etc/nginx\nfullchain_path = /home/ubuntu/letsencrypt/chain.pem\nemail = None\ncsr = None\nagree_dev_preview = None\nredirect = None\nverb = certonly\nverbose_count = -3\nconfig_file = None\nrenew_by_default = False\nhsts = False\napache_handle_sites = True\nauthenticator = webroot\ndomains = isnot.org,\nrsa_key_size = 2048\napache_challenge_location = /etc/apache2\ncheckpoints = 1\nmanual_test_mode = False\napache = False\ncert_path = /home/ubuntu/letsencrypt/cert.pem\nwebroot_path = /var/www/\nreinstall = False\nexpand = False\nstrict_permissions = False\napache_server_root = /etc/apache2\naccount = None\ndry_run = False\nmanual_public_ip_logging_ok = False\nchain_path = /home/ubuntu/letsencrypt/chain.pem\nbreak_my_certs = False\nstandalone = True\nmanual = False\nserver = https://acme-staging.api.letsencrypt.org/directory\nwebroot = True\nos_packages_only = False\napache_init_script = None\nuser_agent = None\napache_le_vhost_ext = -le-ssl.conf\ndebug = False\nlogs_dir = /var/log/letsencrypt\napache_vhost_root = /etc/apache2/sites-available\nconfigurator = None\n"
  },
  {
    "path": "certbot/tests/testdata/sample-renewal.conf",
    "content": "cert = MAGICDIR/live/sample-renewal/cert.pem\nprivkey = MAGICDIR/live/sample-renewal/privkey.pem\nchain = MAGICDIR/live/sample-renewal/chain.pem\nfullchain = MAGICDIR/live/sample-renewal/fullchain.pem\nrenew_before_expiry = 4 years\n\n# Options and defaults used in the renewal process\n[renewalparams]\nno_self_upgrade = False\napache_enmod = a2enmod\nno_verify_ssl = False\nifaces = None\napache_dismod = a2dismod\nregister_unsafely_without_email = False\napache_handle_modules = True\nuir = None\ninstaller = None\nnginx_ctl = nginx\nconfig_dir = MAGICDIR\ntext_mode = False\nfunc = <function obtain_cert at 0x7f093a163c08>\nstaging = True\nprepare = False\nwork_dir = /var/lib/letsencrypt\ntos = False\ninit = False\nhttp01_port = 80\nduplicate = False\nnoninteractive_mode = True\nkey_path = None\nnginx = False\nnginx_server_root = /etc/nginx\nfullchain_path = /home/ubuntu/letsencrypt/chain.pem\nemail = None\ncsr = None\nagree_dev_preview = None\nredirect = None\nverb = certonly\nverbose_count = -3\nconfig_file = None\nrenew_by_default = False\nhsts = False\napache_handle_sites = True\nauthenticator = standalone\ndomains = isnot.org,\nrsa_key_size = 2048\napache_challenge_location = /etc/apache2\ncheckpoints = 1\nmanual_test_mode = False\napache = False\ncert_path = /home/ubuntu/letsencrypt/cert.pem\nwebroot_path = None\nreinstall = False\nexpand = False\nstrict_permissions = False\napache_server_root = /etc/apache2\naccount = None\ndry_run = False\nmanual_public_ip_logging_ok = False\nchain_path = /home/ubuntu/letsencrypt/chain.pem\nbreak_my_certs = False\nstandalone = True\nmanual = False\nserver = https://acme-staging-v02.api.letsencrypt.org/directory\nwebroot = False\nos_packages_only = False\napache_init_script = None\nuser_agent = None\napache_le_vhost_ext = -le-ssl.conf\ndebug = False\nlogs_dir = /var/log/letsencrypt\napache_vhost_root = /etc/apache2/sites-available\nconfigurator = None\nmust_staple = True\n[[webroot_map]]\n"
  },
  {
    "path": "certbot/tests/testdata/webrootconftest.ini",
    "content": "webroot\nwebroot-path = /tmp\ndomains = eg.com, eg2.com\n"
  },
  {
    "path": "certbot/tests/util.py",
    "content": "\"\"\"Test utilities.\"\"\"\nimport logging\nfrom multiprocessing import Event\nfrom multiprocessing import Process\nimport shutil\nimport sys\nimport tempfile\nimport unittest\n\nfrom cryptography.hazmat.backends import default_backend\nfrom cryptography.hazmat.primitives import serialization\nimport josepy as jose\nimport mock\nimport OpenSSL\nimport pkg_resources\nimport six\nfrom six.moves import reload_module\n\nfrom certbot import interfaces\nfrom certbot import util\nfrom certbot._internal import configuration\nfrom certbot._internal import constants\nfrom certbot._internal import lock\nfrom certbot._internal import storage\nfrom certbot.compat import filesystem\nfrom certbot.compat import os\nfrom certbot.display import util as display_util\n\n\ndef vector_path(*names):\n    \"\"\"Path to a test vector.\"\"\"\n    return pkg_resources.resource_filename(\n        __name__, os.path.join('testdata', *names))\n\n\ndef load_vector(*names):\n    \"\"\"Load contents of a test vector.\"\"\"\n    # luckily, resource_string opens file in binary mode\n    data = pkg_resources.resource_string(\n        __name__, os.path.join('testdata', *names))\n    # Try at most to convert CRLF to LF when data is text\n    try:\n        return data.decode().replace('\\r\\n', '\\n').encode()\n    except ValueError:\n        # Failed to process the file with standard encoding.\n        # Most likely not a text file, return its bytes untouched.\n        return data\n\n\ndef _guess_loader(filename, loader_pem, loader_der):\n    _, ext = os.path.splitext(filename)\n    if ext.lower() == '.pem':\n        return loader_pem\n    elif ext.lower() == '.der':\n        return loader_der\n    raise ValueError(\"Loader could not be recognized based on extension\")  # pragma: no cover\n\n\ndef load_cert(*names):\n    \"\"\"Load certificate.\"\"\"\n    loader = _guess_loader(\n        names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1)\n    return OpenSSL.crypto.load_certificate(loader, load_vector(*names))\n\n\ndef load_csr(*names):\n    \"\"\"Load certificate request.\"\"\"\n    loader = _guess_loader(\n        names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1)\n    return OpenSSL.crypto.load_certificate_request(loader, load_vector(*names))\n\n\ndef load_comparable_csr(*names):\n    \"\"\"Load ComparableX509 certificate request.\"\"\"\n    return jose.ComparableX509(load_csr(*names))\n\n\ndef load_rsa_private_key(*names):\n    \"\"\"Load RSA private key.\"\"\"\n    loader = _guess_loader(names[-1], serialization.load_pem_private_key,\n                           serialization.load_der_private_key)\n    return jose.ComparableRSAKey(loader(\n        load_vector(*names), password=None, backend=default_backend()))\n\n\ndef load_pyopenssl_private_key(*names):\n    \"\"\"Load pyOpenSSL private key.\"\"\"\n    loader = _guess_loader(\n        names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1)\n    return OpenSSL.crypto.load_privatekey(loader, load_vector(*names))\n\n\ndef make_lineage(config_dir, testfile):\n    \"\"\"Creates a lineage defined by testfile.\n\n    This creates the archive, live, and renewal directories if\n    necessary and creates a simple lineage.\n\n    :param str config_dir: path to the configuration directory\n    :param str testfile: configuration file to base the lineage on\n\n    :returns: path to the renewal conf file for the created lineage\n    :rtype: str\n\n    \"\"\"\n    lineage_name = testfile[:-len('.conf')]\n\n    conf_dir = os.path.join(\n        config_dir, constants.RENEWAL_CONFIGS_DIR)\n    archive_dir = os.path.join(\n        config_dir, constants.ARCHIVE_DIR, lineage_name)\n    live_dir = os.path.join(\n        config_dir, constants.LIVE_DIR, lineage_name)\n\n    for directory in (archive_dir, conf_dir, live_dir,):\n        if not os.path.exists(directory):\n            filesystem.makedirs(directory)\n\n    sample_archive = vector_path('sample-archive')\n    for kind in os.listdir(sample_archive):\n        shutil.copyfile(os.path.join(sample_archive, kind),\n                        os.path.join(archive_dir, kind))\n\n    for kind in storage.ALL_FOUR:\n        os.symlink(os.path.join(archive_dir, '{0}1.pem'.format(kind)),\n                   os.path.join(live_dir, '{0}.pem'.format(kind)))\n\n    conf_path = os.path.join(config_dir, conf_dir, testfile)\n    with open(vector_path(testfile)) as src:\n        with open(conf_path, 'w') as dst:\n            dst.writelines(\n                line.replace('MAGICDIR', config_dir) for line in src)\n\n    return conf_path\n\n\ndef patch_get_utility(target='zope.component.getUtility'):\n    \"\"\"Patch zope.component.getUtility to use a special mock IDisplay.\n\n    The mock IDisplay works like a regular mock object, except it also\n    also asserts that methods are called with valid arguments.\n\n    :param str target: path to patch\n\n    :returns: mock zope.component.getUtility\n    :rtype: mock.MagicMock\n\n    \"\"\"\n    return mock.patch(target, new_callable=_create_get_utility_mock)\n\n\ndef patch_get_utility_with_stdout(target='zope.component.getUtility',\n                                  stdout=None):\n    \"\"\"Patch zope.component.getUtility to use a special mock IDisplay.\n\n    The mock IDisplay works like a regular mock object, except it also\n    also asserts that methods are called with valid arguments.\n\n    The `message` argument passed to the IDisplay methods is passed to\n    stdout's write method.\n\n    :param str target: path to patch\n    :param object stdout: object to write standard output to; it is\n        expected to have a `write` method\n\n    :returns: mock zope.component.getUtility\n    :rtype: mock.MagicMock\n\n    \"\"\"\n    stdout = stdout if stdout else six.StringIO()\n\n    freezable_mock = _create_get_utility_mock_with_stdout(stdout)\n    return mock.patch(target, new=freezable_mock)\n\n\nclass FreezableMock(object):\n    \"\"\"Mock object with the ability to freeze attributes.\n\n    This class works like a regular mock.MagicMock object, except\n    attributes and behavior set before the object is frozen cannot\n    be changed during tests.\n\n    If a func argument is provided to the constructor, this function\n    is called first when an instance of FreezableMock is called,\n    followed by the usual behavior defined by MagicMock. The return\n    value of func is ignored.\n\n    \"\"\"\n    def __init__(self, frozen=False, func=None, return_value=mock.sentinel.DEFAULT):\n        self._frozen_set = set() if frozen else {'freeze', }\n        self._func = func\n        self._mock = mock.MagicMock()\n        if return_value != mock.sentinel.DEFAULT:\n            self.return_value = return_value\n        self._frozen = frozen\n\n    def freeze(self):\n        \"\"\"Freeze object preventing further changes.\"\"\"\n        self._frozen = True\n\n    def __call__(self, *args, **kwargs):\n        if self._func is not None:\n            self._func(*args, **kwargs)\n        return self._mock(*args, **kwargs)\n\n    def __getattribute__(self, name):\n        if name == '_frozen':\n            try:\n                return object.__getattribute__(self, name)\n            except AttributeError:\n                return False\n        elif name in ('return_value', 'side_effect',):\n            return getattr(object.__getattribute__(self, '_mock'), name)\n        elif name == '_frozen_set' or name in self._frozen_set:\n            return object.__getattribute__(self, name)\n        else:\n            return getattr(object.__getattribute__(self, '_mock'), name)\n\n    def __setattr__(self, name, value):\n        \"\"\" Before it is frozen, attributes are set on the FreezableMock\n        instance and added to the _frozen_set. Attributes in the _frozen_set\n        cannot be changed after the FreezableMock is frozen. In this case,\n        they are set on the underlying _mock.\n\n        In cases of return_value and side_effect, these attributes are always\n        passed through to the instance's _mock and added to the _frozen_set\n        before the object is frozen.\n\n        \"\"\"\n        if self._frozen:\n            if name in self._frozen_set:\n                raise AttributeError('Cannot change frozen attribute ' + name)\n            return setattr(self._mock, name, value)\n\n        if name != '_frozen_set':\n            self._frozen_set.add(name)\n\n        if name in ('return_value', 'side_effect'):\n            return setattr(self._mock, name, value)\n\n        return object.__setattr__(self, name, value)\n\n\ndef _create_get_utility_mock():\n    display = FreezableMock()\n    # Use pylint code for disable to keep on single line under line length limit\n    for name in interfaces.IDisplay.names():  # pylint: E1120\n        if name != 'notification':\n            frozen_mock = FreezableMock(frozen=True, func=_assert_valid_call)\n            setattr(display, name, frozen_mock)\n    display.freeze()\n    return FreezableMock(frozen=True, return_value=display)\n\n\ndef _create_get_utility_mock_with_stdout(stdout):\n    def _write_msg(message, *unused_args, **unused_kwargs):\n        \"\"\"Write to message to stdout.\n        \"\"\"\n        if message:\n            stdout.write(message)\n\n    def mock_method(*args, **kwargs):\n        \"\"\"\n        Mock function for IDisplay methods.\n        \"\"\"\n        _assert_valid_call(args, kwargs)\n        _write_msg(*args, **kwargs)\n\n\n    display = FreezableMock()\n    # Use pylint code for disable to keep on single line under line length limit\n    for name in interfaces.IDisplay.names():  # pylint: E1120\n        if name == 'notification':\n            frozen_mock = FreezableMock(frozen=True,\n                                        func=_write_msg)\n            setattr(display, name, frozen_mock)\n        else:\n            frozen_mock = FreezableMock(frozen=True,\n                                        func=mock_method)\n            setattr(display, name, frozen_mock)\n    display.freeze()\n\n    return FreezableMock(frozen=True, return_value=display)\n\n\ndef _assert_valid_call(*args, **kwargs):\n    assert_args = [args[0] if args else kwargs['message']]\n\n    assert_kwargs = {}\n    assert_kwargs['default'] = kwargs.get('default', None)\n    assert_kwargs['cli_flag'] = kwargs.get('cli_flag', None)\n    assert_kwargs['force_interactive'] = kwargs.get('force_interactive', False)\n\n    display_util.assert_valid_call(*assert_args, **assert_kwargs)\n\n\nclass TempDirTestCase(unittest.TestCase):\n    \"\"\"Base test class which sets up and tears down a temporary directory\"\"\"\n\n    def setUp(self):\n        \"\"\"Execute before test\"\"\"\n        self.tempdir = tempfile.mkdtemp()\n\n    def tearDown(self):\n        \"\"\"Execute after test\"\"\"\n        # Cleanup opened resources after a test. This is usually done through atexit handlers in\n        # Certbot, but during tests, atexit will not run registered functions before tearDown is\n        # called and instead will run them right before the entire test process exits.\n        # It is a problem on Windows, that does not accept to clean resources before closing them.\n        logging.shutdown()\n        # Remove logging handlers that have been closed so they won't be\n        # accidentally used in future tests.\n        logging.getLogger().handlers = []\n        util._release_locks()  # pylint: disable=protected-access\n\n        shutil.rmtree(self.tempdir)\n\n\nclass ConfigTestCase(TempDirTestCase):\n    \"\"\"Test class which sets up a NamespaceConfig object.\"\"\"\n    def setUp(self):\n        super(ConfigTestCase, self).setUp()\n        self.config = configuration.NamespaceConfig(\n            mock.MagicMock(**constants.CLI_DEFAULTS)\n        )\n        self.config.verb = \"certonly\"\n        self.config.config_dir = os.path.join(self.tempdir, 'config')\n        self.config.work_dir = os.path.join(self.tempdir, 'work')\n        self.config.logs_dir = os.path.join(self.tempdir, 'logs')\n        self.config.cert_path = constants.CLI_DEFAULTS['auth_cert_path']\n        self.config.fullchain_path = constants.CLI_DEFAULTS['auth_chain_path']\n        self.config.chain_path = constants.CLI_DEFAULTS['auth_chain_path']\n        self.config.server = \"https://example.com\"\n\n\ndef _handle_lock(event_in, event_out, path):\n    \"\"\"\n    Acquire a file lock on given path, then wait to release it. This worker is coordinated\n    using events to signal when the lock should be acquired and released.\n    :param multiprocessing.Event event_in: event object to signal when to release the lock\n    :param multiprocessing.Event event_out: event object to signal when the lock is acquired\n    :param path: the path to lock\n    \"\"\"\n    if os.path.isdir(path):\n        my_lock = lock.lock_dir(path)\n    else:\n        my_lock = lock.LockFile(path)\n    try:\n        event_out.set()\n        assert event_in.wait(timeout=20), 'Timeout while waiting to release the lock.'\n    finally:\n        my_lock.release()\n\n\ndef lock_and_call(callback, path_to_lock):\n    \"\"\"\n    Grab a lock on path_to_lock from a foreign process then execute the callback.\n    :param callable callback: object to call after acquiring the lock\n    :param str path_to_lock: path to file or directory to lock\n    \"\"\"\n    # Reload certbot.util module to reset internal _LOCKS dictionary.\n    reload_module(util)\n\n    emit_event = Event()\n    receive_event = Event()\n    process = Process(target=_handle_lock, args=(emit_event, receive_event, path_to_lock))\n    process.start()\n\n    # Wait confirmation that lock is acquired\n    assert receive_event.wait(timeout=10), 'Timeout while waiting to acquire the lock.'\n    # Execute the callback\n    callback()\n    # Trigger unlock from foreign process\n    emit_event.set()\n\n    # Wait for process termination\n    process.join(timeout=10)\n    assert process.exitcode == 0\n\n\ndef skip_on_windows(reason):\n    \"\"\"Decorator to skip permanently a test on Windows. A reason is required.\"\"\"\n    def wrapper(function):\n        \"\"\"Wrapped version\"\"\"\n        return unittest.skipIf(sys.platform == 'win32', reason)(function)\n    return wrapper\n\n\ndef temp_join(path):\n    \"\"\"\n    Return the given path joined to the tempdir path for the current platform\n    Eg.: 'cert' => /tmp/cert (Linux) or 'C:\\\\Users\\\\currentuser\\\\AppData\\\\Temp\\\\cert' (Windows)\n    \"\"\"\n    return os.path.join(tempfile.gettempdir(), path)\n"
  },
  {
    "path": "certbot/util.py",
    "content": "\"\"\"Utilities for all Certbot.\"\"\"\n# distutils.version under virtualenv confuses pylint\n# For more info, see: https://github.com/PyCQA/pylint/issues/73\nimport argparse\nimport atexit\nimport collections\nfrom collections import OrderedDict\nimport distutils.version\nimport errno\nimport logging\nimport platform\nimport re\nimport socket\nimport subprocess\nimport sys\n\nimport configargparse\nimport six\n\nfrom acme.magic_typing import Tuple\nfrom acme.magic_typing import Union\nfrom certbot import errors\nfrom certbot._internal import constants\nfrom certbot._internal import lock\nfrom certbot.compat import filesystem\nfrom certbot.compat import os\n\n_USE_DISTRO = sys.platform.startswith('linux')\nif _USE_DISTRO:\n    import distro\n\nlogger = logging.getLogger(__name__)\n\n\nKey = collections.namedtuple(\"Key\", \"file pem\")\n# Note: form is the type of data, \"pem\" or \"der\"\nCSR = collections.namedtuple(\"CSR\", \"file data form\")\n\n\n# ANSI SGR escape codes\n# Formats text as bold or with increased intensity\nANSI_SGR_BOLD = '\\033[1m'\n# Colors text red\nANSI_SGR_RED = \"\\033[31m\"\n# Resets output format\nANSI_SGR_RESET = \"\\033[0m\"\n\n\nPERM_ERR_FMT = os.linesep.join((\n    \"The following error was encountered:\", \"{0}\",\n    \"Either run as root, or set --config-dir, \"\n    \"--work-dir, and --logs-dir to writeable paths.\"))\n\n\n# Stores importing process ID to be used by atexit_register()\n_INITIAL_PID = os.getpid()\n# Maps paths to locked directories to their lock object. All locks in\n# the dict are attempted to be cleaned up at program exit. If the\n# program exits before the lock is cleaned up, it is automatically\n# released, but the file isn't deleted.\n_LOCKS = OrderedDict() # type: OrderedDict[str, lock.LockFile]\n\n\ndef run_script(params, log=logger.error):\n    \"\"\"Run the script with the given params.\n\n    :param list params: List of parameters to pass to Popen\n    :param callable log: Logger method to use for errors\n\n    \"\"\"\n    try:\n        proc = subprocess.Popen(params,\n                                stdout=subprocess.PIPE,\n                                stderr=subprocess.PIPE,\n                                universal_newlines=True)\n\n    except (OSError, ValueError):\n        msg = \"Unable to run the command: %s\" % \" \".join(params)\n        log(msg)\n        raise errors.SubprocessError(msg)\n\n    stdout, stderr = proc.communicate()\n\n    if proc.returncode != 0:\n        msg = \"Error while running %s.\\n%s\\n%s\" % (\n            \" \".join(params), stdout, stderr)\n        # Enter recovery routine...\n        log(msg)\n        raise errors.SubprocessError(msg)\n\n    return stdout, stderr\n\n\ndef exe_exists(exe):\n    \"\"\"Determine whether path/name refers to an executable.\n\n    :param str exe: Executable path or name\n\n    :returns: If exe is a valid executable\n    :rtype: bool\n\n    \"\"\"\n    path, _ = os.path.split(exe)\n    if path:\n        return filesystem.is_executable(exe)\n    for path in os.environ[\"PATH\"].split(os.pathsep):\n        if filesystem.is_executable(os.path.join(path, exe)):\n            return True\n\n    return False\n\n\ndef lock_dir_until_exit(dir_path):\n    \"\"\"Lock the directory at dir_path until program exit.\n\n    :param str dir_path: path to directory\n\n    :raises errors.LockError: if the lock is held by another process\n\n    \"\"\"\n    if not _LOCKS:  # this is the first lock to be released at exit\n        atexit_register(_release_locks)\n\n    if dir_path not in _LOCKS:\n        _LOCKS[dir_path] = lock.lock_dir(dir_path)\n\n\ndef _release_locks():\n    for dir_lock in six.itervalues(_LOCKS):\n        try:\n            dir_lock.release()\n        except:  # pylint: disable=bare-except\n            msg = 'Exception occurred releasing lock: {0!r}'.format(dir_lock)\n            logger.debug(msg, exc_info=True)\n    _LOCKS.clear()\n\n\ndef set_up_core_dir(directory, mode, strict):\n    \"\"\"Ensure directory exists with proper permissions and is locked.\n\n    :param str directory: Path to a directory.\n    :param int mode: Directory mode.\n    :param bool strict: require directory to be owned by current user\n\n    :raises .errors.LockError: if the directory cannot be locked\n    :raises .errors.Error: if the directory cannot be made or verified\n\n    \"\"\"\n    try:\n        make_or_verify_dir(directory, mode, strict)\n        lock_dir_until_exit(directory)\n    except OSError as error:\n        logger.debug(\"Exception was:\", exc_info=True)\n        raise errors.Error(PERM_ERR_FMT.format(error))\n\n\ndef make_or_verify_dir(directory, mode=0o755, strict=False):\n    \"\"\"Make sure directory exists with proper permissions.\n\n    :param str directory: Path to a directory.\n    :param int mode: Directory mode.\n    :param bool strict: require directory to be owned by current user\n\n    :raises .errors.Error: if a directory already exists,\n        but has wrong permissions or owner\n\n    :raises OSError: if invalid or inaccessible file names and\n        paths, or other arguments that have the correct type,\n        but are not accepted by the operating system.\n\n    \"\"\"\n    try:\n        filesystem.makedirs(directory, mode)\n    except OSError as exception:\n        if exception.errno == errno.EEXIST:\n            if strict and not filesystem.check_permissions(directory, mode):\n                raise errors.Error(\n                    \"%s exists, but it should be owned by current user with\"\n                    \" permissions %s\" % (directory, oct(mode)))\n        else:\n            raise\n\n\ndef safe_open(path, mode=\"w\", chmod=None):\n    \"\"\"Safely open a file.\n\n    :param str path: Path to a file.\n    :param str mode: Same os `mode` for `open`.\n    :param int chmod: Same as `mode` for `filesystem.open`, uses Python defaults\n        if ``None``.\n\n    \"\"\"\n    open_args = ()  # type: Union[Tuple[()], Tuple[int]]\n    if chmod is not None:\n        open_args = (chmod,)\n    fdopen_args = ()  # type: Union[Tuple[()], Tuple[int]]\n    fd = filesystem.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR, *open_args)\n    return os.fdopen(fd, mode, *fdopen_args)\n\n\ndef _unique_file(path, filename_pat, count, chmod, mode):\n    while True:\n        current_path = os.path.join(path, filename_pat(count))\n        try:\n            return safe_open(current_path, chmod=chmod, mode=mode),\\\n                os.path.abspath(current_path)\n        except OSError as err:\n            # \"File exists,\" is okay, try a different name.\n            if err.errno != errno.EEXIST:\n                raise\n        count += 1\n\n\ndef unique_file(path, chmod=0o777, mode=\"w\"):\n    \"\"\"Safely finds a unique file.\n\n    :param str path: path/filename.ext\n    :param int chmod: File mode\n    :param str mode: Open mode\n\n    :returns: tuple of file object and file name\n\n    \"\"\"\n    path, tail = os.path.split(path)\n    return _unique_file(\n        path, filename_pat=(lambda count: \"%04d_%s\" % (count, tail)),\n        count=0, chmod=chmod, mode=mode)\n\n\ndef unique_lineage_name(path, filename, chmod=0o644, mode=\"w\"):\n    \"\"\"Safely finds a unique file using lineage convention.\n\n    :param str path: directory path\n    :param str filename: proposed filename\n    :param int chmod: file mode\n    :param str mode: open mode\n\n    :returns: tuple of file object and file name (which may be modified\n        from the requested one by appending digits to ensure uniqueness)\n\n    :raises OSError: if writing files fails for an unanticipated reason,\n        such as a full disk or a lack of permission to write to\n        specified location.\n\n    \"\"\"\n    preferred_path = os.path.join(path, \"%s.conf\" % (filename))\n    try:\n        return safe_open(preferred_path, chmod=chmod), preferred_path\n    except OSError as err:\n        if err.errno != errno.EEXIST:\n            raise\n    return _unique_file(\n        path, filename_pat=(lambda count: \"%s-%04d.conf\" % (filename, count)),\n        count=1, chmod=chmod, mode=mode)\n\n\ndef safely_remove(path):\n    \"\"\"Remove a file that may not exist.\"\"\"\n    try:\n        os.remove(path)\n    except OSError as err:\n        if err.errno != errno.ENOENT:\n            raise\n\n\ndef get_filtered_names(all_names):\n    \"\"\"Removes names that aren't considered valid by Let's Encrypt.\n\n    :param set all_names: all names found in the configuration\n\n    :returns: all found names that are considered valid by LE\n    :rtype: set\n\n    \"\"\"\n    filtered_names = set()\n    for name in all_names:\n        try:\n            filtered_names.add(enforce_le_validity(name))\n        except errors.ConfigurationError:\n            logger.debug('Not suggesting name \"%s\"', name, exc_info=True)\n    return filtered_names\n\ndef get_os_info():\n    \"\"\"\n    Get OS name and version\n\n    :returns: (os_name, os_version)\n    :rtype: `tuple` of `str`\n    \"\"\"\n\n    return get_python_os_info(pretty=False)\n\ndef get_os_info_ua():\n    \"\"\"\n    Get OS name and version string for User Agent\n\n    :returns: os_ua\n    :rtype: `str`\n    \"\"\"\n    if _USE_DISTRO:\n        os_info = distro.name(pretty=True)\n\n    if not _USE_DISTRO or not os_info:\n        return \" \".join(get_python_os_info(pretty=True))\n    return os_info\n\ndef get_systemd_os_like():\n    \"\"\"\n    Get a list of strings that indicate the distribution likeness to\n    other distributions.\n\n    :returns: List of distribution acronyms\n    :rtype: `list` of `str`\n    \"\"\"\n\n    if _USE_DISTRO:\n        return distro.like().split(\" \")\n    return []\n\ndef get_var_from_file(varname, filepath=\"/etc/os-release\"):\n    \"\"\"\n    Get single value from a file formatted like systemd /etc/os-release\n\n    :param str varname: Name of variable to fetch\n    :param str filepath: File path of os-release file\n    :returns: requested value\n    :rtype: `str`\n    \"\"\"\n\n    var_string = varname+\"=\"\n    if not os.path.isfile(filepath):\n        return \"\"\n    with open(filepath, 'r') as fh:\n        contents = fh.readlines()\n\n    for line in contents:\n        if line.strip().startswith(var_string):\n            # Return the value of var, normalized\n            return _normalize_string(line.strip()[len(var_string):])\n    return \"\"\n\ndef _normalize_string(orig):\n    \"\"\"\n    Helper function for get_var_from_file() to remove quotes\n    and whitespaces\n    \"\"\"\n    return orig.replace('\"', '').replace(\"'\", \"\").strip()\n\ndef get_python_os_info(pretty=False):\n    \"\"\"\n    Get Operating System type/distribution and major version\n    using python platform module\n\n    :param bool pretty: If the returned OS name should be in longer (pretty) form\n\n    :returns: (os_name, os_version)\n    :rtype: `tuple` of `str`\n    \"\"\"\n    info = platform.system_alias(\n        platform.system(),\n        platform.release(),\n        platform.version()\n    )\n    os_type, os_ver, _ = info\n    os_type = os_type.lower()\n    if os_type.startswith('linux') and _USE_DISTRO:\n        info = distro.linux_distribution(pretty)\n        # On arch, distro.linux_distribution() is reportedly ('','',''),\n        # so handle it defensively\n        if info[0]:\n            os_type = info[0]\n        if info[1]:\n            os_ver = info[1]\n    elif os_type.startswith('darwin'):\n        try:\n            proc = subprocess.Popen(\n                [\"/usr/bin/sw_vers\", \"-productVersion\"],\n                stdout=subprocess.PIPE,\n                universal_newlines=True,\n            )\n        except OSError:\n            proc = subprocess.Popen(\n                [\"sw_vers\", \"-productVersion\"],\n                stdout=subprocess.PIPE,\n                universal_newlines=True,\n            )\n        os_ver = proc.communicate()[0].rstrip('\\n')\n    elif os_type.startswith('freebsd'):\n        # eg \"9.3-RC3-p1\"\n        os_ver = os_ver.partition(\"-\")[0]\n        os_ver = os_ver.partition(\".\")[0]\n    elif platform.win32_ver()[1]:\n        os_ver = platform.win32_ver()[1]\n    else:\n        # Cases known to fall here: Cygwin python\n        os_ver = ''\n    return os_type, os_ver\n\n# Just make sure we don't get pwned... Make sure that it also doesn't\n# start with a period or have two consecutive periods <- this needs to\n# be done in addition to the regex\nEMAIL_REGEX = re.compile(\"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+$\")\n\n\ndef safe_email(email):\n    \"\"\"Scrub email address before using it.\"\"\"\n    if EMAIL_REGEX.match(email) is not None:\n        return not email.startswith(\".\") and \"..\" not in email\n    logger.warning(\"Invalid email address: %s.\", email)\n    return False\n\n\nclass _ShowWarning(argparse.Action):\n    \"\"\"Action to log a warning when an argument is used.\"\"\"\n    def __call__(self, unused1, unused2, unused3, option_string=None):\n        logger.warning(\"Use of %s is deprecated.\", option_string)\n\n\ndef add_deprecated_argument(add_argument, argument_name, nargs):\n    \"\"\"Adds a deprecated argument with the name argument_name.\n\n    Deprecated arguments are not shown in the help. If they are used on\n    the command line, a warning is shown stating that the argument is\n    deprecated and no other action is taken.\n\n    :param callable add_argument: Function that adds arguments to an\n        argument parser/group.\n    :param str argument_name: Name of deprecated argument.\n    :param nargs: Value for nargs when adding the argument to argparse.\n\n    \"\"\"\n    if _ShowWarning not in configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE:\n        # In version 0.12.0 ACTION_TYPES_THAT_DONT_NEED_A_VALUE was\n        # changed from a set to a tuple.\n        if isinstance(configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE, set):\n            configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE.add(\n                _ShowWarning)\n        else:\n            configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE += (\n                _ShowWarning,)\n    add_argument(argument_name, action=_ShowWarning,\n                 help=argparse.SUPPRESS, nargs=nargs)\n\n\ndef enforce_le_validity(domain):\n    \"\"\"Checks that Let's Encrypt will consider domain to be valid.\n\n    :param str domain: FQDN to check\n    :type domain: `str` or `unicode`\n    :returns: The domain cast to `str`, with ASCII-only contents\n    :rtype: str\n    :raises ConfigurationError: for invalid domains and cases where Let's\n                                Encrypt currently will not issue certificates\n\n    \"\"\"\n    domain = enforce_domain_sanity(domain)\n    if not re.match(\"^[A-Za-z0-9.-]*$\", domain):\n        raise errors.ConfigurationError(\n            \"{0} contains an invalid character. \"\n            \"Valid characters are A-Z, a-z, 0-9, ., and -.\".format(domain))\n\n    labels = domain.split(\".\")\n    if len(labels) < 2:\n        raise errors.ConfigurationError(\n            \"{0} needs at least two labels\".format(domain))\n    for label in labels:\n        if label.startswith(\"-\"):\n            raise errors.ConfigurationError(\n                'label \"{0}\" in domain \"{1}\" cannot start with \"-\"'.format(\n                    label, domain))\n        if label.endswith(\"-\"):\n            raise errors.ConfigurationError(\n                'label \"{0}\" in domain \"{1}\" cannot end with \"-\"'.format(\n                    label, domain))\n    return domain\n\ndef enforce_domain_sanity(domain):\n    \"\"\"Method which validates domain value and errors out if\n    the requirements are not met.\n\n    :param domain: Domain to check\n    :type domain: `str` or `unicode`\n    :raises ConfigurationError: for invalid domains and cases where Let's\n                                Encrypt currently will not issue certificates\n\n    :returns: The domain cast to `str`, with ASCII-only contents\n    :rtype: str\n    \"\"\"\n    # Unicode\n    try:\n        if isinstance(domain, six.binary_type):\n            domain = domain.decode('utf-8')\n        domain.encode('ascii')\n    except UnicodeError:\n        raise errors.ConfigurationError(\"Non-ASCII domain names not supported. \"\n            \"To issue for an Internationalized Domain Name, use Punycode.\")\n\n    domain = domain.lower()\n\n    # Remove trailing dot\n    domain = domain[:-1] if domain.endswith(u'.') else domain\n\n    # Separately check for odd \"domains\" like \"http://example.com\" to fail\n    # fast and provide a clear error message\n    for scheme in [\"http\", \"https\"]:  # Other schemes seem unlikely\n        if domain.startswith(\"{0}://\".format(scheme)):\n            raise errors.ConfigurationError(\n                \"Requested name {0} appears to be a URL, not a FQDN. \"\n                \"Try again without the leading \\\"{1}://\\\".\".format(\n                    domain, scheme\n                )\n            )\n\n    # Explain separately that IP addresses aren't allowed (apart from not\n    # being FQDNs) because hope springs eternal concerning this point\n    try:\n        socket.inet_aton(domain)\n        raise errors.ConfigurationError(\n            \"Requested name {0} is an IP address. The Let's Encrypt \"\n            \"certificate authority will not issue certificates for a \"\n            \"bare IP address.\".format(domain))\n    except socket.error:\n        # It wasn't an IP address, so that's good\n        pass\n\n    # FQDN checks according to RFC 2181: domain name should be less than 255\n    # octets (inclusive). And each label is 1 - 63 octets (inclusive).\n    # https://tools.ietf.org/html/rfc2181#section-11\n    msg = \"Requested domain {0} is not a FQDN because\".format(domain)\n    if len(domain) > 255:\n        raise errors.ConfigurationError(\"{0} it is too long.\".format(msg))\n    labels = domain.split('.')\n    for l in labels:\n        if not l:\n            raise errors.ConfigurationError(\"{0} it contains an empty label.\".format(msg))\n        if len(l) > 63:\n            raise errors.ConfigurationError(\"{0} label {1} is too long.\".format(msg, l))\n\n    return domain\n\n\ndef is_wildcard_domain(domain):\n    \"\"\"\"Is domain a wildcard domain?\n\n    :param domain: domain to check\n    :type domain: `bytes` or `str` or `unicode`\n\n    :returns: True if domain is a wildcard, otherwise, False\n    :rtype: bool\n\n    \"\"\"\n    if isinstance(domain, six.text_type):\n        wildcard_marker = u\"*.\"\n    else:\n        wildcard_marker = b\"*.\"\n\n    return domain.startswith(wildcard_marker)\n\n\ndef get_strict_version(normalized):\n    \"\"\"Converts a normalized version to a strict version.\n\n    :param str normalized: normalized version string\n\n    :returns: An equivalent strict version\n    :rtype: distutils.version.StrictVersion\n\n    \"\"\"\n    # strict version ending with \"a\" and a number designates a pre-release\n    return distutils.version.StrictVersion(normalized.replace(\".dev\", \"a\"))\n\n\ndef is_staging(srv):\n    \"\"\"\n    Determine whether a given ACME server is a known test / staging server.\n\n    :param str srv: the URI for the ACME server\n    :returns: True iff srv is a known test / staging server\n    :rtype bool:\n    \"\"\"\n    return srv == constants.STAGING_URI or \"staging\" in srv\n\n\ndef atexit_register(func, *args, **kwargs):\n    \"\"\"Sets func to be called before the program exits.\n\n    Special care is taken to ensure func is only called when the process\n    that first imports this module exits rather than any child processes.\n\n    :param function func: function to be called in case of an error\n\n    \"\"\"\n    atexit.register(_atexit_call, func, *args, **kwargs)\n\n\ndef _atexit_call(func, *args, **kwargs):\n    if _INITIAL_PID == os.getpid():\n        func(*args, **kwargs)\n"
  },
  {
    "path": "certbot.egg-info/PKG-INFO",
    "content": "Metadata-Version: 2.1\nName: certbot\nVersion: 1.3.0\nSummary: ACME client\nHome-page: https://github.com/letsencrypt/letsencrypt\nAuthor: Certbot Project\nAuthor-email: client-dev@letsencrypt.org\nLicense: Apache License 2.0\nDescription: .. This file contains a series of comments that are used to include sections of this README in other files. Do not modify these comments unless you know what you are doing. tag:intro-begin\n        \n        Certbot is part of EFF’s effort to encrypt the entire Internet. Secure communication over the Web relies on HTTPS, which requires the use of a digital certificate that lets browsers verify the identity of web servers (e.g., is that really google.com?). Web servers obtain their certificates from trusted third parties called certificate authorities (CAs). Certbot is an easy-to-use client that fetches a certificate from Let’s Encrypt—an open certificate authority launched by the EFF, Mozilla, and others—and deploys it to a web server.\n        \n        Anyone who has gone through the trouble of setting up a secure website knows what a hassle getting and maintaining a certificate is. Certbot and Let’s Encrypt can automate away the pain and let you turn on and manage HTTPS with simple commands. Using Certbot and Let's Encrypt is free, so there’s no need to arrange payment.\n        \n        How you use Certbot depends on the configuration of your web server. The best way to get started is to use our `interactive guide <https://certbot.eff.org>`_. It generates instructions based on your configuration settings. In most cases, you’ll need `root or administrator access <https://certbot.eff.org/faq/#does-certbot-require-root-administrator-privileges>`_ to your web server to run Certbot.\n        \n        Certbot is meant to be run directly on your web server, not on your personal computer. If you’re using a hosted service and don’t have direct access to your web server, you might not be able to use Certbot. Check with your hosting provider for documentation about uploading certificates or using certificates issued by Let’s Encrypt.\n        \n        Certbot is a fully-featured, extensible client for the Let's\n        Encrypt CA (or any other CA that speaks the `ACME\n        <https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md>`_\n        protocol) that can automate the tasks of obtaining certificates and\n        configuring webservers to use them. This client runs on Unix-based operating\n        systems.\n        \n        To see the changes made to Certbot between versions please refer to our\n        `changelog <https://github.com/certbot/certbot/blob/master/certbot/CHANGELOG.md>`_.\n        \n        Until May 2016, Certbot was named simply ``letsencrypt`` or ``letsencrypt-auto``,\n        depending on install method. Instructions on the Internet, and some pieces of the\n        software, may still refer to this older name.\n        \n        Contributing\n        ------------\n        \n        If you'd like to contribute to this project please read `Developer Guide\n        <https://certbot.eff.org/docs/contributing.html>`_.\n        \n        This project is governed by `EFF's Public Projects Code of Conduct <https://www.eff.org/pages/eppcode>`_.\n        \n        .. _installation:\n        \n        How to run the client\n        ---------------------\n        \n        The easiest way to install and run Certbot is by visiting `certbot.eff.org`_,\n        where you can find the correct instructions for many web server and OS\n        combinations.  For more information, see `Get Certbot\n        <https://certbot.eff.org/docs/install.html>`_.\n        \n        .. _certbot.eff.org: https://certbot.eff.org/\n        \n        Understanding the client in more depth\n        --------------------------------------\n        \n        To understand what the client is doing in detail, it's important to\n        understand the way it uses plugins.  Please see the `explanation of\n        plugins <https://certbot.eff.org/docs/using.html#plugins>`_ in\n        the User Guide.\n        \n        Links\n        =====\n        \n        .. Do not modify this comment unless you know what you're doing. tag:links-begin\n        \n        Documentation: https://certbot.eff.org/docs\n        \n        Software project: https://github.com/certbot/certbot\n        \n        Notes for developers: https://certbot.eff.org/docs/contributing.html\n        \n        Main Website: https://certbot.eff.org\n        \n        Let's Encrypt Website: https://letsencrypt.org\n        \n        Community: https://community.letsencrypt.org\n        \n        ACME spec: http://ietf-wg-acme.github.io/acme/\n        \n        ACME working area in github: https://github.com/ietf-wg-acme/acme\n        \n        |build-status| |container|\n        \n        .. |build-status| image:: https://travis-ci.com/certbot/certbot.svg?branch=master\n           :target: https://travis-ci.com/certbot/certbot\n           :alt: Travis CI status\n        \n        .. |container| image:: https://quay.io/repository/letsencrypt/letsencrypt/status\n           :target: https://quay.io/repository/letsencrypt/letsencrypt\n           :alt: Docker Repository on Quay.io\n        \n        .. Do not modify this comment unless you know what you're doing. tag:links-end\n        \n        System Requirements\n        ===================\n        \n        See https://certbot.eff.org/docs/install.html#system-requirements.\n        \n        .. Do not modify this comment unless you know what you're doing. tag:intro-end\n        \n        .. Do not modify this comment unless you know what you're doing. tag:features-begin\n        \n        Current Features\n        =====================\n        \n        * Supports multiple web servers:\n        \n          - apache/2.x\n          - nginx/0.8.48+\n          - webroot (adds files to webroot directories in order to prove control of\n            domains and obtain certs)\n          - standalone (runs its own simple webserver to prove you control a domain)\n          - other server software via `third party plugins <https://certbot.eff.org/docs/using.html#third-party-plugins>`_\n        \n        * The private key is generated locally on your system.\n        * Can talk to the Let's Encrypt CA or optionally to other ACME\n          compliant services.\n        * Can get domain-validated (DV) certificates.\n        * Can revoke certificates.\n        * Adjustable RSA key bit-length (2048 (default), 4096, ...).\n        * Can optionally install a http -> https redirect, so your site effectively\n          runs https only (Apache only)\n        * Fully automated.\n        * Configuration changes are logged and can be reverted.\n        * Supports an interactive text UI, or can be driven entirely from the\n          command line.\n        * Free and Open Source Software, made with Python.\n        \n        .. Do not modify this comment unless you know what you're doing. tag:features-end\n        \n        For extensive documentation on using and contributing to Certbot, go to https://certbot.eff.org/docs. If you would like to contribute to the project or run the latest code from git, you should read our `developer guide <https://certbot.eff.org/docs/contributing.html>`_.\n        \nPlatform: UNKNOWN\nClassifier: Development Status :: 5 - Production/Stable\nClassifier: Environment :: Console\nClassifier: Environment :: Console :: Curses\nClassifier: Intended Audience :: System Administrators\nClassifier: License :: OSI Approved :: Apache Software License\nClassifier: Operating System :: POSIX :: Linux\nClassifier: Programming Language :: Python\nClassifier: Programming Language :: Python :: 2\nClassifier: Programming Language :: Python :: 2.7\nClassifier: Programming Language :: Python :: 3\nClassifier: Programming Language :: Python :: 3.5\nClassifier: Programming Language :: Python :: 3.6\nClassifier: Programming Language :: Python :: 3.7\nClassifier: Programming Language :: Python :: 3.8\nClassifier: Topic :: Internet :: WWW/HTTP\nClassifier: Topic :: Security\nClassifier: Topic :: System :: Installation/Setup\nClassifier: Topic :: System :: Networking\nClassifier: Topic :: System :: Systems Administration\nClassifier: Topic :: Utilities\nRequires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*\nProvides-Extra: dev3\nProvides-Extra: docs\nProvides-Extra: dev\n"
  },
  {
    "path": "certbot.egg-info/SOURCES.txt",
    "content": "CHANGELOG.md\nLICENSE.txt\nMANIFEST.in\nREADME.rst\nsetup.cfg\nsetup.py\ncertbot/__init__.py\ncertbot/achallenges.py\ncertbot/crypto_util.py\ncertbot/errors.py\ncertbot/interfaces.py\ncertbot/main.py\ncertbot/ocsp.py\ncertbot/reverter.py\ncertbot/ssl-dhparams.pem\ncertbot/util.py\ncertbot.egg-info/PKG-INFO\ncertbot.egg-info/SOURCES.txt\ncertbot.egg-info/dependency_links.txt\ncertbot.egg-info/entry_points.txt\ncertbot.egg-info/requires.txt\ncertbot.egg-info/top_level.txt\ncertbot/_internal/__init__.py\ncertbot/_internal/account.py\ncertbot/_internal/auth_handler.py\ncertbot/_internal/cert_manager.py\ncertbot/_internal/client.py\ncertbot/_internal/configuration.py\ncertbot/_internal/constants.py\ncertbot/_internal/eff.py\ncertbot/_internal/error_handler.py\ncertbot/_internal/hooks.py\ncertbot/_internal/lock.py\ncertbot/_internal/log.py\ncertbot/_internal/main.py\ncertbot/_internal/renewal.py\ncertbot/_internal/reporter.py\ncertbot/_internal/storage.py\ncertbot/_internal/updater.py\ncertbot/_internal/cli/__init__.py\ncertbot/_internal/cli/cli_constants.py\ncertbot/_internal/cli/cli_utils.py\ncertbot/_internal/cli/group_adder.py\ncertbot/_internal/cli/helpful.py\ncertbot/_internal/cli/paths_parser.py\ncertbot/_internal/cli/plugins_parsing.py\ncertbot/_internal/cli/report_config_interaction.py\ncertbot/_internal/cli/subparsers.py\ncertbot/_internal/cli/verb_help.py\ncertbot/_internal/display/__init__.py\ncertbot/_internal/display/completer.py\ncertbot/_internal/display/dummy_readline.py\ncertbot/_internal/display/enhancements.py\ncertbot/_internal/plugins/__init__.py\ncertbot/_internal/plugins/disco.py\ncertbot/_internal/plugins/manual.py\ncertbot/_internal/plugins/null.py\ncertbot/_internal/plugins/selection.py\ncertbot/_internal/plugins/standalone.py\ncertbot/_internal/plugins/webroot.py\ncertbot/compat/__init__.py\ncertbot/compat/_path.py\ncertbot/compat/filesystem.py\ncertbot/compat/misc.py\ncertbot/compat/os.py\ncertbot/display/__init__.py\ncertbot/display/ops.py\ncertbot/display/util.py\ncertbot/plugins/__init__.py\ncertbot/plugins/common.py\ncertbot/plugins/dns_common.py\ncertbot/plugins/dns_common_lexicon.py\ncertbot/plugins/dns_test_common.py\ncertbot/plugins/dns_test_common_lexicon.py\ncertbot/plugins/enhancements.py\ncertbot/plugins/storage.py\ncertbot/plugins/util.py\ncertbot/tests/__init__.py\ncertbot/tests/acme_util.py\ncertbot/tests/util.py\ncertbot/tests/testdata/README\ncertbot/tests/testdata/cert-5sans_512.pem\ncertbot/tests/testdata/cert-nosans_nistp256.pem\ncertbot/tests/testdata/cert-san_512.pem\ncertbot/tests/testdata/cert_2048.pem\ncertbot/tests/testdata/cert_512.pem\ncertbot/tests/testdata/cert_512_bad.pem\ncertbot/tests/testdata/cert_fullchain_2048.pem\ncertbot/tests/testdata/cli.ini\ncertbot/tests/testdata/csr-6sans_512.conf\ncertbot/tests/testdata/csr-6sans_512.pem\ncertbot/tests/testdata/csr-nonames_512.pem\ncertbot/tests/testdata/csr-nosans_512.conf\ncertbot/tests/testdata/csr-nosans_512.pem\ncertbot/tests/testdata/csr-nosans_nistp256.pem\ncertbot/tests/testdata/csr-san_512.pem\ncertbot/tests/testdata/csr_512.der\ncertbot/tests/testdata/csr_512.pem\ncertbot/tests/testdata/nistp256_key.pem\ncertbot/tests/testdata/ocsp_certificate.pem\ncertbot/tests/testdata/ocsp_issuer_certificate.pem\ncertbot/tests/testdata/ocsp_responder_certificate.pem\ncertbot/tests/testdata/os-release\ncertbot/tests/testdata/rsa2048_key.pem\ncertbot/tests/testdata/rsa256_key.pem\ncertbot/tests/testdata/rsa512_key.pem\ncertbot/tests/testdata/sample-renewal-ancient.conf\ncertbot/tests/testdata/sample-renewal.conf\ncertbot/tests/testdata/webrootconftest.ini\ncertbot/tests/testdata/sample-archive/cert1.pem\ncertbot/tests/testdata/sample-archive/chain1.pem\ncertbot/tests/testdata/sample-archive/fullchain1.pem\ncertbot/tests/testdata/sample-archive/privkey1.pem\ndocs/.gitignore\ndocs/Makefile\ndocs/api.rst\ndocs/challenges.rst\ndocs/ciphers.rst\ndocs/cli-help.txt\ndocs/compatibility.rst\ndocs/conf.py\ndocs/contributing.rst\ndocs/index.rst\ndocs/install.rst\ndocs/intro.rst\ndocs/make.bat\ndocs/packaging.rst\ndocs/resources.rst\ndocs/using.rst\ndocs/what.rst\ndocs/_static/.gitignore\ndocs/_templates/footer.html\ndocs/api/certbot.achallenges.rst\ndocs/api/certbot.compat.filesystem.rst\ndocs/api/certbot.compat.misc.rst\ndocs/api/certbot.compat.os.rst\ndocs/api/certbot.compat.rst\ndocs/api/certbot.crypto_util.rst\ndocs/api/certbot.display.ops.rst\ndocs/api/certbot.display.rst\ndocs/api/certbot.display.util.rst\ndocs/api/certbot.errors.rst\ndocs/api/certbot.interfaces.rst\ndocs/api/certbot.main.rst\ndocs/api/certbot.ocsp.rst\ndocs/api/certbot.plugins.common.rst\ndocs/api/certbot.plugins.dns_common.rst\ndocs/api/certbot.plugins.dns_common_lexicon.rst\ndocs/api/certbot.plugins.dns_test_common.rst\ndocs/api/certbot.plugins.dns_test_common_lexicon.rst\ndocs/api/certbot.plugins.enhancements.rst\ndocs/api/certbot.plugins.rst\ndocs/api/certbot.plugins.storage.rst\ndocs/api/certbot.plugins.util.rst\ndocs/api/certbot.reverter.rst\ndocs/api/certbot.rst\ndocs/api/certbot.tests.acme_util.rst\ndocs/api/certbot.tests.rst\ndocs/api/certbot.tests.util.rst\ndocs/api/certbot.util.rst\ndocs/man/certbot.rst\nexamples/.gitignore\nexamples/cli.ini\nexamples/dev-cli.ini\nexamples/generate-csr.sh\nexamples/openssl.cnf\nexamples/plugins/certbot_example_plugins.py\nexamples/plugins/setup.py\ntests/account_test.py\ntests/auth_handler_test.py\ntests/cert_manager_test.py\ntests/cli_test.py\ntests/client_test.py\ntests/configuration_test.py\ntests/crypto_util_test.py\ntests/eff_test.py\ntests/error_handler_test.py\ntests/errors_test.py\ntests/helpful_test.py\ntests/hook_test.py\ntests/lock_test.py\ntests/log_test.py\ntests/main_test.py\ntests/ocsp_test.py\ntests/renewal_test.py\ntests/renewupdater_test.py\ntests/reporter_test.py\ntests/reverter_test.py\ntests/storage_test.py\ntests/util_test.py\ntests/compat/__init__.py\ntests/compat/filesystem_test.py\ntests/compat/os_test.py\ntests/display/__init__.py\ntests/display/completer_test.py\ntests/display/enhancements_test.py\ntests/display/ops_test.py\ntests/display/util_test.py\ntests/plugins/__init__.py\ntests/plugins/common_test.py\ntests/plugins/disco_test.py\ntests/plugins/dns_common_lexicon_test.py\ntests/plugins/dns_common_test.py\ntests/plugins/enhancements_test.py\ntests/plugins/manual_test.py\ntests/plugins/null_test.py\ntests/plugins/selection_test.py\ntests/plugins/standalone_test.py\ntests/plugins/storage_test.py\ntests/plugins/util_test.py\ntests/plugins/webroot_test.py"
  },
  {
    "path": "certbot.egg-info/dependency_links.txt",
    "content": "\n"
  },
  {
    "path": "certbot.egg-info/entry_points.txt",
    "content": "[certbot.plugins]\nmanual = certbot._internal.plugins.manual:Authenticator\nnull = certbot._internal.plugins.null:Installer\nstandalone = certbot._internal.plugins.standalone:Authenticator\nwebroot = certbot._internal.plugins.webroot:Authenticator\n\n[console_scripts]\ncertbot = certbot.main:main\n\n"
  },
  {
    "path": "certbot.egg-info/requires.txt",
    "content": "acme>=0.40.0\nConfigArgParse>=0.9.3\nconfigobj\ncryptography>=1.2.3\ndistro>=1.0.1\njosepy>=1.1.0\nmock\nparsedatetime>=1.3\npyrfc3339\npytz\nsetuptools\nzope.component\nzope.interface\n\n[:sys_platform == \"win32\"]\npywin32>=227\n\n[dev]\ncoverage\nipdb\npytest\npytest-cov\npytest-xdist\ntox\ntwine\nwheel\n\n[dev3]\nastroid\nmypy\npylint\n\n[docs]\nrepoze.sphinx.autointerface\nSphinx>=1.2\nsphinx_rtd_theme\n"
  },
  {
    "path": "certbot.egg-info/top_level.txt",
    "content": "certbot\n"
  },
  {
    "path": "debian/README.source",
    "content": "Because of version dependencies, the debian/control file must be\ngenerated dynamically with each upstream version update. clean is\noverridden to ensure that this is done properly; make sure to run\n\"debian/rules clean\" prior to assembling the source package.\n"
  },
  {
    "path": "debian/certbot.cron.d",
    "content": "# /etc/cron.d/certbot: crontab entries for the certbot package\n#\n# Upstream recommends attempting renewal twice a day\n#\n# Eventually, this will be an opportunity to validate certificates\n# haven't been revoked, etc.  Renewal will only occur if expiration\n# is within 30 days.\n#\n# Important Note!  This cronjob will NOT be executed if you are\n# running systemd as your init system.  If you are running systemd,\n# the cronjob.timer function takes precedence over this cronjob.  For\n# more details, see the systemd.timer manpage, or use systemctl show\n# certbot.timer.\nSHELL=/bin/sh\nPATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin\n\n0 */12 * * * root test -x /usr/bin/certbot -a \\! -d /run/systemd/system && perl -e 'sleep int(rand(43200))' && certbot -q renew\n"
  },
  {
    "path": "debian/certbot.docs",
    "content": "README.rst\n"
  },
  {
    "path": "debian/certbot.links",
    "content": "usr/bin/certbot usr/bin/letsencrypt\nusr/share/man/man1/certbot.1.gz usr/share/man/man1/letsencrypt.1.gz\n"
  },
  {
    "path": "debian/certbot.logrotate",
    "content": "/var/log/letsencrypt/*.log {\n    rotate 12\n    weekly\n    compress\n    missingok\n}"
  },
  {
    "path": "debian/certbot.manpages",
    "content": "build/man/*.1\n"
  },
  {
    "path": "debian/certbot.postrm",
    "content": "#!/bin/sh\n# postrm script for letsencrypt\n#\n# see: dh_installdeb(1)\n\nset -e\n\n# summary of how this script can be called:\n#        * <postrm> `remove'\n#        * <postrm> `purge'\n#        * <old-postrm> `upgrade' <new-version>\n#        * <new-postrm> `failed-upgrade' <old-version>\n#        * <new-postrm> `abort-install'\n#        * <new-postrm> `abort-install' <old-version>\n#        * <new-postrm> `abort-upgrade' <old-version>\n#        * <disappearer's-postrm> `disappear' <overwriter>\n#          <overwriter-version>\n# for details, see https://www.debian.org/doc/debian-policy/ or\n# the debian-policy package\n\n\ncase \"$1\" in\n    purge)\n        rm -rf /etc/letsencrypt /var/log/letsencrypt\n    ;;\n\n    remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)\n    ;;\n\n    *)\n        echo \"postrm called with unknown argument \\`$1'\" >&2\n        exit 1\n    ;;\nesac\n\n# dh_installdeb will replace this with shell code automatically\n# generated by other debhelper scripts.\n\n#DEBHELPER#\n\nexit 0\n"
  },
  {
    "path": "debian/certbot.service",
    "content": "[Unit]\nDescription=Certbot\nDocumentation=file:///usr/share/doc/python-certbot-doc/html/index.html\nDocumentation=https://letsencrypt.readthedocs.io/en/latest/\n[Service]\nType=oneshot\nExecStart=/usr/bin/certbot -q renew\nPrivateTmp=true\n"
  },
  {
    "path": "debian/certbot.timer",
    "content": "[Unit]\nDescription=Run certbot twice daily\n\n[Timer]\nOnCalendar=*-*-* 00,12:00:00\nRandomizedDelaySec=43200\nPersistent=true\n\n[Install]\nWantedBy=timers.target\n"
  },
  {
    "path": "debian/changelog",
    "content": "python-certbot (1.3.0-1) unstable; urgency=medium\n\n  * New upstream version 1.3.0\n  * Enable end-to-end testing 🎉\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Thu, 26 Mar 2020 21:07:58 -0400\n\npython-certbot (1.1.0-1) unstable; urgency=medium\n\n  * New upstream version 1.1.0\n  * Drop inactive Uploaders\n  * Bump S-V; no changes needed\n  * Cleanup unnecessary version deps (cme fix)\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Thu, 23 Jan 2020 23:02:48 -0500\n\npython-certbot (0.40.0-1) unstable; urgency=medium\n\n  * New upstream version 0.40.0\n  * Switch to debian-compat instead of d/compat\n  * Bump python-acme version to 0.40.0\n  * Bump S-V; no changes needed\n  * Drop unnecessary lintian override.\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Tue, 05 Nov 2019 19:40:23 -0500\n\npython-certbot (0.39.0-1) unstable; urgency=medium\n\n  * New upstream version 0.39.0\n  * Add dep on python3-distro\n  * Drop transitional dummy package (Closes: #940765)\n  * Fix duplicate calls to install systemd timer (Closes: #924262)\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Tue, 01 Oct 2019 21:54:04 -0400\n\npython-certbot (0.36.0-1) unstable; urgency=medium\n\n  * New upstream version 0.36.0\n  * Bump dh, compat to 12\n  * Bump S-V; no changes needed\n  * Add lintian override for one-shot systemd service.\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Thu, 11 Jul 2019 16:36:58 -0400\n\npython-certbot (0.35.1-1) unstable; urgency=medium\n\n  * New upstream version 0.35.1\n  * Invoke pytest through setup.py.\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Tue, 09 Jul 2019 18:31:34 -0400\n\npython-certbot (0.31.0-1) unstable; urgency=medium\n\n  * New upstream version 0.31.0\n  * Bump deps changed by upstream.\n  * Bump S-V; no changes needed.\n  * Refresh patches.\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Sat, 09 Feb 2019 19:39:59 -0500\n\npython-certbot (0.28.0-2) unstable; urgency=medium\n\n  * Remove unnecessary letsencrypt.postrm (Closes: #921423)\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Tue, 05 Feb 2019 22:15:02 -0500\n\npython-certbot (0.28.0-1) unstable; urgency=medium\n\n  * Add systemd warning to crontab file (Closes: #908841)\n  * New upstream version 0.28.0\n  * Refresh patch affected by unrelated changes\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Wed, 07 Nov 2018 18:19:31 -0500\n\npython-certbot (0.27.0-1) unstable; urgency=medium\n\n  * New upstream version 0.27.0\n  * Refresh patch after upstream migration to codecov\n  * Bump python-sphinx requirement defensively; bump S-V with no changes\n  * Bump dep on python-acme to 0.26.0~\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Wed, 05 Sep 2018 20:29:44 -0400\n\npython-certbot (0.26.1-1) unstable; urgency=medium\n\n  * New upstream release.\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Wed, 18 Jul 2018 01:10:01 -0400\n\npython-certbot (0.26.0-1) unstable; urgency=medium\n\n  * New upstream version 0.26.0\n  * Bump S-V; add R-R-R: no\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Thu, 12 Jul 2018 22:39:22 -0400\n\npython-certbot (0.25.0-1) unstable; urgency=medium\n\n  * New upstream version 0.25.0\n  * Bump python-acme dep version.\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Mon, 11 Jun 2018 22:05:17 -0400\n\npython-certbot (0.24.0-2) unstable; urgency=medium\n\n  * Update team email address. (Closes: #899858)\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Mon, 28 May 2018 19:03:01 -0400\n\npython-certbot (0.24.0-1) unstable; urgency=medium\n\n  * Add OR to dep on python-distutils for stretch-bpo\n  * New upstream version 0.24.0\n  * Bump version dep on python3-acme\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Thu, 03 May 2018 19:43:04 -0400\n\npython-certbot (0.23.0-1) unstable; urgency=medium\n\n  * New upstream release.\n  * Add testdata back in to prevent test failure in RDeps. (Closes: #894025)\n  * Bump S-V; no changes needed.\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Fri, 06 Apr 2018 23:23:17 -0400\n\npython-certbot (0.22.2-2) unstable; urgency=medium\n\n  * Change the way we remove testdata for better downstream support\n  * Add dep on python3-distutils (Closes: #893775)\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Thu, 22 Mar 2018 18:53:32 -0400\n\npython-certbot (0.22.2-1) unstable; urgency=medium\n\n  * New upstream release.\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Wed, 21 Mar 2018 00:54:31 -0400\n\npython-certbot (0.22.0-1) unstable; urgency=medium\n\n  * New upstream release -- now with wildcards!\n  * Break the strict dependency relationship between certbot packages.\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Thu, 15 Mar 2018 20:22:37 -0400\n\npython-certbot (0.21.1-1) unstable; urgency=high\n\n  * New upstream release.\n  * Move d/copyright format to HTTPS\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Tue, 30 Jan 2018 21:02:48 -0500\n\npython-certbot (0.20.0-3) unstable; urgency=medium\n\n  * Setup logrotation for certbot log files. (Closes: #873581, #881176)\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Tue, 09 Jan 2018 22:03:27 -0500\n\npython-certbot (0.20.0-2) unstable; urgency=low\n\n  * Add additional Breaks on py2 variants of libs.\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Sun, 07 Jan 2018 22:58:45 -0500\n\npython-certbot (0.20.0-1) unstable; urgency=low\n\n  * New upstream release.\n  * Switch to python3!\n  * Update to debhelper 11, bump S-V.\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Fri, 05 Jan 2018 21:49:26 -0500\n\npython-certbot (0.19.0-1) unstable; urgency=medium\n\n  * New upstream release. (Closes: #838548)\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Wed, 04 Oct 2017 19:39:01 -0400\n\npython-certbot (0.18.2-1) unstable; urgency=medium\n\n  * New upstream release.\n  * Bump S-V; no changes needed.\n  * Switch from python-sphinx to python3-sphinx\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Sun, 01 Oct 2017 18:44:11 -0400\n\npython-certbot (0.17.0-2) unstable; urgency=high\n\n  * Revert d/rules for systemd cleanup. (Closes: #872090)\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Mon, 14 Aug 2017 22:28:10 -0400\n\npython-certbot (0.17.0-1) unstable; urgency=medium\n\n  [ Mattia Rizzolo ]\n  * d/control: rename git repository to python-certbot too\n\n  [ Harlan Lieberman-Berg ]\n  * New upstream version 0.17.0\n  * Bump S-V to 4.0.1, changing Priority to optional.\n  * Bump B-D on python-cryptography\n  * Add very basic autopkgtest.\n  * Refresh patches.\n  * Fix merge failure.\n  * Tweak d/rules for systemd cleanup, raise compat to 10.\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Sun, 06 Aug 2017 17:49:12 -0400\n\npython-certbot (0.14.2-1) experimental; urgency=medium\n\n  * Team upload.\n  * New upstream release.\n\n -- Robie Basak <robie.basak@ubuntu.com>  Fri, 26 May 2017 12:51:44 +0100\n\npython-certbot (0.12.0-1) experimental; urgency=medium\n\n  * New upstream release.\n  * Add python-ipdb as build dependency.\n  * Drop unnecessary dependency on dh-systemd (Closes: #856239)\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Tue, 21 Mar 2017 23:10:14 -0400\n\npython-certbot (0.11.1-1) unstable; urgency=medium\n\n  * New upstream release.\n  * Add .pc to gitignore\n  * Drop python-psutil dep no longer needed\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Sun, 19 Feb 2017 14:05:17 -0500\n\npython-certbot (0.10.2-1) unstable; urgency=medium\n\n  * New upstream release.\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Thu, 26 Jan 2017 01:11:55 -0500\n\npython-certbot (0.10.1-1) unstable; urgency=medium\n\n  [ Ondřej Surý ]\n  * Tweaks to B-D, Depends for backporting to Trusty. (Closes: #844687)\n\n  [ Harlan Lieberman-Berg ]\n  * New upstream version.\n  * Update dependencies dropped by upstream, misc. cleanup\n  * Remove ugly PYTHONPATH hack no longer needed, misc. cleanup\n  * Pre-create /etc/letsencrypt (Closes: #845792)\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Fri, 20 Jan 2017 22:19:59 -0500\n\npython-certbot (0.9.3-1) unstable; urgency=medium\n\n  * New upstream version. (Closes: #840995)\n  * Add python-psutil to Recommends.\n  * Refresh patches.\n  * Add support for systemd timers (Closes: #833453)\n  * Add Documentation to systemd unit service\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Sat, 22 Oct 2016 22:03:06 -0400\n\npython-certbot (0.8.1-3) unstable; urgency=medium\n\n  * Prevent network access at build time. (Closes: #834833)\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Fri, 02 Sep 2016 18:31:14 -0400\n\npython-certbot (0.8.1-2) unstable; urgency=medium\n\n  * Ship new crontab, fixing errors when using apache plugin\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Thu, 07 Jul 2016 21:11:59 -0400\n\npython-certbot (0.8.1-1) unstable; urgency=medium\n\n  * New upstream release.\n  * Add pydist-overrides to copy version deps.\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Sun, 26 Jun 2016 20:55:52 -0400\n\npython-certbot (0.8.0-1) unstable; urgency=high\n\n  * New upstream release. (Closes: #824452)\n  * Bump python-psutil dependency.\n  * Add Suggests for -doc. (Closes: #825598)\n  * Fix cron file to run only twice. (Closes: #825732)\n  * Add transitional dummy for letsencrypt. (Closes: #826010)\n  * Remove manpage patch no longer needed.\n  * Refresh patches.\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Thu, 02 Jun 2016 20:09:02 -0400\n\npython-certbot (0.6.0-2) unstable; urgency=medium\n\n  * Fix major bug in crontab file. (Closes: #824709)\n  * Fix homepage to point to new location. (Thanks, Axel!)\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Wed, 18 May 2016 21:06:12 -0400\n\npython-certbot (0.6.0-1) unstable; urgency=medium\n\n  * New upstream release.\n  * Migrate package to the certbot name.\n  * Require parsedatetime >= 1.3 (Closes: #818587)\n  * Update d/watch with new name.\n  * Refresh patches.\n  * Add cronjob to automatically renew certificates.\n  * Ship symlink from old letsencrypt binary.\n  * Switch Breaks for -apache to the certbot name.\n  * Reword short descriptions\n  * Bump S-V; no changes needed.\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Sat, 14 May 2016 16:45:44 -0400\n\npython-letsencrypt (0.5.0-1) unstable; urgency=medium\n\n  * New upstream release. (Closes: #820721)\n  * Change my email address\n  * Add version constraint on python-setuptools\n  * Clean up after build properly (Closes: #818667)\n\n -- Harlan Lieberman-Berg <hlieberman@debian.org>  Wed, 13 Apr 2016 19:04:44 -0400\n\npython-letsencrypt (0.4.1-1) unstable; urgency=medium\n\n  * New upstream release.\n  * Match version restriction on python-parsedatetime\n  * Bump S-V; no changes needed\n  * Fix Vcs-git URL\n\n -- Harlan Lieberman-Berg <hlieberman@setec.io>  Mon, 29 Feb 2016 21:22:32 -0500\n\npython-letsencrypt (0.4.0-1) unstable; urgency=medium\n\n  * New upstream version. (Closes: #813689)\n  * Ship the upstream tests and testdata. (Closes: #814010)\n\n -- Harlan Lieberman-Berg <hlieberman@setec.io>  Thu, 11 Feb 2016 20:54:43 -0500\n\npython-letsencrypt (0.3.0-1) unstable; urgency=medium\n\n  * New upstream version.  (Closes: #812223)\n  * Switch Vcs-git to https.\n\n -- Harlan Lieberman-Berg <hlieberman@setec.io>  Thu, 28 Jan 2016 19:09:44 -0500\n\npython-letsencrypt (0.2.0-1) unstable; urgency=medium\n\n  * New upstream version.\n  * Delete config and log directories on purge (closes: #809355)\n  * Drop priority to extra due to transitive dep on python-mock.\n  * Drop dep on pyopenssl to 0.13, include dep version for configargparse.\n  * Drop permission-failures patch applied upstream.\n\n -- Harlan Lieberman-Berg <hlieberman@setec.io>  Fri, 15 Jan 2016 19:21:56 -0500\n\npython-letsencrypt (0.1.1-3) unstable; urgency=medium\n\n  * New version to fix FTBFS.  (Closes: #808361)\n\n -- Harlan Lieberman-Berg <hlieberman@setec.io>  Tue, 22 Dec 2015 23:06:12 -0500\n\npython-letsencrypt (0.1.1-2) unstable; urgency=medium\n\n  * Dynamically generate d/control.\n\n -- Harlan Lieberman-Berg <hlieberman@setec.io>  Sat, 19 Dec 2015 17:50:38 -0500\n\npython-letsencrypt (0.1.1-1) unstable; urgency=medium\n\n  * New upstream version.\n\n -- Harlan Lieberman-Berg <hlieberman@setec.io>  Tue, 15 Dec 2015 21:41:01 -0500\n\npython-letsencrypt (0.1.0-2) unstable; urgency=medium\n\n  * First release for sid.\n\n -- Harlan Lieberman-Berg <hlieberman@setec.io>  Thu, 10 Dec 2015 23:33:21 -0500\n\npython-letsencrypt (0.1.0-1) experimental; urgency=medium\n\n  * New upstream release.\n  * Refresh patches.\n  * Switch PGP key to new upstream key.\n\n -- Harlan Lieberman-Berg <hlieberman@setec.io>  Thu, 03 Dec 2015 21:24:59 -0500\n\npython-letsencrypt (0.0.0.dev20151123-2) experimental; urgency=medium\n\n  * Alter dependency to solve unsatisfiable dependency. (Closes: #805738)\n\n -- Harlan Lieberman-Berg <hlieberman@setec.io>  Tue, 01 Dec 2015 21:29:49 -0500\n\npython-letsencrypt (0.0.0.dev20151123-1) experimental; urgency=medium\n\n  * New upstream version.\n\n -- Harlan Lieberman-Berg <hlieberman@setec.io>  Sun, 29 Nov 2015 23:39:15 -0500\n\npython-letsencrypt (0.0.0.dev20151114-3) experimental; urgency=medium\n\n  * Fix documentation errors due to path errors. (Closes: #805262)\n\n -- Harlan Lieberman-Berg <hlieberman@setec.io>  Fri, 20 Nov 2015 21:19:21 -0500\n\npython-letsencrypt (0.0.0.dev20151114-2) experimental; urgency=medium\n\n  * Version dependency of letsencrypt on python-letsencrypt (closes: #805185)\n  * Add python-acme dependency on python-letsencrypt (closes: #805186)\n\n -- Francois Marier <francois@debian.org>  Sun, 15 Nov 2015 10:07:50 -0800\n\npython-letsencrypt (0.0.0.dev20151114-1) experimental; urgency=medium\n\n  [ Francois Marier ]\n  * Bump the python-sphinx dependency to >= 1.3.1-1 to ensure that\n    python-sphinx-rtd-theme is pulled in.\n\n  [ Harlan Lieberman-Berg ]\n  * New upstream release. (Closes: #805049)\n  * Alter version of python-sphinx for backports compatibility.\n  * Update dependency versions to match setup.py\n\n -- Harlan Lieberman-Berg <hlieberman@setec.io>  Sat, 14 Nov 2015 12:26:04 -0500\n\npython-letsencrypt (0.0.0.dev20151104-1) experimental; urgency=medium\n\n  * Initial release. (Closes: #774387)\n\n -- Francois Marier <francois@debian.org>  Wed, 11 Nov 2015 18:49:07 -0800\n"
  },
  {
    "path": "debian/cli.ini",
    "content": "# Because we are using logrotate for greater flexibility, disable the\n# internal certbot logrotation.\nmax-log-backups = 0"
  },
  {
    "path": "debian/control",
    "content": "Source: python-certbot\nMaintainer: Debian Let's Encrypt <team+letsencrypt@tracker.debian.org>\nUploaders: Harlan Lieberman-Berg <hlieberman@debian.org>\nSection: python\nTestsuite: autopkgtest-pkg-python\nPriority: optional\nBuild-Depends: debhelper-compat (= 12),\n               dh-python,\n               python3,\n               python3-acme (>= 0.40.0~),\n               python3-configargparse,\n               python3-configobj,\n               python3-cryptography (>= 1.2.3),\n               python3-distro,\n               python3-distutils | python3 (<< 3.6.5~),\n               python3-josepy,\n               python3-mock,\n               python3-parsedatetime,\n               python3-pytest,\n               python3-repoze.sphinx.autointerface,\n               python3-requests,\n               python3-rfc3339,\n               python3-setuptools,\n               python3-sphinx (>= 1.6),\n               python3-sphinx-rtd-theme,\n               python3-tz,\n               python3-zope.component,\n               python3-zope.interface\nStandards-Version: 4.5.0\nVcs-Browser: https://salsa.debian.org/letsencrypt-team/certbot/certbot\nVcs-Git: https://salsa.debian.org/letsencrypt-team/certbot/certbot.git\nHomepage: https://certbot.eff.org/\nRules-Requires-Root: no\n\nPackage: python3-certbot\nArchitecture: all\nDepends: python3-acme (>= 0.29.0~),\n         python3-requests,\n         ${misc:Depends},\n         ${python3:Depends}\nRecommends: certbot\nSuggests: python-certbot-doc\nBreaks: python-letsencrypt (<= 0.6.0)\nReplaces: python-letsencrypt\nDescription: main library for certbot\n The objective of Certbot, Let's Encrypt, and the ACME (Automated\n Certificate Management Environment) protocol is to make it possible\n to set up an HTTPS server and have it automatically obtain a\n browser-trusted certificate, without any human intervention. This is\n accomplished by running a certificate management agent on the web\n server.\n .\n This agent is used to:\n .\n   - Automatically prove to the Let's Encrypt CA that you control the website\n   - Obtain a browser-trusted certificate and set it up on your web server\n   - Keep track of when your certificate is going to expire, and renew it\n   - Help you revoke the certificate if that ever becomes necessary.\n .\n This package contains the main libraries.\n\nPackage: certbot\nArchitecture: all\nSection: web\nDepends: python3-certbot (= ${source:Version}),\n         ${misc:Depends},\n         ${python3:Depends}\nSuggests: python3-certbot-apache,\n          python3-certbot-nginx,\n          python-certbot-doc\nProvides: letsencrypt\nReplaces: letsencrypt\nDescription: automatically configure HTTPS using Let's Encrypt\n The objective of Certbot, Let's Encrypt, and the ACME (Automated\n Certificate Management Environment) protocol is to make it possible\n to set up an HTTPS server and have it automatically obtain a\n browser-trusted certificate, without any human intervention. This is\n accomplished by running a certificate management agent on the web\n server.\n .\n This agent is used to:\n .\n   - Automatically prove to the Let's Encrypt CA that you control the website\n   - Obtain a browser-trusted certificate and set it up on your web server\n   - Keep track of when your certificate is going to expire, and renew it\n   - Help you revoke the certificate if that ever becomes necessary.\n .\n This package contains the main application, including the standalone\n and the manual authenticators.\n\nPackage: python-certbot-doc\nArchitecture: all\nSection: doc\nDepends: ${misc:Depends},\n         ${sphinxdoc:Depends}\nDescription: client documentation for certbot\n The objective of Certbot, Let's Encrypt, and the ACME (Automated\n Certificate Management Environment) protocol is to make it possible\n to set up an HTTPS server and have it automatically obtain a\n browser-trusted certificate, without any human intervention. This is\n accomplished by running a certificate management agent on the web\n server.\n .\n This agent is used to:\n .\n   - Automatically prove to the Let's Encrypt CA that you control the website\n   - Obtain a browser-trusted certificate and set it up on your web server\n   - Keep track of when your certificate is going to expire, and renew it\n   - Help you revoke the certificate if that ever becomes necessary.\n .\n This package contains the documentation.\n"
  },
  {
    "path": "debian/copyright",
    "content": "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\nSource: https://pypi.python.org/pypi/certbot\nUpstream-Name: certbot\n\nFiles: *\nCopyright: 2015, Electronic Frontier Foundation and others\n           2014, Fatih Erikli\nLicense: LetsEncrypt\n\nFiles: debian/*\nCopyright: 2015, Harlan Lieberman-Berg <hlieberman@setec.io>\n           2015, Francois Marier <francois@debian.org>\nLicense: Apache-2.0\n\nLicense: Apache-2.0\n Licensed under the Apache License, Version 2.0 (the \"License\"); you\n may not use this file except in compliance with the License.  You may\n obtain a copy of the License at\n .\n http://www.apache.org/licenses/LICENSE-2.0\n .\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied.  See the License for the specific language governing\n permissions and limitations under the License.\n .\n On Debian systems, the complete text of the Apache version 2.0\n license can be found in \"/usr/share/common-licenses/Apache-2.0\".\n\nLicense: LetsEncrypt\n Let's Encrypt Python Client\n Copyright (c) Electronic Frontier Foundation and others\n Licensed Apache Version 2.0\n .\n On Debian systems, the complete text of the Apache version 2.0\n license can be found in \"/usr/share/common-licenses/Apache-2.0\".\n .\n Incorporating code from nginxparser\n Copyright (c) 2014 Fatih Erikli\n Licensed MIT\n .\n Permission is hereby granted, free of charge, to any person obtaining a copy of\n this software and associated documentation files (the \"Software\"), to deal in\n the Software without restriction, including without limitation the rights to\n use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\n the Software, and to permit persons to whom the Software is furnished to do so,\n subject to the following conditions:\n .\n The above copyright notice and this permission notice shall be included in all\n copies or substantial portions of the Software.\n .\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\n FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\n COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\n IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "debian/patches/0001-remove-external-images.patch",
    "content": "Description:  Remove external image links in documentation\nAuthor: Harlan Lieberman-Berg <hlieberman@setec.io>\nForwarded: not-needed\nLast-Update: 2016-05-12\n\nIndex: python-certbot/README.rst\n===================================================================\n--- python-certbot.orig/README.rst\n+++ python-certbot/README.rst\n@@ -71,16 +71,6 @@ ACME spec: http://ietf-wg-acme.github.io\n \n ACME working area in github: https://github.com/ietf-wg-acme/acme\n \n-|build-status| |container|\n-\n-.. |build-status| image:: https://travis-ci.com/certbot/certbot.svg?branch=master\n-   :target: https://travis-ci.com/certbot/certbot\n-   :alt: Travis CI status\n-\n-.. |container| image:: https://quay.io/repository/letsencrypt/letsencrypt/status\n-   :target: https://quay.io/repository/letsencrypt/letsencrypt\n-   :alt: Docker Repository on Quay.io\n-\n .. Do not modify this comment unless you know what you're doing. tag:links-end\n \n System Requirements\n"
  },
  {
    "path": "debian/patches/series",
    "content": "0001-remove-external-images.patch\n"
  },
  {
    "path": "debian/pydist-overrides",
    "content": "ConfigArgParse python-configargparse; PEP386\ncryptography python-cryptography; PEP386\nparsedatetime python-parsedatetime; PEP386\npsutil python-psutil; PEP386\nsetuptools python-setuptools; PEP386\n"
  },
  {
    "path": "debian/python-certbot-doc.doc-base",
    "content": "Document: python-certbot-doc\nTitle: Documentation for Certbot\nAuthor: Let's Encrypt Team\nAbstract: These HTML documentation contain the auto-generated\n documentation for the certbot application.\nSection: Web Development\n\nFormat: HTML\nIndex: /usr/share/doc/python3-certbot/html/index.html\nFiles: /usr/share/doc/python3-certbot/html/*.html\n"
  },
  {
    "path": "debian/python-certbot-doc.docs",
    "content": "build/html\n"
  },
  {
    "path": "debian/python-certbot-doc.examples",
    "content": "examples/*\n"
  },
  {
    "path": "debian/python3-certbot.lintian-overrides",
    "content": "# The README file detected in testdata/ is an explanation of how the\n# testdata keys were generated.\npython3-certbot binary: package-contains-documentation-outside-usr-share-doc usr/lib/python3/dist-packages/certbot/tests/testdata/README\n"
  },
  {
    "path": "debian/rules",
    "content": "#!/usr/bin/make -ef\n\nexport PYBUILD_NAME = certbot\n\n%:\n\tdh $@ --with python3,sphinxdoc --buildsystem=pybuild\n\noverride_dh_install:\n\tmkdir -p debian/certbot/usr/bin debian/certbot/etc/letsencrypt\n\tmv debian/python3-certbot/usr/bin/* debian/certbot/usr/bin\n\trm -rf debian/python3-certbot/usr/bin\n\tmv debian/cli.ini debian/certbot/etc/letsencrypt/cli.ini\n\thttp_proxy='127.0.0.1:9' \\\n\t\thttps_proxy='127.0.0.1:9' \\\n\t\tsphinx-build -N -bhtml docs/ build/html\n\thttp_proxy='127.0.0.1:9' \\\n\t\thttps_proxy='127.0.0.1:8' \\\n\t\tsphinx-build -N -bman docs/ build/man\n\noverride_dh_installsystemd:\n\tdh_installsystemd --no-start --package=certbot certbot.service\n\tdh_installsystemd --package=certbot certbot.timer\n\noverride_dh_installinit:\n\noverride_dh_installdocs:\n\tdh_installdocs --doc-main-package=python3-certbot -p python-certbot-doc\n\tdh_installdocs -p certbot -p python3-certbot\n\noverride_dh_installexamples:\n\tdh_installexamples --doc-main-package=python3-certbot -p python-certbot-doc\n\noverride_dh_auto_test:\nifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))\n\tpython3 setup.py test\nendif\n"
  },
  {
    "path": "debian/source/format",
    "content": "3.0 (quilt)\n"
  },
  {
    "path": "debian/tests/certs/README.md",
    "content": "# certs/localhost\n\nThis directory contains an end-entity (leaf) certificate (`cert.pem`) and\na private key (`key.pem`) for the Pebble HTTPS server. It includes `127.0.0.1`\nas an IP address SAN, and `[localhost, pebble]` as DNS SANs.\n"
  },
  {
    "path": "debian/tests/certs/cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDGzCCAgOgAwIBAgIIbEfayDFsBtwwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE\nAxMVbWluaWNhIHJvb3QgY2EgMjRlMmRiMCAXDTE3MTIwNjE5NDIxMFoYDzIxMDcx\nMjA2MTk0MjEwWjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB\nAQUAA4IBDwAwggEKAoIBAQCbFMW3DXXdErvQf2lCZ0qz0DGEWadDoF0O2neM5mVa\nVQ7QGW0xc5Qwvn3Tl62C0JtwLpF0pG2BICIN+DHdVaIUwkf77iBS2doH1I3waE1I\n8GkV9JrYmFY+j0dA1SwBmqUZNXhLNwZGq1a91nFSI59DZNy/JciqxoPX2K++ojU2\nFPpuXe2t51NmXMsszpa+TDqF/IeskA9A/ws6UIh4Mzhghx7oay2/qqj2IIPjAmJj\ni73kdUvtEry3wmlkBvtVH50+FscS9WmPC5h3lDTk5nbzSAXKuFusotuqy3XTgY5B\nPiRAwkZbEY43JNfqenQPHo7mNTt29i+NVVrBsnAa5ovrAgMBAAGjYzBhMA4GA1Ud\nDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0T\nAQH/BAIwADAiBgNVHREEGzAZgglsb2NhbGhvc3SCBnBlYmJsZYcEfwAAATANBgkq\nhkiG9w0BAQsFAAOCAQEAYIkXff8H28KS0KyLHtbbSOGU4sujHHVwiVXSATACsNAE\nD0Qa8hdtTQ6AUqA6/n8/u1tk0O4rPE/cTpsM3IJFX9S3rZMRsguBP7BSr1Lq/XAB\n7JP/CNHt+Z9aKCKcg11wIX9/B9F7pyKM3TdKgOpqXGV6TMuLjg5PlYWI/07lVGFW\n/mSJDRs8bSCFmbRtEqc4lpwlrpz+kTTnX6G7JDLfLWYw/xXVqwFfdengcDTHCc8K\nwtgGq/Gu6vcoBxIO3jaca+OIkMfxxXmGrcNdseuUCa3RMZ8Qy03DqGu6Y6XQyK4B\nW8zIG6H9SVKkAznM2yfYhW8v2ktcaZ95/OBHY97ZIw==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "debian/tests/certs/key.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAmxTFtw113RK70H9pQmdKs9AxhFmnQ6BdDtp3jOZlWlUO0Blt\nMXOUML5905etgtCbcC6RdKRtgSAiDfgx3VWiFMJH++4gUtnaB9SN8GhNSPBpFfSa\n2JhWPo9HQNUsAZqlGTV4SzcGRqtWvdZxUiOfQ2TcvyXIqsaD19ivvqI1NhT6bl3t\nredTZlzLLM6Wvkw6hfyHrJAPQP8LOlCIeDM4YIce6Gstv6qo9iCD4wJiY4u95HVL\n7RK8t8JpZAb7VR+dPhbHEvVpjwuYd5Q05OZ280gFyrhbrKLbqst104GOQT4kQMJG\nWxGONyTX6np0Dx6O5jU7dvYvjVVawbJwGuaL6wIDAQABAoIBAGW9W/S6lO+DIcoo\nPHL+9sg+tq2gb5ZzN3nOI45BfI6lrMEjXTqLG9ZasovFP2TJ3J/dPTnrwZdr8Et/\n357YViwORVFnKLeSCnMGpFPq6YEHj7mCrq+YSURjlRhYgbVPsi52oMOfhrOIJrEG\nZXPAwPRi0Ftqu1omQEqz8qA7JHOkjB2p0i2Xc/uOSJccCmUDMlksRYz8zFe8wHuD\nXvUL2k23n2pBZ6wiez6Xjr0wUQ4ESI02x7PmYgA3aqF2Q6ECDwHhjVeQmAuypMF6\nIaTjIJkWdZCW96pPaK1t+5nTNZ+Mg7tpJ/PRE4BkJvqcfHEOOl6wAE8gSk5uVApY\nZRKGmGkCgYEAzF9iRXYo7A/UphL11bR0gqxB6qnQl54iLhqS/E6CVNcmwJ2d9pF8\n5HTfSo1/lOXT3hGV8gizN2S5RmWBrc9HBZ+dNrVo7FYeeBiHu+opbX1X/C1HC0m1\nwJNsyoXeqD1OFc1WbDpHz5iv4IOXzYdOdKiYEcTv5JkqE7jomqBLQk8CgYEAwkG/\nrnwr4ThUo/DG5oH+l0LVnHkrJY+BUSI33g3eQ3eM0MSbfJXGT7snh5puJW0oXP7Z\nGw88nK3Vnz2nTPesiwtO2OkUVgrIgWryIvKHaqrYnapZHuM+io30jbZOVaVTMR9c\nX/7/d5/evwXuP7p2DIdZKQKKFgROm1XnhNqVgaUCgYBD/ogHbCR5RVsOVciMbRlG\nUGEt3YmUp/vfMuAsKUKbT2mJM+dWHVlb+LZBa4pC06QFgfxNJi/aAhzSGvtmBEww\nxsXbaceauZwxgJfIIUPfNZCMSdQVIVTi2Smcx6UofBz6i/Jw14MEwlvhamaa7qVf\nkqflYYwelga1wRNCPopLaQKBgQCWsZqZKQqBNMm0Q9yIhN+TR+2d7QFjqeePoRPl\n1qxNejhq25ojE607vNv1ff9kWUGuoqSZMUC76r6FQba/JoNbefI4otd7x/GzM9uS\n8MHMJazU4okwROkHYwgLxxkNp6rZuJJYheB4VDTfyyH/ng5lubmY7rdgTQcNyZ5I\nmajRYQKBgAMKJ3RlII0qvAfNFZr4Y2bNIq+60Z+Qu2W5xokIHCFNly3W1XDDKGFe\nCCPHSvQljinke3P9gPt2HVdXxcnku9VkTti+JygxuLkVg7E0/SWwrWfGsaMJs+84\nfK+mTZay2d3v24r9WKEKwLykngYPyZw5+BdWU0E+xx5lGUd3U4gG\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "debian/tests/control",
    "content": "# Smoke test\nTest-Command: certbot --help\nRestrictions: superficial\nFeatures: test-name=smoke\n\n# End to End HTTP\nTests: http-01\nDepends: pebble\nRestrictions: allow-stderr, isolation-container"
  },
  {
    "path": "debian/tests/http-01",
    "content": "#!/bin/bash\n\n# First, define a safe place for us to puke files\nif [ -d $AUTOPKGTEST_TMP ]\nthen\n    TMP_DIR=$AUTOPKGTEST_TMP\nelse\n    TMP_DIR=`mktemp -d`\nfi\n\n# Background pebble, since we'll need that\n## Speed up pebble\nexport PEBBLE_VA_NOSLEEP = 1\n## Prevent flakiness from nonce failures\nexport PEBBLE_WFE_NONCEREJECT = 0\npebble -config debian/tests/pebble-config.json &\n\n# Next, trigger certbot\ncertbot \\\n    --no-random-sleep-on-renew \\\n    --server https://localhost:14000/directory \\\n    --no-verify-ssl \\\n    --http-01-port 5002 \\\n    --https-port 5001 \\\n    --manual-public-ip-logging-ok \\\n    --config-dir ${TMP_DIR}/certbot/http_01/conf \\\n    --work-dir ${TMP_DIR}/certbot/http_01/work \\\n    --logs-dir ${TMP_DIR}/certbot/http_01/logs \\\n    --non-interactive \\\n    --no-redirect \\\n    --agree-tos \\\n    --register-unsafely-without-email \\\n    --debug \\\n    -vv \\\n    certonly -d localhost --standalone\n"
  },
  {
    "path": "debian/tests/pebble-config.json",
    "content": "{\n  \"pebble\": {\n    \"listenAddress\": \"0.0.0.0:14000\",\n    \"managementListenAddress\": \"0.0.0.0:15000\",\n    \"certificate\": \"debian/tests/certs/cert.pem\",\n    \"privateKey\": \"debian/tests/certs/key.pem\",\n    \"httpPort\": 5002,\n    \"tlsPort\": 5001,\n    \"ocspResponderURL\": \"\",\n    \"externalAccountBindingRequired\": false\n  }\n}\n"
  },
  {
    "path": "debian/upstream/signing-key.asc",
    "content": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQENBFZVq4kBCADJvp9fLg1WqQ3KJl9ayOk23i5PNGSF6loT2muvoUcbQFUKC6ie\nxC3chvIIIrXPG1lJhNxXONUaiooBrDLo17MGM5C6k8j5FZfAqxirC40rL4yDF+cq\n2ObuURaWX6t0eS9k6B0Kg8aqru9bKHO/NQNqN/nw8Kyyg5D2jdn2HPcMn6/5RWrv\nq2TRk3lFggunm4wb2i8Gegu04/bgcfEyxvI0Y+gLR4n3vu1/m4oEVuwxwqggb5BB\nAc5knkiCNZl6sGwZxCXxJcK4J+3O5RNdF7K7v/B8S8djN6fKmcjtPn0tsB6xkaQ7\nosaGQy2dOlh3ZWZDhtACCBJmCp1hx5zerkuJABEBAAG0NkxldCdzIEVuY3J5cHQg\nQ2xpZW50IFRlYW0gPGxldHNlbmNyeXB0LWNsaWVudEBlZmYub3JnPokBPgQTAQIA\nKAUCVlWriQIbAwUJFo5qAAYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQTRfJ\nlc2XdfISLAf/XJzoA/L2cIErpnJUuhuPcHHDnt2wspiNuzpwH5ojob6L3DTXCYrE\nNQUm8wbBgruDpO9OLvQrzmlRIdDU/IkcHD6lfiRT3lhfAcZBZfEVqhSvyRP2VABX\n1V+bSGJM9sLJZqgw26fD7GX5VUxvZfeqN4wW4ufdtrkRb+evtuOWDGjV/OfD285U\n0mv1JIdJ/DnUXoNDn1Lr7RJJtTfYUzQXAvgmB2Fkn1nkg3drhJ8+mj5VAzRTEX8a\nzb/ss56cW8BFGNWcfwefMQq5PLQIOczBtkVyTNKKwMorwfTcp0GnNSNil/mTkrcJ\nmjRMTbXwlNxcq+G7Sg7hG6+PYj2zbjXaD7kBDQRWVauJAQgAwXk3jChBJmlH7ir4\nIPVC8D8FI3oqMotEX05DbvjZB0+S8MCqkxor5MaMBEXZMiMUO7u5+FRWwFL1befI\nPFxKI48PRm1hZNaQPu+3qXfEutCWhNYBIQogSdN8oOg2HX+tNk9OUryRhIdeDhYE\nPtZzJv5bca9GaJilhMJrKuK4FdQFiCQVXLKwY7g2knzIG81IyQj+pd0EhJlMeGU4\nWVXA/LG4tOejRCkJSNAEeFktNOYKR3ERWwgZxHB8/apPeww80Kk6Pbc9uPfGTeec\npcpwdUqIxTzkfkdb6SL7VQa01BzgbidFeKEKCPD7eq/kATcUPl6q+fC9AismlKmC\nzU/a4QARAQABiQElBBgBAgAPBQJWVauJAhsMBQkWjmoAAAoJEE0XyZXNl3Xyoz4I\nAJ8HVTvss13crU2SBNIFce2EIkXquUPqnv6vuNFFq+3Qv4atHch+p3rnkSZ8yTud\nIT0tyYO/5dRPoiKFzh2HqHftKe61oT1i6xGkfQmMdz2Y1A1Jl6EUEs8/8uiDONtz\n7PrKTMcIQOSRdUkDHO8OXALiA/it20cVLq39bP7bFDT31bIGyRKlF9beNnd1BINT\nQPa1O8JxeE6NLPdmGeHAXyEPUgcjvXrCLKUSvM0KlB81N0SjX0RpM7qyX3XLnj+f\nQOJ+0pbvluMnn2Ooejkz9F6bNr1SN9cu0TWFMgoqvES0mL3PD6dSW5QNfIDNy+TA\nzaOjYTu55/3JvbyRD26ouau5AQ0EVlWsMwEIALhDTFjI97adohYQMgIBFbfkY1ET\nbtQiwyxqBMOVDY5857cYgY5KKcdM50Y9SbK0VX9ScBsB0x28IIr/gBHk5SB0yc7L\nxVByT3oOf6dktXLS2LljIFwsz+g1qi7bdS3ROBmQW8U1Jbae/XsLV1OcEsu7V8Re\nbdN0nyNzsyw4C2DcyNDD4SG39PnBMV0JSeSIrAhJm+Ca71KmMqS0kklYqXUcScop\nEvYHNJf4EBxHd2BMSMwSDCQfnNXR3b5ddKVUQsgXl9HVnWVZGXo6IGAIVGZCQ367\nyhuGfJKXxyR0NHSowk1/MHWv1/R3pjhEnW8zccyWUhG+LB2ufKDSwaV5jmcAEQEA\nAYkBJQQYAQIADwUCVlWsMwIbIAUJFo5qAAAKCRBNF8mVzZd18hqLCACCeF+ySpKK\n30DyfDJ26wRjmx6OQigz5ZdP+qmuavyajDFnforKZh4iOfScN/jMvRh20WKHkmaz\nOWG4HgvnLeWj3DMxTpP46wH4XWgC+XQ1jeWMi4fkUa3E8JQiPS970miaUXKakhSE\nz+pfY6uf9Ay5FBgTqg2zAmCA6yAzMogqQRKi8yvR9MWCbEAJtuTcR3fi3d61dsko\nhKuiNfXDlFt3+aTr00lEqqASPy2cguj97kfycT/ANfpYI7iN2DkgR9EOGx0H1WOx\nfc5eEtQViqAu2qrnUOEpsoCBOr7pktv/MWHMwJx72E3L5qhjjC872dWPU2cH5Y0y\nn7BVBdxwDVJQ\n=qw93\n-----END PGP PUBLIC KEY BLOCK-----\n"
  },
  {
    "path": "debian/watch",
    "content": "version=3\nopts=uversionmangle=s/(rc|a|b|c)/~$1/,pgpsigurlmangle=s/$/.asc/ \\\nhttps://pypi.debian.net/certbot/certbot-(.+)\\.(?:zip|tgz|tbz|txz|(?:tar\\.(?:gz|bz2|xz)))\n"
  },
  {
    "path": "docs/.gitignore",
    "content": "/_build/\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nPAPER         =\nBUILDDIR      = _build\n\n# User-friendly check for sphinx-build\nifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)\n$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)\nendif\n\n# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n# the i18n builder cannot share the environment and doctrees with the others\nI18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n\n.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext\n\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  html       to make standalone HTML files\"\n\t@echo \"  dirhtml    to make HTML files named index.html in directories\"\n\t@echo \"  singlehtml to make a single large HTML file\"\n\t@echo \"  pickle     to make pickle files\"\n\t@echo \"  json       to make JSON files\"\n\t@echo \"  htmlhelp   to make HTML files and a HTML help project\"\n\t@echo \"  qthelp     to make HTML files and a qthelp project\"\n\t@echo \"  devhelp    to make HTML files and a Devhelp project\"\n\t@echo \"  epub       to make an epub\"\n\t@echo \"  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\"\n\t@echo \"  latexpdf   to make LaTeX files and run them through pdflatex\"\n\t@echo \"  latexpdfja to make LaTeX files and run them through platex/dvipdfmx\"\n\t@echo \"  text       to make text files\"\n\t@echo \"  man        to make manual pages\"\n\t@echo \"  texinfo    to make Texinfo files\"\n\t@echo \"  info       to make Texinfo files and run them through makeinfo\"\n\t@echo \"  gettext    to make PO message catalogs\"\n\t@echo \"  changes    to make an overview of all changed/added/deprecated items\"\n\t@echo \"  xml        to make Docutils-native XML files\"\n\t@echo \"  pseudoxml  to make pseudoxml-XML files for display purposes\"\n\t@echo \"  linkcheck  to check all external links for integrity\"\n\t@echo \"  doctest    to run all doctests embedded in the documentation (if enabled)\"\n\t@echo \"  coverage   to run coverage check of the documentation (if enabled)\"\n\nclean:\n\trm -rf $(BUILDDIR)/*\n\nhtml:\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\n\ndirhtml:\n\t$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/dirhtml.\"\n\nsinglehtml:\n\t$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml\n\t@echo\n\t@echo \"Build finished. The HTML page is in $(BUILDDIR)/singlehtml.\"\n\npickle:\n\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle\n\t@echo\n\t@echo \"Build finished; now you can process the pickle files.\"\n\njson:\n\t$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json\n\t@echo\n\t@echo \"Build finished; now you can process the JSON files.\"\n\nhtmlhelp:\n\t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp\n\t@echo\n\t@echo \"Build finished; now you can run HTML Help Workshop with the\" \\\n\t      \".hhp project file in $(BUILDDIR)/htmlhelp.\"\n\nqthelp:\n\t$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp\n\t@echo\n\t@echo \"Build finished; now you can run \"qcollectiongenerator\" with the\" \\\n\t      \".qhcp project file in $(BUILDDIR)/qthelp, like this:\"\n\t@echo \"# qcollectiongenerator $(BUILDDIR)/qthelp/LetsEncrypt.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/LetsEncrypt.qhc\"\n\ndevhelp:\n\t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp\n\t@echo\n\t@echo \"Build finished.\"\n\t@echo \"To view the help file:\"\n\t@echo \"# mkdir -p $$HOME/.local/share/devhelp/LetsEncrypt\"\n\t@echo \"# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/LetsEncrypt\"\n\t@echo \"# devhelp\"\n\nepub:\n\t$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub\n\t@echo\n\t@echo \"Build finished. The epub file is in $(BUILDDIR)/epub.\"\n\nlatex:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo\n\t@echo \"Build finished; the LaTeX files are in $(BUILDDIR)/latex.\"\n\t@echo \"Run \\`make' in that directory to run these through (pdf)latex\" \\\n\t      \"(use \\`make latexpdf' here to do that automatically).\"\n\nlatexpdf:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through pdflatex...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\nlatexpdfja:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through platex and dvipdfmx...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\ntext:\n\t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text\n\t@echo\n\t@echo \"Build finished. The text files are in $(BUILDDIR)/text.\"\n\nman:\n\t$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man\n\t@echo\n\t@echo \"Build finished. The manual pages are in $(BUILDDIR)/man.\"\n\ntexinfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo\n\t@echo \"Build finished. The Texinfo files are in $(BUILDDIR)/texinfo.\"\n\t@echo \"Run \\`make' in that directory to run these through makeinfo\" \\\n\t      \"(use \\`make info' here to do that automatically).\"\n\ninfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo \"Running Texinfo files through makeinfo...\"\n\tmake -C $(BUILDDIR)/texinfo info\n\t@echo \"makeinfo finished; the Info files are in $(BUILDDIR)/texinfo.\"\n\ngettext:\n\t$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale\n\t@echo\n\t@echo \"Build finished. The message catalogs are in $(BUILDDIR)/locale.\"\n\nchanges:\n\t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes\n\t@echo\n\t@echo \"The overview file is in $(BUILDDIR)/changes.\"\n\nlinkcheck:\n\t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck\n\t@echo\n\t@echo \"Link check complete; look for any errors in the above output \" \\\n\t      \"or in $(BUILDDIR)/linkcheck/output.txt.\"\n\ndoctest:\n\t$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest\n\t@echo \"Testing of doctests in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/doctest/output.txt.\"\n\ncoverage:\n\t$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage\n\t@echo \"Testing of coverage in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/coverage/python.txt.\"\n\nxml:\n\t$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml\n\t@echo\n\t@echo \"Build finished. The XML files are in $(BUILDDIR)/xml.\"\n\npseudoxml:\n\t$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml\n\t@echo\n\t@echo \"Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml.\"\n"
  },
  {
    "path": "docs/_static/.gitignore",
    "content": ""
  },
  {
    "path": "docs/_templates/footer.html",
    "content": "<footer>\n  {% if (theme_prev_next_buttons_location == 'bottom' or theme_prev_next_buttons_location == 'both') and (next or prev) %}\n    <div class=\"rst-footer-buttons\" role=\"navigation\" aria-label=\"footer navigation\">\n      {% if next %}\n        <a href=\"{{ next.link|e }}\" class=\"btn btn-neutral float-right\" title=\"{{ next.title|striptags|e }}\" accesskey=\"n\" rel=\"next\">{{ _('Next') }} <span class=\"fa fa-arrow-circle-right\"></span></a>\n      {% endif %}\n      {% if prev %}\n        <a href=\"{{ prev.link|e }}\" class=\"btn btn-neutral\" title=\"{{ prev.title|striptags|e }}\" accesskey=\"p\" rel=\"prev\"><span class=\"fa fa-arrow-circle-left\"></span> {{ _('Previous') }}</a>\n      {% endif %}\n    </div>\n  {% endif %}\n\n  <hr/>\n\n  <div role=\"contentinfo\">\n    <p>\n    <span class=\"copyright\">\n    &copy; Copyright 2014-2018 - The Certbot software and documentation are licensed under the Apache 2.0 license as described at <a href=\"https://eff.org/cb-license\">https://eff.org/cb-license</a>.\n    </span>\n    <br>\n    <br>\n    <span class=\"status\">\n        <a href=\"https://letsencrypt.status.io/\">Let's Encrypt Status</a>\n    </span>\n\n    {%- if build_id and build_url %}\n      {% trans build_url=build_url, build_id=build_id %}\n        <span class=\"build\">\n          Build\n          <a href=\"{{ build_url }}\">{{ build_id }}</a>.\n        </span>\n      {% endtrans %}\n    {%- elif commit %}\n      {% trans commit=commit %}\n        <span class=\"commit\">\n          Revision <code>{{ commit }}</code>.\n        </span>\n      {% endtrans %}\n    {%- elif last_updated %}\n      {% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}\n    {%- endif %}\n\n    </p>\n  </div>\n\n  {%- if show_sphinx %}\n  {% trans %}Built with <a href=\"http://sphinx-doc.org/\">Sphinx</a> using a <a href=\"https://github.com/snide/sphinx_rtd_theme\">theme</a> provided by <a href=\"https://readthedocs.org\">Read the Docs</a>{% endtrans %}.\n  {%- endif %}\n\n  {%- block extrafooter %} {% endblock %}\n\n</footer>\n"
  },
  {
    "path": "docs/api/certbot.achallenges.rst",
    "content": "certbot.achallenges module\n==========================\n\n.. automodule:: certbot.achallenges\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/certbot.compat.filesystem.rst",
    "content": "certbot.compat.filesystem module\n================================\n\n.. automodule:: certbot.compat.filesystem\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/certbot.compat.misc.rst",
    "content": "certbot.compat.misc module\n==========================\n\n.. automodule:: certbot.compat.misc\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/certbot.compat.os.rst",
    "content": "certbot.compat.os module\n========================\n\n.. automodule:: certbot.compat.os\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/certbot.compat.rst",
    "content": "certbot.compat package\n======================\n\n.. automodule:: certbot.compat\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\nSubmodules\n----------\n\n.. toctree::\n\n   certbot.compat.filesystem\n   certbot.compat.misc\n   certbot.compat.os\n\n"
  },
  {
    "path": "docs/api/certbot.crypto_util.rst",
    "content": "certbot.crypto\\_util module\n===========================\n\n.. automodule:: certbot.crypto_util\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/certbot.display.ops.rst",
    "content": "certbot.display.ops module\n==========================\n\n.. automodule:: certbot.display.ops\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/certbot.display.rst",
    "content": "certbot.display package\n=======================\n\n.. automodule:: certbot.display\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\nSubmodules\n----------\n\n.. toctree::\n\n   certbot.display.ops\n   certbot.display.util\n\n"
  },
  {
    "path": "docs/api/certbot.display.util.rst",
    "content": "certbot.display.util module\n===========================\n\n.. automodule:: certbot.display.util\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/certbot.errors.rst",
    "content": "certbot.errors module\n=====================\n\n.. automodule:: certbot.errors\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/certbot.interfaces.rst",
    "content": "certbot.interfaces module\n=========================\n\n.. automodule:: certbot.interfaces\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/certbot.main.rst",
    "content": "certbot.main module\n===================\n\n.. automodule:: certbot.main\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/certbot.ocsp.rst",
    "content": "certbot.ocsp package\n======================\n\n.. automodule:: certbot.ocsp\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/certbot.plugins.common.rst",
    "content": "certbot.plugins.common module\n=============================\n\n.. automodule:: certbot.plugins.common\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/certbot.plugins.dns_common.rst",
    "content": "certbot.plugins.dns\\_common module\n==================================\n\n.. automodule:: certbot.plugins.dns_common\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/certbot.plugins.dns_common_lexicon.rst",
    "content": "certbot.plugins.dns\\_common\\_lexicon module\n===========================================\n\n.. automodule:: certbot.plugins.dns_common_lexicon\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/certbot.plugins.dns_test_common.rst",
    "content": "certbot.plugins.dns\\_test\\_common module\n========================================\n\n.. automodule:: certbot.plugins.dns_test_common\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/certbot.plugins.dns_test_common_lexicon.rst",
    "content": "certbot.plugins.dns\\_test\\_common\\_lexicon module\n=================================================\n\n.. automodule:: certbot.plugins.dns_test_common_lexicon\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/certbot.plugins.enhancements.rst",
    "content": "certbot.plugins.enhancements module\n===================================\n\n.. automodule:: certbot.plugins.enhancements\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/certbot.plugins.rst",
    "content": "certbot.plugins package\n=======================\n\n.. automodule:: certbot.plugins\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\nSubmodules\n----------\n\n.. toctree::\n\n   certbot.plugins.common\n   certbot.plugins.dns_common\n   certbot.plugins.dns_common_lexicon\n   certbot.plugins.dns_test_common\n   certbot.plugins.dns_test_common_lexicon\n   certbot.plugins.enhancements\n   certbot.plugins.storage\n   certbot.plugins.util\n\n"
  },
  {
    "path": "docs/api/certbot.plugins.storage.rst",
    "content": "certbot.plugins.storage module\n==============================\n\n.. automodule:: certbot.plugins.storage\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/certbot.plugins.util.rst",
    "content": "certbot.plugins.util module\n===========================\n\n.. automodule:: certbot.plugins.util\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/certbot.reverter.rst",
    "content": "certbot.reverter module\n=======================\n\n.. automodule:: certbot.reverter\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/certbot.rst",
    "content": "certbot package\n===============\n\n.. automodule:: certbot\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\nSubpackages\n-----------\n\n.. toctree::\n\n    certbot.compat\n    certbot.display\n    certbot.plugins\n    certbot.tests\n\nSubmodules\n----------\n\n.. toctree::\n\n   certbot.achallenges\n   certbot.crypto_util\n   certbot.errors\n   certbot.interfaces\n   certbot.main\n   certbot.ocsp\n   certbot.reverter\n   certbot.util\n\n"
  },
  {
    "path": "docs/api/certbot.tests.acme_util.rst",
    "content": "certbot.tests.acme\\_util module\n===============================\n\n.. automodule:: certbot.tests.acme_util\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/certbot.tests.rst",
    "content": "certbot.tests package\n=====================\n\n.. automodule:: certbot.tests\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\nSubmodules\n----------\n\n.. toctree::\n\n   certbot.tests.acme_util\n   certbot.tests.util\n\n"
  },
  {
    "path": "docs/api/certbot.tests.util.rst",
    "content": "certbot.tests.util module\n=========================\n\n.. automodule:: certbot.tests.util\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api/certbot.util.rst",
    "content": "certbot.util module\n===================\n\n.. automodule:: certbot.util\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/api.rst",
    "content": "=================\nAPI Documentation\n=================\n\n.. toctree::\n   :maxdepth: 4\n\n   api/certbot\n"
  },
  {
    "path": "docs/challenges.rst",
    "content": "Challenges\n==========\n\nTo receive a certificate from Let's Encrypt certificate authority (CA), you must pass a *challenge* to \nprove you control each of the domain names that will be listed in the certificate. A challenge is one of \na list of specified tasks that only someone who controls the domain should be able to accomplish, such as:\n\n* Posting a specified file in a specified location on a web site (the HTTP-01 challenge)\n* Posting a specified DNS record in the domain name system (the DNS-01 challenge)\n\nIt’s possible to complete each type of challenge *automatically* (Certbot directly makes the necessary \nchanges itself, or runs another program that does so), or *manually* (Certbot tells you to make a \ncertain change, and you edit a configuration file of some kind in order to accomplish it). Certbot's \ndesign favors performing challenges automatically, and this is the normal case for most users of Certbot.\n\nSome plugins offer an *authenticator*, meaning that they can satisfy challenges:\n\n* Apache plugin: (HTTP-01) Tries to edit your Apache configuration files to temporarily serve files to\n  satisfy challenges from the certificate authority. Use the Apache plugin when you're running Certbot on a\n  web server with Apache listening on port 80.\n* Nginx plugin: (HTTP-01) Tries to edit your nginx configuration files to temporarily serve files to\n  satisfy challenges from the certificate authority. Use the nginx plugin when you're running Certbot on a\n  web server with nginx listening on port 80.\n* Webroot plugin: (HTTP-01) Tries to place a file where it can be served over HTTP on port 80 by a\n  web server running on your system. Use the Webroot plugin when you're running Certbot on \n  a web server with any server application listening on port 80 serving files from a folder on disk in response.\n* Standalone plugin: (HTTP-01) Tries to run a temporary web server listening on HTTP on port 80. Use the\n  Standalone plugin if no existing program is listening to this port.\n* Manual plugin: (DNS-01 or HTTP-01) Either tells you what changes to make to your configuration or updates \n  your DNS records using an external script (for DNS-01) or your webroot (for HTTP-01). Use the Manual \n  plugin if you have the technical knowledge to make configuration changes yourself when asked to do so,\n  and are prepared to repeat these steps every time the certificate needs to be renewed. \n\nTips for Challenges\n-------------------\nGeneral tips:\n\n* Run Certbot on your web server, not on your laptop or another server. It’s usually the easiest way to get a certificate.\n* Use a tool like the DNSchecker at dnsstuff.com to check your DNS records to make sure \n  there are no serious errors. A DNS error can prevent a certificate authority from \n  issuing a certificate, even if it does not prevent your site from loading in a browser.\n* If you are using Apache or NGINX plugins, make sure the configuration of your Apache or NGINX server is correct.\n\nHTTP-01 Challenge\n~~~~~~~~~~~~~~~~~\n\n* Make sure the domain name exists and is already pointed to the public IP address of the server where \n  you’re requesting the certificate.\n* Make sure port 80 is open, publicly reachable from the Internet, and not blocked by a router or firewall.\n* When using the Webroot plugin or the manual plugin, make sure the the webroot directory exists and that you\n  specify it properly. If you set the webroot directory for example.com to `/var/www/example.com`\n  then a file placed in `/var/www/example.com/.well-known/acme-challenge/testfile` should appear on\n  your web site at `http://example.com/.well-known/acme-challenge/testfile` (A redirection to HTTPS\n  is OK here and should not stop the challenge from working.)\n* In some web server configurations, all pages are dynamically generated by some kind of framework, \n  usually using a database backend. In this case, there might not be a particular directory \n  from which the web server can serve filesdirectly. Using the Webroot plugin in this case \n  requires making a change to your web server configuration first.\n* Make sure your web server serves files properly from the directory where the challenge \n  file is placed (e. g. `/.well-known/acme-challenge`) to the expected location on the \n  website without adding a header or footer.\n* When using the Standalone plugin, make sure another program is not already listening to port 80 on the server.\n* When using the Webroot plugin, make sure there is a web server listening on port 80.\n\nDNS-01 Challenge\n~~~~~~~~~~~~~~~~\n\n* When using the manual plugin, make sure your DNS records are correctly updated; \n  you must be able to make appropriate changes to your DNS zone in order to pass the challenge.\n\n"
  },
  {
    "path": "docs/ciphers.rst",
    "content": "============\nCiphersuites\n============\n\n.. contents:: Table of Contents\n   :local:\n\n\n.. _ciphersuites:\n\nIntroduction\n============\n\nAutoupdates\n-----------\n\nWithin certain limits, TLS server software can choose what kind of\ncryptography to use when a client connects. These choices can affect\nsecurity, compatibility, and performance in complex ways. Most of\nthese options are independent of a particular certificate. Certbot\ntries to provide defaults that we think are most useful to our users.\n\nAs described below, Certbot will default to modifying\nserver software's cryptographic settings to keep these up-to-date with\nwhat we think are appropriate defaults when new versions of the Certbot\nare installed (for example, by an operating system package manager).\n\nWhen this feature is implemented, this document will be updated\nto describe how to disable these automatic changes.\n\n\nCryptographic choices\n---------------------\n\nSoftware that uses cryptography must inevitably make choices about what\nkind of cryptography to use and how. These choices entail assumptions\nabout how well particular cryptographic mechanisms resist attack, and what\ntrade-offs are available and appropriate. The choices are constrained\nby compatibility issues (in order to interoperate with other software,\nan implementation must agree to use cryptographic mechanisms that the\nother side also supports) and protocol issues (cryptographic mechanisms\nmust be specified in protocols and there must be a way to agree to use\nthem in a particular context).\n\nThe best choices for a particular application may change over time in\nresponse to new research, new standardization events, changes in computer\nhardware, and changes in the prevalence of legacy software. Much important\nresearch on cryptanalysis and cryptographic vulnerabilities is unpublished\nbecause many researchers have been working in the interest of improving\nsome entities' communications security while weakening, or failing to\nimprove, others' security. But important information that improves our\nunderstanding of the state of the art is published regularly.\n\nWhen enabling TLS support in a compatible web server (which is a separate\nstep from obtaining a certificate), Certbot has the ability to\nupdate that web server's TLS configuration. Again, this is *different\nfrom the cryptographic particulars of the certificate itself*; the\ncertificate as of the initial release will be RSA-signed using one of\nLet's Encrypt's 2048-bit RSA keys, and will describe the subscriber's\nRSA public key (\"subject public key\") of at least 2048 bits, which is\nused for key establishment.\n\nNote that the subscriber's RSA public key can be used in a wide variety\nof key establishment methods, most of which do not use RSA directly\nfor key exchange, but only for authenticating the server!  For example,\nin DHE and ECDHE key exchanges, the subject public key is just used to\nsign other parameters for authentication. You do not have to \"use RSA\"\nfor other purposes just because you're using an RSA key for authentication.\n\nThe certificate doesn't specify other cryptographic or ciphersuite\nparticulars; for example, it doesn't say whether or not parties should\nuse a particular symmetric algorithm like 3DES, or what cipher modes\nthey should use. All of these details are negotiated between client\nand server independent of the content of the ciphersuite. The\nLet's Encrypt project hopes to provide useful defaults that reflect\ngood security choices with respect to the publicly-known state of the\nart. However, the Let's Encrypt certificate authority does *not*\ndictate end-users' security policy, and any site is welcome to change\nits preferences in accordance with its own policy or its administrators'\npreferences, and use different cryptographic mechanisms or parameters,\nor a different priority order, than the defaults provided by Certbot.\n\nIf you don't use Certbot to configure your server directly, because the \nclient doesn't integrate with your server software or because you chose \nnot to use this integration, then the cryptographic defaults haven't been\nmodified, and the cryptography chosen by the server will still be whatever\nthe default for your software was.  For example, if you obtain a\ncertificate using *standalone* mode and then manually install it in an IMAP\nor LDAP server, your cryptographic settings will not be modified by the\nclient in any way.\n\n\nSources of defaults\n-------------------\n\nInitially, Certbot will configure users' servers to use the cryptographic\ndefaults recommended by the Mozilla project. These settings are well-reasoned\nrecommendations that carefully consider client software compatibility. They\nare described at\n\nhttps://wiki.mozilla.org/Security/Server_Side_TLS\n\nand the version implemented by Certbot will be the\nversion that was most current as of the release date of each client\nversion. Mozilla offers three separate sets of cryptographic options,\nwhich trade off security and compatibility differently. These are\nreferred to as the \"Modern\", \"Intermediate\", and \"Old\" configurations\n(in order from most secure to least secure, and least-backwards compatible\nto most-backwards compatible). The client will follow the Mozilla defaults\nfor the *Intermediate* configuration by default, at least with regards to\nciphersuites and TLS versions. Mozilla's web site describes which client\nsoftware will be compatible with each configuration. You can also use\nthe Qualys SSL Labs site, which Certbot will suggest\nwhen installing a certificate, to test your server and see whether it\nwill be compatible with particular software versions.\n\nIt will be possible to ask Certbot to instead apply (and track) Modern\nor Old configurations.\n\nThe Let's Encrypt project expects to follow the Mozilla recommendations\nin the future as those recommendations are updated. (For example, some\nusers have proposed prioritizing a new ciphersuite known as ``0xcc13``\nwhich uses the ChaCha and Poly1305 algorithms, and which is already\nimplemented by the Chrome browser.  Mozilla has delayed recommending\n``0xcc13`` over compatibility and standardization concerns, but is likely\nto recommend it in the future once these concerns have been addressed. At\nthat point, Certbot would likely follow the Mozilla recommendations and favor\nthe use of this ciphersuite as well.)\n\nThe Let's Encrypt project may deviate from the Mozilla recommendations\nin the future if good cause is shown and we believe our users'\npriorities would be well-served by doing so. In general, please address\nrelevant proposals for changing priorities to the Mozilla security\nteam first, before asking the Certbot developers to change\nCertbot's priorities. The Mozilla security team is likely to have more\nresources and expertise to bring to bear on evaluating reasons why its\nrecommendations should be updated.\n\nThe Let's Encrypt project will entertain proposals to create a *very*\nsmall number of alternative configurations (apart from Modern,\nIntermediate, and Old) that there's reason to believe would be widely\nused by sysadmins; this would usually be a preferable course to modifying\nan existing configuration. For example, if many sysadmins want their\nservers configured to track a different expert recommendation, Certbot\ncould add an option to do so.\n\n\nResources for recommendations\n-----------------------------\n\nIn the course of considering how to handle this issue, we received\nrecommendations with sources of expert guidance on ciphersuites and other\ncryptographic parameters. We're grateful to everyone who contributed\nsuggestions. The recommendations we received are available under Feedback_.\n\nCertbot users are welcome to review these authorities to\nbetter inform their own cryptographic parameter choices. We also\nwelcome suggestions of other resources to add to this list. Please keep\nin mind that different recommendations may reflect different priorities\nor evaluations of trade-offs, especially related to compatibility!\n\n\nChanging your settings\n----------------------\n\nThis will probably look something like\n\n.. code-block:: shell\n\n  certbot --cipher-recommendations mozilla-secure\n  certbot --cipher-recommendations mozilla-intermediate\n  certbot --cipher-recommendations mozilla-old\n\nto track Mozilla's *Secure*, *Intermediate*, or *Old* recommendations,\nand\n\n.. code-block:: shell\n\n  certbot --update-ciphers on\n\nto enable updating ciphers with each new Certbot release, or\n\n.. code-block:: shell\n\n  certbot --update-ciphers off\n\nto disable automatic configuration updates. These features have not yet\nbeen implemented and this syntax may change when they are implemented.\n\n\nTODO\n----\n\nThe status of this feature is tracked as part of issue #1123 in our\nbug tracker.\n\nhttps://github.com/certbot/certbot/issues/1123\n\nPrior to implementation of #1123, the client does not actually modify\nciphersuites (this is intended to be implemented as a \"configuration\nenhancement\", but the only configuration enhancement implemented\nso far is redirecting HTTP requests to HTTPS in web servers, the\n\"redirect\" enhancement). The changes here would probably be either a new\n\"ciphersuite\" enhancement in each plugin that provides an installer,\nor a family of enhancements, one per selectable ciphersuite configuration.\n\nFeedback\n========\nWe receive lots of feedback on the type of ciphersuites that Let's Encrypt supports and list some collated feedback below. This section aims to track suggestions and references that people have offered or identified to improve the ciphersuites that Let's Encrypt enables when configuring TLS on servers.\n\nBecause of the Chatham House Rule applicable to some of the discussions, people are *not* individually credited for their suggestions, but most suggestions here were made or found by other people, and I thank them for their contributions.\n\nSome people provided rationale information mostly having to do with compatibility of particular user-agents (especially UAs that don't support ECC, or that don't support DH groups > 1024 bits).  Some ciphersuite configurations have been chosen to try to increase compatibility with older UAs while allowing newer UAs to negotiate stronger crypto.  For example, some configurations forego forward secrecy entirely for connections from old UAs, like by offering ECDHE and RSA key exchange, but no DHE at all.  (There are UAs that can fail the negotiation completely if a DHE ciphersuite with prime > 1024 bits is offered.)\n\nReferences\n----------\n\nRFC 7575\n~~~~~~~~\n\nIETF has published a BCP document, RFC 7525, \"Recommendations for Secure Use of Transport Layer Security (TLS) and Datagram Transport Layer Security (DTLS)\"\n\nhttps://datatracker.ietf.org/doc/rfc7525/\n\nBetterCrypto.org\n~~~~~~~~~~~~~~~~\n\nBetterCrypto.org, a collaboration of mostly European IT security experts, has published a draft paper, \"Applied Crypto Hardening\"\n\nhttps://bettercrypto.org/\n\nFF-DHE Internet-Draft\n~~~~~~~~~~~~~~~~~~~~~\n\nGillmor's Internet-Draft \"Negotiated Discrete Log Diffie-Hellman Ephemeral Parameters for TLS\" is being developed at the IETF TLS WG.  It advocates using *standardized* DH groups in all cases, not individually-chosen ones (mostly because of the Triple Handshake attack which can involve maliciously choosing invalid DH groups).  The draft provides a list of recommended groups, with primes beginning at 2048 bits and going up from there.  It also has a new protocol mechanism for agreeing to use these groups, with the possibility of backwards compatibility (and use of weaker DH groups) for older clients and servers that don't know about this mechanism.\n\nhttps://tools.ietf.org/html/draft-ietf-tls-negotiated-ff-dhe-10\n\nMozilla\n~~~~~~~\n\nMozilla's general server configuration guidance is available at https://wiki.mozilla.org/Security/Server_Side_TLS\n\nMozilla has also produced a configuration generator: https://mozilla.github.io/server-side-tls/ssl-config-generator/\n\nDutch National Cyber Security Centre\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe Dutch National Cyber Security Centre has published guidance on \"ICT-beveiligingsrichtlijnen voor Transport Layer Security (TLS)\" (\"IT Security Guidelines for Transport Layer Security (TLS)\").  These are available only in Dutch at\n\nhttps://web.archive.org/web/20190516085116/https://www.ncsc.nl/actueel/whitepapers/ict-beveiligingsrichtlijnen-voor-transport-layer-security-tls.html\n\nI have access to an English-language summary of the recommendations.\n\nKeylength.com\n~~~~~~~~~~~~~\n\nDamien Giry collects recommendations by academic researchers and standards organizations about keylengths for particular cryptoperiods, years, or security levels.  The keylength recommendations of the various sources are summarized in a chart.  This site has been updated over time and includes expert guidance from eight sources published between 2000 and 2017.\n\nhttp://www.keylength.com/\n\nNIST\n~~~~\nNISA published its \"NIST Special Publication 800-52 Revision 1: Guidelines for the Selection, Configuration, and Use of Transport Layer Security (TLS) Implementations\"\n\nhttp://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-52r1.pdf\n\nand its \"NIST Special Publication 800-57: Recommendation for Key Management – Part 1: General (Revision 3)\"\n\nhttp://csrc.nist.gov/publications/nistpubs/800-57/sp800-57_part1_rev3_general.pdf\n\nENISA\n~~~~~\n\nENISA published its \"Algorithms, Key Sizes and Parameters Report - 2013\"\n\nhttps://www.enisa.europa.eu/activities/identity-and-trust/library/deliverables/algorithms-key-sizes-and-parameters-report\n\nWeakDH/Logjam\n-------------\n\nThe WeakDH/Logjam research has thrown into question the safety of some existing practice using DH ciphersuites, especially the use of standardized groups with a prime ≤ 1024 bits.  The authors provided detailed guidance, including ciphersuite lists, at\n\nhttps://weakdh.org/sysadmin.html\n\nThese lists may have been derived from Mozilla's recommendations.\nOne of the authors clarified his view of the priorities for various changes as a result of the research at\n\nhttps://web.archive.org/web/20150526022820/https://www.ietf.org/mail-archive/web/tls/current/msg16496.html\n\nIn particular, he supports ECDHE and also supports the use of the standardized groups in the FF-DHE Internet-Draft mentioned above (which isn't clear from the group's original recommendations).\n\nParticular sites' opinions or configurations\n--------------------------------------------\n\nAmazon ELB\n~~~~~~~~~~\n\nAmazon ELB explains its current ciphersuite choices at\n\nhttps://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/elb-security-policy-table.html\n\nU.S. Government 18F\n~~~~~~~~~~~~~~~~~~~\n\nThe 18F site (https://18f.gsa.gov/) is using \n\n::\n\n    ssl_ciphers 'kEECDH+ECDSA+AES128 kEECDH+ECDSA+AES256 kEECDH+AES128 kEECDH+AES256 kEDH+AES128 kEDH+AES256 DES-CBC3-SHA +SHA !aNULL !eNULL !LOW !MD5 !EXP !DSS !PSK !SRP !kECDH !CAMELLIA !RC4 !SEED';\n\nDuraconf\n~~~~~~~~\n\nThe Duraconf project collects particular configuration files, with an apparent focus on avoiding the use of obsolete symmetric ciphers and hash functions, and favoring forward secrecy while not requiring it.\n\nhttps://github.com/ioerror/duraconf\n\nSite scanning or rating tools\n-----------------------------\n\nQualys SSL Labs\n~~~~~~~~~~~~~~~\n\nQualys offers the best-known TLS security scanner, maintained by Ivan Ristić.\n\nhttps://www.ssllabs.com/\n\nDutch NCSC\n~~~~~~~~~~\n\nThe Dutch NCSC, mentioned above, has also made available its own site security scanner which indicates how well sites comply with the recommendations.\n\nhttps://en.internet.nl/\n\nJava compatibility issue\n------------------------\n\nA lot of backward-compatibility concerns have to do with Java hard-coding DHE primes to a 1024-bit limit, accepting DHE ciphersuites in negotiation, and then aborting the connection entirely if a prime > 1024 bits is presented.  The simple summary is that servers offering a Java-compatible DHE ciphersuite in preference to other Java-compatible ciphersuites, and then presenting a DH group with a prime > 1024 bits, will be completely incompatible with clients running some versions of Java.  (This may also be the case with very old MSIE versions...?)  There are various strategies for dealing with this, and maybe we can document the options here.\n"
  },
  {
    "path": "docs/cli-help.txt",
    "content": "usage: \n  certbot [SUBCOMMAND] [options] [-d DOMAIN] [-d DOMAIN] ...\n\nCertbot can obtain and install HTTPS/TLS/SSL certificates.  By default,\nit will attempt to use a webserver both for obtaining and installing the\ncertificate. The most common SUBCOMMANDS and flags are:\n\nobtain, install, and renew certificates:\n    (default) run   Obtain & install a certificate in your current webserver\n    certonly        Obtain or renew a certificate, but do not install it\n    renew           Renew all previously obtained certificates that are near expiry\n    enhance         Add security enhancements to your existing configuration\n   -d DOMAINS       Comma-separated list of domains to obtain a certificate for\n\n  --apache          Use the Apache plugin for authentication & installation\n  --standalone      Run a standalone webserver for authentication\n  --nginx           Use the Nginx plugin for authentication & installation\n  --webroot         Place files in a server's webroot folder for authentication\n  --manual          Obtain certificates interactively, or using shell script hooks\n\n   -n               Run non-interactively\n  --test-cert       Obtain a test certificate from a staging server\n  --dry-run         Test \"renew\" or \"certonly\" without saving any certificates to disk\n\nmanage certificates:\n    certificates    Display information about certificates you have from Certbot\n    revoke          Revoke a certificate (supply --cert-name or --cert-path)\n    delete          Delete a certificate (supply --cert-name)\n\nmanage your account:\n    register        Create an ACME account\n    unregister      Deactivate an ACME account\n    update_account  Update an ACME account\n  --agree-tos       Agree to the ACME server's Subscriber Agreement\n   -m EMAIL         Email address for important account notifications\n\noptional arguments:\n  -h, --help            show this help message and exit\n  -c CONFIG_FILE, --config CONFIG_FILE\n                        path to config file (default: /etc/letsencrypt/cli.ini\n                        and ~/.config/letsencrypt/cli.ini)\n  -v, --verbose         This flag can be used multiple times to incrementally\n                        increase the verbosity of output, e.g. -vvv. (default:\n                        -2)\n  --max-log-backups MAX_LOG_BACKUPS\n                        Specifies the maximum number of backup logs that\n                        should be kept by Certbot's built in log rotation.\n                        Setting this flag to 0 disables log rotation entirely,\n                        causing Certbot to always append to the same log file.\n                        (default: 1000)\n  -n, --non-interactive, --noninteractive\n                        Run without ever asking for user input. This may\n                        require additional command line flags; the client will\n                        try to explain which ones are required if it finds one\n                        missing (default: False)\n  --force-interactive   Force Certbot to be interactive even if it detects\n                        it's not being run in a terminal. This flag cannot be\n                        used with the renew subcommand. (default: False)\n  -d DOMAIN, --domains DOMAIN, --domain DOMAIN\n                        Domain names to apply. For multiple domains you can\n                        use multiple -d flags or enter a comma separated list\n                        of domains as a parameter. The first domain provided\n                        will be the subject CN of the certificate, and all\n                        domains will be Subject Alternative Names on the\n                        certificate. The first domain will also be used in\n                        some software user interfaces and as the file paths\n                        for the certificate and related material unless\n                        otherwise specified or you already have a certificate\n                        with the same name. In the case of a name collision it\n                        will append a number like 0001 to the file path name.\n                        (default: Ask)\n  --eab-kid EAB_KID     Key Identifier for External Account Binding (default:\n                        None)\n  --eab-hmac-key EAB_HMAC_KEY\n                        HMAC key for External Account Binding (default: None)\n  --cert-name CERTNAME  Certificate name to apply. This name is used by\n                        Certbot for housekeeping and in file paths; it doesn't\n                        affect the content of the certificate itself. To see\n                        certificate names, run 'certbot certificates'. When\n                        creating a new certificate, specifies the new\n                        certificate's name. (default: the first provided\n                        domain or the name of an existing certificate on your\n                        system for the same domains)\n  --dry-run             Perform a test run of the client, obtaining test\n                        (invalid) certificates but not saving them to disk.\n                        This can currently only be used with the 'certonly'\n                        and 'renew' subcommands. Note: Although --dry-run\n                        tries to avoid making any persistent changes on a\n                        system, it is not completely side-effect free: if used\n                        with webserver authenticator plugins like apache and\n                        nginx, it makes and then reverts temporary config\n                        changes in order to obtain test certificates, and\n                        reloads webservers to deploy and then roll back those\n                        changes. It also calls --pre-hook and --post-hook\n                        commands if they are defined because they may be\n                        necessary to accurately simulate renewal. --deploy-\n                        hook commands are not called. (default: False)\n  --debug-challenges    After setting up challenges, wait for user input\n                        before submitting to CA (default: False)\n  --preferred-challenges PREF_CHALLS\n                        A sorted, comma delimited list of the preferred\n                        challenge to use during authorization with the most\n                        preferred challenge listed first (Eg, \"dns\" or\n                        \"http,dns\"). Not all plugins support all challenges.\n                        See https://certbot.eff.org/docs/using.html#plugins\n                        for details. ACME Challenges are versioned, but if you\n                        pick \"http\" rather than \"http-01\", Certbot will select\n                        the latest version automatically. (default: [])\n  --user-agent USER_AGENT\n                        Set a custom user agent string for the client. User\n                        agent strings allow the CA to collect high level\n                        statistics about success rates by OS, plugin and use\n                        case, and to know when to deprecate support for past\n                        Python versions and flags. If you wish to hide this\n                        information from the Let's Encrypt server, set this to\n                        \"\". (default: CertbotACMEClient/1.2.0 (certbot(-auto);\n                        OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY\n                        (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel).\n                        The flags encoded in the user agent are: --duplicate,\n                        --force-renew, --allow-subset-of-names, -n, and\n                        whether any hooks are set.\n  --user-agent-comment USER_AGENT_COMMENT\n                        Add a comment to the default user agent string. May be\n                        used when repackaging Certbot or calling it from\n                        another tool to allow additional statistical data to\n                        be collected. Ignored if --user-agent is set.\n                        (Example: Foo-Wrapper/1.0) (default: None)\n\nautomation:\n  Flags for automating execution & other tweaks\n\n  --keep-until-expiring, --keep, --reinstall\n                        If the requested certificate matches an existing\n                        certificate, always keep the existing one until it is\n                        due for renewal (for the 'run' subcommand this means\n                        reinstall the existing certificate). (default: Ask)\n  --expand              If an existing certificate is a strict subset of the\n                        requested names, always expand and replace it with the\n                        additional names. (default: Ask)\n  --version             show program's version number and exit\n  --force-renewal, --renew-by-default\n                        If a certificate already exists for the requested\n                        domains, renew it now, regardless of whether it is\n                        near expiry. (Often --keep-until-expiring is more\n                        appropriate). Also implies --expand. (default: False)\n  --renew-with-new-domains\n                        If a certificate already exists for the requested\n                        certificate name but does not match the requested\n                        domains, renew it now, regardless of whether it is\n                        near expiry. (default: False)\n  --reuse-key           When renewing, use the same private key as the\n                        existing certificate. (default: False)\n  --allow-subset-of-names\n                        When performing domain validation, do not consider it\n                        a failure if authorizations can not be obtained for a\n                        strict subset of the requested domains. This may be\n                        useful for allowing renewals for multiple domains to\n                        succeed even if some domains no longer point at this\n                        system. This option cannot be used with --csr.\n                        (default: False)\n  --agree-tos           Agree to the ACME Subscriber Agreement (default: Ask)\n  --duplicate           Allow making a certificate lineage that duplicates an\n                        existing one (both can be renewed in parallel)\n                        (default: False)\n  --os-packages-only    (certbot-auto only) install OS package dependencies\n                        and then stop (default: False)\n  --no-self-upgrade     (certbot-auto only) prevent the certbot-auto script\n                        from upgrading itself to newer released versions\n                        (default: Upgrade automatically)\n  --no-bootstrap        (certbot-auto only) prevent the certbot-auto script\n                        from installing OS-level dependencies (default: Prompt\n                        to install OS-wide dependencies, but exit if the user\n                        says 'No')\n  --no-permissions-check\n                        (certbot-auto only) skip the check on the file system\n                        permissions of the certbot-auto script (default:\n                        False)\n  -q, --quiet           Silence all output except errors. Useful for\n                        automation via cron. Implies --non-interactive.\n                        (default: False)\n\nsecurity:\n  Security parameters & server settings\n\n  --rsa-key-size N      Size of the RSA key. (default: 2048)\n  --must-staple         Adds the OCSP Must Staple extension to the\n                        certificate. Autoconfigures OCSP Stapling for\n                        supported setups (Apache version >= 2.3.3 ). (default:\n                        False)\n  --redirect            Automatically redirect all HTTP traffic to HTTPS for\n                        the newly authenticated vhost. (default: Ask)\n  --no-redirect         Do not automatically redirect all HTTP traffic to\n                        HTTPS for the newly authenticated vhost. (default:\n                        Ask)\n  --hsts                Add the Strict-Transport-Security header to every HTTP\n                        response. Forcing browser to always use SSL for the\n                        domain. Defends against SSL Stripping. (default: None)\n  --uir                 Add the \"Content-Security-Policy: upgrade-insecure-\n                        requests\" header to every HTTP response. Forcing the\n                        browser to use https:// for every http:// resource.\n                        (default: None)\n  --staple-ocsp         Enables OCSP Stapling. A valid OCSP response is\n                        stapled to the certificate that the server offers\n                        during TLS. (default: None)\n  --strict-permissions  Require that all configuration files are owned by the\n                        current user; only needed if your config is somewhere\n                        unsafe like /tmp/ (default: False)\n  --auto-hsts           Gradually increasing max-age value for HTTP Strict\n                        Transport Security security header (default: False)\n\ntesting:\n  The following flags are meant for testing and integration purposes only.\n\n  --test-cert, --staging\n                        Use the staging server to obtain or revoke test\n                        (invalid) certificates; equivalent to --server https\n                        ://acme-staging-v02.api.letsencrypt.org/directory\n                        (default: False)\n  --debug               Show tracebacks in case of errors, and allow certbot-\n                        auto execution on experimental platforms (default:\n                        False)\n  --no-verify-ssl       Disable verification of the ACME server's certificate.\n                        (default: False)\n  --http-01-port HTTP01_PORT\n                        Port used in the http-01 challenge. This only affects\n                        the port Certbot listens on. A conforming ACME server\n                        will still attempt to connect on port 80. (default:\n                        80)\n  --http-01-address HTTP01_ADDRESS\n                        The address the server listens to during http-01\n                        challenge. (default: )\n  --https-port HTTPS_PORT\n                        Port used to serve HTTPS. This affects which port\n                        Nginx will listen on after a LE certificate is\n                        installed. (default: 443)\n  --break-my-certs      Be willing to replace or renew valid certificates with\n                        invalid (testing/staging) certificates (default:\n                        False)\n\npaths:\n  Flags for changing execution paths & servers\n\n  --cert-path CERT_PATH\n                        Path to where certificate is saved (with auth --csr),\n                        installed from, or revoked. (default: None)\n  --key-path KEY_PATH   Path to private key for certificate installation or\n                        revocation (if account key is missing) (default: None)\n  --fullchain-path FULLCHAIN_PATH\n                        Accompanying path to a full certificate chain\n                        (certificate plus chain). (default: None)\n  --chain-path CHAIN_PATH\n                        Accompanying path to a certificate chain. (default:\n                        None)\n  --config-dir CONFIG_DIR\n                        Configuration directory. (default: /etc/letsencrypt)\n  --work-dir WORK_DIR   Working directory. (default: /var/lib/letsencrypt)\n  --logs-dir LOGS_DIR   Logs directory. (default: /var/log/letsencrypt)\n  --server SERVER       ACME Directory Resource URI. (default:\n                        https://acme-v02.api.letsencrypt.org/directory)\n\nmanage:\n  Various subcommands and flags are available for managing your\n  certificates:\n\n  certificates          List certificates managed by Certbot\n  delete                Clean up all files related to a certificate\n  renew                 Renew all certificates (or one specified with --cert-\n                        name)\n  revoke                Revoke a certificate specified with --cert-path or\n                        --cert-name\n  update_symlinks       Recreate symlinks in your /etc/letsencrypt/live/\n                        directory\n\nrun:\n  Options for obtaining & installing certificates\n\ncertonly:\n  Options for modifying how a certificate is obtained\n\n  --csr CSR             Path to a Certificate Signing Request (CSR) in DER or\n                        PEM format. Currently --csr only works with the\n                        'certonly' subcommand. (default: None)\n\nrenew:\n  The 'renew' subcommand will attempt to renew all certificates (or more\n  precisely, certificate lineages) you have previously obtained if they are\n  close to expiry, and print a summary of the results. By default, 'renew'\n  will reuse the options used to create obtain or most recently successfully\n  renew each certificate lineage. You can try it with `--dry-run` first. For\n  more fine-grained control, you can renew individual lineages with the\n  `certonly` subcommand. Hooks are available to run commands before and\n  after renewal; see https://certbot.eff.org/docs/using.html#renewal for\n  more information on these.\n\n  --pre-hook PRE_HOOK   Command to be run in a shell before obtaining any\n                        certificates. Intended primarily for renewal, where it\n                        can be used to temporarily shut down a webserver that\n                        might conflict with the standalone plugin. This will\n                        only be called if a certificate is actually to be\n                        obtained/renewed. When renewing several certificates\n                        that have identical pre-hooks, only the first will be\n                        executed. (default: None)\n  --post-hook POST_HOOK\n                        Command to be run in a shell after attempting to\n                        obtain/renew certificates. Can be used to deploy\n                        renewed certificates, or to restart any servers that\n                        were stopped by --pre-hook. This is only run if an\n                        attempt was made to obtain/renew a certificate. If\n                        multiple renewed certificates have identical post-\n                        hooks, only one will be run. (default: None)\n  --deploy-hook DEPLOY_HOOK\n                        Command to be run in a shell once for each\n                        successfully issued certificate. For this command, the\n                        shell variable $RENEWED_LINEAGE will point to the\n                        config live subdirectory (for example,\n                        \"/etc/letsencrypt/live/example.com\") containing the\n                        new certificates and keys; the shell variable\n                        $RENEWED_DOMAINS will contain a space-delimited list\n                        of renewed certificate domains (for example,\n                        \"example.com www.example.com\" (default: None)\n  --disable-hook-validation\n                        Ordinarily the commands specified for --pre-hook\n                        /--post-hook/--deploy-hook will be checked for\n                        validity, to see if the programs being run are in the\n                        $PATH, so that mistakes can be caught early, even when\n                        the hooks aren't being run just yet. The validation is\n                        rather simplistic and fails if you use more advanced\n                        shell constructs, so you can use this switch to\n                        disable it. (default: False)\n  --no-directory-hooks  Disable running executables found in Certbot's hook\n                        directories during renewal. (default: False)\n  --disable-renew-updates\n                        Disable automatic updates to your server configuration\n                        that would otherwise be done by the selected installer\n                        plugin, and triggered when the user executes \"certbot\n                        renew\", regardless of if the certificate is renewed.\n                        This setting does not apply to important TLS\n                        configuration updates. (default: False)\n  --no-autorenew        Disable auto renewal of certificates. (default: True)\n\ncertificates:\n  List certificates managed by Certbot\n\ndelete:\n  Options for deleting a certificate\n\nrevoke:\n  Options for revocation of certificates\n\n  --reason {unspecified,keycompromise,affiliationchanged,superseded,cessationofoperation}\n                        Specify reason for revoking certificate. (default:\n                        unspecified)\n  --delete-after-revoke\n                        Delete certificates after revoking them, along with\n                        all previous and later versions of those certificates.\n                        (default: None)\n  --no-delete-after-revoke\n                        Do not delete certificates after revoking them. This\n                        option should be used with caution because the 'renew'\n                        subcommand will attempt to renew undeleted revoked\n                        certificates. (default: None)\n\nregister:\n  Options for account registration\n\n  --register-unsafely-without-email\n                        Specifying this flag enables registering an account\n                        with no email address. This is strongly discouraged,\n                        because in the event of key loss or account compromise\n                        you will irrevocably lose access to your account. You\n                        will also be unable to receive notice about impending\n                        expiration or revocation of your certificates. Updates\n                        to the Subscriber Agreement will still affect you, and\n                        will be effective 14 days after posting an update to\n                        the web site. (default: False)\n  -m EMAIL, --email EMAIL\n                        Email used for registration and recovery contact. Use\n                        comma to register multiple emails, ex:\n                        u1@example.com,u2@example.com. (default: Ask).\n  --eff-email           Share your e-mail address with EFF (default: None)\n  --no-eff-email        Don't share your e-mail address with EFF (default:\n                        None)\n\nupdate_account:\n  Options for account modification\n\nunregister:\n  Options for account deactivation.\n\n  --account ACCOUNT_ID  Account ID to use (default: None)\n\ninstall:\n  Options for modifying how a certificate is deployed\n\nrollback:\n  Options for rolling back server configuration changes\n\n  --checkpoints N       Revert configuration N number of checkpoints.\n                        (default: 1)\n\nplugins:\n  Options for the \"plugins\" subcommand\n\n  --init                Initialize plugins. (default: False)\n  --prepare             Initialize and prepare plugins. (default: False)\n  --authenticators      Limit to authenticator plugins only. (default: None)\n  --installers          Limit to installer plugins only. (default: None)\n\nupdate_symlinks:\n  Recreates certificate and key symlinks in /etc/letsencrypt/live, if you\n  changed them by hand or edited a renewal configuration file\n\nenhance:\n  Helps to harden the TLS configuration by adding security enhancements to\n  already existing configuration.\n\nplugins:\n  Plugin Selection: Certbot client supports an extensible plugins\n  architecture. See 'certbot plugins' for a list of all installed plugins\n  and their names. You can force a particular plugin by setting options\n  provided below. Running --help <plugin_name> will list flags specific to\n  that plugin.\n\n  --configurator CONFIGURATOR\n                        Name of the plugin that is both an authenticator and\n                        an installer. Should not be used together with\n                        --authenticator or --installer. (default: Ask)\n  -a AUTHENTICATOR, --authenticator AUTHENTICATOR\n                        Authenticator plugin name. (default: None)\n  -i INSTALLER, --installer INSTALLER\n                        Installer plugin name (also used to find domains).\n                        (default: None)\n  --apache              Obtain and install certificates using Apache (default:\n                        False)\n  --nginx               Obtain and install certificates using Nginx (default:\n                        False)\n  --standalone          Obtain certificates using a \"standalone\" webserver.\n                        (default: False)\n  --manual              Provide laborious manual instructions for obtaining a\n                        certificate (default: False)\n  --webroot             Obtain certificates by placing files in a webroot\n                        directory. (default: False)\n  --dns-cloudflare      Obtain certificates using a DNS TXT record (if you are\n                        using Cloudflare for DNS). (default: False)\n  --dns-cloudxns        Obtain certificates using a DNS TXT record (if you are\n                        using CloudXNS for DNS). (default: False)\n  --dns-digitalocean    Obtain certificates using a DNS TXT record (if you are\n                        using DigitalOcean for DNS). (default: False)\n  --dns-dnsimple        Obtain certificates using a DNS TXT record (if you are\n                        using DNSimple for DNS). (default: False)\n  --dns-dnsmadeeasy     Obtain certificates using a DNS TXT record (if you are\n                        using DNS Made Easy for DNS). (default: False)\n  --dns-gehirn          Obtain certificates using a DNS TXT record (if you are\n                        using Gehirn Infrastructure Service for DNS).\n                        (default: False)\n  --dns-google          Obtain certificates using a DNS TXT record (if you are\n                        using Google Cloud DNS). (default: False)\n  --dns-linode          Obtain certificates using a DNS TXT record (if you are\n                        using Linode for DNS). (default: False)\n  --dns-luadns          Obtain certificates using a DNS TXT record (if you are\n                        using LuaDNS for DNS). (default: False)\n  --dns-nsone           Obtain certificates using a DNS TXT record (if you are\n                        using NS1 for DNS). (default: False)\n  --dns-ovh             Obtain certificates using a DNS TXT record (if you are\n                        using OVH for DNS). (default: False)\n  --dns-rfc2136         Obtain certificates using a DNS TXT record (if you are\n                        using BIND for DNS). (default: False)\n  --dns-route53         Obtain certificates using a DNS TXT record (if you are\n                        using Route53 for DNS). (default: False)\n  --dns-sakuracloud     Obtain certificates using a DNS TXT record (if you are\n                        using Sakura Cloud for DNS). (default: False)\n\napache:\n  Apache Web Server plugin (Please note that the default values of the\n  Apache plugin options change depending on the operating system Certbot is\n  run on.)\n\n  --apache-enmod APACHE_ENMOD\n                        Path to the Apache 'a2enmod' binary (default: None)\n  --apache-dismod APACHE_DISMOD\n                        Path to the Apache 'a2dismod' binary (default: None)\n  --apache-le-vhost-ext APACHE_LE_VHOST_EXT\n                        SSL vhost configuration extension (default: -le-\n                        ssl.conf)\n  --apache-server-root APACHE_SERVER_ROOT\n                        Apache server root directory (default: /etc/apache2)\n  --apache-vhost-root APACHE_VHOST_ROOT\n                        Apache server VirtualHost configuration root (default:\n                        None)\n  --apache-logs-root APACHE_LOGS_ROOT\n                        Apache server logs directory (default:\n                        /var/log/apache2)\n  --apache-challenge-location APACHE_CHALLENGE_LOCATION\n                        Directory path for challenge configuration (default:\n                        /etc/apache2)\n  --apache-handle-modules APACHE_HANDLE_MODULES\n                        Let installer handle enabling required modules for you\n                        (Only Ubuntu/Debian currently) (default: False)\n  --apache-handle-sites APACHE_HANDLE_SITES\n                        Let installer handle enabling sites for you (Only\n                        Ubuntu/Debian currently) (default: False)\n  --apache-ctl APACHE_CTL\n                        Full path to Apache control script (default:\n                        apache2ctl)\n\ndns-cloudflare:\n  Obtain certificates using a DNS TXT record (if you are using Cloudflare\n  for DNS).\n\n  --dns-cloudflare-propagation-seconds DNS_CLOUDFLARE_PROPAGATION_SECONDS\n                        The number of seconds to wait for DNS to propagate\n                        before asking the ACME server to verify the DNS\n                        record. (default: 10)\n  --dns-cloudflare-credentials DNS_CLOUDFLARE_CREDENTIALS\n                        Cloudflare credentials INI file. (default: None)\n\ndns-cloudxns:\n  Obtain certificates using a DNS TXT record (if you are using CloudXNS for\n  DNS).\n\n  --dns-cloudxns-propagation-seconds DNS_CLOUDXNS_PROPAGATION_SECONDS\n                        The number of seconds to wait for DNS to propagate\n                        before asking the ACME server to verify the DNS\n                        record. (default: 30)\n  --dns-cloudxns-credentials DNS_CLOUDXNS_CREDENTIALS\n                        CloudXNS credentials INI file. (default: None)\n\ndns-digitalocean:\n  Obtain certs using a DNS TXT record (if you are using DigitalOcean for\n  DNS).\n\n  --dns-digitalocean-propagation-seconds DNS_DIGITALOCEAN_PROPAGATION_SECONDS\n                        The number of seconds to wait for DNS to propagate\n                        before asking the ACME server to verify the DNS\n                        record. (default: 10)\n  --dns-digitalocean-credentials DNS_DIGITALOCEAN_CREDENTIALS\n                        DigitalOcean credentials INI file. (default: None)\n\ndns-dnsimple:\n  Obtain certificates using a DNS TXT record (if you are using DNSimple for\n  DNS).\n\n  --dns-dnsimple-propagation-seconds DNS_DNSIMPLE_PROPAGATION_SECONDS\n                        The number of seconds to wait for DNS to propagate\n                        before asking the ACME server to verify the DNS\n                        record. (default: 30)\n  --dns-dnsimple-credentials DNS_DNSIMPLE_CREDENTIALS\n                        DNSimple credentials INI file. (default: None)\n\ndns-dnsmadeeasy:\n  Obtain certificates using a DNS TXT record (if you are using DNS Made Easy\n  for DNS).\n\n  --dns-dnsmadeeasy-propagation-seconds DNS_DNSMADEEASY_PROPAGATION_SECONDS\n                        The number of seconds to wait for DNS to propagate\n                        before asking the ACME server to verify the DNS\n                        record. (default: 60)\n  --dns-dnsmadeeasy-credentials DNS_DNSMADEEASY_CREDENTIALS\n                        DNS Made Easy credentials INI file. (default: None)\n\ndns-gehirn:\n  Obtain certificates using a DNS TXT record (if you are using Gehirn\n  Infrastructure Service for DNS).\n\n  --dns-gehirn-propagation-seconds DNS_GEHIRN_PROPAGATION_SECONDS\n                        The number of seconds to wait for DNS to propagate\n                        before asking the ACME server to verify the DNS\n                        record. (default: 30)\n  --dns-gehirn-credentials DNS_GEHIRN_CREDENTIALS\n                        Gehirn Infrastructure Service credentials file.\n                        (default: None)\n\ndns-google:\n  Obtain certificates using a DNS TXT record (if you are using Google Cloud\n  DNS for DNS).\n\n  --dns-google-propagation-seconds DNS_GOOGLE_PROPAGATION_SECONDS\n                        The number of seconds to wait for DNS to propagate\n                        before asking the ACME server to verify the DNS\n                        record. (default: 60)\n  --dns-google-credentials DNS_GOOGLE_CREDENTIALS\n                        Path to Google Cloud DNS service account JSON file.\n                        (See https://developers.google.com/identity/protocols/\n                        OAuth2ServiceAccount#creatinganaccount forinformation\n                        about creating a service account and\n                        https://cloud.google.com/dns/access-\n                        control#permissions_and_roles for information about\n                        therequired permissions.) (default: None)\n\ndns-linode:\n  Obtain certs using a DNS TXT record (if you are using Linode for DNS).\n\n  --dns-linode-propagation-seconds DNS_LINODE_PROPAGATION_SECONDS\n                        The number of seconds to wait for DNS to propagate\n                        before asking the ACME server to verify the DNS\n                        record. (default: 1200)\n  --dns-linode-credentials DNS_LINODE_CREDENTIALS\n                        Linode credentials INI file. (default: None)\n\ndns-luadns:\n  Obtain certificates using a DNS TXT record (if you are using LuaDNS for\n  DNS).\n\n  --dns-luadns-propagation-seconds DNS_LUADNS_PROPAGATION_SECONDS\n                        The number of seconds to wait for DNS to propagate\n                        before asking the ACME server to verify the DNS\n                        record. (default: 30)\n  --dns-luadns-credentials DNS_LUADNS_CREDENTIALS\n                        LuaDNS credentials INI file. (default: None)\n\ndns-nsone:\n  Obtain certificates using a DNS TXT record (if you are using NS1 for DNS).\n\n  --dns-nsone-propagation-seconds DNS_NSONE_PROPAGATION_SECONDS\n                        The number of seconds to wait for DNS to propagate\n                        before asking the ACME server to verify the DNS\n                        record. (default: 30)\n  --dns-nsone-credentials DNS_NSONE_CREDENTIALS\n                        NS1 credentials file. (default: None)\n\ndns-ovh:\n  Obtain certificates using a DNS TXT record (if you are using OVH for DNS).\n\n  --dns-ovh-propagation-seconds DNS_OVH_PROPAGATION_SECONDS\n                        The number of seconds to wait for DNS to propagate\n                        before asking the ACME server to verify the DNS\n                        record. (default: 30)\n  --dns-ovh-credentials DNS_OVH_CREDENTIALS\n                        OVH credentials INI file. (default: None)\n\ndns-rfc2136:\n  Obtain certificates using a DNS TXT record (if you are using BIND for\n  DNS).\n\n  --dns-rfc2136-propagation-seconds DNS_RFC2136_PROPAGATION_SECONDS\n                        The number of seconds to wait for DNS to propagate\n                        before asking the ACME server to verify the DNS\n                        record. (default: 60)\n  --dns-rfc2136-credentials DNS_RFC2136_CREDENTIALS\n                        RFC 2136 credentials INI file. (default: None)\n\ndns-route53:\n  Obtain certificates using a DNS TXT record (if you are using AWS Route53\n  for DNS).\n\n  --dns-route53-propagation-seconds DNS_ROUTE53_PROPAGATION_SECONDS\n                        The number of seconds to wait for DNS to propagate\n                        before asking the ACME server to verify the DNS\n                        record. (default: 10)\n\ndns-sakuracloud:\n  Obtain certificates using a DNS TXT record (if you are using Sakura Cloud\n  for DNS).\n\n  --dns-sakuracloud-propagation-seconds DNS_SAKURACLOUD_PROPAGATION_SECONDS\n                        The number of seconds to wait for DNS to propagate\n                        before asking the ACME server to verify the DNS\n                        record. (default: 90)\n  --dns-sakuracloud-credentials DNS_SAKURACLOUD_CREDENTIALS\n                        Sakura Cloud credentials file. (default: None)\n\nmanual:\n  Authenticate through manual configuration or custom shell scripts. When\n  using shell scripts, an authenticator script must be provided. The\n  environment variables available to this script depend on the type of\n  challenge. $CERTBOT_DOMAIN will always contain the domain being\n  authenticated. For HTTP-01 and DNS-01, $CERTBOT_VALIDATION is the\n  validation string, and $CERTBOT_TOKEN is the filename of the resource\n  requested when performing an HTTP-01 challenge. An additional cleanup\n  script can also be provided and can use the additional variable\n  $CERTBOT_AUTH_OUTPUT which contains the stdout output from the auth\n  script.\n\n  --manual-auth-hook MANUAL_AUTH_HOOK\n                        Path or command to execute for the authentication\n                        script (default: None)\n  --manual-cleanup-hook MANUAL_CLEANUP_HOOK\n                        Path or command to execute for the cleanup script\n                        (default: None)\n  --manual-public-ip-logging-ok\n                        Automatically allows public IP logging (default: Ask)\n\nnginx:\n  Nginx Web Server plugin\n\n  --nginx-server-root NGINX_SERVER_ROOT\n                        Nginx server root directory. (default: /etc/nginx or\n                        /usr/local/etc/nginx)\n  --nginx-ctl NGINX_CTL\n                        Path to the 'nginx' binary, used for 'configtest' and\n                        retrieving nginx version number. (default: nginx)\n\nnull:\n  Null Installer\n\nstandalone:\n  Spin up a temporary webserver\n\nwebroot:\n  Place files in webroot directory\n\n  --webroot-path WEBROOT_PATH, -w WEBROOT_PATH\n                        public_html / webroot path. This can be specified\n                        multiple times to handle different domains; each\n                        domain will have the webroot path that preceded it.\n                        For instance: `-w /var/www/example -d example.com -d\n                        www.example.com -w /var/www/thing -d thing.net -d\n                        m.thing.net` (default: Ask)\n  --webroot-map WEBROOT_MAP\n                        JSON dictionary mapping domains to webroot paths; this\n                        implies -d for each entry. You may need to escape this\n                        from your shell. E.g.: --webroot-map\n                        '{\"eg1.is,m.eg1.is\":\"/www/eg1/\", \"eg2.is\":\"/www/eg2\"}'\n                        This option is merged with, but takes precedence over,\n                        -w / -d entries. At present, if you put webroot-map in\n                        a config file, it needs to be on a single line, like:\n                        webroot-map = {\"example.com\":\"/var/www\"}. (default:\n                        {})\n"
  },
  {
    "path": "docs/compatibility.rst",
    "content": "=======================\nBackwards Compatibility\n=======================\n\nAll Certbot components including `acme <https://acme-python.readthedocs.io/>`_,\nCertbot, and :ref:`non-third party plugins <plugins>` follow `Semantic\nVersioning <https://semver.org/>`_ both for its Python :doc:`API <api>` and for the\napplication itself. This means that we will not change behavior in a backwards\nincompatible way except in a new major version of the project.\n\n.. note:: None of this applies to the behavior of Certbot distribution\n    mechanisms such as :ref:`certbot-auto <certbot-auto>` or OS packages whose\n    behavior may change at any time. Semantic versioning only applies to the\n    common Certbot components that are installed by various distribution\n    methods.\n\nFor Certbot as an application, the command line interface and non-interactive\nbehavior can be considered stable with two exceptions. The first is that no\naspects of Certbot's console or log output should be considered stable and it\nmay change at any time. The second is that Certbot's behavior should only be\nconsidered stable with certain files but not all. Files with which users should\nexpect Certbot to maintain its current behavior with are:\n\n* ``/etc/letsencrypt/live/<domain>/{cert,chain,fullchain,privkey}.pem`` where\n  ``<domain>`` is the name given to ``--cert-name``. If ``--cert-name`` is not\n  set by the user, it is the first domain given to ``--domains``.\n* :ref:`CLI configuration files <config-file>`\n* Hook directories in ``/etc/letsencrypt/renewal-hooks``\n\nCertbot's behavior with other files may change at any point.\n\nAnother area where Certbot should not be considered stable is its behavior when\nnot run in non-interactive mode which also may change at any point.\n\nIn general, if we're making a change that we expect will break some users, we\nwill bump the major version and will have warned about it in a prior release\nwhen possible. For our Python API, we will issue warnings using Python's\nwarning module. For application level changes, we will print and log warning\nmessages.\n"
  },
  {
    "path": "docs/conf.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Certbot documentation build configuration file, created by\n# sphinx-quickstart on Sun Nov 23 20:35:21 2014.\n#\n# This file is execfile()d with the current directory set to its\n# containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\nimport codecs\nimport os\nimport re\nimport sys\n\nimport sphinx\n\nhere = os.path.abspath(os.path.dirname(__file__))\n\n# read version number (and other metadata) from package init\ninit_fn = os.path.join(here, '..', 'certbot', '__init__.py')\nwith codecs.open(init_fn, encoding='utf8') as fd:\n    meta = dict(re.findall(r\"\"\"__([a-z]+)__ = '([^']+)\"\"\", fd.read()))\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\nsys.path.insert(0, os.path.abspath(os.path.join(here, '..')))\n\n# -- General configuration ------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\nneeds_sphinx = '1.2'\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n# ones.\nextensions = [\n    'sphinx.ext.autodoc',\n    'sphinx.ext.intersphinx',\n    'sphinx.ext.todo',\n    'sphinx.ext.coverage',\n    'sphinx.ext.viewcode',\n    'repoze.sphinx.autointerface',\n]\n\nif sphinx.version_info >= (1, 6):\n    extensions.append('sphinx.ext.imgconverter')\n\nautodoc_member_order = 'bysource'\nautodoc_default_flags = ['show-inheritance']\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['_templates']\n\n# The suffix of source filenames.\nsource_suffix = '.rst'\n\n# The encoding of source files.\n#source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = 'index'\n\n# General information about the project.\nproject = u'Certbot'\n# this is now overridden by the footer.html template\n#copyright = u'2014-2018 - The Certbot software and documentation are licensed under the Apache 2.0 license as described at https://eff.org/cb-license.'\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n# The short X.Y version.\nversion = '.'.join(meta['version'].split('.')[:2])\n# The full version, including alpha/beta/rc tags.\nrelease = meta['version']\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#\n# This is also used if you do content translation via gettext catalogs.\n# Usually you set \"language\" from the command line for these cases.\nlanguage = None\n\n# There are two options for replacing |today|: either, you set today to some\n# non-false value, then it is used:\n#today = ''\n# Else, today_fmt is used as the format for a strftime call.\n#today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = ['_build']\n\n# The reST default role (used for this markup: `text`) to use for all\n# documents.\ndefault_role = 'py:obj'\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n#add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n#add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n#show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = 'sphinx'\n\n# A list of ignored prefixes for module index sorting.\n#modindex_common_prefix = []\n\n# If true, keep warnings as \"system message\" paragraphs in the built documents.\n#keep_warnings = False\n\n# If true, `todo` and `todoList` produce output, else they produce nothing.\ntodo_include_todos = False\n\nsuppress_warnings = ['image.nonlocal_uri']\n\n# -- Options for HTML output ----------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\n\n# http://docs.readthedocs.org/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs\n# on_rtd is whether we are on readthedocs.org\non_rtd = os.environ.get('READTHEDOCS', None) == 'True'\nif not on_rtd:  # only import and set the theme if we're building docs locally\n    import sphinx_rtd_theme\n    html_theme = 'sphinx_rtd_theme'\n    html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]\n# otherwise, readthedocs.org uses their theme by default, so no need to specify it\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n#html_theme_options = {}\n\n# Add any paths that contain custom themes here, relative to this directory.\n#html_theme_path = []\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\n#html_title = None\n\n# A shorter title for the navigation bar.  Default is the same as html_title.\n#html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\n#html_logo = None\n\n# The name of an image file (within the static path) to use as favicon of the\n# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32\n# pixels large.\n#html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = ['_static']\n\n# Add any extra paths that contain custom files (such as robots.txt or\n# .htaccess) here, relative to this directory. These files are copied\n# directly to the root of the documentation.\n#html_extra_path = []\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,\n# using the given strftime format.\n#html_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n#html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\n#html_sidebars = {}\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n#html_additional_pages = {}\n\n# If false, no module index is generated.\n#html_domain_indices = True\n\n# If false, no index is generated.\n#html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n#html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\n#html_show_sourcelink = True\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer. Default is True.\n#html_show_sphinx = True\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer. Default is True.\n#html_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages will\n# contain a <link> tag referring to it.  The value of this option must be the\n# base URL from which the finished HTML is served.\n#html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n#html_file_suffix = None\n\n# Language to be used for generating the HTML full-text search index.\n# Sphinx supports the following languages:\n#   'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'\n#   'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'\n#html_search_language = 'en'\n\n# A dictionary with options for the search language support, empty by default.\n# Now only 'ja' uses this config value\n#html_search_options = {'type': 'default'}\n\n# The name of a javascript file (relative to the configuration directory) that\n# implements a search results scorer. If empty, the default will be used.\n#html_search_scorer = 'scorer.js'\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = 'Certbotdoc'\n\n# -- Options for LaTeX output ---------------------------------------------\n\nlatex_elements = {\n    # The paper size ('letterpaper' or 'a4paper').\n    #'papersize': 'letterpaper',\n\n    # The font size ('10pt', '11pt' or '12pt').\n    #'pointsize': '10pt',\n\n    # Additional stuff for the LaTeX preamble.\n    #'preamble': '',\n\n    # Latex figure (float) alignment\n    #'figure_align': 'htbp',\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title,\n#  author, documentclass [howto, manual, or own class]).\nlatex_documents = [\n    ('index', 'Certbot.tex', u'Certbot Documentation',\n     u'Certbot Project', 'manual'),\n]\n\n# The name of an image file (relative to this directory) to place at the top of\n# the title page.\n#latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\n#latex_use_parts = False\n\n# If true, show page references after internal links.\n#latex_show_pagerefs = False\n\n# If true, show URL addresses after external links.\n#latex_show_urls = False\n\n# Documents to append as an appendix to all manuals.\n#latex_appendices = []\n\n# If false, no module index is generated.\n#latex_domain_indices = True\n\n\n# -- Options for manual page output ---------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [\n    ('index', 'certbot', u'Certbot Documentation',\n     [project], 7),\n    ('man/certbot', 'certbot', u'certbot script documentation',\n     [project], 1),\n]\n\n# If true, show URL addresses after external links.\n#man_show_urls = False\n\n\n# -- Options for Texinfo output -------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n    ('index', 'Certbot', u'Certbot Documentation',\n     u'Certbot Project', 'Certbot', 'One line description of project.',\n     'Miscellaneous'),\n]\n\n# Documents to append as an appendix to all manuals.\n#texinfo_appendices = []\n\n# If false, no module index is generated.\n#texinfo_domain_indices = True\n\n# How to display URL addresses: 'footnote', 'no', or 'inline'.\n#texinfo_show_urls = 'footnote'\n\n# If true, do not generate a @detailmenu in the \"Top\" node's menu.\n#texinfo_no_detailmenu = False\n\n\nintersphinx_mapping = {\n    'python': ('https://docs.python.org/', None),\n    'acme': ('https://acme-python.readthedocs.org/en/latest/', None),\n}\n"
  },
  {
    "path": "docs/contributing.rst",
    "content": "===============\nDeveloper Guide\n===============\n\n.. contents:: Table of Contents\n   :local:\n\n\n.. _getting_started:\n\nGetting Started\n===============\n\nCertbot has the same :ref:`system requirements <system_requirements>` when set\nup for development.  While the section below will help you install Certbot and\nits dependencies, Certbot needs to be run on a UNIX-like OS so if you're using\nWindows, you'll need to set up a (virtual) machine running an OS such as Linux\nand continue with these instructions on that UNIX-like OS.\n\n.. _local copy:\n\nRunning a local copy of the client\n----------------------------------\n\nRunning the client in developer mode from your local tree is a little different\nthan running Certbot as a user. To get set up, clone our git repository by\nrunning:\n\n.. code-block:: shell\n\n   git clone https://github.com/certbot/certbot\n\nIf you're on macOS, we recommend you skip the rest of this section and instead\nrun Certbot in Docker. You can find instructions for how to do this :ref:`here\n<docker-dev>`. If you're running on Linux, you can run the following commands to\ninstall dependencies and set up a virtual environment where you can run\nCertbot.\n\nInstall the OS system dependencies required to run Certbot.\n\n.. code-block:: shell\n\n   # For APT-based distributions (e.g. Debian, Ubuntu ...)\n   sudo apt update\n   sudo apt install python3-dev python3-venv gcc libaugeas0 libssl-dev \\\n                    libffi-dev ca-certificates openssl\n   # For RPM-based distributions (e.g. Fedora, CentOS ...)\n   # NB1: old distributions will use yum instead of dnf\n   # NB2: RHEL-based distributions use python3X-devel instead of python3-devel (e.g. python36-devel)\n   sudo dnf install python3-devel gcc augeas-libs openssl-devel libffi-devel \\\n                    redhat-rpm-config ca-certificates openssl\n\nSet up the Python virtual environment that will host your Certbot local instance.\n\n.. code-block:: shell\n\n   cd certbot\n   python tools/venv3.py\n\n.. note:: You may need to repeat this when\n  Certbot's dependencies change or when a new plugin is introduced.\n\nYou can now run the copy of Certbot from git either by executing\n``venv3/bin/certbot``, or by activating the virtual environment. You can do the\nlatter by running:\n\n.. code-block:: shell\n\n   source venv3/bin/activate\n\nAfter running this command, ``certbot`` and development tools like ``ipdb``,\n``ipython``, ``pytest``, and ``tox`` are available in the shell where you ran\nthe command. These tools are installed in the virtual environment and are kept\nseparate from your global Python installation. This works by setting\nenvironment variables so the right executables are found and Python can pull in\nthe versions of various packages needed by Certbot.  More information can be\nfound in the `virtualenv docs`_.\n\n.. _`virtualenv docs`: https://virtualenv.pypa.io\n\nFind issues to work on\n----------------------\n\nYou can find the open issues in the `github issue tracker`_.  Comparatively\neasy ones are marked `good first issue`_.  If you're starting work on\nsomething, post a comment to let others know and seek feedback on your plan\nwhere appropriate.\n\nOnce you've got a working branch, you can open a pull request.  All changes in\nyour pull request must have thorough unit test coverage, pass our\ntests, and be compliant with the :ref:`coding style <coding-style>`.\n\n.. _github issue tracker: https://github.com/certbot/certbot/issues\n.. _good first issue: https://github.com/certbot/certbot/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22\n\n.. _testing:\n\nTesting\n-------\n\nYou can test your code in several ways:\n\n- running the `automated unit`_ tests,\n- running the `automated integration`_ tests\n- running an *ad hoc* `manual integration`_ test\n\n.. _automated unit:\n\nRunning automated unit tests\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nWhen you are working in a file ``foo.py``, there should also be a file ``foo_test.py``\neither in the same directory as ``foo.py`` or in the ``tests`` subdirectory\n(if there isn't, make one). While you are working on your code and tests, run\n``python foo_test.py`` to run the relevant tests.\n\nFor debugging, we recommend putting\n``import ipdb; ipdb.set_trace()`` statements inside the source code.\n\nOnce you are done with your code changes, and the tests in ``foo_test.py`` pass,\nrun all of the unittests for Certbot with ``tox -e py27`` (this uses Python\n2.7).\n\nOnce all the unittests pass, check for sufficient test coverage using ``tox -e\npy27-cover``, and then check for code style with ``tox -e lint`` (all files) or\n``pylint --rcfile=.pylintrc path/to/file.py`` (single file at a time).\n\nOnce all of the above is successful, you may run the full test suite using\n``tox --skip-missing-interpreters``. We recommend running the commands above\nfirst, because running all tests like this is very slow, and the large amount\nof output can make it hard to find specific failures when they happen.\n\n.. warning:: The full test suite may attempt to modify your system's Apache\n  config if your user has sudo permissions, so it should not be run on a\n  production Apache server.\n\n.. _automated integration:\n\nRunning automated integration tests\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nGenerally it is sufficient to open a pull request and let Github and Travis run\nintegration tests for you. However, you may want to run them locally before submitting\nyour pull request. You need Docker and docker-compose installed and working.\n\nThe tox environment `integration` will setup `Pebble`_, the Let's Encrypt ACME CA server\nfor integration testing, then launch the Certbot integration tests.\n\nWith a user allowed to access your local Docker daemon, run:\n\n.. code-block:: shell\n\n  tox -e integration\n\nTests will be run using pytest. A test report and a code coverage report will be\ndisplayed at the end of the integration tests execution.\n\n.. _Pebble: https://github.com/letsencrypt/pebble\n\n.. _manual integration:\n\nRunning manual integration tests\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nYou can also manually execute Certbot against a local instance of the `Pebble`_ ACME server.\nThis is useful to verify that the modifications done to the code makes Certbot behave as expected.\n\nTo do so you need:\n\n- Docker installed, and a user with access to the Docker client,\n- an available `local copy`_ of Certbot.\n\nThe virtual environment set up with `python tools/venv.py` contains two commands\nthat can be used once the virtual environment is activated:\n\n.. code-block:: shell\n\n    run_acme_server\n\n- Starts a local instance of Pebble and runs in the foreground printing its logs.\n- Press CTRL+C to stop this instance.\n- This instance is configured to validate challenges against certbot executed locally.\n\n.. code-block:: shell\n\n    certbot_test [ARGS...]\n\n- Execute certbot with the provided arguments and other arguments useful for testing purposes,\n  such as: verbose output, full tracebacks in case Certbot crashes, *etc.*\n- Execution is preconfigured to interact with the Pebble CA started with ``run_acme_server``.\n- Any arguments can be passed as they would be to Certbot (eg. ``certbot_test certonly -d test.example.com``).\n\nHere is a typical workflow to verify that Certbot successfully issued a certificate\nusing an HTTP-01 challenge on a machine with Python 3:\n\n.. code-block:: shell\n\n    python tools/venv3.py\n    source venv3/bin/activate\n    run_acme_server &\n    certbot_test certonly --standalone -d test.example.com\n    # To stop Pebble, launch `fg` to get back the background job, then press CTRL+C\n\nRunning tests in CI\n~~~~~~~~~~~~~~~~~~~\n\nCertbot uses both Azure Pipelines and Travis to run continuous integration\ntests. If you are using our Azure and Travis setup, a branch whose name starts\nwith `test-` will run all Azure and Travis tests on that branch. If the branch\nname starts with `azure-test-`, it will run all of our Azure tests and none of\nour Travis tests. If the branch stats with `travis-test-`, only our Travis\ntests will be run.\n\nCode components and layout\n==========================\n\nThe following components of the Certbot repository are distributed to users:\n\nacme\n  contains all protocol specific code\ncertbot\n  main client code\ncertbot-apache and certbot-nginx\n  client code to configure specific web servers\ncertbot-dns-*\n  client code to configure DNS providers\ncertbot-auto and letsencrypt-auto\n  shell scripts to install Certbot and its dependencies on UNIX systems\nwindows installer\n  Installs Certbot on Windows and is built using the files in windows-installer/\n\nPlugin-architecture\n-------------------\n\nCertbot has a plugin architecture to facilitate support for\ndifferent webservers, other TLS servers, and operating systems.\nThe interfaces available for plugins to implement are defined in\n`interfaces.py`_ and `plugins/common.py`_.\n\nThe main two plugin interfaces are `~certbot.interfaces.IAuthenticator`, which\nimplements various ways of proving domain control to a certificate authority,\nand `~certbot.interfaces.IInstaller`, which configures a server to use a\ncertificate once it is issued. Some plugins, like the built-in Apache and Nginx\nplugins, implement both interfaces and perform both tasks. Others, like the\nbuilt-in Standalone authenticator, implement just one interface.\n\nThere are also `~certbot.interfaces.IDisplay` plugins,\nwhich can change how prompts are displayed to a user.\n\n.. _interfaces.py: https://github.com/certbot/certbot/blob/master/certbot/interfaces.py\n.. _plugins/common.py: https://github.com/certbot/certbot/blob/master/certbot/plugins/common.py#L34\n\n\nAuthenticators\n--------------\n\nAuthenticators are plugins that prove control of a domain name by solving a\nchallenge provided by the ACME server. ACME currently defines several types of\nchallenges: HTTP, TLS-ALPN, and DNS, represented by classes in `acme.challenges`.\nAn authenticator plugin should implement support for at least one challenge type.\n\nAn Authenticator indicates which challenges it supports by implementing\n`get_chall_pref(domain)` to return a sorted list of challenge types in\npreference order.\n\nAn Authenticator must also implement `perform(achalls)`, which \"performs\" a list\nof challenges by, for instance, provisioning a file on an HTTP server, or\nsetting a TXT record in DNS. Once all challenges have succeeded or failed,\nCertbot will call the plugin's `cleanup(achalls)` method to remove any files or\nDNS records that were needed only during authentication.\n\nInstaller\n---------\n\nInstallers plugins exist to actually setup the certificate in a server,\npossibly tweak the security configuration to make it more correct and secure\n(Fix some mixed content problems, turn on HSTS, redirect to HTTPS, etc).\nInstaller plugins tell the main client about their abilities to do the latter\nvia the :meth:`~.IInstaller.supported_enhancements` call. We currently\nhave two Installers in the tree, the `~.ApacheConfigurator`. and the\n`~.NginxConfigurator`.  External projects have made some progress toward\nsupport for IIS, Icecast and Plesk.\n\nInstallers and Authenticators will oftentimes be the same class/object\n(because for instance both tasks can be performed by a webserver like nginx)\nthough this is not always the case (the standalone plugin is an authenticator\nthat listens on port 80, but it cannot install certs; a postfix plugin would\nbe an installer but not an authenticator).\n\nInstallers and Authenticators are kept separate because\nit should be possible to use the `~.StandaloneAuthenticator` (it sets\nup its own Python server to perform challenges) with a program that\ncannot solve challenges itself (Such as MTA installers).\n\n\nInstaller Development\n---------------------\n\nThere are a few existing classes that may be beneficial while\ndeveloping a new `~certbot.interfaces.IInstaller`.\nInstallers aimed to reconfigure UNIX servers may use Augeas for\nconfiguration parsing and can inherit from `~.AugeasConfigurator` class\nto handle much of the interface. Installers that are unable to use\nAugeas may still find the `~.Reverter` class helpful in handling\nconfiguration checkpoints and rollback.\n\n\n.. _dev-plugin:\n\nWriting your own plugin\n~~~~~~~~~~~~~~~~~~~~~~~\n\n.. note:: The Certbot team is not currently accepting any new DNS plugins\n    because we want to rethink our approach to the challenge and resolve some\n    issues like `#6464 <https://github.com/certbot/certbot/issues/6464>`_,\n    `#6503 <https://github.com/certbot/certbot/issues/6503>`_, and `#6504\n    <https://github.com/certbot/certbot/issues/6504>`_ first.\n\n    In the meantime, you're welcome to release it as a third-party plugin. See\n    `certbot-dns-ispconfig <https://github.com/m42e/certbot-dns-ispconfig>`_\n    for one example of that.\n\nCertbot client supports dynamic discovery of plugins through the\n`setuptools entry points`_ using the `certbot.plugins` group. This\nway you can, for example, create a custom implementation of\n`~certbot.interfaces.IAuthenticator` or the\n`~certbot.interfaces.IInstaller` without having to merge it\nwith the core upstream source code. An example is provided in\n``examples/plugins/`` directory.\n\nWhile developing, you can install your plugin into a Certbot development\nvirtualenv like this:\n\n.. code-block:: shell\n\n  . venv/bin/activate\n  pip install -e examples/plugins/\n  certbot_test plugins\n\nYour plugin should show up in the output of the last command. If not,\nit was not installed properly.\n\nOnce you've finished your plugin and published it, you can have your\nusers install it system-wide with `pip install`. Note that this will\nonly work for users who have Certbot installed from OS packages or via\npip. Users who run `certbot-auto` are currently unable to use third-party\nplugins. It's technically possible to install third-party plugins into\nthe virtualenv used by `certbot-auto`, but they will be wiped away when\n`certbot-auto` upgrades.\n\n.. _`setuptools entry points`:\n    http://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points\n\n.. _coding-style:\n\nCoding style\n============\n\nPlease:\n\n1. **Be consistent with the rest of the code**.\n\n2. Read `PEP 8 - Style Guide for Python Code`_.\n\n3. Follow the `Google Python Style Guide`_, with the exception that we\n   use `Sphinx-style`_ documentation::\n\n        def foo(arg):\n            \"\"\"Short description.\n\n            :param int arg: Some number.\n\n            :returns: Argument\n            :rtype: int\n\n            \"\"\"\n            return arg\n\n4. Remember to use ``pylint``.\n\n.. _Google Python Style Guide:\n  https://google.github.io/styleguide/pyguide.html\n.. _Sphinx-style: http://sphinx-doc.org/\n.. _PEP 8 - Style Guide for Python Code:\n  https://www.python.org/dev/peps/pep-0008\n\nUse ``certbot.compat.os`` instead of ``os``\n===========================================\n\n\nPython's standard library ``os`` module lacks full support for several Windows\nsecurity features about file permissions (eg. DACLs). However several files\nhandled by Certbot (eg. private keys) need strongly restricted access\non both Linux and Windows.\n\nTo help with this, the ``certbot.compat.os`` module wraps the standard\n``os`` module, and forbids usage of methods that lack support for these Windows\nsecurity features.\n\nAs a developer, when working on Certbot or its plugins, you must use ``certbot.compat.os``\nin every place you would need ``os`` (eg. ``from certbot.compat import os`` instead of\n``import os``). Otherwise the tests will fail when your PR is submitted.\n\n.. _type annotations:\n\nMypy type annotations\n=====================\n\nCertbot uses the `mypy`_ static type checker. Python 3 natively supports official type annotations,\nwhich can then be tested for consistency using mypy. Python 2 doesn’t, but type annotations can\nbe `added in comments`_. Mypy does some type checks even without type annotations; we can find\nbugs in Certbot even without a fully annotated codebase.\n\nCertbot supports both Python 2 and 3, so we’re using Python 2-style annotations.\n\nZulip wrote a `great guide`_ to using mypy. It’s useful, but you don’t have to read the whole thing\nto start contributing to Certbot.\n\nTo run mypy on Certbot, use ``tox -e mypy`` on a machine that has Python 3 installed.\n\nNote that instead of just importing ``typing``, due to packaging issues, in Certbot we import from\n``acme.magic_typing`` and have to add some comments for pylint like this:\n\n.. code-block:: python\n\n  from acme.magic_typing import Dict\n\nAlso note that OpenSSL, which we rely on, has type definitions for crypto but not SSL. We use both.\nThose imports should look like this:\n\n.. code-block:: python\n\n  from OpenSSL import crypto\n  from OpenSSL import SSL # type: ignore # https://github.com/python/typeshed/issues/2052\n\n.. _mypy: https://mypy.readthedocs.io\n.. _added in comments: https://mypy.readthedocs.io/en/latest/cheat_sheet.html\n.. _great guide: https://blog.zulip.org/2016/10/13/static-types-in-python-oh-mypy/\n\nSubmitting a pull request\n=========================\n\nSteps:\n\n1. Write your code! When doing this, you should add :ref:`mypy type annotations\n   <type annotations>` for any functions you add or modify. You can check that\n   you've done this correctly by running ``tox -e mypy`` on a machine that has\n   Python 3 installed.\n2. Make sure your environment is set up properly and that you're in your\n   virtualenv. You can do this by following the instructions in the\n   :ref:`Getting Started <getting_started>` section.\n3. Run ``tox -e lint`` to check for pylint errors. Fix any errors.\n4. Run ``tox --skip-missing-interpreters`` to run the entire test suite\n   including coverage. The ``--skip-missing-interpreters`` argument ignores\n   missing versions of Python needed for running the tests. Fix any errors.\n5. Submit the PR. Once your PR is open, please do not force push to the branch\n   containing your pull request to squash or amend commits. We use `squash\n   merges <https://github.com/blog/2141-squash-your-commits>`_ on PRs and\n   rewriting commits makes changes harder to track between reviews.\n6. Did your tests pass on Travis? If they didn't, fix any errors.\n\n.. _ask for help:\n\nAsking for help\n===============\n\nIf you have any questions while working on a Certbot issue, don't hesitate to\nask for help! You can do this in the Certbot channel in EFF's Mattermost\ninstance for its open source projects as described below.\n\nYou can get involved with several of EFF's software projects such as Certbot at\nthe `EFF Open Source Contributor Chat Platform\n<https://opensource.eff.org/signup_user_complete/?id=6iqur37ucfrctfswrs14iscobw>`_.\nBy signing up for the EFF Open Source Contributor Chat Platform, you consent to\nshare your personal information with the Electronic Frontier Foundation, which\nis the operator and data controller for this platform. The channels will be\navailable both to EFF, and to other users of EFFOSCCP, who may use or disclose\ninformation in these channels outside of EFFOSCCP. EFF will use your\ninformation, according to the `Privacy Policy <https://www.eff.org/policy>`_,\nto further the mission of EFF, including hosting and moderating the discussions\non this platform.\n\nUse of EFFOSCCP is subject to the `EFF Code of Conduct\n<https://www.eff.org/pages/eppcode>`_. When investigating an alleged Code of\nConduct violation, EFF may review discussion channels or direct messages.\n\nUpdating certbot-auto and letsencrypt-auto\n==========================================\n\n.. note:: We are currently only accepting changes to certbot-auto that fix\n  regressions on platforms where certbot-auto is the recommended installation\n  method at https://certbot.eff.org/instructions. If you are unsure if a change\n  you want to make qualifies, don't hesitate to `ask for help`_!\n\nUpdating the scripts\n--------------------\nDevelopers should *not* modify the ``certbot-auto`` and ``letsencrypt-auto`` files\nin the root directory of the repository.  Rather, modify the\n``letsencrypt-auto.template`` and associated platform-specific shell scripts in\nthe ``letsencrypt-auto-source`` and\n``letsencrypt-auto-source/pieces/bootstrappers`` directory, respectively.\n\nBuilding letsencrypt-auto-source/letsencrypt-auto\n-------------------------------------------------\nOnce changes to any of the aforementioned files have been made, the\n``letsencrypt-auto-source/letsencrypt-auto`` script should be updated.  In lieu of\nmanually updating this script, run the build script, which lives at\n``letsencrypt-auto-source/build.py``:\n\n.. code-block:: shell\n\n   python letsencrypt-auto-source/build.py\n\nRunning ``build.py`` will update the ``letsencrypt-auto-source/letsencrypt-auto``\nscript.  Note that the ``certbot-auto`` and ``letsencrypt-auto`` scripts in the root\ndirectory of the repository will remain **unchanged** after this script is run.\nYour changes will be propagated to these files during the next release of\nCertbot.\n\nOpening a PR\n------------\nWhen opening a PR, ensure that the following files are committed:\n\n1. ``letsencrypt-auto-source/letsencrypt-auto.template`` and\n   ``letsencrypt-auto-source/pieces/bootstrappers/*``\n2. ``letsencrypt-auto-source/letsencrypt-auto`` (generated by ``build.py``)\n\nIt might also be a good idea to double check that **no** changes were\ninadvertently made to the ``certbot-auto`` or ``letsencrypt-auto`` scripts in the\nroot of the repository.  These scripts will be updated by the core developers\nduring the next release.\n\n\nUpdating the documentation\n==========================\n\nMany of the packages in the Certbot repository have documentation in a\n``docs/`` directory. This directory is located under the top level directory\nfor the package. For instance, Certbot's documentation is under\n``certbot/docs``.\n\nTo build the documentation of a package, make sure you have followed the\ninstructions to set up a `local copy`_ of Certbot including activating the\nvirtual environment. After that, ``cd`` to the docs directory you want to build\nand run the command:\n\n.. code-block:: shell\n\n   make clean html\n\nThis would generate the HTML documentation in ``_build/html`` in your current\n``docs/`` directory.\n\n.. _docker-dev:\n\nRunning the client with Docker\n==============================\n\nYou can use Docker Compose to quickly set up an environment for running and\ntesting Certbot. To install Docker Compose, follow the instructions at\nhttps://docs.docker.com/compose/install/.\n\n.. note:: Linux users can simply run ``pip install docker-compose`` to get\n  Docker Compose after installing Docker Engine and activating your shell as\n  described in the :ref:`Getting Started <getting_started>` section.\n\nNow you can develop on your host machine, but run Certbot and test your changes\nin Docker. When using ``docker-compose`` make sure you are inside your clone of\nthe Certbot repository. As an example, you can run the following command to\ncheck for linting errors::\n\n  docker-compose run --rm --service-ports development bash -c 'tox -e lint'\n\nYou can also leave a terminal open running a shell in the Docker container and\nmodify Certbot code in another window. The Certbot repo on your host machine is\nmounted inside of the container so any changes you make immediately take\neffect. To do this, run::\n\n  docker-compose run --rm --service-ports development bash\n\nNow running the check for linting errors described above is as easy as::\n\n  tox -e lint\n\n.. _prerequisites:\n\nNotes on OS dependencies\n========================\n\nOS-level dependencies can be installed like so:\n\n.. code-block:: shell\n\n   ./certbot-auto --debug --os-packages-only\n\nIn general...\n\n* ``sudo`` is required as a suggested way of running privileged process\n* `Python`_ 2.7 or 3.5+ is required\n* `Augeas`_ is required for the Python bindings\n* ``virtualenv`` is used for managing other Python library dependencies\n\n.. _Python: https://wiki.python.org/moin/BeginnersGuide/Download\n.. _Augeas: http://augeas.net/\n.. _Virtualenv: https://virtualenv.pypa.io\n\n\nFreeBSD\n-------\n\nFreeBSD by default uses ``tcsh``. In order to activate virtualenv (see\nabove), you will need a compatible shell, e.g. ``pkg install bash &&\nbash``.\n"
  },
  {
    "path": "docs/index.rst",
    "content": "Welcome to the Certbot documentation!\n==================================================\n\n.. toctree::\n   :maxdepth: 2\n\n   intro\n   what\n   install\n   using\n   contributing\n   packaging\n   compatibility\n   resources\n\n.. toctree::\n   :maxdepth: 1\n\n   api\n\n\nIndices and tables\n==================\n\n* :ref:`genindex`\n* :ref:`modindex`\n* :ref:`search`\n"
  },
  {
    "path": "docs/install.rst",
    "content": "=====================\nGet Certbot\n=====================\n\n.. contents:: Table of Contents\n   :local:\n\n\nAbout Certbot\n=============\n\n*Certbot is meant to be run directly on a web server*, normally by a system administrator. In most cases, running Certbot on your personal computer is not a useful option. The instructions below relate to installing and running Certbot on a server.\n\nSystem administrators can use Certbot directly to request certificates; they should *not* allow unprivileged users to run arbitrary Certbot commands as ``root``, because Certbot allows its user to specify arbitrary file locations and run arbitrary scripts.\n\nCertbot is packaged for many common operating systems and web servers. Check whether\n``certbot`` (or ``letsencrypt``) is packaged for your web server's OS by visiting\ncertbot.eff.org_, where you will also find the correct installation instructions for\nyour system.\n\n.. Note:: Unless you have very specific requirements, we kindly suggest that you use the Certbot packages provided by your package manager (see certbot.eff.org_). If such packages are not available, we recommend using ``certbot-auto``, which automates the process of installing Certbot on your system.\n\n.. _certbot.eff.org: https://certbot.eff.org\n\n\n.. _system_requirements:\n\nSystem Requirements\n===================\n\nCertbot currently requires Python 2.7 or 3.5+ running on a UNIX-like operating\nsystem. By default, it requires root access in order to write to\n``/etc/letsencrypt``, ``/var/log/letsencrypt``, ``/var/lib/letsencrypt``; to\nbind to port 80 (if you use the ``standalone`` plugin) and to read and\nmodify webserver configurations (if you use the ``apache`` or ``nginx``\nplugins).  If none of these apply to you, it is theoretically possible to run\nwithout root privileges, but for most users who want to avoid running an ACME\nclient as root, either `letsencrypt-nosudo\n<https://github.com/diafygi/letsencrypt-nosudo>`_ or `simp_le\n<https://github.com/zenhack/simp_le>`_ are more appropriate choices.\n\nThe Apache plugin currently requires an OS with augeas version 1.0; currently `it\nsupports\n<https://github.com/certbot/certbot/blob/master/certbot-apache/certbot_apache/_internal/constants.py>`_\nmodern OSes based on Debian, Ubuntu, Fedora, SUSE, Gentoo and Darwin.\n\n\nAdditional integrity verification of certbot-auto script can be done by verifying its digital signature.\nThis requires a local installation of gpg2, which comes packaged in many Linux distributions under name gnupg or gnupg2.\n\n\nInstalling with ``certbot-auto`` requires 512MB of RAM in order to build some\nof the dependencies. Installing from pre-built OS packages avoids this\nrequirement. You can also temporarily set a swap file. See \"Problems with\nPython virtual environment\" below for details.\n\n\nAlternate installation methods\n================================\n\nIf you are offline or your operating system doesn't provide a package, you can use\nan alternate method for installing ``certbot``.\n\n.. _certbot-auto:\n\nCertbot-Auto\n------------\n\nThe ``certbot-auto`` wrapper script installs Certbot, obtaining some dependencies\nfrom your web server OS and putting others in a python virtual environment. You can\ndownload and run it as follows::\n\n  wget https://dl.eff.org/certbot-auto\n  sudo mv certbot-auto /usr/local/bin/certbot-auto\n  sudo chown root /usr/local/bin/certbot-auto\n  sudo chmod 0755 /usr/local/bin/certbot-auto\n  /usr/local/bin/certbot-auto --help\n\nTo remove certbot-auto, just delete it and the files it places under /opt/eff.org, along with any cronjob or systemd timer you may have created.\n\nTo check the integrity of the ``certbot-auto`` script,\nyou can use these steps::\n\n\n\t    user@webserver:~$ wget -N https://dl.eff.org/certbot-auto.asc\n\t    user@webserver:~$ gpg2 --keyserver pool.sks-keyservers.net --recv-key A2CFB51FA275A7286234E7B24D17C995CD9775F2\n\t    user@webserver:~$ gpg2 --trusted-key 4D17C995CD9775F2 --verify certbot-auto.asc /usr/local/bin/certbot-auto\n\n\n\nThe output of the last command should look something like::\n\n\n\t    gpg: Signature made Wed 02 May 2018 05:29:12 AM IST\n\t    gpg:                using RSA key A2CFB51FA275A7286234E7B24D17C995CD9775F2\n\t    gpg: key 4D17C995CD9775F2 marked as ultimately trusted\n\t    gpg: checking the trustdb\n\t    gpg: marginals needed: 3  completes needed: 1  trust model: pgp\n\t    gpg: depth: 0  valid:   2  signed:   2  trust: 0-, 0q, 0n, 0m, 0f, 2u\n\t    gpg: depth: 1  valid:   2  signed:   0  trust: 2-, 0q, 0n, 0m, 0f, 0u\n\t    gpg: next trustdb check due at 2027-11-22\n\t    gpg: Good signature from \"Let's Encrypt Client Team <letsencrypt-client@eff.org>\" [ultimate]\n\n\n\nThe ``certbot-auto`` command updates to the latest client release automatically.\nSince ``certbot-auto`` is a wrapper to ``certbot``, it accepts exactly\nthe same command line flags and arguments. For more information, see\n`Certbot command-line options <https://certbot.eff.org/docs/using.html#command-line-options>`_.\n\nFor full command line help, you can type::\n\n  /usr/local/bin/certbot-auto --help all\n\nProblems with Python virtual environment\n----------------------------------------\n\nOn a low memory system such as VPS with less than 512MB of RAM, the required dependencies of Certbot will fail to build.\nThis can be identified if the pip outputs contains something like ``internal compiler error: Killed (program cc1)``.\nYou can workaround this restriction by creating a temporary swapfile::\n\n  user@webserver:~$ sudo fallocate -l 1G /tmp/swapfile\n  user@webserver:~$ sudo chmod 600 /tmp/swapfile\n  user@webserver:~$ sudo mkswap /tmp/swapfile\n  user@webserver:~$ sudo swapon /tmp/swapfile\n\nDisable and remove the swapfile once the virtual environment is constructed::\n\n  user@webserver:~$ sudo swapoff /tmp/swapfile\n  user@webserver:~$ sudo rm /tmp/swapfile\n\n.. _docker-user:\n\nRunning with Docker\n-------------------\n\nDocker_ is an amazingly simple and quick way to obtain a\ncertificate. However, this mode of operation is unable to install\ncertificates or configure your webserver, because our installer\nplugins cannot reach your webserver from inside the Docker container.\n\nMost users should use the operating system packages (see instructions at\ncertbot.eff.org_) or, as a fallback, ``certbot-auto``. You should only\nuse Docker if you are sure you know what you are doing and have a\ngood reason to do so.\n\nYou should definitely read the :ref:`where-certs` section, in order to\nknow how to manage the certs\nmanually. `Our ciphersuites page <ciphers.html>`__\nprovides some information about recommended ciphersuites. If none of\nthese make much sense to you, you should definitely use the\ncertbot-auto_ method, which enables you to use installer plugins\nthat cover both of those hard topics.\n\nIf you're still not convinced and have decided to use this method, from\nthe server that the domain you're requesting a certficate for resolves\nto, `install Docker`_, then issue a command like the one found below. If\nyou are using Certbot with the :ref:`Standalone` plugin, you will need\nto make the port it uses accessible from outside of the container by\nincluding something like ``-p 80:80`` or ``-p 443:443`` on the command\nline before ``certbot/certbot``.\n\n.. code-block:: shell\n\n   sudo docker run -it --rm --name certbot \\\n               -v \"/etc/letsencrypt:/etc/letsencrypt\" \\\n               -v \"/var/lib/letsencrypt:/var/lib/letsencrypt\" \\\n               certbot/certbot certonly\n\nRunning Certbot with the ``certonly`` command will obtain a certificate and place it in the directory\n``/etc/letsencrypt/live`` on your system. Because Certonly cannot install the certificate from\nwithin Docker, you must install the certificate manually according to the procedure\nrecommended by the provider of your webserver.\n\nThere are also Docker images for each of Certbot's DNS plugins available\nat https://hub.docker.com/u/certbot which automate doing domain\nvalidation over DNS for popular providers. To use one, just replace\n``certbot/certbot`` in the command above with the name of the image you\nwant to use. For example, to use Certbot's plugin for Amazon Route 53,\nyou'd use ``certbot/dns-route53``. You may also need to add flags to\nCertbot and/or mount additional directories to provide access to your\nDNS API credentials as specified in the :ref:`DNS plugin documentation\n<dns_plugins>`. If you would like to obtain a wildcard certificate from\nLet's Encrypt's ACMEv2 server, you'll need to include ``--server\nhttps://acme-v02.api.letsencrypt.org/directory`` on the command line as\nwell.\n\nFor more information about the layout\nof the ``/etc/letsencrypt`` directory, see :ref:`where-certs`.\n\n.. _Docker: https://docker.com\n.. _`install Docker`: https://docs.docker.com/engine/installation/\n\nOperating System Packages\n-------------------------\n\n**Arch Linux**\n\n.. code-block:: shell\n\n   sudo pacman -S certbot\n\n**Debian**\n\nIf you run Debian Buster or Debian testing/Sid, you can easily install certbot\npackages through commands like:\n\n.. code-block:: shell\n\n   sudo apt-get update\n   sudo apt-get install certbot\n\nIf you run Debian Stretch, we recommend you use the packages in Debian\nbackports repository. First you'll have to follow the instructions at\nhttps://backports.debian.org/Instructions/ to enable the Stretch backports repo,\nif you have not already done so. Then run:\n\n.. code-block:: shell\n\n   sudo apt-get install certbot -t stretch-backports\n\nIn all of these cases, there also packages available to help Certbot integrate\nwith Apache, nginx, or various DNS services. If you are using Apache or nginx,\nwe strongly recommend that you install the ``python-certbot-apache`` or\n``python-certbot-nginx`` package so that Certbot can fully automate HTTPS\nconfiguration for your server. A full list of these packages can be found\nthrough a command like:\n\n.. code-block:: shell\n\n    apt search 'python-certbot*'\n\nThey can be installed by running the same installation command above but\nreplacing ``certbot`` with the name of the desired package.\n\nThere are no Certbot packages available for Debian Jessie and Jessie users\nshould instead use certbot-auto_.\n\n**Ubuntu**\n\nIf you run Ubuntu Trusty, Xenial, or Bionic, certbot is available through the official PPA,\nthat can be installed as followed:\n\n.. code-block:: shell\n\n   sudo apt-get update\n   sudo apt-get install software-properties-common\n   sudo add-apt-repository universe\n   sudo add-apt-repository ppa:certbot/certbot\n   sudo apt-get update\n\nThen, certbot can be installed using:\n\n.. code-block:: shell\n\n   sudo apt-get install certbot\n\nOptionally to install the Certbot Apache plugin, you can use:\n\n.. code-block:: shell\n\n   sudo apt-get install python-certbot-apache\n\n**Fedora**\n\n.. code-block:: shell\n\n    sudo dnf install certbot python2-certbot-apache\n\n**FreeBSD**\n\n  * Port: ``cd /usr/ports/security/py-certbot && make install clean``\n  * Package: ``pkg install py27-certbot``\n\n**Gentoo**\n\nThe official Certbot client is available in Gentoo Portage. If you\nwant to use the Apache plugin, it has to be installed separately:\n\n.. code-block:: shell\n\n   emerge -av app-crypt/certbot\n   emerge -av app-crypt/certbot-apache\n\nWhen using the Apache plugin, you will run into a \"cannot find an\nSSLCertificateFile directive\" or \"cannot find an SSLCertificateKeyFile\ndirective for certificate\" error if you're sporting the default Gentoo\n``httpd.conf``. You can fix this by commenting out two lines in\n``/etc/apache2/httpd.conf`` as follows:\n\nChange\n\n.. code-block:: shell\n\n   <IfDefine SSL>\n   LoadModule ssl_module modules/mod_ssl.so\n   </IfDefine>\n\nto\n\n.. code-block:: shell\n\n   #<IfDefine SSL>\n   LoadModule ssl_module modules/mod_ssl.so\n   #</IfDefine>\n\nFor the time being, this is the only way for the Apache plugin to recognise\nthe appropriate directives when installing the certificate.\nNote: this change is not required for the other plugins.\n\n**NetBSD**\n\n  * Build from source: ``cd /usr/pkgsrc/security/py-certbot && make install clean``\n  * Install pre-compiled package: ``pkg_add py27-certbot``\n\n**OpenBSD**\n\n  * Port: ``cd /usr/ports/security/letsencrypt/client && make install clean``\n  * Package: ``pkg_add letsencrypt``\n\n**Other Operating Systems**\n\nOS packaging is an ongoing effort. If you'd like to package\nCertbot for your distribution of choice please have a\nlook at the :doc:`packaging`.\n\nInstalling from source\n----------------------\n\nInstallation from source is only supported for developers and the\nwhole process is described in the :doc:`contributing`.\n\n.. warning:: Please do **not** use ``python certbot/setup.py install``, ``python pip\n   install certbot``, or ``easy_install certbot``. Please do **not** attempt the\n   installation commands as superuser/root and/or without virtual environment,\n   e.g. ``sudo python certbot/setup.py install``, ``sudo pip install``, ``sudo\n   ./venv/bin/...``. These modes of operation might corrupt your operating\n   system and are **not supported** by the Certbot team!\n"
  },
  {
    "path": "docs/intro.rst",
    "content": "=====================\nIntroduction\n=====================\n\n.. note::\n    To get started quickly, use the `interactive installation guide <https://certbot.eff.org>`_.\n\n.. include:: ../README.rst\n    :start-after: tag:intro-begin\n    :end-before: tag:intro-end\n"
  },
  {
    "path": "docs/make.bat",
    "content": "@ECHO OFF\r\n\r\nREM Command file for Sphinx documentation\r\n\r\nif \"%SPHINXBUILD%\" == \"\" (\r\n\tset SPHINXBUILD=sphinx-build\r\n)\r\nset BUILDDIR=_build\r\nset ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .\r\nset I18NSPHINXOPTS=%SPHINXOPTS% .\r\nif NOT \"%PAPER%\" == \"\" (\r\n\tset ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%\r\n\tset I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%\r\n)\r\n\r\nif \"%1\" == \"\" goto help\r\n\r\nif \"%1\" == \"help\" (\r\n\t:help\r\n\techo.Please use `make ^<target^>` where ^<target^> is one of\r\n\techo.  html       to make standalone HTML files\r\n\techo.  dirhtml    to make HTML files named index.html in directories\r\n\techo.  singlehtml to make a single large HTML file\r\n\techo.  pickle     to make pickle files\r\n\techo.  json       to make JSON files\r\n\techo.  htmlhelp   to make HTML files and a HTML help project\r\n\techo.  qthelp     to make HTML files and a qthelp project\r\n\techo.  devhelp    to make HTML files and a Devhelp project\r\n\techo.  epub       to make an epub\r\n\techo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\r\n\techo.  text       to make text files\r\n\techo.  man        to make manual pages\r\n\techo.  texinfo    to make Texinfo files\r\n\techo.  gettext    to make PO message catalogs\r\n\techo.  changes    to make an overview over all changed/added/deprecated items\r\n\techo.  xml        to make Docutils-native XML files\r\n\techo.  pseudoxml  to make pseudoxml-XML files for display purposes\r\n\techo.  linkcheck  to check all external links for integrity\r\n\techo.  doctest    to run all doctests embedded in the documentation if enabled\r\n\techo.  coverage   to run coverage check of the documentation if enabled\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"clean\" (\r\n\tfor /d %%i in (%BUILDDIR%\\*) do rmdir /q /s %%i\r\n\tdel /q /s %BUILDDIR%\\*\r\n\tgoto end\r\n)\r\n\r\n\r\nREM Check if sphinx-build is available and fallback to Python version if any\r\n%SPHINXBUILD% 2> nul\r\nif errorlevel 9009 goto sphinx_python\r\ngoto sphinx_ok\r\n\r\n:sphinx_python\r\n\r\nset SPHINXBUILD=python -m sphinx.__init__\r\n%SPHINXBUILD% 2> nul\r\nif errorlevel 9009 (\r\n\techo.\r\n\techo.The 'sphinx-build' command was not found. Make sure you have Sphinx\r\n\techo.installed, then set the SPHINXBUILD environment variable to point\r\n\techo.to the full path of the 'sphinx-build' executable. Alternatively you\r\n\techo.may add the Sphinx directory to PATH.\r\n\techo.\r\n\techo.If you don't have Sphinx installed, grab it from\r\n\techo.http://sphinx-doc.org/\r\n\texit /b 1\r\n)\r\n\r\n:sphinx_ok\r\n\r\n\r\nif \"%1\" == \"html\" (\r\n\t%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The HTML pages are in %BUILDDIR%/html.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"dirhtml\" (\r\n\t%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"singlehtml\" (\r\n\t%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"pickle\" (\r\n\t%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; now you can process the pickle files.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"json\" (\r\n\t%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; now you can process the JSON files.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"htmlhelp\" (\r\n\t%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; now you can run HTML Help Workshop with the ^\r\n.hhp project file in %BUILDDIR%/htmlhelp.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"qthelp\" (\r\n\t%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; now you can run \"qcollectiongenerator\" with the ^\r\n.qhcp project file in %BUILDDIR%/qthelp, like this:\r\n\techo.^> qcollectiongenerator %BUILDDIR%\\qthelp\\LetsEncrypt.qhcp\r\n\techo.To view the help file:\r\n\techo.^> assistant -collectionFile %BUILDDIR%\\qthelp\\LetsEncrypt.ghc\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"devhelp\" (\r\n\t%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"epub\" (\r\n\t%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The epub file is in %BUILDDIR%/epub.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"latex\" (\r\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished; the LaTeX files are in %BUILDDIR%/latex.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"latexpdf\" (\r\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\r\n\tcd %BUILDDIR%/latex\r\n\tmake all-pdf\r\n\tcd %~dp0\r\n\techo.\r\n\techo.Build finished; the PDF files are in %BUILDDIR%/latex.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"latexpdfja\" (\r\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\r\n\tcd %BUILDDIR%/latex\r\n\tmake all-pdf-ja\r\n\tcd %~dp0\r\n\techo.\r\n\techo.Build finished; the PDF files are in %BUILDDIR%/latex.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"text\" (\r\n\t%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The text files are in %BUILDDIR%/text.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"man\" (\r\n\t%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The manual pages are in %BUILDDIR%/man.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"texinfo\" (\r\n\t%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"gettext\" (\r\n\t%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The message catalogs are in %BUILDDIR%/locale.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"changes\" (\r\n\t%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.The overview file is in %BUILDDIR%/changes.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"linkcheck\" (\r\n\t%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Link check complete; look for any errors in the above output ^\r\nor in %BUILDDIR%/linkcheck/output.txt.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"doctest\" (\r\n\t%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Testing of doctests in the sources finished, look at the ^\r\nresults in %BUILDDIR%/doctest/output.txt.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"coverage\" (\r\n\t%SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Testing of coverage in the sources finished, look at the ^\r\nresults in %BUILDDIR%/coverage/python.txt.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"xml\" (\r\n\t%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The XML files are in %BUILDDIR%/xml.\r\n\tgoto end\r\n)\r\n\r\nif \"%1\" == \"pseudoxml\" (\r\n\t%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml\r\n\tif errorlevel 1 exit /b 1\r\n\techo.\r\n\techo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.\r\n\tgoto end\r\n)\r\n\r\n:end\r\n"
  },
  {
    "path": "docs/man/certbot.rst",
    "content": ".. literalinclude:: ../cli-help.txt\n"
  },
  {
    "path": "docs/packaging.rst",
    "content": "===============\nPackaging Guide\n===============\n\nReleases\n========\n\nWe release packages and upload them to PyPI (wheels and source tarballs).\n\n- https://pypi.python.org/pypi/acme\n- https://pypi.python.org/pypi/certbot\n- https://pypi.python.org/pypi/certbot-apache\n- https://pypi.python.org/pypi/certbot-nginx\n- https://pypi.python.org/pypi/certbot-dns-cloudflare\n- https://pypi.python.org/pypi/certbot-dns-cloudxns\n- https://pypi.python.org/pypi/certbot-dns-digitalocean\n- https://pypi.python.org/pypi/certbot-dns-dnsimple\n- https://pypi.python.org/pypi/certbot-dns-dnsmadeeasy\n- https://pypi.python.org/pypi/certbot-dns-google\n- https://pypi.python.org/pypi/certbot-dns-linode\n- https://pypi.python.org/pypi/certbot-dns-luadns\n- https://pypi.python.org/pypi/certbot-dns-nsone\n- https://pypi.python.org/pypi/certbot-dns-ovh\n- https://pypi.python.org/pypi/certbot-dns-rfc2136\n- https://pypi.python.org/pypi/certbot-dns-route53\n\nThe following scripts are used in the process:\n\n- https://github.com/certbot/certbot/blob/master/tools/release.sh\n\nWe use git tags to identify releases, using `Semantic Versioning`_. For\nexample: `v0.11.1`.\n\n.. _`Semantic Versioning`: http://semver.org/\n\nOur packages are cryptographically signed and their signature can be verified\nusing the PGP key ``A2CFB51FA275A7286234E7B24D17C995CD9775F2``. This key can be\nfound on major key servers and at https://dl.eff.org/certbot.pub.\n\nNotes for package maintainers\n=============================\n\n0. Please use our tagged releases, not ``master``!\n\n1. Do not package ``certbot-compatibility-test`` as it's only used internally.\n\n2. To run tests on our packages, you should use ``python setup.py test``. Doing things like running ``pytest`` directly on our package files may not work because Certbot relies on setuptools to register and find its plugins.\n\n3. If you'd like to include automated renewal in your package ``certbot renew -q`` should be added to crontab or systemd timer. Additionally you should include a random per-machine time offset to avoid having a large number of your clients hit Let's Encrypt's servers simultaneously.\n\n4. ``jws`` is an internal script for ``acme`` module and it doesn't have to be packaged - it's mostly for debugging: you can use it as ``echo foo | jws sign | jws verify``.\n\n5. Do get in touch with us. We are happy to make any changes that will make packaging easier. If you need to apply some patches don't do it downstream - make a PR here.\n"
  },
  {
    "path": "docs/resources.rst",
    "content": "=====================\nResources\n=====================\n\n.. include:: ../README.rst\n    :start-after: tag:links-begin\n    :end-before: tag:links-end\n"
  },
  {
    "path": "docs/using.rst",
    "content": "==========\nUser Guide\n==========\n\n.. contents:: Table of Contents\n   :local:\n\nCertbot Commands\n================\n\nCertbot uses a number of different commands (also referred\nto as \"subcommands\") to request specific actions such as\nobtaining, renewing, or revoking certificates. The most important\nand commonly-used commands will be discussed throughout this\ndocument; an exhaustive list also appears near the end of the document.\n\nThe ``certbot`` script on your web server might be named ``letsencrypt`` if your system uses an older package, or ``certbot-auto`` if you used an alternate installation method. Throughout the docs, whenever you see ``certbot``, swap in the correct name as needed.\n\n.. _plugins:\n\nGetting certificates (and choosing plugins)\n===========================================\n\nThe Certbot client supports two types of plugins for\nobtaining and installing certificates: authenticators and installers.\n\nAuthenticators are plugins used with the ``certonly`` command to obtain a certificate.\nThe authenticator validates that you\ncontrol the domain(s) you are requesting a certificate for, obtains a certificate for the specified\ndomain(s), and places the certificate in the ``/etc/letsencrypt`` directory on your\nmachine. The authenticator does not install the certificate (it does not edit any of your server's configuration files to serve the\nobtained certificate). If you specify multiple domains to authenticate, they will\nall be listed in a single certificate. To obtain multiple separate certificates\nyou will need to run Certbot multiple times.\n\nInstallers are Plugins used with the ``install`` command to install a certificate.\nThese plugins can modify your webserver's configuration to\nserve your website over HTTPS using certificates obtained by certbot.\n\nPlugins that do both can be used with the ``certbot run`` command, which is the default\nwhen no command is specified. The ``run`` subcommand can also be used to specify\na combination_ of distinct authenticator and installer plugins.\n\n=========== ==== ==== =============================================================== =============================\nPlugin      Auth Inst Notes                                                           Challenge types (and port)\n=========== ==== ==== =============================================================== =============================\napache_     Y    Y    | Automates obtaining and installing a certificate with Apache. http-01_ (80)\nnginx_      Y    Y    | Automates obtaining and installing a certificate with Nginx.  http-01_ (80)\nwebroot_    Y    N    | Obtains a certificate by writing to the webroot directory of  http-01_ (80)\n                      | an already running webserver.\nstandalone_ Y    N    | Uses a \"standalone\" webserver to obtain a certificate.        http-01_ (80)\n                      | Requires port 80 to be available. This is useful on\n                      | systems with no webserver, or when direct integration with\n                      | the local webserver is not supported or not desired.\n|dns_plugs| Y    N    | This category of plugins automates obtaining a certificate by dns-01_ (53)\n                      | modifying DNS records to prove you have control over a\n                      | domain. Doing domain validation in this way is\n                      | the only way to obtain wildcard certificates from Let's\n                      | Encrypt.\nmanual_     Y    N    | Helps you obtain a certificate by giving you instructions to  http-01_ (80) or\n                      | perform domain validation yourself. Additionally allows you   dns-01_ (53)\n                      | to specify scripts to automate the validation task in a\n                      | customized way.\n=========== ==== ==== =============================================================== =============================\n\n.. |dns_plugs| replace:: :ref:`DNS plugins <dns_plugins>`\n\nUnder the hood, plugins use one of several ACME protocol challenges_ to\nprove you control a domain. The options are http-01_ (which uses port 80)\nand dns-01_ (requiring configuration of a DNS server on\nport 53, though that's often not the same machine as your webserver). A few\nplugins support more than one challenge type, in which case you can choose one\nwith ``--preferred-challenges``.\n\nThere are also many third-party-plugins_ available. Below we describe in more detail\nthe circumstances in which each plugin can be used, and how to use it.\n\n.. _challenges: https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7\n.. _http-01: https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7.2\n.. _dns-01: https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7.4\n\nApache\n------\n\nThe Apache plugin currently `supports\n<https://github.com/certbot/certbot/blob/master/certbot-apache/certbot_apache/_internal/entrypoint.py>`_\nmodern OSes based on Debian, Fedora, SUSE, Gentoo and Darwin.\nThis automates both obtaining *and* installing certificates on an Apache\nwebserver. To specify this plugin on the command line, simply include\n``--apache``.\n\nWebroot\n-------\n\nIf you're running a local webserver for which you have the ability\nto modify the content being served, and you'd prefer not to stop the\nwebserver during the certificate issuance process, you can use the webroot\nplugin to obtain a certificate by including ``certonly`` and ``--webroot`` on\nthe command line. In addition, you'll need to specify ``--webroot-path``\nor ``-w`` with the top-level directory (\"web root\") containing the files\nserved by your webserver. For example, ``--webroot-path /var/www/html``\nor ``--webroot-path /usr/share/nginx/html`` are two common webroot paths.\n\nIf you're getting a certificate for many domains at once, the plugin\nneeds to know where each domain's files are served from, which could\npotentially be a separate directory for each domain. When requesting a\ncertificate for multiple domains, each domain will use the most recently\nspecified ``--webroot-path``. So, for instance,\n\n::\n\n    certbot certonly --webroot -w /var/www/example -d www.example.com -d example.com -w /var/www/other -d other.example.net -d another.other.example.net\n\nwould obtain a single certificate for all of those names, using the\n``/var/www/example`` webroot directory for the first two, and\n``/var/www/other`` for the second two.\n\nThe webroot plugin works by creating a temporary file for each of your requested\ndomains in ``${webroot-path}/.well-known/acme-challenge``. Then the Let's Encrypt\nvalidation server makes HTTP requests to validate that the DNS for each\nrequested domain resolves to the server running certbot. An example request\nmade to your web server would look like:\n\n::\n\n    66.133.109.36 - - [05/Jan/2016:20:11:24 -0500] \"GET /.well-known/acme-challenge/HGr8U1IeTW4kY_Z6UIyaakzOkyQgPr_7ArlLgtZE8SX HTTP/1.1\" 200 87 \"-\" \"Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)\"\n\nNote that to use the webroot plugin, your server must be configured to serve\nfiles from hidden directories. If ``/.well-known`` is treated specially by\nyour webserver configuration, you might need to modify the configuration\nto ensure that files inside ``/.well-known/acme-challenge`` are served by\nthe webserver.\n\nNginx\n-----\n\nThe Nginx plugin should work for most configurations. We recommend backing up\nNginx configurations before using it (though you can also revert changes to\nconfigurations with ``certbot --nginx rollback``). You can use it by providing\nthe ``--nginx`` flag on the commandline.\n\n::\n\n   certbot --nginx\n\n.. _standalone:\n\nStandalone\n----------\n\nUse standalone mode to obtain a certificate if you don't want to use (or don't currently have)\nexisting server software. The standalone plugin does not rely on any other server\nsoftware running on the machine where you obtain the certificate.\n\nTo obtain a certificate using a \"standalone\" webserver, you can use the\nstandalone plugin by including ``certonly`` and ``--standalone``\non the command line. This plugin needs to bind to port 80 in\norder to perform domain validation, so you may need to stop your\nexisting webserver.\n\nIt must still be possible for your machine to accept inbound connections from\nthe Internet on the specified port using each requested domain name.\n\nBy default, Certbot first attempts to bind to the port for all interfaces using\nIPv6 and then bind to that port using IPv4; Certbot continues so long as at\nleast one bind succeeds. On most Linux systems, IPv4 traffic will be routed to\nthe bound IPv6 port and the failure during the second bind is expected.\n\nUse ``--<challenge-type>-address`` to explicitly tell Certbot which interface\n(and protocol) to bind.\n\n.. _dns_plugins:\n\nDNS Plugins\n-----------\n\nIf you'd like to obtain a wildcard certificate from Let's Encrypt or run\n``certbot`` on a machine other than your target webserver, you can use one of\nCertbot's DNS plugins.\n\nThese plugins are not included in a default Certbot installation and must be\ninstalled separately. While the DNS plugins cannot currently be used with\n``certbot-auto``, they are available in many OS package managers and as Docker\nimages. Visit https://certbot.eff.org to learn the best way to use the DNS\nplugins on your system.\n\nOnce installed, you can find documentation on how to use each plugin at:\n\n* `certbot-dns-cloudflare <https://certbot-dns-cloudflare.readthedocs.io>`_\n* `certbot-dns-cloudxns <https://certbot-dns-cloudxns.readthedocs.io>`_\n* `certbot-dns-digitalocean <https://certbot-dns-digitalocean.readthedocs.io>`_\n* `certbot-dns-dnsimple <https://certbot-dns-dnsimple.readthedocs.io>`_\n* `certbot-dns-dnsmadeeasy <https://certbot-dns-dnsmadeeasy.readthedocs.io>`_\n* `certbot-dns-google <https://certbot-dns-google.readthedocs.io>`_\n* `certbot-dns-linode <https://certbot-dns-linode.readthedocs.io>`_\n* `certbot-dns-luadns <https://certbot-dns-luadns.readthedocs.io>`_\n* `certbot-dns-nsone <https://certbot-dns-nsone.readthedocs.io>`_\n* `certbot-dns-ovh <https://certbot-dns-ovh.readthedocs.io>`_\n* `certbot-dns-rfc2136 <https://certbot-dns-rfc2136.readthedocs.io>`_\n* `certbot-dns-route53 <https://certbot-dns-route53.readthedocs.io>`_\n\nManual\n------\n\nIf you'd like to obtain a certificate running ``certbot`` on a machine\nother than your target webserver or perform the steps for domain\nvalidation yourself, you can use the manual plugin. While hidden from\nthe UI, you can use the plugin to obtain a certificate by specifying\n``certonly`` and ``--manual`` on the command line. This requires you\nto copy and paste commands into another terminal session, which may\nbe on a different computer.\n\nThe manual plugin can use either the ``http`` or the ``dns`` challenge. You can use the ``--preferred-challenges`` option\nto choose the challenge of your preference.\n\nThe ``http`` challenge will ask you to place a file with a specific name and\nspecific content in the ``/.well-known/acme-challenge/`` directory directly\nin the top-level directory (“web root”) containing the files served by your\nwebserver. In essence it's the same as the webroot_ plugin, but not automated.\n\nWhen using the ``dns`` challenge, ``certbot`` will ask you to place a TXT DNS\nrecord with specific contents under the domain name consisting of the hostname\nfor which you want a certificate issued, prepended by ``_acme-challenge``.\n\nFor example, for the domain ``example.com``, a zone file entry would look like:\n\n::\n\n        _acme-challenge.example.com. 300 IN TXT \"gfj9Xq...Rg85nM\"\n\n\nAdditionally you can specify scripts to prepare for validation and\nperform the authentication procedure and/or clean up after it by using\nthe ``--manual-auth-hook`` and ``--manual-cleanup-hook`` flags. This is\ndescribed in more depth in the hooks_ section.\n\n.. _combination:\n\nCombining plugins\n-----------------\n\nSometimes you may want to specify a combination of distinct authenticator and\ninstaller plugins. To do so, specify the authenticator plugin with\n``--authenticator`` or ``-a`` and the installer plugin with ``--installer`` or\n``-i``.\n\nFor instance, you could create a certificate using the webroot_ plugin\nfor authentication and the apache_ plugin for installation.\n\n::\n\n    certbot run -a webroot -i apache -w /var/www/html -d example.com\n\nOr you could create a certificate using the manual_ plugin for authentication\nand the nginx_ plugin for installation. (Note that this certificate cannot\nbe renewed automatically.)\n\n::\n\n    certbot run -a manual -i nginx -d example.com\n\n.. _third-party-plugins:\n\nThird-party plugins\n-------------------\n\nThere are also a number of third-party plugins for the client, provided by\nother developers. Many are beta/experimental, but some are already in\nwidespread use:\n\n================== ==== ==== ===============================================================\nPlugin             Auth Inst Notes\n================== ==== ==== ===============================================================\nhaproxy_           Y    Y    Integration with the HAProxy load balancer\ns3front_           Y    Y    Integration with Amazon CloudFront distribution of S3 buckets\ngandi_             Y    N    Obtain certificates via the Gandi LiveDNS API\nvarnish_           Y    N    Obtain certificates via a Varnish server\nexternal-auth_     Y    Y    A plugin for convenient scripting\npritunl_           N    Y    Install certificates in pritunl distributed OpenVPN servers\nproxmox_           N    Y    Install certificates in Proxmox Virtualization servers\ndns-standalone_    Y    N    Obtain certificates via an integrated DNS server\ndns-ispconfig_     Y    N    DNS Authentication using ISPConfig as DNS server\n================== ==== ==== ===============================================================\n\n.. _haproxy: https://github.com/greenhost/certbot-haproxy\n.. _s3front: https://github.com/dlapiduz/letsencrypt-s3front\n.. _gandi: https://github.com/obynio/certbot-plugin-gandi\n.. _varnish: http://git.sesse.net/?p=letsencrypt-varnish-plugin\n.. _pritunl: https://github.com/kharkevich/letsencrypt-pritunl\n.. _proxmox: https://github.com/kharkevich/letsencrypt-proxmox\n.. _external-auth: https://github.com/EnigmaBridge/certbot-external-auth\n.. _dns-standalone: https://github.com/siilike/certbot-dns-standalone\n.. _dns-ispconfig: https://github.com/m42e/certbot-dns-ispconfig\n\nIf you're interested, you can also :ref:`write your own plugin <dev-plugin>`.\n\n.. _managing-certs:\n\nManaging certificates\n=====================\n\nTo view a list of the certificates Certbot knows about, run\nthe ``certificates`` subcommand:\n\n``certbot certificates``\n\nThis returns information in the following format::\n\n  Found the following certs:\n    Certificate Name: example.com\n      Domains: example.com, www.example.com\n      Expiry Date: 2017-02-19 19:53:00+00:00 (VALID: 30 days)\n      Certificate Path: /etc/letsencrypt/live/example.com/fullchain.pem\n      Private Key Path: /etc/letsencrypt/live/example.com/privkey.pem\n\n``Certificate Name`` shows the name of the certificate. Pass this name\nusing the ``--cert-name`` flag to specify a particular certificate for the ``run``,\n``certonly``, ``certificates``, ``renew``, and ``delete`` commands. Example::\n\n  certbot certonly --cert-name example.com\n\n.. _updating_certs:\n\nRe-creating and Updating Existing Certificates\n----------------------------------------------\n\nYou can use ``certonly`` or ``run`` subcommands to request\nthe creation of a single new certificate even if you already have an\nexisting certificate with some of the same domain names.\n\nIf a certificate is requested with ``run`` or ``certonly`` specifying a\ncertificate name that already exists, Certbot updates\nthe existing certificate. Otherwise a new certificate\nis created and assigned the specified name.\n\nThe ``--force-renewal``, ``--duplicate``, and ``--expand`` options\ncontrol Certbot's behavior when re-creating\na certificate with the same name as an existing certificate.\nIf you don't specify a requested behavior, Certbot may ask you what you intended.\n\n\n``--force-renewal`` tells Certbot to request a new certificate\nwith the same domains as an existing certificate. Each domain\nmust be explicitly specified via ``-d``. If successful, this certificate\nis saved alongside the earlier one and symbolic links (the \"``live``\"\nreference) will be updated to point to the new certificate. This is a\nvalid method of renewing a specific individual\ncertificate.\n\n``--duplicate`` tells Certbot to create a separate, unrelated certificate\nwith the same domains as an existing certificate. This certificate is\nsaved completely separately from the prior one. Most users will not\nneed to issue this command in normal circumstances.\n\n``--expand`` tells Certbot to update an existing certificate with a new\ncertificate that contains all of the old domains and one or more additional\nnew domains. With the ``--expand`` option, use the ``-d`` option to specify\nall existing domains and one or more new domains.\n\nExample:\n\n.. code-block:: none\n\n  certbot --expand -d existing.com,example.com,newdomain.com\n\nIf you prefer, you can specify the domains individually like this:\n\n.. code-block:: none\n\n  certbot --expand -d existing.com -d example.com -d newdomain.com\n\nConsider using ``--cert-name`` instead of ``--expand``, as it gives more control\nover which certificate is modified and it lets you remove domains as well as adding them.\n\n\n``--allow-subset-of-names`` tells Certbot to continue with certificate generation if\nonly some of the specified domain authorizations can be obtained. This may\nbe useful if some domains specified in a certificate no longer point at this\nsystem.\n\nWhenever you obtain a new certificate in any of these ways, the new\ncertificate exists alongside any previously obtained certificates, whether\nor not the previous certificates have expired. The generation of a new\ncertificate counts against several rate limits that are intended to prevent\nabuse of the ACME protocol, as described\n`here <https://community.letsencrypt.org/t/rate-limits-for-lets-encrypt/6769>`__.\n\n.. _changing:\n\nChanging a Certificate's Domains\n================================\n\nThe ``--cert-name`` flag can also be used to modify the domains a certificate contains,\nby specifying new domains using the ``-d`` or ``--domains`` flag. If certificate ``example.com``\npreviously contained ``example.com`` and ``www.example.com``, it can be modified to only\ncontain ``example.com`` by specifying only ``example.com`` with the ``-d`` or ``--domains`` flag. Example::\n\n  certbot certonly --cert-name example.com -d example.com\n\nThe same format can be used to expand the set of domains a certificate contains, or to\nreplace that set entirely::\n\n  certbot certonly --cert-name example.com -d example.org,www.example.org\n\n\nRevoking certificates\n---------------------\n\nIf your account key has been compromised or you otherwise need to revoke a certificate,\nuse the ``revoke`` command to do so. Note that the ``revoke`` command takes the certificate path\n(ending in ``cert.pem``), not a certificate name or domain. Example::\n\n  certbot revoke --cert-path /etc/letsencrypt/live/CERTNAME/cert.pem\n\nYou can also specify the reason for revoking your certificate by using the ``reason`` flag.\nReasons include ``unspecified`` which is the default, as well as ``keycompromise``,\n``affiliationchanged``, ``superseded``, and ``cessationofoperation``::\n\n  certbot revoke --cert-path /etc/letsencrypt/live/CERTNAME/cert.pem --reason keycompromise\n\nAdditionally, if a certificate\nis a test certificate obtained via the ``--staging`` or ``--test-cert`` flag, that flag must be passed to the\n``revoke`` subcommand.\nOnce a certificate is revoked (or for other certificate management tasks), all of a certificate's\nrelevant files can be removed from the system with the ``delete`` subcommand::\n\n  certbot delete --cert-name example.com\n\n.. note:: If you don't use ``delete`` to remove the certificate completely, it will be renewed automatically at the next renewal event.\n\n.. note:: Revoking a certificate will have no effect on the rate limit imposed by the Let's Encrypt server.\n\n.. _renewal:\n\nRenewing certificates\n---------------------\n\n.. note:: Let's Encrypt CA issues short-lived certificates (90\n   days). Make sure you renew the certificates at least once in 3\n   months.\n\n.. seealso:: Many of the certbot clients obtained through a\n   distribution come with automatic renewal out of the box,\n   such as Debian and Ubuntu versions installed through `apt`,\n   CentOS/RHEL 7 through EPEL, etc.  See `Automated Renewals`_\n   for more details.\n\nAs of version 0.10.0, Certbot supports a ``renew`` action to check\nall installed certificates for impending expiry and attempt to renew\nthem. The simplest form is simply\n\n``certbot renew``\n\nThis command attempts to renew any previously-obtained certificates that\nexpire in less than 30 days. The same plugin and options that were used\nat the time the certificate was originally issued will be used for the\nrenewal attempt, unless you specify other plugins or options. Unlike ``certonly``, ``renew`` acts on\nmultiple certificates and always takes into account whether each one is near\nexpiry. Because of this, ``renew`` is suitable (and designed) for automated use,\nto allow your system to automatically renew each certificate when appropriate.\nSince ``renew`` only renews certificates that are near expiry it can be\nrun as frequently as you want - since it will usually take no action.\n\nThe ``renew`` command includes hooks for running commands or scripts before or after a certificate is\nrenewed. For example, if you have a single certificate obtained using\nthe standalone_ plugin, you might need to stop the webserver\nbefore renewing so standalone can bind to the necessary ports, and\nthen restart it after the plugin is finished. Example::\n\n  certbot renew --pre-hook \"service nginx stop\" --post-hook \"service nginx start\"\n\nIf a hook exits with a non-zero exit code, the error will be printed\nto ``stderr`` but renewal will be attempted anyway. A failing hook\ndoesn't directly cause Certbot to exit with a non-zero exit code, but\nsince Certbot exits with a non-zero exit code when renewals fail, a\nfailed hook causing renewal failures will indirectly result in a\nnon-zero exit code. Hooks will only be run if a certificate is due for\nrenewal, so you can run the above command frequently without\nunnecessarily stopping your webserver.\n\nWhen Certbot detects that a certificate is due for renewal, ``--pre-hook``\nand ``--post-hook`` hooks run before and after each attempt to renew it.\nIf you want your hook to run only after a successful renewal, use\n``--deploy-hook`` in a command like this.\n\n``certbot renew --deploy-hook /path/to/deploy-hook-script``\n\nYou can also specify hooks by placing files in subdirectories of Certbot's\nconfiguration directory. Assuming your configuration directory is\n``/etc/letsencrypt``, any executable files found in\n``/etc/letsencrypt/renewal-hooks/pre``,\n``/etc/letsencrypt/renewal-hooks/deploy``, and\n``/etc/letsencrypt/renewal-hooks/post`` will be run as pre, deploy, and post\nhooks respectively when any certificate is renewed with the ``renew``\nsubcommand. These hooks are run in alphabetical order and are not run for other\nsubcommands. (The order the hooks are run is determined by the byte value of\nthe characters in their filenames and is not dependent on your locale.)\n\nHooks specified in the command line, :ref:`configuration file\n<config-file>`, or :ref:`renewal configuration files <renewal-config-file>` are\nrun as usual after running all hooks in these directories. One minor exception\nto this is if a hook specified elsewhere is simply the path to an executable\nfile in the hook directory of the same type (e.g. your pre-hook is the path to\nan executable in ``/etc/letsencrypt/renewal-hooks/pre``), the file is not run a\nsecond time. You can stop Certbot from automatically running executables found\nin these directories by including ``--no-directory-hooks`` on the command line.\n\nMore information about hooks can be found by running\n``certbot --help renew``.\n\nIf you're sure that this command executes successfully without human\nintervention, you can add the command to ``crontab`` (since certificates\nare only renewed when they're determined to be near expiry, the command\ncan run on a regular basis, like every week or every day). In that case,\nyou are likely to want to use the ``-q`` or ``--quiet`` quiet flag to\nsilence all output except errors.\n\nIf you are manually renewing all of your certificates, the\n``--force-renewal`` flag may be helpful; it causes the expiration time of\nthe certificate(s) to be ignored when considering renewal, and attempts to\nrenew each and every installed certificate regardless of its age. (This\nform is not appropriate to run daily because each certificate will be\nrenewed every day, which will quickly run into the certificate authority\nrate limit.)\n\nNote that options provided to ``certbot renew`` will apply to\n*every* certificate for which renewal is attempted; for example,\n``certbot renew --rsa-key-size 4096`` would try to replace every\nnear-expiry certificate with an equivalent certificate using a 4096-bit\nRSA public key. If a certificate is successfully renewed using\nspecified options, those options will be saved and used for future\nrenewals of that certificate.\n\nAn alternative form that provides for more fine-grained control over the\nrenewal process (while renewing specified certificates one at a time),\nis ``certbot certonly`` with the complete set of subject domains of\na specific certificate specified via `-d` flags. You may also want to\ninclude the ``-n`` or ``--noninteractive`` flag to prevent blocking on\nuser input (which is useful when running the command from cron).\n\n``certbot certonly -n -d example.com -d www.example.com``\n\nAll of the domains covered by the certificate must be specified in\nthis case in order to renew and replace the old certificate rather\nthan obtaining a new one; don't forget any `www.` domains! Specifying\na subset of the domains creates a new, separate certificate containing\nonly those domains, rather than replacing the original certificate.\nWhen run with a set of domains corresponding to an existing certificate,\nthe ``certonly`` command attempts to renew that specific certificate.\n\nPlease note that the CA will send notification emails to the address\nyou provide if you do not renew certificates that are about to expire.\n\nCertbot is working hard to improve the renewal process, and we\napologize for any inconvenience you encounter in integrating these\ncommands into your individual environment.\n\n.. note:: ``certbot renew`` exit status will only be 1 if a renewal attempt failed.\n  This means ``certbot renew`` exit status will be 0 if no certificate needs to be updated.\n  If you write a custom script and expect to run a command only after a certificate was actually renewed\n  you will need to use the ``--deploy-hook`` since the exit status will be 0 both on successful renewal\n  and when renewal is not necessary.\n\n.. _renewal-config-file:\n\n\nModifying the Renewal Configuration File\n----------------------------------------\n\nWhen a certificate is issued, by default Certbot creates a renewal configuration file that\ntracks the options that were selected when Certbot was run. This allows Certbot\nto use those same options again when it comes time for renewal. These renewal\nconfiguration files are located at ``/etc/letsencrypt/renewal/CERTNAME``.\n\nFor advanced certificate management tasks, it is possible to manually modify the certificate's\nrenewal configuration file, but this is discouraged since it can easily break Certbot's\nability to renew your certificates. If you choose to modify the renewal configuration file\nwe advise you to test its validity with the ``certbot renew --dry-run`` command.\n\n.. warning:: Modifying any files in ``/etc/letsencrypt`` can damage them so Certbot can no longer properly manage its certificates, and we do not recommend doing so.\n\nFor most tasks, it is safest to limit yourself to pointing symlinks at the files there, or using\n``--deploy-hook`` to copy / make new files based upon those files, if your operational situation requires it\n(for instance, combining certificates and keys in different way, or having copies of things with different\nspecific permissions that are demanded by other programs).\n\nIf the contents of ``/etc/letsencrypt/archive/CERTNAME`` are moved to a new folder, first specify\nthe new folder's name in the renewal configuration file, then run ``certbot update_symlinks`` to\npoint the symlinks in ``/etc/letsencrypt/live/CERTNAME`` to the new folder.\n\nIf you would like the live certificate files whose symlink location Certbot updates on each run to\nreside in a different location, first move them to that location, then specify the full path of\neach of the four files in the renewal configuration file. Since the symlinks are relative links,\nyou must follow this with an invocation of ``certbot update_symlinks``.\n\nFor example, say that a certificate's renewal configuration file previously contained the following\ndirectives::\n\n  archive_dir = /etc/letsencrypt/archive/example.com\n  cert = /etc/letsencrypt/live/example.com/cert.pem\n  privkey = /etc/letsencrypt/live/example.com/privkey.pem\n  chain = /etc/letsencrypt/live/example.com/chain.pem\n  fullchain = /etc/letsencrypt/live/example.com/fullchain.pem\n\nThe following commands could be used to specify where these files are located::\n\n  mv /etc/letsencrypt/archive/example.com /home/user/me/certbot/example_archive\n  sed -i 's,/etc/letsencrypt/archive/example.com,/home/user/me/certbot/example_archive,' /etc/letsencrypt/renewal/example.com.conf\n  mv /etc/letsencrypt/live/example.com/*.pem /home/user/me/certbot/\n  sed -i 's,/etc/letsencrypt/live/example.com,/home/user/me/certbot,g' /etc/letsencrypt/renewal/example.com.conf\n  certbot update_symlinks\n\nAutomated Renewals\n------------------\n\nMany Linux distributions provide automated renewal when you use the\npackages installed through their system package manager.  The\nfollowing table is an *incomplete* list of distributions which do so,\nas well as their methods for doing so.\n\nIf you are not sure whether or not your system has this already\nautomated, refer to your distribution's documentation, or check your\nsystem's crontab (typically in `/etc/crontab/` and `/etc/cron.*/*` and\nsystemd timers (`systemctl list-timers`).\n\n.. csv-table:: Distributions with Automated Renewal\n   :header: \"Distribution Name\", \"Distribution Version\", \"Automation Method\"\n\n   \"CentOS\", \"EPEL 7\", \"systemd\"\n   \"Debian\", \"jessie\", \"cron, systemd\"\n   \"Debian\", \"stretch\", \"cron, systemd\"\n   \"Debian\", \"testing/sid\", \"cron, systemd\"\n   \"Fedora\", \"26\", \"systemd\"\n   \"Fedora\", \"27\", \"systemd\"\n   \"RHEL\", \"EPEL 7\", \"systemd\"\n   \"Ubuntu\", \"17.10\", \"cron, systemd\"\n   \"Ubuntu\", \"certbot PPA\", \"cron, systemd\"\n\n.. _where-certs:\n\nWhere are my certificates?\n==========================\n\nAll generated keys and issued certificates can be found in\n``/etc/letsencrypt/live/$domain``. In the case of creating a SAN certificate\nwith multiple alternative names, ``$domain`` is the first domain passed in\nvia -d parameter. Rather than copying, please point\nyour (web) server configuration directly to those files (or create\nsymlinks). During the renewal_, ``/etc/letsencrypt/live`` is updated\nwith the latest necessary files.\n\nFor historical reasons, the containing directories are created with\npermissions of ``0700`` meaning that certificates are accessible only\nto servers that run as the root user.  **If you will never downgrade\nto an older version of Certbot**, then you can safely fix this using\n``chmod 0755 /etc/letsencrypt/{live,archive}``.\n\nFor servers that drop root privileges before attempting to read the\nprivate key file, you will also need to use ``chgrp`` and ``chmod\n0640`` to allow the server to read\n``/etc/letsencrypt/live/$domain/privkey.pem``.\n\n.. note:: ``/etc/letsencrypt/archive`` and ``/etc/letsencrypt/keys``\n   contain all previous keys and certificates, while\n   ``/etc/letsencrypt/live`` symlinks to the latest versions.\n\nThe following files are available:\n\n``privkey.pem``\n  Private key for the certificate.\n\n  .. warning:: This **must be kept secret at all times**! Never share\n     it with anyone, including Certbot developers. You cannot\n     put it into a safe, however - your server still needs to access\n     this file in order for SSL/TLS to work.\n\n  .. note:: As of Certbot version 0.29.0, private keys for new certificate\n     default to ``0600``. Any changes to the group mode or group owner (gid)\n     of this file will be preserved on renewals.\n\n  This is what Apache needs for `SSLCertificateKeyFile\n  <https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslcertificatekeyfile>`_,\n  and Nginx for `ssl_certificate_key\n  <http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate_key>`_.\n\n``fullchain.pem``\n  All certificates, **including** server certificate (aka leaf certificate or\n  end-entity certificate). The server certificate is the first one in this file,\n  followed by any intermediates.\n\n  This is what Apache >= 2.4.8 needs for `SSLCertificateFile\n  <https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslcertificatefile>`_,\n  and what Nginx needs for `ssl_certificate\n  <http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate>`_.\n\n``cert.pem`` and ``chain.pem`` (less common)\n  ``cert.pem`` contains the server certificate by itself, and\n  ``chain.pem`` contains the additional intermediate certificate or\n  certificates that web browsers will need in order to validate the\n  server certificate. If you provide one of these files to your web\n  server, you **must** provide both of them, or some browsers will show\n  \"This Connection is Untrusted\" errors for your site, `some of the time\n  <https://whatsmychaincert.com/>`_.\n\n  Apache < 2.4.8 needs these for `SSLCertificateFile\n  <https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslcertificatefile>`_.\n  and `SSLCertificateChainFile\n  <https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslcertificatechainfile>`_,\n  respectively.\n\n  If you're using OCSP stapling with Nginx >= 1.3.7, ``chain.pem`` should be\n  provided as the `ssl_trusted_certificate\n  <http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_trusted_certificate>`_\n  to validate OCSP responses.\n\n.. note:: All files are PEM-encoded.\n   If you need other format, such as DER or PFX, then you\n   could convert using ``openssl``. You can automate that with\n   ``--deploy-hook`` if you're using automatic renewal_.\n\n.. _hooks:\n\nPre and Post Validation Hooks\n=============================\n\nCertbot allows for the specification of pre and post validation hooks when run\nin manual mode. The flags to specify these scripts are ``--manual-auth-hook``\nand ``--manual-cleanup-hook`` respectively and can be used as follows:\n\n::\n\n certbot certonly --manual --manual-auth-hook /path/to/http/authenticator.sh --manual-cleanup-hook /path/to/http/cleanup.sh -d secure.example.com\n\nThis will run the ``authenticator.sh`` script, attempt the validation, and then run\nthe ``cleanup.sh`` script. Additionally certbot will pass relevant environment\nvariables to these scripts:\n\n- ``CERTBOT_DOMAIN``: The domain being authenticated\n- ``CERTBOT_VALIDATION``: The validation string (HTTP-01 and DNS-01 only)\n- ``CERTBOT_TOKEN``: Resource name part of the HTTP-01 challenge (HTTP-01 only)\n\nAdditionally for cleanup:\n\n- ``CERTBOT_AUTH_OUTPUT``: Whatever the auth script wrote to stdout\n\nExample usage for HTTP-01:\n\n::\n\n certbot certonly --manual --preferred-challenges=http --manual-auth-hook /path/to/http/authenticator.sh --manual-cleanup-hook /path/to/http/cleanup.sh -d secure.example.com\n\n/path/to/http/authenticator.sh\n\n.. code-block:: none\n\n   #!/bin/bash\n   echo $CERTBOT_VALIDATION > /var/www/htdocs/.well-known/acme-challenge/$CERTBOT_TOKEN\n\n/path/to/http/cleanup.sh\n\n.. code-block:: none\n\n   #!/bin/bash\n   rm -f /var/www/htdocs/.well-known/acme-challenge/$CERTBOT_TOKEN\n\nExample usage for DNS-01 (Cloudflare API v4) (for example purposes only, do not use as-is)\n\n::\n\n certbot certonly --manual --preferred-challenges=dns --manual-auth-hook /path/to/dns/authenticator.sh --manual-cleanup-hook /path/to/dns/cleanup.sh -d secure.example.com\n\n/path/to/dns/authenticator.sh\n\n.. code-block:: none\n\n   #!/bin/bash\n\n   # Get your API key from https://www.cloudflare.com/a/account/my-account\n   API_KEY=\"your-api-key\"\n   EMAIL=\"your.email@example.com\"\n\n   # Strip only the top domain to get the zone id\n   DOMAIN=$(expr match \"$CERTBOT_DOMAIN\" '.*\\.\\(.*\\..*\\)')\n\n   # Get the Cloudflare zone id\n   ZONE_EXTRA_PARAMS=\"status=active&page=1&per_page=20&order=status&direction=desc&match=all\"\n   ZONE_ID=$(curl -s -X GET \"https://api.cloudflare.com/client/v4/zones?name=$DOMAIN&$ZONE_EXTRA_PARAMS\" \\\n        -H     \"X-Auth-Email: $EMAIL\" \\\n        -H     \"X-Auth-Key: $API_KEY\" \\\n        -H     \"Content-Type: application/json\" | python -c \"import sys,json;print(json.load(sys.stdin)['result'][0]['id'])\")\n\n   # Create TXT record\n   CREATE_DOMAIN=\"_acme-challenge.$CERTBOT_DOMAIN\"\n   RECORD_ID=$(curl -s -X POST \"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records\" \\\n        -H     \"X-Auth-Email: $EMAIL\" \\\n        -H     \"X-Auth-Key: $API_KEY\" \\\n        -H     \"Content-Type: application/json\" \\\n        --data '{\"type\":\"TXT\",\"name\":\"'\"$CREATE_DOMAIN\"'\",\"content\":\"'\"$CERTBOT_VALIDATION\"'\",\"ttl\":120}' \\\n                | python -c \"import sys,json;print(json.load(sys.stdin)['result']['id'])\")\n   # Save info for cleanup\n   if [ ! -d /tmp/CERTBOT_$CERTBOT_DOMAIN ];then\n           mkdir -m 0700 /tmp/CERTBOT_$CERTBOT_DOMAIN\n   fi\n   echo $ZONE_ID > /tmp/CERTBOT_$CERTBOT_DOMAIN/ZONE_ID\n   echo $RECORD_ID > /tmp/CERTBOT_$CERTBOT_DOMAIN/RECORD_ID\n\n   # Sleep to make sure the change has time to propagate over to DNS\n   sleep 25\n\n/path/to/dns/cleanup.sh\n\n.. code-block:: none\n\n   #!/bin/bash\n\n   # Get your API key from https://www.cloudflare.com/a/account/my-account\n   API_KEY=\"your-api-key\"\n   EMAIL=\"your.email@example.com\"\n\n   if [ -f /tmp/CERTBOT_$CERTBOT_DOMAIN/ZONE_ID ]; then\n           ZONE_ID=$(cat /tmp/CERTBOT_$CERTBOT_DOMAIN/ZONE_ID)\n           rm -f /tmp/CERTBOT_$CERTBOT_DOMAIN/ZONE_ID\n   fi\n\n   if [ -f /tmp/CERTBOT_$CERTBOT_DOMAIN/RECORD_ID ]; then\n           RECORD_ID=$(cat /tmp/CERTBOT_$CERTBOT_DOMAIN/RECORD_ID)\n           rm -f /tmp/CERTBOT_$CERTBOT_DOMAIN/RECORD_ID\n   fi\n\n   # Remove the challenge TXT record from the zone\n   if [ -n \"${ZONE_ID}\" ]; then\n       if [ -n \"${RECORD_ID}\" ]; then\n           curl -s -X DELETE \"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID\" \\\n                   -H \"X-Auth-Email: $EMAIL\" \\\n                   -H \"X-Auth-Key: $API_KEY\" \\\n                   -H \"Content-Type: application/json\"\n       fi\n   fi\n\n.. _lock-files:\n\nChanging the ACME Server\n========================\n\nBy default, Certbot uses Let's Encrypt's initial production server at\nhttps://acme-v01.api.letsencrypt.org/. You can tell Certbot to use a\ndifferent CA by providing ``--server`` on the command line or in a\n:ref:`configuration file <config-file>` with the URL of the server's\nACME directory. For example, if you would like to use Let's Encrypt's\nnew ACMEv2 server, you would add ``--server\nhttps://acme-v02.api.letsencrypt.org/directory`` to the command line.\nCertbot will automatically select which version of the ACME protocol to\nuse based on the contents served at the provided URL.\n\nIf you use ``--server`` to specify an ACME CA that implements a newer\nversion of the spec, you may be able to obtain a certificate for a\nwildcard domain. Some CAs (such as Let's Encrypt) require that domain\nvalidation for wildcard domains must be done through modifications to\nDNS records which means that the dns-01_ challenge type must be used. To\nsee a list of Certbot plugins that support this challenge type and how\nto use them, see plugins_.\n\nLock Files\n==========\n\nWhen processing a validation Certbot writes a number of lock files on your system\nto prevent multiple instances from overwriting each other's changes. This means\nthat by default two instances of Certbot will not be able to run in parallel.\n\nSince the directories used by Certbot are configurable, Certbot\nwill write a lock file for all of the directories it uses. This include Certbot's\n``--work-dir``, ``--logs-dir``, and ``--config-dir``. By default these are\n``/var/lib/letsencrypt``, ``/var/log/letsencrypt``, and ``/etc/letsencrypt``\nrespectively. Additionally if you are using Certbot with Apache or nginx it will\nlock the configuration folder for that program, which are typically also in the\n``/etc`` directory.\n\nNote that these lock files will only prevent other instances of Certbot from\nusing those directories, not other processes. If you'd like to run multiple\ninstances of Certbot simultaneously you should specify different directories\nas the ``--work-dir``, ``--logs-dir``, and ``--config-dir`` for each instance\nof Certbot that you would like to run.\n\n.. _config-file:\n\nConfiguration file\n==================\n\nCertbot accepts a global configuration file that applies its options to all invocations\nof Certbot. Certificate specific configuration choices should be set in the ``.conf``\nfiles that can be found in ``/etc/letsencrypt/renewal``.\n\nBy default no cli.ini file is created (though it may exist already if you installed Certbot\nvia a package manager, for instance).\nAfter creating one it is possible to specify the location of this configuration file with\n``certbot --config cli.ini`` (or shorter ``-c cli.ini``). An\nexample configuration file is shown below:\n\n.. include:: ../examples/cli.ini\n   :code: ini\n\nBy default, the following locations are searched:\n\n- ``/etc/letsencrypt/cli.ini``\n- ``$XDG_CONFIG_HOME/letsencrypt/cli.ini`` (or\n  ``~/.config/letsencrypt/cli.ini`` if ``$XDG_CONFIG_HOME`` is not\n  set).\n\nSince this configuration file applies to all invocations of certbot it is incorrect\nto list domains in it. Listing domains in cli.ini may prevent renewal from working.\nAdditionally due to how arguments in cli.ini are parsed, options which wish to\nnot be set should not be listed. Options set to false will instead be read\nas being set to true by older versions of Certbot, since they have been listed\nin the config file.\n\n.. keep it up to date with constants.py\n\n.. _log-rotation:\n\nLog Rotation\n============\n\nBy default certbot stores status logs in ``/var/log/letsencrypt``. By default\ncertbot will begin rotating logs once there are 1000 logs in the log directory.\nMeaning that once 1000 files are in ``/var/log/letsencrypt`` Certbot will delete\nthe oldest one to make room for new logs. The number of subsequent logs can be\nchanged by passing the desired number to the command line flag\n``--max-log-backups``.\n\n.. note:: Some distributions, including Debian and Ubuntu, disable\n   certbot's internal log rotation in favor of a more traditional\n   logrotate script.  If you are using a distribution's packages and\n   want to alter the log rotation, check `/etc/logrotate.d/` for a\n   certbot rotation script.\n\n.. _command-line:\n\nCertbot command-line options\n============================\n\nCertbot supports a lot of command line options. Here's the full list, from\n``certbot --help all``:\n\n.. literalinclude:: cli-help.txt\n\nGetting help\n============\n\nIf you're having problems, we recommend posting on the Let's Encrypt\n`Community Forum <https://community.letsencrypt.org>`_.\n\nIf you find a bug in the software, please do report it in our `issue\ntracker <https://github.com/certbot/certbot/issues>`_. Remember to\ngive us as much information as possible:\n\n- copy and paste exact command line used and the output (though mind\n  that the latter might include some personally identifiable\n  information, including your email and domains)\n- copy and paste logs from ``/var/log/letsencrypt`` (though mind they\n  also might contain personally identifiable information)\n- copy and paste ``certbot --version`` output\n- your operating system, including specific version\n- specify which installation method you've chosen\n"
  },
  {
    "path": "docs/what.rst",
    "content": "======================\nWhat is a Certificate?\n======================\n\nA public key or digital *certificate* (formerly called an SSL certificate) uses a public key \nand a private key to enable secure communication between a client program (web browser, email client, \netc.) and a server over an encrypted SSL (secure socket layer) or TLS (transport layer security) connection.\nThe certificate is used both to encrypt the initial stage of communication (secure key exchange) \nand to identify the server. The certificate\nincludes information about the key, information about the server identity, and the digital signature\nof the certificate issuer. If the issuer is trusted by the software that initiates the communication,\nand the signature is valid, then the key can be used to communicate securely with the server identified by \nthe certificate. Using a certificate is a good way to prevent \"man-in-the-middle\" attacks, in which\nsomeone in between you and the server you think you are talking to is able to insert their own (harmful)\ncontent.\n\nYou can use Certbot to easily obtain and configure a free certificate from Let's Encrypt, a\njoint project of EFF, Mozilla, and many other sponsors.\n\nCertificates and Lineages\n=========================\n\nCertbot introduces the concept of a *lineage,* which is a collection of all the versions of a certificate\nplus Certbot configuration information maintained for that certificate from\nrenewal to renewal. Whenever you renew a certificate, Certbot keeps the same configuration unless\nyou explicitly change it, for example by adding or removing domains. If you add domains, you can \neither add them to an existing lineage or create\na new one. \n\nSee also:\n:ref:`updating_certs`\n"
  },
  {
    "path": "examples/.gitignore",
    "content": "# generate-csr.sh:\n/key.pem\n/csr.der"
  },
  {
    "path": "examples/cli.ini",
    "content": "# This is an example of the kind of things you can do in a configuration file.\n# All flags used by the client can be configured here. Run Certbot with\n# \"--help\" to learn more about the available options.\n#\n# Note that these options apply automatically to all use of Certbot for\n# obtaining or renewing certificates, so options specific to a single\n# certificate on a system with several certificates should not be placed\n# here.\n\n# Use a 4096 bit RSA key instead of 2048\nrsa-key-size = 4096\n\n# Uncomment and update to register with the specified e-mail address\n# email = foo@example.com\n\n# Uncomment to use the standalone authenticator on port 443\n# authenticator = standalone\n\n# Uncomment to use the webroot authenticator. Replace webroot-path with the\n# path to the public_html / webroot folder being served by your web server.\n# authenticator = webroot\n# webroot-path = /usr/share/nginx/html\n"
  },
  {
    "path": "examples/dev-cli.ini",
    "content": "# Always use the staging/testing server - avoids rate limiting\nserver = https://acme-staging-v02.api.letsencrypt.org/directory\n\n# This is an example configuration file for developers\nconfig-dir = /tmp/le/conf\nwork-dir = /tmp/le/conf\nlogs-dir = /tmp/le/logs\n\n# make sure to use a valid email and domains!\nemail = foo@example.com\ndomains = example.com\n\ntext = True\nagree-tos = True\ndebug = True\n# Unfortunately, it's not possible to specify \"verbose\" multiple times\n# (correspondingly to -vvvvvv)\nverbose = True\n\nauthenticator = standalone\n"
  },
  {
    "path": "examples/generate-csr.sh",
    "content": "#!/bin/sh\n# This script generates a simple SAN CSR to be used with Let's Encrypt\n# CA. Mostly intended for \"auth --csr\" testing, but, since it's easily\n# auditable, feel free to adjust it and use it on your production web\n# server.\n\nif [ \"$#\" -lt 1 ]\nthen\n  echo \"Usage: $0 domain [domain...]\" >&2\n  exit 1\nfi\n\ndomains=\"DNS:$1\"\nshift\nfor x in \"$@\"\ndo\n  domains=\"$domains,DNS:$x\"\ndone\n\nSAN=\"$domains\" openssl req -config \"${OPENSSL_CNF:-openssl.cnf}\" \\\n  -new -nodes -subj '/' -reqexts san \\\n  -out \"${CSR_PATH:-csr.der}\" \\\n  -keyout \"${KEY_PATH:-key.pem}\" \\\n  -newkey rsa:2048 \\\n  -outform DER\n# 512 or 1024 too low for Boulder, 2048 is smallest for tests\n\necho \"You can now run: certbot auth --csr ${CSR_PATH:-csr.der}\"\n"
  },
  {
    "path": "examples/openssl.cnf",
    "content": "[ req ]\ndistinguished_name = req_distinguished_name\n[ req_distinguished_name ]\n[ san ]\nsubjectAltName=${ENV::SAN}\n"
  },
  {
    "path": "examples/plugins/certbot_example_plugins.py",
    "content": "\"\"\"Example Certbot plugins.\n\nFor full examples, see `certbot.plugins`.\n\n\"\"\"\nimport zope.interface\n\nfrom certbot import interfaces\nfrom certbot.plugins import common\n\n\n@zope.interface.implementer(interfaces.IAuthenticator)\n@zope.interface.provider(interfaces.IPluginFactory)\nclass Authenticator(common.Plugin):\n    \"\"\"Example Authenticator.\"\"\"\n\n    description = \"Example Authenticator plugin\"\n\n    # Implement all methods from IAuthenticator, remembering to add\n    # \"self\" as first argument, e.g. def prepare(self)...\n\n\n@zope.interface.implementer(interfaces.IInstaller)\n@zope.interface.provider(interfaces.IPluginFactory)\nclass Installer(common.Plugin):\n    \"\"\"Example Installer.\"\"\"\n\n    description = \"Example Installer plugin\"\n\n    # Implement all methods from IInstaller, remembering to add\n    # \"self\" as first argument, e.g. def get_all_names(self)...\n"
  },
  {
    "path": "examples/plugins/setup.py",
    "content": "from setuptools import setup\n\nsetup(\n    name='certbot-example-plugins',\n    package='certbot_example_plugins.py',\n    install_requires=[\n        'certbot',\n        'zope.interface',\n    ],\n    entry_points={\n        'certbot.plugins': [\n            'example_authenticator = certbot_example_plugins:Authenticator',\n            'example_installer = certbot_example_plugins:Installer',\n        ],\n    },\n)\n"
  },
  {
    "path": "setup.cfg",
    "content": "[bdist_wheel]\nuniversal = 1\n\n[easy_install]\nzip_ok = false\n\n[egg_info]\ntag_build = \ntag_date = 0\n\n"
  },
  {
    "path": "setup.py",
    "content": "import codecs\nfrom distutils.version import StrictVersion\nimport os\nimport re\nimport sys\n\nfrom setuptools import __version__ as setuptools_version\nfrom setuptools import find_packages\nfrom setuptools import setup\nfrom setuptools.command.test import test as TestCommand\n\n# Workaround for http://bugs.python.org/issue8876, see\n# http://bugs.python.org/issue8876#msg208792\n# This can be removed when using Python 2.7.9 or later:\n# https://hg.python.org/cpython/raw-file/v2.7.9/Misc/NEWS\nif os.path.abspath(__file__).split(os.path.sep)[1] == 'vagrant':\n    del os.link\n\n\ndef read_file(filename, encoding='utf8'):\n    \"\"\"Read unicode from given file.\"\"\"\n    with codecs.open(filename, encoding=encoding) as fd:\n        return fd.read()\n\n\nhere = os.path.abspath(os.path.dirname(__file__))\n\n# read version number (and other metadata) from package init\ninit_fn = os.path.join(here, 'certbot', '__init__.py')\nmeta = dict(re.findall(r\"\"\"__([a-z]+)__ = '([^']+)\"\"\", read_file(init_fn)))\n\nreadme = read_file(os.path.join(here, 'README.rst'))\nversion = meta['version']\n\n# This package relies on PyOpenSSL, requests, and six, however, it isn't\n# specified here to avoid masking the more specific request requirements in\n# acme. See https://github.com/pypa/pip/issues/988 for more info.\ninstall_requires = [\n    'acme>=0.40.0',\n    # We technically need ConfigArgParse 0.10.0 for Python 2.6 support, but\n    # saying so here causes a runtime error against our temporary fork of 0.9.3\n    # in which we added 2.6 support (see #2243), so we relax the requirement.\n    'ConfigArgParse>=0.9.3',\n    'configobj',\n    'cryptography>=1.2.3',  # load_pem_x509_certificate\n    'distro>=1.0.1',\n    # 1.1.0+ is required to avoid the warnings described at\n    # https://github.com/certbot/josepy/issues/13.\n    'josepy>=1.1.0',\n    'mock',\n    'parsedatetime>=1.3',  # Calendar.parseDT\n    'pyrfc3339',\n    'pytz',\n    'setuptools',\n    'zope.component',\n    'zope.interface',\n]\n\n# Add pywin32 on Windows platforms to handle low-level system calls.\n# This dependency needs to be added using environment markers to avoid its installation on Linux.\n# However environment markers are supported only with setuptools >= 36.2.\n# So this dependency is not added for old Linux distributions with old setuptools,\n# in order to allow these systems to build certbot from sources.\npywin32_req = 'pywin32>=227'  # do not forget to edit pywin32 dependency accordingly in windows-installer/construct.py\nif StrictVersion(setuptools_version) >= StrictVersion('36.2'):\n    install_requires.append(pywin32_req + \" ; sys_platform == 'win32'\")\nelif 'bdist_wheel' in sys.argv[1:]:\n    raise RuntimeError('Error, you are trying to build certbot wheels using an old version '\n                       'of setuptools. Version 36.2+ of setuptools is required.')\nelif os.name == 'nt':\n    # This branch exists to improve this package's behavior on Windows. Without\n    # it, if the sdist is installed on Windows with an old version of\n    # setuptools, pywin32 will not be specified as a dependency.\n    install_requires.append(pywin32_req)\n\ndev_extras = [\n    'coverage',\n    'ipdb',\n    'pytest',\n    'pytest-cov',\n    'pytest-xdist',\n    'tox',\n    'twine',\n    'wheel',\n]\n\ndev3_extras = [\n    'astroid',\n    'mypy',\n    'pylint',\n]\n\ndocs_extras = [\n    # If you have Sphinx<1.5.1, you need docutils<0.13.1\n    # https://github.com/sphinx-doc/sphinx/issues/3212\n    'repoze.sphinx.autointerface',\n    'Sphinx>=1.2', # Annotation support\n    'sphinx_rtd_theme',\n]\n\n\nclass PyTest(TestCommand):\n    user_options = []\n\n    def initialize_options(self):\n        TestCommand.initialize_options(self)\n        self.pytest_args = ''\n\n    def run_tests(self):\n        import shlex\n        # import here, cause outside the eggs aren't loaded\n        import pytest\n        errno = pytest.main(shlex.split(self.pytest_args))\n        sys.exit(errno)\n\n\nsetup(\n    name='certbot',\n    version=version,\n    description=\"ACME client\",\n    long_description=readme,\n    url='https://github.com/letsencrypt/letsencrypt',\n    author=\"Certbot Project\",\n    author_email='client-dev@letsencrypt.org',\n    license='Apache License 2.0',\n    python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*',\n    classifiers=[\n        'Development Status :: 5 - Production/Stable',\n        'Environment :: Console',\n        'Environment :: Console :: Curses',\n        'Intended Audience :: System Administrators',\n        'License :: OSI Approved :: Apache Software License',\n        'Operating System :: POSIX :: Linux',\n        'Programming Language :: Python',\n        'Programming Language :: Python :: 2',\n        'Programming Language :: Python :: 2.7',\n        'Programming Language :: Python :: 3',\n        'Programming Language :: Python :: 3.5',\n        'Programming Language :: Python :: 3.6',\n        'Programming Language :: Python :: 3.7',\n        'Programming Language :: Python :: 3.8',\n        'Topic :: Internet :: WWW/HTTP',\n        'Topic :: Security',\n        'Topic :: System :: Installation/Setup',\n        'Topic :: System :: Networking',\n        'Topic :: System :: Systems Administration',\n        'Topic :: Utilities',\n    ],\n\n    packages=find_packages(exclude=['docs', 'examples', 'tests', 'venv']),\n    include_package_data=True,\n\n    install_requires=install_requires,\n    extras_require={\n        'dev': dev_extras,\n        'dev3': dev3_extras,\n        'docs': docs_extras,\n    },\n\n    test_suite='certbot',\n    tests_require=[\"pytest\"],\n    cmdclass={\"test\": PyTest},\n\n    entry_points={\n        'console_scripts': [\n            'certbot = certbot.main:main',\n        ],\n        'certbot.plugins': [\n            'manual = certbot._internal.plugins.manual:Authenticator',\n            'null = certbot._internal.plugins.null:Installer',\n            'standalone = certbot._internal.plugins.standalone:Authenticator',\n            'webroot = certbot._internal.plugins.webroot:Authenticator',\n        ],\n    },\n)\n"
  },
  {
    "path": "tests/account_test.py",
    "content": "\"\"\"Tests for certbot._internal.account.\"\"\"\nimport datetime\nimport json\nimport unittest\n\nimport josepy as jose\nimport mock\nimport pytz\n\nfrom acme import messages\nfrom certbot import errors\nfrom certbot.compat import filesystem\nfrom certbot.compat import misc\nfrom certbot.compat import os\nimport certbot.tests.util as test_util\n\nKEY = jose.JWKRSA.load(test_util.load_vector(\"rsa512_key.pem\"))\n\n\nclass AccountTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.account.Account.\"\"\"\n\n    def setUp(self):\n        from certbot._internal.account import Account\n        self.regr = mock.MagicMock()\n        self.meta = Account.Meta(\n            creation_host=\"test.certbot.org\",\n            creation_dt=datetime.datetime(\n                2015, 7, 4, 14, 4, 10, tzinfo=pytz.UTC))\n        self.acc = Account(self.regr, KEY, self.meta)\n        self.regr.__repr__ = mock.MagicMock(return_value=\"i_am_a_regr\")\n\n        with mock.patch(\"certbot._internal.account.socket\") as mock_socket:\n            mock_socket.getfqdn.return_value = \"test.certbot.org\"\n            with mock.patch(\"certbot._internal.account.datetime\") as mock_dt:\n                mock_dt.datetime.now.return_value = self.meta.creation_dt\n                self.acc_no_meta = Account(self.regr, KEY)\n\n    def test_init(self):\n        self.assertEqual(self.regr, self.acc.regr)\n        self.assertEqual(KEY, self.acc.key)\n        self.assertEqual(self.meta, self.acc_no_meta.meta)\n\n    def test_id(self):\n        self.assertEqual(\n            self.acc.id, \"7adac10320f585ddf118429c0c4af2cd\")\n\n    def test_slug(self):\n        self.assertEqual(\n            self.acc.slug, \"test.certbot.org@2015-07-04T14:04:10Z (7ada)\")\n\n    def test_repr(self):\n        self.assertTrue(repr(self.acc).startswith(\n          \"<Account(i_am_a_regr, 7adac10320f585ddf118429c0c4af2cd, Meta(\"))\n\nclass ReportNewAccountTest(test_util.ConfigTestCase):\n    \"\"\"Tests for certbot._internal.account.report_new_account.\"\"\"\n\n    def _call(self):\n        from certbot._internal.account import report_new_account\n        report_new_account(self.config)\n\n    @mock.patch(\"certbot._internal.account.zope.component.queryUtility\")\n    def test_no_reporter(self, mock_zope):\n        mock_zope.return_value = None\n        self._call()\n\n    @mock.patch(\"certbot._internal.account.zope.component.queryUtility\")\n    def test_it(self, mock_zope):\n        self._call()\n        call_list = mock_zope().add_message.call_args_list\n        self.assertTrue(self.config.config_dir in call_list[0][0][0])\n\n\nclass AccountMemoryStorageTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.account.AccountMemoryStorage.\"\"\"\n\n    def setUp(self):\n        from certbot._internal.account import AccountMemoryStorage\n        self.storage = AccountMemoryStorage()\n\n    def test_it(self):\n        account = mock.Mock(id=\"x\")\n        self.assertEqual([], self.storage.find_all())\n        self.assertRaises(errors.AccountNotFound, self.storage.load, \"x\")\n        self.storage.save(account, None)\n        self.assertEqual([account], self.storage.find_all())\n        self.assertEqual(account, self.storage.load(\"x\"))\n        self.storage.save(account, None)\n        self.assertEqual([account], self.storage.find_all())\n\n\nclass AccountFileStorageTest(test_util.ConfigTestCase):\n    \"\"\"Tests for certbot._internal.account.AccountFileStorage.\"\"\"\n\n    def setUp(self):\n        super(AccountFileStorageTest, self).setUp()\n\n        from certbot._internal.account import AccountFileStorage\n        self.storage = AccountFileStorage(self.config)\n\n        from certbot._internal.account import Account\n        new_authzr_uri = \"hi\"\n        self.acc = Account(\n            regr=messages.RegistrationResource(\n                uri=None, body=messages.Registration(),\n                new_authzr_uri=new_authzr_uri),\n            key=KEY)\n        self.mock_client = mock.MagicMock()\n        self.mock_client.directory.new_authz = new_authzr_uri\n\n    def test_init_creates_dir(self):\n        self.assertTrue(os.path.isdir(\n            misc.underscores_for_unsupported_characters_in_path(self.config.accounts_dir)))\n\n    def test_save_and_restore(self):\n        self.storage.save(self.acc, self.mock_client)\n        account_path = os.path.join(self.config.accounts_dir, self.acc.id)\n        self.assertTrue(os.path.exists(account_path))\n        for file_name in \"regr.json\", \"meta.json\", \"private_key.json\":\n            self.assertTrue(os.path.exists(\n                os.path.join(account_path, file_name)))\n        self.assertTrue(\n            filesystem.check_mode(os.path.join(account_path, \"private_key.json\"), 0o400))\n\n        # restore\n        loaded = self.storage.load(self.acc.id)\n        self.assertEqual(self.acc, loaded)\n\n    def test_save_and_restore_old_version(self):\n        \"\"\"Saved regr should include a new_authzr_uri for older Certbots\"\"\"\n        self.storage.save(self.acc, self.mock_client)\n        path = os.path.join(self.config.accounts_dir, self.acc.id, \"regr.json\")\n        with open(path, \"r\") as f:\n            regr = json.load(f)\n        self.assertTrue(\"new_authzr_uri\" in regr)\n\n    def test_save_regr(self):\n        self.storage.save_regr(self.acc, self.mock_client)\n        account_path = os.path.join(self.config.accounts_dir, self.acc.id)\n        self.assertTrue(os.path.exists(account_path))\n        self.assertTrue(os.path.exists(os.path.join(\n            account_path, \"regr.json\")))\n        for file_name in \"meta.json\", \"private_key.json\":\n            self.assertFalse(os.path.exists(\n                os.path.join(account_path, file_name)))\n\n    def test_find_all(self):\n        self.storage.save(self.acc, self.mock_client)\n        self.assertEqual([self.acc], self.storage.find_all())\n\n    def test_find_all_none_empty_list(self):\n        self.assertEqual([], self.storage.find_all())\n\n    def test_find_all_accounts_dir_absent(self):\n        os.rmdir(self.config.accounts_dir)\n        self.assertEqual([], self.storage.find_all())\n\n    def test_find_all_load_skips(self):\n        # pylint: disable=protected-access\n        self.storage._load_for_server_path = mock.MagicMock(\n            side_effect=[\"x\", errors.AccountStorageError, \"z\"])\n        with mock.patch(\"certbot._internal.account.os.listdir\") as mock_listdir:\n            mock_listdir.return_value = [\"x\", \"y\", \"z\"]\n            self.assertEqual([\"x\", \"z\"], self.storage.find_all())\n\n    def test_load_non_existent_raises_error(self):\n        self.assertRaises(errors.AccountNotFound, self.storage.load, \"missing\")\n\n    def _set_server(self, server):\n        self.config.server = server\n        from certbot._internal.account import AccountFileStorage\n        self.storage = AccountFileStorage(self.config)\n\n    def test_find_all_neither_exists(self):\n        self._set_server('https://acme-staging-v02.api.letsencrypt.org/directory')\n        self.assertEqual([], self.storage.find_all())\n        self.assertEqual([], self.storage.find_all())\n        self.assertFalse(os.path.islink(self.config.accounts_dir))\n\n    def test_find_all_find_before_save(self):\n        self._set_server('https://acme-staging-v02.api.letsencrypt.org/directory')\n        self.assertEqual([], self.storage.find_all())\n        self.storage.save(self.acc, self.mock_client)\n        self.assertEqual([self.acc], self.storage.find_all())\n        self.assertEqual([self.acc], self.storage.find_all())\n        self.assertFalse(os.path.islink(self.config.accounts_dir))\n        # we shouldn't have created a v1 account\n        prev_server_path = 'https://acme-staging.api.letsencrypt.org/directory'\n        self.assertFalse(os.path.isdir(self.config.accounts_dir_for_server_path(prev_server_path)))\n\n    def test_find_all_save_before_find(self):\n        self._set_server('https://acme-staging-v02.api.letsencrypt.org/directory')\n        self.storage.save(self.acc, self.mock_client)\n        self.assertEqual([self.acc], self.storage.find_all())\n        self.assertEqual([self.acc], self.storage.find_all())\n        self.assertFalse(os.path.islink(self.config.accounts_dir))\n        self.assertTrue(os.path.isdir(self.config.accounts_dir))\n        prev_server_path = 'https://acme-staging.api.letsencrypt.org/directory'\n        self.assertFalse(os.path.isdir(self.config.accounts_dir_for_server_path(prev_server_path)))\n\n    def test_find_all_server_downgrade(self):\n        # don't use v2 accounts with a v1 url\n        self._set_server('https://acme-staging-v02.api.letsencrypt.org/directory')\n        self.assertEqual([], self.storage.find_all())\n        self.storage.save(self.acc, self.mock_client)\n        self.assertEqual([self.acc], self.storage.find_all())\n        self._set_server('https://acme-staging.api.letsencrypt.org/directory')\n        self.assertEqual([], self.storage.find_all())\n\n    def test_upgrade_version_staging(self):\n        self._set_server('https://acme-staging.api.letsencrypt.org/directory')\n        self.storage.save(self.acc, self.mock_client)\n        self._set_server('https://acme-staging-v02.api.letsencrypt.org/directory')\n        self.assertEqual([self.acc], self.storage.find_all())\n\n    def test_upgrade_version_production(self):\n        self._set_server('https://acme-v01.api.letsencrypt.org/directory')\n        self.storage.save(self.acc, self.mock_client)\n        self._set_server('https://acme-v02.api.letsencrypt.org/directory')\n        self.assertEqual([self.acc], self.storage.find_all())\n\n    @mock.patch('certbot.compat.os.rmdir')\n    def test_corrupted_account(self, mock_rmdir):\n        # pylint: disable=protected-access\n        self._set_server('https://acme-staging.api.letsencrypt.org/directory')\n        self.storage.save(self.acc, self.mock_client)\n        mock_rmdir.side_effect = OSError\n        self.storage._load_for_server_path = mock.MagicMock(\n            side_effect=errors.AccountStorageError)\n        self._set_server('https://acme-staging-v02.api.letsencrypt.org/directory')\n        self.assertEqual([], self.storage.find_all())\n\n    def test_upgrade_load(self):\n        self._set_server('https://acme-staging.api.letsencrypt.org/directory')\n        self.storage.save(self.acc, self.mock_client)\n        prev_account = self.storage.load(self.acc.id)\n        self._set_server('https://acme-staging-v02.api.letsencrypt.org/directory')\n        account = self.storage.load(self.acc.id)\n        self.assertEqual(prev_account, account)\n\n    def test_upgrade_load_single_account(self):\n        self._set_server('https://acme-staging.api.letsencrypt.org/directory')\n        self.storage.save(self.acc, self.mock_client)\n        prev_account = self.storage.load(self.acc.id)\n        self._set_server_and_stop_symlink('https://acme-staging-v02.api.letsencrypt.org/directory')\n        account = self.storage.load(self.acc.id)\n        self.assertEqual(prev_account, account)\n\n    def test_load_ioerror(self):\n        self.storage.save(self.acc, self.mock_client)\n        mock_open = mock.mock_open()\n        mock_open.side_effect = IOError\n        with mock.patch(\"six.moves.builtins.open\", mock_open):\n            self.assertRaises(\n                errors.AccountStorageError, self.storage.load, self.acc.id)\n\n    def test_save_ioerrors(self):\n        mock_open = mock.mock_open()\n        mock_open.side_effect = IOError  # TODO: [None, None, IOError]\n        with mock.patch(\"six.moves.builtins.open\", mock_open):\n            self.assertRaises(\n                errors.AccountStorageError, self.storage.save,\n                    self.acc, self.mock_client)\n\n    def test_delete(self):\n        self.storage.save(self.acc, self.mock_client)\n        self.storage.delete(self.acc.id)\n        self.assertRaises(errors.AccountNotFound, self.storage.load, self.acc.id)\n\n    def test_delete_no_account(self):\n        self.assertRaises(errors.AccountNotFound, self.storage.delete, self.acc.id)\n\n    def _assert_symlinked_account_removed(self):\n        # create v1 account\n        self._set_server('https://acme-staging.api.letsencrypt.org/directory')\n        self.storage.save(self.acc, self.mock_client)\n        # ensure v2 isn't already linked to it\n        with mock.patch('certbot._internal.constants.LE_REUSE_SERVERS', {}):\n            self._set_server('https://acme-staging-v02.api.letsencrypt.org/directory')\n            self.assertRaises(errors.AccountNotFound, self.storage.load, self.acc.id)\n\n    def _test_delete_folders(self, server_url):\n        # create symlinked servers\n        self._set_server('https://acme-staging.api.letsencrypt.org/directory')\n        self.storage.save(self.acc, self.mock_client)\n        self._set_server('https://acme-staging-v02.api.letsencrypt.org/directory')\n        self.storage.load(self.acc.id)\n\n        # delete starting at given server_url\n        self._set_server(server_url)\n        self.storage.delete(self.acc.id)\n\n        # make sure we're gone from both urls\n        self._set_server('https://acme-staging.api.letsencrypt.org/directory')\n        self.assertRaises(errors.AccountNotFound, self.storage.load, self.acc.id)\n        self._set_server('https://acme-staging-v02.api.letsencrypt.org/directory')\n        self.assertRaises(errors.AccountNotFound, self.storage.load, self.acc.id)\n\n    def test_delete_folders_up(self):\n        self._test_delete_folders('https://acme-staging.api.letsencrypt.org/directory')\n        self._assert_symlinked_account_removed()\n\n    def test_delete_folders_down(self):\n        self._test_delete_folders('https://acme-staging-v02.api.letsencrypt.org/directory')\n        self._assert_symlinked_account_removed()\n\n    def _set_server_and_stop_symlink(self, server_path):\n        self._set_server(server_path)\n        with open(os.path.join(self.config.accounts_dir, 'foo'), 'w') as f:\n            f.write('bar')\n\n    def test_delete_shared_account_up(self):\n        self._set_server_and_stop_symlink('https://acme-staging-v02.api.letsencrypt.org/directory')\n        self._test_delete_folders('https://acme-staging.api.letsencrypt.org/directory')\n\n    def test_delete_shared_account_down(self):\n        self._set_server_and_stop_symlink('https://acme-staging-v02.api.letsencrypt.org/directory')\n        self._test_delete_folders('https://acme-staging-v02.api.letsencrypt.org/directory')\n\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/auth_handler_test.py",
    "content": "\"\"\"Tests for certbot._internal.auth_handler.\"\"\"\nimport functools\nimport logging\nimport unittest\n\nimport mock\nimport zope.component\n\nfrom acme import challenges\nfrom acme import client as acme_client\nfrom acme import errors as acme_errors\nfrom acme import messages\nfrom certbot import achallenges\nfrom certbot import errors\nfrom certbot import interfaces\nfrom certbot import util\nfrom certbot.tests import acme_util\nfrom certbot.tests import util as test_util\n\n\nclass ChallengeFactoryTest(unittest.TestCase):\n    # pylint: disable=protected-access\n\n    def setUp(self):\n        from certbot._internal.auth_handler import AuthHandler\n\n        # Account is mocked...\n        self.handler = AuthHandler(None, None, mock.Mock(key=\"mock_key\"), [])\n\n        self.authzr = acme_util.gen_authzr(\n            messages.STATUS_PENDING, \"test\", acme_util.CHALLENGES,\n            [messages.STATUS_PENDING] * 6, False)\n\n    def test_all(self):\n        achalls = self.handler._challenge_factory(\n            self.authzr, range(0, len(acme_util.CHALLENGES)))\n\n        self.assertEqual(\n            [achall.chall for achall in achalls], acme_util.CHALLENGES)\n\n    def test_one_http(self):\n        achalls = self.handler._challenge_factory(self.authzr, [0])\n\n        self.assertEqual(\n            [achall.chall for achall in achalls], [acme_util.HTTP01])\n\n    def test_unrecognized(self):\n        authzr = acme_util.gen_authzr(\n             messages.STATUS_PENDING, \"test\",\n             [mock.Mock(chall=\"chall\", typ=\"unrecognized\")],\n             [messages.STATUS_PENDING])\n\n        self.assertRaises(\n             errors.Error, self.handler._challenge_factory, authzr, [0])\n\n\nclass HandleAuthorizationsTest(unittest.TestCase):\n    \"\"\"handle_authorizations test.\n\n    This tests everything except for all functions under _poll_challenges.\n\n    \"\"\"\n\n    def setUp(self):\n        from certbot._internal.auth_handler import AuthHandler\n\n        self.mock_display = mock.Mock()\n        zope.component.provideUtility(\n            self.mock_display, interfaces.IDisplay)\n        zope.component.provideUtility(\n            mock.Mock(debug_challenges=False), interfaces.IConfig)\n\n        self.mock_auth = mock.MagicMock(name=\"ApacheConfigurator\")\n\n        self.mock_auth.get_chall_pref.return_value = [challenges.HTTP01]\n\n        self.mock_auth.perform.side_effect = gen_auth_resp\n\n        self.mock_account = mock.Mock(key=util.Key(\"file_path\", \"PEM\"))\n        self.mock_net = mock.MagicMock(spec=acme_client.Client)\n        self.mock_net.acme_version = 1\n        self.mock_net.retry_after.side_effect = acme_client.Client.retry_after\n\n        self.handler = AuthHandler(\n            self.mock_auth, self.mock_net, self.mock_account, [])\n\n        logging.disable(logging.CRITICAL)\n\n    def tearDown(self):\n        logging.disable(logging.NOTSET)\n\n    def _test_name1_http_01_1_common(self, combos):\n        authzr = gen_dom_authzr(domain=\"0\", challs=acme_util.CHALLENGES, combos=combos)\n        mock_order = mock.MagicMock(authorizations=[authzr])\n\n        self.mock_net.poll.side_effect = _gen_mock_on_poll(retry=1, wait_value=30)\n        with mock.patch('certbot._internal.auth_handler.time') as mock_time:\n            authzr = self.handler.handle_authorizations(mock_order)\n\n            self.assertEqual(self.mock_net.answer_challenge.call_count, 1)\n\n            self.assertEqual(self.mock_net.poll.call_count, 2)  # Because there is one retry\n            self.assertEqual(mock_time.sleep.call_count, 2)\n            # Retry-After header is 30 seconds, but at the time sleep is invoked, several\n            # instructions are executed, and next pool is in less than 30 seconds.\n            self.assertTrue(mock_time.sleep.call_args_list[1][0][0] <= 30)\n            # However, assert that we did not took the default value of 3 seconds.\n            self.assertTrue(mock_time.sleep.call_args_list[1][0][0] > 3)\n\n            self.assertEqual(self.mock_auth.cleanup.call_count, 1)\n            # Test if list first element is http-01, use typ because it is an achall\n            self.assertEqual(\n                self.mock_auth.cleanup.call_args[0][0][0].typ, \"http-01\")\n\n            self.assertEqual(len(authzr), 1)\n\n    def test_name1_http_01_1_acme_1(self):\n        self._test_name1_http_01_1_common(combos=True)\n\n    def test_name1_http_01_1_acme_2(self):\n        self.mock_net.acme_version = 2\n        self._test_name1_http_01_1_common(combos=False)\n\n    def test_name1_http_01_1_dns_1_acme_1(self):\n        self.mock_net.poll.side_effect = _gen_mock_on_poll()\n        self.mock_auth.get_chall_pref.return_value.append(challenges.DNS01)\n\n        authzr = gen_dom_authzr(domain=\"0\", challs=acme_util.CHALLENGES, combos=False)\n        mock_order = mock.MagicMock(authorizations=[authzr])\n        authzr = self.handler.handle_authorizations(mock_order)\n\n        self.assertEqual(self.mock_net.answer_challenge.call_count, 2)\n\n        self.assertEqual(self.mock_net.poll.call_count, 1)\n\n        self.assertEqual(self.mock_auth.cleanup.call_count, 1)\n        # Test if list first element is http-01, use typ because it is an achall\n        for achall in self.mock_auth.cleanup.call_args[0][0]:\n            self.assertTrue(achall.typ in [\"http-01\", \"dns-01\"])\n\n        # Length of authorizations list\n        self.assertEqual(len(authzr), 1)\n\n    def test_name1_http_01_1_dns_1_acme_2(self):\n        self.mock_net.acme_version = 2\n        self.mock_net.poll.side_effect = _gen_mock_on_poll()\n        self.mock_auth.get_chall_pref.return_value.append(challenges.DNS01)\n\n        authzr = gen_dom_authzr(domain=\"0\", challs=acme_util.CHALLENGES, combos=False)\n        mock_order = mock.MagicMock(authorizations=[authzr])\n        authzr = self.handler.handle_authorizations(mock_order)\n\n        self.assertEqual(self.mock_net.answer_challenge.call_count, 1)\n\n        self.assertEqual(self.mock_net.poll.call_count, 1)\n\n        self.assertEqual(self.mock_auth.cleanup.call_count, 1)\n        cleaned_up_achalls = self.mock_auth.cleanup.call_args[0][0]\n        self.assertEqual(len(cleaned_up_achalls), 1)\n        self.assertEqual(cleaned_up_achalls[0].typ, \"http-01\")\n\n        # Length of authorizations list\n        self.assertEqual(len(authzr), 1)\n\n    def _test_name3_http_01_3_common(self, combos):\n        self.mock_net.request_domain_challenges.side_effect = functools.partial(\n            gen_dom_authzr, challs=acme_util.CHALLENGES, combos=combos)\n\n        authzrs = [gen_dom_authzr(domain=\"0\", challs=acme_util.CHALLENGES),\n                   gen_dom_authzr(domain=\"1\", challs=acme_util.CHALLENGES),\n                   gen_dom_authzr(domain=\"2\", challs=acme_util.CHALLENGES)]\n        mock_order = mock.MagicMock(authorizations=authzrs)\n\n        self.mock_net.poll.side_effect = _gen_mock_on_poll()\n        authzr = self.handler.handle_authorizations(mock_order)\n\n        self.assertEqual(self.mock_net.answer_challenge.call_count, 3)\n\n        # Check poll call\n        self.assertEqual(self.mock_net.poll.call_count, 3)\n\n        self.assertEqual(self.mock_auth.cleanup.call_count, 1)\n\n        self.assertEqual(len(authzr), 3)\n\n    def test_name3_http_01_3_common_acme_1(self):\n        self._test_name3_http_01_3_common(combos=True)\n\n    def test_name3_http_01_3_common_acme_2(self):\n        self.mock_net.acme_version = 2\n        self._test_name3_http_01_3_common(combos=False)\n\n    def test_debug_challenges(self):\n        zope.component.provideUtility(\n            mock.Mock(debug_challenges=True), interfaces.IConfig)\n        authzrs = [gen_dom_authzr(domain=\"0\", challs=acme_util.CHALLENGES)]\n        mock_order = mock.MagicMock(authorizations=authzrs)\n\n        self.mock_net.poll.side_effect = _gen_mock_on_poll()\n\n        self.handler.handle_authorizations(mock_order)\n\n        self.assertEqual(self.mock_net.answer_challenge.call_count, 1)\n        self.assertEqual(self.mock_display.notification.call_count, 1)\n\n    def test_perform_failure(self):\n        authzrs = [gen_dom_authzr(domain=\"0\", challs=acme_util.CHALLENGES)]\n        mock_order = mock.MagicMock(authorizations=authzrs)\n\n        self.mock_auth.perform.side_effect = errors.AuthorizationError\n\n        self.assertRaises(\n            errors.AuthorizationError, self.handler.handle_authorizations, mock_order)\n\n    def test_max_retries_exceeded(self):\n        authzrs = [gen_dom_authzr(domain=\"0\", challs=acme_util.CHALLENGES)]\n        mock_order = mock.MagicMock(authorizations=authzrs)\n\n        # We will return STATUS_PENDING twice before returning STATUS_VALID.\n        self.mock_net.poll.side_effect = _gen_mock_on_poll(retry=2)\n\n        with self.assertRaises(errors.AuthorizationError) as error:\n            # We retry only once, so retries will be exhausted before STATUS_VALID is returned.\n            self.handler.handle_authorizations(mock_order, False, 1)\n        self.assertTrue('All authorizations were not finalized by the CA.' in str(error.exception))\n\n    def test_no_domains(self):\n        mock_order = mock.MagicMock(authorizations=[])\n        self.assertRaises(errors.AuthorizationError, self.handler.handle_authorizations, mock_order)\n\n    def _test_preferred_challenge_choice_common(self, combos):\n        authzrs = [gen_dom_authzr(domain=\"0\", challs=acme_util.CHALLENGES, combos=combos)]\n        mock_order = mock.MagicMock(authorizations=authzrs)\n\n        self.mock_auth.get_chall_pref.return_value.append(challenges.HTTP01)\n\n        self.handler.pref_challs.extend((challenges.HTTP01.typ,\n                                         challenges.DNS01.typ,))\n\n        self.mock_net.poll.side_effect = _gen_mock_on_poll()\n        self.handler.handle_authorizations(mock_order)\n\n        self.assertEqual(self.mock_auth.cleanup.call_count, 1)\n        self.assertEqual(\n            self.mock_auth.cleanup.call_args[0][0][0].typ, \"http-01\")\n\n    def test_preferred_challenge_choice_common_acme_1(self):\n        self._test_preferred_challenge_choice_common(combos=True)\n\n    def test_preferred_challenge_choice_common_acme_2(self):\n        self.mock_net.acme_version = 2\n        self._test_preferred_challenge_choice_common(combos=False)\n\n    def _test_preferred_challenges_not_supported_common(self, combos):\n        authzrs = [gen_dom_authzr(domain=\"0\", challs=acme_util.CHALLENGES, combos=combos)]\n        mock_order = mock.MagicMock(authorizations=authzrs)\n        self.handler.pref_challs.append(challenges.DNS01.typ)\n        self.assertRaises(\n            errors.AuthorizationError, self.handler.handle_authorizations, mock_order)\n\n    def test_preferred_challenges_not_supported_acme_1(self):\n        self._test_preferred_challenges_not_supported_common(combos=True)\n\n    def test_preferred_challenges_not_supported_acme_2(self):\n        self.mock_net.acme_version = 2\n        self._test_preferred_challenges_not_supported_common(combos=False)\n\n    def test_dns_only_challenge_not_supported(self):\n        authzrs = [gen_dom_authzr(domain=\"0\", challs=[acme_util.DNS01])]\n        mock_order = mock.MagicMock(authorizations=authzrs)\n        self.assertRaises(\n            errors.AuthorizationError, self.handler.handle_authorizations, mock_order)\n\n    def test_perform_error(self):\n        self.mock_auth.perform.side_effect = errors.AuthorizationError\n\n        authzr = gen_dom_authzr(domain=\"0\", challs=acme_util.CHALLENGES, combos=True)\n        mock_order = mock.MagicMock(authorizations=[authzr])\n        self.assertRaises(errors.AuthorizationError, self.handler.handle_authorizations, mock_order)\n\n        self.assertEqual(self.mock_auth.cleanup.call_count, 1)\n        self.assertEqual(\n            self.mock_auth.cleanup.call_args[0][0][0].typ, \"http-01\")\n\n    def test_answer_error(self):\n        self.mock_net.answer_challenge.side_effect = errors.AuthorizationError\n\n        authzrs = [gen_dom_authzr(domain=\"0\", challs=acme_util.CHALLENGES)]\n        mock_order = mock.MagicMock(authorizations=authzrs)\n\n        self.assertRaises(\n            errors.AuthorizationError, self.handler.handle_authorizations, mock_order)\n        self.assertEqual(self.mock_auth.cleanup.call_count, 1)\n        self.assertEqual(\n            self.mock_auth.cleanup.call_args[0][0][0].typ, \"http-01\")\n\n    def test_incomplete_authzr_error(self):\n        authzrs = [gen_dom_authzr(domain=\"0\", challs=acme_util.CHALLENGES)]\n        mock_order = mock.MagicMock(authorizations=authzrs)\n        self.mock_net.poll.side_effect = _gen_mock_on_poll(status=messages.STATUS_INVALID)\n\n        with test_util.patch_get_utility():\n            with self.assertRaises(errors.AuthorizationError) as error:\n                self.handler.handle_authorizations(mock_order, False)\n        self.assertTrue('Some challenges have failed.' in str(error.exception))\n        self.assertEqual(self.mock_auth.cleanup.call_count, 1)\n        self.assertEqual(\n            self.mock_auth.cleanup.call_args[0][0][0].typ, \"http-01\")\n\n    def test_best_effort(self):\n        def _conditional_mock_on_poll(authzr):\n            \"\"\"This mock will invalidate one authzr, and invalidate the other one\"\"\"\n            valid_mock = _gen_mock_on_poll(messages.STATUS_VALID)\n            invalid_mock = _gen_mock_on_poll(messages.STATUS_INVALID)\n\n            if authzr.body.identifier.value == 'will-be-invalid':\n                return invalid_mock(authzr)\n            return valid_mock(authzr)\n\n        # Two authzrs. Only one will be valid.\n        authzrs = [gen_dom_authzr(domain=\"will-be-valid\", challs=acme_util.CHALLENGES),\n                   gen_dom_authzr(domain=\"will-be-invalid\", challs=acme_util.CHALLENGES)]\n        self.mock_net.poll.side_effect = _conditional_mock_on_poll\n\n        mock_order = mock.MagicMock(authorizations=authzrs)\n\n        with mock.patch('certbot._internal.auth_handler._report_failed_authzrs') as mock_report:\n            valid_authzr = self.handler.handle_authorizations(mock_order, True)\n\n        # Because best_effort=True, we did not blow up. Instead ...\n        self.assertEqual(len(valid_authzr), 1)  # ... the valid authzr has been processed\n        self.assertEqual(mock_report.call_count, 1)  # ... the invalid authzr has been reported\n\n        self.mock_net.poll.side_effect = _gen_mock_on_poll(status=messages.STATUS_INVALID)\n\n        with test_util.patch_get_utility():\n            with self.assertRaises(errors.AuthorizationError) as error:\n                self.handler.handle_authorizations(mock_order, True)\n\n        # Despite best_effort=True, process will fail because no authzr is valid.\n        self.assertTrue('All challenges have failed.' in str(error.exception))\n\n    def test_validated_challenge_not_rerun(self):\n        # With a pending challenge that is not supported by the plugin, we\n        # expect an exception to be raised.\n        authzr = acme_util.gen_authzr(\n                messages.STATUS_PENDING, \"0\",\n                [acme_util.DNS01],\n                [messages.STATUS_PENDING], False)\n        mock_order = mock.MagicMock(authorizations=[authzr])\n        self.assertRaises(\n            errors.AuthorizationError, self.handler.handle_authorizations, mock_order)\n\n        # With a validated challenge that is not supported by the plugin, we\n        # expect the challenge to not be solved again and\n        # handle_authorizations() to succeed.\n        authzr = acme_util.gen_authzr(\n                messages.STATUS_VALID, \"0\",\n                [acme_util.DNS01],\n                [messages.STATUS_VALID], False)\n        mock_order = mock.MagicMock(authorizations=[authzr])\n        self.handler.handle_authorizations(mock_order)\n\n    def test_valid_authzrs_deactivated(self):\n        \"\"\"When we deactivate valid authzrs in an orderr, we expect them to become deactivated\n        and to receive a list of deactivated authzrs in return.\"\"\"\n        def _mock_deactivate(authzr):\n            if authzr.body.status == messages.STATUS_VALID:\n                if authzr.body.identifier.value == \"is_valid_but_will_fail\":\n                    raise acme_errors.Error(\"Mock deactivation ACME error\")\n                authzb = authzr.body.update(status=messages.STATUS_DEACTIVATED)\n                authzr = messages.AuthorizationResource(body=authzb)\n            else: # pragma: no cover\n                raise errors.Error(\"Can't deactivate non-valid authz\")\n            return authzr\n\n        to_deactivate = [(\"is_valid\", messages.STATUS_VALID),\n                         (\"is_pending\", messages.STATUS_PENDING),\n                         (\"is_valid_but_will_fail\", messages.STATUS_VALID)]\n\n        to_deactivate = [acme_util.gen_authzr(a[1], a[0], [acme_util.HTTP01],\n                         [a[1], False]) for a in to_deactivate]\n        orderr = mock.MagicMock(authorizations=to_deactivate)\n\n        self.mock_net.deactivate_authorization.side_effect = _mock_deactivate\n\n        authzrs, failed = self.handler.deactivate_valid_authorizations(orderr)\n\n        self.assertEqual(self.mock_net.deactivate_authorization.call_count, 2)\n        self.assertEqual(len(authzrs), 1)\n        self.assertEqual(len(failed), 1)\n        self.assertEqual(authzrs[0].body.identifier.value, \"is_valid\")\n        self.assertEqual(authzrs[0].body.status, messages.STATUS_DEACTIVATED)\n        self.assertEqual(failed[0].body.identifier.value, \"is_valid_but_will_fail\")\n        self.assertEqual(failed[0].body.status, messages.STATUS_VALID)\n\n\ndef _gen_mock_on_poll(status=messages.STATUS_VALID, retry=0, wait_value=1):\n    state = {'count': retry}\n\n    def _mock(authzr):\n        state['count'] = state['count'] - 1\n        effective_status = status if state['count'] < 0 else messages.STATUS_PENDING\n        updated_azr = acme_util.gen_authzr(\n            effective_status,\n            authzr.body.identifier.value,\n            [challb.chall for challb in authzr.body.challenges],\n            [effective_status] * len(authzr.body.challenges),\n            authzr.body.combinations)\n        return updated_azr, mock.MagicMock(headers={'Retry-After': str(wait_value)})\n    return _mock\n\n\nclass ChallbToAchallTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.auth_handler.challb_to_achall.\"\"\"\n\n    def _call(self, challb):\n        from certbot._internal.auth_handler import challb_to_achall\n        return challb_to_achall(challb, \"account_key\", \"domain\")\n\n    def test_it(self):\n        self.assertEqual(\n            self._call(acme_util.HTTP01_P),\n            achallenges.KeyAuthorizationAnnotatedChallenge(\n                challb=acme_util.HTTP01_P, account_key=\"account_key\",\n                domain=\"domain\"),\n        )\n\n\nclass GenChallengePathTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.auth_handler.gen_challenge_path.\n\n    .. todo:: Add more tests for dumb_path... depending on what we want to do.\n\n    \"\"\"\n    def setUp(self):\n        logging.disable(logging.FATAL)\n\n    def tearDown(self):\n        logging.disable(logging.NOTSET)\n\n    @classmethod\n    def _call(cls, challbs, preferences, combinations):\n        from certbot._internal.auth_handler import gen_challenge_path\n        return gen_challenge_path(challbs, preferences, combinations)\n\n    def test_common_case(self):\n        \"\"\"Given DNS01 and HTTP01 with appropriate combos.\"\"\"\n        challbs = (acme_util.DNS01_P, acme_util.HTTP01_P)\n        prefs = [challenges.DNS01, challenges.HTTP01]\n        combos = ((0,), (1,))\n\n        # Smart then trivial dumb path test\n        self.assertEqual(self._call(challbs, prefs, combos), (0,))\n        self.assertTrue(self._call(challbs, prefs, None))\n        # Rearrange order...\n        self.assertEqual(self._call(challbs[::-1], prefs, combos), (1,))\n        self.assertTrue(self._call(challbs[::-1], prefs, None))\n\n    def test_not_supported(self):\n        challbs = (acme_util.DNS01_P, acme_util.HTTP01_P)\n        prefs = [challenges.HTTP01]\n        combos = ((0, 1),)\n\n        # smart path fails because no challs in perfs satisfies combos\n        self.assertRaises(\n            errors.AuthorizationError, self._call, challbs, prefs, combos)\n        # dumb path fails because all challbs are not supported\n        self.assertRaises(\n            errors.AuthorizationError, self._call, challbs, prefs, None)\n\n\nclass ReportFailedAuthzrsTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.auth_handler._report_failed_authzrs.\"\"\"\n    # pylint: disable=protected-access\n\n    def setUp(self):\n        kwargs = {\n            \"chall\": acme_util.HTTP01,\n            \"uri\": \"uri\",\n            \"status\": messages.STATUS_INVALID,\n            \"error\": messages.Error.with_code(\"tls\", detail=\"detail\"),\n        }\n\n        # Prevent future regressions if the error type changes\n        self.assertTrue(kwargs[\"error\"].description is not None)\n\n        http_01 = messages.ChallengeBody(**kwargs)\n\n        kwargs[\"chall\"] = acme_util.HTTP01\n        http_01 = messages.ChallengeBody(**kwargs)\n\n        self.authzr1 = mock.MagicMock()\n        self.authzr1.body.identifier.value = 'example.com'\n        self.authzr1.body.challenges = [http_01, http_01]\n\n        kwargs[\"error\"] = messages.Error.with_code(\"dnssec\", detail=\"detail\")\n        http_01_diff = messages.ChallengeBody(**kwargs)\n\n        self.authzr2 = mock.MagicMock()\n        self.authzr2.body.identifier.value = 'foo.bar'\n        self.authzr2.body.challenges = [http_01_diff]\n\n    @test_util.patch_get_utility()\n    def test_same_error_and_domain(self, mock_zope):\n        from certbot._internal import auth_handler\n\n        auth_handler._report_failed_authzrs([self.authzr1], 'key')\n        call_list = mock_zope().add_message.call_args_list\n        self.assertTrue(len(call_list) == 1)\n        self.assertTrue(\"Domain: example.com\\nType:   tls\\nDetail: detail\" in call_list[0][0][0])\n\n    @test_util.patch_get_utility()\n    def test_different_errors_and_domains(self, mock_zope):\n        from certbot._internal import auth_handler\n\n        auth_handler._report_failed_authzrs([self.authzr1, self.authzr2], 'key')\n        self.assertTrue(mock_zope().add_message.call_count == 2)\n\n\ndef gen_auth_resp(chall_list):\n    \"\"\"Generate a dummy authorization response.\"\"\"\n    return [\"%s%s\" % (chall.__class__.__name__, chall.domain)\n            for chall in chall_list]\n\n\ndef gen_dom_authzr(domain, challs, combos=True):\n    \"\"\"Generates new authzr for domains.\"\"\"\n    return acme_util.gen_authzr(\n        messages.STATUS_PENDING, domain, challs,\n        [messages.STATUS_PENDING] * len(challs), combos)\n\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/cert_manager_test.py",
    "content": "\n\"\"\"Tests for certbot._internal.cert_manager.\"\"\"\n# pylint: disable=protected-access\nimport re\nimport shutil\nimport tempfile\nimport unittest\n\nimport configobj\nimport mock\n\nfrom certbot import errors\nfrom certbot._internal import configuration\nfrom certbot._internal.storage import ALL_FOUR\nfrom certbot.compat import filesystem\nfrom certbot.compat import os\nfrom certbot.display import util as display_util\nfrom certbot.tests import util as test_util\nimport storage_test\n\n\nclass BaseCertManagerTest(test_util.ConfigTestCase):\n    \"\"\"Base class for setting up Cert Manager tests.\n    \"\"\"\n    def setUp(self):\n        super(BaseCertManagerTest, self).setUp()\n\n        self.config.quiet = False\n        filesystem.makedirs(self.config.renewal_configs_dir)\n\n        self.domains = {\n            \"example.org\": None,\n            \"other.com\": os.path.join(self.config.config_dir, \"specialarchive\")\n        }\n        self.config_files = dict((domain, self._set_up_config(domain, self.domains[domain]))\n            for domain in self.domains)\n\n        # We also create a file that isn't a renewal config in the same\n        # location to test that logic that reads in all-and-only renewal\n        # configs will ignore it and NOT attempt to parse it.\n        with open(os.path.join(self.config.renewal_configs_dir, \"IGNORE.THIS\"), \"w\") as junk:\n            junk.write(\"This file should be ignored!\")\n\n    def _set_up_config(self, domain, custom_archive):\n        # TODO: maybe provide NamespaceConfig.make_dirs?\n        # TODO: main() should create those dirs, c.f. #902\n        filesystem.makedirs(os.path.join(self.config.live_dir, domain))\n        config_file = configobj.ConfigObj()\n\n        if custom_archive is not None:\n            filesystem.makedirs(custom_archive)\n            config_file[\"archive_dir\"] = custom_archive\n        else:\n            filesystem.makedirs(os.path.join(self.config.default_archive_dir, domain))\n\n        for kind in ALL_FOUR:\n            config_file[kind] = os.path.join(self.config.live_dir, domain,\n                                        kind + \".pem\")\n\n        config_file.filename = os.path.join(self.config.renewal_configs_dir,\n                                       domain + \".conf\")\n        config_file.write()\n        return config_file\n\n\nclass UpdateLiveSymlinksTest(BaseCertManagerTest):\n    \"\"\"Tests for certbot._internal.cert_manager.update_live_symlinks\n    \"\"\"\n    def test_update_live_symlinks(self):\n        \"\"\"Test update_live_symlinks\"\"\"\n        # create files with incorrect symlinks\n        from certbot._internal import cert_manager\n        archive_paths = {}\n        for domain in self.domains:\n            custom_archive = self.domains[domain]\n            if custom_archive is not None:\n                archive_dir_path = custom_archive\n            else:\n                archive_dir_path = os.path.join(self.config.default_archive_dir, domain)\n            archive_paths[domain] = dict((kind,\n                os.path.join(archive_dir_path, kind + \"1.pem\")) for kind in ALL_FOUR)\n            for kind in ALL_FOUR:\n                live_path = self.config_files[domain][kind]\n                archive_path = archive_paths[domain][kind]\n                open(archive_path, 'a').close()\n                # path is incorrect but base must be correct\n                os.symlink(os.path.join(self.config.config_dir, kind + \"1.pem\"), live_path)\n\n        # run update symlinks\n        cert_manager.update_live_symlinks(self.config)\n\n        # check that symlinks go where they should\n        prev_dir = os.getcwd()\n        try:\n            for domain in self.domains:\n                for kind in ALL_FOUR:\n                    os.chdir(os.path.dirname(self.config_files[domain][kind]))\n                    self.assertEqual(\n                        filesystem.realpath(os.readlink(self.config_files[domain][kind])),\n                        filesystem.realpath(archive_paths[domain][kind]))\n        finally:\n            os.chdir(prev_dir)\n\n\nclass DeleteTest(storage_test.BaseRenewableCertTest):\n    \"\"\"Tests for certbot._internal.cert_manager.delete\n    \"\"\"\n\n    def _call(self):\n        from certbot._internal import cert_manager\n        cert_manager.delete(self.config)\n\n    @test_util.patch_get_utility()\n    @mock.patch('certbot._internal.cert_manager.lineage_for_certname')\n    @mock.patch('certbot._internal.storage.delete_files')\n    def test_delete_from_config(self, mock_delete_files, mock_lineage_for_certname,\n        unused_get_utility):\n        \"\"\"Test delete\"\"\"\n        mock_lineage_for_certname.return_value = self.test_rc\n        self.config.certname = \"example.org\"\n        self._call()\n        mock_delete_files.assert_called_once_with(self.config, \"example.org\")\n\n    @test_util.patch_get_utility()\n    @mock.patch('certbot._internal.cert_manager.lineage_for_certname')\n    @mock.patch('certbot._internal.storage.delete_files')\n    def test_delete_interactive_single(self, mock_delete_files, mock_lineage_for_certname,\n        mock_util):\n        \"\"\"Test delete\"\"\"\n        mock_lineage_for_certname.return_value = self.test_rc\n        mock_util().checklist.return_value = (display_util.OK, [\"example.org\"])\n        self._call()\n        mock_delete_files.assert_called_once_with(self.config, \"example.org\")\n\n    @test_util.patch_get_utility()\n    @mock.patch('certbot._internal.cert_manager.lineage_for_certname')\n    @mock.patch('certbot._internal.storage.delete_files')\n    def test_delete_interactive_multiple(self, mock_delete_files, mock_lineage_for_certname,\n        mock_util):\n        \"\"\"Test delete\"\"\"\n        mock_lineage_for_certname.return_value = self.test_rc\n        mock_util().checklist.return_value = (display_util.OK, [\"example.org\", \"other.org\"])\n        self._call()\n        mock_delete_files.assert_any_call(self.config, \"example.org\")\n        mock_delete_files.assert_any_call(self.config, \"other.org\")\n        self.assertEqual(mock_delete_files.call_count, 2)\n\n\nclass CertificatesTest(BaseCertManagerTest):\n    \"\"\"Tests for certbot._internal.cert_manager.certificates\n    \"\"\"\n    def _certificates(self, *args, **kwargs):\n        from certbot._internal.cert_manager import certificates\n        return certificates(*args, **kwargs)\n\n    @mock.patch('certbot._internal.cert_manager.logger')\n    @test_util.patch_get_utility()\n    def test_certificates_parse_fail(self, mock_utility, mock_logger):\n        self._certificates(self.config)\n        self.assertTrue(mock_logger.warning.called) #pylint: disable=no-member\n        self.assertTrue(mock_utility.called)\n\n    @mock.patch('certbot._internal.cert_manager.logger')\n    @test_util.patch_get_utility()\n    def test_certificates_quiet(self, mock_utility, mock_logger):\n        self.config.quiet = True\n        self._certificates(self.config)\n        self.assertFalse(mock_utility.notification.called)\n        self.assertTrue(mock_logger.warning.called) #pylint: disable=no-member\n\n    @mock.patch('certbot.crypto_util.verify_renewable_cert')\n    @mock.patch('certbot._internal.cert_manager.logger')\n    @test_util.patch_get_utility()\n    @mock.patch(\"certbot._internal.storage.RenewableCert\")\n    @mock.patch('certbot._internal.cert_manager._report_human_readable')\n    def test_certificates_parse_success(self, mock_report, mock_renewable_cert,\n        mock_utility, mock_logger, mock_verifier):\n        mock_verifier.return_value = None\n        mock_report.return_value = \"\"\n        self._certificates(self.config)\n        self.assertFalse(mock_logger.warning.called)\n        self.assertTrue(mock_report.called)\n        self.assertTrue(mock_utility.called)\n        self.assertTrue(mock_renewable_cert.called)\n\n    @mock.patch('certbot._internal.cert_manager.logger')\n    @test_util.patch_get_utility()\n    def test_certificates_no_files(self, mock_utility, mock_logger):\n        empty_tempdir = tempfile.mkdtemp()\n        empty_config = configuration.NamespaceConfig(mock.MagicMock(\n            config_dir=os.path.join(empty_tempdir, \"config\"),\n            work_dir=os.path.join(empty_tempdir, \"work\"),\n            logs_dir=os.path.join(empty_tempdir, \"logs\"),\n            quiet=False\n        ))\n\n        filesystem.makedirs(empty_config.renewal_configs_dir)\n        self._certificates(empty_config)\n        self.assertFalse(mock_logger.warning.called)\n        self.assertTrue(mock_utility.called)\n        shutil.rmtree(empty_tempdir)\n\n    @mock.patch('certbot._internal.cert_manager.ocsp.RevocationChecker.ocsp_revoked')\n    def test_report_human_readable(self, mock_revoked):\n        mock_revoked.return_value = None\n        from certbot._internal import cert_manager\n        import datetime\n        import pytz\n        expiry = pytz.UTC.fromutc(datetime.datetime.utcnow())\n\n        cert = mock.MagicMock(lineagename=\"nameone\")\n        cert.target_expiry = expiry\n        cert.names.return_value = [\"nameone\", \"nametwo\"]\n        cert.is_test_cert = False\n        parsed_certs = [cert]\n\n        mock_config = mock.MagicMock(certname=None, lineagename=None)\n        # pylint: disable=protected-access\n\n        # pylint: disable=protected-access\n        get_report = lambda: cert_manager._report_human_readable(mock_config, parsed_certs)\n\n        out = get_report()\n        self.assertTrue(\"INVALID: EXPIRED\" in out)\n\n        cert.target_expiry += datetime.timedelta(hours=2)\n        # pylint: disable=protected-access\n        out = get_report()\n        self.assertTrue('1 hour(s)' in out or '2 hour(s)' in out)\n        self.assertTrue('VALID' in out and 'INVALID' not in out)\n\n        cert.target_expiry += datetime.timedelta(days=1)\n        # pylint: disable=protected-access\n        out = get_report()\n        self.assertTrue('1 day' in out)\n        self.assertFalse('under' in out)\n        self.assertTrue('VALID' in out and 'INVALID' not in out)\n\n        cert.target_expiry += datetime.timedelta(days=2)\n        # pylint: disable=protected-access\n        out = get_report()\n        self.assertTrue('3 days' in out)\n        self.assertTrue('VALID' in out and 'INVALID' not in out)\n\n        cert.is_test_cert = True\n        mock_revoked.return_value = True\n        out = get_report()\n        self.assertTrue('INVALID: TEST_CERT, REVOKED' in out)\n\n        cert = mock.MagicMock(lineagename=\"indescribable\")\n        cert.target_expiry = expiry\n        cert.names.return_value = [\"nameone\", \"thrice.named\"]\n        cert.is_test_cert = True\n        parsed_certs.append(cert)\n\n        out = get_report()\n        self.assertEqual(len(re.findall(\"INVALID:\", out)), 2)\n        mock_config.domains = [\"thrice.named\"]\n        out = get_report()\n        self.assertEqual(len(re.findall(\"INVALID:\", out)), 1)\n        mock_config.domains = [\"nameone\"]\n        out = get_report()\n        self.assertEqual(len(re.findall(\"INVALID:\", out)), 2)\n        mock_config.certname = \"indescribable\"\n        out = get_report()\n        self.assertEqual(len(re.findall(\"INVALID:\", out)), 1)\n        mock_config.certname = \"horror\"\n        out = get_report()\n        self.assertEqual(len(re.findall(\"INVALID:\", out)), 0)\n\n\nclass SearchLineagesTest(BaseCertManagerTest):\n    \"\"\"Tests for certbot._internal.cert_manager._search_lineages.\"\"\"\n\n    @mock.patch('certbot.util.make_or_verify_dir')\n    @mock.patch('certbot._internal.storage.renewal_conf_files')\n    @mock.patch('certbot._internal.storage.RenewableCert')\n    def test_cert_storage_error(self, mock_renewable_cert, mock_renewal_conf_files,\n                                mock_make_or_verify_dir):\n        mock_renewal_conf_files.return_value = [\"badfile\"]\n        mock_renewable_cert.side_effect = errors.CertStorageError\n        from certbot._internal import cert_manager\n        # pylint: disable=protected-access\n        self.assertEqual(cert_manager._search_lineages(self.config, lambda x: x, \"check\"), \"check\")\n        self.assertTrue(mock_make_or_verify_dir.called)\n\n\nclass LineageForCertnameTest(BaseCertManagerTest):\n    \"\"\"Tests for certbot._internal.cert_manager.lineage_for_certname\"\"\"\n\n    @mock.patch('certbot.util.make_or_verify_dir')\n    @mock.patch('certbot._internal.storage.renewal_file_for_certname')\n    @mock.patch('certbot._internal.storage.RenewableCert')\n    def test_found_match(self, mock_renewable_cert, mock_renewal_conf_file,\n                         mock_make_or_verify_dir):\n        mock_renewal_conf_file.return_value = \"somefile.conf\"\n        mock_match = mock.Mock(lineagename=\"example.com\")\n        mock_renewable_cert.return_value = mock_match\n        from certbot._internal import cert_manager\n        self.assertEqual(cert_manager.lineage_for_certname(self.config, \"example.com\"), mock_match)\n        self.assertTrue(mock_make_or_verify_dir.called)\n\n    @mock.patch('certbot.util.make_or_verify_dir')\n    @mock.patch('certbot._internal.storage.renewal_file_for_certname')\n    def test_no_match(self, mock_renewal_conf_file, mock_make_or_verify_dir):\n        mock_renewal_conf_file.return_value = \"other.com.conf\"\n        from certbot._internal import cert_manager\n        self.assertEqual(cert_manager.lineage_for_certname(self.config, \"example.com\"), None)\n        self.assertTrue(mock_make_or_verify_dir.called)\n\n    @mock.patch('certbot.util.make_or_verify_dir')\n    @mock.patch('certbot._internal.storage.renewal_file_for_certname')\n    def test_no_renewal_file(self, mock_renewal_conf_file, mock_make_or_verify_dir):\n        mock_renewal_conf_file.side_effect = errors.CertStorageError()\n        from certbot._internal import cert_manager\n        self.assertEqual(cert_manager.lineage_for_certname(self.config, \"example.com\"), None)\n        self.assertTrue(mock_make_or_verify_dir.called)\n\n\nclass DomainsForCertnameTest(BaseCertManagerTest):\n    \"\"\"Tests for certbot._internal.cert_manager.domains_for_certname\"\"\"\n\n    @mock.patch('certbot.util.make_or_verify_dir')\n    @mock.patch('certbot._internal.storage.renewal_file_for_certname')\n    @mock.patch('certbot._internal.storage.RenewableCert')\n    def test_found_match(self, mock_renewable_cert, mock_renewal_conf_file,\n                         mock_make_or_verify_dir):\n        mock_renewal_conf_file.return_value = \"somefile.conf\"\n        mock_match = mock.Mock(lineagename=\"example.com\")\n        domains = [\"example.com\", \"example.org\"]\n        mock_match.names.return_value = domains\n        mock_renewable_cert.return_value = mock_match\n        from certbot._internal import cert_manager\n        self.assertEqual(cert_manager.domains_for_certname(self.config, \"example.com\"),\n            domains)\n        self.assertTrue(mock_make_or_verify_dir.called)\n\n    @mock.patch('certbot.util.make_or_verify_dir')\n    @mock.patch('certbot._internal.storage.renewal_file_for_certname')\n    def test_no_match(self, mock_renewal_conf_file, mock_make_or_verify_dir):\n        mock_renewal_conf_file.return_value = \"somefile.conf\"\n        from certbot._internal import cert_manager\n        self.assertEqual(cert_manager.domains_for_certname(self.config, \"other.com\"), None)\n        self.assertTrue(mock_make_or_verify_dir.called)\n\n\nclass RenameLineageTest(BaseCertManagerTest):\n    \"\"\"Tests for certbot._internal.cert_manager.rename_lineage\"\"\"\n\n    def setUp(self):\n        super(RenameLineageTest, self).setUp()\n        self.config.certname = \"example.org\"\n        self.config.new_certname = \"after\"\n\n    def _call(self, *args, **kwargs):\n        from certbot._internal import cert_manager\n        return cert_manager.rename_lineage(*args, **kwargs)\n\n    @mock.patch('certbot._internal.storage.renewal_conf_files')\n    @test_util.patch_get_utility()\n    def test_no_certname(self, mock_get_utility, mock_renewal_conf_files):\n        self.config.certname = None\n        self.config.new_certname = \"two\"\n\n        # if not choices\n        mock_renewal_conf_files.return_value = []\n        self.assertRaises(errors.Error, self._call, self.config)\n\n        mock_renewal_conf_files.return_value = [\"one.conf\"]\n        util_mock = mock_get_utility()\n        util_mock.menu.return_value = (display_util.CANCEL, 0)\n        self.assertRaises(errors.Error, self._call, self.config)\n\n        util_mock.menu.return_value = (display_util.OK, -1)\n        self.assertRaises(errors.Error, self._call, self.config)\n\n    @test_util.patch_get_utility()\n    def test_no_new_certname(self, mock_get_utility):\n        self.config.certname = \"one\"\n        self.config.new_certname = None\n\n        util_mock = mock_get_utility()\n        util_mock.input.return_value = (display_util.CANCEL, \"name\")\n        self.assertRaises(errors.Error, self._call, self.config)\n\n        util_mock.input.return_value = (display_util.OK, None)\n        self.assertRaises(errors.Error, self._call, self.config)\n\n    @test_util.patch_get_utility()\n    @mock.patch('certbot._internal.cert_manager.lineage_for_certname')\n    def test_no_existing_certname(self, mock_lineage_for_certname, unused_get_utility):\n        self.config.certname = \"one\"\n        self.config.new_certname = \"two\"\n        mock_lineage_for_certname.return_value = None\n        self.assertRaises(errors.ConfigurationError,\n            self._call, self.config)\n\n    @test_util.patch_get_utility()\n    @mock.patch(\"certbot._internal.storage.RenewableCert._check_symlinks\")\n    def test_rename_cert(self, mock_check, unused_get_utility):\n        mock_check.return_value = True\n        self._call(self.config)\n        from certbot._internal import cert_manager\n        updated_lineage = cert_manager.lineage_for_certname(self.config, self.config.new_certname)\n        self.assertTrue(updated_lineage is not None)\n        self.assertEqual(updated_lineage.lineagename, self.config.new_certname)\n\n    @test_util.patch_get_utility()\n    @mock.patch(\"certbot._internal.storage.RenewableCert._check_symlinks\")\n    def test_rename_cert_interactive_certname(self, mock_check, mock_get_utility):\n        mock_check.return_value = True\n        self.config.certname = None\n        util_mock = mock_get_utility()\n        util_mock.menu.return_value = (display_util.OK, 0)\n        self._call(self.config)\n        from certbot._internal import cert_manager\n        updated_lineage = cert_manager.lineage_for_certname(self.config, self.config.new_certname)\n        self.assertTrue(updated_lineage is not None)\n        self.assertEqual(updated_lineage.lineagename, self.config.new_certname)\n\n    @test_util.patch_get_utility()\n    @mock.patch(\"certbot._internal.storage.RenewableCert._check_symlinks\")\n    def test_rename_cert_bad_new_certname(self, mock_check, unused_get_utility):\n        mock_check.return_value = True\n\n        # for example, don't rename to existing certname\n        self.config.new_certname = \"example.org\"\n        self.assertRaises(errors.ConfigurationError, self._call, self.config)\n\n        self.config.new_certname = \"one{0}two\".format(os.path.sep)\n        self.assertRaises(errors.ConfigurationError, self._call, self.config)\n\n\nclass DuplicativeCertsTest(storage_test.BaseRenewableCertTest):\n    \"\"\"Test to avoid duplicate lineages.\"\"\"\n\n    def setUp(self):\n        super(DuplicativeCertsTest, self).setUp()\n        self.config_file.write()\n        self._write_out_ex_kinds()\n\n    @mock.patch('certbot.util.make_or_verify_dir')\n    def test_find_duplicative_names(self, unused_makedir):\n        from certbot._internal.cert_manager import find_duplicative_certs\n        test_cert = test_util.load_vector('cert-san_512.pem')\n        with open(self.test_rc.cert, 'wb') as f:\n            f.write(test_cert)\n\n        # No overlap at all\n        result = find_duplicative_certs(\n            self.config, ['wow.net', 'hooray.org'])\n        self.assertEqual(result, (None, None))\n\n        # Totally identical\n        result = find_duplicative_certs(\n            self.config, ['example.com', 'www.example.com'])\n        self.assertTrue(result[0].configfile.filename.endswith('example.org.conf'))\n        self.assertEqual(result[1], None)\n\n        # Superset\n        result = find_duplicative_certs(\n            self.config, ['example.com', 'www.example.com', 'something.new'])\n        self.assertEqual(result[0], None)\n        self.assertTrue(result[1].configfile.filename.endswith('example.org.conf'))\n\n        # Partial overlap doesn't count\n        result = find_duplicative_certs(\n            self.config, ['example.com', 'something.new'])\n        self.assertEqual(result, (None, None))\n\n\nclass CertPathToLineageTest(storage_test.BaseRenewableCertTest):\n    \"\"\"Tests for certbot._internal.cert_manager.cert_path_to_lineage\"\"\"\n\n    def setUp(self):\n        super(CertPathToLineageTest, self).setUp()\n        self.config_file.write()\n        self._write_out_ex_kinds()\n        self.fullchain = os.path.join(self.config.config_dir, 'live', 'example.org',\n                'fullchain.pem')\n        self.config.cert_path = (self.fullchain, '')\n\n    def _call(self, cli_config):\n        from certbot._internal.cert_manager import cert_path_to_lineage\n        return cert_path_to_lineage(cli_config)\n\n    def _archive_files(self, cli_config, filetype):\n        from certbot._internal.cert_manager import _archive_files\n        return _archive_files(cli_config, filetype)\n\n    def test_basic_match(self):\n        self.assertEqual('example.org', self._call(self.config))\n\n    def test_no_match_exists(self):\n        bad_test_config = self.config\n        bad_test_config.cert_path = os.path.join(self.config.config_dir, 'live',\n                'SailorMoon', 'fullchain.pem')\n        self.assertRaises(errors.Error, self._call, bad_test_config)\n\n    @mock.patch('certbot._internal.cert_manager._acceptable_matches')\n    def test_options_fullchain(self, mock_acceptable_matches):\n        mock_acceptable_matches.return_value = [lambda x: x.fullchain_path]\n        self.config.fullchain_path = self.fullchain\n        self.assertEqual('example.org', self._call(self.config))\n\n    @mock.patch('certbot._internal.cert_manager._acceptable_matches')\n    def test_options_cert_path(self, mock_acceptable_matches):\n        mock_acceptable_matches.return_value = [lambda x: x.cert_path]\n        test_cert_path = os.path.join(self.config.config_dir, 'live', 'example.org',\n                'cert.pem')\n        self.config.cert_path = (test_cert_path, '')\n        self.assertEqual('example.org', self._call(self.config))\n\n    @mock.patch('certbot._internal.cert_manager._acceptable_matches')\n    def test_options_archive_cert(self, mock_acceptable_matches):\n        # Also this and the next test check that the regex of _archive_files is working.\n        self.config.cert_path = (os.path.join(self.config.config_dir, 'archive', 'example.org',\n            'cert11.pem'), '')\n        mock_acceptable_matches.return_value = [lambda x: self._archive_files(x, 'cert')]\n        self.assertEqual('example.org', self._call(self.config))\n\n    @mock.patch('certbot._internal.cert_manager._acceptable_matches')\n    def test_options_archive_fullchain(self, mock_acceptable_matches):\n        self.config.cert_path = (os.path.join(self.config.config_dir, 'archive',\n            'example.org', 'fullchain11.pem'), '')\n        mock_acceptable_matches.return_value = [lambda x:\n                self._archive_files(x, 'fullchain')]\n        self.assertEqual('example.org', self._call(self.config))\n\n\nclass MatchAndCheckOverlaps(storage_test.BaseRenewableCertTest):\n    \"\"\"Tests for certbot._internal.cert_manager.match_and_check_overlaps w/o overlapping\n       archive dirs.\"\"\"\n    # A test with real overlapping archive dirs can be found in tests/boulder_integration.sh\n    def setUp(self):\n        super(MatchAndCheckOverlaps, self).setUp()\n        self.config_file.write()\n        self._write_out_ex_kinds()\n        self.fullchain = os.path.join(self.config.config_dir, 'live', 'example.org',\n                'fullchain.pem')\n        self.config.cert_path = (self.fullchain, '')\n\n    def _call(self, cli_config, acceptable_matches, match_func, rv_func):\n        from certbot._internal.cert_manager import match_and_check_overlaps\n        return match_and_check_overlaps(cli_config, acceptable_matches, match_func, rv_func)\n\n    def test_basic_match(self):\n        from certbot._internal.cert_manager import _acceptable_matches\n        self.assertEqual(['example.org'], self._call(self.config, _acceptable_matches(),\n            lambda x: self.config.cert_path[0], lambda x: x.lineagename))\n\n    @mock.patch('certbot._internal.cert_manager._search_lineages')\n    def test_no_matches(self, mock_search_lineages):\n        mock_search_lineages.return_value = []\n        self.assertRaises(errors.Error, self._call, self.config, None, None, None)\n\n    @mock.patch('certbot._internal.cert_manager._search_lineages')\n    def test_too_many_matches(self, mock_search_lineages):\n        mock_search_lineages.return_value = ['spider', 'dance']\n        self.assertRaises(errors.OverlappingMatchFound, self._call, self.config, None, None, None)\n\n\nclass GetCertnameTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.cert_manager.\"\"\"\n\n    def setUp(self):\n        get_utility_patch = test_util.patch_get_utility()\n        self.mock_get_utility = get_utility_patch.start()\n        self.addCleanup(get_utility_patch.stop)\n        self.config = mock.MagicMock()\n        self.config.certname = None\n\n    @mock.patch('certbot._internal.storage.renewal_conf_files')\n    @mock.patch('certbot._internal.storage.lineagename_for_filename')\n    def test_get_certnames(self, mock_name, mock_files):\n        mock_files.return_value = ['example.com.conf']\n        mock_name.return_value = 'example.com'\n        from certbot._internal import cert_manager\n        prompt = \"Which certificate would you\"\n        self.mock_get_utility().menu.return_value = (display_util.OK, 0)\n        self.assertEqual(\n            cert_manager.get_certnames(\n                self.config, \"verb\", allow_multiple=False), ['example.com'])\n        self.assertTrue(\n            prompt in self.mock_get_utility().menu.call_args[0][0])\n\n    @mock.patch('certbot._internal.storage.renewal_conf_files')\n    @mock.patch('certbot._internal.storage.lineagename_for_filename')\n    def test_get_certnames_custom_prompt(self, mock_name, mock_files):\n        mock_files.return_value = ['example.com.conf']\n        mock_name.return_value = 'example.com'\n        from certbot._internal import cert_manager\n        prompt = \"custom prompt\"\n        self.mock_get_utility().menu.return_value = (display_util.OK, 0)\n        self.assertEqual(\n            cert_manager.get_certnames(\n                self.config, \"verb\", allow_multiple=False, custom_prompt=prompt),\n            ['example.com'])\n        self.assertEqual(self.mock_get_utility().menu.call_args[0][0],\n                          prompt)\n\n    @mock.patch('certbot._internal.storage.renewal_conf_files')\n    @mock.patch('certbot._internal.storage.lineagename_for_filename')\n    def test_get_certnames_user_abort(self, mock_name, mock_files):\n        mock_files.return_value = ['example.com.conf']\n        mock_name.return_value = 'example.com'\n        from certbot._internal import cert_manager\n        self.mock_get_utility().menu.return_value = (display_util.CANCEL, 0)\n        self.assertRaises(\n            errors.Error,\n            cert_manager.get_certnames,\n            self.config, \"erroring_anyway\", allow_multiple=False)\n\n    @mock.patch('certbot._internal.storage.renewal_conf_files')\n    @mock.patch('certbot._internal.storage.lineagename_for_filename')\n    def test_get_certnames_allow_multiple(self, mock_name, mock_files):\n        mock_files.return_value = ['example.com.conf']\n        mock_name.return_value = 'example.com'\n        from certbot._internal import cert_manager\n        prompt = \"Which certificate(s) would you\"\n        self.mock_get_utility().checklist.return_value = (display_util.OK,\n                                                          ['example.com'])\n        self.assertEqual(\n            cert_manager.get_certnames(\n                self.config, \"verb\", allow_multiple=True), ['example.com'])\n        self.assertTrue(\n            prompt in self.mock_get_utility().checklist.call_args[0][0])\n\n    @mock.patch('certbot._internal.storage.renewal_conf_files')\n    @mock.patch('certbot._internal.storage.lineagename_for_filename')\n    def test_get_certnames_allow_multiple_custom_prompt(self, mock_name, mock_files):\n        mock_files.return_value = ['example.com.conf']\n        mock_name.return_value = 'example.com'\n        from certbot._internal import cert_manager\n        prompt = \"custom prompt\"\n        self.mock_get_utility().checklist.return_value = (display_util.OK,\n                                                          ['example.com'])\n        self.assertEqual(\n            cert_manager.get_certnames(\n                self.config, \"verb\", allow_multiple=True, custom_prompt=prompt),\n            ['example.com'])\n        self.assertEqual(\n            self.mock_get_utility().checklist.call_args[0][0],\n            prompt)\n\n    @mock.patch('certbot._internal.storage.renewal_conf_files')\n    @mock.patch('certbot._internal.storage.lineagename_for_filename')\n    def test_get_certnames_allow_multiple_user_abort(self, mock_name, mock_files):\n        mock_files.return_value = ['example.com.conf']\n        mock_name.return_value = 'example.com'\n        from certbot._internal import cert_manager\n        self.mock_get_utility().checklist.return_value = (display_util.CANCEL, [])\n        self.assertRaises(\n            errors.Error,\n            cert_manager.get_certnames,\n            self.config, \"erroring_anyway\", allow_multiple=True)\n\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/cli_test.py",
    "content": "\"\"\"Tests for certbot._internal.cli.\"\"\"\nimport argparse\nimport copy\nimport tempfile\nimport unittest\n\nimport mock\nimport six\nfrom six.moves import reload_module  # pylint: disable=import-error\n\nfrom acme import challenges\nfrom certbot import errors\nfrom certbot._internal import cli\nfrom certbot._internal import constants\nfrom certbot._internal.plugins import disco\nfrom certbot.compat import filesystem\nfrom certbot.compat import os\nimport certbot.tests.util as test_util\nfrom certbot.tests.util import TempDirTestCase\n\nPLUGINS = disco.PluginsRegistry.find_all()\n\n\nclass TestReadFile(TempDirTestCase):\n    \"\"\"Test cli.read_file\"\"\"\n    def test_read_file(self):\n        curr_dir = os.getcwd()\n        try:\n            # On Windows current directory may be on a different drive than self.tempdir.\n            # However a relative path between two different drives is invalid. So we move to\n            # self.tempdir to ensure that we stay on the same drive.\n            os.chdir(self.tempdir)\n            # The read-only filesystem introduced with macOS Catalina can break\n            # code using relative paths below. See\n            # https://bugs.python.org/issue38295 for another example of this.\n            # Eliminating any possible symlinks in self.tempdir before passing\n            # it to os.path.relpath solves the problem. This is done by calling\n            # filesystem.realpath which removes any symlinks in the path on\n            # POSIX systems.\n            real_path = filesystem.realpath(os.path.join(self.tempdir, 'foo'))\n            relative_path = os.path.relpath(real_path)\n            self.assertRaises(\n                argparse.ArgumentTypeError, cli.read_file, relative_path)\n\n            test_contents = b'bar\\n'\n            with open(relative_path, 'wb') as f:\n                f.write(test_contents)\n\n            path, contents = cli.read_file(relative_path)\n            self.assertEqual(path, os.path.abspath(path))\n            self.assertEqual(contents, test_contents)\n        finally:\n            os.chdir(curr_dir)\n\n\nclass FlagDefaultTest(unittest.TestCase):\n    \"\"\"Tests cli.flag_default\"\"\"\n\n    def test_default_directories(self):\n        if os.name != 'nt':\n            self.assertEqual(cli.flag_default('config_dir'), '/etc/letsencrypt')\n            self.assertEqual(cli.flag_default('work_dir'), '/var/lib/letsencrypt')\n            self.assertEqual(cli.flag_default('logs_dir'), '/var/log/letsencrypt')\n        else:\n            self.assertEqual(cli.flag_default('config_dir'), 'C:\\\\Certbot')\n            self.assertEqual(cli.flag_default('work_dir'), 'C:\\\\Certbot\\\\lib')\n            self.assertEqual(cli.flag_default('logs_dir'), 'C:\\\\Certbot\\\\log')\n\n\nclass ParseTest(unittest.TestCase):\n    '''Test the cli args entrypoint'''\n\n\n    def setUp(self):\n        reload_module(cli)\n\n    @staticmethod\n    def _unmocked_parse(*args, **kwargs):\n        \"\"\"Get result of cli.prepare_and_parse_args.\"\"\"\n        return cli.prepare_and_parse_args(PLUGINS, *args, **kwargs)\n\n    @staticmethod\n    def parse(*args, **kwargs):\n        \"\"\"Mocks zope.component.getUtility and calls _unmocked_parse.\"\"\"\n        with test_util.patch_get_utility():\n            return ParseTest._unmocked_parse(*args, **kwargs)\n\n    def _help_output(self, args):\n        \"Run a command, and return the output string for scrutiny\"\n\n        output = six.StringIO()\n\n        def write_msg(message, *args, **kwargs): # pylint: disable=missing-docstring,unused-argument\n            output.write(message)\n\n        with mock.patch('certbot._internal.main.sys.stdout', new=output):\n            with test_util.patch_get_utility() as mock_get_utility:\n                mock_get_utility().notification.side_effect = write_msg\n                with mock.patch('certbot._internal.main.sys.stderr'):\n                    self.assertRaises(SystemExit, self._unmocked_parse, args, output)\n\n        return output.getvalue()\n\n    @mock.patch(\"certbot._internal.cli.helpful.flag_default\")\n    def test_cli_ini_domains(self, mock_flag_default):\n        with tempfile.NamedTemporaryFile() as tmp_config:\n            tmp_config.close()  # close now because of compatibility issues on Windows\n            # use a shim to get ConfigArgParse to pick up tmp_config\n            shim = (\n                    lambda v: copy.deepcopy(constants.CLI_DEFAULTS[v])\n                    if v != \"config_files\"\n                    else [tmp_config.name]\n                    )\n            mock_flag_default.side_effect = shim\n\n            namespace = self.parse([\"certonly\"])\n            self.assertEqual(namespace.domains, [])\n            with open(tmp_config.name, 'w') as file_h:\n                file_h.write(\"domains = example.com\")\n            namespace = self.parse([\"certonly\"])\n            self.assertEqual(namespace.domains, [\"example.com\"])\n            namespace = self.parse([\"renew\"])\n            self.assertEqual(namespace.domains, [])\n\n    def test_no_args(self):\n        namespace = self.parse([])\n        for d in ('config_dir', 'logs_dir', 'work_dir'):\n            self.assertEqual(getattr(namespace, d), cli.flag_default(d))\n\n    def test_install_abspath(self):\n        cert = 'cert'\n        key = 'key'\n        chain = 'chain'\n        fullchain = 'fullchain'\n\n        with mock.patch('certbot._internal.main.install'):\n            namespace = self.parse(['install', '--cert-path', cert,\n                                    '--key-path', 'key', '--chain-path',\n                                    'chain', '--fullchain-path', 'fullchain'])\n\n        self.assertEqual(namespace.cert_path, os.path.abspath(cert))\n        self.assertEqual(namespace.key_path, os.path.abspath(key))\n        self.assertEqual(namespace.chain_path, os.path.abspath(chain))\n        self.assertEqual(namespace.fullchain_path, os.path.abspath(fullchain))\n\n    def test_help(self):\n        self._help_output(['--help'])  # assert SystemExit is raised here\n        out = self._help_output(['--help', 'all'])\n        self.assertTrue(\"--configurator\" in out)\n        self.assertTrue(\"how a certificate is deployed\" in out)\n        self.assertTrue(\"--webroot-path\" in out)\n        self.assertTrue(\"--text\" not in out)\n        self.assertTrue(\"%s\" not in out)\n        self.assertTrue(\"{0}\" not in out)\n        self.assertTrue(\"--renew-hook\" not in out)\n\n        out = self._help_output(['-h', 'nginx'])\n        if \"nginx\" in PLUGINS:\n            # may be false while building distributions without plugins\n            self.assertTrue(\"--nginx-ctl\" in out)\n        self.assertTrue(\"--webroot-path\" not in out)\n        self.assertTrue(\"--checkpoints\" not in out)\n\n        out = self._help_output(['-h'])\n        self.assertTrue(\"letsencrypt-auto\" not in out)  # test cli.cli_command\n        if \"nginx\" in PLUGINS:\n            self.assertTrue(\"Use the Nginx plugin\" in out)\n        else:\n            self.assertTrue(\"(the certbot nginx plugin is not\" in out)\n\n        out = self._help_output(['--help', 'plugins'])\n        self.assertTrue(\"--webroot-path\" not in out)\n        self.assertTrue(\"--prepare\" in out)\n        self.assertTrue('\"plugins\" subcommand' in out)\n\n        # test multiple topics\n        out = self._help_output(['-h', 'renew'])\n        self.assertTrue(\"--keep\" in out)\n        out = self._help_output(['-h', 'automation'])\n        self.assertTrue(\"--keep\" in out)\n        out = self._help_output(['-h', 'revoke'])\n        self.assertTrue(\"--keep\" not in out)\n\n        out = self._help_output(['--help', 'install'])\n        self.assertTrue(\"--cert-path\" in out)\n        self.assertTrue(\"--key-path\" in out)\n\n        out = self._help_output(['--help', 'revoke'])\n        self.assertTrue(\"--cert-path\" in out)\n        self.assertTrue(\"--key-path\" in out)\n        self.assertTrue(\"--reason\" in out)\n        self.assertTrue(\"--delete-after-revoke\" in out)\n        self.assertTrue(\"--no-delete-after-revoke\" in out)\n\n        out = self._help_output(['-h', 'register'])\n        self.assertTrue(\"--cert-path\" not in out)\n        self.assertTrue(\"--key-path\" not in out)\n\n        out = self._help_output(['-h'])\n        self.assertTrue(cli.SHORT_USAGE in out)\n        self.assertTrue(cli.COMMAND_OVERVIEW[:100] in out)\n        self.assertTrue(\"%s\" not in out)\n        self.assertTrue(\"{0}\" not in out)\n\n    def test_help_no_dashes(self):\n        self._help_output(['help'])  # assert SystemExit is raised here\n\n        out = self._help_output(['help', 'all'])\n        self.assertTrue(\"--configurator\" in out)\n        self.assertTrue(\"how a certificate is deployed\" in out)\n        self.assertTrue(\"--webroot-path\" in out)\n        self.assertTrue(\"--text\" not in out)\n        self.assertTrue(\"%s\" not in out)\n        self.assertTrue(\"{0}\" not in out)\n\n        out = self._help_output(['help', 'install'])\n        self.assertTrue(\"--cert-path\" in out)\n        self.assertTrue(\"--key-path\" in out)\n\n        out = self._help_output(['help', 'revoke'])\n        self.assertTrue(\"--cert-path\" in out)\n        self.assertTrue(\"--key-path\" in out)\n\n    def test_parse_domains(self):\n        short_args = ['-d', 'example.com']\n        namespace = self.parse(short_args)\n        self.assertEqual(namespace.domains, ['example.com'])\n\n        short_args = ['-d', 'trailing.period.com.']\n        namespace = self.parse(short_args)\n        self.assertEqual(namespace.domains, ['trailing.period.com'])\n\n        short_args = ['-d', 'example.com,another.net,third.org,example.com']\n        namespace = self.parse(short_args)\n        self.assertEqual(namespace.domains, ['example.com', 'another.net',\n                                             'third.org'])\n\n        long_args = ['--domains', 'example.com']\n        namespace = self.parse(long_args)\n        self.assertEqual(namespace.domains, ['example.com'])\n\n        long_args = ['--domains', 'trailing.period.com.']\n        namespace = self.parse(long_args)\n        self.assertEqual(namespace.domains, ['trailing.period.com'])\n\n        long_args = ['--domains', 'example.com,another.net,example.com']\n        namespace = self.parse(long_args)\n        self.assertEqual(namespace.domains, ['example.com', 'another.net'])\n\n    def test_preferred_challenges(self):\n        short_args = ['--preferred-challenges', 'http, dns']\n        namespace = self.parse(short_args)\n\n        expected = [challenges.HTTP01.typ, challenges.DNS01.typ]\n        self.assertEqual(namespace.pref_challs, expected)\n\n        short_args = ['--preferred-challenges', 'jumping-over-the-moon']\n        # argparse.ArgumentError makes argparse print more information\n        # to stderr and call sys.exit()\n        with mock.patch('sys.stderr'):\n            self.assertRaises(SystemExit, self.parse, short_args)\n\n    def test_server_flag(self):\n        namespace = self.parse('--server example.com'.split())\n        self.assertEqual(namespace.server, 'example.com')\n\n    def test_must_staple_flag(self):\n        short_args = ['--must-staple']\n        namespace = self.parse(short_args)\n        self.assertTrue(namespace.must_staple)\n        self.assertTrue(namespace.staple)\n\n    def _check_server_conflict_message(self, parser_args, conflicting_args):\n        try:\n            self.parse(parser_args)\n            self.fail(  # pragma: no cover\n                \"The following flags didn't conflict with \"\n                '--server: {0}'.format(', '.join(conflicting_args)))\n        except errors.Error as error:\n            self.assertTrue('--server' in str(error))\n            for arg in conflicting_args:\n                self.assertTrue(arg in str(error))\n\n    def test_staging_flag(self):\n        short_args = ['--staging']\n        namespace = self.parse(short_args)\n        self.assertTrue(namespace.staging)\n        self.assertEqual(namespace.server, constants.STAGING_URI)\n\n        short_args += '--server example.com'.split()\n        self._check_server_conflict_message(short_args, '--staging')\n\n    def _assert_dry_run_flag_worked(self, namespace, existing_account):\n        self.assertTrue(namespace.dry_run)\n        self.assertTrue(namespace.break_my_certs)\n        self.assertTrue(namespace.staging)\n        self.assertEqual(namespace.server, constants.STAGING_URI)\n\n        if existing_account:\n            self.assertTrue(namespace.tos)\n            self.assertTrue(namespace.register_unsafely_without_email)\n        else:\n            self.assertFalse(namespace.tos)\n            self.assertFalse(namespace.register_unsafely_without_email)\n\n    def test_dry_run_flag(self):\n        config_dir = tempfile.mkdtemp()\n        short_args = '--dry-run --config-dir {0}'.format(config_dir).split()\n        self.assertRaises(errors.Error, self.parse, short_args)\n\n        self._assert_dry_run_flag_worked(\n            self.parse(short_args + ['auth']), False)\n        self._assert_dry_run_flag_worked(\n            self.parse(short_args + ['certonly']), False)\n        self._assert_dry_run_flag_worked(\n            self.parse(short_args + ['renew']), False)\n\n        account_dir = os.path.join(config_dir, constants.ACCOUNTS_DIR)\n        filesystem.mkdir(account_dir)\n        filesystem.mkdir(os.path.join(account_dir, 'fake_account_dir'))\n\n        self._assert_dry_run_flag_worked(self.parse(short_args + ['auth']), True)\n        self._assert_dry_run_flag_worked(self.parse(short_args + ['renew']), True)\n        self._assert_dry_run_flag_worked(self.parse(short_args + ['certonly']), True)\n\n        short_args += ['certonly']\n\n        # `--dry-run --server example.com` should emit example.com\n        self.assertEqual(self.parse(short_args + ['--server', 'example.com']).server,\n                         'example.com')\n\n        # `--dry-run --server STAGING_URI` should emit STAGING_URI\n        self.assertEqual(self.parse(short_args + ['--server', constants.STAGING_URI]).server,\n                         constants.STAGING_URI)\n\n        # `--dry-run --server LIVE` should emit STAGING_URI\n        self.assertEqual(self.parse(short_args + ['--server', cli.flag_default(\"server\")]).server,\n                         constants.STAGING_URI)\n\n        # `--dry-run --server example.com --staging` should emit an error\n        conflicts = ['--staging']\n        self._check_server_conflict_message(short_args + ['--server', 'example.com', '--staging'],\n                                            conflicts)\n\n    def test_option_was_set(self):\n        key_size_option = 'rsa_key_size'\n        key_size_value = cli.flag_default(key_size_option)\n        self.parse('--rsa-key-size {0}'.format(key_size_value).split())\n\n        self.assertTrue(cli.option_was_set(key_size_option, key_size_value))\n        self.assertTrue(cli.option_was_set('no_verify_ssl', True))\n\n        config_dir_option = 'config_dir'\n        self.assertFalse(cli.option_was_set(\n            config_dir_option, cli.flag_default(config_dir_option)))\n        self.assertFalse(cli.option_was_set(\n            'authenticator', cli.flag_default('authenticator')))\n\n    def test_encode_revocation_reason(self):\n        for reason, code in constants.REVOCATION_REASONS.items():\n            namespace = self.parse(['--reason', reason])\n            self.assertEqual(namespace.reason, code)\n        for reason, code in constants.REVOCATION_REASONS.items():\n            namespace = self.parse(['--reason', reason.upper()])\n            self.assertEqual(namespace.reason, code)\n\n    def test_force_interactive(self):\n        self.assertRaises(\n            errors.Error, self.parse, \"renew --force-interactive\".split())\n        self.assertRaises(\n            errors.Error, self.parse, \"-n --force-interactive\".split())\n\n    def test_deploy_hook_conflict(self):\n        with mock.patch(\"certbot._internal.cli.sys.stderr\"):\n            self.assertRaises(SystemExit, self.parse,\n                              \"--renew-hook foo --deploy-hook bar\".split())\n\n    def test_deploy_hook_matches_renew_hook(self):\n        value = \"foo\"\n        namespace = self.parse([\"--renew-hook\", value,\n                                \"--deploy-hook\", value,\n                                \"--disable-hook-validation\"])\n        self.assertEqual(namespace.deploy_hook, value)\n        self.assertEqual(namespace.renew_hook, value)\n\n    def test_deploy_hook_sets_renew_hook(self):\n        value = \"foo\"\n        namespace = self.parse(\n            [\"--deploy-hook\", value, \"--disable-hook-validation\"])\n        self.assertEqual(namespace.deploy_hook, value)\n        self.assertEqual(namespace.renew_hook, value)\n\n    def test_renew_hook_conflict(self):\n        with mock.patch(\"certbot._internal.cli.sys.stderr\"):\n            self.assertRaises(SystemExit, self.parse,\n                              \"--deploy-hook foo --renew-hook bar\".split())\n\n    def test_renew_hook_matches_deploy_hook(self):\n        value = \"foo\"\n        namespace = self.parse([\"--deploy-hook\", value,\n                                \"--renew-hook\", value,\n                                \"--disable-hook-validation\"])\n        self.assertEqual(namespace.deploy_hook, value)\n        self.assertEqual(namespace.renew_hook, value)\n\n    def test_renew_hook_does_not_set_renew_hook(self):\n        value = \"foo\"\n        namespace = self.parse(\n            [\"--renew-hook\", value, \"--disable-hook-validation\"])\n        self.assertEqual(namespace.deploy_hook, None)\n        self.assertEqual(namespace.renew_hook, value)\n\n    def test_max_log_backups_error(self):\n        with mock.patch('certbot._internal.cli.sys.stderr'):\n            self.assertRaises(\n                SystemExit, self.parse, \"--max-log-backups foo\".split())\n            self.assertRaises(\n                SystemExit, self.parse, \"--max-log-backups -42\".split())\n\n    def test_max_log_backups_success(self):\n        value = \"42\"\n        namespace = self.parse([\"--max-log-backups\", value])\n        self.assertEqual(namespace.max_log_backups, int(value))\n\n    def test_unchanging_defaults(self):\n        namespace = self.parse([])\n        self.assertEqual(namespace.domains, [])\n        self.assertEqual(namespace.pref_challs, [])\n\n        namespace.pref_challs = [challenges.HTTP01.typ]\n        namespace.domains = ['example.com']\n\n        namespace = self.parse([])\n        self.assertEqual(namespace.domains, [])\n        self.assertEqual(namespace.pref_challs, [])\n\n    def test_no_directory_hooks_set(self):\n        self.assertFalse(self.parse([\"--no-directory-hooks\"]).directory_hooks)\n\n    def test_no_directory_hooks_unset(self):\n        self.assertTrue(self.parse([]).directory_hooks)\n\n    def test_delete_after_revoke(self):\n        namespace = self.parse([\"--delete-after-revoke\"])\n        self.assertTrue(namespace.delete_after_revoke)\n\n    def test_delete_after_revoke_default(self):\n        namespace = self.parse([])\n        self.assertEqual(namespace.delete_after_revoke, None)\n\n    def test_no_delete_after_revoke(self):\n        namespace = self.parse([\"--no-delete-after-revoke\"])\n        self.assertFalse(namespace.delete_after_revoke)\n\n    def test_allow_subset_with_wildcard(self):\n        self.assertRaises(errors.Error, self.parse,\n                          \"--allow-subset-of-names -d *.example.org\".split())\n\n    def test_route53_no_revert(self):\n        for help_flag in ['-h', '--help']:\n            for topic in ['all', 'plugins', 'dns-route53']:\n                self.assertFalse('certbot-route53:auth' in self._help_output([help_flag, topic]))\n\n    def test_no_permissions_check_accepted(self):\n        namespace = self.parse([\"--no-permissions-check\"])\n        self.assertTrue(namespace.no_permissions_check)\n\n\nclass DefaultTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.cli._Default.\"\"\"\n\n\n    def setUp(self):\n        # pylint: disable=protected-access\n        self.default1 = cli._Default()\n        self.default2 = cli._Default()\n\n    def test_boolean(self):\n        self.assertFalse(self.default1)\n        self.assertFalse(self.default2)\n\n    def test_equality(self):\n        self.assertEqual(self.default1, self.default2)\n\n    def test_hash(self):\n        self.assertEqual(hash(self.default1), hash(self.default2))\n\n\nclass SetByCliTest(unittest.TestCase):\n    \"\"\"Tests for certbot.set_by_cli and related functions.\"\"\"\n\n\n    def setUp(self):\n        reload_module(cli)\n\n    def test_deploy_hook(self):\n        self.assertTrue(_call_set_by_cli(\n            'renew_hook', '--deploy-hook foo'.split(), 'renew'))\n\n    def test_webroot_map(self):\n        args = '-w /var/www/html -d example.com'.split()\n        verb = 'renew'\n        self.assertTrue(_call_set_by_cli('webroot_map', args, verb))\n\n    def test_report_config_interaction_str(self):\n        cli.report_config_interaction('manual_public_ip_logging_ok',\n                                      'manual_auth_hook')\n        cli.report_config_interaction('manual_auth_hook', 'manual')\n\n        self._test_report_config_interaction_common()\n\n    def test_report_config_interaction_iterable(self):\n        cli.report_config_interaction(('manual_public_ip_logging_ok',),\n                                      ('manual_auth_hook',))\n        cli.report_config_interaction(('manual_auth_hook',), ('manual',))\n\n        self._test_report_config_interaction_common()\n\n    def _test_report_config_interaction_common(self):\n        \"\"\"Tests implied interaction between manual flags.\n\n        --manual implies --manual-auth-hook which implies\n        --manual-public-ip-logging-ok. These interactions don't actually\n        exist in the client, but are used here for testing purposes.\n\n        \"\"\"\n\n        args = ['--manual']\n        verb = 'renew'\n        for v in ('manual', 'manual_auth_hook', 'manual_public_ip_logging_ok'):\n            self.assertTrue(_call_set_by_cli(v, args, verb))\n\n        # https://github.com/python/mypy/issues/2087\n        cli.set_by_cli.detector = None  # type: ignore\n\n        args = ['--manual-auth-hook', 'command']\n        for v in ('manual_auth_hook', 'manual_public_ip_logging_ok'):\n            self.assertTrue(_call_set_by_cli(v, args, verb))\n\n        self.assertFalse(_call_set_by_cli('manual', args, verb))\n\n\ndef _call_set_by_cli(var, args, verb):\n    with mock.patch('certbot._internal.cli.helpful_parser') as mock_parser:\n        with test_util.patch_get_utility():\n            mock_parser.args = args\n            mock_parser.verb = verb\n            return cli.set_by_cli(var)\n\n\nif __name__ == '__main__':\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/client_test.py",
    "content": "\"\"\"Tests for certbot._internal.client.\"\"\"\nimport platform\nimport shutil\nimport tempfile\nimport unittest\n\nfrom josepy import interfaces\nimport mock\n\nfrom certbot import errors\nfrom certbot import util\nfrom certbot._internal import account\nfrom certbot.compat import filesystem\nfrom certbot.compat import os\nimport certbot.tests.util as test_util\n\nKEY = test_util.load_vector(\"rsa512_key.pem\")\nCSR_SAN = test_util.load_vector(\"csr-san_512.pem\")\n\n# pylint: disable=line-too-long\n\nclass DetermineUserAgentTest(test_util.ConfigTestCase):\n    \"\"\"Tests for certbot._internal.client.determine_user_agent.\"\"\"\n\n    def _call(self):\n        from certbot._internal.client import determine_user_agent\n        return determine_user_agent(self.config)\n\n    @mock.patch.dict(os.environ, {\"CERTBOT_DOCS\": \"1\"})\n    def test_docs_value(self):\n        self._test(expect_doc_values=True)\n\n    @mock.patch.dict(os.environ, {})\n    def test_real_values(self):\n        self._test(expect_doc_values=False)\n\n    def _test(self, expect_doc_values):\n        ua = self._call()\n\n        if expect_doc_values:\n            doc_value_check = self.assertIn\n            real_value_check = self.assertNotIn\n        else:\n            doc_value_check = self.assertNotIn\n            real_value_check = self.assertIn\n\n        doc_value_check(\"certbot(-auto)\", ua)\n        doc_value_check(\"OS_NAME OS_VERSION\", ua)\n        doc_value_check(\"major.minor.patchlevel\", ua)\n        real_value_check(util.get_os_info_ua(), ua)\n        real_value_check(platform.python_version(), ua)\n\n\nclass RegisterTest(test_util.ConfigTestCase):\n    \"\"\"Tests for certbot._internal.client.register.\"\"\"\n\n    def setUp(self):\n        super(RegisterTest, self).setUp()\n        self.config.rsa_key_size = 1024\n        self.config.register_unsafely_without_email = False\n        self.config.email = \"alias@example.com\"\n        self.account_storage = account.AccountMemoryStorage()\n\n    def _call(self):\n        from certbot._internal.client import register\n        tos_cb = mock.MagicMock()\n        return register(self.config, self.account_storage, tos_cb)\n\n    @staticmethod\n    def _public_key_mock():\n        m = mock.Mock(__class__=interfaces.JSONDeSerializable)\n        m.to_partial_json.return_value = '{\"a\": 1}'\n        return m\n\n    @staticmethod\n    def _new_acct_dir_mock():\n        return \"/acme/new-account\"\n\n    @staticmethod\n    def _true_mock():\n        return True\n\n    @staticmethod\n    def _false_mock():\n        return False\n\n    def test_no_tos(self):\n        with mock.patch(\"certbot._internal.client.acme_client.BackwardsCompatibleClientV2\") as mock_client:\n            mock_client.new_account_and_tos().terms_of_service = \"http://tos\"\n            mock_client().external_account_required.side_effect = self._false_mock\n            with mock.patch(\"certbot._internal.eff.handle_subscription\") as mock_handle:\n                with mock.patch(\"certbot._internal.account.report_new_account\"):\n                    mock_client().new_account_and_tos.side_effect = errors.Error\n                    self.assertRaises(errors.Error, self._call)\n                    self.assertFalse(mock_handle.called)\n\n                    mock_client().new_account_and_tos.side_effect = None\n                    self._call()\n                    self.assertTrue(mock_handle.called)\n\n    def test_it(self):\n        with mock.patch(\"certbot._internal.client.acme_client.BackwardsCompatibleClientV2\") as mock_client:\n            mock_client().external_account_required.side_effect = self._false_mock\n            with mock.patch(\"certbot._internal.account.report_new_account\"):\n                with mock.patch(\"certbot._internal.eff.handle_subscription\"):\n                    self._call()\n\n    @mock.patch(\"certbot._internal.account.report_new_account\")\n    @mock.patch(\"certbot._internal.client.display_ops.get_email\")\n    def test_email_retry(self, _rep, mock_get_email):\n        from acme import messages\n        self.config.noninteractive_mode = False\n        msg = \"DNS problem: NXDOMAIN looking up MX for example.com\"\n        mx_err = messages.Error.with_code('invalidContact', detail=msg)\n        with mock.patch(\"certbot._internal.client.acme_client.BackwardsCompatibleClientV2\") as mock_client:\n            mock_client().external_account_required.side_effect = self._false_mock\n            with mock.patch(\"certbot._internal.eff.handle_subscription\") as mock_handle:\n                mock_client().new_account_and_tos.side_effect = [mx_err, mock.MagicMock()]\n                self._call()\n                self.assertEqual(mock_get_email.call_count, 1)\n                self.assertTrue(mock_handle.called)\n\n    @mock.patch(\"certbot._internal.account.report_new_account\")\n    def test_email_invalid_noninteractive(self, _rep):\n        from acme import messages\n        self.config.noninteractive_mode = True\n        msg = \"DNS problem: NXDOMAIN looking up MX for example.com\"\n        mx_err = messages.Error.with_code('invalidContact', detail=msg)\n        with mock.patch(\"certbot._internal.client.acme_client.BackwardsCompatibleClientV2\") as mock_client:\n            mock_client().external_account_required.side_effect = self._false_mock\n            with mock.patch(\"certbot._internal.eff.handle_subscription\"):\n                mock_client().new_account_and_tos.side_effect = [mx_err, mock.MagicMock()]\n                self.assertRaises(errors.Error, self._call)\n\n    def test_needs_email(self):\n        self.config.email = None\n        self.assertRaises(errors.Error, self._call)\n\n    @mock.patch(\"certbot._internal.client.logger\")\n    def test_without_email(self, mock_logger):\n        with mock.patch(\"certbot._internal.eff.handle_subscription\") as mock_handle:\n            with mock.patch(\"certbot._internal.client.acme_client.BackwardsCompatibleClientV2\") as mock_clnt:\n                mock_clnt().external_account_required.side_effect = self._false_mock\n                with mock.patch(\"certbot._internal.account.report_new_account\"):\n                    self.config.email = None\n                    self.config.register_unsafely_without_email = True\n                    self.config.dry_run = False\n                    self._call()\n                    mock_logger.info.assert_called_once_with(mock.ANY)\n                    self.assertTrue(mock_handle.called)\n\n    @mock.patch(\"certbot._internal.account.report_new_account\")\n    @mock.patch(\"certbot._internal.client.display_ops.get_email\")\n    def test_dry_run_no_staging_account(self, _rep, mock_get_email):\n        \"\"\"Tests dry-run for no staging account, expect account created with no email\"\"\"\n        with mock.patch(\"certbot._internal.client.acme_client.BackwardsCompatibleClientV2\") as mock_client:\n            mock_client().external_account_required.side_effect = self._false_mock\n            with mock.patch(\"certbot._internal.eff.handle_subscription\"):\n                with mock.patch(\"certbot._internal.account.report_new_account\"):\n                    self.config.dry_run = True\n                    self._call()\n                    # check Certbot did not ask the user to provide an email\n                    self.assertFalse(mock_get_email.called)\n                    # check Certbot created an account with no email. Contact should return empty\n                    self.assertFalse(mock_client().new_account_and_tos.call_args[0][0].contact)\n\n    def test_with_eab_arguments(self):\n        with mock.patch(\"certbot._internal.client.acme_client.BackwardsCompatibleClientV2\") as mock_client:\n            mock_client().client.directory.__getitem__ = mock.Mock(\n                side_effect=self._new_acct_dir_mock\n            )\n            mock_client().external_account_required.side_effect = self._false_mock\n            with mock.patch(\"certbot._internal.eff.handle_subscription\"):\n                target = \"certbot._internal.client.messages.ExternalAccountBinding.from_data\"\n                with mock.patch(target) as mock_eab_from_data:\n                    self.config.eab_kid = \"test-kid\"\n                    self.config.eab_hmac_key = \"J2OAqW4MHXsrHVa_PVg0Y-L_R4SYw0_aL1le6mfblbE\"\n                    self._call()\n\n                    self.assertTrue(mock_eab_from_data.called)\n\n    def test_without_eab_arguments(self):\n        with mock.patch(\"certbot._internal.client.acme_client.BackwardsCompatibleClientV2\") as mock_client:\n            mock_client().external_account_required.side_effect = self._false_mock\n            with mock.patch(\"certbot._internal.eff.handle_subscription\"):\n                target = \"certbot._internal.client.messages.ExternalAccountBinding.from_data\"\n                with mock.patch(target) as mock_eab_from_data:\n                    self.config.eab_kid = None\n                    self.config.eab_hmac_key = None\n                    self._call()\n\n                    self.assertFalse(mock_eab_from_data.called)\n\n    def test_external_account_required_without_eab_arguments(self):\n        with mock.patch(\"certbot._internal.client.acme_client.BackwardsCompatibleClientV2\") as mock_client:\n            mock_client().client.net.key.public_key = mock.Mock(side_effect=self._public_key_mock)\n            mock_client().external_account_required.side_effect = self._true_mock\n            with mock.patch(\"certbot._internal.eff.handle_subscription\"):\n                with mock.patch(\"certbot._internal.client.messages.ExternalAccountBinding.from_data\"):\n                    self.config.eab_kid = None\n                    self.config.eab_hmac_key = None\n\n                    self.assertRaises(errors.Error, self._call)\n\n    def test_unsupported_error(self):\n        from acme import messages\n        msg = \"Test\"\n        mx_err = messages.Error.with_code(\"malformed\", detail=msg, title=\"title\")\n        with mock.patch(\"certbot._internal.client.acme_client.BackwardsCompatibleClientV2\") as mock_client:\n            mock_client().client.directory.__getitem__ = mock.Mock(\n                side_effect=self._new_acct_dir_mock\n            )\n            mock_client().external_account_required.side_effect = self._false_mock\n            with mock.patch(\"certbot._internal.eff.handle_subscription\") as mock_handle:\n                mock_client().new_account_and_tos.side_effect = [mx_err, mock.MagicMock()]\n                self.assertRaises(messages.Error, self._call)\n        self.assertFalse(mock_handle.called)\n\n\nclass ClientTestCommon(test_util.ConfigTestCase):\n    \"\"\"Common base class for certbot._internal.client.Client tests.\"\"\"\n\n    def setUp(self):\n        super(ClientTestCommon, self).setUp()\n        self.config.no_verify_ssl = False\n        self.config.allow_subset_of_names = False\n\n        self.account = mock.MagicMock(**{\"key.pem\": KEY})\n\n        from certbot._internal.client import Client\n        with mock.patch(\"certbot._internal.client.acme_client.BackwardsCompatibleClientV2\") as acme:\n            self.acme_client = acme\n            self.acme = acme.return_value = mock.MagicMock()\n            self.client = Client(\n                config=self.config, account_=self.account,\n                auth=None, installer=None)\n\n\nclass ClientTest(ClientTestCommon):\n    \"\"\"Tests for certbot._internal.client.Client.\"\"\"\n\n    def setUp(self):\n        super(ClientTest, self).setUp()\n\n        self.config.allow_subset_of_names = False\n        self.config.dry_run = False\n        self.eg_domains = [\"example.com\", \"www.example.com\"]\n        self.eg_order = mock.MagicMock(\n            authorizations=[None],\n            csr_pem=mock.sentinel.csr_pem)\n\n    def test_init_acme_verify_ssl(self):\n        net = self.acme_client.call_args[0][0]\n        self.assertTrue(net.verify_ssl)\n\n    def _mock_obtain_certificate(self):\n        self.client.auth_handler = mock.MagicMock()\n        self.client.auth_handler.handle_authorizations.return_value = [None]\n        self.client.auth_handler.deactivate_valid_authorizations.return_value = ([], [])\n        self.acme.finalize_order.return_value = self.eg_order\n        self.acme.new_order.return_value = self.eg_order\n        self.eg_order.update.return_value = self.eg_order\n\n    def _check_obtain_certificate(self, auth_count=1):\n        if auth_count == 1:\n            self.client.auth_handler.handle_authorizations.assert_called_once_with(\n                self.eg_order,\n                self.config.allow_subset_of_names)\n        else:\n            self.assertEqual(self.client.auth_handler.handle_authorizations.call_count, auth_count)\n\n        self.acme.finalize_order.assert_called_once_with(\n            self.eg_order, mock.ANY)\n\n    @mock.patch(\"certbot._internal.client.crypto_util\")\n    @mock.patch(\"certbot._internal.client.logger\")\n    @test_util.patch_get_utility()\n    def test_obtain_certificate_from_csr(self, unused_mock_get_utility,\n                                         mock_logger, mock_crypto_util):\n        self._mock_obtain_certificate()\n        test_csr = util.CSR(form=\"pem\", file=None, data=CSR_SAN)\n        auth_handler = self.client.auth_handler\n        self._set_mock_from_fullchain(mock_crypto_util.cert_and_chain_from_fullchain)\n\n        orderr = self.acme.new_order(test_csr.data)\n        auth_handler.handle_authorizations(orderr, False)\n        self.assertEqual(\n            (mock.sentinel.cert, mock.sentinel.chain),\n            self.client.obtain_certificate_from_csr(\n                test_csr,\n                orderr=orderr))\n        # and that the cert was obtained correctly\n        self._check_obtain_certificate()\n\n        # Test for orderr=None\n        self.assertEqual(\n            (mock.sentinel.cert, mock.sentinel.chain),\n            self.client.obtain_certificate_from_csr(\n                test_csr,\n                orderr=None))\n        auth_handler.handle_authorizations.assert_called_with(self.eg_order, False)\n\n        # Test for no auth_handler\n        self.client.auth_handler = None\n        self.assertRaises(\n            errors.Error,\n            self.client.obtain_certificate_from_csr,\n            test_csr)\n        mock_logger.warning.assert_called_once_with(mock.ANY)\n\n    @mock.patch(\"certbot._internal.client.crypto_util\")\n    def test_obtain_certificate(self, mock_crypto_util):\n        csr = util.CSR(form=\"pem\", file=None, data=CSR_SAN)\n        mock_crypto_util.init_save_csr.return_value = csr\n        mock_crypto_util.init_save_key.return_value = mock.sentinel.key\n        self._set_mock_from_fullchain(mock_crypto_util.cert_and_chain_from_fullchain)\n\n        self._test_obtain_certificate_common(mock.sentinel.key, csr)\n\n        mock_crypto_util.init_save_key.assert_called_once_with(\n            self.config.rsa_key_size, self.config.key_dir)\n        mock_crypto_util.init_save_csr.assert_called_once_with(\n            mock.sentinel.key, self.eg_domains, self.config.csr_dir)\n        mock_crypto_util.cert_and_chain_from_fullchain.assert_called_once_with(\n            self.eg_order.fullchain_pem)\n\n    @mock.patch(\"certbot._internal.client.crypto_util\")\n    @mock.patch(\"certbot.compat.os.remove\")\n    def test_obtain_certificate_partial_success(self, mock_remove, mock_crypto_util):\n        csr = util.CSR(form=\"pem\", file=mock.sentinel.csr_file, data=CSR_SAN)\n        key = util.CSR(form=\"pem\", file=mock.sentinel.key_file, data=CSR_SAN)\n        mock_crypto_util.init_save_csr.return_value = csr\n        mock_crypto_util.init_save_key.return_value = key\n        self._set_mock_from_fullchain(mock_crypto_util.cert_and_chain_from_fullchain)\n\n        authzr = self._authzr_from_domains([\"example.com\"])\n        self.config.allow_subset_of_names = True\n        self._test_obtain_certificate_common(key, csr, authzr_ret=authzr, auth_count=2)\n\n        self.assertEqual(mock_crypto_util.init_save_key.call_count, 2)\n        self.assertEqual(mock_crypto_util.init_save_csr.call_count, 2)\n        self.assertEqual(mock_remove.call_count, 2)\n        self.assertEqual(mock_crypto_util.cert_and_chain_from_fullchain.call_count, 1)\n\n    @mock.patch(\"certbot._internal.client.crypto_util\")\n    @mock.patch(\"certbot._internal.client.acme_crypto_util\")\n    def test_obtain_certificate_dry_run(self, mock_acme_crypto, mock_crypto):\n        csr = util.CSR(form=\"pem\", file=None, data=CSR_SAN)\n        mock_acme_crypto.make_csr.return_value = CSR_SAN\n        mock_crypto.make_key.return_value = mock.sentinel.key_pem\n        key = util.Key(file=None, pem=mock.sentinel.key_pem)\n        self._set_mock_from_fullchain(mock_crypto.cert_and_chain_from_fullchain)\n\n        self.client.config.dry_run = True\n        self._test_obtain_certificate_common(key, csr)\n\n        mock_crypto.make_key.assert_called_once_with(self.config.rsa_key_size)\n        mock_acme_crypto.make_csr.assert_called_once_with(\n            mock.sentinel.key_pem, self.eg_domains, self.config.must_staple)\n        mock_crypto.init_save_key.assert_not_called()\n        mock_crypto.init_save_csr.assert_not_called()\n        self.assertEqual(mock_crypto.cert_and_chain_from_fullchain.call_count, 1)\n\n    @mock.patch(\"certbot._internal.client.logger\")\n    @mock.patch(\"certbot._internal.client.crypto_util\")\n    @mock.patch(\"certbot._internal.client.acme_crypto_util\")\n    def test_obtain_certificate_dry_run_authz_deactivations_failed(self, mock_acme_crypto,\n                                                                   mock_crypto, mock_log):\n        from acme import messages\n        csr = util.CSR(form=\"pem\", file=None, data=CSR_SAN)\n        mock_acme_crypto.make_csr.return_value = CSR_SAN\n        mock_crypto.make_key.return_value = mock.sentinel.key_pem\n        key = util.Key(file=None, pem=mock.sentinel.key_pem)\n        self._set_mock_from_fullchain(mock_crypto.cert_and_chain_from_fullchain)\n\n        self._mock_obtain_certificate()\n        self.client.config.dry_run = True\n\n        # Two authzs that are already valid and should get deactivated (dry run)\n        authzrs = self._authzr_from_domains([\"example.com\", \"www.example.com\"])\n        for authzr in authzrs:\n            authzr.body.status = messages.STATUS_VALID\n\n        # One deactivation succeeds, one fails\n        auth_handler = self.client.auth_handler\n        auth_handler.deactivate_valid_authorizations.return_value = ([authzrs[0]], [authzrs[1]])\n\n        # Certificate should get issued despite one failed deactivation\n        self.eg_order.authorizations = authzrs\n        self.client.auth_handler.handle_authorizations.return_value = authzrs\n        with test_util.patch_get_utility():\n            result = self.client.obtain_certificate(self.eg_domains)\n        self.assertEqual(result, (mock.sentinel.cert, mock.sentinel.chain, key, csr))\n        self._check_obtain_certificate(1)\n\n        # Deactivation success/failure should have been handled properly\n        self.assertEqual(auth_handler.deactivate_valid_authorizations.call_count, 1,\n                        \"Deactivate authorizations should be called\")\n        self.assertEqual(self.acme.new_order.call_count, 2,\n                        \"Order should be recreated due to successfully deactivated authorizations\")\n        mock_log.warning.assert_called_with(\"Certbot was unable to obtain fresh authorizations for\"\n                                            \" every domain. The dry run will continue, but results\"\n                                            \" may not be accurate.\")\n\n    def _set_mock_from_fullchain(self, mock_from_fullchain):\n        mock_cert = mock.Mock()\n        mock_cert.encode.return_value = mock.sentinel.cert\n        mock_chain = mock.Mock()\n        mock_chain.encode.return_value = mock.sentinel.chain\n        mock_from_fullchain.return_value = (mock_cert, mock_chain)\n\n    def _authzr_from_domains(self, domains):\n        authzr = []\n\n        # domain ordering should not be affected by authorization order\n        for domain in reversed(domains):\n            authzr.append(\n                mock.MagicMock(\n                    body=mock.MagicMock(\n                        identifier=mock.MagicMock(\n                            value=domain))))\n        return authzr\n\n    def _test_obtain_certificate_common(self, key, csr, authzr_ret=None, auth_count=1):\n        self._mock_obtain_certificate()\n\n        # return_value is essentially set to (None, None) in\n        # _mock_obtain_certificate(), which breaks this test.\n        # Thus fixed by the next line.\n        authzr = authzr_ret or self._authzr_from_domains(self.eg_domains)\n\n        self.eg_order.authorizations = authzr\n        self.client.auth_handler.handle_authorizations.return_value = authzr\n\n        with test_util.patch_get_utility():\n            result = self.client.obtain_certificate(self.eg_domains)\n\n        self.assertEqual(\n            result,\n            (mock.sentinel.cert, mock.sentinel.chain, key, csr))\n        self._check_obtain_certificate(auth_count)\n\n    @mock.patch('certbot._internal.client.Client.obtain_certificate')\n    @mock.patch('certbot._internal.storage.RenewableCert.new_lineage')\n    def test_obtain_and_enroll_certificate(self,\n                                           mock_storage, mock_obtain_certificate):\n        domains = [\"*.example.com\", \"example.com\"]\n        mock_obtain_certificate.return_value = (mock.MagicMock(),\n                                                mock.MagicMock(), mock.MagicMock(), None)\n\n        self.client.config.dry_run = False\n        self.assertTrue(self.client.obtain_and_enroll_certificate(domains, \"example_cert\"))\n\n        self.assertTrue(self.client.obtain_and_enroll_certificate(domains, None))\n        self.assertTrue(self.client.obtain_and_enroll_certificate(domains[1:], None))\n\n        self.client.config.dry_run = True\n\n        self.assertFalse(self.client.obtain_and_enroll_certificate(domains, None))\n\n        names = [call[0][0] for call in mock_storage.call_args_list]\n        self.assertEqual(names, [\"example_cert\", \"example.com\", \"example.com\"])\n\n    @mock.patch(\"certbot._internal.cli.helpful_parser\")\n    def test_save_certificate(self, mock_parser):\n        certs = [\"cert_512.pem\", \"cert-san_512.pem\"]\n        tmp_path = tempfile.mkdtemp()\n        filesystem.chmod(tmp_path, 0o755)  # TODO: really??\n\n        cert_pem = test_util.load_vector(certs[0])\n        chain_pem = (test_util.load_vector(certs[0]) + test_util.load_vector(certs[1]))\n        candidate_cert_path = os.path.join(tmp_path, \"certs\", \"cert_512.pem\")\n        candidate_chain_path = os.path.join(tmp_path, \"chains\", \"chain.pem\")\n        candidate_fullchain_path = os.path.join(tmp_path, \"chains\", \"fullchain.pem\")\n        mock_parser.verb = \"certonly\"\n        mock_parser.args = [\"--cert-path\", candidate_cert_path,\n                            \"--chain-path\", candidate_chain_path,\n                            \"--fullchain-path\", candidate_fullchain_path]\n\n        cert_path, chain_path, fullchain_path = self.client.save_certificate(\n            cert_pem, chain_pem, candidate_cert_path, candidate_chain_path,\n            candidate_fullchain_path)\n\n        self.assertEqual(os.path.dirname(cert_path),\n                         os.path.dirname(candidate_cert_path))\n        self.assertEqual(os.path.dirname(chain_path),\n                         os.path.dirname(candidate_chain_path))\n        self.assertEqual(os.path.dirname(fullchain_path),\n                         os.path.dirname(candidate_fullchain_path))\n\n        with open(cert_path, \"rb\") as cert_file:\n            cert_contents = cert_file.read()\n        self.assertEqual(cert_contents, test_util.load_vector(certs[0]))\n\n        with open(chain_path, \"rb\") as chain_file:\n            chain_contents = chain_file.read()\n        self.assertEqual(chain_contents, test_util.load_vector(certs[0]) +\n                         test_util.load_vector(certs[1]))\n\n        shutil.rmtree(tmp_path)\n\n    def test_deploy_certificate_success(self):\n        self.assertRaises(errors.Error, self.client.deploy_certificate,\n                          [\"foo.bar\"], \"key\", \"cert\", \"chain\", \"fullchain\")\n\n        installer = mock.MagicMock()\n        self.client.installer = installer\n\n        self.client.deploy_certificate(\n            [\"foo.bar\"], \"key\", \"cert\", \"chain\", \"fullchain\")\n        installer.deploy_cert.assert_called_once_with(\n            cert_path=os.path.abspath(\"cert\"),\n            chain_path=os.path.abspath(\"chain\"),\n            domain='foo.bar',\n            fullchain_path='fullchain',\n            key_path=os.path.abspath(\"key\"))\n        self.assertEqual(installer.save.call_count, 2)\n        installer.restart.assert_called_once_with()\n\n    def test_deploy_certificate_failure(self):\n        installer = mock.MagicMock()\n        self.client.installer = installer\n\n        installer.deploy_cert.side_effect = errors.PluginError\n        self.assertRaises(errors.PluginError, self.client.deploy_certificate,\n                          [\"foo.bar\"], \"key\", \"cert\", \"chain\", \"fullchain\")\n        installer.recovery_routine.assert_called_once_with()\n\n    def test_deploy_certificate_save_failure(self):\n        installer = mock.MagicMock()\n        self.client.installer = installer\n\n        installer.save.side_effect = errors.PluginError\n        self.assertRaises(errors.PluginError, self.client.deploy_certificate,\n                          [\"foo.bar\"], \"key\", \"cert\", \"chain\", \"fullchain\")\n        installer.recovery_routine.assert_called_once_with()\n\n    @test_util.patch_get_utility()\n    def test_deploy_certificate_restart_failure(self, mock_get_utility):\n        installer = mock.MagicMock()\n        installer.restart.side_effect = [errors.PluginError, None]\n        self.client.installer = installer\n\n        self.assertRaises(errors.PluginError, self.client.deploy_certificate,\n                          [\"foo.bar\"], \"key\", \"cert\", \"chain\", \"fullchain\")\n        self.assertEqual(mock_get_utility().add_message.call_count, 1)\n        installer.rollback_checkpoints.assert_called_once_with()\n        self.assertEqual(installer.restart.call_count, 2)\n\n    @test_util.patch_get_utility()\n    def test_deploy_certificate_restart_failure2(self, mock_get_utility):\n        installer = mock.MagicMock()\n        installer.restart.side_effect = errors.PluginError\n        installer.rollback_checkpoints.side_effect = errors.ReverterError\n        self.client.installer = installer\n\n        self.assertRaises(errors.PluginError, self.client.deploy_certificate,\n                          [\"foo.bar\"], \"key\", \"cert\", \"chain\", \"fullchain\")\n        self.assertEqual(mock_get_utility().add_message.call_count, 1)\n        installer.rollback_checkpoints.assert_called_once_with()\n        self.assertEqual(installer.restart.call_count, 1)\n\n\nclass EnhanceConfigTest(ClientTestCommon):\n    \"\"\"Tests for certbot._internal.client.Client.enhance_config.\"\"\"\n\n    def setUp(self):\n        super(EnhanceConfigTest, self).setUp()\n\n        self.config.hsts = False\n        self.config.redirect = False\n        self.config.staple = False\n        self.config.uir = False\n        self.domain = \"example.org\"\n\n    def test_no_installer(self):\n        self.assertRaises(\n            errors.Error, self.client.enhance_config, [self.domain], None)\n\n    @mock.patch(\"certbot._internal.client.enhancements\")\n    def test_unsupported(self, mock_enhancements):\n        self.client.installer = mock.MagicMock()\n        self.client.installer.supported_enhancements.return_value = []\n\n        self.config.redirect = None\n        self.config.hsts = True\n        with mock.patch(\"certbot._internal.client.logger\") as mock_logger:\n            self.client.enhance_config([self.domain], None)\n        self.assertEqual(mock_logger.warning.call_count, 1)\n        self.client.installer.enhance.assert_not_called()\n        mock_enhancements.ask.assert_not_called()\n\n    @mock.patch(\"certbot._internal.client.logger\")\n    def test_already_exists_header(self, mock_log):\n        self.config.hsts = True\n        self._test_with_already_existing()\n        self.assertTrue(mock_log.warning.called)\n        self.assertEqual(mock_log.warning.call_args[0][1],\n                          'Strict-Transport-Security')\n\n    @mock.patch(\"certbot._internal.client.logger\")\n    def test_already_exists_redirect(self, mock_log):\n        self.config.redirect = True\n        self._test_with_already_existing()\n        self.assertTrue(mock_log.warning.called)\n        self.assertEqual(mock_log.warning.call_args[0][1],\n                          'redirect')\n\n    @mock.patch(\"certbot._internal.client.logger\")\n    def test_config_set_no_warning_redirect(self, mock_log):\n        self.config.redirect = False\n        self._test_with_already_existing()\n        self.assertFalse(mock_log.warning.called)\n\n    @mock.patch(\"certbot._internal.client.enhancements.ask\")\n    @mock.patch(\"certbot._internal.client.logger\")\n    def test_warn_redirect(self, mock_log, mock_ask):\n        self.config.redirect = None\n        mock_ask.return_value = False\n        self._test_with_already_existing()\n        self.assertTrue(mock_log.warning.called)\n        self.assertTrue(\"disable\" in mock_log.warning.call_args[0][0])\n\n    def test_no_ask_hsts(self):\n        self.config.hsts = True\n        self._test_with_all_supported()\n        self.client.installer.enhance.assert_called_with(\n            self.domain, \"ensure-http-header\", \"Strict-Transport-Security\")\n\n    def test_no_ask_redirect(self):\n        self.config.redirect = True\n        self._test_with_all_supported()\n        self.client.installer.enhance.assert_called_with(\n            self.domain, \"redirect\", None)\n\n    def test_no_ask_staple(self):\n        self.config.staple = True\n        self._test_with_all_supported()\n        self.client.installer.enhance.assert_called_with(\n            self.domain, \"staple-ocsp\", None)\n\n    def test_no_ask_uir(self):\n        self.config.uir = True\n        self._test_with_all_supported()\n        self.client.installer.enhance.assert_called_with(\n            self.domain, \"ensure-http-header\", \"Upgrade-Insecure-Requests\")\n\n    def test_enhance_failure(self):\n        self.client.installer = mock.MagicMock()\n        self.client.installer.enhance.side_effect = errors.PluginError\n        self._test_error()\n        self.client.installer.recovery_routine.assert_called_once_with()\n\n    def test_save_failure(self):\n        self.client.installer = mock.MagicMock()\n        self.client.installer.save.side_effect = errors.PluginError\n        self._test_error()\n        self.client.installer.recovery_routine.assert_called_once_with()\n        self.client.installer.save.assert_called_once_with(mock.ANY)\n\n    def test_restart_failure(self):\n        self.client.installer = mock.MagicMock()\n        self.client.installer.restart.side_effect = [errors.PluginError, None]\n        self._test_error_with_rollback()\n\n    def test_restart_failure2(self):\n        installer = mock.MagicMock()\n        installer.restart.side_effect = errors.PluginError\n        installer.rollback_checkpoints.side_effect = errors.ReverterError\n        self.client.installer = installer\n        self._test_error_with_rollback()\n\n    @mock.patch(\"certbot._internal.client.enhancements.ask\")\n    def test_ask(self, mock_ask):\n        self.config.redirect = None\n        mock_ask.return_value = True\n        self._test_with_all_supported()\n\n    def _test_error_with_rollback(self):\n        self._test_error()\n        self.assertTrue(self.client.installer.restart.called)\n\n    def _test_error(self):\n        self.config.redirect = True\n        with test_util.patch_get_utility() as mock_gu:\n            self.assertRaises(\n                errors.PluginError, self._test_with_all_supported)\n        self.assertEqual(mock_gu().add_message.call_count, 1)\n\n    def _test_with_all_supported(self):\n        if self.client.installer is None:\n            self.client.installer = mock.MagicMock()\n        self.client.installer.supported_enhancements.return_value = [\n            \"ensure-http-header\", \"redirect\", \"staple-ocsp\"]\n        self.client.enhance_config([self.domain], None)\n        self.assertEqual(self.client.installer.save.call_count, 1)\n        self.assertEqual(self.client.installer.restart.call_count, 1)\n\n    def _test_with_already_existing(self):\n        self.client.installer = mock.MagicMock()\n        self.client.installer.supported_enhancements.return_value = [\n            \"ensure-http-header\", \"redirect\", \"staple-ocsp\"]\n        self.client.installer.enhance.side_effect = errors.PluginEnhancementAlreadyPresent()\n        self.client.enhance_config([self.domain], None)\n\n\nclass RollbackTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.client.rollback.\"\"\"\n\n    def setUp(self):\n        self.m_install = mock.MagicMock()\n\n    @classmethod\n    def _call(cls, checkpoints, side_effect):\n        from certbot._internal.client import rollback\n        with mock.patch(\"certbot._internal.client.plugin_selection.pick_installer\") as mpi:\n            mpi.side_effect = side_effect\n            rollback(None, checkpoints, {}, mock.MagicMock())\n\n    def test_no_problems(self):\n        self._call(1, self.m_install)\n        self.assertEqual(self.m_install().rollback_checkpoints.call_count, 1)\n        self.assertEqual(self.m_install().restart.call_count, 1)\n\n    def test_no_installer(self):\n        self._call(1, None)  # Just make sure no exceptions are raised\n\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/compat/__init__.py",
    "content": ""
  },
  {
    "path": "tests/compat/filesystem_test.py",
    "content": "\"\"\"Tests for certbot.compat.filesystem\"\"\"\nimport contextlib\nimport errno\nimport unittest\n\nimport mock\n\nfrom certbot import util\nfrom certbot._internal import lock\nfrom certbot.compat import filesystem\nfrom certbot.compat import os\nimport certbot.tests.util as test_util\nfrom certbot.tests.util import TempDirTestCase\n\ntry:\n    # pylint: disable=import-error\n    import win32api\n    import win32security\n    import ntsecuritycon\n    # pylint: enable=import-error\n    POSIX_MODE = False\nexcept ImportError:\n    POSIX_MODE = True\n\n\n\nEVERYBODY_SID = 'S-1-1-0'\nSYSTEM_SID = 'S-1-5-18'\nADMINS_SID = 'S-1-5-32-544'\n\n\n@unittest.skipIf(POSIX_MODE, reason='Tests specific to Windows security')\nclass WindowsChmodTests(TempDirTestCase):\n    \"\"\"Unit tests for Windows chmod function in filesystem module\"\"\"\n    def setUp(self):\n        super(WindowsChmodTests, self).setUp()\n        self.probe_path = _create_probe(self.tempdir)\n\n    def test_symlink_resolution(self):\n        link_path = os.path.join(self.tempdir, 'link')\n        os.symlink(self.probe_path, link_path)\n\n        ref_dacl_probe = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl()\n        ref_dacl_link = _get_security_dacl(link_path).GetSecurityDescriptorDacl()\n\n        filesystem.chmod(link_path, 0o700)\n\n        # Assert the real file is impacted, not the link.\n        cur_dacl_probe = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl()\n        cur_dacl_link = _get_security_dacl(link_path).GetSecurityDescriptorDacl()\n        self.assertFalse(filesystem._compare_dacls(ref_dacl_probe, cur_dacl_probe))  # pylint: disable=protected-access\n        self.assertTrue(filesystem._compare_dacls(ref_dacl_link, cur_dacl_link))  # pylint: disable=protected-access\n\n    def test_world_permission(self):\n        everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID)\n\n        filesystem.chmod(self.probe_path, 0o700)\n        dacl = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl()\n\n        self.assertFalse([dacl.GetAce(index) for index in range(0, dacl.GetAceCount())\n                          if dacl.GetAce(index)[2] == everybody])\n\n        filesystem.chmod(self.probe_path, 0o704)\n        dacl = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl()\n\n        self.assertTrue([dacl.GetAce(index) for index in range(0, dacl.GetAceCount())\n                         if dacl.GetAce(index)[2] == everybody])\n\n    def test_group_permissions_noop(self):\n        filesystem.chmod(self.probe_path, 0o700)\n        ref_dacl_probe = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl()\n\n        filesystem.chmod(self.probe_path, 0o740)\n        cur_dacl_probe = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl()\n\n        self.assertTrue(filesystem._compare_dacls(ref_dacl_probe, cur_dacl_probe))  # pylint: disable=protected-access\n\n    def test_admin_permissions(self):\n        system = win32security.ConvertStringSidToSid(SYSTEM_SID)\n        admins = win32security.ConvertStringSidToSid(ADMINS_SID)\n\n        filesystem.chmod(self.probe_path, 0o400)\n        dacl = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl()\n\n        system_aces = [dacl.GetAce(index) for index in range(0, dacl.GetAceCount())\n                       if dacl.GetAce(index)[2] == system]\n        admin_aces = [dacl.GetAce(index) for index in range(0, dacl.GetAceCount())\n                      if dacl.GetAce(index)[2] == admins]\n\n        self.assertEqual(len(system_aces), 1)\n        self.assertEqual(len(admin_aces), 1)\n\n        self.assertEqual(system_aces[0][1], ntsecuritycon.FILE_ALL_ACCESS)\n        self.assertEqual(admin_aces[0][1], ntsecuritycon.FILE_ALL_ACCESS)\n\n    def test_read_flag(self):\n        self._test_flag(4, ntsecuritycon.FILE_GENERIC_READ)\n\n    def test_execute_flag(self):\n        self._test_flag(1, ntsecuritycon.FILE_GENERIC_EXECUTE)\n\n    def test_write_flag(self):\n        self._test_flag(2, (ntsecuritycon.FILE_ALL_ACCESS\n                            ^ ntsecuritycon.FILE_GENERIC_READ\n                            ^ ntsecuritycon.FILE_GENERIC_EXECUTE))\n\n    def test_full_flag(self):\n        self._test_flag(7, ntsecuritycon.FILE_ALL_ACCESS)\n\n    def _test_flag(self, everyone_mode, windows_flag):\n        # Note that flag is tested against `everyone`, not `user`, because practically these unit\n        # tests are executed with admin privilege, so current user is effectively the admins group,\n        # and so will always have all rights.\n        filesystem.chmod(self.probe_path, 0o700 | everyone_mode)\n        dacl = _get_security_dacl(self.probe_path).GetSecurityDescriptorDacl()\n        everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID)\n\n        acls_everybody = [dacl.GetAce(index) for index in range(0, dacl.GetAceCount())\n                          if dacl.GetAce(index)[2] == everybody]\n\n        self.assertEqual(len(acls_everybody), 1)\n\n        acls_everybody = acls_everybody[0]\n\n        self.assertEqual(acls_everybody[1], windows_flag)\n\n    def test_user_admin_dacl_consistency(self):\n        # Set ownership of target to authenticated user\n        authenticated_user, _, _ = win32security.LookupAccountName(\"\", win32api.GetUserName())\n        security_owner = _get_security_owner(self.probe_path)\n        _set_owner(self.probe_path, security_owner, authenticated_user)\n\n        filesystem.chmod(self.probe_path, 0o700)\n\n        security_dacl = _get_security_dacl(self.probe_path)\n        # We expect three ACE: one for admins, one for system, and one for the user\n        self.assertEqual(security_dacl.GetSecurityDescriptorDacl().GetAceCount(), 3)\n\n        # Set ownership of target to Administrators user group\n        admin_user = win32security.ConvertStringSidToSid(ADMINS_SID)\n        security_owner = _get_security_owner(self.probe_path)\n        _set_owner(self.probe_path, security_owner, admin_user)\n\n        filesystem.chmod(self.probe_path, 0o700)\n\n        security_dacl = _get_security_dacl(self.probe_path)\n        # We expect only two ACE: one for admins, one for system,\n        # since the user is also the admins group\n        self.assertEqual(security_dacl.GetSecurityDescriptorDacl().GetAceCount(), 2)\n\n\nclass ComputePrivateKeyModeTest(TempDirTestCase):\n    def setUp(self):\n        super(ComputePrivateKeyModeTest, self).setUp()\n        self.probe_path = _create_probe(self.tempdir)\n\n    def test_compute_private_key_mode(self):\n        filesystem.chmod(self.probe_path, 0o777)\n        new_mode = filesystem.compute_private_key_mode(self.probe_path, 0o600)\n\n        if POSIX_MODE:\n            # On Linux RWX permissions for group and R permission for world\n            # are persisted from the existing moe\n            self.assertEqual(new_mode, 0o674)\n        else:\n            # On Windows no permission is persisted\n            self.assertEqual(new_mode, 0o600)\n\n\n@unittest.skipIf(POSIX_MODE, reason='Tests specific to Windows security')\nclass WindowsOpenTest(TempDirTestCase):\n    def test_new_file_correct_permissions(self):\n        path = os.path.join(self.tempdir, 'file')\n\n        desc = filesystem.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR, 0o700)\n        os.close(desc)\n\n        dacl = _get_security_dacl(path).GetSecurityDescriptorDacl()\n        everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID)\n\n        self.assertFalse([dacl.GetAce(index) for index in range(0, dacl.GetAceCount())\n                          if dacl.GetAce(index)[2] == everybody])\n\n    def test_existing_file_correct_permissions(self):\n        path = os.path.join(self.tempdir, 'file')\n        open(path, 'w').close()\n\n        desc = filesystem.open(path, os.O_EXCL | os.O_RDWR, 0o700)\n        os.close(desc)\n\n        dacl = _get_security_dacl(path).GetSecurityDescriptorDacl()\n        everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID)\n\n        self.assertFalse([dacl.GetAce(index) for index in range(0, dacl.GetAceCount())\n                          if dacl.GetAce(index)[2] == everybody])\n\n    def test_create_file_on_open(self):\n        # os.O_CREAT | os.O_EXCL + file not exists = OK\n        self._test_one_creation(1, file_exist=False, flags=(os.O_CREAT | os.O_EXCL))\n\n        # os.O_CREAT | os.O_EXCL + file exists = EEXIST OS exception\n        with self.assertRaises(OSError) as raised:\n            self._test_one_creation(2, file_exist=True, flags=(os.O_CREAT | os.O_EXCL))\n        self.assertEqual(raised.exception.errno, errno.EEXIST)\n\n        # os.O_CREAT + file not exists = OK\n        self._test_one_creation(3, file_exist=False, flags=os.O_CREAT)\n\n        # os.O_CREAT + file exists = OK\n        self._test_one_creation(4, file_exist=True, flags=os.O_CREAT)\n\n        # os.O_CREAT + file exists (locked) = EACCES OS exception\n        path = os.path.join(self.tempdir, '5')\n        open(path, 'w').close()\n        filelock = lock.LockFile(path)\n        try:\n            with self.assertRaises(OSError) as raised:\n                self._test_one_creation(5, file_exist=True, flags=os.O_CREAT)\n            self.assertEqual(raised.exception.errno, errno.EACCES)\n        finally:\n            filelock.release()\n\n        # os.O_CREAT not set + file not exists = OS exception\n        with self.assertRaises(OSError):\n            self._test_one_creation(6, file_exist=False, flags=os.O_RDONLY)\n\n    def _test_one_creation(self, num, file_exist, flags):\n        one_file = os.path.join(self.tempdir, str(num))\n        if file_exist and not os.path.exists(one_file):\n            with open(one_file, 'w'):\n                pass\n\n        handler = None\n        try:\n            handler = filesystem.open(one_file, flags)\n        finally:\n            if handler:\n                os.close(handler)\n\n\n@unittest.skipIf(POSIX_MODE, reason='Test specific to Windows security')\nclass WindowsMkdirTests(test_util.TempDirTestCase):\n    \"\"\"Unit tests for Windows mkdir + makedirs functions in filesystem module\"\"\"\n    def test_mkdir_correct_permissions(self):\n        path = os.path.join(self.tempdir, 'dir')\n\n        filesystem.mkdir(path, 0o700)\n\n        everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID)\n\n        dacl = _get_security_dacl(path).GetSecurityDescriptorDacl()\n        self.assertFalse([dacl.GetAce(index) for index in range(0, dacl.GetAceCount())\n                          if dacl.GetAce(index)[2] == everybody])\n\n    def test_makedirs_correct_permissions(self):\n        path = os.path.join(self.tempdir, 'dir')\n        subpath = os.path.join(path, 'subpath')\n\n        filesystem.makedirs(subpath, 0o700)\n\n        everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID)\n\n        dacl = _get_security_dacl(subpath).GetSecurityDescriptorDacl()\n        self.assertFalse([dacl.GetAce(index) for index in range(0, dacl.GetAceCount())\n                          if dacl.GetAce(index)[2] == everybody])\n\n    def test_makedirs_switch_os_mkdir(self):\n        path = os.path.join(self.tempdir, 'dir')\n        import os as std_os  # pylint: disable=os-module-forbidden\n        original_mkdir = std_os.mkdir\n\n        filesystem.makedirs(path)\n        self.assertEqual(original_mkdir, std_os.mkdir)\n\n        try:\n            filesystem.makedirs(path)  # Will fail because path already exists\n        except OSError:\n            pass\n        self.assertEqual(original_mkdir, std_os.mkdir)\n\n\nclass OwnershipTest(test_util.TempDirTestCase):\n    \"\"\"Tests about copy_ownership_and_apply_mode and has_same_ownership\"\"\"\n    def setUp(self):\n        super(OwnershipTest, self).setUp()\n        self.probe_path = _create_probe(self.tempdir)\n\n    @unittest.skipIf(POSIX_MODE, reason='Test specific to Windows security')\n    def test_copy_ownership_windows(self):\n        system = win32security.ConvertStringSidToSid(SYSTEM_SID)\n        security = win32security.SECURITY_ATTRIBUTES().SECURITY_DESCRIPTOR\n        security.SetSecurityDescriptorOwner(system, False)\n\n        with mock.patch('win32security.GetFileSecurity') as mock_get:\n            with mock.patch('win32security.SetFileSecurity') as mock_set:\n                mock_get.return_value = security\n                filesystem.copy_ownership_and_apply_mode(\n                    'dummy', self.probe_path, 0o700, copy_user=True, copy_group=False)\n\n        self.assertEqual(mock_set.call_count, 2)\n\n        first_call = mock_set.call_args_list[0]\n        security = first_call[0][2]\n        self.assertEqual(system, security.GetSecurityDescriptorOwner())\n\n        second_call = mock_set.call_args_list[1]\n        security = second_call[0][2]\n        dacl = security.GetSecurityDescriptorDacl()\n        everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID)\n        self.assertTrue(dacl.GetAceCount())\n        self.assertFalse([dacl.GetAce(index) for index in range(0, dacl.GetAceCount())\n                          if dacl.GetAce(index)[2] == everybody])\n\n    @unittest.skipUnless(POSIX_MODE, reason='Test specific to Linux security')\n    def test_copy_ownership_linux(self):\n        with mock.patch('os.chown') as mock_chown:\n            with mock.patch('os.chmod') as mock_chmod:\n                with mock.patch('os.stat') as mock_stat:\n                    mock_stat.return_value.st_uid = 50\n                    mock_stat.return_value.st_gid = 51\n                    filesystem.copy_ownership_and_apply_mode(\n                        'dummy', self.probe_path, 0o700, copy_user=True, copy_group=True)\n\n        mock_chown.assert_called_once_with(self.probe_path, 50, 51)\n        mock_chmod.assert_called_once_with(self.probe_path, 0o700)\n\n    def test_has_same_ownership(self):\n        path1 = os.path.join(self.tempdir, 'test1')\n        path2 = os.path.join(self.tempdir, 'test2')\n\n        util.safe_open(path1, 'w').close()\n        util.safe_open(path2, 'w').close()\n\n        self.assertTrue(filesystem.has_same_ownership(path1, path2))\n\n\nclass CheckPermissionsTest(test_util.TempDirTestCase):\n    \"\"\"Tests relative to functions that check modes.\"\"\"\n    def setUp(self):\n        super(CheckPermissionsTest, self).setUp()\n        self.probe_path = _create_probe(self.tempdir)\n\n    def test_check_mode(self):\n        self.assertTrue(filesystem.check_mode(self.probe_path, 0o744))\n\n        filesystem.chmod(self.probe_path, 0o700)\n        self.assertFalse(filesystem.check_mode(self.probe_path, 0o744))\n\n    @unittest.skipIf(POSIX_MODE, reason='Test specific to Windows security')\n    def test_check_owner_windows(self):\n        self.assertTrue(filesystem.check_owner(self.probe_path))\n\n        system = win32security.ConvertStringSidToSid(SYSTEM_SID)\n        security = win32security.SECURITY_ATTRIBUTES().SECURITY_DESCRIPTOR\n        security.SetSecurityDescriptorOwner(system, False)\n\n        with mock.patch('win32security.GetFileSecurity') as mock_get:\n            mock_get.return_value = security\n            self.assertFalse(filesystem.check_owner(self.probe_path))\n\n    @unittest.skipUnless(POSIX_MODE, reason='Test specific to Linux security')\n    def test_check_owner_linux(self):\n        self.assertTrue(filesystem.check_owner(self.probe_path))\n\n        import os as std_os  # pylint: disable=os-module-forbidden\n        # See related inline comment in certbot.compat.filesystem.check_owner method\n        # that explains why MyPy/PyLint check disable is needed here.\n        uid = std_os.getuid()\n\n        with mock.patch('os.getuid') as mock_uid:\n            mock_uid.return_value = uid + 1\n            self.assertFalse(filesystem.check_owner(self.probe_path))\n\n    def test_check_permissions(self):\n        self.assertTrue(filesystem.check_permissions(self.probe_path, 0o744))\n\n        with mock.patch('certbot.compat.filesystem.check_mode') as mock_mode:\n            mock_mode.return_value = False\n            self.assertFalse(filesystem.check_permissions(self.probe_path, 0o744))\n\n        with mock.patch('certbot.compat.filesystem.check_owner') as mock_owner:\n            mock_owner.return_value = False\n            self.assertFalse(filesystem.check_permissions(self.probe_path, 0o744))\n\n    def test_check_min_permissions(self):\n        filesystem.chmod(self.probe_path, 0o744)\n        self.assertTrue(filesystem.has_min_permissions(self.probe_path, 0o744))\n\n        filesystem.chmod(self.probe_path, 0o700)\n        self.assertFalse(filesystem.has_min_permissions(self.probe_path, 0o744))\n\n        filesystem.chmod(self.probe_path, 0o741)\n        self.assertFalse(filesystem.has_min_permissions(self.probe_path, 0o744))\n\n    def test_is_world_reachable(self):\n        filesystem.chmod(self.probe_path, 0o744)\n        self.assertTrue(filesystem.has_world_permissions(self.probe_path))\n\n        filesystem.chmod(self.probe_path, 0o700)\n        self.assertFalse(filesystem.has_world_permissions(self.probe_path))\n\n\nclass OsReplaceTest(test_util.TempDirTestCase):\n    \"\"\"Test to ensure consistent behavior of rename method\"\"\"\n    def test_os_replace_to_existing_file(self):\n        \"\"\"Ensure that replace will effectively rename src into dst for all platforms.\"\"\"\n        src = os.path.join(self.tempdir, 'src')\n        dst = os.path.join(self.tempdir, 'dst')\n        open(src, 'w').close()\n        open(dst, 'w').close()\n\n        # On Windows, a direct call to os.rename would fail because dst already exists.\n        filesystem.replace(src, dst)\n\n        self.assertFalse(os.path.exists(src))\n        self.assertTrue(os.path.exists(dst))\n\n\nclass RealpathTest(test_util.TempDirTestCase):\n    \"\"\"Tests for realpath method\"\"\"\n    def setUp(self):\n        super(RealpathTest, self).setUp()\n        self.probe_path = _create_probe(self.tempdir)\n\n    def test_symlink_resolution(self):\n        # Remove any symlinks already in probe_path\n        self.probe_path = filesystem.realpath(self.probe_path)\n        # Absolute resolution\n        link_path = os.path.join(self.tempdir, 'link_abs')\n        os.symlink(self.probe_path, link_path)\n\n        self.assertEqual(self.probe_path, filesystem.realpath(self.probe_path))\n        self.assertEqual(self.probe_path, filesystem.realpath(link_path))\n\n        # Relative resolution\n        curdir = os.getcwd()\n        link_path = os.path.join(self.tempdir, 'link_rel')\n        probe_name = os.path.basename(self.probe_path)\n        try:\n            os.chdir(os.path.dirname(self.probe_path))\n            os.symlink(probe_name, link_path)\n\n            self.assertEqual(self.probe_path, filesystem.realpath(probe_name))\n            self.assertEqual(self.probe_path, filesystem.realpath(link_path))\n        finally:\n            os.chdir(curdir)\n\n    def test_symlink_loop_mitigation(self):\n        link1_path = os.path.join(self.tempdir, 'link1')\n        link2_path = os.path.join(self.tempdir, 'link2')\n        link3_path = os.path.join(self.tempdir, 'link3')\n        os.symlink(link1_path, link2_path)\n        os.symlink(link2_path, link3_path)\n        os.symlink(link3_path, link1_path)\n\n        with self.assertRaises(RuntimeError) as error:\n            filesystem.realpath(link1_path)\n        self.assertTrue('link1 is a loop!' in str(error.exception))\n\n\nclass IsExecutableTest(test_util.TempDirTestCase):\n    \"\"\"Tests for is_executable method\"\"\"\n    def test_not_executable(self):\n        file_path = os.path.join(self.tempdir, \"foo\")\n\n        # On Windows a file created within Certbot will always have all permissions to the\n        # Administrators group set. Since the unit tests are typically executed under elevated\n        # privileges, it means that current user will always have effective execute rights on the\n        # hook script, and so the test will fail. To prevent that and represent a file created\n        # outside Certbot as typically a hook file is, we mock the _generate_dacl function in\n        # certbot.compat.filesystem to give rights only to the current user. This implies removing\n        # all ACEs except the first one from the DACL created by original _generate_dacl function.\n\n        from certbot.compat.filesystem import _generate_dacl\n\n        def _execute_mock(user_sid, mode):\n            dacl = _generate_dacl(user_sid, mode)\n            for _ in range(1, dacl.GetAceCount()):\n                dacl.DeleteAce(1)  # DeleteAce dynamically updates the internal index mapping.\n            return dacl\n\n        # create a non-executable file\n        with mock.patch(\"certbot.compat.filesystem._generate_dacl\", side_effect=_execute_mock):\n            os.close(filesystem.open(file_path, os.O_CREAT | os.O_WRONLY, 0o666))\n\n        self.assertFalse(filesystem.is_executable(file_path))\n\n    @mock.patch(\"certbot.compat.filesystem.os.path.isfile\")\n    @mock.patch(\"certbot.compat.filesystem.os.access\")\n    def test_full_path(self, mock_access, mock_isfile):\n        with _fix_windows_runtime():\n            mock_access.return_value = True\n            mock_isfile.return_value = True\n            self.assertTrue(filesystem.is_executable(\"/path/to/exe\"))\n\n    @mock.patch(\"certbot.compat.filesystem.os.path.isfile\")\n    @mock.patch(\"certbot.compat.filesystem.os.access\")\n    def test_rel_path(self, mock_access, mock_isfile):\n        with _fix_windows_runtime():\n            mock_access.return_value = True\n            mock_isfile.return_value = True\n            self.assertTrue(filesystem.is_executable(\"exe\"))\n\n    @mock.patch(\"certbot.compat.filesystem.os.path.isfile\")\n    @mock.patch(\"certbot.compat.filesystem.os.access\")\n    def test_not_found(self, mock_access, mock_isfile):\n        with _fix_windows_runtime():\n            mock_access.return_value = True\n            mock_isfile.return_value = False\n            self.assertFalse(filesystem.is_executable(\"exe\"))\n\n\n@contextlib.contextmanager\ndef _fix_windows_runtime():\n    if os.name != 'nt':\n        yield\n    else:\n        with mock.patch('win32security.GetFileSecurity') as mock_get:\n            dacl_mock = mock_get.return_value.GetSecurityDescriptorDacl\n            mode_mock = dacl_mock.return_value.GetEffectiveRightsFromAcl\n            mode_mock.return_value = ntsecuritycon.FILE_GENERIC_EXECUTE\n            yield\n\n\ndef _get_security_dacl(target):\n    return win32security.GetFileSecurity(target, win32security.DACL_SECURITY_INFORMATION)\n\n\ndef _get_security_owner(target):\n    return win32security.GetFileSecurity(target, win32security.OWNER_SECURITY_INFORMATION)\n\n\ndef _set_owner(target, security_owner, user):\n    security_owner.SetSecurityDescriptorOwner(user, False)\n    win32security.SetFileSecurity(\n        target, win32security.OWNER_SECURITY_INFORMATION, security_owner)\n\n\ndef _create_probe(tempdir):\n    filesystem.chmod(tempdir, 0o744)\n    probe_path = os.path.join(tempdir, 'probe')\n    util.safe_open(probe_path, 'w', chmod=0o744).close()\n    return probe_path\n\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/compat/os_test.py",
    "content": "\"\"\"Unit test for os module.\"\"\"\nimport unittest\n\nfrom certbot.compat import os\n\n\nclass OsTest(unittest.TestCase):\n    \"\"\"Unit tests for os module.\"\"\"\n    def test_forbidden_methods(self):\n        # Checks for os module\n        for method in ['chmod', 'chown', 'open', 'mkdir', 'makedirs', 'rename',\n                       'replace', 'access', 'stat', 'fstat']:\n            self.assertRaises(RuntimeError, getattr(os, method))\n        # Checks for os.path module\n        for method in ['realpath']:\n            self.assertRaises(RuntimeError, getattr(os.path, method))\n\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/configuration_test.py",
    "content": "\"\"\"Tests for certbot._internal.configuration.\"\"\"\nimport unittest\n\nimport mock\n\nfrom certbot import errors\nfrom certbot._internal import constants\nfrom certbot.compat import misc\nfrom certbot.compat import os\nfrom certbot.tests import util as test_util\n\n\nclass NamespaceConfigTest(test_util.ConfigTestCase):\n    \"\"\"Tests for certbot._internal.configuration.NamespaceConfig.\"\"\"\n\n    def setUp(self):\n        super(NamespaceConfigTest, self).setUp()\n        self.config.foo = 'bar' # pylint: disable=blacklisted-name\n        self.config.server = 'https://acme-server.org:443/new'\n        self.config.https_port = 1234\n        self.config.http01_port = 4321\n\n    def test_init_same_ports(self):\n        self.config.namespace.https_port = 4321\n        from certbot._internal.configuration import NamespaceConfig\n        self.assertRaises(errors.Error, NamespaceConfig, self.config.namespace)\n\n    def test_proxy_getattr(self):\n        self.assertEqual(self.config.foo, 'bar')\n        self.assertEqual(self.config.work_dir, os.path.join(self.tempdir, 'work'))\n\n    def test_server_path(self):\n        self.assertEqual(['acme-server.org:443', 'new'],\n                         self.config.server_path.split(os.path.sep))\n\n        self.config.namespace.server = ('http://user:pass@acme.server:443'\n                                 '/p/a/t/h;parameters?query#fragment')\n        self.assertEqual(['user:pass@acme.server:443', 'p', 'a', 't', 'h'],\n                         self.config.server_path.split(os.path.sep))\n\n    @mock.patch('certbot._internal.configuration.constants')\n    def test_dynamic_dirs(self, mock_constants):\n        mock_constants.ACCOUNTS_DIR = 'acc'\n        mock_constants.BACKUP_DIR = 'backups'\n        mock_constants.CSR_DIR = 'csr'\n\n        mock_constants.IN_PROGRESS_DIR = '../p'\n        mock_constants.KEY_DIR = 'keys'\n        mock_constants.TEMP_CHECKPOINT_DIR = 't'\n\n        ref_path = misc.underscores_for_unsupported_characters_in_path(\n            'acc/acme-server.org:443/new')\n        self.assertEqual(\n            os.path.normpath(self.config.accounts_dir),\n            os.path.normpath(os.path.join(self.config.config_dir, ref_path)))\n        self.assertEqual(\n            os.path.normpath(self.config.backup_dir),\n            os.path.normpath(os.path.join(self.config.work_dir, 'backups')))\n        self.assertEqual(\n            os.path.normpath(self.config.csr_dir),\n            os.path.normpath(os.path.join(self.config.config_dir, 'csr')))\n        self.assertEqual(\n            os.path.normpath(self.config.in_progress_dir),\n            os.path.normpath(os.path.join(self.config.work_dir, '../p')))\n        self.assertEqual(\n            os.path.normpath(self.config.key_dir),\n            os.path.normpath(os.path.join(self.config.config_dir, 'keys')))\n        self.assertEqual(\n            os.path.normpath(self.config.temp_checkpoint_dir),\n            os.path.normpath(os.path.join(self.config.work_dir, 't')))\n\n    def test_absolute_paths(self):\n        from certbot._internal.configuration import NamespaceConfig\n\n        config_base = \"foo\"\n        work_base = \"bar\"\n        logs_base = \"baz\"\n        server = \"mock.server\"\n\n        mock_namespace = mock.MagicMock(spec=['config_dir', 'work_dir',\n                                              'logs_dir', 'http01_port',\n                                              'https_port',\n                                              'domains', 'server'])\n        mock_namespace.config_dir = config_base\n        mock_namespace.work_dir = work_base\n        mock_namespace.logs_dir = logs_base\n        mock_namespace.server = server\n        config = NamespaceConfig(mock_namespace)\n\n        self.assertTrue(os.path.isabs(config.config_dir))\n        self.assertEqual(config.config_dir,\n                         os.path.join(os.getcwd(), config_base))\n        self.assertTrue(os.path.isabs(config.work_dir))\n        self.assertEqual(config.work_dir,\n                         os.path.join(os.getcwd(), work_base))\n        self.assertTrue(os.path.isabs(config.logs_dir))\n        self.assertEqual(config.logs_dir,\n                         os.path.join(os.getcwd(), logs_base))\n        self.assertTrue(os.path.isabs(config.accounts_dir))\n        self.assertTrue(os.path.isabs(config.backup_dir))\n        self.assertTrue(os.path.isabs(config.csr_dir))\n        self.assertTrue(os.path.isabs(config.in_progress_dir))\n        self.assertTrue(os.path.isabs(config.key_dir))\n        self.assertTrue(os.path.isabs(config.temp_checkpoint_dir))\n\n    @mock.patch('certbot._internal.configuration.constants')\n    def test_renewal_dynamic_dirs(self, mock_constants):\n        mock_constants.ARCHIVE_DIR = 'a'\n        mock_constants.LIVE_DIR = 'l'\n        mock_constants.RENEWAL_CONFIGS_DIR = 'renewal_configs'\n\n        self.assertEqual(\n                self.config.default_archive_dir, os.path.join(self.config.config_dir, 'a'))\n        self.assertEqual(\n                self.config.live_dir, os.path.join(self.config.config_dir, 'l'))\n        self.assertEqual(\n                self.config.renewal_configs_dir, os.path.join(\n                    self.config.config_dir, 'renewal_configs'))\n\n    def test_renewal_absolute_paths(self):\n        from certbot._internal.configuration import NamespaceConfig\n\n        config_base = \"foo\"\n        work_base = \"bar\"\n        logs_base = \"baz\"\n\n        mock_namespace = mock.MagicMock(spec=['config_dir', 'work_dir',\n                                              'logs_dir', 'http01_port',\n                                              'https_port',\n                                              'domains', 'server'])\n        mock_namespace.config_dir = config_base\n        mock_namespace.work_dir = work_base\n        mock_namespace.logs_dir = logs_base\n        config = NamespaceConfig(mock_namespace)\n\n        self.assertTrue(os.path.isabs(config.default_archive_dir))\n        self.assertTrue(os.path.isabs(config.live_dir))\n        self.assertTrue(os.path.isabs(config.renewal_configs_dir))\n\n    def test_get_and_set_attr(self):\n        self.config.foo = 42\n        self.assertEqual(self.config.namespace.foo, 42)\n        self.config.namespace.bar = 1337\n        self.assertEqual(self.config.bar, 1337)\n\n    def test_hook_directories(self):\n        self.assertEqual(self.config.renewal_hooks_dir,\n                         os.path.join(self.config.config_dir,\n                                      constants.RENEWAL_HOOKS_DIR))\n        self.assertEqual(self.config.renewal_pre_hooks_dir,\n                         os.path.join(self.config.renewal_hooks_dir,\n                                      constants.RENEWAL_PRE_HOOKS_DIR))\n        self.assertEqual(self.config.renewal_deploy_hooks_dir,\n                         os.path.join(self.config.renewal_hooks_dir,\n                                      constants.RENEWAL_DEPLOY_HOOKS_DIR))\n        self.assertEqual(self.config.renewal_post_hooks_dir,\n                         os.path.join(self.config.renewal_hooks_dir,\n                                      constants.RENEWAL_POST_HOOKS_DIR))\n\n\nif __name__ == '__main__':\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/crypto_util_test.py",
    "content": "\"\"\"Tests for certbot.crypto_util.\"\"\"\nimport logging\nimport unittest\n\nimport mock\nimport OpenSSL\nimport zope.component\n\nfrom certbot import errors\nfrom certbot import interfaces\nfrom certbot import util\nfrom certbot.compat import filesystem\nfrom certbot.compat import os\nimport certbot.tests.util as test_util\n\nRSA256_KEY = test_util.load_vector('rsa256_key.pem')\nRSA256_KEY_PATH = test_util.vector_path('rsa256_key.pem')\nRSA512_KEY = test_util.load_vector('rsa512_key.pem')\nRSA2048_KEY_PATH = test_util.vector_path('rsa2048_key.pem')\nCERT_PATH = test_util.vector_path('cert_512.pem')\nCERT = test_util.load_vector('cert_512.pem')\nSS_CERT_PATH = test_util.vector_path('cert_2048.pem')\nSS_CERT = test_util.load_vector('cert_2048.pem')\nP256_KEY = test_util.load_vector('nistp256_key.pem')\nP256_CERT_PATH = test_util.vector_path('cert-nosans_nistp256.pem')\nP256_CERT = test_util.load_vector('cert-nosans_nistp256.pem')\n\nclass InitSaveKeyTest(test_util.TempDirTestCase):\n    \"\"\"Tests for certbot.crypto_util.init_save_key.\"\"\"\n    def setUp(self):\n        super(InitSaveKeyTest, self).setUp()\n\n        self.workdir = os.path.join(self.tempdir, 'workdir')\n        filesystem.mkdir(self.workdir, mode=0o700)\n\n        logging.disable(logging.CRITICAL)\n        zope.component.provideUtility(\n            mock.Mock(strict_permissions=True), interfaces.IConfig)\n\n    def tearDown(self):\n        super(InitSaveKeyTest, self).tearDown()\n\n        logging.disable(logging.NOTSET)\n\n    @classmethod\n    def _call(cls, key_size, key_dir):\n        from certbot.crypto_util import init_save_key\n        return init_save_key(key_size, key_dir, 'key-certbot.pem')\n\n    @mock.patch('certbot.crypto_util.make_key')\n    def test_success(self, mock_make):\n        mock_make.return_value = b'key_pem'\n        key = self._call(1024, self.workdir)\n        self.assertEqual(key.pem, b'key_pem')\n        self.assertTrue('key-certbot.pem' in key.file)\n        self.assertTrue(os.path.exists(os.path.join(self.workdir, key.file)))\n\n    @mock.patch('certbot.crypto_util.make_key')\n    def test_key_failure(self, mock_make):\n        mock_make.side_effect = ValueError\n        self.assertRaises(ValueError, self._call, 431, self.workdir)\n\n\nclass InitSaveCSRTest(test_util.TempDirTestCase):\n    \"\"\"Tests for certbot.crypto_util.init_save_csr.\"\"\"\n\n    def setUp(self):\n        super(InitSaveCSRTest, self).setUp()\n\n        zope.component.provideUtility(\n            mock.Mock(strict_permissions=True), interfaces.IConfig)\n\n    @mock.patch('acme.crypto_util.make_csr')\n    @mock.patch('certbot.crypto_util.util.make_or_verify_dir')\n    def test_it(self, unused_mock_verify, mock_csr):\n        from certbot.crypto_util import init_save_csr\n\n        mock_csr.return_value = b'csr_pem'\n\n        csr = init_save_csr(\n            mock.Mock(pem='dummy_key'), 'example.com', self.tempdir)\n\n        self.assertEqual(csr.data, b'csr_pem')\n        self.assertTrue('csr-certbot.pem' in csr.file)\n\n\nclass ValidCSRTest(unittest.TestCase):\n    \"\"\"Tests for certbot.crypto_util.valid_csr.\"\"\"\n\n    @classmethod\n    def _call(cls, csr):\n        from certbot.crypto_util import valid_csr\n        return valid_csr(csr)\n\n    def test_valid_pem_true(self):\n        self.assertTrue(self._call(test_util.load_vector('csr_512.pem')))\n\n    def test_valid_pem_san_true(self):\n        self.assertTrue(self._call(test_util.load_vector('csr-san_512.pem')))\n\n    def test_valid_der_false(self):\n        self.assertFalse(self._call(test_util.load_vector('csr_512.der')))\n\n    def test_empty_false(self):\n        self.assertFalse(self._call(''))\n\n    def test_random_false(self):\n        self.assertFalse(self._call('foo bar'))\n\n\nclass CSRMatchesPubkeyTest(unittest.TestCase):\n    \"\"\"Tests for certbot.crypto_util.csr_matches_pubkey.\"\"\"\n\n    @classmethod\n    def _call(cls, *args, **kwargs):\n        from certbot.crypto_util import csr_matches_pubkey\n        return csr_matches_pubkey(*args, **kwargs)\n\n    def test_valid_true(self):\n        self.assertTrue(self._call(\n            test_util.load_vector('csr_512.pem'), RSA512_KEY))\n\n    def test_invalid_false(self):\n        self.assertFalse(self._call(\n            test_util.load_vector('csr_512.pem'), RSA256_KEY))\n\n\nclass ImportCSRFileTest(unittest.TestCase):\n    \"\"\"Tests for certbot.certbot_util.import_csr_file.\"\"\"\n\n    @classmethod\n    def _call(cls, *args, **kwargs):\n        from certbot.crypto_util import import_csr_file\n        return import_csr_file(*args, **kwargs)\n\n    def test_der_csr(self):\n        csrfile = test_util.vector_path('csr_512.der')\n        data = test_util.load_vector('csr_512.der')\n        data_pem = test_util.load_vector('csr_512.pem')\n\n        self.assertEqual(\n            (OpenSSL.crypto.FILETYPE_PEM,\n             util.CSR(file=csrfile,\n                      data=data_pem,\n                      form=\"pem\"),\n             [\"Example.com\"]),\n            self._call(csrfile, data))\n\n    def test_pem_csr(self):\n        csrfile = test_util.vector_path('csr_512.pem')\n        data = test_util.load_vector('csr_512.pem')\n\n        self.assertEqual(\n            (OpenSSL.crypto.FILETYPE_PEM,\n             util.CSR(file=csrfile,\n                      data=data,\n                      form=\"pem\"),\n             [\"Example.com\"],),\n            self._call(csrfile, data))\n\n    def test_bad_csr(self):\n        self.assertRaises(errors.Error, self._call,\n                          test_util.vector_path('cert_512.pem'),\n                          test_util.load_vector('cert_512.pem'))\n\n\nclass MakeKeyTest(unittest.TestCase):\n    \"\"\"Tests for certbot.crypto_util.make_key.\"\"\"\n\n    def test_it(self):  # pylint: disable=no-self-use\n        from certbot.crypto_util import make_key\n        # Do not test larger keys as it takes too long.\n        OpenSSL.crypto.load_privatekey(\n            OpenSSL.crypto.FILETYPE_PEM, make_key(1024))\n\n\nclass VerifyCertSetup(unittest.TestCase):\n    \"\"\"Refactoring for verification tests.\"\"\"\n\n    def setUp(self):\n        super(VerifyCertSetup, self).setUp()\n\n        self.renewable_cert = mock.MagicMock()\n        self.renewable_cert.cert_path = SS_CERT_PATH\n        self.renewable_cert.chain_path = SS_CERT_PATH\n        self.renewable_cert.key_path = RSA2048_KEY_PATH\n        self.renewable_cert.fullchain_path = test_util.vector_path('cert_fullchain_2048.pem')\n\n        self.bad_renewable_cert = mock.MagicMock()\n        self.bad_renewable_cert.chain_path = SS_CERT_PATH\n        self.bad_renewable_cert.cert_path = SS_CERT_PATH\n        self.bad_renewable_cert.fullchain_path = SS_CERT_PATH\n\n\nclass VerifyRenewableCertTest(VerifyCertSetup):\n    \"\"\"Tests for certbot.crypto_util.verify_renewable_cert.\"\"\"\n\n    def _call(self, renewable_cert):\n        from certbot.crypto_util import verify_renewable_cert\n        return verify_renewable_cert(renewable_cert)\n\n    def test_verify_renewable_cert(self):\n        self.assertEqual(None, self._call(self.renewable_cert))\n\n    @mock.patch('certbot.crypto_util.verify_renewable_cert_sig', side_effect=errors.Error(\"\"))\n    def test_verify_renewable_cert_failure(self, unused_verify_renewable_cert_sign):\n        self.assertRaises(errors.Error, self._call, self.bad_renewable_cert)\n\n\nclass VerifyRenewableCertSigTest(VerifyCertSetup):\n    \"\"\"Tests for certbot.crypto_util.verify_renewable_cert.\"\"\"\n\n    def _call(self, renewable_cert):\n        from certbot.crypto_util import verify_renewable_cert_sig\n        return verify_renewable_cert_sig(renewable_cert)\n\n    def test_cert_sig_match(self):\n        self.assertEqual(None, self._call(self.renewable_cert))\n\n    def test_cert_sig_match_ec(self):\n        renewable_cert = mock.MagicMock()\n        renewable_cert.cert_path = P256_CERT_PATH\n        renewable_cert.chain_path = P256_CERT_PATH\n        renewable_cert.key_path = P256_KEY\n        self.assertEqual(None, self._call(renewable_cert))\n\n    def test_cert_sig_mismatch(self):\n        self.bad_renewable_cert.cert_path = test_util.vector_path('cert_512_bad.pem')\n        self.assertRaises(errors.Error, self._call, self.bad_renewable_cert)\n\n\nclass VerifyFullchainTest(VerifyCertSetup):\n    \"\"\"Tests for certbot.crypto_util.verify_fullchain.\"\"\"\n\n    def _call(self, renewable_cert):\n        from certbot.crypto_util import verify_fullchain\n        return verify_fullchain(renewable_cert)\n\n    def test_fullchain_matches(self):\n        self.assertEqual(None, self._call(self.renewable_cert))\n\n    def test_fullchain_mismatch(self):\n        self.assertRaises(errors.Error, self._call, self.bad_renewable_cert)\n\n    def test_fullchain_ioerror(self):\n        self.bad_renewable_cert.chain = \"dog\"\n        self.assertRaises(errors.Error, self._call, self.bad_renewable_cert)\n\n\nclass VerifyCertMatchesPrivKeyTest(VerifyCertSetup):\n    \"\"\"Tests for certbot.crypto_util.verify_cert_matches_priv_key.\"\"\"\n\n    def _call(self, renewable_cert):\n        from certbot.crypto_util import verify_cert_matches_priv_key\n        return verify_cert_matches_priv_key(renewable_cert.cert, renewable_cert.privkey)\n\n    def test_cert_priv_key_match(self):\n        self.renewable_cert.cert = SS_CERT_PATH\n        self.renewable_cert.privkey = RSA2048_KEY_PATH\n        self.assertEqual(None, self._call(self.renewable_cert))\n\n    def test_cert_priv_key_mismatch(self):\n        self.bad_renewable_cert.privkey = RSA256_KEY_PATH\n        self.bad_renewable_cert.cert = SS_CERT_PATH\n\n        self.assertRaises(errors.Error, self._call, self.bad_renewable_cert)\n\n\nclass ValidPrivkeyTest(unittest.TestCase):\n    \"\"\"Tests for certbot.crypto_util.valid_privkey.\"\"\"\n\n    @classmethod\n    def _call(cls, privkey):\n        from certbot.crypto_util import valid_privkey\n        return valid_privkey(privkey)\n\n    def test_valid_true(self):\n        self.assertTrue(self._call(RSA512_KEY))\n\n    def test_empty_false(self):\n        self.assertFalse(self._call(''))\n\n    def test_random_false(self):\n        self.assertFalse(self._call('foo bar'))\n\n\nclass GetSANsFromCertTest(unittest.TestCase):\n    \"\"\"Tests for certbot.crypto_util.get_sans_from_cert.\"\"\"\n\n    @classmethod\n    def _call(cls, *args, **kwargs):\n        from certbot.crypto_util import get_sans_from_cert\n        return get_sans_from_cert(*args, **kwargs)\n\n    def test_single(self):\n        self.assertEqual([], self._call(test_util.load_vector('cert_512.pem')))\n\n    def test_san(self):\n        self.assertEqual(\n            ['example.com', 'www.example.com'],\n            self._call(test_util.load_vector('cert-san_512.pem')))\n\n\nclass GetNamesFromCertTest(unittest.TestCase):\n    \"\"\"Tests for certbot.crypto_util.get_names_from_cert.\"\"\"\n\n    @classmethod\n    def _call(cls, *args, **kwargs):\n        from certbot.crypto_util import get_names_from_cert\n        return get_names_from_cert(*args, **kwargs)\n\n    def test_single(self):\n        self.assertEqual(\n            ['example.com'],\n            self._call(test_util.load_vector('cert_512.pem')))\n\n    def test_san(self):\n        self.assertEqual(\n            ['example.com', 'www.example.com'],\n            self._call(test_util.load_vector('cert-san_512.pem')))\n\n    def test_common_name_sans_order(self):\n        # Tests that the common name comes first\n        # followed by the SANS in alphabetical order\n        self.assertEqual(\n            ['example.com'] + ['{0}.example.com'.format(c) for c in 'abcd'],\n            self._call(test_util.load_vector('cert-5sans_512.pem')))\n\n    def test_parse_non_cert(self):\n        self.assertRaises(OpenSSL.crypto.Error, self._call, \"hello there\")\n\n\nclass CertLoaderTest(unittest.TestCase):\n    \"\"\"Tests for certbot.crypto_util.pyopenssl_load_certificate\"\"\"\n\n    def test_load_valid_cert(self):\n        from certbot.crypto_util import pyopenssl_load_certificate\n\n        cert, file_type = pyopenssl_load_certificate(CERT)\n        self.assertEqual(cert.digest('sha256'),\n                         OpenSSL.crypto.load_certificate(file_type, CERT).digest('sha256'))\n\n    def test_load_invalid_cert(self):\n        from certbot.crypto_util import pyopenssl_load_certificate\n        bad_cert_data = CERT.replace(b\"BEGIN CERTIFICATE\", b\"ASDFASDFASDF!!!\")\n        self.assertRaises(\n            errors.Error, pyopenssl_load_certificate, bad_cert_data)\n\n\nclass NotBeforeTest(unittest.TestCase):\n    \"\"\"Tests for certbot.crypto_util.notBefore\"\"\"\n\n    def test_notBefore(self):\n        from certbot.crypto_util import notBefore\n        self.assertEqual(notBefore(CERT_PATH).isoformat(),\n                         '2014-12-11T22:34:45+00:00')\n\n\nclass NotAfterTest(unittest.TestCase):\n    \"\"\"Tests for certbot.crypto_util.notAfter\"\"\"\n\n    def test_notAfter(self):\n        from certbot.crypto_util import notAfter\n        self.assertEqual(notAfter(CERT_PATH).isoformat(),\n                         '2014-12-18T22:34:45+00:00')\n\n\nclass Sha256sumTest(unittest.TestCase):\n    \"\"\"Tests for certbot.crypto_util.notAfter\"\"\"\n    def test_sha256sum(self):\n        from certbot.crypto_util import sha256sum\n        self.assertEqual(sha256sum(CERT_PATH),\n            '914ffed8daf9e2c99d90ac95c77d54f32cbd556672facac380f0c063498df84e')\n\n\nclass CertAndChainFromFullchainTest(unittest.TestCase):\n    \"\"\"Tests for certbot.crypto_util.cert_and_chain_from_fullchain\"\"\"\n\n    def test_cert_and_chain_from_fullchain(self):\n        cert_pem = CERT.decode()\n        chain_pem = cert_pem + SS_CERT.decode()\n        fullchain_pem = cert_pem + chain_pem\n        spacey_fullchain_pem = cert_pem + u'\\n' + chain_pem\n        from certbot.crypto_util import cert_and_chain_from_fullchain\n        for fullchain in (fullchain_pem, spacey_fullchain_pem):\n            cert_out, chain_out = cert_and_chain_from_fullchain(fullchain)\n            self.assertEqual(cert_out, cert_pem)\n            self.assertEqual(chain_out, chain_pem)\n\n\nif __name__ == '__main__':\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/display/__init__.py",
    "content": "\"\"\"Certbot Display Tests\"\"\"\n"
  },
  {
    "path": "tests/display/completer_test.py",
    "content": "\"\"\"Test certbot._internal.display.completer.\"\"\"\ntry:\n    import readline  # pylint: disable=import-error\nexcept ImportError:\n    import certbot._internal.display.dummy_readline as readline  # type: ignore\nimport string\nimport sys\nimport unittest\n\nimport mock\nfrom six.moves import reload_module  # pylint: disable=import-error\n\nfrom acme.magic_typing import List  # pylint: disable=unused-import, no-name-in-module\nfrom certbot.compat import filesystem  # pylint: disable=ungrouped-imports\nfrom certbot.compat import os  # pylint: disable=ungrouped-imports\nimport certbot.tests.util as test_util  # pylint: disable=ungrouped-imports\n\n\nclass CompleterTest(test_util.TempDirTestCase):\n    \"\"\"Test certbot._internal.display.completer.Completer.\"\"\"\n\n    def setUp(self):\n        super(CompleterTest, self).setUp()\n\n        # directories must end with os.sep for completer to\n        # search inside the directory for possible completions\n        if self.tempdir[-1] != os.sep:\n            self.tempdir += os.sep\n\n        self.paths = []  # type: List[str]\n        # create some files and directories in temp_dir\n        for c in string.ascii_lowercase:\n            path = os.path.join(self.tempdir, c)\n            self.paths.append(path)\n            if ord(c) % 2:\n                filesystem.mkdir(path)\n            else:\n                with open(path, 'w'):\n                    pass\n\n    def test_complete(self):\n        from certbot._internal.display import completer\n        my_completer = completer.Completer()\n        num_paths = len(self.paths)\n\n        for i in range(num_paths):\n            completion = my_completer.complete(self.tempdir, i)\n            self.assertTrue(completion in self.paths)\n            self.paths.remove(completion)\n\n        self.assertFalse(self.paths)\n        completion = my_completer.complete(self.tempdir, num_paths)\n        self.assertEqual(completion, None)\n\n    @unittest.skipIf('readline' not in sys.modules,\n                     reason='Not relevant if readline is not available.')\n    def test_import_error(self):\n        original_readline = sys.modules['readline']\n        sys.modules['readline'] = None\n\n        self.test_context_manager_with_unmocked_readline()\n\n        sys.modules['readline'] = original_readline\n\n    def test_context_manager_with_unmocked_readline(self):\n        from certbot._internal.display import completer\n        reload_module(completer)\n\n        original_completer = readline.get_completer()\n        original_delims = readline.get_completer_delims()\n\n        with completer.Completer():\n            pass\n\n        self.assertEqual(readline.get_completer(), original_completer)\n        self.assertEqual(readline.get_completer_delims(), original_delims)\n\n    @mock.patch('certbot._internal.display.completer.readline', autospec=True)\n    def test_context_manager_libedit(self, mock_readline):\n        mock_readline.__doc__ = 'libedit'\n        self._test_context_manager_with_mock_readline(mock_readline)\n\n    @mock.patch('certbot._internal.display.completer.readline', autospec=True)\n    def test_context_manager_readline(self, mock_readline):\n        mock_readline.__doc__ = 'GNU readline'\n        self._test_context_manager_with_mock_readline(mock_readline)\n\n    def _test_context_manager_with_mock_readline(self, mock_readline):\n        from certbot._internal.display import completer\n\n        mock_readline.parse_and_bind.side_effect = enable_tab_completion\n\n        with completer.Completer():\n            pass\n\n        self.assertTrue(mock_readline.parse_and_bind.called)\n\n\ndef enable_tab_completion(unused_command):\n    \"\"\"Enables readline tab completion using the system specific syntax.\"\"\"\n    libedit = readline.__doc__ is not None and 'libedit' in readline.__doc__\n    command = 'bind ^I rl_complete' if libedit else 'tab: complete'\n    readline.parse_and_bind(command)\n\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/display/enhancements_test.py",
    "content": "\"\"\"Module for enhancement UI.\"\"\"\nimport logging\nimport unittest\n\nimport mock\n\nfrom certbot import errors\nfrom certbot.display import util as display_util\n\n\nclass AskTest(unittest.TestCase):\n    \"\"\"Test the ask method.\"\"\"\n    def setUp(self):\n        logging.disable(logging.CRITICAL)\n\n    def tearDown(self):\n        logging.disable(logging.NOTSET)\n\n    @classmethod\n    def _call(cls, enhancement):\n        from certbot._internal.display.enhancements import ask\n        return ask(enhancement)\n\n    @mock.patch(\"certbot._internal.display.enhancements.util\")\n    def test_redirect(self, mock_util):\n        mock_util().menu.return_value = (display_util.OK, 1)\n        self.assertTrue(self._call(\"redirect\"))\n\n    def test_key_error(self):\n        self.assertRaises(errors.Error, self._call, \"unknown_enhancement\")\n\n\nclass RedirectTest(unittest.TestCase):\n    \"\"\"Test the redirect_by_default method.\"\"\"\n    @classmethod\n    def _call(cls):\n        from certbot._internal.display.enhancements import redirect_by_default\n        return redirect_by_default()\n\n    @mock.patch(\"certbot._internal.display.enhancements.util\")\n    def test_secure(self, mock_util):\n        mock_util().menu.return_value = (display_util.OK, 1)\n        self.assertTrue(self._call())\n\n    @mock.patch(\"certbot._internal.display.enhancements.util\")\n    def test_cancel(self, mock_util):\n        mock_util().menu.return_value = (display_util.CANCEL, 1)\n        self.assertFalse(self._call())\n\n    @mock.patch(\"certbot._internal.display.enhancements.util\")\n    def test_easy(self, mock_util):\n        mock_util().menu.return_value = (display_util.OK, 0)\n        self.assertFalse(self._call())\n\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/display/ops_test.py",
    "content": "# coding=utf-8\n\"\"\"Test certbot.display.ops.\"\"\"\nimport sys\nimport unittest\n\nimport josepy as jose\nimport mock\nimport zope.component\n\nfrom acme import messages\nfrom certbot import errors\nfrom certbot._internal import account\nfrom certbot.compat import filesystem\nfrom certbot.compat import os\nfrom certbot.display import ops\nfrom certbot.display import util as display_util\nimport certbot.tests.util as test_util\n\nKEY = jose.JWKRSA.load(test_util.load_vector(\"rsa512_key.pem\"))\n\n\nclass GetEmailTest(unittest.TestCase):\n    \"\"\"Tests for certbot.display.ops.get_email.\"\"\"\n\n    @classmethod\n    def _call(cls, **kwargs):\n        from certbot.display.ops import get_email\n        return get_email(**kwargs)\n\n    @test_util.patch_get_utility(\"certbot.display.ops.z_util\")\n    def test_cancel_none(self, mock_get_utility):\n        mock_input = mock_get_utility().input\n        mock_input.return_value = (display_util.CANCEL, \"foo@bar.baz\")\n        self.assertRaises(errors.Error, self._call)\n        self.assertRaises(errors.Error, self._call, optional=False)\n\n    @test_util.patch_get_utility(\"certbot.display.ops.z_util\")\n    def test_ok_safe(self, mock_get_utility):\n        mock_input = mock_get_utility().input\n        mock_input.return_value = (display_util.OK, \"foo@bar.baz\")\n        with mock.patch(\"certbot.display.ops.util.safe_email\") as mock_safe_email:\n            mock_safe_email.return_value = True\n            self.assertTrue(self._call() == \"foo@bar.baz\")\n\n    @test_util.patch_get_utility(\"certbot.display.ops.z_util\")\n    def test_ok_not_safe(self, mock_get_utility):\n        mock_input = mock_get_utility().input\n        mock_input.return_value = (display_util.OK, \"foo@bar.baz\")\n        with mock.patch(\"certbot.display.ops.util.safe_email\") as mock_safe_email:\n            mock_safe_email.side_effect = [False, True]\n            self.assertTrue(self._call() == \"foo@bar.baz\")\n\n    @test_util.patch_get_utility(\"certbot.display.ops.z_util\")\n    def test_invalid_flag(self, mock_get_utility):\n        invalid_txt = \"There seem to be problems\"\n        mock_input = mock_get_utility().input\n        mock_input.return_value = (display_util.OK, \"foo@bar.baz\")\n        with mock.patch(\"certbot.display.ops.util.safe_email\") as mock_safe_email:\n            mock_safe_email.return_value = True\n            self._call()\n            self.assertTrue(invalid_txt not in mock_input.call_args[0][0])\n            self._call(invalid=True)\n            self.assertTrue(invalid_txt in mock_input.call_args[0][0])\n\n    @test_util.patch_get_utility(\"certbot.display.ops.z_util\")\n    def test_optional_flag(self, mock_get_utility):\n        mock_input = mock_get_utility().input\n        mock_input.return_value = (display_util.OK, \"foo@bar.baz\")\n        with mock.patch(\"certbot.display.ops.util.safe_email\") as mock_safe_email:\n            mock_safe_email.side_effect = [False, True]\n            self._call(optional=False)\n            for call in mock_input.call_args_list:\n                self.assertTrue(\n                    \"--register-unsafely-without-email\" not in call[0][0])\n\n    @test_util.patch_get_utility(\"certbot.display.ops.z_util\")\n    def test_optional_invalid_unsafe(self, mock_get_utility):\n        invalid_txt = \"There seem to be problems\"\n        mock_input = mock_get_utility().input\n        mock_input.return_value = (display_util.OK, \"foo@bar.baz\")\n        with mock.patch(\"certbot.display.ops.util.safe_email\") as mock_safe_email:\n            mock_safe_email.side_effect = [False, True]\n            self._call(invalid=True)\n            self.assertTrue(invalid_txt in mock_input.call_args[0][0])\n\n\nclass ChooseAccountTest(test_util.TempDirTestCase):\n    \"\"\"Tests for certbot.display.ops.choose_account.\"\"\"\n    def setUp(self):\n        super(ChooseAccountTest, self).setUp()\n\n        zope.component.provideUtility(display_util.FileDisplay(sys.stdout,\n                                                               False))\n\n        self.account_keys_dir = os.path.join(self.tempdir, \"keys\")\n        filesystem.makedirs(self.account_keys_dir, 0o700)\n\n        self.config = mock.MagicMock(\n            accounts_dir=self.tempdir,\n            account_keys_dir=self.account_keys_dir,\n            server=\"certbot-demo.org\")\n        self.key = KEY\n\n        self.acc1 = account.Account(messages.RegistrationResource(\n            uri=None, body=messages.Registration.from_data(\n                email=\"email1@g.com\")), self.key)\n        self.acc2 = account.Account(messages.RegistrationResource(\n            uri=None, body=messages.Registration.from_data(\n                email=\"email2@g.com\", phone=\"phone\")), self.key)\n\n    @classmethod\n    def _call(cls, accounts):\n        return ops.choose_account(accounts)\n\n    @test_util.patch_get_utility(\"certbot.display.ops.z_util\")\n    def test_one(self, mock_util):\n        mock_util().menu.return_value = (display_util.OK, 0)\n        self.assertEqual(self._call([self.acc1]), self.acc1)\n\n    @test_util.patch_get_utility(\"certbot.display.ops.z_util\")\n    def test_two(self, mock_util):\n        mock_util().menu.return_value = (display_util.OK, 1)\n        self.assertEqual(self._call([self.acc1, self.acc2]), self.acc2)\n\n    @test_util.patch_get_utility(\"certbot.display.ops.z_util\")\n    def test_cancel(self, mock_util):\n        mock_util().menu.return_value = (display_util.CANCEL, 1)\n        self.assertTrue(self._call([self.acc1, self.acc2]) is None)\n\n\nclass GenSSLLabURLs(unittest.TestCase):\n    \"\"\"Loose test of _gen_ssl_lab_urls. URL can change easily in the future.\"\"\"\n    def setUp(self):\n        zope.component.provideUtility(display_util.FileDisplay(sys.stdout,\n                                                               False))\n\n    @classmethod\n    def _call(cls, domains):\n        from certbot.display.ops import _gen_ssl_lab_urls\n        return _gen_ssl_lab_urls(domains)\n\n    def test_zero(self):\n        self.assertEqual(self._call([]), [])\n\n    def test_two(self):\n        urls = self._call([\"eff.org\", \"umich.edu\"])\n        self.assertTrue(\"eff.org\" in urls[0])\n        self.assertTrue(\"umich.edu\" in urls[1])\n\n\nclass GenHttpsNamesTest(unittest.TestCase):\n    \"\"\"Test _gen_https_names.\"\"\"\n    def setUp(self):\n        zope.component.provideUtility(display_util.FileDisplay(sys.stdout,\n                                                               False))\n\n    @classmethod\n    def _call(cls, domains):\n        from certbot.display.ops import _gen_https_names\n        return _gen_https_names(domains)\n\n    def test_zero(self):\n        self.assertEqual(self._call([]), \"\")\n\n    def test_one(self):\n        doms = [\n            \"example.com\",\n            \"asllkjsadfljasdf.c\",\n        ]\n        for dom in doms:\n            self.assertEqual(self._call([dom]), \"https://%s\" % dom)\n\n    def test_two(self):\n        domains_list = [\n            [\"foo.bar.org\", \"bar.org\"],\n            [\"paypal.google.facebook.live.com\", \"*.zombo.example.com\"],\n        ]\n        for doms in domains_list:\n            self.assertEqual(\n                self._call(doms),\n                \"https://{dom[0]} and https://{dom[1]}\".format(dom=doms))\n\n    def test_three(self):\n        doms = [\"a.org\", \"b.org\", \"c.org\"]\n        # We use an oxford comma\n        self.assertEqual(\n            self._call(doms),\n            \"https://{dom[0]}, https://{dom[1]}, and https://{dom[2]}\".format(\n                dom=doms))\n\n    def test_four(self):\n        doms = [\"a.org\", \"b.org\", \"c.org\", \"d.org\"]\n        exp = (\"https://{dom[0]}, https://{dom[1]}, https://{dom[2]}, \"\n               \"and https://{dom[3]}\".format(dom=doms))\n\n        self.assertEqual(self._call(doms), exp)\n\n\nclass ChooseNamesTest(unittest.TestCase):\n    \"\"\"Test choose names.\"\"\"\n    def setUp(self):\n        zope.component.provideUtility(display_util.FileDisplay(sys.stdout,\n                                                               False))\n        self.mock_install = mock.MagicMock()\n\n    @classmethod\n    def _call(cls, installer, question=None):\n        from certbot.display.ops import choose_names\n        return choose_names(installer, question)\n\n    @mock.patch(\"certbot.display.ops._choose_names_manually\")\n    def test_no_installer(self, mock_manual):\n        self._call(None)\n        self.assertEqual(mock_manual.call_count, 1)\n\n    @test_util.patch_get_utility(\"certbot.display.ops.z_util\")\n    def test_no_installer_cancel(self, mock_util):\n        mock_util().input.return_value = (display_util.CANCEL, [])\n        self.assertEqual(self._call(None), [])\n\n    @test_util.patch_get_utility(\"certbot.display.ops.z_util\")\n    def test_no_names_choose(self, mock_util):\n        self.mock_install().get_all_names.return_value = set()\n        domain = \"example.com\"\n        mock_util().input.return_value = (display_util.OK, domain)\n\n        actual_doms = self._call(self.mock_install)\n        self.assertEqual(mock_util().input.call_count, 1)\n        self.assertEqual(actual_doms, [domain])\n        self.assertTrue(\n            \"configuration files\" in mock_util().input.call_args[0][0])\n\n    def test_sort_names_trivial(self):\n        from certbot.display.ops import _sort_names\n\n        #sort an empty list\n        self.assertEqual(_sort_names([]), [])\n\n        #sort simple domains\n        some_domains = [\"ex.com\", \"zx.com\", \"ax.com\"]\n        self.assertEqual(_sort_names(some_domains), [\"ax.com\", \"ex.com\", \"zx.com\"])\n\n        #Sort subdomains of a single domain\n        domain = \".ex.com\"\n        unsorted_short = [\"e\", \"a\", \"z\", \"y\"]\n        unsorted_long = [us + domain for us in unsorted_short]\n\n        sorted_short = sorted(unsorted_short)\n        sorted_long = [us + domain for us in sorted_short]\n\n        self.assertEqual(_sort_names(unsorted_long), sorted_long)\n\n    def test_sort_names_many(self):\n        from certbot.display.ops import _sort_names\n\n        unsorted_domains = [\".cx.com\", \".bx.com\", \".ax.com\", \".dx.com\"]\n        unsorted_short = [\"www\", \"bnother.long.subdomain\", \"a\", \"a.long.subdomain\", \"z\", \"b\"]\n        #Of course sorted doesn't work here ;-)\n        sorted_short = [\"a\", \"b\", \"a.long.subdomain\", \"bnother.long.subdomain\", \"www\", \"z\"]\n\n        to_sort = []\n        for short in unsorted_short:\n            for domain in unsorted_domains:\n                to_sort.append(short+domain)\n        sortd = []\n        for domain in sorted(unsorted_domains):\n            for short in sorted_short:\n                sortd.append(short+domain)\n        self.assertEqual(_sort_names(to_sort), sortd)\n\n\n    @test_util.patch_get_utility(\"certbot.display.ops.z_util\")\n    def test_filter_names_valid_return(self, mock_util):\n        self.mock_install.get_all_names.return_value = set([\"example.com\"])\n        mock_util().checklist.return_value = (display_util.OK, [\"example.com\"])\n\n        names = self._call(self.mock_install)\n        self.assertEqual(names, [\"example.com\"])\n        self.assertEqual(mock_util().checklist.call_count, 1)\n\n    @test_util.patch_get_utility(\"certbot.display.ops.z_util\")\n    def test_filter_namees_override_question(self, mock_util):\n        self.mock_install.get_all_names.return_value = set([\"example.com\"])\n        mock_util().checklist.return_value = (display_util.OK, [\"example.com\"])\n        names = self._call(self.mock_install, \"Custom\")\n        self.assertEqual(names, [\"example.com\"])\n        self.assertEqual(mock_util().checklist.call_count, 1)\n        self.assertEqual(mock_util().checklist.call_args[0][0], \"Custom\")\n\n    @test_util.patch_get_utility(\"certbot.display.ops.z_util\")\n    def test_filter_names_nothing_selected(self, mock_util):\n        self.mock_install.get_all_names.return_value = set([\"example.com\"])\n        mock_util().checklist.return_value = (display_util.OK, [])\n\n        self.assertEqual(self._call(self.mock_install), [])\n\n    @test_util.patch_get_utility(\"certbot.display.ops.z_util\")\n    def test_filter_names_cancel(self, mock_util):\n        self.mock_install.get_all_names.return_value = set([\"example.com\"])\n        mock_util().checklist.return_value = (\n            display_util.CANCEL, [\"example.com\"])\n\n        self.assertEqual(self._call(self.mock_install), [])\n\n    def test_get_valid_domains(self):\n        from certbot.display.ops import get_valid_domains\n        all_valid = [\"example.com\", \"second.example.com\",\n                     \"also.example.com\", \"under_score.example.com\",\n                     \"justtld\", \"*.wildcard.com\"]\n        all_invalid = [\"öóòps.net\", \"uniçodé.com\"]\n        two_valid = [\"example.com\", \"úniçøde.com\", \"also.example.com\"]\n        self.assertEqual(get_valid_domains(all_valid), all_valid)\n        self.assertEqual(get_valid_domains(all_invalid), [])\n        self.assertEqual(len(get_valid_domains(two_valid)), 2)\n\n    @test_util.patch_get_utility(\"certbot.display.ops.z_util\")\n    def test_choose_manually(self, mock_util):\n        from certbot.display.ops import _choose_names_manually\n        utility_mock = mock_util()\n        # No retry\n        utility_mock.yesno.return_value = False\n        # IDN and no retry\n        utility_mock.input.return_value = (display_util.OK,\n                                          \"uniçodé.com\")\n        self.assertEqual(_choose_names_manually(), [])\n        # IDN exception with previous mocks\n        with mock.patch(\n                \"certbot.display.ops.display_util.separate_list_input\"\n        ) as mock_sli:\n            unicode_error = UnicodeEncodeError('mock', u'', 0, 1, 'mock')\n            mock_sli.side_effect = unicode_error\n            self.assertEqual(_choose_names_manually(), [])\n        # Valid domains\n        utility_mock.input.return_value = (display_util.OK,\n                                          (\"example.com,\"\n                                           \"under_score.example.com,\"\n                                           \"justtld,\"\n                                           \"valid.example.com\"))\n        self.assertEqual(_choose_names_manually(),\n                         [\"example.com\", \"under_score.example.com\",\n                          \"justtld\", \"valid.example.com\"])\n\n    @test_util.patch_get_utility(\"certbot.display.ops.z_util\")\n    def test_choose_manually_retry(self, mock_util):\n        from certbot.display.ops import _choose_names_manually\n        utility_mock = mock_util()\n        # Three iterations\n        utility_mock.input.return_value = (display_util.OK,\n                                          \"uniçodé.com\")\n        utility_mock.yesno.side_effect = [True, True, False]\n        _choose_names_manually()\n        self.assertEqual(utility_mock.yesno.call_count, 3)\n\n\nclass SuccessInstallationTest(unittest.TestCase):\n    \"\"\"Test the success installation message.\"\"\"\n    @classmethod\n    def _call(cls, names):\n        from certbot.display.ops import success_installation\n        success_installation(names)\n\n    @test_util.patch_get_utility(\"certbot.display.ops.z_util\")\n    def test_success_installation(self, mock_util):\n        mock_util().notification.return_value = None\n        names = [\"example.com\", \"abc.com\"]\n\n        self._call(names)\n\n        self.assertEqual(mock_util().notification.call_count, 1)\n        arg = mock_util().notification.call_args_list[0][0][0]\n\n        for name in names:\n            self.assertTrue(name in arg)\n\n\nclass SuccessRenewalTest(unittest.TestCase):\n    \"\"\"Test the success renewal message.\"\"\"\n    @classmethod\n    def _call(cls, names):\n        from certbot.display.ops import success_renewal\n        success_renewal(names)\n\n    @test_util.patch_get_utility(\"certbot.display.ops.z_util\")\n    def test_success_renewal(self, mock_util):\n        mock_util().notification.return_value = None\n        names = [\"example.com\", \"abc.com\"]\n\n        self._call(names)\n\n        self.assertEqual(mock_util().notification.call_count, 1)\n        arg = mock_util().notification.call_args_list[0][0][0]\n\n        for name in names:\n            self.assertTrue(name in arg)\n\nclass SuccessRevocationTest(unittest.TestCase):\n    \"\"\"Test the success revocation message.\"\"\"\n    @classmethod\n    def _call(cls, path):\n        from certbot.display.ops import success_revocation\n        success_revocation(path)\n\n    @test_util.patch_get_utility(\"certbot.display.ops.z_util\")\n    def test_success_revocation(self, mock_util):\n        mock_util().notification.return_value = None\n        path = \"/path/to/cert.pem\"\n        self._call(path)\n        mock_util().notification.assert_called_once_with(\n            \"Congratulations! You have successfully revoked the certificate \"\n            \"that was located at {0}{1}{1}\".format(\n                path,\n                os.linesep), pause=False)\n        self.assertTrue(path in mock_util().notification.call_args[0][0])\n\n\nclass ValidatorTests(unittest.TestCase):\n    \"\"\"Tests for `validated_input` and `validated_directory`.\"\"\"\n\n    __ERROR = \"Must be non-empty\"\n\n    valid_input = \"asdf\"\n    valid_directory = \"/var/www/html\"\n\n    @staticmethod\n    def __validator(m):\n        if m == \"\":\n            raise errors.PluginError(ValidatorTests.__ERROR)\n\n    @test_util.patch_get_utility()\n    def test_input_blank_with_validator(self, mock_util):\n        mock_util().input.side_effect = [(display_util.OK, \"\"),\n                                         (display_util.OK, \"\"),\n                                         (display_util.OK, \"\"),\n                                         (display_util.OK, self.valid_input)]\n\n        returned = ops.validated_input(self.__validator, \"message\", force_interactive=True)\n        self.assertEqual(ValidatorTests.__ERROR, mock_util().notification.call_args[0][0])\n        self.assertEqual(returned, (display_util.OK, self.valid_input))\n\n    @test_util.patch_get_utility()\n    def test_input_validation_with_default(self, mock_util):\n        mock_util().input.side_effect = [(display_util.OK, self.valid_input)]\n\n        returned = ops.validated_input(self.__validator, \"msg\", default=\"other\")\n        self.assertEqual(returned, (display_util.OK, self.valid_input))\n\n    @test_util.patch_get_utility()\n    def test_input_validation_with_bad_default(self, mock_util):\n        mock_util().input.side_effect = [(display_util.OK, self.valid_input)]\n\n        self.assertRaises(AssertionError,\n                          ops.validated_input,\n                          self.__validator, \"msg\", default=\"\")\n\n    @test_util.patch_get_utility()\n    def test_input_cancel_with_validator(self, mock_util):\n        mock_util().input.side_effect = [(display_util.CANCEL, \"\")]\n\n        code, unused_raw = ops.validated_input(self.__validator, \"message\", force_interactive=True)\n        self.assertEqual(code, display_util.CANCEL)\n\n    @test_util.patch_get_utility()\n    def test_directory_select_validation(self, mock_util):\n        mock_util().directory_select.side_effect = [(display_util.OK, \"\"),\n                                                    (display_util.OK, self.valid_directory)]\n\n        returned = ops.validated_directory(self.__validator, \"msg\", force_interactive=True)\n        self.assertEqual(ValidatorTests.__ERROR, mock_util().notification.call_args[0][0])\n        self.assertEqual(returned, (display_util.OK, self.valid_directory))\n\n    @test_util.patch_get_utility()\n    def test_directory_select_validation_with_default(self, mock_util):\n        mock_util().directory_select.side_effect = [(display_util.OK, self.valid_directory)]\n\n        returned = ops.validated_directory(self.__validator, \"msg\", default=\"other\")\n        self.assertEqual(returned, (display_util.OK, self.valid_directory))\n\n    @test_util.patch_get_utility()\n    def test_directory_select_validation_with_bad_default(self, mock_util):\n        mock_util().directory_select.side_effect = [(display_util.OK, self.valid_directory)]\n\n        self.assertRaises(AssertionError,\n                          ops.validated_directory,\n                          self.__validator, \"msg\", default=\"\")\n\n\nclass ChooseValuesTest(unittest.TestCase):\n    \"\"\"Test choose_values.\"\"\"\n    @classmethod\n    def _call(cls, values, question):\n        from certbot.display.ops import choose_values\n        return choose_values(values, question)\n\n    @test_util.patch_get_utility(\"certbot.display.ops.z_util\")\n    def test_choose_names_success(self, mock_util):\n        items = [\"first\", \"second\", \"third\"]\n        mock_util().checklist.return_value = (display_util.OK, [items[2]])\n        result = self._call(items, None)\n        self.assertEqual(result, [items[2]])\n        self.assertTrue(mock_util().checklist.called)\n        self.assertEqual(mock_util().checklist.call_args[0][0], None)\n\n    @test_util.patch_get_utility(\"certbot.display.ops.z_util\")\n    def test_choose_names_success_question(self, mock_util):\n        items = [\"first\", \"second\", \"third\"]\n        question = \"Which one?\"\n        mock_util().checklist.return_value = (display_util.OK, [items[1]])\n        result = self._call(items, question)\n        self.assertEqual(result, [items[1]])\n        self.assertTrue(mock_util().checklist.called)\n        self.assertEqual(mock_util().checklist.call_args[0][0], question)\n\n    @test_util.patch_get_utility(\"certbot.display.ops.z_util\")\n    def test_choose_names_user_cancel(self, mock_util):\n        items = [\"first\", \"second\", \"third\"]\n        question = \"Want to cancel?\"\n        mock_util().checklist.return_value = (display_util.CANCEL, [])\n        result = self._call(items, question)\n        self.assertEqual(result, [])\n        self.assertTrue(mock_util().checklist.called)\n        self.assertEqual(mock_util().checklist.call_args[0][0], question)\n\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/display/util_test.py",
    "content": "\"\"\"Test :mod:`certbot.display.util`.\"\"\"\nimport inspect\nimport socket\nimport tempfile\nimport unittest\n\nimport mock\nimport six\n\nfrom certbot import errors\nfrom certbot import interfaces\nfrom certbot.display import util as display_util\n\nCHOICES = [(\"First\", \"Description1\"), (\"Second\", \"Description2\")]\nTAGS = [\"tag1\", \"tag2\", \"tag3\"]\nTAGS_CHOICES = [(\"1\", \"tag1\"), (\"2\", \"tag2\"), (\"3\", \"tag3\")]\n\n\nclass InputWithTimeoutTest(unittest.TestCase):\n    \"\"\"Tests for certbot.display.util.input_with_timeout.\"\"\"\n    @classmethod\n    def _call(cls, *args, **kwargs):\n        from certbot.display.util import input_with_timeout\n        return input_with_timeout(*args, **kwargs)\n\n    def test_eof(self):\n        with tempfile.TemporaryFile(\"r+\") as f:\n            with mock.patch(\"certbot.display.util.sys.stdin\", new=f):\n                self.assertRaises(EOFError, self._call)\n\n    def test_input(self, prompt=None):\n        expected = \"foo bar\"\n        stdin = six.StringIO(expected + \"\\n\")\n        with mock.patch(\"certbot.compat.misc.select.select\") as mock_select:\n            mock_select.return_value = ([stdin], [], [],)\n            self.assertEqual(self._call(prompt), expected)\n\n    @mock.patch(\"certbot.display.util.sys.stdout\")\n    def test_input_with_prompt(self, mock_stdout):\n        prompt = \"test prompt: \"\n        self.test_input(prompt)\n        mock_stdout.write.assert_called_once_with(prompt)\n        mock_stdout.flush.assert_called_once_with()\n\n    def test_timeout(self):\n        stdin = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        stdin.bind(('', 0))\n        stdin.listen(1)\n        with mock.patch(\"certbot.display.util.sys.stdin\", stdin):\n            self.assertRaises(errors.Error, self._call, timeout=0.001)\n        stdin.close()\n\n\nclass FileOutputDisplayTest(unittest.TestCase):\n    \"\"\"Test stdout display.\n\n    Most of this class has to deal with visual output.  In order to test how the\n    functions look to a user, uncomment the test_visual function.\n\n    \"\"\"\n    def setUp(self):\n        super(FileOutputDisplayTest, self).setUp()\n        self.mock_stdout = mock.MagicMock()\n        self.displayer = display_util.FileDisplay(self.mock_stdout, False)\n\n    def test_notification_no_pause(self):\n        self.displayer.notification(\"message\", False)\n        string = self.mock_stdout.write.call_args[0][0]\n\n        self.assertTrue(\"message\" in string)\n\n    def test_notification_pause(self):\n        input_with_timeout = \"certbot.display.util.input_with_timeout\"\n        with mock.patch(input_with_timeout, return_value=\"enter\"):\n            self.displayer.notification(\"message\", force_interactive=True)\n\n        self.assertTrue(\"message\" in self.mock_stdout.write.call_args[0][0])\n\n    def test_notification_noninteractive(self):\n        self._force_noninteractive(self.displayer.notification, \"message\")\n        string = self.mock_stdout.write.call_args[0][0]\n        self.assertTrue(\"message\" in string)\n\n    def test_notification_noninteractive2(self):\n        # The main purpose of this test is to make sure we only call\n        # logger.warning once which _force_noninteractive checks internally\n        self._force_noninteractive(self.displayer.notification, \"message\")\n        string = self.mock_stdout.write.call_args[0][0]\n        self.assertTrue(\"message\" in string)\n\n        self.assertTrue(self.displayer.skipped_interaction)\n\n        self._force_noninteractive(self.displayer.notification, \"message2\")\n        string = self.mock_stdout.write.call_args[0][0]\n        self.assertTrue(\"message2\" in string)\n\n    @mock.patch(\"certbot.display.util.\"\n                \"FileDisplay._get_valid_int_ans\")\n    def test_menu(self, mock_ans):\n        mock_ans.return_value = (display_util.OK, 1)\n        ret = self.displayer.menu(\"message\", CHOICES, force_interactive=True)\n        self.assertEqual(ret, (display_util.OK, 0))\n\n    def test_menu_noninteractive(self):\n        default = 0\n        result = self._force_noninteractive(\n            self.displayer.menu, \"msg\", CHOICES, default=default)\n        self.assertEqual(result, (display_util.OK, default))\n\n    def test_input_cancel(self):\n        input_with_timeout = \"certbot.display.util.input_with_timeout\"\n        with mock.patch(input_with_timeout, return_value=\"c\"):\n            code, _ = self.displayer.input(\"message\", force_interactive=True)\n\n        self.assertTrue(code, display_util.CANCEL)\n\n    def test_input_normal(self):\n        input_with_timeout = \"certbot.display.util.input_with_timeout\"\n        with mock.patch(input_with_timeout, return_value=\"domain.com\"):\n            code, input_ = self.displayer.input(\"message\", force_interactive=True)\n\n        self.assertEqual(code, display_util.OK)\n        self.assertEqual(input_, \"domain.com\")\n\n    def test_input_noninteractive(self):\n        default = \"foo\"\n        code, input_ = self._force_noninteractive(\n            self.displayer.input, \"message\", default=default)\n\n        self.assertEqual(code, display_util.OK)\n        self.assertEqual(input_, default)\n\n    def test_input_assertion_fail(self):\n        # If the call to util.assert_valid_call is commented out, an\n        # error.Error is raised, otherwise, an AssertionError is raised.\n        self.assertRaises(Exception, self._force_noninteractive,\n                          self.displayer.input, \"message\", cli_flag=\"--flag\")\n\n    def test_input_assertion_fail2(self):\n        with mock.patch(\"certbot.display.util.assert_valid_call\"):\n            self.assertRaises(errors.Error, self._force_noninteractive,\n                              self.displayer.input, \"msg\", cli_flag=\"--flag\")\n\n    def test_yesno(self):\n        input_with_timeout = \"certbot.display.util.input_with_timeout\"\n        with mock.patch(input_with_timeout, return_value=\"Yes\"):\n            self.assertTrue(self.displayer.yesno(\n                \"message\", force_interactive=True))\n        with mock.patch(input_with_timeout, return_value=\"y\"):\n            self.assertTrue(self.displayer.yesno(\n                \"message\", force_interactive=True))\n        with mock.patch(input_with_timeout, side_effect=[\"maybe\", \"y\"]):\n            self.assertTrue(self.displayer.yesno(\n                \"message\", force_interactive=True))\n        with mock.patch(input_with_timeout, return_value=\"No\"):\n            self.assertFalse(self.displayer.yesno(\n                \"message\", force_interactive=True))\n        with mock.patch(input_with_timeout, side_effect=[\"cancel\", \"n\"]):\n            self.assertFalse(self.displayer.yesno(\n                \"message\", force_interactive=True))\n\n        with mock.patch(input_with_timeout, return_value=\"a\"):\n            self.assertTrue(self.displayer.yesno(\n                \"msg\", yes_label=\"Agree\", force_interactive=True))\n\n    def test_yesno_noninteractive(self):\n        self.assertTrue(self._force_noninteractive(\n            self.displayer.yesno, \"message\", default=True))\n\n    @mock.patch(\"certbot.display.util.input_with_timeout\")\n    def test_checklist_valid(self, mock_input):\n        mock_input.return_value = \"2 1\"\n        code, tag_list = self.displayer.checklist(\n            \"msg\", TAGS, force_interactive=True)\n        self.assertEqual(\n            (code, set(tag_list)), (display_util.OK, set([\"tag1\", \"tag2\"])))\n\n    @mock.patch(\"certbot.display.util.input_with_timeout\")\n    def test_checklist_empty(self, mock_input):\n        mock_input.return_value = \"\"\n        code, tag_list = self.displayer.checklist(\"msg\", TAGS, force_interactive=True)\n        self.assertEqual(\n            (code, set(tag_list)), (display_util.OK, set([\"tag1\", \"tag2\", \"tag3\"])))\n\n    @mock.patch(\"certbot.display.util.input_with_timeout\")\n    def test_checklist_miss_valid(self, mock_input):\n        mock_input.side_effect = [\"10\", \"tag1 please\", \"1\"]\n\n        ret = self.displayer.checklist(\"msg\", TAGS, force_interactive=True)\n        self.assertEqual(ret, (display_util.OK, [\"tag1\"]))\n\n    @mock.patch(\"certbot.display.util.input_with_timeout\")\n    def test_checklist_miss_quit(self, mock_input):\n        mock_input.side_effect = [\"10\", \"c\"]\n\n        ret = self.displayer.checklist(\"msg\", TAGS, force_interactive=True)\n        self.assertEqual(ret, (display_util.CANCEL, []))\n\n    def test_checklist_noninteractive(self):\n        default = TAGS\n        code, input_ = self._force_noninteractive(\n            self.displayer.checklist, \"msg\", TAGS, default=default)\n\n        self.assertEqual(code, display_util.OK)\n        self.assertEqual(input_, default)\n\n    def test_scrub_checklist_input_valid(self):\n        # pylint: disable=protected-access\n        indices = [\n            [\"1\"],\n            [\"1\", \"2\", \"1\"],\n            [\"2\", \"3\"],\n        ]\n        exp = [\n            set([\"tag1\"]),\n            set([\"tag1\", \"tag2\"]),\n            set([\"tag2\", \"tag3\"]),\n        ]\n        for i, list_ in enumerate(indices):\n            set_tags = set(\n                self.displayer._scrub_checklist_input(list_, TAGS))\n            self.assertEqual(set_tags, exp[i])\n\n    @mock.patch(\"certbot.display.util.input_with_timeout\")\n    def test_directory_select(self, mock_input):\n        args = [\"msg\", \"/var/www/html\", \"--flag\", True]\n        user_input = \"/var/www/html\"\n        mock_input.return_value = user_input\n\n        returned = self.displayer.directory_select(*args)\n        self.assertEqual(returned, (display_util.OK, user_input))\n\n    def test_directory_select_noninteractive(self):\n        default = \"/var/www/html\"\n        code, input_ = self._force_noninteractive(\n            self.displayer.directory_select, \"msg\", default=default)\n\n        self.assertEqual(code, display_util.OK)\n        self.assertEqual(input_, default)\n\n    def _force_noninteractive(self, func, *args, **kwargs):\n        skipped_interaction = self.displayer.skipped_interaction\n\n        with mock.patch(\"certbot.display.util.sys.stdin\") as mock_stdin:\n            mock_stdin.isatty.return_value = False\n            with mock.patch(\"certbot.display.util.logger\") as mock_logger:\n                result = func(*args, **kwargs)\n\n        if skipped_interaction:\n            self.assertFalse(mock_logger.warning.called)\n        else:\n            self.assertEqual(mock_logger.warning.call_count, 1)\n\n        return result\n\n    def test_scrub_checklist_input_invalid(self):\n        # pylint: disable=protected-access\n        indices = [\n            [\"0\"],\n            [\"4\"],\n            [\"tag1\"],\n            [\"1\", \"tag1\"],\n            [\"2\", \"o\"]\n        ]\n        for list_ in indices:\n            self.assertEqual(\n                self.displayer._scrub_checklist_input(list_, TAGS), [])\n\n    def test_print_menu(self):\n        # pylint: disable=protected-access\n        # This is purely cosmetic... just make sure there aren't any exceptions\n        self.displayer._print_menu(\"msg\", CHOICES)\n        self.displayer._print_menu(\"msg\", TAGS)\n\n    def test_wrap_lines(self):\n        # pylint: disable=protected-access\n        msg = (\"This is just a weak test{0}\"\n               \"This function is only meant to be for easy viewing{0}\"\n               \"Test a really really really really really really really really \"\n               \"really really really really long line...\".format('\\n'))\n        text = display_util._wrap_lines(msg)\n\n        self.assertEqual(text.count('\\n'), 3)\n\n    def test_get_valid_int_ans_valid(self):\n        # pylint: disable=protected-access\n        input_with_timeout = \"certbot.display.util.input_with_timeout\"\n        with mock.patch(input_with_timeout, return_value=\"1\"):\n            self.assertEqual(\n                self.displayer._get_valid_int_ans(1), (display_util.OK, 1))\n        ans = \"2\"\n        with mock.patch(input_with_timeout, return_value=ans):\n            self.assertEqual(\n                self.displayer._get_valid_int_ans(3),\n                (display_util.OK, int(ans)))\n\n    def test_get_valid_int_ans_invalid(self):\n        # pylint: disable=protected-access\n        answers = [\n            [\"0\", \"c\"],\n            [\"4\", \"one\", \"C\"],\n            [\"c\"],\n        ]\n        input_with_timeout = \"certbot.display.util.input_with_timeout\"\n        for ans in answers:\n            with mock.patch(input_with_timeout, side_effect=ans):\n                self.assertEqual(\n                    self.displayer._get_valid_int_ans(3),\n                    (display_util.CANCEL, -1))\n\n    def test_methods_take_force_interactive(self):\n        # Every IDisplay method implemented by FileDisplay must take\n        # force_interactive to prevent workflow regressions.\n        for name in interfaces.IDisplay.names():\n            if six.PY2:\n                getargspec = inspect.getargspec\n            else:\n                getargspec = inspect.getfullargspec\n            arg_spec = getargspec(getattr(self.displayer, name))  # pylint: disable=deprecated-method\n            self.assertTrue(\"force_interactive\" in arg_spec.args)\n\n\nclass NoninteractiveDisplayTest(unittest.TestCase):\n    \"\"\"Test non-interactive display. These tests are pretty easy!\"\"\"\n    def setUp(self):\n        super(NoninteractiveDisplayTest, self).setUp()\n        self.mock_stdout = mock.MagicMock()\n        self.displayer = display_util.NoninteractiveDisplay(self.mock_stdout)\n\n    def test_notification_no_pause(self):\n        self.displayer.notification(\"message\", 10)\n        string = self.mock_stdout.write.call_args[0][0]\n\n        self.assertTrue(\"message\" in string)\n\n    def test_input(self):\n        d = \"an incomputable value\"\n        ret = self.displayer.input(\"message\", default=d)\n        self.assertEqual(ret, (display_util.OK, d))\n        self.assertRaises(errors.MissingCommandlineFlag, self.displayer.input, \"message\")\n\n    def test_menu(self):\n        ret = self.displayer.menu(\"message\", CHOICES, default=1)\n        self.assertEqual(ret, (display_util.OK, 1))\n        self.assertRaises(errors.MissingCommandlineFlag, self.displayer.menu, \"message\", CHOICES)\n\n    def test_yesno(self):\n        d = False\n        ret = self.displayer.yesno(\"message\", default=d)\n        self.assertEqual(ret, d)\n        self.assertRaises(errors.MissingCommandlineFlag, self.displayer.yesno, \"message\")\n\n    def test_checklist(self):\n        d = [1, 3]\n        ret = self.displayer.checklist(\"message\", TAGS, default=d)\n        self.assertEqual(ret, (display_util.OK, d))\n        self.assertRaises(errors.MissingCommandlineFlag, self.displayer.checklist, \"message\", TAGS)\n\n    def test_directory_select(self):\n        default = \"/var/www/html\"\n        expected = (display_util.OK, default)\n        actual = self.displayer.directory_select(\"msg\", default)\n        self.assertEqual(expected, actual)\n\n        self.assertRaises(\n            errors.MissingCommandlineFlag, self.displayer.directory_select, \"msg\")\n\n    def test_methods_take_kwargs(self):\n        # Every IDisplay method implemented by NoninteractiveDisplay\n        # should take **kwargs because every method of FileDisplay must\n        # take force_interactive which doesn't apply to\n        # NoninteractiveDisplay.\n\n        # Use pylint code for disable to keep on single line under line length limit\n        for name in interfaces.IDisplay.names():  # pylint: disable=E1120\n            method = getattr(self.displayer, name)\n            # asserts method accepts arbitrary keyword arguments\n            if six.PY2:\n                result = inspect.getargspec(method).keywords  # pylint:deprecated-method\n                self.assertFalse(result is None)\n            else:\n                result = inspect.getfullargspec(method).varkw\n                self.assertFalse(result is None)\n\n\nclass SeparateListInputTest(unittest.TestCase):\n    \"\"\"Test Module functions.\"\"\"\n    def setUp(self):\n        self.exp = [\"a\", \"b\", \"c\", \"test\"]\n\n    @classmethod\n    def _call(cls, input_):\n        from certbot.display.util import separate_list_input\n        return separate_list_input(input_)\n\n    def test_commas(self):\n        self.assertEqual(self._call(\"a,b,c,test\"), self.exp)\n\n    def test_spaces(self):\n        self.assertEqual(self._call(\"a b c test\"), self.exp)\n\n    def test_both(self):\n        self.assertEqual(self._call(\"a, b, c, test\"), self.exp)\n\n    def test_mess(self):\n        actual = [\n            self._call(\"  a , b    c \\t test\"),\n            self._call(\",a, ,, , b c  test  \"),\n            self._call(\",,,,, , a b,,, , c,test\"),\n        ]\n\n        for act in actual:\n            self.assertEqual(act, self.exp)\n\n\nclass PlaceParensTest(unittest.TestCase):\n    @classmethod\n    def _call(cls, label):  # pylint: disable=protected-access\n        from certbot.display.util import _parens_around_char\n        return _parens_around_char(label)\n\n    def test_single_letter(self):\n        self.assertEqual(\"(a)\", self._call(\"a\"))\n\n    def test_multiple(self):\n        self.assertEqual(\"(L)abel\", self._call(\"Label\"))\n        self.assertEqual(\"(y)es please\", self._call(\"yes please\"))\n\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/eff_test.py",
    "content": "\"\"\"Tests for certbot._internal.eff.\"\"\"\nimport unittest\n\nimport mock\nimport requests\n\nfrom certbot._internal import constants\nimport certbot.tests.util as test_util\n\n\nclass HandleSubscriptionTest(test_util.ConfigTestCase):\n    \"\"\"Tests for certbot._internal.eff.handle_subscription.\"\"\"\n    def setUp(self):\n        super(HandleSubscriptionTest, self).setUp()\n        self.email = 'certbot@example.org'\n        self.config.email = self.email\n        self.config.eff_email = None\n\n    def _call(self):\n        from certbot._internal.eff import handle_subscription\n        return handle_subscription(self.config)\n\n    @test_util.patch_get_utility()\n    @mock.patch('certbot._internal.eff.subscribe')\n    def test_failure(self, mock_subscribe, mock_get_utility):\n        self.config.email = None\n        self.config.eff_email = True\n        self._call()\n        self.assertFalse(mock_subscribe.called)\n        self.assertFalse(mock_get_utility().yesno.called)\n        actual = mock_get_utility().add_message.call_args[0][0]\n        expected_part = \"because you didn't provide an e-mail address\"\n        self.assertTrue(expected_part in actual)\n\n    @mock.patch('certbot._internal.eff.subscribe')\n    def test_no_subscribe_with_no_prompt(self, mock_subscribe):\n        self.config.eff_email = False\n        with test_util.patch_get_utility() as mock_get_utility:\n            self._call()\n        self.assertFalse(mock_subscribe.called)\n        self._assert_no_get_utility_calls(mock_get_utility)\n\n    @test_util.patch_get_utility()\n    @mock.patch('certbot._internal.eff.subscribe')\n    def test_subscribe_with_no_prompt(self, mock_subscribe, mock_get_utility):\n        self.config.eff_email = True\n        self._call()\n        self._assert_subscribed(mock_subscribe)\n        self._assert_no_get_utility_calls(mock_get_utility)\n\n    def _assert_no_get_utility_calls(self, mock_get_utility):\n        self.assertFalse(mock_get_utility().yesno.called)\n        self.assertFalse(mock_get_utility().add_message.called)\n\n    @test_util.patch_get_utility()\n    @mock.patch('certbot._internal.eff.subscribe')\n    def test_subscribe_with_prompt(self, mock_subscribe, mock_get_utility):\n        mock_get_utility().yesno.return_value = True\n        self._call()\n        self._assert_subscribed(mock_subscribe)\n        self.assertFalse(mock_get_utility().add_message.called)\n        self._assert_correct_yesno_call(mock_get_utility)\n\n    def _assert_subscribed(self, mock_subscribe):\n        self.assertTrue(mock_subscribe.called)\n        self.assertEqual(mock_subscribe.call_args[0][0], self.email)\n\n    @test_util.patch_get_utility()\n    @mock.patch('certbot._internal.eff.subscribe')\n    def test_no_subscribe_with_prompt(self, mock_subscribe, mock_get_utility):\n        mock_get_utility().yesno.return_value = False\n        self._call()\n        self.assertFalse(mock_subscribe.called)\n        self.assertFalse(mock_get_utility().add_message.called)\n        self._assert_correct_yesno_call(mock_get_utility)\n\n    def _assert_correct_yesno_call(self, mock_get_utility):\n        self.assertTrue(mock_get_utility().yesno.called)\n        call_args, call_kwargs = mock_get_utility().yesno.call_args\n        actual = call_args[0]\n        expected_part = 'Electronic Frontier Foundation'\n        self.assertTrue(expected_part in actual)\n        self.assertFalse(call_kwargs.get('default', True))\n\n\nclass SubscribeTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.eff.subscribe.\"\"\"\n    def setUp(self):\n        self.email = 'certbot@example.org'\n        self.json = {'status': True}\n        self.response = mock.Mock(ok=True)\n        self.response.json.return_value = self.json\n\n    @mock.patch('certbot._internal.eff.requests.post')\n    def _call(self, mock_post):\n        mock_post.return_value = self.response\n\n        from certbot._internal.eff import subscribe\n        subscribe(self.email)\n        self._check_post_call(mock_post)\n\n    def _check_post_call(self, mock_post):\n        self.assertEqual(mock_post.call_count, 1)\n        call_args, call_kwargs = mock_post.call_args\n        self.assertEqual(call_args[0], constants.EFF_SUBSCRIBE_URI)\n\n        data = call_kwargs.get('data')\n        self.assertFalse(data is None)\n        self.assertEqual(data.get('email'), self.email)\n\n    @test_util.patch_get_utility()\n    def test_bad_status(self, mock_get_utility):\n        self.json['status'] = False\n        self._call()\n        actual = self._get_reported_message(mock_get_utility)\n        expected_part = 'because your e-mail address appears to be invalid.'\n        self.assertTrue(expected_part in actual)\n\n    @test_util.patch_get_utility()\n    def test_not_ok(self, mock_get_utility):\n        self.response.ok = False\n        self.response.raise_for_status.side_effect = requests.exceptions.HTTPError\n        self._call()\n        actual = self._get_reported_message(mock_get_utility)\n        unexpected_part = 'because'\n        self.assertFalse(unexpected_part in actual)\n\n    @test_util.patch_get_utility()\n    def test_response_not_json(self, mock_get_utility):\n        self.response.json.side_effect = ValueError()\n        self._call()\n        actual = self._get_reported_message(mock_get_utility)\n        expected_part = 'problem'\n        self.assertTrue(expected_part in actual)\n\n    @test_util.patch_get_utility()\n    def test_response_json_missing_status_element(self, mock_get_utility):\n        self.json.clear()\n        self._call()\n        actual = self._get_reported_message(mock_get_utility)\n        expected_part = 'problem'\n        self.assertTrue(expected_part in actual)\n\n    def _get_reported_message(self, mock_get_utility):\n        self.assertTrue(mock_get_utility().add_message.called)\n        return mock_get_utility().add_message.call_args[0][0]\n\n    @test_util.patch_get_utility()\n    def test_subscribe(self, mock_get_utility):\n        self._call()\n        self.assertFalse(mock_get_utility.called)\n\n\nif __name__ == '__main__':\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/error_handler_test.py",
    "content": "\"\"\"Tests for certbot._internal.error_handler.\"\"\"\nimport contextlib\nimport signal\nimport sys\nimport unittest\n\nimport mock\n\nfrom acme.magic_typing import Callable  # pylint: disable=unused-import, no-name-in-module\nfrom acme.magic_typing import Dict  # pylint: disable=unused-import, no-name-in-module\nfrom acme.magic_typing import Union  # pylint: disable=unused-import, no-name-in-module\nfrom certbot.compat import os\n\n\ndef get_signals(signums):\n    \"\"\"Get the handlers for an iterable of signums.\"\"\"\n    return dict((s, signal.getsignal(s)) for s in signums)\n\n\ndef set_signals(sig_handler_dict):\n    \"\"\"Set the signal (keys) with the handler (values) from the input dict.\"\"\"\n    for s, h in sig_handler_dict.items():\n        signal.signal(s, h)\n\n\n@contextlib.contextmanager\ndef signal_receiver(signums):\n    \"\"\"Context manager to catch signals\"\"\"\n    signals = []\n    prev_handlers = get_signals(signums)  # type: Dict[int, Union[int, None, Callable]]\n    set_signals(dict((s, lambda s, _: signals.append(s)) for s in signums))\n    yield signals\n    set_signals(prev_handlers)\n\n\ndef send_signal(signum):\n    \"\"\"Send the given signal\"\"\"\n    os.kill(os.getpid(), signum)\n\n\nclass ErrorHandlerTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.error_handler.ErrorHandler.\"\"\"\n\n    def setUp(self):\n        from certbot._internal import error_handler\n\n        self.init_func = mock.MagicMock()\n        self.init_args = set((42,))\n        self.init_kwargs = {'foo': 'bar'}\n        self.handler = error_handler.ErrorHandler(self.init_func,\n                                                  *self.init_args,\n                                                  **self.init_kwargs)\n\n        # pylint: disable=protected-access\n        self.signals = error_handler._SIGNALS\n\n    def test_context_manager(self):\n        exception_raised = False\n        try:\n            with self.handler:\n                raise ValueError\n        except ValueError:\n            exception_raised = True\n\n        self.assertTrue(exception_raised)\n        self.init_func.assert_called_once_with(*self.init_args,\n                                               **self.init_kwargs)\n\n    def test_context_manager_with_signal(self):\n        if not self.signals:\n            self.skipTest(reason='Signals cannot be handled on Windows.')\n        init_signals = get_signals(self.signals)\n        with signal_receiver(self.signals) as signals_received:\n            with self.handler:\n                should_be_42 = 42\n                send_signal(self.signals[0])\n                should_be_42 *= 10\n\n        # check execution stoped when the signal was sent\n        self.assertEqual(42, should_be_42)\n        # assert signals were caught\n        self.assertEqual([self.signals[0]], signals_received)\n        # assert the error handling function was just called once\n        self.init_func.assert_called_once_with(*self.init_args,\n                                               **self.init_kwargs)\n        for signum in self.signals:\n            self.assertEqual(init_signals[signum], signal.getsignal(signum))\n\n    def test_bad_recovery(self):\n        bad_func = mock.MagicMock(side_effect=[ValueError])\n        self.handler.register(bad_func)\n        try:\n            with self.handler:\n                raise ValueError\n        except ValueError:\n            pass\n        self.init_func.assert_called_once_with(*self.init_args,\n                                               **self.init_kwargs)\n        bad_func.assert_called_once_with()\n\n    def test_bad_recovery_with_signal(self):\n        if not self.signals:\n            self.skipTest(reason='Signals cannot be handled on Windows.')\n        sig1 = self.signals[0]\n        sig2 = self.signals[-1]\n        bad_func = mock.MagicMock(side_effect=lambda: send_signal(sig1))\n        self.handler.register(bad_func)\n        with signal_receiver(self.signals) as signals_received:\n            with self.handler:\n                send_signal(sig2)\n        self.assertEqual([sig2, sig1], signals_received)\n        self.init_func.assert_called_once_with(*self.init_args,\n                                               **self.init_kwargs)\n        bad_func.assert_called_once_with()\n\n    def test_sysexit_ignored(self):\n        try:\n            with self.handler:\n                sys.exit(0)\n        except SystemExit:\n            pass\n        self.assertFalse(self.init_func.called)\n\n    def test_regular_exit(self):\n        func = mock.MagicMock()\n        self.handler.register(func)\n        with self.handler:\n            pass\n        self.init_func.assert_not_called()\n        func.assert_not_called()\n\n\nclass ExitHandlerTest(ErrorHandlerTest):\n    \"\"\"Tests for certbot._internal.error_handler.ExitHandler.\"\"\"\n\n    def setUp(self):\n        from certbot._internal import error_handler\n        super(ExitHandlerTest, self).setUp()\n        self.handler = error_handler.ExitHandler(self.init_func,\n                                                 *self.init_args,\n                                                 **self.init_kwargs)\n\n    def test_regular_exit(self):\n        func = mock.MagicMock()\n        self.handler.register(func)\n        with self.handler:\n            pass\n        self.init_func.assert_called_once_with(*self.init_args,\n                                               **self.init_kwargs)\n        func.assert_called_once_with()\n\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/errors_test.py",
    "content": "\"\"\"Tests for certbot.errors.\"\"\"\nimport unittest\n\nimport mock\n\nfrom acme import messages\nfrom certbot import achallenges\nfrom certbot.tests import acme_util\n\n\nclass FailedChallengesTest(unittest.TestCase):\n    \"\"\"Tests for certbot.errors.FailedChallenges.\"\"\"\n\n    def setUp(self):\n        from certbot.errors import FailedChallenges\n        self.error = FailedChallenges({achallenges.DNS(\n            domain=\"example.com\", challb=messages.ChallengeBody(\n                chall=acme_util.DNS01, uri=None,\n                error=messages.Error.with_code(\"tls\", detail=\"detail\")))})\n\n    def test_str(self):\n        self.assertTrue(str(self.error).startswith(\n            \"Failed authorization procedure. example.com (dns-01): \"\n            \"urn:ietf:params:acme:error:tls\"))\n\n    def test_unicode(self):\n        from certbot.errors import FailedChallenges\n        arabic_detail = u'\\u0639\\u062f\\u0627\\u0644\\u0629'\n        arabic_error = FailedChallenges({achallenges.DNS(\n            domain=\"example.com\", challb=messages.ChallengeBody(\n                chall=acme_util.DNS01, uri=None,\n                error=messages.Error.with_code(\"tls\", detail=arabic_detail)))})\n\n        self.assertTrue(str(arabic_error).startswith(\n            \"Failed authorization procedure. example.com (dns-01): \"\n            \"urn:ietf:params:acme:error:tls\"))\n\n\nclass StandaloneBindErrorTest(unittest.TestCase):\n    \"\"\"Tests for certbot.errors.StandaloneBindError.\"\"\"\n\n    def setUp(self):\n        from certbot.errors import StandaloneBindError\n        self.error = StandaloneBindError(mock.sentinel.error, 1234)\n\n    def test_instance_args(self):\n        self.assertEqual(mock.sentinel.error, self.error.socket_error)\n        self.assertEqual(1234, self.error.port)\n\n    def test_str(self):\n        self.assertTrue(str(self.error).startswith(\n            \"Problem binding to port 1234: \"))\n\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/helpful_test.py",
    "content": "\"\"\"Tests for certbot.helpful_parser\"\"\"\nimport unittest\n\nfrom certbot import errors\nfrom certbot._internal.cli import HelpfulArgumentParser\nfrom certbot._internal.cli import _DomainsAction\nfrom certbot._internal import constants\n\n\nclass TestScanningFlags(unittest.TestCase):\n    '''Test the prescan_for_flag method of HelpfulArgumentParser'''\n    def test_prescan_no_help_flag(self):\n        arg_parser = HelpfulArgumentParser(['run'], {})\n        detected_flag = arg_parser.prescan_for_flag('--help',\n                                                        ['all', 'certonly'])\n        self.assertFalse(detected_flag)\n        detected_flag = arg_parser.prescan_for_flag('-h',\n                                                        ['all, certonly'])\n        self.assertFalse(detected_flag)\n\n    def test_prescan_unvalid_topic(self):\n        arg_parser = HelpfulArgumentParser(['--help', 'all'], {})\n        detected_flag = arg_parser.prescan_for_flag('--help',\n                                                    ['potato'])\n        self.assertIs(detected_flag, True)\n        detected_flag = arg_parser.prescan_for_flag('-h',\n                                                    arg_parser.help_topics)\n        self.assertFalse(detected_flag)\n\n    def test_prescan_valid_topic(self):\n        arg_parser = HelpfulArgumentParser(['-h', 'all'], {})\n        detected_flag = arg_parser.prescan_for_flag('-h',\n                                                    arg_parser.help_topics)\n        self.assertEqual(detected_flag, 'all')\n        detected_flag = arg_parser.prescan_for_flag('--help',\n                                                    arg_parser.help_topics)\n        self.assertFalse(detected_flag)\n\nclass TestDetermineVerbs(unittest.TestCase):\n    '''Tests for determine_verb methods of HelpfulArgumentParser'''\n    def test_determine_verb_wrong_verb(self):\n        arg_parser = HelpfulArgumentParser(['potato'], {})\n        self.assertEqual(arg_parser.verb, \"run\")\n        self.assertEqual(arg_parser.args, [\"potato\"])\n\n    def test_determine_verb_help(self):\n        arg_parser = HelpfulArgumentParser(['--help', 'everything'], {})\n        self.assertEqual(arg_parser.verb, \"help\")\n        self.assertEqual(arg_parser.args, [\"--help\", \"everything\"])\n        arg_parser = HelpfulArgumentParser(['-d', 'some_domain', '--help',\n                                               'all'], {})\n        self.assertEqual(arg_parser.verb, \"help\")\n        self.assertEqual(arg_parser.args, ['-d', 'some_domain', '--help',\n                                               'all'])\n\n    def test_determine_verb(self):\n        arg_parser = HelpfulArgumentParser(['certonly'], {})\n        self.assertEqual(arg_parser.verb, 'certonly')\n        self.assertEqual(arg_parser.args, [])\n\n        arg_parser = HelpfulArgumentParser(['auth'], {})\n        self.assertEqual(arg_parser.verb, 'certonly')\n        self.assertEqual(arg_parser.args, [])\n\n        arg_parser = HelpfulArgumentParser(['everything'], {})\n        self.assertEqual(arg_parser.verb, 'run')\n        self.assertEqual(arg_parser.args, [])\n\n\nclass TestAdd(unittest.TestCase):\n    '''Tests for add method in HelpfulArgumentParser'''\n    def test_add_trivial_argument(self):\n        arg_parser = HelpfulArgumentParser(['run'], {})\n        arg_parser.add(None, \"--hello-world\")\n        parsed_args = arg_parser.parser.parse_args(['--hello-world',\n                                                    'Hello World!'])\n        self.assertIs(parsed_args.hello_world, 'Hello World!')\n        self.assertFalse(hasattr(parsed_args, 'potato'))\n\n    def test_add_expected_argument(self):\n        arg_parser = HelpfulArgumentParser(['--help', 'run'], {})\n        arg_parser.add(\n                [None, \"run\", \"certonly\", \"register\"],\n                \"--eab-kid\", dest=\"eab_kid\", action=\"store\",\n                metavar=\"EAB_KID\",\n                help=\"Key Identifier for External Account Binding\")\n        parsed_args = arg_parser.parser.parse_args([\"--eab-kid\", None])\n        self.assertIs(parsed_args.eab_kid, None)\n        self.assertTrue(hasattr(parsed_args, 'eab_kid'))\n\n\nclass TestAddGroup(unittest.TestCase):\n    '''Test add_group method of HelpfulArgumentParser'''\n    def test_add_group_no_input(self):\n        arg_parser = HelpfulArgumentParser(['run'], {})\n        self.assertRaises(TypeError, arg_parser.add_group)\n\n    def test_add_group_topic_not_visible(self):\n        # The user request help on run. A topic that given somewhere in the\n        # args won't be added to the groups in the parser.\n        arg_parser = HelpfulArgumentParser(['--help', 'run'], {})\n        arg_parser.add_group(\"auth\",\n                                         description=\"description of auth\")\n        self.assertEqual(arg_parser.groups, {})\n\n    def test_add_group_topic_requested_help(self):\n        arg_parser = HelpfulArgumentParser(['--help', 'run'], {})\n        arg_parser.add_group(\"run\",\n                                         description=\"description of run\")\n        self.assertTrue(arg_parser.groups[\"run\"])\n        arg_parser.add_group(\"certonly\", description=\"description of certonly\")\n        with self.assertRaises(KeyError):\n            self.assertFalse(arg_parser.groups[\"certonly\"])\n\n\nclass TestParseArgsErrors(unittest.TestCase):\n    '''Tests for errors that should be met for some cases in parse_args method\n    in HelpfulArgumentParser'''\n    def test_parse_args_renew_force_interactive(self):\n        arg_parser = HelpfulArgumentParser(['renew', '--force-interactive'],\n                                           {})\n        arg_parser.add(\n            None, constants.FORCE_INTERACTIVE_FLAG, action=\"store_true\")\n\n        with self.assertRaises(errors.Error):\n            arg_parser.parse_args()\n\n    def test_parse_args_non_interactive_and_force_interactive(self):\n        arg_parser = HelpfulArgumentParser(['--force-interactive',\n                                            '--non-interactive'], {})\n        arg_parser.add(\n            None, constants.FORCE_INTERACTIVE_FLAG, action=\"store_true\")\n        arg_parser.add(\n            None, \"--non-interactive\", dest=\"noninteractive_mode\",\n            action=\"store_true\"\n        )\n\n        with self.assertRaises(errors.Error):\n            arg_parser.parse_args()\n\n    def test_parse_args_subset_names_wildcard_domain(self):\n        arg_parser = HelpfulArgumentParser(['--domain',\n                                           '*.example.com,potato.example.com',\n                                           '--allow-subset-of-names'], {})\n        # The following arguments are added because they have to be defined\n        # in order for arg_parser to run completely. They are not used for the\n        # test.\n        arg_parser.add(\n            None, constants.FORCE_INTERACTIVE_FLAG, action=\"store_true\")\n        arg_parser.add(\n            None, \"--non-interactive\", dest=\"noninteractive_mode\",\n            action=\"store_true\")\n        arg_parser.add(\n            None, \"--staging\"\n        )\n        arg_parser.add(None, \"--dry-run\")\n        arg_parser.add(None, \"--csr\")\n        arg_parser.add(None, \"--must-staple\")\n        arg_parser.add(None, \"--validate-hooks\")\n\n        arg_parser.add(None, \"-d\", \"--domain\", dest=\"domains\",\n                       metavar=\"DOMAIN\", action=_DomainsAction)\n        arg_parser.add(None, \"--allow-subset-of-names\")\n        # with self.assertRaises(errors.Error):\n        #    arg_parser.parse_args()\n\n    def test_parse_args_hosts_and_auto_hosts(self):\n        arg_parser = HelpfulArgumentParser(['--hsts', '--auto-hsts'], {})\n\n        arg_parser.add(\n            None, \"--hsts\", action=\"store_true\", dest=\"hsts\")\n        arg_parser.add(\n            None, \"--auto-hsts\", action=\"store_true\", dest=\"auto_hsts\")\n        # The following arguments are added because they have to be defined\n        # in order for arg_parser to run completely. They are not used for the\n        # test.\n        arg_parser.add(\n            None, constants.FORCE_INTERACTIVE_FLAG, action=\"store_true\")\n        arg_parser.add(\n            None, \"--non-interactive\", dest=\"noninteractive_mode\",\n            action=\"store_true\")\n        arg_parser.add(None, \"--staging\")\n        arg_parser.add(None, \"--dry-run\")\n        arg_parser.add(None, \"--csr\")\n        arg_parser.add(None, \"--must-staple\")\n        arg_parser.add(None, \"--validate-hooks\")\n        arg_parser.add(None, \"--allow-subset-of-names\")\n        with self.assertRaises(errors.Error):\n            arg_parser.parse_args()\n\n\nif __name__ == '__main__':\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/hook_test.py",
    "content": "\"\"\"Tests for certbot._internal.hooks.\"\"\"\nimport unittest\n\nimport mock\n\nfrom acme.magic_typing import List  # pylint: disable=unused-import, no-name-in-module\nfrom certbot import errors\nfrom certbot import util\nfrom certbot.compat import filesystem\nfrom certbot.compat import os\nfrom certbot.tests import util as test_util\n\n\nclass ValidateHooksTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.hooks.validate_hooks.\"\"\"\n\n    @classmethod\n    def _call(cls, *args, **kwargs):\n        from certbot._internal.hooks import validate_hooks\n        return validate_hooks(*args, **kwargs)\n\n    @mock.patch(\"certbot._internal.hooks.validate_hook\")\n    def test_it(self, mock_validate_hook):\n        config = mock.MagicMock()\n        self._call(config)\n\n        types = [call[0][1] for call in mock_validate_hook.call_args_list]\n        self.assertEqual(set((\"pre\", \"post\", \"deploy\",)), set(types[:-1]))\n        # This ensures error messages are about deploy hooks when appropriate\n        self.assertEqual(\"renew\", types[-1])\n\n\nclass ValidateHookTest(test_util.TempDirTestCase):\n    \"\"\"Tests for certbot._internal.hooks.validate_hook.\"\"\"\n\n    @classmethod\n    def _call(cls, *args, **kwargs):\n        from certbot._internal.hooks import validate_hook\n        return validate_hook(*args, **kwargs)\n\n    def test_hook_not_executable(self):\n        # prevent unnecessary modifications to PATH\n        with mock.patch(\"certbot._internal.hooks.plug_util.path_surgery\"):\n            # We just mock out filesystem.is_executable since on Windows, it is difficult\n            # to get a fully working test around executable permissions. See\n            # certbot.tests.compat.filesystem::NotExecutableTest for more in-depth tests.\n            with mock.patch(\"certbot._internal.hooks.filesystem.is_executable\", return_value=False):\n                self.assertRaises(errors.HookCommandNotFound, self._call, 'dummy', \"foo\")\n\n    @mock.patch(\"certbot._internal.hooks.util.exe_exists\")\n    def test_not_found(self, mock_exe_exists):\n        mock_exe_exists.return_value = False\n        with mock.patch(\"certbot._internal.hooks.plug_util.path_surgery\") as mock_ps:\n            self.assertRaises(errors.HookCommandNotFound, self._call, \"foo\", \"bar\")\n        self.assertTrue(mock_ps.called)\n\n    @mock.patch(\"certbot._internal.hooks._prog\")\n    def test_unset(self, mock_prog):\n        self._call(None, \"foo\")\n        self.assertFalse(mock_prog.called)\n\n\nclass HookTest(test_util.ConfigTestCase):\n    \"\"\"Common base class for hook tests.\"\"\"\n\n    @classmethod\n    def _call(cls, *args, **kwargs):  # pragma: no cover\n        \"\"\"Calls the method being tested with the given arguments.\"\"\"\n        raise NotImplementedError\n\n    @classmethod\n    def _call_with_mock_execute(cls, *args, **kwargs):\n        \"\"\"Calls self._call after mocking out certbot._internal.hooks.execute.\n\n        The mock execute object is returned rather than the return value\n        of self._call.\n\n        \"\"\"\n        with mock.patch(\"certbot._internal.hooks.execute\") as mock_execute:\n            mock_execute.return_value = (\"\", \"\")\n            cls._call(*args, **kwargs)\n        return mock_execute\n\n\nclass PreHookTest(HookTest):\n    \"\"\"Tests for certbot._internal.hooks.pre_hook.\"\"\"\n\n    @classmethod\n    def _call(cls, *args, **kwargs):\n        from certbot._internal.hooks import pre_hook\n        return pre_hook(*args, **kwargs)\n\n    def setUp(self):\n        super(PreHookTest, self).setUp()\n        self.config.pre_hook = \"foo\"\n\n        filesystem.makedirs(self.config.renewal_pre_hooks_dir)\n        self.dir_hook = os.path.join(self.config.renewal_pre_hooks_dir, \"bar\")\n        create_hook(self.dir_hook)\n\n        # Reset this value as it may have been modified by past tests\n        self._reset_pre_hook_already()\n\n    def tearDown(self):\n        # Reset this value so it's unmodified for future tests\n        self._reset_pre_hook_already()\n        super(PreHookTest, self).tearDown()\n\n    def _reset_pre_hook_already(self):\n        from certbot._internal.hooks import executed_pre_hooks\n        executed_pre_hooks.clear()\n\n    def test_certonly(self):\n        self.config.verb = \"certonly\"\n        self._test_nonrenew_common()\n\n    def test_run(self):\n        self.config.verb = \"run\"\n        self._test_nonrenew_common()\n\n    def _test_nonrenew_common(self):\n        mock_execute = self._call_with_mock_execute(self.config)\n        mock_execute.assert_called_once_with(\"pre-hook\", self.config.pre_hook)\n        self._test_no_executions_common()\n\n    def test_no_hooks(self):\n        self.config.pre_hook = None\n        self.config.verb = \"renew\"\n        os.remove(self.dir_hook)\n\n        with mock.patch(\"certbot._internal.hooks.logger\") as mock_logger:\n            mock_execute = self._call_with_mock_execute(self.config)\n        self.assertFalse(mock_execute.called)\n        self.assertFalse(mock_logger.info.called)\n\n    def test_renew_disabled_dir_hooks(self):\n        self.config.directory_hooks = False\n        mock_execute = self._call_with_mock_execute(self.config)\n        mock_execute.assert_called_once_with(\"pre-hook\", self.config.pre_hook)\n        self._test_no_executions_common()\n\n    def test_renew_no_overlap(self):\n        self.config.verb = \"renew\"\n        mock_execute = self._call_with_mock_execute(self.config)\n        mock_execute.assert_any_call(\"pre-hook\", self.dir_hook)\n        mock_execute.assert_called_with(\"pre-hook\", self.config.pre_hook)\n        self._test_no_executions_common()\n\n    def test_renew_with_overlap(self):\n        self.config.pre_hook = self.dir_hook\n        self.config.verb = \"renew\"\n        mock_execute = self._call_with_mock_execute(self.config)\n        mock_execute.assert_called_once_with(\"pre-hook\", self.dir_hook)\n        self._test_no_executions_common()\n\n    def _test_no_executions_common(self):\n        with mock.patch(\"certbot._internal.hooks.logger\") as mock_logger:\n            mock_execute = self._call_with_mock_execute(self.config)\n        self.assertFalse(mock_execute.called)\n        self.assertTrue(mock_logger.info.called)\n\n\nclass PostHookTest(HookTest):\n    \"\"\"Tests for certbot._internal.hooks.post_hook.\"\"\"\n\n    @classmethod\n    def _call(cls, *args, **kwargs):\n        from certbot._internal.hooks import post_hook\n        return post_hook(*args, **kwargs)\n\n    def setUp(self):\n        super(PostHookTest, self).setUp()\n\n        self.config.post_hook = \"bar\"\n        filesystem.makedirs(self.config.renewal_post_hooks_dir)\n        self.dir_hook = os.path.join(self.config.renewal_post_hooks_dir, \"foo\")\n        create_hook(self.dir_hook)\n\n        # Reset this value as it may have been modified by past tests\n        self._reset_post_hook_eventually()\n\n    def tearDown(self):\n        # Reset this value so it's unmodified for future tests\n        self._reset_post_hook_eventually()\n        super(PostHookTest, self).tearDown()\n\n    def _reset_post_hook_eventually(self):\n        from certbot._internal.hooks import post_hooks\n        del post_hooks[:]\n\n    def test_certonly_and_run_with_hook(self):\n        for verb in (\"certonly\", \"run\",):\n            self.config.verb = verb\n            mock_execute = self._call_with_mock_execute(self.config)\n            mock_execute.assert_called_once_with(\"post-hook\", self.config.post_hook)\n            self.assertFalse(self._get_eventually())\n\n    def test_cert_only_and_run_without_hook(self):\n        self.config.post_hook = None\n        for verb in (\"certonly\", \"run\",):\n            self.config.verb = verb\n            self.assertFalse(self._call_with_mock_execute(self.config).called)\n            self.assertFalse(self._get_eventually())\n\n    def test_renew_disabled_dir_hooks(self):\n        self.config.directory_hooks = False\n        self._test_renew_common([self.config.post_hook])\n\n    def test_renew_no_config_hook(self):\n        self.config.post_hook = None\n        self._test_renew_common([self.dir_hook])\n\n    def test_renew_no_dir_hook(self):\n        os.remove(self.dir_hook)\n        self._test_renew_common([self.config.post_hook])\n\n    def test_renew_no_hooks(self):\n        self.config.post_hook = None\n        os.remove(self.dir_hook)\n        self._test_renew_common([])\n\n    def test_renew_no_overlap(self):\n        expected = [self.dir_hook, self.config.post_hook]\n        self._test_renew_common(expected)\n\n        self.config.post_hook = \"baz\"\n        expected.append(self.config.post_hook)\n        self._test_renew_common(expected)\n\n    def test_renew_with_overlap(self):\n        self.config.post_hook = self.dir_hook\n        self._test_renew_common([self.dir_hook])\n\n    def _test_renew_common(self, expected):\n        self.config.verb = \"renew\"\n\n        for _ in range(2):\n            self._call(self.config)\n            self.assertEqual(self._get_eventually(), expected)\n\n    def _get_eventually(self):\n        from certbot._internal.hooks import post_hooks\n        return post_hooks\n\n\nclass RunSavedPostHooksTest(HookTest):\n    \"\"\"Tests for certbot._internal.hooks.run_saved_post_hooks.\"\"\"\n\n    @classmethod\n    def _call(cls, *args, **kwargs):\n        from certbot._internal.hooks import run_saved_post_hooks\n        return run_saved_post_hooks()\n\n    def _call_with_mock_execute_and_eventually(self, *args, **kwargs):\n        \"\"\"Call run_saved_post_hooks but mock out execute and eventually\n\n        certbot._internal.hooks.post_hooks is replaced with\n        self.eventually. The mock execute object is returned rather than\n        the return value of run_saved_post_hooks.\n\n        \"\"\"\n        eventually_path = \"certbot._internal.hooks.post_hooks\"\n        with mock.patch(eventually_path, new=self.eventually):\n            return self._call_with_mock_execute(*args, **kwargs)\n\n    def setUp(self):\n        super(RunSavedPostHooksTest, self).setUp()\n        self.eventually = []  # type: List[str]\n\n    def test_empty(self):\n        self.assertFalse(self._call_with_mock_execute_and_eventually().called)\n\n    def test_multiple(self):\n        self.eventually = [\"foo\", \"bar\", \"baz\", \"qux\"]\n        mock_execute = self._call_with_mock_execute_and_eventually()\n\n        calls = mock_execute.call_args_list\n        for actual_call, expected_arg in zip(calls, self.eventually):\n            self.assertEqual(actual_call[0][1], expected_arg)\n\n    def test_single(self):\n        self.eventually = [\"foo\"]\n        mock_execute = self._call_with_mock_execute_and_eventually()\n        mock_execute.assert_called_once_with(\"post-hook\", self.eventually[0])\n\n\nclass RenewalHookTest(HookTest):\n    \"\"\"Common base class for testing deploy/renew hooks.\"\"\"\n    # Needed for https://github.com/PyCQA/pylint/issues/179\n    # pylint: disable=abstract-method\n\n    def _call_with_mock_execute(self, *args, **kwargs):\n        \"\"\"Calls self._call after mocking out certbot._internal.hooks.execute.\n\n        The mock execute object is returned rather than the return value\n        of self._call. The mock execute object asserts that environment\n        variables were properly set.\n\n        \"\"\"\n        domains = kwargs[\"domains\"] if \"domains\" in kwargs else args[1]\n        lineage = kwargs[\"lineage\"] if \"lineage\" in kwargs else args[2]\n\n        def execute_side_effect(*unused_args, **unused_kwargs):\n            \"\"\"Assert environment variables are properly set.\n\n            :returns: two strings imitating no output from the hook\n            :rtype: `tuple` of `str`\n\n            \"\"\"\n            self.assertEqual(os.environ[\"RENEWED_DOMAINS\"], \" \".join(domains))\n            self.assertEqual(os.environ[\"RENEWED_LINEAGE\"], lineage)\n            return (\"\", \"\")\n\n        with mock.patch(\"certbot._internal.hooks.execute\") as mock_execute:\n            mock_execute.side_effect = execute_side_effect\n            self._call(*args, **kwargs)\n        return mock_execute\n\n    def setUp(self):\n        super(RenewalHookTest, self).setUp()\n        self.vars_to_clear = set(\n            var for var in (\"RENEWED_DOMAINS\", \"RENEWED_LINEAGE\",)\n            if var not in os.environ)\n\n    def tearDown(self):\n        for var in self.vars_to_clear:\n            os.environ.pop(var, None)\n        super(RenewalHookTest, self).tearDown()\n\n\nclass DeployHookTest(RenewalHookTest):\n    \"\"\"Tests for certbot._internal.hooks.deploy_hook.\"\"\"\n\n    @classmethod\n    def _call(cls, *args, **kwargs):\n        from certbot._internal.hooks import deploy_hook\n        return deploy_hook(*args, **kwargs)\n\n    @mock.patch(\"certbot._internal.hooks.logger\")\n    def test_dry_run(self, mock_logger):\n        self.config.deploy_hook = \"foo\"\n        self.config.dry_run = True\n        mock_execute = self._call_with_mock_execute(\n            self.config, [\"example.org\"], \"/foo/bar\")\n        self.assertFalse(mock_execute.called)\n        self.assertTrue(mock_logger.warning.called)\n\n    @mock.patch(\"certbot._internal.hooks.logger\")\n    def test_no_hook(self, mock_logger):\n        self.config.deploy_hook = None\n        mock_execute = self._call_with_mock_execute(\n            self.config, [\"example.org\"], \"/foo/bar\")\n        self.assertFalse(mock_execute.called)\n        self.assertFalse(mock_logger.info.called)\n\n    def test_success(self):\n        domains = [\"example.org\", \"example.net\"]\n        lineage = \"/foo/bar\"\n        self.config.deploy_hook = \"foo\"\n        mock_execute = self._call_with_mock_execute(\n            self.config, domains, lineage)\n        mock_execute.assert_called_once_with(\"deploy-hook\", self.config.deploy_hook)\n\n\nclass RenewHookTest(RenewalHookTest):\n    \"\"\"Tests for certbot._internal.hooks.renew_hook\"\"\"\n\n    @classmethod\n    def _call(cls, *args, **kwargs):\n        from certbot._internal.hooks import renew_hook\n        return renew_hook(*args, **kwargs)\n\n    def setUp(self):\n        super(RenewHookTest, self).setUp()\n        self.config.renew_hook = \"foo\"\n\n        filesystem.makedirs(self.config.renewal_deploy_hooks_dir)\n        self.dir_hook = os.path.join(self.config.renewal_deploy_hooks_dir,\n                                     \"bar\")\n        create_hook(self.dir_hook)\n\n    def test_disabled_dir_hooks(self):\n        self.config.directory_hooks = False\n        mock_execute = self._call_with_mock_execute(\n            self.config, [\"example.org\"], \"/foo/bar\")\n        mock_execute.assert_called_once_with(\"deploy-hook\", self.config.renew_hook)\n\n    @mock.patch(\"certbot._internal.hooks.logger\")\n    def test_dry_run(self, mock_logger):\n        self.config.dry_run = True\n        mock_execute = self._call_with_mock_execute(\n            self.config, [\"example.org\"], \"/foo/bar\")\n        self.assertFalse(mock_execute.called)\n        self.assertEqual(mock_logger.warning.call_count, 2)\n\n    def test_no_hooks(self):\n        self.config.renew_hook = None\n        os.remove(self.dir_hook)\n\n        with mock.patch(\"certbot._internal.hooks.logger\") as mock_logger:\n            mock_execute = self._call_with_mock_execute(\n                self.config, [\"example.org\"], \"/foo/bar\")\n        self.assertFalse(mock_execute.called)\n        self.assertFalse(mock_logger.info.called)\n\n    def test_overlap(self):\n        self.config.renew_hook = self.dir_hook\n        mock_execute = self._call_with_mock_execute(\n            self.config, [\"example.net\", \"example.org\"], \"/foo/bar\")\n        mock_execute.assert_called_once_with(\"deploy-hook\", self.dir_hook)\n\n    def test_no_overlap(self):\n        mock_execute = self._call_with_mock_execute(\n            self.config, [\"example.org\"], \"/foo/bar\")\n        mock_execute.assert_any_call(\"deploy-hook\", self.dir_hook)\n        mock_execute.assert_called_with(\"deploy-hook\", self.config.renew_hook)\n\n\nclass ExecuteTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.hooks.execute.\"\"\"\n\n    @classmethod\n    def _call(cls, *args, **kwargs):\n        from certbot._internal.hooks import execute\n        return execute(*args, **kwargs)\n\n    def test_it(self):\n        for returncode in range(0, 2):\n            for stdout in (\"\", \"Hello World!\",):\n                for stderr in (\"\", \"Goodbye Cruel World!\"):\n                    self._test_common(returncode, stdout, stderr)\n\n    def _test_common(self, returncode, stdout, stderr):\n        given_command = \"foo\"\n        given_name = \"foo-hook\"\n        with mock.patch(\"certbot._internal.hooks.Popen\") as mock_popen:\n            mock_popen.return_value.communicate.return_value = (stdout, stderr)\n            mock_popen.return_value.returncode = returncode\n            with mock.patch(\"certbot._internal.hooks.logger\") as mock_logger:\n                self.assertEqual(self._call(given_name, given_command), (stderr, stdout))\n\n        executed_command = mock_popen.call_args[1].get(\n            \"args\", mock_popen.call_args[0][0])\n        self.assertEqual(executed_command, given_command)\n\n        mock_logger.info.assert_any_call(\"Running %s command: %s\",\n                                         given_name, given_command)\n        if stdout:\n            mock_logger.info.assert_any_call(mock.ANY, mock.ANY,\n                                             mock.ANY, stdout)\n        if stderr or returncode:\n            self.assertTrue(mock_logger.error.called)\n\n\nclass ListHooksTest(test_util.TempDirTestCase):\n    \"\"\"Tests for certbot._internal.hooks.list_hooks.\"\"\"\n\n    @classmethod\n    def _call(cls, *args, **kwargs):\n        from certbot._internal.hooks import list_hooks\n        return list_hooks(*args, **kwargs)\n\n    def test_empty(self):\n        self.assertFalse(self._call(self.tempdir))\n\n    def test_multiple(self):\n        names = sorted(\n            os.path.join(self.tempdir, basename)\n            for basename in (\"foo\", \"bar\", \"baz\", \"qux\")\n        )\n        for name in names:\n            create_hook(name)\n\n        self.assertEqual(self._call(self.tempdir), names)\n\n    def test_single(self):\n        name = os.path.join(self.tempdir, \"foo\")\n        create_hook(name)\n\n        self.assertEqual(self._call(self.tempdir), [name])\n\n    def test_ignore_tilde(self):\n        name = os.path.join(self.tempdir, \"foo~\")\n        create_hook(name)\n\n        self.assertEqual(self._call(self.tempdir), [])\n\n\ndef create_hook(file_path):\n    \"\"\"Creates an executable file at the specified path.\n\n    :param str file_path: path to create the file at\n\n    \"\"\"\n    util.safe_open(file_path, mode=\"w\", chmod=0o744).close()\n\n\nif __name__ == '__main__':\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/lock_test.py",
    "content": "\"\"\"Tests for certbot._internal.lock.\"\"\"\nimport functools\nimport multiprocessing\nimport unittest\n\nimport mock\n\nfrom certbot import errors\nfrom certbot.compat import os\nfrom certbot.tests import util as test_util\n\ntry:\n    import fcntl  # pylint: disable=import-error,unused-import\nexcept ImportError:\n    POSIX_MODE = False\nelse:\n    POSIX_MODE = True\n\n\n\n\nclass LockDirTest(test_util.TempDirTestCase):\n    \"\"\"Tests for certbot._internal.lock.lock_dir.\"\"\"\n    @classmethod\n    def _call(cls, *args, **kwargs):\n        from certbot._internal.lock import lock_dir\n        return lock_dir(*args, **kwargs)\n\n    def test_it(self):\n        assert_raises = functools.partial(\n            self.assertRaises, errors.LockError, self._call, self.tempdir)\n        lock_path = os.path.join(self.tempdir, '.certbot.lock')\n        test_util.lock_and_call(assert_raises, lock_path)\n\n\nclass LockFileTest(test_util.TempDirTestCase):\n    \"\"\"Tests for certbot._internal.lock.LockFile.\"\"\"\n    @classmethod\n    def _call(cls, *args, **kwargs):\n        from certbot._internal.lock import LockFile\n        return LockFile(*args, **kwargs)\n\n    def setUp(self):\n        super(LockFileTest, self).setUp()\n        self.lock_path = os.path.join(self.tempdir, 'test.lock')\n\n    def test_acquire_without_deletion(self):\n        # acquire the lock in another process but don't delete the file\n        child = multiprocessing.Process(target=self._call,\n                                        args=(self.lock_path,))\n        child.start()\n        child.join()\n        self.assertEqual(child.exitcode, 0)\n        self.assertTrue(os.path.exists(self.lock_path))\n\n        # Test we're still able to properly acquire and release the lock\n        self.test_removed()\n\n    def test_contention(self):\n        assert_raises = functools.partial(\n            self.assertRaises, errors.LockError, self._call, self.lock_path)\n        test_util.lock_and_call(assert_raises, self.lock_path)\n\n    def test_locked_repr(self):\n        lock_file = self._call(self.lock_path)\n        try:\n            locked_repr = repr(lock_file)\n            self._test_repr_common(lock_file, locked_repr)\n            self.assertTrue('acquired' in locked_repr)\n        finally:\n            lock_file.release()\n\n    def test_released_repr(self):\n        lock_file = self._call(self.lock_path)\n        lock_file.release()\n        released_repr = repr(lock_file)\n        self._test_repr_common(lock_file, released_repr)\n        self.assertTrue('released' in released_repr)\n\n    def _test_repr_common(self, lock_file, lock_repr):\n        self.assertTrue(lock_file.__class__.__name__ in lock_repr)\n        self.assertTrue(self.lock_path in lock_repr)\n\n    @test_util.skip_on_windows(\n        'Race conditions on lock are specific to the non-blocking file access approach on Linux.')\n    def test_race(self):\n        should_delete = [True, False]\n        # Normally os module should not be imported in certbot codebase except in certbot.compat\n        # for the sake of compatibility over Windows and Linux.\n        # We make an exception here, since test_race is a test function called only on Linux.\n        from os import stat  # pylint: disable=os-module-forbidden\n\n        def delete_and_stat(path):\n            \"\"\"Wrap os.stat and maybe delete the file first.\"\"\"\n            if path == self.lock_path and should_delete.pop(0):\n                os.remove(path)\n            return stat(path)\n\n        with mock.patch('certbot._internal.lock.filesystem.os.stat') as mock_stat:\n            mock_stat.side_effect = delete_and_stat\n            self._call(self.lock_path)\n        self.assertFalse(should_delete)\n\n    def test_removed(self):\n        lock_file = self._call(self.lock_path)\n        lock_file.release()\n        self.assertFalse(os.path.exists(self.lock_path))\n\n    def test_unexpected_lockf_or_locking_err(self):\n        if POSIX_MODE:\n            mocked_function = 'certbot._internal.lock.fcntl.lockf'\n        else:\n            mocked_function = 'certbot._internal.lock.msvcrt.locking'\n        msg = 'hi there'\n        with mock.patch(mocked_function) as mock_lock:\n            mock_lock.side_effect = IOError(msg)\n            try:\n                self._call(self.lock_path)\n            except IOError as err:\n                self.assertTrue(msg in str(err))\n            else:  # pragma: no cover\n                self.fail('IOError not raised')\n\n    def test_unexpected_os_err(self):\n        if POSIX_MODE:\n            mock_function = 'certbot._internal.lock.filesystem.os.stat'\n        else:\n            mock_function = 'certbot._internal.lock.msvcrt.locking'\n        # The only expected errno are ENOENT and EACCES in lock module.\n        msg = 'hi there'\n        with mock.patch(mock_function) as mock_os:\n            mock_os.side_effect = OSError(msg)\n            try:\n                self._call(self.lock_path)\n            except OSError as err:\n                self.assertTrue(msg in str(err))\n            else:  # pragma: no cover\n                self.fail('OSError not raised')\n\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/log_test.py",
    "content": "\"\"\"Tests for certbot._internal.log.\"\"\"\nimport logging\nimport logging.handlers\nimport sys\nimport time\nimport unittest\n\nimport mock\nimport six\n\nfrom acme import messages\nfrom acme.magic_typing import Optional  # pylint: disable=unused-import, no-name-in-module\nfrom certbot import errors\nfrom certbot import util\nfrom certbot._internal import constants\nfrom certbot.compat import filesystem\nfrom certbot.compat import os\nfrom certbot.tests import util as test_util\n\n\nclass PreArgParseSetupTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.log.pre_arg_parse_setup.\"\"\"\n\n    @classmethod\n    def _call(cls, *args, **kwargs):  # pylint: disable=unused-argument\n        from certbot._internal.log import pre_arg_parse_setup\n        return pre_arg_parse_setup()\n\n    @mock.patch('certbot._internal.log.sys')\n    @mock.patch('certbot._internal.log.pre_arg_parse_except_hook')\n    @mock.patch('certbot._internal.log.logging.getLogger')\n    @mock.patch('certbot._internal.log.util.atexit_register')\n    def test_it(self, mock_register, mock_get, mock_except_hook, mock_sys):\n        mock_sys.argv = ['--debug']\n        mock_sys.version_info = sys.version_info\n        self._call()\n\n        mock_root_logger = mock_get()\n        mock_root_logger.setLevel.assert_called_once_with(logging.DEBUG)\n        self.assertEqual(mock_root_logger.addHandler.call_count, 2)\n\n        memory_handler = None  # type: Optional[logging.handlers.MemoryHandler]\n        for call in mock_root_logger.addHandler.call_args_list:\n            handler = call[0][0]\n            if memory_handler is None and isinstance(handler, logging.handlers.MemoryHandler):\n                memory_handler = handler\n                target = memory_handler.target  # type: ignore\n            else:\n                self.assertTrue(isinstance(handler, logging.StreamHandler))\n        self.assertTrue(\n            isinstance(target, logging.StreamHandler))\n\n        mock_register.assert_called_once_with(logging.shutdown)\n        mock_sys.excepthook(1, 2, 3)\n        mock_except_hook.assert_called_once_with(\n            memory_handler, 1, 2, 3, debug=True, log_path=mock.ANY)\n\n\nclass PostArgParseSetupTest(test_util.ConfigTestCase):\n    \"\"\"Tests for certbot._internal.log.post_arg_parse_setup.\"\"\"\n\n    @classmethod\n    def _call(cls, *args, **kwargs):\n        from certbot._internal.log import post_arg_parse_setup\n        return post_arg_parse_setup(*args, **kwargs)\n\n    def setUp(self):\n        super(PostArgParseSetupTest, self).setUp()\n        self.config.debug = False\n        self.config.max_log_backups = 1000\n        self.config.quiet = False\n        self.config.verbose_count = constants.CLI_DEFAULTS['verbose_count']\n        self.devnull = open(os.devnull, 'w')\n\n        from certbot._internal.log import ColoredStreamHandler\n        self.stream_handler = ColoredStreamHandler(six.StringIO())\n        from certbot._internal.log import MemoryHandler, TempHandler\n        self.temp_handler = TempHandler()\n        self.temp_path = self.temp_handler.path\n        self.memory_handler = MemoryHandler(self.temp_handler)\n        self.root_logger = mock.MagicMock(\n            handlers=[self.memory_handler, self.stream_handler])\n\n    def tearDown(self):\n        self.memory_handler.close()\n        self.stream_handler.close()\n        self.temp_handler.close()\n        self.devnull.close()\n        super(PostArgParseSetupTest, self).tearDown()\n\n    def test_common(self):\n        with mock.patch('certbot._internal.log.logging.getLogger') as mock_get_logger:\n            mock_get_logger.return_value = self.root_logger\n            except_hook_path = 'certbot._internal.log.post_arg_parse_except_hook'\n            with mock.patch(except_hook_path) as mock_except_hook:\n                with mock.patch('certbot._internal.log.sys') as mock_sys:\n                    mock_sys.version_info = sys.version_info\n                    self._call(self.config)\n\n        self.root_logger.removeHandler.assert_called_once_with(\n            self.memory_handler)\n        self.assertTrue(self.root_logger.addHandler.called)\n        self.assertTrue(os.path.exists(os.path.join(\n            self.config.logs_dir, 'letsencrypt.log')))\n        self.assertFalse(os.path.exists(self.temp_path))\n        mock_sys.excepthook(1, 2, 3)\n        mock_except_hook.assert_called_once_with(\n            1, 2, 3, debug=self.config.debug, log_path=self.config.logs_dir)\n\n        level = self.stream_handler.level\n        if self.config.quiet:\n            self.assertEqual(level, constants.QUIET_LOGGING_LEVEL)\n        else:\n            self.assertEqual(level, -self.config.verbose_count * 10)\n\n    def test_debug(self):\n        self.config.debug = True\n        self.test_common()\n\n    def test_quiet(self):\n        self.config.quiet = True\n        self.test_common()\n\n\nclass SetupLogFileHandlerTest(test_util.ConfigTestCase):\n    \"\"\"Tests for certbot._internal.log.setup_log_file_handler.\"\"\"\n\n    @classmethod\n    def _call(cls, *args, **kwargs):\n        from certbot._internal.log import setup_log_file_handler\n        return setup_log_file_handler(*args, **kwargs)\n\n    def setUp(self):\n        super(SetupLogFileHandlerTest, self).setUp()\n        self.config.max_log_backups = 42\n\n    @mock.patch('certbot._internal.main.logging.handlers.RotatingFileHandler')\n    def test_failure(self, mock_handler):\n        mock_handler.side_effect = IOError\n\n        try:\n            self._call(self.config, 'test.log', '%(message)s')\n        except errors.Error as err:\n            self.assertTrue('--logs-dir' in str(err))\n        else:  # pragma: no cover\n            self.fail('Error not raised.')\n\n    def test_success_with_rollover(self):\n        self._test_success_common(should_rollover=True)\n\n    def test_success_without_rollover(self):\n        self.config.max_log_backups = 0\n        self._test_success_common(should_rollover=False)\n\n    def _test_success_common(self, should_rollover):\n        log_file = 'test.log'\n        handler, log_path = self._call(self.config, log_file, '%(message)s')\n        handler.close()\n\n        self.assertEqual(handler.level, logging.DEBUG)\n        self.assertEqual(handler.formatter.converter, time.localtime)\n\n        expected_path = os.path.join(self.config.logs_dir, log_file)\n        self.assertEqual(log_path, expected_path)\n\n        backup_path = os.path.join(self.config.logs_dir, log_file + '.1')\n        self.assertEqual(os.path.exists(backup_path), should_rollover)\n\n    @mock.patch('certbot._internal.log.logging.handlers.RotatingFileHandler')\n    def test_max_log_backups_used(self, mock_handler):\n        self._call(self.config, 'test.log', '%(message)s')\n        backup_count = mock_handler.call_args[1]['backupCount']\n        self.assertEqual(self.config.max_log_backups, backup_count)\n\n\nclass ColoredStreamHandlerTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.log.ColoredStreamHandler\"\"\"\n\n    def setUp(self):\n        self.stream = six.StringIO()\n        self.stream.isatty = lambda: True\n        self.logger = logging.getLogger()\n        self.logger.setLevel(logging.DEBUG)\n\n        from certbot._internal.log import ColoredStreamHandler\n        self.handler = ColoredStreamHandler(self.stream)\n        self.logger.addHandler(self.handler)\n\n    def tearDown(self):\n        self.handler.close()\n\n    def test_format(self):\n        msg = 'I did a thing'\n        self.logger.debug(msg)\n        self.assertEqual(self.stream.getvalue(), '{0}\\n'.format(msg))\n\n    def test_format_and_red_level(self):\n        msg = 'I did another thing'\n        self.handler.red_level = logging.DEBUG\n        self.logger.debug(msg)\n\n        self.assertEqual(self.stream.getvalue(),\n                         '{0}{1}{2}\\n'.format(util.ANSI_SGR_RED,\n                                              msg,\n                                              util.ANSI_SGR_RESET))\n\n\nclass MemoryHandlerTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.log.MemoryHandler\"\"\"\n    def setUp(self):\n        self.logger = logging.getLogger(__name__)\n        self.logger.setLevel(logging.DEBUG)\n        self.msg = 'hi there'\n        self.stream = six.StringIO()\n\n        self.stream_handler = logging.StreamHandler(self.stream)\n        from certbot._internal.log import MemoryHandler\n        self.handler = MemoryHandler(self.stream_handler)\n        self.logger.addHandler(self.handler)\n\n    def tearDown(self):\n        self.handler.close()\n        self.stream_handler.close()\n\n    def test_flush(self):\n        self._test_log_debug()\n        self.handler.flush(force=True)\n        self.assertEqual(self.stream.getvalue(), self.msg + '\\n')\n\n    def test_not_flushed(self):\n        # By default, logging.ERROR messages and higher are flushed\n        self.logger.critical(self.msg)\n        self.handler.flush()\n        self.assertEqual(self.stream.getvalue(), '')\n\n    def test_target_reset(self):\n        self._test_log_debug()\n\n        new_stream = six.StringIO()\n        new_stream_handler = logging.StreamHandler(new_stream)\n        self.handler.setTarget(new_stream_handler)\n        self.handler.flush(force=True)\n        self.assertEqual(self.stream.getvalue(), '')\n        self.assertEqual(new_stream.getvalue(), self.msg + '\\n')\n        new_stream_handler.close()\n\n    def _test_log_debug(self):\n        self.logger.debug(self.msg)\n\n\nclass TempHandlerTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.log.TempHandler.\"\"\"\n    def setUp(self):\n        self.closed = False\n        from certbot._internal.log import TempHandler\n        self.handler = TempHandler()\n\n    def tearDown(self):\n        self.handler.close()\n\n    def test_permissions(self):\n        self.assertTrue(filesystem.check_permissions(self.handler.path, 0o600))\n\n    def test_delete(self):\n        self.handler.close()\n        self.assertFalse(os.path.exists(self.handler.path))\n\n    def test_no_delete(self):\n        self.handler.emit(mock.MagicMock())\n        self.handler.close()\n        self.assertTrue(os.path.exists(self.handler.path))\n        os.remove(self.handler.path)\n\n\nclass PreArgParseExceptHookTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.log.pre_arg_parse_except_hook.\"\"\"\n    @classmethod\n    def _call(cls, *args, **kwargs):\n        from certbot._internal.log import pre_arg_parse_except_hook\n        return pre_arg_parse_except_hook(*args, **kwargs)\n\n    @mock.patch('certbot._internal.log.post_arg_parse_except_hook')\n    def test_it(self, mock_post_arg_parse_except_hook):\n        memory_handler = mock.MagicMock()\n        args = ('some', 'args',)\n        kwargs = {'some': 'kwargs'}\n\n        self._call(memory_handler, *args, **kwargs)\n\n        mock_post_arg_parse_except_hook.assert_called_once_with(\n            *args, **kwargs)\n        memory_handler.flush.assert_called_once_with(force=True)\n\n\nclass PostArgParseExceptHookTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.log.post_arg_parse_except_hook.\"\"\"\n    @classmethod\n    def _call(cls, *args, **kwargs):\n        from certbot._internal.log import post_arg_parse_except_hook\n        return post_arg_parse_except_hook(*args, **kwargs)\n\n    def setUp(self):\n        self.error_msg = 'test error message'\n        self.log_path = 'foo.log'\n\n    def test_base_exception(self):\n        exc_type = KeyboardInterrupt\n        mock_logger, output = self._test_common(exc_type, debug=False)\n        self._assert_exception_logged(mock_logger.error, exc_type)\n        self._assert_logfile_output(output)\n\n    def test_debug(self):\n        exc_type = ValueError\n        mock_logger, output = self._test_common(exc_type, debug=True)\n        self._assert_exception_logged(mock_logger.error, exc_type)\n        self._assert_logfile_output(output)\n\n    def test_custom_error(self):\n        exc_type = errors.PluginError\n        mock_logger, output = self._test_common(exc_type, debug=False)\n        self._assert_exception_logged(mock_logger.debug, exc_type)\n        self._assert_quiet_output(mock_logger, output)\n\n    def test_acme_error(self):\n        # Get an arbitrary error code\n        acme_code = next(six.iterkeys(messages.ERROR_CODES))\n\n        def get_acme_error(msg):\n            \"\"\"Wraps ACME errors so the constructor takes only a msg.\"\"\"\n            return messages.Error.with_code(acme_code, detail=msg)\n\n        mock_logger, output = self._test_common(get_acme_error, debug=False)\n        self._assert_exception_logged(mock_logger.debug, messages.Error)\n        self._assert_quiet_output(mock_logger, output)\n        self.assertFalse(messages.ERROR_PREFIX in output)\n\n    def test_other_error(self):\n        exc_type = ValueError\n        mock_logger, output = self._test_common(exc_type, debug=False)\n        self._assert_exception_logged(mock_logger.debug, exc_type)\n        self._assert_quiet_output(mock_logger, output)\n\n    def _test_common(self, error_type, debug):\n        \"\"\"Returns the mocked logger and stderr output.\"\"\"\n        mock_err = six.StringIO()\n\n        def write_err(*args, **unused_kwargs):\n            \"\"\"Write error to mock_err.\"\"\"\n            mock_err.write(args[0])\n\n        try:\n            raise error_type(self.error_msg)\n        except BaseException:\n            exc_info = sys.exc_info()\n            with mock.patch('certbot._internal.log.logger') as mock_logger:\n                mock_logger.error.side_effect = write_err\n                with mock.patch('certbot._internal.log.sys.stderr', mock_err):\n                    try:\n                        self._call(\n                            *exc_info, debug=debug, log_path=self.log_path)\n                    except SystemExit as exit_err:\n                        mock_err.write(str(exit_err))\n                    else:  # pragma: no cover\n                        self.fail('SystemExit not raised.')\n\n        output = mock_err.getvalue()\n        return mock_logger, output\n\n    def _assert_exception_logged(self, log_func, exc_type):\n        self.assertTrue(log_func.called)\n        call_kwargs = log_func.call_args[1]\n        self.assertTrue('exc_info' in call_kwargs)\n\n        actual_exc_info = call_kwargs['exc_info']\n        expected_exc_info = (exc_type, mock.ANY, mock.ANY)\n        self.assertEqual(actual_exc_info, expected_exc_info)\n\n    def _assert_logfile_output(self, output):\n        self.assertTrue('Please see the logfile' in output)\n        self.assertTrue(self.log_path in output)\n\n    def _assert_quiet_output(self, mock_logger, output):\n        self.assertFalse(mock_logger.exception.called)\n        self.assertTrue(mock_logger.debug.called)\n        self.assertTrue(self.error_msg in output)\n\n\nclass ExitWithLogPathTest(test_util.TempDirTestCase):\n    \"\"\"Tests for certbot._internal.log.exit_with_log_path.\"\"\"\n    @classmethod\n    def _call(cls, *args, **kwargs):\n        from certbot._internal.log import exit_with_log_path\n        return exit_with_log_path(*args, **kwargs)\n\n    def test_log_file(self):\n        log_file = os.path.join(self.tempdir, 'test.log')\n        open(log_file, 'w').close()\n\n        err_str = self._test_common(log_file)\n        self.assertTrue('logfiles' not in err_str)\n        self.assertTrue(log_file in err_str)\n\n    def test_log_dir(self):\n        err_str = self._test_common(self.tempdir)\n        self.assertTrue('logfiles' in err_str)\n        self.assertTrue(self.tempdir in err_str)\n\n    # pylint: disable=inconsistent-return-statements\n    def _test_common(self, *args, **kwargs):\n        try:\n            self._call(*args, **kwargs)\n        except SystemExit as err:\n            return str(err)\n        self.fail('SystemExit was not raised.')  # pragma: no cover\n\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/main_test.py",
    "content": "# coding=utf-8\n\"\"\"Tests for certbot._internal.main.\"\"\"\n# pylint: disable=too-many-lines\nfrom __future__ import print_function\n\nimport datetime\nimport itertools\nimport json\nimport shutil\nimport sys\nimport tempfile\nimport traceback\nimport unittest\n\nimport josepy as jose\nimport mock\nimport pytz\nimport six\nfrom six.moves import reload_module  # pylint: disable=import-error\n\nfrom acme.magic_typing import List  # pylint: disable=unused-import, no-name-in-module\nfrom certbot import crypto_util\nfrom certbot import errors\nfrom certbot import interfaces  # pylint: disable=unused-import\nfrom certbot import util\nfrom certbot._internal import account\nfrom certbot._internal import cli\nfrom certbot._internal import configuration\nfrom certbot._internal import constants\nfrom certbot._internal import main\nfrom certbot._internal import updater\nfrom certbot._internal.plugins import disco\nfrom certbot._internal.plugins import manual\nfrom certbot._internal.plugins import null\nfrom certbot.compat import filesystem\nfrom certbot.compat import os\nfrom certbot.plugins import enhancements\nimport certbot.tests.util as test_util\n\nCERT_PATH = test_util.vector_path('cert_512.pem')\nCERT = test_util.vector_path('cert_512.pem')\nCSR = test_util.vector_path('csr_512.der')\nKEY = test_util.vector_path('rsa256_key.pem')\nJWK = jose.JWKRSA.load(test_util.load_vector('rsa512_key.pem'))\nRSA2048_KEY_PATH = test_util.vector_path('rsa2048_key.pem')\nSS_CERT_PATH = test_util.vector_path('cert_2048.pem')\n\n\nclass TestHandleIdenticalCerts(unittest.TestCase):\n    \"\"\"Test for certbot._internal.main._handle_identical_cert_request\"\"\"\n    def test_handle_identical_cert_request_pending(self):\n        mock_lineage = mock.Mock()\n        mock_lineage.ensure_deployed.return_value = False\n        # pylint: disable=protected-access\n        ret = main._handle_identical_cert_request(mock.Mock(), mock_lineage)\n        self.assertEqual(ret, (\"reinstall\", mock_lineage))\n\n\nclass RunTest(test_util.ConfigTestCase):\n    \"\"\"Tests for certbot._internal.main.run.\"\"\"\n\n    def setUp(self):\n        super(RunTest, self).setUp()\n        self.domain = 'example.org'\n        patches = [\n            mock.patch('certbot._internal.main._get_and_save_cert'),\n            mock.patch('certbot._internal.main.display_ops.success_installation'),\n            mock.patch('certbot._internal.main.display_ops.success_renewal'),\n            mock.patch('certbot._internal.main._init_le_client'),\n            mock.patch('certbot._internal.main._suggest_donation_if_appropriate'),\n            mock.patch('certbot._internal.main._report_new_cert'),\n            mock.patch('certbot._internal.main._find_cert')]\n\n        self.mock_auth = patches[0].start()\n        self.mock_success_installation = patches[1].start()\n        self.mock_success_renewal = patches[2].start()\n        self.mock_init = patches[3].start()\n        self.mock_suggest_donation = patches[4].start()\n        self.mock_report_cert = patches[5].start()\n        self.mock_find_cert = patches[6].start()\n        for patch in patches:\n            self.addCleanup(patch.stop)\n\n    def _call(self):\n        args = '-a webroot -i null -d {0}'.format(self.domain).split()\n        plugins = disco.PluginsRegistry.find_all()\n        config = configuration.NamespaceConfig(\n            cli.prepare_and_parse_args(plugins, args))\n\n        from certbot._internal.main import run\n        run(config, plugins)\n\n    def test_newcert_success(self):\n        self.mock_auth.return_value = mock.Mock()\n        self.mock_find_cert.return_value = True, None\n        self._call()\n        self.mock_success_installation.assert_called_once_with([self.domain])\n\n    def test_reinstall_success(self):\n        self.mock_auth.return_value = mock.Mock()\n        self.mock_find_cert.return_value = False, mock.Mock()\n        self._call()\n        self.mock_success_installation.assert_called_once_with([self.domain])\n\n    def test_renewal_success(self):\n        self.mock_auth.return_value = mock.Mock()\n        self.mock_find_cert.return_value = True, mock.Mock()\n        self._call()\n        self.mock_success_renewal.assert_called_once_with([self.domain])\n\n    @mock.patch('certbot._internal.main.plug_sel.choose_configurator_plugins')\n    def test_run_enhancement_not_supported(self, mock_choose):\n        mock_choose.return_value = (null.Installer(self.config, \"null\"), None)\n        plugins = disco.PluginsRegistry.find_all()\n        self.config.auto_hsts = True\n        self.assertRaises(errors.NotSupportedError,\n                          main.run,\n                          self.config, plugins)\n\n\nclass CertonlyTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.main.certonly.\"\"\"\n\n    def setUp(self):\n        self.get_utility_patch = test_util.patch_get_utility()\n        self.mock_get_utility = self.get_utility_patch.start()\n\n    def tearDown(self):\n        self.get_utility_patch.stop()\n\n    def _call(self, args):\n        plugins = disco.PluginsRegistry.find_all()\n        config = configuration.NamespaceConfig(\n            cli.prepare_and_parse_args(plugins, args))\n\n        with mock.patch('certbot._internal.main._init_le_client') as mock_init:\n            with mock.patch('certbot._internal.main._suggest_donation_if_appropriate'):\n                main.certonly(config, plugins)\n\n        return mock_init()  # returns the client\n\n    @mock.patch('certbot._internal.main._find_cert')\n    @mock.patch('certbot._internal.main._get_and_save_cert')\n    @mock.patch('certbot._internal.main._report_new_cert')\n    def test_no_reinstall_text_pause(self, unused_report, mock_auth,\n        mock_find_cert):\n        mock_notification = self.mock_get_utility().notification\n        mock_notification.side_effect = self._assert_no_pause\n        mock_auth.return_value = mock.Mock()\n        mock_find_cert.return_value = False, None\n        self._call('certonly --webroot -d example.com'.split())\n\n    def _assert_no_pause(self, message, pause=True):  # pylint: disable=unused-argument\n        self.assertFalse(pause)\n\n    @mock.patch('certbot._internal.cert_manager.lineage_for_certname')\n    @mock.patch('certbot._internal.cert_manager.domains_for_certname')\n    @mock.patch('certbot._internal.renewal.renew_cert')\n    @mock.patch('certbot._internal.main._report_new_cert')\n    def test_find_lineage_for_domains_and_certname(self, mock_report_cert,\n        mock_renew_cert, mock_domains, mock_lineage):\n        domains = ['example.com', 'test.org']\n        mock_domains.return_value = domains\n        mock_lineage.names.return_value = domains\n        self._call(('certonly --webroot -d example.com -d test.org '\n            '--cert-name example.com').split())\n        self.assertTrue(mock_lineage.call_count == 1)\n        self.assertTrue(mock_domains.call_count == 1)\n        self.assertTrue(mock_renew_cert.call_count == 1)\n        self.assertTrue(mock_report_cert.call_count == 1)\n\n        # user confirms updating lineage with new domains\n        self._call(('certonly --webroot -d example.com -d test.com '\n            '--cert-name example.com').split())\n        self.assertTrue(mock_lineage.call_count == 2)\n        self.assertTrue(mock_domains.call_count == 2)\n        self.assertTrue(mock_renew_cert.call_count == 2)\n        self.assertTrue(mock_report_cert.call_count == 2)\n\n        # error in _ask_user_to_confirm_new_names\n        self.mock_get_utility().yesno.return_value = False\n        self.assertRaises(errors.ConfigurationError, self._call,\n            ('certonly --webroot -d example.com -d test.com --cert-name example.com').split())\n\n    @mock.patch('certbot._internal.cert_manager.domains_for_certname')\n    @mock.patch('certbot.display.ops.choose_names')\n    @mock.patch('certbot._internal.cert_manager.lineage_for_certname')\n    @mock.patch('certbot._internal.main._report_new_cert')\n    def test_find_lineage_for_domains_new_certname(self, mock_report_cert,\n        mock_lineage, mock_choose_names, mock_domains_for_certname):\n        mock_lineage.return_value = None\n\n        # no lineage with this name but we specified domains so create a new cert\n        self._call(('certonly --webroot -d example.com -d test.com '\n            '--cert-name example.com').split())\n        self.assertTrue(mock_lineage.call_count == 1)\n        self.assertTrue(mock_report_cert.call_count == 1)\n\n        # no lineage with this name and we didn't give domains\n        mock_choose_names.return_value = [\"somename\"]\n        mock_domains_for_certname.return_value = None\n        self._call(('certonly --webroot --cert-name example.com').split())\n        self.assertTrue(mock_choose_names.called)\n\nclass FindDomainsOrCertnameTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.main._find_domains_or_certname.\"\"\"\n\n    @mock.patch('certbot.display.ops.choose_names')\n    def test_display_ops(self, mock_choose_names):\n        mock_config = mock.Mock(domains=None, certname=None)\n        mock_choose_names.return_value = \"domainname\"\n        # pylint: disable=protected-access\n        self.assertEqual(main._find_domains_or_certname(mock_config, None),\n            (\"domainname\", None))\n\n    @mock.patch('certbot.display.ops.choose_names')\n    def test_no_results(self, mock_choose_names):\n        mock_config = mock.Mock(domains=None, certname=None)\n        mock_choose_names.return_value = []\n        # pylint: disable=protected-access\n        self.assertRaises(errors.Error, main._find_domains_or_certname, mock_config, None)\n\n    @mock.patch('certbot._internal.cert_manager.domains_for_certname')\n    def test_grab_domains(self, mock_domains):\n        mock_config = mock.Mock(domains=None, certname=\"one.com\")\n        mock_domains.return_value = [\"one.com\", \"two.com\"]\n        # pylint: disable=protected-access\n        self.assertEqual(main._find_domains_or_certname(mock_config, None),\n            ([\"one.com\", \"two.com\"], \"one.com\"))\n\n\nclass RevokeTest(test_util.TempDirTestCase):\n    \"\"\"Tests for certbot._internal.main.revoke.\"\"\"\n\n    def setUp(self):\n        super(RevokeTest, self).setUp()\n\n        shutil.copy(CERT_PATH, self.tempdir)\n        self.tmp_cert_path = os.path.abspath(os.path.join(self.tempdir,\n            'cert_512.pem'))\n        with open(self.tmp_cert_path, 'r') as f:\n            self.tmp_cert = (self.tmp_cert_path, f.read())\n\n        patches = [\n            mock.patch('acme.client.BackwardsCompatibleClientV2'),\n            mock.patch('certbot._internal.client.Client'),\n            mock.patch('certbot._internal.main._determine_account'),\n            mock.patch('certbot._internal.main.display_ops.success_revocation')\n        ]\n        self.mock_acme_client = patches[0].start()\n        patches[1].start()\n        self.mock_determine_account = patches[2].start()\n        self.mock_success_revoke = patches[3].start()\n        for patch in patches:\n            self.addCleanup(patch.stop)\n\n        from certbot._internal.account import Account\n\n        self.regr = mock.MagicMock()\n        self.meta = Account.Meta(\n            creation_host=\"test.certbot.org\",\n            creation_dt=datetime.datetime(\n                2015, 7, 4, 14, 4, 10, tzinfo=pytz.UTC))\n        self.acc = Account(self.regr, JWK, self.meta)\n\n        self.mock_determine_account.return_value = (self.acc, None)\n\n    def _call(self, args=None):\n        if not args:\n            args = 'revoke --cert-path={0} '\n            args = args.format(self.tmp_cert_path).split()\n        plugins = disco.PluginsRegistry.find_all()\n        config = configuration.NamespaceConfig(\n            cli.prepare_and_parse_args(plugins, args))\n\n        from certbot._internal.main import revoke\n        revoke(config, plugins)\n\n    @mock.patch('certbot._internal.main._delete_if_appropriate')\n    @mock.patch('certbot._internal.main.client.acme_client')\n    def test_revoke_with_reason(self, mock_acme_client,\n            mock_delete_if_appropriate):\n        mock_delete_if_appropriate.return_value = False\n        mock_revoke = mock_acme_client.BackwardsCompatibleClientV2().revoke\n        expected = []\n        for reason, code in constants.REVOCATION_REASONS.items():\n            args = 'revoke --cert-path={0} --reason {1}'.format(self.tmp_cert_path, reason).split()\n            self._call(args)\n            expected.append(mock.call(mock.ANY, code))\n            args = 'revoke --cert-path={0} --reason {1}'.format(self.tmp_cert_path,\n                    reason.upper()).split()\n            self._call(args)\n            expected.append(mock.call(mock.ANY, code))\n        self.assertEqual(expected, mock_revoke.call_args_list)\n\n    @mock.patch('certbot._internal.main._delete_if_appropriate')\n    @mock.patch('certbot._internal.storage.cert_path_for_cert_name')\n    def test_revoke_by_certname(self, mock_cert_path_for_cert_name,\n            mock_delete_if_appropriate):\n        args = 'revoke --cert-name=example.com'.split()\n        mock_cert_path_for_cert_name.return_value = self.tmp_cert\n        mock_delete_if_appropriate.return_value = False\n        self._call(args)\n        self.mock_success_revoke.assert_called_once_with(self.tmp_cert_path)\n\n    @mock.patch('certbot._internal.main._delete_if_appropriate')\n    def test_revocation_success(self, mock_delete_if_appropriate):\n        self._call()\n        mock_delete_if_appropriate.return_value = False\n        self.mock_success_revoke.assert_called_once_with(self.tmp_cert_path)\n\n    def test_revocation_error(self):\n        from acme import errors as acme_errors\n        self.mock_acme_client.side_effect = acme_errors.ClientError()\n        self.assertRaises(acme_errors.ClientError, self._call)\n        self.mock_success_revoke.assert_not_called()\n\n    @mock.patch('certbot._internal.main._delete_if_appropriate')\n    @mock.patch('certbot._internal.cert_manager.delete')\n    @test_util.patch_get_utility()\n    def test_revocation_with_prompt(self, mock_get_utility,\n            mock_delete, mock_delete_if_appropriate):\n        mock_get_utility().yesno.return_value = False\n        mock_delete_if_appropriate.return_value = False\n        self._call()\n        self.assertFalse(mock_delete.called)\n\nclass DeleteIfAppropriateTest(test_util.ConfigTestCase):\n    \"\"\"Tests for certbot._internal.main._delete_if_appropriate \"\"\"\n\n    def _call(self, mock_config):\n        from certbot._internal.main import _delete_if_appropriate\n        _delete_if_appropriate(mock_config)\n\n    def _test_delete_opt_out_common(self, mock_get_utility):\n        with mock.patch('certbot._internal.cert_manager.delete') as mock_delete:\n            self._call(self.config)\n        mock_delete.assert_not_called()\n        self.assertTrue(mock_get_utility().add_message.called)\n\n    @test_util.patch_get_utility()\n    def test_delete_flag_opt_out(self, mock_get_utility):\n        self.config.delete_after_revoke = False\n        self._test_delete_opt_out_common(mock_get_utility)\n\n    @test_util.patch_get_utility()\n    def test_delete_prompt_opt_out(self, mock_get_utility):\n        util_mock = mock_get_utility()\n        util_mock.yesno.return_value = False\n        self._test_delete_opt_out_common(mock_get_utility)\n\n    @mock.patch('certbot._internal.storage.renewal_file_for_certname')\n    @mock.patch('certbot._internal.cert_manager.delete')\n    @mock.patch('certbot._internal.cert_manager.match_and_check_overlaps')\n    @mock.patch('certbot._internal.storage.full_archive_path')\n    @mock.patch('certbot._internal.cert_manager.cert_path_to_lineage')\n    @test_util.patch_get_utility()\n    def test_overlapping_archive_dirs(self, mock_get_utility,\n            mock_cert_path_to_lineage, mock_archive,\n            mock_match_and_check_overlaps, mock_delete,\n            mock_renewal_file_for_certname):\n        # pylint: disable = unused-argument\n        config = self.config\n        config.cert_path = \"/some/reasonable/path\"\n        config.certname = \"\"\n        mock_cert_path_to_lineage.return_value = \"example.com\"\n        mock_match_and_check_overlaps.side_effect = errors.OverlappingMatchFound()\n        self._call(config)\n        mock_delete.assert_not_called()\n\n    @mock.patch('certbot._internal.storage.renewal_file_for_certname')\n    @mock.patch('certbot._internal.cert_manager.match_and_check_overlaps')\n    @mock.patch('certbot._internal.storage.full_archive_path')\n    @mock.patch('certbot._internal.cert_manager.delete')\n    @mock.patch('certbot._internal.cert_manager.cert_path_to_lineage')\n    @test_util.patch_get_utility()\n    def test_cert_path_only(self, mock_get_utility,\n            mock_cert_path_to_lineage, mock_delete, mock_archive,\n            mock_overlapping_archive_dirs, mock_renewal_file_for_certname):\n        # pylint: disable = unused-argument\n        config = self.config\n        config.cert_path = \"/some/reasonable/path\"\n        config.certname = \"\"\n        mock_cert_path_to_lineage.return_value = \"example.com\"\n        mock_overlapping_archive_dirs.return_value = False\n        self._call(config)\n        self.assertEqual(mock_delete.call_count, 1)\n\n    @mock.patch('certbot._internal.storage.renewal_file_for_certname')\n    @mock.patch('certbot._internal.cert_manager.match_and_check_overlaps')\n    @mock.patch('certbot._internal.storage.full_archive_path')\n    @mock.patch('certbot._internal.cert_manager.cert_path_to_lineage')\n    @mock.patch('certbot._internal.cert_manager.delete')\n    @test_util.patch_get_utility()\n    def test_noninteractive_deletion(self, mock_get_utility, mock_delete,\n            mock_cert_path_to_lineage, mock_full_archive_dir,\n            mock_match_and_check_overlaps, mock_renewal_file_for_certname):\n        # pylint: disable = unused-argument\n        config = self.config\n        config.namespace.noninteractive_mode = True\n        config.cert_path = \"/some/reasonable/path\"\n        config.certname = \"\"\n        mock_cert_path_to_lineage.return_value = \"example.com\"\n        mock_full_archive_dir.return_value = \"\"\n        mock_match_and_check_overlaps.return_value = \"\"\n        self._call(config)\n        self.assertEqual(mock_delete.call_count, 1)\n\n    @mock.patch('certbot._internal.storage.renewal_file_for_certname')\n    @mock.patch('certbot._internal.cert_manager.match_and_check_overlaps')\n    @mock.patch('certbot._internal.storage.full_archive_path')\n    @mock.patch('certbot._internal.cert_manager.cert_path_to_lineage')\n    @mock.patch('certbot._internal.cert_manager.delete')\n    @test_util.patch_get_utility()\n    def test_opt_in_deletion(self, mock_get_utility, mock_delete,\n            mock_cert_path_to_lineage, mock_full_archive_dir,\n            mock_match_and_check_overlaps, mock_renewal_file_for_certname):\n        # pylint: disable = unused-argument\n        config = self.config\n        config.namespace.delete_after_revoke = True\n        config.cert_path = \"/some/reasonable/path\"\n        config.certname = \"\"\n        mock_cert_path_to_lineage.return_value = \"example.com\"\n        mock_full_archive_dir.return_value = \"\"\n        mock_match_and_check_overlaps.return_value = \"\"\n        self._call(config)\n        self.assertEqual(mock_delete.call_count, 1)\n        self.assertFalse(mock_get_utility().yesno.called)\n\n\nclass DetermineAccountTest(test_util.ConfigTestCase):\n    \"\"\"Tests for certbot._internal.main._determine_account.\"\"\"\n\n    def setUp(self):\n        super(DetermineAccountTest, self).setUp()\n        self.config.account = None\n        self.config.email = None\n        self.config.register_unsafely_without_email = False\n        self.accs = [mock.MagicMock(id='x'), mock.MagicMock(id='y')]\n        self.account_storage = account.AccountMemoryStorage()\n        # For use in saving accounts: fake out the new_authz URL.\n        self.mock_client = mock.MagicMock()\n        self.mock_client.directory.new_authz = \"hi\"\n\n    def _call(self):\n        # pylint: disable=protected-access\n        from certbot._internal.main import _determine_account\n        with mock.patch('certbot._internal.main.account.AccountFileStorage') as mock_storage:\n            mock_storage.return_value = self.account_storage\n            return _determine_account(self.config)\n\n    def test_args_account_set(self):\n        self.account_storage.save(self.accs[1], self.mock_client)\n        self.config.account = self.accs[1].id\n        self.assertEqual((self.accs[1], None), self._call())\n        self.assertEqual(self.accs[1].id, self.config.account)\n        self.assertTrue(self.config.email is None)\n\n    def test_single_account(self):\n        self.account_storage.save(self.accs[0], self.mock_client)\n        self.assertEqual((self.accs[0], None), self._call())\n        self.assertEqual(self.accs[0].id, self.config.account)\n        self.assertTrue(self.config.email is None)\n\n    @mock.patch('certbot._internal.client.display_ops.choose_account')\n    def test_multiple_accounts(self, mock_choose_accounts):\n        for acc in self.accs:\n            self.account_storage.save(acc, self.mock_client)\n        mock_choose_accounts.return_value = self.accs[1]\n        self.assertEqual((self.accs[1], None), self._call())\n        self.assertEqual(\n            set(mock_choose_accounts.call_args[0][0]), set(self.accs))\n        self.assertEqual(self.accs[1].id, self.config.account)\n        self.assertTrue(self.config.email is None)\n\n    @mock.patch('certbot._internal.client.display_ops.get_email')\n    def test_no_accounts_no_email(self, mock_get_email):\n        mock_get_email.return_value = 'foo@bar.baz'\n\n        with mock.patch('certbot._internal.main.client') as client:\n            client.register.return_value = (\n                self.accs[0], mock.sentinel.acme)\n            self.assertEqual((self.accs[0], mock.sentinel.acme), self._call())\n        client.register.assert_called_once_with(\n            self.config, self.account_storage, tos_cb=mock.ANY)\n\n        self.assertEqual(self.accs[0].id, self.config.account)\n        self.assertEqual('foo@bar.baz', self.config.email)\n\n    def test_no_accounts_email(self):\n        self.config.email = 'other email'\n        with mock.patch('certbot._internal.main.client') as client:\n            client.register.return_value = (self.accs[1], mock.sentinel.acme)\n            self._call()\n        self.assertEqual(self.accs[1].id, self.config.account)\n        self.assertEqual('other email', self.config.email)\n\n\nclass MainTest(test_util.ConfigTestCase):\n    \"\"\"Tests for different commands.\"\"\"\n\n    def setUp(self):\n        super(MainTest, self).setUp()\n\n        filesystem.mkdir(self.config.logs_dir)\n        self.standard_args = ['--config-dir', self.config.config_dir,\n                              '--work-dir', self.config.work_dir,\n                              '--logs-dir', self.config.logs_dir, '--text']\n\n        self.mock_sleep = mock.patch('time.sleep').start()\n\n    def tearDown(self):\n        # Reset globals in cli\n        reload_module(cli)\n\n        super(MainTest, self).tearDown()\n\n    def _call(self, args, stdout=None, mockisfile=False):\n        \"\"\"Run the cli with output streams, actual client and optionally\n        os.path.isfile() mocked out\"\"\"\n\n        if mockisfile:\n            orig_open = os.path.isfile\n\n            def mock_isfile(fn, *args, **kwargs):  # pylint: disable=unused-argument\n                \"\"\"Mock os.path.isfile()\"\"\"\n                if (fn.endswith(\"cert\") or\n                        fn.endswith(\"chain\") or\n                        fn.endswith(\"privkey\")):\n                    return True\n                return orig_open(fn)\n\n            with mock.patch(\"certbot.compat.os.path.isfile\") as mock_if:\n                mock_if.side_effect = mock_isfile\n                with mock.patch('certbot._internal.main.client') as client:\n                    ret, stdout, stderr = self._call_no_clientmock(args, stdout)\n                    return ret, stdout, stderr, client\n        else:\n            with mock.patch('certbot._internal.main.client') as client:\n                ret, stdout, stderr = self._call_no_clientmock(args, stdout)\n                return ret, stdout, stderr, client\n\n    def _call_no_clientmock(self, args, stdout=None):\n        \"Run the client with output streams mocked out\"\n        args = self.standard_args + args\n\n        toy_stdout = stdout if stdout else six.StringIO()\n        with mock.patch('certbot._internal.main.sys.stdout', new=toy_stdout):\n            with mock.patch('certbot._internal.main.sys.stderr') as stderr:\n                with mock.patch(\"certbot.util.atexit\"):\n                    ret = main.main(args[:])  # NOTE: parser can alter its args!\n        return ret, toy_stdout, stderr\n\n    def test_no_flags(self):\n        with mock.patch('certbot._internal.main.run') as mock_run:\n            self._call([])\n            self.assertEqual(1, mock_run.call_count)\n\n    def test_version_string_program_name(self):\n        toy_out = six.StringIO()\n        toy_err = six.StringIO()\n        with mock.patch('certbot._internal.main.sys.stdout', new=toy_out):\n            with mock.patch('certbot._internal.main.sys.stderr', new=toy_err):\n                try:\n                    main.main([\"--version\"])\n                except SystemExit:\n                    pass\n                finally:\n                    output = toy_out.getvalue() or toy_err.getvalue()\n                    self.assertTrue(\"certbot\" in output, \"Output is {0}\".format(output))\n\n    def _cli_missing_flag(self, args, message):\n        \"Ensure that a particular error raises a missing cli flag error containing message\"\n        exc = None\n        try:\n            with mock.patch('certbot._internal.main.sys.stderr'):\n                main.main(self.standard_args + args[:])  # NOTE: parser can alter its args!\n        except errors.MissingCommandlineFlag as exc_:\n            exc = exc_\n            self.assertTrue(message in str(exc))\n        self.assertTrue(exc is not None)\n\n    @mock.patch('certbot._internal.log.post_arg_parse_setup')\n    def test_noninteractive(self, _):\n        args = ['-n', 'certonly']\n        self._cli_missing_flag(args, \"specify a plugin\")\n        args.extend(['--standalone', '-d', 'eg.is'])\n        self._cli_missing_flag(args, \"register before running\")\n\n    @mock.patch('certbot._internal.log.post_arg_parse_setup')\n    @mock.patch('certbot._internal.main._report_new_cert')\n    @mock.patch('certbot._internal.main.client.acme_client.Client')\n    @mock.patch('certbot._internal.main._determine_account')\n    @mock.patch('certbot._internal.main.client.Client.obtain_and_enroll_certificate')\n    @mock.patch('certbot._internal.main._get_and_save_cert')\n    def test_user_agent(self, gsc, _obt, det, _client, _, __):\n        # Normally the client is totally mocked out, but here we need more\n        # arguments to automate it...\n        args = [\"--standalone\", \"certonly\", \"-m\", \"none@none.com\",\n                \"-d\", \"example.com\", '--agree-tos'] + self.standard_args\n        det.return_value = mock.MagicMock(), None\n        gsc.return_value = mock.MagicMock()\n\n        with mock.patch('certbot._internal.main.client.acme_client.ClientNetwork') as acme_net:\n            self._call_no_clientmock(args)\n            os_ver = util.get_os_info_ua()\n            ua = acme_net.call_args[1][\"user_agent\"]\n            self.assertTrue(os_ver in ua)\n            import platform\n            plat = platform.platform()\n            if \"linux\" in plat.lower():\n                self.assertTrue(util.get_os_info_ua() in ua)\n\n        with mock.patch('certbot._internal.main.client.acme_client.ClientNetwork') as acme_net:\n            ua = \"bandersnatch\"\n            args += [\"--user-agent\", ua]\n            self._call_no_clientmock(args)\n            acme_net.assert_called_once_with(mock.ANY, account=mock.ANY, verify_ssl=True,\n                user_agent=ua)\n\n    @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins')\n    @mock.patch('certbot._internal.main.plug_sel.pick_installer')\n    def test_installer_selection(self, mock_pick_installer, _rec):\n        self._call(['install', '--domains', 'foo.bar', '--cert-path', 'cert',\n                    '--key-path', 'privkey', '--chain-path', 'chain'], mockisfile=True)\n        self.assertEqual(mock_pick_installer.call_count, 1)\n\n    @mock.patch('certbot._internal.main._install_cert')\n    @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins')\n    @mock.patch('certbot._internal.main.plug_sel.pick_installer')\n    def test_installer_certname(self, _inst, _rec, mock_install):\n        mock_lineage = mock.MagicMock(cert_path=test_util.temp_join('cert'),\n                                      chain_path=test_util.temp_join('chain'),\n                                      fullchain_path=test_util.temp_join('chain'),\n                                      key_path=test_util.temp_join('privkey'))\n\n        with mock.patch(\"certbot._internal.cert_manager.lineage_for_certname\") as mock_getlin:\n            mock_getlin.return_value = mock_lineage\n            self._call(['install', '--cert-name', 'whatever'], mockisfile=True)\n            call_config = mock_install.call_args[0][0]\n            self.assertEqual(call_config.cert_path, test_util.temp_join('cert'))\n            self.assertEqual(call_config.fullchain_path, test_util.temp_join('chain'))\n            self.assertEqual(call_config.key_path, test_util.temp_join('privkey'))\n\n    @mock.patch('certbot._internal.log.post_arg_parse_setup')\n    @mock.patch('certbot._internal.main._install_cert')\n    @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins')\n    @mock.patch('certbot._internal.main.plug_sel.pick_installer')\n    def test_installer_param_override(self, _inst, _rec, mock_install, _):\n        mock_lineage = mock.MagicMock(cert_path=test_util.temp_join('cert'),\n                                      chain_path=test_util.temp_join('chain'),\n                                      fullchain_path=test_util.temp_join('chain'),\n                                      key_path=test_util.temp_join('privkey'))\n        with mock.patch(\"certbot._internal.cert_manager.lineage_for_certname\") as mock_getlin:\n            mock_getlin.return_value = mock_lineage\n            self._call(['install', '--cert-name', 'whatever',\n                        '--key-path', test_util.temp_join('overriding_privkey')], mockisfile=True)\n            call_config = mock_install.call_args[0][0]\n            self.assertEqual(call_config.cert_path, test_util.temp_join('cert'))\n            self.assertEqual(call_config.fullchain_path, test_util.temp_join('chain'))\n            self.assertEqual(call_config.chain_path, test_util.temp_join('chain'))\n            self.assertEqual(call_config.key_path, test_util.temp_join('overriding_privkey'))\n\n            mock_install.reset()\n\n            self._call(['install', '--cert-name', 'whatever',\n                        '--cert-path', test_util.temp_join('overriding_cert')], mockisfile=True)\n            call_config = mock_install.call_args[0][0]\n            self.assertEqual(call_config.cert_path, test_util.temp_join('overriding_cert'))\n            self.assertEqual(call_config.fullchain_path, test_util.temp_join('chain'))\n            self.assertEqual(call_config.key_path, test_util.temp_join('privkey'))\n\n    @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins')\n    @mock.patch('certbot._internal.main.plug_sel.pick_installer')\n    def test_installer_param_error(self, _inst, _rec):\n        self.assertRaises(errors.ConfigurationError,\n                          self._call,\n                          ['install', '--cert-name', 'notfound',\n                           '--key-path', 'invalid'])\n\n    @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins')\n    @mock.patch('certbot._internal.main.plug_sel.pick_installer')\n    @mock.patch('certbot._internal.cert_manager.get_certnames')\n    @mock.patch('certbot._internal.main._install_cert')\n    def test_installer_select_cert(self, mock_inst, mock_getcert, _inst, _rec):\n        mock_lineage = mock.MagicMock(cert_path=test_util.temp_join('cert'),\n                                      chain_path=test_util.temp_join('chain'),\n                                      fullchain_path=test_util.temp_join('chain'),\n                                      key_path=test_util.temp_join('privkey'))\n        with mock.patch(\"certbot._internal.cert_manager.lineage_for_certname\") as mock_getlin:\n            mock_getlin.return_value = mock_lineage\n            self._call(['install'], mockisfile=True)\n        self.assertTrue(mock_getcert.called)\n        self.assertTrue(mock_inst.called)\n\n    @mock.patch('certbot._internal.log.post_arg_parse_setup')\n    @mock.patch('certbot._internal.main._report_new_cert')\n    @mock.patch('certbot.util.exe_exists')\n    def test_configurator_selection(self, mock_exe_exists, _, __):\n        mock_exe_exists.return_value = True\n        real_plugins = disco.PluginsRegistry.find_all()\n        args = ['--apache', '--authenticator', 'standalone']\n\n        # This needed two calls to find_all(), which we're avoiding for now\n        # because of possible side effects:\n        # https://github.com/letsencrypt/letsencrypt/commit/51ed2b681f87b1eb29088dd48718a54f401e4855\n        #with mock.patch('certbot._internal.cli.plugins_testable') as plugins:\n        #    plugins.return_value = {\"apache\": True, \"nginx\": True}\n        #    ret, _, _, _ = self._call(args)\n        #    self.assertTrue(\"Too many flags setting\" in ret)\n\n        args = [\"install\", \"--nginx\", \"--cert-path\",\n                test_util.temp_join('blah'), \"--key-path\", test_util.temp_join('blah'),\n                \"--nginx-server-root\", \"/nonexistent/thing\", \"-d\",\n                \"example.com\", \"--debug\"]\n        if \"nginx\" in real_plugins:\n            # Sending nginx a non-existent conf dir will simulate misconfiguration\n            # (we can only do that if certbot-nginx is actually present)\n            ret, _, _, _ = self._call(args)\n            self.assertTrue(\"The nginx plugin is not working\" in ret)\n            self.assertTrue(\"MisconfigurationError\" in ret)\n\n        self._cli_missing_flag([\"--standalone\"], \"With the standalone plugin, you probably\")\n\n        with mock.patch(\"certbot._internal.main._init_le_client\") as mock_init:\n            with mock.patch(\"certbot._internal.main._get_and_save_cert\") as mock_gsc:\n                mock_gsc.return_value = mock.MagicMock()\n                self._call([\"certonly\", \"--manual\", \"-d\", \"foo.bar\"])\n                unused_config, auth, unused_installer = mock_init.call_args[0]\n                self.assertTrue(isinstance(auth, manual.Authenticator))\n\n        with mock.patch('certbot._internal.main.certonly') as mock_certonly:\n            self._call([\"auth\", \"--standalone\"])\n            self.assertEqual(1, mock_certonly.call_count)\n\n    @mock.patch('certbot._internal.log.post_arg_parse_setup')\n    def test_rollback(self, _):\n        _, _, _, client = self._call(['rollback'])\n        self.assertEqual(1, client.rollback.call_count)\n\n        _, _, _, client = self._call(['rollback', '--checkpoints', '123'])\n        client.rollback.assert_called_once_with(\n            mock.ANY, 123, mock.ANY, mock.ANY)\n\n    @mock.patch('certbot._internal.cert_manager.update_live_symlinks')\n    def test_update_symlinks(self, mock_cert_manager):\n        self._call_no_clientmock(['update_symlinks'])\n        self.assertEqual(1, mock_cert_manager.call_count)\n\n    @mock.patch('certbot._internal.cert_manager.certificates')\n    def test_certificates(self, mock_cert_manager):\n        self._call_no_clientmock(['certificates'])\n        self.assertEqual(1, mock_cert_manager.call_count)\n\n    @mock.patch('certbot._internal.cert_manager.delete')\n    def test_delete(self, mock_cert_manager):\n        self._call_no_clientmock(['delete'])\n        self.assertEqual(1, mock_cert_manager.call_count)\n\n    @mock.patch('certbot._internal.log.post_arg_parse_setup')\n    def test_plugins(self, _):\n        flags = ['--init', '--prepare', '--authenticators', '--installers']\n        for args in itertools.chain(\n                *(itertools.combinations(flags, r)\n                  for r in six.moves.range(len(flags)))):\n            self._call(['plugins'] + list(args))\n\n    @mock.patch('certbot._internal.main.plugins_disco')\n    @mock.patch('certbot._internal.main.cli.HelpfulArgumentParser.determine_help_topics')\n    def test_plugins_no_args(self, _det, mock_disco):\n        ifaces = []  # type: List[interfaces.IPlugin]\n        plugins = mock_disco.PluginsRegistry.find_all()\n\n        stdout = six.StringIO()\n        with test_util.patch_get_utility_with_stdout(stdout=stdout):\n            _, stdout, _, _ = self._call(['plugins'], stdout)\n\n        plugins.visible.assert_called_once_with()\n        plugins.visible().ifaces.assert_called_once_with(ifaces)\n        filtered = plugins.visible().ifaces()\n        self.assertEqual(stdout.getvalue().strip(), str(filtered))\n\n    @mock.patch('certbot._internal.main.plugins_disco')\n    @mock.patch('certbot._internal.main.cli.HelpfulArgumentParser.determine_help_topics')\n    def test_plugins_no_args_unprivileged(self, _det, mock_disco):\n        ifaces = []  # type: List[interfaces.IPlugin]\n        plugins = mock_disco.PluginsRegistry.find_all()\n\n        def throw_error(directory, mode, strict):\n            \"\"\"Raises error.Error.\"\"\"\n            _, _, _ = directory, mode, strict\n            raise errors.Error()\n\n        stdout = six.StringIO()\n        with mock.patch('certbot.util.set_up_core_dir') as mock_set_up_core_dir:\n            with test_util.patch_get_utility_with_stdout(stdout=stdout):\n                mock_set_up_core_dir.side_effect = throw_error\n                _, stdout, _, _ = self._call(['plugins'], stdout)\n\n        plugins.visible.assert_called_once_with()\n        plugins.visible().ifaces.assert_called_once_with(ifaces)\n        filtered = plugins.visible().ifaces()\n        self.assertEqual(stdout.getvalue().strip(), str(filtered))\n\n    @mock.patch('certbot._internal.main.plugins_disco')\n    @mock.patch('certbot._internal.main.cli.HelpfulArgumentParser.determine_help_topics')\n    def test_plugins_init(self, _det, mock_disco):\n        ifaces = []  # type: List[interfaces.IPlugin]\n        plugins = mock_disco.PluginsRegistry.find_all()\n\n        stdout = six.StringIO()\n        with test_util.patch_get_utility_with_stdout(stdout=stdout):\n            _, stdout, _, _ = self._call(['plugins', '--init'], stdout)\n\n        plugins.visible.assert_called_once_with()\n        plugins.visible().ifaces.assert_called_once_with(ifaces)\n        filtered = plugins.visible().ifaces()\n        self.assertEqual(filtered.init.call_count, 1)\n        filtered.verify.assert_called_once_with(ifaces)\n        verified = filtered.verify()\n        self.assertEqual(stdout.getvalue().strip(), str(verified))\n\n    @mock.patch('certbot._internal.main.plugins_disco')\n    @mock.patch('certbot._internal.main.cli.HelpfulArgumentParser.determine_help_topics')\n    def test_plugins_prepare(self, _det, mock_disco):\n        ifaces = []  # type: List[interfaces.IPlugin]\n        plugins = mock_disco.PluginsRegistry.find_all()\n\n        stdout = six.StringIO()\n        with test_util.patch_get_utility_with_stdout(stdout=stdout):\n            _, stdout, _, _ = self._call(['plugins', '--init', '--prepare'], stdout)\n\n        plugins.visible.assert_called_once_with()\n        plugins.visible().ifaces.assert_called_once_with(ifaces)\n        filtered = plugins.visible().ifaces()\n        self.assertEqual(filtered.init.call_count, 1)\n        filtered.verify.assert_called_once_with(ifaces)\n        verified = filtered.verify()\n        verified.prepare.assert_called_once_with()\n        verified.available.assert_called_once_with()\n        available = verified.available()\n        self.assertEqual(stdout.getvalue().strip(), str(available))\n\n    def test_certonly_abspath(self):\n        cert = 'cert'\n        key = 'key'\n        chain = 'chain'\n        fullchain = 'fullchain'\n\n        with mock.patch('certbot._internal.main.certonly') as mock_certonly:\n            self._call(['certonly', '--cert-path', cert, '--key-path', 'key',\n                        '--chain-path', 'chain',\n                        '--fullchain-path', 'fullchain'])\n\n        config, unused_plugins = mock_certonly.call_args[0]\n        self.assertEqual(config.cert_path, os.path.abspath(cert))\n        self.assertEqual(config.key_path, os.path.abspath(key))\n        self.assertEqual(config.chain_path, os.path.abspath(chain))\n        self.assertEqual(config.fullchain_path, os.path.abspath(fullchain))\n\n    def test_certonly_bad_args(self):\n        try:\n            self._call(['-a', 'bad_auth', 'certonly'])\n            assert False, \"Exception should have been raised\"\n        except errors.PluginSelectionError as e:\n            self.assertTrue('The requested bad_auth plugin does not appear' in str(e))\n\n    def test_check_config_sanity_domain(self):\n        # FQDN\n        self.assertRaises(errors.ConfigurationError,\n                          self._call,\n                          ['-d', 'a' * 64])\n        # FQDN 2\n        self.assertRaises(errors.ConfigurationError,\n                          self._call,\n                          ['-d', (('a' * 50) + '.') * 10])\n        # Bare IP address (this is actually a different error message now)\n        self.assertRaises(errors.ConfigurationError,\n                          self._call,\n                          ['-d', '204.11.231.35'])\n\n    def test_csr_with_besteffort(self):\n        self.assertRaises(\n            errors.Error, self._call,\n            'certonly --csr {0} --allow-subset-of-names'.format(CSR).split())\n\n    def test_run_with_csr(self):\n        # This is an error because you can only use --csr with certonly\n        try:\n            self._call(['--csr', CSR])\n        except errors.Error as e:\n            assert \"Please try the certonly\" in repr(e)\n            return\n        assert False, \"Expected supplying --csr to fail with default verb\"\n\n    def test_csr_with_no_domains(self):\n        self.assertRaises(\n            errors.Error, self._call,\n            'certonly --csr {0}'.format(\n                test_util.vector_path('csr-nonames_512.pem')).split())\n\n    def test_csr_with_inconsistent_domains(self):\n        self.assertRaises(\n            errors.Error, self._call,\n            'certonly -d example.org --csr {0}'.format(CSR).split())\n\n    def _certonly_new_request_common(self, mock_client, args=None):\n        with mock.patch('certbot._internal.main._find_lineage_for_domains_and_certname') \\\n            as mock_renewal:\n            mock_renewal.return_value = (\"newcert\", None)\n            with mock.patch('certbot._internal.main._init_le_client') as mock_init:\n                mock_init.return_value = mock_client\n                if args is None:\n                    args = []\n                args += '-d foo.bar -a standalone certonly'.split()\n                self._call(args)\n\n    @test_util.patch_get_utility()\n    def test_certonly_dry_run_new_request_success(self, mock_get_utility):\n        mock_client = mock.MagicMock()\n        mock_client.obtain_and_enroll_certificate.return_value = None\n        self._certonly_new_request_common(mock_client, ['--dry-run'])\n        self.assertEqual(\n            mock_client.obtain_and_enroll_certificate.call_count, 1)\n        self.assertTrue(\n            'dry run' in mock_get_utility().add_message.call_args[0][0])\n        # Asserts we don't suggest donating after a successful dry run\n        self.assertEqual(mock_get_utility().add_message.call_count, 1)\n\n    @mock.patch('certbot.crypto_util.notAfter')\n    @test_util.patch_get_utility()\n    def test_certonly_new_request_success(self, mock_get_utility, mock_notAfter):\n        cert_path = os.path.normpath(os.path.join(self.config.config_dir, 'live/foo.bar'))\n        key_path = os.path.normpath(os.path.join(self.config.config_dir, 'live/baz.qux'))\n        date = '1970-01-01'\n        mock_notAfter().date.return_value = date\n\n        mock_lineage = mock.MagicMock(cert=cert_path, fullchain=cert_path,\n                                      fullchain_path=cert_path, key_path=key_path)\n        mock_client = mock.MagicMock()\n        mock_client.obtain_and_enroll_certificate.return_value = mock_lineage\n        self._certonly_new_request_common(mock_client)\n        self.assertEqual(\n            mock_client.obtain_and_enroll_certificate.call_count, 1)\n        cert_msg = mock_get_utility().add_message.call_args_list[0][0][0]\n        self.assertTrue(cert_path in cert_msg)\n        self.assertTrue(date in cert_msg)\n        self.assertTrue(key_path in cert_msg)\n        self.assertTrue(\n            'donate' in mock_get_utility().add_message.call_args[0][0])\n\n    def test_certonly_new_request_failure(self):\n        mock_client = mock.MagicMock()\n        mock_client.obtain_and_enroll_certificate.return_value = False\n        self.assertRaises(errors.Error,\n                          self._certonly_new_request_common, mock_client)\n\n    def _test_renewal_common(self, due_for_renewal, extra_args, log_out=None,\n                             args=None, should_renew=True, error_expected=False,\n                             quiet_mode=False, expiry_date=datetime.datetime.now(),\n                             reuse_key=False):\n        cert_path = test_util.vector_path('cert_512.pem')\n        chain_path = os.path.normpath(os.path.join(self.config.config_dir,\n                                                   'live/foo.bar/fullchain.pem'))\n        mock_lineage = mock.MagicMock(cert=cert_path, fullchain=chain_path,\n                                      cert_path=cert_path, fullchain_path=chain_path)\n        mock_lineage.should_autorenew.return_value = due_for_renewal\n        mock_lineage.has_pending_deployment.return_value = False\n        mock_lineage.names.return_value = ['isnot.org']\n        mock_certr = mock.MagicMock()\n        mock_key = mock.MagicMock(pem='pem_key')\n        mock_client = mock.MagicMock()\n        stdout = six.StringIO()\n        mock_client.obtain_certificate.return_value = (mock_certr, 'chain',\n                                                       mock_key, 'csr')\n\n        def write_msg(message, *args, **kwargs):  # pylint: disable=unused-argument\n            \"\"\"Write message to stdout.\"\"\"\n            stdout.write(message)\n\n        try:\n            with mock.patch('certbot._internal.cert_manager.find_duplicative_certs') as mock_fdc:\n                mock_fdc.return_value = (mock_lineage, None)\n                with mock.patch('certbot._internal.main._init_le_client') as mock_init:\n                    mock_init.return_value = mock_client\n                    with test_util.patch_get_utility() as mock_get_utility:\n                        if not quiet_mode:\n                            mock_get_utility().notification.side_effect = write_msg\n                        with mock.patch('certbot._internal.main.renewal.OpenSSL') as mock_ssl:\n                            mock_latest = mock.MagicMock()\n                            mock_latest.get_issuer.return_value = \"Fake fake\"\n                            mock_ssl.crypto.load_certificate.return_value = mock_latest\n                            with mock.patch('certbot._internal.main.renewal.crypto_util') \\\n                                as mock_crypto_util:\n                                mock_crypto_util.notAfter.return_value = expiry_date\n                                if not args:\n                                    args = ['-d', 'isnot.org', '-a', 'standalone', 'certonly']\n                                if extra_args:\n                                    args += extra_args\n                                try:\n                                    ret, stdout, _, _ = self._call(args, stdout)\n                                    if ret:\n                                        print(\"Returned\", ret)\n                                        raise AssertionError(ret)\n                                    assert not error_expected, \"renewal should have errored\"\n                                except: # pylint: disable=bare-except\n                                    if not error_expected:\n                                        raise AssertionError(\n                                            \"Unexpected renewal error:\\n\" +\n                                            traceback.format_exc())\n\n            if should_renew:\n                if reuse_key:\n                    # The location of the previous live privkey.pem is passed\n                    # to obtain_certificate\n                    mock_client.obtain_certificate.assert_called_once_with(['isnot.org'],\n                        os.path.normpath(os.path.join(\n                            self.config.config_dir, \"live/sample-renewal/privkey.pem\")))\n                else:\n                    mock_client.obtain_certificate.assert_called_once_with(['isnot.org'], None)\n            else:\n                self.assertEqual(mock_client.obtain_certificate.call_count, 0)\n        except:\n            self._dump_log()\n            raise\n        finally:\n            if log_out:\n                with open(os.path.join(self.config.logs_dir, \"letsencrypt.log\")) as lf:\n                    self.assertTrue(log_out in lf.read())\n\n        return mock_lineage, mock_get_utility, stdout\n\n    @mock.patch('certbot.crypto_util.notAfter')\n    def test_certonly_renewal(self, _):\n        lineage, get_utility, _ = self._test_renewal_common(True, [])\n        self.assertEqual(lineage.save_successor.call_count, 1)\n        lineage.update_all_links_to.assert_called_once_with(\n            lineage.latest_common_version())\n        cert_msg = get_utility().add_message.call_args_list[0][0][0]\n        self.assertTrue('fullchain.pem' in cert_msg)\n        self.assertTrue('donate' in get_utility().add_message.call_args[0][0])\n\n    @mock.patch('certbot._internal.log.logging.handlers.RotatingFileHandler.doRollover')\n    @mock.patch('certbot.crypto_util.notAfter')\n    def test_certonly_renewal_triggers(self, _, __):\n        # --dry-run should force renewal\n        _, get_utility, _ = self._test_renewal_common(False, ['--dry-run', '--keep'],\n                                                      log_out=\"simulating renewal\")\n        self.assertEqual(get_utility().add_message.call_count, 1)\n        self.assertTrue('dry run' in get_utility().add_message.call_args[0][0])\n\n        self._test_renewal_common(False, ['--renew-by-default', '-tvv', '--debug'],\n                                  log_out=\"Auto-renewal forced\")\n        self.assertEqual(get_utility().add_message.call_count, 1)\n\n        self._test_renewal_common(False, ['-tvv', '--debug', '--keep'],\n                                  log_out=\"not yet due\", should_renew=False)\n\n    def _dump_log(self):\n        print(\"Logs:\")\n        log_path = os.path.join(self.config.logs_dir, \"letsencrypt.log\")\n        if os.path.exists(log_path):\n            with open(log_path) as lf:\n                print(lf.read())\n\n    def test_renew_verb(self):\n        test_util.make_lineage(self.config.config_dir, 'sample-renewal.conf')\n        args = [\"renew\", \"--dry-run\", \"-tvv\"]\n        self._test_renewal_common(True, [], args=args, should_renew=True)\n\n    def test_reuse_key(self):\n        test_util.make_lineage(self.config.config_dir, 'sample-renewal.conf')\n        args = [\"renew\", \"--dry-run\", \"--reuse-key\"]\n        self._test_renewal_common(True, [], args=args, should_renew=True, reuse_key=True)\n\n    @mock.patch('certbot._internal.storage.RenewableCert.save_successor')\n    def test_reuse_key_no_dry_run(self, unused_save_successor):\n        test_util.make_lineage(self.config.config_dir, 'sample-renewal.conf')\n        args = [\"renew\", \"--reuse-key\"]\n        self._test_renewal_common(True, [], args=args, should_renew=True, reuse_key=True)\n\n    @mock.patch('sys.stdin')\n    def test_noninteractive_renewal_delay(self, stdin):\n        stdin.isatty.return_value = False\n        test_util.make_lineage(self.config.config_dir, 'sample-renewal.conf')\n        args = [\"renew\", \"--dry-run\", \"-tvv\"]\n        self._test_renewal_common(True, [], args=args, should_renew=True)\n        self.assertEqual(self.mock_sleep.call_count, 1)\n        # in main.py:\n        #     sleep_time = random.randint(1, 60*8)\n        sleep_call_arg = self.mock_sleep.call_args[0][0]\n        self.assertTrue(1 <= sleep_call_arg <= 60*8)\n\n    @mock.patch('sys.stdin')\n    def test_interactive_no_renewal_delay(self, stdin):\n        stdin.isatty.return_value = True\n        test_util.make_lineage(self.config.config_dir, 'sample-renewal.conf')\n        args = [\"renew\", \"--dry-run\", \"-tvv\"]\n        self._test_renewal_common(True, [], args=args, should_renew=True)\n        self.assertEqual(self.mock_sleep.call_count, 0)\n\n    @mock.patch('certbot._internal.renewal.should_renew')\n    def test_renew_skips_recent_certs(self, should_renew):\n        should_renew.return_value = False\n        test_util.make_lineage(self.config.config_dir, 'sample-renewal.conf')\n        expiry = datetime.datetime.now() + datetime.timedelta(days=90)\n        _, _, stdout = self._test_renewal_common(False, extra_args=None, should_renew=False,\n                                                 args=['renew'], expiry_date=expiry)\n        self.assertTrue('No renewals were attempted.' in stdout.getvalue())\n        self.assertTrue('The following certs are not due for renewal yet:' in stdout.getvalue())\n\n    @mock.patch('certbot._internal.log.post_arg_parse_setup')\n    def test_quiet_renew(self, _):\n        test_util.make_lineage(self.config.config_dir, 'sample-renewal.conf')\n        args = [\"renew\", \"--dry-run\"]\n        _, _, stdout = self._test_renewal_common(True, [], args=args, should_renew=True)\n        out = stdout.getvalue()\n        self.assertTrue(\"renew\" in out)\n\n        args = [\"renew\", \"--dry-run\", \"-q\"]\n        _, _, stdout = self._test_renewal_common(True, [], args=args,\n                                                 should_renew=True, quiet_mode=True)\n        out = stdout.getvalue()\n        self.assertEqual(\"\", out)\n\n    def test_renew_hook_validation(self):\n        test_util.make_lineage(self.config.config_dir, 'sample-renewal.conf')\n        args = [\"renew\", \"--dry-run\", \"--post-hook=no-such-command\"]\n        self._test_renewal_common(True, [], args=args, should_renew=False,\n                                  error_expected=True)\n\n    def test_renew_no_hook_validation(self):\n        test_util.make_lineage(self.config.config_dir, 'sample-renewal.conf')\n        args = [\"renew\", \"--dry-run\", \"--post-hook=no-such-command\",\n                \"--disable-hook-validation\"]\n        with mock.patch(\"certbot._internal.hooks.post_hook\"):\n            self._test_renewal_common(True, [], args=args, should_renew=True,\n                                      error_expected=False)\n\n    def test_renew_verb_empty_config(self):\n        rd = os.path.join(self.config.config_dir, 'renewal')\n        if not os.path.exists(rd):\n            filesystem.makedirs(rd)\n        with open(os.path.join(rd, 'empty.conf'), 'w'):\n            pass  # leave the file empty\n        args = [\"renew\", \"--dry-run\", \"-tvv\"]\n        self._test_renewal_common(False, [], args=args, should_renew=False, error_expected=True)\n\n    def test_renew_with_certname(self):\n        test_util.make_lineage(self.config.config_dir, 'sample-renewal.conf')\n        self._test_renewal_common(True, [], should_renew=True,\n            args=['renew', '--dry-run', '--cert-name', 'sample-renewal'])\n\n    def test_renew_with_bad_certname(self):\n        self._test_renewal_common(True, [], should_renew=False,\n            args=['renew', '--dry-run', '--cert-name', 'sample-renewal'],\n            error_expected=True)\n\n    def _make_dummy_renewal_config(self):\n        renewer_configs_dir = os.path.join(self.config.config_dir, 'renewal')\n        filesystem.makedirs(renewer_configs_dir)\n        with open(os.path.join(renewer_configs_dir, 'test.conf'), 'w') as f:\n            f.write(\"My contents don't matter\")\n\n    def _test_renew_common(self, renewalparams=None, names=None,\n                           assert_oc_called=None, **kwargs):\n        self._make_dummy_renewal_config()\n        with mock.patch('certbot._internal.storage.RenewableCert') as mock_rc:\n            mock_lineage = mock.MagicMock()\n            mock_lineage.fullchain = \"somepath/fullchain.pem\"\n            if renewalparams is not None:\n                mock_lineage.configuration = {'renewalparams': renewalparams}\n            if names is not None:\n                mock_lineage.names.return_value = names\n            mock_rc.return_value = mock_lineage\n            with mock.patch('certbot._internal.main.renew_cert') as mock_renew_cert:\n                kwargs.setdefault('args', ['renew'])\n                self._test_renewal_common(True, None, should_renew=False, **kwargs)\n\n            if assert_oc_called is not None:\n                if assert_oc_called:\n                    self.assertTrue(mock_renew_cert.called)\n                else:\n                    self.assertFalse(mock_renew_cert.called)\n\n    def test_renew_no_renewalparams(self):\n        self._test_renew_common(assert_oc_called=False, error_expected=True)\n\n    def test_renew_no_authenticator(self):\n        self._test_renew_common(renewalparams={}, assert_oc_called=False,\n            error_expected=True)\n\n    def test_renew_with_bad_int(self):\n        renewalparams = {'authenticator': 'webroot',\n                         'rsa_key_size': 'over 9000'}\n        self._test_renew_common(renewalparams=renewalparams, error_expected=True,\n                                assert_oc_called=False)\n\n    def test_renew_with_nonetype_http01(self):\n        renewalparams = {'authenticator': 'webroot',\n                         'http01_port': 'None'}\n        self._test_renew_common(renewalparams=renewalparams,\n                                assert_oc_called=True)\n\n    def test_renew_with_bad_domain(self):\n        renewalparams = {'authenticator': 'webroot'}\n        names = ['uniçodé.com']\n        self._test_renew_common(renewalparams=renewalparams, error_expected=True,\n                                names=names, assert_oc_called=False)\n\n    @mock.patch('certbot._internal.plugins.selection.choose_configurator_plugins')\n    def test_renew_with_configurator(self, mock_sel):\n        mock_sel.return_value = (mock.MagicMock(), mock.MagicMock())\n        renewalparams = {'authenticator': 'webroot'}\n        self._test_renew_common(\n            renewalparams=renewalparams, assert_oc_called=True,\n            args='renew --configurator apache'.split())\n\n    def test_renew_plugin_config_restoration(self):\n        renewalparams = {'authenticator': 'webroot',\n                         'webroot_path': 'None',\n                         'webroot_imaginary_flag': '42'}\n        self._test_renew_common(renewalparams=renewalparams,\n                                assert_oc_called=True)\n\n    def test_renew_with_webroot_map(self):\n        renewalparams = {'authenticator': 'webroot'}\n        self._test_renew_common(\n            renewalparams=renewalparams, assert_oc_called=True,\n            args=['renew', '--webroot-map', json.dumps({'example.com': tempfile.gettempdir()})])\n\n    def test_renew_reconstitute_error(self):\n        # pylint: disable=protected-access\n        with mock.patch('certbot._internal.main.renewal._reconstitute') as mock_reconstitute:\n            mock_reconstitute.side_effect = Exception\n            self._test_renew_common(assert_oc_called=False, error_expected=True)\n\n    def test_renew_obtain_cert_error(self):\n        self._make_dummy_renewal_config()\n        with mock.patch('certbot._internal.storage.RenewableCert') as mock_rc:\n            mock_lineage = mock.MagicMock()\n            mock_lineage.fullchain = \"somewhere/fullchain.pem\"\n            mock_rc.return_value = mock_lineage\n            mock_lineage.configuration = {\n                'renewalparams': {'authenticator': 'webroot'}}\n            with mock.patch('certbot._internal.main.renew_cert') as mock_renew_cert:\n                mock_renew_cert.side_effect = Exception\n                self._test_renewal_common(True, None, error_expected=True,\n                                          args=['renew'], should_renew=False)\n\n    def test_renew_with_bad_cli_args(self):\n        self._test_renewal_common(True, None, args='renew -d example.com'.split(),\n                                  should_renew=False, error_expected=True)\n        self._test_renewal_common(True, None, args='renew --csr {0}'.format(CSR).split(),\n                                  should_renew=False, error_expected=True)\n\n    def test_no_renewal_with_hooks(self):\n        _, _, stdout = self._test_renewal_common(\n            due_for_renewal=False, extra_args=None, should_renew=False,\n            args=['renew', '--post-hook',\n                  '{0} -c \"from __future__ import print_function; print(\\'hello world\\');\"'\n                  .format(sys.executable)])\n        self.assertTrue('No hooks were run.' in stdout.getvalue())\n\n    @test_util.patch_get_utility()\n    @mock.patch('certbot._internal.main._find_lineage_for_domains_and_certname')\n    @mock.patch('certbot._internal.main._init_le_client')\n    def test_certonly_reinstall(self, mock_init, mock_renewal, mock_get_utility):\n        mock_renewal.return_value = ('reinstall', mock.MagicMock())\n        mock_init.return_value = mock_client = mock.MagicMock()\n        self._call(['-d', 'foo.bar', '-a', 'standalone', 'certonly'])\n        self.assertFalse(mock_client.obtain_certificate.called)\n        self.assertFalse(mock_client.obtain_and_enroll_certificate.called)\n        self.assertEqual(mock_get_utility().add_message.call_count, 0)\n        #self.assertTrue('donate' not in mock_get_utility().add_message.call_args[0][0])\n\n    def _test_certonly_csr_common(self, extra_args=None):\n        certr = 'certr'\n        chain = 'chain'\n        mock_client = mock.MagicMock()\n        mock_client.obtain_certificate_from_csr.return_value = (certr, chain)\n        cert_path = os.path.normpath(os.path.join(\n            self.config.config_dir,\n            'live/example.com/cert_512.pem'))\n        full_path = os.path.normpath(os.path.join(\n            self.config.config_dir,\n            'live/example.com/fullchain.pem'))\n        mock_client.save_certificate.return_value = cert_path, None, full_path\n        with mock.patch('certbot._internal.main._init_le_client') as mock_init:\n            mock_init.return_value = mock_client\n            with test_util.patch_get_utility() as mock_get_utility:\n                chain_path = os.path.normpath(os.path.join(\n                    self.config.config_dir,\n                    'live/example.com/chain.pem'))\n                args = ('-a standalone certonly --csr {0} --cert-path {1} '\n                        '--chain-path {2} --fullchain-path {3}').format(\n                            CSR, cert_path, chain_path, full_path).split()\n                if extra_args:\n                    args += extra_args\n                with mock.patch('certbot._internal.main.crypto_util'):\n                    self._call(args)\n\n        if '--dry-run' in args:\n            self.assertFalse(mock_client.save_certificate.called)\n        else:\n            mock_client.save_certificate.assert_called_once_with(\n                certr, chain, cert_path, chain_path, full_path)\n\n        return mock_get_utility\n\n    def test_certonly_csr(self):\n        mock_get_utility = self._test_certonly_csr_common()\n        cert_msg = mock_get_utility().add_message.call_args_list[0][0][0]\n        self.assertTrue('fullchain.pem' in cert_msg)\n        self.assertFalse('Your key file has been saved at' in cert_msg)\n        self.assertTrue(\n            'donate' in mock_get_utility().add_message.call_args[0][0])\n\n    def test_certonly_csr_dry_run(self):\n        mock_get_utility = self._test_certonly_csr_common(['--dry-run'])\n        self.assertEqual(mock_get_utility().add_message.call_count, 1)\n        self.assertTrue(\n            'dry run' in mock_get_utility().add_message.call_args[0][0])\n\n    @mock.patch('certbot._internal.main._delete_if_appropriate')\n    @mock.patch('certbot._internal.main.client.acme_client')\n    def test_revoke_with_key(self, mock_acme_client,\n            mock_delete_if_appropriate):\n        mock_delete_if_appropriate.return_value = False\n        server = 'foo.bar'\n        self._call_no_clientmock(['--cert-path', SS_CERT_PATH, '--key-path', RSA2048_KEY_PATH,\n                                 '--server', server, 'revoke'])\n        with open(RSA2048_KEY_PATH, 'rb') as f:\n            mock_acme_client.BackwardsCompatibleClientV2.assert_called_once_with(\n                mock.ANY, jose.JWK.load(f.read()), server)\n        with open(SS_CERT_PATH, 'rb') as f:\n            cert = crypto_util.pyopenssl_load_certificate(f.read())[0]\n            mock_revoke = mock_acme_client.BackwardsCompatibleClientV2().revoke\n            mock_revoke.assert_called_once_with(\n                    jose.ComparableX509(cert),\n                    mock.ANY)\n\n    def test_revoke_with_key_mismatch(self):\n        server = 'foo.bar'\n        self.assertRaises(errors.Error, self._call_no_clientmock,\n            ['--cert-path', CERT, '--key-path', KEY,\n                                 '--server', server, 'revoke'])\n\n    @mock.patch('certbot._internal.main._delete_if_appropriate')\n    @mock.patch('certbot._internal.main._determine_account')\n    def test_revoke_without_key(self, mock_determine_account,\n            mock_delete_if_appropriate):\n        mock_delete_if_appropriate.return_value = False\n        mock_determine_account.return_value = (mock.MagicMock(), None)\n        _, _, _, client = self._call(['--cert-path', CERT, 'revoke'])\n        with open(CERT) as f:\n            cert = crypto_util.pyopenssl_load_certificate(f.read())[0]\n            mock_revoke = client.acme_from_config_key().revoke\n            mock_revoke.assert_called_once_with(\n                    jose.ComparableX509(cert),\n                    mock.ANY)\n\n    @mock.patch('certbot._internal.log.post_arg_parse_setup')\n    def test_register(self, _):\n        with mock.patch('certbot._internal.main.client') as mocked_client:\n            acc = mock.MagicMock()\n            acc.id = \"imaginary_account\"\n            mocked_client.register.return_value = (acc, \"worked\")\n            self._call_no_clientmock([\"register\", \"--email\", \"user@example.org\"])\n            # TODO: It would be more correct to explicitly check that\n            #       _determine_account() gets called in the above case,\n            #       but coverage statistics should also show that it did.\n            with mock.patch('certbot._internal.main.account') as mocked_account:\n                mocked_storage = mock.MagicMock()\n                mocked_account.AccountFileStorage.return_value = mocked_storage\n                mocked_storage.find_all.return_value = [\"an account\"]\n                x = self._call_no_clientmock([\"register\", \"--email\", \"user@example.org\"])\n                self.assertTrue(\"There is an existing account\" in x[0])\n\n    def test_update_account_no_existing_accounts(self):\n        # with mock.patch('certbot._internal.main.client') as mocked_client:\n        with mock.patch('certbot._internal.main.account') as mocked_account:\n            mocked_storage = mock.MagicMock()\n            mocked_account.AccountFileStorage.return_value = mocked_storage\n            mocked_storage.find_all.return_value = []\n            x = self._call_no_clientmock(\n                [\"update_account\", \"--email\",\n                 \"user@example.org\"])\n            self.assertTrue(\"Could not find an existing account\" in x[0])\n\n    @mock.patch('certbot._internal.main.display_ops.get_email')\n    @test_util.patch_get_utility()\n    def test_update_account_with_email(self, mock_utility, mock_email):\n        email = \"user@example.com\"\n        mock_email.return_value = email\n        with mock.patch('certbot._internal.eff.handle_subscription') as mock_handle:\n            with mock.patch('certbot._internal.main._determine_account') as mocked_det:\n                with mock.patch('certbot._internal.main.account') as mocked_account:\n                    with mock.patch('certbot._internal.main.client') as mocked_client:\n                        mocked_storage = mock.MagicMock()\n                        mocked_account.AccountFileStorage.return_value = mocked_storage\n                        mocked_storage.find_all.return_value = [\"an account\"]\n                        mocked_det.return_value = (mock.MagicMock(), \"foo\")\n                        cb_client = mock.MagicMock()\n                        mocked_client.Client.return_value = cb_client\n                        x = self._call_no_clientmock(\n                            [\"update_account\"])\n                        # When registration change succeeds, the return value\n                        # of register() is None\n                        self.assertTrue(x[0] is None)\n                        # and we got supposedly did update the registration from\n                        # the server\n                        self.assertTrue(\n                            cb_client.acme.update_registration.called)\n                        # and we saved the updated registration on disk\n                        self.assertTrue(mocked_storage.save_regr.called)\n                        self.assertTrue(\n                            email in mock_utility().add_message.call_args[0][0])\n                        self.assertTrue(mock_handle.called)\n\n    @mock.patch('certbot._internal.plugins.selection.choose_configurator_plugins')\n    @mock.patch('certbot._internal.updater._run_updaters')\n    def test_plugin_selection_error(self, mock_run, mock_choose):\n        mock_choose.side_effect = errors.PluginSelectionError\n        self.assertRaises(errors.PluginSelectionError, main.renew_cert,\n                          None, None, None)\n\n        self.config.dry_run = False\n        updater.run_generic_updaters(self.config, None, None)\n        # Make sure we're returning None, and hence not trying to run the\n        # without installer\n        self.assertFalse(mock_run.called)\n\n\nclass UnregisterTest(unittest.TestCase):\n    def setUp(self):\n        self.patchers = {\n            '_determine_account': mock.patch('certbot._internal.main._determine_account'),\n            'account': mock.patch('certbot._internal.main.account'),\n            'client': mock.patch('certbot._internal.main.client'),\n            'get_utility': test_util.patch_get_utility()}\n        self.mocks = dict((k, v.start()) for k, v in self.patchers.items())\n\n    def tearDown(self):\n        for patch in self.patchers.values():\n            patch.stop()\n\n    def test_abort_unregister(self):\n        self.mocks['account'].AccountFileStorage.return_value = mock.Mock()\n\n        util_mock = self.mocks['get_utility']()\n        util_mock.yesno.return_value = False\n\n        config = mock.Mock()\n        unused_plugins = mock.Mock()\n\n        res = main.unregister(config, unused_plugins)\n        self.assertEqual(res, \"Deactivation aborted.\")\n\n    def test_unregister(self):\n        mocked_storage = mock.MagicMock()\n        mocked_storage.find_all.return_value = [\"an account\"]\n\n        self.mocks['account'].AccountFileStorage.return_value = mocked_storage\n        self.mocks['_determine_account'].return_value = (mock.MagicMock(), \"foo\")\n\n        cb_client = mock.MagicMock()\n        self.mocks['client'].Client.return_value = cb_client\n\n        config = mock.MagicMock()\n        unused_plugins = mock.MagicMock()\n\n        res = main.unregister(config, unused_plugins)\n\n        self.assertTrue(res is None)\n        self.assertTrue(cb_client.acme.deactivate_registration.called)\n        m = \"Account deactivated.\"\n        self.assertTrue(m in self.mocks['get_utility']().add_message.call_args[0][0])\n\n    def test_unregister_no_account(self):\n        mocked_storage = mock.MagicMock()\n        mocked_storage.find_all.return_value = []\n        self.mocks['account'].AccountFileStorage.return_value = mocked_storage\n\n        cb_client = mock.MagicMock()\n        self.mocks['client'].Client.return_value = cb_client\n\n        config = mock.MagicMock()\n        unused_plugins = mock.MagicMock()\n\n        res = main.unregister(config, unused_plugins)\n        m = \"Could not find existing account to deactivate.\"\n        self.assertEqual(res, m)\n        self.assertFalse(cb_client.acme.deactivate_registration.called)\n\n\nclass MakeOrVerifyNeededDirs(test_util.ConfigTestCase):\n    \"\"\"Tests for certbot._internal.main.make_or_verify_needed_dirs.\"\"\"\n\n    @mock.patch(\"certbot._internal.main.util\")\n    def test_it(self, mock_util):\n        main.make_or_verify_needed_dirs(self.config)\n        for core_dir in (self.config.config_dir, self.config.work_dir,):\n            mock_util.set_up_core_dir.assert_any_call(\n                core_dir, constants.CONFIG_DIRS_MODE,\n                self.config.strict_permissions\n            )\n\n        hook_dirs = (self.config.renewal_pre_hooks_dir,\n                     self.config.renewal_deploy_hooks_dir,\n                     self.config.renewal_post_hooks_dir,)\n        for hook_dir in hook_dirs:\n            # default mode of 755 is used\n            mock_util.make_or_verify_dir.assert_any_call(\n                hook_dir, strict=self.config.strict_permissions)\n\n\nclass EnhanceTest(test_util.ConfigTestCase):\n    \"\"\"Tests for certbot._internal.main.enhance.\"\"\"\n\n    def setUp(self):\n        super(EnhanceTest, self).setUp()\n        self.get_utility_patch = test_util.patch_get_utility()\n        self.mock_get_utility = self.get_utility_patch.start()\n        self.mockinstaller = mock.MagicMock(spec=enhancements.AutoHSTSEnhancement)\n\n    def tearDown(self):\n        self.get_utility_patch.stop()\n\n    def _call(self, args):\n        plugins = disco.PluginsRegistry.find_all()\n        config = configuration.NamespaceConfig(\n            cli.prepare_and_parse_args(plugins, args))\n\n        with mock.patch('certbot._internal.cert_manager.get_certnames') as mock_certs:\n            mock_certs.return_value = ['example.com']\n            with mock.patch('certbot._internal.cert_manager.domains_for_certname') as mock_dom:\n                mock_dom.return_value = ['example.com']\n                with mock.patch('certbot._internal.main._init_le_client') as mock_init:\n                    mock_client = mock.MagicMock()\n                    mock_client.config = config\n                    mock_init.return_value = mock_client\n                    main.enhance(config, plugins)\n                    return mock_client # returns the client\n\n    @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins')\n    @mock.patch('certbot._internal.cert_manager.lineage_for_certname')\n    @mock.patch('certbot._internal.main.display_ops.choose_values')\n    @mock.patch('certbot._internal.main._find_domains_or_certname')\n    def test_selection_question(self, mock_find, mock_choose, mock_lineage, _rec):\n        mock_lineage.return_value = mock.MagicMock(chain_path=\"/tmp/nonexistent\")\n        mock_choose.return_value = ['example.com']\n        mock_find.return_value = (None, None)\n        with mock.patch('certbot._internal.main.plug_sel.pick_installer') as mock_pick:\n            self._call(['enhance', '--redirect'])\n            self.assertTrue(mock_pick.called)\n            # Check that the message includes \"enhancements\"\n            self.assertTrue(\"enhancements\" in mock_pick.call_args[0][3])\n\n    @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins')\n    @mock.patch('certbot._internal.cert_manager.lineage_for_certname')\n    @mock.patch('certbot._internal.main.display_ops.choose_values')\n    @mock.patch('certbot._internal.main._find_domains_or_certname')\n    def test_selection_auth_warning(self, mock_find, mock_choose, mock_lineage, _rec):\n        mock_lineage.return_value = mock.MagicMock(chain_path=\"/tmp/nonexistent\")\n        mock_choose.return_value = [\"example.com\"]\n        mock_find.return_value = (None, None)\n        with mock.patch('certbot._internal.main.plug_sel.pick_installer'):\n            with mock.patch('certbot._internal.main.plug_sel.logger.warning') as mock_log:\n                mock_client = self._call(['enhance', '-a', 'webroot', '--redirect'])\n                self.assertTrue(mock_log.called)\n                self.assertTrue(\"make sense\" in mock_log.call_args[0][0])\n                self.assertTrue(mock_client.enhance_config.called)\n\n    @mock.patch('certbot._internal.cert_manager.lineage_for_certname')\n    @mock.patch('certbot._internal.main.display_ops.choose_values')\n    @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins')\n    def test_enhance_config_call(self, _rec, mock_choose, mock_lineage):\n        mock_lineage.return_value = mock.MagicMock(chain_path=\"/tmp/nonexistent\")\n        mock_choose.return_value = [\"example.com\"]\n        with mock.patch('certbot._internal.main.plug_sel.pick_installer'):\n            mock_client = self._call(['enhance', '--redirect', '--hsts'])\n            req_enh = [\"redirect\", \"hsts\"]\n            not_req_enh = [\"uir\"]\n            self.assertTrue(mock_client.enhance_config.called)\n            self.assertTrue(\n                all([getattr(mock_client.config, e) for e in req_enh]))\n            self.assertFalse(\n                any([getattr(mock_client.config, e) for e in not_req_enh]))\n            self.assertTrue(\n                \"example.com\" in mock_client.enhance_config.call_args[0][0])\n\n    @mock.patch('certbot._internal.cert_manager.lineage_for_certname')\n    @mock.patch('certbot._internal.main.display_ops.choose_values')\n    @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins')\n    def test_enhance_noninteractive(self, _rec, mock_choose, mock_lineage):\n        mock_lineage.return_value = mock.MagicMock(\n            chain_path=\"/tmp/nonexistent\")\n        mock_choose.return_value = [\"example.com\"]\n        with mock.patch('certbot._internal.main.plug_sel.pick_installer'):\n            mock_client = self._call(['enhance', '--redirect',\n                                      '--hsts', '--non-interactive'])\n            self.assertTrue(mock_client.enhance_config.called)\n            self.assertFalse(mock_choose.called)\n\n    @mock.patch('certbot._internal.main.display_ops.choose_values')\n    @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins')\n    def test_user_abort_domains(self, _rec, mock_choose):\n        mock_choose.return_value = []\n        with mock.patch('certbot._internal.main.plug_sel.pick_installer'):\n            self.assertRaises(errors.Error,\n                              self._call,\n                              ['enhance', '--redirect', '--hsts'])\n\n    def test_no_enhancements_defined(self):\n        self.assertRaises(errors.MisconfigurationError,\n                          self._call, ['enhance', '-a', 'null'])\n\n    @mock.patch('certbot._internal.main.plug_sel.choose_configurator_plugins')\n    @mock.patch('certbot._internal.main.display_ops.choose_values')\n    @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins')\n    def test_plugin_selection_error(self, _rec, mock_choose, mock_pick):\n        mock_choose.return_value = [\"example.com\"]\n        mock_pick.return_value = (None, None)\n        mock_pick.side_effect = errors.PluginSelectionError()\n        mock_client = self._call(['enhance', '--hsts'])\n        self.assertFalse(mock_client.enhance_config.called)\n\n    @mock.patch('certbot._internal.cert_manager.lineage_for_certname')\n    @mock.patch('certbot._internal.main.display_ops.choose_values')\n    @mock.patch('certbot._internal.main.plug_sel.pick_installer')\n    @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins')\n    @test_util.patch_get_utility()\n    def test_enhancement_enable(self, _, _rec, mock_inst, mock_choose, mock_lineage):\n        mock_inst.return_value = self.mockinstaller\n        mock_choose.return_value = [\"example.com\", \"another.tld\"]\n        mock_lineage.return_value = mock.MagicMock(chain_path=\"/tmp/nonexistent\")\n        self._call(['enhance', '--auto-hsts'])\n        self.assertTrue(self.mockinstaller.enable_autohsts.called)\n        self.assertEqual(self.mockinstaller.enable_autohsts.call_args[0][1],\n                          [\"example.com\", \"another.tld\"])\n\n    @mock.patch('certbot._internal.cert_manager.lineage_for_certname')\n    @mock.patch('certbot._internal.main.display_ops.choose_values')\n    @mock.patch('certbot._internal.main.plug_sel.pick_installer')\n    @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins')\n    @test_util.patch_get_utility()\n    def test_enhancement_enable_not_supported(self, _, _rec, mock_inst, mock_choose, mock_lineage):\n        mock_inst.return_value = null.Installer(self.config, \"null\")\n        mock_choose.return_value = [\"example.com\", \"another.tld\"]\n        mock_lineage.return_value = mock.MagicMock(chain_path=\"/tmp/nonexistent\")\n        self.assertRaises(\n            errors.NotSupportedError,\n            self._call, ['enhance', '--auto-hsts'])\n\n    def test_enhancement_enable_conflict(self):\n        self.assertRaises(\n            errors.Error,\n            self._call, ['enhance', '--auto-hsts', '--hsts'])\n\n\nclass InstallTest(test_util.ConfigTestCase):\n    \"\"\"Tests for certbot._internal.main.install.\"\"\"\n\n    def setUp(self):\n        super(InstallTest, self).setUp()\n        self.mockinstaller = mock.MagicMock(spec=enhancements.AutoHSTSEnhancement)\n\n    @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins')\n    @mock.patch('certbot._internal.main.plug_sel.pick_installer')\n    def test_install_enhancement_not_supported(self, mock_inst, _rec):\n        mock_inst.return_value = null.Installer(self.config, \"null\")\n        plugins = disco.PluginsRegistry.find_all()\n        self.config.auto_hsts = True\n        self.config.certname = \"nonexistent\"\n        self.assertRaises(errors.NotSupportedError,\n                          main.install,\n                          self.config, plugins)\n\n    @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins')\n    @mock.patch('certbot._internal.main.plug_sel.pick_installer')\n    def test_install_enhancement_no_certname(self, mock_inst, _rec):\n        mock_inst.return_value = self.mockinstaller\n        plugins = disco.PluginsRegistry.find_all()\n        self.config.auto_hsts = True\n        self.config.certname = None\n        self.config.key_path = \"/tmp/nonexistent\"\n        self.config.cert_path = \"/tmp/nonexistent\"\n        self.assertRaises(errors.ConfigurationError,\n                          main.install,\n                          self.config, plugins)\n\n\nif __name__ == '__main__':\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/ocsp_test.py",
    "content": "\"\"\"Tests for ocsp.py\"\"\"\n# pylint: disable=protected-access\nimport contextlib\nfrom datetime import datetime\nfrom datetime import timedelta\nimport unittest\n\nfrom cryptography import x509\nfrom cryptography.exceptions import InvalidSignature\nfrom cryptography.exceptions import UnsupportedAlgorithm\nfrom cryptography.hazmat.backends import default_backend\nfrom cryptography.hazmat.primitives import hashes  # type: ignore\nimport mock\nimport pytz\n\nfrom certbot import errors\nfrom certbot.tests import util as test_util\n\ntry:\n    # Only cryptography>=2.5 has ocsp module\n    # and signature_hash_algorithm attribute in OCSPResponse class\n    from cryptography.x509 import ocsp as ocsp_lib  # pylint: disable=import-error\n    getattr(ocsp_lib.OCSPResponse, 'signature_hash_algorithm')\nexcept (ImportError, AttributeError):  # pragma: no cover\n    ocsp_lib = None  # type: ignore\n\n\nout = \"\"\"Missing = in header key=value\nocsp: Use -help for summary.\n\"\"\"\n\n\nclass OCSPTestOpenSSL(unittest.TestCase):\n    \"\"\"\n    OCSP revocation tests using OpenSSL binary.\n    \"\"\"\n\n    def setUp(self):\n        from certbot import ocsp\n        with mock.patch('certbot.ocsp.Popen') as mock_popen:\n            with mock.patch('certbot.util.exe_exists') as mock_exists:\n                mock_communicate = mock.MagicMock()\n                mock_communicate.communicate.return_value = (None, out)\n                mock_popen.return_value = mock_communicate\n                mock_exists.return_value = True\n                self.checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True)\n\n    def tearDown(self):\n        pass\n\n    @mock.patch('certbot.ocsp.logger.info')\n    @mock.patch('certbot.ocsp.Popen')\n    @mock.patch('certbot.util.exe_exists')\n    def test_init(self, mock_exists, mock_popen, mock_log):\n        mock_communicate = mock.MagicMock()\n        mock_communicate.communicate.return_value = (None, out)\n        mock_popen.return_value = mock_communicate\n        mock_exists.return_value = True\n\n        from certbot import ocsp\n        checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True)\n        self.assertEqual(mock_popen.call_count, 1)\n        self.assertEqual(checker.host_args(\"x\"), [\"Host=x\"])\n\n        mock_communicate.communicate.return_value = (None, out.partition(\"\\n\")[2])\n        checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True)\n        self.assertEqual(checker.host_args(\"x\"), [\"Host\", \"x\"])\n        self.assertEqual(checker.broken, False)\n\n        mock_exists.return_value = False\n        mock_popen.call_count = 0\n        checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True)\n        self.assertEqual(mock_popen.call_count, 0)\n        self.assertEqual(mock_log.call_count, 1)\n        self.assertEqual(checker.broken, True)\n\n    @mock.patch('certbot.ocsp._determine_ocsp_server')\n    @mock.patch('certbot.ocsp.crypto_util.notAfter')\n    @mock.patch('certbot.util.run_script')\n    def test_ocsp_revoked(self, mock_run, mock_na, mock_determine):\n        now = pytz.UTC.fromutc(datetime.utcnow())\n        cert_obj = mock.MagicMock()\n        cert_obj.cert_path = \"x\"\n        cert_obj.chain_path = \"y\"\n        mock_na.return_value = now + timedelta(hours=2)\n\n        self.checker.broken = True\n        mock_determine.return_value = (\"\", \"\")\n        self.assertEqual(self.checker.ocsp_revoked(cert_obj), False)\n\n        self.checker.broken = False\n        mock_run.return_value = tuple(openssl_happy[1:])\n        self.assertEqual(self.checker.ocsp_revoked(cert_obj), False)\n        self.assertEqual(mock_run.call_count, 0)\n\n        mock_determine.return_value = (\"http://x.co\", \"x.co\")\n        self.assertEqual(self.checker.ocsp_revoked(cert_obj), False)\n        mock_run.side_effect = errors.SubprocessError(\"Unable to load certificate launcher\")\n        self.assertEqual(self.checker.ocsp_revoked(cert_obj), False)\n        self.assertEqual(mock_run.call_count, 2)\n\n        # cert expired\n        mock_na.return_value = now\n        mock_determine.return_value = (\"\", \"\")\n        count_before = mock_determine.call_count\n        self.assertEqual(self.checker.ocsp_revoked(cert_obj), False)\n        self.assertEqual(mock_determine.call_count, count_before)\n\n    def test_determine_ocsp_server(self):\n        cert_path = test_util.vector_path('ocsp_certificate.pem')\n\n        from certbot import ocsp\n        result = ocsp._determine_ocsp_server(cert_path)\n        self.assertEqual(('http://ocsp.test4.buypass.com', 'ocsp.test4.buypass.com'), result)\n\n    @mock.patch('certbot.ocsp.logger')\n    @mock.patch('certbot.util.run_script')\n    def test_translate_ocsp(self, mock_run, mock_log):\n        # pylint: disable=protected-access\n        mock_run.return_value = openssl_confused\n        from certbot import ocsp\n        self.assertEqual(ocsp._translate_ocsp_query(*openssl_happy), False)\n        self.assertEqual(ocsp._translate_ocsp_query(*openssl_confused), False)\n        self.assertEqual(mock_log.debug.call_count, 1)\n        self.assertEqual(mock_log.warning.call_count, 0)\n        mock_log.debug.call_count = 0\n        self.assertEqual(ocsp._translate_ocsp_query(*openssl_unknown), False)\n        self.assertEqual(mock_log.debug.call_count, 1)\n        self.assertEqual(mock_log.warning.call_count, 0)\n        self.assertEqual(ocsp._translate_ocsp_query(*openssl_expired_ocsp), False)\n        self.assertEqual(mock_log.debug.call_count, 2)\n        self.assertEqual(ocsp._translate_ocsp_query(*openssl_broken), False)\n        self.assertEqual(mock_log.warning.call_count, 1)\n        mock_log.info.call_count = 0\n        self.assertEqual(ocsp._translate_ocsp_query(*openssl_revoked), True)\n        self.assertEqual(mock_log.info.call_count, 0)\n        self.assertEqual(ocsp._translate_ocsp_query(*openssl_expired_ocsp_revoked), True)\n        self.assertEqual(mock_log.info.call_count, 1)\n\n\n@unittest.skipIf(not ocsp_lib,\n                 reason='This class tests functionalities available only on cryptography>=2.5.0')\nclass OSCPTestCryptography(unittest.TestCase):\n    \"\"\"\n    OCSP revokation tests using Cryptography >= 2.4.0\n    \"\"\"\n\n    def setUp(self):\n        from certbot import ocsp\n        self.checker = ocsp.RevocationChecker()\n        self.cert_path = test_util.vector_path('ocsp_certificate.pem')\n        self.chain_path = test_util.vector_path('ocsp_issuer_certificate.pem')\n        self.cert_obj = mock.MagicMock()\n        self.cert_obj.cert_path = self.cert_path\n        self.cert_obj.chain_path = self.chain_path\n        now = pytz.UTC.fromutc(datetime.utcnow())\n        self.mock_notAfter = mock.patch('certbot.ocsp.crypto_util.notAfter',\n                                        return_value=now + timedelta(hours=2))\n        self.mock_notAfter.start()\n        # Ensure the mock.patch is stopped even if test raises an exception\n        self.addCleanup(self.mock_notAfter.stop)\n\n    @mock.patch('certbot.ocsp._determine_ocsp_server')\n    @mock.patch('certbot.ocsp._check_ocsp_cryptography')\n    def test_ensure_cryptography_toggled(self, mock_revoke, mock_determine):\n        mock_determine.return_value = ('http://example.com', 'example.com')\n        self.checker.ocsp_revoked(self.cert_obj)\n\n        mock_revoke.assert_called_once_with(self.cert_path, self.chain_path, 'http://example.com')\n\n    def test_revoke(self):\n        with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL):\n            revoked = self.checker.ocsp_revoked(self.cert_obj)\n        self.assertTrue(revoked)\n\n    def test_responder_is_issuer(self):\n        issuer = x509.load_pem_x509_certificate(\n            test_util.load_vector('ocsp_issuer_certificate.pem'), default_backend())\n\n        with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED,\n                        ocsp_lib.OCSPResponseStatus.SUCCESSFUL) as mocks:\n            mocks['mock_response'].return_value.responder_name = issuer.subject\n            self.checker.ocsp_revoked(self.cert_obj)\n        # Here responder and issuer are the same. So only the signature of the OCSP\n        # response is checked (using the issuer/responder public key).\n        self.assertEqual(mocks['mock_check'].call_count, 1)\n        self.assertEqual(mocks['mock_check'].call_args[0][0].public_numbers(),\n                         issuer.public_key().public_numbers())\n\n    def test_responder_is_authorized_delegate(self):\n        issuer = x509.load_pem_x509_certificate(\n            test_util.load_vector('ocsp_issuer_certificate.pem'), default_backend())\n        responder = x509.load_pem_x509_certificate(\n            test_util.load_vector('ocsp_responder_certificate.pem'), default_backend())\n\n        with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED,\n                        ocsp_lib.OCSPResponseStatus.SUCCESSFUL) as mocks:\n            self.checker.ocsp_revoked(self.cert_obj)\n        # Here responder and issuer are not the same. Two signatures will be checked then,\n        # first to verify the responder cert (using the issuer public key), second to\n        # to verify the OCSP response itself (using the responder public key).\n        self.assertEqual(mocks['mock_check'].call_count, 2)\n        self.assertEqual(mocks['mock_check'].call_args_list[0][0][0].public_numbers(),\n                         issuer.public_key().public_numbers())\n        self.assertEqual(mocks['mock_check'].call_args_list[1][0][0].public_numbers(),\n                         responder.public_key().public_numbers())\n\n    def test_revoke_resiliency(self):\n        # Server return an invalid HTTP response\n        with _ocsp_mock(ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.SUCCESSFUL,\n                        http_status_code=400):\n            revoked = self.checker.ocsp_revoked(self.cert_obj)\n        self.assertFalse(revoked)\n\n        # OCSP response in invalid\n        with _ocsp_mock(ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.UNAUTHORIZED):\n            revoked = self.checker.ocsp_revoked(self.cert_obj)\n        self.assertFalse(revoked)\n\n        # OCSP response is valid, but certificate status is unknown\n        with _ocsp_mock(ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.SUCCESSFUL):\n            revoked = self.checker.ocsp_revoked(self.cert_obj)\n        self.assertFalse(revoked)\n\n        # The OCSP response says that the certificate is revoked, but certificate\n        # does not contain the OCSP extension.\n        with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL):\n            with mock.patch('cryptography.x509.Extensions.get_extension_for_class',\n                            side_effect=x509.ExtensionNotFound(\n                                'Not found', x509.AuthorityInformationAccessOID.OCSP)):\n                revoked = self.checker.ocsp_revoked(self.cert_obj)\n        self.assertFalse(revoked)\n\n        # OCSP response uses an unsupported signature.\n        with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL,\n                        check_signature_side_effect=UnsupportedAlgorithm('foo')):\n            revoked = self.checker.ocsp_revoked(self.cert_obj)\n        self.assertFalse(revoked)\n\n        # OSCP signature response is invalid.\n        with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL,\n                        check_signature_side_effect=InvalidSignature('foo')):\n            revoked = self.checker.ocsp_revoked(self.cert_obj)\n        self.assertFalse(revoked)\n\n        # Assertion error on OCSP response validity\n        with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL,\n                        check_signature_side_effect=AssertionError('foo')):\n            revoked = self.checker.ocsp_revoked(self.cert_obj)\n        self.assertFalse(revoked)\n\n        # No responder cert in OCSP response\n        with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED,\n                        ocsp_lib.OCSPResponseStatus.SUCCESSFUL) as mocks:\n            mocks['mock_response'].return_value.certificates = []\n            revoked = self.checker.ocsp_revoked(self.cert_obj)\n        self.assertFalse(revoked)\n\n        # Responder cert is not signed by certificate issuer\n        with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED,\n                        ocsp_lib.OCSPResponseStatus.SUCCESSFUL) as mocks:\n            cert = mocks['mock_response'].return_value.certificates[0]\n            mocks['mock_response'].return_value.certificates[0] = mock.Mock(\n                issuer='fake', subject=cert.subject)\n            revoked = self.checker.ocsp_revoked(self.cert_obj)\n        self.assertFalse(revoked)\n\n        with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL):\n            # This mock is necessary to avoid the first call contained in _determine_ocsp_server\n            # of the method cryptography.x509.Extensions.get_extension_for_class.\n            with mock.patch('certbot.ocsp._determine_ocsp_server') as mock_server:\n                mock_server.return_value = ('https://example.com', 'example.com')\n                with mock.patch('cryptography.x509.Extensions.get_extension_for_class',\n                                side_effect=x509.ExtensionNotFound(\n                                    'Not found', x509.AuthorityInformationAccessOID.OCSP)):\n                    revoked = self.checker.ocsp_revoked(self.cert_obj)\n        self.assertFalse(revoked)\n\n\n@contextlib.contextmanager\ndef _ocsp_mock(certificate_status, response_status,\n               http_status_code=200, check_signature_side_effect=None):\n    with mock.patch('certbot.ocsp.ocsp.load_der_ocsp_response') as mock_response:\n        mock_response.return_value = _construct_mock_ocsp_response(\n            certificate_status, response_status)\n        with mock.patch('certbot.ocsp.requests.post') as mock_post:\n            mock_post.return_value = mock.Mock(status_code=http_status_code)\n            with mock.patch('certbot.ocsp.crypto_util.verify_signed_payload') \\\n                as mock_check:\n                if check_signature_side_effect:\n                    mock_check.side_effect = check_signature_side_effect\n                yield {\n                    'mock_response': mock_response,\n                    'mock_post': mock_post,\n                    'mock_check': mock_check,\n                }\n\n\ndef _construct_mock_ocsp_response(certificate_status, response_status):\n    cert = x509.load_pem_x509_certificate(\n        test_util.load_vector('ocsp_certificate.pem'), default_backend())\n    issuer = x509.load_pem_x509_certificate(\n        test_util.load_vector('ocsp_issuer_certificate.pem'), default_backend())\n    responder = x509.load_pem_x509_certificate(\n        test_util.load_vector('ocsp_responder_certificate.pem'), default_backend())\n    builder = ocsp_lib.OCSPRequestBuilder()\n    builder = builder.add_certificate(cert, issuer, hashes.SHA1())\n    request = builder.build()\n\n    return mock.Mock(\n        response_status=response_status,\n        certificate_status=certificate_status,\n        serial_number=request.serial_number,\n        issuer_key_hash=request.issuer_key_hash,\n        issuer_name_hash=request.issuer_name_hash,\n        responder_name=responder.subject,\n        certificates=[responder],\n        hash_algorithm=hashes.SHA1(),\n        next_update=datetime.now() + timedelta(days=1),\n        this_update=datetime.now() - timedelta(days=1),\n        signature_algorithm_oid=x509.oid.SignatureAlgorithmOID.RSA_WITH_SHA1,\n    )\n\n\n# pylint: disable=line-too-long\nopenssl_confused = (\"\", \"\"\"\n/etc/letsencrypt/live/example.org/cert.pem: good\n\tThis Update: Dec 17 00:00:00 2016 GMT\n\tNext Update: Dec 24 00:00:00 2016 GMT\n\"\"\",\n\"\"\"\nResponse Verify Failure\n139903674214048:error:27069065:OCSP routines:OCSP_basic_verify:certificate verify error:ocsp_vfy.c:138:Verify error:unable to get local issuer certificate\n\"\"\")\n\nopenssl_happy = (\"blah.pem\", \"\"\"\nblah.pem: good\n\tThis Update: Dec 20 18:00:00 2016 GMT\n\tNext Update: Dec 27 18:00:00 2016 GMT\n\"\"\",\n\"Response verify OK\")\n\nopenssl_revoked = (\"blah.pem\", \"\"\"\nblah.pem: revoked\n\tThis Update: Dec 20 01:00:00 2016 GMT\n\tNext Update: Dec 27 01:00:00 2016 GMT\n\tRevocation Time: Dec 20 01:46:34 2016 GMT\n\"\"\",\n\"\"\"Response verify OK\"\"\")\n\nopenssl_unknown = (\"blah.pem\", \"\"\"\nblah.pem: unknown\n\tThis Update: Dec 20 18:00:00 2016 GMT\n\tNext Update: Dec 27 18:00:00 2016 GMT\n\"\"\",\n\"Response verify OK\")\n\nopenssl_broken = (\"\", \"tentacles\", \"Response verify OK\")\n\nopenssl_expired_ocsp = (\"blah.pem\", \"\"\"\nblah.pem: WARNING: Status times invalid.\n140659132298912:error:2707307D:OCSP routines:OCSP_check_validity:status expired:ocsp_cl.c:372:\ngood\n\tThis Update: Apr  6 00:00:00 2016 GMT\n\tNext Update: Apr 13 00:00:00 2016 GMT\n\"\"\",\n\"\"\"Response verify OK\"\"\")\n\nopenssl_expired_ocsp_revoked = (\"blah.pem\", \"\"\"\nblah.pem: WARNING: Status times invalid.\n140659132298912:error:2707307D:OCSP routines:OCSP_check_validity:status expired:ocsp_cl.c:372:\nrevoked\n\tThis Update: Apr  6 00:00:00 2016 GMT\n\tNext Update: Apr 13 00:00:00 2016 GMT\n\"\"\",\n\"\"\"Response verify OK\"\"\")\n\n\nif __name__ == '__main__':\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/plugins/__init__.py",
    "content": "\"\"\"Certbot Plugins Tests\"\"\"\n"
  },
  {
    "path": "tests/plugins/common_test.py",
    "content": "\"\"\"Tests for certbot.plugins.common.\"\"\"\nimport functools\nimport shutil\nimport unittest\n\nimport josepy as jose\nimport mock\n\nfrom acme import challenges\nfrom certbot import achallenges\nfrom certbot import crypto_util\nfrom certbot import errors\nfrom certbot.compat import filesystem\nfrom certbot.compat import os\nfrom certbot.tests import acme_util\nfrom certbot.tests import util as test_util\n\nAUTH_KEY = jose.JWKRSA.load(test_util.load_vector(\"rsa512_key.pem\"))\nACHALL = achallenges.KeyAuthorizationAnnotatedChallenge(\n            challb=acme_util.chall_to_challb(challenges.HTTP01(token=b'token1'),\n                                             \"pending\"),\n            domain=\"encryption-example.demo\", account_key=AUTH_KEY)\n\nclass NamespaceFunctionsTest(unittest.TestCase):\n    \"\"\"Tests for certbot.plugins.common.*_namespace functions.\"\"\"\n\n    def test_option_namespace(self):\n        from certbot.plugins.common import option_namespace\n        self.assertEqual(\"foo-\", option_namespace(\"foo\"))\n\n    def test_dest_namespace(self):\n        from certbot.plugins.common import dest_namespace\n        self.assertEqual(\"foo_\", dest_namespace(\"foo\"))\n\n    def test_dest_namespace_with_dashes(self):\n        from certbot.plugins.common import dest_namespace\n        self.assertEqual(\"foo_bar_\", dest_namespace(\"foo-bar\"))\n\n\nclass PluginTest(unittest.TestCase):\n    \"\"\"Test for certbot.plugins.common.Plugin.\"\"\"\n\n    def setUp(self):\n        from certbot.plugins.common import Plugin\n\n        class MockPlugin(Plugin):  # pylint: disable=missing-docstring\n            @classmethod\n            def add_parser_arguments(cls, add):\n                add(\"foo-bar\", dest=\"different_to_foo_bar\", x=1, y=None)\n\n        self.plugin_cls = MockPlugin\n        self.config = mock.MagicMock()\n        self.plugin = MockPlugin(config=self.config, name=\"mock\")\n\n    def test_init(self):\n        self.assertEqual(\"mock\", self.plugin.name)\n        self.assertEqual(self.config, self.plugin.config)\n\n    def test_option_namespace(self):\n        self.assertEqual(\"mock-\", self.plugin.option_namespace)\n\n    def test_option_name(self):\n        self.assertEqual(\"mock-foo_bar\", self.plugin.option_name(\"foo_bar\"))\n\n    def test_dest_namespace(self):\n        self.assertEqual(\"mock_\", self.plugin.dest_namespace)\n\n    def test_dest(self):\n        self.assertEqual(\"mock_foo_bar\", self.plugin.dest(\"foo-bar\"))\n        self.assertEqual(\"mock_foo_bar\", self.plugin.dest(\"foo_bar\"))\n\n    def test_conf(self):\n        self.assertEqual(self.config.mock_foo_bar, self.plugin.conf(\"foo-bar\"))\n\n    def test_inject_parser_options(self):\n        parser = mock.MagicMock()\n        self.plugin_cls.inject_parser_options(parser, \"mock\")\n        # note that inject_parser_options doesn't check if dest has\n        # correct prefix\n        parser.add_argument.assert_called_once_with(\n            \"--mock-foo-bar\", dest=\"different_to_foo_bar\", x=1, y=None)\n\n\nclass InstallerTest(test_util.ConfigTestCase):\n    \"\"\"Tests for certbot.plugins.common.Installer.\"\"\"\n\n    def setUp(self):\n        super(InstallerTest, self).setUp()\n        filesystem.mkdir(self.config.config_dir)\n        from certbot.plugins.common import Installer\n\n        self.installer = Installer(config=self.config,\n                                   name=\"Installer\")\n        self.reverter = self.installer.reverter\n\n    def test_add_to_real_checkpoint(self):\n        files = set((\"foo.bar\", \"baz.qux\",))\n        save_notes = \"foo bar baz qux\"\n        self._test_wrapped_method(\"add_to_checkpoint\", files, save_notes)\n\n    def test_add_to_real_checkpoint2(self):\n        self._test_add_to_checkpoint_common(False)\n\n    def test_add_to_temporary_checkpoint(self):\n        self._test_add_to_checkpoint_common(True)\n\n    def _test_add_to_checkpoint_common(self, temporary):\n        files = set((\"foo.bar\", \"baz.qux\",))\n        save_notes = \"foo bar baz qux\"\n\n        installer_func = functools.partial(self.installer.add_to_checkpoint,\n                                           temporary=temporary)\n\n        if temporary:\n            reverter_func_name = \"add_to_temp_checkpoint\"\n        else:\n            reverter_func_name = \"add_to_checkpoint\"\n\n        self._test_adapted_method(installer_func, reverter_func_name, files, save_notes)\n\n    def test_finalize_checkpoint(self):\n        self._test_wrapped_method(\"finalize_checkpoint\", \"foo\")\n\n    def test_recovery_routine(self):\n        self._test_wrapped_method(\"recovery_routine\")\n\n    def test_revert_temporary_config(self):\n        self._test_wrapped_method(\"revert_temporary_config\")\n\n    def test_rollback_checkpoints(self):\n        self._test_wrapped_method(\"rollback_checkpoints\", 42)\n\n    def _test_wrapped_method(self, name, *args, **kwargs):\n        \"\"\"Test a wrapped reverter method.\n\n        :param str name: name of the method to test\n        :param tuple args: position arguments to method\n        :param dict kwargs: keyword arguments to method\n\n        \"\"\"\n        installer_func = getattr(self.installer, name)\n        self._test_adapted_method(installer_func, name, *args, **kwargs)\n\n    def _test_adapted_method(self, installer_func,\n                             reverter_func_name, *passed_args, **passed_kwargs):\n        \"\"\"Test an adapted reverter method\n\n        :param callable installer_func: installer method to test\n        :param str reverter_func_name: name of the method on the\n            reverter that should be called\n        :param tuple passed_args: positional arguments passed from\n            installer method to the reverter method\n        :param dict passed_kargs: keyword arguments passed from\n            installer method to the reverter method\n\n        \"\"\"\n        with mock.patch.object(self.reverter, reverter_func_name) as reverter_func:\n            installer_func(*passed_args, **passed_kwargs)\n            reverter_func.assert_called_once_with(*passed_args, **passed_kwargs)\n            reverter_func.side_effect = errors.ReverterError\n            self.assertRaises(\n                errors.PluginError, installer_func, *passed_args, **passed_kwargs)\n\n    def test_install_ssl_dhparams(self):\n        self.installer.install_ssl_dhparams()\n        self.assertTrue(os.path.isfile(self.installer.ssl_dhparams))\n\n    def _current_ssl_dhparams_hash(self):\n        from certbot._internal.constants import SSL_DHPARAMS_SRC\n        return crypto_util.sha256sum(SSL_DHPARAMS_SRC)\n\n    def test_current_file_hash_in_all_hashes(self):\n        from certbot._internal.constants import ALL_SSL_DHPARAMS_HASHES\n        self.assertTrue(self._current_ssl_dhparams_hash() in ALL_SSL_DHPARAMS_HASHES,\n            \"Constants.ALL_SSL_DHPARAMS_HASHES must be appended\"\n            \" with the sha256 hash of self.config.ssl_dhparams when it is updated.\")\n\n\nclass AddrTest(unittest.TestCase):\n    \"\"\"Tests for certbot.plugins.common.Addr.\"\"\"\n\n    def setUp(self):\n        from certbot.plugins.common import Addr\n        self.addr1 = Addr.fromstring(\"192.168.1.1\")\n        self.addr2 = Addr.fromstring(\"192.168.1.1:*\")\n        self.addr3 = Addr.fromstring(\"192.168.1.1:80\")\n        self.addr4 = Addr.fromstring(\"[fe00::1]\")\n        self.addr5 = Addr.fromstring(\"[fe00::1]:*\")\n        self.addr6 = Addr.fromstring(\"[fe00::1]:80\")\n        self.addr7 = Addr.fromstring(\"[fe00::1]:5\")\n        self.addr8 = Addr.fromstring(\"[fe00:1:2:3:4:5:6:7:8:9]:8080\")\n\n    def test_fromstring(self):\n        self.assertEqual(self.addr1.get_addr(), \"192.168.1.1\")\n        self.assertEqual(self.addr1.get_port(), \"\")\n        self.assertEqual(self.addr2.get_addr(), \"192.168.1.1\")\n        self.assertEqual(self.addr2.get_port(), \"*\")\n        self.assertEqual(self.addr3.get_addr(), \"192.168.1.1\")\n        self.assertEqual(self.addr3.get_port(), \"80\")\n        self.assertEqual(self.addr4.get_addr(), \"[fe00::1]\")\n        self.assertEqual(self.addr4.get_port(), \"\")\n        self.assertEqual(self.addr5.get_addr(), \"[fe00::1]\")\n        self.assertEqual(self.addr5.get_port(), \"*\")\n        self.assertEqual(self.addr6.get_addr(), \"[fe00::1]\")\n        self.assertEqual(self.addr6.get_port(), \"80\")\n        self.assertEqual(self.addr6.get_ipv6_exploded(),\n                         \"fe00:0:0:0:0:0:0:1\")\n        self.assertEqual(self.addr1.get_ipv6_exploded(),\n                         \"\")\n        self.assertEqual(self.addr7.get_port(), \"5\")\n        self.assertEqual(self.addr8.get_ipv6_exploded(),\n                         \"fe00:1:2:3:4:5:6:7\")\n\n    def test_str(self):\n        self.assertEqual(str(self.addr1), \"192.168.1.1\")\n        self.assertEqual(str(self.addr2), \"192.168.1.1:*\")\n        self.assertEqual(str(self.addr3), \"192.168.1.1:80\")\n        self.assertEqual(str(self.addr4), \"[fe00::1]\")\n        self.assertEqual(str(self.addr5), \"[fe00::1]:*\")\n        self.assertEqual(str(self.addr6), \"[fe00::1]:80\")\n\n    def test_get_addr_obj(self):\n        self.assertEqual(str(self.addr1.get_addr_obj(\"443\")), \"192.168.1.1:443\")\n        self.assertEqual(str(self.addr2.get_addr_obj(\"\")), \"192.168.1.1\")\n        self.assertEqual(str(self.addr1.get_addr_obj(\"*\")), \"192.168.1.1:*\")\n        self.assertEqual(str(self.addr4.get_addr_obj(\"443\")), \"[fe00::1]:443\")\n        self.assertEqual(str(self.addr5.get_addr_obj(\"\")), \"[fe00::1]\")\n        self.assertEqual(str(self.addr4.get_addr_obj(\"*\")), \"[fe00::1]:*\")\n\n    def test_eq(self):\n        self.assertEqual(self.addr1, self.addr2.get_addr_obj(\"\"))\n        self.assertNotEqual(self.addr1, self.addr2)\n        self.assertFalse(self.addr1 == 3333)\n\n        self.assertEqual(self.addr4, self.addr4.get_addr_obj(\"\"))\n        self.assertNotEqual(self.addr4, self.addr5)\n        self.assertFalse(self.addr4 == 3333)\n        from certbot.plugins.common import Addr\n        self.assertEqual(self.addr4, Addr.fromstring(\"[fe00:0:0::1]\"))\n        self.assertEqual(self.addr4, Addr.fromstring(\"[fe00:0::0:0:1]\"))\n\n\n    def test_set_inclusion(self):\n        from certbot.plugins.common import Addr\n        set_a = set([self.addr1, self.addr2])\n        addr1b = Addr.fromstring(\"192.168.1.1\")\n        addr2b = Addr.fromstring(\"192.168.1.1:*\")\n        set_b = set([addr1b, addr2b])\n\n        self.assertEqual(set_a, set_b)\n\n        set_c = set([self.addr4, self.addr5])\n        addr4b = Addr.fromstring(\"[fe00::1]\")\n        addr5b = Addr.fromstring(\"[fe00::1]:*\")\n        set_d = set([addr4b, addr5b])\n\n        self.assertEqual(set_c, set_d)\n\n\nclass ChallengePerformerTest(unittest.TestCase):\n    \"\"\"Tests for certbot.plugins.common.ChallengePerformer.\"\"\"\n\n    def setUp(self):\n        configurator = mock.MagicMock()\n\n        from certbot.plugins.common import ChallengePerformer\n        self.performer = ChallengePerformer(configurator)\n\n    def test_add_chall(self):\n        self.performer.add_chall(ACHALL, 0)\n        self.assertEqual(1, len(self.performer.achalls))\n        self.assertEqual([0], self.performer.indices)\n\n    def test_perform(self):\n        self.assertRaises(NotImplementedError, self.performer.perform)\n\n\nclass InstallVersionControlledFileTest(test_util.TempDirTestCase):\n    \"\"\"Tests for certbot.plugins.common.install_version_controlled_file.\"\"\"\n\n    def setUp(self):\n        super(InstallVersionControlledFileTest, self).setUp()\n        self.hashes = [\"someotherhash\"]\n        self.dest_path = os.path.join(self.tempdir, \"options-ssl-dest.conf\")\n        self.hash_path = os.path.join(self.tempdir, \".options-ssl-conf.txt\")\n        self.old_path = os.path.join(self.tempdir, \"options-ssl-old.conf\")\n        self.source_path = os.path.join(self.tempdir, \"options-ssl-src.conf\")\n        for path in (self.source_path, self.old_path,):\n            with open(path, \"w\") as f:\n                f.write(path)\n            self.hashes.append(crypto_util.sha256sum(path))\n\n    def _call(self):\n        from certbot.plugins.common import install_version_controlled_file\n        install_version_controlled_file(self.dest_path,\n                                        self.hash_path,\n                                        self.source_path,\n                                        self.hashes)\n\n    def _current_file_hash(self):\n        return crypto_util.sha256sum(self.source_path)\n\n    def _assert_current_file(self):\n        self.assertTrue(os.path.isfile(self.dest_path))\n        self.assertEqual(crypto_util.sha256sum(self.dest_path),\n            self._current_file_hash())\n\n    def test_no_file(self):\n        self.assertFalse(os.path.isfile(self.dest_path))\n        self._call()\n        self._assert_current_file()\n\n    def test_current_file(self):\n        # 1st iteration installs the file, the 2nd checks if it needs updating\n        for _ in range(2):\n            self._call()\n            self._assert_current_file()\n\n    def test_prev_file_updates_to_current(self):\n        shutil.copyfile(self.old_path, self.dest_path)\n        self._call()\n        self._assert_current_file()\n\n    def test_manually_modified_current_file_does_not_update(self):\n        self._call()\n        with open(self.dest_path, \"a\") as mod_ssl_conf:\n            mod_ssl_conf.write(\"a new line for the wrong hash\\n\")\n        with mock.patch(\"certbot.plugins.common.logger\") as mock_logger:\n            self._call()\n            self.assertFalse(mock_logger.warning.called)\n        self.assertTrue(os.path.isfile(self.dest_path))\n        self.assertEqual(crypto_util.sha256sum(self.source_path),\n            self._current_file_hash())\n        self.assertNotEqual(crypto_util.sha256sum(self.dest_path),\n            self._current_file_hash())\n\n    def test_manually_modified_past_file_warns(self):\n        with open(self.dest_path, \"a\") as mod_ssl_conf:\n            mod_ssl_conf.write(\"a new line for the wrong hash\\n\")\n        with open(self.hash_path, \"w\") as f:\n            f.write(\"hashofanoldversion\")\n        with mock.patch(\"certbot.plugins.common.logger\") as mock_logger:\n            self._call()\n            self.assertEqual(mock_logger.warning.call_args[0][0],\n                \"%s has been manually modified; updated file \"\n                \"saved to %s. We recommend updating %s for security purposes.\")\n        self.assertEqual(crypto_util.sha256sum(self.source_path),\n            self._current_file_hash())\n        # only print warning once\n        with mock.patch(\"certbot.plugins.common.logger\") as mock_logger:\n            self._call()\n            self.assertFalse(mock_logger.warning.called)\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/plugins/disco_test.py",
    "content": "\"\"\"Tests for certbot._internal.plugins.disco.\"\"\"\nimport functools\nimport string\nimport unittest\n\nimport mock\nimport pkg_resources\nimport six\nimport zope.interface\n\nfrom acme.magic_typing import List  # pylint: disable=unused-import, no-name-in-module\nfrom certbot import errors\nfrom certbot import interfaces\nfrom certbot._internal.plugins import standalone\nfrom certbot._internal.plugins import webroot\n\nEP_SA = pkg_resources.EntryPoint(\n    \"sa\", \"certbot._internal.plugins.standalone\",\n    attrs=(\"Authenticator\",),\n    dist=mock.MagicMock(key=\"certbot\"))\nEP_WR = pkg_resources.EntryPoint(\n    \"wr\", \"certbot._internal.plugins.webroot\",\n    attrs=(\"Authenticator\",),\n    dist=mock.MagicMock(key=\"certbot\"))\n\n\nclass PluginEntryPointTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.plugins.disco.PluginEntryPoint.\"\"\"\n\n    def setUp(self):\n        self.ep1 = pkg_resources.EntryPoint(\n            \"ep1\", \"p1.ep1\", dist=mock.MagicMock(key=\"p1\"))\n        self.ep1prim = pkg_resources.EntryPoint(\n            \"ep1\", \"p2.ep2\", dist=mock.MagicMock(key=\"p2\"))\n        # nested\n        self.ep2 = pkg_resources.EntryPoint(\n            \"ep2\", \"p2.foo.ep2\", dist=mock.MagicMock(key=\"p2\"))\n        # project name != top-level package name\n        self.ep3 = pkg_resources.EntryPoint(\n            \"ep3\", \"a.ep3\", dist=mock.MagicMock(key=\"p3\"))\n\n        from certbot._internal.plugins.disco import PluginEntryPoint\n        self.plugin_ep = PluginEntryPoint(EP_SA)\n\n    def test_entry_point_to_plugin_name(self):\n        from certbot._internal.plugins.disco import PluginEntryPoint\n\n        names = {\n            self.ep1: \"p1:ep1\",\n            self.ep1prim: \"p2:ep1\",\n            self.ep2: \"p2:ep2\",\n            self.ep3: \"p3:ep3\",\n            EP_SA: \"sa\",\n        }\n\n        for entry_point, name in six.iteritems(names):\n            self.assertEqual(\n                name, PluginEntryPoint.entry_point_to_plugin_name(entry_point))\n\n    def test_description(self):\n        self.assertTrue(\"temporary webserver\" in self.plugin_ep.description)\n\n    def test_description_with_name(self):\n        self.plugin_ep.plugin_cls = mock.MagicMock(description=\"Desc\")\n        self.assertEqual(\n            \"Desc (sa)\", self.plugin_ep.description_with_name)\n\n    def test_long_description(self):\n        self.plugin_ep.plugin_cls = mock.MagicMock(\n            long_description=\"Long desc\")\n        self.assertEqual(\n            \"Long desc\", self.plugin_ep.long_description)\n\n    def test_long_description_nonexistent(self):\n        self.plugin_ep.plugin_cls = mock.MagicMock(\n            description=\"Long desc not found\", spec=[\"description\"])\n        self.assertEqual(\n            \"Long desc not found\", self.plugin_ep.long_description)\n\n    def test_ifaces(self):\n        self.assertTrue(self.plugin_ep.ifaces((interfaces.IAuthenticator,)))\n        self.assertFalse(self.plugin_ep.ifaces((interfaces.IInstaller,)))\n        self.assertFalse(self.plugin_ep.ifaces((\n            interfaces.IInstaller, interfaces.IAuthenticator)))\n\n    def test__init__(self):\n        self.assertFalse(self.plugin_ep.initialized)\n        self.assertFalse(self.plugin_ep.prepared)\n        self.assertFalse(self.plugin_ep.misconfigured)\n        self.assertFalse(self.plugin_ep.available)\n        self.assertTrue(self.plugin_ep.problem is None)\n        self.assertTrue(self.plugin_ep.entry_point is EP_SA)\n        self.assertEqual(\"sa\", self.plugin_ep.name)\n\n        self.assertTrue(self.plugin_ep.plugin_cls is standalone.Authenticator)\n\n    def test_init(self):\n        config = mock.MagicMock()\n        plugin = self.plugin_ep.init(config=config)\n        self.assertTrue(self.plugin_ep.initialized)\n        self.assertTrue(plugin.config is config)\n        # memoize!\n        self.assertTrue(self.plugin_ep.init() is plugin)\n        self.assertTrue(plugin.config is config)\n        # try to give different config\n        self.assertTrue(self.plugin_ep.init(123) is plugin)\n        self.assertTrue(plugin.config is config)\n\n        self.assertFalse(self.plugin_ep.prepared)\n        self.assertFalse(self.plugin_ep.misconfigured)\n        self.assertFalse(self.plugin_ep.available)\n\n    def test_verify(self):\n        iface1 = mock.MagicMock(__name__=\"iface1\")\n        iface2 = mock.MagicMock(__name__=\"iface2\")\n        iface3 = mock.MagicMock(__name__=\"iface3\")\n        # pylint: disable=protected-access\n        self.plugin_ep._initialized = plugin = mock.MagicMock()\n\n        exceptions = zope.interface.exceptions\n        with mock.patch(\"certbot._internal.plugins.\"\n                        \"disco.zope.interface\") as mock_zope:\n            mock_zope.exceptions = exceptions\n\n            def verify_object(iface, obj):  # pylint: disable=missing-docstring\n                assert obj is plugin\n                assert iface is iface1 or iface is iface2 or iface is iface3\n                if iface is iface3:\n                    raise mock_zope.exceptions.BrokenImplementation(None, None)\n            mock_zope.verify.verifyObject.side_effect = verify_object\n            self.assertTrue(self.plugin_ep.verify((iface1,)))\n            self.assertTrue(self.plugin_ep.verify((iface1, iface2)))\n            self.assertFalse(self.plugin_ep.verify((iface3,)))\n            self.assertFalse(self.plugin_ep.verify((iface1, iface3)))\n\n    def test_prepare(self):\n        config = mock.MagicMock()\n        self.plugin_ep.init(config=config)\n        self.plugin_ep.prepare()\n        self.assertTrue(self.plugin_ep.prepared)\n        self.assertFalse(self.plugin_ep.misconfigured)\n\n        # output doesn't matter that much, just test if it runs\n        str(self.plugin_ep)\n\n    def test_prepare_misconfigured(self):\n        plugin = mock.MagicMock()\n        plugin.prepare.side_effect = errors.MisconfigurationError\n        # pylint: disable=protected-access\n        self.plugin_ep._initialized = plugin\n        self.assertTrue(isinstance(self.plugin_ep.prepare(),\n                                   errors.MisconfigurationError))\n        self.assertTrue(self.plugin_ep.prepared)\n        self.assertTrue(self.plugin_ep.misconfigured)\n        self.assertTrue(isinstance(self.plugin_ep.problem,\n                                   errors.MisconfigurationError))\n        self.assertTrue(self.plugin_ep.available)\n\n    def test_prepare_no_installation(self):\n        plugin = mock.MagicMock()\n        plugin.prepare.side_effect = errors.NoInstallationError\n        # pylint: disable=protected-access\n        self.plugin_ep._initialized = plugin\n        self.assertTrue(isinstance(self.plugin_ep.prepare(),\n                                   errors.NoInstallationError))\n        self.assertTrue(self.plugin_ep.prepared)\n        self.assertFalse(self.plugin_ep.misconfigured)\n        self.assertFalse(self.plugin_ep.available)\n\n    def test_prepare_generic_plugin_error(self):\n        plugin = mock.MagicMock()\n        plugin.prepare.side_effect = errors.PluginError\n        # pylint: disable=protected-access\n        self.plugin_ep._initialized = plugin\n        self.assertTrue(isinstance(self.plugin_ep.prepare(), errors.PluginError))\n        self.assertTrue(self.plugin_ep.prepared)\n        self.assertFalse(self.plugin_ep.misconfigured)\n        self.assertFalse(self.plugin_ep.available)\n\n    def test_repr(self):\n        self.assertEqual(\"PluginEntryPoint#sa\", repr(self.plugin_ep))\n\n\nclass PluginsRegistryTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.plugins.disco.PluginsRegistry.\"\"\"\n\n    @classmethod\n    def _create_new_registry(cls, plugins):\n        from certbot._internal.plugins.disco import PluginsRegistry\n        return PluginsRegistry(plugins)\n\n    def setUp(self):\n        self.plugin_ep = mock.MagicMock()\n        self.plugin_ep.name = \"mock\"\n        self.plugin_ep.__hash__.side_effect = TypeError\n        self.plugins = {self.plugin_ep.name: self.plugin_ep}\n        self.reg = self._create_new_registry(self.plugins)\n\n    def test_find_all(self):\n        from certbot._internal.plugins.disco import PluginsRegistry\n        with mock.patch(\"certbot._internal.plugins.disco.pkg_resources\") as mock_pkg:\n            mock_pkg.iter_entry_points.side_effect = [iter([EP_SA]),\n                                                      iter([EP_WR])]\n            plugins = PluginsRegistry.find_all()\n        self.assertTrue(plugins[\"sa\"].plugin_cls is standalone.Authenticator)\n        self.assertTrue(plugins[\"sa\"].entry_point is EP_SA)\n        self.assertTrue(plugins[\"wr\"].plugin_cls is webroot.Authenticator)\n        self.assertTrue(plugins[\"wr\"].entry_point is EP_WR)\n\n    def test_getitem(self):\n        self.assertEqual(self.plugin_ep, self.reg[\"mock\"])\n\n    def test_iter(self):\n        self.assertEqual([\"mock\"], list(self.reg))\n\n    def test_len(self):\n        self.assertEqual(0, len(self._create_new_registry({})))\n        self.assertEqual(1, len(self.reg))\n\n    def test_init(self):\n        self.plugin_ep.init.return_value = \"baz\"\n        self.assertEqual([\"baz\"], self.reg.init(\"bar\"))\n        self.plugin_ep.init.assert_called_once_with(\"bar\")\n\n    def test_filter(self):\n        self.assertEqual(\n            self.plugins,\n            self.reg.filter(lambda p_ep: p_ep.name.startswith(\"m\")))\n        self.assertEqual(\n            {}, self.reg.filter(lambda p_ep: p_ep.name.startswith(\"b\")))\n\n    def test_ifaces(self):\n        self.plugin_ep.ifaces.return_value = True\n        # pylint: disable=protected-access\n        self.assertEqual(self.plugins, self.reg.ifaces()._plugins)\n        self.plugin_ep.ifaces.return_value = False\n        self.assertEqual({}, self.reg.ifaces()._plugins)\n\n    def test_verify(self):\n        self.plugin_ep.verify.return_value = True\n        # pylint: disable=protected-access\n        self.assertEqual(\n            self.plugins, self.reg.verify(mock.MagicMock())._plugins)\n        self.plugin_ep.verify.return_value = False\n        self.assertEqual({}, self.reg.verify(mock.MagicMock())._plugins)\n\n    def test_prepare(self):\n        self.plugin_ep.prepare.return_value = \"baz\"\n        self.assertEqual([\"baz\"], self.reg.prepare())\n        self.plugin_ep.prepare.assert_called_once_with()\n\n    def test_prepare_order(self):\n        order = []  # type: List[str]\n        plugins = dict(\n            (c, mock.MagicMock(prepare=functools.partial(order.append, c)))\n            for c in string.ascii_letters)\n        reg = self._create_new_registry(plugins)\n        reg.prepare()\n        # order of prepare calls must be sorted to prevent deadlock\n        # caused by plugins acquiring locks during prepare\n        self.assertEqual(order, sorted(string.ascii_letters))\n\n    def test_available(self):\n        self.plugin_ep.available = True\n        # pylint: disable=protected-access\n        self.assertEqual(self.plugins, self.reg.available()._plugins)\n        self.plugin_ep.available = False\n        self.assertEqual({}, self.reg.available()._plugins)\n\n    def test_find_init(self):\n        self.assertTrue(self.reg.find_init(mock.Mock()) is None)\n        self.plugin_ep.initialized = True\n        self.assertTrue(\n            self.reg.find_init(self.plugin_ep.init()) is self.plugin_ep)\n\n    def test_repr(self):\n        self.plugin_ep.__repr__ = lambda _: \"PluginEntryPoint#mock\"\n        self.assertEqual(\"PluginsRegistry(PluginEntryPoint#mock)\",\n                         repr(self.reg))\n\n    def test_str(self):\n        self.assertEqual(\"No plugins\", str(self._create_new_registry({})))\n        self.plugin_ep.__str__ = lambda _: \"Mock\"\n        self.assertEqual(\"Mock\", str(self.reg))\n        plugins = {self.plugin_ep.name: self.plugin_ep, \"foo\": \"Bar\"}\n        reg = self._create_new_registry(plugins)\n        self.assertEqual(\"Bar\\n\\nMock\", str(reg))\n\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/plugins/dns_common_lexicon_test.py",
    "content": "\"\"\"Tests for certbot.plugins.dns_common_lexicon.\"\"\"\n\nimport unittest\n\nimport mock\n\nfrom certbot.plugins import dns_common_lexicon\nfrom certbot.plugins import dns_test_common_lexicon\n\n\nclass LexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconClientTest):\n\n    class _FakeLexiconClient(dns_common_lexicon.LexiconClient):\n        pass\n\n    def setUp(self):\n        super(LexiconClientTest, self).setUp()\n\n        self.client = LexiconClientTest._FakeLexiconClient()\n        self.provider_mock = mock.MagicMock()\n\n        self.client.provider = self.provider_mock\n\n\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/plugins/dns_common_test.py",
    "content": "\"\"\"Tests for certbot.plugins.dns_common.\"\"\"\n\nimport collections\nimport logging\nimport unittest\n\nimport mock\n\nfrom certbot import errors\nfrom certbot import util\nfrom certbot.compat import os\nfrom certbot.display import util as display_util\nfrom certbot.plugins import dns_common\nfrom certbot.plugins import dns_test_common\nfrom certbot.tests import util as test_util\n\n\nclass DNSAuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest):\n    # pylint: disable=protected-access\n\n    class _FakeDNSAuthenticator(dns_common.DNSAuthenticator):\n        _setup_credentials = mock.MagicMock()\n        _perform = mock.MagicMock()\n        _cleanup = mock.MagicMock()\n\n        def more_info(self):  # pylint: disable=missing-docstring,no-self-use\n            return 'A fake authenticator for testing.'\n\n    class _FakeConfig(object):\n        fake_propagation_seconds = 0\n        fake_config_key = 1\n        fake_other_key = None\n        fake_file_path = None\n\n    def setUp(self):\n        super(DNSAuthenticatorTest, self).setUp()\n\n        self.config = DNSAuthenticatorTest._FakeConfig()\n\n        self.auth = DNSAuthenticatorTest._FakeDNSAuthenticator(self.config, \"fake\")\n\n    def test_perform(self):\n        self.auth.perform([self.achall])\n\n        self.auth._perform.assert_called_once_with(dns_test_common.DOMAIN, mock.ANY, mock.ANY)\n\n    def test_cleanup(self):\n        self.auth._attempt_cleanup = True\n\n        self.auth.cleanup([self.achall])\n\n        self.auth._cleanup.assert_called_once_with(dns_test_common.DOMAIN, mock.ANY, mock.ANY)\n\n    @test_util.patch_get_utility()\n    def test_prompt(self, mock_get_utility):\n        mock_display = mock_get_utility()\n        mock_display.input.side_effect = ((display_util.OK, \"\",),\n                                          (display_util.OK, \"value\",))\n\n        self.auth._configure(\"other_key\", \"\")\n        self.assertEqual(self.auth.config.fake_other_key, \"value\")\n\n    @test_util.patch_get_utility()\n    def test_prompt_canceled(self, mock_get_utility):\n        mock_display = mock_get_utility()\n        mock_display.input.side_effect = ((display_util.CANCEL, \"c\",),)\n\n        self.assertRaises(errors.PluginError, self.auth._configure, \"other_key\", \"\")\n\n    @test_util.patch_get_utility()\n    def test_prompt_file(self, mock_get_utility):\n        path = os.path.join(self.tempdir, 'file.ini')\n        open(path, \"wb\").close()\n\n        mock_display = mock_get_utility()\n        mock_display.directory_select.side_effect = ((display_util.OK, \"\",),\n                                                     (display_util.OK, \"not-a-file.ini\",),\n                                                     (display_util.OK, self.tempdir),\n                                                     (display_util.OK, path,))\n\n        self.auth._configure_file(\"file_path\", \"\")\n        self.assertEqual(self.auth.config.fake_file_path, path)\n\n    @test_util.patch_get_utility()\n    def test_prompt_file_canceled(self, mock_get_utility):\n        mock_display = mock_get_utility()\n        mock_display.directory_select.side_effect = ((display_util.CANCEL, \"c\",),)\n\n        self.assertRaises(errors.PluginError, self.auth._configure_file, \"file_path\", \"\")\n\n    def test_configure_credentials(self):\n        path = os.path.join(self.tempdir, 'file.ini')\n        dns_test_common.write({\"fake_test\": \"value\"}, path)\n        setattr(self.config, \"fake_credentials\", path)\n\n        credentials = self.auth._configure_credentials(\"credentials\", \"\", {\"test\": \"\"})\n\n        self.assertEqual(credentials.conf(\"test\"), \"value\")\n\n    @test_util.patch_get_utility()\n    def test_prompt_credentials(self, mock_get_utility):\n        bad_path = os.path.join(self.tempdir, 'bad-file.ini')\n        dns_test_common.write({\"fake_other\": \"other_value\"}, bad_path)\n\n        path = os.path.join(self.tempdir, 'file.ini')\n        dns_test_common.write({\"fake_test\": \"value\"}, path)\n        setattr(self.config, \"fake_credentials\", \"\")\n\n        mock_display = mock_get_utility()\n        mock_display.directory_select.side_effect = ((display_util.OK, \"\",),\n                                                     (display_util.OK, \"not-a-file.ini\",),\n                                                     (display_util.OK, self.tempdir),\n                                                     (display_util.OK, bad_path),\n                                                     (display_util.OK, path,))\n\n        credentials = self.auth._configure_credentials(\"credentials\", \"\", {\"test\": \"\"})\n        self.assertEqual(credentials.conf(\"test\"), \"value\")\n\n\nclass CredentialsConfigurationTest(test_util.TempDirTestCase):\n    class _MockLoggingHandler(logging.Handler):\n        messages = None\n\n        def __init__(self, *args, **kwargs):\n            self.reset()\n            logging.Handler.__init__(self, *args, **kwargs)\n\n        def emit(self, record):\n            self.messages[record.levelname.lower()].append(record.getMessage())\n\n        def reset(self):\n            \"\"\"Allows the handler to be reset between tests.\"\"\"\n            self.messages = collections.defaultdict(list)\n\n    def test_valid_file(self):\n        path = os.path.join(self.tempdir, 'too-permissive-file.ini')\n\n        dns_test_common.write({\"test\": \"value\", \"other\": 1}, path)\n\n        credentials_configuration = dns_common.CredentialsConfiguration(path)\n        self.assertEqual(\"value\", credentials_configuration.conf(\"test\"))\n        self.assertEqual(\"1\", credentials_configuration.conf(\"other\"))\n\n    def test_nonexistent_file(self):\n        path = os.path.join(self.tempdir, 'not-a-file.ini')\n\n        self.assertRaises(errors.PluginError, dns_common.CredentialsConfiguration, path)\n\n    def test_valid_file_with_unsafe_permissions(self):\n        log = self._MockLoggingHandler()\n        dns_common.logger.addHandler(log)\n\n        path = os.path.join(self.tempdir, 'too-permissive-file.ini')\n        util.safe_open(path, \"wb\", 0o744).close()\n\n        dns_common.CredentialsConfiguration(path)\n\n        self.assertEqual(1, len([_ for _ in log.messages['warning'] if _.startswith(\"Unsafe\")]))\n\n\nclass CredentialsConfigurationRequireTest(test_util.TempDirTestCase):\n\n    def setUp(self):\n        super(CredentialsConfigurationRequireTest, self).setUp()\n\n        self.path = os.path.join(self.tempdir, 'file.ini')\n\n    def _write(self, values):\n        dns_test_common.write(values, self.path)\n\n    def test_valid(self):\n        self._write({\"test\": \"value\", \"other\": 1})\n\n        credentials_configuration = dns_common.CredentialsConfiguration(self.path)\n        credentials_configuration.require({\"test\": \"\", \"other\": \"\"})\n\n    def test_valid_but_extra(self):\n        self._write({\"test\": \"value\", \"other\": 1})\n\n        credentials_configuration = dns_common.CredentialsConfiguration(self.path)\n        credentials_configuration.require({\"test\": \"\"})\n\n    def test_valid_empty(self):\n        self._write({})\n\n        credentials_configuration = dns_common.CredentialsConfiguration(self.path)\n        credentials_configuration.require({})\n\n    def test_missing(self):\n        self._write({})\n\n        credentials_configuration = dns_common.CredentialsConfiguration(self.path)\n        self.assertRaises(errors.PluginError, credentials_configuration.require, {\"test\": \"\"})\n\n    def test_blank(self):\n        self._write({\"test\": \"\"})\n\n        credentials_configuration = dns_common.CredentialsConfiguration(self.path)\n        self.assertRaises(errors.PluginError, credentials_configuration.require, {\"test\": \"\"})\n\n    def test_typo(self):\n        self._write({\"tets\": \"typo!\"})\n\n        credentials_configuration = dns_common.CredentialsConfiguration(self.path)\n        self.assertRaises(errors.PluginError, credentials_configuration.require, {\"test\": \"\"})\n\n\nclass DomainNameGuessTest(unittest.TestCase):\n\n    def test_simple_case(self):\n        self.assertTrue(\n            'example.com' in\n            dns_common.base_domain_name_guesses(\"example.com\")\n        )\n\n    def test_sub_domain(self):\n        self.assertTrue(\n            'example.com' in\n            dns_common.base_domain_name_guesses(\"foo.bar.baz.example.com\")\n        )\n\n    def test_second_level_domain(self):\n        self.assertTrue(\n            'example.co.uk' in\n            dns_common.base_domain_name_guesses(\"foo.bar.baz.example.co.uk\")\n        )\n\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/plugins/enhancements_test.py",
    "content": "\"\"\"Tests for new style enhancements\"\"\"\nimport unittest\n\nimport mock\n\nfrom certbot._internal.plugins import null\nfrom certbot.plugins import enhancements\nimport certbot.tests.util as test_util\n\n\nclass EnhancementTest(test_util.ConfigTestCase):\n    \"\"\"Tests for new style enhancements in certbot.plugins.enhancements\"\"\"\n\n    def setUp(self):\n        super(EnhancementTest, self).setUp()\n        self.mockinstaller = mock.MagicMock(spec=enhancements.AutoHSTSEnhancement)\n\n\n    @test_util.patch_get_utility()\n    def test_enhancement_enabled_enhancements(self, _):\n        FAKEINDEX = [\n            {\n                \"name\": \"autohsts\",\n                \"cli_dest\": \"auto_hsts\",\n            },\n            {\n                \"name\": \"somethingelse\",\n                \"cli_dest\": \"something\",\n            }\n        ]\n        with mock.patch(\"certbot.plugins.enhancements._INDEX\", FAKEINDEX):\n            self.config.auto_hsts = True\n            self.config.something = True\n            enabled = list(enhancements.enabled_enhancements(self.config))\n        self.assertEqual(len(enabled), 2)\n        self.assertTrue([i for i in enabled if i[\"name\"] == \"autohsts\"])\n        self.assertTrue([i for i in enabled if i[\"name\"] == \"somethingelse\"])\n\n    def test_are_requested(self):\n        self.assertEqual(len(list(enhancements.enabled_enhancements(self.config))), 0)\n        self.assertFalse(enhancements.are_requested(self.config))\n        self.config.auto_hsts = True\n        self.assertEqual(len(list(enhancements.enabled_enhancements(self.config))), 1)\n        self.assertTrue(enhancements.are_requested(self.config))\n\n    def test_are_supported(self):\n        self.config.auto_hsts = True\n        unsupported = null.Installer(self.config, \"null\")\n        self.assertTrue(enhancements.are_supported(self.config, self.mockinstaller))\n        self.assertFalse(enhancements.are_supported(self.config, unsupported))\n\n    def test_enable(self):\n        self.config.auto_hsts = True\n        domains = [\"example.com\", \"www.example.com\"]\n        lineage = \"lineage\"\n        enhancements.enable(lineage, domains, self.mockinstaller, self.config)\n        self.assertTrue(self.mockinstaller.enable_autohsts.called)\n        self.assertEqual(self.mockinstaller.enable_autohsts.call_args[0],\n                          (lineage, domains))\n\n\nif __name__ == '__main__':\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/plugins/manual_test.py",
    "content": "\"\"\"Tests for certbot._internal.plugins.manual\"\"\"\nimport sys\nimport unittest\n\nimport mock\nimport six\n\nfrom acme import challenges\nfrom certbot import errors\nfrom certbot.compat import filesystem\nfrom certbot.compat import os\nfrom certbot.tests import acme_util\nfrom certbot.tests import util as test_util\n\n\nclass AuthenticatorTest(test_util.TempDirTestCase):\n    \"\"\"Tests for certbot._internal.plugins.manual.Authenticator.\"\"\"\n\n    def setUp(self):\n        super(AuthenticatorTest, self).setUp()\n        self.http_achall = acme_util.HTTP01_A\n        self.dns_achall = acme_util.DNS01_A\n        self.dns_achall_2 = acme_util.DNS01_A_2\n        self.achalls = [self.http_achall, self.dns_achall, self.dns_achall_2]\n        for d in [\"config_dir\", \"work_dir\", \"in_progress\"]:\n            filesystem.mkdir(os.path.join(self.tempdir, d))\n            # \"backup_dir\" and \"temp_checkpoint_dir\" get created in\n            # certbot.util.make_or_verify_dir() during the Reverter\n            # initialization.\n        self.config = mock.MagicMock(\n            http01_port=0, manual_auth_hook=None, manual_cleanup_hook=None,\n            manual_public_ip_logging_ok=False, noninteractive_mode=False,\n            validate_hooks=False,\n            config_dir=os.path.join(self.tempdir, \"config_dir\"),\n            work_dir=os.path.join(self.tempdir, \"work_dir\"),\n            backup_dir=os.path.join(self.tempdir, \"backup_dir\"),\n            temp_checkpoint_dir=os.path.join(\n                                        self.tempdir, \"temp_checkpoint_dir\"),\n            in_progress_dir=os.path.join(self.tempdir, \"in_progess\"))\n\n        from certbot._internal.plugins.manual import Authenticator\n        self.auth = Authenticator(self.config, name='manual')\n\n    def test_prepare_no_hook_noninteractive(self):\n        self.config.noninteractive_mode = True\n        self.assertRaises(errors.PluginError, self.auth.prepare)\n\n    def test_prepare_bad_hook(self):\n        self.config.manual_auth_hook = os.path.abspath(os.sep)  # is / on UNIX\n        self.config.validate_hooks = True\n        self.assertRaises(errors.HookCommandNotFound, self.auth.prepare)\n\n    def test_more_info(self):\n        self.assertTrue(isinstance(self.auth.more_info(), six.string_types))\n\n    def test_get_chall_pref(self):\n        self.assertEqual(self.auth.get_chall_pref('example.org'),\n                         [challenges.HTTP01, challenges.DNS01])\n\n    @test_util.patch_get_utility()\n    def test_ip_logging_not_ok(self, mock_get_utility):\n        mock_get_utility().yesno.return_value = False\n        self.assertRaises(errors.PluginError, self.auth.perform, [])\n\n    @test_util.patch_get_utility()\n    def test_ip_logging_ok(self, mock_get_utility):\n        mock_get_utility().yesno.return_value = True\n        self.auth.perform([])\n        self.assertTrue(self.config.manual_public_ip_logging_ok)\n\n    def test_script_perform(self):\n        self.config.manual_public_ip_logging_ok = True\n        self.config.manual_auth_hook = (\n            '{0} -c \"from __future__ import print_function;'\n            'from certbot.compat import os;  print(os.environ.get(\\'CERTBOT_DOMAIN\\'));'\n            'print(os.environ.get(\\'CERTBOT_TOKEN\\', \\'notoken\\'));'\n            'print(os.environ.get(\\'CERTBOT_VALIDATION\\', \\'novalidation\\'));\"'\n            .format(sys.executable))\n        dns_expected = '{0}\\n{1}\\n{2}'.format(\n            self.dns_achall.domain, 'notoken',\n            self.dns_achall.validation(self.dns_achall.account_key))\n        http_expected = '{0}\\n{1}\\n{2}'.format(\n            self.http_achall.domain, self.http_achall.chall.encode('token'),\n            self.http_achall.validation(self.http_achall.account_key))\n\n        self.assertEqual(\n            self.auth.perform(self.achalls),\n            [achall.response(achall.account_key) for achall in self.achalls])\n        self.assertEqual(\n            self.auth.env[self.dns_achall]['CERTBOT_AUTH_OUTPUT'],\n            dns_expected)\n        self.assertEqual(\n            self.auth.env[self.http_achall]['CERTBOT_AUTH_OUTPUT'],\n            http_expected)\n\n    @test_util.patch_get_utility()\n    def test_manual_perform(self, mock_get_utility):\n        self.config.manual_public_ip_logging_ok = True\n        self.assertEqual(\n            self.auth.perform(self.achalls),\n            [achall.response(achall.account_key) for achall in self.achalls])\n        for i, (args, kwargs) in enumerate(mock_get_utility().notification.call_args_list):\n            achall = self.achalls[i]\n            self.assertTrue(\n                achall.validation(achall.account_key) in args[0])\n            self.assertFalse(kwargs['wrap'])\n\n    def test_cleanup(self):\n        self.config.manual_public_ip_logging_ok = True\n        self.config.manual_auth_hook = ('{0} -c \"import sys; sys.stdout.write(\\'foo\\')\"'\n                                        .format(sys.executable))\n        self.config.manual_cleanup_hook = '# cleanup'\n        self.auth.perform(self.achalls)\n\n        for achall in self.achalls:\n            self.auth.cleanup([achall])\n            self.assertEqual(os.environ['CERTBOT_AUTH_OUTPUT'], 'foo')\n            self.assertEqual(os.environ['CERTBOT_DOMAIN'], achall.domain)\n            if isinstance(achall.chall, (challenges.HTTP01, challenges.DNS01)):\n                self.assertEqual(\n                    os.environ['CERTBOT_VALIDATION'],\n                    achall.validation(achall.account_key))\n            if isinstance(achall.chall, challenges.HTTP01):\n                self.assertEqual(\n                    os.environ['CERTBOT_TOKEN'],\n                    achall.chall.encode('token'))\n            else:\n                self.assertFalse('CERTBOT_TOKEN' in os.environ)\n\n\nif __name__ == '__main__':\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/plugins/null_test.py",
    "content": "\"\"\"Tests for certbot._internal.plugins.null.\"\"\"\nimport unittest\n\nimport mock\nimport six\n\n\nclass InstallerTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.plugins.null.Installer.\"\"\"\n\n    def setUp(self):\n        from certbot._internal.plugins.null import Installer\n        self.installer = Installer(config=mock.MagicMock(), name=\"null\")\n\n    def test_it(self):\n        self.assertTrue(isinstance(self.installer.more_info(), six.string_types))\n        self.assertEqual([], self.installer.get_all_names())\n        self.assertEqual([], self.installer.supported_enhancements())\n\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/plugins/selection_test.py",
    "content": "\"\"\"Tests for letsencrypt.plugins.selection\"\"\"\nimport sys\nimport unittest\n\nimport mock\nimport zope.component\n\nfrom acme.magic_typing import List  # pylint: disable=unused-import, no-name-in-module\nfrom certbot import errors\nfrom certbot import interfaces\nfrom certbot._internal.plugins.disco import PluginsRegistry\nfrom certbot.compat import os\nfrom certbot.display import util as display_util\nfrom certbot.tests import util as test_util\n\n\nclass ConveniencePickPluginTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.plugins.selection.pick_*.\"\"\"\n\n    def _test(self, fun, ifaces):\n        config = mock.Mock()\n        default = mock.Mock()\n        plugins = mock.Mock()\n\n        with mock.patch(\"certbot._internal.plugins.selection.pick_plugin\") as mock_p:\n            mock_p.return_value = \"foo\"\n            self.assertEqual(\"foo\", fun(config, default, plugins, \"Question?\"))\n        mock_p.assert_called_once_with(\n            config, default, plugins, \"Question?\", ifaces)\n\n    def test_authenticator(self):\n        from certbot._internal.plugins.selection import pick_authenticator\n        self._test(pick_authenticator, (interfaces.IAuthenticator,))\n\n    def test_installer(self):\n        from certbot._internal.plugins.selection import pick_installer\n        self._test(pick_installer, (interfaces.IInstaller,))\n\n    def test_configurator(self):\n        from certbot._internal.plugins.selection import pick_configurator\n        self._test(pick_configurator,\n            (interfaces.IAuthenticator, interfaces.IInstaller))\n\n\nclass PickPluginTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.plugins.selection.pick_plugin.\"\"\"\n\n    def setUp(self):\n        self.config = mock.Mock(noninteractive_mode=False)\n        self.default = None\n        self.reg = mock.MagicMock()\n        self.question = \"Question?\"\n        self.ifaces = []  # type: List[interfaces.IPlugin]\n\n    def _call(self):\n        from certbot._internal.plugins.selection import pick_plugin\n        return pick_plugin(self.config, self.default, self.reg,\n                           self.question, self.ifaces)\n\n    def test_default_provided(self):\n        self.default = \"foo\"\n        self._call()\n        self.assertEqual(1, self.reg.filter.call_count)\n\n    def test_no_default(self):\n        self._call()\n        self.assertEqual(1, self.reg.visible().ifaces.call_count)\n\n    def test_no_candidate(self):\n        self.assertTrue(self._call() is None)\n\n    def test_single(self):\n        plugin_ep = mock.MagicMock()\n        plugin_ep.init.return_value = \"foo\"\n        plugin_ep.misconfigured = False\n\n        self.reg.visible().ifaces().verify().available.return_value = {\n            \"bar\": plugin_ep}\n        self.assertEqual(\"foo\", self._call())\n\n    def test_single_misconfigured(self):\n        plugin_ep = mock.MagicMock()\n        plugin_ep.init.return_value = \"foo\"\n        plugin_ep.misconfigured = True\n\n        self.reg.visible().ifaces().verify().available.return_value = {\n            \"bar\": plugin_ep}\n        self.assertTrue(self._call() is None)\n\n    def test_multiple(self):\n        plugin_ep = mock.MagicMock()\n        plugin_ep.init.return_value = \"foo\"\n        self.reg.visible().ifaces().verify().available.return_value = {\n            \"bar\": plugin_ep,\n            \"baz\": plugin_ep,\n        }\n        with mock.patch(\"certbot._internal.plugins.selection.choose_plugin\") as mock_choose:\n            mock_choose.return_value = plugin_ep\n            self.assertEqual(\"foo\", self._call())\n        mock_choose.assert_called_once_with(\n            [plugin_ep, plugin_ep], self.question)\n\n    def test_choose_plugin_none(self):\n        self.reg.visible().ifaces().verify().available.return_value = {\n            \"bar\": None,\n            \"baz\": None,\n        }\n\n        with mock.patch(\"certbot._internal.plugins.selection.choose_plugin\") as mock_choose:\n            mock_choose.return_value = None\n            self.assertTrue(self._call() is None)\n\n\nclass ChoosePluginTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.plugins.selection.choose_plugin.\"\"\"\n\n    def setUp(self):\n        zope.component.provideUtility(display_util.FileDisplay(sys.stdout,\n                                                               False))\n        self.mock_apache = mock.Mock(\n            description_with_name=\"a\", misconfigured=True)\n        self.mock_apache.name = \"apache\"\n        self.mock_stand = mock.Mock(\n            description_with_name=\"s\", misconfigured=False)\n        self.mock_stand.init().more_info.return_value = \"standalone\"\n        self.plugins = [\n            self.mock_apache,\n            self.mock_stand,\n        ]\n\n    def _call(self):\n        from certbot._internal.plugins.selection import choose_plugin\n        return choose_plugin(self.plugins, \"Question?\")\n\n    @test_util.patch_get_utility(\"certbot._internal.plugins.selection.z_util\")\n    def test_selection(self, mock_util):\n        mock_util().menu.side_effect = [(display_util.OK, 0),\n                                        (display_util.OK, 1)]\n        self.assertEqual(self.mock_stand, self._call())\n        self.assertEqual(mock_util().notification.call_count, 1)\n\n    @test_util.patch_get_utility(\"certbot._internal.plugins.selection.z_util\")\n    def test_more_info(self, mock_util):\n        mock_util().menu.side_effect = [\n            (display_util.OK, 1),\n        ]\n\n        self.assertEqual(self.mock_stand, self._call())\n\n    @test_util.patch_get_utility(\"certbot._internal.plugins.selection.z_util\")\n    def test_no_choice(self, mock_util):\n        mock_util().menu.return_value = (display_util.CANCEL, 0)\n        self.assertTrue(self._call() is None)\n\n    @test_util.patch_get_utility(\"certbot._internal.plugins.selection.z_util\")\n    def test_new_interaction_avoidance(self, mock_util):\n        mock_nginx = mock.Mock(\n            description_with_name=\"n\", misconfigured=False)\n        mock_nginx.init().more_info.return_value = \"nginx plugin\"\n        mock_nginx.name = \"nginx\"\n        self.plugins[1] = mock_nginx\n        mock_util().menu.return_value = (display_util.CANCEL, 0)\n\n        unset_cb_auto = os.environ.get(\"CERTBOT_AUTO\") is None\n        if unset_cb_auto:\n            os.environ[\"CERTBOT_AUTO\"] = \"foo\"\n        try:\n            self._call()\n        finally:\n            if unset_cb_auto:\n                del os.environ[\"CERTBOT_AUTO\"]\n\n        self.assertTrue(\"default\" in mock_util().menu.call_args[1])\n\nclass GetUnpreparedInstallerTest(test_util.ConfigTestCase):\n    \"\"\"Tests for certbot._internal.plugins.selection.get_unprepared_installer.\"\"\"\n\n    def setUp(self):\n        super(GetUnpreparedInstallerTest, self).setUp()\n        self.mock_apache_fail_ep = mock.Mock(\n            description_with_name=\"afail\")\n        self.mock_apache_fail_ep.name = \"afail\"\n        self.mock_apache_ep = mock.Mock(\n            description_with_name=\"apache\")\n        self.mock_apache_ep.name = \"apache\"\n        self.mock_apache_plugin = mock.MagicMock()\n        self.mock_apache_ep.init.return_value = self.mock_apache_plugin\n        self.plugins = PluginsRegistry({\n            \"afail\": self.mock_apache_fail_ep,\n            \"apache\": self.mock_apache_ep,\n        })\n\n    def _call(self):\n        from certbot._internal.plugins.selection import get_unprepared_installer\n        return get_unprepared_installer(self.config, self.plugins)\n\n    def test_no_installer_defined(self):\n        self.config.configurator = None\n        self.assertEqual(self._call(), None)\n\n    def test_no_available_installers(self):\n        self.config.configurator = \"apache\"\n        self.plugins = PluginsRegistry({})\n        self.assertRaises(errors.PluginSelectionError, self._call)\n\n    def test_get_plugin(self):\n        self.config.configurator = \"apache\"\n        installer = self._call()\n        self.assertTrue(installer is self.mock_apache_plugin)\n\n    def test_multiple_installers_returned(self):\n        self.config.configurator = \"apache\"\n        # Two plugins with the same name\n        self.mock_apache_fail_ep.name = \"apache\"\n        self.assertRaises(errors.PluginSelectionError, self._call)\n\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/plugins/standalone_test.py",
    "content": "\"\"\"Tests for certbot._internal.plugins.standalone.\"\"\"\n# https://github.com/python/typeshed/blob/master/stdlib/2and3/socket.pyi\nimport socket\nfrom socket import errno as socket_errors  # type: ignore\nimport unittest\n\nimport josepy as jose\nimport mock\nimport OpenSSL.crypto  # pylint: disable=unused-import\nimport six\n\nfrom acme import challenges\nfrom acme import standalone as acme_standalone  # pylint: disable=unused-import\nfrom acme.magic_typing import Dict  # pylint: disable=unused-import, no-name-in-module\nfrom acme.magic_typing import Set  # pylint: disable=unused-import, no-name-in-module\nfrom acme.magic_typing import Tuple  # pylint: disable=unused-import, no-name-in-module\nfrom certbot import achallenges\nfrom certbot import errors\nfrom certbot.tests import acme_util\nfrom certbot.tests import util as test_util\n\n\nclass ServerManagerTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.plugins.standalone.ServerManager.\"\"\"\n\n    def setUp(self):\n        from certbot._internal.plugins.standalone import ServerManager\n        self.certs = {}  # type: Dict[bytes, Tuple[OpenSSL.crypto.PKey, OpenSSL.crypto.X509]]\n        self.http_01_resources = {} \\\n        # type: Set[acme_standalone.HTTP01RequestHandler.HTTP01Resource]\n        self.mgr = ServerManager(self.certs, self.http_01_resources)\n\n    def test_init(self):\n        self.assertTrue(self.mgr.certs is self.certs)\n        self.assertTrue(\n            self.mgr.http_01_resources is self.http_01_resources)\n\n    def _test_run_stop(self, challenge_type):\n        server = self.mgr.run(port=0, challenge_type=challenge_type)\n        port = server.getsocknames()[0][1]\n        self.assertEqual(self.mgr.running(), {port: server})\n        self.mgr.stop(port=port)\n        self.assertEqual(self.mgr.running(), {})\n\n    def test_run_stop_http_01(self):\n        self._test_run_stop(challenges.HTTP01)\n\n    def test_run_idempotent(self):\n        server = self.mgr.run(port=0, challenge_type=challenges.HTTP01)\n        port = server.getsocknames()[0][1]\n        server2 = self.mgr.run(port=port, challenge_type=challenges.HTTP01)\n        self.assertEqual(self.mgr.running(), {port: server})\n        self.assertTrue(server is server2)\n        self.mgr.stop(port)\n        self.assertEqual(self.mgr.running(), {})\n\n    def test_run_bind_error(self):\n        some_server = socket.socket(socket.AF_INET6)\n        some_server.bind((\"\", 0))\n        port = some_server.getsockname()[1]\n        maybe_another_server = socket.socket()\n        try:\n            maybe_another_server.bind((\"\", port))\n        except socket.error:\n            pass\n        self.assertRaises(\n            errors.StandaloneBindError, self.mgr.run, port,\n            challenge_type=challenges.HTTP01)\n        self.assertEqual(self.mgr.running(), {})\n        some_server.close()\n        maybe_another_server.close()\n\n\ndef get_open_port():\n    \"\"\"Gets an open port number from the OS.\"\"\"\n    open_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)\n    open_socket.bind((\"\", 0))\n    port = open_socket.getsockname()[1]\n    open_socket.close()\n    return port\n\n\nclass AuthenticatorTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.plugins.standalone.Authenticator.\"\"\"\n\n    def setUp(self):\n        from certbot._internal.plugins.standalone import Authenticator\n\n        self.config = mock.MagicMock(http01_port=get_open_port())\n        self.auth = Authenticator(self.config, name=\"standalone\")\n        self.auth.servers = mock.MagicMock()\n\n    def test_more_info(self):\n        self.assertTrue(isinstance(self.auth.more_info(), six.string_types))\n\n    def test_get_chall_pref(self):\n        self.assertEqual(self.auth.get_chall_pref(domain=None),\n                         [challenges.HTTP01])\n\n    def test_perform(self):\n        achalls = self._get_achalls()\n        response = self.auth.perform(achalls)\n\n        expected = [achall.response(achall.account_key) for achall in achalls]\n        self.assertEqual(response, expected)\n\n    @test_util.patch_get_utility()\n    def test_perform_eaddrinuse_retry(self, mock_get_utility):\n        mock_utility = mock_get_utility()\n        errno = socket_errors.EADDRINUSE\n        error = errors.StandaloneBindError(mock.MagicMock(errno=errno), -1)\n        self.auth.servers.run.side_effect = [error] + 2 * [mock.MagicMock()]\n        mock_yesno = mock_utility.yesno\n        mock_yesno.return_value = True\n\n        self.test_perform()\n        self._assert_correct_yesno_call(mock_yesno)\n\n    @test_util.patch_get_utility()\n    def test_perform_eaddrinuse_no_retry(self, mock_get_utility):\n        mock_utility = mock_get_utility()\n        mock_yesno = mock_utility.yesno\n        mock_yesno.return_value = False\n\n        errno = socket_errors.EADDRINUSE\n        self.assertRaises(errors.PluginError, self._fail_perform, errno)\n        self._assert_correct_yesno_call(mock_yesno)\n\n    def _assert_correct_yesno_call(self, mock_yesno):\n        yesno_args, yesno_kwargs = mock_yesno.call_args\n        self.assertTrue(\"in use\" in yesno_args[0])\n        self.assertFalse(yesno_kwargs.get(\"default\", True))\n\n    def test_perform_eacces(self):\n        errno = socket_errors.EACCES\n        self.assertRaises(errors.PluginError, self._fail_perform, errno)\n\n    def test_perform_unexpected_socket_error(self):\n        errno = socket_errors.ENOTCONN\n        self.assertRaises(\n            errors.StandaloneBindError, self._fail_perform, errno)\n\n    def _fail_perform(self, errno):\n        error = errors.StandaloneBindError(mock.MagicMock(errno=errno), -1)\n        self.auth.servers.run.side_effect = error\n        self.auth.perform(self._get_achalls())\n\n    @classmethod\n    def _get_achalls(cls):\n        domain = b'localhost'\n        key = jose.JWK.load(test_util.load_vector('rsa512_key.pem'))\n        http_01 = achallenges.KeyAuthorizationAnnotatedChallenge(\n            challb=acme_util.HTTP01_P, domain=domain, account_key=key)\n\n        return [http_01]\n\n    def test_cleanup(self):\n        self.auth.servers.running.return_value = {\n            1: \"server1\",\n            2: \"server2\",\n        }\n        self.auth.served[\"server1\"].add(\"chall1\")\n        self.auth.served[\"server2\"].update([\"chall2\", \"chall3\"])\n\n        self.auth.cleanup([\"chall1\"])\n        self.assertEqual(self.auth.served, {\n            \"server1\": set(), \"server2\": set([\"chall2\", \"chall3\"])})\n        self.auth.servers.stop.assert_called_once_with(1)\n\n        self.auth.servers.running.return_value = {\n            2: \"server2\",\n        }\n        self.auth.cleanup([\"chall2\"])\n        self.assertEqual(self.auth.served, {\n            \"server1\": set(), \"server2\": set([\"chall3\"])})\n        self.assertEqual(1, self.auth.servers.stop.call_count)\n\n        self.auth.cleanup([\"chall3\"])\n        self.assertEqual(self.auth.served, {\n            \"server1\": set(), \"server2\": set([])})\n        self.auth.servers.stop.assert_called_with(2)\n\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/plugins/storage_test.py",
    "content": "\"\"\"Tests for certbot.plugins.storage.PluginStorage\"\"\"\nimport json\nimport unittest\n\nimport mock\n\nfrom certbot import errors\nfrom certbot.compat import filesystem\nfrom certbot.compat import os\nfrom certbot.plugins import common\nfrom certbot.tests import util as test_util\n\n\nclass PluginStorageTest(test_util.ConfigTestCase):\n    \"\"\"Test for certbot.plugins.storage.PluginStorage\"\"\"\n\n    def setUp(self):\n        super(PluginStorageTest, self).setUp()\n        self.plugin_cls = common.Installer\n        filesystem.mkdir(self.config.config_dir)\n        with mock.patch(\"certbot.reverter.util\"):\n            self.plugin = self.plugin_cls(config=self.config, name=\"mockplugin\")\n\n    def test_load_errors_cant_read(self):\n        with open(os.path.join(self.config.config_dir,\n                               \".pluginstorage.json\"), \"w\") as fh:\n            fh.write(\"dummy\")\n        # When unable to read file that exists\n        mock_open = mock.mock_open()\n        mock_open.side_effect = IOError\n        self.plugin.storage.storagepath = os.path.join(self.config.config_dir,\n                                                       \".pluginstorage.json\")\n        with mock.patch(\"six.moves.builtins.open\", mock_open):\n            with mock.patch('certbot.compat.os.path.isfile', return_value=True):\n                with mock.patch(\"certbot.reverter.util\"):\n                    self.assertRaises(errors.PluginStorageError,\n                                      self.plugin.storage._load)  # pylint: disable=protected-access\n\n    def test_load_errors_empty(self):\n        with open(os.path.join(self.config.config_dir, \".pluginstorage.json\"), \"w\") as fh:\n            fh.write('')\n        with mock.patch(\"certbot.plugins.storage.logger.debug\") as mock_log:\n            # Should not error out but write a debug log line instead\n            with mock.patch(\"certbot.reverter.util\"):\n                nocontent = self.plugin_cls(self.config, \"mockplugin\")\n            self.assertRaises(KeyError,\n                              nocontent.storage.fetch, \"value\")\n            self.assertTrue(mock_log.called)\n            self.assertTrue(\"no values loaded\" in mock_log.call_args[0][0])\n\n    def test_load_errors_corrupted(self):\n        with open(os.path.join(self.config.config_dir,\n                               \".pluginstorage.json\"), \"w\") as fh:\n            fh.write('invalid json')\n        with mock.patch(\"certbot.plugins.storage.logger.error\") as mock_log:\n            with mock.patch(\"certbot.reverter.util\"):\n                corrupted = self.plugin_cls(self.config, \"mockplugin\")\n            self.assertRaises(errors.PluginError,\n                              corrupted.storage.fetch,\n                              \"value\")\n            self.assertTrue(\"is corrupted\" in mock_log.call_args[0][0])\n\n    def test_save_errors_cant_serialize(self):\n        with mock.patch(\"certbot.plugins.storage.logger.error\") as mock_log:\n            # Set data as something that can't be serialized\n            self.plugin.storage._initialized = True  # pylint: disable=protected-access\n            self.plugin.storage.storagepath = \"/tmp/whatever\"\n            self.plugin.storage._data = self.plugin_cls  # pylint: disable=protected-access\n            self.assertRaises(errors.PluginStorageError,\n                              self.plugin.storage.save)\n            self.assertTrue(\"Could not serialize\" in mock_log.call_args[0][0])\n\n    def test_save_errors_unable_to_write_file(self):\n        mock_open = mock.mock_open()\n        mock_open.side_effect = IOError\n        with mock.patch(\"certbot.compat.filesystem.open\", mock_open):\n            with mock.patch(\"certbot.plugins.storage.logger.error\") as mock_log:\n                self.plugin.storage._data = {\"valid\": \"data\"}  # pylint: disable=protected-access\n                self.plugin.storage._initialized = True  # pylint: disable=protected-access\n                self.assertRaises(errors.PluginStorageError,\n                                  self.plugin.storage.save)\n                self.assertTrue(\"Could not write\" in mock_log.call_args[0][0])\n\n    def test_save_uninitialized(self):\n        with mock.patch(\"certbot.reverter.util\"):\n            self.assertRaises(errors.PluginStorageError,\n                              self.plugin_cls(self.config, \"x\").storage.save)\n\n    def test_namespace_isolation(self):\n        with mock.patch(\"certbot.reverter.util\"):\n            plugin1 = self.plugin_cls(self.config, \"first\")\n            plugin2 = self.plugin_cls(self.config, \"second\")\n        plugin1.storage.put(\"first_key\", \"first_value\")\n        self.assertRaises(KeyError,\n                          plugin2.storage.fetch, \"first_key\")\n        self.assertRaises(KeyError,\n                          plugin2.storage.fetch, \"first\")\n        self.assertEqual(plugin1.storage.fetch(\"first_key\"), \"first_value\")\n\n\n    def test_saved_state(self):\n        self.plugin.storage.put(\"testkey\", \"testvalue\")\n        # Write to disk\n        self.plugin.storage.save()\n        with mock.patch(\"certbot.reverter.util\"):\n            another = self.plugin_cls(self.config, \"mockplugin\")\n        self.assertEqual(another.storage.fetch(\"testkey\"), \"testvalue\")\n\n        with open(os.path.join(self.config.config_dir,\n                               \".pluginstorage.json\"), 'r') as fh:\n            psdata = fh.read()\n        psjson = json.loads(psdata)\n        self.assertTrue(\"mockplugin\" in psjson.keys())\n        self.assertEqual(len(psjson), 1)\n        self.assertEqual(psjson[\"mockplugin\"][\"testkey\"], \"testvalue\")\n\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/plugins/util_test.py",
    "content": "\"\"\"Tests for certbot.plugins.util.\"\"\"\nimport unittest\n\nimport mock\n\nfrom certbot.compat import os\n\n\nclass GetPrefixTest(unittest.TestCase):\n    \"\"\"Tests for certbot.plugins.get_prefixes.\"\"\"\n    def test_get_prefix(self):\n        from certbot.plugins.util import get_prefixes\n        self.assertEqual(\n            get_prefixes('/a/b/c'),\n            [os.path.normpath(path) for path in ['/a/b/c', '/a/b', '/a', '/']])\n        self.assertEqual(get_prefixes('/'), [os.path.normpath('/')])\n        self.assertEqual(get_prefixes('a'), ['a'])\n\n\nclass PathSurgeryTest(unittest.TestCase):\n    \"\"\"Tests for certbot.plugins.path_surgery.\"\"\"\n\n    @mock.patch(\"certbot.plugins.util.logger.debug\")\n    def test_path_surgery(self, mock_debug):\n        from certbot.plugins.util import path_surgery\n        all_path = {\"PATH\": \"/usr/local/bin:/bin/:/usr/sbin/:/usr/local/sbin/\"}\n        with mock.patch.dict('os.environ', all_path):\n            with mock.patch('certbot.util.exe_exists') as mock_exists:\n                mock_exists.return_value = True\n                self.assertEqual(path_surgery(\"eg\"), True)\n                self.assertEqual(mock_debug.call_count, 0)\n                self.assertEqual(os.environ[\"PATH\"], all_path[\"PATH\"])\n        if os.name != 'nt':\n            # This part is specific to Linux since on Windows no PATH surgery is ever done.\n            no_path = {\"PATH\": \"/tmp/\"}\n            with mock.patch.dict('os.environ', no_path):\n                path_surgery(\"thingy\")\n                self.assertEqual(mock_debug.call_count, 2 if os.name != 'nt' else 1)\n                self.assertTrue(\"Failed to find\" in mock_debug.call_args[0][0])\n                self.assertTrue(\"/usr/local/bin\" in os.environ[\"PATH\"])\n                self.assertTrue(\"/tmp\" in os.environ[\"PATH\"])\n\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/plugins/webroot_test.py",
    "content": "\"\"\"Tests for certbot._internal.plugins.webroot.\"\"\"\n\nfrom __future__ import print_function\n\nimport argparse\nimport errno\nimport json\nimport shutil\nimport tempfile\nimport unittest\n\nimport josepy as jose\nimport mock\nimport six\n\nfrom acme import challenges\nfrom certbot import achallenges\nfrom certbot import errors\nfrom certbot.compat import filesystem\nfrom certbot.compat import os\nfrom certbot.display import util as display_util\nfrom certbot.tests import acme_util\nfrom certbot.tests import util as test_util\n\nKEY = jose.JWKRSA.load(test_util.load_vector(\"rsa512_key.pem\"))\n\n\nclass AuthenticatorTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.plugins.webroot.Authenticator.\"\"\"\n\n    achall = achallenges.KeyAuthorizationAnnotatedChallenge(\n        challb=acme_util.HTTP01_P, domain=\"thing.com\", account_key=KEY)\n\n    def setUp(self):\n        from certbot._internal.plugins.webroot import Authenticator\n        # On Linux directories created by tempfile.mkdtemp inherit their permissions from their\n        # parent directory. So the actual permissions are inconsistent over various tests env.\n        # To circumvent this, a dedicated sub-workspace is created under the workspace, using\n        # filesystem.mkdir to get consistent permissions.\n        self.workspace = tempfile.mkdtemp()\n        self.path = os.path.join(self.workspace, 'webroot')\n        filesystem.mkdir(self.path)\n        self.partial_root_challenge_path = os.path.join(\n            self.path, \".well-known\")\n        self.root_challenge_path = os.path.join(\n            self.path, \".well-known\", \"acme-challenge\")\n        self.validation_path = os.path.join(\n            self.root_challenge_path,\n            \"ZXZhR3hmQURzNnBTUmIyTEF2OUlaZjE3RHQzanV4R0orUEN0OTJ3citvQQ\")\n        self.config = mock.MagicMock(webroot_path=self.path,\n                                     webroot_map={\"thing.com\": self.path})\n        self.auth = Authenticator(self.config, \"webroot\")\n\n    def tearDown(self):\n        shutil.rmtree(self.path)\n\n    def test_more_info(self):\n        more_info = self.auth.more_info()\n        self.assertTrue(isinstance(more_info, six.string_types))\n        self.assertTrue(self.path in more_info)\n\n    def test_add_parser_arguments(self):\n        add = mock.MagicMock()\n        self.auth.add_parser_arguments(add)\n        self.assertEqual(2, add.call_count)\n\n    def test_prepare(self):\n        self.auth.prepare()  # shouldn't raise any exceptions\n\n    @test_util.patch_get_utility()\n    def test_webroot_from_list(self, mock_get_utility):\n        self.config.webroot_path = []\n        self.config.webroot_map = {\"otherthing.com\": self.path}\n        mock_display = mock_get_utility()\n        mock_display.menu.return_value = (display_util.OK, 1,)\n\n        self.auth.perform([self.achall])\n        self.assertTrue(mock_display.menu.called)\n        for call in mock_display.menu.call_args_list:\n            self.assertTrue(self.achall.domain in call[0][0])\n            self.assertTrue(all(\n                webroot in call[0][1]\n                for webroot in six.itervalues(self.config.webroot_map)))\n        self.assertEqual(self.config.webroot_map[self.achall.domain],\n                         self.path)\n\n    @test_util.patch_get_utility()\n    def test_webroot_from_list_help_and_cancel(self, mock_get_utility):\n        self.config.webroot_path = []\n        self.config.webroot_map = {\"otherthing.com\": self.path}\n\n        mock_display = mock_get_utility()\n        mock_display.menu.side_effect = ((display_util.CANCEL, -1),)\n        self.assertRaises(errors.PluginError, self.auth.perform, [self.achall])\n        self.assertTrue(mock_display.menu.called)\n        for call in mock_display.menu.call_args_list:\n            self.assertTrue(self.achall.domain in call[0][0])\n            self.assertTrue(all(\n                webroot in call[0][1]\n                for webroot in six.itervalues(self.config.webroot_map)))\n\n    @test_util.patch_get_utility()\n    def test_new_webroot(self, mock_get_utility):\n        self.config.webroot_path = []\n        self.config.webroot_map = {\"something.com\": self.path}\n\n        mock_display = mock_get_utility()\n        mock_display.menu.return_value = (display_util.OK, 0,)\n        with mock.patch('certbot.display.ops.validated_directory') as m:\n            m.side_effect = ((display_util.CANCEL, -1),\n                             (display_util.OK, self.path,))\n\n            self.auth.perform([self.achall])\n\n        self.assertEqual(self.config.webroot_map[self.achall.domain], self.path)\n\n    @test_util.patch_get_utility()\n    def test_new_webroot_empty_map_cancel(self, mock_get_utility):\n        self.config.webroot_path = []\n        self.config.webroot_map = {}\n\n        mock_display = mock_get_utility()\n        mock_display.menu.return_value = (display_util.OK, 0,)\n        with mock.patch('certbot.display.ops.validated_directory') as m:\n            m.return_value = (display_util.CANCEL, -1)\n            self.assertRaises(errors.PluginError,\n                              self.auth.perform,\n                              [self.achall])\n\n    def test_perform_missing_root(self):\n        self.config.webroot_path = None\n        self.config.webroot_map = {}\n        self.assertRaises(errors.PluginError, self.auth.perform, [])\n\n    def test_perform_reraises_other_errors(self):\n        self.auth.full_path = os.path.join(self.path, \"null\")\n        permission_canary = os.path.join(self.path, \"rnd\")\n        with open(permission_canary, \"w\") as f:\n            f.write(\"thingimy\")\n        filesystem.chmod(self.path, 0o000)\n        try:\n            open(permission_canary, \"r\")\n            print(\"Warning, running tests as root skips permissions tests...\")\n        except IOError:\n            # ok, permissions work, test away...\n            self.assertRaises(errors.PluginError, self.auth.perform, [])\n        filesystem.chmod(self.path, 0o700)\n\n    @mock.patch(\"certbot._internal.plugins.webroot.filesystem.copy_ownership_and_apply_mode\")\n    def test_failed_chown(self, mock_ownership):\n        mock_ownership.side_effect = OSError(errno.EACCES, \"msg\")\n        self.auth.perform([self.achall])  # exception caught and logged\n\n    @test_util.patch_get_utility()\n    def test_perform_new_webroot_not_in_map(self, mock_get_utility):\n        new_webroot = tempfile.mkdtemp()\n        self.config.webroot_path = []\n        self.config.webroot_map = {\"whatever.com\": self.path}\n        mock_display = mock_get_utility()\n        mock_display.menu.side_effect = ((display_util.OK, 0),\n                                         (display_util.OK, new_webroot))\n        achall = achallenges.KeyAuthorizationAnnotatedChallenge(\n            challb=acme_util.HTTP01_P, domain=\"something.com\", account_key=KEY)\n        with mock.patch('certbot.display.ops.validated_directory') as m:\n            m.return_value = (display_util.OK, new_webroot,)\n            self.auth.perform([achall])\n        self.assertEqual(self.config.webroot_map[achall.domain], new_webroot)\n\n    def test_perform_permissions(self):\n        self.auth.prepare()\n\n        # Remove exec bit from permission check, so that it\n        # matches the file\n        self.auth.perform([self.achall])\n        self.assertTrue(filesystem.check_mode(self.validation_path, 0o644))\n\n        # Check permissions of the directories\n        for dirpath, dirnames, _ in os.walk(self.path):\n            for directory in dirnames:\n                full_path = os.path.join(dirpath, directory)\n                self.assertTrue(filesystem.check_mode(full_path, 0o755))\n\n        self.assertTrue(filesystem.has_same_ownership(self.validation_path, self.path))\n\n    def test_perform_cleanup(self):\n        self.auth.prepare()\n        responses = self.auth.perform([self.achall])\n        self.assertEqual(1, len(responses))\n        self.assertTrue(os.path.exists(self.validation_path))\n        with open(self.validation_path) as validation_f:\n            validation = validation_f.read()\n        self.assertTrue(\n            challenges.KeyAuthorizationChallengeResponse(\n                key_authorization=validation).verify(\n                    self.achall.chall, KEY.public_key()))\n\n        self.auth.cleanup([self.achall])\n        self.assertFalse(os.path.exists(self.validation_path))\n        self.assertFalse(os.path.exists(self.root_challenge_path))\n        self.assertFalse(os.path.exists(self.partial_root_challenge_path))\n\n    def test_perform_cleanup_existing_dirs(self):\n        filesystem.mkdir(self.partial_root_challenge_path)\n        self.auth.prepare()\n        self.auth.perform([self.achall])\n        self.auth.cleanup([self.achall])\n\n        # Ensure we don't \"clean up\" directories that previously existed\n        self.assertFalse(os.path.exists(self.validation_path))\n        self.assertFalse(os.path.exists(self.root_challenge_path))\n\n    def test_perform_cleanup_multiple_challenges(self):\n        bingo_achall = achallenges.KeyAuthorizationAnnotatedChallenge(\n            challb=acme_util.chall_to_challb(\n                challenges.HTTP01(token=b\"bingo\"), \"pending\"),\n            domain=\"thing.com\", account_key=KEY)\n\n        bingo_validation_path = \"YmluZ28\"\n        filesystem.mkdir(self.partial_root_challenge_path)\n        self.auth.prepare()\n        self.auth.perform([bingo_achall, self.achall])\n\n        self.auth.cleanup([self.achall])\n        self.assertFalse(os.path.exists(bingo_validation_path))\n        self.assertTrue(os.path.exists(self.root_challenge_path))\n        self.auth.cleanup([bingo_achall])\n        self.assertFalse(os.path.exists(self.validation_path))\n        self.assertFalse(os.path.exists(self.root_challenge_path))\n\n    def test_cleanup_leftovers(self):\n        self.auth.prepare()\n        self.auth.perform([self.achall])\n\n        leftover_path = os.path.join(self.root_challenge_path, 'leftover')\n        filesystem.mkdir(leftover_path)\n\n        self.auth.cleanup([self.achall])\n        self.assertFalse(os.path.exists(self.validation_path))\n        self.assertTrue(os.path.exists(self.root_challenge_path))\n\n        os.rmdir(leftover_path)\n\n    @mock.patch('certbot.compat.os.rmdir')\n    def test_cleanup_failure(self, mock_rmdir):\n        self.auth.prepare()\n        self.auth.perform([self.achall])\n\n        os_error = OSError()\n        os_error.errno = errno.EACCES\n        mock_rmdir.side_effect = os_error\n\n        self.auth.cleanup([self.achall])\n        self.assertFalse(os.path.exists(self.validation_path))\n        self.assertTrue(os.path.exists(self.root_challenge_path))\n\n\nclass WebrootActionTest(unittest.TestCase):\n    \"\"\"Tests for webroot argparse actions.\"\"\"\n\n    achall = achallenges.KeyAuthorizationAnnotatedChallenge(\n        challb=acme_util.HTTP01_P, domain=\"thing.com\", account_key=KEY)\n\n    def setUp(self):\n        from certbot._internal.plugins.webroot import Authenticator\n        self.path = tempfile.mkdtemp()\n        self.parser = argparse.ArgumentParser()\n        self.parser.add_argument(\"-d\", \"--domains\",\n                                 action=\"append\", default=[])\n        Authenticator.inject_parser_options(self.parser, \"webroot\")\n\n    def test_webroot_map_action(self):\n        args = self.parser.parse_args(\n            [\"--webroot-map\", json.dumps({'thing.com': self.path})])\n        self.assertEqual(args.webroot_map[\"thing.com\"], self.path)\n\n    def test_domain_before_webroot(self):\n        args = self.parser.parse_args(\n            \"-d {0} -w {1}\".format(self.achall.domain, self.path).split())\n        config = self._get_config_after_perform(args)\n        self.assertEqual(config.webroot_map[self.achall.domain], self.path)\n\n    def test_domain_before_webroot_error(self):\n        self.assertRaises(errors.PluginError, self.parser.parse_args,\n                          \"-d foo -w bar -w baz\".split())\n        self.assertRaises(errors.PluginError, self.parser.parse_args,\n                          \"-d foo -w bar -d baz -w qux\".split())\n\n    def test_multiwebroot(self):\n        args = self.parser.parse_args(\"-w {0} -d {1} -w {2} -d bar\".format(\n            self.path, self.achall.domain, tempfile.mkdtemp()).split())\n        self.assertEqual(args.webroot_map[self.achall.domain], self.path)\n        config = self._get_config_after_perform(args)\n        self.assertEqual(\n            config.webroot_map[self.achall.domain], self.path)\n\n    def test_webroot_map_partial_without_perform(self):\n        # This test acknowledges the fact that webroot_map content will be partial if webroot\n        # plugin perform method is not invoked (corner case when all auths are already valid).\n        # To not be a problem, the webroot_path must always been conserved during renew.\n        # This condition is challenged by:\n        # certbot.tests.renewal_tests::RenewalTest::test_webroot_params_conservation\n        # See https://github.com/certbot/certbot/pull/7095 for details.\n        other_webroot_path = tempfile.mkdtemp()\n        args = self.parser.parse_args(\"-w {0} -d {1} -w {2} -d bar\".format(\n            self.path, self.achall.domain, other_webroot_path).split())\n        self.assertEqual(args.webroot_map, {self.achall.domain: self.path})\n        self.assertEqual(args.webroot_path, [self.path, other_webroot_path])\n\n    def _get_config_after_perform(self, config):\n        from certbot._internal.plugins.webroot import Authenticator\n        auth = Authenticator(config, \"webroot\")\n        auth.perform([self.achall])\n        return auth.config\n\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/renewal_test.py",
    "content": "\"\"\"Tests for certbot._internal.renewal\"\"\"\nimport unittest\n\nimport mock\n\nfrom acme import challenges\nfrom certbot import errors\nfrom certbot._internal import configuration\nfrom certbot._internal import storage\nimport certbot.tests.util as test_util\n\n\nclass RenewalTest(test_util.ConfigTestCase):\n    @mock.patch('certbot._internal.cli.set_by_cli')\n    def test_ancient_webroot_renewal_conf(self, mock_set_by_cli):\n        mock_set_by_cli.return_value = False\n        rc_path = test_util.make_lineage(\n            self.config.config_dir, 'sample-renewal-ancient.conf')\n        self.config.account = None\n        self.config.email = None\n        self.config.webroot_path = None\n        config = configuration.NamespaceConfig(self.config)\n        lineage = storage.RenewableCert(rc_path, config)\n        renewalparams = lineage.configuration['renewalparams']\n        # pylint: disable=protected-access\n        from certbot._internal import renewal\n        renewal._restore_webroot_config(config, renewalparams)\n        self.assertEqual(config.webroot_path, ['/var/www/'])\n\n    @mock.patch('certbot._internal.renewal.cli.set_by_cli')\n    def test_webroot_params_conservation(self, mock_set_by_cli):\n        # For more details about why this test is important, see:\n        # certbot._internal.plugins.webroot_test::\n        #   WebrootActionTest::test_webroot_map_partial_without_perform\n        from certbot._internal import renewal\n        mock_set_by_cli.return_value = False\n\n        renewalparams = {\n            'webroot_map': {'test.example.com': '/var/www/test'},\n            'webroot_path': ['/var/www/test', '/var/www/other'],\n        }\n        renewal._restore_webroot_config(self.config, renewalparams)  # pylint: disable=protected-access\n        self.assertEqual(self.config.webroot_map, {'test.example.com': '/var/www/test'})\n        self.assertEqual(self.config.webroot_path, ['/var/www/test', '/var/www/other'])\n\n        renewalparams = {\n            'webroot_map': {},\n            'webroot_path': '/var/www/test',\n        }\n        renewal._restore_webroot_config(self.config, renewalparams)  # pylint: disable=protected-access\n        self.assertEqual(self.config.webroot_map, {})\n        self.assertEqual(self.config.webroot_path, ['/var/www/test'])\n\n\nclass RestoreRequiredConfigElementsTest(test_util.ConfigTestCase):\n    \"\"\"Tests for certbot._internal.renewal.restore_required_config_elements.\"\"\"\n    @classmethod\n    def _call(cls, *args, **kwargs):\n        from certbot._internal.renewal import restore_required_config_elements\n        return restore_required_config_elements(*args, **kwargs)\n\n    @mock.patch('certbot._internal.renewal.cli.set_by_cli')\n    def test_allow_subset_of_names_success(self, mock_set_by_cli):\n        mock_set_by_cli.return_value = False\n        self._call(self.config, {'allow_subset_of_names': 'True'})\n        self.assertTrue(self.config.allow_subset_of_names is True)\n\n    @mock.patch('certbot._internal.renewal.cli.set_by_cli')\n    def test_allow_subset_of_names_failure(self, mock_set_by_cli):\n        mock_set_by_cli.return_value = False\n        renewalparams = {'allow_subset_of_names': 'maybe'}\n        self.assertRaises(\n            errors.Error, self._call, self.config, renewalparams)\n\n    @mock.patch('certbot._internal.renewal.cli.set_by_cli')\n    def test_pref_challs_list(self, mock_set_by_cli):\n        mock_set_by_cli.return_value = False\n        renewalparams = {'pref_challs': 'http-01, dns'.split(',')}\n        self._call(self.config, renewalparams)\n        expected = [challenges.HTTP01.typ, challenges.DNS01.typ]\n        self.assertEqual(self.config.pref_challs, expected)\n\n    @mock.patch('certbot._internal.renewal.cli.set_by_cli')\n    def test_pref_challs_str(self, mock_set_by_cli):\n        mock_set_by_cli.return_value = False\n        renewalparams = {'pref_challs': 'dns'}\n        self._call(self.config, renewalparams)\n        expected = [challenges.DNS01.typ]\n        self.assertEqual(self.config.pref_challs, expected)\n\n    @mock.patch('certbot._internal.renewal.cli.set_by_cli')\n    def test_pref_challs_failure(self, mock_set_by_cli):\n        mock_set_by_cli.return_value = False\n        renewalparams = {'pref_challs': 'finding-a-shrubbery'}\n        self.assertRaises(errors.Error, self._call, self.config, renewalparams)\n\n    @mock.patch('certbot._internal.renewal.cli.set_by_cli')\n    def test_must_staple_success(self, mock_set_by_cli):\n        mock_set_by_cli.return_value = False\n        self._call(self.config, {'must_staple': 'True'})\n        self.assertTrue(self.config.must_staple is True)\n\n    @mock.patch('certbot._internal.renewal.cli.set_by_cli')\n    def test_must_staple_failure(self, mock_set_by_cli):\n        mock_set_by_cli.return_value = False\n        renewalparams = {'must_staple': 'maybe'}\n        self.assertRaises(\n            errors.Error, self._call, self.config, renewalparams)\n\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/renewupdater_test.py",
    "content": "\"\"\"Tests for renewal updater interfaces\"\"\"\nimport unittest\n\nimport mock\n\nfrom certbot import interfaces\nfrom certbot._internal import main\nfrom certbot._internal import updater\nfrom certbot.plugins import enhancements\nimport certbot.tests.util as test_util\n\n\nclass RenewUpdaterTest(test_util.ConfigTestCase):\n    \"\"\"Tests for interfaces.RenewDeployer and interfaces.GenericUpdater\"\"\"\n\n    def setUp(self):\n        super(RenewUpdaterTest, self).setUp()\n        self.generic_updater = mock.MagicMock(spec=interfaces.GenericUpdater)\n        self.generic_updater.restart = mock.MagicMock()\n        self.renew_deployer = mock.MagicMock(spec=interfaces.RenewDeployer)\n        self.mockinstaller = mock.MagicMock(spec=enhancements.AutoHSTSEnhancement)\n\n    @mock.patch('certbot._internal.main._get_and_save_cert')\n    @mock.patch('certbot._internal.plugins.selection.choose_configurator_plugins')\n    @mock.patch('certbot._internal.plugins.selection.get_unprepared_installer')\n    @test_util.patch_get_utility()\n    def test_server_updates(self, _, mock_geti, mock_select, mock_getsave):\n        mock_getsave.return_value = mock.MagicMock()\n        mock_generic_updater = self.generic_updater\n\n        # Generic Updater\n        mock_select.return_value = (mock_generic_updater, None)\n        mock_geti.return_value = mock_generic_updater\n        with mock.patch('certbot._internal.main._init_le_client'):\n            main.renew_cert(self.config, None, mock.MagicMock())\n        self.assertTrue(mock_generic_updater.restart.called)\n\n        mock_generic_updater.restart.reset_mock()\n        mock_generic_updater.generic_updates.reset_mock()\n        updater.run_generic_updaters(self.config, mock.MagicMock(), None)\n        self.assertEqual(mock_generic_updater.generic_updates.call_count, 1)\n        self.assertFalse(mock_generic_updater.restart.called)\n\n    def test_renew_deployer(self):\n        lineage = mock.MagicMock()\n        mock_deployer = self.renew_deployer\n        updater.run_renewal_deployer(self.config, lineage, mock_deployer)\n        self.assertTrue(mock_deployer.renew_deploy.called_with(lineage))\n\n    @mock.patch(\"certbot._internal.updater.logger.debug\")\n    def test_updater_skip_dry_run(self, mock_log):\n        self.config.dry_run = True\n        updater.run_generic_updaters(self.config, None, None)\n        self.assertTrue(mock_log.called)\n        self.assertEqual(mock_log.call_args[0][0],\n                          \"Skipping updaters in dry-run mode.\")\n\n    @mock.patch(\"certbot._internal.updater.logger.debug\")\n    def test_deployer_skip_dry_run(self, mock_log):\n        self.config.dry_run = True\n        updater.run_renewal_deployer(self.config, None, None)\n        self.assertTrue(mock_log.called)\n        self.assertEqual(mock_log.call_args[0][0],\n                          \"Skipping renewal deployer in dry-run mode.\")\n\n    @mock.patch('certbot._internal.plugins.selection.get_unprepared_installer')\n    def test_enhancement_updates(self, mock_geti):\n        mock_geti.return_value = self.mockinstaller\n        updater.run_generic_updaters(self.config, mock.MagicMock(), None)\n        self.assertTrue(self.mockinstaller.update_autohsts.called)\n        self.assertEqual(self.mockinstaller.update_autohsts.call_count, 1)\n\n    def test_enhancement_deployer(self):\n        updater.run_renewal_deployer(self.config, mock.MagicMock(),\n                                     self.mockinstaller)\n        self.assertTrue(self.mockinstaller.deploy_autohsts.called)\n\n    @mock.patch('certbot._internal.plugins.selection.get_unprepared_installer')\n    def test_enhancement_updates_not_called(self, mock_geti):\n        self.config.disable_renew_updates = True\n        mock_geti.return_value = self.mockinstaller\n        updater.run_generic_updaters(self.config, mock.MagicMock(), None)\n        self.assertFalse(self.mockinstaller.update_autohsts.called)\n\n    def test_enhancement_deployer_not_called(self):\n        self.config.disable_renew_updates = True\n        updater.run_renewal_deployer(self.config, mock.MagicMock(),\n                                     self.mockinstaller)\n        self.assertFalse(self.mockinstaller.deploy_autohsts.called)\n\n    @mock.patch('certbot._internal.plugins.selection.get_unprepared_installer')\n    def test_enhancement_no_updater(self, mock_geti):\n        FAKEINDEX = [\n            {\n                \"name\": \"Test\",\n                \"class\": enhancements.AutoHSTSEnhancement,\n                \"updater_function\": None,\n                \"deployer_function\": \"deploy_autohsts\",\n                \"enable_function\": \"enable_autohsts\"\n            }\n        ]\n        mock_geti.return_value = self.mockinstaller\n        with mock.patch(\"certbot.plugins.enhancements._INDEX\", FAKEINDEX):\n            updater.run_generic_updaters(self.config, mock.MagicMock(), None)\n        self.assertFalse(self.mockinstaller.update_autohsts.called)\n\n    def test_enhancement_no_deployer(self):\n        FAKEINDEX = [\n            {\n                \"name\": \"Test\",\n                \"class\": enhancements.AutoHSTSEnhancement,\n                \"updater_function\": \"deploy_autohsts\",\n                \"deployer_function\": None,\n                \"enable_function\": \"enable_autohsts\"\n            }\n        ]\n        with mock.patch(\"certbot.plugins.enhancements._INDEX\", FAKEINDEX):\n            updater.run_renewal_deployer(self.config, mock.MagicMock(),\n                                         self.mockinstaller)\n        self.assertFalse(self.mockinstaller.deploy_autohsts.called)\n\n\nif __name__ == '__main__':\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/reporter_test.py",
    "content": "\"\"\"Tests for certbot._internal.reporter.\"\"\"\nimport sys\nimport unittest\n\nimport mock\nimport six\n\n\nclass ReporterTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.reporter.Reporter.\"\"\"\n    def setUp(self):\n        from certbot._internal import reporter\n        self.reporter = reporter.Reporter(mock.MagicMock(quiet=False))\n\n        self.old_stdout = sys.stdout  # type: ignore\n        sys.stdout = six.StringIO()\n\n    def tearDown(self):\n        sys.stdout = self.old_stdout\n\n    def test_multiline_message(self):\n        self.reporter.add_message(\"Line 1\\nLine 2\", self.reporter.LOW_PRIORITY)\n        self.reporter.print_messages()\n        output = sys.stdout.getvalue()  # type: ignore\n        self.assertTrue(\"Line 1\\n\" in output)\n        self.assertTrue(\"Line 2\" in output)\n\n    def test_tty_print_empty(self):\n        sys.stdout.isatty = lambda: True  # type: ignore\n        self.test_no_tty_print_empty()\n\n    def test_no_tty_print_empty(self):\n        self.reporter.print_messages()\n        self.assertEqual(sys.stdout.getvalue(), \"\")  # type: ignore\n        try:\n            raise ValueError\n        except ValueError:\n            self.reporter.print_messages()\n        self.assertEqual(sys.stdout.getvalue(), \"\")  # type: ignore\n\n    def test_tty_successful_exit(self):\n        sys.stdout.isatty = lambda: True  # type: ignore\n        self._successful_exit_common()\n\n    def test_no_tty_successful_exit(self):\n        self._successful_exit_common()\n\n    def test_tty_unsuccessful_exit(self):\n        sys.stdout.isatty = lambda: True  # type: ignore\n        self._unsuccessful_exit_common()\n\n    def test_no_tty_unsuccessful_exit(self):\n        self._unsuccessful_exit_common()\n\n    def _successful_exit_common(self):\n        self._add_messages()\n        self.reporter.print_messages()\n        output = sys.stdout.getvalue()  # type: ignore\n        self.assertTrue(\"IMPORTANT NOTES:\" in output)\n        self.assertTrue(\"High\" in output)\n        self.assertTrue(\"Med\" in output)\n        self.assertTrue(\"Low\" in output)\n\n    def _unsuccessful_exit_common(self):\n        self._add_messages()\n        try:\n            raise ValueError\n        except ValueError:\n            self.reporter.print_messages()\n        output = sys.stdout.getvalue()  # type: ignore\n        self.assertTrue(\"IMPORTANT NOTES:\" in output)\n        self.assertTrue(\"High\" in output)\n        self.assertTrue(\"Med\" not in output)\n        self.assertTrue(\"Low\" not in output)\n\n    def _add_messages(self):\n        self.reporter.add_message(\"High\", self.reporter.HIGH_PRIORITY)\n        self.reporter.add_message(\n            \"Med\", self.reporter.MEDIUM_PRIORITY, on_crash=False)\n        self.reporter.add_message(\n            \"Low\", self.reporter.LOW_PRIORITY, on_crash=False)\n\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/reverter_test.py",
    "content": "\"\"\"Test certbot.reverter.\"\"\"\nimport csv\nimport logging\nimport shutil\nimport tempfile\nimport unittest\n\nimport mock\nimport six\n\nfrom certbot import errors\nfrom certbot.compat import os\nfrom certbot.tests import util as test_util\n\n\nclass ReverterCheckpointLocalTest(test_util.ConfigTestCase):\n    \"\"\"Test the Reverter Class.\"\"\"\n    def setUp(self):\n        super(ReverterCheckpointLocalTest, self).setUp()\n        from certbot.reverter import Reverter\n\n        # Disable spurious errors... we are trying to test for them\n        logging.disable(logging.CRITICAL)\n\n        self.reverter = Reverter(self.config)\n\n        tup = setup_test_files()\n        self.config1, self.config2, self.dir1, self.dir2, self.sets = tup\n\n    def tearDown(self):\n        shutil.rmtree(self.config.work_dir)\n        shutil.rmtree(self.dir1)\n        shutil.rmtree(self.dir2)\n\n        logging.disable(logging.NOTSET)\n\n    @mock.patch(\"certbot.reverter.Reverter._read_and_append\")\n    def test_no_change(self, mock_read):\n        mock_read.side_effect = OSError(\"cannot even\")\n        try:\n            self.reverter.add_to_checkpoint(self.sets[0], \"save1\")\n        except OSError:\n            pass\n        self.reverter.finalize_checkpoint(\"blah\")\n        path = os.listdir(self.reverter.config.backup_dir)[0]\n        no_change = os.path.join(self.reverter.config.backup_dir, path, \"CHANGES_SINCE\")\n        with open(no_change, \"r\") as f:\n            x = f.read()\n        self.assertTrue(\"No changes\" in x)\n\n    def test_basic_add_to_temp_checkpoint(self):\n        # These shouldn't conflict even though they are both named config.txt\n        self.reverter.add_to_temp_checkpoint(self.sets[0], \"save1\")\n        self.reverter.add_to_temp_checkpoint(self.sets[1], \"save2\")\n\n        self.assertTrue(os.path.isdir(self.config.temp_checkpoint_dir))\n        self.assertEqual(get_save_notes(\n            self.config.temp_checkpoint_dir), \"save1save2\")\n        self.assertFalse(os.path.isfile(\n            os.path.join(self.config.temp_checkpoint_dir, \"NEW_FILES\")))\n\n        self.assertEqual(\n            get_filepaths(self.config.temp_checkpoint_dir),\n            \"{0}\\n{1}\\n\".format(self.config1, self.config2))\n\n    def test_add_to_checkpoint_copy_failure(self):\n        with mock.patch(\"certbot.reverter.shutil.copy2\") as mock_copy2:\n            mock_copy2.side_effect = IOError(\"bad copy\")\n            self.assertRaises(\n                errors.ReverterError, self.reverter.add_to_checkpoint,\n                self.sets[0], \"save1\")\n\n    def test_checkpoint_conflict(self):\n        \"\"\"Make sure that checkpoint errors are thrown appropriately.\"\"\"\n        config3 = os.path.join(self.dir1, \"config3.txt\")\n        self.reverter.register_file_creation(True, config3)\n        update_file(config3, \"This is a new file!\")\n\n        self.reverter.add_to_checkpoint(self.sets[2], \"save1\")\n        # This shouldn't throw an error\n        self.reverter.add_to_temp_checkpoint(self.sets[0], \"save2\")\n        # Raise error\n        self.assertRaises(errors.ReverterError, self.reverter.add_to_checkpoint,\n                          self.sets[2], \"save3\")\n        # Should not cause an error\n        self.reverter.add_to_checkpoint(self.sets[1], \"save4\")\n\n        # Check to make sure new files are also checked...\n        self.assertRaises(errors.ReverterError, self.reverter.add_to_checkpoint,\n                          set([config3]), \"invalid save\")\n\n    def test_multiple_saves_and_temp_revert(self):\n        self.reverter.add_to_temp_checkpoint(self.sets[0], \"save1\")\n        update_file(self.config1, \"updated-directive\")\n        self.reverter.add_to_temp_checkpoint(self.sets[0], \"save2-updated dir\")\n        update_file(self.config1, \"new directive change that we won't keep\")\n\n        self.reverter.revert_temporary_config()\n        self.assertEqual(read_in(self.config1), \"directive-dir1\")\n\n    def test_multiple_registration_fail_and_revert(self):\n\n        config3 = os.path.join(self.dir1, \"config3.txt\")\n        update_file(config3, \"Config3\")\n        config4 = os.path.join(self.dir2, \"config4.txt\")\n        update_file(config4, \"Config4\")\n\n        # Test multiple registrations and two registrations at once\n        self.reverter.register_file_creation(True, self.config1)\n        self.reverter.register_file_creation(True, self.config2)\n        self.reverter.register_file_creation(True, config3, config4)\n\n        # Simulate Certbot crash... recovery routine is run\n        self.reverter.recovery_routine()\n\n        self.assertFalse(os.path.isfile(self.config1))\n        self.assertFalse(os.path.isfile(self.config2))\n        self.assertFalse(os.path.isfile(config3))\n        self.assertFalse(os.path.isfile(config4))\n\n    def test_multiple_registration_same_file(self):\n        self.reverter.register_file_creation(True, self.config1)\n        self.reverter.register_file_creation(True, self.config1)\n        self.reverter.register_file_creation(True, self.config1)\n        self.reverter.register_file_creation(True, self.config1)\n\n        files = get_new_files(self.config.temp_checkpoint_dir)\n\n        self.assertEqual(len(files), 1)\n\n    def test_register_file_creation_write_error(self):\n        m_open = mock.mock_open()\n        with mock.patch(\"certbot.reverter.open\", m_open, create=True):\n            m_open.side_effect = OSError(\"bad open\")\n            self.assertRaises(\n                errors.ReverterError, self.reverter.register_file_creation,\n                True, self.config1)\n\n    def test_bad_registration(self):\n        # Made this mistake and want to make sure it doesn't happen again...\n        self.assertRaises(\n            errors.ReverterError, self.reverter.register_file_creation,\n            \"filepath\")\n\n    def test_register_undo_command(self):\n        coms = [\n            [\"a2dismod\", \"ssl\"],\n            [\"a2dismod\", \"rewrite\"],\n            [\"cleanslate\"]\n        ]\n        for com in coms:\n            self.reverter.register_undo_command(True, com)\n\n        act_coms = get_undo_commands(self.config.temp_checkpoint_dir)\n\n        for a_com, com in six.moves.zip(act_coms, coms):\n            self.assertEqual(a_com, com)\n\n    def test_bad_register_undo_command(self):\n        m_open = mock.mock_open()\n        with mock.patch(\"certbot.reverter.open\", m_open, create=True):\n            m_open.side_effect = OSError(\"bad open\")\n            self.assertRaises(\n                errors.ReverterError, self.reverter.register_undo_command,\n                True, [\"command\"])\n\n    @mock.patch(\"certbot.util.run_script\")\n    def test_run_undo_commands(self, mock_run):\n        mock_run.side_effect = [\"\", errors.SubprocessError]\n        coms = [\n            [\"invalid_command\"],\n            [\"a2dismod\", \"ssl\"],\n        ]\n        for com in coms:\n            self.reverter.register_undo_command(True, com)\n\n        self.reverter.revert_temporary_config()\n\n        self.assertEqual(mock_run.call_count, 2)\n\n    def test_recovery_routine_in_progress_failure(self):\n        self.reverter.add_to_checkpoint(self.sets[0], \"perm save\")\n\n        # pylint: disable=protected-access\n        self.reverter._recover_checkpoint = mock.MagicMock(\n            side_effect=errors.ReverterError)\n        self.assertRaises(errors.ReverterError, self.reverter.recovery_routine)\n\n    def test_recover_checkpoint_revert_temp_failures(self):\n\n        mock_recover = mock.MagicMock(\n            side_effect=errors.ReverterError(\"e\"))\n\n        # pylint: disable=protected-access\n        self.reverter._recover_checkpoint = mock_recover\n\n        self.reverter.add_to_temp_checkpoint(self.sets[0], \"config1 save\")\n\n        self.assertRaises(\n            errors.ReverterError, self.reverter.revert_temporary_config)\n\n    def test_recover_checkpoint_rollback_failure(self):\n        mock_recover = mock.MagicMock(\n            side_effect=errors.ReverterError(\"e\"))\n        # pylint: disable=protected-access\n        self.reverter._recover_checkpoint = mock_recover\n\n        self.reverter.add_to_checkpoint(self.sets[0], \"config1 save\")\n        self.reverter.finalize_checkpoint(\"Title\")\n\n        self.assertRaises(\n            errors.ReverterError, self.reverter.rollback_checkpoints, 1)\n\n    def test_recover_checkpoint_copy_failure(self):\n        self.reverter.add_to_temp_checkpoint(self.sets[0], \"save1\")\n\n        with mock.patch(\"certbot.reverter.shutil.copy2\") as mock_copy2:\n            mock_copy2.side_effect = OSError(\"bad copy\")\n            self.assertRaises(\n                errors.ReverterError, self.reverter.revert_temporary_config)\n\n    def test_recover_checkpoint_rm_failure(self):\n        self.reverter.add_to_temp_checkpoint(self.sets[0], \"temp save\")\n\n        with mock.patch(\"certbot.reverter.shutil.rmtree\") as mock_rmtree:\n            mock_rmtree.side_effect = OSError(\"Cannot remove tree\")\n            self.assertRaises(\n                errors.ReverterError, self.reverter.revert_temporary_config)\n\n    @mock.patch(\"certbot.reverter.logger.warning\")\n    def test_recover_checkpoint_missing_new_files(self, mock_warn):\n        self.reverter.register_file_creation(\n            True, os.path.join(self.dir1, \"missing_file.txt\"))\n        self.reverter.revert_temporary_config()\n        self.assertEqual(mock_warn.call_count, 1)\n\n    @mock.patch(\"certbot.reverter.os.remove\")\n    def test_recover_checkpoint_remove_failure(self, mock_remove):\n        self.reverter.register_file_creation(True, self.config1)\n        mock_remove.side_effect = OSError(\"Can't remove\")\n        self.assertRaises(\n            errors.ReverterError, self.reverter.revert_temporary_config)\n\n    def test_recovery_routine_temp_and_perm(self):\n        # Register a new perm checkpoint file\n        config3 = os.path.join(self.dir1, \"config3.txt\")\n        self.reverter.register_file_creation(False, config3)\n        update_file(config3, \"This is a new perm file!\")\n\n        # Add changes to perm checkpoint\n        self.reverter.add_to_checkpoint(self.sets[0], \"perm save1\")\n        update_file(self.config1, \"updated perm config1\")\n        self.reverter.add_to_checkpoint(self.sets[1], \"perm save2\")\n        update_file(self.config2, \"updated perm config2\")\n\n        # Add changes to a temporary checkpoint\n        self.reverter.add_to_temp_checkpoint(self.sets[0], \"temp save1\")\n        update_file(self.config1, \"second update now temp config1\")\n\n        # Register a new temp checkpoint file\n        config4 = os.path.join(self.dir2, \"config4.txt\")\n        self.reverter.register_file_creation(True, config4)\n        update_file(config4, \"New temporary file!\")\n\n        # Now erase everything\n        self.reverter.recovery_routine()\n\n        # Now Run tests\n        # These were new files.. they should be removed\n        self.assertFalse(os.path.isfile(config3))\n        self.assertFalse(os.path.isfile(config4))\n\n        # Check to make sure everything got rolled back appropriately\n        self.assertEqual(read_in(self.config1), \"directive-dir1\")\n        self.assertEqual(read_in(self.config2), \"directive-dir2\")\n\n\nclass TestFullCheckpointsReverter(test_util.ConfigTestCase):\n    \"\"\"Tests functions having to deal with full checkpoints.\"\"\"\n    def setUp(self):\n        super(TestFullCheckpointsReverter, self).setUp()\n        from certbot.reverter import Reverter\n        # Disable spurious errors...\n        logging.disable(logging.CRITICAL)\n\n        self.reverter = Reverter(self.config)\n\n        tup = setup_test_files()\n        self.config1, self.config2, self.dir1, self.dir2, self.sets = tup\n\n    def tearDown(self):\n        shutil.rmtree(self.config.work_dir)\n        shutil.rmtree(self.dir1)\n        shutil.rmtree(self.dir2)\n\n        logging.disable(logging.NOTSET)\n\n    def test_rollback_improper_inputs(self):\n        self.assertRaises(\n            errors.ReverterError, self.reverter.rollback_checkpoints, \"-1\")\n        self.assertRaises(\n            errors.ReverterError, self.reverter.rollback_checkpoints, -1000)\n        self.assertRaises(\n            errors.ReverterError, self.reverter.rollback_checkpoints, \"one\")\n\n    def test_rollback_finalize_checkpoint_valid_inputs(self):\n\n        config3 = self._setup_three_checkpoints()\n\n        # Check resulting backup directory\n        self.assertEqual(len(os.listdir(self.config.backup_dir)), 3)\n        # Check rollbacks\n        # First rollback\n        self.reverter.rollback_checkpoints(1)\n        self.assertEqual(read_in(self.config1), \"update config1\")\n        self.assertEqual(read_in(self.config2), \"update config2\")\n        # config3 was not included in checkpoint\n        self.assertEqual(read_in(config3), \"Final form config3\")\n\n        # Second rollback\n        self.reverter.rollback_checkpoints(1)\n        self.assertEqual(read_in(self.config1), \"update config1\")\n        self.assertEqual(read_in(self.config2), \"directive-dir2\")\n        self.assertFalse(os.path.isfile(config3))\n\n        # One dir left... check title\n        all_dirs = os.listdir(self.config.backup_dir)\n        self.assertEqual(len(all_dirs), 1)\n        self.assertTrue(\n            \"First Checkpoint\" in get_save_notes(\n                os.path.join(self.config.backup_dir, all_dirs[0])))\n        # Final rollback\n        self.reverter.rollback_checkpoints(1)\n        self.assertEqual(read_in(self.config1), \"directive-dir1\")\n\n    def test_finalize_checkpoint_no_in_progress(self):\n        # No need to warn for this... just make sure there are no errors.\n        self.reverter.finalize_checkpoint(\"No checkpoint...\")\n\n    @mock.patch(\"certbot.reverter.shutil.move\")\n    def test_finalize_checkpoint_cannot_title(self, mock_move):\n        self.reverter.add_to_checkpoint(self.sets[0], \"perm save\")\n        mock_move.side_effect = OSError(\"cannot move\")\n\n        self.assertRaises(\n            errors.ReverterError, self.reverter.finalize_checkpoint, \"Title\")\n\n    @mock.patch(\"certbot.reverter.filesystem.replace\")\n    def test_finalize_checkpoint_no_rename_directory(self, mock_replace):\n\n        self.reverter.add_to_checkpoint(self.sets[0], \"perm save\")\n        mock_replace.side_effect = OSError\n\n        self.assertRaises(\n            errors.ReverterError, self.reverter.finalize_checkpoint, \"Title\")\n\n    @mock.patch(\"certbot.reverter.logger\")\n    def test_rollback_too_many(self, mock_logger):\n        # Test no exist warning...\n        self.reverter.rollback_checkpoints(1)\n        self.assertEqual(mock_logger.warning.call_count, 1)\n\n        # Test Generic warning\n        self._setup_three_checkpoints()\n        mock_logger.warning.call_count = 0\n        self.reverter.rollback_checkpoints(4)\n        self.assertEqual(mock_logger.warning.call_count, 1)\n\n    def test_multi_rollback(self):\n        config3 = self._setup_three_checkpoints()\n        self.reverter.rollback_checkpoints(3)\n\n        self.assertEqual(read_in(self.config1), \"directive-dir1\")\n        self.assertEqual(read_in(self.config2), \"directive-dir2\")\n        self.assertFalse(os.path.isfile(config3))\n\n    def _setup_three_checkpoints(self):\n        \"\"\"Generate some finalized checkpoints.\"\"\"\n        # Checkpoint1 - config1\n        self.reverter.add_to_checkpoint(self.sets[0], \"first save\")\n        self.reverter.finalize_checkpoint(\"First Checkpoint\")\n\n        update_file(self.config1, \"update config1\")\n\n        # Checkpoint2 - new file config3, update config2\n        config3 = os.path.join(self.dir1, \"config3.txt\")\n        self.reverter.register_file_creation(False, config3)\n        update_file(config3, \"directive-config3\")\n        self.reverter.add_to_checkpoint(self.sets[1], \"second save\")\n        self.reverter.finalize_checkpoint(\"Second Checkpoint\")\n\n        update_file(self.config2, \"update config2\")\n        update_file(config3, \"update config3\")\n\n        # Checkpoint3 - update config1, config2\n        self.reverter.add_to_checkpoint(self.sets[2], \"third save\")\n        self.reverter.finalize_checkpoint(\"Third Checkpoint - Save both\")\n\n        update_file(self.config1, \"Final form config1\")\n        update_file(self.config2, \"Final form config2\")\n        update_file(config3, \"Final form config3\")\n\n        return config3\n\n\ndef setup_test_files():\n    \"\"\"Setup sample configuration files.\"\"\"\n    dir1 = tempfile.mkdtemp(\"dir1\")\n    dir2 = tempfile.mkdtemp(\"dir2\")\n    config1 = os.path.join(dir1, \"config.txt\")\n    config2 = os.path.join(dir2, \"config.txt\")\n    with open(config1, \"w\") as file_fd:\n        file_fd.write(\"directive-dir1\")\n    with open(config2, \"w\") as file_fd:\n        file_fd.write(\"directive-dir2\")\n\n    sets = [set([config1]),\n            set([config2]),\n            set([config1, config2])]\n\n    return config1, config2, dir1, dir2, sets\n\n\ndef get_save_notes(dire):\n    \"\"\"Read save notes\"\"\"\n    return read_in(os.path.join(dire, \"CHANGES_SINCE\"))\n\n\ndef get_filepaths(dire):\n    \"\"\"Get Filepaths\"\"\"\n    return read_in(os.path.join(dire, \"FILEPATHS\"))\n\n\ndef get_new_files(dire):\n    \"\"\"Get new files.\"\"\"\n    return read_in(os.path.join(dire, \"NEW_FILES\")).splitlines()\n\n\ndef get_undo_commands(dire):\n    \"\"\"Get new files.\"\"\"\n    with open(os.path.join(dire, \"COMMANDS\")) as csvfile:\n        return list(csv.reader(csvfile))\n\n\ndef read_in(path):\n    \"\"\"Read in a file, return the str\"\"\"\n    with open(path, \"r\") as file_fd:\n        return file_fd.read()\n\n\ndef update_file(filename, string):\n    \"\"\"Update a file with a new value.\"\"\"\n    with open(filename, \"w\") as file_fd:\n        file_fd.write(string)\n\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/storage_test.py",
    "content": "\"\"\"Tests for certbot._internal.storage.\"\"\"\n# pylint disable=protected-access\nimport datetime\nimport shutil\nimport stat\nimport unittest\n\nimport configobj\nimport mock\nimport pytz\nimport six\n\nimport certbot\nfrom certbot import errors\nfrom certbot._internal.storage import ALL_FOUR\nfrom certbot.compat import filesystem\nfrom certbot.compat import os\nimport certbot.tests.util as test_util\n\nCERT = test_util.load_cert('cert_512.pem')\n\n\ndef unlink_all(rc_object):\n    \"\"\"Unlink all four items associated with this RenewableCert.\"\"\"\n    for kind in ALL_FOUR:\n        os.unlink(getattr(rc_object, kind))\n\n\ndef fill_with_sample_data(rc_object):\n    \"\"\"Put dummy data into all four files of this RenewableCert.\"\"\"\n    for kind in ALL_FOUR:\n        with open(getattr(rc_object, kind), \"w\") as f:\n            f.write(kind)\n\n\nclass RelevantValuesTest(unittest.TestCase):\n    \"\"\"Tests for certbot._internal.storage.relevant_values.\"\"\"\n\n    def setUp(self):\n        self.values = {\"server\": \"example.org\"}\n\n    def _call(self, *args, **kwargs):\n        from certbot._internal.storage import relevant_values\n        return relevant_values(*args, **kwargs)\n\n    @mock.patch(\"certbot._internal.cli.option_was_set\")\n    @mock.patch(\"certbot._internal.plugins.disco.PluginsRegistry.find_all\")\n    def test_namespace(self, mock_find_all, mock_option_was_set):\n        mock_find_all.return_value = [\"certbot-foo:bar\"]\n        mock_option_was_set.return_value = True\n\n        self.values[\"certbot_foo:bar_baz\"] = 42\n        self.assertEqual(\n            self._call(self.values.copy()), self.values)\n\n    @mock.patch(\"certbot._internal.cli.option_was_set\")\n    def test_option_set(self, mock_option_was_set):\n        mock_option_was_set.return_value = True\n\n        self.values[\"allow_subset_of_names\"] = True\n        self.values[\"authenticator\"] = \"apache\"\n        self.values[\"rsa_key_size\"] = 1337\n        expected_relevant_values = self.values.copy()\n        self.values[\"hello\"] = \"there\"\n\n        self.assertEqual(self._call(self.values), expected_relevant_values)\n\n    @mock.patch(\"certbot._internal.cli.option_was_set\")\n    def test_option_unset(self, mock_option_was_set):\n        mock_option_was_set.return_value = False\n\n        expected_relevant_values = self.values.copy()\n        self.values[\"rsa_key_size\"] = 2048\n\n        self.assertEqual(self._call(self.values), expected_relevant_values)\n\n\nclass BaseRenewableCertTest(test_util.ConfigTestCase):\n    \"\"\"Base class for setting up Renewable Cert tests.\n\n    .. note:: It may be required to write out self.config for\n    your test.  Check :class:`.cli_test.DuplicateCertTest` for an example.\n\n    \"\"\"\n\n    def setUp(self):\n        from certbot._internal import storage\n\n        super(BaseRenewableCertTest, self).setUp()\n\n        # TODO: maybe provide NamespaceConfig.make_dirs?\n        # TODO: main() should create those dirs, c.f. #902\n        filesystem.makedirs(os.path.join(self.config.config_dir, \"live\", \"example.org\"))\n        archive_path = os.path.join(self.config.config_dir, \"archive\", \"example.org\")\n        filesystem.makedirs(archive_path)\n        filesystem.makedirs(os.path.join(self.config.config_dir, \"renewal\"))\n\n        config_file = configobj.ConfigObj()\n        for kind in ALL_FOUR:\n            kind_path = os.path.join(self.config.config_dir, \"live\", \"example.org\",\n                                        kind + \".pem\")\n            config_file[kind] = kind_path\n        with open(os.path.join(self.config.config_dir, \"live\", \"example.org\",\n                                        \"README\"), 'a'):\n            pass\n        config_file[\"archive\"] = archive_path\n        config_file.filename = os.path.join(self.config.config_dir, \"renewal\",\n                                       \"example.org.conf\")\n        config_file.write()\n        self.config_file = config_file\n\n        # We also create a file that isn't a renewal config in the same\n        # location to test that logic that reads in all-and-only renewal\n        # configs will ignore it and NOT attempt to parse it.\n        with open(os.path.join(self.config.config_dir, \"renewal\", \"IGNORE.THIS\"), \"w\") as junk:\n            junk.write(\"This file should be ignored!\")\n\n        self.defaults = configobj.ConfigObj()\n\n        with mock.patch(\"certbot._internal.storage.RenewableCert._check_symlinks\") as check:\n            check.return_value = True\n            self.test_rc = storage.RenewableCert(config_file.filename, self.config)\n\n    def _write_out_kind(self, kind, ver, value=None):\n        link = getattr(self.test_rc, kind)\n        if os.path.lexists(link):\n            os.unlink(link)\n        os.symlink(os.path.join(os.path.pardir, os.path.pardir, \"archive\",\n                                \"example.org\", \"{0}{1}.pem\".format(kind, ver)),\n                   link)\n        with open(link, \"wb\") as f:\n            f.write(kind.encode('ascii') if value is None else value)\n        if kind == \"privkey\":\n            filesystem.chmod(link, 0o600)\n\n    def _write_out_ex_kinds(self):\n        for kind in ALL_FOUR:\n            self._write_out_kind(kind, 12)\n            self._write_out_kind(kind, 11)\n\n\nclass RenewableCertTests(BaseRenewableCertTest):\n    \"\"\"Tests for certbot._internal.storage.\"\"\"\n\n    def test_initialization(self):\n        self.assertEqual(self.test_rc.lineagename, \"example.org\")\n        for kind in ALL_FOUR:\n            self.assertEqual(\n                getattr(self.test_rc, kind), os.path.join(\n                    self.config.config_dir, \"live\", \"example.org\", kind + \".pem\"))\n\n    def test_renewal_bad_config(self):\n        \"\"\"Test that the RenewableCert constructor will complain if\n        the renewal configuration file doesn't end in \".conf\"\n\n        \"\"\"\n        from certbot._internal import storage\n        broken = os.path.join(self.config.config_dir, \"broken.conf\")\n        with open(broken, \"w\") as f:\n            f.write(\"[No closing bracket for you!\")\n        self.assertRaises(errors.CertStorageError, storage.RenewableCert,\n                          broken, self.config)\n        os.unlink(broken)\n        self.assertRaises(errors.CertStorageError, storage.RenewableCert,\n                          \"fun\", self.config)\n\n    def test_renewal_incomplete_config(self):\n        \"\"\"Test that the RenewableCert constructor will complain if\n        the renewal configuration file is missing a required file element.\"\"\"\n        from certbot._internal import storage\n        config = configobj.ConfigObj()\n        config[\"cert\"] = \"imaginary_cert.pem\"\n        # Here the required privkey is missing.\n        config[\"chain\"] = \"imaginary_chain.pem\"\n        config[\"fullchain\"] = \"imaginary_fullchain.pem\"\n        config.filename = os.path.join(self.config.config_dir, \"imaginary_config.conf\")\n        config.write()\n        self.assertRaises(errors.CertStorageError, storage.RenewableCert,\n                          config.filename, self.config)\n\n    def test_no_renewal_version(self):\n        from certbot._internal import storage\n\n        self._write_out_ex_kinds()\n        self.assertTrue(\"version\" not in self.config_file)\n\n        with mock.patch(\"certbot._internal.storage.logger\") as mock_logger:\n            storage.RenewableCert(self.config_file.filename, self.config)\n        self.assertFalse(mock_logger.warning.called)\n\n    def test_renewal_newer_version(self):\n        from certbot._internal import storage\n\n        self._write_out_ex_kinds()\n        self.config_file[\"version\"] = \"99.99.99\"\n        self.config_file.write()\n\n        with mock.patch(\"certbot._internal.storage.logger\") as mock_logger:\n            storage.RenewableCert(self.config_file.filename, self.config)\n        self.assertTrue(mock_logger.info.called)\n        self.assertTrue(\"version\" in mock_logger.info.call_args[0][0])\n\n    def test_consistent(self):\n        # pylint: disable=protected-access\n        oldcert = self.test_rc.cert\n        self.test_rc.cert = \"relative/path\"\n        # Absolute path for item requirement\n        self.assertFalse(self.test_rc._consistent())\n        self.test_rc.cert = oldcert\n        # Items must exist requirement\n        self.assertFalse(self.test_rc._consistent())\n        # Items must be symlinks requirements\n        fill_with_sample_data(self.test_rc)\n        self.assertFalse(self.test_rc._consistent())\n        unlink_all(self.test_rc)\n        # Items must point to desired place if they are relative\n        for kind in ALL_FOUR:\n            os.symlink(os.path.join(\"..\", kind + \"17.pem\"),\n                       getattr(self.test_rc, kind))\n        self.assertFalse(self.test_rc._consistent())\n        unlink_all(self.test_rc)\n        # Items must point to desired place if they are absolute\n        for kind in ALL_FOUR:\n            os.symlink(os.path.join(self.config.config_dir, kind + \"17.pem\"),\n                       getattr(self.test_rc, kind))\n        self.assertFalse(self.test_rc._consistent())\n        unlink_all(self.test_rc)\n        # Items must point to things that exist\n        for kind in ALL_FOUR:\n            os.symlink(os.path.join(\"..\", \"..\", \"archive\", \"example.org\",\n                                    kind + \"17.pem\"),\n                       getattr(self.test_rc, kind))\n        self.assertFalse(self.test_rc._consistent())\n        # This version should work\n        fill_with_sample_data(self.test_rc)\n        self.assertTrue(self.test_rc._consistent())\n        # Items must point to things that follow the naming convention\n        os.unlink(self.test_rc.fullchain)\n        os.symlink(os.path.join(\"..\", \"..\", \"archive\", \"example.org\",\n                                \"fullchain_17.pem\"), self.test_rc.fullchain)\n        with open(self.test_rc.fullchain, \"w\") as f:\n            f.write(\"wrongly-named fullchain\")\n        self.assertFalse(self.test_rc._consistent())\n\n    def test_current_target(self):\n        # Relative path logic\n        self._write_out_kind(\"cert\", 17)\n        self.assertTrue(os.path.samefile(self.test_rc.current_target(\"cert\"),\n                                         os.path.join(self.config.config_dir, \"archive\",\n                                                      \"example.org\",\n                                                      \"cert17.pem\")))\n        # Absolute path logic\n        os.unlink(self.test_rc.cert)\n        os.symlink(os.path.join(self.config.config_dir, \"archive\", \"example.org\",\n                                \"cert17.pem\"), self.test_rc.cert)\n        with open(self.test_rc.cert, \"w\") as f:\n            f.write(\"cert\")\n        self.assertTrue(os.path.samefile(self.test_rc.current_target(\"cert\"),\n                                         os.path.join(self.config.config_dir, \"archive\",\n                                                      \"example.org\",\n                                                      \"cert17.pem\")))\n\n    def test_current_version(self):\n        for ver in (1, 5, 10, 20):\n            self._write_out_kind(\"cert\", ver)\n        os.unlink(self.test_rc.cert)\n        os.symlink(os.path.join(\"..\", \"..\", \"archive\", \"example.org\",\n                                \"cert10.pem\"), self.test_rc.cert)\n        self.assertEqual(self.test_rc.current_version(\"cert\"), 10)\n\n    def test_no_current_version(self):\n        self.assertEqual(self.test_rc.current_version(\"cert\"), None)\n\n    def test_latest_and_next_versions(self):\n        for ver in six.moves.range(1, 6):\n            for kind in ALL_FOUR:\n                self._write_out_kind(kind, ver)\n        self.assertEqual(self.test_rc.latest_common_version(), 5)\n        self.assertEqual(self.test_rc.next_free_version(), 6)\n        # Having one kind of file of a later version doesn't change the\n        # result\n        self._write_out_kind(\"privkey\", 7)\n        self.assertEqual(self.test_rc.latest_common_version(), 5)\n        # ... although it does change the next free version\n        self.assertEqual(self.test_rc.next_free_version(), 8)\n        # Nor does having three out of four change the result\n        self._write_out_kind(\"cert\", 7)\n        self._write_out_kind(\"fullchain\", 7)\n        self.assertEqual(self.test_rc.latest_common_version(), 5)\n        # If we have everything from a much later version, it does change\n        # the result\n        for kind in ALL_FOUR:\n            self._write_out_kind(kind, 17)\n        self.assertEqual(self.test_rc.latest_common_version(), 17)\n        self.assertEqual(self.test_rc.next_free_version(), 18)\n\n    @mock.patch(\"certbot._internal.storage.logger\")\n    def test_ensure_deployed(self, mock_logger):\n        mock_update = self.test_rc.update_all_links_to = mock.Mock()\n        mock_has_pending = self.test_rc.has_pending_deployment = mock.Mock()\n        self.test_rc.latest_common_version = mock.Mock()\n\n        mock_has_pending.return_value = False\n        self.assertEqual(self.test_rc.ensure_deployed(), True)\n        self.assertEqual(mock_update.call_count, 0)\n        self.assertEqual(mock_logger.warning.call_count, 0)\n\n        mock_has_pending.return_value = True\n        self.assertEqual(self.test_rc.ensure_deployed(), False)\n        self.assertEqual(mock_update.call_count, 1)\n        self.assertEqual(mock_logger.warning.call_count, 1)\n\n\n    def test_update_link_to(self):\n        for ver in six.moves.range(1, 6):\n            for kind in ALL_FOUR:\n                self._write_out_kind(kind, ver)\n                self.assertEqual(ver, self.test_rc.current_version(kind))\n        # pylint: disable=protected-access\n        self.test_rc._update_link_to(\"cert\", 3)\n        self.test_rc._update_link_to(\"privkey\", 2)\n        self.assertEqual(3, self.test_rc.current_version(\"cert\"))\n        self.assertEqual(2, self.test_rc.current_version(\"privkey\"))\n        self.assertEqual(5, self.test_rc.current_version(\"chain\"))\n        self.assertEqual(5, self.test_rc.current_version(\"fullchain\"))\n        # Currently we are allowed to update to a version that doesn't exist\n        self.test_rc._update_link_to(\"chain\", 3000)\n        # However, current_version doesn't allow querying the resulting\n        # version (because it's a broken link).\n        self.assertEqual(os.path.basename(os.readlink(self.test_rc.chain)),\n                         \"chain3000.pem\")\n\n    def test_version(self):\n        self._write_out_kind(\"cert\", 12)\n        # TODO: We should probably test that the directory is still the\n        #       same, but it's tricky because we can get an absolute\n        #       path out when we put a relative path in.\n        self.assertEqual(\"cert8.pem\",\n                         os.path.basename(self.test_rc.version(\"cert\", 8)))\n\n    def test_update_all_links_to_success(self):\n        for ver in six.moves.range(1, 6):\n            for kind in ALL_FOUR:\n                self._write_out_kind(kind, ver)\n                self.assertEqual(ver, self.test_rc.current_version(kind))\n        self.assertEqual(self.test_rc.latest_common_version(), 5)\n        for ver in six.moves.range(1, 6):\n            self.test_rc.update_all_links_to(ver)\n            for kind in ALL_FOUR:\n                self.assertEqual(ver, self.test_rc.current_version(kind))\n            self.assertEqual(self.test_rc.latest_common_version(), 5)\n\n    def test_update_all_links_to_partial_failure(self):\n        def unlink_or_raise(path, real_unlink=os.unlink):\n            # pylint: disable=missing-docstring\n            basename = os.path.basename(path)\n            if \"fullchain\" in basename and basename.startswith(\"prev\"):\n                raise ValueError\n            real_unlink(path)\n\n        self._write_out_ex_kinds()\n        with mock.patch(\"certbot._internal.storage.os.unlink\") as mock_unlink:\n            mock_unlink.side_effect = unlink_or_raise\n            self.assertRaises(ValueError, self.test_rc.update_all_links_to, 12)\n\n        for kind in ALL_FOUR:\n            self.assertEqual(self.test_rc.current_version(kind), 12)\n\n    def test_update_all_links_to_full_failure(self):\n        def unlink_or_raise(path, real_unlink=os.unlink):\n            # pylint: disable=missing-docstring\n            if \"fullchain\" in os.path.basename(path):\n                raise ValueError\n            real_unlink(path)\n\n        self._write_out_ex_kinds()\n        with mock.patch(\"certbot._internal.storage.os.unlink\") as mock_unlink:\n            mock_unlink.side_effect = unlink_or_raise\n            self.assertRaises(ValueError, self.test_rc.update_all_links_to, 12)\n\n        for kind in ALL_FOUR:\n            self.assertEqual(self.test_rc.current_version(kind), 11)\n\n    def test_has_pending_deployment(self):\n        for ver in six.moves.range(1, 6):\n            for kind in ALL_FOUR:\n                self._write_out_kind(kind, ver)\n                self.assertEqual(ver, self.test_rc.current_version(kind))\n        for ver in six.moves.range(1, 6):\n            self.test_rc.update_all_links_to(ver)\n            for kind in ALL_FOUR:\n                self.assertEqual(ver, self.test_rc.current_version(kind))\n            if ver < 5:\n                self.assertTrue(self.test_rc.has_pending_deployment())\n            else:\n                self.assertFalse(self.test_rc.has_pending_deployment())\n\n    def test_names(self):\n        # Trying the current version\n        self._write_out_kind(\"cert\", 12, test_util.load_vector(\"cert-san_512.pem\"))\n\n        self.assertEqual(self.test_rc.names(),\n                         [\"example.com\", \"www.example.com\"])\n\n        # Trying missing cert\n        os.unlink(self.test_rc.cert)\n        self.assertRaises(errors.CertStorageError, self.test_rc.names)\n\n    @mock.patch(\"certbot._internal.storage.cli\")\n    @mock.patch(\"certbot._internal.storage.datetime\")\n    def test_time_interval_judgments(self, mock_datetime, mock_cli):\n        \"\"\"Test should_autorenew() on the basis of expiry time windows.\"\"\"\n        test_cert = test_util.load_vector(\"cert_512.pem\")\n\n        self._write_out_ex_kinds()\n\n        self.test_rc.update_all_links_to(12)\n        with open(self.test_rc.cert, \"wb\") as f:\n            f.write(test_cert)\n        self.test_rc.update_all_links_to(11)\n        with open(self.test_rc.cert, \"wb\") as f:\n            f.write(test_cert)\n\n        mock_datetime.timedelta = datetime.timedelta\n        mock_cli.set_by_cli.return_value = False\n        self.test_rc.configuration[\"renewalparams\"] = {}\n\n        for (current_time, interval, result) in [\n                # 2014-12-13 12:00:00+00:00 (about 5 days prior to expiry)\n                # Times that should result in autorenewal/autodeployment\n                (1418472000, \"2 months\", True), (1418472000, \"1 week\", True),\n                # Times that should not\n                (1418472000, \"4 days\", False), (1418472000, \"2 days\", False),\n                # 2009-05-01 12:00:00+00:00 (about 5 years prior to expiry)\n                # Times that should result in autorenewal/autodeployment\n                (1241179200, \"7 years\", True),\n                (1241179200, \"11 years 2 months\", True),\n                # Times that should not\n                (1241179200, \"8 hours\", False), (1241179200, \"2 days\", False),\n                (1241179200, \"40 days\", False), (1241179200, \"9 months\", False),\n                # 2015-01-01 (after expiry has already happened, so all\n                #            intervals should cause autorenewal/autodeployment)\n                (1420070400, \"0 seconds\", True),\n                (1420070400, \"10 seconds\", True),\n                (1420070400, \"10 minutes\", True),\n                (1420070400, \"10 weeks\", True), (1420070400, \"10 months\", True),\n                (1420070400, \"10 years\", True), (1420070400, \"99 months\", True),\n        ]:\n            sometime = datetime.datetime.utcfromtimestamp(current_time)\n            mock_datetime.datetime.utcnow.return_value = sometime\n            self.test_rc.configuration[\"deploy_before_expiry\"] = interval\n            self.test_rc.configuration[\"renew_before_expiry\"] = interval\n            self.assertEqual(self.test_rc.should_autorenew(), result)\n\n    def test_autorenewal_is_enabled(self):\n        self.test_rc.configuration[\"renewalparams\"] = {}\n        self.assertTrue(self.test_rc.autorenewal_is_enabled())\n        self.test_rc.configuration[\"renewalparams\"][\"autorenew\"] = \"True\"\n        self.assertTrue(self.test_rc.autorenewal_is_enabled())\n\n        self.test_rc.configuration[\"renewalparams\"][\"autorenew\"] = \"False\"\n        self.assertFalse(self.test_rc.autorenewal_is_enabled())\n\n    @mock.patch(\"certbot._internal.storage.cli\")\n    @mock.patch(\"certbot._internal.storage.RenewableCert.ocsp_revoked\")\n    def test_should_autorenew(self, mock_ocsp, mock_cli):\n        \"\"\"Test should_autorenew on the basis of reasons other than\n        expiry time window.\"\"\"\n        mock_cli.set_by_cli.return_value = False\n        # Autorenewal turned off\n        self.test_rc.configuration[\"renewalparams\"] = {\"autorenew\": \"False\"}\n        self.assertFalse(self.test_rc.should_autorenew())\n        self.test_rc.configuration[\"renewalparams\"][\"autorenew\"] = \"True\"\n        for kind in ALL_FOUR:\n            self._write_out_kind(kind, 12)\n        # Mandatory renewal on the basis of OCSP revocation\n        mock_ocsp.return_value = True\n        self.assertTrue(self.test_rc.should_autorenew())\n        mock_ocsp.return_value = False\n\n    @mock.patch(\"certbot._internal.storage.relevant_values\")\n    def test_save_successor(self, mock_rv):\n        # Mock relevant_values() to claim that all values are relevant here\n        # (to avoid instantiating parser)\n        mock_rv.side_effect = lambda x: x\n\n        for ver in six.moves.range(1, 6):\n            for kind in ALL_FOUR:\n                self._write_out_kind(kind, ver)\n        self.test_rc.update_all_links_to(3)\n        self.assertEqual(\n            6, self.test_rc.save_successor(3, b'new cert', None,\n                                           b'new chain', self.config))\n        with open(self.test_rc.version(\"cert\", 6)) as f:\n            self.assertEqual(f.read(), \"new cert\")\n        with open(self.test_rc.version(\"chain\", 6)) as f:\n            self.assertEqual(f.read(), \"new chain\")\n        with open(self.test_rc.version(\"fullchain\", 6)) as f:\n            self.assertEqual(f.read(), \"new cert\" + \"new chain\")\n        # version 6 of the key should be a link back to version 3\n        self.assertFalse(os.path.islink(self.test_rc.version(\"privkey\", 3)))\n        self.assertTrue(os.path.islink(self.test_rc.version(\"privkey\", 6)))\n        # Let's try two more updates\n        self.assertEqual(\n            7, self.test_rc.save_successor(6, b'again', None,\n                                           b'newer chain', self.config))\n        self.assertEqual(\n            8, self.test_rc.save_successor(7, b'hello', None,\n                                           b'other chain', self.config))\n        # All of the subsequent versions should link directly to the original\n        # privkey.\n        for i in (6, 7, 8):\n            self.assertTrue(os.path.islink(self.test_rc.version(\"privkey\", i)))\n            self.assertEqual(\"privkey3.pem\", os.path.basename(os.readlink(\n                self.test_rc.version(\"privkey\", i))))\n\n        for kind in ALL_FOUR:\n            self.assertEqual(self.test_rc.available_versions(kind), list(six.moves.range(1, 9)))\n            self.assertEqual(self.test_rc.current_version(kind), 3)\n        # Test updating from latest version rather than old version\n        self.test_rc.update_all_links_to(8)\n        self.assertEqual(\n            9, self.test_rc.save_successor(8, b'last', None,\n                                           b'attempt', self.config))\n        for kind in ALL_FOUR:\n            self.assertEqual(self.test_rc.available_versions(kind),\n                             list(six.moves.range(1, 10)))\n            self.assertEqual(self.test_rc.current_version(kind), 8)\n        with open(self.test_rc.version(\"fullchain\", 9)) as f:\n            self.assertEqual(f.read(), \"last\" + \"attempt\")\n        temp_config_file = os.path.join(self.config.renewal_configs_dir,\n                                        self.test_rc.lineagename) + \".conf.new\"\n        with open(temp_config_file, \"w\") as f:\n            f.write(\"We previously crashed while writing me :(\")\n        # Test updating when providing a new privkey.  The key should\n        # be saved in a new file rather than creating a new symlink.\n        self.assertEqual(\n            10, self.test_rc.save_successor(9, b'with', b'a',\n                                            b'key', self.config))\n        self.assertTrue(os.path.exists(self.test_rc.version(\"privkey\", 10)))\n        self.assertFalse(os.path.islink(self.test_rc.version(\"privkey\", 10)))\n        self.assertFalse(os.path.exists(temp_config_file))\n\n    @test_util.skip_on_windows('Group/everybody permissions are not maintained on Windows.')\n    @mock.patch(\"certbot._internal.storage.relevant_values\")\n    def test_save_successor_maintains_group_mode(self, mock_rv):\n        # Mock relevant_values() to claim that all values are relevant here\n        # (to avoid instantiating parser)\n        mock_rv.side_effect = lambda x: x\n        for kind in ALL_FOUR:\n            self._write_out_kind(kind, 1)\n        self.test_rc.update_all_links_to(1)\n        self.assertTrue(filesystem.check_mode(self.test_rc.version(\"privkey\", 1), 0o600))\n        filesystem.chmod(self.test_rc.version(\"privkey\", 1), 0o444)\n        # If no new key, permissions should be the same (we didn't write any keys)\n        self.test_rc.save_successor(1, b\"newcert\", None, b\"new chain\", self.config)\n        self.assertTrue(filesystem.check_mode(self.test_rc.version(\"privkey\", 2), 0o444))\n        # If new key, permissions should be kept as 644\n        self.test_rc.save_successor(2, b\"newcert\", b\"new_privkey\", b\"new chain\", self.config)\n        self.assertTrue(filesystem.check_mode(self.test_rc.version(\"privkey\", 3), 0o644))\n        # If permissions reverted, next renewal will also revert permissions of new key\n        filesystem.chmod(self.test_rc.version(\"privkey\", 3), 0o400)\n        self.test_rc.save_successor(3, b\"newcert\", b\"new_privkey\", b\"new chain\", self.config)\n        self.assertTrue(filesystem.check_mode(self.test_rc.version(\"privkey\", 4), 0o600))\n\n    @mock.patch(\"certbot._internal.storage.relevant_values\")\n    @mock.patch(\"certbot._internal.storage.filesystem.copy_ownership_and_apply_mode\")\n    def test_save_successor_maintains_gid(self, mock_ownership, mock_rv):\n        # Mock relevant_values() to claim that all values are relevant here\n        # (to avoid instantiating parser)\n        mock_rv.side_effect = lambda x: x\n        for kind in ALL_FOUR:\n            self._write_out_kind(kind, 1)\n        self.test_rc.update_all_links_to(1)\n        self.test_rc.save_successor(1, b\"newcert\", None, b\"new chain\", self.config)\n        self.assertFalse(mock_ownership.called)\n        self.test_rc.save_successor(2, b\"newcert\", b\"new_privkey\", b\"new chain\", self.config)\n        self.assertTrue(mock_ownership.called)\n\n    @mock.patch(\"certbot._internal.storage.relevant_values\")\n    def test_new_lineage(self, mock_rv):\n        \"\"\"Test for new_lineage() class method.\"\"\"\n        # Mock relevant_values to say everything is relevant here (so we\n        # don't have to mock the parser to help it decide!)\n        mock_rv.side_effect = lambda x: x\n\n        from certbot._internal import storage\n        result = storage.RenewableCert.new_lineage(\n            \"the-lineage.com\", b\"cert\", b\"privkey\", b\"chain\", self.config)\n        # This consistency check tests most relevant properties about the\n        # newly created cert lineage.\n        # pylint: disable=protected-access\n        self.assertTrue(result._consistent())\n        self.assertTrue(os.path.exists(os.path.join(\n            self.config.renewal_configs_dir, \"the-lineage.com.conf\")))\n        self.assertTrue(os.path.exists(os.path.join(\n            self.config.live_dir, \"README\")))\n        self.assertTrue(os.path.exists(os.path.join(\n            self.config.live_dir, \"the-lineage.com\", \"README\")))\n        self.assertTrue(filesystem.check_mode(result.key_path, 0o600))\n        with open(result.fullchain, \"rb\") as f:\n            self.assertEqual(f.read(), b\"cert\" + b\"chain\")\n        # Let's do it again and make sure it makes a different lineage\n        result = storage.RenewableCert.new_lineage(\n            \"the-lineage.com\", b\"cert2\", b\"privkey2\", b\"chain2\", self.config)\n        self.assertTrue(os.path.exists(os.path.join(\n            self.config.renewal_configs_dir, \"the-lineage.com-0001.conf\")))\n        self.assertTrue(os.path.exists(os.path.join(\n            self.config.live_dir, \"the-lineage.com-0001\", \"README\")))\n        # Now trigger the detection of already existing files\n        filesystem.mkdir(os.path.join(\n            self.config.live_dir, \"the-lineage.com-0002\"))\n        self.assertRaises(errors.CertStorageError,\n                          storage.RenewableCert.new_lineage, \"the-lineage.com\",\n                          b\"cert3\", b\"privkey3\", b\"chain3\", self.config)\n        filesystem.mkdir(os.path.join(self.config.default_archive_dir, \"other-example.com\"))\n        self.assertRaises(errors.CertStorageError,\n                          storage.RenewableCert.new_lineage,\n                          \"other-example.com\", b\"cert4\",\n                          b\"privkey4\", b\"chain4\", self.config)\n        # Make sure it can accept renewal parameters\n        result = storage.RenewableCert.new_lineage(\n            \"the-lineage.com\", b\"cert2\", b\"privkey2\", b\"chain2\", self.config)\n        # TODO: Conceivably we could test that the renewal parameters actually\n        #       got saved\n\n    @mock.patch(\"certbot._internal.storage.relevant_values\")\n    def test_new_lineage_nonexistent_dirs(self, mock_rv):\n        \"\"\"Test that directories can be created if they don't exist.\"\"\"\n        # Mock relevant_values to say everything is relevant here (so we\n        # don't have to mock the parser to help it decide!)\n        mock_rv.side_effect = lambda x: x\n\n        from certbot._internal import storage\n        shutil.rmtree(self.config.renewal_configs_dir)\n        shutil.rmtree(self.config.default_archive_dir)\n        shutil.rmtree(self.config.live_dir)\n\n        storage.RenewableCert.new_lineage(\n            \"the-lineage.com\", b\"cert2\", b\"privkey2\", b\"chain2\", self.config)\n        self.assertTrue(os.path.exists(\n            os.path.join(\n                self.config.renewal_configs_dir, \"the-lineage.com.conf\")))\n        self.assertTrue(os.path.exists(os.path.join(\n            self.config.live_dir, \"the-lineage.com\", \"privkey.pem\")))\n        self.assertTrue(os.path.exists(os.path.join(\n            self.config.default_archive_dir, \"the-lineage.com\", \"privkey1.pem\")))\n\n    @mock.patch(\"certbot._internal.storage.util.unique_lineage_name\")\n    def test_invalid_config_filename(self, mock_uln):\n        from certbot._internal import storage\n        mock_uln.return_value = \"this_does_not_end_with_dot_conf\", \"yikes\"\n        self.assertRaises(errors.CertStorageError,\n                          storage.RenewableCert.new_lineage, \"example.com\",\n                          \"cert\", \"privkey\", \"chain\", self.config)\n\n    def test_bad_kind(self):\n        self.assertRaises(\n            errors.CertStorageError, self.test_rc.current_target, \"elephant\")\n        self.assertRaises(\n            errors.CertStorageError, self.test_rc.current_version, \"elephant\")\n        self.assertRaises(\n            errors.CertStorageError, self.test_rc.version, \"elephant\", 17)\n        self.assertRaises(\n            errors.CertStorageError,\n            self.test_rc.available_versions, \"elephant\")\n        self.assertRaises(\n            errors.CertStorageError,\n            self.test_rc.newest_available_version, \"elephant\")\n        # pylint: disable=protected-access\n        self.assertRaises(\n            errors.CertStorageError,\n            self.test_rc._update_link_to, \"elephant\", 17)\n\n    @mock.patch(\"certbot.ocsp.RevocationChecker.ocsp_revoked_by_paths\")\n    def test_ocsp_revoked(self, mock_checker):\n        # Write out test files\n        for kind in ALL_FOUR:\n            self._write_out_kind(kind, 1)\n        version = self.test_rc.latest_common_version()\n        expected_cert_path = self.test_rc.version(\"cert\", version)\n        expected_chain_path = self.test_rc.version(\"chain\", version)\n\n        # Test with cert revoked\n        mock_checker.return_value = True\n        self.assertTrue(self.test_rc.ocsp_revoked(version))\n        self.assertEqual(mock_checker.call_args[0][0], expected_cert_path)\n        self.assertEqual(mock_checker.call_args[0][1], expected_chain_path)\n\n        # Test with cert not revoked\n        mock_checker.return_value = False\n        self.assertFalse(self.test_rc.ocsp_revoked(version))\n        self.assertEqual(mock_checker.call_args[0][0], expected_cert_path)\n        self.assertEqual(mock_checker.call_args[0][1], expected_chain_path)\n\n        # Test with error\n        mock_checker.side_effect = ValueError\n        with mock.patch(\"certbot._internal.storage.logger.warning\") as logger:\n            self.assertFalse(self.test_rc.ocsp_revoked(version))\n        self.assertEqual(mock_checker.call_args[0][0], expected_cert_path)\n        self.assertEqual(mock_checker.call_args[0][1], expected_chain_path)\n        log_msg = logger.call_args[0][0]\n        self.assertIn(\"An error occurred determining the OCSP status\", log_msg)\n\n    def test_add_time_interval(self):\n        from certbot._internal import storage\n\n        # this month has 30 days, and the next year is a leap year\n        time_1 = pytz.UTC.fromutc(datetime.datetime(2003, 11, 20, 11, 59, 21))\n\n        # this month has 31 days, and the next year is not a leap year\n        time_2 = pytz.UTC.fromutc(datetime.datetime(2012, 10, 18, 21, 31, 16))\n\n        # in different time zone (GMT+8)\n        time_3 = pytz.timezone('Asia/Shanghai').fromutc(\n            datetime.datetime(2015, 10, 26, 22, 25, 41))\n\n        intended = {\n            (time_1, \"\"): time_1,\n            (time_2, \"\"): time_2,\n            (time_3, \"\"): time_3,\n            (time_1, \"17 days\"): time_1 + datetime.timedelta(17),\n            (time_2, \"17 days\"): time_2 + datetime.timedelta(17),\n            (time_1, \"30\"): time_1 + datetime.timedelta(30),\n            (time_2, \"30\"): time_2 + datetime.timedelta(30),\n            (time_1, \"7 weeks\"): time_1 + datetime.timedelta(49),\n            (time_2, \"7 weeks\"): time_2 + datetime.timedelta(49),\n            # 1 month is always 30 days, no matter which month it is\n            (time_1, \"1 month\"): time_1 + datetime.timedelta(30),\n            (time_2, \"1 month\"): time_2 + datetime.timedelta(31),\n            # 1 year could be 365 or 366 days, depends on the year\n            (time_1, \"1 year\"): time_1 + datetime.timedelta(366),\n            (time_2, \"1 year\"): time_2 + datetime.timedelta(365),\n            (time_1, \"1 year 1 day\"): time_1 + datetime.timedelta(367),\n            (time_2, \"1 year 1 day\"): time_2 + datetime.timedelta(366),\n            (time_1, \"1 year-1 day\"): time_1 + datetime.timedelta(365),\n            (time_2, \"1 year-1 day\"): time_2 + datetime.timedelta(364),\n            (time_1, \"4 years\"): time_1 + datetime.timedelta(1461),\n            (time_2, \"4 years\"): time_2 + datetime.timedelta(1461),\n        }\n\n        for parameters, excepted in intended.items():\n            base_time, interval = parameters\n            self.assertEqual(storage.add_time_interval(base_time, interval),\n                             excepted)\n\n    def test_is_test_cert(self):\n        self.test_rc.configuration[\"renewalparams\"] = {}\n        rp = self.test_rc.configuration[\"renewalparams\"]\n        self.assertEqual(self.test_rc.is_test_cert, False)\n        rp[\"server\"] = \"https://acme-staging-v02.api.letsencrypt.org/directory\"\n        self.assertEqual(self.test_rc.is_test_cert, True)\n        rp[\"server\"] = \"https://staging.someotherca.com/directory\"\n        self.assertEqual(self.test_rc.is_test_cert, True)\n        rp[\"server\"] = \"https://acme-v01.api.letsencrypt.org/directory\"\n        self.assertEqual(self.test_rc.is_test_cert, False)\n        rp[\"server\"] = \"https://acme-v02.api.letsencrypt.org/directory\"\n        self.assertEqual(self.test_rc.is_test_cert, False)\n\n    def test_missing_cert(self):\n        from certbot._internal import storage\n        self.assertRaises(errors.CertStorageError,\n                          storage.RenewableCert,\n                          self.config_file.filename, self.config)\n        os.symlink(\"missing\", self.config_file[ALL_FOUR[0]])\n        self.assertRaises(errors.CertStorageError,\n                          storage.RenewableCert,\n                          self.config_file.filename, self.config)\n\n    def test_write_renewal_config(self):\n        # Mostly tested by the process of creating and updating lineages,\n        # but we can test that this successfully creates files, removes\n        # unneeded items, and preserves comments.\n        temp = os.path.join(self.config.config_dir, \"sample-file\")\n        temp2 = os.path.join(self.config.config_dir, \"sample-file.new\")\n        with open(temp, \"w\") as f:\n            f.write(\"[renewalparams]\\nuseful = value # A useful value\\n\"\n                    \"useless = value # Not needed\\n\")\n        filesystem.chmod(temp, 0o640)\n        target = {}\n        for x in ALL_FOUR:\n            target[x] = \"somewhere\"\n        archive_dir = \"the_archive\"\n        relevant_data = {\"useful\": \"new_value\"}\n\n        from certbot._internal import storage\n        storage.write_renewal_config(temp, temp2, archive_dir, target, relevant_data)\n\n        with open(temp2, \"r\") as f:\n            content = f.read()\n        # useful value was updated\n        self.assertTrue(\"useful = new_value\" in content)\n        # associated comment was preserved\n        self.assertTrue(\"A useful value\" in content)\n        # useless value was deleted\n        self.assertTrue(\"useless\" not in content)\n        # check version was stored\n        self.assertTrue(\"version = {0}\".format(certbot.__version__) in content)\n        # ensure permissions are copied\n        self.assertEqual(stat.S_IMODE(os.lstat(temp).st_mode),\n                         stat.S_IMODE(os.lstat(temp2).st_mode))\n\n    def test_update_symlinks(self):\n        from certbot._internal import storage\n        archive_dir_path = os.path.join(self.config.config_dir, \"archive\", \"example.org\")\n        for kind in ALL_FOUR:\n            live_path = self.config_file[kind]\n            basename = kind + \"1.pem\"\n            archive_path = os.path.join(archive_dir_path, basename)\n            open(archive_path, 'a').close()\n            os.symlink(os.path.join(self.config.config_dir, basename), live_path)\n        self.assertRaises(errors.CertStorageError,\n                          storage.RenewableCert, self.config_file.filename,\n                          self.config)\n        storage.RenewableCert(self.config_file.filename, self.config,\n            update_symlinks=True)\n\nclass DeleteFilesTest(BaseRenewableCertTest):\n    \"\"\"Tests for certbot._internal.storage.delete_files\"\"\"\n    def setUp(self):\n        super(DeleteFilesTest, self).setUp()\n\n        for kind in ALL_FOUR:\n            kind_path = os.path.join(self.config.config_dir, \"live\", \"example.org\",\n                                        kind + \".pem\")\n            with open(kind_path, 'a'):\n                pass\n        self.config_file.write()\n        self.assertTrue(os.path.exists(os.path.join(\n            self.config.renewal_configs_dir, \"example.org.conf\")))\n        self.assertTrue(os.path.exists(os.path.join(\n            self.config.live_dir, \"example.org\")))\n        self.assertTrue(os.path.exists(os.path.join(\n            self.config.config_dir, \"archive\", \"example.org\")))\n\n    def _call(self):\n        from certbot._internal import storage\n        with mock.patch(\"certbot._internal.storage.logger\"):\n            storage.delete_files(self.config, \"example.org\")\n\n    def test_delete_all_files(self):\n        self._call()\n\n        self.assertFalse(os.path.exists(os.path.join(\n            self.config.renewal_configs_dir, \"example.org.conf\")))\n        self.assertFalse(os.path.exists(os.path.join(\n            self.config.live_dir, \"example.org\")))\n        self.assertFalse(os.path.exists(os.path.join(\n            self.config.config_dir, \"archive\", \"example.org\")))\n\n    def test_bad_renewal_config(self):\n        with open(self.config_file.filename, 'a') as config_file:\n            config_file.write(\"asdfasfasdfasdf\")\n\n        self.assertRaises(errors.CertStorageError, self._call)\n        self.assertTrue(os.path.exists(os.path.join(\n            self.config.live_dir, \"example.org\")))\n        self.assertFalse(os.path.exists(os.path.join(\n            self.config.renewal_configs_dir, \"example.org.conf\")))\n\n    def test_no_renewal_config(self):\n        os.remove(self.config_file.filename)\n        self.assertRaises(errors.CertStorageError, self._call)\n        self.assertTrue(os.path.exists(os.path.join(\n            self.config.live_dir, \"example.org\")))\n        self.assertFalse(os.path.exists(self.config_file.filename))\n\n    def test_no_cert_file(self):\n        os.remove(os.path.join(\n            self.config.live_dir, \"example.org\", \"cert.pem\"))\n        self._call()\n        self.assertFalse(os.path.exists(self.config_file.filename))\n        self.assertFalse(os.path.exists(os.path.join(\n            self.config.live_dir, \"example.org\")))\n        self.assertFalse(os.path.exists(os.path.join(\n            self.config.config_dir, \"archive\", \"example.org\")))\n\n    def test_no_readme_file(self):\n        os.remove(os.path.join(\n            self.config.live_dir, \"example.org\", \"README\"))\n        self._call()\n        self.assertFalse(os.path.exists(self.config_file.filename))\n        self.assertFalse(os.path.exists(os.path.join(\n            self.config.live_dir, \"example.org\")))\n        self.assertFalse(os.path.exists(os.path.join(\n            self.config.config_dir, \"archive\", \"example.org\")))\n\n    def test_livedir_not_empty(self):\n        with open(os.path.join(\n            self.config.live_dir, \"example.org\", \"other_file\"), 'a'):\n            pass\n        self._call()\n        self.assertFalse(os.path.exists(self.config_file.filename))\n        self.assertTrue(os.path.exists(os.path.join(\n            self.config.live_dir, \"example.org\")))\n        self.assertFalse(os.path.exists(os.path.join(\n            self.config.config_dir, \"archive\", \"example.org\")))\n\n    def test_no_archive(self):\n        archive_dir = os.path.join(self.config.config_dir, \"archive\", \"example.org\")\n        os.rmdir(archive_dir)\n        self._call()\n        self.assertFalse(os.path.exists(self.config_file.filename))\n        self.assertFalse(os.path.exists(os.path.join(\n            self.config.live_dir, \"example.org\")))\n        self.assertFalse(os.path.exists(archive_dir))\n\nclass CertPathForCertNameTest(BaseRenewableCertTest):\n    \"\"\"Test for certbot._internal.storage.cert_path_for_cert_name\"\"\"\n    def setUp(self):\n        super(CertPathForCertNameTest, self).setUp()\n        self.config_file.write()\n        self._write_out_ex_kinds()\n        self.fullchain = os.path.join(self.config.config_dir, 'live', 'example.org',\n                'fullchain.pem')\n        self.config.cert_path = (self.fullchain, '')\n\n    def _call(self, cli_config, certname):\n        from certbot._internal.storage import cert_path_for_cert_name\n        return cert_path_for_cert_name(cli_config, certname)\n\n    def test_simple_cert_name(self):\n        self.assertEqual(self._call(self.config, 'example.org'), (self.fullchain, 'fullchain'))\n\n    def test_no_such_cert_name(self):\n        self.assertRaises(errors.CertStorageError, self._call, self.config, 'fake-example.org')\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  },
  {
    "path": "tests/util_test.py",
    "content": "\"\"\"Tests for certbot.util.\"\"\"\nimport argparse\nimport errno\nimport sys\nimport unittest\n\nimport mock\nimport six\nfrom six.moves import reload_module  # pylint: disable=import-error\n\nfrom certbot import errors\nfrom certbot.compat import filesystem\nfrom certbot.compat import os\nimport certbot.tests.util as test_util\n\n\nclass RunScriptTest(unittest.TestCase):\n    \"\"\"Tests for certbot.util.run_script.\"\"\"\n    @classmethod\n    def _call(cls, params):\n        from certbot.util import run_script\n        return run_script(params)\n\n    @mock.patch(\"certbot.util.subprocess.Popen\")\n    def test_default(self, mock_popen):\n        \"\"\"These will be changed soon enough with reload.\"\"\"\n        mock_popen().returncode = 0\n        mock_popen().communicate.return_value = (\"stdout\", \"stderr\")\n\n        out, err = self._call([\"test\"])\n        self.assertEqual(out, \"stdout\")\n        self.assertEqual(err, \"stderr\")\n\n    @mock.patch(\"certbot.util.subprocess.Popen\")\n    def test_bad_process(self, mock_popen):\n        mock_popen.side_effect = OSError\n\n        self.assertRaises(errors.SubprocessError, self._call, [\"test\"])\n\n    @mock.patch(\"certbot.util.subprocess.Popen\")\n    def test_failure(self, mock_popen):\n        mock_popen().communicate.return_value = (\"\", \"\")\n        mock_popen().returncode = 1\n\n        self.assertRaises(errors.SubprocessError, self._call, [\"test\"])\n\n\nclass ExeExistsTest(unittest.TestCase):\n    \"\"\"Tests for certbot.util.exe_exists.\"\"\"\n\n    @classmethod\n    def _call(cls, exe):\n        from certbot.util import exe_exists\n        return exe_exists(exe)\n\n    def test_exe_exists(self):\n        with mock.patch(\"certbot.util.filesystem.is_executable\", return_value=True):\n            self.assertTrue(self._call(\"/path/to/exe\"))\n\n    def test_exe_not_exists(self):\n        with mock.patch(\"certbot.util.filesystem.is_executable\", return_value=False):\n            self.assertFalse(self._call(\"/path/to/exe\"))\n\n\nclass LockDirUntilExit(test_util.TempDirTestCase):\n    \"\"\"Tests for certbot.util.lock_dir_until_exit.\"\"\"\n    @classmethod\n    def _call(cls, *args, **kwargs):\n        from certbot.util import lock_dir_until_exit\n        return lock_dir_until_exit(*args, **kwargs)\n\n    def setUp(self):\n        super(LockDirUntilExit, self).setUp()\n        # reset global state from other tests\n        import certbot.util\n        reload_module(certbot.util)\n\n    @mock.patch('certbot.util.logger')\n    @mock.patch('certbot.util.atexit_register')\n    def test_it(self, mock_register, mock_logger):\n        subdir = os.path.join(self.tempdir, 'subdir')\n        filesystem.mkdir(subdir)\n        self._call(self.tempdir)\n        self._call(subdir)\n        self._call(subdir)\n\n        self.assertEqual(mock_register.call_count, 1)\n        registered_func = mock_register.call_args[0][0]\n\n        from certbot import util\n        # Despite lock_dir_until_exit has been called twice to subdir, its lock should have been\n        # added only once. So we expect to have two lock references: for self.tempdir and subdir\n        self.assertTrue(len(util._LOCKS) == 2)  # pylint: disable=protected-access\n        registered_func()  # Exception should not be raised\n        # Logically, logger.debug, that would be invoked in case of unlock failure,\n        # should never been called.\n        self.assertEqual(mock_logger.debug.call_count, 0)\n\n\nclass SetUpCoreDirTest(test_util.TempDirTestCase):\n    \"\"\"Tests for certbot.util.make_or_verify_core_dir.\"\"\"\n\n    def _call(self, *args, **kwargs):\n        from certbot.util import set_up_core_dir\n        return set_up_core_dir(*args, **kwargs)\n\n    @mock.patch('certbot.util.lock_dir_until_exit')\n    def test_success(self, mock_lock):\n        new_dir = os.path.join(self.tempdir, 'new')\n        self._call(new_dir, 0o700, False)\n        self.assertTrue(os.path.exists(new_dir))\n        self.assertEqual(mock_lock.call_count, 1)\n\n    @mock.patch('certbot.util.make_or_verify_dir')\n    def test_failure(self, mock_make_or_verify):\n        mock_make_or_verify.side_effect = OSError\n        self.assertRaises(errors.Error, self._call, self.tempdir, 0o700, False)\n\n\nclass MakeOrVerifyDirTest(test_util.TempDirTestCase):\n    \"\"\"Tests for certbot.util.make_or_verify_dir.\n\n    Note that it is not possible to test for a wrong directory owner,\n    as this testing script would have to be run as root.\n\n    \"\"\"\n\n    def setUp(self):\n        super(MakeOrVerifyDirTest, self).setUp()\n\n        self.path = os.path.join(self.tempdir, \"foo\")\n        filesystem.mkdir(self.path, 0o600)\n\n    def _call(self, directory, mode):\n        from certbot.util import make_or_verify_dir\n        return make_or_verify_dir(directory, mode, strict=True)\n\n    def test_creates_dir_when_missing(self):\n        path = os.path.join(self.tempdir, \"bar\")\n        self._call(path, 0o650)\n        self.assertTrue(os.path.isdir(path))\n        self.assertTrue(filesystem.check_mode(path, 0o650))\n\n    def test_existing_correct_mode_does_not_fail(self):\n        self._call(self.path, 0o600)\n        self.assertTrue(filesystem.check_mode(self.path, 0o600))\n\n    def test_existing_wrong_mode_fails(self):\n        self.assertRaises(errors.Error, self._call, self.path, 0o400)\n\n    def test_reraises_os_error(self):\n        with mock.patch.object(filesystem, \"makedirs\") as makedirs:\n            makedirs.side_effect = OSError()\n            self.assertRaises(OSError, self._call, \"bar\", 12312312)\n\n\nclass UniqueFileTest(test_util.TempDirTestCase):\n    \"\"\"Tests for certbot.util.unique_file.\"\"\"\n\n    def setUp(self):\n        super(UniqueFileTest, self).setUp()\n\n        self.default_name = os.path.join(self.tempdir, \"foo.txt\")\n\n    def _call(self, mode=0o600):\n        from certbot.util import unique_file\n        return unique_file(self.default_name, mode)\n\n    def test_returns_fd_for_writing(self):\n        fd, name = self._call()\n        fd.write(\"bar\")\n        fd.close()\n        with open(name) as f:\n            self.assertEqual(f.read(), \"bar\")\n\n    def test_right_mode(self):\n        fd1, name1 = self._call(0o700)\n        fd2, name2 = self._call(0o600)\n        self.assertTrue(filesystem.check_mode(name1, 0o700))\n        self.assertTrue(filesystem.check_mode(name2, 0o600))\n        fd1.close()\n        fd2.close()\n\n    def test_default_exists(self):\n        fd1, name1 = self._call()  # create 0000_foo.txt\n        fd2, name2 = self._call()\n        fd3, name3 = self._call()\n\n        self.assertNotEqual(name1, name2)\n        self.assertNotEqual(name1, name3)\n        self.assertNotEqual(name2, name3)\n\n        self.assertEqual(os.path.dirname(name1), self.tempdir)\n        self.assertEqual(os.path.dirname(name2), self.tempdir)\n        self.assertEqual(os.path.dirname(name3), self.tempdir)\n\n        basename1 = os.path.basename(name2)\n        self.assertTrue(basename1.endswith(\"foo.txt\"))\n        basename2 = os.path.basename(name2)\n        self.assertTrue(basename2.endswith(\"foo.txt\"))\n        basename3 = os.path.basename(name3)\n        self.assertTrue(basename3.endswith(\"foo.txt\"))\n\n        fd1.close()\n        fd2.close()\n        fd3.close()\n\n\ntry:\n    file_type = file\nexcept NameError:\n    import io\n    file_type = io.TextIOWrapper  # type: ignore\n\n\nclass UniqueLineageNameTest(test_util.TempDirTestCase):\n    \"\"\"Tests for certbot.util.unique_lineage_name.\"\"\"\n\n    def _call(self, filename, mode=0o777):\n        from certbot.util import unique_lineage_name\n        return unique_lineage_name(self.tempdir, filename, mode)\n\n    def test_basic(self):\n        f, path = self._call(\"wow\")\n        self.assertTrue(isinstance(f, file_type))\n        self.assertEqual(os.path.join(self.tempdir, \"wow.conf\"), path)\n        f.close()\n\n    def test_multiple(self):\n        items = []\n        for _ in six.moves.range(10):\n            items.append(self._call(\"wow\"))\n        f, name = items[-1]\n        self.assertTrue(isinstance(f, file_type))\n        self.assertTrue(isinstance(name, six.string_types))\n        self.assertTrue(\"wow-0009.conf\" in name)\n        for f, _ in items:\n            f.close()\n\n    def test_failure(self):\n        with mock.patch(\"certbot.compat.filesystem.open\", side_effect=OSError(errno.EIO)):\n            self.assertRaises(OSError, self._call, \"wow\")\n\n\nclass SafelyRemoveTest(test_util.TempDirTestCase):\n    \"\"\"Tests for certbot.util.safely_remove.\"\"\"\n\n    def setUp(self):\n        super(SafelyRemoveTest, self).setUp()\n\n        self.path = os.path.join(self.tempdir, \"foo\")\n\n    def _call(self):\n        from certbot.util import safely_remove\n        return safely_remove(self.path)\n\n    def test_exists(self):\n        with open(self.path, \"w\"):\n            pass  # just create the file\n        self._call()\n        self.assertFalse(os.path.exists(self.path))\n\n    def test_missing(self):\n        self._call()\n        # no error, yay!\n        self.assertFalse(os.path.exists(self.path))\n\n    def test_other_error_passthrough(self):\n        with mock.patch(\"certbot.util.os.remove\") as mock_remove:\n            mock_remove.side_effect = OSError\n            self.assertRaises(OSError, self._call)\n\n\nclass SafeEmailTest(unittest.TestCase):\n    \"\"\"Test safe_email.\"\"\"\n    @classmethod\n    def _call(cls, addr):\n        from certbot.util import safe_email\n        return safe_email(addr)\n\n    def test_valid_emails(self):\n        addrs = [\n            \"certbot@certbot.org\",\n            \"tbd.ade@gmail.com\",\n            \"abc_def.jdk@hotmail.museum\",\n        ]\n        for addr in addrs:\n            self.assertTrue(self._call(addr), \"%s failed.\" % addr)\n\n    def test_invalid_emails(self):\n        addrs = [\n            \"certbot@certbot..org\",\n            \".tbd.ade@gmail.com\",\n            \"~/abc_def.jdk@hotmail.museum\",\n        ]\n        for addr in addrs:\n            self.assertFalse(self._call(addr), \"%s failed.\" % addr)\n\n\nclass AddDeprecatedArgumentTest(unittest.TestCase):\n    \"\"\"Test add_deprecated_argument.\"\"\"\n    def setUp(self):\n        self.parser = argparse.ArgumentParser()\n\n    def _call(self, argument_name, nargs):\n        from certbot.util import add_deprecated_argument\n        add_deprecated_argument(self.parser.add_argument, argument_name, nargs)\n\n    def test_warning_no_arg(self):\n        self._call(\"--old-option\", 0)\n        with mock.patch(\"certbot.util.logger.warning\") as mock_warn:\n            self.parser.parse_args([\"--old-option\"])\n        self.assertEqual(mock_warn.call_count, 1)\n        self.assertTrue(\"is deprecated\" in mock_warn.call_args[0][0])\n        self.assertEqual(\"--old-option\", mock_warn.call_args[0][1])\n\n    def test_warning_with_arg(self):\n        self._call(\"--old-option\", 1)\n        with mock.patch(\"certbot.util.logger.warning\") as mock_warn:\n            self.parser.parse_args([\"--old-option\", \"42\"])\n        self.assertEqual(mock_warn.call_count, 1)\n        self.assertTrue(\"is deprecated\" in mock_warn.call_args[0][0])\n        self.assertEqual(\"--old-option\", mock_warn.call_args[0][1])\n\n    def test_help(self):\n        self._call(\"--old-option\", 2)\n        stdout = six.StringIO()\n        with mock.patch(\"sys.stdout\", new=stdout):\n            try:\n                self.parser.parse_args([\"-h\"])\n            except SystemExit:\n                pass\n        self.assertTrue(\"--old-option\" not in stdout.getvalue())\n\n    def test_set_constant(self):\n        \"\"\"Test when ACTION_TYPES_THAT_DONT_NEED_A_VALUE is a set.\n\n        This variable is a set in configargparse versions < 0.12.0.\n\n        \"\"\"\n        self._test_constant_common(set)\n\n    def test_tuple_constant(self):\n        \"\"\"Test when ACTION_TYPES_THAT_DONT_NEED_A_VALUE is a tuple.\n\n        This variable is a tuple in configargparse versions >= 0.12.0.\n\n        \"\"\"\n        self._test_constant_common(tuple)\n\n    def _test_constant_common(self, typ):\n        with mock.patch(\"certbot.util.configargparse\") as mock_configargparse:\n            mock_configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE = typ()\n            self._call(\"--old-option\", 1)\n            self._call(\"--old-option2\", 2)\n        self.assertEqual(\n            len(mock_configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE), 1)\n\n\nclass EnforceLeValidity(unittest.TestCase):\n    \"\"\"Test enforce_le_validity.\"\"\"\n    def _call(self, domain):\n        from certbot.util import enforce_le_validity\n        return enforce_le_validity(domain)\n\n    def test_sanity(self):\n        self.assertRaises(errors.ConfigurationError, self._call, u\"..\")\n\n    def test_invalid_chars(self):\n        self.assertRaises(\n            errors.ConfigurationError, self._call, u\"hello_world.example.com\")\n\n    def test_leading_hyphen(self):\n        self.assertRaises(\n            errors.ConfigurationError, self._call, u\"-a.example.com\")\n\n    def test_trailing_hyphen(self):\n        self.assertRaises(\n            errors.ConfigurationError, self._call, u\"a-.example.com\")\n\n    def test_one_label(self):\n        self.assertRaises(errors.ConfigurationError, self._call, u\"com\")\n\n    def test_valid_domain(self):\n        self.assertEqual(self._call(u\"example.com\"), u\"example.com\")\n\n    def test_input_with_scheme(self):\n        self.assertRaises(errors.ConfigurationError, self._call, u\"http://example.com\")\n        self.assertRaises(errors.ConfigurationError, self._call, u\"https://example.com\")\n\n    def test_valid_input_with_scheme_name(self):\n        self.assertEqual(self._call(u\"http.example.com\"), u\"http.example.com\")\n\n\nclass EnforceDomainSanityTest(unittest.TestCase):\n    \"\"\"Test enforce_domain_sanity.\"\"\"\n\n    def _call(self, domain):\n        from certbot.util import enforce_domain_sanity\n        return enforce_domain_sanity(domain)\n\n    def test_nonascii_str(self):\n        self.assertRaises(errors.ConfigurationError, self._call,\n                          u\"eichh\\u00f6rnchen.example.com\".encode(\"utf-8\"))\n\n    def test_nonascii_unicode(self):\n        self.assertRaises(errors.ConfigurationError, self._call,\n                          u\"eichh\\u00f6rnchen.example.com\")\n\n    def test_too_long(self):\n        long_domain = u\"a\"*256\n        self.assertRaises(errors.ConfigurationError, self._call,\n                          long_domain)\n\n    def test_not_too_long(self):\n        not_too_long_domain = u\"{0}.{1}.{2}.{3}\".format(\"a\"*63, \"b\"*63, \"c\"*63, \"d\"*63)\n        self._call(not_too_long_domain)\n\n    def test_empty_label(self):\n        empty_label_domain = u\"fizz..example.com\"\n        self.assertRaises(errors.ConfigurationError, self._call,\n                          empty_label_domain)\n\n    def test_empty_trailing_label(self):\n        empty_trailing_label_domain = u\"example.com..\"\n        self.assertRaises(errors.ConfigurationError, self._call,\n                          empty_trailing_label_domain)\n\n    def test_long_label_1(self):\n        long_label_domain = u\"a\"*64\n        self.assertRaises(errors.ConfigurationError, self._call,\n                          long_label_domain)\n\n    def test_long_label_2(self):\n        long_label_domain = u\"{0}.{1}.com\".format(u\"a\"*64, u\"b\"*63)\n        self.assertRaises(errors.ConfigurationError, self._call,\n                          long_label_domain)\n\n    def test_not_long_label(self):\n        not_too_long_label_domain = u\"{0}.{1}.com\".format(u\"a\"*63, u\"b\"*63)\n        self._call(not_too_long_label_domain)\n\n    def test_empty_domain(self):\n        empty_domain = u\"\"\n        self.assertRaises(errors.ConfigurationError, self._call,\n                          empty_domain)\n\n    def test_punycode_ok(self):\n        # Punycode is now legal, so no longer an error; instead check\n        # that it's _not_ an error (at the initial sanity check stage)\n        self._call('this.is.xn--ls8h.tld')\n\n\nclass IsWildcardDomainTest(unittest.TestCase):\n    \"\"\"Tests for is_wildcard_domain.\"\"\"\n\n    def setUp(self):\n        self.wildcard = u\"*.example.org\"\n        self.no_wildcard = u\"example.org\"\n\n    def _call(self, domain):\n        from certbot.util import is_wildcard_domain\n        return is_wildcard_domain(domain)\n\n    def test_no_wildcard(self):\n        self.assertFalse(self._call(self.no_wildcard))\n        self.assertFalse(self._call(self.no_wildcard.encode()))\n\n    def test_wildcard(self):\n        self.assertTrue(self._call(self.wildcard))\n        self.assertTrue(self._call(self.wildcard.encode()))\n\n\nclass OsInfoTest(unittest.TestCase):\n    \"\"\"Test OS / distribution detection\"\"\"\n\n    @mock.patch(\"certbot.util.distro\")\n    @unittest.skipUnless(sys.platform.startswith(\"linux\"), \"requires Linux\")\n    def test_systemd_os_release_like(self, m_distro):\n        import certbot.util as cbutil\n        m_distro.like.return_value = \"first debian third\"\n        id_likes = cbutil.get_systemd_os_like()\n        self.assertEqual(len(id_likes), 3)\n        self.assertTrue(\"debian\" in id_likes)\n\n    @mock.patch(\"certbot.util.distro\")\n    @unittest.skipUnless(sys.platform.startswith(\"linux\"), \"requires Linux\")\n    def test_get_os_info_ua(self, m_distro):\n        import certbot.util as cbutil\n        with mock.patch('platform.system_alias',\n                        return_value=('linux', '42', '42')):\n            m_distro.name.return_value = \"\"\n            m_distro.linux_distribution.return_value = (\"something\", \"1.0\", \"codename\")\n            cbutil.get_python_os_info(pretty=True)\n            self.assertEqual(cbutil.get_os_info_ua(),\n                            \" \".join(cbutil.get_python_os_info(pretty=True)))\n\n        m_distro.name.return_value = \"whatever\"\n        self.assertEqual(cbutil.get_os_info_ua(), \"whatever\")\n\n    @mock.patch(\"certbot.util.distro\")\n    @unittest.skipUnless(sys.platform.startswith(\"linux\"), \"requires Linux\")\n    def test_get_os_info(self, m_distro):\n        import certbot.util as cbutil\n        with mock.patch(\"platform.system\") as mock_platform:\n            m_distro.linux_distribution.return_value = (\"name\", \"version\", 'x')\n            mock_platform.return_value = \"linux\"\n            self.assertEqual(cbutil.get_os_info(), (\"name\", \"version\"))\n\n            m_distro.linux_distribution.return_value = (\"something\", \"else\")\n            self.assertEqual(cbutil.get_os_info(), (\"something\", \"else\"))\n\n    @mock.patch(\"certbot.util.subprocess.Popen\")\n    def test_non_systemd_os_info(self, popen_mock):\n        import certbot.util as cbutil\n        with mock.patch('certbot.util._USE_DISTRO', False):\n            with mock.patch('platform.system_alias',\n                            return_value=('NonSystemD', '42', '42')):\n                self.assertEqual(cbutil.get_python_os_info()[0], 'nonsystemd')\n\n            with mock.patch('platform.system_alias',\n                            return_value=('darwin', '', '')):\n                comm_mock = mock.Mock()\n                comm_attrs = {'communicate.return_value':\n                            ('42.42.42', 'error')}\n                comm_mock.configure_mock(**comm_attrs)\n                popen_mock.return_value = comm_mock\n                self.assertEqual(cbutil.get_python_os_info()[0], 'darwin')\n                self.assertEqual(cbutil.get_python_os_info()[1], '42.42.42')\n\n            with mock.patch('platform.system_alias',\n                            return_value=('freebsd', '9.3-RC3-p1', '')):\n                self.assertEqual(cbutil.get_python_os_info(), (\"freebsd\", \"9\"))\n\n            with mock.patch('platform.system_alias',\n                            return_value=('windows', '', '')):\n                with mock.patch('platform.win32_ver',\n                                return_value=('4242', '95', '2', '')):\n                    self.assertEqual(cbutil.get_python_os_info(),\n                                    (\"windows\", \"95\"))\n\n    @mock.patch(\"certbot.util.distro\")\n    @unittest.skipUnless(sys.platform.startswith(\"linux\"), \"requires Linux\")\n    def test_python_os_info_notfound(self, m_distro):\n        import certbot.util as cbutil\n        m_distro.linux_distribution.return_value = ('', '', '')\n        self.assertEqual(cbutil.get_python_os_info()[0], \"linux\")\n\n    @mock.patch(\"certbot.util.distro\")\n    @unittest.skipUnless(sys.platform.startswith(\"linux\"), \"requires Linux\")\n    def test_python_os_info_custom(self, m_distro):\n        import certbot.util as cbutil\n        m_distro.linux_distribution.return_value = ('testdist', '42', '')\n        self.assertEqual(cbutil.get_python_os_info(), (\"testdist\", \"42\"))\n\n\nclass AtexitRegisterTest(unittest.TestCase):\n    \"\"\"Tests for certbot.util.atexit_register.\"\"\"\n    def setUp(self):\n        self.func = mock.MagicMock()\n        self.args = ('hi',)\n        self.kwargs = {'answer': 42}\n\n    @classmethod\n    def _call(cls, *args, **kwargs):\n        from certbot.util import atexit_register\n        return atexit_register(*args, **kwargs)\n\n    def test_called(self):\n        self._test_common(os.getpid())\n        self.func.assert_called_with(*self.args, **self.kwargs)\n\n    def test_not_called(self):\n        self._test_common(initial_pid=-1)\n        self.assertFalse(self.func.called)\n\n    def _test_common(self, initial_pid):\n        with mock.patch('certbot.util._INITIAL_PID', initial_pid):\n            with mock.patch('certbot.util.atexit') as mock_atexit:\n                self._call(self.func, *self.args, **self.kwargs)\n\n            # _INITIAL_PID must be mocked when calling atexit_func\n            self.assertTrue(mock_atexit.register.called)\n            args, kwargs = mock_atexit.register.call_args\n            atexit_func = args[0]\n            atexit_func(*args[1:], **kwargs)\n\n\nif __name__ == \"__main__\":\n    unittest.main()  # pragma: no cover\n"
  }
]