aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorNikos Mavrogiannopoulos <nmav@redhat.com>2017-10-23 13:49:23 +0200
committerDaniel Stenberg <daniel@haxx.se>2017-12-01 17:35:14 +0100
commitc92d2e14cfb0db662f958effd2ac86f995cf1b5a (patch)
treea5352cc296c3f7230dc0e34391262941b070feda /lib
parent3973ee6a654e96c027afee86037680334b24e709 (diff)
Added support for libssh SSH SCP back-end
libssh is an alternative library to libssh2. https://www.libssh.org/ That patch set also introduces support for ECDSA ed25519 keys, as well as gssapi authentication. Signed-off-by: Nikos Mavrogiannopoulos <nmav@redhat.com>
Diffstat (limited to 'lib')
-rw-r--r--lib/Makefile.inc7
-rw-r--r--lib/curl_path.c179
-rw-r--r--lib/curl_path.h45
-rw-r--r--lib/easy.c11
-rw-r--r--lib/setopt.c3
-rw-r--r--lib/ssh-libssh.c1234
-rw-r--r--lib/ssh.c183
-rw-r--r--lib/ssh.h48
-rw-r--r--lib/url.c5
-rw-r--r--lib/urldata.h2
-rw-r--r--lib/version.c15
11 files changed, 1537 insertions, 195 deletions
diff --git a/lib/Makefile.inc b/lib/Makefile.inc
index 0a88b8eb8..61e80cf52 100644
--- a/lib/Makefile.inc
+++ b/lib/Makefile.inc
@@ -46,7 +46,7 @@ LIB_CFILES = file.c timeval.c base64.c hostip.c progress.c formdata.c \
http_digest.c md4.c md5.c http_negotiate.c inet_pton.c strtoofft.c \
strerror.c amigaos.c hostasyn.c hostip4.c hostip6.c hostsyn.c \
inet_ntop.c parsedate.c select.c tftp.c splay.c strdup.c socks.c \
- ssh.c curl_addrinfo.c socks_gssapi.c socks_sspi.c \
+ ssh.c ssh-libssh.c curl_addrinfo.c socks_gssapi.c socks_sspi.c \
curl_sspi.c slist.c nonblock.c curl_memrchr.c imap.c pop3.c smtp.c \
pingpong.c rtsp.c curl_threads.c warnless.c hmac.c curl_rtmp.c \
openldap.c curl_gethostname.c gopher.c idn_win32.c \
@@ -54,7 +54,7 @@ LIB_CFILES = file.c timeval.c base64.c hostip.c progress.c formdata.c \
http_ntlm.c curl_ntlm_wb.c curl_ntlm_core.c curl_sasl.c rand.c \
curl_multibyte.c hostcheck.c conncache.c pipeline.c dotdot.c \
x509asn1.c http2.c smb.c curl_endian.c curl_des.c system_win32.c \
- mime.c sha256.c setopt.c
+ mime.c sha256.c setopt.c curl_path.c
LIB_HFILES = arpa_telnet.h netrc.h file.h timeval.h hostip.h progress.h \
formdata.h cookie.h http.h sendf.h ftp.h url.h dict.h if2ip.h \
@@ -73,7 +73,8 @@ LIB_HFILES = arpa_telnet.h netrc.h file.h timeval.h hostip.h progress.h \
curl_sasl.h curl_multibyte.h hostcheck.h conncache.h \
curl_setup_once.h multihandle.h setup-vms.h pipeline.h dotdot.h \
x509asn1.h http2.h sigpipe.h smb.h curl_endian.h curl_des.h \
- curl_printf.h system_win32.h rand.h mime.h curl_sha256.h setopt.h
+ curl_printf.h system_win32.h rand.h mime.h curl_sha256.h setopt.h \
+ curl_path.h
LIB_RCFILES = libcurl.rc
diff --git a/lib/curl_path.c b/lib/curl_path.c
new file mode 100644
index 000000000..cd6592096
--- /dev/null
+++ b/lib/curl_path.c
@@ -0,0 +1,179 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2017, Daniel Stenberg, <daniel@haxx.se>, 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 https://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.
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#include <curl/curl.h>
+#include "curl_memory.h"
+#include "curl_path.h"
+#include "escape.h"
+#include "memdebug.h"
+
+/* figure out the path to work with in this particular request */
+CURLcode Curl_getworkingpath(struct connectdata *conn,
+ char *homedir, /* when SFTP is used */
+ char **path) /* returns the allocated
+ real path to work with */
+{
+ struct Curl_easy *data = conn->data;
+ char *real_path = NULL;
+ char *working_path;
+ size_t working_path_len;
+ CURLcode result =
+ Curl_urldecode(data, data->state.path, 0, &working_path,
+ &working_path_len, FALSE);
+ if(result)
+ return result;
+
+ /* Check for /~/, indicating relative to the user's home directory */
+ if(conn->handler->protocol & CURLPROTO_SCP) {
+ real_path = malloc(working_path_len + 1);
+ if(real_path == NULL) {
+ free(working_path);
+ return CURLE_OUT_OF_MEMORY;
+ }
+ if((working_path_len > 3) && (!memcmp(working_path, "/~/", 3)))
+ /* It is referenced to the home directory, so strip the leading '/~/' */
+ memcpy(real_path, working_path + 3, 4 + working_path_len-3);
+ else
+ memcpy(real_path, working_path, 1 + working_path_len);
+ }
+ else if(conn->handler->protocol & CURLPROTO_SFTP) {
+ if((working_path_len > 1) && (working_path[1] == '~')) {
+ size_t homelen = strlen(homedir);
+ real_path = malloc(homelen + working_path_len + 1);
+ if(real_path == NULL) {
+ free(working_path);
+ return CURLE_OUT_OF_MEMORY;
+ }
+ /* It is referenced to the home directory, so strip the
+ leading '/' */
+ memcpy(real_path, homedir, homelen);
+ real_path[homelen] = '/';
+ real_path[homelen + 1] = '\0';
+ if(working_path_len > 3) {
+ memcpy(real_path + homelen + 1, working_path + 3,
+ 1 + working_path_len -3);
+ }
+ }
+ else {
+ real_path = malloc(working_path_len + 1);
+ if(real_path == NULL) {
+ free(working_path);
+ return CURLE_OUT_OF_MEMORY;
+ }
+ memcpy(real_path, working_path, 1 + working_path_len);
+ }
+ }
+
+ free(working_path);
+
+ /* store the pointer for the caller to receive */
+ *path = real_path;
+
+ return CURLE_OK;
+}
+
+/* The get_pathname() function is being borrowed from OpenSSH sftp.c
+ version 4.6p1. */
+/*
+ * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+CURLcode Curl_get_pathname(const char **cpp, char **path)
+{
+ const char *cp = *cpp, *end;
+ char quot;
+ unsigned int i, j;
+ static const char WHITESPACE[] = " \t\r\n";
+
+ cp += strspn(cp, WHITESPACE);
+ if(!*cp) {
+ *cpp = cp;
+ *path = NULL;
+ return CURLE_QUOTE_ERROR;
+ }
+
+ *path = malloc(strlen(cp) + 1);
+ if(*path == NULL)
+ return CURLE_OUT_OF_MEMORY;
+
+ /* Check for quoted filenames */
+ if(*cp == '\"' || *cp == '\'') {
+ quot = *cp++;
+
+ /* Search for terminating quote, unescape some chars */
+ for(i = j = 0; i <= strlen(cp); i++) {
+ if(cp[i] == quot) { /* Found quote */
+ i++;
+ (*path)[j] = '\0';
+ break;
+ }
+ if(cp[i] == '\0') { /* End of string */
+ /*error("Unterminated quote");*/
+ goto fail;
+ }
+ if(cp[i] == '\\') { /* Escaped characters */
+ i++;
+ if(cp[i] != '\'' && cp[i] != '\"' &&
+ cp[i] != '\\') {
+ /*error("Bad escaped character '\\%c'",
+ cp[i]);*/
+ goto fail;
+ }
+ }
+ (*path)[j++] = cp[i];
+ }
+
+ if(j == 0) {
+ /*error("Empty quotes");*/
+ goto fail;
+ }
+ *cpp = cp + i + strspn(cp + i, WHITESPACE);
+ }
+ else {
+ /* Read to end of filename */
+ end = strpbrk(cp, WHITESPACE);
+ if(end == NULL)
+ end = strchr(cp, '\0');
+ *cpp = end + strspn(end, WHITESPACE);
+
+ memcpy(*path, cp, end - cp);
+ (*path)[end - cp] = '\0';
+ }
+ return CURLE_OK;
+
+ fail:
+ Curl_safefree(*path);
+ return CURLE_QUOTE_ERROR;
+}
diff --git a/lib/curl_path.h b/lib/curl_path.h
new file mode 100644
index 000000000..3ce16eb88
--- /dev/null
+++ b/lib/curl_path.h
@@ -0,0 +1,45 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2017, Daniel Stenberg, <daniel@haxx.se>, 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 https://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.
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+#include <curl/curl.h>
+#include "urldata.h"
+
+#ifdef WIN32
+# undef PATH_MAX
+# define PATH_MAX MAX_PATH
+# ifndef R_OK
+# define R_OK 4
+# endif
+#endif
+
+#ifndef PATH_MAX
+#define PATH_MAX 1024 /* just an extra precaution since there are systems that
+ have their definition hidden well */
+#endif
+
+CURLcode Curl_getworkingpath(struct connectdata *conn,
+ char *homedir,
+ char **path);
+
+CURLcode
+Curl_get_pathname(const char **cpp, char **path);
diff --git a/lib/easy.c b/lib/easy.c
index 5917db36a..72506488a 100644
--- a/lib/easy.c
+++ b/lib/easy.c
@@ -253,6 +253,13 @@ static CURLcode global_init(long flags, bool memoryfuncs)
}
#endif
+#if defined(USE_LIBSSH)
+ if(ssh_init()) {
+ DEBUGF(fprintf(stderr, "Error: libssh_init failed\n"));
+ return CURLE_FAILED_INIT;
+ }
+#endif
+
if(flags & CURL_GLOBAL_ACK_EINTR)
Curl_ack_eintr = 1;
@@ -330,6 +337,10 @@ void curl_global_cleanup(void)
(void)libssh2_exit();
#endif
+#if defined(USE_LIBSSH)
+ (void)ssh_finalize();
+#endif
+
init_flags = 0;
}
diff --git a/lib/setopt.c b/lib/setopt.c
index 70466bffb..944d17347 100644
--- a/lib/setopt.c
+++ b/lib/setopt.c
@@ -2110,7 +2110,7 @@ static CURLcode setopt(struct Curl_easy *data, CURLoption option,
data->set.proxy_ssl.primary.sessionid = data->set.ssl.primary.sessionid;
break;
-#ifdef USE_LIBSSH2
+#if defined(USE_LIBSSH2) || defined(USE_LIBSSH)
/* we only include SSH options if explicitly built to support SSH */
case CURLOPT_SSH_AUTH_TYPES:
data->set.ssh_auth_types = va_arg(param, long);
@@ -2161,7 +2161,6 @@ static CURLcode setopt(struct Curl_easy *data, CURLoption option,
data->set.ssh_keyfunc_userp = va_arg(param, void *);
break;
#endif /* HAVE_LIBSSH2_KNOWNHOST_API */
-
#endif /* USE_LIBSSH2 */
case CURLOPT_HTTP_TRANSFER_DECODING:
diff --git a/lib/ssh-libssh.c b/lib/ssh-libssh.c
new file mode 100644
index 000000000..6c05f6d2d
--- /dev/null
+++ b/lib/ssh-libssh.c
@@ -0,0 +1,1234 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2017 Red Hat, Inc.
+ *
+ * Authors: Nikos Mavrogiannopoulos, Tomas Mraz, Stanislav Zidek, Robert Kolcun
+ *
+ * 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 https://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.
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef USE_LIBSSH
+
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+
+#include <libssh/libssh.h>
+
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+#ifdef HAVE_UTSNAME_H
+#include <sys/utsname.h>
+#endif
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+#ifdef __VMS
+#include <in.h>
+#include <inet.h>
+#endif
+
+#if (defined(NETWARE) && defined(__NOVELL_LIBC__))
+#undef in_addr_t
+#define in_addr_t unsigned long
+#endif
+
+#include <curl/curl.h>
+#include "urldata.h"
+#include "sendf.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 "strdup.h"
+#include "strcase.h"
+#include "vtls/vtls.h"
+#include "connect.h"
+#include "strerror.h"
+#include "inet_ntop.h"
+#include "parsedate.h" /* for the week day and month names */
+#include "sockaddr.h" /* required for Curl_sockaddr_storage */
+#include "strtoofft.h"
+#include "multiif.h"
+#include "select.h"
+#include "warnless.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+#include "curl_path.h"
+
+/* Local functions: */
+static CURLcode myssh_connect(struct connectdata *conn, bool *done);
+static CURLcode myssh_multi_statemach(struct connectdata *conn,
+ bool *done);
+static CURLcode myssh_do_it(struct connectdata *conn, bool *done);
+
+static CURLcode scp_done(struct connectdata *conn,
+ CURLcode, bool premature);
+static CURLcode scp_doing(struct connectdata *conn, bool *dophase_done);
+static CURLcode scp_disconnect(struct connectdata *conn,
+ bool dead_connection);
+
+static int myssh_getsock(struct connectdata *conn, curl_socket_t *sock,
+ int numsocks);
+
+static int myssh_perform_getsock(const struct connectdata *conn,
+ curl_socket_t *sock,
+ int numsocks);
+
+static CURLcode myssh_setup_connection(struct connectdata *conn);
+
+/*
+ * SCP protocol handler.
+ */
+
+const struct Curl_handler Curl_handler_scp = {
+ "SCP", /* scheme */
+ myssh_setup_connection, /* setup_connection */
+ myssh_do_it, /* do_it */
+ scp_done, /* done */
+ ZERO_NULL, /* do_more */
+ myssh_connect, /* connect_it */
+ myssh_multi_statemach, /* connecting */
+ scp_doing, /* doing */
+ myssh_getsock, /* proto_getsock */
+ myssh_getsock, /* doing_getsock */
+ ZERO_NULL, /* domore_getsock */
+ myssh_perform_getsock, /* perform_getsock */
+ scp_disconnect, /* disconnect */
+ ZERO_NULL, /* readwrite */
+ ZERO_NULL, /* connection_check */
+ PORT_SSH, /* defport */
+ CURLPROTO_SCP, /* protocol */
+ PROTOPT_DIRLOCK | PROTOPT_CLOSEACTION | PROTOPT_NOURLQUERY /* flags */
+};
+
+/*
+ * SSH State machine related code
+ */
+/* This is the ONLY way to change SSH state! */
+static void state(struct connectdata *conn, sshstate nowstate)
+{
+ struct ssh_conn *sshc = &conn->proto.sshc;
+#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
+ /* for debug purposes */
+ static const char *const names[] = {
+ "SSH_STOP",
+ "SSH_INIT",
+ "SSH_S_STARTUP",
+ "SSH_HOSTKEY",
+ "SSH_AUTHLIST",
+ "SSH_AUTH_PKEY_INIT",
+ "SSH_AUTH_PKEY",
+ "SSH_AUTH_PASS_INIT",
+ "SSH_AUTH_PASS",
+ "SSH_AUTH_AGENT_INIT",
+ "SSH_AUTH_AGENT_LIST",
+ "SSH_AUTH_AGENT",
+ "SSH_AUTH_HOST_INIT",
+ "SSH_AUTH_HOST",
+ "SSH_AUTH_KEY_INIT",
+ "SSH_AUTH_KEY",
+ "SSH_AUTH_GSSAPI",
+ "SSH_AUTH_DONE",
+ "SSH_SFTP_INIT",
+ "SSH_SFTP_REALPATH",
+ "SSH_SFTP_QUOTE_INIT",
+ "SSH_SFTP_POSTQUOTE_INIT",
+ "SSH_SFTP_QUOTE",
+ "SSH_SFTP_NEXT_QUOTE",
+ "SSH_SFTP_QUOTE_STAT",
+ "SSH_SFTP_QUOTE_SETSTAT",
+ "SSH_SFTP_QUOTE_SYMLINK",
+ "SSH_SFTP_QUOTE_MKDIR",
+ "SSH_SFTP_QUOTE_RENAME",
+ "SSH_SFTP_QUOTE_RMDIR",
+ "SSH_SFTP_QUOTE_UNLINK",
+ "SSH_SFTP_QUOTE_STATVFS",
+ "SSH_SFTP_GETINFO",
+ "SSH_SFTP_FILETIME",
+ "SSH_SFTP_TRANS_INIT",
+ "SSH_SFTP_UPLOAD_INIT",
+ "SSH_SFTP_CREATE_DIRS_INIT",
+ "SSH_SFTP_CREATE_DIRS",
+ "SSH_SFTP_CREATE_DIRS_MKDIR",
+ "SSH_SFTP_READDIR_INIT",
+ "SSH_SFTP_READDIR",
+ "SSH_SFTP_READDIR_LINK",
+ "SSH_SFTP_READDIR_BOTTOM",
+ "SSH_SFTP_READDIR_DONE",
+ "SSH_SFTP_DOWNLOAD_INIT",
+ "SSH_SFTP_DOWNLOAD_STAT",
+ "SSH_SFTP_CLOSE",
+ "SSH_SFTP_SHUTDOWN",
+ "SSH_SCP_TRANS_INIT",
+ "SSH_SCP_UPLOAD_INIT",
+ "SSH_SCP_DOWNLOAD_INIT",
+ "SSH_SCP_DOWNLOAD",
+ "SSH_SCP_DONE",
+ "SSH_SCP_SEND_EOF",
+ "SSH_SCP_WAIT_EOF",
+ "SSH_SCP_WAIT_CLOSE",
+ "SSH_SCP_CHANNEL_FREE",
+ "SSH_SESSION_DISCONNECT",
+ "SSH_SESSION_FREE",
+ "QUIT"
+ };
+
+
+ if(sshc->state != nowstate) {
+ infof(conn->data, "SSH %p state change from %s to %s\n",
+ (void *) sshc, names[sshc->state], names[nowstate]);
+ }
+#endif
+
+ sshc->state = nowstate;
+}
+
+/* Multiple options:
+ * 1. data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5] is set with an MD5
+ * hash (90s style auth, not sure we should have it here)
+ * 2. data->set.ssh_keyfunc callback is set. Then we do trust on first
+ * use. We even save on knownhosts if CURLKHSTAT_FINE_ADD_TO_FILE
+ * is returned by it.
+ * 3. none of the above. We only accept if it is present on known hosts.
+ *
+ * Returns SSH_OK or SSH_ERROR.
+ */
+static int myssh_is_known(struct connectdata *conn)
+{
+ int rc;
+ struct Curl_easy *data = conn->data;
+ struct ssh_conn *sshc = &conn->proto.sshc;
+ ssh_key pubkey;
+ size_t hlen;
+ unsigned char *hash = NULL;
+ char *base64 = NULL;
+ int vstate;
+ enum curl_khmatch keymatch;
+ struct curl_khkey foundkey;
+ curl_sshkeycallback func =
+ data->set.ssh_keyfunc;
+
+ rc = ssh_get_publickey(sshc->ssh_session, &pubkey);
+ if(rc != SSH_OK)
+ return rc;
+
+ if(data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]) {
+ rc = ssh_get_publickey_hash(pubkey, SSH_PUBLICKEY_HASH_MD5,
+ &hash, &hlen);
+ if(rc != SSH_OK)
+ goto cleanup;
+
+ if(hlen != strlen(data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]) ||
+ memcmp(&data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5], hash, hlen)) {
+ rc = SSH_ERROR;
+ goto cleanup;
+ }
+
+ rc = SSH_OK;
+ goto cleanup;
+ }
+
+ if(data->set.ssl.primary.verifyhost != TRUE) {
+ rc = SSH_OK;
+ goto cleanup;
+ }
+
+ vstate = ssh_is_server_known(sshc->ssh_session);
+ switch(vstate) {
+ case SSH_SERVER_KNOWN_OK:
+ keymatch = CURLKHMATCH_OK;
+ case SSH_SERVER_FILE_NOT_FOUND:
+ case SSH_SERVER_NOT_KNOWN:
+ keymatch = CURLKHMATCH_MISSING;
+ default:
+ keymatch = CURLKHMATCH_MISMATCH;
+ }
+
+ if(func) { /* use callback to determine action */
+ rc = ssh_pki_export_pubkey_base64(pubkey, &base64);
+ if(rc != SSH_OK)
+ goto cleanup;
+
+ foundkey.key = base64;
+ foundkey.len = strlen(base64);
+
+ switch(ssh_key_type(pubkey)) {
+ case SSH_KEYTYPE_RSA:
+ foundkey.keytype = CURLKHTYPE_RSA;
+ break;
+ case SSH_KEYTYPE_RSA1:
+ foundkey.keytype = CURLKHTYPE_RSA1;
+ break;
+ case SSH_KEYTYPE_ECDSA:
+ foundkey.keytype = CURLKHTYPE_ECDSA;
+ break;
+#if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0,7,0)
+ case SSH_KEYTYPE_ED25519:
+ foundkey.keytype = CURLKHTYPE_ED25519;
+ break;
+#endif
+ case SSH_KEYTYPE_DSS:
+ foundkey.keytype = CURLKHTYPE_DSS;
+ break;
+ default:
+ rc = SSH_ERROR;
+ goto cleanup;
+ }
+
+ /* we don't have anything equivalent to knownkey. Always NULL */
+ rc = func(data, NULL, &foundkey, /* from the remote host */
+ keymatch, data->set.ssh_keyfunc_userp);
+
+ switch(rc) {
+ case CURLKHSTAT_FINE_ADD_TO_FILE:
+ rc = ssh_write_knownhost(sshc->ssh_session);
+ if(rc != SSH_OK) {
+ goto cleanup;
+ }
+ break;
+ case CURLKHSTAT_FINE:
+ break;
+ default: /* REJECT/DEFER */
+ rc = SSH_ERROR;
+ goto cleanup;
+ }
+ }
+ else {
+ if(keymatch != CURLKHMATCH_OK) {
+ rc = SSH_ERROR;
+ goto cleanup;
+ }
+ }
+ rc = SSH_OK;
+
+cleanup:
+ if(hash)
+ ssh_clean_pubkey_hash(&hash);
+ ssh_key_free(pubkey);
+ return rc;
+}
+
+#define MOVE_TO_ERROR_STATE(_r) { \
+ state(conn, SSH_SESSION_FREE); \
+ sshc->actualcode = _r; \
+ rc = SSH_ERROR; \
+ break; \
+}
+
+#define MOVE_TO_LAST_AUTH \
+ if(sshc->auth_methods & SSH_AUTH_METHOD_PASSWORD) { \
+ rc = SSH_OK; \
+ state(conn, SSH_AUTH_PASS_INIT); \
+ break; \
+ } \
+ else { \
+ MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); \
+ }
+
+#define MOVE_TO_SECONDARY_AUTH \
+ if(sshc->auth_methods & SSH_AUTH_METHOD_GSSAPI_MIC) { \
+ rc = SSH_OK; \
+ state(conn, SSH_AUTH_GSSAPI); \
+ break; \
+ } \
+ else { \
+ MOVE_TO_LAST_AUTH; \
+ }
+
+
+/*
+ * ssh_statemach_act() runs the SSH state machine as far as it can without
+ * blocking and without reaching the end. The data the pointer 'block' points
+ * to will be set to TRUE if the libssh function returns SSH_AGAIN
+ * meaning it wants to be called again when the socket is ready
+ */
+static CURLcode myssh_statemach_act(struct connectdata *conn, bool *block)
+{
+ CURLcode result = CURLE_OK;
+ struct Curl_easy *data = conn->data;
+ struct SSHPROTO *protop = data->req.protop;
+ struct ssh_conn *sshc = &conn->proto.sshc;
+ int rc = SSH_NO_ERROR;
+ const char *err_msg;
+ *block = 0; /* we're not blocking by default */
+
+ do {
+
+ switch(sshc->state) {
+ case SSH_INIT:
+ sshc->secondCreateDirs = 0;
+ sshc->nextstate = SSH_NO_STATE;
+ sshc->actualcode = CURLE_OK;
+
+#if 0
+ ssh_set_log_level(SSH_LOG_PACKET);
+#endif
+
+ /* Set libssh to non-blocking, since everything internally is
+ non-blocking */
+ ssh_set_blocking(sshc->ssh_session, 0);
+
+ state(conn, SSH_S_STARTUP);
+ /* fall-through */
+
+ case SSH_S_STARTUP:
+ rc = ssh_connect(sshc->ssh_session);
+ if(rc == SSH_AGAIN)
+ break;
+
+ if(rc != SSH_OK) {
+ failf(data, "Failure establishing ssh session");
+ MOVE_TO_ERROR_STATE(CURLE_FAILED_INIT);
+ }
+
+ state(conn, SSH_HOSTKEY);
+
+ /* fall-through */
+ case SSH_HOSTKEY:
+
+ rc = myssh_is_known(conn);
+ if(rc != SSH_OK) {
+ MOVE_TO_ERROR_STATE(CURLE_PEER_FAILED_VERIFICATION);
+ }
+
+ state(conn, SSH_AUTHLIST);
+ /* fall through */
+ case SSH_AUTHLIST:{
+ sshc->authed = FALSE;
+
+ rc = ssh_userauth_none(sshc->ssh_session, NULL);
+ if(rc == SSH_AUTH_AGAIN) {
+ rc = SSH_AGAIN;
+ break;
+ }
+
+ if(rc == SSH_AUTH_SUCCESS) {
+ sshc->authed = TRUE;
+ infof(data, "Authenticated with none\n");
+ state(conn, SSH_AUTH_DONE);
+ break;
+ }
+ else if(rc == SSH_AUTH_ERROR) {
+ MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED);
+ }
+
+ sshc->auth_methods = ssh_userauth_list(sshc->ssh_session, NULL);
+ if(sshc->auth_methods & SSH_AUTH_METHOD_PUBLICKEY) {
+ state(conn, SSH_AUTH_PKEY_INIT);
+ }
+ else if(sshc->auth_methods & SSH_AUTH_METHOD_GSSAPI_MIC) {
+ state(conn, SSH_AUTH_GSSAPI);
+ }
+ else if(sshc->auth_methods & SSH_AUTH_METHOD_PASSWORD) {
+ state(conn, SSH_AUTH_PASS_INIT);
+ }
+ else { /* unsupported authentication method */
+ MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED);
+ }
+
+ break;
+ }
+ case SSH_AUTH_PKEY_INIT:
+ if(!(data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY)) {
+ MOVE_TO_SECONDARY_AUTH;
+ }
+
+ /* Two choices, (1) private key was given on CMD,
+ * (2) use the "default" keys. */
+ if(data->set.str[STRING_SSH_PRIVATE_KEY]) {
+ if(sshc->pubkey && !data->set.ssl.key_passwd) {
+ rc = ssh_userauth_try_publickey(sshc->ssh_session, NULL,
+ sshc->pubkey);
+ if(rc == SSH_AUTH_AGAIN) {
+ rc = SSH_AGAIN;
+ break;
+ }
+
+ if(rc != SSH_OK) {
+ MOVE_TO_SECONDARY_AUTH;
+ }
+ }
+
+ rc = ssh_pki_import_privkey_file(data->
+ set.str[STRING_SSH_PRIVATE_KEY],
+ data->set.ssl.key_passwd, NULL,
+ NULL, &sshc->privkey);
+ if(rc != SSH_OK) {
+ failf(data, "Could not load private key file %s",
+ data->set.str[STRING_SSH_PRIVATE_KEY]);
+ break;
+ }
+
+ state(conn, SSH_AUTH_PKEY);
+ break;
+
+ }
+ else {
+ infof(data, "Authentication using SSH public key file\n");
+
+ rc = ssh_userauth_publickey_auto(sshc->ssh_session, NULL,
+ data->set.ssl.key_passwd);
+ if(rc == SSH_AUTH_AGAIN) {
+ rc = SSH_AGAIN;
+ break;
+ }
+ if(rc == SSH_AUTH_SUCCESS) {
+ rc = SSH_OK;
+ sshc->authed = TRUE;
+ infof(data, "Completed public key authentication\n");
+ state(conn, SSH_AUTH_DONE);
+ break;
+ }
+
+ MOVE_TO_SECONDARY_AUTH;
+ }
+ break;
+ case SSH_AUTH_PKEY:
+ rc = ssh_userauth_publickey(sshc->ssh_session, NULL, sshc->privkey);
+ if(rc == SSH_AUTH_AGAIN) {
+ rc = SSH_AGAIN;
+ break;
+ }
+
+ if(rc == SSH_AUTH_SUCCESS) {
+ sshc->authed = TRUE;
+ infof(data, "Completed public key authentication\n");
+ state(conn, SSH_AUTH_DONE);
+ break;
+ }
+ else {
+ infof(data, "Failed public key authentication (rc: %d)\n", rc);
+ MOVE_TO_SECONDARY_AUTH;
+ }
+ break;
+
+ case SSH_AUTH_GSSAPI:
+ if(!(data->set.ssh_auth_types & CURLSSH_AUTH_GSSAPI)) {
+ MOVE_TO_LAST_AUTH;
+ }
+
+ rc = ssh_userauth_gssapi(sshc->ssh_session);
+ if(rc == SSH_AUTH_AGAIN) {
+ rc = SSH_AGAIN;
+ break;
+ }
+
+ if(rc == SSH_AUTH_SUCCESS) {
+ rc = SSH_OK;
+ sshc->authed = TRUE;
+ infof(data, "Completed gssapi authentication\n");
+ state(conn, SSH_AUTH_DONE);
+ break;
+ }
+
+ MOVE_TO_LAST_AUTH;
+ break;
+
+ case SSH_AUTH_PASS_INIT:
+ if(!(data->set.ssh_auth_types & CURLSSH_AUTH_PASSWORD)) {
+ /* Host key authentication is intentionally not implemented */
+ MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED);
+ }
+ state(conn, SSH_AUTH_PASS);
+ /* fall through */
+
+ case SSH_AUTH_PASS:
+ rc = ssh_userauth_password(sshc->ssh_session, NULL, conn->passwd);
+ if(rc == SSH_AUTH_AGAIN) {
+ rc = SSH_AGAIN;
+ break;
+ }
+
+ if(rc == SSH_AUTH_SUCCESS) {
+ sshc->authed = TRUE;
+ infof(data, "Completed password authentication\n");
+ state(conn, SSH_AUTH_DONE);
+ }
+ else {
+ MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED);
+ }
+ break;
+
+ case SSH_AUTH_DONE:
+ if(!sshc->authed) {
+ failf(data, "Authentication failure");
+ MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED);
+ break;
+ }
+
+ /*
+ * At this point we have an authenticated ssh session.
+ */
+ infof(data, "Authentication complete\n");
+
+ Curl_pgrsTime(conn->data, TIMER_APPCONNECT); /* SSH is connected */
+
+ conn->sockfd = ssh_get_fd(sshc->ssh_session);
+ conn->writesockfd = CURL_SOCKET_BAD;
+
+ infof(data, "SSH CONNECT phase done\n");
+ state(conn, SSH_STOP);
+ break;
+
+ case SSH_SCP_TRANS_INIT:
+ result = Curl_getworkingpath(conn, sshc->homedir, &protop->path);
+ if(result) {
+ sshc->actualcode = result;
+ state(conn, SSH_STOP);
+ break;
+ }
+
+ /* Functions from the SCP subsystem cannot handle/return SSH_AGAIN */
+ ssh_set_blocking(sshc->ssh_session, 1);
+
+ if(data->set.upload) {
+ if(data->state.infilesize < 0) {
+ failf(data, "SCP requires a known file size for upload");
+ sshc->actualcode = CURLE_UPLOAD_FAILED;
+ MOVE_TO_ERROR_STATE(CURLE_UPLOAD_FAILED);
+ }
+
+ sshc->scp_session =
+ ssh_scp_new(sshc->ssh_session, SSH_SCP_WRITE, protop->path);
+ state(conn, SSH_SCP_UPLOAD_INIT);
+ }
+ else {
+ sshc->scp_session =
+ ssh_scp_new(sshc->ssh_session, SSH_SCP_READ, protop->path);
+ state(conn, SSH_SCP_DOWNLOAD_INIT);
+ }
+
+ if(!sshc->scp_session) {
+ err_msg = ssh_get_error(sshc->ssh_session);
+ failf(conn->data, "%s", err_msg);
+ MOVE_TO_ERROR_STATE(CURLE_UPLOAD_FAILED);
+ }
+
+ break;
+
+ case SSH_SCP_UPLOAD_INIT:
+
+ rc = ssh_scp_init(sshc->scp_session);
+ if(rc != SSH_OK) {
+ err_msg = ssh_get_error(sshc->ssh_session);
+ failf(conn->data, "%s", err_msg);
+ MOVE_TO_ERROR_STATE(CURLE_UPLOAD_FAILED);
+ }
+
+ rc = ssh_scp_push_file(sshc->scp_session, protop->path,
+ data->state.infilesize,
+ (int)data->set.new_file_perms);
+ if(rc != SSH_OK) {
+ err_msg = ssh_get_error(sshc->ssh_session);
+ failf(conn->data, "%s", err_msg);
+ MOVE_TO_ERROR_STATE(CURLE_UPLOAD_FAILED);
+ }
+
+ /* upload data */
+ Curl_setup_transfer(conn, -1, data->req.size, FALSE, NULL,
+ FIRSTSOCKET, NULL);
+
+ /* not set by Curl_setup_transfer to preserve keepon bits */
+ conn->sockfd = conn->writesockfd;
+
+ /* store this original bitmask setup to use later on if we can't
+ figure out a "real" bitmask */
+ sshc->orig_waitfor = data->req.keepon;
+
+ /* we want to use the _sending_ function even when the socket turns
+ out readable as the underlying libssh scp send function will deal
+ with both accordingly */
+ conn->cselect_bits = CURL_CSELECT_OUT;
+
+ state(conn, SSH_STOP);
+
+ break;
+
+ case SSH_SCP_DOWNLOAD_INIT:
+
+ rc = ssh_scp_init(sshc->scp_session);
+ if(rc != SSH_OK) {
+ err_msg = ssh_get_error(sshc->ssh_session);
+ failf(conn->data, "%s", err_msg);
+ MOVE_TO_ERROR_STATE(CURLE_COULDNT_CONNECT);
+ }
+ state(conn, SSH_SCP_DOWNLOAD);
+ /* fall through */
+
+ case SSH_SCP_DOWNLOAD:{
+ curl_off_t bytecount;
+
+ rc = ssh_scp_pull_request(sshc->scp_session);
+ if(rc != SSH_SCP_REQUEST_NEWFILE) {
+ err_msg = ssh_get_error(sshc->ssh_session);
+ failf(conn->data, "%s", err_msg);
+ MOVE_TO_ERROR_STATE(CURLE_REMOTE_FILE_NOT_FOUND);
+ break;
+ }
+
+ /* download data */
+ bytecount = ssh_scp_request_get_size(sshc->scp_session);
+ data->req.maxdownload = (curl_off_t) bytecount;
+ Curl_setup_transfer(conn, FIRSTSOCKET, bytecount, FALSE, NULL, -1,
+ NULL);
+
+ /* not set by Curl_setup_transfer to preserve keepon bits */
+ conn->writesockfd = conn->sockfd;
+
+ /* we want to use the _receiving_ function even when the socket turns
+ out writableable as the underlying libssh recv function will deal
+ with both accordingly */
+ conn->cselect_bits = CURL_CSELECT_IN;
+
+ state(conn, SSH_STOP);
+ break;
+ }
+ case SSH_SCP_DONE:
+ if(data->set.upload)
+ state(conn, SSH_SCP_SEND_EOF);
+ else
+ state(conn, SSH_SCP_CHANNEL_FREE);
+ break;
+
+ case SSH_SCP_SEND_EOF:
+ if(sshc->scp_session) {
+ rc = ssh_scp_close(sshc->scp_session);
+ if(rc == SSH_AGAIN) {
+ /* Currently the ssh_scp_close handles waiting for EOF in
+ * blocking way.
+ */
+ break;
+ }
+ if(rc != SSH_OK) {
+ infof(data, "Failed to close libssh scp channel: %s\n",
+ ssh_get_error(sshc->ssh_session));
+ }
+ }
+
+ state(conn, SSH_SCP_CHANNEL_FREE);
+ break;
+
+ case SSH_SCP_CHANNEL_FREE:
+ if(sshc->scp_session) {
+ ssh_scp_free(sshc->scp_session);
+ sshc->scp_session = NULL;
+ }
+ DEBUGF(infof(data, "SCP DONE phase complete\n"));
+
+ ssh_set_blocking(sshc->ssh_session, 0);
+
+ state(conn, SSH_SESSION_DISCONNECT);
+ /* fall through */
+
+ case SSH_SESSION_DISCONNECT:
+ /* during weird times when we've been prematurely aborted, the channel
+ is still alive when we reach this state and we MUST kill the channel
+ properly first */
+ if(sshc->scp_session) {
+ ssh_scp_free(sshc->scp_session);
+ sshc->scp_session = NULL;
+ }
+
+ ssh_disconnect(sshc->ssh_session);
+
+ Curl_safefree(sshc->homedir);
+ conn->data->state.most_recent_ftp_entrypath = NULL;
+
+ state(conn, SSH_SESSION_FREE);
+ /* fall through */
+ case SSH_SESSION_FREE:
+ if(sshc->ssh_session) {
+ ssh_free(sshc->ssh_session);
+ sshc->ssh_session = NULL;
+ }
+
+ /* worst-case scenario cleanup */
+
+ DEBUGASSERT(sshc->ssh_session == NULL);
+ DEBUGASSERT(sshc->scp_session == NULL);
+
+ if(sshc->privkey)
+ ssh_key_free(sshc->privkey);
+ if(sshc->pubkey)
+ ssh_key_free(sshc->pubkey);
+
+ Curl_safefree(sshc->rsa_pub);
+ Curl_safefree(sshc->rsa);
+
+ Curl_safefree(sshc->quote_path1);
+ Curl_safefree(sshc->quote_path2);
+
+ Curl_safefree(sshc->homedir);
+
+ Curl_safefree(sshc->readdir_filename);
+ Curl_safefree(sshc->readdir_longentry);
+ Curl_safefree(sshc->readdir_line);
+ Curl_safefree(sshc->readdir_linkPath);
+
+ /* the code we are about to return */
+ result = sshc->actualcode;
+
+ memset(sshc, 0, sizeof(struct ssh_conn));
+
+ connclose(conn, "SSH session free");
+ sshc->state = SSH_SESSION_FREE; /* current */
+ sshc->nextstate = SSH_NO_STATE;
+ state(conn, SSH_STOP);
+ break;
+
+ case SSH_QUIT:
+ /* fallthrough, just stop! */
+ default:
+ /* internal error */
+ sshc->nextstate = SSH_NO_STATE;
+ state(conn, SSH_STOP);
+ break;
+
+ }
+ } while(!rc && (sshc->state != SSH_STOP));
+
+
+ if(rc == SSH_AGAIN) {
+ /* we would block, we need to wait for the socket to be ready (in the
+ right direction too)! */
+ *block = TRUE;
+ }
+
+ return result;
+}
+
+
+/* called by the multi interface to figure out what socket(s) to wait for and
+ for what actions in the DO_DONE, PERFORM and WAITPERFORM states */
+static int myssh_perform_getsock(const struct connectdata *conn,
+ curl_socket_t *sock, /* points to numsocks
+ number of sockets */
+ int numsocks)
+{
+ int bitmap = GETSOCK_BLANK;
+ (void) numsocks;
+
+ sock[0] = conn->sock[FIRSTSOCKET];
+
+ if(conn->waitfor & KEEP_RECV)
+ bitmap |= GETSOCK_READSOCK(FIRSTSOCKET);
+
+ if(conn->waitfor & KEEP_SEND)
+ bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET);
+
+ return bitmap;
+}
+
+/* Generic function called by the multi interface to figure out what socket(s)
+ to wait for and for what actions during the DOING and PROTOCONNECT states*/
+static int myssh_getsock(struct connectdata *conn,
+ curl_socket_t *sock, /* points to numsocks
+ number of sockets */
+ int numsocks)
+{
+ /* if we know the direction we can use the generic *_getsock() function even
+ for the protocol_connect and doing states */
+ return myssh_perform_getsock(conn, sock, numsocks);
+}
+
+static void myssh_block2waitfor(struct connectdata *conn, bool block)
+{
+ struct ssh_conn *sshc = &conn->proto.sshc;
+ int dir;
+
+ /* If it didn't block, or nothing was returned by ssh_get_poll_flags
+ * have the original set */
+ conn->waitfor = sshc->orig_waitfor;
+
+ if(block) {
+ dir = ssh_get_poll_flags(sshc->ssh_session);
+ if(dir & SSH_READ_PENDING) {
+ /* translate the libssh2 define bits into our own bit defines */
+ conn->waitfor = KEEP_RECV;
+ }
+ else if(dir & SSH_WRITE_PENDING) {
+ conn->waitfor = KEEP_SEND;
+ }
+ }
+}
+
+/* called repeatedly until done from multi.c */
+static CURLcode myssh_multi_statemach(struct connectdata *conn,
+ bool *done)
+{
+ struct ssh_conn *sshc = &conn->proto.sshc;
+ CURLcode result = CURLE_OK;
+ bool block; /* we store the status and use that to provide a ssh_getsock()
+ implementation */
+
+ result = myssh_statemach_act(conn, &block);
+ *done = (sshc->state == SSH_STOP) ? TRUE : FALSE;
+ myssh_block2waitfor(conn, block);
+
+ return result;
+}
+
+static CURLcode myssh_block_statemach(struct connectdata *conn,
+ bool disconnect)
+{
+ struct ssh_conn *sshc = &conn->proto.sshc;
+ CURLcode result = CURLE_OK;
+ struct Curl_easy *data = conn->data;
+
+ while((sshc->state != SSH_STOP) && !result) {
+ bool block;
+ timediff_t left = 1000;
+ struct curltime now = Curl_now();
+
+ result = myssh_statemach_act(conn, &block);
+ if(result)
+ break;
+
+ if(!disconnect) {
+ if(Curl_pgrsUpdate(conn))
+ return CURLE_ABORTED_BY_CALLBACK;
+
+ result = Curl_speedcheck(data, now);
+ if(result)
+ break;
+
+ left = Curl_timeleft(data, NULL, FALSE);
+ if(left < 0) {
+ failf(data, "Operation timed out");
+ return CURLE_OPERATION_TIMEDOUT;
+ }
+ }
+
+ if(!result && block) {
+ curl_socket_t sock = conn->sock[FIRSTSOCKET];
+ curl_socket_t fd_read = CURL_SOCKET_BAD;
+ fd_read = sock;
+ /* wait for the socket to become ready */
+ (void) Curl_socket_check(fd_read, CURL_SOCKET_BAD,
+ CURL_SOCKET_BAD, left > 1000 ? 1000 : left);
+ }
+
+ }
+
+ return result;
+}
+
+/*
+ * SSH setup connection
+ */
+static CURLcode myssh_setup_connection(struct connectdata *conn)
+{
+ struct SSHPROTO *ssh;
+
+ conn->data->req.protop = ssh = calloc(1, sizeof(struct SSHPROTO));
+ if(!ssh)
+ return CURLE_OUT_OF_MEMORY;
+
+ return CURLE_OK;
+}
+
+static Curl_recv scp_recv;
+static Curl_send scp_send;
+
+/*
+ * Curl_ssh_connect() gets called from Curl_protocol_connect() to allow us to
+ * do protocol-specific actions at connect-time.
+ */
+static CURLcode myssh_connect(struct connectdata *conn, bool *done)
+{
+ struct ssh_conn *ssh;
+ CURLcode result;
+ struct Curl_easy *data = conn->data;
+ int rc;
+
+ /* initialize per-handle data if not already */
+ if(!data->req.protop)
+ myssh_setup_connection(conn);
+
+ /* We default to persistent connections. We set this already in this connect
+ function to make the re-use checks properly be able to check this bit. */
+ connkeep(conn, "SSH default");
+
+ if(conn->handler->protocol & CURLPROTO_SCP) {
+ conn->recv[FIRSTSOCKET] = scp_recv;
+ conn->send[FIRSTSOCKET] = scp_send;
+ }
+ else
+ return CURLE_SSH;
+
+ ssh = &conn->proto.sshc;
+
+ ssh->ssh_session = ssh_new();
+ if(ssh->ssh_session == NULL) {
+ failf(data, "Failure initialising ssh session");
+ return CURLE_FAILED_INIT;
+ }
+
+ if(conn->user) {
+ infof(data, "User: %s\n", conn->user);
+ ssh_options_set(ssh->ssh_session, SSH_OPTIONS_USER, conn->user);
+ }
+
+ if(data->set.str[STRING_SSH_KNOWNHOSTS]) {
+ infof(data, "Known hosts: %s\n", data->set.str[STRING_SSH_KNOWNHOSTS]);
+ ssh_options_set(ssh->ssh_session, SSH_OPTIONS_KNOWNHOSTS,
+ data->set.str[STRING_SSH_KNOWNHOSTS]);
+ }
+
+ ssh_options_set(ssh->ssh_session, SSH_OPTIONS_HOST, conn->host.name);
+ if(conn->remote_port)
+ ssh_options_set(ssh->ssh_session, SSH_OPTIONS_PORT,
+ &conn->remote_port);
+
+ if(data->set.ssh_compression) {
+ ssh_options_set(ssh->ssh_session, SSH_OPTIONS_COMPRESSION,
+ "zlib,zlib@openssh.com,none");
+ }
+
+ ssh->privkey = NULL;
+ ssh->pubkey = NULL;
+
+ if(data->set.str[STRING_SSH_PUBLIC_KEY]) {
+ rc = ssh_pki_import_pubkey_file(data->set.str[STRING_SSH_PUBLIC_KEY],
+ &ssh->pubkey);
+ if(rc != SSH_OK) {
+ failf(data, "Could not load public key file");
+ /* ignore */
+ }
+ }
+
+ /* we do not verify here, we do it at the state machine,
+ * after connection */
+
+ state(conn, SSH_INIT);
+
+ result = myssh_multi_statemach(conn, done);
+
+ return result;
+}
+
+/* called from multi.c while DOing */
+static CURLcode scp_doing(struct connectdata *conn, bool *dophase_done)
+{
+ CURLcode result;
+
+ result = myssh_multi_statemach(conn, dophase_done);
+
+ if(*dophase_done) {
+ DEBUGF(infof(conn->data, "DO phase is complete\n"));
+ }
+ return result;
+}
+
+/*
+ ***********************************************************************
+ *
+ * scp_perform()
+ *
+ * This is the actual DO function for SCP. Get a file according to
+ * the options previously setup.
+ */
+
+static
+CURLcode scp_perform(struct connectdata *conn,
+ bool *connected, bool *dophase_done)
+{
+ CURLcode result = CURLE_OK;
+
+ DEBUGF(infof(conn->data, "DO phase starts\n"));
+
+ *dophase_done = FALSE; /* not done yet */
+
+ /* start the first command in the DO phase */
+ state(conn, SSH_SCP_TRANS_INIT);
+
+ result = myssh_multi_statemach(conn, dophase_done);
+
+ *connected = conn->bits.tcpconnect[FIRSTSOCKET];
+
+ if(*dophase_done) {
+ DEBUGF(infof(conn->data, "DO phase is complete\n"));
+ }
+
+ return result;
+}
+
+static CURLcode myssh_do_it(struct connectdata *conn, bool *done)
+{
+ CURLcode result;
+ bool connected = 0;
+ struct Curl_easy *data = conn->data;
+ struct ssh_conn *sshc = &conn->proto.sshc;
+
+ *done = FALSE; /* default to false */
+
+ data->req.size = -1; /* make sure this is unknown at this point */
+
+ sshc->actualcode = CURLE_OK; /* reset error code */
+ sshc->secondCreateDirs = 0; /* reset the create dir attempt state
+ variable */
+
+ Curl_pgrsSetUploadCounter(data, 0);
+ Curl_pgrsSetDownloadCounter(data, 0);
+ Curl_pgrsSetUploadSize(data, -1);
+ Curl_pgrsSetDownloadSize(data, -1);
+
+ if(conn->handler->protocol & CURLPROTO_SCP)
+ result = scp_perform(conn, &connected, done);
+ else
+ result = CURLE_SSH;
+
+ return result;
+}
+
+/* BLOCKING, but the function is using the state machine so the only reason
+ this is still blocking is that the multi interface code has no support for
+ disconnecting operations that takes a while */
+static CURLcode scp_disconnect(struct connectdata *conn,
+ bool dead_connection)
+{
+ CURLcode result = CURLE_OK;
+ struct ssh_conn *ssh = &conn->proto.sshc;
+ (void) dead_connection;
+
+ if(ssh->ssh_session) {
+ /* only if there's a session still around to use! */
+
+ state(conn, SSH_SESSION_DISCONNECT);
+
+ result = myssh_block_statemach(conn, TRUE);
+ }
+
+ return result;
+}
+
+/* generic done function for both SCP and SFTP called from their specific
+ done functions */
+static CURLcode myssh_done(struct connectdata *conn, CURLcode status)
+{
+ CURLcode result = CURLE_OK;
+ struct SSHPROTO *protop = conn->data->req.protop;
+
+ if(!status) {
+ /* run the state-machine
+
+ TODO: when the multi interface is used, this _really_ should be using
+ the ssh_multi_statemach function but we have no general support for
+ non-blocking DONE operations!
+ */
+ result = myssh_block_statemach(conn, FALSE);
+ }
+ else
+ result = status;
+
+ if(protop)
+ Curl_safefree(protop->path);
+ if(Curl_pgrsDone(conn))
+ return CURLE_ABORTED_BY_CALLBACK;
+
+ conn->data->req.keepon = 0; /* clear all bits */
+ return result;
+}
+
+
+static CURLcode scp_done(struct connectdata *conn, CURLcode status,
+ bool premature)
+{
+ (void) premature; /* not used */
+
+ if(!status)
+ state(conn, SSH_SCP_DONE);
+
+ return myssh_done(conn, status);
+
+}
+
+static ssize_t scp_send(struct connectdata *conn, int sockindex,
+ const void *mem, size_t len, CURLcode *err)
+{
+ int rc;
+ (void) sockindex; /* we only support SCP on the fixed known primary socket */
+ (void) err;
+
+ rc = ssh_scp_write(conn->proto.sshc.scp_session, mem, len);
+
+#if 0
+ /* The following code is misleading, mostly added as wishful thinking
+ * that libssh at some point will implement non-blocking ssh_scp_write/read.
+ * Currently rc can only be number of bytes read or SSH_ERROR. */
+ myssh_block2waitfor(conn, (rc == SSH_AGAIN) ? TRUE : FALSE);
+
+ if(rc == SSH_AGAIN) {
+ *err = CURLE_AGAIN;
+ return 0;
+ }
+ else
+#endif
+ if(rc != SSH_OK) {
+ *err = CURLE_SSH;
+ return -1;
+ }
+
+ return len;
+}
+
+static ssize_t scp_recv(struct connectdata *conn, int sockindex,
+ char *mem, size_t len, CURLcode *err)
+{
+ ssize_t nread;
+ (void) err;
+ (void) sockindex; /* we only support SCP on the fixed known primary socket */
+
+ /* libssh returns int */
+ nread = ssh_scp_read(conn->proto.sshc.scp_session, mem, len);
+
+#if 0
+ /* The following code is misleading, mostly added as wishful thinking
+ * that libssh at some point will implement non-blocking ssh_scp_write/read.
+ * Currently rc can only be SSH_OK or SSH_ERROR. */
+
+ myssh_block2waitfor(conn, (nread == SSH_AGAIN) ? TRUE : FALSE);
+ if(nread == SSH_AGAIN) {
+ *err = CURLE_AGAIN;
+ nread = -1;
+ }
+#endif
+
+ return nread;
+}
+
+#endif /* USE_LIBSSH */
diff --git a/lib/ssh.c b/lib/ssh.c
index 2496e7cff..54f08edf7 100644
--- a/lib/ssh.c
+++ b/lib/ssh.c
@@ -87,21 +87,9 @@
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
#include "curl_memory.h"
+#include "curl_path.h"
#include "memdebug.h"
-#ifdef WIN32
-# undef PATH_MAX
-# define PATH_MAX MAX_PATH
-# ifndef R_OK
-# define R_OK 4
-# endif
-#endif
-
-#ifndef PATH_MAX
-#define PATH_MAX 1024 /* just an extra precaution since there are systems that
- have their definition hidden well */
-#endif
-
#if LIBSSH2_VERSION_NUM >= 0x010206
/* libssh2_sftp_statvfs and friends were added in 1.2.6 */
#define HAS_STATVFS_SUPPORT 1
@@ -120,16 +108,10 @@ static LIBSSH2_ALLOC_FUNC(my_libssh2_malloc);
static LIBSSH2_REALLOC_FUNC(my_libssh2_realloc);
static LIBSSH2_FREE_FUNC(my_libssh2_free);
-static CURLcode get_pathname(const char **cpp, char **path);
-
static CURLcode ssh_connect(struct connectdata *conn, bool *done);
static CURLcode ssh_multi_statemach(struct connectdata *conn, bool *done);
static CURLcode ssh_do(struct connectdata *conn, bool *done);
-static CURLcode ssh_getworkingpath(struct connectdata *conn,
- char *homedir, /* when SFTP is used */
- char **path);
-
static CURLcode scp_done(struct connectdata *conn,
CURLcode, bool premature);
static CURLcode scp_doing(struct connectdata *conn,
@@ -410,70 +392,6 @@ static void state(struct connectdata *conn, sshstate nowstate)
sshc->state = nowstate;
}
-/* figure out the path to work with in this particular request */
-static CURLcode ssh_getworkingpath(struct connectdata *conn,
- char *homedir, /* when SFTP is used */
- char **path) /* returns the allocated
- real path to work with */
-{
- struct Curl_easy *data = conn->data;
- char *real_path = NULL;
- char *working_path;
- size_t working_path_len;
- CURLcode result =
- Curl_urldecode(data, data->state.path, 0, &working_path,
- &working_path_len, FALSE);
- if(result)
- return result;
-
- /* Check for /~/, indicating relative to the user's home directory */
- if(conn->handler->protocol & CURLPROTO_SCP) {
- real_path = malloc(working_path_len + 1);
- if(real_path == NULL) {
- free(working_path);
- return CURLE_OUT_OF_MEMORY;
- }
- if((working_path_len > 3) && (!memcmp(working_path, "/~/", 3)))
- /* It is referenced to the home directory, so strip the leading '/~/' */
- memcpy(real_path, working_path + 3, 4 + working_path_len-3);
- else
- memcpy(real_path, working_path, 1 + working_path_len);
- }
- else if(conn->handler->protocol & CURLPROTO_SFTP) {
- if((working_path_len > 1) && (working_path[1] == '~')) {
- size_t homelen = strlen(homedir);
- real_path = malloc(homelen + working_path_len + 1);
- if(real_path == NULL) {
- free(working_path);
- return CURLE_OUT_OF_MEMORY;
- }
- /* It is referenced to the home directory, so strip the
- leading '/' */
- memcpy(real_path, homedir, homelen);
- real_path[homelen] = '/';
- real_path[homelen + 1] = '\0';
- if(working_path_len > 3) {
- memcpy(real_path + homelen + 1, working_path + 3,
- 1 + working_path_len -3);
- }
- }
- else {
- real_path = malloc(working_path_len + 1);
- if(real_path == NULL) {
- free(working_path);
- return CURLE_OUT_OF_MEMORY;
- }
- memcpy(real_path, working_path, 1 + working_path_len);
- }
- }
-
- free(working_path);
-
- /* store the pointer for the caller to receive */
- *path = real_path;
-
- return CURLE_OK;
-}
#ifdef HAVE_LIBSSH2_KNOWNHOST_API
static int sshkeycallback(struct Curl_easy *easy,
@@ -1184,7 +1102,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn, bool *block)
case SSH_SFTP_QUOTE_INIT:
- result = ssh_getworkingpath(conn, sshc->homedir, &sftp_scp->path);
+ result = Curl_getworkingpath(conn, sshc->homedir, &sftp_scp->path);
if(result) {
sshc->actualcode = result;
state(conn, SSH_STOP);
@@ -1279,7 +1197,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn, bool *block)
* also, every command takes at least one argument so we get that
* first argument right now
*/
- result = get_pathname(&cp, &sshc->quote_path1);
+ result = Curl_get_pathname(&cp, &sshc->quote_path1);
if(result) {
if(result == CURLE_OUT_OF_MEMORY)
failf(data, "Out of memory");
@@ -1304,7 +1222,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn, bool *block)
/* sshc->quote_path1 contains the mode to set */
/* get the destination */
- result = get_pathname(&cp, &sshc->quote_path2);
+ result = Curl_get_pathname(&cp, &sshc->quote_path2);
if(result) {
if(result == CURLE_OUT_OF_MEMORY)
failf(data, "Out of memory");
@@ -1326,7 +1244,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn, bool *block)
/* symbolic linking */
/* sshc->quote_path1 is the source */
/* get the destination */
- result = get_pathname(&cp, &sshc->quote_path2);
+ result = Curl_get_pathname(&cp, &sshc->quote_path2);
if(result) {
if(result == CURLE_OUT_OF_MEMORY)
failf(data, "Out of memory");
@@ -1351,7 +1269,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn, bool *block)
/* rename file */
/* first param is the source path */
/* second param is the dest. path */
- result = get_pathname(&cp, &sshc->quote_path2);
+ result = Curl_get_pathname(&cp, &sshc->quote_path2);
if(result) {
if(result == CURLE_OUT_OF_MEMORY)
failf(data, "Out of memory");
@@ -2399,7 +2317,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn, bool *block)
break;
case SSH_SCP_TRANS_INIT:
- result = ssh_getworkingpath(conn, sshc->homedir, &sftp_scp->path);
+ result = Curl_getworkingpath(conn, sshc->homedir, &sftp_scp->path);
if(result) {
sshc->actualcode = result;
state(conn, SSH_STOP);
@@ -3307,93 +3225,6 @@ static ssize_t sftp_recv(struct connectdata *conn, int sockindex,
return nread;
}
-/* The get_pathname() function is being borrowed from OpenSSH sftp.c
- version 4.6p1. */
-/*
- * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-static CURLcode
-get_pathname(const char **cpp, char **path)
-{
- const char *cp = *cpp, *end;
- char quot;
- unsigned int i, j;
- static const char WHITESPACE[] = " \t\r\n";
-
- cp += strspn(cp, WHITESPACE);
- if(!*cp) {
- *cpp = cp;
- *path = NULL;
- return CURLE_QUOTE_ERROR;
- }
-
- *path = malloc(strlen(cp) + 1);
- if(*path == NULL)
- return CURLE_OUT_OF_MEMORY;
-
- /* Check for quoted filenames */
- if(*cp == '\"' || *cp == '\'') {
- quot = *cp++;
-
- /* Search for terminating quote, unescape some chars */
- for(i = j = 0; i <= strlen(cp); i++) {
- if(cp[i] == quot) { /* Found quote */
- i++;
- (*path)[j] = '\0';
- break;
- }
- if(cp[i] == '\0') { /* End of string */
- /*error("Unterminated quote");*/
- goto fail;
- }
- if(cp[i] == '\\') { /* Escaped characters */
- i++;
- if(cp[i] != '\'' && cp[i] != '\"' &&
- cp[i] != '\\') {
- /*error("Bad escaped character '\\%c'",
- cp[i]);*/
- goto fail;
- }
- }
- (*path)[j++] = cp[i];
- }
-
- if(j == 0) {
- /*error("Empty quotes");*/
- goto fail;
- }
- *cpp = cp + i + strspn(cp + i, WHITESPACE);
- }
- else {
- /* Read to end of filename */
- end = strpbrk(cp, WHITESPACE);
- if(end == NULL)
- end = strchr(cp, '\0');
- *cpp = end + strspn(end, WHITESPACE);
-
- memcpy(*path, cp, end - cp);
- (*path)[end - cp] = '\0';
- }
- return CURLE_OK;
-
- fail:
- Curl_safefree(*path);
- return CURLE_QUOTE_ERROR;
-}
-
-
static const char *sftp_libssh2_strerror(int err)
{
switch(err) {
diff --git a/lib/ssh.h b/lib/ssh.h
index b350dcf3a..4c204423d 100644
--- a/lib/ssh.h
+++ b/lib/ssh.h
@@ -24,9 +24,11 @@
#include "curl_setup.h"
-#ifdef HAVE_LIBSSH2_H
+#if defined(HAVE_LIBSSH2_H)
#include <libssh2.h>
#include <libssh2_sftp.h>
+#elif defined(HAVE_LIBSSH_LIBSSH_H)
+#include <libssh/libssh.h>
#endif /* HAVE_LIBSSH2_H */
/****************************************************************************
@@ -51,6 +53,7 @@ typedef enum {
SSH_AUTH_HOST,
SSH_AUTH_KEY_INIT,
SSH_AUTH_KEY,
+ SSH_AUTH_GSSAPI,
SSH_AUTH_DONE,
SSH_SFTP_INIT,
SSH_SFTP_REALPATH, /* Last state in SSH-CONNECT */
@@ -86,6 +89,7 @@ typedef enum {
SSH_SCP_TRANS_INIT, /* First state in SCP-DO */
SSH_SCP_UPLOAD_INIT,
SSH_SCP_DOWNLOAD_INIT,
+ SSH_SCP_DOWNLOAD,
SSH_SCP_DONE,
SSH_SCP_SEND_EOF,
SSH_SCP_WAIT_EOF,
@@ -109,7 +113,8 @@ struct SSHPROTO {
struct */
struct ssh_conn {
const char *authlist; /* List of auth. methods, managed by libssh2 */
-#ifdef USE_LIBSSH2
+
+ /* common */
const char *passphrase; /* pass-phrase to use */
char *rsa_pub; /* path name */
char *rsa; /* path name */
@@ -120,14 +125,11 @@ struct ssh_conn {
struct curl_slist *quote_item; /* for the quote option */
char *quote_path1; /* two generic pointers for the QUOTE stuff */
char *quote_path2;
- LIBSSH2_SFTP_ATTRIBUTES quote_attrs; /* used by the SFTP_QUOTE state */
+
bool acceptfail; /* used by the SFTP_QUOTE (continue if
quote command fails) */
char *homedir; /* when doing SFTP we figure out home dir in the
connect phase */
-
- /* Here's a set of struct members used by the SFTP_READDIR state */
- LIBSSH2_SFTP_ATTRIBUTES readdir_attrs;
char *readdir_filename;
char *readdir_longentry;
int readdir_len, readdir_totalLen, readdir_currLen;
@@ -139,11 +141,25 @@ struct ssh_conn {
second attempt has been made to change
to/create a directory */
char *slash_pos; /* used by the SFTP_CREATE_DIRS state */
+
+ int orig_waitfor; /* default READ/WRITE bits wait for */
+
+#if defined(USE_LIBSSH)
+/* our variables */
+ ssh_key privkey;
+ ssh_key pubkey;
+ int auth_methods;
+ ssh_session ssh_session;
+ ssh_scp scp_session;
+#elif defined(USE_LIBSSH2)
+ LIBSSH2_SFTP_ATTRIBUTES quote_attrs; /* used by the SFTP_QUOTE state */
+
+ /* Here's a set of struct members used by the SFTP_READDIR state */
+ LIBSSH2_SFTP_ATTRIBUTES readdir_attrs;
LIBSSH2_SESSION *ssh_session; /* Secure Shell session */
LIBSSH2_CHANNEL *ssh_channel; /* Secure Shell channel handle */
LIBSSH2_SFTP *sftp_session; /* SFTP handle */
LIBSSH2_SFTP_HANDLE *sftp_handle;
- int orig_waitfor; /* default READ/WRITE bits wait for */
#ifdef HAVE_LIBSSH2_AGENT_API
LIBSSH2_AGENT *ssh_agent; /* proxy to ssh-agent/pageant */
@@ -156,10 +172,16 @@ struct ssh_conn {
#ifdef HAVE_LIBSSH2_KNOWNHOST_API
LIBSSH2_KNOWNHOSTS *kh;
#endif
-#endif /* USE_LIBSSH2 */
+#endif /* USE_LIBSSH */
};
-#ifdef USE_LIBSSH2
+#if defined(USE_LIBSSH)
+
+#define CURL_LIBSSH_VERSION ssh_version(0)
+
+extern const struct Curl_handler Curl_handler_scp;
+
+#elif defined(USE_LIBSSH2)
/* Feature detection based on version numbers to better work with
non-configure platforms */
@@ -190,6 +212,14 @@ struct ssh_conn {
#define HAVE_LIBSSH2_SESSION_HANDSHAKE 1
#endif
+#ifdef HAVE_LIBSSH2_VERSION
+/* get it run-time if possible */
+#define CURL_LIBSSH2_VERSION libssh2_version(0)
+#else
+/* use build-time if run-time not possible */
+#define CURL_LIBSSH2_VERSION LIBSSH2_VERSION
+#endif
+
extern const struct Curl_handler Curl_handler_scp;
extern const struct Curl_handler Curl_handler_sftp;
diff --git a/lib/url.c b/lib/url.c
index 0cc77f9fa..731e46fea 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -196,8 +196,11 @@ static const struct Curl_handler * const protocols[] = {
&Curl_handler_tftp,
#endif
-#ifdef USE_LIBSSH2
+#if defined(USE_LIBSSH2) || defined(USE_LIBSSH)
&Curl_handler_scp,
+#endif
+
+#if defined(USE_LIBSSH2)
&Curl_handler_sftp,
#endif
diff --git a/lib/urldata.h b/lib/urldata.h
index 2052a0299..ed6bbb4f0 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -1410,7 +1410,7 @@ enum dupstring {
STRING_RTSP_SESSION_ID, /* Session ID to use */
STRING_RTSP_STREAM_URI, /* Stream URI for this request */
STRING_RTSP_TRANSPORT, /* Transport for this session */
-#ifdef USE_LIBSSH2
+#if defined(USE_LIBSSH2) || defined(USE_LIBSSH)
STRING_SSH_PRIVATE_KEY, /* path to the private key file for auth */
STRING_SSH_PUBLIC_KEY, /* path to the public key file for auth */
STRING_SSH_HOST_PUBLIC_KEY_MD5, /* md5 of host public key in ascii hex */
diff --git a/lib/version.c b/lib/version.c
index 79cc0bba4..f0861ea7a 100644
--- a/lib/version.c
+++ b/lib/version.c
@@ -26,6 +26,7 @@
#include "urldata.h"
#include "vtls/vtls.h"
#include "http2.h"
+#include "ssh.h"
#include "curl_printf.h"
#ifdef USE_ARES
@@ -176,6 +177,11 @@ char *curl_version(void)
left -= len;
ptr += len;
#endif
+#ifdef USE_LIBSSH
+ len = snprintf(ptr, left, " libssh/%s", CURL_LIBSSH_VERSION);
+ left -= len;
+ ptr += len;
+#endif
#ifdef USE_NGHTTP2
len = Curl_http2_ver(ptr, left);
left -= len;
@@ -264,7 +270,7 @@ static const char * const protocols[] = {
#ifndef CURL_DISABLE_RTSP
"rtsp",
#endif
-#ifdef USE_LIBSSH2
+#if defined(USE_LIBSSH) || defined(USE_LIBSSH2)
"scp",
#endif
#ifdef USE_LIBSSH2
@@ -379,7 +385,7 @@ static curl_version_info_data version_info = {
curl_version_info_data *curl_version_info(CURLversion stamp)
{
static bool initialized;
-#ifdef USE_LIBSSH2
+#if defined(USE_LIBSSH) || defined(USE_LIBSSH2)
static char ssh_buffer[80];
#endif
#ifdef USE_SSL
@@ -431,9 +437,12 @@ curl_version_info_data *curl_version_info(CURLversion stamp)
#endif /* _LIBICONV_VERSION */
#endif
-#ifdef USE_LIBSSH2
+#if defined(USE_LIBSSH2)
snprintf(ssh_buffer, sizeof(ssh_buffer), "libssh2/%s", LIBSSH2_VERSION);
version_info.libssh_version = ssh_buffer;
+#elif defined(USE_LIBSSH)
+ snprintf(ssh_buffer, sizeof(ssh_buffer), "libssh/%s", CURL_LIBSSH_VERSION);
+ version_info.libssh_version = ssh_buffer;
#endif
#ifdef HAVE_BROTLI