-
Notifications
You must be signed in to change notification settings - Fork 21
/
mkdns
executable file
·191 lines (161 loc) · 6.77 KB
/
mkdns
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
#!/usr/bin/env python3
from textwrap import dedent
from optparse import OptionParser
from socket import AF_INET, AF_INET6, inet_pton, error as socket_error
from formatter import Formatter
from filereader import get_communities_data
from ipaddress import ip_address
class DnsmasqFormatter(Formatter):
"""Formatter for dnsmasq"""
def add_data(self, domains, servers):
for domain in domains:
for server in servers:
self.config.append("server=/%s/%s" % (domain, server))
class BindFormatter(Formatter):
"""Formatter for bind9 (>=9.8) using type static-stub"""
def add_data(self, domains, servers):
for domain in domains:
self.config.append(dedent("""
zone "%s" {
type static-stub;
server-addresses { %s; };
};
""" % (domain, "; ".join(servers))).lstrip())
class BindForwardFormatter(Formatter):
"""Formatter for bind9 using type forward"""
def add_data(self, domains, servers):
for domain in domains:
self.config.append(dedent("""
zone "%s" {
type forward;
forwarders { %s; };
forward only;
};
""" % (domain, "; ".join(servers))).lstrip())
class UnboundForwardFormatter(object):
"""Impersonate a Formatter for unbound using forward-zone"""
def __init__(self):
self.zones = {}
self.community = None
self.buffer = []
self.add_header()
def add_header(self):
self.buffer.append('#\n# This file is automatically generated.\n#\n')
def add_comment(self, community_string):
# this function simply remembers the last community
self.community = community_string.strip()
def add_data(self, domains, servers):
self.zones[self.community] = {'forward': {}, 'reverse': {}}
for domain in domains:
domain = str(domain)
if domain.endswith('ip6.arpa') or domain.endswith('in-addr.arpa'):
self.zones[self.community]['reverse'][domain] = servers
else:
self.zones[self.community]['forward'][domain] = servers
def add_server_block(self):
self.buffer.append('server:')
self.buffer.append('\tlocal-zone: "10.in-addr.arpa" nodefault')
for community, zone_type in self.zones.items():
self.buffer.append('\n\t# %s' % community)
for zone in zone_type['forward'].keys():
self.buffer.append('\tdomain-insecure: "%s"' % zone)
self.buffer.append('\tprivate-domain: "%s"' % zone)
for zone in zone_type['reverse'].keys():
self.buffer.append('\tdomain-insecure: "%s"' % zone)
self.buffer.append('\tlocal-zone: "%s" nodefault' % zone)
def add_zone_blocks(self):
for community, zone_type in self.zones.items():
self.buffer.append('\n#\n# %s\n#\n' % community)
for zone, servers in zone_type['forward'].items():
self._add_zone(zone, servers)
for zone, servers in zone_type['reverse'].items():
self._add_zone(zone, servers)
def _add_zone(self, zone, servers):
self.buffer.append('forward-zone:')
self.buffer.append('\tname: "%s"' % zone)
for server in servers:
self.buffer.append('\tforward-addr: %s' % server)
def finalize(self):
self.add_server_block()
self.add_zone_blocks()
return "\n".join(self.buffer)
class PowerDNSForwardFormatter(Formatter):
"""Formatter for pdns using type forward"""
def add_data(self, domains, servers):
servers = [
"[{}]".format(server) if ip_address(server).version == 6 else server
for server in servers
]
for domain in domains:
self.config.append(dedent("""
%s=%s
""" % (domain, ", ".join(servers))).lstrip())
def create_config(srcdir, fmtclass, exclude=set(), filters=[]):
"""
Generates a configuration using all files in srcdir
(non-recursively) excluding communities from 'exclude'.
The files are read in lexicographic order to produce deterministic
results.
"""
formatter = fmtclass()
for community, data in get_communities_data(srcdir, exclude):
try:
domains = data['domains']
nameservers = data['nameservers']
except (TypeError, KeyError):
continue
formatter.add_comment("\n%s\n" % community)
servers = filter(lambda d: all(f(d) for f in filters), nameservers)
servers = list(servers)
if len(domains) == 0:
formatter.add_comment("No valid domains found")
elif len(servers) == 0:
formatter.add_comment("No valid servers found")
else:
formatter.add_data(domains, servers)
print(formatter.finalize())
if __name__ == "__main__":
def try_inet_pton(af, ip):
try:
inet_pton(af, ip)
return True
except socket_error:
return False
formatters = {
"dnsmasq": DnsmasqFormatter,
"bind": BindFormatter,
"bind-forward": BindForwardFormatter,
"unbound": UnboundForwardFormatter,
"pdns": PowerDNSForwardFormatter,
}
filters = {
"v4": lambda value: try_inet_pton(AF_INET, value),
"v6": lambda value: try_inet_pton(AF_INET6, value),
}
parser = OptionParser()
parser.add_option("-f", "--format", dest="fmt",
help="""Create config in format FMT.
Possible values: %s. Default: dnsmasq""" %
", ".join(formatters.keys()),
metavar="FMT",
choices=list(formatters.keys()),
default="dnsmasq")
parser.add_option("-s", "--sourcedir", dest="src",
help="Use files in DIR as input files. Default: "
"../icvpn-meta/",
metavar="DIR",
default="../icvpn-meta/")
parser.add_option("-x", "--exclude", dest="exclude", action="append",
help="Exclude COMMUNITY (may be repeated)",
metavar="COMMUNITY",
default=[])
parser.add_option("--filter", dest="filter",
help="""Only include certain servers.
Possible choices: %s""" %
", ".join(filters.keys()),
choices=list(filters.keys()))
(options, args) = parser.parse_args()
create_config(options.src,
formatters[options.fmt],
set(options.exclude),
[filters[options.filter]] if options.filter else [])