sábado, 13 de março de 2010

Conectando com servidor remoto via SSH

Um dos padrões de projeto mais legais é o State, com ele, um objeto pode mudar seu comportamento em tempo de execução dependendo do estado corrente e cada implementação de estado preocupa-se apenas com o que deve fazer, deixando assim um código limpo e totalmente reutilizável.


state pattern

Cada ConcreteState implementa um comportamento específico permitindo, dessa forma, que novos comportamentos sejam anexados.

No caso de uma conexão SSH:


state




Nosso contexto inicia-se como SSHStateClosed, assim que executamos o método open() nosso estado passa a ser SSHStateEstabilished e podemos verificar a assinatura do host e autenticar; Assim que autenticado o estado passa à SSHStateListen para que possamos enviar comandos ao servidor remoto.

Como uma autenticação pode ser de várias formas, deixamos a implementação separada em um objeto específico, dessa forma, podemos autenticar com usuário e senha ou, se for o caso, utilizando certificados digitais (entre outras).

Um comando deveria ser implementado da mesma forma, porém, para simplificar as coisas, vamos trabalhar com strings mesmo:





com/ssh/auth/SSHAuthenticatePassword.php
<?php
/**
 * @author   João Batista Neto
 * @package  ssh
 * @category ssh, state, design pattern
 */
namespace com\ssh\auth;

/**
 * @see ISSHAuthentication
 */
use com\ssh\interfaces\ISSHAuthentication;

/**
 * Implementa uma autenticação utilizando nome de usuário e senha
 * @author   João Batista Neto
 * @final
 * @package  ssh
 * @category ssh, state, authentication, design pattern
 */
final class SSHAuthenticatePassword implements ISSHAuthentication {
        /**
         * @var string
         */
        private $user;
 
        /**
         * @var string
         */
        private $pswd;
 
        /**
         * Constroi o objeto de autenticação
         * @param string $user
         * @param string $pswd
         */
        public function __construct( $user , $pswd ){
                $this->user =& $user;
                $this->pswd =& $pswd;
        }
 
        /**
         * Efetua a autenticação
         * @param resource $resource
         * @throws InvalidArgumentException se o recurso não for válido
         */
        public function authenticate( &$resource ){
                if ( is_resource( $resource ) ){
                        return \ssh2_auth_password( $resource , $this->user , $this->pswd );
                } else {
                        throw new \InvalidArgumentException( 'Recurso inválido.' );
                }
 
                return false;
        }
 
        /**
         * Recupera o nome do usuário
         * @return string
         */
        public function getUser(){
                return $this->user;
        }
}



com/ssh/interfaces/AbstractSSHState.php
<?php
/**
 * @author     João Batista Neto
 * @package    ssh
 * @subpackage interfaces
 * @category   ssh, state, design pattern
 */
namespace com\ssh\interfaces;

/**
 * Interface para um estado de conexão
 * @abstract
 * @author     João Batista Neto
 * @package    ssh
 * @subpackage interfaces
 * @category   sh, state, design pattern
 */
abstract class AbstractSSHState implements ISSHState {
        /**
         * @var resource
         */
        protected $resource;
 
        /**
         * Autentica o usuário
         * @param ISSHAuthentication $auth
         * @param ISSHConnection $context
         * @return boolean
         * @throws BadMethodCallException se o estado não implementar o método authenticate
         */
        public function authenticate( ISSHAuthentication $auth , ISSHConnection $context ){
                throw new \BadMethodCallException( sprintf( '%s não implementa o método %s' , get_class( $this ) , __METHOD__ ) );
        }
 
        /**
         * Executa um comando no servidor remoto
         * @param string $command
         * @param ISSHConnection $context
         * @return boolean
         * @throws BadMethodCallException se o estado não implementar o método execute
         */
        public function execute( $command , ISSHConnection $context ){
                throw new \BadMethodCallException( sprintf( '%s não implementa o método %s' , get_class( $this ) , __METHOD__ ) );
        }
 
