Skip to content
Snippets Groups Projects
ssh.c 73 KiB
Newer Older
  • Learn to ignore specific revisions
  • /***************************************************************************
    
     *                                  _   _ ____  _
     *  Project                     ___| | | |  _ \| |
     *                             / __| | | | |_) | |
     *                            | (__| |_| |  _ <| |___
     *                             \___|\___/|_| \_\_____|
     *
    
     * Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
    
     *
     * This software is licensed as described in the file COPYING, which
     * you should have received as part of this distribution. The terms
     * are also available at http://curl.haxx.se/docs/copyright.html.
     *
     * You may opt to use, copy, modify, merge, publish, distribute and/or sell
     * copies of the Software, and permit persons to whom the Software is
     * furnished to do so, under the terms of the COPYING file.
     *
     * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
     * KIND, either express or implied.
     *
     * $Id$
     ***************************************************************************/
    
    
    #include "setup.h"
    
    #ifdef USE_LIBSSH2
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <stdarg.h>
    #include <ctype.h>
    #include <limits.h>
    
    #include <libssh2.h>
    #include <libssh2_sftp.h>
    
    
    #if !defined(LIBSSH2_VERSION_NUM) || (LIBSSH2_VERSION_NUM < 0x001000)
    #error "this requires libssh2 0.16 or later"
    #endif
    
    
    #ifdef HAVE_UNISTD_H
    #include <unistd.h>
    #endif
    
    #ifdef HAVE_FCNTL_H
    #include <fcntl.h>
    #endif
    
    
    #ifdef HAVE_TIME_H
    #include <time.h>
    #endif
    
    
    #ifdef HAVE_SYS_SOCKET_H
    #include <sys/socket.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 "easyif.h" /* for Curl_convert_... prototypes */
    
    #include "if2ip.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 "strequal.h"
    #include "sslgen.h"
    #include "connect.h"
    #include "strerror.h"
    #include "memory.h"
    #include "inet_ntop.h"
    #include "select.h"
    #include "parsedate.h" /* for the week day and month names */
    #include "sockaddr.h" /* required for Curl_sockaddr_storage */
    #include "multiif.h"
    
    #if defined(HAVE_INET_NTOA_R) && !defined(HAVE_INET_NTOA_R_DECL)
    #include "inet_ntoa_r.h"
    #endif
    
    #define _MPRINTF_REPLACE /* use our functions only */
    #include <curl/mprintf.h>
    
    #define _MPRINTF_REPLACE /* use our functions only */
    #include <curl/mprintf.h>
    
    /* The last #include file should be: */
    #ifdef CURLDEBUG
    #include "memdebug.h"
    #endif
    
    
    #ifndef PATH_MAX
    #define PATH_MAX 1024 /* just an extra precaution since there are systems that
                             have their definition hidden well */
    #endif
    
    
    static const char *sftp_libssh2_strerror(unsigned long err);
    
    static LIBSSH2_ALLOC_FUNC(libssh2_malloc);
    static LIBSSH2_REALLOC_FUNC(libssh2_realloc);
    static LIBSSH2_FREE_FUNC(libssh2_free);
    
    
    Yang Tse's avatar
    Yang Tse committed
    static CURLcode get_pathname(const char **cpp, char **path);
    
    static CURLcode ssh_connect(struct connectdata *conn, bool *done);
    static CURLcode ssh_multi_statemach(struct connectdata *conn, bool *done);
    static CURLcode ssh_do(struct connectdata *conn, bool *done);
    
    static CURLcode ssh_getworkingpath(struct connectdata *conn,
                                       char *homedir, /* when SFTP is used */
                                       char **path);
    
    static CURLcode scp_done(struct connectdata *conn,
                             CURLcode, bool premature);
    static CURLcode scp_doing(struct connectdata *conn,
                              bool *dophase_done);
    static CURLcode scp_disconnect(struct connectdata *conn);
    
    static CURLcode sftp_done(struct connectdata *conn,
                              CURLcode, bool premature);
    static CURLcode sftp_doing(struct connectdata *conn,
                               bool *dophase_done);
    static CURLcode sftp_disconnect(struct connectdata *conn);
    static
    CURLcode sftp_perform(struct connectdata *conn,
                          bool *connected,
                          bool *dophase_done);
    
    /*
     * SCP protocol handler.
     */
    
    const struct Curl_handler Curl_handler_scp = {
      "SCP",                                /* scheme */
    
      ZERO_NULL,                            /* setup_connection */
    
      ZERO_NULL,                            /* do_more */
    
      ssh_connect,                          /* connect_it */
      ssh_multi_statemach,                  /* connecting */
      scp_doing,                            /* doing */
    
      ZERO_NULL,                            /* proto_getsock */
      ZERO_NULL,                            /* doing_getsock */
    
      PORT_SSH,                             /* defport */
      PROT_SCP                              /* protocol */
    };
    
    
    /*
     * SFTP protocol handler.
     */
    
    const struct Curl_handler Curl_handler_sftp = {
      "SFTP",                               /* scheme */
    
      ZERO_NULL,                            /* setup_connection */
    
      ssh_do,                               /* do_it */
      sftp_done,                            /* done */
    
      ZERO_NULL,                            /* do_more */
    
      ssh_connect,                          /* connect_it */
      ssh_multi_statemach,                  /* connecting */
      sftp_doing,                           /* doing */
    
      ZERO_NULL,                            /* proto_getsock */
      ZERO_NULL,                            /* doing_getsock */
    
    static void
    kbd_callback(const char *name, int name_len, const char *instruction,
                 int instruction_len, int num_prompts,
                 const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts,
                 LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses,
                 void **abstract)
    {
    
      struct connectdata *conn = (struct connectdata *)*abstract;
    
    #ifdef CURL_LIBSSH2_DEBUG
      fprintf(stderr, "name=%s\n", name);
      fprintf(stderr, "name_len=%d\n", name_len);
      fprintf(stderr, "instruction=%s\n", instruction);
      fprintf(stderr, "instruction_len=%d\n", instruction_len);
      fprintf(stderr, "num_prompts=%d\n", num_prompts);
    
    #else
      (void)name;
      (void)name_len;
      (void)instruction;
      (void)instruction_len;
    
    #endif  /* CURL_LIBSSH2_DEBUG */
    
        responses[0].text = strdup(conn->passwd);
        responses[0].length = strlen(conn->passwd);
    
      }
      (void)prompts;
      (void)abstract;
    } /* kbd_callback */
    
    
    static CURLcode sftp_libssh2_error_to_CURLE(unsigned long err)
    
      switch (err) {
        case LIBSSH2_FX_OK:
          return CURLE_OK;
    
        case LIBSSH2_ERROR_ALLOC:
          return CURLE_OUT_OF_MEMORY;
    
    
        case LIBSSH2_FX_NO_SUCH_FILE:
        case LIBSSH2_FX_NO_SUCH_PATH:
          return CURLE_REMOTE_FILE_NOT_FOUND;
    
        case LIBSSH2_FX_PERMISSION_DENIED:
        case LIBSSH2_FX_WRITE_PROTECT:
        case LIBSSH2_FX_LOCK_CONFlICT:
          return CURLE_REMOTE_ACCESS_DENIED;
    
        case LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM:
        case LIBSSH2_FX_QUOTA_EXCEEDED:
          return CURLE_REMOTE_DISK_FULL;
    
        case LIBSSH2_FX_FILE_ALREADY_EXISTS:
          return CURLE_REMOTE_FILE_EXISTS;
    
        case LIBSSH2_FX_DIR_NOT_EMPTY:
          return CURLE_QUOTE_ERROR;
    
    
      return CURLE_SSH;
    }
    
    static CURLcode libssh2_session_error_to_CURLE(int err)
    {
    
    
      /* TODO: map some more of the libssh2 errors to the more appropriate CURLcode
         error code, and possibly add a few new SSH-related one. We must however
         not return or even depend on libssh2 errors in the public libcurl API */
    
    
      return CURLE_SSH;
    }
    
    static LIBSSH2_ALLOC_FUNC(libssh2_malloc)
    {
    
      (void)abstract; /* arg not used */
    
      return malloc(count);
    }
    
    static LIBSSH2_REALLOC_FUNC(libssh2_realloc)
    {
    
      (void)abstract; /* arg not used */
    
      return realloc(ptr, count);
    }
    
    static LIBSSH2_FREE_FUNC(libssh2_free)
    {
    
      (void)abstract; /* arg not used */
    
     */
    /* This is the ONLY way to change SSH state! */
    
    static void state(struct connectdata *conn, sshstate nowstate)
    
    {
    #if defined(CURLDEBUG) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
      /* for debug purposes */
    
      static const char * const names[] = {
    
        "SSH_S_STARTUP",
        "SSH_AUTHLIST",
        "SSH_AUTH_PKEY_INIT",
        "SSH_AUTH_PKEY",
        "SSH_AUTH_PASS_INIT",
        "SSH_AUTH_PASS",
        "SSH_AUTH_HOST_INIT",
        "SSH_AUTH_HOST",
        "SSH_AUTH_KEY_INIT",
        "SSH_AUTH_KEY",
        "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_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_SCP_TRANS_INIT",
        "SSH_SCP_UPLOAD_INIT",
        "SSH_SCP_DOWNLOAD_INIT",
        "SSH_SCP_DONE",
        "SSH_SCP_SEND_EOF",
        "SSH_SCP_WAIT_EOF",
        "SSH_SCP_WAIT_CLOSE",
        "SSH_SCP_CHANNEL_FREE",
    
        "QUIT"
      };
    #endif
      struct ssh_conn *sshc = &conn->proto.sshc;
    
    #if defined(CURLDEBUG) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
    
        infof(conn->data, "SFTP %p state change from %s to %s\n",
    
              sshc, names[sshc->state], names[nowstate]);
    
      sshc->state = nowstate;
    
    /* figure out the path to work with in this particular request */
    static CURLcode ssh_getworkingpath(struct connectdata *conn,
                                       char *homedir,  /* when SFTP is used */
                                       char **path) /* returns the  allocated
                                                       real path to work with */
    {
      struct SessionHandle *data = conn->data;
    
      char *real_path = NULL;
    
      working_path = curl_easy_unescape(data, data->state.path, 0,
    
                                        &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);
        if(real_path == NULL) {
          free(working_path);
          return CURLE_OUT_OF_MEMORY;
        }
        if((working_path_len > 1) && (working_path[1] == '~'))
          /* It is referenced to the home directory, so strip the leading '/' */
          memcpy(real_path, working_path+1, 1 + working_path_len-1);
        else
          memcpy(real_path, working_path, 1 + working_path_len);
      }
      else if(conn->protocol & PROT_SFTP) {
        if((working_path_len > 1) && (working_path[1] == '~')) {
          size_t homelen = strlen(homedir);
          real_path = (char *)malloc(homelen + working_path_len + 1);
          if(real_path == NULL) {
            free(working_path);
            return CURLE_OUT_OF_MEMORY;
          }
          /* It is referenced to the home directory, so strip the
             leading '/' */
          memcpy(real_path, homedir, homelen);
          real_path[homelen] = '/';
          real_path[homelen+1] = '\0';
          if(working_path_len > 3) {
            memcpy(real_path+homelen+1, working_path + 3,
                   1 + working_path_len -3);
          }
        }
        else {
          real_path = (char *)malloc(working_path_len+1);
          if(real_path == NULL) {
            free(working_path);
            return CURLE_OUT_OF_MEMORY;
          }
          memcpy(real_path, working_path, 1+working_path_len);
        }
      }
    
      free(working_path);
    
      /* store the pointer for the caller to receive */
      *path = real_path;
    
      return CURLE_OK;
    }
    
    
    static CURLcode ssh_statemach_act(struct connectdata *conn)
    {
      CURLcode result = CURLE_OK;
    
      struct SessionHandle *data = conn->data;
    
      struct SSHPROTO *sftp_scp = data->state.proto.ssh;
    
      struct ssh_conn *sshc = &conn->proto.sshc;
      curl_socket_t sock = conn->sock[FIRSTSOCKET];
    #ifdef CURL_LIBSSH2_DEBUG
      const char *fingerprint;
    #endif /* CURL_LIBSSH2_DEBUG */
    
      const char *host_public_key_md5;
      int rc,i;
    
      case SSH_S_STARTUP:
        sshc->secondCreateDirs = 0;
        sshc->nextstate = SSH_NO_STATE;
        sshc->actualcode = CURLE_OK;
    
        rc = libssh2_session_startup(sshc->ssh_session, sock);
        if(rc == LIBSSH2_ERROR_EAGAIN) {
          break;
        }
        else if(rc) {
          failf(data, "Failure establishing ssh session");
          state(conn, SSH_SESSION_FREE);
          sshc->actualcode = CURLE_FAILED_INIT;
          break;
        }
    
        /* Set libssh2 to non-blocking, since cURL is all non-blocking */
        libssh2_session_set_blocking(sshc->ssh_session, 0);
    
    #ifdef CURL_LIBSSH2_DEBUG
    
        /*
         * Before we authenticate we should check the hostkey's fingerprint
         * against our known hosts. How that is handled (reading from file,
         * whatever) is up to us. As for know not much is implemented, besides
         * showing how to get the fingerprint.
         */
        fingerprint = libssh2_hostkey_hash(sshc->ssh_session,
                                           LIBSSH2_HOSTKEY_HASH_MD5);
    
        /* The fingerprint points to static storage (!), don't free() it. */
        infof(data, "Fingerprint: ");
        for (rc = 0; rc < 16; rc++) {
          infof(data, "%02X ", (unsigned char) fingerprint[rc]);
        }
        infof(data, "\n");
    
    #endif /* CURL_LIBSSH2_DEBUG */
    
        /* Before we authenticate we check the hostkey's MD5 fingerprint
         * against a known fingerprint, if available.  This implementation pulls
         * it from the curl option.
         */
        if(data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5] &&
           strlen(data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]) == 32) {
          char buf[33];
          host_public_key_md5 = libssh2_hostkey_hash(sshc->ssh_session,
                                                     LIBSSH2_HOSTKEY_HASH_MD5);
          for (i = 0; i < 16; i++)
            snprintf(&buf[i*2], 3, "%02x",
                     (unsigned char) host_public_key_md5[i]);
          if(!strequal(buf, data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5])) {
            failf(data,
                  "Denied establishing ssh session: mismatch md5 fingerprint. "
                  "Remote %s is not equal to %s",
                  buf, data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]);
            state(conn, SSH_SESSION_FREE);
            sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION;
            break;
    
        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
         * must never change it later. Thus, always specify the correct username
         * here, even though the libssh2 docs kind of indicate that it should be
         * possible to get a 'generic' list (not user-specific) of authentication
         * methods, presumably with a blank username. That won't work in my
         * experience.
         * So always specify it here.
         */
        sshc->authlist = libssh2_userauth_list(sshc->ssh_session,
                                               conn->user,
                                               strlen(conn->user));
    
        if(!sshc->authlist) {
          if((err = libssh2_session_last_errno(sshc->ssh_session)) ==
             LIBSSH2_ERROR_EAGAIN) {
            break;
          }
          else {
            state(conn, SSH_SESSION_FREE);
            sshc->actualcode = libssh2_session_error_to_CURLE(err);
            break;
    
        }
        infof(data, "SSH authentication methods available: %s\n", sshc->authlist);
    
      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;
    
          /* To ponder about: should really the lib be messing about with the
             HOME environment variable etc? */
          home = curl_getenv("HOME");
    
          if(data->set.str[STRING_SSH_PUBLIC_KEY])
            sshc->rsa_pub = aprintf("%s", data->set.str[STRING_SSH_PUBLIC_KEY]);
          else if(home)
            sshc->rsa_pub = aprintf("%s/.ssh/id_dsa.pub", home);
          else
            /* as a final resort, try current dir! */
            sshc->rsa_pub = strdup("id_dsa.pub");
    
            state(conn, SSH_SESSION_FREE);
            sshc->actualcode = CURLE_OUT_OF_MEMORY;
    
          if(data->set.str[STRING_SSH_PRIVATE_KEY])
            sshc->rsa = aprintf("%s", data->set.str[STRING_SSH_PRIVATE_KEY]);
          else if(home)
            sshc->rsa = aprintf("%s/.ssh/id_dsa", home);
          else
            /* as a final resort, try current dir! */
            sshc->rsa = strdup("id_dsa");
    
          if(sshc->rsa == NULL) {
            Curl_safefree(home);
            home = NULL;
            Curl_safefree(sshc->rsa_pub);
            sshc->rsa_pub = NULL;
            state(conn, SSH_SESSION_FREE);
            sshc->actualcode = CURLE_OUT_OF_MEMORY;
    
          sshc->passphrase = data->set.str[STRING_KEY_PASSWD];
          if(!sshc->passphrase)
            sshc->passphrase = "";
    
          infof(data, "Using ssh public key file %s\n", sshc->rsa_pub);
          infof(data, "Using ssh private key file %s\n", sshc->rsa);
    
          state(conn, SSH_AUTH_PKEY);
        }
        else {
          state(conn, SSH_AUTH_PASS_INIT);
        }
        break;
    
      case SSH_AUTH_PKEY:
        /* The function below checks if the files exists, no need to stat() here.
         */
        rc = libssh2_userauth_publickey_fromfile(sshc->ssh_session,
                                                 conn->user, sshc->rsa_pub,
                                                 sshc->rsa, sshc->passphrase);
        if(rc == LIBSSH2_ERROR_EAGAIN) {
    
        Curl_safefree(sshc->rsa_pub);
        sshc->rsa_pub = NULL;
        Curl_safefree(sshc->rsa);
        sshc->rsa = NULL;
    
        if(rc == 0) {
          sshc->authed = TRUE;
          infof(data, "Initialized SSH public key authentication\n");
          state(conn, SSH_AUTH_DONE);
        }
        else {
    
          char *err_msg;
          (void)libssh2_session_last_error(sshc->ssh_session,
                                           &err_msg, NULL, 0);
          infof(data, "SSH public key authentication failed: %s\n", err_msg);
    
      case SSH_AUTH_PASS_INIT:
        if((data->set.ssh_auth_types & CURLSSH_AUTH_PASSWORD) &&
           (strstr(sshc->authlist, "password") != NULL)) {
          state(conn, SSH_AUTH_PASS);
        }
        else {
          state(conn, SSH_AUTH_HOST_INIT);
        }
        break;
    
      case SSH_AUTH_PASS:
        rc = libssh2_userauth_password(sshc->ssh_session, conn->user,
                                       conn->passwd);
        if(rc == LIBSSH2_ERROR_EAGAIN) {
    
        }
        else if(rc == 0) {
          sshc->authed = TRUE;
          infof(data, "Initialized password authentication\n");
          state(conn, SSH_AUTH_DONE);
        }
        else {
          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)) {
          state(conn, SSH_AUTH_HOST);
        }
        else {
          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(sshc->ssh_session,
                                                      conn->user,
                                                      strlen(conn->user),
                                                      &kbd_callback);
        if(rc == LIBSSH2_ERROR_EAGAIN) {
    
        }
        else if(rc == 0) {
          sshc->authed = TRUE;
          infof(data, "Initialized keyboard interactive authentication\n");
        }
        state(conn, SSH_AUTH_DONE);
        break;
    
      case SSH_AUTH_DONE:
        if(!sshc->authed) {
          failf(data, "Authentication failure");
          state(conn, SSH_SESSION_FREE);
          sshc->actualcode = CURLE_LOGIN_DENIED;
          break;
        }
    
        /*
         * At this point we have an authenticated ssh session.
         */
        infof(data, "Authentication complete\n");
    
        conn->sockfd = sock;
        conn->writesockfd = CURL_SOCKET_BAD;
    
        if(conn->protocol == PROT_SFTP) {
          state(conn, SSH_SFTP_INIT);
    
        }
        infof(data, "SSH CONNECT phase done\n");
        state(conn, SSH_STOP);
        break;
    
      case SSH_SFTP_INIT:
        /*
         * Start the libssh2 sftp session
         */
        sshc->sftp_session = libssh2_sftp_init(sshc->ssh_session);
        if(!sshc->sftp_session) {
          if(libssh2_session_last_errno(sshc->ssh_session) ==
             LIBSSH2_ERROR_EAGAIN) {
            break;
    
            char *err_msg;
    
            (void)libssh2_session_last_error(sshc->ssh_session,
                                             &err_msg, NULL, 0);
            failf(data, "Failure initializing sftp session: %s", err_msg);
    
            state(conn, SSH_SESSION_FREE);
            sshc->actualcode = CURLE_FAILED_INIT;
            break;
    
        }
        state(conn, SSH_SFTP_REALPATH);
        break;
    
      case SSH_SFTP_REALPATH:
      {
        char tempHome[PATH_MAX];
    
        /*
         * Get the "home" directory
         */
        rc = libssh2_sftp_realpath(sshc->sftp_session, ".",
                                   tempHome, PATH_MAX-1);
        if(rc == LIBSSH2_ERROR_EAGAIN) {
    
        }
        else if(rc > 0) {
          /* It seems that this string is not always NULL terminated */
          tempHome[rc] = '\0';
          sshc->homedir = (char *)strdup(tempHome);
          if(!sshc->homedir) {
            state(conn, SSH_SFTP_CLOSE);
            sshc->actualcode = CURLE_OUT_OF_MEMORY;
            break;
    
        }
        else {
          /* Return the error type */
          err = libssh2_sftp_last_error(sshc->sftp_session);
          result = sftp_libssh2_error_to_CURLE(err);
          DEBUGF(infof(data, "error = %d makes libcurl = %d\n", err, result));
          state(conn, SSH_STOP);
    
        }
      }
      /* 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\n"));
      state(conn, SSH_STOP);
      break;
    
        result = ssh_getworkingpath(conn, sshc->homedir, &sftp_scp->path);
        if(result) {
          sshc->actualcode = result;
          state(conn, SSH_STOP);
          break;
        }
    
        if(data->set.quote) {
          infof(data, "Sending quote commands\n");
          sshc->quote_item = data->set.quote;
          state(conn, SSH_SFTP_QUOTE);
        }
        else {
          state(conn, SSH_SFTP_TRANS_INIT);
        }
        break;
    
      case SSH_SFTP_POSTQUOTE_INIT:
        if(data->set.postquote) {
          infof(data, "Sending quote commands\n");
          sshc->quote_item = data->set.postquote;
          state(conn, SSH_SFTP_QUOTE);
        }
        else {
          state(conn, SSH_STOP);
        }
        break;
    
      case SSH_SFTP_QUOTE:
        /* Send any quote commands */
      {
        const char *cp;
    
        /*
         * Support some of the "FTP" commands
         */
        if(curl_strnequal(sshc->quote_item->data, "PWD", 3)) {
          /* output debug output if that is requested */
          if(data->set.verbose) {
            char tmp[PATH_MAX+1];
    
            Curl_debug(data, CURLINFO_HEADER_OUT, (char *)"PWD\n", 4, conn);
            snprintf(tmp, PATH_MAX, "257 \"%s\" is current directory.\n",
                     sftp_scp->path);
            Curl_debug(data, CURLINFO_HEADER_IN, tmp, strlen(tmp), conn);
    
        }
        else if(sshc->quote_item->data) {
          /*
           * the arguments following the command must be separated from the
           * command with a space so we can check for it unconditionally
           */
          cp = strchr(sshc->quote_item->data, ' ');
          if(cp == NULL) {
            failf(data, "Syntax error in SFTP command. Supply parameter(s)!");
            state(conn, SSH_SFTP_CLOSE);
            sshc->actualcode = CURLE_QUOTE_ERROR;
    
    
          /*
           * also, every command takes at least one argument so we get that
           * first argument right now
           */
    
    Yang Tse's avatar
    Yang Tse committed
          result = get_pathname(&cp, &sshc->quote_path1);
          if(result) {
            if(result == CURLE_OUT_OF_MEMORY)
    
              failf(data, "Out of memory");
            else
              failf(data, "Syntax error: Bad first parameter");
    
    Yang Tse's avatar
    Yang Tse committed
            sshc->actualcode = result;
    
          /*
           * SFTP is a binary protocol, so we don't send text commands to
           * the server. Instead, we scan for commands for commands used by
           * OpenSSH's sftp program and call the appropriate libssh2
           * functions.
           */
          if(curl_strnequal(sshc->quote_item->data, "chgrp ", 6) ||
             curl_strnequal(sshc->quote_item->data, "chmod ", 6) ||
             curl_strnequal(sshc->quote_item->data, "chown ", 6) ) {
            /* attribute change */
    
            /* sshc->quote_path1 contains the mode to set */
            /* get the destination */
    
    Yang Tse's avatar
    Yang Tse committed
            result = get_pathname(&cp, &sshc->quote_path2);
            if(result) {
              if(result == CURLE_OUT_OF_MEMORY)
    
                failf(data, "Out of memory");
              else
                failf(data, "Syntax error in chgrp/chmod/chown: "
                      "Bad second parameter");
    
              Curl_safefree(sshc->quote_path1);
              sshc->quote_path1 = NULL;
              state(conn, SSH_SFTP_CLOSE);
    
    Yang Tse's avatar
    Yang Tse committed
              sshc->actualcode = result;
    
            memset(&sshc->quote_attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES));
            state(conn, SSH_SFTP_QUOTE_STAT);
            break;
    
          else if(curl_strnequal(sshc->quote_item->data, "ln ", 3) ||
                  curl_strnequal(sshc->quote_item->data, "symlink ", 8)) {
            /* symbolic linking */
            /* sshc->quote_path1 is the source */
            /* get the destination */
    
    Yang Tse's avatar
    Yang Tse committed
            result = get_pathname(&cp, &sshc->quote_path2);
            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);
              sshc->quote_path1 = NULL;
              state(conn, SSH_SFTP_CLOSE);
    
    Yang Tse's avatar
    Yang Tse committed
              sshc->actualcode = result;
    
            state(conn, SSH_SFTP_QUOTE_SYMLINK);
            break;
    
          else if(curl_strnequal(sshc->quote_item->data, "mkdir ", 6)) {
            /* create dir */
            state(conn, SSH_SFTP_QUOTE_MKDIR);
            break;
          }
          else if(curl_strnequal(sshc->quote_item->data, "rename ", 7)) {
            /* rename file */
            /* first param is the source path */
            /* second param is the dest. path */
    
    Yang Tse's avatar
    Yang Tse committed
            result = get_pathname(&cp, &sshc->quote_path2);
            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);
              sshc->quote_path1 = NULL;
              state(conn, SSH_SFTP_CLOSE);
    
    Yang Tse's avatar
    Yang Tse committed
              sshc->actualcode = result;
    
          }
          else if(curl_strnequal(sshc->quote_item->data, "rmdir ", 6)) {
            /* delete dir */
            state(conn, SSH_SFTP_QUOTE_RMDIR);
    
          else if(curl_strnequal(sshc->quote_item->data, "rm ", 3)) {
            state(conn, SSH_SFTP_QUOTE_UNLINK);
    
            Curl_safefree(sshc->quote_path1);
            sshc->quote_path1 = NULL;
    
            Curl_safefree(sshc->quote_path2);
            sshc->quote_path2 = NULL;
          }
    
        }
      }
      if(!sshc->quote_item) {
        state(conn, SSH_SFTP_TRANS_INIT);
      }
      break;
    
      case SSH_SFTP_NEXT_QUOTE:
        if(sshc->quote_path1) {
          Curl_safefree(sshc->quote_path1);
          sshc->quote_path1 = NULL;
        }
        if(sshc->quote_path2) {
          Curl_safefree(sshc->quote_path2);
          sshc->quote_path2 = NULL;
        }
    
        sshc->quote_item = sshc->quote_item->next;
    
        if(sshc->quote_item) {
          state(conn, SSH_SFTP_QUOTE);
        }
        else {
          if(sshc->nextstate != SSH_NO_STATE) {
            state(conn, sshc->nextstate);
            sshc->nextstate = SSH_NO_STATE;