/* * Copyright (c) 2000, 2002, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.jndi.dns; import javax.naming.InvalidNameException; /** * The ResourceRecord class represents a DNS resource record. * The string format is based on the master file representation in * RFC 1035. * * @author Scott Seligman */ public class ResourceRecord { /* * Resource record type codes */ static final int TYPE_A = 1; static final int TYPE_NS = 2; static final int TYPE_CNAME = 5; static final int TYPE_SOA = 6; static final int TYPE_PTR = 12; static final int TYPE_HINFO = 13; static final int TYPE_MX = 15; static final int TYPE_TXT = 16; static final int TYPE_AAAA = 28; static final int TYPE_SRV = 33; static final int TYPE_NAPTR = 35; static final int QTYPE_AXFR = 252; // zone transfer static final int QTYPE_STAR = 255; // query type "*" /* * Mapping from resource record type codes to type name strings. */ static final String rrTypeNames[] = { null, "A", "NS", null, null, "CNAME", "SOA", null, null, null, null, null, "PTR", "HINFO", null, "MX", "TXT", null, null, null, null, null, null, null, null, null, null, null, "AAAA", null, null, null, null, "SRV", null, "NAPTR" }; /* * Resource record class codes */ static final int CLASS_INTERNET = 1; static final int CLASS_HESIOD = 2; static final int QCLASS_STAR = 255; // query class "*" /* * Mapping from resource record type codes to class name strings. */ static final String rrClassNames[] = { null, "IN", null, null, "HS" }; byte[] msg; // DNS message int msgLen; // msg size (in octets) boolean qSection; // true if this RR is part of question section // and therefore has no ttl or rdata int offset; // offset of RR w/in msg int rrlen; // number of octets in encoded RR DnsName name; // name field of RR, including root label int rrtype; // type field of RR String rrtypeName; // name of of rrtype int rrclass; // class field of RR String rrclassName; // name of rrclass int ttl = 0; // ttl field of RR int rdlen = 0; // number of octets of rdata Object rdata = null; // rdata -- most are String, unknown are byte[] /* * Constructs a new ResourceRecord. The encoded data of the DNS * message is contained in msg; data for this RR begins at msg[offset]. * If qSection is true this RR is part of a question section. It's * not a true resource record in that case, but is treated as if it * were a shortened one (with no ttl or rdata). If decodeRdata is * false, the rdata is not decoded (and getRdata() will return null) * unless this is an SOA record. * * @throws InvalidNameException if a decoded domain name isn't valid. * @throws ArrayIndexOutOfBoundsException given certain other corrupt data. */ ResourceRecord(byte[] msg, int msgLen, int offset, boolean qSection, boolean decodeRdata) throws InvalidNameException { this.msg = msg; this.msgLen = msgLen; this.offset = offset; this.qSection = qSection; decode(decodeRdata); } public String toString() { String text = name + " " + rrclassName + " " + rrtypeName; if (!qSection) { text += " " + ttl + " " + ((rdata != null) ? rdata : "[n/a]"); } return text; } /* * Returns the name field of this RR, including the root label. */ public DnsName getName() { return name; } /* * Returns the number of octets in the encoded RR. */ public int size() { return rrlen; } public int getType() { return rrtype; } public int getRrclass() { return rrclass; } public Object getRdata() { return rdata; } public static String getTypeName(int rrtype) { return valueToName(rrtype, rrTypeNames); } public static int getType(String typeName) { return nameToValue(typeName, rrTypeNames); } public static String getRrclassName(int rrclass) { return valueToName(rrclass, rrClassNames); } public static int getRrclass(String className) { return nameToValue(className, rrClassNames); } private static String valueToName(int val, String[] names) { String name = null; if ((val > 0) && (val < names.length)) { name = names[val]; } else if (val == QTYPE_STAR) { // QTYPE_STAR == QCLASS_STAR name = "*"; } if (name == null) { name = Integer.toString(val); } return name; } private static int nameToValue(String name, String[] names) { if (name.equals("")) { return -1; // invalid name } else if (name.equals("*")) { return QTYPE_STAR; // QTYPE_STAR == QCLASS_STAR } if (Character.isDigit(name.charAt(0))) { try { return Integer.parseInt(name); } catch (NumberFormatException e) { } } for (int i = 1; i < names.length; i++) { if ((names[i] != null) && name.equalsIgnoreCase(names[i])) { return i; } } return -1; // unknown name } /* * Compares two SOA record serial numbers using 32-bit serial number * arithmetic as defined in RFC 1982. Serial numbers are unsigned * 32-bit quantities. Returns a negative, zero, or positive value * as the first serial number is less than, equal to, or greater * than the second. If the serial numbers are not comparable the * result is undefined. Note that the relation is not transitive. */ public static int compareSerialNumbers(long s1, long s2) { long diff = s2 - s1; if (diff == 0) { return 0; } else if ((diff > 0 && diff <= 0x7FFFFFFF) || (diff < 0 && -diff > 0x7FFFFFFF)) { return -1; } else { return 1; } } /* * Decodes the binary format of the RR. * May throw ArrayIndexOutOfBoundsException given corrupt data. */ private void decode(boolean decodeRdata) throws InvalidNameException { int pos = offset; // index of next unread octet name = new DnsName(); // NAME pos = decodeName(pos, name); rrtype = getUShort(pos); // TYPE rrtypeName = (rrtype < rrTypeNames.length) ? rrTypeNames[rrtype] : null; if (rrtypeName == null) { rrtypeName = Integer.toString(rrtype); } pos += 2; rrclass = getUShort(pos); // CLASS rrclassName = (rrclass < rrClassNames.length) ? rrClassNames[rrclass] : null; if (rrclassName == null) { rrclassName = Integer.toString(rrclass); } pos += 2; if (!qSection) { ttl = getInt(pos); // TTL pos += 4; rdlen = getUShort(pos); // RDLENGTH pos += 2; rdata = (decodeRdata || // RDATA (rrtype == TYPE_SOA)) ? decodeRdata(pos) : null; if (rdata instanceof DnsName) { rdata = rdata.toString(); } pos += rdlen; } rrlen = pos - offset; msg = null; // free up for GC } /* * Returns the 1-byte unsigned value at msg[pos]. */ private int getUByte(int pos) { return (msg[pos] & 0xFF); } /* * Returns the 2-byte unsigned value at msg[pos]. The high * order byte comes first. */ private int getUShort(int pos) { return (((msg[pos] & 0xFF) << 8) | (msg[pos + 1] & 0xFF)); } /* * Returns the 4-byte signed value at msg[pos]. The high * order byte comes first. */ private int getInt(int pos) { return ((getUShort(pos) << 16) | getUShort(pos + 2)); } /* * Returns the 4-byte unsigned value at msg[pos]. The high * order byte comes first. */ private long getUInt(int pos) { return (getInt(pos) & 0xffffffffL); } /* * Returns the name encoded at msg[pos], including the root label. */ private DnsName decodeName(int pos) throws InvalidNameException { DnsName n = new DnsName(); decodeName(pos, n); return n; } /* * Prepends to "n" the domain name encoded at msg[pos], including the root * label. Returns the index into "msg" following the name. */ private int decodeName(int pos, DnsName n) throws InvalidNameException { if (msg[pos] == 0) { // end of name n.add(0, ""); return (pos + 1); } else if ((msg[pos] & 0xC0) != 0) { // name compression decodeName(getUShort(pos) & 0x3FFF, n); return (pos + 2); } else { // append a label int len = msg[pos++]; try { n.add(0, new String(msg, pos, len, "ISO-8859-1")); } catch (java.io.UnsupportedEncodingException e) { // assert false : "ISO-Latin-1 charset unavailable"; } return decodeName(pos + len, n); } } /* * Returns the rdata encoded at msg[pos]. The format is dependent * on the rrtype and rrclass values, which have already been set. * The length of the encoded data is rdlen, which has already been * set. * The rdata of records with unknown type/class combinations is * returned in a newly-allocated byte array. */ private Object decodeRdata(int pos) throws InvalidNameException { if (rrclass == CLASS_INTERNET) { switch (rrtype) { case TYPE_A: return decodeA(pos); case TYPE_AAAA: return decodeAAAA(pos); case TYPE_CNAME: case TYPE_NS: case TYPE_PTR: return decodeName(pos); case TYPE_MX: return decodeMx(pos); case TYPE_SOA: return decodeSoa(pos); case TYPE_SRV: return decodeSrv(pos); case TYPE_NAPTR: return decodeNaptr(pos); case TYPE_TXT: return decodeTxt(pos); case TYPE_HINFO: return decodeHinfo(pos); } } // Unknown RR type/class byte[] rd = new byte[rdlen]; System.arraycopy(msg, pos, rd, 0, rdlen); return rd; } /* * Returns the rdata of an MX record that is encoded at msg[pos]. */ private String decodeMx(int pos) throws InvalidNameException { int preference = getUShort(pos); pos += 2; DnsName name = decodeName(pos); return (preference + " " + name); } /* * Returns the rdata of an SOA record that is encoded at msg[pos]. */ private String decodeSoa(int pos) throws InvalidNameException { DnsName mname = new DnsName(); pos = decodeName(pos, mname); DnsName rname = new DnsName(); pos = decodeName(pos, rname); long serial = getUInt(pos); pos += 4; long refresh = getUInt(pos); pos += 4; long retry = getUInt(pos); pos += 4; long expire = getUInt(pos); pos += 4; long minimum = getUInt(pos); // now used as negative TTL pos += 4; return (mname + " " + rname + " " + serial + " " + refresh + " " + retry + " " + expire + " " + minimum); } /* * Returns the rdata of an SRV record that is encoded at msg[pos]. * See RFC 2782. */ private String decodeSrv(int pos) throws InvalidNameException { int priority = getUShort(pos); pos += 2; int weight = getUShort(pos); pos += 2; int port = getUShort(pos); pos += 2; DnsName target = decodeName(pos); return (priority + " " + weight + " " + port + " " + target); } /* * Returns the rdata of an NAPTR record that is encoded at msg[pos]. * See RFC 2915. */ private String decodeNaptr(int pos) throws InvalidNameException { int order = getUShort(pos); pos += 2; int preference = getUShort(pos); pos += 2; StringBuffer flags = new StringBuffer(); pos += decodeCharString(pos, flags); StringBuffer services = new StringBuffer(); pos += decodeCharString(pos, services); StringBuffer regexp = new StringBuffer(rdlen); pos += decodeCharString(pos, regexp); DnsName replacement = decodeName(pos); return (order + " " + preference + " " + flags + " " + services + " " + regexp + " " + replacement); } /* * Returns the rdata of a TXT record that is encoded at msg[pos]. * The rdata consists of one or more <character-string>s. */ private String decodeTxt(int pos) { StringBuffer buf = new StringBuffer(rdlen); int end = pos + rdlen; while (pos < end) { pos += decodeCharString(pos, buf); if (pos < end) { buf.append(' '); } } return buf.toString(); } /* * Returns the rdata of an HINFO record that is encoded at msg[pos]. * The rdata consists of two <character-string>s. */ private String decodeHinfo(int pos) { StringBuffer buf = new StringBuffer(rdlen); pos += decodeCharString(pos, buf); buf.append(' '); pos += decodeCharString(pos, buf); return buf.toString(); } /* * Decodes the <character-string> at msg[pos] and adds it to buf. * If the string contains one of the meta-characters ' ', '\\', or * '"', then the result is quoted and any embedded '\\' or '"' * chars are escaped with '\\'. Empty strings are also quoted. * Returns the size of the encoded string, including the initial * length octet. */ private int decodeCharString(int pos, StringBuffer buf) { int start = buf.length(); // starting index of this string int len = getUByte(pos++); // encoded string length boolean quoted = (len == 0); // quote string if empty for (int i = 0; i < len; i++) { int c = getUByte(pos++); quoted |= (c == ' '); if ((c == '\\') || (c == '"')) { quoted = true; buf.append('\\'); } buf.append((char) c); } if (quoted) { buf.insert(start, '"'); buf.append('"'); } return (len + 1); // size includes initial octet } /* * Returns the rdata of an A record, in dotted-decimal format, * that is encoded at msg[pos]. */ private String decodeA(int pos) { return ((msg[pos] & 0xff) + "." + (msg[pos + 1] & 0xff) + "." + (msg[pos + 2] & 0xff) + "." + (msg[pos + 3] & 0xff)); } /* * Returns the rdata of an AAAA record, in colon-separated format, * that is encoded at msg[pos]. For example: 4321:0:1:2:3:4:567:89ab. * See RFCs 1886 and 2373. */ private String decodeAAAA(int pos) { int[] addr6 = new int[8]; // the unsigned 16-bit words of the address for (int i = 0; i < 8; i++) { addr6[i] = getUShort(pos); pos += 2; } // Find longest sequence of two or more zeros, to compress them. int curBase = -1; int curLen = 0; int bestBase = -1; int bestLen = 0; for (int i = 0; i < 8; i++) { if (addr6[i] == 0) { if (curBase == -1) { // new sequence curBase = i; curLen = 1; } else { // extend sequence ++curLen; if ((curLen >= 2) && (curLen > bestLen)) { bestBase = curBase; bestLen = curLen; } } } else { // not in sequence curBase = -1; } } // If addr begins with at least 6 zeros and is not :: or ::1, // or with 5 zeros followed by 0xffff, use the text format for // IPv4-compatible or IPv4-mapped addresses. if (bestBase == 0) { if ((bestLen == 6) || ((bestLen == 7) && (addr6[7] > 1))) { return ("::" + decodeA(pos - 4)); } else if ((bestLen == 5) && (addr6[5] == 0xffff)) { return ("::ffff:" + decodeA(pos - 4)); } } // If bestBase != -1, compress zeros in [bestBase, bestBase+bestLen) boolean compress = (bestBase != -1); StringBuffer buf = new StringBuffer(40); if (bestBase == 0) { buf.append(':'); } for (int i = 0; i < 8; i++) { if (!compress || (i < bestBase) || (i >= bestBase + bestLen)) { buf.append(Integer.toHexString(addr6[i])); if (i < 7) { buf.append(':'); } } else if (compress && (i == bestBase)) { // first compressed zero buf.append(':'); } } return buf.toString(); } }