Compare commits

...

312 Commits

Author SHA1 Message Date
sydneyli
a421be7b67 bump version number 2018-06-15 15:12:36 -07:00
sydneyli
d9ae16a649 fix more comments 2018-06-14 17:35:51 -07:00
sydneyli
88ae0949cc lint 2018-06-11 13:52:02 -07:00
sydneyli
e4f5e8f455 some cleanup 2018-06-11 13:32:13 -07:00
sydneyli
605eeba234 fix(postfix): tls_only and server_only params behave nicely together 2018-06-11 11:13:47 -07:00
sydneyli
e1e7eaa512 fix(postfix docs): add .rst files and fix build 2018-06-11 10:40:06 -07:00
sydneyli
97f9ab2804 Merge branch 'master' into postfix 2018-06-11 10:35:23 -07:00
sydneyli
8af5564043 import mypy types 2018-06-11 09:59:06 -07:00
sydneyli
7d73788a3a test(postfix): mypy 2018-06-11 09:28:55 -07:00
sydneyli
591125dcb5 test(postfix): coverage to 100 2018-06-10 14:40:03 -07:00
sydneyli
64feda281a fix(postfix): brad's comments 2018-06-04 08:22:28 -07:00
sydneyli
e6bd6287de mypy fix 2018-05-09 16:06:40 -07:00
sydneyli
ed9e6a51d8 Revert dockerfile change 2018-05-09 15:38:10 -07:00
sydneyli
febbb4e97c testfix for python 3.4 2018-05-09 15:01:39 -07:00
sydneyli
7900612f14 Remove print statement 2018-05-09 14:05:06 -07:00
sydneyli
e852f57a7e cleanup leftover files 2018-05-04 08:10:49 -07:00
sydneyli
d0bf706d26 some cleanup and testfixing 2018-05-04 07:31:09 -07:00
sydneyli
875f9c9604 99% test coverage 2018-05-04 00:30:59 -07:00
sydneyli
2e11236670 sphinx quickstart 2018-05-02 16:01:15 -07:00
sydneyli
558ec4d452 Remove STARTTLS policy enhancement from this branch. 2018-05-02 15:38:15 -07:00
sydneyli
81a472b29a More various fixes. Everything minus testing done 2018-05-02 15:24:09 -07:00
sydneyli
0e0db9ea44 Mock out postconf calls from tests and test coverage for master overrides 2018-04-27 16:01:45 -07:00
sydneyli
5fa405214b Python 3 fixes and Postconf tester extends TempDir test class 2018-04-27 14:46:32 -07:00
sydneyli
d744602279 Address comments in installer 2018-04-27 14:25:22 -07:00
sydneyli
f52f345d3c Address small comments on postconf and util 2018-04-27 12:32:40 -07:00
sydneyli
3a4b93889e Address comments on README and setup.py 2018-04-27 12:13:53 -07:00
sydneyli
52b053b297 Merge branch 'master' into postfix 2018-04-12 07:45:47 -07:00
sydneyli
d784888eef Moar fixes against policy API. 2018-04-12 01:08:38 -07:00
sydneyli
7230f346da testing(postfix): Added more varieties of certificates to test against. 2018-03-30 18:56:24 -07:00
sydneyli
850ccbddd0 starttls-everywhere => starttls-policy 2018-03-28 07:53:19 -07:00
sydneyli
5c5c34dc11 Changes against new policy API 2018-03-28 01:30:11 -07:00
sydneyli
f00949d269 fixing tests and lint 2018-03-27 08:39:50 -07:00
sydneyli
c521c3577f Moving testing infra from starttls repo to certbot-postfix 2018-03-25 23:54:27 -07:00
sydneyli
7e51aa1aaa Added a README, and small documentation fixes throughout 2018-03-21 02:53:38 -07:00
sydneyli
70d7b0f2ca Merge pull request #5769 from sydneyli/postfix
Postfix: enhancement update
2018-03-20 11:07:00 -07:00
Sydney Li
1388e161cd Policy is now an enhancement and reverting works 2018-03-13 11:09:21 -07:00
Sydney Li
4eb9bd7d4a smaller fixes 2018-03-12 17:10:09 -07:00
sydneyli
c1df4cef6b Merge pull request #5576 from sydneyli/postfix
Postfix plugin update.
Now installs and enforces per-domain TLS policies against STARTTLS policy directory.
2018-03-05 18:02:30 -08:00
Sydney Li
114b7226b6 Documentation fix 2018-03-05 17:58:24 -08:00
Sydney Li
a8a07c9b3a Cleanup and test fixes 2018-03-05 16:20:07 -08:00
Sydney Li
ad13ca0159 Update client to use new policy API 2018-03-05 16:18:54 -08:00
Sydney Li
00964a0ae3 Print warning when setting configuration parameter that is overridden by master. 2018-03-05 14:13:05 -08:00
Sydney Li
671b6da2ee Cleaning up TLS policy code. 2018-02-15 17:17:56 -08:00
Sydney Li
f125cc7b89 Fixing up postfix plugin
- Finishing refactor of postconf/postfix command-line utilities
 - Plugin uses starttls_policy plugin to specify per-domain policies
