#!/usr/bin/perl
#
# $Id: Fingerprint.pm,v 1.17 2005/09/05 13:33:36 jakob Exp $
#
# Copyright (c) 2003,2004,2005 Roy Arends & Jakob Schlyter.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. The name of the authors may not be used to endorse or promote products
#    derived from this software without specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

require 5.6.0;

package Net::DNS::Fingerprint;

use strict;
use warnings;
use Net::DNS 0.42;

our $VERSION = "0.9.3";

my %default = (
    source   => undef,
    timeout  => 5,
    retry    => 1,
    forcetcp => 0,
    debug    => 0,
    qversion => 0,
    qchaos   => 0,
    separator => " ",
);

my $versionlength = 40;

my @qy = ("0,IQUERY,0,0,1,0,0,0,NOERROR,0,0,0,0",
          "0,NS_NOTIFY_OP,0,0,0,0,0,0,NOERROR,0,0,0,0",
          "0,QUERY,0,0,0,0,0,0,NOERROR,0,0,0,0",
          "0,IQUERY,0,0,0,0,1,1,NOERROR,0,0,0,0",
          "0,QUERY,0,0,0,0,0,0,NOTIMP,0,0,0,0",
          "0,IQUERY,1,0,1,1,1,1,NOERROR,0,0,0,0",
          "0,UPDATE,0,0,0,1,0,0,NOERROR,0,0,0,0",
          "0,QUERY,1,1,1,1,1,1,NOERROR,0,0,0,0", 
          "0,QUERY,0,0,0,0,0,1,NOERROR,0,0,0,0",
         );

