Bug #66407: netsec_readline and inc and long lines.
Enhance netsec_readline and callers in popsbr.c and inc.c so that
"inc" can handle long message lines over POP. (Long encoded lines are
sent by some mailers, in violation of the MIME standards.)
The test-pop test from the previous segment of this patch now passes,
showing that this patch adds the new functionality.
diff --git a/h/netsec.h b/h/netsec.h
index 6c69c82c..d375b93a 100644
--- a/h/netsec.h
+++ b/h/netsec.h
@@ -153,6 +153,37 @@ void netsec_set_timeout(netsec_context *ns_context, int timeout);
char *netsec_readline(netsec_context *ns_context, size_t *length,
char **errstr);
+/*
+ * Read a "line" or part of one from the network.
+ * Similar to netsec_readline, except that it may return an incomplete line if
+ * the line is too long to buffer.
+ *
+ * If partial_line_status is passed as a non-NULL pointer, an incomplete line
+ * may be returned. (If partial_line_status is NULL, an error is returned for
+ * an overly long line, and netsec_readline_or_part behaves the same as
+ * netsec_readline.) A partial line that ends before the CR/LF will never have
+ * fewer than 2 characters, so that dot un-stuffing can still be implemented
+ * with the result.
+ *
+ * Arguments are the same as for netsec_readline, plus:
+ *
+ * partial_line_status - What part of a complete line is returned.
+ * If the pointer is non-NULL, then on a non-error return, it is set to any
+ * of the following bits ORed together:
+ *
+ * NETSEC_LINE_START - the returned text includes the beginning of the line
+ * NETSEC_LINE_END - the returned text includes the end of the line
+ *
+ * Thus for a complete line, *partial_line_status would be set to
+ * (NETSEC_LINE_START | NETSEC_LINE_END).
+ *
+ * Returns pointer to a static string, or NULL on error.
+ */
+char *netsec_readline_or_part(netsec_context *ns_context, size_t *length,
+ char **errstr, int *partial_line_status);
+#define NETSEC_LINE_START 1
+#define NETSEC_LINE_END 2
+
/*
* Read bytes from the network.
*
diff --git a/sbr/netsec.c b/sbr/netsec.c
index bf0ff9cf..08cc5059 100644
--- a/sbr/netsec.c
+++ b/sbr/netsec.c
@@ -70,6 +70,7 @@ struct _netsec_context {
unsigned char *ns_inptr; /* Our read buffer input pointer */
unsigned int ns_inbuflen; /* Length of data in input buffer */
unsigned int ns_inbufsize; /* Size of input buffer */
+ char ns_readline_last_char; /* character following the last returned */
unsigned char *ns_outbuffer;/* Output buffer */
unsigned char *ns_outptr; /* Output buffer pointer */
unsigned int ns_outbuflen; /* Output buffer data length */
@@ -158,6 +159,7 @@ netsec_init(void)
nsc->ns_inbuffer = mh_xmalloc(nsc->ns_inbufsize);
nsc->ns_inptr = nsc->ns_inbuffer;
nsc->ns_inbuflen = 0;
+ nsc->ns_readline_last_char = '\n';
nsc->ns_outbufsize = NETSEC_BUFSIZE;
nsc->ns_outbuffer = mh_xmalloc(nsc->ns_outbufsize);
nsc->ns_outptr = nsc->ns_outbuffer;
@@ -436,8 +438,31 @@ netsec_read(netsec_context *nsc, void *buffer, size_t size, char **errstr)
char *
netsec_readline(netsec_context *nsc, size_t *len, char **errstr)
{
- unsigned char *ptr = nsc->ns_inptr;
+ return netsec_readline_or_part(nsc, len, errstr, NULL);
+}
+
+char *
+netsec_readline_or_part(netsec_context *nsc, size_t *len, char **errstr,
+ int *partial_line_status)
+{
+ unsigned char *ptr;
size_t count = 0, offset;
+ int line_status = 0;
+ /* line_max is the longest line we expect caller can handle.
+ Reserve 2 so caller can add \n\0 */
+ size_t line_max = BUFSIZ - 2;
+
+ if (nsc->ns_readline_last_char == '\r'
+ || nsc->ns_readline_last_char == '\n') {
+ line_status = NETSEC_LINE_START;
+ ptr = nsc->ns_inptr;
+ } else {
+ /* put back the character we clobbered with NUL on previous call */
+ nsc->ns_inptr -= 1;
+ nsc->ns_inbuflen += 1;
+ ptr = nsc->ns_inptr;
+ *ptr = nsc->ns_readline_last_char;
+ }
retry:
/*
@@ -447,42 +472,53 @@ retry:
while (count < nsc->ns_inbuflen) {
count++;
if (*ptr++ == '\n') {
- char *sptr = (char *) nsc->ns_inptr;
- if (count > 1 && *(ptr - 2) == '\r')
- ptr--;
- *--ptr = '\0';
- if (len)
- *len = ptr - nsc->ns_inptr;
- nsc->ns_inptr += count;
- nsc->ns_inbuflen -= count;
- if (nsc->ns_snoop) {
+ line_status |= NETSEC_LINE_END;
+ } else if (count <= line_max) {
+ continue; /* keep counting */
+ } else if (!partial_line_status) {
+ break; /* caller cannot accept this overflow */
+ }
+ char *sptr = (char *) nsc->ns_inptr;
+ if (count > 1 && *(ptr - 2) == '\r' && *(ptr - 1) == '\n')
+ ptr--;
+ ptr--;
+ nsc->ns_readline_last_char = *ptr;
+ *ptr = '\0';
+ if (len)
+ *len = ptr - nsc->ns_inptr;
+ nsc->ns_inptr += count;
+ nsc->ns_inbuflen -= count;
+ if (partial_line_status) {
+ *partial_line_status = line_status;
+ }
+ if (nsc->ns_snoop) {
#ifdef CYRUS_SASL
- if (nsc->sasl_seclayer)
- fprintf(stderr, "(sasl-decrypted) ");
+ if (nsc->sasl_seclayer)
+ fprintf(stderr, "(sasl-decrypted) ");
#endif /* CYRUS_SASL */
#ifdef TLS_SUPPORT
- if (nsc->tls_active)
- fprintf(stderr, "(tls-decrypted) ");
+ if (nsc->tls_active)
+ fprintf(stderr, "(tls-decrypted) ");
#endif /* TLS_SUPPORT */
- fprintf(stderr, "<= ");
- if (nsc->ns_snoop_cb)
- nsc->ns_snoop_cb(nsc, sptr, strlen(sptr),
- nsc->ns_snoop_context);
- else {
- fputs(sptr, stderr);
- putc('\n', stderr);
- }
- }
- return sptr;
- }
+ fprintf(stderr, "<= ");
+ if (nsc->ns_snoop_cb)
+ nsc->ns_snoop_cb(nsc, sptr, strlen(sptr),
+ nsc->ns_snoop_context);
+ else {
+ fputs(sptr, stderr);
+ putc('\n', stderr);
+ }
+ }
+ return sptr;
}
/*
* Hm, we didn't find a \n. If we've already searched half of the input
- * buffer, return an error.
+ * buffer or have accumulated enough to possibly overflow our caller,
+ * return an error.
*/
- if (count >= nsc->ns_inbufsize / 2) {
+ if (count >= nsc->ns_inbufsize / 2 || count > line_max) {
netsec_err(errstr, "Unable to find a line terminator after %zu bytes",
count);
return NULL;
diff --git a/uip/popsbr.c b/uip/popsbr.c
index 89fc4cac..326fe827 100644
--- a/uip/popsbr.c
+++ b/uip/popsbr.c
@@ -30,12 +30,12 @@ static netsec_context *nsc = NULL;
*/
static int command(const char *, ...) CHECK_PRINTF(1, 2);
-static int multiline(void);
+static int multiline(int);
static int traverse(int (*)(void *, char *), void *closure,
const char *, ...) CHECK_PRINTF(3, 4);
static int vcommand(const char *, va_list) CHECK_PRINTF(1, 0);
-static int pop_getline (char *, int, netsec_context *);
+static int pop_getline (char *, int, netsec_context *, int *);
static int pop_sasl_callback(enum sasl_message_type, unsigned const char *,
unsigned int, unsigned char **, unsigned int *,
void *, char **);
@@ -60,7 +60,7 @@ check_mech(char *server_mechs, size_t server_mechs_size)
return NOTOK;
}
- while ((status = multiline()) != DONE) {
+ while ((status = multiline(0)) != DONE) {
if (status == NOTOK)
return NOTOK;
@@ -100,7 +100,7 @@ pop_start_tls(void)
return NOTOK;
}
- while ((status = multiline()) != DONE) {
+ while ((status = multiline(0)) != DONE) {
if (status == NOTOK)
return NOTOK;
@@ -285,7 +285,7 @@ pop_init (char *host, char *port, char *user, char *proxy, int snoop,
}
}
- switch (pop_getline (response, sizeof response, nsc)) {
+ switch (pop_getline (response, sizeof response, nsc, NULL)) {
case OK:
if (poprint)
fprintf (stderr, "<--- %s\n", response);
@@ -580,7 +580,7 @@ traverse (int (*action)(void *, char *), void *closure, const char *fmt, ...)
netsec_set_snoop(nsc, 0);
for (;;) {
- result = multiline();
+ result = multiline(1);
if (result == OK) {
result = (*action)(closure, response);
if (result == OK)
@@ -667,7 +667,7 @@ vcommand (const char *fmt, va_list ap)
return NOTOK;
}
- switch (pop_getline (response, sizeof response, nsc)) {
+ switch (pop_getline (response, sizeof response, nsc, NULL)) {
case OK:
if (poprint)
fprintf (stderr, "<--- %s\n", response);
@@ -687,30 +687,35 @@ vcommand (const char *fmt, va_list ap)
static int
-multiline (void)
+multiline (int include_newline)
{
- char buffer[BUFSIZ + LEN(TRM)];
+ char buffer[BUFSIZ]; /* same size as response */
+ int line_part;
- if (pop_getline (buffer, sizeof buffer, nsc) != OK)
+ /* reserve 1 byte so we can append \n */
+ if (pop_getline (buffer, sizeof buffer - 1, nsc, &line_part) != OK)
return NOTOK;
char *b = buffer;
- if (has_prefix(buffer, TRM)) {
+ if ((line_part & NETSEC_LINE_START) && has_prefix(buffer, TRM)) {
b += LEN(TRM);
if (!*b)
return DONE;
}
+ if ((line_part & NETSEC_LINE_END) && include_newline) {
+ strcat(buffer, "\n");
+ }
ABORTCPY(response, b);
return OK;
}
/*
- * This is now just a thin wrapper around netsec_readline().
+ * This is now just a thin wrapper around netsec_readline_or_part().
*/
static int
-pop_getline (char *s, int n, netsec_context *ns)
+pop_getline (char *s, int n, netsec_context *ns, int *partial_line_status)
{
/* int c = -2; */
char *p;
@@ -718,7 +723,7 @@ pop_getline (char *s, int n, netsec_context *ns)
/* int rc; */
char *errstr;
- p = netsec_readline(ns, &len, &errstr);
+ p = netsec_readline_or_part(ns, &len, &errstr, partial_line_status);
if (p == NULL) {
strncpy(response, errstr, sizeof(response));
diff --git a/uip/inc.c b/uip/inc.c
index dd0a646d..ddbc13cb 100644
--- a/uip/inc.c
+++ b/uip/inc.c
@@ -900,7 +900,7 @@ pop_action(void *closure, char *s)
int n;
pc = closure;
- n = fprintf(pc->mailout, "%s\n", s);
+ n = fprintf(pc->mailout, "%s", s);
if (n < 0)
return NOTOK;
pc->written += n; /* Count linefeed too. */