From ba6f20a2442ab1ebfe947cff19a552f92114a29a Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 8 Nov 2007 10:22:49 +0000 Subject: [PATCH] Bug report #1823487 (http://curl.haxx.se/bug/view.cgi?id=1823487) pointed out that SFTP requests didn't use persistent connections. Neither did SCP ones. I gave the SSH code a good beating and now both SCP and SFTP should use persistent connections fine. I also did a bunch for indent changes as well as a bug fix for the "keyboard interactive" auth. --- CHANGES | 7 + RELEASE-NOTES | 5 +- lib/ssh.c | 2770 +++++++++++++++++++++++++------------------------ lib/urldata.h | 36 +- 4 files changed, 1417 insertions(+), 1401 deletions(-) diff --git a/CHANGES b/CHANGES index 497bf4389..ebf6ebd1b 100644 --- a/CHANGES +++ b/CHANGES @@ -6,6 +6,13 @@ Changelog +Daniel S (8 Nov 2007) +- Bug report #1823487 (http://curl.haxx.se/bug/view.cgi?id=1823487) pointed + out that SFTP requests didn't use persistent connections. Neither did SCP + ones. I gave the SSH code a good beating and now both SCP and SFTP should + use persistent connections fine. I also did a bunch for indent changes as + well as a bug fix for the "keyboard interactive" auth. + Dan F (6 Nov 2007) - Improved telnet support by drastically reducing the number of write callbacks needed to pass a buffer to the user. Instead one per byte it diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 633c6f8cb..fe3b550e2 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -4,7 +4,7 @@ Curl and libcurl 7.17.2 Command line options: 121 curl_easy_setopt() options: 147 Public functions in libcurl: 55 - Public web site mirrors: 41 + Public web site mirrors: 42 Known libcurl bindings: 36 Contributors: 597 @@ -18,6 +18,7 @@ This release includes the following bugfixes: with NSS, and also when SCP, SFTP and libz are not available o free problem in the curl tool for users with empty home dir o curl.h version 7.17.1 problem when building C++ apps with MSVC + o SFTP and SCP use persistent connections This release includes the following known bugs: @@ -34,6 +35,6 @@ New curl mirrors: This release would not have looked like this without help, code, reports and advice from friends like these: - Dan Fandrich, Gisle Vanem, Toby Peterson + Dan Fandrich, Gisle Vanem, Toby Peterson, Yang Tse Thanks! (and sorry if I forgot to mention someone) diff --git a/lib/ssh.c b/lib/ssh.c index 96d9367b1..ab9ddccc6 100644 --- a/lib/ssh.c +++ b/lib/ssh.c @@ -134,21 +134,29 @@ static LIBSSH2_FREE_FUNC(libssh2_free); static int get_pathname(const char **cpp, char **path); -static CURLcode Curl_ssh_connect(struct connectdata *conn, bool *done); -static CURLcode Curl_ssh_multi_statemach(struct connectdata *conn, bool *done); +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 Curl_scp_do(struct connectdata *conn, bool *done); -static CURLcode Curl_scp_done(struct connectdata *conn, - CURLcode, bool premature); -static CURLcode Curl_scp_doing(struct connectdata *conn, - bool *dophase_done); +static CURLcode ssh_getworkingpath(struct connectdata *conn, + char *homedir, /* when SFTP is used */ + char **path); -static CURLcode Curl_sftp_do(struct connectdata *conn, bool *done); -static CURLcode Curl_sftp_done(struct connectdata *conn, - CURLcode, bool premature); -static CURLcode Curl_sftp_doing(struct connectdata *conn, - bool *dophase_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); +static CURLcode sftp_done(struct connectdata *conn, + CURLcode, bool premature); +static CURLcode sftp_doing(struct connectdata *conn, + bool *dophase_done); +static CURLcode sftp_disconnect(struct connectdata *conn); +static +CURLcode sftp_perform(struct connectdata *conn, + bool *connected, + bool *dophase_done); /* * SCP protocol handler. */ @@ -156,15 +164,15 @@ static CURLcode Curl_sftp_doing(struct connectdata *conn, const struct Curl_handler Curl_handler_scp = { "SCP", /* scheme */ ZERO_NULL, /* setup_connection */ - Curl_scp_do, /* do_it */ - Curl_scp_done, /* done */ + ssh_do, /* do_it */ + scp_done, /* done */ ZERO_NULL, /* do_more */ - Curl_ssh_connect, /* connect_it */ - Curl_ssh_multi_statemach, /* connecting */ - Curl_scp_doing, /* doing */ + ssh_connect, /* connect_it */ + ssh_multi_statemach, /* connecting */ + scp_doing, /* doing */ ZERO_NULL, /* proto_getsock */ ZERO_NULL, /* doing_getsock */ - ZERO_NULL, /* disconnect */ + scp_disconnect, /* disconnect */ PORT_SSH, /* defport */ PROT_SCP /* protocol */ }; @@ -177,15 +185,15 @@ const struct Curl_handler Curl_handler_scp = { const struct Curl_handler Curl_handler_sftp = { "SFTP", /* scheme */ ZERO_NULL, /* setup_connection */ - Curl_sftp_do, /* do_it */ - Curl_sftp_done, /* done */ + ssh_do, /* do_it */ + sftp_done, /* done */ ZERO_NULL, /* do_more */ - Curl_ssh_connect, /* connect_it */ - Curl_ssh_multi_statemach, /* connecting */ - Curl_sftp_doing, /* doing */ + ssh_connect, /* connect_it */ + ssh_multi_statemach, /* connecting */ + sftp_doing, /* doing */ ZERO_NULL, /* proto_getsock */ ZERO_NULL, /* doing_getsock */ - ZERO_NULL, /* disconnect */ + sftp_disconnect, /* disconnect */ PORT_SSH, /* defport */ PROT_SFTP /* protocol */ }; @@ -198,7 +206,7 @@ kbd_callback(const char *name, int name_len, const char *instruction, LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses, void **abstract) { - struct SSHPROTO *ssh = (struct SSHPROTO *)*abstract; + struct connectdata *conn = (struct connectdata *)*abstract; #ifdef CURL_LIBSSH2_DEBUG fprintf(stderr, "name=%s\n", name); @@ -213,8 +221,8 @@ kbd_callback(const char *name, int name_len, const char *instruction, (void)instruction_len; #endif /* CURL_LIBSSH2_DEBUG */ if(num_prompts == 1) { - responses[0].text = strdup(ssh->passwd); - responses[0].length = strlen(ssh->passwd); + responses[0].text = strdup(conn->passwd); + responses[0].length = strlen(conn->passwd); } (void)prompts; (void)abstract; @@ -305,7 +313,6 @@ static void state(struct connectdata *conn, sshstate nowstate) "SSH_AUTH_DONE", "SSH_SFTP_INIT", "SSH_SFTP_REALPATH", - "SSH_GET_WORKINGPATH", "SSH_SFTP_QUOTE_INIT", "SSH_SFTP_POSTQUOTE_INIT", "SSH_SFTP_QUOTE", @@ -339,8 +346,7 @@ static void state(struct connectdata *conn, sshstate nowstate) "SSH_SCP_WAIT_EOF", "SSH_SCP_WAIT_CLOSE", "SSH_SCP_CHANNEL_FREE", - "SSH_CHANNEL_CLOSE", - "SSH_SESSION_DISCONECT", + "SSH_SESSION_DISCONNECT", "SSH_SESSION_FREE", "QUIT" }; @@ -357,6 +363,71 @@ 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 SessionHandle *data = conn->data; + char *real_path; + char *working_path; + int working_path_len; + + working_path = curl_easy_unescape(data, data->reqdata.path, 0, + &working_path_len); + if(!working_path) + return CURLE_OUT_OF_MEMORY; + + /* Check for /~/ , indicating relative to the user's home directory */ + if(conn->protocol & PROT_SCP) { + real_path = (char *)malloc(working_path_len+1); + if(real_path == NULL) { + free(working_path); + return CURLE_OUT_OF_MEMORY; + } + if((working_path_len > 1) && (working_path[1] == '~')) + /* It is referenced to the home directory, so strip the leading '/' */ + memcpy(real_path, working_path+1, 1 + working_path_len-1); + else + memcpy(real_path, working_path, 1 + working_path_len); + } + else if(conn->protocol & PROT_SFTP) { + if((working_path_len > 1) && (working_path[1] == '~')) { + size_t homelen = strlen(homedir); + real_path = (char *)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 = (char *)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; +} + static CURLcode ssh_statemach_act(struct connectdata *conn) { CURLcode result = CURLE_OK; @@ -369,597 +440,512 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) #endif /* CURL_LIBSSH2_DEBUG */ const char *host_public_key_md5; int rc,i; - long err; + int err; switch(sshc->state) { - case SSH_S_STARTUP: - sshc->secondCreateDirs = 0; - sshc->nextState = SSH_NO_STATE; - sshc->actualCode = CURLE_OK; + case SSH_S_STARTUP: + sshc->secondCreateDirs = 0; + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_OK; - rc = libssh2_session_startup(sshc->ssh_session, sock); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc) { - failf(data, "Failure establishing ssh session"); - state(conn, SSH_SESSION_FREE); - sshc->actualCode = CURLE_FAILED_INIT; - break; - } + rc = libssh2_session_startup(sshc->ssh_session, sock); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc) { + failf(data, "Failure establishing ssh session"); + state(conn, SSH_SESSION_FREE); + sshc->actualcode = CURLE_FAILED_INIT; + break; + } - /* Set libssh2 to non-blocking, since cURL is all non-blocking */ - libssh2_session_set_blocking(sshc->ssh_session, 0); + /* Set libssh2 to non-blocking, since cURL is all non-blocking */ + libssh2_session_set_blocking(sshc->ssh_session, 0); #ifdef CURL_LIBSSH2_DEBUG - /* - * 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(sshc->ssh_session, - LIBSSH2_HOSTKEY_HASH_MD5); + /* + * 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(sshc->ssh_session, + LIBSSH2_HOSTKEY_HASH_MD5); - /* The fingerprint points to static storage (!), don't free() it. */ - infof(data, "Fingerprint: "); - for (rc = 0; rc < 16; rc++) { - infof(data, "%02X ", (unsigned char) fingerprint[rc]); - } - infof(data, "\n"); + /* The fingerprint points to static storage (!), don't free() it. */ + infof(data, "Fingerprint: "); + for (rc = 0; rc < 16; rc++) { + infof(data, "%02X ", (unsigned char) fingerprint[rc]); + } + infof(data, "\n"); #endif /* CURL_LIBSSH2_DEBUG */ - /* Before we authenticate we check the hostkey's MD5 fingerprint - * against a known fingerprint, if available. This implementation pulls - * it from the curl option. - */ - if(data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5] && - strlen(data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]) == 32) - { - char buf[33]; - host_public_key_md5 = libssh2_hostkey_hash(sshc->ssh_session, - LIBSSH2_HOSTKEY_HASH_MD5); - for (i = 0; i < 16; i++) - snprintf(&buf[i*2], 3, "%02x", - (unsigned char) host_public_key_md5[i]); - if(!strequal(buf, data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5])) { - failf(data, - "Denied establishing ssh session: mismatch md5 fingerprint. " - "Remote %s is not equal to %s", - buf, data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]); - state(conn, SSH_SESSION_FREE); - sshc->actualCode = CURLE_PEER_FAILED_VERIFICATION; - break; - } + /* Before we authenticate we check the hostkey's MD5 fingerprint + * against a known fingerprint, if available. This implementation pulls + * it from the curl option. + */ + if(data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5] && + strlen(data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]) == 32) { + char buf[33]; + host_public_key_md5 = libssh2_hostkey_hash(sshc->ssh_session, + LIBSSH2_HOSTKEY_HASH_MD5); + for (i = 0; i < 16; i++) + snprintf(&buf[i*2], 3, "%02x", + (unsigned char) host_public_key_md5[i]); + if(!strequal(buf, data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5])) { + failf(data, + "Denied establishing ssh session: mismatch md5 fingerprint. " + "Remote %s is not equal to %s", + buf, data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]); + state(conn, SSH_SESSION_FREE); + sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; + break; } + } - state(conn, SSH_AUTHLIST); - break; + state(conn, SSH_AUTHLIST); + break; - case SSH_AUTHLIST: - /* TBD - methods to check the host keys need to be done */ + case SSH_AUTHLIST: + /* 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. - */ - sshc->authlist = libssh2_userauth_list(sshc->ssh_session, - sftp_scp->user, - strlen(sftp_scp->user)); + /* + * 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. + */ + sshc->authlist = libssh2_userauth_list(sshc->ssh_session, + conn->user, + strlen(conn->user)); - if(!sshc->authlist) { - if((err = libssh2_session_last_errno(sshc->ssh_session)) == - LIBSSH2_ERROR_EAGAIN) { - break; - } - else { - state(conn, SSH_SESSION_FREE); - sshc->actualCode = libssh2_session_error_to_CURLE(err); - break; - } + if(!sshc->authlist) { + if((err = libssh2_session_last_errno(sshc->ssh_session)) == + LIBSSH2_ERROR_EAGAIN) { + break; } - infof(data, "SSH authentication methods available: %s\n", sshc->authlist); + else { + state(conn, SSH_SESSION_FREE); + sshc->actualcode = libssh2_session_error_to_CURLE(err); + break; + } + } + infof(data, "SSH authentication methods available: %s\n", sshc->authlist); - state(conn, SSH_AUTH_PKEY_INIT); - break; + state(conn, SSH_AUTH_PKEY_INIT); + break; - case SSH_AUTH_PKEY_INIT: - /* - * Check the supported auth types in the order I feel is most secure - * with the requested type of authentication - */ - sshc->authed = FALSE; + case SSH_AUTH_PKEY_INIT: + /* + * Check the supported auth types in the order I feel is most secure + * with the requested type of authentication + */ + sshc->authed = FALSE; - if((data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY) && - (strstr(sshc->authlist, "publickey") != NULL)) { - char *home; + if((data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY) && + (strstr(sshc->authlist, "publickey") != NULL)) { + char *home; - sshc->rsa_pub = sshc->rsa = NULL; + sshc->rsa_pub = sshc->rsa = NULL; - /* To ponder about: should really the lib be messing about with the - HOME environment variable etc? */ - home = curl_getenv("HOME"); + /* To ponder about: should really the lib be messing about with the + HOME environment variable etc? */ + home = curl_getenv("HOME"); - if(data->set.str[STRING_SSH_PUBLIC_KEY]) - sshc->rsa_pub = aprintf("%s", data->set.str[STRING_SSH_PUBLIC_KEY]); - else if(home) - sshc->rsa_pub = aprintf("%s/.ssh/id_dsa.pub", home); - else - /* as a final resort, try current dir! */ - sshc->rsa_pub = strdup("id_dsa.pub"); - - if(sshc->rsa_pub == NULL) { - Curl_safefree(home); - home = NULL; - state(conn, SSH_SESSION_FREE); - sshc->actualCode = CURLE_OUT_OF_MEMORY; - break; - } - - if(data->set.str[STRING_SSH_PRIVATE_KEY]) - sshc->rsa = aprintf("%s", data->set.str[STRING_SSH_PRIVATE_KEY]); - else if(home) - sshc->rsa = aprintf("%s/.ssh/id_dsa", home); - else - /* as a final resort, try current dir! */ - sshc->rsa = strdup("id_dsa"); - - if(sshc->rsa == NULL) { - Curl_safefree(home); - home = NULL; - Curl_safefree(sshc->rsa_pub); - sshc->rsa_pub = NULL; - state(conn, SSH_SESSION_FREE); - sshc->actualCode = CURLE_OUT_OF_MEMORY; - break; - } - - sshc->passphrase = data->set.str[STRING_KEY_PASSWD]; - if(!sshc->passphrase) - sshc->passphrase = ""; + if(data->set.str[STRING_SSH_PUBLIC_KEY]) + sshc->rsa_pub = aprintf("%s", data->set.str[STRING_SSH_PUBLIC_KEY]); + else if(home) + sshc->rsa_pub = aprintf("%s/.ssh/id_dsa.pub", home); + else + /* as a final resort, try current dir! */ + sshc->rsa_pub = strdup("id_dsa.pub"); + if(sshc->rsa_pub == NULL) { Curl_safefree(home); home = NULL; - - infof(data, "Using ssh public key file %s\n", sshc->rsa_pub); - infof(data, "Using ssh private key file %s\n", sshc->rsa); - - state(conn, SSH_AUTH_PKEY); - } else { - state(conn, SSH_AUTH_PASS_INIT); - } - break; - - case SSH_AUTH_PKEY: - /* The function below checks if the files exists, no need to stat() here. - */ - rc = libssh2_userauth_publickey_fromfile(sshc->ssh_session, - sftp_scp->user, sshc->rsa_pub, - sshc->rsa, sshc->passphrase); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - - Curl_safefree(sshc->rsa_pub); - sshc->rsa_pub = NULL; - Curl_safefree(sshc->rsa); - sshc->rsa = NULL; - - if(rc == 0) { - sshc->authed = TRUE; - infof(data, "Initialized SSH public key authentication\n"); - state(conn, SSH_AUTH_DONE); - } - else { - state(conn, SSH_AUTH_PASS_INIT); - } - break; - - case SSH_AUTH_PASS_INIT: - if((data->set.ssh_auth_types & CURLSSH_AUTH_PASSWORD) && - (strstr(sshc->authlist, "password") != NULL)) { - state(conn, SSH_AUTH_PASS); - } else { - state(conn, SSH_AUTH_HOST_INIT); - } - break; - - case SSH_AUTH_PASS: - rc = libssh2_userauth_password(sshc->ssh_session, sftp_scp->user, - sftp_scp->passwd); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc == 0) { - sshc->authed = TRUE; - infof(data, "Initialized password authentication\n"); - state(conn, SSH_AUTH_DONE); - } - else { - state(conn, SSH_AUTH_HOST_INIT); - } - break; - - case SSH_AUTH_HOST_INIT: - if((data->set.ssh_auth_types & CURLSSH_AUTH_HOST) && - (strstr(sshc->authlist, "hostbased") != NULL)) { - state(conn, SSH_AUTH_HOST); - } - else { - state(conn, SSH_AUTH_KEY_INIT); - } - break; - - case SSH_AUTH_HOST: - state(conn, SSH_AUTH_KEY_INIT); - break; - - case SSH_AUTH_KEY_INIT: - if((data->set.ssh_auth_types & CURLSSH_AUTH_KEYBOARD) - && (strstr(sshc->authlist, "keyboard-interactive") != NULL)) { - state(conn, SSH_AUTH_KEY); - } - else { - state(conn, SSH_AUTH_DONE); - } - break; - - case SSH_AUTH_KEY: - /* Authentication failed. Continue with keyboard-interactive now. */ - rc = libssh2_userauth_keyboard_interactive_ex(sshc->ssh_session, - sftp_scp->user, - strlen(sftp_scp->user), - &kbd_callback); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc == 0) { - sshc->authed = TRUE; - infof(data, "Initialized keyboard interactive authentication\n"); - } - state(conn, SSH_AUTH_DONE); - break; - - case SSH_AUTH_DONE: - if(!sshc->authed) { - failf(data, "Authentication failure"); state(conn, SSH_SESSION_FREE); - sshc->actualCode = CURLE_LOGIN_DENIED; + sshc->actualcode = CURLE_OUT_OF_MEMORY; + break; + } + + if(data->set.str[STRING_SSH_PRIVATE_KEY]) + sshc->rsa = aprintf("%s", data->set.str[STRING_SSH_PRIVATE_KEY]); + else if(home) + sshc->rsa = aprintf("%s/.ssh/id_dsa", home); + else + /* as a final resort, try current dir! */ + sshc->rsa = strdup("id_dsa"); + + if(sshc->rsa == NULL) { + Curl_safefree(home); + home = NULL; + Curl_safefree(sshc->rsa_pub); + sshc->rsa_pub = NULL; + state(conn, SSH_SESSION_FREE); + sshc->actualcode = CURLE_OUT_OF_MEMORY; + break; + } + + sshc->passphrase = data->set.str[STRING_KEY_PASSWD]; + if(!sshc->passphrase) + sshc->passphrase = ""; + + Curl_safefree(home); + home = NULL; + + infof(data, "Using ssh public key file %s\n", sshc->rsa_pub); + infof(data, "Using ssh private key file %s\n", sshc->rsa); + + state(conn, SSH_AUTH_PKEY); + } + else { + state(conn, SSH_AUTH_PASS_INIT); + } + break; + + case SSH_AUTH_PKEY: + /* The function below checks if the files exists, no need to stat() here. + */ + rc = libssh2_userauth_publickey_fromfile(sshc->ssh_session, + conn->user, sshc->rsa_pub, + sshc->rsa, sshc->passphrase); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + + Curl_safefree(sshc->rsa_pub); + sshc->rsa_pub = NULL; + Curl_safefree(sshc->rsa); + sshc->rsa = NULL; + + if(rc == 0) { + sshc->authed = TRUE; + infof(data, "Initialized SSH public key authentication\n"); + state(conn, SSH_AUTH_DONE); + } + else { + state(conn, SSH_AUTH_PASS_INIT); + } + break; + + case SSH_AUTH_PASS_INIT: + if((data->set.ssh_auth_types & CURLSSH_AUTH_PASSWORD) && + (strstr(sshc->authlist, "password") != NULL)) { + state(conn, SSH_AUTH_PASS); + } + else { + state(conn, SSH_AUTH_HOST_INIT); + } + break; + + case SSH_AUTH_PASS: + rc = libssh2_userauth_password(sshc->ssh_session, conn->user, + conn->passwd); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc == 0) { + sshc->authed = TRUE; + infof(data, "Initialized password authentication\n"); + state(conn, SSH_AUTH_DONE); + } + else { + state(conn, SSH_AUTH_HOST_INIT); + } + break; + + case SSH_AUTH_HOST_INIT: + if((data->set.ssh_auth_types & CURLSSH_AUTH_HOST) && + (strstr(sshc->authlist, "hostbased") != NULL)) { + state(conn, SSH_AUTH_HOST); + } + else { + state(conn, SSH_AUTH_KEY_INIT); + } + break; + + case SSH_AUTH_HOST: + state(conn, SSH_AUTH_KEY_INIT); + break; + + case SSH_AUTH_KEY_INIT: + if((data->set.ssh_auth_types & CURLSSH_AUTH_KEYBOARD) + && (strstr(sshc->authlist, "keyboard-interactive") != NULL)) { + state(conn, SSH_AUTH_KEY); + } + else { + state(conn, SSH_AUTH_DONE); + } + break; + + case SSH_AUTH_KEY: + /* Authentication failed. Continue with keyboard-interactive now. */ + rc = libssh2_userauth_keyboard_interactive_ex(sshc->ssh_session, + conn->user, + strlen(conn->user), + &kbd_callback); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc == 0) { + sshc->authed = TRUE; + infof(data, "Initialized keyboard interactive authentication\n"); + } + state(conn, SSH_AUTH_DONE); + break; + + case SSH_AUTH_DONE: + if(!sshc->authed) { + failf(data, "Authentication failure"); + state(conn, SSH_SESSION_FREE); + sshc->actualcode = CURLE_LOGIN_DENIED; + break; + } + + /* + * At this point we have an authenticated ssh session. + */ + infof(data, "Authentication complete\n"); + + conn->sockfd = sock; + conn->writesockfd = CURL_SOCKET_BAD; + + if(conn->protocol == PROT_SFTP) { + state(conn, SSH_SFTP_INIT); + break; + } + infof(data, "SSH CONNECT phase done\n"); + state(conn, SSH_STOP); + break; + + case SSH_SFTP_INIT: + /* + * Start the libssh2 sftp session + */ + sshc->sftp_session = libssh2_sftp_init(sshc->ssh_session); + if(!sshc->sftp_session) { + if(libssh2_session_last_errno(sshc->ssh_session) == + LIBSSH2_ERROR_EAGAIN) { + break; + } + else { + failf(data, "Failure initialising sftp session\n"); + state(conn, SSH_SESSION_FREE); + sshc->actualcode = CURLE_FAILED_INIT; + break; + } + } + state(conn, SSH_SFTP_REALPATH); + break; + + case SSH_SFTP_REALPATH: + { + char tempHome[PATH_MAX]; + + /* + * Get the "home" directory + */ + rc = libssh2_sftp_realpath(sshc->sftp_session, ".", + tempHome, PATH_MAX-1); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc > 0) { + /* It seems that this string is not always NULL terminated */ + tempHome[rc] = '\0'; + sshc->homedir = (char *)strdup(tempHome); + if(!sshc->homedir) { + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_OUT_OF_MEMORY; + break; + } + } + else { + /* Return the error type */ + err = libssh2_sftp_last_error(sshc->sftp_session); + result = sftp_libssh2_error_to_CURLE(err); + DEBUGF(infof(data, "error = %d makes libcurl = %d\n", err, result)); + state(conn, SSH_STOP); + break; + } + } + /* This is the last step in the SFTP connect phase. Do note that while + we get the homedir here, we get the "workingpath" in the DO action + since the homedir will remain the same between request but the + working path will not. */ + DEBUGF(infof(data, "SSH CONNECT phase done\n")); + state(conn, SSH_STOP); + break; + + case SSH_SFTP_QUOTE_INIT: + + result = ssh_getworkingpath(conn, sshc->homedir, &sftp_scp->path); + if(result) { + sshc->actualcode = result; + state(conn, SSH_STOP); + break; + } + + if(data->set.quote) { + infof(data, "Sending quote commands\n"); + sshc->quote_item = data->set.quote; + state(conn, SSH_SFTP_QUOTE); + } + else { + state(conn, SSH_SFTP_TRANS_INIT); + } + break; + + case SSH_SFTP_POSTQUOTE_INIT: + if(data->set.postquote) { + infof(data, "Sending quote commands\n"); + sshc->quote_item = data->set.postquote; + state(conn, SSH_SFTP_QUOTE); + } + else { + state(conn, SSH_STOP); + } + break; + + case SSH_SFTP_QUOTE: + /* Send any quote commands */ + { + const char *cp; + + /* + * Support some of the "FTP" commands + */ + if(curl_strnequal(sshc->quote_item->data, "PWD", 3)) { + /* output debug output if that is requested */ + if(data->set.verbose) { + char tmp[PATH_MAX+1]; + + Curl_debug(data, CURLINFO_HEADER_OUT, (char *)"PWD\n", 4, conn); + snprintf(tmp, PATH_MAX, "257 \"%s\" is current directory.\n", + sftp_scp->path); + Curl_debug(data, CURLINFO_HEADER_IN, tmp, strlen(tmp), conn); + } + state(conn, SSH_SFTP_NEXT_QUOTE); + break; + } + else if(sshc->quote_item->data) { + fprintf(stderr, "data: %s\n", sshc->quote_item->data); + /* + * the arguments following the command must be separated from the + * command with a space so we can check for it unconditionally + */ + cp = strchr(sshc->quote_item->data, ' '); + if(cp == NULL) { + failf(data, "Syntax error in SFTP command. Supply parameter(s)!"); + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_QUOTE_ERROR; break; } /* - * At this point we have an authenticated ssh session. + * also, every command takes at least one argument so we get that + * first argument right now */ - infof(data, "Authentication complete\n"); - - conn->sockfd = sock; - conn->writesockfd = CURL_SOCKET_BAD; - - if(conn->protocol == PROT_SFTP) { - state(conn, SSH_SFTP_INIT); + err = get_pathname(&cp, &sshc->quote_path1); + if(err) { + if(err == CURLE_OUT_OF_MEMORY) + failf(data, "Out of memory"); + else + failf(data, "Syntax error: Bad first parameter"); + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = err; break; } - state(conn, SSH_GET_WORKINGPATH); - break; - case SSH_SFTP_INIT: /* - * Start the libssh2 sftp session + * SFTP is a binary protocol, so we don't send text commands to + * the server. Instead, we scan for commands for commands used by + * OpenSSH's sftp program and call the appropriate libssh2 + * functions. */ - sshc->sftp_session = libssh2_sftp_init(sshc->ssh_session); - if(!sshc->sftp_session) { - if(libssh2_session_last_errno(sshc->ssh_session) == - LIBSSH2_ERROR_EAGAIN) { - break; - } - else { - failf(data, "Failure initialising sftp session\n"); - state(conn, SSH_SESSION_FREE); - sshc->actualCode = CURLE_FAILED_INIT; - break; - } - } - state(conn, SSH_SFTP_REALPATH); - break; + if(curl_strnequal(sshc->quote_item->data, "chgrp ", 6) || + curl_strnequal(sshc->quote_item->data, "chmod ", 6) || + curl_strnequal(sshc->quote_item->data, "chown ", 6) ) { + /* attribute change */ - case SSH_SFTP_REALPATH: - { - char tempHome[PATH_MAX]; - - /* - * Get the "home" directory - */ - rc = libssh2_sftp_realpath(sshc->sftp_session, ".", - tempHome, PATH_MAX-1); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc > 0) { - /* It seems that this string is not always NULL terminated */ - tempHome[rc] = '\0'; - sftp_scp->homedir = (char *)strdup(tempHome); - if(!sftp_scp->homedir) { - state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = CURLE_OUT_OF_MEMORY; - break; - } - } - else { - /* Return the error type */ - result = libssh2_sftp_last_error(sshc->sftp_session); - DEBUGF(infof(data, "error = %d\n", result)); - state(conn, SSH_STOP); - break; - } - state(conn, SSH_GET_WORKINGPATH); - } - break; - - case SSH_GET_WORKINGPATH: - { - char *real_path; - char *working_path; - int working_path_len; - - working_path = curl_easy_unescape(data, data->reqdata.path, 0, - &working_path_len); - if(!working_path) { - result = CURLE_OUT_OF_MEMORY; - state(conn, SSH_STOP); - break; - } - - /* Check for /~/ , indicating relative to the user's home directory */ - if(conn->protocol == PROT_SCP) { - real_path = (char *)malloc(working_path_len+1); - if(real_path == NULL) { - Curl_safefree(working_path); - working_path = NULL; - state(conn, SSH_SESSION_FREE); - sshc->actualCode = CURLE_OUT_OF_MEMORY; - break; - } - if(working_path[1] == '~') - /* It is referenced to the home directory, so strip the - leading '/' */ - memcpy(real_path, working_path+1, 1 + working_path_len-1); + /* sshc->quote_path1 contains the mode to set */ + /* get the destination */ + err = get_pathname(&cp, &sshc->quote_path2); + if(err) { + if(err == CURLE_OUT_OF_MEMORY) + failf(data, "Out of memory"); else - memcpy(real_path, working_path, 1 + working_path_len); - } - else if(conn->protocol == PROT_SFTP) { - if(working_path[1] == '~') { - real_path = (char *)malloc(strlen(sftp_scp->homedir) + - working_path_len + 1); - if(real_path == NULL) { - Curl_safefree(sftp_scp->homedir); - sftp_scp->homedir = NULL; - Curl_safefree(working_path); - working_path = NULL; - state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = CURLE_OUT_OF_MEMORY; - break; - } - /* It is referenced to the home directory, so strip the - leading '/' */ - memcpy(real_path, sftp_scp->homedir, strlen(sftp_scp->homedir)); - real_path[strlen(sftp_scp->homedir)] = '/'; - real_path[strlen(sftp_scp->homedir)+1] = '\0'; - if(working_path_len > 3) { - memcpy(real_path+strlen(sftp_scp->homedir)+1, working_path + 3, - 1 + working_path_len -3); - } - } - else { - real_path = (char *)malloc(working_path_len+1); - if(real_path == NULL) { - Curl_safefree(sftp_scp->homedir); - sftp_scp->homedir = NULL; - Curl_safefree(working_path); - working_path = NULL; - state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = CURLE_OUT_OF_MEMORY; - break; - } - memcpy(real_path, working_path, 1+working_path_len); - } - } - else { - Curl_safefree(working_path); - working_path = NULL; - state(conn, SSH_SESSION_FREE); - sshc->actualCode = CURLE_FAILED_INIT; + failf(data, "Syntax error in chgrp/chmod/chown: " + "Bad second parameter"); + Curl_safefree(sshc->quote_path1); + sshc->quote_path1 = NULL; + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = err; break; } - - Curl_safefree(working_path); - working_path = NULL; - sftp_scp->path = real_path; - - /* Connect is all done */ - state(conn, SSH_STOP); + memset(&sshc->quote_attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); + state(conn, SSH_SFTP_QUOTE_STAT); + break; } - break; - - case SSH_SFTP_QUOTE_INIT: - if(data->set.quote) { - infof(data, "Sending quote commands\n"); - sshc->quote_item = data->set.quote; - state(conn, SSH_SFTP_QUOTE); - } - else { - state(conn, SSH_SFTP_TRANS_INIT); - } - break; - - case SSH_SFTP_POSTQUOTE_INIT: - if(data->set.postquote) { - infof(data, "Sending quote commands\n"); - sshc->quote_item = data->set.postquote; - state(conn, SSH_SFTP_QUOTE); - } - else { - state(conn, SSH_STOP); - } - break; - - case SSH_SFTP_QUOTE: - /* Send any quote commands */ - { - const char *cp; - - /* - * Support some of the "FTP" commands - */ - if(curl_strnequal(sshc->quote_item->data, "PWD", 3)) { - /* output debug output if that is requested */ - if(data->set.verbose) { - char tmp[PATH_MAX+1]; - - Curl_debug(data, CURLINFO_HEADER_OUT, (char *)"PWD\n", 4, conn); - snprintf(tmp, PATH_MAX, "257 \"%s\" is current directory.\n", - sftp_scp->path); - Curl_debug(data, CURLINFO_HEADER_IN, tmp, strlen(tmp), conn); - } - state(conn, SSH_SFTP_NEXT_QUOTE); + else if(curl_strnequal(sshc->quote_item->data, "ln ", 3) || + curl_strnequal(sshc->quote_item->data, "symlink ", 8)) { + /* symbolic linking */ + /* sshc->quote_path1 is the source */ + /* get the destination */ + err = get_pathname(&cp, &sshc->quote_path2); + if(err) { + if(err == CURLE_OUT_OF_MEMORY) + failf(data, "Out of memory"); + else + failf(data, + "Syntax error in ln/symlink: Bad second parameter"); + Curl_safefree(sshc->quote_path1); + sshc->quote_path1 = NULL; + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = err; break; } - else if(sshc->quote_item->data) { - fprintf(stderr, "data: %s\n", sshc->quote_item->data); - /* - * the arguments following the command must be separated from the - * command with a space so we can check for it unconditionally - */ - cp = strchr(sshc->quote_item->data, ' '); - if(cp == NULL) { - failf(data, "Syntax error in SFTP command. Supply parameter(s)!"); - state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = CURLE_QUOTE_ERROR; - break; - } - - /* - * also, every command takes at least one argument so we get that - * first argument right now - */ - err = get_pathname(&cp, &sshc->quote_path1); - if(err) { - if(err == CURLE_OUT_OF_MEMORY) - failf(data, "Out of memory"); - else - failf(data, "Syntax error: Bad first parameter"); - state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = err; - break; - } - - /* - * SFTP is a binary protocol, so we don't send text commands to - * the server. Instead, we scan for commands for commands used by - * OpenSSH's sftp program and call the appropriate libssh2 - * functions. - */ - if(curl_strnequal(sshc->quote_item->data, "chgrp ", 6) || - curl_strnequal(sshc->quote_item->data, "chmod ", 6) || - curl_strnequal(sshc->quote_item->data, "chown ", 6) ) { - /* attribute change */ - - /* sshc->quote_path1 contains the mode to set */ - /* get the destination */ - err = get_pathname(&cp, &sshc->quote_path2); - if(err) { - if(err == CURLE_OUT_OF_MEMORY) - failf(data, "Out of memory"); - else - failf(data, "Syntax error in chgrp/chmod/chown: " - "Bad second parameter"); - Curl_safefree(sshc->quote_path1); - sshc->quote_path1 = NULL; - state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = err; - break; - } - memset(&sshc->quote_attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); - state(conn, SSH_SFTP_QUOTE_STAT); - break; - } - else if(curl_strnequal(sshc->quote_item->data, "ln ", 3) || - curl_strnequal(sshc->quote_item->data, "symlink ", 8)) { - /* symbolic linking */ - /* sshc->quote_path1 is the source */ - /* get the destination */ - err = get_pathname(&cp, &sshc->quote_path2); - if(err) { - if(err == CURLE_OUT_OF_MEMORY) - failf(data, "Out of memory"); - else - failf(data, - "Syntax error in ln/symlink: Bad second parameter"); - Curl_safefree(sshc->quote_path1); - sshc->quote_path1 = NULL; - state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = err; - break; - } - state(conn, SSH_SFTP_QUOTE_SYMLINK); - break; - } - else if(curl_strnequal(sshc->quote_item->data, "mkdir ", 6)) { - /* create dir */ - state(conn, SSH_SFTP_QUOTE_MKDIR); - break; - } - else if(curl_strnequal(sshc->quote_item->data, "rename ", 7)) { - /* rename file */ - /* first param is the source path */ - /* second param is the dest. path */ - err = get_pathname(&cp, &sshc->quote_path2); - if(err) { - if(err == CURLE_OUT_OF_MEMORY) - failf(data, "Out of memory"); - else - failf(data, "Syntax error in rename: Bad second parameter"); - Curl_safefree(sshc->quote_path1); - sshc->quote_path1 = NULL; - state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = err; - break; - } - state(conn, SSH_SFTP_QUOTE_RENAME); - break; - } - else if(curl_strnequal(sshc->quote_item->data, "rmdir ", 6)) { - /* delete dir */ - state(conn, SSH_SFTP_QUOTE_RMDIR); - break; - } - else if(curl_strnequal(sshc->quote_item->data, "rm ", 3)) { - state(conn, SSH_SFTP_QUOTE_UNLINK); - break; - } - - if(sshc->quote_path1) { - Curl_safefree(sshc->quote_path1); - sshc->quote_path1 = NULL; - } - if(sshc->quote_path2) { - Curl_safefree(sshc->quote_path2); - sshc->quote_path2 = NULL; - } + state(conn, SSH_SFTP_QUOTE_SYMLINK); + break; + } + else if(curl_strnequal(sshc->quote_item->data, "mkdir ", 6)) { + /* create dir */ + state(conn, SSH_SFTP_QUOTE_MKDIR); + break; + } + else if(curl_strnequal(sshc->quote_item->data, "rename ", 7)) { + /* rename file */ + /* first param is the source path */ + /* second param is the dest. path */ + err = get_pathname(&cp, &sshc->quote_path2); + if(err) { + if(err == CURLE_OUT_OF_MEMORY) + failf(data, "Out of memory"); + else + failf(data, "Syntax error in rename: Bad second parameter"); + Curl_safefree(sshc->quote_path1); + sshc->quote_path1 = NULL; + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = err; + break; } + state(conn, SSH_SFTP_QUOTE_RENAME); + break; } - if(!sshc->quote_item) { - state(conn, SSH_SFTP_TRANS_INIT); + else if(curl_strnequal(sshc->quote_item->data, "rmdir ", 6)) { + /* delete dir */ + state(conn, SSH_SFTP_QUOTE_RMDIR); + break; + } + else if(curl_strnequal(sshc->quote_item->data, "rm ", 3)) { + state(conn, SSH_SFTP_QUOTE_UNLINK); + break; } - break; - case SSH_SFTP_NEXT_QUOTE: if(sshc->quote_path1) { Curl_safefree(sshc->quote_path1); sshc->quote_path1 = NULL; @@ -968,824 +954,855 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) Curl_safefree(sshc->quote_path2); sshc->quote_path2 = NULL; } + } + } + if(!sshc->quote_item) { + state(conn, SSH_SFTP_TRANS_INIT); + } + break; - sshc->quote_item = sshc->quote_item->next; + case SSH_SFTP_NEXT_QUOTE: + if(sshc->quote_path1) { + Curl_safefree(sshc->quote_path1); + sshc->quote_path1 = NULL; + } + if(sshc->quote_path2) { + Curl_safefree(sshc->quote_path2); + sshc->quote_path2 = NULL; + } - if(sshc->quote_item) { - state(conn, SSH_SFTP_QUOTE); - } else { - if(sshc->nextState != SSH_NO_STATE) { - state(conn, sshc->nextState); - sshc->nextState = SSH_NO_STATE; - } else { - state(conn, SSH_SFTP_TRANS_INIT); - } - } - break; + sshc->quote_item = sshc->quote_item->next; - case SSH_SFTP_QUOTE_STAT: - rc = libssh2_sftp_stat(sshc->sftp_session, sshc->quote_path2, - &sshc->quote_attrs); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; + if(sshc->quote_item) { + state(conn, SSH_SFTP_QUOTE); + } + else { + if(sshc->nextstate != SSH_NO_STATE) { + state(conn, sshc->nextstate); + sshc->nextstate = SSH_NO_STATE; } - else if(rc != 0) { /* get those attributes */ - err = libssh2_sftp_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - sshc->quote_path1 = NULL; - Curl_safefree(sshc->quote_path2); - sshc->quote_path2 = NULL; - failf(data, "Attempt to get SFTP stats failed: %s", - sftp_libssh2_strerror(err)); - state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = CURLE_QUOTE_ERROR; - break; - } - - /* Now set the new attributes... */ - if(curl_strnequal(sshc->quote_item->data, "chgrp", 5)) { - sshc->quote_attrs.gid = strtol(sshc->quote_path1, NULL, 10); - if(sshc->quote_attrs.gid == 0 && !ISDIGIT(sshc->quote_path1[0])) { - Curl_safefree(sshc->quote_path1); - sshc->quote_path1 = NULL; - Curl_safefree(sshc->quote_path2); - sshc->quote_path2 = NULL; - failf(data, "Syntax error: chgrp gid not a number"); - state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = CURLE_QUOTE_ERROR; - break; - } - } - else if(curl_strnequal(sshc->quote_item->data, "chmod", 5)) { - sshc->quote_attrs.permissions = strtol(sshc->quote_path1, NULL, 8); - /* permissions are octal */ - if(sshc->quote_attrs.permissions == 0 && - !ISDIGIT(sshc->quote_path1[0])) { - Curl_safefree(sshc->quote_path1); - sshc->quote_path1 = NULL; - Curl_safefree(sshc->quote_path2); - sshc->quote_path2 = NULL; - failf(data, "Syntax error: chmod permissions not a number"); - state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = CURLE_QUOTE_ERROR; - break; - } - } - else if(curl_strnequal(sshc->quote_item->data, "chown", 5)) { - sshc->quote_attrs.uid = strtol(sshc->quote_path1, NULL, 10); - if(sshc->quote_attrs.uid == 0 && !ISDIGIT(sshc->quote_path1[0])) { - Curl_safefree(sshc->quote_path1); - sshc->quote_path1 = NULL; - Curl_safefree(sshc->quote_path2); - sshc->quote_path2 = NULL; - failf(data, "Syntax error: chown uid not a number"); - state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = CURLE_QUOTE_ERROR; - break; - } - } - - /* Now send the completed structure... */ - state(conn, SSH_SFTP_QUOTE_SETSTAT); - break; - - case SSH_SFTP_QUOTE_SETSTAT: - rc = libssh2_sftp_setstat(sshc->sftp_session, sshc->quote_path2, - &sshc->quote_attrs); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } else if(rc != 0) { - err = libssh2_sftp_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - sshc->quote_path1 = NULL; - Curl_safefree(sshc->quote_path2); - sshc->quote_path2 = NULL; - failf(data, "Attempt to set SFTP stats failed: %s", - sftp_libssh2_strerror(err)); - state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = CURLE_QUOTE_ERROR; - break; - } - state(conn, SSH_SFTP_NEXT_QUOTE); - break; - - case SSH_SFTP_QUOTE_SYMLINK: - rc = libssh2_sftp_symlink(sshc->sftp_session, sshc->quote_path1, - sshc->quote_path2); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc != 0) { - err = libssh2_sftp_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - sshc->quote_path1 = NULL; - Curl_safefree(sshc->quote_path2); - sshc->quote_path2 = NULL; - failf(data, "symlink command failed: %s", - sftp_libssh2_strerror(err)); - state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = CURLE_QUOTE_ERROR; - break; - } - state(conn, SSH_SFTP_NEXT_QUOTE); - break; - - case SSH_SFTP_QUOTE_MKDIR: - rc = libssh2_sftp_mkdir(sshc->sftp_session, sshc->quote_path1, 0755); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc != 0) { - err = libssh2_sftp_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - sshc->quote_path1 = NULL; - failf(data, "mkdir command failed: %s", sftp_libssh2_strerror(err)); - state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = CURLE_QUOTE_ERROR; - break; - } - state(conn, SSH_SFTP_NEXT_QUOTE); - break; - - case SSH_SFTP_QUOTE_RENAME: - rc = libssh2_sftp_rename(sshc->sftp_session, sshc->quote_path1, - sshc->quote_path2); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc != 0) { - err = libssh2_sftp_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - sshc->quote_path1 = NULL; - Curl_safefree(sshc->quote_path2); - sshc->quote_path2 = NULL; - failf(data, "rename command failed: %s", sftp_libssh2_strerror(err)); - state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = CURLE_QUOTE_ERROR; - break; - } - state(conn, SSH_SFTP_NEXT_QUOTE); - break; - - case SSH_SFTP_QUOTE_RMDIR: - rc = libssh2_sftp_rmdir(sshc->sftp_session, sshc->quote_path1); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc != 0) { - err = libssh2_sftp_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - sshc->quote_path1 = NULL; - failf(data, "rmdir command failed: %s", sftp_libssh2_strerror(err)); - state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = CURLE_QUOTE_ERROR; - break; - } - state(conn, SSH_SFTP_NEXT_QUOTE); - break; - - case SSH_SFTP_QUOTE_UNLINK: - rc = libssh2_sftp_unlink(sshc->sftp_session, sshc->quote_path1); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc != 0) { - err = libssh2_sftp_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - sshc->quote_path1 = NULL; - failf(data, "rm command failed: %s", sftp_libssh2_strerror(err)); - state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = CURLE_QUOTE_ERROR; - break; - } - state(conn, SSH_SFTP_NEXT_QUOTE); - break; - - case SSH_SFTP_TRANS_INIT: - if(data->set.upload) - state(conn, SSH_SFTP_UPLOAD_INIT); else { - if(sftp_scp->path[strlen(sftp_scp->path)-1] == '/') - state(conn, SSH_SFTP_READDIR_INIT); - else - state(conn, SSH_SFTP_DOWNLOAD_INIT); + state(conn, SSH_SFTP_TRANS_INIT); } + } + break; + + case SSH_SFTP_QUOTE_STAT: + rc = libssh2_sftp_stat(sshc->sftp_session, sshc->quote_path2, + &sshc->quote_attrs); + if(rc == LIBSSH2_ERROR_EAGAIN) { break; + } + else if(rc != 0) { /* get those attributes */ + err = libssh2_sftp_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + sshc->quote_path1 = NULL; + Curl_safefree(sshc->quote_path2); + sshc->quote_path2 = NULL; + failf(data, "Attempt to get SFTP stats failed: %s", + sftp_libssh2_strerror(err)); + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } - case SSH_SFTP_UPLOAD_INIT: - /* - * 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. - */ - sshc->sftp_handle = - libssh2_sftp_open(sshc->sftp_session, sftp_scp->path, - LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC, - data->set.new_file_perms); - if(!sshc->sftp_handle) { - if(libssh2_session_last_errno(sshc->ssh_session) == - LIBSSH2_ERROR_EAGAIN) { - break; - } - else { - err = libssh2_sftp_last_error(sshc->sftp_session); - failf(data, "Upload failed: %s", sftp_libssh2_strerror(err)); - if(sshc->secondCreateDirs) { - state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = err; - break; - } - else if(((err == LIBSSH2_FX_NO_SUCH_FILE) || - (err == LIBSSH2_FX_FAILURE) || - (err == LIBSSH2_FX_NO_SUCH_PATH)) && - (data->set.ftp_create_missing_dirs && - (strlen(sftp_scp->path) > 1))) { - /* try to create the path remotely */ - sshc->secondCreateDirs = 1; - state(conn, SSH_SFTP_CREATE_DIRS_INIT); - break; - } - state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = sftp_libssh2_error_to_CURLE(err); - break; - } - } - - /* upload data */ - result = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, - FIRSTSOCKET, NULL); - - if(result) { + /* Now set the new attributes... */ + if(curl_strnequal(sshc->quote_item->data, "chgrp", 5)) { + sshc->quote_attrs.gid = strtol(sshc->quote_path1, NULL, 10); + if(sshc->quote_attrs.gid == 0 && !ISDIGIT(sshc->quote_path1[0])) { + Curl_safefree(sshc->quote_path1); + sshc->quote_path1 = NULL; + Curl_safefree(sshc->quote_path2); + sshc->quote_path2 = NULL; + failf(data, "Syntax error: chgrp gid not a number"); state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = result; - } else { - state(conn, SSH_STOP); - } - break; - - case SSH_SFTP_CREATE_DIRS_INIT: - if(strlen(sftp_scp->path) > 1) { - sshc->slash_pos = sftp_scp->path + 1; /* ignore the leading '/' */ - state(conn, SSH_SFTP_CREATE_DIRS); - } else { - state(conn, SSH_SFTP_UPLOAD_INIT); - } - break; - - case SSH_SFTP_CREATE_DIRS: - if((sshc->slash_pos = strchr(sshc->slash_pos, '/')) != NULL) { - *sshc->slash_pos = 0; - - infof(data, "Creating directory '%s'\n", sftp_scp->path); - state(conn, SSH_SFTP_CREATE_DIRS_MKDIR); - break; - } else { - state(conn, SSH_SFTP_UPLOAD_INIT); - } - break; - - case SSH_SFTP_CREATE_DIRS_MKDIR: - /* 'mode' - parameter is preliminary - default to 0644 */ - rc = libssh2_sftp_mkdir(sshc->sftp_session, sftp_scp->path, - data->set.new_directory_perms); - if(rc == LIBSSH2_ERROR_EAGAIN) { + sshc->actualcode = CURLE_QUOTE_ERROR; break; } - *sshc->slash_pos = '/'; - ++sshc->slash_pos; - if(rc == -1) { - unsigned int sftp_err = 0; - /* - * abort if failure wasn't that the dir already exists or the - * permission was denied (creation might succeed further - * down the path) - retry on unspecific FAILURE also - */ - sftp_err = libssh2_sftp_last_error(sshc->sftp_session); - if((sftp_err != LIBSSH2_FX_FILE_ALREADY_EXISTS) && - (sftp_err != LIBSSH2_FX_FAILURE) && - (sftp_err != LIBSSH2_FX_PERMISSION_DENIED)) { - result = sftp_libssh2_error_to_CURLE(sftp_err); + } + else if(curl_strnequal(sshc->quote_item->data, "chmod", 5)) { + sshc->quote_attrs.permissions = strtol(sshc->quote_path1, NULL, 8); + /* permissions are octal */ + if(sshc->quote_attrs.permissions == 0 && + !ISDIGIT(sshc->quote_path1[0])) { + Curl_safefree(sshc->quote_path1); + sshc->quote_path1 = NULL; + Curl_safefree(sshc->quote_path2); + sshc->quote_path2 = NULL; + failf(data, "Syntax error: chmod permissions not a number"); + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + } + else if(curl_strnequal(sshc->quote_item->data, "chown", 5)) { + sshc->quote_attrs.uid = strtol(sshc->quote_path1, NULL, 10); + if(sshc->quote_attrs.uid == 0 && !ISDIGIT(sshc->quote_path1[0])) { + Curl_safefree(sshc->quote_path1); + sshc->quote_path1 = NULL; + Curl_safefree(sshc->quote_path2); + sshc->quote_path2 = NULL; + failf(data, "Syntax error: chown uid not a number"); + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + } + + /* Now send the completed structure... */ + state(conn, SSH_SFTP_QUOTE_SETSTAT); + break; + + case SSH_SFTP_QUOTE_SETSTAT: + rc = libssh2_sftp_setstat(sshc->sftp_session, sshc->quote_path2, + &sshc->quote_attrs); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc != 0) { + err = libssh2_sftp_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + sshc->quote_path1 = NULL; + Curl_safefree(sshc->quote_path2); + sshc->quote_path2 = NULL; + failf(data, "Attempt to set SFTP stats failed: %s", + sftp_libssh2_strerror(err)); + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + state(conn, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_QUOTE_SYMLINK: + rc = libssh2_sftp_symlink(sshc->sftp_session, sshc->quote_path1, + sshc->quote_path2); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc != 0) { + err = libssh2_sftp_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + sshc->quote_path1 = NULL; + Curl_safefree(sshc->quote_path2); + sshc->quote_path2 = NULL; + failf(data, "symlink command failed: %s", + sftp_libssh2_strerror(err)); + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + state(conn, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_QUOTE_MKDIR: + rc = libssh2_sftp_mkdir(sshc->sftp_session, sshc->quote_path1, 0755); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc != 0) { + err = libssh2_sftp_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + sshc->quote_path1 = NULL; + failf(data, "mkdir command failed: %s", sftp_libssh2_strerror(err)); + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + state(conn, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_QUOTE_RENAME: + rc = libssh2_sftp_rename(sshc->sftp_session, sshc->quote_path1, + sshc->quote_path2); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc != 0) { + err = libssh2_sftp_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + sshc->quote_path1 = NULL; + Curl_safefree(sshc->quote_path2); + sshc->quote_path2 = NULL; + failf(data, "rename command failed: %s", sftp_libssh2_strerror(err)); + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + state(conn, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_QUOTE_RMDIR: + rc = libssh2_sftp_rmdir(sshc->sftp_session, sshc->quote_path1); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc != 0) { + err = libssh2_sftp_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + sshc->quote_path1 = NULL; + failf(data, "rmdir command failed: %s", sftp_libssh2_strerror(err)); + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + state(conn, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_QUOTE_UNLINK: + rc = libssh2_sftp_unlink(sshc->sftp_session, sshc->quote_path1); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc != 0) { + err = libssh2_sftp_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + sshc->quote_path1 = NULL; + failf(data, "rm command failed: %s", sftp_libssh2_strerror(err)); + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + state(conn, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_TRANS_INIT: + if(data->set.upload) + state(conn, SSH_SFTP_UPLOAD_INIT); + else { + if(sftp_scp->path[strlen(sftp_scp->path)-1] == '/') + state(conn, SSH_SFTP_READDIR_INIT); + else + state(conn, SSH_SFTP_DOWNLOAD_INIT); + } + break; + + case SSH_SFTP_UPLOAD_INIT: + /* + * 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. + */ + sshc->sftp_handle = + libssh2_sftp_open(sshc->sftp_session, sftp_scp->path, + LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC, + data->set.new_file_perms); + if(!sshc->sftp_handle) { + if(libssh2_session_last_errno(sshc->ssh_session) == + LIBSSH2_ERROR_EAGAIN) { + break; + } + else { + err = libssh2_sftp_last_error(sshc->sftp_session); + failf(data, "Upload failed: %s", sftp_libssh2_strerror(err)); + if(sshc->secondCreateDirs) { state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = result; + sshc->actualcode = err; break; } + else if(((err == LIBSSH2_FX_NO_SUCH_FILE) || + (err == LIBSSH2_FX_FAILURE) || + (err == LIBSSH2_FX_NO_SUCH_PATH)) && + (data->set.ftp_create_missing_dirs && + (strlen(sftp_scp->path) > 1))) { + /* try to create the path remotely */ + sshc->secondCreateDirs = 1; + state(conn, SSH_SFTP_CREATE_DIRS_INIT); + break; + } + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = sftp_libssh2_error_to_CURLE(err); + break; } + } + + /* upload data */ + result = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, + FIRSTSOCKET, NULL); + + if(result) { + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = result; + } + else { + state(conn, SSH_STOP); + } + break; + + case SSH_SFTP_CREATE_DIRS_INIT: + if(strlen(sftp_scp->path) > 1) { + sshc->slash_pos = sftp_scp->path + 1; /* ignore the leading '/' */ state(conn, SSH_SFTP_CREATE_DIRS); - break; + } + else { + state(conn, SSH_SFTP_UPLOAD_INIT); + } + break; - case SSH_SFTP_READDIR_INIT: + case SSH_SFTP_CREATE_DIRS: + if((sshc->slash_pos = strchr(sshc->slash_pos, '/')) != NULL) { + *sshc->slash_pos = 0; + + infof(data, "Creating directory '%s'\n", sftp_scp->path); + state(conn, SSH_SFTP_CREATE_DIRS_MKDIR); + break; + } + else { + state(conn, SSH_SFTP_UPLOAD_INIT); + } + break; + + case SSH_SFTP_CREATE_DIRS_MKDIR: + /* 'mode' - parameter is preliminary - default to 0644 */ + rc = libssh2_sftp_mkdir(sshc->sftp_session, sftp_scp->path, + data->set.new_directory_perms); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + *sshc->slash_pos = '/'; + ++sshc->slash_pos; + if(rc == -1) { + unsigned int sftp_err = 0; /* - * This is a directory that we are trying to get, so produce a - * directory listing + * abort if failure wasn't that the dir already exists or the + * permission was denied (creation might succeed further + * down the path) - retry on unspecific FAILURE also */ - sshc->sftp_handle = libssh2_sftp_opendir(sshc->sftp_session, - sftp_scp->path); - if(!sshc->sftp_handle) { - if(libssh2_session_last_errno(sshc->ssh_session) == - LIBSSH2_ERROR_EAGAIN) { - break; - } - else { - err = libssh2_sftp_last_error(sshc->sftp_session); - failf(data, "Could not open directory for reading: %s", - sftp_libssh2_strerror(err)); - state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = sftp_libssh2_error_to_CURLE(err); - break; - } - } - if((sshc->readdir_filename = (char *)malloc(PATH_MAX+1)) == NULL) { + sftp_err = libssh2_sftp_last_error(sshc->sftp_session); + if((sftp_err != LIBSSH2_FX_FILE_ALREADY_EXISTS) && + (sftp_err != LIBSSH2_FX_FAILURE) && + (sftp_err != LIBSSH2_FX_PERMISSION_DENIED)) { + result = sftp_libssh2_error_to_CURLE(sftp_err); state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = CURLE_OUT_OF_MEMORY; + sshc->actualcode = result; break; } - if((sshc->readdir_longentry = (char *)malloc(PATH_MAX+1)) == NULL) { - Curl_safefree(sshc->readdir_filename); - sshc->readdir_filename = NULL; - state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = CURLE_OUT_OF_MEMORY; + } + state(conn, SSH_SFTP_CREATE_DIRS); + break; + + case SSH_SFTP_READDIR_INIT: + /* + * This is a directory that we are trying to get, so produce a + * directory listing + */ + sshc->sftp_handle = libssh2_sftp_opendir(sshc->sftp_session, + sftp_scp->path); + if(!sshc->sftp_handle) { + if(libssh2_session_last_errno(sshc->ssh_session) == + LIBSSH2_ERROR_EAGAIN) { break; } - state(conn, SSH_SFTP_READDIR); + else { + err = libssh2_sftp_last_error(sshc->sftp_session); + failf(data, "Could not open directory for reading: %s", + sftp_libssh2_strerror(err)); + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = sftp_libssh2_error_to_CURLE(err); + break; + } + } + if((sshc->readdir_filename = (char *)malloc(PATH_MAX+1)) == NULL) { + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_OUT_OF_MEMORY; break; + } + if((sshc->readdir_longentry = (char *)malloc(PATH_MAX+1)) == NULL) { + Curl_safefree(sshc->readdir_filename); + sshc->readdir_filename = NULL; + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_OUT_OF_MEMORY; + break; + } + state(conn, SSH_SFTP_READDIR); + break; - case SSH_SFTP_READDIR: - sshc->readdir_len = libssh2_sftp_readdir_ex(sshc->sftp_handle, - sshc->readdir_filename, - PATH_MAX, - sshc->readdir_longentry, - PATH_MAX, - &sshc->readdir_attrs); - if(sshc->readdir_len == LIBSSH2_ERROR_EAGAIN) { - break; + case SSH_SFTP_READDIR: + sshc->readdir_len = libssh2_sftp_readdir_ex(sshc->sftp_handle, + sshc->readdir_filename, + PATH_MAX, + sshc->readdir_longentry, + PATH_MAX, + &sshc->readdir_attrs); + if(sshc->readdir_len == LIBSSH2_ERROR_EAGAIN) { + break; + } + if(sshc->readdir_len > 0) { + sshc->readdir_filename[sshc->readdir_len] = '\0'; + + if(data->set.ftp_list_only) { + char *tmpLine; + + tmpLine = aprintf("%s\n", sshc->readdir_filename); + if(tmpLine == NULL) { + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_OUT_OF_MEMORY; + break; + } + result = Curl_client_write(conn, CLIENTWRITE_BODY, tmpLine, 0); + Curl_safefree(tmpLine); + + /* output debug output if that is requested */ + if(data->set.verbose) { + Curl_debug(data, CURLINFO_DATA_OUT, sshc->readdir_filename, + sshc->readdir_len, conn); + } } - if(sshc->readdir_len > 0) { - sshc->readdir_filename[sshc->readdir_len] = '\0'; + else { + sshc->readdir_currLen = strlen(sshc->readdir_longentry); + sshc->readdir_totalLen = 80 + sshc->readdir_currLen; + sshc->readdir_line = (char *)calloc(sshc->readdir_totalLen, 1); + if(!sshc->readdir_line) { + Curl_safefree(sshc->readdir_filename); + sshc->readdir_filename = NULL; + Curl_safefree(sshc->readdir_longentry); + sshc->readdir_longentry = NULL; + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_OUT_OF_MEMORY; + break; + } - if(data->set.ftp_list_only) { - char *tmpLine; - - tmpLine = aprintf("%s\n", sshc->readdir_filename); - if(tmpLine == NULL) { - state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = CURLE_OUT_OF_MEMORY; - break; - } - result = Curl_client_write(conn, CLIENTWRITE_BODY, tmpLine, 0); - Curl_safefree(tmpLine); - - /* output debug output if that is requested */ - if(data->set.verbose) { - Curl_debug(data, CURLINFO_DATA_OUT, sshc->readdir_filename, - sshc->readdir_len, conn); - } - } else { - sshc->readdir_currLen = strlen(sshc->readdir_longentry); - sshc->readdir_totalLen = 80 + sshc->readdir_currLen; - sshc->readdir_line = (char *)calloc(sshc->readdir_totalLen, 1); - if(!sshc->readdir_line) { + memcpy(sshc->readdir_line, sshc->readdir_longentry, + sshc->readdir_currLen); + if((sshc->readdir_attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) && + ((sshc->readdir_attrs.permissions & LIBSSH2_SFTP_S_IFMT) == + LIBSSH2_SFTP_S_IFLNK)) { + sshc->readdir_linkPath = (char *)malloc(PATH_MAX + 1); + if(sshc->readdir_linkPath == NULL) { Curl_safefree(sshc->readdir_filename); sshc->readdir_filename = NULL; Curl_safefree(sshc->readdir_longentry); sshc->readdir_longentry = NULL; state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = CURLE_OUT_OF_MEMORY; + sshc->actualcode = CURLE_OUT_OF_MEMORY; break; } - memcpy(sshc->readdir_line, sshc->readdir_longentry, - sshc->readdir_currLen); - if((sshc->readdir_attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) && - ((sshc->readdir_attrs.permissions & LIBSSH2_SFTP_S_IFMT) == - LIBSSH2_SFTP_S_IFLNK)) { - sshc->readdir_linkPath = (char *)malloc(PATH_MAX + 1); - if(sshc->readdir_linkPath == NULL) { - Curl_safefree(sshc->readdir_filename); - sshc->readdir_filename = NULL; - Curl_safefree(sshc->readdir_longentry); - sshc->readdir_longentry = NULL; - state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = CURLE_OUT_OF_MEMORY; - break; - } - - snprintf(sshc->readdir_linkPath, PATH_MAX, "%s%s", sftp_scp->path, - sshc->readdir_filename); - state(conn, SSH_SFTP_READDIR_LINK); - break; - } - state(conn, SSH_SFTP_READDIR_BOTTOM); + snprintf(sshc->readdir_linkPath, PATH_MAX, "%s%s", sftp_scp->path, + sshc->readdir_filename); + state(conn, SSH_SFTP_READDIR_LINK); break; } - } - else if(sshc->readdir_len == 0) { - Curl_safefree(sshc->readdir_filename); - sshc->readdir_filename = NULL; - Curl_safefree(sshc->readdir_longentry); - sshc->readdir_longentry = NULL; - state(conn, SSH_SFTP_READDIR_DONE); + state(conn, SSH_SFTP_READDIR_BOTTOM); break; } - else if(sshc->readdir_len <= 0) { - err = libssh2_sftp_last_error(sshc->sftp_session); - sshc->actualCode = err; - failf(data, "Could not open remote file for reading: %s :: %d", - sftp_libssh2_strerror(err), - libssh2_session_last_errno(sshc->ssh_session)); - Curl_safefree(sshc->readdir_filename); - sshc->readdir_filename = NULL; - Curl_safefree(sshc->readdir_longentry); - sshc->readdir_longentry = NULL; - state(conn, SSH_SFTP_CLOSE); - break; - } - break; - - case SSH_SFTP_READDIR_LINK: - sshc->readdir_len = libssh2_sftp_readlink(sshc->sftp_session, - sshc->readdir_linkPath, - sshc->readdir_filename, - PATH_MAX); - if(sshc->readdir_len == LIBSSH2_ERROR_EAGAIN) { - break; - } - Curl_safefree(sshc->readdir_linkPath); - sshc->readdir_linkPath = NULL; - sshc->readdir_line = realloc(sshc->readdir_line, - sshc->readdir_totalLen + 4 + - sshc->readdir_len); - if(!sshc->readdir_line) { - Curl_safefree(sshc->readdir_filename); - sshc->readdir_filename = NULL; - Curl_safefree(sshc->readdir_longentry); - sshc->readdir_longentry = NULL; - state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = CURLE_OUT_OF_MEMORY; - break; - } - - sshc->readdir_currLen += snprintf(sshc->readdir_line + - sshc->readdir_currLen, - sshc->readdir_totalLen - - sshc->readdir_currLen, - " -> %s", - sshc->readdir_filename); - - state(conn, SSH_SFTP_READDIR_BOTTOM); - break; - - case SSH_SFTP_READDIR_BOTTOM: - sshc->readdir_currLen += snprintf(sshc->readdir_line + - sshc->readdir_currLen, - sshc->readdir_totalLen - - sshc->readdir_currLen, "\n"); - result = Curl_client_write(conn, CLIENTWRITE_BODY, - sshc->readdir_line, 0); - - /* output debug output if that is requested */ - if(data->set.verbose) { - Curl_debug(data, CURLINFO_DATA_OUT, sshc->readdir_line, - sshc->readdir_currLen, conn); - } - Curl_safefree(sshc->readdir_line); - sshc->readdir_line = NULL; - state(conn, SSH_SFTP_READDIR); - break; - - case SSH_SFTP_READDIR_DONE: - if(libssh2_sftp_closedir(sshc->sftp_handle) == - LIBSSH2_ERROR_EAGAIN) { - break; - } - sshc->sftp_handle = NULL; + } + else if(sshc->readdir_len == 0) { Curl_safefree(sshc->readdir_filename); sshc->readdir_filename = NULL; Curl_safefree(sshc->readdir_longentry); sshc->readdir_longentry = NULL; - - /* no data to transfer */ - result = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); - state(conn, SSH_STOP); + state(conn, SSH_SFTP_READDIR_DONE); break; - - case SSH_SFTP_DOWNLOAD_INIT: - /* - * Work on getting the specified file - */ - sshc->sftp_handle = - libssh2_sftp_open(sshc->sftp_session, sftp_scp->path, - LIBSSH2_FXF_READ, data->set.new_file_perms); - if(!sshc->sftp_handle) { - if(libssh2_session_last_errno(sshc->ssh_session) == - LIBSSH2_ERROR_EAGAIN) { - break; - } - else { - err = libssh2_sftp_last_error(sshc->sftp_session); - failf(data, "Could not open remote file for reading: %s", - sftp_libssh2_strerror(err)); - state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = sftp_libssh2_error_to_CURLE(err); - break; - } - } - state(conn, SSH_SFTP_DOWNLOAD_STAT); + } + else if(sshc->readdir_len <= 0) { + err = libssh2_sftp_last_error(sshc->sftp_session); + sshc->actualcode = err; + failf(data, "Could not open remote file for reading: %s :: %d", + sftp_libssh2_strerror(err), + libssh2_session_last_errno(sshc->ssh_session)); + Curl_safefree(sshc->readdir_filename); + sshc->readdir_filename = NULL; + Curl_safefree(sshc->readdir_longentry); + sshc->readdir_longentry = NULL; + state(conn, SSH_SFTP_CLOSE); break; + } + break; - case SSH_SFTP_DOWNLOAD_STAT: - { - LIBSSH2_SFTP_ATTRIBUTES attrs; + case SSH_SFTP_READDIR_LINK: + sshc->readdir_len = libssh2_sftp_readlink(sshc->sftp_session, + sshc->readdir_linkPath, + sshc->readdir_filename, + PATH_MAX); + if(sshc->readdir_len == LIBSSH2_ERROR_EAGAIN) { + break; + } + Curl_safefree(sshc->readdir_linkPath); + sshc->readdir_linkPath = NULL; + sshc->readdir_line = realloc(sshc->readdir_line, + sshc->readdir_totalLen + 4 + + sshc->readdir_len); + if(!sshc->readdir_line) { + Curl_safefree(sshc->readdir_filename); + sshc->readdir_filename = NULL; + Curl_safefree(sshc->readdir_longentry); + sshc->readdir_longentry = NULL; + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_OUT_OF_MEMORY; + break; + } - rc = libssh2_sftp_stat(sshc->sftp_session, sftp_scp->path, &attrs); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc) { - /* - * libssh2_sftp_open() didn't return an error, so maybe the server - * just doesn't support stat() - */ - data->reqdata.size = -1; - data->reqdata.maxdownload = -1; - } else { - data->reqdata.size = attrs.filesize; - data->reqdata.maxdownload = attrs.filesize; - Curl_pgrsSetDownloadSize(data, attrs.filesize); - } + sshc->readdir_currLen += snprintf(sshc->readdir_line + + sshc->readdir_currLen, + sshc->readdir_totalLen - + sshc->readdir_currLen, + " -> %s", + sshc->readdir_filename); + + state(conn, SSH_SFTP_READDIR_BOTTOM); + break; + + case SSH_SFTP_READDIR_BOTTOM: + sshc->readdir_currLen += snprintf(sshc->readdir_line + + sshc->readdir_currLen, + sshc->readdir_totalLen - + sshc->readdir_currLen, "\n"); + result = Curl_client_write(conn, CLIENTWRITE_BODY, + sshc->readdir_line, 0); + + /* output debug output if that is requested */ + if(data->set.verbose) { + Curl_debug(data, CURLINFO_DATA_OUT, sshc->readdir_line, + sshc->readdir_currLen, conn); + } + Curl_safefree(sshc->readdir_line); + sshc->readdir_line = NULL; + state(conn, SSH_SFTP_READDIR); + break; + + case SSH_SFTP_READDIR_DONE: + if(libssh2_sftp_closedir(sshc->sftp_handle) == + LIBSSH2_ERROR_EAGAIN) { + break; + } + sshc->sftp_handle = NULL; + Curl_safefree(sshc->readdir_filename); + sshc->readdir_filename = NULL; + Curl_safefree(sshc->readdir_longentry); + sshc->readdir_longentry = NULL; + + /* no data to transfer */ + result = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + state(conn, SSH_STOP); + break; + + case SSH_SFTP_DOWNLOAD_INIT: + /* + * Work on getting the specified file + */ + sshc->sftp_handle = + libssh2_sftp_open(sshc->sftp_session, sftp_scp->path, + LIBSSH2_FXF_READ, data->set.new_file_perms); + if(!sshc->sftp_handle) { + if(libssh2_session_last_errno(sshc->ssh_session) == + LIBSSH2_ERROR_EAGAIN) { + break; } - - /* Setup the actual download */ - result = Curl_setup_transfer(conn, FIRSTSOCKET, data->reqdata.size, - FALSE, NULL, -1, NULL); - if(result) { + else { + err = libssh2_sftp_last_error(sshc->sftp_session); + failf(data, "Could not open remote file for reading: %s", + sftp_libssh2_strerror(err)); state(conn, SSH_SFTP_CLOSE); - sshc->actualCode = result; - } else { - state(conn, SSH_STOP); + sshc->actualcode = sftp_libssh2_error_to_CURLE(err); + break; } + } + state(conn, SSH_SFTP_DOWNLOAD_STAT); + break; + + case SSH_SFTP_DOWNLOAD_STAT: + { + LIBSSH2_SFTP_ATTRIBUTES attrs; + + rc = libssh2_sftp_stat(sshc->sftp_session, sftp_scp->path, &attrs); + if(rc == LIBSSH2_ERROR_EAGAIN) { break; - - case SSH_SFTP_CLOSE: - if(sshc->sftp_handle) { - rc = libssh2_sftp_close(sshc->sftp_handle); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc < 0) { - infof(data, "Failed to close libssh2 file\n"); - } - sshc->sftp_handle = NULL; - } - state(conn, SSH_SFTP_SHUTDOWN); - break; - - case SSH_SFTP_SHUTDOWN: - if(sshc->sftp_session) { - rc = libssh2_sftp_shutdown(sshc->sftp_session); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc < 0) { - infof(data, "Failed to stop libssh2 sftp subsystem\n"); - } - sshc->sftp_session = NULL; - } - - Curl_safefree(sftp_scp->path); - sftp_scp->path = NULL; - - Curl_safefree(sftp_scp->homedir); - sftp_scp->homedir = NULL; - - state(conn, SSH_CHANNEL_CLOSE); - break; - - case SSH_SCP_TRANS_INIT: - if(data->set.upload) { - if(data->set.infilesize < 0) { - failf(data, "SCP requires a known file size for upload"); - sshc->actualCode = CURLE_UPLOAD_FAILED; - state(conn, SSH_SCP_CHANNEL_FREE); - break; - } - state(conn, SSH_SCP_UPLOAD_INIT); - } else { - state(conn, SSH_SCP_DOWNLOAD_INIT); - } - break; - - case SSH_SCP_UPLOAD_INIT: + } + else if(rc) { /* - * 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. + * libssh2_sftp_open() didn't return an error, so maybe the server + * just doesn't support stat() */ - sshc->ssh_channel = - libssh2_scp_send_ex(sshc->ssh_session, sftp_scp->path, - data->set.new_file_perms, - data->set.infilesize, 0, 0); - if(!sshc->ssh_channel) { - if(libssh2_session_last_errno(sshc->ssh_session) == - LIBSSH2_ERROR_EAGAIN) { - break; - } else { - int ssh_err; - char *err_msg; + data->reqdata.size = -1; + data->reqdata.maxdownload = -1; + } + else { + data->reqdata.size = attrs.filesize; + data->reqdata.maxdownload = attrs.filesize; + Curl_pgrsSetDownloadSize(data, attrs.filesize); + } + } - ssh_err = libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0); - err = libssh2_session_error_to_CURLE(ssh_err); - failf(conn->data, "%s", err_msg); - state(conn, SSH_SCP_CHANNEL_FREE); - sshc->actualCode = err; - break; - } + /* Setup the actual download */ + result = Curl_setup_transfer(conn, FIRSTSOCKET, data->reqdata.size, + FALSE, NULL, -1, NULL); + if(result) { + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = result; + } + else { + state(conn, SSH_STOP); + } + break; + + case SSH_SFTP_CLOSE: + if(sshc->sftp_handle) { + rc = libssh2_sftp_close(sshc->sftp_handle); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; } + else if(rc < 0) { + infof(data, "Failed to close libssh2 file\n"); + } + sshc->sftp_handle = NULL; + } + Curl_safefree(sftp_scp->path); + sftp_scp->path = NULL; - /* upload data */ - result = Curl_setup_transfer(conn, -1, data->reqdata.size, FALSE, NULL, - FIRSTSOCKET, NULL); + DEBUGF(infof(data, "SFTP DONE done\n")); +#if 0 /* PREV */ + state(conn, SSH_SFTP_SHUTDOWN); +#endif + state(conn, SSH_STOP); + result = sshc->actualcode; + break; - if(result) { + case SSH_SFTP_SHUTDOWN: + if(sshc->sftp_session) { + rc = libssh2_sftp_shutdown(sshc->sftp_session); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc < 0) { + infof(data, "Failed to stop libssh2 sftp subsystem\n"); + } + sshc->sftp_session = NULL; + } + + Curl_safefree(sshc->homedir); + sshc->homedir = NULL; + + state(conn, SSH_SESSION_DISCONNECT); + break; + + case SSH_SCP_TRANS_INIT: + result = ssh_getworkingpath(conn, sshc->homedir, &sftp_scp->path); + if(result) { + sshc->actualcode = result; + state(conn, SSH_STOP); + break; + } + + if(data->set.upload) { + if(data->set.infilesize < 0) { + failf(data, "SCP requires a known file size for upload"); + sshc->actualcode = CURLE_UPLOAD_FAILED; state(conn, SSH_SCP_CHANNEL_FREE); - sshc->actualCode = result; - } else { - state(conn, SSH_STOP); + break; } - break; + state(conn, SSH_SCP_UPLOAD_INIT); + } + else { + state(conn, SSH_SCP_DOWNLOAD_INIT); + } + break; - case SSH_SCP_DOWNLOAD_INIT: - { - /* - * We must check the remote file; if it is a directory no values will - * be set in sb - */ - struct stat sb; - curl_off_t bytecount; - - memset(&sb, 0, sizeof(struct stat)); - sshc->ssh_channel = libssh2_scp_recv(sshc->ssh_session, - sftp_scp->path, &sb); - if(!sshc->ssh_channel) { - if(libssh2_session_last_errno(sshc->ssh_session) == - LIBSSH2_ERROR_EAGAIN) { - break; - } else { - int ssh_err; - char *err_msg; - - ssh_err = libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0); - err = libssh2_session_error_to_CURLE(ssh_err); - failf(conn->data, "%s", err_msg); - state(conn, SSH_SCP_CHANNEL_FREE); - sshc->actualCode = err; - break; - } - } - - /* download data */ - bytecount = (curl_off_t)sb.st_size; - data->reqdata.maxdownload = (curl_off_t)sb.st_size; - result = Curl_setup_transfer(conn, FIRSTSOCKET, - bytecount, FALSE, NULL, -1, NULL); - - if(result) { - state(conn, SSH_SCP_CHANNEL_FREE); - sshc->actualCode = result; - } else { - state(conn, SSH_STOP); - } + case SSH_SCP_UPLOAD_INIT: + /* + * 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. + */ + sshc->ssh_channel = + libssh2_scp_send_ex(sshc->ssh_session, sftp_scp->path, + data->set.new_file_perms, + data->set.infilesize, 0, 0); + if(!sshc->ssh_channel) { + if(libssh2_session_last_errno(sshc->ssh_session) == + LIBSSH2_ERROR_EAGAIN) { + break; } - break; + else { + int ssh_err; + char *err_msg; - case SSH_SCP_DONE: - if(data->set.upload) { - state(conn, SSH_SCP_SEND_EOF); - } else { + ssh_err = libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + err = libssh2_session_error_to_CURLE(ssh_err); + failf(conn->data, "%s", err_msg); state(conn, SSH_SCP_CHANNEL_FREE); + sshc->actualcode = err; + break; } - break; + } - case SSH_SCP_SEND_EOF: - if(sshc->ssh_channel) { - rc = libssh2_channel_send_eof(sshc->ssh_channel); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc) { - infof(data, "Failed to send libssh2 channel EOF\n"); - } - } - state(conn, SSH_SCP_WAIT_EOF); - break; + /* upload data */ + result = Curl_setup_transfer(conn, -1, data->reqdata.size, FALSE, NULL, + FIRSTSOCKET, NULL); - case SSH_SCP_WAIT_EOF: - if(sshc->ssh_channel) { - rc = libssh2_channel_wait_eof(sshc->ssh_channel); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc) { - infof(data, "Failed to get channel EOF\n"); - } - } - state(conn, SSH_SCP_WAIT_CLOSE); - break; - - case SSH_SCP_WAIT_CLOSE: - if(sshc->ssh_channel) { - rc = libssh2_channel_wait_closed(sshc->ssh_channel); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc) { - infof(data, "Channel failed to close\n"); - } - } + if(result) { state(conn, SSH_SCP_CHANNEL_FREE); - break; - - case SSH_SCP_CHANNEL_FREE: - if(sshc->ssh_channel) { - rc = libssh2_channel_free(sshc->ssh_channel); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc < 0) { - infof(data, "Failed to free libssh2 scp subsystem\n"); - } - sshc->ssh_channel = NULL; - } - state(conn, SSH_SESSION_DISCONECT); - break; - - case SSH_CHANNEL_CLOSE: - if(sshc->ssh_channel) { - rc = libssh2_channel_close(sshc->ssh_channel); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc < 0) { - infof(data, "Failed to stop libssh2 channel subsystem\n"); - } - sshc->ssh_channel = NULL; - } - state(conn, SSH_SESSION_DISCONECT); - break; - - case SSH_SESSION_DISCONECT: - if(sshc->ssh_session) { - rc = libssh2_session_disconnect(sshc->ssh_session, "Shutdown"); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc < 0) { - infof(data, "Failed to disconnect libssh2 session\n"); - } - } - - Curl_safefree(sftp_scp->path); - sftp_scp->path = NULL; - - Curl_safefree(sftp_scp->homedir); - sftp_scp->homedir = NULL; - - state(conn, SSH_SESSION_FREE); - break; - - case SSH_SESSION_FREE: - if(sshc->ssh_session) { - rc = libssh2_session_free(sshc->ssh_session); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc < 0) { - infof(data, "Failed to free libssh2 session\n"); - } - sshc->ssh_session = NULL; - } - sshc->nextState = SSH_NO_STATE; + sshc->actualcode = result; + } + else { state(conn, SSH_STOP); - result = sshc->actualCode; - break; + } + break; - case SSH_QUIT: - /* fallthrough, just stop! */ - default: - /* internal error */ - sshc->nextState = SSH_NO_STATE; + case SSH_SCP_DOWNLOAD_INIT: + { + /* + * We must check the remote file; if it is a directory no values will + * be set in sb + */ + struct stat sb; + curl_off_t bytecount; + + /* clear the struct scp recv will fill in */ + memset(&sb, 0, sizeof(struct stat)); + + /* get a fresh new channel from the ssh layer */ + sshc->ssh_channel = libssh2_scp_recv(sshc->ssh_session, + sftp_scp->path, &sb); + if(!sshc->ssh_channel) { + if(libssh2_session_last_errno(sshc->ssh_session) == + LIBSSH2_ERROR_EAGAIN) { + break; + } + else { + int ssh_err; + char *err_msg; + + ssh_err = libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + err = libssh2_session_error_to_CURLE(ssh_err); + failf(conn->data, "%s", err_msg); + state(conn, SSH_SCP_CHANNEL_FREE); + sshc->actualcode = err; + break; + } + } + + /* download data */ + bytecount = (curl_off_t)sb.st_size; + data->reqdata.maxdownload = (curl_off_t)sb.st_size; + result = Curl_setup_transfer(conn, FIRSTSOCKET, + bytecount, FALSE, NULL, -1, NULL); + + if(result) { + state(conn, SSH_SCP_CHANNEL_FREE); + sshc->actualcode = result; + } + else state(conn, SSH_STOP); - break; + } + 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->ssh_channel) { + rc = libssh2_channel_send_eof(sshc->ssh_channel); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc) { + infof(data, "Failed to send libssh2 channel EOF\n"); + } + } + state(conn, SSH_SCP_WAIT_EOF); + break; + + case SSH_SCP_WAIT_EOF: + if(sshc->ssh_channel) { + rc = libssh2_channel_wait_eof(sshc->ssh_channel); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc) { + infof(data, "Failed to get channel EOF\n"); + } + } + state(conn, SSH_SCP_WAIT_CLOSE); + break; + + case SSH_SCP_WAIT_CLOSE: + if(sshc->ssh_channel) { + rc = libssh2_channel_wait_closed(sshc->ssh_channel); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc) { + infof(data, "Channel failed to close\n"); + } + } + state(conn, SSH_SCP_CHANNEL_FREE); + break; + + case SSH_SCP_CHANNEL_FREE: + if(sshc->ssh_channel) { + rc = libssh2_channel_free(sshc->ssh_channel); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc < 0) { + infof(data, "Failed to free libssh2 scp subsystem\n"); + } + sshc->ssh_channel = NULL; + } + DEBUGF(infof(data, "SCP DONE phase complete\n")); +#if 0 /* PREV */ + state(conn, SSH_SESSION_DISCONNECT); +#endif + state(conn, SSH_STOP); + result = sshc->actualcode; + break; + + case SSH_SESSION_DISCONNECT: + if(sshc->ssh_session) { + rc = libssh2_session_disconnect(sshc->ssh_session, "Shutdown"); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc < 0) { + infof(data, "Failed to disconnect libssh2 session\n"); + } + } + + Curl_safefree(sshc->homedir); + sshc->homedir = NULL; + + state(conn, SSH_SESSION_FREE); + break; + + case SSH_SESSION_FREE: + if(sshc->ssh_session) { + rc = libssh2_session_free(sshc->ssh_session); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc < 0) { + infof(data, "Failed to free libssh2 session\n"); + } + sshc->ssh_session = NULL; + } + sshc->nextstate = SSH_NO_STATE; + state(conn, SSH_STOP); + result = sshc->actualcode; + break; + + case SSH_QUIT: + /* fallthrough, just stop! */ + default: + /* internal error */ + sshc->nextstate = SSH_NO_STATE; + state(conn, SSH_STOP); + break; } return result; } /* called repeatedly until done from multi.c */ -static CURLcode Curl_ssh_multi_statemach(struct connectdata *conn, bool *done) +static CURLcode ssh_multi_statemach(struct connectdata *conn, bool *done) { struct ssh_conn *sshc = &conn->proto.sshc; CURLcode result = CURLE_OK; @@ -1830,12 +1847,6 @@ static CURLcode ssh_init(struct connectdata *conn) /* get some initial data into the ssh struct */ ssh->bytecountp = &data->reqdata.keep.bytecount; - /* no need to duplicate them, this connectdata struct won't change */ - ssh->user = conn->user; - ssh->passwd = conn->passwd; - - ssh->errorstr = NULL; - return CURLE_OK; } @@ -1843,21 +1854,16 @@ static CURLcode ssh_init(struct connectdata *conn) * Curl_ssh_connect() gets called from Curl_protocol_connect() to allow us to * do protocol-specific actions at connect-time. */ -static CURLcode Curl_ssh_connect(struct connectdata *conn, bool *done) +static CURLcode ssh_connect(struct connectdata *conn, bool *done) { struct ssh_conn *ssh; curl_socket_t sock; CURLcode result; struct SessionHandle *data = conn->data; -#if 0 - /* Due to the fact that the state machine always cleans up and kills all - handles, we cannot currently re-use SCP or SFTP connections... */ - /* 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. */ conn->bits.close = FALSE; -#endif /* If there already is a protocol-specific struct allocated for this sessionhandle, deal with it */ @@ -1879,7 +1885,7 @@ static CURLcode Curl_ssh_connect(struct connectdata *conn, bool *done) #endif /* CURL_LIBSSH2_DEBUG */ sock = conn->sock[FIRSTSOCKET]; ssh->ssh_session = libssh2_session_init_ex(libssh2_malloc, libssh2_free, - libssh2_realloc, ssh); + libssh2_realloc, conn); if(ssh->ssh_session == NULL) { failf(data, "Failure initialising ssh session"); return CURLE_FAILED_INIT; @@ -1896,7 +1902,7 @@ static CURLcode Curl_ssh_connect(struct connectdata *conn, bool *done) state(conn, SSH_S_STARTUP); if(data->state.used_interface == Curl_if_multi) - result = Curl_ssh_multi_statemach(conn, done); + result = ssh_multi_statemach(conn, done); else { result = ssh_easy_statemach(conn); if(!result) @@ -1931,8 +1937,9 @@ CURLcode scp_perform(struct connectdata *conn, /* run the state-machine */ if(conn->data->state.used_interface == Curl_if_multi) { - result = Curl_ssh_multi_statemach(conn, dophase_done); - } else { + result = ssh_multi_statemach(conn, dophase_done); + } + else { result = ssh_easy_statemach(conn); *dophase_done = TRUE; /* with the easy interface we are done here */ } @@ -1946,11 +1953,11 @@ CURLcode scp_perform(struct connectdata *conn, } /* called from multi.c while DOing */ -static CURLcode Curl_scp_doing(struct connectdata *conn, +static CURLcode scp_doing(struct connectdata *conn, bool *dophase_done) { CURLcode result; - result = Curl_ssh_multi_statemach(conn, dophase_done); + result = ssh_multi_statemach(conn, dophase_done); if(*dophase_done) { DEBUGF(infof(conn->data, "DO phase is complete\n")); @@ -1958,8 +1965,12 @@ static CURLcode Curl_scp_doing(struct connectdata *conn, return result; } +/* + * The DO function is generic for both protocols. There was previously two + * separate ones but this way means less duplicated code. + */ -static CURLcode Curl_scp_do(struct connectdata *conn, bool *done) +static CURLcode ssh_do(struct connectdata *conn, bool *done) { CURLcode res; bool connected = 0; @@ -1975,9 +1986,8 @@ static CURLcode Curl_scp_do(struct connectdata *conn, bool *done) * Curl_ssh_connect() function. */ res = ssh_init(conn); - if(res) { + if(res) return res; - } data->reqdata.size = -1; /* make sure this is unknown at this point */ @@ -1986,13 +1996,30 @@ static CURLcode Curl_scp_do(struct connectdata *conn, bool *done) Curl_pgrsSetUploadSize(data, 0); Curl_pgrsSetDownloadSize(data, 0); - res = scp_perform(conn, &connected, done); + if(conn->protocol & PROT_SCP) + res = scp_perform(conn, &connected, done); + else + res = sftp_perform(conn, &connected, done); return res; } -static CURLcode Curl_scp_done(struct connectdata *conn, CURLcode status, - bool premature) +/* 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) +{ + CURLcode result; + + state(conn, SSH_SESSION_DISCONNECT); + + result = ssh_easy_statemach(conn); + + return result; +} + +static CURLcode scp_done(struct connectdata *conn, CURLcode status, + bool premature) { CURLcode result = CURLE_OK; bool done = FALSE; @@ -2003,7 +2030,7 @@ static CURLcode Curl_scp_done(struct connectdata *conn, CURLcode status, state(conn, SSH_SCP_DONE); /* run the state-machine */ if(conn->data->state.used_interface == Curl_if_multi) { - result = Curl_ssh_multi_statemach(conn, &done); + result = ssh_multi_statemach(conn, &done); } else { result = ssh_easy_statemach(conn); @@ -2016,7 +2043,11 @@ static CURLcode Curl_scp_done(struct connectdata *conn, CURLcode status, } if(done) { - Curl_safefree(conn->data->reqdata.proto.ssh); + struct SSHPROTO *sftp_scp = conn->data->reqdata.proto.ssh; + + Curl_safefree(sftp_scp->path); + + Curl_safefree(sftp_scp); conn->data->reqdata.proto.ssh = NULL; Curl_pgrsDone(conn); } @@ -2086,7 +2117,7 @@ CURLcode sftp_perform(struct connectdata *conn, /* run the state-machine */ if(conn->data->state.used_interface == Curl_if_multi) { - result = Curl_ssh_multi_statemach(conn, dophase_done); + result = ssh_multi_statemach(conn, dophase_done); } else { result = ssh_easy_statemach(conn); @@ -2102,11 +2133,11 @@ CURLcode sftp_perform(struct connectdata *conn, } /* called from multi.c while DOing */ -static CURLcode Curl_sftp_doing(struct connectdata *conn, - bool *dophase_done) +static CURLcode sftp_doing(struct connectdata *conn, + bool *dophase_done) { CURLcode result; - result = Curl_ssh_multi_statemach(conn, dophase_done); + result = ssh_multi_statemach(conn, dophase_done); if(*dophase_done) { DEBUGF(infof(conn->data, "DO phase is complete\n")); @@ -2114,46 +2145,25 @@ static CURLcode Curl_sftp_doing(struct connectdata *conn, return result; } -static CURLcode Curl_sftp_do(struct connectdata *conn, bool *done) +/* 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 sftp_disconnect(struct connectdata *conn) { - CURLcode res; - bool connected = 0; - struct SessionHandle *data = conn->data; + CURLcode result; - *done = FALSE; /* default to false */ + DEBUGF(infof(conn->data, "SSH DISCONNECT starts now\n")); - /* - * Since connections can be re-used between SessionHandles, this might be a - * connection already existing but on a fresh SessionHandle struct so we must - * make sure we have a good 'struct SSHPROTO' to play with. For new - * connections, the struct SSHPROTO is allocated and setup in the - * Curl_ssh_connect() function. - */ - res = ssh_init(conn); - if(res) - return res; + state(conn, SSH_SFTP_SHUTDOWN); + result = ssh_easy_statemach(conn); - data->reqdata.size = -1; /* make sure this is unknown at this point */ + DEBUGF(infof(conn->data, "SSH DISCONNECT is done\n")); - Curl_pgrsSetUploadCounter(data, 0); - Curl_pgrsSetDownloadCounter(data, 0); - Curl_pgrsSetUploadSize(data, 0); - Curl_pgrsSetDownloadSize(data, 0); + return result; - res = sftp_perform(conn, &connected, done); - - if(CURLE_OK == res) { - - if(!done) { - /* the DO phase has not completed yet */ - return CURLE_OK; - } - } - - return res; } -static CURLcode Curl_sftp_done(struct connectdata *conn, CURLcode status, +static CURLcode sftp_done(struct connectdata *conn, CURLcode status, bool premature) { CURLcode result = CURLE_OK; @@ -2165,7 +2175,7 @@ static CURLcode Curl_sftp_done(struct connectdata *conn, CURLcode status, if(status == CURLE_OK) { /* Before we shut down, see if there are any post-quote commands to send: */ if(!status && !premature && conn->data->set.postquote) { - sshc->nextState = SSH_SFTP_CLOSE; + sshc->nextstate = SSH_SFTP_CLOSE; state(conn, SSH_SFTP_POSTQUOTE_INIT); } else @@ -2173,7 +2183,7 @@ static CURLcode Curl_sftp_done(struct connectdata *conn, CURLcode status, /* run the state-machine */ if(conn->data->state.used_interface == Curl_if_multi) - result = Curl_ssh_multi_statemach(conn, &done); + result = ssh_multi_statemach(conn, &done); else { result = ssh_easy_statemach(conn); done = TRUE; diff --git a/lib/urldata.h b/lib/urldata.h index 228afd860..8deededc2 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -431,7 +431,8 @@ struct ftp_conn { typedef enum { SSH_NO_STATE = -1, /* Used for "nextState" so say there is none */ SSH_STOP = 0, /* do nothing state, stops the state machine */ - SSH_S_STARTUP, /* Session startup */ + + SSH_S_STARTUP, /* Session startup, First rate in SSH-CONNECT */ SSH_AUTHLIST, SSH_AUTH_PKEY_INIT, SSH_AUTH_PKEY, @@ -443,10 +444,10 @@ typedef enum { SSH_AUTH_KEY, SSH_AUTH_DONE, SSH_SFTP_INIT, - SSH_SFTP_REALPATH, - SSH_GET_WORKINGPATH, - SSH_SFTP_QUOTE_INIT, - SSH_SFTP_POSTQUOTE_INIT, + SSH_SFTP_REALPATH, /* Last state in SSH-CONNECT */ + + SSH_SFTP_QUOTE_INIT, /* First state in SFTP-DO */ + SSH_SFTP_POSTQUOTE_INIT, /* (Possibly) First state in SFTP-DONE */ SSH_SFTP_QUOTE, SSH_SFTP_NEXT_QUOTE, SSH_SFTP_QUOTE_STAT, @@ -467,20 +468,19 @@ typedef enum { 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_SFTP_DOWNLOAD_STAT, /* Last state in SFTP-DO */ + SSH_SFTP_CLOSE, /* Last state in SFTP-DONE */ + SSH_SFTP_SHUTDOWN, /* First state in SFTP-DISCONNECT */ + SSH_SCP_TRANS_INIT, /* First state in SCP-DO */ SSH_SCP_UPLOAD_INIT, SSH_SCP_DOWNLOAD_INIT, SSH_SCP_DONE, SSH_SCP_SEND_EOF, SSH_SCP_WAIT_EOF, SSH_SCP_WAIT_CLOSE, - SSH_SCP_CHANNEL_FREE, - SSH_CHANNEL_CLOSE, - SSH_SESSION_DISCONECT, - SSH_SESSION_FREE, + SSH_SCP_CHANNEL_FREE, /* Last state in SCP-DONE */ + SSH_SESSION_DISCONNECT, /* First state in SCP-DISCONNECT */ + SSH_SESSION_FREE, /* Last state in SCP/SFTP-DISCONNECT */ SSH_QUIT, SSH_LAST /* never used */ } sshstate; @@ -491,11 +491,7 @@ typedef enum { struct. */ struct SSHPROTO { curl_off_t *bytecountp; - char *user; - char *passwd; char *path; /* the path we operate on */ - char *homedir; - char *errorstr; }; /* ssh_conn is used for struct connection-oriented data in the connectdata @@ -508,12 +504,14 @@ struct ssh_conn { char *rsa; /* path name */ bool authed; /* the connection has been authenticated fine */ sshstate state; /* always use ssh.c:state() to change state! */ - sshstate nextState; /* the state to goto after stopping */ - CURLcode actualCode; /* the actual error code */ + sshstate nextstate; /* the state to goto after stopping */ + CURLcode actualcode; /* the actual error code */ 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 */ + 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;