diff options
Diffstat (limited to 'libs/libssh2/src/scp.c')
-rw-r--r-- | libs/libssh2/src/scp.c | 1126 |
1 files changed, 1126 insertions, 0 deletions
diff --git a/libs/libssh2/src/scp.c b/libs/libssh2/src/scp.c new file mode 100644 index 0000000000..22778dd38a --- /dev/null +++ b/libs/libssh2/src/scp.c @@ -0,0 +1,1126 @@ +/* Copyright (c) 2009-2010 by Daniel Stenberg + * Copyright (c) 2004-2008, Sara Golemon <sarag@libssh2.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" +#include <errno.h> +#include <stdlib.h> + +#include "channel.h" +#include "session.h" + + +/* Max. length of a quoted string after libssh2_shell_quotearg() processing */ +#define _libssh2_shell_quotedsize(s) (3 * strlen(s) + 2) + +/* + This function quotes a string in a way suitable to be used with a + shell, e.g. the file name + one two + becomes + 'one two' + + The resulting output string is crafted in a way that makes it usable + with the two most common shell types: Bourne Shell derived shells + (sh, ksh, ksh93, bash, zsh) and C-Shell derivates (csh, tcsh). + + The following special cases are handled: + o If the string contains an apostrophy itself, the apostrophy + character is written in quotation marks, e.g. "'". + The shell cannot handle the syntax 'doesn\'t', so we close the + current argument word, add the apostrophe in quotation marks "", + and open a new argument word instead (_ indicate the input + string characters): + _____ _ _ + 'doesn' "'" 't' + + Sequences of apostrophes are combined in one pair of quotation marks: + a'''b + becomes + _ ___ _ + 'a'"'''"'b' + + o If the string contains an exclamation mark (!), the C-Shell + interprets it as an event number. Using \! (not within quotation + marks or single quotation marks) is a mechanism understood by + both Bourne Shell and C-Shell. + + If a quotation was already started, the argument word is closed + first: + a!b + + become + _ _ _ + 'a'\!'b' + + The result buffer must be large enough for the expanded result. A + bad case regarding expansion is alternating characters and + apostrophes: + + a'b'c'd' (length 8) gets converted to + 'a'"'"'b'"'"'c'"'"'d'"'" (length 24) + + This is the worst case. + + Maximum length of the result: + 1 + 6 * (length(input) + 1) / 2) + 1 + + => 3 * length(input) + 2 + + Explanation: + o leading apostrophe + o one character / apostrophe pair (two characters) can get + represented as 6 characters: a' -> a'"'"' + o String terminator (+1) + + A result buffer three times the size of the input buffer + 2 + characters should be safe. + + References: + o csh-compatible quotation (special handling for '!' etc.), see + http://www.grymoire.com/Unix/Csh.html#toc-uh-10 + + Return value: + Length of the resulting string (not counting the terminating '\0'), + or 0 in case of errors, e.g. result buffer too small + + Note: this function could possible be used elsewhere within libssh2, but + until then it is kept static and in this source file. +*/ + +static unsigned +shell_quotearg(const char *path, unsigned char *buf, + unsigned bufsize) +{ + const char *src; + unsigned char *dst, *endp; + + /* + * Processing States: + * UQSTRING: unquoted string: ... -- used for quoting exclamation + * marks. This is the initial state + * SQSTRING: single-quoted-string: '... -- any character may follow + * QSTRING: quoted string: "... -- only apostrophes may follow + */ + enum { UQSTRING, SQSTRING, QSTRING } state = UQSTRING; + + endp = &buf[bufsize]; + src = path; + dst = buf; + while (*src && dst < endp - 1) { + + switch (*src) { + /* + * Special handling for apostrophe. + * An apostrophe is always written in quotation marks, e.g. + * ' -> "'". + */ + + case '\'': + switch (state) { + case UQSTRING: /* Unquoted string */ + if (dst+1 >= endp) + return 0; + *dst++ = '"'; + break; + case QSTRING: /* Continue quoted string */ + break; + case SQSTRING: /* Close single quoted string */ + if (dst+2 >= endp) + return 0; + *dst++ = '\''; + *dst++ = '"'; + break; + default: + break; + } + state = QSTRING; + break; + + /* + * Special handling for exclamation marks. CSH interprets + * exclamation marks even when quoted with apostrophes. We convert + * it to the plain string \!, because both Bourne Shell and CSH + * interpret that as a verbatim exclamation mark. + */ + + case '!': + switch (state) { + case UQSTRING: + if (dst+1 >= endp) + return 0; + *dst++ = '\\'; + break; + case QSTRING: + if (dst+2 >= endp) + return 0; + *dst++ = '"'; /* Closing quotation mark */ + *dst++ = '\\'; + break; + case SQSTRING: /* Close single quoted string */ + if (dst+2 >= endp) + return 0; + *dst++ = '\''; + *dst++ = '\\'; + break; + default: + break; + } + state = UQSTRING; + break; + + /* + * Ordinary character: prefer single-quoted string + */ + + default: + switch (state) { + case UQSTRING: + if (dst+1 >= endp) + return 0; + *dst++ = '\''; + break; + case QSTRING: + if (dst+2 >= endp) + return 0; + *dst++ = '"'; /* Closing quotation mark */ + *dst++ = '\''; + break; + case SQSTRING: /* Continue single quoted string */ + break; + default: + break; + } + state = SQSTRING; /* Start single-quoted string */ + break; + } + + if (dst+1 >= endp) + return 0; + *dst++ = *src++; + } + + switch (state) { + case UQSTRING: + break; + case QSTRING: /* Close quoted string */ + if (dst+1 >= endp) + return 0; + *dst++ = '"'; + break; + case SQSTRING: /* Close single quoted string */ + if (dst+1 >= endp) + return 0; + *dst++ = '\''; + break; + default: + break; + } + + if (dst+1 >= endp) + return 0; + *dst = '\0'; + + /* The result cannot be larger than 3 * strlen(path) + 2 */ + /* assert((dst - buf) <= (3 * (src - path) + 2)); */ + + return dst - buf; +} + +/* + * scp_recv + * + * Open a channel and request a remote file via SCP + * + */ +static LIBSSH2_CHANNEL * +scp_recv(LIBSSH2_SESSION * session, const char *path, libssh2_struct_stat * sb) +{ + int cmd_len; + int rc; + int tmp_err_code; + const char *tmp_err_msg; + + if (session->scpRecv_state == libssh2_NB_state_idle) { + session->scpRecv_mode = 0; + session->scpRecv_size = 0; + session->scpRecv_mtime = 0; + session->scpRecv_atime = 0; + + session->scpRecv_command_len = + _libssh2_shell_quotedsize(path) + sizeof("scp -f ") + (sb?1:0); + + session->scpRecv_command = + LIBSSH2_ALLOC(session, session->scpRecv_command_len); + + if (!session->scpRecv_command) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate a command buffer for " + "SCP session"); + return NULL; + } + + snprintf((char *)session->scpRecv_command, + session->scpRecv_command_len, + "scp -%sf ", sb?"p":""); + + cmd_len = strlen((char *)session->scpRecv_command); + cmd_len += shell_quotearg(path, + &session->scpRecv_command[cmd_len], + session->scpRecv_command_len - cmd_len); + + session->scpRecv_command[cmd_len] = '\0'; + session->scpRecv_command_len = cmd_len + 1; + + _libssh2_debug(session, LIBSSH2_TRACE_SCP, + "Opening channel for SCP receive"); + + session->scpRecv_state = libssh2_NB_state_created; + } + + if (session->scpRecv_state == libssh2_NB_state_created) { + /* Allocate a channel */ + session->scpRecv_channel = + _libssh2_channel_open(session, "session", + sizeof("session") - 1, + LIBSSH2_CHANNEL_WINDOW_DEFAULT, + LIBSSH2_CHANNEL_PACKET_DEFAULT, NULL, + 0); + if (!session->scpRecv_channel) { + if (libssh2_session_last_errno(session) != + LIBSSH2_ERROR_EAGAIN) { + LIBSSH2_FREE(session, session->scpRecv_command); + session->scpRecv_command = NULL; + session->scpRecv_state = libssh2_NB_state_idle; + } + else { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block starting up channel"); + } + return NULL; + } + + session->scpRecv_state = libssh2_NB_state_sent; + } + + if (session->scpRecv_state == libssh2_NB_state_sent) { + /* Request SCP for the desired file */ + rc = _libssh2_channel_process_startup(session->scpRecv_channel, "exec", + sizeof("exec") - 1, + (char *) session->scpRecv_command, + session->scpRecv_command_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block requesting SCP startup"); + return NULL; + } else if (rc) { + LIBSSH2_FREE(session, session->scpRecv_command); + session->scpRecv_command = NULL; + goto scp_recv_error; + } + LIBSSH2_FREE(session, session->scpRecv_command); + session->scpRecv_command = NULL; + + _libssh2_debug(session, LIBSSH2_TRACE_SCP, "Sending initial wakeup"); + /* SCP ACK */ + session->scpRecv_response[0] = '\0'; + + session->scpRecv_state = libssh2_NB_state_sent1; + } + + if (session->scpRecv_state == libssh2_NB_state_sent1) { + rc = _libssh2_channel_write(session->scpRecv_channel, 0, + session->scpRecv_response, 1); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block sending initial wakeup"); + return NULL; + } else if (rc != 1) { + goto scp_recv_error; + } + + /* Parse SCP response */ + session->scpRecv_response_len = 0; + + session->scpRecv_state = libssh2_NB_state_sent2; + } + + if ((session->scpRecv_state == libssh2_NB_state_sent2) + || (session->scpRecv_state == libssh2_NB_state_sent3)) { + while (sb && (session->scpRecv_response_len < + LIBSSH2_SCP_RESPONSE_BUFLEN)) { + unsigned char *s, *p; + + if (session->scpRecv_state == libssh2_NB_state_sent2) { + rc = _libssh2_channel_read(session->scpRecv_channel, 0, + (char *) session-> + scpRecv_response + + session->scpRecv_response_len, 1); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block waiting for SCP response"); + return NULL; + } + else if (rc < 0) { + /* error, give up */ + _libssh2_error(session, rc, "Failed reading SCP response"); + goto scp_recv_error; + } + else if(rc == 0) + goto scp_recv_empty_channel; + + session->scpRecv_response_len++; + + if (session->scpRecv_response[0] != 'T') { + size_t err_len; + char *err_msg; + + /* there can be + 01 for warnings + 02 for errors + + The following string MUST be newline terminated + */ + err_len = + _libssh2_channel_packet_data_len(session-> + scpRecv_channel, 0); + err_msg = LIBSSH2_ALLOC(session, err_len + 1); + if (!err_msg) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Failed to get memory "); + goto scp_recv_error; + } + + /* Read the remote error message */ + (void)_libssh2_channel_read(session->scpRecv_channel, 0, + err_msg, err_len); + /* If it failed for any reason, we ignore it anyway. */ + + /* zero terminate the error */ + err_msg[err_len]=0; + + _libssh2_debug(session, LIBSSH2_TRACE_SCP, + "got %02x %s", session->scpRecv_response[0], + err_msg); + + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Failed to recv file"); + + LIBSSH2_FREE(session, err_msg); + goto scp_recv_error; + } + + if ((session->scpRecv_response_len > 1) && + ((session-> + scpRecv_response[session->scpRecv_response_len - 1] < + '0') + || (session-> + scpRecv_response[session->scpRecv_response_len - 1] > + '9')) + && (session-> + scpRecv_response[session->scpRecv_response_len - 1] != + ' ') + && (session-> + scpRecv_response[session->scpRecv_response_len - 1] != + '\r') + && (session-> + scpRecv_response[session->scpRecv_response_len - 1] != + '\n')) { + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid data in SCP response"); + goto scp_recv_error; + } + + if ((session->scpRecv_response_len < 9) + || (session-> + scpRecv_response[session->scpRecv_response_len - 1] != + '\n')) { + if (session->scpRecv_response_len == + LIBSSH2_SCP_RESPONSE_BUFLEN) { + /* You had your chance */ + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Unterminated response from SCP server"); + goto scp_recv_error; + } + /* Way too short to be an SCP response, or not done yet, + short circuit */ + continue; + } + + /* We're guaranteed not to go under response_len == 0 by the + logic above */ + while ((session-> + scpRecv_response[session->scpRecv_response_len - 1] == + '\r') + || (session-> + scpRecv_response[session->scpRecv_response_len - + 1] == '\n')) + session->scpRecv_response_len--; + session->scpRecv_response[session->scpRecv_response_len] = + '\0'; + + if (session->scpRecv_response_len < 8) { + /* EOL came too soon */ + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid response from SCP server, " + "too short" ); + goto scp_recv_error; + } + + s = session->scpRecv_response + 1; + + p = (unsigned char *) strchr((char *) s, ' '); + if (!p || ((p - s) <= 0)) { + /* No spaces or space in the wrong spot */ + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid response from SCP server, " + "malformed mtime"); + goto scp_recv_error; + } + + *(p++) = '\0'; + /* Make sure we don't get fooled by leftover values */ + session->scpRecv_mtime = strtol((char *) s, NULL, 10); + + s = (unsigned char *) strchr((char *) p, ' '); + if (!s || ((s - p) <= 0)) { + /* No spaces or space in the wrong spot */ + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid response from SCP server, malformed mtime.usec"); + goto scp_recv_error; + } + + /* Ignore mtime.usec */ + s++; + p = (unsigned char *) strchr((char *) s, ' '); + if (!p || ((p - s) <= 0)) { + /* No spaces or space in the wrong spot */ + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid response from SCP server, too short or malformed"); + goto scp_recv_error; + } + + *p = '\0'; + /* Make sure we don't get fooled by leftover values */ + session->scpRecv_atime = strtol((char *) s, NULL, 10); + + /* SCP ACK */ + session->scpRecv_response[0] = '\0'; + + session->scpRecv_state = libssh2_NB_state_sent3; + } + + if (session->scpRecv_state == libssh2_NB_state_sent3) { + rc = _libssh2_channel_write(session->scpRecv_channel, 0, + session->scpRecv_response, 1); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block waiting to send SCP ACK"); + return NULL; + } else if (rc != 1) { + goto scp_recv_error; + } + + _libssh2_debug(session, LIBSSH2_TRACE_SCP, + "mtime = %ld, atime = %ld", + session->scpRecv_mtime, session->scpRecv_atime); + + /* We *should* check that atime.usec is valid, but why let + that stop use? */ + break; + } + } + + session->scpRecv_state = libssh2_NB_state_sent4; + } + + if (session->scpRecv_state == libssh2_NB_state_sent4) { + session->scpRecv_response_len = 0; + + session->scpRecv_state = libssh2_NB_state_sent5; + } + + if ((session->scpRecv_state == libssh2_NB_state_sent5) + || (session->scpRecv_state == libssh2_NB_state_sent6)) { + while (session->scpRecv_response_len < LIBSSH2_SCP_RESPONSE_BUFLEN) { + char *s, *p, *e = NULL; + + if (session->scpRecv_state == libssh2_NB_state_sent5) { + rc = _libssh2_channel_read(session->scpRecv_channel, 0, + (char *) session-> + scpRecv_response + + session->scpRecv_response_len, 1); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block waiting for SCP response"); + return NULL; + } + else if (rc < 0) { + /* error, bail out*/ + _libssh2_error(session, rc, "Failed reading SCP response"); + goto scp_recv_error; + } + else if(rc == 0) + goto scp_recv_empty_channel; + + session->scpRecv_response_len++; + + if (session->scpRecv_response[0] != 'C') { + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid response from SCP server"); + goto scp_recv_error; + } + + if ((session->scpRecv_response_len > 1) && + (session-> + scpRecv_response[session->scpRecv_response_len - 1] != + '\r') + && (session-> + scpRecv_response[session->scpRecv_response_len - 1] != + '\n') + && + (session-> + scpRecv_response[session->scpRecv_response_len - 1] + < 32)) { + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid data in SCP response"); + goto scp_recv_error; + } + + if ((session->scpRecv_response_len < 7) + || (session-> + scpRecv_response[session->scpRecv_response_len - 1] != + '\n')) { + if (session->scpRecv_response_len == + LIBSSH2_SCP_RESPONSE_BUFLEN) { + /* You had your chance */ + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Unterminated response from SCP server"); + goto scp_recv_error; + } + /* Way too short to be an SCP response, or not done yet, + short circuit */ + continue; + } + + /* We're guaranteed not to go under response_len == 0 by the + logic above */ + while ((session-> + scpRecv_response[session->scpRecv_response_len - 1] == + '\r') + || (session-> + scpRecv_response[session->scpRecv_response_len - + 1] == '\n')) { + session->scpRecv_response_len--; + } + session->scpRecv_response[session->scpRecv_response_len] = + '\0'; + + if (session->scpRecv_response_len < 6) { + /* EOL came too soon */ + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid response from SCP server, too short"); + goto scp_recv_error; + } + + s = (char *) session->scpRecv_response + 1; + + p = strchr(s, ' '); + if (!p || ((p - s) <= 0)) { + /* No spaces or space in the wrong spot */ + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid response from SCP server, malformed mode"); + goto scp_recv_error; + } + + *(p++) = '\0'; + /* Make sure we don't get fooled by leftover values */ + + session->scpRecv_mode = strtol(s, &e, 8); + if (e && *e) { + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid response from SCP server, invalid mode"); + goto scp_recv_error; + } + + s = strchr(p, ' '); + if (!s || ((s - p) <= 0)) { + /* No spaces or space in the wrong spot */ + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid response from SCP server, too short or malformed"); + goto scp_recv_error; + } + + *s = '\0'; + /* Make sure we don't get fooled by leftover values */ + session->scpRecv_size = scpsize_strtol(p, &e, 10); + if (e && *e) { + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid response from SCP server, invalid size"); + goto scp_recv_error; + } + + /* SCP ACK */ + session->scpRecv_response[0] = '\0'; + + session->scpRecv_state = libssh2_NB_state_sent6; + } + + if (session->scpRecv_state == libssh2_NB_state_sent6) { + rc = _libssh2_channel_write(session->scpRecv_channel, 0, + session->scpRecv_response, 1); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block sending SCP ACK"); + return NULL; + } else if (rc != 1) { + goto scp_recv_error; + } + _libssh2_debug(session, LIBSSH2_TRACE_SCP, + "mode = 0%lo size = %ld", session->scpRecv_mode, + session->scpRecv_size); + + /* We *should* check that basename is valid, but why let that + stop us? */ + break; + } + } + + session->scpRecv_state = libssh2_NB_state_sent7; + } + + if (sb) { + memset(sb, 0, sizeof(libssh2_struct_stat)); + + sb->st_mtime = session->scpRecv_mtime; + sb->st_atime = session->scpRecv_atime; + sb->st_size = session->scpRecv_size; + sb->st_mode = (unsigned short)session->scpRecv_mode; + } + + session->scpRecv_state = libssh2_NB_state_idle; + return session->scpRecv_channel; + + scp_recv_empty_channel: + /* the code only jumps here as a result of a zero read from channel_read() + so we check EOF status to avoid getting stuck in a loop */ + if(libssh2_channel_eof(session->scpRecv_channel)) + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Unexpected channel close"); + else + return session->scpRecv_channel; + /* fall-through */ + scp_recv_error: + tmp_err_code = session->err_code; + tmp_err_msg = session->err_msg; + while (libssh2_channel_free(session->scpRecv_channel) == + LIBSSH2_ERROR_EAGAIN); + session->err_code = tmp_err_code; + session->err_msg = tmp_err_msg; + session->scpRecv_channel = NULL; + session->scpRecv_state = libssh2_NB_state_idle; + return NULL; +} + +/* + * libssh2_scp_recv + * + * DEPRECATED + * + * Open a channel and request a remote file via SCP. This receives files larger + * than 2 GB, but is unable to report the proper size on platforms where the + * st_size member of struct stat is limited to 2 GB (e.g. windows). + * + */ +LIBSSH2_API LIBSSH2_CHANNEL * +libssh2_scp_recv(LIBSSH2_SESSION *session, const char *path, struct stat * sb) +{ + LIBSSH2_CHANNEL *ptr; + + /* scp_recv uses libssh2_struct_stat, so pass one if the caller gave us a struct to populate... */ + libssh2_struct_stat sb_intl; + libssh2_struct_stat *sb_ptr; + sb_ptr = sb ? &sb_intl : NULL; + + BLOCK_ADJUST_ERRNO(ptr, session, scp_recv(session, path, sb_ptr)); + + /* ...and populate the caller's with as much info as fits. */ + if (sb) { + memset(sb, 0, sizeof(struct stat)); + + sb->st_mtime = sb_intl.st_mtime; + sb->st_atime = sb_intl.st_atime; + sb->st_size = (off_t)sb_intl.st_size; + sb->st_mode = sb_intl.st_mode; + } + + return ptr; +} + +/* + * libssh2_scp_recv2 + * + * Open a channel and request a remote file via SCP. This supports files > 2GB + * on platforms that support it. + * + */ +LIBSSH2_API LIBSSH2_CHANNEL * +libssh2_scp_recv2(LIBSSH2_SESSION *session, const char *path, libssh2_struct_stat * sb) +{ + LIBSSH2_CHANNEL *ptr; + BLOCK_ADJUST_ERRNO(ptr, session, scp_recv(session, path, sb)); + return ptr; +} + +/* + * scp_send() + * + * Send a file using SCP + * + */ +static LIBSSH2_CHANNEL * +scp_send(LIBSSH2_SESSION * session, const char *path, int mode, + libssh2_int64_t size, time_t mtime, time_t atime) +{ + int cmd_len; + int rc; + int tmp_err_code; + const char *tmp_err_msg; + + if (session->scpSend_state == libssh2_NB_state_idle) { + session->scpSend_command_len = + _libssh2_shell_quotedsize(path) + sizeof("scp -t ") + + ((mtime || atime)?1:0); + + session->scpSend_command = + LIBSSH2_ALLOC(session, session->scpSend_command_len); + + if (!session->scpSend_command) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate a command buffer for " + "SCP session"); + return NULL; + } + + snprintf((char *)session->scpSend_command, + session->scpSend_command_len, + "scp -%st ", (mtime || atime)?"p":""); + + cmd_len = strlen((char *)session->scpSend_command); + cmd_len += shell_quotearg(path, + &session->scpSend_command[cmd_len], + session->scpSend_command_len - cmd_len); + + session->scpSend_command[cmd_len] = '\0'; + session->scpSend_command_len = cmd_len + 1; + + _libssh2_debug(session, LIBSSH2_TRACE_SCP, + "Opening channel for SCP send"); + /* Allocate a channel */ + + session->scpSend_state = libssh2_NB_state_created; + } + + if (session->scpSend_state == libssh2_NB_state_created) { + session->scpSend_channel = + _libssh2_channel_open(session, "session", sizeof("session") - 1, + LIBSSH2_CHANNEL_WINDOW_DEFAULT, + LIBSSH2_CHANNEL_PACKET_DEFAULT, NULL, 0); + if (!session->scpSend_channel) { + if (libssh2_session_last_errno(session) != LIBSSH2_ERROR_EAGAIN) { + /* previous call set libssh2_session_last_error(), pass it + through */ + LIBSSH2_FREE(session, session->scpSend_command); + session->scpSend_command = NULL; + session->scpSend_state = libssh2_NB_state_idle; + } + else { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block starting up channel"); + } + return NULL; + } + + session->scpSend_state = libssh2_NB_state_sent; + } + + if (session->scpSend_state == libssh2_NB_state_sent) { + /* Request SCP for the desired file */ + rc = _libssh2_channel_process_startup(session->scpSend_channel, "exec", + sizeof("exec") - 1, + (char *) session->scpSend_command, + session->scpSend_command_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block requesting SCP startup"); + return NULL; + } + else if (rc) { + /* previous call set libssh2_session_last_error(), pass it + through */ + LIBSSH2_FREE(session, session->scpSend_command); + session->scpSend_command = NULL; + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Unknown error while getting error string"); + goto scp_send_error; + } + LIBSSH2_FREE(session, session->scpSend_command); + session->scpSend_command = NULL; + + session->scpSend_state = libssh2_NB_state_sent1; + } + + if (session->scpSend_state == libssh2_NB_state_sent1) { + /* Wait for ACK */ + rc = _libssh2_channel_read(session->scpSend_channel, 0, + (char *) session->scpSend_response, 1); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block waiting for response from remote"); + return NULL; + } + else if (rc < 0) { + _libssh2_error(session, rc, "SCP failure"); + goto scp_send_error; + } + else if(!rc) + /* remain in the same state */ + goto scp_send_empty_channel; + else if (session->scpSend_response[0] != 0) { + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid ACK response from remote"); + goto scp_send_error; + } + if (mtime || atime) { + /* Send mtime and atime to be used for file */ + session->scpSend_response_len = + snprintf((char *) session->scpSend_response, + LIBSSH2_SCP_RESPONSE_BUFLEN, "T%ld 0 %ld 0\n", + (long)mtime, (long)atime); + _libssh2_debug(session, LIBSSH2_TRACE_SCP, "Sent %s", + session->scpSend_response); + } + + session->scpSend_state = libssh2_NB_state_sent2; + } + + /* Send mtime and atime to be used for file */ + if (mtime || atime) { + if (session->scpSend_state == libssh2_NB_state_sent2) { + rc = _libssh2_channel_write(session->scpSend_channel, 0, + session->scpSend_response, + session->scpSend_response_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block sending time data for SCP file"); + return NULL; + } else if (rc != (int)session->scpSend_response_len) { + _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send time data for SCP file"); + goto scp_send_error; + } + + session->scpSend_state = libssh2_NB_state_sent3; + } + + if (session->scpSend_state == libssh2_NB_state_sent3) { + /* Wait for ACK */ + rc = _libssh2_channel_read(session->scpSend_channel, 0, + (char *) session->scpSend_response, 1); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block waiting for response"); + return NULL; + } + else if (rc < 0) { + _libssh2_error(session, rc, "SCP failure"); + goto scp_send_error; + } + else if(!rc) + /* remain in the same state */ + goto scp_send_empty_channel; + else if (session->scpSend_response[0] != 0) { + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid SCP ACK response"); + goto scp_send_error; + } + + session->scpSend_state = libssh2_NB_state_sent4; + } + } else { + if (session->scpSend_state == libssh2_NB_state_sent2) { + session->scpSend_state = libssh2_NB_state_sent4; + } + } + + if (session->scpSend_state == libssh2_NB_state_sent4) { + /* Send mode, size, and basename */ + const char *base = strrchr(path, '/'); + if (base) + base++; + else + base = path; + + session->scpSend_response_len = + snprintf((char *) session->scpSend_response, + LIBSSH2_SCP_RESPONSE_BUFLEN, "C0%o %" + LIBSSH2_INT64_T_FORMAT " %s\n", mode, + size, base); + _libssh2_debug(session, LIBSSH2_TRACE_SCP, "Sent %s", + session->scpSend_response); + + session->scpSend_state = libssh2_NB_state_sent5; + } + + if (session->scpSend_state == libssh2_NB_state_sent5) { + rc = _libssh2_channel_write(session->scpSend_channel, 0, + session->scpSend_response, + session->scpSend_response_len); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block send core file data for SCP file"); + return NULL; + } else if (rc != (int)session->scpSend_response_len) { + _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, + "Unable to send core file data for SCP file"); + goto scp_send_error; + } + + session->scpSend_state = libssh2_NB_state_sent6; + } + + if (session->scpSend_state == libssh2_NB_state_sent6) { + /* Wait for ACK */ + rc = _libssh2_channel_read(session->scpSend_channel, 0, + (char *) session->scpSend_response, 1); + if (rc == LIBSSH2_ERROR_EAGAIN) { + _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, + "Would block waiting for response"); + return NULL; + } + else if (rc < 0) { + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Invalid ACK response from remote"); + goto scp_send_error; + } + else if (rc == 0) + goto scp_send_empty_channel; + + else if (session->scpSend_response[0] != 0) { + size_t err_len; + char *err_msg; + + err_len = + _libssh2_channel_packet_data_len(session->scpSend_channel, 0); + err_msg = LIBSSH2_ALLOC(session, err_len + 1); + if (!err_msg) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "failed to get memory"); + goto scp_send_error; + } + + /* Read the remote error message */ + rc = _libssh2_channel_read(session->scpSend_channel, 0, + err_msg, err_len); + if (rc > 0) { + err_msg[err_len]=0; + _libssh2_debug(session, LIBSSH2_TRACE_SCP, + "got %02x %s", session->scpSend_response[0], + err_msg); + } + LIBSSH2_FREE(session, err_msg); + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "failed to send file"); + goto scp_send_error; + } + } + + session->scpSend_state = libssh2_NB_state_idle; + return session->scpSend_channel; + + scp_send_empty_channel: + /* the code only jumps here as a result of a zero read from channel_read() + so we check EOF status to avoid getting stuck in a loop */ + if(libssh2_channel_eof(session->scpSend_channel)) { + _libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, + "Unexpected channel close"); + } + else + return session->scpSend_channel; + /* fall-through */ + scp_send_error: + tmp_err_code = session->err_code; + tmp_err_msg = session->err_msg; + while (libssh2_channel_free(session->scpSend_channel) == + LIBSSH2_ERROR_EAGAIN); + session->err_code = tmp_err_code; + session->err_msg = tmp_err_msg; + session->scpSend_channel = NULL; + session->scpSend_state = libssh2_NB_state_idle; + return NULL; +} + +/* + * libssh2_scp_send_ex + * + * Send a file using SCP. Old API. + */ +LIBSSH2_API LIBSSH2_CHANNEL * +libssh2_scp_send_ex(LIBSSH2_SESSION *session, const char *path, int mode, + size_t size, long mtime, long atime) +{ + LIBSSH2_CHANNEL *ptr; + BLOCK_ADJUST_ERRNO(ptr, session, + scp_send(session, path, mode, size, + (time_t)mtime, (time_t)atime)); + return ptr; +} + +/* + * libssh2_scp_send64 + * + * Send a file using SCP + */ +LIBSSH2_API LIBSSH2_CHANNEL * +libssh2_scp_send64(LIBSSH2_SESSION *session, const char *path, int mode, + libssh2_int64_t size, time_t mtime, time_t atime) +{ + LIBSSH2_CHANNEL *ptr; + BLOCK_ADJUST_ERRNO(ptr, session, + scp_send(session, path, mode, size, mtime, atime)); + return ptr; +} |