diff -r 9f51d187f0bf include/curl/curl.h
--- a/include/curl/curl.h	Sat Apr 20 15:42:02 2013 +0300
+++ b/include/curl/curl.h	Sat Apr 20 15:47:31 2013 +0300
@@ -252,6 +252,10 @@
   size_t b_used;
 };
 
+typedef long (*curl_fileinfo_list_callback)(const struct curl_fileinfo *finf,
+                                            const struct curl_fileinfo *linf,
+                                            void *userptr);
+
 /* return codes for CURLOPT_CHUNK_BGN_FUNCTION */
 #define CURL_CHUNK_BGN_FUNC_OK      0
 #define CURL_CHUNK_BGN_FUNC_FAIL    1 /* tell the lib to end the task */
@@ -960,6 +964,7 @@
   CINIT(UPLOAD, LONG, 46),       /* this is an upload */
   CINIT(POST, LONG, 47),         /* HTTP POST method */
   CINIT(DIRLISTONLY, LONG, 48),  /* bare names when listing directories */
+  CINIT(DIRLISTFILES, LONG, 49),
 
   CINIT(APPEND, LONG, 50),       /* Append instead of overwrite on upload! */
 
@@ -1536,6 +1541,8 @@
   /* set the SMTP auth originator */
   CINIT(MAIL_AUTH, OBJECTPOINT, 217),
 
+  CINIT(DIRLISTFILES_CALLBACK, FUNCTIONPOINT, 218),
+
   CURLOPT_LASTENTRY /* the last unused */
 } CURLoption;
 
diff -r 9f51d187f0bf lib/fileinfo.h
--- a/lib/fileinfo.h	Sat Apr 20 15:42:02 2013 +0300
+++ b/lib/fileinfo.h	Sat Apr 20 15:47:31 2013 +0300
@@ -28,6 +28,7 @@
 
 void Curl_fileinfo_dtor(void *, void *);
 
+/* it's not implemented, isn't it? */
 struct curl_fileinfo *Curl_fileinfo_dup(const struct curl_fileinfo *src);
 
 #endif /* HEADER_CURL_FILEINFO_H */
diff -r 9f51d187f0bf lib/ssh.c
--- a/lib/ssh.c	Sat Apr 20 15:42:02 2013 +0300
+++ b/lib/ssh.c	Sat Apr 20 15:47:31 2013 +0300
@@ -83,6 +83,7 @@
 #include "multiif.h"
 #include "select.h"
 #include "warnless.h"
+#include "fileinfo.h"
 
 #define _MPRINTF_REPLACE /* use our functions only */
 #include <curl/mprintf.h>
@@ -368,6 +369,8 @@
     "SSH_SFTP_READDIR_INIT",
     "SSH_SFTP_READDIR",
     "SSH_SFTP_READDIR_LINK",
+    "SSH_SFTP_READDIR_LINK_REALPATH",
+    "SSH_SFTP_READDIR_LINK_STAT",
     "SSH_SFTP_READDIR_BOTTOM",
     "SSH_SFTP_READDIR_DONE",
     "SSH_SFTP_DOWNLOAD_INIT",
@@ -676,6 +679,89 @@
     return ssh_knownhost(conn);
 }
 
