Location: PHPKode > scripts > Proxy Test > ProxyTest.class.php
<?php
/*
    ProxyTest.class.php  -  By Freman
====================================================
        Based on pxytest by Chip Rosenthal
        -    http://www.unicom.com/sw/pxytest/

Original Copyright Notice and Licence:
	Copyright (c) 1994-2002, Chip Rosenthal <hide@address.com>.
	All rights reserved.
	The most recent version of this software may be obtained for free at
	<http://www.unicom.com/sw/>. 
	
	http://www.unicom.com/sw/license.html

	Note: you won't find the php class at the above urls

====================================================

    Note: I've removed all things relating to
    actualy sending mail, you can only try to
    connect. - Just in case someone can't set
    up their scripts properly
====================================================

	verbosity: (default:3)
    	0 - Display nothing but program errors.
    	1 - Display final test result.
    	2 - Display individual test results.
    	3 - Display details of individual tests.

	construct:
		$config = array(
						"hostname" => "this.hostname.com",	// Not currently used
						"mailserver" => "mail.hostname.com",  // The mail server to try to connect to
						"mailport" => "25", // The port the mail server is running on
						"verbosity" => 2, // See above, can be used to turn off all output and only variables will be set
						"scan" => "basic", //The depth of scan basic|full|socks
						"stoponproxy" => true,  //Stop when we find an open proxy
						"htmloutput" => true,  //htmlize everything we come output
						);
		$scanner = new ProxyTest($config);
		
	scan:
		$found = $scanner->PerformScan('123.245.167.234');
	alternative:
		$found = $scanner->PerformScan('proxy.somehost.com');
	
	$found === $scanner->FoundProxies;
	
	$scanner->FoundProxies is an array of ports/protocols that we found proxies on.
	$scanner->Num_Open_Poxies is an integer count of FoundProxies
*/

define('SERVER_NAME',$_SERVER['SERVER_NAME']);
ini_set('max_execution_time','120');		//Try to avoid the whole "script timeout" situation
class ProxyTest {

    var $_Tags_Scanlists = array (
                                "basic" => array(
                                        // 80 - Web server with unsecured/misconfigured proxy function.
                                        "80",  "80/http-post",
                                        // 3128 - Well known port for the "squid" web cache.
                                        "3128",
                                        // 8080 - Well known port for the "webcache" service - secondary http
                                        "8080", "8080/http-post",
                                        // 8081 - Well known port for the "tproxy" transparent proxy service.
                                        "8081",
                                        // 1080 - Well known port for the "socks" proxy service.
                                        "1080/socks4", "1080/socks4",
                                        // 23 - Well known for the "telnet" service - also wingate proxy
                                        "23/telnet", "23/cisco", "23/wingate",
                                        // 6588 - AnalogX proxy
                                        "6588",
                                        // 1180 - brand or vendor unknown - Trojin?
                                        "1180/socks4",
                                ),
                                "full" => array(
                                        // Start with all the basic scans.
                                        "basic",
                                        // Add in the ports where we;ve seen reports of occasional proxies
                                        "81", "85", "1182", "1282", "4480", "7033", "8000", "8085", "8090", "8095", "8100", "8105", "8110", "8888",
                                        // 1180 - brand or vendor unknown - Trojin?
                                        "1180/socks5", "1181/cisco", "1181/telnet","1181/wingate",
                                ),
                                "socks" => array(
                                        "1180/socks4",
                                        "1180/socks5",
                                ),
                          );
    // Stop us from sucking to much data at once
    var $Input_threshold = 2048;

    // Timeouts on waiting to connect and for input...
    var $Timeout_Connect = 5;
    var $Timeout_Data = 10;

    // Associate test protocols with procedure.
    var $Test_By_Protocol_Type = array (
                                    "http-connect" => "proxy_test_http_connect",
                                    "http-post"    => "proxy_test_http_post",
                                    "socks4"       => "proxy_test_socks4",
                                    "socks5"       => "proxy_test_socks5",
                                    "wingate"      => "proxy_test_wingate",
                                    "telnet"       => "proxy_test_telnet",
                                    "cisco"        => "proxy_test_cisco",
                                 );

