Buffer overrun fix. fixes: bug #787
[exim.git] / src / src / string.c
index 2fe57b3030b64568dc077f50484e89a2066d37d1..83455294dc824b5ce5e4734725c513110ce66174 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/string.c,v 1.9 2006/02/13 11:13:37 ph10 Exp $ */
+/* $Cambridge: exim/src/src/string.c,v 1.14 2008/12/12 14:36:37 nm4 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2006 */
+/* Copyright (c) University of Cambridge 1995 - 2007 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Miscellaneous string-handling functions. Some are not required for
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Miscellaneous string-handling functions. Some are not required for
@@ -449,6 +449,67 @@ return ss;
 
 
 
 
 
 
+/*************************************************
+*    Copy string if long, inserting newlines     *
+*************************************************/
+
+/* If the given string is longer than 75 characters, it is copied, and within
+the copy, certain space characters are converted into newlines.
+
+Argument:  pointer to the string
+Returns:   pointer to the possibly altered string
+*/
+
+uschar *
+string_split_message(uschar *msg)
+{
+uschar *s, *ss;
+
+if (msg == NULL || Ustrlen(msg) <= 75) return msg;
+s = ss = msg = string_copy(msg);
+
+for (;;)
+  {
+  int i = 0;
+  while (i < 75 && *ss != 0 && *ss != '\n') ss++, i++;
+  if (*ss == 0) break;
+  if (*ss == '\n')
+    s = ++ss;
+  else
+    {
+    uschar *t = ss + 1;
+    uschar *tt = NULL;
+    while (--t > s + 35)
+      {
+      if (*t == ' ')
+        {
+        if (t[-1] == ':') { tt = t; break; }
+        if (tt == NULL) tt = t;
+        }
+      }
+
+    if (tt == NULL)          /* Can't split behind - try ahead */
+      {
+      t = ss + 1;
+      while (*t != 0)
+        {
+        if (*t == ' ' || *t == '\n')
+          { tt = t; break; }
+        t++;
+        }
+      }
+
+    if (tt == NULL) break;   /* Can't find anywhere to split */
+    *tt = '\n';
+    s = ss = tt+1;
+    }
+  }
+
+return msg;
+}
+
+
+
 /*************************************************
 *   Copy returned DNS domain name, de-escaping   *
 *************************************************/
 /*************************************************
 *   Copy returned DNS domain name, de-escaping   *
 *************************************************/
