bcb4e51a409d94ae670de96afb8483a4f7855294Stephan Bosch/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
402f9183489c1a75736b1e9068c33fe2741a366dTimo Sirainen
402f9183489c1a75736b1e9068c33fe2741a366dTimo Sirainen#include "lib.h"
402f9183489c1a75736b1e9068c33fe2741a366dTimo Sirainen#include "printf-format-fix.h"
402f9183489c1a75736b1e9068c33fe2741a366dTimo Sirainen
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainenstatic const char *
2ac5f36aa7c2e7a07ba8815d43a6d7483f62e74cTimo Sirainenfix_format_real(const char *fmt, const char *p, size_t *len_r)
402f9183489c1a75736b1e9068c33fe2741a366dTimo Sirainen{
402f9183489c1a75736b1e9068c33fe2741a366dTimo Sirainen const char *errstr;
402f9183489c1a75736b1e9068c33fe2741a366dTimo Sirainen char *buf;
2ac5f36aa7c2e7a07ba8815d43a6d7483f62e74cTimo Sirainen size_t len1, len2, len3;
402f9183489c1a75736b1e9068c33fe2741a366dTimo Sirainen
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen i_assert((size_t)(p - fmt) < INT_MAX);
1dcf22e98ba8310e8daa8c9297936c6f3a645e7aPhil Carmody i_assert(p[0] == '%' && p[1] == 'm');
402f9183489c1a75736b1e9068c33fe2741a366dTimo Sirainen
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen errstr = strerror(errno);
402f9183489c1a75736b1e9068c33fe2741a366dTimo Sirainen
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen /* we'll assume that there's only one %m in the format string.
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen this simplifies the code and there's really no good reason to have
1dcf22e98ba8310e8daa8c9297936c6f3a645e7aPhil Carmody it multiple times. Callers can trap this case themselves. */
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen len1 = p - fmt;
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen len2 = strlen(errstr);
c61d9048580e49485fac676a0585f5303e9a844aTimo Sirainen len3 = strlen(p + 2);
402f9183489c1a75736b1e9068c33fe2741a366dTimo Sirainen
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen /* @UNSAFE */
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen buf = t_buffer_get(len1 + len2 + len3 + 1);
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen memcpy(buf, fmt, len1);
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen memcpy(buf + len1, errstr, len2);
c61d9048580e49485fac676a0585f5303e9a844aTimo Sirainen memcpy(buf + len1 + len2, p + 2, len3 + 1);
402f9183489c1a75736b1e9068c33fe2741a366dTimo Sirainen
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen *len_r = len1 + len2 + len3;
402f9183489c1a75736b1e9068c33fe2741a366dTimo Sirainen return buf;
402f9183489c1a75736b1e9068c33fe2741a366dTimo Sirainen}
402f9183489c1a75736b1e9068c33fe2741a366dTimo Sirainen
686a3be710a464396cbfbc7b05eaa7fe16f3cd1cTimo Sirainenstatic bool verify_length(const char **p)
686a3be710a464396cbfbc7b05eaa7fe16f3cd1cTimo Sirainen{
686a3be710a464396cbfbc7b05eaa7fe16f3cd1cTimo Sirainen if (**p == '*') {
686a3be710a464396cbfbc7b05eaa7fe16f3cd1cTimo Sirainen /* We don't bother supporting "*m$" - it's not used
686a3be710a464396cbfbc7b05eaa7fe16f3cd1cTimo Sirainen anywhere and seems a bit dangerous. */
686a3be710a464396cbfbc7b05eaa7fe16f3cd1cTimo Sirainen *p += 1;
9bf221d642eba214b81346abd9ba2f9b9f2eb937Phil Carmody } else if (**p >= '0' && **p <= '9') {
686a3be710a464396cbfbc7b05eaa7fe16f3cd1cTimo Sirainen /* Limit to 4 digits - we'll never want more than that.
686a3be710a464396cbfbc7b05eaa7fe16f3cd1cTimo Sirainen Some implementations might not handle long digits
686a3be710a464396cbfbc7b05eaa7fe16f3cd1cTimo Sirainen correctly, or maybe even could be used for DoS due
9bf221d642eba214b81346abd9ba2f9b9f2eb937Phil Carmody to using too much CPU. If you want to express '99'
9bf221d642eba214b81346abd9ba2f9b9f2eb937Phil Carmody as '00099', then you lose in this function. */
686a3be710a464396cbfbc7b05eaa7fe16f3cd1cTimo Sirainen unsigned int i = 0;
686a3be710a464396cbfbc7b05eaa7fe16f3cd1cTimo Sirainen do {
686a3be710a464396cbfbc7b05eaa7fe16f3cd1cTimo Sirainen *p += 1;
686a3be710a464396cbfbc7b05eaa7fe16f3cd1cTimo Sirainen if (++i > 4)
686a3be710a464396cbfbc7b05eaa7fe16f3cd1cTimo Sirainen return FALSE;
686a3be710a464396cbfbc7b05eaa7fe16f3cd1cTimo Sirainen } while (**p >= '0' && **p <= '9');
686a3be710a464396cbfbc7b05eaa7fe16f3cd1cTimo Sirainen }
686a3be710a464396cbfbc7b05eaa7fe16f3cd1cTimo Sirainen return TRUE;
686a3be710a464396cbfbc7b05eaa7fe16f3cd1cTimo Sirainen}
686a3be710a464396cbfbc7b05eaa7fe16f3cd1cTimo Sirainen
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainenstatic const char *
2ac5f36aa7c2e7a07ba8815d43a6d7483f62e74cTimo Sirainenprintf_format_fix_noalloc(const char *format, size_t *len_r)
402f9183489c1a75736b1e9068c33fe2741a366dTimo Sirainen{
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen /* NOTE: This function is overly strict in what it accepts. Some
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen format strings that are valid (and safe) in C99 will cause a panic
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen here. This is because we don't really need to support the weirdest
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen special cases, and we're also being extra careful not to pass
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen anything to the underlying libc printf, which might treat the string
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen differently than us and unexpectedly handling it as %n. For example
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen "%**%n" with glibc. */
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen /* Allow only the standard C99 flags. There are also <'> and <I> flags,
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen but we don't really need them. And at worst if they're not supported
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen by the underlying printf, they could potentially be used to work
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen around our restrictions. */
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen const char printf_flags[] = "#0- +";
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen /* As a tiny optimization keep the most commonly used conversion
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen specifiers first, so strchr() stops early. */
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen static const char *printf_specifiers = "sudcixXpoeEfFgGaA";
51292e327a91a257b8baea83560b3a8323cac8c5Timo Sirainen const char *ret, *p, *p2;
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen char *flag;
51292e327a91a257b8baea83560b3a8323cac8c5Timo Sirainen
51292e327a91a257b8baea83560b3a8323cac8c5Timo Sirainen p = ret = format;
51292e327a91a257b8baea83560b3a8323cac8c5Timo Sirainen while ((p2 = strchr(p, '%')) != NULL) {
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen const unsigned int start_pos = p2 - format;
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen
51292e327a91a257b8baea83560b3a8323cac8c5Timo Sirainen p = p2+1;
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen if (*p == '%') {
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen /* we'll be strict and allow %% only when there are no
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen optinal flags or modifiers. */
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen p++;
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen continue;
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen }
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen /* 1) zero or more flags. We'll add a further restriction that
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen each flag can be used only once, since there's no need to
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen use them more than once, and some implementations might
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen add their own limits. */
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen bool printf_flags_seen[N_ELEMENTS(printf_flags)] = { FALSE, };
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen while (*p != '\0' &&
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen (flag = strchr(printf_flags, *p)) != NULL) {
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen unsigned int flag_idx = flag - printf_flags;
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen if (printf_flags_seen[flag_idx]) {
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen i_panic("Duplicate %% flag '%c' starting at #%u in '%s'",
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen *p, start_pos, format);
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen }
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen printf_flags_seen[flag_idx] = TRUE;
4baf980b75800ad3595c39dc3b8d913f2686affdTimo Sirainen p++;
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen }
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen /* 2) Optional minimum field width */
686a3be710a464396cbfbc7b05eaa7fe16f3cd1cTimo Sirainen if (!verify_length(&p)) {
686a3be710a464396cbfbc7b05eaa7fe16f3cd1cTimo Sirainen i_panic("Too large minimum field width starting at #%u in '%s'",
686a3be710a464396cbfbc7b05eaa7fe16f3cd1cTimo Sirainen start_pos, format);
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen }
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen /* 3) Optional precision */
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen if (*p == '.') {
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen p++;
85174bd658f999cb3aa145885fd85674f8595fbbTimo Sirainen if (!verify_length(&p)) {
85174bd658f999cb3aa145885fd85674f8595fbbTimo Sirainen i_panic("Too large precision starting at #%u in '%s'",
85174bd658f999cb3aa145885fd85674f8595fbbTimo Sirainen start_pos, format);
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen }
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen }
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen /* 4) Optional length modifier */
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen switch (*p) {
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen case 'h':
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen if (*++p == 'h')
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen p++;
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen break;
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen case 'l':
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen if (*++p == 'l')
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen p++;
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen break;
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen case 'L':
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen case 'j':
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen case 'z':
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen case 't':
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen p++;
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen break;
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen }
ebe00087d3c7f9706d4acb9017eaad912404516cTimo Sirainen
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen /* 5) conversion specifier */
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen if (*p == '\0' || strchr(printf_specifiers, *p) == NULL) {
ebe00087d3c7f9706d4acb9017eaad912404516cTimo Sirainen switch (*p) {
ebe00087d3c7f9706d4acb9017eaad912404516cTimo Sirainen case 'n':
ebe00087d3c7f9706d4acb9017eaad912404516cTimo Sirainen i_panic("%%n modifier used");
ebe00087d3c7f9706d4acb9017eaad912404516cTimo Sirainen case 'm':
ebe00087d3c7f9706d4acb9017eaad912404516cTimo Sirainen if (ret != format)
ebe00087d3c7f9706d4acb9017eaad912404516cTimo Sirainen i_panic("%%m used twice");
ebe00087d3c7f9706d4acb9017eaad912404516cTimo Sirainen ret = fix_format_real(format, p-1, len_r);
ebe00087d3c7f9706d4acb9017eaad912404516cTimo Sirainen break;
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen case '\0':
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen i_panic("Missing %% specifier starting at #%u in '%s'",
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen start_pos, format);
ebe00087d3c7f9706d4acb9017eaad912404516cTimo Sirainen default:
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen i_panic("Unsupported 0x%02x specifier starting at #%u in '%s'",
c398eca6b0fc6583687bd6fe2ee2dbcca2ae9387Timo Sirainen *p, start_pos, format);
ebe00087d3c7f9706d4acb9017eaad912404516cTimo Sirainen }
402f9183489c1a75736b1e9068c33fe2741a366dTimo Sirainen }
51292e327a91a257b8baea83560b3a8323cac8c5Timo Sirainen p++;
402f9183489c1a75736b1e9068c33fe2741a366dTimo Sirainen }
402f9183489c1a75736b1e9068c33fe2741a366dTimo Sirainen
1dcf22e98ba8310e8daa8c9297936c6f3a645e7aPhil Carmody if (ret == format)
51292e327a91a257b8baea83560b3a8323cac8c5Timo Sirainen *len_r = p - format + strlen(p);
1dcf22e98ba8310e8daa8c9297936c6f3a645e7aPhil Carmody return ret;
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen}
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen
2ac5f36aa7c2e7a07ba8815d43a6d7483f62e74cTimo Sirainenconst char *printf_format_fix_get_len(const char *format, size_t *len_r)
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen{
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen const char *ret;
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen ret = printf_format_fix_noalloc(format, len_r);
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen if (ret != format)
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen t_buffer_alloc(*len_r + 1);
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen return ret;
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen}
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainenconst char *printf_format_fix(const char *format)
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen{
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen const char *ret;
2ac5f36aa7c2e7a07ba8815d43a6d7483f62e74cTimo Sirainen size_t len;
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen ret = printf_format_fix_noalloc(format, &len);
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen if (ret != format)
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen t_buffer_alloc(len + 1);
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen return ret;
402f9183489c1a75736b1e9068c33fe2741a366dTimo Sirainen}
402f9183489c1a75736b1e9068c33fe2741a366dTimo Sirainen
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainenconst char *printf_format_fix_unsafe(const char *format)
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen{
2ac5f36aa7c2e7a07ba8815d43a6d7483f62e74cTimo Sirainen size_t len;
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen return printf_format_fix_noalloc(format, &len);
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen}