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