my %initrule = (header => $qy[0], query  => ". IN A", );
my @iq = ("1,IQUERY,0,0,1,0,0,0,FORMERR,0,0,0,0",   # iq0
          "1,IQUERY,0,0,1,0,0,0,FORMERR,1,0,0,0",   # iq1
          "1,IQUERY,0,0,1,0,0,0,NOTIMP,0,0,0,0",    # iq2
          "1,IQUERY,0,0,1,0,0,0,NOTIMP,1,0,0,0",    # iq3
          "1,IQUERY,0,0,1,1,0,0,FORMERR,0,0,0,0",   # iq4
          "1,IQUERY,0,0,1,1,0,0,NOTIMP,0,0,0,0",    # iq5 
          "1,IQUERY,0,0,1,1,0,0,NOTIMP,1,0,0,0",    # iq6
          "1,IQUERY,1,0,1,0,0,0,NOTIMP,1,0,0,0",    # iq7
          "1,QUERY,1,0,1,0,0,0,NOTIMP,1,0,0,0",
          "1,QUERY,0,0,0,0,0,0,NOTIMP,0,0,0,0",
          "1,IQUERY,0,0,1,1,0,0,FORMERR,1,0,0,0",   # iq10
	  "1,NS_NOTIFY_OP,0,0,0,0,0,0,FORMERR,1,0,0,0",
          "1,NS_NOTIFY_OP,0,0,0,0,0,0,NOTIMP,0,0,0,0",
          "1,NS_NOTIFY_OP,0,0,0,0,0,0,NOTIMP,1,0,0,0",
          "1,NS_NOTIFY_OP,0,0,0,0,0,0,NXDOMAIN,1,0,0,0",
          "1,NS_NOTIFY_OP,0,0,0,0,0,0,REFUSED,1,0,0,0",
          "1,NS_NOTIFY_OP,0,0,0,0,0,0,SERVFAIL,1,0,0,0",
          "1,NS_NOTIFY_OP,0,0,0,1,0,0,FORMERR,1,0,0,0",
          "1,NS_NOTIFY_OP,0,0,0,1,0,0,NOTIMP,0,0,0,0",
          "1,NS_NOTIFY_OP,0,0,0,1,0,0,NOTIMP,1,0,0,0",
          "1,NS_NOTIFY_OP,0,0,0,1,0,0,REFUSED,1,0,0,0", # iq20
          "1,NS_NOTIFY_OP,0,0,0,1,0,0,SERVFAIL,1,0,0,0",
          "1,NS_NOTIFY_OP,1,0,0,0,0,0,NOTIMP,1,0,0,0",
          "1,QUERY,1,0,0,0,0,0,NOTIMP,1,0,0,0",
          "1,NS_NOTIFY_OP,1,0,0,0,0,0,SERVFAIL,1,0,0,0",
	  "1,IQUERY,0,0,0,0,1,1,NOTIMP,0,0,0,0",
          "1,IQUERY,0,0,0,0,0,0,NOTIMP,0,0,0,0",
          "1,IQUERY,0,0,1,1,1,1,FORMERR,0,0,0,0",
          "1,IQUERY,1,0,1,1,1,1,FORMERR,0,0,0,0",
	  "1,QUERY,.,0,1,.,.,.,NOTIMP,.+,.+,.+,.+",
          "1,QUERY,.,0,1,.,.,.,.+,.+,.+,.+,.+", #iq30
          "1,QUERY,0,0,.,.,0,0,NXDOMAIN,1,0,0,0",    
	  "1,QUERY,0,0,.,.,0,0,FORMERR,1,0,0,0",
          "1,UPDATE,0,0,0,0,0,0,NOTIMP,0,0,0,0",
          "1,UPDATE,0,0,0,1,0,0,NOTIMP,0,0,0,0",
          "1,QUERY,0,0,1,0,0,0,NOERROR,1,0,0,0",
          "1,QUERY,1,1,1,1,1,1,NOTIMP,1,0,0,0",
          "1,QUERY,0,0,0,0,0,0,NOERROR,1,0,.+,0",
          "1,QUERY,0,0,1,0,0,0,FORMERR,1,0,0,0",
          "1,IQUERY,0,0,1,0,1,1,NOTIMP,1,0,0,0",
          "1,IQUERY,0,0,0,1,1,1,REFUSED,1,0,0,0", #iq40
          "1,UPDATE,0,0,0,1,0,0,REFUSED,1,0,0,0",
          "1,IQUERY,0,0,0,1,1,1,FORMERR,0,0,0,0",
          "1,IQUERY,0,0,0,1,0,0,NOTIMP,0,0,0,0",
          "1,QUERY,1,0,1,0,0,0,FORMERR,1,0,0,0",
          "1,UPDATE,0,0,0,0,0,0,FORMERR,1,0,0,0",
          "1,UPDATE,0,0,0,0,0,0,FORMERR,0,0,0,0",
          "1,QUERY,0,0,1,0,0,0,FORMERR,0,0,0,0",
          "1,QUERY,0,0,1,0,0,0,SERVFAIL,1,0,0,0", #iq48
          "1,QUERY,1,0,1,0,0,0,NXDOMAIN,1,0,1,0",
          "1,QUERY,0,0,1,0,0,0,REFUSED,1,0,0,0", #iq50
          "1,QUERY,0,0,1,0,0,0,NOERROR,1,1,0,0",
          "1,IQUERY,0,0,1,0,0,0,REFUSED,0,0,0,0",
          "1,QUERY,0,0,0,0,0,0,FORMERR,0,0,0,0",
          "1,QUERY,0,0,1,1,1,0,NOERROR,1,0,1,0",
          "1,QUERY,0,0,1,1,0,0,NOERROR,1,0,1,0",
          "1,QUERY,0,0,1,0,1,0,NOERROR,.+,.+,.+,.+", 
          "1,QUERY,0,0,1,0,0,0,.+,.+,.+,.+,.+",
          "1,QUERY,1,0,1,0,0,0,NOERROR,1,1,0,0",
          "1,QUERY,0,0,1,1,0,0,SERVFAIL,1,0,0,0", 
          "1,QUERY,1,0,1,1,0,0,NOERROR,1,1,0,0", #iq60
          "1,QUERY,0,0,1,1,0,0,REFUSED,1,0,0,0",
          "1,QUERY,0,0,0,0,0,0,NOTIMP,1,0,0,0",
          "1,QUERY,1,0,1,1,0,0,NOERROR,1,0,1,0",
	  "1,IQUERY,0,0,1,1,1,1,NOTIMP,0,0,0,0",
          "1,UPDATE,0,0,0,0,0,0,REFUSED,0,0,0,0",
          "1,IQUERY,0,0,0,1,1,1,NOTIMP,1,0,0,0",
          "1,IQUERY,0,0,0,1,0,0,NOTIMP,1,0,0,0",
          "1,QUERY,0,1,1,1,1,1,NOERROR,1,0,.,0",
          "1,QUERY,0,1,1,1,0,1,NOERROR,1,0,.,0",
          "1,IQUERY,0,0,1,0,0,0,REFUSED,1,0,0,0", #iq70
          "1,IQUERY,1,0,1,1,1,1,NOTIMP,1,0,0,0",
          "1,IQUERY,0,0,1,0,0,0,NOERROR,1,0,0,0",
	  "1,QUERY,1,0,1,1,0,0,NOERROR,1,0,0,0",
          "1,IQUERY,1,0,1,1,0,0,NXDOMAIN,1,0,0,0",
          "1,UPDATE,0,0,0,1,0,0,FORMERR,0,0,0,0",
	  "1,IQUERY,1,0,1,0,0,0,NXDOMAIN,1,0,0,0",
          "1,QUERY,0,0,1,1,0,0,FORMERR,1,0,0,0",
	  "1,QUERY,0,0,0,1,0,0,SERVFAIL,1,0,0,0",
	  "1,QUERY,0,0,1,1,0,0,NOERROR,1,1,0,0",
	  "1,IQUERY,1,0,1,0,0,0,NOERROR,1,0,0,0", #iq80
	  "1,IQUERY,1,0,1,1,0,0,NOTIMP,1,0,0,0",
          "1,QUERY,0,0,1,1,0,0,NOERROR,1,0,0,0",
          "1,QUERY,1,0,1,1,0,0,NOERROR,1,1,1,.+",
          "1,QUERY,0,0,1,1,0,0,REFUSED,0,0,0,0",
          "1,UPDATE,0,0,0,1,0,0,NOTIMP,1,0,0,0",
          "1,QUERY,1,0,0,1,0,0,NXDOMAIN,1,0,0,0",
          "1,QUERY,0,0,0,1,0,0,NOTIMP,0,0,0,0",
          "1,QUERY,0,0,0,0,0,0,REFUSED,1,0,0,0",
          "1,QUERY,1,0,1,1,0,0,NXDOMAIN,1,0,0,0",
          "1,QUERY,1,0,0,0,0,0,NOERROR,1,1,0,0", #iq90
          "1,IQUERY,1,0,1,1,0,1,NOTIMP,1,0,0,0",
          "1,QUERY,0,0,0,1,0,0,NOTIMP,1,0,0,0",
          "1,QUERY,0,0,1,0,0,1,SERVFAIL,1,0,0,0",
);

