Skip to content
ssh.c 98.5 KiB
Newer Older
    }
    /* 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;
    case SSH_SFTP_QUOTE_INIT:

      result = ssh_getworkingpath(conn, sshc->homedir, &sftp_scp->path);
Yang Tse's avatar
Yang Tse committed
      if(result) {
        sshc->actualcode = result;
      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;

      if(curl_strequal("pwd", sshc->quote_item->data)) {
        /* output debug output if that is requested */
        char *tmp = aprintf("257 \"%s\" is current directory.\n",
                            sftp_scp->path);
        if(!tmp) {
          result = CURLE_OUT_OF_MEMORY;
          state(conn, SSH_SFTP_CLOSE);
          break;
        }
        if(data->set.verbose) {
          Curl_debug(data, CURLINFO_HEADER_OUT, (char *)"PWD\n", 4, conn);
          Curl_debug(data, CURLINFO_HEADER_IN, tmp, strlen(tmp), conn);
        }
        /* 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(conn, CLIENTWRITE_HEADER, tmp, strlen(tmp));
        free(tmp);
        state(conn, SSH_SFTP_NEXT_QUOTE);
        break;
      }
      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;
          break;
        }

        /*
         * also, every command takes at least one argument so we get that
         * first argument right now
         */
        result = get_pathname(&cp, &sshc->quote_path1);
