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 <array>
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 typedef boost::archive::iterators::base64_from_binary<
186  boost::archive::iterators::transform_width<std::string::const_iterator, 6, 8> >
187  base64_text;
188 
189 typedef boost::archive::iterators::transform_width<
190  boost::archive::iterators::binary_from_base64<
191  boost::archive::iterators::remove_whitespace<std::string::const_iterator> >,
192  8, 6>
193  base64_to_bin;
194 
195 std::string Utils::fromBase64(std::string base64_string) {
196  int64_t paddingChars = std::count(base64_string.begin(), base64_string.end(), '=');
197  std::replace(base64_string.begin(), base64_string.end(), '=', 'A');
198  std::string result(base64_to_bin(base64_string.begin()), base64_to_bin(base64_string.end()));
199  result.erase(result.end() - paddingChars, result.end());
200  return result;
201 }
202 
203 std::string Utils::toBase64(const std::string &tob64) {
204  std::string b64sig(base64_text(tob64.begin()), base64_text(tob64.end()));
205  b64sig.append((3 - tob64.length() % 3) % 3, '=');
206  return b64sig;
207 }
208 
209 // Strip leading and trailing quotes
210 std::string Utils::stripQuotes(const std::string &value) {
211  std::string res = value;
212  res.erase(std::remove(res.begin(), res.end(), '\"'), res.end());
213  return res;
214 }
215 
216 // Add leading and trailing quotes
217 std::string Utils::addQuotes(const std::string &value) { return "\"" + value + "\""; }
218 
219 std::string Utils::extractField(const std::string &in, unsigned int field_id) {
220  std::string out;
221  auto it = in.begin();
222 
223  // skip spaces
224  for (; it != in.end() && (isspace(*it) != 0); it++) {
225  ;
226  }
227  for (unsigned int k = 0; k < field_id; k++) {
228  bool empty = true;
229  for (; it != in.end() && (isspace(*it) == 0); it++) {
230  empty = false;
231  }
232  if (empty) {
233  throw std::runtime_error(std::string("No such field ").append(std::to_string(field_id)));
234  }
235  for (; it != in.end() && (isspace(*it) != 0); it++) {
236  ;
237  }
238  }
239 
240  for (; it != in.end() && (isspace(*it) == 0); it++) {
241  out += *it;
242  }
243  return out;
244 }
245 
246 Json::Value Utils::parseJSON(const std::string &json_str) {
247  std::istringstream strs(json_str);
248  Json::Value json_value;
249  parseFromStream(Json::CharReaderBuilder(), strs, &json_value, nullptr);
250  return json_value;
251 }
252 
253 Json::Value Utils::parseJSONFile(const boost::filesystem::path &filename) {
254  std::ifstream path_stream(filename.c_str());
255  std::string content((std::istreambuf_iterator<char>(path_stream)), std::istreambuf_iterator<char>());
256  return Utils::parseJSON(content);
257 }
258 
259 std::string Utils::genPrettyName() {
260  std::random_device urandom;
261 
262  std::uniform_int_distribution<size_t> adverbs_dist(0, adverbs.size() - 1);
263  std::uniform_int_distribution<size_t> nouns_dist(0, names.size() - 1);
264  std::uniform_int_distribution<size_t> digits(0, 999);
265  std::stringstream pretty_name;
266  pretty_name << adverbs[adverbs_dist(urandom)];
267  pretty_name << "-";
268  pretty_name << names[nouns_dist(urandom)];
269  pretty_name << "-";
270  pretty_name << digits(urandom);
271  std::string res = pretty_name.str();
272  std::transform(res.begin(), res.end(), res.begin(), ::tolower);
273  return res;
274 }
275 
276 std::string Utils::readFile(const boost::filesystem::path &filename, const bool trim) {
277  boost::filesystem::path tmpFilename = filename;
278  tmpFilename += ".new";
279  // that's kind of dangerous to include a specific use-case business logic
280  // into a generic function used by many use cases
281  // TODO: consider refactoring it, e.g. a generic readFile + a specific one with that includes
282  // the ".new" file handling
283  if (boost::filesystem::exists(tmpFilename)) {
284  LOG_WARNING << tmpFilename << " was found on FS, removing";
285  boost::filesystem::remove(tmpFilename);
286  }
287  std::ifstream path_stream(filename.c_str());
288  std::string content((std::istreambuf_iterator<char>(path_stream)), std::istreambuf_iterator<char>());
289 
290  if (trim) {
291  boost::trim_if(content, boost::is_any_of(" \t\r\n"));
292  }
293  return content;
294 }
295 
296 static constexpr size_t BSIZE = 20 * 512;
297 
299  archive_state(std::istream &is_in) : is(is_in) {}
300  std::istream &is;
301  std::array<char, BSIZE> buf{};
302 };
303 
304 static ssize_t read_cb(struct archive *a, void *client_data, const void **buffer) {
305  auto *s = reinterpret_cast<archive_state *>(client_data);
306  if (s->is.fail()) {
307  archive_set_error(a, -1, "unable to read from stream");
308  return 0;
309  }
310  if (s->is.eof()) {
311  return 0;
312  }
313  s->is.read(s->buf.data(), BSIZE);
314  if (!s->is.eof() && s->is.fail()) {
315  archive_set_error(a, -1, "unable to read from stream");
316  return 0;
317  }
318  *buffer = s->buf.data();
319 
320  return s->is.gcount();
321 }
322 
323 void Utils::writeFile(const boost::filesystem::path &filename, const std::string &content, bool create_directories) {
324  if (create_directories) {
325  boost::filesystem::create_directories(filename.parent_path());
326  }
327  Utils::writeFile(filename, content.c_str(), content.size());
328 }
329 
330 void Utils::writeFile(const boost::filesystem::path &filename, const char *content, size_t size) {
331  // also replace the target file atomically by creating filename.new and
332  // renaming it to the target file name
333  boost::filesystem::path tmpFilename = filename;
334  tmpFilename += ".new";
335 
336  std::ofstream file(tmpFilename.c_str());
337  if (!file.good()) {
338  throw std::runtime_error(std::string("Error opening file ") + tmpFilename.string());
339  }
340  file.write(content, static_cast<std::streamsize>(size));
341  file.close();
342 
343  boost::filesystem::rename(tmpFilename, filename);
344 }
345 
346 void Utils::writeFile(const boost::filesystem::path &filename, const Json::Value &content, bool create_directories) {
347  Utils::writeFile(filename, jsonToStr(content), create_directories);
348 }
349 
350 std::string Utils::jsonToStr(const Json::Value &json) {
351  std::stringstream ss;
352  ss << json;
353  return ss.str();
354 }
355 
356 std::string Utils::jsonToCanonicalStr(const Json::Value &json) {
357  static Json::StreamWriterBuilder wbuilder = []() {
358  Json::StreamWriterBuilder w;
359  wbuilder["indentation"] = "";
360  return w;
361  }();
362  return Json::writeString(wbuilder, json);
363 }
364 
365 Json::Value Utils::getHardwareInfo() {
366  std::string result;
367  const int exit_code = shell("lshw -json", &result);
368 
369  if (exit_code != 0) {
370  LOG_WARNING << "Could not execute lshw (is it installed?).";
371  return Json::Value();
372  }
373  const Json::Value parsed = Utils::parseJSON(result);
374  return (parsed.isArray()) ? parsed[0] : parsed;
375 }
376 
377 Json::Value Utils::getNetworkInfo() {
378  // get interface with default route
379  std::ifstream path_stream("/proc/net/route");
380  std::string route_content((std::istreambuf_iterator<char>(path_stream)), std::istreambuf_iterator<char>());
381 
382  struct Itf {
383  std::string name = std::string();
384  std::string ip = std::string();
385  std::string mac = std::string();
386  } itf;
387  std::istringstream route_stream(route_content);
388  std::array<char, 200> line{};
389 
390  // skip first line
391  route_stream.getline(&line[0], line.size());
392  while (route_stream.getline(&line[0], line.size())) {
393  std::string itfn = Utils::extractField(&line[0], 0);
394  std::string droute = Utils::extractField(&line[0], 1);
395  if (droute == "00000000") {
396  itf.name = itfn;
397  // take the first routing to 0
398  break;
399  }
400  }
401 
402  if (itf.name != "") {
403  {
404  // get ip address
405  StructGuard<struct ifaddrs> ifaddrs(nullptr, freeifaddrs);
406  {
407  struct ifaddrs *ifa;
408  if (getifaddrs(&ifa) < 0) {
409  LOG_ERROR << "getifaddrs: " << std::strerror(errno);
410  } else {
411  ifaddrs.reset(ifa);
412  }
413  }
414  if (ifaddrs != nullptr) {
415  for (struct ifaddrs *ifa = ifaddrs.get(); ifa != nullptr; ifa = ifa->ifa_next) {
416  if (itf.name == ifa->ifa_name) {
417  if (ifa->ifa_addr == nullptr) {
418  continue;
419  }
420  if (ifa->ifa_addr->sa_family != AF_INET) {
421  continue;
422  }
423  const struct sockaddr_storage *sa = reinterpret_cast<struct sockaddr_storage *>(ifa->ifa_addr);
424 
425  itf.ip = Utils::ipDisplayName(*sa);
426  }
427  }
428  }
429  }
430  {
431  // get mac address
432  std::ifstream mac_stream("/sys/class/net/" + itf.name + "/address");
433  std::string m((std::istreambuf_iterator<char>(mac_stream)), std::istreambuf_iterator<char>());
434  itf.mac = std::move(m);
435  boost::trim_right(itf.mac);
436  }
437  }
438 
439  Json::Value network_info;
440  network_info["local_ipv4"] = itf.ip;
441  network_info["mac"] = itf.mac;
442  network_info["hostname"] = Utils::getHostname();
443 
444  return network_info;
445 }
446 
447 std::string Utils::getHostname() {
448  std::array<char, 200> hostname{};
449  if (gethostname(hostname.data(), hostname.size()) < 0) {
450  return "";
451  }
452  return std::string(hostname.data());
453 }
454 
455 std::string Utils::randomUuid() {
456  std::random_device urandom;
457  boost::uuids::basic_random_generator<std::random_device> uuid_gen(urandom);
458  return boost::uuids::to_string(uuid_gen());
459 }
460 
461 // Note that this doesn't work with broken symlinks.
462 void Utils::copyDir(const boost::filesystem::path &from, const boost::filesystem::path &to) {
463  boost::filesystem::remove_all(to);
464 
465  boost::filesystem::create_directories(to);
466  boost::filesystem::directory_iterator it(from);
467 
468  for (; it != boost::filesystem::directory_iterator(); it++) {
469  if (boost::filesystem::is_directory(it->path())) {
470  copyDir(it->path(), to / it->path().filename());
471  } else {
472  boost::filesystem::copy_file(it->path(), to / it->path().filename());
473  }
474  }
475 }
476 
477 std::string Utils::readFileFromArchive(std::istream &as, const std::string &filename, const bool trim) {
478  struct archive *a = archive_read_new();
479  if (a == nullptr) {
480  LOG_ERROR << "archive error: could not initialize archive object";
481  throw std::runtime_error("archive error");
482  }
483  archive_read_support_filter_all(a);
484  archive_read_support_format_all(a);
485  auto state = std_::make_unique<archive_state>(std::ref(as));
486  int r = archive_read_open(a, reinterpret_cast<void *>(state.get()), nullptr, read_cb, nullptr);
487  if (r != ARCHIVE_OK) {
488  LOG_ERROR << "archive error: " << archive_error_string(a);
489  archive_read_free(a);
490  throw std::runtime_error("archive error");
491  }
492 
493  bool found = false;
494  std::stringstream out_stream;
495  struct archive_entry *entry;
496  while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
497  if (filename != archive_entry_pathname(entry)) {
498  archive_read_data_skip(a);
499  continue;
500  }
501 
502  const char *buff;
503  size_t size;
504  int64_t offset;
505 
506  for (;;) {
507  r = archive_read_data_block(a, reinterpret_cast<const void **>(&buff), &size, &offset);
508  if (r == ARCHIVE_EOF) {
509  found = true;
510  break;
511  }
512  if (r != ARCHIVE_OK) {
513  LOG_ERROR << "archive error: " << archive_error_string(a);
514  break;
515  }
516  if (size > 0 && buff != nullptr) {
517  out_stream.write(buff, static_cast<ssize_t>(size));
518  }
519  }
520  }
521 
522  r = archive_read_free(a);
523  if (r != ARCHIVE_OK) {
524  LOG_ERROR << "archive error: " << archive_error_string(a);
525  }
526 
527  if (!found) {
528  throw std::runtime_error("could not extract " + filename + " from archive");
529  }
530 
531  std::string result = out_stream.str();
532  if (trim) {
533  boost::trim_if(result, boost::is_any_of(" \t\r\n"));
534  }
535  return result;
536 }
537 
538 static ssize_t write_cb(struct archive *a, void *client_data, const void *buffer, size_t length) {
539  auto *s = reinterpret_cast<std::ostream *>(client_data);
540  s->write(reinterpret_cast<const char *>(buffer), static_cast<ssize_t>(length));
541  if (s->fail()) {
542  archive_set_error(a, -1, "unable to write in stream");
543  return -1;
544  }
545 
546  return static_cast<ssize_t>(length);
547 }
548 
549 void Utils::writeArchive(const std::map<std::string, std::string> &entries, std::ostream &as) {
550  struct archive *a = archive_write_new();
551  if (a == nullptr) {
552  LOG_ERROR << "archive error: could not initialize archive object";
553  throw std::runtime_error("archive error");
554  }
555  archive_write_set_format_pax(a);
556  archive_write_add_filter_gzip(a);
557 
558  int r = archive_write_open(a, reinterpret_cast<void *>(&as), nullptr, write_cb, nullptr);
559  if (r != ARCHIVE_OK) {
560  LOG_ERROR << "archive error: " << archive_error_string(a);
561  archive_write_free(a);
562  throw std::runtime_error("archive error");
563  }
564 
565  struct archive_entry *entry = archive_entry_new();
566  for (const auto &el : entries) {
567  archive_entry_clear(entry);
568  archive_entry_set_filetype(entry, AE_IFREG);
569  archive_entry_set_perm(entry, S_IRWXU | S_IRWXG | S_IRWXO);
570  archive_entry_set_size(entry, static_cast<ssize_t>(el.second.size()));
571  archive_entry_set_pathname(entry, el.first.c_str());
572  if (archive_write_header(a, entry) != 0) {
573  LOG_ERROR << "archive error: " << archive_error_string(a);
574  archive_entry_free(entry);
575  archive_write_free(a);
576  throw std::runtime_error("archive error");
577  }
578  if (archive_write_data(a, el.second.c_str(), el.second.size()) < 0) {
579  LOG_ERROR << "archive error: " << archive_error_string(a);
580  archive_entry_free(entry);
581  archive_write_free(a);
582  throw std::runtime_error("archive error");
583  }
584  }
585  archive_entry_free(entry);
586  r = archive_write_free(a);
587  if (r != ARCHIVE_OK) {
588  LOG_ERROR << "archive error: " << archive_error_string(a);
589  }
590 }
591 
592 sockaddr_storage Utils::ipGetSockaddr(int fd) {
593  sockaddr_storage ss{};
594  socklen_t len = sizeof(ss);
595  if (getsockname(fd, reinterpret_cast<sockaddr *>(&ss), &len) < 0) {
596  throw std::runtime_error(std::string("Could not get sockaddr: ") + std::strerror(errno));
597  }
598 
599  return ss;
600 }
601 
602 std::string Utils::ipDisplayName(const sockaddr_storage &saddr) {
603  std::array<char, INET6_ADDRSTRLEN> ipstr{};
604 
605  switch (saddr.ss_family) {
606  case AF_INET: {
607  const auto *sa = reinterpret_cast<const sockaddr_in *>(&saddr);
608  inet_ntop(AF_INET, &sa->sin_addr, ipstr.data(), ipstr.size());
609  return std::string(ipstr.data());
610  }
611  case AF_INET6: {
612  const auto *sa = reinterpret_cast<const sockaddr_in6 *>(&saddr);
613  inet_ntop(AF_INET6, &sa->sin6_addr, ipstr.data(), ipstr.size());
614  return std::string(ipstr.data());
615  }
616  default:
617  return "unknown";
618  }
619 }
620 
621 int Utils::ipPort(const sockaddr_storage &saddr) {
622  in_port_t p;
623  if (saddr.ss_family == AF_INET) {
624  const auto *sa = reinterpret_cast<const sockaddr_in *>(&saddr);
625  p = sa->sin_port;
626  } else if (saddr.ss_family == AF_INET6) {
627  const auto *sa = reinterpret_cast<const sockaddr_in6 *>(&saddr);
628  p = sa->sin6_port;
629  } else {
630  return -1;
631  }
632 
633  return ntohs(p);
634 }
635 
636 int Utils::shell(const std::string &command, std::string *output, bool include_stderr) {
637  char buffer[128];
638  std::string full_command(command);
639  if (include_stderr) {
640  full_command += " 2>&1";
641  }
642  FILE *pipe = popen(full_command.c_str(), "r");
643  if (pipe == nullptr) {
644  *output = "popen() failed!";
645  return -1;
646  }
647  while (feof(pipe) == 0) {
648  if (fgets(buffer, 128, pipe) != nullptr) {
649  *output += buffer;
650  }
651  }
652  int exitcode = pclose(pipe);
653  return WEXITSTATUS(exitcode);
654 }
655 
656 boost::filesystem::path Utils::absolutePath(const boost::filesystem::path &root, const boost::filesystem::path &file) {
657  if (file.is_absolute() || root.empty()) {
658  return file;
659  }
660  return (root / file);
661 }
662 
663 // passing ext argument keep the leading point e.g. '.ext'
664 // results are returned sorted in alpanumeric order
665 std::vector<boost::filesystem::path> Utils::getDirEntriesByExt(const boost::filesystem::path &dir_path,
666  const std::string &ext) {
667  std::vector<boost::filesystem::path> entries;
668  boost::filesystem::directory_iterator entryItEnd, entryIt(dir_path);
669  for (; entryIt != entryItEnd; ++entryIt) {
670  const auto &entry_path = entryIt->path();
671  if (!boost::filesystem::is_directory(*entryIt) && entry_path.extension().string() == ext) {
672  entries.push_back(entry_path);
673  }
674  }
675  std::sort(entries.begin(), entries.end());
676  return entries;
677 }
678 
679 void Utils::createDirectories(const boost::filesystem::path &path, mode_t mode) {
680  boost::filesystem::path parent = path.parent_path();
681  if (!parent.empty() && !boost::filesystem::exists(parent)) {
682  Utils::createDirectories(parent, mode);
683  }
684  if (mkdir(path.c_str(), mode) == -1) {
685  throw std::runtime_error(std::string("could not create directory: ").append(path.native()));
686  }
687  std::cout << "created: " << path.native() << "\n";
688 }
689 
690 bool Utils::createSecureDirectory(const boost::filesystem::path &path) {
691  if (mkdir(path.c_str(), S_IRWXU) == 0) {
692  // directory created successfully
693  return true;
694  }
695 
696  // mkdir failed, see if the directory already exists with correct permissions
697  struct stat st {};
698  int ret = stat(path.c_str(), &st);
699  // checks: - stat succeeded
700  // - is a directory
701  // - no read and write permissions for group and others
702  // - owner is current user
703  return (ret >= 0 && ((st.st_mode & S_IFDIR) == S_IFDIR) && (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) == S_IRWXU &&
704  (st.st_uid == getuid()));
705 }
706 
707 std::string Utils::urlEncode(const std::string &input) {
708  std::string res;
709 
710  for (char c : input) {
711  if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '.' ||
712  c == '_' || c == '~' || c == '/') {
713  res.push_back(c);
714  } else {
715  res.push_back('%');
716  auto nib = static_cast<char>(((c >> 4) & 0x0F));
717  res.push_back(static_cast<char>((nib < 10) ? nib + '0' : nib - 10 + 'A'));
718  nib = static_cast<char>(c & 0x0F);
719  res.push_back(static_cast<char>((nib < 10) ? nib + '0' : nib - 10 + 'A'));
720  }
721  }
722  return res;
723 }
724 
725 CURL *Utils::curlDupHandleWrapper(CURL *const curl_in, const bool using_pkcs11) {
726  CURL *curl = curl_easy_duphandle(curl_in);
727 
728  // This is a workaround for a bug in curl. It has been fixed in
729  // 75a845d8cfa71688d59d43788c35829b25b6d6af (curl 7.61.1), but that is not
730  // the default in most distributions yet, so we will continue to use the
731  // workaround.
732  if (using_pkcs11) {
733  curlEasySetoptWrapper(curl, CURLOPT_SSLENGINE, "pkcs11");
734  }
735  return curl;
736 }
737 
739  public:
740  SafeTempRoot(const SafeTempRoot &) = delete;
741  SafeTempRoot operator=(const SafeTempRoot &) = delete;
742  // provide this as a static method so that we can use C++ static destructor
743  // to remove the temp root
744  static boost::filesystem::path &Get() {
745  static SafeTempRoot r;
746 
747  return r.path;
748  }
749 
750  private:
751  SafeTempRoot() {
752  boost::filesystem::path prefix = Utils::getStorageRootPath();
753  if (prefix.empty()) {
754  prefix = boost::filesystem::temp_directory_path();
755  }
756  boost::filesystem::path p = prefix / boost::filesystem::unique_path("aktualizr-%%%%-%%%%-%%%%-%%%%");
757  if (mkdir(p.c_str(), S_IRWXU) == -1) {
758  throw std::runtime_error(std::string("could not create temporary directory root: ").append(p.native()));
759  }
760 
761  path = boost::filesystem::path(p);
762  }
763  ~SafeTempRoot() {
764  try {
765  boost::filesystem::remove_all(path);
766  } catch (...) {
767  // ignore this, not critical
768  }
769  }
770 
771  boost::filesystem::path path;
772 };
773 
774 std::string Utils::storage_root_path_;
775 
776 void Utils::setStorageRootPath(const std::string &storage_root_path) { storage_root_path_ = storage_root_path; }
777 
778 boost::filesystem::path Utils::getStorageRootPath() { return storage_root_path_; }
779 
780 void Utils::setUserAgent(std::string user_agent) { user_agent_ = std::move(user_agent); }
781 
782 const char *Utils::getUserAgent() {
783  if (user_agent_.empty()) {
784  user_agent_ = (std::string("Aktualizr/") + aktualizr_version());
785  }
786  return user_agent_.c_str();
787 }
788 
789 std::string Utils::user_agent_;
790 
791 void Utils::setCaPath(boost::filesystem::path path) { ca_path_ = std::move(path); }
792 
793 const char *Utils::getCaPath() { return ca_path_.c_str(); }
794 
795 boost::filesystem::path Utils::ca_path_{"/etc/ssl/certs"};
796 
797 TemporaryFile::TemporaryFile(const std::string &hint)
798  : tmp_name_(SafeTempRoot::Get() / boost::filesystem::unique_path(std::string("%%%%-%%%%-").append(hint))) {}
799 
800 TemporaryFile::~TemporaryFile() { boost::filesystem::remove(tmp_name_); }
801 
802 void TemporaryFile::PutContents(const std::string &contents) const {
803  mode_t mode = S_IRUSR | S_IWUSR;
804  int fd = open(Path().c_str(), O_WRONLY | O_CREAT | O_TRUNC, mode);
805  if (fd < 0) {
806  throw std::runtime_error(std::string("Could not write content to file: ") + Path().string() + ": " +
807  std::strerror(errno));
808  }
809  ssize_t written = write(fd, contents.c_str(), contents.size());
810  close(fd);
811  if (written < 0 || static_cast<size_t>(written) != contents.size()) {
812  throw std::runtime_error(std::string("Could not write content to file: ") + Path().string());
813  }
814 }
815 
816 boost::filesystem::path TemporaryFile::Path() const { return tmp_name_; }
817 
818 std::string TemporaryFile::PathString() const { return Path().string(); }
819 
820 TemporaryDirectory::TemporaryDirectory(const std::string &hint)
821  : tmp_name_(SafeTempRoot::Get() / boost::filesystem::unique_path(std::string("%%%%-%%%%-").append(hint))) {
822  Utils::createDirectories(tmp_name_, S_IRWXU);
823 }
824 
825 TemporaryDirectory::~TemporaryDirectory() { boost::filesystem::remove_all(tmp_name_); }
826 
827 boost::filesystem::path TemporaryDirectory::Path() const { return tmp_name_; }
828 
829 boost::filesystem::path TemporaryDirectory::operator/(const boost::filesystem::path &subdir) const {
830  return (tmp_name_ / subdir);
831 }
832 
833 std::string TemporaryDirectory::PathString() const { return Path().string(); }
834 
835 void Utils::setSocketPort(sockaddr_storage *addr, in_port_t port) {
836  if (addr->ss_family == AF_INET) {
837  reinterpret_cast<sockaddr_in *>(addr)->sin_port = port;
838  } else if (addr->ss_family == AF_INET6) {
839  reinterpret_cast<sockaddr_in6 *>(addr)->sin6_port = port;
840  }
841 }
842 
843 bool operator<(const sockaddr_storage &left, const sockaddr_storage &right) {
844  if (left.ss_family == AF_INET) {
845  throw std::runtime_error("IPv4 addresses are not supported");
846  }
847  const unsigned char *left_addr = reinterpret_cast<const sockaddr_in6 *>(&left)->sin6_addr.s6_addr; // NOLINT
848  const unsigned char *right_addr = reinterpret_cast<const sockaddr_in6 *>(&right)->sin6_addr.s6_addr; // NOLINT
849  int res = memcmp(left_addr, right_addr, 16);
850 
851  return (res < 0);
852 }
853 
854 Socket::Socket() {
855  socket_fd_ = socket(AF_INET, SOCK_STREAM, 0);
856  if (-1 == socket_fd_) {
857  throw std::system_error(errno, std::system_category(), "socket");
858  }
859 }
860 
861 Socket::~Socket() { ::close(socket_fd_); }
862 
863 std::string Socket::toString() const {
864  auto saddr = Utils::ipGetSockaddr(socket_fd_);
865  return Utils::ipDisplayName(saddr) + ":" + std::to_string(Utils::ipPort(saddr));
866 }
867 
868 void Socket::bind(in_port_t port, bool reuse) const {
869  sockaddr_in sa{};
870  memset(&sa, 0, sizeof(sa));
871  sa.sin_family = AF_INET;
872  sa.sin_port = htons(port);
873  sa.sin_addr.s_addr = htonl(INADDR_ANY);
874 
875  int reuseaddr = reuse ? 1 : 0;
876  if (-1 == setsockopt(socket_fd_, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr))) {
877  throw std::system_error(errno, std::system_category(), "socket");
878  }
879 
880  if (-1 == ::bind(socket_fd_, reinterpret_cast<const sockaddr *>(&sa), sizeof(sa))) {
881  throw std::system_error(errno, std::system_category(), "socket");
882  }
883 }
884 
885 ListenSocket::ListenSocket(in_port_t port) : _port(port) {
886  bind(port);
887  if (_port == 0) {
888  // ephemeral port was bound, find out its real port number
889  auto ephemeral_port = Utils::ipPort(Utils::ipGetSockaddr(socket_fd_));
890  if (-1 != ephemeral_port) {
891  _port = static_cast<in_port_t>(ephemeral_port);
892  }
893  }
894 }
895 
896 ConnectionSocket::ConnectionSocket(const std::string &ip, in_port_t port, in_port_t bind_port)
897  : remote_sock_address_{} {
898  memset(&remote_sock_address_, 0, sizeof(remote_sock_address_));
899  remote_sock_address_.sin_family = AF_INET;
900  if (-1 == inet_pton(AF_INET, ip.c_str(), &(remote_sock_address_.sin_addr))) {
901  throw std::system_error(errno, std::system_category(), "socket");
902  }
903  remote_sock_address_.sin_port = htons(port);
904 
905  if (bind_port > 0) {
906  bind(bind_port);
907  }
908 }
909 
910 ConnectionSocket::~ConnectionSocket() { ::shutdown(socket_fd_, SHUT_RDWR); }
911 
912 int ConnectionSocket::connect() {
913  return ::connect(socket_fd_, reinterpret_cast<const struct sockaddr *>(&remote_sock_address_),
914  sizeof(remote_sock_address_));
915 }
916 
917 CurlEasyWrapper::CurlEasyWrapper() {
918  handle = curl_easy_init();
919  if (handle == nullptr) {
920  throw std::runtime_error("Could not initialize curl handle");
921  }
922  curlEasySetoptWrapper(handle, CURLOPT_USERAGENT, Utils::getUserAgent());
923 }
924 
925 CurlEasyWrapper::~CurlEasyWrapper() {
926  if (handle != nullptr) {
927  curl_easy_cleanup(handle);
928  }
929 }
archive_state
Definition: utils.cc:298
SafeTempRoot
Definition: utils.cc:738
result
Results of libaktualizr API calls.
Definition: results.h:13