#!/usr/local/bin/perl -w
#------------------------------------------------------------------------------
# Usage:  mresolv2 [ -t timeout ] [ file... ]
#
# Reads IP addresses and issues non-blocking DNS queries.  Answers are
# sent to standard output in RR format:
#
# 1.1.168.192.in-addr.arpa.   86400  IN  PTR    host1.example.com.
#
# 1.2.168.192.in-addr.arpa.  86400  IN  CNAME  1.0-16.2.168.192.in-addr.arpa.
# 1.0-16.2.168.192.in-addr.arpa. 86400  IN  PTR  host2.example.com.
#
# Michael Fuhr
# mike@fuhr.org
#
# Copyright (c) 1999-2000 Michael Fuhr.  All rights reserved.  This program
# is free software; you can redistribute it and/or modify it under the same
# terms as Perl itself. 
#
# $Id: mresolv2,v 1.3 2000/11/24 17:14:40 mfuhr Exp mfuhr $
#------------------------------------------------------------------------------

use IO::Socket;
use IO::Select;
use Net::DNS;
use Getopt::Std;
use strict;

use constant DEFAULT_TIMEOUT    => 30;
use constant DEFAULT_NAMESERVER => "127.0.0.1";
use constant DEFAULT_PORT       => 53;
use constant DEFAULT_SRCADDR    => "0.0.0.0";
use constant DEFAULT_SRCPORT    => 0;

$| = 1;

#------------------------------------------------------------------------------
# Get the timeout or use a reasonable default.
#------------------------------------------------------------------------------

my $progname = $0;
$progname =~ s!.*/!!;

my %opt;
getopts("t:", \%opt);

my $timeout = defined($opt{"t"}) ? $opt{"t"} : DEFAULT_TIMEOUT;
die "$progname: invalid timeout: $timeout\n" if $timeout < 0;

#------------------------------------------------------------------------------
# Create a socket pointing at the default nameserver.
#------------------------------------------------------------------------------

my $sock = IO::Socket::INET->new(Proto => "udp");
die "couldn't create socket: $!" unless $sock;

my $sockaddr_src = sockaddr_in(DEFAULT_SRCPORT, inet_aton(DEFAULT_SRCADDR));
$sock->bind($sockaddr_src) or die "can't bind socket: $!";

my $res = Net::DNS::Resolver->new;
my $ns = $res->nameservers ? ($res->nameservers)[0] : DEFAULT_NAMESERVER;
my $sockaddr_dst = sockaddr_in(DEFAULT_PORT, inet_aton($ns));

my $sel = IO::Select->new;
$sel->add($sock);

#------------------------------------------------------------------------------
# Read IP addresses and send queries.
#------------------------------------------------------------------------------

my $pending = 0;

while (<>) {
    chomp;
    next unless /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;

    my $ip = "$4.$3.$2.$1.in-addr.arpa";

    my $packet = Net::DNS::Packet->new($ip, "PTR");
    $sock->send($packet->data, 0, $sockaddr_dst) or die "send: $ip: $!";
    ++$pending;

    while ($sel->can_read(0)) {
	--$pending;
        my $buf = "";
        $sock->recv($buf, 512) or die "recv: $!";
        next unless $buf;

        my $ans = Net::DNS::Packet->new(\$buf);
        next unless $ans;

        foreach my $rr ($ans->answer) {
	    $rr->print;

	    if ($rr->type eq "CNAME") {
		my $cname = $rr->cname;
		my $packet = Net::DNS::Packet->new($cname, "PTR");
                $sock->send($packet->data, 0, $sockaddr_dst)
		    or die "send: $cname: $!";
                ++$pending;
	    }
	}
    }
}

#------------------------------------------------------------------------------
# Wait for outstanding requests until all are complete or we've timed out.
#------------------------------------------------------------------------------

while ($pending > 0 && $sel->can_read($timeout)) {
    --$pending;
    my $buf = "";
    $sock->recv($buf, 512) or die "recv: $!";
    next unless $buf;

    my $ans = Net::DNS::Packet->new(\$buf);
    next unless $ans;

    foreach my $rr ($ans->answer) {
        $rr->print;

        if ($rr->type eq "CNAME") {
	    my $cname = $rr->cname;
            my $packet = Net::DNS::Packet->new($cname, "PTR");
            $sock->send($packet->data, 0, $sockaddr_dst)
		or die "send: $cname: $!";
            ++$pending;
        }
    }
}
