static CURLcode ssh_statemach_act()

in libs/curl/lib/vssh/libssh2.c [968:3049]


static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block)
{
  CURLcode result = CURLE_OK;
  struct connectdata *conn = data->conn;
  struct SSHPROTO *sshp = data->req.p.ssh;
  struct ssh_conn *sshc = &conn->proto.sshc;
  curl_socket_t sock = conn->sock[FIRSTSOCKET];
  int rc = LIBSSH2_ERROR_NONE;
  int ssherr;
  unsigned long sftperr;
  int seekerr = CURL_SEEKFUNC_OK;
  size_t readdir_len;
  *block = 0; /* we are not blocking by default */

  do {
    switch(sshc->state) {
    case SSH_INIT:
      sshc->secondCreateDirs = 0;
      sshc->nextstate = SSH_NO_STATE;
      sshc->actualcode = CURLE_OK;

      /* Set libssh2 to non-blocking, since everything internally is
         non-blocking */
      libssh2_session_set_blocking(sshc->ssh_session, 0);

      result = ssh_force_knownhost_key_type(data);
      if(result) {
        state(data, SSH_SESSION_FREE);
        sshc->actualcode = result;
        break;
      }

      state(data, SSH_S_STARTUP);
      FALLTHROUGH();

    case SSH_S_STARTUP:
      rc = session_startup(sshc->ssh_session, sock);
      if(rc == LIBSSH2_ERROR_EAGAIN) {
        break;
      }
      if(rc) {
        char *err_msg = NULL;
        (void)libssh2_session_last_error(sshc->ssh_session, &err_msg, NULL, 0);
        failf(data, "Failure establishing ssh session: %d, %s", rc, err_msg);

        state(data, SSH_SESSION_FREE);
        sshc->actualcode = CURLE_FAILED_INIT;
        break;
      }

      state(data, SSH_HOSTKEY);

      FALLTHROUGH();
    case SSH_HOSTKEY:
      /*
       * 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.
       */
      result = ssh_check_fingerprint(data);
      if(!result)
        state(data, SSH_AUTHLIST);
      /* ssh_check_fingerprint sets state appropriately on error */
      break;

    case SSH_AUTHLIST:
      /*
       * 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 will not work in my
       * experience.
       * So always specify it here.
       */
      sshc->authlist = libssh2_userauth_list(sshc->ssh_session,
                                             conn->user,
                                             curlx_uztoui(strlen(conn->user)));

      if(!sshc->authlist) {
        if(libssh2_userauth_authenticated(sshc->ssh_session)) {
          sshc->authed = TRUE;
          infof(data, "SSH user accepted with no authentication");
          state(data, SSH_AUTH_DONE);
          break;
        }
        ssherr = libssh2_session_last_errno(sshc->ssh_session);
        if(ssherr == LIBSSH2_ERROR_EAGAIN)
          rc = LIBSSH2_ERROR_EAGAIN;
        else {
          state(data, SSH_SESSION_FREE);
          sshc->actualcode = libssh2_session_error_to_CURLE(ssherr);
        }
        break;
      }
      infof(data, "SSH authentication methods available: %s",
            sshc->authlist);

      state(data, 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)) {
        bool out_of_memory = FALSE;

        sshc->rsa_pub = sshc->rsa = NULL;

        if(data->set.str[STRING_SSH_PRIVATE_KEY])
          sshc->rsa = strdup(data->set.str[STRING_SSH_PRIVATE_KEY]);
        else {
          /* To ponder about: should really the lib be messing about with the
             HOME environment variable etc? */
          char *home = curl_getenv("HOME");
          struct_stat sbuf;

          /* If no private key file is specified, try some common paths. */
          if(home) {
            /* Try ~/.ssh first. */
            sshc->rsa = aprintf("%s/.ssh/id_rsa", home);
            if(!sshc->rsa)
              out_of_memory = TRUE;
            else if(stat(sshc->rsa, &sbuf)) {
              Curl_safefree(sshc->rsa);
              sshc->rsa = aprintf("%s/.ssh/id_dsa", home);
              if(!sshc->rsa)
                out_of_memory = TRUE;
              else if(stat(sshc->rsa, &sbuf)) {
                Curl_safefree(sshc->rsa);
              }
            }
            free(home);
          }
          if(!out_of_memory && !sshc->rsa) {
            /* Nothing found; try the current dir. */
            sshc->rsa = strdup("id_rsa");
            if(sshc->rsa && stat(sshc->rsa, &sbuf)) {
              Curl_safefree(sshc->rsa);
              sshc->rsa = strdup("id_dsa");
              if(sshc->rsa && stat(sshc->rsa, &sbuf)) {
                Curl_safefree(sshc->rsa);
                /* Out of guesses. Set to the empty string to avoid
                 * surprising info messages. */
                sshc->rsa = strdup("");
              }
            }
          }
        }

        /*
         * Unless the user explicitly specifies a public key file, let
         * libssh2 extract the public key from the private key file.
         * This is done by simply passing sshc->rsa_pub = NULL.
         */
        if(data->set.str[STRING_SSH_PUBLIC_KEY]
           /* treat empty string the same way as NULL */
           && data->set.str[STRING_SSH_PUBLIC_KEY][0]) {
          sshc->rsa_pub = strdup(data->set.str[STRING_SSH_PUBLIC_KEY]);
          if(!sshc->rsa_pub)
            out_of_memory = TRUE;
        }

        if(out_of_memory || !sshc->rsa) {
          Curl_safefree(sshc->rsa);
          Curl_safefree(sshc->rsa_pub);
          state(data, SSH_SESSION_FREE);
          sshc->actualcode = CURLE_OUT_OF_MEMORY;
          break;
        }

        sshc->passphrase = data->set.ssl.key_passwd;
        if(!sshc->passphrase)
          sshc->passphrase = "";

        if(sshc->rsa_pub)
          infof(data, "Using SSH public key file '%s'", sshc->rsa_pub);
        infof(data, "Using SSH private key file '%s'", sshc->rsa);

        state(data, SSH_AUTH_PKEY);
      }
      else {
        state(data, 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_ex(sshc->ssh_session,
                                                  conn->user,
                                                  curlx_uztoui(
                                                    strlen(conn->user)),
                                                  sshc->rsa_pub,
                                                  sshc->rsa, sshc->passphrase);
      if(rc == LIBSSH2_ERROR_EAGAIN) {
        break;
      }

      Curl_safefree(sshc->rsa_pub);
      Curl_safefree(sshc->rsa);

      if(rc == 0) {
        sshc->authed = TRUE;
        infof(data, "Initialized SSH public key authentication");
        state(data, SSH_AUTH_DONE);
      }
      else {
        char *err_msg = NULL;
        char unknown[] = "Reason unknown (-1)";
        if(rc == -1) {
          /* No error message has been set and the last set error message, if
             any, is from a previous error so ignore it. #11837 */
          err_msg = unknown;
        }
        else {
          (void)libssh2_session_last_error(sshc->ssh_session,
                                           &err_msg, NULL, 0);
        }
        infof(data, "SSH public key authentication failed: %s", err_msg);
        state(data, SSH_AUTH_PASS_INIT);
        rc = 0; /* clear rc and continue */
      }
      break;

    case SSH_AUTH_PASS_INIT:
      if((data->set.ssh_auth_types & CURLSSH_AUTH_PASSWORD) &&
         (strstr(sshc->authlist, "password") != NULL)) {
        state(data, SSH_AUTH_PASS);
      }
      else {
        state(data, SSH_AUTH_HOST_INIT);
        rc = 0; /* clear rc and continue */
      }
      break;

    case SSH_AUTH_PASS:
      rc = libssh2_userauth_password_ex(sshc->ssh_session, conn->user,
                                        curlx_uztoui(strlen(conn->user)),
                                        conn->passwd,
                                        curlx_uztoui(strlen(conn->passwd)),
                                        NULL);
      if(rc == LIBSSH2_ERROR_EAGAIN) {
        break;
      }
      if(rc == 0) {
        sshc->authed = TRUE;
        infof(data, "Initialized password authentication");
        state(data, SSH_AUTH_DONE);
      }
      else {
        state(data, SSH_AUTH_HOST_INIT);
        rc = 0; /* clear rc and continue */
      }
      break;

    case SSH_AUTH_HOST_INIT:
      if((data->set.ssh_auth_types & CURLSSH_AUTH_HOST) &&
         (strstr(sshc->authlist, "hostbased") != NULL)) {
        state(data, SSH_AUTH_HOST);
      }
      else {
        state(data, SSH_AUTH_AGENT_INIT);
      }
      break;

    case SSH_AUTH_HOST:
      state(data, SSH_AUTH_AGENT_INIT);
      break;

    case SSH_AUTH_AGENT_INIT:
#ifdef HAVE_LIBSSH2_AGENT_API
      if((data->set.ssh_auth_types & CURLSSH_AUTH_AGENT)
         && (strstr(sshc->authlist, "publickey") != NULL)) {

        /* Connect to the ssh-agent */
        /* The agent could be shared by a curl thread i believe
           but nothing obvious as keys can be added/removed at any time */
        if(!sshc->ssh_agent) {
          sshc->ssh_agent = libssh2_agent_init(sshc->ssh_session);
          if(!sshc->ssh_agent) {
            infof(data, "Could not create agent object");

            state(data, SSH_AUTH_KEY_INIT);
            break;
          }
        }

        rc = libssh2_agent_connect(sshc->ssh_agent);
        if(rc == LIBSSH2_ERROR_EAGAIN)
          break;
        if(rc < 0) {
          infof(data, "Failure connecting to agent");
          state(data, SSH_AUTH_KEY_INIT);
          rc = 0; /* clear rc and continue */
        }
        else {
          state(data, SSH_AUTH_AGENT_LIST);
        }
      }
      else
#endif /* HAVE_LIBSSH2_AGENT_API */
        state(data, SSH_AUTH_KEY_INIT);
      break;

    case SSH_AUTH_AGENT_LIST:
#ifdef HAVE_LIBSSH2_AGENT_API
      rc = libssh2_agent_list_identities(sshc->ssh_agent);

      if(rc == LIBSSH2_ERROR_EAGAIN)
        break;
      if(rc < 0) {
        infof(data, "Failure requesting identities to agent");
        state(data, SSH_AUTH_KEY_INIT);
        rc = 0; /* clear rc and continue */
      }
      else {
        state(data, SSH_AUTH_AGENT);
        sshc->sshagent_prev_identity = NULL;
      }
#endif
      break;

    case SSH_AUTH_AGENT:
#ifdef HAVE_LIBSSH2_AGENT_API
      /* as prev_identity evolves only after an identity user auth finished we
         can safely request it again as long as EAGAIN is returned here or by
         libssh2_agent_userauth */
      rc = libssh2_agent_get_identity(sshc->ssh_agent,
                                      &sshc->sshagent_identity,
                                      sshc->sshagent_prev_identity);
      if(rc == LIBSSH2_ERROR_EAGAIN)
        break;

      if(rc == 0) {
        rc = libssh2_agent_userauth(sshc->ssh_agent, conn->user,
                                    sshc->sshagent_identity);

        if(rc < 0) {
          if(rc != LIBSSH2_ERROR_EAGAIN) {
            /* tried and failed? go to next identity */
            sshc->sshagent_prev_identity = sshc->sshagent_identity;
          }
          break;
        }
      }

      if(rc < 0)
        infof(data, "Failure requesting identities to agent");
      else if(rc == 1)
        infof(data, "No identity would match");

      if(rc == LIBSSH2_ERROR_NONE) {
        sshc->authed = TRUE;
        infof(data, "Agent based authentication successful");
        state(data, SSH_AUTH_DONE);
      }
      else {
        state(data, SSH_AUTH_KEY_INIT);
        rc = 0; /* clear rc and continue */
      }
#endif
      break;

    case SSH_AUTH_KEY_INIT:
      if((data->set.ssh_auth_types & CURLSSH_AUTH_KEYBOARD)
         && (strstr(sshc->authlist, "keyboard-interactive") != NULL)) {
        state(data, SSH_AUTH_KEY);
      }
      else {
        state(data, 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,
                                                    curlx_uztoui(
                                                      strlen(conn->user)),
                                                    &kbd_callback);
      if(rc == LIBSSH2_ERROR_EAGAIN) {
        break;
      }
      if(rc == 0) {
        sshc->authed = TRUE;
        infof(data, "Initialized keyboard interactive authentication");
      }
      state(data, SSH_AUTH_DONE);
      break;

    case SSH_AUTH_DONE:
      if(!sshc->authed) {
        failf(data, "Authentication failure");
        state(data, SSH_SESSION_FREE);
        sshc->actualcode = 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:
      /*
       * Start the libssh2 sftp session
       */
      sshc->sftp_session = libssh2_sftp_init(sshc->ssh_session);
      if(!sshc->sftp_session) {
        char *err_msg = NULL;
        if(libssh2_session_last_errno(sshc->ssh_session) ==
           LIBSSH2_ERROR_EAGAIN) {
          rc = LIBSSH2_ERROR_EAGAIN;
          break;
        }

        (void)libssh2_session_last_error(sshc->ssh_session,
                                         &err_msg, NULL, 0);
        failf(data, "Failure initializing sftp session: %s", err_msg);
        state(data, SSH_SESSION_FREE);
        sshc->actualcode = CURLE_FAILED_INIT;
        break;
      }
      state(data, SSH_SFTP_REALPATH);
      break;

    case SSH_SFTP_REALPATH:
    {
      char tempHome[PATH_MAX];

      /*
       * Get the "home" directory
       */
      rc = sftp_libssh2_realpath(sshc->sftp_session, ".",
                                 tempHome, PATH_MAX-1);
      if(rc == LIBSSH2_ERROR_EAGAIN) {
        break;
      }
      if(rc > 0) {
        /* It seems that this string is not always NULL terminated */
        tempHome[rc] = '\0';
        sshc->homedir = strdup(tempHome);
        if(!sshc->homedir) {
          state(data, SSH_SFTP_CLOSE);
          sshc->actualcode = CURLE_OUT_OF_MEMORY;
          break;
        }
        data->state.most_recent_ftp_entrypath = sshc->homedir;
      }
      else {
        /* Return the error type */
        sftperr = libssh2_sftp_last_error(sshc->sftp_session);
        if(sftperr)
          result = sftp_libssh2_error_to_CURLE(sftperr);
        else
          /* in this case, the error was not in the SFTP level but for example
             a time-out or similar */
          result = CURLE_SSH;
        sshc->actualcode = result;
        DEBUGF(infof(data, "error = %lu makes libcurl = %d",
                     sftperr, (int)result));
        state(data, SSH_STOP);
        break;
      }
    }
    /* 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, &sshp->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 */
    {
      const char *cp;

      /*
       * Support some of the "FTP" commands
       *
       * 'sshc->quote_item' is already verified to be non-NULL before it
       * switched to this state.
       */
      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 responds. */

      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",
                            sshp->path);
        if(!tmp) {
          result = CURLE_OUT_OF_MEMORY;
          state(data, SSH_SFTP_CLOSE);
          sshc->nextstate = SSH_NO_STATE;
          break;
        }
        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);
        break;
      }

      /*
       * 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 command '%s', missing parameter",
              cmd);
        state(data, SSH_SFTP_CLOSE);
        sshc->nextstate = SSH_NO_STATE;
        sshc->actualcode = CURLE_QUOTE_ERROR;
        break;
      }

      /*
       * 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 to '%s'", cmd);
        state(data, SSH_SFTP_CLOSE);
        sshc->nextstate = SSH_NO_STATE;
        sshc->actualcode = result;
        break;
      }

      /*
       * SFTP is a binary protocol, so we do not send text commands
       * to the server. Instead, we scan for commands used by
       * OpenSSH's sftp program and call the appropriate libssh2
       * 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 %s: Bad second parameter", cmd);
          Curl_safefree(sshc->quote_path1);
          state(data, SSH_SFTP_CLOSE);
          sshc->nextstate = SSH_NO_STATE;
          sshc->actualcode = result;
          break;
        }
        memset(&sshp->quote_attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES));
        state(data, SSH_SFTP_QUOTE_STAT);
        break;
      }
      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;
          break;
        }
        state(data, SSH_SFTP_QUOTE_SYMLINK);
        break;
      }
      else if(strncasecompare(cmd, "mkdir ", 6)) {
        /* create dir */
        state(data, SSH_SFTP_QUOTE_MKDIR);
        break;
      }
      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;
          break;
        }
        state(data, SSH_SFTP_QUOTE_RENAME);
        break;
      }
      else if(strncasecompare(cmd, "rmdir ", 6)) {
        /* delete dir */
        state(data, SSH_SFTP_QUOTE_RMDIR);
        break;
      }
      else if(strncasecompare(cmd, "rm ", 3)) {
        state(data, SSH_SFTP_QUOTE_UNLINK);
        break;
      }
#ifdef HAS_STATVFS_SUPPORT
      else if(strncasecompare(cmd, "statvfs ", 8)) {
        state(data, SSH_SFTP_QUOTE_STATVFS);
        break;
      }
#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;
      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:
    {
      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 responds. */

      if(cmd[0] == '*') {
        cmd++;
        sshc->acceptfail = TRUE;
      }

      if(!strncasecompare(cmd, "chmod", 5)) {
        /* Since chown and chgrp only set owner OR group but libssh2 wants to
         * set them both at once, we need to obtain the current ownership
         * first. This takes an extra protocol round trip.
         */
        rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshc->quote_path2,
                                  curlx_uztoui(strlen(sshc->quote_path2)),
                                  LIBSSH2_SFTP_STAT,
                                  &sshp->quote_attrs);
        if(rc == LIBSSH2_ERROR_EAGAIN) {
          break;
        }
        if(rc && !sshc->acceptfail) { /* get those attributes */
          sftperr = libssh2_sftp_last_error(sshc->sftp_session);
          Curl_safefree(sshc->quote_path1);
          Curl_safefree(sshc->quote_path2);
          failf(data, "Attempt to get SFTP stats failed: %s",
                sftp_libssh2_strerror(sftperr));
          state(data, SSH_SFTP_CLOSE);
          sshc->nextstate = SSH_NO_STATE;
          sshc->actualcode = CURLE_QUOTE_ERROR;
          break;
        }
      }

      /* Now set the new attributes... */
      if(strncasecompare(cmd, "chgrp", 5)) {
        sshp->quote_attrs.gid = strtoul(sshc->quote_path1, NULL, 10);
        sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_UIDGID;
        if(sshp->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;
          break;
        }
      }
      else if(strncasecompare(cmd, "chmod", 5)) {
        sshp->quote_attrs.permissions = strtoul(sshc->quote_path1, NULL, 8);
        sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_PERMISSIONS;
        /* permissions are octal */
        if(sshp->quote_attrs.permissions == 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;
          break;
        }
      }
      else if(strncasecompare(cmd, "chown", 5)) {
        sshp->quote_attrs.uid = strtoul(sshc->quote_path1, NULL, 10);
        sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_UIDGID;
        if(sshp->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;
          break;
        }
      }
      else if(strncasecompare(cmd, "atime", 5) ||
              strncasecompare(cmd, "mtime", 5)) {
        time_t date = Curl_getdate_capped(sshc->quote_path1);
        bool fail = FALSE;

        if(date == -1) {
          failf(data, "incorrect date format for %.*s", 5, cmd);
          fail = TRUE;
        }
#if SIZEOF_TIME_T > SIZEOF_LONG
        if(date > 0xffffffff) {
          /* if 'long' cannot old >32bit, this date cannot be sent */
          failf(data, "date overflow");
          fail = TRUE;
        }
#endif
        if(fail) {
          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;
          break;
        }
        if(strncasecompare(cmd, "atime", 5))
          sshp->quote_attrs.atime = (unsigned long)date;
        else /* mtime */
          sshp->quote_attrs.mtime = (unsigned long)date;

        sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_ACMODTIME;
      }

      /* Now send the completed structure... */
      state(data, SSH_SFTP_QUOTE_SETSTAT);
      break;
    }

    case SSH_SFTP_QUOTE_SETSTAT:
      rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshc->quote_path2,
                                curlx_uztoui(strlen(sshc->quote_path2)),
                                LIBSSH2_SFTP_SETSTAT,
                                &sshp->quote_attrs);
      if(rc == LIBSSH2_ERROR_EAGAIN) {
        break;
      }
      if(rc && !sshc->acceptfail) {
        sftperr = libssh2_sftp_last_error(sshc->sftp_session);
        Curl_safefree(sshc->quote_path1);
        Curl_safefree(sshc->quote_path2);
        failf(data, "Attempt to set SFTP stats failed: %s",
              sftp_libssh2_strerror(sftperr));
        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_SYMLINK:
      rc = libssh2_sftp_symlink_ex(sshc->sftp_session, sshc->quote_path1,
                                   curlx_uztoui(strlen(sshc->quote_path1)),
                                   sshc->quote_path2,
                                   curlx_uztoui(strlen(sshc->quote_path2)),
                                   LIBSSH2_SFTP_SYMLINK);
      if(rc == LIBSSH2_ERROR_EAGAIN) {
        break;
      }
      if(rc && !sshc->acceptfail) {
        sftperr = libssh2_sftp_last_error(sshc->sftp_session);
        Curl_safefree(sshc->quote_path1);
        Curl_safefree(sshc->quote_path2);
        failf(data, "symlink command failed: %s",
              sftp_libssh2_strerror(sftperr));
        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 = libssh2_sftp_mkdir_ex(sshc->sftp_session, sshc->quote_path1,
                                 curlx_uztoui(strlen(sshc->quote_path1)),
                                 (long)data->set.new_directory_perms);
      if(rc == LIBSSH2_ERROR_EAGAIN) {
        break;
      }
      if(rc && !sshc->acceptfail) {
        sftperr = libssh2_sftp_last_error(sshc->sftp_session);
        Curl_safefree(sshc->quote_path1);
        failf(data, "mkdir command failed: %s",
              sftp_libssh2_strerror(sftperr));
        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 = libssh2_sftp_rename_ex(sshc->sftp_session, sshc->quote_path1,
                                  curlx_uztoui(strlen(sshc->quote_path1)),
                                  sshc->quote_path2,
                                  curlx_uztoui(strlen(sshc->quote_path2)),
                                  LIBSSH2_SFTP_RENAME_OVERWRITE |
                                  LIBSSH2_SFTP_RENAME_ATOMIC |
                                  LIBSSH2_SFTP_RENAME_NATIVE);

      if(rc == LIBSSH2_ERROR_EAGAIN) {
        break;
      }
      if(rc && !sshc->acceptfail) {
        sftperr = libssh2_sftp_last_error(sshc->sftp_session);
        Curl_safefree(sshc->quote_path1);
        Curl_safefree(sshc->quote_path2);
        failf(data, "rename command failed: %s",
              sftp_libssh2_strerror(sftperr));
        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 = libssh2_sftp_rmdir_ex(sshc->sftp_session, sshc->quote_path1,
                                 curlx_uztoui(strlen(sshc->quote_path1)));
      if(rc == LIBSSH2_ERROR_EAGAIN) {
        break;
      }
      if(rc && !sshc->acceptfail) {
        sftperr = libssh2_sftp_last_error(sshc->sftp_session);
        Curl_safefree(sshc->quote_path1);
        failf(data, "rmdir command failed: %s",
              sftp_libssh2_strerror(sftperr));
        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 = libssh2_sftp_unlink_ex(sshc->sftp_session, sshc->quote_path1,
                                  curlx_uztoui(strlen(sshc->quote_path1)));
      if(rc == LIBSSH2_ERROR_EAGAIN) {
        break;
      }
      if(rc && !sshc->acceptfail) {
        sftperr = libssh2_sftp_last_error(sshc->sftp_session);
        Curl_safefree(sshc->quote_path1);
        failf(data, "rm command failed: %s", sftp_libssh2_strerror(sftperr));
        state(data, SSH_SFTP_CLOSE);
        sshc->nextstate = SSH_NO_STATE;
        sshc->actualcode = CURLE_QUOTE_ERROR;
        break;
      }
      state(data, SSH_SFTP_NEXT_QUOTE);
      break;

#ifdef HAS_STATVFS_SUPPORT
    case SSH_SFTP_QUOTE_STATVFS:
    {
      LIBSSH2_SFTP_STATVFS statvfs;
      rc = libssh2_sftp_statvfs(sshc->sftp_session, sshc->quote_path1,
                                curlx_uztoui(strlen(sshc->quote_path1)),
                                &statvfs);

      if(rc == LIBSSH2_ERROR_EAGAIN) {
        break;
      }
      if(rc && !sshc->acceptfail) {
        sftperr = libssh2_sftp_last_error(sshc->sftp_session);
        Curl_safefree(sshc->quote_path1);
        failf(data, "statvfs command failed: %s",
              sftp_libssh2_strerror(sftperr));
        state(data, SSH_SFTP_CLOSE);
        sshc->nextstate = SSH_NO_STATE;
        sshc->actualcode = CURLE_QUOTE_ERROR;
        break;
      }
      else if(rc == 0) {
        #ifdef _MSC_VER
        #define CURL_LIBSSH2_VFS_SIZE_MASK "I64u"
        #else
        #define CURL_LIBSSH2_VFS_SIZE_MASK "llu"
        #endif
        char *tmp = aprintf("statvfs:\n"
                            "f_bsize: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
                            "f_frsize: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
                            "f_blocks: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
                            "f_bfree: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
                            "f_bavail: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
                            "f_files: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
                            "f_ffree: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
                            "f_favail: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
                            "f_fsid: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
                            "f_flag: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
                            "f_namemax: %" CURL_LIBSSH2_VFS_SIZE_MASK "\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);
        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;
    }
#endif
    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:
    {
      LIBSSH2_SFTP_ATTRIBUTES attrs;

      rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path,
                                curlx_uztoui(strlen(sshp->path)),
                                LIBSSH2_SFTP_STAT, &attrs);
      if(rc == LIBSSH2_ERROR_EAGAIN) {
        break;
      }
      if(rc == 0) {
        data->info.filetime = (time_t)attrs.mtime;
      }

      state(data, SSH_SFTP_TRANS_INIT);
      break;
    }

    case SSH_SFTP_TRANS_INIT:
      if(data->state.upload)
        state(data, SSH_SFTP_UPLOAD_INIT);
      else {
        if(sshp->path[strlen(sshp->path)-1] == '/')
          state(data, SSH_SFTP_READDIR_INIT);
        else
          state(data, SSH_SFTP_DOWNLOAD_INIT);
      }
      break;

    case SSH_SFTP_UPLOAD_INIT:
    {
      unsigned long flags;
      /*
       * NOTE!!!  libssh2 requires that the destination path is a full path
       *          that includes the destination file and name OR ends in a "/"
       *          If this is not done the destination file will be named the
       *          same name as the last directory in the path.
       */

      if(data->state.resume_from) {
        LIBSSH2_SFTP_ATTRIBUTES attrs;
        if(data->state.resume_from < 0) {
          rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path,
                                    curlx_uztoui(strlen(sshp->path)),
                                    LIBSSH2_SFTP_STAT, &attrs);
          if(rc == LIBSSH2_ERROR_EAGAIN) {
            break;
          }
          if(rc) {
            data->state.resume_from = 0;
          }
          else {
            curl_off_t size = attrs.filesize;
            if(size < 0) {
              failf(data, "Bad file size (%" CURL_FORMAT_CURL_OFF_T ")", size);
              return CURLE_BAD_DOWNLOAD_RESUME;
            }
            data->state.resume_from = attrs.filesize;
          }
        }
      }

      if(data->set.remote_append)
        /* Try to open for append, but create if nonexisting */
        flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_APPEND;
      else if(data->state.resume_from > 0)
        /* If we have restart position then open for append */
        flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_APPEND;
      else
        /* Clear file before writing (normal behavior) */
        flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC;

      sshc->sftp_handle =
        libssh2_sftp_open_ex(sshc->sftp_session, sshp->path,
                             curlx_uztoui(strlen(sshp->path)),
                             flags, (long)data->set.new_file_perms,
                             LIBSSH2_SFTP_OPENFILE);

      if(!sshc->sftp_handle) {
        rc = libssh2_session_last_errno(sshc->ssh_session);

        if(LIBSSH2_ERROR_EAGAIN == rc)
          break;

        if(LIBSSH2_ERROR_SFTP_PROTOCOL == rc)
          /* only when there was an SFTP protocol error can we extract
             the sftp error! */
          sftperr = libssh2_sftp_last_error(sshc->sftp_session);
        else
          sftperr = LIBSSH2_FX_OK; /* not an sftp error at all */

        if(sshc->secondCreateDirs) {
          state(data, SSH_SFTP_CLOSE);
          sshc->actualcode = sftperr != LIBSSH2_FX_OK ?
            sftp_libssh2_error_to_CURLE(sftperr):CURLE_SSH;
          failf(data, "Creating the dir/file failed: %s",
                sftp_libssh2_strerror(sftperr));
          break;
        }
        if(((sftperr == LIBSSH2_FX_NO_SUCH_FILE) ||
            (sftperr == LIBSSH2_FX_FAILURE) ||
            (sftperr == LIBSSH2_FX_NO_SUCH_PATH)) &&
           (data->set.ftp_create_missing_dirs &&
            (strlen(sshp->path) > 1))) {
          /* try to create the path remotely */
          rc = 0; /* clear rc and continue */
          sshc->secondCreateDirs = 1;
          state(data, SSH_SFTP_CREATE_DIRS_INIT);
          break;
        }
        state(data, SSH_SFTP_CLOSE);
        sshc->actualcode = sftperr != LIBSSH2_FX_OK ?
          sftp_libssh2_error_to_CURLE(sftperr):CURLE_SSH;
        if(!sshc->actualcode) {
          /* Sometimes, for some reason libssh2_sftp_last_error() returns zero
             even though libssh2_sftp_open() failed previously! We need to
             work around that! */
          sshc->actualcode = CURLE_SSH;
          sftperr = LIBSSH2_FX_OK;
        }
        failf(data, "Upload failed: %s (%lu/%d)",
              sftperr != LIBSSH2_FX_OK ?
              sftp_libssh2_strerror(sftperr):"ssh error",
              sftperr, rc);
        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(data->set.seek_func) {
          Curl_set_in_callback(data, true);
          seekerr = data->set.seek_func(data->set.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 (cannot seek to offset) */
          do {
            char scratch[4*1024];
            size_t readthisamountnow =
              (data->state.resume_from - passed >
                (curl_off_t)sizeof(scratch)) ?
              sizeof(scratch) : curlx_sotouz(data->state.resume_from - passed);

            size_t actuallyread;
            Curl_set_in_callback(data, true);
            actuallyread = data->state.fread_func(scratch, 1,
                                                  readthisamountnow,
                                                  data->state.in);
            Curl_set_in_callback(data, false);

            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");
              return CURLE_FTP_COULDNT_USE_REST;
            }
          } while(passed < data->state.resume_from);
        }

        /* 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);
        }

        SFTP_SEEK(sshc->sftp_handle, data->state.resume_from);
      }
      if(data->state.infilesize > 0) {
        data->req.size = data->state.infilesize;
        Curl_pgrsSetUploadSize(data, data->state.infilesize);
      }
      /* upload data */
      Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE);

      /* not set by Curl_xfer_setup to preserve keepon bits */
      conn->sockfd = conn->writesockfd;

      if(result) {
        state(data, SSH_SFTP_CLOSE);
        sshc->actualcode = result;
      }
      else {
        /* store this original bitmask setup to use later on if we cannot
           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 libssh2 sftp send function will deal
           with both accordingly */
        data->state.select_bits = CURL_CSELECT_OUT;

        /* since we do not 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(sshp->path) > 1) {
        sshc->slash_pos = sshp->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'", sshp->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 = libssh2_sftp_mkdir_ex(sshc->sftp_session, sshp->path,
                                 curlx_uztoui(strlen(sshp->path)),
                                 (long)data->set.new_directory_perms);
      if(rc == LIBSSH2_ERROR_EAGAIN) {
        break;
      }
      *sshc->slash_pos = '/';
      ++sshc->slash_pos;
      if(rc < 0) {
        /*
         * Abort if failure was not that the dir already exists or the
         * permission was denied (creation might succeed further down the
         * path) - retry on unspecific FAILURE also
         */
        sftperr = libssh2_sftp_last_error(sshc->sftp_session);
        if((sftperr != LIBSSH2_FX_FILE_ALREADY_EXISTS) &&
           (sftperr != LIBSSH2_FX_FAILURE) &&
           (sftperr != LIBSSH2_FX_PERMISSION_DENIED)) {
          result = sftp_libssh2_error_to_CURLE(sftperr);
          state(data, SSH_SFTP_CLOSE);
          sshc->actualcode = result?result:CURLE_SSH;
          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->req.no_body) {
        state(data, SSH_STOP);
        break;
      }

      /*
       * This is a directory that we are trying to get, so produce a directory
       * listing
       */
      sshc->sftp_handle = libssh2_sftp_open_ex(sshc->sftp_session,
                                               sshp->path,
                                               curlx_uztoui(
                                                 strlen(sshp->path)),
                                               0, 0, LIBSSH2_SFTP_OPENDIR);
      if(!sshc->sftp_handle) {
        if(libssh2_session_last_errno(sshc->ssh_session) ==
           LIBSSH2_ERROR_EAGAIN) {
          rc = LIBSSH2_ERROR_EAGAIN;
          break;
        }
        sftperr = libssh2_sftp_last_error(sshc->sftp_session);
        failf(data, "Could not open directory for reading: %s",
              sftp_libssh2_strerror(sftperr));
        state(data, SSH_SFTP_CLOSE);
        result = sftp_libssh2_error_to_CURLE(sftperr);
        sshc->actualcode = result?result:CURLE_SSH;
        break;
      }
      sshp->readdir_filename = malloc(PATH_MAX + 1);
      if(!sshp->readdir_filename) {
        state(data, SSH_SFTP_CLOSE);
        sshc->actualcode = CURLE_OUT_OF_MEMORY;
        break;
      }
      sshp->readdir_longentry = malloc(PATH_MAX + 1);
      if(!sshp->readdir_longentry) {
        Curl_safefree(sshp->readdir_filename);
        state(data, SSH_SFTP_CLOSE);
        sshc->actualcode = CURLE_OUT_OF_MEMORY;
        break;
      }
      Curl_dyn_init(&sshp->readdir, PATH_MAX * 2);
      state(data, SSH_SFTP_READDIR);
      break;

    case SSH_SFTP_READDIR:
      rc = libssh2_sftp_readdir_ex(sshc->sftp_handle,
                                   sshp->readdir_filename,
                                   PATH_MAX,
                                   sshp->readdir_longentry,
                                   PATH_MAX,
                                   &sshp->readdir_attrs);
      if(rc == LIBSSH2_ERROR_EAGAIN) {
        break;
      }
      if(rc > 0) {
        readdir_len = (size_t) rc;
        sshp->readdir_filename[readdir_len] = '\0';

        if(data->set.list_only) {
          result = Curl_client_write(data, CLIENTWRITE_BODY,
                                     sshp->readdir_filename,
                                     readdir_len);
          if(!result)
            result = Curl_client_write(data, CLIENTWRITE_BODY,
                                       (char *)"\n", 1);
          if(result) {
            state(data, SSH_STOP);
            break;
          }

        }
        else {
          result = Curl_dyn_add(&sshp->readdir, sshp->readdir_longentry);

          if(!result) {
            if((sshp->readdir_attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) &&
               ((sshp->readdir_attrs.permissions & LIBSSH2_SFTP_S_IFMT) ==
                LIBSSH2_SFTP_S_IFLNK)) {
              Curl_dyn_init(&sshp->readdir_link, PATH_MAX);
              result = Curl_dyn_addf(&sshp->readdir_link, "%s%s", sshp->path,
                                     sshp->readdir_filename);
              state(data, SSH_SFTP_READDIR_LINK);
              if(!result)
                break;
            }
            else {
              state(data, SSH_SFTP_READDIR_BOTTOM);
              break;
            }
          }
          sshc->actualcode = result;
          state(data, SSH_SFTP_CLOSE);
          break;
        }
      }
      else if(rc == 0) {
        Curl_safefree(sshp->readdir_filename);
        Curl_safefree(sshp->readdir_longentry);
        state(data, SSH_SFTP_READDIR_DONE);
        break;
      }
      else if(rc < 0) {
        sftperr = libssh2_sftp_last_error(sshc->sftp_session);
        result = sftp_libssh2_error_to_CURLE(sftperr);
        sshc->actualcode = result?result:CURLE_SSH;
        failf(data, "Could not open remote file for reading: %s :: %d",
              sftp_libssh2_strerror(sftperr),
              libssh2_session_last_errno(sshc->ssh_session));
        Curl_safefree(sshp->readdir_filename);
        Curl_safefree(sshp->readdir_longentry);
        state(data, SSH_SFTP_CLOSE);
        break;
      }
      break;

    case SSH_SFTP_READDIR_LINK:
      rc =
        libssh2_sftp_symlink_ex(sshc->sftp_session,
                                Curl_dyn_ptr(&sshp->readdir_link),
                                (unsigned int)
                                  Curl_dyn_len(&sshp->readdir_link),
                                sshp->readdir_filename,
                                PATH_MAX, LIBSSH2_SFTP_READLINK);
      if(rc == LIBSSH2_ERROR_EAGAIN) {
        break;
      }
      Curl_dyn_free(&sshp->readdir_link);

      /* append filename and extra output */
      result = Curl_dyn_addf(&sshp->readdir, " -> %s", sshp->readdir_filename);

      if(result) {
        Curl_safefree(sshp->readdir_filename);
        Curl_safefree(sshp->readdir_longentry);
        state(data, SSH_SFTP_CLOSE);
        sshc->actualcode = result;
        break;
      }

      state(data, SSH_SFTP_READDIR_BOTTOM);
      break;

    case SSH_SFTP_READDIR_BOTTOM:
      result = Curl_dyn_addn(&sshp->readdir, "\n", 1);
      if(!result)
        result = Curl_client_write(data, CLIENTWRITE_BODY,
                                   Curl_dyn_ptr(&sshp->readdir),
                                   Curl_dyn_len(&sshp->readdir));

      if(result) {
        Curl_dyn_free(&sshp->readdir);
        state(data, SSH_STOP);
      }
      else {
        Curl_dyn_reset(&sshp->readdir);
        state(data, SSH_SFTP_READDIR);
      }
      break;

    case SSH_SFTP_READDIR_DONE:
      if(libssh2_sftp_closedir(sshc->sftp_handle) ==
         LIBSSH2_ERROR_EAGAIN) {
        rc = LIBSSH2_ERROR_EAGAIN;
        break;
      }
      sshc->sftp_handle = NULL;
      Curl_safefree(sshp->readdir_filename);
      Curl_safefree(sshp->readdir_longentry);

      /* no data to transfer */
      Curl_xfer_setup_nop(data);
      state(data, SSH_STOP);
      break;

    case SSH_SFTP_DOWNLOAD_INIT:
      /*
       * Work on getting the specified file
       */
      sshc->sftp_handle =
        libssh2_sftp_open_ex(sshc->sftp_session, sshp->path,
                             curlx_uztoui(strlen(sshp->path)),
                             LIBSSH2_FXF_READ, (long)data->set.new_file_perms,
                             LIBSSH2_SFTP_OPENFILE);
      if(!sshc->sftp_handle) {
        if(libssh2_session_last_errno(sshc->ssh_session) ==
           LIBSSH2_ERROR_EAGAIN) {
          rc = LIBSSH2_ERROR_EAGAIN;
          break;
        }
        sftperr = libssh2_sftp_last_error(sshc->sftp_session);
        failf(data, "Could not open remote file for reading: %s",
              sftp_libssh2_strerror(sftperr));
        state(data, SSH_SFTP_CLOSE);
        result = sftp_libssh2_error_to_CURLE(sftperr);
        sshc->actualcode = result?result:CURLE_SSH;
        break;
      }
      state(data, SSH_SFTP_DOWNLOAD_STAT);
      break;

    case SSH_SFTP_DOWNLOAD_STAT:
    {
      LIBSSH2_SFTP_ATTRIBUTES attrs;

      rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path,
                                curlx_uztoui(strlen(sshp->path)),
                                LIBSSH2_SFTP_STAT, &attrs);
      if(rc == LIBSSH2_ERROR_EAGAIN) {
        break;
      }
      if(rc ||
         !(attrs.flags & LIBSSH2_SFTP_ATTR_SIZE) ||
         (attrs.filesize == 0)) {
        /*
         * libssh2_sftp_open() did not return an error, so maybe the server
         * just does not support stat()
         * OR the server does not return a file size with a stat()
         * OR file size is 0
         */
        data->req.size = -1;
        data->req.maxdownload = -1;
        Curl_pgrsSetDownloadSize(data, -1);
      }
      else {
        curl_off_t size = attrs.filesize;

        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, 10, &from);
          if(from_t == CURL_OFFT_FLOW)
            return CURLE_RANGE_ERROR;
          while(*ptr && (ISBLANK(*ptr) || (*ptr == '-')))
            ptr++;
          to_t = curlx_strtoofft(ptr, &ptr2, 10, &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,
                  (curl_off_t)attrs.filesize);
            return CURLE_BAD_DOWNLOAD_RESUME;
          }
          if(from > to) {
            from = to;
            size = 0;
          }
          else {
            if((to - from) == CURL_OFF_T_MAX)
              return CURLE_RANGE_ERROR;
            size = to - from + 1;
          }

          SFTP_SEEK(sshc->sftp_handle, from);
        }
        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 are supposed to download the last abs(from) bytes */
          if((curl_off_t)attrs.filesize < -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, (curl_off_t)attrs.filesize);
            return CURLE_BAD_DOWNLOAD_RESUME;
          }
          /* download from where? */
          data->state.resume_from += attrs.filesize;
        }
        else {
          if((curl_off_t)attrs.filesize < 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, (curl_off_t)attrs.filesize);
            return CURLE_BAD_DOWNLOAD_RESUME;
          }
        }
        /* Now store the number of bytes we are expected to download */
        data->req.size = attrs.filesize - data->state.resume_from;
        data->req.maxdownload = attrs.filesize - data->state.resume_from;
        Curl_pgrsSetDownloadSize(data,
                                 attrs.filesize - data->state.resume_from);
        SFTP_SEEK(sshc->sftp_handle, data->state.resume_from);
      }
    }

    /* Setup the actual download */
    if(data->req.size == 0) {
      /* no data to transfer */
      Curl_xfer_setup_nop(data);
      infof(data, "File already completely downloaded");
      state(data, SSH_STOP);
      break;
    }
    Curl_xfer_setup1(data, CURL_XFER_RECV, data->req.size, FALSE);

    /* not set by Curl_xfer_setup 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 libssh2 recv function will deal
       with both accordingly */
    data->state.select_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 {
      state(data, SSH_STOP);
    }
    break;

    case SSH_SFTP_CLOSE:
      if(sshc->sftp_handle) {
        rc = libssh2_sftp_close(sshc->sftp_handle);
        if(rc == LIBSSH2_ERROR_EAGAIN) {
          break;
        }
        if(rc < 0) {
          char *err_msg = NULL;
          (void)libssh2_session_last_error(sshc->ssh_session,
                                           &err_msg, NULL, 0);
          infof(data, "Failed to close libssh2 file: %d %s", rc, err_msg);
        }
        sshc->sftp_handle = NULL;
      }

      Curl_safefree(sshp->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_handle) {
        rc = libssh2_sftp_close(sshc->sftp_handle);
        if(rc == LIBSSH2_ERROR_EAGAIN) {
          break;
        }
        if(rc < 0) {
          char *err_msg = NULL;
          (void)libssh2_session_last_error(sshc->ssh_session, &err_msg,
                                           NULL, 0);
          infof(data, "Failed to close libssh2 file: %d %s", rc, err_msg);
        }
        sshc->sftp_handle = NULL;
      }
      if(sshc->sftp_session) {
        rc = libssh2_sftp_shutdown(sshc->sftp_session);
        if(rc == LIBSSH2_ERROR_EAGAIN) {
          break;
        }
        if(rc < 0) {
          infof(data, "Failed to stop libssh2 sftp subsystem");
        }
        sshc->sftp_session = NULL;
      }

      Curl_safefree(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, &sshp->path);
      if(result) {
        sshc->actualcode = result;
        state(data, SSH_STOP);
        break;
      }

      if(data->state.upload) {
        if(data->state.infilesize < 0) {
          failf(data, "SCP requires a known file size for upload");
          sshc->actualcode = CURLE_UPLOAD_FAILED;
          state(data, SSH_SCP_CHANNEL_FREE);
          break;
        }
        state(data, SSH_SCP_UPLOAD_INIT);
      }
      else {
        state(data, SSH_SCP_DOWNLOAD_INIT);
      }
      break;

    case SSH_SCP_UPLOAD_INIT:
      /*
       * libssh2 requires that the destination path is a full path that
       * includes the destination file and name OR ends in a "/" . If this is
       * not done the destination file will be named the same name as the last
       * directory in the path.
       */
      sshc->ssh_channel =
        SCP_SEND(sshc->ssh_session, sshp->path, data->set.new_file_perms,
                 data->state.infilesize);
      if(!sshc->ssh_channel) {
        int ssh_err;
        char *err_msg = NULL;

        if(libssh2_session_last_errno(sshc->ssh_session) ==
           LIBSSH2_ERROR_EAGAIN) {
          rc = LIBSSH2_ERROR_EAGAIN;
          break;
        }

        ssh_err = (int)(libssh2_session_last_error(sshc->ssh_session,
                                                   &err_msg, NULL, 0));
        failf(data, "%s", err_msg);
        state(data, SSH_SCP_CHANNEL_FREE);
        sshc->actualcode = libssh2_session_error_to_CURLE(ssh_err);
        /* Map generic errors to upload failed */
        if(sshc->actualcode == CURLE_SSH ||
           sshc->actualcode == CURLE_REMOTE_FILE_NOT_FOUND)
          sshc->actualcode = CURLE_UPLOAD_FAILED;
        break;
      }

      /* upload data */
      data->req.size = data->state.infilesize;
      Curl_pgrsSetUploadSize(data, data->state.infilesize);
      Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE);

      /* not set by Curl_xfer_setup to preserve keepon bits */
      conn->sockfd = conn->writesockfd;

      if(result) {
        state(data, SSH_SCP_CHANNEL_FREE);
        sshc->actualcode = result;
      }
      else {
        /* store this original bitmask setup to use later on if we cannot
           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 libssh2 scp send function will deal
           with both accordingly */
        data->state.select_bits = CURL_CSELECT_OUT;

        state(data, SSH_STOP);
      }
      break;

    case SSH_SCP_DOWNLOAD_INIT:
    {
      curl_off_t bytecount;

      /*
       * We must check the remote file; if it is a directory no values will
       * be set in sb
       */

      /*
       * If support for >2GB files exists, use it.
       */

      /* get a fresh new channel from the ssh layer */
#if LIBSSH2_VERSION_NUM < 0x010700
      struct stat sb;
      memset(&sb, 0, sizeof(struct stat));
      sshc->ssh_channel = libssh2_scp_recv(sshc->ssh_session,
                                           sshp->path, &sb);
#else
      libssh2_struct_stat sb;
      memset(&sb, 0, sizeof(libssh2_struct_stat));
      sshc->ssh_channel = libssh2_scp_recv2(sshc->ssh_session,
                                            sshp->path, &sb);
#endif

      if(!sshc->ssh_channel) {
        int ssh_err;
        char *err_msg = NULL;

        if(libssh2_session_last_errno(sshc->ssh_session) ==
           LIBSSH2_ERROR_EAGAIN) {
          rc = LIBSSH2_ERROR_EAGAIN;
          break;
        }


        ssh_err = (int)(libssh2_session_last_error(sshc->ssh_session,
                                                   &err_msg, NULL, 0));
        failf(data, "%s", err_msg);
        state(data, SSH_SCP_CHANNEL_FREE);
        sshc->actualcode = libssh2_session_error_to_CURLE(ssh_err);
        break;
      }

      /* download data */
      bytecount = (curl_off_t)sb.st_size;
      data->req.maxdownload = (curl_off_t)sb.st_size;
      Curl_xfer_setup1(data, CURL_XFER_RECV, bytecount, FALSE);

      /* not set by Curl_xfer_setup 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 libssh2 recv function will deal
         with both accordingly */
      data->state.select_bits = CURL_CSELECT_IN;

      if(result) {
        state(data, SSH_SCP_CHANNEL_FREE);
        sshc->actualcode = result;
      }
      else
        state(data, SSH_STOP);
    }
    break;

    case SSH_SCP_DONE:
      if(data->state.upload)
        state(data, SSH_SCP_SEND_EOF);
      else
        state(data, SSH_SCP_CHANNEL_FREE);
      break;

    case SSH_SCP_SEND_EOF:
      if(sshc->ssh_channel) {
        rc = libssh2_channel_send_eof(sshc->ssh_channel);
        if(rc == LIBSSH2_ERROR_EAGAIN) {
          break;
        }
        if(rc) {
          char *err_msg = NULL;
          (void)libssh2_session_last_error(sshc->ssh_session,
                                           &err_msg, NULL, 0);
          infof(data, "Failed to send libssh2 channel EOF: %d %s",
                rc, err_msg);
        }
      }
      state(data, SSH_SCP_WAIT_EOF);
      break;

    case SSH_SCP_WAIT_EOF:
      if(sshc->ssh_channel) {
        rc = libssh2_channel_wait_eof(sshc->ssh_channel);
        if(rc == LIBSSH2_ERROR_EAGAIN) {
          break;
        }
        if(rc) {
          char *err_msg = NULL;
          (void)libssh2_session_last_error(sshc->ssh_session,
                                           &err_msg, NULL, 0);
          infof(data, "Failed to get channel EOF: %d %s", rc, err_msg);
        }
      }
      state(data, SSH_SCP_WAIT_CLOSE);
      break;

    case SSH_SCP_WAIT_CLOSE:
      if(sshc->ssh_channel) {
        rc = libssh2_channel_wait_closed(sshc->ssh_channel);
        if(rc == LIBSSH2_ERROR_EAGAIN) {
          break;
        }
        if(rc) {
          char *err_msg = NULL;
          (void)libssh2_session_last_error(sshc->ssh_session,
                                           &err_msg, NULL, 0);
          infof(data, "Channel failed to close: %d %s", rc, err_msg);
        }
      }
      state(data, SSH_SCP_CHANNEL_FREE);
      break;

    case SSH_SCP_CHANNEL_FREE:
      if(sshc->ssh_channel) {
        rc = libssh2_channel_free(sshc->ssh_channel);
        if(rc == LIBSSH2_ERROR_EAGAIN) {
          break;
        }
        if(rc < 0) {
          char *err_msg = NULL;
          (void)libssh2_session_last_error(sshc->ssh_session,
                                           &err_msg, NULL, 0);
          infof(data, "Failed to free libssh2 scp subsystem: %d %s",
                rc, err_msg);
        }
        sshc->ssh_channel = NULL;
      }
      DEBUGF(infof(data, "SCP DONE phase complete"));
