/* * SPDX-License-Identifier: ISC * * Copyright (c) 2013-2015, 2019 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This is an open source non-commercial project. Dear PVS-Studio, please check it. * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com */ #include #include #include #include #include #include #include #ifdef HAVE_STRING_H # include #endif /* HAVE_STRING_H */ #ifdef HAVE_STRINGS_H # include #endif /* HAVE_STRINGS_H */ #define DEFAULT_TEXT_DOMAIN "sudo" #include "sudo_gettext.h" /* must be included before sudo_compat.h */ #include "sudo_compat.h" #include "sudo_util.h" enum strtonum_err { STN_INITIAL, STN_VALID, STN_INVALID, STN_TOOSMALL, STN_TOOBIG }; /* * Convert a string to a number in the range [minval, maxval] * Unlike strtonum(), this returns the first non-digit in endp (if not NULL). */ long long sudo_strtonumx(const char *str, long long minval, long long maxval, char **endp, const char **errstrp) { enum strtonum_err errval = STN_INITIAL; long long lastval, result = 0; const char *cp = str; unsigned char ch; int remainder; char sign; if (minval > maxval) { errval = STN_INVALID; goto done; } /* Trim leading space and check sign, if any. */ do { ch = *cp++; } while (isspace(ch)); switch (ch) { case '-': sign = '-'; ch = *cp++; break; case '+': ch = *cp++; /* FALLTHROUGH */ default: sign = '+'; break; } /* * To prevent overflow we determine the highest (or lowest in * the case of negative numbers) value result can have *before* * if its multiplied (divided) by 10 as well as the remainder. * If result matches this value and the next digit is larger than * the remainder, we know the result is out of range. * The remainder is always positive since it is compared against * an unsigned digit. */ if (sign == '-') { lastval = minval / 10; remainder = -(minval % 10); if (remainder < 0) { lastval += 1; remainder += 10; } for (;; ch = *cp++) { if (!isdigit(ch)) break; ch -= '0'; if (result < lastval || (result == lastval && ch > remainder)) { errval = STN_TOOSMALL; break; } else { errval = STN_VALID; result *= 10; result -= ch; } } if (result > maxval) errval = STN_TOOBIG; } else { lastval = maxval / 10; remainder = maxval % 10; for (;; ch = *cp++) { if (!isdigit(ch)) break; ch -= '0'; if (result > lastval || (result == lastval && ch > remainder)) { errval = STN_TOOBIG; break; } else { errval = STN_VALID; result *= 10; result += ch; } } if (result < minval) errval = STN_TOOSMALL; } done: switch (errval) { case STN_INITIAL: case STN_VALID: if (errstrp != NULL) *errstrp = NULL; break; case STN_INVALID: result = 0; errno = EINVAL; if (errstrp != NULL) *errstrp = N_("invalid value"); break; case STN_TOOSMALL: /* Skip remaining digits. */ do { ch = *cp++; } while (isdigit(ch)); result = 0; errno = ERANGE; if (errstrp != NULL) *errstrp = N_("value too small"); break; case STN_TOOBIG: /* Skip remaining digits. */ do { ch = *cp++; } while (isdigit(ch)); result = 0; errno = ERANGE; if (errstrp != NULL) *errstrp = N_("value too large"); break; } if (endp != NULL) *endp = (char *)(errval == STN_INITIAL ? str : cp - 1); return result; } /* * Convert a string to a number in the range [minval, maxval] */ long long sudo_strtonum(const char *str, long long minval, long long maxval, const char **errstrp) { const char *errstr; char *ep; long long ret; ret = sudo_strtonumx(str, minval, maxval, &ep, &errstr); /* Check for empty string and terminating NUL. */ if (str == ep || *ep != '\0') { errno = EINVAL; errstr = N_("invalid value"); ret = 0; } if (errstrp != NULL) *errstrp = errstr; return ret; }