File: 1.13.3b/server/base/tests/dns.inc.php (View as Code)

1: 2: /* ------------------------------------------------------------- 3: This file is the PurplePixie PHP DNS Query Classes 4: 5: The software is (C) Copyright 2008 PurplePixie Systems 6: 7: This is free software: you can redistribute it and/or modify 8: it under the terms of the GNU General Public License as published by 9: the Free Software Foundation, either version 3 of the License, or 10: (at your option) any later version. 11: 12: The software is distributed in the hope that it will be useful, 13: but WITHOUT ANY WARRANTY; without even the implied warranty of 14: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15: GNU General Public License for more details. 16: 17: You should have received a copy of the GNU General Public License 18: along with this software. If not, see www.gnu.org/licenses 19: 20: For more information see www.purplepixie.org/phpdns 21: -------------------------------------------------------------- */ 22: 23: // 24: // Version 0.02 20th June 2008 25: // David Cutting -- dcutting (some_sort_of_at_sign) purplepixie dot org 26: // 27: 28: class DNSTypes 29: { 30: var $types_by_id; 31: var $types_by_name; 32: 33: function AddType($id,$name) 34: { 35: $this->types_by_id[$id]=$name; 36: $this->types_by_name[$name]=$id; 37: } 38: 39: function DNSTypes() 40: { 41: $this->types_by_id=array(); 42: $this->types_by_name=array(); 43: 44: $this->AddType(1,"A"); 45: $this->AddType(2,"NS"); 46: $this->AddType(5,"CNAME"); 47: $this->AddType(6,"SOA"); 48: $this->AddType(12,"PTR"); 49: $this->AddType(15,"MX"); 50: $this->AddType(16,"TXT"); 51: $this->AddType(255,"ANY"); 52: } 53: 54: function GetByName($name) 55: { 56: if (isset($this->types_by_name[$name])) return $this->types_by_name[$name]; 57: return 0; 58: } 59: 60: function GetById($id) 61: { 62: if (isset($this->types_by_id[$id])) return $this->types_by_id[$id]; 63: return ""; 64: } 65: } 66: 67: class DNSResult 68: { 69: var $type; 70: var $typeid; 71: var $class; 72: var $ttl; 73: var $data; 74: var $domain; 75: var $string; 76: var $extas=array(); 77: } 78: 79: class DNSAnswer 80: { 81: var $count=0; 82: var $results=array(); 83: 84: function AddResult($type,$typeid,$class,$ttl,$data,$domain="",$string="",$extras=array()) 85: { 86: $this->results[$this->count]=new DNSResult(); 87: $this->results[$this->count]->type=$type; 88: $this->results[$this->count]->typeid=$typeid; 89: $this->results[$this->count]->class=$class; 90: $this->results[$this->count]->ttl=$ttl; 91: $this->results[$this->count]->data=$data; 92: $this->results[$this->count]->domain=$domain; 93: $this->results[$this->count]->string=$string; 94: $this->results[$this->count]->extras=$extras; 95: $this->count++; 96: return ($this->count-1); 97: } 98: } 99: 100: 101: class DNSQuery 102: { 103: var $server=""; 104: var $port; 105: var $timeout; 106: var $udp; 107: var $debug; 108: var $binarydebug=false; 109: 110: var $types; 111: 112: var $rawbuffer=""; 113: var $rawheader=""; 114: var $rawresponse=""; 115: var $header; 116: var $responsecounter=0; 117: 118: var $lastnameservers; 119: var $lastadditional; 120: 121: var $error=false; 122: var $lasterror=""; 123: 124: function ReadResponse($count=1,$offset="") 125: { 126: if ($offset=="") // no offset so use and increment the ongoing counter 127: { 128: $return=substr($this->rawbuffer,$this->responsecounter,$count); 129: $this->responsecounter+=$count; 130: } 131: else 132: { 133: $return=substr($this->rawbuffer,$offset,$count); 134: } 135: return $return; 136: } 137: 138: function ReadDomainLabels($offset,&$counter=0) 139: { 140: $labels=array(); 141: $startoffset=$offset; 142: $return=false; 143: while (!$return) 144: { 145: $label_len=ord($this->ReadResponse(1,$offset++)); 146: if ($label_len<=0) $return=true; // end of data 147: else if ($label_len<64) // uncompressed data 148: { 149: $labels[]=$this->ReadResponse($label_len,$offset); 150: $offset+=$label_len; 151: } 152: else // label_len>=64 -- pointer 153: { 154: $nextitem=$this->ReadResponse(1,$offset++); 155: $pointer_offset = ( ($label_len & 0x3f) << 8 ) + ord($nextitem); 156: // Branch Back Upon Ourselves... 157: $this->Debug("Label Offset: ".$pointer_offset); 158: $pointer_labels=$this->ReadDomainLabels($pointer_offset); 159: foreach($pointer_labels as $ptr_label) 160: $labels[]=$ptr_label; 161: $return=true; 162: } 163: } 164: $counter=$offset-$startoffset; 165: return $labels; 166: } 167: 168: function ReadDomainLabel() 169: { 170: $count=0; 171: $labels=$this->ReadDomainLabels($this->responsecounter,$count); 172: $domain=implode(".",$labels); 173: $this->responsecounter+=$count; 174: $this->Debug("Label ".$domain." len ".$count); 175: return $domain; 176: } 177: 178: function Debug($text) 179: { 180: if ($this->debug) echo $text."\n"; 181: } 182: 183: function DebugBinary($data) 184: { 185: if ($this->binarydebug) 186: { 187: for ($a=0; $a188: { 189: echo $a; 190: echo "\t"; 191: printf("%d",$data[$a]); 192: echo "\t"; 193: $hex=bin2hex($data[$a]); 194: echo "0x".$hex; 195: echo "\t"; 196: $dec=hexdec($hex); 197: echo $dec; 198: echo "\t"; 199: if (($dec>30)&&($dec<150)) echo $data[$a]; 200: echo "\n"; 201: } 202: } 203: } 204: 205: function SetError($text) 206: { 207: $this->error=true; 208: $this->lasterror=$text; 209: $this->Debug("Error: ".$text); 210: } 211: 212: function ClearError() 213: { 214: $this->error=false; 215: $this->lasterror=""; 216: } 217: 218: function DNSQuery($server,$port=53,$timeout=60,$udp=true,$debug=false) 219: { 220: $this->server=$server; 221: $this->port=$port; 222: $this->timeout=$timeout; 223: $this->udp=$udp; 224: $this->debug=$debug; 225: 226: $this->types=new DNSTypes(); 227: $this->Debug("DNSQuery Class Initialised"); 228: } 229: 230: 231: function ReadRecord() 232: { 233: // First the pesky domain names - maybe not so pesky though I suppose 234: 235: $domain=$this->ReadDomainLabel(); 236: 237: 238: 239: $ans_header_bin=$this->ReadResponse(10); // 10 byte header 240: $ans_header=unpack("ntype/nclass/Nttl/nlength",$ans_header_bin); 241: $this->Debug("Record Type ".$ans_header['type']." Class ".$ans_header['class']." TTL ".$ans_header['ttl']." Length ".$ans_header['length']); 242: 243: $typeid=$this->types->GetById($ans_header['type']); 244: $extras=array(); 245: $data=""; 246: $string=""; 247: 248: switch($typeid) 249: { 250: case "A": 251: $ip=implode(".",unpack("Ca/Cb/Cc/Cd",$this->ReadResponse(4))); 252: $data=$ip; 253: $string=$domain." has address ".$ip; 254: break; 255: 256: case "NS": 257: $nameserver=$this->ReadDomainLabel(); 258: $data=$nameserver; 259: $string=$domain." nameserver ".$nameserver; 260: break; 261: 262: case "PTR": 263: $data=$this->ReadDomainLabel(); 264: $string=$domain." points to ".$data; 265: break; 266: 267: case "CNAME": 268: $data=$this->ReadDomainLabel(); 269: $string=$domain." alias of ".$data; 270: break; 271: 272: case "MX": 273: $prefs=$this->ReadResponse(2); 274: $prefs=unpack("nlevel",$prefs); 275: $extras['level']=$prefs['level']; 276: $data=$this->ReadDomainLabel(); 277: $string=$domain." mailserver ".$data." (pri=".$extras['level'].")"; 278: break; 279: 280: case "SOA": 281: // Label First 282: $data=$this->ReadDomainLabel(); 283: $responsible=$this->ReadDomainLabel(); 284: 285: $buffer=$this->ReadResponse(20); 286: $extras=unpack("nserial/nrefresh/Nretry/Nexpiry/Nminttl",$buffer); 287: $dot=strpos($responsible,"."); 288: $responsible[$dot]="@"; 289: $extras['responsible']=$responsible; 290: $string=$domain." SOA ".$data." Serial ".$extras['serial']; 291: break; 292: 293: case "TXT": 294: $data=$this->ReadResponse($ans_header['length']); 295: $string=$domain." TEXT ".$data; 296: break; 297: 298: default: // something we can't deal with 299: $stuff=$this->ReadResponse($ans_header['length']); 300: break; 301: 302: } 303: 304: //$dns_answer->AddResult($ans_header['type'],$typeid,$ans_header['class'],$ans_header['ttl'],$data,$domain,$string,$extras); 305: $return=array( 306: "header" => $ans_header, 307: "typeid" => $typeid, 308: "data" => $data, 309: "domain" => $domain, 310: "string" => $string, 311: "extras" => $extras ); 312: return $return; 313: } 314: 315: 316: 317: 318: 319: 320: function Query($question,$type="A") 321: { 322: $this->ClearError(); 323: $typeid=$this->types->GetByName($type); 324: if ($typeid===false) 325: { 326: $this->SetError("Invalid Query Type ".$type); 327: return false; 328: } 329: 330: if ($this->udp) $host="udp://".$this->server; 331: else $host=$this->server; 332: 333: if (!$socket=fsockopen($host,$this->port,$this->timeout)) 334: { 335: $this->SetError("Failed to Open Socket"); 336: return false; 337: } 338: 339: // Split Into Labels 340: if (preg_match("/[a-z|A-Z]/",$question)==0) // IP Address 341: { 342: $labeltmp=explode(".",$question); // reverse ARPA format 343: for ($i=count($labeltmp)-1; $i>=0; $i--) 344: $labels[]=$labeltmp[$i]; 345: $labels[]="IN-ADDR"; 346: $labels[]="ARPA"; 347: } 348: else // hostname 349: $labels=explode(".",$question); 350: 351: $question_binary=""; 352: for ($a=0; $a353: { 354: $size=strlen($labels[$a]); 355: $question_binary.=pack("C",$size); // size byte first 356: $question_binary.=$labels[$a]; // then the label 357: } 358: $question_binary.=pack("C",0); // end it off 359: 360: $this->Debug("Question: ".$question." (type=".$type."/".$typeid.")"); 361: 362: $id=rand(1,255)|(rand(0,255)<<8); // generate the ID 363: 364: // Set standard codes and flags 365: $flags=0x0100 & 0x0300; // recursion & queryspecmask 366: $opcode=0x0000; // opcode 367: 368: // Build the header 369: $header=""; 370: $header.=pack("n",$id); 371: $header.=pack("n",$opcode | $flags); 372: $header.=pack("nnnn",1,0,0,0); 373: $header.=$question_binary; 374: $header.=pack("n",$typeid); 375: $header.=pack("n",0x0001); // internet class 376: $headersize=strlen($header); 377: $headersizebin=pack("n",$headersize); 378: 379: $this->Debug("Header Length: ".$headersize." Bytes"); 380: $this->DebugBinary($header); 381: 382: if ( ($this->udp) && ($headersize>=512) ) 383: { 384: $this->SetError("Question too big for UDP (".$headersize." bytes)"); 385: fclose($socket); 386: return false; 387: } 388: 389: if ($this->udp) // UDP method 390: { 391: if (!fwrite($socket,$header,$headersize)) 392: { 393: $this->SetError("Failed to write question to socket"); 394: fclose($socket); 395: return false; 396: } 397: if (!$this->rawbuffer=fread($socket,4096)) // read until the end with UDP 398: { 399: $this->SetError("Failed to write read data buffer"); 400: fclose($socket); 401: return false; 402: } 403: } 404: else // TCP 405: { 406: if (!fwrite($socket,$headersizebin)) // write the socket 407: { 408: $this->SetError("Failed to write question length to TCP socket"); 409: fclose($socket); 410: return false; 411: } 412: if (!fwrite($socket,$header,$headersize)) 413: { 414: $this->SetError("Failed to write question to TCP socket"); 415: fclose($socket); 416: return false; 417: } 418: if (!$returnsize=fread($socket,2)) 419: { 420: $this->SetError("Failed to read size from TCP socket"); 421: fclose($socket); 422: return false; 423: } 424: $tmplen=unpack("nlength",$returnsize); 425: $datasize=$tmplen['length']; 426: $this->Debug("TCP Stream Length Limit ".$datasize); 427: if (!$this->rawbuffer=fread($socket,$datasize)) 428: { 429: $this->SetError("Failed to read data buffer"); 430: fclose($socket); 431: return false; 432: } 433: } 434: fclose($socket); 435: 436: $buffersize=strlen($this->rawbuffer); 437: $this->Debug("Read Buffer Size ".$buffersize); 438: 439: if ($buffersize<12) 440: { 441: $this->SetError("Return Buffer too Small"); 442: return false; 443: } 444: 445: $this->rawheader=substr($this->rawbuffer,0,12); // first 12 bytes is the header 446: $this->rawresponse=substr($this->rawbuffer,12); // after that the response 447: 448: $this->responsecounter=12; // start parsing response counter from 12 - no longer using response so can do pointers 449: 450: $this->DebugBinary($this->rawbuffer); 451: 452: $this->header=unpack("nid/nspec/nqdcount/nancount/nnscount/narcount",$this->rawheader); 453: 454: $answers=$this->header['ancount']; 455: $this->Debug("Query Returned ".$answers." Answers"); 456: 457: $dns_answer=new DNSAnswer(); 458: 459: // Deal with the header question data 460: if ($this->header['qdcount']>0) 461: { 462: $this->Debug("Found ".$this->header['qdcount']." Questions"); 463: for ($a=0; $a<$this->header['qdcount']; $a++) 464: { 465: $c=1; 466: while ($c!=0) 467: { 468: $c=hexdec(bin2hex($this->ReadResponse(1))); 469: } 470: $extradata=$this->ReadResponse(4); 471: } 472: } 473: 474: // New Functional Method 475: for ($a=0; $a<$this->header['ancount']; $a++) 476: { 477: $record=$this->ReadRecord(); 478: $dns_answer->AddResult($record['header']['type'],$record['typeid'],$record['header']['class'],$record['header']['ttl'], 479: $record['data'],$record['domain'],$record['string'],$record['extras']); 480: } 481: 482: $this->lastnameservers=new DNSAnswer(); 483: for ($a=0; $a<$this->header['nscount']; $a++) 484: { 485: $record=$this->ReadRecord(); 486: $this->lastnameservers->AddResult($record['header']['type'],$record['typeid'],$record['header']['class'],$record['header']['ttl'], 487: $record['data'],$record['domain'],$record['string'],$record['extras']); 488: } 489: 490: $this->lastadditional=new DNSAnswer(); 491: for ($a=0; $a<$this->header['arcount']; $a++) 492: { 493: $record=$this->ReadRecord(); 494: $this->lastadditional->AddResult($record['header']['type'],$record['typeid'],$record['header']['class'],$record['header']['ttl'], 495: $record['data'],$record['domain'],$record['string'],$record['extras']); 496: } 497: 498: 499: 500: 501: return $dns_answer; 502: } 503: 504: 505: function SmartALookup($hostname,$depth=0) 506: { 507: $this->Debug("SmartALookup for ".$hostname." depth ".$depth); 508: if ($depth>5) return ""; // avoid recursive lookups 509: // The SmartALookup function will resolve CNAMES using the additional properties if possible 510: $answer=$this->Query($hostname,"A"); 511: 512: if ($answer===false) return ""; // failed totally 513: if ($answer->count<=0) return ""; // no records at all returned 514: 515: $best_answer=""; 516: $best_answer_typeid=0; 517: 518: $records=$answer->count; 519: for ($a=0; $a<$records; $a++) 520: { 521: $data=$answer->results[$a]->data; 522: $answer_typeid=$answer->results[$a]->typeid; 523: 524: if ($answer_typeid=="A") // found it 525: { 526: $best_answer=$data; 527: $best_answer_typeid="A"; 528: $a=$records+10; 529: } 530: else if ($answer_typeid=="CNAME") // alias 531: { 532: $best_answer=$data; 533: $best_answer_typeid="CNAME"; 534: // and keep going 535: } 536: 537: } 538: 539: if ( ($best_answer=="") || ($best_answer_typeid=="") ) return ""; 540: 541: if ($best_answer_typeid=="A") return $best_answer; // got an IP ok 542: 543: if ($best_answer_typeid!="CNAME") return ""; // shouldn't ever happen 544: 545: $newtarget=$best_answer; // this is what we now need to resolve 546: 547: // First is it in the additional section 548: for ($a=0; $a<$this->lastadditional->count; $a++) 549: { 550: if ( ($this->lastadditional->results[$a]->domain==$hostname) && 551: ($this->lastadditional->results[$a]->typeid=="A") ) 552: return $this->lastadditional->results[$a]->data; 553: } 554: 555: // Not in the results 556: 557: return $this->SmartALookup($newtarget,++$depth); 558: } 559: 560: } 561: 562: ?>