View Javadoc

1   /**************************************************************** 
2    * This work is derived from 'jnamed.java' distributed in       *
3    * 'dnsjava-2.0.5'. This original is licensed as follows:       *
4    * Copyright (c) 1999-2005, Brian Wellington                    *
5    * All rights reserved.                                         *
6    *                                                              *
7    * Redistribution and use in source and binary forms, with or   * 
8    * without modification, are permitted provided that the        *  
9    * following conditions are met:                                * 
10   *                                                              * 
11   *  * Redistributions of source code must retain the above      *
12   *    copyright notice, this list of conditions and the         *
13   *    following disclaimer.                                     *
14   *  * Redistributions in binary form must reproduce the above   *
15   *    copyright notice, this list of conditions and the         *
16   *    following disclaimer in the documentation and/or other    *
17   *    materials provided with the distribution.                 *
18   *  * Neither the name of the dnsjava project nor the names     *
19   *    of its contributors may be used to endorse or promote     *
20   *    products derived from this software without specific      *
21   *    prior written permission.                                 *
22   *                                                              *
23   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND       *
24   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,  *
25   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF     *
26   * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE     *
27   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR         *
28   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, *
29   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,     *
30   * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR       *
31   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS         *
32   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF            *
33   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT    *
34   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT   *
35   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          *
36   * POSSIBILITY OF SUCH DAMAGE.                                  *
37   *                                                              *
38   * Modifications are                                            * 
39   * Licensed to the Apache Software Foundation (ASF) under one   *
40   * or more contributor license agreements.  See the NOTICE file *
41   * distributed with this work for additional information        *
42   * regarding copyright ownership.  The ASF licenses this file   *
43   * to you under the Apache License, Version 2.0 (the            *
44   * "License"); you may not use this file except in compliance   *
45   * with the License.  You may obtain a copy of the License at   *
46   *                                                              *
47   *   http://www.apache.org/licenses/LICENSE-2.0                 *
48   *                                                              *
49   * Unless required by applicable law or agreed to in writing,   *
50   * software distributed under the License is distributed on an  *
51   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
52   * KIND, either express or implied.  See the License for the    *
53   * specific language governing permissions and limitations      *
54   * under the License.                                           *
55   ****************************************************************/
56  
57  package org.apache.james.jspf.tester;
58  
59  import org.xbill.DNS.AAAARecord;
60  import org.xbill.DNS.ARecord;
61  import org.xbill.DNS.Address;
62  import org.xbill.DNS.CNAMERecord;
63  import org.xbill.DNS.DClass;
64  import org.xbill.DNS.DNAMERecord;
65  import org.xbill.DNS.ExtendedFlags;
66  import org.xbill.DNS.Flags;
67  import org.xbill.DNS.Header;
68  import org.xbill.DNS.MXRecord;
69  import org.xbill.DNS.Message;
70  import org.xbill.DNS.NSRecord;
71  import org.xbill.DNS.Name;
72  import org.xbill.DNS.NameTooLongException;
73  import org.xbill.DNS.OPTRecord;
74  import org.xbill.DNS.Opcode;
75  import org.xbill.DNS.PTRRecord;
76  import org.xbill.DNS.RRset;
77  import org.xbill.DNS.Rcode;
78  import org.xbill.DNS.Record;
79  import org.xbill.DNS.SOARecord;
80  import org.xbill.DNS.SPFRecord;
81  import org.xbill.DNS.Section;
82  import org.xbill.DNS.SetResponse;
83  import org.xbill.DNS.TXTRecord;
84  import org.xbill.DNS.TextParseException;
85  import org.xbill.DNS.Type;
86  import org.xbill.DNS.Zone;
87  
88  import java.io.IOException;
89  import java.net.InetAddress;
90  import java.net.Socket;
91  import java.net.UnknownHostException;
92  import java.util.HashMap;
93  import java.util.HashSet;
94  import java.util.Iterator;
95  import java.util.LinkedList;
96  import java.util.List;
97  import java.util.Random;
98  
99  public class DNSTestingServer implements ResponseGenerator {
100 
101     static final int FLAG_DNSSECOK = 1;
102 
103     static final int FLAG_SIGONLY = 2;
104 
105     protected Zone zone;
106     
107     private HashSet timeoutServers;
108     
109     Random random = new Random();
110 
111     public DNSTestingServer(String address, String porta)
112             throws TextParseException, IOException {
113 
114         Integer port = new Integer(porta != null ? porta : "53");
115         InetAddress addr = Address.getByAddress(address != null ? address
116                 : "0.0.0.0");
117 
118         Thread t;
119         t = new Thread(new TCPListener(addr, port.intValue(), this));
120         t.setDaemon(true);
121         t.start();
122 
123         t = new Thread(new UDPListener(addr, port.intValue(), this));
124         t.setDaemon(true);
125         t.start();
126 
127         zone = null;
128     }
129 
130     public synchronized void setData(HashMap zonedata) {
131         try {
132             this.timeoutServers = new HashSet();
133             List records = new LinkedList();
134 
135             records.add(new SOARecord(Name.root, DClass.IN, 3600, Name.root,
136                     Name.root, 857623948, 0, 0, 0, 0));
137             records.add(new NSRecord(Name.root, DClass.IN, 3600, Name.root));
138 
139             Iterator hosts = zonedata.keySet().iterator();
140             while (hosts.hasNext()) {
141                 String host = (String) hosts.next();
142                 Name hostname;
143                 if (!host.endsWith(".")) {
144                     hostname = Name.fromString(host + ".");
145                 } else {
146                     hostname = Name.fromString(host);
147                 }
148 
149                 List l = (List) zonedata.get(host);
150                 if (l != null)
151                     for (Iterator i = l.iterator(); i.hasNext();) {
152                         Object o = i.next();
153                         if (o instanceof HashMap) {
154                             HashMap hm = (HashMap) o;
155 
156                             Iterator types = hm.keySet().iterator();
157 
158                             while (types.hasNext()) {
159                                 String type = (String) types.next();
160                                 if ("MX".equals(type)) {
161                                     List mxList = (List) hm.get(type);
162                                     Iterator mxs = mxList.iterator();
163                                     while (mxs.hasNext()) {
164                                         Long prio = (Long) mxs.next();
165                                         String cname = (String) mxs.next();
166                                         if (cname != null) {
167                                             if (cname.length() > 0 &&  !cname.endsWith(".")) cname += ".";
168                                             
169                                             records.add(new MXRecord(hostname,
170                                                     DClass.IN, 3600, prio
171                                                             .intValue(), Name
172                                                             .fromString(cname)));
173                                         }
174                                     }
175                                 } else {
176                                     Object value = hm.get(type);
177                                     if ("A".equals(type)) {
178                                         records.add(new ARecord(hostname,
179                                                 DClass.IN, 3600, Address
180                                                         .getByAddress((String) value)));
181                                     } else if ("AAAA".equals(type)) {
182                                         records.add(new AAAARecord(hostname,
183                                                 DClass.IN, 3600, Address
184                                                         .getByAddress((String) value)));
185                                     } else if ("SPF".equals(type)) {
186                                         if (value instanceof List) {
187                                             records.add(new SPFRecord(hostname,
188                                                     DClass.IN, 3600, (List) value));
189                                         } else {
190                                             records.add(new SPFRecord(hostname,
191                                                     DClass.IN, 3600, (String) value));
192                                         }
193                                     } else if ("TXT".equals(type)) {
194                                         if (value instanceof List) {
195                                             records.add(new TXTRecord(hostname,
196                                                     DClass.IN, 3600, (List) value));
197                                         } else {
198                                             records.add(new TXTRecord(hostname,
199                                                     DClass.IN, 3600, (String) value));
200                                         }
201                                     } else {
202                                         if (!((String) value).endsWith(".")) {
203                                             value = ((String) value)+".";
204                                         }
205                                         if ("PTR".equals(type)) {
206                                             records
207                                                     .add(new PTRRecord(
208                                                             hostname,
209                                                             DClass.IN,
210                                                             3600,
211                                                             Name
212                                                                     .fromString((String) value)));
213                                         } else if ("CNAME".equals(type)) {
214                                             records.add(new CNAMERecord(
215                                                     hostname, DClass.IN, 3600,
216                                                     Name.fromString((String) value)));
217                                         } else {
218                                             throw new IllegalStateException(
219                                                     "Unsupported type: " + type);
220                                         }
221                                     }
222                                 }
223                             }
224                         } else if ("TIMEOUT".equals(o)) {
225                             timeoutServers.add(hostname);
226                         } else {
227                             throw new IllegalStateException(
228                                     "getRecord found an unexpected data");
229                         }
230                     }
231             }
232 
233             zone = new Zone(Name.root, (Record[]) records
234                     .toArray(new Record[] {}));
235             
236         } catch (TextParseException e) {
237             // TODO Auto-generated catch block
238             e.printStackTrace();
239         } catch (UnknownHostException e) {
240             // TODO Auto-generated catch block
241             e.printStackTrace();
242         } catch (IOException e) {
243             // TODO Auto-generated catch block
244             e.printStackTrace();
245         }
246     }
247 
248     private SOARecord findSOARecord() {
249         return zone.getSOA();
250     }
251 
252     private RRset findNSRecords() {
253         return zone.getNS();
254     }
255 
256     // TODO verify why enabling this lookup will make some test to fail!
257     private RRset findARecord(Name name) {
258         return null;
259         //return zone.findExactMatch(name, Type.A);
260     }
261 
262     private SetResponse findRecords(Name name, int type) {
263         SetResponse sr = zone.findRecords(name, type);
264         
265         if (sr == null || sr.answers() == null || sr.answers().length == 0) {
266             boolean timeout = timeoutServers.contains(name);
267             if (timeout) {
268                 try {
269                     Thread.sleep(2100);
270                 }
271                 catch (InterruptedException e) {
272                 }
273                 return null;
274             }
275         }
276         
277         try {
278             Thread.sleep(random.nextInt(500));
279         }
280         catch (Exception e) {} 
281         
282         return sr;
283     }
284 
285     void addRRset(Name name, Message response, RRset rrset, int section,
286             int flags) {
287         for (int s = 1; s <= section; s++)
288             if (response.findRRset(name, rrset.getType(), s))
289                 return;
290         if ((flags & FLAG_SIGONLY) == 0) {
291             Iterator it = rrset.rrs();
292             while (it.hasNext()) {
293                 Record r = (Record) it.next();
294                 if (r.getName().isWild() && !name.isWild())
295                     r = r.withName(name);
296                 response.addRecord(r, section);
297             }
298         }
299         if ((flags & (FLAG_SIGONLY | FLAG_DNSSECOK)) != 0) {
300             Iterator it = rrset.sigs();
301             while (it.hasNext()) {
302                 Record r = (Record) it.next();
303                 if (r.getName().isWild() && !name.isWild())
304                     r = r.withName(name);
305                 response.addRecord(r, section);
306             }
307         }
308     }
309 
310     private void addGlue(Message response, Name name, int flags) {
311         RRset a = findARecord(name);
312         if (a == null)
313             return;
314         addRRset(name, response, a, Section.ADDITIONAL, flags);
315     }
316 
317     private void addAdditional2(Message response, int section, int flags) {
318         Record[] records = response.getSectionArray(section);
319         for (int i = 0; i < records.length; i++) {
320             Record r = records[i];
321             Name glueName = r.getAdditionalName();
322             if (glueName != null)
323                 addGlue(response, glueName, flags);
324         }
325     }
326 
327     private final void addAdditional(Message response, int flags) {
328         addAdditional2(response, Section.ANSWER, flags);
329         addAdditional2(response, Section.AUTHORITY, flags);
330     }
331 
332     byte addAnswer(Message response, Name name, int type, int dclass,
333             int iterations, int flags) {
334         SetResponse sr;
335         byte rcode = Rcode.NOERROR;
336 
337         if (iterations > 6)
338             return Rcode.NOERROR;
339 
340         if (type == Type.SIG || type == Type.RRSIG) {
341             type = Type.ANY;
342             flags |= FLAG_SIGONLY;
343         }
344 
345         sr = findRecords(name, type);
346 
347         // TIMEOUT
348         if (sr == null) {
349             return -1;
350         }
351         
352         if (sr.isNXDOMAIN() || sr.isNXRRSET()) {
353             if (sr.isNXDOMAIN())
354                 response.getHeader().setRcode(Rcode.NXDOMAIN);
355 
356             response.addRecord(findSOARecord(), Section.AUTHORITY);
357 
358             if (iterations == 0)
359                 response.getHeader().setFlag(Flags.AA);
360 
361             rcode = Rcode.NXDOMAIN;
362 
363         } else if (sr.isDelegation()) {
364             RRset nsRecords = sr.getNS();
365             addRRset(nsRecords.getName(), response, nsRecords,
366                     Section.AUTHORITY, flags);
367         } else if (sr.isCNAME()) {
368             CNAMERecord cname = sr.getCNAME();
369             RRset rrset = new RRset(cname);
370             addRRset(name, response, rrset, Section.ANSWER, flags);
371             if (iterations == 0)
372                 response.getHeader().setFlag(Flags.AA);
373             rcode = addAnswer(response, cname.getTarget(), type, dclass,
374                     iterations + 1, flags);
375         } else if (sr.isDNAME()) {
376             DNAMERecord dname = sr.getDNAME();
377             RRset rrset = new RRset(dname);
378             addRRset(name, response, rrset, Section.ANSWER, flags);
379             Name newname;
380             try {
381                 newname = name.fromDNAME(dname);
382             } catch (NameTooLongException e) {
383                 return Rcode.YXDOMAIN;
384             }
385             rrset = new RRset(new CNAMERecord(name, dclass, 0, newname));
386             addRRset(name, response, rrset, Section.ANSWER, flags);
387             if (iterations == 0)
388                 response.getHeader().setFlag(Flags.AA);
389             rcode = addAnswer(response, newname, type, dclass, iterations + 1,
390                     flags);
391         } else if (sr.isSuccessful()) {
392             RRset[] rrsets = sr.answers();
393             for (int i = 0; i < rrsets.length; i++)
394                 addRRset(name, response, rrsets[i], Section.ANSWER, flags);
395 
396             RRset findNSRecords = findNSRecords();
397             addRRset(findNSRecords.getName(), response, findNSRecords,
398                     Section.AUTHORITY, flags);
399 
400             if (iterations == 0)
401                 response.getHeader().setFlag(Flags.AA);
402         }
403         return rcode;
404     }
405 
406     public byte[] generateReply(Message query, int length, Socket s)
407             throws IOException {
408         Header header;
409         int maxLength;
410         int flags = 0;
411 
412         header = query.getHeader();
413         if (header.getFlag(Flags.QR))
414             return null;
415         if (header.getRcode() != Rcode.NOERROR)
416             return errorMessage(query, Rcode.FORMERR);
417         if (header.getOpcode() != Opcode.QUERY)
418             return errorMessage(query, Rcode.NOTIMP);
419 
420         Record queryRecord = query.getQuestion();
421 
422         OPTRecord queryOPT = query.getOPT();
423         if (queryOPT != null && queryOPT.getVersion() > 0) {
424         }
425 
426         if (s != null)
427             maxLength = 65535;
428         else if (queryOPT != null)
429             maxLength = Math.max(queryOPT.getPayloadSize(), 512);
430         else
431             maxLength = 512;
432 
433         if (queryOPT != null && (queryOPT.getFlags() & ExtendedFlags.DO) != 0)
434             flags = FLAG_DNSSECOK;
435 
436         Message response = new Message(query.getHeader().getID());
437         response.getHeader().setFlag(Flags.QR);
438         if (query.getHeader().getFlag(Flags.RD))
439             response.getHeader().setFlag(Flags.RD);
440         response.addRecord(queryRecord, Section.QUESTION);
441 
442         Name name = queryRecord.getName();
443         int type = queryRecord.getType();
444         int dclass = queryRecord.getDClass();
445         if (!Type.isRR(type) && type != Type.ANY)
446             return errorMessage(query, Rcode.NOTIMP);
447 
448         byte rcode = addAnswer(response, name, type, dclass, 0, flags);
449         
450         // TIMEOUT
451         if (rcode == -1) {
452             return null;
453         }
454         
455         if (rcode != Rcode.NOERROR && rcode != Rcode.NXDOMAIN)
456             return errorMessage(query, rcode);
457 
458         addAdditional(response, flags);
459 
460         if (queryOPT != null) {
461             int optflags = (flags == FLAG_DNSSECOK) ? ExtendedFlags.DO : 0;
462             OPTRecord opt = new OPTRecord((short) 4096, rcode, (byte) 0,
463                     optflags);
464             response.addRecord(opt, Section.ADDITIONAL);
465         }
466 
467         return response.toWire(maxLength);
468     }
469 
470     byte[] buildErrorMessage(Header header, int rcode, Record question) {
471         Message response = new Message();
472         response.setHeader(header);
473         for (int i = 0; i < 4; i++)
474             response.removeAllRecords(i);
475         if (rcode == Rcode.SERVFAIL)
476             response.addRecord(question, Section.QUESTION);
477         header.setRcode(rcode);
478         return response.toWire();
479     }
480 
481     public byte[] formerrMessage(byte[] in) {
482         Header header;
483         try {
484             header = new Header(in);
485         } catch (IOException e) {
486             return null;
487         }
488         return buildErrorMessage(header, Rcode.FORMERR, null);
489     }
490 
491     public byte[] errorMessage(Message query, int rcode) {
492         return buildErrorMessage(query.getHeader(), rcode, query.getQuestion());
493     }
494 
495     public byte[] generateReply(byte[] in, int length) {
496         Message query;
497         byte[] response = null;
498         try {
499             query = new Message(in);
500             response = generateReply(query, length, null);
501         } catch (IOException e) {
502             response = formerrMessage(in);
503         }
504         return response;
505     }
506 
507 }