Blame


1 84c190b6 2021-12-17 jrmu #!/usr/bin/perl
2 84c190b6 2021-12-17 jrmu
3 84c190b6 2021-12-17 jrmu package DNS;
4 84c190b6 2021-12-17 jrmu
5 84c190b6 2021-12-17 jrmu use strict;
6 84c190b6 2021-12-17 jrmu use warnings;
7 84c190b6 2021-12-17 jrmu use OpenBSD::Pledge;
8 84c190b6 2021-12-17 jrmu use OpenBSD::Unveil;
9 84c190b6 2021-12-17 jrmu use File::Copy qw(copy);
10 84c190b6 2021-12-17 jrmu
11 84c190b6 2021-12-17 jrmu my %conf = %main::conf;
12 84c190b6 2021-12-17 jrmu my $chans = $conf{chans};
13 84c190b6 2021-12-17 jrmu my $staff = $conf{staff};
14 84c190b6 2021-12-17 jrmu my $key = $conf{key};
15 84c190b6 2021-12-17 jrmu my $hash = $conf{hash};
16 84c190b6 2021-12-17 jrmu my $hostname = $conf{hostname};
17 84c190b6 2021-12-17 jrmu my $verbose = $conf{verbose};
18 84c190b6 2021-12-17 jrmu my $ip4 = $conf{ip4};
19 84c190b6 2021-12-17 jrmu my $ip6 = $conf{ip6};
20 84c190b6 2021-12-17 jrmu my $ip6subnet = $conf{ip6subnet};
21 84c190b6 2021-12-17 jrmu my $zonedir = $conf{zonedir};
22 84c190b6 2021-12-17 jrmu my $hostnameif = $conf{hostnameif};
23 84c190b6 2021-12-17 jrmu if (host($hostname) =~ /(\d+\.){3,}\d+/) {
24 84c190b6 2021-12-17 jrmu $ip4 = $&;
25 84c190b6 2021-12-17 jrmu }
26 84c190b6 2021-12-17 jrmu main::cbind("msg", "-", "setrdns", \&msetrdns);
27 84c190b6 2021-12-17 jrmu main::cbind("msg", "-", "delrdns", \&mdelrdns);
28 84c190b6 2021-12-17 jrmu main::cbind("msg", "-", "setdns", \&msetdns);
29 84c190b6 2021-12-17 jrmu main::cbind("msg", "-", "deldns", \&mdeldns);
30 84c190b6 2021-12-17 jrmu main::cbind("msg", "-", "host", \&mhost);
31 84c190b6 2021-12-17 jrmu main::cbind("msg", "-", "nextdns", \&mnextdns);
32 84c190b6 2021-12-17 jrmu main::cbind("msg", "-", "readip6s", \&mreadip6s);
33 84c190b6 2021-12-17 jrmu
34 84c190b6 2021-12-17 jrmu sub init {
35 84c190b6 2021-12-17 jrmu unveil("$zonedir", "rwc") or die "Unable to unveil $!";
36 84c190b6 2021-12-17 jrmu unveil("/usr/bin/doas", "rx") or die "Unable to unveil $!";
37 84c190b6 2021-12-17 jrmu unveil("/usr/bin/host", "rx") or die "Unable to unveil $!";
38 84c190b6 2021-12-17 jrmu unveil("$hostnameif", "rwc") or die "Unable to unveil $!";
39 84c190b6 2021-12-17 jrmu }
40 84c190b6 2021-12-17 jrmu
41 84c190b6 2021-12-17 jrmu # !setrdns 2001:bd8:: username.example.com
42 84c190b6 2021-12-17 jrmu sub msetrdns {
43 84c190b6 2021-12-17 jrmu my ($bot, $nick, $host, $hand, $text) = @_;
44 84c190b6 2021-12-17 jrmu if (! (main::isstaff($bot, $nick))) { return; }
45 84c190b6 2021-12-17 jrmu if ($text =~ /^([0-9A-Fa-f:\.]{3,})\s+([-0-9A-Za-z\.]+)$/) {
46 84c190b6 2021-12-17 jrmu my ($ip, $hostname) = ($1, $2);
47 84c190b6 2021-12-17 jrmu if (setrdns($ip, $ip6subnet, $hostname)) {
48 84c190b6 2021-12-17 jrmu main::putserv($bot, "PRIVMSG $nick :$hostname set to $ip");
49 84c190b6 2021-12-17 jrmu } else {
50 84c190b6 2021-12-17 jrmu main::putserv($bot, "PRIVMSG $nick :ERROR: failed to set rDNS");
51 84c190b6 2021-12-17 jrmu }
52 84c190b6 2021-12-17 jrmu }
53 84c190b6 2021-12-17 jrmu }
54 84c190b6 2021-12-17 jrmu
55 84c190b6 2021-12-17 jrmu # !delrdns 2001:bd8::
56 84c190b6 2021-12-17 jrmu sub mdelrdns {
57 84c190b6 2021-12-17 jrmu my ($bot, $nick, $host, $hand, $text) = @_;
58 84c190b6 2021-12-17 jrmu if (! (main::isstaff($bot, $nick))) { return; }
59 84c190b6 2021-12-17 jrmu if ($text =~ /^([0-9A-Fa-f:\.]{3,})$/) {
60 84c190b6 2021-12-17 jrmu my ($ip) = ($1);
61 84c190b6 2021-12-17 jrmu if (delrdns($ip, $ip6subnet)) {
62 84c190b6 2021-12-17 jrmu main::putserv($bot, "PRIVMSG $nick :$ip rDNS deleted");
63 84c190b6 2021-12-17 jrmu } else {
64 84c190b6 2021-12-17 jrmu main::putserv($bot, "PRIVMSG $nick :ERROR: failed to set rDNS");
65 84c190b6 2021-12-17 jrmu }
66 84c190b6 2021-12-17 jrmu }
67 84c190b6 2021-12-17 jrmu }
68 84c190b6 2021-12-17 jrmu # !setdns username 1.2.3.4
69 84c190b6 2021-12-17 jrmu sub msetdns {
70 84c190b6 2021-12-17 jrmu my ($bot, $nick, $host, $hand, $text) = @_;
71 84c190b6 2021-12-17 jrmu if (! (main::isstaff($bot, $nick))) { return; }
72 84c190b6 2021-12-17 jrmu if ($text =~ /^([-0-9A-Za-z\.]+)\s+([0-9A-Fa-f:\.]+)/) {
73 84c190b6 2021-12-17 jrmu my ($name, $value) = ($1, $2);
74 84c190b6 2021-12-17 jrmu if ($value =~ /:/ and setdns($name, $hostname, "AAAA", $value)) {
75 84c190b6 2021-12-17 jrmu main::putserv($bot, "PRIVMSG $nick :$name.$hostname AAAA set to $value");
76 84c190b6 2021-12-17 jrmu } elsif (setdns($name, $hostname, "A", $value)) {
77 84c190b6 2021-12-17 jrmu main::putserv($bot, "PRIVMSG $nick :$name.$hostname A set to $value");
78 84c190b6 2021-12-17 jrmu } else {
79 84c190b6 2021-12-17 jrmu main::putserv($bot, "PRIVMSG $nick :ERROR: failed to set DNS");
80 84c190b6 2021-12-17 jrmu }
81 84c190b6 2021-12-17 jrmu }
82 84c190b6 2021-12-17 jrmu }
83 84c190b6 2021-12-17 jrmu
84 84c190b6 2021-12-17 jrmu # !deldns username
85 84c190b6 2021-12-17 jrmu sub mdeldns {
86 84c190b6 2021-12-17 jrmu my ($bot, $nick, $host, $hand, $text) = @_;
87 84c190b6 2021-12-17 jrmu if (! (main::isstaff($bot, $nick))) { return; }
88 84c190b6 2021-12-17 jrmu if ($text =~ /^([-0-9A-Za-z\.]+)$/) {
89 84c190b6 2021-12-17 jrmu my ($name) = ($1);
90 84c190b6 2021-12-17 jrmu if (setdns($name, $hostname)) {
91 84c190b6 2021-12-17 jrmu main::putserv($bot, "PRIVMSG $nick :$text deleted");
92 84c190b6 2021-12-17 jrmu } else {
93 84c190b6 2021-12-17 jrmu main::putserv($bot, "PRIVMSG $nick :ERROR: failed to delete DNS records");
94 84c190b6 2021-12-17 jrmu }
95 84c190b6 2021-12-17 jrmu }
96 84c190b6 2021-12-17 jrmu }
97 84c190b6 2021-12-17 jrmu
98 84c190b6 2021-12-17 jrmu # !host username
99 84c190b6 2021-12-17 jrmu sub mhost {
100 84c190b6 2021-12-17 jrmu my ($bot, $nick, $host, $hand, $text) = @_;
101 84c190b6 2021-12-17 jrmu if (! (main::isstaff($bot, $nick))) { return; }
102 84c190b6 2021-12-17 jrmu if ($text =~ /^([-0-9A-Za-z:\.]{3,})/) {
103 84c190b6 2021-12-17 jrmu my ($hostname) = ($1);
104 84c190b6 2021-12-17 jrmu main::putserv($bot, "PRIVMSG $nick :".host($hostname));
105 84c190b6 2021-12-17 jrmu }
106 84c190b6 2021-12-17 jrmu }
107 84c190b6 2021-12-17 jrmu
108 84c190b6 2021-12-17 jrmu # !nextdns username
109 84c190b6 2021-12-17 jrmu sub mnextdns {
110 84c190b6 2021-12-17 jrmu my ($bot, $nick, $host, $hand, $text) = @_;
111 84c190b6 2021-12-17 jrmu if (! (main::isstaff($bot, $nick))) { return; }
112 84c190b6 2021-12-17 jrmu if ($text =~ /^([-0-9a-zA-Z]+)/) {
113 84c190b6 2021-12-17 jrmu main::putserv($bot, "PRIVMSG $nick :$text set to ".nextdns($text));
114 84c190b6 2021-12-17 jrmu }
115 84c190b6 2021-12-17 jrmu }
116 84c190b6 2021-12-17 jrmu
117 84c190b6 2021-12-17 jrmu # !readip6s
118 84c190b6 2021-12-17 jrmu sub mreadip6s {
119 84c190b6 2021-12-17 jrmu my ($bot, $nick, $host, $hand, $text) = @_;
120 84c190b6 2021-12-17 jrmu if (! (main::isstaff($bot, $nick))) { return; }
121 84c190b6 2021-12-17 jrmu foreach my $line (readip6s($hostnameif)) {
122 84c190b6 2021-12-17 jrmu print "$line\n"
123 84c190b6 2021-12-17 jrmu }
124 84c190b6 2021-12-17 jrmu }
125 84c190b6 2021-12-17 jrmu
126 84c190b6 2021-12-17 jrmu # Return list of ipv6 addresses from filename
127 84c190b6 2021-12-17 jrmu sub readip6s {
128 84c190b6 2021-12-17 jrmu my ($filename) = @_;
129 84c190b6 2021-12-17 jrmu my @lines = main::readarray($filename);
130 84c190b6 2021-12-17 jrmu my @ipv6s;
131 84c190b6 2021-12-17 jrmu foreach my $line (@lines) {
132 84c190b6 2021-12-17 jrmu if ($line =~ /^\s*inet6\s+(alias\s+)?([0-9a-f:]{4,})\s+[0-9]+\s*$/i) {
133 84c190b6 2021-12-17 jrmu push(@ipv6s, $2);
134 84c190b6 2021-12-17 jrmu } elsif ($line =~ /^\s*([0-9a-f:]{4,})\s*$/i) {
135 84c190b6 2021-12-17 jrmu push(@ipv6s, $1);
136 84c190b6 2021-12-17 jrmu }
137 84c190b6 2021-12-17 jrmu }
138 84c190b6 2021-12-17 jrmu return @ipv6s;
139 84c190b6 2021-12-17 jrmu }
140 84c190b6 2021-12-17 jrmu
141 84c190b6 2021-12-17 jrmu # set rdns of $ip6 to $hostname given $subnet
142 84c190b6 2021-12-17 jrmu # return true on success; false on failure
143 84c190b6 2021-12-17 jrmu sub setrdns {
144 84c190b6 2021-12-17 jrmu my ($ip6, $subnet, $hostname) = @_;
145 84c190b6 2021-12-17 jrmu my $digits = ip6full($ip6);
146 84c190b6 2021-12-17 jrmu $digits =~ tr/://d;
147 84c190b6 2021-12-17 jrmu my $reversed = reverse($digits);
148 84c190b6 2021-12-17 jrmu my $origin = substr($reversed, 32-$subnet/4);
149 84c190b6 2021-12-17 jrmu $origin = join('.', split(//, $origin)).".ip6.arpa";
150 84c190b6 2021-12-17 jrmu my $name = substr($reversed, 0, 32-$subnet/4);
151 84c190b6 2021-12-17 jrmu $name = join('.', split(//, $name));
152 84c190b6 2021-12-17 jrmu # delete old PTR records, then set new one
153 b99a91cd 2023-03-06 jrmu return setdns($name, $origin) && setdns($name, $origin, "PTR", $hostname.".");
154 84c190b6 2021-12-17 jrmu }
155 84c190b6 2021-12-17 jrmu # delete rdns of $ip6 given $subnet
156 84c190b6 2021-12-17 jrmu # return true on success; false on failure
157 84c190b6 2021-12-17 jrmu sub delrdns {
158 84c190b6 2021-12-17 jrmu my ($ip6, $subnet) = @_;
159 84c190b6 2021-12-17 jrmu return setrdns($ip6, $subnet);
160 f805bda2 2023-02-23 jrmu }
161 f805bda2 2023-02-23 jrmu
162 84c190b6 2021-12-17 jrmu # given $origin. create $name RR of $type and set to $value if provided;
163 84c190b6 2021-12-17 jrmu # if $value is missing, delete $domain
164 84c190b6 2021-12-17 jrmu # returns true upon success, false upon failure
165 84c190b6 2021-12-17 jrmu sub setdns {
166 84c190b6 2021-12-17 jrmu my ($name, $origin, $type, $value) = @_;
167 84c190b6 2021-12-17 jrmu my $filename = "$zonedir/$origin";
168 84c190b6 2021-12-17 jrmu my @lines = main::readarray($filename);
169 84c190b6 2021-12-17 jrmu foreach my $line (@lines) {
170 84c190b6 2021-12-17 jrmu # increment the zone's serial number
171 84c190b6 2021-12-17 jrmu if ($line =~ /(\d{8})(\d{2})((\s+\d+){4}\s*\))/) {
172 84c190b6 2021-12-17 jrmu my $date = main::date();
173 84c190b6 2021-12-17 jrmu my $serial = 0;
174 84c190b6 2021-12-17 jrmu if ($date <= $1) { $serial = $2+1; }
175 84c190b6 2021-12-17 jrmu $line = $`.$date.sprintf("%02d",$serial).$3.$';
176 84c190b6 2021-12-17 jrmu }
177 84c190b6 2021-12-17 jrmu }
178 84c190b6 2021-12-17 jrmu if (!defined($value)) { # delete records
179 84c190b6 2021-12-17 jrmu @lines = grep !/\b$name\s*3600\s*IN/, @lines;
180 84c190b6 2021-12-17 jrmu } else {
181 84c190b6 2021-12-17 jrmu push(@lines, "$name 3600 IN $type $value");
182 84c190b6 2021-12-17 jrmu }
183 84c190b6 2021-12-17 jrmu # trailing newline necessary
184 84c190b6 2021-12-17 jrmu main::writefile("$filename.bak", join("\n", @lines)."\n");
185 84c190b6 2021-12-17 jrmu copy "$filename.bak", $filename;
186 84c190b6 2021-12-17 jrmu if (system("doas -u _nsd nsd-control reload")) {
187 84c190b6 2021-12-17 jrmu return 0;
188 84c190b6 2021-12-17 jrmu } else {
189 84c190b6 2021-12-17 jrmu return 1;
190 84c190b6 2021-12-17 jrmu }
191 84c190b6 2021-12-17 jrmu }
192 84c190b6 2021-12-17 jrmu
193 84c190b6 2021-12-17 jrmu # given hostname, return IP addresses; or given IP address, return hostname
194 84c190b6 2021-12-17 jrmu sub host {
195 84c190b6 2021-12-17 jrmu my ($name) = @_;
196 84c190b6 2021-12-17 jrmu my @matches;
197 84c190b6 2021-12-17 jrmu my @lines = split /\n/m, `host $name`;
198 84c190b6 2021-12-17 jrmu if ($name =~ /^[0-9\.]+$/ or $name =~ /:/) { # IP address
199 84c190b6 2021-12-17 jrmu foreach my $line (@lines) {
200 84c190b6 2021-12-17 jrmu if ($line =~ /([\d\.]+).(in-addr|ip6).arpa domain name pointer (.*)/) {
201 84c190b6 2021-12-17 jrmu push(@matches, $3);
202 84c190b6 2021-12-17 jrmu }
203 84c190b6 2021-12-17 jrmu }
204 84c190b6 2021-12-17 jrmu } else { # hostname
205 84c190b6 2021-12-17 jrmu foreach my $line (@lines) {
206 84c190b6 2021-12-17 jrmu if ($line =~ /$name has (IPv6 )?address ([0-9a-fA-F\.:]+)/) {
207 84c190b6 2021-12-17 jrmu push(@matches, $2);
208 84c190b6 2021-12-17 jrmu }
209 84c190b6 2021-12-17 jrmu }
210 84c190b6 2021-12-17 jrmu }
211 84c190b6 2021-12-17 jrmu return join(' ', @matches);
212 84c190b6 2021-12-17 jrmu }
213 84c190b6 2021-12-17 jrmu
214 84c190b6 2021-12-17 jrmu # Return an ipv6 address with all zeroes filled in
215 84c190b6 2021-12-17 jrmu sub ip6full {
216 84c190b6 2021-12-17 jrmu my ($ip6) = @_;
217 84c190b6 2021-12-17 jrmu my $left = substr($ip6, 0, index($ip6, "::"));
218 84c190b6 2021-12-17 jrmu my $leftcolons = ($left =~ tr/://);
219 84c190b6 2021-12-17 jrmu $ip6 =~ s{::}{:};
220 84c190b6 2021-12-17 jrmu my @quartets = split(':', $ip6);
221 84c190b6 2021-12-17 jrmu my $length = scalar(@quartets);
222 84c190b6 2021-12-17 jrmu for (my $n = 1; $n <= 8 - $length; $n++) {
223 84c190b6 2021-12-17 jrmu splice(@quartets, $leftcolons+1, 0, "0000");
224 84c190b6 2021-12-17 jrmu }
225 84c190b6 2021-12-17 jrmu my @newquartets = map(sprintf('%04s', $_), @quartets);
226 84c190b6 2021-12-17 jrmu my $full = join(':',@newquartets);
227 84c190b6 2021-12-17 jrmu return $full;
228 84c190b6 2021-12-17 jrmu }
229 84c190b6 2021-12-17 jrmu # Returns the network part of the first IPv6 address (indicated by subnet)
230 84c190b6 2021-12-17 jrmu # with the host part of the second IPv6 address
231 84c190b6 2021-12-17 jrmu sub ip6mask {
232 84c190b6 2021-12-17 jrmu my ($ip6net, $subnet, $ip6host) = @_;
233 84c190b6 2021-12-17 jrmu my $netdigits = ip6full($ip6net);
234 84c190b6 2021-12-17 jrmu $netdigits =~ tr/://d;
235 84c190b6 2021-12-17 jrmu my $hostdigits = ip6full($ip6host);
236 84c190b6 2021-12-17 jrmu $hostdigits =~ tr/://d;
237 84c190b6 2021-12-17 jrmu my $digits = substr($netdigits,0,($subnet/4)).substr($hostdigits,($subnet/4));
238 84c190b6 2021-12-17 jrmu my $ip6;
239 84c190b6 2021-12-17 jrmu for (my $n = 0; $n < 32; $n++) {
240 84c190b6 2021-12-17 jrmu if ($n > 0 && $n % 4 == 0) {
241 84c190b6 2021-12-17 jrmu $ip6 .= ":";
242 84c190b6 2021-12-17 jrmu }
243 84c190b6 2021-12-17 jrmu $ip6 .= substr($digits,$n,1);
244 84c190b6 2021-12-17 jrmu }
245 84c190b6 2021-12-17 jrmu return $ip6;
246 84c190b6 2021-12-17 jrmu }
247 84c190b6 2021-12-17 jrmu sub randip6 {
248 84c190b6 2021-12-17 jrmu return join ':', map { sprintf '%04x', rand 0x10000 } (1 .. 8);
249 84c190b6 2021-12-17 jrmu }
250 84c190b6 2021-12-17 jrmu
251 84c190b6 2021-12-17 jrmu # create A and AAAA records for subdomain, set the rDNS,
252 84c190b6 2021-12-17 jrmu # and return the new ipv6 address
253 84c190b6 2021-12-17 jrmu sub nextdns {
254 84c190b6 2021-12-17 jrmu my ($subdomain) = @_;
255 84c190b6 2021-12-17 jrmu my $newip6 = $ip6;
256 84c190b6 2021-12-17 jrmu my @allip6s = readip6s($hostnameif);
257 84c190b6 2021-12-17 jrmu while (grep(/$newip6/, @allip6s)) {
258 84c190b6 2021-12-17 jrmu $newip6 = ip6mask($ip6, $ip6subnet,randip6());
259 84c190b6 2021-12-17 jrmu }
260 84c190b6 2021-12-17 jrmu main::appendfile($hostnameif, "inet6 alias $newip6 48\n");
261 84c190b6 2021-12-17 jrmu `doas ifconfig vio0 inet6 $newip6/48`;
262 84c190b6 2021-12-17 jrmu if (setdns($subdomain, $hostname, "A", $ip4) && setdns($subdomain, $hostname, "AAAA", $newip6) && setrdns($newip6, $ip6subnet, "$subdomain.$hostname")) {
263 84c190b6 2021-12-17 jrmu return "$newip6";
264 84c190b6 2021-12-17 jrmu }
265 84c190b6 2021-12-17 jrmu return "false";
266 84c190b6 2021-12-17 jrmu }
267 84c190b6 2021-12-17 jrmu
268 84c190b6 2021-12-17 jrmu 1; # MUST BE LAST STATEMENT IN FILE