Initial revision
This commit is contained in:
408
src/scp.c
Normal file
408
src/scp.c
Normal file
@@ -0,0 +1,408 @@
|
||||
/* Copyright (c) 2004, Sara Golemon <sarag@users.sourceforge.net>
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define LIBSSH2_SCP_RESPONSE_BUFLEN 256
|
||||
|
||||
/* {{{ libssh2_scp_recv
|
||||
* Open a channel and request a remote file via SCP
|
||||
*/
|
||||
LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session, char *path, struct stat *sb)
|
||||
{
|
||||
int path_len = strlen(path);
|
||||
unsigned char *command, response[LIBSSH2_SCP_RESPONSE_BUFLEN];
|
||||
unsigned long command_len = path_len + sizeof("scp -f "), response_len;
|
||||
LIBSSH2_CHANNEL *channel;
|
||||
long mode = 0, size = 0, mtime = 0, atime = 0;
|
||||
|
||||
if (sb) {
|
||||
command_len++;
|
||||
}
|
||||
|
||||
command = LIBSSH2_ALLOC(session, command_len);
|
||||
if (!command) {
|
||||
libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate a command buffer for scp session", 0);
|
||||
return NULL;
|
||||
}
|
||||
if (sb) {
|
||||
memcpy(command, "scp -pf ", sizeof("scp -pf ") - 1);
|
||||
memcpy(command + sizeof("scp -pf ") - 1, path, path_len);
|
||||
} else {
|
||||
memcpy(command, "scp -f ", sizeof("scp -f ") - 1);
|
||||
memcpy(command + sizeof("scp -f ") - 1, path, path_len);
|
||||
}
|
||||
command[command_len - 1] = '\0';
|
||||
|
||||
/* Allocate a channel */
|
||||
if ((channel = libssh2_channel_open_session(session)) == NULL) {
|
||||
LIBSSH2_FREE(session, command);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Request SCP for the desired file */
|
||||
if (libssh2_channel_process_startup(channel, "exec", sizeof("exec") - 1, command, command_len)) {
|
||||
LIBSSH2_FREE(session, command);
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
LIBSSH2_FREE(session, command);
|
||||
|
||||
/* SCP ACK */
|
||||
response[0] = '\0';
|
||||
if (libssh2_channel_write(channel, response, 1) != 1) {
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Parse SCP response */
|
||||
response_len = 0;
|
||||
while (sb && (response_len < LIBSSH2_SCP_RESPONSE_BUFLEN)) {
|
||||
unsigned char *s, *p;
|
||||
|
||||
if (libssh2_channel_read(channel, response + response_len, 1) <= 0) {
|
||||
/* Timeout, give up */
|
||||
libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Timed out waiting for SCP response", 0);
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
response_len++;
|
||||
|
||||
if (response[0] != 'T') {
|
||||
libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid data in SCP response, missing Time data", 0);
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((response_len > 1) &&
|
||||
((response[response_len-1] < '0') || (response[response_len-1] > '9')) &&
|
||||
(response[response_len-1] != ' ') &&
|
||||
(response[response_len-1] != '\r') &&
|
||||
(response[response_len-1] != '\n')) {
|
||||
libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid data in SCP response", 0);
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((response_len < 9) || (response[response_len-1] != '\n')) {
|
||||
if (response_len == LIBSSH2_SCP_RESPONSE_BUFLEN) {
|
||||
/* You had your chance */
|
||||
libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Unterminated response from SCP server", 0);
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
/* 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 ((response[response_len-1] == '\r') || (response[response_len-1] == '\n')) response_len--;
|
||||
response[response_len] = '\0';
|
||||
|
||||
if (response_len < 8) {
|
||||
/* EOL came too soon */
|
||||
libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, too short", 0);
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s = 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 mtime", 0);
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*(p++) = '\0';
|
||||
/* Make sure we don't get fooled by leftover values */
|
||||
errno = 0;
|
||||
mtime = strtol(s, NULL, 10);
|
||||
if (errno) {
|
||||
libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, invalid mtime", 0);
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
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, malformed mtime.usec", 0);
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Ignore mtime.usec */
|
||||
s++;
|
||||
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, too short or malformed", 0);
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*(p++) = '\0';
|
||||
/* Make sure we don't get fooled by leftover values */
|
||||
errno = 0;
|
||||
atime = strtol(s, NULL, 10);
|
||||
if (errno) {
|
||||
libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, invalid atime", 0);
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* SCP ACK */
|
||||
response[0] = '\0';
|
||||
if (libssh2_channel_write(channel, response, 1) != 1) {
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* We *should* check that atime.usec is valid, but why let that stop use? */
|
||||
break;
|
||||
}
|
||||
|
||||
response_len = 0;
|
||||
while (response_len < LIBSSH2_SCP_RESPONSE_BUFLEN) {
|
||||
char *s, *p, *e = NULL;
|
||||
|
||||
if (libssh2_channel_read(channel, response + response_len, 1) <= 0) {
|
||||
/* Timeout, give up */
|
||||
libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Timed out waiting for SCP response", 0);
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
response_len++;
|
||||
|
||||
if (response[0] != 'C') {
|
||||
libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server", 0);
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((response_len > 1) &&
|
||||
(response[response_len-1] != '\r') &&
|
||||
(response[response_len-1] != '\n') &&
|
||||
((response[response_len-1] < 32) || (response[response_len-1] > 126))) {
|
||||
libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid data in SCP response", 0);
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((response_len < 7) || (response[response_len-1] != '\n')) {
|
||||
if (response_len == LIBSSH2_SCP_RESPONSE_BUFLEN) {
|
||||
/* You had your chance */
|
||||
libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Unterminated response from SCP server", 0);
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
/* 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 ((response[response_len-1] == '\r') || (response[response_len-1] == '\n')) response_len--;
|
||||
response[response_len] = '\0';
|
||||
|
||||
if (response_len < 6) {
|
||||
/* EOL came too soon */
|
||||
libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, too short", 0);
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s = 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", 0);
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*(p++) = '\0';
|
||||
/* Make sure we don't get fooled by leftover values */
|
||||
errno = 0;
|
||||
mode = strtol(s, &e, 8);
|
||||
if ((e && *e) || errno) {
|
||||
libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, invalid mode", 0);
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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", 0);
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*(s++) = '\0';
|
||||
/* Make sure we don't get fooled by leftover values */
|
||||
errno = 0;
|
||||
size = strtol(p, &e, 10);
|
||||
if ((e && *e) || errno) {
|
||||
libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, invalid size", 0);
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* SCP ACK */
|
||||
response[0] = '\0';
|
||||
if (libssh2_channel_write(channel, response, 1) != 1) {
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* We *should* check that basename is valid, but why let that stop us? */
|
||||
break;
|
||||
}
|
||||
|
||||
if (sb) {
|
||||
memset(sb, 0, sizeof(struct stat));
|
||||
|
||||
sb->st_mtime = mtime;
|
||||
sb->st_atime = atime;
|
||||
sb->st_size = size;
|
||||
sb->st_mode = mode;
|
||||
}
|
||||
/* Let the data BEGIN! */
|
||||
|
||||
return channel;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ libssh2_scp_send_ex
|
||||
* Send a file using SCP
|
||||
*/
|
||||
LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_send_ex(LIBSSH2_SESSION *session, char *path, int mode, size_t size, long mtime, long atime)
|
||||
{
|
||||
int path_len = strlen(path);
|
||||
unsigned char *command, *base, response[LIBSSH2_SCP_RESPONSE_BUFLEN];
|
||||
unsigned long response_len, command_len = path_len + sizeof("scp -t ");
|
||||
LIBSSH2_CHANNEL *channel;
|
||||
|
||||
if (mtime || atime) {
|
||||
command_len++;
|
||||
}
|
||||
|
||||
command = LIBSSH2_ALLOC(session, command_len);
|
||||
if (!command) {
|
||||
libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate a command buffer for scp session", 0);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (mtime || atime) {
|
||||
memcpy(command, "scp -pt ", sizeof("scp -pt ") - 1);
|
||||
memcpy(command + sizeof("scp -pt ") - 1, path, path_len);
|
||||
} else {
|
||||
memcpy(command, "scp -t ", sizeof("scp -t ") - 1);
|
||||
memcpy(command + sizeof("scp -t ") - 1, path, path_len);
|
||||
}
|
||||
command[command_len - 1] = '\0';
|
||||
|
||||
/* Allocate a channel */
|
||||
if ((channel = libssh2_channel_open_session(session)) == NULL) {
|
||||
LIBSSH2_FREE(session, command);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Request SCP for the desired file */
|
||||
if (libssh2_channel_process_startup(channel, "exec", sizeof("exec") - 1, command, command_len)) {
|
||||
LIBSSH2_FREE(session, command);
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
LIBSSH2_FREE(session, command);
|
||||
|
||||
/* Wait for ACK */
|
||||
if ((libssh2_channel_read(channel, response, 1) <= 0) || (response[0] != 0)) {
|
||||
libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid ACK response from remote", 0);
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Send mtime and atime to be used for file */
|
||||
if (mtime || atime) {
|
||||
response_len = snprintf(response, LIBSSH2_SCP_RESPONSE_BUFLEN, "T%ld 0 %ld 0\n", mtime, atime);
|
||||
if (libssh2_channel_write(channel, response, response_len) != response_len) {
|
||||
libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send time data for SCP file", 0);
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
/* Wait for ACK */
|
||||
if ((libssh2_channel_read(channel, response, 1) <= 0) || (response[0] != 0)) {
|
||||
libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid ACK response from remote", 0);
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Send mode, size, and basename */
|
||||
base = strrchr(path, '/');
|
||||
if (base) {
|
||||
base++;
|
||||
} else {
|
||||
base = path;
|
||||
}
|
||||
|
||||
response_len = snprintf(response, LIBSSH2_SCP_RESPONSE_BUFLEN, "C0%o %lu %s\n", mode, (unsigned long)size, base);
|
||||
if (libssh2_channel_write(channel, response, response_len) != response_len) {
|
||||
libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send core file data for SCP file", 0);
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
/* Wait for ACK */
|
||||
if ((libssh2_channel_read(channel, response, 1) <= 0) || (response[0] != 0)) {
|
||||
libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid ACK response from remote", 0);
|
||||
libssh2_channel_free(channel);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Ready to send file */
|
||||
|
||||
return channel;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
Reference in New Issue
Block a user