summaryrefslogtreecommitdiffstats
path: root/src/rnp/rnpcfg.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/rnp/rnpcfg.cpp')
-rw-r--r--src/rnp/rnpcfg.cpp572
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;
+}