version 1.1, 2021/05/15 15:12:32 |
version 1.2, 2021/07/20 17:40:08 |
Line 16 my $key = $conf{key}; |
|
Line 16 my $key = $conf{key}; |
|
my $hash = $conf{hash}; |
my $hash = $conf{hash}; |
my $hostname = $conf{hostname}; |
my $hostname = $conf{hostname}; |
my $verbose = $conf{verbose}; |
my $verbose = $conf{verbose}; |
my $ipv4 = $conf{ipv4}; |
my $ip4 = $conf{ip4}; |
|
my $ip6 = $conf{ip6}; |
|
my $ip6subnet = $conf{ip6subnet}; |
my $zonedir = $conf{zonedir}; |
my $zonedir = $conf{zonedir}; |
my $ipv6path = $conf{ipv6path}; |
|
my $hostnameif = $conf{hostnameif}; |
my $hostnameif = $conf{hostnameif}; |
# Validate ipv6s if it exists, otherwise load addresses from /etc/hostname.if |
|
my @ipv6s; |
|
if (!(-s "$ipv6path")) { |
|
print "No IPv6 addresses in $ipv6path, loading from $hostnameif...\n"; |
|
@ipv6s = readipv6s($hostnameif); |
|
} else { |
|
@ipv6s = readipv6s($ipv6path); |
|
} |
|
if (!@ipv6s) { die "No IPv6 addresses in $ipv6path or $hostnameif!"; } |
|
if (host($hostname) =~ /(\d+\.){3,}\d+/) { |
if (host($hostname) =~ /(\d+\.){3,}\d+/) { |
$ipv4 = $&; |
$ip4 = $&; |
} |
} |
main::cbind("msg", "-", "setrdns", \&msetrdns); |
main::cbind("msg", "-", "setrdns", \&msetrdns); |
main::cbind("msg", "-", "delrdns", \&mdelrdns); |
main::cbind("msg", "-", "delrdns", \&mdelrdns); |
Line 38 main::cbind("msg", "-", "setdns", \&msetdns); |
|
Line 30 main::cbind("msg", "-", "setdns", \&msetdns); |
|
main::cbind("msg", "-", "deldns", \&mdeldns); |
main::cbind("msg", "-", "deldns", \&mdeldns); |
main::cbind("msg", "-", "host", \&mhost); |
main::cbind("msg", "-", "host", \&mhost); |
main::cbind("msg", "-", "nextdns", \&mnextdns); |
main::cbind("msg", "-", "nextdns", \&mnextdns); |
|
main::cbind("msg", "-", "readip6s", \&mreadip6s); |
|
|
sub init { |
sub init { |
unveil("$ipv6path", "rwc") or die "Unable to unveil $!"; |
|
unveil("$zonedir", "rwc") or die "Unable to unveil $!"; |
unveil("$zonedir", "rwc") or die "Unable to unveil $!"; |
#dependencies for doas |
|
unveil("/usr/bin/doas", "rx") or die "Unable to unveil $!"; |
unveil("/usr/bin/doas", "rx") or die "Unable to unveil $!"; |
#dependencies for host |
|
unveil("/usr/bin/host", "rx") or die "Unable to unveil $!"; |
unveil("/usr/bin/host", "rx") or die "Unable to unveil $!"; |
|
unveil("$hostnameif", "rwc") or die "Unable to unveil $!"; |
} |
} |
|
|
|
# !setrdns 2001:bd8:: username.example.com |
sub msetrdns { |
sub msetrdns { |
my ($bot, $nick, $host, $hand, $text) = @_; |
my ($bot, $nick, $host, $hand, $text) = @_; |
if (! (main::isstaff($bot, $nick))) { return; } |
if (! (main::isstaff($bot, $nick))) { return; } |
if ($text =~ /^([0-9A-Fa-f:\.]{3,})\s+([-0-9A-Za-z\.]+)/) { |
if ($text =~ /^([0-9A-Fa-f:\.]{3,})\s+([-0-9A-Za-z\.]+)$/) { |
my ($ip, $hostname) = ($1, $2); |
my ($ip, $hostname) = ($1, $2); |
if (setrdns($ip, $hostname)) { |
if (setrdns($ip, $ip6subnet, $hostname)) { |
main::putserv($bot, "PRIVMSG $nick :$hostname set to $ip"); |
main::putserv($bot, "PRIVMSG $nick :$hostname set to $ip"); |
} else { |
} else { |
main::putserv($bot, "PRIVMSG $nick :ERROR: failed to set rDNS"); |
main::putserv($bot, "PRIVMSG $nick :ERROR: failed to set rDNS"); |
} |
} |
} |
} |
} |
} |
|
|
|
# !delrdns 2001:bd8:: |
sub mdelrdns { |
sub mdelrdns { |
my ($bot, $nick, $host, $hand, $text) = @_; |
my ($bot, $nick, $host, $hand, $text) = @_; |
if (! (main::isstaff($bot, $nick))) { return; } |
if (! (main::isstaff($bot, $nick))) { return; } |
if ($text =~ /^([0-9A-Fa-f:\.]{3,})$/) { |
if ($text =~ /^([0-9A-Fa-f:\.]{3,})$/) { |
my $ip = $1; |
my ($ip) = ($1); |
my $hostname = "notset"; |
if (delrdns($ip, $ip6subnet)) { |
if (setrdns($ip, $hostname)) { |
|
main::putserv($bot, "PRIVMSG $nick :$ip rDNS deleted"); |
main::putserv($bot, "PRIVMSG $nick :$ip rDNS deleted"); |
} else { |
} else { |
main::putserv($bot, "PRIVMSG $nick :ERROR: failed to set rDNS"); |
main::putserv($bot, "PRIVMSG $nick :ERROR: failed to set rDNS"); |
} |
} |
} |
} |
} |
} |
|
# !setdns username 1.2.3.4 |
sub msetdns { |
sub msetdns { |
my ($bot, $nick, $host, $hand, $text) = @_; |
my ($bot, $nick, $host, $hand, $text) = @_; |
if (! (main::isstaff($bot, $nick))) { return; } |
if (! (main::isstaff($bot, $nick))) { return; } |
if ($text =~ /^([-0-9A-Za-z\.]+)\s+([0-9A-Fa-f:\.]+)/) { |
if ($text =~ /^([-0-9A-Za-z\.]+)\s+([0-9A-Fa-f:\.]+)/) { |
my ($hostname, $ip) = ($1, $2); |
my ($name, $value) = ($1, $2); |
if (setdns($hostname, $ip)) { |
if ($value =~ /:/ and setdns($name, $hostname, "AAAA", $value)) { |
main::putserv($bot, "PRIVMSG $nick :$hostname set to $ip"); |
main::putserv($bot, "PRIVMSG $nick :$name.$hostname AAAA set to $value"); |
|
} elsif (setdns($name, $hostname, "A", $value)) { |
|
main::putserv($bot, "PRIVMSG $nick :$name.$hostname A set to $value"); |
} else { |
} else { |
main::putserv($bot, "PRIVMSG $nick :ERROR: failed to set DNS"); |
main::putserv($bot, "PRIVMSG $nick :ERROR: failed to set DNS"); |
} |
} |
} |
} |
} |
} |
|
|
|
# !deldns username |
sub mdeldns { |
sub mdeldns { |
my ($bot, $nick, $host, $hand, $text) = @_; |
my ($bot, $nick, $host, $hand, $text) = @_; |
if (! (main::isstaff($bot, $nick))) { return; } |
if (! (main::isstaff($bot, $nick))) { return; } |
if ($text =~ /^([-0-9A-Za-z\.]+)/) { |
if ($text =~ /^([-0-9A-Za-z\.]+)$/) { |
if (setdns($text)) { |
my ($name) = ($1); |
|
if (setdns($name, $hostname)) { |
main::putserv($bot, "PRIVMSG $nick :$text deleted"); |
main::putserv($bot, "PRIVMSG $nick :$text deleted"); |
} else { |
} else { |
main::putserv($bot, "PRIVMSG $nick :ERROR: failed to delete DNS records"); |
main::putserv($bot, "PRIVMSG $nick :ERROR: failed to delete DNS records"); |
} |
} |
} |
} |
} |
} |
|
|
|
# !host username |
sub mhost { |
sub mhost { |
my ($bot, $nick, $host, $hand, $text) = @_; |
my ($bot, $nick, $host, $hand, $text) = @_; |
if (! (main::isstaff($bot, $nick))) { return; } |
if (! (main::isstaff($bot, $nick))) { return; } |
if ($text =~ /^([-0-9A-Za-z:\.]{3,})/) { |
if ($text =~ /^([-0-9A-Za-z:\.]{3,})/) { |
my ($hostname, $version) = ($1, $2); |
my ($hostname) = ($1); |
main::putserv($bot, "PRIVMSG $nick :".host($hostname)); |
main::putserv($bot, "PRIVMSG $nick :".host($hostname)); |
} |
} |
} |
} |
|
|
|
# !nextdns username |
sub mnextdns { |
sub mnextdns { |
my ($bot, $nick, $host, $hand, $text) = @_; |
my ($bot, $nick, $host, $hand, $text) = @_; |
if (! (main::isstaff($bot, $nick))) { return; } |
if (! (main::isstaff($bot, $nick))) { return; } |
|
|
} |
} |
} |
} |
|
|
# Given filename, return a list of ipv6 addresses |
# !readip6s |
sub readipv6s { |
sub mreadip6s { |
|
my ($bot, $nick, $host, $hand, $text) = @_; |
|
if (! (main::isstaff($bot, $nick))) { return; } |
|
foreach my $line (readip6s($hostnameif)) { |
|
print "$line\n" |
|
} |
|
} |
|
|
|
# Return list of ipv6 addresses from filename |
|
sub readip6s { |
my ($filename) = @_; |
my ($filename) = @_; |
my @lines = main::readarray($filename); |
my @lines = main::readarray($filename); |
my @ipv6s; |
my @ipv6s; |
foreach my $line (@lines) { |
foreach my $line (@lines) { |
if ($line =~ /^\s*inet6 (alias )?([0-9a-f:]{4,}) [0-9]+\s*$/i) { |
if ($line =~ /^\s*inet6\s+(alias\s+)?([0-9a-f:]{4,})\s+[0-9]+\s*$/i) { |
push(@ipv6s, $2); |
push(@ipv6s, $2); |
} elsif ($line =~ /^\s*([0-9a-f:]{4,})\s*$/i) { |
} elsif ($line =~ /^\s*([0-9a-f:]{4,})\s*$/i) { |
push(@ipv6s, $1); |
push(@ipv6s, $1); |
|
|
return @ipv6s; |
return @ipv6s; |
} |
} |
|
|
# TODO: fix rdns request with buyvm's api, the ips must not skip 0s |
# set rdns of $ip6 to $hostname given $subnet |
# returns true upon success, false upon failure |
# return true on success; false on failure |
sub setrdns { |
sub setrdns { |
my ($ip, $hostname) = @_; |
my ($ip6, $subnet, $hostname) = @_; |
my $stdout = `curl -d \"key=$key&hash=$hash&action=rdns&ip=$ip&rdns=$hostname\" https://manage.buyvm.net/api/client/command.php`; |
my $digits = ip6full($ip6); |
if ($stdout !~ /success/) { |
$digits =~ tr/://d; |
return 0; |
my $reversed = reverse($digits); |
} |
my $origin = substr($reversed, 32-$subnet/4); |
return 1; |
$origin = join('.', split(//, $origin)).".ip6.arpa"; |
|
my $name = substr($reversed, 0, 32-$subnet/4); |
|
$name = join('.', split(//, $name)); |
|
# delete old PTR records, then set new one |
|
return setdns($name, $origin) && setdns($name, $origin, "PTR", $hostname); |
} |
} |
# set $domain to $ip if provided; otherwise, delete $domain |
# delete rdns of $ip6 given $subnet |
|
# return true on success; false on failure |
|
sub delrdns { |
|
my ($ip6, $subnet) = @_; |
|
return setrdns($ip6, $subnet); |
|
} |
|
|
|
# given $origin. create $name RR of $type and set to $value if provided; |
|
# if $value is missing, delete $domain |
# returns true upon success, false upon failure |
# returns true upon success, false upon failure |
sub setdns { |
sub setdns { |
my ($domain, $ip) = @_; |
my ($name, $origin, $type, $value) = @_; |
my $filename = "$zonedir/$hostname"; |
my $filename = "$zonedir/$origin"; |
my $subdomain; |
|
if ($domain =~ /^([a-zA-Z][-\.a-zA-Z0-9]+)\.$hostname$/) { |
|
$subdomain = $1; |
|
} else { |
|
return 0; |
|
} |
|
my @lines = main::readarray($filename); |
my @lines = main::readarray($filename); |
foreach my $line (@lines) { |
foreach my $line (@lines) { |
# increment the zone's serial number |
# increment the zone's serial number |
|
|
$line = $`.$date.sprintf("%02d",$serial).$3.$'; |
$line = $`.$date.sprintf("%02d",$serial).$3.$'; |
} |
} |
} |
} |
if ($ip =~ /^([0-9\.]+)$/) { # if IPv4 |
if (!defined($value)) { # delete records |
push(@lines, "$subdomain 3600 IN A $ip"); |
@lines = grep !/\b$name\s*3600\s*IN/, @lines; |
} elsif ($ip =~ /:/) { # if IPv6 |
} else { |
push(@lines, "$subdomain 3600 IN AAAA $ip"); |
push(@lines, "$name 3600 IN $type $value"); |
} elsif (!defined($ip)) { # delete records |
|
@lines = grep !/\b$subdomain\s*3600\s*IN/, @lines; |
|
} |
} |
# trailing newline necessary |
# trailing newline necessary |
main::writefile("$filename.bak", join("\n", @lines)."\n"); |
main::writefile("$filename.bak", join("\n", @lines)."\n"); |
|
|
return join(' ', @matches); |
return join(' ', @matches); |
} |
} |
|
|
|
# Return an ipv6 address with all zeroes filled in |
|
sub ip6full { |
|
my ($ip6) = @_; |
|
my $left = substr($ip6, 0, index($ip6, "::")); |
|
my $leftcolons = ($left =~ tr/://); |
|
$ip6 =~ s{::}{:}; |
|
my @quartets = split(':', $ip6); |
|
my $length = scalar(@quartets); |
|
for (my $n = 1; $n <= 8 - $length; $n++) { |
|
splice(@quartets, $leftcolons+1, 0, "0000"); |
|
} |
|
my @newquartets = map(sprintf('%04s', $_), @quartets); |
|
my $full = join(':',@newquartets); |
|
return $full; |
|
} |
|
# Returns the network part of the first IPv6 address (indicated by subnet) |
|
# with the host part of the second IPv6 address |
|
sub ip6mask { |
|
my ($ip6net, $subnet, $ip6host) = @_; |
|
my $netdigits = ip6full($ip6net); |
|
$netdigits =~ tr/://d; |
|
my $hostdigits = ip6full($ip6host); |
|
$hostdigits =~ tr/://d; |
|
my $digits = substr($netdigits,0,($subnet/4)).substr($hostdigits,($subnet/4)); |
|
my $ip6; |
|
for (my $n = 0; $n < 32; $n++) { |
|
if ($n > 0 && $n % 4 == 0) { |
|
$ip6 .= ":"; |
|
} |
|
$ip6 .= substr($digits,$n,1); |
|
} |
|
return $ip6; |
|
} |
|
sub randip6 { |
|
return join ':', map { sprintf '%04x', rand 0x10000 } (1 .. 8); |
|
} |
|
|
# create A and AAAA records for subdomain, set the rDNS, |
# create A and AAAA records for subdomain, set the rDNS, |
# and return the new ipv6 address |
# and return the new ipv6 address |
sub nextdns { |
sub nextdns { |
my ($subdomain) = @_; |
my ($subdomain) = @_; |
my $ipv6 = shift(@ipv6s); |
my $newip6 = $ip6; |
my $fqdn = "$subdomain.$hostname"; |
my @allip6s = readip6s($hostnameif); |
main::writefile($ipv6path, join("\n", @ipv6s)); |
while (grep(/$newip6/, @allip6s)) { |
if (setdns($fqdn, $ipv4) && setdns($fqdn, $ipv6) && setrdns($ipv6, $fqdn)) { |
$newip6 = ip6mask($ip6, $ip6subnet,randip6()); |
return "$ipv6"; |
} |
|
main::appendfile($hostnameif, "inet6 alias $newip6 48\n"); |
|
`doas ifconfig vio0 inet6 $newip6/48`; |
|
if (setdns($subdomain, $hostname, "A", $ip4) && setdns($subdomain, $hostname, "AAAA", $newip6) && setrdns($newip6, $ip6subnet, "$subdomain.$hostname")) { |
|
return "$newip6"; |
} |
} |
return "false"; |
return "false"; |
} |
} |