Location: PHPKode > scripts > SSH in PHP > ssh-in-php/ssh_in_php.php
<?php
/**
 * SSHinPHP :: SSH and PHP bastardisation of the highest grade
 * 
 * supports only SSHv1, but SSHv2 implementation will be done - at some day
 *
 * @author nexus <hide@address.com>
 * @see http://www.universe-people.com
 *
 * be sure to take some really strong drug before opening @see link or reading
 * this source code
 */
function blob2integer($b) {
	$integer = 0;
	for ($i = 0; $i < strlen($b); $i++) {
		$integer = bcmul($integer,256);
		$integer = bcadd($integer,ord($b[$i]));
	}
	return $integer;
}

function integer2blob($integer) {
	$blob = "";
	while ($integer > 0) {
		$blob = chr(bcmod($integer,256)).$blob;
		$integer = bcdiv($integer,256);
	}
	return $blob;
}

function integer2dword ($int) {
	if (($int < pow(2,32)) && ($int >= 0)) {
		$dword = "";
		while ($int > 0) {
			$dword = chr(bcmod ($int,256)).$dword;
			$int = bcdiv ($int,256);
		}
		return str_pad($dword,4,chr(0),STR_PAD_LEFT);
	} else {
		throw new SSHException('integer must be >= 0 and < 2^32 for dword conversion');
	}
}


function dword2integer ($s) {
	if (strlen($s) == 4) {
		return bcadd(bcmul(bcadd(bcmul(bcadd(bcmul(ord($s[0]),256),ord($s[1])),256),ord($s[2])),256),ord($s[3]));
	} else {
		throw new SSHException('dword must be exactly 4 bytes long for integer conversion');
	}
}


function hex2int ($hex) {
	if (strlen($hex) % 2 == 1) {
		$hex = "0".$hex;
	}

	$int = 0;
	for ($i = 0; $i < strlen($hex); $i += 2) {
		$int = bcmul($int,256);
		$int = bcadd($int,hexdec(substr($hex,$i,2)));
	}
	return $int;
}

function int2hex ($int) {
	$hex = "";
	while ($int > 0) {
		$hex = dechex(bcmod ($int,256)).$hex;
		$int = bcdiv ($int,256);
	}
	return $hex;
}

function ul_and ($a,$b) {
	if (strlen($a) > 4 && strlen($b) > 4) {
		throw new Exception("a and b must be unsigned long interpreted as binary string shorter (or equal) than 4 bytes");
	}
	$a = str_pad($a,4,chr(0),STR_PAD_LEFT);
	$b = str_pad($b,4,chr(0),STR_PAD_LEFT);

	return chr(ord($a[0]) & ord($b[0])).chr(ord($a[1]) & ord($b[1])).chr(ord($a[2]) & ord($b[2])).chr(ord($a[3]) & ord($b[3]));
}

function ul_or ($a,$b) {
	if (strlen($a) > 4 && strlen($b) > 4) {
		throw new Exception("a and b must be unsigned long interpreted as binary string shorter (or equal) than 4 bytes");
	}
	$a = str_pad($a,4,chr(0),STR_PAD_LEFT);
	$b = str_pad($b,4,chr(0),STR_PAD_LEFT);

	return chr(ord($a[0]) | ord($b[0])).chr(ord($a[1]) | ord($b[1])).chr(ord($a[2]) | ord($b[2])).chr(ord($a[3]) | ord($b[3]));
}

function ul_xor ($a,$b) {
	if (strlen($a) > 4 && strlen($b) > 4) {
		throw new Exception("a and b must be unsigned long interpreted as binary string shorter (or equal) than 4 bytes");
	}
	$a = str_pad($a,4,chr(0),STR_PAD_LEFT);
	$b = str_pad($b,4,chr(0),STR_PAD_LEFT);

	return chr(ord($a[0]) ^ ord($b[0])).chr(ord($a[1]) ^ ord($b[1])).chr(ord($a[2]) ^ ord($b[2])).chr(ord($a[3]) ^ ord($b[3]));
}

