mirror of
https://github.com/phpseclib/phpseclib.git
synced 2024-11-19 19:55:11 +00:00
Merge pull request #648 from terrafrost/agentforwarding-2.0-1
Agent Forwarding for 2.0 branch * terrafrost/agentforwarding-2.0-1: SSH2: missed a file in the merge removed unwarrented user_error preference isset over array_key_exists, return false on failure, break after return channel opened moved agent forwarding channel handling to filter method and reusing existing open channels to request forwarding removed stopSSHForwarding determining what failure to expect addresses low hanging fruit comments from terrafrost and bantu removed superfluous default case SSH agent forwarding implementation
This commit is contained in:
commit
28a2f0fc0c
@ -103,6 +103,7 @@ class SSH2
|
|||||||
const CHANNEL_EXEC = 0; // PuTTy uses 0x100
|
const CHANNEL_EXEC = 0; // PuTTy uses 0x100
|
||||||
const CHANNEL_SHELL = 1;
|
const CHANNEL_SHELL = 1;
|
||||||
const CHANNEL_SUBSYSTEM = 2;
|
const CHANNEL_SUBSYSTEM = 2;
|
||||||
|
const CHANNEL_AGENT_FORWARD = 3;
|
||||||
/**#@-*/
|
/**#@-*/
|
||||||
|
|
||||||
/**#@+
|
/**#@+
|
||||||
@ -835,6 +836,14 @@ class SSH2
|
|||||||
*/
|
*/
|
||||||
var $windowRows = 24;
|
var $windowRows = 24;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A System_SSH_Agent for use in the SSH2 Agent Forwarding scenario
|
||||||
|
*
|
||||||
|
* @var System_SSH_Agent
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
var $agent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default Constructor.
|
* Default Constructor.
|
||||||
*
|
*
|
||||||
@ -2055,6 +2064,7 @@ class SSH2
|
|||||||
*/
|
*/
|
||||||
function _ssh_agent_login($username, $agent)
|
function _ssh_agent_login($username, $agent)
|
||||||
{
|
{
|
||||||
|
$this->agent = $agent;
|
||||||
$keys = $agent->requestIdentities();
|
$keys = $agent->requestIdentities();
|
||||||
foreach ($keys as $key) {
|
foreach ($keys as $key) {
|
||||||
if ($this->_privatekey_login($username, $key)) {
|
if ($this->_privatekey_login($username, $key)) {
|
||||||
@ -2234,6 +2244,7 @@ class SSH2
|
|||||||
if (!$this->_send_binary_packet($packet)) {
|
if (!$this->_send_binary_packet($packet)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$response = $this->_get_binary_packet();
|
$response = $this->_get_binary_packet();
|
||||||
if ($response === false) {
|
if ($response === false) {
|
||||||
user_error('Connection closed by server');
|
user_error('Connection closed by server');
|
||||||
@ -2400,6 +2411,24 @@ class SSH2
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an available open channel
|
||||||
|
*
|
||||||
|
* @return Integer
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
function _get_open_channel()
|
||||||
|
{
|
||||||
|
$channel = self::CHANNEL_EXEC;
|
||||||
|
do {
|
||||||
|
if (isset($this->channel_status[$channel]) && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_OPEN) {
|
||||||
|
return $channel;
|
||||||
|
}
|
||||||
|
} while ($channel++ < self::CHANNEL_SUBSYSTEM);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the output of an interactive shell
|
* Returns the output of an interactive shell
|
||||||
*
|
*
|
||||||
@ -2758,18 +2787,41 @@ class SSH2
|
|||||||
case NET_SSH2_MSG_CHANNEL_OPEN: // see http://tools.ietf.org/html/rfc4254#section-5.1
|
case NET_SSH2_MSG_CHANNEL_OPEN: // see http://tools.ietf.org/html/rfc4254#section-5.1
|
||||||
$this->_string_shift($payload, 1);
|
$this->_string_shift($payload, 1);
|
||||||
extract(unpack('Nlength', $this->_string_shift($payload, 4)));
|
extract(unpack('Nlength', $this->_string_shift($payload, 4)));
|
||||||
$this->errors[] = 'SSH_MSG_CHANNEL_OPEN: ' . utf8_decode($this->_string_shift($payload, $length));
|
$data = $this->_string_shift($payload, $length);
|
||||||
|
|
||||||
$this->_string_shift($payload, 4); // skip over client channel
|
|
||||||
extract(unpack('Nserver_channel', $this->_string_shift($payload, 4)));
|
extract(unpack('Nserver_channel', $this->_string_shift($payload, 4)));
|
||||||
|
switch($data) {
|
||||||
|
case 'auth-agent':
|
||||||
|
case 'auth-agent@openssh.com':
|
||||||
|
if (isset($this->agent)) {
|
||||||
|
$new_channel = self::CHANNEL_AGENT_FORWARD;
|
||||||
|
|
||||||
|
extract(unpack('Nremote_window_size', $this->_string_shift($payload, 4)));
|
||||||
|
extract(unpack('Nremote_maximum_packet_size', $this->_string_shift($payload, 4)));
|
||||||
|
|
||||||
|
$this->packet_size_client_to_server[$new_channel] = $remote_window_size;
|
||||||
|
$this->window_size_server_to_client[$new_channel] = $remote_maximum_packet_size;
|
||||||
|
$this->window_size_client_to_server[$new_channel] = $this->window_size;
|
||||||
|
|
||||||
|
$packet_size = 0x4000;
|
||||||
|
|
||||||
|
$packet = pack('CN4',
|
||||||
|
NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, $server_channel, $new_channel, $packet_size, $packet_size);
|
||||||
|
|
||||||
|
$this->server_channels[$new_channel] = $server_channel;
|
||||||
|
$this->channel_status[$new_channel] = NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION;
|
||||||
|
if (!$this->_send_binary_packet($packet)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
$packet = pack('CN3a*Na*',
|
$packet = pack('CN3a*Na*',
|
||||||
NET_SSH2_MSG_REQUEST_FAILURE, $server_channel, NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED, 0, '', 0, '');
|
NET_SSH2_MSG_REQUEST_FAILURE, $server_channel, NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED, 0, '', 0, '');
|
||||||
|
|
||||||
if (!$this->_send_binary_packet($packet)) {
|
if (!$this->_send_binary_packet($packet)) {
|
||||||
return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
|
return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
$payload = $this->_get_binary_packet();
|
$payload = $this->_get_binary_packet();
|
||||||
break;
|
break;
|
||||||
case NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST:
|
case NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST:
|
||||||
@ -2906,8 +2958,16 @@ class SSH2
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
extract(unpack('Ctype/Nchannel', $this->_string_shift($response, 5)));
|
extract(unpack('Ctype', $this->_string_shift($response, 1)));
|
||||||
|
|
||||||
|
if ($type == NET_SSH2_MSG_CHANNEL_OPEN) {
|
||||||
|
extract(unpack('Nlength', $this->_string_shift($response, 4)));
|
||||||
|
} else {
|
||||||
|
extract(unpack('Nchannel', $this->_string_shift($response, 4)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// will not be setup yet on incoming channel open request
|
||||||
|
if (isset($channel) && isset($this->channel_status[$channel]) && isset($this->window_size_server_to_client[$channel])) {
|
||||||
$this->window_size_server_to_client[$channel]-= strlen($response);
|
$this->window_size_server_to_client[$channel]-= strlen($response);
|
||||||
|
|
||||||
// resize the window, if appropriate
|
// resize the window, if appropriate
|
||||||
@ -2929,7 +2989,9 @@ class SSH2
|
|||||||
$this->window_size_client_to_server[$channel] = $window_size;
|
$this->window_size_client_to_server[$channel] = $window_size;
|
||||||
$temp = unpack('Npacket_size_client_to_server', $this->_string_shift($response, 4));
|
$temp = unpack('Npacket_size_client_to_server', $this->_string_shift($response, 4));
|
||||||
$this->packet_size_client_to_server[$channel] = $temp['packet_size_client_to_server'];
|
$this->packet_size_client_to_server[$channel] = $temp['packet_size_client_to_server'];
|
||||||
return $client_channel == $channel ? true : $this->_get_channel_packet($client_channel, $skip_extended);
|
$result = $client_channel == $channel ? true : $this->_get_channel_packet($client_channel, $skip_extended);
|
||||||
|
$this->_on_channel_open();
|
||||||
|
return $result;
|
||||||
//case NET_SSH2_MSG_CHANNEL_OPEN_FAILURE:
|
//case NET_SSH2_MSG_CHANNEL_OPEN_FAILURE:
|
||||||
default:
|
default:
|
||||||
user_error('Unable to open channel');
|
user_error('Unable to open channel');
|
||||||
@ -2949,6 +3011,7 @@ class SSH2
|
|||||||
case NET_SSH2_MSG_CHANNEL_CLOSE:
|
case NET_SSH2_MSG_CHANNEL_CLOSE:
|
||||||
return $type == NET_SSH2_MSG_CHANNEL_CLOSE ? true : $this->_get_channel_packet($client_channel, $skip_extended);
|
return $type == NET_SSH2_MSG_CHANNEL_CLOSE ? true : $this->_get_channel_packet($client_channel, $skip_extended);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ie. $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA
|
// ie. $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA
|
||||||
|
|
||||||
@ -2965,6 +3028,15 @@ class SSH2
|
|||||||
*/
|
*/
|
||||||
extract(unpack('Nlength', $this->_string_shift($response, 4)));
|
extract(unpack('Nlength', $this->_string_shift($response, 4)));
|
||||||
$data = $this->_string_shift($response, $length);
|
$data = $this->_string_shift($response, $length);
|
||||||
|
|
||||||
|
if ($channel == self::CHANNEL_AGENT_FORWARD) {
|
||||||
|
$agent_response = $this->agent->_forward_data($data);
|
||||||
|
if (!is_bool($agent_response)) {
|
||||||
|
$this->_send_channel_packet($channel, $agent_response);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if ($client_channel == $channel) {
|
if ($client_channel == $channel) {
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
@ -3401,6 +3473,22 @@ class SSH2
|
|||||||
return $this->log_boundary . str_pad(dechex(ord($matches[0])), 2, '0', STR_PAD_LEFT);
|
return $this->log_boundary . str_pad(dechex(ord($matches[0])), 2, '0', STR_PAD_LEFT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function for agent->_on_channel_open()
|
||||||
|
*
|
||||||
|
* Used when channels are created to inform agent
|
||||||
|
* of said channel opening. Must be called after
|
||||||
|
* channel open confirmation received
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
function _on_channel_open()
|
||||||
|
{
|
||||||
|
if (isset($this->agent)) {
|
||||||
|
$this->agent->_on_channel_open($this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all errors
|
* Returns all errors
|
||||||
*
|
*
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pure-PHP ssh-agent client.
|
* Pure-PHP ssh-agent client.
|
||||||
*
|
*
|
||||||
@ -61,6 +62,19 @@ class Agent
|
|||||||
const SSH_AGENT_SIGN_RESPONSE = 14;
|
const SSH_AGENT_SIGN_RESPONSE = 14;
|
||||||
/**#@-*/
|
/**#@-*/
|
||||||
|
|
||||||
|
/**@+
|
||||||
|
* Agent forwarding status
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
// no forwarding requested and not active
|
||||||
|
const FORWARD_NONE = 0;
|
||||||
|
// request agent forwarding when opportune
|
||||||
|
const FORWARD_REQUEST = 1;
|
||||||
|
// forwarding has been request and is active
|
||||||
|
const FORWARD_ACTIVE = 2;
|
||||||
|
/**#@-*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unused
|
* Unused
|
||||||
*/
|
*/
|
||||||
@ -74,6 +88,29 @@ class Agent
|
|||||||
*/
|
*/
|
||||||
var $fsock;
|
var $fsock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agent forwarding status
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
var $forward_status = self::FORWARD_NONE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Buffer for accumulating forwarded authentication
|
||||||
|
* agent data arriving on SSH data channel destined
|
||||||
|
* for agent unix socket
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
var $socket_buffer = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracking the number of bytes we are expecting
|
||||||
|
* to arrive for the agent socket on the SSH data
|
||||||
|
* channel
|
||||||
|
*/
|
||||||
|
var $expected_bytes = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default Constructor
|
* Default Constructor
|
||||||
*
|
*
|
||||||
@ -156,4 +193,107 @@ class Agent
|
|||||||
|
|
||||||
return $identities;
|
return $identities;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal that agent forwarding should
|
||||||
|
* be requested when a channel is opened
|
||||||
|
*
|
||||||
|
* @param Net_SSH2 $ssh
|
||||||
|
* @return Boolean
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
function startSSHForwarding($ssh)
|
||||||
|
{
|
||||||
|
if ($this->forward_status == self::FORWARD_NONE) {
|
||||||
|
$this->forward_status = self::FORWARD_REQUEST;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request agent forwarding of remote server
|
||||||
|
*
|
||||||
|
* @param Net_SSH2 $ssh
|
||||||
|
* @return Boolean
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
function _request_forwarding($ssh)
|
||||||
|
{
|
||||||
|
$request_channel = $ssh->_get_open_channel();
|
||||||
|
if ($request_channel === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$packet = pack('CNNa*C',
|
||||||
|
NET_SSH2_MSG_CHANNEL_REQUEST, $ssh->server_channels[$request_channel], strlen('auth-agent-req@openssh.com'), 'auth-agent-req@openssh.com', 1);
|
||||||
|
|
||||||
|
$ssh->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_REQUEST;
|
||||||
|
|
||||||
|
if (!$ssh->_send_binary_packet($packet)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $ssh->_get_channel_packet($request_channel);
|
||||||
|
if ($response === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ssh->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_OPEN;
|
||||||
|
$this->forward_status = self::FORWARD_ACTIVE;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On successful channel open
|
||||||
|
*
|
||||||
|
* This method is called upon successful channel
|
||||||
|
* open to give the SSH Agent an opportunity
|
||||||
|
* to take further action. i.e. request agent forwarding
|
||||||
|
*
|
||||||
|
* @param Net_SSH2 $ssh
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
function _on_channel_open($ssh)
|
||||||
|
{
|
||||||
|
if ($this->forward_status == self::FORWARD_REQUEST) {
|
||||||
|
$this->_request_forwarding($ssh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forward data to SSH Agent and return data reply
|
||||||
|
*
|
||||||
|
* @param String $data
|
||||||
|
* @return data from SSH Agent
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
function _forward_data($data)
|
||||||
|
{
|
||||||
|
if ($this->expected_bytes > 0) {
|
||||||
|
$this->socket_buffer.= $data;
|
||||||
|
$this->expected_bytes -= strlen($data);
|
||||||
|
} else {
|
||||||
|
$agent_data_bytes = current(unpack('N', $data));
|
||||||
|
$current_data_bytes = strlen($data);
|
||||||
|
$this->socket_buffer = $data;
|
||||||
|
if ($current_data_bytes != $agent_data_bytes + 4) {
|
||||||
|
$this->expected_bytes = ($agent_data_bytes + 4) - $current_data_bytes;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen($this->socket_buffer) != fwrite($this->fsock, $this->socket_buffer)) {
|
||||||
|
user_error('Connection closed attempting to forward data to SSH agent');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->socket_buffer = '';
|
||||||
|
$this->expected_bytes = 0;
|
||||||
|
|
||||||
|
$agent_reply_bytes = current(unpack('N', fread($this->fsock, 4)));
|
||||||
|
|
||||||
|
$agent_reply_data = fread($this->fsock, $agent_reply_bytes);
|
||||||
|
$agent_reply_data = current(unpack('a*', $agent_reply_data));
|
||||||
|
|
||||||
|
return pack('Na*', $agent_reply_bytes, $agent_reply_data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,5 +30,26 @@ class Functional_Net_SSH2AgentTest extends PhpseclibFunctionalTestCase
|
|||||||
$ssh->login($this->getEnv('SSH_USERNAME'), $agent),
|
$ssh->login($this->getEnv('SSH_USERNAME'), $agent),
|
||||||
'SSH2 login using Agent failed.'
|
'SSH2 login using Agent failed.'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return array('ssh' => $ssh, 'ssh-agent' => $agent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends testAgentLogin
|
||||||
|
*/
|
||||||
|
public function testAgentForward($args)
|
||||||
|
{
|
||||||
|
$ssh = $args['ssh'];
|
||||||
|
$agent = $args['ssh-agent'];
|
||||||
|
|
||||||
|
$hostname = $this->getEnv('SSH_HOSTNAME');
|
||||||
|
$username = $this->getEnv('SSH_USERNAME');
|
||||||
|
|
||||||
|
$this->assertEquals($username, trim($ssh->exec('whoami')));
|
||||||
|
|
||||||
|
$agent->startSSHForwarding($ssh);
|
||||||
|
$this->assertEquals($username, trim($ssh->exec("ssh " . $username . "@" . $hostname . ' \'whoami\'')));
|
||||||
|
|
||||||
|
return $args;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,4 +28,6 @@ ssh-add "$HOME/.ssh/id_rsa"
|
|||||||
# Allow the private key of the travis user to log in as phpseclib user
|
# Allow the private key of the travis user to log in as phpseclib user
|
||||||
sudo mkdir -p "/home/$USERNAME/.ssh/"
|
sudo mkdir -p "/home/$USERNAME/.ssh/"
|
||||||
sudo cp "$HOME/.ssh/id_rsa.pub" "/home/$USERNAME/.ssh/authorized_keys"
|
sudo cp "$HOME/.ssh/id_rsa.pub" "/home/$USERNAME/.ssh/authorized_keys"
|
||||||
|
sudo ssh-keyscan -t rsa localhost > "/tmp/known_hosts"
|
||||||
|
sudo cp "/tmp/known_hosts" "/home/$USERNAME/.ssh/known_hosts"
|
||||||
sudo chown "$USERNAME:$USERNAME" "/home/$USERNAME/.ssh/" -R
|
sudo chown "$USERNAME:$USERNAME" "/home/$USERNAME/.ssh/" -R
|
||||||
|
Loading…
Reference in New Issue
Block a user