2018-02-15 11:26:11 -08:00
Sydney Li
b042cb933d Merge branch 'master' into postfix 2018-02-15 11:11:09 -08:00
Brad Warren
757ade8277 Fix split strip typo 2017-11-02 17:09:59 -07:00
Brad Warren
f91c20d8f8 Use _get_output instead of _call 2017-11-02 17:06:23 -07:00
Brad Warren
fa2c126d0e Add ReadOnlyMainMap 2017-11-02 17:03:03 -07:00
Brad Warren
df776a87e7 Add PostfixUtilBase 2017-11-02 16:05:18 -07:00
Brad Warren
c810bd06d3 Add and test verify_exe_exists 2017-10-27 17:29:51 -07:00
Brad Warren
d18dd1fc18 test check_all_output 2017-10-27 17:08:39 -07:00
Brad Warren
503bffeee8 add check_all_output 2017-10-27 17:02:11 -07:00
Brad Warren
a79e87a76c refactor postconf usage 2017-10-20 12:18:32 -07:00
Brad Warren
270f66d33c make dunder method an under method 2017-10-17 16:18:12 -07:00
Brad Warren
4e19c19dfc Merge branch 'master' into postfix 2017-10-17 16:11:37 -07:00
Brad Warren
b3a4928173 Test smtpd_tls_security_level conditional 2017-10-10 11:56:09 -07:00
Brad Warren
e0ad7c0406 Use tls_security_level instead of use_tls.
smtpd_tls_security_level should be used instead according to Postfix documentation.
2017-10-10 11:48:17 -07:00
Brad Warren
8cf6953fba Decrease minimum version to Postfix 2.6.
This is the minimum version that allows us to set ciphers to be used with
opportunistic TLS and is the oldest version packaged in any major distro.
2017-10-06 17:45:51 -07:00
Brad Warren
ed6e13ccd3 s/config_dir/config_utility 2017-09-06 17:13:38 -07:00
Brad Warren
c6a5a002ad bump required coverage to 100 2017-08-30 14:04:56 -07:00
Brad Warren
e0308ac04c bump cover to 100% 2017-08-30 14:04:15 -07:00
Brad Warren
26343821a7 protect get_config_var 2017-08-30 13:36:43 -07:00
Brad Warren
8f52b1626f Use postfix getters and setters 2017-08-30 13:23:41 -07:00
Brad Warren
361fa62f00 Add getters and setters 2017-08-30 13:18:41 -07:00
Brad Warren
f2eb6326cf Move mock and call to InstallerTest 2017-08-30 13:12:56 -07:00
Brad Warren
3e3952b545 Test deploy_cert and save and add MockPostfix. 2017-08-29 17:31:08 -07:00
Brad Warren
3d3932cca3 Always clear save_notes on save 2017-08-29 15:50:04 -07:00
Brad Warren
9a3ba574d7 document instance variables 2017-08-29 15:48:54 -07:00
Brad Warren
b31d37d9dc Don't duplicate changes to Postfix config 2017-08-29 15:45:28 -07:00
Brad Warren
e57da5768d Refactor get_config_var 2017-08-29 11:26:37 -07:00
Brad Warren
c4a303a113 test more_info() 2017-08-29 11:09:48 -07:00
Brad Warren
7fd4ed2a42 cover++ 2017-08-29 11:06:24 -07:00
Brad Warren
4e7440c88f Delint 2017-08-29 10:59:28 -07:00
Brad Warren
78c0e91732 pep8ify 2017-08-29 10:48:57 -07:00
Brad Warren
a827462177 Add certbot-postfix to tools 2017-08-29 10:44:52 -07:00
Brad Warren
66953435c9 Merge remote-tracking branch 'starttls-everywhere/certbotify' into postfix 2017-08-29 10:44:07 -07:00
Brad Warren
dde0bf0821 Remove non-plugin files 2017-08-29 10:42:31 -07:00
Brad Warren
142bc33545 Remove dead code 2017-08-29 10:38:53 -07:00
Brad Warren
a339de80f4 Add save() 2017-08-28 17:17:29 -07:00
Brad Warren
d663f7981a Add _write_config_changes 2017-08-28 15:24:51 -07:00
Brad Warren
72637b2cf6 Add util.check_call 2017-08-28 15:20:22 -07:00
Brad Warren
d0ea5958f9 Protect _set_config_var 2017-08-28 14:54:53 -07:00
Brad Warren
4805fb4b88 Add deploy_cert 2017-08-28 14:52:05 -07:00
Brad Warren
11b820c0e4 add set_config_var 2017-08-28 14:41:07 -07:00
Brad Warren
b21b66c0c0 Test restart command. 2017-08-25 14:29:50 -07:00
Brad Warren
218e15c9d4 Make restart() more robust. 2017-08-25 14:25:22 -07:00
Brad Warren
a6c08a2e25 Update prepare docstring. 2017-08-25 14:03:29 -07:00
Brad Warren
68dc678eed Call config_test in prepare 2017-08-25 14:02:57 -07:00
Brad Warren
5f3be9b1cf test config_test failure 2017-08-25 13:58:45 -07:00
Brad Warren
2ae187b1d6 Update config_test method 2017-08-25 13:55:21 -07:00
Brad Warren
b4b5c44750 Check postfix executable is found. 2017-08-25 13:38:07 -07:00
Brad Warren
a29a99fb6f Add --postfix-ctl flag. 2017-08-25 13:34:03 -07:00
Brad Warren
0f4c5c2305 Use common installer base 2017-08-25 11:41:30 -07:00
Brad Warren
60c6cc5f2a Write enhance() 2017-08-24 15:26:52 -07:00
Brad Warren
00e28592b6 Add supported_enhancements 2017-08-24 15:18:40 -07:00
Brad Warren
b92df1b71c Remove unused find_postfix_cf 2017-08-24 15:15:41 -07:00
Brad Warren
baf0d3343a Remove postfix version notes 2017-08-24 15:13:02 -07:00
Brad Warren
cc3896d5d4 Add test_lock_error 2017-08-24 15:12:11 -07:00
Brad Warren
b94e268f83 Remove unused certs_only_config 2017-08-24 15:05:39 -07:00
Brad Warren
b9177948d3 Remove _write_config 2017-08-24 15:05:24 -07:00
Brad Warren
3b2e9e49be Remove unneeded instance variables 2017-08-24 15:04:36 -07:00
Brad Warren
0efc02d6ee Lock the Postfix config dir 2017-08-24 15:03:11 -07:00
Brad Warren
967a1830e6 Rewrite get_all_names 2017-08-24 14:59:25 -07:00
Brad Warren
90ffe2aac0 Remove legacy get_all_certs_and_keys() method 2017-08-24 09:04:57 -07:00
Brad Warren
b342f40c2b remove old comments 2017-08-23 15:16:36 -07:00
Brad Warren
83e37acc8b group IPlugin methods 2017-08-23 15:16:07 -07:00
Brad Warren
d1f3a2deef move _get_version 2017-08-23 15:13:06 -07:00
Brad Warren
c9813a44d7 protect get_version() 2017-08-10 16:42:48 -07:00
Brad Warren
b98f541b91 clean up prepare() 2017-08-10 16:41:59 -07:00
Brad Warren
50a1f6340f Add _check_version. 2017-08-10 16:38:27 -07:00
Brad Warren
8c4ff5cb63 Use context manager to read conf file. 2017-08-10 16:36:08 -07:00
Brad Warren
290f5b8ce7 add test_set_config_dir 2017-08-10 16:35:21 -07:00
Brad Warren
48c5731a6b Write out temp config instead of mocking. 2017-08-10 16:29:08 -07:00
Brad Warren
749f758adb use a temporary directory 2017-08-10 16:25:30 -07:00
Brad Warren
2e8a8dfed5 add _set_config_dir 2017-08-10 16:17:46 -07:00
Brad Warren
25d1f6ec75 Test all branches of test_get_config_var 2017-08-10 16:11:09 -07:00
Brad Warren
4c4b63437f Test building of get_config_var command 2017-08-10 15:28:17 -07:00
Brad Warren
02c7eca6da Rename test classes and methods. 2017-08-10 15:08:41 -07:00
Brad Warren
b72dfc0c08 Add get_config_var 2017-08-10 15:07:32 -07:00
Brad Warren
7334fc3066 Rename postfix_dir to config_dir 2017-08-10 14:29:56 -07:00
Brad Warren
4e5740615c Use util.check_output in Postfix installer 2017-08-10 14:25:08 -07:00
Brad Warren
5a1d031f07 Rename util to certbot_util 2017-08-10 14:19:44 -07:00
Brad Warren
4715b2b12c Further document check_output 2017-08-10 14:18:27 -07:00
Brad Warren
5beaae3b65 Add check_output function and tests. 2017-08-10 14:17:13 -07:00
Brad Warren
dfd1cceb9b Test prepare() failure due to missing postconf 2017-08-10 12:26:47 -07:00
Brad Warren
192f0f60da test add_parser_arguments 2017-08-10 12:19:59 -07:00
Brad Warren
b395b72d1b Don't hardcode postconf path. 2017-08-10 11:51:01 -07:00
Brad Warren
a2dbf2fe4c Fix spacing 2017-08-10 11:20:21 -07:00
Brad Warren
86fe5ad362 Move calls to postconf to prepare(). 2017-08-10 11:16:46 -07:00
Brad Warren
89ae874f89 (temporarily) remove ca-certificates logic 2017-08-10 11:03:47 -07:00
Brad Warren
2a217189a6 (temporarily) remove policy_file 2017-08-10 10:58:27 -07:00
Brad Warren
481fb8413b Fix Postfix Installer __init__() 2017-08-10 08:50:08 -07:00
Brad Warren
d97a15861b Add --postfix-config-dir argument 2017-08-09 16:06:06 -07:00
Brad Warren
a15fe57225 remove policy config param 2017-08-09 15:57:19 -07:00
Brad Warren
6c5a8423b8 Remove unused logger from tests 2017-08-04 10:28:52 -07:00
Brad Warren
a66500ea38 Remove unused version argument. 2017-08-04 10:25:09 -07:00
Brad Warren
49cdfcec06 add six dependency 2017-08-04 10:20:36 -07:00
Brad Warren
b50a71ff4e Remove fopen argument in favor of mock.
This simplifies the actual production code and is a more standard approach in
Python.
2017-08-04 10:19:46 -07:00
Brad Warren
b37be61807 Import installer module directly in tests. 2017-08-04 10:12:54 -07:00
Brad Warren
4a3fd19c93 Move parse_line to the end of installer.py 2017-08-04 10:00:04 -07:00
Brad Warren
66ba0b5276 Remove invalid permissions exception.
Once things like locks are added, this error shouldn't be possible as it will
have occurred earlier.
2017-08-04 09:57:44 -07:00
Brad Warren
61c2209110 Use Certbot error types in the Postfix Installer 2017-08-04 09:56:39 -07:00
Brad Warren
694746409f s/ExistingConfigError/MisconfigurationError 2017-08-04 09:42:26 -07:00
Brad Warren
1c258c0a2c Add basic docstrings to Installer 2017-08-04 09:31:10 -07:00
Brad Warren
6c4b3c08a7 Clean up installer imports 2017-08-04 09:30:11 -07:00
Brad Warren
c2a8ce59ae Remove code to run the installer as on its own. 2017-08-04 09:28:22 -07:00
Brad Warren
5bf4ad1f52 Rename PostfixConfigGenerator to simply Installer 2017-08-04 09:25:12 -07:00
Brad Warren
ae08dc6bea Fix Postfix installer tests 2017-08-04 09:24:04 -07:00
Brad Warren
f89051cc2a Completely implement the Certbot plugin interfaces 2017-08-04 09:18:51 -07:00
Brad Warren
74b22a596e Ignore egg-info dirs 2017-08-04 09:03:30 -07:00
Brad Warren
e2d95b3719 Create packaging around PostfixConfigGenerator. 2017-08-04 09:02:56 -07:00
Aaron Zauner
dca274085d Merge pull request #38 from ekohl/patch-1
Correct markdown link syntax
2017-05-13 07:59:15 +02:00
Ewoud Kohl van Wijngaarden
619e273ae5 Correct markdown link syntax 2017-05-10 15:44:55 +02:00
Peter Eckersley
baa563f359 Merge pull request #30 from EFForg/azet/readme-fixup
README fixup
2016-05-12 19:12:50 -07:00
Aaron Zauner
64f2ddfa80 Merge pull request #31 from EFForg/dmwilcox/start-le-api
Dmwilcox/start le api
2016-05-03 13:45:42 +07:00
Daniel Wilcox
a5f23b5314 Configure logger to be a touch louder... than silent 2016-04-28 17:10:21 -07:00
Daniel Wilcox
af38c30c9c Fix path to postfix config variable. 2016-04-28 17:02:29 -07:00
Daniel Wilcox
887871833d Fix typo in changing quotes. 2016-04-28 16:44:25 -07:00
Daniel Wilcox
5d07b70269 Change over to using logging module from print statements. 2016-04-28 16:40:06 -07:00
Daniel Wilcox
c43602c908 Add simple config_test implementation. 2016-04-28 16:30:08 -07:00
Daniel Wilcox
4d24eb83a8 Move version fetching into get_version and implement more_info method. 2016-04-28 16:11:37 -07:00
Daniel Wilcox
7edceec8ac Add test case and fix to properly handle configs with no smtpd_tls_* vars. 2016-04-28 15:27:11 -07:00
Daniel Wilcox
c6baa82ee4 Implement basic get_all_certs_keys, tests pass. 2016-04-28 15:14:06 -07:00
Daniel Wilcox
e75bafa439 Add basic test for get_all_certs_keys IInstaller interface method. 2016-04-28 12:27:49 -07:00
Daniel Wilcox
cc83e9ba52 Wrap some lines, new style exceptions, return check for restart. 2016-04-28 12:26:56 -07:00
Peter Eckersley
5d355044c4 Merge pull request #28 from EFForg/azet/disable-v2-v3-fixup
set _all_ client&server options to exclude v2 and v3 #24
2016-04-25 23:18:05 +10:00
Aaron Zauner
1f95ac9640 README fixup pt. 1 2016-04-20 12:43:51 +07:00
Daniel Wilcox
5928fae89e Merge branch 'master' of github.com:dmwilcox/starttls-everywhere into hackathon 2016-03-29 15:04:23 -07:00
Daniel Wilcox
0bf2537a55 Add initial gitignore. 2016-03-29 14:32:53 -07:00
Daniel Wilcox
fd1cef3fa0 Implement get_all_names. 2016-03-29 14:31:27 -07:00
Daniel Wilcox
fee9c86233 Add failing test for get_all_names. 2016-03-29 14:20:33 -07:00
Daniel Wilcox
5cc317408c Move attributes into init and allow for injecting file contents for testing. 2016-03-29 14:19:13 -07:00
Aaron Zauner
2900d5122c set _all_ client&server options to exclude v2 and v3 #24 2016-03-29 19:31:00 +02:00
Peter Eckersley
c7a8d1cb7a Merge pull request #27 from EFForg/azet/disable-v2-v3
disable SSLv2,3 client-side too #24
2016-03-29 10:12:48 -07:00
Aaron Zauner
0ba508ee2d disable SSLv2,3 client-side too #24 2016-03-29 19:02:00 +02:00
Aaron Zauner
1d37e94e17 disable SSLv3 and v3 by default #24 2016-03-29 18:43:08 +02:00
Peter Eckersley
843e156a51 Merge pull request #26 from EFForg/log-summary
Improve log summary code
2016-03-29 09:23:43 -07:00
Peter Eckersley
ce88098ba6 Merge remote-tracking branch 'origin/master' 2016-03-29 09:21:15 -07:00
Aaron Zauner
1cde7f9b54 added doc. on postfix version dependent features 2016-03-29 16:22:19 +02:00
Aaron Zauner
e42a222c5d preliminary Postfix version check 2016-03-29 15:44:51 +02:00
Peter Eckersley
87022782fb Catch stray missing line 2016-03-09 10:59:10 -08:00
Peter Eckersley
a58c652443 ConfigParser is gone! 2016-03-09 10:54:15 -08:00
Peter Eckersley
0ce1684ba6 Changes to MTAConfigGenerator needed to be moved by hand 2016-03-09 10:50:15 -08:00
Peter Eckersley
af1e94be5a Merge branch 'log-summary'
Including adding dmwilcox's mandatory policy argument to jsha's argparse code
2016-03-09 10:05:43 -08:00
Peter Eckersley
e88eac65da [documentation] Add links to LEPC interface sources 2016-03-02 10:47:59 -08:00
Peter Eckersley
874c59012a Update README 2016-03-02 10:47:52 -08:00
Peter Eckersley
b2977ad6a9 Documentation details 2016-03-01 19:06:02 -08:00
Peter Eckersley
a435036a1e Document MVP objectives 2016-03-01 19:03:47 -08:00
Peter Eckersley
3a8e1d7a70 Further status update 2016-03-01 16:40:03 -08:00
Peter Eckersley
1210c04f14 tweak README 2016-03-01 16:38:55 -08:00
Peter Eckersley
6e2b6a0817 Update README 2016-03-01 16:37:56 -08:00
Peter Eckersley
9a122626b9 Merge pull request #23 from EFForg/delimiters
Fix postfix TLS policy map delimeters
2016-03-01 16:23:10 -08:00
Peter Eckersley
d0629e62ea Merge remote-tracking branch 'origin/master' into delimiters 2016-02-24 19:08:37 -08:00
Peter Eckersley
b8ce13f96a Merge pull request #21 from EFForg/master
Restructured project directory, unified scripts, moved to subdirs
2016-02-24 19:06:06 -08:00
Peter Eckersley
6db2858825 Correct policy map delimitation 2016-02-24 18:19:47 -08:00
Aaron Zauner
4d14423a21 re-structured project folder..
* Removed `ConfigParser.py` (ACK by Daniel).
* Removed MTAConfigGenerator-stub and renamed to `PostfixConfigGenerator.py`
* Moved all text/csv processing scripts to `tools/`.
* Moved all configuration files into dedicated `examples/` directory.
* unified all shebangs to `#!/usr/bin/env python` (default system python).
* Moved domain CSV and text-files to `share/`.
2016-02-24 23:35:52 +01:00
Peter Eckersley
959e943de8 Merge remote-tracking branch 'github/master' 2016-02-18 18:58:45 -08:00
Peter Eckersley
9e42f6ed08 Clarify project status 2016-02-18 18:58:34 -08:00
dmwilcox
bd20b50879 Merge pull request #19 from EFForg/hackathon
We now install certs!
2016-02-17 12:34:23 -08:00
Peter Eckersley
28bb0eb6ac Obtain acceptable_mxs the right way 2016-02-17 12:20:26 -08:00
Peter Eckersley
074fef773b Make up a domain 2016-02-17 12:14:24 -08:00
Peter Eckersley
3aeb62cf7e bugfixes, cleanups 2016-02-17 12:13:28 -08:00
Peter Eckersley
8f5b8558d2 Actually deploy a cert?
- Also add missing selves to interface methods
2016-02-17 12:09:25 -08:00
Peter Eckersley
47a5b7e3ba Start implementing cert installation 2016-02-17 11:56:41 -08:00
Peter Eckersley
cdc8b94823 Merge remote-tracking branch 'dmwilcox/master' into hackathon 2016-02-17 11:38:06 -08:00
dmwilcox
fedf970284 Fix bad import to be import *as*... as it should be. 2016-02-17 11:37:28 -08:00
Peter Eckersley
965027ce52 Start metamorphising to use LE's IInstaller interface 2016-02-17 11:36:38 -08:00
dmwilcox
1cde095dc2 Merge pull request #18 from EFForg/hackathon
hackathon work
2016-02-17 11:22:18 -08:00
Peter Eckersley
39a01190d5 Use 4 space indents
For greater compatibility with LE codebases
2016-02-17 11:19:02 -08:00
Peter Eckersley
9539f21390 Merge pull request #16 from dmwilcox/master
New Config container -- drop-in replacement
2016-02-17 10:34:14 -08:00
dmwilcox
9abef4c0bd Log MX records that will not be configured. 2016-02-17 10:20:56 -08:00
dmwilcox
146fce3878 Add comment about magic hat trick with class properties. 2016-02-17 09:51:37 -08:00
dmwilcox
904dc11b03 Add docstrings for Config objects update/merge methods. 2016-02-17 09:45:37 -08:00
dmwilcox
7c6c3efb0f Confirmed Postfix log parsing is working again. 2016-01-21 01:46:30 -08:00
dmwilcox
c87b5d6a78 Hook the MTA config generation into the new config container. 2016-01-21 00:56:29 -08:00
pypoet
9a71b18b85 Fix updates and merges, add testing to make sure they stay fixed. 2015-10-23 18:26:26 -07:00
pypoet
6da5de6b19 Beginnings of generic config composibility in place. 2015-10-16 00:57:42 -04:00
pypoet
147f58bdbc Rounds out missing features and is now on par with ConfigParser.py.
Still missing logging, composibility and a couple of attributes.
2015-10-14 02:49:34 -04:00
pypoet
fe17c873c0 Initial re-vamp of the Config object to centralize validation and
lay the basis for making compositions of configs and overrides.
Lots of TODOs, be warned.
2015-10-13 17:09:03 -04:00
Jacob Hoffman-Andrews
613a8f5e88 Improve cronjob operation to only process diffs. 2014-10-20 16:15:13 -04:00
Jacob Hoffman-Andrews
f04e8259a9 Protocols separated by colons, not commas. 2014-10-20 09:29:22 -04:00
Jacob Hoffman-Andrews
828c00b758 Find deferred lines in log summary.
Also add a cron mode that only emits output if there's something wrong.
2014-10-17 15:28:49 -04:00
Jacob Hoffman-Andrews
1d47acddfd Remove extraneous print of config. 2014-10-17 15:28:44 -04:00
Jacob Hoffman-Andrews
726afb8b95 Install CAfile on config generation. 2014-10-17 15:28:32 -04:00
Jacob Hoffman-Andrews
a1d016d031 Add motivating examples to README 2014-10-13 15:57:59 -04:00
jsha
91f6be6b3b Merge pull request #6 from jsha/move-collection
Move collection
2014-10-08 16:36:19 -04:00
Peter Eckersley
97cce82e5a Merge pull request #7 from jsha/min-tls-version
Treat min-tls-version as a minimum.
2014-10-08 11:32:54 -07:00
Jacob Hoffman-Andrews
622fc72dc1 Treat min-tls-version as a minimum.
Fixes #5.
2014-09-10 17:36:46 -04:00
Jacob Hoffman-Andrews
42c63cb6dd More informative message for RDNS fail. 2014-09-08 16:25:40 -04:00
Jacob Hoffman-Andrews
31e320d0a7 Collect certs in a subdir. 2014-08-13 15:59:50 -04:00
Jacob Hoffman-Andrews
7f9dadd681 Merge branch 'master' of github.com:EFForg/starttls-everywhere 2014-08-13 10:50:18 -04:00
Jacob Hoffman-Andrews
ad40618897 Respond to pde's comments 2014-08-13 10:49:50 -04:00
jsha
e0edc1b7ec Add link to mailing list. 2014-08-12 12:18:32 -04:00
Peter Eckersley
78a55c3823 Question about postfix logparsing output 2014-08-08 13:16:40 -07:00
Peter Eckersley
9cafcf1caf Comment regexp 2014-08-08 13:15:15 -07:00
Peter Eckersley
8c6d28ce95 Comment with some sample postfix log lines
(both those we support already, and those we may want to in the future...)
2014-08-08 13:01:33 -07:00
Peter Eckersley
30ba7e9305 Deduplicate stray comment line 2014-08-08 13:01:04 -07:00
Peter Eckersley
a6700e3172 Merge branch 'misc' of ssh://github.com/EFForg/starttls-everywhere into misc 2014-08-08 11:58:26 -07:00
Peter Eckersley
0bd8134e5f Comments
(and code review in comment form)
2014-08-08 11:57:01 -07:00
Peter Eckersley
ff5810d78f Don't accept files on the command line that don't do anything 2014-08-08 11:36:04 -07:00
Peter Eckersley
21ff3acf93 Set/list comprehensions are a bit more readable than lambdas 2014-08-08 11:26:59 -07:00
Jacob Hoffman-Andrews
cebc6f9a20 ProcessGoogleSTARTTLSDomains -> latest CSV format.
Also output in a more useful format, require >= 99% encrypted output in the CSV,
hande .{...} domains, and manually add gmail.com.
2014-08-08 13:28:01 -04:00
Jacob Hoffman-Andrews
749c4e39e0 Update meta-config with latest domains. 2014-08-07 17:34:40 -04:00
Jacob Hoffman-Andrews
6a1aa8e6b6 Update to latest config format. 2014-08-07 17:34:21 -04:00
Jacob Hoffman-Andrews
bdd4d01dc7 Update and sort golden-domains.txt, commit google-starttls-domains.csv 2014-08-07 16:57:51 -04:00
Jacob Hoffman-Andrews
6a40e1964b Notice if there are no "Trusted" entries. 2014-08-07 15:05:10 -04:00
Jacob Hoffman-Andrews
82ef8b185a Merge branch 'master' of github.com:EFForg/starttls-everywhere into misc
Conflicts:
	MTAConfigGenerator.py
	starttls-everywhere.json
