Merge from master into 4.next
[exim.git] / src / src / string.c
index 958ffbfee09afd0905b72f64d8f8cfbab4d05aa3..be1a1d7a4771ddaf2a90cb4ce97552a4ff1f9211 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/string.c,v 1.10 2006/02/23 10:25:45 ph10 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 - 2016 */
 /* 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
@@ -36,7 +34,7 @@ Returns:    0 if the string is not a textual representation of an IP address
 */
 
 int
 */
 
 int
-string_is_ip_address(uschar *s, int *maskptr)
+string_is_ip_address(const uschar *s, int *maskptr)
 {
 int i;
 int yield = 4;
 {
 int i;
 int yield = 4;
@@ -46,7 +44,7 @@ offset. */
 
 if (maskptr != NULL)
   {
 
 if (maskptr != NULL)
   {
-  uschar *ss = s + Ustrlen(s);
+  const uschar *ss = s + Ustrlen(s);
   *maskptr = 0;
   if (s != ss && isdigit(*(--ss)))
     {
   *maskptr = 0;
   if (s != ss && isdigit(*(--ss)))
     {
@@ -167,7 +165,7 @@ Returns:      pointer to the buffer
 uschar *
 string_format_size(int size, uschar *buffer)
 {
 uschar *
 string_format_size(int size, uschar *buffer)
 {
-if (size == 0) Ustrcpy(CS buffer, "     ");
+if (size == 0) Ustrcpy(buffer, "     ");
 else if (size < 1024) sprintf(CS buffer, "%5d", size);
 else if (size < 10*1024)
   sprintf(CS buffer, "%4.1fK", (double)size / 1024.0);
 else if (size < 1024) sprintf(CS buffer, "%5d", size);
 else if (size < 10*1024)
   sprintf(CS buffer, "%4.1fK", (double)size / 1024.0);
@@ -212,7 +210,6 @@ return yield;
 
 
 
 
 
 
-#ifndef COMPILE_UTILITY
 /*************************************************
 *          Interpret escape sequence             *
 *************************************************/
 /*************************************************
 *          Interpret escape sequence             *
 *************************************************/
@@ -227,10 +224,13 @@ Returns:   the value of the character escape
 */
 
 int
 */
 
 int
-string_interpret_escape(uschar **pp)
+string_interpret_escape(const uschar **pp)
 {
 {
+#ifdef COMPILE_UTILITY
+const uschar *hex_digits= CUS"0123456789abcdef";
+#endif
 int ch;
 int ch;
-uschar *p = *pp;
+const uschar *p = *pp;
 ch = *(++p);
 if (isdigit(ch) && ch != '8' && ch != '9')
   {
 ch = *(++p);
 if (isdigit(ch) && ch != '8' && ch != '9')
   {
@@ -244,9 +244,12 @@ if (isdigit(ch) && ch != '8' && ch != '9')
   }
 else switch(ch)
   {
   }
 else switch(ch)
   {
+  case 'b':  ch = '\b'; break;
+  case 'f':  ch = '\f'; break;
   case 'n':  ch = '\n'; break;
   case 'r':  ch = '\r'; break;
   case 't':  ch = '\t'; break;
   case 'n':  ch = '\n'; break;
   case 'r':  ch = '\r'; break;
   case 't':  ch = '\t'; break;
+  case 'v':  ch = '\v'; break;
   case 'x':
   ch = 0;
   if (isxdigit(p[1]))
   case 'x':
   ch = 0;
   if (isxdigit(p[1]))
@@ -261,7 +264,6 @@ else switch(ch)
 *pp = p;
 return ch;
 }
 *pp = p;
 return ch;
 }
-#endif  /* COMPILE_UTILITY */
 
 
 
 
 
 
@@ -282,12 +284,12 @@ Arguments:
 Returns:        string with non-printers encoded as printing sequences
 */
 
 Returns:        string with non-printers encoded as printing sequences
 */
 
-uschar *
-string_printing2(uschar *s, BOOL allow_tab)
+const uschar *
+string_printing2(const uschar *s, BOOL allow_tab)
 {
 int nonprintcount = 0;
 int length = 0;
 {
 int nonprintcount = 0;
 int length = 0;
-uschar *t = s;
+const uschar *t = s;
 uschar *ss, *tt;
 
 while (*t != 0)
 uschar *ss, *tt;
 
 while (*t != 0)
@@ -302,7 +304,7 @@ if (nonprintcount == 0) return s;
 /* Get a new block of store guaranteed big enough to hold the
 expanded string. */
 
 /* Get a new block of store guaranteed big enough to hold the
 expanded string. */
 
-ss = store_get(length + nonprintcount * 4 + 1);
+ss = store_get(length + nonprintcount * 3 + 1);
 
 /* Copy everying, escaping non printers. */
 
 
 /* Copy everying, escaping non printers. */
 
@@ -333,6 +335,73 @@ return ss;
 }
 #endif  /* COMPILE_UTILITY */
 
 }
 #endif  /* COMPILE_UTILITY */
 
+/*************************************************
+*        Undo printing escapes in string         *
+*************************************************/
+
+/* This function is the reverse of string_printing2.  It searches for
+backslash characters and if any are found, it makes a new copy of the
+string with escape sequences parsed.  Otherwise it returns the original
+string.
+
+Arguments:
+  s             the input string
+
+Returns:        string with printing escapes parsed back
+*/
+
+uschar *
+string_unprinting(uschar *s)
+{
+uschar *p, *q, *r, *ss;
+int len, off;
+
+p = Ustrchr(s, '\\');
+if (!p) return s;
+
+len = Ustrlen(s) + 1;
+ss = store_get(len);
+
+q = ss;
+off = p - s;
+if (off)
+  {
+  memcpy(q, s, off);
+  q += off;
+  }
+
+while (*p)
+  {
+  if (*p == '\\')
+    {
+    *q++ = string_interpret_escape((const uschar **)&p);
+    p++;
+    }
+  else
+    {
+    r = Ustrchr(p, '\\');
+    if (!r)
+      {
+      off = Ustrlen(p);
+      memcpy(q, p, off);
+      p += off;
+      q += off;
+      break;
+      }
+    else
+      {
+      off = r - p;
+      memcpy(q, p, off);
+      q += off;
+      p = r;
+      }
+    }
+  }
+*q = '\0';
+
+return ss;
+}
+
 
 
 
 
 
 
@@ -347,7 +416,7 @@ Returns:  copy of string in new store
 */
 
 uschar *
 */
 
 uschar *
-string_copy(uschar *s)
+string_copy(const uschar *s)
 {
 int len = Ustrlen(s) + 1;
 uschar *ss = store_get(len);
 {
 int len = Ustrlen(s) + 1;
 uschar *ss = store_get(len);
@@ -368,7 +437,7 @@ Returns:  copy of string in new store
 */
 
 uschar *
 */
 
 uschar *
-string_copy_malloc(uschar *s)
+string_copy_malloc(const uschar *s)
 {
 int len = Ustrlen(s) + 1;
 uschar *ss = store_malloc(len);
 {
 int len = Ustrlen(s) + 1;
 uschar *ss = store_malloc(len);
@@ -388,7 +457,7 @@ Returns:  copy of string in new store, with letters lowercased
 */
 
 uschar *
 */
 
 uschar *
-string_copylc(uschar *s)
+string_copylc(const uschar *s)
 {
 uschar *ss = store_get(Ustrlen(s) + 1);
 uschar *p = ss;
 {
 uschar *ss = store_get(Ustrlen(s) + 1);
 uschar *p = ss;
@@ -414,7 +483,7 @@ Returns:    copy of string in new store
 */
 
 uschar *
 */
 
 uschar *
-string_copyn(uschar *s, int n)
+string_copyn(const uschar *s, int n)
 {
 uschar *ss = store_get(n + 1);
 Ustrncpy(ss, s, n);
 {
 uschar *ss = store_get(n + 1);
 Ustrncpy(ss, s, n);
@@ -449,6 +518,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   *
 *************************************************/
@@ -509,9 +639,9 @@ Returns:   the new string
 */
 
 uschar *
 */
 
 uschar *
-string_dequote(uschar **sptr)
+string_dequote(const uschar **sptr)
 {
 {
-uschar *s = *sptr;
+const uschar *s = *sptr;
 uschar *t, *yield;
 
 /* First find the end of the string */
 uschar *t, *yield;
 
 /* First find the end of the string */
@@ -580,14 +710,16 @@ Returns:    pointer to fresh piece of store containing sprintf'ed string
 */
 
 uschar *
 */
 
 uschar *
-string_sprintf(char *format, ...)
+string_sprintf(const char *format, ...)
 {
 va_list ap;
 uschar buffer[STRING_SPRINTF_BUFFER_SIZE];
 va_start(ap, format);
 if (!string_vformat(buffer, sizeof(buffer), format, ap))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE,
 {
 va_list ap;
 uschar buffer[STRING_SPRINTF_BUFFER_SIZE];
 va_start(ap, format);
 if (!string_vformat(buffer, sizeof(buffer), format, ap))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE,
-    "string_sprintf expansion was longer than %d", sizeof(buffer));
+    "string_sprintf expansion was longer than " SIZE_T_FMT
+    "; format string was (%s)\nexpansion started '%.32s'",
+    sizeof(buffer), format, buffer);
 va_end(ap);
 return string_copy(buffer);
 }
 va_end(ap);
 return string_copy(buffer);
 }
@@ -608,7 +740,7 @@ Returns:    < 0, = 0, or > 0, according to the comparison
 */
 
 int
 */
 
 int
-strncmpic(uschar *s, uschar *t, int n)
+strncmpic(const uschar *s, const uschar *t, int n)
 {
 while (n--)
   {
 {
 while (n--)
   {
@@ -632,7 +764,7 @@ Returns:    < 0, = 0, or > 0, according to the comparison
 */
 
 int
 */
 
 int
-strcmpic(uschar *s, uschar *t)
+strcmpic(const uschar *s, const uschar *t)
 {
 while (*s != 0)
   {
 {
 while (*s != 0)
   {
@@ -703,19 +835,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
@@ -730,22 +869,30 @@ Returns:     pointer to buffer, containing the next substring,
 */
 
 uschar *
 */
 
 uschar *
-string_nextinlist(uschar **listptr, int *separator, uschar *buffer, int buflen)
+string_nextinlist(const uschar **listptr, int *separator, uschar *buffer, int buflen)
 {
 {
-register int p = 0;
-register int sep = *separator;
-register uschar *s = *listptr;
+int sep = *separator;
+const 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 +901,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)
   {
+  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 +927,37 @@ if (buffer != NULL)
 
 else
   {
 
 else
   {
+  int size = 0;
+  int ptr = 0;
+  const 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_catn(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 */
@@ -808,6 +968,95 @@ return buffer;
 #endif  /* COMPILE_UTILITY */
 
 
 #endif  /* COMPILE_UTILITY */
 
 
+#ifndef COMPILE_UTILITY
+/************************************************
+*      Add element to separated list           *
+************************************************/
+/* This function is used to build a list, returning
+an allocated null-terminated growable string. The
+given element has any embedded seperator characters
+doubled.
+
+Arguments:
+  list points to the start of the list that is being built, or NULL
+       if this is a new list that has no contents yet
+  sep  list seperator charactoer
+  ele  new lement to be appended to the list
+
+Returns:  pointer to the start of the list, changed if copied for expansion.
+*/
+
+uschar *
+string_append_listele(uschar * list, uschar sep, const uschar * ele)
+{
+uschar * new = NULL;
+int sz = 0, off = 0;
+uschar * sp;
+
+if (list)
+  {
+  new = string_cat (new, &sz, &off, list);
+  new = string_catn(new, &sz, &off, &sep, 1);
+  }
+
+while((sp = Ustrchr(ele, sep)))
+  {
+  new = string_catn(new, &sz, &off, ele, sp-ele+1);
+  new = string_catn(new, &sz, &off, &sep, 1);
+  ele = sp+1;
+  }
+new = string_cat(new, &sz, &off, ele);
+new[off] = '\0';
+return new;
+}
+
+
+static const uschar *
+Ustrnchr(const uschar * s, int c, unsigned * len)
+{
+unsigned siz = *len;
+while (siz)
+  {
+  if (!*s) return NULL;
+  if (*s == c)
+    {
+    *len = siz;
+    return s;
+    }
+  s++;
+  siz--;
+  }
+return NULL;
+}
+
+uschar *
+string_append_listele_n(uschar * list, uschar sep, const uschar * ele,
+  unsigned len)
+{
+uschar * new = NULL;
+int sz = 0, off = 0;
+const uschar * sp;
+
+if (list)
+  {
+  new = string_cat (new, &sz, &off, list);
+  new = string_catn(new, &sz, &off, &sep, 1);
+  }
+
+while((sp = Ustrnchr(ele, sep, &len)))
+  {
+  new = string_catn(new, &sz, &off, ele, sp-ele+1);
+  new = string_catn(new, &sz, &off, &sep, 1);
+  ele = sp+1;
+  len--;
+  }
+new = string_catn(new, &sz, &off, ele, len);
+new[off] = '\0';
+return new;
+}
+#endif  /* COMPILE_UTILITY */
+
+
 
 #ifndef COMPILE_UTILITY
 /*************************************************
 
 #ifndef COMPILE_UTILITY
 /*************************************************
@@ -829,7 +1078,7 @@ Arguments:
              characters, updated to the new offset
   s        points to characters to add
   count    count of characters to add; must not exceed the length of s, if s
              characters, updated to the new offset
   s        points to characters to add
   count    count of characters to add; must not exceed the length of s, if s
-             is a C string
+             is a C string.  If -1 given, strlen(s) is used.
 
 If string is given as NULL, *size and *ptr should both be zero.
 
 
 If string is given as NULL, *size and *ptr should both be zero.
 
@@ -837,10 +1086,12 @@ Returns:   pointer to the start of the string, changed if copied for expansion.
            Note that a NUL is not added, though space is left for one. This is
            because string_cat() is often called multiple times to build up a
            string - there's no point adding the NUL till the end.
            Note that a NUL is not added, though space is left for one. This is
            because string_cat() is often called multiple times to build up a
            string - there's no point adding the NUL till the end.
+
 */
 */
+/* coverity[+alloc] */
 
 uschar *
 
 uschar *
-string_cat(uschar *string, int *size, int *ptr, const uschar *s, int count)
+string_catn(uschar *string, int *size, int *ptr, const uschar *s, int count)
 {
 int p = *ptr;
 
 {
 int p = *ptr;
 
@@ -883,12 +1134,25 @@ if (p + count >= *size)
 
 /* Because we always specify the exact number of characters to copy, we can
 use memcpy(), which is likely to be more efficient than strncopy() because the
 
 /* Because we always specify the exact number of characters to copy, we can
 use memcpy(), which is likely to be more efficient than strncopy() because the
-latter has to check for zero bytes. */
+latter has to check for zero bytes.
+
+The Coverity annotation deals with the lack of correlated variable tracking;
+common use is a null string and zero size and pointer, on first use for a
+string being built. The "if" above then allocates, but Coverity assume that
+the "if" might not happen and whines for a null-deref done by the memcpy(). */
 
 
+/* coverity[deref_parm_field_in_call] : FALSE */
 memcpy(string + p, s, count);
 *ptr = p + count;
 return string;
 }
 memcpy(string + p, s, count);
 *ptr = p + count;
 return string;
 }
+
+
+uschar *
+string_cat(uschar *string, int *size, int *ptr, const uschar *s)
+{
+return string_catn(string, size, ptr, s, Ustrlen(s));
+}
 #endif  /* COMPILE_UTILITY */
 
 
 #endif  /* COMPILE_UTILITY */
 
 
@@ -926,7 +1190,7 @@ va_start(ap, count);
 for (i = 0; i < count; i++)
   {
   uschar *t = va_arg(ap, uschar *);
 for (i = 0; i < count; i++)
   {
   uschar *t = va_arg(ap, uschar *);
-  string = string_cat(string, size, ptr, t, Ustrlen(t));
+  string = string_cat(string, size, ptr, t);
   }
 va_end(ap);
 
   }
 va_end(ap);
 
@@ -948,10 +1212,10 @@ on whether the variable length list of data arguments are given explicitly or
 as a va_list item.
 
 The formats are the usual printf() ones, with some omissions (never used) and
 as a va_list item.
 
 The formats are the usual printf() ones, with some omissions (never used) and
-two additions for strings: %S forces lower case, and %#s or %#S prints nothing
-for a NULL string. Without the # "NULL" is printed (useful in debugging). There
-is also the addition of %D, which inserts the date in the form used for
-datestamped log files.
+three additions for strings: %S forces lower case, %T forces upper case, and
+%#s or %#S prints nothing for a NULL string. Without thr # "NULL" is printed
+(useful in debugging). There is also the addition of %D and %M, which insert
+the date in the form used for datestamped log files.
 
 Arguments:
   buffer       a buffer in which to put the formatted string
 
 Arguments:
   buffer       a buffer in which to put the formatted string
@@ -963,7 +1227,7 @@ Returns:       TRUE if the result fitted in the buffer
 */
 
 BOOL
 */
 
 BOOL
-string_format(uschar *buffer, int buflen, char *format, ...)
+string_format(uschar *buffer, int buflen, const char *format, ...)
 {
 BOOL yield;
 va_list ap;
 {
 BOOL yield;
 va_list ap;
@@ -975,17 +1239,20 @@ return yield;
 
 
 BOOL
 
 
 BOOL
-string_vformat(uschar *buffer, int buflen, char *format, va_list ap)
+string_vformat(uschar *buffer, int buflen, const char *format, va_list ap)
 {
 {
-enum { L_NORMAL, L_SHORT, L_LONG, L_LONGLONG, L_LONGDOUBLE };
+/* We assume numbered ascending order, C does not guarantee that */
+enum { L_NORMAL=1, L_SHORT=2, L_LONG=3, L_LONGLONG=4, L_LONGDOUBLE=5, L_SIZE=6 };
 
 BOOL yield = TRUE;
 int width, precision;
 
 BOOL yield = TRUE;
 int width, precision;
-char *fp = format;             /* Deliberately not unsigned */
+const char *fp = format;       /* Deliberately not unsigned */
 uschar *p = buffer;
 uschar *last = buffer + buflen - 1;
 
 string_datestamp_offset = -1;  /* Datestamp not inserted */
 uschar *p = buffer;
 uschar *last = buffer + buflen - 1;
 
 string_datestamp_offset = -1;  /* Datestamp not inserted */
+string_datestamp_length = 0;   /* Datestamp not inserted */
+string_datestamp_type = 0;     /* Datestamp not inserted */
 
 /* Scan the format and handle the insertions */
 
 
 /* Scan the format and handle the insertions */
 
@@ -994,8 +1261,8 @@ while (*fp != 0)
   int length = L_NORMAL;
   int *nptr;
   int slen;
   int length = L_NORMAL;
   int *nptr;
   int slen;
-  char *null = "NULL";         /* ) These variables */
-  char *item_start, *s;        /* ) are deliberately */
+  const char *null = "NULL";   /* ) These variables */
+  const char *item_start, *s;  /* ) are deliberately */
   char newformat[16];          /* ) not unsigned */
 
   /* Non-% characters just get copied verbatim */
   char newformat[16];          /* ) not unsigned */
 
   /* Non-% characters just get copied verbatim */
@@ -1045,7 +1312,7 @@ while (*fp != 0)
       }
     }
 
       }
     }
 
-  /* Skip over 'h', 'L', 'l', and 'll', remembering the item length */
+  /* Skip over 'h', 'L', 'l', 'll' and 'z', remembering the item length */
 
   if (*fp == 'h')
     { fp++; length = L_SHORT; }
 
   if (*fp == 'h')
     { fp++; length = L_SHORT; }
@@ -1064,6 +1331,8 @@ while (*fp != 0)
       length = L_LONG;
       }
     }
       length = L_LONG;
       }
     }
+  else if (*fp == 'z')
+    { fp++; length = L_SIZE; }
 
   /* Handle each specific format type. */
 
 
   /* Handle each specific format type. */
 
@@ -1093,6 +1362,7 @@ while (*fp != 0)
       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;
       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;
+      case L_SIZE:     sprintf(CS p, newformat, va_arg(ap, size_t)); break;
       }
     while (*p) p++;
     break;
       }
     while (*p) p++;
     break;
@@ -1140,19 +1410,32 @@ while (*fp != 0)
     *p++ = va_arg(ap, int);
     break;
 
     *p++ = va_arg(ap, int);
     break;
 
-    case 'D':                   /* Insert datestamp for log file names */
-    s = CS tod_stamp(tod_log_datestamp);
+    case 'D':                   /* Insert daily datestamp for log file names */
+    s = CS tod_stamp(tod_log_datestamp_daily);
+    string_datestamp_offset = p - buffer;   /* Passed back via global */
+    string_datestamp_length = Ustrlen(s);   /* Passed back via global */
+    string_datestamp_type = tod_log_datestamp_daily;
+    slen = string_datestamp_length;
+    goto INSERT_STRING;
+
+    case 'M':                   /* Insert monthly datestamp for log file names */
+    s = CS tod_stamp(tod_log_datestamp_monthly);
     string_datestamp_offset = p - buffer;   /* Passed back via global */
     string_datestamp_offset = p - buffer;   /* Passed back via global */
+    string_datestamp_length = Ustrlen(s);   /* Passed back via global */
+    string_datestamp_type = tod_log_datestamp_monthly;
+    slen = string_datestamp_length;
     goto INSERT_STRING;
 
     case 's':
     case 'S':                   /* Forces *lower* case */
     goto INSERT_STRING;
 
     case 's':
     case 'S':                   /* Forces *lower* case */
+    case 'T':                   /* Forces *upper* case */
     s = va_arg(ap, char *);
 
     s = va_arg(ap, char *);
 
-    INSERT_STRING:              /* Come to from %D above */
     if (s == NULL) s = null;
     slen = Ustrlen(s);
 
     if (s == NULL) s = null;
     slen = Ustrlen(s);
 
+    INSERT_STRING:              /* Come to from %D or %M above */
+
     /* If the width is specified, check that there is a precision
     set; if not, set it to the width to prevent overruns of long
     strings. */
     /* If the width is specified, check that there is a precision
     set; if not, set it to the width to prevent overruns of long
     strings. */
@@ -1178,14 +1461,23 @@ 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')
       while (*p) { *p = tolower(*p); p++; }
       }
     sprintf(CS p, "%*.*s", width, precision, s);
     if (fp[-1] == 'S')
       while (*p) { *p = tolower(*p); p++; }
+    else if (fp[-1] == 'T')
+      while (*p) { *p = toupper(*p); p++; }
     else
       while (*p) p++;
     if (!yield) goto END_FORMAT;
     else
       while (*p) p++;
     if (!yield) goto END_FORMAT;
@@ -1230,7 +1522,7 @@ Returns:        a message, in dynamic store
 */
 
 uschar *
 */
 
 uschar *
-string_open_failed(int eno, char *format, ...)
+string_open_failed(int eno, const char *format, ...)
 {
 va_list ap;
 uschar buffer[1024];
 {
 va_list ap;
 uschar buffer[1024];
@@ -1244,6 +1536,7 @@ specified messages. If it does, the message just gets truncated, and there
 doesn't seem much we can do about that. */
 
 (void)string_vformat(buffer+15, sizeof(buffer) - 15, format, ap);
 doesn't seem much we can do about that. */
 
 (void)string_vformat(buffer+15, sizeof(buffer) - 15, format, ap);
+va_end(ap);
 
 return (eno == EACCES)?
   string_sprintf("%s: %s (euid=%ld egid=%ld)", buffer, strerror(eno),
 
 return (eno == EACCES)?
   string_sprintf("%s: %s (euid=%ld egid=%ld)", buffer, strerror(eno),
@@ -1254,159 +1547,19 @@ return (eno == EACCES)?
 
 
 
 
 
 
-#ifndef COMPILE_UTILITY
-/*************************************************
-*        Generate local prt for logging          *
-*************************************************/
-
-/* This function is a subroutine for use in string_log_address() below.
 
 
-Arguments:
-  addr        the address being logged
-  yield       the current dynamic buffer pointer
-  sizeptr     points to current size
-  ptrptr      points to current insert pointer
 
 
-Returns:      the new value of the buffer pointer
-*/
+#ifndef COMPILE_UTILITY
+/* qsort(3), currently used to sort the environment variables
+for -bP environment output, needs a function to compare two pointers to string
+pointers. Here it is. */
 
 
-static uschar *
-string_get_localpart(address_item *addr, uschar *yield, int *sizeptr,
-  int *ptrptr)
+int
+string_compare_by_pointer(const void *a, const void *b)
 {
 {
-if (testflag(addr, af_include_affixes) && addr->prefix != NULL)
-  yield = string_cat(yield, sizeptr, ptrptr, addr->prefix,
-    Ustrlen(addr->prefix));
-yield = string_cat(yield, sizeptr, ptrptr, addr->local_part,
-  Ustrlen(addr->local_part));
-if (testflag(addr, af_include_affixes) && addr->suffix != NULL)
-  yield = string_cat(yield, sizeptr, ptrptr, addr->suffix,
-    Ustrlen(addr->suffix));
-return yield;
+return Ustrcmp(* CUSS a, * CUSS b);
 }
 }
-
-
-/*************************************************
-*          Generate log address list             *
-*************************************************/
-
-/* This function generates a list consisting of an address and its parents, for
-use in logging lines. For saved onetime aliased addresses, the onetime parent
-field is used. If the address was delivered by a transport with rcpt_include_
-affixes set, the af_include_affixes bit will be set in the address. In that
-case, we include the affixes here too.
-
-Arguments:
-  addr          bottom (ultimate) address
-  all_parents   if TRUE, include all parents
-  success       TRUE for successful delivery
-
-Returns:        a string in dynamic store
-*/
-
-uschar *
-string_log_address(address_item *addr, BOOL all_parents, BOOL success)
-{
-int size = 64;
-int ptr = 0;
-BOOL add_topaddr = TRUE;
-uschar *yield = store_get(size);
-address_item *topaddr;
-
-/* Find the ultimate parent */
-
-for (topaddr = addr; topaddr->parent != NULL; topaddr = topaddr->parent);
-
-/* We start with just the local part for pipe, file, and reply deliveries, and
-for successful local deliveries from routers that have the log_as_local flag
-set. File deliveries from filters can be specified as non-absolute paths in
-cases where the transport is goin to complete the path. If there is an error
-before this happens (expansion failure) the local part will not be updated, and
-so won't necessarily look like a path. Add extra text for this case. */
-
-if (testflag(addr, af_pfr) ||
-      (success &&
-       addr->router != NULL && addr->router->log_as_local &&
-       addr->transport != NULL && addr->transport->info->local))
-  {
-  if (testflag(addr, af_file) && addr->local_part[0] != '/')
-    yield = string_cat(yield, &size, &ptr, CUS"save ", 5);
-  yield = string_get_localpart(addr, yield, &size, &ptr);
-  }
-
-/* Other deliveries start with the full address. It we have split it into local
-part and domain, use those fields. Some early failures can happen before the
-splitting is done; in those cases use the original field. */
-
-else
-  {
-  if (addr->local_part != NULL)
-    {
-    yield = string_get_localpart(addr, yield, &size, &ptr);
-    yield = string_cat(yield, &size, &ptr, US"@", 1);
-    yield = string_cat(yield, &size, &ptr, addr->domain,
-      Ustrlen(addr->domain) );
-    }
-  else
-    {
-    yield = string_cat(yield, &size, &ptr, addr->address, Ustrlen(addr->address));
-    }
-  yield[ptr] = 0;
-
-  /* If the address we are going to print is the same as the top address,
-  and all parents are not being included, don't add on the top address. First
-  of all, do a caseless comparison; if this succeeds, do a caseful comparison
-  on the local parts. */
-
-  if (strcmpic(yield, topaddr->address) == 0 &&
-      Ustrncmp(yield, topaddr->address, Ustrchr(yield, '@') - yield) == 0 &&
-      addr->onetime_parent == NULL &&
-      (!all_parents || addr->parent == NULL || addr->parent == topaddr))
-    add_topaddr = FALSE;
-  }
-
-/* If all parents are requested, or this is a local pipe/file/reply, and
-there is at least one intermediate parent, show it in brackets, and continue
-with all of them if all are wanted. */
-
-if ((all_parents || testflag(addr, af_pfr)) &&
-    addr->parent != NULL &&
-    addr->parent != topaddr)
-  {
-  uschar *s = US" (";
-  address_item *addr2;
-  for (addr2 = addr->parent; addr2 != topaddr; addr2 = addr2->parent)
-    {
-    yield = string_cat(yield, &size, &ptr, s, 2);
-    yield = string_cat(yield, &size, &ptr, addr2->address, Ustrlen(addr2->address));
-    if (!all_parents) break;
-    s = US", ";
-    }
-  yield = string_cat(yield, &size, &ptr, US")", 1);
-  }
-
-/* Add the top address if it is required */
-
-if (add_topaddr)
-  {
-  yield = string_cat(yield, &size, &ptr, US" <", 2);
-
-  if (addr->onetime_parent == NULL)
-    yield = string_cat(yield, &size, &ptr, topaddr->address,
-      Ustrlen(topaddr->address));
-  else
-    yield = string_cat(yield, &size, &ptr, addr->onetime_parent,
-      Ustrlen(addr->onetime_parent));
-
-  yield = string_cat(yield, &size, &ptr, US">", 1);
-  }
-
-yield[ptr] = 0;  /* string_cat() leaves space */
-return yield;
-}
-#endif  /* COMPILE_UTILITY */
-
-
+#endif /* COMPILE_UTILITY */