    var $Hostname = SERVER_NAME;	  // Not currently used
                                 
    var $Num_Open_Poxies = 0;
    var $Mail_Server = '';
    var $Mail_Port = 25;
/*
      0 - Display nothing but program errors.
      1 - Display final test result.
      2 - Display individual test results.
      3 - Display details of individual tests.
*/
    var $Verbosity = 3;
    var $Scan = "basic";
    var $SMTP_Banner = "220 ";        // Trailing space is intentional
    var $StopOnProxy = true;
	var $FoundProxies = array();
	var $HTMLOutput = true;
    
	// See above for calling
    function ProxyTest ($config = null) {
    	// Quickly cycle through the list of possible parameters and override the defaults
        $quickassign = array(
                          'hostname'=>'Hostname',
                          'mailserver'=>'Mail_Server',
                          'mailport'=>'Mail_Port',
                          'verbosity'=>'Verbosity',
                          'scan'=>'Scan',
                          'stoponproxy'=>'StopOnProxy',
                          'htmloutput'=>'HTMLOutput'
                       );

        if (!is_null($config)) {
            foreach ($quickassign as $a => $b) {
                if (isset($config[$a])) {
                    $this->$b = $config[$a];
                }
            }
        }
        
        // Confirm and convert the mail_server variable to an ip address
        $this->Mail_Server = $this->locate_mailserver($this->Mail_Server);
    }

    // PerformScan('123.213.214.152'); OR PerformScan('proxy.somehost.com')
    function PerformScan ($target) {
    	// IP? No? lets dns it!
        if (!preg_match("/(\d{1,3}\.){3,3}\d{1,3}/",$target)) {
            $target = gethostbyname($target) or die("Unknown Host: \"$target\"\n");
        }
        
        // Load the ports list for the scan we want to do
        $portslist = $this->_Tags_Scanlists[$this->Scan];

        $found = false;  // Always hoping we don't find one
        // Loop through the ports list
        while (count($portslist) > 0 && !$found) {
            $portspec = array_shift($portslist);
            
            // If this is an entrytag, expand it out and push the values onto the front of the list.
            if (isset($this->_Tags_Scanlists[$portspec])) {
                array_unshift($portslist,$this->_Tags_Scanlists[$portspec]);
                continue;
            }
			
            // Parse the port specification in form:  num[-num][/proto]
            list($minport,$maxport,$proto) = $this->parse_portspec($portspec);
            if ($proto == "all") {
                array_unshift($portslist, array_map(create_function('$n','return"'.$minport.'-'.$maxport.'/$n";'),array_keys($this->Test_By_Protocol_Type)));
                continue;
            }
            
            $test_function = $this->Test_By_Protocol_Type[$proto] or die("Unknown proxy type \"$proto\"\n");
            
            // Go through the range of ports specified
            for ($port=$minport; $port<=$maxport; $port++) {
            	//Perform the tests
            	if ($this->_handle_result($this->_perform_proxy_test($target,$port,$proto,$test_function),$port.':'.$proto)) {
            		// Break if we arn't going to process all the tests
            		$found = true;
            		break;
            	}
            }
        }
        // Return the found proxies as the result.
        return $this->FoundProxies;
    }

    // locate_mailserver($mail_server)
    function locate_mailserver($server) {
    	// Was I given an IP? no? lets make one!
    	if (preg_match("/^(\d{1,3}\.){3,3}\d{1,3}$/",$server)) {
    		$addr = $server;
    	} else {
    		$addr = gethostbyname($server) or die("Host lookup for \"$server\" failed\n");
    	}
    	return $addr;
    }
    
    // Perform the test
    function _perform_proxy_test($addr,$port,$proto,$func) {
    	$this->PrintD("Testing address \"$addr\" port \"$port\" proto \"$proto\"\n",2);

    	$sock = @fsockopen($addr,$port,$errno,$errstr,$this->Timeout_Connect);
    	if (!$sock) {
    		$this->PrintD("Cannot connect to $addr:$port\n",2);
    	} else {
    		$this->PrintD("Connected to $addr:$port\n",2);
    		if (!$this->$func($sock)) {
    			fclose($sock);
    			return false;
    		}
    		$this->PrintD("*** ALERT - open proxy detected\n",2);
    		fclose($sock);
    		return true;
    	}
    }
    