2014-08-06 18:26:31 -04:00
Jacob Hoffman-Andrews
dd4f9d35ae Improve checker and starttls-everywhere.json.
Now we alphabetize keys on output for more useful diffs.
2014-08-06 18:23:13 -04:00
Jacob Hoffman-Andrews
127d49e837 Manually add a couple known-good domains.
These were skipped because in the Google data they are represented as,
e.g. 'gmail.{..}'.
2014-08-06 18:22:32 -04:00
Jacob Hoffman-Andrews
a85fad98c0 Restore default config 2014-08-06 18:22:08 -04:00
Jacob Hoffman-Andrews
1c3c69aaad Allow parameters to MTAConfigGenerator. 2014-08-06 17:08:49 -04:00
Jacob Hoffman-Andrews
3de8c2a651 Do a better job finding address domain from mx doman. 2014-08-06 17:08:30 -04:00
Peter Eckersley
803c39e585 Merge remote-tracking branch 'github/master'
Conflicts:
	ConfigParser.py
2014-06-19 09:11:15 -07:00
Jacob Hoffman-Andrews
51980e212f First pass at logs analysis 2014-06-18 17:50:41 -04:00
Peter Eckersley
3df343495e Various fixups 2014-06-18 10:58:53 -07:00
Peter Eckersley
45aeb5b003 Merge remote-tracking branch 'github/master'
Conflicts:
	ConfigParser.py
	starttls-everywhere.json
2014-06-18 10:56:48 -07:00
Jacob Hoffman-Andrews
67ee3b0488 Config format change - don't use * as it's misleading. 2014-06-18 12:32:17 -04:00
Peter Eckersley
2eba47a716 Verify more of the policy language 2014-06-16 14:23:51 -07:00
Jacob Hoffman-Andrews
51f90ffafb Write policies based on address domain, not stripped mx-domain 2014-06-16 18:26:56 +00:00
Peter Eckersley
8d6b6c358a Merge branch 'master' of ssh://github.com/jsha/starttls-everywhere
Conflicts:
	MTAConfigGenerator.py
2014-06-16 02:47:15 -07:00
Peter Eckersley
9da4f93ae5 Changes for live demo 2014-06-16 02:46:46 -07:00
Jacob Hoffman-Andrews
3cf61a54b7 Add alternatives section 2014-06-13 13:57:19 -04:00
Jacob Hoffman-Andrews
43d457aa77 Typo cleanup in MTAConfigGenerator 2014-06-12 13:18:20 -04:00
Jacob Hoffman-Andrews
499f6c2fad Add comment to ProcessgoogleSTARTTLSDomains.py 2014-06-12 11:53:02 -04:00
Jacob Hoffman-Andrews
9cd71642fb Fix italicization boundaries 2014-06-12 11:50:39 -04:00
Jacob Hoffman-Andrews
7a7329fa19 Merge remote-tracking branch 'pde/master' 2014-06-12 11:49:09 -04:00
Jacob Hoffman-Andrews
9ce047980a Add .gitignore 2014-06-12 11:40:17 -04:00
Peter Eckersley
02abaf57bd Remove stray conf entry from README 2014-06-12 05:24:51 -07:00
Peter Eckersley
3d7b53daf1 Tune verbosity; reload postfix conf if we can 2014-06-12 05:24:05 -07:00
Peter Eckersley
e99abfacfd Include the demo example with the real stuff, temporarily 2014-06-12 05:22:30 -07:00
Peter Eckersley
a2ee328bc0 Paramaterise "/etc/postfix" 2014-06-11 10:32:52 -07:00
Peter Eckersley
34cba3accf Now successfully parsing the larger policy set 2014-06-11 09:51:56 -07:00
Peter Eckersley
3712a45399 Further (and different, and better) standardisation 2014-06-11 09:48:43 -07:00
Peter Eckersley
182e9b29e4 Trying to standardize JSON terms 2014-06-11 09:42:17 -07:00
Peter Eckersley
2540f1f1e8 Writing to the domain-wise policy file actually works now. 2014-06-11 09:31:41 -07:00
Peter Eckersley
eea1b0d8c5 Switch naming conventions so that modules are importable :)
It turns out that python won't import modules with hyphens in their names.
It seems that CamelCase is most consistent with our Class naming.  However
feel free to do something different instead :)
2014-06-11 09:18:56 -07:00
Peter Eckersley
6e1bcfdb2a WIP implementing domain-wise TLS policies 2014-06-11 09:17:50 -07:00
Peter Eckersley
d7e4d93190 Merge branch 'master' of ssh://github.com/jsha/starttls-everywhere 2014-06-11 09:13:01 -07:00
Jacob Hoffman-Andrews
0c4e332811 Set up test CA and valid signed cert by that CA.
Also require valid cert for host 'valid'.
2014-06-11 11:45:28 -04:00
Peter Eckersley
46ce09d36d MTA config wrangling seems to work 2014-06-11 05:01:46 -07:00
Peter Eckersley
269f15f9ee Merge branch 'master' of ssh://github.com/jsha/starttls-everywhere 2014-06-11 00:38:40 -07:00
Jacob Hoffman-Andrews
21e841fd13 Move synced folders into a common one.
Also, create certificates.
2014-06-10 15:08:52 -04:00
Peter Eckersley
a03db04ff4 WIP implementing deletion of existing cf lines 2014-06-10 08:08:40 -07:00
Peter Eckersley
d0bcc13059 Break ground on an postfix config wrangling engine 2014-06-10 08:08:17 -07:00
Jacob Hoffman-Andrews
17425e7337 Merge branch 'master' of github.com:jsha/starttls-everywhere
Conflicts:
	README.md