function ul_shift_l ($a,$amount) {
	if (strlen($a) > 4) {
		throw new Exception("a must be unsigned long interpreted as binary string shorter (or equal) than 4 bytes");
	}
	$a = str_pad($a,4,chr(0),STR_PAD_LEFT);

	if ($amount >= 0 && $amount < 8) {
		$o[0] = ((ord($a[0]) << $amount) & 255) | ((ord($a[1]) >> (8-$amount)) & 255);
		$o[1] = ((ord($a[1]) << $amount) & 255) | ((ord($a[2]) >> (8-$amount)) & 255);
		$o[2] = ((ord($a[2]) << $amount) & 255) | ((ord($a[3]) >> (8-$amount)) & 255);
		$o[3] = ((ord($a[3]) << $amount) & 255);
	} elseif ($amount >= 8 && $amount < 16) {
		$a[0] = $a[1]; $a[1] = $a[2]; $a[2] = $a[3]; $a[3] = chr(0);
		$amount -= 8;
		$o[0] = ((ord($a[0]) << $amount) & 255) | ((ord($a[1]) >> (8-$amount)) & 255);
		$o[1] = ((ord($a[1]) << $amount) & 255) | ((ord($a[2]) >> (8-$amount)) & 255);
		$o[2] = ((ord($a[2]) << $amount) & 255);
		$o[3] = $a[3];
	} elseif ($amount >= 16 && $amount < 24) {
		$a[0] = $a[2]; $a[1] = $a[3]; $a[2] = chr(0); $a[3] = chr(0);
		$amount -= 16;
		$o[0] = ((ord($a[0]) << $amount) & 255) | ((ord($a[1]) >> (8-$amount)) & 255);
		$o[1] = ((ord($a[1]) << $amount) & 255);
		$o[2] = $a[2];
		$o[3] = $a[3];
	} elseif ($amount >= 24 && $amount < 32) {
		$a[0] = $a[3]; $a[1] = chr(0); $a[2] = chr(0); $a[3] = chr(0);
		$amount -= 24;
		$o[0] = ((ord($a[0]) << $amount) & 255);
		$o[1] = $a[1];
		$o[2] = $a[2];
		$o[3] = $a[3];
	} else {
		throw new Exception("amount must be >=0 and < 32");
	}
	return chr($o[0]).chr($o[1]).chr($o[2]).chr($o[3]);
}

function ul_shift_r ($a,$amount) {
	if (strlen($a) > 4) {
		throw new Exception("a must be unsigned long interpreted as binary string shorter (or equal) than 4 bytes");
	}
	$a = str_pad($a,4,chr(0),STR_PAD_LEFT);

	if ($amount >= 0 && $amount < 8) {
		$o[0] = ((ord($a[0]) >> $amount) & 255);
		$o[1] = ((ord($a[1]) >> $amount) & 255) | ((ord($a[0]) << (8-$amount)) & 255);
		$o[2] = ((ord($a[2]) >> $amount) & 255) | ((ord($a[1]) << (8-$amount)) & 255);
		$o[3] = ((ord($a[3]) >> $amount) & 255) | ((ord($a[2]) << (8-$amount)) & 255);
	} elseif ($amount >= 8 && $amount < 16) {
		$a[3] = $a[2]; $a[2] = $a[1]; $a[1] = $a[0]; $a[0] = chr(0);
		$amount -= 8;
		$o[0] = $a[0];
		$o[1] = ((ord($a[1]) >> $amount) & 255);
		$o[2] = ((ord($a[2]) >> $amount) & 255) | ((ord($a[1]) << (8-$amount)) & 255);
		$o[3] = ((ord($a[3]) >> $amount) & 255) | ((ord($a[2]) << (8-$amount)) & 255);
	} elseif ($amount >= 16 && $amount < 24) {
		$a[3] = $a[1]; $a[2] = $a[0]; $a[1] = chr(0); $a[0] = chr(0);
		$amount -= 16;
		$o[0] = $a[0];
		$o[1] = $a[1];
		$o[2] = ((ord($a[2]) >> $amount) & 255);
		$o[3] = ((ord($a[3]) >> $amount) & 255) | ((ord($a[2]) << (8-$amount)) && 255);
	} elseif ($amount >= 24 && $amount < 32) {
		$a[3] = $a[0]; $a[2] = chr(0); $a[1] = chr(0); $a[0] = chr(0);
		$amount -= 24;
		$o[0] = $a[0];
		$o[1] = $a[1];
		$o[2] = $a[2];
		$o[3] = ((ord($a[3]) >> $amount) & 255);
	} else {
		throw new Exception("amount must be >=0 and < 32");
	}
	return chr($o[0]).chr($o[1]).chr($o[2]).chr($o[3]);
}

/**
 * RSA
 */

function modexp ($a, $x, $n) {
	$r = 1;
	while ( bccomp($x, 0 ) > 0) {
		if ( bcmod($x, 2) == 1 ) {
			$r = bcmod(bcmul($r, $a) , $n);
		}
		$a = bcmod(bcmul( $a, $a ) , $n);
		$x = bcdiv($x, 2);
	}
	return($r);
}


function rsacrypt ($a,$x,$n) {
	$a_blob = integer2blob($a);
	$a_blob_len = strlen($a_blob);
	$key_len = strlen(integer2blob($n));

	$padded_data = chr(0).chr(2);
	for ($i = 0; $i < ($key_len - $a_blob_len - 1 - 2) ;$i++) {
		$padded_data .= chr(rand(1,255));
	}
	$padded_data .= chr(0);
	$padded_data .= $a_blob;

	$enc_data = modexp(blob2integer($padded_data),$x,$n);
	return $enc_data;
}

