components/proftpd/patches/15825705.patch
branchs11-update
changeset 3224 589e0e82672d
parent 3216 d8137835c0b4
child 3225 7bbaa2f97cd1
equal deleted inserted replaced
3216:d8137835c0b4 3224:589e0e82672d
     1 http://bugs.proftpd.org/show_bug.cgi?id=4014
       
     2 
       
     3 I addition, this patch backports the pr_str_get_nbytes() function.
       
     4 
       
     5 diff --git a/include/options.h b/include/options.h
       
     6 index 96fa35d..0b29bf7 100644
       
     7 --- a/include/options.h
       
     8 +++ b/include/options.h
       
     9 @@ -110,6 +110,13 @@
       
    10  # define PR_TUNABLE_XFER_BUFFER_SIZE	PR_TUNABLE_BUFFER_SIZE
       
    11  #endif
       
    12  
       
    13 +/* Maximum FTP command size.  For details on this size of 512KB, see
       
    14 + * the Bug#4014 discussion.
       
    15 + */
       
    16 +#ifndef PR_TUNABLE_CMD_BUFFER_SIZE
       
    17 +# define PR_TUNABLE_CMD_BUFFER_SIZE    (512 * 1024)
       
    18 +#endif
       
    19 +
       
    20  /* Maximum path length.  GNU HURD (and some others) do not define
       
    21   * MAXPATHLEN.  POSIX' PATH_MAX is mandated to be at least 256 
       
    22   * (according to some), so 1K, in the absense of MAXPATHLEN, should be
       
    23 diff --git a/include/str.h b/include/str.h
       
    24 index b4fed7c..1cf8724 100644
       
    25 --- a/include/str.h
       
    26 +++ b/include/str.h
       
    27 @@ -39,6 +39,7 @@ char *pstrndup(pool *, const char *, size_t);
       
    28  
       
    29  char *pr_str_strip(pool *, char *);
       
    30  char *pr_str_strip_end(char *, char *);
       
    31 +int pr_str_get_nbytes(const char *, const char *, off_t *);
       
    32  char *pr_str_get_word(char **, int);
       
    33  
       
    34  #define PR_STR_FL_PRESERVE_COMMENTS		0x0001
       
    35 diff --git a/modules/mod_core.c b/modules/mod_core.c
       
    36 index 18a47c2..922f4d1 100644
       
    37 --- a/modules/mod_core.c
       
    38 +++ b/modules/mod_core.c
       
    39 @@ -2240,18 +2240,44 @@ MODRET set_allowforeignaddress(cmd_rec *cmd) {
       
    40  }
       
    41  
       
    42  MODRET set_commandbuffersize(cmd_rec *cmd) {
       
    43 -  int size = 0;
       
    44 +  int res;
       
    45 +  size_t size = 0;
       
    46 +  off_t nbytes = 0;
       
    47    config_rec *c = NULL;
       
    48 +  const char *units = NULL;
       
    49 +
       
    50 +  if (cmd->argc < 2 || cmd->argc > 3) {
       
    51 +    CONF_ERROR(cmd, "wrong number of parameters")
       
    52 +  }
       
    53  
       
    54 -  CHECK_ARGS(cmd, 1);
       
    55    CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
       
    56  
       
    57 -  /* NOTE: need to add checks for maximum possible sizes, negative sizes. */
       
    58 -  size = atoi(cmd->argv[1]);
       
    59 +  if (cmd->argc == 3) {
       
    60 +    units = cmd->argv[2];
       
    61 +  }
       
    62 +
       
    63 +  if (pr_str_get_nbytes(cmd->argv[1], units, &nbytes) < 0) {
       
    64 +    CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to parse: ",
       
    65 +      cmd->argv[1], " ", units, ": ", strerror(errno), NULL));
       
    66 +  }
       
    67 +
       
    68 +  if (nbytes > PR_TUNABLE_CMD_BUFFER_SIZE) {
       
    69 +    char max[1024];
       
    70 +
       
    71 +    snprintf(max, sizeof(max)-1, "%lu", (unsigned long)
       
    72 +      PR_TUNABLE_CMD_BUFFER_SIZE);
       
    73 +    max[sizeof(max)-1] = '\0';
       
    74 +
       
    75 +    CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "size ", cmd->argv[1], units,
       
    76 +      "exceeds max size ", max, NULL));
       
    77 +  }
       
    78 +
       
    79 +  /* Possible truncation here, but only for an absurdly large size. */
       
    80 +  size = (size_t) nbytes;
       
    81  
       
    82    c = add_config_param(cmd->argv[0], 1, NULL);
       
    83 -  c->argv[0] = pcalloc(c->pool, sizeof(int));
       
    84 -  *((int *) c->argv[0]) = size;
       
    85 +  c->argv[0] = pcalloc(c->pool, sizeof(size_t));
       
    86 +  *((size_t *) c->argv[0]) = size;
       
    87  
       
    88    return PR_HANDLED(cmd);
       
    89  }
       
    90 diff --git a/src/main.c b/src/main.c
       
    91 index 3e6d637..660e14b 100644
       
    92 --- a/src/main.c
       
    93 +++ b/src/main.c
       
    94 @@ -466,42 +466,21 @@ static int _dispatch(cmd_rec *cmd, int cmd_type, int validate, char *match) {
       
    95    return success;
       
    96  }
       
    97  
       
    98 -/* Returns the appropriate maximum buffer length to use for FTP commands
       
    99 - * from the client, taking the CommandBufferSize directive into account.
       
   100 +/* Returns the appropriate maximum buffer size to use for FTP commands
       
   101 + * from the client.
       
   102   */
       
   103 -static long get_max_cmd_len(size_t buflen) {
       
   104 -  long res;
       
   105 -  int *bufsz = NULL;
       
   106 -  size_t default_cmd_bufsz;
       
   107 -
       
   108 -  /* It's possible for the admin to select a PR_TUNABLE_BUFFER_SIZE which
       
   109 -   * is smaller than PR_DEFAULT_CMD_BUFSZ.  We need to handle such cases
       
   110 -   * properly.
       
   111 -   */
       
   112 -  default_cmd_bufsz = PR_DEFAULT_CMD_BUFSZ;
       
   113 -  if (default_cmd_bufsz > buflen) {
       
   114 -    default_cmd_bufsz = buflen;
       
   115 -  }
       
   116 - 
       
   117 +static size_t get_max_cmd_sz(void) {
       
   118 +  size_t res;
       
   119 +  size_t *bufsz = NULL;
       
   120 +
       
   121    bufsz = get_param_ptr(main_server->conf, "CommandBufferSize", FALSE);
       
   122    if (bufsz == NULL) {
       
   123 -    res = default_cmd_bufsz;
       
   124 -
       
   125 -  } else if (*bufsz <= 0) {
       
   126 -    pr_log_pri(PR_LOG_WARNING, "invalid CommandBufferSize size (%d) given, "
       
   127 -      "using default buffer size (%lu) instead", *bufsz,
       
   128 -      (unsigned long) default_cmd_bufsz);
       
   129 -    res = default_cmd_bufsz;
       
   130 -
       
   131 -  } else if (*bufsz + 1 > buflen) {
       
   132 -    pr_log_pri(PR_LOG_WARNING, "invalid CommandBufferSize size (%d) given, "
       
   133 -      "using default buffer size (%lu) instead", *bufsz,
       
   134 -      (unsigned long) default_cmd_bufsz);
       
   135 -    res = default_cmd_bufsz;
       
   136 +    res = PR_DEFAULT_CMD_BUFSZ;
       
   137  
       
   138    } else {
       
   139 -    pr_log_debug(DEBUG1, "setting CommandBufferSize to %d", *bufsz);
       
   140 -    res = (long) *bufsz;
       
   141 +    pr_log_debug(DEBUG1, "setting CommandBufferSize to %lu",
       
   142 +      (unsigned long) *bufsz);
       
   143 +    res = *bufsz;
       
   144    }
       
   145  
       
   146    return res;
       
   147 @@ -509,21 +488,29 @@ static long get_max_cmd_len(size_t buflen) {
       
   148  
       
   149  int pr_cmd_read(cmd_rec **res) {
       
   150    static long cmd_bufsz = -1;
       
   151 -  char buf[PR_DEFAULT_CMD_BUFSZ+1] = {'\0'};
       
   152 +  static char *cmd_buf = NULL;
       
   153    char *cp;
       
   154 -  size_t buflen;
       
   155 +  size_t cmd_buflen;
       
   156  
       
   157    if (res == NULL) {
       
   158      errno = EINVAL;
       
   159      return -1;
       
   160    }
       
   161  
       
   162 +  if (cmd_bufsz == -1) {
       
   163 +    cmd_bufsz = get_max_cmd_sz();
       
   164 +  }
       
   165 +
       
   166 +  if (cmd_buf == NULL) {
       
   167 +    cmd_buf = pcalloc(session.pool, cmd_bufsz + 1);
       
   168 +  }
       
   169 +
       
   170    while (TRUE) {
       
   171      pr_signals_handle();
       
   172  
       
   173 -    memset(buf, '\0', sizeof(buf));
       
   174 +    memset(cmd_buf, '\0', cmd_bufsz);
       
   175  
       
   176 -    if (pr_netio_telnet_gets(buf, sizeof(buf)-1, session.c->instrm,
       
   177 +    if (pr_netio_telnet_gets(cmd_buf, cmd_bufsz, session.c->instrm,
       
   178          session.c->outstrm) == NULL) {
       
   179  
       
   180        if (errno == E2BIG) {
       
   181 @@ -544,9 +531,6 @@ int pr_cmd_read(cmd_rec **res) {
       
   182      break;
       
   183    }
       
   184  
       
   185 -  if (cmd_bufsz == -1)
       
   186 -    cmd_bufsz = get_max_cmd_len(sizeof(buf));
       
   187 -
       
   188    /* This strlen(3) is guaranteed to terminate; the last byte of buf is
       
   189     * always NUL, since pr_netio_telnet_gets() is told that the buf size is
       
   190     * one byte less than it really is.
       
   191 @@ -554,26 +538,28 @@ int pr_cmd_read(cmd_rec **res) {
       
   192     * If the strlen(3) says that the length is less than the cmd_bufsz, then
       
   193     * there is no need to truncate the buffer by inserting a NUL.
       
   194     */
       
   195 -  buflen = strlen(buf);
       
   196 -  if (buflen > (cmd_bufsz - 1)) {
       
   197 +  cmd_buflen = strlen(cmd_buf);
       
   198 +  if (cmd_buflen > cmd_bufsz) {
       
   199      pr_log_debug(DEBUG0, "truncating incoming command length (%lu bytes) to "
       
   200        "CommandBufferSize %lu; use the CommandBufferSize directive to increase "
       
   201 -      "the allowed command length", (unsigned long) buflen,
       
   202 +      "the allowed command length", (unsigned long) cmd_buflen,
       
   203        (unsigned long) cmd_bufsz);
       
   204 -    buf[cmd_bufsz - 1] = '\0';
       
   205 +    cmd_buf[cmd_bufsz-1] = '\0';
       
   206    }
       
   207  
       
   208 -  if (buflen &&
       
   209 -      (buf[buflen-1] == '\n' || buf[buflen-1] == '\r')) {
       
   210 -    buf[buflen-1] = '\0';
       
   211 -    buflen--;
       
   212 +  if (cmd_buflen &&
       
   213 +      (cmd_buf[cmd_buflen-1] == '\n' || cmd_buf[cmd_buflen-1] == '\r')) {
       
   214 +    cmd_buf[cmd_buflen-1] = '\0';
       
   215 +    cmd_buflen--;
       
   216  
       
   217 -    if (buflen &&
       
   218 -        (buf[buflen-1] == '\n' || buf[buflen-1] =='\r'))
       
   219 -      buf[buflen-1] = '\0';
       
   220 +    if (cmd_buflen &&
       
   221 +        (cmd_buf[cmd_buflen-1] == '\n' || cmd_buf[cmd_buflen-1] =='\r')) {
       
   222 +      cmd_buf[cmd_buflen-1] = '\0';
       
   223 +      cmd_buflen--;
       
   224 +    }
       
   225    }
       
   226  
       
   227 -  cp = buf;
       
   228 +  cp = cmd_buf;
       
   229    if (*cp == '\r')
       
   230      cp++;
       
   231  
       
   232 @@ -587,11 +573,11 @@ int pr_cmd_read(cmd_rec **res) {
       
   233       * command handlers themselves, via cmd->arg.  This small hack
       
   234       * reduces the burden on SITE module developers, however.
       
   235       */
       
   236 -    if (strncasecmp(cp, C_SITE, 4) == 0)
       
   237 +    if (strncasecmp(cp, C_SITE, 4) == 0) {
       
   238        flags |= PR_STR_FL_PRESERVE_WHITESPACE;
       
   239 +    }
       
   240  
       
   241      cmd = make_ftp_cmd(session.pool, cp, flags);
       
   242 -
       
   243      if (cmd) {
       
   244        *res = cmd;
       
   245      } 
       
   246 diff --git a/src/str.c b/src/str.c
       
   247 index d243a17..4f327bf 100644
       
   248 --- a/src/str.c
       
   249 +++ b/src/str.c
       
   250 @@ -367,6 +367,81 @@ char *pr_str_strip_end(char *s, char *ch) {
       
   251    return s;
       
   252  }
       
   253  
       
   254 +int pr_str_get_nbytes(const char *str, const char *units, off_t *nbytes) {
       
   255 +  off_t sz;
       
   256 +  char *ptr = NULL;
       
   257 +  float factor = 0.0;
       
   258 +
       
   259 +  if (str == NULL) {
       
   260 +    errno = EINVAL;
       
   261 +    return -1;
       
   262 +  }
       
   263 +
       
   264 +  /* No negative numbers. */
       
   265 +  if (*str == '-') {
       
   266 +    errno = EINVAL;
       
   267 +    return -1;
       
   268 +  }
       
   269 +
       
   270 +  if (units == NULL ||
       
   271 +      *units == '\0') {
       
   272 +    factor = 1.0;
       
   273 +
       
   274 +  } else if (strncasecmp(units, "KB", 3) == 0) {
       
   275 +    factor = 1024.0;
       
   276 +
       
   277 +  } else if (strncasecmp(units, "MB", 3) == 0) {
       
   278 +    factor = 1024.0 * 1024.0;
       
   279 +
       
   280 +  } else if (strncasecmp(units, "GB", 3) == 0) {
       
   281 +    factor = 1024.0 * 1024.0 * 1024.0;
       
   282 +
       
   283 +  } else if (strncasecmp(units, "TB", 3) == 0) {
       
   284 +    factor = 1024.0 * 1024.0 * 1024.0 * 1024.0;
       
   285 +
       
   286 +  } else if (strncasecmp(units, "B", 2) == 0) {
       
   287 +    factor = 1.0;
       
   288 +
       
   289 +  } else {
       
   290 +    errno = EINVAL;
       
   291 +    return -1;
       
   292 +  }
       
   293 +
       
   294 +  errno = 0;
       
   295 +
       
   296 +#ifdef HAVE_STRTOULL
       
   297 +  sz = strtoull(str, &ptr, 10);
       
   298 +#else
       
   299 +  sz = strtoul(str, &ptr, 10);
       
   300 +#endif /* !HAVE_STRTOULL */
       
   301 +
       
   302 +  if (errno == ERANGE) {
       
   303 +    return -1;
       
   304 +  }
       
   305 +
       
   306 +  if (ptr != NULL && *ptr) {
       
   307 +    /* Error parsing the given string */
       
   308 +    errno = EINVAL;
       
   309 +    return -1;
       
   310 +  }
       
   311 +
       
   312 +  /* Don't bother applying the factor if the result will overflow the result. */
       
   313 +#ifdef ULLONG_MAX
       
   314 +  if (sz > (ULLONG_MAX / factor)) {
       
   315 +#else
       
   316 +  if (sz > (ULONG_MAX / factor)) {
       
   317 +#endif /* !ULLONG_MAX */
       
   318 +    errno = ERANGE;
       
   319 +    return -1;
       
   320 +  }
       
   321 +
       
   322 +  if (nbytes != NULL) {
       
   323 +    *nbytes = (off_t) (sz * factor);
       
   324 +  }
       
   325 +
       
   326 +  return 0;
       
   327 +}
       
   328 +
       
   329  char *pr_str_get_word(char **cp, int flags) {
       
   330    char *res, *dst;
       
   331    char quote_mode = 0;
       
   332 diff --git a/tests/t/lib/ProFTPD/Tests/Config/CommandBufferSize.pm b/tests/t/lib/ProFTPD/Tests/Config/CommandBufferSize.pm
       
   333 index ed4672a..a57c898 100644
       
   334 --- a/tests/t/lib/ProFTPD/Tests/Config/CommandBufferSize.pm
       
   335 +++ b/tests/t/lib/ProFTPD/Tests/Config/CommandBufferSize.pm
       
   336 @@ -94,6 +94,8 @@ sub cmdbuffersz_small {
       
   337      die("Can't open $test_path: $!");
       
   338    }
       
   339  
       
   340 +  my $idle_timeout = 3;
       
   341 +
       
   342    my $config = {
       
   343      PidFile => $pid_file,
       
   344      ScoreboardFile => $scoreboard_file,
       
   345 @@ -103,6 +105,7 @@ sub cmdbuffersz_small {
       
   346      AuthGroupFile => $auth_group_file,
       
   347  
       
   348      CommandBufferSize => $cmdbufsz,
       
   349 +    TimeoutIdle => $idle_timeout,
       
   350  
       
   351      IfModules => {
       
   352        'mod_delay.c' => {
       
   353 @@ -128,44 +131,16 @@ sub cmdbuffersz_small {
       
   354    defined(my $pid = fork()) or die("Can't fork: $!");
       
   355    if ($pid) {
       
   356      eval {
       
   357 -      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
       
   358 +      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1);
       
   359        $client->login($user, $passwd);
       
   360  
       
   361 -      my $conn = $client->list_raw($test_file);
       
   362 -      unless ($conn) {
       
   363 -        die("Failed to LIST $test_file: " . $client->response_code() . " " .
       
   364 -          $client->response_msg());
       
   365 +      # Since our filename is longer than the CommandBufferSize, proftpd
       
   366 +      # should simply ignore this.  It will fail because of the idle timeout
       
   367 +      # first.
       
   368 +      eval { $client->stat($test_file) };
       
   369 +      unless ($@) {
       
   370 +        die("STAT command succeeded unexpectedly");
       
   371        }
       
   372 -
       
   373 -      my $buf;
       
   374 -      $conn->read($buf, 8192, 30);
       
   375 -      eval { $conn->close() };
       
   376 -
       
   377 -      my $resp_code = $client->response_code();
       
   378 -      my $resp_msg = $client->response_msg();
       
   379 -
       
   380 -      # CommandBufferSize works by truncating any input longer than the
       
   381 -      # configured length.  (It should arguably reject such longer input,
       
   382 -      # but that is a different consideration.)
       
   383 -      #
       
   384 -      # Since our file name is longer than the CommandBufferSize, it means
       
   385 -      # the path will be truncated, and the LIST should return 450.
       
   386 -
       
   387 -      my $expected;
       
   388 -
       
   389 -      $expected = 450;
       
   390 -      $self->assert($expected == $resp_code,
       
   391 -        test_msg("Expected $expected, got $resp_code"));
       
   392 -
       
   393 -      # This length is CommandBufferSize - "LIST"(4) - " "(1) - 1 for
       
   394 -      # the NUL reserved in the code.  Thus CommandBufferSize - 6.
       
   395 -      my $truncated_name = ("A" x ($cmdbufsz - 6));
       
   396 -
       
   397 -      $expected = "$truncated_name: No such file or directory";
       
   398 -      $self->assert($expected eq $resp_msg,
       
   399 -        test_msg("Expected '$expected', got '$resp_msg'"));
       
   400 -
       
   401 -      $client->quit();
       
   402      };
       
   403  
       
   404      if ($@) {
       
   405 @@ -176,7 +151,7 @@ sub cmdbuffersz_small {
       
   406      $wfh->flush();
       
   407  
       
   408    } else {
       
   409 -    eval { server_wait($config_file, $rfh) };
       
   410 +    eval { server_wait($config_file, $rfh, 15) };
       
   411      if ($@) {
       
   412        warn($@);
       
   413        exit 1;
       
   414