2014-06-09 14:56:34 -07:00
jsha
7bdb63376c Merge pull request #2 from pde/master
Initial checkin of config parser, some changes to the config format.
2014-06-09 14:53:23 -07:00
Jacob Hoffman-Andrews
bdbc46fc84 Add candidate starttls-everywhere config json 2014-06-09 13:10:43 -07:00
Jacob Hoffman-Andrews
0d43d2988a Update check-starttls.py to generate starttls everywhere config. 2014-06-09 13:08:01 -07:00
Jacob Hoffman-Andrews
79924108c7 Reorder JSON file to emphasize MX policies over address-domain -> MX domain mapping. 2014-06-09 10:12:09 -07:00
Peter Eckersley
c033905b16 Now validating most of the config json 2014-06-08 06:22:32 -07:00
Peter Eckersley
839c523048 Fix typos 2014-06-08 06:22:22 -07:00
Peter Eckersley
7c81f23a07 Merge branch 'master' of ssh://github.com/jsha/starttls-everywhere 2014-06-07 06:50:21 -07:00
Jacob Hoffman-Andrews
e534a43d1a Make sender actually attempt TLS on outbound connections. 2014-06-06 16:07:38 -07:00
Peter Eckersley
fcd1a98201 Break ground on a config parser 2014-06-06 15:54:33 -07:00
Peter Eckersley
dc606eac7d Some tweaks to the config format 2014-06-06 15:54:22 -07:00
Jacob Hoffman-Andrews
372c96d9fd Update Postfix configuration and mail-send-loop 2014-06-06 14:06:08 -07:00
Jacob Hoffman-Andrews
5a9f90dc30 Merge branch 'master' of github.com:jsha/starttls-everywhere 2014-06-06 14:05:03 -07:00
Jacob Hoffman-Andrews
ce0a6a1814 Add example config.json 2014-06-06 14:04:38 -07:00
jsha
fa5acdf674 Simplify SPKI hash usage 2014-06-06 13:44:03 -07:00
jsha
31db3b7034 Merge pull request #1 from pde/master
Add specific postfix configs
2014-06-06 12:59:41 -07:00
Peter Eckersley
30938260d4 Split postfix configs across the VMs, and start making them do things 2014-06-05 19:45:21 -07:00
Peter Eckersley
7bd06a4d35 Work in progress 2014-06-05 17:04:28 -07:00
Peter Eckersley
ace9d2383d Start work on forcing TLS to valid 2014-06-05 16:51:38 -07:00
Jacob Hoffman-Andrews
ed0c024209 Improved provisioning and certificate checking. 2014-06-05 16:01:07 -07:00
Jacob Hoffman-Andrews
714cb17dcb Clarify example usage of pinsets by including a CA 2014-06-05 14:26:45 -07:00
Jacob Hoffman-Andrews
6fb51d5422 Example shouldn't include hashes from Chrome source. 2014-06-05 14:11:08 -07:00
jsha
4b5b9f164f nexthop-domains -> address-domains 2014-06-05 11:51:19 -07:00
jsha
3d9d5607bd Formatting issue #2 in design doc 2014-06-05 11:34:12 -07:00
jsha
aa417eec15 Formatting issue in design doc 2014-06-05 11:10:07 -07:00
jsha
844ec79f01 Add design doc 2014-06-05 11:05:08 -07:00
Jacob Hoffman-Andrews
f0b9ef2716 Add a Vagrantfile and the list of golden domains. 2014-06-05 10:43:01 -07:00
Jacob Hoffman-Andrews
8857302347 check-starttls and process-google-starttls-domains.py 2014-06-04 14:41:18 -07:00
28 changed files with 1997 additions and 1 deletions

190
certbot-postfix/LICENSE.txt Normal file
View File

@@ -0,0 +1,190 @@
Copyright 2017 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.
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

View File

@@ -0,0 +1,4 @@
include LICENSE.txt
include README.rst
recursive-include certbot_postfix/testdata *
recursive-include certbot_postfix/docs *

View File

@@ -0,0 +1,10 @@
==========================
Postfix plugin for Certbot
==========================
To install your certs with this plugin, run:
``certbot install --installer postfix --cert-path <path to cert> --key-path <path to key> -d <MX hostname>``
And there you go! If you'd like to obtain these certificates via certbot, there's more documentation on how to do this `here <https://certbot.eff.org/docs/using.html#getting-certificates-and-choosing-plugins>`_.

View File

@@ -0,0 +1,3 @@
"""Certbot Postfix plugin."""
from certbot_postfix.installer import Installer

View File

@@ -0,0 +1,63 @@
"""Postfix plugin constants."""
# pylint: disable=unused-import, no-name-in-module
from acme.magic_typing import Dict, Tuple, Union
# pylint: enable=unused-import, no-name-in-module
MINIMUM_VERSION = (2, 11,)
# If the value of a default VAR is a tuple, then the values which
# come LATER in the tuple are more strict/more secure.
# Certbot will default to the first value in the tuple, but will
# not override "more secure" settings.
ACCEPTABLE_SERVER_SECURITY_LEVELS = ("may", "encrypt")
ACCEPTABLE_CLIENT_SECURITY_LEVELS = ("may", "encrypt",
"dane", "dane-only",
"fingerprint",
"verify", "secure")
ACCEPTABLE_CIPHER_LEVELS = ("medium", "high")
# Exporting certain ciphers to prevent logjam: https://weakdh.org/sysadmin.html
EXCLUDE_CIPHERS = ("aNULL, eNULL, EXPORT, DES, RC4, MD5, PSK, aECDH, "
"EDH-DSS-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA, KRB5-DES, CBC3-SHA",)
TLS_VERSIONS = ("SSLv2", "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2")
# Should NOT use SSLv2/3.
ACCEPTABLE_TLS_VERSIONS = ("TLSv1", "TLSv1.1", "TLSv1.2")
# Variables associated with enabling opportunistic TLS.
TLS_SERVER_VARS = {
"smtpd_tls_security_level": ACCEPTABLE_SERVER_SECURITY_LEVELS,
} # type:Dict[str, Tuple[str, ...]]
TLS_CLIENT_VARS = {
"smtp_tls_security_level": ACCEPTABLE_CLIENT_SECURITY_LEVELS,
} # type:Dict[str, Tuple[str, ...]]
# Default variables for a secure MTA server [receiver].
DEFAULT_SERVER_VARS = {
"smtpd_tls_auth_only": ("yes",),
"smtpd_tls_mandatory_protocols": ("!SSLv2, !SSLv3",),
"smtpd_tls_protocols": ("!SSLv2, !SSLv3",),
"smtpd_tls_ciphers": ACCEPTABLE_CIPHER_LEVELS,
"smtpd_tls_mandatory_ciphers": ACCEPTABLE_CIPHER_LEVELS,
"smtpd_tls_exclude_ciphers": EXCLUDE_CIPHERS,
"smtpd_tls_eecdh_grade": ("strong",),
} # type:Dict[str, Tuple[str, ...]]
# Default variables for a secure MTA client [sender].
DEFAULT_CLIENT_VARS = {
"smtp_tls_ciphers": ACCEPTABLE_CIPHER_LEVELS,
"smtp_tls_exclude_ciphers": EXCLUDE_CIPHERS,
"smtp_tls_mandatory_ciphers": ACCEPTABLE_CIPHER_LEVELS,
} # type:Dict[str, Tuple[str, ...]]
CLI_DEFAULTS = dict(
config_dir="/etc/postfix",
ctl="postfix",
config_utility="postconf",
tls_only=False,
ignore_master_overrides=False,
server_only=False,
)
"""CLI defaults."""

View File

@@ -0,0 +1,288 @@
"""certbot installer plugin for postfix."""
import logging
import os
import zope.interface
import zope.component
import six
from certbot import errors
from certbot import interfaces
from certbot import util as certbot_util
from certbot.plugins import common as plugins_common
from certbot_postfix import constants
from certbot_postfix import postconf
from certbot_postfix import util
# pylint: disable=unused-import, no-name-in-module
from acme.magic_typing import Callable, Dict, List
# pylint: enable=unused-import, no-name-in-module
logger = logging.getLogger(__name__)
@zope.interface.implementer(interfaces.IInstaller)
@zope.interface.provider(interfaces.IPluginFactory)
class Installer(plugins_common.Installer):
"""Certbot installer plugin for Postfix.
:ivar str config_dir: Postfix configuration directory to modify
:ivar list save_notes: documentation for proposed changes. This is
cleared and stored in Certbot checkpoints when save() is called
:ivar postconf: Wrapper for Postfix configuration command-line tool.
:type postconf: :class: `certbot_postfix.postconf.ConfigMain`
:ivar postfix: Wrapper for Postfix command-line tool.
:type postfix: :class: `certbot_postfix.util.PostfixUtil`
"""
description = "Configure TLS with the Postfix MTA"
@classmethod
def add_parser_arguments(cls, add):
add("ctl", default=constants.CLI_DEFAULTS["ctl"],
help="Path to the 'postfix' control program.")
# This directory points to Postfix's configuration directory.
add("config-dir", default=constants.CLI_DEFAULTS["config_dir"],
help="Path to the directory containing the "
"Postfix main.cf file to modify instead of using the "
"default configuration paths.")
add("config-utility", default=constants.CLI_DEFAULTS["config_utility"],
help="Path to the 'postconf' executable.")
add("tls-only", action="store_true", default=constants.CLI_DEFAULTS["tls_only"],
help="Only set params to enable opportunistic TLS and install certificates.")
add("server-only", action="store_true", default=constants.CLI_DEFAULTS["server_only"],
help="Only set server params (prefixed with smtpd*)")
add("ignore-master-overrides", action="store_true",
default=constants.CLI_DEFAULTS["ignore_master_overrides"],
help="Ignore errors reporting overridden TLS parameters in master.cf.")
def __init__(self, *args, **kwargs):
super(Installer, self).__init__(*args, **kwargs)
# Wrapper around postconf commands
self.postfix = None
self.postconf = None
# Files to save
self.save_notes = [] # type: List[str]
self._enhance_func = {} # type: Dict[str, Callable[[str, str], None]]
# Since we only need to enable TLS once for all domains,
# keep track of whether this enhancement was already called.
self._tls_enabled = False
def prepare(self):
"""Prepare the installer.
:raises errors.PluginError: when an unexpected error occurs
:raises errors.MisconfigurationError: when the config is invalid
:raises errors.NoInstallationError: when can't find installation
:raises errors.NotSupportedError: when version is not supported
"""
# Verify postfix and postconf are installed
for param in ("ctl", "config_utility",):
util.verify_exe_exists(self.conf(param),
"Cannot find executable '{0}'. You can provide the "
"path to this command with --{1}".format(
self.conf(param),
self.option_name(param)))
# Set up CLI tools
self.postfix = util.PostfixUtil(self.conf('config-dir'))
self.postconf = postconf.ConfigMain(self.conf('config-utility'),
self.conf('ignore-master-overrides'),
self.conf('config-dir'))
# Ensure current configuration is valid.
self.config_test()
# Check Postfix version
self._check_version()
self._lock_config_dir()
self.install_ssl_dhparams()
def config_test(self):
"""Test to see that the current Postfix configuration is valid.
:raises errors.MisconfigurationError: If the configuration is invalid.
"""
self.postfix.test()
def _check_version(self):
"""Verifies that the installed Postfix version is supported.
:raises errors.NotSupportedError: if the version is unsupported
"""
if self._get_version() < constants.MINIMUM_VERSION:
version_string = '.'.join([str(n) for n in constants.MINIMUM_VERSION])
raise errors.NotSupportedError('Postfix version must be at least %s' % version_string)
def _lock_config_dir(self):
"""Stop two Postfix plugins from modifying the config at once.
:raises .PluginError: if unable to acquire the lock
"""
try:
certbot_util.lock_dir_until_exit(self.conf('config-dir'))
except (OSError, errors.LockError):
logger.debug("Encountered error:", exc_info=True)
raise errors.PluginError(
"Unable to lock %s" % self.conf('config-dir'))
def more_info(self):
"""Human-readable string to help the user. Describes steps taken and any relevant
info to help the user decide which plugin to use.
:rtype: str
"""
return (
"Configures Postfix to try to authenticate mail servers, use "
"installed certificates and disable weak ciphers and protocols.{0}"
"Server root: {root}{0}"
"Version: {version}".format(
os.linesep,
root=self.conf('config-dir'),
version='.'.join([str(i) for i in self._get_version()]))
)
def _get_version(self):
"""Return the version of Postfix, as a tuple. (e.g. '2.11.3' is (2, 11, 3))
:returns: version
:rtype: tuple
:raises errors.PluginError: Unable to find Postfix version.
"""
mail_version = self.postconf.get_default("mail_version")
return tuple(int(i) for i in mail_version.split('.'))
def get_all_names(self):
"""Returns all names that may be authenticated.
:rtype: `set` of `str`
"""
return certbot_util.get_filtered_names(self.postconf.get(var)
for var in ('mydomain', 'myhostname', 'myorigin',))
def _set_vars(self, var_dict):
"""Sets all parameters in var_dict to config file. If current value is already set
as more secure (acceptable), then don't set/overwrite it.
"""
for param, acceptable in six.iteritems(var_dict):
if not util.is_acceptable_value(param, self.postconf.get(param), acceptable):
self.postconf.set(param, acceptable[0], acceptable)
def _confirm_changes(self):
"""Confirming outstanding updates for configuration parameters.
:raises errors.PluginError: when user rejects the configuration changes.
"""
updates = self.postconf.get_changes()
output_string = "Postfix TLS configuration parameters to update in main.cf:\n"
for name, value in six.iteritems(updates):
output_string += "{0} = {1}\n".format(name, value)
output_string += "Is this okay?\n"
if not zope.component.getUtility(interfaces.IDisplay).yesno(output_string,
force_interactive=True, default=True):
raise errors.PluginError(
"Manually rejected configuration changes.\n"
"Try using --tls-only or --server-only to change a particular"
"subset of configuration parameters.")
def deploy_cert(self, domain, cert_path,
key_path, chain_path, fullchain_path):
"""Configure the Postfix SMTP server to use the given TLS cert.
:param str domain: domain to deploy certificate file
:param str cert_path: absolute path to the certificate file
:param str key_path: absolute path to the private key file
:param str chain_path: absolute path to the certificate chain file
:param str fullchain_path: absolute path to the certificate fullchain
file (cert plus chain)
:raises .PluginError: when cert cannot be deployed
"""
# pylint: disable=unused-argument
if self._tls_enabled:
return
self._tls_enabled = True
self.save_notes.append("Configuring TLS for {0}".format(domain))
self.postconf.set("smtpd_tls_cert_file", cert_path)
self.postconf.set("smtpd_tls_key_file", key_path)
self._set_vars(constants.TLS_SERVER_VARS)
if not self.conf('server_only'):
self._set_vars(constants.TLS_CLIENT_VARS)
if not self.conf('tls_only'):
self._set_vars(constants.DEFAULT_SERVER_VARS)
if not self.conf('server_only'):
self._set_vars(constants.DEFAULT_CLIENT_VARS)
# Despite the name, this option also supports 2048-bit DH params.
# http://www.postfix.org/FORWARD_SECRECY_README.html#server_fs
self.postconf.set("smtpd_tls_dh1024_param_file", self.ssl_dhparams)
self._confirm_changes()
def enhance(self, domain, enhancement, options=None):
"""Raises an exception since this installer doesn't support any enhancements.
"""
# pylint: disable=unused-argument
raise errors.PluginError(
"Unsupported enhancement: {0}".format(enhancement))
def supported_enhancements(self):
"""Returns a list of supported enhancements.
:rtype: list
"""
return []
def save(self, title=None, temporary=False):
"""Creates backups and writes changes to 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. `title` has no effect if temporary is true.
:param bool temporary: Indicates whether the changes made will
be quickly reversed in the future (challenges)
:raises errors.PluginError: when save is unsuccessful
"""
save_files = set((os.path.join(self.conf('config-dir'), "main.cf"),))
self.add_to_checkpoint(save_files,
"\n".join(self.save_notes), temporary)
self.postconf.flush()
del self.save_notes[:]
if title and not temporary:
self.finalize_checkpoint(title)
def recovery_routine(self):
super(Installer, self).recovery_routine()
self.postconf = postconf.ConfigMain(self.conf('config-utility'),
self.conf('ignore-master-overrides'),
self.conf('config-dir'))
def rollback_checkpoints(self, rollback=1):
"""Rollback saved checkpoints.
:param int rollback: Number of checkpoints to revert
:raises .errors.PluginError: If there is a problem with the input or
the function is unable to correctly revert the configuration
"""
super(Installer, self).rollback_checkpoints(rollback)
self.postconf = postconf.ConfigMain(self.conf('config-utility'),
self.conf('ignore-master-overrides'),
self.conf('config-dir'))
def restart(self):
"""Restart or refresh the server content.
:raises .PluginError: when server cannot be restarted
"""
self.postfix.restart()

