From 2147284cad624325f5b0034c2f394db62086d9e6 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 2 Nov 2006 21:56:40 +0000 Subject: James Housley brought support for SCP transfers --- CHANGES | 4 + RELEASE-NOTES | 4 +- configure.ac | 68 ++++++ docs/libcurl/curl_version_info.3 | 7 +- include/curl/curl.h | 28 ++- lib/Makefile.inc | 4 +- lib/easy.c | 3 + lib/sendf.c | 17 +- lib/ssh.c | 501 +++++++++++++++++++++++++++++++++++++++ lib/ssh.h | 40 ++++ lib/strerror.c | 6 + lib/url.c | 43 +++- lib/urldata.h | 29 +++ lib/version.c | 25 ++ 14 files changed, 768 insertions(+), 11 deletions(-) create mode 100644 lib/ssh.c create mode 100644 lib/ssh.h diff --git a/CHANGES b/CHANGES index ecc22cea4..0a7304515 100644 --- a/CHANGES +++ b/CHANGES @@ -6,6 +6,10 @@ Changelog +Daniel (2 November 2006) +- James Housley brought support for SCP transfers, based on the libssh2 library + for the actual network protocol stuff. + Version 7.16.0 (30 October 2006) Daniel (25 October 2006) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 8387cad44..4c31a5087 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -11,7 +11,7 @@ Curl and libcurl 7.16.1 This release includes the following changes: - o + o Support for SCP added This release includes the following bugfixes: @@ -28,6 +28,6 @@ New curl mirrors: This release would not have looked like this without help, code, reports and advice from friends like these: - + James Housley Thanks! (and sorry if I forgot to mention someone) diff --git a/configure.ac b/configure.ac index fb44a9557..b33681541 100644 --- a/configure.ac +++ b/configure.ac @@ -78,6 +78,7 @@ AC_SUBST(PKGADD_VENDOR) dnl dnl initialize all the info variables curl_ssl_msg="no (--with-ssl / --with-gnutls)" + curl_ssh_msg="no (--with-libssh2)" curl_zlib_msg="no (--with-zlib)" curl_krb4_msg="no (--with-krb4*)" curl_gss_msg="no (--with-gssapi)" @@ -1043,6 +1044,72 @@ if test X"$OPT_SSL" != Xno; then fi +dnl ********************************************************************** +dnl Check for the presence of LIBSSH2 libraries and headers +dnl ********************************************************************** + +dnl Default to compiler & linker defaults for LIBSSH2 files & libraries. +OPT_LIBSSH2=off +AC_ARG_WITH(libssh2,dnl +AC_HELP_STRING([--with-libssh2=PATH],[Where to look for libssh2, PATH points to the LIBSSH2 installation (default: /usr/local/lib); when possible, set the PKG_CONFIG_PATH environment variable instead of using this option]) +AC_HELP_STRING([--without-libssh2], [disable LIBSSH2]), + OPT_LIBSSH2=$withval) + +if test X"$OPT_LIBSSH2" != Xno; then + dnl backup the pre-libssh2 variables + CLEANLDFLAGS="$LDFLAGS" + CLEANCPPFLAGS="$CPPFLAGS" + CLEANLIBS="$LIBS" + + case "$OPT_LIBSSH2" in + yes) + dnl --with-libssh2 (without path) used + PREFIX_LIBSSH2=/usr/local/lib + LIB_LIBSSH2="$PREFIX_LIBSSH2/lib$libsuff" + ;; + off) + dnl no --with-libssh2 option given, just check default places + PREFIX_LIBSSH2= + ;; + *) + dnl check the given --with-libssh2 spot + PREFIX_LIBSSH2=$OPT_LIBSSH2 + LIB_LIBSSH2="$PREFIX_LIBSSH2/lib$libsuff" + LDFLAGS="$LDFLAGS -L$LIB_LIBSSH2" + CPPFLAGS="$CPPFLAGS -I$PREFIX_LIBSSH2/include/libssh2 -I$PREFIX_LIBSSH2/include" + ;; + esac + + if test X"$HAVECRYPTO" = X"yes"; then + dnl This is only reasonable to do if crypto actually is there: check for + dnl LIBSSH2 libs NOTE: it is important to do this AFTER the crypto lib + + AC_CHECK_LIB(ssh2, libssh2_channel_open_ex) + + AC_CHECK_HEADERS(libssh2.h, + curl_ssh_msg="enabled (libSSH2)" + LIBSSH2_ENABLED=1 + AC_DEFINE(USE_LIBSSH2, 1, [if libSSH2 is in use])) + + if test X"OPT_LIBSSH2" != Xoff && + test "$LIBSSH2_ENABLED" != "1"; then + AC_MSG_ERROR([libSSH2 libs and/or directories were not found where specified!]) + fi + fi + + if test "$LIBSSH2_ENABLED" = "1"; then + if test -n "$LIB_LIBSSH2"; then + dnl when the libssh2 shared libs were found in a path that the run-time + dnl linker doesn't search through, we need to add it to LD_LIBRARY_PATH + dnl to prevent further configure tests to fail due to this + + LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$LIB_LIBSSH2" + export LD_LIBRARY_PATH + AC_MSG_NOTICE([Added $LIB_LIBSSH2 to LD_LIBRARY_PATH]) + fi + fi +fi + dnl ********************************************************************** dnl Check for the random seed preferences dnl ********************************************************************** @@ -2076,6 +2143,7 @@ AC_MSG_NOTICE([Configured to build curl/libcurl: Install prefix: ${prefix} Compiler: ${CC} SSL support: ${curl_ssl_msg} + SSH support: ${curl_ssh_msg} zlib support: ${curl_zlib_msg} krb4 support: ${curl_krb4_msg} GSSAPI support: ${curl_gss_msg} diff --git a/docs/libcurl/curl_version_info.3 b/docs/libcurl/curl_version_info.3 index a9fd55d59..21f72aebe 100644 --- a/docs/libcurl/curl_version_info.3 +++ b/docs/libcurl/curl_version_info.3 @@ -21,7 +21,7 @@ .\" * $Id$ .\" ************************************************************************** .\" -.TH curl_version_info 3 "19 Apr 2006" "libcurl 7.15.4" "libcurl Manual" +.TH curl_version_info 3 "2 Nov 2006" "libcurl 7.16.1" "libcurl Manual" .SH NAME curl_version_info - returns run-time libcurl version info .SH SYNOPSIS @@ -66,6 +66,11 @@ typedef struct { /* when 'age' is 2 or higher, the member below also exists: */ const char *libidn; /* human readable string */ + /* when 'age' is 3 or higher, the members below also exist: */ + int iconv_ver_num; /* '_libiconv_version' if iconv support enabled */ + + const char *libssh_version; /* human readable string */ + } curl_version_info_data; .fi diff --git a/include/curl/curl.h b/include/curl/curl.h index 36b52bb05..ea5b46201 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -392,6 +392,11 @@ typedef enum { CURLOPT_CONV_FROM_UTF8_FUNCTION */ CURLE_SSL_CACERT_BADFILE, /* 77 - could not load CACERT file, missing or wrong format */ + CURLE_REMOTE_FILE_NOT_FOUND, /* 78 - remote file not found */ + CURLE_SSH, /* 79 - error from the SSH layer, somewhat + generic so the error message will be of + interest when this has happened */ + CURL_LAST /* never use! */ } CURLcode; @@ -427,6 +432,14 @@ typedef enum { #define CURLAUTH_ANY ~0 /* all types set */ #define CURLAUTH_ANYSAFE (~CURLAUTH_BASIC) +#define CURLSSH_AUTH_ANY ~0 /* all types supported by the server */ +#define CURLSSH_AUTH_NONE 0 /* none allowed, silly but complete */ +#define CURLSSH_AUTH_PUBLICKEY (1<<0) /* public/private key files */ +#define CURLSSH_AUTH_PASSWORD (1<<1) /* password */ +#define CURLSSH_AUTH_HOST (1<<2) /* host key files */ +#define CURLSSH_AUTH_KEYBOARD (1<<3) /* keyboard interactive */ +#define CURLSSH_AUTH_DEFAULT CURLSSH_AUTH_ANY + #ifndef CURL_NO_OLDIES /* define this to test if your app builds with all the obsolete stuff removed! */ /* this was the error code 50 in 7.7.3 and a few earlier versions, this @@ -1029,6 +1042,13 @@ typedef enum { enabled (== 1) */ CINIT(SSL_SESSIONID_CACHE, LONG, 150), + /* allowed SSH authentication methods */ + CINIT(SSH_AUTH_TYPES, LONG, 151), + + /* Used by scp/sftp to do public/private key authentication */ + CINIT(SSH_PUBLIC_KEYFILE, OBJECTPOINT, 152), + CINIT(SSH_PRIVATE_KEYFILE, OBJECTPOINT, 153), + CURLOPT_LASTENTRY /* the last unused */ } CURLoption; @@ -1506,6 +1526,7 @@ typedef enum { CURLVERSION_FIRST, CURLVERSION_SECOND, CURLVERSION_THIRD, + CURLVERSION_FOURTH, CURLVERSION_LAST /* never actually use this */ } CURLversion; @@ -1514,7 +1535,7 @@ typedef enum { meant to be a built-in version number for what kind of struct the caller expects. If the struct ever changes, we redefine the NOW to another enum from above. */ -#define CURLVERSION_NOW CURLVERSION_THIRD +#define CURLVERSION_NOW CURLVERSION_FOURTH typedef struct { CURLversion age; /* age of the returned struct */ @@ -1535,8 +1556,13 @@ typedef struct { /* This field was added in CURLVERSION_THIRD */ const char *libidn; + /* These field were added in CURLVERSION_FOURTH */ + /* Same as '_libiconv_version' if built with HAVE_ICONV */ int iconv_ver_num; + + const char *libssh_version; /* human readable string */ + } curl_version_info_data; #define CURL_VERSION_IPV6 (1<<0) /* IPv6-enabled */ diff --git a/lib/Makefile.inc b/lib/Makefile.inc index d91eac7a2..69e2d420f 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -8,7 +8,7 @@ CSOURCES = file.c timeval.c base64.c hostip.c progress.c formdata.c \ content_encoding.c share.c http_digest.c md5.c http_negotiate.c \ http_ntlm.c inet_pton.c strtoofft.c strerror.c hostares.c hostasyn.c \ hostip4.c hostip6.c hostsyn.c hostthre.c inet_ntop.c parsedate.c \ - select.c gtls.c sslgen.c tftp.c splay.c strdup.c socks.c + select.c gtls.c sslgen.c tftp.c splay.c strdup.c socks.c ssh.c HHEADERS = arpa_telnet.h netrc.h file.h timeval.h base64.h hostip.h \ progress.h formdata.h cookie.h http.h sendf.h ftp.h url.h dict.h \ @@ -18,6 +18,6 @@ HHEADERS = arpa_telnet.h netrc.h file.h timeval.h base64.h hostip.h \ share.h md5.h http_digest.h http_negotiate.h http_ntlm.h ca-bundle.h \ inet_pton.h strtoofft.h strerror.h inet_ntop.h curlx.h memory.h \ setup.h transfer.h select.h easyif.h multiif.h parsedate.h sslgen.h \ - gtls.h tftp.h sockaddr.h splay.h strdup.h setup_once.h socks.h + gtls.h tftp.h sockaddr.h splay.h strdup.h setup_once.h socks.h ssh.h diff --git a/lib/easy.c b/lib/easy.c index b2e026326..7436017c0 100644 --- a/lib/easy.c +++ b/lib/easy.c @@ -715,6 +715,9 @@ void curl_easy_reset(CURL *curl) /* This is our prefered CA cert bundle since install time */ data->set.ssl.CAfile = (char *)CURL_CA_BUNDLE; #endif + + data->set.ssh_auth_types = CURLSSH_AUTH_DEFAULT; /* defaults to any auth + type */ } #ifdef CURL_DOES_CONVERSIONS diff --git a/lib/sendf.c b/lib/sendf.c index 01a23e445..59c43f5f0 100644 --- a/lib/sendf.c +++ b/lib/sendf.c @@ -45,6 +45,7 @@ #include "sendf.h" #include "connect.h" /* for the Curl_sockerrno() proto */ #include "sslgen.h" +#include "ssh.h" #define _MPRINTF_REPLACE /* use the internal *printf() functions */ #include @@ -326,9 +327,15 @@ CURLcode Curl_write(struct connectdata *conn, CURLcode retcode; int num = (sockfd == conn->sock[SECONDARYSOCKET]); - if (conn->ssl[num].use) + if (conn->ssl[num].use) { /* only TRUE if SSL enabled */ bytes_written = Curl_ssl_send(conn, num, mem, len); + } +#ifdef USE_LIBSSH2 + else if (conn->protocol & PROT_SCP) { + bytes_written = Curl_scp_send(conn, num, mem, len); + } +#endif /* !USE_LIBSSH2 */ else { if(conn->sec_complete) /* only TRUE if krb4 enabled */ @@ -499,9 +506,15 @@ int Curl_read(struct connectdata *conn, /* connection data */ if(conn->ssl[num].use) { nread = Curl_ssl_recv(conn, num, conn->master_buffer, bytesfromsocket); - if(nread == -1) + if(nread == -1) { return -1; /* -1 from Curl_ssl_recv() means EWOULDBLOCK */ + } + } +#ifdef USE_LIBSSH2 + else if (conn->protocol & PROT_SCP) { + nread = Curl_scp_recv(conn, num, conn->master_buffer, bytesfromsocket); } +#endif /* !USE_LIBSSH2 */ else { if(conn->sec_complete) nread = Curl_sec_read(conn, sockfd, conn->master_buffer, diff --git a/lib/ssh.c b/lib/ssh.c new file mode 100644 index 000000000..9f676b8e3 --- /dev/null +++ b/lib/ssh.c @@ -0,0 +1,501 @@ +/*************************************************************************** +* _ _ ____ _ +* Project ___| | | | _ \| | +* / __| | | | |_) | | +* | (__| |_| | _ <| |___ +* \___|\___/|_| \_\_____| +* +* Copyright (C) 1998 - 2006, Daniel Stenberg, , et al. +* +* This software is licensed as described in the file COPYING, which +* you should have received as part of this distribution. The terms +* are also available at http://curl.haxx.se/docs/copyright.html. +* +* You may opt to use, copy, modify, merge, publish, distribute and/or sell +* copies of the Software, and permit persons to whom the Software is +* furnished to do so, under the terms of the COPYING file. +* +* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +* KIND, either express or implied. +* +* $Id$ +***************************************************************************/ + +#define CURL_LIBSSH2_DEBUG + +#include "setup.h" + +#ifdef USE_LIBSSH2 +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef HAVE_FCNTL_H +#include +#endif + +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif + +#if defined(WIN32) && !defined(__GNUC__) || defined(__MINGW32__) + +#else /* probably some kind of unix */ +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#include +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_UTSNAME_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef VMS +#include +#include +#endif +#endif + +#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) +#undef in_addr_t +#define in_addr_t unsigned long +#endif + +#include +#include "urldata.h" +#include "sendf.h" +#include "easyif.h" /* for Curl_convert_... prototypes */ + +#include "if2ip.h" +#include "hostip.h" +#include "progress.h" +#include "transfer.h" +#include "escape.h" +#include "http.h" /* for HTTP proxy tunnel stuff */ +#include "ssh.h" +#include "url.h" +#include "speedcheck.h" +#include "getinfo.h" + +#include "strtoofft.h" +#include "strequal.h" +#include "sslgen.h" +#include "connect.h" +#include "strerror.h" +#include "memory.h" +#include "inet_ntop.h" +#include "select.h" +#include "parsedate.h" /* for the week day and month names */ +#include "sockaddr.h" /* required for Curl_sockaddr_storage */ +#include "multiif.h" + +#if defined(HAVE_INET_NTOA_R) && !defined(HAVE_INET_NTOA_R_DECL) +#include "inet_ntoa_r.h" +#endif + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#if defined(WIN32) || defined(MSDOS) || defined(__EMX__) +#define DIRSEP '\\' +#else +#define DIRSEP '/' +#endif + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +/* The last #include file should be: */ +#ifdef CURLDEBUG +#include "memdebug.h" +#endif + +static LIBSSH2_ALLOC_FUNC(libssh2_malloc); +static LIBSSH2_REALLOC_FUNC(libssh2_realloc); +static LIBSSH2_FREE_FUNC(libssh2_free); + +struct auth_ +{ + const char * user; + const char * pw; +} auth; + +static void +kbd_callback(const char *name, int name_len, const char *instruction, + int instruction_len, int num_prompts, + const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts, + LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses, + void **abstract) +{ +#ifdef CURL_LIBSSH2_DEBUG + fprintf(stderr, "name=%s\n", name); + fprintf(stderr, "name_len=%d\n", name_len); + fprintf(stderr, "instruction=%s\n", instruction); + fprintf(stderr, "instruction_len=%d\n", instruction_len); + fprintf(stderr, "num_prompts=%d\n", num_prompts); +#endif /* CURL_LIBSSH2_DEBUG */ + if (num_prompts == 1) { + responses[0].text = strdup(auth.pw); + responses[0].length = strlen(auth.pw); + } + (void)prompts; + (void)abstract; + return; +} /* kbd_callback */ + +static CURLcode libssh2_error_to_CURLE(struct connectdata *conn) +{ + int errorcode; + struct SCPPROTO *scp = conn->data->reqdata.proto.scp; + + /* Get the libssh2 error code and string */ + errorcode = libssh2_session_last_error(scp->scpSession, &scp->errorstr, NULL, + 0); + if (errorcode == LIBSSH2_FX_OK) + return CURLE_OK; + + infof(conn->data, "libssh2 error %d, '%s'\n", errorcode, scp->errorstr); + + /* TODO: map some of the libssh2 errors to the more appropriate CURLcode + error code, and possibly add a few new SSH-related one. We must however + not return or even depend on libssh2 errors in the public libcurl API */ + + return CURLE_SSH; +} + +static LIBSSH2_ALLOC_FUNC(libssh2_malloc) +{ + return malloc(count); + (void)abstract; +} + +static LIBSSH2_REALLOC_FUNC(libssh2_realloc) +{ + return realloc(ptr, count); + (void)abstract; +} + +static LIBSSH2_FREE_FUNC(libssh2_free) +{ + free(ptr); + (void)abstract; +} + +static CURLcode scp_init(struct connectdata *conn) +{ + struct SessionHandle *data = conn->data; + struct SCPPROTO *scp; + if (data->reqdata.proto.scp) + return CURLE_OK; + + scp = (struct SCPPROTO *)calloc(sizeof(struct SCPPROTO), 1); + if (!scp) + return CURLE_OUT_OF_MEMORY; + + data->reqdata.proto.scp = scp; + + /* get some initial data into the scp struct */ + scp->bytecountp = &data->reqdata.keep.bytecount; + + /* no need to duplicate them, this connectdata struct won't change */ + scp->user = conn->user; + scp->passwd = conn->passwd; + + scp->errorstr = NULL; + + return CURLE_OK; +} + +/* + * Curl_scp_connect() gets called from Curl_protocol_connect() to allow us to + * do protocol-specific actions at connect-time. + */ +CURLcode Curl_scp_connect(struct connectdata *conn, bool *done) +{ + int i; + struct SCPPROTO *scp; + const char *fingerprint; + const char *authlist; + char *home; + char rsa_pub[PATH_MAX]; + char rsa[PATH_MAX]; + curl_socket_t sock; + char *real_path; + char *working_path; + bool authed = FALSE; + CURLcode result; + struct SessionHandle *data = conn->data; + + result = scp_init(conn); + if (result) + return result; + + rsa_pub[0] = rsa[0] = '\0'; + + scp = data->reqdata.proto.scp; + + working_path = curl_easy_unescape(data, data->reqdata.path, 0, NULL); + if (!working_path) + 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 + if (scp->user) { + infof(data, "User: %s\n", scp->user); + } + if (scp->passwd) { + infof(data, "Password: %s\n", scp->passwd); + } +#endif /* CURL_LIBSSH2_DEBUG */ + sock = conn->sock[FIRSTSOCKET]; + scp->scpSession = libssh2_session_init_ex(libssh2_malloc, libssh2_free, + libssh2_realloc, NULL); + if (scp->scpSession == NULL) { + failf(data, "Failure initialising ssh session\n"); + return CURLE_FAILED_INIT; + } +#ifdef CURL_LIBSSH2_DEBUG + infof(data, "Socket: %d\n", sock); +#endif /* CURL_LIBSSH2_DEBUG */ + + if (libssh2_session_startup(scp->scpSession, sock)) { + failf(data, "Failure establishing ssh session\n"); + return CURLE_FAILED_INIT; + } + + /* + * Before we authenticate we should check the hostkey's fingerprint against + * our known hosts. How that is handled (reading from file, whatever) is + * up to us. As for know not much is implemented, besides showing how to + * get the fingerprint. + */ + fingerprint = libssh2_hostkey_hash(scp->scpSession, + LIBSSH2_HOSTKEY_HASH_MD5); + +#ifdef CURL_LIBSSH2_DEBUG + /* The fingerprint points to static storage (!), don't free() it. */ + for (i = 0; i < 16; i++) { + infof(data, "%02X ", (unsigned char) fingerprint[i]); + } + infof(data, "\n"); +#endif /* CURL_LIBSSH2_DEBUG */ + + /* TBD - methods to check the host keys need to be done */ + + /* + * Figure out authentication methods + * NB: As soon as we have provided a username to an openssh server we must + * never change it later. Thus, always specify the correct username here, + * even though the libssh2 docs kind of indicate that it should be possible + * to get a 'generic' list (not user-specific) of authentication methods, + * presumably with a blank username. That won't work in my experience. + * So always specify it here. + */ + authlist = libssh2_userauth_list(scp->scpSession, scp->user, + strlen(scp->user)); + + /* + * Check the supported auth types in the order I feel is most secure with the + * requested type of authentication + */ + if ((data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY) && + (strstr(authlist, "publickey") != NULL)) { + /* To ponder about: should really the lib be messing about with the HOME + environment variable etc? */ + home = curl_getenv("HOME"); + + if (data->set.ssh_public_key) + snprintf(rsa_pub, sizeof(rsa_pub), "%s", data->set.ssh_public_key); + else if(home) + snprintf(rsa_pub, sizeof(rsa_pub), "%s/.ssh/id_dsa.pub", home); + + if(data->set.ssh_private_key) + snprintf(rsa, sizeof(rsa), "%s", data->set.ssh_private_key); + else if(home) { + snprintf(rsa, sizeof(rsa), "%s/.ssh/id_dsa", home); + } + + curl_free(home); + + if (rsa_pub[0]) { + /* The function below checks if the files exists, no need to stat() here. + */ + if (libssh2_userauth_publickey_fromfile(scp->scpSession, scp->user, + rsa_pub, rsa, "") == 0) { + authed = TRUE; + } + } + } + if (!authed && + (data->set.ssh_auth_types & CURLSSH_AUTH_PASSWORD) && + (strstr(authlist, "password") != NULL)) { + if (libssh2_userauth_password(scp->scpSession, scp->user, scp->passwd) + == 0) { + authed = TRUE; + } + } + if (!authed && (data->set.ssh_auth_types & CURLSSH_AUTH_HOST) && + (strstr(authlist, "hostbased") != NULL)) { + } + if (!authed && (data->set.ssh_auth_types & CURLSSH_AUTH_KEYBOARD) + && (strstr(authlist, "keyboard-interactive") != NULL)) { + /* Authentication failed. Continue with keyboard-interactive now. */ + auth.user = scp->user; + auth.pw = scp->passwd; + if (libssh2_userauth_keyboard_interactive_ex(scp->scpSession, scp->user, + strlen(scp->user), + &kbd_callback) == 0) { + authed = TRUE; + } + } + + if (!authed) { + failf(data, "Authentication failure\n"); + return CURLE_FAILED_INIT; + } + + /* + * At this point we have an authenticated ssh session. + */ + conn->sockfd = sock; + conn->writesockfd = CURL_SOCKET_BAD; + + *done = TRUE; + return CURLE_OK; +} + +CURLcode Curl_scp_do(struct connectdata *conn, bool *done) +{ + struct stat sb; + struct SCPPROTO *scp = conn->data->reqdata.proto.scp; + CURLcode res = CURLE_OK; + + *done = TRUE; /* unconditionally */ + + if (conn->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. + */ + scp->scpChannel = libssh2_scp_send_ex(scp->scpSession, scp->path, + S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, + conn->data->set.infilesize, 0, 0); + if (scp->scpChannel == NULL) { + return CURLE_FAILED_INIT; + } + conn->writesockfd = conn->sockfd; + conn->sockfd = CURL_SOCKET_BAD; + } + else { + /* + * We must check the remote file, if it is a directory I have no idea + * what I will do until the scp "-r" option is supported + */ + memset(&sb, 0, sizeof(struct stat)); + if ((scp->scpChannel = libssh2_scp_recv(scp->scpSession, scp->path, &sb)) + == NULL) { + if ((sb.st_mode == 0) && (sb.st_atime == 0) && (sb.st_mtime == 0) && + (sb.st_size == 0)) { + /* Since sb is still empty, it is likely the file was not found */ + return CURLE_REMOTE_FILE_NOT_FOUND; + } + return libssh2_error_to_CURLE(conn); + } + conn->data->reqdata.size = sb.st_size; + conn->data->reqdata.maxdownload = sb.st_size; + } + + return res; +} + +CURLcode Curl_scp_done(struct connectdata *conn, CURLcode status) +{ + struct SCPPROTO *scp = conn->data->reqdata.proto.scp; + + Curl_safefree(scp->freepath); + scp->freepath = NULL; + + if (scp->scpChannel) { + if (libssh2_channel_close(scp->scpChannel) < 0) { + failf(conn->data, "Failed to stop libssh2 channel subsystem\n"); + } + } + + if (scp->scpSession) { + libssh2_session_disconnect(scp->scpSession, "Shutdown"); + libssh2_session_free(scp->scpSession); + } + + free(conn->data->reqdata.proto.scp); + conn->data->reqdata.proto.scp = NULL; + Curl_pgrsDone(conn); + + (void)status; /* unused */ + + return CURLE_OK; +} + +/* return number of received (decrypted) bytes */ +int Curl_scp_send(struct connectdata *conn, int sockindex, + void *mem, size_t len) +{ + ssize_t nwrite; + + nwrite = libssh2_channel_write(conn->data->reqdata.proto.scp->scpChannel, + mem, len); + (void)sockindex; + return nwrite; +} + +/* + * If the read would block (EWOULDBLOCK) we return -1. Otherwise we return + * a regular CURLcode value. + */ +int Curl_scp_recv(struct connectdata *conn, int sockindex, + char *mem, size_t len) +{ + ssize_t nread; + + nread = libssh2_channel_read(conn->data->reqdata.proto.scp->scpChannel, + mem, len); + (void)sockindex; + return nread; +} + +#endif /* USE_LIBSSH2 */ diff --git a/lib/ssh.h b/lib/ssh.h new file mode 100644 index 000000000..56f658a80 --- /dev/null +++ b/lib/ssh.h @@ -0,0 +1,40 @@ +#ifndef __SFTP_H +#define __SFTP_H + +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2006, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * $Id$ + ***************************************************************************/ + +#ifdef USE_LIBSSH2 + +CURLcode Curl_scp_connect(struct connectdata *conn, bool *done); +CURLcode Curl_scp_do(struct connectdata *conn, bool *done); +CURLcode Curl_scp_done(struct connectdata *conn, CURLcode); + +int Curl_scp_send(struct connectdata *conn, int sockindex, + void *mem, size_t len); +int Curl_scp_recv(struct connectdata *conn, int sockindex, + char *mem, size_t len); + +#endif + +#endif /* USE_LIBSSH2 */ diff --git a/lib/strerror.c b/lib/strerror.c index 3e466c688..4c2dacabc 100644 --- a/lib/strerror.c +++ b/lib/strerror.c @@ -277,6 +277,12 @@ curl_easy_strerror(CURLcode error) case CURLE_CONV_REQD: return "caller must register CURLOPT_CONV_ callback options"; + case CURLE_REMOTE_FILE_NOT_FOUND: + return "Remote file not found"; + + case CURLE_SSH: + return "Error in the SSH layer"; + /* error codes not used by current libcurl */ case CURLE_URL_MALFORMAT_USER: case CURLE_FTP_USER_PASSWORD_INCORRECT: diff --git a/lib/url.c b/lib/url.c index 6b823df34..92e4a3b09 100644 --- a/lib/url.c +++ b/lib/url.c @@ -128,6 +128,7 @@ void idn_free (void *ptr); /* prototype from idn-free.h, not provided by #include "http.h" #include "file.h" #include "ldap.h" +#include "ssh.h" #include "url.h" #include "connect.h" #include "inet_ntop.h" @@ -165,7 +166,7 @@ static void signalPipeClose(struct curl_llist *pipe); #define MAX_PIPELINE_LENGTH 5 -/* +/* * We use this ZERO_NULL to avoid picky compiler warnings, * when assigning a NULL pointer to a function pointer var. */ @@ -546,6 +547,9 @@ CURLcode Curl_open(struct SessionHandle **curl) the first call to curl_easy_perform() or when the handle is added to a multi stack. */ + data->set.ssh_auth_types = CURLSSH_AUTH_DEFAULT; /* defaults to any auth + type */ + /* most recent connection is not yet defined */ data->state.lastconnect = -1; @@ -1673,6 +1677,24 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option, data->set.ssl.sessionid = (bool)(0 != va_arg(param, long)); break; + case CURLOPT_SSH_AUTH_TYPES: + data->set.ssh_auth_types = va_arg(param, long); + break; + + case CURLOPT_SSH_PUBLIC_KEYFILE: + /* + * Use this file instead of the $HOME/.ssh/id_dsa.pub file + */ + data->set.ssh_public_key = va_arg(param, char *); + break; + + case CURLOPT_SSH_PRIVATE_KEYFILE: + /* + * Use this file instead of the $HOME/.ssh/id_dsa file + */ + data->set.ssh_private_key = va_arg(param, char *); + break; + default: /* unknown tag and its companion, just ignore: */ result = CURLE_FAILED_INIT; /* correct this */ @@ -3023,7 +3045,7 @@ static CURLcode CreateConnection(struct SessionHandle *data, conn->curl_connecting = Curl_https_connecting; conn->curl_proto_getsock = Curl_https_getsock; -#else /* USE_SS */ +#else /* USE_SSL */ failf(data, LIBCURL_NAME " was built with SSL disabled, https: not supported!"); return CURLE_UNSUPPORTED_PROTOCOL; @@ -3213,6 +3235,21 @@ static CURLcode CreateConnection(struct SessionHandle *data, #else failf(data, LIBCURL_NAME " was built with TFTP disabled!"); +#endif + } + else if (strequal(conn->protostr, "SCP")) { +#ifdef USE_LIBSSH2 + conn->port = PORT_SSH; + conn->remote_port = PORT_SSH; + conn->protocol = PROT_SCP; + conn->curl_connect = Curl_scp_connect; /* ssh_connect? */ + conn->curl_do = Curl_scp_do; + conn->curl_done = Curl_scp_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 { @@ -3381,7 +3418,7 @@ static CURLcode CreateConnection(struct SessionHandle *data, user[0] =0; /* to make everything well-defined */ passwd[0]=0; - if (conn->protocol & (PROT_FTP|PROT_HTTP)) { + if (conn->protocol & (PROT_FTP|PROT_HTTP|PROT_SCP)) { /* This is a FTP or HTTP URL, we will now try to extract the possible * user+password pair in a string like: * ftp://user:password@ftp.my.site:8021/README */ diff --git a/lib/urldata.h b/lib/urldata.h index c93b46dff..a42b09c76 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -35,6 +35,7 @@ #define PORT_DICT 2628 #define PORT_LDAP 389 #define PORT_TFTP 69 +#define PORT_SSH 22 #define DICT_MATCH "/MATCH:" #define DICT_MATCH2 "/M:" @@ -109,6 +110,11 @@ # endif #endif +#ifdef HAVE_LIBSSH2_H +#include +#include +#endif /* HAVE_LIBSSH2_H */ + /* Download buffer size, keep it fairly big for speed reasons */ #undef BUFSIZE #define BUFSIZE CURL_MAX_WRITE_SIZE @@ -392,6 +398,22 @@ struct ftp_conn { ftpstate state; /* always use ftp.c:state() to change state! */ }; +struct SCPPROTO { + curl_off_t *bytecountp; + char *user; + char *passwd; + char *path; /* the path we operate on */ + char *freepath; /* pointer to the allocated block we must + free, this might differ from the 'path' + pointer */ + char *errorstr; +#ifdef USE_LIBSSH2 + LIBSSH2_SESSION *scpSession; /* Secure Shell session */ + LIBSSH2_CHANNEL *scpChannel; /* SCP channel handle */ +#endif /* USE_LIBSSH2 */ +}; + + /**************************************************************************** * FILE unique setup ***************************************************************************/ @@ -647,6 +669,7 @@ struct HandleData { struct FILEPROTO *file; void *telnet; /* private for telnet.c-eyes only */ void *generic; + struct SCPPROTO *scp; } proto; }; @@ -681,6 +704,7 @@ struct connectdata { #define PROT_FTPS (1<<9) #define PROT_SSL (1<<10) /* protocol requires SSL */ #define PROT_TFTP (1<<11) +#define PROT_SCP (1<<12) /* '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 @@ -1250,6 +1274,11 @@ struct UserDefined { bool ftp_skip_ip; /* skip the IP address the FTP server passes on to us */ bool connect_only; /* make connection, let application use the socket */ + long ssh_auth_types; /* allowed SSH auth types */ + char *ssh_public_key; /* the path to the public key file for + authentication */ + char *ssh_private_key; /* the path to the private key file for + authentication */ }; struct Names { diff --git a/lib/version.c b/lib/version.c index 722f93b22..c778896b2 100644 --- a/lib/version.c +++ b/lib/version.c @@ -45,6 +45,11 @@ #include #endif +#ifdef USE_LIBSSH2 +#include +#endif + + char *curl_version(void) { static char version[200]; @@ -88,6 +93,11 @@ char *curl_version(void) left -= len; ptr += len; #endif +#ifdef USE_LIBSSH2 + len = snprintf(ptr, left, " libssh2/%s", LIBSSH2_VERSION); + left -= len; + ptr += len; +#endif return version; } @@ -125,6 +135,11 @@ static const char * const protocols[] = { "ftps", #endif #endif + +#ifdef USE_LIBSSH2 + "scp", +#endif + NULL }; @@ -179,10 +194,15 @@ static curl_version_info_data version_info = { 0, /* c-ares version numerical */ NULL, /* libidn version */ 0, /* iconv version */ + NULL, /* ssh lib version */ }; curl_version_info_data *curl_version_info(CURLversion stamp) { +#ifdef USE_LIBSSH2 + static char ssh_buffer[80]; +#endif + #ifdef USE_SSL static char ssl_buffer[80]; Curl_ssl_version(ssl_buffer, sizeof(ssl_buffer)); @@ -217,6 +237,11 @@ curl_version_info_data *curl_version_info(CURLversion stamp) #endif /* _LIBICONV_VERSION */ #endif +#ifdef USE_LIBSSH2 + snprintf(ssh_buffer, sizeof(ssh_buffer), "libssh2/%s", LIBSSH2_VERSION); + version_info.libssh_version = ssh_buffer; +#endif + (void)stamp; /* avoid compiler warnings, we don't use this */ return &version_info; -- cgit v1.2.3