[BACK]Return to DNS.pm CVS log [TXT][DIR] Up to [local] / botnow

Annotation of botnow/DNS.pm, Revision 1.2

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

CVSweb