@@ -703,19 +764,26 @@ return NULL;
 /* Leading and trailing space is removed from each item. The separator in the
 list is controlled by the int pointed to by the separator argument as follows:
 
 /* Leading and trailing space is removed from each item. The separator in the
 list is controlled by the int pointed to by the separator argument as follows:
 
-  If its value is > 0 it is used as the delimiter.
-    (If its value is actually > UCHAR_MAX there is only one item in the list.
+  If the value is > 0 it is used as the separator. This is typically used for
+  sublists such as slash-separated options. The value is always a printing
+  character.
+
+    (If the value is actually > UCHAR_MAX there is only one item in the list.
     This is used for some cases when called via functions that sometimes
     plough through lists, and sometimes are given single items.)
     This is used for some cases when called via functions that sometimes
     plough through lists, and sometimes are given single items.)
-  If its value is <= 0, the string is inspected for a leading <x, where
-    x is an ispunct() value. If found, it is used as the delimiter. If not
-    found: (a) if separator == 0, ':' is used
-           (b) if separator <0, then -separator is used
-    In all cases the value of the separator that is used is written back to
-      the int so that it is used on subsequent calls as we progress through
-      the list.
 
 
-The separator can always be represented in the string by doubling.
+  If the value is <= 0, the string is inspected for a leading <x, where x is an
+  ispunct() or an iscntrl() character. If found, x is used as the separator. If
+  not found:
+
+      (a) if separator == 0, ':' is used
+      (b) if separator <0, -separator is used
+
+  In all cases the value of the separator that is used is written back to the
+  int so that it is used on subsequent calls as we progress through the list.
+
+A literal ispunct() separator can be represented in an item by doubling, but
+there is no way to include an iscntrl() separator as part of the data.
 
 Arguments:
   listptr    points to a pointer to the current start of the list; the
 
 Arguments:
   listptr    points to a pointer to the current start of the list; the
@@ -732,20 +800,28 @@ Returns:     pointer to buffer, containing the next substring,
 uschar *
 string_nextinlist(uschar **listptr, int *separator, uschar *buffer, int buflen)
 {
 uschar *
 string_nextinlist(uschar **listptr, int *separator, uschar *buffer, int buflen)
 {
-register int p = 0;
 register int sep = *separator;
 register uschar *s = *listptr;
 register int sep = *separator;
 register uschar *s = *listptr;
+BOOL sep_is_special;
 
 if (s == NULL) return NULL;
 
 if (s == NULL) return NULL;
-while (isspace(*s)) s++;
+
+/* This allows for a fixed specified separator to be an iscntrl() character,
+but at the time of implementation, this is never the case. However, it's best
+to be conservative. */
+
+while (isspace(*s) && *s != sep) s++;
+
+/* A change of separator is permitted, so look for a leading '<' followed by an
+allowed character. */
 
 if (sep <= 0)
   {
 
 if (sep <= 0)
   {
-  if (*s == '<' && ispunct(s[1]))
+  if (*s == '<' && (ispunct(s[1]) || iscntrl(s[1])))
     {
     sep = s[1];
     s += 2;
     {
     sep = s[1];
     s += 2;
-    while (isspace(*s)) s++;
+    while (isspace(*s) && *s != sep) s++;
     }
   else
     {
     }
   else
     {
@@ -754,15 +830,22 @@ if (sep <= 0)
   *separator = sep;
   }
 
   *separator = sep;
   }
 
+/* An empty string has no list elements */
+
 if (*s == 0) return NULL;
 
 if (*s == 0) return NULL;
 
+/* Note whether whether or not the separator is an iscntrl() character. */
+
+sep_is_special = iscntrl(sep);
+
 /* Handle the case when a buffer is provided. */
 
 if (buffer != NULL)
   {
 /* Handle the case when a buffer is provided. */
 
 if (buffer != NULL)
   {
+  register int p = 0;
   for (; *s != 0; s++)
     {
   for (; *s != 0; s++)
     {
-    if (*s == sep && *(++s) != sep) break;
+    if (*s == sep && (*(++s) != sep || sep_is_special)) break;
     if (p < buflen - 1) buffer[p++] = *s;
     }
   while (p > 0 && isspace(buffer[p-1])) p--;
     if (p < buflen - 1) buffer[p++] = *s;
     }
   while (p > 0 && isspace(buffer[p-1])) p--;
@@ -773,31 +856,37 @@ if (buffer != NULL)
 
 else
   {
 
 else
   {
+  int size = 0;
+  int ptr = 0;
+  uschar *ss;
+
   /* We know that *s != 0 at this point. However, it might be pointing to a
   /* We know that *s != 0 at this point. However, it might be pointing to a
-  separator, which could indicate an empty string, or could be doubled to
-  indicate a separator character as data at the start of a string. */
+  separator, which could indicate an empty string, or (if an ispunct()
+  character) could be doubled to indicate a separator character as data at the
+  start of a string. Avoid getting working memory for an empty item. */
 
   if (*s == sep)
     {
     s++;
 
   if (*s == sep)
     {
     s++;
-    if (*s != sep) buffer = string_copy(US"");
+    if (*s != sep || sep_is_special)
+      {
+      *listptr = s;
+      return string_copy(US"");
+      }
     }
 
     }
 
-  if (buffer == NULL)
+  /* Not an empty string; the first character is guaranteed to be a data
+  character. */
+
+  for (;;)
     {
     {
-    int size = 0;
-    int ptr = 0;
-    uschar *ss;
-    for (;;)
-      {
-      for (ss = s + 1; *ss != 0 && *ss != sep; ss++);
-      buffer = string_cat(buffer, &size, &ptr, s, ss-s);
-      s = ss;
-      if (*s == 0 || *(++s) != sep) break;
-      }
-    while (ptr > 0 && isspace(buffer[ptr-1])) ptr--;
-    buffer[ptr] = 0;
+    for (ss = s + 1; *ss != 0 && *ss != sep; ss++);
+    buffer = string_cat(buffer, &size, &ptr, s, ss-s);
+    s = ss;
+    if (*s == 0 || *(++s) != sep || sep_is_special) break;
     }
     }
+  while (ptr > 0 && isspace(buffer[ptr-1])) ptr--;
+  buffer[ptr] = 0;
   }
 
 /* Update the current pointer and return the new string */
   }
 
 /* Update the current pointer and return the new string */
@@ -1178,10 +1267,17 @@ while (*fp != 0)
     not OK, add part of the string (debugging uses this to show as
     much as possible). */
 
     not OK, add part of the string (debugging uses this to show as
     much as possible). */
 
+    if (p == last)
+      {
+      yield = FALSE;
+      goto END_FORMAT;
+      }
     if (p >= last - width)
       {
       yield = FALSE;
       width = precision = last - p - 1;
     if (p >= last - width)
       {
       yield = FALSE;
       width = precision = last - p - 1;
+      if (width < 0) width = 0;
+      if (precision < 0) precision = 0;
       }
     sprintf(CS p, "%*.*s", width, precision, s);
     if (fp[-1] == 'S')
       }
     sprintf(CS p, "%*.*s", width, precision, s);
     if (fp[-1] == 'S')
@@ -1472,8 +1568,10 @@ printf("Testing string_format\n");
 while (fgets(CS buffer, sizeof(buffer), stdin) != NULL)
   {
   void *args[3];
 while (fgets(CS buffer, sizeof(buffer), stdin) != NULL)
   {
   void *args[3];
+  long long llargs[3];
   double dargs[3];
   int dflag = 0;
   double dargs[3];
   int dflag = 0;
+  int llflag = 0;
   int n = 0;
   int count;
   int countset = 0;
   int n = 0;
   int count;
   int countset = 0;
@@ -1504,6 +1602,11 @@ while (fgets(CS buffer, sizeof(buffer), stdin) != NULL)
         dflag = 1;
         dargs[n++] = Ustrtod(outbuf, NULL);
         }
         dflag = 1;
         dargs[n++] = Ustrtod(outbuf, NULL);
         }
+      else if (Ustrstr(outbuf, "ll") != NULL)
+        {
+        llflag = 1;
+        llargs[n++] = strtoull(CS outbuf, NULL, 10);
+        }
       else
         {
         args[n++] = (void *)Uatoi(outbuf);
       else
         {
         args[n++] = (void *)Uatoi(outbuf);
@@ -1526,11 +1629,16 @@ while (fgets(CS buffer, sizeof(buffer), stdin) != NULL)
     if (*s == ',') s++;
     }
 
     if (*s == ',') s++;
     }
 
-  if (!dflag) printf("%s\n", string_format(outbuf, sizeof(outbuf), CS format,
-    args[0], args[1], args[2])? "True" : "False");
+  if (!dflag && !llflag)
+    printf("%s\n", string_format(outbuf, sizeof(outbuf), CS format,
+      args[0], args[1], args[2])? "True" : "False");
+
+  else if (dflag)
+    printf("%s\n", string_format(outbuf, sizeof(outbuf), CS format,
+      dargs[0], dargs[1], dargs[2])? "True" : "False");
 
   else printf("%s\n", string_format(outbuf, sizeof(outbuf), CS format,
 
   else printf("%s\n", string_format(outbuf, sizeof(outbuf), CS format,
-    dargs[0], dargs[1], dargs[2])? "True" : "False");
+    llargs[0], llargs[1], llargs[2])? "True" : "False");
 
   printf("%s\n", CS outbuf);
   if (countset) printf("count=%d\n", count);
 
   printf("%s\n", CS outbuf);
   if (countset) printf("count=%d\n", count);