function blob_shift_l ($blob,$amount,$preserve_len = true) {
	if ($amount >= 8 || $amount < 0) {
		throw new Exception('when doing blob_shift_l amount must be 0 < amount <= 8');
	}

	$result = "";
	$len = strlen($blob);
	for ($i = 0; $i < $len; $i++) {
		if ($i == 0 && !$preserve_len) {
			$result .= chr(ord($blob[$i]) >> (8-$amount));
		}
		if ($i > 0) {
			$result[$i-1] = chr(ord($result[$i-1]) | ord($blob[$i] >> (8-$amount)));
		}
		$result .= chr(ord($blob[$i]) << $amount);
	}

	return blob2integer($result);
}

/**
 * crc
 */

define ('SSH_CRC_CACHE_FILE','/tmp/crc.cache');
function crc_table($index) {
	static $crctable = array();

	if (count($crctable) < 256) {
		/**
		 * preload crc_table
		 */
		if (!file_exists(SSH_CRC_CACHE_FILE)) {
			/**
			 * if file doesn't exist, generate it
			 */
			$crc_file = fopen(SSH_CRC_CACHE_FILE,'w+');
			if (!$crc_file) {
				throw new Exception('Couldn\'t create CRC cache file!');
			}

			$crcword = integer2dword(0); // unsigned long

			for ($i = 0; $i < 256; $i++) {
				$newbyte = integer2dword(0); // unsigned long
				$x32term = integer2dword(0); // unsigned long
				$j = 0; // int;

				$crcword = integer2dword(0);
				$newbyte = integer2dword($i);
				for ($j = 0; $j < 8; $j++) {
					$x32term = ul_and(ul_xor($crcword,$newbyte), integer2dword(1));
					$crcword = ul_xor(ul_shift_r($crcword, 1), (ord($x32term[3]) == 1 ? integer2dword(hex2int("EDB88320")) : integer2dword(0)));
					$newbyte = ul_shift_r($newbyte, 1);
				}
				$crctable[$i] = $crcword;
			}
			
			fwrite($crc_file,serialize($crctable));
			fclose($crc_file);
		} else {
			$crc_file = fopen(SSH_CRC_CACHE_FILE,'r');
			$crctable = unserialize(fread($crc_file,filesize(SSH_CRC_CACHE_FILE)));
			fclose($crc_file);
		}
	}
	
	return $crctable[$index];
}

function crc($buf) {
	$crcword = integer2dword(0); // unsigned long
	$p = 0;	// pointer to the buf
	$len = strlen($buf); // len of sa buf
	while ($len--) {
		$newbyte = $buf[$p++];
		$newbyte = ul_xor($newbyte,ul_and($crcword, chr(0xFF)));
		$crcword = ul_xor(ul_shift_r($crcword, 8),crc_table(dword2integer($newbyte)));
	}
	return $crcword;
}


/**
 * 3des implementation 'cos of some wierdness in mcrypt implementation
 */

class DESException extends Exception {
}

class triple_des_ctx {
	public $key;
	public $iv0, $iv1;

	public function __construct($key=null,$iv0=null,$iv1=null) {
		$this->key = $key;
		$this->iv0 = $iv0;
		$this->iv1 = $iv1;
	}
}

class triple_des {
	private $enc_m;
	private $dec_m;

	private $enc_ctxs;
	private $dec_ctxs;

	public function __construct() {
		$this->enc_m = mcrypt_module_open('des','','cbc','');
		$this->dec_m = mcrypt_module_open('des','','cbc','');
	}

	public function __destruct() {
		mcrypt_module_close($this->enc_m);
		mcrypt_module_close($this->dec_m);
	}

	public function set_keys ($key) {
		$this->enc_ctxs[0] = new triple_des_ctx(substr($key,0,8),integer2dword(0),integer2dword(0));
		$this->enc_ctxs[1] = new triple_des_ctx(substr($key,8,8),integer2dword(0),integer2dword(0));
		$this->enc_ctxs[2] = new triple_des_ctx(substr($key,16,8),integer2dword(0),integer2dword(0));

		$this->dec_ctxs[0] = new triple_des_ctx(substr($key,0,8),integer2dword(0),integer2dword(0));
		$this->dec_ctxs[1] = new triple_des_ctx(substr($key,8,8),integer2dword(0),integer2dword(0));
		$this->dec_ctxs[2] = new triple_des_ctx(substr($key,16,8),integer2dword(0),integer2dword(0));
	}

	private function des_encipher (&$data,&$ctx) {
		mcrypt_generic_init($this->enc_m,$ctx->key,str_repeat(chr(0),8));
		$enc = mcrypt_generic($this->enc_m,$data);
		mcrypt_generic_deinit($this->enc_m);
		return $enc;
	}

	private function des_decipher ($data,&$ctx) {
		mcrypt_generic_init($this->dec_m,$ctx->key,str_repeat(chr(0),8));
		$dec = mdecrypt_generic($this->dec_m,$data);
		mcrypt_generic_deinit($this->dec_m);
		return $dec;
	}

