1
0
mirror of https://github.com/moparisthebest/curl synced 2024-12-21 15:48:49 -05:00
curl/lib/ssh-libssh.c
Nikos Mavrogiannopoulos c92d2e14cf
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>
2017-12-01 17:35:14 +01:00

1235 lines
34 KiB
C

/***************************************************************************
* _ _ ____ _
* 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 */