1<?php 2/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ 3 4/** 5 * DNS Library for handling lookups and updates. 6 * 7 * PHP Version 5 8 * 9 * Copyright (c) 2010, Mike Pultz <mike@mikepultz.com>. 10 * All rights reserved. 11 * 12 * Redistribution and use in source and binary forms, with or without 13 * modification, are permitted provided that the following conditions 14 * are met: 15 * 16 * * Redistributions of source code must retain the above copyright 17 * notice, this list of conditions and the following disclaimer. 18 * 19 * * Redistributions in binary form must reproduce the above copyright 20 * notice, this list of conditions and the following disclaimer in 21 * the documentation and/or other materials provided with the 22 * distribution. 23 * 24 * * Neither the name of Mike Pultz nor the names of his contributors 25 * may be used to endorse or promote products derived from this 26 * software without specific prior written permission. 27 * 28 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 29 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 30 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 31 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 32 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 33 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 34 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 35 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 36 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRIC 37 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 38 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 39 * POSSIBILITY OF SUCH DAMAGE. 40 * 41 * @category Networking 42 * @package Net_DNS2 43 * @author Mike Pultz <mike@mikepultz.com> 44 * @copyright 2010 Mike Pultz <mike@mikepultz.com> 45 * @license http://www.opensource.org/licenses/bsd-license.php BSD License 46 * @version SVN: $Id$ 47 * @link http://pear.php.net/package/Net_DNS2 48 * @since File available since Release 0.6.0 49 * 50 */ 51 52 53/** 54 * This is the base class for DNS Resource Records 55 * 56 * Each resource record type (defined in RR/*.php) extends this class for 57 * base functionality. 58 * 59 * This class handles parsing and constructing the common parts of the DNS 60 * resource records, while the RR specific functionality is handled in each 61 * child class. 62 * 63 * DNS resource record format - RFC1035 section 4.1.3 64 * 65 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 66 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 67 * | | 68 * / / 69 * / NAME / 70 * | | 71 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 72 * | TYPE | 73 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 74 * | CLASS | 75 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 76 * | TTL | 77 * | | 78 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 79 * | RDLENGTH | 80 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--| 81 * / RDATA / 82 * / / 83 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 84 * 85 * @category Networking 86 * @package Net_DNS2 87 * @author Mike Pultz <mike@mikepultz.com> 88 * @license http://www.opensource.org/licenses/bsd-license.php BSD License 89 * @link http://pear.php.net/package/Net_DNS2 90 * 91 */ 92abstract class Net_DNS2_RR 93{ 94 /* 95 * The name of the resource record 96 */ 97 public $name; 98 99 /* 100 * The resource record type 101 */ 102 public $type; 103 104 /* 105 * The resouce record class 106 */ 107 public $class; 108 109 /* 110 * The time to live for this resource record 111 */ 112 public $ttl; 113 114 /* 115 * The length of the rdata field 116 */ 117 public $rdlength; 118 119 /* 120 * The resource record specific data as a packed binary string 121 */ 122 public $rdata; 123 124 /** 125 * abstract definition - method to return a RR as a string; not to 126 * be confused with the __toString() magic method. 127 * 128 * @return string 129 * @access protected 130 * 131 */ 132 abstract protected function rrToString(); 133 134 /** 135 * abstract definition - parses a RR from a standard DNS config line 136 * 137 * @param array $rdata a string split line of values for the rdata 138 * 139 * @return boolean 140 * @access protected 141 * 142 */ 143 abstract protected function rrFromString(array $rdata); 144 145 /** 146 * abstract definition - sets a Net_DNS2_RR from a Net_DNS2_Packet object 147 * 148 * @param Net_DNS2_Packet &$packet a Net_DNS2_Packet packet to parse the RR from 149 * 150 * @return boolean 151 * @access protected 152 * 153 */ 154 abstract protected function rrSet(Net_DNS2_Packet &$packet); 155 156 /** 157 * abstract definition - returns a binary packet DNS RR object 158 * 159 * @param Net_DNS2_Packet &$packet a Net_DNS2_Packet packet use for 160 * compressed names 161 * 162 * @return mixed either returns a binary packed string or 163 * null on failure 164 * @access protected 165 * 166 */ 167 abstract protected function rrGet(Net_DNS2_Packet &$packet); 168 169 /** 170 * Constructor - builds a new Net_DNS2_RR object 171 * 172 * @param Net_DNS2_Packet &$packet a Net_DNS2_Packet packet or null to create 173 * an empty object 174 * @param array $rr an array with RR parse values or null to 175 * create an empty object 176 * 177 * @throws Net_DNS2_Exception 178 * @access public 179 * 180 */ 181 public function __construct(Net_DNS2_Packet &$packet = null, array $rr = null) 182 { 183 if ( (!is_null($packet)) && (!is_null($rr)) ) { 184 185 if ($this->set($packet, $rr) == false) { 186 187 throw new Net_DNS2_Exception( 188 'failed to generate resource record', 189 Net_DNS2_Lookups::E_RR_INVALID 190 ); 191 } 192 } else { 193 194 $class = Net_DNS2_Lookups::$rr_types_class_to_id[get_class($this)]; 195 if (isset($class)) { 196 197 $this->type = Net_DNS2_Lookups::$rr_types_by_id[$class]; 198 } 199 200 $this->class = 'IN'; 201 $this->ttl = 86400; 202 } 203 } 204 205 /** 206 * magic __toString() method to return the Net_DNS2_RR object object as a string 207 * 208 * @return string 209 * @access public 210 * 211 */ 212 public function __toString() 213 { 214 return $this->name . '. ' . $this->ttl . ' ' . $this->class . 215 ' ' . $this->type . ' ' . $this->rrToString(); 216 } 217 218 /** 219 * return a formatted string; if a string has spaces in it, then return 220 * it with double quotes around it, otherwise, return it as it was passed in. 221 * 222 * @param string $string the string to format 223 * 224 * @return string 225 * @access protected 226 * 227 */ 228 protected function formatString($string) 229 { 230 return '"' . str_replace('"', '\"', trim($string, '"')) . '"'; 231 } 232 233 /** 234 * builds an array of strings from an array of chunks of text split by spaces 235 * 236 * @param array $chunks an array of chunks of text split by spaces 237 * 238 * @return array 239 * @access protected 240 * 241 */ 242 protected function buildString(array $chunks) 243 { 244 $data = array(); 245 $c = 0; 246 $in = false; 247 248 foreach ($chunks as $r) { 249 250 $r = trim($r); 251 if (strlen($r) == 0) { 252 continue; 253 } 254 255 if ( ($r[0] == '"') 256 && ($r[strlen($r) - 1] == '"') 257 && ($r[strlen($r) - 2] != '\\') 258 ) { 259 260 $data[$c] = $r; 261 ++$c; 262 $in = false; 263 264 } else if ($r[0] == '"') { 265 266 $data[$c] = $r; 267 $in = true; 268 269 } else if ( ($r[strlen($r) - 1] == '"') 270 && ($r[strlen($r) - 2] != '\\') 271 ) { 272 273 $data[$c] .= ' ' . $r; 274 ++$c; 275 $in = false; 276 277 } else { 278 279 if ($in == true) { 280 $data[$c] .= ' ' . $r; 281 } else { 282 $data[$c++] = $r; 283 } 284 } 285 } 286 287 foreach ($data as $index => $string) { 288 289 $data[$index] = str_replace('\"', '"', trim($string, '"')); 290 } 291 292 return $data; 293 } 294 295 /** 296 * builds a new Net_DNS2_RR object 297 * 298 * @param Net_DNS2_Packet &$packet a Net_DNS2_Packet packet or null to create 299 * an empty object 300 * @param array $rr an array with RR parse values or null to 301 * create an empty object 302 * 303 * @return boolean 304 * @throws Net_DNS2_Exception 305 * @access public 306 * 307 */ 308 public function set(Net_DNS2_Packet &$packet, array $rr) 309 { 310 $this->name = $rr['name']; 311 $this->type = Net_DNS2_Lookups::$rr_types_by_id[$rr['type']]; 312 313 // 314 // for RR OPT (41), the class value includes the requestors UDP payload size, 315 // and not a class value 316 // 317 if ($this->type == 'OPT') { 318 $this->class = $rr['class']; 319 } else { 320 $this->class = Net_DNS2_Lookups::$classes_by_id[$rr['class']]; 321 } 322 323 $this->ttl = $rr['ttl']; 324 $this->rdlength = $rr['rdlength']; 325 $this->rdata = substr($packet->rdata, $packet->offset, $rr['rdlength']); 326 327 return $this->rrSet($packet); 328 } 329 330 /** 331 * returns a binary packed DNS RR object 332 * 333 * @param Net_DNS2_Packet &$packet a Net_DNS2_Packet packet used for 334 * compressing names 335 * 336 * @return string 337 * @throws Net_DNS2_Exception 338 * @access public 339 * 340 */ 341 public function get(Net_DNS2_Packet &$packet) 342 { 343 $data = ''; 344 $rdata = ''; 345 346 // 347 // pack the name 348 // 349 $data = $packet->compress($this->name, $packet->offset); 350 351 // 352 // pack the main values 353 // 354 if ($this->type == 'OPT') { 355 356 // 357 // pre-build the TTL value 358 // 359 $this->preBuild(); 360 361 // 362 // the class value is different for OPT types 363 // 364 $data .= pack( 365 'nnN', 366 Net_DNS2_Lookups::$rr_types_by_name[$this->type], 367 $this->class, 368 $this->ttl 369 ); 370 } else { 371 372 $data .= pack( 373 'nnN', 374 Net_DNS2_Lookups::$rr_types_by_name[$this->type], 375 Net_DNS2_Lookups::$classes_by_name[$this->class], 376 $this->ttl 377 ); 378 } 379 380 // 381 // increase the offset, and allow for the rdlength 382 // 383 $packet->offset += 10; 384 385 // 386 // get the RR specific details 387 // 388 if ($this->rdlength != -1) { 389 390 $rdata = $this->rrGet($packet); 391 } 392 393 // 394 // add the RR 395 // 396 $data .= pack('n', strlen($rdata)) . $rdata; 397 398 return $data; 399 } 400 401 /** 402 * parses a binary packet, and returns the appropriate Net_DNS2_RR object, 403 * based on the RR type of the binary content. 404 * 405 * @param Net_DNS2_Packet &$packet a Net_DNS2_Packet packet used for 406 * decompressing names 407 * 408 * @return mixed returns a new Net_DNS2_RR_* object for 409 * the given RR 410 * @throws Net_DNS2_Exception 411 * @access public 412 * 413 */ 414 public static function parse(Net_DNS2_Packet &$packet) 415 { 416 $object = array(); 417 418 // 419 // expand the name 420 // 421 $object['name'] = $packet->expand($packet, $packet->offset); 422 if (is_null($object['name'])) { 423 424 throw new Net_DNS2_Exception( 425 'failed to parse resource record: failed to expand name.', 426 Net_DNS2_Lookups::E_PARSE_ERROR 427 ); 428 } 429 if ($packet->rdlength < ($packet->offset + 10)) { 430 431 throw new Net_DNS2_Exception( 432 'failed to parse resource record: packet too small.', 433 Net_DNS2_Lookups::E_PARSE_ERROR 434 ); 435 } 436 437 // 438 // unpack the RR details 439 // 440 $object['type'] = ord($packet->rdata[$packet->offset++]) << 8 | 441 ord($packet->rdata[$packet->offset++]); 442 $object['class'] = ord($packet->rdata[$packet->offset++]) << 8 | 443 ord($packet->rdata[$packet->offset++]); 444 445 $object['ttl'] = ord($packet->rdata[$packet->offset++]) << 24 | 446 ord($packet->rdata[$packet->offset++]) << 16 | 447 ord($packet->rdata[$packet->offset++]) << 8 | 448 ord($packet->rdata[$packet->offset++]); 449 450 $object['rdlength'] = ord($packet->rdata[$packet->offset++]) << 8 | 451 ord($packet->rdata[$packet->offset++]); 452 453 if ($packet->rdlength < ($packet->offset + $object['rdlength'])) { 454 return null; 455 } 456 457 // 458 // lookup the class to use 459 // 460 $o = null; 461 $class = Net_DNS2_Lookups::$rr_types_id_to_class[$object['type']]; 462 463 if (isset($class)) { 464 465 $o = new $class($packet, $object); 466 if ($o) { 467 468 $packet->offset += $object['rdlength']; 469 } 470 } else { 471 472 throw new Net_DNS2_Exception( 473 'un-implemented resource record type: ' . $object['type'], 474 Net_DNS2_Lookups::E_RR_INVALID 475 ); 476 } 477 478 return $o; 479 } 480 481 /** 482 * cleans up some RR data 483 * 484 * @param string $data the text string to clean 485 * 486 * @return string returns the cleaned string 487 * 488 * @access public 489 * 490 */ 491 public function cleanString($data) 492 { 493 return strtolower(rtrim($data, '.')); 494 } 495 496 /** 497 * parses a standard RR format lines, as defined by rfc1035 (kinda) 498 * 499 * In our implementation, the domain *must* be specified- format must be 500 * 501 * <name> [<ttl>] [<class>] <type> <rdata> 502 * or 503 * <name> [<class>] [<ttl>] <type> <rdata> 504 * 505 * name, title, class and type are parsed by this function, rdata is passed 506 * to the RR specific classes for parsing. 507 * 508 * @param string $line a standard DNS config line 509 * 510 * @return mixed returns a new Net_DNS2_RR_* object for the given RR 511 * @throws Net_DNS2_Exception 512 * @access public 513 * 514 */ 515 public static function fromString($line) 516 { 517 if (strlen($line) == 0) { 518 throw new Net_DNS2_Exception( 519 'empty config line provided.', 520 Net_DNS2_Lookups::E_PARSE_ERROR 521 ); 522 } 523 524 $name = ''; 525 $type = ''; 526 $class = 'IN'; 527 $ttl = 86400; 528 529 // 530 // split the line by spaces 531 // 532 $values = preg_split('/[\s]+/', $line); 533 if (count($values) < 3) { 534 535 throw new Net_DNS2_Exception( 536 'failed to parse config: minimum of name, type and rdata required.', 537 Net_DNS2_Lookups::E_PARSE_ERROR 538 ); 539 } 540 541 // 542 // assume the first value is the name 543 // 544 $name = trim(strtolower(array_shift($values)), '.'); 545 546 // 547 // The next value is either a TTL, Class or Type 548 // 549 foreach ($values as $value) { 550 551 switch(true) { 552 case is_numeric($value): 553 554 $ttl = array_shift($values); 555 break; 556 557 // 558 // this is here because of a bug in is_numeric() in certain versions of 559 // PHP on windows. 560 // 561 case ($value === 0): 562 563 $ttl = array_shift($values); 564 break; 565 566 case isset(Net_DNS2_Lookups::$classes_by_name[strtoupper($value)]): 567 568 $class = strtoupper(array_shift($values)); 569 break; 570 571 case isset(Net_DNS2_Lookups::$rr_types_by_name[strtoupper($value)]): 572 573 $type = strtoupper(array_shift($values)); 574 break 2; 575 break; 576 577 default: 578 579 throw new Net_DNS2_Exception( 580 'invalid config line provided: unknown file: ' . $value, 581 Net_DNS2_Lookups::E_PARSE_ERROR 582 ); 583 } 584 } 585 586 // 587 // lookup the class to use 588 // 589 $o = null; 590 $class_name = Net_DNS2_Lookups::$rr_types_id_to_class[ 591 Net_DNS2_Lookups::$rr_types_by_name[$type] 592 ]; 593 594 if (isset($class_name)) { 595 596 $o = new $class_name; 597 if (!is_null($o)) { 598 599 // 600 // set the parsed values 601 // 602 $o->name = $name; 603 $o->class = $class; 604 $o->ttl = $ttl; 605 606 // 607 // parse the rdata 608 // 609 if ($o->rrFromString($values) === false) { 610 611 throw new Net_DNS2_Exception( 612 'failed to parse rdata for config: ' . $line, 613 Net_DNS2_Lookups::E_PARSE_ERROR 614 ); 615 } 616 617 } else { 618 619 throw new Net_DNS2_Exception( 620 'failed to create new RR record for type: ' . $type, 621 Net_DNS2_Lookups::E_RR_INVALID 622 ); 623 } 624 625 } else { 626 627 throw new Net_DNS2_Exception( 628 'un-implemented resource record type: '. $type, 629 Net_DNS2_Lookups::E_RR_INVALID 630 ); 631 } 632 633 return $o; 634 } 635} 636 637/* 638 * Local variables: 639 * tab-width: 4 640 * c-basic-offset: 4 641 * c-hanging-comment-ender-p: nil 642 * End: 643 */ 644?> 645