	private function des_cbc_encrypt(&$data,&$ctx) {
		if ((strlen($data) & 7) != 0) {
			throw new DESException('block length must be multiple of 8');
		}

		$iv0 = $ctx->iv0;
		$iv1 = $ctx->iv1;

		$result = "";

		for ($i = 0; $i < strlen($data); $i += 8) {
			$iv0 = ul_xor($iv0,substr($data,$i,4));
			$iv1 = ul_xor($iv1,substr($data,$i+4,4));
			$iv = $iv0.$iv1;
			$out = $this->des_encipher($iv,$ctx);
			$iv0 = substr($out,0,4);
			$iv1 = substr($out,4,4);

			$result.= $out;
		}

		$ctx->iv0 = $iv0;
		$ctx->iv1 = $iv1;
		$data = $result;
	}

	private function des_cbc_decrypt (&$data,&$ctx) {
		if ((strlen($data) & 7) != 0) {
			throw new DESException('block length must be multiple of 8');
		}

		$iv0 = $ctx->iv0;
		$iv1 = $ctx->iv1;

		$result = "";

		for ($i = 0; $i < strlen($data); $i += 8) {
			$l = substr($data,$i,4);
			$r = substr($data,$i+4,4);
			$out = $this->des_decipher($l.$r,$ctx);
			$iv0 = ul_xor($iv0,substr($out,0,4));
			$iv1 = ul_xor($iv1,substr($out,4,4));
			$result.= $iv0.$iv1;
			$iv0 = $l;
			$iv1 = $r;
		}
		$ctx->iv0 = $iv0;
		$ctx->iv1 = $iv1;
		$data = $result;
	}

	private function des_3cbc_encrypt(&$data,&$ctxs) {
		$this->des_cbc_encrypt($data,$ctxs[0]);
		$this->des_cbc_decrypt($data,$ctxs[1]);
		$this->des_cbc_encrypt($data,$ctxs[2]);
	}

	private function des_3cbc_decrypt(&$data,&$ctxs) {
		$this->des_cbc_decrypt($data,$ctxs[2]);
		$this->des_cbc_encrypt($data,$ctxs[1]);
		$this->des_cbc_decrypt($data,$ctxs[0]);
	}

	public function encrypt (&$data) {
		$this->des_3cbc_encrypt($data,$this->enc_ctxs);
	}

	public function decrypt (&$data) {
		$this->des_3cbc_decrypt($data,$this->dec_ctxs);
	}
}

class SSHException extends Exception {
}

function mpint_read ($data,&$pos) {
	$bits_binary = substr($data,$pos,2);
	$bits = ord($bits_binary[0])*256 + ord($bits_binary[1]);
	$pos+=2;

	$len = floor(($bits + 7) / 8);
	$result = "0";
	for ($i = 0; $i < $len; $i++) {
		$result = bcmul($result,256);
		$result = bcadd($result,ord(substr($data,$pos,1))); $pos++;
	}

	return $result;
}

function mpint_create($int) {
	$out = integer2blob($int);
	$bits = strlen($out)*8;
	$out = chr(($bits >> 8) & 255 ).chr($bits & 255).$out;
	return $out;
}

function bits_set($b) {
	$bits_set = 0;
	for ($i = 0; $i < strlen($b); $i++) {
		$byte = decbin(ord($b[$i]));
		for ($bit = 0; $bit < strlen($byte); $bit++) {
			if ($byte[$bit] == 1) {
				$bits_set++;
			}
		}
	}
	return $bits_set;
}

function hex_dump ($s) {
	$line_pos = 0;
	for ($i = 0; $i < strlen($s); $i++) {
		$line_pos++;
		echo str_pad(dechex(ord($s[$i])),2,'0',STR_PAD_LEFT)." ";
		if ($line_pos % 8 == 0 && $line_pos % 16 != 0) {
			echo "- ";
		}
		if ($line_pos % 16 == 0) {
			echo "| ".strtr(substr($s,$line_pos-16,16),"\r\n","  ")."\n";
		}

		if (($i == strlen($s) - 1) && ($line_pos % 16 != 0)) {
			echo str_repeat(" ",(($line_pos % 16 <= 8) ? ((16 - ($line_pos % 16))*3+2) : ((16 - ($line_pos % 16))*3) ))."| ".strtr(substr($s,$line_pos-($line_pos%16),16),"\r\n","  ")."\n";
		}
	}
	echo "\n";
}

function hex_dump_openssh ($s) {
	$line_pos = 0;
	for ($i = 0; $i < strlen($s); $i++) {
		$line_pos++;
		echo str_pad(dechex(ord($s[$i])),2,'0',STR_PAD_LEFT).":";

		if ($line_pos % 15 == 0) {
			echo "\n";
		}
	}
	echo "\n";
}

/**
 * support class for creating ssh packet
 *
 */
class SSH_packet_forge {
	private $mac_algo = 'hmac-md5';
	private $cipher = '3des-cbc'; private $cipher_block_size = 8; // bytes 64 bit for 3des-cbc

	private $key = null;

	private $packet = "";

	private $packet_seq_no = 0;

