~ubuntu-branches/ubuntu/raring/bind9/raring

« back to all changes in this revision

Viewing changes to bin/tests/system/ans.pl

  • Committer: Package Import Robot
  • Author(s): LaMont Jones
  • Date: 2012-01-19 12:30:31 UTC
  • mfrom: (1.9.1) (26.1.6 sid)
  • Revision ID: package-import@ubuntu.com-20120119123031-0j2wlg66ll5ogpz2
Tags: 1:9.8.1.dfsg.P1-1~build1
preciseĀ upload

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/perl
 
2
#
 
3
# Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
 
4
#
 
5
# Permission to use, copy, modify, and/or distribute this software for any
 
6
# purpose with or without fee is hereby granted, provided that the above
 
7
# copyright notice and this permission notice appear in all copies.
 
8
#
 
9
# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
 
10
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 
11
# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
 
12
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 
13
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
 
14
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 
15
# PERFORMANCE OF THIS SOFTWARE.
 
16
 
 
17
# $Id: ans.pl,v 1.2.2.4 2011-03-18 04:40:30 each Exp $
 
18
 
 
19
#
 
20
# This is the name server from hell.  It provides canned
 
21
# responses based on pattern matching the queries, and
 
22
# can be reprogrammed on-the-fly over a TCP connection.
 
23
#
 
24
# The server listens for control connections on port 5301.
 
25
# A control connection is a TCP stream of lines like
 
26
#
 
27
#  /pattern/
 
28
#  name ttl type rdata
 
29
#  name ttl type rdata
 
30
#  ...
 
31
#  /pattern/
 
32
#  name ttl type rdata
 
33
#  name ttl type rdata
 
34
#  ...
 
35
#
 
36
# There can be any number of patterns, each associated
 
37
# with any number of response RRs.  Each pattern is a
 
38
# Perl regular expression.
 
39
#
 
40
# Each incoming query is converted into a string of the form
 
41
# "qname qtype" (the printable query domain name, space,
 
42
# printable query type) and matched against each pattern.
 
43
#
 
44
# The first pattern matching the query is selected, and
 
45
# the RR following the pattern line are sent in the
 
46
# answer section of the response.
 
47
#
 
48
# Each new control connection causes the current set of
 
49
# patterns and responses to be cleared before adding new
 
50
# ones.
 
51
#
 
52
# The server handles UDP and TCP queries.  Zone transfer
 
53
# responses work, but must fit in a single 64 k message.
 
54
#
 
55
# Now you can add TSIG, just specify key/key data with:
 
56
#
 
57
#  /pattern <key> <key_data>/
 
58
#  name ttl type rdata
 
59
#  name ttl type rdata
 
60
#
 
61
#  Note that this data will still be sent with any request for
 
62
#  pattern, only this data will be signed. Currently, this is only
 
63
#  done for TCP.
 
64
 
 
65
 
 
66
use IO::File;
 
67
use IO::Socket;
 
68
use Data::Dumper;
 
69
use Net::DNS;
 
70
use Net::DNS::Packet;
 
71
use strict;
 
72
 
 
73
# Ignore SIGPIPE so we won't fail if peer closes a TCP socket early
 
74
local $SIG{PIPE} = 'IGNORE';
 
75
 
 
76
# Flush logged output after every line
 
77
local $| = 1;
 
78
 
 
79
# We default to listening on 10.53.0.2 for historical reasons
 
80
# XXX: we should also be able to specify IPv6
 
81
my $server_addr = "10.53.0.2";
 
82
if (@ARGV > 0) {
 
83
        $server_addr = @ARGV[0];
 
84
}
 
85
 
 
86
# XXX: we should also be able to set the port numbers to listen on.
 
87
my $ctlsock = IO::Socket::INET->new(LocalAddr => "$server_addr",
 
88
   LocalPort => 5301, Proto => "tcp", Listen => 5, Reuse => 1) or die "$!";
 
89
 
 
90
my $udpsock = IO::Socket::INET->new(LocalAddr => "$server_addr",
 
91
   LocalPort => 5300, Proto => "udp", Reuse => 1) or die "$!";
 
92
 
 
93
my $tcpsock = IO::Socket::INET->new(LocalAddr => "$server_addr",
 
94
   LocalPort => 5300, Proto => "tcp", Listen => 5, Reuse => 1) or die "$!";
 
95
 
 
96
print "listening on $server_addr:5300,5301.\n";
 