+static
+CURLcode fill_culrfile_info_with_sftp_file_attributes(
+                          struct curl_fileinfo * file,
+                          const char * filename,
+                          const int filenameLen,
+                          LIBSSH2_SFTP_ATTRIBUTES * ssh_attrs)
+{
+  file->flags |= CURLFINFOFLAG_KNOWN_FILENAME;
+  file->b_size =
+  file->b_used = filenameLen + 1;
+  file->b_data = (char*)malloc(file->b_size);
+  if(NULL == file->b_data) {
+    return CURLE_OUT_OF_MEMORY;
+  }
+
+  file->filename = file->b_data;
+  strcpy(file->filename, filename);
+
+  if((ssh_attrs->flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) &&
+     (ssh_attrs->permissions & LIBSSH2_SFTP_S_IFMT)) {
+    file->flags |= CURLFINFOFLAG_KNOWN_FILETYPE;
+
+    if(LIBSSH2_SFTP_S_ISLNK(ssh_attrs->permissions)) {
+      file->filetype = CURLFILETYPE_SYMLINK;
+    }
+    else if(LIBSSH2_SFTP_S_ISREG(ssh_attrs->permissions)) {
+      file->filetype = CURLFILETYPE_FILE;
+    }
+    else if(LIBSSH2_SFTP_S_ISDIR(ssh_attrs->permissions)) {
+      file->filetype = CURLFILETYPE_DIRECTORY;
+    }
+    else if(LIBSSH2_SFTP_S_ISCHR(ssh_attrs->permissions)) {
+      file->filetype = CURLFILETYPE_DEVICE_CHAR;
+    }
+    else if(LIBSSH2_SFTP_S_ISBLK(ssh_attrs->permissions)) {
+      file->filetype = CURLFILETYPE_DEVICE_BLOCK;
+    }
+    else if(LIBSSH2_SFTP_S_ISFIFO(ssh_attrs->permissions)) {
+      file->filetype = CURLFILETYPE_NAMEDPIPE;
+    }
+    else if(LIBSSH2_SFTP_S_ISSOCK(ssh_attrs->permissions)) {
+      file->filetype = CURLFILETYPE_SOCKET;
+    }
+    else {
+      /* log error, pass unknown or tell that
+       file type is not known? */
+      file->flags &=
+      (~CURLFINFOFLAG_KNOWN_FILETYPE);
+      file->filetype = CURLFILETYPE_UNKNOWN;
+    }
+  }
+
+  if(ssh_attrs->flags & LIBSSH2_SFTP_ATTR_ACMODTIME) {
+    file->flags |= CURLFINFOFLAG_KNOWN_TIME;
+    file->time = ssh_attrs->mtime;
+  }
+
+  if(ssh_attrs->flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) {
+    file->flags |= CURLFINFOFLAG_KNOWN_PERM;
+    file->perm =
+    ssh_attrs->permissions &
+    (LIBSSH2_SFTP_S_IRWXU |
+     LIBSSH2_SFTP_S_IRWXG |
+     LIBSSH2_SFTP_S_IRWXO);
+  }
+
+  if(ssh_attrs->flags & LIBSSH2_SFTP_ATTR_UIDGID) {
+    file->flags |=
+    (CURLFINFOFLAG_KNOWN_UID | CURLFINFOFLAG_KNOWN_GID);
+    file->uid = ssh_attrs->uid;
+    file->gid = ssh_attrs->gid;
+  }
+
+  if(ssh_attrs->flags & LIBSSH2_SFTP_ATTR_SIZE) {
+    file->flags |= CURLFINFOFLAG_KNOWN_SIZE;
+    /* type do not match. return error, trim, tell that size is not
+     known, change curl_fileinfo? */
+    file->size = ssh_attrs->filesize;
+  }
+
+  return CURLE_OK;
+}
+
 /*
  * ssh_statemach_act() runs the SSH state machine as far as it can without
  * blocking and without reaching the end.  The data the pointer 'block' points
@@ -1490,7 +1576,7 @@
         failf(data, "mkdir command failed: %s", sftp_libssh2_strerror(err));
         state(conn, SSH_SFTP_CLOSE);
         sshc->nextstate = SSH_NO_STATE;
-        sshc->actualcode = CURLE_QUOTE_ERROR;
+        sshc->actualcode = sftp_libssh2_error_to_CURLE(err);
         break;
       }
       state(conn, SSH_SFTP_NEXT_QUOTE);
@@ -1515,7 +1601,7 @@
         failf(data, "rename command failed: %s", sftp_libssh2_strerror(err));
         state(conn, SSH_SFTP_CLOSE);
         sshc->nextstate = SSH_NO_STATE;
-        sshc->actualcode = CURLE_QUOTE_ERROR;
+        sshc->actualcode = sftp_libssh2_error_to_CURLE(err);
         break;
       }
       state(conn, SSH_SFTP_NEXT_QUOTE);
@@ -1533,7 +1619,7 @@
         failf(data, "rmdir command failed: %s", sftp_libssh2_strerror(err));
         state(conn, SSH_SFTP_CLOSE);
         sshc->nextstate = SSH_NO_STATE;
-        sshc->actualcode = CURLE_QUOTE_ERROR;
+        sshc->actualcode = sftp_libssh2_error_to_CURLE(err);
         break;
       }
       state(conn, SSH_SFTP_NEXT_QUOTE);
@@ -1551,7 +1637,7 @@
         failf(data, "rm command failed: %s", sftp_libssh2_strerror(err));
         state(conn, SSH_SFTP_CLOSE);
         sshc->nextstate = SSH_NO_STATE;
-        sshc->actualcode = CURLE_QUOTE_ERROR;
+        sshc->actualcode = sftp_libssh2_error_to_CURLE(err);
         break;
       }
       state(conn, SSH_SFTP_NEXT_QUOTE);
@@ -1841,6 +1927,8 @@
       break;
 
     case SSH_SFTP_READDIR:
+      sshc->readdir_fileinfo = sshc->readdir_link_fileinfo = 0;
+
       sshc->readdir_len = libssh2_sftp_readdir_ex(sshc->sftp_handle,
                                                   sshc->readdir_filename,
                                                   PATH_MAX,
@@ -1851,6 +1939,7 @@
         rc = LIBSSH2_ERROR_EAGAIN;
         break;
       }
+
       if(sshc->readdir_len > 0) {
         sshc->readdir_filename[sshc->readdir_len] = '\0';
 
@@ -1882,12 +1971,35 @@
           }
         }
         else {
+          if(data->set.ftp_list_files) {
+            sshc->readdir_fileinfo = Curl_fileinfo_alloc();
+            if(sshc->readdir_fileinfo == NULL) {
+              state(conn, SSH_SFTP_CLOSE);
+              sshc->actualcode = CURLE_OUT_OF_MEMORY;
+              break;
+            }
+
+            result =
+            fill_culrfile_info_with_sftp_file_attributes(
+                                    sshc->readdir_fileinfo,
+                                    sshc->readdir_filename,
+                                    sshc->readdir_len,
+                                    &sshc->readdir_attrs);
+            if(CURLE_OK != result) {
+              state(conn, SSH_SFTP_CLOSE);
+              sshc->actualcode = result;
+              break;
+            }
+          }
+
           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);
             Curl_safefree(sshc->readdir_longentry);
+              Curl_fileinfo_dtor(0 /*dummy*/, sshc->readdir_fileinfo);
+              sshc->readdir_fileinfo = 0;
             state(conn, SSH_SFTP_CLOSE);
             sshc->actualcode = CURLE_OUT_OF_MEMORY;
             break;
