Home | History | Annotate | Line # | Download | only in system
      1      1.1  christos #!/usr/bin/perl
      2  1.1.1.4  christos 
      3      1.1  christos # Copyright (C) Internet Systems Consortium, Inc. ("ISC")
      4      1.1  christos #
      5  1.1.1.4  christos # SPDX-License-Identifier: MPL-2.0
      6  1.1.1.4  christos #
      7      1.1  christos # This Source Code Form is subject to the terms of the Mozilla Public
      8  1.1.1.4  christos # License, v. 2.0.  If a copy of the MPL was not distributed with this
      9  1.1.1.3  christos # file, you can obtain one at https://mozilla.org/MPL/2.0/.
     10      1.1  christos #
     11      1.1  christos # See the COPYRIGHT file distributed with this work for additional
     12      1.1  christos # information regarding copyright ownership.
     13      1.1  christos 
     14      1.1  christos #
     15      1.1  christos # This is the name server from hell.  It provides canned
     16      1.1  christos # responses based on pattern matching the queries, and
     17      1.1  christos # can be reprogrammed on-the-fly over a TCP connection.
     18      1.1  christos #
     19      1.1  christos # The server listens for queries on port 5300 (or PORT).
     20      1.1  christos #
     21      1.1  christos # The server listens for control connections on port 5301 (or EXTRAPORT1).
     22      1.1  christos #
     23      1.1  christos # A control connection is a TCP stream of lines like
     24      1.1  christos #
     25      1.1  christos #  /pattern/
     26      1.1  christos #  name ttl type rdata
     27      1.1  christos #  name ttl type rdata
     28      1.1  christos #  ...
     29      1.1  christos #  /pattern/
     30      1.1  christos #  name ttl type rdata
     31      1.1  christos #  name ttl type rdata
     32      1.1  christos #  ...
     33      1.1  christos #
     34      1.1  christos # There can be any number of patterns, each associated
     35      1.1  christos # with any number of response RRs.  Each pattern is a
     36      1.1  christos # Perl regular expression.  If an empty pattern ("//") is
     37      1.1  christos # received, the server will ignore all incoming queries (TCP
     38      1.1  christos # connections will still be accepted, but both UDP queries
     39      1.1  christos # and TCP queries will not be responded to).  If a non-empty
     40      1.1  christos # pattern is then received over the same control connection,
     41      1.1  christos # default behavior is restored.
     42      1.1  christos #
     43      1.1  christos # Each incoming query is converted into a string of the form
     44      1.1  christos # "qname qtype" (the printable query domain name, space,
     45      1.1  christos # printable query type) and matched against each pattern.
     46      1.1  christos #
     47      1.1  christos # The first pattern matching the query is selected, and
     48      1.1  christos # the RR following the pattern line are sent in the
     49      1.1  christos # answer section of the response.
     50      1.1  christos #
     51      1.1  christos # Each new control connection causes the current set of
     52      1.1  christos # patterns and responses to be cleared before adding new
     53      1.1  christos # ones.
     54      1.1  christos #
     55      1.1  christos # The server handles UDP and TCP queries.  Zone transfer
     56      1.1  christos # responses work, but must fit in a single 64 k message.
     57      1.1  christos #
     58      1.1  christos # Now you can add TSIG, just specify key/key data with:
     59      1.1  christos #
     60      1.1  christos #  /pattern <key> <key_data>/
     61      1.1  christos #  name ttl type rdata
     62      1.1  christos #  name ttl type rdata
     63      1.1  christos #
     64      1.1  christos #  Note that this data will still be sent with any request for
     65      1.1  christos #  pattern, only this data will be signed. Currently, this is only
     66      1.1  christos #  done for TCP.
     67  1.1.1.2  christos #
     68  1.1.1.6  christos # /pattern NOTIMP <key> <key_data>/
     69  1.1.1.6  christos # /pattern NOTIMP/
     70  1.1.1.6  christos #
     71  1.1.1.6  christos # Return a NOTIMP response
     72  1.1.1.6  christos #
     73  1.1.1.6  christos # /pattern EDNS=NOTIMP <key> <key_data>/
     74  1.1.1.6  christos # /pattern EDNS=NOTIMP/
     75  1.1.1.6  christos #
     76  1.1.1.6  christos # Return a NOTIMP response to an EDNS request
     77  1.1.1.6  christos #
     78  1.1.1.6  christos # /pattern EDNS=FORMERR <key> <key_data>/
     79  1.1.1.6  christos # /pattern EDNS=FORMERR/
     80  1.1.1.6  christos #
     81  1.1.1.6  christos # Return a FORMERR response to an EDNS request
     82  1.1.1.6  christos #
     83  1.1.1.2  christos # /pattern bad-id <key> <key_data>/
     84  1.1.1.2  christos # /pattern bad-id/
     85  1.1.1.2  christos #
     86  1.1.1.2  christos # will add 50 to the message id of the response.
     87      1.1  christos 
     88      1.1  christos 
     89      1.1  christos use IO::File;
     90      1.1  christos use IO::Socket;
     91      1.1  christos use Data::Dumper;
     92      1.1  christos use Net::DNS;
     93      1.1  christos use Net::DNS::Packet;
     94      1.1  christos use strict;
     95      1.1  christos 
     96      1.1  christos # Ignore SIGPIPE so we won't fail if peer closes a TCP socket early
     97      1.1  christos local $SIG{PIPE} = 'IGNORE';
     98      1.1  christos 
     99      1.1  christos # Flush logged output after every line
    100      1.1  christos local $| = 1;
    101      1.1  christos 
    102      1.1  christos # We default to listening on 10.53.0.2 for historical reasons
    103      1.1  christos # XXX: we should also be able to specify IPv6
    104      1.1  christos my $server_addr = "10.53.0.2";
    105      1.1  christos if (@ARGV > 0) {
    106      1.1  christos 	$server_addr = @ARGV[0];
    107      1.1  christos }
    108      1.1  christos 
    109      1.1  christos my $mainport = int($ENV{'PORT'});
    110      1.1  christos if (!$mainport) { $mainport = 5300; }
    111      1.1  christos my $ctrlport = int($ENV{'EXTRAPORT1'});
    112      1.1  christos if (!$ctrlport) { $ctrlport = 5301; }
    113  1.1.1.6  christos my $hmac_algorithm = $ENV{'DEFAULT_HMAC'};
    114  1.1.1.6  christos if (!defined($hmac_algorithm)) { $hmac_algorithm = "hmac-sha256"; }
    115      1.1  christos 
    116      1.1  christos # XXX: we should also be able to set the port numbers to listen on.
    117      1.1  christos my $ctlsock = IO::Socket::INET->new(LocalAddr => "$server_addr",
    118      1.1  christos    LocalPort => $ctrlport, Proto => "tcp", Listen => 5, Reuse => 1) or die "$!";
    119      1.1  christos 
    120      1.1  christos my $udpsock = IO::Socket::INET->new(LocalAddr => "$server_addr",
    121      1.1  christos    LocalPort => $mainport, Proto => "udp", Reuse => 1) or die "$!";
    122      1.1  christos 
    123      1.1  christos my $tcpsock = IO::Socket::INET->new(LocalAddr => "$server_addr",
    124      1.1  christos    LocalPort => $mainport, Proto => "tcp", Listen => 5, Reuse => 1) or die "$!";
    125      1.1  christos 
    126      1.1  christos print "listening on $server_addr:$mainport,$ctrlport.\n";
    127      1.1  christos print "Using Net::DNS $Net::DNS::VERSION\n";
    128      1.1  christos 
    129      1.1  christos my $pidf = new IO::File "ans.pid", "w" or die "cannot open pid file: $!";
    130      1.1  christos print $pidf "$$\n" or die "cannot write pid file: $!";
    131      1.1  christos $pidf->close or die "cannot close pid file: $!";;
    132      1.1  christos sub rmpid { unlink "ans.pid"; exit 1; };
    133      1.1  christos 
    134      1.1  christos $SIG{INT} = \&rmpid;
    135      1.1  christos $SIG{TERM} = \&rmpid;
    136      1.1  christos 
    137      1.1  christos #my @answers = ();
    138      1.1  christos my @rules;
    139      1.1  christos my $udphandler;
    140      1.1  christos my $tcphandler;
    141      1.1  christos 
    142      1.1  christos sub handleUDP {
    143      1.1  christos 	my ($buf) = @_;
    144      1.1  christos 	my $request;
    145      1.1  christos 
    146      1.1  christos 	if ($Net::DNS::VERSION > 0.68) {
    147      1.1  christos 		$request = new Net::DNS::Packet(\$buf, 0);
    148      1.1  christos 		$@ and die $@;
    149      1.1  christos 	} else {
    150      1.1  christos 		my $err;
    151      1.1  christos 		($request, $err) = new Net::DNS::Packet(\$buf, 0);
    152      1.1  christos 		$err and die $err;
    153      1.1  christos 	}
    154      1.1  christos 
    155      1.1  christos 	my @questions = $request->question;
    156      1.1  christos 	my $qname = $questions[0]->qname;
    157      1.1  christos 	my $qtype = $questions[0]->qtype;
    158      1.1  christos 	my $qclass = $questions[0]->qclass;
    159      1.1  christos 	my $id = $request->header->id;
    160      1.1  christos 
    161      1.1  christos 	my $packet = new Net::DNS::Packet($qname, $qtype, $qclass);
    162      1.1  christos 	$packet->header->qr(1);
    163      1.1  christos 	$packet->header->aa(1);
    164      1.1  christos 	$packet->header->id($id);
    165      1.1  christos 
    166      1.1  christos 	# get the existing signature if any, and clear the additional section
    167      1.1  christos 	my $prev_tsig;
    168      1.1  christos 	while (my $rr = $request->pop("additional")) {
    169      1.1  christos 		$prev_tsig = $rr if ($rr->type eq "TSIG");
    170      1.1  christos 	}
    171      1.1  christos 
    172      1.1  christos 	my $r;
    173      1.1  christos 	foreach $r (@rules) {
    174      1.1  christos 		my $pattern = $r->{pattern};
    175      1.1  christos 		my($dbtype, $key_name, $key_data) = split(/ /,$pattern);
    176  1.1.1.5  christos 		print "[handleUDP] $dbtype, $key_name, $key_data\n";
    177      1.1  christos 		if ("$qname $qtype" =~ /$dbtype/) {
    178      1.1  christos 			my $a;
    179      1.1  christos 			foreach $a (@{$r->{answer}}) {
    180      1.1  christos 				$packet->push("answer", $a);
    181      1.1  christos 			}
    182  1.1.1.5  christos 			if (defined($key_name) && defined($key_data)) {
    183      1.1  christos 				my $tsig;
    184      1.1  christos 				# Sign the packet
    185      1.1  christos 				print "  Signing the response with " .
    186      1.1  christos 				      "$key_name/$key_data\n";
    187      1.1  christos 
    188      1.1  christos 				if ($Net::DNS::VERSION < 0.69) {
    189      1.1  christos 					$tsig = Net::DNS::RR->new(
    190      1.1  christos 						   "$key_name TSIG $key_data");
    191      1.1  christos 				} else {
    192      1.1  christos 					$tsig = Net::DNS::RR->new(
    193      1.1  christos 							name => $key_name,
    194  1.1.1.6  christos 							algorithm => $hmac_algorithm,
    195      1.1  christos 							type => 'TSIG',
    196      1.1  christos 							key  => $key_data);
    197      1.1  christos 				}
    198      1.1  christos 
    199      1.1  christos 				# These kluges are necessary because Net::DNS
    200      1.1  christos 				# doesn't know how to sign responses.  We
    201      1.1  christos 				# clear compnames so that the TSIG key and
    202      1.1  christos 				# algorithm name won't be compressed, and
    203      1.1  christos 				# add one to arcount because the signing
    204      1.1  christos 				# function will attempt to decrement it,
    205      1.1  christos 				# which is incorrect in a response. Finally
    206      1.1  christos 				# we set request_mac to the previous digest.
    207      1.1  christos 				$packet->{"compnames"} = {}
    208      1.1  christos 					if ($Net::DNS::VERSION < 0.70);
    209      1.1  christos 				$packet->{"header"}{"arcount"} += 1
    210      1.1  christos 					if ($Net::DNS::VERSION < 0.70);
    211      1.1  christos 				if (defined($prev_tsig)) {
    212      1.1  christos 					if ($Net::DNS::VERSION < 0.73) {
    213      1.1  christos 						my $rmac = pack('n H*',
    214      1.1  christos 							length($prev_tsig->mac)/2,
    215      1.1  christos 							$prev_tsig->mac);
    216      1.1  christos 						$tsig->{"request_mac"} =
    217      1.1  christos 							unpack("H*", $rmac);
    218      1.1  christos 					} else {
    219      1.1  christos 						$tsig->request_mac(
    220      1.1  christos 							 $prev_tsig->mac);
    221      1.1  christos 					}
    222      1.1  christos 				}
    223  1.1.1.5  christos 
    224      1.1  christos 				$packet->sign_tsig($tsig);
    225      1.1  christos 			}
    226      1.1  christos 			last;
    227      1.1  christos 		}
    228      1.1  christos 	}
    229      1.1  christos 	#$packet->print;
    230      1.1  christos 
    231      1.1  christos 	return $packet->data;
    232      1.1  christos }
    233      1.1  christos 
    234      1.1  christos # namelen:
    235      1.1  christos # given a stream of data, reads a DNS-formatted name and returns its
    236      1.1  christos # total length, thus making it possible to skip past it.
    237      1.1  christos sub namelen {
    238      1.1  christos 	my ($data) = @_;
    239      1.1  christos 	my $len = 0;
    240      1.1  christos 	my $label_len = 0;
    241      1.1  christos 	do {
    242      1.1  christos 		$label_len = unpack("c", $data);
    243      1.1  christos 		$data = substr($data, $label_len + 1);
    244      1.1  christos 		$len += $label_len + 1;
    245      1.1  christos 	} while ($label_len != 0);
    246      1.1  christos 	return ($len);
    247      1.1  christos }
    248      1.1  christos 
    249      1.1  christos # packetlen:
    250      1.1  christos # given a stream of data, reads a DNS wire-format packet and returns
    251      1.1  christos # its total length, making it possible to skip past it.
    252      1.1  christos sub packetlen {
    253      1.1  christos 	my ($data) = @_;
    254      1.1  christos 	my $q;
    255      1.1  christos 	my $rr;
    256      1.1  christos 	my $header;
    257      1.1  christos 	my $offset;
    258      1.1  christos 
    259      1.1  christos 	#
    260      1.1  christos 	# decode/encode were introduced in Net::DNS 0.68
    261      1.1  christos 	# parse is no longer a method and calling it here makes perl croak.
    262      1.1  christos 	#
    263      1.1  christos 	my $decode = 0;
    264      1.1  christos 	$decode = 1 if ($Net::DNS::VERSION >= 0.68);
    265      1.1  christos 
    266      1.1  christos 	if ($decode) {
    267      1.1  christos 		($header, $offset) = Net::DNS::Header->decode(\$data);
    268      1.1  christos 	} else {
    269      1.1  christos 		($header, $offset) = Net::DNS::Header->parse(\$data);
    270      1.1  christos 	}
    271  1.1.1.5  christos 
    272      1.1  christos 	for (1 .. $header->qdcount) {
    273      1.1  christos 		if ($decode) {
    274      1.1  christos 			($q, $offset) =
    275      1.1  christos 				 Net::DNS::Question->decode(\$data, $offset);
    276      1.1  christos 		} else {
    277      1.1  christos 			($q, $offset) =
    278      1.1  christos 				 Net::DNS::Question->parse(\$data, $offset);
    279      1.1  christos 		}
    280      1.1  christos 	}
    281      1.1  christos 	for (1 .. $header->ancount) {
    282      1.1  christos 		if ($decode) {
    283      1.1  christos 			($q, $offset) = Net::DNS::RR->decode(\$data, $offset);
    284      1.1  christos 		} else {
    285      1.1  christos 			($q, $offset) = Net::DNS::RR->parse(\$data, $offset);
    286      1.1  christos 		}
    287      1.1  christos 	}
    288      1.1  christos 	for (1 .. $header->nscount) {
    289      1.1  christos 		if ($decode) {
    290      1.1  christos 			($q, $offset) = Net::DNS::RR->decode(\$data, $offset);
    291      1.1  christos 		} else {
    292      1.1  christos 			($q, $offset) = Net::DNS::RR->parse(\$data, $offset);
    293      1.1  christos 		}
    294      1.1  christos 	}
    295      1.1  christos 	for (1 .. $header->arcount) {
    296      1.1  christos 		if ($decode) {
    297      1.1  christos 			($q, $offset) = Net::DNS::RR->decode(\$data, $offset);
    298      1.1  christos 		} else {
    299      1.1  christos 			($q, $offset) = Net::DNS::RR->parse(\$data, $offset);
    300      1.1  christos 		}
    301      1.1  christos 	}
    302      1.1  christos 	return $offset;
    303      1.1  christos }
    304      1.1  christos 
    305      1.1  christos # sign_tcp_continuation:
    306      1.1  christos # This is a hack to correct the problem that Net::DNS has no idea how
    307      1.1  christos # to sign multiple-message TCP responses.  Several data that are included
    308      1.1  christos # in the digest when signing a query or the first message of a response are
    309      1.1  christos # omitted when signing subsequent messages in a TCP stream.
    310      1.1  christos #
    311      1.1  christos # Net::DNS::Packet->sign_tsig() has the ability to use a custom signing
    312      1.1  christos # function (specified by calling Packet->sign_func()).  We use this
    313      1.1  christos # function as the signing function for TCP continuations, and it removes
    314      1.1  christos # the unwanted data from the digest before calling the default sign_hmac
    315      1.1  christos # function.
    316      1.1  christos sub sign_tcp_continuation {
    317      1.1  christos 	my ($key, $data) = @_;
    318      1.1  christos 
    319      1.1  christos 	# copy out first two bytes: size of the previous MAC
    320      1.1  christos 	my $rmacsize = unpack("n", $data);
    321      1.1  christos 	$data = substr($data, 2);
    322      1.1  christos 
    323      1.1  christos 	# copy out previous MAC
    324      1.1  christos 	my $rmac = substr($data, 0, $rmacsize);
    325      1.1  christos 	$data = substr($data, $rmacsize);
    326      1.1  christos 
    327      1.1  christos 	# try parsing out the packet information
    328      1.1  christos 	my $plen = packetlen($data);
    329      1.1  christos 	my $pdata = substr($data, 0, $plen);
    330      1.1  christos 	$data = substr($data, $plen);
    331      1.1  christos 
    332      1.1  christos 	# remove the keyname, ttl, class, and algorithm name
    333      1.1  christos 	$data = substr($data, namelen($data));
    334      1.1  christos 	$data = substr($data, 6);
    335      1.1  christos 	$data = substr($data, namelen($data));
    336      1.1  christos 
    337      1.1  christos 	# preserve the TSIG data
    338      1.1  christos 	my $tdata = substr($data, 0, 8);
    339      1.1  christos 
    340      1.1  christos 	# prepare a new digest and sign with it
    341      1.1  christos 	$data = pack("n", $rmacsize) . $rmac . $pdata . $tdata;
    342      1.1  christos 	return Net::DNS::RR::TSIG::sign_hmac($key, $data);
    343      1.1  christos }
    344      1.1  christos 
    345      1.1  christos sub handleTCP {
    346      1.1  christos 	my ($buf) = @_;
    347      1.1  christos 	my $request;
    348      1.1  christos 
    349      1.1  christos 	if ($Net::DNS::VERSION > 0.68) {
    350      1.1  christos 		$request = new Net::DNS::Packet(\$buf, 0);
    351      1.1  christos 		$@ and die $@;
    352      1.1  christos 	} else {
    353      1.1  christos 		my $err;
    354      1.1  christos 		($request, $err) = new Net::DNS::Packet(\$buf, 0);
    355      1.1  christos 		$err and die $err;
    356      1.1  christos 	}
    357  1.1.1.5  christos 
    358      1.1  christos 	my @questions = $request->question;
    359      1.1  christos 	my $qname = $questions[0]->qname;
    360      1.1  christos 	my $qtype = $questions[0]->qtype;
    361      1.1  christos 	my $qclass = $questions[0]->qclass;
    362      1.1  christos 	my $id = $request->header->id;
    363  1.1.1.6  christos 	my @additional = $request->additional;
    364  1.1.1.6  christos 	my $has_opt = 0;
    365  1.1.1.6  christos 	foreach (@additional) {
    366  1.1.1.6  christos 		$has_opt = 1 if (ref($_) eq 'Net::DNS::RR::OPT');
    367  1.1.1.6  christos 	}
    368      1.1  christos 
    369      1.1  christos 	my $opaque;
    370      1.1  christos 
    371      1.1  christos 	my $packet = new Net::DNS::Packet($qname, $qtype, $qclass);
    372      1.1  christos 	$packet->header->qr(1);
    373      1.1  christos 	$packet->header->aa(1);
    374      1.1  christos 	$packet->header->id($id);
    375      1.1  christos 
    376      1.1  christos 	# get the existing signature if any, and clear the additional section
    377      1.1  christos 	my $prev_tsig;
    378      1.1  christos 	my $signer;
    379      1.1  christos 	my $continuation = 0;
    380      1.1  christos 	if ($Net::DNS::VERSION < 0.81) {
    381      1.1  christos 		while (my $rr = $request->pop("additional")) {
    382      1.1  christos 			if ($rr->type eq "TSIG") {
    383      1.1  christos 				$prev_tsig = $rr;
    384      1.1  christos 			}
    385      1.1  christos 		}
    386      1.1  christos 	}
    387      1.1  christos 
    388      1.1  christos 	my @results = ();
    389      1.1  christos 	my $count_these = 0;
    390      1.1  christos 
    391      1.1  christos 	my $r;
    392      1.1  christos 	foreach $r (@rules) {
    393      1.1  christos 		my $pattern = $r->{pattern};
    394  1.1.1.5  christos 		my($dbtype, $key_name, $key_data, $tname) = split(/ /,$pattern);
    395  1.1.1.5  christos 		print "[handleTCP] $dbtype, $key_name, $key_data, $tname \n";
    396      1.1  christos 		if ("$qname $qtype" =~ /$dbtype/) {
    397      1.1  christos 			$count_these++;
    398      1.1  christos 			my $a;
    399  1.1.1.6  christos 			my $done = 0;
    400  1.1.1.6  christos 
    401  1.1.1.6  christos 			while (defined($key_name) &&
    402  1.1.1.6  christos 			       ($key_name eq "NOTIMP" || $key_name eq "EDNS=NOTIMP" ||
    403  1.1.1.6  christos 				$key_name eq "EDNS=FORMERR" || $key_name eq "bad-id")) {
    404  1.1.1.6  christos 
    405  1.1.1.6  christos 				if (defined($key_name) && $key_name eq "NOTIMP") {
    406  1.1.1.6  christos 					$packet->header->rcode('NOTIMP') if (!$done);
    407  1.1.1.6  christos 					$key_name = $key_data;
    408  1.1.1.6  christos 					($key_data, $tname) = split(/ /,$tname);
    409  1.1.1.6  christos 					$done = 1;
    410  1.1.1.6  christos 				}
    411  1.1.1.6  christos 
    412  1.1.1.6  christos 				if (defined($key_name) && $key_name eq "EDNS=NOTIMP") {
    413  1.1.1.6  christos 					if ($has_opt) {
    414  1.1.1.6  christos 						$packet->header->rcode('NOTIMP') if (!$done);
    415  1.1.1.6  christos 						$done = 1;
    416  1.1.1.6  christos 					}
    417  1.1.1.6  christos 					$key_name = $key_data;
    418  1.1.1.6  christos 					($key_data, $tname) = split(/ /,$tname);
    419  1.1.1.6  christos 				}
    420  1.1.1.6  christos 
    421  1.1.1.6  christos 				if (defined($key_name) && $key_name eq "EDNS=FORMERR") {
    422  1.1.1.6  christos 					if ($has_opt) {
    423  1.1.1.6  christos 						$packet->header->rcode('FORMERR') if (!$done);
    424  1.1.1.6  christos 						$done = 1;
    425  1.1.1.6  christos 					}
    426  1.1.1.6  christos 					$key_name = $key_data;
    427  1.1.1.6  christos 					($key_data, $tname) = split(/ /,$tname);
    428  1.1.1.6  christos 				}
    429  1.1.1.6  christos 
    430  1.1.1.6  christos 				if (defined($key_name) && $key_name eq "bad-id") {
    431  1.1.1.6  christos 					$packet->header->id(($id+50)%0xffff);
    432  1.1.1.6  christos 					$key_name = $key_data;
    433  1.1.1.6  christos 					($key_data, $tname) = split(/ /,$tname);
    434  1.1.1.6  christos 				}
    435      1.1  christos 			}
    436  1.1.1.6  christos 
    437  1.1.1.6  christos 			if (!$done) {
    438  1.1.1.6  christos 				foreach $a (@{$r->{answer}}) {
    439  1.1.1.6  christos 					$packet->push("answer", $a);
    440  1.1.1.6  christos 				}
    441  1.1.1.2  christos 			}
    442  1.1.1.6  christos 
    443      1.1  christos 			if (defined($key_name) && defined($key_data)) {
    444      1.1  christos 				my $tsig;
    445      1.1  christos 				# sign the packet
    446  1.1.1.5  christos 				print "  Signing the data with " .
    447      1.1  christos 				      "$key_name/$key_data\n";
    448      1.1  christos 
    449      1.1  christos 				if ($Net::DNS::VERSION < 0.69) {
    450      1.1  christos 					$tsig = Net::DNS::RR->new(
    451      1.1  christos 						   "$key_name TSIG $key_data");
    452  1.1.1.6  christos 					$tsig->algorithm = $hmac_algorithm;
    453      1.1  christos 				} elsif ($Net::DNS::VERSION >= 0.81 &&
    454      1.1  christos 					 $continuation) {
    455      1.1  christos 				} elsif ($Net::DNS::VERSION >= 0.75 &&
    456      1.1  christos 					 $continuation) {
    457      1.1  christos 					$tsig = $prev_tsig;
    458      1.1  christos 				} else {
    459      1.1  christos 					$tsig = Net::DNS::RR->new(
    460      1.1  christos 							name => $key_name,
    461  1.1.1.6  christos 							algorithm => $hmac_algorithm,
    462      1.1  christos 							type => 'TSIG',
    463      1.1  christos 							key  => $key_data);
    464      1.1  christos 				}
    465      1.1  christos 
    466      1.1  christos 				# These kluges are necessary because Net::DNS
    467      1.1  christos 				# doesn't know how to sign responses.  We
    468      1.1  christos 				# clear compnames so that the TSIG key and
    469      1.1  christos 				# algorithm name won't be compressed, and
    470      1.1  christos 				# add one to arcount because the signing
    471      1.1  christos 				# function will attempt to decrement it,
    472      1.1  christos 				# which is incorrect in a response. Finally
    473      1.1  christos 				# we set request_mac to the previous digest.
    474      1.1  christos 				$packet->{"compnames"} = {}
    475      1.1  christos 					if ($Net::DNS::VERSION < 0.70);
    476      1.1  christos 				$packet->{"header"}{"arcount"} += 1
    477      1.1  christos 					if ($Net::DNS::VERSION < 0.70);
    478      1.1  christos 				if (defined($prev_tsig)) {
    479      1.1  christos 					if ($Net::DNS::VERSION < 0.73) {
    480      1.1  christos 						my $rmac = pack('n H*',
    481      1.1  christos 							length($prev_tsig->mac)/2,
    482      1.1  christos 							$prev_tsig->mac);
    483      1.1  christos 						$tsig->{"request_mac"} =
    484      1.1  christos 							unpack("H*", $rmac);
    485      1.1  christos 					} elsif ($Net::DNS::VERSION < 0.81) {
    486      1.1  christos 						$tsig->request_mac(
    487      1.1  christos 							 $prev_tsig->mac);
    488      1.1  christos 					}
    489      1.1  christos 				}
    490  1.1.1.5  christos 
    491      1.1  christos 				$tsig->sign_func($signer) if defined($signer);
    492      1.1  christos 				$tsig->continuation($continuation) if
    493      1.1  christos 					 ($Net::DNS::VERSION >= 0.71 &&
    494      1.1  christos 					  $Net::DNS::VERSION <= 0.74 );
    495      1.1  christos 				if ($Net::DNS::VERSION < 0.81) {
    496      1.1  christos 					$packet->sign_tsig($tsig);
    497      1.1  christos 				} elsif ($continuation) {
    498      1.1  christos 					$opaque = $packet->sign_tsig($opaque);
    499      1.1  christos 				} else {
    500      1.1  christos 					$opaque = $packet->sign_tsig($request);
    501      1.1  christos 				}
    502      1.1  christos 				$signer = \&sign_tcp_continuation
    503      1.1  christos 					if ($Net::DNS::VERSION < 0.70);
    504      1.1  christos 				$continuation = 1;
    505      1.1  christos 
    506      1.1  christos 				my $copy =
    507      1.1  christos 					Net::DNS::Packet->new(\($packet->data));
    508      1.1  christos 				$prev_tsig = $copy->pop("additional");
    509      1.1  christos 			}
    510      1.1  christos 			#$packet->print;
    511      1.1  christos 			push(@results,$packet->data);
    512  1.1.1.6  christos 			last if ($done);
    513  1.1.1.5  christos 			if ($tname eq "") {
    514  1.1.1.5  christos 				$tname = $qname;
    515  1.1.1.5  christos 			}
    516  1.1.1.5  christos 			$packet = new Net::DNS::Packet($tname, $qtype, $qclass);
    517      1.1  christos 			$packet->header->qr(1);
    518      1.1  christos 			$packet->header->aa(1);
    519      1.1  christos 			$packet->header->id($id);
    520      1.1  christos 		}
    521      1.1  christos 	}
    522      1.1  christos 	print " A total of $count_these patterns matched\n";
    523      1.1  christos 	return \@results;
    524      1.1  christos }
    525      1.1  christos 
    526      1.1  christos # Main
    527      1.1  christos my $rin;
    528      1.1  christos my $rout;
    529      1.1  christos for (;;) {
    530      1.1  christos 	$rin = '';
    531      1.1  christos 	vec($rin, fileno($ctlsock), 1) = 1;
    532      1.1  christos 	vec($rin, fileno($tcpsock), 1) = 1;
    533      1.1  christos 	vec($rin, fileno($udpsock), 1) = 1;
    534      1.1  christos 
    535      1.1  christos 	select($rout = $rin, undef, undef, undef);
    536      1.1  christos 
    537      1.1  christos 	if (vec($rout, fileno($ctlsock), 1)) {
    538      1.1  christos 		warn "ctl conn";
    539      1.1  christos 		my $conn = $ctlsock->accept;
    540      1.1  christos 		my $rule = ();
    541      1.1  christos 		@rules = ();
    542      1.1  christos 		while (my $line = $conn->getline) {
    543      1.1  christos 			chomp $line;
    544      1.1  christos 			if ($line =~ m!^/(.*)/$!) {
    545      1.1  christos 				if (length($1) == 0) {
    546      1.1  christos 					$udphandler = sub { return; };
    547      1.1  christos 					$tcphandler = sub { return; };
    548      1.1  christos 				} else {
    549      1.1  christos 					$udphandler = \&handleUDP;
    550      1.1  christos 					$tcphandler = \&handleTCP;
    551      1.1  christos 					$rule = { pattern => $1, answer => [] };
    552      1.1  christos 					push(@rules, $rule);
    553      1.1  christos 				}
    554      1.1  christos 			} else {
    555      1.1  christos 				push(@{$rule->{answer}},
    556      1.1  christos 				     new Net::DNS::RR($line));
    557      1.1  christos 			}
    558      1.1  christos 		}
    559      1.1  christos 		$conn->close;
    560      1.1  christos 		#print Dumper(@rules);
    561      1.1  christos 		#print "+=+=+ $rules[0]->{'pattern'}\n";
    562      1.1  christos 		#print "+=+=+ $rules[0]->{'answer'}->[0]->{'rname'}\n";
    563      1.1  christos 		#print "+=+=+ $rules[0]->{'answer'}->[0]\n";
    564      1.1  christos 	} elsif (vec($rout, fileno($udpsock), 1)) {
    565      1.1  christos 		printf "UDP request\n";
    566      1.1  christos 		my $buf;
    567      1.1  christos 		$udpsock->recv($buf, 512);
    568      1.1  christos 		my $result = &$udphandler($buf);
    569      1.1  christos 		if (defined($result)) {
    570      1.1  christos 			my $num_chars = $udpsock->send($result);
    571      1.1  christos 			print "  Sent $num_chars bytes via UDP\n";
    572      1.1  christos 		}
    573      1.1  christos 	} elsif (vec($rout, fileno($tcpsock), 1)) {
    574      1.1  christos 		my $conn = $tcpsock->accept;
    575      1.1  christos 		my $buf;
    576      1.1  christos 		for (;;) {
    577      1.1  christos 			my $lenbuf;
    578      1.1  christos 			my $n = $conn->sysread($lenbuf, 2);
    579      1.1  christos 			last unless $n == 2;
    580      1.1  christos 			my $len = unpack("n", $lenbuf);
    581      1.1  christos 			$n = $conn->sysread($buf, $len);
    582      1.1  christos 			last unless $n == $len;
    583      1.1  christos 			print "TCP request\n";
    584      1.1  christos 			my $result = &$tcphandler($buf);
    585      1.1  christos 			if (defined($result)) {
    586      1.1  christos 				foreach my $response (@$result) {
    587      1.1  christos 					$len = length($response);
    588      1.1  christos 					$n = $conn->syswrite(pack("n", $len), 2);
    589      1.1  christos 					$n = $conn->syswrite($response, $len);
    590      1.1  christos 					print "    Sent: $n chars via TCP\n";
    591      1.1  christos 				}
    592      1.1  christos 			}
    593      1.1  christos 		}
    594      1.1  christos 		$conn->close;
    595      1.1  christos 	}
    596      1.1  christos }
    597