1
0
mirror of https://github.com/moparisthebest/curl synced 2024-12-22 08:08:50 -05:00

James Housley did lots of work and introduced SFTP downloads.

This commit is contained in:
Daniel Stenberg 2006-11-24 22:14:39 +00:00
parent bcd8a3b240
commit a634f64400
8 changed files with 619 additions and 130 deletions

View File

@ -6,6 +6,9 @@
Changelog Changelog
Daniel (24 November 2006)
- James Housley did lots of work and introduced SFTP downloads.
Daniel (13 November 2006) Daniel (13 November 2006)
- Ron in bug #1595348 (http://curl.haxx.se/bug/view.cgi?id=1595348) pointed - Ron in bug #1595348 (http://curl.haxx.se/bug/view.cgi?id=1595348) pointed
out a stack overwrite (and the corresponding fix) on 64bit Windows when out a stack overwrite (and the corresponding fix) on 64bit Windows when

View File

@ -11,7 +11,7 @@ Curl and libcurl 7.16.1
This release includes the following changes: This release includes the following changes:
o Support for SCP added o Support for SCP and SFTP were added
This release includes the following bugfixes: This release includes the following bugfixes:

View File

@ -364,6 +364,8 @@ CURLcode Curl_write(struct connectdata *conn,
#ifdef USE_LIBSSH2 #ifdef USE_LIBSSH2
else if (conn->protocol & PROT_SCP) else if (conn->protocol & PROT_SCP)
bytes_written = Curl_scp_send(conn, num, mem, len); bytes_written = Curl_scp_send(conn, num, mem, len);
else if (conn->protocol & PROT_SFTP)
bytes_written = Curl_sftp_send(conn, num, mem, len);
#endif /* !USE_LIBSSH2 */ #endif /* !USE_LIBSSH2 */
else if(conn->sec_complete) else if(conn->sec_complete)
/* only TRUE if krb4 enabled */ /* only TRUE if krb4 enabled */
@ -522,6 +524,9 @@ int Curl_read(struct connectdata *conn, /* connection data */
/* TODO: return CURLE_OK also for nread <= 0 /* TODO: return CURLE_OK also for nread <= 0
read failures and timeouts ? */ read failures and timeouts ? */
} }
else if (conn->protocol & PROT_SFTP) {
nread = Curl_sftp_recv(conn, num, conn->master_buffer, bytesfromsocket);
}
#endif /* !USE_LIBSSH2 */ #endif /* !USE_LIBSSH2 */
else { else {
if(conn->sec_complete) if(conn->sec_complete)

659
lib/ssh.c
View File

@ -51,6 +51,10 @@
#include <sys/stat.h> #include <sys/stat.h>
#endif #endif
#ifdef HAVE_TIME_H
#include <time.h>
#endif
#ifdef WIN32 #ifdef WIN32
#else /* probably some kind of unix */ #else /* probably some kind of unix */
@ -130,6 +134,10 @@
#include "memdebug.h" #include "memdebug.h"
#endif #endif
#ifndef LIBSSH2_SFTP_S_IRUSR
/* Here's a work-around for those of you who happend to run a libssh2 version
that is 0.14 or older. We should remove this kludge as soon as we can
require a more recent libssh2 release. */
#ifndef S_IRGRP #ifndef S_IRGRP
#define S_IRGRP 0 #define S_IRGRP 0
#endif #endif
@ -138,16 +146,31 @@
#define S_IROTH 0 #define S_IROTH 0
#endif #endif
#define LIBSSH2_SFTP_S_IRUSR S_IRUSR
#define LIBSSH2_SFTP_S_IWUSR S_IWUSR
#define LIBSSH2_SFTP_S_IRGRP S_IRGRP
#define LIBSSH2_SFTP_S_IROTH S_IROTH
#define LIBSSH2_SFTP_S_IRUSR S_IRUSR
#define LIBSSH2_SFTP_S_IWUSR S_IWUSR
#define LIBSSH2_SFTP_S_IRGRP S_IRGRP
#define LIBSSH2_SFTP_S_IROTH S_IROTH
#define LIBSSH2_SFTP_S_IFMT S_IFMT
#define LIBSSH2_SFTP_S_IFDIR S_IFDIR
#define LIBSSH2_SFTP_S_IFLNK S_IFLNK
#define LIBSSH2_SFTP_S_IFSOCK S_IFSOCK
#define LIBSSH2_SFTP_S_IFCHR S_IFCHR
#define LIBSSH2_SFTP_S_IFBLK S_IFBLK
#define LIBSSH2_SFTP_S_IXUSR S_IXUSR
#define LIBSSH2_SFTP_S_IWGRP S_IWGRP
#define LIBSSH2_SFTP_S_IXGRP S_IXGRP
#define LIBSSH2_SFTP_S_IWOTH S_IWOTH
#define LIBSSH2_SFTP_S_IXOTH S_IXOTH
#endif
static LIBSSH2_ALLOC_FUNC(libssh2_malloc); static LIBSSH2_ALLOC_FUNC(libssh2_malloc);
static LIBSSH2_REALLOC_FUNC(libssh2_realloc); static LIBSSH2_REALLOC_FUNC(libssh2_realloc);
static LIBSSH2_FREE_FUNC(libssh2_free); static LIBSSH2_FREE_FUNC(libssh2_free);
struct auth_
{
const char * user;
const char * pw;
} auth;
static void static void
kbd_callback(const char *name, int name_len, const char *instruction, kbd_callback(const char *name, int name_len, const char *instruction,
int instruction_len, int num_prompts, int instruction_len, int num_prompts,
@ -155,6 +178,8 @@ kbd_callback(const char *name, int name_len, const char *instruction,
LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses, LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses,
void **abstract) void **abstract)
{ {
struct SSHPROTO *ssh = (struct SSHPROTO *)*abstract;
#ifdef CURL_LIBSSH2_DEBUG #ifdef CURL_LIBSSH2_DEBUG
fprintf(stderr, "name=%s\n", name); fprintf(stderr, "name=%s\n", name);
fprintf(stderr, "name_len=%d\n", name_len); fprintf(stderr, "name_len=%d\n", name_len);
@ -163,22 +188,21 @@ kbd_callback(const char *name, int name_len, const char *instruction,
fprintf(stderr, "num_prompts=%d\n", num_prompts); fprintf(stderr, "num_prompts=%d\n", num_prompts);
#endif /* CURL_LIBSSH2_DEBUG */ #endif /* CURL_LIBSSH2_DEBUG */
if (num_prompts == 1) { if (num_prompts == 1) {
responses[0].text = strdup(auth.pw); responses[0].text = strdup(ssh->passwd);
responses[0].length = strlen(auth.pw); responses[0].length = strlen(ssh->passwd);
} }
(void)prompts; (void)prompts;
(void)abstract; (void)abstract;
return;
} /* kbd_callback */ } /* kbd_callback */
static CURLcode libssh2_error_to_CURLE(struct connectdata *conn) static CURLcode libssh2_error_to_CURLE(struct connectdata *conn)
{ {
int errorcode; int errorcode;
struct SCPPROTO *scp = conn->data->reqdata.proto.scp; struct SSHPROTO *scp = conn->data->reqdata.proto.ssh;
/* Get the libssh2 error code and string */ /* Get the libssh2 error code and string */
errorcode = libssh2_session_last_error(scp->scpSession, &scp->errorstr, NULL, errorcode = libssh2_session_last_error(scp->ssh_session, &scp->errorstr,
0); NULL, 0);
if (errorcode == LIBSSH2_FX_OK) if (errorcode == LIBSSH2_FX_OK)
return CURLE_OK; return CURLE_OK;
@ -209,102 +233,96 @@ static LIBSSH2_FREE_FUNC(libssh2_free)
(void)abstract; (void)abstract;
} }
static CURLcode scp_init(struct connectdata *conn) static CURLcode ssh_init(struct connectdata *conn)
{ {
struct SessionHandle *data = conn->data; struct SessionHandle *data = conn->data;
struct SCPPROTO *scp; struct SSHPROTO *ssh;
if (data->reqdata.proto.scp) if (data->reqdata.proto.ssh)
return CURLE_OK; return CURLE_OK;
scp = (struct SCPPROTO *)calloc(sizeof(struct SCPPROTO), 1); ssh = (struct SSHPROTO *)calloc(sizeof(struct SSHPROTO), 1);
if (!scp) if (!ssh)
return CURLE_OUT_OF_MEMORY; return CURLE_OUT_OF_MEMORY;
data->reqdata.proto.scp = scp; data->reqdata.proto.ssh = ssh;
/* get some initial data into the scp struct */ /* get some initial data into the ssh struct */
scp->bytecountp = &data->reqdata.keep.bytecount; ssh->bytecountp = &data->reqdata.keep.bytecount;
/* no need to duplicate them, this connectdata struct won't change */ /* no need to duplicate them, this connectdata struct won't change */
scp->user = conn->user; ssh->user = conn->user;
scp->passwd = conn->passwd; ssh->passwd = conn->passwd;
scp->errorstr = NULL; ssh->errorstr = NULL;
ssh->ssh_session = NULL;
ssh->ssh_channel = NULL;
ssh->sftp_session = NULL;
ssh->sftp_handle = NULL;
return CURLE_OK; return CURLE_OK;
} }
/* /*
* Curl_scp_connect() gets called from Curl_protocol_connect() to allow us to * Curl_ssh_connect() gets called from Curl_protocol_connect() to allow us to
* do protocol-specific actions at connect-time. * do protocol-specific actions at connect-time.
*/ */
CURLcode Curl_scp_connect(struct connectdata *conn, bool *done) CURLcode Curl_ssh_connect(struct connectdata *conn, bool *done)
{ {
int i; int i;
struct SCPPROTO *scp; struct SSHPROTO *ssh;
const char *fingerprint; const char *fingerprint;
const char *authlist; const char *authlist;
char *home; char *home;
char rsa_pub[PATH_MAX]; char rsa_pub[PATH_MAX];
char rsa[PATH_MAX]; char rsa[PATH_MAX];
char tempHome[PATH_MAX];
curl_socket_t sock; curl_socket_t sock;
char *real_path; char *real_path;
char *working_path; char *working_path;
int working_path_len;
bool authed = FALSE; bool authed = FALSE;
CURLcode result; CURLcode result;
struct SessionHandle *data = conn->data; struct SessionHandle *data = conn->data;
result = scp_init(conn); rsa_pub[0] = rsa[0] = '\0';
result = ssh_init(conn);
if (result) if (result)
return result; return result;
rsa_pub[0] = rsa[0] = '\0'; ssh = data->reqdata.proto.ssh;
scp = data->reqdata.proto.scp; working_path = curl_easy_unescape(data, data->reqdata.path, 0,
&working_path_len);
working_path = curl_easy_unescape(data, data->reqdata.path, 0, NULL);
if (!working_path) if (!working_path)
return CURLE_OUT_OF_MEMORY; return CURLE_OUT_OF_MEMORY;
real_path = (char *)malloc(strlen(working_path)+1);
if (real_path == NULL) {
Curl_safefree(working_path);
return CURLE_OUT_OF_MEMORY;
}
/* Check for /~/ , indicating realative to the users home directory */
if (working_path[1] == '~')
/* It is referenced to the home directory, so strip the leading '/' */
memcpy(real_path, working_path+1, 1+strlen(working_path)-1);
else
memcpy(real_path, working_path, 1+strlen(working_path));
Curl_safefree(working_path);
scp->path = real_path;
#ifdef CURL_LIBSSH2_DEBUG #ifdef CURL_LIBSSH2_DEBUG
if (scp->user) { if (ssh->user) {
infof(data, "User: %s\n", scp->user); infof(data, "User: %s\n", ssh->user);
} }
if (scp->passwd) { if (ssh->passwd) {
infof(data, "Password: %s\n", scp->passwd); infof(data, "Password: %s\n", ssh->passwd);
} }
#endif /* CURL_LIBSSH2_DEBUG */ #endif /* CURL_LIBSSH2_DEBUG */
sock = conn->sock[FIRSTSOCKET]; sock = conn->sock[FIRSTSOCKET];
scp->scpSession = libssh2_session_init_ex(libssh2_malloc, libssh2_free, ssh->ssh_session = libssh2_session_init_ex(libssh2_malloc, libssh2_free,
libssh2_realloc, NULL); libssh2_realloc, ssh);
if (scp->scpSession == NULL) { if (ssh->ssh_session == NULL) {
failf(data, "Failure initialising ssh session\n"); failf(data, "Failure initialising ssh session\n");
Curl_safefree(scp->path); Curl_safefree(ssh->path);
return CURLE_FAILED_INIT; return CURLE_FAILED_INIT;
} }
#ifdef CURL_LIBSSH2_DEBUG #ifdef CURL_LIBSSH2_DEBUG
infof(data, "Socket: %d\n", sock); infof(data, "SSH socket: %d\n", sock);
#endif /* CURL_LIBSSH2_DEBUG */ #endif /* CURL_LIBSSH2_DEBUG */
if (libssh2_session_startup(scp->scpSession, sock)) { if (libssh2_session_startup(ssh->ssh_session, sock)) {
failf(data, "Failure establishing ssh session\n"); failf(data, "Failure establishing ssh session\n");
libssh2_session_free(scp->scpSession); libssh2_session_free(ssh->ssh_session);
Curl_safefree(scp->path); ssh->ssh_session = NULL;
Curl_safefree(ssh->path);
return CURLE_FAILED_INIT; return CURLE_FAILED_INIT;
} }
@ -314,7 +332,7 @@ CURLcode Curl_scp_connect(struct connectdata *conn, bool *done)
* up to us. As for know not much is implemented, besides showing how to * up to us. As for know not much is implemented, besides showing how to
* get the fingerprint. * get the fingerprint.
*/ */
fingerprint = libssh2_hostkey_hash(scp->scpSession, fingerprint = libssh2_hostkey_hash(ssh->ssh_session,
LIBSSH2_HOSTKEY_HASH_MD5); LIBSSH2_HOSTKEY_HASH_MD5);
#ifdef CURL_LIBSSH2_DEBUG #ifdef CURL_LIBSSH2_DEBUG
@ -336,8 +354,8 @@ CURLcode Curl_scp_connect(struct connectdata *conn, bool *done)
* presumably with a blank username. That won't work in my experience. * presumably with a blank username. That won't work in my experience.
* So always specify it here. * So always specify it here.
*/ */
authlist = libssh2_userauth_list(scp->scpSession, scp->user, authlist = libssh2_userauth_list(ssh->ssh_session, ssh->user,
strlen(scp->user)); strlen(ssh->user));
/* /*
* Check the supported auth types in the order I feel is most secure with the * Check the supported auth types in the order I feel is most secure with the
@ -351,21 +369,20 @@ CURLcode Curl_scp_connect(struct connectdata *conn, bool *done)
if (data->set.ssh_public_key) if (data->set.ssh_public_key)
snprintf(rsa_pub, sizeof(rsa_pub), "%s", data->set.ssh_public_key); snprintf(rsa_pub, sizeof(rsa_pub), "%s", data->set.ssh_public_key);
else if(home) else if (home)
snprintf(rsa_pub, sizeof(rsa_pub), "%s/.ssh/id_dsa.pub", home); snprintf(rsa_pub, sizeof(rsa_pub), "%s/.ssh/id_dsa.pub", home);
if(data->set.ssh_private_key) if (data->set.ssh_private_key)
snprintf(rsa, sizeof(rsa), "%s", data->set.ssh_private_key); snprintf(rsa, sizeof(rsa), "%s", data->set.ssh_private_key);
else if(home) { else if (home)
snprintf(rsa, sizeof(rsa), "%s/.ssh/id_dsa", home); snprintf(rsa, sizeof(rsa), "%s/.ssh/id_dsa", home);
}
curl_free(home); curl_free(home);
if (rsa_pub[0]) { if (rsa_pub[0]) {
/* The function below checks if the files exists, no need to stat() here. /* The function below checks if the files exists, no need to stat() here.
*/ */
if (libssh2_userauth_publickey_fromfile(scp->scpSession, scp->user, if (libssh2_userauth_publickey_fromfile(ssh->ssh_session, ssh->user,
rsa_pub, rsa, "") == 0) { rsa_pub, rsa, "") == 0) {
authed = TRUE; authed = TRUE;
} }
@ -374,21 +391,17 @@ CURLcode Curl_scp_connect(struct connectdata *conn, bool *done)
if (!authed && if (!authed &&
(data->set.ssh_auth_types & CURLSSH_AUTH_PASSWORD) && (data->set.ssh_auth_types & CURLSSH_AUTH_PASSWORD) &&
(strstr(authlist, "password") != NULL)) { (strstr(authlist, "password") != NULL)) {
if (libssh2_userauth_password(scp->scpSession, scp->user, scp->passwd) if (!libssh2_userauth_password(ssh->ssh_session, ssh->user, ssh->passwd))
== 0) {
authed = TRUE; authed = TRUE;
} }
}
if (!authed && (data->set.ssh_auth_types & CURLSSH_AUTH_HOST) && if (!authed && (data->set.ssh_auth_types & CURLSSH_AUTH_HOST) &&
(strstr(authlist, "hostbased") != NULL)) { (strstr(authlist, "hostbased") != NULL)) {
} }
if (!authed && (data->set.ssh_auth_types & CURLSSH_AUTH_KEYBOARD) if (!authed && (data->set.ssh_auth_types & CURLSSH_AUTH_KEYBOARD)
&& (strstr(authlist, "keyboard-interactive") != NULL)) { && (strstr(authlist, "keyboard-interactive") != NULL)) {
/* Authentication failed. Continue with keyboard-interactive now. */ /* Authentication failed. Continue with keyboard-interactive now. */
auth.user = scp->user; if (libssh2_userauth_keyboard_interactive_ex(ssh->ssh_session, ssh->user,
auth.pw = scp->passwd; strlen(ssh->user),
if (libssh2_userauth_keyboard_interactive_ex(scp->scpSession, scp->user,
strlen(scp->user),
&kbd_callback) == 0) { &kbd_callback) == 0) {
authed = TRUE; authed = TRUE;
} }
@ -396,8 +409,9 @@ CURLcode Curl_scp_connect(struct connectdata *conn, bool *done)
if (!authed) { if (!authed) {
failf(data, "Authentication failure\n"); failf(data, "Authentication failure\n");
libssh2_session_free(scp->scpSession); libssh2_session_free(ssh->ssh_session);
Curl_safefree(scp->path); ssh->ssh_session = NULL;
Curl_safefree(ssh->path);
return CURLE_FAILED_INIT; return CURLE_FAILED_INIT;
} }
@ -407,6 +421,96 @@ CURLcode Curl_scp_connect(struct connectdata *conn, bool *done)
conn->sockfd = sock; conn->sockfd = sock;
conn->writesockfd = CURL_SOCKET_BAD; conn->writesockfd = CURL_SOCKET_BAD;
if (conn->protocol == PROT_SFTP) {
/*
* Start the libssh2 sftp session
*/
ssh->sftp_session = libssh2_sftp_init(ssh->ssh_session);
if (ssh->sftp_session == NULL) {
failf(data, "Failure initialising sftp session\n");
libssh2_sftp_shutdown(ssh->sftp_session);
ssh->sftp_session = NULL;
libssh2_session_free(ssh->ssh_session);
ssh->ssh_session = NULL;
return CURLE_FAILED_INIT;
}
/*
* Get the "home" directory
*/
i = libssh2_sftp_realpath(ssh->sftp_session, ".", tempHome, PATH_MAX-1);
if (i > 0) {
/* It seems that this string is not always NULL terminated */
tempHome[i] = '\0';
ssh->homedir = (char *)strdup(tempHome);
if (!ssh->homedir) {
libssh2_sftp_shutdown(ssh->sftp_session);
ssh->sftp_session = NULL;
libssh2_session_free(ssh->ssh_session);
ssh->ssh_session = NULL;
return CURLE_OUT_OF_MEMORY;
}
}
else {
/* Return the error type */
i = libssh2_sftp_last_error(ssh->sftp_session);
DEBUGF(infof(data, "error = %d\n", i));
}
}
/* Check for /~/ , indicating realative to the users home directory */
if (conn->protocol == PROT_SCP) {
real_path = (char *)malloc(working_path_len+1);
if (real_path == NULL) {
Curl_safefree(working_path);
libssh2_session_free(ssh->ssh_session);
ssh->ssh_session = NULL;
return CURLE_OUT_OF_MEMORY;
}
if (working_path[1] == '~')
/* It is referenced to the home directory, so strip the leading '/' */
memcpy(real_path, working_path+1, 1 + working_path_len-1);
else
memcpy(real_path, working_path, 1 + working_path_len);
}
else if (conn->protocol == PROT_SFTP) {
if (working_path[1] == '~') {
real_path = (char *)malloc(strlen(ssh->homedir) +
working_path_len + 1);
if (real_path == NULL) {
libssh2_sftp_shutdown(ssh->sftp_session);
ssh->sftp_session = NULL;
libssh2_session_free(ssh->ssh_session);
ssh->ssh_session = NULL;
Curl_safefree(working_path);
return CURLE_OUT_OF_MEMORY;
}
/* It is referenced to the home directory, so strip the leading '/' */
memcpy(real_path, ssh->homedir, strlen(ssh->homedir));
real_path[strlen(ssh->homedir)] = '/';
real_path[strlen(ssh->homedir)+1] = '\0';
if (working_path_len > 3) {
memcpy(real_path+strlen(ssh->homedir)+1, working_path + 3,
1 + working_path_len -3);
}
}
else {
real_path = (char *)malloc(working_path_len+1);
if (real_path == NULL) {
libssh2_session_free(ssh->ssh_session);
ssh->ssh_session = NULL;
Curl_safefree(working_path);
return CURLE_OUT_OF_MEMORY;
}
memcpy(real_path, working_path, 1+working_path_len);
}
}
else
return CURLE_FAILED_INIT;
Curl_safefree(working_path);
ssh->path = real_path;
*done = TRUE; *done = TRUE;
return CURLE_OK; return CURLE_OK;
} }
@ -414,7 +518,7 @@ CURLcode Curl_scp_connect(struct connectdata *conn, bool *done)
CURLcode Curl_scp_do(struct connectdata *conn, bool *done) CURLcode Curl_scp_do(struct connectdata *conn, bool *done)
{ {
struct stat sb; struct stat sb;
struct SCPPROTO *scp = conn->data->reqdata.proto.scp; struct SSHPROTO *scp = conn->data->reqdata.proto.ssh;
CURLcode res = CURLE_OK; CURLcode res = CURLE_OK;
*done = TRUE; /* unconditionally */ *done = TRUE; /* unconditionally */
@ -426,23 +530,27 @@ CURLcode Curl_scp_do(struct connectdata *conn, bool *done)
* If this is not done the destination file will be named the * If this is not done the destination file will be named the
* same name as the last directory in the path. * same name as the last directory in the path.
*/ */
scp->scpChannel = libssh2_scp_send_ex(scp->scpSession, scp->path, scp->ssh_channel = libssh2_scp_send_ex(scp->ssh_session, scp->path,
S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, LIBSSH2_SFTP_S_IRUSR|
LIBSSH2_SFTP_S_IWUSR|
LIBSSH2_SFTP_S_IRGRP|
LIBSSH2_SFTP_S_IROTH,
conn->data->set.infilesize, 0, 0); conn->data->set.infilesize, 0, 0);
if (scp->scpChannel == NULL) { if (!scp->ssh_channel)
return CURLE_FAILED_INIT; return CURLE_FAILED_INIT;
}
conn->writesockfd = conn->sockfd; /* upload data */
conn->sockfd = CURL_SOCKET_BAD; res = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, FIRSTSOCKET, NULL);
} }
else { else {
/* /*
* We must check the remote file, if it is a directory I have no idea * We must check the remote file, if it is a directory no vaules will
* what I will do until the scp "-r" option is supported * be set in sb
*/ */
curl_off_t bytecount;
memset(&sb, 0, sizeof(struct stat)); memset(&sb, 0, sizeof(struct stat));
if ((scp->scpChannel = libssh2_scp_recv(scp->scpSession, scp->path, &sb)) scp->ssh_channel = libssh2_scp_recv(scp->ssh_session, scp->path, &sb);
== NULL) { if (!scp->ssh_channel) {
if ((sb.st_mode == 0) && (sb.st_atime == 0) && (sb.st_mtime == 0) && if ((sb.st_mode == 0) && (sb.st_atime == 0) && (sb.st_mtime == 0) &&
(sb.st_size == 0)) { (sb.st_size == 0)) {
/* Since sb is still empty, it is likely the file was not found */ /* Since sb is still empty, it is likely the file was not found */
@ -450,8 +558,10 @@ CURLcode Curl_scp_do(struct connectdata *conn, bool *done)
} }
return libssh2_error_to_CURLE(conn); return libssh2_error_to_CURLE(conn);
} }
conn->data->reqdata.size = sb.st_size; /* download data */
conn->data->reqdata.maxdownload = sb.st_size; bytecount = (curl_off_t) sb.st_size;
res = Curl_setup_transfer(conn, FIRSTSOCKET,
bytecount, FALSE, NULL, -1, NULL);
} }
return res; return res;
@ -459,24 +569,25 @@ CURLcode Curl_scp_do(struct connectdata *conn, bool *done)
CURLcode Curl_scp_done(struct connectdata *conn, CURLcode status) CURLcode Curl_scp_done(struct connectdata *conn, CURLcode status)
{ {
struct SCPPROTO *scp = conn->data->reqdata.proto.scp; struct SSHPROTO *scp = conn->data->reqdata.proto.ssh;
Curl_safefree(scp->freepath); Curl_safefree(scp->path);
scp->freepath = NULL; scp->path = NULL;
if (scp->scpChannel) { if (scp->ssh_channel) {
if (libssh2_channel_close(scp->scpChannel) < 0) { if (libssh2_channel_close(scp->ssh_channel) < 0) {
failf(conn->data, "Failed to stop libssh2 channel subsystem\n"); infof(conn->data, "Failed to stop libssh2 channel subsystem\n");
} }
} }
if (scp->scpSession) { if (scp->ssh_session) {
libssh2_session_disconnect(scp->scpSession, "Shutdown"); libssh2_session_disconnect(scp->ssh_session, "Shutdown");
libssh2_session_free(scp->scpSession); libssh2_session_free(scp->ssh_session);
scp->ssh_session = NULL;
} }
free(conn->data->reqdata.proto.scp); free(conn->data->reqdata.proto.ssh);
conn->data->reqdata.proto.scp = NULL; conn->data->reqdata.proto.ssh = NULL;
Curl_pgrsDone(conn); Curl_pgrsDone(conn);
(void)status; /* unused */ (void)status; /* unused */
@ -485,12 +596,18 @@ CURLcode Curl_scp_done(struct connectdata *conn, CURLcode status)
} }
/* return number of received (decrypted) bytes */ /* return number of received (decrypted) bytes */
int Curl_scp_send(struct connectdata *conn, int sockindex, ssize_t Curl_scp_send(struct connectdata *conn, int sockindex,
void *mem, size_t len) void *mem, size_t len)
{ {
ssize_t nwrite; ssize_t nwrite;
nwrite = libssh2_channel_write(conn->data->reqdata.proto.scp->scpChannel, /* libssh2_channel_write() returns int
*
* NOTE: we should not store nor rely on connection-related data to be
* in the SessionHandle struct
*/
nwrite = (ssize_t)
libssh2_channel_write(conn->data->reqdata.proto.ssh->ssh_channel,
mem, len); mem, len);
(void)sockindex; (void)sockindex;
return nwrite; return nwrite;
@ -500,15 +617,351 @@ int Curl_scp_send(struct connectdata *conn, int sockindex,
* If the read would block (EWOULDBLOCK) we return -1. Otherwise we return * If the read would block (EWOULDBLOCK) we return -1. Otherwise we return
* a regular CURLcode value. * a regular CURLcode value.
*/ */
int Curl_scp_recv(struct connectdata *conn, int sockindex, ssize_t Curl_scp_recv(struct connectdata *conn, int sockindex,
char *mem, size_t len) char *mem, size_t len)
{ {
ssize_t nread; ssize_t nread;
nread = libssh2_channel_read(conn->data->reqdata.proto.scp->scpChannel, /* libssh2_channel_read() returns int
*
* NOTE: we should not store nor rely on connection-related data to be
* in the SessionHandle struct
*/
nread = (ssize_t)
libssh2_channel_read(conn->data->reqdata.proto.ssh->ssh_channel,
mem, len); mem, len);
(void)sockindex; (void)sockindex;
return nread; return nread;
} }
/*
* =============== SFTP ===============
*/
CURLcode Curl_sftp_do(struct connectdata *conn, bool *done)
{
LIBSSH2_SFTP_ATTRIBUTES attrs;
struct SSHPROTO *sftp = conn->data->reqdata.proto.ssh;
CURLcode res = CURLE_OK;
struct SessionHandle *data = conn->data;
curl_off_t bytecount = 0;
char *buf = data->state.buffer;
*done = TRUE; /* unconditionally */
if (data->set.upload) {
/*
* NOTE!!! libssh2 requires that the destination path is a full path
* that includes the destination file and name OR ends in a "/" .
* If this is not done the destination file will be named the
* same name as the last directory in the path.
*/
sftp->sftp_handle =
libssh2_sftp_open(sftp->sftp_session, sftp->path,
LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT,
LIBSSH2_SFTP_S_IRUSR|LIBSSH2_SFTP_S_IWUSR|
LIBSSH2_SFTP_S_IRGRP|LIBSSH2_SFTP_S_IROTH);
if (!sftp->sftp_handle)
return CURLE_FAILED_INIT;
/* upload data */
res = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, FIRSTSOCKET, NULL);
}
else {
if (sftp->path[strlen(sftp->path)-1] == '/') {
/*
* This is a directory that we are trying to get, so produce a
* directory listing
*
* **BLOCKING behaviour** This should be made into a state machine and
* get a separate function called from Curl_sftp_recv() when there is
* data to read from the network, instead of "hanging" here.
*/
char filename[PATH_MAX+1];
int len, totalLen, currLen;
char *line;
sftp->sftp_handle =
libssh2_sftp_opendir(sftp->sftp_session, sftp->path);
if (!sftp->sftp_handle)
return CURLE_SSH;
while ((len = libssh2_sftp_readdir(sftp->sftp_handle, filename,
PATH_MAX, &attrs)) > 0) {
filename[len] = '\0';
if (data->set.ftp_list_only) {
if ((attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) &&
((attrs.permissions & LIBSSH2_SFTP_S_IFMT) ==
LIBSSH2_SFTP_S_IFDIR)) {
infof(data, "%s\n", filename);
}
}
else {
totalLen = 80 + len;
line = (char *)malloc(totalLen);
if (!line)
return CURLE_OUT_OF_MEMORY;
if (!(attrs.flags & LIBSSH2_SFTP_ATTR_UIDGID))
attrs.uid = attrs.gid =0;
currLen = snprintf(line, totalLen, "---------- 1 %5d %5d",
attrs.uid, attrs.gid);
if (attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) {
if ((attrs.permissions & LIBSSH2_SFTP_S_IFMT) ==
LIBSSH2_SFTP_S_IFDIR) {
line[0] = 'd';
}
else if ((attrs.permissions & LIBSSH2_SFTP_S_IFMT) ==
LIBSSH2_SFTP_S_IFLNK) {
line[0] = 'l';
}
else if ((attrs.permissions & LIBSSH2_SFTP_S_IFMT) ==
LIBSSH2_SFTP_S_IFSOCK) {
line[0] = 's';
}
else if ((attrs.permissions & LIBSSH2_SFTP_S_IFMT) ==
LIBSSH2_SFTP_S_IFCHR) {
line[0] = 'c';
}
else if ((attrs.permissions & LIBSSH2_SFTP_S_IFMT) ==
LIBSSH2_SFTP_S_IFBLK) {
line[0] = 'b';
}
if (attrs.permissions & LIBSSH2_SFTP_S_IRUSR) {
line[1] = 'r';
}
if (attrs.permissions & LIBSSH2_SFTP_S_IWUSR) {
line[2] = 'w';
}
if (attrs.permissions & LIBSSH2_SFTP_S_IXUSR) {
line[3] = 'x';
}
if (attrs.permissions & LIBSSH2_SFTP_S_IRGRP) {
line[4] = 'r';
}
if (attrs.permissions & LIBSSH2_SFTP_S_IWGRP) {
line[5] = 'w';
}
if (attrs.permissions & LIBSSH2_SFTP_S_IXGRP) {
line[6] = 'x';
}
if (attrs.permissions & LIBSSH2_SFTP_S_IROTH) {
line[7] = 'r';
}
if (attrs.permissions & LIBSSH2_SFTP_S_IWOTH) {
line[8] = 'w';
}
if (attrs.permissions & LIBSSH2_SFTP_S_IXOTH) {
line[9] = 'x';
}
}
if (attrs.flags & LIBSSH2_SFTP_ATTR_SIZE) {
currLen += snprintf(line+currLen, totalLen-currLen, "%11lld",
attrs.filesize);
}
if (attrs.flags & LIBSSH2_SFTP_ATTR_ACMODTIME) {
const char *months[12] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
struct tm *nowParts;
time_t now, remoteTime;
now = time(NULL);
remoteTime = (time_t)attrs.mtime;
nowParts = localtime(&remoteTime);
if ((time_t)attrs.mtime > (now - (3600 * 24 * 180))) {
currLen += snprintf(line+currLen, totalLen-currLen,
" %s %2d %2d:%02d", months[nowParts->tm_mon],
nowParts->tm_mday, nowParts->tm_hour,
nowParts->tm_min);
}
else {
currLen += snprintf(line+currLen, totalLen-currLen,
" %s %2d %5d", months[nowParts->tm_mon],
nowParts->tm_mday, 1900+nowParts->tm_year);
}
}
currLen += snprintf(line+currLen, totalLen-currLen, " %s", filename);
if ((attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) &&
((attrs.permissions & LIBSSH2_SFTP_S_IFMT) ==
LIBSSH2_SFTP_S_IFLNK)) {
char linkPath[PATH_MAX + 1];
snprintf(linkPath, PATH_MAX, "%s%s", sftp->path, filename);
len = libssh2_sftp_readlink(sftp->sftp_session, linkPath, filename,
PATH_MAX);
line = realloc(line, totalLen + 4 + len);
if (!line)
return CURLE_OUT_OF_MEMORY;
currLen += snprintf(line+currLen, totalLen-currLen, " -> %s",
filename);
}
infof(data, "%s\n", line);
free(line);
}
}
libssh2_sftp_closedir(sftp->sftp_handle);
sftp->sftp_handle = NULL;
/* no data to transfer */
res = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
}
else {
/*
* Work on getting the specified file
*/
sftp->sftp_handle =
libssh2_sftp_open(sftp->sftp_session, sftp->path, LIBSSH2_FXF_READ,
LIBSSH2_SFTP_S_IRUSR|LIBSSH2_SFTP_S_IWUSR|
LIBSSH2_SFTP_S_IRGRP|LIBSSH2_SFTP_S_IROTH);
if (!sftp->sftp_handle)
return CURLE_SSH;
if (libssh2_sftp_stat(sftp->sftp_session, sftp->path, &attrs)) {
/*
* libssh2_sftp_open() didn't return an error, so maybe the server
* just doesn't support stat()
*/
data->reqdata.size = -1;
data->reqdata.maxdownload = -1;
}
else {
data->reqdata.size = attrs.filesize;
data->reqdata.maxdownload = attrs.filesize;
Curl_pgrsSetDownloadSize(data, attrs.filesize);
}
Curl_pgrsTime(data, TIMER_STARTTRANSFER);
/* Now download data. The libssh2 0.14 doesn't offer any way to do this
without using this BLOCKING approach, so here's room for improvement
once libssh2 can return EWOULDBLOCK to us. */
#if 0
/* code left here just because this is what this function will use the
day libssh2 is improved */
res = Curl_setup_transfer(conn, FIRSTSOCKET,
bytecount, FALSE, NULL, -1, NULL);
#endif
while (res == CURLE_OK) {
size_t nread;
/* NOTE: most *read() functions return ssize_t but this returns size_t
which normally is unsigned! */
nread = libssh2_sftp_read(data->reqdata.proto.ssh->sftp_handle,
buf, BUFSIZE-1);
if (nread > 0)
buf[nread] = 0;
/* this check can be changed to a <= 0 when nread is changed to a
signed variable type */
if ((nread == 0) || (nread == (size_t)~0))
break;
bytecount += nread;
res = Curl_client_write(conn, CLIENTWRITE_BODY, buf, nread);
if(res)
return res;
Curl_pgrsSetDownloadCounter(data, bytecount);
if(Curl_pgrsUpdate(conn))
res = CURLE_ABORTED_BY_CALLBACK;
else {
struct timeval now = Curl_tvnow();
res = Curl_speedcheck(data, now);
}
}
if(Curl_pgrsUpdate(conn))
res = CURLE_ABORTED_BY_CALLBACK;
/* no (more) data to transfer */
res = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
}
}
return res;
}
CURLcode Curl_sftp_done(struct connectdata *conn, CURLcode status)
{
struct SSHPROTO *sftp = conn->data->reqdata.proto.ssh;
Curl_safefree(sftp->path);
sftp->path = NULL;
Curl_safefree(sftp->homedir);
sftp->homedir = NULL;
if (sftp->sftp_handle) {
if (libssh2_sftp_close(sftp->sftp_handle) < 0) {
infof(conn->data, "Failed to close libssh2 file\n");
}
}
if (sftp->sftp_session) {
if (libssh2_sftp_shutdown(sftp->sftp_session) < 0) {
infof(conn->data, "Failed to stop libssh2 sftp subsystem\n");
}
}
if (sftp->ssh_channel) {
if (libssh2_channel_close(sftp->ssh_channel) < 0) {
infof(conn->data, "Failed to stop libssh2 channel subsystem\n");
}
}
if (sftp->ssh_session) {
libssh2_session_disconnect(sftp->ssh_session, "Shutdown");
libssh2_session_free(sftp->ssh_session);
sftp->ssh_session = NULL;
}
free(conn->data->reqdata.proto.ssh);
conn->data->reqdata.proto.ssh = NULL;
Curl_pgrsDone(conn);
(void)status; /* unused */
return CURLE_OK;
}
/* return number of received (decrypted) bytes */
ssize_t Curl_sftp_send(struct connectdata *conn, int sockindex,
void *mem, size_t len)
{
ssize_t nwrite;
/* libssh2_sftp_write() returns size_t !*/
nwrite = (ssize_t)
libssh2_sftp_write(conn->data->reqdata.proto.ssh->sftp_handle, mem, len);
(void)sockindex;
return nwrite;
}
/*
* If the read would block (EWOULDBLOCK) we return -1. Otherwise we return
* a regular CURLcode value.
*/
ssize_t Curl_sftp_recv(struct connectdata *conn, int sockindex,
char *mem, size_t len)
{
ssize_t nread;
/* libssh2_sftp_read() returns size_t !*/
nread = (ssize_t)
libssh2_sftp_read(conn->data->reqdata.proto.ssh->sftp_handle, mem, len);
(void)sockindex;
return nread;
}
#endif /* USE_LIBSSH2 */ #endif /* USE_LIBSSH2 */

View File

@ -1,5 +1,5 @@
#ifndef __SFTP_H #ifndef __SSH_H
#define __SFTP_H #define __SSH_H
/*************************************************************************** /***************************************************************************
* _ _ ____ _ * _ _ ____ _
@ -26,15 +26,24 @@
#ifdef USE_LIBSSH2 #ifdef USE_LIBSSH2
CURLcode Curl_scp_connect(struct connectdata *conn, bool *done); CURLcode Curl_ssh_connect(struct connectdata *conn, bool *done);
CURLcode Curl_scp_do(struct connectdata *conn, bool *done); CURLcode Curl_scp_do(struct connectdata *conn, bool *done);
CURLcode Curl_scp_done(struct connectdata *conn, CURLcode); CURLcode Curl_scp_done(struct connectdata *conn, CURLcode);
int Curl_scp_send(struct connectdata *conn, int sockindex, ssize_t Curl_scp_send(struct connectdata *conn, int sockindex,
void *mem, size_t len); void *mem, size_t len);
int Curl_scp_recv(struct connectdata *conn, int sockindex, ssize_t Curl_scp_recv(struct connectdata *conn, int sockindex,
char *mem, size_t len); char *mem, size_t len);
#endif CURLcode Curl_sftp_do(struct connectdata *conn, bool *done);
CURLcode Curl_sftp_done(struct connectdata *conn, CURLcode);
ssize_t Curl_sftp_send(struct connectdata *conn, int sockindex,
void *mem, size_t len);
ssize_t Curl_sftp_recv(struct connectdata *conn, int sockindex,
char *mem, size_t len);
#endif /* USE_LIBSSH2 */ #endif /* USE_LIBSSH2 */
#endif /* __SSH_H */

View File

@ -3246,7 +3246,7 @@ static CURLcode CreateConnection(struct SessionHandle *data,
conn->port = PORT_SSH; conn->port = PORT_SSH;
conn->remote_port = PORT_SSH; conn->remote_port = PORT_SSH;
conn->protocol = PROT_SCP; conn->protocol = PROT_SCP;
conn->curl_connect = Curl_scp_connect; /* ssh_connect? */ conn->curl_connect = Curl_ssh_connect; /* ssh_connect? */
conn->curl_do = Curl_scp_do; conn->curl_do = Curl_scp_do;
conn->curl_done = Curl_scp_done; conn->curl_done = Curl_scp_done;
conn->curl_do_more = (Curl_do_more_func)ZERO_NULL; conn->curl_do_more = (Curl_do_more_func)ZERO_NULL;
@ -3256,7 +3256,22 @@ static CURLcode CreateConnection(struct SessionHandle *data,
return CURLE_UNSUPPORTED_PROTOCOL; return CURLE_UNSUPPORTED_PROTOCOL;
#endif #endif
} }
else { else if (strequal(conn->protostr, "SFTP")) {
#ifdef USE_LIBSSH2
conn->port = PORT_SSH;
conn->remote_port = PORT_SSH;
conn->protocol = PROT_SFTP;
conn->curl_connect = Curl_ssh_connect; /* ssh_connect? */
conn->curl_do = Curl_sftp_do;
conn->curl_done = Curl_sftp_done;
conn->curl_do_more = (Curl_do_more_func)NULL;
#else
failf(data, LIBCURL_NAME
" was built without LIBSSH2, scp: not supported!");
return CURLE_UNSUPPORTED_PROTOCOL;
#endif
}
else {
/* We fell through all checks and thus we don't support the specified /* We fell through all checks and thus we don't support the specified
protocol */ protocol */
failf(data, "Unsupported protocol: %s", conn->protostr); failf(data, "Unsupported protocol: %s", conn->protostr);
@ -3422,9 +3437,9 @@ static CURLcode CreateConnection(struct SessionHandle *data,
user[0] =0; /* to make everything well-defined */ user[0] =0; /* to make everything well-defined */
passwd[0]=0; passwd[0]=0;
if (conn->protocol & (PROT_FTP|PROT_HTTP|PROT_SCP)) { if (conn->protocol & (PROT_FTP|PROT_HTTP|PROT_SCP|PROT_SFTP)) {
/* This is a FTP or HTTP URL, we will now try to extract the possible /* This is a FTP, HTTP, SCP or SFTP URL, we will now try to extract the
* user+password pair in a string like: * possible user+password pair in a string like:
* ftp://user:password@ftp.my.site:8021/README */ * ftp://user:password@ftp.my.site:8021/README */
char *ptr=strchr(conn->host.name, '@'); char *ptr=strchr(conn->host.name, '@');
char *userpass = conn->host.name; char *userpass = conn->host.name;

View File

@ -398,18 +398,18 @@ struct ftp_conn {
ftpstate state; /* always use ftp.c:state() to change state! */ ftpstate state; /* always use ftp.c:state() to change state! */
}; };
struct SCPPROTO { struct SSHPROTO {
curl_off_t *bytecountp; curl_off_t *bytecountp;
char *user; char *user;
char *passwd; char *passwd;
char *path; /* the path we operate on */ char *path; /* the path we operate on */
char *freepath; /* pointer to the allocated block we must char *homedir;
free, this might differ from the 'path'
pointer */
char *errorstr; char *errorstr;
#ifdef USE_LIBSSH2 #ifdef USE_LIBSSH2
LIBSSH2_SESSION *scpSession; /* Secure Shell session */ LIBSSH2_SESSION *ssh_session; /* Secure Shell session */
LIBSSH2_CHANNEL *scpChannel; /* SCP channel handle */ LIBSSH2_CHANNEL *ssh_channel; /* Secure Shell channel handle */
LIBSSH2_SFTP *sftp_session; /* SFTP handle */
LIBSSH2_SFTP_HANDLE *sftp_handle;
#endif /* USE_LIBSSH2 */ #endif /* USE_LIBSSH2 */
}; };
@ -673,7 +673,7 @@ struct HandleData {
struct FILEPROTO *file; struct FILEPROTO *file;
void *telnet; /* private for telnet.c-eyes only */ void *telnet; /* private for telnet.c-eyes only */
void *generic; void *generic;
struct SCPPROTO *scp; struct SSHPROTO *ssh;
} proto; } proto;
}; };
@ -709,6 +709,7 @@ struct connectdata {
#define PROT_SSL (1<<10) /* protocol requires SSL */ #define PROT_SSL (1<<10) /* protocol requires SSL */
#define PROT_TFTP (1<<11) #define PROT_TFTP (1<<11)
#define PROT_SCP (1<<12) #define PROT_SCP (1<<12)
#define PROT_SFTP (1<<13)
/* 'dns_entry' is the particular host we use. This points to an entry in the /* 'dns_entry' is the particular host we use. This points to an entry in the
DNS cache and it will not get pruned while locked. It gets unlocked in DNS cache and it will not get pruned while locked. It gets unlocked in
@ -830,8 +831,10 @@ struct connectdata {
struct sockaddr_in local_addr; struct sockaddr_in local_addr;
#endif #endif
bool readchannel_inuse; /* whether the read channel is in use by an easy handle */ bool readchannel_inuse; /* whether the read channel is in use by an easy
bool writechannel_inuse; /* whether the write channel is in use by an easy handle */ handle */
bool writechannel_inuse; /* whether the write channel is in use by an easy
handle */
bool is_in_pipeline; /* TRUE if this connection is in a pipeline */ bool is_in_pipeline; /* TRUE if this connection is in a pipeline */
struct curl_llist *send_pipe; /* List of handles waiting to struct curl_llist *send_pipe; /* List of handles waiting to

View File

@ -138,6 +138,7 @@ static const char * const protocols[] = {
#ifdef USE_LIBSSH2 #ifdef USE_LIBSSH2
"scp", "scp",
"sftp",
#endif #endif
NULL NULL