]> code.delx.au - monosys/blob - dnsctl
split-mvimg
[monosys] / dnsctl
1 #!/usr/bin/env python3
2
3 import argparse
4 import datetime
5 import os
6 import subprocess
7 import sys
8 import re
9
10
11 def increment_serial(line):
12 current_serial = re.search(R"\b\d\d*\b", line).group(0)
13
14 current = int(current_serial)
15 current_num = current % 100
16 current_date = (current - current_num) / 100
17 new_date = int(datetime.datetime.now().strftime("%Y%m%d"))
18 if current_date == new_date:
19 next_num = current_num + 1
20 else:
21 next_num = 0
22
23 if next_num >= 100:
24 raise ValueError("Too many serial changes today!")
25 new_serial = str(new_date) + str(next_num).zfill(2)
26 line = line.replace(current_serial, new_serial)
27
28 return line
29
30 def replace_ip(line):
31 source_ip, source_port, dest_ip, dest_port = os.environ["SSH_CONNECTION"].split()
32 line = re.sub(R"\b\d\d?\d?\.\d\d?\d?\.\d\d?\d?\.\d\d?\d?\b", source_ip, line)
33 return line
34
35 def update_dyndns(zonefile, dnslabel):
36 out = []
37 with open(zonefile, encoding="utf-8") as f:
38 for line in f:
39 if line.find("Serial") >= 0:
40 line = increment_serial(line)
41 elif line.find("DYNDNS") >= 0 and line.find(dnslabel) >= 0:
42 line = replace_ip(line)
43 out.append(line)
44
45 with open(zonefile, "w", encoding="utf-8") as f:
46 f.writelines(out)
47
48 reload_bind()
49
50
51 def read_zonefile(zonefile):
52 with open(zonefile, encoding="utf-8") as f:
53 sys.stdout.write(f.read())
54
55
56 def write_zonefile(zonefile):
57 data = sys.stdin.read()
58 if not data.strip().endswith("; END"):
59 print("Missing end of file marker -- ; END")
60 sys.exit(1)
61
62 with open(zonefile, "w", encoding="utf-8") as f:
63 f.write(data)
64
65 reload_bind()
66
67 def tail_logs():
68 subprocess.check_call(["sudo", "journalctl", "-u", "nsd", "-f"])
69
70 def reload_bind():
71 subprocess.check_call(["sudo", "systemctl", "reload", "nsd"])
72 print("nsd reloaded")
73
74
75 def parse_ssh_args():
76 parser = argparse.ArgumentParser(description="Edit zone files")
77
78 parser.add_argument("--zonefile",
79 help="the zone file to operate on")
80
81
82 action_group = parser.add_mutually_exclusive_group(required=True)
83
84 action_group.add_argument("--logs", action="store_true",
85 help="show bind logs")
86
87 action_group.add_argument("--dyndns",
88 help="update the specified dnslabel with the SSH origin IP")
89
90 action_group.add_argument("--read", action="store_true",
91 help="print the zone file to stdout")
92
93 action_group.add_argument("--write", action="store_true",
94 help="save the contents of stdin to the zone file")
95
96 ssh_args = os.environ.get("SSH_ORIGINAL_COMMAND", "--help").split()[1:]
97 args = parser.parse_args(ssh_args)
98
99 if not args.logs and not args.zonefile:
100 print("Required parameter: --zonefile\n")
101 parser.print_help()
102 sys.exit(0)
103
104 return args
105
106 def parse_cmdline_args():
107 parser = argparse.ArgumentParser(description="Edit zone files")
108
109 parser.add_argument("--allow-zonefile", required=True, action="append",
110 help="specify allowed zone files")
111
112 parser.add_argument("--allow-write", action="store_true",
113 help="allow --write option")
114
115 parser.add_argument("--allow-dyndns",
116 help="allow --dyndns option")
117
118 return parser.parse_args()
119
120 def parse_args():
121 cmdline_args = parse_cmdline_args()
122 ssh_args = parse_ssh_args()
123
124 if ssh_args.zonefile and ssh_args.zonefile not in cmdline_args.allow_zonefile:
125 print("The specified zonefile is not on the allowed list:", cmdline_args.allow_zonefile)
126 sys.exit(1)
127
128 if ssh_args.dyndns and ssh_args.dyndns != cmdline_args.allow_dyndns:
129 print("Dynamic DNS is only allowed for:", cmdline_args.allow_dyndns)
130 sys.exit(1)
131
132 if ssh_args.write and not cmdline_args.allow_write:
133 print("Write to zonefile is not allowed")
134 sys.exit(1)
135
136 return ssh_args
137
138
139 def main():
140 args = parse_args()
141
142 if args.logs:
143 tail_logs()
144 elif args.dyndns:
145 update_dyndns(args.zonefile, args.dyndns)
146 elif args.read:
147 read_zonefile(args.zonefile)
148 elif args.write:
149 write_zonefile(args.zonefile)
150 else:
151 assert False, "Bad action"
152
153 if __name__ == "__main__":
154 main()