View File

@@ -0,0 +1,152 @@
"""Classes that wrap the postconf command line utility.
"""
import six
from certbot import errors
from certbot_postfix import util
# pylint: disable=unused-import, no-name-in-module
from acme.magic_typing import Dict, List, Tuple
# pylint: enable=unused-import, no-name-in-module
class ConfigMain(util.PostfixUtilBase):
"""A parser for Postfix's main.cf file."""
def __init__(self, executable, ignore_master_overrides=False, config_dir=None):
super(ConfigMain, self).__init__(executable, config_dir)
# Whether to ignore overrides from master.
self._ignore_master_overrides = ignore_master_overrides
# List of all current Postfix parameters, from `postconf` command.
self._db = {} # type: Dict[str, str]
# List of current master.cf overrides from Postfix config. Dictionary
# of parameter name => list of tuples (service name, paramter value)
# Note: We should never modify master without explicit permission.
self._master_db = {} # type: Dict[str, List[Tuple[str, str]]]
# List of all changes requested to the Postfix parameters as they are now
# in _db. These changes are flushed to `postconf` on `flush`.
self._updated = {} # type: Dict[str, str]
self._read_from_conf()
def _read_from_conf(self):
"""Reads initial parameter state from `main.cf` into this object.
"""
out = self._get_output()
for name, value in _parse_main_output(out):
self._db[name] = value
out = self._get_output_master()
for name, value in _parse_main_output(out):
service, param_name = name.rsplit("/", 1)
if param_name not in self._master_db:
self._master_db[param_name] = []
self._master_db[param_name].append((service, value))
def _get_output_master(self):
"""Retrieves output for `master.cf` parameters."""
return self._get_output('-P')
def get_default(self, name):
"""Retrieves default value of parameter `name` from postfix parameters.
:param str name: The name of the parameter to fetch.
:returns: The default value of parameter `name`.
:rtype: str
"""
out = self._get_output(['-d', name])
_, value = next(_parse_main_output(out), (None, None))
return value
def get(self, name):
"""Retrieves working value of parameter `name` from postfix parameters.
:param str name: The name of the parameter to fetch.
:returns: The value of parameter `name`.
:rtype: str
"""
if name in self._updated:
return self._updated[name]
return self._db[name]
def get_master_overrides(self, name):
"""Retrieves list of overrides for parameter `name` in postfix's Master config
file.
:returns: List of tuples (service, value), meaning that parameter `name`
is overridden as `value` for `service`.
:rtype: `list` of `tuple` of `str`
"""
if name in self._master_db:
return self._master_db[name]
return None
def set(self, name, value, acceptable_overrides=None):
"""Sets parameter `name` to `value`. If `name` is overridden by a particular service in
`master.cf`, reports any of these parameter conflicts as long as
`ignore_master_overrides` was not set.
.. note:: that this function does not flush these parameter values to main.cf;
To do that, use `flush`.
:param str name: The name of the parameter to set.
:param str value: The value of the parameter.
:param tuple acceptable_overrides: If the master configuration file overrides `value`
with a value in acceptable_overrides.
"""
if name not in self._db:
raise KeyError("Parameter name %s is not a valid Postfix parameter name.", name)
# Check to see if this parameter is overridden by master.
overrides = self.get_master_overrides(name)
if not self._ignore_master_overrides and overrides is not None:
util.report_master_overrides(name, overrides, acceptable_overrides)
if value != self._db[name]:
# _db contains the "original" state of parameters. We only care about
# writes if they cause a delta from the original state.
self._updated[name] = value
elif name in self._updated:
# If this write reverts a previously updated parameter back to the
# original DB's state, we don't have to keep track of it in _updated.
del self._updated[name]
def flush(self):
"""Flushes all parameter changes made using `self.set`, to `main.cf`
:raises error.PluginError: When flush to main.cf fails for some reason.
"""
if len(self._updated) == 0:
return
args = ['-e']
for name, value in six.iteritems(self._updated):
args.append('{0}={1}'.format(name, value))
try:
self._get_output(args)
except IOError as e:
raise errors.PluginError("Unable to save to Postfix config: %v", e)
for name, value in six.iteritems(self._updated):
self._db[name] = value
self._updated = {}
def get_changes(self):
""" Return queued changes to main.cf.
:rtype: dict[str, str]
"""
return self._updated
def _parse_main_output(output):
"""Parses the raw output from Postconf about main.cf.
Expects the output to look like:
.. code-block:: none
name1 = value1
name2 = value2
:param str output: data postconf wrote to stdout about main.cf
:returns: generator providing key-value pairs from main.cf
:rtype: Iterator[tuple(str, str)]
"""
for line in output.splitlines():
name, _, value = line.partition(" =")
yield name, value.strip()

View File

@@ -0,0 +1 @@
""" Certbot Postfix Tests """

View File