my @ruleset = (
    { fingerprint => "query timed out" , header => $qy[0],  query => "com. IN A", ruleset => [
          { fingerprint => "query timed out", header => $qy[7], query => ". CH A", ruleset => [
                 { fingerprint => "query timed out", header => $qy[6], query => ". IN A", ruleset => [
                        { fingerprint => $iq[38], result => { vendor => "Digital Lumber", product => "Oak DNS", version =>"" },  qv => "version.oak",}, 
                        { fingerprint => "query timed out", result => "TIMEOUT",}, 
                        { fingerprint => ".+", state => "q0tq0tq7tq6r?", }, ]
                 },
                 { fingerprint => $iq[35], result => { vendor => "XBILL", product => "jnamed (dnsjava)", version => "" }, },
                 { fingerprint => $iq[36], result => { vendor => "menandmice", product => "QuickDNS", version => ""}, }, 
                 { fingerprint => $iq[37], result => { vendor => "unknown", product => "NonSequitur DNS", version => ""}, },
                 { fingerprint => ".+", state => "q0tq0tq7r?", }, ]  
          },
	  { fingerprint => $iq[35], result => { vendor => "eNom", product => "eNom DNS", version =>""}, },
          { fingerprint => ".+", state => "q0tq0r?", },]
    },
 
    { fingerprint => $iq[0], header => $qy[1], query=> "jjjjjjjjjjjj IN A", ruleset => [
	  { fingerprint => $iq[12], result => { vendor => "ISC", product => "BIND", version => "8.4.1-p1" },  qv => "version.bind",},                         
	  { fingerprint => $iq[13], result => { vendor => "ISC", product => "BIND", version => "8 plus root server modifications"},  qv => "version.bind",}, 
          { fingerprint => $iq[15], result => { vendor => "Cisco", product => "CNR", version => ""}, },
	  { fingerprint => $iq[16], header => $qy[2], query => "hostname.bind CH TXT", ruleset => [
                  { fingerprint => $iq[58], result => { vendor => "ISC", product => "BIND", version => "8.3.0-RC1 -- 8.4.4"},  qv => "version.bind",},     
                  { fingerprint => $iq[50], result => { vendor => "ISC", product => "BIND", version => "8.3.0-RC1 -- 8.4.4"},  qv => "version.bind",},    
                  { fingerprint => $iq[48], result => { vendor => "ISC", product => "BIND", version => "8.2.2-P3 -- 8.3.0-T2A"},  qv => "version.bind",},
                  { fingerprint => ".+", state => "q0r0q1r16q2r?", },]
          },
          { fingerprint => ".+", state => "q0r0q1r?", },]
    },

    { fingerprint => $iq[1], header => $qy[2], query => ". IN IXFR", ruleset => [
          { fingerprint => $iq[31], result => { vendor => "Microsoft", product => "Windows DNS", version => "2000" }, },				
          { fingerprint => $iq[32], result => { vendor => "Microsoft", product => "Windows DNS", version => "NT4" }, },
          { fingerprint => $iq[50], result => { vendor => "Microsoft", product => "Windows DNS", version => "2003"}, },
          { fingerprint => ".+", state => "q0r1q2r?", }, ]
    },

    { fingerprint => $iq[2], header => $qy[1], ruleset => [
	  { fingerprint => $iq[11], result => { vendor => "ISC", product => "BIND", version => "9.2.3rc1 -- 9.6.1-P1" }, qv => "version.bind",},    
	  { fingerprint => $iq[12], header => $qy[3], ruleset => [
		{ fingerprint => $iq[25], header => $qy[6], ruleset => [
                      { fingerprint => $iq[33], result => { vendor => "bboy", product => "MyDNS", version => "" },},				
                      { fingerprint => $iq[34], header => $qy[2],  query  => "012345678901234567890123456789012345678901234567890123456789012.012345678901234567890123456789012345678901234567890123456789012.012345678901234567890123456789012345678901234567890123456789012.0123456789012345678901234567890123456789012345678901234567890. IN A", ruleset => [
                           { fingerprint => $iq[47], result => { vendor => "NLnetLabs", product => "NSD", version => "1.0.3 -- 1.2.1 (uncertain)"}, qv => "version.server", }, 
                           { fingerprint => $iq[48], header => $qy[2],  query  => "hostname.bind CH TXT", ruleset => [
                                     { fingerprint => $iq[50], result => { vendor => "NLnetLabs", product => "NSD", version => "1.2.2 (uncertain)" }, qv => "version.server", },
				     { fingerprint => $iq[51], header => $qy[8], query => ". IN A", ruleset => [
                                           { fingerprint => $iq[93], result => { vendor => "NLnetLabs", product => "NSD", version => "1.2.3 -- 2.1.2 (uncertain)" } , qv => "version.server",  },
                                           { fingerprint => $iq[48], result => { vendor => "NLnetLabs", product => "NSD", version => "2.1.3 (uncertain)" }, qv => "version.server",  }, 
                                           { fingerprint => ".+", state => "q0r2q1r12q3r25q6r34q2r48q2r51q8r?", }, ]
                                     },
                                     { fingerprint => ".+", state => "q0r2q1r12q3r25q6r34q2r48q2r?", }, ]
                           },
                           { fingerprint => $iq[49], header => $qy[2],  query  => "hostname.bind CH TXT", ruleset => [
                                     { fingerprint => $iq[50], result => { vendor => "NLnetLabs", product => "NSD", version => "1.2.2 [root] (uncertain)"} , qv => "version.server",  },
                                     { fingerprint => $iq[51], result => { vendor => "NLnetLabs", product => "NSD", version => "1.2.3 [root] (uncertain)"}, qv => "version.server", }, 
                                     { fingerprint => ".+", state => "q0r2q1r12q3r25q6r34q2r49q2r?", }, ]
                           },
                           { fingerprint => $iq[53], result => { vendor => "NLnetLabs", product=>"NSD", version => "1.0.2 (uncertain)"}, qv => "version.server", },
                           { fingerprint => ".+", state => "q0r2q1r12q3r25q6r34q2a?", },]
                      },
                      { fingerprint => ".+", state => "q0r2q1r12q3r25q6r?", },]
                },
		{ fingerprint => $iq[26], result => { vendor => "VeriSign", product => "ATLAS", version => ""},}, 
                { fingerprint => ".+", state => "q0r2q1r12q3r?", },] 
          },
	  { fingerprint => $iq[15],  header => $qy[6], ruleset => [
               { fingerprint => $iq[45], result => { vendor => "Nominum", product =>"ANS", version =>""}, qv => "version.bind",},
               { fingerprint => $iq[65], result => { vendor => "ISC", product => "BIND", version => "9.2.3rc1 -- 9.6.1-P1" },  qv => "version.bind",},
               { fingerprint => $iq[46], header => $qy[7], ruleset => [
                      { fingerprint => $iq[56], result => { vendor => "ISC", product => "BIND", version => "9.0.0b5 -- 9.0.1" }, qv => "version.bind",},
                      { fingerprint => $iq[57], result => { vendor => "ISC", product => "BIND", version => "9.1.0 -- 9.1.3" }, qv => "version.bind",}, 
                      { fingerprint => ".+", state => "q0r2q1r15q6r46q7r?", }, ]
               },
               { fingerprint => ".+", state => "q0r2q1r15q6r?", },]
          },
	  { fingerprint => $iq[16], header => $qy[4], ruleset => [
                { fingerprint => $iq[29], result => { vendor => "ISC", product => "BIND", version => "9.2.0a1 -- 9.2.0rc3"}, qv => "version.bind",},
                { fingerprint => $iq[30],  header => $qy[0], query  => ". A CLASS0" , ruleset => [
                        { fingerprint => $iq[2], result => { vendor=>"ISC", product => "BIND", version =>"9.2.0rc7 -- 9.2.2-P3"}, qv => "version.bind", },
                        { fingerprint => $iq[0], result => { vendor=>"ISC", product => "BIND", version =>"9.2.0rc4 -- 9.2.0rc6"}, qv => "version.bind", },
                        { fingerprint => ".+", result => { vendor => "ISC", product => "BIND", version =>"9.2.0rc4 -- 9.2.2-P3"}, qv => "version.bind", }, ]
                },
                { fingerprint => ".+", state => "q0r2q1r16q4r?", },]
          },
          { fingerprint => ".+", state => "q0r2q1r?", }, ]
    },
    
    { fingerprint => $iq[3], header => $qy[1], ruleset => [
          { fingerprint => "query timed out", header => $qy[5], ruleset => [
                { fingerprint => $iq[3], result => { vendor => "sourceforge", product =>"Dents", version =>""}, qv => "version.bind", },
                { fingerprint => $iq[81], result => { vendor => "Microsoft", product => "Windows DNS", version => "2003" },},
                { fingerprint => $iq[91], result => { vendor => "Microsoft", product => "Windows DNS", version => "2003" },},
                { fingerprint => ".+", state => "q0r3q1tq5r?", }, ]

          },     
	  { fingerprint => $iq[14], result => { vendor => "UltraDNS", product => "", version =>"v2.7.0.2 -- 2.7.3"}, qv => "version.bind", }, 
          { fingerprint => $iq[13], header => $qy[5], ruleset => [
                { fingerprint => $iq[39], result => { vendor => "pliant", product => "DNS Server", version =>""},},
                { fingerprint => $iq[7], result => { vendor => "JHSOFT", product => "simple DNS plus", version =>""}, }, 
		{ fingerprint => $iq[71], header => $qy[6], ruleset => [
                        { fingerprint => $iq[41], result => { vendor =>"Netnumber", product =>"ENUM server", version =>""}, },
                        { fingerprint => $iq[85], result => { vendor =>"Raiden", product => "DNSD", version => ""}, }, ]
                },
                { fingerprint => ".+", state => "q0r3q1r13q5r?", }, ]
                },
          { fingerprint => ".+", state => "q0r3q1r?", }, ]
    },
    
    { fingerprint => $iq[4], header => $qy[1], query=> "jjjjjjjjjjjj IN A", ruleset => [
          { fingerprint => $iq[17], result => { vendor => "ISC", product => "BIND", version =>"9.0.0b5 -- 9.0.1 [rcursion enabled]"},qv => "version.bind", },
	  { fingerprint => $iq[18], header => $qy[5], query=> ". IN A" , ruleset => [
                { fingerprint => $iq[27], result => { vendor => "ISC", product => "BIND", version => "4.9.3 -- 4.9.11"}, qv => "version.bind", },
                { fingerprint => $iq[28], result => { vendor => "ISC", product => "BIND", version => "4.8 -- 4.8.3"}, }, 
                { fingerprint => ".+", state => "q0r4q1r18q5r?", }, ]
          },
          { fingerprint => $iq[19], result => {vendor => "ISC", product =>"BIND", version => "8.2.1 [recursion enabled]"}, qv => "version.bind", },           
	  { fingerprint => $iq[20], header => $qy[3], query=> ". IN A", ruleset => [
                { fingerprint => $iq[42], result => {vendor => "ISC", product =>"BIND", version =>"8.1-REL -- 8.2.1-T4B [recursion enabled]"}, qv => "version.bind", }, 
                { fingerprint => ".+", state => "q0r4q1r20q3r?", },]
          },
	  { fingerprint => $iq[21], header => $qy[2], query => "hostname.bind CH TXT", ruleset => [
                { fingerprint => $iq[60], result => {vendor =>"ISC", product => "BIND", version => "8.3.0-RC1 -- 8.4.4 [recursion enabled]"},  qv => "version.bind",},
                { fingerprint => $iq[59], header => $qy[7], query=> ". IN A", ruleset => [
			 { fingerprint => $iq[68], result => {vendor =>"ISC", product => "BIND", version => "8.1-REL -- 8.2.1-T4B [recursion enabled]"}, qv => "version.bind", },
                         { fingerprint => $iq[69], result => {vendor =>"ISC", product => "BIND", version => "8.2.2-P3 -- 8.3.0-T2A [recursion enabled]"},  qv => "version.bind",},
                         { fingerprint => "connection failed", result => { vendor =>"Runtop", product => "dsl/cable", version =>""},},
                	 { fingerprint => ".+", state => "q0r4q1r21q2r59q7r?", },]
                },

	        { fingerprint => $iq[58], result => {vendor => "ISC", product =>"BIND", version => "8.3.0-RC1 -- 8.4.4 [recursion local]"},  qv => "version.bind",},
                { fingerprint => $iq[50], result => {vendor => "ISC", product =>"BIND", version => "8.3.0-RC1 -- 8.4.4 [recursion local]"},  qv => "version.bind",},
		{ fingerprint => $iq[61], result => {vendor => "ISC", product =>"BIND", version => "8.3.0-RC1 -- 8.4.4 [recursion local]"},  qv => "version.bind",},
                { fingerprint => $iq[48], result => {vendor => "ISC", product =>"BIND", version => "8.2.2-P3 -- 8.3.0-T2A [recursion local]"},  qv => "version.bind",},
                { fingerprint => ".+", state => "q0r4q1r21q2r?", },]
          },
          { fingerprint => ".+", state => "q0r4q1r?", }, ]
    },

    { fingerprint => $iq[5], header => $qy[1], ruleset => [
          { fingerprint => $iq[11], result => { vendor => "ISC", product => "BIND", version => "9.2.3rc1 -- 9.6.1-P1", option => "recursion enabled,split view" }, qv => "version.bind",},
	  { fingerprint => $iq[17], result => {vendor => "ISC", product =>"BIND", version => "9.2.3rc1 -- 9.6.1-P1 [recursion enabled]"}, qv => "version.bind",},
          { fingerprint => $iq[18], header => $qy[5], ruleset => [
		{ fingerprint => $iq[5], header => $qy[7], query  => ". IN A", ruleset => [
	             { fingerprint => $iq[84], result => {vendor => "Nominum", product =>"CNS", version => ""}, qv => "version.bind",},
                     { fingerprint => $iq[59], result => {vendor => "Mikrotik", product =>"dsl/cable", version => ""}, },
                     { fingerprint => $iq[82], result => {vendor => "Mikrotik", product =>"dsl/cable", version => ""}, },
                     { fingerprint => ".+", state => "q0r5q1r18q5r5q7r?", }, ]
                },
                { fingerprint => $iq[64], result => "unknown, smells like old BIND 4", },
                { fingerprint => ".+", state => "q0r5q1r18q5r?", }, ]
          }, 
	  { fingerprint => $iq[20], header => $qy[7], ruleset => [
                { fingerprint => $iq[54], result => {vendor => "ISC", product =>"BIND", version => "9.0.0b5 -- 9.0.1 [recursion enabled]"}, qv => "version.bind",},
                { fingerprint => $iq[55], result => {vendor => "ISC", product =>"BIND", version => "9.1.0 -- 9.1.3 [recursion enabled]"}, qv => "version.bind",},
                { fingerprint => $iq[63], result => {vendor => "ISC", product =>"BIND", version => "4.9.3 -- 4.9.11 [recursion enabled]"}, qv => "version.bind",},
                { fingerprint => $iq[61], result => {vendor => "ISC", product =>"BIND", version => "9.0.0b5 -- 9.1.3 [recursion local]"}, qv => "version.bind",},
                { fingerprint => ".+", state => "q0r5q1r20q7r?", }, ]
          },   
	  { fingerprint => $iq[21], header => $qy[4], ruleset => [
	        { fingerprint => "query timed out", result => {vendor => "ISC", product =>"BIND", version => "9.2.0a1 -- 9.2.2-P3 [recursion enabled]"}, qv => "version.bind", },
                { fingerprint => $iq[29], result => {vendor => "ISC", product =>"BIND", version => "9.2.0a1 -- 9.2.0rc3 [recursion enabled]"}, qv => "version.bind", },
		{ fingerprint => $iq[61], header => $qy[0], query  => ". A CLASS0" , ruleset => [
                        { fingerprint => $iq[2], result => {vendor => "ISC", product =>"BIND", version => "9.2.0rc7 -- 9.2.2-P3 [recursion local]"}, qv => "version.bind", },
                        { fingerprint => $iq[0], result => {vendor => "ISC", product =>"BIND", version => "9.2.0a1 -- 9.2.0rc6 [recursion local]"}, qv => "version.bind", },
                        { fingerprint => ".+", result => {vendor => "ISC", product =>"BIND", version => "9.2.0a1 -- 9.2.2-P3 [recursion local]"}, qv => "version.bind", }, ]
                },
                { fingerprint => $iq[30], header => $qy[0], query  => ". A CLASS0" , ruleset => [
		 	{ fingerprint => $iq[2], result => {vendor => "ISC", product =>"BIND", version => "9.2.0rc7 -- 9.2.2-P3 [recursion enabled]"}, qv => "version.bind", },
                        { fingerprint => $iq[0], result => {vendor => "ISC", product =>"BIND", version => "9.2.0rc4 -- 9.2.0rc6 [recursion enabled]"}, qv => "version.bind", },
                        { fingerprint => ".+", result => {vendor => "ISC", product =>"BIND", version => "9.2.0rc4 -- 9.2.2-P3 [recursion enabled]"}, qv => "version.bind", }, ] 
                },
                { fingerprint => ".+", state => "q0r5q1r21q4r?", }, ]
          }, 
          { fingerprint => ".+", state => "q0r5q1r?", }, ]
    },
	 
    { fingerprint => $iq[6], header => $qy[1], ruleset => [
	  { fingerprint => $iq[15], result => {vendor => "incognito", product =>"DNS commander", version => "v2.3.1.1 -- 4.0.5.1"}, qv => "version.bind",  },
	  { fingerprint => $iq[19], header => $qy[3], ruleset => [
            	{ fingerprint => $iq[66], result => {vendor => "vermicelli", product =>"totd", version => ""}, },
                { fingerprint => $iq[67], result => {vendor => "JHSOFT", product =>"simple DNS plus", version => "[recursion enabled]"}, }, 
                { fingerprint => ".+", state => "q0r6q1r19q3r?", }, ]
          },
          { fingerprint => ".+", state => "q0r6q1r?", }, ]
    },
    
    { fingerprint => $iq[7], header => $qy[1], ruleset => [
	  { fingerprint => $iq[22], result => {vendor => "PowerDNS", product =>"PowerDNS", version => "2.9.4 -- 2.9.11"}, qv => "version.bind", },
          { fingerprint => $iq[24], result => {vendor => "PowerDNS", product =>"PowerDNS", version => "2.8 -- 2.9.3"}, qv => "version.bind", },
	  { fingerprint => ".+", state => "q0r7q1r?", }, ]
    },

    { fingerprint => $iq[8], header => $qy[1], ruleset => [
          { fingerprint => $iq[23], header => $qy[2] , query => ". CH A", ruleset => [
                { fingerprint => "query timed out", result => { vendor => "DJ Bernstein", product => "TinyDNS", version => "1.04"} ,},
                { fingerprint => $iq[32], result => {vendor => "DJ Bernstein", product => "TinyDNS", version => "1.05"} ,}, 
                { fingerprint => ".+", state => "q0r8q1r23q2r?",},]
          },
	  { fingerprint => ".+", state => "q0r8q1r?", }, ]
    },
    
    { fingerprint => $iq[9], header => $qy[1], ruleset => [
	  { fingerprint => $iq[9], result => { vendor => "Sam Trenholme", product =>"MaraDNS", version => ""}, qv => "erre-con-erre-cigarro.maradns.org"}, 
	  { fingerprint => ".+", state => "q0r9q1r?", }, ]
    },

    { fingerprint => $iq[10], result => { vendor => "Microsoft", product =>"?", version => ""}, },
    { fingerprint => $iq[26], result => { vendor => "Meilof Veeningen", product =>"Posadis", version =>""}, },
    { fingerprint => $iq[43], header => $qy[6], ruleset => [
                { fingerprint => $iq[34], result => { vendor => "Paul Rombouts", product =>"pdnsd", version =>""}, },
                { fingerprint => $iq[75], result => { vendor => "antirez", product =>"Yaku-NS", version =>""}, },
                { fingerprint => ".+", state => "q0r43q6r?", }, ]
    },

    { fingerprint => $iq[44], result => { vendor =>"cpan", product=>"Net::DNS Nameserver", version =>""}, qv => "version.bind", },
    { fingerprint => $iq[52], result => { vendor =>"NLnetLabs", product=>"NSD", version => "1.0 alpha (uncertain)"}, },
    { fingerprint => $iq[55], result => { vendor =>"robtex", product=>"Viking DNS module", version=>""}, },
    { fingerprint => $iq[59], result => { vendor =>"Max Feoktistov", product=>"small HTTP server [recursion enabled]", version =>""}, },
    { fingerprint => $iq[60], result => { vendor =>"Axis", product=>"video server", version =>""}, },
    { fingerprint => $iq[62], header => $qy[7], query => "1.0.0.127.in-addr.arpa. IN PTR", ruleset => [
                { fingerprint => $iq[62], result => { vendor =>"Michael Tokarev", product=>"rbldnsd",version=>""}, qv => "version.bind", },
                { fingerprint => $iq[79], result => { vendor =>"4D", product=>"WebSTAR", version=>""}, },
                { fingerprint => $iq[83], result => { vendor =>"Netopia", product =>"dsl/cable", version => ""},},
                { fingerprint => $iq[90], result => { vendor =>"TZO", product=>"Tzolkin DNS",version=>""}, },
                { fingerprint => "query timed out", result => { vendor =>"Netopia", product =>"dsl/cable", version=>""},},
                { fingerprint => ".+", state => "q0r62q7r?", }, ]
    },
    { fingerprint => $iq[70], result => { vendor =>"Yutaka Sato", product=>"DeleGate DNS", version=>""},},
    { fingerprint => $iq[72], result => { vendor =>"", product =>"sheerdns", version=>""}, },
    { fingerprint => $iq[73], result => { vendor =>"Matthew Pratt", product=>"dproxy", version=>""}, },
    { fingerprint => $iq[74], result => { vendor =>"Brad Garcia", product=>"dnrd",version=>""}, },
    { fingerprint => $iq[76], result => { vendor =>"Sourceforge", product=>"JDNSS",version=>""}, },
    { fingerprint => $iq[77], result => { vendor =>"Dan Kaminsky", product=>"nomde DNS tunnel",version=>""}, },
    { fingerprint => $iq[78], result => { vendor =>"Max Feoktistov", product=>"small HTTP server", version =>""}, },
    { fingerprint => $iq[79], result => { vendor =>"robtex", product=>"Viking DNS module", version=>""}, },
    { fingerprint => $iq[80], result => { vendor =>"Fasthosts", product=>"Envisage DNS server", version=>""}, },
    { fingerprint => $iq[81], result => { vendor =>"WinGate", product=>"Wingate DNS", version=>""},},
    { fingerprint => $iq[82], result => { vendor =>"Ascenvision", product=>"SwiftDNS", version=>""},},
    { fingerprint => $iq[86], result => { vendor =>"Nortel Networks", product=>"Instant Internet",version=>""}, },
    { fingerprint => $iq[87], result => { vendor =>"ATOS", product=>"Stargate ADSL", version=>""},},
    { fingerprint => $iq[88], result => { vendor =>"3Com", product=>"Office Connect Remote", version=>""},},
    { fingerprint => $iq[89], result => { vendor =>"Alteon", product=>"ACEswitch", version=>""},},
    { fingerprint => $iq[90], result => { vendor =>"javaprofessionals", product=>"javadns/jdns", version=>""},},

    { fingerprint => $iq[92], result => { vendor =>"Beehive", product=>"CoDoNS",version=>""}, },
    { fingerprint => ".+", state => "q0r?", }, 

);