	private $packet_length; 	// uint32
	private $padding_length;	// byte
	private $data = "";			// byte[n1] n1 = packet_length - padding_length - 1
	private $padding = "";		// byte[n2] n2 = padding_length
	private $mac;				// byte[m] m = mac_length

	private $fd;

	private $tdes;

	public function __construct($fd) {
		$this->fd = $fd;
	}

	private function compute_packet_length () {
		$this->packet_length = strlen($this->data); 	// payload length
		$this->packet_length += 4; // CRC

		// compute padding
		$padding_growth = 8-(($this->packet_length) % 8);
		$this->padding_length = $padding_growth;
		$this->padding = str_repeat(chr(0),$this->padding_length);
	}

	public function fill_data ($data) {
		$this->data = $data;
	}


	public function build_packet() {
		$this->compute_packet_length();
		$packet = integer2dword($this->packet_length);

		$data = $this->padding;
		$data.= $this->data;
		$data.= crc($this->padding.$this->data);
		if ($this->key != null) {
			/**
			 * encryption is turned on
			 * encrypt data
			 */
			//			echo "enc. debug: plain: \n"; hex_dump($data);
			$this->tdes->encrypt($data);
			//			echo "enc. debug: encrypt: \n"; hex_dump($data);
		}
		$packet .= $data;

		$this->packet = $packet;

		if (!feof($this->fd)) {
			fputs($this->fd,$this->packet);
		} else {
			throw new SSHException('Not connected!');
		}

		$this->increment_seq_no();
	}

	public function get_packet() {
		return $this->packet;
	}

	private function increment_seq_no() {
		if ($this->packet_seq_no == pow(2,32)) {
			$this->packet_seq_no = 0;
		} else {
			$this->packet_seq_no++;
		}
	}

	public function set_key($key,&$tdes) {
		$this->key = $key;
		$this->tdes = $tdes;
	}
}

class SSH_packet_disolver {
	private $fd;

	private $content;
	private $packet_type;

	private $key = null;

	private $tdes;

	public function set_key($key,&$tdes) {
		$this->key = $key;
		$this->tdes = $tdes;
	}

	public function get_data() {
		return $this->content;
	}

	public function get_packet_type() {
		return ord($this->packet_type);
	}

	public function __construct($fd) {
		$this->fd = $fd;
	}

	private $raw_inbuff = null;

	public function read_packet () {
		/**
		 * handle raw read buffer
		 * make sure we know expected length of packet
		 */
		if ($this->raw_inbuff == null || strlen($this->raw_inbuff) < 4) {
			/**
			 * it's empty
			 */
			$this->raw_inbuff = fread($this->fd,4-strlen($this->raw_inbuff));
			if (strlen($this->raw_inbuff) < 4) {
				$this->content = "";
				return ;
			}
		}
		/**
		 * read into raw buffer whole packet
		 */
		if (dword2integer(substr($this->raw_inbuff,0,4)) > strlen($this->raw_inbuff) - 4 - (8-((dword2integer(substr($this->raw_inbuff,0,4))) % 8))) {
			$bytes_missing = dword2integer(substr($this->raw_inbuff,0,4)) - (strlen($this->raw_inbuff) - 4 - (8-((dword2integer(substr($this->raw_inbuff,0,4))) % 8)));
			$this->raw_inbuff.= fread($this->fd,$bytes_missing);
			if (dword2integer(substr($this->raw_inbuff,0,4)) > strlen($this->raw_inbuff) - 4) {
				/**
				 * packet is still not completed, try it next time
				 */
				$this->content = "";
				return ;
			} elseif (dword2integer(substr($this->raw_inbuff,0,4)) < strlen($this->raw_inbuff) - 4 - (8-((dword2integer(substr($this->raw_inbuff,0,4))) % 8))) {
				throw new SSHException('Something really wierd happend!!!');
			}
		}

		/**
		 * at this point $this->raw_inbuff should contain whole packet
		 */
		/**
		 * how long packet is
		 */
		$pos = 0;
		$data = substr($this->raw_inbuff,$pos,4); $pos += 4;
		$packet_len = dword2integer($data);

		$padding_len = 8-(($packet_len) % 8);
		if ($this->key == null) {
			/**
			 * read padding
			 */
			$padding = substr($this->raw_inbuff,$pos,$padding_len); $pos+=$padding_len;

			/**
			 * now packet type
			 */
			$packet_type = substr($this->raw_inbuff,$pos,1); $pos+=1;

			/**
			 * data
			 */
			$content = substr($this->raw_inbuff,$pos,$packet_len-5); $pos+=$packet_len-5;

			$crc = substr($this->raw_inbuff,$pos,4);$pos+=4;
		} else {
			/**
			 * encrypted data CRYPT
			 */
			$enc_content = substr($this->raw_inbuff,$pos,$packet_len+$padding_len);$pos+=$packet_len+$padding_len;

			/**
			 * decrypt packet
			 */
			$this->tdes->decrypt($enc_content);
			$dec_content = $enc_content;

			$xpos = 0;
			$padding = substr($dec_content,$xpos,$padding_len); $xpos += $padding_len;
			$packet_type = substr($dec_content,$xpos,1); $xpos += 1;
			$content = substr($dec_content,$xpos,$packet_len-5); $xpos += $packet_len-5;
			$crc = substr($dec_content,$xpos,4);

		}

		/**
		 * CRC - TODO: checks
		 */
		if ($crc != crc($padding.$packet_type.$content)) {
			echo "!!! Warning: Bad CRC in packet from server\n";
		}

		$this->packet_type = $packet_type;
		if ($packet_type == SSH_SMSG_EXITSTATUS) {
			throw new Exception('Connection closed by peer.');
		}
		$this->content = $content;

		/**
		 * null raw read buffer
		 */
		$this->raw_inbuff = null;
	}

}