@@ -0,0 +1,314 @@
"""Tests for certbot_postfix.installer."""
from contextlib import contextmanager
import copy
import functools
import os
import pkg_resources
import six
import unittest
import mock
from certbot import errors
from certbot.tests import util as certbot_test_util
# pylint: disable=unused-import, no-name-in-module
from acme.magic_typing import Dict, Tuple, Union
# pylint: enable=unused-import, no-name-in-module
DEFAULT_MAIN_CF = {
"smtpd_tls_cert_file": "",
"smtpd_tls_key_file": "",
"smtpd_tls_dh1024_param_file": "",
"smtpd_tls_security_level": "none",
"smtpd_tls_auth_only": "",
"smtpd_tls_mandatory_protocols": "",
"smtpd_tls_protocols": "",
"smtpd_tls_ciphers": "",
"smtpd_tls_exclude_ciphers": "",
"smtpd_tls_mandatory_ciphers": "",
"smtpd_tls_eecdh_grade": "medium",
"smtp_tls_security_level": "",
"smtp_tls_ciphers": "",
"smtp_tls_exclude_ciphers": "",
"smtp_tls_mandatory_ciphers": "",
"mail_version": "3.2.3"
}
def _main_cf_with(obj):
main_cf = copy.copy(DEFAULT_MAIN_CF)
main_cf.update(obj)
return main_cf
class InstallerTest(certbot_test_util.ConfigTestCase):
# pylint: disable=too-many-public-methods
def setUp(self):
super(InstallerTest, self).setUp()
_config_file = pkg_resources.resource_filename("certbot_postfix.tests",
os.path.join("testdata", "config.json"))
self.config.postfix_ctl = "postfix"
self.config.postfix_config_dir = self.tempdir
self.config.postfix_config_utility = "postconf"
self.config.postfix_tls_only = False
self.config.postfix_server_only = False
self.config.config_dir = self.tempdir
@mock.patch("certbot_postfix.installer.util.is_acceptable_value")
def test_set_vars(self, mock_is_acceptable_value):
mock_is_acceptable_value.return_value = True
with create_installer(self.config) as installer:
installer.prepare()
mock_is_acceptable_value.return_value = False
@mock.patch("certbot_postfix.installer.util.is_acceptable_value")
def test_acceptable_value(self, mock_is_acceptable_value):
mock_is_acceptable_value.return_value = True
with create_installer(self.config) as installer:
installer.prepare()
mock_is_acceptable_value.return_value = False
@certbot_test_util.patch_get_utility()
def test_confirm_changes_no_raises_error(self, mock_util):
mock_util().yesno.return_value = False
with create_installer(self.config) as installer:
installer.prepare()
self.assertRaises(errors.PluginError, installer.deploy_cert,
"example.com", "cert_path", "key_path",
"chain_path", "fullchain_path")
@certbot_test_util.patch_get_utility()
def test_save(self, mock_util):
mock_util().yesno.return_value = True
with create_installer(self.config) as installer:
installer.prepare()
installer.postconf.flush = mock.Mock()
installer.reverter = mock.Mock()
installer.deploy_cert("example.com", "cert_path", "key_path",
"chain_path", "fullchain_path")
installer.save()
self.assertEqual(installer.save_notes, [])
self.assertEqual(installer.postconf.flush.call_count, 1)
self.assertEqual(installer.reverter.add_to_checkpoint.call_count, 1)
@certbot_test_util.patch_get_utility()
def test_save_with_title(self, mock_util):
mock_util().yesno.return_value = True
with create_installer(self.config) as installer:
installer.prepare()
installer.postconf.flush = mock.Mock()
installer.reverter = mock.Mock()
installer.deploy_cert("example.com", "cert_path", "key_path",
"chain_path", "fullchain_path")
installer.save(title="new_file!")
self.assertEqual(installer.reverter.finalize_checkpoint.call_count, 1)
@certbot_test_util.patch_get_utility()
def test_rollback_checkpoints_resets_postconf(self, mock_util):
mock_util().yesno.return_value = True
with create_installer(self.config) as installer:
installer.prepare()
installer.deploy_cert("example.com", "cert_path", "key_path",
"chain_path", "fullchain_path")
installer.rollback_checkpoints()
self.assertEqual(installer.postconf.get_changes(), {})
@certbot_test_util.patch_get_utility()
def test_recovery_routine_resets_postconf(self, mock_util):
mock_util().yesno.return_value = True
with create_installer(self.config) as installer:
installer.prepare()
installer.deploy_cert("example.com", "cert_path", "key_path",
"chain_path", "fullchain_path")
installer.recovery_routine()
self.assertEqual(installer.postconf.get_changes(), {})
def test_restart(self):
with create_installer(self.config) as installer:
installer.prepare()
installer.restart()
self.assertEqual(installer.postfix.restart.call_count, 1)
def test_add_parser_arguments(self):
options = set(("ctl", "config-dir", "config-utility",
"tls-only", "server-only", "ignore-master-overrides"))
mock_add = mock.MagicMock()
from certbot_postfix import installer
installer.Installer.add_parser_arguments(mock_add)
for call in mock_add.call_args_list:
self.assertTrue(call[0][0] in options)
def test_no_postconf_prepare(self):
with create_installer(self.config) as installer:
installer_path = "certbot_postfix.installer"
exe_exists_path = installer_path + ".certbot_util.exe_exists"
path_surgery_path = "certbot_postfix.util.plugins_util.path_surgery"
with mock.patch(path_surgery_path, return_value=False):
with mock.patch(exe_exists_path, return_value=False):
self.assertRaises(errors.NoInstallationError,
installer.prepare)
def test_old_version(self):
with create_installer(self.config, main_cf=_main_cf_with({"mail_version": "0.0.1"}))\
as installer:
self.assertRaises(errors.NotSupportedError, installer.prepare)
def test_lock_error(self):
with create_installer(self.config) as installer:
assert_raises = functools.partial(self.assertRaises,
errors.PluginError,
installer.prepare)
certbot_test_util.lock_and_call(assert_raises, self.tempdir)
@mock.patch('certbot.util.lock_dir_until_exit')
def test_dir_locked(self, lock_dir):
with create_installer(self.config) as installer:
lock_dir.side_effect = errors.LockError
self.assertRaises(errors.PluginError, installer.prepare)
def test_more_info(self):
with create_installer(self.config) as installer:
installer.prepare()
output = installer.more_info()
self.assertTrue("Postfix" in output)
self.assertTrue(self.tempdir in output)
self.assertTrue(DEFAULT_MAIN_CF["mail_version"] in output)
def test_get_all_names(self):
config = {"mydomain": "example.org",
"myhostname": "mail.example.org",
"myorigin": "example.org"}
with create_installer(self.config, main_cf=_main_cf_with(config)) as installer:
installer.prepare()
result = installer.get_all_names()
self.assertEqual(result, set(config.values()))
@certbot_test_util.patch_get_utility()
def test_deploy(self, mock_util):
mock_util().yesno.return_value = True
from certbot_postfix import constants
with create_installer(self.config) as installer:
installer.prepare()
# pylint: disable=protected-access
installer.deploy_cert("example.com", "cert_path", "key_path",
"chain_path", "fullchain_path")
changes = installer.postconf.get_changes()
expected = {} # type: Dict[str, Tuple[str, ...]]
expected.update(constants.TLS_SERVER_VARS)
expected.update(constants.DEFAULT_SERVER_VARS)
expected.update(constants.DEFAULT_CLIENT_VARS)
self.assertEqual(changes["smtpd_tls_key_file"], "key_path")
self.assertEqual(changes["smtpd_tls_cert_file"], "cert_path")
for name, value in six.iteritems(expected):
self.assertEqual(changes[name], value[0])
@certbot_test_util.patch_get_utility()
def test_tls_only(self, mock_util):
mock_util().yesno.return_value = True
with create_installer(self.config) as installer:
installer.prepare()
installer.conf = lambda x: x == "tls_only"
installer.postconf.set = mock.Mock()
installer.deploy_cert("example.com", "cert_path", "key_path",
"chain_path", "fullchain_path")
self.assertEqual(installer.postconf.set.call_count, 4)
@certbot_test_util.patch_get_utility()
def test_server_only(self, mock_util):
mock_util().yesno.return_value = True
with create_installer(self.config) as installer:
installer.prepare()
installer.conf = lambda x: x == "server_only"
installer.postconf.set = mock.Mock()
installer.deploy_cert("example.com", "cert_path", "key_path",
"chain_path", "fullchain_path")
self.assertEqual(installer.postconf.set.call_count, 11)
@certbot_test_util.patch_get_utility()
def test_tls_and_server_only(self, mock_util):
mock_util().yesno.return_value = True
with create_installer(self.config) as installer:
installer.prepare()
installer.conf = lambda x: True
installer.postconf.set = mock.Mock()
installer.deploy_cert("example.com", "cert_path", "key_path",
"chain_path", "fullchain_path")
self.assertEqual(installer.postconf.set.call_count, 3)
@certbot_test_util.patch_get_utility()
def test_deploy_twice(self, mock_util):
# Deploying twice on the same installer shouldn't do anything!
mock_util().yesno.return_value = True
with create_installer(self.config) as installer:
installer.prepare()
from certbot_postfix.postconf import ConfigMain
with mock.patch.object(ConfigMain, "set", wraps=installer.postconf.set) as fake_set:
installer.deploy_cert("example.com", "cert_path", "key_path",
"chain_path", "fullchain_path")
self.assertEqual(fake_set.call_count, 15)
fake_set.reset_mock()
installer.deploy_cert("example.com", "cert_path", "key_path",
"chain_path", "fullchain_path")
fake_set.assert_not_called()
@certbot_test_util.patch_get_utility()
def test_deploy_already_secure(self, mock_util):
# Should not overwrite "more-secure" parameters
mock_util().yesno.return_value = True
more_secure = {
"smtpd_tls_security_level": "encrypt",
"smtpd_tls_protocols": "!SSLv3, !SSLv2, !TLSv1",
"smtpd_tls_eecdh_grade": "strong"
}
with create_installer(self.config,\
main_cf=_main_cf_with(more_secure)) as installer:
installer.prepare()
installer.deploy_cert("example.com", "cert_path", "key_path",
"chain_path", "fullchain_path")
for param in more_secure.keys():
self.assertFalse(param in installer.postconf.get_changes())
def test_enhance(self):
with create_installer(self.config) as installer:
installer.prepare()
self.assertRaises(errors.PluginError,
installer.enhance,
"example.org", "redirect")
def test_supported_enhancements(self):
with create_installer(self.config) as installer:
installer.prepare()
self.assertEqual(installer.supported_enhancements(), [])
@contextmanager
def create_installer(config, main_cf=DEFAULT_MAIN_CF):
# pylint: disable=dangerous-default-value
"""Creates a Postfix installer with calls to `postconf` and `postfix` mocked out.
In particular, creates a ConfigMain object that does regular things, but seeds it
with values from `main_cf` and `master_cf` dicts.
"""
from certbot_postfix.postconf import ConfigMain
from certbot_postfix import installer
def _mock_init_postconf(postconf, executable, ignore_master_overrides=False, config_dir=None):
# pylint: disable=protected-access,unused-argument
postconf._ignore_master_overrides = ignore_master_overrides
postconf._db = main_cf
postconf._master_db = {}
postconf._updated = {}
# override get_default to get from main
postconf.get_default = lambda name: main_cf[name]
with mock.patch.object(ConfigMain, "__init__", _mock_init_postconf):
exe_exists_path = "certbot_postfix.installer.certbot_util.exe_exists"
with mock.patch(exe_exists_path, return_value=True):
with mock.patch("certbot_postfix.installer.util.PostfixUtil",
return_value=mock.Mock()):
yield installer.Installer(config, "postfix")
if __name__ == "__main__":
unittest.main() # pragma: no cover

View File

@@ -0,0 +1,107 @@
"""Tests for certbot_postfix.postconf."""
import mock
import unittest
from certbot import errors
class PostConfTest(unittest.TestCase):
"""Tests for certbot_postfix.util.PostConf."""
def setUp(self):
from certbot_postfix.postconf import ConfigMain
super(PostConfTest, self).setUp()
with mock.patch('certbot_postfix.util.PostfixUtilBase._get_output') as mock_call:
with mock.patch('certbot_postfix.postconf.ConfigMain._get_output_master') as \
mock_master_call:
with mock.patch('certbot_postfix.postconf.util.verify_exe_exists') as verify_exe:
verify_exe.return_value = True
mock_call.return_value = ('default_parameter = value\n'
'extra_param =\n'
'overridden_by_master = default\n')
mock_master_call.return_value = (
'service/type/overridden_by_master = master_value\n'
'service2/type/overridden_by_master = master_value2\n'
)
self.config = ConfigMain('postconf', False)
@mock.patch('certbot_postfix.util.PostfixUtilBase._get_output')
@mock.patch('certbot_postfix.postconf.util.verify_exe_exists')
def test_get_output_master(self, mock_verify_exe, mock_get_output):
from certbot_postfix.postconf import ConfigMain
mock_verify_exe.return_value = True
ConfigMain('postconf', lambda x, y, z: None)
mock_get_output.assert_called_with('-P')
@mock.patch('certbot_postfix.util.PostfixUtilBase._get_output')
def test_read_default(self, mock_get_output):
mock_get_output.return_value = 'param = default_value'
self.assertEqual(self.config.get_default('param'), 'default_value')
@mock.patch('certbot_postfix.util.PostfixUtilBase._call')
def test_set(self, mock_call):
self.config.set('extra_param', 'other_value')
self.assertEqual(self.config.get('extra_param'), 'other_value')
self.config.flush()
mock_call.assert_called_with(['-e', 'extra_param=other_value'])
def test_set_bad_param_name(self):
self.assertRaises(KeyError, self.config.set, 'nonexistent_param', 'some_value')
@mock.patch('certbot_postfix.util.PostfixUtilBase._call')
def test_write_revert(self, mock_call):
self.config.set('default_parameter', 'fake_news')
# revert config set
self.config.set('default_parameter', 'value')
self.config.flush()
mock_call.assert_not_called()
@mock.patch('certbot_postfix.util.PostfixUtilBase._call')
def test_write_default(self, mock_call):
self.config.set('default_parameter', 'value')
self.config.flush()
mock_call.assert_not_called()
def test_master_overrides(self):
self.assertEqual(self.config.get_master_overrides('overridden_by_master'),
[('service/type', 'master_value'),
('service2/type', 'master_value2')])
def test_set_check_override(self):
self.assertRaises(errors.PluginError, self.config.set,
'overridden_by_master', 'new_value')
def test_ignore_check_override(self):
# pylint: disable=protected-access
self.config._ignore_master_overrides = True
self.config.set('overridden_by_master', 'new_value')
def test_check_acceptable_overrides(self):
self.config.set('overridden_by_master', 'new_value',
('master_value', 'master_value2'))
@mock.patch('certbot_postfix.util.PostfixUtilBase._get_output')
def test_flush(self, mock_out):
self.config.set('default_parameter', 'new_value')
self.config.set('extra_param', 'another_value')
self.config.flush()
arguments = mock_out.call_args_list[-1][0][0]
self.assertEquals('-e', arguments[0])
self.assertTrue('default_parameter=new_value' in arguments)
self.assertTrue('extra_param=another_value' in arguments)
@mock.patch('certbot_postfix.util.PostfixUtilBase._get_output')
def test_flush_updates_object(self, mock_out):
self.config.set('default_parameter', 'new_value')
self.config.flush()
mock_out.reset_mock()
self.config.set('default_parameter', 'new_value')
mock_out.assert_not_called()
@mock.patch('certbot_postfix.util.PostfixUtilBase._get_output')
def test_flush_throws_error_on_fail(self, mock_out):
mock_out.side_effect = [IOError("oh no!")]
self.config.set('default_parameter', 'new_value')
self.assertRaises(errors.PluginError, self.config.flush)
if __name__ == '__main__': # pragma: no cover
unittest.main()

