Index: lib/ssh.c
===================================================================
RCS file: /cvsroot/curl/curl/lib/ssh.c,v
retrieving revision 1.25
diff -u -r1.25 ssh.c
--- lib/ssh.c	29 Mar 2007 19:17:18 -0000	1.25
+++ lib/ssh.c	29 Mar 2007 20:44:46 -0000
@@ -157,6 +157,10 @@
 #define LIBSSH2_SFTP_S_IXOTH S_IXOTH
 #endif
 
+/* Local functions: */
+static CURLcode Curl_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);
@@ -952,6 +956,15 @@
 
   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 = Curl_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) {
@@ -1000,6 +1013,454 @@
   return nwrite;
 }
 
+
+/* The get_pathname() function is being borrowed from OpenSSH sftp.c version 4.6p1. */
+/*
+ * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
+ *
+ * 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 (0);
+  }
+  
+  *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 char *Curl_sftp_libssh2_strerror(unsigned long err)
+{
+  switch (err)
+  {
+    case LIBSSH2_FX_NO_SUCH_FILE:
+      return "No such file or directory";
+      break;
+    case LIBSSH2_FX_PERMISSION_DENIED:
+      return "Permission denied";
+      break;
+    case LIBSSH2_FX_FAILURE:
+      return "Operation failed";
+      break;
+    case LIBSSH2_FX_BAD_MESSAGE:
+      return "Bad message from SFTP server";
+      break;
+    case LIBSSH2_FX_NO_CONNECTION:
+      return "Not connected to SFTP server";
+      break;
+    case LIBSSH2_FX_CONNECTION_LOST:
+      return "Connection to SFTP server lost";
+      break;
+    case LIBSSH2_FX_OP_UNSUPPORTED:
+      return "Operation not supported by SFTP server";
+      break;
+    case LIBSSH2_FX_INVALID_HANDLE:
+      return "Invalid handle";
+      break;
+    case LIBSSH2_FX_NO_SUCH_PATH:
+      return "No such file or directory";
+      break;
+    case LIBSSH2_FX_FILE_ALREADY_EXISTS:
+      return "File already exists";
+      break;
+    case LIBSSH2_FX_WRITE_PROTECT:
+      return "File is write protected";
+      break;
+    case LIBSSH2_FX_NO_MEDIA:
+      return "No media";
+      break;
+    case LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM:
+      return "Disk full";
+      break;
+    case LIBSSH2_FX_QUOTA_EXCEEDED:
+      return "User quota exceeded";
+      break;
+    case LIBSSH2_FX_UNKNOWN_PRINCIPLE:
+      return "Unknown principle";
+      break;
+    case LIBSSH2_FX_LOCK_CONFlICT:
+      return "File lock conflict";
+      break;
+    case LIBSSH2_FX_DIR_NOT_EMPTY:
+      return "Directory not empty";
+      break;
+    case LIBSSH2_FX_NOT_A_DIRECTORY:
+      return "Not a directory";
+      break;
+    case LIBSSH2_FX_INVALID_FILENAME:
+      return "Invalid filename";
+      break;
+    case LIBSSH2_FX_LINK_LOOP:
+      return "Link points to itself";
+      break;
+  }
+  return "Unknown error in libssh2";
+}
+
+
+static CURLcode Curl_sftp_sendquote(struct connectdata *conn,
+                                    struct curl_slist *quote)
+{
+  struct curl_slist *item;
+  char *path1, *path2;
+  const char *cp;
+  LIBSSH2_SFTP_ATTRIBUTES attrs;
+  long err;
+  
+  item = quote;
+  while (item)
+  {
+    if (item->data)
+    {
+      cp = item->data;
+      path1 = NULL;
+      path2 = NULL;
+      memset(&attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES));
+      
+      /* 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", 5) == 0 || 
+          curl_strnequal(item->data, "chmod", 5) == 0 || 
+          curl_strnequal(item->data, "chown", 5) == 0)  /* attributes change */
+      {
+        cp = strchr(cp, ' ');
+        if (cp == NULL)
+        {
+          failf(conn->data, 
+                "Syntax error in chgrp/chmod/chown: Supply parameters");
+          return CURLE_FTP_QUOTE_ERROR;
+        }
+        err = get_pathname(&cp, &path1);  /* "path1" contains the mode to set */
+        if (err)
+        {
+          if (err == CURLE_OUT_OF_MEMORY)
+            failf(conn->data, "Out of memory");
+          else
+            failf(conn->data,
+                  "Syntax error in chgrp/chmod/chown: Bad first parameter");
+          return err;
+        }
+        err = get_pathname(&cp, &path2);  /* get the destination */
+        if (err)
+        {
+          if (err == CURLE_OUT_OF_MEMORY)
+            failf(conn->data, "Out of memory");
+          else
+            failf(conn->data,
+                  "Syntax error in chgrp/chmod/chown: Bad second parameter");
+          free(path1);
+          return err;
+        }
+        if (libssh2_sftp_stat(conn->data->reqdata.proto.ssh->sftp_session, 
+                    path2, &attrs) != 0)  /* get those attributes */
+        {
+          err = libssh2_sftp_last_error(conn->data->reqdata.proto.ssh->sftp_session);
+          free(path1);
+          free(path2);
+          failf(conn->data, "Attempt to get SFTP stats failed: %s", 
+                Curl_sftp_libssh2_strerror(err));
+          return CURLE_FTP_QUOTE_ERROR;
+        }
+        
+        /* Now set the new attributes... */
+        if (curl_strnequal(item->data, "chgrp", 5) == 0)
+        {
+          attrs.gid = strtol(path1, NULL, 10);
+          if (attrs.gid == 0)
+          {
+            free(path1);
+            free(path2);
+            failf(conn->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(conn->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(conn->data, "Syntax error: chown uid not a number");
+            return CURLE_FTP_QUOTE_ERROR;
+          }
+        }
+        
+        /* Now send the completed structure... */
+        if (libssh2_sftp_setstat(conn->data->reqdata.proto.ssh->sftp_session,
+                     path2, &attrs) != 0)
+        {
+          err = libssh2_sftp_last_error(conn->data->reqdata.proto.ssh->sftp_session);
+          free(path1);
+          free(path2);
+          failf(conn->data, "Attempt to set SFTP stats failed: %s", 
+                Curl_sftp_libssh2_strerror(err));
+          return CURLE_FTP_QUOTE_ERROR;
+        }
+      }
+      else if (curl_strnequal(item->data, "ln", 2) == 0 ||
+               curl_strnequal(item->data, "symlink", 7) != 0)
+               /* symbolic linking */
+      {
+        cp = strchr(cp, ' ');
+        if (cp == NULL)
+        {
+          failf(conn->data, "Syntax error in ln/symlink: Supply parameters");
+          return CURLE_FTP_QUOTE_ERROR;
+        }
+        err = get_pathname(&cp, &path1);  /* get the source */
+        if (err)
+        {
+          if (err == CURLE_OUT_OF_MEMORY)
+            failf(conn->data, "Out of memory");
+          else
+            failf(conn->data,
+                  "Syntax error in ln/symlink: Bad first parameter");
+          return err;
+        }
+        err = get_pathname(&cp, &path2);  /* get the destination */
+        if (err)
+        {
+          if (err == CURLE_OUT_OF_MEMORY)
+            failf(conn->data, "Out of memory");
+          else
+            failf(conn->data,
+                  "Syntax error in ln/symlink: Bad second parameter");
+          free(path1);
+          return err;
+        }
+        if (libssh2_sftp_symlink(conn->data->reqdata.proto.ssh->sftp_session,
+                     path1, path2) != 0)
+        {
+          err = libssh2_sftp_last_error(conn->data->reqdata.proto.ssh->sftp_session);
+          free(path1);
+          free(path2);
+          failf(conn->data, "symlink command failed: %s", 
+                Curl_sftp_libssh2_strerror(err));
+          return CURLE_FTP_QUOTE_ERROR;
+        }
+      }
+      else if (curl_strnequal(item->data, "mkdir", 5) == 0)  /* create new directory */
+      {
+        cp = strchr(cp, ' ');
+        if (cp == NULL)
+        {
+          failf(conn->data, "Syntax error in mkdir: Supply parameters");
+          return CURLE_FTP_QUOTE_ERROR;
+        }
+        err = get_pathname(&cp, &path1);
+        if (err)
+        {
+          if (err == CURLE_OUT_OF_MEMORY)
+            failf(conn->data, "Out of memory");
+          else
+            failf(conn->data,
+                  "Syntax error in mkdir: Bad parameter");
+          return err;
+        }
+        if (libssh2_sftp_mkdir(conn->data->reqdata.proto.ssh->sftp_session,
+                     path1, 0744) != 0)
+        {
+          err = libssh2_sftp_last_error(conn->data->reqdata.proto.ssh->sftp_session);
+          free(path1);
+          failf(conn->data, "mkdir command failed: %s", 
+                Curl_sftp_libssh2_strerror(err));
+          return CURLE_FTP_QUOTE_ERROR;
+        }
+      }
+      else if (curl_strnequal(item->data, "rename", 6) == 0)  /* rename file */
+      {
+        cp = strchr(cp, ' ');
+        if (cp == NULL)
+        {
+          failf(conn->data, "Syntax error in rename: Supply parameters");
+          return CURLE_FTP_QUOTE_ERROR;
+        }
+        err = get_pathname(&cp, &path1);  /* first param is the source path */
+        if (err)
+        {
+          if (err == CURLE_OUT_OF_MEMORY)
+            failf(conn->data, "Out of memory");
+          else
+            failf(conn->data,
+                  "Syntax error in rename: Bad first parameter");
+          return err;
+        }
+        err = get_pathname(&cp, &path2);  /* second param is the dest. path */
+        if (err)
+        {
+          if (err == CURLE_OUT_OF_MEMORY)
+            failf(conn->data, "Out of memory");
+          else
+            failf(conn->data,
+                  "Syntax error in rename: Bad second parameter");
+          free(path1);
+          return err;
+        }
+        if (libssh2_sftp_rename(conn->data->reqdata.proto.ssh->sftp_session, 
+                    path1, path2) != 0)
+        {
+          err = libssh2_sftp_last_error(conn->data->reqdata.proto.ssh->sftp_session);
+          free(path1);
+          free(path2);
+          failf(conn->data, "rename command failed: %s", 
+                Curl_sftp_libssh2_strerror(err));
+          return CURLE_FTP_QUOTE_ERROR;
+        }
+      }
+      else if (curl_strnequal(item->data, "rm", 2) == 0)  /* delete file */
+      {
+        cp = strchr(cp, ' ');
+        if (cp == NULL)
+        {
+          failf(conn->data, "Syntax error in rm: Supply parameters");
+          return CURLE_FTP_QUOTE_ERROR;
+        }
+        err = get_pathname(&cp, &path1);
+        if (err)
+        {
+          if (err == CURLE_OUT_OF_MEMORY)
+            failf(conn->data, "Out of memory");
+          else
+            failf(conn->data,
+                  "Syntax error in rm: Bad parameter");
+          return err;
+        }
+        if (libssh2_sftp_unlink(conn->data->reqdata.proto.ssh->sftp_session,
+                    path1) != 0)
+        {
+          err = libssh2_sftp_last_error(conn->data->reqdata.proto.ssh->sftp_session);
+          free(path1);
+          failf(conn->data, "rm command failed: %s", 
+                Curl_sftp_libssh2_strerror(err));
+          return CURLE_FTP_QUOTE_ERROR;
+        }
+      }
+      else if (curl_strnequal(item->data, "rmdir", 5) == 0)  /* delete directory */
+      {
+        cp = strchr(cp, ' ');
+        if (cp == NULL)
+        {
+          failf(conn->data, "Syntax error in rmdir: Supply parameters");
+          return CURLE_FTP_QUOTE_ERROR;
+        }
+        err = get_pathname(&cp, &path1);
+        if (err)
+        {
+          if (err == CURLE_OUT_OF_MEMORY)
+            failf(conn->data, "Out of memory");
+          else
+            failf(conn->data,
+                  "Syntax error in rmdir: Bad parameter");
+          return err;
+        }
+        if (libssh2_sftp_rmdir(conn->data->reqdata.proto.ssh->sftp_session,
+                     path1) != 0)
+        {
+          err = libssh2_sftp_last_error(conn->data->reqdata.proto.ssh->sftp_session);
+          free(path1);
+          failf(conn->data, "rmdir command failed: %s", 
+              Curl_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.

