| /*************************************************************************** | |
| * _ _ ____ _ | |
| * Project ___| | | | _ \| | | |
| * / __| | | | |_) | | | |
| * | (__| |_| | _ <| |___ | |
| * \___|\___/|_| \_\_____| | |
| * | |
| * Copyright (C) 2017 - 2021 Red Hat, Inc. | |
| * | |
| * Authors: Nikos Mavrogiannopoulos, Tomas Mraz, Stanislav Zidek, | |
| * Robert Kolcun, Andreas Schneider | |
| * | |
| * This software is licensed as described in the file COPYING, which | |
| * you should have received as part of this distribution. The terms | |
| * are also available at https://curl.se/docs/copyright.html. | |
| * | |
| * You may opt to use, copy, modify, merge, publish, distribute and/or sell | |
| * copies of the Software, and permit persons to whom the Software is | |
| * furnished to do so, under the terms of the COPYING file. | |
| * | |
| * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | |
| * KIND, either express or implied. | |
| * | |
| ***************************************************************************/ | |
| #include "curl_setup.h" | |
| #ifdef USE_LIBSSH | |
| #include <limits.h> | |
| #include <libssh/libssh.h> | |
| #include <libssh/sftp.h> | |
| #ifdef HAVE_FCNTL_H | |
| #include <fcntl.h> | |
| #endif | |
| #ifdef HAVE_NETINET_IN_H | |
| #include <netinet/in.h> | |
| #endif | |
| #ifdef HAVE_ARPA_INET_H | |
| #include <arpa/inet.h> | |
| #endif | |
| #ifdef HAVE_UTSNAME_H | |
| #include <sys/utsname.h> | |
| #endif | |
| #ifdef HAVE_NETDB_H | |
| #include <netdb.h> | |
| #endif | |
| #ifdef __VMS | |
| #include <in.h> | |
| #include <inet.h> | |
| #endif | |
| #if (defined(NETWARE) && defined(__NOVELL_LIBC__)) | |
| #undef in_addr_t | |
| #define in_addr_t unsigned long | |
| #endif | |
| #include <curl/curl.h> | |
| #include "urldata.h" | |
| #include "sendf.h" | |
| #include "hostip.h" | |
| #include "progress.h" | |
| #include "transfer.h" | |
| #include "escape.h" | |
| #include "http.h" /* for HTTP proxy tunnel stuff */ | |
| #include "ssh.h" | |
| #include "url.h" | |
| #include "speedcheck.h" | |
| #include "getinfo.h" | |
| #include "strdup.h" | |
| #include "strcase.h" | |
| #include "vtls/vtls.h" | |
| #include "connect.h" | |
| #include "inet_ntop.h" | |
| #include "parsedate.h" /* for the week day and month names */ | |
| #include "sockaddr.h" /* required for Curl_sockaddr_storage */ | |
| #include "strtoofft.h" | |
| #include "multiif.h" | |
| #include "select.h" | |
| #include "warnless.h" | |
| /* for permission and open flags */ | |
| #include <sys/types.h> | |
| #include <sys/stat.h> | |
| #include <unistd.h> | |
| #include <fcntl.h> | |
| /* The last 3 #include files should be in this order */ | |
| #include "curl_printf.h" | |
| #include "curl_memory.h" | |
| #include "memdebug.h" | |
| #include "curl_path.h" | |
| /* A recent macro provided by libssh. Or make our own. */ | |
| #ifndef SSH_STRING_FREE_CHAR | |
| #define SSH_STRING_FREE_CHAR(x) \ | |
| do { \ | |
| if(x) { \ | |
| ssh_string_free_char(x); \ | |
| x = NULL; \ | |
| } \ | |
| } while(0) | |
| #endif | |
| /* Local functions: */ | |
| static CURLcode myssh_connect(struct Curl_easy *data, bool *done); | |
| static CURLcode myssh_multi_statemach(struct Curl_easy *data, | |
| bool *done); | |
| static CURLcode myssh_do_it(struct Curl_easy *data, bool *done); | |
| static CURLcode scp_done(struct Curl_easy *data, | |
| CURLcode, bool premature); | |
| static CURLcode scp_doing(struct Curl_easy *data, bool *dophase_done); | |
| static CURLcode scp_disconnect(struct Curl_easy *data, | |
| struct connectdata *conn, | |
| bool dead_connection); | |
| static CURLcode sftp_done(struct Curl_easy *data, | |
| CURLcode, bool premature); | |
| static CURLcode sftp_doing(struct Curl_easy *data, | |
| bool *dophase_done); | |
| static CURLcode sftp_disconnect(struct Curl_easy *data, | |
| struct connectdata *conn, | |
| bool dead); | |
| static | |
| CURLcode sftp_perform(struct Curl_easy *data, | |
| bool *connected, | |
| bool *dophase_done); | |
| static void sftp_quote(struct Curl_easy *data); | |
| static void sftp_quote_stat(struct Curl_easy *data); | |
| static int myssh_getsock(struct Curl_easy *data, | |
| struct connectdata *conn, curl_socket_t *sock); | |
| static CURLcode myssh_setup_connection(struct Curl_easy *data, | |
| struct connectdata *conn); | |
| /* | |
| * SCP protocol handler. | |
| */ | |
| const struct Curl_handler Curl_handler_scp = { | |
| "SCP", /* scheme */ | |
| myssh_setup_connection, /* setup_connection */ | |
| myssh_do_it, /* do_it */ | |
| scp_done, /* done */ | |
| ZERO_NULL, /* do_more */ | |
| myssh_connect, /* connect_it */ | |
| myssh_multi_statemach, /* connecting */ | |
| scp_doing, /* doing */ | |
| myssh_getsock, /* proto_getsock */ | |
| myssh_getsock, /* doing_getsock */ | |
| ZERO_NULL, /* domore_getsock */ | |
| myssh_getsock, /* perform_getsock */ | |
| scp_disconnect, /* disconnect */ | |
| ZERO_NULL, /* readwrite */ | |
| ZERO_NULL, /* connection_check */ | |
| ZERO_NULL, /* attach connection */ | |
| PORT_SSH, /* defport */ | |
| CURLPROTO_SCP, /* protocol */ | |
| CURLPROTO_SCP, /* family */ | |
| PROTOPT_DIRLOCK | PROTOPT_CLOSEACTION | PROTOPT_NOURLQUERY /* flags */ | |
| }; | |
| /* | |
| * SFTP protocol handler. | |
| */ | |
| const struct Curl_handler Curl_handler_sftp = { | |
| "SFTP", /* scheme */ | |
| myssh_setup_connection, /* setup_connection */ | |
| myssh_do_it, /* do_it */ | |
| sftp_done, /* done */ | |
| ZERO_NULL, /* do_more */ | |
| myssh_connect, /* connect_it */ | |
| myssh_multi_statemach, /* connecting */ | |
| sftp_doing, /* doing */ | |
| myssh_getsock, /* proto_getsock */ | |
| myssh_getsock, /* doing_getsock */ | |
| ZERO_NULL, /* domore_getsock */ | |
| myssh_getsock, /* perform_getsock */ | |
| sftp_disconnect, /* disconnect */ | |
| ZERO_NULL, /* readwrite */ | |
| ZERO_NULL, /* connection_check */ | |
| ZERO_NULL, /* attach connection */ | |
| PORT_SSH, /* defport */ | |
| CURLPROTO_SFTP, /* protocol */ | |
| CURLPROTO_SFTP, /* family */ | |
| PROTOPT_DIRLOCK | PROTOPT_CLOSEACTION | |
| | PROTOPT_NOURLQUERY /* flags */ | |
| }; | |
| static CURLcode sftp_error_to_CURLE(int err) | |
| { | |
| switch(err) { | |
| case SSH_FX_OK: | |
| return CURLE_OK; | |
| case SSH_FX_NO_SUCH_FILE: | |
| case SSH_FX_NO_SUCH_PATH: | |
| return CURLE_REMOTE_FILE_NOT_FOUND; | |
| case SSH_FX_PERMISSION_DENIED: | |
| case SSH_FX_WRITE_PROTECT: | |
| return CURLE_REMOTE_ACCESS_DENIED; | |
| case SSH_FX_FILE_ALREADY_EXISTS: | |
| return CURLE_REMOTE_FILE_EXISTS; | |
| default: | |
| break; | |
| } | |
| return CURLE_SSH; | |
| } | |
| #ifndef DEBUGBUILD | |
| #define state(x,y) mystate(x,y) | |
| #else | |
| #define state(x,y) mystate(x,y, __LINE__) | |
| #endif | |
| /* | |
| * SSH State machine related code | |
| */ | |
| /* This is the ONLY way to change SSH state! */ | |
| static void mystate(struct Curl_easy *data, sshstate nowstate | |
| #ifdef DEBUGBUILD | |
| , int lineno | |
| #endif | |
| ) | |
| { | |
| struct connectdata *conn = data->conn; | |
| struct ssh_conn *sshc = &conn->proto.sshc; | |
| #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) | |
| /* for debug purposes */ | |
| static const char *const names[] = { | |
| "SSH_STOP", | |
| "SSH_INIT", | |
| "SSH_S_STARTUP", | |
| "SSH_HOSTKEY", | |
| "SSH_AUTHLIST", | |
| "SSH_AUTH_PKEY_INIT", | |
| "SSH_AUTH_PKEY", | |
| "SSH_AUTH_PASS_INIT", | |
| "SSH_AUTH_PASS", | |
| "SSH_AUTH_AGENT_INIT", | |
| "SSH_AUTH_AGENT_LIST", | |
| "SSH_AUTH_AGENT", | |
| "SSH_AUTH_HOST_INIT", | |
| "SSH_AUTH_HOST", | |
| "SSH_AUTH_KEY_INIT", | |
| "SSH_AUTH_KEY", | |
| "SSH_AUTH_GSSAPI", | |
| "SSH_AUTH_DONE", | |
| "SSH_SFTP_INIT", | |
| "SSH_SFTP_REALPATH", | |
| "SSH_SFTP_QUOTE_INIT", | |
| "SSH_SFTP_POSTQUOTE_INIT", | |
| "SSH_SFTP_QUOTE", | |
| "SSH_SFTP_NEXT_QUOTE", | |
| "SSH_SFTP_QUOTE_STAT", | |
| "SSH_SFTP_QUOTE_SETSTAT", | |
| "SSH_SFTP_QUOTE_SYMLINK", | |
| "SSH_SFTP_QUOTE_MKDIR", | |
| "SSH_SFTP_QUOTE_RENAME", | |
| "SSH_SFTP_QUOTE_RMDIR", | |
| "SSH_SFTP_QUOTE_UNLINK", | |
| "SSH_SFTP_QUOTE_STATVFS", | |
| "SSH_SFTP_GETINFO", | |
| "SSH_SFTP_FILETIME", | |
| "SSH_SFTP_TRANS_INIT", | |
| "SSH_SFTP_UPLOAD_INIT", | |
| "SSH_SFTP_CREATE_DIRS_INIT", | |
| "SSH_SFTP_CREATE_DIRS", | |
| "SSH_SFTP_CREATE_DIRS_MKDIR", | |
| "SSH_SFTP_READDIR_INIT", | |
| "SSH_SFTP_READDIR", | |
| "SSH_SFTP_READDIR_LINK", | |
| "SSH_SFTP_READDIR_BOTTOM", | |
| "SSH_SFTP_READDIR_DONE", | |
| "SSH_SFTP_DOWNLOAD_INIT", | |
| "SSH_SFTP_DOWNLOAD_STAT", | |
| "SSH_SFTP_CLOSE", | |
| "SSH_SFTP_SHUTDOWN", | |
| "SSH_SCP_TRANS_INIT", | |
| "SSH_SCP_UPLOAD_INIT", | |
| "SSH_SCP_DOWNLOAD_INIT", | |
| "SSH_SCP_DOWNLOAD", | |
| "SSH_SCP_DONE", | |
| "SSH_SCP_SEND_EOF", | |
| "SSH_SCP_WAIT_EOF", | |
| "SSH_SCP_WAIT_CLOSE", | |
| "SSH_SCP_CHANNEL_FREE", | |
| "SSH_SESSION_DISCONNECT", | |
| "SSH_SESSION_FREE", | |
| "QUIT" | |
| }; | |
| if(sshc->state != nowstate) { | |
| infof(data, "SSH %p state change from %s to %s (line %d)", | |
| (void *) sshc, names[sshc->state], names[nowstate], | |
| lineno); | |
| } | |
| #endif | |
| sshc->state = nowstate; | |
| } | |
| /* Multiple options: | |
| * 1. data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5] is set with an MD5 | |
| * hash (90s style auth, not sure we should have it here) | |
| * 2. data->set.ssh_keyfunc callback is set. Then we do trust on first | |
| * use. We even save on knownhosts if CURLKHSTAT_FINE_ADD_TO_FILE | |
| * is returned by it. | |
| * 3. none of the above. We only accept if it is present on known hosts. | |
| * | |
| * Returns SSH_OK or SSH_ERROR. | |
| */ | |
| static int myssh_is_known(struct Curl_easy *data) | |
| { | |
| int rc; | |
| struct connectdata *conn = data->conn; | |
| struct ssh_conn *sshc = &conn->proto.sshc; | |
| ssh_key pubkey; | |
| size_t hlen; | |
| unsigned char *hash = NULL; | |
| char *found_base64 = NULL; | |
| char *known_base64 = NULL; | |
| int vstate; | |
| enum curl_khmatch keymatch; | |
| struct curl_khkey foundkey; | |
| struct curl_khkey *knownkeyp = NULL; | |
| curl_sshkeycallback func = | |
| data->set.ssh_keyfunc; | |
| #if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0,9,0) | |
| struct ssh_knownhosts_entry *knownhostsentry = NULL; | |
| struct curl_khkey knownkey; | |
| #endif | |
| #if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0,8,0) | |
| rc = ssh_get_server_publickey(sshc->ssh_session, &pubkey); | |
| #else | |
| rc = ssh_get_publickey(sshc->ssh_session, &pubkey); | |
| #endif | |
| if(rc != SSH_OK) | |
| return rc; | |
| if(data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]) { | |
| int i; | |
| char md5buffer[33]; | |
| const char *pubkey_md5 = data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]; | |
| rc = ssh_get_publickey_hash(pubkey, SSH_PUBLICKEY_HASH_MD5, | |
| &hash, &hlen); | |
| if(rc != SSH_OK || hlen != 16) { | |
| failf(data, | |
| "Denied establishing ssh session: md5 fingerprint not available"); | |
| goto cleanup; | |
| } | |
| for(i = 0; i < 16; i++) | |
| msnprintf(&md5buffer[i*2], 3, "%02x", (unsigned char)hash[i]); | |
| infof(data, "SSH MD5 fingerprint: %s", md5buffer); | |
| if(!strcasecompare(md5buffer, pubkey_md5)) { | |
| failf(data, | |
| "Denied establishing ssh session: mismatch md5 fingerprint. " | |
| "Remote %s is not equal to %s", md5buffer, pubkey_md5); | |
| rc = SSH_ERROR; | |
| goto cleanup; | |
| } | |
| rc = SSH_OK; | |
| goto cleanup; | |
| } | |
| if(data->set.ssl.primary.verifyhost != TRUE) { | |
| rc = SSH_OK; | |
| goto cleanup; | |
| } | |
| #if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0,9,0) | |
| /* Get the known_key from the known hosts file */ | |
| vstate = ssh_session_get_known_hosts_entry(sshc->ssh_session, | |
| &knownhostsentry); | |
| /* Case an entry was found in a known hosts file */ | |
| if(knownhostsentry) { | |
| if(knownhostsentry->publickey) { | |
| rc = ssh_pki_export_pubkey_base64(knownhostsentry->publickey, | |
| &known_base64); | |
| if(rc != SSH_OK) { | |
| goto cleanup; | |
| } | |
| knownkey.key = known_base64; | |
| knownkey.len = strlen(known_base64); | |
| switch(ssh_key_type(knownhostsentry->publickey)) { | |
| case SSH_KEYTYPE_RSA: | |
| knownkey.keytype = CURLKHTYPE_RSA; | |
| break; | |
| case SSH_KEYTYPE_RSA1: | |
| knownkey.keytype = CURLKHTYPE_RSA1; | |
| break; | |
| case SSH_KEYTYPE_ECDSA: | |
| case SSH_KEYTYPE_ECDSA_P256: | |
| case SSH_KEYTYPE_ECDSA_P384: | |
| case SSH_KEYTYPE_ECDSA_P521: | |
| knownkey.keytype = CURLKHTYPE_ECDSA; | |
| break; | |
| case SSH_KEYTYPE_ED25519: | |
| knownkey.keytype = CURLKHTYPE_ED25519; | |
| break; | |
| case SSH_KEYTYPE_DSS: | |
| knownkey.keytype = CURLKHTYPE_DSS; | |
| break; | |
| default: | |
| rc = SSH_ERROR; | |
| goto cleanup; | |
| } | |
| knownkeyp = &knownkey; | |
| } | |
| } | |
| switch(vstate) { | |
| case SSH_KNOWN_HOSTS_OK: | |
| keymatch = CURLKHMATCH_OK; | |
| break; | |
| case SSH_KNOWN_HOSTS_OTHER: | |
| /* fallthrough */ | |
| case SSH_KNOWN_HOSTS_NOT_FOUND: | |
| /* fallthrough */ | |
| case SSH_KNOWN_HOSTS_UNKNOWN: | |
| /* fallthrough */ | |
| case SSH_KNOWN_HOSTS_ERROR: | |
| keymatch = CURLKHMATCH_MISSING; | |
| break; | |
| default: | |
| keymatch = CURLKHMATCH_MISMATCH; | |
| break; | |
| } | |
| #else | |
| vstate = ssh_is_server_known(sshc->ssh_session); | |
| switch(vstate) { | |
| case SSH_SERVER_KNOWN_OK: | |
| keymatch = CURLKHMATCH_OK; | |
| break; | |
| case SSH_SERVER_FILE_NOT_FOUND: | |
| /* fallthrough */ | |
| case SSH_SERVER_NOT_KNOWN: | |
| keymatch = CURLKHMATCH_MISSING; | |
| break; | |
| default: | |
| keymatch = CURLKHMATCH_MISMATCH; | |
| break; | |
| } | |
| #endif | |
| if(func) { /* use callback to determine action */ | |
| rc = ssh_pki_export_pubkey_base64(pubkey, &found_base64); | |
| if(rc != SSH_OK) | |
| goto cleanup; | |
| foundkey.key = found_base64; | |
| foundkey.len = strlen(found_base64); | |
| switch(ssh_key_type(pubkey)) { | |
| case SSH_KEYTYPE_RSA: | |
| foundkey.keytype = CURLKHTYPE_RSA; | |
| break; | |
| case SSH_KEYTYPE_RSA1: | |
| foundkey.keytype = CURLKHTYPE_RSA1; | |
| break; | |
| case SSH_KEYTYPE_ECDSA: | |
| #if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0,9,0) | |
| case SSH_KEYTYPE_ECDSA_P256: | |
| case SSH_KEYTYPE_ECDSA_P384: | |
| case SSH_KEYTYPE_ECDSA_P521: | |
| #endif | |
| foundkey.keytype = CURLKHTYPE_ECDSA; | |
| break; | |
| #if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0,7,0) | |
| case SSH_KEYTYPE_ED25519: | |
| foundkey.keytype = CURLKHTYPE_ED25519; | |
| break; | |
| #endif | |
| case SSH_KEYTYPE_DSS: | |
| foundkey.keytype = CURLKHTYPE_DSS; | |
| break; | |
| default: | |
| rc = SSH_ERROR; | |
| goto cleanup; | |
| } | |
| Curl_set_in_callback(data, true); | |
| rc = func(data, knownkeyp, /* from the knownhosts file */ | |
| &foundkey, /* from the remote host */ | |
| keymatch, data->set.ssh_keyfunc_userp); | |
| Curl_set_in_callback(data, false); | |
| switch(rc) { | |
| case CURLKHSTAT_FINE_ADD_TO_FILE: | |
| #if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0,8,0) | |
| rc = ssh_session_update_known_hosts(sshc->ssh_session); | |
| #else | |
| rc = ssh_write_knownhost(sshc->ssh_session); | |
| #endif | |
| if(rc != SSH_OK) { | |
| goto cleanup; | |
| } | |
| break; | |
| case CURLKHSTAT_FINE: | |
| break; | |
| default: /* REJECT/DEFER */ | |
| rc = SSH_ERROR; | |
| goto cleanup; | |
| } | |
| } | |
| else { | |
| if(keymatch != CURLKHMATCH_OK) { | |
| rc = SSH_ERROR; | |
| goto cleanup; | |
| } | |
| } | |
| rc = SSH_OK; | |
| cleanup: | |
| if(found_base64) { | |
| (free)(found_base64); | |
| } | |
| if(known_base64) { | |
| (free)(known_base64); | |
| } | |
| if(hash) | |
| ssh_clean_pubkey_hash(&hash); | |
| ssh_key_free(pubkey); | |
| #if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0,9,0) | |
| if(knownhostsentry) { | |
| ssh_knownhosts_entry_free(knownhostsentry); | |
| } | |
| #endif | |
| return rc; | |
| } | |
| #define MOVE_TO_ERROR_STATE(_r) do { \ | |
| state(data, SSH_SESSION_DISCONNECT); \ | |
| sshc->actualcode = _r; \ | |
| rc = SSH_ERROR; \ | |
| } while(0) | |
| #define MOVE_TO_SFTP_CLOSE_STATE() do { \ | |
| state(data, SSH_SFTP_CLOSE); \ | |
| sshc->actualcode = \ | |
| sftp_error_to_CURLE(sftp_get_error(sshc->sftp_session)); \ | |
| rc = SSH_ERROR; \ | |
| } while(0) | |
| #define MOVE_TO_LAST_AUTH do { \ | |
| if(sshc->auth_methods & SSH_AUTH_METHOD_PASSWORD) { \ | |
| rc = SSH_OK; \ | |
| state(data, SSH_AUTH_PASS_INIT); \ | |
| } \ | |
| else { \ | |
| MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); \ | |
| } \ | |
| } while(0) | |
| #define MOVE_TO_TERTIARY_AUTH do { \ | |
| if(sshc->auth_methods & SSH_AUTH_METHOD_INTERACTIVE) { \ | |
| rc = SSH_OK; \ | |
| state(data, SSH_AUTH_KEY_INIT); \ | |
| } \ | |
| else { \ | |
| MOVE_TO_LAST_AUTH; \ | |
| } \ | |
| } while(0) | |
| #define MOVE_TO_SECONDARY_AUTH do { \ | |
| if(sshc->auth_methods & SSH_AUTH_METHOD_GSSAPI_MIC) { \ | |
| rc = SSH_OK; \ | |
| state(data, SSH_AUTH_GSSAPI); \ | |
| } \ | |
| else { \ | |
| MOVE_TO_TERTIARY_AUTH; \ | |
| } \ | |
| } while(0) | |
| static | |
| int myssh_auth_interactive(struct connectdata *conn) | |
| { | |
| int rc; | |
| struct ssh_conn *sshc = &conn->proto.sshc; | |
| int nprompts; | |
| restart: | |
| switch(sshc->kbd_state) { | |
| case 0: | |
| rc = ssh_userauth_kbdint(sshc->ssh_session, NULL, NULL); | |
| if(rc == SSH_AUTH_AGAIN) | |
| return SSH_AGAIN; | |
| if(rc != SSH_AUTH_INFO) | |
| return SSH_ERROR; | |
| nprompts = ssh_userauth_kbdint_getnprompts(sshc->ssh_session); | |
| if(nprompts != 1) | |
| return SSH_ERROR; | |
| rc = ssh_userauth_kbdint_setanswer(sshc->ssh_session, 0, conn->passwd); | |
| if(rc < 0) | |
| return SSH_ERROR; | |
| /* FALLTHROUGH */ | |
| case 1: | |
| sshc->kbd_state = 1; | |
| rc = ssh_userauth_kbdint(sshc->ssh_session, NULL, NULL); | |
| if(rc == SSH_AUTH_AGAIN) | |
| return SSH_AGAIN; | |
| else if(rc == SSH_AUTH_SUCCESS) | |
| rc = SSH_OK; | |
| else if(rc == SSH_AUTH_INFO) { | |
| nprompts = ssh_userauth_kbdint_getnprompts(sshc->ssh_session); | |
| if(nprompts) | |
| return SSH_ERROR; | |
| sshc->kbd_state = 2; | |
| goto restart; | |
| } | |
| else | |
| rc = SSH_ERROR; | |
| break; | |
| case 2: | |
| sshc->kbd_state = 2; | |
| rc = ssh_userauth_kbdint(sshc->ssh_session, NULL, NULL); | |
| if(rc == SSH_AUTH_AGAIN) | |
| return SSH_AGAIN; | |
| else if(rc == SSH_AUTH_SUCCESS) | |
| rc = SSH_OK; | |
| else | |
| rc = SSH_ERROR; | |
| break; | |
| default: | |
| return SSH_ERROR; | |
| } | |
| sshc->kbd_state = 0; | |
| return rc; | |
| } | |
| /* | |
| * ssh_statemach_act() runs the SSH state machine as far as it can without | |
| * blocking and without reaching the end. The data the pointer 'block' points | |
| * to will be set to TRUE if the libssh function returns SSH_AGAIN | |
| * meaning it wants to be called again when the socket is ready | |
| */ | |
| static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) | |
| { | |
| CURLcode result = CURLE_OK; | |
| struct connectdata *conn = data->conn; | |
| struct SSHPROTO *protop = data->req.p.ssh; | |
| struct ssh_conn *sshc = &conn->proto.sshc; | |
| curl_socket_t sock = conn->sock[FIRSTSOCKET]; | |
| int rc = SSH_NO_ERROR, err; | |
| char *new_readdir_line; | |
| int seekerr = CURL_SEEKFUNC_OK; | |
| const char *err_msg; | |
| *block = 0; /* we're not blocking by default */ | |
| do { | |
| switch(sshc->state) { | |
| case SSH_INIT: | |
| sshc->secondCreateDirs = 0; | |
| sshc->nextstate = SSH_NO_STATE; | |
| sshc->actualcode = CURLE_OK; | |
| #if 0 | |
| ssh_set_log_level(SSH_LOG_PROTOCOL); | |
| #endif | |
| /* Set libssh to non-blocking, since everything internally is | |
| non-blocking */ | |
| ssh_set_blocking(sshc->ssh_session, 0); | |
| state(data, SSH_S_STARTUP); | |
| /* FALLTHROUGH */ | |
| case SSH_S_STARTUP: | |
| rc = ssh_connect(sshc->ssh_session); | |
| if(rc == SSH_AGAIN) | |
| break; | |
| if(rc != SSH_OK) { | |
| failf(data, "Failure establishing ssh session"); | |
| MOVE_TO_ERROR_STATE(CURLE_FAILED_INIT); | |
| break; | |
| } | |
| state(data, SSH_HOSTKEY); | |
| /* FALLTHROUGH */ | |
| case SSH_HOSTKEY: | |
| rc = myssh_is_known(data); | |
| if(rc != SSH_OK) { | |
| MOVE_TO_ERROR_STATE(CURLE_PEER_FAILED_VERIFICATION); | |
| break; | |
| } | |
| state(data, SSH_AUTHLIST); | |
| /* FALLTHROUGH */ | |
| case SSH_AUTHLIST:{ | |
| sshc->authed = FALSE; | |
| rc = ssh_userauth_none(sshc->ssh_session, NULL); | |
| if(rc == SSH_AUTH_AGAIN) { | |
| rc = SSH_AGAIN; | |
| break; | |
| } | |
| if(rc == SSH_AUTH_SUCCESS) { | |
| sshc->authed = TRUE; | |
| infof(data, "Authenticated with none"); | |
| state(data, SSH_AUTH_DONE); | |
| break; | |
| } | |
| else if(rc == SSH_AUTH_ERROR) { | |
| MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); | |
| break; | |
| } | |
| sshc->auth_methods = ssh_userauth_list(sshc->ssh_session, NULL); | |
| if(sshc->auth_methods & SSH_AUTH_METHOD_PUBLICKEY) { | |
| state(data, SSH_AUTH_PKEY_INIT); | |
| infof(data, "Authentication using SSH public key file"); | |
| } | |
| else if(sshc->auth_methods & SSH_AUTH_METHOD_GSSAPI_MIC) { | |
| state(data, SSH_AUTH_GSSAPI); | |
| } | |
| else if(sshc->auth_methods & SSH_AUTH_METHOD_INTERACTIVE) { | |
| state(data, SSH_AUTH_KEY_INIT); | |
| } | |
| else if(sshc->auth_methods & SSH_AUTH_METHOD_PASSWORD) { | |
| state(data, SSH_AUTH_PASS_INIT); | |
| } | |
| else { /* unsupported authentication method */ | |
| MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); | |
| break; | |
| } | |
| break; | |
| } | |
| case SSH_AUTH_PKEY_INIT: | |
| if(!(data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY)) { | |
| MOVE_TO_SECONDARY_AUTH; | |
| break; | |
| } | |
| /* Two choices, (1) private key was given on CMD, | |
| * (2) use the "default" keys. */ | |
| if(data->set.str[STRING_SSH_PRIVATE_KEY]) { | |
| if(sshc->pubkey && !data->set.ssl.key_passwd) { | |
| rc = ssh_userauth_try_publickey(sshc->ssh_session, NULL, | |
| sshc->pubkey); | |
| if(rc == SSH_AUTH_AGAIN) { | |
| rc = SSH_AGAIN; | |
| break; | |
| } | |
| if(rc != SSH_OK) { | |
| MOVE_TO_SECONDARY_AUTH; | |
| break; | |
| } | |
| } | |
| rc = ssh_pki_import_privkey_file(data-> | |
| set.str[STRING_SSH_PRIVATE_KEY], | |
| data->set.ssl.key_passwd, NULL, | |
| NULL, &sshc->privkey); | |
| if(rc != SSH_OK) { | |
| failf(data, "Could not load private key file %s", | |
| data->set.str[STRING_SSH_PRIVATE_KEY]); | |
| MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); | |
| break; | |
| } | |
| state(data, SSH_AUTH_PKEY); | |
| break; | |
| } | |
| else { | |
| rc = ssh_userauth_publickey_auto(sshc->ssh_session, NULL, | |
| data->set.ssl.key_passwd); | |
| if(rc == SSH_AUTH_AGAIN) { | |
| rc = SSH_AGAIN; | |
| break; | |
| } | |
| if(rc == SSH_AUTH_SUCCESS) { | |
| rc = SSH_OK; | |
| sshc->authed = TRUE; | |
| infof(data, "Completed public key authentication"); | |
| state(data, SSH_AUTH_DONE); | |
| break; | |
| } | |
| MOVE_TO_SECONDARY_AUTH; | |
| } | |
| break; | |
| case SSH_AUTH_PKEY: | |
| rc = ssh_userauth_publickey(sshc->ssh_session, NULL, sshc->privkey); | |
| if(rc == SSH_AUTH_AGAIN) { | |
| rc = SSH_AGAIN; | |
| break; | |
| } | |
| if(rc == SSH_AUTH_SUCCESS) { | |
| sshc->authed = TRUE; | |
| infof(data, "Completed public key authentication"); | |
| state(data, SSH_AUTH_DONE); | |
| break; | |
| } | |
| else { | |
| infof(data, "Failed public key authentication (rc: %d)", rc); | |
| MOVE_TO_SECONDARY_AUTH; | |
| } | |
| break; | |
| case SSH_AUTH_GSSAPI: | |
| if(!(data->set.ssh_auth_types & CURLSSH_AUTH_GSSAPI)) { | |
| MOVE_TO_TERTIARY_AUTH; | |
| break; | |
| } | |
| rc = ssh_userauth_gssapi(sshc->ssh_session); | |
| if(rc == SSH_AUTH_AGAIN) { | |
| rc = SSH_AGAIN; | |
| break; | |
| } | |
| if(rc == SSH_AUTH_SUCCESS) { | |
| rc = SSH_OK; | |
| sshc->authed = TRUE; | |
| infof(data, "Completed gssapi authentication"); | |
| state(data, SSH_AUTH_DONE); | |
| break; | |
| } | |
| MOVE_TO_TERTIARY_AUTH; | |
| break; | |
| case SSH_AUTH_KEY_INIT: | |
| if(data->set.ssh_auth_types & CURLSSH_AUTH_KEYBOARD) { | |
| state(data, SSH_AUTH_KEY); | |
| } | |
| else { | |
| MOVE_TO_LAST_AUTH; | |
| } | |
| break; | |
| case SSH_AUTH_KEY: | |
| /* Authentication failed. Continue with keyboard-interactive now. */ | |
| rc = myssh_auth_interactive(conn); | |
| if(rc == SSH_AGAIN) { | |
| break; | |
| } | |
| if(rc == SSH_OK) { | |
| sshc->authed = TRUE; | |
| infof(data, "completed keyboard interactive authentication"); | |
| } | |
| state(data, SSH_AUTH_DONE); | |
| break; | |
| case SSH_AUTH_PASS_INIT: | |
| if(!(data->set.ssh_auth_types & CURLSSH_AUTH_PASSWORD)) { | |
| /* Host key authentication is intentionally not implemented */ | |
| MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); | |
| break; | |
| } | |
| state(data, SSH_AUTH_PASS); | |
| /* FALLTHROUGH */ | |
| case SSH_AUTH_PASS: | |
| rc = ssh_userauth_password(sshc->ssh_session, NULL, conn->passwd); | |
| if(rc == SSH_AUTH_AGAIN) { | |
| rc = SSH_AGAIN; | |
| break; | |
| } | |
| if(rc == SSH_AUTH_SUCCESS) { | |
| sshc->authed = TRUE; | |
| infof(data, "Completed password authentication"); | |
| state(data, SSH_AUTH_DONE); | |
| } | |
| else { | |
| MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); | |
| } | |
| break; | |
| case SSH_AUTH_DONE: | |
| if(!sshc->authed) { | |
| failf(data, "Authentication failure"); | |
| MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); | |
| break; | |
| } | |
| /* | |
| * At this point we have an authenticated ssh session. | |
| */ | |
| infof(data, "Authentication complete"); | |
| Curl_pgrsTime(data, TIMER_APPCONNECT); /* SSH is connected */ | |
| conn->sockfd = sock; | |
| conn->writesockfd = CURL_SOCKET_BAD; | |
| if(conn->handler->protocol == CURLPROTO_SFTP) { | |
| state(data, SSH_SFTP_INIT); | |
| break; | |
| } | |
| infof(data, "SSH CONNECT phase done"); | |
| state(data, SSH_STOP); | |
| break; | |
| case SSH_SFTP_INIT: | |
| ssh_set_blocking(sshc->ssh_session, 1); | |
| sshc->sftp_session = sftp_new(sshc->ssh_session); | |
| if(!sshc->sftp_session) { | |
| failf(data, "Failure initializing sftp session: %s", | |
| ssh_get_error(sshc->ssh_session)); | |
| MOVE_TO_ERROR_STATE(CURLE_COULDNT_CONNECT); | |
| break; | |
| } | |
| rc = sftp_init(sshc->sftp_session); | |
| if(rc != SSH_OK) { | |
| rc = sftp_get_error(sshc->sftp_session); | |
| failf(data, "Failure initializing sftp session: %s", | |
| ssh_get_error(sshc->ssh_session)); | |
| MOVE_TO_ERROR_STATE(sftp_error_to_CURLE(rc)); | |
| break; | |
| } | |
| state(data, SSH_SFTP_REALPATH); | |
| /* FALLTHROUGH */ | |
| case SSH_SFTP_REALPATH: | |
| /* | |
| * Get the "home" directory | |
| */ | |
| sshc->homedir = sftp_canonicalize_path(sshc->sftp_session, "."); | |
| if(!sshc->homedir) { | |
| MOVE_TO_ERROR_STATE(CURLE_COULDNT_CONNECT); | |
| break; | |
| } | |
| data->state.most_recent_ftp_entrypath = sshc->homedir; | |
| /* This is the last step in the SFTP connect phase. Do note that while | |
| we get the homedir here, we get the "workingpath" in the DO action | |
| since the homedir will remain the same between request but the | |
| working path will not. */ | |
| DEBUGF(infof(data, "SSH CONNECT phase done")); | |
| state(data, SSH_STOP); | |
| break; | |
| case SSH_SFTP_QUOTE_INIT: | |
| result = Curl_getworkingpath(data, sshc->homedir, &protop->path); | |
| if(result) { | |
| sshc->actualcode = result; | |
| state(data, SSH_STOP); | |
| break; | |
| } | |
| if(data->set.quote) { | |
| infof(data, "Sending quote commands"); | |
| sshc->quote_item = data->set.quote; | |
| state(data, SSH_SFTP_QUOTE); | |
| } | |
| else { | |
| state(data, SSH_SFTP_GETINFO); | |
| } | |
| break; | |
| case SSH_SFTP_POSTQUOTE_INIT: | |
| if(data->set.postquote) { | |
| infof(data, "Sending quote commands"); | |
| sshc->quote_item = data->set.postquote; | |
| state(data, SSH_SFTP_QUOTE); | |
| } | |
| else { | |
| state(data, SSH_STOP); | |
| } | |
| break; | |
| case SSH_SFTP_QUOTE: | |
| /* Send any quote commands */ | |
| sftp_quote(data); | |
| break; | |
| case SSH_SFTP_NEXT_QUOTE: | |
| Curl_safefree(sshc->quote_path1); | |
| Curl_safefree(sshc->quote_path2); | |
| sshc->quote_item = sshc->quote_item->next; | |
| if(sshc->quote_item) { | |
| state(data, SSH_SFTP_QUOTE); | |
| } | |
| else { | |
| if(sshc->nextstate != SSH_NO_STATE) { | |
| state(data, sshc->nextstate); | |
| sshc->nextstate = SSH_NO_STATE; | |
| } | |
| else { | |
| state(data, SSH_SFTP_GETINFO); | |
| } | |
| } | |
| break; | |
| case SSH_SFTP_QUOTE_STAT: | |
| sftp_quote_stat(data); | |
| break; | |
| case SSH_SFTP_QUOTE_SETSTAT: | |
| rc = sftp_setstat(sshc->sftp_session, sshc->quote_path2, | |
| sshc->quote_attrs); | |
| if(rc && !sshc->acceptfail) { | |
| Curl_safefree(sshc->quote_path1); | |
| Curl_safefree(sshc->quote_path2); | |
| failf(data, "Attempt to set SFTP stats failed: %s", | |
| ssh_get_error(sshc->ssh_session)); | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->nextstate = SSH_NO_STATE; | |
| sshc->actualcode = CURLE_QUOTE_ERROR; | |
| /* sshc->actualcode = sftp_error_to_CURLE(err); | |
| * we do not send the actual error; we return | |
| * the error the libssh2 backend is returning */ | |
| break; | |
| } | |
| state(data, SSH_SFTP_NEXT_QUOTE); | |
| break; | |
| case SSH_SFTP_QUOTE_SYMLINK: | |
| rc = sftp_symlink(sshc->sftp_session, sshc->quote_path2, | |
| sshc->quote_path1); | |
| if(rc && !sshc->acceptfail) { | |
| Curl_safefree(sshc->quote_path1); | |
| Curl_safefree(sshc->quote_path2); | |
| failf(data, "symlink command failed: %s", | |
| ssh_get_error(sshc->ssh_session)); | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->nextstate = SSH_NO_STATE; | |
| sshc->actualcode = CURLE_QUOTE_ERROR; | |
| break; | |
| } | |
| state(data, SSH_SFTP_NEXT_QUOTE); | |
| break; | |
| case SSH_SFTP_QUOTE_MKDIR: | |
| rc = sftp_mkdir(sshc->sftp_session, sshc->quote_path1, | |
| (mode_t)data->set.new_directory_perms); | |
| if(rc && !sshc->acceptfail) { | |
| Curl_safefree(sshc->quote_path1); | |
| failf(data, "mkdir command failed: %s", | |
| ssh_get_error(sshc->ssh_session)); | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->nextstate = SSH_NO_STATE; | |
| sshc->actualcode = CURLE_QUOTE_ERROR; | |
| break; | |
| } | |
| state(data, SSH_SFTP_NEXT_QUOTE); | |
| break; | |
| case SSH_SFTP_QUOTE_RENAME: | |
| rc = sftp_rename(sshc->sftp_session, sshc->quote_path1, | |
| sshc->quote_path2); | |
| if(rc && !sshc->acceptfail) { | |
| Curl_safefree(sshc->quote_path1); | |
| Curl_safefree(sshc->quote_path2); | |
| failf(data, "rename command failed: %s", | |
| ssh_get_error(sshc->ssh_session)); | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->nextstate = SSH_NO_STATE; | |
| sshc->actualcode = CURLE_QUOTE_ERROR; | |
| break; | |
| } | |
| state(data, SSH_SFTP_NEXT_QUOTE); | |
| break; | |
| case SSH_SFTP_QUOTE_RMDIR: | |
| rc = sftp_rmdir(sshc->sftp_session, sshc->quote_path1); | |
| if(rc && !sshc->acceptfail) { | |
| Curl_safefree(sshc->quote_path1); | |
| failf(data, "rmdir command failed: %s", | |
| ssh_get_error(sshc->ssh_session)); | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->nextstate = SSH_NO_STATE; | |
| sshc->actualcode = CURLE_QUOTE_ERROR; | |
| break; | |
| } | |
| state(data, SSH_SFTP_NEXT_QUOTE); | |
| break; | |
| case SSH_SFTP_QUOTE_UNLINK: | |
| rc = sftp_unlink(sshc->sftp_session, sshc->quote_path1); | |
| if(rc && !sshc->acceptfail) { | |
| Curl_safefree(sshc->quote_path1); | |
| failf(data, "rm command failed: %s", | |
| ssh_get_error(sshc->ssh_session)); | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->nextstate = SSH_NO_STATE; | |
| sshc->actualcode = CURLE_QUOTE_ERROR; | |
| break; | |
| } | |
| state(data, SSH_SFTP_NEXT_QUOTE); | |
| break; | |
| case SSH_SFTP_QUOTE_STATVFS: | |
| { | |
| sftp_statvfs_t statvfs; | |
| statvfs = sftp_statvfs(sshc->sftp_session, sshc->quote_path1); | |
| if(!statvfs && !sshc->acceptfail) { | |
| Curl_safefree(sshc->quote_path1); | |
| failf(data, "statvfs command failed: %s", | |
| ssh_get_error(sshc->ssh_session)); | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->nextstate = SSH_NO_STATE; | |
| sshc->actualcode = CURLE_QUOTE_ERROR; | |
| break; | |
| } | |
| else if(statvfs) { | |
| char *tmp = aprintf("statvfs:\n" | |
| "f_bsize: %llu\n" "f_frsize: %llu\n" | |
| "f_blocks: %llu\n" "f_bfree: %llu\n" | |
| "f_bavail: %llu\n" "f_files: %llu\n" | |
| "f_ffree: %llu\n" "f_favail: %llu\n" | |
| "f_fsid: %llu\n" "f_flag: %llu\n" | |
| "f_namemax: %llu\n", | |
| statvfs->f_bsize, statvfs->f_frsize, | |
| statvfs->f_blocks, statvfs->f_bfree, | |
| statvfs->f_bavail, statvfs->f_files, | |
| statvfs->f_ffree, statvfs->f_favail, | |
| statvfs->f_fsid, statvfs->f_flag, | |
| statvfs->f_namemax); | |
| sftp_statvfs_free(statvfs); | |
| if(!tmp) { | |
| result = CURLE_OUT_OF_MEMORY; | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->nextstate = SSH_NO_STATE; | |
| break; | |
| } | |
| result = Curl_client_write(data, CLIENTWRITE_HEADER, tmp, strlen(tmp)); | |
| free(tmp); | |
| if(result) { | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->nextstate = SSH_NO_STATE; | |
| sshc->actualcode = result; | |
| } | |
| } | |
| state(data, SSH_SFTP_NEXT_QUOTE); | |
| break; | |
| } | |
| case SSH_SFTP_GETINFO: | |
| if(data->set.get_filetime) { | |
| state(data, SSH_SFTP_FILETIME); | |
| } | |
| else { | |
| state(data, SSH_SFTP_TRANS_INIT); | |
| } | |
| break; | |
| case SSH_SFTP_FILETIME: | |
| { | |
| sftp_attributes attrs; | |
| attrs = sftp_stat(sshc->sftp_session, protop->path); | |
| if(attrs) { | |
| data->info.filetime = attrs->mtime; | |
| sftp_attributes_free(attrs); | |
| } | |
| state(data, SSH_SFTP_TRANS_INIT); | |
| break; | |
| } | |
| case SSH_SFTP_TRANS_INIT: | |
| if(data->set.upload) | |
| state(data, SSH_SFTP_UPLOAD_INIT); | |
| else { | |
| if(protop->path[strlen(protop->path)-1] == '/') | |
| state(data, SSH_SFTP_READDIR_INIT); | |
| else | |
| state(data, SSH_SFTP_DOWNLOAD_INIT); | |
| } | |
| break; | |
| case SSH_SFTP_UPLOAD_INIT: | |
| { | |
| int flags; | |
| if(data->state.resume_from) { | |
| sftp_attributes attrs; | |
| if(data->state.resume_from < 0) { | |
| attrs = sftp_stat(sshc->sftp_session, protop->path); | |
| if(attrs) { | |
| curl_off_t size = attrs->size; | |
| if(size < 0) { | |
| failf(data, "Bad file size (%" CURL_FORMAT_CURL_OFF_T ")", size); | |
| MOVE_TO_ERROR_STATE(CURLE_BAD_DOWNLOAD_RESUME); | |
| break; | |
| } | |
| data->state.resume_from = attrs->size; | |
| sftp_attributes_free(attrs); | |
| } | |
| else { | |
| data->state.resume_from = 0; | |
| } | |
| } | |
| } | |
| if(data->set.remote_append) | |
| /* Try to open for append, but create if nonexisting */ | |
| flags = O_WRONLY|O_CREAT|O_APPEND; | |
| else if(data->state.resume_from > 0) | |
| /* If we have restart position then open for append */ | |
| flags = O_WRONLY|O_APPEND; | |
| else | |
| /* Clear file before writing (normal behavior) */ | |
| flags = O_WRONLY|O_CREAT|O_TRUNC; | |
| if(sshc->sftp_file) | |
| sftp_close(sshc->sftp_file); | |
| sshc->sftp_file = | |
| sftp_open(sshc->sftp_session, protop->path, | |
| flags, (mode_t)data->set.new_file_perms); | |
| if(!sshc->sftp_file) { | |
| err = sftp_get_error(sshc->sftp_session); | |
| if(((err == SSH_FX_NO_SUCH_FILE || err == SSH_FX_FAILURE || | |
| err == SSH_FX_NO_SUCH_PATH)) && | |
| (data->set.ftp_create_missing_dirs && | |
| (strlen(protop->path) > 1))) { | |
| /* try to create the path remotely */ | |
| rc = 0; | |
| sshc->secondCreateDirs = 1; | |
| state(data, SSH_SFTP_CREATE_DIRS_INIT); | |
| break; | |
| } | |
| else { | |
| MOVE_TO_SFTP_CLOSE_STATE(); | |
| break; | |
| } | |
| } | |
| /* If we have a restart point then we need to seek to the correct | |
| position. */ | |
| if(data->state.resume_from > 0) { | |
| /* Let's read off the proper amount of bytes from the input. */ | |
| if(conn->seek_func) { | |
| Curl_set_in_callback(data, true); | |
| seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, | |
| SEEK_SET); | |
| Curl_set_in_callback(data, false); | |
| } | |
| if(seekerr != CURL_SEEKFUNC_OK) { | |
| curl_off_t passed = 0; | |
| if(seekerr != CURL_SEEKFUNC_CANTSEEK) { | |
| failf(data, "Could not seek stream"); | |
| return CURLE_FTP_COULDNT_USE_REST; | |
| } | |
| /* seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ | |
| do { | |
| size_t readthisamountnow = | |
| (data->state.resume_from - passed > data->set.buffer_size) ? | |
| (size_t)data->set.buffer_size : | |
| curlx_sotouz(data->state.resume_from - passed); | |
| size_t actuallyread = | |
| data->state.fread_func(data->state.buffer, 1, | |
| readthisamountnow, data->state.in); | |
| passed += actuallyread; | |
| if((actuallyread == 0) || (actuallyread > readthisamountnow)) { | |
| /* this checks for greater-than only to make sure that the | |
| CURL_READFUNC_ABORT return code still aborts */ | |
| failf(data, "Failed to read data"); | |
| MOVE_TO_ERROR_STATE(CURLE_FTP_COULDNT_USE_REST); | |
| break; | |
| } | |
| } while(passed < data->state.resume_from); | |
| if(rc) | |
| break; | |
| } | |
| /* now, decrease the size of the read */ | |
| if(data->state.infilesize > 0) { | |
| data->state.infilesize -= data->state.resume_from; | |
| data->req.size = data->state.infilesize; | |
| Curl_pgrsSetUploadSize(data, data->state.infilesize); | |
| } | |
| rc = sftp_seek64(sshc->sftp_file, data->state.resume_from); | |
| if(rc) { | |
| MOVE_TO_SFTP_CLOSE_STATE(); | |
| break; | |
| } | |
| } | |
| if(data->state.infilesize > 0) { | |
| data->req.size = data->state.infilesize; | |
| Curl_pgrsSetUploadSize(data, data->state.infilesize); | |
| } | |
| /* upload data */ | |
| Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET); | |
| /* not set by Curl_setup_transfer to preserve keepon bits */ | |
| conn->sockfd = conn->writesockfd; | |
| /* store this original bitmask setup to use later on if we can't | |
| figure out a "real" bitmask */ | |
| sshc->orig_waitfor = data->req.keepon; | |
| /* we want to use the _sending_ function even when the socket turns | |
| out readable as the underlying libssh sftp send function will deal | |
| with both accordingly */ | |
| conn->cselect_bits = CURL_CSELECT_OUT; | |
| /* since we don't really wait for anything at this point, we want the | |
| state machine to move on as soon as possible so we set a very short | |
| timeout here */ | |
| Curl_expire(data, 0, EXPIRE_RUN_NOW); | |
| state(data, SSH_STOP); | |
| break; | |
| } | |
| case SSH_SFTP_CREATE_DIRS_INIT: | |
| if(strlen(protop->path) > 1) { | |
| sshc->slash_pos = protop->path + 1; /* ignore the leading '/' */ | |
| state(data, SSH_SFTP_CREATE_DIRS); | |
| } | |
| else { | |
| state(data, SSH_SFTP_UPLOAD_INIT); | |
| } | |
| break; | |
| case SSH_SFTP_CREATE_DIRS: | |
| sshc->slash_pos = strchr(sshc->slash_pos, '/'); | |
| if(sshc->slash_pos) { | |
| *sshc->slash_pos = 0; | |
| infof(data, "Creating directory '%s'", protop->path); | |
| state(data, SSH_SFTP_CREATE_DIRS_MKDIR); | |
| break; | |
| } | |
| state(data, SSH_SFTP_UPLOAD_INIT); | |
| break; | |
| case SSH_SFTP_CREATE_DIRS_MKDIR: | |
| /* 'mode' - parameter is preliminary - default to 0644 */ | |
| rc = sftp_mkdir(sshc->sftp_session, protop->path, | |
| (mode_t)data->set.new_directory_perms); | |
| *sshc->slash_pos = '/'; | |
| ++sshc->slash_pos; | |
| if(rc < 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 | |
| */ | |
| err = sftp_get_error(sshc->sftp_session); | |
| if((err != SSH_FX_FILE_ALREADY_EXISTS) && | |
| (err != SSH_FX_FAILURE) && | |
| (err != SSH_FX_PERMISSION_DENIED)) { | |
| MOVE_TO_SFTP_CLOSE_STATE(); | |
| break; | |
| } | |
| rc = 0; /* clear rc and continue */ | |
| } | |
| state(data, SSH_SFTP_CREATE_DIRS); | |
| break; | |
| case SSH_SFTP_READDIR_INIT: | |
| Curl_pgrsSetDownloadSize(data, -1); | |
| if(data->set.opt_no_body) { | |
| state(data, SSH_STOP); | |
| break; | |
| } | |
| /* | |
| * This is a directory that we are trying to get, so produce a directory | |
| * listing | |
| */ | |
| sshc->sftp_dir = sftp_opendir(sshc->sftp_session, | |
| protop->path); | |
| if(!sshc->sftp_dir) { | |
| failf(data, "Could not open directory for reading: %s", | |
| ssh_get_error(sshc->ssh_session)); | |
| MOVE_TO_SFTP_CLOSE_STATE(); | |
| break; | |
| } | |
| state(data, SSH_SFTP_READDIR); | |
| break; | |
| case SSH_SFTP_READDIR: | |
| if(sshc->readdir_attrs) | |
| sftp_attributes_free(sshc->readdir_attrs); | |
| sshc->readdir_attrs = sftp_readdir(sshc->sftp_session, sshc->sftp_dir); | |
| if(sshc->readdir_attrs) { | |
| sshc->readdir_filename = sshc->readdir_attrs->name; | |
| sshc->readdir_longentry = sshc->readdir_attrs->longname; | |
| sshc->readdir_len = strlen(sshc->readdir_filename); | |
| if(data->set.list_only) { | |
| char *tmpLine; | |
| tmpLine = aprintf("%s\n", sshc->readdir_filename); | |
| if(!tmpLine) { | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->actualcode = CURLE_OUT_OF_MEMORY; | |
| break; | |
| } | |
| result = Curl_client_write(data, CLIENTWRITE_BODY, | |
| tmpLine, sshc->readdir_len + 1); | |
| free(tmpLine); | |
| if(result) { | |
| state(data, SSH_STOP); | |
| break; | |
| } | |
| /* since this counts what we send to the client, we include the | |
| newline in this counter */ | |
| data->req.bytecount += sshc->readdir_len + 1; | |
| /* output debug output if that is requested */ | |
| Curl_debug(data, CURLINFO_DATA_OUT, (char *)sshc->readdir_filename, | |
| sshc->readdir_len); | |
| } | |
| else { | |
| sshc->readdir_currLen = strlen(sshc->readdir_longentry); | |
| sshc->readdir_totalLen = 80 + sshc->readdir_currLen; | |
| sshc->readdir_line = calloc(sshc->readdir_totalLen, 1); | |
| if(!sshc->readdir_line) { | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->actualcode = CURLE_OUT_OF_MEMORY; | |
| break; | |
| } | |
| memcpy(sshc->readdir_line, sshc->readdir_longentry, | |
| sshc->readdir_currLen); | |
| if((sshc->readdir_attrs->flags & SSH_FILEXFER_ATTR_PERMISSIONS) && | |
| ((sshc->readdir_attrs->permissions & S_IFMT) == | |
| S_IFLNK)) { | |
| sshc->readdir_linkPath = aprintf("%s%s", protop->path, | |
| sshc->readdir_filename); | |
| if(!sshc->readdir_linkPath) { | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->actualcode = CURLE_OUT_OF_MEMORY; | |
| break; | |
| } | |
| state(data, SSH_SFTP_READDIR_LINK); | |
| break; | |
| } | |
| state(data, SSH_SFTP_READDIR_BOTTOM); | |
| break; | |
| } | |
| } | |
| else if(sftp_dir_eof(sshc->sftp_dir)) { | |
| state(data, SSH_SFTP_READDIR_DONE); | |
| break; | |
| } | |
| else { | |
| failf(data, "Could not open remote file for reading: %s", | |
| ssh_get_error(sshc->ssh_session)); | |
| MOVE_TO_SFTP_CLOSE_STATE(); | |
| break; | |
| } | |
| break; | |
| case SSH_SFTP_READDIR_LINK: | |
| if(sshc->readdir_link_attrs) | |
| sftp_attributes_free(sshc->readdir_link_attrs); | |
| sshc->readdir_link_attrs = sftp_lstat(sshc->sftp_session, | |
| sshc->readdir_linkPath); | |
| if(sshc->readdir_link_attrs == 0) { | |
| failf(data, "Could not read symlink for reading: %s", | |
| ssh_get_error(sshc->ssh_session)); | |
| MOVE_TO_SFTP_CLOSE_STATE(); | |
| break; | |
| } | |
| if(!sshc->readdir_link_attrs->name) { | |
| sshc->readdir_tmp = sftp_readlink(sshc->sftp_session, | |
| sshc->readdir_linkPath); | |
| if(!sshc->readdir_filename) | |
| sshc->readdir_len = 0; | |
| else | |
| sshc->readdir_len = strlen(sshc->readdir_tmp); | |
| sshc->readdir_longentry = NULL; | |
| sshc->readdir_filename = sshc->readdir_tmp; | |
| } | |
| else { | |
| sshc->readdir_len = strlen(sshc->readdir_link_attrs->name); | |
| sshc->readdir_filename = sshc->readdir_link_attrs->name; | |
| sshc->readdir_longentry = sshc->readdir_link_attrs->longname; | |
| } | |
| Curl_safefree(sshc->readdir_linkPath); | |
| /* get room for the filename and extra output */ | |
| sshc->readdir_totalLen += 4 + sshc->readdir_len; | |
| new_readdir_line = Curl_saferealloc(sshc->readdir_line, | |
| sshc->readdir_totalLen); | |
| if(!new_readdir_line) { | |
| sshc->readdir_line = NULL; | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->actualcode = CURLE_OUT_OF_MEMORY; | |
| break; | |
| } | |
| sshc->readdir_line = new_readdir_line; | |
| sshc->readdir_currLen += msnprintf(sshc->readdir_line + | |
| sshc->readdir_currLen, | |
| sshc->readdir_totalLen - | |
| sshc->readdir_currLen, | |
| " -> %s", | |
| sshc->readdir_filename); | |
| sftp_attributes_free(sshc->readdir_link_attrs); | |
| sshc->readdir_link_attrs = NULL; | |
| sshc->readdir_filename = NULL; | |
| sshc->readdir_longentry = NULL; | |
| state(data, SSH_SFTP_READDIR_BOTTOM); | |
| /* FALLTHROUGH */ | |
| case SSH_SFTP_READDIR_BOTTOM: | |
| sshc->readdir_currLen += msnprintf(sshc->readdir_line + | |
| sshc->readdir_currLen, | |
| sshc->readdir_totalLen - | |
| sshc->readdir_currLen, "\n"); | |
| result = Curl_client_write(data, CLIENTWRITE_BODY, | |
| sshc->readdir_line, | |
| sshc->readdir_currLen); | |
| if(!result) { | |
| /* output debug output if that is requested */ | |
| Curl_debug(data, CURLINFO_DATA_OUT, sshc->readdir_line, | |
| sshc->readdir_currLen); | |
| data->req.bytecount += sshc->readdir_currLen; | |
| } | |
| Curl_safefree(sshc->readdir_line); | |
| ssh_string_free_char(sshc->readdir_tmp); | |
| sshc->readdir_tmp = NULL; | |
| if(result) { | |
| state(data, SSH_STOP); | |
| } | |
| else | |
| state(data, SSH_SFTP_READDIR); | |
| break; | |
| case SSH_SFTP_READDIR_DONE: | |
| sftp_closedir(sshc->sftp_dir); | |
| sshc->sftp_dir = NULL; | |
| /* no data to transfer */ | |
| Curl_setup_transfer(data, -1, -1, FALSE, -1); | |
| state(data, SSH_STOP); | |
| break; | |
| case SSH_SFTP_DOWNLOAD_INIT: | |
| /* | |
| * Work on getting the specified file | |
| */ | |
| if(sshc->sftp_file) | |
| sftp_close(sshc->sftp_file); | |
| sshc->sftp_file = sftp_open(sshc->sftp_session, protop->path, | |
| O_RDONLY, (mode_t)data->set.new_file_perms); | |
| if(!sshc->sftp_file) { | |
| failf(data, "Could not open remote file for reading: %s", | |
| ssh_get_error(sshc->ssh_session)); | |
| MOVE_TO_SFTP_CLOSE_STATE(); | |
| break; | |
| } | |
| state(data, SSH_SFTP_DOWNLOAD_STAT); | |
| break; | |
| case SSH_SFTP_DOWNLOAD_STAT: | |
| { | |
| sftp_attributes attrs; | |
| curl_off_t size; | |
| attrs = sftp_fstat(sshc->sftp_file); | |
| if(!attrs || | |
| !(attrs->flags & SSH_FILEXFER_ATTR_SIZE) || | |
| (attrs->size == 0)) { | |
| /* | |
| * sftp_fstat didn't return an error, so maybe the server | |
| * just doesn't support stat() | |
| * OR the server doesn't return a file size with a stat() | |
| * OR file size is 0 | |
| */ | |
| data->req.size = -1; | |
| data->req.maxdownload = -1; | |
| Curl_pgrsSetDownloadSize(data, -1); | |
| size = 0; | |
| } | |
| else { | |
| size = attrs->size; | |
| sftp_attributes_free(attrs); | |
| if(size < 0) { | |
| failf(data, "Bad file size (%" CURL_FORMAT_CURL_OFF_T ")", size); | |
| return CURLE_BAD_DOWNLOAD_RESUME; | |
| } | |
| if(data->state.use_range) { | |
| curl_off_t from, to; | |
| char *ptr; | |
| char *ptr2; | |
| CURLofft to_t; | |
| CURLofft from_t; | |
| from_t = curlx_strtoofft(data->state.range, &ptr, 0, &from); | |
| if(from_t == CURL_OFFT_FLOW) { | |
| return CURLE_RANGE_ERROR; | |
| } | |
| while(*ptr && (ISSPACE(*ptr) || (*ptr == '-'))) | |
| ptr++; | |
| to_t = curlx_strtoofft(ptr, &ptr2, 0, &to); | |
| if(to_t == CURL_OFFT_FLOW) { | |
| return CURLE_RANGE_ERROR; | |
| } | |
| if((to_t == CURL_OFFT_INVAL) /* no "to" value given */ | |
| || (to >= size)) { | |
| to = size - 1; | |
| } | |
| if(from_t) { | |
| /* from is relative to end of file */ | |
| from = size - to; | |
| to = size - 1; | |
| } | |
| if(from > size) { | |
| failf(data, "Offset (%" | |
| CURL_FORMAT_CURL_OFF_T ") was beyond file size (%" | |
| CURL_FORMAT_CURL_OFF_T ")", from, size); | |
| return CURLE_BAD_DOWNLOAD_RESUME; | |
| } | |
| if(from > to) { | |
| from = to; | |
| size = 0; | |
| } | |
| else { | |
| size = to - from + 1; | |
| } | |
| rc = sftp_seek64(sshc->sftp_file, from); | |
| if(rc) { | |
| MOVE_TO_SFTP_CLOSE_STATE(); | |
| break; | |
| } | |
| } | |
| data->req.size = size; | |
| data->req.maxdownload = size; | |
| Curl_pgrsSetDownloadSize(data, size); | |
| } | |
| /* We can resume if we can seek to the resume position */ | |
| if(data->state.resume_from) { | |
| if(data->state.resume_from < 0) { | |
| /* We're supposed to download the last abs(from) bytes */ | |
| if((curl_off_t)size < -data->state.resume_from) { | |
| failf(data, "Offset (%" | |
| CURL_FORMAT_CURL_OFF_T ") was beyond file size (%" | |
| CURL_FORMAT_CURL_OFF_T ")", | |
| data->state.resume_from, size); | |
| return CURLE_BAD_DOWNLOAD_RESUME; | |
| } | |
| /* download from where? */ | |
| data->state.resume_from += size; | |
| } | |
| else { | |
| if((curl_off_t)size < data->state.resume_from) { | |
| failf(data, "Offset (%" CURL_FORMAT_CURL_OFF_T | |
| ") was beyond file size (%" CURL_FORMAT_CURL_OFF_T ")", | |
| data->state.resume_from, size); | |
| return CURLE_BAD_DOWNLOAD_RESUME; | |
| } | |
| } | |
| /* Now store the number of bytes we are expected to download */ | |
| data->req.size = size - data->state.resume_from; | |
| data->req.maxdownload = size - data->state.resume_from; | |
| Curl_pgrsSetDownloadSize(data, | |
| size - data->state.resume_from); | |
| rc = sftp_seek64(sshc->sftp_file, data->state.resume_from); | |
| if(rc) { | |
| MOVE_TO_SFTP_CLOSE_STATE(); | |
| break; | |
| } | |
| } | |
| } | |
| /* Setup the actual download */ | |
| if(data->req.size == 0) { | |
| /* no data to transfer */ | |
| Curl_setup_transfer(data, -1, -1, FALSE, -1); | |
| infof(data, "File already completely downloaded"); | |
| state(data, SSH_STOP); | |
| break; | |
| } | |
| Curl_setup_transfer(data, FIRSTSOCKET, data->req.size, FALSE, -1); | |
| /* not set by Curl_setup_transfer to preserve keepon bits */ | |
| conn->writesockfd = conn->sockfd; | |
| /* we want to use the _receiving_ function even when the socket turns | |
| out writableable as the underlying libssh recv function will deal | |
| with both accordingly */ | |
| conn->cselect_bits = CURL_CSELECT_IN; | |
| if(result) { | |
| /* this should never occur; the close state should be entered | |
| at the time the error occurs */ | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->actualcode = result; | |
| } | |
| else { | |
| sshc->sftp_recv_state = 0; | |
| state(data, SSH_STOP); | |
| } | |
| break; | |
| case SSH_SFTP_CLOSE: | |
| if(sshc->sftp_file) { | |
| sftp_close(sshc->sftp_file); | |
| sshc->sftp_file = NULL; | |
| } | |
| Curl_safefree(protop->path); | |
| DEBUGF(infof(data, "SFTP DONE done")); | |
| /* Check if nextstate is set and move .nextstate could be POSTQUOTE_INIT | |
| After nextstate is executed, the control should come back to | |
| SSH_SFTP_CLOSE to pass the correct result back */ | |
| if(sshc->nextstate != SSH_NO_STATE && | |
| sshc->nextstate != SSH_SFTP_CLOSE) { | |
| state(data, sshc->nextstate); | |
| sshc->nextstate = SSH_SFTP_CLOSE; | |
| } | |
| else { | |
| state(data, SSH_STOP); | |
| result = sshc->actualcode; | |
| } | |
| break; | |
| case SSH_SFTP_SHUTDOWN: | |
| /* during times we get here due to a broken transfer and then the | |
| sftp_handle might not have been taken down so make sure that is done | |
| before we proceed */ | |
| if(sshc->sftp_file) { | |
| sftp_close(sshc->sftp_file); | |
| sshc->sftp_file = NULL; | |
| } | |
| if(sshc->sftp_session) { | |
| sftp_free(sshc->sftp_session); | |
| sshc->sftp_session = NULL; | |
| } | |
| SSH_STRING_FREE_CHAR(sshc->homedir); | |
| data->state.most_recent_ftp_entrypath = NULL; | |
| state(data, SSH_SESSION_DISCONNECT); | |
| break; | |
| case SSH_SCP_TRANS_INIT: | |
| result = Curl_getworkingpath(data, sshc->homedir, &protop->path); | |
| if(result) { | |
| sshc->actualcode = result; | |
| state(data, SSH_STOP); | |
| break; | |
| } | |
| /* Functions from the SCP subsystem cannot handle/return SSH_AGAIN */ | |
| ssh_set_blocking(sshc->ssh_session, 1); | |
| if(data->set.upload) { | |
| if(data->state.infilesize < 0) { | |
| failf(data, "SCP requires a known file size for upload"); | |
| sshc->actualcode = CURLE_UPLOAD_FAILED; | |
| MOVE_TO_ERROR_STATE(CURLE_UPLOAD_FAILED); | |
| break; | |
| } | |
| sshc->scp_session = | |
| ssh_scp_new(sshc->ssh_session, SSH_SCP_WRITE, protop->path); | |
| state(data, SSH_SCP_UPLOAD_INIT); | |
| } | |
| else { | |
| sshc->scp_session = | |
| ssh_scp_new(sshc->ssh_session, SSH_SCP_READ, protop->path); | |
| state(data, SSH_SCP_DOWNLOAD_INIT); | |
| } | |
| if(!sshc->scp_session) { | |
| err_msg = ssh_get_error(sshc->ssh_session); | |
| failf(data, "%s", err_msg); | |
| MOVE_TO_ERROR_STATE(CURLE_UPLOAD_FAILED); | |
| } | |
| break; | |
| case SSH_SCP_UPLOAD_INIT: | |
| rc = ssh_scp_init(sshc->scp_session); | |
| if(rc != SSH_OK) { | |
| err_msg = ssh_get_error(sshc->ssh_session); | |
| failf(data, "%s", err_msg); | |
| MOVE_TO_ERROR_STATE(CURLE_UPLOAD_FAILED); | |
| break; | |
| } | |
| rc = ssh_scp_push_file(sshc->scp_session, protop->path, | |
| data->state.infilesize, | |
| (int)data->set.new_file_perms); | |
| if(rc != SSH_OK) { | |
| err_msg = ssh_get_error(sshc->ssh_session); | |
| failf(data, "%s", err_msg); | |
| MOVE_TO_ERROR_STATE(CURLE_UPLOAD_FAILED); | |
| break; | |
| } | |
| /* upload data */ | |
| Curl_setup_transfer(data, -1, data->req.size, FALSE, FIRSTSOCKET); | |
| /* not set by Curl_setup_transfer to preserve keepon bits */ | |
| conn->sockfd = conn->writesockfd; | |
| /* store this original bitmask setup to use later on if we can't | |
| figure out a "real" bitmask */ | |
| sshc->orig_waitfor = data->req.keepon; | |
| /* we want to use the _sending_ function even when the socket turns | |
| out readable as the underlying libssh scp send function will deal | |
| with both accordingly */ | |
| conn->cselect_bits = CURL_CSELECT_OUT; | |
| state(data, SSH_STOP); | |
| break; | |
| case SSH_SCP_DOWNLOAD_INIT: | |
| rc = ssh_scp_init(sshc->scp_session); | |
| if(rc != SSH_OK) { | |
| err_msg = ssh_get_error(sshc->ssh_session); | |
| failf(data, "%s", err_msg); | |
| MOVE_TO_ERROR_STATE(CURLE_COULDNT_CONNECT); | |
| break; | |
| } | |
| state(data, SSH_SCP_DOWNLOAD); | |
| /* FALLTHROUGH */ | |
| case SSH_SCP_DOWNLOAD:{ | |
| curl_off_t bytecount; | |
| rc = ssh_scp_pull_request(sshc->scp_session); | |
| if(rc != SSH_SCP_REQUEST_NEWFILE) { | |
| err_msg = ssh_get_error(sshc->ssh_session); | |
| failf(data, "%s", err_msg); | |
| MOVE_TO_ERROR_STATE(CURLE_REMOTE_FILE_NOT_FOUND); | |
| break; | |
| } | |
| /* download data */ | |
| bytecount = ssh_scp_request_get_size(sshc->scp_session); | |
| data->req.maxdownload = (curl_off_t) bytecount; | |
| Curl_setup_transfer(data, FIRSTSOCKET, bytecount, FALSE, -1); | |
| /* not set by Curl_setup_transfer to preserve keepon bits */ | |
| conn->writesockfd = conn->sockfd; | |
| /* we want to use the _receiving_ function even when the socket turns | |
| out writableable as the underlying libssh recv function will deal | |
| with both accordingly */ | |
| conn->cselect_bits = CURL_CSELECT_IN; | |
| state(data, SSH_STOP); | |
| break; | |
| } | |
| case SSH_SCP_DONE: | |
| if(data->set.upload) | |
| state(data, SSH_SCP_SEND_EOF); | |
| else | |
| state(data, SSH_SCP_CHANNEL_FREE); | |
| break; | |
| case SSH_SCP_SEND_EOF: | |
| if(sshc->scp_session) { | |
| rc = ssh_scp_close(sshc->scp_session); | |
| if(rc == SSH_AGAIN) { | |
| /* Currently the ssh_scp_close handles waiting for EOF in | |
| * blocking way. | |
| */ | |
| break; | |
| } | |
| if(rc != SSH_OK) { | |
| infof(data, "Failed to close libssh scp channel: %s", | |
| ssh_get_error(sshc->ssh_session)); | |
| } | |
| } | |
| state(data, SSH_SCP_CHANNEL_FREE); | |
| break; | |
| case SSH_SCP_CHANNEL_FREE: | |
| if(sshc->scp_session) { | |
| ssh_scp_free(sshc->scp_session); | |
| sshc->scp_session = NULL; | |
| } | |
| DEBUGF(infof(data, "SCP DONE phase complete")); | |
| ssh_set_blocking(sshc->ssh_session, 0); | |
| state(data, SSH_SESSION_DISCONNECT); | |
| /* FALLTHROUGH */ | |
| case SSH_SESSION_DISCONNECT: | |
| /* during weird times when we've been prematurely aborted, the channel | |
| is still alive when we reach this state and we MUST kill the channel | |
| properly first */ | |
| if(sshc->scp_session) { | |
| ssh_scp_free(sshc->scp_session); | |
| sshc->scp_session = NULL; | |
| } | |
| ssh_disconnect(sshc->ssh_session); | |
| SSH_STRING_FREE_CHAR(sshc->homedir); | |
| data->state.most_recent_ftp_entrypath = NULL; | |
| state(data, SSH_SESSION_FREE); | |
| /* FALLTHROUGH */ | |
| case SSH_SESSION_FREE: | |
| if(sshc->ssh_session) { | |
| ssh_free(sshc->ssh_session); | |
| sshc->ssh_session = NULL; | |
| } | |
| /* worst-case scenario cleanup */ | |
| DEBUGASSERT(sshc->ssh_session == NULL); | |
| DEBUGASSERT(sshc->scp_session == NULL); | |
| if(sshc->readdir_tmp) { | |
| ssh_string_free_char(sshc->readdir_tmp); | |
| sshc->readdir_tmp = NULL; | |
| } | |
| if(sshc->quote_attrs) | |
| sftp_attributes_free(sshc->quote_attrs); | |
| if(sshc->readdir_attrs) | |
| sftp_attributes_free(sshc->readdir_attrs); | |
| if(sshc->readdir_link_attrs) | |
| sftp_attributes_free(sshc->readdir_link_attrs); | |
| if(sshc->privkey) | |
| ssh_key_free(sshc->privkey); | |
| if(sshc->pubkey) | |
| ssh_key_free(sshc->pubkey); | |
| Curl_safefree(sshc->rsa_pub); | |
| Curl_safefree(sshc->rsa); | |
| Curl_safefree(sshc->quote_path1); | |
| Curl_safefree(sshc->quote_path2); | |
| Curl_safefree(sshc->readdir_line); | |
| Curl_safefree(sshc->readdir_linkPath); | |
| SSH_STRING_FREE_CHAR(sshc->homedir); | |
| /* the code we are about to return */ | |
| result = sshc->actualcode; | |
| memset(sshc, 0, sizeof(struct ssh_conn)); | |
| connclose(conn, "SSH session free"); | |
| sshc->state = SSH_SESSION_FREE; /* current */ | |
| sshc->nextstate = SSH_NO_STATE; | |
| state(data, SSH_STOP); | |
| break; | |
| case SSH_QUIT: | |
| /* fallthrough, just stop! */ | |
| default: | |
| /* internal error */ | |
| sshc->nextstate = SSH_NO_STATE; | |
| state(data, SSH_STOP); | |
| break; | |
| } | |
| } while(!rc && (sshc->state != SSH_STOP)); | |
| if(rc == SSH_AGAIN) { | |
| /* we would block, we need to wait for the socket to be ready (in the | |
| right direction too)! */ | |
| *block = TRUE; | |
| } | |
| return result; | |
| } | |
| /* called by the multi interface to figure out what socket(s) to wait for and | |
| for what actions in the DO_DONE, PERFORM and WAITPERFORM states */ | |
| static int myssh_getsock(struct Curl_easy *data, | |
| struct connectdata *conn, | |
| curl_socket_t *sock) | |
| { | |
| int bitmap = GETSOCK_BLANK; | |
| (void)data; | |
| sock[0] = conn->sock[FIRSTSOCKET]; | |
| if(conn->waitfor & KEEP_RECV) | |
| bitmap |= GETSOCK_READSOCK(FIRSTSOCKET); | |
| if(conn->waitfor & KEEP_SEND) | |
| bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET); | |
| return bitmap; | |
| } | |
| static void myssh_block2waitfor(struct connectdata *conn, bool block) | |
| { | |
| struct ssh_conn *sshc = &conn->proto.sshc; | |
| /* If it didn't block, or nothing was returned by ssh_get_poll_flags | |
| * have the original set */ | |
| conn->waitfor = sshc->orig_waitfor; | |
| if(block) { | |
| int dir = ssh_get_poll_flags(sshc->ssh_session); | |
| if(dir & SSH_READ_PENDING) { | |
| /* translate the libssh define bits into our own bit defines */ | |
| conn->waitfor = KEEP_RECV; | |
| } | |
| else if(dir & SSH_WRITE_PENDING) { | |
| conn->waitfor = KEEP_SEND; | |
| } | |
| } | |
| } | |
| /* called repeatedly until done from multi.c */ | |
| static CURLcode myssh_multi_statemach(struct Curl_easy *data, | |
| bool *done) | |
| { | |
| struct connectdata *conn = data->conn; | |
| struct ssh_conn *sshc = &conn->proto.sshc; | |
| bool block; /* we store the status and use that to provide a ssh_getsock() | |
| implementation */ | |
| CURLcode result = myssh_statemach_act(data, &block); | |
| *done = (sshc->state == SSH_STOP) ? TRUE : FALSE; | |
| myssh_block2waitfor(conn, block); | |
| return result; | |
| } | |
| static CURLcode myssh_block_statemach(struct Curl_easy *data, | |
| bool disconnect) | |
| { | |
| struct connectdata *conn = data->conn; | |
| struct ssh_conn *sshc = &conn->proto.sshc; | |
| CURLcode result = CURLE_OK; | |
| while((sshc->state != SSH_STOP) && !result) { | |
| bool block; | |
| timediff_t left = 1000; | |
| struct curltime now = Curl_now(); | |
| result = myssh_statemach_act(data, &block); | |
| if(result) | |
| break; | |
| if(!disconnect) { | |
| if(Curl_pgrsUpdate(data)) | |
| return CURLE_ABORTED_BY_CALLBACK; | |
| result = Curl_speedcheck(data, now); | |
| if(result) | |
| break; | |
| left = Curl_timeleft(data, NULL, FALSE); | |
| if(left < 0) { | |
| failf(data, "Operation timed out"); | |
| return CURLE_OPERATION_TIMEDOUT; | |
| } | |
| } | |
| if(block) { | |
| curl_socket_t fd_read = conn->sock[FIRSTSOCKET]; | |
| /* wait for the socket to become ready */ | |
| (void) Curl_socket_check(fd_read, CURL_SOCKET_BAD, | |
| CURL_SOCKET_BAD, left > 1000 ? 1000 : left); | |
| } | |
| } | |
| return result; | |
| } | |
| /* | |
| * SSH setup connection | |
| */ | |
| static CURLcode myssh_setup_connection(struct Curl_easy *data, | |
| struct connectdata *conn) | |
| { | |
| struct SSHPROTO *ssh; | |
| (void)conn; | |
| data->req.p.ssh = ssh = calloc(1, sizeof(struct SSHPROTO)); | |
| if(!ssh) | |
| return CURLE_OUT_OF_MEMORY; | |
| return CURLE_OK; | |
| } | |
| static Curl_recv scp_recv, sftp_recv; | |
| static Curl_send scp_send, sftp_send; | |
| /* | |
| * Curl_ssh_connect() gets called from Curl_protocol_connect() to allow us to | |
| * do protocol-specific actions at connect-time. | |
| */ | |
| static CURLcode myssh_connect(struct Curl_easy *data, bool *done) | |
| { | |
| struct ssh_conn *ssh; | |
| CURLcode result; | |
| struct connectdata *conn = data->conn; | |
| curl_socket_t sock = conn->sock[FIRSTSOCKET]; | |
| int rc; | |
| /* initialize per-handle data if not already */ | |
| if(!data->req.p.ssh) | |
| myssh_setup_connection(data, conn); | |
| /* We default to persistent connections. We set this already in this connect | |
| function to make the re-use checks properly be able to check this bit. */ | |
| connkeep(conn, "SSH default"); | |
| if(conn->handler->protocol & CURLPROTO_SCP) { | |
| conn->recv[FIRSTSOCKET] = scp_recv; | |
| conn->send[FIRSTSOCKET] = scp_send; | |
| } | |
| else { | |
| conn->recv[FIRSTSOCKET] = sftp_recv; | |
| conn->send[FIRSTSOCKET] = sftp_send; | |
| } | |
| ssh = &conn->proto.sshc; | |
| ssh->ssh_session = ssh_new(); | |
| if(!ssh->ssh_session) { | |
| failf(data, "Failure initialising ssh session"); | |
| return CURLE_FAILED_INIT; | |
| } | |
| rc = ssh_options_set(ssh->ssh_session, SSH_OPTIONS_HOST, conn->host.name); | |
| if(rc != SSH_OK) { | |
| failf(data, "Could not set remote host"); | |
| return CURLE_FAILED_INIT; | |
| } | |
| rc = ssh_options_parse_config(ssh->ssh_session, NULL); | |
| if(rc != SSH_OK) { | |
| infof(data, "Could not parse SSH configuration files"); | |
| /* ignore */ | |
| } | |
| rc = ssh_options_set(ssh->ssh_session, SSH_OPTIONS_FD, &sock); | |
| if(rc != SSH_OK) { | |
| failf(data, "Could not set socket"); | |
| return CURLE_FAILED_INIT; | |
| } | |
| if(conn->user && conn->user[0] != '\0') { | |
| infof(data, "User: %s", conn->user); | |
| rc = ssh_options_set(ssh->ssh_session, SSH_OPTIONS_USER, conn->user); | |
| if(rc != SSH_OK) { | |
| failf(data, "Could not set user"); | |
| return CURLE_FAILED_INIT; | |
| } | |
| } | |
| if(data->set.str[STRING_SSH_KNOWNHOSTS]) { | |
| infof(data, "Known hosts: %s", data->set.str[STRING_SSH_KNOWNHOSTS]); | |
| rc = ssh_options_set(ssh->ssh_session, SSH_OPTIONS_KNOWNHOSTS, | |
| data->set.str[STRING_SSH_KNOWNHOSTS]); | |
| if(rc != SSH_OK) { | |
| failf(data, "Could not set known hosts file path"); | |
| return CURLE_FAILED_INIT; | |
| } | |
| } | |
| if(conn->remote_port) { | |
| rc = ssh_options_set(ssh->ssh_session, SSH_OPTIONS_PORT, | |
| &conn->remote_port); | |
| if(rc != SSH_OK) { | |
| failf(data, "Could not set remote port"); | |
| return CURLE_FAILED_INIT; | |
| } | |
| } | |
| if(data->set.ssh_compression) { | |
| rc = ssh_options_set(ssh->ssh_session, SSH_OPTIONS_COMPRESSION, | |
| "zlib,zlib@openssh.com,none"); | |
| if(rc != SSH_OK) { | |
| failf(data, "Could not set compression"); | |
| return CURLE_FAILED_INIT; | |
| } | |
| } | |
| ssh->privkey = NULL; | |
| ssh->pubkey = NULL; | |
| if(data->set.str[STRING_SSH_PUBLIC_KEY]) { | |
| rc = ssh_pki_import_pubkey_file(data->set.str[STRING_SSH_PUBLIC_KEY], | |
| &ssh->pubkey); | |
| if(rc != SSH_OK) { | |
| failf(data, "Could not load public key file"); | |
| return CURLE_FAILED_INIT; | |
| } | |
| } | |
| /* we do not verify here, we do it at the state machine, | |
| * after connection */ | |
| state(data, SSH_INIT); | |
| result = myssh_multi_statemach(data, done); | |
| return result; | |
| } | |
| /* called from multi.c while DOing */ | |
| static CURLcode scp_doing(struct Curl_easy *data, bool *dophase_done) | |
| { | |
| CURLcode result; | |
| result = myssh_multi_statemach(data, dophase_done); | |
| if(*dophase_done) { | |
| DEBUGF(infof(data, "DO phase is complete")); | |
| } | |
| return result; | |
| } | |
| /* | |
| *********************************************************************** | |
| * | |
| * scp_perform() | |
| * | |
| * This is the actual DO function for SCP. Get a file according to | |
| * the options previously setup. | |
| */ | |
| static | |
| CURLcode scp_perform(struct Curl_easy *data, | |
| bool *connected, bool *dophase_done) | |
| { | |
| CURLcode result = CURLE_OK; | |
| struct connectdata *conn = data->conn; | |
| DEBUGF(infof(data, "DO phase starts")); | |
| *dophase_done = FALSE; /* not done yet */ | |
| /* start the first command in the DO phase */ | |
| state(data, SSH_SCP_TRANS_INIT); | |
| result = myssh_multi_statemach(data, dophase_done); | |
| *connected = conn->bits.tcpconnect[FIRSTSOCKET]; | |
| if(*dophase_done) { | |
| DEBUGF(infof(data, "DO phase is complete")); | |
| } | |
| return result; | |
| } | |
| static CURLcode myssh_do_it(struct Curl_easy *data, bool *done) | |
| { | |
| CURLcode result; | |
| bool connected = 0; | |
| struct connectdata *conn = data->conn; | |
| struct ssh_conn *sshc = &conn->proto.sshc; | |
| *done = FALSE; /* default to false */ | |
| data->req.size = -1; /* make sure this is unknown at this point */ | |
| sshc->actualcode = CURLE_OK; /* reset error code */ | |
| sshc->secondCreateDirs = 0; /* reset the create dir attempt state | |
| variable */ | |
| Curl_pgrsSetUploadCounter(data, 0); | |
| Curl_pgrsSetDownloadCounter(data, 0); | |
| Curl_pgrsSetUploadSize(data, -1); | |
| Curl_pgrsSetDownloadSize(data, -1); | |
| if(conn->handler->protocol & CURLPROTO_SCP) | |
| result = scp_perform(data, &connected, done); | |
| else | |
| result = sftp_perform(data, &connected, done); | |
| return result; | |
| } | |
| /* BLOCKING, but the function is using the state machine so the only reason | |
| this is still blocking is that the multi interface code has no support for | |
| disconnecting operations that takes a while */ | |
| static CURLcode scp_disconnect(struct Curl_easy *data, | |
| struct connectdata *conn, | |
| bool dead_connection) | |
| { | |
| CURLcode result = CURLE_OK; | |
| struct ssh_conn *ssh = &conn->proto.sshc; | |
| (void) dead_connection; | |
| if(ssh->ssh_session) { | |
| /* only if there's a session still around to use! */ | |
| state(data, SSH_SESSION_DISCONNECT); | |
| result = myssh_block_statemach(data, TRUE); | |
| } | |
| return result; | |
| } | |
| /* generic done function for both SCP and SFTP called from their specific | |
| done functions */ | |
| static CURLcode myssh_done(struct Curl_easy *data, CURLcode status) | |
| { | |
| CURLcode result = CURLE_OK; | |
| struct SSHPROTO *protop = data->req.p.ssh; | |
| if(!status) { | |
| /* run the state-machine */ | |
| result = myssh_block_statemach(data, FALSE); | |
| } | |
| else | |
| result = status; | |
| if(protop) | |
| Curl_safefree(protop->path); | |
| if(Curl_pgrsDone(data)) | |
| return CURLE_ABORTED_BY_CALLBACK; | |
| data->req.keepon = 0; /* clear all bits */ | |
| return result; | |
| } | |
| static CURLcode scp_done(struct Curl_easy *data, CURLcode status, | |
| bool premature) | |
| { | |
| (void) premature; /* not used */ | |
| if(!status) | |
| state(data, SSH_SCP_DONE); | |
| return myssh_done(data, status); | |
| } | |
| static ssize_t scp_send(struct Curl_easy *data, int sockindex, | |
| const void *mem, size_t len, CURLcode *err) | |
| { | |
| int rc; | |
| struct connectdata *conn = data->conn; | |
| (void) sockindex; /* we only support SCP on the fixed known primary socket */ | |
| (void) err; | |
| rc = ssh_scp_write(conn->proto.sshc.scp_session, mem, len); | |
| #if 0 | |
| /* The following code is misleading, mostly added as wishful thinking | |
| * that libssh at some point will implement non-blocking ssh_scp_write/read. | |
| * Currently rc can only be number of bytes read or SSH_ERROR. */ | |
| myssh_block2waitfor(conn, (rc == SSH_AGAIN) ? TRUE : FALSE); | |
| if(rc == SSH_AGAIN) { | |
| *err = CURLE_AGAIN; | |
| return 0; | |
| } | |
| else | |
| #endif | |
| if(rc != SSH_OK) { | |
| *err = CURLE_SSH; | |
| return -1; | |
| } | |
| return len; | |
| } | |
| static ssize_t scp_recv(struct Curl_easy *data, int sockindex, | |
| char *mem, size_t len, CURLcode *err) | |
| { | |
| ssize_t nread; | |
| struct connectdata *conn = data->conn; | |
| (void) err; | |
| (void) sockindex; /* we only support SCP on the fixed known primary socket */ | |
| /* libssh returns int */ | |
| nread = ssh_scp_read(conn->proto.sshc.scp_session, mem, len); | |
| #if 0 | |
| /* The following code is misleading, mostly added as wishful thinking | |
| * that libssh at some point will implement non-blocking ssh_scp_write/read. | |
| * Currently rc can only be SSH_OK or SSH_ERROR. */ | |
| myssh_block2waitfor(conn, (nread == SSH_AGAIN) ? TRUE : FALSE); | |
| if(nread == SSH_AGAIN) { | |
| *err = CURLE_AGAIN; | |
| nread = -1; | |
| } | |
| #endif | |
| return nread; | |
| } | |
| /* | |
| * =============== SFTP =============== | |
| */ | |
| /* | |
| *********************************************************************** | |
| * | |
| * 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 Curl_easy *data, | |
| bool *connected, | |
| bool *dophase_done) | |
| { | |
| CURLcode result = CURLE_OK; | |
| struct connectdata *conn = data->conn; | |
| DEBUGF(infof(data, "DO phase starts")); | |
| *dophase_done = FALSE; /* not done yet */ | |
| /* start the first command in the DO phase */ | |
| state(data, SSH_SFTP_QUOTE_INIT); | |
| /* run the state-machine */ | |
| result = myssh_multi_statemach(data, dophase_done); | |
| *connected = conn->bits.tcpconnect[FIRSTSOCKET]; | |
| if(*dophase_done) { | |
| DEBUGF(infof(data, "DO phase is complete")); | |
| } | |
| return result; | |
| } | |
| /* called from multi.c while DOing */ | |
| static CURLcode sftp_doing(struct Curl_easy *data, | |
| bool *dophase_done) | |
| { | |
| CURLcode result = myssh_multi_statemach(data, dophase_done); | |
| if(*dophase_done) { | |
| DEBUGF(infof(data, "DO phase is complete")); | |
| } | |
| return result; | |
| } | |
| /* BLOCKING, but the function is using the state machine so the only reason | |
| this is still blocking is that the multi interface code has no support for | |
| disconnecting operations that takes a while */ | |
| static CURLcode sftp_disconnect(struct Curl_easy *data, | |
| struct connectdata *conn, | |
| bool dead_connection) | |
| { | |
| CURLcode result = CURLE_OK; | |
| (void) dead_connection; | |
| DEBUGF(infof(data, "SSH DISCONNECT starts now")); | |
| if(conn->proto.sshc.ssh_session) { | |
| /* only if there's a session still around to use! */ | |
| state(data, SSH_SFTP_SHUTDOWN); | |
| result = myssh_block_statemach(data, TRUE); | |
| } | |
| DEBUGF(infof(data, "SSH DISCONNECT is done")); | |
| return result; | |
| } | |
| static CURLcode sftp_done(struct Curl_easy *data, CURLcode status, | |
| bool premature) | |
| { | |
| struct connectdata *conn = data->conn; | |
| struct ssh_conn *sshc = &conn->proto.sshc; | |
| if(!status) { | |
| /* Post quote commands are executed after the SFTP_CLOSE state to avoid | |
| errors that could happen due to open file handles during POSTQUOTE | |
| operation */ | |
| if(!premature && data->set.postquote && !conn->bits.retry) | |
| sshc->nextstate = SSH_SFTP_POSTQUOTE_INIT; | |
| state(data, SSH_SFTP_CLOSE); | |
| } | |
| return myssh_done(data, status); | |
| } | |
| /* return number of sent bytes */ | |
| static ssize_t sftp_send(struct Curl_easy *data, int sockindex, | |
| const void *mem, size_t len, CURLcode *err) | |
| { | |
| ssize_t nwrite; | |
| struct connectdata *conn = data->conn; | |
| (void)sockindex; | |
| nwrite = sftp_write(conn->proto.sshc.sftp_file, mem, len); | |
| myssh_block2waitfor(conn, FALSE); | |
| #if 0 /* not returned by libssh on write */ | |
| if(nwrite == SSH_AGAIN) { | |
| *err = CURLE_AGAIN; | |
| nwrite = 0; | |
| } | |
| else | |
| #endif | |
| if(nwrite < 0) { | |
| *err = CURLE_SSH; | |
| nwrite = -1; | |
| } | |
| return nwrite; | |
| } | |
| /* | |
| * Return number of received (decrypted) bytes | |
| * or <0 on error | |
| */ | |
| static ssize_t sftp_recv(struct Curl_easy *data, int sockindex, | |
| char *mem, size_t len, CURLcode *err) | |
| { | |
| ssize_t nread; | |
| struct connectdata *conn = data->conn; | |
| (void)sockindex; | |
| DEBUGASSERT(len < CURL_MAX_READ_SIZE); | |
| switch(conn->proto.sshc.sftp_recv_state) { | |
| case 0: | |
| conn->proto.sshc.sftp_file_index = | |
| sftp_async_read_begin(conn->proto.sshc.sftp_file, | |
| (uint32_t)len); | |
| if(conn->proto.sshc.sftp_file_index < 0) { | |
| *err = CURLE_RECV_ERROR; | |
| return -1; | |
| } | |
| /* FALLTHROUGH */ | |
| case 1: | |
| conn->proto.sshc.sftp_recv_state = 1; | |
| nread = sftp_async_read(conn->proto.sshc.sftp_file, | |
| mem, (uint32_t)len, | |
| conn->proto.sshc.sftp_file_index); | |
| myssh_block2waitfor(conn, (nread == SSH_AGAIN)?TRUE:FALSE); | |
| if(nread == SSH_AGAIN) { | |
| *err = CURLE_AGAIN; | |
| return -1; | |
| } | |
| else if(nread < 0) { | |
| *err = CURLE_RECV_ERROR; | |
| return -1; | |
| } | |
| conn->proto.sshc.sftp_recv_state = 0; | |
| return nread; | |
| default: | |
| /* we never reach here */ | |
| return -1; | |
| } | |
| } | |
| static void sftp_quote(struct Curl_easy *data) | |
| { | |
| const char *cp; | |
| struct connectdata *conn = data->conn; | |
| struct SSHPROTO *protop = data->req.p.ssh; | |
| struct ssh_conn *sshc = &conn->proto.sshc; | |
| CURLcode result; | |
| /* | |
| * Support some of the "FTP" commands | |
| */ | |
| char *cmd = sshc->quote_item->data; | |
| sshc->acceptfail = FALSE; | |
| /* if a command starts with an asterisk, which a legal SFTP command never | |
| can, the command will be allowed to fail without it causing any | |
| aborts or cancels etc. It will cause libcurl to act as if the command | |
| is successful, whatever the server reponds. */ | |
| if(cmd[0] == '*') { | |
| cmd++; | |
| sshc->acceptfail = TRUE; | |
| } | |
| if(strcasecompare("pwd", cmd)) { | |
| /* output debug output if that is requested */ | |
| char *tmp = aprintf("257 \"%s\" is current directory.\n", | |
| protop->path); | |
| if(!tmp) { | |
| sshc->actualcode = CURLE_OUT_OF_MEMORY; | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->nextstate = SSH_NO_STATE; | |
| return; | |
| } | |
| Curl_debug(data, CURLINFO_HEADER_OUT, (char *) "PWD\n", 4); | |
| Curl_debug(data, CURLINFO_HEADER_IN, tmp, strlen(tmp)); | |
| /* this sends an FTP-like "header" to the header callback so that the | |
| current directory can be read very similar to how it is read when | |
| using ordinary FTP. */ | |
| result = Curl_client_write(data, CLIENTWRITE_HEADER, tmp, strlen(tmp)); | |
| free(tmp); | |
| if(result) { | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->nextstate = SSH_NO_STATE; | |
| sshc->actualcode = result; | |
| } | |
| else | |
| state(data, SSH_SFTP_NEXT_QUOTE); | |
| return; | |
| } | |
| /* | |
| * the arguments following the command must be separated from the | |
| * command with a space so we can check for it unconditionally | |
| */ | |
| cp = strchr(cmd, ' '); | |
| if(!cp) { | |
| failf(data, "Syntax error in SFTP command. Supply parameter(s)!"); | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->nextstate = SSH_NO_STATE; | |
| sshc->actualcode = CURLE_QUOTE_ERROR; | |
| return; | |
| } | |
| /* | |
| * also, every command takes at least one argument so we get that | |
| * first argument right now | |
| */ | |
| result = Curl_get_pathname(&cp, &sshc->quote_path1, sshc->homedir); | |
| if(result) { | |
| if(result == CURLE_OUT_OF_MEMORY) | |
| failf(data, "Out of memory"); | |
| else | |
| failf(data, "Syntax error: Bad first parameter"); | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->nextstate = SSH_NO_STATE; | |
| sshc->actualcode = result; | |
| return; | |
| } | |
| /* | |
| * SFTP is a binary protocol, so we don't send text commands | |
| * to the server. Instead, we scan for commands used by | |
| * OpenSSH's sftp program and call the appropriate libssh | |
| * functions. | |
| */ | |
| if(strncasecompare(cmd, "chgrp ", 6) || | |
| strncasecompare(cmd, "chmod ", 6) || | |
| strncasecompare(cmd, "chown ", 6) || | |
| strncasecompare(cmd, "atime ", 6) || | |
| strncasecompare(cmd, "mtime ", 6)) { | |
| /* attribute change */ | |
| /* sshc->quote_path1 contains the mode to set */ | |
| /* get the destination */ | |
| result = Curl_get_pathname(&cp, &sshc->quote_path2, sshc->homedir); | |
| if(result) { | |
| if(result == CURLE_OUT_OF_MEMORY) | |
| failf(data, "Out of memory"); | |
| else | |
| failf(data, "Syntax error in chgrp/chmod/chown/atime/mtime: " | |
| "Bad second parameter"); | |
| Curl_safefree(sshc->quote_path1); | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->nextstate = SSH_NO_STATE; | |
| sshc->actualcode = result; | |
| return; | |
| } | |
| sshc->quote_attrs = NULL; | |
| state(data, SSH_SFTP_QUOTE_STAT); | |
| return; | |
| } | |
| if(strncasecompare(cmd, "ln ", 3) || | |
| strncasecompare(cmd, "symlink ", 8)) { | |
| /* symbolic linking */ | |
| /* sshc->quote_path1 is the source */ | |
| /* get the destination */ | |
| result = Curl_get_pathname(&cp, &sshc->quote_path2, sshc->homedir); | |
| if(result) { | |
| if(result == 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); | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->nextstate = SSH_NO_STATE; | |
| sshc->actualcode = result; | |
| return; | |
| } | |
| state(data, SSH_SFTP_QUOTE_SYMLINK); | |
| return; | |
| } | |
| else if(strncasecompare(cmd, "mkdir ", 6)) { | |
| /* create dir */ | |
| state(data, SSH_SFTP_QUOTE_MKDIR); | |
| return; | |
| } | |
| else if(strncasecompare(cmd, "rename ", 7)) { | |
| /* rename file */ | |
| /* first param is the source path */ | |
| /* second param is the dest. path */ | |
| result = Curl_get_pathname(&cp, &sshc->quote_path2, sshc->homedir); | |
| if(result) { | |
| if(result == CURLE_OUT_OF_MEMORY) | |
| failf(data, "Out of memory"); | |
| else | |
| failf(data, "Syntax error in rename: Bad second parameter"); | |
| Curl_safefree(sshc->quote_path1); | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->nextstate = SSH_NO_STATE; | |
| sshc->actualcode = result; | |
| return; | |
| } | |
| state(data, SSH_SFTP_QUOTE_RENAME); | |
| return; | |
| } | |
| else if(strncasecompare(cmd, "rmdir ", 6)) { | |
| /* delete dir */ | |
| state(data, SSH_SFTP_QUOTE_RMDIR); | |
| return; | |
| } | |
| else if(strncasecompare(cmd, "rm ", 3)) { | |
| state(data, SSH_SFTP_QUOTE_UNLINK); | |
| return; | |
| } | |
| #ifdef HAS_STATVFS_SUPPORT | |
| else if(strncasecompare(cmd, "statvfs ", 8)) { | |
| state(data, SSH_SFTP_QUOTE_STATVFS); | |
| return; | |
| } | |
| #endif | |
| failf(data, "Unknown SFTP command"); | |
| Curl_safefree(sshc->quote_path1); | |
| Curl_safefree(sshc->quote_path2); | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->nextstate = SSH_NO_STATE; | |
| sshc->actualcode = CURLE_QUOTE_ERROR; | |
| } | |
| static void sftp_quote_stat(struct Curl_easy *data) | |
| { | |
| struct connectdata *conn = data->conn; | |
| struct ssh_conn *sshc = &conn->proto.sshc; | |
| char *cmd = sshc->quote_item->data; | |
| sshc->acceptfail = FALSE; | |
| /* if a command starts with an asterisk, which a legal SFTP command never | |
| can, the command will be allowed to fail without it causing any | |
| aborts or cancels etc. It will cause libcurl to act as if the command | |
| is successful, whatever the server reponds. */ | |
| if(cmd[0] == '*') { | |
| cmd++; | |
| sshc->acceptfail = TRUE; | |
| } | |
| /* We read the file attributes, store them in sshc->quote_attrs | |
| * and modify them accordingly to command. Then we switch to | |
| * QUOTE_SETSTAT state to write new ones. | |
| */ | |
| if(sshc->quote_attrs) | |
| sftp_attributes_free(sshc->quote_attrs); | |
| sshc->quote_attrs = sftp_stat(sshc->sftp_session, sshc->quote_path2); | |
| if(!sshc->quote_attrs) { | |
| Curl_safefree(sshc->quote_path1); | |
| Curl_safefree(sshc->quote_path2); | |
| failf(data, "Attempt to get SFTP stats failed: %d", | |
| sftp_get_error(sshc->sftp_session)); | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->nextstate = SSH_NO_STATE; | |
| sshc->actualcode = CURLE_QUOTE_ERROR; | |
| return; | |
| } | |
| /* Now set the new attributes... */ | |
| if(strncasecompare(cmd, "chgrp", 5)) { | |
| sshc->quote_attrs->gid = (uint32_t)strtoul(sshc->quote_path1, NULL, 10); | |
| if(sshc->quote_attrs->gid == 0 && !ISDIGIT(sshc->quote_path1[0]) && | |
| !sshc->acceptfail) { | |
| Curl_safefree(sshc->quote_path1); | |
| Curl_safefree(sshc->quote_path2); | |
| failf(data, "Syntax error: chgrp gid not a number"); | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->nextstate = SSH_NO_STATE; | |
| sshc->actualcode = CURLE_QUOTE_ERROR; | |
| return; | |
| } | |
| sshc->quote_attrs->flags |= SSH_FILEXFER_ATTR_UIDGID; | |
| } | |
| else if(strncasecompare(cmd, "chmod", 5)) { | |
| mode_t perms; | |
| perms = (mode_t)strtoul(sshc->quote_path1, NULL, 8); | |
| /* permissions are octal */ | |
| if(perms == 0 && !ISDIGIT(sshc->quote_path1[0])) { | |
| Curl_safefree(sshc->quote_path1); | |
| Curl_safefree(sshc->quote_path2); | |
| failf(data, "Syntax error: chmod permissions not a number"); | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->nextstate = SSH_NO_STATE; | |
| sshc->actualcode = CURLE_QUOTE_ERROR; | |
| return; | |
| } | |
| sshc->quote_attrs->permissions = perms; | |
| sshc->quote_attrs->flags |= SSH_FILEXFER_ATTR_PERMISSIONS; | |
| } | |
| else if(strncasecompare(cmd, "chown", 5)) { | |
| sshc->quote_attrs->uid = (uint32_t)strtoul(sshc->quote_path1, NULL, 10); | |
| if(sshc->quote_attrs->uid == 0 && !ISDIGIT(sshc->quote_path1[0]) && | |
| !sshc->acceptfail) { | |
| Curl_safefree(sshc->quote_path1); | |
| Curl_safefree(sshc->quote_path2); | |
| failf(data, "Syntax error: chown uid not a number"); | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->nextstate = SSH_NO_STATE; | |
| sshc->actualcode = CURLE_QUOTE_ERROR; | |
| return; | |
| } | |
| sshc->quote_attrs->flags |= SSH_FILEXFER_ATTR_UIDGID; | |
| } | |
| else if(strncasecompare(cmd, "atime", 5)) { | |
| time_t date = Curl_getdate_capped(sshc->quote_path1); | |
| if(date == -1) { | |
| Curl_safefree(sshc->quote_path1); | |
| Curl_safefree(sshc->quote_path2); | |
| failf(data, "Syntax error: incorrect access date format"); | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->nextstate = SSH_NO_STATE; | |
| sshc->actualcode = CURLE_QUOTE_ERROR; | |
| return; | |
| } | |
| sshc->quote_attrs->atime = (uint32_t)date; | |
| sshc->quote_attrs->flags |= SSH_FILEXFER_ATTR_ACMODTIME; | |
| } | |
| else if(strncasecompare(cmd, "mtime", 5)) { | |
| time_t date = Curl_getdate_capped(sshc->quote_path1); | |
| if(date == -1) { | |
| Curl_safefree(sshc->quote_path1); | |
| Curl_safefree(sshc->quote_path2); | |
| failf(data, "Syntax error: incorrect modification date format"); | |
| state(data, SSH_SFTP_CLOSE); | |
| sshc->nextstate = SSH_NO_STATE; | |
| sshc->actualcode = CURLE_QUOTE_ERROR; | |
| return; | |
| } | |
| sshc->quote_attrs->mtime = (uint32_t)date; | |
| sshc->quote_attrs->flags |= SSH_FILEXFER_ATTR_ACMODTIME; | |
| } | |
| /* Now send the completed structure... */ | |
| state(data, SSH_SFTP_QUOTE_SETSTAT); | |
| return; | |
| } | |
| CURLcode Curl_ssh_init(void) | |
| { | |
| if(ssh_init()) { | |
| DEBUGF(fprintf(stderr, "Error: libssh_init failed\n")); | |
| return CURLE_FAILED_INIT; | |
| } | |
| return CURLE_OK; | |
| } | |
| void Curl_ssh_cleanup(void) | |
| { | |
| (void)ssh_finalize(); | |
| } | |
| void Curl_ssh_version(char *buffer, size_t buflen) | |
| { | |
| (void)msnprintf(buffer, buflen, "libssh/%s", CURL_LIBSSH_VERSION); | |
| } | |
| #endif /* USE_LIBSSH */ |