diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index c7c22126..03d2ea47 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -48,7 +48,7 @@ * @author Jim Wigginton * @copyright MMIX Jim Wigginton * @license http://www.gnu.org/licenses/lgpl.txt - * @version $Id: SFTP.php,v 1.15 2010-02-11 16:17:40 terrafrost Exp $ + * @version $Id: SFTP.php,v 1.16 2010-02-12 23:02:13 terrafrost Exp $ * @link http://phpseclib.sourceforge.net */ @@ -202,6 +202,16 @@ class Net_SFTP extends Net_SSH2 { */ var $packet_log = array(); + /** + * Error information + * + * @see Net_SFTP::getSFTPErrors() + * @see Net_SFTP::getLastSFTPError() + * @var String + * @access private + */ + var $errors = array(); + /** * Default Constructor. * @@ -253,7 +263,14 @@ class Net_SFTP extends Net_SSH2 { ); $this->status_codes = array( 0 => 'NET_SFTP_STATUS_OK', - 1 => 'NET_SFTP_STATUS_EOF' + 1 => 'NET_SFTP_STATUS_EOF', + 2 => 'NET_SFTP_STATUS_NO_SUCH_FILE', + 3 => 'NET_SFTP_STATUS_PERMISSION_DENIED', + 4 => 'NET_SFTP_STATUS_FAILURE', + 5 => 'NET_SFTP_STATUS_BAD_MESSAGE', + 6 => 'NET_SFTP_STATUS_NO_CONNECTION', + 7 => 'NET_SFTP_STATUS_CONNECTION_LOST', + 8 => 'NET_SFTP_STATUS_OP_UNSUPPORTED' ); // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-7.1 // the order, in this case, matters quite a lot - see Net_SFTP::_parseAttributes() to understand why @@ -477,10 +494,8 @@ class Net_SFTP extends Net_SSH2 { $realpath = $this->_string_shift($response, $length); break; case NET_SFTP_STATUS: - // skip over the status code - hopefully the error message will give us all the info we need, anyway - $this->_string_shift($response, 4); - extract(unpack('Nlength', $this->_string_shift($response, 4))); - $this->debug_info.= "\r\n\r\nSSH_FXP_STATUS:\r\n" . $this->_string_shift($response, $length); + extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8))); + $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length); return false; default: user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS', E_USER_NOTICE); @@ -522,6 +537,8 @@ class Net_SFTP extends Net_SSH2 { $handle = substr($response, 4); break; case NET_SFTP_STATUS: + extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8))); + $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length); return false; default: user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS', E_USER_NOTICE); @@ -532,12 +549,19 @@ class Net_SFTP extends Net_SSH2 { return false; } - $this->_get_sftp_packet(); + $response = $this->_get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE); return false; } + extract(unpack('Nstatus', $this->_string_shift($response, 4))); + if ($status != NET_SFTP_STATUS_OK) { + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length); + return false; + } + $this->pwd = $dir; return true; } @@ -575,6 +599,8 @@ class Net_SFTP extends Net_SSH2 { break; case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED + extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8))); + $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length); return false; default: user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS', E_USER_NOTICE); @@ -608,7 +634,7 @@ class Net_SFTP extends Net_SSH2 { extract(unpack('Nstatus', $this->_string_shift($response, 4))); if ($status != NET_SFTP_STATUS_EOF) { extract(unpack('Nlength', $this->_string_shift($response, 4))); - $this->debug_info.= "\r\n\r\nSSH_FXP_STATUS:\r\n" . $this->_string_shift($response, $length); + $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length); return false; } break 2; @@ -624,12 +650,19 @@ class Net_SFTP extends Net_SSH2 { // "The client MUST release all resources associated with the handle regardless of the status." // -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.3 - $this->_get_sftp_packet(); + $response = $this->_get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE); return false; } + extract(unpack('Nstatus', $this->_string_shift($response, 4))); + if ($status != NET_SFTP_STATUS_OK) { + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length); + return false; + } + return $contents; } @@ -668,12 +701,18 @@ class Net_SFTP extends Net_SSH2 { -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.6 */ - $this->_get_sftp_packet(); + $response = $this->_get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE); return false; } + extract(unpack('Nstatus', $this->_string_shift($response, 4))); + if ($status != NET_SFTP_STATUS_EOF) { + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length); + } + // rather than return what the permissions *should* be, we'll return what they actually are. this will also // tell us if the file actually exists. // incidentally ,SFTPv4+ adds an additional 32-bit integer field - flags - to the following: @@ -688,6 +727,8 @@ class Net_SFTP extends Net_SSH2 { $attrs = $this->_parseAttributes($response); return $attrs['permissions']; case NET_SFTP_STATUS: + extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8))); + $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length); return false; } @@ -728,7 +769,7 @@ class Net_SFTP extends Net_SSH2 { extract(unpack('Nstatus', $this->_string_shift($response, 4))); if ($status != NET_SFTP_STATUS_OK) { extract(unpack('Nlength', $this->_string_shift($response, 4))); - $this->debug_info.= "\r\n\r\nSSH_FXP_STATUS:\r\n" . $this->_string_shift($response, $length); + $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length); return false; } @@ -766,6 +807,8 @@ class Net_SFTP extends Net_SSH2 { extract(unpack('Nstatus', $this->_string_shift($response, 4))); if ($status != NET_SFTP_STATUS_OK) { // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED? + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length); return false; } @@ -815,9 +858,8 @@ class Net_SFTP extends Net_SSH2 { $handle = substr($response, 4); break; case NET_SFTP_STATUS: - $this->_string_shift($response, 4); - extract(unpack('Nlength', $this->_string_shift($response, 4))); - $this->debug_info.= "\r\n\r\nSSH_FXP_STATUS:\r\n" . $this->_string_shift($response, $length); + extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8))); + $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length); return false; default: user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS', E_USER_NOTICE); @@ -857,12 +899,18 @@ class Net_SFTP extends Net_SSH2 { $i++; } - while ($i-- > 0){ - $this->_get_sftp_packet(); + while ($i--) { + $response = $this->_get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE); return false; } + + extract(unpack('Nstatus', $this->_string_shift($response, 4))); + if ($status != NET_SFTP_STATUS_OK) { + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length); + } } if ($mode == NET_SFTP_LOCAL_FILE) { @@ -873,12 +921,19 @@ class Net_SFTP extends Net_SSH2 { return false; } - $this->_get_sftp_packet(); + $response = $this->_get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE); return false; } + extract(unpack('Nstatus', $this->_string_shift($response, 4))); + if ($status != NET_SFTP_STATUS_OK) { + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length); + return false; + } + return true; } @@ -916,6 +971,8 @@ class Net_SFTP extends Net_SSH2 { $handle = substr($response, 4); break; case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED + extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8))); + $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length); return false; default: user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS', E_USER_NOTICE); @@ -933,13 +990,14 @@ class Net_SFTP extends Net_SSH2 { $attrs = $this->_parseAttributes($response); break; case NET_SFTP_STATUS: + extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8))); + $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length); return false; default: user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS', E_USER_NOTICE); return false; } - if ($local_file !== false) { $fp = fopen($local_file, 'w'); if (!$fp) { @@ -968,10 +1026,9 @@ class Net_SFTP extends Net_SSH2 { } break; case NET_SFTP_STATUS: - $this->_string_shift($response, 4); - extract(unpack('Nlength', $this->_string_shift($response, 4))); - $this->debug_info.= "\r\n\r\nSSH_FXP_STATUS:\r\n" . $this->_string_shift($response, $length); - return false; + extract(unpack('Nstatus/Nlength', $this->_string_shift($response, 8))); + $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length); + break 2; default: user_error('Expected SSH_FXP_DATA or SSH_FXP_STATUS', E_USER_NOTICE); return false; @@ -982,12 +1039,19 @@ class Net_SFTP extends Net_SSH2 { return false; } - $this->_get_sftp_packet(); + $response = $this->_get_sftp_packet(); if ($this->packet_type != NET_SFTP_STATUS) { user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE); return false; } + extract(unpack('Nstatus', $this->_string_shift($response, 4))); + if ($status != NET_SFTP_STATUS_OK) { + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length); + return false; + } + if (isset($content)) { return $content; } @@ -1025,10 +1089,15 @@ class Net_SFTP extends Net_SSH2 { return false; } - extract(unpack('Nstatus', $this->_string_shift($response, 4))); - // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED - return $status == NET_SFTP_STATUS_OK; + extract(unpack('Nstatus', $this->_string_shift($response, 4))); + if ($status != NET_SFTP_STATUS_OK) { + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length); + return false; + } + + return true; } /** @@ -1063,10 +1132,15 @@ class Net_SFTP extends Net_SSH2 { return false; } - extract(unpack('Nstatus', $this->_string_shift($response, 4))); - // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED - return $status == NET_SFTP_STATUS_OK; + extract(unpack('Nstatus', $this->_string_shift($response, 4))); + if ($status != NET_SFTP_STATUS_OK) { + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $this->sftp_errors[] = $this->status_codes[$status] . ': ' . $this->_string_shift($response, $length); + return false; + } + + return true; } /** @@ -1198,12 +1272,6 @@ class Net_SFTP extends Net_SSH2 { $this->packet_type = ord($this->_string_shift($this->packet_buffer)); - if (defined('NET_SFTP_LOGGING')) { - $this->packet_type_log[] = '<- ' . $this->packet_types[$this->packet_type] . - ' (' . round($stop - $start, 4) . 's)'; - $this->packet_log[] = $packet; - } - if ($this->request_id !== false) { $this->_string_shift($this->packet_buffer, 4); // remove the request id $length-= 5; // account for the request id and the packet type @@ -1211,7 +1279,15 @@ class Net_SFTP extends Net_SSH2 { $length-= 1; // account for the packet type } - return $this->_string_shift($this->packet_buffer, $length); + $packet = $this->_string_shift($this->packet_buffer, $length); + + if (defined('NET_SFTP_LOGGING')) { + $this->packet_type_log[] = '<- ' . $this->packet_types[$this->packet_type] . + ' (' . round($stop - $start, 4) . 's)'; + $this->packet_log[] = $packet; + } + + return $packet; } /** @@ -1239,6 +1315,28 @@ class Net_SFTP extends Net_SSH2 { return $return; } + /** + * Returns all errors + * + * @return String + * @access public + */ + function getSFTPErrors() + { + return $this->sftp_errors; + } + + /** + * Returns the last error + * + * @return String + * @access public + */ + function getLastSFTPError() + { + return $this->sftp_errors[count($this->sftp_errors) - 1]; + } + /** * Get supported SFTP versions * diff --git a/phpseclib/Net/SSH2.php b/phpseclib/Net/SSH2.php index edd08ebc..3d45e189 100644 --- a/phpseclib/Net/SSH2.php +++ b/phpseclib/Net/SSH2.php @@ -60,7 +60,7 @@ * @author Jim Wigginton * @copyright MMVII Jim Wigginton * @license http://www.gnu.org/licenses/lgpl.txt - * @version $Id: SSH2.php,v 1.36 2010-02-11 07:02:51 terrafrost Exp $ + * @version $Id: SSH2.php,v 1.37 2010-02-12 23:02:13 terrafrost Exp $ * @link http://phpseclib.sourceforge.net */ @@ -176,13 +176,14 @@ class Net_SSH2 { var $bitmap = 0; /** - * Debug Info + * Error information * - * @see Net_SSH2::getDebugInfo() + * @see Net_SSH2::getErrors() + * @see Net_SSH2::getLastError() * @var String * @access private */ - var $debug_info = ''; + var $errors = array(); /** * Server Identifier @@ -637,7 +638,7 @@ class Net_SSH2 { $temp = ''; while (!feof($this->fsock) && !preg_match('#^SSH-(\d\.\d+)#', $temp, $matches)) { if (substr($temp, -2) == "\r\n") { - $this->debug_info.= $temp; + $this->errors[] = $temp; $temp = ''; } $temp.= fgets($this->fsock, 255); @@ -665,7 +666,7 @@ class Net_SSH2 { } $this->server_identifier = trim($temp); - $this->debug_info = utf8_decode($this->debug_info); + $this->errors[] = utf8_decode($this->debug_info); if ($matches[1] != '1.99' && $matches[1] != '2.0') { user_error("Cannot connect to SSH $matches[1] servers", E_USER_NOTICE); @@ -1400,7 +1401,7 @@ class Net_SSH2 { $this->message_number_log[count($this->message_number_log) - 1] = 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ'; } extract(unpack('Nlength', $this->_string_shift($response, 4))); - $this->debug_info.= "\r\n\r\nSSH_MSG_USERAUTH_PASSWD_CHANGEREQ:\r\n" . utf8_decode($this->_string_shift($response, $length)); + $this->errors[] = 'SSH_MSG_USERAUTH_PASSWD_CHANGEREQ: ' . utf8_decode($this->_string_shift($response, $length)); return $this->_disconnect(NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); case NET_SSH2_MSG_USERAUTH_FAILURE: // either the login is bad or the server employees multi-factor authentication @@ -1462,7 +1463,7 @@ class Net_SSH2 { switch ($type) { case NET_SSH2_MSG_USERAUTH_FAILURE: extract(unpack('Nlength', $this->_string_shift($response, 4))); - $this->debug_info.= "\r\n\r\nSSH_MSG_USERAUTH_FAILURE:\r\n" . $this->_string_shift($response, $length); + $this->errors[] = 'SSH_MSG_USERAUTH_FAILURE: ' . $this->_string_shift($response, $length); return $this->_disconnect(NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); case NET_SSH2_MSG_USERAUTH_PK_OK: // we'll just take it on faith that the public key blob and the public key algorithm name are as @@ -1675,7 +1676,7 @@ class Net_SSH2 { case NET_SSH2_MSG_DISCONNECT: $this->_string_shift($payload, 1); extract(unpack('Nreason_code/Nlength', $this->_string_shift($payload, 8))); - $this->debug_info.= "\r\n\r\nSSH_MSG_DISCONNECT:\r\n" . $this->disconnect_reasons[$reason_code] . "\r\n" . utf8_decode($this->_string_shift($payload, $length)); + $this->errors[] = 'SSH_MSG_DISCONNECT: ' . $this->disconnect_reasons[$reason_code] . "\r\n" . utf8_decode($this->_string_shift($payload, $length)); $this->bitmask = 0; return false; case NET_SSH2_MSG_IGNORE: @@ -1684,7 +1685,7 @@ class Net_SSH2 { case NET_SSH2_MSG_DEBUG: $this->_string_shift($payload, 2); extract(unpack('Nlength', $this->_string_shift($payload, 4))); - $this->debug_info.= "\r\n\r\nSSH_MSG_DEBUG:\r\n" . utf8_decode($this->_string_shift($payload, $length)); + $this->errors[] = 'SSH_MSG_DEBUG: ' . utf8_decode($this->_string_shift($payload, $length)); $payload = $this->_get_binary_packet(); break; case NET_SSH2_MSG_UNIMPLEMENTED: @@ -1703,7 +1704,7 @@ class Net_SSH2 { if (($this->bitmap & NET_SSH2_MASK_CONSTRUCTOR) && !($this->bitmap & NET_SSH2_MASK_LOGIN) && ord($payload[0]) == NET_SSH2_MSG_USERAUTH_BANNER) { $this->_string_shift($payload, 1); extract(unpack('Nlength', $this->_string_shift($payload, 4))); - $this->debug_info.= "\r\n\r\nSSH_MSG_USERAUTH_BANNER:\r\n" . utf8_decode($this->_string_shift($payload, $length)); + $this->errors[] = 'SSH_MSG_USERAUTH_BANNER: ' . utf8_decode($this->_string_shift($payload, $length)); $payload = $this->_get_binary_packet(); } @@ -1713,7 +1714,7 @@ class Net_SSH2 { case NET_SSH2_MSG_GLOBAL_REQUEST: // see http://tools.ietf.org/html/rfc4254#section-4 $this->_string_shift($payload, 1); extract(unpack('Nlength', $this->_string_shift($payload))); - $this->debug_info.= "\r\n\r\nSSH_MSG_GLOBAL_REQUEST:\r\n" . utf8_decode($this->_string_shift($payload, $length)); + $this->errors[] = 'SSH_MSG_GLOBAL_REQUEST: ' . utf8_decode($this->_string_shift($payload, $length)); if (!$this->_send_binary_packet(pack('C', NET_SSH2_MSG_REQUEST_FAILURE))) { return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); @@ -1724,7 +1725,7 @@ class Net_SSH2 { case NET_SSH2_MSG_CHANNEL_OPEN: // see http://tools.ietf.org/html/rfc4254#section-5.1 $this->_string_shift($payload, 1); extract(unpack('N', $this->_string_shift($payload, 4))); - $this->debug_info.= "\r\n\r\nSSH_MSG_CHANNEL_OPEN:\r\n" . utf8_decode($this->_string_shift($payload, $length)); + $this->errors[] = 'SSH_MSG_CHANNEL_OPEN: ' . utf8_decode($this->_string_shift($payload, $length)); $this->_string_shift($payload, 4); // skip over client channel extract(unpack('Nserver_channel', $this->_string_shift($payload, 4))); @@ -1825,7 +1826,7 @@ class Net_SSH2 { $data = $this->_string_shift($response, $length); switch ($data_type_code) { case NET_SSH2_EXTENDED_DATA_STDERR: - $this->debug_info.= "\r\n\r\nSSH_MSG_CHANNEL_EXTENDED_DATA (SSH_EXTENDED_DATA_STDERR):\r\n" . $data; + $this->errors[] = 'SSH_MSG_CHANNEL_EXTENDED_DATA (SSH_EXTENDED_DATA_STDERR): ' . $data; } break; case NET_SSH2_MSG_CHANNEL_REQUEST: @@ -1835,10 +1836,12 @@ class Net_SSH2 { case 'exit-signal': $this->_string_shift($response, 1); extract(unpack('Nlength', $this->_string_shift($response, 4))); - $this->debug_info.= "\r\n\r\nSSH_MSG_CHANNEL_REQUEST (exit-signal):\r\nSIG" . $this->_string_shift($response, $length); + $this->errors[] = 'SSH_MSG_CHANNEL_REQUEST (exit-signal): ' . $this->_string_shift($response, $length); $this->_string_shift($response, 1); extract(unpack('Nlength', $this->_string_shift($response, 4))); - $this->debug_info.= "\r\n" . $this->_string_shift($response, $length); + if ($length) { + $this->errors[count($this->errors)].= "\r\n" . $this->_string_shift($response, $length); + } //case 'exit-status': default: // "Some systems may not implement signals, in which case they SHOULD ignore this message." @@ -2072,16 +2075,25 @@ class Net_SSH2 { } /** - * Returns Debug Information - * - * If any debug information is sent by the server, this function can be used to access it. + * Returns all errors * * @return String * @access public */ - function getDebugInfo() + function getErrors() { - return $this->debug_info; + return $this->errors; + } + + /** + * Returns the last error + * + * @return String + * @access public + */ + function getLastError() + { + return $this->errors[count($this->errors) - 1]; } /**