From 96f4af4db93c5ab39afa044dad6fd05d6b9a7247 Mon Sep 17 00:00:00 2001 From: James Housley Date: Wed, 13 Jun 2007 12:15:23 +0000 Subject: [PATCH] Commit Tom Regner's code for SFTP create missing directories. This patch uses the --ftp-create-dirs flag to control if cURL will try and create directories that are specified in an upload path, but don't exist. --- lib/ssh.c | 227 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 136 insertions(+), 91 deletions(-) diff --git a/lib/ssh.c b/lib/ssh.c index 2890e46ce..1c48f4505 100644 --- a/lib/ssh.c +++ b/lib/ssh.c @@ -110,12 +110,6 @@ #define _MPRINTF_REPLACE /* use our functions only */ #include -#if defined(WIN32) || defined(MSDOS) || defined(__EMX__) -#define DIRSEP '\\' -#else -#define DIRSEP '/' -#endif - #define _MPRINTF_REPLACE /* use our functions only */ #include @@ -136,31 +130,37 @@ #define S_IROTH 0 #endif -#define LIBSSH2_SFTP_S_IRUSR S_IRUSR -#define LIBSSH2_SFTP_S_IWUSR S_IWUSR -#define LIBSSH2_SFTP_S_IRGRP S_IRGRP -#define LIBSSH2_SFTP_S_IROTH S_IROTH -#define LIBSSH2_SFTP_S_IRUSR S_IRUSR -#define LIBSSH2_SFTP_S_IWUSR S_IWUSR -#define LIBSSH2_SFTP_S_IRGRP S_IRGRP -#define LIBSSH2_SFTP_S_IROTH S_IROTH -#define LIBSSH2_SFTP_S_IFMT S_IFMT -#define LIBSSH2_SFTP_S_IFDIR S_IFDIR -#define LIBSSH2_SFTP_S_IFLNK S_IFLNK -#define LIBSSH2_SFTP_S_IFSOCK S_IFSOCK -#define LIBSSH2_SFTP_S_IFCHR S_IFCHR -#define LIBSSH2_SFTP_S_IFBLK S_IFBLK -#define LIBSSH2_SFTP_S_IXUSR S_IXUSR -#define LIBSSH2_SFTP_S_IWGRP S_IWGRP -#define LIBSSH2_SFTP_S_IXGRP S_IXGRP -#define LIBSSH2_SFTP_S_IWOTH S_IWOTH -#define LIBSSH2_SFTP_S_IXOTH S_IXOTH +/* File mode */ +/* Read, write, execute/search by owner */ +#define LIBSSH2_SFTP_S_IRWXU S_IRWXU /* RWX mask for owner */ +#define LIBSSH2_SFTP_S_IRUSR S_IRUSR /* R for owner */ +#define LIBSSH2_SFTP_S_IWUSR S_IWUSR /* W for owner */ +#define LIBSSH2_SFTP_S_IXUSR S_IXUSR /* X for owner */ +/* Read, write, execute/search by group */ +#define LIBSSH2_SFTP_S_IRWXG S_IRWXG /* RWX mask for group */ +#define LIBSSH2_SFTP_S_IRGRP S_IRGRP /* R for group */ +#define LIBSSH2_SFTP_S_IWGRP S_IWGRP /* W for group */ +#define LIBSSH2_SFTP_S_IXGRP S_IXGRP /* X for group */ +/* Read, write, execute/search by others */ +#define LIBSSH2_SFTP_S_IRWXO S_IRWXO /* RWX mask for other */ +#define LIBSSH2_SFTP_S_IROTH S_IROTH /* R for other */ +#define LIBSSH2_SFTP_S_IWOTH S_IWOTH /* W for other */ +#define LIBSSH2_SFTP_S_IXOTH S_IXOTH /* X for other */ + +/* File type */ +#define LIBSSH2_SFTP_S_IFMT S_IFMT /* type of file mask */ +#define LIBSSH2_SFTP_S_IFDIR S_IFDIR /* directory */ +#define LIBSSH2_SFTP_S_IFLNK S_IFLNK /* symbolic link */ +#define LIBSSH2_SFTP_S_IFSOCK S_IFSOCK /* socket */ +#define LIBSSH2_SFTP_S_IFCHR S_IFCHR /* character special */ +#define LIBSSH2_SFTP_S_IFBLK S_IFBLK /* block special */ #endif /* Local functions: */ static const char *sftp_libssh2_strerror(unsigned long err); static CURLcode sftp_sendquote(struct connectdata *conn, struct curl_slist *quote); +static CURLcode sftp_create_dirs(struct connectdata *conn); static LIBSSH2_ALLOC_FUNC(libssh2_malloc); static LIBSSH2_REALLOC_FUNC(libssh2_realloc); @@ -236,7 +236,7 @@ static LIBSSH2_FREE_FUNC(libssh2_free) #if (LIBSSH2_APINO >= 200706012030) /* - * SSH State machine related code + * SSH State machine related code */ /* This is the ONLY way to change SSH state! */ static void state(struct connectdata *conn, ftpstate state) @@ -265,14 +265,14 @@ static void state(struct connectdata *conn, ftpstate state) }; #endif struct ssh_conn *sshc = &conn->proto.sshc; - + #if defined(CURLDEBUG) && !defined(CURL_DISABLE_VERBOSE_STRINGS) if (sshc->state != state) { infof(conn->data, "FTP %p state change from %s to %s\n", sshc, names[sshc->state], names[state]); } #endif - + sshc->state = state; } @@ -287,9 +287,9 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) const char *fingerprint; #endif /* CURL_LIBSSH2_DEBUG */ int rc; - + ssh = data->reqdata.proto.ssh; - + switch(sshc->state) { case SSH_S_STARTUP: rc = libssh2_session_startup(ssh->ssh_session, sock); @@ -302,10 +302,10 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) sshc->actualCode = CURLE_FAILED_INIT; break; } - + /* Set libssh2 to non-blocking, since cURL is all non-blocking */ libssh2_session_set_blocking(ssh->ssh_session, 0); - + #ifdef CURL_LIBSSH2_DEBUG /* * Before we authenticate we should check the hostkey's fingerprint @@ -315,7 +315,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) */ fingerprint = libssh2_hostkey_hash(ssh->ssh_session, LIBSSH2_HOSTKEY_HASH_MD5); - + /* The fingerprint points to static storage (!), don't free() it. */ infof(data, "Fingerprint: "); for (i = 0; i < 16; i++) { @@ -323,13 +323,13 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) } infof(data, "\n"); #endif /* CURL_LIBSSH2_DEBUG */ - + state(conn, SSH_AUTHLIST); break; - + case SSH_AUTHLIST: /* TBD - methods to check the host keys need to be done */ - + /* * Figure out authentication methods * NB: As soon as we have provided a username to an openssh server we @@ -342,7 +342,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) */ sshc->authlist = libssh2_userauth_list(ssh->ssh_session, ssh->user, strlen(ssh->user)); - + if (!sshc->authlist) { if (libssh2_session_last_errno(ssh->ssh_session) == LIBSSH2_ERROR_EAGAIN) { @@ -357,46 +357,46 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) state(conn, SSH_AUTH_PKEY_INIT); break; - + case SSH_AUTH_PKEY_INIT: /* * Check the supported auth types in the order I feel is most secure with * the requested type of authentication */ sshc->authed = FALSE; - + if ((data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY) && (strstr(sshc->authlist, "publickey") != NULL)) { char *home; - + sshc->rsa_pub[0] = sshc->rsa[0] = '\0'; - + /* To ponder about: should really the lib be messing about with the HOME environment variable etc? */ home = curl_getenv("HOME"); - + if (data->set.ssh_public_key) snprintf(sshc->rsa_pub, sizeof(sshc->rsa_pub), "%s", data->set.ssh_public_key); else if (home) snprintf(sshc->rsa_pub, sizeof(sshc->rsa_pub), "%s/.ssh/id_dsa.pub", home); - + if (data->set.ssh_private_key) snprintf(sshc->rsa, sizeof(sshc->rsa), "%s", data->set.ssh_private_key); else if (home) snprintf(sshc->rsa, sizeof(sshc->rsa), "%s/.ssh/id_dsa", home); - + sshc->passphrase = data->set.key_passwd; if (!sshc->passphrase) sshc->passphrase = ""; - + curl_free(home); - + 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); - + if (sshc->rsa_pub[0]) { state(conn, SSH_AUTH_PKEY); } else { @@ -406,7 +406,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) state(conn, SSH_AUTH_PASS_INIT); } break; - + case SSH_AUTH_PKEY: /* The function below checks if the files exists, no need to stat() here. */ @@ -433,7 +433,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) state(conn, SSH_AUTH_HOST_INIT); } break; - + case SSH_AUTH_PASS: rc = libssh2_userauth_password(ssh->ssh_session, ssh->user, ssh->passwd); @@ -448,7 +448,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) state(conn, SSH_AUTH_HOST_INIT); } break; - + case SSH_AUTH_HOST_INIT: if ((data->set.ssh_auth_types & CURLSSH_AUTH_HOST) && (strstr(sshc->authlist, "hostbased") != NULL)) { @@ -457,20 +457,20 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) state(conn, SSH_AUTH_KEY_INIT); } break; - + case SSH_AUTH_HOST: state(conn, SSH_AUTH_KEY_INIT); break; - + case SSH_AUTH_KEY_INIT: if ((data->set.ssh_auth_types & CURLSSH_AUTH_KEYBOARD) && (strstr(sshc->authlist, "keyboard-interactive") != NULL)) { state(conn, SSH_AUTH_KEY); } else { state(conn, SSH_AUTH_DONE); - } + } break; - + case SSH_AUTH_KEY: /* Authentication failed. Continue with keyboard-interactive now. */ rc = libssh2_userauth_keyboard_interactive_ex(ssh->ssh_session, @@ -486,7 +486,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) } state(conn, SSH_AUTH_DONE); break; - + case SSH_AUTH_DONE: if (!sshc->authed) { failf(data, "Authentication failure"); @@ -494,12 +494,12 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) sshc->actualCode = CURLE_LOGIN_DENIED; break; } - + /* * At this point we have an authenticated ssh session. */ infof(conn->data, "Authentication complete\n"); - + conn->sockfd = sock; conn->writesockfd = CURL_SOCKET_BAD; @@ -509,7 +509,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) } state(conn, SSH_GET_WORKINGPATH); break; - + case SSH_SFTP_INIT: /* * Start the libssh2 sftp session @@ -528,11 +528,11 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) } state(conn, SSH_SFTP_REALPATH); break; - + case SSH_SFTP_REALPATH: { char tempHome[PATH_MAX]; - + /* * Get the "home" directory */ @@ -560,13 +560,13 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) state(conn, SSH_GET_WORKINGPATH); } break; - + case SSH_GET_WORKINGPATH: { char *real_path; char *working_path; int working_path_len; - + working_path = curl_easy_unescape(data, data->reqdata.path, 0, &working_path_len); if (!working_path) { @@ -574,7 +574,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) result = CURLE_OUT_OF_MEMORY; break; } - + /* Check for /~/ , indicating relative to the user's home directory */ if (conn->protocol == PROT_SCP) { real_path = (char *)malloc(working_path_len+1); @@ -632,7 +632,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) sshc->actualCode = CURLE_FAILED_INIT; break; } - + Curl_safefree(working_path); ssh->path = real_path; @@ -649,7 +649,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) ssh->sftp_session = NULL; state(conn, SSH_SESSION_FREE); break; - + case SSH_SESSION_FREE: rc = libssh2_session_free(ssh->ssh_session); if (rc == LIBSSH2_ERROR_EAGAIN) { @@ -659,7 +659,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) state(conn, SSH_STOP); result = sshc->actualCode; break; - + case SSH_QUIT: /* fallthrough, just stop! */ default: @@ -685,9 +685,9 @@ CURLcode Curl_ssh_multi_statemach(struct connectdata *conn, #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"); @@ -721,11 +721,11 @@ static CURLcode ssh_easy_statemach(struct connectdata *conn) 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 */ @@ -839,7 +839,7 @@ CURLcode Curl_ssh_connect(struct connectdata *conn, bool *done) #if (LIBSSH2_APINO >= 200706012030) state(conn, SSH_S_STARTUP); - + if (data->state.used_interface == Curl_if_multi) result = Curl_ssh_multi_statemach(conn, done); else { @@ -857,9 +857,9 @@ CURLcode Curl_ssh_connect(struct connectdata *conn, bool *done) (void)authlist; /* not used */ (void)fingerprint; /* not used */ (void)i; /* not used */ - + #else /* !(LIBSSH2_APINO >= 200706012030) */ - + if (libssh2_session_startup(ssh->ssh_session, sock)) { failf(data, "Failure establishing ssh session"); libssh2_session_free(ssh->ssh_session); @@ -1029,7 +1029,7 @@ CURLcode Curl_ssh_connect(struct connectdata *conn, bool *done) &working_path_len); if (!working_path) return CURLE_OUT_OF_MEMORY; - + /* Check for /~/ , indicating relative to the user's home directory */ if (conn->protocol == PROT_SCP) { real_path = (char *)malloc(working_path_len+1); @@ -1352,9 +1352,37 @@ CURLcode Curl_sftp_do(struct connectdata *conn, bool *done) (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); + 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, + LIBSSH2_SFTP_S_IRUSR|LIBSSH2_SFTP_S_IWUSR| + LIBSSH2_SFTP_S_IRGRP|LIBSSH2_SFTP_S_IROTH); + 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) */ @@ -1365,9 +1393,26 @@ CURLcode Curl_sftp_do(struct connectdata *conn, bool *done) LIBSSH2_SFTP_S_IRGRP|LIBSSH2_SFTP_S_IROTH); 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); + 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) { + sftp->sftp_handle = libssh2_sftp_open(sftp->sftp_session, sftp->path, + LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC, + LIBSSH2_SFTP_S_IRUSR|LIBSSH2_SFTP_S_IWUSR| + LIBSSH2_SFTP_S_IRGRP|LIBSSH2_SFTP_S_IROTH); + } + } + 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); + } } #endif /* !(LIBSSH2_APINO >= 200706012030) */ @@ -1789,9 +1834,9 @@ ssize_t Curl_sftp_recv(struct connectdata *conn, int sockindex, { ssize_t nread; (void)sockindex; - + /* libssh2_sftp_read() returns size_t !*/ - + #if defined(LIBSSH2SFTP_EAGAIN) && (LIBSSH2_APINO < 200706012030) /* we prefer the non-blocking API but that didn't exist previously */ nread = (ssize_t) @@ -2231,37 +2276,37 @@ static CURLcode sftp_create_dirs(struct connectdata *conn) { unsigned int sftp_err = 0; int rc; struct SSHPROTO *sftp = conn->data->reqdata.proto.ssh; - + if (strlen(sftp->path) > 1) { char *slash_pos = sftp->path + 1; /* ignore the leading '/' */ - + while ((slash_pos = strchr(slash_pos, '/')) != NULL) { *slash_pos = 0; - + 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, + while ((rc = libssh2_sftp_mkdir(sftp->sftp_session, sftp->path, LIBSSH2_SFTP_S_IRWXU | LIBSSH2_SFTP_S_IRGRP | LIBSSH2_SFTP_S_IXGRP | LIBSSH2_SFTP_S_IROTH | LIBSSH2_SFTP_S_IXOTH)) == LIBSSH2_ERROR_EAGAIN); #else /* !(LIBSSH2_APINO >= 200706012030) */ - rc = libssh2_sftp_mkdir(sftp->sftp_session, sftp->path, + rc = libssh2_sftp_mkdir(sftp->sftp_session, sftp->path, LIBSSH2_SFTP_S_IRWXU | LIBSSH2_SFTP_S_IRGRP | LIBSSH2_SFTP_S_IXGRP | LIBSSH2_SFTP_S_IROTH | LIBSSH2_SFTP_S_IXOTH); #endif /* !(LIBSSH2_APINO >= 200706012030) */ *slash_pos = '/'; ++slash_pos; - if (rc == -1) { + if (rc == -1) { /* abort if failure wasn't that the dir already exists or the - * permission was denied (creation might succeed further + * permission was denied (creation might succeed further * down the path) - retry on unspecific FAILURE also */ sftp_err = libssh2_sftp_last_error(sftp->sftp_session); - if ((sftp_err != LIBSSH2_FX_FILE_ALREADY_EXISTS) && - (sftp_err != LIBSSH2_FX_FAILURE) && + if ((sftp_err != LIBSSH2_FX_FILE_ALREADY_EXISTS) && + (sftp_err != LIBSSH2_FX_FAILURE) && (sftp_err != LIBSSH2_FX_PERMISSION_DENIED)) { result = -1; break;