blob: 60a1125dc21ee09a9161a9c2a6ab397caa89d008 [file] [log] [blame]
/****************************************************************
* This work is derived from 'jnamed.java' distributed in *
* 'dnsjava-2.0.5'. This original is licensed as follows: *
* Copyright (c) 1999-2005, Brian Wellington *
* All rights reserved. *
* *
* Redistribution and use in source and binary forms, with or *
* without modification, are permitted provided that the *
* following conditions are met: *
* *
* * Redistributions of source code must retain the above *
* copyright notice, this list of conditions and the *
* following disclaimer. *
* * Redistributions in binary form must reproduce the above *
* copyright notice, this list of conditions and the *
* following disclaimer in the documentation and/or other *
* materials provided with the distribution. *
* * Neither the name of the dnsjava project nor the names *
* of its contributors may be used to endorse or promote *
* products derived from this software without specific *
* prior written permission. *
* *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND *
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, *
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF *
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE *
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR *
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, *
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, *
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR *
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF *
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT *
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT *
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE *
* POSSIBILITY OF SUCH DAMAGE. *
* *
* Modifications are *
* Licensed to the Apache Software Foundation (ASF) under one *
* or more contributor license agreements. See the NOTICE file *
* distributed with this work for additional information *
* regarding copyright ownership. The ASF licenses this file *
* to you under the Apache License, Version 2.0 (the *
* "License"); you may not use this file except in compliance *
* with the License. You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, *
* software distributed under the License is distributed on an *
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
* KIND, either express or implied. See the License for the *
* specific language governing permissions and limitations *
* under the License. *
****************************************************************/
package org.apache.james.jspf.tester;
import org.xbill.DNS.AAAARecord;
import org.xbill.DNS.ARecord;
import org.xbill.DNS.Address;
import org.xbill.DNS.CNAMERecord;
import org.xbill.DNS.DClass;
import org.xbill.DNS.DNAMERecord;
import org.xbill.DNS.ExtendedFlags;
import org.xbill.DNS.Flags;
import org.xbill.DNS.Header;
import org.xbill.DNS.MXRecord;
import org.xbill.DNS.Message;
import org.xbill.DNS.NSRecord;
import org.xbill.DNS.Name;
import org.xbill.DNS.NameTooLongException;
import org.xbill.DNS.OPTRecord;
import org.xbill.DNS.Opcode;
import org.xbill.DNS.PTRRecord;
import org.xbill.DNS.RRset;
import org.xbill.DNS.Rcode;
import org.xbill.DNS.Record;
import org.xbill.DNS.SOARecord;
import org.xbill.DNS.SPFRecord;
import org.xbill.DNS.Section;
import org.xbill.DNS.SetResponse;
import org.xbill.DNS.TXTRecord;
import org.xbill.DNS.TextParseException;
import org.xbill.DNS.Type;
import org.xbill.DNS.Zone;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
public class DNSTestingServer implements ResponseGenerator {
static final int FLAG_DNSSECOK = 1;
static final int FLAG_SIGONLY = 2;
protected Zone zone;
private Set<Name> timeoutServers;
Random random = new Random();
public DNSTestingServer(String address, String porta)
throws TextParseException, IOException {
Integer port = new Integer(porta != null ? porta : "53");
InetAddress addr = Address.getByAddress(address != null ? address
: "0.0.0.0");
Thread t;
t = new Thread(new TCPListener(addr, port.intValue(), this));
t.setDaemon(true);
t.start();
t = new Thread(new UDPListener(addr, port.intValue(), this));
t.setDaemon(true);
t.start();
zone = null;
}
@SuppressWarnings("unchecked")
public synchronized void setData(Map<String, List<?>> map) {
try {
this.timeoutServers = new HashSet<Name>();
List<Record> records = new LinkedList<Record>();
records.add(new SOARecord(Name.root, DClass.IN, 3600, Name.root,
Name.root, 857623948, 0, 0, 0, 0));
records.add(new NSRecord(Name.root, DClass.IN, 3600, Name.root));
Iterator<String> hosts = map.keySet().iterator();
while (hosts.hasNext()) {
String host = (String) hosts.next();
Name hostname;
if (!host.endsWith(".")) {
hostname = Name.fromString(host + ".");
} else {
hostname = Name.fromString(host);
}
List<?> l = map.get(host);
if (l != null)
for (Iterator<?> i = l.iterator(); i.hasNext();) {
Object o = i.next();
if (o instanceof Map) {
Map<String, ?> hm = (Map) o;
Iterator<String> types = hm.keySet().iterator();
while (types.hasNext()) {
String type = (String) types.next();
if ("MX".equals(type)) {
List<?> mxList = (List<?>) hm.get(type);
Iterator<?> mxs = mxList.iterator();
while (mxs.hasNext()) {
Long prio = (Long) mxs.next();
String cname = (String) mxs.next();
if (cname != null) {
if (cname.length() > 0 && !cname.endsWith(".")) cname += ".";
records.add(new MXRecord(hostname,
DClass.IN, 3600, prio
.intValue(), Name
.fromString(cname)));
}
}
} else {
Object value = hm.get(type);
if ("A".equals(type)) {
records.add(new ARecord(hostname,
DClass.IN, 3600, Address
.getByAddress((String) value)));
} else if ("AAAA".equals(type)) {
records.add(new AAAARecord(hostname,
DClass.IN, 3600, Address
.getByAddress((String) value)));
} else if ("SPF".equals(type)) {
if (value instanceof List<?>) {
records.add(new SPFRecord(hostname,
DClass.IN, 3600L, (List<String>) value));
} else {
records.add(new SPFRecord(hostname,
DClass.IN, 3600, (String) value));
}
} else if ("TXT".equals(type)) {
if (value instanceof List<?>) {
records.add(new TXTRecord(hostname,
DClass.IN, 3600L, (List<String>) value));
} else {
records.add(new TXTRecord(hostname,
DClass.IN, 3600, (String) value));
}
} else {
if (!((String) value).endsWith(".")) {
value = ((String) value)+".";
}
if ("PTR".equals(type)) {
records
.add(new PTRRecord(
hostname,
DClass.IN,
3600,
Name
.fromString((String) value)));
} else if ("CNAME".equals(type)) {
records.add(new CNAMERecord(
hostname, DClass.IN, 3600,
Name.fromString((String) value)));
} else {
throw new IllegalStateException(
"Unsupported type: " + type);
}
}
}
}
} else if ("TIMEOUT".equals(o)) {
timeoutServers.add(hostname);
} else {
throw new IllegalStateException(
"getRecord found an unexpected data");
}
}
}
zone = new Zone(Name.root, (Record[]) records
.toArray(new Record[] {}));
} catch (TextParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private SOARecord findSOARecord() {
return zone.getSOA();
}
private RRset findNSRecords() {
return zone.getNS();
}
// TODO verify why enabling this lookup will make some test to fail!
private RRset findARecord(Name name) {
return null;
//return zone.findExactMatch(name, Type.A);
}
private SetResponse findRecords(Name name, int type) {
SetResponse sr = zone.findRecords(name, type);
if (sr == null || sr.answers() == null || sr.answers().size() == 0) {
boolean timeout = timeoutServers.contains(name);
if (timeout) {
try {
Thread.sleep(2100);
}
catch (InterruptedException e) {
}
return null;
}
}
try {
Thread.sleep(random.nextInt(500));
}
catch (Exception e) {}
return sr;
}
@SuppressWarnings("unchecked")
void addRRset(Name name, Message response, RRset rrset, int section,
int flags) {
for (int s = 1; s <= section; s++)
if (response.findRRset(name, rrset.getType(), s))
return;
if ((flags & FLAG_SIGONLY) == 0) {
Iterator<Record> it = rrset.rrs().iterator();
while (it.hasNext()) {
Record r = (Record) it.next();
if (r.getName().isWild() && !name.isWild())
r = r.withName(name);
response.addRecord(r, section);
}
}
if ((flags & (FLAG_SIGONLY | FLAG_DNSSECOK)) != 0) {
Iterator it = rrset.sigs().iterator();
while (it.hasNext()) {
Record r = (Record) it.next();
if (r.getName().isWild() && !name.isWild())
r = r.withName(name);
response.addRecord(r, section);
}
}
}
private void addGlue(Message response, Name name, int flags) {
RRset a = findARecord(name);
if (a == null)
return;
addRRset(name, response, a, Section.ADDITIONAL, flags);
}
private void addAdditional2(Message response, int section, int flags) {
Record[] records = response.getSectionArray(section);
for (int i = 0; i < records.length; i++) {
Record r = records[i];
Name glueName = r.getAdditionalName();
if (glueName != null)
addGlue(response, glueName, flags);
}
}
private final void addAdditional(Message response, int flags) {
addAdditional2(response, Section.ANSWER, flags);
addAdditional2(response, Section.AUTHORITY, flags);
}
byte addAnswer(Message response, Name name, int type, int dclass,
int iterations, int flags) {
SetResponse sr;
byte rcode = Rcode.NOERROR;
if (iterations > 6)
return Rcode.NOERROR;
if (type == Type.SIG || type == Type.RRSIG) {
type = Type.ANY;
flags |= FLAG_SIGONLY;
}
sr = findRecords(name, type);
// TIMEOUT
if (sr == null) {
return -1;
}
if (sr.isNXDOMAIN() || sr.isNXRRSET()) {
if (sr.isNXDOMAIN())
response.getHeader().setRcode(Rcode.NXDOMAIN);
response.addRecord(findSOARecord(), Section.AUTHORITY);
if (iterations == 0)
response.getHeader().setFlag(Flags.AA);
rcode = Rcode.NXDOMAIN;
} else if (sr.isDelegation()) {
RRset nsRecords = sr.getNS();
addRRset(nsRecords.getName(), response, nsRecords,
Section.AUTHORITY, flags);
} else if (sr.isCNAME()) {
CNAMERecord cname = sr.getCNAME();
RRset rrset = new RRset(cname);
addRRset(name, response, rrset, Section.ANSWER, flags);
if (iterations == 0)
response.getHeader().setFlag(Flags.AA);
rcode = addAnswer(response, cname.getTarget(), type, dclass,
iterations + 1, flags);
} else if (sr.isDNAME()) {
DNAMERecord dname = sr.getDNAME();
RRset rrset = new RRset(dname);
addRRset(name, response, rrset, Section.ANSWER, flags);
Name newname;
try {
newname = name.fromDNAME(dname);
} catch (NameTooLongException e) {
return Rcode.YXDOMAIN;
}
rrset = new RRset(new CNAMERecord(name, dclass, 0, newname));
addRRset(name, response, rrset, Section.ANSWER, flags);
if (iterations == 0)
response.getHeader().setFlag(Flags.AA);
rcode = addAnswer(response, newname, type, dclass, iterations + 1,
flags);
} else if (sr.isSuccessful()) {
List<RRset> rrsets = sr.answers();
for (int i = 0; i < rrsets.size(); i++)
addRRset(name, response, rrsets.get(i), Section.ANSWER, flags);
RRset findNSRecords = findNSRecords();
addRRset(findNSRecords.getName(), response, findNSRecords,
Section.AUTHORITY, flags);
if (iterations == 0)
response.getHeader().setFlag(Flags.AA);
}
return rcode;
}
public byte[] generateReply(Message query, int length, Socket s)
throws IOException {
Header header;
int maxLength;
int flags = 0;
header = query.getHeader();
if (header.getFlag(Flags.QR))
return null;
if (header.getRcode() != Rcode.NOERROR)
return errorMessage(query, Rcode.FORMERR);
if (header.getOpcode() != Opcode.QUERY)
return errorMessage(query, Rcode.NOTIMP);
Record queryRecord = query.getQuestion();
OPTRecord queryOPT = query.getOPT();
if (queryOPT != null && queryOPT.getVersion() > 0) {
}
if (s != null)
maxLength = 65535;
else if (queryOPT != null)
maxLength = Math.max(queryOPT.getPayloadSize(), 512);
else
maxLength = 512;
if (queryOPT != null && (queryOPT.getFlags() & ExtendedFlags.DO) != 0)
flags = FLAG_DNSSECOK;
Message response = new Message(query.getHeader().getID());
response.getHeader().setFlag(Flags.QR);
if (query.getHeader().getFlag(Flags.RD))
response.getHeader().setFlag(Flags.RD);
response.addRecord(queryRecord, Section.QUESTION);
Name name = queryRecord.getName();
int type = queryRecord.getType();
int dclass = queryRecord.getDClass();
if (!Type.isRR(type) && type != Type.ANY)
return errorMessage(query, Rcode.NOTIMP);
byte rcode = addAnswer(response, name, type, dclass, 0, flags);
// TIMEOUT
if (rcode == -1) {
return null;
}
if (rcode != Rcode.NOERROR && rcode != Rcode.NXDOMAIN)
return errorMessage(query, rcode);
addAdditional(response, flags);
if (queryOPT != null) {
int optflags = (flags == FLAG_DNSSECOK) ? ExtendedFlags.DO : 0;
OPTRecord opt = new OPTRecord((short) 4096, rcode, (byte) 0,
optflags);
response.addRecord(opt, Section.ADDITIONAL);
}
return response.toWire(maxLength);
}
byte[] buildErrorMessage(Header header, int rcode, Record question) {
Message response = new Message();
response.setHeader(header);
for (int i = 0; i < 4; i++)
response.removeAllRecords(i);
if (rcode == Rcode.SERVFAIL)
response.addRecord(question, Section.QUESTION);
header.setRcode(rcode);
return response.toWire();
}
public byte[] formerrMessage(byte[] in) {
Header header;
try {
header = new Header(in);
} catch (IOException e) {
return null;
}
return buildErrorMessage(header, Rcode.FORMERR, null);
}
public byte[] errorMessage(Message query, int rcode) {
return buildErrorMessage(query.getHeader(), rcode, query.getQuestion());
}
public byte[] generateReply(byte[] in, int length) {
Message query;
byte[] response = null;
try {
query = new Message(in);
response = generateReply(query, length, null);
} catch (IOException e) {
response = formerrMessage(in);
}
return response;
}
}