######################################################################

sub new
{
    my $proto = shift;
    my $class = ref($proto) || $proto;
    my $self = {};

    my %config = @_;

    foreach my $k (keys %default) {
	if (defined $config{$k}) {
	    $self->{$k} = $config{$k};
	} else {
	    $self->{$k} = $default{$k};
	}
    }

    bless $self, $class;
    return $self;
}

sub hash
{
    my $self = shift;

    my $addr = shift;
    my $port = shift;

    $port = 53 unless($port);

    return $self->init($addr, $port);
}

sub string
{
    my $self = shift;

    my $addr = shift;
    my $port = shift;

    $port = 53 unless($port);

    my %r = $self->hash($addr, $port);

    my @s = ();

    if (defined $r{error}) {
	push @s, $r{error};
    } elsif (defined $r{result}) {
	push @s, $r{result};
    } else {
	push @s, $r{vendor}  if(defined $r{vendor});
	push @s, $r{product} if(defined $r{product});
	push @s, $r{version} if(defined $r{version});
	push @s, "[$r{option}]" if(defined $r{option});
    }

    push @s, $r{vstring} if(defined $r{vstring});

    push @s, "($r{state};$r{id})" if($self->{debug});

    return join($self->{separator}, @s);
}

sub query_version
{  
    my $self = shift;

    my $qserver = shift;
    my $qport   = shift;
    my $ident   = shift;

    my $rrset = " id: ";
    my $resolver = Net::DNS::Resolver->new;

    $resolver->nameservers($qserver);
    $resolver->port($qport);
    $resolver->srcaddr($self->{source});
    $resolver->retry($self->{retry});
    $resolver->retrans($self->{timeout});
    $resolver->usevc($self->{forcetcp});
    my $query = $resolver->query($ident, 'TXT', 'CH');

    if ($query && $query->header->ancount > 0) {
        foreach my $rr ($query->answer) {
            ($rrset = $rrset . "\"" . $rr->txtdata . "\" ") if ($rr->type eq "TXT");
        }
       $rrset =~ s/\n/\" \"/g;
       if (length($rrset) > $versionlength) {
          $rrset = substr($rrset,0,$versionlength)."..."
       }
       return $rrset;
    }

    return " id unavailable (".$resolver->errorstring.")";
}

