certbot/certbot-postfix/certbot_postfix/postconf.py
Sydney Li 4ba153949d Fixing up postfix plugin
- Finishing refactor of postconf/postfix command-line utilities
 - Plugin uses starttls_policy plugin to specify per-domain policies

Cleaning up TLS policy code.

Print warning when setting configuration parameter that is overridden by master.

Update client to use new policy API

Cleanup and test fixes

Documentation fix

smaller fixes

Policy is now an enhancement and reverting works

Added a README, and small documentation fixes throughout

Moving testing infra from starttls repo to certbot-postfix

fixing tests and lint

Changes against new policy API

starttls-everywhere => starttls-policy

testing(postfix): Added more varieties of certificates to test against.

Moar fixes against policy API.

Address comments on README and setup.py

Address small comments on postconf and util

Address comments in installer

Python 3 fixes and Postconf tester extends TempDir test class

Mock out postconf calls from tests and test coverage for master overrides

More various fixes. Everything minus testing done

Remove STARTTLS policy enhancement from this branch.

sphinx quickstart

99% test coverage

some cleanup and testfixing

cleanup leftover files

Remove print statement

testfix for python 3.4

Revert dockerfile change

mypy fix

fix(postfix): brad's comments

test(postfix): coverage to 100

test(postfix): mypy

import mypy types

fix(postfix docs): add .rst files and fix build

fix(postfix): tls_only and server_only params behave nicely together

some cleanup

lint

fix more comments

bump version number
2018-06-15 15:46:55 -07:00

152 lines
5.8 KiB
Python

"""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()