Buffer overrun fix. fixes: bug #787
[exim.git] / src / src / string.c
index ba04aa47137d7759ce6eff4de37e2b2ef19bdfb3..83455294dc824b5ce5e4734725c513110ce66174 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/string.c,v 1.4 2005/06/07 15:20:56 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 - 2005 */
+/* 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
@@ -28,6 +28,7 @@ Arguments:
   s         a string
   maskptr   NULL if no mask is permitted to follow
             otherwise, points to an int where the offset of '/' is placed
   s         a string
   maskptr   NULL if no mask is permitted to follow
             otherwise, points to an int where the offset of '/' is placed
+            if there is no / followed by trailing digits, *maskptr is set 0
 
 Returns:    0 if the string is not a textual representation of an IP address
             4 if it is an IPv4 address
 
 Returns:    0 if the string is not a textual representation of an IP address
             4 if it is an IPv4 address
@@ -127,7 +128,9 @@ if (Ustrchr(s, ':') != NULL)
   sign, which introduces the interface specifier (scope id) of a link local
   address. */
 
   sign, which introduces the interface specifier (scope id) of a link local
   address. */
 
-  if (!v4end) return (*s == 0 || *s == '%' || *s == '/')? yield : 0;
+  if (!v4end)
+    return (*s == 0 || *s == '%' ||
+           (*s == '/' && maskptr != NULL && *maskptr != 0))? yield : 0;
   }
 
 /* Test for IPv4 address, which may be the tail-end of an IPv6 address. */
   }
 
 /* Test for IPv4 address, which may be the tail-end of an IPv6 address. */
@@ -139,7 +142,8 @@ for (i = 0; i < 4; i++)
   if (isdigit(*s) && isdigit(*(++s))) s++;
   }
 
   if (isdigit(*s) && isdigit(*(++s))) s++;
   }
 
-return (*s == 0 || *s == '/')? yield : 0;
+return (*s == 0 || (*s == '/' && maskptr != NULL && *maskptr != 0))?
+  yield : 0;
 }
 #endif  /* COMPILE_UTILITY */
 
 }
 #endif  /* COMPILE_UTILITY */
 
@@ -445,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   *
 *************************************************/
@@ -699,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
@@ -728,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
     {
@@ -750,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--;
@@ -769,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 */
@@ -973,6 +1066,8 @@ return yield;
 BOOL
 string_vformat(uschar *buffer, int buflen, char *format, va_list ap)
 {
 BOOL
 string_vformat(uschar *buffer, int buflen, char *format, va_list ap)
 {
+enum { L_NORMAL, L_SHORT, L_LONG, L_LONGLONG, L_LONGDOUBLE };
+
 BOOL yield = TRUE;
 int width, precision;
 char *fp = format;             /* Deliberately not unsigned */
 BOOL yield = TRUE;
 int width, precision;
 char *fp = format;             /* Deliberately not unsigned */
@@ -985,6 +1080,7 @@ string_datestamp_offset = -1;  /* Datestamp not inserted */
 
 while (*fp != 0)
   {
 
 while (*fp != 0)
   {
+  int length = L_NORMAL;
   int *nptr;
   int slen;
   char *null = "NULL";         /* ) These variables */
   int *nptr;
   int slen;
   char *null = "NULL";         /* ) These variables */
@@ -1038,7 +1134,25 @@ while (*fp != 0)
       }
     }
 
       }
     }
 
-  if (strchr("hlL", *fp) != NULL) fp++;
+  /* Skip over 'h', 'L', 'l', and 'll', remembering the item length */
+
+  if (*fp == 'h')
+    { fp++; length = L_SHORT; }
+  else if (*fp == 'L')
+    { fp++; length = L_LONGDOUBLE; }
+  else if (*fp == 'l')
+    {
+    if (fp[1] == 'l')
+      {
+      fp += 2;
+      length = L_LONGLONG;
+      }
+    else
+      {
+      fp++;
+      length = L_LONG;
+      }
+    }
 
   /* Handle each specific format type. */
 
 
   /* Handle each specific format type. */
 
@@ -1054,10 +1168,21 @@ while (*fp != 0)
     case 'u':
     case 'x':
     case 'X':
     case 'u':
     case 'x':
     case 'X':
-    if (p >= last - 12) { yield = FALSE; goto END_FORMAT; }
+    if (p >= last - ((length > L_LONG)? 24 : 12))
+      { yield = FALSE; goto END_FORMAT; }
     strncpy(newformat, item_start, fp - item_start);
     newformat[fp - item_start] = 0;
     strncpy(newformat, item_start, fp - item_start);
     newformat[fp - item_start] = 0;
-    sprintf(CS p, newformat, va_arg(ap, int));
+
+    /* Short int is promoted to int when passing through ..., so we must use
+    int for va_arg(). */
+
+    switch(length)
+      {
+      case L_SHORT:
+      case L_NORMAL:   sprintf(CS p, newformat, va_arg(ap, int)); break;
+      case L_LONG:     sprintf(CS p, newformat, va_arg(ap, long int)); break;
+      case L_LONGLONG: sprintf(CS p, newformat, va_arg(ap, LONGLONG_T)); break;
+      }
     while (*p) p++;
     break;
 
     while (*p) p++;
     break;
 
@@ -1085,7 +1210,10 @@ while (*fp != 0)
     if (p >= last - precision - 8) { yield = FALSE; goto END_FORMAT; }
     strncpy(newformat, item_start, fp - item_start);
     newformat[fp-item_start] = 0;
     if (p >= last - precision - 8) { yield = FALSE; goto END_FORMAT; }
     strncpy(newformat, item_start, fp - item_start);
     newformat[fp-item_start] = 0;
-    sprintf(CS p, newformat, va_arg(ap, double));
+    if (length == L_LONGDOUBLE)
+      sprintf(CS p, newformat, va_arg(ap, long double));
+    else
+      sprintf(CS p, newformat, va_arg(ap, double));
     while (*p) p++;
     break;
 
     while (*p) p++;
     break;
 
@@ -1139,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')
@@ -1433,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;
@@ -1465,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);
@@ -1487,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);