sub init
{
    my $self = shift;

    my $qserver = shift;
    my $qport   = shift;

    return $self->process($qserver, $qport,
			  $initrule{header}, $initrule{query}, \@ruleset);
}

sub process
{
    my $self = shift;

    my $qserver = shift;
    my $qport   = shift;
    my $qheader = shift;
    my $qstring = shift;
    my $ruleref = shift;
    my $ver;
    my $id;
    my %ret;

    if ($self->{debug}) {
	print STDERR "==> PROCESS $qserver:$qport $qheader $qstring\n";
	print STDERR "\n";
    }

    my ($answer, $ress) = $self->probe($qserver, $qport,
				       $qheader, $qstring);

    if ($answer) {
	$id = header2fp($answer->header);
    } else {
	$id = $ress;
    }

    print STDERR "==> \"$id\"\n" if ($self->{debug});

    for my $rule (@$ruleref) {

 	$ver = " "; 

	# we must have a fingerprint
	die "missing fingerprint" unless (defined $rule->{fingerprint});

	# skip to next rule unless we have a matching fingerprint
	next unless ($id =~ /$rule->{fingerprint}/);

	# return if we have a result
	if (defined $rule->{result}) {
            if (defined $rule->{qv}) {
		$ver = $self->query_version($qserver, $qport,
					    $rule->{qv}) if $self->{qversion};
	    }
            if ($self->{qchaos}) {
		$ver = $self->query_version($qserver, $qport,
					    "version.bind");
	    }
	    $ret{vstring} = $ver if($ver);

	    if (ref($rule->{result})) {
		$ret{vendor}  = $rule->{result}{vendor};
		$ret{product} = $rule->{result}{product};
		$ret{version} = $rule->{result}{version};
		$ret{option}  = $rule->{result}{option};
	    } else {		
		$ret{result} = $rule->{result};
	    }

	    return %ret;
	}

        # print state if no matches
        if (defined $rule->{state}) {
            $ver = $self->query_version($qserver, $qport,
					"hostname.bind") if $self->{qversion};
	    $ret{vstring} = $ver if($ver);

	    $ret{error} = "No match found";
	    $ret{state} = $rule->{state};
	    $ret{id} = $id;

	    return %ret;
        }

	# update query if defined
	if (defined $rule->{query}) {
	    $qstring = $rule->{query};
	}

	# recurse if we have a new header and a new ruleset
	if (defined $rule->{header} && defined $rule->{ruleset}) {
	    return $self->process($qserver, $qport,
				  $rule->{header}, $qstring, $rule->{ruleset});
	}

	die "syntax error";
    }

    return %ret;
}

