--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/components/proftpd/patches/15825705.patch Wed Jan 29 09:13:43 2014 -0800
@@ -0,0 +1,414 @@
+http://bugs.proftpd.org/show_bug.cgi?id=4014
+
+I addition, this patch backports the pr_str_get_nbytes() function.
+
+diff --git a/include/options.h b/include/options.h
+index 96fa35d..0b29bf7 100644
+--- a/include/options.h
++++ b/include/options.h
+@@ -110,6 +110,13 @@
+ # define PR_TUNABLE_XFER_BUFFER_SIZE PR_TUNABLE_BUFFER_SIZE
+ #endif
+
++/* Maximum FTP command size. For details on this size of 512KB, see
++ * the Bug#4014 discussion.
++ */
++#ifndef PR_TUNABLE_CMD_BUFFER_SIZE
++# define PR_TUNABLE_CMD_BUFFER_SIZE (512 * 1024)
++#endif
++
+ /* Maximum path length. GNU HURD (and some others) do not define
+ * MAXPATHLEN. POSIX' PATH_MAX is mandated to be at least 256
+ * (according to some), so 1K, in the absense of MAXPATHLEN, should be
+diff --git a/include/str.h b/include/str.h
+index b4fed7c..1cf8724 100644
+--- a/include/str.h
++++ b/include/str.h
+@@ -39,6 +39,7 @@ char *pstrndup(pool *, const char *, size_t);
+
+ char *pr_str_strip(pool *, char *);
+ char *pr_str_strip_end(char *, char *);
++int pr_str_get_nbytes(const char *, const char *, off_t *);
+ char *pr_str_get_word(char **, int);
+
+ #define PR_STR_FL_PRESERVE_COMMENTS 0x0001
+diff --git a/modules/mod_core.c b/modules/mod_core.c
+index 18a47c2..922f4d1 100644
+--- a/modules/mod_core.c
++++ b/modules/mod_core.c
+@@ -2240,18 +2240,44 @@ MODRET set_allowforeignaddress(cmd_rec *cmd) {
+ }
+
+ MODRET set_commandbuffersize(cmd_rec *cmd) {
+- int size = 0;
++ int res;
++ size_t size = 0;
++ off_t nbytes = 0;
+ config_rec *c = NULL;
++ const char *units = NULL;
++
++ if (cmd->argc < 2 || cmd->argc > 3) {
++ CONF_ERROR(cmd, "wrong number of parameters")
++ }
+
+- CHECK_ARGS(cmd, 1);
+ CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
+
+- /* NOTE: need to add checks for maximum possible sizes, negative sizes. */
+- size = atoi(cmd->argv[1]);
++ if (cmd->argc == 3) {
++ units = cmd->argv[2];
++ }
++
++ if (pr_str_get_nbytes(cmd->argv[1], units, &nbytes) < 0) {
++ CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to parse: ",
++ cmd->argv[1], " ", units, ": ", strerror(errno), NULL));
++ }
++
++ if (nbytes > PR_TUNABLE_CMD_BUFFER_SIZE) {
++ char max[1024];
++
++ snprintf(max, sizeof(max)-1, "%lu", (unsigned long)
++ PR_TUNABLE_CMD_BUFFER_SIZE);
++ max[sizeof(max)-1] = '\0';
++
++ CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "size ", cmd->argv[1], units,
++ "exceeds max size ", max, NULL));
++ }
++
++ /* Possible truncation here, but only for an absurdly large size. */
++ size = (size_t) nbytes;
+
+ c = add_config_param(cmd->argv[0], 1, NULL);
+- c->argv[0] = pcalloc(c->pool, sizeof(int));
+- *((int *) c->argv[0]) = size;
++ c->argv[0] = pcalloc(c->pool, sizeof(size_t));
++ *((size_t *) c->argv[0]) = size;
+
+ return PR_HANDLED(cmd);
+ }
+diff --git a/src/main.c b/src/main.c
+index 3e6d637..660e14b 100644
+--- a/src/main.c
++++ b/src/main.c
+@@ -466,42 +466,21 @@ static int _dispatch(cmd_rec *cmd, int cmd_type, int validate, char *match) {
+ return success;
+ }
+
+-/* Returns the appropriate maximum buffer length to use for FTP commands
+- * from the client, taking the CommandBufferSize directive into account.
++/* Returns the appropriate maximum buffer size to use for FTP commands
++ * from the client.
+ */
+-static long get_max_cmd_len(size_t buflen) {
+- long res;
+- int *bufsz = NULL;
+- size_t default_cmd_bufsz;
+-
+- /* It's possible for the admin to select a PR_TUNABLE_BUFFER_SIZE which
+- * is smaller than PR_DEFAULT_CMD_BUFSZ. We need to handle such cases
+- * properly.
+- */
+- default_cmd_bufsz = PR_DEFAULT_CMD_BUFSZ;
+- if (default_cmd_bufsz > buflen) {
+- default_cmd_bufsz = buflen;
+- }
+-
++static size_t get_max_cmd_sz(void) {
++ size_t res;
++ size_t *bufsz = NULL;
++
+ bufsz = get_param_ptr(main_server->conf, "CommandBufferSize", FALSE);
+ if (bufsz == NULL) {
+- res = default_cmd_bufsz;
+-
+- } else if (*bufsz <= 0) {
+- pr_log_pri(PR_LOG_WARNING, "invalid CommandBufferSize size (%d) given, "
+- "using default buffer size (%lu) instead", *bufsz,
+- (unsigned long) default_cmd_bufsz);
+- res = default_cmd_bufsz;
+-
+- } else if (*bufsz + 1 > buflen) {
+- pr_log_pri(PR_LOG_WARNING, "invalid CommandBufferSize size (%d) given, "
+- "using default buffer size (%lu) instead", *bufsz,
+- (unsigned long) default_cmd_bufsz);
+- res = default_cmd_bufsz;
++ res = PR_DEFAULT_CMD_BUFSZ;
+
+ } else {
+- pr_log_debug(DEBUG1, "setting CommandBufferSize to %d", *bufsz);
+- res = (long) *bufsz;
++ pr_log_debug(DEBUG1, "setting CommandBufferSize to %lu",
++ (unsigned long) *bufsz);
++ res = *bufsz;
+ }
+
+ return res;
+@@ -509,21 +488,29 @@ static long get_max_cmd_len(size_t buflen) {
+
+ int pr_cmd_read(cmd_rec **res) {
+ static long cmd_bufsz = -1;
+- char buf[PR_DEFAULT_CMD_BUFSZ+1] = {'\0'};
++ static char *cmd_buf = NULL;
+ char *cp;
+- size_t buflen;
++ size_t cmd_buflen;
+
+ if (res == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
++ if (cmd_bufsz == -1) {
++ cmd_bufsz = get_max_cmd_sz();
++ }
++
++ if (cmd_buf == NULL) {
++ cmd_buf = pcalloc(session.pool, cmd_bufsz + 1);
++ }
++
+ while (TRUE) {
+ pr_signals_handle();
+
+- memset(buf, '\0', sizeof(buf));
++ memset(cmd_buf, '\0', cmd_bufsz);
+
+- if (pr_netio_telnet_gets(buf, sizeof(buf)-1, session.c->instrm,
++ if (pr_netio_telnet_gets(cmd_buf, cmd_bufsz, session.c->instrm,
+ session.c->outstrm) == NULL) {
+
+ if (errno == E2BIG) {
+@@ -544,9 +531,6 @@ int pr_cmd_read(cmd_rec **res) {
+ break;
+ }
+
+- if (cmd_bufsz == -1)
+- cmd_bufsz = get_max_cmd_len(sizeof(buf));
+-
+ /* This strlen(3) is guaranteed to terminate; the last byte of buf is
+ * always NUL, since pr_netio_telnet_gets() is told that the buf size is
+ * one byte less than it really is.
+@@ -554,26 +538,28 @@ int pr_cmd_read(cmd_rec **res) {
+ * If the strlen(3) says that the length is less than the cmd_bufsz, then
+ * there is no need to truncate the buffer by inserting a NUL.
+ */
+- buflen = strlen(buf);
+- if (buflen > (cmd_bufsz - 1)) {
++ cmd_buflen = strlen(cmd_buf);
++ if (cmd_buflen > cmd_bufsz) {
+ pr_log_debug(DEBUG0, "truncating incoming command length (%lu bytes) to "
+ "CommandBufferSize %lu; use the CommandBufferSize directive to increase "
+- "the allowed command length", (unsigned long) buflen,
++ "the allowed command length", (unsigned long) cmd_buflen,
+ (unsigned long) cmd_bufsz);
+- buf[cmd_bufsz - 1] = '\0';
++ cmd_buf[cmd_bufsz-1] = '\0';
+ }
+
+- if (buflen &&
+- (buf[buflen-1] == '\n' || buf[buflen-1] == '\r')) {
+- buf[buflen-1] = '\0';
+- buflen--;
++ if (cmd_buflen &&
++ (cmd_buf[cmd_buflen-1] == '\n' || cmd_buf[cmd_buflen-1] == '\r')) {
++ cmd_buf[cmd_buflen-1] = '\0';
++ cmd_buflen--;
+
+- if (buflen &&
+- (buf[buflen-1] == '\n' || buf[buflen-1] =='\r'))
+- buf[buflen-1] = '\0';
++ if (cmd_buflen &&
++ (cmd_buf[cmd_buflen-1] == '\n' || cmd_buf[cmd_buflen-1] =='\r')) {
++ cmd_buf[cmd_buflen-1] = '\0';
++ cmd_buflen--;
++ }
+ }
+
+- cp = buf;
++ cp = cmd_buf;
+ if (*cp == '\r')
+ cp++;
+
+@@ -587,11 +573,11 @@ int pr_cmd_read(cmd_rec **res) {
+ * command handlers themselves, via cmd->arg. This small hack
+ * reduces the burden on SITE module developers, however.
+ */
+- if (strncasecmp(cp, C_SITE, 4) == 0)
++ if (strncasecmp(cp, C_SITE, 4) == 0) {
+ flags |= PR_STR_FL_PRESERVE_WHITESPACE;
++ }
+
+ cmd = make_ftp_cmd(session.pool, cp, flags);
+-
+ if (cmd) {
+ *res = cmd;
+ }
+diff --git a/src/str.c b/src/str.c
+index d243a17..4f327bf 100644
+--- a/src/str.c
++++ b/src/str.c
+@@ -367,6 +367,81 @@ char *pr_str_strip_end(char *s, char *ch) {
+ return s;
+ }
+
++int pr_str_get_nbytes(const char *str, const char *units, off_t *nbytes) {
++ off_t sz;
++ char *ptr = NULL;
++ float factor = 0.0;
++
++ if (str == NULL) {
++ errno = EINVAL;
++ return -1;
++ }
++
++ /* No negative numbers. */
++ if (*str == '-') {
++ errno = EINVAL;
++ return -1;
++ }
++
++ if (units == NULL ||
++ *units == '\0') {
++ factor = 1.0;
++
++ } else if (strncasecmp(units, "KB", 3) == 0) {
++ factor = 1024.0;
++
++ } else if (strncasecmp(units, "MB", 3) == 0) {
++ factor = 1024.0 * 1024.0;
++
++ } else if (strncasecmp(units, "GB", 3) == 0) {
++ factor = 1024.0 * 1024.0 * 1024.0;
++
++ } else if (strncasecmp(units, "TB", 3) == 0) {
++ factor = 1024.0 * 1024.0 * 1024.0 * 1024.0;
++
++ } else if (strncasecmp(units, "B", 2) == 0) {
++ factor = 1.0;
++
++ } else {
++ errno = EINVAL;
++ return -1;
++ }
++
++ errno = 0;
++
++#ifdef HAVE_STRTOULL
++ sz = strtoull(str, &ptr, 10);
++#else
++ sz = strtoul(str, &ptr, 10);
++#endif /* !HAVE_STRTOULL */
++
++ if (errno == ERANGE) {
++ return -1;
++ }
++
++ if (ptr != NULL && *ptr) {
++ /* Error parsing the given string */
++ errno = EINVAL;
++ return -1;
++ }
++
++ /* Don't bother applying the factor if the result will overflow the result. */
++#ifdef ULLONG_MAX
++ if (sz > (ULLONG_MAX / factor)) {
++#else
++ if (sz > (ULONG_MAX / factor)) {
++#endif /* !ULLONG_MAX */
++ errno = ERANGE;
++ return -1;
++ }
++
++ if (nbytes != NULL) {
++ *nbytes = (off_t) (sz * factor);
++ }
++
++ return 0;
++}
++
+ char *pr_str_get_word(char **cp, int flags) {
+ char *res, *dst;
+ char quote_mode = 0;
+diff --git a/tests/t/lib/ProFTPD/Tests/Config/CommandBufferSize.pm b/tests/t/lib/ProFTPD/Tests/Config/CommandBufferSize.pm
+index ed4672a..a57c898 100644
+--- a/tests/t/lib/ProFTPD/Tests/Config/CommandBufferSize.pm
++++ b/tests/t/lib/ProFTPD/Tests/Config/CommandBufferSize.pm
+@@ -94,6 +94,8 @@ sub cmdbuffersz_small {
+ die("Can't open $test_path: $!");
+ }
+
++ my $idle_timeout = 3;
++
+ my $config = {
+ PidFile => $pid_file,
+ ScoreboardFile => $scoreboard_file,
+@@ -103,6 +105,7 @@ sub cmdbuffersz_small {
+ AuthGroupFile => $auth_group_file,
+
+ CommandBufferSize => $cmdbufsz,
++ TimeoutIdle => $idle_timeout,
+
+ IfModules => {
+ 'mod_delay.c' => {
+@@ -128,44 +131,16 @@ sub cmdbuffersz_small {
+ defined(my $pid = fork()) or die("Can't fork: $!");
+ if ($pid) {
+ eval {
+- my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
++ my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1);
+ $client->login($user, $passwd);
+
+- my $conn = $client->list_raw($test_file);
+- unless ($conn) {
+- die("Failed to LIST $test_file: " . $client->response_code() . " " .
+- $client->response_msg());
++ # Since our filename is longer than the CommandBufferSize, proftpd
++ # should simply ignore this. It will fail because of the idle timeout
++ # first.
++ eval { $client->stat($test_file) };
++ unless ($@) {
++ die("STAT command succeeded unexpectedly");
+ }
+-
+- my $buf;
+- $conn->read($buf, 8192, 30);
+- eval { $conn->close() };
+-
+- my $resp_code = $client->response_code();
+- my $resp_msg = $client->response_msg();
+-
+- # CommandBufferSize works by truncating any input longer than the
+- # configured length. (It should arguably reject such longer input,
+- # but that is a different consideration.)
+- #
+- # Since our file name is longer than the CommandBufferSize, it means
+- # the path will be truncated, and the LIST should return 450.
+-
+- my $expected;
+-
+- $expected = 450;
+- $self->assert($expected == $resp_code,
+- test_msg("Expected $expected, got $resp_code"));
+-
+- # This length is CommandBufferSize - "LIST"(4) - " "(1) - 1 for
+- # the NUL reserved in the code. Thus CommandBufferSize - 6.
+- my $truncated_name = ("A" x ($cmdbufsz - 6));
+-
+- $expected = "$truncated_name: No such file or directory";
+- $self->assert($expected eq $resp_msg,
+- test_msg("Expected '$expected', got '$resp_msg'"));
+-
+- $client->quit();
+ };
+
+ if ($@) {
+@@ -176,7 +151,7 @@ sub cmdbuffersz_small {
+ $wfh->flush();
+
+ } else {
+- eval { server_wait($config_file, $rfh) };
++ eval { server_wait($config_file, $rfh, 15) };
+ if ($@) {
+ warn($@);
+ exit 1;
+