#if 0 /* PREV */
      state(data, SSH_SESSION_DISCONNECT);
#endif
      state(data, SSH_STOP);
      result = sshc->actualcode;
      break;

    case SSH_SESSION_DISCONNECT:
      /* during weird times when we have been prematurely aborted, the channel
         is still alive when we reach this state and we MUST kill the channel
         properly first */
      if(sshc->ssh_channel) {
        rc = libssh2_channel_free(sshc->ssh_channel);
        if(rc == LIBSSH2_ERROR_EAGAIN) {
          break;
        }
        if(rc < 0) {
          char *err_msg = NULL;
          (void)libssh2_session_last_error(sshc->ssh_session,
                                           &err_msg, NULL, 0);
          infof(data, "Failed to free libssh2 scp subsystem: %d %s",
                rc, err_msg);
        }
        sshc->ssh_channel = NULL;
      }

      if(sshc->ssh_session) {
        rc = libssh2_session_disconnect(sshc->ssh_session, "Shutdown");
        if(rc == LIBSSH2_ERROR_EAGAIN) {
          break;
        }
        if(rc < 0) {
          char *err_msg = NULL;
          (void)libssh2_session_last_error(sshc->ssh_session,
                                           &err_msg, NULL, 0);
          infof(data, "Failed to disconnect libssh2 session: %d %s",
                rc, err_msg);
        }
      }

      Curl_safefree(sshc->homedir);
      data->state.most_recent_ftp_entrypath = NULL;

      state(data, SSH_SESSION_FREE);
      break;

    case SSH_SESSION_FREE:
