A few days ago I reported a problem with using POP3 inc to get
messages with long lines. The problem lead to corruption of email,
and could conceivably cause inc (or any other POP3-using MH program)
to crash.
This patch fixes that problem. I have fixed up the popsbr.c getline()
and multiline() functions so that they handle long lines better.
Short lines are now terminated with \n\0; parts of long lines are
terminated with \0. getline() and multiline() now return the number
of characters copied.
I have had to go through popsbr.c and various other files which use
these functions or other popsbr.c functions in order to make them all
understand the new interface. I hope I have got everything right, but
I can't be sure as there are large parts of NMH which I don't use.
Certainly inc now works OK for me.
Nick Barnes
Director
Ravenbrook Limited
<http://www.ravenbrook.com/>
diff -bru /usr/ports/mail/nmh/work/nmh-1.0.4/h/popsbr.h ./h/popsbr.h
--- /usr/ports/mail/nmh/work/nmh-1.0.4/h/popsbr.h Fri Feb 4 01:32:12 2000
+++ ./h/popsbr.h Thu Jan 31 11:08:28 2002
@@ -25,11 +25,11 @@
int pop_init (char *, char *, char *, int, int, int);
int pop_fd (char *, int, char *, int);
int pop_stat (int *, int *);
-int pop_retr (int, int (*)());
+int pop_retr (int, int (*)(const char *, int));
int pop_dele (int);
int pop_noop (void);
int pop_rset (void);
-int pop_top (int, int, int (*)());
+int pop_top (int, int, int (*)(const char *, int));
int pop_quit (void);
int pop_done (void);
@@ -40,7 +40,7 @@
#endif
#ifdef BPOP
-int pop_xtnd (int (*)(), char *, ...);
+int pop_xtnd (int (*)(const char *, int), char *, ...);
#endif
#if defined(MPOP) && !defined(NNTP)
@@ -50,7 +50,7 @@
#if !defined(NNTP) && defined(MPOP)
/* otherwise they are static functions */
int command(const char *, ...);
-int multiline(void);
+int multiline(int);
#endif
/*
diff -bru /usr/ports/mail/nmh/work/nmh-1.0.4/uip/inc.c ./uip/inc.c
--- /usr/ports/mail/nmh/work/nmh-1.0.4/uip/inc.c Fri Mar 17 20:11:04 2000
+++ ./uip/inc.c Thu Jan 31 14:00:34 2002
@@ -153,8 +153,8 @@
#ifdef POP
int done(int);
-static int pop_action(char *);
-static int pop_pack(char *);
+static int pop_action(const char *, int);
+static int pop_pack(const char *, int);
static int map_count(void);
#endif
@@ -909,26 +909,26 @@
}
static int
-pop_action (char *s)
+pop_action (const char *s, int l)
{
- fprintf (pf, "%s\n", s);
- stop += strlen (s) + 1;
+ fprintf (pf, "%s", s);
+ stop += strlen (s);
return 0; /* Is return value used? This was missing before 1999-07-15. */
}
static int
-pop_pack (char *s)
+pop_pack (const char *s, int l)
{
int j;
char buffer[BUFSIZ];
- snprintf (buffer, sizeof(buffer), "%s\n", s);
+ strncpy (buffer, sizeof(buffer), s);
for (j = 0; (j = stringdex (mmdlm1, buffer)) >= 0; buffer[j]++)
continue;
for (j = 0; (j = stringdex (mmdlm2, buffer)) >= 0; buffer[j]++)
continue;
fputs (buffer, pf);
- size += strlen (buffer) + 1;
+ size += strlen (buffer);
return 0; /* Is return value used? This was missing before 1999-07-15. */
}
diff -bru /usr/ports/mail/nmh/work/nmh-1.0.4/uip/msh.c ./uip/msh.c
--- /usr/ports/mail/nmh/work/nmh-1.0.4/uip/msh.c Mon Mar 6 20:19:04 2000
+++ ./uip/msh.c Thu Jan 31 11:15:35 2002
@@ -192,10 +192,10 @@
#ifdef BPOP
# ifdef NNTP
-static int pop_statmsg (char *);
+static int pop_statmsg (const char *, int);
# endif /* NNTP */
static int read_pop (void);
-static int pop_action (char *);
+static int pop_action (const char *, int);
#endif /* BPOP */
static void m_gMsgs (int);
@@ -884,7 +884,7 @@
static int pop_base = 0;
static int
-pop_statmsg (char *s)
+pop_statmsg (const char *s, int l /* line length (unused) */)
{
register int i, n;
@@ -913,9 +913,9 @@
static int
-pop_action (char *s)
+pop_action (char *s, int linelen)
{
- fprintf (yp, "%s\n", s);
+ fprintf (yp, "%s", s);
}
#endif /* BPOP */
diff -bru /usr/ports/mail/nmh/work/nmh-1.0.4/uip/mshcmds.c ./uip/mshcmds.c
--- /usr/ports/mail/nmh/work/nmh-1.0.4/uip/mshcmds.c Fri Feb 4 20:28:24 2000
+++ ./uip/mshcmds.c Thu Jan 31 13:34:00 2002
@@ -2194,15 +2194,14 @@
for (;;) {
int size;
- switch (pop_multiline ()) {
+ switch (pop_multiline (0)) {
case NOTOK:
fprintf (stderr, "%s", response);
- /* and fall... */
- case DONE:
+ break;
+ case 0:
fprintf (stderr,"\n");
break;
-
- case OK:
+ default:
if (sscanf (response, "%d %d", &msgnum, &size) == 2
&& mp->lowmsg <= msgnum
&& msgnum <= mp->hghmsg
diff -bru /usr/ports/mail/nmh/work/nmh-1.0.4/uip/popi.c ./uip/popi.c
--- /usr/ports/mail/nmh/work/nmh-1.0.4/uip/popi.c Fri Feb 4 20:28:24 2000
+++ ./uip/popi.c Thu Jan 31 14:06:01 2002
@@ -84,6 +84,9 @@
*/
int sc_width (void); /* from termsbr.c */
+static int
+retr_action (const char *rsp, int linelen);
+static int retr_action_flag;
int
main (int argc, char **argv)
@@ -347,7 +350,7 @@
if (cp)
*cp = ' ';
pop_command ("%s%s", popicmds[i].sw, cp ? cp : "");
- printf ("%s\n", response);
+ printf ("%s", response);
break;
case LISTCMD:
@@ -355,19 +358,18 @@
*cp = ' ';
if (pop_command ("%s%s", popicmds[i].sw, cp ? cp : "")
== OK) {
- printf ("%s\n", response);
+ printf ("%s", response);
if (!cp)
for (;;) {
- switch (pop_multiline ()) {
- case DONE:
- strcpy (response, ".");
- /* and fall... */
+ switch (pop_multiline (0)) {
+ case 0:
+ printf(".\n");
+ break;
case NOTOK:
- printf ("%s\n", response);
+ printf ("%s", response);
break;
-
- case OK:
- printf ("%s\n", response);
+ default:
+ printf ("%s", response);
continue;
}
break;
@@ -380,10 +382,12 @@
advise (NULL, "missing argument to %s", buffer);
break;
}
- retr_action (NULL, OK);
+ retr_action_flag = OK;
+ retr_action (NULL, 0);
pop_retr (atoi (++cp), retr_action);
- retr_action (NULL, DONE);
- printf ("%s\n", response);
+ retr_action_flag = DONE;
+ retr_action (NULL, 0);
+ printf ("%s", response);
break;
case SCANCMD:
@@ -413,7 +417,7 @@
*fp = '\0';
pop_command ("xtnd scan %d \"%s\"", width, ep);
- printf ("%s\n", response);
+ printf ("%s", response);
free (ep);
}
@@ -430,7 +434,7 @@
static int
-retr_action (char *rsp, int flag)
+retr_action (const char *rsp, int len)
{
static FILE *fp;
@@ -438,7 +442,7 @@
static int msgnum;
static char *cp;
- if (flag == OK) {
+ if (retr_action_flag == OK) {
if (!(mp = folder_realloc (mp, mp->lowoff, msgnum = mp->hghmsg + 1)))
adios (NULL, "unable to allocate folder storage");
@@ -471,7 +475,7 @@
return;
}
- fprintf (fp, "%s\n", rsp);
+ fprintf (fp, "%s", rsp);
}
diff -bru /usr/ports/mail/nmh/work/nmh-1.0.4/uip/popsbr.c ./uip/popsbr.c
--- /usr/ports/mail/nmh/work/nmh-1.0.4/uip/popsbr.c Fri Feb 4 01:32:12 2000
+++ ./uip/popsbr.c Thu Jan 31 14:19:06 2002
@@ -63,10 +63,10 @@
#if defined(NNTP) || !defined(MPOP)
/* otherwise they are not static functions */
static int command(const char *, ...);
-static int multiline(void);
+static int multiline(int more);
#endif
-static int traverse (int (*)(), const char *, ...);
+static int traverse (int (*)(const char *, int), const char *, ...);
static int vcommand(const char *, va_list);
static int getline (char *, int, FILE *);
static int putline (char *, FILE *);
@@ -122,6 +122,7 @@
{
int fd1, fd2;
char buffer[BUFSIZ];
+ int linelen;
#ifdef APOP
int apop;
@@ -170,10 +171,10 @@
SIGNAL (SIGPIPE, SIG_IGN);
- switch (getline (response, sizeof response, input)) {
- case OK:
+ linelen = getline (response, sizeof response, input);
+ if (linelen > 0) { /* some bytes */
if (poprint)
- fprintf (stderr, "<--- %s\n", response);
+ fprintf (stderr, "<--- %s", response);
#ifndef NNTP
if (*response == '+') {
# ifndef KPOP
@@ -183,8 +184,7 @@
if (cp && command ("APOP %s", cp) != NOTOK)
return OK;
- }
- else
+ } else
# endif /* APOP */
if (command ("USER %s", user) != NOTOK
&& command ("%s %s", rpop ? "RPOP" : (pophack++, "PASS"),
@@ -206,17 +206,13 @@
command ("QUIT");
strncpy (response, buffer, sizeof(response));
/* and fall */
+ }
- case NOTOK:
- case DONE:
if (poprint)
fprintf (stderr, "%s\n", response);
fclose (input);
fclose (output);
return NOTOK;
- }
-
- return NOTOK; /* NOTREACHED */
}
#ifdef NNTP
@@ -303,7 +299,7 @@
#ifdef NNTP
int
-pop_exists (int (*action)())
+pop_exists (int (*action)(const char *, int))
{
#ifdef XMSGS /* hacked into NNTP 1.5 */
if (traverse (action, "XMSGS %d-%d", (targ_t) xtnd_first, (targ_t) xtnd_last) ==
OK)
@@ -326,6 +322,7 @@
#endif
{
int i;
+ int linelen;
#ifndef BPOP
int *ids = NULL;
#endif
@@ -356,14 +353,15 @@
if (command ("LIST") == NOTOK)
return NOTOK;
- for (i = 0; i < *nmsgs; i++)
- switch (multiline ()) {
- case NOTOK:
+ for (i = 0; i < *nmsgs; i++) {
+ /* LIST lines must be smaller than our buffer */
+ switch(multiline (0)) {
+ case NOTOK: /* EOF or I/O error */
return NOTOK;
- case DONE:
+ case 0: /* end of multi-line */
*nmsgs = ++i;
return OK;
- case OK:
+ default:
*msgs = *bytes = 0;
if (ids) {
*ids = 0;
@@ -374,13 +372,14 @@
sscanf (response, "%d %d", msgs++, bytes++);
break;
}
+ }
for (;;)
- switch (multiline ()) {
- case NOTOK:
+ switch (multiline (0)) {
+ case NOTOK: /* EOF or I/O error */
return NOTOK;
- case DONE:
+ case 0: /* end of multi-line */
return OK;
- case OK:
+ default: /* a multi-line line */
break;
}
#else /* NNTP */
@@ -390,7 +389,7 @@
int
-pop_retr (int msgno, int (*action)())
+pop_retr (int msgno, int (*action)(const char *, int))
{
#ifndef NNTP
return traverse (action, "RETR %d", (targ_t) msgno);
@@ -401,11 +400,13 @@
static int
-traverse (int (*action)(), const char *fmt, ...)
+traverse (int (*action)(const char *, int), const char *fmt, ...)
{
int result;
va_list ap;
char buffer[sizeof(response)];
+ int linelen;
+ int more = 0;
va_start(ap, fmt);
result = vcommand (fmt, ap);
@@ -415,19 +416,23 @@
return NOTOK;
strncpy (buffer, response, sizeof(buffer));
- for (;;)
- switch (multiline ()) {
- case NOTOK:
+ for (;;) {
+ linelen = multiline(more);
+ switch(linelen) {
+ case NOTOK: /* EOF or I/O error. */
return NOTOK;
- case DONE:
+ case 0: /* end of multi-line */
strncpy (response, buffer, sizeof(response));
return OK;
- case OK:
- (*action) (response);
+ default: /* some bytes */
+ /* does this include the end of a line? */
+ more = (response[linelen-1] != '\n');
+ (*action) (response, linelen);
break;
}
+ }
}
@@ -462,7 +467,7 @@
int
-pop_top (int msgno, int lines, int (*action)())
+pop_top (int msgno, int lines, int (*action)(const char *, int))
{
#ifndef NNTP
return traverse (action, "TOP %d %d", (targ_t) msgno, (targ_t) lines);
@@ -474,7 +479,7 @@
#ifdef BPOP
int
-pop_xtnd (int (*action)(), char *fmt, ...)
+pop_xtnd (int (*action)(const char *, int), char *fmt, ...)
{
int result;
va_list ap;
@@ -521,7 +526,7 @@
xtnd_first = atoi (ap[2]);
xtnd_last = atoi (ap[3]);
- (*action) (buffer);
+ (*action) (buffer, strlen(buffer));
return OK;
} else { /* XTND "BBOARDS" */
@@ -577,6 +582,7 @@
vcommand (const char *fmt, va_list ap)
{
char *cp, buffer[BUFSIZ];
+ int linelen;
vsnprintf (buffer, sizeof(buffer), fmt, ap);
if (poprint) {
@@ -595,81 +601,113 @@
if (putline (buffer, output) == NOTOK)
return NOTOK;
- switch (getline (response, sizeof response, input)) {
- case OK:
+ linelen = getline (response, sizeof response, input);
+ if (linelen > 0) {
if (poprint)
- fprintf (stderr, "<--- %s\n", response);
+ fprintf (stderr, "<--- %s", response);
#ifndef NNTP
return (*response == '+' ? OK : NOTOK);
#else /* NNTP */
return (*response < CHAR_ERR ? OK : NOTOK);
#endif /* NNTP */
-
- case NOTOK:
- case DONE:
+ }
if (poprint)
- fprintf (stderr, "%s\n", response);
+ fprintf (stderr, "%s", response);
return NOTOK;
- }
-
- return NOTOK; /* NOTREACHED */
}
+/* multiline(more)
+ *
+ * Handle multi-line responses (RFC1939, section 3). A line which is
+ * a lone '.' ends the response. Any other line which begins with '.'
+ * is "byte-stuffed" by prepending another '.'.
+ *
+ * If the 'more' argument is non-zero, we are getting more of a
+ * part-read line, so the '.' handling does not apply.
+ *
+ * On I/O error or EOF, return NOTOK.
+ * If we have reached the end of the response, return 0.
+ * Otherwise, return the number of bytes.
+ */
+
#if defined(MPOP) && !defined(NNTP)
int
-multiline (void)
+multiline (int more)
#else
static int
-multiline (void)
+multiline (int more)
#endif
{
- char buffer[BUFSIZ + TRMLEN];
-
- if (getline (buffer, sizeof buffer, input) != OK)
+ char buffer[BUFSIZ];
+ int linelen = getline (buffer, sizeof buffer, input);
+ if (linelen <= 0)
return NOTOK;
#ifdef DEBUG
if (poprint)
- fprintf (stderr, "<--- %s\n", response);
+ fprintf (stderr, "<--- %s", buffer);
#endif DEBUG
- if (strncmp (buffer, TRM, TRMLEN) == 0) {
- if (buffer[TRMLEN] == 0)
- return DONE;
- else
- strncpy (response, buffer + TRMLEN, sizeof(response));
+ if ((!more) &&
+ (strncmp (buffer, TRM, TRMLEN) == 0)) { /* leading '.' */
+ if (buffer[TRMLEN] == '\n') { /* lone '.': end of multi-line */
+ return 0;
+ } else {
+ strncpy (response, buffer+TRMLEN, sizeof(response));
+ return (linelen - TRMLEN);
+ }
}
- else
strncpy (response, buffer, sizeof(response));
-
- return OK;
+ return linelen;
}
+/* getline(buf, bufsize, file)
+ * get a CRLF terminated line, and return it with \n in place of CRLF.
+ * If we don't reach a terminating CRLF, this will return as much as
+ * it can. NUL-terminate the result.
+ *
+ * Return value is the number of data bytes, including any \n, not
+ * including the NUL.
+ *
+ * Return NOTOK if we get a connection error.
+ * Return 0 if there are no bytes (EOF).
+ *
+ * SMTP says that all lines are terminated with CRLF and CR and LF do
+ * not appear bare. So we can just discard all CRs. This avoids the problem
+ * of a CRLF being split across buffers. */
static int
getline (char *s, int n, FILE *iop)
{
int c;
char *p;
+ char *limit = s+n-1;
p = s;
- while (--n > 0 && (c = fgetc (iop)) != EOF)
- if ((*p++ = c) == '\n')
+ for (;;) {
+ if (p == limit)
+ break;
+ while ((c = fgetc(iop)) == '\r') /* drop CRs */
+ ;
+ if (c == EOF)
break;
+ *p++ = c;
+ if (c == '\n')
+ break;
+ }
+
if (ferror (iop) && c != EOF) {
- strncpy (response, "error on connection", sizeof(response));
+ strncpy (response, "error on connection\n", sizeof(response));
return NOTOK;
}
if (c == EOF && p == s) {
- strncpy (response, "connection closed by foreign host", sizeof(response));
- return DONE;
+ strncpy (response,
+ "connection closed by foreign host\n", sizeof(response));
}
- *p = 0;
- if (*--p == '\n')
- *p = 0;
- if (*--p == '\r')
+
+ /* add terminating NUL */
*p = 0;
- return OK;
+ return p-s;
}