Annotation of botnow/DNS.pm, Revision 1.1.1.1
1.1 bountyht 1: #!/usr/bin/perl
2:
3: package DNS;
4:
5: use strict;
6: use warnings;
7: use OpenBSD::Pledge;
8: use OpenBSD::Unveil;
9: use Data::Dumper;
10: use File::Copy qw(copy);
11:
12: my %conf = %main::conf;
13: my $chans = $conf{chans};
14: my $staff = $conf{staff};
15: my $key = $conf{key};
16: my $hash = $conf{hash};
17: my $hostname = $conf{hostname};
18: my $verbose = $conf{verbose};
19: my $ipv4 = $conf{ipv4};
20: my $zonedir = $conf{zonedir};
21: my $ipv6path = $conf{ipv6path};
22: my $hostnameif = $conf{hostnameif};
23: # Validate ipv6s if it exists, otherwise load addresses from /etc/hostname.if
24: my @ipv6s;
25: if (!(-s "$ipv6path")) {
26: print "No IPv6 addresses in $ipv6path, loading from $hostnameif...\n";
27: @ipv6s = readipv6s($hostnameif);
28: } else {
29: @ipv6s = readipv6s($ipv6path);
30: }
31: if (!@ipv6s) { die "No IPv6 addresses in $ipv6path or $hostnameif!"; }
32: if (host($hostname) =~ /(\d+\.){3,}\d+/) {
33: $ipv4 = $&;
34: }
35: main::cbind("msg", "-", "setrdns", \&msetrdns);
36: main::cbind("msg", "-", "delrdns", \&mdelrdns);
37: main::cbind("msg", "-", "setdns", \&msetdns);
38: main::cbind("msg", "-", "deldns", \&mdeldns);
39: main::cbind("msg", "-", "host", \&mhost);
40: main::cbind("msg", "-", "nextdns", \&mnextdns);
41:
42: sub init {
43: unveil("$ipv6path", "rwc") or die "Unable to unveil $!";
44: unveil("$zonedir", "rwc") or die "Unable to unveil $!";
45: #dependencies for doas
46: unveil("/usr/bin/doas", "rx") or die "Unable to unveil $!";
47: #dependencies for host
48: unveil("/usr/bin/host", "rx") or die "Unable to unveil $!";
49: }
50:
51: sub msetrdns {
52: my ($bot, $nick, $host, $hand, $text) = @_;
53: if (! (main::isstaff($bot, $nick))) { return; }
54: if ($text =~ /^([0-9A-Fa-f:\.]{3,})\s+([-0-9A-Za-z\.]+)/) {
55: my ($ip, $hostname) = ($1, $2);
56: if (setrdns($ip, $hostname)) {
57: main::putserv($bot, "PRIVMSG $nick :$hostname set to $ip");
58: } else {
59: main::putserv($bot, "PRIVMSG $nick :ERROR: failed to set rDNS");
60: }
61: }
62: }
63: sub mdelrdns {
64: my ($bot, $nick, $host, $hand, $text) = @_;
65: if (! (main::isstaff($bot, $nick))) { return; }
66: if ($text =~ /^([0-9A-Fa-f:\.]{3,})$/) {
67: my $ip = $1;
68: my $hostname = "notset";
69: if (setrdns($ip, $hostname)) {
70: main::putserv($bot, "PRIVMSG $nick :$ip rDNS deleted");
71: } else {
72: main::putserv($bot, "PRIVMSG $nick :ERROR: failed to set rDNS");
73: }
74: }
75: }
76: sub msetdns {
77: my ($bot, $nick, $host, $hand, $text) = @_;
78: if (! (main::isstaff($bot, $nick))) { return; }
79: if ($text =~ /^([-0-9A-Za-z\.]+)\s+([0-9A-Fa-f:\.]+)/) {
80: my ($hostname, $ip) = ($1, $2);
81: if (setdns($hostname, $ip)) {
82: main::putserv($bot, "PRIVMSG $nick :$hostname set to $ip");
83: } else {
84: main::putserv($bot, "PRIVMSG $nick :ERROR: failed to set DNS");
85: }
86: }
87: }
88: sub mdeldns {
89: my ($bot, $nick, $host, $hand, $text) = @_;
90: if (! (main::isstaff($bot, $nick))) { return; }
91: if ($text =~ /^([-0-9A-Za-z\.]+)/) {
92: if (setdns($text)) {
93: main::putserv($bot, "PRIVMSG $nick :$text deleted");
94: } else {
95: main::putserv($bot, "PRIVMSG $nick :ERROR: failed to delete DNS records");
96: }
97: }
98: }
99: sub mhost {
100: my ($bot, $nick, $host, $hand, $text) = @_;
101: if (! (main::isstaff($bot, $nick))) { return; }
102: if ($text =~ /^([-0-9A-Za-z:\.]{3,})/) {
103: my ($hostname, $version) = ($1, $2);
104: main::putserv($bot, "PRIVMSG $nick :".host($hostname));
105: }
106: }
107:
108: sub mnextdns {
109: my ($bot, $nick, $host, $hand, $text) = @_;
110: if (! (main::isstaff($bot, $nick))) { return; }
111: if ($text =~ /^([-0-9a-zA-Z]+)/) {
112: main::putserv($bot, "PRIVMSG $nick :$text set to ".nextdns($text));
113: }
114: }
115:
116: # Given filename, return a list of ipv6 addresses
117: sub readipv6s {
118: my ($filename) = @_;
119: my @lines = main::readarray($filename);
120: my @ipv6s;
121: foreach my $line (@lines) {
122: if ($line =~ /^\s*inet6 (alias )?([0-9a-f:]{4,}) [0-9]+\s*$/i) {
123: push(@ipv6s, $2);
124: } elsif ($line =~ /^\s*([0-9a-f:]{4,})\s*$/i) {
125: push(@ipv6s, $1);
126: }
127: }
128: return @ipv6s;
129: }
130:
131: # TODO: fix rdns request with buyvm's api, the ips must not skip 0s
132: # returns true upon success, false upon failure
133: sub setrdns {
134: my ($ip, $hostname) = @_;
135: my $stdout = `curl -d \"key=$key&hash=$hash&action=rdns&ip=$ip&rdns=$hostname\" https://manage.buyvm.net/api/client/command.php`;
136: if ($stdout !~ /success/) {
137: return 0;
138: }
139: return 1;
140: }
141: # set $domain to $ip if provided; otherwise, delete $domain
142: # returns true upon success, false upon failure
143: sub setdns {
144: my ($domain, $ip) = @_;
145: my $filename = "$zonedir/$hostname";
146: my $subdomain;
147: if ($domain =~ /^([a-zA-Z][-\.a-zA-Z0-9]+)\.$hostname$/) {
148: $subdomain = $1;
149: } else {
150: return 0;
151: }
152: my @lines = main::readarray($filename);
153: foreach my $line (@lines) {
154: # increment the zone's serial number
155: if ($line =~ /(\d{8})(\d{2})((\s+\d+){4}\s*\))/) {
156: my $date = main::date();
157: my $serial = 0;
158: if ($date <= $1) { $serial = $2+1; }
159: $line = $`.$date.sprintf("%02d",$serial).$3.$';
160: }
161: }
162: if ($ip =~ /^([0-9\.]+)$/) { # if IPv4
163: push(@lines, "$subdomain 3600 IN A $ip");
164: } elsif ($ip =~ /:/) { # if IPv6
165: push(@lines, "$subdomain 3600 IN AAAA $ip");
166: } elsif (!defined($ip)) { # delete records
167: @lines = grep !/\b$subdomain\s*3600\s*IN/, @lines;
168: }
169: # trailing newline necessary
170: main::writefile("$filename.bak", join("\n", @lines)."\n");
171: copy "$filename.bak", $filename;
172: if (system("doas -u _nsd nsd-control reload")) {
173: return 0;
174: } else {
175: return 1;
176: }
177: }
178:
179: # given hostname, return IP addresses; or given IP address, return hostname
180: sub host {
181: my ($name) = @_;
182: my @matches;
183: my @lines = split /\n/m, `host $name`;
184: if ($name =~ /^[0-9\.]+$/ or $name =~ /:/) { # IP address
185: foreach my $line (@lines) {
186: if ($line =~ /([\d\.]+).(in-addr|ip6).arpa domain name pointer (.*)/) {
187: push(@matches, $3);
188: }
189: }
190: } else { # hostname
191: foreach my $line (@lines) {
192: if ($line =~ /$name has (IPv6 )?address ([0-9a-fA-F\.:]+)/) {
193: push(@matches, $2);
194: }
195: }
196: }
197: return join(' ', @matches);
198: }
199:
200: # create A and AAAA records for subdomain, set the rDNS,
201: # and return the new ipv6 address
202: sub nextdns {
203: my ($subdomain) = @_;
204: my $ipv6 = shift(@ipv6s);
205: my $fqdn = "$subdomain.$hostname";
206: main::writefile($ipv6path, join("\n", @ipv6s));
207: if (setdns($fqdn, $ipv4) && setdns($fqdn, $ipv6) && setrdns($ipv6, $fqdn)) {
208: return "$ipv6";
209: }
210: return "false";
211: }
212:
213: 1; # MUST BE LAST STATEMENT IN FILE
CVSweb