DNS: explicit alloc/free of workspace
[exim.git] / src / src / string.c
index 97d71d3a4d157bbec1cb0d6daf5c10f01e409d2b..afdb517a25e146bd4d64e114c3eb2117cb324ed9 100644 (file)
@@ -3,6 +3,7 @@
 *************************************************/
 
 /* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Miscellaneous string-handling functions. Some are not required for
@@ -12,7 +13,6 @@ utilities and tests, and are cut out by the COMPILE_UTILITY macro. */
 #include "exim.h"
 #include <assert.h>
 
-static void gstring_rebuffer(gstring * g);
 
 #ifndef COMPILE_UTILITY
 /*************************************************
@@ -281,17 +281,17 @@ return ch;
 /* This function is called for critical strings. It checks for any
 non-printing characters, and if any are found, it makes a new copy
 of the string with suitable escape sequences. It is most often called by the
-macro string_printing(), which sets allow_tab TRUE.
+macro string_printing(), which sets flags to 0.
 
 Arguments:
   s             the input string
-  allow_tab     TRUE to allow tab as a printing character
+  flags                Bit 0: convert tabs.  Bit 1: convert spaces.
 
 Returns:        string with non-printers encoded as printing sequences
 */
 
 const uschar *
-string_printing2(const uschar *s, BOOL allow_tab)
+string_printing2(const uschar *s, int flags)
 {
 int nonprintcount = 0;
 int length = 0;
@@ -301,7 +301,10 @@ uschar *ss, *tt;
 while (*t != 0)
   {
   int c = *t++;
-  if (!mac_isprint(c) || (!allow_tab && c == '\t')) nonprintcount++;
+  if (  !mac_isprint(c)
+     || flags & SP_TAB && c == '\t'
+     || flags & SP_SPACE && c == ' '
+     ) nonprintcount++;
   length++;
   }
 
@@ -310,17 +313,19 @@ if (nonprintcount == 0) return s;
 /* Get a new block of store guaranteed big enough to hold the
 expanded string. */
 
-ss = store_get(length + nonprintcount * 3 + 1, is_tainted(s));
+tt = ss = store_get(length + nonprintcount * 3 + 1, is_tainted(s));
 
 /* Copy everything, escaping non printers. */
 
-t = s;
-tt = ss;
-
-while (*t != 0)
+for (t = s; *t; )
   {
   int c = *t;
-  if (mac_isprint(c) && (allow_tab || c != '\t')) *tt++ = *t++; else
+  if (  mac_isprint(c)
+     && (!(flags & SP_TAB) || c != '\t')
+     && (!(flags & SP_SPACE) || c != ' ')
+     )
+    *tt++ = *t++;
+  else
     {
     *tt++ = '\\';
     switch (*t)
@@ -570,23 +575,19 @@ uschar *
 string_copy_dnsdomain(uschar *s)
 {
 uschar *yield;
-uschar *ss = yield = store_get(Ustrlen(s) + 1, is_tainted(s));
+uschar *ss = yield = store_get(Ustrlen(s) + 1, TRUE);  /* always treat as tainted */
 
 while (*s != 0)
   {
   if (*s != '\\')
-    {
     *ss++ = *s++;
-    }
   else if (isdigit(s[1]))
     {
     *ss++ = (s[1] - '0')*100 + (s[2] - '0')*10 + s[3] - '0';
     s += 4;
     }
   else if (*(++s) != 0)
-    {
     *ss++ = *s++;
-    }
   }
 
 *ss = 0;
@@ -678,12 +679,20 @@ Returns:    pointer to fresh piece of store containing sprintf'ed string
 uschar *
 string_sprintf_trc(const char *format, const uschar * func, unsigned line, ...)
 {
-gstring * g;
-va_list ap;
+#ifdef COMPILE_UTILITY
+uschar buffer[STRING_SPRINTF_BUFFER_SIZE];
+gstring gs = { .size = STRING_SPRINTF_BUFFER_SIZE, .ptr = 0, .s = buffer };
+gstring * g = &gs;
+unsigned flags = 0;
+#else
+gstring * g = NULL;
+unsigned flags = SVFMT_REBUFFER|SVFMT_EXTEND;
+#endif
 
+va_list ap;
 va_start(ap, line);
-g = string_vformat_trc(NULL, func, line, STRING_SPRINTF_BUFFER_SIZE,
-       SVFMT_REBUFFER|SVFMT_EXTEND, format, ap);
+g = string_vformat_trc(g, func, line, STRING_SPRINTF_BUFFER_SIZE,
+       flags, format, ap);
 va_end(ap);
 
 if (!g)
@@ -692,8 +701,12 @@ if (!g)
     " called from %s %d\n",
     STRING_SPRINTF_BUFFER_SIZE, format, func, line);
 
+#ifdef COMPILE_UTILITY
+return string_copyn(g->s, g->ptr);
+#else
 gstring_release_unused(g);
 return string_from_gstring(g);
+#endif
 }
 
 
@@ -845,6 +858,9 @@ Arguments:
   separator  a pointer to the separator character in an int (see above)
   buffer     where to put a copy of the next string in the list; or
                NULL if the next string is returned in new memory
+            Note that if the list is tainted then a provided buffer must be
+            also (else we trap, with a message referencing the callsite).
+            If we do the allocation, taint is handled there.
   buflen     when buffer is not NULL, the size of buffer; otherwise ignored
 
 Returns:     pointer to buffer, containing the next substring,
@@ -852,7 +868,8 @@ Returns:     pointer to buffer, containing the next substring,
 */
 
 uschar *
-string_nextinlist(const uschar **listptr, int *separator, uschar *buffer, int buflen)
+string_nextinlist_trc(const uschar **listptr, int *separator, uschar *buffer, int buflen,
+ const uschar * func, int line)
 {
 int sep = *separator;
 const uschar *s = *listptr;
@@ -895,6 +912,8 @@ sep_is_special = iscntrl(sep);
 if (buffer)
   {
   int p = 0;
+  if (is_tainted(s) && !is_tainted(buffer))
+    die_tainted(US"string_nextinlist", func, line);
   for (; *s; s++)
     {
     if (*s == sep && (*(++s) != sep || sep_is_special)) break;
@@ -916,14 +935,11 @@ else
   start of a string. Avoid getting working memory for an empty item. */
 
   if (*s == sep)
-    {
-    s++;
-    if (*s != sep || sep_is_special)
+    if (*++s != sep || sep_is_special)
       {
       *listptr = s;
       return string_copy(US"");
       }
-    }
 
   /* Not an empty string; the first character is guaranteed to be a data
   character. */
@@ -936,7 +952,10 @@ else
     s = ss;
     if (!*s || *++s != sep || sep_is_special) break;
     }
-  while (g->ptr > 0 && isspace(g->s[g->ptr-1])) g->ptr--;
+  /* while (g->ptr > 0 && isspace(g->s[g->ptr-1])) g->ptr--; */
+  while (  g->ptr > 0 && isspace(g->s[g->ptr-1])
+       && (g->ptr == 1 || g->s[g->ptr-2] != '\\') )
+    g->ptr--;
   buffer = string_from_gstring(g);
   gstring_release_unused(g);
   }
@@ -1231,22 +1250,26 @@ return !!gp;
 
 
 
-/* Copy the content of a string to tainted memory */
-static void
-gstring_rebuffer(gstring * g)
-{
-uschar * s = store_get(g->size, TRUE);
-memcpy(s, g->s, g->ptr);
-g->s = s;
-}
-
-
 
 /* Build or append to a growing-string, sprintf-style.
 
+Arguments:
+       g       a growable-string
+       func    called-from function name, for debug
+       line    called-from file line number, for debug
+       limit   maximum string size
+       flags   see below
+       format  printf-like format string
+       ap      variable-args pointer
+
+Flags:
+       SVFMT_EXTEND            buffer can be created or exteded as needed
+       SVFMT_REBUFFER          buffer can be recopied to tainted mem as needed
+       SVFMT_TAINT_NOCHK       do not check inputs for taint
+
 If the "extend" flag is true, the string passed in can be NULL,
 empty, or non-empty.  Growing is subject to an overall limit given
-by the size_limit argument.
+by the limit argument.
 
 If the "extend" flag is false, the string passed in may not be NULL,
 will not be grown, and is usable in the original place after return.
@@ -1600,7 +1623,6 @@ string supplied as data, adds the strerror() text, and if the failure was
 "Permission denied", reads and includes the euid and egid.
 
 Arguments:
-  eno           the value of errno after the failure
   format        a text format string - deliberately not uschar *
   ...           arguments for the format string
 
@@ -1608,7 +1630,7 @@ Returns:        a message, in dynamic store
 */
 
 uschar *
-string_open_failed_trc(int eno, const uschar * func, unsigned line,
+string_open_failed_trc(const uschar * func, unsigned line,
   const char *format, ...)
 {
 va_list ap;
@@ -1623,23 +1645,27 @@ doesn't seem much we can do about that. */
 
 va_start(ap, format);
 (void) string_vformat_trc(g, func, line, STRING_SPRINTF_BUFFER_SIZE,
-       0, format, ap);
-string_from_gstring(g);
-gstring_release_unused(g);
+       SVFMT_REBUFFER, format, ap);
 va_end(ap);
 
-return eno == EACCES
-  ? string_sprintf("%s: %s (euid=%ld egid=%ld)", g->s, strerror(eno),
-    (long int)geteuid(), (long int)getegid())
-  : string_sprintf("%s: %s", g->s, strerror(eno));
+g = string_catn(g, US": ", 2);
+g = string_cat(g, US strerror(errno));
+
+if (errno == EACCES)
+  {
+  int save_errno = errno;
+  g = string_fmt_append(g, " (euid=%ld egid=%ld)",
+    (long int)geteuid(), (long int)getegid());
+  errno = save_errno;
+  }
+gstring_release_unused(g);
+return string_from_gstring(g);
 }
-#endif  /* COMPILE_UTILITY */
 
 
 
 
 
-#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. */
@@ -1666,6 +1692,7 @@ int main(void)
 uschar buffer[256];
 
 printf("Testing is_ip_address\n");
+store_init();
 
 while (fgets(CS buffer, sizeof(buffer), stdin) != NULL)
   {