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 "logging.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 rnp_cfg_list_val *list = dynamic_cast<rnp_cfg_list_val *>(vals_[key]);
191 list->val().push_back(val);
192 }
193
194 bool
has(const std::string & key) const195 rnp_cfg::has(const std::string &key) const
196 {
197 return vals_.count(key);
198 }
199
200 const std::string &
get_str(const std::string & key) const201 rnp_cfg::get_str(const std::string &key) const
202 {
203 if (!has(key) || (vals_.at(key)->type() != RNP_CFG_VAL_STRING)) {
204 return empty_str_;
205 }
206 return (dynamic_cast<const rnp_cfg_str_val *>(vals_.at(key)))->val();
207 }
208
209 const char *
get_cstr(const std::string & key) const210 rnp_cfg::get_cstr(const std::string &key) const
211 {
212 if (!has(key) || (vals_.at(key)->type() != RNP_CFG_VAL_STRING)) {
213 return NULL;
214 }
215 return (dynamic_cast<const rnp_cfg_str_val *>(vals_.at(key)))->val().c_str();
216 }
217
218 int
get_int(const std::string & key,int def) const219 rnp_cfg::get_int(const std::string &key, int def) const
220 {
221 if (!has(key)) {
222 return def;
223 }
224 const rnp_cfg_val *val = vals_.at(key);
225 switch (val->type()) {
226 case RNP_CFG_VAL_INT:
227 return (dynamic_cast<const rnp_cfg_int_val *>(val))->val();
228 case RNP_CFG_VAL_BOOL:
229 return (dynamic_cast<const rnp_cfg_bool_val *>(val))->val();
230 case RNP_CFG_VAL_STRING:
231 return atoi((dynamic_cast<const rnp_cfg_str_val *>(val))->val().c_str());
232 default:
233 return def;
234 }
235 }
236
237 bool
get_bool(const std::string & key) const238 rnp_cfg::get_bool(const std::string &key) const
239 {
240 if (!has(key)) {
241 return false;
242 }
243 const rnp_cfg_val *val = vals_.at(key);
244 switch (val->type()) {
245 case RNP_CFG_VAL_INT:
246 return (dynamic_cast<const rnp_cfg_int_val *>(val))->val();
247 case RNP_CFG_VAL_BOOL:
248 return (dynamic_cast<const rnp_cfg_bool_val *>(val))->val();
249 case RNP_CFG_VAL_STRING: {
250 const std::string &str = (dynamic_cast<const rnp_cfg_str_val *>(val))->val();
251 return !strcasecmp(str.c_str(), "true") || (atoi(str.c_str()) > 0);
252 }
253 default:
254 return false;
255 }
256 }
257
258 size_t
get_count(const std::string & key) const259 rnp_cfg::get_count(const std::string &key) const
260 {
261 if (!has(key) || (vals_.at(key)->type() != RNP_CFG_VAL_LIST)) {
262 return 0;
263 }
264 const rnp_cfg_list_val *val = dynamic_cast<const rnp_cfg_list_val *>(vals_.at(key));
265 return val->val().size();
266 }
267
268 const std::string &
get_str(const std::string & key,size_t idx) const269 rnp_cfg::get_str(const std::string &key, size_t idx) const
270 {
271 if (get_count(key) <= idx) {
272 RNP_LOG("idx is out of bounds for \"%s\"", key.c_str());
273 throw std::invalid_argument("idx");
274 }
275 const rnp_cfg_list_val *val = dynamic_cast<const rnp_cfg_list_val *>(vals_.at(key));
276 return val->val().at(idx);
277 }
278
279 std::vector<std::string>
get_list(const std::string & key) const280 rnp_cfg::get_list(const std::string &key) const
281 {
282 if (!has(key)) {
283 /* it's okay to return empty list */
284 return std::vector<std::string>();
285 }
286 if (vals_.at(key)->type() != RNP_CFG_VAL_LIST) {
287 RNP_LOG("no list at the key \"%s\"", key.c_str());
288 throw std::invalid_argument("key");
289 }
290 const rnp_cfg_list_val *val = dynamic_cast<const rnp_cfg_list_val *>(vals_.at(key));
291 return val->val();
292 }
293
294 int
get_pswdtries() const295 rnp_cfg::get_pswdtries() const
296 {
297 const std::string &numtries = get_str(CFG_NUMTRIES);
298 int num = atoi(numtries.c_str());
299 if (numtries.empty() || (num <= 0)) {
300 return MAX_PASSWORD_ATTEMPTS;
301 } else if (numtries == "unlimited") {
302 return INFINITE_ATTEMPTS;
303 }
304 return num;
305 }
306
307 const std::string
get_hashalg() const308 rnp_cfg::get_hashalg() const
309 {
310 const std::string hash_alg = get_str(CFG_HASH);
311 if (!hash_alg.empty()) {
312 return hash_alg;
313 }
314 return DEFAULT_HASH_ALG;
315 }
316
317 void
copy(const rnp_cfg & src)318 rnp_cfg::copy(const rnp_cfg &src)
319 {
320 for (const auto &it : src.vals_) {
321 if (has(it.first)) {
322 unset(it.first);
323 }
324 rnp_cfg_val *val = NULL;
325 switch (it.second->type()) {
326 case RNP_CFG_VAL_INT:
327 val = new rnp_cfg_int_val(*(dynamic_cast<rnp_cfg_int_val *>(it.second)));
328 break;
329 case RNP_CFG_VAL_BOOL:
330 val = new rnp_cfg_bool_val(*(dynamic_cast<rnp_cfg_bool_val *>(it.second)));
331 break;
332 case RNP_CFG_VAL_STRING:
333 val = new rnp_cfg_str_val(*(dynamic_cast<rnp_cfg_str_val *>(it.second)));
334 break;
335 case RNP_CFG_VAL_LIST:
336 val = new rnp_cfg_list_val(*(dynamic_cast<rnp_cfg_list_val *>(it.second)));
337 break;
338 default:
339 continue;
340 }
341 vals_[it.first] = val;
342 }
343 }
344
345 void
clear()346 rnp_cfg::clear()
347 {
348 for (const auto &it : vals_) {
349 delete it.second;
350 }
351 vals_.clear();
352 }
353
~rnp_cfg()354 rnp_cfg::~rnp_cfg()
355 {
356 clear();
357 }
358
359 /**
360 * @brief Get number of days in month.
361 *
362 * @param year number of year, i.e. 2021
363 * @param month number of month, 1..12
364 * @return number of days (28..31) or 0 if month is wrong.
365 */
366 static int
days_in_month(int year,int month)367 days_in_month(int year, int month)
368 {
369 switch (month) {
370 case 1:
371 case 3:
372 case 5:
373 case 7:
374 case 8:
375 case 10:
376 case 12:
377 return 31;
378 case 4:
379 case 6:
380 case 9:
381 case 11:
382 return 30;
383 case 2: {
384 bool leap_year = !(year % 400) || (!(year % 4) && (year % 100));
385 return leap_year ? 29 : 28;
386 }
387 default:
388 return 0;
389 }
390 }
391
392 /**
393 * @brief Grabs date from the string in %Y-%m-%d format
394 *
395 * @param s [in] NULL-terminated string with the date
396 * @param t [out] On successful return result will be placed here
397 * @return true on success or false otherwise
398 */
399
400 /** @brief
401 *
402 * @param s [in] NULL-terminated string with the date
403 * @param t [out] UNIX timestamp of
404 * successfully parsed date
405 * @return 0 when parsed successfully
406 * 1 when s doesn't match the regex
407 * -1 when
408 * s matches the regex but the date is not acceptable
409 * -2 failure
410 */
411 static int
grabdate(const char * s,uint64_t * t)412 grabdate(const char *s, uint64_t *t)
413 {
414 /* fill time zone information */
415 const time_t now = time(NULL);
416 struct tm tm = *localtime(&now);
417 tm.tm_hour = 0;
418 tm.tm_min = 0;
419 tm.tm_sec = 0;
420 #ifndef RNP_USE_STD_REGEX
421 static regex_t r;
422 static int compiled;
423 regmatch_t matches[10];
424
425 if (!compiled) {
426 compiled = 1;
427 if (regcomp(&r,
428 "^([0-9][0-9][0-9][0-9])[-/]([0-9][0-9])[-/]([0-9][0-9])$",
429 REG_EXTENDED) != 0) {
430 RNP_LOG("failed to compile regexp");
431 return -2;
432 }
433 }
434 if (regexec(&r, s, 10, matches, 0) != 0) {
435 return 1;
436 }
437 int year = (int) strtol(&s[(int) matches[1].rm_so], NULL, 10);
438 int mon = (int) strtol(&s[(int) matches[2].rm_so], NULL, 10);
439 int mday = (int) strtol(&s[(int) matches[3].rm_so], NULL, 10);
440 #else
441 static std::regex re("^([0-9][0-9][0-9][0-9])[-/]([0-9][0-9])[-/]([0-9][0-9])$",
442 std::regex_constants::ECMAScript);
443 std::smatch result;
444 std::string input = s;
445
446 if (!std::regex_search(input, result, re)) {
447 return 1;
448 }
449 int year = (int) strtol(result[1].str().c_str(), NULL, 10);
450 int mon = (int) strtol(result[2].str().c_str(), NULL, 10);
451 int mday = (int) strtol(result[3].str().c_str(), NULL, 10);
452 #endif
453 if (year < 1970 || mon < 1 || mon > 12 || !mday || (mday > days_in_month(year, mon))) {
454 return -1;
455 }
456 tm.tm_year = year - 1900;
457 tm.tm_mon = mon - 1;
458 tm.tm_mday = mday;
459
460 struct tm check_tm = tm;
461 time_t built_time = rnp_mktime(&tm);
462 time_t check_time = mktime(&check_tm);
463 if (built_time != check_time) {
464 /* If date is beyond of yk2038 and we have 32-bit signed time_t, we need to reduce
465 * timestamp */
466 RNP_LOG("Warning: date %s is beyond of 32-bit time_t, so timestamp was reduced to "
467 "maximum supported value.",
468 s);
469 }
470 *t = built_time;
471 return 0;
472 }
473
474 int
get_expiration(const char * s,uint32_t * res)475 get_expiration(const char *s, uint32_t *res)
476 {
477 if (!s || !strlen(s)) {
478 return -1;
479 }
480 uint64_t delta;
481 uint64_t t;
482 int grabdate_result = grabdate(s, &t);
483 if (!grabdate_result) {
484 uint64_t now = time(NULL);
485 if (t > now) {
486 delta = t - now;
487 if (delta > UINT32_MAX) {
488 return -3;
489 }
490 *res = delta;
491 return 0;
492 }
493 return -2;
494 } else if (grabdate_result < 0) {
495 return -2;
496 }
497 #ifndef RNP_USE_STD_REGEX
498 static regex_t r;
499 static int compiled;
500 regmatch_t matches[10];
501
502 if (!compiled) {
503 compiled = 1;
504 if (regcomp(&r, "^([0-9]+)([hdwmy]?)$", REG_EXTENDED | REG_ICASE) != 0) {
505 RNP_LOG("failed to compile regexp");
506 return -2;
507 }
508 }
509 if (regexec(&r, s, 10, matches, 0) != 0) {
510 return -2;
511 }
512 auto delta_str = &s[(int) matches[1].rm_so];
513 char mult = s[(int) matches[2].rm_so];
514 #else
515 static std::regex re("^([0-9]+)([hdwmy]?)$",
516 std::regex_constants::ECMAScript | std::regex_constants::icase);
517 std::smatch result;
518 std::string input = s;
519
520 if (!std::regex_search(input, result, re)) {
521 return -2;
522 }
523 std::string delta_stdstr = result[1].str();
524 const char *delta_str = delta_stdstr.c_str();
525 char mult = result[2].str()[0];
526 #endif
527 errno = 0;
528 delta = (uint64_t) strtoul(delta_str, NULL, 10);
529 if (errno || delta > UINT_MAX) {
530 return -3;
531 }
532 switch (std::tolower(mult)) {
533 case 'h':
534 delta *= 60 * 60;
535 break;
536 case 'd':
537 delta *= 60 * 60 * 24;
538 break;
539 case 'w':
540 delta *= 60 * 60 * 24 * 7;
541 break;
542 case 'm':
543 delta *= 60 * 60 * 24 * 31;
544 break;
545 case 'y':
546 delta *= 60 * 60 * 24 * 365;
547 break;
548 }
549 if (delta > UINT32_MAX) {
550 return -4;
551 }
552 *res = delta;
553 return 0;
554 }
555
556 int64_t
get_creation(const char * s)557 get_creation(const char *s)
558 {
559 uint64_t t;
560
561 if (!s || !strlen(s)) {
562 return time(NULL);
563 }
564 if (!grabdate(s, &t)) {
565 return t;
566 }
567 return (uint64_t) strtoll(s, NULL, 10);
568 }
569