de8c2ff
--- proftpd-1.3.2a/modules/mod_facts.c.mlsd	2009-04-28 22:17:45.000000000 +0100
de8c2ff
+++ proftpd-1.3.2a/modules/mod_facts.c	2009-09-07 14:17:39.000000000 +0100
de8c2ff
@@ -22,7 +22,7 @@
de8c2ff
  * resulting executable, without including the source code for OpenSSL in the
de8c2ff
  * source distribution.
de8c2ff
  *
de8c2ff
- * $Id: mod_facts.c,v 1.13.2.1 2009/04/28 21:17:45 castaglia Exp $
de8c2ff
+ * $Id: mod_facts.c,v 1.22 2009/04/09 15:59:38 castaglia Exp $
de8c2ff
  */
de8c2ff
 
de8c2ff
 #include "conf.h"
de8c2ff
@@ -227,7 +227,17 @@
de8c2ff
     ptr = buf + buflen;
de8c2ff
   }
de8c2ff
 
de8c2ff
-  snprintf(ptr, bufsz - buflen, " %s\n", info->path);
de8c2ff
+  /* MLST entries are not sent via pr_data_xfer(), and thus we do not need
de8c2ff
+   * to include an LF at the end; it is appended by pr_response_send_raw().
de8c2ff
+   * But MLSD entries DO need the trailing LF, so that it can be converted
de8c2ff
+   * into a CRLF sequence by pr_data_xfer().
de8c2ff
+   */
de8c2ff
+  if (strcmp(session.curr_cmd, C_MLSD) == 0) {
de8c2ff
+    snprintf(ptr, bufsz - buflen, " %s\n", info->path);
de8c2ff
+
de8c2ff
+  } else {
de8c2ff
+    snprintf(ptr, bufsz - buflen, " %s", info->path);
de8c2ff
+  }
de8c2ff
 
de8c2ff
   buf[bufsz-1] = '\0';
de8c2ff
   buflen = strlen(buf);
de8c2ff
@@ -244,11 +254,17 @@
de8c2ff
  * channel, wherease MLSD's output is sent via a data transfer, much like
de8c2ff
  * LIST or NLST.
de8c2ff
  */
de8c2ff
-static char mlinfo_buf[PR_TUNABLE_BUFFER_SIZE];
de8c2ff
+static char *mlinfo_buf = NULL;
de8c2ff
+static size_t mlinfo_bufsz = 0;
de8c2ff
 static size_t mlinfo_buflen = 0;
de8c2ff
 
de8c2ff
 static void facts_mlinfobuf_init(void) {
de8c2ff
-  memset(mlinfo_buf, '\0', sizeof(mlinfo_buf));
de8c2ff
+  if (mlinfo_buf == NULL) {
de8c2ff
+    mlinfo_bufsz = pr_config_get_xfer_bufsz();
de8c2ff
+    mlinfo_buf = palloc(session.pool, mlinfo_bufsz);
de8c2ff
+  }
de8c2ff
+
de8c2ff
+  memset(mlinfo_buf, '\0', mlinfo_bufsz);
de8c2ff
   mlinfo_buflen = 0;
de8c2ff
 }
de8c2ff
 
de8c2ff
@@ -261,11 +277,11 @@
de8c2ff
   /* If this buffer will exceed the capacity of mlinfo_buf, then flush
de8c2ff
    * mlinfo_buf.
de8c2ff
    */
de8c2ff
-  if (buflen >= (sizeof(mlinfo_buf) - mlinfo_buflen)) {
de8c2ff
+  if (buflen >= (mlinfo_bufsz - mlinfo_buflen)) {
de8c2ff
     (void) facts_mlinfobuf_flush();
de8c2ff
   }
de8c2ff
 
de8c2ff
-  sstrcat(mlinfo_buf, buf, sizeof(mlinfo_buf));
de8c2ff
+  sstrcat(mlinfo_buf, buf, mlinfo_bufsz);
de8c2ff
   mlinfo_buflen += buflen;
de8c2ff
 }
de8c2ff
 
de8c2ff
@@ -358,6 +374,8 @@
de8c2ff
   char buf[PR_TUNABLE_BUFFER_SIZE];
de8c2ff
 
de8c2ff
   (void) facts_mlinfo_fmt(info, buf, sizeof(buf));
de8c2ff
+
de8c2ff
+  /* The trailing CRLF will be added by pr_response_send_raw(). */
de8c2ff
   pr_response_send_raw("%s", buf);
de8c2ff
 }
de8c2ff
 
de8c2ff
@@ -595,7 +613,6 @@
de8c2ff
  */
de8c2ff
 
