Skip to content
Snippets Groups Projects
ssh.c 75 KiB
Newer Older
  • Learn to ignore specific revisions
  •       ssh->ssh_session = NULL;
          return CURLE_FAILED_INIT;
        }
    
        /*
         * Get the "home" directory
         */
    
        i = libssh2_sftp_realpath(ssh->sftp_session, ".", tempHome, PATH_MAX-1);
        if (i > 0) {
    
          /* It seems that this string is not always NULL terminated */
          tempHome[i] = '\0';
          ssh->homedir = (char *)strdup(tempHome);
          if (!ssh->homedir) {
            libssh2_sftp_shutdown(ssh->sftp_session);
            ssh->sftp_session = NULL;
            libssh2_session_free(ssh->ssh_session);
            ssh->ssh_session = NULL;
            return CURLE_OUT_OF_MEMORY;
          }
        }
        else {
          /* Return the error type */
          i = libssh2_sftp_last_error(ssh->sftp_session);
          DEBUGF(infof(data, "error = %d\n", i));
        }
      }
    
    
      working_path = curl_easy_unescape(data, data->reqdata.path, 0,
                                        &working_path_len);
      if (!working_path)
        return CURLE_OUT_OF_MEMORY;
    
    Dan Fandrich's avatar
    Dan Fandrich committed
      /* Check for /~/ , indicating relative to the user's home directory */
    
      if (conn->protocol == PROT_SCP) {
        real_path = (char *)malloc(working_path_len+1);
        if (real_path == NULL) {
          libssh2_session_free(ssh->ssh_session);
          ssh->ssh_session = NULL;
    
          Curl_safefree(working_path);
    
          return CURLE_OUT_OF_MEMORY;
        }
        if (working_path[1] == '~')
          /* It is referenced to the home directory, so strip the leading '/' */
          memcpy(real_path, working_path+1, 1 + working_path_len-1);
        else
          memcpy(real_path, working_path, 1 + working_path_len);
      }
      else if (conn->protocol == PROT_SFTP) {
        if (working_path[1] == '~') {
          real_path = (char *)malloc(strlen(ssh->homedir) +
                                     working_path_len + 1);
          if (real_path == NULL) {
            libssh2_sftp_shutdown(ssh->sftp_session);
            ssh->sftp_session = NULL;
            libssh2_session_free(ssh->ssh_session);
            ssh->ssh_session = NULL;
    
            Curl_safefree(ssh->homedir);
            ssh->homedir = NULL;
    
            Curl_safefree(working_path);
            return CURLE_OUT_OF_MEMORY;
          }
          /* It is referenced to the home directory, so strip the leading '/' */
          memcpy(real_path, ssh->homedir, strlen(ssh->homedir));
          real_path[strlen(ssh->homedir)] = '/';
          real_path[strlen(ssh->homedir)+1] = '\0';
          if (working_path_len > 3) {
            memcpy(real_path+strlen(ssh->homedir)+1, working_path + 3,
                   1 + working_path_len -3);
          }
        }
        else {
          real_path = (char *)malloc(working_path_len+1);
          if (real_path == NULL) {
    
            libssh2_sftp_shutdown(ssh->sftp_session);
            ssh->sftp_session = NULL;
    
            libssh2_session_free(ssh->ssh_session);
            ssh->ssh_session = NULL;
    
            Curl_safefree(ssh->homedir);
            ssh->homedir = NULL;
    
            Curl_safefree(working_path);
            return CURLE_OUT_OF_MEMORY;
          }
          memcpy(real_path, working_path, 1+working_path_len);
        }
      }
    
      else {
        libssh2_session_free(ssh->ssh_session);
        ssh->ssh_session = NULL;
        Curl_safefree(working_path);
    
    
      Curl_safefree(working_path);
      ssh->path = real_path;
    
    
      *done = TRUE;
      return CURLE_OK;
    
    #endif /* !(LIBSSH2_APINO >= 200706012030) */
    
    }
    
    CURLcode Curl_scp_do(struct connectdata *conn, bool *done)
    {
      struct stat sb;
    
      struct SSHPROTO *scp = conn->data->reqdata.proto.ssh;
    
      CURLcode res = CURLE_OK;
    
      *done = TRUE; /* unconditionally */
    
      if (conn->data->set.upload) {
    
        if(conn->data->set.infilesize < 0) {
          failf(conn->data, "SCP requries a known file size for upload");
          return CURLE_UPLOAD_FAILED;
        }
    
         * 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 (LIBSSH2_APINO >= 200706012030)
        do {
          scp->ssh_channel = libssh2_scp_send_ex(scp->ssh_session, scp->path,
                                                 LIBSSH2_SFTP_S_IRUSR|
                                                 LIBSSH2_SFTP_S_IWUSR|
                                                 LIBSSH2_SFTP_S_IRGRP|
                                                 LIBSSH2_SFTP_S_IROTH,
                                                 conn->data->set.infilesize, 0, 0);
          if (!scp->ssh_channel &&
              (libssh2_session_last_errno(scp->ssh_session) !=
               LIBSSH2_ERROR_EAGAIN)) {
            return CURLE_FAILED_INIT;
          }
        } while (!scp->ssh_channel);
    #else /* !(LIBSSH2_APINO >= 200706012030) */
    
        scp->ssh_channel = libssh2_scp_send_ex(scp->ssh_session, scp->path,
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
                                               LIBSSH2_SFTP_S_IRUSR|
                                               LIBSSH2_SFTP_S_IWUSR|
                                               LIBSSH2_SFTP_S_IRGRP|
                                               LIBSSH2_SFTP_S_IROTH,
                                               conn->data->set.infilesize, 0, 0);
    
          return CURLE_FAILED_INIT;
    
    #endif /* !(LIBSSH2_APINO >= 200706012030) */
    
    
        /* upload data */
        res = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, FIRSTSOCKET, NULL);
    
    Dan Fandrich's avatar
    Dan Fandrich committed
         * We must check the remote file; if it is a directory no values will
    
        memset(&sb, 0, sizeof(struct stat));
    
    #if (LIBSSH2_APINO >= 200706012030)
        do {
          scp->ssh_channel = libssh2_scp_recv(scp->ssh_session, scp->path, &sb);
          if (!scp->ssh_channel &&
              (libssh2_session_last_errno(scp->ssh_session) !=
               LIBSSH2_ERROR_EAGAIN)) {
            if ((sb.st_mode == 0) && (sb.st_atime == 0) && (sb.st_mtime == 0) &&
                (sb.st_size == 0)) {
              /* Since sb is still empty, it is likely the file was not found */
              return CURLE_REMOTE_FILE_NOT_FOUND;
            }
            return libssh2_session_error_to_CURLE(
              libssh2_session_last_error(scp->ssh_session, NULL, NULL, 0));
          }
        } while (!scp->ssh_channel);
    #else /* !(LIBSSH2_APINO >= 200706012030) */
    
        scp->ssh_channel = libssh2_scp_recv(scp->ssh_session, scp->path, &sb);
        if (!scp->ssh_channel) {
    
          if ((sb.st_mode == 0) && (sb.st_atime == 0) && (sb.st_mtime == 0) &&
              (sb.st_size == 0)) {
            /* Since sb is still empty, it is likely the file was not found */
            return CURLE_REMOTE_FILE_NOT_FOUND;
          }
    
          return libssh2_session_error_to_CURLE(
            libssh2_session_last_error(scp->ssh_session, NULL, NULL, 0));
    
    #endif /* !(LIBSSH2_APINO >= 200706012030) */
    
        /* download data */
        bytecount = (curl_off_t) sb.st_size;
    
        conn->data->reqdata.maxdownload =  (curl_off_t) sb.st_size;
    
        res = Curl_setup_transfer(conn, FIRSTSOCKET,
                                  bytecount, FALSE, NULL, -1, NULL);
    
    CURLcode Curl_scp_done(struct connectdata *conn, CURLcode status,
                           bool premature)
    
      struct SSHPROTO *scp = conn->data->reqdata.proto.ssh;
    
      Curl_safefree(scp->path);
      scp->path = NULL;
    
    #if (LIBSSH2_APINO >= 200706012030)
        if (conn->data->set.upload) {
          while ((rc = libssh2_channel_send_eof(scp->ssh_channel)) ==
                 LIBSSH2_ERROR_EAGAIN);
          if (rc) {
            infof(conn->data, "Failed to send libssh2 channel EOF\n");
          }
          while ((rc = libssh2_channel_wait_eof(scp->ssh_channel)) ==
                 LIBSSH2_ERROR_EAGAIN);
          if (rc) {
            infof(conn->data, "Failed to get channel EOF\n");
          }
          while ((rc = libssh2_channel_wait_closed(scp->ssh_channel)) ==
                 LIBSSH2_ERROR_EAGAIN);
          if (rc) {
            infof(conn->data, "Channel failed to close\n");
          }
        }
    #else /* !(LIBSSH2_APINO >= 200706012030) */
    
        if (conn->data->set.upload &&
            libssh2_channel_send_eof(scp->ssh_channel) < 0) {
    
          infof(conn->data, "Failed to send libssh2 channel EOF\n");
        }
    
        if (libssh2_channel_close(scp->ssh_channel) < 0) {
          infof(conn->data, "Failed to stop libssh2 channel subsystem\n");
    
    #endif /* !(LIBSSH2_APINO >= 200706012030) */
    
        libssh2_channel_free(scp->ssh_channel);
    
    #if (LIBSSH2_APINO >= 200706012030)
    
        while (libssh2_session_disconnect(scp->ssh_session, "Shutdown") ==
    
               LIBSSH2_ERROR_EAGAIN);
    #else /* !(LIBSSH2_APINO >= 200706012030) */
    
        libssh2_session_disconnect(scp->ssh_session, "Shutdown");
    
    #endif /* !(LIBSSH2_APINO >= 200706012030) */
    
        libssh2_session_free(scp->ssh_session);
        scp->ssh_session = NULL;
    
      free(conn->data->reqdata.proto.ssh);
      conn->data->reqdata.proto.ssh = NULL;
    
      Curl_pgrsDone(conn);
    
      (void)status; /* unused */
    
      (void) rc;    /* possiby unused */
    
    
      return CURLE_OK;
    }
    
    /* return number of received (decrypted) bytes */
    
    ssize_t Curl_scp_send(struct connectdata *conn, int sockindex,
                          void *mem, size_t len)
    
      /* libssh2_channel_write() returns int
       *
       * NOTE: we should not store nor rely on connection-related data to be
       * in the SessionHandle struct
       */
    
    #if defined(LIBSSH2CHANNEL_EAGAIN) && (LIBSSH2_APINO < 200706012030)
    
      nwrite = (ssize_t)
        libssh2_channel_writenb(conn->data->reqdata.proto.ssh->ssh_channel,
                                mem, len);
    #else
    
      nwrite = (ssize_t)
        libssh2_channel_write(conn->data->reqdata.proto.ssh->ssh_channel,
                              mem, len);
    
    #if (LIBSSH2_APINO >= 200706012030)
      if (nwrite == LIBSSH2_ERROR_EAGAIN) {
        return 0;
      }
    #endif
    
      (void)sockindex;
      return nwrite;
    }
    
    /*
     * If the read would block (EWOULDBLOCK) we return -1. Otherwise we return
     * a regular CURLcode value.
     */
    
    ssize_t Curl_scp_recv(struct connectdata *conn, int sockindex,
    
      (void)sockindex; /* we only support SCP on the fixed known primary socket */
    
      /* libssh2_channel_read() returns int
       *
       * NOTE: we should not store nor rely on connection-related data to be
       * in the SessionHandle struct
       */
    
    
    #if defined(LIBSSH2CHANNEL_EAGAIN) && (LIBSSH2_APINO < 200706012030)
    
      /* we prefer the non-blocking API but that didn't exist previously */
      nread = (ssize_t)
        libssh2_channel_readnb(conn->data->reqdata.proto.ssh->ssh_channel,
                               mem, len);
    #else
    
      nread = (ssize_t)
        libssh2_channel_read(conn->data->reqdata.proto.ssh->ssh_channel,
                             mem, len);
    
      return nread;
    }
    
    /*
     * =============== SFTP ===============
     */
    
    CURLcode Curl_sftp_do(struct connectdata *conn, bool *done)
    {
      LIBSSH2_SFTP_ATTRIBUTES attrs;
      struct SSHPROTO *sftp = conn->data->reqdata.proto.ssh;
      CURLcode res = CURLE_OK;
      struct SessionHandle *data = conn->data;
      curl_off_t bytecount = 0;
      char *buf = data->state.buffer;
    
      unsigned long err = 0;
    
      /* Send any quote commands */
      if(conn->data->set.quote) {
        infof(conn->data, "Sending quote commands\n");
        res = sftp_sendquote(conn, conn->data->set.quote);
        if (res != CURLE_OK)
          return res;
      }
    
    
      if (data->set.upload) {
        /*
         * 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 (LIBSSH2_APINO >= 200706012030)
        do {
          sftp->sftp_handle =
            libssh2_sftp_open(sftp->sftp_session, sftp->path,
                            LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC,
                            LIBSSH2_SFTP_S_IRUSR|LIBSSH2_SFTP_S_IWUSR|
                            LIBSSH2_SFTP_S_IRGRP|LIBSSH2_SFTP_S_IROTH);
          if (!sftp->sftp_handle &&
              (libssh2_session_last_errno(sftp->ssh_session) !=
               LIBSSH2_ERROR_EAGAIN)) {
            err = libssh2_sftp_last_error(sftp->sftp_session);
    
            if (((err == LIBSSH2_FX_NO_SUCH_FILE) ||
                 (err == LIBSSH2_FX_FAILURE) ||
                 (err == LIBSSH2_FX_NO_SUCH_PATH)) &&
                (conn->data->set.ftp_create_missing_dirs &&
                 (strlen(sftp->path) > 1))) {
              /* try to create the path remotely */
              res = sftp_create_dirs(conn);
              if (res == 0) {
                do {
                  sftp->sftp_handle = libssh2_sftp_open(sftp->sftp_session,
                              sftp->path,
                              LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC,
                              LIBSSH2_SFTP_S_IRUSR|LIBSSH2_SFTP_S_IWUSR|
                              LIBSSH2_SFTP_S_IRGRP|LIBSSH2_SFTP_S_IROTH);
                  if (!sftp->sftp_handle &&
                      (libssh2_session_last_errno(sftp->ssh_session) !=
                       LIBSSH2_ERROR_EAGAIN)) {
                    err = libssh2_sftp_last_error(sftp->sftp_session);
                    failf(conn->data, "Could not open remote file for writing: %s",
                          sftp_libssh2_strerror(err));
                    return sftp_libssh2_error_to_CURLE(err);
                  }
                } while (!sftp->sftp_handle);
              }
            }
            if (!sftp->sftp_handle) {
              err = libssh2_sftp_last_error(sftp->sftp_session);
              failf(conn->data, "Could not open remote file for writing: %s",
                    sftp_libssh2_strerror(err));
              return sftp_libssh2_error_to_CURLE(err);
            }
    
          }
        } while (!sftp->sftp_handle);
    #else /* !(LIBSSH2_APINO >= 200706012030) */
    
        sftp->sftp_handle =
          libssh2_sftp_open(sftp->sftp_session, sftp->path,
    
                            LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC,
    
                            LIBSSH2_SFTP_S_IRUSR|LIBSSH2_SFTP_S_IWUSR|
                            LIBSSH2_SFTP_S_IRGRP|LIBSSH2_SFTP_S_IROTH);
    
        if (!sftp->sftp_handle) {
          err = libssh2_sftp_last_error(sftp->sftp_session);
    
          if (((err == LIBSSH2_FX_NO_SUCH_FILE) ||
              (err == LIBSSH2_FX_FAILURE) ||
              (err == LIBSSH2_FX_NO_SUCH_PATH)) &&
              (conn->data->set.ftp_create_missing_dirs &&
               (strlen(sftp->path) > 1))) {
            /* try to create the path remotely */
            res = sftp_create_dirs(conn);
            if (res == 0) {
              sftp->sftp_handle = libssh2_sftp_open(sftp->sftp_session, sftp->path,
                        LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC,
                        LIBSSH2_SFTP_S_IRUSR|LIBSSH2_SFTP_S_IWUSR|
                        LIBSSH2_SFTP_S_IRGRP|LIBSSH2_SFTP_S_IROTH);
            }
          }
          if (!sftp->sftp_handle) {
            err = libssh2_sftp_last_error(sftp->sftp_session);
            failf(conn->data, "Could not open remote file for writing: %s",
                  sftp_libssh2_strerror(err));
            return sftp_libssh2_error_to_CURLE(err);
          }
    
    #endif /* !(LIBSSH2_APINO >= 200706012030) */
    
    
        /* upload data */
        res = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, FIRSTSOCKET, NULL);
      }
      else {
        if (sftp->path[strlen(sftp->path)-1] == '/') {
          /*
           * This is a directory that we are trying to get, so produce a
           * directory listing
           *
           * **BLOCKING behaviour** This should be made into a state machine and
           * get a separate function called from Curl_sftp_recv() when there is
           * data to read from the network, instead of "hanging" here.
           */
          char filename[PATH_MAX+1];
          int len, totalLen, currLen;
          char *line;
    
    
    #if (LIBSSH2_APINO >= 200706012030)
          do {
            sftp->sftp_handle =
              libssh2_sftp_opendir(sftp->sftp_session, sftp->path);
            if (!sftp->sftp_handle &&
                (libssh2_session_last_errno(sftp->ssh_session) !=
                 LIBSSH2_ERROR_EAGAIN)) {
              err = libssh2_sftp_last_error(sftp->sftp_session);
              failf(conn->data, "Could not open directory for reading: %s",
                    sftp_libssh2_strerror(err));
              return sftp_libssh2_error_to_CURLE(err);
            }
          } while (!sftp->sftp_handle);
    #else /* !(LIBSSH2_APINO >= 200706012030) */
    
          sftp->sftp_handle =
            libssh2_sftp_opendir(sftp->sftp_session, sftp->path);
    
          if (!sftp->sftp_handle) {
            err = libssh2_sftp_last_error(sftp->sftp_session);
            failf(conn->data, "Could not open directory for reading: %s",
                sftp_libssh2_strerror(err));
            return sftp_libssh2_error_to_CURLE(err);
          }
    
    #endif /* !(LIBSSH2_APINO >= 200706012030) */
    
          do {
    #if (LIBSSH2_APINO >= 200706012030)
            while ((len = libssh2_sftp_readdir(sftp->sftp_handle, filename,
                                               PATH_MAX, &attrs)) ==
                   LIBSSH2_ERROR_EAGAIN);
    #else /* !(LIBSSH2_APINO >= 200706012030) */
            len = libssh2_sftp_readdir(sftp->sftp_handle, filename,
                                       PATH_MAX, &attrs);
    #endif /* !(LIBSSH2_APINO >= 200706012030) */
            if (len > 0) {
              filename[len] = '\0';
    
              if (data->set.ftp_list_only) {
                if ((attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) &&
                    ((attrs.permissions & LIBSSH2_SFTP_S_IFMT) ==
                     LIBSSH2_SFTP_S_IFDIR)) {
                  infof(data, "%s\n", filename);
                }
    
              else {
                totalLen = 80 + len;
                line = (char *)malloc(totalLen);
                if (!line)
                  return CURLE_OUT_OF_MEMORY;
    
                if (!(attrs.flags & LIBSSH2_SFTP_ATTR_UIDGID))
                  attrs.uid = attrs.gid =0;
    
                currLen = snprintf(line, totalLen, "----------   1 %5d %5d",
                                   attrs.uid, attrs.gid);
    
                if (attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) {
                  if ((attrs.permissions & LIBSSH2_SFTP_S_IFMT) ==
                      LIBSSH2_SFTP_S_IFDIR) {
                    line[0] = 'd';
                  }
                  else if ((attrs.permissions & LIBSSH2_SFTP_S_IFMT) ==
                           LIBSSH2_SFTP_S_IFLNK) {
                    line[0] = 'l';
                  }
                  else if ((attrs.permissions & LIBSSH2_SFTP_S_IFMT) ==
                           LIBSSH2_SFTP_S_IFSOCK) {
                    line[0] = 's';
                  }
                  else if ((attrs.permissions & LIBSSH2_SFTP_S_IFMT) ==
                           LIBSSH2_SFTP_S_IFCHR) {
                    line[0] = 'c';
                  }
                  else if ((attrs.permissions & LIBSSH2_SFTP_S_IFMT) ==
                           LIBSSH2_SFTP_S_IFBLK) {
                    line[0] = 'b';
                  }
                  if (attrs.permissions & LIBSSH2_SFTP_S_IRUSR) {
                    line[1] = 'r';
                  }
                  if (attrs.permissions & LIBSSH2_SFTP_S_IWUSR) {
                    line[2] = 'w';
                  }
                  if (attrs.permissions & LIBSSH2_SFTP_S_IXUSR) {
                    line[3] = 'x';
                  }
                  if (attrs.permissions & LIBSSH2_SFTP_S_IRGRP) {
                    line[4] = 'r';
                  }
                  if (attrs.permissions & LIBSSH2_SFTP_S_IWGRP) {
                    line[5] = 'w';
                  }
                  if (attrs.permissions & LIBSSH2_SFTP_S_IXGRP) {
                    line[6] = 'x';
                  }
                  if (attrs.permissions & LIBSSH2_SFTP_S_IROTH) {
                    line[7] = 'r';
                  }
                  if (attrs.permissions & LIBSSH2_SFTP_S_IWOTH) {
                    line[8] = 'w';
                  }
                  if (attrs.permissions & LIBSSH2_SFTP_S_IXOTH) {
                    line[9] = 'x';
                  }
    
                if (attrs.flags & LIBSSH2_SFTP_ATTR_SIZE) {
                  currLen += snprintf(line+currLen, totalLen-currLen, "%11lld",
                                      attrs.filesize);
    
                if (attrs.flags & LIBSSH2_SFTP_ATTR_ACMODTIME) {
                  static const char * const months[12] = {
                    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
                    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
                  struct tm *nowParts;
                  time_t now, remoteTime;
    
                  now = time(NULL);
                  remoteTime = (time_t)attrs.mtime;
                  nowParts = localtime(&remoteTime);
    
                  if ((time_t)attrs.mtime > (now - (3600 * 24 * 180))) {
                    currLen += snprintf(line+currLen, totalLen-currLen,
                                        " %s %2d %2d:%02d",
                                        months[nowParts->tm_mon],
                                        nowParts->tm_mday, nowParts->tm_hour,
                                        nowParts->tm_min);
                  }
                  else {
                    currLen += snprintf(line+currLen, totalLen-currLen,
                                        " %s %2d %5d", months[nowParts->tm_mon],
                                        nowParts->tm_mday, 1900+nowParts->tm_year);
                  }
    
                currLen += snprintf(line+currLen, totalLen-currLen, " %s",
                                    filename);
                if ((attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) &&
                    ((attrs.permissions & LIBSSH2_SFTP_S_IFMT) ==
                     LIBSSH2_SFTP_S_IFLNK)) {
                  char linkPath[PATH_MAX + 1];
    
                  snprintf(linkPath, PATH_MAX, "%s%s", sftp->path, filename);
    
    #if (LIBSSH2_APINO >= 200706012030)
                  while ((len = libssh2_sftp_readlink(sftp->sftp_session, linkPath,
                                                      filename, PATH_MAX)) ==
                         LIBSSH2_ERROR_EAGAIN);
    #else /* !(LIBSSH2_APINO >= 200706012030) */
    
                  len = libssh2_sftp_readlink(sftp->sftp_session, linkPath,
                                              filename, PATH_MAX);
    
    #endif /* !(LIBSSH2_APINO >= 200706012030) */
    
                  line = realloc(line, totalLen + 4 + len);
                  if (!line)
                    return CURLE_OUT_OF_MEMORY;
    
                  currLen += snprintf(line+currLen, totalLen-currLen, " -> %s",
                                      filename);
    
                currLen += snprintf(line+currLen, totalLen-currLen, "\n");
                res = Curl_client_write(conn, CLIENTWRITE_BODY, line, 0);
                free(line);
    
            else if (len <= 0) {
              break;
            }
          } while (1);
    #if (LIBSSH2_APINO >= 200706012030)
          while (libssh2_sftp_closedir(sftp->sftp_handle) == LIBSSH2_ERROR_EAGAIN);
    #else /* !(LIBSSH2_APINO >= 200706012030) */
    
          libssh2_sftp_closedir(sftp->sftp_handle);
    
    #endif /* !(LIBSSH2_APINO >= 200706012030) */
    
          sftp->sftp_handle = NULL;
    
          /* no data to transfer */
          res = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
        }
        else {
          /*
           * Work on getting the specified file
           */
    
    #if (LIBSSH2_APINO >= 200706012030)
          do {
            sftp->sftp_handle =
              libssh2_sftp_open(sftp->sftp_session, sftp->path, LIBSSH2_FXF_READ,
                              LIBSSH2_SFTP_S_IRUSR|LIBSSH2_SFTP_S_IWUSR|
                              LIBSSH2_SFTP_S_IRGRP|LIBSSH2_SFTP_S_IROTH);
            if (!sftp->sftp_handle &&
                (libssh2_session_last_errno(sftp->ssh_session) !=
                                       LIBSSH2_ERROR_EAGAIN)) {
              err = libssh2_sftp_last_error(sftp->sftp_session);
              failf(conn->data, "Could not open remote file for reading: %s",
                    sftp_libssh2_strerror(err));
              return sftp_libssh2_error_to_CURLE(err);
            }
          } while (!sftp->sftp_handle);
    #else /* !(LIBSSH2_APINO >= 200706012030) */
    
            libssh2_sftp_open(sftp->sftp_session, sftp->path, LIBSSH2_FXF_READ,
                              LIBSSH2_SFTP_S_IRUSR|LIBSSH2_SFTP_S_IWUSR|
                              LIBSSH2_SFTP_S_IRGRP|LIBSSH2_SFTP_S_IROTH);
    
          if (!sftp->sftp_handle) {
            err = libssh2_sftp_last_error(sftp->sftp_session);
            failf(conn->data, "Could not open remote file for reading: %s",
                sftp_libssh2_strerror(err));
            return sftp_libssh2_error_to_CURLE(err);
          }
    
    #endif /* !(LIBSSH2_APINO >= 200706012030) */
    
    #if (LIBSSH2_APINO >= 200706012030)
          while ((rc = libssh2_sftp_stat(sftp->sftp_session, sftp->path, &attrs))
                 == LIBSSH2_ERROR_EAGAIN);
    #else /* !(LIBSSH2_APINO >= 200706012030) */
          rc = libssh2_sftp_stat(sftp->sftp_session, sftp->path, &attrs);
    #endif /* !(LIBSSH2_APINO >= 200706012030) */
          if (rc) {
    
            /*
             * libssh2_sftp_open() didn't return an error, so maybe the server
             * just doesn't support stat()
             */
            data->reqdata.size = -1;
            data->reqdata.maxdownload = -1;
          }
          else {
            data->reqdata.size = attrs.filesize;
            data->reqdata.maxdownload = attrs.filesize;
            Curl_pgrsSetDownloadSize(data, attrs.filesize);
          }
    
          Curl_pgrsTime(data, TIMER_STARTTRANSFER);
    
          /* Now download data. The libssh2 0.14 doesn't offer any way to do this
             without using this BLOCKING approach, so here's room for improvement
             once libssh2 can return EWOULDBLOCK to us. */
    #if 0
          /* code left here just because this is what this function will use the
             day libssh2 is improved */
          res = Curl_setup_transfer(conn, FIRSTSOCKET,
                                    bytecount, FALSE, NULL, -1, NULL);
    #endif
          while (res == CURLE_OK) {
    
    #if (LIBSSH2_APINO >= 200706012030)
            ssize_t nread;
    
            while ((nread = libssh2_sftp_read(data->reqdata.proto.ssh->sftp_handle,
                                      buf, BUFSIZE-1)) == LIBSSH2_ERROR_EAGAIN);
    #else /* !(LIBSSH2_APINO >= 200706012030) */
    
            size_t nread;
            /* NOTE: most *read() functions return ssize_t but this returns size_t
    
              which normally is unsigned! */
    
            nread = libssh2_sftp_read(data->reqdata.proto.ssh->sftp_handle,
                                      buf, BUFSIZE-1);
    
    #endif /* !(LIBSSH2_APINO >= 200706012030) */
    
    #if (LIBSSH2_APINO >= 200706012030)
            if (nread <= 0)
              break;
    #else /* !(LIBSSH2_APINO >= 200706012030) */
    
            /* this check can be changed to a <= 0 when nread is changed to a
    
              signed variable type */
    
            if ((nread == 0) || (nread == (size_t)~0))
              break;
    
    #endif /* !(LIBSSH2_APINO >= 200706012030) */
    
    
            bytecount += nread;
    
            res = Curl_client_write(conn, CLIENTWRITE_BODY, buf, nread);
            if(res)
              return res;
    
            Curl_pgrsSetDownloadCounter(data, bytecount);
    
            if(Curl_pgrsUpdate(conn))
              res = CURLE_ABORTED_BY_CALLBACK;
            else {
              struct timeval now = Curl_tvnow();
              res = Curl_speedcheck(data, now);
            }
          }
          if(Curl_pgrsUpdate(conn))
            res = CURLE_ABORTED_BY_CALLBACK;
    
          /* no (more) data to transfer */
          res = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
        }
      }
    
      return res;
    }
    
    
    CURLcode Curl_sftp_done(struct connectdata *conn, CURLcode status,
                            bool premature)
    
    {
      struct SSHPROTO *sftp = conn->data->reqdata.proto.ssh;
    
    
      Curl_safefree(sftp->path);
      sftp->path = NULL;
    
      Curl_safefree(sftp->homedir);
      sftp->homedir = NULL;
    
      if (sftp->sftp_handle) {
    
    #if (LIBSSH2_APINO >= 200706012030)
        while ((ret = libssh2_sftp_close(sftp->sftp_handle)) ==
               LIBSSH2_ERROR_EAGAIN);
        if (ret < 0) {
          infof(conn->data, "Failed to close libssh2 file\n");
        }
    #else /* !(LIBSSH2_APINO >= 200706012030) */
    
        if (libssh2_sftp_close(sftp->sftp_handle) < 0) {
          infof(conn->data, "Failed to close libssh2 file\n");
        }
    
    #endif /* !(LIBSSH2_APINO >= 200706012030) */
    
      /* Before we shut down, see if there are any post-quote commands to send: */
      if(!status && !premature && conn->data->set.postquote) {
        infof(conn->data, "Sending postquote commands\n");
        rc = sftp_sendquote(conn, conn->data->set.postquote);
      }
    
    
    #if (LIBSSH2_APINO >= 200706012030)
        while ((ret = libssh2_sftp_shutdown(sftp->sftp_session)) ==
               LIBSSH2_ERROR_EAGAIN);
        if (ret < 0) {
          infof(conn->data, "Failed to stop libssh2 sftp subsystem\n");
        }
    #else /* !(LIBSSH2_APINO >= 200706012030) */
    
        if (libssh2_sftp_shutdown(sftp->sftp_session) < 0) {
          infof(conn->data, "Failed to stop libssh2 sftp subsystem\n");
        }
    
    #endif /* !(LIBSSH2_APINO >= 200706012030) */
    
    #if (LIBSSH2_APINO >= 200706012030)
        while ((ret = libssh2_channel_close(sftp->ssh_channel)) ==
               LIBSSH2_ERROR_EAGAIN);
        if (ret < 0) {
          infof(conn->data, "Failed to stop libssh2 channel subsystem\n");
        }
    #else /* !(LIBSSH2_APINO >= 200706012030) */
    
        if (libssh2_channel_close(sftp->ssh_channel) < 0) {
          infof(conn->data, "Failed to stop libssh2 channel subsystem\n");
        }
    
    #endif /* !(LIBSSH2_APINO >= 200706012030) */
    
    #if (LIBSSH2_APINO >= 200706012030)
        while (libssh2_session_disconnect(sftp->ssh_session, "Shutdown") ==
               LIBSSH2_ERROR_EAGAIN);
    #else /* !(LIBSSH2_APINO >= 200706012030) */
    
        libssh2_session_disconnect(sftp->ssh_session, "Shutdown");
    
    #endif /* !(LIBSSH2_APINO >= 200706012030) */
    
        libssh2_session_free(sftp->ssh_session);
        sftp->ssh_session = NULL;
      }
    
      free(conn->data->reqdata.proto.ssh);
      conn->data->reqdata.proto.ssh = NULL;
      Curl_pgrsDone(conn);
    
      (void)status; /* unused */
    
      (void)ret;    /* possibly unused */
    
    }
    
    /* return number of received (decrypted) bytes */
    ssize_t Curl_sftp_send(struct connectdata *conn, int sockindex,
                           void *mem, size_t len)
    {
    
      ssize_t nwrite;   /* libssh2_sftp_write() used to return size_t in 0.14
                           but is changed to ssize_t in 0.15! */
    
    #if defined(LIBSSH2SFTP_EAGAIN) && (LIBSSH2_APINO < 200706012030)
    
      /* we prefer the non-blocking API but that didn't exist previously */
      nwrite = (ssize_t)
        libssh2_sftp_writenb(conn->data->reqdata.proto.ssh->sftp_handle, mem, len);
    #else
    
      nwrite = (ssize_t)
        libssh2_sftp_write(conn->data->reqdata.proto.ssh->sftp_handle, mem, len);
    
    #if (LIBSSH2_APINO >= 200706012030)
      if (nwrite == LIBSSH2_ERROR_EAGAIN) {
        return 0;
      }
    #endif
    
    /*
     * If the read would block (EWOULDBLOCK) we return -1. Otherwise we return
     * a regular CURLcode value.
     */
    ssize_t Curl_sftp_recv(struct connectdata *conn, int sockindex,
                           char *mem, size_t len)
    {
      ssize_t nread;
      (void)sockindex;
    
      /* libssh2_sftp_read() returns size_t !*/
    
    #if defined(LIBSSH2SFTP_EAGAIN) && (LIBSSH2_APINO < 200706012030)
      /* we prefer the non-blocking API but that didn't exist previously */
      nread = (ssize_t)
        libssh2_sftp_readnb(conn->data->reqdata.proto.ssh->sftp_handle, mem, len);
    #else
      nread = (ssize_t)
        libssh2_sftp_read(conn->data->reqdata.proto.ssh->sftp_handle, mem, len);
    #endif
      return nread;
    }
    
    
    /* The get_pathname() function is being borrowed from OpenSSH sftp.c
       version 4.6p1. */
    /*
     * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
     *
     * Permission to use, copy, modify, and distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     */
    static int
    get_pathname(const char **cpp, char **path)
    {
      const char *cp = *cpp, *end;
      char quot;
      u_int i, j;
    
      static const char * const WHITESPACE = " \t\r\n";
    
    
      cp += strspn(cp, WHITESPACE);
      if (!*cp) {
        *cpp = cp;
        *path = NULL;
    
        return CURLE_FTP_QUOTE_ERROR;
    
      }
    
      *path = malloc(strlen(cp) + 1);
      if (*path == NULL)
        return CURLE_OUT_OF_MEMORY;
    
      /* Check for quoted filenames */
      if (*cp == '\"' || *cp == '\'') {
        quot = *cp++;
    
        /* Search for terminating quote, unescape some chars */
        for (i = j = 0; i <= strlen(cp); i++) {
          if (cp[i] == quot) {  /* Found quote */
            i++;
            (*path)[j] = '\0';
            break;
          }
          if (cp[i] == '\0') {  /* End of string */
            /*error("Unterminated quote");*/
            goto fail;
          }
          if (cp[i] == '\\') {  /* Escaped characters */
            i++;
            if (cp[i] != '\'' && cp[i] != '\"' &&
                cp[i] != '\\') {
              /*error("Bad escaped character '\\%c'",
                  cp[i]);*/
              goto fail;
            }
          }
          (*path)[j++] = cp[i];
        }
    
        if (j == 0) {
          /*error("Empty quotes");*/
          goto fail;
        }
        *cpp = cp + i + strspn(cp + i, WHITESPACE);
      }
      else {
        /* Read to end of filename */
        end = strpbrk(cp, WHITESPACE);
        if (end == NULL)
          end = strchr(cp, '\0');
        *cpp = end + strspn(end, WHITESPACE);
    
        memcpy(*path, cp, end - cp);
        (*path)[end - cp] = '\0';
      }
      return (0);
    
      fail:
        free(*path);
        *path = NULL;
        return CURLE_FTP_QUOTE_ERROR;
    }
    
    
    static const char *sftp_libssh2_strerror(unsigned long err)
    {
      switch (err) {
      case LIBSSH2_FX_NO_SUCH_FILE:
        return "No such file or directory";
      case LIBSSH2_FX_PERMISSION_DENIED:
        return "Permission denied";
      case LIBSSH2_FX_FAILURE:
        return "Operation failed";
      case LIBSSH2_FX_BAD_MESSAGE:
        return "Bad message from SFTP server";
      case LIBSSH2_FX_NO_CONNECTION:
        return "Not connected to SFTP server";
      case LIBSSH2_FX_CONNECTION_LOST:
        return "Connection to SFTP server lost";
      case LIBSSH2_FX_OP_UNSUPPORTED:
        return "Operation not supported by SFTP server";
      case LIBSSH2_FX_INVALID_HANDLE:
        return "Invalid handle";
      case LIBSSH2_FX_NO_SUCH_PATH:
        return "No such file or directory";
      case LIBSSH2_FX_FILE_ALREADY_EXISTS:
        return "File already exists";
      case LIBSSH2_FX_WRITE_PROTECT:
        return "File is write protected";
      case LIBSSH2_FX_NO_MEDIA:
        return "No media";
      case LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM:
        return "Disk full";
      case LIBSSH2_FX_QUOTA_EXCEEDED:
        return "User quota exceeded";
      case LIBSSH2_FX_UNKNOWN_PRINCIPLE:
        return "Unknown principle";
      case LIBSSH2_FX_LOCK_CONFlICT:
        return "File lock conflict";
      case LIBSSH2_FX_DIR_NOT_EMPTY:
        return "Directory not empty";
      case LIBSSH2_FX_NOT_A_DIRECTORY:
        return "Not a directory";
      case LIBSSH2_FX_INVALID_FILENAME:
        return "Invalid filename";
      case LIBSSH2_FX_LINK_LOOP:
        return "Link points to itself";
      }
      return "Unknown error in libssh2";
    }
    
    /* BLOCKING */
    static CURLcode sftp_sendquote(struct connectdata *conn,
                                   struct curl_slist *quote)
    {
      struct curl_slist *item=quote;
      const char *cp;
      long err;
      struct SessionHandle *data = conn->data;
      LIBSSH2_SFTP *sftp_session = data->reqdata.proto.ssh->sftp_session;