From 20d33ad7e50ee41020927d30457dd6c1ee975bd0 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 2 Apr 2007 21:24:05 +0000 Subject: Nick Zitzmann made CURLOPT_POSTQUOTE work for SFTP as well. --- CHANGES | 37 +++++++ RELEASE-NOTES | 3 +- lib/ssh.c | 339 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 377 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 7d4cf8fa3..3439ceae8 100644 --- a/CHANGES +++ b/CHANGES @@ -6,6 +6,43 @@ Changelog +Daniel S (2 April 2007) +- Nick Zitzmann made the CURLOPT_POSTQUOTE option work for SFTP as well. The + accepted commands are as follows: + + chgrp (gid) (path) + Changes the group ID of the file or directory at (path) to (gid). (gid) + must be a number. + + chmod (perms) (path) + Changes the permissions of the file or directory at (path) to + (perms). (perms) must be a number in the format used by the chmod Unix + command. + + chown (uid) (path) + Changes the user ID of the file or directory at (path) to (uid). (uid) + must be a number. + + ln (source) (dest) + Creates a symbolic link at (dest) that points to the file located at + (source). + + mkdir (path) + Creates a new directory at (path). + + rename (source) (dest) + Moves the file or directory at (source) to (dest). + + rm (path) + Deletes the file located at (path). + + rmdir (path) + Deletes the directory located at (path). This command will raise an error + if the directory is not empty. + + symlink (source) (dest) + Same as ln. + Daniel S (1 April 2007) - Robert Iakobashvili made curl_multi_remove_handle() a lot faster when many easy handles are added to a multi handle, by avoiding the looping over all diff --git a/RELEASE-NOTES b/RELEASE-NOTES index e3b9521ff..084dc6ceb 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -25,6 +25,7 @@ This release includes the following changes: o --key and new --pubkey options for SSH public key file logins o --pass now works for a SSH public key file, too o select (2) support no longer needed to build the library if poll() used + o CURLOPT_POSTQUOTE works for SFTP This release includes the following bugfixes: @@ -71,6 +72,6 @@ advice from friends like these: Michal Marek, Robson Braga Araujo, Ian Turner, Linus Nielsen Feltzing, Ravi Pratap, Adam D. Moss, Jose Kahan, Hang Kin Lau, Justin Fletcher, Robert Iakobashvili, Bryan Henderson, Eygene Ryabinkin, Daniel Johnson, - Matt Kraai + Matt Kraai, Nick Zitzmann Thanks! (and sorry if I forgot to mention someone) diff --git a/lib/ssh.c b/lib/ssh.c index ccf4a921c..ed4d2790a 100644 --- a/lib/ssh.c +++ b/lib/ssh.c @@ -157,6 +157,10 @@ #define LIBSSH2_SFTP_S_IXOTH S_IXOTH #endif +/* Local functions: */ +static CURLcode sftp_sendquote(struct connectdata *conn, + struct curl_slist *quote); + static LIBSSH2_ALLOC_FUNC(libssh2_malloc); static LIBSSH2_REALLOC_FUNC(libssh2_realloc); static LIBSSH2_FREE_FUNC(libssh2_free); @@ -953,6 +957,14 @@ CURLcode Curl_sftp_done(struct connectdata *conn, CURLcode status, Curl_safefree(sftp->homedir); sftp->homedir = NULL; + /* Before we shut down, see if there are any post-quote commands to send: */ + if(!status && !premature && conn->data->set.postquote) { + CURLcode result = sftp_sendquote(conn, conn->data->set.postquote); + + if (result != CURLE_OK) + return result; + } + if (sftp->sftp_handle) { if (libssh2_sftp_close(sftp->sftp_handle) < 0) { infof(conn->data, "Failed to close libssh2 file\n"); @@ -1000,12 +1012,337 @@ ssize_t Curl_sftp_send(struct connectdata *conn, int sockindex, return nwrite; } + +/* The get_pathname() function is being borrowed from OpenSSH sftp.c + version 4.6p1. */ +/* + * Copyright (c) 2001-2004 Damien Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +static int +get_pathname(const char **cpp, char **path) +{ + const char *cp = *cpp, *end; + char quot; + u_int i, j; + const char *WHITESPACE = " \t\r\n"; + + cp += strspn(cp, WHITESPACE); + if (!*cp) { + *cpp = cp; + *path = NULL; + return CURLE_FTP_QUOTE_ERROR; /* this was originally 0 in OpenSSH + but we want it to be an error */ + } + + *path = malloc(strlen(cp) + 1); + if (*path == NULL) + return CURLE_OUT_OF_MEMORY; + + /* Check for quoted filenames */ + if (*cp == '\"' || *cp == '\'') { + quot = *cp++; + + /* Search for terminating quote, unescape some chars */ + for (i = j = 0; i <= strlen(cp); i++) { + if (cp[i] == quot) { /* Found quote */ + i++; + (*path)[j] = '\0'; + break; + } + if (cp[i] == '\0') { /* End of string */ + /*error("Unterminated quote");*/ + goto fail; + } + if (cp[i] == '\\') { /* Escaped characters */ + i++; + if (cp[i] != '\'' && cp[i] != '\"' && + cp[i] != '\\') { + /*error("Bad escaped character '\\%c'", + cp[i]);*/ + goto fail; + } + } + (*path)[j++] = cp[i]; + } + + if (j == 0) { + /*error("Empty quotes");*/ + goto fail; + } + *cpp = cp + i + strspn(cp + i, WHITESPACE); + } + else { + /* Read to end of filename */ + end = strpbrk(cp, WHITESPACE); + if (end == NULL) + end = strchr(cp, '\0'); + *cpp = end + strspn(end, WHITESPACE); + + memcpy(*path, cp, end - cp); + (*path)[end - cp] = '\0'; + } + return (0); + + fail: + free(*path); + *path = NULL; + return CURLE_FTP_QUOTE_ERROR; +} + + +static const char *sftp_libssh2_strerror(unsigned long err) +{ + switch (err) { + case LIBSSH2_FX_NO_SUCH_FILE: + return "No such file or directory"; + case LIBSSH2_FX_PERMISSION_DENIED: + return "Permission denied"; + case LIBSSH2_FX_FAILURE: + return "Operation failed"; + case LIBSSH2_FX_BAD_MESSAGE: + return "Bad message from SFTP server"; + case LIBSSH2_FX_NO_CONNECTION: + return "Not connected to SFTP server"; + case LIBSSH2_FX_CONNECTION_LOST: + return "Connection to SFTP server lost"; + case LIBSSH2_FX_OP_UNSUPPORTED: + return "Operation not supported by SFTP server"; + case LIBSSH2_FX_INVALID_HANDLE: + return "Invalid handle"; + case LIBSSH2_FX_NO_SUCH_PATH: + return "No such file or directory"; + case LIBSSH2_FX_FILE_ALREADY_EXISTS: + return "File already exists"; + case LIBSSH2_FX_WRITE_PROTECT: + return "File is write protected"; + case LIBSSH2_FX_NO_MEDIA: + return "No media"; + case LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM: + return "Disk full"; + case LIBSSH2_FX_QUOTA_EXCEEDED: + return "User quota exceeded"; + case LIBSSH2_FX_UNKNOWN_PRINCIPLE: + return "Unknown principle"; + case LIBSSH2_FX_LOCK_CONFlICT: + return "File lock conflict"; + case LIBSSH2_FX_DIR_NOT_EMPTY: + return "Directory not empty"; + case LIBSSH2_FX_NOT_A_DIRECTORY: + return "Not a directory"; + case LIBSSH2_FX_INVALID_FILENAME: + return "Invalid filename"; + case LIBSSH2_FX_LINK_LOOP: + return "Link points to itself"; + } + return "Unknown error in libssh2"; +} + +/* BLOCKING */ +static CURLcode sftp_sendquote(struct connectdata *conn, + struct curl_slist *quote) +{ + struct curl_slist *item=quote; + const char *cp; + long err; + struct SessionHandle *data = conn->data; + LIBSSH2_SFTP *sftp_session = data->reqdata.proto.ssh->sftp_session; + + while (item) { + if (item->data) { + char *path1 = NULL; + char *path2 = NULL; + + /* the arguments following the command must be separated from the + command with a space so we can check for it unconditionally */ + cp = strchr(item->data, ' '); + if (cp == NULL) { + failf(data, "Syntax error in SFTP command. Supply parameter(s)!"); + return CURLE_FTP_QUOTE_ERROR; + } + + /* also, every command takes at least one argument so we get that first + argument right now */ + err = get_pathname(&cp, &path1); + if (err) { + if (err == CURLE_OUT_OF_MEMORY) + failf(data, "Out of memory"); + else + failf(data, "Syntax error: Bad first parameter"); + return err; + } + + /* SFTP is a binary protocol, so we don't send text commands to the + server. Instead, we scan for commands for commands used by OpenSSH's + sftp program and call the appropriate libssh2 functions. */ + if (curl_strnequal(item->data, "chgrp ", 6) || + curl_strnequal(item->data, "chmod ", 6) || + curl_strnequal(item->data, "chown ", 6) ) { /* attribute change */ + LIBSSH2_SFTP_ATTRIBUTES attrs; + + /* path1 contains the mode to set */ + err = get_pathname(&cp, &path2); /* get the destination */ + if (err) { + if (err == CURLE_OUT_OF_MEMORY) + failf(data, "Out of memory"); + else + failf(data, + "Syntax error in chgrp/chmod/chown: Bad second parameter"); + free(path1); + return err; + } + memset(&attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); + if (libssh2_sftp_stat(sftp_session, + path2, &attrs) != 0) { /* get those attributes */ + err = libssh2_sftp_last_error(sftp_session); + free(path1); + free(path2); + failf(data, "Attempt to get SFTP stats failed: %s", + sftp_libssh2_strerror(err)); + return CURLE_FTP_QUOTE_ERROR; + } + + /* Now set the new attributes... */ + if (curl_strnequal(item->data, "chgrp", 5)) { + attrs.gid = strtol(path1, NULL, 10); + if (attrs.gid == 0) { + free(path1); + free(path2); + failf(data, "Syntax error: chgrp gid not a number"); + return CURLE_FTP_QUOTE_ERROR; + } + } + else if (curl_strnequal(item->data, "chmod", 5)) { + attrs.permissions = strtol(path1, NULL, 8);/* permissions are octal */ + if (attrs.permissions == 0) { + free(path1); + free(path2); + failf(data, "Syntax error: chmod permissions not a number"); + return CURLE_FTP_QUOTE_ERROR; + } + } + else if (curl_strnequal(item->data, "chown", 5)) { + attrs.uid = strtol(path1, NULL, 10); + if (attrs.uid == 0) { + free(path1); + free(path2); + failf(data, "Syntax error: chown uid not a number"); + return CURLE_FTP_QUOTE_ERROR; + } + } + + /* Now send the completed structure... */ + if (libssh2_sftp_setstat(sftp_session, path2, &attrs) != 0) { + err = libssh2_sftp_last_error(sftp_session); + free(path1); + free(path2); + failf(data, "Attempt to set SFTP stats failed: %s", + sftp_libssh2_strerror(err)); + return CURLE_FTP_QUOTE_ERROR; + } + } + else if (curl_strnequal(item->data, "ln ", 3) || + curl_strnequal(item->data, "symlink ", 8)) { + /* symbolic linking */ + /* path1 is the source */ + err = get_pathname(&cp, &path2); /* get the destination */ + if (err) { + if (err == CURLE_OUT_OF_MEMORY) + failf(data, "Out of memory"); + else + failf(data, + "Syntax error in ln/symlink: Bad second parameter"); + free(path1); + return err; + } + if (libssh2_sftp_symlink(sftp_session, path1, path2) != 0) { + err = libssh2_sftp_last_error(sftp_session); + free(path1); + free(path2); + failf(data, "symlink command failed: %s", + sftp_libssh2_strerror(err)); + return CURLE_FTP_QUOTE_ERROR; + } + } + else if (curl_strnequal(item->data, "mkdir ", 6)) { /* create dir */ + if (libssh2_sftp_mkdir(sftp_session, path1, 0744) != 0) { + err = libssh2_sftp_last_error(sftp_session); + free(path1); + failf(data, "mkdir command failed: %s", + sftp_libssh2_strerror(err)); + return CURLE_FTP_QUOTE_ERROR; + } + } + else if (curl_strnequal(item->data, "rename ", 7)) { /* rename file */ + /* first param is the source path */ + err = get_pathname(&cp, &path2); /* second param is the dest. path */ + if (err) { + if (err == CURLE_OUT_OF_MEMORY) + failf(data, "Out of memory"); + else + failf(data, + "Syntax error in rename: Bad second parameter"); + free(path1); + return err; + } + if (libssh2_sftp_rename(sftp_session, + path1, path2) != 0) { + err = libssh2_sftp_last_error(sftp_session); + free(path1); + free(path2); + failf(data, "rename command failed: %s", + sftp_libssh2_strerror(err)); + return CURLE_FTP_QUOTE_ERROR; + } + } + else if (curl_strnequal(item->data, "rmdir ", 6)) { /* delete dir */ + if (libssh2_sftp_rmdir(sftp_session, + path1) != 0) { + err = libssh2_sftp_last_error(sftp_session); + free(path1); + failf(data, "rmdir command failed: %s", + sftp_libssh2_strerror(err)); + return CURLE_FTP_QUOTE_ERROR; + } + } + else if (curl_strnequal(item->data, "rm ", 3)) { /* delete file */ + if (libssh2_sftp_unlink(sftp_session, path1) != 0) { + err = libssh2_sftp_last_error(sftp_session); + free(path1); + failf(data, "rm command failed: %s", + sftp_libssh2_strerror(err)); + return CURLE_FTP_QUOTE_ERROR; + } + } + + if (path1) + free(path1); + if (path2) + free(path2); + } + item = item->next; + } + return CURLE_OK; +} + + /* * If the read would block (EWOULDBLOCK) we return -1. Otherwise we return * a regular CURLcode value. */ ssize_t Curl_sftp_recv(struct connectdata *conn, int sockindex, - char *mem, size_t len) + char *mem, size_t len) { ssize_t nread; (void)sockindex; -- cgit v1.2.3