diff options
Diffstat (limited to 'src/rnp/rnpcfg.cpp')
-rw-r--r-- | src/rnp/rnpcfg.cpp | 572 |
1 files changed, 572 insertions, 0 deletions
diff --git a/src/rnp/rnpcfg.cpp b/src/rnp/rnpcfg.cpp new file mode 100644 index 0000000..e1c35a3 --- /dev/null +++ b/src/rnp/rnpcfg.cpp @@ -0,0 +1,572 @@ +/* + * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <limits.h> +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#else +#include "uniwin.h" +#endif +#include <sys/stat.h> +#include <time.h> +#include <errno.h> +#include <stdexcept> +#include <inttypes.h> + +#include "config.h" +#include "rnpcfg.h" +#include "defaults.h" +#include "utils.h" +#include "time-utils.h" +#include <rnp/rnp.h> + +// must be placed after include "utils.h" +#ifndef RNP_USE_STD_REGEX +#include <regex.h> +#else +#include <regex> +#endif + +typedef enum rnp_cfg_val_type_t { + RNP_CFG_VAL_NULL = 0, + RNP_CFG_VAL_INT = 1, + RNP_CFG_VAL_BOOL = 2, + RNP_CFG_VAL_STRING = 3, + RNP_CFG_VAL_LIST = 4 +} rnp_cfg_val_type_t; + +class rnp_cfg_val { + rnp_cfg_val_type_t type_; + + public: + rnp_cfg_val(rnp_cfg_val_type_t t) : type_(t){}; + rnp_cfg_val_type_t + type() const + { + return type_; + }; + + virtual ~rnp_cfg_val(){}; +}; + +class rnp_cfg_int_val : public rnp_cfg_val { + int val_; + + public: + rnp_cfg_int_val(int val) : rnp_cfg_val(RNP_CFG_VAL_INT), val_(val){}; + int + val() const + { + return val_; + }; +}; + +class rnp_cfg_bool_val : public rnp_cfg_val { + bool val_; + + public: + rnp_cfg_bool_val(bool val) : rnp_cfg_val(RNP_CFG_VAL_BOOL), val_(val){}; + bool + val() const + { + return val_; + }; +}; + +class rnp_cfg_str_val : public rnp_cfg_val { + std::string val_; + + public: + rnp_cfg_str_val(const std::string &val) : rnp_cfg_val(RNP_CFG_VAL_STRING), val_(val){}; + const std::string & + val() const + { + return val_; + }; +}; + +class rnp_cfg_list_val : public rnp_cfg_val { + std::vector<std::string> val_; + + public: + rnp_cfg_list_val() : rnp_cfg_val(RNP_CFG_VAL_LIST), val_(){}; + std::vector<std::string> & + val() + { + return val_; + }; + const std::vector<std::string> & + val() const + { + return val_; + }; +}; + +void +rnp_cfg::load_defaults() +{ + set_bool(CFG_OVERWRITE, false); + set_str(CFG_OUTFILE, ""); + set_str(CFG_ZALG, DEFAULT_Z_ALG); + set_int(CFG_ZLEVEL, DEFAULT_Z_LEVEL); + set_str(CFG_CIPHER, DEFAULT_SYMM_ALG); + set_int(CFG_NUMTRIES, MAX_PASSWORD_ATTEMPTS); + set_int(CFG_S2K_MSEC, DEFAULT_S2K_MSEC); +} + +void +rnp_cfg::set_str(const std::string &key, const std::string &val) +{ + unset(key); + vals_[key] = new rnp_cfg_str_val(val); +} + +void +rnp_cfg::set_str(const std::string &key, const char *val) +{ + unset(key); + vals_[key] = new rnp_cfg_str_val(val); +} + +void +rnp_cfg::set_int(const std::string &key, int val) +{ + unset(key); + vals_[key] = new rnp_cfg_int_val(val); +} + +void +rnp_cfg::set_bool(const std::string &key, bool val) +{ + unset(key); + vals_[key] = new rnp_cfg_bool_val(val); +} + +void +rnp_cfg::unset(const std::string &key) +{ + if (!vals_.count(key)) { + return; + } + delete vals_[key]; + vals_.erase(key); +} + +void +rnp_cfg::add_str(const std::string &key, const std::string &val) +{ + if (!vals_.count(key)) { + vals_[key] = new rnp_cfg_list_val(); + } + if (vals_[key]->type() != RNP_CFG_VAL_LIST) { + RNP_LOG("expected list val for \"%s\"", key.c_str()); + throw std::invalid_argument("type"); + } + (dynamic_cast<rnp_cfg_list_val &>(*vals_[key])).val().push_back(val); +} + +bool +rnp_cfg::has(const std::string &key) const +{ + return vals_.count(key); +} + +const std::string & +rnp_cfg::get_str(const std::string &key) const +{ + if (!has(key) || (vals_.at(key)->type() != RNP_CFG_VAL_STRING)) { + return empty_str_; + } + return (dynamic_cast<const rnp_cfg_str_val &>(*vals_.at(key))).val(); +} + +const char * +rnp_cfg::get_cstr(const std::string &key) const +{ + if (!has(key) || (vals_.at(key)->type() != RNP_CFG_VAL_STRING)) { + return NULL; + } + return (dynamic_cast<const rnp_cfg_str_val &>(*vals_.at(key))).val().c_str(); +} + +int +rnp_cfg::get_int(const std::string &key, int def) const +{ + if (!has(key)) { + return def; + } + const rnp_cfg_val *val = vals_.at(key); + switch (val->type()) { + case RNP_CFG_VAL_INT: + return (dynamic_cast<const rnp_cfg_int_val &>(*val)).val(); + case RNP_CFG_VAL_BOOL: + return (dynamic_cast<const rnp_cfg_bool_val &>(*val)).val(); + case RNP_CFG_VAL_STRING: + return atoi((dynamic_cast<const rnp_cfg_str_val &>(*val)).val().c_str()); + default: + return def; + } +} + +bool +rnp_cfg::get_bool(const std::string &key) const +{ + if (!has(key)) { + return false; + } + const rnp_cfg_val *val = vals_.at(key); + switch (val->type()) { + case RNP_CFG_VAL_INT: + return (dynamic_cast<const rnp_cfg_int_val &>(*val)).val(); + case RNP_CFG_VAL_BOOL: + return (dynamic_cast<const rnp_cfg_bool_val &>(*val)).val(); + case RNP_CFG_VAL_STRING: { + const std::string &str = (dynamic_cast<const rnp_cfg_str_val &>(*val)).val(); + return !strcasecmp(str.c_str(), "true") || (atoi(str.c_str()) > 0); + } + default: + return false; + } +} + +size_t +rnp_cfg::get_count(const std::string &key) const +{ + if (!has(key) || (vals_.at(key)->type() != RNP_CFG_VAL_LIST)) { + return 0; + } + return (dynamic_cast<const rnp_cfg_list_val &>(*vals_.at(key))).val().size(); +} + +const std::string & +rnp_cfg::get_str(const std::string &key, size_t idx) const +{ + if (get_count(key) <= idx) { + RNP_LOG("idx is out of bounds for \"%s\"", key.c_str()); + throw std::invalid_argument("idx"); + } + return (dynamic_cast<const rnp_cfg_list_val &>(*vals_.at(key))).val().at(idx); +} + +std::vector<std::string> +rnp_cfg::get_list(const std::string &key) const +{ + if (!has(key)) { + /* it's okay to return empty list */ + return std::vector<std::string>(); + } + if (vals_.at(key)->type() != RNP_CFG_VAL_LIST) { + RNP_LOG("no list at the key \"%s\"", key.c_str()); + throw std::invalid_argument("key"); + } + return (dynamic_cast<const rnp_cfg_list_val &>(*vals_.at(key))).val(); +} + +int +rnp_cfg::get_pswdtries() const +{ + const std::string &numtries = get_str(CFG_NUMTRIES); + int num = atoi(numtries.c_str()); + if (numtries.empty() || (num <= 0)) { + return MAX_PASSWORD_ATTEMPTS; + } else if (numtries == "unlimited") { + return INFINITE_ATTEMPTS; + } + return num; +} + +const std::string +rnp_cfg::get_hashalg() const +{ + const std::string hash_alg = get_str(CFG_HASH); + if (!hash_alg.empty()) { + return hash_alg; + } + return DEFAULT_HASH_ALG; +} + +bool +rnp_cfg::get_expiration(const std::string &key, uint32_t &seconds) const +{ + if (!has(key)) { + return false; + } + const std::string &val = get_str(key); + uint64_t delta; + uint64_t t; + if (parse_date(val, t)) { + uint64_t now = time(); + if (t > now) { + delta = t - now; + if (delta > UINT32_MAX) { + RNP_LOG("Expiration time exceeds 32-bit value"); + return false; + } + seconds = delta; + return true; + } + return false; + } + const char *reg = "^([0-9]+)([hdwmy]?)$"; +#ifndef RNP_USE_STD_REGEX + static regex_t r; + static int compiled; + regmatch_t matches[3]; + + if (!compiled) { + compiled = 1; + if (regcomp(&r, reg, REG_EXTENDED | REG_ICASE)) { + RNP_LOG("failed to compile regexp"); + return false; + } + } + if (regexec(&r, val.c_str(), ARRAY_SIZE(matches), matches, 0)) { + return false; + } + auto delta_str = &val.c_str()[matches[1].rm_so]; + char mult = val.c_str()[matches[2].rm_so]; +#else + static std::regex re(reg, std::regex_constants::extended | std::regex_constants::icase); + std::smatch result; + + if (!std::regex_search(val, result, re)) { + return false; + } + std::string delta_stdstr = result[1].str(); + const char *delta_str = delta_stdstr.c_str(); + char mult = result[2].str()[0]; +#endif + errno = 0; + delta = strtoul(delta_str, NULL, 10); + if (errno || delta > UINT_MAX) { + RNP_LOG("Invalid expiration '%s'.", delta_str); + return false; + } + switch (std::tolower(mult)) { + case 'h': + delta *= 60 * 60; + break; + case 'd': + delta *= 60 * 60 * 24; + break; + case 'w': + delta *= 60 * 60 * 24 * 7; + break; + case 'm': + delta *= 60 * 60 * 24 * 31; + break; + case 'y': + delta *= 60 * 60 * 24 * 365; + break; + } + if (delta > UINT32_MAX) { + RNP_LOG("Expiration value exceed 32 bit."); + return false; + } + seconds = delta; + return true; +} + +bool +rnp_cfg::extract_timestamp(const std::string &st, uint64_t &t) const +{ + if (st.empty()) { + return false; + } + if (parse_date(st, t)) { + return true; + } + /* Check if string is UNIX timestamp */ + for (auto c : st) { + if (!isdigit(c)) { + return false; + } + } + t = std::stoll(st); + return true; +} + +uint64_t +rnp_cfg::get_sig_creation() const +{ + uint64_t t = 0; + if (extract_timestamp(get_str(CFG_CREATION), t)) { + return t; + } + return time(); +} + +uint64_t +rnp_cfg::time() const +{ + uint64_t t = 0; + if (extract_timestamp(get_str(CFG_CURTIME), t)) { + return t; + } + return ::time(NULL); +} + +void +rnp_cfg::copy(const rnp_cfg &src) +{ + for (const auto &it : src.vals_) { + if (has(it.first)) { + unset(it.first); + } + rnp_cfg_val *val = NULL; + switch (it.second->type()) { + case RNP_CFG_VAL_INT: + val = new rnp_cfg_int_val(dynamic_cast<const rnp_cfg_int_val &>(*it.second)); + break; + case RNP_CFG_VAL_BOOL: + val = new rnp_cfg_bool_val(dynamic_cast<const rnp_cfg_bool_val &>(*it.second)); + break; + case RNP_CFG_VAL_STRING: + val = new rnp_cfg_str_val(dynamic_cast<const rnp_cfg_str_val &>(*it.second)); + break; + case RNP_CFG_VAL_LIST: + val = new rnp_cfg_list_val(dynamic_cast<const rnp_cfg_list_val &>(*it.second)); + break; + default: + continue; + } + vals_[it.first] = val; + } +} + +void +rnp_cfg::clear() +{ + for (const auto &it : vals_) { + delete it.second; + } + vals_.clear(); +} + +rnp_cfg::~rnp_cfg() +{ + clear(); +} + +/** + * @brief Get number of days in month. + * + * @param year number of year, i.e. 2021 + * @param month number of month, 1..12 + * @return number of days (28..31) or 0 if month is wrong. + */ +static int +days_in_month(int year, int month) +{ + switch (month) { + case 1: + case 3: + case 5: + case 7: + case 8: + case 10: + case 12: + return 31; + case 4: + case 6: + case 9: + case 11: + return 30; + case 2: { + bool leap_year = !(year % 400) || (!(year % 4) && (year % 100)); + return leap_year ? 29 : 28; + } + default: + return 0; + } +} + +bool +rnp_cfg::parse_date(const std::string &s, uint64_t &t) const +{ + /* fill time zone information */ + struct tm tm; + rnp_localtime(::time(NULL), tm); + tm.tm_hour = 0; + tm.tm_min = 0; + tm.tm_sec = 0; + const char *reg = "^([0-9]{4})[-/\\.]([0-9]{2})[-/\\.]([0-9]{2})$"; +#ifndef RNP_USE_STD_REGEX + static regex_t r; + static int compiled; + + if (!compiled) { + compiled = 1; + if (regcomp(&r, reg, REG_EXTENDED)) { + RNP_LOG("failed to compile regexp"); + return false; + } + } + regmatch_t matches[4]; + if (regexec(&r, s.c_str(), ARRAY_SIZE(matches), matches, 0)) { + return false; + } + int year = strtol(&s[matches[1].rm_so], NULL, 10); + int mon = strtol(&s[matches[2].rm_so], NULL, 10); + int mday = strtol(&s[matches[3].rm_so], NULL, 10); +#else + static std::regex re(reg, std::regex_constants::extended); + std::smatch result; + + if (!std::regex_search(s, result, re)) { + return false; + } + int year = std::stoi(result[1].str()); + int mon = std::stoi(result[2].str()); + int mday = std::stoi(result[3].str()); +#endif + if (year < 1970 || mon < 1 || mon > 12 || !mday || (mday > days_in_month(year, mon))) { + RNP_LOG("invalid date: %s.", s.c_str()); + return false; + } + tm.tm_year = year - 1900; + tm.tm_mon = mon - 1; + tm.tm_mday = mday; + /* line below is required to correctly handle DST changes */ + tm.tm_isdst = -1; + + struct tm check_tm = tm; + time_t built_time = rnp_mktime(&tm); + time_t check_time = mktime(&check_tm); + if (built_time != check_time) { + /* If date is beyond of yk2038 and we have 32-bit signed time_t, we need to reduce + * timestamp */ + RNP_LOG("Warning: date %s is beyond of 32-bit time_t, so timestamp was reduced to " + "maximum supported value.", + s.c_str()); + } + t = built_time; + return true; +} |