• Minecraft server+Telnet addon; Telnet class for PHP isn't getting the buffer
    6 replies, posted
I have setup a Minecraft server with Bukkit and am using the [url=http://forums.bukkit.org/threads/admn-mctelnet-v1-2-1-remote-console-access-using-telnet-531.5299/]MCTerm[/url] addon. I've been able to query the server using [php] <html> <head> <title>Terminal</title> </head> <body> <pre><?php include('telnet.php'); $host = '127.0.0.1'; $port = '8765'; $user = 'console'; $pass = 'toast'; try{ $connection = new Telnet($host,$port,1); $connection->login($user,$pass); //hurdur echo $connection->exec('list'); }catch(Exception $e) { echo 'Error: '.$e; } ?></pre> </body> </html> [/php] and this class for telnet connections I've found from Google [php] <?php /** * Telnet class * * Used to execute remote commands via telnet connection * Usess sockets functions and fgetc() to process result * * All methods throw Exceptions on error * * Written by Dalibor Andzakovic <dali@swerve.co.nz> * Based on the code originally written by Marc Ennaji and extended by * Matthias Blaser <mb@adfinis.ch> */ class Telnet { private $host; private $port; private $timeout; private $socket = NULL; private $buffer = NULL; private $prompt; private $errno; private $errstr; private $NULL; private $DC1; private $WILL; private $WONT; private $DO; private $DONT; private $IAC; const TELNET_ERROR = FALSE; const TELNET_OK = TRUE; /** * Constructor. Initialises host, port and timeout parameters * defaults to localhost port 23 (standard telnet port) * * @param string $host Host name or IP addres * @param int $port TCP port number * @param int $timeout Connection timeout in seconds * @return void */ public function __construct($host = '127.0.0.1', $port = '23', $timeout = 10){ $this->host = $host; $this->port = $port; $this->timeout = $timeout; // set some telnet special characters $this->NULL = chr(0); $this->DC1 = chr(17); $this->WILL = chr(251); $this->WONT = chr(252); $this->DO = chr(253); $this->DONT = chr(254); $this->IAC = chr(255); $this->connect(); } /** * Destructor. Cleans up socket connection and command buffer * * @return void */ public function __destruct() { // cleanup resources $this->disconnect(); $this->buffer = NULL; } /** * Attempts connection to remote host. Returns TRUE if sucessful. * * @return boolean */ public function connect(){ // check if we need to convert host to IP if (!preg_match('/([0-9]{1,3}\\.){3,3}[0-9]{1,3}/', $this->host)) { $ip = gethostbyname($this->host); if($this->host == $ip){ throw new Exception("Cannot resolve $this->host"); } else{ $this->host = $ip; } } // attempt connection $this->socket = fsockopen($this->host, $this->port, $this->errno, $this->errstr, $this->timeout); if (!$this->socket){ throw new Exception("Cannot connect to $this->host on port $this->port"); } return self::TELNET_OK; } /** * Closes IP socket * * @return boolean */ public function disconnect(){ if ($this->socket){ if (! fclose($this->socket)){ throw new Exception("Error while closing telnet socket"); } $this->socket = NULL; } return self::TELNET_OK; } /** * Executes command and returns a string with result. * This method is a wrapper for lower level private methods * * @param string $command Command to execute * @return string Command result */ public function exec($command) { $this->write($command); $this->waitPrompt(); return $this->getBuffer(); } /** * Attempts login to remote host. * This method is a wrapper for lower level private methods and should be * modified to reflect telnet implementation details like login/password * and line prompts. Defaults to standard unix non-root prompts * * @param string $username Username * @param string $password Password * @return boolean */ public function login($username, $password) { try{ $this->setPrompt('Username:'); $this->waitPrompt(); $this->write($username); $this->setPrompt('Password:'); $this->waitPrompt(); $this->write($password); $this->setPrompt(':'); $this->waitPrompt(); } catch(Exception $e){ throw new Exception("Login failed."); } return self::TELNET_OK; } /** * Sets the string of characters to respond to. * This should be set to the last character of the command line prompt * * @param string $s String to respond to * @return boolean */ public function setPrompt($s = '$'){ $this->prompt = $s; return self::TELNET_OK; } /** * Gets character from the socket * * @return void */ private function getc() { return fgetc($this->socket); } /** * Clears internal command buffer * * @return void */ private function clearBuffer() { $this->buffer = ''; } /** * Reads characters from the socket and adds them to command buffer. * Handles telnet control characters. Stops when prompt is ecountered. * * @param string $prompt * @return boolean */ private function readTo($prompt){ if (!$this->socket){ throw new Exception("Telnet connection closed"); } // clear the buffer $this->clearBuffer(); do{ $c = $this->getc(); if ($c === false){ throw new Exception("Couldn't find the requested : '" . $prompt . "', it was not in the data returned from server : '" . $buf . "'"); } // Interpreted As Command if ($c == $this->IAC){ if ($this->negotiateTelnetOptions()){ continue; } } // append current char to global buffer $this->buffer .= $c; // we've encountered the prompt. Break out of the loop if ((substr($this->buffer, strlen($this->buffer) - strlen($prompt))) == $prompt){ return self::TELNET_OK; } } while($c != $this->NULL || $c != $this->DC1); } /** * Write command to a socket * * @param string $buffer Stuff to write to socket * @param boolean $addNewLine Default true, adds newline to the command * @return boolean */ private function write($buffer, $addNewLine = true){ if (!$this->socket){ throw new Exception("Telnet connection closed"); } // clear buffer from last command $this->clearBuffer(); if ($addNewLine == true){ $buffer .= "\n"; } if (!fwrite($this->socket, $buffer) < 0){ throw new Exception("Error writing to socket"); } return self::TELNET_OK; } /** * Returns the content of the command buffer * * @return string Content of the command buffer */ private function getBuffer(){ //HERP A DERP return trim($this->buffer); // cut last line (is always prompt) $buf = explode("\n", $this->buffer); unset($buf[count($buf)-1]); $buf = implode("\n",$buf); return trim($buf); } /** * Telnet control character magic * * @param string $command Character to check * @return boolean */ private function negotiateTelnetOptions(){ $c = $this->getc(); if ($c != $this->IAC){ if (($c == $this->DO) || ($c == $this->DONT)){ $opt = $this->getc(); fwrite($this->socket, $this->IAC . $this->WONT . $opt); } else if (($c == $this->WILL) || ($c == $this->WONT)) { $opt = $this->getc(); fwrite($this->socket, $this->IAC . $this->DONT . $opt); } else { throw new Exception('Error: unknown control character ' . ord($c )); } } else{ throw new Exception('Error: Something Wicked Happened'); } return self::TELNET_OK; } /** * Reads socket until prompt is encountered */ private function waitPrompt(){ return $this->readTo($this->prompt); } } ?> [/php] But the problem is that the server can get the command and execute it, but I'm not seeing the buffer or what the server responds with. I know it should work because sending commands through PuTTY works. I'd be grateful if you find what's wrong, or have a better class or addon I could use. Thanks.
Try using $e->getMessage() when you catch the exception, and see if an error is generated.
[QUOTE=deadeye536;28630462]Try using $e->getMessage() when you catch the exception, and see if an error is generated.[/QUOTE] But no exception is raised :saddowns:
Partial solution: On line 273, I see the comment "cut last line (is always prompt)". This caused problems because it would cut off the line. Now I only get one line. How can I make it loop until it gets everything? (like for long responses from help) Edit: I figured out why it sometimes cuts off. It looks for $prompt, which is by default ":", and that is problematic when the server responds with "Players online[u]:[/u] ...". I think I might have to rewrite the addon so that it disconnects the remote connection and just have php loop until the connection is disconnected, or have it put "DONE" at the end and use that. I can't think of any other way do this.
You're going about this the wrong way. Why don't you create a bukkit plugin that you can connect to via TCP, or use one like MineQuery?
[QUOTE=compwhizii;28643939]You're going about this the wrong way. Why don't you create a bukkit plugin that you can connect to via TCP, or use one like MineQuery?[/QUOTE] I'm not using Minequery because I want to send commands, and doesn't all mq do is get the players info? I'd prefer not having to make my own plugin since this already does it. My problem is just PHP being weird.
[QUOTE=cas97;28646154]I'm not using Minequery because I want to send commands, and doesn't all mq do is get the players info? I'd prefer not having to make my own plugin since this already does it. My problem is just PHP being weird.[/QUOTE] What info do you need? I will gladly make you a plugin.
Sorry, you need to Log In to post a reply to this thread.