Poor man's telnet program

Submitted by Kamal Wickramanayake on July 9, 2010 - 12:13

About 10 years ago I wanted to write a telnet client in PHP. My need was to login, execute some commands and logout. I was lazy to read the telnet protocol details. Hence I used the following approach to do it which worked for my purpose. Let your programming language be Java, C, C++, PHP, C# or anything else. It needs to have a TCP socket creation facility (TCP networking).

Here are the steps:

  1. Have the telnet server running.
  2. Use a network traffic monitoring tool running (or use a proxy).
  3. Use a commonly availale telnet client and login to the server.
  4. Stop the network traffic monitoring tool and identify the client/server conversation byte streams. Identify the different chunks of data sent to the server (by the client) and to the client (by the server).
  5. Write your telnet client (in your preferred programming language) to send the same chunks of bytes to the server and implement the conversation.

Special Note: This approach is not that great if you think of using your new telnet client with different telnet servers (which may have different telnet session negotiation mechanism) or if the telnet server software is updated and server configurations are changed where the same chunks of bytes may not work after such changes. If such a general purpose telnet client is needed and you want to write from the scratch, you need read the telnet protocol details well.

Here's the telnet client I wrote in PHP. Look at the connect() function. You see that a new TCP socket is created and chunks of bytes are sent to the server and read from the server. That is in fact the handshaking taking place. I didn't go about understanding the exact meaning of these messages. I just wanted to get the login prompt. And then I continuted with executing commands.

<?php

class Telnet {
    var $host;
    var $port;
    var $login;
    var $connection;
    var $errno;
    var $errstr;
    var $bashPrompt = "apacheTelnetPrompt";
    var $timeout = 30; // Set this to 30
 
    function Telnet($host,$port) {
    	$this->host = $host;
	$this->port = $port;
    }

    function connect() {
	$this->connection =fsockopen($this->host, $this->port, $this->errno, $this->errstr, 30);
	fread($this->connection, 3);
	$send = array(255,251,24);
	$this->sendBytes($send);
	fread($this->connection, 9);

	$send = array(255,251,32,255,252,35,255,251,39);
	$this->sendBytes($send);
	fread($this->connection, 18);

	$send = array(255, 250, 32, 0, 51, 56, 52, 48, 48, 44, 51, 56,
			 52, 48, 48, 255, 240, 255, 250, 39, 0, 255, 240,
			 255, 250, 24, 0, 76, 73, 78, 85, 88, 255, 240);
	$this->sendBytes($send);
	fread($this->connection, 15);

	$send = array(255, 253, 3, 255, 252, 1, 255, 251, 31, 255, 250, 31,
			0, 80, 0, 25, 255, 240, 255, 253, 5, 255, 251, 33);
	$this->sendBytes($send);

// Get the login prompt
	if($this->expect("login: "))
	    return TRUE;
	else
	    return FALSE;
    }

// Loing method
// Accepts two strings (login name , password)
    function login($login, $password) {
	$this->login = $login;

	$send = array(255, 253, 1);
	$this->sendBytes($send);
// Send the login name here
	$this->send($this->login."\r".chr(0));
	
// Expect the Password prompt and send the password
	if(!$this->expect("Password: "))
	    return FALSE;

// Send the password
	$this->send($password."\r".chr(0));

// Expect the success in login
	if(!$this->expect("Last login: "))
	    return FALSE;

// Set the prompt
	$this->send("export PS1=$this->bashPrompt\r".chr(0));
// Read the echoed output
	if(!$this->expect("export PS1=$this->bashPrompt\r\n"))
	    return false;

// Expect the changed prompt
	if(!$this->expect($this->bashPrompt))
	    return FALSE;

// You are logged in, return true
	return true;
    }

// Accepted: an array of integers
    function sendBytes($send) {
	for($i = 0; $i < sizeof($send); $i++)
	    if(fwrite($this->connection, chr($send[$i])) == -1)
		return false;
	return true;
    }

