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