    // function for the http-connect proxy check
    function proxy_test_http_connect($sock) {
    	$this->wrsock($sock, "CONNECT $this->Mail_Server:$this->Mail_Port HTTP/1.0\r\n\r\n");
    	$data = $this->rdsock($sock);
    	if (preg_match("/^HTTP\/\S+\s+(200)\s+/",$data)) {
    		return $this->found_smtp_banner($sock);
    	}
    	return false;
    }
    
    // function for the http-post proxy check
    function proxy_test_http_post ($sock) {
    	$doc = "RSET\r\nQUIT\r\n";
    	$this->wrsock($sock,"POST http://$this->Mail_Server:$this->Mail_Port/ HTTP/1.0\r\n");
    	$this->wrsock($sock,"Connect-Type: text/plain\r\n");
    	$this->wrsock($sock,"Connect-Length: ".strlen($doc)."\r\n\r\n");
    	$this->wrsock($sock, $doc);
    	return $this->found_smtp_banner($sock,array("abort" => "^HTTP\/1.\d [45]\d\d"));
    }
    
    // function for the socks4 proxy check
    function probe_test_socks4 ($sock) {
    	$Socks4_Connect_Responses = array(
    									90 => "request granted",
    									91 => "request rejected or failed",
    									92 => "request rejected, ident required",
    									93 => "request rejected, ident mismatch"
    								);
/*
       CONNECT request:
        VN              1 byte          socks version (4)
        CD              1 byte          command code (1 = connect)
        DSTPORT 2 bytes         destination port
        DSTIP   4 bytes         destination address
        USERID  variable        (not used here)
        NULL    1 byte
*/
    	$msg = pack("CCnA4x",4,1,$this->Mail_Port,inet_aton($this->Mail_Server));
    	$this->wrsock($sock,$msg);
    	
/*
        CONNECT reply:
         VN              1 byte          version of the reply code (should be 0)
         CD              1 byte          command code (the result)
         DSTPORT 2 bytes
         DSTIP   4 bytes
*/
    	if (!($msg = $this->rdsock($sock,8))) {
    		return false;
    	}
    	$repcode = unpack("C*",$msg);
    	$repcode = $repcode[1];
    	$repmsg = $Socks4_Connect_Responses[$repcode] || "unknown reply code";
    	$this->PrintD("socks reply code = $repcode ($repmsg)\n",3);
    	if ($repcode == 90) {
    		// Grab the smtp banner, but return TRUE even if that chokes
    		found_smtp_banner($sock);
    		return true;
    	} else {
    		return false;
    	}
    }