    function expect($expected) {
	while(true) {
	    $read = $this->getCharWithTimeout($this->timeout);
	    if(!$read)
		return false;
	    if($read != $expected[0])
		continue;

	    $index = 1;
	    while($index < strlen($expected)) {
		$read = $this->getCharWithTimeout($this->timeout);
		if(!$read)
		    return false;
		if($read == $expected[$index])
		    $index++;
		else if($read == $expected[0])
		    $index = 1;
		else
		    continue 2;
	    }
	    return true;
	}
    }

// Send a string to the socket
// Accepted: the string to be sent
    function send($string) {
	if(fwrite($this->connection, $string) == -1)
	    return false;
	else
	    return true;
    }

    function getCharWithTimeout($timeout) {
	socket_set_blocking($this->connection, false);
	for($i = 0; $i < $timeout; $i++) {
	    $read = fgetc($this->connection);
	    // remove the line below
	    //echo $read;
	    if($read) {
		socket_set_blocking($this->connection, true);
		return $read;
	    } else
		sleep(1);
	}
	socket_set_blocking($this->connection, true);
	return false; 
    }

    function isBashPrompt($value) {
	if($value == $this->bashPrompt)
	    return TRUE;
	else
	    return FALSE;
    }

    function getExitStatus() {
	$this->flushInput();
	$this->send("echo $?\r".chr(0));
	$this->expect("echo $?\r\n"); // Read the echoed output
	$status = fgets($this->connection, 4096);
	return $status;
    }

// Reads all the content present at the input
    function flushInput() {
	$all;
	$info = socket_get_status($this->connection);
	for($i = 0; $i < $info['unread_bytes']; $i++)
	    $all .= fgetc($this->connection);
	return $all;
    }
	
    function logout() {
	$this->send("exit\r".chr(0));
	usleep(25000);
	$this->flushInput();
	fclose($this->connection);
    }
}
?>

What follows is the usage of the telnet client. This was used as a form submission handling script to get a new Linux account created after initializing the telnet session. What's in bold text is the usage of the above Telnet class.

<?php
/*

Telnet to localhost and create a new account.

Accepts the following HTTP POST variables:
    $adduserargs - arguments to adduser command
    $privilage_login - login name of the privilaged person who can create accounts
    $privilage_password
    $submit - form submitted

*/

$exitcode = 101;


// Match the pattern of $adduserargs
// Set $exitcode to 102 if cannot be matched
$pattern = ".*[;&|><`].*";
if(ereg($pattern, $adduserargs))
    $exitcode = 102;

if($submit && ($exitcode == 101)) {

    include('Telnet.php');

    // Telnet to the machine
    $t = new Telnet("localhost", 23);

    if($t->connect()) {
    	if($t->login($privilage_login, $privilage_password)) {
	    // Gain superuser privilage
	    $t->send("sudo -v\r".chr(0));
	    if($t->expect("Password:")) {
		$t->send("$privilage_password\r".chr(0));
		if($t->expect($t->bashPrompt)) {
	    	    // Send the command
	    	    $adduserargs = stripslashes($adduserargs);
	            $t->send("sudo /usr/sbin/adduser $adduserargs\r".chr(0));
	    	    // If bash prompt cannot be seen, break the command
    	            if(!$t->expect($t->bashPrompt)) {
	    		$t->send(chr(3));
	    	    	fread($t->connection,3);
    	    	    }
		} else {
	    	    $t->send(chr(3));
	    	    fread($t->connection,3);
		}
	    } else {
	        $t->send(chr(3));
	        fread($t->connection,3);
	    }
	    
	    $exitcode = (int) $t->getExitStatus();
	    // Kill the sudo session
	    $t->send("sudo -K\r".chr(0));
	    $t->flushInput();
		
	    $t->logout();
    	} // if logged in ends
    } // if Connected ends
} // if submitted ends

echo $exitcode;