-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathMTAConfigGenerator.py
executable file
·132 lines (112 loc) · 4.5 KB
/
MTAConfigGenerator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
#!/usr/bin/env python
import string
import os.path
POSTFIX_DIR = "/etc/postfix"
DEFAULT_POLICY_FILE = os.path.join(POSTFIX_DIR, "starttls_everywhere_policy")
POLICY_CF_ENTRY="texthash:" + DEFAULT_POLICY_FILE
def parse_line(line_data):
"return the and right hand sides of stripped, non-comment postfix config line"
# lines are like:
# smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
num,line = line_data
left, sep, right = line.partition("=")
if not sep:
return None
return (num, left.strip(), right.strip())
#def get_cf_values(lines, var):
class MTAConfigGenerator:
def __init__(self, policy_config):
self.policy_config = policy_config
class ExistingConfigError(ValueError): pass
class PostfixConfigGenerator(MTAConfigGenerator):
def __init__(self, policy_config, fixup=False):
self.fixup = fixup
MTAConfigGenerator.__init__(self, policy_config)
self.postfix_cf_file = self.find_postfix_cf()
self.wrangle_existing_config()
self.set_domainwise_tls_policies()
def ensure_cf_var(self, var, ideal, also_acceptable):
"""
Ensure that existing postfix config @var is in the list of @acceptable
values; if not, set it to the ideal value. """
acceptable = [ideal] + also_acceptable
l = [(num,line) for num,line in enumerate(self.cf) if line.startswith(var)]
if not any(l):
self.additions.append(var + " = " + ideal)
else:
values = map(parse_line, l)
if len(set(values)) > 1:
if self.fixup:
#print "Scheduling deletions:" + `values`
conflicting_lines = [num for num,_var,val in values]
self.deletions.extend(conflicting_lines)
self.additions.append(var + " = " + ideal)
else:
raise ExistingConfigError, "Conflicting existing config values " + `l`
val = values[0][2]
if val not in acceptable:
#print "Scheduling deletions:" + `values`
if self.fixup:
self.deletions.append(values[0][0])
self.additions.append(var + " = " + ideal)
else:
raise ExistingConfigError, "Existing config has %s=%s"%(var,val)
def wrangle_existing_config(self):
"""
Try to ensure/mutate that the config file is in a sane state.
Fixup means we'll delete existing lines if necessary to get there.
"""
self.additions = []
self.deletions = []
self.fn = self.find_postfix_cf()
self.raw_cf = open(self.fn).readlines()
self.cf = map(string.strip, self.raw_cf)
#self.cf = [line for line in cf if line and not line.startswith("#")]
# Check we're currently accepting inbound STARTTLS sensibly
self.ensure_cf_var("smtpd_use_tls", "yes", [])
# Ideally we use it opportunistically in the outbound direction
self.ensure_cf_var("smtp_tls_security_level", "may", ["encrypt"])
# Maximum verbosity lets us collect failure information
self.ensure_cf_var("smtp_tls_loglevel", "1", [])
# Inject a reference to our per-domain policy map
self.ensure_cf_var("smtp_tls_policy_maps", POLICY_CF_ENTRY, [])
self.maybe_add_config_lines()
def maybe_add_config_lines(self):
if not self.additions:
return
if self.fixup:
print "Deleting lines:", self.deletions
self.additions[:0]=["#","# New config lines added by STARTTLS Everywhere","#"]
new_cf_lines = "\n".join(self.additions) + "\n"
print "Adding to %s:" % self.fn
print new_cf_lines
if self.raw_cf[-1][-1] == "\n": sep = ""
else: sep = "\n"
self.new_cf = ""
for num, line in enumerate(self.raw_cf):
if self.fixup and num in self.deletions:
self.new_cf += "# Line removed by STARTTLS Everywhere\n# " + line
else:
self.new_cf += line
self.new_cf += sep + new_cf_lines
print self.new_cf
f = open(self.fn, "w")
f.write(self.new_cf)
f.close()
def find_postfix_cf(self):
"Search far and wide for the correct postfix configuration file"
return os.path.join(POSTFIX_DIR,"main.cf")
def set_domainwise_tls_policies(self):
self.policy_lines = []
for domain, policy in self.policy_config.tls_policies.items():
entry = domain + " encrypt"
if "min-tls-version" in policy:
entry += " " + policy["min-tls-version"]
self.policy_lines.append(entry)
f = open(DEFAULT_POLICY_FILE, "w")
f.write("\n".join(self.policy_lines) + "\n")
f.close()
if __name__ == "__main__":
import ConfigParser
c = ConfigParser.Config("starttls-everywhere.json")
pcgen = PostfixConfigGenerator(c, fixup=True)