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