From 6f511983944e20805f92eb71a1c9782d9131e005 Mon Sep 17 00:00:00 2001 From: sydneyli Date: Thu, 5 Apr 2018 14:03:19 -0700 Subject: [PATCH] Sendmail mvp --- certbot-sendmail/LICENSE.txt | 216 +++++++++++++++++ certbot-sendmail/MANIFEST.in | 2 + certbot-sendmail/README.rst | 1 + certbot-sendmail/certbot_sendmail/__init__.py | 1 + .../certbot_sendmail/configurator.py | 226 ++++++++++++++++++ .../certbot_sendmail/constants.py | 9 + .../certbot_sendmail/options-ssl-dovecot.conf | 11 + .../certbot_sendmail/tests/__init__.py | 1 + .../tests/configurator_test.py | 80 +++++++ .../tests/testdata/etc_mail/starttls.m4 | 55 +++++ certbot-sendmail/setup.cfg | 2 + certbot-sendmail/setup.py | 70 ++++++ certbot/plugins/disco.py | 1 + tools/venv.sh | 1 + tools/venv3.sh | 1 + 15 files changed, 677 insertions(+) create mode 100644 certbot-sendmail/LICENSE.txt create mode 100644 certbot-sendmail/MANIFEST.in create mode 100644 certbot-sendmail/README.rst create mode 100644 certbot-sendmail/certbot_sendmail/__init__.py create mode 100644 certbot-sendmail/certbot_sendmail/configurator.py create mode 100644 certbot-sendmail/certbot_sendmail/constants.py create mode 100644 certbot-sendmail/certbot_sendmail/options-ssl-dovecot.conf create mode 100644 certbot-sendmail/certbot_sendmail/tests/__init__.py create mode 100644 certbot-sendmail/certbot_sendmail/tests/configurator_test.py create mode 100755 certbot-sendmail/certbot_sendmail/tests/testdata/etc_mail/starttls.m4 create mode 100644 certbot-sendmail/setup.cfg create mode 100644 certbot-sendmail/setup.py diff --git a/certbot-sendmail/LICENSE.txt b/certbot-sendmail/LICENSE.txt new file mode 100644 index 000000000..9a792ccb7 --- /dev/null +++ b/certbot-sendmail/LICENSE.txt @@ -0,0 +1,216 @@ + Copyright 2018 Electronic Frontier Foundation and others + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Incorporating code from nginxparser + Copyright 2014 Fatih Erikli + Licensed MIT + + +Text of Apache License +====================== + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + +Text of MIT License +=================== +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/certbot-sendmail/MANIFEST.in b/certbot-sendmail/MANIFEST.in new file mode 100644 index 000000000..97e2ad3df --- /dev/null +++ b/certbot-sendmail/MANIFEST.in @@ -0,0 +1,2 @@ +include LICENSE.txt +include README.rst diff --git a/certbot-sendmail/README.rst b/certbot-sendmail/README.rst new file mode 100644 index 000000000..9372baf0a --- /dev/null +++ b/certbot-sendmail/README.rst @@ -0,0 +1 @@ +Sendmail plugin for Certbot diff --git a/certbot-sendmail/certbot_sendmail/__init__.py b/certbot-sendmail/certbot_sendmail/__init__.py new file mode 100644 index 000000000..b223b3bc6 --- /dev/null +++ b/certbot-sendmail/certbot_sendmail/__init__.py @@ -0,0 +1 @@ +"""Certbot sendmail plugin.""" diff --git a/certbot-sendmail/certbot_sendmail/configurator.py b/certbot-sendmail/certbot_sendmail/configurator.py new file mode 100644 index 000000000..a2774803b --- /dev/null +++ b/certbot-sendmail/certbot_sendmail/configurator.py @@ -0,0 +1,226 @@ +"""Sendmail Configuration""" +import sys +import logging +import os +import difflib +import re +import socket +import subprocess +import tempfile +import time + +import OpenSSL +import six +import zope.component +import zope.interface + +from certbot import constants as core_constants +from certbot import crypto_util +from certbot import errors +from certbot import interfaces +from certbot import util + +from certbot.plugins import common + +from certbot_sendmail import constants + + +logger = logging.getLogger(__name__) + + +class FileChanges(object): + def __init__(self, filepath): + self._filepath = filepath + self._lines = [] + self._append = [] + if not os.path.isfile(filepath): + open(filepath, "a").close() + with open(filepath) as f: + self._lines = f.readlines() + self._diff = {} # line number: new line + + def replace_first(self, regex_find_line, replace_value, full_replace): + """ regex to match line, new line to replace it with.""" + p = re.compile(regex_find_line) + for i, line in enumerate(self._lines): + result = re.search(p, line) + if not result or not result.group("value"): + continue + start, end = result.span("value") + self._diff[i] = line[0:start] + replace_value + line[end:] + return + self._append.append(full_replace) + + def flush(self, diff_file=None): + """ print diff """ + new_filelines = [] + for i, line in enumerate(self._lines): + to_append = line + if i in self._diff: + to_append = self._diff[i] + new_filelines.append(to_append) + new_filelines.extend(self._append) + + diff = "".join(difflib.unified_diff(self._lines, new_filelines)) + if diff_file: + with open(diff_file, "w") as f: + f.write(diff) + message = ("The appropriate diff has been written to {diff_file}.\n" + "Review these changes, then apply them with:\n\n" + " patch -b {tls_file} -i {diff_file}\n\n" + "This should also create a backup of the original file at {tls_file}.orig\n").format( + diff_file=diff_file, tls_file=self._filepath) + else: + message = ("Review and apply the following diff to {tls_file}." + "Continue when finished:\n\n{content}\n\n".format( + tls_file=self._filepath, content=diff)) + zope.component.getUtility(interfaces.IDisplay).notification(message, pause=True) + +@zope.interface.implementer(interfaces.IAuthenticator, interfaces.IInstaller) +@zope.interface.provider(interfaces.IPluginFactory) +class SendmailConfigurator(common.Installer): + # pylint: disable=too-many-instance-attributes,too-many-public-methods + """Sendmail configurator. + + .. todo:: Add proper support for comments in the config. Currently, + config files modified by the configurator will lose all their comments. + + :ivar config: Configuration. + :type config: :class:`~certbot.interfaces.IConfig` + + :ivar str save_notes: Human-readable config change notes + + :ivar reverter: saves and reverts checkpoints + :type reverter: :class:`certbot.reverter.Reverter` + + """ + + description = "Sendmail Web Server plugin - Alpha" + + @classmethod + def add_parser_arguments(cls, add): + add("server-root", default=constants.CLI_DEFAULTS["server_root"], + help="Sendmail server root directory.") + add("tls-config-file", default=constants.CLI_DEFAULTS["tls_config_file"], + help="Filename for the relevant TLS options.") + add("diff-file", default=constants.CLI_DEFAULTS["diff_file"], + help="Optional output file for diff. If specified diff will be written" + "to file rather than stdout.") + + @property + def tls_config_file(self): + """Sendmail TLS config file.""" + # TODO (sydli): check if there's an intermediate "TLS" directory; if so, drop + # it there. + return os.path.join(self.conf("server_root"), self.conf("tls-config-file")) + + def __init__(self, *args, **kwargs): + """Initialize an Sendmail Configurator. """ + super(SendmailConfigurator, self).__init__(*args, **kwargs) + + # Files to save + self.save_notes = "" + self.reverter.recovery_routine() + self.changes = None + + # This is called in determine_authenticator and determine_installer + def prepare(self): + """Prepare the authenticator/installer. + + :raises .errors.NoInstallationError: If Sendmail ctl cannot be found + :raises .errors.MisconfigurationError: If Sendmail is misconfigured + """ + # Prevent two Sendmail plugins from modifying a config at once + try: + util.lock_dir_until_exit(self.conf("server-root")) + except (OSError, errors.LockError): + logger.debug("Encountered error:", exc_info=True) + raise errors.PluginError( + "Unable to lock %s", self.conf("server-root")) + self.changes = FileChanges(self.tls_config_file) + # TODO (sydli): swallow configuration files into memory + + # Entry point in main.py for installing cert + def deploy_cert(self, domain, cert_path, key_path, + chain_path=None, fullchain_path=None): + # pylint: disable=unused-argument + """Deploys certificate to specified virtual host. + """ + fullchain_dir, _ = os.path.split(fullchain_path) + regex_metapattern = r"define\(`conf{param}',\s*`(?P.+)'\)" + full_string = "define(`conf{param}', `{value}')dnl\n" + # TODO: Instead of setting CACERT_PATH to the live/ dir, + # We should really just ensure that Let's Encrypt's cert + # is trusted. + config_params = { + "CACERT_PATH": fullchain_dir, + "CACERT": fullchain_path, + "SERVER_CERT": cert_path, + "SERVER_KEY": key_path, + "CLIENT_CERT": cert_path, + "CLIENT_KEY": key_path, + } + for param in config_params: + yay_for_regex_parsing = regex_metapattern.format(param=param) + self.changes.replace_first(yay_for_regex_parsing, + config_params[param], + full_string.format(param=param, value=config_params[param])) + + ################################## + # enhancement methods (IInstaller) + ################################## + def supported_enhancements(self): # pylint: disable=no-self-use + """Returns currently supported enhancements.""" + return [] + + def enhance(self, domain, enhancement, options=None): + """Enhance configuration. + + :param str domain: domain to enhance + :param str enhancement: enhancement type defined in + :const:`~certbot.constants.ENHANCEMENTS` + :param options: options for the enhancement + See :const:`~certbot.constants.ENHANCEMENTS` + documentation for appropriate parameter. + + """ + raise NotImplemented("The Sendmail plugin does not support any enhancements") + + ###################################### + # Sendmail management (IInstaller) + ###################################### + def restart(self): + """Restarts sendmail. Not implemented. """ + pass + + def config_test(self): # pylint: disable=no-self-use + """Check the configuration of Sendmail for errors. + """ + pass + + def more_info(self): + """Human-readable string to help understand the module""" + return ( + "Configures Sendmail to install STARTTLS with a valid cert.{0}" + "Server root: {root}{0}".format( + os.linesep, root=self.parser.config_root)) + + ################################################### + # Wrapper functions for Reverter class (IInstaller) + ################################################### + def save(self, title=None, temporary=False): + """Saves all changes to the configuration files. + + :param str title: The title of the save. If a title is given, the + configuration will be saved as a new checkpoint and put in a + timestamped directory. + + :param bool temporary: Indicates whether the changes made will + be quickly reversed in the future (ie. challenges) + + :raises .errors.PluginError: If there was an error in + an attempt to save the configuration, or an error creating a + checkpoint + + """ + self.changes.flush(self.conf("diff_file")) diff --git a/certbot-sendmail/certbot_sendmail/constants.py b/certbot-sendmail/certbot_sendmail/constants.py new file mode 100644 index 000000000..d7d86068d --- /dev/null +++ b/certbot-sendmail/certbot_sendmail/constants.py @@ -0,0 +1,9 @@ +"""sendmail plugin constants.""" +import pkg_resources + +CLI_DEFAULTS = dict( + server_root="/etc/mail", + tls_config_file="starttls.m4", + diff_file=None, +) +"""CLI defaults.""" diff --git a/certbot-sendmail/certbot_sendmail/options-ssl-dovecot.conf b/certbot-sendmail/certbot_sendmail/options-ssl-dovecot.conf new file mode 100644 index 000000000..d53333615 --- /dev/null +++ b/certbot-sendmail/certbot_sendmail/options-ssl-dovecot.conf @@ -0,0 +1,11 @@ +# This file contains important security parameters. If you modify this file +# manually, Certbot will be unable to automatically provide future security +# updates. Instead, Certbot will print and log an error message with a path to +# the up-to-date file that you will need to refer to when manually updating +# this file. + +ssl_protocols = !SSLv3 !SSLv2 +ssl_prefer_server_ciphers = yes + +ssl_cipher_list = "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS" +ssl_dh_parameters_length = 2048 diff --git a/certbot-sendmail/certbot_sendmail/tests/__init__.py b/certbot-sendmail/certbot_sendmail/tests/__init__.py new file mode 100644 index 000000000..3aeba625f --- /dev/null +++ b/certbot-sendmail/certbot_sendmail/tests/__init__.py @@ -0,0 +1 @@ +"""Certbot Sendmail Tests""" diff --git a/certbot-sendmail/certbot_sendmail/tests/configurator_test.py b/certbot-sendmail/certbot_sendmail/tests/configurator_test.py new file mode 100644 index 000000000..ddc5c928e --- /dev/null +++ b/certbot-sendmail/certbot_sendmail/tests/configurator_test.py @@ -0,0 +1,80 @@ +"""Test for certbot_sendmail.configurator.""" +import unittest +import mock +import os +import subprocess +import shutil +import tempfile + +from certbot import errors +from certbot.tests import util as certbot_test_util + +from certbot.plugins import common +from certbot_sendmail import configurator + +class SendmailConfiguratorTest(unittest.TestCase): + """Test a semi complex vhost configuration.""" + def setUp(self): + super(SendmailConfiguratorTest, self).setUp() + + def test_prepare(self): + config, _ = get_configurator() + config.prepare() + + @certbot_test_util.patch_get_utility() + def test_deploy(self, mock_getutility): + mock_utility = mock_getutility() + mock_utility.notification = mock.MagicMock(return_value=True) + config, _ = get_configurator() + config.prepare() + config.deploy_cert("example.com", "path/cert123.pem", "path/ultrasecret123.pem", + "path/one_chain.pem", "path/full_chain.pem") + config.save() + + @certbot_test_util.patch_get_utility() + def test_configure_empty(self, mock_getutility): + mock_utility = mock_getutility() + mock_utility.notification = mock.MagicMock(return_value=True) + config, tempdir = get_configurator(tls_filename="nonexistant.m4", output_diff="output.diff") + config.prepare() + config.deploy_cert("example.com", "path/cert123.pem", "path/ultrasecret123.pem", + "path/one_chain.pem", "path/full_chain.pem") + config.save() + with open(os.path.join(tempdir, "output.diff")) as f: + actual = f.readlines() + expected = [ + "--- \n", "+++ \n", "@@ -0,0 +1,6 @@\n", + "+define(`confSERVER_CERT', `path/cert123.pem')dnl\n", + "+define(`confCACERT_PATH', `path')dnl\n", + "+define(`confSERVER_KEY', `path/ultrasecret123.pem')dnl\n", + "+define(`confCACERT', `path/full_chain.pem')dnl\n", + "+define(`confCLIENT_CERT', `path/cert123.pem')dnl\n", + "+define(`confCLIENT_KEY', `path/ultrasecret123.pem')dnl\n", + ] + self.assertEqual(tuple(expected), tuple(actual)) + +def get_configurator(tls_filename="starttls.m4", write_tls_data=None, output_diff=None): + tempdir, _, _ = common.dir_setup( + "etc_mail", "certbot_sendmail.tests") + backups = os.path.join(tempdir, "backups") + config_path = os.path.join(tempdir, "etc_mail") + if write_tls_data: + tls_path = os.path.join(config_path, tls_filename) + with open(tls_path, 'w') as f: + f.write(write_tls_data) + diff_file = None + if output_diff: + diff_file = os.path.join(tempdir, output_diff) + return configurator.SendmailConfigurator( + config=mock.MagicMock( + backup_dir=backups, + temp_checkpoint_dir=os.path.join(backups, "temp_checkpoints"), + in_progress_dir=os.path.join(backups, "IN_PROGRESS"), + sendmail_server_root=config_path, + sendmail_tls_config_file=tls_filename, + sendmail_diff_file=diff_file, + ), name="sendmail"), tempdir + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/certbot-sendmail/certbot_sendmail/tests/testdata/etc_mail/starttls.m4 b/certbot-sendmail/certbot_sendmail/tests/testdata/etc_mail/starttls.m4 new file mode 100755 index 000000000..5fb3a107b --- /dev/null +++ b/certbot-sendmail/certbot_sendmail/tests/testdata/etc_mail/starttls.m4 @@ -0,0 +1,55 @@ +divert(-1)dnl +#################################################################### +#################################################################### +divert(0)dnl +VERSIONID(`$Id: starttls.m4,v 8.15.2-10 2018-01-13 23:43:05 cowboy Exp $') +dnl # +dnl #--------------------------------------------------------------------- +dnl # Bring in Autoconf results +dnl #--------------------------------------------------------------------- +ifdef(`sm_version', `dnl', +`include(`/usr/share/sendmail/cf/debian/autoconf.m4')dnl') +dnl # +dnl # Check to see if inclusion is valid (version >= 8.11.0, tls enabled) +ifelse(eval(sm_version_math >= 527104), `1', `dnl +ifelse(sm_enable_tls, `yes', `dnl +dnl # +dnl # To support shared keyfiles, we need them to be group readable +dnl # +define(`confDONT_BLAME_SENDMAIL',dnl + defn(`confDONT_BLAME_SENDMAIL')`,GroupReadableKeyFile')dnl +dnl # +dnl # ...Do not touch anything above this line... +dnl # +dnl # Set a more reasonable timeout on negotiation +dnl # +define(`confTO_STARTTLS', `2m')dnl # <= EDIT +dnl # +dnl # CA directory - CA certs should be herein +define(`confCACERT_PATH', `/etc/ssl/certs')dnl # <= EDIT +dnl # +dnl # CA file (may be the same as client/server certificate) +define(`confCACERT', `/etc/mail/tls/sendmail-server.crt')dnl # <= EDIT +dnl # +dnl # Certificate Revocation List +define(`confCRL', `')dnl # <= EDIT +dnl # CRL not found... do not issue warnings on it! +undefine(`confCRL')dnl +dnl # +dnl # Server certificate/key (can be in the same file, and shared w/client) +dnl # NOTE: The key must *NOT* be encrypted !!! +define(`confSERVER_CERT', `/etc/mail/tls/sendmail-server.crt')dnl # <= EDIT +define(`confSERVER_KEY', `/etc/mail/tls/sendmail-common.key')dnl # <= EDIT +dnl # +dnl # Client certificate/key (can be in the same file, and shared w/server) +dnl # NOTE: The key must *NOT* be encrypted !!! +define(`confCLIENT_CERT', `/etc/mail/tls/sendmail-client.crt')dnl # <= EDIT +define(`confCLIENT_KEY', `/etc/mail/tls/sendmail-common.key')dnl # <= EDIT +dnl # +dnl # DH parameters +define(`confDH_PARAMETERS', `/etc/mail/tls/sendmail-common.prm')dnl # <= EDIT +dnl # +dnl # Optional settings +define(`confTLS_SRV_OPTIONS', `V')dnl # <= EDIT +dnl # +')')dnl diff --git a/certbot-sendmail/setup.cfg b/certbot-sendmail/setup.cfg new file mode 100644 index 000000000..2a9acf13d --- /dev/null +++ b/certbot-sendmail/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal = 1 diff --git a/certbot-sendmail/setup.py b/certbot-sendmail/setup.py new file mode 100644 index 000000000..071c6d3cb --- /dev/null +++ b/certbot-sendmail/setup.py @@ -0,0 +1,70 @@ +import sys + +from setuptools import setup +from setuptools import find_packages + + +version = '0.22.0.dev0' + +# Please update tox.ini when modifying dependency version requirements +install_requires = [ + 'acme=={0}'.format(version), + 'certbot=={0}'.format(version), + 'mock', + 'PyOpenSSL', + 'pyparsing>=1.5.5', # Python3 support; perhaps unnecessary? + # For pkg_resources. >=1.0 so pip resolves it to a version cryptography + # will tolerate; see #2599: + 'setuptools>=1.0', + 'zope.interface', +] + +docs_extras = [ + 'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags + 'sphinx_rtd_theme', +] + +setup( + name='certbot-sendmail', + version=version, + description="Sendmail plugin for Certbot", + url='https://github.com/letsencrypt/letsencrypt', + author="Certbot Project", + author_email='client-dev@letsencrypt.org', + license='Apache License 2.0', + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Environment :: Plugins', + 'Intended Audience :: System Administrators', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: POSIX :: Linux', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Security', + 'Topic :: System :: Installation/Setup', + 'Topic :: System :: Networking', + 'Topic :: System :: Systems Administration', + 'Topic :: Utilities', + ], + + packages=find_packages(), + include_package_data=True, + install_requires=install_requires, + extras_require={ + 'docs': docs_extras, + }, + entry_points={ + 'certbot.plugins': [ + 'sendmail = certbot_sendmail.configurator:SendmailConfigurator', + ], + }, + test_suite='certbot_sendmail', +) diff --git a/certbot/plugins/disco.py b/certbot/plugins/disco.py index 7be320efc..3f120c481 100644 --- a/certbot/plugins/disco.py +++ b/certbot/plugins/disco.py @@ -41,6 +41,7 @@ class PluginEntryPoint(object): "certbot-dns-sakuracloud", "certbot-nginx", "certbot-postfix", + "certbot-sendmail", ] """Distributions for which prefix will be omitted.""" diff --git a/tools/venv.sh b/tools/venv.sh index 5692f9ebf..5e36ff431 100755 --- a/tools/venv.sh +++ b/tools/venv.sh @@ -30,5 +30,6 @@ fi -e certbot-dns-sakuracloud \ -e certbot-nginx \ -e certbot-postfix \ + -e certbot-sendmail \ -e letshelp-certbot \ -e certbot-compatibility-test diff --git a/tools/venv3.sh b/tools/venv3.sh index 07512f370..502ef02d0 100755 --- a/tools/venv3.sh +++ b/tools/venv3.sh @@ -29,5 +29,6 @@ fi -e certbot-dns-sakuracloud \ -e certbot-nginx \ -e certbot-postfix \ + -e certbot-sendmail \ -e letshelp-certbot \ -e certbot-compatibility-test