    // function for the socks4 proxy check
    // untested - read comment in the original pxytest.pl file
    function probe_test_socks5 ($sock) {
    	$Socks5_Methods = array(
    		0 => "no authentication required",
    		1 => "GSSAPI",
    		2 => "username/password",
    		255 => "no acceptable methods"
    	);
    	
    	$Socks5_Connect_Responses = array(
    									0 => "succeeded",
    									1 => "general SOCKS server failure",
    									2 => "connection not allowed by ruleset",
    									3 => "Network unreachable",
    									4 => "Host unreachable",
    									5 => "Connection refused",
    									6 => "TTL expired",
    									7 => "Command not supported",
    									8 => "Address type not supported"
    								);

/*
       METHOD SELECT message:
        VER              1 byte  socks version (5)
        NMETHODS 1 byte  number of method identifies
        METHODS  var     list of methods (0 = no auth)
*/
    	$msg = pack("CCC",5,1,0);
    	$this->wrsock($sock,$msg);

/*
       METHOD SELECT reply:
        VER              1 byte  socks version (5)
        METHOD   1 byte  method to use
*/  	
    	if (!($msg = $this->rdsock($sock,2))) {
    		return false;
    	}
    	$repcode = unpack("C*",$msg);
    	$repcode = $repcode[1];
    	$repmsg = $Socks5_Methods[$repcode] || "unknown or reserved method type";
    	$this->PrintD("socks reply code = $repcode ($repmsg)\n");
    	if ($repcode != 0) {
    		return false;
    	}

/*
        CONNECT request:
         VER             1 byte          socks version (5)
         CMD             1 byte          command code (1 = connect)
         RSV             1 byte          reserved
         ATYP    1 byte          address type (1 = IPv4)
         DST.ADDR        variable        destination address
         DST.PORT        2 bytes         destination port
*/
    	$msg = pack("CCCCa4n", 5, 1, 0, 1, inet_aton($this->Mail_Server),$this->Mail_Port);
    	$this->wrsock($sock,$msg);

/*
        CONNECT reply:
         VER             1 byte          socks version (5)
         REP             1 byte          reply code
         RSV             1 byte          reserved
         ATYP    1 byte          address type (1 = IPv4)
         BND.ADDR        variable        server bound address
         BND.PORT        2 bytes         server bound port
*/
    	if (!($msg = $this->rdsock($sock,10))) {
    		return false;
    	}
    	$repcode = unpack("C*",$msg);
    	$repcode = $repcode[1];
    	$repmsg = $Socks5_Connect_Responses[$repcode] || "unknown or reserved reply code";
    	$this->PrintD("socks reply code = $repcode ($repmsg)\n",3);
    	if ($repcode != 0) {
    		return false;
    	} else {
    		// Grab the smtp banner, but return TRUE even if that chokes
    		found_smtp_banner($sock);
    		return true;
    	}
    }
   
    // function for the wingate proxy check 
    function proxy_test_wingate($sock) {
    	$this->wrsock($sock,"$this->Mail_Server:$this->Mail_Port\r\n");
    	if ($rep = $this->rdsock($sock)) {
    		return found_smtp_banner($sock, array("abort" => "^[Pp]assword:"));
    	} else {
    		return false;
    	}
    }
    
    // function for the telnet proxy check
    // to check for systems that allow telnet commands
    function proxy_test_telnet($sock) {
    	if ($this->wrsock($sock, "telnet $this->Mail_Server $this->Mail_Port\r\n")) {
    		return found_smtp_banner($sock, array("abort" => "^[Pp]assword:"));
    	} else {
    		return false;
    	}
    }
    
    // for the cisco proxy check
    // Idea being that any cisco device on it's default configuration can be used for
    // A telnet proxy.
    function proxy_test_cisco ($sock) {
    	if (!$this->rdsock_for_message($sock,"^[Uu]ser [Aa]ccess [Vv]erification")) {
    		return false;
    	}
        $this->wrsock($sock, "cisco\r\n");
    	if (!$this->rdsock_for_message($sock,"^[Pp]assword")) {
    		return false;
    	}
        // Cor! we got in, lets run a telnet proxy check
    	return $this->proxy_test_telnet($sock);
    }
    
    // Check for smtp banner in reply.
    // $config = array("abort" => array('^match','this$'));
    // $config = array("abort" => '^match\s+this');
    function found_smtp_banner ($sock,$config=array()) {
    	$config["match"] = "^\Q$this->SMTP_Banner";
    	return $this->rdsock_for_message($sock,$config);
    }
    
    // Read from the socket till we find our match or abort.
    // $config = array(
    //				"abort" => "see above",
    //				"match" => "matchthis",
    //			 );
    function rdsock_for_message ($sock,$config) {
    	$amountread = 0;
    	if (isset($config['match'])) {
    		$matching = $config['match'];
    	} else {
    		die("Must specifiy \"match\" for rdsock_for_message()\n");
    	}
    	if (isset($config['abort'])) {
    		$abortlist = $config['abort'];
    	} else {
    		$abortlist = false;
    	}
    	while (!feof($sock)) {
    		$read = $this->rdsock($sock);
    		if ($read === false) {
    			return false;
    		}
    		if (preg_match("/$matching/",$read)) {
    			return true;
    		}
    		if (is_array($abortlist)) {
    			foreach ($abortlist as $pat) {
    				if (preg_match("/$pat/",$read)) {
    					return false;
    				}
    			}
    		} elseif (!($abortlist === false)) {
    			if (preg_match("/$abortlist/",$read)) {
    				return false;
    			}
    		}
    		$amountread += strlen($read);
    		if ($this->Input_threshold <= $amountread) {
    			$this->PrintD("<<< WARNING: input threshold exceeded - bailing out\n",3);
    			return false;
    		}
    	}
       	return false;
    }	