View File

@@ -0,0 +1,205 @@
"""Tests for certbot_postfix.util."""
import subprocess
import unittest
import mock
from certbot import errors
class PostfixUtilBaseTest(unittest.TestCase):
"""Tests for certbot_postfix.util.PostfixUtilBase."""
@classmethod
def _create_object(cls, *args, **kwargs):
from certbot_postfix.util import PostfixUtilBase
return PostfixUtilBase(*args, **kwargs)
@mock.patch('certbot_postfix.util.verify_exe_exists')
def test_no_exe(self, mock_verify):
expected_error = errors.NoInstallationError
mock_verify.side_effect = expected_error
self.assertRaises(expected_error, self._create_object, 'nonexistent')
def test_object_creation(self):
with mock.patch('certbot_postfix.util.verify_exe_exists'):
self._create_object('existent')
@mock.patch('certbot_postfix.util.check_all_output')
def test_call_extends_args(self, mock_output):
# pylint: disable=protected-access
with mock.patch('certbot_postfix.util.verify_exe_exists'):
mock_output.return_value = 'expected'
postfix = self._create_object('executable')
postfix._call(['many', 'extra', 'args'])
mock_output.assert_called_with(['executable', 'many', 'extra', 'args'])
postfix._call()
mock_output.assert_called_with(['executable'])
def test_create_with_config(self):
# pylint: disable=protected-access
with mock.patch('certbot_postfix.util.verify_exe_exists'):
postfix = self._create_object('exec', 'config_dir')
self.assertEqual(postfix._base_command, ['exec', '-c', 'config_dir'])
class PostfixUtilTest(unittest.TestCase):
def setUp(self):
# pylint: disable=protected-access
from certbot_postfix.util import PostfixUtil
with mock.patch('certbot_postfix.util.verify_exe_exists'):
self.postfix = PostfixUtil()
self.postfix._call = mock.Mock()
self.mock_call = self.postfix._call
def test_test(self):
self.postfix.test()
self.mock_call.assert_called_with(['check'])
def test_test_raises_error_when_check_fails(self):
self.mock_call.side_effect = [subprocess.CalledProcessError(1, "")]
self.assertRaises(errors.MisconfigurationError, self.postfix.test)
self.mock_call.assert_called_with(['check'])
def test_restart_while_running(self):
self.mock_call.side_effect = [subprocess.CalledProcessError(1, ""), None]
self.postfix.restart()
self.mock_call.assert_called_with(['start'])
def test_restart_while_not_running(self):
self.postfix.restart()
self.mock_call.assert_called_with(['reload'])
def test_restart_raises_error_when_reload_fails(self):
self.mock_call.side_effect = [None, subprocess.CalledProcessError(1, "")]
self.assertRaises(errors.PluginError, self.postfix.restart)
self.mock_call.assert_called_with(['reload'])
def test_restart_raises_error_when_start_fails(self):
self.mock_call.side_effect = [
subprocess.CalledProcessError(1, ""),
subprocess.CalledProcessError(1, "")]
self.assertRaises(errors.PluginError, self.postfix.restart)
self.mock_call.assert_called_with(['start'])
class CheckAllOutputTest(unittest.TestCase):
"""Tests for certbot_postfix.util.check_all_output."""
@classmethod
def _call(cls, *args, **kwargs):
from certbot_postfix.util import check_all_output
return check_all_output(*args, **kwargs)
@mock.patch('certbot_postfix.util.logger')
@mock.patch('certbot_postfix.util.subprocess.Popen')
def test_command_error(self, mock_popen, mock_logger):
command = 'foo'
retcode = 42
output = 'bar'
err = 'baz'
mock_popen().communicate.return_value = (output, err)
mock_popen().poll.return_value = 42
self.assertRaises(subprocess.CalledProcessError, self._call, command)
log_args = mock_logger.debug.call_args[0]
for value in (command, retcode, output, err,):
self.assertTrue(value in log_args)
@mock.patch('certbot_postfix.util.subprocess.Popen')
def test_success(self, mock_popen):
command = 'foo'
expected = ('bar', '')
mock_popen().communicate.return_value = expected
mock_popen().poll.return_value = 0
self.assertEqual(self._call(command), expected)
def test_stdout_error(self):
self.assertRaises(ValueError, self._call, stdout=None)
def test_stderr_error(self):
self.assertRaises(ValueError, self._call, stderr=None)
def test_universal_newlines_error(self):
self.assertRaises(ValueError, self._call, universal_newlines=False)
class VerifyExeExistsTest(unittest.TestCase):
"""Tests for certbot_postfix.util.verify_exe_exists."""
@classmethod
def _call(cls, *args, **kwargs):
from certbot_postfix.util import verify_exe_exists
return verify_exe_exists(*args, **kwargs)
@mock.patch('certbot_postfix.util.certbot_util.exe_exists')
@mock.patch('certbot_postfix.util.plugins_util.path_surgery')
def test_failure(self, mock_exe_exists, mock_path_surgery):
mock_exe_exists.return_value = mock_path_surgery.return_value = False
self.assertRaises(errors.NoInstallationError, self._call, 'foo')
@mock.patch('certbot_postfix.util.certbot_util.exe_exists')
def test_simple_success(self, mock_exe_exists):
mock_exe_exists.return_value = True
self._call('foo')
@mock.patch('certbot_postfix.util.certbot_util.exe_exists')
@mock.patch('certbot_postfix.util.plugins_util.path_surgery')
def test_successful_surgery(self, mock_exe_exists, mock_path_surgery):
mock_exe_exists.return_value = False
mock_path_surgery.return_value = True
self._call('foo')
class TestUtils(unittest.TestCase):
""" Testing random utility functions in util.py
"""
def test_report_master_overrides(self):
from certbot_postfix.util import report_master_overrides
self.assertRaises(errors.PluginError, report_master_overrides, 'name',
[('service/type', 'value')])
# Shouldn't raise error
report_master_overrides('name', [('service/type', 'value')],
acceptable_overrides=('value',))
def test_no_acceptable_value(self):
from certbot_postfix.util import is_acceptable_value
self.assertFalse(is_acceptable_value('name', 'value', None))
def test_is_acceptable_value(self):
from certbot_postfix.util import is_acceptable_value
self.assertTrue(is_acceptable_value('name', 'value', ('value',)))
self.assertFalse(is_acceptable_value('name', 'bad', ('value',)))
def test_is_acceptable_tuples(self):
from certbot_postfix.util import is_acceptable_value
self.assertTrue(is_acceptable_value('name', 'value', ('value', 'value1')))
self.assertFalse(is_acceptable_value('name', 'bad', ('value', 'value1')))
def test_is_acceptable_protocols(self):
from certbot_postfix.util import is_acceptable_value
# SSLv2 and SSLv3 are both not supported, unambiguously
self.assertFalse(is_acceptable_value('tls_mandatory_protocols_lol',
'SSLv2, SSLv3', None))
self.assertFalse(is_acceptable_value('tls_protocols_lol',
'SSLv2, SSLv3', None))
self.assertFalse(is_acceptable_value('tls_protocols_lol',
'!SSLv2, !TLSv1', None))
self.assertFalse(is_acceptable_value('tls_protocols_lol',
'!SSLv2, SSLv3, !SSLv3, ', None))
self.assertTrue(is_acceptable_value('tls_protocols_lol',
'!SSLv2, !SSLv3', None))
self.assertTrue(is_acceptable_value('tls_protocols_lol',
'!SSLv3, !TLSv1, !SSLv2', None))
# TLSv1.2 is supported unambiguously
self.assertFalse(is_acceptable_value('tls_protocols_lol',
'TLSv1, TLSv1.1,', None))
self.assertFalse(is_acceptable_value('tls_protocols_lol',
'TLSv1.2, !TLSv1.2,', None))
self.assertTrue(is_acceptable_value('tls_protocols_lol',
'TLSv1.2, ', None))
self.assertTrue(is_acceptable_value('tls_protocols_lol',
'TLSv1, TLSv1.1, TLSv1.2', None))
if __name__ == '__main__': # pragma: no cover
unittest.main()

View File

