X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/8aa16eb712afac844fb28ed465c6076c6b0ea22b..d8b76fa95c55331db4f475ee34caa7e8725ec421:/src/src/string.c diff --git a/src/src/string.c b/src/src/string.c index fbdc0246d..8ecbc0cd7 100644 --- a/src/src/string.c +++ b/src/src/string.c @@ -3,6 +3,7 @@ *************************************************/ /* Copyright (c) University of Cambridge 1995 - 2018 */ +/* Copyright (c) The Exim Maintainers 2020 - 2021 */ /* 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 -static void gstring_rebuffer(gstring * g); #ifndef COMPILE_UTILITY /************************************************* @@ -281,27 +281,30 @@ 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; const uschar *t = s; uschar *ss, *tt; -while (*t != 0) +while (*t) { 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) @@ -428,17 +433,13 @@ string_copy_function(const uschar *s) return string_copy_taint(s, is_tainted(s)); } -/* This function assumes that memcpy() is faster than strcpy(). -As above, but explicitly specifying the result taint status +/* As above, but explicitly specifying the result taint status */ uschar * string_copy_taint_function(const uschar * s, BOOL tainted) { -int len = Ustrlen(s) + 1; -uschar *ss = store_get(len, tainted); -memcpy(ss, s, len); -return ss; +return string_copy_taint(s, tainted); } @@ -458,12 +459,9 @@ Returns: copy of string in new store */ uschar * -string_copyn_function(const uschar *s, int n) +string_copyn_function(const uschar * s, int n) { -uschar *ss = store_get(n + 1, is_tainted(s)); -Ustrncpy(ss, s, n); -ss[n] = 0; -return ss; +return string_copyn(s, n); } #endif @@ -479,10 +477,10 @@ Returns: copy of string in new store */ uschar * -string_copy_malloc(const uschar *s) +string_copy_malloc(const uschar * s) { int len = Ustrlen(s) + 1; -uschar *ss = store_malloc(len); +uschar * ss = store_malloc(len); memcpy(ss, s, len); return ss; } @@ -501,37 +499,37 @@ Returns: pointer to the possibly altered string */ uschar * -string_split_message(uschar *msg) +string_split_message(uschar * msg) { uschar *s, *ss; -if (msg == NULL || Ustrlen(msg) <= 75) return msg; +if (!msg || 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; + while (i < 75 && *ss && *ss != '\n') ss++, i++; + if (!*ss) break; if (*ss == '\n') s = ++ss; else { - uschar *t = ss + 1; - uschar *tt = NULL; + 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) tt = t; } } - if (tt == NULL) /* Can't split behind - try ahead */ + if (!tt) /* Can't split behind - try ahead */ { t = ss + 1; - while (*t != 0) + while (*t) { if (*t == ' ' || *t == '\n') { tt = t; break; } @@ -539,7 +537,7 @@ for (;;) } } - if (tt == NULL) break; /* Can't find anywhere to split */ + if (!tt) break; /* Can't find anywhere to split */ *tt = '\n'; s = ss = tt+1; } @@ -567,26 +565,22 @@ Returns: copy of string in new store, de-escaped */ uschar * -string_copy_dnsdomain(uschar *s) +string_copy_dnsdomain(uschar * s) { -uschar *yield; -uschar *ss = yield = store_get(Ustrlen(s) + 1, is_tainted(s)); +uschar * yield; +uschar * ss = yield = store_get(Ustrlen(s) + 1, TRUE); /* always treat as tainted */ -while (*s != 0) +while (*s) { 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) - { + else if (*++s) *ss++ = *s++; - } } *ss = 0; @@ -610,15 +604,15 @@ Returns: the new string */ uschar * -string_dequote(const uschar **sptr) +string_dequote(const uschar ** sptr) { -const uschar *s = *sptr; -uschar *t, *yield; +const uschar * s = * sptr; +uschar * t, * yield; /* First find the end of the string */ if (*s != '\"') - while (*s != 0 && !isspace(*s)) s++; + while (*s && !isspace(*s)) s++; else { s++; @@ -638,11 +632,11 @@ s = *sptr; /* Do the copy */ if (*s != '\"') - while (*s != 0 && !isspace(*s)) *t++ = *s++; + while (*s && !isspace(*s)) *t++ = *s++; else { s++; - while (*s != 0 && *s != '\"') + while (*s && *s != '\"') { *t++ = *s == '\\' ? string_interpret_escape(&s) : *s; s++; @@ -670,13 +664,15 @@ everything. Taint is taken from the worst of the arguments. Arguments: format a printf() format - deliberately char * rather than uschar * because it will most usually be a literal string + func caller, for debug + line caller, for debug ... arguments for format Returns: pointer to fresh piece of store containing sprintf'ed string */ uschar * -string_sprintf_trc(const char *format, const uschar * func, unsigned line, ...) +string_sprintf_trc(const char * format, const uschar * func, unsigned line, ...) { #ifdef COMPILE_UTILITY uschar buffer[STRING_SPRINTF_BUFFER_SIZE]; @@ -724,7 +720,7 @@ Returns: < 0, = 0, or > 0, according to the comparison */ int -strncmpic(const uschar *s, const uschar *t, int n) +strncmpic(const uschar * s, const uschar * t, int n) { while (n--) { @@ -748,9 +744,9 @@ Returns: < 0, = 0, or > 0, according to the comparison */ int -strcmpic(const uschar *s, const uschar *t) +strcmpic(const uschar * s, const uschar * t) { -while (*s != 0) +while (*s) { int c = tolower(*s++) - tolower(*t++); if (c != 0) return c; @@ -775,10 +771,10 @@ Returns: pointer to substring in string, or NULL if not found */ uschar * -strstric(uschar *s, uschar *t, BOOL space_follows) +strstric(uschar * s, uschar * t, BOOL space_follows) { -uschar *p = t; -uschar *yield = NULL; +uschar * p = t; +uschar * yield = NULL; int cl = tolower(*p); int cu = toupper(*p); @@ -786,8 +782,8 @@ while (*s) { if (*s == cl || *s == cu) { - if (yield == NULL) yield = s; - if (*(++p) == 0) + if (!yield) yield = s; + if (!*++p) { if (!space_follows || s[1] == ' ' || s[1] == '\n' ) return yield; yield = NULL; @@ -797,7 +793,7 @@ while (*s) cu = toupper(*p); s++; } - else if (yield != NULL) + else if (yield) { yield = NULL; p = t; @@ -857,17 +853,24 @@ 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 + func caller, for debug + line caller, for debug + Returns: pointer to buffer, containing the next substring, or NULL if no more substrings */ 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; +const uschar * s = *listptr; BOOL sep_is_special; if (!s) return NULL; @@ -907,6 +910,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; @@ -928,14 +933,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. */ @@ -948,9 +950,15 @@ else s = ss; if (!*s || *++s != sep || sep_is_special) break; } - while (g->ptr > 0 && isspace(g->s[g->ptr-1])) g->ptr--; + + /* Trim trailing spaces from the returned string */ + + /* 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); + gstring_release_unused_trc(g, CCS func, line); } /* Update the current pointer and return the new string */ @@ -1084,7 +1092,16 @@ existing length of the string. */ unsigned inc = oldsize < 4096 ? 127 : 1023; +if (g->ptr < 0 || g->ptr > g->size || g->size >= INT_MAX/2) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, + "internal error in gstring_grow (ptr %d size %d)", g->ptr, g->size); + if (count <= 0) return; + +if (count >= INT_MAX/2 - g->ptr) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, + "internal error in gstring_grow (ptr %d count %d)", g->ptr, count); + g->size = (p + count + inc + 1) & ~inc; /* one for a NUL */ /* Try to extend an existing allocation. If the result of calling @@ -1113,16 +1130,16 @@ terminated, because the number of characters to add is given explicitly. It is sometimes called to extract parts of other strings. Arguments: - string points to the start of the string that is being built, or NULL - if this is a new string that has no contents yet + g growable-string that is being built, or NULL if not assigned yet 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. -Returns: pointer to the start of the string, changed if copied for expansion. +Returns: growable 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. + NULL is a possible return. */ /* coverity[+alloc] */ @@ -1133,17 +1150,26 @@ string_catn(gstring * g, const uschar *s, int count) int p; BOOL srctaint = is_tainted(s); +if (count < 0) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, + "internal error in string_catn (count %d)", count); +if (count == 0) return g; + if (!g) { unsigned inc = count < 4096 ? 127 : 1023; - unsigned size = ((count + inc) & ~inc) + 1; + unsigned size = ((count + inc) & ~inc) + 1; /* round up requested count */ g = string_get_tainted(size, srctaint); } else if (srctaint && !is_tainted(g->s)) gstring_rebuffer(g); +if (g->ptr < 0 || g->ptr > g->size) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, + "internal error in string_catn (ptr %d size %d)", g->ptr, g->size); + p = g->ptr; -if (p + count >= g->size) +if (count >= g->size - p) gstring_grow(g, count); /* Because we always specify the exact number of characters to copy, we can @@ -1157,9 +1183,9 @@ return g; gstring * -string_cat(gstring *string, const uschar *s) +string_cat(gstring * g, const uschar * s) { -return string_catn(string, s, Ustrlen(s)); +return string_catn(g, s, Ustrlen(s)); } @@ -1172,30 +1198,29 @@ return string_catn(string, s, Ustrlen(s)); It calls string_cat() to do the dirty work. Arguments: - string expanding-string that is being built, or NULL - if this is a new string that has no contents yet + g growable-string that is being built, or NULL if not yet assigned count the number of strings to append ... "count" uschar* arguments, which must be valid zero-terminated C strings -Returns: pointer to the start of the string, changed if copied for expansion. +Returns: growable string, changed if copied for expansion. The string is not zero-terminated - see string_cat() above. */ __inline__ gstring * -string_append(gstring *string, int count, ...) +string_append(gstring * g, int count, ...) { va_list ap; va_start(ap, count); while (count-- > 0) { - uschar *t = va_arg(ap, uschar *); - string = string_cat(string, t); + uschar * t = va_arg(ap, uschar *); + g = string_cat(g, t); } va_end(ap); -return string; +return g; } #endif @@ -1231,7 +1256,7 @@ BOOL string_format_trc(uschar * buffer, int buflen, const uschar * func, unsigned line, const char * format, ...) { -gstring g = { .size = buflen, .ptr = 0, .s = buffer }, *gp; +gstring g = { .size = buflen, .ptr = 0, .s = buffer }, * gp; va_list ap; va_start(ap, format); gp = string_vformat_trc(&g, func, line, STRING_SPRINTF_BUFFER_SIZE, @@ -1243,22 +1268,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. @@ -1270,7 +1299,7 @@ string, not nul-terminated. gstring * string_vformat_trc(gstring * g, const uschar * func, unsigned line, - unsigned size_limit, unsigned flags, const char *format, va_list ap) + unsigned size_limit, unsigned flags, const char * format, va_list ap) { enum ltypes { L_NORMAL=1, L_SHORT=2, L_LONG=3, L_LONGLONG=4, L_LONGDOUBLE=5, L_SIZE=6 }; @@ -1310,7 +1339,7 @@ off = g->ptr; /* remember initial offset in gstring */ while (*fp) { int length = L_NORMAL; - int *nptr; + int * nptr; int slen; const char *null = "NULL"; /* ) These variables */ const char *item_start, *s; /* ) are deliberately */ @@ -1612,16 +1641,17 @@ 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 * + func caller, for debug + line caller, for debug ... arguments for the format string Returns: a message, in dynamic store */ uschar * -string_open_failed_trc(int eno, const uschar * func, unsigned line, - const char *format, ...) +string_open_failed_trc(const uschar * func, unsigned line, + const char * format, ...) { va_list ap; gstring * g = string_get(1024); @@ -1635,23 +1665,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. */ @@ -1678,6 +1712,7 @@ int main(void) uschar buffer[256]; printf("Testing is_ip_address\n"); +store_init(); while (fgets(CS buffer, sizeof(buffer), stdin) != NULL) {