    // Read from sock, $config can contain timeout and/or bytes to be read
    function rdsock ($sock,$config=array()) {
    	if (isset($config['bytes'])) {
    		$nb = $config['bytes'];
    	}
    	if (isset($config['timeout'])) {
    		$timeout = $config['timeout'];
    	} else {
    		$timeout = $this->Timeout_Data;
    	}
    	stream_set_timeout($sock,$timeout);
    	if (isset($nb)) {
    		$data = fread($sock,$nb);
    	} else {
    		$data = fgets($sock);
    	}
    	if ($data === false) {
    		$status = socket_get_status($sock);
    		if ($status['timed_out']) {
    			$this->PrintD("<<< ERROR: error reading from socket: Socket Timed Out\n",3);
    		} else {
    			$this->PrintD("<<< ERROR: error reading from socket: Unknown error\n",3);
    		}
    	} elseif (empty($data)) {
    		$this->PrintD("<<< EOF: end of input\n",3);
    	} else {
    		$this->PrintD("<<< ".$this->printable_msg($data)."\n",3);
    	}
    	return $data;
    }
    
    // Write the data to socket
    function wrsock ($sock,$data) {
    	$this->PrintD(">>> ".$this->printable_msg($data)."\n",3);
    	stream_set_timeout($sock,$this->Timeout_Data);
    	$res = fwrite($sock,$data);
    	if ($res === false) {
    		$status = socket_get_status($sock);
    		if ($status['timed_out']) {
    			$this->PrintD(">>> ERROR: error writing to socket: Socket Timed Out\n",3);
    		} else {
    			$this->PrintD(">>> ERROR: error writing to socket: Unknown error\n",3);
    		}
    	} 
    	return $res;
    }
    
    // Return a printable message
    function printable_msg($msg) {
    	if (preg_match("/^[[:print:][:space:]]*$/",$msg)) {
    		$msg = preg_replace("/\r/","\\r",$msg);
    		$msg = preg_replace("/\n/","\\n",$msg);
    		return $msg;
    	}
    	$x = unpack("C*",$msg);
    	return "binary message: ".join(" ", array_map(create_function('$n','sprintf("%d",$n)'),$x));
    }
    
    // Handle the result of the _perform_proxy_test
    // Returns true if proxy found and we are ment to stop on find.
    function _handle_result ($rc = null,$tc = null) {
        if (is_null($rc) or $rc === false) {
            return false;
        }
        if ($this->StopOnProxy) {
            $this->PrintD("Test complete - identified open proxy $tc\n");
            $this->Num_Open_Poxies ++;
            array_push($this->FoundProxies,$tc);
            return true;
        }
        $this->PrintD("Identified open proxy $tc\n");
        $this->Num_Open_Poxies ++;
        array_push($this->FoundProxies,$tc);
        return false;
    }

    // Parse port specification
    // $spec = "80[-8080][/telnet]"
    function parse_portspec ($spec) {
        if (preg_match("/^(\d+)(-(\d+))?(\/([-\w]+))?$/",$spec,$m)) {
            $min = $m[1];
            $max = (isset($m[3]) && !empty($m[3])) ? $m[3] : $m[1];
            $proto = (isset($m[5])) ? $m[5] : "http-connect";
            if ($proto == "http") {
                $proto = "http-connect";
            }
            return array($min,$max,$proto);
         } else {
            die("Bad port specification \"$spec\"\n");
         }
    }
          
    // Print our verbose info, or not as the case may be
    function PrintD ($text,$level=1) {
        if ($this->Verbosity >= $level) {
            if ($this->HTMLOutput === true) {
            	echo htmlentities($text);
            } else {
        		echo $text;
            }
            flush();
		    ob_flush();
        }
    }
}
?>
Return current item: Proxy Test