        /**
         * Recupera a fingerprint do servidor
         * @param ISSHConnection $context
         * @return string
         * @throws BadMethodCallException se o estado não implementar o método execute
         */
        public function getFingerprint( ISSHConnection $context ){
                throw new \BadMethodCallException( sprintf( '%s não implementa o método %s' , get_class( $this ) , __METHOD__ ) );
        }
 
        /**
         * Abre uma nova conexão
         * @param string $host
         * @param integer $port
         * @param ISSHConnection $context
         * @return boolean
         * @throws BadMethodCallException se o estado não implementar o método open
         */
        public function open( $host , $port = 22 , ISSHConnection $context ){
                throw new \BadMethodCallException( sprintf( '%s não implementa o método %s' , get_class( $this ) , __METHOD__ ) );
        }
 
        /**
         * Define o recurso da conexão
         * @param resource $resource
         * @throws InvalidArgumentException se o recurso não for válido
         */
        public function setResource( &$resource ){
                if ( !is_resource( $resource ) ){
                        throw new \InvalidArgumentException( 'Recurso inválido.' );
                } else {
                        $this->resource =& $resource;
                }
        }
}



com/ssh/interfaces/ISSHAuthentication.php
<?php
/**
 * @author     João Batista Neto
 * @package    ssh
 * @subpackage interfaces
 * @category   ssh, state, design pattern
 */
namespace com\ssh\interfaces;

/**
 * Interface para autenticação SSH
 * @author     João Batista Neto
 * @package    ssh
 * @subpackage interfaces
 * @category   ssh, state, design pattern
 */
interface ISSHAuthentication {
        /**
         * Efetua a autenticação do usuário
         * @param resource $resource
         */
        public function authenticate( &$resource );
 
        /**
         * Recupera o nome do usuário
         * @return string
         */
        public function getUser();
}



com/ssh/interfaces/ISSHConnection.php
<?php
/**
 * @author     João Batista Neto
 * @package    ssh
 * @subpackage interfaces
 * @category   ssh, state, design pattern
 */
namespace com\ssh\interfaces;

/**
 * Interface para uma conexão SSH
 * @author     João Batista Neto
 * @package    ssh
 * @subpackage interfaces
 * @category   ssh, state, design pattern
 */
interface ISSHConnection {
        /**
         * Autentica um usuário
         * @param ISSHAuthentication $auth
         * @return boolean
         */
        public function authenticate( ISSHAuthentication $auth );
 
        /**
         * Executa um comando no servidor remoto
         * @param string $command
         * @return boolean
         */
        public function execute( $command );
 
        /**
         * Recupera a fingerprint do servidor
         * @return string
         */
        public function getFingerprint();
 
        /**
         * Abre uma nova conexão
         * @param stirng $host
         * @param integer $port
         * @return boolean
         */
        public function open( $host , $port );
 
        /**
         * Modifica o estado da conexão
         * @param ISSHState $state
         */
        public function changeState( ISSHState $state );
}

com/ssh/interfaces/ISSHState.php
<?php
/**
 * @author     João Batista Neto
 * @package    ssh
 * @subpackage interfaces
 * @category   ssh, state, design pattern
 */
namespace com\ssh\interfaces;

/**
 * Interface para um estado de conexão
 * @author     João Batista Neto
 * @package    ssh
 * @subpackage interfaces
 * @category   ssh, state, design pattern
 */
interface ISSHState {
        /**
         * Autentica o usuário
         * @param ISSHAuthentication $auth
         * @param ISSHConnection $context
         */
        public function authenticate( ISSHAuthentication $auth , ISSHConnection $context );
 
        /**
         * Executa um comando no servidor remoto
         * @param string $command
         * @param ISSHConnection $context
         */
        public function execute( $command , ISSHConnection $context );
 
        /**
         * Recupera a fingerprint do servidor
         * @param ISSHConnection $context
         * @return string
         */
        public function getFingerprint( ISSHConnection $context );
 
        /**
         * Abre uma nova conexão
         * @param string $host
         * @param integer $port
         * @param ISSHConnection $context
         */
        public function open( $host , $port = 22 , ISSHConnection $context );
 