@@ -1902,6 +2014,8 @@
             if(sshc->readdir_linkPath == NULL) {
               Curl_safefree(sshc->readdir_filename);
               Curl_safefree(sshc->readdir_longentry);
+                Curl_fileinfo_dtor(0 /*dummy*/, sshc->readdir_fileinfo);
+                sshc->readdir_fileinfo = 0;
               state(conn, SSH_SFTP_CLOSE);
               sshc->actualcode = CURLE_OUT_OF_MEMORY;
               break;
@@ -1956,6 +2070,8 @@
         Curl_safefree(sshc->readdir_line);
         Curl_safefree(sshc->readdir_filename);
         Curl_safefree(sshc->readdir_longentry);
+        Curl_fileinfo_dtor(0 /*dummy*/, sshc->readdir_fileinfo);
+        sshc->readdir_fileinfo = 0;
         state(conn, SSH_SFTP_CLOSE);
         sshc->actualcode = CURLE_OUT_OF_MEMORY;
         break;
@@ -1969,9 +2085,162 @@
                                         " -> %s",
                                         sshc->readdir_filename);
 
+      if(data->set.ftp_list_files) {
+        sshc->readdir_fileinfo->b_size += sshc->readdir_len + 1;
+        sshc->readdir_fileinfo->b_data =
+          realloc(sshc->readdir_fileinfo->b_data,
+                  sshc->readdir_fileinfo->b_size);
+        sshc->readdir_fileinfo->filename = sshc->readdir_fileinfo->b_data;
+        if(!sshc->readdir_fileinfo->b_data) {
+          Curl_safefree(sshc->readdir_line);
+          Curl_safefree(sshc->readdir_filename);
+          Curl_safefree(sshc->readdir_longentry);
+          Curl_fileinfo_dtor(0 /*dummy*/, sshc->readdir_fileinfo);
+          sshc->readdir_fileinfo = 0;
+          state(conn, SSH_SFTP_CLOSE);
+          sshc->actualcode = CURLE_OUT_OF_MEMORY;
+          break;
+        }
+        sshc->readdir_fileinfo->strings.target =
+          sshc->readdir_fileinfo->b_data + sshc->readdir_fileinfo->b_used;
+        strcpy(sshc->readdir_fileinfo->strings.target, sshc->readdir_filename);
+        sshc->readdir_fileinfo->b_used = sshc->readdir_fileinfo->b_size;
+        state(conn, SSH_SFTP_READDIR_LINK_REALPATH);
+        break;
+      }
+
       state(conn, SSH_SFTP_READDIR_BOTTOM);
       break;
 
