File: 0.04.30a/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) 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: $tmp=0; // PHP4 fix for pass-by-ref 155: $nextitem=$this->ReadResponse(1,$offset++); 156: $pointer_offset = ( ($label_len & 0x3f) << 8 ) + ord($nextitem); 157: // Branch Back Upon Ourselves... 158: $this->Debug("Label Offset: ".$pointer_offset); 159: $pointer_labels=$this->ReadDomainLabels($pointer_offset,$tmp); 160: foreach($pointer_labels as $ptr_label) 161: $labels[]=$ptr_label; 162: $return=true; 163: } 164: } 165: $counter=$offset-$startoffset; 166: return $labels; 167: } 168: 169: function ReadDomainLabel() 170: { 171: $count=0; 172: $labels=$this->ReadDomainLabels($this->responsecounter,$count); 173: $domain=implode(".",$labels); 174: $this->responsecounter+=$count; 175: $this->Debug("Label ".$domain." len ".$count); 176: return $domain; 177: } 178: 179: function Debug($text) 180: { 181: if ($this->debug) echo $text."\n"; 182: } 183: 184: function DebugBinary($data) 185: { 186: if ($this->binarydebug) 187: { 188: for ($a=0; $a189: { 190: echo $a; 191: echo "\t"; 192: printf("%d",$data[$a]); 193: echo "\t"; 194: $hex=bin2hex($data[$a]); 195: echo "0x".$hex; 196: echo "\t"; 197: $dec=hexdec($hex); 198: echo $dec; 199: echo "\t"; 200: if (($dec>30)&&($dec<150)) echo $data[$a]; 201: echo "\n"; 202: } 203: } 204: } 205: 206: function SetError($text) 207: { 208: $this->error=true; 209: $this->lasterror=$text; 210: $this->Debug("Error: ".$text); 211: } 212: 213: function ClearError() 214: { 215: $this->error=false; 216: $this->lasterror=""; 217: } 218: 219: function DNSQuery($server,$port=53,$timeout=60,$udp=true,$debug=false) 220: { 221: $this->server=$server; 222: $this->port=$port; 223: $this->timeout=$timeout; 224: $this->udp=$udp; 225: $this->debug=$debug; 226: 227: $this->types=new DNSTypes(); 228: $this->Debug("DNSQuery Class Initialised"); 229: } 230: 231: 232: function ReadRecord() 233: { 234: // First the pesky domain names - maybe not so pesky though I suppose 235: 236: $domain=$this->ReadDomainLabel(); 237: 238: 239: 240: $ans_header_bin=$this->ReadResponse(10); // 10 byte header 241: $ans_header=unpack("ntype/nclass/Nttl/nlength",$ans_header_bin); 242: $this->Debug("Record Type ".$ans_header['type']." Class ".$ans_header['class']." TTL ".$ans_header['ttl']." Length ".$ans_header['length']); 243: 244: $typeid=$this->types->GetById($ans_header['type']); 245: $extras=array(); 246: $data=""; 247: $string=""; 248: 249: switch($typeid) 250: { 251: case "A": 252: $ip=implode(".",unpack("Ca/Cb/Cc/Cd",$this->ReadResponse(4))); 253: $data=$ip; 254: $string=$domain." has address ".$ip; 255: break; 256: 257: case "NS": 258: $nameserver=$this->ReadDomainLabel(); 259: $data=$nameserver; 260: $string=$domain." nameserver ".$nameserver; 261: break; 262: 263: case "PTR": 264: $data=$this->ReadDomainLabel(); 265: $string=$domain." points to ".$data; 266: break; 267: 268: case "CNAME": 269: $data=$this->ReadDomainLabel(); 270: $string=$domain." alias of ".$data; 271: break; 272: 273: case "MX": 274: $prefs=$this->ReadResponse(2); 275: $prefs=unpack("nlevel",$prefs); 276: $extras['level']=$prefs['level']; 277: $data=$this->ReadDomainLabel(); 278: $string=$domain." mailserver ".$data." (pri=".$extras['level'].")"; 279: break; 280: 281: case "SOA": 282: // Label First 283: $data=$this->ReadDomainLabel(); 284: $responsible=$this->ReadDomainLabel(); 285: 286: $buffer=$this->ReadResponse(20); 287: $extras=unpack("nserial/nrefresh/Nretry/Nexpiry/Nminttl",$buffer); 288: $dot=strpos($responsible,"."); 289: $responsible[$dot]="@"; 290: $extras['responsible']=$responsible; 291: $string=$domain." SOA ".$data." Serial ".$extras['serial']; 292: break; 293: 294: case "TXT": 295: $data=$this->ReadResponse($ans_header['length']); 296: $string=$domain." TEXT ".$data; 297: break; 298: 299: default: // something we can't deal with 300: $stuff=$this->ReadResponse($ans_header['length']); 301: break; 302: 303: } 304: 305: //$dns_answer->AddResult($ans_header['type'],$typeid,$ans_header['class'],$ans_header['ttl'],$data,$domain,$string,$extras); 306: $return=array( 307: "header" => $ans_header, 308: "typeid" => $typeid, 309: "data" => $data, 310: "domain" => $domain, 311: "string" => $string, 312: "extras" => $extras ); 313: return $return; 314: } 315: 316: 317: 318: 319: 320: 321: function Query($question,$type="A") 322: { 323: $this->ClearError(); 324: $typeid=$this->types->GetByName($type); 325: if ($typeid===false) 326: { 327: $this->SetError("Invalid Query Type ".$type); 328: return false; 329: } 330: 331: if ($this->udp) $host="udp://".$this->server; 332: else $host=$this->server; 333: 334: if (!$socket=fsockopen($host,$this->port,$this->timeout)) 335: { 336: $this->SetError("Failed to Open Socket"); 337: return false; 338: } 339: 340: // Split Into Labels 341: if (preg_match("/[a-z|A-Z]/",$question)==0) // IP Address 342: { 343: $labeltmp=explode(".",$question); // reverse ARPA format 344: for ($i=count($labeltmp)-1; $i>=0; $i--) 345: $labels[]=$labeltmp[$i]; 346: $labels[]="IN-ADDR"; 347: $labels[]="ARPA"; 348: } 349: else // hostname 350: $labels=explode(".",$question); 351: 352: $question_binary=""; 353: for ($a=0; $a354: { 355: $size=strlen($labels[$a]); 356: $question_binary.=pack("C",$size); // size byte first 357: $question_binary.=$labels[$a]; // then the label 358: } 359: $question_binary.=pack("C",0); // end it off 360: 361: $this->Debug("Question: ".$question." (type=".$type."/".$typeid.")"); 362: 363: $id=rand(1,255)|(rand(0,255)<<8); // generate the ID 364: 365: // Set standard codes and flags 366: $flags=0x0100 & 0x0300; // recursion & queryspecmask 367: $opcode=0x0000; // opcode 368: 369: // Build the header 370: $header=""; 371: $header.=pack("n",$id); 372: $header.=pack("n",$opcode | $flags); 373: $header.=pack("nnnn",1,0,0,0); 374: $header.=$question_binary; 375: $header.=pack("n",$typeid); 376: $header.=pack("n",0x0001); // internet class 377: $headersize=strlen($header); 378: $headersizebin=pack("n",$headersize); 379: 380: $this->Debug("Header Length: ".$headersize." Bytes"); 381: $this->DebugBinary($header); 382: 383: if ( ($this->udp) && ($headersize>=512) ) 384: { 385: $this->SetError("Question too big for UDP (".$headersize." bytes)"); 386: fclose($socket); 387: return false; 388: } 389: 390: if ($this->udp) // UDP method 391: { 392: if (!fwrite($socket,$header,$headersize)) 393: { 394: $this->SetError("Failed to write question to socket"); 395: fclose($socket); 396: return false; 397: } 398: if (!$this->rawbuffer=fread($socket,4096)) // read until the end with UDP 399: { 400: $this->SetError("Failed to write read data buffer"); 401: fclose($socket); 402: return false; 403: } 404: } 405: else // TCP 406: { 407: if (!fwrite($socket,$headersizebin)) // write the socket 408: { 409: $this->SetError("Failed to write question length to TCP socket"); 410: fclose($socket); 411: return false; 412: } 413: if (!fwrite($socket,$header,$headersize)) 414: { 415: $this->SetError("Failed to write question to TCP socket"); 416: fclose($socket); 417: return false; 418: } 419: if (!$returnsize=fread($socket,2)) 420: { 421: $this->SetError("Failed to read size from TCP socket"); 422: fclose($socket); 423: return false; 424: } 425: $tmplen=unpack("nlength",$returnsize); 426: $datasize=$tmplen['length']; 427: $this->Debug("TCP Stream Length Limit ".$datasize); 428: if (!$this->rawbuffer=fread($socket,$datasize)) 429: { 430: $this->SetError("Failed to read data buffer"); 431: fclose($socket); 432: return false; 433: } 434: } 435: fclose($socket); 436: 437: $buffersize=strlen($this->rawbuffer); 438: $this->Debug("Read Buffer Size ".$buffersize); 439: 440: if ($buffersize<12) 441: { 442: $this->SetError("Return Buffer too Small"); 443: return false; 444: } 445: 446: $this->rawheader=substr($this->rawbuffer,0,12); // first 12 bytes is the header 447: $this->rawresponse=substr($this->rawbuffer,12); // after that the response 448: 449: $this->responsecounter=12; // start parsing response counter from 12 - no longer using response so can do pointers 450: 451: $this->DebugBinary($this->rawbuffer); 452: 453: $this->header=unpack("nid/nspec/nqdcount/nancount/nnscount/narcount",$this->rawheader); 454: 455: $answers=$this->header['ancount']; 456: $this->Debug("Query Returned ".$answers." Answers"); 457: 458: $dns_answer=new DNSAnswer(); 459: 460: // Deal with the header question data 461: if ($this->header['qdcount']>0) 462: { 463: $this->Debug("Found ".$this->header['qdcount']." Questions"); 464: for ($a=0; $a<$this->header['qdcount']; $a++) 465: { 466: $c=1; 467: while ($c!=0) 468: { 469: $c=hexdec(bin2hex($this->ReadResponse(1))); 470: } 471: $extradata=$this->ReadResponse(4); 472: } 473: } 474: 475: // New Functional Method 476: for ($a=0; $a<$this->header['ancount']; $a++) 477: { 478: $record=$this->ReadRecord(); 479: $dns_answer->AddResult($record['header']['type'],$record['typeid'],$record['header']['class'],$record['header']['ttl'], 480: $record['data'],$record['domain'],$record['string'],$record['extras']); 481: } 482: 483: $this->lastnameservers=new DNSAnswer(); 484: for ($a=0; $a<$this->header['nscount']; $a++) 485: { 486: $record=$this->ReadRecord(); 487: $this->lastnameservers->AddResult($record['header']['type'],$record['typeid'],$record['header']['class'],$record['header']['ttl'], 488: $record['data'],$record['domain'],$record['string'],$record['extras']); 489: } 490: 491: $this->lastadditional=new DNSAnswer(); 492: for ($a=0; $a<$this->header['arcount']; $a++) 493: { 494: $record=$this->ReadRecord(); 495: $this->lastadditional->AddResult($record['header']['type'],$record['typeid'],$record['header']['class'],$record['header']['ttl'], 496: $record['data'],$record['domain'],$record['string'],$record['extras']); 497: } 498: 499: 500: 501: 502: return $dns_answer; 503: } 504: 505: 506: function SmartALookup($hostname,$depth=0) 507: { 508: $this->Debug("SmartALookup for ".$hostname." depth ".$depth); 509: if ($depth>5) return ""; // avoid recursive lookups 510: // The SmartALookup function will resolve CNAMES using the additional properties if possible 511: $answer=$this->Query($hostname,"A"); 512: 513: if ($answer===false) return ""; // failed totally 514: if ($answer->count<=0) return ""; // no records at all returned 515: 516: $best_answer=""; 517: $best_answer_typeid=0; 518: 519: $records=$answer->count; 520: for ($a=0; $a<$records; $a++) 521: { 522: $data=$answer->results[$a]->data; 523: $answer_typeid=$answer->results[$a]->typeid; 524: 525: if ($answer_typeid=="A") // found it 526: { 527: $best_answer=$data; 528: $best_answer_typeid="A"; 529: $a=$records+10; 530: } 531: else if ($answer_typeid=="CNAME") // alias 532: { 533: $best_answer=$data; 534: $best_answer_typeid="CNAME"; 535: // and keep going 536: } 537: 538: } 539: 540: if ( ($best_answer=="") || ($best_answer_typeid=="") ) return ""; 541: 542: if ($best_answer_typeid=="A") return $best_answer; // got an IP ok 543: 544: if ($best_answer_typeid!="CNAME") return ""; // shouldn't ever happen 545: 546: $newtarget=$best_answer; // this is what we now need to resolve 547: 548: // First is it in the additional section 549: for ($a=0; $a<$this->lastadditional->count; $a++) 550: { 551: if ( ($this->lastadditional->results[$a]->domain==$hostname) && 552: ($this->lastadditional->results[$a]->typeid=="A") ) 553: return $this->lastadditional->results[$a]->data; 554: } 555: 556: // Not in the results 557: 558: return $this->SmartALookup($newtarget,++$depth); 559: } 560: 561: } 562: 563: ?>