#!/usr/bin/env perl # # Copyright (c) 2023 Mischa Peters # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # use 5.024; use strict; use warnings; use autodie; use Fcntl qw(:flock); use File::Basename; use File::Copy; use POSIX qw(strftime); use Net::IP; my $ipv4_range = new Net::IP("46.23.80.0/20"); my $ipv6_range = new Net::IP("2a03:6000::/29"); my $nsd = "/var/nsd/zones/reverse"; my $v6_zone = "2a03.6000"; my $default_ptr = "powered-by.openbsd.amsterdam"; my $workdir = dirname($0); my $serial; my $serial_prev; my $zonefile; my $match; my $replace; if (! $ARGV[0]) { print STDERR "usage: $0 \n"; exit 1; } my $client_ip = $ARGV[0]; my $ip = new Net::IP($client_ip); if ($ip->overlaps($ipv4_range)) { ($zonefile = $client_ip) =~ s/^((\d{1,3}\.){3})\d+$/${1}0/; $match = substr($client_ip, rindex($client_ip, '.')+1); } elsif ($ip->overlaps($ipv6_range)) { $zonefile = $v6_zone; $match = substr($ip->reverse_ip(), 0, 47); } $replace = "${match}\t\tIN\tPTR\t${default_ptr}."; if (qx(rlog ${nsd}/${zonefile} | grep 'locked by') =~ m/locked by/) { _log("$client_ip zone file locked, trying again later..."); next; } else { open my $fh_in, '<', "${nsd}/$zonefile"; open my $fh_out, '>', "${workdir}/zonefiles/$zonefile"; while (my $row = <$fh_in>) { chomp $row; if ($row =~ m/^\s*(\d+)\s*; serial$/) { $serial = $serial_prev = $1; my $timestamp = strftime ("%Y%m%d", localtime()) . "01"; if ($serial < $timestamp) { $serial = $timestamp; } else { $serial++; } $row =~ s/${serial_prev}/${serial}/; } if ($row =~ m/^${match}\s+IN\s+PTR\s+\S+( ;.*)?$/) { if ($1) { my $comment = $1; $row =~ s/^${match}\s+.*$/${replace}${comment}/; } else { $row =~ s/^${match}\s+.*$/${replace}/; } } print $fh_out "$row\n"; } close $fh_in; close $fh_out; (my $diff = qx(diff ${nsd}/${zonefile} ${workdir}/zonefiles/${zonefile} | wc -l)) =~ s/^\s*(.*?)\s*$/$1/; if ($diff == 8) { _log("$client_ip diff within limits ($diff), $serial_prev -> $serial"); copy("${nsd}/${zonefile}", "${workdir}/zonefiles-archive/${zonefile}-${serial}"); qx(co -q -l ${nsd}/${zonefile}); copy("${workdir}/zonefiles/${zonefile}", "${nsd}/${zonefile}"); qx(ci -q -u -m"updated for ${client_ip}" ${nsd}/${zonefile}); qx(rcctl reload nsd); qx(rdist -f /etc/Distfile) if (-r '/etc/Distfile'); my $protect = qx(pfctl -t protected -T delete $client_ip 2>&1); chomp $protect; qx(pfctl -t protected -T show > /etc/pf.protected); _log("$client_ip protected $protect"); } else { _log("$client_ip diff is outside limits ($diff)"); } } sub _log { my ($msg) = @_; open my $fh, '>>', '/var/log/ptrd.log'; flock $fh, LOCK_EX; print $fh sprintf("%s %s: %s \n", strftime("%b %d %H:%M:%S", localtime), basename($0), $msg); close $fh; }