97
 
 
98
my $pidf = new IO::File "ans.pid", "w" or die "cannot open pid file: $!";
 
99
print $pidf "$$\n" or die "cannot write pid file: $!";
 
100
$pidf->close or die "cannot close pid file: $!";;
 
101
sub rmpid { unlink "ans.pid"; exit 1; };
 
102
 
 
103
$SIG{INT} = \&rmpid;
 
104
$SIG{TERM} = \&rmpid;
 
105
 
 
106
#my @answers = ();
 
107
my @rules;
 
108
sub handleUDP {
 
109
        my ($buf) = @_;
 
110
 
 
111
        my ($packet, $err) = new Net::DNS::Packet(\$buf, 0);
 
112
        $err and die $err;
 
113
 
 
114
        $packet->header->qr(1);
 
115
        $packet->header->aa(1);
 
116
 
 
117
        my @questions = $packet->question;
 
118
        my $qname = $questions[0]->qname;
 
119
        my $qtype = $questions[0]->qtype;
 
120
 
 
121
        # get the existing signature if any, and clear the additional section
 
122
        my $prev_tsig;
 
123
        while (my $rr = $packet->pop("additional")) {
 
124
                if ($rr->type eq "TSIG") {
 
125
                        $prev_tsig = $rr;
 
126
                }
 
127
        }
 
128
 
 
129
        my $r;
 
130
        foreach $r (@rules) {
 
131
                my $pattern = $r->{pattern};
 
132
                my($dbtype, $key_name, $key_data) = split(/ /,$pattern);
 
133
                print "[handleUDP] $dbtype, $key_name, $key_data \n";
 
134
                if ("$qname $qtype" =~ /$dbtype/) {
 
135
                        my $a;
 
136
                        foreach $a (@{$r->{answer}}) {
 
137
                                $packet->push("answer", $a);
 
138
                        }
 
139
                        if(defined($key_name) && defined($key_data)) {
 
140
                                # Sign the packet
 
141
                                print "  Signing the response with " .
 
142
                                      "$key_name/$key_data\n";
 
143
                                my $tsig = Net::DNS::RR->
 
144
                                        new("$key_name TSIG $key_data");
 
145
 
 
146
                                # These kluges are necessary because Net::DNS
 
147
                                # doesn't know how to sign responses.  We
 
148
                                # clear compnames so that the TSIG key and
 
149
                                # algorithm name won't be compressed, and
 
150
                                # add one to arcount because the signing
 
151
                                # function will attempt to decrement it,
 
152
                                # which is incorrect in a response. Finally
 
153
                                # we set request_mac to the previous digest.
 
154
                                $packet->{"compnames"} = {};
 
155
                                $packet->{"header"}{"arcount"} += 1;
 
156
                                if (defined($prev_tsig)) {
 
157
                                        my $rmac = pack('n H*',
 
158
                                                $prev_tsig->mac_size,
 
159
                                                $prev_tsig->mac);
 
160
                                        $tsig->{"request_mac"} =
 
161
                                                unpack("H*", $rmac);
 
162
                                }
 
163
                                
 
164
                                $packet->sign_tsig($tsig);
 
165
                        }
 
166
                        last;
 
167
                }
 
168
        }
 
169
        #$packet->print;
 
170
 
 
171
        return $packet->data;
 
172
}
 
173
 
 
174
# namelen:
 
175
# given a stream of data, reads a DNS-formatted name and returns its
 
176
# total length, thus making it possible to skip past it.
 
177
sub namelen {
 
178
        my ($data) = @_;
 
179
        my $len = 0;
 
180
        my $label_len = 0;
 
181
        do {
 
182
                $label_len = unpack("c", $data);
 
183
                $data = substr($data, $label_len + 1);
 
184
                $len += $label_len + 1;
 
185
        } while ($label_len != 0);
 
186
        return ($len);
 
187
}
 
188
 
 
189
# packetlen:
 
190
# given a stream of data, reads a DNS wire-format packet and returns
 
191
# its total length, making it possible to skip past it.
 