        /**
         * Define o recurso da conexão
         * @param resource $resource
         */
        public function setResource( &$resource );
}


com/ssh/state/SSHStateClosed.php
<?php
/**
 * @author   João Batista Neto
 * @package  ssh
 * @category ssh, state, design pattern
 */
namespace com\ssh\state;

/**
 * @see AbstractSSHState
 */
use com\ssh\interfaces\AbstractSSHState;

/**
 * @see ISSHConnection
 */
use com\ssh\interfaces\ISSHConnection;

/**
 * Implementa o estado para uma conexão fechada
 * @author   João Batista Neto
 * @final
 * @package  ssh
 * @category ssh, state, design pattern
 */
final class SSHStateClosed extends AbstractSSHState {
        /**
         * @staticvar
         * @var ISSHConnection
         */
        static private $context;
 
        /**
         * Abre uma nova conexão
         * @param string $host
         * @param integer $port
         * @param ISSHConnection $context
         * @return boolean
         * @throws RuntimeException se não for possível estabelecer a conexão
         * @uses SSHStateEstabilished
         */
        public function open( $host , $port = 22 , ISSHConnection $context ){
                $resource = \ssh2_connect( $host , $port , array(
                        'disconnect'    => array( 'SSHStateClosed' , 'disconnect' )
                ) );
 
                if ( !is_resource( $resource ) ){
                        throw new \RuntimeException( 'Não foi possível estabelecer a conexão.' );
                } else {
                        $estabilished = new SSHStateEstabilished();
                        $estabilished->setResource( $resource );
 
                        self::$context =& $context;
                        self::$context->changeState( $estabilished );
                }
 
                return true;
        }
 
        /**
         * Muda o estado da conexão para SSHStateClosed caso o servidor envie um disconnect
         * @static
         */
        static public function disconnect(){
                self::$context->changeState( new SSHStateClosed() );
        }
}

com/ssh/state/SSHStateEstabilished.php
<?php
/**
 * @author   João Batista Neto
 * @package  ssh
 * @category ssh, state, design pattern
 */
namespace com\ssh\state;

/**
 * @see AbstractSSHState
 */
use com\ssh\interfaces\AbstractSSHState;

/**
 * @see ISSHConnection
 */
use com\ssh\interfaces\ISSHConnection;

/**
 * @see ISSHAuthentication
 */
use com\ssh\interfaces\ISSHAuthentication;

/**
 * Implementa o estado para uma conexão estabelecida
 * @author   João Batista Neto
 * @final
 * @package  ssh
 * @category ssh, state, design pattern
 */
final class SSHStateEstabilished extends AbstractSSHState {
        /**
         * Efetua a autenticação do usuário
         * @param ISSHAuthentication $auth
         * @param ISSHConnection $context
         * @return boolean
         * @throws RuntimeException se não for possível autenticar o usuário
         * @uses SSHStateAuthenticated
         */
        public function authenticate( ISSHAuthentication $auth , ISSHConnection $context ){
                if ( !$auth->authenticate( $this->resource ) ){
                        throw new RuntimeException( sprintf( 'Não foi possível autenticar o usuário %s.' , $auth->getUser() ) );
                } else {
                        $authenticated = new SSHStateListen();
                        $authenticated->setResource( $this->resource );
                        $context->changeState( $authenticated );
                }
 
                return true;
        }
 
        /**
         * Recupera o hash da chave do servidor da conexão ativa
         * @return string
         * @throws LogicException se o estado não implementar o método execute
         */
        public function getFingerprint( ISSHConnection $context ){
                return \ssh2_fingerprint( $this->resource , \SSH2_FINGERPRINT_MD5 | \SSH2_FINGERPRINT_HEX );
        }
}

com/ssh/state/SSHStateListen.php
<?php
/**
 * @author              João Batista Neto
 * @package             ssh
 * @category    ssh, state, design pattern
 */
namespace com\ssh\state;