#ifdef HAVE_LIBSSH2_KNOWNHOST_API
      if(sshc->kh) {
        libssh2_knownhost_free(sshc->kh);
        sshc->kh = NULL;
      }
#endif

#ifdef HAVE_LIBSSH2_AGENT_API
      if(sshc->ssh_agent) {
        rc = libssh2_agent_disconnect(sshc->ssh_agent);
        if(rc == LIBSSH2_ERROR_EAGAIN) {
          break;
        }
        if(rc < 0) {
          char *err_msg = NULL;
          (void)libssh2_session_last_error(sshc->ssh_session,
                                           &err_msg, NULL, 0);
          infof(data, "Failed to disconnect from libssh2 agent: %d %s",
                rc, err_msg);
        }
        libssh2_agent_free(sshc->ssh_agent);
        sshc->ssh_agent = NULL;

        /* NB: there is no need to free identities, they are part of internal
           agent stuff */
        sshc->sshagent_identity = NULL;
        sshc->sshagent_prev_identity = NULL;
      }
#endif

      if(sshc->ssh_session) {
        rc = libssh2_session_free(sshc->ssh_session);
        if(rc == LIBSSH2_ERROR_EAGAIN) {
          break;
        }
        if(rc < 0) {
          char *err_msg = NULL;
          (void)libssh2_session_last_error(sshc->ssh_session,
                                           &err_msg, NULL, 0);
          infof(data, "Failed to free libssh2 session: %d %s", rc, err_msg);
        }
        sshc->ssh_session = NULL;
      }

      /* worst-case scenario cleanup */

      DEBUGASSERT(sshc->ssh_session == NULL);
      DEBUGASSERT(sshc->ssh_channel == NULL);
      DEBUGASSERT(sshc->sftp_session == NULL);
      DEBUGASSERT(sshc->sftp_handle == NULL);
#ifdef HAVE_LIBSSH2_KNOWNHOST_API
      DEBUGASSERT(sshc->kh == NULL);
#endif
#ifdef HAVE_LIBSSH2_AGENT_API
      DEBUGASSERT(sshc->ssh_agent == NULL);
#endif

      Curl_safefree(sshc->rsa_pub);
      Curl_safefree(sshc->rsa);
      Curl_safefree(sshc->quote_path1);
      Curl_safefree(sshc->quote_path2);
      Curl_safefree(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:
    default:
      /* internal error */
      sshc->nextstate = SSH_NO_STATE;
      state(data, SSH_STOP);
      break;
    }

  } while(!rc && (sshc->state != SSH_STOP));

  if(rc == LIBSSH2_ERROR_EAGAIN) {
    /* we would block, we need to wait for the socket to be ready (in the
       right direction too)! */
    *block = TRUE;
  }

  return result;
}