+    case SSH_SFTP_READDIR_LINK_REALPATH:
+      {
+        char linkRealPath[PATH_MAX];
+
+        /* starts with ".", "..", "~" or "/" - ok
+           no - prepend with working path
+         */
+        if('/' != sshc->readdir_filename[0] &&
+           '~' != sshc->readdir_filename[0] &&
+           strlen(sshc->readdir_filename) >= 2 &&
+           0 != memcmp("./", sshc->readdir_filename, 2) &&
+           0 != memcmp("..", sshc->readdir_filename, 2)) {
+
+          /* we could also rely on the fact that it's PATH_MAX+1,
+             but let's do another way
+           */
+          size_t workingPathLen = strlen(sftp_scp->path);
+
+          sshc->readdir_filename =
+            realloc(sshc->readdir_filename,
+                    strlen(sshc->readdir_filename) + workingPathLen + 1);
+          if(!sshc->readdir_filename) {
+            Curl_safefree(sshc->readdir_line);
+            Curl_safefree(sshc->readdir_filename);
+            Curl_safefree(sshc->readdir_longentry);
+            Curl_fileinfo_dtor(0 /*dummy*/, sshc->readdir_fileinfo);
+            sshc->readdir_fileinfo = 0;
+            state(conn, SSH_SFTP_CLOSE);
+            sshc->actualcode = CURLE_OUT_OF_MEMORY;
+            break;
+          }
+
+          memmove(sshc->readdir_filename + workingPathLen,
+                  sshc->readdir_filename,
+                  strlen(sshc->readdir_filename) + 1);
+          memcpy(sshc->readdir_filename, sftp_scp->path, workingPathLen);
+        }
+
+        rc = sftp_libssh2_realpath(sshc->sftp_session, sshc->readdir_filename,
+                                   linkRealPath, PATH_MAX-1);
+        if(rc == LIBSSH2_ERROR_EAGAIN) {
+          break;
+        }
+        else if(rc > 0) {
+          /* It seems that this string is not always NULL terminated */
+          linkRealPath[rc] = '\0';
+          sshc->readdir_filename = realloc(sshc->readdir_filename,
+                                           strlen(linkRealPath));
+          if(!sshc->readdir_filename) {
+            Curl_safefree(sshc->readdir_line);
+            Curl_safefree(sshc->readdir_filename);
+            Curl_safefree(sshc->readdir_longentry);
+            Curl_fileinfo_dtor(0 /*dummy*/, sshc->readdir_fileinfo);
+            sshc->readdir_fileinfo = 0;
+            state(conn, SSH_SFTP_CLOSE);
+            sshc->actualcode = CURLE_OUT_OF_MEMORY;
+            break;
+          }
+        }
+        else {
+          err = sftp_libssh2_last_error(sshc->sftp_session);
+          failf(data, "Attempt to get SFTP realpath for %s failed: %s",
+                sshc->readdir_filename,
+                sftp_libssh2_strerror(err));
+          state(conn, SSH_SFTP_READDIR_BOTTOM);
+          break;
+        }
+      }
+
+      state(conn, SSH_SFTP_READDIR_LINK_STAT);
+      break;
+
+    case SSH_SFTP_READDIR_LINK_STAT:
+    {
+      LIBSSH2_SFTP_ATTRIBUTES attrs;
+
+      rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshc->readdir_filename,
+                                curlx_uztoui(strlen(sshc->readdir_filename)),
+                                LIBSSH2_SFTP_STAT, &attrs);
+      if(rc == LIBSSH2_ERROR_EAGAIN) {
+        break;
+      }
+      else if(rc) {
+        /* what to do? the reason can be - server doesn't support stat at all,
+           we don't have access to the folder, etc.
+           I think we should fail whole directory listing if we've failed
+           to get link destination file info - dst path would be enough
+         */
+        err = sftp_libssh2_last_error(sshc->sftp_session);
+        failf(data, "Attempt to get SFTP stats failed: %s",
+              sftp_libssh2_strerror(err));
+      }
+      else {
+        sshc->readdir_link_fileinfo = Curl_fileinfo_alloc();
+        if(sshc->readdir_link_fileinfo == NULL) {
+          Curl_safefree(sshc->readdir_line);
+          Curl_safefree(sshc->readdir_filename);
+          Curl_safefree(sshc->readdir_longentry);
+          Curl_fileinfo_dtor(0 /*dummy*/, sshc->readdir_fileinfo);
+          sshc->readdir_fileinfo = 0;
+          state(conn, SSH_SFTP_CLOSE);
+          sshc->actualcode = CURLE_OUT_OF_MEMORY;
+          break;
+        }
+
+        result =
+        fill_culrfile_info_with_sftp_file_attributes(
+                                               sshc->readdir_link_fileinfo,
+                                               sshc->readdir_filename,
+                                               strlen(sshc->readdir_filename),
+                                               &attrs);
+        if(CURLE_OK != result) {
+          Curl_safefree(sshc->readdir_line);
+          Curl_safefree(sshc->readdir_filename);
+          Curl_safefree(sshc->readdir_longentry);
+          Curl_fileinfo_dtor(0 /*dummy*/, sshc->readdir_fileinfo);
+          sshc->readdir_fileinfo = 0;
+          Curl_fileinfo_dtor(0 /*dummy*/, sshc->readdir_link_fileinfo);
+          sshc->readdir_link_fileinfo = 0;
+          state(conn, SSH_SFTP_CLOSE);
+          sshc->actualcode = result;
+          break;
+        }
+      }
+
+      state(conn, SSH_SFTP_READDIR_BOTTOM);
+      break;
+    }
+
     case SSH_SFTP_READDIR_BOTTOM:
       sshc->readdir_currLen += snprintf(sshc->readdir_line +
                                         sshc->readdir_currLen,
@@ -1991,6 +2260,20 @@
         data->req.bytecount += sshc->readdir_currLen;
       }
       Curl_safefree(sshc->readdir_line);