de8c2ff
 MODRET facts_mff(cmd_rec *cmd) {
de8c2ff
-  register unsigned int i;
de8c2ff
   const char *path, *decoded_path;
de8c2ff
   char *facts, *ptr;
de8c2ff
 
de8c2ff
@@ -606,18 +623,16 @@
de8c2ff
 
de8c2ff
   facts = cmd->argv[1];
de8c2ff
 
de8c2ff
-  /* The path can contain spaces; it is thus the concatenation of all of the
de8c2ff
-   * arguments after the timestamp.
de8c2ff
+  /* The path can contain spaces.  Thus we need to use cmd->arg, not cmd->argv,
de8c2ff
+   * to find the path.  But cmd->arg contains the facts as well.  Thus we
de8c2ff
+   * find the FIRST space in cmd->arg; the path is everything past that space.
de8c2ff
    */
de8c2ff
-  path = pstrdup(cmd->tmp_pool, cmd->argv[2]);
de8c2ff
-  for (i = 3; i < cmd->argc; i++) {
de8c2ff
-    path = pstrcat(cmd->tmp_pool, path, " ", cmd->argv[i], NULL);
de8c2ff
-  }
de8c2ff
+  ptr = strchr(cmd->arg, ' ');
de8c2ff
+  path = pstrdup(cmd->tmp_pool, ptr + 1);
de8c2ff
 
de8c2ff
   decoded_path = pr_fs_decode_path(cmd->tmp_pool, path);
de8c2ff
 
de8c2ff
-  if (!dir_check(cmd->tmp_pool, cmd->argv[0], cmd->group, (char *) decoded_path,
de8c2ff
-      NULL)) {
de8c2ff
+  if (!dir_check(cmd->tmp_pool, cmd, cmd->group, (char *) decoded_path, NULL)) {
de8c2ff
     pr_log_debug(DEBUG4, MOD_FACTS_VERSION ": %s command denied by <Limit>",
de8c2ff
       cmd->argv[0]);
de8c2ff
     pr_response_add_err(R_550, _("Unable to handle command"));
de8c2ff
@@ -739,7 +754,6 @@
de8c2ff
 }
de8c2ff
 
de8c2ff
 MODRET facts_mfmt(cmd_rec *cmd) {
de8c2ff
-  register unsigned int i;
de8c2ff
   const char *path, *decoded_path;
de8c2ff
   char *timestamp, *ptr;
de8c2ff
   int res;
de8c2ff
@@ -751,18 +765,16 @@
de8c2ff
 
de8c2ff
   timestamp = cmd->argv[1];
de8c2ff
 
de8c2ff
-  /* The path can contain spaces; it is thus the concatenation of all of the
de8c2ff
-   * arguments after the timestamp.
de8c2ff
+  /* The path can contain spaces.  Thus we need to use cmd->arg, not cmd->argv,
de8c2ff
+   * to find the path.  But cmd->arg contains the facts as well.  Thus we
de8c2ff
+   * find the FIRST space in cmd->arg; the path is everything past that space.
de8c2ff
    */
de8c2ff
-  path = pstrdup(cmd->tmp_pool, cmd->argv[2]);
de8c2ff
-  for (i = 3; i < cmd->argc; i++) {
de8c2ff
-    path = pstrcat(cmd->tmp_pool, path, " ", cmd->argv[i], NULL);
de8c2ff
-  }
de8c2ff
+  ptr = strchr(cmd->arg, ' ');
de8c2ff
+  path = pstrdup(cmd->tmp_pool, ptr + 1);
de8c2ff
 
de8c2ff
   decoded_path = pr_fs_decode_path(cmd->tmp_pool, path);
de8c2ff
 
de8c2ff
-  if (!dir_check(cmd->tmp_pool, cmd->argv[0], cmd->group, (char *) decoded_path,
de8c2ff
-      NULL)) {
de8c2ff
+  if (!dir_check(cmd->tmp_pool, cmd, cmd->group, (char *) decoded_path, NULL)) {
de8c2ff
     pr_log_debug(DEBUG4, MOD_FACTS_VERSION ": %s command denied by <Limit>",
de8c2ff
       cmd->argv[0]);
de8c2ff
     pr_response_add_err(R_550, _("Unable to handle command"));
de8c2ff
@@ -820,33 +832,28 @@
de8c2ff
   DIR *dirh;
de8c2ff
   struct dirent *dent;
de8c2ff
 
de8c2ff
-  if (cmd->argc > 2) {
de8c2ff
-    pr_response_add_err(R_501, _("Invalid number of arguments"));
de8c2ff
-    return PR_ERROR(cmd);
de8c2ff
-  }
de8c2ff
-
de8c2ff
   if (cmd->argc != 1) {
de8c2ff
-    path = cmd->argv[1];
de8c2ff
+    path = pstrdup(cmd->tmp_pool, cmd->arg);
de8c2ff
     decoded_path = pr_fs_decode_path(cmd->tmp_pool, path);
de8c2ff
 
de8c2ff
   } else {
de8c2ff
     decoded_path = path = pr_fs_getcwd();
de8c2ff
   }
de8c2ff
 
de8c2ff
-  if (!dir_check(cmd->tmp_pool, cmd->argv[0], cmd->group, (char *) decoded_path,
de8c2ff
-      NULL)) {
de8c2ff
+  if (!dir_check(cmd->tmp_pool, cmd, cmd->group, (char *) decoded_path, NULL)) {
de8c2ff
     pr_log_debug(DEBUG4, MOD_FACTS_VERSION ": %s command denied by <Limit>",
de8c2ff
       cmd->argv[0]);
de8c2ff
     pr_response_add_err(R_550, _("Unable to handle command"));
de8c2ff
     return PR_ERROR(cmd);
de8c2ff
   }
de8c2ff
 
de8c2ff
-  /* RFC3659 explicitly does NOT support glob characters. */
de8c2ff
+  /* RFC3659 explicitly does NOT support glob characters.  So warn about
de8c2ff
+   * this, but let the command continue as is.  We don't actually call
de8c2ff
+   * glob(3) here, so no expansion will occur.
de8c2ff
+   */
de8c2ff
   if (strpbrk(decoded_path, "{[*?") != NULL) {
de8c2ff
-    pr_log_debug(DEBUG2, MOD_FACTS_VERSION ": unable to handle MLSD command: "
de8c2ff
-      "target '%s' contains glob characters", decoded_path);
de8c2ff
-    pr_response_add_err(R_550, _("Unable to handle command"));
de8c2ff
-    return PR_ERROR(cmd);
de8c2ff
+    pr_log_debug(DEBUG9, MOD_FACTS_VERSION ": glob characters in MLSD ('%s') "
de8c2ff
+      "ignored", decoded_path);
de8c2ff
   }
de8c2ff
 
de8c2ff
   /* Make sure that the given path is actually a directory. */
de8c2ff
@@ -870,11 +877,11 @@
de8c2ff
     int xerrno = errno;
de8c2ff
 
de8c2ff
     pr_trace_msg("fileperms", 1, "MLSD, user '%s' (UID %lu, GID %lu): "
de8c2ff
-      "error reading directory '%s': %s", session.user,
de8c2ff
+      "error opening directory '%s': %s", session.user,
de8c2ff
       (unsigned long) session.uid, (unsigned long) session.gid,
de8c2ff
       decoded_path, strerror(xerrno));
de8c2ff
 
de8c2ff
-    pr_response_add_err(R_550, _("'%s' cannot be listed"), path);
de8c2ff
+    pr_response_add_err(R_550, "%s: %s", path, strerror(xerrno));
de8c2ff
     return PR_ERROR(cmd);
de8c2ff
   }
de8c2ff
 
de8c2ff
@@ -899,16 +906,14 @@
de8c2ff
     /* Check that the file can be listed. */
de8c2ff
     abs_path = dir_realpath(cmd->tmp_pool, rel_path);
de8c2ff
     if (abs_path) {
de8c2ff
-      res = dir_check(cmd->tmp_pool, cmd->argv[0], cmd->group, abs_path,
de8c2ff
-        &hidden);
de8c2ff
+      res = dir_check(cmd->tmp_pool, cmd, cmd->group, abs_path, &hidden);
de8c2ff
       
de8c2ff
     } else {
de8c2ff
       abs_path = dir_canonical_path(cmd->tmp_pool, rel_path);
de8c2ff
       if (abs_path == NULL)
de8c2ff
         abs_path = rel_path;
de8c2ff
 
de8c2ff
-      res = dir_check_canon(cmd->tmp_pool, cmd->argv[0], cmd->group, abs_path,
de8c2ff
-        &hidden);
de8c2ff
+      res = dir_check_canon(cmd->tmp_pool, cmd, cmd->group, abs_path, &hidden);
de8c2ff
     }
de8c2ff
 
de8c2ff
     if (!res || hidden) {
de8c2ff
@@ -955,20 +960,15 @@
de8c2ff
   const char *path, *decoded_path;
de8c2ff
   struct mlinfo info;
de8c2ff
 
de8c2ff
-  if (cmd->argc > 2) {
de8c2ff
-    pr_response_add_err(R_501, _("Invalid number of arguments"));
de8c2ff
-    return PR_ERROR(cmd);
de8c2ff
-  }
de8c2ff
-
de8c2ff
   if (cmd->argc != 1) {
de8c2ff
-    path = cmd->argv[1];
de8c2ff
+    path = pstrdup(cmd->tmp_pool, cmd->arg);
de8c2ff
     decoded_path = pr_fs_decode_path(cmd->tmp_pool, path);
de8c2ff
 
de8c2ff
   } else {
de8c2ff
     decoded_path = path = pr_fs_getcwd();
de8c2ff
   }
de8c2ff
 
de8c2ff
-  if (!dir_check(cmd->tmp_pool, cmd->argv[0], cmd->group, (char *) decoded_path,
de8c2ff
+  if (!dir_check(cmd->tmp_pool, cmd, cmd->group, (char *) decoded_path,
de8c2ff
       &hidden)) {
de8c2ff
     pr_log_debug(DEBUG4, MOD_FACTS_VERSION ": %s command denied by <Limit>",
de8c2ff
       cmd->argv[0]);
de8c2ff
@@ -1155,6 +1155,7 @@
de8c2ff
 
de8c2ff
   pr_feat_add("MFF modify;UNIX.group;UNIX.mode;");
de8c2ff
   pr_feat_add("MFMT");
de8c2ff
+  pr_feat_add("TVFS");
de8c2ff
 
de8c2ff
   facts_mlst_feat_add(session.pool);
de8c2ff