define ('SSH_VERSION','0.1');

define ('SSH_SMSG_PUBLIC_KEY',2);
define ('SSH_SMSG_SUCCESS',14);
define ('SSH_SMSG_FAILURE',15);

define ('SSH_CMSG_SESSION_KEY',3);
define ('SSH_CMSG_USER',4);
define ('SSH_CMSG_AUTH_PASSWORD',9);
define ('SSH_CMSG_REQUEST_PTY',10);
define ('SSH_CMSG_EXEC_SHELL',12);
define ('SSH_CMSG_STDIN_DATA',16);
define ('SSH_CMSG_EOF',19);

define ('SSH_SMSG_STDOUT_DATA',17);
define ('SSH_SMSG_STDERR_DATA',18);
define ('SSH_SMSG_EXITSTATUS',20);

define('SSH_CIPHER_3DES',3);

class SSH_in_PHP {
	/**
	 * host to which we want to connect
	 *
	 * @var string
	 */
	private $host;
	/**
	 * port to which we are connected
	 *
	 * @var integer
	 */
	private $port;

	/**
	 * file descriptor of our ssh connection
	 *
	 * @var integer
	 */
	private $fd;

	private $ssh_version;
	private $ssh_server;

	/**
	 * packet forge class
	 *
	 * @var SSH_packet_forge
	 */
	private $packet_forge;

	/**
	 * packet disolver class
	 *
	 * @var SSH_packet_disolver
	 */	
	private $packet_disolver;

	private $session_id;

	private $session_key;

	private $login;
	private $passwd;

	private $logged = false;

	private $inbuffer, $outbuffer;

	/**
	 * constructor, setup all important variables
	 *
	 * @param string $host
	 * @param integer $port
	 */

	public function __construct($host,$port=22) {
		$this->host = $host;
		$this->port = $port;
	}

	/**
	 * connect to the specified host and do all important work
	 * that is needed after succesfull connection
	 *
	 */
	public function connect ($login,$passwd) {
		/**
		 * open connection to host
		 */
		$this->fd = fsockopen($this->host,$this->port,$errno,$errstr);
		if (!$this->fd) {
			/**
			 * if connection was unsuccesfull, throw an exception
			 */
			throw new SSHException($errstr,$errno);
		}

		/**
		 * remember login and password
		 */
		$this->login = $login;
		$this->passwd = $passwd;

		/**
		 * create our packet forge
		 */
		$this->packet_forge = new SSH_packet_forge($this->fd);
		$this->packet_disolver = new SSH_packet_disolver($this->fd);

		/**
		 * get info about ssh on the other side
		 */
		$this->connect_get_peer_info();
		$this->connect_send_our_info();
		//		$this->negotiate_algo();
		//		$this->kex();
		$this->ex_pub_key();
		try {
			$this->login();
		} catch (Exception $e) {
			$this->disconnect();
			throw new SSHException('Unable to authenticate - disconnecting!');
		}
		try {
			$this->req_pty_and_shell();
		} catch (Exception $e) {
			$this->disconnect();
			throw new SSHException($e->getMessage());
		}
		/**
		 * set nonblocking mode for unlimited reading
		 */
		$this->set_non_blocking();
	}

	private function set_non_blocking() {
		socket_set_blocking($this->fd,false);
	}

	private function read_update_inbuff() {
		$this->packet_disolver->read_packet();
		if (($this->packet_disolver->get_packet_type() == SSH_SMSG_STDOUT_DATA) ||
		($this->packet_disolver->get_packet_type() == SSH_SMSG_STDOUT_DATA)) {
			$newdata = $this->packet_disolver->get_data();
			if (strlen($newdata) > 0) {
				$str_len = dword2integer(substr($newdata,0,4));
				$this->inbuffer .= substr($newdata,4,$str_len);
			}
		} else {
			//			throw new SSHException('Unknown data packet type: '.$this->packet_disolver->get_packet_type());
		}
	}