+
+      if(data->set.ftp_list_files) {
+        if(conn->data->set.ftp_fileinfo_list_callback) {
+          conn->data->set.ftp_fileinfo_list_callback(
+                                                   sshc->readdir_fileinfo,
+                                                   sshc->readdir_link_fileinfo,
+                                                   data->set.out);
+        }
+        Curl_fileinfo_dtor(0 /*dummy*/, sshc->readdir_fileinfo);
+        sshc->readdir_fileinfo = 0;
+        Curl_fileinfo_dtor(0 /*dummy*/, sshc->readdir_link_fileinfo);
+        sshc->readdir_link_fileinfo = 0;
+      }
+
       if(result) {
         state(conn, SSH_STOP);
       }
diff -r 9f51d187f0bf lib/ssh.h
--- a/lib/ssh.h	Sat Apr 20 15:42:02 2013 +0300
+++ b/lib/ssh.h	Sat Apr 20 15:47:31 2013 +0300
@@ -74,6 +74,8 @@
   SSH_SFTP_READDIR_INIT,
   SSH_SFTP_READDIR,
   SSH_SFTP_READDIR_LINK,
+  SSH_SFTP_READDIR_LINK_REALPATH,
+  SSH_SFTP_READDIR_LINK_STAT,
   SSH_SFTP_READDIR_BOTTOM,
   SSH_SFTP_READDIR_DONE,
   SSH_SFTP_DOWNLOAD_INIT,
@@ -130,6 +132,8 @@
   int readdir_len, readdir_totalLen, readdir_currLen;
   char *readdir_line;
   char *readdir_linkPath;
+  struct curl_fileinfo * readdir_fileinfo;
+  struct curl_fileinfo * readdir_link_fileinfo;
   /* end of READDIR stuff */
 
   int secondCreateDirs;         /* counter use by the code to see if the
diff -r 9f51d187f0bf lib/url.c
--- a/lib/url.c	Sat Apr 20 15:42:02 2013 +0300
+++ b/lib/url.c	Sat Apr 20 15:47:31 2013 +0300
@@ -795,6 +795,12 @@
      */
     data->set.ftp_list_only = (0 != va_arg(param, long))?TRUE:FALSE;
     break;
+  case CURLOPT_DIRLISTFILES:
+    data->set.ftp_list_files = (0 != va_arg(param, long))?TRUE:FALSE;
+    break;
+  case CURLOPT_DIRLISTFILES_CALLBACK:
+    data->set.ftp_fileinfo_list_callback = va_arg(param, void *);
+    break;
   case CURLOPT_APPEND:
     /*
      * We want to upload and append to an existing file.
diff -r 9f51d187f0bf lib/urldata.h
--- a/lib/urldata.h	Sat Apr 20 15:42:02 2013 +0300
+++ b/lib/urldata.h	Sat Apr 20 15:47:31 2013 +0300
@@ -1510,6 +1510,8 @@
   bool prefer_ascii;     /* ASCII rather than binary */
   bool ftp_append;       /* append, not overwrite, on upload */
   bool ftp_list_only;    /* switch FTP command for listing directories */
+  bool ftp_list_files;
+  curl_fileinfo_list_callback ftp_fileinfo_list_callback;
   bool ftp_use_port;     /* use the FTP PORT command */
   bool hide_progress;    /* don't use the progress meter */
   bool http_fail_on_error;  /* fail on HTTP error codes >= 300 */