@@ -0,0 +1,292 @@
"""Utility functions for use in the Postfix installer."""
import logging
import re
import subprocess
from certbot import errors
from certbot import util as certbot_util
from certbot.plugins import util as plugins_util
from certbot_postfix import constants
logger = logging.getLogger(__name__)
COMMAND = "postfix"
class PostfixUtilBase(object):
"""A base class for wrapping Postfix command line utilities."""
def __init__(self, executable, config_dir=None):
"""Sets up the Postfix utility class.
:param str executable: name or path of the Postfix utility
:param str config_dir: path to an alternative Postfix config
:raises .NoInstallationError: when the executable isn't found
"""
self.executable = executable
verify_exe_exists(executable)
self._set_base_command(config_dir)
self.config_dir = None
def _set_base_command(self, config_dir):
self._base_command = [self.executable]
if config_dir is not None:
self._base_command.extend(('-c', config_dir,))
def _call(self, extra_args=None):
"""Runs the Postfix utility and returns the result.
:param list extra_args: additional arguments for the command
:returns: data written to stdout and stderr
:rtype: `tuple` of `str`
:raises subprocess.CalledProcessError: if the command fails
"""
args = list(self._base_command)
if extra_args is not None:
args.extend(extra_args)
return check_all_output(args)
def _get_output(self, extra_args=None):
"""Runs the Postfix utility and returns only stdout output.
This function relies on self._call for running the utility.
:param list extra_args: additional arguments for the command
:returns: data written to stdout
:rtype: str
:raises subprocess.CalledProcessError: if the command fails
"""
return self._call(extra_args)[0]
class PostfixUtil(PostfixUtilBase):
"""Wrapper around Postfix CLI tool.
"""
def __init__(self, config_dir=None):
super(PostfixUtil, self).__init__(COMMAND, config_dir)
def test(self):
"""Make sure the configuration is valid.
:raises .MisconfigurationError: if the config is invalid
"""
try:
self._call(["check"])
except subprocess.CalledProcessError as e:
logger.debug("Could not check postfix configuration:\n%s", e)
raise errors.MisconfigurationError(
"Postfix failed internal configuration check.")
def restart(self):
"""Restart or refresh the server content.
:raises .PluginError: when server cannot be restarted
"""
logger.info("Reloading Postfix configuration...")
if self._is_running():
self._reload()
else:
self._start()
def _is_running(self):
"""Is Postfix currently running?
Uses the 'postfix status' command to determine if Postfix is
currently running using the specified configuration files.
:returns: True if Postfix is running, otherwise, False
:rtype: bool
"""
try:
self._call(["status"])
except subprocess.CalledProcessError:
return False
return True
def _start(self):
"""Instructions Postfix to start running.
:raises .PluginError: when Postfix cannot start
"""
try:
self._call(["start"])
except subprocess.CalledProcessError:
raise errors.PluginError("Postfix failed to start")
def _reload(self):
"""Instructs Postfix to reload its configuration.
If Postfix isn't currently running, this method will fail.
:raises .PluginError: when Postfix cannot reload
"""
try:
self._call(["reload"])
except subprocess.CalledProcessError:
raise errors.PluginError(
"Postfix failed to reload its configuration")
def check_all_output(*args, **kwargs):
"""A version of subprocess.check_output that also captures stderr.
This is the same as :func:`subprocess.check_output` except output
written to stderr is also captured and returned to the caller. The
return value is a tuple of two strings (rather than byte strings).
To accomplish this, the caller cannot set the stdout, stderr, or
universal_newlines parameters to :class:`subprocess.Popen`.
Additionally, if the command exits with a nonzero status, output is
not included in the raised :class:`subprocess.CalledProcessError`
because Python 2.6 does not support this. Instead, the failure
including the output is logged.
:param tuple args: positional arguments for Popen
:param dict kwargs: keyword arguments for Popen
:returns: data written to stdout and stderr
:rtype: `tuple` of `str`
:raises ValueError: if arguments are invalid
:raises subprocess.CalledProcessError: if the command fails
"""
for keyword in ('stdout', 'stderr', 'universal_newlines',):
if keyword in kwargs:
raise ValueError(
keyword + ' argument not allowed, it will be overridden.')
kwargs['stdout'] = subprocess.PIPE
kwargs['stderr'] = subprocess.PIPE
kwargs['universal_newlines'] = True
process = subprocess.Popen(*args, **kwargs)
output, err = process.communicate()
retcode = process.poll()
if retcode:
cmd = kwargs.get('args')
if cmd is None:
cmd = args[0]
logger.debug(
"'%s' exited with %d. stdout output was:\n%s\nstderr output was:\n%s",
cmd, retcode, output, err)
raise subprocess.CalledProcessError(retcode, cmd)
return (output, err)
def verify_exe_exists(exe, message=None):
"""Ensures an executable with the given name is available.
If an executable isn't found for the given path or name, extra
directories are added to the user's PATH to help find system
utilities that may not be available in the default cron PATH.
:param str exe: executable path or name
:param str message: Error message to print.
:raises .NoInstallationError: when the executable isn't found
"""
if message is None:
message = "Cannot find executable '{0}'.".format(exe)
if not (certbot_util.exe_exists(exe) or plugins_util.path_surgery(exe)):
raise errors.NoInstallationError(message)
def report_master_overrides(name, overrides, acceptable_overrides=None):
"""If the value for a parameter `name` is overridden by other services,
report a warning to notify the user. If `parameter` is a TLS version parameter
(i.e., `parameter` contains 'tls_protocols' or 'tls_mandatory_protocols'), then
`acceptable_overrides` isn't used each value in overrides is inspected for secure TLS
versions.
:param str name: The name of the parameter that is being overridden.
:param list overrides: The values that other services are setting for `name`.
Each override is a tuple: (service name, value)
:param tuple acceptable_overrides: Override values that are acceptable. For instance, if
another service is overriding our parameter with a more secure option, we don't have
to warn. If this is set to None, errors are raised for *any* overrides of `name`!
"""
error_string = ""
for override in overrides:
service, value = override
# If this override is acceptable:
if acceptable_overrides is not None and \
is_acceptable_value(name, value, acceptable_overrides):
continue
error_string += " {0}: {1}\n".format(service, value)
if error_string:
raise errors.PluginError("{0} is overridden with less secure options by the "
"following services in master.cf:\n".format(name) + error_string)
def is_acceptable_value(parameter, value, acceptable=None):
""" Returns whether the `value` for this `parameter` is acceptable,
given a tuple of `acceptable` values. If `parameter` is a TLS version parameter
(i.e., `parameter` contains 'tls_protocols' or 'tls_mandatory_protocols'), then
`acceptable` isn't used and `value` is inspected for secure TLS versions.
:param str parameter: The name of the parameter being set.
:param str value: Proposed new value for parameter.
:param tuple acceptable: List of acceptable values for parameter.
"""
# Check if param value is a comma-separated list of protocols.
# Otherwise, just check whether the value is in the acceptable list.
if 'tls_protocols' in parameter or 'tls_mandatory_protocols' in parameter:
return _has_acceptable_tls_versions(value)
if acceptable is not None:
return value in acceptable
return False
def _has_acceptable_tls_versions(parameter_string):
"""
Checks to see if the list of TLS protocols is acceptable.
This requires that TLSv1.2 is supported, and neither SSLv2 nor SSLv3 are supported.
Should be a string of protocol names delimited by commas, spaces, or colons.
Postfix's documents suggest listing protocols to exclude, like "!SSLv2, !SSLv3".
Listing the protocols to include, like "TLSv1, TLSv1.1, TLSv1.2" is okay as well,
though not recommended
When these two modes are interspersed, the presence of a single non-negated protocol name
(i.e. "TLSv1" rather than "!TLSv1") automatically excludes all other unnamed protocols.
In addition, the presence of both a protocol name inclusion and exclusion isn't explicitly
documented, so this method should return False if it encounters contradicting statements
about TLSv1.2, SSLv2, or SSLv3. (for instance, "SSLv3, !SSLv3").
"""
if not parameter_string:
return False
bad_versions = list(constants.TLS_VERSIONS)
for version in constants.ACCEPTABLE_TLS_VERSIONS:
del bad_versions[bad_versions.index(version)]
supported_version_list = re.split("[, :]+", parameter_string)
# The presence of any non-"!" protocol listing excludes the others by default.
inclusion_list = False
for version in supported_version_list:
if not version:
continue
if version in bad_versions: # short-circuit if we recognize any bad version
return False
if version[0] != "!":
inclusion_list = True
if inclusion_list: # For any inclusion list, we still require TLS 1.2.
if "TLSv1.2" not in supported_version_list or "!TLSv1.2" in supported_version_list:
return False
else:
for bad_version in bad_versions:
if "!" + bad_version not in supported_version_list:
return False
return True

1
certbot-postfix/docs/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/_build/

View File

@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = certbot-postfix
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

View File

@@ -0,0 +1,8 @@
=================
API Documentation
=================
.. toctree::
:glob:
api/**

View File

@@ -0,0 +1,5 @@
:mod:`certbot_postfix.installer`
--------------------------------------
.. automodule:: certbot_postfix.installer
:members:

View File

@@ -0,0 +1,5 @@
:mod:`certbot_postfix.postconf`
-------------------------------
.. automodule:: certbot_postfix.postconf
:members:

View File

@@ -0,0 +1,190 @@
# -*- coding: utf-8 -*-
#
# Configuration file for the Sphinx documentation builder.
#
# This file does only contain a selection of the most common options. For a
# full list see the documentation:
# http://www.sphinx-doc.org/en/master/config
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- Project information -----------------------------------------------------
project = u'certbot-postfix'
copyright = u'2018, Certbot Project'
author = u'Certbot Project'
# The short X.Y version
version = u'0'
# The full version, including alpha/beta/rc tags
release = u'0'
# -- General configuration ---------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
'sphinx.ext.todo',
'sphinx.ext.coverage',
'sphinx.ext.viewcode',
]
autodoc_member_order = 'bysource'
autodoc_default_flags = ['show-inheritance', 'private-members']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = u'en'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path .
exclude_patterns = [u'_build', 'Thumbs.db', '.DS_Store']
default_role = 'py:obj'
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
# http://docs.readthedocs.org/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs
# on_rtd is whether we are on readthedocs.org
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
if not on_rtd: # only import and set the theme if we're building docs locally
import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# otherwise, readthedocs.org uses their theme by default, so no need to specify it
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
# html_static_path = ['_static']
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# The default sidebars (for documents that don't match any pattern) are
# defined by theme itself. Builtin themes are using these templates by
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
# 'searchbox.html']``.
#
# html_sidebars = {}
# -- Options for HTMLHelp output ---------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'certbot-postfixdoc'
# -- Options for LaTeX output ------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'certbot-postfix.tex', u'certbot-postfix Documentation',
u'Certbot Project', 'manual'),
]
# -- Options for manual page output ------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'certbot-postfix', u'certbot-postfix Documentation',
[author], 1)
]
# -- Options for Texinfo output ----------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'certbot-postfix', u'certbot-postfix Documentation',
author, 'certbot-postfix', 'One line description of project.',
'Miscellaneous'),
]
# -- Extension configuration -------------------------------------------------
# -- Options for intersphinx extension ---------------------------------------
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
'python': ('https://docs.python.org/', None),
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
'certbot': ('https://certbot.eff.org/docs/', None),
}
# -- Options for todo extension ----------------------------------------------
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True

View File

@@ -0,0 +1,28 @@
.. certbot-postfix documentation master file, created by
sphinx-quickstart on Wed May 2 16:01:06 2018.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to certbot-postfix's documentation!
===========================================
.. toctree::
:maxdepth: 2
:caption: Contents:
.. automodule:: certbot_postfix
:members:
.. toctree::
:maxdepth: 1
api
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View File

@@ -0,0 +1,36 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
set SPHINXPROJ=certbot-postfix
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
:end
popd

View File

@@ -0,0 +1,2 @@
acme[dev]==0.25.0
certbot[dev]==0.23.0

View File

@@ -0,0 +1,2 @@
[bdist_wheel]
universal = 1

63
certbot-postfix/setup.py Normal file
View File

@@ -0,0 +1,63 @@
from setuptools import setup
from setuptools import find_packages
version = '0.26.0.dev0'
install_requires = [
'acme>=0.25.0',
'certbot>0.23.0',
'setuptools',
'six',
'zope.component',
'zope.interface',
]
docs_extras = [
'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags
'sphinx_rtd_theme',
]
setup(
name='certbot-postfix',
version=version,
description="Postfix plugin for Certbot",
url='https://github.com/certbot/certbot',
author="Certbot Project",
author_email='client-dev@letsencrypt.org',
license='Apache License 2.0',
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*',
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.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Topic :: Communications :: Email :: Mail Transport Agents',
'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': [
'postfix = certbot_postfix:Installer',
],
},
test_suite='certbot_postfix',
)

View File

@@ -36,6 +36,7 @@ class PluginEntryPoint(object):
"certbot-dns-rfc2136",
"certbot-dns-route53",
"certbot-nginx",
"certbot-postfix",
]
"""Distributions for which prefix will be omitted."""

View File

@@ -25,5 +25,6 @@ fi
-e certbot-dns-rfc2136 \
-e certbot-dns-route53 \
-e certbot-nginx \
-e certbot-postfix \
-e letshelp-certbot \
-e certbot-compatibility-test

View File

@@ -23,5 +23,6 @@ fi
-e certbot-dns-nsone \
-e certbot-dns-route53 \
-e certbot-nginx \
-e certbot-postfix \
-e letshelp-certbot \
-e certbot-compatibility-test

View File

@@ -9,7 +9,7 @@
# -e makes sure we fail fast and don't submit coveralls submit
if [ "xxx$1" = "xxx" ]; then
pkgs="certbot acme certbot_apache certbot_dns_cloudflare certbot_dns_cloudxns certbot_dns_digitalocean certbot_dns_dnsimple certbot_dns_dnsmadeeasy certbot_dns_google certbot_dns_luadns certbot_dns_nsone certbot_dns_rfc2136 certbot_dns_route53 certbot_nginx letshelp_certbot"
pkgs="certbot acme certbot_apache certbot_dns_cloudflare certbot_dns_cloudxns certbot_dns_digitalocean certbot_dns_dnsimple certbot_dns_dnsmadeeasy certbot_dns_google certbot_dns_luadns certbot_dns_nsone certbot_dns_rfc2136 certbot_dns_route53 certbot_nginx certbot_postfix letshelp_certbot"
else
pkgs="$@"
fi
@@ -43,6 +43,8 @@ cover () {
min=92
elif [ "$1" = "certbot_nginx" ]; then
min=97
elif [ "$1" = "certbot_postfix" ]; then
min=100
elif [ "$1" = "letshelp_certbot" ]; then
min=100
else

View File

@@ -31,6 +31,7 @@ all_packages =
certbot-apache \
{[base]dns_packages} \
certbot-nginx \
certbot-postfix \
letshelp-certbot
install_packages =
{toxinidir}/tools/pip_install_editable.sh {[base]all_packages}
@@ -50,6 +51,7 @@ source_paths =
certbot-dns-rfc2136/certbot_dns_rfc2136
certbot-dns-route53/certbot_dns_route53
certbot-nginx/certbot_nginx
certbot-postfix/certbot_postfix
letshelp-certbot/letshelp_certbot
tests/lock_test.py