	public function read($max_len = null) {
		if (!feof($this->fd)) {
			if ($this->logged) {
				$this->read_update_inbuff();
				if ($max_len != null && $max_len < strlen($this->inbuffer)) {
					$ret = substr($this->inbuffer,0,$max_len);
					$this->inbuffer = substr($this->inbuffer,$max_len,strlen($this->inbuffer)-$max_len);
				} else {
					$ret = $this->inbuffer;
					$this->inbuffer = "";
				}
				return $ret;

			} else {
				throw new SSHException('You need to login first before reading and writing data');
			}
		} else {
			throw new SSHException('Disconnected!');
		}
	}

	public function write($data) {
		if (!feof($this->fd)) {
			if ($this->logged) {
				$content = chr(SSH_CMSG_STDIN_DATA);
				$content.=integer2dword(strlen($data));
				$content.=$data;
				$this->packet_forge->fill_data($content);
				$this->packet_forge->build_packet();
			} else {
				throw new SSHException('You need to login first before reading and writing data');
			}
		} else {
			throw new SSHException('Disconnected!');
		}
	}

	private function req_pty_and_shell() {
		/**
		 * request pty vt100 80x24, that supports really nothing ;))
		 */
		$content = chr(SSH_CMSG_REQUEST_PTY);
		$term_type = "vt100";
		$content.= integer2dword(strlen($term_type));
		$content.= $term_type;
		$content.= integer2dword(24); // height
		$content.= integer2dword(80); // width
		$content.= integer2dword(0); // graph. height
		$content.= integer2dword(0); // graph. width
		$content.= chr(0); // terminal modes, we support nothing, we pass end of args immediatly

		$this->packet_forge->fill_data($content);
		$this->packet_forge->build_packet();

		$this->packet_disolver->read_packet();
		if ($this->packet_disolver->get_packet_type() != SSH_SMSG_SUCCESS) {
			throw new Exception('Unable to allocate pty - disconnecting!');
		}

		/**
		 * request interactive shell
		 */
		$content = chr(SSH_CMSG_EXEC_SHELL);
		$this->packet_forge->fill_data($content);
		$this->packet_forge->build_packet();
		if ($this->packet_disolver->get_packet_type() != SSH_SMSG_SUCCESS) {
			throw new Exception('Unable to start interactive shell - disconnecting!');
		}
	}

	private function login() {
		$content = chr(SSH_CMSG_USER);
		$content.= integer2dword(strlen($this->login));
		$content.= $this->login;
		$this->packet_forge->fill_data($content);
		$this->packet_forge->build_packet();

		$this->packet_disolver->read_packet();
		if ($this->packet_disolver->get_packet_type() == SSH_SMSG_SUCCESS) {
			echo "login OK, no pass needed"; flush();
		} elseif ($this->packet_disolver->get_packet_type() == SSH_SMSG_FAILURE) {
			/**
			 * maybe user is ok, but needs passwd
			 * try to send passwd
			 */
			$content = chr(SSH_CMSG_AUTH_PASSWORD);
			$content.= integer2dword(strlen($this->passwd));
			$content.= $this->passwd;
			$this->packet_forge->fill_data($content);
			$this->packet_forge->build_packet();

			/**
			 * read reply
			 */
			$this->packet_disolver->read_packet();
			if ($this->packet_disolver->get_packet_type() == SSH_SMSG_SUCCESS) {
				echo "login OK\n";
				$this->logged = true;
				$this->inbuffer = $this->outbuffer = "";
			} else {
				throw new Exception('Password auth rejected!');
			}
		} else {
			throw new Exception("Bad response, expecting SSH_MSG_SUCCESS or SSH_SMSG_FAILURE");
		}
	}

