Aktualizr
C++ SOTA Client
utils.cc
1 #include "utilities/utils.h"
2 
3 #include <algorithm>
4 #include <array>
5 #include <cstdio>
6 #include <cstdlib>
7 #include <fstream>
8 #include <iomanip>
9 #include <iostream>
10 #include <random>
11 #include <sstream>
12 #include <stdexcept>
13 #include <string>
14 
15 #include <archive.h>
16 #include <archive_entry.h>
17 #include <arpa/inet.h>
18 #include <fcntl.h>
19 #include <glob.h>
20 #include <ifaddrs.h>
21 #include <netinet/in.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24 
25 #include <boost/algorithm/string.hpp>
26 #include <boost/archive/iterators/base64_from_binary.hpp>
27 #include <boost/archive/iterators/binary_from_base64.hpp>
28 #include <boost/archive/iterators/remove_whitespace.hpp>
29 #include <boost/archive/iterators/transform_width.hpp>
30 #include <boost/filesystem.hpp>
31 #include <boost/uuid/random_generator.hpp>
32 #include <boost/uuid/uuid_io.hpp>
33 
34 #include "aktualizr_version.h"
35 #include "logging/logging.h"
36 
37 static const std::array<const char *, 132> adverbs = {
38  "adorable", "acidic", "ample", "aromatic", "artistic", "attractive", "basic", "beautiful",
39  "best", "blissful", "bubbly", "celebrated", "cheap", "chilly", "cloudy", "colorful",
40  "colossal", "complete", "conventional", "costly", "creamy", "crisp", "dense", "double",
41  "dry", "easy", "every", "exotic", "expert", "fake", "fabulous", "fast",
42  "fine", "firm", "first", "flaky", "flat", "fluffy", "frozen", "generous",
43  "giant", "glass", "glorious", "golden", "good", "grand", "great", "half",
44  "happy", "hard", "healthy", "heavy", "hot", "huge", "humble", "ideal",
45  "icy", "incredible", "interesting", "joyous", "juicy", "jumbo", "large", "late",
46  "lavish", "leafy", "lean", "light", "lovely", "marvelous", "mature", "modern",
47  "modest", "neat", "new", "nice", "nifty", "nutty", "oily", "ornate",
48  "perfect", "plain", "posh", "pretty", "prime", "proper", "pure", "quick",
49  "raw", "real", "rich", "ripe", "safe", "salty", "several", "short",
50  "simple", "slim", "slow", "smooth", "soft", "solid", "speedy", "spotless",
51  "strong", "stylish", "subtle", "super", "sweet", "tasty", "tempting", "tender",
52  "terrific", "thick", "thin", "tidy", "tiny", "twin", "ultimate", "unique",
53  "uniform", "unusual", "valuable", "vast", "warm", "wavy", "wet", "whole",
54  "wide", "wild", "wooden", "young"};
55 
56 static const std::array<const char *, 128> names = {"Allerlei",
57  "Apfelkuchen",
58  "Backerbsen",
59  "Baumkuchen",
60  "Beetenbartsch",
61  "Berliner",
62  "Bethmaennchen",
63  "Biersuppe",
64  "Birnenfladen",
65  "Bohnen",
66  "Bratapfel",
67  "Bratkartoffeln",
68  "Brezel",
69  "Broetchen",
70  "Butterkuchen",
71  "Currywurst",
72  "Dampfnudel",
73  "Dibbelabbes",
74  "Eierkuchen",
75  "Eintopf",
76  "Erbsensuppe",
77  "Flaedlesuppe",
78  "Flammkuchen",
79  "Fliederbeersuppe",
80  "Franzbroetchen",
81  "Funkenkuechlein",
82  "Gedadschde",
83  "Gemueseschnitzel",
84  "Germknoedel",
85  "Gerstensuppe",
86  "Griessbrei",
87  "Gruenkohl",
88  "Gruetze",
89  "Gummibaerchen",
90  "Gurkensalat",
91  "Habermus",
92  "Haddekuche",
93  "Hagebuttenmark",
94  "Handkaese",
95  "Herrencreme",
96  "Hoorische",
97  "Kaesekuchen",
98  "Kaiserschmarrn",
99  "Kartoffelpueree",
100  "Kartoffelpuffer",
101  "Kartoffelsalat",
102  "Kastanien",
103  "Kichererbsen",
104  "Kirschenmichel",
105  "Kirschtorte",
106  "Klaben",
107  "Kloesse",
108  "Kluntjes",
109  "Knaeckebrot",
110  "Kniekuechle",
111  "Knoedel",
112  "Kohlroulade",
113  "Krautfleckerl",
114  "Kuerbiskernbrot",
115  "Kuerbissuppe",
116  "Lebkuchen",
117  "Linsen",
118  "Loeffelerbsen",
119  "Magenbrot",
120  "Marillenknoedel",
121  "Maroni",
122  "Marsch",
123  "Marzipan",
124  "Maultaschen",
125  "Milliramstrudel",
126  "Mischbrot",
127  "Mohnnudeln",
128  "Mohnpielen",
129  "Mohnzelten",
130  "Muesli",
131  "Nussecke",
132  "Nusstorte",
133  "Palatschinke",
134  "Pellkartoffeln",
135  "Pfannkuchen",
136  "Pfefferkuchen",
137  "Pillekuchen",
138  "Pommes",
139  "Poschweck",
140  "Powidltascherl",
141  "Printen",
142  "Prinzregententorte",
143  "Pumpernickel",
144  "Punschkrapfen",
145  "Quarkkeulchen",
146  "Quetschkartoffeln",
147  "Raclette",
148  "Radi",
149  "Reibekuchen",
150  "Reinling",
151  "Riebel",
152  "Roeggelchen",
153  "Roesti",
154  "Sauerkraut",
155  "Schmalzkuchen",
156  "Schmorgurken",
157  "Schnippelbohnen",
158  "Schoeberl",
159  "Schrippe",
160  "Schupfnudel",
161  "Schuxen",
162  "Schwammerlsuppe",
163  "Schweineohren",
164  "Sonnenblumenkernbrot",
165  "Spaetzle",
166  "Spaghettieis",
167  "Spargel",
168  "Spekulatius",
169  "Springerle",
170  "Spritzkuchen",
171  "Stampfkartoffeln",
172  "Sterz",
173  "Stollen",
174  "Streuselkuchen",
175  "Tilsit",
176  "Toastbrot",
177  "Topfenstrudel",
178  "Vollkornbrot",
179  "Wibele",
180  "Wickelkloesse",
181  "Zimtwaffeln",
182  "Zwetschkenroester",
183  "Zwiebelkuchen"};
184 
185 using base64_text = boost::archive::iterators::base64_from_binary<
186  boost::archive::iterators::transform_width<std::string::const_iterator, 6, 8> >;
187 
188 using base64_to_bin = boost::archive::iterators::transform_width<
189  boost::archive::iterators::binary_from_base64<
190  boost::archive::iterators::remove_whitespace<std::string::const_iterator> >,
191  8, 6>;
192 
193 std::string Utils::fromBase64(std::string base64_string) {
194  int64_t paddingChars = std::count(base64_string.begin(), base64_string.end(), '=');
195  std::replace(base64_string.begin(), base64_string.end(), '=', 'A');
196  std::string result(base64_to_bin(base64_string.begin()), base64_to_bin(base64_string.end()));
197  result.erase(result.end() - paddingChars, result.end());
198  return result;
199 }
200 
201 std::string Utils::toBase64(const std::string &tob64) {
202  std::string b64sig(base64_text(tob64.begin()), base64_text(tob64.end()));
203  b64sig.append((3 - tob64.length() % 3) % 3, '=');
204  return b64sig;
205 }
206 
207 // Strip leading and trailing quotes
208 std::string Utils::stripQuotes(const std::string &value) {
209  std::string res = value;
210  res.erase(std::remove(res.begin(), res.end(), '\"'), res.end());
211  return res;
212 }
213 
214 // Add leading and trailing quotes
215 std::string Utils::addQuotes(const std::string &value) { return "\"" + value + "\""; }
216 
217 std::string Utils::extractField(const std::string &in, unsigned int field_id) {
218  std::string out;
219  auto it = in.begin();
220 
221  // skip spaces
222  for (; it != in.end() && (isspace(*it) != 0); it++) {
223  ;
224  }
225  for (unsigned int k = 0; k < field_id; k++) {
226  bool empty = true;
227  for (; it != in.end() && (isspace(*it) == 0); it++) {
228  empty = false;
229  }
230  if (empty) {
231  throw std::runtime_error(std::string("No such field ").append(std::to_string(field_id)));
232  }
233  for (; it != in.end() && (isspace(*it) != 0); it++) {
234  ;
235  }
236  }
237 
238  for (; it != in.end() && (isspace(*it) == 0); it++) {
239  out += *it;
240  }
241  return out;
242 }
243 
244 Json::Value Utils::parseJSON(const std::string &json_str) {
245  std::istringstream strs(json_str);
246  Json::Value json_value;
247  parseFromStream(Json::CharReaderBuilder(), strs, &json_value, nullptr);
248  return json_value;
249 }
250 
251 Json::Value Utils::parseJSONFile(const boost::filesystem::path &filename) {
252  std::ifstream path_stream(filename.c_str());
253  std::string content((std::istreambuf_iterator<char>(path_stream)), std::istreambuf_iterator<char>());
254  return Utils::parseJSON(content);
255 }
256 
257 std::string Utils::genPrettyName() {
258  std::random_device urandom;
259 
260  std::uniform_int_distribution<size_t> adverbs_dist(0, adverbs.size() - 1);
261  std::uniform_int_distribution<size_t> nouns_dist(0, names.size() - 1);
262  std::uniform_int_distribution<size_t> digits(0, 999);
263  std::stringstream pretty_name;
264  pretty_name << adverbs.at(adverbs_dist(urandom));
265  pretty_name << "-";
266  pretty_name << names.at(nouns_dist(urandom));
267  pretty_name << "-";
268  pretty_name << digits(urandom);
269  std::string res = pretty_name.str();
270  std::transform(res.begin(), res.end(), res.begin(), ::tolower);
271  return res;
272 }
273 
274 std::string Utils::readFile(const boost::filesystem::path &filename, const bool trim) {
275  boost::filesystem::path tmpFilename = filename;
276  tmpFilename += ".new";
277  // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
278  if (boost::filesystem::exists(tmpFilename)) {
279  LOG_WARNING << tmpFilename << " was found on FS, removing";
280  boost::filesystem::remove(tmpFilename);
281  }
282  std::ifstream path_stream(filename.c_str());
283  std::string content((std::istreambuf_iterator<char>(path_stream)), std::istreambuf_iterator<char>());
284 
285  if (trim) {
286  boost::trim_if(content, boost::is_any_of(" \t\r\n"));
287  }
288  return content;
289 }
290 
291 static constexpr size_t BSIZE = 20 * 512;
292 
294  public:
295  explicit archive_state(std::istream &is_in) : is(is_in) {}
296  std::istream &is;
297  std::array<char, BSIZE> buf{};
298 };
299 
300 static ssize_t read_cb(struct archive *a, void *client_data, const void **buffer) {
301  auto *s = reinterpret_cast<archive_state *>(client_data);
302  if (s->is.fail()) {
303  archive_set_error(a, -1, "unable to read from stream");
304  return 0;
305  }
306  if (s->is.eof()) {
307  return 0;
308  }
309  s->is.read(s->buf.data(), BSIZE);
310  if (!s->is.eof() && s->is.fail()) {
311  archive_set_error(a, -1, "unable to read from stream");
312  return 0;
313  }
314  *buffer = s->buf.data();
315 
316  return s->is.gcount();
317 }
318 
319 void Utils::writeFile(const boost::filesystem::path &filename, const std::string &content, bool create_directories) {
320  if (create_directories) {
321  boost::filesystem::create_directories(filename.parent_path());
322  }
323  Utils::writeFile(filename, content.c_str(), content.size());
324 }
325 
326 void Utils::writeFile(const boost::filesystem::path &filename, const char *content, size_t size) {
327  // also replace the target file atomically by creating filename.new and
328  // renaming it to the target file name
329  boost::filesystem::path tmpFilename = filename;
330  tmpFilename += ".new";
331 
332  std::ofstream file(tmpFilename.c_str());
333  if (!file.good()) {
334  throw std::runtime_error(std::string("Error opening file ") + tmpFilename.string());
335  }
336  file.write(content, static_cast<std::streamsize>(size));
337  file.close();
338 
339  boost::filesystem::rename(tmpFilename, filename);
340 }
341 
342 void Utils::writeFile(const boost::filesystem::path &filename, const Json::Value &content, bool create_directories) {
343  Utils::writeFile(filename, jsonToStr(content), create_directories);
344 }
345 
346 std::string Utils::jsonToStr(const Json::Value &json) {
347  std::stringstream ss;
348  ss << json;
349  return ss.str();
350 }
351 
352 std::string Utils::jsonToCanonicalStr(const Json::Value &json) {
353  static Json::StreamWriterBuilder wbuilder = []() {
354  Json::StreamWriterBuilder w;
355  wbuilder["indentation"] = "";
356  return w;
357  }();
358  return Json::writeString(wbuilder, json);
359 }
360 
361 Json::Value Utils::getHardwareInfo() {
362  std::string result;
363  const int exit_code = shell("lshw -json", &result);
364 
365  if (exit_code != 0) {
366  LOG_WARNING << "Could not execute lshw (is it installed?).";
367  return Json::Value();
368  }
369  const Json::Value parsed = Utils::parseJSON(result);
370  return (parsed.isArray()) ? parsed[0] : parsed;
371 }
372 
373 Json::Value Utils::getNetworkInfo() {
374  // get interface with default route
375  std::ifstream path_stream("/proc/net/route");
376  std::string route_content((std::istreambuf_iterator<char>(path_stream)), std::istreambuf_iterator<char>());
377 
378  struct Itf {
379  std::string name = std::string();
380  std::string ip = std::string();
381  std::string mac = std::string();
382  } itf;
383  std::istringstream route_stream(route_content);
384  std::array<char, 200> line{};
385 
386  // skip first line
387  route_stream.getline(&line[0], line.size());
388  while (route_stream.getline(&line[0], line.size())) {
389  std::string itfn = Utils::extractField(&line[0], 0);
390  std::string droute = Utils::extractField(&line[0], 1);
391  if (droute == "00000000") {
392  itf.name = itfn;
393  // take the first routing to 0
394  break;
395  }
396  }
397 
398  if (!itf.name.empty()) {
399  {
400  // get ip address
401  StructGuard<struct ifaddrs> ifaddrs(nullptr, freeifaddrs);
402  {
403  struct ifaddrs *ifa;
404  if (getifaddrs(&ifa) < 0) {
405  LOG_ERROR << "getifaddrs: " << std::strerror(errno);
406  } else {
407  ifaddrs.reset(ifa);
408  }
409  }
410  if (ifaddrs != nullptr) {
411  for (struct ifaddrs *ifa = ifaddrs.get(); ifa != nullptr; ifa = ifa->ifa_next) {
412  if (itf.name == ifa->ifa_name) {
413  if (ifa->ifa_addr == nullptr) {
414  continue;
415  }
416  if (ifa->ifa_addr->sa_family != AF_INET) {
417  continue;
418  }
419  const struct sockaddr_storage *sa = reinterpret_cast<struct sockaddr_storage *>(ifa->ifa_addr);
420 
421  itf.ip = Utils::ipDisplayName(*sa);
422  }
423  }
424  }
425  }
426  {
427  // get mac address
428  std::ifstream mac_stream("/sys/class/net/" + itf.name + "/address");
429  std::string m((std::istreambuf_iterator<char>(mac_stream)), std::istreambuf_iterator<char>());
430  itf.mac = std::move(m);
431  boost::trim_right(itf.mac);
432  }
433  }
434 
435  Json::Value network_info;
436  network_info["local_ipv4"] = itf.ip;
437  network_info["mac"] = itf.mac;
438  network_info["hostname"] = Utils::getHostname();
439 
440  return network_info;
441 }
442 
443 std::string Utils::getHostname() {
444  std::array<char, 200> hostname{};
445  if (gethostname(hostname.data(), hostname.size()) < 0) {
446  return "";
447  }
448  return std::string(hostname.data());
449 }
450 
451 std::string Utils::randomUuid() {
452  std::random_device urandom;
453  boost::uuids::basic_random_generator<std::random_device> uuid_gen(urandom);
454  return boost::uuids::to_string(uuid_gen());
455 }
456 
457 // Note that this doesn't work with broken symlinks.
458 void Utils::copyDir(const boost::filesystem::path &from, const boost::filesystem::path &to) {
459  boost::filesystem::remove_all(to);
460 
461  boost::filesystem::create_directories(to);
462  boost::filesystem::directory_iterator it(from);
463 
464  for (; it != boost::filesystem::directory_iterator(); it++) {
465  if (boost::filesystem::is_directory(it->path())) {
466  copyDir(it->path(), to / it->path().filename());
467  } else {
468  boost::filesystem::copy_file(it->path(), to / it->path().filename());
469  }
470  }
471 }
472 
473 std::string Utils::readFileFromArchive(std::istream &as, const std::string &filename, const bool trim) {
474  StructGuardInt<struct archive> a(archive_read_new(), archive_read_free);
475  if (a == nullptr) {
476  LOG_ERROR << "archive error: could not initialize archive object";
477  throw std::runtime_error("archive error");
478  }
479  archive_read_support_filter_all(a.get());
480  archive_read_support_format_all(a.get());
481  auto state = std_::make_unique<archive_state>(std::ref(as));
482  int r = archive_read_open(a.get(), reinterpret_cast<void *>(state.get()), nullptr, read_cb, nullptr);
483  if (r != ARCHIVE_OK) {
484  LOG_ERROR << "archive error: " << archive_error_string(a.get());
485  throw std::runtime_error("archive error");
486  }
487 
488  bool found = false;
489  std::stringstream out_stream;
490  struct archive_entry *entry;
491  while (archive_read_next_header(a.get(), &entry) == ARCHIVE_OK) {
492  if (filename != archive_entry_pathname(entry)) {
493  archive_read_data_skip(a.get());
494  continue;
495  }
496 
497  const char *buff;
498  size_t size;
499  int64_t offset;
500 
501  for (;;) {
502  r = archive_read_data_block(a.get(), reinterpret_cast<const void **>(&buff), &size, &offset);
503  if (r == ARCHIVE_EOF) {
504  found = true;
505  break;
506  } else if (r != ARCHIVE_OK) {
507  LOG_ERROR << "archive error: " << archive_error_string(a.get());
508  break;
509  }
510  if (size > 0 && buff != nullptr) {
511  out_stream.write(buff, static_cast<ssize_t>(size));
512  }
513  }
514  }
515 
516  r = archive_read_close(a.get());
517  if (r != ARCHIVE_OK) {
518  LOG_ERROR << "archive error: " << archive_error_string(a.get());
519  }
520 
521  if (!found) {
522  throw std::runtime_error("could not extract " + filename + " from archive");
523  }
524 
525  std::string result = out_stream.str();
526  if (trim) {
527  boost::trim_if(result, boost::is_any_of(" \t\r\n"));
528  }
529  return result;
530 }
531 
532 static ssize_t write_cb(struct archive *a, void *client_data, const void *buffer, size_t length) {
533  auto *s = reinterpret_cast<std::ostream *>(client_data);
534  s->write(reinterpret_cast<const char *>(buffer), static_cast<ssize_t>(length));
535  if (s->fail()) {
536  archive_set_error(a, -1, "unable to write in stream");
537  return -1;
538  }
539 
540  return static_cast<ssize_t>(length);
541 }
542 
543 void Utils::writeArchive(const std::map<std::string, std::string> &entries, std::ostream &as) {
544  StructGuardInt<struct archive> a(archive_write_new(), archive_write_free);
545  if (a == nullptr) {
546  LOG_ERROR << "archive error: could not initialize archive object";
547  throw std::runtime_error("archive error");
548  }
549  archive_write_set_format_pax(a.get());
550  archive_write_add_filter_gzip(a.get());
551 
552  int r = archive_write_open(a.get(), reinterpret_cast<void *>(&as), nullptr, write_cb, nullptr);
553  if (r != ARCHIVE_OK) {
554  LOG_ERROR << "archive error: " << archive_error_string(a.get());
555  throw std::runtime_error("archive error");
556  }
557 
558  StructGuard<struct archive_entry> entry(archive_entry_new(), archive_entry_free);
559  for (const auto &el : entries) {
560  archive_entry_clear(entry.get());
561  archive_entry_set_filetype(entry.get(), AE_IFREG);
562  archive_entry_set_perm(entry.get(), S_IRWXU | S_IRWXG | S_IRWXO);
563  archive_entry_set_size(entry.get(), static_cast<ssize_t>(el.second.size()));
564  archive_entry_set_pathname(entry.get(), el.first.c_str());
565  if (archive_write_header(a.get(), entry.get()) != 0) {
566  LOG_ERROR << "archive error: " << archive_error_string(a.get());
567  throw std::runtime_error("archive error");
568  }
569  if (archive_write_data(a.get(), el.second.c_str(), el.second.size()) < 0) {
570  LOG_ERROR << "archive error: " << archive_error_string(a.get());
571  throw std::runtime_error("archive error");
572  }
573  }
574  r = archive_write_close(a.get());
575  if (r != ARCHIVE_OK) {
576  LOG_ERROR << "archive error: " << archive_error_string(a.get());
577  }
578 }
579 
580 /* Removing a file from an archive isn't possible in the obvious sense. The only
581  * way to do so in practice is to create a new archive, copy everything you
582  * _don't_ want to remove, and then replace the old archive with the new one.
583  */
584 void Utils::removeFileFromArchive(const boost::filesystem::path &archive_path, const std::string &filename) {
585  std::ifstream as_in(archive_path.c_str(), std::ios::in | std::ios::binary);
586  if (as_in.fail()) {
587  LOG_ERROR << "Unable to open provided provisioning archive " << archive_path << ": " << std::strerror(errno);
588  throw std::runtime_error("Unable to parse provisioning credentials");
589  }
590  const boost::filesystem::path outfile = archive_path.string() + "-" + boost::filesystem::unique_path().string();
591  std::ofstream as_out(outfile.c_str(), std::ios::out | std::ios::binary);
592  if (as_out.fail()) {
593  LOG_ERROR << "Unable to create file " << outfile << ": " << std::strerror(errno);
594  throw std::runtime_error("Unable to parse provisioning credentials");
595  }
596 
597  StructGuardInt<struct archive> a_in(archive_read_new(), archive_read_free);
598  if (a_in == nullptr) {
599  LOG_ERROR << "archive error: could not initialize archive object";
600  throw std::runtime_error("archive error");
601  }
602  archive_read_support_filter_all(a_in.get());
603  archive_read_support_format_all(a_in.get());
604  auto state = std_::make_unique<archive_state>(std::ref(as_in));
605  int r = archive_read_open(a_in.get(), reinterpret_cast<void *>(state.get()), nullptr, read_cb, nullptr);
606  if (r != ARCHIVE_OK) {
607  LOG_ERROR << "archive error: " << archive_error_string(a_in.get());
608  throw std::runtime_error("archive error");
609  }
610 
611  StructGuardInt<struct archive> a_out(archive_write_new(), archive_write_free);
612  if (a_out == nullptr) {
613  LOG_ERROR << "archive error: could not initialize archive object";
614  throw std::runtime_error("archive error");
615  }
616  archive_write_set_format_zip(a_out.get());
617  r = archive_write_open(a_out.get(), reinterpret_cast<void *>(&as_out), nullptr, write_cb, nullptr);
618  if (r != ARCHIVE_OK) {
619  LOG_ERROR << "archive error: " << archive_error_string(a_out.get());
620  throw std::runtime_error("archive error");
621  }
622 
623  bool found = false;
624  struct archive_entry *entry_in;
625  while (archive_read_next_header(a_in.get(), &entry_in) == ARCHIVE_OK) {
626  const char *entry_name = archive_entry_pathname(entry_in);
627  if (filename == entry_name) {
628  archive_read_data_skip(a_in.get());
629  found = true;
630  continue;
631  }
632 
633  StructGuard<struct archive_entry> entry_out(archive_entry_new(), archive_entry_free);
634  const struct stat *entry_stat = archive_entry_stat(entry_in);
635  archive_entry_copy_stat(entry_out.get(), entry_stat);
636  archive_entry_set_pathname(entry_out.get(), entry_name);
637  if (archive_write_header(a_out.get(), entry_out.get()) != 0) {
638  LOG_ERROR << "archive error: " << archive_error_string(a_out.get());
639  throw std::runtime_error("archive error");
640  }
641 
642  const char *buff;
643  size_t size;
644  int64_t offset;
645 
646  for (;;) {
647  r = archive_read_data_block(a_in.get(), reinterpret_cast<const void **>(&buff), &size, &offset);
648  if (r == ARCHIVE_EOF) {
649  break;
650  } else if (r != ARCHIVE_OK) {
651  LOG_ERROR << "archive error: " << archive_error_string(a_in.get());
652  break;
653  }
654  if (size > 0 && buff != nullptr) {
655  if (archive_write_data(a_out.get(), buff, size) < 0) {
656  LOG_ERROR << "archive error: " << archive_error_string(a_out.get());
657  throw std::runtime_error("archive error");
658  }
659  }
660  }
661  }
662 
663  r = archive_read_close(a_in.get());
664  if (r != ARCHIVE_OK) {
665  LOG_ERROR << "archive error: " << archive_error_string(a_in.get());
666  }
667 
668  r = archive_write_close(a_out.get());
669  if (r != ARCHIVE_OK) {
670  LOG_ERROR << "archive error: " << archive_error_string(a_out.get());
671  }
672 
673  if (found) {
674  boost::filesystem::rename(outfile, archive_path);
675  } else {
676  boost::filesystem::remove(outfile);
677  throw std::runtime_error("Requested file not found in archive!");
678  }
679 }
680 
681 sockaddr_storage Utils::ipGetSockaddr(int fd) {
682  sockaddr_storage ss{};
683  socklen_t len = sizeof(ss);
684  if (getsockname(fd, reinterpret_cast<sockaddr *>(&ss), &len) < 0) {
685  throw std::runtime_error(std::string("Could not get sockaddr: ") + std::strerror(errno));
686  }
687 
688  return ss;
689 }
690 
691 std::string Utils::ipDisplayName(const sockaddr_storage &saddr) {
692  std::array<char, INET6_ADDRSTRLEN> ipstr{};
693 
694  switch (saddr.ss_family) {
695  case AF_INET: {
696  const auto *sa = reinterpret_cast<const sockaddr_in *>(&saddr);
697  inet_ntop(AF_INET, &sa->sin_addr, ipstr.data(), ipstr.size());
698  return std::string(ipstr.data());
699  }
700  case AF_INET6: {
701  const auto *sa = reinterpret_cast<const sockaddr_in6 *>(&saddr);
702  inet_ntop(AF_INET6, &sa->sin6_addr, ipstr.data(), ipstr.size());
703  return std::string(ipstr.data());
704  }
705  default:
706  return "unknown";
707  }
708 }
709 
710 int Utils::ipPort(const sockaddr_storage &saddr) {
711  in_port_t p;
712  if (saddr.ss_family == AF_INET) {
713  const auto *sa = reinterpret_cast<const sockaddr_in *>(&saddr);
714  p = sa->sin_port;
715  } else if (saddr.ss_family == AF_INET6) {
716  const auto *sa = reinterpret_cast<const sockaddr_in6 *>(&saddr);
717  p = sa->sin6_port;
718  } else {
719  return -1;
720  }
721 
722  return ntohs(p); // NOLINT(readability-isolate-declaration)
723 }
724 
725 int Utils::shell(const std::string &command, std::string *output, bool include_stderr) {
726  std::array<char, 128> buffer{};
727  std::string full_command(command);
728  if (include_stderr) {
729  full_command += " 2>&1";
730  }
731  FILE *pipe = popen(full_command.c_str(), "r");
732  if (pipe == nullptr) {
733  *output = "popen() failed!";
734  return -1;
735  }
736  while (feof(pipe) == 0) {
737  if (fgets(buffer.data(), buffer.size(), pipe) != nullptr) {
738  *output += buffer.data();
739  }
740  }
741  int exitcode = pclose(pipe);
742  return WEXITSTATUS(exitcode);
743 }
744 
745 boost::filesystem::path Utils::absolutePath(const boost::filesystem::path &root, const boost::filesystem::path &file) {
746  if (file.is_absolute() || root.empty()) {
747  return file;
748  }
749  return (root / file);
750 }
751 
752 // passing ext argument keep the leading point e.g. '.ext'
753 // results are returned sorted in alpanumeric order
754 std::vector<boost::filesystem::path> Utils::getDirEntriesByExt(const boost::filesystem::path &dir_path,
755  const std::string &ext) {
756  std::vector<boost::filesystem::path> entries;
757  boost::filesystem::directory_iterator entryItEnd;
758  boost::filesystem::directory_iterator entryIt(dir_path);
759  for (; entryIt != entryItEnd; ++entryIt) {
760  const auto &entry_path = entryIt->path();
761  if (!boost::filesystem::is_directory(*entryIt) && entry_path.extension().string() == ext) {
762  entries.push_back(entry_path);
763  }
764  }
765  std::sort(entries.begin(), entries.end());
766  return entries;
767 }
768 
769 void Utils::createDirectories(const boost::filesystem::path &path, mode_t mode) {
770  boost::filesystem::path parent = path.parent_path();
771  if (!parent.empty() && !boost::filesystem::exists(parent)) {
772  Utils::createDirectories(parent, mode);
773  }
774  if (mkdir(path.c_str(), mode) == -1) {
775  throw std::runtime_error(std::string("could not create directory: ").append(path.native()));
776  }
777  std::cout << "created: " << path.native() << "\n";
778 }
779 
780 bool Utils::createSecureDirectory(const boost::filesystem::path &path) {
781  if (mkdir(path.c_str(), S_IRWXU) == 0) {
782  // directory created successfully
783  return true;
784  }
785 
786  // mkdir failed, see if the directory already exists with correct permissions
787  struct stat st {};
788  int ret = stat(path.c_str(), &st);
789  // checks: - stat succeeded
790  // - is a directory
791  // - no read and write permissions for group and others
792  // - owner is current user
793  return (ret >= 0 && ((st.st_mode & S_IFDIR) == S_IFDIR) && (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) == S_IRWXU &&
794  (st.st_uid == getuid()));
795 }
796 
797 std::string Utils::urlEncode(const std::string &input) {
798  std::string res;
799 
800  for (char c : input) {
801  if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '.' ||
802  c == '_' || c == '~' || c == '/') {
803  res.push_back(c);
804  } else {
805  res.push_back('%');
806  auto nib = static_cast<char>(((c >> 4) & 0x0F));
807  res.push_back(static_cast<char>((nib < 10) ? nib + '0' : nib - 10 + 'A'));
808  nib = static_cast<char>(c & 0x0F);
809  res.push_back(static_cast<char>((nib < 10) ? nib + '0' : nib - 10 + 'A'));
810  }
811  }
812  return res;
813 }
814 
815 CURL *Utils::curlDupHandleWrapper(CURL *const curl_in, const bool using_pkcs11) {
816  CURL *curl = curl_easy_duphandle(curl_in);
817 
818  // This is a workaround for a bug in curl. It has been fixed in
819  // 75a845d8cfa71688d59d43788c35829b25b6d6af (curl 7.61.1), but that is not
820  // the default in most distributions yet, so we will continue to use the
821  // workaround.
822  if (using_pkcs11) {
823  curlEasySetoptWrapper(curl, CURLOPT_SSLENGINE, "pkcs11");
824  }
825  return curl;
826 }
827 
829  public:
830  SafeTempRoot(const SafeTempRoot &) = delete;
831  SafeTempRoot(const SafeTempRoot &&) = delete;
832  SafeTempRoot operator=(const SafeTempRoot &) = delete;
833  SafeTempRoot operator=(const SafeTempRoot &&) = delete;
834  // provide this as a static method so that we can use C++ static destructor
835  // to remove the temp root
836  static boost::filesystem::path &Get() {
837  static SafeTempRoot r;
838 
839  return r.path;
840  }
841 
842  private:
843  SafeTempRoot() {
844  boost::filesystem::path prefix = Utils::getStorageRootPath();
845  if (prefix.empty()) {
846  prefix = boost::filesystem::temp_directory_path();
847  }
848  boost::filesystem::path p = prefix / boost::filesystem::unique_path("aktualizr-%%%%-%%%%-%%%%-%%%%");
849  if (mkdir(p.c_str(), S_IRWXU) == -1) {
850  throw std::runtime_error(std::string("Could not create temporary directory root: ").append(p.native()));
851  }
852 
853  path = boost::filesystem::path(p);
854  }
855  ~SafeTempRoot() {
856  try {
857  boost::filesystem::remove_all(path);
858  } catch (...) {
859  // ignore this, not critical
860  }
861  }
862 
863  boost::filesystem::path path;
864 };
865 
866 std::string Utils::storage_root_path_;
867 
868 void Utils::setStorageRootPath(const std::string &storage_root_path) { storage_root_path_ = storage_root_path; }
869 
870 boost::filesystem::path Utils::getStorageRootPath() { return storage_root_path_; }
871 
872 void Utils::setUserAgent(std::string user_agent) { user_agent_ = std::move(user_agent); }
873 
874 const char *Utils::getUserAgent() {
875  if (user_agent_.empty()) {
876  user_agent_ = (std::string("Aktualizr/") + aktualizr_version());
877  }
878  return user_agent_.c_str();
879 }
880 
881 std::string Utils::user_agent_;
882 
883 void Utils::setCaPath(boost::filesystem::path path) { ca_path_ = std::move(path); }
884 
885 const char *Utils::getCaPath() { return ca_path_.c_str(); }
886 
887 boost::filesystem::path Utils::ca_path_{"/etc/ssl/certs"};
888 
889 TemporaryFile::TemporaryFile(const std::string &hint)
890  : tmp_name_(SafeTempRoot::Get() / boost::filesystem::unique_path(std::string("%%%%-%%%%-").append(hint))) {}
891 
892 TemporaryFile::~TemporaryFile() { boost::filesystem::remove(tmp_name_); }
893 
894 void TemporaryFile::PutContents(const std::string &contents) const {
895  mode_t mode = S_IRUSR | S_IWUSR;
896  int fd = open(Path().c_str(), O_WRONLY | O_CREAT | O_TRUNC, mode);
897  if (fd < 0) {
898  throw std::runtime_error(std::string("Could not write content to file: ") + Path().string() + ": " +
899  std::strerror(errno));
900  }
901  ssize_t written = write(fd, contents.c_str(), contents.size());
902  close(fd);
903  if (written < 0 || static_cast<size_t>(written) != contents.size()) {
904  throw std::runtime_error(std::string("Could not write content to file: ") + Path().string());
905  }
906 }
907 
908 boost::filesystem::path TemporaryFile::Path() const { return tmp_name_; }
909 
910 std::string TemporaryFile::PathString() const { return Path().string(); }
911 
912 TemporaryDirectory::TemporaryDirectory(const std::string &hint)
913  : tmp_name_(SafeTempRoot::Get() / boost::filesystem::unique_path(std::string("%%%%-%%%%-").append(hint))) {
914  Utils::createDirectories(tmp_name_, S_IRWXU);
915 }
916 
917 TemporaryDirectory::~TemporaryDirectory() { boost::filesystem::remove_all(tmp_name_); }
918 
919 boost::filesystem::path TemporaryDirectory::Path() const { return tmp_name_; }
920 
921 boost::filesystem::path TemporaryDirectory::operator/(const boost::filesystem::path &subdir) const {
922  return (tmp_name_ / subdir);
923 }
924 
925 std::string TemporaryDirectory::PathString() const { return Path().string(); }
926 
927 Socket::Socket() {
928  socket_fd_ = socket(AF_INET, SOCK_STREAM, 0);
929  if (-1 == socket_fd_) {
930  throw std::system_error(errno, std::system_category(), "socket");
931  }
932 }
933 
934 Socket::~Socket() { ::close(socket_fd_); }
935 
936 std::string Socket::toString() const {
937  auto saddr = Utils::ipGetSockaddr(socket_fd_);
938  return Utils::ipDisplayName(saddr) + ":" + std::to_string(Utils::ipPort(saddr));
939 }
940 
941 void Socket::bind(in_port_t port, bool reuse) const {
942  sockaddr_in sa{};
943  memset(&sa, 0, sizeof(sa));
944  sa.sin_family = AF_INET;
945  sa.sin_port = htons(port); // NOLINT(readability-isolate-declaration)
946  sa.sin_addr.s_addr = htonl(INADDR_ANY); // NOLINT(readability-isolate-declaration)
947 
948  int reuseaddr = reuse ? 1 : 0;
949  if (-1 == setsockopt(socket_fd_, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr))) {
950  throw std::system_error(errno, std::system_category(), "socket");
951  }
952 
953  if (-1 == ::bind(socket_fd_, reinterpret_cast<const sockaddr *>(&sa), sizeof(sa))) {
954  throw std::system_error(errno, std::system_category(), "socket");
955  }
956 }
957 
958 ListenSocket::ListenSocket(in_port_t port) : _port(port) {
959  bind(port);
960  if (_port == 0) {
961  // ephemeral port was bound, find out its real port number
962  auto ephemeral_port = Utils::ipPort(Utils::ipGetSockaddr(socket_fd_));
963  if (-1 != ephemeral_port) {
964  _port = static_cast<in_port_t>(ephemeral_port);
965  }
966  }
967 }
968 
969 ConnectionSocket::ConnectionSocket(const std::string &ip, in_port_t port, in_port_t bind_port)
970  : remote_sock_address_{} {
971  memset(&remote_sock_address_, 0, sizeof(remote_sock_address_));
972  remote_sock_address_.sin_family = AF_INET;
973  if (-1 == inet_pton(AF_INET, ip.c_str(), &(remote_sock_address_.sin_addr))) {
974  throw std::system_error(errno, std::system_category(), "socket");
975  }
976  remote_sock_address_.sin_port = htons(port); // NOLINT(readability-isolate-declaration)
977 
978  if (bind_port > 0) {
979  bind(bind_port);
980  }
981 }
982 
983 ConnectionSocket::~ConnectionSocket() { ::shutdown(socket_fd_, SHUT_RDWR); }
984 
985 int ConnectionSocket::connect() {
986  return ::connect(socket_fd_, reinterpret_cast<const struct sockaddr *>(&remote_sock_address_),
987  sizeof(remote_sock_address_));
988 }
989 
990 CurlEasyWrapper::CurlEasyWrapper() {
991  handle = curl_easy_init();
992  if (handle == nullptr) {
993  throw std::runtime_error("Could not initialize curl handle");
994  }
995  curlEasySetoptWrapper(handle, CURLOPT_USERAGENT, Utils::getUserAgent());
996 }
997 
998 CurlEasyWrapper::~CurlEasyWrapper() {
999  if (handle != nullptr) {
1000  curl_easy_cleanup(handle);
1001  }
1002 }
archive_state
Definition: utils.cc:293
SafeTempRoot
Definition: utils.cc:828
result
Results of libaktualizr API calls.
Definition: results.h:12