192
sub packetlen {
 
193
        my ($data) = @_;
 
194
        my $q;
 
195
        my $rr;
 
196
 
 
197
        my ($header, $offset) = Net::DNS::Header->parse(\$data);
 
198
        for (1 .. $header->qdcount) {
 
199
                ($q, $offset) = Net::DNS::Question->parse(\$data, $offset);
 
200
        }
 
201
        for (1 .. $header->ancount) {
 
202
                ($rr, $offset) = Net::DNS::RR->parse(\$data, $offset);
 
203
        }
 
204
        for (1 .. $header->nscount) {
 
205
                ($rr, $offset) = Net::DNS::RR->parse(\$data, $offset);
 
206
        }
 
207
        for (1 .. $header->arcount) {
 
208
                ($rr, $offset) = Net::DNS::RR->parse(\$data, $offset);
 
209
        }
 
210
        return $offset;
 
211
}
 
212
 
 
213
# sign_tcp_continuation:
 
214
# This is a hack to correct the problem that Net::DNS has no idea how
 
215
# to sign multiple-message TCP responses.  Several data that are included
 
216
# in the digest when signing a query or the first message of a response are
 
217
# omitted when signing subsequent messages in a TCP stream.
 
218
#
 
219
# Net::DNS::Packet->sign_tsig() has the ability to use a custom signing
 
220
# function (specified by calling Packet->sign_func()).  We use this
 
221
# function as the signing function for TCP continuations, and it removes
 
222
# the unwanted data from the digest before calling the default sign_hmac
 
223
# function.
 
224
sub sign_tcp_continuation {
 
225
        my ($key, $data) = @_;
 
226
 
 
227
        # copy out first two bytes: size of the previous MAC
 
228
        my $rmacsize = unpack("n", $data);
 
229
        $data = substr($data, 2);
 
230
 
 
231
        # copy out previous MAC
 
232
        my $rmac = substr($data, 0, $rmacsize);
 
233
        $data = substr($data, $rmacsize);
 
234
 
 
235
        # try parsing out the packet information
 
236
        my $plen = packetlen($data);
 
237
        my $pdata = substr($data, 0, $plen);
 
238
        $data = substr($data, $plen);
 
239
 
 
240
        # remove the keyname, ttl, class, and algorithm name
 
241
        $data = substr($data, namelen($data));
 
242
        $data = substr($data, 6);
 
243
        $data = substr($data, namelen($data));
 
244
 
 
245
        # preserve the TSIG data
 
246
        my $tdata = substr($data, 0, 8);
 
247
 
 
248
        # prepare a new digest and sign with it
 
249
        $data = pack("n", $rmacsize) . $rmac . $pdata . $tdata;
 
250
        return Net::DNS::RR::TSIG::sign_hmac($key, $data);
 
251
}
 
252
 
 
253
sub handleTCP {
 
254
        my ($buf) = @_;
 
255
 
 
256
        my ($packet, $err) = new Net::DNS::Packet(\$buf, 0);
 
257
        $err and die $err;
 
258
        
 
259
        $packet->header->qr(1);
 
260
        $packet->header->aa(1);
 
261
        
 
262
        my @questions = $packet->question;
 
263
        my $qname = $questions[0]->qname;
 
264
        my $qtype = $questions[0]->qtype;
 
265
 
 
266
        # get the existing signature if any, and clear the additional section
 
267
        my $prev_tsig;
 
268
        my $signer;
 
269
        while (my $rr = $packet->pop("additional")) {
 
270
                if ($rr->type eq "TSIG") {
 
271
                        $prev_tsig = $rr;
 
272
                }
 
273
        }
 
274
 
 
275
        my @results = ();
 
276
        my $count_these = 0;
 
277
 
 
278
        my $r;
 
279
        foreach $r (@rules) {
 
280
                my $pattern = $r->{pattern};
 
281
                my($dbtype, $key_name, $key_data) = split(/ /,$pattern);
 
282
                print "[handleTCP] $dbtype, $key_name, $key_data \n";
 
283
                if ("$qname $qtype" =~ /$dbtype/) {
 
284
                        $count_these++;
 
285
                        my $a;
 
286
                        foreach $a (@{$r->{answer}}) {
 
287
                                $packet->push("answer", $a);
 
288
                        }
 
289
                        if(defined($key_name) && defined($key_data)) {
 
290
                                # sign the packet
 
291
                                print "  Signing the data with " . 
 
292
                                      "$key_name/$key_data\n";
 
293
 
 
294
                                my $tsig = Net::DNS::RR->
 
295
                                        new("$key_name TSIG $key_data");
 
296
 
 
297
                                # These kluges are necessary because Net::DNS
 
298
                                # doesn't know how to sign responses.  We
 
299
                                # clear compnames so that the TSIG key and
 
300
                                # algorithm name won't be compressed, and
 
301
                                # add one to arcount because the signing
 
302
                                # function will attempt to decrement it,
 
303
                                # which is incorrect in a response. Finally
 
304
                                # we set request_mac to the previous digest.
 
305
                                $packet->{"compnames"} = {};
 
306
                                $packet->{"header"}{"arcount"} += 1;
 
307
                                if (defined($prev_tsig)) {
 
308
                                        my $rmac = pack('n H*',
 
309
                                                $prev_tsig->mac_size,
 
310
                                                $prev_tsig->mac);
 
311
                                        $tsig->{"request_mac"} =
 
312
                                                unpack("H*", $rmac);
 
313
                                }
 
314
                                
 
315
                                $tsig->sign_func($signer) if defined($signer);
 
316
                                $packet->sign_tsig($tsig);
 
317
                                $signer = \&sign_tcp_continuation;
 
318
 
 
319
                                my $copy =
 
320
                                        Net::DNS::Packet->new(\($packet->data));
 
321
                                $prev_tsig = $copy->pop("additional");
 
322
                        }
 
323
                        #$packet->print;
 
324
                        push(@results,$packet->data);
 
325
                        $packet = new Net::DNS::Packet(\$buf, 0);
 
326
                        $packet->header->qr(1);
 
327
                        $packet->header->aa(1);
 
328
                }
 
329
        }
 
330
        print " A total of $count_these patterns matched\n";
 
331
        return \@results;
 
332
}
 