Yang Tse's avatar
Yang Tse committed
        if(result) {
          if(result == CURLE_OUT_OF_MEMORY)
            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 */
          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);
            sshc->actualcode = result;
            break;
          }
          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 */
          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);
            sshc->actualcode = result;
            break;
          }
          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 */
          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);
            sshc->actualcode = result;
            break;
          }
          state(conn, SSH_SFTP_QUOTE_RENAME);
          break;
        }
        else if(curl_strnequal(sshc->quote_item->data, "rmdir ", 6)) {
          /* delete dir */
          state(conn, SSH_SFTP_QUOTE_RMDIR);
          break;
        }
        else if(curl_strnequal(sshc->quote_item->data, "rm ", 3)) {
          state(conn, SSH_SFTP_QUOTE_UNLINK);
          break;
        }

        failf(data, "Unknown SFTP command");
        Curl_safefree(sshc->quote_path1);
        sshc->quote_path1 = NULL;
        Curl_safefree(sshc->quote_path2);
        sshc->quote_path2 = NULL;
        state(conn, SSH_SFTP_CLOSE);
        sshc->actualcode = CURLE_QUOTE_ERROR;
    }
    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;
        }
        else {
          state(conn, SSH_SFTP_TRANS_INIT);
        }
      }
      break;

    case SSH_SFTP_QUOTE_STAT:
      if(!curl_strnequal(sshc->quote_item->data, "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,
                                  (unsigned int)strlen(sshc->quote_path2),
                                  LIBSSH2_SFTP_STAT,
                                  &sshc->quote_attrs);
        if(rc == LIBSSH2_ERROR_EAGAIN) {
          break;
        }
        else if(rc != 0) { /* get those attributes */
          err = (int)(libssh2_sftp_last_error(sshc->sftp_session));
          Curl_safefree(sshc->quote_path1);
          sshc->quote_path1 = NULL;
          Curl_safefree(sshc->quote_path2);
          sshc->quote_path2 = NULL;
          failf(data, "Attempt to get SFTP stats failed: %s",
                sftp_libssh2_strerror(err));

      /* Now set the new attributes... */
      if(curl_strnequal(sshc->quote_item->data, "chgrp", 5)) {
Yang Tse's avatar
 
Yang Tse committed
        sshc->quote_attrs.gid = strtoul(sshc->quote_path1, NULL, 10);
        sshc->quote_attrs.flags = LIBSSH2_SFTP_ATTR_UIDGID;
        if(sshc->quote_attrs.gid == 0 && !ISDIGIT(sshc->quote_path1[0])) {
          Curl_safefree(sshc->quote_path1);
          sshc->quote_path1 = NULL;
          Curl_safefree(sshc->quote_path2);
          sshc->quote_path2 = NULL;
          failf(data, "Syntax error: chgrp gid not a number");
      else if(curl_strnequal(sshc->quote_item->data, "chmod", 5)) {
Yang Tse's avatar
 
Yang Tse committed
        sshc->quote_attrs.permissions = strtoul(sshc->quote_path1, NULL, 8);
        sshc->quote_attrs.flags = LIBSSH2_SFTP_ATTR_PERMISSIONS;
        /* permissions are octal */
        if(sshc->quote_attrs.permissions == 0 &&
           !ISDIGIT(sshc->quote_path1[0])) {
          Curl_safefree(sshc->quote_path1);
          sshc->quote_path1 = NULL;
          Curl_safefree(sshc->quote_path2);
          sshc->quote_path2 = NULL;
          failf(data, "Syntax error: chmod permissions not a number");
          state(conn, SSH_SFTP_CLOSE);
          sshc->actualcode = CURLE_QUOTE_ERROR;
          break;
        }
      else if(curl_strnequal(sshc->quote_item->data, "chown", 5)) {
Yang Tse's avatar
 
Yang Tse committed
        sshc->quote_attrs.uid = strtoul(sshc->quote_path1, NULL, 10);
        sshc->quote_attrs.flags = LIBSSH2_SFTP_ATTR_UIDGID;
        if(sshc->quote_attrs.uid == 0 && !ISDIGIT(sshc->quote_path1[0])) {
          Curl_safefree(sshc->quote_path1);
          sshc->quote_path1 = NULL;
          Curl_safefree(sshc->quote_path2);
          sshc->quote_path2 = NULL;
          failf(data, "Syntax error: chown uid not a number");
          state(conn, SSH_SFTP_CLOSE);
          sshc->actualcode = CURLE_QUOTE_ERROR;
          break;
        }
      /* Now send the completed structure... */
      state(conn, SSH_SFTP_QUOTE_SETSTAT);
      rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshc->quote_path2,
                                (unsigned int)strlen(sshc->quote_path2),
                                LIBSSH2_SFTP_SETSTAT,
        err = (int)(libssh2_sftp_last_error(sshc->sftp_session));
        Curl_safefree(sshc->quote_path1);
        sshc->quote_path1 = NULL;
        Curl_safefree(sshc->quote_path2);
        sshc->quote_path2 = NULL;
        failf(data, "Attempt to set SFTP stats failed: %s",
              sftp_libssh2_strerror(err));
        state(conn, SSH_SFTP_CLOSE);
        sshc->actualcode = CURLE_QUOTE_ERROR;
        break;
      rc = libssh2_sftp_symlink_ex(sshc->sftp_session, sshc->quote_path1,
                                   (unsigned int)strlen(sshc->quote_path1),
                                   sshc->quote_path2,
                                   (unsigned int)strlen(sshc->quote_path2),
                                   LIBSSH2_SFTP_SYMLINK);
      if(rc == LIBSSH2_ERROR_EAGAIN) {
        break;
      }
      else if(rc != 0) {
        err = (int)(libssh2_sftp_last_error(sshc->sftp_session));
        Curl_safefree(sshc->quote_path1);
        sshc->quote_path1 = NULL;
        Curl_safefree(sshc->quote_path2);
        sshc->quote_path2 = NULL;
        failf(data, "symlink command failed: %s",
              sftp_libssh2_strerror(err));
        sshc->actualcode = CURLE_QUOTE_ERROR;
      state(conn, SSH_SFTP_NEXT_QUOTE);
      break;

    case SSH_SFTP_QUOTE_MKDIR:
      rc = libssh2_sftp_mkdir_ex(sshc->sftp_session, sshc->quote_path1,
                                 (unsigned int)strlen(sshc->quote_path1),
                                 0755);
      if(rc == LIBSSH2_ERROR_EAGAIN) {
        break;
      }
      else if(rc != 0) {
        err = (int)(libssh2_sftp_last_error(sshc->sftp_session));
        Curl_safefree(sshc->quote_path1);
        sshc->quote_path1 = NULL;
        failf(data, "mkdir command failed: %s", sftp_libssh2_strerror(err));
        sshc->actualcode = CURLE_QUOTE_ERROR;
      state(conn, SSH_SFTP_NEXT_QUOTE);
      break;

    case SSH_SFTP_QUOTE_RENAME:
      rc = libssh2_sftp_rename_ex(sshc->sftp_session, sshc->quote_path1,
                                  (unsigned int)strlen(sshc->quote_path1),
                                  sshc->quote_path2,
                                  (unsigned int)strlen(sshc->quote_path2),
                                  LIBSSH2_SFTP_RENAME_OVERWRITE |
                                  LIBSSH2_SFTP_RENAME_ATOMIC |
                                  LIBSSH2_SFTP_RENAME_NATIVE);
      if(rc == LIBSSH2_ERROR_EAGAIN) {
        break;
      }
      else if(rc != 0) {
        err = (int)(libssh2_sftp_last_error(sshc->sftp_session));
        Curl_safefree(sshc->quote_path1);
        sshc->quote_path1 = NULL;
        Curl_safefree(sshc->quote_path2);
        sshc->quote_path2 = NULL;
        failf(data, "rename command failed: %s", sftp_libssh2_strerror(err));
        sshc->actualcode = CURLE_QUOTE_ERROR;
      rc = libssh2_sftp_rmdir_ex(sshc->sftp_session, sshc->quote_path1,
                                 (unsigned int)strlen(sshc->quote_path1));
      if(rc == LIBSSH2_ERROR_EAGAIN) {
        break;
      }
      else if(rc != 0) {
        err = (int)(libssh2_sftp_last_error(sshc->sftp_session));
        Curl_safefree(sshc->quote_path1);
        sshc->quote_path1 = NULL;
        failf(data, "rmdir command failed: %s", sftp_libssh2_strerror(err));
        state(conn, SSH_SFTP_CLOSE);
        sshc->actualcode = CURLE_QUOTE_ERROR;
        break;
      }
      state(conn, SSH_SFTP_NEXT_QUOTE);
      rc = libssh2_sftp_unlink_ex(sshc->sftp_session, sshc->quote_path1,
                                  (unsigned int)strlen(sshc->quote_path1));
      if(rc == LIBSSH2_ERROR_EAGAIN) {
        break;
      }
      else if(rc != 0) {
        err = (int)(libssh2_sftp_last_error(sshc->sftp_session));
        Curl_safefree(sshc->quote_path1);
        sshc->quote_path1 = NULL;
        failf(data, "rm command failed: %s", sftp_libssh2_strerror(err));
        state(conn, SSH_SFTP_CLOSE);
        sshc->actualcode = CURLE_QUOTE_ERROR;
        break;
      }
      state(conn, SSH_SFTP_NEXT_QUOTE);
    case SSH_SFTP_TRANS_INIT:
      if(data->set.upload)
        state(conn, SSH_SFTP_UPLOAD_INIT);
      else {
        if(data->set.opt_no_body)
          state(conn, SSH_STOP);
        else if(sftp_scp->path[strlen(sftp_scp->path)-1] == '/')
          state(conn, SSH_SFTP_READDIR_INIT);
        else
          state(conn, SSH_SFTP_DOWNLOAD_INIT);
      }
    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 != 0) {
        LIBSSH2_SFTP_ATTRIBUTES attrs;
        if(data->state.resume_from < 0) {
          rc = libssh2_sftp_stat_ex(sshc->sftp_session, sftp_scp->path,
                                    (unsigned int)strlen(sftp_scp->path),
                                    LIBSSH2_SFTP_STAT, &attrs);
          if(rc == LIBSSH2_ERROR_EAGAIN) {
            break;
          }
          else if(rc) {
            data->state.resume_from = 0;
          }
          else {
            curl_off_t size = attrs.filesize;
            if(size < 0) {
              failf(data, "Bad file size (%" FORMAT_OFF_T ")", size);
              return CURLE_BAD_DOWNLOAD_RESUME;
            }
      if(data->set.ftp_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 behaviour) */
        flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC;
        libssh2_sftp_open_ex(sshc->sftp_session, sftp_scp->path,
                             (unsigned int)strlen(sftp_scp->path),
                             flags, 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;
        else {
          if(LIBSSH2_ERROR_SFTP_PROTOCOL == rc)
            /* only when there was an SFTP protocol error can we extract
               the sftp error! */
            err = (int)(libssh2_sftp_last_error(sshc->sftp_session));
          else
            err = -1; /* not an sftp error at all */
          if(sshc->secondCreateDirs) {
            state(conn, SSH_SFTP_CLOSE);
            sshc->actualcode = err>= LIBSSH2_FX_OK?
              sftp_libssh2_error_to_CURLE(err):CURLE_SSH;
            failf(data, "Creating the dir/file failed: %s",
                  sftp_libssh2_strerror(err));
            break;
          }
          else if(((err == LIBSSH2_FX_NO_SUCH_FILE) ||
                   (err == LIBSSH2_FX_FAILURE) ||
                   (err == LIBSSH2_FX_NO_SUCH_PATH)) &&
                  (data->set.ftp_create_missing_dirs &&
                   (strlen(sftp_scp->path) > 1))) {
            /* try to create the path remotely */
            sshc->secondCreateDirs = 1;
            state(conn, SSH_SFTP_CREATE_DIRS_INIT);
            break;
          }
          sshc->actualcode = err>= LIBSSH2_FX_OK?
            sftp_libssh2_error_to_CURLE(err):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;
            err=-1;
          }
          failf(data, "Upload failed: %s (%d/%d)",
                err>= LIBSSH2_FX_OK?sftp_libssh2_strerror(err):"ssh error",
                err, rc);
      /* If we have 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(conn->seek_func) {
          seekerr = conn->seek_func(conn->seek_client, data->state.resume_from,
                                    SEEK_SET);
        }
          if(seekerr != CURL_SEEKFUNC_CANTSEEK) {
            failf(data, "Could not seek stream");
            return CURLE_FTP_COULDNT_USE_REST;
          }
          /* seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */
          else {
            curl_off_t passed=0;
            do {
              size_t readthisamountnow =
                (data->state.resume_from - passed > CURL_OFF_T_C(BUFSIZE)) ?
                BUFSIZE : curlx_sotouz(data->state.resume_from - passed);
              size_t actuallyread =
                conn->fread_func(data->state.buffer, 1, readthisamountnow,
                                 conn->fread_in);
              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);
          }
        if(data->set.infilesize > 0) {
          data->set.infilesize -= data->state.resume_from;
          data->req.size = data->set.infilesize;
          Curl_pgrsSetUploadSize(data, data->set.infilesize);
        SFTP_SEEK(sshc->sftp_handle, data->state.resume_from);
      }
      if(data->set.infilesize > 0) {
        data->req.size = data->set.infilesize;
        Curl_pgrsSetUploadSize(data, data->set.infilesize);
      }
      Curl_setup_transfer(conn, -1, -1, FALSE, NULL, FIRSTSOCKET, NULL);
      /* not set by Curl_setup_transfer to preserve keepon bits */
      conn->sockfd = conn->writesockfd;
      if(result) {
        state(conn, SSH_SFTP_CLOSE);
        sshc->actualcode = result;
      }
      else {
        /* store this original bitmask setup to use later on if we can't
           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 */
        conn->cselect_bits = CURL_CSELECT_OUT;

        /* since we don't 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, 1);

    case SSH_SFTP_CREATE_DIRS_INIT:
      if(strlen(sftp_scp->path) > 1) {
        sshc->slash_pos = sftp_scp->path + 1; /* ignore the leading '/' */
        state(conn, SSH_SFTP_CREATE_DIRS);
    case SSH_SFTP_CREATE_DIRS:
      if((sshc->slash_pos = strchr(sshc->slash_pos, '/')) != NULL) {
        *sshc->slash_pos = 0;

        infof(data, "Creating directory '%s'\n", sftp_scp->path);
        state(conn, SSH_SFTP_CREATE_DIRS_MKDIR);
    case SSH_SFTP_CREATE_DIRS_MKDIR:
      /* 'mode' - parameter is preliminary - default to 0644 */
      rc = libssh2_sftp_mkdir_ex(sshc->sftp_session, sftp_scp->path,
                                 (unsigned int)strlen(sftp_scp->path),
                                 data->set.new_directory_perms);
      if(rc == LIBSSH2_ERROR_EAGAIN) {
        break;
      }
      *sshc->slash_pos = '/';
      ++sshc->slash_pos;
      if(rc == -1) {
        unsigned int sftp_err = 0;
        /*
         * Abort if failure wasn't that the dir already exists or the
         * permission was denied (creation might succeed further down the
         * path) - retry on unspecific FAILURE also
         */
        sftp_err = (unsigned int)(libssh2_sftp_last_error(sshc->sftp_session));
        if((sftp_err != LIBSSH2_FX_FILE_ALREADY_EXISTS) &&
           (sftp_err != LIBSSH2_FX_FAILURE) &&
           (sftp_err != LIBSSH2_FX_PERMISSION_DENIED)) {
          result = sftp_libssh2_error_to_CURLE(sftp_err);
          sshc->actualcode = result?result:CURLE_SSH;
    case SSH_SFTP_READDIR_INIT:
      /*
       * 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,
                                               sftp_scp->path,
                                               (unsigned int)
                                               strlen(sftp_scp->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;
          err = (int)(libssh2_sftp_last_error(sshc->sftp_session));
          failf(data, "Could not open directory for reading: %s",
                sftp_libssh2_strerror(err));
          result = sftp_libssh2_error_to_CURLE(err);
          sshc->actualcode = result?result:CURLE_SSH;
      }
      if((sshc->readdir_filename = malloc(PATH_MAX+1)) == NULL) {
        state(conn, SSH_SFTP_CLOSE);
        sshc->actualcode = CURLE_OUT_OF_MEMORY;
        break;
      }
      if((sshc->readdir_longentry = malloc(PATH_MAX+1)) == NULL) {
        Curl_safefree(sshc->readdir_filename);
        sshc->readdir_filename = NULL;
        state(conn, SSH_SFTP_CLOSE);
        sshc->actualcode = CURLE_OUT_OF_MEMORY;
        break;
      }
      state(conn, SSH_SFTP_READDIR);
      break;

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

        if(data->set.ftp_list_only) {
          char *tmpLine;

          tmpLine = aprintf("%s\n", sshc->readdir_filename);
          if(tmpLine == NULL) {
            state(conn, SSH_SFTP_CLOSE);
            sshc->actualcode = CURLE_OUT_OF_MEMORY;
            break;
          }
          result = Curl_client_write(conn, CLIENTWRITE_BODY,
                                     tmpLine, sshc->readdir_len+1);
          Curl_safefree(tmpLine);
          if(result) {
            state(conn, SSH_STOP);
            break;
          }
          /* since this counts what we send to the client, we include the
             newline in this counter */
          data->req.bytecount += sshc->readdir_len+1;

          /* output debug output if that is requested */
          if(data->set.verbose) {
            Curl_debug(data, CURLINFO_DATA_OUT, sshc->readdir_filename,
                       sshc->readdir_len, conn);
          }
        }
        else {
          sshc->readdir_currLen = (int)strlen(sshc->readdir_longentry);
          sshc->readdir_totalLen = 80 + sshc->readdir_currLen;
          sshc->readdir_line = calloc(sshc->readdir_totalLen, 1);
          if(!sshc->readdir_line) {
            Curl_safefree(sshc->readdir_filename);
            sshc->readdir_filename = NULL;
            Curl_safefree(sshc->readdir_longentry);
            sshc->readdir_longentry = NULL;
            state(conn, SSH_SFTP_CLOSE);
            sshc->actualcode = CURLE_OUT_OF_MEMORY;
          memcpy(sshc->readdir_line, sshc->readdir_longentry,
                 sshc->readdir_currLen);
          if((sshc->readdir_attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) &&
             ((sshc->readdir_attrs.permissions & LIBSSH2_SFTP_S_IFMT) ==
              LIBSSH2_SFTP_S_IFLNK)) {
            sshc->readdir_linkPath = malloc(PATH_MAX + 1);
            if(sshc->readdir_linkPath == NULL) {
              Curl_safefree(sshc->readdir_filename);
              sshc->readdir_filename = NULL;
              Curl_safefree(sshc->readdir_longentry);
              sshc->readdir_longentry = NULL;
              state(conn, SSH_SFTP_CLOSE);
              sshc->actualcode = CURLE_OUT_OF_MEMORY;
              break;
            }

            snprintf(sshc->readdir_linkPath, PATH_MAX, "%s%s", sftp_scp->path,
                     sshc->readdir_filename);
            state(conn, SSH_SFTP_READDIR_LINK);
            break;
          }
          state(conn, SSH_SFTP_READDIR_BOTTOM);
      }
      else if(sshc->readdir_len == 0) {
        Curl_safefree(sshc->readdir_filename);
        sshc->readdir_filename = NULL;
        Curl_safefree(sshc->readdir_longentry);
        sshc->readdir_longentry = NULL;
        state(conn, SSH_SFTP_READDIR_DONE);
        break;
      }
      else if(sshc->readdir_len <= 0) {
        err = (int)(libssh2_sftp_last_error(sshc->sftp_session));
        result = sftp_libssh2_error_to_CURLE(err);
        sshc->actualcode = result?result:CURLE_SSH;
        failf(data, "Could not open remote file for reading: %s :: %d",
              sftp_libssh2_strerror(err),
              libssh2_session_last_errno(sshc->ssh_session));
        Curl_safefree(sshc->readdir_filename);
        sshc->readdir_filename = NULL;
        Curl_safefree(sshc->readdir_longentry);
        sshc->readdir_longentry = NULL;
        state(conn, SSH_SFTP_CLOSE);
      sshc->readdir_len =
        libssh2_sftp_symlink_ex(sshc->sftp_session,
                                sshc->readdir_linkPath,
                                (unsigned int) strlen(sshc->readdir_linkPath),
                                sshc->readdir_filename,
                                PATH_MAX, LIBSSH2_SFTP_READLINK);
      if(sshc->readdir_len == LIBSSH2_ERROR_EAGAIN) {
        rc = LIBSSH2_ERROR_EAGAIN;
        break;
      }
      Curl_safefree(sshc->readdir_linkPath);
      sshc->readdir_linkPath = NULL;
      sshc->readdir_line = realloc(sshc->readdir_line,
                                   sshc->readdir_totalLen + 4 +
                                   sshc->readdir_len);
      if(!sshc->readdir_line) {
        Curl_safefree(sshc->readdir_filename);
        sshc->readdir_filename = NULL;
        Curl_safefree(sshc->readdir_longentry);
        sshc->readdir_longentry = NULL;
        state(conn, SSH_SFTP_CLOSE);
        sshc->actualcode = CURLE_OUT_OF_MEMORY;
        break;
      }
      sshc->readdir_currLen += snprintf(sshc->readdir_line +
                                        sshc->readdir_currLen,
                                        sshc->readdir_totalLen -
                                        sshc->readdir_currLen,
                                        " -> %s",
                                        sshc->readdir_filename);
      state(conn, SSH_SFTP_READDIR_BOTTOM);
      break;
    case SSH_SFTP_READDIR_BOTTOM:
      sshc->readdir_currLen += snprintf(sshc->readdir_line +
                                        sshc->readdir_currLen,
                                        sshc->readdir_totalLen -
                                        sshc->readdir_currLen, "\n");
      result = Curl_client_write(conn, CLIENTWRITE_BODY,
                                 sshc->readdir_line,
                                 sshc->readdir_currLen);
        /* output debug output if that is requested */
        if(data->set.verbose) {
          Curl_debug(data, CURLINFO_DATA_OUT, sshc->readdir_line,
                     sshc->readdir_currLen, conn);
        }
        data->req.bytecount += sshc->readdir_currLen;
      Curl_safefree(sshc->readdir_line);
      sshc->readdir_line = NULL;
      if(result) {
        state(conn, SSH_STOP);
      }
      else
        state(conn, SSH_SFTP_READDIR);
    case SSH_SFTP_READDIR_DONE:
      if(libssh2_sftp_closedir(sshc->sftp_handle) ==
      sshc->sftp_handle = NULL;
      Curl_safefree(sshc->readdir_filename);
      sshc->readdir_filename = NULL;
      Curl_safefree(sshc->readdir_longentry);
      sshc->readdir_longentry = NULL;
      Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
        libssh2_sftp_open_ex(sshc->sftp_session, sftp_scp->path,
                             (unsigned int)strlen(sftp_scp->path),
                             LIBSSH2_FXF_READ, 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;
          err = (int)(libssh2_sftp_last_error(sshc->sftp_session));
          failf(data, "Could not open remote file for reading: %s",
                sftp_libssh2_strerror(err));
          state(conn, SSH_SFTP_CLOSE);
          result = sftp_libssh2_error_to_CURLE(err);
          sshc->actualcode = result?result:CURLE_SSH;
          break;
    case SSH_SFTP_DOWNLOAD_STAT:
    {
      LIBSSH2_SFTP_ATTRIBUTES attrs;

      rc = libssh2_sftp_stat_ex(sshc->sftp_session, sftp_scp->path,
                                (unsigned int)strlen(sftp_scp->path),
                                LIBSSH2_SFTP_STAT, &attrs);
      if(rc == LIBSSH2_ERROR_EAGAIN) {
        break;
      }
      else if(rc) {
        /*
         * libssh2_sftp_open() didn't return an error, so maybe the server
         * just doesn't support stat()
         */
        data->req.size = -1;
        data->req.maxdownload = -1;
        curl_off_t size = attrs.filesize;
        if(size < 0) {
          failf(data, "Bad file size (%" FORMAT_OFF_T ")", size);
          return CURLE_BAD_DOWNLOAD_RESUME;
        }
        if(conn->data->state.use_range) {
          curl_off_t from, to;
          char *ptr;
          char *ptr2;

          from=curlx_strtoofft(conn->data->state.range, &ptr, 0);
Yang Tse's avatar
Yang Tse committed
          while(*ptr && (ISSPACE(*ptr) || (*ptr=='-')))
            ptr++;
          to=curlx_strtoofft(ptr, &ptr2, 0);
          if((ptr == ptr2) /* no "to" value given */
             || (to >= size)) {
            to = size - 1;
          }
          if(from < 0) {
            /* from is relative to end of file */
            from += size;
          }
          if(from >= size) {
            failf(data, "Offset (%"
                  FORMAT_OFF_T ") was beyond file size (%" FORMAT_OFF_T ")",
                  from, attrs.filesize);
            return CURLE_BAD_DOWNLOAD_RESUME;
          }
          if(from > to) {
            from = to;
            size = 0;
          }
          else {
            size = to - from + 1;
          }

          SFTP_SEEK(conn->proto.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're supposed to download the last abs(from) bytes */
          if((curl_off_t)attrs.filesize < -data->state.resume_from) {
            failf(data, "Offset (%"
                  FORMAT_OFF_T ") was beyond file size (%" FORMAT_OFF_T ")",
                  data->state.resume_from, attrs.filesize);
            return CURLE_BAD_DOWNLOAD_RESUME;
          }
          /* download from where? */
          data->state.resume_from += attrs.filesize;