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