1 /*
2 * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com).
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
16 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
18 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 * POSSIBILITY OF SUCH DAMAGE.
25 */
26
27 #include <stdlib.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <sys/types.h>
31 #include <limits.h>
32 #ifdef HAVE_SYS_PARAM_H
33 #include <sys/param.h>
34 #else
35 #include "uniwin.h"
36 #endif
37 #include <sys/stat.h>
38 #include <time.h>
39 #include <errno.h>
40 #include <stdexcept>
41
42 #include "rnpcfg.h"
43 #include "defaults.h"
44 #include "utils.h"
45 #include "time-utils.h"
46 #include <rnp/rnp.h>
47
48 // must be placed after include "utils.h"
49 #ifndef RNP_USE_STD_REGEX
50 #include <regex.h>
51 #else
52 #include <regex>
53 #endif
54
55 typedef enum rnp_cfg_val_type_t {
56 RNP_CFG_VAL_NULL = 0,
57 RNP_CFG_VAL_INT = 1,
58 RNP_CFG_VAL_BOOL = 2,
59 RNP_CFG_VAL_STRING = 3,
60 RNP_CFG_VAL_LIST = 4
61 } rnp_cfg_val_type_t;
62
63 class rnp_cfg_val {
64 rnp_cfg_val_type_t type_;
65
66 public:
rnp_cfg_val(rnp_cfg_val_type_t t)67 rnp_cfg_val(rnp_cfg_val_type_t t) : type_(t){};
68 rnp_cfg_val_type_t
type() const69 type() const
70 {
71 return type_;
72 };
73
~rnp_cfg_val()74 virtual ~rnp_cfg_val(){};
75 };
76
77 class rnp_cfg_int_val : public rnp_cfg_val {
78 int val_;
79
80 public:
rnp_cfg_int_val(int val)81 rnp_cfg_int_val(int val) : rnp_cfg_val(RNP_CFG_VAL_INT), val_(val){};
82 int
val() const83 val() const
84 {
85 return val_;
86 };
87 };
88
89 class rnp_cfg_bool_val : public rnp_cfg_val {
90 bool val_;
91
92 public:
rnp_cfg_bool_val(bool val)93 rnp_cfg_bool_val(bool val) : rnp_cfg_val(RNP_CFG_VAL_BOOL), val_(val){};
94 bool
val() const95 val() const
96 {
97 return val_;
98 };
99 };
100
101 class rnp_cfg_str_val : public rnp_cfg_val {
102 std::string val_;
103
104 public:
rnp_cfg_str_val(const std::string & val)105 rnp_cfg_str_val(const std::string &val) : rnp_cfg_val(RNP_CFG_VAL_STRING), val_(val){};
106 const std::string &
val() const107 val() const
108 {
109 return val_;
110 };
111 };
112
113 class rnp_cfg_list_val : public rnp_cfg_val {
114 std::vector<std::string> val_;
115
116 public:
rnp_cfg_list_val()117 rnp_cfg_list_val() : rnp_cfg_val(RNP_CFG_VAL_LIST), val_(){};
118 std::vector<std::string> &
val()119 val()
120 {
121 return val_;
122 };
123 const std::vector<std::string> &
val() const124 val() const
125 {
126 return val_;
127 };
128 };
129
130 void
load_defaults()131 rnp_cfg::load_defaults()
132 {
133 set_bool(CFG_OVERWRITE, false);
134 set_str(CFG_OUTFILE, "");
135 set_str(CFG_ZALG, DEFAULT_Z_ALG);
136 set_int(CFG_ZLEVEL, DEFAULT_Z_LEVEL);
137 set_str(CFG_CIPHER, DEFAULT_SYMM_ALG);
138 set_int(CFG_NUMTRIES, MAX_PASSWORD_ATTEMPTS);
139 set_int(CFG_S2K_MSEC, DEFAULT_S2K_MSEC);
140 }
141
142 void
set_str(const std::string & key,const std::string & val)143 rnp_cfg::set_str(const std::string &key, const std::string &val)
144 {
145 unset(key);
146 vals_[key] = new rnp_cfg_str_val(val);
147 }
148
149 void
set_str(const std::string & key,const char * val)150 rnp_cfg::set_str(const std::string &key, const char *val)
151 {
152 unset(key);
153 vals_[key] = new rnp_cfg_str_val(val);
154 }
155
156 void
set_int(const std::string & key,int val)157 rnp_cfg::set_int(const std::string &key, int val)
158 {
159 unset(key);
160 vals_[key] = new rnp_cfg_int_val(val);
161 }
162
163 void
set_bool(const std::string & key,bool val)164 rnp_cfg::set_bool(const std::string &key, bool val)
165 {
166 unset(key);
167 vals_[key] = new rnp_cfg_bool_val(val);
168 }
169
170 void
unset(const std::string & key)171 rnp_cfg::unset(const std::string &key)
172 {
173 if (!vals_.count(key)) {
174 return;
175 }
176 delete vals_[key];
177 vals_.erase(key);
178 }
179
180 void
add_str(const std::string & key,const std::string & val)181 rnp_cfg::add_str(const std::string &key, const std::string &val)
182 {
183 if (!vals_.count(key)) {
184 vals_[key] = new rnp_cfg_list_val();
185 }
186 if (vals_[key]->type() != RNP_CFG_VAL_LIST) {
187 RNP_LOG("expected list val for \"%s\"", key.c_str());
188 throw std::invalid_argument("type");
189 }
190 (dynamic_cast<rnp_cfg_list_val &>(*vals_[key])).val().push_back(val);
191 }
192
193 bool
has(const std::string & key) const194 rnp_cfg::has(const std::string &key) const
195 {
196 return vals_.count(key);
197 }
198
199 const std::string &
get_str(const std::string & key) const200 rnp_cfg::get_str(const std::string &key) const
201 {
202 if (!has(key) || (vals_.at(key)->type() != RNP_CFG_VAL_STRING)) {
203 return empty_str_;
204 }
205 return (dynamic_cast<const rnp_cfg_str_val &>(*vals_.at(key))).val();
206 }
207
208 const char *
get_cstr(const std::string & key) const209 rnp_cfg::get_cstr(const std::string &key) const
210 {
211 if (!has(key) || (vals_.at(key)->type() != RNP_CFG_VAL_STRING)) {
212 return NULL;
213 }
214 return (dynamic_cast<const rnp_cfg_str_val &>(*vals_.at(key))).val().c_str();
215 }
216
217 int
get_int(const std::string & key,int def) const218 rnp_cfg::get_int(const std::string &key, int def) const
219 {
220 if (!has(key)) {
221 return def;
222 }
223 const rnp_cfg_val *val = vals_.at(key);
224 switch (val->type()) {
225 case RNP_CFG_VAL_INT:
226 return (dynamic_cast<const rnp_cfg_int_val &>(*val)).val();
227 case RNP_CFG_VAL_BOOL:
228 return (dynamic_cast<const rnp_cfg_bool_val &>(*val)).val();
229 case RNP_CFG_VAL_STRING:
230 return atoi((dynamic_cast<const rnp_cfg_str_val &>(*val)).val().c_str());
231 default:
232 return def;
233 }
234 }
235
236 bool
get_bool(const std::string & key) const237 rnp_cfg::get_bool(const std::string &key) const
238 {
239 if (!has(key)) {
240 return false;
241 }
242 const rnp_cfg_val *val = vals_.at(key);
243 switch (val->type()) {
244 case RNP_CFG_VAL_INT:
245 return (dynamic_cast<const rnp_cfg_int_val &>(*val)).val();
246 case RNP_CFG_VAL_BOOL:
247 return (dynamic_cast<const rnp_cfg_bool_val &>(*val)).val();
248 case RNP_CFG_VAL_STRING: {
249 const std::string &str = (dynamic_cast<const rnp_cfg_str_val &>(*val)).val();
250 return !strcasecmp(str.c_str(), "true") || (atoi(str.c_str()) > 0);
251 }
252 default:
253 return false;
254 }
255 }
256
257 size_t
get_count(const std::string & key) const258 rnp_cfg::get_count(const std::string &key) const
259 {
260 if (!has(key) || (vals_.at(key)->type() != RNP_CFG_VAL_LIST)) {
261 return 0;
262 }
263 return (dynamic_cast<const rnp_cfg_list_val &>(*vals_.at(key))).val().size();
264 }
265
266 const std::string &
get_str(const std::string & key,size_t idx) const267 rnp_cfg::get_str(const std::string &key, size_t idx) const
268 {
269 if (get_count(key) <= idx) {
270 RNP_LOG("idx is out of bounds for \"%s\"", key.c_str());
271 throw std::invalid_argument("idx");
272 }
273 return (dynamic_cast<const rnp_cfg_list_val &>(*vals_.at(key))).val().at(idx);
274 }
275
276 std::vector<std::string>
get_list(const std::string & key) const277 rnp_cfg::get_list(const std::string &key) const
278 {
279 if (!has(key)) {
280 /* it's okay to return empty list */
281 return std::vector<std::string>();
282 }
283 if (vals_.at(key)->type() != RNP_CFG_VAL_LIST) {
284 RNP_LOG("no list at the key \"%s\"", key.c_str());
285 throw std::invalid_argument("key");
286 }
287 return (dynamic_cast<const rnp_cfg_list_val &>(*vals_.at(key))).val();
288 }
289
290 int
get_pswdtries() const291 rnp_cfg::get_pswdtries() const
292 {
293 const std::string &numtries = get_str(CFG_NUMTRIES);
294 int num = atoi(numtries.c_str());
295 if (numtries.empty() || (num <= 0)) {
296 return MAX_PASSWORD_ATTEMPTS;
297 } else if (numtries == "unlimited") {
298 return INFINITE_ATTEMPTS;
299 }
300 return num;
301 }
302
303 const std::string
get_hashalg() const304 rnp_cfg::get_hashalg() const
305 {
306 const std::string hash_alg = get_str(CFG_HASH);
307 if (!hash_alg.empty()) {
308 return hash_alg;
309 }
310 return DEFAULT_HASH_ALG;
311 }
312
313 bool
get_expiration(const std::string & key,uint32_t & seconds) const314 rnp_cfg::get_expiration(const std::string &key, uint32_t &seconds) const
315 {
316 if (!has(key)) {
317 return false;
318 }
319 const std::string &val = get_str(key);
320 uint64_t delta;
321 uint64_t t;
322 if (parse_date(val, t)) {
323 uint64_t now = time(NULL);
324 if (t > now) {
325 delta = t - now;
326 if (delta > UINT32_MAX) {
327 RNP_LOG("Expiration time exceeds 32-bit value");
328 return false;
329 }
330 seconds = delta;
331 return true;
332 }
333 return false;
334 }
335 const char *reg = "^([0-9]+)([hdwmy]?)$";
336 #ifndef RNP_USE_STD_REGEX
337 static regex_t r;
338 static int compiled;
339 regmatch_t matches[3];
340
341 if (!compiled) {
342 compiled = 1;
343 if (regcomp(&r, reg, REG_EXTENDED | REG_ICASE)) {
344 RNP_LOG("failed to compile regexp");
345 return false;
346 }
347 }
348 if (regexec(&r, val.c_str(), ARRAY_SIZE(matches), matches, 0)) {
349 return false;
350 }
351 auto delta_str = &val.c_str()[matches[1].rm_so];
352 char mult = val.c_str()[matches[2].rm_so];
353 #else
354 static std::regex re(reg, std::regex_constants::extended | std::regex_constants::icase);
355 std::smatch result;
356
357 if (!std::regex_search(val, result, re)) {
358 return false;
359 }
360 std::string delta_stdstr = result[1].str();
361 const char *delta_str = delta_stdstr.c_str();
362 char mult = result[2].str()[0];
363 #endif
364 errno = 0;
365 delta = strtoul(delta_str, NULL, 10);
366 if (errno || delta > UINT_MAX) {
367 RNP_LOG("Invalid expiration '%s'.", delta_str);
368 return false;
369 }
370 switch (std::tolower(mult)) {
371 case 'h':
372 delta *= 60 * 60;
373 break;
374 case 'd':
375 delta *= 60 * 60 * 24;
376 break;
377 case 'w':
378 delta *= 60 * 60 * 24 * 7;
379 break;
380 case 'm':
381 delta *= 60 * 60 * 24 * 31;
382 break;
383 case 'y':
384 delta *= 60 * 60 * 24 * 365;
385 break;
386 }
387 if (delta > UINT32_MAX) {
388 RNP_LOG("Expiration value exceed 32 bit.");
389 return false;
390 }
391 seconds = delta;
392 return true;
393 }
394
395 uint64_t
get_sig_creation() const396 rnp_cfg::get_sig_creation() const
397 {
398 if (!has(CFG_CREATION)) {
399 return time(NULL);
400 }
401 const std::string &cr = get_str(CFG_CREATION);
402 /* Check if string is date */
403 uint64_t t;
404 if (parse_date(cr, t)) {
405 return t;
406 }
407 /* Check if string is UNIX timestamp */
408 for (auto c : cr) {
409 if (!isdigit(c)) {
410 return time(NULL);
411 }
412 }
413 return std::stoll(cr);
414 }
415
416 void
copy(const rnp_cfg & src)417 rnp_cfg::copy(const rnp_cfg &src)
418 {
419 for (const auto &it : src.vals_) {
420 if (has(it.first)) {
421 unset(it.first);
422 }
423 rnp_cfg_val *val = NULL;
424 switch (it.second->type()) {
425 case RNP_CFG_VAL_INT:
426 val = new rnp_cfg_int_val(dynamic_cast<const rnp_cfg_int_val &>(*it.second));
427 break;
428 case RNP_CFG_VAL_BOOL:
429 val = new rnp_cfg_bool_val(dynamic_cast<const rnp_cfg_bool_val &>(*it.second));
430 break;
431 case RNP_CFG_VAL_STRING:
432 val = new rnp_cfg_str_val(dynamic_cast<const rnp_cfg_str_val &>(*it.second));
433 break;
434 case RNP_CFG_VAL_LIST:
435 val = new rnp_cfg_list_val(dynamic_cast<const rnp_cfg_list_val &>(*it.second));
436 break;
437 default:
438 continue;
439 }
440 vals_[it.first] = val;
441 }
442 }
443
444 void
clear()445 rnp_cfg::clear()
446 {
447 for (const auto &it : vals_) {
448 delete it.second;
449 }
450 vals_.clear();
451 }
452
~rnp_cfg()453 rnp_cfg::~rnp_cfg()
454 {
455 clear();
456 }
457
458 /**
459 * @brief Get number of days in month.
460 *
461 * @param year number of year, i.e. 2021
462 * @param month number of month, 1..12
463 * @return number of days (28..31) or 0 if month is wrong.
464 */
465 static int
days_in_month(int year,int month)466 days_in_month(int year, int month)
467 {
468 switch (month) {
469 case 1:
470 case 3:
471 case 5:
472 case 7:
473 case 8:
474 case 10:
475 case 12:
476 return 31;
477 case 4:
478 case 6:
479 case 9:
480 case 11:
481 return 30;
482 case 2: {
483 bool leap_year = !(year % 400) || (!(year % 4) && (year % 100));
484 return leap_year ? 29 : 28;
485 }
486 default:
487 return 0;
488 }
489 }
490
491 bool
parse_date(const std::string & s,uint64_t & t) const492 rnp_cfg::parse_date(const std::string &s, uint64_t &t) const
493 {
494 /* fill time zone information */
495 const time_t now = time(NULL);
496 struct tm tm = *localtime(&now);
497 tm.tm_hour = 0;
498 tm.tm_min = 0;
499 tm.tm_sec = 0;
500 const char *reg = "^([0-9]{4})[-/\\.]([0-9]{2})[-/\\.]([0-9]{2})$";
501 #ifndef RNP_USE_STD_REGEX
502 static regex_t r;
503 static int compiled;
504
505 if (!compiled) {
506 compiled = 1;
507 if (regcomp(&r, reg, REG_EXTENDED)) {
508 RNP_LOG("failed to compile regexp");
509 return false;
510 }
511 }
512 regmatch_t matches[4];
513 if (regexec(&r, s.c_str(), ARRAY_SIZE(matches), matches, 0)) {
514 return false;
515 }
516 int year = strtol(&s[matches[1].rm_so], NULL, 10);
517 int mon = strtol(&s[matches[2].rm_so], NULL, 10);
518 int mday = strtol(&s[matches[3].rm_so], NULL, 10);
519 #else
520 static std::regex re(reg, std::regex_constants::extended);
521 std::smatch result;
522
523 if (!std::regex_search(s, result, re)) {
524 return false;
525 }
526 int year = std::stoi(result[1].str());
527 int mon = std::stoi(result[2].str());
528 int mday = std::stoi(result[3].str());
529 #endif
530 if (year < 1970 || mon < 1 || mon > 12 || !mday || (mday > days_in_month(year, mon))) {
531 RNP_LOG("invalid date: %s.", s.c_str());
532 return false;
533 }
534 tm.tm_year = year - 1900;
535 tm.tm_mon = mon - 1;
536 tm.tm_mday = mday;
537 /* line below is required to correctly handle DST changes */
538 tm.tm_isdst = -1;
539
540 struct tm check_tm = tm;
541 time_t built_time = rnp_mktime(&tm);
542 time_t check_time = mktime(&check_tm);
543 if (built_time != check_time) {
544 /* If date is beyond of yk2038 and we have 32-bit signed time_t, we need to reduce
545 * timestamp */
546 RNP_LOG("Warning: date %s is beyond of 32-bit time_t, so timestamp was reduced to "
547 "maximum supported value.",
548 s.c_str());
549 }
550 t = built_time;
551 return true;
552 }
553