1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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
238 e.printStackTrace();
239 } catch (UnknownHostException e) {
240
241 e.printStackTrace();
242 } catch (IOException e) {
243
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
257 private RRset findARecord(Name name) {
258 return null;
259
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
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
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 }