Merge branch 'master' of ssh://git.exim.org/home/git/exim
[exim.git] / src / src / string.c
index 62678b14500f68c9752a2b38909b325b82d8227b..914390255f43f59e55a919f00a3fb3d0d55d8689 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/string.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* Copyright (c) University of Cambridge 1995 - 2014 */
 /* 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 +26,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
@@ -35,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;
@@ -45,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)))
     {
@@ -127,7 +126,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 +140,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 */
 
@@ -208,7 +210,6 @@ return yield;
 
 
 
 
 
 
-#ifndef COMPILE_UTILITY
 /*************************************************
 *          Interpret escape sequence             *
 *************************************************/
 /*************************************************
 *          Interpret escape sequence             *
 *************************************************/
@@ -225,6 +226,9 @@ Returns:   the value of the character escape
 int
 string_interpret_escape(uschar **pp)
 {
 int
 string_interpret_escape(uschar **pp)
 {
+#ifdef COMPILE_UTILITY
+const uschar *hex_digits= CUS"0123456789abcdef";
+#endif
 int ch;
 uschar *p = *pp;
 ch = *(++p);
 int ch;
 uschar *p = *pp;
 ch = *(++p);
@@ -240,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]))
@@ -257,7 +264,6 @@ else switch(ch)
 *pp = p;
 return ch;
 }
 *pp = p;
 return ch;
 }
-#endif  /* COMPILE_UTILITY */
 
 
 
 
 
 
@@ -329,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(&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;
+}
+
 
 
 
 
 
 
@@ -343,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);
@@ -445,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   *
 *************************************************/
@@ -576,14 +710,15 @@ 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 " (%s)",
+    sizeof(buffer), format);
 va_end(ap);
 return string_copy(buffer);
 }
 va_end(ap);
 return string_copy(buffer);
 }
@@ -604,7 +739,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--)
   {
@@ -628,7 +763,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)
   {
@@ -699,19 +834,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 +870,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 +900,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 +926,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 */
@@ -804,6 +967,50 @@ return buffer;
 #endif  /* COMPILE_UTILITY */
 
 
 #endif  /* COMPILE_UTILITY */
 
 
+#ifndef COMPILE_UTILITY
+/************************************************
+*      Add element to seperated 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, Ustrlen(list));
+  new = string_cat(new, &sz, &off, &sep, 1);
+  }
+
+while((sp = Ustrchr(ele, sep)))
+  {
+  new = string_cat(new, &sz, &off, ele, sp-ele+1);
+  new = string_cat(new, &sz, &off, &sep, 1);
+  ele = sp+1;
+  }
+new = string_cat(new, &sz, &off, ele, Ustrlen(ele));
+new[off] = '\0';
+return new;
+}
+#endif  /* COMPILE_UTILITY */
+
+
 
 #ifndef COMPILE_UTILITY
 /*************************************************
 
 #ifndef COMPILE_UTILITY
 /*************************************************
@@ -944,9 +1151,9 @@ 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, %#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
+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 and %M, which insert the date in the form used for
 datestamped log files.
 
 Arguments:
 datestamped log files.
 
 Arguments:
@@ -959,7 +1166,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;
@@ -971,24 +1178,30 @@ 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)
 {
 {
+/* 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 */
 
 while (*fp != 0)
   {
 
 /* Scan the format and handle the insertions */
 
 while (*fp != 0)
   {
+  int length = L_NORMAL;
   int *nptr;
   int slen;
   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 */
@@ -1038,7 +1251,27 @@ while (*fp != 0)
       }
     }
 
       }
     }
 
-  if (strchr("hlL", *fp) != NULL) fp++;
+  /* Skip over 'h', 'L', 'l', 'll' and 'z', 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;
+      }
+    }
+  else if (*fp == 'z')
+    { fp++; length = L_SIZE; }
 
   /* Handle each specific format type. */
 
 
   /* Handle each specific format type. */
 
@@ -1054,10 +1287,22 @@ 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;
+      case L_SIZE:     sprintf(CS p, newformat, va_arg(ap, size_t)); break;
+      }
     while (*p) p++;
     break;
 
     while (*p) p++;
     break;
 
@@ -1070,9 +1315,11 @@ while (*fp != 0)
     break;
 
     /* %f format is inherently insecure if the numbers that it may be
     break;
 
     /* %f format is inherently insecure if the numbers that it may be
-    handed are unknown (e.g. 1e300). However, in Exim, the only use of %f
-    is for printing load averages, and these are actually stored as integers
-    (load average * 1000) so the size of the numbers is constrained. */
+    handed are unknown (e.g. 1e300). However, in Exim, %f is used for
+    printing load averages, and these are actually stored as integers
+    (load average * 1000) so the size of the numbers is constrained.
+    It is also used for formatting sending rates, where the simplicity
+    of the format prevents overflow. */
 
     case 'f':
     case 'e':
 
     case 'f':
     case 'e':
@@ -1083,7 +1330,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;
 
@@ -1099,19 +1349,31 @@ 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 */
     s = va_arg(ap, char *);
 
     goto INSERT_STRING;
 
     case 's':
     case 'S':                   /* Forces *lower* case */
     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. */
@@ -1137,10 +1399,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')
@@ -1189,7 +1458,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];
@@ -1431,8 +1700,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;
@@ -1463,6 +1734,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);
@@ -1485,11 +1761,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);