1 /*
2  * Copyright (C) 2018 Rafael Ostertag
3  *
4  * This file is part of YAPET.
5  *
6  * YAPET is free software: you can redistribute it and/or modify it under the
7  * terms of the GNU General Public License as published by the Free Software
8  * Foundation, either version 3 of the License, or (at your option) any later
9  * version.
10  *
11  * YAPET is distributed in the hope that it will be useful, but WITHOUT ANY
12  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
14  * details.
15  *
16  * You should have received a copy of the GNU General Public License along with
17  * YAPET.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  * Additional permission under GNU GPL version 3 section 7
20  *
21  * If you modify this program, or any covered work, by linking or combining it
22  * with the OpenSSL project's OpenSSL library (or a modified version of that
23  * library), containing parts covered by the terms of the OpenSSL or SSLeay
24  * licenses, Rafael Ostertag grants you additional permission to convey the
25  * resulting work.  Corresponding Source for a non-source form of such a
26  * combination shall include the source code for the parts of OpenSSL used as
27  * well as that of the covered work.
28  */
29 
30 #ifdef HAVE_CONFIG_H
31 #include "config.h"
32 #endif
33 
34 #ifdef CFGDEBUG
35 #include <iostream>
36 #endif
37 
38 #include <cctype>
39 #include <cstdio>
40 #include <cstdlib>
41 
42 #include <pwd.h>
43 #include <unistd.h>
44 
45 #include "cfg.h"
46 #include "intl.h"
47 
48 using namespace YAPET;
49 using namespace YAPET::CONFIG;
50 
51 namespace YAPET {
52 namespace CONFIG {
trim(const std::string & s)53 std::string trim(const std::string& s) {
54     if (s.empty()) return s;
55 
56     // find leading spaces
57     std::string::size_type pos = 0;
58     while (std::isspace(s[pos++]) && s.length() > pos)
59         ;
60     pos--;
61     assert(pos < s.length());
62 
63     std::string working_copy(s.substr(pos));
64 
65     // find trailing spaces
66     pos = working_copy.length() - 1;
67     while (std::isspace(working_copy[pos]) && pos != 0) {
68         pos--;
69     }
70     assert(pos < working_copy.length());
71 
72     return working_copy.substr(0, pos + 1);
73 }
74 
getHomeDir()75 std::string getHomeDir() {
76     std::string homedir("");
77 
78     char* hd = getenv("HOME");
79 
80     if (hd != 0) {
81         homedir = hd;
82 
83         if (homedir[homedir.length()] != '/') homedir.push_back('/');
84 
85         return homedir;
86     }
87 
88     struct passwd* pwd;
89     pwd = getpwuid(getuid());
90 
91     if (pwd != 0) {
92         homedir = pwd->pw_dir;
93 
94         if (homedir[homedir.length()] != '/') homedir.push_back('/');
95 
96         return homedir;
97     }
98 
99     assert(!homedir.empty());
100     return homedir;
101 }
102 }  // namespace CONFIG
103 }  // namespace YAPET
104 
105 //
106 // Class CfgValPetFile
107 //
cleanup_path(const std::string & p)108 std::string CfgValPetFile::cleanup_path(const std::string& p) {
109     if (p.empty()) return p;
110 
111     std::string working_copy(p);
112 
113     // If there is a ~ at the very first position, replace that with
114     // the home directory of the user.
115     if (working_copy[0] == '~') working_copy.replace(0, 1, getHomeDir());
116 
117     if (working_copy.find("//") == std::string::npos) return working_copy;
118 
119     std::string::size_type pos = 0;
120 
121     while ((pos = working_copy.find("//")) != std::string::npos)
122         working_copy = working_copy.replace(pos, 2, "/");
123 
124     return working_copy;
125 }
126 
add_suffix(const std::string & p)127 std::string CfgValPetFile::add_suffix(const std::string& p) {
128     if (p.empty()) {
129         return p;
130     }
131 
132     if (p.length() < 4) {
133         // Since this holds, there can no ".pet"
134         return p + Consts::DEFAULT_FILE_SUFFIX;
135     }
136 
137     // We don't have to check for the string length, because that
138     // already happened above.
139     if (p.substr(p.length() - 4, 4) != Consts::DEFAULT_FILE_SUFFIX) {
140         return p + Consts::DEFAULT_FILE_SUFFIX;
141     }
142 
143     return p;
144 }
145 
set(const std::string & s)146 void CfgValPetFile::set(const std::string& s) {
147     CfgValStr::set(add_suffix(cleanup_path(s)));
148 }
149 
set_str(const std::string & s)150 void CfgValPetFile::set_str(const std::string& s) { set(s); }
151 //
152 // Class CfgValBool
153 //
set_str(const std::string & s)154 void CfgValBool::set_str(const std::string& s) {
155     std::string sanitized(tolower(trim(s)));
156 
157     if (sanitized == "0" || sanitized == "false" || sanitized == "no" ||
158         sanitized == "disable" || sanitized == "disabled") {
159         set(false);
160         return;
161     }
162 
163     if (sanitized == "1" || sanitized == "true" || sanitized == "yes" ||
164         sanitized == "enable" || sanitized == "enabled") {
165         set(true);
166         return;
167     }
168 
169     char msg[YAPET::Consts::EXCEPTION_MESSAGE_BUFFER_SIZE];
170     std::snprintf(msg, YAPET::Consts::EXCEPTION_MESSAGE_BUFFER_SIZE,
171                   _("'%s' is not a valid bool"), sanitized.c_str());
172     throw std::invalid_argument(msg);
173 }
174 
175 //
176 // Class CfgValInt
177 //
set_str(const std::string & s)178 void CfgValInt::set_str(const std::string& s) { set(std::atoi(s.c_str())); }
179 
setup_map()180 void Config::setup_map() {
181     _options.clear();
182 
183     _options["load"] = &petfile;
184     _options["locktimeout"] = &timeout;
185     _options["checkfsecurity"] = &filesecurity;
186     _options["allowlockquit"] = &allow_lock_quit;
187     _options["pwinputtimeout"] = &pw_input_timeout;
188     _options["pwgen_pwlen"] = &pwgenpwlen;
189     _options["pwgen_letters"] = &pwgen_letters;
190     _options["pwgen_digits"] = &pwgen_digits;
191     _options["pwgen_punct"] = &pwgen_punct;
192     _options["pwgen_special"] = &pwgen_special;
193     _options["pwgen_other"] = &pwgen_other;
194     _options["argon2_memory"] = &argon2_memory;
195     _options["argon2_parallelism"] = &argon2_parallelism;
196     _options["argon2_iterations"] = &argon2_iterations;
197     _options["colors"] = &colors;
198     // ignorerc can't be set in the configuration file
199 }
200 
Config()201 Config::Config()
202     : _options{},
203       petfile{std::string{}},
204       timeout{Consts::DEFAULT_LOCK_TIMEOUT, Consts::MIN_LOCK_TIMEOUT,
205               Consts::MIN_LOCK_TIMEOUT},
206       filesecurity{Consts::DEFAULT_FILE_SECURITY},
207       pwgenpwlen{Consts::DEFAULT_PASSWORD_LENGTH,
208                  Consts::DEFAULT_PASSWORD_LENGTH, Consts::MIN_PASSWORD_LENGTH,
209                  Consts::MAX_PASSWORD_LENGTH},
210       pwgen_letters{yapet::pwgen::isLetters(Consts::DEFAULT_CHARACTER_POOLS)},
211       pwgen_digits{yapet::pwgen::isDigits(Consts::DEFAULT_CHARACTER_POOLS)},
212       pwgen_punct{yapet::pwgen::isPunct(Consts::DEFAULT_CHARACTER_POOLS)},
213       pwgen_special{yapet::pwgen::isSpecial(Consts::DEFAULT_CHARACTER_POOLS)},
214       pwgen_other{yapet::pwgen::isOther(Consts::DEFAULT_CHARACTER_POOLS)},
215       allow_lock_quit{Consts::DEFAULT_ALLOW_LOCK_QUIT},
216       pw_input_timeout{Consts::DEFAULT_PASSWORD_INPUT_TIMEOUT,
217                        Consts::MIN_LOCK_TIMEOUT, Consts::MIN_LOCK_TIMEOUT},
218       argon2_memory{Consts::DEFAULT_ARGON2_MEMORY,
219                     Consts::DEFAULT_ARGON2_MEMORY, Consts::MIN_ARGON2_MEMORY},
220       argon2_parallelism{Consts::DEFAULT_ARGON2_PARALLELISM,
221                          Consts::DEFAULT_ARGON2_PARALLELISM,
222                          Consts::MIN_ARGON2_PARALLELISM},
223       argon2_iterations{Consts::DEFAULT_ARGON2_TIME_COST,
224                         Consts::DEFAULT_ARGON2_TIME_COST,
225                         Consts::MIN_ARGON2_TIME_COSTS},
226       ignorerc{false},
227       colors{} {
228     setup_map();
229 }
230 
Config(const Config & c)231 Config::Config(const Config& c)
232     : _options{},
233       petfile{c.petfile},
234       timeout{c.timeout},
235       filesecurity{c.filesecurity},
236       pwgenpwlen{c.pwgenpwlen},
237       pwgen_letters{c.pwgen_letters},
238       pwgen_digits{c.pwgen_digits},
239       pwgen_punct{c.pwgen_punct},
240       pwgen_special{c.pwgen_special},
241       pwgen_other{c.pwgen_other},
242       allow_lock_quit{c.allow_lock_quit},
243       pw_input_timeout{c.pw_input_timeout},
244       argon2_memory{c.argon2_memory},
245       argon2_parallelism{c.argon2_parallelism},
246       argon2_iterations{c.argon2_iterations},
247       ignorerc{c.ignorerc},
248       colors{c.colors} {
249     setup_map();
250 }
251 
~Config()252 Config::~Config() {}
253 
operator =(const Config & c)254 Config& Config::operator=(const Config& c) {
255     if (&c == this) return *this;
256 
257     petfile = c.petfile;
258     timeout = c.timeout;
259     filesecurity = c.filesecurity;
260     pwgenpwlen = c.pwgenpwlen;
261     pwgen_letters = c.pwgen_letters;
262     pwgen_digits = c.pwgen_digits;
263     pwgen_punct = c.pwgen_punct;
264     pwgen_special = c.pwgen_special;
265     pwgen_other = c.pwgen_other;
266     allow_lock_quit = c.allow_lock_quit;
267     pw_input_timeout = c.pw_input_timeout;
268     argon2_memory = c.argon2_memory;
269     argon2_parallelism = c.argon2_parallelism;
270     argon2_iterations = c.argon2_iterations;
271     ignorerc = c.ignorerc;
272     colors = c.colors;
273 
274     setup_map();
275 
276     return *this;
277 }
278 
character_pools() const279 int Config::character_pools() const {
280     int pools = 0;
281 
282     if (pwgen_letters) pools |= yapet::pwgen::LETTERS;
283 
284     if (pwgen_digits) pools |= yapet::pwgen::DIGITS;
285 
286     if (pwgen_punct) pools |= yapet::pwgen::PUNCT;
287 
288     if (pwgen_special) pools |= yapet::pwgen::SPECIAL;
289 
290     if (pwgen_other) pools |= yapet::pwgen::OTHER;
291 
292     return pools;
293 }
294 
lock()295 void Config::lock() {
296     petfile.lock();
297     timeout.lock();
298     filesecurity.lock();
299     pwgenpwlen.lock();
300     pwgen_letters.lock();
301     pwgen_digits.lock();
302     pwgen_punct.lock();
303     pwgen_special.lock();
304     pwgen_other.lock();
305     allow_lock_quit.lock();
306     pw_input_timeout.lock();
307     argon2_iterations.lock();
308     argon2_memory.lock();
309     argon2_parallelism.lock();
310     ignorerc.lock();
311     colors.lock();
312 }
313 
unlock()314 void Config::unlock() {
315     petfile.unlock();
316     timeout.unlock();
317     filesecurity.unlock();
318     pwgenpwlen.unlock();
319     pwgen_letters.unlock();
320     pwgen_digits.unlock();
321     pwgen_punct.unlock();
322     pwgen_special.unlock();
323     pwgen_other.unlock();
324     allow_lock_quit.unlock();
325     pw_input_timeout.unlock();
326     argon2_iterations.unlock();
327     argon2_memory.unlock();
328     argon2_parallelism.unlock();
329     ignorerc.unlock();
330     colors.unlock();
331 }
332 
operator [](const std::string & key)333 CfgValBase& Config::operator[](const std::string& key) {
334     if (key.empty())
335         throw std::invalid_argument(_("Configuration key must not be empty"));
336 
337     std::map<std::string, CfgValBase*>::iterator it = _options.find(key);
338 
339     if (it == _options.end()) {
340         char msg[YAPET::Consts::EXCEPTION_MESSAGE_BUFFER_SIZE];
341         std::snprintf(msg, YAPET::Consts::EXCEPTION_MESSAGE_BUFFER_SIZE,
342                       _("Configuration key '%s' not found"), key.c_str());
343         throw std::invalid_argument(msg);
344     }
345 
346     return *((*it).second);
347 }
348