aboutsummaryrefslogtreecommitdiff
path: root/src/org/xbill/DNS
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/xbill/DNS')
-rw-r--r--src/org/xbill/DNS/A6Record.java127
-rw-r--r--src/org/xbill/DNS/AAAARecord.java67
-rw-r--r--src/org/xbill/DNS/AFSDBRecord.java46
-rw-r--r--src/org/xbill/DNS/APLRecord.java287
-rw-r--r--src/org/xbill/DNS/ARecord.java90
-rw-r--r--src/org/xbill/DNS/Address.java402
-rw-r--r--src/org/xbill/DNS/CERTRecord.java224
-rw-r--r--src/org/xbill/DNS/CNAMERecord.java45
-rw-r--r--src/org/xbill/DNS/Cache.java846
-rw-r--r--src/org/xbill/DNS/Client.java58
-rw-r--r--src/org/xbill/DNS/ClientSubnetOption.java175
-rw-r--r--src/org/xbill/DNS/Compression.java72
-rw-r--r--src/org/xbill/DNS/Credibility.java50
-rw-r--r--src/org/xbill/DNS/DClass.java92
-rw-r--r--src/org/xbill/DNS/DHCIDRecord.java65
-rw-r--r--src/org/xbill/DNS/DLVRecord.java132
-rw-r--r--src/org/xbill/DNS/DNAMERecord.java45
-rw-r--r--src/org/xbill/DNS/DNSInput.java239
-rw-r--r--src/org/xbill/DNS/DNSKEYRecord.java91
-rw-r--r--src/org/xbill/DNS/DNSOutput.java203
-rw-r--r--src/org/xbill/DNS/DNSSEC.java1026
-rw-r--r--src/org/xbill/DNS/DSRecord.java157
-rw-r--r--src/org/xbill/DNS/EDNSOption.java215
-rw-r--r--src/org/xbill/DNS/EmptyRecord.java42
-rw-r--r--src/org/xbill/DNS/ExtendedFlags.java45
-rw-r--r--src/org/xbill/DNS/ExtendedResolver.java419
-rw-r--r--src/org/xbill/DNS/Flags.java81
-rw-r--r--src/org/xbill/DNS/FormattedTime.java79
-rw-r--r--src/org/xbill/DNS/GPOSRecord.java178
-rw-r--r--src/org/xbill/DNS/Generator.java264
-rw-r--r--src/org/xbill/DNS/GenericEDNSOption.java47
-rw-r--r--src/org/xbill/DNS/HINFORecord.java95
-rw-r--r--src/org/xbill/DNS/Header.java286
-rw-r--r--src/org/xbill/DNS/IPSECKEYRecord.java231
-rw-r--r--src/org/xbill/DNS/ISDNRecord.java105
-rw-r--r--src/org/xbill/DNS/InvalidDClassException.java18
-rw-r--r--src/org/xbill/DNS/InvalidTTLException.java18
-rw-r--r--src/org/xbill/DNS/InvalidTypeException.java18
-rw-r--r--src/org/xbill/DNS/KEYBase.java161
-rw-r--r--src/org/xbill/DNS/KEYRecord.java352
-rw-r--r--src/org/xbill/DNS/KXRecord.java51
-rw-r--r--src/org/xbill/DNS/LOCRecord.java314
-rw-r--r--src/org/xbill/DNS/Lookup.java647
-rw-r--r--src/org/xbill/DNS/MBRecord.java42
-rw-r--r--src/org/xbill/DNS/MDRecord.java43
-rw-r--r--src/org/xbill/DNS/MFRecord.java43
-rw-r--r--src/org/xbill/DNS/MGRecord.java38
-rw-r--r--src/org/xbill/DNS/MINFORecord.java90
-rw-r--r--src/org/xbill/DNS/MRRecord.java38
-rw-r--r--src/org/xbill/DNS/MXRecord.java57
-rw-r--r--src/org/xbill/DNS/Master.java427
-rw-r--r--src/org/xbill/DNS/Message.java611
-rw-r--r--src/org/xbill/DNS/Mnemonic.java210
-rw-r--r--src/org/xbill/DNS/NAPTRRecord.java154
-rw-r--r--src/org/xbill/DNS/NSAPRecord.java106
-rw-r--r--src/org/xbill/DNS/NSAP_PTRRecord.java38
-rw-r--r--src/org/xbill/DNS/NSEC3PARAMRecord.java165
-rw-r--r--src/org/xbill/DNS/NSEC3Record.java266
-rw-r--r--src/org/xbill/DNS/NSECRecord.java98
-rw-r--r--src/org/xbill/DNS/NSIDOption.java29
-rw-r--r--src/org/xbill/DNS/NSRecord.java42
-rw-r--r--src/org/xbill/DNS/NULLRecord.java67
-rw-r--r--src/org/xbill/DNS/NXTRecord.java111
-rw-r--r--src/org/xbill/DNS/Name.java822
-rw-r--r--src/org/xbill/DNS/NameTooLongException.java24
-rw-r--r--src/org/xbill/DNS/OPTRecord.java191
-rw-r--r--src/org/xbill/DNS/Opcode.java60
-rw-r--r--src/org/xbill/DNS/Options.java123
-rw-r--r--src/org/xbill/DNS/PTRRecord.java38
-rw-r--r--src/org/xbill/DNS/PXRecord.java96
-rw-r--r--src/org/xbill/DNS/RPRecord.java82
-rw-r--r--src/org/xbill/DNS/RRSIGRecord.java50
-rw-r--r--src/org/xbill/DNS/RRset.java258
-rw-r--r--src/org/xbill/DNS/RTRecord.java48
-rw-r--r--src/org/xbill/DNS/Rcode.java123
-rw-r--r--src/org/xbill/DNS/Record.java736
-rw-r--r--src/org/xbill/DNS/RelativeNameException.java24
-rw-r--r--src/org/xbill/DNS/ResolveThread.java45
-rw-r--r--src/org/xbill/DNS/Resolver.java95
-rw-r--r--src/org/xbill/DNS/ResolverConfig.java509
-rw-r--r--src/org/xbill/DNS/ResolverListener.java30
-rw-r--r--src/org/xbill/DNS/ReverseMap.java130
-rw-r--r--src/org/xbill/DNS/SIG0.java79
-rw-r--r--src/org/xbill/DNS/SIGBase.java193
-rw-r--r--src/org/xbill/DNS/SIGRecord.java50
-rw-r--r--src/org/xbill/DNS/SOARecord.java162
-rw-r--r--src/org/xbill/DNS/SPFRecord.java44
-rw-r--r--src/org/xbill/DNS/SRVRecord.java114
-rw-r--r--src/org/xbill/DNS/SSHFPRecord.java108
-rw-r--r--src/org/xbill/DNS/Section.java92
-rw-r--r--src/org/xbill/DNS/Serial.java61
-rw-r--r--src/org/xbill/DNS/SetResponse.java202
-rw-r--r--src/org/xbill/DNS/SimpleResolver.java351
-rw-r--r--src/org/xbill/DNS/SingleCompressedNameBase.java31
-rw-r--r--src/org/xbill/DNS/SingleNameBase.java61
-rw-r--r--src/org/xbill/DNS/TCPClient.java132
-rw-r--r--src/org/xbill/DNS/TKEYRecord.java225
-rw-r--r--src/org/xbill/DNS/TLSARecord.java156
-rw-r--r--src/org/xbill/DNS/TSIG.java592
-rw-r--r--src/org/xbill/DNS/TSIGRecord.java220
-rw-r--r--src/org/xbill/DNS/TTL.java113
-rw-r--r--src/org/xbill/DNS/TXTBase.java123
-rw-r--r--src/org/xbill/DNS/TXTRecord.java44
-rw-r--r--src/org/xbill/DNS/TextParseException.java25
-rw-r--r--src/org/xbill/DNS/Tokenizer.java713
-rw-r--r--src/org/xbill/DNS/Type.java361
-rw-r--r--src/org/xbill/DNS/TypeBitmap.java147
-rw-r--r--src/org/xbill/DNS/U16NameBase.java75
-rw-r--r--src/org/xbill/DNS/UDPClient.java164
-rw-r--r--src/org/xbill/DNS/UNKRecord.java54
-rw-r--r--src/org/xbill/DNS/Update.java300
-rw-r--r--src/org/xbill/DNS/WKSRecord.java719
-rw-r--r--src/org/xbill/DNS/WireParseException.java31
-rw-r--r--src/org/xbill/DNS/X25Record.java86
-rw-r--r--src/org/xbill/DNS/Zone.java559
-rw-r--r--src/org/xbill/DNS/ZoneTransferException.java23
-rw-r--r--src/org/xbill/DNS/ZoneTransferIn.java680
-rw-r--r--src/org/xbill/DNS/spi/DNSJavaNameService.java176
-rw-r--r--src/org/xbill/DNS/spi/services/sun.net.spi.nameservice.NameServiceDescriptor1
-rw-r--r--src/org/xbill/DNS/tests/primary.java59
-rw-r--r--src/org/xbill/DNS/tests/xfrin.java109
-rw-r--r--src/org/xbill/DNS/utils/HMAC.java182
-rw-r--r--src/org/xbill/DNS/utils/base16.java73
-rw-r--r--src/org/xbill/DNS/utils/base32.java213
-rw-r--r--src/org/xbill/DNS/utils/base64.java145
-rw-r--r--src/org/xbill/DNS/utils/hexdump.java56
-rw-r--r--src/org/xbill/DNS/windows/DNSServer.properties4
-rw-r--r--src/org/xbill/DNS/windows/DNSServer_de.properties4
-rw-r--r--src/org/xbill/DNS/windows/DNSServer_fr.properties4
-rw-r--r--src/org/xbill/DNS/windows/DNSServer_ja.properties4
-rw-r--r--src/org/xbill/DNS/windows/DNSServer_pl.properties4
131 files changed, 22488 insertions, 0 deletions
diff --git a/src/org/xbill/DNS/A6Record.java b/src/org/xbill/DNS/A6Record.java
new file mode 100644
index 0000000..a1c613a
--- /dev/null
+++ b/src/org/xbill/DNS/A6Record.java
@@ -0,0 +1,127 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.net.*;
+
+/**
+ * A6 Record - maps a domain name to an IPv6 address (experimental)
+ *
+ * @author Brian Wellington
+ */
+
+public class A6Record extends Record {
+
+private static final long serialVersionUID = -8815026887337346789L;
+
+private int prefixBits;
+private InetAddress suffix;
+private Name prefix;
+
+A6Record() {}
+
+Record
+getObject() {
+ return new A6Record();
+}
+
+/**
+ * Creates an A6 Record from the given data
+ * @param prefixBits The number of bits in the address prefix
+ * @param suffix The address suffix
+ * @param prefix The name of the prefix
+ */
+public
+A6Record(Name name, int dclass, long ttl, int prefixBits,
+ InetAddress suffix, Name prefix)
+{
+ super(name, Type.A6, dclass, ttl);
+ this.prefixBits = checkU8("prefixBits", prefixBits);
+ if (suffix != null && Address.familyOf(suffix) != Address.IPv6)
+ throw new IllegalArgumentException("invalid IPv6 address");
+ this.suffix = suffix;
+ if (prefix != null)
+ this.prefix = checkName("prefix", prefix);
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ prefixBits = in.readU8();
+ int suffixbits = 128 - prefixBits;
+ int suffixbytes = (suffixbits + 7) / 8;
+ if (prefixBits < 128) {
+ byte [] bytes = new byte[16];
+ in.readByteArray(bytes, 16 - suffixbytes, suffixbytes);
+ suffix = InetAddress.getByAddress(bytes);
+ }
+ if (prefixBits > 0)
+ prefix = new Name(in);
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ prefixBits = st.getUInt8();
+ if (prefixBits > 128) {
+ throw st.exception("prefix bits must be [0..128]");
+ } else if (prefixBits < 128) {
+ String s = st.getString();
+ try {
+ suffix = Address.getByAddress(s, Address.IPv6);
+ }
+ catch (UnknownHostException e) {
+ throw st.exception("invalid IPv6 address: " + s);
+ }
+ }
+ if (prefixBits > 0)
+ prefix = st.getName(origin);
+}
+
+/** Converts rdata to a String */
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(prefixBits);
+ if (suffix != null) {
+ sb.append(" ");
+ sb.append(suffix.getHostAddress());
+ }
+ if (prefix != null) {
+ sb.append(" ");
+ sb.append(prefix);
+ }
+ return sb.toString();
+}
+
+/** Returns the number of bits in the prefix */
+public int
+getPrefixBits() {
+ return prefixBits;
+}
+
+/** Returns the address suffix */
+public InetAddress
+getSuffix() {
+ return suffix;
+}
+
+/** Returns the address prefix */
+public Name
+getPrefix() {
+ return prefix;
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeU8(prefixBits);
+ if (suffix != null) {
+ int suffixbits = 128 - prefixBits;
+ int suffixbytes = (suffixbits + 7) / 8;
+ byte [] data = suffix.getAddress();
+ out.writeByteArray(data, 16 - suffixbytes, suffixbytes);
+ }
+ if (prefix != null)
+ prefix.toWire(out, null, canonical);
+}
+
+}
diff --git a/src/org/xbill/DNS/AAAARecord.java b/src/org/xbill/DNS/AAAARecord.java
new file mode 100644
index 0000000..4b637aa
--- /dev/null
+++ b/src/org/xbill/DNS/AAAARecord.java
@@ -0,0 +1,67 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.net.*;
+
+/**
+ * IPv6 Address Record - maps a domain name to an IPv6 address
+ *
+ * @author Brian Wellington
+ */
+
+public class AAAARecord extends Record {
+
+private static final long serialVersionUID = -4588601512069748050L;
+
+private InetAddress address;
+
+AAAARecord() {}
+
+Record
+getObject() {
+ return new AAAARecord();
+}
+
+/**
+ * Creates an AAAA Record from the given data
+ * @param address The address suffix
+ */
+public
+AAAARecord(Name name, int dclass, long ttl, InetAddress address) {
+ super(name, Type.AAAA, dclass, ttl);
+ if (Address.familyOf(address) != Address.IPv6)
+ throw new IllegalArgumentException("invalid IPv6 address");
+ this.address = address;
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ address = InetAddress.getByAddress(name.toString(),
+ in.readByteArray(16));
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ address = st.getAddress(Address.IPv6);
+}
+
+/** Converts rdata to a String */
+String
+rrToString() {
+ return address.getHostAddress();
+}
+
+/** Returns the address */
+public InetAddress
+getAddress() {
+ return address;
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeByteArray(address.getAddress());
+}
+
+}
diff --git a/src/org/xbill/DNS/AFSDBRecord.java b/src/org/xbill/DNS/AFSDBRecord.java
new file mode 100644
index 0000000..4814faa
--- /dev/null
+++ b/src/org/xbill/DNS/AFSDBRecord.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * AFS Data Base Record - maps a domain name to the name of an AFS cell
+ * database server.
+ *
+ *
+ * @author Brian Wellington
+ */
+
+public class AFSDBRecord extends U16NameBase {
+
+private static final long serialVersionUID = 3034379930729102437L;
+
+AFSDBRecord() {}
+
+Record
+getObject() {
+ return new AFSDBRecord();
+}
+
+/**
+ * Creates an AFSDB Record from the given data.
+ * @param subtype Indicates the type of service provided by the host.
+ * @param host The host providing the service.
+ */
+public
+AFSDBRecord(Name name, int dclass, long ttl, int subtype, Name host) {
+ super(name, Type.AFSDB, dclass, ttl, subtype, "subtype", host, "host");
+}
+
+/** Gets the subtype indicating the service provided by the host. */
+public int
+getSubtype() {
+ return getU16Field();
+}
+
+/** Gets the host providing service for the domain. */
+public Name
+getHost() {
+ return getNameField();
+}
+
+}
diff --git a/src/org/xbill/DNS/APLRecord.java b/src/org/xbill/DNS/APLRecord.java
new file mode 100644
index 0000000..5940da2
--- /dev/null
+++ b/src/org/xbill/DNS/APLRecord.java
@@ -0,0 +1,287 @@
+// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import org.xbill.DNS.utils.*;
+
+/**
+ * APL - Address Prefix List. See RFC 3123.
+ *
+ * @author Brian Wellington
+ */
+
+/*
+ * Note: this currently uses the same constants as the Address class;
+ * this could change if more constants are defined for APL records.
+ */
+
+public class APLRecord extends Record {
+
+public static class Element {
+ public final int family;
+ public final boolean negative;
+ public final int prefixLength;
+ public final Object address;
+
+ private
+ Element(int family, boolean negative, Object address, int prefixLength)
+ {
+ this.family = family;
+ this.negative = negative;
+ this.address = address;
+ this.prefixLength = prefixLength;
+ if (!validatePrefixLength(family, prefixLength)) {
+ throw new IllegalArgumentException("invalid prefix " +
+ "length");
+ }
+ }
+
+ /**
+ * Creates an APL element corresponding to an IPv4 or IPv6 prefix.
+ * @param negative Indicates if this prefix is a negation.
+ * @param address The IPv4 or IPv6 address.
+ * @param prefixLength The length of this prefix, in bits.
+ * @throws IllegalArgumentException The prefix length is invalid.
+ */
+ public
+ Element(boolean negative, InetAddress address, int prefixLength) {
+ this(Address.familyOf(address), negative, address,
+ prefixLength);
+ }
+
+ public String
+ toString() {
+ StringBuffer sb = new StringBuffer();
+ if (negative)
+ sb.append("!");
+ sb.append(family);
+ sb.append(":");
+ if (family == Address.IPv4 || family == Address.IPv6)
+ sb.append(((InetAddress) address).getHostAddress());
+ else
+ sb.append(base16.toString((byte []) address));
+ sb.append("/");
+ sb.append(prefixLength);
+ return sb.toString();
+ }
+
+ public boolean
+ equals(Object arg) {
+ if (arg == null || !(arg instanceof Element))
+ return false;
+ Element elt = (Element) arg;
+ return (family == elt.family &&
+ negative == elt.negative &&
+ prefixLength == elt.prefixLength &&
+ address.equals(elt.address));
+ }
+
+ public int
+ hashCode() {
+ return address.hashCode() + prefixLength + (negative ? 1 : 0);
+ }
+}
+
+private static final long serialVersionUID = -1348173791712935864L;
+
+private List elements;
+
+APLRecord() {}
+
+Record
+getObject() {
+ return new APLRecord();
+}
+
+private static boolean
+validatePrefixLength(int family, int prefixLength) {
+ if (prefixLength < 0 || prefixLength >= 256)
+ return false;
+ if ((family == Address.IPv4 && prefixLength > 32) ||
+ (family == Address.IPv6 && prefixLength > 128))
+ return false;
+ return true;
+}
+
+/**
+ * Creates an APL Record from the given data.
+ * @param elements The list of APL elements.
+ */
+public
+APLRecord(Name name, int dclass, long ttl, List elements) {
+ super(name, Type.APL, dclass, ttl);
+ this.elements = new ArrayList(elements.size());
+ for (Iterator it = elements.iterator(); it.hasNext(); ) {
+ Object o = it.next();
+ if (!(o instanceof Element)) {
+ throw new IllegalArgumentException("illegal element");
+ }
+ Element element = (Element) o;
+ if (element.family != Address.IPv4 &&
+ element.family != Address.IPv6)
+ {
+ throw new IllegalArgumentException("unknown family");
+ }
+ this.elements.add(element);
+
+ }
+}
+
+private static byte []
+parseAddress(byte [] in, int length) throws WireParseException {
+ if (in.length > length)
+ throw new WireParseException("invalid address length");
+ if (in.length == length)
+ return in;
+ byte [] out = new byte[length];
+ System.arraycopy(in, 0, out, 0, in.length);
+ return out;
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ elements = new ArrayList(1);
+ while (in.remaining() != 0) {
+ int family = in.readU16();
+ int prefix = in.readU8();
+ int length = in.readU8();
+ boolean negative = (length & 0x80) != 0;
+ length &= ~0x80;
+
+ byte [] data = in.readByteArray(length);
+ Element element;
+ if (!validatePrefixLength(family, prefix)) {
+ throw new WireParseException("invalid prefix length");
+ }
+
+ if (family == Address.IPv4 || family == Address.IPv6) {
+ data = parseAddress(data,
+ Address.addressLength(family));
+ InetAddress addr = InetAddress.getByAddress(data);
+ element = new Element(negative, addr, prefix);
+ } else {
+ element = new Element(family, negative, data, prefix);
+ }
+ elements.add(element);
+
+ }
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ elements = new ArrayList(1);
+ while (true) {
+ Tokenizer.Token t = st.get();
+ if (!t.isString())
+ break;
+
+ boolean negative = false;
+ int family = 0;
+ int prefix = 0;
+
+ String s = t.value;
+ int start = 0;
+ if (s.startsWith("!")) {
+ negative = true;
+ start = 1;
+ }
+ int colon = s.indexOf(':', start);
+ if (colon < 0)
+ throw st.exception("invalid address prefix element");
+ int slash = s.indexOf('/', colon);
+ if (slash < 0)
+ throw st.exception("invalid address prefix element");
+
+ String familyString = s.substring(start, colon);
+ String addressString = s.substring(colon + 1, slash);
+ String prefixString = s.substring(slash + 1);
+
+ try {
+ family = Integer.parseInt(familyString);
+ }
+ catch (NumberFormatException e) {
+ throw st.exception("invalid family");
+ }
+ if (family != Address.IPv4 && family != Address.IPv6)
+ throw st.exception("unknown family");
+
+ try {
+ prefix = Integer.parseInt(prefixString);
+ }
+ catch (NumberFormatException e) {
+ throw st.exception("invalid prefix length");
+ }
+
+ if (!validatePrefixLength(family, prefix)) {
+ throw st.exception("invalid prefix length");
+ }
+
+ byte [] bytes = Address.toByteArray(addressString, family);
+ if (bytes == null)
+ throw st.exception("invalid IP address " +
+ addressString);
+
+ InetAddress address = InetAddress.getByAddress(bytes);
+ elements.add(new Element(negative, address, prefix));
+ }
+ st.unget();
+}
+
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ for (Iterator it = elements.iterator(); it.hasNext(); ) {
+ Element element = (Element) it.next();
+ sb.append(element);
+ if (it.hasNext())
+ sb.append(" ");
+ }
+ return sb.toString();
+}
+
+/** Returns the list of APL elements. */
+public List
+getElements() {
+ return elements;
+}
+
+private static int
+addressLength(byte [] addr) {
+ for (int i = addr.length - 1; i >= 0; i--) {
+ if (addr[i] != 0)
+ return i + 1;
+ }
+ return 0;
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ for (Iterator it = elements.iterator(); it.hasNext(); ) {
+ Element element = (Element) it.next();
+ int length = 0;
+ byte [] data;
+ if (element.family == Address.IPv4 ||
+ element.family == Address.IPv6)
+ {
+ InetAddress addr = (InetAddress) element.address;
+ data = addr.getAddress();
+ length = addressLength(data);
+ } else {
+ data = (byte []) element.address;
+ length = data.length;
+ }
+ int wlength = length;
+ if (element.negative) {
+ wlength |= 0x80;
+ }
+ out.writeU16(element.family);
+ out.writeU8(element.prefixLength);
+ out.writeU8(wlength);
+ out.writeByteArray(data, 0, length);
+ }
+}
+
+}
diff --git a/src/org/xbill/DNS/ARecord.java b/src/org/xbill/DNS/ARecord.java
new file mode 100644
index 0000000..4e13aa7
--- /dev/null
+++ b/src/org/xbill/DNS/ARecord.java
@@ -0,0 +1,90 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.net.*;
+import java.io.*;
+
+/**
+ * Address Record - maps a domain name to an Internet address
+ *
+ * @author Brian Wellington
+ */
+
+public class ARecord extends Record {
+
+private static final long serialVersionUID = -2172609200849142323L;
+
+private int addr;
+
+ARecord() {}
+
+Record
+getObject() {
+ return new ARecord();
+}
+
+private static final int
+fromArray(byte [] array) {
+ return (((array[0] & 0xFF) << 24) |
+ ((array[1] & 0xFF) << 16) |
+ ((array[2] & 0xFF) << 8) |
+ (array[3] & 0xFF));
+}
+
+private static final byte []
+toArray(int addr) {
+ byte [] bytes = new byte[4];
+ bytes[0] = (byte) ((addr >>> 24) & 0xFF);
+ bytes[1] = (byte) ((addr >>> 16) & 0xFF);
+ bytes[2] = (byte) ((addr >>> 8) & 0xFF);
+ bytes[3] = (byte) (addr & 0xFF);
+ return bytes;
+}
+
+/**
+ * Creates an A Record from the given data
+ * @param address The address that the name refers to
+ */
+public
+ARecord(Name name, int dclass, long ttl, InetAddress address) {
+ super(name, Type.A, dclass, ttl);
+ if (Address.familyOf(address) != Address.IPv4)
+ throw new IllegalArgumentException("invalid IPv4 address");
+ addr = fromArray(address.getAddress());
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ addr = fromArray(in.readByteArray(4));
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ InetAddress address = st.getAddress(Address.IPv4);
+ addr = fromArray(address.getAddress());
+}
+
+/** Converts rdata to a String */
+String
+rrToString() {
+ return (Address.toDottedQuad(toArray(addr)));
+}
+
+/** Returns the Internet address */
+public InetAddress
+getAddress() {
+ try {
+ return InetAddress.getByAddress(name.toString(),
+ toArray(addr));
+ } catch (UnknownHostException e) {
+ return null;
+ }
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeU32(((long)addr) & 0xFFFFFFFFL);
+}
+
+}
diff --git a/src/org/xbill/DNS/Address.java b/src/org/xbill/DNS/Address.java
new file mode 100644
index 0000000..799185b
--- /dev/null
+++ b/src/org/xbill/DNS/Address.java
@@ -0,0 +1,402 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.net.*;
+import java.net.Inet6Address;
+
+/**
+ * Routines dealing with IP addresses. Includes functions similar to
+ * those in the java.net.InetAddress class.
+ *
+ * @author Brian Wellington
+ */
+
+public final class Address {
+
+public static final int IPv4 = 1;
+public static final int IPv6 = 2;
+
+private
+Address() {}
+
+private static byte []
+parseV4(String s) {
+ int numDigits;
+ int currentOctet;
+ byte [] values = new byte[4];
+ int currentValue;
+ int length = s.length();
+
+ currentOctet = 0;
+ currentValue = 0;
+ numDigits = 0;
+ for (int i = 0; i < length; i++) {
+ char c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ /* Can't have more than 3 digits per octet. */
+ if (numDigits == 3)
+ return null;
+ /* Octets shouldn't start with 0, unless they are 0. */
+ if (numDigits > 0 && currentValue == 0)
+ return null;
+ numDigits++;
+ currentValue *= 10;
+ currentValue += (c - '0');
+ /* 255 is the maximum value for an octet. */
+ if (currentValue > 255)
+ return null;
+ } else if (c == '.') {
+ /* Can't have more than 3 dots. */
+ if (currentOctet == 3)
+ return null;
+ /* Two consecutive dots are bad. */
+ if (numDigits == 0)
+ return null;
+ values[currentOctet++] = (byte) currentValue;
+ currentValue = 0;
+ numDigits = 0;
+ } else
+ return null;
+ }
+ /* Must have 4 octets. */
+ if (currentOctet != 3)
+ return null;
+ /* The fourth octet can't be empty. */
+ if (numDigits == 0)
+ return null;
+ values[currentOctet] = (byte) currentValue;
+ return values;
+}
+
+private static byte []
+parseV6(String s) {
+ int range = -1;
+ byte [] data = new byte[16];
+
+ String [] tokens = s.split(":", -1);
+
+ int first = 0;
+ int last = tokens.length - 1;
+
+ if (tokens[0].length() == 0) {
+ // If the first two tokens are empty, it means the string
+ // started with ::, which is fine. If only the first is
+ // empty, the string started with :, which is bad.
+ if (last - first > 0 && tokens[1].length() == 0)
+ first++;
+ else
+ return null;
+ }
+
+ if (tokens[last].length() == 0) {
+ // If the last two tokens are empty, it means the string
+ // ended with ::, which is fine. If only the last is
+ // empty, the string ended with :, which is bad.
+ if (last - first > 0 && tokens[last - 1].length() == 0)
+ last--;
+ else
+ return null;
+ }
+
+ if (last - first + 1 > 8)
+ return null;
+
+ int i, j;
+ for (i = first, j = 0; i <= last; i++) {
+ if (tokens[i].length() == 0) {
+ if (range >= 0)
+ return null;
+ range = j;
+ continue;
+ }
+
+ if (tokens[i].indexOf('.') >= 0) {
+ // An IPv4 address must be the last component
+ if (i < last)
+ return null;
+ // There can't have been more than 6 components.
+ if (i > 6)
+ return null;
+ byte [] v4addr = Address.toByteArray(tokens[i], IPv4);
+ if (v4addr == null)
+ return null;
+ for (int k = 0; k < 4; k++)
+ data[j++] = v4addr[k];
+ break;
+ }
+
+ try {
+ for (int k = 0; k < tokens[i].length(); k++) {
+ char c = tokens[i].charAt(k);
+ if (Character.digit(c, 16) < 0)
+ return null;
+ }
+ int x = Integer.parseInt(tokens[i], 16);
+ if (x > 0xFFFF || x < 0)
+ return null;
+ data[j++] = (byte)(x >>> 8);
+ data[j++] = (byte)(x & 0xFF);
+ }
+ catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
+ if (j < 16 && range < 0)
+ return null;
+
+ if (range >= 0) {
+ int empty = 16 - j;
+ System.arraycopy(data, range, data, range + empty, j - range);
+ for (i = range; i < range + empty; i++)
+ data[i] = 0;
+ }
+
+ return data;
+}
+
+/**
+ * Convert a string containing an IP address to an array of 4 or 16 integers.
+ * @param s The address, in text format.
+ * @param family The address family.
+ * @return The address
+ */
+public static int []
+toArray(String s, int family) {
+ byte [] byteArray = toByteArray(s, family);
+ if (byteArray == null)
+ return null;
+ int [] intArray = new int[byteArray.length];
+ for (int i = 0; i < byteArray.length; i++)
+ intArray[i] = byteArray[i] & 0xFF;
+ return intArray;
+}
+
+/**
+ * Convert a string containing an IPv4 address to an array of 4 integers.
+ * @param s The address, in text format.
+ * @return The address
+ */
+public static int []
+toArray(String s) {
+ return toArray(s, IPv4);
+}
+
+/**
+ * Convert a string containing an IP address to an array of 4 or 16 bytes.
+ * @param s The address, in text format.
+ * @param family The address family.
+ * @return The address
+ */
+public static byte []
+toByteArray(String s, int family) {
+ if (family == IPv4)
+ return parseV4(s);
+ else if (family == IPv6)
+ return parseV6(s);
+ else
+ throw new IllegalArgumentException("unknown address family");
+}
+
+/**
+ * Determines if a string contains a valid IP address.
+ * @param s The string
+ * @return Whether the string contains a valid IP address
+ */
+public static boolean
+isDottedQuad(String s) {
+ byte [] address = Address.toByteArray(s, IPv4);
+ return (address != null);
+}
+
+/**
+ * Converts a byte array containing an IPv4 address into a dotted quad string.
+ * @param addr The array
+ * @return The string representation
+ */
+public static String
+toDottedQuad(byte [] addr) {
+ return ((addr[0] & 0xFF) + "." + (addr[1] & 0xFF) + "." +
+ (addr[2] & 0xFF) + "." + (addr[3] & 0xFF));
+}
+
+/**
+ * Converts an int array containing an IPv4 address into a dotted quad string.
+ * @param addr The array
+ * @return The string representation
+ */
+public static String
+toDottedQuad(int [] addr) {
+ return (addr[0] + "." + addr[1] + "." + addr[2] + "." + addr[3]);
+}
+
+private static Record []
+lookupHostName(String name) throws UnknownHostException {
+ try {
+ Record [] records = new Lookup(name).run();
+ if (records == null)
+ throw new UnknownHostException("unknown host");
+ return records;
+ }
+ catch (TextParseException e) {
+ throw new UnknownHostException("invalid name");
+ }
+}
+
+private static InetAddress
+addrFromRecord(String name, Record r) throws UnknownHostException {
+ ARecord a = (ARecord) r;
+ return InetAddress.getByAddress(name, a.getAddress().getAddress());
+}
+
+/**
+ * Determines the IP address of a host
+ * @param name The hostname to look up
+ * @return The first matching IP address
+ * @exception UnknownHostException The hostname does not have any addresses
+ */
+public static InetAddress
+getByName(String name) throws UnknownHostException {
+ try {
+ return getByAddress(name);
+ } catch (UnknownHostException e) {
+ Record [] records = lookupHostName(name);
+ return addrFromRecord(name, records[0]);
+ }
+}
+
+/**
+ * Determines all IP address of a host
+ * @param name The hostname to look up
+ * @return All matching IP addresses
+ * @exception UnknownHostException The hostname does not have any addresses
+ */
+public static InetAddress []
+getAllByName(String name) throws UnknownHostException {
+ try {
+ InetAddress addr = getByAddress(name);
+ return new InetAddress[] {addr};
+ } catch (UnknownHostException e) {
+ Record [] records = lookupHostName(name);
+ InetAddress [] addrs = new InetAddress[records.length];
+ for (int i = 0; i < records.length; i++)
+ addrs[i] = addrFromRecord(name, records[i]);
+ return addrs;
+ }
+}
+
+/**
+ * Converts an address from its string representation to an IP address.
+ * The address can be either IPv4 or IPv6.
+ * @param addr The address, in string form
+ * @return The IP addresses
+ * @exception UnknownHostException The address is not a valid IP address.
+ */
+public static InetAddress
+getByAddress(String addr) throws UnknownHostException {
+ byte [] bytes;
+ bytes = toByteArray(addr, IPv4);
+ if (bytes != null)
+ return InetAddress.getByAddress(addr, bytes);
+ bytes = toByteArray(addr, IPv6);
+ if (bytes != null)
+ return InetAddress.getByAddress(addr, bytes);
+ throw new UnknownHostException("Invalid address: " + addr);
+}
+
+/**
+ * Converts an address from its string representation to an IP address in
+ * a particular family.
+ * @param addr The address, in string form
+ * @param family The address family, either IPv4 or IPv6.
+ * @return The IP addresses
+ * @exception UnknownHostException The address is not a valid IP address in
+ * the specified address family.
+ */
+public static InetAddress
+getByAddress(String addr, int family) throws UnknownHostException {
+ if (family != IPv4 && family != IPv6)
+ throw new IllegalArgumentException("unknown address family");
+ byte [] bytes;
+ bytes = toByteArray(addr, family);
+ if (bytes != null)
+ return InetAddress.getByAddress(addr, bytes);
+ throw new UnknownHostException("Invalid address: " + addr);
+}
+
+/**
+ * Determines the hostname for an address
+ * @param addr The address to look up
+ * @return The associated host name
+ * @exception UnknownHostException There is no hostname for the address
+ */
+public static String
+getHostName(InetAddress addr) throws UnknownHostException {
+ Name name = ReverseMap.fromAddress(addr);
+ Record [] records = new Lookup(name, Type.PTR).run();
+ if (records == null)
+ throw new UnknownHostException("unknown address");
+ PTRRecord ptr = (PTRRecord) records[0];
+ return ptr.getTarget().toString();
+}
+
+/**
+ * Returns the family of an InetAddress.
+ * @param address The supplied address.
+ * @return The family, either IPv4 or IPv6.
+ */
+public static int
+familyOf(InetAddress address) {
+ if (address instanceof Inet4Address)
+ return IPv4;
+ if (address instanceof Inet6Address)
+ return IPv6;
+ throw new IllegalArgumentException("unknown address family");
+}
+
+/**
+ * Returns the length of an address in a particular family.
+ * @param family The address family, either IPv4 or IPv6.
+ * @return The length of addresses in that family.
+ */
+public static int
+addressLength(int family) {
+ if (family == IPv4)
+ return 4;
+ if (family == IPv6)
+ return 16;
+ throw new IllegalArgumentException("unknown address family");
+}
+
+/**
+ * Truncates an address to the specified number of bits. For example,
+ * truncating the address 10.1.2.3 to 8 bits would yield 10.0.0.0.
+ * @param address The source address
+ * @param maskLength The number of bits to truncate the address to.
+ */
+public static InetAddress
+truncate(InetAddress address, int maskLength)
+{
+ int family = familyOf(address);
+ int maxMaskLength = addressLength(family) * 8;
+ if (maskLength < 0 || maskLength > maxMaskLength)
+ throw new IllegalArgumentException("invalid mask length");
+ if (maskLength == maxMaskLength)
+ return address;
+ byte [] bytes = address.getAddress();
+ for (int i = maskLength / 8 + 1; i < bytes.length; i++)
+ bytes[i] = 0;
+ int maskBits = maskLength % 8;
+ int bitmask = 0;
+ for (int i = 0; i < maskBits; i++)
+ bitmask |= (1 << (7 - i));
+ bytes[maskLength / 8] &= bitmask;
+ try {
+ return InetAddress.getByAddress(bytes);
+ } catch (UnknownHostException e) {
+ throw new IllegalArgumentException("invalid address");
+ }
+}
+
+}
diff --git a/src/org/xbill/DNS/CERTRecord.java b/src/org/xbill/DNS/CERTRecord.java
new file mode 100644
index 0000000..39bcef3
--- /dev/null
+++ b/src/org/xbill/DNS/CERTRecord.java
@@ -0,0 +1,224 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import org.xbill.DNS.utils.*;
+
+/**
+ * Certificate Record - Stores a certificate associated with a name. The
+ * certificate might also be associated with a KEYRecord.
+ * @see KEYRecord
+ *
+ * @author Brian Wellington
+ */
+
+public class CERTRecord extends Record {
+
+public static class CertificateType {
+ /** Certificate type identifiers. See RFC 4398 for more detail. */
+
+ private CertificateType() {}
+
+ /** PKIX (X.509v3) */
+ public static final int PKIX = 1;
+
+ /** Simple Public Key Infrastructure */
+ public static final int SPKI = 2;
+
+ /** Pretty Good Privacy */
+ public static final int PGP = 3;
+
+ /** URL of an X.509 data object */
+ public static final int IPKIX = 4;
+
+ /** URL of an SPKI certificate */
+ public static final int ISPKI = 5;
+
+ /** Fingerprint and URL of an OpenPGP packet */
+ public static final int IPGP = 6;
+
+ /** Attribute Certificate */
+ public static final int ACPKIX = 7;
+
+ /** URL of an Attribute Certificate */
+ public static final int IACPKIX = 8;
+
+ /** Certificate format defined by URI */
+ public static final int URI = 253;
+
+ /** Certificate format defined by OID */
+ public static final int OID = 254;
+
+ private static Mnemonic types = new Mnemonic("Certificate type",
+ Mnemonic.CASE_UPPER);
+
+ static {
+ types.setMaximum(0xFFFF);
+ types.setNumericAllowed(true);
+
+ types.add(PKIX, "PKIX");
+ types.add(SPKI, "SPKI");
+ types.add(PGP, "PGP");
+ types.add(PKIX, "IPKIX");
+ types.add(SPKI, "ISPKI");
+ types.add(PGP, "IPGP");
+ types.add(PGP, "ACPKIX");
+ types.add(PGP, "IACPKIX");
+ types.add(URI, "URI");
+ types.add(OID, "OID");
+ }
+
+ /**
+ * Converts a certificate type into its textual representation
+ */
+ public static String
+ string(int type) {
+ return types.getText(type);
+ }
+
+ /**
+ * Converts a textual representation of an certificate type into its
+ * numeric code. Integers in the range 0..65535 are also accepted.
+ * @param s The textual representation of the algorithm
+ * @return The algorithm code, or -1 on error.
+ */
+ public static int
+ value(String s) {
+ return types.getValue(s);
+ }
+}
+
+/** PKIX (X.509v3) */
+public static final int PKIX = CertificateType.PKIX;
+
+/** Simple Public Key Infrastructure */
+public static final int SPKI = CertificateType.SPKI;
+
+/** Pretty Good Privacy */
+public static final int PGP = CertificateType.PGP;
+
+/** Certificate format defined by URI */
+public static final int URI = CertificateType.URI;
+
+/** Certificate format defined by IOD */
+public static final int OID = CertificateType.OID;
+
+private static final long serialVersionUID = 4763014646517016835L;
+
+private int certType, keyTag;
+private int alg;
+private byte [] cert;
+
+CERTRecord() {}
+
+Record
+getObject() {
+ return new CERTRecord();
+}
+
+/**
+ * Creates a CERT Record from the given data
+ * @param certType The type of certificate (see constants)
+ * @param keyTag The ID of the associated KEYRecord, if present
+ * @param alg The algorithm of the associated KEYRecord, if present
+ * @param cert Binary data representing the certificate
+ */
+public
+CERTRecord(Name name, int dclass, long ttl, int certType, int keyTag,
+ int alg, byte [] cert)
+{
+ super(name, Type.CERT, dclass, ttl);
+ this.certType = checkU16("certType", certType);
+ this.keyTag = checkU16("keyTag", keyTag);
+ this.alg = checkU8("alg", alg);
+ this.cert = cert;
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ certType = in.readU16();
+ keyTag = in.readU16();
+ alg = in.readU8();
+ cert = in.readByteArray();
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ String certTypeString = st.getString();
+ certType = CertificateType.value(certTypeString);
+ if (certType < 0)
+ throw st.exception("Invalid certificate type: " +
+ certTypeString);
+ keyTag = st.getUInt16();
+ String algString = st.getString();
+ alg = DNSSEC.Algorithm.value(algString);
+ if (alg < 0)
+ throw st.exception("Invalid algorithm: " + algString);
+ cert = st.getBase64();
+}
+
+/**
+ * Converts rdata to a String
+ */
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append (certType);
+ sb.append (" ");
+ sb.append (keyTag);
+ sb.append (" ");
+ sb.append (alg);
+ if (cert != null) {
+ if (Options.check("multiline")) {
+ sb.append(" (\n");
+ sb.append(base64.formatString(cert, 64, "\t", true));
+ } else {
+ sb.append(" ");
+ sb.append(base64.toString(cert));
+ }
+ }
+ return sb.toString();
+}
+
+/**
+ * Returns the type of certificate
+ */
+public int
+getCertType() {
+ return certType;
+}
+
+/**
+ * Returns the ID of the associated KEYRecord, if present
+ */
+public int
+getKeyTag() {
+ return keyTag;
+}
+
+/**
+ * Returns the algorithm of the associated KEYRecord, if present
+ */
+public int
+getAlgorithm() {
+ return alg;
+}
+
+/**
+ * Returns the binary representation of the certificate
+ */
+public byte []
+getCert() {
+ return cert;
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeU16(certType);
+ out.writeU16(keyTag);
+ out.writeU8(alg);
+ out.writeByteArray(cert);
+}
+
+}
diff --git a/src/org/xbill/DNS/CNAMERecord.java b/src/org/xbill/DNS/CNAMERecord.java
new file mode 100644
index 0000000..8db9453
--- /dev/null
+++ b/src/org/xbill/DNS/CNAMERecord.java
@@ -0,0 +1,45 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * CNAME Record - maps an alias to its real name
+ *
+ * @author Brian Wellington
+ */
+
+public class CNAMERecord extends SingleCompressedNameBase {
+
+private static final long serialVersionUID = -4020373886892538580L;
+
+CNAMERecord() {}
+
+Record
+getObject() {
+ return new CNAMERecord();
+}
+
+/**
+ * Creates a new CNAMERecord with the given data
+ * @param alias The name to which the CNAME alias points
+ */
+public
+CNAMERecord(Name name, int dclass, long ttl, Name alias) {
+ super(name, Type.CNAME, dclass, ttl, alias, "alias");
+}
+
+/**
+ * Gets the target of the CNAME Record
+ */
+public Name
+getTarget() {
+ return getSingleName();
+}
+
+/** Gets the alias specified by the CNAME Record */
+public Name
+getAlias() {
+ return getSingleName();
+}
+
+}
diff --git a/src/org/xbill/DNS/Cache.java b/src/org/xbill/DNS/Cache.java
new file mode 100644
index 0000000..5497f45
--- /dev/null
+++ b/src/org/xbill/DNS/Cache.java
@@ -0,0 +1,846 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * A cache of DNS records. The cache obeys TTLs, so items are purged after
+ * their validity period is complete. Negative answers are cached, to
+ * avoid repeated failed DNS queries. The credibility of each RRset is
+ * maintained, so that more credible records replace less credible records,
+ * and lookups can specify the minimum credibility of data they are requesting.
+ * @see RRset
+ * @see Credibility
+ *
+ * @author Brian Wellington
+ */
+
+public class Cache {
+
+private interface Element {
+ public boolean expired();
+ public int compareCredibility(int cred);
+ public int getType();
+}
+
+private static int
+limitExpire(long ttl, long maxttl) {
+ if (maxttl >= 0 && maxttl < ttl)
+ ttl = maxttl;
+ long expire = (System.currentTimeMillis() / 1000) + ttl;
+ if (expire < 0 || expire > Integer.MAX_VALUE)
+ return Integer.MAX_VALUE;
+ return (int)expire;
+}
+
+private static class CacheRRset extends RRset implements Element {
+ private static final long serialVersionUID = 5971755205903597024L;
+
+ int credibility;
+ int expire;
+
+ public
+ CacheRRset(Record rec, int cred, long maxttl) {
+ super();
+ this.credibility = cred;
+ this.expire = limitExpire(rec.getTTL(), maxttl);
+ addRR(rec);
+ }
+
+ public
+ CacheRRset(RRset rrset, int cred, long maxttl) {
+ super(rrset);
+ this.credibility = cred;
+ this.expire = limitExpire(rrset.getTTL(), maxttl);
+ }
+
+ public final boolean
+ expired() {
+ int now = (int)(System.currentTimeMillis() / 1000);
+ return (now >= expire);
+ }
+
+ public final int
+ compareCredibility(int cred) {
+ return credibility - cred;
+ }
+
+ public String
+ toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(super.toString());
+ sb.append(" cl = ");
+ sb.append(credibility);
+ return sb.toString();
+ }
+}
+
+private static class NegativeElement implements Element {
+ int type;
+ Name name;
+ int credibility;
+ int expire;
+
+ public
+ NegativeElement(Name name, int type, SOARecord soa, int cred,
+ long maxttl)
+ {
+ this.name = name;
+ this.type = type;
+ long cttl = 0;
+ if (soa != null)
+ cttl = soa.getMinimum();
+ this.credibility = cred;
+ this.expire = limitExpire(cttl, maxttl);
+ }
+
+ public int
+ getType() {
+ return type;
+ }
+
+ public final boolean
+ expired() {
+ int now = (int)(System.currentTimeMillis() / 1000);
+ return (now >= expire);
+ }
+
+ public final int
+ compareCredibility(int cred) {
+ return credibility - cred;
+ }
+
+ public String
+ toString() {
+ StringBuffer sb = new StringBuffer();
+ if (type == 0)
+ sb.append("NXDOMAIN " + name);
+ else
+ sb.append("NXRRSET " + name + " " + Type.string(type));
+ sb.append(" cl = ");
+ sb.append(credibility);
+ return sb.toString();
+ }
+}
+
+private static class CacheMap extends LinkedHashMap {
+ private int maxsize = -1;
+
+ CacheMap(int maxsize) {
+ super(16, (float) 0.75, true);
+ this.maxsize = maxsize;
+ }
+
+ int
+ getMaxSize() {
+ return maxsize;
+ }
+
+ void
+ setMaxSize(int maxsize) {
+ /*
+ * Note that this doesn't shrink the size of the map if
+ * the maximum size is lowered, but it should shrink as
+ * entries expire.
+ */
+ this.maxsize = maxsize;
+ }
+
+ protected boolean removeEldestEntry(Map.Entry eldest) {
+ return maxsize >= 0 && size() > maxsize;
+ }
+}
+
+private CacheMap data;
+private int maxncache = -1;
+private int maxcache = -1;
+private int dclass;
+
+private static final int defaultMaxEntries = 50000;
+
+/**
+ * Creates an empty Cache
+ *
+ * @param dclass The DNS class of this cache
+ * @see DClass
+ */
+public
+Cache(int dclass) {
+ this.dclass = dclass;
+ data = new CacheMap(defaultMaxEntries);
+}
+
+/**
+ * Creates an empty Cache for class IN.
+ * @see DClass
+ */
+public
+Cache() {
+ this(DClass.IN);
+}
+
+/**
+ * Creates a Cache which initially contains all records in the specified file.
+ */
+public
+Cache(String file) throws IOException {
+ data = new CacheMap(defaultMaxEntries);
+ Master m = new Master(file);
+ Record record;
+ while ((record = m.nextRecord()) != null)
+ addRecord(record, Credibility.HINT, m);
+}
+
+private synchronized Object
+exactName(Name name) {
+ return data.get(name);
+}
+
+private synchronized void
+removeName(Name name) {
+ data.remove(name);
+}
+
+private synchronized Element []
+allElements(Object types) {
+ if (types instanceof List) {
+ List typelist = (List) types;
+ int size = typelist.size();
+ return (Element []) typelist.toArray(new Element[size]);
+ } else {
+ Element set = (Element) types;
+ return new Element[] {set};
+ }
+}
+
+private synchronized Element
+oneElement(Name name, Object types, int type, int minCred) {
+ Element found = null;
+
+ if (type == Type.ANY)
+ throw new IllegalArgumentException("oneElement(ANY)");
+ if (types instanceof List) {
+ List list = (List) types;
+ for (int i = 0; i < list.size(); i++) {
+ Element set = (Element) list.get(i);
+ if (set.getType() == type) {
+ found = set;
+ break;
+ }
+ }
+ } else {
+ Element set = (Element) types;
+ if (set.getType() == type)
+ found = set;
+ }
+ if (found == null)
+ return null;
+ if (found.expired()) {
+ removeElement(name, type);
+ return null;
+ }
+ if (found.compareCredibility(minCred) < 0)
+ return null;
+ return found;
+}
+
+private synchronized Element
+findElement(Name name, int type, int minCred) {
+ Object types = exactName(name);
+ if (types == null)
+ return null;
+ return oneElement(name, types, type, minCred);
+}
+
+private synchronized void
+addElement(Name name, Element element) {
+ Object types = data.get(name);
+ if (types == null) {
+ data.put(name, element);
+ return;
+ }
+ int type = element.getType();
+ if (types instanceof List) {
+ List list = (List) types;
+ for (int i = 0; i < list.size(); i++) {
+ Element elt = (Element) list.get(i);
+ if (elt.getType() == type) {
+ list.set(i, element);
+ return;
+ }
+ }
+ list.add(element);
+ } else {
+ Element elt = (Element) types;
+ if (elt.getType() == type)
+ data.put(name, element);
+ else {
+ LinkedList list = new LinkedList();
+ list.add(elt);
+ list.add(element);
+ data.put(name, list);
+ }
+ }
+}
+
+private synchronized void
+removeElement(Name name, int type) {
+ Object types = data.get(name);
+ if (types == null) {
+ return;
+ }
+ if (types instanceof List) {
+ List list = (List) types;
+ for (int i = 0; i < list.size(); i++) {
+ Element elt = (Element) list.get(i);
+ if (elt.getType() == type) {
+ list.remove(i);
+ if (list.size() == 0)
+ data.remove(name);
+ return;
+ }
+ }
+ } else {
+ Element elt = (Element) types;
+ if (elt.getType() != type)
+ return;
+ data.remove(name);
+ }
+}
+
+/** Empties the Cache. */
+public synchronized void
+clearCache() {
+ data.clear();
+}
+
+/**
+ * Adds a record to the Cache.
+ * @param r The record to be added
+ * @param cred The credibility of the record
+ * @param o The source of the record (this could be a Message, for example)
+ * @see Record
+ */
+public synchronized void
+addRecord(Record r, int cred, Object o) {
+ Name name = r.getName();
+ int type = r.getRRsetType();
+ if (!Type.isRR(type))
+ return;
+ Element element = findElement(name, type, cred);
+ if (element == null) {
+ CacheRRset crrset = new CacheRRset(r, cred, maxcache);
+ addRRset(crrset, cred);
+ } else if (element.compareCredibility(cred) == 0) {
+ if (element instanceof CacheRRset) {
+ CacheRRset crrset = (CacheRRset) element;
+ crrset.addRR(r);
+ }
+ }
+}
+
+/**
+ * Adds an RRset to the Cache.
+ * @param rrset The RRset to be added
+ * @param cred The credibility of these records
+ * @see RRset
+ */
+public synchronized void
+addRRset(RRset rrset, int cred) {
+ long ttl = rrset.getTTL();
+ Name name = rrset.getName();
+ int type = rrset.getType();
+ Element element = findElement(name, type, 0);
+ if (ttl == 0) {
+ if (element != null && element.compareCredibility(cred) <= 0)
+ removeElement(name, type);
+ } else {
+ if (element != null && element.compareCredibility(cred) <= 0)
+ element = null;
+ if (element == null) {
+ CacheRRset crrset;
+ if (rrset instanceof CacheRRset)
+ crrset = (CacheRRset) rrset;
+ else
+ crrset = new CacheRRset(rrset, cred, maxcache);
+ addElement(name, crrset);
+ }
+ }
+}
+
+/**
+ * Adds a negative entry to the Cache.
+ * @param name The name of the negative entry
+ * @param type The type of the negative entry
+ * @param soa The SOA record to add to the negative cache entry, or null.
+ * The negative cache ttl is derived from the SOA.
+ * @param cred The credibility of the negative entry
+ */
+public synchronized void
+addNegative(Name name, int type, SOARecord soa, int cred) {
+ long ttl = 0;
+ if (soa != null)
+ ttl = soa.getTTL();
+ Element element = findElement(name, type, 0);
+ if (ttl == 0) {
+ if (element != null && element.compareCredibility(cred) <= 0)
+ removeElement(name, type);
+ } else {
+ if (element != null && element.compareCredibility(cred) <= 0)
+ element = null;
+ if (element == null)
+ addElement(name, new NegativeElement(name, type,
+ soa, cred,
+ maxncache));
+ }
+}
+
+/**
+ * Finds all matching sets or something that causes the lookup to stop.
+ */
+protected synchronized SetResponse
+lookup(Name name, int type, int minCred) {
+ int labels;
+ int tlabels;
+ Element element;
+ Name tname;
+ Object types;
+ SetResponse sr;
+
+ labels = name.labels();
+
+ for (tlabels = labels; tlabels >= 1; tlabels--) {
+ boolean isRoot = (tlabels == 1);
+ boolean isExact = (tlabels == labels);
+
+ if (isRoot)
+ tname = Name.root;
+ else if (isExact)
+ tname = name;
+ else
+ tname = new Name(name, labels - tlabels);
+
+ types = data.get(tname);
+ if (types == null)
+ continue;
+
+ /*
+ * If this is the name, look for the actual type or a CNAME
+ * (unless it's an ANY query, where we return everything).
+ * Otherwise, look for a DNAME.
+ */
+ if (isExact && type == Type.ANY) {
+ sr = new SetResponse(SetResponse.SUCCESSFUL);
+ Element [] elements = allElements(types);
+ int added = 0;
+ for (int i = 0; i < elements.length; i++) {
+ element = elements[i];
+ if (element.expired()) {
+ removeElement(tname, element.getType());
+ continue;
+ }
+ if (!(element instanceof CacheRRset))
+ continue;
+ if (element.compareCredibility(minCred) < 0)
+ continue;
+ sr.addRRset((CacheRRset)element);
+ added++;
+ }
+ /* There were positive entries */
+ if (added > 0)
+ return sr;
+ } else if (isExact) {
+ element = oneElement(tname, types, type, minCred);
+ if (element != null &&
+ element instanceof CacheRRset)
+ {
+ sr = new SetResponse(SetResponse.SUCCESSFUL);
+ sr.addRRset((CacheRRset) element);
+ return sr;
+ } else if (element != null) {
+ sr = new SetResponse(SetResponse.NXRRSET);
+ return sr;
+ }
+
+ element = oneElement(tname, types, Type.CNAME, minCred);
+ if (element != null &&
+ element instanceof CacheRRset)
+ {
+ return new SetResponse(SetResponse.CNAME,
+ (CacheRRset) element);
+ }
+ } else {
+ element = oneElement(tname, types, Type.DNAME, minCred);
+ if (element != null &&
+ element instanceof CacheRRset)
+ {
+ return new SetResponse(SetResponse.DNAME,
+ (CacheRRset) element);
+ }
+ }
+
+ /* Look for an NS */
+ element = oneElement(tname, types, Type.NS, minCred);
+ if (element != null && element instanceof CacheRRset)
+ return new SetResponse(SetResponse.DELEGATION,
+ (CacheRRset) element);
+
+ /* Check for the special NXDOMAIN element. */
+ if (isExact) {
+ element = oneElement(tname, types, 0, minCred);
+ if (element != null)
+ return SetResponse.ofType(SetResponse.NXDOMAIN);
+ }
+
+ }
+ return SetResponse.ofType(SetResponse.UNKNOWN);
+}
+
+/**
+ * Looks up Records in the Cache. This follows CNAMEs and handles negatively
+ * cached data.
+ * @param name The name to look up
+ * @param type The type to look up
+ * @param minCred The minimum acceptable credibility
+ * @return A SetResponse object
+ * @see SetResponse
+ * @see Credibility
+ */
+public SetResponse
+lookupRecords(Name name, int type, int minCred) {
+ return lookup(name, type, minCred);
+}
+
+private RRset []
+findRecords(Name name, int type, int minCred) {
+ SetResponse cr = lookupRecords(name, type, minCred);
+ if (cr.isSuccessful())
+ return cr.answers();
+ else
+ return null;
+}
+
+/**
+ * Looks up credible Records in the Cache (a wrapper around lookupRecords).
+ * Unlike lookupRecords, this given no indication of why failure occurred.
+ * @param name The name to look up
+ * @param type The type to look up
+ * @return An array of RRsets, or null
+ * @see Credibility
+ */
+public RRset []
+findRecords(Name name, int type) {
+ return findRecords(name, type, Credibility.NORMAL);
+}
+
+/**
+ * Looks up Records in the Cache (a wrapper around lookupRecords). Unlike
+ * lookupRecords, this given no indication of why failure occurred.
+ * @param name The name to look up
+ * @param type The type to look up
+ * @return An array of RRsets, or null
+ * @see Credibility
+ */
+public RRset []
+findAnyRecords(Name name, int type) {
+ return findRecords(name, type, Credibility.GLUE);
+}
+
+private final int
+getCred(int section, boolean isAuth) {
+ if (section == Section.ANSWER) {
+ if (isAuth)
+ return Credibility.AUTH_ANSWER;
+ else
+ return Credibility.NONAUTH_ANSWER;
+ } else if (section == Section.AUTHORITY) {
+ if (isAuth)
+ return Credibility.AUTH_AUTHORITY;
+ else
+ return Credibility.NONAUTH_AUTHORITY;
+ } else if (section == Section.ADDITIONAL) {
+ return Credibility.ADDITIONAL;
+ } else
+ throw new IllegalArgumentException("getCred: invalid section");
+}
+
+private static void
+markAdditional(RRset rrset, Set names) {
+ Record first = rrset.first();
+ if (first.getAdditionalName() == null)
+ return;
+
+ Iterator it = rrset.rrs();
+ while (it.hasNext()) {
+ Record r = (Record) it.next();
+ Name name = r.getAdditionalName();
+ if (name != null)
+ names.add(name);
+ }
+}
+
+/**
+ * Adds all data from a Message into the Cache. Each record is added with
+ * the appropriate credibility, and negative answers are cached as such.
+ * @param in The Message to be added
+ * @return A SetResponse that reflects what would be returned from a cache
+ * lookup, or null if nothing useful could be cached from the message.
+ * @see Message
+ */
+public SetResponse
+addMessage(Message in) {
+ boolean isAuth = in.getHeader().getFlag(Flags.AA);
+ Record question = in.getQuestion();
+ Name qname;
+ Name curname;
+ int qtype;
+ int qclass;
+ int cred;
+ int rcode = in.getHeader().getRcode();
+ boolean completed = false;
+ RRset [] answers, auth, addl;
+ SetResponse response = null;
+ boolean verbose = Options.check("verbosecache");
+ HashSet additionalNames;
+
+ if ((rcode != Rcode.NOERROR && rcode != Rcode.NXDOMAIN) ||
+ question == null)
+ return null;
+
+ qname = question.getName();
+ qtype = question.getType();
+ qclass = question.getDClass();
+
+ curname = qname;
+
+ additionalNames = new HashSet();
+
+ answers = in.getSectionRRsets(Section.ANSWER);
+ for (int i = 0; i < answers.length; i++) {
+ if (answers[i].getDClass() != qclass)
+ continue;
+ int type = answers[i].getType();
+ Name name = answers[i].getName();
+ cred = getCred(Section.ANSWER, isAuth);
+ if ((type == qtype || qtype == Type.ANY) &&
+ name.equals(curname))
+ {
+ addRRset(answers[i], cred);
+ completed = true;
+ if (curname == qname) {
+ if (response == null)
+ response = new SetResponse(
+ SetResponse.SUCCESSFUL);
+ response.addRRset(answers[i]);
+ }
+ markAdditional(answers[i], additionalNames);
+ } else if (type == Type.CNAME && name.equals(curname)) {
+ CNAMERecord cname;
+ addRRset(answers[i], cred);
+ if (curname == qname)
+ response = new SetResponse(SetResponse.CNAME,
+ answers[i]);
+ cname = (CNAMERecord) answers[i].first();
+ curname = cname.getTarget();
+ } else if (type == Type.DNAME && curname.subdomain(name)) {
+ DNAMERecord dname;
+ addRRset(answers[i], cred);
+ if (curname == qname)
+ response = new SetResponse(SetResponse.DNAME,
+ answers[i]);
+ dname = (DNAMERecord) answers[i].first();
+ try {
+ curname = curname.fromDNAME(dname);
+ }
+ catch (NameTooLongException e) {
+ break;
+ }
+ }
+ }
+
+ auth = in.getSectionRRsets(Section.AUTHORITY);
+ RRset soa = null, ns = null;
+ for (int i = 0; i < auth.length; i++) {
+ if (auth[i].getType() == Type.SOA &&
+ curname.subdomain(auth[i].getName()))
+ soa = auth[i];
+ else if (auth[i].getType() == Type.NS &&
+ curname.subdomain(auth[i].getName()))
+ ns = auth[i];
+ }
+ if (!completed) {
+ /* This is a negative response or a referral. */
+ int cachetype = (rcode == Rcode.NXDOMAIN) ? 0 : qtype;
+ if (rcode == Rcode.NXDOMAIN || soa != null || ns == null) {
+ /* Negative response */
+ cred = getCred(Section.AUTHORITY, isAuth);
+ SOARecord soarec = null;
+ if (soa != null)
+ soarec = (SOARecord) soa.first();
+ addNegative(curname, cachetype, soarec, cred);
+ if (response == null) {
+ int responseType;
+ if (rcode == Rcode.NXDOMAIN)
+ responseType = SetResponse.NXDOMAIN;
+ else
+ responseType = SetResponse.NXRRSET;
+ response = SetResponse.ofType(responseType);
+ }
+ /* DNSSEC records are not cached. */
+ } else {
+ /* Referral response */
+ cred = getCred(Section.AUTHORITY, isAuth);
+ addRRset(ns, cred);
+ markAdditional(ns, additionalNames);
+ if (response == null)
+ response = new SetResponse(
+ SetResponse.DELEGATION,
+ ns);
+ }
+ } else if (rcode == Rcode.NOERROR && ns != null) {
+ /* Cache the NS set from a positive response. */
+ cred = getCred(Section.AUTHORITY, isAuth);
+ addRRset(ns, cred);
+ markAdditional(ns, additionalNames);
+ }
+
+ addl = in.getSectionRRsets(Section.ADDITIONAL);
+ for (int i = 0; i < addl.length; i++) {
+ int type = addl[i].getType();
+ if (type != Type.A && type != Type.AAAA && type != Type.A6)
+ continue;
+ Name name = addl[i].getName();
+ if (!additionalNames.contains(name))
+ continue;
+ cred = getCred(Section.ADDITIONAL, isAuth);
+ addRRset(addl[i], cred);
+ }
+ if (verbose)
+ System.out.println("addMessage: " + response);
+ return (response);
+}
+
+/**
+ * Flushes an RRset from the cache
+ * @param name The name of the records to be flushed
+ * @param type The type of the records to be flushed
+ * @see RRset
+ */
+public void
+flushSet(Name name, int type) {
+ removeElement(name, type);
+}
+
+/**
+ * Flushes all RRsets with a given name from the cache
+ * @param name The name of the records to be flushed
+ * @see RRset
+ */
+public void
+flushName(Name name) {
+ removeName(name);
+}
+
+/**
+ * Sets the maximum length of time that a negative response will be stored
+ * in this Cache. A negative value disables this feature (that is, sets
+ * no limit).
+ */
+public void
+setMaxNCache(int seconds) {
+ maxncache = seconds;
+}
+
+/**
+ * Gets the maximum length of time that a negative response will be stored
+ * in this Cache. A negative value indicates no limit.
+ */
+public int
+getMaxNCache() {
+ return maxncache;
+}
+
+/**
+ * Sets the maximum length of time that records will be stored in this
+ * Cache. A negative value disables this feature (that is, sets no limit).
+ */
+public void
+setMaxCache(int seconds) {
+ maxcache = seconds;
+}
+
+/**
+ * Gets the maximum length of time that records will be stored
+ * in this Cache. A negative value indicates no limit.
+ */
+public int
+getMaxCache() {
+ return maxcache;
+}
+
+/**
+ * Gets the current number of entries in the Cache, where an entry consists
+ * of all records with a specific Name.
+ */
+public int
+getSize() {
+ return data.size();
+}
+
+/**
+ * Gets the maximum number of entries in the Cache, where an entry consists
+ * of all records with a specific Name. A negative value is treated as an
+ * infinite limit.
+ */
+public int
+getMaxEntries() {
+ return data.getMaxSize();
+}
+
+/**
+ * Sets the maximum number of entries in the Cache, where an entry consists
+ * of all records with a specific Name. A negative value is treated as an
+ * infinite limit.
+ *
+ * Note that setting this to a value lower than the current number
+ * of entries will not cause the Cache to shrink immediately.
+ *
+ * The default maximum number of entries is 50000.
+ *
+ * @param entries The maximum number of entries in the Cache.
+ */
+public void
+setMaxEntries(int entries) {
+ data.setMaxSize(entries);
+}
+
+/**
+ * Returns the DNS class of this cache.
+ */
+public int
+getDClass() {
+ return dclass;
+}
+
+/**
+ * Returns the contents of the Cache as a string.
+ */
+public String
+toString() {
+ StringBuffer sb = new StringBuffer();
+ synchronized (this) {
+ Iterator it = data.values().iterator();
+ while (it.hasNext()) {
+ Element [] elements = allElements(it.next());
+ for (int i = 0; i < elements.length; i++) {
+ sb.append(elements[i]);
+ sb.append("\n");
+ }
+ }
+ }
+ return sb.toString();
+}
+
+}
diff --git a/src/org/xbill/DNS/Client.java b/src/org/xbill/DNS/Client.java
new file mode 100644
index 0000000..2eef44f
--- /dev/null
+++ b/src/org/xbill/DNS/Client.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2005 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.net.*;
+import java.nio.channels.*;
+import org.xbill.DNS.utils.hexdump;
+
+class Client {
+
+protected long endTime;
+protected SelectionKey key;
+
+protected
+Client(SelectableChannel channel, long endTime) throws IOException {
+ boolean done = false;
+ Selector selector = null;
+ this.endTime = endTime;
+ try {
+ selector = Selector.open();
+ channel.configureBlocking(false);
+ key = channel.register(selector, SelectionKey.OP_READ);
+ done = true;
+ }
+ finally {
+ if (!done && selector != null)
+ selector.close();
+ if (!done)
+ channel.close();
+ }
+}
+
+static protected void
+blockUntil(SelectionKey key, long endTime) throws IOException {
+ long timeout = endTime - System.currentTimeMillis();
+ int nkeys = 0;
+ if (timeout > 0)
+ nkeys = key.selector().select(timeout);
+ else if (timeout == 0)
+ nkeys = key.selector().selectNow();
+ if (nkeys == 0)
+ throw new SocketTimeoutException();
+}
+
+static protected void
+verboseLog(String prefix, byte [] data) {
+ if (Options.check("verbosemsg"))
+ System.err.println(hexdump.dump(prefix, data));
+}
+
+void
+cleanup() throws IOException {
+ key.selector().close();
+ key.channel().close();
+}
+
+}
diff --git a/src/org/xbill/DNS/ClientSubnetOption.java b/src/org/xbill/DNS/ClientSubnetOption.java
new file mode 100644
index 0000000..4a98a12
--- /dev/null
+++ b/src/org/xbill/DNS/ClientSubnetOption.java
@@ -0,0 +1,175 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.net.*;
+import java.util.regex.*;
+
+/**
+ * The Client Subnet EDNS Option, defined in
+ * http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-00
+ * ("Client subnet in DNS requests").
+ *
+ * The option is used to convey information about the IP address of the
+ * originating client, so that an authoritative server can make decisions
+ * based on this address, rather than the address of the intermediate
+ * caching name server.
+ *
+ * The option is transmitted as part of an OPTRecord in the additional section
+ * of a DNS message, as defined by RFC 2671 (EDNS0).
+ *
+ * An option code has not been assigned by IANA; the value 20730 (used here) is
+ * also used by several other implementations.
+ *
+ * The wire format of the option contains a 2-byte length field (1 for IPv4, 2
+ * for IPv6), a 1-byte source netmask, a 1-byte scope netmask, and an address
+ * truncated to the source netmask length (where the final octet is padded with
+ * bits set to 0)
+ *
+ *
+ * @see OPTRecord
+ *
+ * @author Brian Wellington
+ * @author Ming Zhou &lt;mizhou@bnivideo.com&gt;, Beaumaris Networks
+ */
+public class ClientSubnetOption extends EDNSOption {
+
+private static final long serialVersionUID = -3868158449890266347L;
+
+private int family;
+private int sourceNetmask;
+private int scopeNetmask;
+private InetAddress address;
+
+ClientSubnetOption() {
+ super(EDNSOption.Code.CLIENT_SUBNET);
+}
+
+private static int
+checkMaskLength(String field, int family, int val) {
+ int max = Address.addressLength(family) * 8;
+ if (val < 0 || val > max)
+ throw new IllegalArgumentException("\"" + field + "\" " + val +
+ " must be in the range " +
+ "[0.." + max + "]");
+ return val;
+}
+
+/**
+ * Construct a Client Subnet option. Note that the number of significant bits in
+ * the address must not be greater than the supplied source netmask.
+ * XXX something about Java's mapped addresses
+ * @param sourceNetmask The length of the netmask pertaining to the query.
+ * In replies, it mirrors the same value as in the requests.
+ * @param scopeNetmask The length of the netmask pertaining to the reply.
+ * In requests, it MUST be set to 0. In responses, this may or may not match
+ * the source netmask.
+ * @param address The address of the client.
+ */
+public
+ClientSubnetOption(int sourceNetmask, int scopeNetmask, InetAddress address) {
+ super(EDNSOption.Code.CLIENT_SUBNET);
+
+ this.family = Address.familyOf(address);
+ this.sourceNetmask = checkMaskLength("source netmask", this.family,
+ sourceNetmask);
+ this.scopeNetmask = checkMaskLength("scope netmask", this.family,
+ scopeNetmask);
+ this.address = Address.truncate(address, sourceNetmask);
+
+ if (!address.equals(this.address))
+ throw new IllegalArgumentException("source netmask is not " +
+ "valid for address");
+}
+
+/**
+ * Construct a Client Subnet option with scope netmask set to 0.
+ * @param sourceNetmask The length of the netmask pertaining to the query.
+ * In replies, it mirrors the same value as in the requests.
+ * @param address The address of the client.
+ * @see ClientSubnetOption
+ */
+public
+ClientSubnetOption(int sourceNetmask, InetAddress address) {
+ this(sourceNetmask, 0, address);
+}
+
+/**
+ * Returns the family of the network address. This will be either IPv4 (1)
+ * or IPv6 (2).
+ */
+public int
+getFamily() {
+ return family;
+}
+
+/** Returns the source netmask. */
+public int
+getSourceNetmask() {
+ return sourceNetmask;
+}
+
+/** Returns the scope netmask. */
+public int
+getScopeNetmask() {
+ return scopeNetmask;
+}
+
+/** Returns the IP address of the client. */
+public InetAddress
+getAddress() {
+ return address;
+}
+
+void
+optionFromWire(DNSInput in) throws WireParseException {
+ family = in.readU16();
+ if (family != Address.IPv4 && family != Address.IPv6)
+ throw new WireParseException("unknown address family");
+ sourceNetmask = in.readU8();
+ if (sourceNetmask > Address.addressLength(family) * 8)
+ throw new WireParseException("invalid source netmask");
+ scopeNetmask = in.readU8();
+ if (scopeNetmask > Address.addressLength(family) * 8)
+ throw new WireParseException("invalid scope netmask");
+
+ // Read the truncated address
+ byte [] addr = in.readByteArray();
+ if (addr.length != (sourceNetmask + 7) / 8)
+ throw new WireParseException("invalid address");
+
+ // Convert it to a full length address.
+ byte [] fulladdr = new byte[Address.addressLength(family)];
+ System.arraycopy(addr, 0, fulladdr, 0, addr.length);
+
+ try {
+ address = InetAddress.getByAddress(fulladdr);
+ } catch (UnknownHostException e) {
+ throw new WireParseException("invalid address", e);
+ }
+
+ InetAddress tmp = Address.truncate(address, sourceNetmask);
+ if (!tmp.equals(address))
+ throw new WireParseException("invalid padding");
+}
+
+void
+optionToWire(DNSOutput out) {
+ out.writeU16(family);
+ out.writeU8(sourceNetmask);
+ out.writeU8(scopeNetmask);
+ out.writeByteArray(address.getAddress(), 0, (sourceNetmask + 7) / 8);
+}
+
+String
+optionToString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(address.getHostAddress());
+ sb.append("/");
+ sb.append(sourceNetmask);
+ sb.append(", scope netmask ");
+ sb.append(scopeNetmask);
+ return sb.toString();
+}
+
+}
diff --git a/src/org/xbill/DNS/Compression.java b/src/org/xbill/DNS/Compression.java
new file mode 100644
index 0000000..e3e81c0
--- /dev/null
+++ b/src/org/xbill/DNS/Compression.java
@@ -0,0 +1,72 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * DNS Name Compression object.
+ * @see Message
+ * @see Name
+ *
+ * @author Brian Wellington
+ */
+
+public class Compression {
+
+private static class Entry {
+ Name name;
+ int pos;
+ Entry next;
+}
+
+private static final int TABLE_SIZE = 17;
+private static final int MAX_POINTER = 0x3FFF;
+private Entry [] table;
+private boolean verbose = Options.check("verbosecompression");
+
+/**
+ * Creates a new Compression object.
+ */
+public
+Compression() {
+ table = new Entry[TABLE_SIZE];
+}
+
+/**
+ * Adds a compression entry mapping a name to a position in a message.
+ * @param pos The position at which the name is added.
+ * @param name The name being added to the message.
+ */
+public void
+add(int pos, Name name) {
+ if (pos > MAX_POINTER)
+ return;
+ int row = (name.hashCode() & 0x7FFFFFFF) % TABLE_SIZE;
+ Entry entry = new Entry();
+ entry.name = name;
+ entry.pos = pos;
+ entry.next = table[row];
+ table[row] = entry;
+ if (verbose)
+ System.err.println("Adding " + name + " at " + pos);
+}
+
+/**
+ * Retrieves the position of the given name, if it has been previously
+ * included in the message.
+ * @param name The name to find in the compression table.
+ * @return The position of the name, or -1 if not found.
+ */
+public int
+get(Name name) {
+ int row = (name.hashCode() & 0x7FFFFFFF) % TABLE_SIZE;
+ int pos = -1;
+ for (Entry entry = table[row]; entry != null; entry = entry.next) {
+ if (entry.name.equals(name))
+ pos = entry.pos;
+ }
+ if (verbose)
+ System.err.println("Looking for " + name + ", found " + pos);
+ return pos;
+}
+
+}
diff --git a/src/org/xbill/DNS/Credibility.java b/src/org/xbill/DNS/Credibility.java
new file mode 100644
index 0000000..fa10686
--- /dev/null
+++ b/src/org/xbill/DNS/Credibility.java
@@ -0,0 +1,50 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * Constants relating to the credibility of cached data, which is based on
+ * the data's source. The constants NORMAL and ANY should be used by most
+ * callers.
+ * @see Cache
+ * @see Section
+ *
+ * @author Brian Wellington
+ */
+
+public final class Credibility {
+
+private
+Credibility() {}
+
+/** A hint or cache file on disk. */
+public static final int HINT = 0;
+
+/** The additional section of a response. */
+public static final int ADDITIONAL = 1;
+
+/** The additional section of a response. */
+public static final int GLUE = 2;
+
+/** The authority section of a nonauthoritative response. */
+public static final int NONAUTH_AUTHORITY = 3;
+
+/** The answer section of a nonauthoritative response. */
+public static final int NONAUTH_ANSWER = 3;
+
+/** The authority section of an authoritative response. */
+public static final int AUTH_AUTHORITY = 4;
+
+/** The answer section of a authoritative response. */
+public static final int AUTH_ANSWER = 4;
+
+/** A zone. */
+public static final int ZONE = 5;
+
+/** Credible data. */
+public static final int NORMAL = 3;
+
+/** Data not required to be credible. */
+public static final int ANY = 1;
+
+}
diff --git a/src/org/xbill/DNS/DClass.java b/src/org/xbill/DNS/DClass.java
new file mode 100644
index 0000000..22180cf
--- /dev/null
+++ b/src/org/xbill/DNS/DClass.java
@@ -0,0 +1,92 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * Constants and functions relating to DNS classes. This is called DClass
+ * to avoid confusion with Class.
+ *
+ * @author Brian Wellington
+ */
+
+public final class DClass {
+
+/** Internet */
+public static final int IN = 1;
+
+/** Chaos network (MIT) */
+public static final int CH = 3;
+
+/** Chaos network (MIT, alternate name) */
+public static final int CHAOS = 3;
+
+/** Hesiod name server (MIT) */
+public static final int HS = 4;
+
+/** Hesiod name server (MIT, alternate name) */
+public static final int HESIOD = 4;
+
+/** Special value used in dynamic update messages */
+public static final int NONE = 254;
+
+/** Matches any class */
+public static final int ANY = 255;
+
+private static class DClassMnemonic extends Mnemonic {
+ public
+ DClassMnemonic() {
+ super("DClass", CASE_UPPER);
+ setPrefix("CLASS");
+ }
+
+ public void
+ check(int val) {
+ DClass.check(val);
+ }
+}
+
+private static Mnemonic classes = new DClassMnemonic();
+
+static {
+ classes.add(IN, "IN");
+ classes.add(CH, "CH");
+ classes.addAlias(CH, "CHAOS");
+ classes.add(HS, "HS");
+ classes.addAlias(HS, "HESIOD");
+ classes.add(NONE, "NONE");
+ classes.add(ANY, "ANY");
+}
+
+private
+DClass() {}
+
+/**
+ * Checks that a numeric DClass is valid.
+ * @throws InvalidDClassException The class is out of range.
+ */
+public static void
+check(int i) {
+ if (i < 0 || i > 0xFFFF)
+ throw new InvalidDClassException(i);
+}
+
+/**
+ * Converts a numeric DClass into a String
+ * @return The canonical string representation of the class
+ * @throws InvalidDClassException The class is out of range.
+ */
+public static String
+string(int i) {
+ return classes.getText(i);
+}
+
+/**
+ * Converts a String representation of a DClass into its numeric value
+ * @return The class code, or -1 on error.
+ */
+public static int
+value(String s) {
+ return classes.getValue(s);
+}
+
+}
diff --git a/src/org/xbill/DNS/DHCIDRecord.java b/src/org/xbill/DNS/DHCIDRecord.java
new file mode 100644
index 0000000..e160a8c
--- /dev/null
+++ b/src/org/xbill/DNS/DHCIDRecord.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2008 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import org.xbill.DNS.utils.base64;
+
+/**
+ * DHCID - Dynamic Host Configuration Protocol (DHCP) ID (RFC 4701)
+ *
+ * @author Brian Wellington
+ */
+
+public class DHCIDRecord extends Record {
+
+private static final long serialVersionUID = -8214820200808997707L;
+
+private byte [] data;
+
+DHCIDRecord() {}
+
+Record
+getObject() {
+ return new DHCIDRecord();
+}
+
+/**
+ * Creates an DHCID Record from the given data
+ * @param data The binary data, which is opaque to DNS.
+ */
+public
+DHCIDRecord(Name name, int dclass, long ttl, byte [] data) {
+ super(name, Type.DHCID, dclass, ttl);
+ this.data = data;
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ data = in.readByteArray();
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ data = st.getBase64();
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeByteArray(data);
+}
+
+String
+rrToString() {
+ return base64.toString(data);
+}
+
+/**
+ * Returns the binary data.
+ */
+public byte []
+getData() {
+ return data;
+}
+
+}
diff --git a/src/org/xbill/DNS/DLVRecord.java b/src/org/xbill/DNS/DLVRecord.java
new file mode 100644
index 0000000..8acc90f
--- /dev/null
+++ b/src/org/xbill/DNS/DLVRecord.java
@@ -0,0 +1,132 @@
+// Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import org.xbill.DNS.utils.*;
+
+/**
+ * DLV - contains a Delegation Lookaside Validation record, which acts
+ * as the equivalent of a DS record in a lookaside zone.
+ * @see DNSSEC
+ * @see DSRecord
+ *
+ * @author David Blacka
+ * @author Brian Wellington
+ */
+
+public class DLVRecord extends Record {
+
+public static final int SHA1_DIGEST_ID = DSRecord.Digest.SHA1;
+public static final int SHA256_DIGEST_ID = DSRecord.Digest.SHA1;
+
+private static final long serialVersionUID = 1960742375677534148L;
+
+private int footprint;
+private int alg;
+private int digestid;
+private byte [] digest;
+
+DLVRecord() {}
+
+Record
+getObject() {
+ return new DLVRecord();
+}
+
+/**
+ * Creates a DLV Record from the given data
+ * @param footprint The original KEY record's footprint (keyid).
+ * @param alg The original key algorithm.
+ * @param digestid The digest id code.
+ * @param digest A hash of the original key.
+ */
+public
+DLVRecord(Name name, int dclass, long ttl, int footprint, int alg,
+ int digestid, byte [] digest)
+{
+ super(name, Type.DLV, dclass, ttl);
+ this.footprint = checkU16("footprint", footprint);
+ this.alg = checkU8("alg", alg);
+ this.digestid = checkU8("digestid", digestid);
+ this.digest = digest;
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ footprint = in.readU16();
+ alg = in.readU8();
+ digestid = in.readU8();
+ digest = in.readByteArray();
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ footprint = st.getUInt16();
+ alg = st.getUInt8();
+ digestid = st.getUInt8();
+ digest = st.getHex();
+}
+
+/**
+ * Converts rdata to a String
+ */
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(footprint);
+ sb.append(" ");
+ sb.append(alg);
+ sb.append(" ");
+ sb.append(digestid);
+ if (digest != null) {
+ sb.append(" ");
+ sb.append(base16.toString(digest));
+ }
+
+ return sb.toString();
+}
+
+/**
+ * Returns the key's algorithm.
+ */
+public int
+getAlgorithm() {
+ return alg;
+}
+
+/**
+ * Returns the key's Digest ID.
+ */
+public int
+getDigestID()
+{
+ return digestid;
+}
+
+/**
+ * Returns the binary hash of the key.
+ */
+public byte []
+getDigest() {
+ return digest;
+}
+
+/**
+ * Returns the key's footprint.
+ */
+public int
+getFootprint() {
+ return footprint;
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeU16(footprint);
+ out.writeU8(alg);
+ out.writeU8(digestid);
+ if (digest != null)
+ out.writeByteArray(digest);
+}
+
+}
diff --git a/src/org/xbill/DNS/DNAMERecord.java b/src/org/xbill/DNS/DNAMERecord.java
new file mode 100644
index 0000000..cbb322f
--- /dev/null
+++ b/src/org/xbill/DNS/DNAMERecord.java
@@ -0,0 +1,45 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * DNAME Record - maps a nonterminal alias (subtree) to a different domain
+ *
+ * @author Brian Wellington
+ */
+
+public class DNAMERecord extends SingleNameBase {
+
+private static final long serialVersionUID = 2670767677200844154L;
+
+DNAMERecord() {}
+
+Record
+getObject() {
+ return new DNAMERecord();
+}
+
+/**
+ * Creates a new DNAMERecord with the given data
+ * @param alias The name to which the DNAME alias points
+ */
+public
+DNAMERecord(Name name, int dclass, long ttl, Name alias) {
+ super(name, Type.DNAME, dclass, ttl, alias, "alias");
+}
+
+/**
+ * Gets the target of the DNAME Record
+ */
+public Name
+getTarget() {
+ return getSingleName();
+}
+
+/** Gets the alias specified by the DNAME Record */
+public Name
+getAlias() {
+ return getSingleName();
+}
+
+}
diff --git a/src/org/xbill/DNS/DNSInput.java b/src/org/xbill/DNS/DNSInput.java
new file mode 100644
index 0000000..d3134ed
--- /dev/null
+++ b/src/org/xbill/DNS/DNSInput.java
@@ -0,0 +1,239 @@
+// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * An class for parsing DNS messages.
+ *
+ * @author Brian Wellington
+ */
+
+public class DNSInput {
+
+private byte [] array;
+private int pos;
+private int end;
+private int saved_pos;
+private int saved_end;
+
+/**
+ * Creates a new DNSInput
+ * @param input The byte array to read from
+ */
+public
+DNSInput(byte [] input) {
+ array = input;
+ pos = 0;
+ end = array.length;
+ saved_pos = -1;
+ saved_end = -1;
+}
+
+/**
+ * Returns the current position.
+ */
+public int
+current() {
+ return pos;
+}
+
+/**
+ * Returns the number of bytes that can be read from this stream before
+ * reaching the end.
+ */
+public int
+remaining() {
+ return end - pos;
+}
+
+private void
+require(int n) throws WireParseException{
+ if (n > remaining()) {
+ throw new WireParseException("end of input");
+ }
+}
+
+/**
+ * Marks the following bytes in the stream as active.
+ * @param len The number of bytes in the active region.
+ * @throws IllegalArgumentException The number of bytes in the active region
+ * is longer than the remainder of the input.
+ */
+public void
+setActive(int len) {
+ if (len > array.length - pos) {
+ throw new IllegalArgumentException("cannot set active " +
+ "region past end of input");
+ }
+ end = pos + len;
+}
+
+/**
+ * Clears the active region of the string. Further operations are not
+ * restricted to part of the input.
+ */
+public void
+clearActive() {
+ end = array.length;
+}
+
+/**
+ * Returns the position of the end of the current active region.
+ */
+public int
+saveActive() {
+ return end;
+}
+
+/**
+ * Restores the previously set active region. This differs from setActive() in
+ * that restoreActive() takes an absolute position, and setActive takes an
+ * offset from the current location.
+ * @param pos The end of the active region.
+ */
+public void
+restoreActive(int pos) {
+ if (pos > array.length) {
+ throw new IllegalArgumentException("cannot set active " +
+ "region past end of input");
+ }
+ end = pos;
+}
+
+/**
+ * Resets the current position of the input stream to the specified index,
+ * and clears the active region.
+ * @param index The position to continue parsing at.
+ * @throws IllegalArgumentException The index is not within the input.
+ */
+public void
+jump(int index) {
+ if (index >= array.length) {
+ throw new IllegalArgumentException("cannot jump past " +
+ "end of input");
+ }
+ pos = index;
+ end = array.length;
+}
+
+/**
+ * Saves the current state of the input stream. Both the current position and
+ * the end of the active region are saved.
+ * @throws IllegalArgumentException The index is not within the input.
+ */
+public void
+save() {
+ saved_pos = pos;
+ saved_end = end;
+}
+
+/**
+ * Restores the input stream to its state before the call to {@link #save}.
+ */
+public void
+restore() {
+ if (saved_pos < 0) {
+ throw new IllegalStateException("no previous state");
+ }
+ pos = saved_pos;
+ end = saved_end;
+ saved_pos = -1;
+ saved_end = -1;
+}
+
+/**
+ * Reads an unsigned 8 bit value from the stream, as an int.
+ * @return An unsigned 8 bit value.
+ * @throws WireParseException The end of the stream was reached.
+ */
+public int
+readU8() throws WireParseException {
+ require(1);
+ return (array[pos++] & 0xFF);
+}
+
+/**
+ * Reads an unsigned 16 bit value from the stream, as an int.
+ * @return An unsigned 16 bit value.
+ * @throws WireParseException The end of the stream was reached.
+ */
+public int
+readU16() throws WireParseException {
+ require(2);
+ int b1 = array[pos++] & 0xFF;
+ int b2 = array[pos++] & 0xFF;
+ return ((b1 << 8) + b2);
+}
+
+/**
+ * Reads an unsigned 32 bit value from the stream, as a long.
+ * @return An unsigned 32 bit value.
+ * @throws WireParseException The end of the stream was reached.
+ */
+public long
+readU32() throws WireParseException {
+ require(4);
+ int b1 = array[pos++] & 0xFF;
+ int b2 = array[pos++] & 0xFF;
+ int b3 = array[pos++] & 0xFF;
+ int b4 = array[pos++] & 0xFF;
+ return (((long)b1 << 24) + (b2 << 16) + (b3 << 8) + b4);
+}
+
+/**
+ * Reads a byte array of a specified length from the stream into an existing
+ * array.
+ * @param b The array to read into.
+ * @param off The offset of the array to start copying data into.
+ * @param len The number of bytes to copy.
+ * @throws WireParseException The end of the stream was reached.
+ */
+public void
+readByteArray(byte [] b, int off, int len) throws WireParseException {
+ require(len);
+ System.arraycopy(array, pos, b, off, len);
+ pos += len;
+}
+
+/**
+ * Reads a byte array of a specified length from the stream.
+ * @return The byte array.
+ * @throws WireParseException The end of the stream was reached.
+ */
+public byte []
+readByteArray(int len) throws WireParseException {
+ require(len);
+ byte [] out = new byte[len];
+ System.arraycopy(array, pos, out, 0, len);
+ pos += len;
+ return out;
+}
+
+/**
+ * Reads a byte array consisting of the remainder of the stream (or the
+ * active region, if one is set.
+ * @return The byte array.
+ */
+public byte []
+readByteArray() {
+ int len = remaining();
+ byte [] out = new byte[len];
+ System.arraycopy(array, pos, out, 0, len);
+ pos += len;
+ return out;
+}
+
+/**
+ * Reads a counted string from the stream. A counted string is a one byte
+ * value indicating string length, followed by bytes of data.
+ * @return A byte array containing the string.
+ * @throws WireParseException The end of the stream was reached.
+ */
+public byte []
+readCountedString() throws WireParseException {
+ require(1);
+ int len = array[pos++] & 0xFF;
+ return readByteArray(len);
+}
+
+}
diff --git a/src/org/xbill/DNS/DNSKEYRecord.java b/src/org/xbill/DNS/DNSKEYRecord.java
new file mode 100644
index 0000000..6e9bafd
--- /dev/null
+++ b/src/org/xbill/DNS/DNSKEYRecord.java
@@ -0,0 +1,91 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.security.PublicKey;
+
+/**
+ * Key - contains a cryptographic public key for use by DNS.
+ * The data can be converted to objects implementing
+ * java.security.interfaces.PublicKey
+ * @see DNSSEC
+ *
+ * @author Brian Wellington
+ */
+
+public class DNSKEYRecord extends KEYBase {
+
+public static class Protocol {
+ private Protocol() {}
+
+ /** Key will be used for DNSSEC */
+ public static final int DNSSEC = 3;
+}
+
+public static class Flags {
+ private Flags() {}
+
+ /** Key is a zone key */
+ public static final int ZONE_KEY = 0x100;
+
+ /** Key is a secure entry point key */
+ public static final int SEP_KEY = 0x1;
+
+ /** Key has been revoked */
+ public static final int REVOKE = 0x80;
+}
+
+private static final long serialVersionUID = -8679800040426675002L;
+
+DNSKEYRecord() {}
+
+Record
+getObject() {
+ return new DNSKEYRecord();
+}
+
+/**
+ * Creates a DNSKEY Record from the given data
+ * @param flags Flags describing the key's properties
+ * @param proto The protocol that the key was created for
+ * @param alg The key's algorithm
+ * @param key Binary representation of the key
+ */
+public
+DNSKEYRecord(Name name, int dclass, long ttl, int flags, int proto, int alg,
+ byte [] key)
+{
+ super(name, Type.DNSKEY, dclass, ttl, flags, proto, alg, key);
+}
+
+/**
+ * Creates a DNSKEY Record from the given data
+ * @param flags Flags describing the key's properties
+ * @param proto The protocol that the key was created for
+ * @param alg The key's algorithm
+ * @param key The key as a PublicKey
+ * @throws DNSSEC.DNSSECException The PublicKey could not be converted into DNS
+ * format.
+ */
+public
+DNSKEYRecord(Name name, int dclass, long ttl, int flags, int proto, int alg,
+ PublicKey key) throws DNSSEC.DNSSECException
+{
+ super(name, Type.DNSKEY, dclass, ttl, flags, proto, alg,
+ DNSSEC.fromPublicKey(key, alg));
+ publicKey = key;
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ flags = st.getUInt16();
+ proto = st.getUInt8();
+ String algString = st.getString();
+ alg = DNSSEC.Algorithm.value(algString);
+ if (alg < 0)
+ throw st.exception("Invalid algorithm: " + algString);
+ key = st.getBase64();
+}
+
+}
diff --git a/src/org/xbill/DNS/DNSOutput.java b/src/org/xbill/DNS/DNSOutput.java
new file mode 100644
index 0000000..29a8f68
--- /dev/null
+++ b/src/org/xbill/DNS/DNSOutput.java
@@ -0,0 +1,203 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * A class for rendering DNS messages.
+ *
+ * @author Brian Wellington
+ */
+
+
+public class DNSOutput {
+
+private byte [] array;
+private int pos;
+private int saved_pos;
+
+/**
+ * Create a new DNSOutput with a specified size.
+ * @param size The initial size
+ */
+public
+DNSOutput(int size) {
+ array = new byte[size];
+ pos = 0;
+ saved_pos = -1;
+}
+
+/**
+ * Create a new DNSOutput
+ */
+public
+DNSOutput() {
+ this(32);
+}
+
+/**
+ * Returns the current position.
+ */
+public int
+current() {
+ return pos;
+}
+
+private void
+check(long val, int bits) {
+ long max = 1;
+ max <<= bits;
+ if (val < 0 || val > max) {
+ throw new IllegalArgumentException(val + " out of range for " +
+ bits + " bit value");
+ }
+}
+
+private void
+need(int n) {
+ if (array.length - pos >= n) {
+ return;
+ }
+ int newsize = array.length * 2;
+ if (newsize < pos + n) {
+ newsize = pos + n;
+ }
+ byte [] newarray = new byte[newsize];
+ System.arraycopy(array, 0, newarray, 0, pos);
+ array = newarray;
+}
+
+/**
+ * Resets the current position of the output stream to the specified index.
+ * @param index The new current position.
+ * @throws IllegalArgumentException The index is not within the output.
+ */
+public void
+jump(int index) {
+ if (index > pos) {
+ throw new IllegalArgumentException("cannot jump past " +
+ "end of data");
+ }
+ pos = index;
+}
+
+/**
+ * Saves the current state of the output stream.
+ * @throws IllegalArgumentException The index is not within the output.
+ */
+public void
+save() {
+ saved_pos = pos;
+}
+
+/**
+ * Restores the input stream to its state before the call to {@link #save}.
+ */
+public void
+restore() {
+ if (saved_pos < 0) {
+ throw new IllegalStateException("no previous state");
+ }
+ pos = saved_pos;
+ saved_pos = -1;
+}
+
+/**
+ * Writes an unsigned 8 bit value to the stream.
+ * @param val The value to be written
+ */
+public void
+writeU8(int val) {
+ check(val, 8);
+ need(1);
+ array[pos++] = (byte)(val & 0xFF);
+}
+
+/**
+ * Writes an unsigned 16 bit value to the stream.
+ * @param val The value to be written
+ */
+public void
+writeU16(int val) {
+ check(val, 16);
+ need(2);
+ array[pos++] = (byte)((val >>> 8) & 0xFF);
+ array[pos++] = (byte)(val & 0xFF);
+}
+
+/**
+ * Writes an unsigned 16 bit value to the specified position in the stream.
+ * @param val The value to be written
+ * @param where The position to write the value.
+ */
+public void
+writeU16At(int val, int where) {
+ check(val, 16);
+ if (where > pos - 2)
+ throw new IllegalArgumentException("cannot write past " +
+ "end of data");
+ array[where++] = (byte)((val >>> 8) & 0xFF);
+ array[where++] = (byte)(val & 0xFF);
+}
+
+/**
+ * Writes an unsigned 32 bit value to the stream.
+ * @param val The value to be written
+ */
+public void
+writeU32(long val) {
+ check(val, 32);
+ need(4);
+ array[pos++] = (byte)((val >>> 24) & 0xFF);
+ array[pos++] = (byte)((val >>> 16) & 0xFF);
+ array[pos++] = (byte)((val >>> 8) & 0xFF);
+ array[pos++] = (byte)(val & 0xFF);
+}
+
+/**
+ * Writes a byte array to the stream.
+ * @param b The array to write.
+ * @param off The offset of the array to start copying data from.
+ * @param len The number of bytes to write.
+ */
+public void
+writeByteArray(byte [] b, int off, int len) {
+ need(len);
+ System.arraycopy(b, off, array, pos, len);
+ pos += len;
+}
+
+/**
+ * Writes a byte array to the stream.
+ * @param b The array to write.
+ */
+public void
+writeByteArray(byte [] b) {
+ writeByteArray(b, 0, b.length);
+}
+
+/**
+ * Writes a counted string from the stream. A counted string is a one byte
+ * value indicating string length, followed by bytes of data.
+ * @param s The string to write.
+ */
+public void
+writeCountedString(byte [] s) {
+ if (s.length > 0xFF) {
+ throw new IllegalArgumentException("Invalid counted string");
+ }
+ need(1 + s.length);
+ array[pos++] = (byte)(s.length & 0xFF);
+ writeByteArray(s, 0, s.length);
+}
+
+/**
+ * Returns a byte array containing the current contents of the stream.
+ */
+public byte []
+toByteArray() {
+ byte [] out = new byte[pos];
+ System.arraycopy(array, 0, out, 0, pos);
+ return out;
+}
+
+}
diff --git a/src/org/xbill/DNS/DNSSEC.java b/src/org/xbill/DNS/DNSSEC.java
new file mode 100644
index 0000000..2707947
--- /dev/null
+++ b/src/org/xbill/DNS/DNSSEC.java
@@ -0,0 +1,1026 @@
+// Copyright (c) 1999-2010 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.math.*;
+import java.security.*;
+import java.security.interfaces.*;
+import java.security.spec.*;
+import java.util.*;
+
+/**
+ * Constants and methods relating to DNSSEC.
+ *
+ * DNSSEC provides authentication for DNS information.
+ * @see RRSIGRecord
+ * @see DNSKEYRecord
+ * @see RRset
+ *
+ * @author Brian Wellington
+ */
+
+public class DNSSEC {
+
+public static class Algorithm {
+ private Algorithm() {}
+
+ /** RSA/MD5 public key (deprecated) */
+ public static final int RSAMD5 = 1;
+
+ /** Diffie Hellman key */
+ public static final int DH = 2;
+
+ /** DSA public key */
+ public static final int DSA = 3;
+
+ /** RSA/SHA1 public key */
+ public static final int RSASHA1 = 5;
+
+ /** DSA/SHA1, NSEC3-aware public key */
+ public static final int DSA_NSEC3_SHA1 = 6;
+
+ /** RSA/SHA1, NSEC3-aware public key */
+ public static final int RSA_NSEC3_SHA1 = 7;
+
+ /** RSA/SHA256 public key */
+ public static final int RSASHA256 = 8;
+
+ /** RSA/SHA512 public key */
+ public static final int RSASHA512 = 10;
+
+ /** ECDSA Curve P-256 with SHA-256 public key **/
+ public static final int ECDSAP256SHA256 = 13;
+
+ /** ECDSA Curve P-384 with SHA-384 public key **/
+ public static final int ECDSAP384SHA384 = 14;
+
+ /** Indirect keys; the actual key is elsewhere. */
+ public static final int INDIRECT = 252;
+
+ /** Private algorithm, specified by domain name */
+ public static final int PRIVATEDNS = 253;
+
+ /** Private algorithm, specified by OID */
+ public static final int PRIVATEOID = 254;
+
+ private static Mnemonic algs = new Mnemonic("DNSSEC algorithm",
+ Mnemonic.CASE_UPPER);
+
+ static {
+ algs.setMaximum(0xFF);
+ algs.setNumericAllowed(true);
+
+ algs.add(RSAMD5, "RSAMD5");
+ algs.add(DH, "DH");
+ algs.add(DSA, "DSA");
+ algs.add(RSASHA1, "RSASHA1");
+ algs.add(DSA_NSEC3_SHA1, "DSA-NSEC3-SHA1");
+ algs.add(RSA_NSEC3_SHA1, "RSA-NSEC3-SHA1");
+ algs.add(RSASHA256, "RSASHA256");
+ algs.add(RSASHA512, "RSASHA512");
+ algs.add(ECDSAP256SHA256, "ECDSAP256SHA256");
+ algs.add(ECDSAP384SHA384, "ECDSAP384SHA384");
+ algs.add(INDIRECT, "INDIRECT");
+ algs.add(PRIVATEDNS, "PRIVATEDNS");
+ algs.add(PRIVATEOID, "PRIVATEOID");
+ }
+
+ /**
+ * Converts an algorithm into its textual representation
+ */
+ public static String
+ string(int alg) {
+ return algs.getText(alg);
+ }
+
+ /**
+ * Converts a textual representation of an algorithm into its numeric
+ * code. Integers in the range 0..255 are also accepted.
+ * @param s The textual representation of the algorithm
+ * @return The algorithm code, or -1 on error.
+ */
+ public static int
+ value(String s) {
+ return algs.getValue(s);
+ }
+}
+
+private
+DNSSEC() { }
+
+private static void
+digestSIG(DNSOutput out, SIGBase sig) {
+ out.writeU16(sig.getTypeCovered());
+ out.writeU8(sig.getAlgorithm());
+ out.writeU8(sig.getLabels());
+ out.writeU32(sig.getOrigTTL());
+ out.writeU32(sig.getExpire().getTime() / 1000);
+ out.writeU32(sig.getTimeSigned().getTime() / 1000);
+ out.writeU16(sig.getFootprint());
+ sig.getSigner().toWireCanonical(out);
+}
+
+/**
+ * Creates a byte array containing the concatenation of the fields of the
+ * SIG record and the RRsets to be signed/verified. This does not perform
+ * a cryptographic digest.
+ * @param rrsig The RRSIG record used to sign/verify the rrset.
+ * @param rrset The data to be signed/verified.
+ * @return The data to be cryptographically signed or verified.
+ */
+public static byte []
+digestRRset(RRSIGRecord rrsig, RRset rrset) {
+ DNSOutput out = new DNSOutput();
+ digestSIG(out, rrsig);
+
+ int size = rrset.size();
+ Record [] records = new Record[size];
+
+ Iterator it = rrset.rrs();
+ Name name = rrset.getName();
+ Name wild = null;
+ int sigLabels = rrsig.getLabels() + 1; // Add the root label back.
+ if (name.labels() > sigLabels)
+ wild = name.wild(name.labels() - sigLabels);
+ while (it.hasNext())
+ records[--size] = (Record) it.next();
+ Arrays.sort(records);
+
+ DNSOutput header = new DNSOutput();
+ if (wild != null)
+ wild.toWireCanonical(header);
+ else
+ name.toWireCanonical(header);
+ header.writeU16(rrset.getType());
+ header.writeU16(rrset.getDClass());
+ header.writeU32(rrsig.getOrigTTL());
+ for (int i = 0; i < records.length; i++) {
+ out.writeByteArray(header.toByteArray());
+ int lengthPosition = out.current();
+ out.writeU16(0);
+ out.writeByteArray(records[i].rdataToWireCanonical());
+ int rrlength = out.current() - lengthPosition - 2;
+ out.save();
+ out.jump(lengthPosition);
+ out.writeU16(rrlength);
+ out.restore();
+ }
+ return out.toByteArray();
+}
+
+/**
+ * Creates a byte array containing the concatenation of the fields of the
+ * SIG(0) record and the message to be signed. This does not perform
+ * a cryptographic digest.
+ * @param sig The SIG record used to sign the rrset.
+ * @param msg The message to be signed.
+ * @param previous If this is a response, the signature from the query.
+ * @return The data to be cryptographically signed.
+ */
+public static byte []
+digestMessage(SIGRecord sig, Message msg, byte [] previous) {
+ DNSOutput out = new DNSOutput();
+ digestSIG(out, sig);
+
+ if (previous != null)
+ out.writeByteArray(previous);
+
+ msg.toWire(out);
+ return out.toByteArray();
+}
+
+/**
+ * A DNSSEC exception.
+ */
+public static class DNSSECException extends Exception {
+ DNSSECException(String s) {
+ super(s);
+ }
+}
+
+/**
+ * An algorithm is unsupported by this DNSSEC implementation.
+ */
+public static class UnsupportedAlgorithmException extends DNSSECException {
+ UnsupportedAlgorithmException(int alg) {
+ super("Unsupported algorithm: " + alg);
+ }
+}
+
+/**
+ * The cryptographic data in a DNSSEC key is malformed.
+ */
+public static class MalformedKeyException extends DNSSECException {
+ MalformedKeyException(KEYBase rec) {
+ super("Invalid key data: " + rec.rdataToString());
+ }
+}
+
+/**
+ * A DNSSEC verification failed because fields in the DNSKEY and RRSIG records
+ * do not match.
+ */
+public static class KeyMismatchException extends DNSSECException {
+ private KEYBase key;
+ private SIGBase sig;
+
+ KeyMismatchException(KEYBase key, SIGBase sig) {
+ super("key " +
+ key.getName() + "/" +
+ DNSSEC.Algorithm.string(key.getAlgorithm()) + "/" +
+ key.getFootprint() + " " +
+ "does not match signature " +
+ sig.getSigner() + "/" +
+ DNSSEC.Algorithm.string(sig.getAlgorithm()) + "/" +
+ sig.getFootprint());
+ }
+}
+
+/**
+ * A DNSSEC verification failed because the signature has expired.
+ */
+public static class SignatureExpiredException extends DNSSECException {
+ private Date when, now;
+
+ SignatureExpiredException(Date when, Date now) {
+ super("signature expired");
+ this.when = when;
+ this.now = now;
+ }
+
+ /**
+ * @return When the signature expired
+ */
+ public Date
+ getExpiration() {
+ return when;
+ }
+
+ /**
+ * @return When the verification was attempted
+ */
+ public Date
+ getVerifyTime() {
+ return now;
+ }
+}
+
+/**
+ * A DNSSEC verification failed because the signature has not yet become valid.
+ */
+public static class SignatureNotYetValidException extends DNSSECException {
+ private Date when, now;
+
+ SignatureNotYetValidException(Date when, Date now) {
+ super("signature is not yet valid");
+ this.when = when;
+ this.now = now;
+ }
+
+ /**
+ * @return When the signature will become valid
+ */
+ public Date
+ getExpiration() {
+ return when;
+ }
+
+ /**
+ * @return When the verification was attempted
+ */
+ public Date
+ getVerifyTime() {
+ return now;
+ }
+}
+
+/**
+ * A DNSSEC verification failed because the cryptographic signature
+ * verification failed.
+ */
+public static class SignatureVerificationException extends DNSSECException {
+ SignatureVerificationException() {
+ super("signature verification failed");
+ }
+}
+
+/**
+ * The key data provided is inconsistent.
+ */
+public static class IncompatibleKeyException extends IllegalArgumentException {
+ IncompatibleKeyException() {
+ super("incompatible keys");
+ }
+}
+
+private static int
+BigIntegerLength(BigInteger i) {
+ return (i.bitLength() + 7) / 8;
+}
+
+private static BigInteger
+readBigInteger(DNSInput in, int len) throws IOException {
+ byte [] b = in.readByteArray(len);
+ return new BigInteger(1, b);
+}
+
+private static BigInteger
+readBigInteger(DNSInput in) {
+ byte [] b = in.readByteArray();
+ return new BigInteger(1, b);
+}
+
+private static void
+writeBigInteger(DNSOutput out, BigInteger val) {
+ byte [] b = val.toByteArray();
+ if (b[0] == 0)
+ out.writeByteArray(b, 1, b.length - 1);
+ else
+ out.writeByteArray(b);
+}
+
+private static PublicKey
+toRSAPublicKey(KEYBase r) throws IOException, GeneralSecurityException {
+ DNSInput in = new DNSInput(r.getKey());
+ int exponentLength = in.readU8();
+ if (exponentLength == 0)
+ exponentLength = in.readU16();
+ BigInteger exponent = readBigInteger(in, exponentLength);
+ BigInteger modulus = readBigInteger(in);
+
+ KeyFactory factory = KeyFactory.getInstance("RSA");
+ return factory.generatePublic(new RSAPublicKeySpec(modulus, exponent));
+}
+
+private static PublicKey
+toDSAPublicKey(KEYBase r) throws IOException, GeneralSecurityException,
+ MalformedKeyException
+{
+ DNSInput in = new DNSInput(r.getKey());
+
+ int t = in.readU8();
+ if (t > 8)
+ throw new MalformedKeyException(r);
+
+ BigInteger q = readBigInteger(in, 20);
+ BigInteger p = readBigInteger(in, 64 + t*8);
+ BigInteger g = readBigInteger(in, 64 + t*8);
+ BigInteger y = readBigInteger(in, 64 + t*8);
+
+ KeyFactory factory = KeyFactory.getInstance("DSA");
+ return factory.generatePublic(new DSAPublicKeySpec(y, p, q, g));
+}
+
+private static class ECKeyInfo {
+ int length;
+ public BigInteger p, a, b, gx, gy, n;
+ EllipticCurve curve;
+ ECParameterSpec spec;
+
+ ECKeyInfo(int length, String p_str, String a_str, String b_str,
+ String gx_str, String gy_str, String n_str)
+ {
+ this.length = length;
+ p = new BigInteger(p_str, 16);
+ a = new BigInteger(a_str, 16);
+ b = new BigInteger(b_str, 16);
+ gx = new BigInteger(gx_str, 16);
+ gy = new BigInteger(gy_str, 16);
+ n = new BigInteger(n_str, 16);
+ curve = new EllipticCurve(new ECFieldFp(p), a, b);
+ spec = new ECParameterSpec(curve, new ECPoint(gx, gy), n, 1);
+ }
+}
+
+// RFC 5114 Section 2.6
+private static final ECKeyInfo ECDSA_P256 = new ECKeyInfo(32,
+ "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF",
+ "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC",
+ "5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B",
+ "6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296",
+ "4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5",
+ "FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551");
+
+// RFC 5114 Section 2.7
+private static final ECKeyInfo ECDSA_P384 = new ECKeyInfo(48,
+ "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF",
+ "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC",
+ "B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF",
+ "AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7",
+ "3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F",
+ "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973");
+
+private static PublicKey
+toECDSAPublicKey(KEYBase r, ECKeyInfo keyinfo) throws IOException,
+ GeneralSecurityException, MalformedKeyException
+{
+ DNSInput in = new DNSInput(r.getKey());
+
+ // RFC 6605 Section 4
+ BigInteger x = readBigInteger(in, keyinfo.length);
+ BigInteger y = readBigInteger(in, keyinfo.length);
+ ECPoint q = new ECPoint(x, y);
+
+ KeyFactory factory = KeyFactory.getInstance("EC");
+ return factory.generatePublic(new ECPublicKeySpec(q, keyinfo.spec));
+}
+
+/** Converts a KEY/DNSKEY record into a PublicKey */
+static PublicKey
+toPublicKey(KEYBase r) throws DNSSECException {
+ int alg = r.getAlgorithm();
+ try {
+ switch (alg) {
+ case Algorithm.RSAMD5:
+ case Algorithm.RSASHA1:
+ case Algorithm.RSA_NSEC3_SHA1:
+ case Algorithm.RSASHA256:
+ case Algorithm.RSASHA512:
+ return toRSAPublicKey(r);
+ case Algorithm.DSA:
+ case Algorithm.DSA_NSEC3_SHA1:
+ return toDSAPublicKey(r);
+ case Algorithm.ECDSAP256SHA256:
+ return toECDSAPublicKey(r, ECDSA_P256);
+ case Algorithm.ECDSAP384SHA384:
+ return toECDSAPublicKey(r, ECDSA_P384);
+ default:
+ throw new UnsupportedAlgorithmException(alg);
+ }
+ }
+ catch (IOException e) {
+ throw new MalformedKeyException(r);
+ }
+ catch (GeneralSecurityException e) {
+ throw new DNSSECException(e.toString());
+ }
+}
+
+private static byte []
+fromRSAPublicKey(RSAPublicKey key) {
+ DNSOutput out = new DNSOutput();
+ BigInteger exponent = key.getPublicExponent();
+ BigInteger modulus = key.getModulus();
+ int exponentLength = BigIntegerLength(exponent);
+
+ if (exponentLength < 256)
+ out.writeU8(exponentLength);
+ else {
+ out.writeU8(0);
+ out.writeU16(exponentLength);
+ }
+ writeBigInteger(out, exponent);
+ writeBigInteger(out, modulus);
+
+ return out.toByteArray();
+}
+
+private static byte []
+fromDSAPublicKey(DSAPublicKey key) {
+ DNSOutput out = new DNSOutput();
+ BigInteger q = key.getParams().getQ();
+ BigInteger p = key.getParams().getP();
+ BigInteger g = key.getParams().getG();
+ BigInteger y = key.getY();
+ int t = (p.toByteArray().length - 64) / 8;
+
+ out.writeU8(t);
+ writeBigInteger(out, q);
+ writeBigInteger(out, p);
+ writeBigInteger(out, g);
+ writeBigInteger(out, y);
+
+ return out.toByteArray();
+}
+
+private static byte []
+fromECDSAPublicKey(ECPublicKey key) {
+ DNSOutput out = new DNSOutput();
+
+ BigInteger x = key.getW().getAffineX();
+ BigInteger y = key.getW().getAffineY();
+
+ writeBigInteger(out, x);
+ writeBigInteger(out, y);
+
+ return out.toByteArray();
+}
+
+/** Builds a DNSKEY record from a PublicKey */
+static byte []
+fromPublicKey(PublicKey key, int alg) throws DNSSECException
+{
+
+ switch (alg) {
+ case Algorithm.RSAMD5:
+ case Algorithm.RSASHA1:
+ case Algorithm.RSA_NSEC3_SHA1:
+ case Algorithm.RSASHA256:
+ case Algorithm.RSASHA512:
+ if (! (key instanceof RSAPublicKey))
+ throw new IncompatibleKeyException();
+ return fromRSAPublicKey((RSAPublicKey) key);
+ case Algorithm.DSA:
+ case Algorithm.DSA_NSEC3_SHA1:
+ if (! (key instanceof DSAPublicKey))
+ throw new IncompatibleKeyException();
+ return fromDSAPublicKey((DSAPublicKey) key);
+ case Algorithm.ECDSAP256SHA256:
+ case Algorithm.ECDSAP384SHA384:
+ if (! (key instanceof ECPublicKey))
+ throw new IncompatibleKeyException();
+ return fromECDSAPublicKey((ECPublicKey) key);
+ default:
+ throw new UnsupportedAlgorithmException(alg);
+ }
+}
+
+/**
+ * Convert an algorithm number to the corresponding JCA string.
+ * @param alg The algorithm number.
+ * @throws UnsupportedAlgorithmException The algorithm is unknown.
+ */
+public static String
+algString(int alg) throws UnsupportedAlgorithmException {
+ switch (alg) {
+ case Algorithm.RSAMD5:
+ return "MD5withRSA";
+ case Algorithm.DSA:
+ case Algorithm.DSA_NSEC3_SHA1:
+ return "SHA1withDSA";
+ case Algorithm.RSASHA1:
+ case Algorithm.RSA_NSEC3_SHA1:
+ return "SHA1withRSA";
+ case Algorithm.RSASHA256:
+ return "SHA256withRSA";
+ case Algorithm.RSASHA512:
+ return "SHA512withRSA";
+ case Algorithm.ECDSAP256SHA256:
+ return "SHA256withECDSA";
+ case Algorithm.ECDSAP384SHA384:
+ return "SHA384withECDSA";
+ default:
+ throw new UnsupportedAlgorithmException(alg);
+ }
+}
+
+private static final int ASN1_SEQ = 0x30;
+private static final int ASN1_INT = 0x2;
+
+private static final int DSA_LEN = 20;
+
+private static byte []
+DSASignaturefromDNS(byte [] dns) throws DNSSECException, IOException {
+ if (dns.length != 1 + DSA_LEN * 2)
+ throw new SignatureVerificationException();
+
+ DNSInput in = new DNSInput(dns);
+ DNSOutput out = new DNSOutput();
+
+ int t = in.readU8();
+
+ byte [] r = in.readByteArray(DSA_LEN);
+ int rlen = DSA_LEN;
+ if (r[0] < 0)
+ rlen++;
+
+ byte [] s = in.readByteArray(DSA_LEN);
+ int slen = DSA_LEN;
+ if (s[0] < 0)
+ slen++;
+
+ out.writeU8(ASN1_SEQ);
+ out.writeU8(rlen + slen + 4);
+
+ out.writeU8(ASN1_INT);
+ out.writeU8(rlen);
+ if (rlen > DSA_LEN)
+ out.writeU8(0);
+ out.writeByteArray(r);
+
+ out.writeU8(ASN1_INT);
+ out.writeU8(slen);
+ if (slen > DSA_LEN)
+ out.writeU8(0);
+ out.writeByteArray(s);
+
+ return out.toByteArray();
+}
+
+private static byte []
+DSASignaturetoDNS(byte [] signature, int t) throws IOException {
+ DNSInput in = new DNSInput(signature);
+ DNSOutput out = new DNSOutput();
+
+ out.writeU8(t);
+
+ int tmp = in.readU8();
+ if (tmp != ASN1_SEQ)
+ throw new IOException();
+ int seqlen = in.readU8();
+
+ tmp = in.readU8();
+ if (tmp != ASN1_INT)
+ throw new IOException();
+ int rlen = in.readU8();
+ if (rlen == DSA_LEN + 1) {
+ if (in.readU8() != 0)
+ throw new IOException();
+ } else if (rlen != DSA_LEN)
+ throw new IOException();
+ byte [] bytes = in.readByteArray(DSA_LEN);
+ out.writeByteArray(bytes);
+
+ tmp = in.readU8();
+ if (tmp != ASN1_INT)
+ throw new IOException();
+ int slen = in.readU8();
+ if (slen == DSA_LEN + 1) {
+ if (in.readU8() != 0)
+ throw new IOException();
+ } else if (slen != DSA_LEN)
+ throw new IOException();
+ bytes = in.readByteArray(DSA_LEN);
+ out.writeByteArray(bytes);
+
+ return out.toByteArray();
+}
+
+private static byte []
+ECDSASignaturefromDNS(byte [] signature, ECKeyInfo keyinfo)
+ throws DNSSECException, IOException
+{
+ if (signature.length != keyinfo.length * 2)
+ throw new SignatureVerificationException();
+
+ DNSInput in = new DNSInput(signature);
+ DNSOutput out = new DNSOutput();
+
+ byte [] r = in.readByteArray(keyinfo.length);
+ int rlen = keyinfo.length;
+ if (r[0] < 0)
+ rlen++;
+
+ byte [] s = in.readByteArray(keyinfo.length);
+ int slen = keyinfo.length;
+ if (s[0] < 0)
+ slen++;
+
+ out.writeU8(ASN1_SEQ);
+ out.writeU8(rlen + slen + 4);
+
+ out.writeU8(ASN1_INT);
+ out.writeU8(rlen);
+ if (rlen > keyinfo.length)
+ out.writeU8(0);
+ out.writeByteArray(r);
+
+ out.writeU8(ASN1_INT);
+ out.writeU8(slen);
+ if (slen > keyinfo.length)
+ out.writeU8(0);
+ out.writeByteArray(s);
+
+ return out.toByteArray();
+}
+
+private static byte []
+ECDSASignaturetoDNS(byte [] signature, ECKeyInfo keyinfo) throws IOException {
+ DNSInput in = new DNSInput(signature);
+ DNSOutput out = new DNSOutput();
+
+ int tmp = in.readU8();
+ if (tmp != ASN1_SEQ)
+ throw new IOException();
+ int seqlen = in.readU8();
+
+ tmp = in.readU8();
+ if (tmp != ASN1_INT)
+ throw new IOException();
+ int rlen = in.readU8();
+ if (rlen == keyinfo.length + 1) {
+ if (in.readU8() != 0)
+ throw new IOException();
+ } else if (rlen != keyinfo.length)
+ throw new IOException();
+ byte[] bytes = in.readByteArray(keyinfo.length);
+ out.writeByteArray(bytes);
+
+ tmp = in.readU8();
+ if (tmp != ASN1_INT)
+ throw new IOException();
+ int slen = in.readU8();
+ if (slen == keyinfo.length + 1) {
+ if (in.readU8() != 0)
+ throw new IOException();
+ } else if (slen != keyinfo.length)
+ throw new IOException();
+ bytes = in.readByteArray(keyinfo.length);
+ out.writeByteArray(bytes);
+
+ return out.toByteArray();
+}
+
+private static void
+verify(PublicKey key, int alg, byte [] data, byte [] signature)
+throws DNSSECException
+{
+ if (key instanceof DSAPublicKey) {
+ try {
+ signature = DSASignaturefromDNS(signature);
+ }
+ catch (IOException e) {
+ throw new IllegalStateException();
+ }
+ } else if (key instanceof ECPublicKey) {
+ try {
+ switch (alg) {
+ case Algorithm.ECDSAP256SHA256:
+ signature = ECDSASignaturefromDNS(signature,
+ ECDSA_P256);
+ break;
+ case Algorithm.ECDSAP384SHA384:
+ signature = ECDSASignaturefromDNS(signature,
+ ECDSA_P384);
+ break;
+ default:
+ throw new UnsupportedAlgorithmException(alg);
+ }
+ }
+ catch (IOException e) {
+ throw new IllegalStateException();
+ }
+ }
+
+ try {
+ Signature s = Signature.getInstance(algString(alg));
+ s.initVerify(key);
+ s.update(data);
+ if (!s.verify(signature))
+ throw new SignatureVerificationException();
+ }
+ catch (GeneralSecurityException e) {
+ throw new DNSSECException(e.toString());
+ }
+}
+
+private static boolean
+matches(SIGBase sig, KEYBase key)
+{
+ return (key.getAlgorithm() == sig.getAlgorithm() &&
+ key.getFootprint() == sig.getFootprint() &&
+ key.getName().equals(sig.getSigner()));
+}
+
+/**
+ * Verify a DNSSEC signature.
+ * @param rrset The data to be verified.
+ * @param rrsig The RRSIG record containing the signature.
+ * @param key The DNSKEY record to verify the signature with.
+ * @throws UnsupportedAlgorithmException The algorithm is unknown
+ * @throws MalformedKeyException The key is malformed
+ * @throws KeyMismatchException The key and signature do not match
+ * @throws SignatureExpiredException The signature has expired
+ * @throws SignatureNotYetValidException The signature is not yet valid
+ * @throws SignatureVerificationException The signature does not verify.
+ * @throws DNSSECException Some other error occurred.
+ */
+public static void
+verify(RRset rrset, RRSIGRecord rrsig, DNSKEYRecord key) throws DNSSECException
+{
+ if (!matches(rrsig, key))
+ throw new KeyMismatchException(key, rrsig);
+
+ Date now = new Date();
+ if (now.compareTo(rrsig.getExpire()) > 0)
+ throw new SignatureExpiredException(rrsig.getExpire(), now);
+ if (now.compareTo(rrsig.getTimeSigned()) < 0)
+ throw new SignatureNotYetValidException(rrsig.getTimeSigned(),
+ now);
+
+ verify(key.getPublicKey(), rrsig.getAlgorithm(),
+ digestRRset(rrsig, rrset), rrsig.getSignature());
+}
+
+private static byte []
+sign(PrivateKey privkey, PublicKey pubkey, int alg, byte [] data,
+ String provider) throws DNSSECException
+{
+ byte [] signature;
+ try {
+ Signature s;
+ if (provider != null)
+ s = Signature.getInstance(algString(alg), provider);
+ else
+ s = Signature.getInstance(algString(alg));
+ s.initSign(privkey);
+ s.update(data);
+ signature = s.sign();
+ }
+ catch (GeneralSecurityException e) {
+ throw new DNSSECException(e.toString());
+ }
+
+ if (pubkey instanceof DSAPublicKey) {
+ try {
+ DSAPublicKey dsa = (DSAPublicKey) pubkey;
+ BigInteger P = dsa.getParams().getP();
+ int t = (BigIntegerLength(P) - 64) / 8;
+ signature = DSASignaturetoDNS(signature, t);
+ }
+ catch (IOException e) {
+ throw new IllegalStateException();
+ }
+ } else if (pubkey instanceof ECPublicKey) {
+ try {
+ switch (alg) {
+ case Algorithm.ECDSAP256SHA256:
+ signature = ECDSASignaturetoDNS(signature,
+ ECDSA_P256);
+ break;
+ case Algorithm.ECDSAP384SHA384:
+ signature = ECDSASignaturetoDNS(signature,
+ ECDSA_P384);
+ break;
+ default:
+ throw new UnsupportedAlgorithmException(alg);
+ }
+ }
+ catch (IOException e) {
+ throw new IllegalStateException();
+ }
+ }
+
+ return signature;
+}
+static void
+checkAlgorithm(PrivateKey key, int alg) throws UnsupportedAlgorithmException
+{
+ switch (alg) {
+ case Algorithm.RSAMD5:
+ case Algorithm.RSASHA1:
+ case Algorithm.RSA_NSEC3_SHA1:
+ case Algorithm.RSASHA256:
+ case Algorithm.RSASHA512:
+ if (! (key instanceof RSAPrivateKey))
+ throw new IncompatibleKeyException();
+ break;
+ case Algorithm.DSA:
+ case Algorithm.DSA_NSEC3_SHA1:
+ if (! (key instanceof DSAPrivateKey))
+ throw new IncompatibleKeyException();
+ break;
+ case Algorithm.ECDSAP256SHA256:
+ case Algorithm.ECDSAP384SHA384:
+ if (! (key instanceof ECPrivateKey))
+ throw new IncompatibleKeyException();
+ break;
+ default:
+ throw new UnsupportedAlgorithmException(alg);
+ }
+}
+
+/**
+ * Generate a DNSSEC signature. key and privateKey must refer to the
+ * same underlying cryptographic key.
+ * @param rrset The data to be signed
+ * @param key The DNSKEY record to use as part of signing
+ * @param privkey The PrivateKey to use when signing
+ * @param inception The time at which the signatures should become valid
+ * @param expiration The time at which the signatures should expire
+ * @throws UnsupportedAlgorithmException The algorithm is unknown
+ * @throws MalformedKeyException The key is malformed
+ * @throws DNSSECException Some other error occurred.
+ * @return The generated signature
+ */
+public static RRSIGRecord
+sign(RRset rrset, DNSKEYRecord key, PrivateKey privkey,
+ Date inception, Date expiration) throws DNSSECException
+{
+ return sign(rrset, key, privkey, inception, expiration, null);
+}
+
+/**
+ * Generate a DNSSEC signature. key and privateKey must refer to the
+ * same underlying cryptographic key.
+ * @param rrset The data to be signed
+ * @param key The DNSKEY record to use as part of signing
+ * @param privkey The PrivateKey to use when signing
+ * @param inception The time at which the signatures should become valid
+ * @param expiration The time at which the signatures should expire
+ * @param provider The name of the JCA provider. If non-null, it will be
+ * passed to JCA getInstance() methods.
+ * @throws UnsupportedAlgorithmException The algorithm is unknown
+ * @throws MalformedKeyException The key is malformed
+ * @throws DNSSECException Some other error occurred.
+ * @return The generated signature
+ */
+public static RRSIGRecord
+sign(RRset rrset, DNSKEYRecord key, PrivateKey privkey,
+ Date inception, Date expiration, String provider) throws DNSSECException
+{
+ int alg = key.getAlgorithm();
+ checkAlgorithm(privkey, alg);
+
+ RRSIGRecord rrsig = new RRSIGRecord(rrset.getName(), rrset.getDClass(),
+ rrset.getTTL(), rrset.getType(),
+ alg, rrset.getTTL(),
+ expiration, inception,
+ key.getFootprint(),
+ key.getName(), null);
+
+ rrsig.setSignature(sign(privkey, key.getPublicKey(), alg,
+ digestRRset(rrsig, rrset), provider));
+ return rrsig;
+}
+
+static SIGRecord
+signMessage(Message message, SIGRecord previous, KEYRecord key,
+ PrivateKey privkey, Date inception, Date expiration)
+ throws DNSSECException
+{
+ int alg = key.getAlgorithm();
+ checkAlgorithm(privkey, alg);
+
+ SIGRecord sig = new SIGRecord(Name.root, DClass.ANY, 0, 0,
+ alg, 0, expiration, inception,
+ key.getFootprint(),
+ key.getName(), null);
+ DNSOutput out = new DNSOutput();
+ digestSIG(out, sig);
+ if (previous != null)
+ out.writeByteArray(previous.getSignature());
+ message.toWire(out);
+
+ sig.setSignature(sign(privkey, key.getPublicKey(),
+ alg, out.toByteArray(), null));
+ return sig;
+}
+
+static void
+verifyMessage(Message message, byte [] bytes, SIGRecord sig, SIGRecord previous,
+ KEYRecord key) throws DNSSECException
+{
+ if (!matches(sig, key))
+ throw new KeyMismatchException(key, sig);
+
+ Date now = new Date();
+
+ if (now.compareTo(sig.getExpire()) > 0)
+ throw new SignatureExpiredException(sig.getExpire(), now);
+ if (now.compareTo(sig.getTimeSigned()) < 0)
+ throw new SignatureNotYetValidException(sig.getTimeSigned(),
+ now);
+
+ DNSOutput out = new DNSOutput();
+ digestSIG(out, sig);
+ if (previous != null)
+ out.writeByteArray(previous.getSignature());
+
+ Header header = (Header) message.getHeader().clone();
+ header.decCount(Section.ADDITIONAL);
+ out.writeByteArray(header.toWire());
+
+ out.writeByteArray(bytes, Header.LENGTH,
+ message.sig0start - Header.LENGTH);
+
+ verify(key.getPublicKey(), sig.getAlgorithm(),
+ out.toByteArray(), sig.getSignature());
+}
+
+/**
+ * Generate the digest value for a DS key
+ * @param key Which is covered by the DS record
+ * @param digestid The type of digest
+ * @return The digest value as an array of bytes
+ */
+static byte []
+generateDSDigest(DNSKEYRecord key, int digestid)
+{
+ MessageDigest digest;
+ try {
+ switch (digestid) {
+ case DSRecord.Digest.SHA1:
+ digest = MessageDigest.getInstance("sha-1");
+ break;
+ case DSRecord.Digest.SHA256:
+ digest = MessageDigest.getInstance("sha-256");
+ break;
+ case DSRecord.Digest.SHA384:
+ digest = MessageDigest.getInstance("sha-384");
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "unknown DS digest type " + digestid);
+ }
+ }
+ catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException("no message digest support");
+ }
+ digest.update(key.getName().toWire());
+ digest.update(key.rdataToWireCanonical());
+ return digest.digest();
+}
+
+}
diff --git a/src/org/xbill/DNS/DSRecord.java b/src/org/xbill/DNS/DSRecord.java
new file mode 100644
index 0000000..4c1ebce
--- /dev/null
+++ b/src/org/xbill/DNS/DSRecord.java
@@ -0,0 +1,157 @@
+// Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import org.xbill.DNS.utils.*;
+
+/**
+ * DS - contains a Delegation Signer record, which acts as a
+ * placeholder for KEY records in the parent zone.
+ * @see DNSSEC
+ *
+ * @author David Blacka
+ * @author Brian Wellington
+ */
+
+public class DSRecord extends Record {
+
+public static class Digest {
+ private Digest() {}
+
+ /** SHA-1 */
+ public static final int SHA1 = 1;
+
+ /** SHA-256 */
+ public static final int SHA256 = 2;
+
+ /** SHA-384 */
+ public static final int SHA384 = 4;
+}
+
+public static final int SHA1_DIGEST_ID = Digest.SHA1;
+public static final int SHA256_DIGEST_ID = Digest.SHA256;
+public static final int SHA384_DIGEST_ID = Digest.SHA384;
+
+private static final long serialVersionUID = -9001819329700081493L;
+
+private int footprint;
+private int alg;
+private int digestid;
+private byte [] digest;
+
+DSRecord() {}
+
+Record
+getObject() {
+ return new DSRecord();
+}
+
+/**
+ * Creates a DS Record from the given data
+ * @param footprint The original KEY record's footprint (keyid).
+ * @param alg The original key algorithm.
+ * @param digestid The digest id code.
+ * @param digest A hash of the original key.
+ */
+public
+DSRecord(Name name, int dclass, long ttl, int footprint, int alg,
+ int digestid, byte [] digest)
+{
+ super(name, Type.DS, dclass, ttl);
+ this.footprint = checkU16("footprint", footprint);
+ this.alg = checkU8("alg", alg);
+ this.digestid = checkU8("digestid", digestid);
+ this.digest = digest;
+}
+
+/**
+ * Creates a DS Record from the given data
+ * @param digestid The digest id code.
+ * @param key The key to digest
+ */
+public
+DSRecord(Name name, int dclass, long ttl, int digestid, DNSKEYRecord key)
+{
+ this(name, dclass, ttl, key.getFootprint(), key.getAlgorithm(),
+ digestid, DNSSEC.generateDSDigest(key, digestid));
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ footprint = in.readU16();
+ alg = in.readU8();
+ digestid = in.readU8();
+ digest = in.readByteArray();
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ footprint = st.getUInt16();
+ alg = st.getUInt8();
+ digestid = st.getUInt8();
+ digest = st.getHex();
+}
+
+/**
+ * Converts rdata to a String
+ */
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(footprint);
+ sb.append(" ");
+ sb.append(alg);
+ sb.append(" ");
+ sb.append(digestid);
+ if (digest != null) {
+ sb.append(" ");
+ sb.append(base16.toString(digest));
+ }
+
+ return sb.toString();
+}
+
+/**
+ * Returns the key's algorithm.
+ */
+public int
+getAlgorithm() {
+ return alg;
+}
+
+/**
+ * Returns the key's Digest ID.
+ */
+public int
+getDigestID()
+{
+ return digestid;
+}
+
+/**
+ * Returns the binary hash of the key.
+ */
+public byte []
+getDigest() {
+ return digest;
+}
+
+/**
+ * Returns the key's footprint.
+ */
+public int
+getFootprint() {
+ return footprint;
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeU16(footprint);
+ out.writeU8(alg);
+ out.writeU8(digestid);
+ if (digest != null)
+ out.writeByteArray(digest);
+}
+
+}
diff --git a/src/org/xbill/DNS/EDNSOption.java b/src/org/xbill/DNS/EDNSOption.java
new file mode 100644
index 0000000..a65bd19
--- /dev/null
+++ b/src/org/xbill/DNS/EDNSOption.java
@@ -0,0 +1,215 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+package org.xbill.DNS;
+
+import java.io.*;
+import java.util.Arrays;
+
+/**
+ * DNS extension options, as described in RFC 2671. The rdata of an OPT record
+ * is defined as a list of options; this represents a single option.
+ *
+ * @author Brian Wellington
+ * @author Ming Zhou &lt;mizhou@bnivideo.com&gt;, Beaumaris Networks
+ */
+public abstract class EDNSOption {
+
+public static class Code {
+ private Code() {}
+
+ /** Name Server Identifier, RFC 5001 */
+ public final static int NSID = 3;
+
+ /** Client Subnet, defined in draft-vandergaast-edns-client-subnet-00 */
+ public final static int CLIENT_SUBNET = 20730;
+
+ private static Mnemonic codes = new Mnemonic("EDNS Option Codes",
+ Mnemonic.CASE_UPPER);
+
+ static {
+ codes.setMaximum(0xFFFF);
+ codes.setPrefix("CODE");
+ codes.setNumericAllowed(true);
+
+ codes.add(NSID, "NSID");
+ codes.add(CLIENT_SUBNET, "CLIENT_SUBNET");
+ }
+
+ /**
+ * Converts an EDNS Option Code into its textual representation
+ */
+ public static String
+ string(int code) {
+ return codes.getText(code);
+ }
+
+ /**
+ * Converts a textual representation of an EDNS Option Code into its
+ * numeric value.
+ * @param s The textual representation of the option code
+ * @return The option code, or -1 on error.
+ */
+ public static int
+ value(String s) {
+ return codes.getValue(s);
+ }
+}
+
+private final int code;
+
+/**
+ *
+ * Creates an option with the given option code and data.
+ */
+public
+EDNSOption(int code) {
+ this.code = Record.checkU16("code", code);
+}
+
+public String
+toString() {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append("{");
+ sb.append(EDNSOption.Code.string(code));
+ sb.append(": ");
+ sb.append(optionToString());
+ sb.append("}");
+
+ return sb.toString();
+}
+
+/**
+ * Returns the EDNS Option's code.
+ *
+ * @return the option code
+ */
+public int
+getCode() {
+ return code;
+}
+
+/**
+ * Returns the EDNS Option's data, as a byte array.
+ *
+ * @return the option data
+ */
+byte []
+getData() {
+ DNSOutput out = new DNSOutput();
+ optionToWire(out);
+ return out.toByteArray();
+}
+
+/**
+ * Converts the wire format of an EDNS Option (the option data only) into the
+ * type-specific format.
+ * @param in The input Stream.
+ */
+abstract void
+optionFromWire(DNSInput in) throws IOException;
+
+/**
+ * Converts the wire format of an EDNS Option (including code and length) into
+ * the type-specific format.
+ * @param out The input stream.
+ */
+static EDNSOption
+fromWire(DNSInput in) throws IOException {
+ int code, length;
+
+ code = in.readU16();
+ length = in.readU16();
+ if (in.remaining() < length)
+ throw new WireParseException("truncated option");
+ int save = in.saveActive();
+ in.setActive(length);
+ EDNSOption option;
+ switch (code) {
+ case Code.NSID:
+ option = new NSIDOption();
+ break;
+ case Code.CLIENT_SUBNET:
+ option = new ClientSubnetOption();
+ break;
+ default:
+ option = new GenericEDNSOption(code);
+ break;
+ }
+ option.optionFromWire(in);
+ in.restoreActive(save);
+
+ return option;
+}
+
+/**
+ * Converts the wire format of an EDNS Option (including code and length) into
+ * the type-specific format.
+ * @return The option, in wire format.
+ */
+public static EDNSOption
+fromWire(byte [] b) throws IOException {
+ return fromWire(new DNSInput(b));
+}
+
+/**
+ * Converts an EDNS Option (the type-specific option data only) into wire format.
+ * @param out The output stream.
+ */
+abstract void
+optionToWire(DNSOutput out);
+
+/**
+ * Converts an EDNS Option (including code and length) into wire format.
+ * @param out The output stream.
+ */
+void
+toWire(DNSOutput out) {
+ out.writeU16(code);
+ int lengthPosition = out.current();
+ out.writeU16(0); /* until we know better */
+ optionToWire(out);
+ int length = out.current() - lengthPosition - 2;
+ out.writeU16At(length, lengthPosition);
+}
+
+/**
+ * Converts an EDNS Option (including code and length) into wire format.
+ * @return The option, in wire format.
+ */
+public byte []
+toWire() throws IOException {
+ DNSOutput out = new DNSOutput();
+ toWire(out);
+ return out.toByteArray();
+}
+
+/**
+ * Determines if two EDNS Options are identical.
+ * @param arg The option to compare to
+ * @return true if the options are equal, false otherwise.
+ */
+public boolean
+equals(Object arg) {
+ if (arg == null || !(arg instanceof EDNSOption))
+ return false;
+ EDNSOption opt = (EDNSOption) arg;
+ if (code != opt.code)
+ return false;
+ return Arrays.equals(getData(), opt.getData());
+}
+
+/**
+ * Generates a hash code based on the EDNS Option's data.
+ */
+public int
+hashCode() {
+ byte [] array = getData();
+ int hashval = 0;
+ for (int i = 0; i < array.length; i++)
+ hashval += ((hashval << 3) + (array[i] & 0xFF));
+ return hashval;
+}
+
+abstract String optionToString();
+
+}
diff --git a/src/org/xbill/DNS/EmptyRecord.java b/src/org/xbill/DNS/EmptyRecord.java
new file mode 100644
index 0000000..f5e61e8
--- /dev/null
+++ b/src/org/xbill/DNS/EmptyRecord.java
@@ -0,0 +1,42 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+
+/**
+ * A class implementing Records with no data; that is, records used in
+ * the question section of messages and meta-records in dynamic update.
+ *
+ * @author Brian Wellington
+ */
+
+class EmptyRecord extends Record {
+
+private static final long serialVersionUID = 3601852050646429582L;
+
+EmptyRecord() {}
+
+Record
+getObject() {
+ return new EmptyRecord();
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+}
+
+String
+rrToString() {
+ return "";
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+}
+
+}
diff --git a/src/org/xbill/DNS/ExtendedFlags.java b/src/org/xbill/DNS/ExtendedFlags.java
new file mode 100644
index 0000000..8f3bbab
--- /dev/null
+++ b/src/org/xbill/DNS/ExtendedFlags.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * Constants and functions relating to EDNS flags.
+ *
+ * @author Brian Wellington
+ */
+
+public final class ExtendedFlags {
+
+private static Mnemonic extflags = new Mnemonic("EDNS Flag",
+ Mnemonic.CASE_LOWER);
+
+/** dnssec ok */
+public static final int DO = 0x8000;
+
+static {
+ extflags.setMaximum(0xFFFF);
+ extflags.setPrefix("FLAG");
+ extflags.setNumericAllowed(true);
+
+ extflags.add(DO, "do");
+}
+
+private
+ExtendedFlags() {}
+
+/** Converts a numeric extended flag into a String */
+public static String
+string(int i) {
+ return extflags.getText(i);
+}
+
+/**
+ * Converts a textual representation of an extended flag into its numeric
+ * value
+ */
+public static int
+value(String s) {
+ return extflags.getValue(s);
+}
+
+}
diff --git a/src/org/xbill/DNS/ExtendedResolver.java b/src/org/xbill/DNS/ExtendedResolver.java
new file mode 100644
index 0000000..f762b84
--- /dev/null
+++ b/src/org/xbill/DNS/ExtendedResolver.java
@@ -0,0 +1,419 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.util.*;
+import java.io.*;
+import java.net.*;
+
+/**
+ * An implementation of Resolver that can send queries to multiple servers,
+ * sending the queries multiple times if necessary.
+ * @see Resolver
+ *
+ * @author Brian Wellington
+ */
+
+public class ExtendedResolver implements Resolver {
+
+private static class Resolution implements ResolverListener {
+ Resolver [] resolvers;
+ int [] sent;
+ Object [] inprogress;
+ int retries;
+ int outstanding;
+ boolean done;
+ Message query;
+ Message response;
+ Throwable thrown;
+ ResolverListener listener;
+
+ public
+ Resolution(ExtendedResolver eres, Message query) {
+ List l = eres.resolvers;
+ resolvers = (Resolver []) l.toArray (new Resolver[l.size()]);
+ if (eres.loadBalance) {
+ int nresolvers = resolvers.length;
+ /*
+ * Note: this is not synchronized, since the
+ * worst thing that can happen is a random
+ * ordering, which is ok.
+ */
+ int start = eres.lbStart++ % nresolvers;
+ if (eres.lbStart > nresolvers)
+ eres.lbStart %= nresolvers;
+ if (start > 0) {
+ Resolver [] shuffle = new Resolver[nresolvers];
+ for (int i = 0; i < nresolvers; i++) {
+ int pos = (i + start) % nresolvers;
+ shuffle[i] = resolvers[pos];
+ }
+ resolvers = shuffle;
+ }
+ }
+ sent = new int[resolvers.length];
+ inprogress = new Object[resolvers.length];
+ retries = eres.retries;
+ this.query = query;
+ }
+
+ /* Asynchronously sends a message. */
+ public void
+ send(int n) {
+ sent[n]++;
+ outstanding++;
+ try {
+ inprogress[n] = resolvers[n].sendAsync(query, this);
+ }
+ catch (Throwable t) {
+ synchronized (this) {
+ thrown = t;
+ done = true;
+ if (listener == null) {
+ notifyAll();
+ return;
+ }
+ }
+ }
+ }
+
+ /* Start a synchronous resolution */
+ public Message
+ start() throws IOException {
+ try {
+ /*
+ * First, try sending synchronously. If this works,
+ * we're done. Otherwise, we'll get an exception
+ * and continue. It would be easier to call send(0),
+ * but this avoids a thread creation. If and when
+ * SimpleResolver.sendAsync() can be made to not
+ * create a thread, this could be changed.
+ */
+ sent[0]++;
+ outstanding++;
+ inprogress[0] = new Object();
+ return resolvers[0].send(query);
+ }
+ catch (Exception e) {
+ /*
+ * This will either cause more queries to be sent
+ * asynchronously or will set the 'done' flag.
+ */
+ handleException(inprogress[0], e);
+ }
+ /*
+ * Wait for a successful response or for each
+ * subresolver to fail.
+ */
+ synchronized (this) {
+ while (!done) {
+ try {
+ wait();
+ }
+ catch (InterruptedException e) {
+ }
+ }
+ }
+ /* Return the response or throw an exception */
+ if (response != null)
+ return response;
+ else if (thrown instanceof IOException)
+ throw (IOException) thrown;
+ else if (thrown instanceof RuntimeException)
+ throw (RuntimeException) thrown;
+ else if (thrown instanceof Error)
+ throw (Error) thrown;
+ else
+ throw new IllegalStateException
+ ("ExtendedResolver failure");
+ }
+
+ /* Start an asynchronous resolution */
+ public void
+ startAsync(ResolverListener listener) {
+ this.listener = listener;
+ send(0);
+ }
+
+ /*
+ * Receive a response. If the resolution hasn't been completed,
+ * either wake up the blocking thread or call the callback.
+ */
+ public void
+ receiveMessage(Object id, Message m) {
+ if (Options.check("verbose"))
+ System.err.println("ExtendedResolver: " +
+ "received message");
+ synchronized (this) {
+ if (done)
+ return;
+ response = m;
+ done = true;
+ if (listener == null) {
+ notifyAll();
+ return;
+ }
+ }
+ listener.receiveMessage(this, response);
+ }
+
+ /*
+ * Receive an exception. If the resolution has been completed,
+ * do nothing. Otherwise make progress.
+ */
+ public void
+ handleException(Object id, Exception e) {
+ if (Options.check("verbose"))
+ System.err.println("ExtendedResolver: got " + e);
+ synchronized (this) {
+ outstanding--;
+ if (done)
+ return;
+ int n;
+ for (n = 0; n < inprogress.length; n++)
+ if (inprogress[n] == id)
+ break;
+ /* If we don't know what this is, do nothing. */
+ if (n == inprogress.length)
+ return;
+ boolean startnext = false;
+ /*
+ * If this is the first response from server n,
+ * we should start sending queries to server n + 1.
+ */
+ if (sent[n] == 1 && n < resolvers.length - 1)
+ startnext = true;
+ if (e instanceof InterruptedIOException) {
+ /* Got a timeout; resend */
+ if (sent[n] < retries)
+ send(n);
+ if (thrown == null)
+ thrown = e;
+ } else if (e instanceof SocketException) {
+ /*
+ * Problem with the socket; don't resend
+ * on it
+ */
+ if (thrown == null ||
+ thrown instanceof InterruptedIOException)
+ thrown = e;
+ } else {
+ /*
+ * Problem with the response; don't resend
+ * on the same socket.
+ */
+ thrown = e;
+ }
+ if (done)
+ return;
+ if (startnext)
+ send(n + 1);
+ if (done)
+ return;
+ if (outstanding == 0) {
+ /*
+ * If we're done and this is synchronous,
+ * wake up the blocking thread.
+ */
+ done = true;
+ if (listener == null) {
+ notifyAll();
+ return;
+ }
+ }
+ if (!done)
+ return;
+ }
+ /* If we're done and this is asynchronous, call the callback. */
+ if (!(thrown instanceof Exception))
+ thrown = new RuntimeException(thrown.getMessage());
+ listener.handleException(this, (Exception) thrown);
+ }
+}
+
+private static final int quantum = 5;
+
+private List resolvers;
+private boolean loadBalance = false;
+private int lbStart = 0;
+private int retries = 3;
+
+private void
+init() {
+ resolvers = new ArrayList();
+}
+
+/**
+ * Creates a new Extended Resolver. The default ResolverConfig is used to
+ * determine the servers for which SimpleResolver contexts should be
+ * initialized.
+ * @see SimpleResolver
+ * @see ResolverConfig
+ * @exception UnknownHostException Failure occured initializing SimpleResolvers
+ */
+public
+ExtendedResolver() throws UnknownHostException {
+ init();
+ String [] servers = ResolverConfig.getCurrentConfig().servers();
+ if (servers != null) {
+ for (int i = 0; i < servers.length; i++) {
+ Resolver r = new SimpleResolver(servers[i]);
+ r.setTimeout(quantum);
+ resolvers.add(r);
+ }
+ }
+ else
+ resolvers.add(new SimpleResolver());
+}
+
+/**
+ * Creates a new Extended Resolver
+ * @param servers An array of server names for which SimpleResolver
+ * contexts should be initialized.
+ * @see SimpleResolver
+ * @exception UnknownHostException Failure occured initializing SimpleResolvers
+ */
+public
+ExtendedResolver(String [] servers) throws UnknownHostException {
+ init();
+ for (int i = 0; i < servers.length; i++) {
+ Resolver r = new SimpleResolver(servers[i]);
+ r.setTimeout(quantum);
+ resolvers.add(r);
+ }
+}
+
+/**
+ * Creates a new Extended Resolver
+ * @param res An array of pre-initialized Resolvers is provided.
+ * @see SimpleResolver
+ * @exception UnknownHostException Failure occured initializing SimpleResolvers
+ */
+public
+ExtendedResolver(Resolver [] res) throws UnknownHostException {
+ init();
+ for (int i = 0; i < res.length; i++)
+ resolvers.add(res[i]);
+}
+
+public void
+setPort(int port) {
+ for (int i = 0; i < resolvers.size(); i++)
+ ((Resolver)resolvers.get(i)).setPort(port);
+}
+
+public void
+setTCP(boolean flag) {
+ for (int i = 0; i < resolvers.size(); i++)
+ ((Resolver)resolvers.get(i)).setTCP(flag);
+}
+
+public void
+setIgnoreTruncation(boolean flag) {
+ for (int i = 0; i < resolvers.size(); i++)
+ ((Resolver)resolvers.get(i)).setIgnoreTruncation(flag);
+}
+
+public void
+setEDNS(int level) {
+ for (int i = 0; i < resolvers.size(); i++)
+ ((Resolver)resolvers.get(i)).setEDNS(level);
+}
+
+public void
+setEDNS(int level, int payloadSize, int flags, List options) {
+ for (int i = 0; i < resolvers.size(); i++)
+ ((Resolver)resolvers.get(i)).setEDNS(level, payloadSize,
+ flags, options);
+}
+
+public void
+setTSIGKey(TSIG key) {
+ for (int i = 0; i < resolvers.size(); i++)
+ ((Resolver)resolvers.get(i)).setTSIGKey(key);
+}
+
+public void
+setTimeout(int secs, int msecs) {
+ for (int i = 0; i < resolvers.size(); i++)
+ ((Resolver)resolvers.get(i)).setTimeout(secs, msecs);
+}
+
+public void
+setTimeout(int secs) {
+ setTimeout(secs, 0);
+}
+
+/**
+ * Sends a message and waits for a response. Multiple servers are queried,
+ * and queries are sent multiple times until either a successful response
+ * is received, or it is clear that there is no successful response.
+ * @param query The query to send.
+ * @return The response.
+ * @throws IOException An error occurred while sending or receiving.
+ */
+public Message
+send(Message query) throws IOException {
+ Resolution res = new Resolution(this, query);
+ return res.start();
+}
+
+/**
+ * Asynchronously sends a message to multiple servers, potentially multiple
+ * times, registering a listener to receive a callback on success or exception.
+ * Multiple asynchronous lookups can be performed in parallel. Since the
+ * callback may be invoked before the function returns, external
+ * synchronization is necessary.
+ * @param query The query to send
+ * @param listener The object containing the callbacks.
+ * @return An identifier, which is also a parameter in the callback
+ */
+public Object
+sendAsync(final Message query, final ResolverListener listener) {
+ Resolution res = new Resolution(this, query);
+ res.startAsync(listener);
+ return res;
+}
+
+/** Returns the nth resolver used by this ExtendedResolver */
+public Resolver
+getResolver(int n) {
+ if (n < resolvers.size())
+ return (Resolver)resolvers.get(n);
+ return null;
+}
+
+/** Returns all resolvers used by this ExtendedResolver */
+public Resolver []
+getResolvers() {
+ return (Resolver []) resolvers.toArray(new Resolver[resolvers.size()]);
+}
+
+/** Adds a new resolver to be used by this ExtendedResolver */
+public void
+addResolver(Resolver r) {
+ resolvers.add(r);
+}
+
+/** Deletes a resolver used by this ExtendedResolver */
+public void
+deleteResolver(Resolver r) {
+ resolvers.remove(r);
+}
+
+/** Sets whether the servers should be load balanced.
+ * @param flag If true, servers will be tried in round-robin order. If false,
+ * servers will always be queried in the same order.
+ */
+public void
+setLoadBalance(boolean flag) {
+ loadBalance = flag;
+}
+
+/** Sets the number of retries sent to each server per query */
+public void
+setRetries(int retries) {
+ this.retries = retries;
+}
+
+}
diff --git a/src/org/xbill/DNS/Flags.java b/src/org/xbill/DNS/Flags.java
new file mode 100644
index 0000000..964ce23
--- /dev/null
+++ b/src/org/xbill/DNS/Flags.java
@@ -0,0 +1,81 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * Constants and functions relating to flags in the DNS header.
+ *
+ * @author Brian Wellington
+ */
+
+public final class Flags {
+
+private static Mnemonic flags = new Mnemonic("DNS Header Flag",
+ Mnemonic.CASE_LOWER);
+
+/** query/response */
+public static final byte QR = 0;
+
+/** authoritative answer */
+public static final byte AA = 5;
+
+/** truncated */
+public static final byte TC = 6;
+
+/** recursion desired */
+public static final byte RD = 7;
+
+/** recursion available */
+public static final byte RA = 8;
+
+/** authenticated data */
+public static final byte AD = 10;
+
+/** (security) checking disabled */
+public static final byte CD = 11;
+
+/** dnssec ok (extended) */
+public static final int DO = ExtendedFlags.DO;
+
+static {
+ flags.setMaximum(0xF);
+ flags.setPrefix("FLAG");
+ flags.setNumericAllowed(true);
+
+ flags.add(QR, "qr");
+ flags.add(AA, "aa");
+ flags.add(TC, "tc");
+ flags.add(RD, "rd");
+ flags.add(RA, "ra");
+ flags.add(AD, "ad");
+ flags.add(CD, "cd");
+}
+
+private
+Flags() {}
+
+/** Converts a numeric Flag into a String */
+public static String
+string(int i) {
+ return flags.getText(i);
+}
+
+/** Converts a String representation of an Flag into its numeric value */
+public static int
+value(String s) {
+ return flags.getValue(s);
+}
+
+/**
+ * Indicates if a bit in the flags field is a flag or not. If it's part of
+ * the rcode or opcode, it's not.
+ */
+public static boolean
+isFlag(int index) {
+ flags.check(index);
+ if ((index >= 1 && index <= 4) || (index >= 12))
+ return false;
+ return true;
+}
+
+}
diff --git a/src/org/xbill/DNS/FormattedTime.java b/src/org/xbill/DNS/FormattedTime.java
new file mode 100644
index 0000000..c76a846
--- /dev/null
+++ b/src/org/xbill/DNS/FormattedTime.java
@@ -0,0 +1,79 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * Routines for converting time values to and from YYYYMMDDHHMMSS format.
+ *
+ * @author Brian Wellington
+ */
+
+import java.util.*;
+import java.text.*;
+
+final class FormattedTime {
+
+private static NumberFormat w2, w4;
+
+static {
+ w2 = new DecimalFormat();
+ w2.setMinimumIntegerDigits(2);
+
+ w4 = new DecimalFormat();
+ w4.setMinimumIntegerDigits(4);
+ w4.setGroupingUsed(false);
+}
+
+private
+FormattedTime() {}
+
+/**
+ * Converts a Date into a formatted string.
+ * @param date The Date to convert.
+ * @return The formatted string.
+ */
+public static String
+format(Date date) {
+ Calendar c = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
+ StringBuffer sb = new StringBuffer();
+
+ c.setTime(date);
+ sb.append(w4.format(c.get(Calendar.YEAR)));
+ sb.append(w2.format(c.get(Calendar.MONTH)+1));
+ sb.append(w2.format(c.get(Calendar.DAY_OF_MONTH)));
+ sb.append(w2.format(c.get(Calendar.HOUR_OF_DAY)));
+ sb.append(w2.format(c.get(Calendar.MINUTE)));
+ sb.append(w2.format(c.get(Calendar.SECOND)));
+ return sb.toString();
+}
+
+/**
+ * Parses a formatted time string into a Date.
+ * @param s The string, in the form YYYYMMDDHHMMSS.
+ * @return The Date object.
+ * @throws TextParseExcetption The string was invalid.
+ */
+public static Date
+parse(String s) throws TextParseException {
+ if (s.length() != 14) {
+ throw new TextParseException("Invalid time encoding: " + s);
+ }
+
+ Calendar c = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
+ c.clear();
+ try {
+ int year = Integer.parseInt(s.substring(0, 4));
+ int month = Integer.parseInt(s.substring(4, 6)) - 1;
+ int date = Integer.parseInt(s.substring(6, 8));
+ int hour = Integer.parseInt(s.substring(8, 10));
+ int minute = Integer.parseInt(s.substring(10, 12));
+ int second = Integer.parseInt(s.substring(12, 14));
+ c.set(year, month, date, hour, minute, second);
+ }
+ catch (NumberFormatException e) {
+ throw new TextParseException("Invalid time encoding: " + s);
+ }
+ return c.getTime();
+}
+
+}
diff --git a/src/org/xbill/DNS/GPOSRecord.java b/src/org/xbill/DNS/GPOSRecord.java
new file mode 100644
index 0000000..688d567
--- /dev/null
+++ b/src/org/xbill/DNS/GPOSRecord.java
@@ -0,0 +1,178 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+
+/**
+ * Geographical Location - describes the physical location of a host.
+ *
+ * @author Brian Wellington
+ */
+
+public class GPOSRecord extends Record {
+
+private static final long serialVersionUID = -6349714958085750705L;
+
+private byte [] latitude, longitude, altitude;
+
+GPOSRecord() {}
+
+Record
+getObject() {
+ return new GPOSRecord();
+}
+
+private void
+validate(double longitude, double latitude) throws IllegalArgumentException
+{
+ if (longitude < -90.0 || longitude > 90.0) {
+ throw new IllegalArgumentException("illegal longitude " +
+ longitude);
+ }
+ if (latitude < -180.0 || latitude > 180.0) {
+ throw new IllegalArgumentException("illegal latitude " +
+ latitude);
+ }
+}
+
+/**
+ * Creates an GPOS Record from the given data
+ * @param longitude The longitude component of the location.
+ * @param latitude The latitude component of the location.
+ * @param altitude The altitude component of the location (in meters above sea
+ * level).
+*/
+public
+GPOSRecord(Name name, int dclass, long ttl, double longitude, double latitude,
+ double altitude)
+{
+ super(name, Type.GPOS, dclass, ttl);
+ validate(longitude, latitude);
+ this.longitude = Double.toString(longitude).getBytes();
+ this.latitude = Double.toString(latitude).getBytes();
+ this.altitude = Double.toString(altitude).getBytes();
+}
+
+/**
+ * Creates an GPOS Record from the given data
+ * @param longitude The longitude component of the location.
+ * @param latitude The latitude component of the location.
+ * @param altitude The altitude component of the location (in meters above sea
+ * level).
+*/
+public
+GPOSRecord(Name name, int dclass, long ttl, String longitude, String latitude,
+ String altitude)
+{
+ super(name, Type.GPOS, dclass, ttl);
+ try {
+ this.longitude = byteArrayFromString(longitude);
+ this.latitude = byteArrayFromString(latitude);
+ validate(getLongitude(), getLatitude());
+ this.altitude = byteArrayFromString(altitude);
+ }
+ catch (TextParseException e) {
+ throw new IllegalArgumentException(e.getMessage());
+ }
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ longitude = in.readCountedString();
+ latitude = in.readCountedString();
+ altitude = in.readCountedString();
+ try {
+ validate(getLongitude(), getLatitude());
+ }
+ catch(IllegalArgumentException e) {
+ throw new WireParseException(e.getMessage());
+ }
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ try {
+ longitude = byteArrayFromString(st.getString());
+ latitude = byteArrayFromString(st.getString());
+ altitude = byteArrayFromString(st.getString());
+ }
+ catch (TextParseException e) {
+ throw st.exception(e.getMessage());
+ }
+ try {
+ validate(getLongitude(), getLatitude());
+ }
+ catch(IllegalArgumentException e) {
+ throw new WireParseException(e.getMessage());
+ }
+}
+
+/** Convert to a String */
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(byteArrayToString(longitude, true));
+ sb.append(" ");
+ sb.append(byteArrayToString(latitude, true));
+ sb.append(" ");
+ sb.append(byteArrayToString(altitude, true));
+ return sb.toString();
+}
+
+/** Returns the longitude as a string */
+public String
+getLongitudeString() {
+ return byteArrayToString(longitude, false);
+}
+
+/**
+ * Returns the longitude as a double
+ * @throws NumberFormatException The string does not contain a valid numeric
+ * value.
+ */
+public double
+getLongitude() {
+ return Double.parseDouble(getLongitudeString());
+}
+
+/** Returns the latitude as a string */
+public String
+getLatitudeString() {
+ return byteArrayToString(latitude, false);
+}
+
+/**
+ * Returns the latitude as a double
+ * @throws NumberFormatException The string does not contain a valid numeric
+ * value.
+ */
+public double
+getLatitude() {
+ return Double.parseDouble(getLatitudeString());
+}
+
+/** Returns the altitude as a string */
+public String
+getAltitudeString() {
+ return byteArrayToString(altitude, false);
+}
+
+/**
+ * Returns the altitude as a double
+ * @throws NumberFormatException The string does not contain a valid numeric
+ * value.
+ */
+public double
+getAltitude() {
+ return Double.parseDouble(getAltitudeString());
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeCountedString(longitude);
+ out.writeCountedString(latitude);
+ out.writeCountedString(altitude);
+}
+
+}
diff --git a/src/org/xbill/DNS/Generator.java b/src/org/xbill/DNS/Generator.java
new file mode 100644
index 0000000..a08d343
--- /dev/null
+++ b/src/org/xbill/DNS/Generator.java
@@ -0,0 +1,264 @@
+// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * A representation of a $GENERATE statement in a master file.
+ *
+ * @author Brian Wellington
+ */
+
+public class Generator {
+
+/** The start of the range. */
+public long start;
+
+/** The end of the range. */
+public long end;
+
+/** The step value of the range. */
+public long step;
+
+/** The pattern to use for generating record names. */
+public final String namePattern;
+
+/** The type of the generated records. */
+public final int type;
+
+/** The class of the generated records. */
+public final int dclass;
+
+/** The ttl of the generated records. */
+public final long ttl;
+
+/** The pattern to use for generating record data. */
+public final String rdataPattern;
+
+/** The origin to append to relative names. */
+public final Name origin;
+
+private long current;
+
+/**
+ * Indicates whether generation is supported for this type.
+ * @throws InvalidTypeException The type is out of range.
+ */
+public static boolean
+supportedType(int type) {
+ Type.check(type);
+ return (type == Type.PTR || type == Type.CNAME || type == Type.DNAME ||
+ type == Type.A || type == Type.AAAA || type == Type.NS);
+}
+
+/**
+ * Creates a specification for generating records, as a $GENERATE
+ * statement in a master file.
+ * @param start The start of the range.
+ * @param end The end of the range.
+ * @param step The step value of the range.
+ * @param namePattern The pattern to use for generating record names.
+ * @param type The type of the generated records. The supported types are
+ * PTR, CNAME, DNAME, A, AAAA, and NS.
+ * @param dclass The class of the generated records.
+ * @param ttl The ttl of the generated records.
+ * @param rdataPattern The pattern to use for generating record data.
+ * @param origin The origin to append to relative names.
+ * @throws IllegalArgumentException The range is invalid.
+ * @throws IllegalArgumentException The type does not support generation.
+ * @throws IllegalArgumentException The dclass is not a valid class.
+ */
+public
+Generator(long start, long end, long step, String namePattern,
+ int type, int dclass, long ttl, String rdataPattern, Name origin)
+{
+ if (start < 0 || end < 0 || start > end || step <= 0)
+ throw new IllegalArgumentException
+ ("invalid range specification");
+ if (!supportedType(type))
+ throw new IllegalArgumentException("unsupported type");
+ DClass.check(dclass);
+
+ this.start = start;
+ this.end = end;
+ this.step = step;
+ this.namePattern = namePattern;
+ this.type = type;
+ this.dclass = dclass;
+ this.ttl = ttl;
+ this.rdataPattern = rdataPattern;
+ this.origin = origin;
+ this.current = start;
+}
+
+private String
+substitute(String spec, long n) throws IOException {
+ boolean escaped = false;
+ byte [] str = spec.getBytes();
+ StringBuffer sb = new StringBuffer();
+
+ for (int i = 0; i < str.length; i++) {
+ char c = (char)(str[i] & 0xFF);
+ if (escaped) {
+ sb.append(c);
+ escaped = false;
+ } else if (c == '\\') {
+ if (i + 1 == str.length)
+ throw new TextParseException
+ ("invalid escape character");
+ escaped = true;
+ } else if (c == '$') {
+ boolean negative = false;
+ long offset = 0;
+ long width = 0;
+ long base = 10;
+ boolean wantUpperCase = false;
+ if (i + 1 < str.length && str[i + 1] == '$') {
+ // '$$' == literal '$' for backwards
+ // compatibility with old versions of BIND.
+ c = (char)(str[++i] & 0xFF);
+ sb.append(c);
+ continue;
+ } else if (i + 1 < str.length && str[i + 1] == '{') {
+ // It's a substitution with modifiers.
+ i++;
+ if (i + 1 < str.length && str[i + 1] == '-') {
+ negative = true;
+ i++;
+ }
+ while (i + 1 < str.length) {
+ c = (char)(str[++i] & 0xFF);
+ if (c == ',' || c == '}')
+ break;
+ if (c < '0' || c > '9')
+ throw new TextParseException(
+ "invalid offset");
+ c -= '0';
+ offset *= 10;
+ offset += c;
+ }
+ if (negative)
+ offset = -offset;
+
+ if (c == ',') {
+ while (i + 1 < str.length) {
+ c = (char)(str[++i] & 0xFF);
+ if (c == ',' || c == '}')
+ break;
+ if (c < '0' || c > '9')
+ throw new
+ TextParseException(
+ "invalid width");
+ c -= '0';
+ width *= 10;
+ width += c;
+ }
+ }
+
+ if (c == ',') {
+ if (i + 1 == str.length)
+ throw new TextParseException(
+ "invalid base");
+ c = (char)(str[++i] & 0xFF);
+ if (c == 'o')
+ base = 8;
+ else if (c == 'x')
+ base = 16;
+ else if (c == 'X') {
+ base = 16;
+ wantUpperCase = true;
+ }
+ else if (c != 'd')
+ throw new TextParseException(
+ "invalid base");
+ }
+
+ if (i + 1 == str.length || str[i + 1] != '}')
+ throw new TextParseException
+ ("invalid modifiers");
+ i++;
+ }
+ long v = n + offset;
+ if (v < 0)
+ throw new TextParseException
+ ("invalid offset expansion");
+ String number;
+ if (base == 8)
+ number = Long.toOctalString(v);
+ else if (base == 16)
+ number = Long.toHexString(v);
+ else
+ number = Long.toString(v);
+ if (wantUpperCase)
+ number = number.toUpperCase();
+ if (width != 0 && width > number.length()) {
+ int zeros = (int)width - number.length();
+ while (zeros-- > 0)
+ sb.append('0');
+ }
+ sb.append(number);
+ } else {
+ sb.append(c);
+ }
+ }
+ return sb.toString();
+}
+
+/**
+ * Constructs and returns the next record in the expansion.
+ * @throws IOException The name or rdata was invalid after substitutions were
+ * performed.
+ */
+public Record
+nextRecord() throws IOException {
+ if (current > end)
+ return null;
+ String namestr = substitute(namePattern, current);
+ Name name = Name.fromString(namestr, origin);
+ String rdata = substitute(rdataPattern, current);
+ current += step;
+ return Record.fromString(name, type, dclass, ttl, rdata, origin);
+}
+
+/**
+ * Constructs and returns all records in the expansion.
+ * @throws IOException The name or rdata of a record was invalid after
+ * substitutions were performed.
+ */
+public Record []
+expand() throws IOException {
+ List list = new ArrayList();
+ for (long i = start; i < end; i += step) {
+ String namestr = substitute(namePattern, current);
+ Name name = Name.fromString(namestr, origin);
+ String rdata = substitute(rdataPattern, current);
+ list.add(Record.fromString(name, type, dclass, ttl,
+ rdata, origin));
+ }
+ return (Record []) list.toArray(new Record[list.size()]);
+}
+
+/**
+ * Converts the generate specification to a string containing the corresponding
+ * $GENERATE statement.
+ */
+public String
+toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("$GENERATE ");
+ sb.append(start + "-" + end);
+ if (step > 1)
+ sb.append("/" + step);
+ sb.append(" ");
+ sb.append(namePattern + " ");
+ sb.append(ttl + " ");
+ if (dclass != DClass.IN || !Options.check("noPrintIN"))
+ sb.append(DClass.string(dclass) + " ");
+ sb.append(Type.string(type) + " ");
+ sb.append(rdataPattern + " ");
+ return sb.toString();
+}
+
+}
diff --git a/src/org/xbill/DNS/GenericEDNSOption.java b/src/org/xbill/DNS/GenericEDNSOption.java
new file mode 100644
index 0000000..7c8b51b
--- /dev/null
+++ b/src/org/xbill/DNS/GenericEDNSOption.java
@@ -0,0 +1,47 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+package org.xbill.DNS;
+
+import java.io.*;
+
+import org.xbill.DNS.utils.base16;
+
+/**
+ * An EDNSOption with no internal structure.
+ *
+ * @author Ming Zhou &lt;mizhou@bnivideo.com&gt;, Beaumaris Networks
+ * @author Brian Wellington
+ */
+public class GenericEDNSOption extends EDNSOption {
+
+private byte [] data;
+
+GenericEDNSOption(int code) {
+ super(code);
+}
+
+/**
+ * Construct a generic EDNS option.
+ * @param data The contents of the option.
+ */
+public
+GenericEDNSOption(int code, byte [] data) {
+ super(code);
+ this.data = Record.checkByteArrayLength("option data", data, 0xFFFF);
+}
+
+void
+optionFromWire(DNSInput in) throws IOException {
+ data = in.readByteArray();
+}
+
+void
+optionToWire(DNSOutput out) {
+ out.writeByteArray(data);
+}
+
+String
+optionToString() {
+ return "<" + base16.toString(data) + ">";
+}
+
+}
diff --git a/src/org/xbill/DNS/HINFORecord.java b/src/org/xbill/DNS/HINFORecord.java
new file mode 100644
index 0000000..18fed32
--- /dev/null
+++ b/src/org/xbill/DNS/HINFORecord.java
@@ -0,0 +1,95 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+
+/**
+ * Host Information - describes the CPU and OS of a host
+ *
+ * @author Brian Wellington
+ */
+
+public class HINFORecord extends Record {
+
+private static final long serialVersionUID = -4732870630947452112L;
+
+private byte [] cpu, os;
+
+HINFORecord() {}
+
+Record
+getObject() {
+ return new HINFORecord();
+}
+
+/**
+ * Creates an HINFO Record from the given data
+ * @param cpu A string describing the host's CPU
+ * @param os A string describing the host's OS
+ * @throws IllegalArgumentException One of the strings has invalid escapes
+ */
+public
+HINFORecord(Name name, int dclass, long ttl, String cpu, String os) {
+ super(name, Type.HINFO, dclass, ttl);
+ try {
+ this.cpu = byteArrayFromString(cpu);
+ this.os = byteArrayFromString(os);
+ }
+ catch (TextParseException e) {
+ throw new IllegalArgumentException(e.getMessage());
+ }
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ cpu = in.readCountedString();
+ os = in.readCountedString();
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ try {
+ cpu = byteArrayFromString(st.getString());
+ os = byteArrayFromString(st.getString());
+ }
+ catch (TextParseException e) {
+ throw st.exception(e.getMessage());
+ }
+}
+
+/**
+ * Returns the host's CPU
+ */
+public String
+getCPU() {
+ return byteArrayToString(cpu, false);
+}
+
+/**
+ * Returns the host's OS
+ */
+public String
+getOS() {
+ return byteArrayToString(os, false);
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeCountedString(cpu);
+ out.writeCountedString(os);
+}
+
+/**
+ * Converts to a string
+ */
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(byteArrayToString(cpu, true));
+ sb.append(" ");
+ sb.append(byteArrayToString(os, true));
+ return sb.toString();
+}
+
+}
diff --git a/src/org/xbill/DNS/Header.java b/src/org/xbill/DNS/Header.java
new file mode 100644
index 0000000..2a44d08
--- /dev/null
+++ b/src/org/xbill/DNS/Header.java
@@ -0,0 +1,286 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * A DNS message header
+ * @see Message
+ *
+ * @author Brian Wellington
+ */
+
+public class Header implements Cloneable {
+
+private int id;
+private int flags;
+private int [] counts;
+
+private static Random random = new Random();
+
+/** The length of a DNS Header in wire format. */
+public static final int LENGTH = 12;
+
+private void
+init() {
+ counts = new int[4];
+ flags = 0;
+ id = -1;
+}
+
+/**
+ * Create a new empty header.
+ * @param id The message id
+ */
+public
+Header(int id) {
+ init();
+ setID(id);
+}
+
+/**
+ * Create a new empty header with a random message id
+ */
+public
+Header() {
+ init();
+}
+
+/**
+ * Parses a Header from a stream containing DNS wire format.
+ */
+Header(DNSInput in) throws IOException {
+ this(in.readU16());
+ flags = in.readU16();
+ for (int i = 0; i < counts.length; i++)
+ counts[i] = in.readU16();
+}
+
+/**
+ * Creates a new Header from its DNS wire format representation
+ * @param b A byte array containing the DNS Header.
+ */
+public
+Header(byte [] b) throws IOException {
+ this(new DNSInput(b));
+}
+
+void
+toWire(DNSOutput out) {
+ out.writeU16(getID());
+ out.writeU16(flags);
+ for (int i = 0; i < counts.length; i++)
+ out.writeU16(counts[i]);
+}
+
+public byte []
+toWire() {
+ DNSOutput out = new DNSOutput();
+ toWire(out);
+ return out.toByteArray();
+}
+
+static private boolean
+validFlag(int bit) {
+ return (bit >= 0 && bit <= 0xF && Flags.isFlag(bit));
+}
+
+static private void
+checkFlag(int bit) {
+ if (!validFlag(bit))
+ throw new IllegalArgumentException("invalid flag bit " + bit);
+}
+
+/**
+ * Sets a flag to the supplied value
+ * @see Flags
+ */
+public void
+setFlag(int bit) {
+ checkFlag(bit);
+ // bits are indexed from left to right
+ flags |= (1 << (15 - bit));
+}
+
+/**
+ * Sets a flag to the supplied value
+ * @see Flags
+ */
+public void
+unsetFlag(int bit) {
+ checkFlag(bit);
+ // bits are indexed from left to right
+ flags &= ~(1 << (15 - bit));
+}
+
+/**
+ * Retrieves a flag
+ * @see Flags
+ */
+public boolean
+getFlag(int bit) {
+ checkFlag(bit);
+ // bits are indexed from left to right
+ return (flags & (1 << (15 - bit))) != 0;
+}
+
+boolean []
+getFlags() {
+ boolean [] array = new boolean[16];
+ for (int i = 0; i < array.length; i++)
+ if (validFlag(i))
+ array[i] = getFlag(i);
+ return array;
+}
+
+/**
+ * Retrieves the message ID
+ */
+public int
+getID() {
+ if (id >= 0)
+ return id;
+ synchronized (this) {
+ if (id < 0)
+ id = random.nextInt(0xffff);
+ return id;
+ }
+}
+
+/**
+ * Sets the message ID
+ */
+public void
+setID(int id) {
+ if (id < 0 || id > 0xffff)
+ throw new IllegalArgumentException("DNS message ID " + id +
+ " is out of range");
+ this.id = id;
+}
+
+/**
+ * Sets the message's rcode
+ * @see Rcode
+ */
+public void
+setRcode(int value) {
+ if (value < 0 || value > 0xF)
+ throw new IllegalArgumentException("DNS Rcode " + value +
+ " is out of range");
+ flags &= ~0xF;
+ flags |= value;
+}
+
+/**
+ * Retrieves the mesasge's rcode
+ * @see Rcode
+ */
+public int
+getRcode() {
+ return flags & 0xF;
+}
+
+/**
+ * Sets the message's opcode
+ * @see Opcode
+ */
+public void
+setOpcode(int value) {
+ if (value < 0 || value > 0xF)
+ throw new IllegalArgumentException("DNS Opcode " + value +
+ "is out of range");
+ flags &= 0x87FF;
+ flags |= (value << 11);
+}
+
+/**
+ * Retrieves the mesasge's opcode
+ * @see Opcode
+ */
+public int
+getOpcode() {
+ return (flags >> 11) & 0xF;
+}
+
+void
+setCount(int field, int value) {
+ if (value < 0 || value > 0xFFFF)
+ throw new IllegalArgumentException("DNS section count " +
+ value + " is out of range");
+ counts[field] = value;
+}
+
+void
+incCount(int field) {
+ if (counts[field] == 0xFFFF)
+ throw new IllegalStateException("DNS section count cannot " +
+ "be incremented");
+ counts[field]++;
+}
+
+void
+decCount(int field) {
+ if (counts[field] == 0)
+ throw new IllegalStateException("DNS section count cannot " +
+ "be decremented");
+ counts[field]--;
+}
+
+/**
+ * Retrieves the record count for the given section
+ * @see Section
+ */
+public int
+getCount(int field) {
+ return counts[field];
+}
+
+/** Converts the header's flags into a String */
+public String
+printFlags() {
+ StringBuffer sb = new StringBuffer();
+
+ for (int i = 0; i < 16; i++)
+ if (validFlag(i) && getFlag(i)) {
+ sb.append(Flags.string(i));
+ sb.append(" ");
+ }
+ return sb.toString();
+}
+
+String
+toStringWithRcode(int newrcode) {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append(";; ->>HEADER<<- ");
+ sb.append("opcode: " + Opcode.string(getOpcode()));
+ sb.append(", status: " + Rcode.string(newrcode));
+ sb.append(", id: " + getID());
+ sb.append("\n");
+
+ sb.append(";; flags: " + printFlags());
+ sb.append("; ");
+ for (int i = 0; i < 4; i++)
+ sb.append(Section.string(i) + ": " + getCount(i) + " ");
+ return sb.toString();
+}
+
+/** Converts the header into a String */
+public String
+toString() {
+ return toStringWithRcode(getRcode());
+}
+
+/* Creates a new Header identical to the current one */
+public Object
+clone() {
+ Header h = new Header();
+ h.id = id;
+ h.flags = flags;
+ System.arraycopy(counts, 0, h.counts, 0, counts.length);
+ return h;
+}
+
+}
diff --git a/src/org/xbill/DNS/IPSECKEYRecord.java b/src/org/xbill/DNS/IPSECKEYRecord.java
new file mode 100644
index 0000000..7eb2956
--- /dev/null
+++ b/src/org/xbill/DNS/IPSECKEYRecord.java
@@ -0,0 +1,231 @@
+// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.net.*;
+import org.xbill.DNS.utils.*;
+
+/**
+ * IPsec Keying Material (RFC 4025)
+ *
+ * @author Brian Wellington
+ */
+
+public class IPSECKEYRecord extends Record {
+
+private static final long serialVersionUID = 3050449702765909687L;
+
+public static class Algorithm {
+ private Algorithm() {}
+
+ public static final int DSA = 1;
+ public static final int RSA = 2;
+}
+
+public static class Gateway {
+ private Gateway() {}
+
+ public static final int None = 0;
+ public static final int IPv4 = 1;
+ public static final int IPv6 = 2;
+ public static final int Name = 3;
+}
+
+private int precedence;
+private int gatewayType;
+private int algorithmType;
+private Object gateway;
+private byte [] key;
+
+IPSECKEYRecord() {}
+
+Record
+getObject() {
+ return new IPSECKEYRecord();
+}
+
+/**
+ * Creates an IPSECKEY Record from the given data.
+ * @param precedence The record's precedence.
+ * @param gatewayType The record's gateway type.
+ * @param algorithmType The record's algorithm type.
+ * @param gateway The record's gateway.
+ * @param key The record's public key.
+ */
+public
+IPSECKEYRecord(Name name, int dclass, long ttl, int precedence,
+ int gatewayType, int algorithmType, Object gateway,
+ byte [] key)
+{
+ super(name, Type.IPSECKEY, dclass, ttl);
+ this.precedence = checkU8("precedence", precedence);
+ this.gatewayType = checkU8("gatewayType", gatewayType);
+ this.algorithmType = checkU8("algorithmType", algorithmType);
+ switch (gatewayType) {
+ case Gateway.None:
+ this.gateway = null;
+ break;
+ case Gateway.IPv4:
+ if (!(gateway instanceof InetAddress))
+ throw new IllegalArgumentException("\"gateway\" " +
+ "must be an IPv4 " +
+ "address");
+ this.gateway = gateway;
+ break;
+ case Gateway.IPv6:
+ if (!(gateway instanceof Inet6Address))
+ throw new IllegalArgumentException("\"gateway\" " +
+ "must be an IPv6 " +
+ "address");
+ this.gateway = gateway;
+ break;
+ case Gateway.Name:
+ if (!(gateway instanceof Name))
+ throw new IllegalArgumentException("\"gateway\" " +
+ "must be a DNS " +
+ "name");
+ this.gateway = checkName("gateway", (Name) gateway);
+ break;
+ default:
+ throw new IllegalArgumentException("\"gatewayType\" " +
+ "must be between 0 and 3");
+ }
+
+ this.key = key;
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ precedence = in.readU8();
+ gatewayType = in.readU8();
+ algorithmType = in.readU8();
+ switch (gatewayType) {
+ case Gateway.None:
+ gateway = null;
+ break;
+ case Gateway.IPv4:
+ gateway = InetAddress.getByAddress(in.readByteArray(4));
+ break;
+ case Gateway.IPv6:
+ gateway = InetAddress.getByAddress(in.readByteArray(16));
+ break;
+ case Gateway.Name:
+ gateway = new Name(in);
+ break;
+ default:
+ throw new WireParseException("invalid gateway type");
+ }
+ if (in.remaining() > 0)
+ key = in.readByteArray();
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ precedence = st.getUInt8();
+ gatewayType = st.getUInt8();
+ algorithmType = st.getUInt8();
+ switch (gatewayType) {
+ case Gateway.None:
+ String s = st.getString();
+ if (!s.equals("."))
+ throw new TextParseException("invalid gateway format");
+ gateway = null;
+ break;
+ case Gateway.IPv4:
+ gateway = st.getAddress(Address.IPv4);
+ break;
+ case Gateway.IPv6:
+ gateway = st.getAddress(Address.IPv6);
+ break;
+ case Gateway.Name:
+ gateway = st.getName(origin);
+ break;
+ default:
+ throw new WireParseException("invalid gateway type");
+ }
+ key = st.getBase64(false);
+}
+
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(precedence);
+ sb.append(" ");
+ sb.append(gatewayType);
+ sb.append(" ");
+ sb.append(algorithmType);
+ sb.append(" ");
+ switch (gatewayType) {
+ case Gateway.None:
+ sb.append(".");
+ break;
+ case Gateway.IPv4:
+ case Gateway.IPv6:
+ InetAddress gatewayAddr = (InetAddress) gateway;
+ sb.append(gatewayAddr.getHostAddress());
+ break;
+ case Gateway.Name:
+ sb.append(gateway);
+ break;
+ }
+ if (key != null) {
+ sb.append(" ");
+ sb.append(base64.toString(key));
+ }
+ return sb.toString();
+}
+
+/** Returns the record's precedence. */
+public int
+getPrecedence() {
+ return precedence;
+}
+
+/** Returns the record's gateway type. */
+public int
+getGatewayType() {
+ return gatewayType;
+}
+
+/** Returns the record's algorithm type. */
+public int
+getAlgorithmType() {
+ return algorithmType;
+}
+
+/** Returns the record's gateway. */
+public Object
+getGateway() {
+ return gateway;
+}
+
+/** Returns the record's public key */
+public byte []
+getKey() {
+ return key;
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeU8(precedence);
+ out.writeU8(gatewayType);
+ out.writeU8(algorithmType);
+ switch (gatewayType) {
+ case Gateway.None:
+ break;
+ case Gateway.IPv4:
+ case Gateway.IPv6:
+ InetAddress gatewayAddr = (InetAddress) gateway;
+ out.writeByteArray(gatewayAddr.getAddress());
+ break;
+ case Gateway.Name:
+ Name gatewayName = (Name) gateway;
+ gatewayName.toWire(out, null, canonical);
+ break;
+ }
+ if (key != null)
+ out.writeByteArray(key);
+}
+
+}
diff --git a/src/org/xbill/DNS/ISDNRecord.java b/src/org/xbill/DNS/ISDNRecord.java
new file mode 100644
index 0000000..8f9b629
--- /dev/null
+++ b/src/org/xbill/DNS/ISDNRecord.java
@@ -0,0 +1,105 @@
+// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+
+/**
+ * ISDN - identifies the ISDN number and subaddress associated with a name.
+ *
+ * @author Brian Wellington
+ */
+
+public class ISDNRecord extends Record {
+
+private static final long serialVersionUID = -8730801385178968798L;
+
+private byte [] address;
+private byte [] subAddress;
+
+ISDNRecord() {}
+
+Record
+getObject() {
+ return new ISDNRecord();
+}
+
+/**
+ * Creates an ISDN Record from the given data
+ * @param address The ISDN number associated with the domain.
+ * @param subAddress The subaddress, if any.
+ * @throws IllegalArgumentException One of the strings is invalid.
+ */
+public
+ISDNRecord(Name name, int dclass, long ttl, String address, String subAddress) {
+ super(name, Type.ISDN, dclass, ttl);
+ try {
+ this.address = byteArrayFromString(address);
+ if (subAddress != null)
+ this.subAddress = byteArrayFromString(subAddress);
+ }
+ catch (TextParseException e) {
+ throw new IllegalArgumentException(e.getMessage());
+ }
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ address = in.readCountedString();
+ if (in.remaining() > 0)
+ subAddress = in.readCountedString();
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ try {
+ address = byteArrayFromString(st.getString());
+ Tokenizer.Token t = st.get();
+ if (t.isString()) {
+ subAddress = byteArrayFromString(t.value);
+ } else {
+ st.unget();
+ }
+ }
+ catch (TextParseException e) {
+ throw st.exception(e.getMessage());
+ }
+}
+
+/**
+ * Returns the ISDN number associated with the domain.
+ */
+public String
+getAddress() {
+ return byteArrayToString(address, false);
+}
+
+/**
+ * Returns the ISDN subaddress, or null if there is none.
+ */
+public String
+getSubAddress() {
+ if (subAddress == null)
+ return null;
+ return byteArrayToString(subAddress, false);
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeCountedString(address);
+ if (subAddress != null)
+ out.writeCountedString(subAddress);
+}
+
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(byteArrayToString(address, true));
+ if (subAddress != null) {
+ sb.append(" ");
+ sb.append(byteArrayToString(subAddress, true));
+ }
+ return sb.toString();
+}
+
+}
diff --git a/src/org/xbill/DNS/InvalidDClassException.java b/src/org/xbill/DNS/InvalidDClassException.java
new file mode 100644
index 0000000..6c95cd4
--- /dev/null
+++ b/src/org/xbill/DNS/InvalidDClassException.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * An exception thrown when an invalid dclass code is specified.
+ *
+ * @author Brian Wellington
+ */
+
+public class InvalidDClassException extends IllegalArgumentException {
+
+public
+InvalidDClassException(int dclass) {
+ super("Invalid DNS class: " + dclass);
+}
+
+}
diff --git a/src/org/xbill/DNS/InvalidTTLException.java b/src/org/xbill/DNS/InvalidTTLException.java
new file mode 100644
index 0000000..95776fe
--- /dev/null
+++ b/src/org/xbill/DNS/InvalidTTLException.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * An exception thrown when an invalid TTL is specified.
+ *
+ * @author Brian Wellington
+ */
+
+public class InvalidTTLException extends IllegalArgumentException {
+
+public
+InvalidTTLException(long ttl) {
+ super("Invalid DNS TTL: " + ttl);
+}
+
+}
diff --git a/src/org/xbill/DNS/InvalidTypeException.java b/src/org/xbill/DNS/InvalidTypeException.java
new file mode 100644
index 0000000..7c61276
--- /dev/null
+++ b/src/org/xbill/DNS/InvalidTypeException.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * An exception thrown when an invalid type code is specified.
+ *
+ * @author Brian Wellington
+ */
+
+public class InvalidTypeException extends IllegalArgumentException {
+
+public
+InvalidTypeException(int type) {
+ super("Invalid DNS type: " + type);
+}
+
+}
diff --git a/src/org/xbill/DNS/KEYBase.java b/src/org/xbill/DNS/KEYBase.java
new file mode 100644
index 0000000..59a2c6c
--- /dev/null
+++ b/src/org/xbill/DNS/KEYBase.java
@@ -0,0 +1,161 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.security.PublicKey;
+
+import org.xbill.DNS.utils.*;
+
+/**
+ * The base class for KEY/DNSKEY records, which have identical formats
+ *
+ * @author Brian Wellington
+ */
+
+abstract class KEYBase extends Record {
+
+private static final long serialVersionUID = 3469321722693285454L;
+
+protected int flags, proto, alg;
+protected byte [] key;
+protected int footprint = -1;
+protected PublicKey publicKey = null;
+
+protected
+KEYBase() {}
+
+public
+KEYBase(Name name, int type, int dclass, long ttl, int flags, int proto,
+ int alg, byte [] key)
+{
+ super(name, type, dclass, ttl);
+ this.flags = checkU16("flags", flags);
+ this.proto = checkU8("proto", proto);
+ this.alg = checkU8("alg", alg);
+ this.key = key;
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ flags = in.readU16();
+ proto = in.readU8();
+ alg = in.readU8();
+ if (in.remaining() > 0)
+ key = in.readByteArray();
+}
+
+/** Converts the DNSKEY/KEY Record to a String */
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(flags);
+ sb.append(" ");
+ sb.append(proto);
+ sb.append(" ");
+ sb.append(alg);
+ if (key != null) {
+ if (Options.check("multiline")) {
+ sb.append(" (\n");
+ sb.append(base64.formatString(key, 64, "\t", true));
+ sb.append(" ; key_tag = ");
+ sb.append(getFootprint());
+ } else {
+ sb.append(" ");
+ sb.append(base64.toString(key));
+ }
+ }
+ return sb.toString();
+}
+
+/**
+ * Returns the flags describing the key's properties
+ */
+public int
+getFlags() {
+ return flags;
+}
+
+/**
+ * Returns the protocol that the key was created for
+ */
+public int
+getProtocol() {
+ return proto;
+}
+
+/**
+ * Returns the key's algorithm
+ */
+public int
+getAlgorithm() {
+ return alg;
+}
+
+/**
+ * Returns the binary data representing the key
+ */
+public byte []
+getKey() {
+ return key;
+}
+
+/**
+ * Returns the key's footprint (after computing it)
+ */
+public int
+getFootprint() {
+ if (footprint >= 0)
+ return footprint;
+
+ int foot = 0;
+
+ DNSOutput out = new DNSOutput();
+ rrToWire(out, null, false);
+ byte [] rdata = out.toByteArray();
+
+ if (alg == DNSSEC.Algorithm.RSAMD5) {
+ int d1 = rdata[rdata.length - 3] & 0xFF;
+ int d2 = rdata[rdata.length - 2] & 0xFF;
+ foot = (d1 << 8) + d2;
+ }
+ else {
+ int i;
+ for (i = 0; i < rdata.length - 1; i += 2) {
+ int d1 = rdata[i] & 0xFF;
+ int d2 = rdata[i + 1] & 0xFF;
+ foot += ((d1 << 8) + d2);
+ }
+ if (i < rdata.length) {
+ int d1 = rdata[i] & 0xFF;
+ foot += (d1 << 8);
+ }
+ foot += ((foot >> 16) & 0xFFFF);
+ }
+ footprint = (foot & 0xFFFF);
+ return footprint;
+}
+
+/**
+ * Returns a PublicKey corresponding to the data in this key.
+ * @throws DNSSEC.DNSSECException The key could not be converted.
+ */
+public PublicKey
+getPublicKey() throws DNSSEC.DNSSECException {
+ if (publicKey != null)
+ return publicKey;
+
+ publicKey = DNSSEC.toPublicKey(this);
+ return publicKey;
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeU16(flags);
+ out.writeU8(proto);
+ out.writeU8(alg);
+ if (key != null)
+ out.writeByteArray(key);
+}
+
+}
diff --git a/src/org/xbill/DNS/KEYRecord.java b/src/org/xbill/DNS/KEYRecord.java
new file mode 100644
index 0000000..3d2e01c
--- /dev/null
+++ b/src/org/xbill/DNS/KEYRecord.java
@@ -0,0 +1,352 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.security.PublicKey;
+import java.util.*;
+
+/**
+ * Key - contains a cryptographic public key. The data can be converted
+ * to objects implementing java.security.interfaces.PublicKey
+ * @see DNSSEC
+ *
+ * @author Brian Wellington
+ */
+
+public class KEYRecord extends KEYBase {
+
+private static final long serialVersionUID = 6385613447571488906L;
+
+public static class Protocol {
+ /**
+ * KEY protocol identifiers.
+ */
+
+ private Protocol() {}
+
+ /** No defined protocol. */
+ public static final int NONE = 0;
+
+ /** Transaction Level Security */
+ public static final int TLS = 1;
+
+ /** Email */
+ public static final int EMAIL = 2;
+
+ /** DNSSEC */
+ public static final int DNSSEC = 3;
+
+ /** IPSEC Control */
+ public static final int IPSEC = 4;
+
+ /** Any protocol */
+ public static final int ANY = 255;
+
+ private static Mnemonic protocols = new Mnemonic("KEY protocol",
+ Mnemonic.CASE_UPPER);
+
+ static {
+ protocols.setMaximum(0xFF);
+ protocols.setNumericAllowed(true);
+
+ protocols.add(NONE, "NONE");
+ protocols.add(TLS, "TLS");
+ protocols.add(EMAIL, "EMAIL");
+ protocols.add(DNSSEC, "DNSSEC");
+ protocols.add(IPSEC, "IPSEC");
+ protocols.add(ANY, "ANY");
+ }
+
+ /**
+ * Converts an KEY protocol value into its textual representation
+ */
+ public static String
+ string(int type) {
+ return protocols.getText(type);
+ }
+
+ /**
+ * Converts a textual representation of a KEY protocol into its
+ * numeric code. Integers in the range 0..255 are also accepted.
+ * @param s The textual representation of the protocol
+ * @return The protocol code, or -1 on error.
+ */
+ public static int
+ value(String s) {
+ return protocols.getValue(s);
+ }
+}
+
+public static class Flags {
+ /**
+ * KEY flags identifiers.
+ */
+
+ private Flags() {}
+
+ /** KEY cannot be used for confidentiality */
+ public static final int NOCONF = 0x4000;
+
+ /** KEY cannot be used for authentication */
+ public static final int NOAUTH = 0x8000;
+
+ /** No key present */
+ public static final int NOKEY = 0xC000;
+
+ /** Bitmask of the use fields */
+ public static final int USE_MASK = 0xC000;
+
+ /** Flag 2 (unused) */
+ public static final int FLAG2 = 0x2000;
+
+ /** Flags extension */
+ public static final int EXTEND = 0x1000;
+
+ /** Flag 4 (unused) */
+ public static final int FLAG4 = 0x0800;
+
+ /** Flag 5 (unused) */
+ public static final int FLAG5 = 0x0400;
+
+ /** Key is owned by a user. */
+ public static final int USER = 0x0000;
+
+ /** Key is owned by a zone. */
+ public static final int ZONE = 0x0100;
+
+ /** Key is owned by a host. */
+ public static final int HOST = 0x0200;
+
+ /** Key owner type 3 (reserved). */
+ public static final int NTYP3 = 0x0300;
+
+ /** Key owner bitmask. */
+ public static final int OWNER_MASK = 0x0300;
+
+ /** Flag 8 (unused) */
+ public static final int FLAG8 = 0x0080;
+
+ /** Flag 9 (unused) */
+ public static final int FLAG9 = 0x0040;
+
+ /** Flag 10 (unused) */
+ public static final int FLAG10 = 0x0020;
+
+ /** Flag 11 (unused) */
+ public static final int FLAG11 = 0x0010;
+
+ /** Signatory value 0 */
+ public static final int SIG0 = 0;
+
+ /** Signatory value 1 */
+ public static final int SIG1 = 1;
+
+ /** Signatory value 2 */
+ public static final int SIG2 = 2;
+
+ /** Signatory value 3 */
+ public static final int SIG3 = 3;
+
+ /** Signatory value 4 */
+ public static final int SIG4 = 4;
+
+ /** Signatory value 5 */
+ public static final int SIG5 = 5;
+
+ /** Signatory value 6 */
+ public static final int SIG6 = 6;
+
+ /** Signatory value 7 */
+ public static final int SIG7 = 7;
+
+ /** Signatory value 8 */
+ public static final int SIG8 = 8;
+
+ /** Signatory value 9 */
+ public static final int SIG9 = 9;
+
+ /** Signatory value 10 */
+ public static final int SIG10 = 10;
+
+ /** Signatory value 11 */
+ public static final int SIG11 = 11;
+
+ /** Signatory value 12 */
+ public static final int SIG12 = 12;
+
+ /** Signatory value 13 */
+ public static final int SIG13 = 13;
+
+ /** Signatory value 14 */
+ public static final int SIG14 = 14;
+
+ /** Signatory value 15 */
+ public static final int SIG15 = 15;
+
+ private static Mnemonic flags = new Mnemonic("KEY flags",
+ Mnemonic.CASE_UPPER);
+
+ static {
+ flags.setMaximum(0xFFFF);
+ flags.setNumericAllowed(false);
+
+ flags.add(NOCONF, "NOCONF");
+ flags.add(NOAUTH, "NOAUTH");
+ flags.add(NOKEY, "NOKEY");
+ flags.add(FLAG2, "FLAG2");
+ flags.add(EXTEND, "EXTEND");
+ flags.add(FLAG4, "FLAG4");
+ flags.add(FLAG5, "FLAG5");
+ flags.add(USER, "USER");
+ flags.add(ZONE, "ZONE");
+ flags.add(HOST, "HOST");
+ flags.add(NTYP3, "NTYP3");
+ flags.add(FLAG8, "FLAG8");
+ flags.add(FLAG9, "FLAG9");
+ flags.add(FLAG10, "FLAG10");
+ flags.add(FLAG11, "FLAG11");
+ flags.add(SIG0, "SIG0");
+ flags.add(SIG1, "SIG1");
+ flags.add(SIG2, "SIG2");
+ flags.add(SIG3, "SIG3");
+ flags.add(SIG4, "SIG4");
+ flags.add(SIG5, "SIG5");
+ flags.add(SIG6, "SIG6");
+ flags.add(SIG7, "SIG7");
+ flags.add(SIG8, "SIG8");
+ flags.add(SIG9, "SIG9");
+ flags.add(SIG10, "SIG10");
+ flags.add(SIG11, "SIG11");
+ flags.add(SIG12, "SIG12");
+ flags.add(SIG13, "SIG13");
+ flags.add(SIG14, "SIG14");
+ flags.add(SIG15, "SIG15");
+ }
+
+ /**
+ * Converts a textual representation of KEY flags into its
+ * numeric code. Integers in the range 0..65535 are also accepted.
+ * @param s The textual representation of the protocol
+ * @return The protocol code, or -1 on error.
+ */
+ public static int
+ value(String s) {
+ int value;
+ try {
+ value = Integer.parseInt(s);
+ if (value >= 0 && value <= 0xFFFF) {
+ return value;
+ }
+ return -1;
+ } catch (NumberFormatException e) {
+ }
+ StringTokenizer st = new StringTokenizer(s, "|");
+ value = 0;
+ while (st.hasMoreTokens()) {
+ int val = flags.getValue(st.nextToken());
+ if (val < 0) {
+ return -1;
+ }
+ value |= val;
+ }
+ return value;
+ }
+}
+
+/* flags */
+/** This key cannot be used for confidentiality (encryption) */
+public static final int FLAG_NOCONF = Flags.NOCONF;
+
+/** This key cannot be used for authentication */
+public static final int FLAG_NOAUTH = Flags.NOAUTH;
+
+/** This key cannot be used for authentication or confidentiality */
+public static final int FLAG_NOKEY = Flags.NOKEY;
+
+/** A zone key */
+public static final int OWNER_ZONE = Flags.ZONE;
+
+/** A host/end entity key */
+public static final int OWNER_HOST = Flags.HOST;
+
+/** A user key */
+public static final int OWNER_USER = Flags.USER;
+
+/* protocols */
+/** Key was created for use with transaction level security */
+public static final int PROTOCOL_TLS = Protocol.TLS;
+
+/** Key was created for use with email */
+public static final int PROTOCOL_EMAIL = Protocol.EMAIL;
+
+/** Key was created for use with DNSSEC */
+public static final int PROTOCOL_DNSSEC = Protocol.DNSSEC;
+
+/** Key was created for use with IPSEC */
+public static final int PROTOCOL_IPSEC = Protocol.IPSEC;
+
+/** Key was created for use with any protocol */
+public static final int PROTOCOL_ANY = Protocol.ANY;
+
+KEYRecord() {}
+
+Record
+getObject() {
+ return new KEYRecord();
+}
+
+/**
+ * Creates a KEY Record from the given data
+ * @param flags Flags describing the key's properties
+ * @param proto The protocol that the key was created for
+ * @param alg The key's algorithm
+ * @param key Binary data representing the key
+ */
+public
+KEYRecord(Name name, int dclass, long ttl, int flags, int proto, int alg,
+ byte [] key)
+{
+ super(name, Type.KEY, dclass, ttl, flags, proto, alg, key);
+}
+
+/**
+ * Creates a KEY Record from the given data
+ * @param flags Flags describing the key's properties
+ * @param proto The protocol that the key was created for
+ * @param alg The key's algorithm
+ * @param key The key as a PublicKey
+ * @throws DNSSEC.DNSSECException The PublicKey could not be converted into DNS
+ * format.
+ */
+public
+KEYRecord(Name name, int dclass, long ttl, int flags, int proto, int alg,
+ PublicKey key) throws DNSSEC.DNSSECException
+{
+ super(name, Type.KEY, dclass, ttl, flags, proto, alg,
+ DNSSEC.fromPublicKey(key, alg));
+ publicKey = key;
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ String flagString = st.getIdentifier();
+ flags = Flags.value(flagString);
+ if (flags < 0)
+ throw st.exception("Invalid flags: " + flagString);
+ String protoString = st.getIdentifier();
+ proto = Protocol.value(protoString);
+ if (proto < 0)
+ throw st.exception("Invalid protocol: " + protoString);
+ String algString = st.getIdentifier();
+ alg = DNSSEC.Algorithm.value(algString);
+ if (alg < 0)
+ throw st.exception("Invalid algorithm: " + algString);
+ /* If this is a null KEY, there's no key data */
+ if ((flags & Flags.USE_MASK) == Flags.NOKEY)
+ key = null;
+ else
+ key = st.getBase64();
+}
+
+}
diff --git a/src/org/xbill/DNS/KXRecord.java b/src/org/xbill/DNS/KXRecord.java
new file mode 100644
index 0000000..481d21b
--- /dev/null
+++ b/src/org/xbill/DNS/KXRecord.java
@@ -0,0 +1,51 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * Key Exchange - delegation of authority
+ *
+ * @author Brian Wellington
+ */
+
+public class KXRecord extends U16NameBase {
+
+private static final long serialVersionUID = 7448568832769757809L;
+
+KXRecord() {}
+
+Record
+getObject() {
+ return new KXRecord();
+}
+
+/**
+ * Creates a KX Record from the given data
+ * @param preference The preference of this KX. Records with lower priority
+ * are preferred.
+ * @param target The host that authority is delegated to
+ */
+public
+KXRecord(Name name, int dclass, long ttl, int preference, Name target) {
+ super(name, Type.KX, dclass, ttl, preference, "preference",
+ target, "target");
+}
+
+/** Returns the target of the KX record */
+public Name
+getTarget() {
+ return getNameField();
+}
+
+/** Returns the preference of this KX record */
+public int
+getPreference() {
+ return getU16Field();
+}
+
+public Name
+getAdditionalName() {
+ return getNameField();
+}
+
+}
diff --git a/src/org/xbill/DNS/LOCRecord.java b/src/org/xbill/DNS/LOCRecord.java
new file mode 100644
index 0000000..4eddc15
--- /dev/null
+++ b/src/org/xbill/DNS/LOCRecord.java
@@ -0,0 +1,314 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.text.*;
+
+/**
+ * Location - describes the physical location of hosts, networks, subnets.
+ *
+ * @author Brian Wellington
+ */
+
+public class LOCRecord extends Record {
+
+private static final long serialVersionUID = 9058224788126750409L;
+
+private static NumberFormat w2, w3;
+
+private long size, hPrecision, vPrecision;
+private long latitude, longitude, altitude;
+
+static {
+ w2 = new DecimalFormat();
+ w2.setMinimumIntegerDigits(2);
+
+ w3 = new DecimalFormat();
+ w3.setMinimumIntegerDigits(3);
+}
+
+LOCRecord() {}
+
+Record
+getObject() {
+ return new LOCRecord();
+}
+
+/**
+ * Creates an LOC Record from the given data
+ * @param latitude The latitude of the center of the sphere
+ * @param longitude The longitude of the center of the sphere
+ * @param altitude The altitude of the center of the sphere, in m
+ * @param size The diameter of a sphere enclosing the described entity, in m.
+ * @param hPrecision The horizontal precision of the data, in m.
+ * @param vPrecision The vertical precision of the data, in m.
+*/
+public
+LOCRecord(Name name, int dclass, long ttl, double latitude, double longitude,
+ double altitude, double size, double hPrecision, double vPrecision)
+{
+ super(name, Type.LOC, dclass, ttl);
+ this.latitude = (long)(latitude * 3600 * 1000 + (1L << 31));
+ this.longitude = (long)(longitude * 3600 * 1000 + (1L << 31));
+ this.altitude = (long)((altitude + 100000) * 100);
+ this.size = (long)(size * 100);
+ this.hPrecision = (long)(hPrecision * 100);
+ this.vPrecision = (long)(vPrecision * 100);
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ int version;
+
+ version = in.readU8();
+ if (version != 0)
+ throw new WireParseException("Invalid LOC version");
+
+ size = parseLOCformat(in.readU8());
+ hPrecision = parseLOCformat(in.readU8());
+ vPrecision = parseLOCformat(in.readU8());
+ latitude = in.readU32();
+ longitude = in.readU32();
+ altitude = in.readU32();
+}
+
+private double
+parseFixedPoint(String s)
+{
+ if (s.matches("^-?\\d+$"))
+ return Integer.parseInt(s);
+ else if (s.matches("^-?\\d+\\.\\d*$")) {
+ String [] parts = s.split("\\.");
+ double value = Integer.parseInt(parts[0]);
+ double fraction = Integer.parseInt(parts[1]);
+ if (value < 0)
+ fraction *= -1;
+ int digits = parts[1].length();
+ return value + (fraction / Math.pow(10, digits));
+ } else
+ throw new NumberFormatException();
+}
+
+private long
+parsePosition(Tokenizer st, String type) throws IOException {
+ boolean isLatitude = type.equals("latitude");
+ int deg = 0, min = 0;
+ double sec = 0;
+ long value;
+ String s;
+
+ deg = st.getUInt16();
+ if (deg > 180 || (deg > 90 && isLatitude))
+ throw st.exception("Invalid LOC " + type + " degrees");
+
+ s = st.getString();
+ try {
+ min = Integer.parseInt(s);
+ if (min < 0 || min > 59)
+ throw st.exception("Invalid LOC " + type + " minutes");
+ s = st.getString();
+ sec = parseFixedPoint(s);
+ if (sec < 0 || sec >= 60)
+ throw st.exception("Invalid LOC " + type + " seconds");
+ s = st.getString();
+ } catch (NumberFormatException e) {
+ }
+
+ if (s.length() != 1)
+ throw st.exception("Invalid LOC " + type);
+
+ value = (long) (1000 * (sec + 60L * (min + 60L * deg)));
+
+ char c = Character.toUpperCase(s.charAt(0));
+ if ((isLatitude && c == 'S') || (!isLatitude && c == 'W'))
+ value = -value;
+ else if ((isLatitude && c != 'N') || (!isLatitude && c != 'E'))
+ throw st.exception("Invalid LOC " + type);
+
+ value += (1L << 31);
+
+ return value;
+}
+
+private long
+parseDouble(Tokenizer st, String type, boolean required, long min, long max,
+ long defaultValue)
+throws IOException
+{
+ Tokenizer.Token token = st.get();
+ if (token.isEOL()) {
+ if (required)
+ throw st.exception("Invalid LOC " + type);
+ st.unget();
+ return defaultValue;
+ }
+ String s = token.value;
+ if (s.length() > 1 && s.charAt(s.length() - 1) == 'm')
+ s = s.substring(0, s.length() - 1);
+ try {
+ long value = (long)(100 * parseFixedPoint(s));
+ if (value < min || value > max)
+ throw st.exception("Invalid LOC " + type);
+ return value;
+ }
+ catch (NumberFormatException e) {
+ throw st.exception("Invalid LOC " + type);
+ }
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ latitude = parsePosition(st, "latitude");
+ longitude = parsePosition(st, "longitude");
+ altitude = parseDouble(st, "altitude", true,
+ -10000000, 4284967295L, 0) + 10000000;
+ size = parseDouble(st, "size", false, 0, 9000000000L, 100);
+ hPrecision = parseDouble(st, "horizontal precision", false,
+ 0, 9000000000L, 1000000);
+ vPrecision = parseDouble(st, "vertical precision", false,
+ 0, 9000000000L, 1000);
+}
+
+private void
+renderFixedPoint(StringBuffer sb, NumberFormat formatter, long value,
+ long divisor)
+{
+ sb.append(value / divisor);
+ value %= divisor;
+ if (value != 0) {
+ sb.append(".");
+ sb.append(formatter.format(value));
+ }
+}
+
+private String
+positionToString(long value, char pos, char neg) {
+ StringBuffer sb = new StringBuffer();
+ char direction;
+
+ long temp = value - (1L << 31);
+ if (temp < 0) {
+ temp = -temp;
+ direction = neg;
+ } else
+ direction = pos;
+
+ sb.append(temp / (3600 * 1000)); /* degrees */
+ temp = temp % (3600 * 1000);
+ sb.append(" ");
+
+ sb.append(temp / (60 * 1000)); /* minutes */
+ temp = temp % (60 * 1000);
+ sb.append(" ");
+
+ renderFixedPoint(sb, w3, temp, 1000); /* seconds */
+ sb.append(" ");
+
+ sb.append(direction);
+
+ return sb.toString();
+}
+
+
+/** Convert to a String */
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+
+ /* Latitude */
+ sb.append(positionToString(latitude, 'N', 'S'));
+ sb.append(" ");
+
+ /* Latitude */
+ sb.append(positionToString(longitude, 'E', 'W'));
+ sb.append(" ");
+
+ /* Altitude */
+ renderFixedPoint(sb, w2, altitude - 10000000, 100);
+ sb.append("m ");
+
+ /* Size */
+ renderFixedPoint(sb, w2, size, 100);
+ sb.append("m ");
+
+ /* Horizontal precision */
+ renderFixedPoint(sb, w2, hPrecision, 100);
+ sb.append("m ");
+
+ /* Vertical precision */
+ renderFixedPoint(sb, w2, vPrecision, 100);
+ sb.append("m");
+
+ return sb.toString();
+}
+
+/** Returns the latitude */
+public double
+getLatitude() {
+ return ((double)(latitude - (1L << 31))) / (3600 * 1000);
+}
+
+/** Returns the longitude */
+public double
+getLongitude() {
+ return ((double)(longitude - (1L << 31))) / (3600 * 1000);
+}
+
+/** Returns the altitude */
+public double
+getAltitude() {
+ return ((double)(altitude - 10000000)) / 100;
+}
+
+/** Returns the diameter of the enclosing sphere */
+public double
+getSize() {
+ return ((double)size) / 100;
+}
+
+/** Returns the horizontal precision */
+public double
+getHPrecision() {
+ return ((double)hPrecision) / 100;
+}
+
+/** Returns the horizontal precision */
+public double
+getVPrecision() {
+ return ((double)vPrecision) / 100;
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeU8(0); /* version */
+ out.writeU8(toLOCformat(size));
+ out.writeU8(toLOCformat(hPrecision));
+ out.writeU8(toLOCformat(vPrecision));
+ out.writeU32(latitude);
+ out.writeU32(longitude);
+ out.writeU32(altitude);
+}
+
+private static long
+parseLOCformat(int b) throws WireParseException {
+ long out = b >> 4;
+ int exp = b & 0xF;
+ if (out > 9 || exp > 9)
+ throw new WireParseException("Invalid LOC Encoding");
+ while (exp-- > 0)
+ out *= 10;
+ return (out);
+}
+
+private int
+toLOCformat(long l) {
+ byte exp = 0;
+ while (l > 9) {
+ exp++;
+ l /= 10;
+ }
+ return (int)((l << 4) + exp);
+}
+
+}
diff --git a/src/org/xbill/DNS/Lookup.java b/src/org/xbill/DNS/Lookup.java
new file mode 100644
index 0000000..0beba59
--- /dev/null
+++ b/src/org/xbill/DNS/Lookup.java
@@ -0,0 +1,647 @@
+// Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.util.*;
+import java.io.*;
+import java.net.*;
+
+/**
+ * The Lookup object issues queries to caching DNS servers. The input consists
+ * of a name, an optional type, and an optional class. Caching is enabled
+ * by default and used when possible to reduce the number of DNS requests.
+ * A Resolver, which defaults to an ExtendedResolver initialized with the
+ * resolvers located by the ResolverConfig class, performs the queries. A
+ * search path of domain suffixes is used to resolve relative names, and is
+ * also determined by the ResolverConfig class.
+ *
+ * A Lookup object may be reused, but should not be used by multiple threads.
+ *
+ * @see Cache
+ * @see Resolver
+ * @see ResolverConfig
+ *
+ * @author Brian Wellington
+ */
+
+public final class Lookup {
+
+private static Resolver defaultResolver;
+private static Name [] defaultSearchPath;
+private static Map defaultCaches;
+private static int defaultNdots;
+
+private Resolver resolver;
+private Name [] searchPath;
+private Cache cache;
+private boolean temporary_cache;
+private int credibility;
+private Name name;
+private int type;
+private int dclass;
+private boolean verbose;
+private int iterations;
+private boolean foundAlias;
+private boolean done;
+private boolean doneCurrent;
+private List aliases;
+private Record [] answers;
+private int result;
+private String error;
+private boolean nxdomain;
+private boolean badresponse;
+private String badresponse_error;
+private boolean networkerror;
+private boolean timedout;
+private boolean nametoolong;
+private boolean referral;
+
+private static final Name [] noAliases = new Name[0];
+
+/** The lookup was successful. */
+public static final int SUCCESSFUL = 0;
+
+/**
+ * The lookup failed due to a data or server error. Repeating the lookup
+ * would not be helpful.
+ */
+public static final int UNRECOVERABLE = 1;
+
+/**
+ * The lookup failed due to a network error. Repeating the lookup may be
+ * helpful.
+ */
+public static final int TRY_AGAIN = 2;
+
+/** The host does not exist. */
+public static final int HOST_NOT_FOUND = 3;
+
+/** The host exists, but has no records associated with the queried type. */
+public static final int TYPE_NOT_FOUND = 4;
+
+public static synchronized void
+refreshDefault() {
+
+ try {
+ defaultResolver = new ExtendedResolver();
+ }
+ catch (UnknownHostException e) {
+ throw new RuntimeException("Failed to initialize resolver");
+ }
+ defaultSearchPath = ResolverConfig.getCurrentConfig().searchPath();
+ defaultCaches = new HashMap();
+ defaultNdots = ResolverConfig.getCurrentConfig().ndots();
+}
+
+static {
+ refreshDefault();
+}
+
+/**
+ * Gets the Resolver that will be used as the default by future Lookups.
+ * @return The default resolver.
+ */
+public static synchronized Resolver
+getDefaultResolver() {
+ return defaultResolver;
+}
+
+/**
+ * Sets the default Resolver to be used as the default by future Lookups.
+ * @param resolver The default resolver.
+ */
+public static synchronized void
+setDefaultResolver(Resolver resolver) {
+ defaultResolver = resolver;
+}
+
+/**
+ * Gets the Cache that will be used as the default for the specified
+ * class by future Lookups.
+ * @param dclass The class whose cache is being retrieved.
+ * @return The default cache for the specified class.
+ */
+public static synchronized Cache
+getDefaultCache(int dclass) {
+ DClass.check(dclass);
+ Cache c = (Cache) defaultCaches.get(Mnemonic.toInteger(dclass));
+ if (c == null) {
+ c = new Cache(dclass);
+ defaultCaches.put(Mnemonic.toInteger(dclass), c);
+ }
+ return c;
+}
+
+/**
+ * Sets the Cache to be used as the default for the specified class by future
+ * Lookups.
+ * @param cache The default cache for the specified class.
+ * @param dclass The class whose cache is being set.
+ */
+public static synchronized void
+setDefaultCache(Cache cache, int dclass) {
+ DClass.check(dclass);
+ defaultCaches.put(Mnemonic.toInteger(dclass), cache);
+}
+
+/**
+ * Gets the search path that will be used as the default by future Lookups.
+ * @return The default search path.
+ */
+public static synchronized Name []
+getDefaultSearchPath() {
+ return defaultSearchPath;
+}
+
+/**
+ * Sets the search path to be used as the default by future Lookups.
+ * @param domains The default search path.
+ */
+public static synchronized void
+setDefaultSearchPath(Name [] domains) {
+ defaultSearchPath = domains;
+}
+
+/**
+ * Sets the search path that will be used as the default by future Lookups.
+ * @param domains The default search path.
+ * @throws TextParseException A name in the array is not a valid DNS name.
+ */
+public static synchronized void
+setDefaultSearchPath(String [] domains) throws TextParseException {
+ if (domains == null) {
+ defaultSearchPath = null;
+ return;
+ }
+ Name [] newdomains = new Name[domains.length];
+ for (int i = 0; i < domains.length; i++)
+ newdomains[i] = Name.fromString(domains[i], Name.root);
+ defaultSearchPath = newdomains;
+}
+
+private final void
+reset() {
+ iterations = 0;
+ foundAlias = false;
+ done = false;
+ doneCurrent = false;
+ aliases = null;
+ answers = null;
+ result = -1;
+ error = null;
+ nxdomain = false;
+ badresponse = false;
+ badresponse_error = null;
+ networkerror = false;
+ timedout = false;
+ nametoolong = false;
+ referral = false;
+ if (temporary_cache)
+ cache.clearCache();
+}
+
+/**
+ * Create a Lookup object that will find records of the given name, type,
+ * and class. The lookup will use the default cache, resolver, and search
+ * path, and look for records that are reasonably credible.
+ * @param name The name of the desired records
+ * @param type The type of the desired records
+ * @param dclass The class of the desired records
+ * @throws IllegalArgumentException The type is a meta type other than ANY.
+ * @see Cache
+ * @see Resolver
+ * @see Credibility
+ * @see Name
+ * @see Type
+ * @see DClass
+ */
+public
+Lookup(Name name, int type, int dclass) {
+ Type.check(type);
+ DClass.check(dclass);
+ if (!Type.isRR(type) && type != Type.ANY)
+ throw new IllegalArgumentException("Cannot query for " +
+ "meta-types other than ANY");
+ this.name = name;
+ this.type = type;
+ this.dclass = dclass;
+ synchronized (Lookup.class) {
+ this.resolver = getDefaultResolver();
+ this.searchPath = getDefaultSearchPath();
+ this.cache = getDefaultCache(dclass);
+ }
+ this.credibility = Credibility.NORMAL;
+ this.verbose = Options.check("verbose");
+ this.result = -1;
+}
+
+/**
+ * Create a Lookup object that will find records of the given name and type
+ * in the IN class.
+ * @param name The name of the desired records
+ * @param type The type of the desired records
+ * @throws IllegalArgumentException The type is a meta type other than ANY.
+ * @see #Lookup(Name,int,int)
+ */
+public
+Lookup(Name name, int type) {
+ this(name, type, DClass.IN);
+}
+
+/**
+ * Create a Lookup object that will find records of type A at the given name
+ * in the IN class.
+ * @param name The name of the desired records
+ * @see #Lookup(Name,int,int)
+ */
+public
+Lookup(Name name) {
+ this(name, Type.A, DClass.IN);
+}
+
+/**
+ * Create a Lookup object that will find records of the given name, type,
+ * and class.
+ * @param name The name of the desired records
+ * @param type The type of the desired records
+ * @param dclass The class of the desired records
+ * @throws TextParseException The name is not a valid DNS name
+ * @throws IllegalArgumentException The type is a meta type other than ANY.
+ * @see #Lookup(Name,int,int)
+ */
+public
+Lookup(String name, int type, int dclass) throws TextParseException {
+ this(Name.fromString(name), type, dclass);
+}
+
+/**
+ * Create a Lookup object that will find records of the given name and type
+ * in the IN class.
+ * @param name The name of the desired records
+ * @param type The type of the desired records
+ * @throws TextParseException The name is not a valid DNS name
+ * @throws IllegalArgumentException The type is a meta type other than ANY.
+ * @see #Lookup(Name,int,int)
+ */
+public
+Lookup(String name, int type) throws TextParseException {
+ this(Name.fromString(name), type, DClass.IN);
+}
+
+/**
+ * Create a Lookup object that will find records of type A at the given name
+ * in the IN class.
+ * @param name The name of the desired records
+ * @throws TextParseException The name is not a valid DNS name
+ * @see #Lookup(Name,int,int)
+ */
+public
+Lookup(String name) throws TextParseException {
+ this(Name.fromString(name), Type.A, DClass.IN);
+}
+
+/**
+ * Sets the resolver to use when performing this lookup. This overrides the
+ * default value.
+ * @param resolver The resolver to use.
+ */
+public void
+setResolver(Resolver resolver) {
+ this.resolver = resolver;
+}
+
+/**
+ * Sets the search path to use when performing this lookup. This overrides the
+ * default value.
+ * @param domains An array of names containing the search path.
+ */
+public void
+setSearchPath(Name [] domains) {
+ this.searchPath = domains;
+}
+
+/**
+ * Sets the search path to use when performing this lookup. This overrides the
+ * default value.
+ * @param domains An array of names containing the search path.
+ * @throws TextParseException A name in the array is not a valid DNS name.
+ */
+public void
+setSearchPath(String [] domains) throws TextParseException {
+ if (domains == null) {
+ this.searchPath = null;
+ return;
+ }
+ Name [] newdomains = new Name[domains.length];
+ for (int i = 0; i < domains.length; i++)
+ newdomains[i] = Name.fromString(domains[i], Name.root);
+ this.searchPath = newdomains;
+}
+
+/**
+ * Sets the cache to use when performing this lookup. This overrides the
+ * default value. If the results of this lookup should not be permanently
+ * cached, null can be provided here.
+ * @param cache The cache to use.
+ */
+public void
+setCache(Cache cache) {
+ if (cache == null) {
+ this.cache = new Cache(dclass);
+ this.temporary_cache = true;
+ } else {
+ this.cache = cache;
+ this.temporary_cache = false;
+ }
+}
+
+/**
+ * Sets ndots to use when performing this lookup, overriding the default value.
+ * Specifically, this refers to the number of "dots" which, if present in a
+ * name, indicate that a lookup for the absolute name should be attempted
+ * before appending any search path elements.
+ * @param ndots The ndots value to use, which must be greater than or equal to
+ * 0.
+ */
+public void
+setNdots(int ndots) {
+ if (ndots < 0)
+ throw new IllegalArgumentException("Illegal ndots value: " +
+ ndots);
+ defaultNdots = ndots;
+}
+
+/**
+ * Sets the minimum credibility level that will be accepted when performing
+ * the lookup. This defaults to Credibility.NORMAL.
+ * @param credibility The minimum credibility level.
+ */
+public void
+setCredibility(int credibility) {
+ this.credibility = credibility;
+}
+
+private void
+follow(Name name, Name oldname) {
+ foundAlias = true;
+ badresponse = false;
+ networkerror = false;
+ timedout = false;
+ nxdomain = false;
+ referral = false;
+ iterations++;
+ if (iterations >= 6 || name.equals(oldname)) {
+ result = UNRECOVERABLE;
+ error = "CNAME loop";
+ done = true;
+ return;
+ }
+ if (aliases == null)
+ aliases = new ArrayList();
+ aliases.add(oldname);
+ lookup(name);
+}
+
+private void
+processResponse(Name name, SetResponse response) {
+ if (response.isSuccessful()) {
+ RRset [] rrsets = response.answers();
+ List l = new ArrayList();
+ Iterator it;
+ int i;
+
+ for (i = 0; i < rrsets.length; i++) {
+ it = rrsets[i].rrs();
+ while (it.hasNext())
+ l.add(it.next());
+ }
+
+ result = SUCCESSFUL;
+ answers = (Record []) l.toArray(new Record[l.size()]);
+ done = true;
+ } else if (response.isNXDOMAIN()) {
+ nxdomain = true;
+ doneCurrent = true;
+ if (iterations > 0) {
+ result = HOST_NOT_FOUND;
+ done = true;
+ }
+ } else if (response.isNXRRSET()) {
+ result = TYPE_NOT_FOUND;
+ answers = null;
+ done = true;
+ } else if (response.isCNAME()) {
+ CNAMERecord cname = response.getCNAME();
+ follow(cname.getTarget(), name);
+ } else if (response.isDNAME()) {
+ DNAMERecord dname = response.getDNAME();
+ try {
+ follow(name.fromDNAME(dname), name);
+ } catch (NameTooLongException e) {
+ result = UNRECOVERABLE;
+ error = "Invalid DNAME target";
+ done = true;
+ }
+ } else if (response.isDelegation()) {
+ // We shouldn't get a referral. Ignore it.
+ referral = true;
+ }
+}
+
+private void
+lookup(Name current) {
+ SetResponse sr = cache.lookupRecords(current, type, credibility);
+ if (verbose) {
+ System.err.println("lookup " + current + " " +
+ Type.string(type));
+ System.err.println(sr);
+ }
+ processResponse(current, sr);
+ if (done || doneCurrent)
+ return;
+
+ Record question = Record.newRecord(current, type, dclass);
+ Message query = Message.newQuery(question);
+ Message response = null;
+ try {
+ response = resolver.send(query);
+ }
+ catch (IOException e) {
+ // A network error occurred. Press on.
+ if (e instanceof InterruptedIOException)
+ timedout = true;
+ else
+ networkerror = true;
+ return;
+ }
+ int rcode = response.getHeader().getRcode();
+ if (rcode != Rcode.NOERROR && rcode != Rcode.NXDOMAIN) {
+ // The server we contacted is broken or otherwise unhelpful.
+ // Press on.
+ badresponse = true;
+ badresponse_error = Rcode.string(rcode);
+ return;
+ }
+
+ if (!query.getQuestion().equals(response.getQuestion())) {
+ // The answer doesn't match the question. That's not good.
+ badresponse = true;
+ badresponse_error = "response does not match query";
+ return;
+ }
+
+ sr = cache.addMessage(response);
+ if (sr == null)
+ sr = cache.lookupRecords(current, type, credibility);
+ if (verbose) {
+ System.err.println("queried " + current + " " +
+ Type.string(type));
+ System.err.println(sr);
+ }
+ processResponse(current, sr);
+}
+
+private void
+resolve(Name current, Name suffix) {
+ doneCurrent = false;
+ Name tname = null;
+ if (suffix == null)
+ tname = current;
+ else {
+ try {
+ tname = Name.concatenate(current, suffix);
+ }
+ catch (NameTooLongException e) {
+ nametoolong = true;
+ return;
+ }
+ }
+ lookup(tname);
+}
+
+/**
+ * Performs the lookup, using the specified Cache, Resolver, and search path.
+ * @return The answers, or null if none are found.
+ */
+public Record []
+run() {
+ if (done)
+ reset();
+ if (name.isAbsolute())
+ resolve(name, null);
+ else if (searchPath == null)
+ resolve(name, Name.root);
+ else {
+ if (name.labels() > defaultNdots)
+ resolve(name, Name.root);
+ if (done)
+ return answers;
+
+ for (int i = 0; i < searchPath.length; i++) {
+ resolve(name, searchPath[i]);
+ if (done)
+ return answers;
+ else if (foundAlias)
+ break;
+ }
+ }
+ if (!done) {
+ if (badresponse) {
+ result = TRY_AGAIN;
+ error = badresponse_error;
+ done = true;
+ } else if (timedout) {
+ result = TRY_AGAIN;
+ error = "timed out";
+ done = true;
+ } else if (networkerror) {
+ result = TRY_AGAIN;
+ error = "network error";
+ done = true;
+ } else if (nxdomain) {
+ result = HOST_NOT_FOUND;
+ done = true;
+ } else if (referral) {
+ result = UNRECOVERABLE;
+ error = "referral";
+ done = true;
+ } else if (nametoolong) {
+ result = UNRECOVERABLE;
+ error = "name too long";
+ done = true;
+ }
+ }
+ return answers;
+}
+
+private void
+checkDone() {
+ if (done && result != -1)
+ return;
+ StringBuffer sb = new StringBuffer("Lookup of " + name + " ");
+ if (dclass != DClass.IN)
+ sb.append(DClass.string(dclass) + " ");
+ sb.append(Type.string(type) + " isn't done");
+ throw new IllegalStateException(sb.toString());
+}
+
+/**
+ * Returns the answers from the lookup.
+ * @return The answers, or null if none are found.
+ * @throws IllegalStateException The lookup has not completed.
+ */
+public Record []
+getAnswers() {
+ checkDone();
+ return answers;
+}
+
+/**
+ * Returns all known aliases for this name. Whenever a CNAME/DNAME is
+ * followed, an alias is added to this array. The last element in this
+ * array will be the owner name for records in the answer, if there are any.
+ * @return The aliases.
+ * @throws IllegalStateException The lookup has not completed.
+ */
+public Name []
+getAliases() {
+ checkDone();
+ if (aliases == null)
+ return noAliases;
+ return (Name []) aliases.toArray(new Name[aliases.size()]);
+}
+
+/**
+ * Returns the result code of the lookup.
+ * @return The result code, which can be SUCCESSFUL, UNRECOVERABLE, TRY_AGAIN,
+ * HOST_NOT_FOUND, or TYPE_NOT_FOUND.
+ * @throws IllegalStateException The lookup has not completed.
+ */
+public int
+getResult() {
+ checkDone();
+ return result;
+}
+
+/**
+ * Returns an error string describing the result code of this lookup.
+ * @return A string, which may either directly correspond the result code
+ * or be more specific.
+ * @throws IllegalStateException The lookup has not completed.
+ */
+public String
+getErrorString() {
+ checkDone();
+ if (error != null)
+ return error;
+ switch (result) {
+ case SUCCESSFUL: return "successful";
+ case UNRECOVERABLE: return "unrecoverable error";
+ case TRY_AGAIN: return "try again";
+ case HOST_NOT_FOUND: return "host not found";
+ case TYPE_NOT_FOUND: return "type not found";
+ }
+ throw new IllegalStateException("unknown result");
+}
+
+}
diff --git a/src/org/xbill/DNS/MBRecord.java b/src/org/xbill/DNS/MBRecord.java
new file mode 100644
index 0000000..6b65edf
--- /dev/null
+++ b/src/org/xbill/DNS/MBRecord.java
@@ -0,0 +1,42 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * Mailbox Record - specifies a host containing a mailbox.
+ *
+ * @author Brian Wellington
+ */
+
+public class MBRecord extends SingleNameBase {
+
+private static final long serialVersionUID = 532349543479150419L;
+
+MBRecord() {}
+
+Record
+getObject() {
+ return new MBRecord();
+}
+
+/**
+ * Creates a new MB Record with the given data
+ * @param mailbox The host containing the mailbox for the domain.
+ */
+public
+MBRecord(Name name, int dclass, long ttl, Name mailbox) {
+ super(name, Type.MB, dclass, ttl, mailbox, "mailbox");
+}
+
+/** Gets the mailbox for the domain */
+public Name
+getMailbox() {
+ return getSingleName();
+}
+
+public Name
+getAdditionalName() {
+ return getSingleName();
+}
+
+}
diff --git a/src/org/xbill/DNS/MDRecord.java b/src/org/xbill/DNS/MDRecord.java
new file mode 100644
index 0000000..dbf51af
--- /dev/null
+++ b/src/org/xbill/DNS/MDRecord.java
@@ -0,0 +1,43 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * Mail Destination Record - specifies a mail agent which delivers mail
+ * for a domain (obsolete)
+ *
+ * @author Brian Wellington
+ */
+
+public class MDRecord extends SingleNameBase {
+
+private static final long serialVersionUID = 5268878603762942202L;
+
+MDRecord() {}
+
+Record
+getObject() {
+ return new MDRecord();
+}
+
+/**
+ * Creates a new MD Record with the given data
+ * @param mailAgent The mail agent that delivers mail for the domain.
+ */
+public
+MDRecord(Name name, int dclass, long ttl, Name mailAgent) {
+ super(name, Type.MD, dclass, ttl, mailAgent, "mail agent");
+}
+
+/** Gets the mail agent for the domain */
+public Name
+getMailAgent() {
+ return getSingleName();
+}
+
+public Name
+getAdditionalName() {
+ return getSingleName();
+}
+
+}
diff --git a/src/org/xbill/DNS/MFRecord.java b/src/org/xbill/DNS/MFRecord.java
new file mode 100644
index 0000000..ff293d7
--- /dev/null
+++ b/src/org/xbill/DNS/MFRecord.java
@@ -0,0 +1,43 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * Mail Forwarder Record - specifies a mail agent which forwards mail
+ * for a domain (obsolete)
+ *
+ * @author Brian Wellington
+ */
+
+public class MFRecord extends SingleNameBase {
+
+private static final long serialVersionUID = -6670449036843028169L;
+
+MFRecord() {}
+
+Record
+getObject() {
+ return new MFRecord();
+}
+
+/**
+ * Creates a new MF Record with the given data
+ * @param mailAgent The mail agent that forwards mail for the domain.
+ */
+public
+MFRecord(Name name, int dclass, long ttl, Name mailAgent) {
+ super(name, Type.MF, dclass, ttl, mailAgent, "mail agent");
+}
+
+/** Gets the mail agent for the domain */
+public Name
+getMailAgent() {
+ return getSingleName();
+}
+
+public Name
+getAdditionalName() {
+ return getSingleName();
+}
+
+}
diff --git a/src/org/xbill/DNS/MGRecord.java b/src/org/xbill/DNS/MGRecord.java
new file mode 100644
index 0000000..5752f49
--- /dev/null
+++ b/src/org/xbill/DNS/MGRecord.java
@@ -0,0 +1,38 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * Mail Group Record - specifies a mailbox which is a member of a mail group.
+ *
+ * @author Brian Wellington
+ */
+
+public class MGRecord extends SingleNameBase {
+
+private static final long serialVersionUID = -3980055550863644582L;
+
+MGRecord() {}
+
+Record
+getObject() {
+ return new MGRecord();
+}
+
+/**
+ * Creates a new MG Record with the given data
+ * @param mailbox The mailbox that is a member of the group specified by the
+ * domain.
+ */
+public
+MGRecord(Name name, int dclass, long ttl, Name mailbox) {
+ super(name, Type.MG, dclass, ttl, mailbox, "mailbox");
+}
+
+/** Gets the mailbox in the mail group specified by the domain */
+public Name
+getMailbox() {
+ return getSingleName();
+}
+
+}
diff --git a/src/org/xbill/DNS/MINFORecord.java b/src/org/xbill/DNS/MINFORecord.java
new file mode 100644
index 0000000..4324cda
--- /dev/null
+++ b/src/org/xbill/DNS/MINFORecord.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+
+/**
+ * Mailbox information Record - lists the address responsible for a mailing
+ * list/mailbox and the address to receive error messages relating to the
+ * mailing list/mailbox.
+ *
+ * @author Brian Wellington
+ */
+
+public class MINFORecord extends Record {
+
+private static final long serialVersionUID = -3962147172340353796L;
+
+private Name responsibleAddress;
+private Name errorAddress;
+
+MINFORecord() {}
+
+Record
+getObject() {
+ return new MINFORecord();
+}
+
+/**
+ * Creates an MINFO Record from the given data
+ * @param responsibleAddress The address responsible for the
+ * mailing list/mailbox.
+ * @param errorAddress The address to receive error messages relating to the
+ * mailing list/mailbox.
+ */
+public
+MINFORecord(Name name, int dclass, long ttl,
+ Name responsibleAddress, Name errorAddress)
+{
+ super(name, Type.MINFO, dclass, ttl);
+
+ this.responsibleAddress = checkName("responsibleAddress",
+ responsibleAddress);
+ this.errorAddress = checkName("errorAddress", errorAddress);
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ responsibleAddress = new Name(in);
+ errorAddress = new Name(in);
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ responsibleAddress = st.getName(origin);
+ errorAddress = st.getName(origin);
+}
+
+/** Converts the MINFO Record to a String */
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(responsibleAddress);
+ sb.append(" ");
+ sb.append(errorAddress);
+ return sb.toString();
+}
+
+/** Gets the address responsible for the mailing list/mailbox. */
+public Name
+getResponsibleAddress() {
+ return responsibleAddress;
+}
+
+/**
+ * Gets the address to receive error messages relating to the mailing
+ * list/mailbox.
+ */
+public Name
+getErrorAddress() {
+ return errorAddress;
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ responsibleAddress.toWire(out, null, canonical);
+ errorAddress.toWire(out, null, canonical);
+}
+
+}
diff --git a/src/org/xbill/DNS/MRRecord.java b/src/org/xbill/DNS/MRRecord.java
new file mode 100644
index 0000000..a7ff4fc
--- /dev/null
+++ b/src/org/xbill/DNS/MRRecord.java
@@ -0,0 +1,38 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * Mailbox Rename Record - specifies a rename of a mailbox.
+ *
+ * @author Brian Wellington
+ */
+
+public class MRRecord extends SingleNameBase {
+
+private static final long serialVersionUID = -5617939094209927533L;
+
+MRRecord() {}
+
+Record
+getObject() {
+ return new MRRecord();
+}
+
+/**
+ * Creates a new MR Record with the given data
+ * @param newName The new name of the mailbox specified by the domain.
+ * domain.
+ */
+public
+MRRecord(Name name, int dclass, long ttl, Name newName) {
+ super(name, Type.MR, dclass, ttl, newName, "new name");
+}
+
+/** Gets the new name of the mailbox specified by the domain */
+public Name
+getNewName() {
+ return getSingleName();
+}
+
+}
diff --git a/src/org/xbill/DNS/MXRecord.java b/src/org/xbill/DNS/MXRecord.java
new file mode 100644
index 0000000..111977d
--- /dev/null
+++ b/src/org/xbill/DNS/MXRecord.java
@@ -0,0 +1,57 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * Mail Exchange - specifies where mail to a domain is sent
+ *
+ * @author Brian Wellington
+ */
+
+public class MXRecord extends U16NameBase {
+
+private static final long serialVersionUID = 2914841027584208546L;
+
+MXRecord() {}
+
+Record
+getObject() {
+ return new MXRecord();
+}
+
+/**
+ * Creates an MX Record from the given data
+ * @param priority The priority of this MX. Records with lower priority
+ * are preferred.
+ * @param target The host that mail is sent to
+ */
+public
+MXRecord(Name name, int dclass, long ttl, int priority, Name target) {
+ super(name, Type.MX, dclass, ttl, priority, "priority",
+ target, "target");
+}
+
+/** Returns the target of the MX record */
+public Name
+getTarget() {
+ return getNameField();
+}
+
+/** Returns the priority of this MX record */
+public int
+getPriority() {
+ return getU16Field();
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeU16(u16Field);
+ nameField.toWire(out, c, canonical);
+}
+
+public Name
+getAdditionalName() {
+ return getNameField();
+}
+
+}
diff --git a/src/org/xbill/DNS/Master.java b/src/org/xbill/DNS/Master.java
new file mode 100644
index 0000000..c795a9c
--- /dev/null
+++ b/src/org/xbill/DNS/Master.java
@@ -0,0 +1,427 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * A DNS master file parser. This incrementally parses the file, returning
+ * one record at a time. When directives are seen, they are added to the
+ * state and used when parsing future records.
+ *
+ * @author Brian Wellington
+ */
+
+public class Master {
+
+private Name origin;
+private File file;
+private Record last = null;
+private long defaultTTL;
+private Master included = null;
+private Tokenizer st;
+private int currentType;
+private int currentDClass;
+private long currentTTL;
+private boolean needSOATTL;
+
+private Generator generator;
+private List generators;
+private boolean noExpandGenerate;
+
+Master(File file, Name origin, long initialTTL) throws IOException {
+ if (origin != null && !origin.isAbsolute()) {
+ throw new RelativeNameException(origin);
+ }
+ this.file = file;
+ st = new Tokenizer(file);
+ this.origin = origin;
+ defaultTTL = initialTTL;
+}
+
+/**
+ * Initializes the master file reader and opens the specified master file.
+ * @param filename The master file.
+ * @param origin The initial origin to append to relative names.
+ * @param ttl The initial default TTL.
+ * @throws IOException The master file could not be opened.
+ */
+public
+Master(String filename, Name origin, long ttl) throws IOException {
+ this(new File(filename), origin, ttl);
+}
+
+/**
+ * Initializes the master file reader and opens the specified master file.
+ * @param filename The master file.
+ * @param origin The initial origin to append to relative names.
+ * @throws IOException The master file could not be opened.
+ */
+public
+Master(String filename, Name origin) throws IOException {
+ this(new File(filename), origin, -1);
+}
+
+/**
+ * Initializes the master file reader and opens the specified master file.
+ * @param filename The master file.
+ * @throws IOException The master file could not be opened.
+ */
+public
+Master(String filename) throws IOException {
+ this(new File(filename), null, -1);
+}
+
+/**
+ * Initializes the master file reader.
+ * @param in The input stream containing a master file.
+ * @param origin The initial origin to append to relative names.
+ * @param ttl The initial default TTL.
+ */
+public
+Master(InputStream in, Name origin, long ttl) {
+ if (origin != null && !origin.isAbsolute()) {
+ throw new RelativeNameException(origin);
+ }
+ st = new Tokenizer(in);
+ this.origin = origin;
+ defaultTTL = ttl;
+}
+
+/**
+ * Initializes the master file reader.
+ * @param in The input stream containing a master file.
+ * @param origin The initial origin to append to relative names.
+ */
+public
+Master(InputStream in, Name origin) {
+ this(in, origin, -1);
+}
+
+/**
+ * Initializes the master file reader.
+ * @param in The input stream containing a master file.
+ */
+public
+Master(InputStream in) {
+ this(in, null, -1);
+}
+
+private Name
+parseName(String s, Name origin) throws TextParseException {
+ try {
+ return Name.fromString(s, origin);
+ }
+ catch (TextParseException e) {
+ throw st.exception(e.getMessage());
+ }
+}
+
+private void
+parseTTLClassAndType() throws IOException {
+ String s;
+ boolean seen_class = false;
+
+
+ // This is a bit messy, since any of the following are legal:
+ // class ttl type
+ // ttl class type
+ // class type
+ // ttl type
+ // type
+ seen_class = false;
+ s = st.getString();
+ if ((currentDClass = DClass.value(s)) >= 0) {
+ s = st.getString();
+ seen_class = true;
+ }
+
+ currentTTL = -1;
+ try {
+ currentTTL = TTL.parseTTL(s);
+ s = st.getString();
+ }
+ catch (NumberFormatException e) {
+ if (defaultTTL >= 0)
+ currentTTL = defaultTTL;
+ else if (last != null)
+ currentTTL = last.getTTL();
+ }
+
+ if (!seen_class) {
+ if ((currentDClass = DClass.value(s)) >= 0) {
+ s = st.getString();
+ } else {
+ currentDClass = DClass.IN;
+ }
+ }
+
+ if ((currentType = Type.value(s)) < 0)
+ throw st.exception("Invalid type '" + s + "'");
+
+ // BIND allows a missing TTL for the initial SOA record, and uses
+ // the SOA minimum value. If the SOA is not the first record,
+ // this is an error.
+ if (currentTTL < 0) {
+ if (currentType != Type.SOA)
+ throw st.exception("missing TTL");
+ needSOATTL = true;
+ currentTTL = 0;
+ }
+}
+
+private long
+parseUInt32(String s) {
+ if (!Character.isDigit(s.charAt(0)))
+ return -1;
+ try {
+ long l = Long.parseLong(s);
+ if (l < 0 || l > 0xFFFFFFFFL)
+ return -1;
+ return l;
+ }
+ catch (NumberFormatException e) {
+ return -1;
+ }
+}
+
+private void
+startGenerate() throws IOException {
+ String s;
+ int n;
+
+ // The first field is of the form start-end[/step]
+ // Regexes would be useful here.
+ s = st.getIdentifier();
+ n = s.indexOf("-");
+ if (n < 0)
+ throw st.exception("Invalid $GENERATE range specifier: " + s);
+ String startstr = s.substring(0, n);
+ String endstr = s.substring(n + 1);
+ String stepstr = null;
+ n = endstr.indexOf("/");
+ if (n >= 0) {
+ stepstr = endstr.substring(n + 1);
+ endstr = endstr.substring(0, n);
+ }
+ long start = parseUInt32(startstr);
+ long end = parseUInt32(endstr);
+ long step;
+ if (stepstr != null)
+ step = parseUInt32(stepstr);
+ else
+ step = 1;
+ if (start < 0 || end < 0 || start > end || step <= 0)
+ throw st.exception("Invalid $GENERATE range specifier: " + s);
+
+ // The next field is the name specification.
+ String nameSpec = st.getIdentifier();
+
+ // Then the ttl/class/type, in the same form as a normal record.
+ // Only some types are supported.
+ parseTTLClassAndType();
+ if (!Generator.supportedType(currentType))
+ throw st.exception("$GENERATE does not support " +
+ Type.string(currentType) + " records");
+
+ // Next comes the rdata specification.
+ String rdataSpec = st.getIdentifier();
+
+ // That should be the end. However, we don't want to move past the
+ // line yet, so put back the EOL after reading it.
+ st.getEOL();
+ st.unget();
+
+ generator = new Generator(start, end, step, nameSpec,
+ currentType, currentDClass, currentTTL,
+ rdataSpec, origin);
+ if (generators == null)
+ generators = new ArrayList(1);
+ generators.add(generator);
+}
+
+private void
+endGenerate() throws IOException {
+ // Read the EOL that we put back before.
+ st.getEOL();
+
+ generator = null;
+}
+
+private Record
+nextGenerated() throws IOException {
+ try {
+ return generator.nextRecord();
+ }
+ catch (Tokenizer.TokenizerException e) {
+ throw st.exception("Parsing $GENERATE: " + e.getBaseMessage());
+ }
+ catch (TextParseException e) {
+ throw st.exception("Parsing $GENERATE: " + e.getMessage());
+ }
+}
+
+/**
+ * Returns the next record in the master file. This will process any
+ * directives before the next record.
+ * @return The next record.
+ * @throws IOException The master file could not be read, or was syntactically
+ * invalid.
+ */
+public Record
+_nextRecord() throws IOException {
+ Tokenizer.Token token;
+ String s;
+
+ if (included != null) {
+ Record rec = included.nextRecord();
+ if (rec != null)
+ return rec;
+ included = null;
+ }
+ if (generator != null) {
+ Record rec = nextGenerated();
+ if (rec != null)
+ return rec;
+ endGenerate();
+ }
+ while (true) {
+ Name name;
+
+ token = st.get(true, false);
+ if (token.type == Tokenizer.WHITESPACE) {
+ Tokenizer.Token next = st.get();
+ if (next.type == Tokenizer.EOL)
+ continue;
+ else if (next.type == Tokenizer.EOF)
+ return null;
+ else
+ st.unget();
+ if (last == null)
+ throw st.exception("no owner");
+ name = last.getName();
+ }
+ else if (token.type == Tokenizer.EOL)
+ continue;
+ else if (token.type == Tokenizer.EOF)
+ return null;
+ else if (((String) token.value).charAt(0) == '$') {
+ s = token.value;
+
+ if (s.equalsIgnoreCase("$ORIGIN")) {
+ origin = st.getName(Name.root);
+ st.getEOL();
+ continue;
+ } else if (s.equalsIgnoreCase("$TTL")) {
+ defaultTTL = st.getTTL();
+ st.getEOL();
+ continue;
+ } else if (s.equalsIgnoreCase("$INCLUDE")) {
+ String filename = st.getString();
+ File newfile;
+ if (file != null) {
+ String parent = file.getParent();
+ newfile = new File(parent, filename);
+ } else {
+ newfile = new File(filename);
+ }
+ Name incorigin = origin;
+ token = st.get();
+ if (token.isString()) {
+ incorigin = parseName(token.value,
+ Name.root);
+ st.getEOL();
+ }
+ included = new Master(newfile, incorigin,
+ defaultTTL);
+ /*
+ * If we continued, we wouldn't be looking in
+ * the new file. Recursing works better.
+ */
+ return nextRecord();
+ } else if (s.equalsIgnoreCase("$GENERATE")) {
+ if (generator != null)
+ throw new IllegalStateException
+ ("cannot nest $GENERATE");
+ startGenerate();
+ if (noExpandGenerate) {
+ endGenerate();
+ continue;
+ }
+ return nextGenerated();
+ } else {
+ throw st.exception("Invalid directive: " + s);
+ }
+ } else {
+ s = token.value;
+ name = parseName(s, origin);
+ if (last != null && name.equals(last.getName())) {
+ name = last.getName();
+ }
+ }
+
+ parseTTLClassAndType();
+ last = Record.fromString(name, currentType, currentDClass,
+ currentTTL, st, origin);
+ if (needSOATTL) {
+ long ttl = ((SOARecord)last).getMinimum();
+ last.setTTL(ttl);
+ defaultTTL = ttl;
+ needSOATTL = false;
+ }
+ return last;
+ }
+}
+
+/**
+ * Returns the next record in the master file. This will process any
+ * directives before the next record.
+ * @return The next record.
+ * @throws IOException The master file could not be read, or was syntactically
+ * invalid.
+ */
+public Record
+nextRecord() throws IOException {
+ Record rec = null;
+ try {
+ rec = _nextRecord();
+ }
+ finally {
+ if (rec == null) {
+ st.close();
+ }
+ }
+ return rec;
+}
+
+/**
+ * Specifies whether $GENERATE statements should be expanded. Whether
+ * expanded or not, the specifications for generated records are available
+ * by calling {@link #generators}. This must be called before a $GENERATE
+ * statement is seen during iteration to have an effect.
+ */
+public void
+expandGenerate(boolean wantExpand) {
+ noExpandGenerate = !wantExpand;
+}
+
+/**
+ * Returns an iterator over the generators specified in the master file; that
+ * is, the parsed contents of $GENERATE statements.
+ * @see Generator
+ */
+public Iterator
+generators() {
+ if (generators != null)
+ return Collections.unmodifiableList(generators).iterator();
+ else
+ return Collections.EMPTY_LIST.iterator();
+}
+
+protected void
+finalize() {
+ st.close();
+}
+
+}
diff --git a/src/org/xbill/DNS/Message.java b/src/org/xbill/DNS/Message.java
new file mode 100644
index 0000000..fe0c3c9
--- /dev/null
+++ b/src/org/xbill/DNS/Message.java
@@ -0,0 +1,611 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.util.*;
+import java.io.*;
+
+/**
+ * A DNS Message. A message is the basic unit of communication between
+ * the client and server of a DNS operation. A message consists of a Header
+ * and 4 message sections.
+ * @see Resolver
+ * @see Header
+ * @see Section
+ *
+ * @author Brian Wellington
+ */
+
+public class Message implements Cloneable {
+
+/** The maximum length of a message in wire format. */
+public static final int MAXLENGTH = 65535;
+
+private Header header;
+private List [] sections;
+private int size;
+private TSIG tsigkey;
+private TSIGRecord querytsig;
+private int tsigerror;
+
+int tsigstart;
+int tsigState;
+int sig0start;
+
+/* The message was not signed */
+static final int TSIG_UNSIGNED = 0;
+
+/* The message was signed and verification succeeded */
+static final int TSIG_VERIFIED = 1;
+
+/* The message was an unsigned message in multiple-message response */
+static final int TSIG_INTERMEDIATE = 2;
+
+/* The message was signed and no verification was attempted. */
+static final int TSIG_SIGNED = 3;
+
+/*
+ * The message was signed and verification failed, or was not signed
+ * when it should have been.
+ */
+static final int TSIG_FAILED = 4;
+
+private static Record [] emptyRecordArray = new Record[0];
+private static RRset [] emptyRRsetArray = new RRset[0];
+
+private
+Message(Header header) {
+ sections = new List[4];
+ this.header = header;
+}
+
+/** Creates a new Message with the specified Message ID */
+public
+Message(int id) {
+ this(new Header(id));
+}
+
+/** Creates a new Message with a random Message ID */
+public
+Message() {
+ this(new Header());
+}
+
+/**
+ * Creates a new Message with a random Message ID suitable for sending as a
+ * query.
+ * @param r A record containing the question
+ */
+public static Message
+newQuery(Record r) {
+ Message m = new Message();
+ m.header.setOpcode(Opcode.QUERY);
+ m.header.setFlag(Flags.RD);
+ m.addRecord(r, Section.QUESTION);
+ return m;
+}
+
+/**
+ * Creates a new Message to contain a dynamic update. A random Message ID
+ * and the zone are filled in.
+ * @param zone The zone to be updated
+ */
+public static Message
+newUpdate(Name zone) {
+ return new Update(zone);
+}
+
+Message(DNSInput in) throws IOException {
+ this(new Header(in));
+ boolean isUpdate = (header.getOpcode() == Opcode.UPDATE);
+ boolean truncated = header.getFlag(Flags.TC);
+ try {
+ for (int i = 0; i < 4; i++) {
+ int count = header.getCount(i);
+ if (count > 0)
+ sections[i] = new ArrayList(count);
+ for (int j = 0; j < count; j++) {
+ int pos = in.current();
+ Record rec = Record.fromWire(in, i, isUpdate);
+ sections[i].add(rec);
+ if (i == Section.ADDITIONAL) {
+ if (rec.getType() == Type.TSIG)
+ tsigstart = pos;
+ if (rec.getType() == Type.SIG) {
+ SIGRecord sig = (SIGRecord) rec;
+ if (sig.getTypeCovered() == 0)
+ sig0start = pos;
+ }
+ }
+ }
+ }
+ } catch (WireParseException e) {
+ if (!truncated)
+ throw e;
+ }
+ size = in.current();
+}
+
+/**
+ * Creates a new Message from its DNS wire format representation
+ * @param b A byte array containing the DNS Message.
+ */
+public
+Message(byte [] b) throws IOException {
+ this(new DNSInput(b));
+}
+
+/**
+ * Replaces the Header with a new one.
+ * @see Header
+ */
+public void
+setHeader(Header h) {
+ header = h;
+}
+
+/**
+ * Retrieves the Header.
+ * @see Header
+ */
+public Header
+getHeader() {
+ return header;
+}
+
+/**
+ * Adds a record to a section of the Message, and adjusts the header.
+ * @see Record
+ * @see Section
+ */
+public void
+addRecord(Record r, int section) {
+ if (sections[section] == null)
+ sections[section] = new LinkedList();
+ header.incCount(section);
+ sections[section].add(r);
+}
+
+/**
+ * Removes a record from a section of the Message, and adjusts the header.
+ * @see Record
+ * @see Section
+ */
+public boolean
+removeRecord(Record r, int section) {
+ if (sections[section] != null && sections[section].remove(r)) {
+ header.decCount(section);
+ return true;
+ }
+ else
+ return false;
+}
+
+/**
+ * Removes all records from a section of the Message, and adjusts the header.
+ * @see Record
+ * @see Section
+ */
+public void
+removeAllRecords(int section) {
+ sections[section] = null;
+ header.setCount(section, 0);
+}
+
+/**
+ * Determines if the given record is already present in the given section.
+ * @see Record
+ * @see Section
+ */
+public boolean
+findRecord(Record r, int section) {
+ return (sections[section] != null && sections[section].contains(r));
+}
+
+/**
+ * Determines if the given record is already present in any section.
+ * @see Record
+ * @see Section
+ */
+public boolean
+findRecord(Record r) {
+ for (int i = Section.ANSWER; i <= Section.ADDITIONAL; i++)
+ if (sections[i] != null && sections[i].contains(r))
+ return true;
+ return false;
+}
+
+/**
+ * Determines if an RRset with the given name and type is already
+ * present in the given section.
+ * @see RRset
+ * @see Section
+ */
+public boolean
+findRRset(Name name, int type, int section) {
+ if (sections[section] == null)
+ return false;
+ for (int i = 0; i < sections[section].size(); i++) {
+ Record r = (Record) sections[section].get(i);
+ if (r.getType() == type && name.equals(r.getName()))
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Determines if an RRset with the given name and type is already
+ * present in any section.
+ * @see RRset
+ * @see Section
+ */
+public boolean
+findRRset(Name name, int type) {
+ return (findRRset(name, type, Section.ANSWER) ||
+ findRRset(name, type, Section.AUTHORITY) ||
+ findRRset(name, type, Section.ADDITIONAL));
+}
+
+/**
+ * Returns the first record in the QUESTION section.
+ * @see Record
+ * @see Section
+ */
+public Record
+getQuestion() {
+ List l = sections[Section.QUESTION];
+ if (l == null || l.size() == 0)
+ return null;
+ return (Record) l.get(0);
+}
+
+/**
+ * Returns the TSIG record from the ADDITIONAL section, if one is present.
+ * @see TSIGRecord
+ * @see TSIG
+ * @see Section
+ */
+public TSIGRecord
+getTSIG() {
+ int count = header.getCount(Section.ADDITIONAL);
+ if (count == 0)
+ return null;
+ List l = sections[Section.ADDITIONAL];
+ Record rec = (Record) l.get(count - 1);
+ if (rec.type != Type.TSIG)
+ return null;
+ return (TSIGRecord) rec;
+}
+
+/**
+ * Was this message signed by a TSIG?
+ * @see TSIG
+ */
+public boolean
+isSigned() {
+ return (tsigState == TSIG_SIGNED ||
+ tsigState == TSIG_VERIFIED ||
+ tsigState == TSIG_FAILED);
+}
+
+/**
+ * If this message was signed by a TSIG, was the TSIG verified?
+ * @see TSIG
+ */
+public boolean
+isVerified() {
+ return (tsigState == TSIG_VERIFIED);
+}
+
+/**
+ * Returns the OPT record from the ADDITIONAL section, if one is present.
+ * @see OPTRecord
+ * @see Section
+ */
+public OPTRecord
+getOPT() {
+ Record [] additional = getSectionArray(Section.ADDITIONAL);
+ for (int i = 0; i < additional.length; i++)
+ if (additional[i] instanceof OPTRecord)
+ return (OPTRecord) additional[i];
+ return null;
+}
+
+/**
+ * Returns the message's rcode (error code). This incorporates the EDNS
+ * extended rcode.
+ */
+public int
+getRcode() {
+ int rcode = header.getRcode();
+ OPTRecord opt = getOPT();
+ if (opt != null)
+ rcode += (opt.getExtendedRcode() << 4);
+ return rcode;
+}
+
+/**
+ * Returns an array containing all records in the given section, or an
+ * empty array if the section is empty.
+ * @see Record
+ * @see Section
+ */
+public Record []
+getSectionArray(int section) {
+ if (sections[section] == null)
+ return emptyRecordArray;
+ List l = sections[section];
+ return (Record []) l.toArray(new Record[l.size()]);
+}
+
+private static boolean
+sameSet(Record r1, Record r2) {
+ return (r1.getRRsetType() == r2.getRRsetType() &&
+ r1.getDClass() == r2.getDClass() &&
+ r1.getName().equals(r2.getName()));
+}
+
+/**
+ * Returns an array containing all records in the given section grouped into
+ * RRsets.
+ * @see RRset
+ * @see Section
+ */
+public RRset []
+getSectionRRsets(int section) {
+ if (sections[section] == null)
+ return emptyRRsetArray;
+ List sets = new LinkedList();
+ Record [] recs = getSectionArray(section);
+ Set hash = new HashSet();
+ for (int i = 0; i < recs.length; i++) {
+ Name name = recs[i].getName();
+ boolean newset = true;
+ if (hash.contains(name)) {
+ for (int j = sets.size() - 1; j >= 0; j--) {
+ RRset set = (RRset) sets.get(j);
+ if (set.getType() == recs[i].getRRsetType() &&
+ set.getDClass() == recs[i].getDClass() &&
+ set.getName().equals(name))
+ {
+ set.addRR(recs[i]);
+ newset = false;
+ break;
+ }
+ }
+ }
+ if (newset) {
+ RRset set = new RRset(recs[i]);
+ sets.add(set);
+ hash.add(name);
+ }
+ }
+ return (RRset []) sets.toArray(new RRset[sets.size()]);
+}
+
+void
+toWire(DNSOutput out) {
+ header.toWire(out);
+ Compression c = new Compression();
+ for (int i = 0; i < 4; i++) {
+ if (sections[i] == null)
+ continue;
+ for (int j = 0; j < sections[i].size(); j++) {
+ Record rec = (Record)sections[i].get(j);
+ rec.toWire(out, i, c);
+ }
+ }
+}
+
+/* Returns the number of records not successfully rendered. */
+private int
+sectionToWire(DNSOutput out, int section, Compression c,
+ int maxLength)
+{
+ int n = sections[section].size();
+ int pos = out.current();
+ int rendered = 0;
+ Record lastrec = null;
+
+ for (int i = 0; i < n; i++) {
+ Record rec = (Record)sections[section].get(i);
+ if (lastrec != null && !sameSet(rec, lastrec)) {
+ pos = out.current();
+ rendered = i;
+ }
+ lastrec = rec;
+ rec.toWire(out, section, c);
+ if (out.current() > maxLength) {
+ out.jump(pos);
+ return n - rendered;
+ }
+ }
+ return 0;
+}
+
+/* Returns true if the message could be rendered. */
+private boolean
+toWire(DNSOutput out, int maxLength) {
+ if (maxLength < Header.LENGTH)
+ return false;
+
+ Header newheader = null;
+
+ int tempMaxLength = maxLength;
+ if (tsigkey != null)
+ tempMaxLength -= tsigkey.recordLength();
+
+ int startpos = out.current();
+ header.toWire(out);
+ Compression c = new Compression();
+ for (int i = 0; i < 4; i++) {
+ int skipped;
+ if (sections[i] == null)
+ continue;
+ skipped = sectionToWire(out, i, c, tempMaxLength);
+ if (skipped != 0) {
+ if (newheader == null)
+ newheader = (Header) header.clone();
+ if (i != Section.ADDITIONAL)
+ newheader.setFlag(Flags.TC);
+ int count = newheader.getCount(i);
+ newheader.setCount(i, count - skipped);
+ for (int j = i + 1; j < 4; j++)
+ newheader.setCount(j, 0);
+
+ out.save();
+ out.jump(startpos);
+ newheader.toWire(out);
+ out.restore();
+ break;
+ }
+ }
+
+ if (tsigkey != null) {
+ TSIGRecord tsigrec = tsigkey.generate(this, out.toByteArray(),
+ tsigerror, querytsig);
+
+ if (newheader == null)
+ newheader = (Header) header.clone();
+ tsigrec.toWire(out, Section.ADDITIONAL, c);
+ newheader.incCount(Section.ADDITIONAL);
+
+ out.save();
+ out.jump(startpos);
+ newheader.toWire(out);
+ out.restore();
+ }
+
+ return true;
+}
+
+/**
+ * Returns an array containing the wire format representation of the Message.
+ */
+public byte []
+toWire() {
+ DNSOutput out = new DNSOutput();
+ toWire(out);
+ size = out.current();
+ return out.toByteArray();
+}
+
+/**
+ * Returns an array containing the wire format representation of the Message
+ * with the specified maximum length. This will generate a truncated
+ * message (with the TC bit) if the message doesn't fit, and will also
+ * sign the message with the TSIG key set by a call to setTSIG(). This
+ * method may return null if the message could not be rendered at all; this
+ * could happen if maxLength is smaller than a DNS header, for example.
+ * @param maxLength The maximum length of the message.
+ * @return The wire format of the message, or null if the message could not be
+ * rendered into the specified length.
+ * @see Flags
+ * @see TSIG
+ */
+public byte []
+toWire(int maxLength) {
+ DNSOutput out = new DNSOutput();
+ toWire(out, maxLength);
+ size = out.current();
+ return out.toByteArray();
+}
+
+/**
+ * Sets the TSIG key and other necessary information to sign a message.
+ * @param key The TSIG key.
+ * @param error The value of the TSIG error field.
+ * @param querytsig If this is a response, the TSIG from the request.
+ */
+public void
+setTSIG(TSIG key, int error, TSIGRecord querytsig) {
+ this.tsigkey = key;
+ this.tsigerror = error;
+ this.querytsig = querytsig;
+}
+
+/**
+ * Returns the size of the message. Only valid if the message has been
+ * converted to or from wire format.
+ */
+public int
+numBytes() {
+ return size;
+}
+
+/**
+ * Converts the given section of the Message to a String.
+ * @see Section
+ */
+public String
+sectionToString(int i) {
+ if (i > 3)
+ return null;
+
+ StringBuffer sb = new StringBuffer();
+
+ Record [] records = getSectionArray(i);
+ for (int j = 0; j < records.length; j++) {
+ Record rec = records[j];
+ if (i == Section.QUESTION) {
+ sb.append(";;\t" + rec.name);
+ sb.append(", type = " + Type.string(rec.type));
+ sb.append(", class = " + DClass.string(rec.dclass));
+ }
+ else
+ sb.append(rec);
+ sb.append("\n");
+ }
+ return sb.toString();
+}
+
+/**
+ * Converts the Message to a String.
+ */
+public String
+toString() {
+ StringBuffer sb = new StringBuffer();
+ OPTRecord opt = getOPT();
+ if (opt != null)
+ sb.append(header.toStringWithRcode(getRcode()) + "\n");
+ else
+ sb.append(header + "\n");
+ if (isSigned()) {
+ sb.append(";; TSIG ");
+ if (isVerified())
+ sb.append("ok");
+ else
+ sb.append("invalid");
+ sb.append('\n');
+ }
+ for (int i = 0; i < 4; i++) {
+ if (header.getOpcode() != Opcode.UPDATE)
+ sb.append(";; " + Section.longString(i) + ":\n");
+ else
+ sb.append(";; " + Section.updString(i) + ":\n");
+ sb.append(sectionToString(i) + "\n");
+ }
+ sb.append(";; Message size: " + numBytes() + " bytes");
+ return sb.toString();
+}
+
+/**
+ * Creates a copy of this Message. This is done by the Resolver before adding
+ * TSIG and OPT records, for example.
+ * @see Resolver
+ * @see TSIGRecord
+ * @see OPTRecord
+ */
+public Object
+clone() {
+ Message m = new Message();
+ for (int i = 0; i < sections.length; i++) {
+ if (sections[i] != null)
+ m.sections[i] = new LinkedList(sections[i]);
+ }
+ m.header = (Header) header.clone();
+ m.size = size;
+ return m;
+}
+
+}
diff --git a/src/org/xbill/DNS/Mnemonic.java b/src/org/xbill/DNS/Mnemonic.java
new file mode 100644
index 0000000..dd60f62
--- /dev/null
+++ b/src/org/xbill/DNS/Mnemonic.java
@@ -0,0 +1,210 @@
+// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.util.HashMap;
+
+/**
+ * A utility class for converting between numeric codes and mnemonics
+ * for those codes. Mnemonics are case insensitive.
+ *
+ * @author Brian Wellington
+ */
+
+class Mnemonic {
+
+private static Integer cachedInts[] = new Integer[64];
+
+static {
+ for (int i = 0; i < cachedInts.length; i++) {
+ cachedInts[i] = new Integer(i);
+ }
+}
+
+/* Strings are case-sensitive. */
+static final int CASE_SENSITIVE = 1;
+
+/* Strings will be stored/searched for in uppercase. */
+static final int CASE_UPPER = 2;
+
+/* Strings will be stored/searched for in lowercase. */
+static final int CASE_LOWER = 3;
+
+private HashMap strings;
+private HashMap values;
+private String description;
+private int wordcase;
+private String prefix;
+private int max;
+private boolean numericok;
+
+/**
+ * Creates a new Mnemonic table.
+ * @param description A short description of the mnemonic to use when
+ * @param wordcase Whether to convert strings into uppercase, lowercase,
+ * or leave them unchanged.
+ * throwing exceptions.
+ */
+public
+Mnemonic(String description, int wordcase) {
+ this.description = description;
+ this.wordcase = wordcase;
+ strings = new HashMap();
+ values = new HashMap();
+ max = Integer.MAX_VALUE;
+}
+
+/** Sets the maximum numeric value */
+public void
+setMaximum(int max) {
+ this.max = max;
+}
+
+/**
+ * Sets the prefix to use when converting to and from values that don't
+ * have mnemonics.
+ */
+public void
+setPrefix(String prefix) {
+ this.prefix = sanitize(prefix);
+}
+
+/**
+ * Sets whether numeric values stored in strings are acceptable.
+ */
+public void
+setNumericAllowed(boolean numeric) {
+ this.numericok = numeric;
+}
+
+/**
+ * Converts an int into a possibly cached Integer.
+ */
+public static Integer
+toInteger(int val) {
+ if (val >= 0 && val < cachedInts.length)
+ return (cachedInts[val]);
+ return new Integer(val);
+}
+
+/**
+ * Checks that a numeric value is within the range [0..max]
+ */
+public void
+check(int val) {
+ if (val < 0 || val > max) {
+ throw new IllegalArgumentException(description + " " + val +
+ "is out of range");
+ }
+}
+
+/* Converts a String to the correct case. */
+private String
+sanitize(String str) {
+ if (wordcase == CASE_UPPER)
+ return str.toUpperCase();
+ else if (wordcase == CASE_LOWER)
+ return str.toLowerCase();
+ return str;
+}
+
+private int
+parseNumeric(String s) {
+ try {
+ int val = Integer.parseInt(s);
+ if (val >= 0 && val <= max)
+ return val;
+ }
+ catch (NumberFormatException e) {
+ }
+ return -1;
+}
+
+/**
+ * Defines the text representation of a numeric value.
+ * @param val The numeric value
+ * @param string The text string
+ */
+public void
+add(int val, String str) {
+ check(val);
+ Integer value = toInteger(val);
+ str = sanitize(str);
+ strings.put(str, value);
+ values.put(value, str);
+}
+
+/**
+ * Defines an additional text representation of a numeric value. This will
+ * be used by getValue(), but not getText().
+ * @param val The numeric value
+ * @param string The text string
+ */
+public void
+addAlias(int val, String str) {
+ check(val);
+ Integer value = toInteger(val);
+ str = sanitize(str);
+ strings.put(str, value);
+}
+
+/**
+ * Copies all mnemonics from one table into another.
+ * @param val The numeric value
+ * @param string The text string
+ * @throws IllegalArgumentException The wordcases of the Mnemonics do not
+ * match.
+ */
+public void
+addAll(Mnemonic source) {
+ if (wordcase != source.wordcase)
+ throw new IllegalArgumentException(source.description +
+ ": wordcases do not match");
+ strings.putAll(source.strings);
+ values.putAll(source.values);
+}
+
+/**
+ * Gets the text mnemonic corresponding to a numeric value.
+ * @param val The numeric value
+ * @return The corresponding text mnemonic.
+ */
+public String
+getText(int val) {
+ check(val);
+ String str = (String) values.get(toInteger(val));
+ if (str != null)
+ return str;
+ str = Integer.toString(val);
+ if (prefix != null)
+ return prefix + str;
+ return str;
+}
+
+/**
+ * Gets the numeric value corresponding to a text mnemonic.
+ * @param str The text mnemonic
+ * @return The corresponding numeric value, or -1 if there is none
+ */
+public int
+getValue(String str) {
+ str = sanitize(str);
+ Integer value = (Integer) strings.get(str);
+ if (value != null) {
+ return value.intValue();
+ }
+ if (prefix != null) {
+ if (str.startsWith(prefix)) {
+ int val = parseNumeric(str.substring(prefix.length()));
+ if (val >= 0) {
+ return val;
+ }
+ }
+ }
+ if (numericok) {
+ return parseNumeric(str);
+ }
+ return -1;
+}
+
+}
diff --git a/src/org/xbill/DNS/NAPTRRecord.java b/src/org/xbill/DNS/NAPTRRecord.java
new file mode 100644
index 0000000..da2ec6d
--- /dev/null
+++ b/src/org/xbill/DNS/NAPTRRecord.java
@@ -0,0 +1,154 @@
+// Copyright (c) 2000-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+
+/**
+ * Name Authority Pointer Record - specifies rewrite rule, that when applied
+ * to an existing string will produce a new domain.
+ *
+ * @author Chuck Santos
+ */
+
+public class NAPTRRecord extends Record {
+
+private static final long serialVersionUID = 5191232392044947002L;
+
+private int order, preference;
+private byte [] flags, service, regexp;
+private Name replacement;
+
+NAPTRRecord() {}
+
+Record
+getObject() {
+ return new NAPTRRecord();
+}
+
+/**
+ * Creates an NAPTR Record from the given data
+ * @param order The order of this NAPTR. Records with lower order are
+ * preferred.
+ * @param preference The preference, used to select between records at the
+ * same order.
+ * @param flags The control aspects of the NAPTRRecord.
+ * @param service The service or protocol available down the rewrite path.
+ * @param regexp The regular/substitution expression.
+ * @param replacement The domain-name to query for the next DNS resource
+ * record, depending on the value of the flags field.
+ * @throws IllegalArgumentException One of the strings has invalid escapes
+ */
+public
+NAPTRRecord(Name name, int dclass, long ttl, int order, int preference,
+ String flags, String service, String regexp, Name replacement)
+{
+ super(name, Type.NAPTR, dclass, ttl);
+ this.order = checkU16("order", order);
+ this.preference = checkU16("preference", preference);
+ try {
+ this.flags = byteArrayFromString(flags);
+ this.service = byteArrayFromString(service);
+ this.regexp = byteArrayFromString(regexp);
+ }
+ catch (TextParseException e) {
+ throw new IllegalArgumentException(e.getMessage());
+ }
+ this.replacement = checkName("replacement", replacement);
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ order = in.readU16();
+ preference = in.readU16();
+ flags = in.readCountedString();
+ service = in.readCountedString();
+ regexp = in.readCountedString();
+ replacement = new Name(in);
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ order = st.getUInt16();
+ preference = st.getUInt16();
+ try {
+ flags = byteArrayFromString(st.getString());
+ service = byteArrayFromString(st.getString());
+ regexp = byteArrayFromString(st.getString());
+ }
+ catch (TextParseException e) {
+ throw st.exception(e.getMessage());
+ }
+ replacement = st.getName(origin);
+}
+
+/** Converts rdata to a String */
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(order);
+ sb.append(" ");
+ sb.append(preference);
+ sb.append(" ");
+ sb.append(byteArrayToString(flags, true));
+ sb.append(" ");
+ sb.append(byteArrayToString(service, true));
+ sb.append(" ");
+ sb.append(byteArrayToString(regexp, true));
+ sb.append(" ");
+ sb.append(replacement);
+ return sb.toString();
+}
+
+/** Returns the order */
+public int
+getOrder() {
+ return order;
+}
+
+/** Returns the preference */
+public int
+getPreference() {
+ return preference;
+}
+
+/** Returns flags */
+public String
+getFlags() {
+ return byteArrayToString(flags, false);
+}
+
+/** Returns service */
+public String
+getService() {
+ return byteArrayToString(service, false);
+}
+
+/** Returns regexp */
+public String
+getRegexp() {
+ return byteArrayToString(regexp, false);
+}
+
+/** Returns the replacement domain-name */
+public Name
+getReplacement() {
+ return replacement;
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeU16(order);
+ out.writeU16(preference);
+ out.writeCountedString(flags);
+ out.writeCountedString(service);
+ out.writeCountedString(regexp);
+ replacement.toWire(out, null, canonical);
+}
+
+public Name
+getAdditionalName() {
+ return replacement;
+}
+
+}
diff --git a/src/org/xbill/DNS/NSAPRecord.java b/src/org/xbill/DNS/NSAPRecord.java
new file mode 100644
index 0000000..a6b2031
--- /dev/null
+++ b/src/org/xbill/DNS/NSAPRecord.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import org.xbill.DNS.utils.*;
+
+/**
+ * NSAP Address Record.
+ *
+ * @author Brian Wellington
+ */
+
+public class NSAPRecord extends Record {
+
+private static final long serialVersionUID = -1037209403185658593L;
+
+private byte [] address;
+
+NSAPRecord() {}
+
+Record
+getObject() {
+ return new NSAPRecord();
+}
+
+private static final byte []
+checkAndConvertAddress(String address) {
+ if (!address.substring(0, 2).equalsIgnoreCase("0x")) {
+ return null;
+ }
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ boolean partial = false;
+ int current = 0;
+ for (int i = 2; i < address.length(); i++) {
+ char c = address.charAt(i);
+ if (c == '.') {
+ continue;
+ }
+ int value = Character.digit(c, 16);
+ if (value == -1) {
+ return null;
+ }
+ if (partial) {
+ current += value;
+ bytes.write(current);
+ partial = false;
+ } else {
+ current = value << 4;
+ partial = true;
+ }
+
+ }
+ if (partial) {
+ return null;
+ }
+ return bytes.toByteArray();
+}
+
+/**
+ * Creates an NSAP Record from the given data
+ * @param address The NSAP address.
+ * @throws IllegalArgumentException The address is not a valid NSAP address.
+ */
+public
+NSAPRecord(Name name, int dclass, long ttl, String address) {
+ super(name, Type.NSAP, dclass, ttl);
+ this.address = checkAndConvertAddress(address);
+ if (this.address == null) {
+ throw new IllegalArgumentException("invalid NSAP address " +
+ address);
+ }
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ address = in.readByteArray();
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ String addr = st.getString();
+ this.address = checkAndConvertAddress(addr);
+ if (this.address == null)
+ throw st.exception("invalid NSAP address " + addr);
+}
+
+/**
+ * Returns the NSAP address.
+ */
+public String
+getAddress() {
+ return byteArrayToString(address, false);
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeByteArray(address);
+}
+
+String
+rrToString() {
+ return "0x" + base16.toString(address);
+}
+
+}
diff --git a/src/org/xbill/DNS/NSAP_PTRRecord.java b/src/org/xbill/DNS/NSAP_PTRRecord.java
new file mode 100644
index 0000000..ecc609f
--- /dev/null
+++ b/src/org/xbill/DNS/NSAP_PTRRecord.java
@@ -0,0 +1,38 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * NSAP Pointer Record - maps a domain name representing an NSAP Address to
+ * a hostname.
+ *
+ * @author Brian Wellington
+ */
+
+public class NSAP_PTRRecord extends SingleNameBase {
+
+private static final long serialVersionUID = 2386284746382064904L;
+
+NSAP_PTRRecord() {}
+
+Record
+getObject() {
+ return new NSAP_PTRRecord();
+}
+
+/**
+ * Creates a new NSAP_PTR Record with the given data
+ * @param target The name of the host with this address
+ */
+public
+NSAP_PTRRecord(Name name, int dclass, long ttl, Name target) {
+ super(name, Type.NSAP_PTR, dclass, ttl, target, "target");
+}
+
+/** Gets the target of the NSAP_PTR Record */
+public Name
+getTarget() {
+ return getSingleName();
+}
+
+}
diff --git a/src/org/xbill/DNS/NSEC3PARAMRecord.java b/src/org/xbill/DNS/NSEC3PARAMRecord.java
new file mode 100644
index 0000000..d663a62
--- /dev/null
+++ b/src/org/xbill/DNS/NSEC3PARAMRecord.java
@@ -0,0 +1,165 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+
+import org.xbill.DNS.utils.base16;
+
+/**
+ * Next SECure name 3 Parameters - this record contains the parameters (hash
+ * algorithm, salt, iterations) used for a valid, complete NSEC3 chain present
+ * in a zone. Zones signed using NSEC3 must include this record at the zone apex
+ * to inform authoritative servers that NSEC3 is being used with the given
+ * parameters.
+ *
+ * @author Brian Wellington
+ * @author David Blacka
+ */
+
+public class NSEC3PARAMRecord extends Record {
+
+private static final long serialVersionUID = -8689038598776316533L;
+
+private int hashAlg;
+private int flags;
+private int iterations;
+private byte salt[];
+
+NSEC3PARAMRecord() {}
+
+Record getObject() {
+ return new NSEC3PARAMRecord();
+}
+
+/**
+ * Creates an NSEC3PARAM record from the given data.
+ *
+ * @param name The ownername of the NSEC3PARAM record (generally the zone name).
+ * @param dclass The class.
+ * @param ttl The TTL.
+ * @param hashAlg The hash algorithm.
+ * @param flags The value of the flags field.
+ * @param iterations The number of hash iterations.
+ * @param salt The salt to use (may be null).
+ */
+public NSEC3PARAMRecord(Name name, int dclass, long ttl, int hashAlg,
+ int flags, int iterations, byte [] salt)
+{
+ super(name, Type.NSEC3PARAM, dclass, ttl);
+ this.hashAlg = checkU8("hashAlg", hashAlg);
+ this.flags = checkU8("flags", flags);
+ this.iterations = checkU16("iterations", iterations);
+
+ if (salt != null) {
+ if (salt.length > 255)
+ throw new IllegalArgumentException("Invalid salt " +
+ "length");
+ if (salt.length > 0) {
+ this.salt = new byte[salt.length];
+ System.arraycopy(salt, 0, this.salt, 0, salt.length);
+ }
+ }
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ hashAlg = in.readU8();
+ flags = in.readU8();
+ iterations = in.readU16();
+
+ int salt_length = in.readU8();
+ if (salt_length > 0)
+ salt = in.readByteArray(salt_length);
+ else
+ salt = null;
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeU8(hashAlg);
+ out.writeU8(flags);
+ out.writeU16(iterations);
+
+ if (salt != null) {
+ out.writeU8(salt.length);
+ out.writeByteArray(salt);
+ } else
+ out.writeU8(0);
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException
+{
+ hashAlg = st.getUInt8();
+ flags = st.getUInt8();
+ iterations = st.getUInt16();
+
+ String s = st.getString();
+ if (s.equals("-"))
+ salt = null;
+ else {
+ st.unget();
+ salt = st.getHexString();
+ if (salt.length > 255)
+ throw st.exception("salt value too long");
+ }
+}
+
+/** Converts rdata to a String */
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(hashAlg);
+ sb.append(' ');
+ sb.append(flags);
+ sb.append(' ');
+ sb.append(iterations);
+ sb.append(' ');
+ if (salt == null)
+ sb.append('-');
+ else
+ sb.append(base16.toString(salt));
+
+ return sb.toString();
+}
+
+/** Returns the hash algorithm */
+public int
+getHashAlgorithm() {
+ return hashAlg;
+}
+
+/** Returns the flags */
+public int
+getFlags() {
+ return flags;
+}
+
+/** Returns the number of iterations */
+public int
+getIterations() {
+ return iterations;
+}
+
+/** Returns the salt */
+public byte []
+getSalt()
+{
+ return salt;
+}
+
+/**
+ * Hashes a name with the parameters of this NSEC3PARAM record.
+ * @param name The name to hash
+ * @return The hashed version of the name
+ * @throws NoSuchAlgorithmException The hash algorithm is unknown.
+ */
+public byte []
+hashName(Name name) throws NoSuchAlgorithmException
+{
+ return NSEC3Record.hashName(name, hashAlg, iterations, salt);
+}
+
+}
diff --git a/src/org/xbill/DNS/NSEC3Record.java b/src/org/xbill/DNS/NSEC3Record.java
new file mode 100644
index 0000000..aa086b8
--- /dev/null
+++ b/src/org/xbill/DNS/NSEC3Record.java
@@ -0,0 +1,266 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.security.*;
+
+import org.xbill.DNS.utils.*;
+
+/**
+ * Next SECure name 3 - this record contains the next hashed name in an
+ * ordered list of hashed names in the zone, and a set of types for which
+ * records exist for this name. The presence of this record in a response
+ * signifies a negative response from a DNSSEC-signed zone.
+ *
+ * This replaces the NSEC and NXT records, when used.
+ *
+ * @author Brian Wellington
+ * @author David Blacka
+ */
+
+public class NSEC3Record extends Record {
+
+public static class Flags {
+ /**
+ * NSEC3 flags identifiers.
+ */
+
+ private Flags() {}
+
+ /** Unsigned delegation are not included in the NSEC3 chain.
+ *
+ */
+ public static final int OPT_OUT = 0x01;
+}
+
+public static class Digest {
+ private Digest() {}
+
+ /** SHA-1 */
+ public static final int SHA1 = 1;
+}
+
+public static final int SHA1_DIGEST_ID = Digest.SHA1;
+
+private static final long serialVersionUID = -7123504635968932855L;
+
+private int hashAlg;
+private int flags;
+private int iterations;
+private byte [] salt;
+private byte [] next;
+private TypeBitmap types;
+
+private static final base32 b32 = new base32(base32.Alphabet.BASE32HEX,
+ false, false);
+
+NSEC3Record() {}
+
+Record getObject() {
+ return new NSEC3Record();
+}
+
+/**
+ * Creates an NSEC3 record from the given data.
+ *
+ * @param name The ownername of the NSEC3 record (base32'd hash plus zonename).
+ * @param dclass The class.
+ * @param ttl The TTL.
+ * @param hashAlg The hash algorithm.
+ * @param flags The value of the flags field.
+ * @param iterations The number of hash iterations.
+ * @param salt The salt to use (may be null).
+ * @param next The next hash (may not be null).
+ * @param types The types present at the original ownername.
+ */
+public NSEC3Record(Name name, int dclass, long ttl, int hashAlg,
+ int flags, int iterations, byte [] salt, byte [] next,
+ int [] types)
+{
+ super(name, Type.NSEC3, dclass, ttl);
+ this.hashAlg = checkU8("hashAlg", hashAlg);
+ this.flags = checkU8("flags", flags);
+ this.iterations = checkU16("iterations", iterations);
+
+ if (salt != null) {
+ if (salt.length > 255)
+ throw new IllegalArgumentException("Invalid salt");
+ if (salt.length > 0) {
+ this.salt = new byte[salt.length];
+ System.arraycopy(salt, 0, this.salt, 0, salt.length);
+ }
+ }
+
+ if (next.length > 255) {
+ throw new IllegalArgumentException("Invalid next hash");
+ }
+ this.next = new byte[next.length];
+ System.arraycopy(next, 0, this.next, 0, next.length);
+ this.types = new TypeBitmap(types);
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ hashAlg = in.readU8();
+ flags = in.readU8();
+ iterations = in.readU16();
+
+ int salt_length = in.readU8();
+ if (salt_length > 0)
+ salt = in.readByteArray(salt_length);
+ else
+ salt = null;
+
+ int next_length = in.readU8();
+ next = in.readByteArray(next_length);
+ types = new TypeBitmap(in);
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeU8(hashAlg);
+ out.writeU8(flags);
+ out.writeU16(iterations);
+
+ if (salt != null) {
+ out.writeU8(salt.length);
+ out.writeByteArray(salt);
+ } else
+ out.writeU8(0);
+
+ out.writeU8(next.length);
+ out.writeByteArray(next);
+ types.toWire(out);
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ hashAlg = st.getUInt8();
+ flags = st.getUInt8();
+ iterations = st.getUInt16();
+
+ String s = st.getString();
+ if (s.equals("-"))
+ salt = null;
+ else {
+ st.unget();
+ salt = st.getHexString();
+ if (salt.length > 255)
+ throw st.exception("salt value too long");
+ }
+
+ next = st.getBase32String(b32);
+ types = new TypeBitmap(st);
+}
+
+/** Converts rdata to a String */
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(hashAlg);
+ sb.append(' ');
+ sb.append(flags);
+ sb.append(' ');
+ sb.append(iterations);
+ sb.append(' ');
+ if (salt == null)
+ sb.append('-');
+ else
+ sb.append(base16.toString(salt));
+ sb.append(' ');
+ sb.append(b32.toString(next));
+
+ if (!types.empty()) {
+ sb.append(' ');
+ sb.append(types.toString());
+ }
+
+ return sb.toString();
+}
+
+/** Returns the hash algorithm */
+public int
+getHashAlgorithm() {
+ return hashAlg;
+}
+
+/** Returns the flags */
+public int
+getFlags() {
+ return flags;
+}
+
+/** Returns the number of iterations */
+public int
+getIterations() {
+ return iterations;
+}
+
+/** Returns the salt */
+public byte []
+getSalt()
+{
+ return salt;
+}
+
+/** Returns the next hash */
+public byte []
+getNext() {
+ return next;
+}
+
+ /** Returns the set of types defined for this name */
+public int []
+getTypes() {
+ return types.toArray();
+}
+
+/** Returns whether a specific type is in the set of types. */
+public boolean
+hasType(int type)
+{
+ return types.contains(type);
+}
+
+static byte []
+hashName(Name name, int hashAlg, int iterations, byte [] salt)
+throws NoSuchAlgorithmException
+{
+ MessageDigest digest;
+ switch (hashAlg) {
+ case Digest.SHA1:
+ digest = MessageDigest.getInstance("sha-1");
+ break;
+ default:
+ throw new NoSuchAlgorithmException("Unknown NSEC3 algorithm" +
+ "identifier: " +
+ hashAlg);
+ }
+ byte [] hash = null;
+ for (int i = 0; i <= iterations; i++) {
+ digest.reset();
+ if (i == 0)
+ digest.update(name.toWireCanonical());
+ else
+ digest.update(hash);
+ if (salt != null)
+ digest.update(salt);
+ hash = digest.digest();
+ }
+ return hash;
+}
+
+/**
+ * Hashes a name with the parameters of this NSEC3 record.
+ * @param name The name to hash
+ * @return The hashed version of the name
+ * @throws NoSuchAlgorithmException The hash algorithm is unknown.
+ */
+public byte []
+hashName(Name name) throws NoSuchAlgorithmException
+{
+ return hashName(name, hashAlg, iterations, salt);
+}
+
+}
diff --git a/src/org/xbill/DNS/NSECRecord.java b/src/org/xbill/DNS/NSECRecord.java
new file mode 100644
index 0000000..e523e37
--- /dev/null
+++ b/src/org/xbill/DNS/NSECRecord.java
@@ -0,0 +1,98 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+
+/**
+ * Next SECure name - this record contains the following name in an
+ * ordered list of names in the zone, and a set of types for which
+ * records exist for this name. The presence of this record in a response
+ * signifies a negative response from a DNSSEC-signed zone.
+ *
+ * This replaces the NXT record.
+ *
+ * @author Brian Wellington
+ * @author David Blacka
+ */
+
+public class NSECRecord extends Record {
+
+private static final long serialVersionUID = -5165065768816265385L;
+
+private Name next;
+private TypeBitmap types;
+
+NSECRecord() {}
+
+Record
+getObject() {
+ return new NSECRecord();
+}
+
+/**
+ * Creates an NSEC Record from the given data.
+ * @param next The following name in an ordered list of the zone
+ * @param types An array containing the types present.
+ */
+public
+NSECRecord(Name name, int dclass, long ttl, Name next, int [] types) {
+ super(name, Type.NSEC, dclass, ttl);
+ this.next = checkName("next", next);
+ for (int i = 0; i < types.length; i++) {
+ Type.check(types[i]);
+ }
+ this.types = new TypeBitmap(types);
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ next = new Name(in);
+ types = new TypeBitmap(in);
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ // Note: The next name is not lowercased.
+ next.toWire(out, null, false);
+ types.toWire(out);
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ next = st.getName(origin);
+ types = new TypeBitmap(st);
+}
+
+/** Converts rdata to a String */
+String
+rrToString()
+{
+ StringBuffer sb = new StringBuffer();
+ sb.append(next);
+ if (!types.empty()) {
+ sb.append(' ');
+ sb.append(types.toString());
+ }
+ return sb.toString();
+}
+
+/** Returns the next name */
+public Name
+getNext() {
+ return next;
+}
+
+/** Returns the set of types defined for this name */
+public int []
+getTypes() {
+ return types.toArray();
+}
+
+/** Returns whether a specific type is in the set of types. */
+public boolean
+hasType(int type) {
+ return types.contains(type);
+}
+
+}
diff --git a/src/org/xbill/DNS/NSIDOption.java b/src/org/xbill/DNS/NSIDOption.java
new file mode 100644
index 0000000..7bcbcd5
--- /dev/null
+++ b/src/org/xbill/DNS/NSIDOption.java
@@ -0,0 +1,29 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * The Name Server Identifier Option, define in RFC 5001.
+ *
+ * @see OPTRecord
+ *
+ * @author Brian Wellington
+ */
+public class NSIDOption extends GenericEDNSOption {
+
+private static final long serialVersionUID = 74739759292589056L;
+
+NSIDOption() {
+ super(EDNSOption.Code.NSID);
+}
+
+/**
+ * Construct an NSID option.
+ * @param data The contents of the option.
+ */
+public
+NSIDOption(byte [] data) {
+ super(EDNSOption.Code.NSID, data);
+}
+
+}
diff --git a/src/org/xbill/DNS/NSRecord.java b/src/org/xbill/DNS/NSRecord.java
new file mode 100644
index 0000000..2908da4
--- /dev/null
+++ b/src/org/xbill/DNS/NSRecord.java
@@ -0,0 +1,42 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * Name Server Record - contains the name server serving the named zone
+ *
+ * @author Brian Wellington
+ */
+
+public class NSRecord extends SingleCompressedNameBase {
+
+private static final long serialVersionUID = 487170758138268838L;
+
+NSRecord() {}
+
+Record
+getObject() {
+ return new NSRecord();
+}
+
+/**
+ * Creates a new NS Record with the given data
+ * @param target The name server for the given domain
+ */
+public
+NSRecord(Name name, int dclass, long ttl, Name target) {
+ super(name, Type.NS, dclass, ttl, target, "target");
+}
+
+/** Gets the target of the NS Record */
+public Name
+getTarget() {
+ return getSingleName();
+}
+
+public Name
+getAdditionalName() {
+ return getSingleName();
+}
+
+}
diff --git a/src/org/xbill/DNS/NULLRecord.java b/src/org/xbill/DNS/NULLRecord.java
new file mode 100644
index 0000000..fa46d61
--- /dev/null
+++ b/src/org/xbill/DNS/NULLRecord.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+
+/**
+ * The NULL Record. This has no defined purpose, but can be used to
+ * hold arbitrary data.
+ *
+ * @author Brian Wellington
+ */
+
+public class NULLRecord extends Record {
+
+private static final long serialVersionUID = -5796493183235216538L;
+
+private byte [] data;
+
+NULLRecord() {}
+
+Record
+getObject() {
+ return new NULLRecord();
+}
+
+/**
+ * Creates a NULL record from the given data.
+ * @param data The contents of the record.
+ */
+public
+NULLRecord(Name name, int dclass, long ttl, byte [] data) {
+ super(name, Type.NULL, dclass, ttl);
+
+ if (data.length > 0xFFFF) {
+ throw new IllegalArgumentException("data must be <65536 bytes");
+ }
+ this.data = data;
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ data = in.readByteArray();
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ throw st.exception("no defined text format for NULL records");
+}
+
+String
+rrToString() {
+ return unknownToString(data);
+}
+
+/** Returns the contents of this record. */
+public byte []
+getData() {
+ return data;
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeByteArray(data);
+}
+
+}
diff --git a/src/org/xbill/DNS/NXTRecord.java b/src/org/xbill/DNS/NXTRecord.java
new file mode 100644
index 0000000..ad04e01
--- /dev/null
+++ b/src/org/xbill/DNS/NXTRecord.java
@@ -0,0 +1,111 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * Next name - this record contains the following name in an ordered list
+ * of names in the zone, and a set of types for which records exist for
+ * this name. The presence of this record in a response signifies a
+ * failed query for data in a DNSSEC-signed zone.
+ *
+ * @author Brian Wellington
+ */
+
+public class NXTRecord extends Record {
+
+private static final long serialVersionUID = -8851454400765507520L;
+
+private Name next;
+private BitSet bitmap;
+
+NXTRecord() {}
+
+Record
+getObject() {
+ return new NXTRecord();
+}
+
+/**
+ * Creates an NXT Record from the given data
+ * @param next The following name in an ordered list of the zone
+ * @param bitmap The set of type for which records exist at this name
+*/
+public
+NXTRecord(Name name, int dclass, long ttl, Name next, BitSet bitmap) {
+ super(name, Type.NXT, dclass, ttl);
+ this.next = checkName("next", next);
+ this.bitmap = bitmap;
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ next = new Name(in);
+ bitmap = new BitSet();
+ int bitmapLength = in.remaining();
+ for (int i = 0; i < bitmapLength; i++) {
+ int t = in.readU8();
+ for (int j = 0; j < 8; j++)
+ if ((t & (1 << (7 - j))) != 0)
+ bitmap.set(i * 8 + j);
+ }
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ next = st.getName(origin);
+ bitmap = new BitSet();
+ while (true) {
+ Tokenizer.Token t = st.get();
+ if (!t.isString())
+ break;
+ int typecode = Type.value(t.value, true);
+ if (typecode <= 0 || typecode > 128)
+ throw st.exception("Invalid type: " + t.value);
+ bitmap.set(typecode);
+ }
+ st.unget();
+}
+
+/** Converts rdata to a String */
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(next);
+ int length = bitmap.length();
+ for (short i = 0; i < length; i++)
+ if (bitmap.get(i)) {
+ sb.append(" ");
+ sb.append(Type.string(i));
+ }
+ return sb.toString();
+}
+
+/** Returns the next name */
+public Name
+getNext() {
+ return next;
+}
+
+/** Returns the set of types defined for this name */
+public BitSet
+getBitmap() {
+ return bitmap;
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ next.toWire(out, null, canonical);
+ int length = bitmap.length();
+ for (int i = 0, t = 0; i < length; i++) {
+ t |= (bitmap.get(i) ? (1 << (7 - i % 8)) : 0);
+ if (i % 8 == 7 || i == length - 1) {
+ out.writeU8(t);
+ t = 0;
+ }
+ }
+}
+
+}
diff --git a/src/org/xbill/DNS/Name.java b/src/org/xbill/DNS/Name.java
new file mode 100644
index 0000000..1331ad9
--- /dev/null
+++ b/src/org/xbill/DNS/Name.java
@@ -0,0 +1,822 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.text.*;
+
+/**
+ * A representation of a domain name. It may either be absolute (fully
+ * qualified) or relative.
+ *
+ * @author Brian Wellington
+ */
+
+public class Name implements Comparable, Serializable {
+
+private static final long serialVersionUID = -7257019940971525644L;
+
+private static final int LABEL_NORMAL = 0;
+private static final int LABEL_COMPRESSION = 0xC0;
+private static final int LABEL_MASK = 0xC0;
+
+/* The name data */
+private byte [] name;
+
+/*
+ * Effectively an 8 byte array, where the low order byte stores the number
+ * of labels and the 7 higher order bytes store per-label offsets.
+ */
+private long offsets;
+
+/* Precomputed hashcode. */
+private int hashcode;
+
+private static final byte [] emptyLabel = new byte[] {(byte)0};
+private static final byte [] wildLabel = new byte[] {(byte)1, (byte)'*'};
+
+/** The root name */
+public static final Name root;
+
+/** The root name */
+public static final Name empty;
+
+/** The maximum length of a Name */
+private static final int MAXNAME = 255;
+
+/** The maximum length of a label a Name */
+private static final int MAXLABEL = 63;
+
+/** The maximum number of labels in a Name */
+private static final int MAXLABELS = 128;
+
+/** The maximum number of cached offsets */
+private static final int MAXOFFSETS = 7;
+
+/* Used for printing non-printable characters */
+private static final DecimalFormat byteFormat = new DecimalFormat();
+
+/* Used to efficiently convert bytes to lowercase */
+private static final byte lowercase[] = new byte[256];
+
+/* Used in wildcard names. */
+private static final Name wild;
+
+static {
+ byteFormat.setMinimumIntegerDigits(3);
+ for (int i = 0; i < lowercase.length; i++) {
+ if (i < 'A' || i > 'Z')
+ lowercase[i] = (byte)i;
+ else
+ lowercase[i] = (byte)(i - 'A' + 'a');
+ }
+ root = new Name();
+ root.appendSafe(emptyLabel, 0, 1);
+ empty = new Name();
+ empty.name = new byte[0];
+ wild = new Name();
+ wild.appendSafe(wildLabel, 0, 1);
+}
+
+private
+Name() {
+}
+
+private final void
+setoffset(int n, int offset) {
+ if (n >= MAXOFFSETS)
+ return;
+ int shift = 8 * (7 - n);
+ offsets &= (~(0xFFL << shift));
+ offsets |= ((long)offset << shift);
+}
+
+private final int
+offset(int n) {
+ if (n == 0 && getlabels() == 0)
+ return 0;
+ if (n < 0 || n >= getlabels())
+ throw new IllegalArgumentException("label out of range");
+ if (n < MAXOFFSETS) {
+ int shift = 8 * (7 - n);
+ return ((int)(offsets >>> shift) & 0xFF);
+ } else {
+ int pos = offset(MAXOFFSETS - 1);
+ for (int i = MAXOFFSETS - 1; i < n; i++)
+ pos += (name[pos] + 1);
+ return (pos);
+ }
+}
+
+private final void
+setlabels(int labels) {
+ offsets &= ~(0xFF);
+ offsets |= labels;
+}
+
+private final int
+getlabels() {
+ return (int)(offsets & 0xFF);
+}
+
+private static final void
+copy(Name src, Name dst) {
+ if (src.offset(0) == 0) {
+ dst.name = src.name;
+ dst.offsets = src.offsets;
+ } else {
+ int offset0 = src.offset(0);
+ int namelen = src.name.length - offset0;
+ int labels = src.labels();
+ dst.name = new byte[namelen];
+ System.arraycopy(src.name, offset0, dst.name, 0, namelen);
+ for (int i = 0; i < labels && i < MAXOFFSETS; i++)
+ dst.setoffset(i, src.offset(i) - offset0);
+ dst.setlabels(labels);
+ }
+}
+
+private final void
+append(byte [] array, int start, int n) throws NameTooLongException {
+ int length = (name == null ? 0 : (name.length - offset(0)));
+ int alength = 0;
+ for (int i = 0, pos = start; i < n; i++) {
+ int len = array[pos];
+ if (len > MAXLABEL)
+ throw new IllegalStateException("invalid label");
+ len++;
+ pos += len;
+ alength += len;
+ }
+ int newlength = length + alength;
+ if (newlength > MAXNAME)
+ throw new NameTooLongException();
+ int labels = getlabels();
+ int newlabels = labels + n;
+ if (newlabels > MAXLABELS)
+ throw new IllegalStateException("too many labels");
+ byte [] newname = new byte[newlength];
+ if (length != 0)
+ System.arraycopy(name, offset(0), newname, 0, length);
+ System.arraycopy(array, start, newname, length, alength);
+ name = newname;
+ for (int i = 0, pos = length; i < n; i++) {
+ setoffset(labels + i, pos);
+ pos += (newname[pos] + 1);
+ }
+ setlabels(newlabels);
+}
+
+private static TextParseException
+parseException(String str, String message) {
+ return new TextParseException("'" + str + "': " + message);
+}
+
+private final void
+appendFromString(String fullName, byte [] array, int start, int n)
+throws TextParseException
+{
+ try {
+ append(array, start, n);
+ }
+ catch (NameTooLongException e) {
+ throw parseException(fullName, "Name too long");
+ }
+}
+
+private final void
+appendSafe(byte [] array, int start, int n) {
+ try {
+ append(array, start, n);
+ }
+ catch (NameTooLongException e) {
+ }
+}
+
+/**
+ * Create a new name from a string and an origin. This does not automatically
+ * make the name absolute; it will be absolute if it has a trailing dot or an
+ * absolute origin is appended.
+ * @param s The string to be converted
+ * @param origin If the name is not absolute, the origin to be appended.
+ * @throws TextParseException The name is invalid.
+ */
+public
+Name(String s, Name origin) throws TextParseException {
+ if (s.equals(""))
+ throw parseException(s, "empty name");
+ else if (s.equals("@")) {
+ if (origin == null)
+ copy(empty, this);
+ else
+ copy(origin, this);
+ return;
+ } else if (s.equals(".")) {
+ copy(root, this);
+ return;
+ }
+ int labelstart = -1;
+ int pos = 1;
+ byte [] label = new byte[MAXLABEL + 1];
+ boolean escaped = false;
+ int digits = 0;
+ int intval = 0;
+ boolean absolute = false;
+ for (int i = 0; i < s.length(); i++) {
+ byte b = (byte) s.charAt(i);
+ if (escaped) {
+ if (b >= '0' && b <= '9' && digits < 3) {
+ digits++;
+ intval *= 10;
+ intval += (b - '0');
+ if (intval > 255)
+ throw parseException(s, "bad escape");
+ if (digits < 3)
+ continue;
+ b = (byte) intval;
+ }
+ else if (digits > 0 && digits < 3)
+ throw parseException(s, "bad escape");
+ if (pos > MAXLABEL)
+ throw parseException(s, "label too long");
+ labelstart = pos;
+ label[pos++] = b;
+ escaped = false;
+ } else if (b == '\\') {
+ escaped = true;
+ digits = 0;
+ intval = 0;
+ } else if (b == '.') {
+ if (labelstart == -1)
+ throw parseException(s, "invalid empty label");
+ label[0] = (byte)(pos - 1);
+ appendFromString(s, label, 0, 1);
+ labelstart = -1;
+ pos = 1;
+ } else {
+ if (labelstart == -1)
+ labelstart = i;
+ if (pos > MAXLABEL)
+ throw parseException(s, "label too long");
+ label[pos++] = b;
+ }
+ }
+ if (digits > 0 && digits < 3)
+ throw parseException(s, "bad escape");
+ if (escaped)
+ throw parseException(s, "bad escape");
+ if (labelstart == -1) {
+ appendFromString(s, emptyLabel, 0, 1);
+ absolute = true;
+ } else {
+ label[0] = (byte)(pos - 1);
+ appendFromString(s, label, 0, 1);
+ }
+ if (origin != null && !absolute)
+ appendFromString(s, origin.name, 0, origin.getlabels());
+}
+
+/**
+ * Create a new name from a string. This does not automatically make the name
+ * absolute; it will be absolute if it has a trailing dot.
+ * @param s The string to be converted
+ * @throws TextParseException The name is invalid.
+ */
+public
+Name(String s) throws TextParseException {
+ this(s, null);
+}
+
+/**
+ * Create a new name from a string and an origin. This does not automatically
+ * make the name absolute; it will be absolute if it has a trailing dot or an
+ * absolute origin is appended. This is identical to the constructor, except
+ * that it will avoid creating new objects in some cases.
+ * @param s The string to be converted
+ * @param origin If the name is not absolute, the origin to be appended.
+ * @throws TextParseException The name is invalid.
+ */
+public static Name
+fromString(String s, Name origin) throws TextParseException {
+ if (s.equals("@") && origin != null)
+ return origin;
+ else if (s.equals("."))
+ return (root);
+
+ return new Name(s, origin);
+}
+
+/**
+ * Create a new name from a string. This does not automatically make the name
+ * absolute; it will be absolute if it has a trailing dot. This is identical
+ * to the constructor, except that it will avoid creating new objects in some
+ * cases.
+ * @param s The string to be converted
+ * @throws TextParseException The name is invalid.
+ */
+public static Name
+fromString(String s) throws TextParseException {
+ return fromString(s, null);
+}
+
+/**
+ * Create a new name from a constant string. This should only be used when
+ the name is known to be good - that is, when it is constant.
+ * @param s The string to be converted
+ * @throws IllegalArgumentException The name is invalid.
+ */
+public static Name
+fromConstantString(String s) {
+ try {
+ return fromString(s, null);
+ }
+ catch (TextParseException e) {
+ throw new IllegalArgumentException("Invalid name '" + s + "'");
+ }
+}
+
+/**
+ * Create a new name from DNS a wire format message
+ * @param in A stream containing the DNS message which is currently
+ * positioned at the start of the name to be read.
+ */
+public
+Name(DNSInput in) throws WireParseException {
+ int len, pos;
+ boolean done = false;
+ byte [] label = new byte[MAXLABEL + 1];
+ boolean savedState = false;
+
+ while (!done) {
+ len = in.readU8();
+ switch (len & LABEL_MASK) {
+ case LABEL_NORMAL:
+ if (getlabels() >= MAXLABELS)
+ throw new WireParseException("too many labels");
+ if (len == 0) {
+ append(emptyLabel, 0, 1);
+ done = true;
+ } else {
+ label[0] = (byte)len;
+ in.readByteArray(label, 1, len);
+ append(label, 0, 1);
+ }
+ break;
+ case LABEL_COMPRESSION:
+ pos = in.readU8();
+ pos += ((len & ~LABEL_MASK) << 8);
+ if (Options.check("verbosecompression"))
+ System.err.println("currently " + in.current() +
+ ", pointer to " + pos);
+
+ if (pos >= in.current() - 2)
+ throw new WireParseException("bad compression");
+ if (!savedState) {
+ in.save();
+ savedState = true;
+ }
+ in.jump(pos);
+ if (Options.check("verbosecompression"))
+ System.err.println("current name '" + this +
+ "', seeking to " + pos);
+ break;
+ default:
+ throw new WireParseException("bad label type");
+ }
+ }
+ if (savedState) {
+ in.restore();
+ }
+}
+
+/**
+ * Create a new name from DNS wire format
+ * @param b A byte array containing the wire format of the name.
+ */
+public
+Name(byte [] b) throws IOException {
+ this(new DNSInput(b));
+}
+
+/**
+ * Create a new name by removing labels from the beginning of an existing Name
+ * @param src An existing Name
+ * @param n The number of labels to remove from the beginning in the copy
+ */
+public
+Name(Name src, int n) {
+ int slabels = src.labels();
+ if (n > slabels)
+ throw new IllegalArgumentException("attempted to remove too " +
+ "many labels");
+ name = src.name;
+ setlabels(slabels - n);
+ for (int i = 0; i < MAXOFFSETS && i < slabels - n; i++)
+ setoffset(i, src.offset(i + n));
+}
+
+/**
+ * Creates a new name by concatenating two existing names.
+ * @param prefix The prefix name.
+ * @param suffix The suffix name.
+ * @return The concatenated name.
+ * @throws NameTooLongException The name is too long.
+ */
+public static Name
+concatenate(Name prefix, Name suffix) throws NameTooLongException {
+ if (prefix.isAbsolute())
+ return (prefix);
+ Name newname = new Name();
+ copy(prefix, newname);
+ newname.append(suffix.name, suffix.offset(0), suffix.getlabels());
+ return newname;
+}
+
+/**
+ * If this name is a subdomain of origin, return a new name relative to
+ * origin with the same value. Otherwise, return the existing name.
+ * @param origin The origin to remove.
+ * @return The possibly relativized name.
+ */
+public Name
+relativize(Name origin) {
+ if (origin == null || !subdomain(origin))
+ return this;
+ Name newname = new Name();
+ copy(this, newname);
+ int length = length() - origin.length();
+ int labels = newname.labels() - origin.labels();
+ newname.setlabels(labels);
+ newname.name = new byte[length];
+ System.arraycopy(name, offset(0), newname.name, 0, length);
+ return newname;
+}
+
+/**
+ * Generates a new Name with the first n labels replaced by a wildcard
+ * @return The wildcard name
+ */
+public Name
+wild(int n) {
+ if (n < 1)
+ throw new IllegalArgumentException("must replace 1 or more " +
+ "labels");
+ try {
+ Name newname = new Name();
+ copy(wild, newname);
+ newname.append(name, offset(n), getlabels() - n);
+ return newname;
+ }
+ catch (NameTooLongException e) {
+ throw new IllegalStateException
+ ("Name.wild: concatenate failed");
+ }
+}
+
+/**
+ * Generates a new Name to be used when following a DNAME.
+ * @param dname The DNAME record to follow.
+ * @return The constructed name.
+ * @throws NameTooLongException The resulting name is too long.
+ */
+public Name
+fromDNAME(DNAMERecord dname) throws NameTooLongException {
+ Name dnameowner = dname.getName();
+ Name dnametarget = dname.getTarget();
+ if (!subdomain(dnameowner))
+ return null;
+
+ int plabels = labels() - dnameowner.labels();
+ int plength = length() - dnameowner.length();
+ int pstart = offset(0);
+
+ int dlabels = dnametarget.labels();
+ int dlength = dnametarget.length();
+
+ if (plength + dlength > MAXNAME)
+ throw new NameTooLongException();
+
+ Name newname = new Name();
+ newname.setlabels(plabels + dlabels);
+ newname.name = new byte[plength + dlength];
+ System.arraycopy(name, pstart, newname.name, 0, plength);
+ System.arraycopy(dnametarget.name, 0, newname.name, plength, dlength);
+
+ for (int i = 0, pos = 0; i < MAXOFFSETS && i < plabels + dlabels; i++) {
+ newname.setoffset(i, pos);
+ pos += (newname.name[pos] + 1);
+ }
+ return newname;
+}
+
+/**
+ * Is this name a wildcard?
+ */
+public boolean
+isWild() {
+ if (labels() == 0)
+ return false;
+ return (name[0] == (byte)1 && name[1] == (byte)'*');
+}
+
+/**
+ * Is this name absolute?
+ */
+public boolean
+isAbsolute() {
+ if (labels() == 0)
+ return false;
+ return (name[name.length - 1] == 0);
+}
+
+/**
+ * The length of the name.
+ */
+public short
+length() {
+ if (getlabels() == 0)
+ return 0;
+ return (short)(name.length - offset(0));
+}
+
+/**
+ * The number of labels in the name.
+ */
+public int
+labels() {
+ return getlabels();
+}
+
+/**
+ * Is the current Name a subdomain of the specified name?
+ */
+public boolean
+subdomain(Name domain) {
+ int labels = labels();
+ int dlabels = domain.labels();
+ if (dlabels > labels)
+ return false;
+ if (dlabels == labels)
+ return equals(domain);
+ return domain.equals(name, offset(labels - dlabels));
+}
+
+private String
+byteString(byte [] array, int pos) {
+ StringBuffer sb = new StringBuffer();
+ int len = array[pos++];
+ for (int i = pos; i < pos + len; i++) {
+ int b = array[i] & 0xFF;
+ if (b <= 0x20 || b >= 0x7f) {
+ sb.append('\\');
+ sb.append(byteFormat.format(b));
+ }
+ else if (b == '"' || b == '(' || b == ')' || b == '.' ||
+ b == ';' || b == '\\' || b == '@' || b == '$')
+ {
+ sb.append('\\');
+ sb.append((char)b);
+ }
+ else
+ sb.append((char)b);
+ }
+ return sb.toString();
+}
+
+/**
+ * Convert a Name to a String
+ * @return The representation of this name as a (printable) String.
+ */
+public String
+toString() {
+ int labels = labels();
+ if (labels == 0)
+ return "@";
+ else if (labels == 1 && name[offset(0)] == 0)
+ return ".";
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0, pos = offset(0); i < labels; i++) {
+ int len = name[pos];
+ if (len > MAXLABEL)
+ throw new IllegalStateException("invalid label");
+ if (len == 0)
+ break;
+ sb.append(byteString(name, pos));
+ sb.append('.');
+ pos += (1 + len);
+ }
+ if (!isAbsolute())
+ sb.deleteCharAt(sb.length() - 1);
+ return sb.toString();
+}
+
+/**
+ * Retrieve the nth label of a Name. This makes a copy of the label; changing
+ * this does not change the Name.
+ * @param n The label to be retrieved. The first label is 0.
+ */
+public byte []
+getLabel(int n) {
+ int pos = offset(n);
+ byte len = (byte)(name[pos] + 1);
+ byte [] label = new byte[len];
+ System.arraycopy(name, pos, label, 0, len);
+ return label;
+}
+
+/**
+ * Convert the nth label in a Name to a String
+ * @param n The label to be converted to a (printable) String. The first
+ * label is 0.
+ */
+public String
+getLabelString(int n) {
+ int pos = offset(n);
+ return byteString(name, pos);
+}
+
+/**
+ * Emit a Name in DNS wire format
+ * @param out The output stream containing the DNS message.
+ * @param c The compression context, or null of no compression is desired.
+ * @throws IllegalArgumentException The name is not absolute.
+ */
+public void
+toWire(DNSOutput out, Compression c) {
+ if (!isAbsolute())
+ throw new IllegalArgumentException("toWire() called on " +
+ "non-absolute name");
+
+ int labels = labels();
+ for (int i = 0; i < labels - 1; i++) {
+ Name tname;
+ if (i == 0)
+ tname = this;
+ else
+ tname = new Name(this, i);
+ int pos = -1;
+ if (c != null)
+ pos = c.get(tname);
+ if (pos >= 0) {
+ pos |= (LABEL_MASK << 8);
+ out.writeU16(pos);
+ return;
+ } else {
+ if (c != null)
+ c.add(out.current(), tname);
+ int off = offset(i);
+ out.writeByteArray(name, off, name[off] + 1);
+ }
+ }
+ out.writeU8(0);
+}
+
+/**
+ * Emit a Name in DNS wire format
+ * @throws IllegalArgumentException The name is not absolute.
+ */
+public byte []
+toWire() {
+ DNSOutput out = new DNSOutput();
+ toWire(out, null);
+ return out.toByteArray();
+}
+
+/**
+ * Emit a Name in canonical DNS wire format (all lowercase)
+ * @param out The output stream to which the message is written.
+ */
+public void
+toWireCanonical(DNSOutput out) {
+ byte [] b = toWireCanonical();
+ out.writeByteArray(b);
+}
+
+/**
+ * Emit a Name in canonical DNS wire format (all lowercase)
+ * @return The canonical form of the name.
+ */
+public byte []
+toWireCanonical() {
+ int labels = labels();
+ if (labels == 0)
+ return (new byte[0]);
+ byte [] b = new byte[name.length - offset(0)];
+ for (int i = 0, spos = offset(0), dpos = 0; i < labels; i++) {
+ int len = name[spos];
+ if (len > MAXLABEL)
+ throw new IllegalStateException("invalid label");
+ b[dpos++] = name[spos++];
+ for (int j = 0; j < len; j++)
+ b[dpos++] = lowercase[(name[spos++] & 0xFF)];
+ }
+ return b;
+}
+
+/**
+ * Emit a Name in DNS wire format
+ * @param out The output stream containing the DNS message.
+ * @param c The compression context, or null of no compression is desired.
+ * @param canonical If true, emit the name in canonicalized form
+ * (all lowercase).
+ * @throws IllegalArgumentException The name is not absolute.
+ */
+public void
+toWire(DNSOutput out, Compression c, boolean canonical) {
+ if (canonical)
+ toWireCanonical(out);
+ else
+ toWire(out, c);
+}
+
+private final boolean
+equals(byte [] b, int bpos) {
+ int labels = labels();
+ for (int i = 0, pos = offset(0); i < labels; i++) {
+ if (name[pos] != b[bpos])
+ return false;
+ int len = name[pos++];
+ bpos++;
+ if (len > MAXLABEL)
+ throw new IllegalStateException("invalid label");
+ for (int j = 0; j < len; j++)
+ if (lowercase[(name[pos++] & 0xFF)] !=
+ lowercase[(b[bpos++] & 0xFF)])
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Are these two Names equivalent?
+ */
+public boolean
+equals(Object arg) {
+ if (arg == this)
+ return true;
+ if (arg == null || !(arg instanceof Name))
+ return false;
+ Name d = (Name) arg;
+ if (d.hashcode == 0)
+ d.hashCode();
+ if (hashcode == 0)
+ hashCode();
+ if (d.hashcode != hashcode)
+ return false;
+ if (d.labels() != labels())
+ return false;
+ return equals(d.name, d.offset(0));
+}
+
+/**
+ * Computes a hashcode based on the value
+ */
+public int
+hashCode() {
+ if (hashcode != 0)
+ return (hashcode);
+ int code = 0;
+ for (int i = offset(0); i < name.length; i++)
+ code += ((code << 3) + lowercase[(name[i] & 0xFF)]);
+ hashcode = code;
+ return hashcode;
+}
+
+/**
+ * Compares this Name to another Object.
+ * @param o The Object to be compared.
+ * @return The value 0 if the argument is a name equivalent to this name;
+ * a value less than 0 if the argument is less than this name in the canonical
+ * ordering, and a value greater than 0 if the argument is greater than this
+ * name in the canonical ordering.
+ * @throws ClassCastException if the argument is not a Name.
+ */
+public int
+compareTo(Object o) {
+ Name arg = (Name) o;
+
+ if (this == arg)
+ return (0);
+
+ int labels = labels();
+ int alabels = arg.labels();
+ int compares = labels > alabels ? alabels : labels;
+
+ for (int i = 1; i <= compares; i++) {
+ int start = offset(labels - i);
+ int astart = arg.offset(alabels - i);
+ int length = name[start];
+ int alength = arg.name[astart];
+ for (int j = 0; j < length && j < alength; j++) {
+ int n = lowercase[(name[j + start + 1]) & 0xFF] -
+ lowercase[(arg.name[j + astart + 1]) & 0xFF];
+ if (n != 0)
+ return (n);
+ }
+ if (length != alength)
+ return (length - alength);
+ }
+ return (labels - alabels);
+}
+
+}
diff --git a/src/org/xbill/DNS/NameTooLongException.java b/src/org/xbill/DNS/NameTooLongException.java
new file mode 100644
index 0000000..114be39
--- /dev/null
+++ b/src/org/xbill/DNS/NameTooLongException.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * An exception thrown when a name is longer than the maximum length of a DNS
+ * name.
+ *
+ * @author Brian Wellington
+ */
+
+public class NameTooLongException extends WireParseException {
+
+public
+NameTooLongException() {
+ super();
+}
+
+public
+NameTooLongException(String s) {
+ super(s);
+}
+
+}
diff --git a/src/org/xbill/DNS/OPTRecord.java b/src/org/xbill/DNS/OPTRecord.java
new file mode 100644
index 0000000..47fef2f
--- /dev/null
+++ b/src/org/xbill/DNS/OPTRecord.java
@@ -0,0 +1,191 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * Options - describes Extended DNS (EDNS) properties of a Message.
+ * No specific options are defined other than those specified in the
+ * header. An OPT should be generated by Resolver.
+ *
+ * EDNS is a method to extend the DNS protocol while providing backwards
+ * compatibility and not significantly changing the protocol. This
+ * implementation of EDNS is mostly complete at level 0.
+ *
+ * @see Message
+ * @see Resolver
+ *
+ * @author Brian Wellington
+ */
+
+public class OPTRecord extends Record {
+
+private static final long serialVersionUID = -6254521894809367938L;
+
+private List options;
+
+OPTRecord() {}
+
+Record
+getObject() {
+ return new OPTRecord();
+}
+
+/**
+ * Creates an OPT Record. This is normally called by SimpleResolver, but can
+ * also be called by a server.
+ * @param payloadSize The size of a packet that can be reassembled on the
+ * sending host.
+ * @param xrcode The value of the extended rcode field. This is the upper
+ * 16 bits of the full rcode.
+ * @param flags Additional message flags.
+ * @param version The EDNS version that this DNS implementation supports.
+ * This should be 0 for dnsjava.
+ * @param options The list of options that comprise the data field. There
+ * are currently no defined options.
+ * @see ExtendedFlags
+ */
+public
+OPTRecord(int payloadSize, int xrcode, int version, int flags, List options) {
+ super(Name.root, Type.OPT, payloadSize, 0);
+ checkU16("payloadSize", payloadSize);
+ checkU8("xrcode", xrcode);
+ checkU8("version", version);
+ checkU16("flags", flags);
+ ttl = ((long)xrcode << 24) + ((long)version << 16) + flags;
+ if (options != null) {
+ this.options = new ArrayList(options);
+ }
+}
+
+/**
+ * Creates an OPT Record with no data. This is normally called by
+ * SimpleResolver, but can also be called by a server.
+ * @param payloadSize The size of a packet that can be reassembled on the
+ * sending host.
+ * @param xrcode The value of the extended rcode field. This is the upper
+ * 16 bits of the full rcode.
+ * @param flags Additional message flags.
+ * @param version The EDNS version that this DNS implementation supports.
+ * This should be 0 for dnsjava.
+ * @see ExtendedFlags
+ */
+public
+OPTRecord(int payloadSize, int xrcode, int version, int flags) {
+ this(payloadSize, xrcode, version, flags, null);
+}
+
+/**
+ * Creates an OPT Record with no data. This is normally called by
+ * SimpleResolver, but can also be called by a server.
+ */
+public
+OPTRecord(int payloadSize, int xrcode, int version) {
+ this(payloadSize, xrcode, version, 0, null);
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ if (in.remaining() > 0)
+ options = new ArrayList();
+ while (in.remaining() > 0) {
+ EDNSOption option = EDNSOption.fromWire(in);
+ options.add(option);
+ }
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ throw st.exception("no text format defined for OPT");
+}
+
+/** Converts rdata to a String */
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ if (options != null) {
+ sb.append(options);
+ sb.append(" ");
+ }
+ sb.append(" ; payload ");
+ sb.append(getPayloadSize());
+ sb.append(", xrcode ");
+ sb.append(getExtendedRcode());
+ sb.append(", version ");
+ sb.append(getVersion());
+ sb.append(", flags ");
+ sb.append(getFlags());
+ return sb.toString();
+}
+
+/** Returns the maximum allowed payload size. */
+public int
+getPayloadSize() {
+ return dclass;
+}
+
+/**
+ * Returns the extended Rcode
+ * @see Rcode
+ */
+public int
+getExtendedRcode() {
+ return (int)(ttl >>> 24);
+}
+
+/** Returns the highest supported EDNS version */
+public int
+getVersion() {
+ return (int)((ttl >>> 16) & 0xFF);
+}
+
+/** Returns the EDNS flags */
+public int
+getFlags() {
+ return (int)(ttl & 0xFFFF);
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ if (options == null)
+ return;
+ Iterator it = options.iterator();
+ while (it.hasNext()) {
+ EDNSOption option = (EDNSOption) it.next();
+ option.toWire(out);
+ }
+}
+
+/**
+ * Gets all options in the OPTRecord. This returns a list of EDNSOptions.
+ */
+public List
+getOptions() {
+ if (options == null)
+ return Collections.EMPTY_LIST;
+ return Collections.unmodifiableList(options);
+}
+
+/**
+ * Gets all options in the OPTRecord with a specific code. This returns a list
+ * of EDNSOptions.
+ */
+public List
+getOptions(int code) {
+ if (options == null)
+ return Collections.EMPTY_LIST;
+ List list = Collections.EMPTY_LIST;
+ for (Iterator it = options.iterator(); it.hasNext(); ) {
+ EDNSOption opt = (EDNSOption) it.next();
+ if (opt.getCode() == code) {
+ if (list == Collections.EMPTY_LIST)
+ list = new ArrayList();
+ list.add(opt);
+ }
+ }
+ return list;
+}
+
+}
diff --git a/src/org/xbill/DNS/Opcode.java b/src/org/xbill/DNS/Opcode.java
new file mode 100644
index 0000000..dadbca1
--- /dev/null
+++ b/src/org/xbill/DNS/Opcode.java
@@ -0,0 +1,60 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * Constants and functions relating to DNS opcodes
+ *
+ * @author Brian Wellington
+ */
+
+public final class Opcode {
+
+/** A standard query */
+public static final int QUERY = 0;
+
+/** An inverse query (deprecated) */
+public static final int IQUERY = 1;
+
+/** A server status request (not used) */
+public static final int STATUS = 2;
+
+/**
+ * A message from a primary to a secondary server to initiate a zone transfer
+ */
+public static final int NOTIFY = 4;
+
+/** A dynamic update message */
+public static final int UPDATE = 5;
+
+private static Mnemonic opcodes = new Mnemonic("DNS Opcode",
+ Mnemonic.CASE_UPPER);
+
+static {
+ opcodes.setMaximum(0xF);
+ opcodes.setPrefix("RESERVED");
+ opcodes.setNumericAllowed(true);
+
+ opcodes.add(QUERY, "QUERY");
+ opcodes.add(IQUERY, "IQUERY");
+ opcodes.add(STATUS, "STATUS");
+ opcodes.add(NOTIFY, "NOTIFY");
+ opcodes.add(UPDATE, "UPDATE");
+}
+
+private
+Opcode() {}
+
+/** Converts a numeric Opcode into a String */
+public static String
+string(int i) {
+ return opcodes.getText(i);
+}
+
+/** Converts a String representation of an Opcode into its numeric value */
+public static int
+value(String s) {
+ return opcodes.getValue(s);
+}
+
+}
diff --git a/src/org/xbill/DNS/Options.java b/src/org/xbill/DNS/Options.java
new file mode 100644
index 0000000..2f1dae3
--- /dev/null
+++ b/src/org/xbill/DNS/Options.java
@@ -0,0 +1,123 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.util.*;
+
+/**
+ * Boolean options:<BR>
+ * bindttl - Print TTLs in BIND format<BR>
+ * multiline - Print records in multiline format<BR>
+ * noprintin - Don't print the class of a record if it's IN<BR>
+ * verbose - Turn on general debugging statements<BR>
+ * verbosemsg - Print all messages sent or received by SimpleResolver<BR>
+ * verbosecompression - Print messages related to name compression<BR>
+ * verbosesec - Print messages related to signature verification<BR>
+ * verbosecache - Print messages related to cache lookups<BR>
+ * <BR>
+ * Valued options:<BR>
+ * tsigfudge=n - Sets the default TSIG fudge value (in seconds)<BR>
+ * sig0validity=n - Sets the default SIG(0) validity period (in seconds)<BR>
+ *
+ * @author Brian Wellington
+ */
+
+public final class Options {
+
+private static Map table;
+
+static {
+ try {
+ refresh();
+ }
+ catch (SecurityException e) {
+ }
+}
+
+private
+Options() {}
+
+public static void
+refresh() {
+ String s = System.getProperty("dnsjava.options");
+ if (s != null) {
+ StringTokenizer st = new StringTokenizer(s, ",");
+ while (st.hasMoreTokens()) {
+ String token = st.nextToken();
+ int index = token.indexOf('=');
+ if (index == -1)
+ set(token);
+ else {
+ String option = token.substring(0, index);
+ String value = token.substring(index + 1);
+ set(option, value);
+ }
+ }
+ }
+}
+
+/** Clears all defined options */
+public static void
+clear() {
+ table = null;
+}
+
+/** Sets an option to "true" */
+public static void
+set(String option) {
+ if (table == null)
+ table = new HashMap();
+ table.put(option.toLowerCase(), "true");
+}
+
+/** Sets an option to the the supplied value */
+public static void
+set(String option, String value) {
+ if (table == null)
+ table = new HashMap();
+ table.put(option.toLowerCase(), value.toLowerCase());
+}
+
+/** Removes an option */
+public static void
+unset(String option) {
+ if (table == null)
+ return;
+ table.remove(option.toLowerCase());
+}
+
+/** Checks if an option is defined */
+public static boolean
+check(String option) {
+ if (table == null)
+ return false;
+ return (table.get(option.toLowerCase()) != null);
+}
+
+/** Returns the value of an option */
+public static String
+value(String option) {
+ if (table == null)
+ return null;
+ return ((String)table.get(option.toLowerCase()));
+}
+
+/**
+ * Returns the value of an option as an integer, or -1 if not defined.
+ */
+public static int
+intValue(String option) {
+ String s = value(option);
+ if (s != null) {
+ try {
+ int val = Integer.parseInt(s);
+ if (val > 0)
+ return (val);
+ }
+ catch (NumberFormatException e) {
+ }
+ }
+ return (-1);
+}
+
+}
diff --git a/src/org/xbill/DNS/PTRRecord.java b/src/org/xbill/DNS/PTRRecord.java
new file mode 100644
index 0000000..89be578
--- /dev/null
+++ b/src/org/xbill/DNS/PTRRecord.java
@@ -0,0 +1,38 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * Pointer Record - maps a domain name representing an Internet Address to
+ * a hostname.
+ *
+ * @author Brian Wellington
+ */
+
+public class PTRRecord extends SingleCompressedNameBase {
+
+private static final long serialVersionUID = -8321636610425434192L;
+
+PTRRecord() {}
+
+Record
+getObject() {
+ return new PTRRecord();
+}
+
+/**
+ * Creates a new PTR Record with the given data
+ * @param target The name of the machine with this address
+ */
+public
+PTRRecord(Name name, int dclass, long ttl, Name target) {
+ super(name, Type.PTR, dclass, ttl, target, "target");
+}
+
+/** Gets the target of the PTR Record */
+public Name
+getTarget() {
+ return getSingleName();
+}
+
+}
diff --git a/src/org/xbill/DNS/PXRecord.java b/src/org/xbill/DNS/PXRecord.java
new file mode 100644
index 0000000..a407241
--- /dev/null
+++ b/src/org/xbill/DNS/PXRecord.java
@@ -0,0 +1,96 @@
+// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+
+/**
+ * X.400 mail mapping record.
+ *
+ * @author Brian Wellington
+ */
+
+public class PXRecord extends Record {
+
+private static final long serialVersionUID = 1811540008806660667L;
+
+private int preference;
+private Name map822;
+private Name mapX400;
+
+PXRecord() {}
+
+Record
+getObject() {
+ return new PXRecord();
+}
+
+/**
+ * Creates an PX Record from the given data
+ * @param preference The preference of this mail address.
+ * @param map822 The RFC 822 component of the mail address.
+ * @param mapX400 The X.400 component of the mail address.
+ */
+public
+PXRecord(Name name, int dclass, long ttl, int preference,
+ Name map822, Name mapX400)
+{
+ super(name, Type.PX, dclass, ttl);
+
+ this.preference = checkU16("preference", preference);
+ this.map822 = checkName("map822", map822);
+ this.mapX400 = checkName("mapX400", mapX400);
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ preference = in.readU16();
+ map822 = new Name(in);
+ mapX400 = new Name(in);
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ preference = st.getUInt16();
+ map822 = st.getName(origin);
+ mapX400 = st.getName(origin);
+}
+
+/** Converts the PX Record to a String */
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(preference);
+ sb.append(" ");
+ sb.append(map822);
+ sb.append(" ");
+ sb.append(mapX400);
+ return sb.toString();
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeU16(preference);
+ map822.toWire(out, null, canonical);
+ mapX400.toWire(out, null, canonical);
+}
+
+/** Gets the preference of the route. */
+public int
+getPreference() {
+ return preference;
+}
+
+/** Gets the RFC 822 component of the mail address. */
+public Name
+getMap822() {
+ return map822;
+}
+
+/** Gets the X.400 component of the mail address. */
+public Name
+getMapX400() {
+ return mapX400;
+}
+
+}
diff --git a/src/org/xbill/DNS/RPRecord.java b/src/org/xbill/DNS/RPRecord.java
new file mode 100644
index 0000000..7aa066c
--- /dev/null
+++ b/src/org/xbill/DNS/RPRecord.java
@@ -0,0 +1,82 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+
+/**
+ * Responsible Person Record - lists the mail address of a responsible person
+ * and a domain where TXT records are available.
+ *
+ * @author Tom Scola <tscola@research.att.com>
+ * @author Brian Wellington
+ */
+
+public class RPRecord extends Record {
+
+private static final long serialVersionUID = 8124584364211337460L;
+
+private Name mailbox;
+private Name textDomain;
+
+RPRecord() {}
+
+Record
+getObject() {
+ return new RPRecord();
+}
+
+/**
+ * Creates an RP Record from the given data
+ * @param mailbox The responsible person
+ * @param textDomain The address where TXT records can be found
+ */
+public
+RPRecord(Name name, int dclass, long ttl, Name mailbox, Name textDomain) {
+ super(name, Type.RP, dclass, ttl);
+
+ this.mailbox = checkName("mailbox", mailbox);
+ this.textDomain = checkName("textDomain", textDomain);
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ mailbox = new Name(in);
+ textDomain = new Name(in);
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ mailbox = st.getName(origin);
+ textDomain = st.getName(origin);
+}
+
+/** Converts the RP Record to a String */
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(mailbox);
+ sb.append(" ");
+ sb.append(textDomain);
+ return sb.toString();
+}
+
+/** Gets the mailbox address of the RP Record */
+public Name
+getMailbox() {
+ return mailbox;
+}
+
+/** Gets the text domain info of the RP Record */
+public Name
+getTextDomain() {
+ return textDomain;
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ mailbox.toWire(out, null, canonical);
+ textDomain.toWire(out, null, canonical);
+}
+
+}
diff --git a/src/org/xbill/DNS/RRSIGRecord.java b/src/org/xbill/DNS/RRSIGRecord.java
new file mode 100644
index 0000000..c092839
--- /dev/null
+++ b/src/org/xbill/DNS/RRSIGRecord.java
@@ -0,0 +1,50 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.util.*;
+
+/**
+ * Recource Record Signature - An RRSIG provides the digital signature of an
+ * RRset, so that the data can be authenticated by a DNSSEC-capable resolver.
+ * The signature is generated by a key contained in a DNSKEY Record.
+ * @see RRset
+ * @see DNSSEC
+ * @see KEYRecord
+ *
+ * @author Brian Wellington
+ */
+
+public class RRSIGRecord extends SIGBase {
+
+private static final long serialVersionUID = -2609150673537226317L;
+
+RRSIGRecord() {}
+
+Record
+getObject() {
+ return new RRSIGRecord();
+}
+
+/**
+ * Creates an RRSIG Record from the given data
+ * @param covered The RRset type covered by this signature
+ * @param alg The cryptographic algorithm of the key that generated the
+ * signature
+ * @param origttl The original TTL of the RRset
+ * @param expire The time at which the signature expires
+ * @param timeSigned The time at which this signature was generated
+ * @param footprint The footprint/key id of the signing key.
+ * @param signer The owner of the signing key
+ * @param signature Binary data representing the signature
+ */
+public
+RRSIGRecord(Name name, int dclass, long ttl, int covered, int alg, long origttl,
+ Date expire, Date timeSigned, int footprint, Name signer,
+ byte [] signature)
+{
+ super(name, Type.RRSIG, dclass, ttl, covered, alg, origttl, expire,
+ timeSigned, footprint, signer, signature);
+}
+
+}
diff --git a/src/org/xbill/DNS/RRset.java b/src/org/xbill/DNS/RRset.java
new file mode 100644
index 0000000..fa1a6ad
--- /dev/null
+++ b/src/org/xbill/DNS/RRset.java
@@ -0,0 +1,258 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.Serializable;
+import java.util.*;
+
+/**
+ * A set of Records with the same name, type, and class. Also included
+ * are all RRSIG records signing the data records.
+ * @see Record
+ * @see RRSIGRecord
+ *
+ * @author Brian Wellington
+ */
+
+public class RRset implements Serializable {
+
+private static final long serialVersionUID = -3270249290171239695L;
+
+/*
+ * rrs contains both normal and RRSIG records, with the RRSIG records
+ * at the end.
+ */
+private List rrs;
+private short nsigs;
+private short position;
+
+/** Creates an empty RRset */
+public
+RRset() {
+ rrs = new ArrayList(1);
+ nsigs = 0;
+ position = 0;
+}
+
+/** Creates an RRset and sets its contents to the specified record */
+public
+RRset(Record record) {
+ this();
+ safeAddRR(record);
+}
+
+/** Creates an RRset with the contents of an existing RRset */
+public
+RRset(RRset rrset) {
+ synchronized (rrset) {
+ rrs = (List) ((ArrayList)rrset.rrs).clone();
+ nsigs = rrset.nsigs;
+ position = rrset.position;
+ }
+}
+
+private void
+safeAddRR(Record r) {
+ if (!(r instanceof RRSIGRecord)) {
+ if (nsigs == 0)
+ rrs.add(r);
+ else
+ rrs.add(rrs.size() - nsigs, r);
+ } else {
+ rrs.add(r);
+ nsigs++;
+ }
+}
+
+/** Adds a Record to an RRset */
+public synchronized void
+addRR(Record r) {
+ if (rrs.size() == 0) {
+ safeAddRR(r);
+ return;
+ }
+ Record first = first();
+ if (!r.sameRRset(first))
+ throw new IllegalArgumentException("record does not match " +
+ "rrset");
+
+ if (r.getTTL() != first.getTTL()) {
+ if (r.getTTL() > first.getTTL()) {
+ r = r.cloneRecord();
+ r.setTTL(first.getTTL());
+ } else {
+ for (int i = 0; i < rrs.size(); i++) {
+ Record tmp = (Record) rrs.get(i);
+ tmp = tmp.cloneRecord();
+ tmp.setTTL(r.getTTL());
+ rrs.set(i, tmp);
+ }
+ }
+ }
+
+ if (!rrs.contains(r))
+ safeAddRR(r);
+}
+
+/** Deletes a Record from an RRset */
+public synchronized void
+deleteRR(Record r) {
+ if (rrs.remove(r) && (r instanceof RRSIGRecord))
+ nsigs--;
+}
+
+/** Deletes all Records from an RRset */
+public synchronized void
+clear() {
+ rrs.clear();
+ position = 0;
+ nsigs = 0;
+}
+
+private synchronized Iterator
+iterator(boolean data, boolean cycle) {
+ int size, start, total;
+
+ total = rrs.size();
+
+ if (data)
+ size = total - nsigs;
+ else
+ size = nsigs;
+ if (size == 0)
+ return Collections.EMPTY_LIST.iterator();
+
+ if (data) {
+ if (!cycle)
+ start = 0;
+ else {
+ if (position >= size)
+ position = 0;
+ start = position++;
+ }
+ } else {
+ start = total - nsigs;
+ }
+
+ List list = new ArrayList(size);
+ if (data) {
+ list.addAll(rrs.subList(start, size));
+ if (start != 0)
+ list.addAll(rrs.subList(0, start));
+ } else {
+ list.addAll(rrs.subList(start, total));
+ }
+
+ return list.iterator();
+}
+
+/**
+ * Returns an Iterator listing all (data) records.
+ * @param cycle If true, cycle through the records so that each Iterator will
+ * start with a different record.
+ */
+public synchronized Iterator
+rrs(boolean cycle) {
+ return iterator(true, cycle);
+}
+
+/**
+ * Returns an Iterator listing all (data) records. This cycles through
+ * the records, so each Iterator will start with a different record.
+ */
+public synchronized Iterator
+rrs() {
+ return iterator(true, true);
+}
+
+/** Returns an Iterator listing all signature records */
+public synchronized Iterator
+sigs() {
+ return iterator(false, false);
+}
+
+/** Returns the number of (data) records */
+public synchronized int
+size() {
+ return rrs.size() - nsigs;
+}
+
+/**
+ * Returns the name of the records
+ * @see Name
+ */
+public Name
+getName() {
+ return first().getName();
+}
+
+/**
+ * Returns the type of the records
+ * @see Type
+ */
+public int
+getType() {
+ return first().getRRsetType();
+}
+
+/**
+ * Returns the class of the records
+ * @see DClass
+ */
+public int
+getDClass() {
+ return first().getDClass();
+}
+
+/** Returns the ttl of the records */
+public synchronized long
+getTTL() {
+ return first().getTTL();
+}
+
+/**
+ * Returns the first record
+ * @throws IllegalStateException if the rrset is empty
+ */
+public synchronized Record
+first() {
+ if (rrs.size() == 0)
+ throw new IllegalStateException("rrset is empty");
+ return (Record) rrs.get(0);
+}
+
+private String
+iteratorToString(Iterator it) {
+ StringBuffer sb = new StringBuffer();
+ while (it.hasNext()) {
+ Record rr = (Record) it.next();
+ sb.append("[");
+ sb.append(rr.rdataToString());
+ sb.append("]");
+ if (it.hasNext())
+ sb.append(" ");
+ }
+ return sb.toString();
+}
+
+/** Converts the RRset to a String */
+public String
+toString() {
+ if (rrs == null)
+ return ("{empty}");
+ StringBuffer sb = new StringBuffer();
+ sb.append("{ ");
+ sb.append(getName() + " ");
+ sb.append(getTTL() + " ");
+ sb.append(DClass.string(getDClass()) + " ");
+ sb.append(Type.string(getType()) + " ");
+ sb.append(iteratorToString(iterator(true, false)));
+ if (nsigs > 0) {
+ sb.append(" sigs: ");
+ sb.append(iteratorToString(iterator(false, false)));
+ }
+ sb.append(" }");
+ return sb.toString();
+}
+
+}
diff --git a/src/org/xbill/DNS/RTRecord.java b/src/org/xbill/DNS/RTRecord.java
new file mode 100644
index 0000000..549731e
--- /dev/null
+++ b/src/org/xbill/DNS/RTRecord.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * Route Through Record - lists a route preference and intermediate host.
+ *
+ * @author Brian Wellington
+ */
+
+public class RTRecord extends U16NameBase {
+
+private static final long serialVersionUID = -3206215651648278098L;
+
+RTRecord() {}
+
+Record
+getObject() {
+ return new RTRecord();
+}
+
+/**
+ * Creates an RT Record from the given data
+ * @param preference The preference of the route. Smaller numbers indicate
+ * more preferred routes.
+ * @param intermediateHost The domain name of the host to use as a router.
+ */
+public
+RTRecord(Name name, int dclass, long ttl, int preference,
+ Name intermediateHost)
+{
+ super(name, Type.RT, dclass, ttl, preference, "preference",
+ intermediateHost, "intermediateHost");
+}
+
+/** Gets the preference of the route. */
+public int
+getPreference() {
+ return getU16Field();
+}
+
+/** Gets the host to use as a router. */
+public Name
+getIntermediateHost() {
+ return getNameField();
+}
+
+}
diff --git a/src/org/xbill/DNS/Rcode.java b/src/org/xbill/DNS/Rcode.java
new file mode 100644
index 0000000..7f0dd1f
--- /dev/null
+++ b/src/org/xbill/DNS/Rcode.java
@@ -0,0 +1,123 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * Constants and functions relating to DNS rcodes (error values)
+ *
+ * @author Brian Wellington
+ */
+
+public final class Rcode {
+
+private static Mnemonic rcodes = new Mnemonic("DNS Rcode",
+ Mnemonic.CASE_UPPER);
+
+private static Mnemonic tsigrcodes = new Mnemonic("TSIG rcode",
+ Mnemonic.CASE_UPPER);
+
+/** No error */
+public static final int NOERROR = 0;
+
+/** Format error */
+public static final int FORMERR = 1;
+
+/** Server failure */
+public static final int SERVFAIL = 2;
+
+/** The name does not exist */
+public static final int NXDOMAIN = 3;
+
+/** The operation requested is not implemented */
+public static final int NOTIMP = 4;
+
+/** Deprecated synonym for NOTIMP. */
+public static final int NOTIMPL = 4;
+
+/** The operation was refused by the server */
+public static final int REFUSED = 5;
+
+/** The name exists */
+public static final int YXDOMAIN = 6;
+
+/** The RRset (name, type) exists */
+public static final int YXRRSET = 7;
+
+/** The RRset (name, type) does not exist */
+public static final int NXRRSET = 8;
+
+/** The requestor is not authorized to perform this operation */
+public static final int NOTAUTH = 9;
+
+/** The zone specified is not a zone */
+public static final int NOTZONE = 10;
+
+/* EDNS extended rcodes */
+/** Unsupported EDNS level */
+public static final int BADVERS = 16;
+
+/* TSIG/TKEY only rcodes */
+/** The signature is invalid (TSIG/TKEY extended error) */
+public static final int BADSIG = 16;
+
+/** The key is invalid (TSIG/TKEY extended error) */
+public static final int BADKEY = 17;
+
+/** The time is out of range (TSIG/TKEY extended error) */
+public static final int BADTIME = 18;
+
+/** The mode is invalid (TKEY extended error) */
+public static final int BADMODE = 19;
+
+static {
+ rcodes.setMaximum(0xFFF);
+ rcodes.setPrefix("RESERVED");
+ rcodes.setNumericAllowed(true);
+
+ rcodes.add(NOERROR, "NOERROR");
+ rcodes.add(FORMERR, "FORMERR");
+ rcodes.add(SERVFAIL, "SERVFAIL");
+ rcodes.add(NXDOMAIN, "NXDOMAIN");
+ rcodes.add(NOTIMP, "NOTIMP");
+ rcodes.addAlias(NOTIMP, "NOTIMPL");
+ rcodes.add(REFUSED, "REFUSED");
+ rcodes.add(YXDOMAIN, "YXDOMAIN");
+ rcodes.add(YXRRSET, "YXRRSET");
+ rcodes.add(NXRRSET, "NXRRSET");
+ rcodes.add(NOTAUTH, "NOTAUTH");
+ rcodes.add(NOTZONE, "NOTZONE");
+ rcodes.add(BADVERS, "BADVERS");
+
+ tsigrcodes.setMaximum(0xFFFF);
+ tsigrcodes.setPrefix("RESERVED");
+ tsigrcodes.setNumericAllowed(true);
+ tsigrcodes.addAll(rcodes);
+
+ tsigrcodes.add(BADSIG, "BADSIG");
+ tsigrcodes.add(BADKEY, "BADKEY");
+ tsigrcodes.add(BADTIME, "BADTIME");
+ tsigrcodes.add(BADMODE, "BADMODE");
+}
+
+private
+Rcode() {}
+
+/** Converts a numeric Rcode into a String */
+public static String
+string(int i) {
+ return rcodes.getText(i);
+}
+
+/** Converts a numeric TSIG extended Rcode into a String */
+public static String
+TSIGstring(int i) {
+ return tsigrcodes.getText(i);
+}
+
+/** Converts a String representation of an Rcode into its numeric value */
+public static int
+value(String s) {
+ return rcodes.getValue(s);
+}
+
+}
diff --git a/src/org/xbill/DNS/Record.java b/src/org/xbill/DNS/Record.java
new file mode 100644
index 0000000..8da7015
--- /dev/null
+++ b/src/org/xbill/DNS/Record.java
@@ -0,0 +1,736 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.text.*;
+import java.util.*;
+import org.xbill.DNS.utils.*;
+
+/**
+ * A generic DNS resource record. The specific record types extend this class.
+ * A record contains a name, type, class, ttl, and rdata.
+ *
+ * @author Brian Wellington
+ */
+
+public abstract class Record implements Cloneable, Comparable, Serializable {
+
+private static final long serialVersionUID = 2694906050116005466L;
+
+protected Name name;
+protected int type, dclass;
+protected long ttl;
+
+private static final DecimalFormat byteFormat = new DecimalFormat();
+
+static {
+ byteFormat.setMinimumIntegerDigits(3);
+}
+
+protected
+Record() {}
+
+Record(Name name, int type, int dclass, long ttl) {
+ if (!name.isAbsolute())
+ throw new RelativeNameException(name);
+ Type.check(type);
+ DClass.check(dclass);
+ TTL.check(ttl);
+ this.name = name;
+ this.type = type;
+ this.dclass = dclass;
+ this.ttl = ttl;
+}
+
+/**
+ * Creates an empty record of the correct type; must be overriden
+ */
+abstract Record
+getObject();
+
+private static final Record
+getEmptyRecord(Name name, int type, int dclass, long ttl, boolean hasData) {
+ Record proto, rec;
+
+ if (hasData) {
+ proto = Type.getProto(type);
+ if (proto != null)
+ rec = proto.getObject();
+ else
+ rec = new UNKRecord();
+ } else
+ rec = new EmptyRecord();
+ rec.name = name;
+ rec.type = type;
+ rec.dclass = dclass;
+ rec.ttl = ttl;
+ return rec;
+}
+
+/**
+ * Converts the type-specific RR to wire format - must be overriden
+ */
+abstract void
+rrFromWire(DNSInput in) throws IOException;
+
+private static Record
+newRecord(Name name, int type, int dclass, long ttl, int length, DNSInput in)
+throws IOException
+{
+ Record rec;
+ rec = getEmptyRecord(name, type, dclass, ttl, in != null);
+ if (in != null) {
+ if (in.remaining() < length)
+ throw new WireParseException("truncated record");
+ in.setActive(length);
+
+ rec.rrFromWire(in);
+
+ if (in.remaining() > 0)
+ throw new WireParseException("invalid record length");
+ in.clearActive();
+ }
+ return rec;
+}
+
+/**
+ * Creates a new record, with the given parameters.
+ * @param name The owner name of the record.
+ * @param type The record's type.
+ * @param dclass The record's class.
+ * @param ttl The record's time to live.
+ * @param length The length of the record's data.
+ * @param data The rdata of the record, in uncompressed DNS wire format. Only
+ * the first length bytes are used.
+ */
+public static Record
+newRecord(Name name, int type, int dclass, long ttl, int length, byte [] data) {
+ if (!name.isAbsolute())
+ throw new RelativeNameException(name);
+ Type.check(type);
+ DClass.check(dclass);
+ TTL.check(ttl);
+
+ DNSInput in;
+ if (data != null)
+ in = new DNSInput(data);
+ else
+ in = null;
+ try {
+ return newRecord(name, type, dclass, ttl, length, in);
+ }
+ catch (IOException e) {
+ return null;
+ }
+}
+
+/**
+ * Creates a new record, with the given parameters.
+ * @param name The owner name of the record.
+ * @param type The record's type.
+ * @param dclass The record's class.
+ * @param ttl The record's time to live.
+ * @param data The complete rdata of the record, in uncompressed DNS wire
+ * format.
+ */
+public static Record
+newRecord(Name name, int type, int dclass, long ttl, byte [] data) {
+ return newRecord(name, type, dclass, ttl, data.length, data);
+}
+
+/**
+ * Creates a new empty record, with the given parameters.
+ * @param name The owner name of the record.
+ * @param type The record's type.
+ * @param dclass The record's class.
+ * @param ttl The record's time to live.
+ * @return An object of a subclass of Record
+ */
+public static Record
+newRecord(Name name, int type, int dclass, long ttl) {
+ if (!name.isAbsolute())
+ throw new RelativeNameException(name);
+ Type.check(type);
+ DClass.check(dclass);
+ TTL.check(ttl);
+
+ return getEmptyRecord(name, type, dclass, ttl, false);
+}
+
+/**
+ * Creates a new empty record, with the given parameters. This method is
+ * designed to create records that will be added to the QUERY section
+ * of a message.
+ * @param name The owner name of the record.
+ * @param type The record's type.
+ * @param dclass The record's class.
+ * @return An object of a subclass of Record
+ */
+public static Record
+newRecord(Name name, int type, int dclass) {
+ return newRecord(name, type, dclass, 0);
+}
+
+static Record
+fromWire(DNSInput in, int section, boolean isUpdate) throws IOException {
+ int type, dclass;
+ long ttl;
+ int length;
+ Name name;
+ Record rec;
+
+ name = new Name(in);
+ type = in.readU16();
+ dclass = in.readU16();
+
+ if (section == Section.QUESTION)
+ return newRecord(name, type, dclass);
+
+ ttl = in.readU32();
+ length = in.readU16();
+ if (length == 0 && isUpdate &&
+ (section == Section.PREREQ || section == Section.UPDATE))
+ return newRecord(name, type, dclass, ttl);
+ rec = newRecord(name, type, dclass, ttl, length, in);
+ return rec;
+}
+
+static Record
+fromWire(DNSInput in, int section) throws IOException {
+ return fromWire(in, section, false);
+}
+
+/**
+ * Builds a Record from DNS uncompressed wire format.
+ */
+public static Record
+fromWire(byte [] b, int section) throws IOException {
+ return fromWire(new DNSInput(b), section, false);
+}
+
+void
+toWire(DNSOutput out, int section, Compression c) {
+ name.toWire(out, c);
+ out.writeU16(type);
+ out.writeU16(dclass);
+ if (section == Section.QUESTION)
+ return;
+ out.writeU32(ttl);
+ int lengthPosition = out.current();
+ out.writeU16(0); /* until we know better */
+ rrToWire(out, c, false);
+ int rrlength = out.current() - lengthPosition - 2;
+ out.writeU16At(rrlength, lengthPosition);
+}
+
+/**
+ * Converts a Record into DNS uncompressed wire format.
+ */
+public byte []
+toWire(int section) {
+ DNSOutput out = new DNSOutput();
+ toWire(out, section, null);
+ return out.toByteArray();
+}
+
+private void
+toWireCanonical(DNSOutput out, boolean noTTL) {
+ name.toWireCanonical(out);
+ out.writeU16(type);
+ out.writeU16(dclass);
+ if (noTTL) {
+ out.writeU32(0);
+ } else {
+ out.writeU32(ttl);
+ }
+ int lengthPosition = out.current();
+ out.writeU16(0); /* until we know better */
+ rrToWire(out, null, true);
+ int rrlength = out.current() - lengthPosition - 2;
+ out.writeU16At(rrlength, lengthPosition);
+}
+
+/*
+ * Converts a Record into canonical DNS uncompressed wire format (all names are
+ * converted to lowercase), optionally ignoring the TTL.
+ */
+private byte []
+toWireCanonical(boolean noTTL) {
+ DNSOutput out = new DNSOutput();
+ toWireCanonical(out, noTTL);
+ return out.toByteArray();
+}
+
+/**
+ * Converts a Record into canonical DNS uncompressed wire format (all names are
+ * converted to lowercase).
+ */
+public byte []
+toWireCanonical() {
+ return toWireCanonical(false);
+}
+
+/**
+ * Converts the rdata in a Record into canonical DNS uncompressed wire format
+ * (all names are converted to lowercase).
+ */
+public byte []
+rdataToWireCanonical() {
+ DNSOutput out = new DNSOutput();
+ rrToWire(out, null, true);
+ return out.toByteArray();
+}
+
+/**
+ * Converts the type-specific RR to text format - must be overriden
+ */
+abstract String rrToString();
+
+/**
+ * Converts the rdata portion of a Record into a String representation
+ */
+public String
+rdataToString() {
+ return rrToString();
+}
+
+/**
+ * Converts a Record into a String representation
+ */
+public String
+toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(name);
+ if (sb.length() < 8)
+ sb.append("\t");
+ if (sb.length() < 16)
+ sb.append("\t");
+ sb.append("\t");
+ if (Options.check("BINDTTL"))
+ sb.append(TTL.format(ttl));
+ else
+ sb.append(ttl);
+ sb.append("\t");
+ if (dclass != DClass.IN || !Options.check("noPrintIN")) {
+ sb.append(DClass.string(dclass));
+ sb.append("\t");
+ }
+ sb.append(Type.string(type));
+ String rdata = rrToString();
+ if (!rdata.equals("")) {
+ sb.append("\t");
+ sb.append(rdata);
+ }
+ return sb.toString();
+}
+
+/**
+ * Converts the text format of an RR to the internal format - must be overriden
+ */
+abstract void
+rdataFromString(Tokenizer st, Name origin) throws IOException;
+
+/**
+ * Converts a String into a byte array.
+ */
+protected static byte []
+byteArrayFromString(String s) throws TextParseException {
+ byte [] array = s.getBytes();
+ boolean escaped = false;
+ boolean hasEscapes = false;
+
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] == '\\') {
+ hasEscapes = true;
+ break;
+ }
+ }
+ if (!hasEscapes) {
+ if (array.length > 255) {
+ throw new TextParseException("text string too long");
+ }
+ return array;
+ }
+
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+
+ int digits = 0;
+ int intval = 0;
+ for (int i = 0; i < array.length; i++) {
+ byte b = array[i];
+ if (escaped) {
+ if (b >= '0' && b <= '9' && digits < 3) {
+ digits++;
+ intval *= 10;
+ intval += (b - '0');
+ if (intval > 255)
+ throw new TextParseException
+ ("bad escape");
+ if (digits < 3)
+ continue;
+ b = (byte) intval;
+ }
+ else if (digits > 0 && digits < 3)
+ throw new TextParseException("bad escape");
+ os.write(b);
+ escaped = false;
+ }
+ else if (array[i] == '\\') {
+ escaped = true;
+ digits = 0;
+ intval = 0;
+ }
+ else
+ os.write(array[i]);
+ }
+ if (digits > 0 && digits < 3)
+ throw new TextParseException("bad escape");
+ array = os.toByteArray();
+ if (array.length > 255) {
+ throw new TextParseException("text string too long");
+ }
+
+ return os.toByteArray();
+}
+
+/**
+ * Converts a byte array into a String.
+ */
+protected static String
+byteArrayToString(byte [] array, boolean quote) {
+ StringBuffer sb = new StringBuffer();
+ if (quote)
+ sb.append('"');
+ for (int i = 0; i < array.length; i++) {
+ int b = array[i] & 0xFF;
+ if (b < 0x20 || b >= 0x7f) {
+ sb.append('\\');
+ sb.append(byteFormat.format(b));
+ } else if (b == '"' || b == '\\') {
+ sb.append('\\');
+ sb.append((char)b);
+ } else
+ sb.append((char)b);
+ }
+ if (quote)
+ sb.append('"');
+ return sb.toString();
+}
+
+/**
+ * Converts a byte array into the unknown RR format.
+ */
+protected static String
+unknownToString(byte [] data) {
+ StringBuffer sb = new StringBuffer();
+ sb.append("\\# ");
+ sb.append(data.length);
+ sb.append(" ");
+ sb.append(base16.toString(data));
+ return sb.toString();
+}
+
+/**
+ * Builds a new Record from its textual representation
+ * @param name The owner name of the record.
+ * @param type The record's type.
+ * @param dclass The record's class.
+ * @param ttl The record's time to live.
+ * @param st A tokenizer containing the textual representation of the rdata.
+ * @param origin The default origin to be appended to relative domain names.
+ * @return The new record
+ * @throws IOException The text format was invalid.
+ */
+public static Record
+fromString(Name name, int type, int dclass, long ttl, Tokenizer st, Name origin)
+throws IOException
+{
+ Record rec;
+
+ if (!name.isAbsolute())
+ throw new RelativeNameException(name);
+ Type.check(type);
+ DClass.check(dclass);
+ TTL.check(ttl);
+
+ Tokenizer.Token t = st.get();
+ if (t.type == Tokenizer.IDENTIFIER && t.value.equals("\\#")) {
+ int length = st.getUInt16();
+ byte [] data = st.getHex();
+ if (data == null) {
+ data = new byte[0];
+ }
+ if (length != data.length)
+ throw st.exception("invalid unknown RR encoding: " +
+ "length mismatch");
+ DNSInput in = new DNSInput(data);
+ return newRecord(name, type, dclass, ttl, length, in);
+ }
+ st.unget();
+ rec = getEmptyRecord(name, type, dclass, ttl, true);
+ rec.rdataFromString(st, origin);
+ t = st.get();
+ if (t.type != Tokenizer.EOL && t.type != Tokenizer.EOF) {
+ throw st.exception("unexpected tokens at end of record");
+ }
+ return rec;
+}
+
+/**
+ * Builds a new Record from its textual representation
+ * @param name The owner name of the record.
+ * @param type The record's type.
+ * @param dclass The record's class.
+ * @param ttl The record's time to live.
+ * @param s The textual representation of the rdata.
+ * @param origin The default origin to be appended to relative domain names.
+ * @return The new record
+ * @throws IOException The text format was invalid.
+ */
+public static Record
+fromString(Name name, int type, int dclass, long ttl, String s, Name origin)
+throws IOException
+{
+ return fromString(name, type, dclass, ttl, new Tokenizer(s), origin);
+}
+
+/**
+ * Returns the record's name
+ * @see Name
+ */
+public Name
+getName() {
+ return name;
+}
+
+/**
+ * Returns the record's type
+ * @see Type
+ */
+public int
+getType() {
+ return type;
+}
+
+/**
+ * Returns the type of RRset that this record would belong to. For all types
+ * except RRSIG, this is equivalent to getType().
+ * @return The type of record, if not RRSIG. If the type is RRSIG,
+ * the type covered is returned.
+ * @see Type
+ * @see RRset
+ * @see SIGRecord
+ */
+public int
+getRRsetType() {
+ if (type == Type.RRSIG) {
+ RRSIGRecord sig = (RRSIGRecord) this;
+ return sig.getTypeCovered();
+ }
+ return type;
+}
+
+/**
+ * Returns the record's class
+ */
+public int
+getDClass() {
+ return dclass;
+}
+
+/**
+ * Returns the record's TTL
+ */
+public long
+getTTL() {
+ return ttl;
+}
+
+/**
+ * Converts the type-specific RR to wire format - must be overriden
+ */
+abstract void
+rrToWire(DNSOutput out, Compression c, boolean canonical);
+
+/**
+ * Determines if two Records could be part of the same RRset.
+ * This compares the name, type, and class of the Records; the ttl and
+ * rdata are not compared.
+ */
+public boolean
+sameRRset(Record rec) {
+ return (getRRsetType() == rec.getRRsetType() &&
+ dclass == rec.dclass &&
+ name.equals(rec.name));
+}
+
+/**
+ * Determines if two Records are identical. This compares the name, type,
+ * class, and rdata (with names canonicalized). The TTLs are not compared.
+ * @param arg The record to compare to
+ * @return true if the records are equal, false otherwise.
+ */
+public boolean
+equals(Object arg) {
+ if (arg == null || !(arg instanceof Record))
+ return false;
+ Record r = (Record) arg;
+ if (type != r.type || dclass != r.dclass || !name.equals(r.name))
+ return false;
+ byte [] array1 = rdataToWireCanonical();
+ byte [] array2 = r.rdataToWireCanonical();
+ return Arrays.equals(array1, array2);
+}
+
+/**
+ * Generates a hash code based on the Record's data.
+ */
+public int
+hashCode() {
+ byte [] array = toWireCanonical(true);
+ int code = 0;
+ for (int i = 0; i < array.length; i++)
+ code += ((code << 3) + (array[i] & 0xFF));
+ return code;
+}
+
+Record
+cloneRecord() {
+ try {
+ return (Record) clone();
+ }
+ catch (CloneNotSupportedException e) {
+ throw new IllegalStateException();
+ }
+}
+
+/**
+ * Creates a new record identical to the current record, but with a different
+ * name. This is most useful for replacing the name of a wildcard record.
+ */
+public Record
+withName(Name name) {
+ if (!name.isAbsolute())
+ throw new RelativeNameException(name);
+ Record rec = cloneRecord();
+ rec.name = name;
+ return rec;
+}
+
+/**
+ * Creates a new record identical to the current record, but with a different
+ * class and ttl. This is most useful for dynamic update.
+ */
+Record
+withDClass(int dclass, long ttl) {
+ Record rec = cloneRecord();
+ rec.dclass = dclass;
+ rec.ttl = ttl;
+ return rec;
+}
+
+/* Sets the TTL to the specified value. This is intentionally not public. */
+void
+setTTL(long ttl) {
+ this.ttl = ttl;
+}
+
+/**
+ * Compares this Record to another Object.
+ * @param o The Object to be compared.
+ * @return The value 0 if the argument is a record equivalent to this record;
+ * a value less than 0 if the argument is less than this record in the
+ * canonical ordering, and a value greater than 0 if the argument is greater
+ * than this record in the canonical ordering. The canonical ordering
+ * is defined to compare by name, class, type, and rdata.
+ * @throws ClassCastException if the argument is not a Record.
+ */
+public int
+compareTo(Object o) {
+ Record arg = (Record) o;
+
+ if (this == arg)
+ return (0);
+
+ int n = name.compareTo(arg.name);
+ if (n != 0)
+ return (n);
+ n = dclass - arg.dclass;
+ if (n != 0)
+ return (n);
+ n = type - arg.type;
+ if (n != 0)
+ return (n);
+ byte [] rdata1 = rdataToWireCanonical();
+ byte [] rdata2 = arg.rdataToWireCanonical();
+ for (int i = 0; i < rdata1.length && i < rdata2.length; i++) {
+ n = (rdata1[i] & 0xFF) - (rdata2[i] & 0xFF);
+ if (n != 0)
+ return (n);
+ }
+ return (rdata1.length - rdata2.length);
+}
+
+/**
+ * Returns the name for which additional data processing should be done
+ * for this record. This can be used both for building responses and
+ * parsing responses.
+ * @return The name to used for additional data processing, or null if this
+ * record type does not require additional data processing.
+ */
+public Name
+getAdditionalName() {
+ return null;
+}
+
+/* Checks that an int contains an unsigned 8 bit value */
+static int
+checkU8(String field, int val) {
+ if (val < 0 || val > 0xFF)
+ throw new IllegalArgumentException("\"" + field + "\" " + val +
+ " must be an unsigned 8 " +
+ "bit value");
+ return val;
+}
+
+/* Checks that an int contains an unsigned 16 bit value */
+static int
+checkU16(String field, int val) {
+ if (val < 0 || val > 0xFFFF)
+ throw new IllegalArgumentException("\"" + field + "\" " + val +
+ " must be an unsigned 16 " +
+ "bit value");
+ return val;
+}
+
+/* Checks that a long contains an unsigned 32 bit value */
+static long
+checkU32(String field, long val) {
+ if (val < 0 || val > 0xFFFFFFFFL)
+ throw new IllegalArgumentException("\"" + field + "\" " + val +
+ " must be an unsigned 32 " +
+ "bit value");
+ return val;
+}
+
+/* Checks that a name is absolute */
+static Name
+checkName(String field, Name name) {
+ if (!name.isAbsolute())
+ throw new RelativeNameException(name);
+ return name;
+}
+
+static byte []
+checkByteArrayLength(String field, byte [] array, int maxLength) {
+ if (array.length > 0xFFFF)
+ throw new IllegalArgumentException("\"" + field + "\" array " +
+ "must have no more than " +
+ maxLength + " elements");
+ byte [] out = new byte[array.length];
+ System.arraycopy(array, 0, out, 0, array.length);
+ return out;
+}
+
+}
diff --git a/src/org/xbill/DNS/RelativeNameException.java b/src/org/xbill/DNS/RelativeNameException.java
new file mode 100644
index 0000000..869fd39
--- /dev/null
+++ b/src/org/xbill/DNS/RelativeNameException.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * An exception thrown when a relative name is passed as an argument to
+ * a method requiring an absolute name.
+ *
+ * @author Brian Wellington
+ */
+
+public class RelativeNameException extends IllegalArgumentException {
+
+public
+RelativeNameException(Name name) {
+ super("'" + name + "' is not an absolute name");
+}
+
+public
+RelativeNameException(String s) {
+ super(s);
+}
+
+}
diff --git a/src/org/xbill/DNS/ResolveThread.java b/src/org/xbill/DNS/ResolveThread.java
new file mode 100644
index 0000000..3087cdb
--- /dev/null
+++ b/src/org/xbill/DNS/ResolveThread.java
@@ -0,0 +1,45 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * A special-purpose thread used by Resolvers (both SimpleResolver and
+ * ExtendedResolver) to perform asynchronous queries.
+ *
+ * @author Brian Wellington
+ */
+
+class ResolveThread extends Thread {
+
+private Message query;
+private Object id;
+private ResolverListener listener;
+private Resolver res;
+
+/** Creates a new ResolveThread */
+public
+ResolveThread(Resolver res, Message query, Object id,
+ ResolverListener listener)
+{
+ this.res = res;
+ this.query = query;
+ this.id = id;
+ this.listener = listener;
+}
+
+
+/**
+ * Performs the query, and executes the callback.
+ */
+public void
+run() {
+ try {
+ Message response = res.send(query);
+ listener.receiveMessage(id, response);
+ }
+ catch (Exception e) {
+ listener.handleException(id, e);
+ }
+}
+
+}
diff --git a/src/org/xbill/DNS/Resolver.java b/src/org/xbill/DNS/Resolver.java
new file mode 100644
index 0000000..7d28d40
--- /dev/null
+++ b/src/org/xbill/DNS/Resolver.java
@@ -0,0 +1,95 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * Interface describing a resolver.
+ *
+ * @author Brian Wellington
+ */
+
+public interface Resolver {
+
+/**
+ * Sets the port to communicate with on the server
+ * @param port The port to send messages to
+ */
+void setPort(int port);
+
+/**
+ * Sets whether TCP connections will be sent by default
+ * @param flag Indicates whether TCP connections are made
+ */
+void setTCP(boolean flag);
+
+/**
+ * Sets whether truncated responses will be ignored. If not, a truncated
+ * response over UDP will cause a retransmission over TCP.
+ * @param flag Indicates whether truncated responses should be ignored.
+ */
+void setIgnoreTruncation(boolean flag);
+
+/**
+ * Sets the EDNS version used on outgoing messages.
+ * @param level The EDNS level to use. 0 indicates EDNS0 and -1 indicates no
+ * EDNS.
+ * @throws IllegalArgumentException An invalid level was indicated.
+ */
+void setEDNS(int level);
+
+/**
+ * Sets the EDNS information on outgoing messages.
+ * @param level The EDNS level to use. 0 indicates EDNS0 and -1 indicates no
+ * EDNS.
+ * @param payloadSize The maximum DNS packet size that this host is capable
+ * of receiving over UDP. If 0 is specified, the default (1280) is used.
+ * @param flags EDNS extended flags to be set in the OPT record.
+ * @param options EDNS options to be set in the OPT record, specified as a
+ * List of OPTRecord.Option elements.
+ * @throws IllegalArgumentException An invalid field was specified.
+ * @see OPTRecord
+ */
+void setEDNS(int level, int payloadSize, int flags, List options);
+
+/**
+ * Specifies the TSIG key that messages will be signed with
+ * @param key The key
+ */
+void setTSIGKey(TSIG key);
+
+/**
+ * Sets the amount of time to wait for a response before giving up.
+ * @param secs The number of seconds to wait.
+ * @param msecs The number of milliseconds to wait.
+ */
+void setTimeout(int secs, int msecs);
+
+/**
+ * Sets the amount of time to wait for a response before giving up.
+ * @param secs The number of seconds to wait.
+ */
+void setTimeout(int secs);
+
+/**
+ * Sends a message and waits for a response.
+ * @param query The query to send.
+ * @return The response
+ * @throws IOException An error occurred while sending or receiving.
+ */
+Message send(Message query) throws IOException;
+
+/**
+ * Asynchronously sends a message registering a listener to receive a callback
+ * on success or exception. Multiple asynchronous lookups can be performed
+ * in parallel. Since the callback may be invoked before the function returns,
+ * external synchronization is necessary.
+ * @param query The query to send
+ * @param listener The object containing the callbacks.
+ * @return An identifier, which is also a parameter in the callback
+ */
+Object sendAsync(final Message query, final ResolverListener listener);
+
+}
diff --git a/src/org/xbill/DNS/ResolverConfig.java b/src/org/xbill/DNS/ResolverConfig.java
new file mode 100644
index 0000000..7b09daf
--- /dev/null
+++ b/src/org/xbill/DNS/ResolverConfig.java
@@ -0,0 +1,509 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+/**
+ * A class that tries to locate name servers and the search path to
+ * be appended to unqualified names.
+ *
+ * The following are attempted, in order, until one succeeds.
+ * <UL>
+ * <LI>The properties 'dns.server' and 'dns.search' (comma delimited lists)
+ * are checked. The servers can either be IP addresses or hostnames
+ * (which are resolved using Java's built in DNS support).
+ * <LI>The sun.net.dns.ResolverConfiguration class is queried.
+ * <LI>On Unix, /etc/resolv.conf is parsed.
+ * <LI>On Windows, ipconfig/winipcfg is called and its output parsed. This
+ * may fail for non-English versions on Windows.
+ * <LI>"localhost" is used as the nameserver, and the search path is empty.
+ * </UL>
+ *
+ * These routines will be called internally when creating Resolvers/Lookups
+ * without explicitly specifying server names, and can also be called
+ * directly if desired.
+ *
+ * @author Brian Wellington
+ * @author <a href="mailto:yannick@meudal.net">Yannick Meudal</a>
+ * @author <a href="mailto:arnt@gulbrandsen.priv.no">Arnt Gulbrandsen</a>
+ */
+
+public class ResolverConfig {
+
+private String [] servers = null;
+private Name [] searchlist = null;
+private int ndots = -1;
+
+private static ResolverConfig currentConfig;
+
+static {
+ refresh();
+}
+
+public
+ResolverConfig() {
+ if (findProperty())
+ return;
+ if (findSunJVM())
+ return;
+ if (servers == null || searchlist == null) {
+ String OS = System.getProperty("os.name");
+ String vendor = System.getProperty("java.vendor");
+ if (OS.indexOf("Windows") != -1) {
+ if (OS.indexOf("95") != -1 ||
+ OS.indexOf("98") != -1 ||
+ OS.indexOf("ME") != -1)
+ find95();
+ else
+ findNT();
+ } else if (OS.indexOf("NetWare") != -1) {
+ findNetware();
+ } else if (vendor.indexOf("Android") != -1) {
+ findAndroid();
+ } else {
+ findUnix();
+ }
+ }
+}
+
+private void
+addServer(String server, List list) {
+ if (list.contains(server))
+ return;
+ if (Options.check("verbose"))
+ System.out.println("adding server " + server);
+ list.add(server);
+}
+
+private void
+addSearch(String search, List list) {
+ Name name;
+ if (Options.check("verbose"))
+ System.out.println("adding search " + search);
+ try {
+ name = Name.fromString(search, Name.root);
+ }
+ catch (TextParseException e) {
+ return;
+ }
+ if (list.contains(name))
+ return;
+ list.add(name);
+}
+
+private int
+parseNdots(String token) {
+ token = token.substring(6);
+ try {
+ int ndots = Integer.parseInt(token);
+ if (ndots >= 0) {
+ if (Options.check("verbose"))
+ System.out.println("setting ndots " + token);
+ return ndots;
+ }
+ }
+ catch (NumberFormatException e) {
+ }
+ return -1;
+}
+
+private void
+configureFromLists(List lserver, List lsearch) {
+ if (servers == null && lserver.size() > 0)
+ servers = (String []) lserver.toArray(new String[0]);
+ if (searchlist == null && lsearch.size() > 0)
+ searchlist = (Name []) lsearch.toArray(new Name[0]);
+}
+
+private void
+configureNdots(int lndots) {
+ if (ndots < 0 && lndots > 0)
+ ndots = lndots;
+}
+
+/**
+ * Looks in the system properties to find servers and a search path.
+ * Servers are defined by dns.server=server1,server2...
+ * The search path is defined by dns.search=domain1,domain2...
+ */
+private boolean
+findProperty() {
+ String prop;
+ List lserver = new ArrayList(0);
+ List lsearch = new ArrayList(0);
+ StringTokenizer st;
+
+ prop = System.getProperty("dns.server");
+ if (prop != null) {
+ st = new StringTokenizer(prop, ",");
+ while (st.hasMoreTokens())
+ addServer(st.nextToken(), lserver);
+ }
+
+ prop = System.getProperty("dns.search");
+ if (prop != null) {
+ st = new StringTokenizer(prop, ",");
+ while (st.hasMoreTokens())
+ addSearch(st.nextToken(), lsearch);
+ }
+ configureFromLists(lserver, lsearch);
+ return (servers != null && searchlist != null);
+}
+
+/**
+ * Uses the undocumented Sun DNS implementation to determine the configuration.
+ * This doesn't work or even compile with all JVMs (gcj, for example).
+ */
+private boolean
+findSunJVM() {
+ List lserver = new ArrayList(0);
+ List lserver_tmp;
+ List lsearch = new ArrayList(0);
+ List lsearch_tmp;
+
+ try {
+ Class [] noClasses = new Class[0];
+ Object [] noObjects = new Object[0];
+ String resConfName = "sun.net.dns.ResolverConfiguration";
+ Class resConfClass = Class.forName(resConfName);
+ Object resConf;
+
+ // ResolverConfiguration resConf = ResolverConfiguration.open();
+ Method open = resConfClass.getDeclaredMethod("open", noClasses);
+ resConf = open.invoke(null, noObjects);
+
+ // lserver_tmp = resConf.nameservers();
+ Method nameservers = resConfClass.getMethod("nameservers",
+ noClasses);
+ lserver_tmp = (List) nameservers.invoke(resConf, noObjects);
+
+ // lsearch_tmp = resConf.searchlist();
+ Method searchlist = resConfClass.getMethod("searchlist",
+ noClasses);
+ lsearch_tmp = (List) searchlist.invoke(resConf, noObjects);
+ }
+ catch (Exception e) {
+ return false;
+ }
+
+ if (lserver_tmp.size() == 0)
+ return false;
+
+ if (lserver_tmp.size() > 0) {
+ Iterator it = lserver_tmp.iterator();
+ while (it.hasNext())
+ addServer((String) it.next(), lserver);
+ }
+
+ if (lsearch_tmp.size() > 0) {
+ Iterator it = lsearch_tmp.iterator();
+ while (it.hasNext())
+ addSearch((String) it.next(), lsearch);
+ }
+ configureFromLists(lserver, lsearch);
+ return true;
+}
+
+/**
+ * Looks in /etc/resolv.conf to find servers and a search path.
+ * "nameserver" lines specify servers. "domain" and "search" lines
+ * define the search path.
+ */
+private void
+findResolvConf(String file) {
+ InputStream in = null;
+ try {
+ in = new FileInputStream(file);
+ }
+ catch (FileNotFoundException e) {
+ return;
+ }
+ InputStreamReader isr = new InputStreamReader(in);
+ BufferedReader br = new BufferedReader(isr);
+ List lserver = new ArrayList(0);
+ List lsearch = new ArrayList(0);
+ int lndots = -1;
+ try {
+ String line;
+ while ((line = br.readLine()) != null) {
+ if (line.startsWith("nameserver")) {
+ StringTokenizer st = new StringTokenizer(line);
+ st.nextToken(); /* skip nameserver */
+ addServer(st.nextToken(), lserver);
+ }
+ else if (line.startsWith("domain")) {
+ StringTokenizer st = new StringTokenizer(line);
+ st.nextToken(); /* skip domain */
+ if (!st.hasMoreTokens())
+ continue;
+ if (lsearch.isEmpty())
+ addSearch(st.nextToken(), lsearch);
+ }
+ else if (line.startsWith("search")) {
+ if (!lsearch.isEmpty())
+ lsearch.clear();
+ StringTokenizer st = new StringTokenizer(line);
+ st.nextToken(); /* skip search */
+ while (st.hasMoreTokens())
+ addSearch(st.nextToken(), lsearch);
+ }
+ else if(line.startsWith("options")) {
+ StringTokenizer st = new StringTokenizer(line);
+ st.nextToken(); /* skip options */
+ while (st.hasMoreTokens()) {
+ String token = st.nextToken();
+ if (token.startsWith("ndots:")) {
+ lndots = parseNdots(token);
+ }
+ }
+ }
+ }
+ br.close();
+ }
+ catch (IOException e) {
+ }
+
+ configureFromLists(lserver, lsearch);
+ configureNdots(lndots);
+}
+
+private void
+findUnix() {
+ findResolvConf("/etc/resolv.conf");
+}
+
+private void
+findNetware() {
+ findResolvConf("sys:/etc/resolv.cfg");
+}
+
+/**
+ * Parses the output of winipcfg or ipconfig.
+ */
+private void
+findWin(InputStream in, Locale locale) {
+ String packageName = ResolverConfig.class.getPackage().getName();
+ String resPackageName = packageName + ".windows.DNSServer";
+ ResourceBundle res;
+ if (locale != null)
+ res = ResourceBundle.getBundle(resPackageName, locale);
+ else
+ res = ResourceBundle.getBundle(resPackageName);
+
+ String host_name = res.getString("host_name");
+ String primary_dns_suffix = res.getString("primary_dns_suffix");
+ String dns_suffix = res.getString("dns_suffix");
+ String dns_servers = res.getString("dns_servers");
+
+ BufferedReader br = new BufferedReader(new InputStreamReader(in));
+ try {
+ List lserver = new ArrayList();
+ List lsearch = new ArrayList();
+ String line = null;
+ boolean readingServers = false;
+ boolean readingSearches = false;
+ while ((line = br.readLine()) != null) {
+ StringTokenizer st = new StringTokenizer(line);
+ if (!st.hasMoreTokens()) {
+ readingServers = false;
+ readingSearches = false;
+ continue;
+ }
+ String s = st.nextToken();
+ if (line.indexOf(":") != -1) {
+ readingServers = false;
+ readingSearches = false;
+ }
+
+ if (line.indexOf(host_name) != -1) {
+ while (st.hasMoreTokens())
+ s = st.nextToken();
+ Name name;
+ try {
+ name = Name.fromString(s, null);
+ }
+ catch (TextParseException e) {
+ continue;
+ }
+ if (name.labels() == 1)
+ continue;
+ addSearch(s, lsearch);
+ } else if (line.indexOf(primary_dns_suffix) != -1) {
+ while (st.hasMoreTokens())
+ s = st.nextToken();
+ if (s.equals(":"))
+ continue;
+ addSearch(s, lsearch);
+ readingSearches = true;
+ } else if (readingSearches ||
+ line.indexOf(dns_suffix) != -1)
+ {
+ while (st.hasMoreTokens())
+ s = st.nextToken();
+ if (s.equals(":"))
+ continue;
+ addSearch(s, lsearch);
+ readingSearches = true;
+ } else if (readingServers ||
+ line.indexOf(dns_servers) != -1)
+ {
+ while (st.hasMoreTokens())
+ s = st.nextToken();
+ if (s.equals(":"))
+ continue;
+ addServer(s, lserver);
+ readingServers = true;
+ }
+ }
+
+ configureFromLists(lserver, lsearch);
+ }
+ catch (IOException e) {
+ }
+ return;
+}
+
+private void
+findWin(InputStream in) {
+ String property = "org.xbill.DNS.windows.parse.buffer";
+ final int defaultBufSize = 8 * 1024;
+ int bufSize = Integer.getInteger(property, defaultBufSize).intValue();
+ BufferedInputStream b = new BufferedInputStream(in, bufSize);
+ b.mark(bufSize);
+ findWin(b, null);
+ if (servers == null) {
+ try {
+ b.reset();
+ }
+ catch (IOException e) {
+ return;
+ }
+ findWin(b, new Locale("", ""));
+ }
+}
+
+/**
+ * Calls winipcfg and parses the result to find servers and a search path.
+ */
+private void
+find95() {
+ String s = "winipcfg.out";
+ try {
+ Process p;
+ p = Runtime.getRuntime().exec("winipcfg /all /batch " + s);
+ p.waitFor();
+ File f = new File(s);
+ findWin(new FileInputStream(f));
+ new File(s).delete();
+ }
+ catch (Exception e) {
+ return;
+ }
+}
+
+/**
+ * Calls ipconfig and parses the result to find servers and a search path.
+ */
+private void
+findNT() {
+ try {
+ Process p;
+ p = Runtime.getRuntime().exec("ipconfig /all");
+ findWin(p.getInputStream());
+ p.destroy();
+ }
+ catch (Exception e) {
+ return;
+ }
+}
+
+/**
+ * Parses the output of getprop, which is the only way to get DNS
+ * info on Android. getprop might disappear in future releases, so
+ * this code comes with a use-by date.
+ */
+private void
+findAndroid() {
+ // This originally looked for all lines containing .dns; but
+ // http://code.google.com/p/android/issues/detail?id=2207#c73
+ // indicates that net.dns* should always be the active nameservers, so
+ // we use those.
+ String re1 = "^\\d+(\\.\\d+){3}$";
+ String re2 = "^[0-9a-f]+(:[0-9a-f]*)+:[0-9a-f]+$";
+ try {
+ ArrayList lserver = new ArrayList();
+ ArrayList lsearch = new ArrayList();
+ String line;
+ Process p = Runtime.getRuntime().exec("getprop");
+ InputStream in = p.getInputStream();
+ InputStreamReader isr = new InputStreamReader(in);
+ BufferedReader br = new BufferedReader(isr);
+ while ((line = br.readLine()) != null ) {
+ StringTokenizer t = new StringTokenizer(line, ":");
+ String name = t.nextToken();
+ if (name.indexOf( "net.dns" ) > -1) {
+ String v = t.nextToken();
+ v = v.replaceAll("[ \\[\\]]", "");
+ if ((v.matches(re1) || v.matches(re2)) &&
+ !lserver.contains(v))
+ lserver.add(v);
+ }
+ }
+ configureFromLists(lserver, lsearch);
+ } catch ( Exception e ) {
+ // ignore resolutely
+ }
+}
+
+/** Returns all located servers */
+public String []
+servers() {
+ return servers;
+}
+
+/** Returns the first located server */
+public String
+server() {
+ if (servers == null)
+ return null;
+ return servers[0];
+}
+
+/** Returns all entries in the located search path */
+public Name []
+searchPath() {
+ return searchlist;
+}
+
+/**
+ * Returns the located ndots value, or the default (1) if not configured.
+ * Note that ndots can only be configured in a resolv.conf file, and will only
+ * take effect if ResolverConfig uses resolv.conf directly (that is, if the
+ * JVM does not include the sun.net.dns.ResolverConfiguration class).
+ */
+public int
+ndots() {
+ if (ndots < 0)
+ return 1;
+ return ndots;
+}
+
+/** Gets the current configuration */
+public static synchronized ResolverConfig
+getCurrentConfig() {
+ return currentConfig;
+}
+
+/** Gets the current configuration */
+public static void
+refresh() {
+ ResolverConfig newConfig = new ResolverConfig();
+ synchronized (ResolverConfig.class) {
+ currentConfig = newConfig;
+ }
+}
+
+}
diff --git a/src/org/xbill/DNS/ResolverListener.java b/src/org/xbill/DNS/ResolverListener.java
new file mode 100644
index 0000000..accf82c
--- /dev/null
+++ b/src/org/xbill/DNS/ResolverListener.java
@@ -0,0 +1,30 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.util.EventListener;
+
+/**
+ * An interface to the asynchronous resolver.
+ * @see Resolver
+ *
+ * @author Brian Wellington
+ */
+
+public interface ResolverListener extends EventListener {
+
+/**
+ * The callback used by an asynchronous resolver
+ * @param id The identifier returned by Resolver.sendAsync()
+ * @param m The response message as returned by the Resolver
+ */
+void receiveMessage(Object id, Message m);
+
+/**
+ * The callback used by an asynchronous resolver when an exception is thrown
+ * @param id The identifier returned by Resolver.sendAsync()
+ * @param e The thrown exception
+ */
+void handleException(Object id, Exception e);
+
+}
diff --git a/src/org/xbill/DNS/ReverseMap.java b/src/org/xbill/DNS/ReverseMap.java
new file mode 100644
index 0000000..f4293de
--- /dev/null
+++ b/src/org/xbill/DNS/ReverseMap.java
@@ -0,0 +1,130 @@
+// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.net.*;
+
+/**
+ * A set functions designed to deal with DNS names used in reverse mappings.
+ * For the IPv4 address a.b.c.d, the reverse map name is d.c.b.a.in-addr.arpa.
+ * For an IPv6 address, the reverse map name is ...ip6.arpa.
+ *
+ * @author Brian Wellington
+ */
+
+public final class ReverseMap {
+
+private static Name inaddr4 = Name.fromConstantString("in-addr.arpa.");
+private static Name inaddr6 = Name.fromConstantString("ip6.arpa.");
+
+/* Otherwise the class could be instantiated */
+private
+ReverseMap() {}
+
+/**
+ * Creates a reverse map name corresponding to an address contained in
+ * an array of 4 bytes (for an IPv4 address) or 16 bytes (for an IPv6 address).
+ * @param addr The address from which to build a name.
+ * @return The name corresponding to the address in the reverse map.
+ */
+public static Name
+fromAddress(byte [] addr) {
+ if (addr.length != 4 && addr.length != 16)
+ throw new IllegalArgumentException("array must contain " +
+ "4 or 16 elements");
+
+ StringBuffer sb = new StringBuffer();
+ if (addr.length == 4) {
+ for (int i = addr.length - 1; i >= 0; i--) {
+ sb.append(addr[i] & 0xFF);
+ if (i > 0)
+ sb.append(".");
+ }
+ } else {
+ int [] nibbles = new int[2];
+ for (int i = addr.length - 1; i >= 0; i--) {
+ nibbles[0] = (addr[i] & 0xFF) >> 4;
+ nibbles[1] = (addr[i] & 0xFF) & 0xF;
+ for (int j = nibbles.length - 1; j >= 0; j--) {
+ sb.append(Integer.toHexString(nibbles[j]));
+ if (i > 0 || j > 0)
+ sb.append(".");
+ }
+ }
+ }
+
+ try {
+ if (addr.length == 4)
+ return Name.fromString(sb.toString(), inaddr4);
+ else
+ return Name.fromString(sb.toString(), inaddr6);
+ }
+ catch (TextParseException e) {
+ throw new IllegalStateException("name cannot be invalid");
+ }
+}
+
+/**
+ * Creates a reverse map name corresponding to an address contained in
+ * an array of 4 integers between 0 and 255 (for an IPv4 address) or 16
+ * integers between 0 and 255 (for an IPv6 address).
+ * @param addr The address from which to build a name.
+ * @return The name corresponding to the address in the reverse map.
+ */
+public static Name
+fromAddress(int [] addr) {
+ byte [] bytes = new byte[addr.length];
+ for (int i = 0; i < addr.length; i++) {
+ if (addr[i] < 0 || addr[i] > 0xFF)
+ throw new IllegalArgumentException("array must " +
+ "contain values " +
+ "between 0 and 255");
+ bytes[i] = (byte) addr[i];
+ }
+ return fromAddress(bytes);
+}
+
+/**
+ * Creates a reverse map name corresponding to an address contained in
+ * an InetAddress.
+ * @param addr The address from which to build a name.
+ * @return The name corresponding to the address in the reverse map.
+ */
+public static Name
+fromAddress(InetAddress addr) {
+ return fromAddress(addr.getAddress());
+}
+
+/**
+ * Creates a reverse map name corresponding to an address contained in
+ * a String.
+ * @param addr The address from which to build a name.
+ * @return The name corresponding to the address in the reverse map.
+ * @throws UnknownHostException The string does not contain a valid address.
+ */
+public static Name
+fromAddress(String addr, int family) throws UnknownHostException {
+ byte [] array = Address.toByteArray(addr, family);
+ if (array == null)
+ throw new UnknownHostException("Invalid IP address");
+ return fromAddress(array);
+}
+
+/**
+ * Creates a reverse map name corresponding to an address contained in
+ * a String.
+ * @param addr The address from which to build a name.
+ * @return The name corresponding to the address in the reverse map.
+ * @throws UnknownHostException The string does not contain a valid address.
+ */
+public static Name
+fromAddress(String addr) throws UnknownHostException {
+ byte [] array = Address.toByteArray(addr, Address.IPv4);
+ if (array == null)
+ array = Address.toByteArray(addr, Address.IPv6);
+ if (array == null)
+ throw new UnknownHostException("Invalid IP address");
+ return fromAddress(array);
+}
+
+}
diff --git a/src/org/xbill/DNS/SIG0.java b/src/org/xbill/DNS/SIG0.java
new file mode 100644
index 0000000..5a00e72
--- /dev/null
+++ b/src/org/xbill/DNS/SIG0.java
@@ -0,0 +1,79 @@
+// Copyright (c) 2001-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.security.PrivateKey;
+import java.util.Date;
+
+/**
+ * Creates SIG(0) transaction signatures.
+ *
+ * @author Pasi Eronen
+ * @author Brian Wellington
+ */
+
+public class SIG0 {
+
+/**
+ * The default validity period for outgoing SIG(0) signed messages.
+ * Can be overriden by the sig0validity option.
+ */
+private static final short VALIDITY = 300;
+
+private
+SIG0() { }
+
+/**
+ * Sign a message with SIG(0). The DNS key and private key must refer to the
+ * same underlying cryptographic key.
+ * @param message The message to be signed
+ * @param key The DNSKEY record to use as part of signing
+ * @param privkey The PrivateKey to use when signing
+ * @param previous If this message is a response, the SIG(0) from the query
+ */
+public static void
+signMessage(Message message, KEYRecord key, PrivateKey privkey,
+ SIGRecord previous) throws DNSSEC.DNSSECException
+{
+
+ int validity = Options.intValue("sig0validity");
+ if (validity < 0)
+ validity = VALIDITY;
+
+ long now = System.currentTimeMillis();
+ Date timeSigned = new Date(now);
+ Date timeExpires = new Date(now + validity * 1000);
+
+ SIGRecord sig = DNSSEC.signMessage(message, previous, key, privkey,
+ timeSigned, timeExpires);
+
+ message.addRecord(sig, Section.ADDITIONAL);
+}
+
+/**
+ * Verify a message using SIG(0).
+ * @param message The message to be signed
+ * @param b An array containing the message in unparsed form. This is
+ * necessary since SIG(0) signs the message in wire format, and we can't
+ * recreate the exact wire format (with the same name compression).
+ * @param key The KEY record to verify the signature with.
+ * @param previous If this message is a response, the SIG(0) from the query
+ */
+public static void
+verifyMessage(Message message, byte [] b, KEYRecord key, SIGRecord previous)
+ throws DNSSEC.DNSSECException
+{
+ SIGRecord sig = null;
+ Record [] additional = message.getSectionArray(Section.ADDITIONAL);
+ for (int i = 0; i < additional.length; i++) {
+ if (additional[i].getType() != Type.SIG)
+ continue;
+ if (((SIGRecord) additional[i]).getTypeCovered() != 0)
+ continue;
+ sig = (SIGRecord) additional[i];
+ break;
+ }
+ DNSSEC.verifyMessage(message, b, sig, previous, key);
+}
+
+}
diff --git a/src/org/xbill/DNS/SIGBase.java b/src/org/xbill/DNS/SIGBase.java
new file mode 100644
index 0000000..6e5f12d
--- /dev/null
+++ b/src/org/xbill/DNS/SIGBase.java
@@ -0,0 +1,193 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.util.*;
+import org.xbill.DNS.utils.*;
+
+/**
+ * The base class for SIG/RRSIG records, which have identical formats
+ *
+ * @author Brian Wellington
+ */
+
+abstract class SIGBase extends Record {
+
+private static final long serialVersionUID = -3738444391533812369L;
+
+protected int covered;
+protected int alg, labels;
+protected long origttl;
+protected Date expire, timeSigned;
+protected int footprint;
+protected Name signer;
+protected byte [] signature;
+
+protected
+SIGBase() {}
+
+public
+SIGBase(Name name, int type, int dclass, long ttl, int covered, int alg,
+ long origttl, Date expire, Date timeSigned, int footprint, Name signer,
+ byte [] signature)
+{
+ super(name, type, dclass, ttl);
+ Type.check(covered);
+ TTL.check(origttl);
+ this.covered = covered;
+ this.alg = checkU8("alg", alg);
+ this.labels = name.labels() - 1;
+ if (name.isWild())
+ this.labels--;
+ this.origttl = origttl;
+ this.expire = expire;
+ this.timeSigned = timeSigned;
+ this.footprint = checkU16("footprint", footprint);
+ this.signer = checkName("signer", signer);
+ this.signature = signature;
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ covered = in.readU16();
+ alg = in.readU8();
+ labels = in.readU8();
+ origttl = in.readU32();
+ expire = new Date(1000 * in.readU32());
+ timeSigned = new Date(1000 * in.readU32());
+ footprint = in.readU16();
+ signer = new Name(in);
+ signature = in.readByteArray();
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ String typeString = st.getString();
+ covered = Type.value(typeString);
+ if (covered < 0)
+ throw st.exception("Invalid type: " + typeString);
+ String algString = st.getString();
+ alg = DNSSEC.Algorithm.value(algString);
+ if (alg < 0)
+ throw st.exception("Invalid algorithm: " + algString);
+ labels = st.getUInt8();
+ origttl = st.getTTL();
+ expire = FormattedTime.parse(st.getString());
+ timeSigned = FormattedTime.parse(st.getString());
+ footprint = st.getUInt16();
+ signer = st.getName(origin);
+ signature = st.getBase64();
+}
+
+/** Converts the RRSIG/SIG Record to a String */
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append (Type.string(covered));
+ sb.append (" ");
+ sb.append (alg);
+ sb.append (" ");
+ sb.append (labels);
+ sb.append (" ");
+ sb.append (origttl);
+ sb.append (" ");
+ if (Options.check("multiline"))
+ sb.append ("(\n\t");
+ sb.append (FormattedTime.format(expire));
+ sb.append (" ");
+ sb.append (FormattedTime.format(timeSigned));
+ sb.append (" ");
+ sb.append (footprint);
+ sb.append (" ");
+ sb.append (signer);
+ if (Options.check("multiline")) {
+ sb.append("\n");
+ sb.append(base64.formatString(signature, 64, "\t",
+ true));
+ } else {
+ sb.append (" ");
+ sb.append(base64.toString(signature));
+ }
+ return sb.toString();
+}
+
+/** Returns the RRset type covered by this signature */
+public int
+getTypeCovered() {
+ return covered;
+}
+
+/**
+ * Returns the cryptographic algorithm of the key that generated the signature
+ */
+public int
+getAlgorithm() {
+ return alg;
+}
+
+/**
+ * Returns the number of labels in the signed domain name. This may be
+ * different than the record's domain name if the record is a wildcard
+ * record.
+ */
+public int
+getLabels() {
+ return labels;
+}
+
+/** Returns the original TTL of the RRset */
+public long
+getOrigTTL() {
+ return origttl;
+}
+
+/** Returns the time at which the signature expires */
+public Date
+getExpire() {
+ return expire;
+}
+
+/** Returns the time at which this signature was generated */
+public Date
+getTimeSigned() {
+ return timeSigned;
+}
+
+/** Returns The footprint/key id of the signing key. */
+public int
+getFootprint() {
+ return footprint;
+}
+
+/** Returns the owner of the signing key */
+public Name
+getSigner() {
+ return signer;
+}
+
+/** Returns the binary data representing the signature */
+public byte []
+getSignature() {
+ return signature;
+}
+
+void
+setSignature(byte [] signature) {
+ this.signature = signature;
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeU16(covered);
+ out.writeU8(alg);
+ out.writeU8(labels);
+ out.writeU32(origttl);
+ out.writeU32(expire.getTime() / 1000);
+ out.writeU32(timeSigned.getTime() / 1000);
+ out.writeU16(footprint);
+ signer.toWire(out, null, canonical);
+ out.writeByteArray(signature);
+}
+
+}
diff --git a/src/org/xbill/DNS/SIGRecord.java b/src/org/xbill/DNS/SIGRecord.java
new file mode 100644
index 0000000..8b6f58d
--- /dev/null
+++ b/src/org/xbill/DNS/SIGRecord.java
@@ -0,0 +1,50 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.util.*;
+
+/**
+ * Signature - A SIG provides the digital signature of an RRset, so that
+ * the data can be authenticated by a DNSSEC-capable resolver. The
+ * signature is usually generated by a key contained in a KEYRecord
+ * @see RRset
+ * @see DNSSEC
+ * @see KEYRecord
+ *
+ * @author Brian Wellington
+ */
+
+public class SIGRecord extends SIGBase {
+
+private static final long serialVersionUID = 4963556060953589058L;
+
+SIGRecord() {}
+
+Record
+getObject() {
+ return new SIGRecord();
+}
+
+/**
+ * Creates an SIG Record from the given data
+ * @param covered The RRset type covered by this signature
+ * @param alg The cryptographic algorithm of the key that generated the
+ * signature
+ * @param origttl The original TTL of the RRset
+ * @param expire The time at which the signature expires
+ * @param timeSigned The time at which this signature was generated
+ * @param footprint The footprint/key id of the signing key.
+ * @param signer The owner of the signing key
+ * @param signature Binary data representing the signature
+ */
+public
+SIGRecord(Name name, int dclass, long ttl, int covered, int alg, long origttl,
+ Date expire, Date timeSigned, int footprint, Name signer,
+ byte [] signature)
+{
+ super(name, Type.SIG, dclass, ttl, covered, alg, origttl, expire,
+ timeSigned, footprint, signer, signature);
+}
+
+}
diff --git a/src/org/xbill/DNS/SOARecord.java b/src/org/xbill/DNS/SOARecord.java
new file mode 100644
index 0000000..7f27077
--- /dev/null
+++ b/src/org/xbill/DNS/SOARecord.java
@@ -0,0 +1,162 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+
+/**
+ * Start of Authority - describes properties of a zone.
+ *
+ * @author Brian Wellington
+ */
+
+public class SOARecord extends Record {
+
+private static final long serialVersionUID = 1049740098229303931L;
+
+private Name host, admin;
+private long serial, refresh, retry, expire, minimum;
+
+SOARecord() {}
+
+Record
+getObject() {
+ return new SOARecord();
+}
+
+/**
+ * Creates an SOA Record from the given data
+ * @param host The primary name server for the zone
+ * @param admin The zone administrator's address
+ * @param serial The zone's serial number
+ * @param refresh The amount of time until a secondary checks for a new serial
+ * number
+ * @param retry The amount of time between a secondary's checks for a new
+ * serial number
+ * @param expire The amount of time until a secondary expires a zone
+ * @param minimum The minimum TTL for records in the zone
+*/
+public
+SOARecord(Name name, int dclass, long ttl, Name host, Name admin,
+ long serial, long refresh, long retry, long expire, long minimum)
+{
+ super(name, Type.SOA, dclass, ttl);
+ this.host = checkName("host", host);
+ this.admin = checkName("admin", admin);
+ this.serial = checkU32("serial", serial);
+ this.refresh = checkU32("refresh", refresh);
+ this.retry = checkU32("retry", retry);
+ this.expire = checkU32("expire", expire);
+ this.minimum = checkU32("minimum", minimum);
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ host = new Name(in);
+ admin = new Name(in);
+ serial = in.readU32();
+ refresh = in.readU32();
+ retry = in.readU32();
+ expire = in.readU32();
+ minimum = in.readU32();
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ host = st.getName(origin);
+ admin = st.getName(origin);
+ serial = st.getUInt32();
+ refresh = st.getTTLLike();
+ retry = st.getTTLLike();
+ expire = st.getTTLLike();
+ minimum = st.getTTLLike();
+}
+
+/** Convert to a String */
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(host);
+ sb.append(" ");
+ sb.append(admin);
+ if (Options.check("multiline")) {
+ sb.append(" (\n\t\t\t\t\t");
+ sb.append(serial);
+ sb.append("\t; serial\n\t\t\t\t\t");
+ sb.append(refresh);
+ sb.append("\t; refresh\n\t\t\t\t\t");
+ sb.append(retry);
+ sb.append("\t; retry\n\t\t\t\t\t");
+ sb.append(expire);
+ sb.append("\t; expire\n\t\t\t\t\t");
+ sb.append(minimum);
+ sb.append(" )\t; minimum");
+ } else {
+ sb.append(" ");
+ sb.append(serial);
+ sb.append(" ");
+ sb.append(refresh);
+ sb.append(" ");
+ sb.append(retry);
+ sb.append(" ");
+ sb.append(expire);
+ sb.append(" ");
+ sb.append(minimum);
+ }
+ return sb.toString();
+}
+
+/** Returns the primary name server */
+public Name
+getHost() {
+ return host;
+}
+
+/** Returns the zone administrator's address */
+public Name
+getAdmin() {
+ return admin;
+}
+
+/** Returns the zone's serial number */
+public long
+getSerial() {
+ return serial;
+}
+
+/** Returns the zone refresh interval */
+public long
+getRefresh() {
+ return refresh;
+}
+
+/** Returns the zone retry interval */
+public long
+getRetry() {
+ return retry;
+}
+
+/** Returns the time until a secondary expires a zone */
+public long
+getExpire() {
+ return expire;
+}
+
+/** Returns the minimum TTL for records in the zone */
+public long
+getMinimum() {
+ return minimum;
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ host.toWire(out, c, canonical);
+ admin.toWire(out, c, canonical);
+ out.writeU32(serial);
+ out.writeU32(refresh);
+ out.writeU32(retry);
+ out.writeU32(expire);
+ out.writeU32(minimum);
+}
+
+}
diff --git a/src/org/xbill/DNS/SPFRecord.java b/src/org/xbill/DNS/SPFRecord.java
new file mode 100644
index 0000000..a286220
--- /dev/null
+++ b/src/org/xbill/DNS/SPFRecord.java
@@ -0,0 +1,44 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.util.*;
+
+/**
+ * Sender Policy Framework (RFC 4408, experimental)
+ *
+ * @author Brian Wellington
+ */
+
+public class SPFRecord extends TXTBase {
+
+private static final long serialVersionUID = -2100754352801658722L;
+
+SPFRecord() {}
+
+Record
+getObject() {
+ return new SPFRecord();
+}
+
+/**
+ * Creates a SPF Record from the given data
+ * @param strings The text strings
+ * @throws IllegalArgumentException One of the strings has invalid escapes
+ */
+public
+SPFRecord(Name name, int dclass, long ttl, List strings) {
+ super(name, Type.SPF, dclass, ttl, strings);
+}
+
+/**
+ * Creates a SPF Record from the given data
+ * @param string One text string
+ * @throws IllegalArgumentException The string has invalid escapes
+ */
+public
+SPFRecord(Name name, int dclass, long ttl, String string) {
+ super(name, Type.SPF, dclass, ttl, string);
+}
+
+}
diff --git a/src/org/xbill/DNS/SRVRecord.java b/src/org/xbill/DNS/SRVRecord.java
new file mode 100644
index 0000000..c0635fb
--- /dev/null
+++ b/src/org/xbill/DNS/SRVRecord.java
@@ -0,0 +1,114 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+
+/**
+ * Server Selection Record - finds hosts running services in a domain. An
+ * SRV record will normally be named _&lt;service&gt;._&lt;protocol&gt;.domain
+ * - examples would be _sips._tcp.example.org (for the secure SIP protocol) and
+ * _http._tcp.example.com (if HTTP used SRV records)
+ *
+ * @author Brian Wellington
+ */
+
+public class SRVRecord extends Record {
+
+private static final long serialVersionUID = -3886460132387522052L;
+
+private int priority, weight, port;
+private Name target;
+
+SRVRecord() {}
+
+Record
+getObject() {
+ return new SRVRecord();
+}
+
+/**
+ * Creates an SRV Record from the given data
+ * @param priority The priority of this SRV. Records with lower priority
+ * are preferred.
+ * @param weight The weight, used to select between records at the same
+ * priority.
+ * @param port The TCP/UDP port that the service uses
+ * @param target The host running the service
+ */
+public
+SRVRecord(Name name, int dclass, long ttl, int priority,
+ int weight, int port, Name target)
+{
+ super(name, Type.SRV, dclass, ttl);
+ this.priority = checkU16("priority", priority);
+ this.weight = checkU16("weight", weight);
+ this.port = checkU16("port", port);
+ this.target = checkName("target", target);
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ priority = in.readU16();
+ weight = in.readU16();
+ port = in.readU16();
+ target = new Name(in);
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ priority = st.getUInt16();
+ weight = st.getUInt16();
+ port = st.getUInt16();
+ target = st.getName(origin);
+}
+
+/** Converts rdata to a String */
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(priority + " ");
+ sb.append(weight + " ");
+ sb.append(port + " ");
+ sb.append(target);
+ return sb.toString();
+}
+
+/** Returns the priority */
+public int
+getPriority() {
+ return priority;
+}
+
+/** Returns the weight */
+public int
+getWeight() {
+ return weight;
+}
+
+/** Returns the port that the service runs on */
+public int
+getPort() {
+ return port;
+}
+
+/** Returns the host running that the service */
+public Name
+getTarget() {
+ return target;
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeU16(priority);
+ out.writeU16(weight);
+ out.writeU16(port);
+ target.toWire(out, null, canonical);
+}
+
+public Name
+getAdditionalName() {
+ return target;
+}
+
+}
diff --git a/src/org/xbill/DNS/SSHFPRecord.java b/src/org/xbill/DNS/SSHFPRecord.java
new file mode 100644
index 0000000..079741e
--- /dev/null
+++ b/src/org/xbill/DNS/SSHFPRecord.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import org.xbill.DNS.utils.*;
+
+/**
+ * SSH Fingerprint - stores the fingerprint of an SSH host key.
+ *
+ * @author Brian Wellington
+ */
+
+public class SSHFPRecord extends Record {
+
+private static final long serialVersionUID = -8104701402654687025L;
+
+public static class Algorithm {
+ private Algorithm() {}
+
+ public static final int RSA = 1;
+ public static final int DSS = 2;
+}
+
+public static class Digest {
+ private Digest() {}
+
+ public static final int SHA1 = 1;
+}
+
+private int alg;
+private int digestType;
+private byte [] fingerprint;
+
+SSHFPRecord() {}
+
+Record
+getObject() {
+ return new SSHFPRecord();
+}
+
+/**
+ * Creates an SSHFP Record from the given data.
+ * @param alg The public key's algorithm.
+ * @param digestType The public key's digest type.
+ * @param fingerprint The public key's fingerprint.
+ */
+public
+SSHFPRecord(Name name, int dclass, long ttl, int alg, int digestType,
+ byte [] fingerprint)
+{
+ super(name, Type.SSHFP, dclass, ttl);
+ this.alg = checkU8("alg", alg);
+ this.digestType = checkU8("digestType", digestType);
+ this.fingerprint = fingerprint;
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ alg = in.readU8();
+ digestType = in.readU8();
+ fingerprint = in.readByteArray();
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ alg = st.getUInt8();
+ digestType = st.getUInt8();
+ fingerprint = st.getHex(true);
+}
+
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(alg);
+ sb.append(" ");
+ sb.append(digestType);
+ sb.append(" ");
+ sb.append(base16.toString(fingerprint));
+ return sb.toString();
+}
+
+/** Returns the public key's algorithm. */
+public int
+getAlgorithm() {
+ return alg;
+}
+
+/** Returns the public key's digest type. */
+public int
+getDigestType() {
+ return digestType;
+}
+
+/** Returns the fingerprint */
+public byte []
+getFingerPrint() {
+ return fingerprint;
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeU8(alg);
+ out.writeU8(digestType);
+ out.writeByteArray(fingerprint);
+}
+
+}
diff --git a/src/org/xbill/DNS/Section.java b/src/org/xbill/DNS/Section.java
new file mode 100644
index 0000000..e0c8caa
--- /dev/null
+++ b/src/org/xbill/DNS/Section.java
@@ -0,0 +1,92 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * Constants and functions relating to DNS message sections
+ *
+ * @author Brian Wellington
+ */
+
+public final class Section {
+
+/** The question (first) section */
+public static final int QUESTION = 0;
+
+/** The answer (second) section */
+public static final int ANSWER = 1;
+
+/** The authority (third) section */
+public static final int AUTHORITY = 2;
+
+/** The additional (fourth) section */
+public static final int ADDITIONAL = 3;
+
+/* Aliases for dynamic update */
+/** The zone (first) section of a dynamic update message */
+public static final int ZONE = 0;
+
+/** The prerequisite (second) section of a dynamic update message */
+public static final int PREREQ = 1;
+
+/** The update (third) section of a dynamic update message */
+public static final int UPDATE = 2;
+
+private static Mnemonic sections = new Mnemonic("Message Section",
+ Mnemonic.CASE_LOWER);
+private static String [] longSections = new String[4];
+private static String [] updateSections = new String[4];
+
+static {
+ sections.setMaximum(3);
+ sections.setNumericAllowed(true);
+
+ sections.add(QUESTION, "qd");
+ sections.add(ANSWER, "an");
+ sections.add(AUTHORITY, "au");
+ sections.add(ADDITIONAL, "ad");
+
+ longSections[QUESTION] = "QUESTIONS";
+ longSections[ANSWER] = "ANSWERS";
+ longSections[AUTHORITY] = "AUTHORITY RECORDS";
+ longSections[ADDITIONAL] = "ADDITIONAL RECORDS";
+
+ updateSections[ZONE] = "ZONE";
+ updateSections[PREREQ] = "PREREQUISITES";
+ updateSections[UPDATE] = "UPDATE RECORDS";
+ updateSections[ADDITIONAL] = "ADDITIONAL RECORDS";
+}
+
+private
+Section() {}
+
+/** Converts a numeric Section into an abbreviation String */
+public static String
+string(int i) {
+ return sections.getText(i);
+}
+
+/** Converts a numeric Section into a full description String */
+public static String
+longString(int i) {
+ sections.check(i);
+ return longSections[i];
+}
+
+/**
+ * Converts a numeric Section into a full description String for an update
+ * Message.
+ */
+public static String
+updString(int i) {
+ sections.check(i);
+ return updateSections[i];
+}
+
+/** Converts a String representation of a Section into its numeric value */
+public static int
+value(String s) {
+ return sections.getValue(s);
+}
+
+}
diff --git a/src/org/xbill/DNS/Serial.java b/src/org/xbill/DNS/Serial.java
new file mode 100644
index 0000000..3a146c6
--- /dev/null
+++ b/src/org/xbill/DNS/Serial.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * Helper functions for doing serial arithmetic. These should be used when
+ * setting/checking SOA serial numbers. SOA serial number arithmetic is
+ * defined in RFC 1982.
+ *
+ * @author Brian Wellington
+ */
+
+public final class Serial {
+
+private static final long MAX32 = 0xFFFFFFFFL;
+
+private
+Serial() {
+}
+
+/**
+ * Compares two numbers using serial arithmetic. The numbers are assumed
+ * to be 32 bit unsigned integers stored in longs.
+ * @param serial1 The first integer
+ * @param serial2 The second integer
+ * @return 0 if the 2 numbers are equal, a positive number if serial1 is greater
+ * than serial2, and a negative number if serial2 is greater than serial1.
+ * @throws IllegalArgumentException serial1 or serial2 is out of range
+ */
+public static int
+compare(long serial1, long serial2) {
+ if (serial1 < 0 || serial1 > MAX32)
+ throw new IllegalArgumentException(serial1 + " out of range");
+ if (serial2 < 0 || serial2 > MAX32)
+ throw new IllegalArgumentException(serial2 + " out of range");
+ long diff = serial1 - serial2;
+ if (diff >= MAX32)
+ diff -= (MAX32 + 1);
+ else if (diff < -MAX32)
+ diff += (MAX32 + 1);
+ return (int)diff;
+}
+
+/**
+ * Increments a serial number. The number is assumed to be a 32 bit unsigned
+ * integer stored in a long. This basically adds 1 and resets the value to
+ * 0 if it is 2^32.
+ * @param serial The serial number
+ * @return The incremented serial number
+ * @throws IllegalArgumentException serial is out of range
+ */
+public static long
+increment(long serial) {
+ if (serial < 0 || serial > MAX32)
+ throw new IllegalArgumentException(serial + " out of range");
+ if (serial == MAX32)
+ return 0;
+ return serial + 1;
+}
+
+}
diff --git a/src/org/xbill/DNS/SetResponse.java b/src/org/xbill/DNS/SetResponse.java
new file mode 100644
index 0000000..05d9f32
--- /dev/null
+++ b/src/org/xbill/DNS/SetResponse.java
@@ -0,0 +1,202 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.util.*;
+
+/**
+ * The Response from a query to Cache.lookupRecords() or Zone.findRecords()
+ * @see Cache
+ * @see Zone
+ *
+ * @author Brian Wellington
+ */
+
+public class SetResponse {
+
+/**
+ * The Cache contains no information about the requested name/type
+ */
+static final int UNKNOWN = 0;
+
+/**
+ * The Zone does not contain the requested name, or the Cache has
+ * determined that the name does not exist.
+ */
+static final int NXDOMAIN = 1;
+
+/**
+ * The Zone contains the name, but no data of the requested type,
+ * or the Cache has determined that the name exists and has no data
+ * of the requested type.
+ */
+static final int NXRRSET = 2;
+
+/**
+ * A delegation enclosing the requested name was found.
+ */
+static final int DELEGATION = 3;
+
+/**
+ * The Cache/Zone found a CNAME when looking for the name.
+ * @see CNAMERecord
+ */
+static final int CNAME = 4;
+
+/**
+ * The Cache/Zone found a DNAME when looking for the name.
+ * @see DNAMERecord
+ */
+static final int DNAME = 5;
+
+/**
+ * The Cache/Zone has successfully answered the question for the
+ * requested name/type/class.
+ */
+static final int SUCCESSFUL = 6;
+
+private static final SetResponse unknown = new SetResponse(UNKNOWN);
+private static final SetResponse nxdomain = new SetResponse(NXDOMAIN);
+private static final SetResponse nxrrset = new SetResponse(NXRRSET);
+
+private int type;
+private Object data;
+
+private
+SetResponse() {}
+
+SetResponse(int type, RRset rrset) {
+ if (type < 0 || type > 6)
+ throw new IllegalArgumentException("invalid type");
+ this.type = type;
+ this.data = rrset;
+}
+
+SetResponse(int type) {
+ if (type < 0 || type > 6)
+ throw new IllegalArgumentException("invalid type");
+ this.type = type;
+ this.data = null;
+}
+
+static SetResponse
+ofType(int type) {
+ switch (type) {
+ case UNKNOWN:
+ return unknown;
+ case NXDOMAIN:
+ return nxdomain;
+ case NXRRSET:
+ return nxrrset;
+ case DELEGATION:
+ case CNAME:
+ case DNAME:
+ case SUCCESSFUL:
+ SetResponse sr = new SetResponse();
+ sr.type = type;
+ sr.data = null;
+ return sr;
+ default:
+ throw new IllegalArgumentException("invalid type");
+ }
+}
+
+void
+addRRset(RRset rrset) {
+ if (data == null)
+ data = new ArrayList();
+ List l = (List) data;
+ l.add(rrset);
+}
+
+/** Is the answer to the query unknown? */
+public boolean
+isUnknown() {
+ return (type == UNKNOWN);
+}
+
+/** Is the answer to the query that the name does not exist? */
+public boolean
+isNXDOMAIN() {
+ return (type == NXDOMAIN);
+}
+
+/** Is the answer to the query that the name exists, but the type does not? */
+public boolean
+isNXRRSET() {
+ return (type == NXRRSET);
+}
+
+/** Is the result of the lookup that the name is below a delegation? */
+public boolean
+isDelegation() {
+ return (type == DELEGATION);
+}
+
+/** Is the result of the lookup a CNAME? */
+public boolean
+isCNAME() {
+ return (type == CNAME);
+}
+
+/** Is the result of the lookup a DNAME? */
+public boolean
+isDNAME() {
+ return (type == DNAME);
+}
+
+/** Was the query successful? */
+public boolean
+isSuccessful() {
+ return (type == SUCCESSFUL);
+}
+
+/** If the query was successful, return the answers */
+public RRset []
+answers() {
+ if (type != SUCCESSFUL)
+ return null;
+ List l = (List) data;
+ return (RRset []) l.toArray(new RRset[l.size()]);
+}
+
+/**
+ * If the query encountered a CNAME, return it.
+ */
+public CNAMERecord
+getCNAME() {
+ return (CNAMERecord)((RRset)data).first();
+}
+
+/**
+ * If the query encountered a DNAME, return it.
+ */
+public DNAMERecord
+getDNAME() {
+ return (DNAMERecord)((RRset)data).first();
+}
+
+/**
+ * If the query hit a delegation point, return the NS set.
+ */
+public RRset
+getNS() {
+ return (RRset)data;
+}
+
+/** Prints the value of the SetResponse */
+public String
+toString() {
+ switch (type) {
+ case UNKNOWN: return "unknown";
+ case NXDOMAIN: return "NXDOMAIN";
+ case NXRRSET: return "NXRRSET";
+ case DELEGATION: return "delegation: " + data;
+ case CNAME: return "CNAME: " + data;
+ case DNAME: return "DNAME: " + data;
+ case SUCCESSFUL: return "successful";
+ default: throw new IllegalStateException();
+ }
+}
+
+}
diff --git a/src/org/xbill/DNS/SimpleResolver.java b/src/org/xbill/DNS/SimpleResolver.java
new file mode 100644
index 0000000..7436133
--- /dev/null
+++ b/src/org/xbill/DNS/SimpleResolver.java
@@ -0,0 +1,351 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.util.*;
+import java.io.*;
+import java.net.*;
+
+/**
+ * An implementation of Resolver that sends one query to one server.
+ * SimpleResolver handles TCP retries, transaction security (TSIG), and
+ * EDNS 0.
+ * @see Resolver
+ * @see TSIG
+ * @see OPTRecord
+ *
+ * @author Brian Wellington
+ */
+
+
+public class SimpleResolver implements Resolver {
+
+/** The default port to send queries to */
+public static final int DEFAULT_PORT = 53;
+
+/** The default EDNS payload size */
+public static final int DEFAULT_EDNS_PAYLOADSIZE = 1280;
+
+private InetSocketAddress address;
+private InetSocketAddress localAddress;
+private boolean useTCP, ignoreTruncation;
+private OPTRecord queryOPT;
+private TSIG tsig;
+private long timeoutValue = 10 * 1000;
+
+private static final short DEFAULT_UDPSIZE = 512;
+
+private static String defaultResolver = "localhost";
+private static int uniqueID = 0;
+
+/**
+ * Creates a SimpleResolver that will query the specified host
+ * @exception UnknownHostException Failure occurred while finding the host
+ */
+public
+SimpleResolver(String hostname) throws UnknownHostException {
+ if (hostname == null) {
+ hostname = ResolverConfig.getCurrentConfig().server();
+ if (hostname == null)
+ hostname = defaultResolver;
+ }
+ InetAddress addr;
+ if (hostname.equals("0"))
+ addr = InetAddress.getLocalHost();
+ else
+ addr = InetAddress.getByName(hostname);
+ address = new InetSocketAddress(addr, DEFAULT_PORT);
+}
+
+/**
+ * Creates a SimpleResolver. The host to query is either found by using
+ * ResolverConfig, or the default host is used.
+ * @see ResolverConfig
+ * @exception UnknownHostException Failure occurred while finding the host
+ */
+public
+SimpleResolver() throws UnknownHostException {
+ this(null);
+}
+
+/**
+ * Gets the destination address associated with this SimpleResolver.
+ * Messages sent using this SimpleResolver will be sent to this address.
+ * @return The destination address associated with this SimpleResolver.
+ */
+InetSocketAddress
+getAddress() {
+ return address;
+}
+
+/** Sets the default host (initially localhost) to query */
+public static void
+setDefaultResolver(String hostname) {
+ defaultResolver = hostname;
+}
+
+public void
+setPort(int port) {
+ address = new InetSocketAddress(address.getAddress(), port);
+}
+
+/**
+ * Sets the address of the server to communicate with.
+ * @param addr The address of the DNS server
+ */
+public void
+setAddress(InetSocketAddress addr) {
+ address = addr;
+}
+
+/**
+ * Sets the address of the server to communicate with (on the default
+ * DNS port)
+ * @param addr The address of the DNS server
+ */
+public void
+setAddress(InetAddress addr) {
+ address = new InetSocketAddress(addr, address.getPort());
+}
+
+/**
+ * Sets the local address to bind to when sending messages.
+ * @param addr The local address to send messages from.
+ */
+public void
+setLocalAddress(InetSocketAddress addr) {
+ localAddress = addr;
+}
+
+/**
+ * Sets the local address to bind to when sending messages. A random port
+ * will be used.
+ * @param addr The local address to send messages from.
+ */
+public void
+setLocalAddress(InetAddress addr) {
+ localAddress = new InetSocketAddress(addr, 0);
+}
+
+public void
+setTCP(boolean flag) {
+ this.useTCP = flag;
+}
+
+public void
+setIgnoreTruncation(boolean flag) {
+ this.ignoreTruncation = flag;
+}
+
+public void
+setEDNS(int level, int payloadSize, int flags, List options) {
+ if (level != 0 && level != -1)
+ throw new IllegalArgumentException("invalid EDNS level - " +
+ "must be 0 or -1");
+ if (payloadSize == 0)
+ payloadSize = DEFAULT_EDNS_PAYLOADSIZE;
+ queryOPT = new OPTRecord(payloadSize, 0, level, flags, options);
+}
+
+public void
+setEDNS(int level) {
+ setEDNS(level, 0, 0, null);
+}
+
+public void
+setTSIGKey(TSIG key) {
+ tsig = key;
+}
+
+TSIG
+getTSIGKey() {
+ return tsig;
+}
+
+public void
+setTimeout(int secs, int msecs) {
+ timeoutValue = (long)secs * 1000 + msecs;
+}
+
+public void
+setTimeout(int secs) {
+ setTimeout(secs, 0);
+}
+
+long
+getTimeout() {
+ return timeoutValue;
+}
+
+private Message
+parseMessage(byte [] b) throws WireParseException {
+ try {
+ return (new Message(b));
+ }
+ catch (IOException e) {
+ if (Options.check("verbose"))
+ e.printStackTrace();
+ if (!(e instanceof WireParseException))
+ e = new WireParseException("Error parsing message");
+ throw (WireParseException) e;
+ }
+}
+
+private void
+verifyTSIG(Message query, Message response, byte [] b, TSIG tsig) {
+ if (tsig == null)
+ return;
+ int error = tsig.verify(response, b, query.getTSIG());
+ if (Options.check("verbose"))
+ System.err.println("TSIG verify: " + Rcode.TSIGstring(error));
+}
+
+private void
+applyEDNS(Message query) {
+ if (queryOPT == null || query.getOPT() != null)
+ return;
+ query.addRecord(queryOPT, Section.ADDITIONAL);
+}
+
+private int
+maxUDPSize(Message query) {
+ OPTRecord opt = query.getOPT();
+ if (opt == null)
+ return DEFAULT_UDPSIZE;
+ else
+ return opt.getPayloadSize();
+}
+
+/**
+ * Sends a message to a single server and waits for a response. No checking
+ * is done to ensure that the response is associated with the query.
+ * @param query The query to send.
+ * @return The response.
+ * @throws IOException An error occurred while sending or receiving.
+ */
+public Message
+send(Message query) throws IOException {
+ if (Options.check("verbose"))
+ System.err.println("Sending to " +
+ address.getAddress().getHostAddress() +
+ ":" + address.getPort());
+
+ if (query.getHeader().getOpcode() == Opcode.QUERY) {
+ Record question = query.getQuestion();
+ if (question != null && question.getType() == Type.AXFR)
+ return sendAXFR(query);
+ }
+
+ query = (Message) query.clone();
+ applyEDNS(query);
+ if (tsig != null)
+ tsig.apply(query, null);
+
+ byte [] out = query.toWire(Message.MAXLENGTH);
+ int udpSize = maxUDPSize(query);
+ boolean tcp = false;
+ long endTime = System.currentTimeMillis() + timeoutValue;
+ do {
+ byte [] in;
+
+ if (useTCP || out.length > udpSize)
+ tcp = true;
+ if (tcp)
+ in = TCPClient.sendrecv(localAddress, address, out,
+ endTime);
+ else
+ in = UDPClient.sendrecv(localAddress, address, out,
+ udpSize, endTime);
+
+ /*
+ * Check that the response is long enough.
+ */
+ if (in.length < Header.LENGTH) {
+ throw new WireParseException("invalid DNS header - " +
+ "too short");
+ }
+ /*
+ * Check that the response ID matches the query ID. We want
+ * to check this before actually parsing the message, so that
+ * if there's a malformed response that's not ours, it
+ * doesn't confuse us.
+ */
+ int id = ((in[0] & 0xFF) << 8) + (in[1] & 0xFF);
+ int qid = query.getHeader().getID();
+ if (id != qid) {
+ String error = "invalid message id: expected " + qid +
+ "; got id " + id;
+ if (tcp) {
+ throw new WireParseException(error);
+ } else {
+ if (Options.check("verbose")) {
+ System.err.println(error);
+ }
+ continue;
+ }
+ }
+ Message response = parseMessage(in);
+ verifyTSIG(query, response, in, tsig);
+ if (!tcp && !ignoreTruncation &&
+ response.getHeader().getFlag(Flags.TC))
+ {
+ tcp = true;
+ continue;
+ }
+ return response;
+ } while (true);
+}
+
+/**
+ * Asynchronously sends a message to a single server, registering a listener
+ * to receive a callback on success or exception. Multiple asynchronous
+ * lookups can be performed in parallel. Since the callback may be invoked
+ * before the function returns, external synchronization is necessary.
+ * @param query The query to send
+ * @param listener The object containing the callbacks.
+ * @return An identifier, which is also a parameter in the callback
+ */
+public Object
+sendAsync(final Message query, final ResolverListener listener) {
+ final Object id;
+ synchronized (this) {
+ id = new Integer(uniqueID++);
+ }
+ Record question = query.getQuestion();
+ String qname;
+ if (question != null)
+ qname = question.getName().toString();
+ else
+ qname = "(none)";
+ String name = this.getClass() + ": " + qname;
+ Thread thread = new ResolveThread(this, query, id, listener);
+ thread.setName(name);
+ thread.setDaemon(true);
+ thread.start();
+ return id;
+}
+
+private Message
+sendAXFR(Message query) throws IOException {
+ Name qname = query.getQuestion().getName();
+ ZoneTransferIn xfrin = ZoneTransferIn.newAXFR(qname, address, tsig);
+ xfrin.setTimeout((int)(getTimeout() / 1000));
+ xfrin.setLocalAddress(localAddress);
+ try {
+ xfrin.run();
+ }
+ catch (ZoneTransferException e) {
+ throw new WireParseException(e.getMessage());
+ }
+ List records = xfrin.getAXFR();
+ Message response = new Message(query.getHeader().getID());
+ response.getHeader().setFlag(Flags.AA);
+ response.getHeader().setFlag(Flags.QR);
+ response.addRecord(query.getQuestion(), Section.QUESTION);
+ Iterator it = records.iterator();
+ while (it.hasNext())
+ response.addRecord((Record)it.next(), Section.ANSWER);
+ return response;
+}
+
+}
diff --git a/src/org/xbill/DNS/SingleCompressedNameBase.java b/src/org/xbill/DNS/SingleCompressedNameBase.java
new file mode 100644
index 0000000..790ca1f
--- /dev/null
+++ b/src/org/xbill/DNS/SingleCompressedNameBase.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * Implements common functionality for the many record types whose format
+ * is a single compressed name.
+ *
+ * @author Brian Wellington
+ */
+
+abstract class SingleCompressedNameBase extends SingleNameBase {
+
+private static final long serialVersionUID = -236435396815460677L;
+
+protected
+SingleCompressedNameBase() {}
+
+protected
+SingleCompressedNameBase(Name name, int type, int dclass, long ttl,
+ Name singleName, String description)
+{
+ super(name, type, dclass, ttl, singleName, description);
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ singleName.toWire(out, c, canonical);
+}
+
+}
diff --git a/src/org/xbill/DNS/SingleNameBase.java b/src/org/xbill/DNS/SingleNameBase.java
new file mode 100644
index 0000000..6d6c041
--- /dev/null
+++ b/src/org/xbill/DNS/SingleNameBase.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+
+/**
+ * Implements common functionality for the many record types whose format
+ * is a single name.
+ *
+ * @author Brian Wellington
+ */
+
+abstract class SingleNameBase extends Record {
+
+private static final long serialVersionUID = -18595042501413L;
+
+protected Name singleName;
+
+protected
+SingleNameBase() {}
+
+protected
+SingleNameBase(Name name, int type, int dclass, long ttl) {
+ super(name, type, dclass, ttl);
+}
+
+protected
+SingleNameBase(Name name, int type, int dclass, long ttl, Name singleName,
+ String description)
+{
+ super(name, type, dclass, ttl);
+ this.singleName = checkName(description, singleName);
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ singleName = new Name(in);
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ singleName = st.getName(origin);
+}
+
+String
+rrToString() {
+ return singleName.toString();
+}
+
+protected Name
+getSingleName() {
+ return singleName;
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ singleName.toWire(out, null, canonical);
+}
+
+}
diff --git a/src/org/xbill/DNS/TCPClient.java b/src/org/xbill/DNS/TCPClient.java
new file mode 100644
index 0000000..1f17d72
--- /dev/null
+++ b/src/org/xbill/DNS/TCPClient.java
@@ -0,0 +1,132 @@
+// Copyright (c) 2005 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.net.*;
+import java.nio.*;
+import java.nio.channels.*;
+
+final class TCPClient extends Client {
+
+public
+TCPClient(long endTime) throws IOException {
+ super(SocketChannel.open(), endTime);
+}
+
+void
+bind(SocketAddress addr) throws IOException {
+ SocketChannel channel = (SocketChannel) key.channel();
+ channel.socket().bind(addr);
+}
+
+void
+connect(SocketAddress addr) throws IOException {
+ SocketChannel channel = (SocketChannel) key.channel();
+ if (channel.connect(addr))
+ return;
+ key.interestOps(SelectionKey.OP_CONNECT);
+ try {
+ while (!channel.finishConnect()) {
+ if (!key.isConnectable())
+ blockUntil(key, endTime);
+ }
+ }
+ finally {
+ if (key.isValid())
+ key.interestOps(0);
+ }
+}
+
+void
+send(byte [] data) throws IOException {
+ SocketChannel channel = (SocketChannel) key.channel();
+ verboseLog("TCP write", data);
+ byte [] lengthArray = new byte[2];
+ lengthArray[0] = (byte)(data.length >>> 8);
+ lengthArray[1] = (byte)(data.length & 0xFF);
+ ByteBuffer [] buffers = new ByteBuffer[2];
+ buffers[0] = ByteBuffer.wrap(lengthArray);
+ buffers[1] = ByteBuffer.wrap(data);
+ int nsent = 0;
+ key.interestOps(SelectionKey.OP_WRITE);
+ try {
+ while (nsent < data.length + 2) {
+ if (key.isWritable()) {
+ long n = channel.write(buffers);
+ if (n < 0)
+ throw new EOFException();
+ nsent += (int) n;
+ if (nsent < data.length + 2 &&
+ System.currentTimeMillis() > endTime)
+ throw new SocketTimeoutException();
+ } else
+ blockUntil(key, endTime);
+ }
+ }
+ finally {
+ if (key.isValid())
+ key.interestOps(0);
+ }
+}
+
+private byte []
+_recv(int length) throws IOException {
+ SocketChannel channel = (SocketChannel) key.channel();
+ int nrecvd = 0;
+ byte [] data = new byte[length];
+ ByteBuffer buffer = ByteBuffer.wrap(data);
+ key.interestOps(SelectionKey.OP_READ);
+ try {
+ while (nrecvd < length) {
+ if (key.isReadable()) {
+ long n = channel.read(buffer);
+ if (n < 0)
+ throw new EOFException();
+ nrecvd += (int) n;
+ if (nrecvd < length &&
+ System.currentTimeMillis() > endTime)
+ throw new SocketTimeoutException();
+ } else
+ blockUntil(key, endTime);
+ }
+ }
+ finally {
+ if (key.isValid())
+ key.interestOps(0);
+ }
+ return data;
+}
+
+byte []
+recv() throws IOException {
+ byte [] buf = _recv(2);
+ int length = ((buf[0] & 0xFF) << 8) + (buf[1] & 0xFF);
+ byte [] data = _recv(length);
+ verboseLog("TCP read", data);
+ return data;
+}
+
+static byte []
+sendrecv(SocketAddress local, SocketAddress remote, byte [] data, long endTime)
+throws IOException
+{
+ TCPClient client = new TCPClient(endTime);
+ try {
+ if (local != null)
+ client.bind(local);
+ client.connect(remote);
+ client.send(data);
+ return client.recv();
+ }
+ finally {
+ client.cleanup();
+ }
+}
+
+static byte []
+sendrecv(SocketAddress addr, byte [] data, long endTime) throws IOException {
+ return sendrecv(null, addr, data, endTime);
+}
+
+}
diff --git a/src/org/xbill/DNS/TKEYRecord.java b/src/org/xbill/DNS/TKEYRecord.java
new file mode 100644
index 0000000..4dcbb5c
--- /dev/null
+++ b/src/org/xbill/DNS/TKEYRecord.java
@@ -0,0 +1,225 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.util.*;
+import org.xbill.DNS.utils.*;
+
+/**
+ * Transaction Key - used to compute and/or securely transport a shared
+ * secret to be used with TSIG.
+ * @see TSIG
+ *
+ * @author Brian Wellington
+ */
+
+public class TKEYRecord extends Record {
+
+private static final long serialVersionUID = 8828458121926391756L;
+
+private Name alg;
+private Date timeInception;
+private Date timeExpire;
+private int mode, error;
+private byte [] key;
+private byte [] other;
+
+/** The key is assigned by the server (unimplemented) */
+public static final int SERVERASSIGNED = 1;
+
+/** The key is computed using a Diffie-Hellman key exchange */
+public static final int DIFFIEHELLMAN = 2;
+
+/** The key is computed using GSS_API (unimplemented) */
+public static final int GSSAPI = 3;
+
+/** The key is assigned by the resolver (unimplemented) */
+public static final int RESOLVERASSIGNED = 4;
+
+/** The key should be deleted */
+public static final int DELETE = 5;
+
+TKEYRecord() {}
+
+Record
+getObject() {
+ return new TKEYRecord();
+}
+
+/**
+ * Creates a TKEY Record from the given data.
+ * @param alg The shared key's algorithm
+ * @param timeInception The beginning of the validity period of the shared
+ * secret or keying material
+ * @param timeExpire The end of the validity period of the shared
+ * secret or keying material
+ * @param mode The mode of key agreement
+ * @param error The extended error field. Should be 0 in queries
+ * @param key The shared secret
+ * @param other The other data field. Currently unused
+ * responses.
+ */
+public
+TKEYRecord(Name name, int dclass, long ttl, Name alg,
+ Date timeInception, Date timeExpire, int mode, int error,
+ byte [] key, byte other[])
+{
+ super(name, Type.TKEY, dclass, ttl);
+ this.alg = checkName("alg", alg);
+ this.timeInception = timeInception;
+ this.timeExpire = timeExpire;
+ this.mode = checkU16("mode", mode);
+ this.error = checkU16("error", error);
+ this.key = key;
+ this.other = other;
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ alg = new Name(in);
+ timeInception = new Date(1000 * in.readU32());
+ timeExpire = new Date(1000 * in.readU32());
+ mode = in.readU16();
+ error = in.readU16();
+
+ int keylen = in.readU16();
+ if (keylen > 0)
+ key = in.readByteArray(keylen);
+ else
+ key = null;
+
+ int otherlen = in.readU16();
+ if (otherlen > 0)
+ other = in.readByteArray(otherlen);
+ else
+ other = null;
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ throw st.exception("no text format defined for TKEY");
+}
+
+protected String
+modeString() {
+ switch (mode) {
+ case SERVERASSIGNED: return "SERVERASSIGNED";
+ case DIFFIEHELLMAN: return "DIFFIEHELLMAN";
+ case GSSAPI: return "GSSAPI";
+ case RESOLVERASSIGNED: return "RESOLVERASSIGNED";
+ case DELETE: return "DELETE";
+ default: return Integer.toString(mode);
+ }
+}
+
+/** Converts rdata to a String */
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(alg);
+ sb.append(" ");
+ if (Options.check("multiline"))
+ sb.append("(\n\t");
+ sb.append(FormattedTime.format(timeInception));
+ sb.append(" ");
+ sb.append(FormattedTime.format(timeExpire));
+ sb.append(" ");
+ sb.append(modeString());
+ sb.append(" ");
+ sb.append(Rcode.TSIGstring(error));
+ if (Options.check("multiline")) {
+ sb.append("\n");
+ if (key != null) {
+ sb.append(base64.formatString(key, 64, "\t", false));
+ sb.append("\n");
+ }
+ if (other != null)
+ sb.append(base64.formatString(other, 64, "\t", false));
+ sb.append(" )");
+ } else {
+ sb.append(" ");
+ if (key != null) {
+ sb.append(base64.toString(key));
+ sb.append(" ");
+ }
+ if (other != null)
+ sb.append(base64.toString(other));
+ }
+ return sb.toString();
+}
+
+/** Returns the shared key's algorithm */
+public Name
+getAlgorithm() {
+ return alg;
+}
+
+/**
+ * Returns the beginning of the validity period of the shared secret or
+ * keying material
+ */
+public Date
+getTimeInception() {
+ return timeInception;
+}
+
+/**
+ * Returns the end of the validity period of the shared secret or
+ * keying material
+ */
+public Date
+getTimeExpire() {
+ return timeExpire;
+}
+
+/** Returns the key agreement mode */
+public int
+getMode() {
+ return mode;
+}
+
+/** Returns the extended error */
+public int
+getError() {
+ return error;
+}
+
+/** Returns the shared secret or keying material */
+public byte []
+getKey() {
+ return key;
+}
+
+/** Returns the other data */
+public byte []
+getOther() {
+ return other;
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ alg.toWire(out, null, canonical);
+
+ out.writeU32(timeInception.getTime() / 1000);
+ out.writeU32(timeExpire.getTime() / 1000);
+
+ out.writeU16(mode);
+ out.writeU16(error);
+
+ if (key != null) {
+ out.writeU16(key.length);
+ out.writeByteArray(key);
+ }
+ else
+ out.writeU16(0);
+
+ if (other != null) {
+ out.writeU16(other.length);
+ out.writeByteArray(other);
+ }
+ else
+ out.writeU16(0);
+}
+
+}
diff --git a/src/org/xbill/DNS/TLSARecord.java b/src/org/xbill/DNS/TLSARecord.java
new file mode 100644
index 0000000..48e2e80
--- /dev/null
+++ b/src/org/xbill/DNS/TLSARecord.java
@@ -0,0 +1,156 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import org.xbill.DNS.utils.*;
+
+/**
+ * Transport Layer Security Authentication
+ *
+ * @author Brian Wellington
+ */
+
+public class TLSARecord extends Record {
+
+private static final long serialVersionUID = 356494267028580169L;
+
+public static class CertificateUsage {
+ private CertificateUsage() {}
+
+ public static final int CA_CONSTRAINT = 0;
+ public static final int SERVICE_CERTIFICATE_CONSTRAINT = 1;
+ public static final int TRUST_ANCHOR_ASSERTION = 2;
+ public static final int DOMAIN_ISSUED_CERTIFICATE = 3;
+}
+
+public static class Selector {
+ private Selector() {}
+
+ /**
+ * Full certificate; the Certificate binary structure defined in
+ * [RFC5280]
+ */
+ public static final int FULL_CERTIFICATE = 0;
+
+ /**
+ * SubjectPublicKeyInfo; DER-encoded binary structure defined in
+ * [RFC5280]
+ */
+ public static final int SUBJECT_PUBLIC_KEY_INFO = 1;
+}
+
+public static class MatchingType {
+ private MatchingType() {}
+
+ /** Exact match on selected content */
+ public static final int EXACT = 0;
+
+ /** SHA-256 hash of selected content [RFC6234] */
+ public static final int SHA256 = 1;
+
+ /** SHA-512 hash of selected content [RFC6234] */
+ public static final int SHA512 = 2;
+}
+
+private int certificateUsage;
+private int selector;
+private int matchingType;
+private byte [] certificateAssociationData;
+
+TLSARecord() {}
+
+Record
+getObject() {
+ return new TLSARecord();
+}
+
+/**
+ * Creates an TLSA Record from the given data
+ * @param certificateUsage The provided association that will be used to
+ * match the certificate presented in the TLS handshake.
+ * @param selector The part of the TLS certificate presented by the server
+ * that will be matched against the association data.
+ * @param matchingType How the certificate association is presented.
+ * @param certificateAssociationData The "certificate association data" to be
+ * matched.
+ */
+public
+TLSARecord(Name name, int dclass, long ttl,
+ int certificateUsage, int selector, int matchingType,
+ byte [] certificateAssociationData)
+{
+ super(name, Type.TLSA, dclass, ttl);
+ this.certificateUsage = checkU8("certificateUsage", certificateUsage);
+ this.selector = checkU8("selector", selector);
+ this.matchingType = checkU8("matchingType", matchingType);
+ this.certificateAssociationData = checkByteArrayLength(
+ "certificateAssociationData",
+ certificateAssociationData,
+ 0xFFFF);
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ certificateUsage = in.readU8();
+ selector = in.readU8();
+ matchingType = in.readU8();
+ certificateAssociationData = in.readByteArray();
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ certificateUsage = st.getUInt8();
+ selector = st.getUInt8();
+ matchingType = st.getUInt8();
+ certificateAssociationData = st.getHex();
+}
+
+/** Converts rdata to a String */
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(certificateUsage);
+ sb.append(" ");
+ sb.append(selector);
+ sb.append(" ");
+ sb.append(matchingType);
+ sb.append(" ");
+ sb.append(base16.toString(certificateAssociationData));
+
+ return sb.toString();
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeU8(certificateUsage);
+ out.writeU8(selector);
+ out.writeU8(matchingType);
+ out.writeByteArray(certificateAssociationData);
+}
+
+/** Returns the certificate usage of the TLSA record */
+public int
+getCertificateUsage() {
+ return certificateUsage;
+}
+
+/** Returns the selector of the TLSA record */
+public int
+getSelector() {
+ return selector;
+}
+
+/** Returns the matching type of the TLSA record */
+public int
+getMatchingType() {
+ return matchingType;
+}
+
+/** Returns the certificate associate data of this TLSA record */
+public final byte []
+getCertificateAssociationData() {
+ return certificateAssociationData;
+}
+
+}
diff --git a/src/org/xbill/DNS/TSIG.java b/src/org/xbill/DNS/TSIG.java
new file mode 100644
index 0000000..d9e6972
--- /dev/null
+++ b/src/org/xbill/DNS/TSIG.java
@@ -0,0 +1,592 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.util.*;
+import org.xbill.DNS.utils.*;
+
+/**
+ * Transaction signature handling. This class generates and verifies
+ * TSIG records on messages, which provide transaction security.
+ * @see TSIGRecord
+ *
+ * @author Brian Wellington
+ */
+
+public class TSIG {
+
+private static final String HMAC_MD5_STR = "HMAC-MD5.SIG-ALG.REG.INT.";
+private static final String HMAC_SHA1_STR = "hmac-sha1.";
+private static final String HMAC_SHA224_STR = "hmac-sha224.";
+private static final String HMAC_SHA256_STR = "hmac-sha256.";
+private static final String HMAC_SHA384_STR = "hmac-sha384.";
+private static final String HMAC_SHA512_STR = "hmac-sha512.";
+
+/** The domain name representing the HMAC-MD5 algorithm. */
+public static final Name HMAC_MD5 = Name.fromConstantString(HMAC_MD5_STR);
+
+/** The domain name representing the HMAC-MD5 algorithm (deprecated). */
+public static final Name HMAC = HMAC_MD5;
+
+/** The domain name representing the HMAC-SHA1 algorithm. */
+public static final Name HMAC_SHA1 = Name.fromConstantString(HMAC_SHA1_STR);
+
+/**
+ * The domain name representing the HMAC-SHA224 algorithm.
+ * Note that SHA224 is not supported by Java out-of-the-box, this requires use
+ * of a third party provider like BouncyCastle.org.
+ */
+public static final Name HMAC_SHA224 = Name.fromConstantString(HMAC_SHA224_STR);
+
+/** The domain name representing the HMAC-SHA256 algorithm. */
+public static final Name HMAC_SHA256 = Name.fromConstantString(HMAC_SHA256_STR);
+
+/** The domain name representing the HMAC-SHA384 algorithm. */
+public static final Name HMAC_SHA384 = Name.fromConstantString(HMAC_SHA384_STR);
+
+/** The domain name representing the HMAC-SHA512 algorithm. */
+public static final Name HMAC_SHA512 = Name.fromConstantString(HMAC_SHA512_STR);
+
+/**
+ * The default fudge value for outgoing packets. Can be overriden by the
+ * tsigfudge option.
+ */
+public static final short FUDGE = 300;
+
+private Name name, alg;
+private String digest;
+private int digestBlockLength;
+private byte [] key;
+
+private void
+getDigest() {
+ if (alg.equals(HMAC_MD5)) {
+ digest = "md5";
+ digestBlockLength = 64;
+ } else if (alg.equals(HMAC_SHA1)) {
+ digest = "sha-1";
+ digestBlockLength = 64;
+ } else if (alg.equals(HMAC_SHA224)) {
+ digest = "sha-224";
+ digestBlockLength = 64;
+ } else if (alg.equals(HMAC_SHA256)) {
+ digest = "sha-256";
+ digestBlockLength = 64;
+ } else if (alg.equals(HMAC_SHA512)) {
+ digest = "sha-512";
+ digestBlockLength = 128;
+ } else if (alg.equals(HMAC_SHA384)) {
+ digest = "sha-384";
+ digestBlockLength = 128;
+ } else
+ throw new IllegalArgumentException("Invalid algorithm");
+}
+
+/**
+ * Creates a new TSIG key, which can be used to sign or verify a message.
+ * @param algorithm The algorithm of the shared key.
+ * @param name The name of the shared key.
+ * @param key The shared key's data.
+ */
+public
+TSIG(Name algorithm, Name name, byte [] key) {
+ this.name = name;
+ this.alg = algorithm;
+ this.key = key;
+ getDigest();
+}
+
+/**
+ * Creates a new TSIG key with the hmac-md5 algorithm, which can be used to
+ * sign or verify a message.
+ * @param name The name of the shared key.
+ * @param key The shared key's data.
+ */
+public
+TSIG(Name name, byte [] key) {
+ this(HMAC_MD5, name, key);
+}
+
+/**
+ * Creates a new TSIG object, which can be used to sign or verify a message.
+ * @param name The name of the shared key.
+ * @param key The shared key's data represented as a base64 encoded string.
+ * @throws IllegalArgumentException The key name is an invalid name
+ * @throws IllegalArgumentException The key data is improperly encoded
+ */
+public
+TSIG(Name algorithm, String name, String key) {
+ this.key = base64.fromString(key);
+ if (this.key == null)
+ throw new IllegalArgumentException("Invalid TSIG key string");
+ try {
+ this.name = Name.fromString(name, Name.root);
+ }
+ catch (TextParseException e) {
+ throw new IllegalArgumentException("Invalid TSIG key name");
+ }
+ this.alg = algorithm;
+ getDigest();
+}
+
+/**
+ * Creates a new TSIG object, which can be used to sign or verify a message.
+ * @param name The name of the shared key.
+ * @param algorithm The algorithm of the shared key. The legal values are
+ * "hmac-md5", "hmac-sha1", "hmac-sha224", "hmac-sha256", "hmac-sha384", and
+ * "hmac-sha512".
+ * @param key The shared key's data represented as a base64 encoded string.
+ * @throws IllegalArgumentException The key name is an invalid name
+ * @throws IllegalArgumentException The key data is improperly encoded
+ */
+public
+TSIG(String algorithm, String name, String key) {
+ this(HMAC_MD5, name, key);
+ if (algorithm.equalsIgnoreCase("hmac-md5"))
+ this.alg = HMAC_MD5;
+ else if (algorithm.equalsIgnoreCase("hmac-sha1"))
+ this.alg = HMAC_SHA1;
+ else if (algorithm.equalsIgnoreCase("hmac-sha224"))
+ this.alg = HMAC_SHA224;
+ else if (algorithm.equalsIgnoreCase("hmac-sha256"))
+ this.alg = HMAC_SHA256;
+ else if (algorithm.equalsIgnoreCase("hmac-sha384"))
+ this.alg = HMAC_SHA384;
+ else if (algorithm.equalsIgnoreCase("hmac-sha512"))
+ this.alg = HMAC_SHA512;
+ else
+ throw new IllegalArgumentException("Invalid TSIG algorithm");
+ getDigest();
+}
+
+/**
+ * Creates a new TSIG object with the hmac-md5 algorithm, which can be used to
+ * sign or verify a message.
+ * @param name The name of the shared key
+ * @param key The shared key's data, represented as a base64 encoded string.
+ * @throws IllegalArgumentException The key name is an invalid name
+ * @throws IllegalArgumentException The key data is improperly encoded
+ */
+public
+TSIG(String name, String key) {
+ this(HMAC_MD5, name, key);
+}
+
+/**
+ * Creates a new TSIG object, which can be used to sign or verify a message.
+ * @param str The TSIG key, in the form name:secret, name/secret,
+ * alg:name:secret, or alg/name/secret. If an algorithm is specified, it must
+ * be "hmac-md5", "hmac-sha1", or "hmac-sha256".
+ * @throws IllegalArgumentException The string does not contain both a name
+ * and secret.
+ * @throws IllegalArgumentException The key name is an invalid name
+ * @throws IllegalArgumentException The key data is improperly encoded
+ */
+static public TSIG
+fromString(String str) {
+ String [] parts = str.split("[:/]", 3);
+ if (parts.length < 2)
+ throw new IllegalArgumentException("Invalid TSIG key " +
+ "specification");
+ if (parts.length == 3) {
+ try {
+ return new TSIG(parts[0], parts[1], parts[2]);
+ } catch (IllegalArgumentException e) {
+ parts = str.split("[:/]", 2);
+ }
+ }
+ return new TSIG(HMAC_MD5, parts[0], parts[1]);
+}
+
+/**
+ * Generates a TSIG record with a specific error for a message that has
+ * been rendered.
+ * @param m The message
+ * @param b The rendered message
+ * @param error The error
+ * @param old If this message is a response, the TSIG from the request
+ * @return The TSIG record to be added to the message
+ */
+public TSIGRecord
+generate(Message m, byte [] b, int error, TSIGRecord old) {
+ Date timeSigned;
+ if (error != Rcode.BADTIME)
+ timeSigned = new Date();
+ else
+ timeSigned = old.getTimeSigned();
+ int fudge;
+ HMAC hmac = null;
+ if (error == Rcode.NOERROR || error == Rcode.BADTIME)
+ hmac = new HMAC(digest, digestBlockLength, key);
+
+ fudge = Options.intValue("tsigfudge");
+ if (fudge < 0 || fudge > 0x7FFF)
+ fudge = FUDGE;
+
+ if (old != null) {
+ DNSOutput out = new DNSOutput();
+ out.writeU16(old.getSignature().length);
+ if (hmac != null) {
+ hmac.update(out.toByteArray());
+ hmac.update(old.getSignature());
+ }
+ }
+
+ /* Digest the message */
+ if (hmac != null)
+ hmac.update(b);
+
+ DNSOutput out = new DNSOutput();
+ name.toWireCanonical(out);
+ out.writeU16(DClass.ANY); /* class */
+ out.writeU32(0); /* ttl */
+ alg.toWireCanonical(out);
+ long time = timeSigned.getTime() / 1000;
+ int timeHigh = (int) (time >> 32);
+ long timeLow = (time & 0xFFFFFFFFL);
+ out.writeU16(timeHigh);
+ out.writeU32(timeLow);
+ out.writeU16(fudge);
+
+ out.writeU16(error);
+ out.writeU16(0); /* No other data */
+
+ if (hmac != null)
+ hmac.update(out.toByteArray());
+
+ byte [] signature;
+ if (hmac != null)
+ signature = hmac.sign();
+ else
+ signature = new byte[0];
+
+ byte [] other = null;
+ if (error == Rcode.BADTIME) {
+ out = new DNSOutput();
+ time = new Date().getTime() / 1000;
+ timeHigh = (int) (time >> 32);
+ timeLow = (time & 0xFFFFFFFFL);
+ out.writeU16(timeHigh);
+ out.writeU32(timeLow);
+ other = out.toByteArray();
+ }
+
+ return (new TSIGRecord(name, DClass.ANY, 0, alg, timeSigned, fudge,
+ signature, m.getHeader().getID(), error, other));
+}
+
+/**
+ * Generates a TSIG record with a specific error for a message and adds it
+ * to the message.
+ * @param m The message
+ * @param error The error
+ * @param old If this message is a response, the TSIG from the request
+ */
+public void
+apply(Message m, int error, TSIGRecord old) {
+ Record r = generate(m, m.toWire(), error, old);
+ m.addRecord(r, Section.ADDITIONAL);
+ m.tsigState = Message.TSIG_SIGNED;
+}
+
+/**
+ * Generates a TSIG record for a message and adds it to the message
+ * @param m The message
+ * @param old If this message is a response, the TSIG from the request
+ */
+public void
+apply(Message m, TSIGRecord old) {
+ apply(m, Rcode.NOERROR, old);
+}
+
+/**
+ * Generates a TSIG record for a message and adds it to the message
+ * @param m The message
+ * @param old If this message is a response, the TSIG from the request
+ */
+public void
+applyStream(Message m, TSIGRecord old, boolean first) {
+ if (first) {
+ apply(m, old);
+ return;
+ }
+ Date timeSigned = new Date();
+ int fudge;
+ HMAC hmac = new HMAC(digest, digestBlockLength, key);
+
+ fudge = Options.intValue("tsigfudge");
+ if (fudge < 0 || fudge > 0x7FFF)
+ fudge = FUDGE;
+
+ DNSOutput out = new DNSOutput();
+ out.writeU16(old.getSignature().length);
+ hmac.update(out.toByteArray());
+ hmac.update(old.getSignature());
+
+ /* Digest the message */
+ hmac.update(m.toWire());
+
+ out = new DNSOutput();
+ long time = timeSigned.getTime() / 1000;
+ int timeHigh = (int) (time >> 32);
+ long timeLow = (time & 0xFFFFFFFFL);
+ out.writeU16(timeHigh);
+ out.writeU32(timeLow);
+ out.writeU16(fudge);
+
+ hmac.update(out.toByteArray());
+
+ byte [] signature = hmac.sign();
+ byte [] other = null;
+
+ Record r = new TSIGRecord(name, DClass.ANY, 0, alg, timeSigned, fudge,
+ signature, m.getHeader().getID(),
+ Rcode.NOERROR, other);
+ m.addRecord(r, Section.ADDITIONAL);
+ m.tsigState = Message.TSIG_SIGNED;
+}
+
+/**
+ * Verifies a TSIG record on an incoming message. Since this is only called
+ * in the context where a TSIG is expected to be present, it is an error
+ * if one is not present. After calling this routine, Message.isVerified() may
+ * be called on this message.
+ * @param m The message
+ * @param b An array containing the message in unparsed form. This is
+ * necessary since TSIG signs the message in wire format, and we can't
+ * recreate the exact wire format (with the same name compression).
+ * @param length The length of the message in the array.
+ * @param old If this message is a response, the TSIG from the request
+ * @return The result of the verification (as an Rcode)
+ * @see Rcode
+ */
+public byte
+verify(Message m, byte [] b, int length, TSIGRecord old) {
+ m.tsigState = Message.TSIG_FAILED;
+ TSIGRecord tsig = m.getTSIG();
+ HMAC hmac = new HMAC(digest, digestBlockLength, key);
+ if (tsig == null)
+ return Rcode.FORMERR;
+
+ if (!tsig.getName().equals(name) || !tsig.getAlgorithm().equals(alg)) {
+ if (Options.check("verbose"))
+ System.err.println("BADKEY failure");
+ return Rcode.BADKEY;
+ }
+ long now = System.currentTimeMillis();
+ long then = tsig.getTimeSigned().getTime();
+ long fudge = tsig.getFudge();
+ if (Math.abs(now - then) > fudge * 1000) {
+ if (Options.check("verbose"))
+ System.err.println("BADTIME failure");
+ return Rcode.BADTIME;
+ }
+
+ if (old != null && tsig.getError() != Rcode.BADKEY &&
+ tsig.getError() != Rcode.BADSIG)
+ {
+ DNSOutput out = new DNSOutput();
+ out.writeU16(old.getSignature().length);
+ hmac.update(out.toByteArray());
+ hmac.update(old.getSignature());
+ }
+ m.getHeader().decCount(Section.ADDITIONAL);
+ byte [] header = m.getHeader().toWire();
+ m.getHeader().incCount(Section.ADDITIONAL);
+ hmac.update(header);
+
+ int len = m.tsigstart - header.length;
+ hmac.update(b, header.length, len);
+
+ DNSOutput out = new DNSOutput();
+ tsig.getName().toWireCanonical(out);
+ out.writeU16(tsig.dclass);
+ out.writeU32(tsig.ttl);
+ tsig.getAlgorithm().toWireCanonical(out);
+ long time = tsig.getTimeSigned().getTime() / 1000;
+ int timeHigh = (int) (time >> 32);
+ long timeLow = (time & 0xFFFFFFFFL);
+ out.writeU16(timeHigh);
+ out.writeU32(timeLow);
+ out.writeU16(tsig.getFudge());
+ out.writeU16(tsig.getError());
+ if (tsig.getOther() != null) {
+ out.writeU16(tsig.getOther().length);
+ out.writeByteArray(tsig.getOther());
+ } else {
+ out.writeU16(0);
+ }
+
+ hmac.update(out.toByteArray());
+
+ byte [] signature = tsig.getSignature();
+ int digestLength = hmac.digestLength();
+ int minDigestLength = digest.equals("md5") ? 10 : digestLength / 2;
+
+ if (signature.length > digestLength) {
+ if (Options.check("verbose"))
+ System.err.println("BADSIG: signature too long");
+ return Rcode.BADSIG;
+ } else if (signature.length < minDigestLength) {
+ if (Options.check("verbose"))
+ System.err.println("BADSIG: signature too short");
+ return Rcode.BADSIG;
+ } else if (!hmac.verify(signature, true)) {
+ if (Options.check("verbose"))
+ System.err.println("BADSIG: signature verification");
+ return Rcode.BADSIG;
+ }
+
+ m.tsigState = Message.TSIG_VERIFIED;
+ return Rcode.NOERROR;
+}
+
+/**
+ * Verifies a TSIG record on an incoming message. Since this is only called
+ * in the context where a TSIG is expected to be present, it is an error
+ * if one is not present. After calling this routine, Message.isVerified() may
+ * be called on this message.
+ * @param m The message
+ * @param b The message in unparsed form. This is necessary since TSIG
+ * signs the message in wire format, and we can't recreate the exact wire
+ * format (with the same name compression).
+ * @param old If this message is a response, the TSIG from the request
+ * @return The result of the verification (as an Rcode)
+ * @see Rcode
+ */
+public int
+verify(Message m, byte [] b, TSIGRecord old) {
+ return verify(m, b, b.length, old);
+}
+
+/**
+ * Returns the maximum length of a TSIG record generated by this key.
+ * @see TSIGRecord
+ */
+public int
+recordLength() {
+ return (name.length() + 10 +
+ alg.length() +
+ 8 + // time signed, fudge
+ 18 + // 2 byte MAC length, 16 byte MAC
+ 4 + // original id, error
+ 8); // 2 byte error length, 6 byte max error field.
+}
+
+public static class StreamVerifier {
+ /**
+ * A helper class for verifying multiple message responses.
+ */
+
+ private TSIG key;
+ private HMAC verifier;
+ private int nresponses;
+ private int lastsigned;
+ private TSIGRecord lastTSIG;
+
+ /** Creates an object to verify a multiple message response */
+ public
+ StreamVerifier(TSIG tsig, TSIGRecord old) {
+ key = tsig;
+ verifier = new HMAC(key.digest, key.digestBlockLength, key.key);
+ nresponses = 0;
+ lastTSIG = old;
+ }
+
+ /**
+ * Verifies a TSIG record on an incoming message that is part of a
+ * multiple message response.
+ * TSIG records must be present on the first and last messages, and
+ * at least every 100 records in between.
+ * After calling this routine, Message.isVerified() may be called on
+ * this message.
+ * @param m The message
+ * @param b The message in unparsed form
+ * @return The result of the verification (as an Rcode)
+ * @see Rcode
+ */
+ public int
+ verify(Message m, byte [] b) {
+ TSIGRecord tsig = m.getTSIG();
+
+ nresponses++;
+
+ if (nresponses == 1) {
+ int result = key.verify(m, b, lastTSIG);
+ if (result == Rcode.NOERROR) {
+ byte [] signature = tsig.getSignature();
+ DNSOutput out = new DNSOutput();
+ out.writeU16(signature.length);
+ verifier.update(out.toByteArray());
+ verifier.update(signature);
+ }
+ lastTSIG = tsig;
+ return result;
+ }
+
+ if (tsig != null)
+ m.getHeader().decCount(Section.ADDITIONAL);
+ byte [] header = m.getHeader().toWire();
+ if (tsig != null)
+ m.getHeader().incCount(Section.ADDITIONAL);
+ verifier.update(header);
+
+ int len;
+ if (tsig == null)
+ len = b.length - header.length;
+ else
+ len = m.tsigstart - header.length;
+ verifier.update(b, header.length, len);
+
+ if (tsig != null) {
+ lastsigned = nresponses;
+ lastTSIG = tsig;
+ }
+ else {
+ boolean required = (nresponses - lastsigned >= 100);
+ if (required) {
+ m.tsigState = Message.TSIG_FAILED;
+ return Rcode.FORMERR;
+ } else {
+ m.tsigState = Message.TSIG_INTERMEDIATE;
+ return Rcode.NOERROR;
+ }
+ }
+
+ if (!tsig.getName().equals(key.name) ||
+ !tsig.getAlgorithm().equals(key.alg))
+ {
+ if (Options.check("verbose"))
+ System.err.println("BADKEY failure");
+ m.tsigState = Message.TSIG_FAILED;
+ return Rcode.BADKEY;
+ }
+
+ DNSOutput out = new DNSOutput();
+ long time = tsig.getTimeSigned().getTime() / 1000;
+ int timeHigh = (int) (time >> 32);
+ long timeLow = (time & 0xFFFFFFFFL);
+ out.writeU16(timeHigh);
+ out.writeU32(timeLow);
+ out.writeU16(tsig.getFudge());
+ verifier.update(out.toByteArray());
+
+ if (verifier.verify(tsig.getSignature()) == false) {
+ if (Options.check("verbose"))
+ System.err.println("BADSIG failure");
+ m.tsigState = Message.TSIG_FAILED;
+ return Rcode.BADSIG;
+ }
+
+ verifier.clear();
+ out = new DNSOutput();
+ out.writeU16(tsig.getSignature().length);
+ verifier.update(out.toByteArray());
+ verifier.update(tsig.getSignature());
+
+ m.tsigState = Message.TSIG_VERIFIED;
+ return Rcode.NOERROR;
+ }
+}
+
+}
diff --git a/src/org/xbill/DNS/TSIGRecord.java b/src/org/xbill/DNS/TSIGRecord.java
new file mode 100644
index 0000000..c7ce9ed
--- /dev/null
+++ b/src/org/xbill/DNS/TSIGRecord.java
@@ -0,0 +1,220 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.util.*;
+import org.xbill.DNS.utils.*;
+
+/**
+ * Transaction Signature - this record is automatically generated by the
+ * resolver. TSIG records provide transaction security between the
+ * sender and receiver of a message, using a shared key.
+ * @see Resolver
+ * @see TSIG
+ *
+ * @author Brian Wellington
+ */
+
+public class TSIGRecord extends Record {
+
+private static final long serialVersionUID = -88820909016649306L;
+
+private Name alg;
+private Date timeSigned;
+private int fudge;
+private byte [] signature;
+private int originalID;
+private int error;
+private byte [] other;
+
+TSIGRecord() {}
+
+Record
+getObject() {
+ return new TSIGRecord();
+}
+
+/**
+ * Creates a TSIG Record from the given data. This is normally called by
+ * the TSIG class
+ * @param alg The shared key's algorithm
+ * @param timeSigned The time that this record was generated
+ * @param fudge The fudge factor for time - if the time that the message is
+ * received is not in the range [now - fudge, now + fudge], the signature
+ * fails
+ * @param signature The signature
+ * @param originalID The message ID at the time of its generation
+ * @param error The extended error field. Should be 0 in queries.
+ * @param other The other data field. Currently used only in BADTIME
+ * responses.
+ * @see TSIG
+ */
+public
+TSIGRecord(Name name, int dclass, long ttl, Name alg, Date timeSigned,
+ int fudge, byte [] signature, int originalID, int error,
+ byte other[])
+{
+ super(name, Type.TSIG, dclass, ttl);
+ this.alg = checkName("alg", alg);
+ this.timeSigned = timeSigned;
+ this.fudge = checkU16("fudge", fudge);
+ this.signature = signature;
+ this.originalID = checkU16("originalID", originalID);
+ this.error = checkU16("error", error);
+ this.other = other;
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ alg = new Name(in);
+
+ long timeHigh = in.readU16();
+ long timeLow = in.readU32();
+ long time = (timeHigh << 32) + timeLow;
+ timeSigned = new Date(time * 1000);
+ fudge = in.readU16();
+
+ int sigLen = in.readU16();
+ signature = in.readByteArray(sigLen);
+
+ originalID = in.readU16();
+ error = in.readU16();
+
+ int otherLen = in.readU16();
+ if (otherLen > 0)
+ other = in.readByteArray(otherLen);
+ else
+ other = null;
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ throw st.exception("no text format defined for TSIG");
+}
+
+/** Converts rdata to a String */
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(alg);
+ sb.append(" ");
+ if (Options.check("multiline"))
+ sb.append("(\n\t");
+
+ sb.append (timeSigned.getTime() / 1000);
+ sb.append (" ");
+ sb.append (fudge);
+ sb.append (" ");
+ sb.append (signature.length);
+ if (Options.check("multiline")) {
+ sb.append ("\n");
+ sb.append (base64.formatString(signature, 64, "\t", false));
+ } else {
+ sb.append (" ");
+ sb.append (base64.toString(signature));
+ }
+ sb.append (" ");
+ sb.append (Rcode.TSIGstring(error));
+ sb.append (" ");
+ if (other == null)
+ sb.append (0);
+ else {
+ sb.append (other.length);
+ if (Options.check("multiline"))
+ sb.append("\n\n\n\t");
+ else
+ sb.append(" ");
+ if (error == Rcode.BADTIME) {
+ if (other.length != 6) {
+ sb.append("<invalid BADTIME other data>");
+ } else {
+ long time = ((long)(other[0] & 0xFF) << 40) +
+ ((long)(other[1] & 0xFF) << 32) +
+ ((other[2] & 0xFF) << 24) +
+ ((other[3] & 0xFF) << 16) +
+ ((other[4] & 0xFF) << 8) +
+ ((other[5] & 0xFF) );
+ sb.append("<server time: ");
+ sb.append(new Date(time * 1000));
+ sb.append(">");
+ }
+ } else {
+ sb.append("<");
+ sb.append(base64.toString(other));
+ sb.append(">");
+ }
+ }
+ if (Options.check("multiline"))
+ sb.append(" )");
+ return sb.toString();
+}
+
+/** Returns the shared key's algorithm */
+public Name
+getAlgorithm() {
+ return alg;
+}
+
+/** Returns the time that this record was generated */
+public Date
+getTimeSigned() {
+ return timeSigned;
+}
+
+/** Returns the time fudge factor */
+public int
+getFudge() {
+ return fudge;
+}
+
+/** Returns the signature */
+public byte []
+getSignature() {
+ return signature;
+}
+
+/** Returns the original message ID */
+public int
+getOriginalID() {
+ return originalID;
+}
+
+/** Returns the extended error */
+public int
+getError() {
+ return error;
+}
+
+/** Returns the other data */
+public byte []
+getOther() {
+ return other;
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ alg.toWire(out, null, canonical);
+
+ long time = timeSigned.getTime() / 1000;
+ int timeHigh = (int) (time >> 32);
+ long timeLow = (time & 0xFFFFFFFFL);
+ out.writeU16(timeHigh);
+ out.writeU32(timeLow);
+ out.writeU16(fudge);
+
+ out.writeU16(signature.length);
+ out.writeByteArray(signature);
+
+ out.writeU16(originalID);
+ out.writeU16(error);
+
+ if (other != null) {
+ out.writeU16(other.length);
+ out.writeByteArray(other);
+ }
+ else
+ out.writeU16(0);
+}
+
+}
diff --git a/src/org/xbill/DNS/TTL.java b/src/org/xbill/DNS/TTL.java
new file mode 100644
index 0000000..01bf416
--- /dev/null
+++ b/src/org/xbill/DNS/TTL.java
@@ -0,0 +1,113 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * Routines for parsing BIND-style TTL values. These values consist of
+ * numbers followed by 1 letter units of time (W - week, D - day, H - hour,
+ * M - minute, S - second).
+ *
+ * @author Brian Wellington
+ */
+
+public final class TTL {
+
+public static final long MAX_VALUE = 0x7FFFFFFFL;
+
+private
+TTL() {}
+
+static void
+check(long i) {
+ if (i < 0 || i > MAX_VALUE)
+ throw new InvalidTTLException(i);
+}
+
+/**
+ * Parses a TTL-like value, which can either be expressed as a number or a
+ * BIND-style string with numbers and units.
+ * @param s The string representing the numeric value.
+ * @param clamp Whether to clamp values in the range [MAX_VALUE + 1, 2^32 -1]
+ * to MAX_VALUE. This should be donw for TTLs, but not other values which
+ * can be expressed in this format.
+ * @return The value as a number of seconds
+ * @throws NumberFormatException The string was not in a valid TTL format.
+ */
+public static long
+parse(String s, boolean clamp) {
+ if (s == null || s.length() == 0 || !Character.isDigit(s.charAt(0)))
+ throw new NumberFormatException();
+ long value = 0;
+ long ttl = 0;
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ long oldvalue = value;
+ if (Character.isDigit(c)) {
+ value = (value * 10) + Character.getNumericValue(c);
+ if (value < oldvalue)
+ throw new NumberFormatException();
+ } else {
+ switch (Character.toUpperCase(c)) {
+ case 'W': value *= 7;
+ case 'D': value *= 24;
+ case 'H': value *= 60;
+ case 'M': value *= 60;
+ case 'S': break;
+ default: throw new NumberFormatException();
+ }
+ ttl += value;
+ value = 0;
+ if (ttl > 0xFFFFFFFFL)
+ throw new NumberFormatException();
+ }
+ }
+ if (ttl == 0)
+ ttl = value;
+
+ if (ttl > 0xFFFFFFFFL)
+ throw new NumberFormatException();
+ else if (ttl > MAX_VALUE && clamp)
+ ttl = MAX_VALUE;
+ return ttl;
+}
+
+/**
+ * Parses a TTL, which can either be expressed as a number or a BIND-style
+ * string with numbers and units.
+ * @param s The string representing the TTL
+ * @return The TTL as a number of seconds
+ * @throws NumberFormatException The string was not in a valid TTL format.
+ */
+public static long
+parseTTL(String s) {
+ return parse(s, true);
+}
+
+public static String
+format(long ttl) {
+ TTL.check(ttl);
+ StringBuffer sb = new StringBuffer();
+ long secs, mins, hours, days, weeks;
+ secs = ttl % 60;
+ ttl /= 60;
+ mins = ttl % 60;
+ ttl /= 60;
+ hours = ttl % 24;
+ ttl /= 24;
+ days = ttl % 7;
+ ttl /= 7;
+ weeks = ttl;
+ if (weeks > 0)
+ sb.append(weeks + "W");
+ if (days > 0)
+ sb.append(days + "D");
+ if (hours > 0)
+ sb.append(hours + "H");
+ if (mins > 0)
+ sb.append(mins + "M");
+ if (secs > 0 || (weeks == 0 && days == 0 && hours == 0 && mins == 0))
+ sb.append(secs + "S");
+ return sb.toString();
+}
+
+}
diff --git a/src/org/xbill/DNS/TXTBase.java b/src/org/xbill/DNS/TXTBase.java
new file mode 100644
index 0000000..fd99bfb
--- /dev/null
+++ b/src/org/xbill/DNS/TXTBase.java
@@ -0,0 +1,123 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * Implements common functionality for the many record types whose format
+ * is a list of strings.
+ *
+ * @author Brian Wellington
+ */
+
+abstract class TXTBase extends Record {
+
+private static final long serialVersionUID = -4319510507246305931L;
+
+protected List strings;
+
+protected
+TXTBase() {}
+
+protected
+TXTBase(Name name, int type, int dclass, long ttl) {
+ super(name, type, dclass, ttl);
+}
+
+protected
+TXTBase(Name name, int type, int dclass, long ttl, List strings) {
+ super(name, type, dclass, ttl);
+ if (strings == null)
+ throw new IllegalArgumentException("strings must not be null");
+ this.strings = new ArrayList(strings.size());
+ Iterator it = strings.iterator();
+ try {
+ while (it.hasNext()) {
+ String s = (String) it.next();
+ this.strings.add(byteArrayFromString(s));
+ }
+ }
+ catch (TextParseException e) {
+ throw new IllegalArgumentException(e.getMessage());
+ }
+}
+
+protected
+TXTBase(Name name, int type, int dclass, long ttl, String string) {
+ this(name, type, dclass, ttl, Collections.singletonList(string));
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ strings = new ArrayList(2);
+ while (in.remaining() > 0) {
+ byte [] b = in.readCountedString();
+ strings.add(b);
+ }
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ strings = new ArrayList(2);
+ while (true) {
+ Tokenizer.Token t = st.get();
+ if (!t.isString())
+ break;
+ try {
+ strings.add(byteArrayFromString(t.value));
+ }
+ catch (TextParseException e) {
+ throw st.exception(e.getMessage());
+ }
+
+ }
+ st.unget();
+}
+
+/** converts to a String */
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ Iterator it = strings.iterator();
+ while (it.hasNext()) {
+ byte [] array = (byte []) it.next();
+ sb.append(byteArrayToString(array, true));
+ if (it.hasNext())
+ sb.append(" ");
+ }
+ return sb.toString();
+}
+
+/**
+ * Returns the text strings
+ * @return A list of Strings corresponding to the text strings.
+ */
+public List
+getStrings() {
+ List list = new ArrayList(strings.size());
+ for (int i = 0; i < strings.size(); i++)
+ list.add(byteArrayToString((byte []) strings.get(i), false));
+ return list;
+}
+
+/**
+ * Returns the text strings
+ * @return A list of byte arrays corresponding to the text strings.
+ */
+public List
+getStringsAsByteArrays() {
+ return strings;
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ Iterator it = strings.iterator();
+ while (it.hasNext()) {
+ byte [] b = (byte []) it.next();
+ out.writeCountedString(b);
+ }
+}
+
+}
diff --git a/src/org/xbill/DNS/TXTRecord.java b/src/org/xbill/DNS/TXTRecord.java
new file mode 100644
index 0000000..ea5de04
--- /dev/null
+++ b/src/org/xbill/DNS/TXTRecord.java
@@ -0,0 +1,44 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.util.*;
+
+/**
+ * Text - stores text strings
+ *
+ * @author Brian Wellington
+ */
+
+public class TXTRecord extends TXTBase {
+
+private static final long serialVersionUID = -5780785764284221342L;
+
+TXTRecord() {}
+
+Record
+getObject() {
+ return new TXTRecord();
+}
+
+/**
+ * Creates a TXT Record from the given data
+ * @param strings The text strings
+ * @throws IllegalArgumentException One of the strings has invalid escapes
+ */
+public
+TXTRecord(Name name, int dclass, long ttl, List strings) {
+ super(name, Type.TXT, dclass, ttl, strings);
+}
+
+/**
+ * Creates a TXT Record from the given data
+ * @param string One text string
+ * @throws IllegalArgumentException The string has invalid escapes
+ */
+public
+TXTRecord(Name name, int dclass, long ttl, String string) {
+ super(name, Type.TXT, dclass, ttl, string);
+}
+
+}
diff --git a/src/org/xbill/DNS/TextParseException.java b/src/org/xbill/DNS/TextParseException.java
new file mode 100644
index 0000000..3b9a425
--- /dev/null
+++ b/src/org/xbill/DNS/TextParseException.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+
+/**
+ * An exception thrown when unable to parse text.
+ *
+ * @author Brian Wellington
+ */
+
+public class TextParseException extends IOException {
+
+public
+TextParseException() {
+ super();
+}
+
+public
+TextParseException(String s) {
+ super(s);
+}
+
+}
diff --git a/src/org/xbill/DNS/Tokenizer.java b/src/org/xbill/DNS/Tokenizer.java
new file mode 100644
index 0000000..bc637ab
--- /dev/null
+++ b/src/org/xbill/DNS/Tokenizer.java
@@ -0,0 +1,713 @@
+// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org)
+//
+// Copyright (C) 2003-2004 Nominum, Inc.
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+// OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.net.*;
+
+import org.xbill.DNS.utils.*;
+
+/**
+ * Tokenizer is used to parse DNS records and zones from text format,
+ *
+ * @author Brian Wellington
+ * @author Bob Halley
+ */
+
+public class Tokenizer {
+
+private static String delim = " \t\n;()\"";
+private static String quotes = "\"";
+
+/** End of file */
+public static final int EOF = 0;
+
+/** End of line */
+public static final int EOL = 1;
+
+/** Whitespace; only returned when wantWhitespace is set */
+public static final int WHITESPACE = 2;
+
+/** An identifier (unquoted string) */
+public static final int IDENTIFIER = 3;
+
+/** A quoted string */
+public static final int QUOTED_STRING = 4;
+
+/** A comment; only returned when wantComment is set */
+public static final int COMMENT = 5;
+
+private PushbackInputStream is;
+private boolean ungottenToken;
+private int multiline;
+private boolean quoting;
+private String delimiters;
+private Token current;
+private StringBuffer sb;
+private boolean wantClose;
+
+private String filename;
+private int line;
+
+public static class Token {
+ /** The type of token. */
+ public int type;
+
+ /** The value of the token, or null for tokens without values. */
+ public String value;
+
+ private
+ Token() {
+ type = -1;
+ value = null;
+ }
+
+ private Token
+ set(int type, StringBuffer value) {
+ if (type < 0)
+ throw new IllegalArgumentException();
+ this.type = type;
+ this.value = value == null ? null : value.toString();
+ return this;
+ }
+
+ /**
+ * Converts the token to a string containing a representation useful
+ * for debugging.
+ */
+ public String
+ toString() {
+ switch (type) {
+ case EOF:
+ return "<eof>";
+ case EOL:
+ return "<eol>";
+ case WHITESPACE:
+ return "<whitespace>";
+ case IDENTIFIER:
+ return "<identifier: " + value + ">";
+ case QUOTED_STRING:
+ return "<quoted_string: " + value + ">";
+ case COMMENT:
+ return "<comment: " + value + ">";
+ default:
+ return "<unknown>";
+ }
+ }
+
+ /** Indicates whether this token contains a string. */
+ public boolean
+ isString() {
+ return (type == IDENTIFIER || type == QUOTED_STRING);
+ }
+
+ /** Indicates whether this token contains an EOL or EOF. */
+ public boolean
+ isEOL() {
+ return (type == EOL || type == EOF);
+ }
+}
+
+static class TokenizerException extends TextParseException {
+ String message;
+
+ public
+ TokenizerException(String filename, int line, String message) {
+ super(filename + ":" + line + ": " + message);
+ this.message = message;
+ }
+
+ public String
+ getBaseMessage() {
+ return message;
+ }
+}
+
+/**
+ * Creates a Tokenizer from an arbitrary input stream.
+ * @param is The InputStream to tokenize.
+ */
+public
+Tokenizer(InputStream is) {
+ if (!(is instanceof BufferedInputStream))
+ is = new BufferedInputStream(is);
+ this.is = new PushbackInputStream(is, 2);
+ ungottenToken = false;
+ multiline = 0;
+ quoting = false;
+ delimiters = delim;
+ current = new Token();
+ sb = new StringBuffer();
+ filename = "<none>";
+ line = 1;
+}
+
+/**
+ * Creates a Tokenizer from a string.
+ * @param s The String to tokenize.
+ */
+public
+Tokenizer(String s) {
+ this(new ByteArrayInputStream(s.getBytes()));
+}
+
+/**
+ * Creates a Tokenizer from a file.
+ * @param f The File to tokenize.
+ */
+public
+Tokenizer(File f) throws FileNotFoundException {
+ this(new FileInputStream(f));
+ wantClose = true;
+ filename = f.getName();
+}
+
+private int
+getChar() throws IOException {
+ int c = is.read();
+ if (c == '\r') {
+ int next = is.read();
+ if (next != '\n')
+ is.unread(next);
+ c = '\n';
+ }
+ if (c == '\n')
+ line++;
+ return c;
+}
+
+private void
+ungetChar(int c) throws IOException {
+ if (c == -1)
+ return;
+ is.unread(c);
+ if (c == '\n')
+ line--;
+}
+
+private int
+skipWhitespace() throws IOException {
+ int skipped = 0;
+ while (true) {
+ int c = getChar();
+ if (c != ' ' && c != '\t') {
+ if (!(c == '\n' && multiline > 0)) {
+ ungetChar(c);
+ return skipped;
+ }
+ }
+ skipped++;
+ }
+}
+
+private void
+checkUnbalancedParens() throws TextParseException {
+ if (multiline > 0)
+ throw exception("unbalanced parentheses");
+}
+
+/**
+ * Gets the next token from a tokenizer.
+ * @param wantWhitespace If true, leading whitespace will be returned as a
+ * token.
+ * @param wantComment If true, comments are returned as tokens.
+ * @return The next token in the stream.
+ * @throws TextParseException The input was invalid.
+ * @throws IOException An I/O error occurred.
+ */
+public Token
+get(boolean wantWhitespace, boolean wantComment) throws IOException {
+ int type;
+ int c;
+
+ if (ungottenToken) {
+ ungottenToken = false;
+ if (current.type == WHITESPACE) {
+ if (wantWhitespace)
+ return current;
+ } else if (current.type == COMMENT) {
+ if (wantComment)
+ return current;
+ } else {
+ if (current.type == EOL)
+ line++;
+ return current;
+ }
+ }
+ int skipped = skipWhitespace();
+ if (skipped > 0 && wantWhitespace)
+ return current.set(WHITESPACE, null);
+ type = IDENTIFIER;
+ sb.setLength(0);
+ while (true) {
+ c = getChar();
+ if (c == -1 || delimiters.indexOf(c) != -1) {
+ if (c == -1) {
+ if (quoting)
+ throw exception("EOF in " +
+ "quoted string");
+ else if (sb.length() == 0)
+ return current.set(EOF, null);
+ else
+ return current.set(type, sb);
+ }
+ if (sb.length() == 0 && type != QUOTED_STRING) {
+ if (c == '(') {
+ multiline++;
+ skipWhitespace();
+ continue;
+ } else if (c == ')') {
+ if (multiline <= 0)
+ throw exception("invalid " +
+ "close " +
+ "parenthesis");
+ multiline--;
+ skipWhitespace();
+ continue;
+ } else if (c == '"') {
+ if (!quoting) {
+ quoting = true;
+ delimiters = quotes;
+ type = QUOTED_STRING;
+ } else {
+ quoting = false;
+ delimiters = delim;
+ skipWhitespace();
+ }
+ continue;
+ } else if (c == '\n') {
+ return current.set(EOL, null);
+ } else if (c == ';') {
+ while (true) {
+ c = getChar();
+ if (c == '\n' || c == -1)
+ break;
+ sb.append((char)c);
+ }
+ if (wantComment) {
+ ungetChar(c);
+ return current.set(COMMENT, sb);
+ } else if (c == -1 &&
+ type != QUOTED_STRING)
+ {
+ checkUnbalancedParens();
+ return current.set(EOF, null);
+ } else if (multiline > 0) {
+ skipWhitespace();
+ sb.setLength(0);
+ continue;
+ } else
+ return current.set(EOL, null);
+ } else
+ throw new IllegalStateException();
+ } else
+ ungetChar(c);
+ break;
+ } else if (c == '\\') {
+ c = getChar();
+ if (c == -1)
+ throw exception("unterminated escape sequence");
+ sb.append('\\');
+ } else if (quoting && c == '\n') {
+ throw exception("newline in quoted string");
+ }
+ sb.append((char)c);
+ }
+ if (sb.length() == 0 && type != QUOTED_STRING) {
+ checkUnbalancedParens();
+ return current.set(EOF, null);
+ }
+ return current.set(type, sb);
+}
+
+/**
+ * Gets the next token from a tokenizer, ignoring whitespace and comments.
+ * @return The next token in the stream.
+ * @throws TextParseException The input was invalid.
+ * @throws IOException An I/O error occurred.
+ */
+public Token
+get() throws IOException {
+ return get(false, false);
+}
+
+/**
+ * Returns a token to the stream, so that it will be returned by the next call
+ * to get().
+ * @throws IllegalStateException There are already ungotten tokens.
+ */
+public void
+unget() {
+ if (ungottenToken)
+ throw new IllegalStateException
+ ("Cannot unget multiple tokens");
+ if (current.type == EOL)
+ line--;
+ ungottenToken = true;
+}
+
+/**
+ * Gets the next token from a tokenizer and converts it to a string.
+ * @return The next token in the stream, as a string.
+ * @throws TextParseException The input was invalid or not a string.
+ * @throws IOException An I/O error occurred.
+ */
+public String
+getString() throws IOException {
+ Token next = get();
+ if (!next.isString()) {
+ throw exception("expected a string");
+ }
+ return next.value;
+}
+
+private String
+_getIdentifier(String expected) throws IOException {
+ Token next = get();
+ if (next.type != IDENTIFIER)
+ throw exception("expected " + expected);
+ return next.value;
+}
+
+/**
+ * Gets the next token from a tokenizer, ensures it is an unquoted string,
+ * and converts it to a string.
+ * @return The next token in the stream, as a string.
+ * @throws TextParseException The input was invalid or not an unquoted string.
+ * @throws IOException An I/O error occurred.
+ */
+public String
+getIdentifier() throws IOException {
+ return _getIdentifier("an identifier");
+}
+
+/**
+ * Gets the next token from a tokenizer and converts it to a long.
+ * @return The next token in the stream, as a long.
+ * @throws TextParseException The input was invalid or not a long.
+ * @throws IOException An I/O error occurred.
+ */
+public long
+getLong() throws IOException {
+ String next = _getIdentifier("an integer");
+ if (!Character.isDigit(next.charAt(0)))
+ throw exception("expected an integer");
+ try {
+ return Long.parseLong(next);
+ } catch (NumberFormatException e) {
+ throw exception("expected an integer");
+ }
+}
+
+/**
+ * Gets the next token from a tokenizer and converts it to an unsigned 32 bit
+ * integer.
+ * @return The next token in the stream, as an unsigned 32 bit integer.
+ * @throws TextParseException The input was invalid or not an unsigned 32
+ * bit integer.
+ * @throws IOException An I/O error occurred.
+ */
+public long
+getUInt32() throws IOException {
+ long l = getLong();
+ if (l < 0 || l > 0xFFFFFFFFL)
+ throw exception("expected an 32 bit unsigned integer");
+ return l;
+}
+
+/**
+ * Gets the next token from a tokenizer and converts it to an unsigned 16 bit
+ * integer.
+ * @return The next token in the stream, as an unsigned 16 bit integer.
+ * @throws TextParseException The input was invalid or not an unsigned 16
+ * bit integer.
+ * @throws IOException An I/O error occurred.
+ */
+public int
+getUInt16() throws IOException {
+ long l = getLong();
+ if (l < 0 || l > 0xFFFFL)
+ throw exception("expected an 16 bit unsigned integer");
+ return (int) l;
+}
+
+/**
+ * Gets the next token from a tokenizer and converts it to an unsigned 8 bit
+ * integer.
+ * @return The next token in the stream, as an unsigned 8 bit integer.
+ * @throws TextParseException The input was invalid or not an unsigned 8
+ * bit integer.
+ * @throws IOException An I/O error occurred.
+ */
+public int
+getUInt8() throws IOException {
+ long l = getLong();
+ if (l < 0 || l > 0xFFL)
+ throw exception("expected an 8 bit unsigned integer");
+ return (int) l;
+}
+
+/**
+ * Gets the next token from a tokenizer and parses it as a TTL.
+ * @return The next token in the stream, as an unsigned 32 bit integer.
+ * @throws TextParseException The input was not valid.
+ * @throws IOException An I/O error occurred.
+ * @see TTL
+ */
+public long
+getTTL() throws IOException {
+ String next = _getIdentifier("a TTL value");
+ try {
+ return TTL.parseTTL(next);
+ }
+ catch (NumberFormatException e) {
+ throw exception("expected a TTL value");
+ }
+}
+
+/**
+ * Gets the next token from a tokenizer and parses it as if it were a TTL.
+ * @return The next token in the stream, as an unsigned 32 bit integer.
+ * @throws TextParseException The input was not valid.
+ * @throws IOException An I/O error occurred.
+ * @see TTL
+ */
+public long
+getTTLLike() throws IOException {
+ String next = _getIdentifier("a TTL-like value");
+ try {
+ return TTL.parse(next, false);
+ }
+ catch (NumberFormatException e) {
+ throw exception("expected a TTL-like value");
+ }
+}
+
+/**
+ * Gets the next token from a tokenizer and converts it to a name.
+ * @param origin The origin to append to relative names.
+ * @return The next token in the stream, as a name.
+ * @throws TextParseException The input was invalid or not a valid name.
+ * @throws IOException An I/O error occurred.
+ * @throws RelativeNameException The parsed name was relative, even with the
+ * origin.
+ * @see Name
+ */
+public Name
+getName(Name origin) throws IOException {
+ String next = _getIdentifier("a name");
+ try {
+ Name name = Name.fromString(next, origin);
+ if (!name.isAbsolute())
+ throw new RelativeNameException(name);
+ return name;
+ }
+ catch (TextParseException e) {
+ throw exception(e.getMessage());
+ }
+}
+
+/**
+ * Gets the next token from a tokenizer and converts it to an IP Address.
+ * @param family The address family.
+ * @return The next token in the stream, as an InetAddress
+ * @throws TextParseException The input was invalid or not a valid address.
+ * @throws IOException An I/O error occurred.
+ * @see Address
+ */
+public InetAddress
+getAddress(int family) throws IOException {
+ String next = _getIdentifier("an address");
+ try {
+ return Address.getByAddress(next, family);
+ }
+ catch (UnknownHostException e) {
+ throw exception(e.getMessage());
+ }
+}
+
+/**
+ * Gets the next token from a tokenizer, which must be an EOL or EOF.
+ * @throws TextParseException The input was invalid or not an EOL or EOF token.
+ * @throws IOException An I/O error occurred.
+ */
+public void
+getEOL() throws IOException {
+ Token next = get();
+ if (next.type != EOL && next.type != EOF) {
+ throw exception("expected EOL or EOF");
+ }
+}
+
+/**
+ * Returns a concatenation of the remaining strings from a Tokenizer.
+ */
+private String
+remainingStrings() throws IOException {
+ StringBuffer buffer = null;
+ while (true) {
+ Tokenizer.Token t = get();
+ if (!t.isString())
+ break;
+ if (buffer == null)
+ buffer = new StringBuffer();
+ buffer.append(t.value);
+ }
+ unget();
+ if (buffer == null)
+ return null;
+ return buffer.toString();
+}
+
+/**
+ * Gets the remaining string tokens until an EOL/EOF is seen, concatenates
+ * them together, and converts the base64 encoded data to a byte array.
+ * @param required If true, an exception will be thrown if no strings remain;
+ * otherwise null be be returned.
+ * @return The byte array containing the decoded strings, or null if there
+ * were no strings to decode.
+ * @throws TextParseException The input was invalid.
+ * @throws IOException An I/O error occurred.
+ */
+public byte []
+getBase64(boolean required) throws IOException {
+ String s = remainingStrings();
+ if (s == null) {
+ if (required)
+ throw exception("expected base64 encoded string");
+ else
+ return null;
+ }
+ byte [] array = base64.fromString(s);
+ if (array == null)
+ throw exception("invalid base64 encoding");
+ return array;
+}
+
+/**
+ * Gets the remaining string tokens until an EOL/EOF is seen, concatenates
+ * them together, and converts the base64 encoded data to a byte array.
+ * @return The byte array containing the decoded strings, or null if there
+ * were no strings to decode.
+ * @throws TextParseException The input was invalid.
+ * @throws IOException An I/O error occurred.
+ */
+public byte []
+getBase64() throws IOException {
+ return getBase64(false);
+}
+
+/**
+ * Gets the remaining string tokens until an EOL/EOF is seen, concatenates
+ * them together, and converts the hex encoded data to a byte array.
+ * @param required If true, an exception will be thrown if no strings remain;
+ * otherwise null be be returned.
+ * @return The byte array containing the decoded strings, or null if there
+ * were no strings to decode.
+ * @throws TextParseException The input was invalid.
+ * @throws IOException An I/O error occurred.
+ */
+public byte []
+getHex(boolean required) throws IOException {
+ String s = remainingStrings();
+ if (s == null) {
+ if (required)
+ throw exception("expected hex encoded string");
+ else
+ return null;
+ }
+ byte [] array = base16.fromString(s);
+ if (array == null)
+ throw exception("invalid hex encoding");
+ return array;
+}
+
+/**
+ * Gets the remaining string tokens until an EOL/EOF is seen, concatenates
+ * them together, and converts the hex encoded data to a byte array.
+ * @return The byte array containing the decoded strings, or null if there
+ * were no strings to decode.
+ * @throws TextParseException The input was invalid.
+ * @throws IOException An I/O error occurred.
+ */
+public byte []
+getHex() throws IOException {
+ return getHex(false);
+}
+
+/**
+ * Gets the next token from a tokenizer and decodes it as hex.
+ * @return The byte array containing the decoded string.
+ * @throws TextParseException The input was invalid.
+ * @throws IOException An I/O error occurred.
+ */
+public byte []
+getHexString() throws IOException {
+ String next = _getIdentifier("a hex string");
+ byte [] array = base16.fromString(next);
+ if (array == null)
+ throw exception("invalid hex encoding");
+ return array;
+}
+
+/**
+ * Gets the next token from a tokenizer and decodes it as base32.
+ * @param b32 The base32 context to decode with.
+ * @return The byte array containing the decoded string.
+ * @throws TextParseException The input was invalid.
+ * @throws IOException An I/O error occurred.
+ */
+public byte []
+getBase32String(base32 b32) throws IOException {
+ String next = _getIdentifier("a base32 string");
+ byte [] array = b32.fromString(next);
+ if (array == null)
+ throw exception("invalid base32 encoding");
+ return array;
+}
+
+/**
+ * Creates an exception which includes the current state in the error message
+ * @param s The error message to include.
+ * @return The exception to be thrown
+ */
+public TextParseException
+exception(String s) {
+ return new TokenizerException(filename, line, s);
+}
+
+/**
+ * Closes any files opened by this tokenizer.
+ */
+public void
+close() {
+ if (wantClose) {
+ try {
+ is.close();
+ }
+ catch (IOException e) {
+ }
+ }
+}
+
+protected void
+finalize() {
+ close();
+}
+
+}
diff --git a/src/org/xbill/DNS/Type.java b/src/org/xbill/DNS/Type.java
new file mode 100644
index 0000000..df84e83
--- /dev/null
+++ b/src/org/xbill/DNS/Type.java
@@ -0,0 +1,361 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.util.HashMap;
+
+/**
+ * Constants and functions relating to DNS Types
+ *
+ * @author Brian Wellington
+ */
+
+public final class Type {
+
+/** Address */
+public static final int A = 1;
+
+/** Name server */
+public static final int NS = 2;
+
+/** Mail destination */
+public static final int MD = 3;
+
+/** Mail forwarder */
+public static final int MF = 4;
+
+/** Canonical name (alias) */
+public static final int CNAME = 5;
+
+/** Start of authority */
+public static final int SOA = 6;
+
+/** Mailbox domain name */
+public static final int MB = 7;
+
+/** Mail group member */
+public static final int MG = 8;
+
+/** Mail rename name */
+public static final int MR = 9;
+
+/** Null record */
+public static final int NULL = 10;
+
+/** Well known services */
+public static final int WKS = 11;
+
+/** Domain name pointer */
+public static final int PTR = 12;
+
+/** Host information */
+public static final int HINFO = 13;
+
+/** Mailbox information */
+public static final int MINFO = 14;
+
+/** Mail routing information */
+public static final int MX = 15;
+
+/** Text strings */
+public static final int TXT = 16;
+
+/** Responsible person */
+public static final int RP = 17;
+
+/** AFS cell database */
+public static final int AFSDB = 18;
+
+/** X.25 calling address */
+public static final int X25 = 19;
+
+/** ISDN calling address */
+public static final int ISDN = 20;
+
+/** Router */
+public static final int RT = 21;
+
+/** NSAP address */
+public static final int NSAP = 22;
+
+/** Reverse NSAP address (deprecated) */
+public static final int NSAP_PTR = 23;
+
+/** Signature */
+public static final int SIG = 24;
+
+/** Key */
+public static final int KEY = 25;
+
+/** X.400 mail mapping */
+public static final int PX = 26;
+
+/** Geographical position (withdrawn) */
+public static final int GPOS = 27;
+
+/** IPv6 address */
+public static final int AAAA = 28;
+
+/** Location */
+public static final int LOC = 29;
+
+/** Next valid name in zone */
+public static final int NXT = 30;
+
+/** Endpoint identifier */
+public static final int EID = 31;
+
+/** Nimrod locator */
+public static final int NIMLOC = 32;
+
+/** Server selection */
+public static final int SRV = 33;
+
+/** ATM address */
+public static final int ATMA = 34;
+
+/** Naming authority pointer */
+public static final int NAPTR = 35;
+
+/** Key exchange */
+public static final int KX = 36;
+
+/** Certificate */
+public static final int CERT = 37;
+
+/** IPv6 address (experimental) */
+public static final int A6 = 38;
+
+/** Non-terminal name redirection */
+public static final int DNAME = 39;
+
+/** Options - contains EDNS metadata */
+public static final int OPT = 41;
+
+/** Address Prefix List */
+public static final int APL = 42;
+
+/** Delegation Signer */
+public static final int DS = 43;
+
+/** SSH Key Fingerprint */
+public static final int SSHFP = 44;
+
+/** IPSEC key */
+public static final int IPSECKEY = 45;
+
+/** Resource Record Signature */
+public static final int RRSIG = 46;
+
+/** Next Secure Name */
+public static final int NSEC = 47;
+
+/** DNSSEC Key */
+public static final int DNSKEY = 48;
+
+/** Dynamic Host Configuration Protocol (DHCP) ID */
+public static final int DHCID = 49;
+
+/** Next SECure, 3rd edition, RFC 5155 */
+public static final int NSEC3 = 50;
+
+/** Next SECure PARAMeter, RFC 5155 */
+public static final int NSEC3PARAM = 51;
+
+/** Transport Layer Security Authentication, draft-ietf-dane-protocol-23 */
+public static final int TLSA = 52;
+
+/** Sender Policy Framework (experimental) */
+public static final int SPF = 99;
+
+/** Transaction key - used to compute a shared secret or exchange a key */
+public static final int TKEY = 249;
+
+/** Transaction signature */
+public static final int TSIG = 250;
+
+/** Incremental zone transfer */
+public static final int IXFR = 251;
+
+/** Zone transfer */
+public static final int AXFR = 252;
+
+/** Transfer mailbox records */
+public static final int MAILB = 253;
+
+/** Transfer mail agent records */
+public static final int MAILA = 254;
+
+/** Matches any type */
+public static final int ANY = 255;
+
+/** DNSSEC Lookaside Validation, RFC 4431 . */
+public static final int DLV = 32769;
+
+
+private static class TypeMnemonic extends Mnemonic {
+ private HashMap objects;
+
+ public
+ TypeMnemonic() {
+ super("Type", CASE_UPPER);
+ setPrefix("TYPE");
+ objects = new HashMap();
+ }
+
+ public void
+ add(int val, String str, Record proto) {
+ super.add(val, str);
+ objects.put(Mnemonic.toInteger(val), proto);
+ }
+
+ public void
+ check(int val) {
+ Type.check(val);
+ }
+
+ public Record
+ getProto(int val) {
+ check(val);
+ return (Record) objects.get(toInteger(val));
+ }
+}
+
+private static TypeMnemonic types = new TypeMnemonic();
+
+static {
+ types.add(A, "A", new ARecord());
+ types.add(NS, "NS", new NSRecord());
+ types.add(MD, "MD", new MDRecord());
+ types.add(MF, "MF", new MFRecord());
+ types.add(CNAME, "CNAME", new CNAMERecord());
+ types.add(SOA, "SOA", new SOARecord());
+ types.add(MB, "MB", new MBRecord());
+ types.add(MG, "MG", new MGRecord());
+ types.add(MR, "MR", new MRRecord());
+ types.add(NULL, "NULL", new NULLRecord());
+ types.add(WKS, "WKS", new WKSRecord());
+ types.add(PTR, "PTR", new PTRRecord());
+ types.add(HINFO, "HINFO", new HINFORecord());
+ types.add(MINFO, "MINFO", new MINFORecord());
+ types.add(MX, "MX", new MXRecord());
+ types.add(TXT, "TXT", new TXTRecord());
+ types.add(RP, "RP", new RPRecord());
+ types.add(AFSDB, "AFSDB", new AFSDBRecord());
+ types.add(X25, "X25", new X25Record());
+ types.add(ISDN, "ISDN", new ISDNRecord());
+ types.add(RT, "RT", new RTRecord());
+ types.add(NSAP, "NSAP", new NSAPRecord());
+ types.add(NSAP_PTR, "NSAP-PTR", new NSAP_PTRRecord());
+ types.add(SIG, "SIG", new SIGRecord());
+ types.add(KEY, "KEY", new KEYRecord());
+ types.add(PX, "PX", new PXRecord());
+ types.add(GPOS, "GPOS", new GPOSRecord());
+ types.add(AAAA, "AAAA", new AAAARecord());
+ types.add(LOC, "LOC", new LOCRecord());
+ types.add(NXT, "NXT", new NXTRecord());
+ types.add(EID, "EID");
+ types.add(NIMLOC, "NIMLOC");
+ types.add(SRV, "SRV", new SRVRecord());
+ types.add(ATMA, "ATMA");
+ types.add(NAPTR, "NAPTR", new NAPTRRecord());
+ types.add(KX, "KX", new KXRecord());
+ types.add(CERT, "CERT", new CERTRecord());
+ types.add(A6, "A6", new A6Record());
+ types.add(DNAME, "DNAME", new DNAMERecord());
+ types.add(OPT, "OPT", new OPTRecord());
+ types.add(APL, "APL", new APLRecord());
+ types.add(DS, "DS", new DSRecord());
+ types.add(SSHFP, "SSHFP", new SSHFPRecord());
+ types.add(IPSECKEY, "IPSECKEY", new IPSECKEYRecord());
+ types.add(RRSIG, "RRSIG", new RRSIGRecord());
+ types.add(NSEC, "NSEC", new NSECRecord());
+ types.add(DNSKEY, "DNSKEY", new DNSKEYRecord());
+ types.add(DHCID, "DHCID", new DHCIDRecord());
+ types.add(NSEC3, "NSEC3", new NSEC3Record());
+ types.add(NSEC3PARAM, "NSEC3PARAM", new NSEC3PARAMRecord());
+ types.add(TLSA, "TLSA", new TLSARecord());
+ types.add(SPF, "SPF", new SPFRecord());
+ types.add(TKEY, "TKEY", new TKEYRecord());
+ types.add(TSIG, "TSIG", new TSIGRecord());
+ types.add(IXFR, "IXFR");
+ types.add(AXFR, "AXFR");
+ types.add(MAILB, "MAILB");
+ types.add(MAILA, "MAILA");
+ types.add(ANY, "ANY");
+ types.add(DLV, "DLV", new DLVRecord());
+}
+
+private
+Type() {
+}
+
+/**
+ * Checks that a numeric Type is valid.
+ * @throws InvalidTypeException The type is out of range.
+ */
+public static void
+check(int val) {
+ if (val < 0 || val > 0xFFFF)
+ throw new InvalidTypeException(val);
+}
+
+/**
+ * Converts a numeric Type into a String
+ * @param val The type value.
+ * @return The canonical string representation of the type
+ * @throws InvalidTypeException The type is out of range.
+ */
+public static String
+string(int val) {
+ return types.getText(val);
+}
+
+/**
+ * Converts a String representation of an Type into its numeric value.
+ * @param s The string representation of the type
+ * @param numberok Whether a number will be accepted or not.
+ * @return The type code, or -1 on error.
+ */
+public static int
+value(String s, boolean numberok) {
+ int val = types.getValue(s);
+ if (val == -1 && numberok) {
+ val = types.getValue("TYPE" + s);
+ }
+ return val;
+}
+
+/**
+ * Converts a String representation of an Type into its numeric value
+ * @return The type code, or -1 on error.
+ */
+public static int
+value(String s) {
+ return value(s, false);
+}
+
+static Record
+getProto(int val) {
+ return types.getProto(val);
+}
+
+/** Is this type valid for a record (a non-meta type)? */
+public static boolean
+isRR(int type) {
+ switch (type) {
+ case OPT:
+ case TKEY:
+ case TSIG:
+ case IXFR:
+ case AXFR:
+ case MAILB:
+ case MAILA:
+ case ANY:
+ return false;
+ default:
+ return true;
+ }
+}
+
+}
diff --git a/src/org/xbill/DNS/TypeBitmap.java b/src/org/xbill/DNS/TypeBitmap.java
new file mode 100644
index 0000000..628cc35
--- /dev/null
+++ b/src/org/xbill/DNS/TypeBitmap.java
@@ -0,0 +1,147 @@
+// Copyright (c) 2004-2009 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * Routines for deal with the lists of types found in NSEC/NSEC3 records.
+ *
+ * @author Brian Wellington
+ */
+
+import java.io.*;
+import java.util.*;
+
+final class TypeBitmap implements Serializable {
+
+private static final long serialVersionUID = -125354057735389003L;
+
+private TreeSet types;
+
+private
+TypeBitmap() {
+ types = new TreeSet();
+}
+
+public
+TypeBitmap(int [] array) {
+ this();
+ for (int i = 0; i < array.length; i++) {
+ Type.check(array[i]);
+ types.add(new Integer(array[i]));
+ }
+}
+
+public
+TypeBitmap(DNSInput in) throws WireParseException {
+ this();
+ int lastbase = -1;
+ while (in.remaining() > 0) {
+ if (in.remaining() < 2)
+ throw new WireParseException
+ ("invalid bitmap descriptor");
+ int mapbase = in.readU8();
+ if (mapbase < lastbase)
+ throw new WireParseException("invalid ordering");
+ int maplength = in.readU8();
+ if (maplength > in.remaining())
+ throw new WireParseException("invalid bitmap");
+ for (int i = 0; i < maplength; i++) {
+ int current = in.readU8();
+ if (current == 0)
+ continue;
+ for (int j = 0; j < 8; j++) {
+ if ((current & (1 << (7 - j))) == 0)
+ continue;
+ int typecode = mapbase * 256 + + i * 8 + j;
+ types.add(Mnemonic.toInteger(typecode));
+ }
+ }
+ }
+}
+
+public
+TypeBitmap(Tokenizer st) throws IOException {
+ this();
+ while (true) {
+ Tokenizer.Token t = st.get();
+ if (!t.isString())
+ break;
+ int typecode = Type.value(t.value);
+ if (typecode < 0) {
+ throw st.exception("Invalid type: " + t.value);
+ }
+ types.add(Mnemonic.toInteger(typecode));
+ }
+ st.unget();
+}
+
+public int []
+toArray() {
+ int [] array = new int[types.size()];
+ int n = 0;
+ for (Iterator it = types.iterator(); it.hasNext(); )
+ array[n++] = ((Integer)it.next()).intValue();
+ return array;
+}
+
+public String
+toString() {
+ StringBuffer sb = new StringBuffer();
+ for (Iterator it = types.iterator(); it.hasNext(); ) {
+ int t = ((Integer)it.next()).intValue();
+ sb.append(Type.string(t));
+ if (it.hasNext())
+ sb.append(' ');
+ }
+ return sb.toString();
+}
+
+private static void
+mapToWire(DNSOutput out, TreeSet map, int mapbase) {
+ int arraymax = (((Integer)map.last()).intValue()) & 0xFF;
+ int arraylength = (arraymax / 8) + 1;
+ int [] array = new int[arraylength];
+ out.writeU8(mapbase);
+ out.writeU8(arraylength);
+ for (Iterator it = map.iterator(); it.hasNext(); ) {
+ int typecode = ((Integer)it.next()).intValue();
+ array[(typecode & 0xFF) / 8] |= (1 << ( 7 - typecode % 8));
+ }
+ for (int j = 0; j < arraylength; j++)
+ out.writeU8(array[j]);
+}
+
+public void
+toWire(DNSOutput out) {
+ if (types.size() == 0)
+ return;
+
+ int mapbase = -1;
+ TreeSet map = new TreeSet();
+
+ for (Iterator it = types.iterator(); it.hasNext(); ) {
+ int t = ((Integer)it.next()).intValue();
+ int base = t >> 8;
+ if (base != mapbase) {
+ if (map.size() > 0) {
+ mapToWire(out, map, mapbase);
+ map.clear();
+ }
+ mapbase = base;
+ }
+ map.add(new Integer(t));
+ }
+ mapToWire(out, map, mapbase);
+}
+
+public boolean
+empty() {
+ return types.isEmpty();
+}
+
+public boolean
+contains(int typecode) {
+ return types.contains(Mnemonic.toInteger(typecode));
+}
+
+}
diff --git a/src/org/xbill/DNS/U16NameBase.java b/src/org/xbill/DNS/U16NameBase.java
new file mode 100644
index 0000000..df3c836
--- /dev/null
+++ b/src/org/xbill/DNS/U16NameBase.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+
+/**
+ * Implements common functionality for the many record types whose format
+ * is an unsigned 16 bit integer followed by a name.
+ *
+ * @author Brian Wellington
+ */
+
+abstract class U16NameBase extends Record {
+
+private static final long serialVersionUID = -8315884183112502995L;
+
+protected int u16Field;
+protected Name nameField;
+
+protected
+U16NameBase() {}
+
+protected
+U16NameBase(Name name, int type, int dclass, long ttl) {
+ super(name, type, dclass, ttl);
+}
+
+protected
+U16NameBase(Name name, int type, int dclass, long ttl, int u16Field,
+ String u16Description, Name nameField, String nameDescription)
+{
+ super(name, type, dclass, ttl);
+ this.u16Field = checkU16(u16Description, u16Field);
+ this.nameField = checkName(nameDescription, nameField);
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ u16Field = in.readU16();
+ nameField = new Name(in);
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ u16Field = st.getUInt16();
+ nameField = st.getName(origin);
+}
+
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(u16Field);
+ sb.append(" ");
+ sb.append(nameField);
+ return sb.toString();
+}
+
+protected int
+getU16Field() {
+ return u16Field;
+}
+
+protected Name
+getNameField() {
+ return nameField;
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeU16(u16Field);
+ nameField.toWire(out, null, canonical);
+}
+
+}
diff --git a/src/org/xbill/DNS/UDPClient.java b/src/org/xbill/DNS/UDPClient.java
new file mode 100644
index 0000000..e752ce4
--- /dev/null
+++ b/src/org/xbill/DNS/UDPClient.java
@@ -0,0 +1,164 @@
+// Copyright (c) 2005 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.net.*;
+import java.security.SecureRandom;
+import java.nio.*;
+import java.nio.channels.*;
+
+final class UDPClient extends Client {
+
+private static final int EPHEMERAL_START = 1024;
+private static final int EPHEMERAL_STOP = 65535;
+private static final int EPHEMERAL_RANGE = EPHEMERAL_STOP - EPHEMERAL_START;
+
+private static SecureRandom prng = new SecureRandom();
+private static volatile boolean prng_initializing = true;
+
+/*
+ * On some platforms (Windows), the SecureRandom module initialization involves
+ * a call to InetAddress.getLocalHost(), which can end up here if using a
+ * dnsjava name service provider.
+ *
+ * This can cause problems in multiple ways.
+ * - If the SecureRandom seed generation process calls into here, and this
+ * module attempts to seed the local SecureRandom object, the thread hangs.
+ * - If something else calls InetAddress.getLocalHost(), and that causes this
+ * module to seed the local SecureRandom object, the thread hangs.
+ *
+ * To avoid both of these, check at initialization time to see if InetAddress
+ * is in the call chain. If so, initialize the SecureRandom object in a new
+ * thread, and disable port randomization until it completes.
+ */
+static {
+ new Thread(new Runnable() {
+ public void run() {
+ int n = prng.nextInt();
+ prng_initializing = false;
+ }}).start();
+}
+
+private boolean bound = false;
+
+public
+UDPClient(long endTime) throws IOException {
+ super(DatagramChannel.open(), endTime);
+}
+
+private void
+bind_random(InetSocketAddress addr) throws IOException
+{
+ if (prng_initializing) {
+ try {
+ Thread.sleep(2);
+ }
+ catch (InterruptedException e) {
+ }
+ if (prng_initializing)
+ return;
+ }
+
+ DatagramChannel channel = (DatagramChannel) key.channel();
+ InetSocketAddress temp;
+
+ for (int i = 0; i < 1024; i++) {
+ try {
+ int port = prng.nextInt(EPHEMERAL_RANGE) +
+ EPHEMERAL_START;
+ if (addr != null)
+ temp = new InetSocketAddress(addr.getAddress(),
+ port);
+ else
+ temp = new InetSocketAddress(port);
+ channel.socket().bind(temp);
+ bound = true;
+ return;
+ }
+ catch (SocketException e) {
+ }
+ }
+}
+
+void
+bind(SocketAddress addr) throws IOException {
+ if (addr == null ||
+ (addr instanceof InetSocketAddress &&
+ ((InetSocketAddress)addr).getPort() == 0))
+ {
+ bind_random((InetSocketAddress) addr);
+ if (bound)
+ return;
+ }
+
+ if (addr != null) {
+ DatagramChannel channel = (DatagramChannel) key.channel();
+ channel.socket().bind(addr);
+ bound = true;
+ }
+}
+
+void
+connect(SocketAddress addr) throws IOException {
+ if (!bound)
+ bind(null);
+ DatagramChannel channel = (DatagramChannel) key.channel();
+ channel.connect(addr);
+}
+
+void
+send(byte [] data) throws IOException {
+ DatagramChannel channel = (DatagramChannel) key.channel();
+ verboseLog("UDP write", data);
+ channel.write(ByteBuffer.wrap(data));
+}
+
+byte []
+recv(int max) throws IOException {
+ DatagramChannel channel = (DatagramChannel) key.channel();
+ byte [] temp = new byte[max];
+ key.interestOps(SelectionKey.OP_READ);
+ try {
+ while (!key.isReadable())
+ blockUntil(key, endTime);
+ }
+ finally {
+ if (key.isValid())
+ key.interestOps(0);
+ }
+ long ret = channel.read(ByteBuffer.wrap(temp));
+ if (ret <= 0)
+ throw new EOFException();
+ int len = (int) ret;
+ byte [] data = new byte[len];
+ System.arraycopy(temp, 0, data, 0, len);
+ verboseLog("UDP read", data);
+ return data;
+}
+
+static byte []
+sendrecv(SocketAddress local, SocketAddress remote, byte [] data, int max,
+ long endTime)
+throws IOException
+{
+ UDPClient client = new UDPClient(endTime);
+ try {
+ client.bind(local);
+ client.connect(remote);
+ client.send(data);
+ return client.recv(max);
+ }
+ finally {
+ client.cleanup();
+ }
+}
+
+static byte []
+sendrecv(SocketAddress addr, byte [] data, int max, long endTime)
+throws IOException
+{
+ return sendrecv(null, addr, data, max, endTime);
+}
+
+}
diff --git a/src/org/xbill/DNS/UNKRecord.java b/src/org/xbill/DNS/UNKRecord.java
new file mode 100644
index 0000000..91c9697
--- /dev/null
+++ b/src/org/xbill/DNS/UNKRecord.java
@@ -0,0 +1,54 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+
+/**
+ * A class implementing Records of unknown and/or unimplemented types. This
+ * class can only be initialized using static Record initializers.
+ *
+ * @author Brian Wellington
+ */
+
+public class UNKRecord extends Record {
+
+private static final long serialVersionUID = -4193583311594626915L;
+
+private byte [] data;
+
+UNKRecord() {}
+
+Record
+getObject() {
+ return new UNKRecord();
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ data = in.readByteArray();
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ throw st.exception("invalid unknown RR encoding");
+}
+
+/** Converts this Record to the String "unknown format" */
+String
+rrToString() {
+ return unknownToString(data);
+}
+
+/** Returns the contents of this record. */
+public byte []
+getData() {
+ return data;
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeByteArray(data);
+}
+
+}
diff --git a/src/org/xbill/DNS/Update.java b/src/org/xbill/DNS/Update.java
new file mode 100644
index 0000000..02a920b
--- /dev/null
+++ b/src/org/xbill/DNS/Update.java
@@ -0,0 +1,300 @@
+// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * A helper class for constructing dynamic DNS (DDNS) update messages.
+ *
+ * @author Brian Wellington
+ */
+
+public class Update extends Message {
+
+private Name origin;
+private int dclass;
+
+/**
+ * Creates an update message.
+ * @param zone The name of the zone being updated.
+ * @param dclass The class of the zone being updated.
+ */
+public
+Update(Name zone, int dclass) {
+ super();
+ if (!zone.isAbsolute())
+ throw new RelativeNameException(zone);
+ DClass.check(dclass);
+ getHeader().setOpcode(Opcode.UPDATE);
+ Record soa = Record.newRecord(zone, Type.SOA, DClass.IN);
+ addRecord(soa, Section.QUESTION);
+ this.origin = zone;
+ this.dclass = dclass;
+}
+
+/**
+ * Creates an update message. The class is assumed to be IN.
+ * @param zone The name of the zone being updated.
+ */
+public
+Update(Name zone) {
+ this(zone, DClass.IN);
+}
+
+private void
+newPrereq(Record rec) {
+ addRecord(rec, Section.PREREQ);
+}
+
+private void
+newUpdate(Record rec) {
+ addRecord(rec, Section.UPDATE);
+}
+
+/**
+ * Inserts a prerequisite that the specified name exists; that is, there
+ * exist records with the given name in the zone.
+ */
+public void
+present(Name name) {
+ newPrereq(Record.newRecord(name, Type.ANY, DClass.ANY, 0));
+}
+
+/**
+ * Inserts a prerequisite that the specified rrset exists; that is, there
+ * exist records with the given name and type in the zone.
+ */
+public void
+present(Name name, int type) {
+ newPrereq(Record.newRecord(name, type, DClass.ANY, 0));
+}
+
+/**
+ * Parses a record from the string, and inserts a prerequisite that the
+ * record exists. Due to the way value-dependent prequisites work, the
+ * condition that must be met is that the set of all records with the same
+ * and type in the update message must be identical to the set of all records
+ * with that name and type on the server.
+ * @throws IOException The record could not be parsed.
+ */
+public void
+present(Name name, int type, String record) throws IOException {
+ newPrereq(Record.fromString(name, type, dclass, 0, record, origin));
+}
+
+/**
+ * Parses a record from the tokenizer, and inserts a prerequisite that the
+ * record exists. Due to the way value-dependent prequisites work, the
+ * condition that must be met is that the set of all records with the same
+ * and type in the update message must be identical to the set of all records
+ * with that name and type on the server.
+ * @throws IOException The record could not be parsed.
+ */
+public void
+present(Name name, int type, Tokenizer tokenizer) throws IOException {
+ newPrereq(Record.fromString(name, type, dclass, 0, tokenizer, origin));
+}
+
+/**
+ * Inserts a prerequisite that the specified record exists. Due to the way
+ * value-dependent prequisites work, the condition that must be met is that
+ * the set of all records with the same and type in the update message must
+ * be identical to the set of all records with that name and type on the server.
+ */
+public void
+present(Record record) {
+ newPrereq(record);
+}
+
+/**
+ * Inserts a prerequisite that the specified name does not exist; that is,
+ * there are no records with the given name in the zone.
+ */
+public void
+absent(Name name) {
+ newPrereq(Record.newRecord(name, Type.ANY, DClass.NONE, 0));
+}
+
+/**
+ * Inserts a prerequisite that the specified rrset does not exist; that is,
+ * there are no records with the given name and type in the zone.
+ */
+public void
+absent(Name name, int type) {
+ newPrereq(Record.newRecord(name, type, DClass.NONE, 0));
+}
+
+/**
+ * Parses a record from the string, and indicates that the record
+ * should be inserted into the zone.
+ * @throws IOException The record could not be parsed.
+ */
+public void
+add(Name name, int type, long ttl, String record) throws IOException {
+ newUpdate(Record.fromString(name, type, dclass, ttl, record, origin));
+}
+
+/**
+ * Parses a record from the tokenizer, and indicates that the record
+ * should be inserted into the zone.
+ * @throws IOException The record could not be parsed.
+ */
+public void
+add(Name name, int type, long ttl, Tokenizer tokenizer) throws IOException {
+ newUpdate(Record.fromString(name, type, dclass, ttl, tokenizer,
+ origin));
+}
+
+/**
+ * Indicates that the record should be inserted into the zone.
+ */
+public void
+add(Record record) {
+ newUpdate(record);
+}
+
+/**
+ * Indicates that the records should be inserted into the zone.
+ */
+public void
+add(Record [] records) {
+ for (int i = 0; i < records.length; i++)
+ add(records[i]);
+}
+
+/**
+ * Indicates that all of the records in the rrset should be inserted into the
+ * zone.
+ */
+public void
+add(RRset rrset) {
+ for (Iterator it = rrset.rrs(); it.hasNext(); )
+ add((Record) it.next());
+}
+
+/**
+ * Indicates that all records with the given name should be deleted from
+ * the zone.
+ */
+public void
+delete(Name name) {
+ newUpdate(Record.newRecord(name, Type.ANY, DClass.ANY, 0));
+}
+
+/**
+ * Indicates that all records with the given name and type should be deleted
+ * from the zone.
+ */
+public void
+delete(Name name, int type) {
+ newUpdate(Record.newRecord(name, type, DClass.ANY, 0));
+}
+
+/**
+ * Parses a record from the string, and indicates that the record
+ * should be deleted from the zone.
+ * @throws IOException The record could not be parsed.
+ */
+public void
+delete(Name name, int type, String record) throws IOException {
+ newUpdate(Record.fromString(name, type, DClass.NONE, 0, record,
+ origin));
+}
+
+/**
+ * Parses a record from the tokenizer, and indicates that the record
+ * should be deleted from the zone.
+ * @throws IOException The record could not be parsed.
+ */
+public void
+delete(Name name, int type, Tokenizer tokenizer) throws IOException {
+ newUpdate(Record.fromString(name, type, DClass.NONE, 0, tokenizer,
+ origin));
+}
+
+/**
+ * Indicates that the specified record should be deleted from the zone.
+ */
+public void
+delete(Record record) {
+ newUpdate(record.withDClass(DClass.NONE, 0));
+}
+
+/**
+ * Indicates that the records should be deleted from the zone.
+ */
+public void
+delete(Record [] records) {
+ for (int i = 0; i < records.length; i++)
+ delete(records[i]);
+}
+
+/**
+ * Indicates that all of the records in the rrset should be deleted from the
+ * zone.
+ */
+public void
+delete(RRset rrset) {
+ for (Iterator it = rrset.rrs(); it.hasNext(); )
+ delete((Record) it.next());
+}
+
+/**
+ * Parses a record from the string, and indicates that the record
+ * should be inserted into the zone replacing any other records with the
+ * same name and type.
+ * @throws IOException The record could not be parsed.
+ */
+public void
+replace(Name name, int type, long ttl, String record) throws IOException {
+ delete(name, type);
+ add(name, type, ttl, record);
+}
+
+/**
+ * Parses a record from the tokenizer, and indicates that the record
+ * should be inserted into the zone replacing any other records with the
+ * same name and type.
+ * @throws IOException The record could not be parsed.
+ */
+public void
+replace(Name name, int type, long ttl, Tokenizer tokenizer) throws IOException
+{
+ delete(name, type);
+ add(name, type, ttl, tokenizer);
+}
+
+/**
+ * Indicates that the record should be inserted into the zone replacing any
+ * other records with the same name and type.
+ */
+public void
+replace(Record record) {
+ delete(record.getName(), record.getType());
+ add(record);
+}
+
+/**
+ * Indicates that the records should be inserted into the zone replacing any
+ * other records with the same name and type as each one.
+ */
+public void
+replace(Record [] records) {
+ for (int i = 0; i < records.length; i++)
+ replace(records[i]);
+}
+
+/**
+ * Indicates that all of the records in the rrset should be inserted into the
+ * zone replacing any other records with the same name and type.
+ */
+public void
+replace(RRset rrset) {
+ delete(rrset.getName(), rrset.getType());
+ for (Iterator it = rrset.rrs(); it.hasNext(); )
+ add((Record) it.next());
+}
+
+}
diff --git a/src/org/xbill/DNS/WKSRecord.java b/src/org/xbill/DNS/WKSRecord.java
new file mode 100644
index 0000000..10b61be
--- /dev/null
+++ b/src/org/xbill/DNS/WKSRecord.java
@@ -0,0 +1,719 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.net.*;
+import java.io.*;
+import java.util.*;
+
+/**
+ * Well Known Services - Lists services offered by this host.
+ *
+ * @author Brian Wellington
+ */
+
+public class WKSRecord extends Record {
+
+private static final long serialVersionUID = -9104259763909119805L;
+
+public static class Protocol {
+ /**
+ * IP protocol identifiers. This is basically copied out of RFC 1010.
+ */
+
+ private Protocol() {}
+
+ /** Internet Control Message */
+ public static final int ICMP = 1;
+
+ /** Internet Group Management */
+ public static final int IGMP = 2;
+
+ /** Gateway-to-Gateway */
+ public static final int GGP = 3;
+
+ /** Stream */
+ public static final int ST = 5;
+
+ /** Transmission Control */
+ public static final int TCP = 6;
+
+ /** UCL */
+ public static final int UCL = 7;
+
+ /** Exterior Gateway Protocol */
+ public static final int EGP = 8;
+
+ /** any private interior gateway */
+ public static final int IGP = 9;
+
+ /** BBN RCC Monitoring */
+ public static final int BBN_RCC_MON = 10;
+
+ /** Network Voice Protocol */
+ public static final int NVP_II = 11;
+
+ /** PUP */
+ public static final int PUP = 12;
+
+ /** ARGUS */
+ public static final int ARGUS = 13;
+
+ /** EMCON */
+ public static final int EMCON = 14;
+
+ /** Cross Net Debugger */
+ public static final int XNET = 15;
+
+ /** Chaos */
+ public static final int CHAOS = 16;
+
+ /** User Datagram */
+ public static final int UDP = 17;
+
+ /** Multiplexing */
+ public static final int MUX = 18;
+
+ /** DCN Measurement Subsystems */
+ public static final int DCN_MEAS = 19;
+
+ /** Host Monitoring */
+ public static final int HMP = 20;
+
+ /** Packet Radio Measurement */
+ public static final int PRM = 21;
+
+ /** XEROX NS IDP */
+ public static final int XNS_IDP = 22;
+
+ /** Trunk-1 */
+ public static final int TRUNK_1 = 23;
+
+ /** Trunk-2 */
+ public static final int TRUNK_2 = 24;
+
+ /** Leaf-1 */
+ public static final int LEAF_1 = 25;
+
+ /** Leaf-2 */
+ public static final int LEAF_2 = 26;
+
+ /** Reliable Data Protocol */
+ public static final int RDP = 27;
+
+ /** Internet Reliable Transaction */
+ public static final int IRTP = 28;
+
+ /** ISO Transport Protocol Class 4 */
+ public static final int ISO_TP4 = 29;
+
+ /** Bulk Data Transfer Protocol */
+ public static final int NETBLT = 30;
+
+ /** MFE Network Services Protocol */
+ public static final int MFE_NSP = 31;
+
+ /** MERIT Internodal Protocol */
+ public static final int MERIT_INP = 32;
+
+ /** Sequential Exchange Protocol */
+ public static final int SEP = 33;
+
+ /** CFTP */
+ public static final int CFTP = 62;
+
+ /** SATNET and Backroom EXPAK */
+ public static final int SAT_EXPAK = 64;
+
+ /** MIT Subnet Support */
+ public static final int MIT_SUBNET = 65;
+
+ /** MIT Remote Virtual Disk Protocol */
+ public static final int RVD = 66;
+
+ /** Internet Pluribus Packet Core */
+ public static final int IPPC = 67;
+
+ /** SATNET Monitoring */
+ public static final int SAT_MON = 69;
+
+ /** Internet Packet Core Utility */
+ public static final int IPCV = 71;
+
+ /** Backroom SATNET Monitoring */
+ public static final int BR_SAT_MON = 76;
+
+ /** WIDEBAND Monitoring */
+ public static final int WB_MON = 78;
+
+ /** WIDEBAND EXPAK */
+ public static final int WB_EXPAK = 79;
+
+ private static Mnemonic protocols = new Mnemonic("IP protocol",
+ Mnemonic.CASE_LOWER);
+
+ static {
+ protocols.setMaximum(0xFF);
+ protocols.setNumericAllowed(true);
+
+ protocols.add(ICMP, "icmp");
+ protocols.add(IGMP, "igmp");
+ protocols.add(GGP, "ggp");
+ protocols.add(ST, "st");
+ protocols.add(TCP, "tcp");
+ protocols.add(UCL, "ucl");
+ protocols.add(EGP, "egp");
+ protocols.add(IGP, "igp");
+ protocols.add(BBN_RCC_MON, "bbn-rcc-mon");
+ protocols.add(NVP_II, "nvp-ii");
+ protocols.add(PUP, "pup");
+ protocols.add(ARGUS, "argus");
+ protocols.add(EMCON, "emcon");
+ protocols.add(XNET, "xnet");
+ protocols.add(CHAOS, "chaos");
+ protocols.add(UDP, "udp");
+ protocols.add(MUX, "mux");
+ protocols.add(DCN_MEAS, "dcn-meas");
+ protocols.add(HMP, "hmp");
+ protocols.add(PRM, "prm");
+ protocols.add(XNS_IDP, "xns-idp");
+ protocols.add(TRUNK_1, "trunk-1");
+ protocols.add(TRUNK_2, "trunk-2");
+ protocols.add(LEAF_1, "leaf-1");
+ protocols.add(LEAF_2, "leaf-2");
+ protocols.add(RDP, "rdp");
+ protocols.add(IRTP, "irtp");
+ protocols.add(ISO_TP4, "iso-tp4");
+ protocols.add(NETBLT, "netblt");
+ protocols.add(MFE_NSP, "mfe-nsp");
+ protocols.add(MERIT_INP, "merit-inp");
+ protocols.add(SEP, "sep");
+ protocols.add(CFTP, "cftp");
+ protocols.add(SAT_EXPAK, "sat-expak");
+ protocols.add(MIT_SUBNET, "mit-subnet");
+ protocols.add(RVD, "rvd");
+ protocols.add(IPPC, "ippc");
+ protocols.add(SAT_MON, "sat-mon");
+ protocols.add(IPCV, "ipcv");
+ protocols.add(BR_SAT_MON, "br-sat-mon");
+ protocols.add(WB_MON, "wb-mon");
+ protocols.add(WB_EXPAK, "wb-expak");
+ }
+
+ /**
+ * Converts an IP protocol value into its textual representation
+ */
+ public static String
+ string(int type) {
+ return protocols.getText(type);
+ }
+
+ /**
+ * Converts a textual representation of an IP protocol into its
+ * numeric code. Integers in the range 0..255 are also accepted.
+ * @param s The textual representation of the protocol
+ * @return The protocol code, or -1 on error.
+ */
+ public static int
+ value(String s) {
+ return protocols.getValue(s);
+ }
+}
+
+public static class Service {
+ /**
+ * TCP/UDP services. This is basically copied out of RFC 1010,
+ * with MIT-ML-DEV removed, as it is not unique, and the description
+ * of SWIFT-RVF fixed.
+ */
+
+ private Service() {}
+
+ /** Remote Job Entry */
+ public static final int RJE = 5;
+
+ /** Echo */
+ public static final int ECHO = 7;
+
+ /** Discard */
+ public static final int DISCARD = 9;
+
+ /** Active Users */
+ public static final int USERS = 11;
+
+ /** Daytime */
+ public static final int DAYTIME = 13;
+
+ /** Quote of the Day */
+ public static final int QUOTE = 17;
+
+ /** Character Generator */
+ public static final int CHARGEN = 19;
+
+ /** File Transfer [Default Data] */
+ public static final int FTP_DATA = 20;
+
+ /** File Transfer [Control] */
+ public static final int FTP = 21;
+
+ /** Telnet */
+ public static final int TELNET = 23;
+
+ /** Simple Mail Transfer */
+ public static final int SMTP = 25;
+
+ /** NSW User System FE */
+ public static final int NSW_FE = 27;
+
+ /** MSG ICP */
+ public static final int MSG_ICP = 29;
+
+ /** MSG Authentication */
+ public static final int MSG_AUTH = 31;
+
+ /** Display Support Protocol */
+ public static final int DSP = 33;
+
+ /** Time */
+ public static final int TIME = 37;
+
+ /** Resource Location Protocol */
+ public static final int RLP = 39;
+
+ /** Graphics */
+ public static final int GRAPHICS = 41;
+
+ /** Host Name Server */
+ public static final int NAMESERVER = 42;
+
+ /** Who Is */
+ public static final int NICNAME = 43;
+
+ /** MPM FLAGS Protocol */
+ public static final int MPM_FLAGS = 44;
+
+ /** Message Processing Module [recv] */
+ public static final int MPM = 45;
+
+ /** MPM [default send] */
+ public static final int MPM_SND = 46;
+
+ /** NI FTP */
+ public static final int NI_FTP = 47;
+
+ /** Login Host Protocol */
+ public static final int LOGIN = 49;
+
+ /** IMP Logical Address Maintenance */
+ public static final int LA_MAINT = 51;
+
+ /** Domain Name Server */
+ public static final int DOMAIN = 53;
+
+ /** ISI Graphics Language */
+ public static final int ISI_GL = 55;
+
+ /** NI MAIL */
+ public static final int NI_MAIL = 61;
+
+ /** VIA Systems - FTP */
+ public static final int VIA_FTP = 63;
+
+ /** TACACS-Database Service */
+ public static final int TACACS_DS = 65;
+
+ /** Bootstrap Protocol Server */
+ public static final int BOOTPS = 67;
+
+ /** Bootstrap Protocol Client */
+ public static final int BOOTPC = 68;
+
+ /** Trivial File Transfer */
+ public static final int TFTP = 69;
+
+ /** Remote Job Service */
+ public static final int NETRJS_1 = 71;
+
+ /** Remote Job Service */
+ public static final int NETRJS_2 = 72;
+
+ /** Remote Job Service */
+ public static final int NETRJS_3 = 73;
+
+ /** Remote Job Service */
+ public static final int NETRJS_4 = 74;
+
+ /** Finger */
+ public static final int FINGER = 79;
+
+ /** HOSTS2 Name Server */
+ public static final int HOSTS2_NS = 81;
+
+ /** SU/MIT Telnet Gateway */
+ public static final int SU_MIT_TG = 89;
+
+ /** MIT Dover Spooler */
+ public static final int MIT_DOV = 91;
+
+ /** Device Control Protocol */
+ public static final int DCP = 93;
+
+ /** SUPDUP */
+ public static final int SUPDUP = 95;
+
+ /** Swift Remote Virtual File Protocol */
+ public static final int SWIFT_RVF = 97;
+
+ /** TAC News */
+ public static final int TACNEWS = 98;
+
+ /** Metagram Relay */
+ public static final int METAGRAM = 99;
+
+ /** NIC Host Name Server */
+ public static final int HOSTNAME = 101;
+
+ /** ISO-TSAP */
+ public static final int ISO_TSAP = 102;
+
+ /** X400 */
+ public static final int X400 = 103;
+
+ /** X400-SND */
+ public static final int X400_SND = 104;
+
+ /** Mailbox Name Nameserver */
+ public static final int CSNET_NS = 105;
+
+ /** Remote Telnet Service */
+ public static final int RTELNET = 107;
+
+ /** Post Office Protocol - Version 2 */
+ public static final int POP_2 = 109;
+
+ /** SUN Remote Procedure Call */
+ public static final int SUNRPC = 111;
+
+ /** Authentication Service */
+ public static final int AUTH = 113;
+
+ /** Simple File Transfer Protocol */
+ public static final int SFTP = 115;
+
+ /** UUCP Path Service */
+ public static final int UUCP_PATH = 117;
+
+ /** Network News Transfer Protocol */
+ public static final int NNTP = 119;
+
+ /** HYDRA Expedited Remote Procedure */
+ public static final int ERPC = 121;
+
+ /** Network Time Protocol */
+ public static final int NTP = 123;
+
+ /** Locus PC-Interface Net Map Server */
+ public static final int LOCUS_MAP = 125;
+
+ /** Locus PC-Interface Conn Server */
+ public static final int LOCUS_CON = 127;
+
+ /** Password Generator Protocol */
+ public static final int PWDGEN = 129;
+
+ /** CISCO FNATIVE */
+ public static final int CISCO_FNA = 130;
+
+ /** CISCO TNATIVE */
+ public static final int CISCO_TNA = 131;
+
+ /** CISCO SYSMAINT */
+ public static final int CISCO_SYS = 132;
+
+ /** Statistics Service */
+ public static final int STATSRV = 133;
+
+ /** INGRES-NET Service */
+ public static final int INGRES_NET = 134;
+
+ /** Location Service */
+ public static final int LOC_SRV = 135;
+
+ /** PROFILE Naming System */
+ public static final int PROFILE = 136;
+
+ /** NETBIOS Name Service */
+ public static final int NETBIOS_NS = 137;
+
+ /** NETBIOS Datagram Service */
+ public static final int NETBIOS_DGM = 138;
+
+ /** NETBIOS Session Service */
+ public static final int NETBIOS_SSN = 139;
+
+ /** EMFIS Data Service */
+ public static final int EMFIS_DATA = 140;
+
+ /** EMFIS Control Service */
+ public static final int EMFIS_CNTL = 141;
+
+ /** Britton-Lee IDM */
+ public static final int BL_IDM = 142;
+
+ /** Survey Measurement */
+ public static final int SUR_MEAS = 243;
+
+ /** LINK */
+ public static final int LINK = 245;
+
+ private static Mnemonic services = new Mnemonic("TCP/UDP service",
+ Mnemonic.CASE_LOWER);
+
+ static {
+ services.setMaximum(0xFFFF);
+ services.setNumericAllowed(true);
+
+ services.add(RJE, "rje");
+ services.add(ECHO, "echo");
+ services.add(DISCARD, "discard");
+ services.add(USERS, "users");
+ services.add(DAYTIME, "daytime");
+ services.add(QUOTE, "quote");
+ services.add(CHARGEN, "chargen");
+ services.add(FTP_DATA, "ftp-data");
+ services.add(FTP, "ftp");
+ services.add(TELNET, "telnet");
+ services.add(SMTP, "smtp");
+ services.add(NSW_FE, "nsw-fe");
+ services.add(MSG_ICP, "msg-icp");
+ services.add(MSG_AUTH, "msg-auth");
+ services.add(DSP, "dsp");
+ services.add(TIME, "time");
+ services.add(RLP, "rlp");
+ services.add(GRAPHICS, "graphics");
+ services.add(NAMESERVER, "nameserver");
+ services.add(NICNAME, "nicname");
+ services.add(MPM_FLAGS, "mpm-flags");
+ services.add(MPM, "mpm");
+ services.add(MPM_SND, "mpm-snd");
+ services.add(NI_FTP, "ni-ftp");
+ services.add(LOGIN, "login");
+ services.add(LA_MAINT, "la-maint");
+ services.add(DOMAIN, "domain");
+ services.add(ISI_GL, "isi-gl");
+ services.add(NI_MAIL, "ni-mail");
+ services.add(VIA_FTP, "via-ftp");
+ services.add(TACACS_DS, "tacacs-ds");
+ services.add(BOOTPS, "bootps");
+ services.add(BOOTPC, "bootpc");
+ services.add(TFTP, "tftp");
+ services.add(NETRJS_1, "netrjs-1");
+ services.add(NETRJS_2, "netrjs-2");
+ services.add(NETRJS_3, "netrjs-3");
+ services.add(NETRJS_4, "netrjs-4");
+ services.add(FINGER, "finger");
+ services.add(HOSTS2_NS, "hosts2-ns");
+ services.add(SU_MIT_TG, "su-mit-tg");
+ services.add(MIT_DOV, "mit-dov");
+ services.add(DCP, "dcp");
+ services.add(SUPDUP, "supdup");
+ services.add(SWIFT_RVF, "swift-rvf");
+ services.add(TACNEWS, "tacnews");
+ services.add(METAGRAM, "metagram");
+ services.add(HOSTNAME, "hostname");
+ services.add(ISO_TSAP, "iso-tsap");
+ services.add(X400, "x400");
+ services.add(X400_SND, "x400-snd");
+ services.add(CSNET_NS, "csnet-ns");
+ services.add(RTELNET, "rtelnet");
+ services.add(POP_2, "pop-2");
+ services.add(SUNRPC, "sunrpc");
+ services.add(AUTH, "auth");
+ services.add(SFTP, "sftp");
+ services.add(UUCP_PATH, "uucp-path");
+ services.add(NNTP, "nntp");
+ services.add(ERPC, "erpc");
+ services.add(NTP, "ntp");
+ services.add(LOCUS_MAP, "locus-map");
+ services.add(LOCUS_CON, "locus-con");
+ services.add(PWDGEN, "pwdgen");
+ services.add(CISCO_FNA, "cisco-fna");
+ services.add(CISCO_TNA, "cisco-tna");
+ services.add(CISCO_SYS, "cisco-sys");
+ services.add(STATSRV, "statsrv");
+ services.add(INGRES_NET, "ingres-net");
+ services.add(LOC_SRV, "loc-srv");
+ services.add(PROFILE, "profile");
+ services.add(NETBIOS_NS, "netbios-ns");
+ services.add(NETBIOS_DGM, "netbios-dgm");
+ services.add(NETBIOS_SSN, "netbios-ssn");
+ services.add(EMFIS_DATA, "emfis-data");
+ services.add(EMFIS_CNTL, "emfis-cntl");
+ services.add(BL_IDM, "bl-idm");
+ services.add(SUR_MEAS, "sur-meas");
+ services.add(LINK, "link");
+ }
+
+ /**
+ * Converts a TCP/UDP service port number into its textual
+ * representation.
+ */
+ public static String
+ string(int type) {
+ return services.getText(type);
+ }
+
+ /**
+ * Converts a textual representation of a TCP/UDP service into its
+ * port number. Integers in the range 0..65535 are also accepted.
+ * @param s The textual representation of the service.
+ * @return The port number, or -1 on error.
+ */
+ public static int
+ value(String s) {
+ return services.getValue(s);
+ }
+}
+private byte [] address;
+private int protocol;
+private int [] services;
+
+WKSRecord() {}
+
+Record
+getObject() {
+ return new WKSRecord();
+}
+
+/**
+ * Creates a WKS Record from the given data
+ * @param address The IP address
+ * @param protocol The IP protocol number
+ * @param services An array of supported services, represented by port number.
+ */
+public
+WKSRecord(Name name, int dclass, long ttl, InetAddress address, int protocol,
+ int [] services)
+{
+ super(name, Type.WKS, dclass, ttl);
+ if (Address.familyOf(address) != Address.IPv4)
+ throw new IllegalArgumentException("invalid IPv4 address");
+ this.address = address.getAddress();
+ this.protocol = checkU8("protocol", protocol);
+ for (int i = 0; i < services.length; i++) {
+ checkU16("service", services[i]);
+ }
+ this.services = new int[services.length];
+ System.arraycopy(services, 0, this.services, 0, services.length);
+ Arrays.sort(this.services);
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ address = in.readByteArray(4);
+ protocol = in.readU8();
+ byte [] array = in.readByteArray();
+ List list = new ArrayList();
+ for (int i = 0; i < array.length; i++) {
+ for (int j = 0; j < 8; j++) {
+ int octet = array[i] & 0xFF;
+ if ((octet & (1 << (7 - j))) != 0) {
+ list.add(new Integer(i * 8 + j));
+ }
+ }
+ }
+ services = new int[list.size()];
+ for (int i = 0; i < list.size(); i++) {
+ services[i] = ((Integer) list.get(i)).intValue();
+ }
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ String s = st.getString();
+ address = Address.toByteArray(s, Address.IPv4);
+ if (address == null)
+ throw st.exception("invalid address");
+
+ s = st.getString();
+ protocol = Protocol.value(s);
+ if (protocol < 0) {
+ throw st.exception("Invalid IP protocol: " + s);
+ }
+
+ List list = new ArrayList();
+ while (true) {
+ Tokenizer.Token t = st.get();
+ if (!t.isString())
+ break;
+ int service = Service.value(t.value);
+ if (service < 0) {
+ throw st.exception("Invalid TCP/UDP service: " +
+ t.value);
+ }
+ list.add(new Integer(service));
+ }
+ st.unget();
+ services = new int[list.size()];
+ for (int i = 0; i < list.size(); i++) {
+ services[i] = ((Integer) list.get(i)).intValue();
+ }
+}
+
+/**
+ * Converts rdata to a String
+ */
+String
+rrToString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(Address.toDottedQuad(address));
+ sb.append(" ");
+ sb.append(protocol);
+ for (int i = 0; i < services.length; i++) {
+ sb.append(" " + services[i]);
+ }
+ return sb.toString();
+}
+
+/**
+ * Returns the IP address.
+ */
+public InetAddress
+getAddress() {
+ try {
+ return InetAddress.getByAddress(address);
+ } catch (UnknownHostException e) {
+ return null;
+ }
+}
+
+/**
+ * Returns the IP protocol.
+ */
+public int
+getProtocol() {
+ return protocol;
+}
+
+/**
+ * Returns the services provided by the host on the specified address.
+ */
+public int []
+getServices() {
+ return services;
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeByteArray(address);
+ out.writeU8(protocol);
+ int highestPort = services[services.length - 1];
+ byte [] array = new byte[highestPort / 8 + 1];
+ for (int i = 0; i < services.length; i++) {
+ int port = services[i];
+ array[port / 8] |= (1 << (7 - port % 8));
+ }
+ out.writeByteArray(array);
+}
+
+}
diff --git a/src/org/xbill/DNS/WireParseException.java b/src/org/xbill/DNS/WireParseException.java
new file mode 100644
index 0000000..2842731
--- /dev/null
+++ b/src/org/xbill/DNS/WireParseException.java
@@ -0,0 +1,31 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+
+/**
+ * An exception thrown when a DNS message is invalid.
+ *
+ * @author Brian Wellington
+ */
+
+public class WireParseException extends IOException {
+
+public
+WireParseException() {
+ super();
+}
+
+public
+WireParseException(String s) {
+ super(s);
+}
+
+public
+WireParseException(String s, Throwable cause) {
+ super(s);
+ initCause(cause);
+}
+
+}
diff --git a/src/org/xbill/DNS/X25Record.java b/src/org/xbill/DNS/X25Record.java
new file mode 100644
index 0000000..1349a1e
--- /dev/null
+++ b/src/org/xbill/DNS/X25Record.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+
+/**
+ * X25 - identifies the PSDN (Public Switched Data Network) address in the
+ * X.121 numbering plan associated with a name.
+ *
+ * @author Brian Wellington
+ */
+
+public class X25Record extends Record {
+
+private static final long serialVersionUID = 4267576252335579764L;
+
+private byte [] address;
+
+X25Record() {}
+
+Record
+getObject() {
+ return new X25Record();
+}
+
+private static final byte []
+checkAndConvertAddress(String address) {
+ int length = address.length();
+ byte [] out = new byte [length];
+ for (int i = 0; i < length; i++) {
+ char c = address.charAt(i);
+ if (!Character.isDigit(c))
+ return null;
+ out[i] = (byte) c;
+ }
+ return out;
+}
+
+/**
+ * Creates an X25 Record from the given data
+ * @param address The X.25 PSDN address.
+ * @throws IllegalArgumentException The address is not a valid PSDN address.
+ */
+public
+X25Record(Name name, int dclass, long ttl, String address) {
+ super(name, Type.X25, dclass, ttl);
+ this.address = checkAndConvertAddress(address);
+ if (this.address == null) {
+ throw new IllegalArgumentException("invalid PSDN address " +
+ address);
+ }
+}
+
+void
+rrFromWire(DNSInput in) throws IOException {
+ address = in.readCountedString();
+}
+
+void
+rdataFromString(Tokenizer st, Name origin) throws IOException {
+ String addr = st.getString();
+ this.address = checkAndConvertAddress(addr);
+ if (this.address == null)
+ throw st.exception("invalid PSDN address " + addr);
+}
+
+/**
+ * Returns the X.25 PSDN address.
+ */
+public String
+getAddress() {
+ return byteArrayToString(address, false);
+}
+
+void
+rrToWire(DNSOutput out, Compression c, boolean canonical) {
+ out.writeCountedString(address);
+}
+
+String
+rrToString() {
+ return byteArrayToString(address, true);
+}
+
+}
diff --git a/src/org/xbill/DNS/Zone.java b/src/org/xbill/DNS/Zone.java
new file mode 100644
index 0000000..866be77
--- /dev/null
+++ b/src/org/xbill/DNS/Zone.java
@@ -0,0 +1,559 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * A DNS Zone. This encapsulates all data related to a Zone, and provides
+ * convenient lookup methods.
+ *
+ * @author Brian Wellington
+ */
+
+public class Zone implements Serializable {
+
+private static final long serialVersionUID = -9220510891189510942L;
+
+/** A primary zone */
+public static final int PRIMARY = 1;
+
+/** A secondary zone */
+public static final int SECONDARY = 2;
+
+private Map data;
+private Name origin;
+private Object originNode;
+private int dclass = DClass.IN;
+private RRset NS;
+private SOARecord SOA;
+private boolean hasWild;
+
+class ZoneIterator implements Iterator {
+ private Iterator zentries;
+ private RRset [] current;
+ private int count;
+ private boolean wantLastSOA;
+
+ ZoneIterator(boolean axfr) {
+ synchronized (Zone.this) {
+ zentries = data.entrySet().iterator();
+ }
+ wantLastSOA = axfr;
+ RRset [] sets = allRRsets(originNode);
+ current = new RRset[sets.length];
+ for (int i = 0, j = 2; i < sets.length; i++) {
+ int type = sets[i].getType();
+ if (type == Type.SOA)
+ current[0] = sets[i];
+ else if (type == Type.NS)
+ current[1] = sets[i];
+ else
+ current[j++] = sets[i];
+ }
+ }
+
+ public boolean
+ hasNext() {
+ return (current != null || wantLastSOA);
+ }
+
+ public Object
+ next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ if (current == null) {
+ wantLastSOA = false;
+ return oneRRset(originNode, Type.SOA);
+ }
+ Object set = current[count++];
+ if (count == current.length) {
+ current = null;
+ while (zentries.hasNext()) {
+ Map.Entry entry = (Map.Entry) zentries.next();
+ if (entry.getKey().equals(origin))
+ continue;
+ RRset [] sets = allRRsets(entry.getValue());
+ if (sets.length == 0)
+ continue;
+ current = sets;
+ count = 0;
+ break;
+ }
+ }
+ return set;
+ }
+
+ public void
+ remove() {
+ throw new UnsupportedOperationException();
+ }
+}
+
+private void
+validate() throws IOException {
+ originNode = exactName(origin);
+ if (originNode == null)
+ throw new IOException(origin + ": no data specified");
+
+ RRset rrset = oneRRset(originNode, Type.SOA);
+ if (rrset == null || rrset.size() != 1)
+ throw new IOException(origin +
+ ": exactly 1 SOA must be specified");
+ Iterator it = rrset.rrs();
+ SOA = (SOARecord) it.next();
+
+ NS = oneRRset(originNode, Type.NS);
+ if (NS == null)
+ throw new IOException(origin + ": no NS set specified");
+}
+
+private final void
+maybeAddRecord(Record record) throws IOException {
+ int rtype = record.getType();
+ Name name = record.getName();
+
+ if (rtype == Type.SOA && !name.equals(origin)) {
+ throw new IOException("SOA owner " + name +
+ " does not match zone origin " +
+ origin);
+ }
+ if (name.subdomain(origin))
+ addRecord(record);
+}
+
+/**
+ * Creates a Zone from the records in the specified master file.
+ * @param zone The name of the zone.
+ * @param file The master file to read from.
+ * @see Master
+ */
+public
+Zone(Name zone, String file) throws IOException {
+ data = new TreeMap();
+
+ if (zone == null)
+ throw new IllegalArgumentException("no zone name specified");
+ Master m = new Master(file, zone);
+ Record record;
+
+ origin = zone;
+ while ((record = m.nextRecord()) != null)
+ maybeAddRecord(record);
+ validate();
+}
+
+/**
+ * Creates a Zone from an array of records.
+ * @param zone The name of the zone.
+ * @param records The records to add to the zone.
+ * @see Master
+ */
+public
+Zone(Name zone, Record [] records) throws IOException {
+ data = new TreeMap();
+
+ if (zone == null)
+ throw new IllegalArgumentException("no zone name specified");
+ origin = zone;
+ for (int i = 0; i < records.length; i++)
+ maybeAddRecord(records[i]);
+ validate();
+}
+
+private void
+fromXFR(ZoneTransferIn xfrin) throws IOException, ZoneTransferException {
+ data = new TreeMap();
+
+ origin = xfrin.getName();
+ List records = xfrin.run();
+ for (Iterator it = records.iterator(); it.hasNext(); ) {
+ Record record = (Record) it.next();
+ maybeAddRecord(record);
+ }
+ if (!xfrin.isAXFR())
+ throw new IllegalArgumentException("zones can only be " +
+ "created from AXFRs");
+ validate();
+}
+
+/**
+ * Creates a Zone by doing the specified zone transfer.
+ * @param xfrin The incoming zone transfer to execute.
+ * @see ZoneTransferIn
+ */
+public
+Zone(ZoneTransferIn xfrin) throws IOException, ZoneTransferException {
+ fromXFR(xfrin);
+}
+
+/**
+ * Creates a Zone by performing a zone transfer to the specified host.
+ * @see ZoneTransferIn
+ */
+public
+Zone(Name zone, int dclass, String remote)
+throws IOException, ZoneTransferException
+{
+ ZoneTransferIn xfrin = ZoneTransferIn.newAXFR(zone, remote, null);
+ xfrin.setDClass(dclass);
+ fromXFR(xfrin);
+}
+
+/** Returns the Zone's origin */
+public Name
+getOrigin() {
+ return origin;
+}
+
+/** Returns the Zone origin's NS records */
+public RRset
+getNS() {
+ return NS;
+}
+
+/** Returns the Zone's SOA record */
+public SOARecord
+getSOA() {
+ return SOA;
+}
+
+/** Returns the Zone's class */
+public int
+getDClass() {
+ return dclass;
+}
+
+private synchronized Object
+exactName(Name name) {
+ return data.get(name);
+}
+
+private synchronized RRset []
+allRRsets(Object types) {
+ if (types instanceof List) {
+ List typelist = (List) types;
+ return (RRset []) typelist.toArray(new RRset[typelist.size()]);
+ } else {
+ RRset set = (RRset) types;
+ return new RRset [] {set};
+ }
+}
+
+private synchronized RRset
+oneRRset(Object types, int type) {
+ if (type == Type.ANY)
+ throw new IllegalArgumentException("oneRRset(ANY)");
+ if (types instanceof List) {
+ List list = (List) types;
+ for (int i = 0; i < list.size(); i++) {
+ RRset set = (RRset) list.get(i);
+ if (set.getType() == type)
+ return set;
+ }
+ } else {
+ RRset set = (RRset) types;
+ if (set.getType() == type)
+ return set;
+ }
+ return null;
+}
+
+private synchronized RRset
+findRRset(Name name, int type) {
+ Object types = exactName(name);
+ if (types == null)
+ return null;
+ return oneRRset(types, type);
+}
+
+private synchronized void
+addRRset(Name name, RRset rrset) {
+ if (!hasWild && name.isWild())
+ hasWild = true;
+ Object types = data.get(name);
+ if (types == null) {
+ data.put(name, rrset);
+ return;
+ }
+ int rtype = rrset.getType();
+ if (types instanceof List) {
+ List list = (List) types;
+ for (int i = 0; i < list.size(); i++) {
+ RRset set = (RRset) list.get(i);
+ if (set.getType() == rtype) {
+ list.set(i, rrset);
+ return;
+ }
+ }
+ list.add(rrset);
+ } else {
+ RRset set = (RRset) types;
+ if (set.getType() == rtype)
+ data.put(name, rrset);
+ else {
+ LinkedList list = new LinkedList();
+ list.add(set);
+ list.add(rrset);
+ data.put(name, list);
+ }
+ }
+}
+
+private synchronized void
+removeRRset(Name name, int type) {
+ Object types = data.get(name);
+ if (types == null) {
+ return;
+ }
+ if (types instanceof List) {
+ List list = (List) types;
+ for (int i = 0; i < list.size(); i++) {
+ RRset set = (RRset) list.get(i);
+ if (set.getType() == type) {
+ list.remove(i);
+ if (list.size() == 0)
+ data.remove(name);
+ return;
+ }
+ }
+ } else {
+ RRset set = (RRset) types;
+ if (set.getType() != type)
+ return;
+ data.remove(name);
+ }
+}
+
+private synchronized SetResponse
+lookup(Name name, int type) {
+ int labels;
+ int olabels;
+ int tlabels;
+ RRset rrset;
+ Name tname;
+ Object types;
+ SetResponse sr;
+
+ if (!name.subdomain(origin))
+ return SetResponse.ofType(SetResponse.NXDOMAIN);
+
+ labels = name.labels();
+ olabels = origin.labels();
+
+ for (tlabels = olabels; tlabels <= labels; tlabels++) {
+ boolean isOrigin = (tlabels == olabels);
+ boolean isExact = (tlabels == labels);
+
+ if (isOrigin)
+ tname = origin;
+ else if (isExact)
+ tname = name;
+ else
+ tname = new Name(name, labels - tlabels);
+
+ types = exactName(tname);
+ if (types == null)
+ continue;
+
+ /* If this is a delegation, return that. */
+ if (!isOrigin) {
+ RRset ns = oneRRset(types, Type.NS);
+ if (ns != null)
+ return new SetResponse(SetResponse.DELEGATION,
+ ns);
+ }
+
+ /* If this is an ANY lookup, return everything. */
+ if (isExact && type == Type.ANY) {
+ sr = new SetResponse(SetResponse.SUCCESSFUL);
+ RRset [] sets = allRRsets(types);
+ for (int i = 0; i < sets.length; i++)
+ sr.addRRset(sets[i]);
+ return sr;
+ }
+
+ /*
+ * If this is the name, look for the actual type or a CNAME.
+ * Otherwise, look for a DNAME.
+ */
+ if (isExact) {
+ rrset = oneRRset(types, type);
+ if (rrset != null) {
+ sr = new SetResponse(SetResponse.SUCCESSFUL);
+ sr.addRRset(rrset);
+ return sr;
+ }
+ rrset = oneRRset(types, Type.CNAME);
+ if (rrset != null)
+ return new SetResponse(SetResponse.CNAME,
+ rrset);
+ } else {
+ rrset = oneRRset(types, Type.DNAME);
+ if (rrset != null)
+ return new SetResponse(SetResponse.DNAME,
+ rrset);
+ }
+
+ /* We found the name, but not the type. */
+ if (isExact)
+ return SetResponse.ofType(SetResponse.NXRRSET);
+ }
+
+ if (hasWild) {
+ for (int i = 0; i < labels - olabels; i++) {
+ tname = name.wild(i + 1);
+
+ types = exactName(tname);
+ if (types == null)
+ continue;
+
+ rrset = oneRRset(types, type);
+ if (rrset != null) {
+ sr = new SetResponse(SetResponse.SUCCESSFUL);
+ sr.addRRset(rrset);
+ return sr;
+ }
+ }
+ }
+
+ return SetResponse.ofType(SetResponse.NXDOMAIN);
+}
+
+/**
+ * Looks up Records in the Zone. This follows CNAMEs and wildcards.
+ * @param name The name to look up
+ * @param type The type to look up
+ * @return A SetResponse object
+ * @see SetResponse
+ */
+public SetResponse
+findRecords(Name name, int type) {
+ return lookup(name, type);
+}
+
+/**
+ * Looks up Records in the zone, finding exact matches only.
+ * @param name The name to look up
+ * @param type The type to look up
+ * @return The matching RRset
+ * @see RRset
+ */
+public RRset
+findExactMatch(Name name, int type) {
+ Object types = exactName(name);
+ if (types == null)
+ return null;
+ return oneRRset(types, type);
+}
+
+/**
+ * Adds an RRset to the Zone
+ * @param rrset The RRset to be added
+ * @see RRset
+ */
+public void
+addRRset(RRset rrset) {
+ Name name = rrset.getName();
+ addRRset(name, rrset);
+}
+
+/**
+ * Adds a Record to the Zone
+ * @param r The record to be added
+ * @see Record
+ */
+public void
+addRecord(Record r) {
+ Name name = r.getName();
+ int rtype = r.getRRsetType();
+ synchronized (this) {
+ RRset rrset = findRRset(name, rtype);
+ if (rrset == null) {
+ rrset = new RRset(r);
+ addRRset(name, rrset);
+ } else {
+ rrset.addRR(r);
+ }
+ }
+}
+
+/**
+ * Removes a record from the Zone
+ * @param r The record to be removed
+ * @see Record
+ */
+public void
+removeRecord(Record r) {
+ Name name = r.getName();
+ int rtype = r.getRRsetType();
+ synchronized (this) {
+ RRset rrset = findRRset(name, rtype);
+ if (rrset == null)
+ return;
+ if (rrset.size() == 1 && rrset.first().equals(r))
+ removeRRset(name, rtype);
+ else
+ rrset.deleteRR(r);
+ }
+}
+
+/**
+ * Returns an Iterator over the RRsets in the zone.
+ */
+public Iterator
+iterator() {
+ return new ZoneIterator(false);
+}
+
+/**
+ * Returns an Iterator over the RRsets in the zone that can be used to
+ * construct an AXFR response. This is identical to {@link #iterator} except
+ * that the SOA is returned at the end as well as the beginning.
+ */
+public Iterator
+AXFR() {
+ return new ZoneIterator(true);
+}
+
+private void
+nodeToString(StringBuffer sb, Object node) {
+ RRset [] sets = allRRsets(node);
+ for (int i = 0; i < sets.length; i++) {
+ RRset rrset = sets[i];
+ Iterator it = rrset.rrs();
+ while (it.hasNext())
+ sb.append(it.next() + "\n");
+ it = rrset.sigs();
+ while (it.hasNext())
+ sb.append(it.next() + "\n");
+ }
+}
+
+/**
+ * Returns the contents of the Zone in master file format.
+ */
+public synchronized String
+toMasterFile() {
+ Iterator zentries = data.entrySet().iterator();
+ StringBuffer sb = new StringBuffer();
+ nodeToString(sb, originNode);
+ while (zentries.hasNext()) {
+ Map.Entry entry = (Map.Entry) zentries.next();
+ if (!origin.equals(entry.getKey()))
+ nodeToString(sb, entry.getValue());
+ }
+ return sb.toString();
+}
+
+/**
+ * Returns the contents of the Zone as a string (in master file format).
+ */
+public String
+toString() {
+ return toMasterFile();
+}
+
+}
diff --git a/src/org/xbill/DNS/ZoneTransferException.java b/src/org/xbill/DNS/ZoneTransferException.java
new file mode 100644
index 0000000..3ba487b
--- /dev/null
+++ b/src/org/xbill/DNS/ZoneTransferException.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+/**
+ * An exception thrown when a zone transfer fails.
+ *
+ * @author Brian Wellington
+ */
+
+public class ZoneTransferException extends Exception {
+
+public
+ZoneTransferException() {
+ super();
+}
+
+public
+ZoneTransferException(String s) {
+ super(s);
+}
+
+}
diff --git a/src/org/xbill/DNS/ZoneTransferIn.java b/src/org/xbill/DNS/ZoneTransferIn.java
new file mode 100644
index 0000000..8a19992
--- /dev/null
+++ b/src/org/xbill/DNS/ZoneTransferIn.java
@@ -0,0 +1,680 @@
+// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org)
+// Parts of this are derived from lib/dns/xfrin.c from BIND 9; its copyright
+// notice follows.
+
+/*
+ * Copyright (C) 1999-2001 Internet Software Consortium.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM
+ * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+ * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+ * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+ * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+/**
+ * An incoming DNS Zone Transfer. To use this class, first initialize an
+ * object, then call the run() method. If run() doesn't throw an exception
+ * the result will either be an IXFR-style response, an AXFR-style response,
+ * or an indication that the zone is up to date.
+ *
+ * @author Brian Wellington
+ */
+
+public class ZoneTransferIn {
+
+private static final int INITIALSOA = 0;
+private static final int FIRSTDATA = 1;
+private static final int IXFR_DELSOA = 2;
+private static final int IXFR_DEL = 3;
+private static final int IXFR_ADDSOA = 4;
+private static final int IXFR_ADD = 5;
+private static final int AXFR = 6;
+private static final int END = 7;
+
+private Name zname;
+private int qtype;
+private int dclass;
+private long ixfr_serial;
+private boolean want_fallback;
+private ZoneTransferHandler handler;
+
+private SocketAddress localAddress;
+private SocketAddress address;
+private TCPClient client;
+private TSIG tsig;
+private TSIG.StreamVerifier verifier;
+private long timeout = 900 * 1000;
+
+private int state;
+private long end_serial;
+private long current_serial;
+private Record initialsoa;
+
+private int rtype;
+
+public static class Delta {
+ /**
+ * All changes between two versions of a zone in an IXFR response.
+ */
+
+ /** The starting serial number of this delta. */
+ public long start;
+
+ /** The ending serial number of this delta. */
+ public long end;
+
+ /** A list of records added between the start and end versions */
+ public List adds;
+
+ /** A list of records deleted between the start and end versions */
+ public List deletes;
+
+ private
+ Delta() {
+ adds = new ArrayList();
+ deletes = new ArrayList();
+ }
+}
+
+public static interface ZoneTransferHandler {
+ /**
+ * Handles a Zone Transfer.
+ */
+
+ /**
+ * Called when an AXFR transfer begins.
+ */
+ public void startAXFR() throws ZoneTransferException;
+
+ /**
+ * Called when an IXFR transfer begins.
+ */
+ public void startIXFR() throws ZoneTransferException;
+
+ /**
+ * Called when a series of IXFR deletions begins.
+ * @param soa The starting SOA.
+ */
+ public void startIXFRDeletes(Record soa) throws ZoneTransferException;
+
+ /**
+ * Called when a series of IXFR adds begins.
+ * @param soa The starting SOA.
+ */
+ public void startIXFRAdds(Record soa) throws ZoneTransferException;
+
+ /**
+ * Called for each content record in an AXFR.
+ * @param r The DNS record.
+ */
+ public void handleRecord(Record r) throws ZoneTransferException;
+};
+
+private static class BasicHandler implements ZoneTransferHandler {
+ private List axfr;
+ private List ixfr;
+
+ public void startAXFR() {
+ axfr = new ArrayList();
+ }
+
+ public void startIXFR() {
+ ixfr = new ArrayList();
+ }
+
+ public void startIXFRDeletes(Record soa) {
+ Delta delta = new Delta();
+ delta.deletes.add(soa);
+ delta.start = getSOASerial(soa);
+ ixfr.add(delta);
+ }
+
+ public void startIXFRAdds(Record soa) {
+ Delta delta = (Delta) ixfr.get(ixfr.size() - 1);
+ delta.adds.add(soa);
+ delta.end = getSOASerial(soa);
+ }
+
+ public void handleRecord(Record r) {
+ List list;
+ if (ixfr != null) {
+ Delta delta = (Delta) ixfr.get(ixfr.size() - 1);
+ if (delta.adds.size() > 0)
+ list = delta.adds;
+ else
+ list = delta.deletes;
+ } else
+ list = axfr;
+ list.add(r);
+ }
+};
+
+private
+ZoneTransferIn() {}
+
+private
+ZoneTransferIn(Name zone, int xfrtype, long serial, boolean fallback,
+ SocketAddress address, TSIG key)
+{
+ this.address = address;
+ this.tsig = key;
+ if (zone.isAbsolute())
+ zname = zone;
+ else {
+ try {
+ zname = Name.concatenate(zone, Name.root);
+ }
+ catch (NameTooLongException e) {
+ throw new IllegalArgumentException("ZoneTransferIn: " +
+ "name too long");
+ }
+ }
+ qtype = xfrtype;
+ dclass = DClass.IN;
+ ixfr_serial = serial;
+ want_fallback = fallback;
+ state = INITIALSOA;
+}
+
+/**
+ * Instantiates a ZoneTransferIn object to do an AXFR (full zone transfer).
+ * @param zone The zone to transfer.
+ * @param address The host/port from which to transfer the zone.
+ * @param key The TSIG key used to authenticate the transfer, or null.
+ * @return The ZoneTransferIn object.
+ * @throws UnknownHostException The host does not exist.
+ */
+public static ZoneTransferIn
+newAXFR(Name zone, SocketAddress address, TSIG key) {
+ return new ZoneTransferIn(zone, Type.AXFR, 0, false, address, key);
+}
+
+/**
+ * Instantiates a ZoneTransferIn object to do an AXFR (full zone transfer).
+ * @param zone The zone to transfer.
+ * @param host The host from which to transfer the zone.
+ * @param port The port to connect to on the server, or 0 for the default.
+ * @param key The TSIG key used to authenticate the transfer, or null.
+ * @return The ZoneTransferIn object.
+ * @throws UnknownHostException The host does not exist.
+ */
+public static ZoneTransferIn
+newAXFR(Name zone, String host, int port, TSIG key)
+throws UnknownHostException
+{
+ if (port == 0)
+ port = SimpleResolver.DEFAULT_PORT;
+ return newAXFR(zone, new InetSocketAddress(host, port), key);
+}
+
+/**
+ * Instantiates a ZoneTransferIn object to do an AXFR (full zone transfer).
+ * @param zone The zone to transfer.
+ * @param host The host from which to transfer the zone.
+ * @param key The TSIG key used to authenticate the transfer, or null.
+ * @return The ZoneTransferIn object.
+ * @throws UnknownHostException The host does not exist.
+ */
+public static ZoneTransferIn
+newAXFR(Name zone, String host, TSIG key)
+throws UnknownHostException
+{
+ return newAXFR(zone, host, 0, key);
+}
+
+/**
+ * Instantiates a ZoneTransferIn object to do an IXFR (incremental zone
+ * transfer).
+ * @param zone The zone to transfer.
+ * @param serial The existing serial number.
+ * @param fallback If true, fall back to AXFR if IXFR is not supported.
+ * @param address The host/port from which to transfer the zone.
+ * @param key The TSIG key used to authenticate the transfer, or null.
+ * @return The ZoneTransferIn object.
+ * @throws UnknownHostException The host does not exist.
+ */
+public static ZoneTransferIn
+newIXFR(Name zone, long serial, boolean fallback, SocketAddress address,
+ TSIG key)
+{
+ return new ZoneTransferIn(zone, Type.IXFR, serial, fallback, address,
+ key);
+}
+
+/**
+ * Instantiates a ZoneTransferIn object to do an IXFR (incremental zone
+ * transfer).
+ * @param zone The zone to transfer.
+ * @param serial The existing serial number.
+ * @param fallback If true, fall back to AXFR if IXFR is not supported.
+ * @param host The host from which to transfer the zone.
+ * @param port The port to connect to on the server, or 0 for the default.
+ * @param key The TSIG key used to authenticate the transfer, or null.
+ * @return The ZoneTransferIn object.
+ * @throws UnknownHostException The host does not exist.
+ */
+public static ZoneTransferIn
+newIXFR(Name zone, long serial, boolean fallback, String host, int port,
+ TSIG key)
+throws UnknownHostException
+{
+ if (port == 0)
+ port = SimpleResolver.DEFAULT_PORT;
+ return newIXFR(zone, serial, fallback,
+ new InetSocketAddress(host, port), key);
+}
+
+/**
+ * Instantiates a ZoneTransferIn object to do an IXFR (incremental zone
+ * transfer).
+ * @param zone The zone to transfer.
+ * @param serial The existing serial number.
+ * @param fallback If true, fall back to AXFR if IXFR is not supported.
+ * @param host The host from which to transfer the zone.
+ * @param key The TSIG key used to authenticate the transfer, or null.
+ * @return The ZoneTransferIn object.
+ * @throws UnknownHostException The host does not exist.
+ */
+public static ZoneTransferIn
+newIXFR(Name zone, long serial, boolean fallback, String host, TSIG key)
+throws UnknownHostException
+{
+ return newIXFR(zone, serial, fallback, host, 0, key);
+}
+
+/**
+ * Gets the name of the zone being transferred.
+ */
+public Name
+getName() {
+ return zname;
+}
+
+/**
+ * Gets the type of zone transfer (either AXFR or IXFR).
+ */
+public int
+getType() {
+ return qtype;
+}
+
+/**
+ * Sets a timeout on this zone transfer. The default is 900 seconds (15
+ * minutes).
+ * @param secs The maximum amount of time that this zone transfer can take.
+ */
+public void
+setTimeout(int secs) {
+ if (secs < 0)
+ throw new IllegalArgumentException("timeout cannot be " +
+ "negative");
+ timeout = 1000L * secs;
+}
+
+/**
+ * Sets an alternate DNS class for this zone transfer.
+ * @param dclass The class to use instead of class IN.
+ */
+public void
+setDClass(int dclass) {
+ DClass.check(dclass);
+ this.dclass = dclass;
+}
+
+/**
+ * Sets the local address to bind to when sending messages.
+ * @param addr The local address to send messages from.
+ */
+public void
+setLocalAddress(SocketAddress addr) {
+ this.localAddress = addr;
+}
+
+private void
+openConnection() throws IOException {
+ long endTime = System.currentTimeMillis() + timeout;
+ client = new TCPClient(endTime);
+ if (localAddress != null)
+ client.bind(localAddress);
+ client.connect(address);
+}
+
+private void
+sendQuery() throws IOException {
+ Record question = Record.newRecord(zname, qtype, dclass);
+
+ Message query = new Message();
+ query.getHeader().setOpcode(Opcode.QUERY);
+ query.addRecord(question, Section.QUESTION);
+ if (qtype == Type.IXFR) {
+ Record soa = new SOARecord(zname, dclass, 0, Name.root,
+ Name.root, ixfr_serial,
+ 0, 0, 0, 0);
+ query.addRecord(soa, Section.AUTHORITY);
+ }
+ if (tsig != null) {
+ tsig.apply(query, null);
+ verifier = new TSIG.StreamVerifier(tsig, query.getTSIG());
+ }
+ byte [] out = query.toWire(Message.MAXLENGTH);
+ client.send(out);
+}
+
+private static long
+getSOASerial(Record rec) {
+ SOARecord soa = (SOARecord) rec;
+ return soa.getSerial();
+}
+
+private void
+logxfr(String s) {
+ if (Options.check("verbose"))
+ System.out.println(zname + ": " + s);
+}
+
+private void
+fail(String s) throws ZoneTransferException {
+ throw new ZoneTransferException(s);
+}
+
+private void
+fallback() throws ZoneTransferException {
+ if (!want_fallback)
+ fail("server doesn't support IXFR");
+
+ logxfr("falling back to AXFR");
+ qtype = Type.AXFR;
+ state = INITIALSOA;
+}
+
+private void
+parseRR(Record rec) throws ZoneTransferException {
+ int type = rec.getType();
+ Delta delta;
+
+ switch (state) {
+ case INITIALSOA:
+ if (type != Type.SOA)
+ fail("missing initial SOA");
+ initialsoa = rec;
+ // Remember the serial number in the initial SOA; we need it
+ // to recognize the end of an IXFR.
+ end_serial = getSOASerial(rec);
+ if (qtype == Type.IXFR &&
+ Serial.compare(end_serial, ixfr_serial) <= 0)
+ {
+ logxfr("up to date");
+ state = END;
+ break;
+ }
+ state = FIRSTDATA;
+ break;
+
+ case FIRSTDATA:
+ // If the transfer begins with 1 SOA, it's an AXFR.
+ // If it begins with 2 SOAs, it's an IXFR.
+ if (qtype == Type.IXFR && type == Type.SOA &&
+ getSOASerial(rec) == ixfr_serial)
+ {
+ rtype = Type.IXFR;
+ handler.startIXFR();
+ logxfr("got incremental response");
+ state = IXFR_DELSOA;
+ } else {
+ rtype = Type.AXFR;
+ handler.startAXFR();
+ handler.handleRecord(initialsoa);
+ logxfr("got nonincremental response");
+ state = AXFR;
+ }
+ parseRR(rec); // Restart...
+ return;
+
+ case IXFR_DELSOA:
+ handler.startIXFRDeletes(rec);
+ state = IXFR_DEL;
+ break;
+
+ case IXFR_DEL:
+ if (type == Type.SOA) {
+ current_serial = getSOASerial(rec);
+ state = IXFR_ADDSOA;
+ parseRR(rec); // Restart...
+ return;
+ }
+ handler.handleRecord(rec);
+ break;
+
+ case IXFR_ADDSOA:
+ handler.startIXFRAdds(rec);
+ state = IXFR_ADD;
+ break;
+
+ case IXFR_ADD:
+ if (type == Type.SOA) {
+ long soa_serial = getSOASerial(rec);
+ if (soa_serial == end_serial) {
+ state = END;
+ break;
+ } else if (soa_serial != current_serial) {
+ fail("IXFR out of sync: expected serial " +
+ current_serial + " , got " + soa_serial);
+ } else {
+ state = IXFR_DELSOA;
+ parseRR(rec); // Restart...
+ return;
+ }
+ }
+ handler.handleRecord(rec);
+ break;
+
+ case AXFR:
+ // Old BINDs sent cross class A records for non IN classes.
+ if (type == Type.A && rec.getDClass() != dclass)
+ break;
+ handler.handleRecord(rec);
+ if (type == Type.SOA) {
+ state = END;
+ }
+ break;
+
+ case END:
+ fail("extra data");
+ break;
+
+ default:
+ fail("invalid state");
+ break;
+ }
+}
+
+private void
+closeConnection() {
+ try {
+ if (client != null)
+ client.cleanup();
+ }
+ catch (IOException e) {
+ }
+}
+
+private Message
+parseMessage(byte [] b) throws WireParseException {
+ try {
+ return new Message(b);
+ }
+ catch (IOException e) {
+ if (e instanceof WireParseException)
+ throw (WireParseException) e;
+ throw new WireParseException("Error parsing message");
+ }
+}
+
+private void
+doxfr() throws IOException, ZoneTransferException {
+ sendQuery();
+ while (state != END) {
+ byte [] in = client.recv();
+ Message response = parseMessage(in);
+ if (response.getHeader().getRcode() == Rcode.NOERROR &&
+ verifier != null)
+ {
+ TSIGRecord tsigrec = response.getTSIG();
+
+ int error = verifier.verify(response, in);
+ if (error != Rcode.NOERROR)
+ fail("TSIG failure");
+ }
+
+ Record [] answers = response.getSectionArray(Section.ANSWER);
+
+ if (state == INITIALSOA) {
+ int rcode = response.getRcode();
+ if (rcode != Rcode.NOERROR) {
+ if (qtype == Type.IXFR &&
+ rcode == Rcode.NOTIMP)
+ {
+ fallback();
+ doxfr();
+ return;
+ }
+ fail(Rcode.string(rcode));
+ }
+
+ Record question = response.getQuestion();
+ if (question != null && question.getType() != qtype) {
+ fail("invalid question section");
+ }
+
+ if (answers.length == 0 && qtype == Type.IXFR) {
+ fallback();
+ doxfr();
+ return;
+ }
+ }
+
+ for (int i = 0; i < answers.length; i++) {
+ parseRR(answers[i]);
+ }
+
+ if (state == END && verifier != null &&
+ !response.isVerified())
+ fail("last message must be signed");
+ }
+}
+
+/**
+ * Does the zone transfer.
+ * @param handler The callback object that handles the zone transfer data.
+ * @throws IOException The zone transfer failed to due an IO problem.
+ * @throws ZoneTransferException The zone transfer failed to due a problem
+ * with the zone transfer itself.
+ */
+public void
+run(ZoneTransferHandler handler) throws IOException, ZoneTransferException {
+ this.handler = handler;
+ try {
+ openConnection();
+ doxfr();
+ }
+ finally {
+ closeConnection();
+ }
+}
+
+/**
+ * Does the zone transfer.
+ * @return A list, which is either an AXFR-style response (List of Records),
+ * and IXFR-style response (List of Deltas), or null, which indicates that
+ * an IXFR was performed and the zone is up to date.
+ * @throws IOException The zone transfer failed to due an IO problem.
+ * @throws ZoneTransferException The zone transfer failed to due a problem
+ * with the zone transfer itself.
+ */
+public List
+run() throws IOException, ZoneTransferException {
+ BasicHandler handler = new BasicHandler();
+ run(handler);
+ if (handler.axfr != null)
+ return handler.axfr;
+ return handler.ixfr;
+}
+
+private BasicHandler
+getBasicHandler() throws IllegalArgumentException {
+ if (handler instanceof BasicHandler)
+ return (BasicHandler) handler;
+ throw new IllegalArgumentException("ZoneTransferIn used callback " +
+ "interface");
+}
+
+/**
+ * Returns true if the response is an AXFR-style response (List of Records).
+ * This will be true if either an IXFR was performed, an IXFR was performed
+ * and the server provided a full zone transfer, or an IXFR failed and
+ * fallback to AXFR occurred.
+ */
+public boolean
+isAXFR() {
+ return (rtype == Type.AXFR);
+}
+
+/**
+ * Gets the AXFR-style response.
+ * @throws IllegalArgumentException The transfer used the callback interface,
+ * so the response was not stored.
+ */
+public List
+getAXFR() {
+ BasicHandler handler = getBasicHandler();
+ return handler.axfr;
+}
+
+/**
+ * Returns true if the response is an IXFR-style response (List of Deltas).
+ * This will be true only if an IXFR was performed and the server provided
+ * an incremental zone transfer.
+ */
+public boolean
+isIXFR() {
+ return (rtype == Type.IXFR);
+}
+
+/**
+ * Gets the IXFR-style response.
+ * @throws IllegalArgumentException The transfer used the callback interface,
+ * so the response was not stored.
+ */
+public List
+getIXFR() {
+ BasicHandler handler = getBasicHandler();
+ return handler.ixfr;
+}
+
+/**
+ * Returns true if the response indicates that the zone is up to date.
+ * This will be true only if an IXFR was performed.
+ * @throws IllegalArgumentException The transfer used the callback interface,
+ * so the response was not stored.
+ */
+public boolean
+isCurrent() {
+ BasicHandler handler = getBasicHandler();
+ return (handler.axfr == null && handler.ixfr == null);
+}
+
+}
diff --git a/src/org/xbill/DNS/spi/DNSJavaNameService.java b/src/org/xbill/DNS/spi/DNSJavaNameService.java
new file mode 100644
index 0000000..14d8adb
--- /dev/null
+++ b/src/org/xbill/DNS/spi/DNSJavaNameService.java
@@ -0,0 +1,176 @@
+// Copyright (c) 2005 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS.spi;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.StringTokenizer;
+
+import org.xbill.DNS.AAAARecord;
+import org.xbill.DNS.ARecord;
+import org.xbill.DNS.ExtendedResolver;
+import org.xbill.DNS.Lookup;
+import org.xbill.DNS.Name;
+import org.xbill.DNS.PTRRecord;
+import org.xbill.DNS.Record;
+import org.xbill.DNS.Resolver;
+import org.xbill.DNS.ReverseMap;
+import org.xbill.DNS.TextParseException;
+import org.xbill.DNS.Type;
+
+/**
+ * This class implements a Name Service Provider, which Java can use
+ * (starting with version 1.4), to perform DNS resolutions instead of using
+ * the standard calls.
+ * <p>
+ * This Name Service Provider uses dnsjava.
+ * <p>
+ * To use this provider, you must set the following system property:
+ * <b>sun.net.spi.nameservice.provider.1=dns,dnsjava</b>
+ *
+ * @author Brian Wellington
+ * @author Paul Cowan (pwc21@yahoo.com)
+ */
+
+public class DNSJavaNameService implements InvocationHandler {
+
+private static final String nsProperty = "sun.net.spi.nameservice.nameservers";
+private static final String domainProperty = "sun.net.spi.nameservice.domain";
+private static final String v6Property = "java.net.preferIPv6Addresses";
+
+private boolean preferV6 = false;
+
+/**
+ * Creates a DNSJavaNameService instance.
+ * <p>
+ * Uses the
+ * <b>sun.net.spi.nameservice.nameservers</b>,
+ * <b>sun.net.spi.nameservice.domain</b>, and
+ * <b>java.net.preferIPv6Addresses</b> properties for configuration.
+ */
+protected
+DNSJavaNameService() {
+ String nameServers = System.getProperty(nsProperty);
+ String domain = System.getProperty(domainProperty);
+ String v6 = System.getProperty(v6Property);
+
+ if (nameServers != null) {
+ StringTokenizer st = new StringTokenizer(nameServers, ",");
+ String [] servers = new String[st.countTokens()];
+ int n = 0;
+ while (st.hasMoreTokens())
+ servers[n++] = st.nextToken();
+ try {
+ Resolver res = new ExtendedResolver(servers);
+ Lookup.setDefaultResolver(res);
+ }
+ catch (UnknownHostException e) {
+ System.err.println("DNSJavaNameService: invalid " +
+ nsProperty);
+ }
+ }
+
+ if (domain != null) {
+ try {
+ Lookup.setDefaultSearchPath(new String[] {domain});
+ }
+ catch (TextParseException e) {
+ System.err.println("DNSJavaNameService: invalid " +
+ domainProperty);
+ }
+ }
+
+ if (v6 != null && v6.equalsIgnoreCase("true"))
+ preferV6 = true;
+}
+
+
+public Object
+invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ try {
+ if (method.getName().equals("getHostByAddr")) {
+ return this.getHostByAddr((byte[]) args[0]);
+ } else if (method.getName().equals("lookupAllHostAddr")) {
+ InetAddress[] addresses;
+ addresses = this.lookupAllHostAddr((String) args[0]);
+ Class returnType = method.getReturnType();
+ if (returnType.equals(InetAddress[].class)) {
+ // method for Java >= 1.6
+ return addresses;
+ } else if (returnType.equals(byte[][].class)) {
+ // method for Java <= 1.5
+ int naddrs = addresses.length;
+ byte [][] byteAddresses = new byte[naddrs][];
+ byte [] addr;
+ for (int i = 0; i < naddrs; i++) {
+ addr = addresses[i].getAddress();
+ byteAddresses[i] = addr;
+ }
+ return byteAddresses;
+ }
+ }
+ } catch (Throwable e) {
+ System.err.println("DNSJavaNameService: Unexpected error.");
+ e.printStackTrace();
+ throw e;
+ }
+ throw new IllegalArgumentException(
+ "Unknown function name or arguments.");
+}
+
+/**
+ * Performs a forward DNS lookup for the host name.
+ * @param host The host name to resolve.
+ * @return All the ip addresses found for the host name.
+ */
+public InetAddress []
+lookupAllHostAddr(String host) throws UnknownHostException {
+ Name name = null;
+
+ try {
+ name = new Name(host);
+ }
+ catch (TextParseException e) {
+ throw new UnknownHostException(host);
+ }
+
+ Record [] records = null;
+ if (preferV6)
+ records = new Lookup(name, Type.AAAA).run();
+ if (records == null)
+ records = new Lookup(name, Type.A).run();
+ if (records == null && !preferV6)
+ records = new Lookup(name, Type.AAAA).run();
+ if (records == null)
+ throw new UnknownHostException(host);
+
+ InetAddress[] array = new InetAddress[records.length];
+ for (int i = 0; i < records.length; i++) {
+ Record record = records[i];
+ if (records[i] instanceof ARecord) {
+ ARecord a = (ARecord) records[i];
+ array[i] = a.getAddress();
+ } else {
+ AAAARecord aaaa = (AAAARecord) records[i];
+ array[i] = aaaa.getAddress();
+ }
+ }
+ return array;
+}
+
+/**
+ * Performs a reverse DNS lookup.
+ * @param addr The ip address to lookup.
+ * @return The host name found for the ip address.
+ */
+public String
+getHostByAddr(byte [] addr) throws UnknownHostException {
+ Name name = ReverseMap.fromAddress(InetAddress.getByAddress(addr));
+ Record [] records = new Lookup(name, Type.PTR).run();
+ if (records == null)
+ throw new UnknownHostException();
+ return ((PTRRecord) records[0]).getTarget().toString();
+}
+}
diff --git a/src/org/xbill/DNS/spi/services/sun.net.spi.nameservice.NameServiceDescriptor b/src/org/xbill/DNS/spi/services/sun.net.spi.nameservice.NameServiceDescriptor
new file mode 100644
index 0000000..1ca895c
--- /dev/null
+++ b/src/org/xbill/DNS/spi/services/sun.net.spi.nameservice.NameServiceDescriptor
@@ -0,0 +1 @@
+org.xbill.DNS.spi.DNSJavaNameServiceDescriptor
diff --git a/src/org/xbill/DNS/tests/primary.java b/src/org/xbill/DNS/tests/primary.java
new file mode 100644
index 0000000..85455b9
--- /dev/null
+++ b/src/org/xbill/DNS/tests/primary.java
@@ -0,0 +1,59 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS.tests;
+
+import java.util.*;
+import org.xbill.DNS.*;
+
+public class primary {
+
+private static void
+usage() {
+ System.out.println("usage: primary [-t] [-a | -i] origin file");
+ System.exit(1);
+}
+
+public static void
+main(String [] args) throws Exception {
+ boolean time = false;
+ boolean axfr = false;
+ boolean iterator = false;
+ int arg = 0;
+
+ if (args.length < 2)
+ usage();
+
+ while (args.length - arg > 2) {
+ if (args[0].equals("-t"))
+ time = true;
+ else if (args[0].equals("-a"))
+ axfr = true;
+ else if (args[0].equals("-i"))
+ iterator = true;
+ arg++;
+ }
+
+ Name origin = Name.fromString(args[arg++], Name.root);
+ String file = args[arg++];
+
+ long start = System.currentTimeMillis();
+ Zone zone = new Zone(origin, file);
+ long end = System.currentTimeMillis();
+ if (axfr) {
+ Iterator it = zone.AXFR();
+ while (it.hasNext()) {
+ System.out.println(it.next());
+ }
+ } else if (iterator) {
+ Iterator it = zone.iterator();
+ while (it.hasNext()) {
+ System.out.println(it.next());
+ }
+ } else {
+ System.out.println(zone);
+ }
+ if (time)
+ System.out.println("; Load time: " + (end - start) + " ms");
+}
+
+}
diff --git a/src/org/xbill/DNS/tests/xfrin.java b/src/org/xbill/DNS/tests/xfrin.java
new file mode 100644
index 0000000..066c70e
--- /dev/null
+++ b/src/org/xbill/DNS/tests/xfrin.java
@@ -0,0 +1,109 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS.tests;
+
+import java.util.*;
+import org.xbill.DNS.*;
+
+public class xfrin {
+
+private static void
+usage(String s) {
+ System.out.println("Error: " + s);
+ System.out.println("usage: xfrin [-i serial] [-k keyname/secret] " +
+ "[-s server] [-p port] [-f] zone");
+ System.exit(1);
+}
+
+public static void
+main(String [] args) throws Exception {
+ ZoneTransferIn xfrin;
+ TSIG key = null;
+ int ixfr_serial = -1;
+ String server = null;
+ int port = SimpleResolver.DEFAULT_PORT;
+ boolean fallback = false;
+ Name zname;
+
+ int arg = 0;
+ while (arg < args.length) {
+ if (args[arg].equals("-i")) {
+ ixfr_serial = Integer.parseInt(args[++arg]);
+ if (ixfr_serial < 0)
+ usage("invalid serial number");
+ } else if (args[arg].equals("-k")) {
+ String s = args[++arg];
+ int index = s.indexOf('/');
+ if (index < 0)
+ usage("invalid key");
+ key = new TSIG(s.substring(0, index),
+ s.substring(index+1));
+ } else if (args[arg].equals("-s")) {
+ server = args[++arg];
+ } else if (args[arg].equals("-p")) {
+ port = Integer.parseInt(args[++arg]);
+ if (port < 0 || port > 0xFFFF)
+ usage("invalid port");
+ } else if (args[arg].equals("-f")) {
+ fallback = true;
+ } else if (args[arg].startsWith("-")) {
+ usage("invalid option");
+ } else {
+ break;
+ }
+ arg++;
+ }
+ if (arg >= args.length)
+ usage("no zone name specified");
+ zname = Name.fromString(args[arg]);
+
+ if (server == null) {
+ Lookup l = new Lookup(zname, Type.NS);
+ Record [] ns = l.run();
+ if (ns == null) {
+ System.out.println("failed to look up NS record: " +
+ l.getErrorString());
+ System.exit(1);
+ }
+ server = ns[0].rdataToString();
+ System.out.println("sending to server '" + server + "'");
+ }
+
+ if (ixfr_serial >= 0)
+ xfrin = ZoneTransferIn.newIXFR(zname, ixfr_serial, fallback,
+ server, port, key);
+ else
+ xfrin = ZoneTransferIn.newAXFR(zname, server, port, key);
+
+ List response = xfrin.run();
+ if (xfrin.isAXFR()) {
+ if (ixfr_serial >= 0)
+ System.out.println("AXFR-like IXFR response");
+ else
+ System.out.println("AXFR response");
+ Iterator it = response.iterator();
+ while (it.hasNext())
+ System.out.println(it.next());
+ } else if (xfrin.isIXFR()) {
+ System.out.println("IXFR response");
+ Iterator it = response.iterator();
+ while (it.hasNext()) {
+ ZoneTransferIn.Delta delta;
+ delta = (ZoneTransferIn.Delta) it.next();
+ System.out.println("delta from " + delta.start +
+ " to " + delta.end);
+ System.out.println("deletes");
+ Iterator it2 = delta.deletes.iterator();
+ while (it2.hasNext())
+ System.out.println(it2.next());
+ System.out.println("adds");
+ it2 = delta.adds.iterator();
+ while (it2.hasNext())
+ System.out.println(it2.next());
+ }
+ } else if (xfrin.isCurrent()) {
+ System.out.println("up to date");
+ }
+}
+
+}
diff --git a/src/org/xbill/DNS/utils/HMAC.java b/src/org/xbill/DNS/utils/HMAC.java
new file mode 100644
index 0000000..5eb5afd
--- /dev/null
+++ b/src/org/xbill/DNS/utils/HMAC.java
@@ -0,0 +1,182 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS.utils;
+
+import java.util.Arrays;
+import java.security.*;
+
+/**
+ * An implementation of the HMAC message authentication code.
+ *
+ * @author Brian Wellington
+ */
+
+public class HMAC {
+
+private MessageDigest digest;
+private int blockLength;
+
+private byte [] ipad, opad;
+
+private static final byte IPAD = 0x36;
+private static final byte OPAD = 0x5c;
+
+private void
+init(byte [] key) {
+ int i;
+
+ if (key.length > blockLength) {
+ key = digest.digest(key);
+ digest.reset();
+ }
+ ipad = new byte[blockLength];
+ opad = new byte[blockLength];
+ for (i = 0; i < key.length; i++) {
+ ipad[i] = (byte) (key[i] ^ IPAD);
+ opad[i] = (byte) (key[i] ^ OPAD);
+ }
+ for (; i < blockLength; i++) {
+ ipad[i] = IPAD;
+ opad[i] = OPAD;
+ }
+ digest.update(ipad);
+}
+
+/**
+ * Creates a new HMAC instance
+ * @param digest The message digest object.
+ * @param blockLength The block length of the message digest.
+ * @param key The secret key
+ */
+public
+HMAC(MessageDigest digest, int blockLength, byte [] key) {
+ digest.reset();
+ this.digest = digest;
+ this.blockLength = blockLength;
+ init(key);
+}
+
+/**
+ * Creates a new HMAC instance
+ * @param digestName The name of the message digest function.
+ * @param blockLength The block length of the message digest.
+ * @param key The secret key.
+ */
+public
+HMAC(String digestName, int blockLength, byte [] key) {
+ try {
+ digest = MessageDigest.getInstance(digestName);
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalArgumentException("unknown digest algorithm "
+ + digestName);
+ }
+ this.blockLength = blockLength;
+ init(key);
+}
+
+/**
+ * Creates a new HMAC instance
+ * @param digest The message digest object.
+ * @param key The secret key
+ * @deprecated won't work with digests using a padding length other than 64;
+ * use {@code HMAC(MessageDigest digest, int blockLength,
+ * byte [] key)} instead.
+ * @see HMAC#HMAC(MessageDigest digest, int blockLength, byte [] key)
+ */
+public
+HMAC(MessageDigest digest, byte [] key) {
+ this(digest, 64, key);
+}
+
+/**
+ * Creates a new HMAC instance
+ * @param digestName The name of the message digest function.
+ * @param key The secret key.
+ * @deprecated won't work with digests using a padding length other than 64;
+ * use {@code HMAC(String digestName, int blockLength, byte [] key)}
+ * instead
+ * @see HMAC#HMAC(String digestName, int blockLength, byte [] key)
+ */
+public
+HMAC(String digestName, byte [] key) {
+ this(digestName, 64, key);
+}
+
+/**
+ * Adds data to the current hash
+ * @param b The data
+ * @param offset The index at which to start adding to the hash
+ * @param length The number of bytes to hash
+ */
+public void
+update(byte [] b, int offset, int length) {
+ digest.update(b, offset, length);
+}
+
+/**
+ * Adds data to the current hash
+ * @param b The data
+ */
+public void
+update(byte [] b) {
+ digest.update(b);
+}
+
+/**
+ * Signs the data (computes the secure hash)
+ * @return An array with the signature
+ */
+public byte []
+sign() {
+ byte [] output = digest.digest();
+ digest.reset();
+ digest.update(opad);
+ return digest.digest(output);
+}
+
+/**
+ * Verifies the data (computes the secure hash and compares it to the input)
+ * @param signature The signature to compare against
+ * @return true if the signature matches, false otherwise
+ */
+public boolean
+verify(byte [] signature) {
+ return verify(signature, false);
+}
+
+/**
+ * Verifies the data (computes the secure hash and compares it to the input)
+ * @param signature The signature to compare against
+ * @param truncation_ok If true, the signature may be truncated; only the
+ * number of bytes in the provided signature are compared.
+ * @return true if the signature matches, false otherwise
+ */
+public boolean
+verify(byte [] signature, boolean truncation_ok) {
+ byte [] expected = sign();
+ if (truncation_ok && signature.length < expected.length) {
+ byte [] truncated = new byte[signature.length];
+ System.arraycopy(expected, 0, truncated, 0, truncated.length);
+ expected = truncated;
+ }
+ return Arrays.equals(signature, expected);
+}
+
+/**
+ * Resets the HMAC object for further use
+ */
+public void
+clear() {
+ digest.reset();
+ digest.update(ipad);
+}
+
+/**
+ * Returns the length of the digest.
+ */
+public int
+digestLength() {
+ return digest.getDigestLength();
+}
+
+}
diff --git a/src/org/xbill/DNS/utils/base16.java b/src/org/xbill/DNS/utils/base16.java
new file mode 100644
index 0000000..58024e6
--- /dev/null
+++ b/src/org/xbill/DNS/utils/base16.java
@@ -0,0 +1,73 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS.utils;
+
+import java.io.*;
+
+/**
+ * Routines for converting between Strings of hex-encoded data and arrays of
+ * binary data. This is not actually used by DNS.
+ *
+ * @author Brian Wellington
+ */
+
+public class base16 {
+
+private static final String Base16 = "0123456789ABCDEF";
+
+private
+base16() {}
+
+/**
+ * Convert binary data to a hex-encoded String
+ * @param b An array containing binary data
+ * @return A String containing the encoded data
+ */
+public static String
+toString(byte [] b) {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+
+ for (int i = 0; i < b.length; i++) {
+ short value = (short) (b[i] & 0xFF);
+ byte high = (byte) (value >> 4);
+ byte low = (byte) (value & 0xF);
+ os.write(Base16.charAt(high));
+ os.write(Base16.charAt(low));
+ }
+ return new String(os.toByteArray());
+}
+
+/**
+ * Convert a hex-encoded String to binary data
+ * @param str A String containing the encoded data
+ * @return An array containing the binary data, or null if the string is invalid
+ */
+public static byte []
+fromString(String str) {
+ ByteArrayOutputStream bs = new ByteArrayOutputStream();
+ byte [] raw = str.getBytes();
+ for (int i = 0; i < raw.length; i++) {
+ if (!Character.isWhitespace((char)raw[i]))
+ bs.write(raw[i]);
+ }
+ byte [] in = bs.toByteArray();
+ if (in.length % 2 != 0) {
+ return null;
+ }
+
+ bs.reset();
+ DataOutputStream ds = new DataOutputStream(bs);
+
+ for (int i = 0; i < in.length; i += 2) {
+ byte high = (byte) Base16.indexOf(Character.toUpperCase((char)in[i]));
+ byte low = (byte) Base16.indexOf(Character.toUpperCase((char)in[i+1]));
+ try {
+ ds.writeByte((high << 4) + low);
+ }
+ catch (IOException e) {
+ }
+ }
+ return bs.toByteArray();
+}
+
+}
diff --git a/src/org/xbill/DNS/utils/base32.java b/src/org/xbill/DNS/utils/base32.java
new file mode 100644
index 0000000..a2f26ea
--- /dev/null
+++ b/src/org/xbill/DNS/utils/base32.java
@@ -0,0 +1,213 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS.utils;
+
+import java.io.*;
+
+/**
+ * Routines for converting between Strings of base32-encoded data and arrays
+ * of binary data. This currently supports the base32 and base32hex alphabets
+ * specified in RFC 4648, sections 6 and 7.
+ *
+ * @author Brian Wellington
+ */
+
+public class base32 {
+
+public static class Alphabet {
+ private Alphabet() {}
+
+ public static final String BASE32 =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=";
+ public static final String BASE32HEX =
+ "0123456789ABCDEFGHIJKLMNOPQRSTUV=";
+};
+
+private String alphabet;
+private boolean padding, lowercase;
+
+/**
+ * Creates an object that can be used to do base32 conversions.
+ * @param alphabet Which alphabet should be used
+ * @param padding Whether padding should be used
+ * @param lowercase Whether lowercase characters should be used.
+ * default parameters (The standard base32 alphabet, no padding, uppercase)
+ */
+public
+base32(String alphabet, boolean padding, boolean lowercase) {
+ this.alphabet = alphabet;
+ this.padding = padding;
+ this.lowercase = lowercase;
+}
+
+static private int
+blockLenToPadding(int blocklen) {
+ switch (blocklen) {
+ case 1:
+ return 6;
+ case 2:
+ return 4;
+ case 3:
+ return 3;
+ case 4:
+ return 1;
+ case 5:
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+static private int
+paddingToBlockLen(int padlen) {
+ switch (padlen) {
+ case 6:
+ return 1;
+ case 4:
+ return 2;
+ case 3:
+ return 3;
+ case 1:
+ return 4;
+ case 0:
+ return 5;
+ default :
+ return -1;
+ }
+}
+
+/**
+ * Convert binary data to a base32-encoded String
+ *
+ * @param b An array containing binary data
+ * @return A String containing the encoded data
+ */
+public String
+toString(byte [] b) {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+
+ for (int i = 0; i < (b.length + 4) / 5; i++) {
+ short s[] = new short[5];
+ int t[] = new int[8];
+
+ int blocklen = 5;
+ for (int j = 0; j < 5; j++) {
+ if ((i * 5 + j) < b.length)
+ s[j] = (short) (b[i * 5 + j] & 0xFF);
+ else {
+ s[j] = 0;
+ blocklen--;
+ }
+ }
+ int padlen = blockLenToPadding(blocklen);
+
+ // convert the 5 byte block into 8 characters (values 0-31).
+
+ // upper 5 bits from first byte
+ t[0] = (byte) ((s[0] >> 3) & 0x1F);
+ // lower 3 bits from 1st byte, upper 2 bits from 2nd.
+ t[1] = (byte) (((s[0] & 0x07) << 2) | ((s[1] >> 6) & 0x03));
+ // bits 5-1 from 2nd.
+ t[2] = (byte) ((s[1] >> 1) & 0x1F);
+ // lower 1 bit from 2nd, upper 4 from 3rd
+ t[3] = (byte) (((s[1] & 0x01) << 4) | ((s[2] >> 4) & 0x0F));
+ // lower 4 from 3rd, upper 1 from 4th.
+ t[4] = (byte) (((s[2] & 0x0F) << 1) | ((s[3] >> 7) & 0x01));
+ // bits 6-2 from 4th
+ t[5] = (byte) ((s[3] >> 2) & 0x1F);
+ // lower 2 from 4th, upper 3 from 5th;
+ t[6] = (byte) (((s[3] & 0x03) << 3) | ((s[4] >> 5) & 0x07));
+ // lower 5 from 5th;
+ t[7] = (byte) (s[4] & 0x1F);
+
+ // write out the actual characters.
+ for (int j = 0; j < t.length - padlen; j++) {
+ char c = alphabet.charAt(t[j]);
+ if (lowercase)
+ c = Character.toLowerCase(c);
+ os.write(c);
+ }
+
+ // write out the padding (if any)
+ if (padding) {
+ for (int j = t.length - padlen; j < t.length; j++)
+ os.write('=');
+ }
+ }
+
+ return new String(os.toByteArray());
+}
+
+/**
+ * Convert a base32-encoded String to binary data
+ *
+ * @param str A String containing the encoded data
+ * @return An array containing the binary data, or null if the string is invalid
+ */
+public byte[]
+fromString(String str) {
+ ByteArrayOutputStream bs = new ByteArrayOutputStream();
+ byte [] raw = str.getBytes();
+ for (int i = 0; i < raw.length; i++)
+ {
+ char c = (char) raw[i];
+ if (!Character.isWhitespace(c)) {
+ c = Character.toUpperCase(c);
+ bs.write((byte) c);
+ }
+ }
+
+ if (padding) {
+ if (bs.size() % 8 != 0)
+ return null;
+ } else {
+ while (bs.size() % 8 != 0)
+ bs.write('=');
+ }
+
+ byte [] in = bs.toByteArray();
+
+ bs.reset();
+ DataOutputStream ds = new DataOutputStream(bs);
+
+ for (int i = 0; i < in.length / 8; i++) {
+ short[] s = new short[8];
+ int[] t = new int[5];
+
+ int padlen = 8;
+ for (int j = 0; j < 8; j++) {
+ char c = (char) in[i * 8 + j];
+ if (c == '=')
+ break;
+ s[j] = (short) alphabet.indexOf(in[i * 8 + j]);
+ if (s[j] < 0)
+ return null;
+ padlen--;
+ }
+ int blocklen = paddingToBlockLen(padlen);
+ if (blocklen < 0)
+ return null;
+
+ // all 5 bits of 1st, high 3 (of 5) of 2nd
+ t[0] = (s[0] << 3) | s[1] >> 2;
+ // lower 2 of 2nd, all 5 of 3rd, high 1 of 4th
+ t[1] = ((s[1] & 0x03) << 6) | (s[2] << 1) | (s[3] >> 4);
+ // lower 4 of 4th, high 4 of 5th
+ t[2] = ((s[3] & 0x0F) << 4) | ((s[4] >> 1) & 0x0F);
+ // lower 1 of 5th, all 5 of 6th, high 2 of 7th
+ t[3] = (s[4] << 7) | (s[5] << 2) | (s[6] >> 3);
+ // lower 3 of 7th, all of 8th
+ t[4] = ((s[6] & 0x07) << 5) | s[7];
+
+ try {
+ for (int j = 0; j < blocklen; j++)
+ ds.writeByte((byte) (t[j] & 0xFF));
+ }
+ catch (IOException e) {
+ }
+ }
+
+ return bs.toByteArray();
+}
+
+}
diff --git a/src/org/xbill/DNS/utils/base64.java b/src/org/xbill/DNS/utils/base64.java
new file mode 100644
index 0000000..54567cf
--- /dev/null
+++ b/src/org/xbill/DNS/utils/base64.java
@@ -0,0 +1,145 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS.utils;
+
+import java.io.*;
+
+/**
+ * Routines for converting between Strings of base64-encoded data and arrays of
+ * binary data.
+ *
+ * @author Brian Wellington
+ */
+
+public class base64 {
+
+private static final String Base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+
+private
+base64() {}
+
+/**
+ * Convert binary data to a base64-encoded String
+ * @param b An array containing binary data
+ * @return A String containing the encoded data
+ */
+public static String
+toString(byte [] b) {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+
+ for (int i = 0; i < (b.length + 2) / 3; i++) {
+ short [] s = new short[3];
+ short [] t = new short[4];
+ for (int j = 0; j < 3; j++) {
+ if ((i * 3 + j) < b.length)
+ s[j] = (short) (b[i*3+j] & 0xFF);
+ else
+ s[j] = -1;
+ }
+
+ t[0] = (short) (s[0] >> 2);
+ if (s[1] == -1)
+ t[1] = (short) (((s[0] & 0x3) << 4));
+ else
+ t[1] = (short) (((s[0] & 0x3) << 4) + (s[1] >> 4));
+ if (s[1] == -1)
+ t[2] = t[3] = 64;
+ else if (s[2] == -1) {
+ t[2] = (short) (((s[1] & 0xF) << 2));
+ t[3] = 64;
+ }
+ else {
+ t[2] = (short) (((s[1] & 0xF) << 2) + (s[2] >> 6));
+ t[3] = (short) (s[2] & 0x3F);
+ }
+ for (int j = 0; j < 4; j++)
+ os.write(Base64.charAt(t[j]));
+ }
+ return new String(os.toByteArray());
+}
+
+/**
+ * Formats data into a nicely formatted base64 encoded String
+ * @param b An array containing binary data
+ * @param lineLength The number of characters per line
+ * @param prefix A string prefixing the characters on each line
+ * @param addClose Whether to add a close parenthesis or not
+ * @return A String representing the formatted output
+ */
+public static String
+formatString(byte [] b, int lineLength, String prefix, boolean addClose) {
+ String s = toString(b);
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < s.length(); i += lineLength) {
+ sb.append (prefix);
+ if (i + lineLength >= s.length()) {
+ sb.append(s.substring(i));
+ if (addClose)
+ sb.append(" )");
+ }
+ else {
+ sb.append(s.substring(i, i + lineLength));
+ sb.append("\n");
+ }
+ }
+ return sb.toString();
+}
+
+
+/**
+ * Convert a base64-encoded String to binary data
+ * @param str A String containing the encoded data
+ * @return An array containing the binary data, or null if the string is invalid
+ */
+public static byte []
+fromString(String str) {
+ ByteArrayOutputStream bs = new ByteArrayOutputStream();
+ byte [] raw = str.getBytes();
+ for (int i = 0; i < raw.length; i++) {
+ if (!Character.isWhitespace((char)raw[i]))
+ bs.write(raw[i]);
+ }
+ byte [] in = bs.toByteArray();
+ if (in.length % 4 != 0) {
+ return null;
+ }
+
+ bs.reset();
+ DataOutputStream ds = new DataOutputStream(bs);
+
+ for (int i = 0; i < (in.length + 3) / 4; i++) {
+ short [] s = new short[4];
+ short [] t = new short[3];
+
+ for (int j = 0; j < 4; j++)
+ s[j] = (short) Base64.indexOf(in[i*4+j]);
+
+ t[0] = (short) ((s[0] << 2) + (s[1] >> 4));
+ if (s[2] == 64) {
+ t[1] = t[2] = (short) (-1);
+ if ((s[1] & 0xF) != 0)
+ return null;
+ }
+ else if (s[3] == 64) {
+ t[1] = (short) (((s[1] << 4) + (s[2] >> 2)) & 0xFF);
+ t[2] = (short) (-1);
+ if ((s[2] & 0x3) != 0)
+ return null;
+ }
+ else {
+ t[1] = (short) (((s[1] << 4) + (s[2] >> 2)) & 0xFF);
+ t[2] = (short) (((s[2] << 6) + s[3]) & 0xFF);
+ }
+
+ try {
+ for (int j = 0; j < 3; j++)
+ if (t[j] >= 0)
+ ds.writeByte(t[j]);
+ }
+ catch (IOException e) {
+ }
+ }
+ return bs.toByteArray();
+}
+
+}
diff --git a/src/org/xbill/DNS/utils/hexdump.java b/src/org/xbill/DNS/utils/hexdump.java
new file mode 100644
index 0000000..1a79a40
--- /dev/null
+++ b/src/org/xbill/DNS/utils/hexdump.java
@@ -0,0 +1,56 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS.utils;
+
+/**
+ * A routine to produce a nice looking hex dump
+ *
+ * @author Brian Wellington
+ */
+
+public class hexdump {
+
+private static final char [] hex = "0123456789ABCDEF".toCharArray();
+
+/**
+ * Dumps a byte array into hex format.
+ * @param description If not null, a description of the data.
+ * @param b The data to be printed.
+ * @param offset The start of the data in the array.
+ * @param length The length of the data in the array.
+ */
+public static String
+dump(String description, byte [] b, int offset, int length) {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append(length + "b");
+ if (description != null)
+ sb.append(" (" + description + ")");
+ sb.append(':');
+
+ int prefixlen = sb.toString().length();
+ prefixlen = (prefixlen + 8) & ~ 7;
+ sb.append('\t');
+
+ int perline = (80 - prefixlen) / 3;
+ for (int i = 0; i < length; i++) {
+ if (i != 0 && i % perline == 0) {
+ sb.append('\n');
+ for (int j = 0; j < prefixlen / 8 ; j++)
+ sb.append('\t');
+ }
+ int value = (int)(b[i + offset]) & 0xFF;
+ sb.append(hex[(value >> 4)]);
+ sb.append(hex[(value & 0xF)]);
+ sb.append(' ');
+ }
+ sb.append('\n');
+ return sb.toString();
+}
+
+public static String
+dump(String s, byte [] b) {
+ return dump(s, b, 0, b.length);
+}
+
+}
diff --git a/src/org/xbill/DNS/windows/DNSServer.properties b/src/org/xbill/DNS/windows/DNSServer.properties
new file mode 100644
index 0000000..25342f9
--- /dev/null
+++ b/src/org/xbill/DNS/windows/DNSServer.properties
@@ -0,0 +1,4 @@
+host_name=Host Name
+primary_dns_suffix=Primary Dns Suffix
+dns_suffix=DNS Suffix
+dns_servers=DNS Servers
diff --git a/src/org/xbill/DNS/windows/DNSServer_de.properties b/src/org/xbill/DNS/windows/DNSServer_de.properties
new file mode 100644
index 0000000..aa3f4a6
--- /dev/null
+++ b/src/org/xbill/DNS/windows/DNSServer_de.properties
@@ -0,0 +1,4 @@
+host_name=Hostname
+primary_dns_suffix=Prim\u00E4res DNS-Suffix
+dns_suffix=DNS-Suffixsuchliste
+dns_servers=DNS-Server
diff --git a/src/org/xbill/DNS/windows/DNSServer_fr.properties b/src/org/xbill/DNS/windows/DNSServer_fr.properties
new file mode 100644
index 0000000..7c87a25
--- /dev/null
+++ b/src/org/xbill/DNS/windows/DNSServer_fr.properties
@@ -0,0 +1,4 @@
+host_name=Nom de l'h\u00F4te
+primary_dns_suffix=Suffixe DNS principal
+dns_suffix=Suffixe DNS propre \u00E0 la connexion
+dns_servers=Serveurs DNS
diff --git a/src/org/xbill/DNS/windows/DNSServer_ja.properties b/src/org/xbill/DNS/windows/DNSServer_ja.properties
new file mode 100644
index 0000000..f873164
--- /dev/null
+++ b/src/org/xbill/DNS/windows/DNSServer_ja.properties
@@ -0,0 +1,4 @@
+host_name=\u30db\u30b9\u30c8\u540d
+primary_dns_suffix=\u30d7\u30e9\u30a4\u30de\u30ea DNS \u30b5\u30d5\u30a3\u30c3\u30af\u30b9
+dns_suffix=DNS \u30b5\u30d5\u30a3\u30c3\u30af\u30b9
+dns_servers=DNS \u30b5\u30fc\u30d0\u30fc
diff --git a/src/org/xbill/DNS/windows/DNSServer_pl.properties b/src/org/xbill/DNS/windows/DNSServer_pl.properties
new file mode 100644
index 0000000..eab5774
--- /dev/null
+++ b/src/org/xbill/DNS/windows/DNSServer_pl.properties
@@ -0,0 +1,4 @@
+host_name=Nazwa hosta
+primary_dns_suffix=Sufiks podstawowej domeny DNS
+dns_suffix=Sufiks DNS konkretnego po\u0142\u0105czenia
+dns_servers=Serwery DNS