sub header2fp
{
    my $header = shift;

    my @list = ($header->qr, 
		$header->opcode,
		$header->aa,
		$header->tc,
		$header->rd,
		$header->ra,
		$header->ad,
		$header->cd,
		$header->rcode,
		$header->qdcount,
		$header->ancount,
		$header->nscount,
		$header->arcount);

    return join(",", @list);
}


sub fp2header
{
    my @list = split(/,/, shift);
    my $header = shift;

    $header->qr(shift @list);
    $header->opcode(shift @list);
    $header->aa(shift @list);
    $header->tc(shift @list);
    $header->rd(shift @list);
    $header->ra(shift @list);
    $header->ad(shift @list);
    $header->cd(shift @list);
    $header->rcode(shift @list);
    $header->qdcount(shift @list);
    $header->ancount(shift @list);
    $header->nscount(shift @list);
    $header->arcount(shift @list);
}

sub probe
{
    my $self = shift;

    my $qserver = shift;
    my $qport = shift;
    my $qheader = shift;
    my @qstring = split(/ /, shift);

    #XXX original, now wrong: my $packet = Net::DNS::Packet->new(\$header->data);
    # next try obtained from:
    # https://lists.verisignlabs.com/pipermail/fpdns-user/2012-November/000026.html
    # does not work either: my $packet = Net::DNS::Packet->new(\$header->encode);
    my $packet = new Net::DNS::Packet;
    fp2header($qheader, $packet->header);

    $packet->push("question", Net::DNS::Question->new(@qstring));

    if ($self->{debug}) {
	print STDERR "==> QUERY BEGIN\n";
	print STDERR $packet->print, "\n";
	print STDERR "==> QUERY END\n";
	print STDERR "\n";
    }

    my $resolver =  Net::DNS::Resolver->new;
    $resolver->nameservers($qserver);
    $resolver->port($qport);
    $resolver->srcaddr($self->{source});
    $resolver->retry($self->{retry});
    $resolver->retrans($self->{timeout});
    $resolver->usevc($self->{forcetcp});
    my $answer = $resolver->send($packet);
    if ($answer && $self->{debug}) {
	print STDERR "==> ANSWER BEGIN\n";
	print STDERR $answer->string, "\n";
	print STDERR "==> ANSWER END\n";
	print STDERR "\n";
    }

    return ($answer, $resolver->errorstring);
}

sub version
{
    return $VERSION;
}

1;