/**
 * @see AbstractSSHState
 */
use com\ssh\interfaces\AbstractSSHState;

/**
 * @see ISSHConnection
 */
use com\ssh\interfaces\ISSHConnection;

/**
 * Implementa o estado para uma conexão autenticada
 * @author   João Batista Neto
 * @final
 * @package  ssh
 * @category ssh, state, design pattern
 */
final class SSHStateListen extends AbstractSSHState {
        /**
         * @var resource
         */
        private $shell;
 
        /**
         * Executa um comando no servidor remoto
         * @param string $command
         * @param ISSHConnection $context
         * @return string A saída do servidor remoto
         * @throws RuntimeException se não for possível executar o comando
         */
        public function execute( $command , ISSHConnection $context ){
                $ret = null;
 
                $stream = \ssh2_exec( $this->resource , $command );
 
                if ( is_resource( $stream ) ){
                        \stream_set_blocking( $stream , true );
 
                        while ( ( $line = \fgets( $stream , 4096 ) ) !== false ){
                                $ret .= $line;
                        }
                } else {
                        throw new \RuntimeException( 'Não foi possível executar o comando.' );
                }
 
                return $ret;
        }
}

com/ssh/SSHConnection.php
<?php
/**
 * @author   João Batista Neto
 * @package  ssh
 * @category ssh, state, design pattern
 */
namespace com\ssh;

/**
 * @see ISSHConnection
 */
use com\ssh\interfaces\ISSHConnection;

/**
 * @see ISSHAuthentication
 */
use com\ssh\interfaces\ISSHAuthentication;

/**
 * @see ISSHState
 */
use com\ssh\interfaces\ISSHState;

/**
 * @see SSHStateClosed
 */
use com\ssh\state\SSHStateClosed;

/**
 * Implementa uma conexão com um servidor remoto via SSH
 * @author   João Batista Neto
 * @final
 * @package  ssh
 * @category ssh, state, design pattern
 */
class SSHConnection implements ISSHConnection {
        /**
         * @var ISSHState
         */
        private $state;
 
        /**
         * Cria o objeto de conexão
         */
        public function __construct(){
                $this->state = new SSHStateClosed();
        }
 
        /**
         * Faz a autenticação no servidor
         * @param ISSHAuthentication $auth
         * @return boolean
         */
        public function authenticate( ISSHAuthentication $auth ){
                return $this->state->authenticate( $auth , $this );
        }
 
        /**
         * Executa um comando no servidor
         * @param string $command
         * @return boolean
         */
        public function execute( $command ){
                return $this->state->execute( $command , $this );
        }
 
        /**
         * Recupera a fingerprint do servidor
         * @return string
         */
        public function getFingerprint(){
                return $this->state->getFingerprint( $this );
        }
 
        /**
         * Abre uma conexão com um servidor remoto
         * @param string $host
         * @param integer $port
         * @return boolean
         */
        public function open( $host , $port = 22 ){
                return $this->state->open( $host , $port , $this );
        }
 
        /**
         * Modifica o estado da conexão
         * @param ISSHState $state
         */
        public function changeState( ISSHState $state ){
                $this->state =& $state;
        }
}


Agora, para conectar em um servidor remoto via PHP é simples:


<?php
use com\ssh\SSHConnection;
use com\ssh\auth\SSHAuthenticatePassword;

$teste = new SSHConnection();
$teste->open( '127.0.0.1' );
$teste->authenticate( new SSHAuthenticatePassword( 'usuario' , 'senha' ) );
echo $teste
->execute( 'ls -l /var/www/html/xxx/com/ssh' );


No meu caso, a saída foi:

total 16
drwxr-xr-x. 3 neto apache 4096 Mar 11 18:18 auth
drwxr-xr-x. 3 neto apache 4096 Mar 11 18:18 interfaces
-rw-r--r--. 1 neto apache 1681 Mar 11 18:17 SSHConnection.php
drwxr-xr-x. 3 neto apache 4096 Mar 12 06:35 state

Nenhum comentário:

Postar um comentário