This patch is a backport of proftpd Bug#4159 from 1.3.6.rc2 to 1.3.5b. It should
be removed as soon as we sync with stable 1.3.6 release.
This patch is hand crafted combination of following two commits. No test files
are included.
Fix regression, caused by change for Bug#4159 with the ASCII translation handling of LIST/NLST output
https://github.com/proftpd/proftpd/commit/3892250b429661dd8d166a55230f9a0da923a26b
Bug#4159: Support ability to disable ASCII translation transparently to FTP clients.
https://github.com/proftpd/proftpd/commit/68229c0bc839260a43ac1c9b9f7ed68e39aa0f1a
http://bugs.proftpd.org/show_bug.cgi?id=4159
diff --git a/doc/modules/mod_xfer.html b/doc/modules/mod_xfer.html
index 19179fa..52b02c0 100644
--- a/doc/modules/mod_xfer.html
+++ b/doc/modules/mod_xfer.html
@@ -33,6 +33,7 @@ file transfers.
<li><a href="#StoreUniquePrefix">StoreUniquePrefix</a>
<li><a href="#TimeoutNoTransfer">TimeoutNoTransfer</a>
<li><a href="#TimeoutStalled">TimeoutStalled</a>
+ <li><a href="#TransferOptions">TransferOptions</a>
<li><a href="#TransferPriority">TransferPriority</a>
<li><a href="#TransferRate">TransferRate</a>
<li><a href="#UseSendfile">UseSendfile</a>
@@ -330,6 +331,36 @@ The maximum allowed <em>seconds</em> value is 65535 (108 minutes).
<p>
<hr>
+<h2><a name="TransferOptions">TransferOptions</a></h2>
+<strong>Syntax:</strong> TransferOptions <em>opt1 ...</em><br>
+<strong>Default:</strong> None<br>
+<strong>Context:</strong> server config, <code>&lt;VirtualHost&gt;</code>, <code>&lt;Global&gt;</code><br>
+<strong>Module:</strong> mod_xfer<br>
+<strong>Compatibility:</strong> 1.3.5b and later
+
+<p>
+The <code>TransferOptions</code> directive to configure various optional data
+transfer behaviors.
+
+<p>
+The currently implemented options are:
+<ul>
+ <li><code>IgnoreASCII</code><br>
+ <p>
+ This option causes proftpd to silently ignore any client requests to
+ perform ASCII translations via the <code>TYPE</code> command. That is,
+ FTP clients can request ASCII translations, and proftpd will respond
+ as the client expects, but will <b>not</b> actually perform the translation
+ for either uploads <i>or</i> downloads. This behavior can be useful in
+ circumstances involving older/mainframe clients and EBCDIC files.
+
+ <p>
+ <b>Note</b> that this option first appeared in
+ <code>proftpd-1.3.5b</code>.
+</ul>
+
+<p>
+<hr>
<h2><a name="TransferPriority">TransferPriority</a></h2>
<strong>Syntax:</strong> TransferPriority <em>cmd-list "low"|"medium"|"high"|number</em><br>
<strong>Default:</strong> None<br>
diff --git a/include/data.h b/include/data.h
index 4eaf5d9..fd5f9d9 100644
--- a/include/data.h
+++ b/include/data.h
@@ -31,6 +31,11 @@
#ifndef PR_DATACONN_H
#define PR_DATACONN_H
+/* Toggles whether to actually perform ASCII translation during the data
+ * transfer.
+ */
+int pr_data_ignore_ascii(int);
+
void pr_data_init(char *, int);
void pr_data_cleanup(void);
int pr_data_open(char *, char *, int, off_t);
@@ -40,6 +45,7 @@ int pr_data_xfer(char *, size_t);
void pr_data_reset(void);
void pr_data_set_linger(long);
+
/* Clear the session.xfer.p pool, if present, and reset any associated
* state.
*/
diff --git a/modules/mod_xfer.c b/modules/mod_xfer.c
index de5c52a..4b75034 100644
--- a/modules/mod_xfer.c
+++ b/modules/mod_xfer.c
@@ -61,6 +61,7 @@ static int xfer_check_limit(cmd_rec *);
/* TransferOptions */
#define PR_XFER_OPT_HANDLE_ALLO 0x0001
+#define PR_XFER_OPT_IGNORE_ASCII 0x0002
static unsigned long xfer_opts = PR_XFER_OPT_HANDLE_ALLO;
/* Transfer priority */
@@ -1923,7 +1924,8 @@ MODRET xfer_rest(cmd_rec *cmd) {
* clients.
*/
if ((session.sf_flags & SF_ASCII) &&
- pos != 0) {
+ pos != 0 &&
+ !(xfer_opts & PR_XFER_OPT_IGNORE_ASCII)) {
pr_log_debug(DEBUG5, "%s not allowed in ASCII mode", cmd->argv[0]);
pr_response_add_err(R_501,
_("%s: Resuming transfers not allowed in ASCII mode"), cmd->argv[0]);
@@ -2587,6 +2589,26 @@ MODRET xfer_post_pass(cmd_rec *cmd) {
*/
}
+ /*
+ * Check for TransferOptions
+ */
+ c = find_config(main_server->conf, CONF_PARAM, "TransferOptions", FALSE);
+ while (c != NULL) {
+ unsigned long opts = 0;
+
+ pr_signals_handle();
+
+ opts = *((unsigned long *) c->argv[0]);
+ xfer_opts |= opts;
+
+ c = find_config_next(c, c->next, CONF_PARAM, "TransferOptions", FALSE);
+ }
+
+ if (xfer_opts & PR_XFER_OPT_IGNORE_ASCII) {
+ pr_log_debug(DEBUG8, "Ignoring ASCII translation for this session");
+ pr_data_ignore_ascii(TRUE);
+ }
+
/* Check for TransferPriority. */
c = find_config(TOPLEVEL_CONF, CONF_PARAM, "TransferPriority", FALSE);
if (c) {
@@ -2999,6 +3021,36 @@ MODRET set_timeoutstalled(cmd_rec *cmd) {
return PR_HANDLED(cmd);
}
+/* usage: TransferOptions opt1 opt2 ... */
+MODRET set_transferoptions(cmd_rec *cmd) {
+ config_rec *c = NULL;
+ register unsigned int i = 0;
+ unsigned long opts = 0UL;
+
+ if (cmd->argc-1 == 0) {
+ CONF_ERROR(cmd, "wrong number of parameters");
+ }
+
+ CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
+
+ c = add_config_param(cmd->argv[0], 1, NULL);
+
+ for (i = 1; i < cmd->argc; i++) {
+ if (strcasecmp(cmd->argv[i], "IgnoreASCII") == 0) {
+ opts |= PR_XFER_OPT_IGNORE_ASCII;
+
+ } else {
+ CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown TransferOption '",
+ cmd->argv[i], "'", NULL));
+ }
+ }
+
+ c->argv[0] = pcalloc(c->pool, sizeof(unsigned long));
+ *((unsigned long *) c->argv[0]) = opts;
+
+ return PR_HANDLED(cmd);
+}
+
/* usage: TransferPriority cmds "low"|"medium"|"high"|number
*/
MODRET set_transferpriority(cmd_rec *cmd) {
@@ -3459,6 +3511,7 @@ static conftable xfer_conftab[] = {
{ "StoreUniquePrefix", set_storeuniqueprefix, NULL },
{ "TimeoutNoTransfer", set_timeoutnoxfer, NULL },
{ "TimeoutStalled", set_timeoutstalled, NULL },
+ { "TransferOptions", set_transferoptions, NULL },
{ "TransferPriority", set_transferpriority, NULL },
{ "TransferRate", set_transferrate, NULL },
{ "UseSendfile", set_usesendfile, NULL },
diff --git a/src/data.c b/src/data.c
index 5136f5f..0c45c77 100644
--- a/src/data.c
+++ b/src/data.c
@@ -40,6 +40,9 @@
static const char *trace_channel = "data";
+#define PR_DATA_OPT_IGNORE_ASCII 0x0001
+static unsigned long data_opts = 0UL;
+
/* local macro */
#define MODE_STRING (session.sf_flags & (SF_ASCII|SF_ASCII_OVERRIDE) ? \
@@ -580,6 +583,33 @@ void pr_data_reset(void) {
session.sf_flags &= (SF_ALL^(SF_ABORT|SF_POST_ABORT|SF_XFER|SF_PASSIVE|SF_ASCII_OVERRIDE|SF_EPSV_ALL));
}
+int pr_data_ignore_ascii(int ignore_ascii) {
+ int res;
+
+ if (ignore_ascii != TRUE &&
+ ignore_ascii != FALSE) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (data_opts & PR_DATA_OPT_IGNORE_ASCII) {
+ if (!ignore_ascii) {
+ data_opts &= ~PR_DATA_OPT_IGNORE_ASCII;
+ }
+
+ res = TRUE;
+
+ } else {
+ if (ignore_ascii) {
+ data_opts |= PR_DATA_OPT_IGNORE_ASCII;
+ }
+
+ res = FALSE;
+ }
+
+ return res;
+}
+
void pr_data_init(char *filename, int direction) {
if (session.xfer.p == NULL) {
data_new_xfer(filename, direction);
@@ -1155,7 +1185,15 @@ int pr_data_xfer(char *cl_buf, size_t cl_size) {
char *buf = session.xfer.buf;
pr_buffer_t *pbuf;
- if (session.sf_flags & (SF_ASCII|SF_ASCII_OVERRIDE)) {
+ /* We use ASCII translation if:
+ *
+ * - SF_ASCII_OVERRIDE session flag is set (e.g. for LIST/NLST)
+ * - SF_ASCII session flag is set, AND IGNORE_ASCII data opt NOT set
+ */
+ if ((session.sf_flags & SF_ASCII_OVERRIDE) ||
+ ((session.sf_flags & SF_ASCII) &&
+ !(data_opts & PR_DATA_OPT_IGNORE_ASCII))) {
+
int adjlen, buflen;
do {
@@ -1330,8 +1368,14 @@ int pr_data_xfer(char *cl_buf, size_t cl_size) {
/* Fill up our internal buffer. */
memcpy(session.xfer.buf, cl_buf, buflen);
-
- if (session.sf_flags & (SF_ASCII|SF_ASCII_OVERRIDE)) {
+ /* We use ASCII translation if:
+ *
+ * - SF_ASCII_OVERRIDE session flag is set (e.g. for LIST/NLST)
+ * - SF_ASCII session flag is set, AND IGNORE_ASCII data opt NOT set
+ */
+ if ((session.sf_flags & SF_ASCII_OVERRIDE) ||
+ ((session.sf_flags & SF_ASCII) &&
+ !(data_opts & PR_DATA_OPT_IGNORE_ASCII))) {
/* Scan the internal buffer, looking for LFs with no preceding CRs.
* Add CRs (and expand the internal buffer) as necessary. xferbuflen
* will be adjusted so that it contains the length of data in