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