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