From 8026d94c07cae4185b3e88872fb68ecd071a2c00 Mon Sep 17 00:00:00 2001 From: James Housley Date: Tue, 10 Jul 2007 22:26:32 +0000 Subject: [PATCH] * Finish moving sftp:// into a state machine so it won't block in multi mode * Move scp:// into a state machine so it won't block in multi mode * When available use the full directory entry from the sftp:// server --- lib/ssh.c | 2179 ++++++++++++++++++++++++++++++++++++------------ lib/ssh.h | 7 + lib/transfer.c | 12 +- lib/url.c | 14 +- lib/urldata.h | 56 +- 5 files changed, 1709 insertions(+), 559 deletions(-) diff --git a/lib/ssh.c b/lib/ssh.c index 6b21c5a8c..673fd3a99 100644 --- a/lib/ssh.c +++ b/lib/ssh.c @@ -1,25 +1,25 @@ /*************************************************************************** -* _ _ ____ _ -* Project ___| | | | _ \| | -* / __| | | | |_) | | -* | (__| |_| | _ <| |___ -* \___|\___/|_| \_\_____| -* -* Copyright (C) 1998 - 2007, Daniel Stenberg, , et al. -* -* This software is licensed as described in the file COPYING, which -* you should have received as part of this distribution. The terms -* are also available at http://curl.haxx.se/docs/copyright.html. -* -* You may opt to use, copy, modify, merge, publish, distribute and/or sell -* copies of the Software, and permit persons to whom the Software is -* furnished to do so, under the terms of the COPYING file. -* -* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY -* KIND, either express or implied. -* -* $Id$ -***************************************************************************/ + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2007, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * $Id$ + ***************************************************************************/ /* #define CURL_LIBSSH2_DEBUG */ @@ -163,14 +163,18 @@ /* Local functions: */ static const char *sftp_libssh2_strerror(unsigned long err); +#if (LIBSSH2_APINO < 200706012030) static CURLcode sftp_sendquote(struct connectdata *conn, struct curl_slist *quote); static CURLcode sftp_create_dirs(struct connectdata *conn); +#endif /* !(LIBSSH2_APINO < 200706012030) */ static LIBSSH2_ALLOC_FUNC(libssh2_malloc); static LIBSSH2_REALLOC_FUNC(libssh2_realloc); static LIBSSH2_FREE_FUNC(libssh2_free); +static int get_pathname(const char **cpp, char **path); + static void kbd_callback(const char *name, int name_len, const char *instruction, int instruction_len, int num_prompts, @@ -244,12 +248,12 @@ static LIBSSH2_FREE_FUNC(libssh2_free) * SSH State machine related code */ /* This is the ONLY way to change SSH state! */ -static void state(struct connectdata *conn, ftpstate state) +static void state(struct connectdata *conn, sshstate state) { #if defined(CURLDEBUG) && !defined(CURL_DISABLE_VERBOSE_STRINGS) /* for debug purposes */ - const char *names[]={ - "STOP", + const char *names[] = { + "SSH_STOP", "SSH_S_STARTUP", "SSH_AUTHLIST", "SSH_AUTH_PKEY_INIT", @@ -264,7 +268,41 @@ static void state(struct connectdata *conn, ftpstate state) "SSH_SFTP_INIT", "SSH_SFTP_REALPATH", "SSH_GET_WORKINGPATH", + "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_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_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", "QUIT" }; @@ -273,7 +311,7 @@ static void state(struct connectdata *conn, ftpstate state) #if defined(CURLDEBUG) && !defined(CURL_DISABLE_VERBOSE_STRINGS) if (sshc->state != state) { - infof(conn->data, "FTP %p state change from %s to %s\n", + infof(conn->data, "SFTP %p state change from %s to %s\n", sshc, names[sshc->state], names[state]); } #endif @@ -284,20 +322,23 @@ static void state(struct connectdata *conn, ftpstate state) static CURLcode ssh_statemach_act(struct connectdata *conn) { CURLcode result = CURLE_OK; - struct SessionHandle *data=conn->data; + struct SessionHandle *data = conn->data; + struct SSHPROTO *sftp_scp = data->reqdata.proto.ssh; struct ssh_conn *sshc = &conn->proto.sshc; curl_socket_t sock = conn->sock[FIRSTSOCKET]; - struct SSHPROTO *ssh; #ifdef CURL_LIBSSH2_DEBUG const char *fingerprint; #endif /* CURL_LIBSSH2_DEBUG */ int rc; - - ssh = data->reqdata.proto.ssh; + long err; switch(sshc->state) { case SSH_S_STARTUP: - rc = libssh2_session_startup(ssh->ssh_session, sock); + sshc->secondCreateDirs = 0; + sshc->nextState = SSH_NO_STATE; + sshc->actualCode = CURLE_OK; + + rc = libssh2_session_startup(sftp_scp->ssh_session, sock); if (rc == LIBSSH2_ERROR_EAGAIN) { break; } @@ -309,7 +350,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) } /* Set libssh2 to non-blocking, since cURL is all non-blocking */ - libssh2_session_set_blocking(ssh->ssh_session, 0); + libssh2_session_set_blocking(sftp_scp->ssh_session, 0); #ifdef CURL_LIBSSH2_DEBUG /* @@ -318,7 +359,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) * whatever) is up to us. As for know not much is implemented, besides * showing how to get the fingerprint. */ - fingerprint = libssh2_hostkey_hash(ssh->ssh_session, + fingerprint = libssh2_hostkey_hash(sftp_scp->ssh_session, LIBSSH2_HOSTKEY_HASH_MD5); /* The fingerprint points to static storage (!), don't free() it. */ @@ -345,11 +386,12 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) * experience. * So always specify it here. */ - sshc->authlist = libssh2_userauth_list(ssh->ssh_session, ssh->user, - strlen(ssh->user)); + sshc->authlist = libssh2_userauth_list(sftp_scp->ssh_session, + sftp_scp->user, + strlen(sftp_scp->user)); if (!sshc->authlist) { - if (libssh2_session_last_errno(ssh->ssh_session) == + if (libssh2_session_last_errno(sftp_scp->ssh_session) == LIBSSH2_ERROR_EAGAIN) { break; } else { @@ -365,8 +407,8 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) case SSH_AUTH_PKEY_INIT: /* - * Check the supported auth types in the order I feel is most secure with - * the requested type of authentication + * Check the supported auth types in the order I feel is most secure + * with the requested type of authentication */ sshc->authed = FALSE; @@ -386,7 +428,8 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) sshc->rsa_pub = aprintf("%s/.ssh/id_dsa.pub", home); if (sshc->rsa_pub == NULL) { - curl_free(home); + Curl_safefree(home); + home = NULL; state(conn, SSH_SESSION_FREE); sshc->actualCode = CURLE_OUT_OF_MEMORY; break; @@ -398,8 +441,10 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) sshc->rsa = aprintf("%s/.ssh/id_dsa", home); if (sshc->rsa == NULL) { - curl_free(home); - curl_free(sshc->rsa_pub); + 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; @@ -409,10 +454,11 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) if (!sshc->passphrase) sshc->passphrase = ""; - curl_free(home); + Curl_safefree(home); + home = NULL; - infof(conn->data, "Using ssh public key file %s\n", sshc->rsa_pub); - infof(conn->data, "Using ssh private key file %s\n", sshc->rsa); + 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 { @@ -423,19 +469,21 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) case SSH_AUTH_PKEY: /* The function below checks if the files exists, no need to stat() here. */ - rc = libssh2_userauth_publickey_fromfile(ssh->ssh_session, ssh->user, - sshc->rsa_pub, sshc->rsa, - sshc->passphrase); + rc = libssh2_userauth_publickey_fromfile(sftp_scp->ssh_session, + sftp_scp->user, sshc->rsa_pub, + sshc->rsa, sshc->passphrase); if (rc == LIBSSH2_ERROR_EAGAIN) { break; } - curl_free(sshc->rsa_pub); - curl_free(sshc->rsa); + Curl_safefree(sshc->rsa_pub); + sshc->rsa_pub = NULL; + Curl_safefree(sshc->rsa); + sshc->rsa = NULL; if (rc == 0) { sshc->authed = TRUE; - infof(conn->data, "Initialized SSH public key authentication\n"); + infof(data, "Initialized SSH public key authentication\n"); state(conn, SSH_AUTH_DONE); } else { state(conn, SSH_AUTH_PASS_INIT); @@ -452,14 +500,14 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) break; case SSH_AUTH_PASS: - rc = libssh2_userauth_password(ssh->ssh_session, ssh->user, - ssh->passwd); + rc = libssh2_userauth_password(sftp_scp->ssh_session, sftp_scp->user, + sftp_scp->passwd); if (rc == LIBSSH2_ERROR_EAGAIN) { break; } else if (rc == 0) { sshc->authed = TRUE; - infof(conn->data, "Initialized password authentication\n"); + infof(data, "Initialized password authentication\n"); state(conn, SSH_AUTH_DONE); } else { state(conn, SSH_AUTH_HOST_INIT); @@ -490,16 +538,16 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) case SSH_AUTH_KEY: /* Authentication failed. Continue with keyboard-interactive now. */ - rc = libssh2_userauth_keyboard_interactive_ex(ssh->ssh_session, - ssh->user, - strlen(ssh->user), + rc = libssh2_userauth_keyboard_interactive_ex(sftp_scp->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(conn->data, "Initialized keyboard interactive authentication\n"); + infof(data, "Initialized keyboard interactive authentication\n"); } state(conn, SSH_AUTH_DONE); break; @@ -515,7 +563,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) /* * At this point we have an authenticated ssh session. */ - infof(conn->data, "Authentication complete\n"); + infof(data, "Authentication complete\n"); conn->sockfd = sock; conn->writesockfd = CURL_SOCKET_BAD; @@ -531,9 +579,9 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) /* * Start the libssh2 sftp session */ - ssh->sftp_session = libssh2_sftp_init(ssh->ssh_session); - if (!ssh->sftp_session) { - if (libssh2_session_last_errno(ssh->ssh_session) == + sftp_scp->sftp_session = libssh2_sftp_init(sftp_scp->ssh_session); + if (!sftp_scp->sftp_session) { + if (libssh2_session_last_errno(sftp_scp->ssh_session) == LIBSSH2_ERROR_EAGAIN) { break; } else { @@ -553,7 +601,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) /* * Get the "home" directory */ - rc = libssh2_sftp_realpath(ssh->sftp_session, ".", + rc = libssh2_sftp_realpath(sftp_scp->sftp_session, ".", tempHome, PATH_MAX-1); if (rc == LIBSSH2_ERROR_EAGAIN) { break; @@ -561,15 +609,15 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) else if (rc > 0) { /* It seems that this string is not always NULL terminated */ tempHome[rc] = '\0'; - ssh->homedir = (char *)strdup(tempHome); - if (!ssh->homedir) { - state(conn, SSH_SFTP_SHUTDOWN); + 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(ssh->sftp_session); + result = libssh2_sftp_last_error(sftp_scp->sftp_session); DEBUGF(infof(data, "error = %d\n", result)); state(conn, SSH_STOP); break; @@ -587,8 +635,8 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) working_path = curl_easy_unescape(data, data->reqdata.path, 0, &working_path_len); if (!working_path) { - state(conn, SSH_STOP); result = CURLE_OUT_OF_MEMORY; + state(conn, SSH_STOP); break; } @@ -597,6 +645,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) 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; @@ -610,33 +659,35 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) } else if (conn->protocol == PROT_SFTP) { if (working_path[1] == '~') { - real_path = (char *)malloc(strlen(ssh->homedir) + + real_path = (char *)malloc(strlen(sftp_scp->homedir) + working_path_len + 1); if (real_path == NULL) { - Curl_safefree(ssh->homedir); - ssh->homedir = NULL; + Curl_safefree(sftp_scp->homedir); + sftp_scp->homedir = NULL; Curl_safefree(working_path); - state(conn, SSH_SFTP_SHUTDOWN); + 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, ssh->homedir, strlen(ssh->homedir)); - real_path[strlen(ssh->homedir)] = '/'; - real_path[strlen(ssh->homedir)+1] = '\0'; + 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(ssh->homedir)+1, working_path + 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(ssh->homedir); - ssh->homedir = NULL; + Curl_safefree(sftp_scp->homedir); + sftp_scp->homedir = NULL; Curl_safefree(working_path); - state(conn, SSH_SFTP_SHUTDOWN); + working_path = NULL; + state(conn, SSH_SFTP_CLOSE); sshc->actualCode = CURLE_OUT_OF_MEMORY; break; } @@ -645,34 +696,1125 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) } else { Curl_safefree(working_path); + working_path = NULL; state(conn, SSH_SESSION_FREE); sshc->actualCode = CURLE_FAILED_INIT; break; } Curl_safefree(working_path); - ssh->path = real_path; + working_path = NULL; + sftp_scp->path = real_path; /* Connect is all done */ state(conn, SSH_STOP); } break; - case SSH_SFTP_SHUTDOWN: - rc = libssh2_sftp_shutdown(ssh->sftp_session); + 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); + } + } + 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_FTP_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; + } + } + } + if (!sshc->quote_item) { + state(conn, SSH_SFTP_TRANS_INIT); + } + break; + + 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; + } + + sshc->quote_item = sshc->quote_item->next; + + 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; + + case SSH_SFTP_QUOTE_STAT: + rc = libssh2_sftp_stat(sftp_scp->sftp_session, sshc->quote_path2, + &sshc->quote_attrs); if (rc == LIBSSH2_ERROR_EAGAIN) { break; } - ssh->sftp_session = NULL; + else if (rc != 0) { /* get those attributes */ + err = libssh2_sftp_last_error(sftp_scp->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_FTP_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_FTP_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_FTP_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_FTP_QUOTE_ERROR; + break; + } + } + + /* Now send the completed structure... */ + state(conn, SSH_SFTP_QUOTE_SETSTAT); + break; + + case SSH_SFTP_QUOTE_SETSTAT: + rc = libssh2_sftp_setstat(sftp_scp->sftp_session, sshc->quote_path2, + &sshc->quote_attrs); + if (rc == LIBSSH2_ERROR_EAGAIN) { + break; + } else if (rc != 0) { + err = libssh2_sftp_last_error(sftp_scp->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_FTP_QUOTE_ERROR; + break; + } + state(conn, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_QUOTE_SYMLINK: + rc = libssh2_sftp_symlink(sftp_scp->sftp_session, sshc->quote_path1, + sshc->quote_path2); + if (rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if (rc != 0) { + err = libssh2_sftp_last_error(sftp_scp->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_FTP_QUOTE_ERROR; + break; + } + state(conn, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_QUOTE_MKDIR: + rc = libssh2_sftp_mkdir(sftp_scp->sftp_session, sshc->quote_path1, 0755); + if (rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if (rc != 0) { + err = libssh2_sftp_last_error(sftp_scp->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_FTP_QUOTE_ERROR; + break; + } + state(conn, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_QUOTE_RENAME: + rc = libssh2_sftp_rename(sftp_scp->sftp_session, sshc->quote_path1, + sshc->quote_path2); + if (rc == LIBSSH2_ERROR_EAGAIN) { + break; + } else if (rc != 0) { + err = libssh2_sftp_last_error(sftp_scp->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_FTP_QUOTE_ERROR; + break; + } + state(conn, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_QUOTE_RMDIR: + rc = libssh2_sftp_rmdir(sftp_scp->sftp_session, sshc->quote_path1); + if (rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if (rc != 0) { + err = libssh2_sftp_last_error(sftp_scp->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_FTP_QUOTE_ERROR; + break; + } + state(conn, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_QUOTE_UNLINK: + rc = libssh2_sftp_unlink(sftp_scp->sftp_session, sshc->quote_path1); + if (rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if (rc != 0) { + err = libssh2_sftp_last_error(sftp_scp->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_FTP_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); + break; + } else { + if (sftp_scp->path[strlen(sftp_scp->path)-1] == '/') { + state(conn, SSH_SFTP_READDIR_INIT); + break; + } else { + state(conn, SSH_SFTP_DOWNLOAD_INIT); + break; + } + } + 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. + */ + sftp_scp->sftp_handle = + libssh2_sftp_open(sftp_scp->sftp_session, sftp_scp->path, + LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC, + data->set.new_file_perms); + if (!sftp_scp->sftp_handle) { + if (libssh2_session_last_errno(sftp_scp->ssh_session) == + LIBSSH2_ERROR_EAGAIN) { + break; + } else { + err = libssh2_sftp_last_error(sftp_scp->sftp_session); + 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) { + 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(sftp_scp->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; + /* + * 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(sftp_scp->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 = result; + break; + } + } + 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 + */ + sftp_scp->sftp_handle = libssh2_sftp_opendir(sftp_scp->sftp_session, + sftp_scp->path); + if (!sftp_scp->sftp_handle) { + if (libssh2_session_last_errno(sftp_scp->ssh_session) == + LIBSSH2_ERROR_EAGAIN) { + break; + } else { + err = libssh2_sftp_last_error(sftp_scp->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: +#if (LIBSSH2_APINO >= 200706151200) + sshc->readdir_len = libssh2_sftp_readdir_ex(sftp_scp->sftp_handle, + sshc->readdir_filename, + PATH_MAX, + sshc->readdir_longentry, + PATH_MAX, + &sshc->readdir_attrs); +#else /* !(LIBSSH2_APINO >= 200706151200) */ + sshc->readdir_len = libssh2_sftp_readdir(sftp_scp->sftp_handle, + sshc->readdir_filename, + PATH_MAX, + &sshc->readdir_attrs); +#endif /* !(LIBSSH2_APINO >= 200706151200) */ + 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); + } + } else { +#if (LIBSSH2_APINO >= 200706151200) + 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; + } + + memcpy(sshc->readdir_line, sshc->readdir_longentry, + sshc->readdir_currLen); +#else /* !(LIBSSH2_APINO >= 200706151200) */ + sshc->readdir_totalLen = 80 + sshc->readdir_len; + sshc->readdir_line = (char *)malloc(sshc->readdir_totalLen); + 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 (!(sshc->readdir_attrs.flags & LIBSSH2_SFTP_ATTR_UIDGID)) { + sshc->readdir_attrs.uid = sshc->readdir_attrs.gid = 0; + } + + sshc->readdir_currLen = snprintf(sshc->readdir_line, + sshc->readdir_totalLen, + "---------- 1 %5d %5d", + sshc->readdir_attrs.uid, + sshc->readdir_attrs.gid); + + if (sshc->readdir_attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) { + if ((sshc->readdir_attrs.permissions & LIBSSH2_SFTP_S_IFMT) == + LIBSSH2_SFTP_S_IFDIR) { + sshc->readdir_line[0] = 'd'; + } + else if ((sshc->readdir_attrs.permissions & LIBSSH2_SFTP_S_IFMT) == + LIBSSH2_SFTP_S_IFLNK) { + sshc->readdir_line[0] = 'l'; + } + else if ((sshc->readdir_attrs.permissions & LIBSSH2_SFTP_S_IFMT) == + LIBSSH2_SFTP_S_IFSOCK) { + sshc->readdir_line[0] = 's'; + } + else if ((sshc->readdir_attrs.permissions & LIBSSH2_SFTP_S_IFMT) == + LIBSSH2_SFTP_S_IFCHR) { + sshc->readdir_line[0] = 'c'; + } + else if ((sshc->readdir_attrs.permissions & LIBSSH2_SFTP_S_IFMT) == + LIBSSH2_SFTP_S_IFBLK) { + sshc->readdir_line[0] = 'b'; + } + if (sshc->readdir_attrs.permissions & LIBSSH2_SFTP_S_IRUSR) { + sshc->readdir_line[1] = 'r'; + } + if (sshc->readdir_attrs.permissions & LIBSSH2_SFTP_S_IWUSR) { + sshc->readdir_line[2] = 'w'; + } + if (sshc->readdir_attrs.permissions & LIBSSH2_SFTP_S_IXUSR) { + sshc->readdir_line[3] = 'x'; + } + if (sshc->readdir_attrs.permissions & LIBSSH2_SFTP_S_IRGRP) { + sshc->readdir_line[4] = 'r'; + } + if (sshc->readdir_attrs.permissions & LIBSSH2_SFTP_S_IWGRP) { + sshc->readdir_line[5] = 'w'; + } + if (sshc->readdir_attrs.permissions & LIBSSH2_SFTP_S_IXGRP) { + sshc->readdir_line[6] = 'x'; + } + if (sshc->readdir_attrs.permissions & LIBSSH2_SFTP_S_IROTH) { + sshc->readdir_line[7] = 'r'; + } + if (sshc->readdir_attrs.permissions & LIBSSH2_SFTP_S_IWOTH) { + sshc->readdir_line[8] = 'w'; + } + if (sshc->readdir_attrs.permissions & LIBSSH2_SFTP_S_IXOTH) { + sshc->readdir_line[9] = 'x'; + } + } + if (sshc->readdir_attrs.flags & LIBSSH2_SFTP_ATTR_SIZE) { + sshc->readdir_currLen += snprintf(sshc->readdir_line + + sshc->readdir_currLen, + sshc->readdir_totalLen - + sshc->readdir_currLen, + "%11lld", + sshc->readdir_attrs.filesize); + } + if (sshc->readdir_attrs.flags & LIBSSH2_SFTP_ATTR_ACMODTIME) { + static const char * const months[12] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + struct tm *nowParts; + time_t now, remoteTime; + + now = time(NULL); + remoteTime = (time_t)sshc->readdir_attrs.mtime; + nowParts = localtime(&remoteTime); + + if ((time_t)sshc->readdir_attrs.mtime > (now - (86400 * 180))) { + sshc->readdir_currLen += snprintf(sshc->readdir_line + + sshc->readdir_currLen, + sshc->readdir_totalLen - + sshc->readdir_currLen, + " %s %2d %2d:%02d", + months[nowParts->tm_mon], + nowParts->tm_mday, + nowParts->tm_hour, + nowParts->tm_min); + } else { + sshc->readdir_currLen += snprintf(sshc->readdir_line + + sshc->readdir_currLen, + sshc->readdir_totalLen - + sshc->readdir_currLen, + " %s %2d %5d", + months[nowParts->tm_mon], + nowParts->tm_mday, + 1900+nowParts->tm_year); + } + } + sshc->readdir_currLen += snprintf(sshc->readdir_line + + sshc->readdir_currLen, + sshc->readdir_totalLen - + sshc->readdir_currLen, " %s", + sshc->readdir_filename); +#endif /* !(LIBSSH2_APINO >= 200706151200) */ + 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); + 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); + break; + } + else if (sshc->readdir_len <= 0) { + err = libssh2_sftp_last_error(sftp_scp->sftp_session); + sshc->actualCode = err; + failf(data, "Could not open remote file for reading: %s :: %d", + sftp_libssh2_strerror(err), + libssh2_session_last_errno(sftp_scp->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(sftp_scp->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(sftp_scp->sftp_handle) == + LIBSSH2_ERROR_EAGAIN) { + break; + } + sftp_scp->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 + */ + sftp_scp->sftp_handle = + libssh2_sftp_open(sftp_scp->sftp_session, sftp_scp->path, + LIBSSH2_FXF_READ, data->set.new_file_perms); + if (!sftp_scp->sftp_handle) { + if (libssh2_session_last_errno(sftp_scp->ssh_session) == + LIBSSH2_ERROR_EAGAIN) { + break; + } else { + err = libssh2_sftp_last_error(sftp_scp->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); + break; + + case SSH_SFTP_DOWNLOAD_STAT: + { + LIBSSH2_SFTP_ATTRIBUTES attrs; + + rc = libssh2_sftp_stat(sftp_scp->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); + } + } + + /* 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 (sftp_scp->sftp_handle) { + rc = libssh2_sftp_close(sftp_scp->sftp_handle); + if (rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if (rc < 0) { + infof(data, "Failed to close libssh2 file\n"); + } + sftp_scp->sftp_handle = NULL; + } + state(conn, SSH_SFTP_SHUTDOWN); + break; + + case SSH_SFTP_SHUTDOWN: + if (sftp_scp->sftp_session) { + rc = libssh2_sftp_shutdown(sftp_scp->sftp_session); + if (rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if (rc < 0) { + infof(data, "Failed to stop libssh2 sftp subsystem\n"); + } + sftp_scp->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: + /* + * libssh2 requires that the destination path is a full path that + * includes the destination file and name OR ends in a "/" . If this is + * not done the destination file will be named the same name as the last + * directory in the path. + */ + sftp_scp->ssh_channel = + libssh2_scp_send_ex(sftp_scp->ssh_session, sftp_scp->path, + data->set.new_file_perms, + data->set.infilesize, 0, 0); + if (!sftp_scp->ssh_channel) { + if (libssh2_session_last_errno(sftp_scp->ssh_session) == + LIBSSH2_ERROR_EAGAIN) { + break; + } else { + int ssh_err; + char *err_msg; + + ssh_err = libssh2_session_last_error(sftp_scp->ssh_session, + &err_msg, NULL, 0); + err = libssh2_session_error_to_CURLE(ssh_err); + failf(conn->data, "%s", err_msg); + Curl_safefree(err_msg); + state(conn, SSH_SCP_CHANNEL_FREE); + sshc->actualCode = err; + break; + } + } + + /* upload data */ + result = Curl_setup_transfer(conn, -1, data->reqdata.size, FALSE, NULL, + FIRSTSOCKET, NULL); + + if (result) { + state(conn, SSH_SCP_CHANNEL_FREE); + sshc->actualCode = result; + } else { + state(conn, SSH_STOP); + } + 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)); + sftp_scp->ssh_channel = libssh2_scp_recv(sftp_scp->ssh_session, + sftp_scp->path, &sb); + if (!sftp_scp->ssh_channel) { + if (libssh2_session_last_errno(sftp_scp->ssh_session) == + LIBSSH2_ERROR_EAGAIN) { + break; + } else { + int ssh_err; + char *err_msg; + + ssh_err = libssh2_session_last_error(sftp_scp->ssh_session, + &err_msg, NULL, 0); + err = libssh2_session_error_to_CURLE(ssh_err); + failf(conn->data, "%s", err_msg); + Curl_safefree(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; + + 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 (sftp_scp->ssh_channel) { + rc = libssh2_channel_send_eof(sftp_scp->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 (sftp_scp->ssh_channel) { + rc = libssh2_channel_wait_eof(sftp_scp->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 (sftp_scp->ssh_channel) { + rc = libssh2_channel_wait_closed(sftp_scp->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 (sftp_scp->ssh_channel) { + rc = libssh2_channel_free(sftp_scp->ssh_channel); + if (rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if (rc < 0) { + infof(data, "Failed to free libssh2 scp subsystem\n"); + } + sftp_scp->ssh_channel = NULL; + } + state(conn, SSH_SESSION_DISCONECT); + break; + + case SSH_CHANNEL_CLOSE: + if (sftp_scp->ssh_channel) { + rc = libssh2_channel_close(sftp_scp->ssh_channel); + if (rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if (rc < 0) { + infof(data, "Failed to stop libssh2 channel subsystem\n"); + } + sftp_scp->ssh_channel = NULL; + } + state(conn, SSH_SESSION_DISCONECT); + break; + + case SSH_SESSION_DISCONECT: + if (sftp_scp->ssh_session) { + rc = libssh2_session_disconnect(sftp_scp->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: - rc = libssh2_session_free(ssh->ssh_session); - if (rc == LIBSSH2_ERROR_EAGAIN) { - break; + if (sftp_scp->ssh_session) { + rc = libssh2_session_free(sftp_scp->ssh_session); + if (rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if (rc < 0) { + infof(data, "Failed to free libssh2 session\n"); + } + sftp_scp->ssh_session = NULL; } - ssh->ssh_session = NULL; + sshc->nextState = SSH_NO_STATE; state(conn, SSH_STOP); result = sshc->actualCode; break; @@ -681,6 +1823,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) /* fallthrough, just stop! */ default: /* internal error */ + sshc->nextState = SSH_NO_STATE; state(conn, SSH_STOP); break; } @@ -692,83 +1835,28 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) CURLcode Curl_ssh_multi_statemach(struct connectdata *conn, bool *done) { -#if 0 - curl_socket_t sock = conn->sock[FIRSTSOCKET]; -#endif - int rc = 1; - struct SessionHandle *data=conn->data; struct ssh_conn *sshc = &conn->proto.sshc; CURLcode result = CURLE_OK; -#if 0 - long timeout_ms = ssh_state_timeout(conn); -#endif - *done = FALSE; /* default to not done yet */ - -#if 0 - if (timeout_ms <= 0) { - failf(data, "SSH response timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - rc = Curl_socket_ready(sshc->sendleft?CURL_SOCKET_BAD:sock, /* reading */ - sshc->sendleft?sock:CURL_SOCKET_BAD, /* writing */ - 0); -#endif - - if (rc == -1) { - failf(data, "select/poll error"); - return CURLE_OUT_OF_MEMORY; - } - else if (rc != 0) { - result = ssh_statemach_act(conn); - *done = (bool)(sshc->state == SSH_STOP); - } - /* if rc == 0, then select() timed out */ + result = ssh_statemach_act(conn); + *done = (bool)(sshc->state == SSH_STOP); return result; } static CURLcode ssh_easy_statemach(struct connectdata *conn) { -#if 0 - curl_socket_t sock = conn->sock[FIRSTSOCKET]; -#endif - int rc = 1; - struct SessionHandle *data=conn->data; struct ssh_conn *sshc = &conn->proto.sshc; CURLcode result = CURLE_OK; - while(sshc->state != SSH_STOP) { -#if 0 - long timeout_ms = ssh_state_timeout(conn); - - if (timeout_ms <=0 ) { - failf(data, "SSH response timeout"); - return CURLE_OPERATION_TIMEDOUT; /* already too little time */ - } - - rc = Curl_socket_ready(sshc->sendleft?CURL_SOCKET_BAD:sock, /* reading */ - sshc->sendleft?sock:CURL_SOCKET_BAD, /* writing */ - (int)timeout_ms); -#endif - - if (rc == -1) { - failf(data, "select/poll error"); - return CURLE_OUT_OF_MEMORY; - } - else if (rc == 0) { - result = CURLE_OPERATION_TIMEDOUT; + while (sshc->state != SSH_STOP) { + result = ssh_statemach_act(conn); + if (result) { break; } - else { - result = ssh_statemach_act(conn); - if (result) - break; - } } -return result; + return result; } #endif /* (LIBSSH2_APINO >= 200706012030) */ @@ -840,7 +1928,7 @@ 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, ssh); if (ssh->ssh_session == NULL) { failf(data, "Failure initialising ssh session"); return CURLE_FAILED_INIT; @@ -923,8 +2011,8 @@ CURLcode Curl_ssh_connect(struct connectdata *conn, bool *done) infof(data, "SSH authentication methods available: %s\n", authlist); /* - * Check the supported auth types in the order I feel is most secure with the - * requested type of authentication + * Check the supported auth types in the order I feel is most secure with + * the requested type of authentication */ if ((data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY) && (strstr(authlist, "publickey") != NULL)) { @@ -953,10 +2041,10 @@ CURLcode Curl_ssh_connect(struct connectdata *conn, bool *done) if (!passphrase) passphrase = ""; - curl_free(home); + Curl_safefree(home); - infof(conn->data, "Using ssh public key file %s\n", rsa_pub); - infof(conn->data, "Using ssh private key file %s\n", rsa); + infof(data, "Using ssh public key file %s\n", rsa_pub); + infof(data, "Using ssh private key file %s\n", rsa); if (rsa_pub[0]) { /* The function below checks if the files exists, no need to stat() here. @@ -964,7 +2052,7 @@ CURLcode Curl_ssh_connect(struct connectdata *conn, bool *done) if (libssh2_userauth_publickey_fromfile(ssh->ssh_session, ssh->user, rsa_pub, rsa, passphrase) == 0) { authed = TRUE; - infof(conn->data, "Initialized SSH public key authentication\n"); + infof(data, "Initialized SSH public key authentication\n"); } } } @@ -973,7 +2061,7 @@ CURLcode Curl_ssh_connect(struct connectdata *conn, bool *done) (strstr(authlist, "password") != NULL)) { if (!libssh2_userauth_password(ssh->ssh_session, ssh->user, ssh->passwd)) { authed = TRUE; - infof(conn->data, "Initialized password authentication\n"); + infof(data, "Initialized password authentication\n"); } } if (!authed && (data->set.ssh_auth_types & CURLSSH_AUTH_HOST) && @@ -986,7 +2074,7 @@ CURLcode Curl_ssh_connect(struct connectdata *conn, bool *done) strlen(ssh->user), &kbd_callback)) { authed = TRUE; - infof(conn->data, "Initialized keyboard interactive authentication\n"); + infof(data, "Initialized keyboard interactive authentication\n"); } } Curl_safefree((void *)authlist); @@ -1002,7 +2090,7 @@ CURLcode Curl_ssh_connect(struct connectdata *conn, bool *done) /* * At this point we have an authenticated ssh session. */ - infof(conn->data, "Authentication complete\n"); + infof(data, "Authentication complete\n"); conn->sockfd = sock; conn->writesockfd = CURL_SOCKET_BAD; @@ -1115,8 +2203,104 @@ CURLcode Curl_ssh_connect(struct connectdata *conn, bool *done) #endif /* !(LIBSSH2_APINO >= 200706012030) */ } +#if (LIBSSH2_APINO >= 200706012030) + +/* + *********************************************************************** + * + * 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); + + /* run the state-machine */ + if (conn->data->state.used_interface == Curl_if_multi) { + result = Curl_ssh_multi_statemach(conn, dophase_done); + } else { + result = ssh_easy_statemach(conn); + *dophase_done = TRUE; /* with the easy interface we are done here */ + } + *connected = conn->bits.tcpconnect; + + if (*dophase_done) { + DEBUGF(infof(conn->data, "DO phase is complete\n")); + } + + return result; +} + +/* called from multi.c while DOing */ +CURLcode Curl_scp_doing(struct connectdata *conn, + bool *dophase_done) +{ + CURLcode result; + result = Curl_ssh_multi_statemach(conn, dophase_done); + + if (*dophase_done) { + DEBUGF(infof(conn->data, "DO phase is complete\n")); + } + return result; +} + +#endif /* !(LIBSSH2_APINO >= 200706012030) */ + CURLcode Curl_scp_do(struct connectdata *conn, bool *done) { +#if (LIBSSH2_APINO >= 200706012030) + CURLcode res; + bool connected = 0; + struct SessionHandle *data = conn->data; + + *done = FALSE; /* default to false */ + + /* + * 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; + } + + data->reqdata.size = -1; /* make sure this is unknown at this point */ + + Curl_pgrsSetUploadCounter(data, 0); + Curl_pgrsSetDownloadCounter(data, 0); + Curl_pgrsSetUploadSize(data, 0); + Curl_pgrsSetDownloadSize(data, 0); + + res = scp_perform(conn, &connected, done); + + if (CURLE_OK == res) { + + if (!done) { + /* the DO phase has not completed yet */ + return CURLE_OK; + } + } + + return res; + +#else /* !(LIBSSH2_APINO >= 200706012030) */ + struct stat sb; struct SSHPROTO *scp = conn->data->reqdata.proto.ssh; CURLcode res = CURLE_OK; @@ -1124,7 +2308,7 @@ CURLcode Curl_scp_do(struct connectdata *conn, bool *done) *done = TRUE; /* unconditionally */ if (conn->data->set.upload) { - if(conn->data->set.infilesize < 0) { + if (conn->data->set.infilesize < 0) { failf(conn->data, "SCP requires a known file size for upload"); return CURLE_UPLOAD_FAILED; } @@ -1134,57 +2318,21 @@ CURLcode Curl_scp_do(struct connectdata *conn, bool *done) * the destination file will be named the same name as the last directory * in the path. */ -#if (LIBSSH2_APINO >= 200706012030) - do { - scp->ssh_channel = libssh2_scp_send_ex(scp->ssh_session, scp->path, - conn->data->set.new_file_perms, - conn->data->set.infilesize, 0, 0); - if (!scp->ssh_channel && - (libssh2_session_last_errno(scp->ssh_session) != - LIBSSH2_ERROR_EAGAIN)) { - int err; - char *err_msg; - - err = libssh2_session_error_to_CURLE( - libssh2_session_last_error(scp->ssh_session, &err_msg, NULL, 0)); - failf(conn->data, "%s", err_msg); - return err; - } - } while (!scp->ssh_channel); -#else /* !(LIBSSH2_APINO >= 200706012030) */ scp->ssh_channel = libssh2_scp_send_ex(scp->ssh_session, scp->path, conn->data->set.new_file_perms, conn->data->set.infilesize, 0, 0); if (!scp->ssh_channel) return CURLE_FAILED_INIT; -#endif /* !(LIBSSH2_APINO >= 200706012030) */ /* upload data */ res = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, FIRSTSOCKET, NULL); - } - else { + } else { /* * We must check the remote file; if it is a directory no values will * be set in sb */ curl_off_t bytecount; memset(&sb, 0, sizeof(struct stat)); -#if (LIBSSH2_APINO >= 200706012030) - do { - scp->ssh_channel = libssh2_scp_recv(scp->ssh_session, scp->path, &sb); - if (!scp->ssh_channel && - (libssh2_session_last_errno(scp->ssh_session) != - LIBSSH2_ERROR_EAGAIN)) { - if ((sb.st_mode == 0) && (sb.st_atime == 0) && (sb.st_mtime == 0) && - (sb.st_size == 0)) { - /* Since sb is still empty, it is likely the file was not found */ - return CURLE_REMOTE_FILE_NOT_FOUND; - } - return libssh2_session_error_to_CURLE( - libssh2_session_last_error(scp->ssh_session, NULL, NULL, 0)); - } - } while (!scp->ssh_channel); -#else /* !(LIBSSH2_APINO >= 200706012030) */ scp->ssh_channel = libssh2_scp_recv(scp->ssh_session, scp->path, &sb); if (!scp->ssh_channel) { if ((sb.st_mode == 0) && (sb.st_atime == 0) && (sb.st_mtime == 0) && @@ -1195,7 +2343,6 @@ CURLcode Curl_scp_do(struct connectdata *conn, bool *done) return libssh2_session_error_to_CURLE( libssh2_session_last_error(scp->ssh_session, NULL, NULL, 0)); } -#endif /* !(LIBSSH2_APINO >= 200706012030) */ /* download data */ bytecount = (curl_off_t) sb.st_size; conn->data->reqdata.maxdownload = (curl_off_t) sb.st_size; @@ -1204,38 +2351,45 @@ CURLcode Curl_scp_do(struct connectdata *conn, bool *done) } return res; +#endif /* !(LIBSSH2_APINO >= 200706012030) */ } CURLcode Curl_scp_done(struct connectdata *conn, CURLcode status, bool premature) { - int rc; +#if (LIBSSH2_APINO >= 200706012030) + CURLcode result = CURLE_OK; + bool done = FALSE; + + if (status == CURLE_OK) { + 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); + } else { + result = ssh_easy_statemach(conn); + done = TRUE; + } + } else { + result = status; + done = TRUE; + } + + if (done) { + Curl_safefree(conn->data->reqdata.proto.ssh); + conn->data->reqdata.proto.ssh = NULL; + Curl_pgrsDone(conn); + } + + return result; +#else /* !(LIBSSH2_APINO >= 200706012030) */ + struct SSHPROTO *scp = conn->data->reqdata.proto.ssh; - (void)premature; /* not used */ Curl_safefree(scp->path); scp->path = NULL; if (scp->ssh_channel) { -#if (LIBSSH2_APINO >= 200706012030) - if (conn->data->set.upload) { - while ((rc = libssh2_channel_send_eof(scp->ssh_channel)) == - LIBSSH2_ERROR_EAGAIN); - if (rc) { - infof(conn->data, "Failed to send libssh2 channel EOF\n"); - } - while ((rc = libssh2_channel_wait_eof(scp->ssh_channel)) == - LIBSSH2_ERROR_EAGAIN); - if (rc) { - infof(conn->data, "Failed to get channel EOF\n"); - } - while ((rc = libssh2_channel_wait_closed(scp->ssh_channel)) == - LIBSSH2_ERROR_EAGAIN); - if (rc) { - infof(conn->data, "Channel failed to close\n"); - } - } -#else /* !(LIBSSH2_APINO >= 200706012030) */ if (conn->data->set.upload && libssh2_channel_send_eof(scp->ssh_channel) < 0) { infof(conn->data, "Failed to send libssh2 channel EOF\n"); @@ -1243,29 +2397,24 @@ CURLcode Curl_scp_done(struct connectdata *conn, CURLcode status, if (libssh2_channel_close(scp->ssh_channel) < 0) { infof(conn->data, "Failed to stop libssh2 channel subsystem\n"); } -#endif /* !(LIBSSH2_APINO >= 200706012030) */ libssh2_channel_free(scp->ssh_channel); } if (scp->ssh_session) { -#if (LIBSSH2_APINO >= 200706012030) - while (libssh2_session_disconnect(scp->ssh_session, "Shutdown") == - LIBSSH2_ERROR_EAGAIN); -#else /* !(LIBSSH2_APINO >= 200706012030) */ libssh2_session_disconnect(scp->ssh_session, "Shutdown"); -#endif /* !(LIBSSH2_APINO >= 200706012030) */ libssh2_session_free(scp->ssh_session); scp->ssh_session = NULL; } - free(conn->data->reqdata.proto.ssh); + Curl_safefree(conn->data->reqdata.proto.ssh); conn->data->reqdata.proto.ssh = NULL; Curl_pgrsDone(conn); - (void)status; /* unused */ - (void) rc; /* possiby unused */ - return CURLE_OK; +#endif /* !(LIBSSH2_APINO >= 200706012030) */ + (void)premature; /* not used */ + (void)status; /* unused */ + } /* return number of received (decrypted) bytes */ @@ -1330,8 +2479,104 @@ ssize_t Curl_scp_recv(struct connectdata *conn, int sockindex, * =============== SFTP =============== */ +#if (LIBSSH2_APINO >= 200706012030) + +/* + *********************************************************************** + * + * sftp_perform() + * + * This is the actual DO function for SFTP. Get a file/directory according to + * the options previously setup. + */ + +static +CURLcode sftp_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_SFTP_QUOTE_INIT); + + /* run the state-machine */ + if (conn->data->state.used_interface == Curl_if_multi) { + result = Curl_ssh_multi_statemach(conn, dophase_done); + } else { + result = ssh_easy_statemach(conn); + *dophase_done = TRUE; /* with the easy interface we are done here */ + } + *connected = conn->bits.tcpconnect; + + if (*dophase_done) { + DEBUGF(infof(conn->data, "DO phase is complete\n")); + } + + return result; +} + +/* called from multi.c while DOing */ +CURLcode Curl_sftp_doing(struct connectdata *conn, + bool *dophase_done) +{ + CURLcode result; + result = Curl_ssh_multi_statemach(conn, dophase_done); + + if (*dophase_done) { + DEBUGF(infof(conn->data, "DO phase is complete\n")); + } + return result; +} + +#endif /* !(LIBSSH2_APINO >= 200706012030) */ + CURLcode Curl_sftp_do(struct connectdata *conn, bool *done) { +#if (LIBSSH2_APINO >= 200706012030) + CURLcode res; + bool connected = 0; + struct SessionHandle *data = conn->data; + + *done = FALSE; /* default to false */ + + /* + * 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; + } + + data->reqdata.size = -1; /* make sure this is unknown at this point */ + + Curl_pgrsSetUploadCounter(data, 0); + Curl_pgrsSetDownloadCounter(data, 0); + Curl_pgrsSetUploadSize(data, 0); + Curl_pgrsSetDownloadSize(data, 0); + + res = sftp_perform(conn, &connected, done); + + if (CURLE_OK == res) { + + if (!done) { + /* the DO phase has not completed yet */ + return CURLE_OK; + } + } + + return res; + +#else /* !(LIBSSH2_APINO >= 200706012030) */ + LIBSSH2_SFTP_ATTRIBUTES attrs; struct SSHPROTO *sftp = conn->data->reqdata.proto.ssh; CURLcode res = CURLE_OK; @@ -1344,7 +2589,7 @@ CURLcode Curl_sftp_do(struct connectdata *conn, bool *done) *done = TRUE; /* unconditionally */ /* Send any quote commands */ - if(conn->data->set.quote) { + if (conn->data->set.quote) { infof(conn->data, "Sending quote commands\n"); res = sftp_sendquote(conn, conn->data->set.quote); if (res != CURLE_OK) @@ -1358,53 +2603,10 @@ CURLcode Curl_sftp_do(struct connectdata *conn, bool *done) * If this is not done the destination file will be named the * same name as the last directory in the path. */ -#if (LIBSSH2_APINO >= 200706012030) - do { - sftp->sftp_handle = - libssh2_sftp_open(sftp->sftp_session, sftp->path, - LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC, - data->set.new_file_perms); - if (!sftp->sftp_handle && - (libssh2_session_last_errno(sftp->ssh_session) != - LIBSSH2_ERROR_EAGAIN)) { - err = libssh2_sftp_last_error(sftp->sftp_session); - if (((err == LIBSSH2_FX_NO_SUCH_FILE) || - (err == LIBSSH2_FX_FAILURE) || - (err == LIBSSH2_FX_NO_SUCH_PATH)) && - (conn->data->set.ftp_create_missing_dirs && - (strlen(sftp->path) > 1))) { - /* try to create the path remotely */ - res = sftp_create_dirs(conn); - if (res == 0) { - do { - sftp->sftp_handle = libssh2_sftp_open(sftp->sftp_session, - sftp->path, - LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC, - data->set.new_file_perms); - if (!sftp->sftp_handle && - (libssh2_session_last_errno(sftp->ssh_session) != - LIBSSH2_ERROR_EAGAIN)) { - err = libssh2_sftp_last_error(sftp->sftp_session); - failf(conn->data, "Could not open remote file for writing: %s", - sftp_libssh2_strerror(err)); - return sftp_libssh2_error_to_CURLE(err); - } - } while (!sftp->sftp_handle); - } - } - if (!sftp->sftp_handle) { - err = libssh2_sftp_last_error(sftp->sftp_session); - failf(conn->data, "Could not open remote file for writing: %s", - sftp_libssh2_strerror(err)); - return sftp_libssh2_error_to_CURLE(err); - } - } - } while (!sftp->sftp_handle); -#else /* !(LIBSSH2_APINO >= 200706012030) */ sftp->sftp_handle = libssh2_sftp_open(sftp->sftp_session, sftp->path, LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC, - data->set.new_file_perms); + conn->data->set.new_file_perms); if (!sftp->sftp_handle) { err = libssh2_sftp_last_error(sftp->sftp_session); if (((err == LIBSSH2_FX_NO_SUCH_FILE) || @@ -1417,7 +2619,7 @@ CURLcode Curl_sftp_do(struct connectdata *conn, bool *done) if (res == 0) { sftp->sftp_handle = libssh2_sftp_open(sftp->sftp_session, sftp->path, LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC, - data->set.new_file_perms); + conn->data->set.new_file_perms); } } if (!sftp->sftp_handle) { @@ -1427,7 +2629,6 @@ CURLcode Curl_sftp_do(struct connectdata *conn, bool *done) return sftp_libssh2_error_to_CURLE(err); } } -#endif /* !(LIBSSH2_APINO >= 200706012030) */ /* upload data */ res = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, FIRSTSOCKET, NULL); @@ -1446,20 +2647,6 @@ CURLcode Curl_sftp_do(struct connectdata *conn, bool *done) int len, totalLen, currLen; char *line; -#if (LIBSSH2_APINO >= 200706012030) - do { - sftp->sftp_handle = - libssh2_sftp_opendir(sftp->sftp_session, sftp->path); - if (!sftp->sftp_handle && - (libssh2_session_last_errno(sftp->ssh_session) != - LIBSSH2_ERROR_EAGAIN)) { - err = libssh2_sftp_last_error(sftp->sftp_session); - failf(conn->data, "Could not open directory for reading: %s", - sftp_libssh2_strerror(err)); - return sftp_libssh2_error_to_CURLE(err); - } - } while (!sftp->sftp_handle); -#else /* !(LIBSSH2_APINO >= 200706012030) */ sftp->sftp_handle = libssh2_sftp_opendir(sftp->sftp_session, sftp->path); if (!sftp->sftp_handle) { @@ -1468,17 +2655,10 @@ CURLcode Curl_sftp_do(struct connectdata *conn, bool *done) sftp_libssh2_strerror(err)); return sftp_libssh2_error_to_CURLE(err); } -#endif /* !(LIBSSH2_APINO >= 200706012030) */ do { -#if (LIBSSH2_APINO >= 200706012030) - while ((len = libssh2_sftp_readdir(sftp->sftp_handle, filename, - PATH_MAX, &attrs)) == - LIBSSH2_ERROR_EAGAIN); -#else /* !(LIBSSH2_APINO >= 200706012030) */ len = libssh2_sftp_readdir(sftp->sftp_handle, filename, PATH_MAX, &attrs); -#endif /* !(LIBSSH2_APINO >= 200706012030) */ if (len > 0) { filename[len] = '\0'; @@ -1491,8 +2671,7 @@ CURLcode Curl_sftp_do(struct connectdata *conn, bool *done) } res = Curl_client_write(conn, CLIENTWRITE_BODY, tmpLine, 0); Curl_safefree(tmpLine); - } - else { + } else { totalLen = 80 + len; line = (char *)malloc(totalLen); if (!line) @@ -1589,14 +2768,8 @@ CURLcode Curl_sftp_do(struct connectdata *conn, bool *done) char linkPath[PATH_MAX + 1]; snprintf(linkPath, PATH_MAX, "%s%s", sftp->path, filename); -#if (LIBSSH2_APINO >= 200706012030) - while ((len = libssh2_sftp_readlink(sftp->sftp_session, linkPath, - filename, PATH_MAX)) == - LIBSSH2_ERROR_EAGAIN); -#else /* !(LIBSSH2_APINO >= 200706012030) */ len = libssh2_sftp_readlink(sftp->sftp_session, linkPath, filename, PATH_MAX); -#endif /* !(LIBSSH2_APINO >= 200706012030) */ line = realloc(line, totalLen + 4 + len); if (!line) return CURLE_OUT_OF_MEMORY; @@ -1607,18 +2780,14 @@ CURLcode Curl_sftp_do(struct connectdata *conn, bool *done) currLen += snprintf(line+currLen, totalLen-currLen, "\n"); res = Curl_client_write(conn, CLIENTWRITE_BODY, line, 0); - free(line); + Curl_safefree(line); } } else if (len <= 0) { break; } } while (1); -#if (LIBSSH2_APINO >= 200706012030) - while (libssh2_sftp_closedir(sftp->sftp_handle) == LIBSSH2_ERROR_EAGAIN); -#else /* !(LIBSSH2_APINO >= 200706012030) */ libssh2_sftp_closedir(sftp->sftp_handle); -#endif /* !(LIBSSH2_APINO >= 200706012030) */ sftp->sftp_handle = NULL; /* no data to transfer */ @@ -1628,38 +2797,17 @@ CURLcode Curl_sftp_do(struct connectdata *conn, bool *done) /* * Work on getting the specified file */ -#if (LIBSSH2_APINO >= 200706012030) - do { - sftp->sftp_handle = - libssh2_sftp_open(sftp->sftp_session, sftp->path, LIBSSH2_FXF_READ, - data->set.new_file_perms); - if (!sftp->sftp_handle && - (libssh2_session_last_errno(sftp->ssh_session) != - LIBSSH2_ERROR_EAGAIN)) { - err = libssh2_sftp_last_error(sftp->sftp_session); - failf(conn->data, "Could not open remote file for reading: %s", - sftp_libssh2_strerror(err)); - return sftp_libssh2_error_to_CURLE(err); - } - } while (!sftp->sftp_handle); -#else /* !(LIBSSH2_APINO >= 200706012030) */ sftp->sftp_handle = libssh2_sftp_open(sftp->sftp_session, sftp->path, LIBSSH2_FXF_READ, - data->set.new_file_perms); + conn->data->set.new_file_perms); if (!sftp->sftp_handle) { err = libssh2_sftp_last_error(sftp->sftp_session); failf(conn->data, "Could not open remote file for reading: %s", sftp_libssh2_strerror(err)); return sftp_libssh2_error_to_CURLE(err); } -#endif /* !(LIBSSH2_APINO >= 200706012030) */ -#if (LIBSSH2_APINO >= 200706012030) - while ((rc = libssh2_sftp_stat(sftp->sftp_session, sftp->path, &attrs)) - == LIBSSH2_ERROR_EAGAIN); -#else /* !(LIBSSH2_APINO >= 200706012030) */ rc = libssh2_sftp_stat(sftp->sftp_session, sftp->path, &attrs); -#endif /* !(LIBSSH2_APINO >= 200706012030) */ if (rc) { /* * libssh2_sftp_open() didn't return an error, so maybe the server @@ -1679,55 +2827,37 @@ CURLcode Curl_sftp_do(struct connectdata *conn, bool *done) /* Now download data. The libssh2 0.14 doesn't offer any way to do this without using this BLOCKING approach, so here's room for improvement once libssh2 can return EWOULDBLOCK to us. */ -#if 0 - /* code left here just because this is what this function will use the - day libssh2 is improved */ - res = Curl_setup_transfer(conn, FIRSTSOCKET, - bytecount, FALSE, NULL, -1, NULL); -#endif while (res == CURLE_OK) { -#if (LIBSSH2_APINO >= 200706012030) - ssize_t nread; - - while ((nread = libssh2_sftp_read(data->reqdata.proto.ssh->sftp_handle, - buf, BUFSIZE-1)) == LIBSSH2_ERROR_EAGAIN); -#else /* !(LIBSSH2_APINO >= 200706012030) */ size_t nread; /* NOTE: most *read() functions return ssize_t but this returns size_t which normally is unsigned! */ nread = libssh2_sftp_read(data->reqdata.proto.ssh->sftp_handle, buf, BUFSIZE-1); -#endif /* !(LIBSSH2_APINO >= 200706012030) */ if (nread > 0) buf[nread] = 0; -#if (LIBSSH2_APINO >= 200706012030) - if (nread <= 0) - break; -#else /* !(LIBSSH2_APINO >= 200706012030) */ /* this check can be changed to a <= 0 when nread is changed to a signed variable type */ if ((nread == 0) || (nread == (size_t)~0)) break; -#endif /* !(LIBSSH2_APINO >= 200706012030) */ bytecount += nread; res = Curl_client_write(conn, CLIENTWRITE_BODY, buf, nread); - if(res) + if (res) return res; Curl_pgrsSetDownloadCounter(data, bytecount); - if(Curl_pgrsUpdate(conn)) + if (Curl_pgrsUpdate(conn)) res = CURLE_ABORTED_BY_CALLBACK; else { struct timeval now = Curl_tvnow(); res = Curl_speedcheck(data, now); } } - if(Curl_pgrsUpdate(conn)) + if (Curl_pgrsUpdate(conn)) res = CURLE_ABORTED_BY_CALLBACK; /* no (more) data to transfer */ @@ -1736,15 +2866,54 @@ CURLcode Curl_sftp_do(struct connectdata *conn, bool *done) } return res; +#endif /* !(LIBSSH2_APINO >= 200706012030) */ } CURLcode Curl_sftp_done(struct connectdata *conn, CURLcode status, bool premature) { + CURLcode result = CURLE_OK; + +#if (LIBSSH2_APINO >= 200706012030) + bool done = FALSE; + struct ssh_conn *sshc = &conn->proto.sshc; + + 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; + state(conn, SSH_SFTP_POSTQUOTE_INIT); + } else { + state(conn, SSH_SFTP_CLOSE); + } + + /* run the state-machine */ + if (conn->data->state.used_interface == Curl_if_multi) { + result = Curl_ssh_multi_statemach(conn, &done); + } else { + result = ssh_easy_statemach(conn); + done = TRUE; + } + } else { + result = status; + done = TRUE; + } + + if (done) { + Curl_safefree(conn->data->reqdata.proto.ssh); + conn->data->reqdata.proto.ssh = NULL; + Curl_pgrsDone(conn); + } + +#else /* !(LIBSSH2_APINO >= 200706012030) */ + struct SSHPROTO *sftp = conn->data->reqdata.proto.ssh; - CURLcode rc = CURLE_OK; - int ret; - (void)premature; /* not used */ + + /* Before we shut down, see if there are any post-quote commands to send: */ + if (!status && !premature && conn->data->set.postquote) { + infof(conn->data, "Sending postquote commands\n"); + result = sftp_sendquote(conn, conn->data->set.postquote); + } Curl_safefree(sftp->path); sftp->path = NULL; @@ -1753,72 +2922,37 @@ CURLcode Curl_sftp_done(struct connectdata *conn, CURLcode status, sftp->homedir = NULL; if (sftp->sftp_handle) { -#if (LIBSSH2_APINO >= 200706012030) - while ((ret = libssh2_sftp_close(sftp->sftp_handle)) == - LIBSSH2_ERROR_EAGAIN); - if (ret < 0) { - infof(conn->data, "Failed to close libssh2 file\n"); - } -#else /* !(LIBSSH2_APINO >= 200706012030) */ if (libssh2_sftp_close(sftp->sftp_handle) < 0) { infof(conn->data, "Failed to close libssh2 file\n"); } -#endif /* !(LIBSSH2_APINO >= 200706012030) */ - } - - /* Before we shut down, see if there are any post-quote commands to send: */ - if(!status && !premature && conn->data->set.postquote) { - infof(conn->data, "Sending postquote commands\n"); - rc = sftp_sendquote(conn, conn->data->set.postquote); } if (sftp->sftp_session) { -#if (LIBSSH2_APINO >= 200706012030) - while ((ret = libssh2_sftp_shutdown(sftp->sftp_session)) == - LIBSSH2_ERROR_EAGAIN); - if (ret < 0) { - infof(conn->data, "Failed to stop libssh2 sftp subsystem\n"); - } -#else /* !(LIBSSH2_APINO >= 200706012030) */ if (libssh2_sftp_shutdown(sftp->sftp_session) < 0) { infof(conn->data, "Failed to stop libssh2 sftp subsystem\n"); } -#endif /* !(LIBSSH2_APINO >= 200706012030) */ } if (sftp->ssh_channel) { -#if (LIBSSH2_APINO >= 200706012030) - while ((ret = libssh2_channel_close(sftp->ssh_channel)) == - LIBSSH2_ERROR_EAGAIN); - if (ret < 0) { - infof(conn->data, "Failed to stop libssh2 channel subsystem\n"); - } -#else /* !(LIBSSH2_APINO >= 200706012030) */ if (libssh2_channel_close(sftp->ssh_channel) < 0) { infof(conn->data, "Failed to stop libssh2 channel subsystem\n"); } -#endif /* !(LIBSSH2_APINO >= 200706012030) */ } if (sftp->ssh_session) { -#if (LIBSSH2_APINO >= 200706012030) - while (libssh2_session_disconnect(sftp->ssh_session, "Shutdown") == - LIBSSH2_ERROR_EAGAIN); -#else /* !(LIBSSH2_APINO >= 200706012030) */ libssh2_session_disconnect(sftp->ssh_session, "Shutdown"); -#endif /* !(LIBSSH2_APINO >= 200706012030) */ libssh2_session_free(sftp->ssh_session); sftp->ssh_session = NULL; } - free(conn->data->reqdata.proto.ssh); + Curl_safefree(conn->data->reqdata.proto.ssh); conn->data->reqdata.proto.ssh = NULL; Curl_pgrsDone(conn); +#endif /* !(LIBSSH2_APINO >= 200706012030) */ (void)status; /* unused */ - (void)ret; /* possibly unused */ - return rc; + return result; } /* return number of received (decrypted) bytes */ @@ -1950,7 +3084,7 @@ get_pathname(const char **cpp, char **path) return (0); fail: - free(*path); + Curl_safefree(*path); *path = NULL; return CURLE_FTP_QUOTE_ERROR; } @@ -1959,55 +3093,75 @@ get_pathname(const char **cpp, char **path) static const char *sftp_libssh2_strerror(unsigned long err) { switch (err) { - case LIBSSH2_FX_NO_SUCH_FILE: - return "No such file or directory"; - case LIBSSH2_FX_PERMISSION_DENIED: - return "Permission denied"; - case LIBSSH2_FX_FAILURE: - return "Operation failed"; - case LIBSSH2_FX_BAD_MESSAGE: - return "Bad message from SFTP server"; - case LIBSSH2_FX_NO_CONNECTION: - return "Not connected to SFTP server"; - case LIBSSH2_FX_CONNECTION_LOST: - return "Connection to SFTP server lost"; - case LIBSSH2_FX_OP_UNSUPPORTED: - return "Operation not supported by SFTP server"; - case LIBSSH2_FX_INVALID_HANDLE: - return "Invalid handle"; - case LIBSSH2_FX_NO_SUCH_PATH: - return "No such file or directory"; - case LIBSSH2_FX_FILE_ALREADY_EXISTS: - return "File already exists"; - case LIBSSH2_FX_WRITE_PROTECT: - return "File is write protected"; - case LIBSSH2_FX_NO_MEDIA: - return "No media"; - case LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM: - return "Disk full"; - case LIBSSH2_FX_QUOTA_EXCEEDED: - return "User quota exceeded"; - case LIBSSH2_FX_UNKNOWN_PRINCIPLE: - return "Unknown principle"; - case LIBSSH2_FX_LOCK_CONFlICT: - return "File lock conflict"; - case LIBSSH2_FX_DIR_NOT_EMPTY: - return "Directory not empty"; - case LIBSSH2_FX_NOT_A_DIRECTORY: - return "Not a directory"; - case LIBSSH2_FX_INVALID_FILENAME: - return "Invalid filename"; - case LIBSSH2_FX_LINK_LOOP: - return "Link points to itself"; + case LIBSSH2_FX_NO_SUCH_FILE: + return "No such file or directory"; + + case LIBSSH2_FX_PERMISSION_DENIED: + return "Permission denied"; + + case LIBSSH2_FX_FAILURE: + return "Operation failed"; + + case LIBSSH2_FX_BAD_MESSAGE: + return "Bad message from SFTP server"; + + case LIBSSH2_FX_NO_CONNECTION: + return "Not connected to SFTP server"; + + case LIBSSH2_FX_CONNECTION_LOST: + return "Connection to SFTP server lost"; + + case LIBSSH2_FX_OP_UNSUPPORTED: + return "Operation not supported by SFTP server"; + + case LIBSSH2_FX_INVALID_HANDLE: + return "Invalid handle"; + + case LIBSSH2_FX_NO_SUCH_PATH: + return "No such file or directory"; + + case LIBSSH2_FX_FILE_ALREADY_EXISTS: + return "File already exists"; + + case LIBSSH2_FX_WRITE_PROTECT: + return "File is write protected"; + + case LIBSSH2_FX_NO_MEDIA: + return "No media"; + + case LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM: + return "Disk full"; + + case LIBSSH2_FX_QUOTA_EXCEEDED: + return "User quota exceeded"; + + case LIBSSH2_FX_UNKNOWN_PRINCIPLE: + return "Unknown principle"; + + case LIBSSH2_FX_LOCK_CONFlICT: + return "File lock conflict"; + + case LIBSSH2_FX_DIR_NOT_EMPTY: + return "Directory not empty"; + + case LIBSSH2_FX_NOT_A_DIRECTORY: + return "Not a directory"; + + case LIBSSH2_FX_INVALID_FILENAME: + return "Invalid filename"; + + case LIBSSH2_FX_LINK_LOOP: + return "Link points to itself"; } return "Unknown error in libssh2"; } +#if (LIBSSH2_APINO < 200706012030) /* BLOCKING */ static CURLcode sftp_sendquote(struct connectdata *conn, struct curl_slist *quote) { - struct curl_slist *item=quote; + struct curl_slist *item = quote; const char *cp; long err; struct SessionHandle *data = conn->data; @@ -2015,7 +3169,21 @@ static CURLcode sftp_sendquote(struct connectdata *conn, int ret; while (item) { - if (item->data) { + /* + * Support some of the "FTP" commands + */ + if (curl_strnequal(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", + data->reqdata.proto.ssh->path); + Curl_debug(data, CURLINFO_HEADER_IN, tmp, strlen(tmp), conn); + } + } + else if (item->data) { char *path1 = NULL; char *path2 = NULL; @@ -2054,40 +3222,26 @@ static CURLcode sftp_sendquote(struct connectdata *conn, else failf(data, "Syntax error in chgrp/chmod/chown: Bad second parameter"); - free(path1); + Curl_safefree(path1); return err; } memset(&attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); -#if (LIBSSH2_APINO >= 200706012030) - while ((ret = libssh2_sftp_stat(sftp_session, - path2, &attrs)) == - LIBSSH2_ERROR_EAGAIN); - if (ret != 0) { /* get those attributes */ - err = libssh2_sftp_last_error(sftp_session); - free(path1); - free(path2); - failf(data, "Attempt to get SFTP stats failed: %s", - sftp_libssh2_strerror(err)); - return CURLE_FTP_QUOTE_ERROR; - } -#else /* !(LIBSSH2_APINO >= 200706012030) */ if (libssh2_sftp_stat(sftp_session, path2, &attrs) != 0) { /* get those attributes */ err = libssh2_sftp_last_error(sftp_session); - free(path1); - free(path2); + Curl_safefree(path1); + Curl_safefree(path2); failf(data, "Attempt to get SFTP stats failed: %s", sftp_libssh2_strerror(err)); return CURLE_FTP_QUOTE_ERROR; } -#endif /* !(LIBSSH2_APINO >= 200706012030) */ /* Now set the new attributes... */ if (curl_strnequal(item->data, "chgrp", 5)) { attrs.gid = strtol(path1, NULL, 10); if (attrs.gid == 0 && !ISDIGIT(path1[0])) { - free(path1); - free(path2); + Curl_safefree(path1); + Curl_safefree(path2); failf(data, "Syntax error: chgrp gid not a number"); return CURLE_FTP_QUOTE_ERROR; } @@ -2095,8 +3249,8 @@ static CURLcode sftp_sendquote(struct connectdata *conn, else if (curl_strnequal(item->data, "chmod", 5)) { attrs.permissions = strtol(path1, NULL, 8);/* permissions are octal */ if (attrs.permissions == 0 && !ISDIGIT(path1[0])) { - free(path1); - free(path2); + Curl_safefree(path1); + Curl_safefree(path2); failf(data, "Syntax error: chmod permissions not a number"); return CURLE_FTP_QUOTE_ERROR; } @@ -2104,35 +3258,22 @@ static CURLcode sftp_sendquote(struct connectdata *conn, else if (curl_strnequal(item->data, "chown", 5)) { attrs.uid = strtol(path1, NULL, 10); if (attrs.uid == 0 && !ISDIGIT(path1[0])) { - free(path1); - free(path2); + Curl_safefree(path1); + Curl_safefree(path2); failf(data, "Syntax error: chown uid not a number"); return CURLE_FTP_QUOTE_ERROR; } } /* Now send the completed structure... */ -#if (LIBSSH2_APINO >= 200706012030) - while ((ret = libssh2_sftp_setstat(sftp_session, path2, &attrs)) == - LIBSSH2_ERROR_EAGAIN); - if (ret != 0) { - err = libssh2_sftp_last_error(sftp_session); - free(path1); - free(path2); - failf(data, "Attempt to set SFTP stats failed: %s", - sftp_libssh2_strerror(err)); - return CURLE_FTP_QUOTE_ERROR; - } -#else /* !(LIBSSH2_APINO >= 200706012030) */ if (libssh2_sftp_setstat(sftp_session, path2, &attrs) != 0) { err = libssh2_sftp_last_error(sftp_session); - free(path1); - free(path2); + Curl_safefree(path1); + Curl_safefree(path2); failf(data, "Attempt to set SFTP stats failed: %s", sftp_libssh2_strerror(err)); return CURLE_FTP_QUOTE_ERROR; } -#endif /* !(LIBSSH2_APINO >= 200706012030) */ } else if (curl_strnequal(item->data, "ln ", 3) || curl_strnequal(item->data, "symlink ", 8)) { @@ -2145,50 +3286,26 @@ static CURLcode sftp_sendquote(struct connectdata *conn, else failf(data, "Syntax error in ln/symlink: Bad second parameter"); - free(path1); + Curl_safefree(path1); return err; } -#if (LIBSSH2_APINO >= 200706012030) - while ((ret = libssh2_sftp_symlink(sftp_session, path1, path2)) == - LIBSSH2_ERROR_EAGAIN); - if (ret != 0) { - err = libssh2_sftp_last_error(sftp_session); - free(path1); - free(path2); - failf(data, "symlink command failed: %s", - sftp_libssh2_strerror(err)); - return CURLE_FTP_QUOTE_ERROR; - } -#else /* !(LIBSSH2_APINO >= 200706012030) */ if (libssh2_sftp_symlink(sftp_session, path1, path2) != 0) { err = libssh2_sftp_last_error(sftp_session); - free(path1); - free(path2); + Curl_safefree(path1); + Curl_safefree(path2); failf(data, "symlink command failed: %s", sftp_libssh2_strerror(err)); return CURLE_FTP_QUOTE_ERROR; } -#endif /* !(LIBSSH2_APINO >= 200706012030) */ } else if (curl_strnequal(item->data, "mkdir ", 6)) { /* create dir */ -#if (LIBSSH2_APINO >= 200706012030) - while ((ret = libssh2_sftp_mkdir(sftp_session, path1, 0744)) == - LIBSSH2_ERROR_EAGAIN); - if (ret != 0) { - err = libssh2_sftp_last_error(sftp_session); - free(path1); - failf(data, "mkdir command failed: %s", sftp_libssh2_strerror(err)); - return CURLE_FTP_QUOTE_ERROR; - } -#else /* !(LIBSSH2_APINO >= 200706012030) */ if (libssh2_sftp_mkdir(sftp_session, path1, 0744) != 0) { err = libssh2_sftp_last_error(sftp_session); - free(path1); + Curl_safefree(path1); failf(data, "mkdir command failed: %s", sftp_libssh2_strerror(err)); return CURLE_FTP_QUOTE_ERROR; } -#endif /* !(LIBSSH2_APINO >= 200706012030) */ } else if (curl_strnequal(item->data, "rename ", 7)) { /* rename file */ /* first param is the source path */ @@ -2199,77 +3316,42 @@ static CURLcode sftp_sendquote(struct connectdata *conn, else failf(data, "Syntax error in rename: Bad second parameter"); - free(path1); + Curl_safefree(path1); return err; } -#if (LIBSSH2_APINO >= 200706012030) - while ((ret = libssh2_sftp_rename(sftp_session, path1, path2)) == - LIBSSH2_ERROR_EAGAIN); - if (ret != 0) { + if (libssh2_sftp_rename(sftp_session, path1, path2) != 0) { err = libssh2_sftp_last_error(sftp_session); - free(path1); - free(path2); - failf(data, "rename command failed: %s", sftp_libssh2_strerror(err)); - return CURLE_FTP_QUOTE_ERROR; - } -#else /* !(LIBSSH2_APINO >= 200706012030) */ - if (libssh2_sftp_rename(sftp_session, - path1, path2) != 0) { - err = libssh2_sftp_last_error(sftp_session); - free(path1); - free(path2); + Curl_safefree(path1); + Curl_safefree(path2); failf(data, "rename command failed: %s", sftp_libssh2_strerror(err)); return CURLE_FTP_QUOTE_ERROR; } -#endif /* !(LIBSSH2_APINO >= 200706012030) */ } else if (curl_strnequal(item->data, "rmdir ", 6)) { /* delete dir */ -#if (LIBSSH2_APINO >= 200706012030) - while ((ret = libssh2_sftp_rmdir(sftp_session, path1)) == - LIBSSH2_ERROR_EAGAIN); - if (ret != 0) { - err = libssh2_sftp_last_error(sftp_session); - free(path1); - failf(data, "rmdir command failed: %s", sftp_libssh2_strerror(err)); - return CURLE_FTP_QUOTE_ERROR; - } -#else /* !(LIBSSH2_APINO >= 200706012030) */ if (libssh2_sftp_rmdir(sftp_session, path1) != 0) { err = libssh2_sftp_last_error(sftp_session); - free(path1); + Curl_safefree(path1); failf(data, "rmdir command failed: %s", sftp_libssh2_strerror(err)); return CURLE_FTP_QUOTE_ERROR; } -#endif /* !(LIBSSH2_APINO >= 200706012030) */ } else if (curl_strnequal(item->data, "rm ", 3)) { /* delete file */ -#if (LIBSSH2_APINO >= 200706012030) - while ((ret = libssh2_sftp_unlink(sftp_session, path1)) == - LIBSSH2_ERROR_EAGAIN); - if (ret != 0) { - err = libssh2_sftp_last_error(sftp_session); - free(path1); - failf(data, "rm command failed: %s", sftp_libssh2_strerror(err)); - return CURLE_FTP_QUOTE_ERROR; - } -#else /* !(LIBSSH2_APINO >= 200706012030) */ if (libssh2_sftp_unlink(sftp_session, path1) != 0) { err = libssh2_sftp_last_error(sftp_session); - free(path1); + Curl_safefree(path1); failf(data, "rm command failed: %s", sftp_libssh2_strerror(err)); return CURLE_FTP_QUOTE_ERROR; } -#endif /* !(LIBSSH2_APINO >= 200706012030) */ } if (path1) - free(path1); + Curl_safefree(path1); if (path2) - free(path2); + Curl_safefree(path2); } item = item->next; } @@ -2281,6 +3363,10 @@ static CURLcode sftp_sendquote(struct connectdata *conn, /* * Create the path sftp->path on the remote site * returns CURL_OK on success, -1 on failure + * + * NOTE: This version of sftp_create_dirs() is only used when + * LIBSSH2_APINO < 200706012030. After that version the code is in + * ssh_statemachine_act() */ static CURLcode sftp_create_dirs(struct connectdata *conn) { CURLcode result = CURLE_OK; @@ -2296,14 +3382,8 @@ static CURLcode sftp_create_dirs(struct connectdata *conn) { infof(conn->data, "Creating directory '%s'\n", sftp->path); /* 'mode' - parameter is preliminary - default to 0644 */ -#if (LIBSSH2_APINO >= 200706012030) - while ((rc = libssh2_sftp_mkdir(sftp->sftp_session, sftp->path, - conn->data->set.new_directory_perms)) == - LIBSSH2_ERROR_EAGAIN); -#else /* !(LIBSSH2_APINO >= 200706012030) */ rc = libssh2_sftp_mkdir(sftp->sftp_session, sftp->path, conn->data->set.new_directory_perms); -#endif /* !(LIBSSH2_APINO >= 200706012030) */ *slash_pos = '/'; ++slash_pos; if (rc == -1) { @@ -2323,5 +3403,6 @@ static CURLcode sftp_create_dirs(struct connectdata *conn) { } return result; } +#endif /* !(LIBSSH2_APINO < 200706012030) */ #endif /* USE_LIBSSH2 */ diff --git a/lib/ssh.h b/lib/ssh.h index d83e013f2..8e5c4718c 100644 --- a/lib/ssh.h +++ b/lib/ssh.h @@ -45,6 +45,13 @@ ssize_t Curl_sftp_send(struct connectdata *conn, int sockindex, ssize_t Curl_sftp_recv(struct connectdata *conn, int sockindex, char *mem, size_t len); +#if (LIBSSH2_APINO >= 200706012030) +CURLcode Curl_sftp_doing(struct connectdata *conn, + bool *dophase_done); +CURLcode Curl_scp_doing(struct connectdata *conn, + bool *dophase_done); +#endif (LIBSSH2_APINO >= 200706012030) + #endif /* USE_LIBSSH2 */ #endif /* __SSH_H */ diff --git a/lib/transfer.c b/lib/transfer.c index 045818a81..c7f4a2d5b 100644 --- a/lib/transfer.c +++ b/lib/transfer.c @@ -330,9 +330,13 @@ CURLcode Curl_readwrite(struct connectdata *conn, /* only use the proper socket if the *_HOLD bit is not set simultaneously as then we are in rate limiting state in that transfer direction */ - if((k->keepon & (KEEP_READ|KEEP_READ_HOLD)) == KEEP_READ) + if((k->keepon & (KEEP_READ|KEEP_READ_HOLD)) == KEEP_READ) { fd_read = conn->sockfd; - else +#if defined(USE_LIBSSH2) && (LIBSSH2_APINO >= 200706012030) + if (conn->protocol & (PROT_SCP|PROT_SFTP)) + select_res |= CURL_CSELECT_IN; +#endif /* USE_LIBSSH2 && (LIBSSH2_APINO >= 200706012030) */ + } else fd_read = CURL_SOCKET_BAD; if((k->keepon & (KEEP_WRITE|KEEP_WRITE_HOLD)) == KEEP_WRITE) @@ -340,11 +344,11 @@ CURLcode Curl_readwrite(struct connectdata *conn, else fd_write = CURL_SOCKET_BAD; - if (!select_res) { /* Call for select()/poll() only, if read/write/error + if (!select_res) { /* Call for select()/poll() only, if read/write/error status is not known. */ select_res = Curl_socket_ready(fd_read, fd_write, 0); } - + if(select_res == CURL_CSELECT_ERR) { failf(data, "select/poll returned error"); return CURLE_SEND_ERROR; diff --git a/lib/url.c b/lib/url.c index aa2aafe09..dc535c9cd 100644 --- a/lib/url.c +++ b/lib/url.c @@ -3361,6 +3361,10 @@ static CURLcode CreateConnection(struct SessionHandle *data, conn->curl_connect = Curl_ssh_connect; /* ssh_connect? */ conn->curl_do = Curl_scp_do; conn->curl_done = Curl_scp_done; +#if (LIBSSH2_APINO >= 200706012030) + conn->curl_connecting = Curl_ssh_multi_statemach; + conn->curl_doing = Curl_scp_doing; +#endif (LIBSSH2_APINO >= 200706012030) conn->curl_do_more = (Curl_do_more_func)ZERO_NULL; #else failf(data, LIBCURL_NAME @@ -3376,14 +3380,18 @@ static CURLcode CreateConnection(struct SessionHandle *data, conn->curl_connect = Curl_ssh_connect; /* ssh_connect? */ conn->curl_do = Curl_sftp_do; conn->curl_done = Curl_sftp_done; - conn->curl_do_more = (Curl_do_more_func)NULL; +#if (LIBSSH2_APINO >= 200706012030) + conn->curl_connecting = Curl_ssh_multi_statemach; + conn->curl_doing = Curl_sftp_doing; +#endif (LIBSSH2_APINO >= 200706012030) + conn->curl_do_more = (Curl_do_more_func)ZERO_NULL; #else failf(data, LIBCURL_NAME " was built without LIBSSH2, scp: not supported!"); return CURLE_UNSUPPORTED_PROTOCOL; #endif -} -else { + } + else { /* We fell through all checks and thus we don't support the specified protocol */ failf(data, "Unsupported protocol: %s", conn->protostr); diff --git a/lib/urldata.h b/lib/urldata.h index 29000b210..0d0d5713c 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -411,8 +411,9 @@ struct ftp_conn { * SSH unique setup ***************************************************************************/ typedef enum { - SSH_STOP, /* do nothing state, stops the state machine */ - SSH_S_STARTUP, /* Session startup */ + 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_AUTHLIST, SSH_AUTH_PKEY_INIT, SSH_AUTH_PKEY, @@ -426,7 +427,41 @@ typedef enum { SSH_SFTP_INIT, SSH_SFTP_REALPATH, SSH_GET_WORKINGPATH, + 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_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_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_QUIT, SSH_LAST /* never used */ @@ -451,12 +486,27 @@ struct SSHPROTO { struct */ struct ssh_conn { const char *authlist; /* List of auth. methods, managed by libssh2 */ +#ifdef USE_LIBSSH2 const char *passphrase; char *rsa_pub; char *rsa; bool authed; sshstate state; /* always use ssh.c:state() to change state! */ + sshstate nextState; /* the state to goto after stopping */ CURLcode actualCode; /* the actual error code */ + struct curl_slist *quote_item; + char *quote_path1; + char *quote_path2; + LIBSSH2_SFTP_ATTRIBUTES quote_attrs; + LIBSSH2_SFTP_ATTRIBUTES readdir_attrs; + char *readdir_filename; + char *readdir_longentry; + int readdir_len, readdir_totalLen, readdir_currLen; + char *readdir_line; + char *readdir_linkPath; + int secondCreateDirs; + char *slash_pos; +#endif /* USE_LIBSSH2 */ }; @@ -906,7 +956,7 @@ struct connectdata { struct curl_llist *recv_pipe; /* List of handles waiting to read their responses on this pipeline */ - char* master_buffer; /* The master buffer allocated on-demand; + char* master_buffer; /* The master buffer allocated on-demand; used for pipelining. */ size_t read_pos; /* Current read position in the master buffer */ size_t buf_len; /* Length of the buffer?? */