	/**
	 * recv server and host public keys
	 * exchange session key
	 * begin encryption
	 */
	private function ex_pub_key() {
		$this->packet_disolver->read_packet();
		if ($this->packet_disolver->get_packet_type() != SSH_SMSG_PUBLIC_KEY) {
			throw new SSHException('Expected SSH_SMSG_PUBLIC_KEY!');
		}
		/**
		 * 8 bytes      anti_spoofing_cookie  
		 * 32-bit int   server_key_bits  
		 * mp-int       server_key_public_exponent  
		 * mp-int       server_key_public_modulus  
		 * 32-bit int   host_key_bits  
		 * mp-int       host_key_public_exponent  
		 * mp-int       host_key_public_modulus  
		 * 32-bit int   protocol_flags  
		 * 32-bit int   supported_ciphers_mask  
		 * 32-bit int   supported_authentications_mask  
		*/
		$content = $this->packet_disolver->get_data();

		$pos = 0;
		$cookie = substr($content,$pos,8); $pos += 8;

		$server_key_bits = dword2integer(substr($content,$pos,4)); $pos += 4;
		$server_key_public_exponent = mpint_read($content,$pos);
		$server_key_public_modulus = mpint_read($content,$pos);

		$host_key_bits = dword2integer(substr($content,$pos,4)); $pos += 4;
		$host_key_public_exponent = mpint_read($content,$pos);
		$host_key_public_modulus = mpint_read($content,$pos);

		$protocol_flags = dword2integer(substr($content,$pos,4)); $pos += 4;
		$supported_ciphers_mask = dword2integer(substr($content,$pos,4)); $pos += 4;
		$supported_authentications_mask = dword2integer(substr($content,$pos,4)); $pos += 4;

		/**
		 * compute session_id
		 */
		$session_id = md5(integer2blob($host_key_public_modulus).integer2blob($server_key_public_modulus).$cookie,true);
		$this->session_id = $session_id;

		/**
		 * generate session key
		 * The key is a 256 bit
	 	 * random number, interpreted as a 32-byte key, with the least
	 	 * significant 8 bits being the first byte of the key
		 */
		$rnd = "";
		for ($i = 0; $i < 32; $i++) {
			$rnd .= chr(rand(0,255));
		}
		$this->session_key = $rnd;
		/*
		* According to the protocol spec, the first byte of the session key
		* is the highest byte of the integer.  The session key is xored with
		* the first 16 bytes of the session id.
		*/
		$key = $this->session_key;
		for ($i = 0; $i < 16; $i++) {
			$key[$i] = $key[$i] ^ $this->session_id[$i];
		}

		/*
		* Encrypt the integer using the public key and host key of the
		* server (key with smaller modulus first).
		*/
		if (bccomp($server_key_public_modulus,$host_key_public_modulus) < 0) {
			/**
			 * host key has larger modulus
			 */
			$crypted = rsacrypt(blob2integer($key),$server_key_public_exponent,$server_key_public_modulus);
			$crypted = rsacrypt($crypted,$host_key_public_exponent,$host_key_public_modulus);
		} else {
			/**
			 * host key has smaller modulus
			 */
			$crypted = rsacrypt(blob2integer($key),$host_key_public_exponent,$host_key_public_modulus);
			$crypted = rsacrypt($crypted,$server_key_public_exponent,$server_key_public_modulus);
		}

		/**
		 * reply with session key
		 *
		 * 1 byte       cipher_type  
		 * 8 bytes      anti_spoofing_cookie  
		 * mp-int       double encrypted session key  
		 * 32-bit int   protocol_flags 
		 */
		$data = chr(SSH_CMSG_SESSION_KEY);
		$data.= chr(SSH_CIPHER_3DES);
		$data.= $cookie;
		/**
		 * double encrypted session_key
		 */
		$data .= mpint_create($crypted);

		$data .= integer2dword(0);		// protocol flags - we do not support any additional features ;))

		$this->packet_forge->fill_data($data);
		$this->packet_forge->build_packet();

		/**
		  * CRYPTOOOO
		  */
		$tri = new triple_des();
		$tri->set_keys($this->session_key);

		$this->packet_disolver->set_key($this->session_key,$tri);
		$this->packet_disolver->read_packet();

		$this->packet_forge->set_key($this->session_key,$tri);

		if ($this->packet_disolver->get_packet_type() == SSH_SMSG_SUCCESS) {
			echo "crypto engine started... let's rock!!!\n"; flush();
		} else {
			throw new Exception("fatal: something bad with crypto engine happend! Dying peacefully... peace, love, flowers...");
		}
	}

	private function connect_send_our_info() {
		/**
		 * if connection is estabilished
		 */
		if (!feof($this->fd)) {
			fputs($this->fd,"SSH-1.5-phpSSH_".SSH_VERSION."\r\n",255);
		} else {
			throw new SSHException('Not connected!');
		}
	}
	/**
	 * gets peer information like ssh version, protocol version etc
	 *
	 */
	private function connect_get_peer_info() {
		/**
		 * if connection is estabilished
		 */
		if (!feof($this->fd)) {
			/**
			 * read initial line
			 */
			$init_line = fgets($this->fd,255);
			/**
			 * and parse it
			 */
			if (!ereg("^SSH\-([0-9\.]+)\-([[:print:]]+)",$init_line,$parts)) {
				throw new SSHException('Not an SSH server on the other side.');
			}
			/**
			 * fill in server info variables
			 */
			$this->ssh_version = $parts[1];
			$this->ssh_server = $parts[2];
			/**
			 * and check whether we support this server
			 */
			if (substr($this->ssh_version,0,1) != "1") {
				throw new SSHException("SSH version {$this->ssh_version} is not supported!");
			}
		} else {
			throw new SSHException('Not connected!');
		}
	}

	/**
	 * close connection
	 *
	 */
	public function disconnect() {
		if ($this->fd) {
			if ($this->logged) {
				$this->packet_forge->fill_data(chr(SSH_CMSG_EOF));
				$this->packet_forge->build_packet();
			} else {
				throw new SSHException('You need to login first before reading and writing data');
			}
			fclose($this->fd);
		} else {
			throw new SSHException('You can\'t close unopened connection');
		}
	}

	public function get_server_ssh_version () {
		return $this->ssh_version;
	}

	public function get_server_soft() {
		return $this->ssh_server;
	}
}

?>
Return current item: SSH in PHP