1 /******************************************************************************
2  *
3  *  Copyright 2017 The Android Open Source Project
4  *
5  *  Licensed under the Apache License, Version 2.0 (the "License");
6  *  you may not use this file except in compliance with the License.
7  *  You may obtain a copy of the License at:
8  *
9  *  http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  *
17  ******************************************************************************/
18 
19 #include "osi/include/config.h"
20 
21 #include <base/files/file_util.h>
22 #include <base/logging.h>
23 #include <ctype.h>
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <libgen.h>
27 #include <log/log.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <sys/stat.h>
32 #include <unistd.h>
33 
34 #include <sstream>
35 #include <type_traits>
36 
Set(std::string key,std::string value)37 void section_t::Set(std::string key, std::string value) {
38   for (entry_t& entry : entries) {
39     if (entry.key == key) {
40       entry.value = value;
41       return;
42     }
43   }
44   // add a new key to the section
45   entries.emplace_back(
46       entry_t{.key = std::move(key), .value = std::move(value)});
47 }
48 
Find(const std::string & key)49 std::list<entry_t>::iterator section_t::Find(const std::string& key) {
50   return std::find_if(
51       entries.begin(), entries.end(),
52       [&key](const entry_t& entry) { return entry.key == key; });
53 }
54 
Has(const std::string & key)55 bool section_t::Has(const std::string& key) {
56   return Find(key) != entries.end();
57 }
58 
Find(const std::string & section)59 std::list<section_t>::iterator config_t::Find(const std::string& section) {
60   return std::find_if(
61       sections.begin(), sections.end(),
62       [&section](const section_t& sec) { return sec.name == section; });
63 }
64 
Has(const std::string & key)65 bool config_t::Has(const std::string& key) {
66   return Find(key) != sections.end();
67 }
68 
69 static bool config_parse(FILE* fp, config_t* config);
70 
71 template <typename T,
72           class = typename std::enable_if<std::is_same<
73               config_t, typename std::remove_const<T>::type>::value>>
section_find(T & config,const std::string & section)74 static auto section_find(T& config, const std::string& section) {
75   return std::find_if(
76       config.sections.begin(), config.sections.end(),
77       [&section](const section_t& sec) { return sec.name == section; });
78 }
79 
entry_find(const config_t & config,const std::string & section,const std::string & key)80 static const entry_t* entry_find(const config_t& config,
81                                  const std::string& section,
82                                  const std::string& key) {
83   auto sec = section_find(config, section);
84   if (sec == config.sections.end()) return nullptr;
85 
86   for (const entry_t& entry : sec->entries) {
87     if (entry.key == key) return &entry;
88   }
89 
90   return nullptr;
91 }
92 
config_new_empty(void)93 std::unique_ptr<config_t> config_new_empty(void) {
94   return std::make_unique<config_t>();
95 }
96 
config_new(const char * filename)97 std::unique_ptr<config_t> config_new(const char* filename) {
98   CHECK(filename != nullptr);
99 
100   std::unique_ptr<config_t> config = config_new_empty();
101 
102   FILE* fp = fopen(filename, "rt");
103   if (!fp) {
104     LOG(ERROR) << __func__ << ": unable to open file '" << filename
105                << "': " << strerror(errno);
106     return nullptr;
107   }
108 
109   if (!config_parse(fp, config.get())) {
110     config.reset();
111   }
112 
113   fclose(fp);
114   return config;
115 }
116 
checksum_read(const char * filename)117 std::string checksum_read(const char* filename) {
118   base::FilePath path(filename);
119   if (!base::PathExists(path)) {
120     LOG(ERROR) << __func__ << ": unable to locate file '" << filename << "'";
121     return "";
122   }
123   std::string encrypted_hash;
124   if (!base::ReadFileToString(path, &encrypted_hash)) {
125     LOG(ERROR) << __func__ << ": unable to read file '" << filename << "'";
126   }
127   return encrypted_hash;
128 }
129 
config_new_clone(const config_t & src)130 std::unique_ptr<config_t> config_new_clone(const config_t& src) {
131   std::unique_ptr<config_t> ret = config_new_empty();
132 
133   for (const section_t& sec : src.sections) {
134     for (const entry_t& entry : sec.entries) {
135       config_set_string(ret.get(), sec.name, entry.key, entry.value);
136     }
137   }
138 
139   return ret;
140 }
141 
config_has_section(const config_t & config,const std::string & section)142 bool config_has_section(const config_t& config, const std::string& section) {
143   return (section_find(config, section) != config.sections.end());
144 }
145 
config_has_key(const config_t & config,const std::string & section,const std::string & key)146 bool config_has_key(const config_t& config, const std::string& section,
147                     const std::string& key) {
148   return (entry_find(config, section, key) != nullptr);
149 }
150 
config_get_int(const config_t & config,const std::string & section,const std::string & key,int def_value)151 int config_get_int(const config_t& config, const std::string& section,
152                    const std::string& key, int def_value) {
153   const entry_t* entry = entry_find(config, section, key);
154   if (!entry) return def_value;
155 
156   char* endptr;
157   int ret = strtol(entry->value.c_str(), &endptr, 0);
158   return (*endptr == '\0') ? ret : def_value;
159 }
160 
config_get_uint64(const config_t & config,const std::string & section,const std::string & key,uint64_t def_value)161 uint64_t config_get_uint64(const config_t& config, const std::string& section,
162                            const std::string& key, uint64_t def_value) {
163   const entry_t* entry = entry_find(config, section, key);
164   if (!entry) return def_value;
165 
166   char* endptr;
167   uint64_t ret = strtoull(entry->value.c_str(), &endptr, 0);
168   return (*endptr == '\0') ? ret : def_value;
169 }
170 
config_get_bool(const config_t & config,const std::string & section,const std::string & key,bool def_value)171 bool config_get_bool(const config_t& config, const std::string& section,
172                      const std::string& key, bool def_value) {
173   const entry_t* entry = entry_find(config, section, key);
174   if (!entry) return def_value;
175 
176   if (entry->value == "true") return true;
177   if (entry->value == "false") return false;
178 
179   return def_value;
180 }
181 
config_get_string(const config_t & config,const std::string & section,const std::string & key,const std::string * def_value)182 const std::string* config_get_string(const config_t& config,
183                                      const std::string& section,
184                                      const std::string& key,
185                                      const std::string* def_value) {
186   const entry_t* entry = entry_find(config, section, key);
187   if (!entry) return def_value;
188 
189   return &entry->value;
190 }
191 
config_set_int(config_t * config,const std::string & section,const std::string & key,int value)192 void config_set_int(config_t* config, const std::string& section,
193                     const std::string& key, int value) {
194   config_set_string(config, section, key, std::to_string(value));
195 }
196 
config_set_uint64(config_t * config,const std::string & section,const std::string & key,uint64_t value)197 void config_set_uint64(config_t* config, const std::string& section,
198                        const std::string& key, uint64_t value) {
199   config_set_string(config, section, key, std::to_string(value));
200 }
201 
config_set_bool(config_t * config,const std::string & section,const std::string & key,bool value)202 void config_set_bool(config_t* config, const std::string& section,
203                      const std::string& key, bool value) {
204   config_set_string(config, section, key, value ? "true" : "false");
205 }
206 
config_set_string(config_t * config,const std::string & section,const std::string & key,const std::string & value)207 void config_set_string(config_t* config, const std::string& section,
208                        const std::string& key, const std::string& value) {
209   CHECK(config);
210 
211   auto sec = section_find(*config, section);
212   if (sec == config->sections.end()) {
213     config->sections.emplace_back(section_t{.name = section});
214     sec = std::prev(config->sections.end());
215   }
216 
217   std::string value_no_newline;
218   size_t newline_position = value.find('\n');
219   if (newline_position != std::string::npos) {
220     android_errorWriteLog(0x534e4554, "70808273");
221     value_no_newline = value.substr(0, newline_position);
222   } else {
223     value_no_newline = value;
224   }
225 
226   for (entry_t& entry : sec->entries) {
227     if (entry.key == key) {
228       entry.value = value_no_newline;
229       return;
230     }
231   }
232 
233   sec->entries.emplace_back(entry_t{.key = key, .value = value_no_newline});
234 }
235 
config_remove_section(config_t * config,const std::string & section)236 bool config_remove_section(config_t* config, const std::string& section) {
237   CHECK(config);
238 
239   auto sec = section_find(*config, section);
240   if (sec == config->sections.end()) return false;
241 
242   config->sections.erase(sec);
243   return true;
244 }
245 
config_remove_key(config_t * config,const std::string & section,const std::string & key)246 bool config_remove_key(config_t* config, const std::string& section,
247                        const std::string& key) {
248   CHECK(config);
249   auto sec = section_find(*config, section);
250   if (sec == config->sections.end()) return false;
251 
252   for (auto entry = sec->entries.begin(); entry != sec->entries.end();
253        ++entry) {
254     if (entry->key == key) {
255       sec->entries.erase(entry);
256       return true;
257     }
258   }
259 
260   return false;
261 }
262 
config_save(const config_t & config,const std::string & filename)263 bool config_save(const config_t& config, const std::string& filename) {
264   CHECK(!filename.empty());
265 
266   // Steps to ensure content of config file gets to disk:
267   //
268   // 1) Open and write to temp file (e.g. bt_config.conf.new).
269   // 2) Flush the stream buffer to the temp file.
270   // 3) Sync the temp file to disk with fsync().
271   // 4) Rename temp file to actual config file (e.g. bt_config.conf).
272   //    This ensures atomic update.
273   // 5) Sync directory that has the conf file with fsync().
274   //    This ensures directory entries are up-to-date.
275   int dir_fd = -1;
276   FILE* fp = nullptr;
277   std::stringstream serialized;
278 
279   // Build temp config file based on config file (e.g. bt_config.conf.new).
280   const std::string temp_filename = filename + ".new";
281 
282   // Extract directory from file path (e.g. /data/misc/bluedroid).
283   const std::string directoryname = base::FilePath(filename).DirName().value();
284   if (directoryname.empty()) {
285     LOG(ERROR) << __func__ << ": error extracting directory from '" << filename
286                << "': " << strerror(errno);
287     goto error;
288   }
289 
290   dir_fd = open(directoryname.c_str(), O_RDONLY);
291   if (dir_fd < 0) {
292     LOG(ERROR) << __func__ << ": unable to open dir '" << directoryname
293                << "': " << strerror(errno);
294     goto error;
295   }
296 
297   fp = fopen(temp_filename.c_str(), "wt");
298   if (!fp) {
299     LOG(ERROR) << __func__ << ": unable to write to file '" << temp_filename
300                << "': " << strerror(errno);
301     goto error;
302   }
303 
304   for (const section_t& section : config.sections) {
305     serialized << "[" << section.name << "]" << std::endl;
306 
307     for (const entry_t& entry : section.entries)
308       serialized << entry.key << " = " << entry.value << std::endl;
309 
310     serialized << std::endl;
311   }
312 
313   if (fprintf(fp, "%s", serialized.str().c_str()) < 0) {
314     LOG(ERROR) << __func__ << ": unable to write to file '" << temp_filename
315                << "': " << strerror(errno);
316     goto error;
317   }
318 
319   // Flush the stream buffer to the temp file.
320   if (fflush(fp) < 0) {
321     LOG(ERROR) << __func__ << ": unable to write flush buffer to file '"
322                << temp_filename << "': " << strerror(errno);
323     goto error;
324   }
325 
326   // Sync written temp file out to disk. fsync() is blocking until data makes it
327   // to disk.
328   if (fsync(fileno(fp)) < 0) {
329     LOG(WARNING) << __func__ << ": unable to fsync file '" << temp_filename
330                  << "': " << strerror(errno);
331   }
332 
333   if (fclose(fp) == EOF) {
334     LOG(ERROR) << __func__ << ": unable to close file '" << temp_filename
335                << "': " << strerror(errno);
336     goto error;
337   }
338   fp = nullptr;
339 
340   // Change the file's permissions to Read/Write by User and Group
341   if (chmod(temp_filename.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) ==
342       -1) {
343     LOG(ERROR) << __func__ << ": unable to change file permissions '"
344                << filename << "': " << strerror(errno);
345     goto error;
346   }
347 
348   // Rename written temp file to the actual config file.
349   if (rename(temp_filename.c_str(), filename.c_str()) == -1) {
350     LOG(ERROR) << __func__ << ": unable to commit file '" << filename
351                << "': " << strerror(errno);
352     goto error;
353   }
354 
355   // This should ensure the directory is updated as well.
356   if (fsync(dir_fd) < 0) {
357     LOG(WARNING) << __func__ << ": unable to fsync dir '" << directoryname
358                  << "': " << strerror(errno);
359   }
360 
361   if (close(dir_fd) < 0) {
362     LOG(ERROR) << __func__ << ": unable to close dir '" << directoryname
363                << "': " << strerror(errno);
364     goto error;
365   }
366 
367   return true;
368 
369 error:
370   // This indicates there is a write issue.  Unlink as partial data is not
371   // acceptable.
372   unlink(temp_filename.c_str());
373   if (fp) fclose(fp);
374   if (dir_fd != -1) close(dir_fd);
375   return false;
376 }
377 
checksum_save(const std::string & checksum,const std::string & filename)378 bool checksum_save(const std::string& checksum, const std::string& filename) {
379   CHECK(!checksum.empty()) << __func__ << ": checksum cannot be empty";
380   CHECK(!filename.empty()) << __func__ << ": filename cannot be empty";
381 
382   // Steps to ensure content of config checksum file gets to disk:
383   //
384   // 1) Open and write to temp file (e.g.
385   // bt_config.conf.encrypted-checksum.new). 2) Sync the temp file to disk with
386   // fsync(). 3) Rename temp file to actual config checksum file (e.g.
387   // bt_config.conf.encrypted-checksum).
388   //    This ensures atomic update.
389   // 4) Sync directory that has the conf file with fsync().
390   //    This ensures directory entries are up-to-date.
391   FILE* fp = nullptr;
392   int dir_fd = -1;
393 
394   // Build temp config checksum file based on config checksum file (e.g.
395   // bt_config.conf.encrypted-checksum.new).
396   const std::string temp_filename = filename + ".new";
397   base::FilePath path(temp_filename);
398 
399   // Extract directory from file path (e.g. /data/misc/bluedroid).
400   const std::string directoryname = base::FilePath(filename).DirName().value();
401   if (directoryname.empty()) {
402     LOG(ERROR) << __func__ << ": error extracting directory from '" << filename
403                << "': " << strerror(errno);
404     goto error2;
405   }
406 
407   dir_fd = open(directoryname.c_str(), O_RDONLY);
408   if (dir_fd < 0) {
409     LOG(ERROR) << __func__ << ": unable to open dir '" << directoryname
410                << "': " << strerror(errno);
411     goto error2;
412   }
413 
414   if (base::WriteFile(path, checksum.data(), checksum.size()) !=
415       (int)checksum.size()) {
416     LOG(ERROR) << __func__ << ": unable to write file '" << filename.c_str();
417     goto error2;
418   }
419 
420   fp = fopen(temp_filename.c_str(), "rb");
421   if (!fp) {
422     LOG(ERROR) << __func__ << ": unable to write to file '" << temp_filename
423                << "': " << strerror(errno);
424     goto error2;
425   }
426 
427   // Sync written temp file out to disk. fsync() is blocking until data makes it
428   // to disk.
429   if (fsync(fileno(fp)) < 0) {
430     LOG(WARNING) << __func__ << ": unable to fsync file '" << temp_filename
431                  << "': " << strerror(errno);
432   }
433 
434   if (fclose(fp) == EOF) {
435     LOG(ERROR) << __func__ << ": unable to close file '" << temp_filename
436                << "': " << strerror(errno);
437     goto error2;
438   }
439   fp = nullptr;
440 
441   // Change the file's permissions to Read/Write by User and Group
442   if (chmod(temp_filename.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) ==
443       -1) {
444     LOG(ERROR) << __func__ << ": unable to change file permissions '"
445                << filename << "': " << strerror(errno);
446     goto error2;
447   }
448 
449   // Rename written temp file to the actual config file.
450   if (rename(temp_filename.c_str(), filename.c_str()) == -1) {
451     LOG(ERROR) << __func__ << ": unable to commit file '" << filename
452                << "': " << strerror(errno);
453     goto error2;
454   }
455 
456   // This should ensure the directory is updated as well.
457   if (fsync(dir_fd) < 0) {
458     LOG(WARNING) << __func__ << ": unable to fsync dir '" << directoryname
459                  << "': " << strerror(errno);
460   }
461 
462   if (close(dir_fd) < 0) {
463     LOG(ERROR) << __func__ << ": unable to close dir '" << directoryname
464                << "': " << strerror(errno);
465     goto error2;
466   }
467 
468   return true;
469 
470 error2:
471   // This indicates there is a write issue.  Unlink as partial data is not
472   // acceptable.
473   unlink(temp_filename.c_str());
474   if (fp) fclose(fp);
475   if (dir_fd != -1) close(dir_fd);
476   return false;
477 }
478 
trim(char * str)479 static char* trim(char* str) {
480   while (isspace(*str)) ++str;
481 
482   if (!*str) return str;
483 
484   char* end_str = str + strlen(str) - 1;
485   while (end_str > str && isspace(*end_str)) --end_str;
486 
487   end_str[1] = '\0';
488   return str;
489 }
490 
config_parse(FILE * fp,config_t * config)491 static bool config_parse(FILE* fp, config_t* config) {
492   CHECK(fp != nullptr);
493   CHECK(config != nullptr);
494 
495   int line_num = 0;
496   char line[1024];
497   char section[1024];
498   strcpy(section, CONFIG_DEFAULT_SECTION);
499 
500   while (fgets(line, sizeof(line), fp)) {
501     char* line_ptr = trim(line);
502     ++line_num;
503 
504     // Skip blank and comment lines.
505     if (*line_ptr == '\0' || *line_ptr == '#') continue;
506 
507     if (*line_ptr == '[') {
508       size_t len = strlen(line_ptr);
509       if (line_ptr[len - 1] != ']') {
510         VLOG(1) << __func__ << ": unterminated section name on line "
511                 << line_num;
512         return false;
513       }
514       strncpy(section, line_ptr + 1, len - 2);  // NOLINT (len < 1024)
515       section[len - 2] = '\0';
516     } else {
517       char* split = strchr(line_ptr, '=');
518       if (!split) {
519         VLOG(1) << __func__ << ": no key/value separator found on line "
520                 << line_num;
521         return false;
522       }
523 
524       *split = '\0';
525       config_set_string(config, section, trim(line_ptr), trim(split + 1));
526     }
527   }
528   return true;
529 }
530