333
 
 
334
# Main
 
335
my $rin;
 
336
my $rout;
 
337
for (;;) {
 
338
        $rin = '';
 
339
        vec($rin, fileno($ctlsock), 1) = 1;
 
340
        vec($rin, fileno($tcpsock), 1) = 1;
 
341
        vec($rin, fileno($udpsock), 1) = 1;
 
342
 
 
343
        select($rout = $rin, undef, undef, undef);
 
344
 
 
345
        if (vec($rout, fileno($ctlsock), 1)) {
 
346
                warn "ctl conn";
 
347
                my $conn = $ctlsock->accept;
 
348
                my $rule = ();
 
349
                @rules = ();
 
350
                while (my $line = $conn->getline) {
 
351
                        chomp $line;
 
352
                        if ($line =~ m!^/(.*)/$!) {
 
353
                                $rule = { pattern => $1, answer => [] };
 
354
                                push(@rules, $rule);
 
355
                        } else {
 
356
                                push(@{$rule->{answer}},
 
357
                                     new Net::DNS::RR($line));
 
358
                        }
 
359
                }
 
360
                $conn->close;
 
361
                #print Dumper(@rules);
 
362
                #print "+=+=+ $rules[0]->{'pattern'}\n";
 
363
                #print "+=+=+ $rules[0]->{'answer'}->[0]->{'rname'}\n";
 
364
                #print "+=+=+ $rules[0]->{'answer'}->[0]\n";
 
365
        } elsif (vec($rout, fileno($udpsock), 1)) {
 
366
                printf "UDP request\n";
 
367
                my $buf;
 
368
                $udpsock->recv($buf, 512);
 
369
                my $result = handleUDP($buf);
 
370
                my $num_chars = $udpsock->send($result);
 
371
                print "  Sent $num_chars bytes via UDP\n";      
 
372
        } elsif (vec($rout, fileno($tcpsock), 1)) {
 
373
                my $conn = $tcpsock->accept;
 
374
                my $buf;
 
375
                for (;;) {
 
376
                        my $lenbuf;
 
377
                        my $n = $conn->sysread($lenbuf, 2);
 
378
                        last unless $n == 2;
 
379
                        my $len = unpack("n", $lenbuf);
 
380
                        $n = $conn->sysread($buf, $len);
 
381
                        last unless $n == $len;
 
382
                        print "TCP request\n";
 
383
                        my $result = handleTCP($buf);
 
384
                        foreach my $response (@$result) {
 
385
                                $len = length($response);
 
386
                                $n = $conn->syswrite(pack("n", $len), 2);
 
387
                                $n = $conn->syswrite($response, $len);
 
388
                                print "    Sent: $n chars via TCP\n";
 
389
                        }
 
390
                }
 
391
                $conn->close;
 
392
        }
 
393
}