001: /*
002: * Copyright 2000-2002 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package com.sun.jndi.dns;
027:
028: import javax.naming.InvalidNameException;
029:
030: /**
031: * The ResourceRecord class represents a DNS resource record.
032: * The string format is based on the master file representation in
033: * RFC 1035.
034: *
035: * @author Scott Seligman
036: * @version 1.22 07/05/05
037: */
038:
039: public class ResourceRecord {
040:
041: /*
042: * Resource record type codes
043: */
044: static final int TYPE_A = 1;
045: static final int TYPE_NS = 2;
046: static final int TYPE_CNAME = 5;
047: static final int TYPE_SOA = 6;
048: static final int TYPE_PTR = 12;
049: static final int TYPE_HINFO = 13;
050: static final int TYPE_MX = 15;
051: static final int TYPE_TXT = 16;
052: static final int TYPE_AAAA = 28;
053: static final int TYPE_SRV = 33;
054: static final int TYPE_NAPTR = 35;
055: static final int QTYPE_AXFR = 252; // zone transfer
056: static final int QTYPE_STAR = 255; // query type "*"
057:
058: /*
059: * Mapping from resource record type codes to type name strings.
060: */
061: static final String rrTypeNames[] = { null, "A", "NS", null, null,
062: "CNAME", "SOA", null, null, null, null, null, "PTR",
063: "HINFO", null, "MX", "TXT", null, null, null, null, null,
064: null, null, null, null, null, null, "AAAA", null, null,
065: null, null, "SRV", null, "NAPTR" };
066:
067: /*
068: * Resource record class codes
069: */
070: static final int CLASS_INTERNET = 1;
071: static final int CLASS_HESIOD = 2;
072: static final int QCLASS_STAR = 255; // query class "*"
073:
074: /*
075: * Mapping from resource record type codes to class name strings.
076: */
077: static final String rrClassNames[] = { null, "IN", null, null, "HS" };
078:
079: byte[] msg; // DNS message
080: int msgLen; // msg size (in octets)
081: boolean qSection; // true if this RR is part of question section
082: // and therefore has no ttl or rdata
083: int offset; // offset of RR w/in msg
084: int rrlen; // number of octets in encoded RR
085: DnsName name; // name field of RR, including root label
086: int rrtype; // type field of RR
087: String rrtypeName; // name of of rrtype
088: int rrclass; // class field of RR
089: String rrclassName; // name of rrclass
090: int ttl = 0; // ttl field of RR
091: int rdlen = 0; // number of octets of rdata
092: Object rdata = null; // rdata -- most are String, unknown are byte[]
093:
094: /*
095: * Constructs a new ResourceRecord. The encoded data of the DNS
096: * message is contained in msg; data for this RR begins at msg[offset].
097: * If qSection is true this RR is part of a question section. It's
098: * not a true resource record in that case, but is treated as if it
099: * were a shortened one (with no ttl or rdata). If decodeRdata is
100: * false, the rdata is not decoded (and getRdata() will return null)
101: * unless this is an SOA record.
102: *
103: * @throws InvalidNameException if a decoded domain name isn't valid.
104: * @throws ArrayIndexOutOfBoundsException given certain other corrupt data.
105: */
106: ResourceRecord(byte[] msg, int msgLen, int offset,
107: boolean qSection, boolean decodeRdata)
108: throws InvalidNameException {
109:
110: this .msg = msg;
111: this .msgLen = msgLen;
112: this .offset = offset;
113: this .qSection = qSection;
114: decode(decodeRdata);
115: }
116:
117: public String toString() {
118: String text = name + " " + rrclassName + " " + rrtypeName;
119: if (!qSection) {
120: text += " " + ttl + " "
121: + ((rdata != null) ? rdata : "[n/a]");
122: }
123: return text;
124: }
125:
126: /*
127: * Returns the name field of this RR, including the root label.
128: */
129: public DnsName getName() {
130: return name;
131: }
132:
133: /*
134: * Returns the number of octets in the encoded RR.
135: */
136: public int size() {
137: return rrlen;
138: }
139:
140: public int getType() {
141: return rrtype;
142: }
143:
144: public int getRrclass() {
145: return rrclass;
146: }
147:
148: public Object getRdata() {
149: return rdata;
150: }
151:
152: public static String getTypeName(int rrtype) {
153: return valueToName(rrtype, rrTypeNames);
154: }
155:
156: public static int getType(String typeName) {
157: return nameToValue(typeName, rrTypeNames);
158: }
159:
160: public static String getRrclassName(int rrclass) {
161: return valueToName(rrclass, rrClassNames);
162: }
163:
164: public static int getRrclass(String className) {
165: return nameToValue(className, rrClassNames);
166: }
167:
168: private static String valueToName(int val, String[] names) {
169: String name = null;
170: if ((val > 0) && (val < names.length)) {
171: name = names[val];
172: } else if (val == QTYPE_STAR) { // QTYPE_STAR == QCLASS_STAR
173: name = "*";
174: }
175: if (name == null) {
176: name = Integer.toString(val);
177: }
178: return name;
179: }
180:
181: private static int nameToValue(String name, String[] names) {
182: if (name.equals("")) {
183: return -1; // invalid name
184: } else if (name.equals("*")) {
185: return QTYPE_STAR; // QTYPE_STAR == QCLASS_STAR
186: }
187: if (Character.isDigit(name.charAt(0))) {
188: try {
189: return Integer.parseInt(name);
190: } catch (NumberFormatException e) {
191: }
192: }
193: for (int i = 1; i < names.length; i++) {
194: if ((names[i] != null) && name.equalsIgnoreCase(names[i])) {
195: return i;
196: }
197: }
198: return -1; // unknown name
199: }
200:
201: /*
202: * Compares two SOA record serial numbers using 32-bit serial number
203: * arithmetic as defined in RFC 1982. Serial numbers are unsigned
204: * 32-bit quantities. Returns a negative, zero, or positive value
205: * as the first serial number is less than, equal to, or greater
206: * than the second. If the serial numbers are not comparable the
207: * result is undefined. Note that the relation is not transitive.
208: */
209: public static int compareSerialNumbers(long s1, long s2) {
210: long diff = s2 - s1;
211: if (diff == 0) {
212: return 0;
213: } else if ((diff > 0 && diff <= 0x7FFFFFFF)
214: || (diff < 0 && -diff > 0x7FFFFFFF)) {
215: return -1;
216: } else {
217: return 1;
218: }
219: }
220:
221: /*
222: * Decodes the binary format of the RR.
223: * May throw ArrayIndexOutOfBoundsException given corrupt data.
224: */
225: private void decode(boolean decodeRdata)
226: throws InvalidNameException {
227: int pos = offset; // index of next unread octet
228:
229: name = new DnsName(); // NAME
230: pos = decodeName(pos, name);
231:
232: rrtype = getUShort(pos); // TYPE
233: rrtypeName = (rrtype < rrTypeNames.length) ? rrTypeNames[rrtype]
234: : null;
235: if (rrtypeName == null) {
236: rrtypeName = Integer.toString(rrtype);
237: }
238: pos += 2;
239:
240: rrclass = getUShort(pos); // CLASS
241: rrclassName = (rrclass < rrClassNames.length) ? rrClassNames[rrclass]
242: : null;
243: if (rrclassName == null) {
244: rrclassName = Integer.toString(rrclass);
245: }
246: pos += 2;
247:
248: if (!qSection) {
249: ttl = getInt(pos); // TTL
250: pos += 4;
251:
252: rdlen = getUShort(pos); // RDLENGTH
253: pos += 2;
254:
255: rdata = (decodeRdata || // RDATA
256: (rrtype == TYPE_SOA)) ? decodeRdata(pos) : null;
257: if (rdata instanceof DnsName) {
258: rdata = rdata.toString();
259: }
260: pos += rdlen;
261: }
262:
263: rrlen = pos - offset;
264:
265: msg = null; // free up for GC
266: }
267:
268: /*
269: * Returns the 1-byte unsigned value at msg[pos].
270: */
271: private int getUByte(int pos) {
272: return (msg[pos] & 0xFF);
273: }
274:
275: /*
276: * Returns the 2-byte unsigned value at msg[pos]. The high
277: * order byte comes first.
278: */
279: private int getUShort(int pos) {
280: return (((msg[pos] & 0xFF) << 8) | (msg[pos + 1] & 0xFF));
281: }
282:
283: /*
284: * Returns the 4-byte signed value at msg[pos]. The high
285: * order byte comes first.
286: */
287: private int getInt(int pos) {
288: return ((getUShort(pos) << 16) | getUShort(pos + 2));
289: }
290:
291: /*
292: * Returns the 4-byte unsigned value at msg[pos]. The high
293: * order byte comes first.
294: */
295: private long getUInt(int pos) {
296: return (getInt(pos) & 0xffffffffL);
297: }
298:
299: /*
300: * Returns the name encoded at msg[pos], including the root label.
301: */
302: private DnsName decodeName(int pos) throws InvalidNameException {
303: DnsName n = new DnsName();
304: decodeName(pos, n);
305: return n;
306: }
307:
308: /*
309: * Prepends to "n" the domain name encoded at msg[pos], including the root
310: * label. Returns the index into "msg" following the name.
311: */
312: private int decodeName(int pos, DnsName n)
313: throws InvalidNameException {
314: if (msg[pos] == 0) { // end of name
315: n.add(0, "");
316: return (pos + 1);
317: } else if ((msg[pos] & 0xC0) != 0) { // name compression
318: decodeName(getUShort(pos) & 0x3FFF, n);
319: return (pos + 2);
320: } else { // append a label
321: int len = msg[pos++];
322: try {
323: n.add(0, new String(msg, pos, len, "ISO-8859-1"));
324: } catch (java.io.UnsupportedEncodingException e) {
325: // assert false : "ISO-Latin-1 charset unavailable";
326: }
327: return decodeName(pos + len, n);
328: }
329: }
330:
331: /*
332: * Returns the rdata encoded at msg[pos]. The format is dependent
333: * on the rrtype and rrclass values, which have already been set.
334: * The length of the encoded data is rdlen, which has already been
335: * set.
336: * The rdata of records with unknown type/class combinations is
337: * returned in a newly-allocated byte array.
338: */
339: private Object decodeRdata(int pos) throws InvalidNameException {
340: if (rrclass == CLASS_INTERNET) {
341: switch (rrtype) {
342: case TYPE_A:
343: return decodeA(pos);
344: case TYPE_AAAA:
345: return decodeAAAA(pos);
346: case TYPE_CNAME:
347: case TYPE_NS:
348: case TYPE_PTR:
349: return decodeName(pos);
350: case TYPE_MX:
351: return decodeMx(pos);
352: case TYPE_SOA:
353: return decodeSoa(pos);
354: case TYPE_SRV:
355: return decodeSrv(pos);
356: case TYPE_NAPTR:
357: return decodeNaptr(pos);
358: case TYPE_TXT:
359: return decodeTxt(pos);
360: case TYPE_HINFO:
361: return decodeHinfo(pos);
362: }
363: }
364: // Unknown RR type/class
365: byte[] rd = new byte[rdlen];
366: System.arraycopy(msg, pos, rd, 0, rdlen);
367: return rd;
368: }
369:
370: /*
371: * Returns the rdata of an MX record that is encoded at msg[pos].
372: */
373: private String decodeMx(int pos) throws InvalidNameException {
374: int preference = getUShort(pos);
375: pos += 2;
376: DnsName name = decodeName(pos);
377: return (preference + " " + name);
378: }
379:
380: /*
381: * Returns the rdata of an SOA record that is encoded at msg[pos].
382: */
383: private String decodeSoa(int pos) throws InvalidNameException {
384: DnsName mname = new DnsName();
385: pos = decodeName(pos, mname);
386: DnsName rname = new DnsName();
387: pos = decodeName(pos, rname);
388:
389: long serial = getUInt(pos);
390: pos += 4;
391: long refresh = getUInt(pos);
392: pos += 4;
393: long retry = getUInt(pos);
394: pos += 4;
395: long expire = getUInt(pos);
396: pos += 4;
397: long minimum = getUInt(pos); // now used as negative TTL
398: pos += 4;
399:
400: return (mname + " " + rname + " " + serial + " " + refresh
401: + " " + retry + " " + expire + " " + minimum);
402: }
403:
404: /*
405: * Returns the rdata of an SRV record that is encoded at msg[pos].
406: * See RFC 2782.
407: */
408: private String decodeSrv(int pos) throws InvalidNameException {
409: int priority = getUShort(pos);
410: pos += 2;
411: int weight = getUShort(pos);
412: pos += 2;
413: int port = getUShort(pos);
414: pos += 2;
415: DnsName target = decodeName(pos);
416: return (priority + " " + weight + " " + port + " " + target);
417: }
418:
419: /*
420: * Returns the rdata of an NAPTR record that is encoded at msg[pos].
421: * See RFC 2915.
422: */
423: private String decodeNaptr(int pos) throws InvalidNameException {
424: int order = getUShort(pos);
425: pos += 2;
426: int preference = getUShort(pos);
427: pos += 2;
428: StringBuffer flags = new StringBuffer();
429: pos += decodeCharString(pos, flags);
430: StringBuffer services = new StringBuffer();
431: pos += decodeCharString(pos, services);
432: StringBuffer regexp = new StringBuffer(rdlen);
433: pos += decodeCharString(pos, regexp);
434: DnsName replacement = decodeName(pos);
435:
436: return (order + " " + preference + " " + flags + " " + services
437: + " " + regexp + " " + replacement);
438: }
439:
440: /*
441: * Returns the rdata of a TXT record that is encoded at msg[pos].
442: * The rdata consists of one or more <character-string>s.
443: */
444: private String decodeTxt(int pos) {
445: StringBuffer buf = new StringBuffer(rdlen);
446: int end = pos + rdlen;
447: while (pos < end) {
448: pos += decodeCharString(pos, buf);
449: if (pos < end) {
450: buf.append(' ');
451: }
452: }
453: return buf.toString();
454: }
455:
456: /*
457: * Returns the rdata of an HINFO record that is encoded at msg[pos].
458: * The rdata consists of two <character-string>s.
459: */
460: private String decodeHinfo(int pos) {
461: StringBuffer buf = new StringBuffer(rdlen);
462: pos += decodeCharString(pos, buf);
463: buf.append(' ');
464: pos += decodeCharString(pos, buf);
465: return buf.toString();
466: }
467:
468: /*
469: * Decodes the <character-string> at msg[pos] and adds it to buf.
470: * If the string contains one of the meta-characters ' ', '\\', or
471: * '"', then the result is quoted and any embedded '\\' or '"'
472: * chars are escaped with '\\'. Empty strings are also quoted.
473: * Returns the size of the encoded string, including the initial
474: * length octet.
475: */
476: private int decodeCharString(int pos, StringBuffer buf) {
477: int start = buf.length(); // starting index of this string
478: int len = getUByte(pos++); // encoded string length
479: boolean quoted = (len == 0); // quote string if empty
480: for (int i = 0; i < len; i++) {
481: int c = getUByte(pos++);
482: quoted |= (c == ' ');
483: if ((c == '\\') || (c == '"')) {
484: quoted = true;
485: buf.append('\\');
486: }
487: buf.append((char) c);
488: }
489: if (quoted) {
490: buf.insert(start, '"');
491: buf.append('"');
492: }
493: return (len + 1); // size includes initial octet
494: }
495:
496: /*
497: * Returns the rdata of an A record, in dotted-decimal format,
498: * that is encoded at msg[pos].
499: */
500: private String decodeA(int pos) {
501: return ((msg[pos] & 0xff) + "." + (msg[pos + 1] & 0xff) + "."
502: + (msg[pos + 2] & 0xff) + "." + (msg[pos + 3] & 0xff));
503: }
504:
505: /*
506: * Returns the rdata of an AAAA record, in colon-separated format,
507: * that is encoded at msg[pos]. For example: 4321:0:1:2:3:4:567:89ab.
508: * See RFCs 1886 and 2373.
509: */
510: private String decodeAAAA(int pos) {
511: int[] addr6 = new int[8]; // the unsigned 16-bit words of the address
512: for (int i = 0; i < 8; i++) {
513: addr6[i] = getUShort(pos);
514: pos += 2;
515: }
516:
517: // Find longest sequence of two or more zeros, to compress them.
518: int curBase = -1;
519: int curLen = 0;
520: int bestBase = -1;
521: int bestLen = 0;
522: for (int i = 0; i < 8; i++) {
523: if (addr6[i] == 0) {
524: if (curBase == -1) { // new sequence
525: curBase = i;
526: curLen = 1;
527: } else { // extend sequence
528: ++curLen;
529: if ((curLen >= 2) && (curLen > bestLen)) {
530: bestBase = curBase;
531: bestLen = curLen;
532: }
533: }
534: } else { // not in sequence
535: curBase = -1;
536: }
537: }
538:
539: // If addr begins with at least 6 zeros and is not :: or ::1,
540: // or with 5 zeros followed by 0xffff, use the text format for
541: // IPv4-compatible or IPv4-mapped addresses.
542: if (bestBase == 0) {
543: if ((bestLen == 6) || ((bestLen == 7) && (addr6[7] > 1))) {
544: return ("::" + decodeA(pos - 4));
545: } else if ((bestLen == 5) && (addr6[5] == 0xffff)) {
546: return ("::ffff:" + decodeA(pos - 4));
547: }
548: }
549:
550: // If bestBase != -1, compress zeros in [bestBase, bestBase+bestLen)
551: boolean compress = (bestBase != -1);
552:
553: StringBuffer buf = new StringBuffer(40);
554: if (bestBase == 0) {
555: buf.append(':');
556: }
557: for (int i = 0; i < 8; i++) {
558: if (!compress || (i < bestBase)
559: || (i >= bestBase + bestLen)) {
560: buf.append(Integer.toHexString(addr6[i]));
561: if (i < 7) {
562: buf.append(':');
563: }
564: } else if (compress && (i == bestBase)) { // first compressed zero
565: buf.append(':');
566: }
567: }
568:
569: return buf.toString();
570: }
571: }
|