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