Aktualizr
C++ SOTA Client
All Classes Namespaces Files Functions Variables Enumerations Enumerator Pages
main.cc
1 #include <stdlib.h>
2 #include <iostream>
3 #include <random>
4 #include <sstream>
5 #include <string>
6 #include <utility>
7 
8 #include <boost/filesystem.hpp>
9 #include <boost/program_options.hpp>
10 
11 #include "json/json.h"
12 
13 #include "bootstrap/bootstrap.h"
14 #include "crypto/crypto.h"
15 #include "http/httpclient.h"
16 #include "libaktualizr/config.h"
17 #include "logging/logging.h"
18 #include "utilities/aktualizr_version.h"
19 #include "utilities/utils.h"
20 
21 namespace bpo = boost::program_options;
22 
23 void checkInfoOptions(const bpo::options_description& description, const bpo::variables_map& vm) {
24  if (vm.count("help") != 0) {
25  std::cout << description << '\n';
26  exit(EXIT_SUCCESS);
27  }
28  if (vm.count("version") != 0) {
29  std::cout << "Current aktualizr-cert-provider version is: " << aktualizr_version() << "\n";
30  exit(EXIT_SUCCESS);
31  }
32 }
33 
34 bpo::variables_map parseOptions(int argc, char** argv) {
35  bpo::options_description description("aktualizr-cert-provider command line options");
36  // clang-format off
37  description.add_options()
38  ("help,h", "print usage")
39  ("version,v", "Current aktualizr-cert-provider version")
40  ("credentials,c", bpo::value<boost::filesystem::path>(), "zipped credentials file")
41  ("fleet-ca", bpo::value<boost::filesystem::path>(), "path to fleet certificate authority certificate (for signing device certificates)")
42  ("fleet-ca-key", bpo::value<boost::filesystem::path>(), "path to the private key of fleet certificate authority")
43  ("bits", bpo::value<int>(), "size of RSA keys in bits")
44  ("days", bpo::value<int>(), "validity term for the certificate in days")
45  ("certificate-c", bpo::value<std::string>(), "value for C field in certificate subject name")
46  ("certificate-st", bpo::value<std::string>(), "value for ST field in certificate subject name")
47  ("certificate-o", bpo::value<std::string>(), "value for O field in certificate subject name")
48  ("certificate-cn", bpo::value<std::string>(), "value for CN field in certificate subject name")
49  ("target,t", bpo::value<std::string>(), "target device to scp credentials to (or [user@]host)")
50  ("port,p", bpo::value<int>(), "target port")
51  ("directory,d", bpo::value<boost::filesystem::path>(), "directory on target to write credentials to (conflicts with -config)")
52  ("root-ca,r", "provide root CA")
53  ("server-url,u", "provide server url file")
54  ("local,l", bpo::value<boost::filesystem::path>(), "local directory to write credentials to")
55  ("config,g", bpo::value<std::vector<boost::filesystem::path> >()->composing(), "configuration file or directory from which to get file names")
56  ("skip-checks,s", "skip strict host key checking for ssh/scp commands");
57  // clang-format on
58 
59  bpo::variables_map vm;
60  std::vector<std::string> unregistered_options;
61  try {
62  bpo::basic_parsed_options<char> parsed_options =
63  bpo::command_line_parser(argc, argv).options(description).allow_unregistered().run();
64  bpo::store(parsed_options, vm);
65  checkInfoOptions(description, vm);
66  bpo::notify(vm);
67  unregistered_options = bpo::collect_unrecognized(parsed_options.options, bpo::include_positional);
68  if (vm.count("help") == 0 && !unregistered_options.empty()) {
69  std::cout << description << "\n";
70  exit(EXIT_FAILURE);
71  }
72  } catch (const bpo::required_option& ex) {
73  // print the error and append the default commandline option description
74  std::cout << ex.what() << std::endl << description;
75  exit(EXIT_FAILURE);
76  } catch (const bpo::error& ex) {
77  checkInfoOptions(description, vm);
78 
79  // print the error message to the standard output too, as the user provided
80  // a non-supported commandline option
81  std::cout << ex.what() << '\n';
82 
83  // set the returnValue, thereby ctest will recognize
84  // that something went wrong
85  exit(EXIT_FAILURE);
86  }
87 
88  return vm;
89 }
90 
91 // I miss Rust's ? operator
92 #define SSL_ERROR(description) \
93  { \
94  std::cerr << (description) << ERR_error_string(ERR_get_error(), nullptr) << std::endl; \
95  return false; \
96  }
97 bool generateAndSign(const std::string& cacert_path, const std::string& capkey_path, std::string* pkey,
98  std::string* cert, const bpo::variables_map& commandline_map) {
99  int rsa_bits = 2048;
100  if (commandline_map.count("bits") != 0) {
101  rsa_bits = (commandline_map["bits"].as<int>());
102  if (rsa_bits < 31) { // sic!
103  std::cerr << "RSA key size can't be smaller than 31 bits" << std::endl;
104  return false;
105  }
106  }
107 
108  int cert_days = 365;
109  if (commandline_map.count("days") != 0) {
110  cert_days = (commandline_map["days"].as<int>());
111  }
112 
113  std::string newcert_c;
114  if (commandline_map.count("certificate-c") != 0) {
115  newcert_c = (commandline_map["certificate-c"].as<std::string>());
116  if (newcert_c.length() != 2) {
117  std::cerr << "Country code (--certificate-c) should be 2 characters long" << std::endl;
118  return false;
119  }
120  };
121 
122  std::string newcert_st;
123  if (commandline_map.count("certificate-st") != 0) {
124  newcert_st = (commandline_map["certificate-st"].as<std::string>());
125  if (newcert_st.empty()) {
126  std::cerr << "State name (--certificate-st) can't be empty" << std::endl;
127  return false;
128  }
129  };
130 
131  std::string newcert_o;
132  if (commandline_map.count("certificate-o") != 0) {
133  newcert_o = (commandline_map["certificate-o"].as<std::string>());
134  if (newcert_o.empty()) {
135  std::cerr << "Organization name (--certificate-o) can't be empty" << std::endl;
136  return false;
137  }
138  };
139 
140  std::string newcert_cn;
141  if (commandline_map.count("certificate-cn") != 0) {
142  newcert_cn = (commandline_map["certificate-cn"].as<std::string>());
143  if (newcert_cn.empty()) {
144  std::cerr << "Common name (--certificate-cn) can't be empty" << std::endl;
145  return false;
146  }
147  } else {
148  newcert_cn = Utils::genPrettyName();
149  }
150 
151  // read CA certificate
152  std::string cacert_contents = Utils::readFile(cacert_path);
153  StructGuard<BIO> bio_in_cacert(BIO_new_mem_buf(cacert_contents.c_str(), static_cast<int>(cacert_contents.size())),
154  BIO_free_all);
155  StructGuard<X509> ca_certificate(PEM_read_bio_X509(bio_in_cacert.get(), nullptr, nullptr, nullptr), X509_free);
156  if (ca_certificate.get() == nullptr) {
157  std::cerr << "Reading CA certificate failed.\n";
158  return false;
159  }
160 
161  // read CA private key
162  std::string capkey_contents = Utils::readFile(capkey_path);
163  StructGuard<BIO> bio_in_capkey(BIO_new_mem_buf(capkey_contents.c_str(), static_cast<int>(capkey_contents.size())),
164  BIO_free_all);
165  StructGuard<EVP_PKEY> ca_privkey(PEM_read_bio_PrivateKey(bio_in_capkey.get(), nullptr, nullptr, nullptr),
166  EVP_PKEY_free);
167  if (ca_privkey.get() == nullptr) SSL_ERROR("PEM_read_bio_PrivateKey failed: ");
168 
169  // create certificate
170  StructGuard<X509> certificate(X509_new(), X509_free);
171  if (certificate.get() == nullptr) SSL_ERROR("X509_new failed: ");
172 
173  X509_set_version(certificate.get(), 2); // X509v3
174 
175  {
176  std::random_device urandom;
177  std::uniform_int_distribution<> serial_dist(0, (1UL << 20) - 1);
178  ASN1_INTEGER_set(X509_get_serialNumber(certificate.get()), serial_dist(urandom));
179  }
180 
181  // create and set certificate subject name
182  StructGuard<X509_NAME> subj(X509_NAME_new(), X509_NAME_free);
183  if (subj.get() == nullptr) SSL_ERROR("X509_NAME_new failed: ");
184 
185  if (!newcert_c.empty()) {
186  if (X509_NAME_add_entry_by_txt(subj.get(), "C", MBSTRING_ASC,
187  reinterpret_cast<const unsigned char*>(newcert_c.c_str()), -1, -1, 0) == 0)
188  SSL_ERROR("X509_NAME_add_entry_by_txt failed: ");
189  }
190 
191  if (!newcert_st.empty()) {
192  if (X509_NAME_add_entry_by_txt(subj.get(), "ST", MBSTRING_ASC,
193  reinterpret_cast<const unsigned char*>(newcert_st.c_str()), -1, -1, 0) == 0)
194  SSL_ERROR("X509_NAME_add_entry_by_txt failed: ");
195  }
196 
197  if (!newcert_o.empty()) {
198  if (X509_NAME_add_entry_by_txt(subj.get(), "O", MBSTRING_ASC,
199  reinterpret_cast<const unsigned char*>(newcert_o.c_str()), -1, -1, 0) == 0)
200  SSL_ERROR("X509_NAME_add_entry_by_txt failed: ");
201  }
202 
203  assert(!newcert_cn.empty());
204  if (X509_NAME_add_entry_by_txt(subj.get(), "CN", MBSTRING_ASC,
205  reinterpret_cast<const unsigned char*>(newcert_cn.c_str()), -1, -1, 0) == 0)
206  SSL_ERROR("X509_NAME_add_entry_by_txt failed: ");
207 
208  if (X509_set_subject_name(certificate.get(), subj.get()) == 0) SSL_ERROR("X509_set_subject_name failed: ");
209 
210  // set issuer name
211  X509_NAME* ca_subj = X509_get_subject_name(ca_certificate.get());
212  if (ca_subj == nullptr) SSL_ERROR("X509_get_subject_name failed: ");
213 
214  if (X509_set_issuer_name(certificate.get(), ca_subj) == 0) SSL_ERROR("X509_set_issuer_name failed: ");
215 
216  // create and set key
217 
218  // freed by owner EVP_PKEY
219  RSA* certificate_rsa = RSA_generate_key(rsa_bits, RSA_F4, nullptr, nullptr);
220  if (certificate_rsa == nullptr) SSL_ERROR("RSA_generate_key failed: ");
221 
222  StructGuard<EVP_PKEY> certificate_pkey(EVP_PKEY_new(), EVP_PKEY_free);
223  if (certificate_pkey.get() == nullptr) SSL_ERROR("EVP_PKEY_new failed: ");
224 
225  if (!EVP_PKEY_assign_RSA(certificate_pkey.get(), certificate_rsa)) // NOLINT
226  SSL_ERROR("EVP_PKEY_assign_RSA failed: ");
227 
228  if (X509_set_pubkey(certificate.get(), certificate_pkey.get()) == 0) SSL_ERROR("X509_set_pubkey failed: ");
229 
230  // set validity period
231  if (X509_gmtime_adj(X509_get_notBefore(certificate.get()), 0) == nullptr) SSL_ERROR("X509_gmtime_adj failed: ");
232 
233  if (X509_gmtime_adj(X509_get_notAfter(certificate.get()), 60L * 60L * 24L * cert_days) == nullptr)
234  SSL_ERROR("X509_gmtime_adj failed: ");
235 
236  // sign
237  const EVP_MD* cert_digest = EVP_sha256();
238  if (X509_sign(certificate.get(), ca_privkey.get(), cert_digest) == 0) SSL_ERROR("X509_sign failed: ");
239 
240  // serialize private key
241  char* privkey_buf;
242  StructGuard<BIO> privkey_file(BIO_new(BIO_s_mem()), BIO_vfree);
243  if (privkey_file == nullptr) {
244  std::cerr << "Error opening memstream" << std::endl;
245  return false;
246  }
247  int ret = PEM_write_bio_RSAPrivateKey(privkey_file.get(), certificate_rsa, nullptr, nullptr, 0, nullptr, nullptr);
248  if (ret == 0) {
249  std::cerr << "PEM_write_RSAPrivateKey" << std::endl;
250  return false;
251  }
252  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
253  auto privkey_len = BIO_get_mem_data(privkey_file.get(), &privkey_buf);
254  *pkey = std::string(privkey_buf, static_cast<size_t>(privkey_len));
255 
256  // serialize certificate
257  char* cert_buf;
258  StructGuard<BIO> cert_file(BIO_new(BIO_s_mem()), BIO_vfree);
259  if (cert_file == nullptr) {
260  std::cerr << "Error opening memstream" << std::endl;
261  return false;
262  }
263  ret = PEM_write_bio_X509(cert_file.get(), certificate.get());
264  if (ret == 0) {
265  std::cerr << "PEM_write_X509" << std::endl;
266  return false;
267  }
268  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
269  auto cert_len = BIO_get_mem_data(cert_file.get(), &cert_buf);
270  *cert = std::string(cert_buf, static_cast<size_t>(cert_len));
271 
272  return true;
273 }
274 
275 class SSHRunner {
276  public:
277  SSHRunner(std::string target, const bool skip_checks, const int port = 22)
278  : target_(std::move(target)), skip_checks_(skip_checks), port_(port) {}
279 
280  void runCmd(const std::string& cmd) const {
281  std::ostringstream prefix;
282 
283  prefix << "ssh ";
284  if (port_ != 22) {
285  prefix << "-p " << port_ << " ";
286  }
287  if (skip_checks_) {
288  prefix << "-o StrictHostKeyChecking=no ";
289  }
290  prefix << target_ << " ";
291 
292  std::string fullCmd = prefix.str() + cmd;
293  std::cout << "Running " << fullCmd << std::endl;
294 
295  int ret = system(fullCmd.c_str());
296  if (ret != 0) {
297  throw std::runtime_error("Error running command on " + target_ + ": " + std::to_string(ret));
298  }
299  }
300 
301  void transferFile(const boost::filesystem::path& inFile, const boost::filesystem::path& targetPath) {
302  // create parent directory
303  runCmd("mkdir -p " + targetPath.parent_path().string());
304 
305  std::ostringstream prefix;
306 
307  prefix << "scp ";
308  if (port_ != 22) {
309  prefix << "-P " << port_ << " ";
310  }
311  if (skip_checks_) {
312  prefix << "-o StrictHostKeyChecking=no ";
313  }
314 
315  std::string fullCmd = prefix.str() + inFile.string() + " " + target_ + ":" + targetPath.string();
316  std::cout << "Running " << fullCmd << std::endl;
317 
318  int ret = system(fullCmd.c_str());
319  if (ret != 0) {
320  throw std::runtime_error("Error copying file on " + target_ + ": " + std::to_string(ret));
321  }
322  }
323 
324  private:
325  std::string target_;
326  bool skip_checks_;
327  int port_;
328 };
329 
330 void copyLocal(const boost::filesystem::path& src, const boost::filesystem::path& dest) {
331  boost::filesystem::path dest_dir = dest.parent_path();
332  if (boost::filesystem::exists(dest_dir)) {
333  boost::filesystem::remove(dest);
334  } else {
335  boost::filesystem::create_directories(dest_dir);
336  }
337  boost::filesystem::copy_file(src, dest);
338 }
339 
340 int main(int argc, char* argv[]) {
341  int exit_code = EXIT_FAILURE;
342 
343  logger_init();
344  logger_set_threshold(static_cast<boost::log::trivial::severity_level>(2));
345 
346  try {
347  bpo::variables_map commandline_map = parseOptions(argc, argv);
348 
349  std::string target;
350  if (commandline_map.count("target") != 0) {
351  target = commandline_map["target"].as<std::string>();
352  }
353  int port = 22;
354  if (commandline_map.count("port") != 0) {
355  port = (commandline_map["port"].as<int>());
356  }
357  const bool provide_ca = commandline_map.count("root-ca") != 0;
358  const bool provide_url = commandline_map.count("server-url") != 0;
359  boost::filesystem::path local_dir;
360  if (commandline_map.count("local") != 0) {
361  local_dir = commandline_map["local"].as<boost::filesystem::path>();
362  }
363  std::vector<boost::filesystem::path> config_path;
364  if (commandline_map.count("config") != 0) {
365  config_path = commandline_map["config"].as<std::vector<boost::filesystem::path>>();
366  }
367  const bool skip_checks = commandline_map.count("skip-checks") != 0;
368 
369  boost::filesystem::path fleet_ca_path = "";
370  if (commandline_map.count("fleet-ca") != 0) {
371  fleet_ca_path = commandline_map["fleet-ca"].as<boost::filesystem::path>();
372  }
373 
374  boost::filesystem::path fleet_ca_key_path = "";
375  if (commandline_map.count("fleet-ca-key") != 0) {
376  fleet_ca_key_path = commandline_map["fleet-ca-key"].as<boost::filesystem::path>();
377  }
378 
379  if (fleet_ca_path.empty() != fleet_ca_key_path.empty()) {
380  std::cerr << "fleet-ca and fleet-ca-key options should be used together" << std::endl;
381  return 1;
382  }
383 
384  if (!commandline_map["directory"].empty() && !commandline_map["config"].empty()) {
385  std::cerr << "Directory (--directory) and config (--config) options cannot be used together" << std::endl;
386  return EXIT_FAILURE;
387  }
388 
389  boost::filesystem::path credentials_path = "";
390  if (commandline_map.count("credentials") != 0) {
391  credentials_path = commandline_map["credentials"].as<boost::filesystem::path>();
392  }
393 
394  if (local_dir.empty() && target.empty()) {
395  std::cerr << "Please provide a local directory and/or target to output the generated files to" << std::endl;
396  return EXIT_FAILURE;
397  }
398 
399  std::string serverUrl;
400  if ((fleet_ca_path.empty() || provide_ca || provide_url) && credentials_path.empty()) {
401  std::cerr
402  << "Error: missing -c/--credentials parameters which is mandatory if the fleet CA is not specified or an "
403  "output of the root CA or a gateway URL is requested";
404  return EXIT_FAILURE;
405  } else {
406  serverUrl = Bootstrap::readServerUrl(credentials_path);
407  }
408 
409  boost::filesystem::path directory = "/var/sota/import";
410  utils::BasedPath pkey_file = utils::BasedPath("pkey.pem");
411  utils::BasedPath cert_file = utils::BasedPath("client.pem");
412  utils::BasedPath ca_file = utils::BasedPath("root.crt");
413  utils::BasedPath url_file = utils::BasedPath("gateway.url");
414  if (!config_path.empty()) {
415  Config config(config_path);
416  // TODO: provide path to root directory in `--local` parameter
417 
418  // try first import base path and then storage path
419  if (!config.import.base_path.empty()) {
420  directory = config.import.base_path;
421  } else if (!config.storage.path.empty()) {
422  directory = config.storage.path;
423  }
424 
425  if (!config.import.tls_pkey_path.empty()) {
426  pkey_file = config.import.tls_pkey_path;
427  } else {
428  pkey_file = config.storage.tls_pkey_path;
429  }
430 
431  if (!config.import.tls_clientcert_path.empty()) {
432  cert_file = config.import.tls_clientcert_path;
433  } else {
434  cert_file = config.storage.tls_clientcert_path;
435  }
436  if (provide_ca) {
437  if (!config.import.tls_cacert_path.empty()) {
438  ca_file = config.import.tls_cacert_path;
439  } else {
440  ca_file = config.storage.tls_cacert_path;
441  }
442  }
443  if (provide_url && !config.tls.server_url_path.empty()) {
444  url_file = config.tls.server_url_path;
445  }
446  }
447 
448  if (!commandline_map["directory"].empty()) {
449  directory = commandline_map["directory"].as<boost::filesystem::path>();
450  }
451 
452  TemporaryFile tmp_pkey_file(pkey_file.get("").filename().string());
453  TemporaryFile tmp_cert_file(cert_file.get("").filename().string());
454  TemporaryFile tmp_ca_file(ca_file.get("").filename().string());
455  TemporaryFile tmp_url_file(url_file.get("").filename().string());
456 
457  std::string pkey;
458  std::string cert;
459  std::string ca;
460 
461  if (fleet_ca_path.empty()) { // no fleet CA => provision with shared credentials
462 
463  std::string device_id = Utils::genPrettyName();
464  std::cout << "Random device ID is " << device_id << "\n";
465 
466  Bootstrap boot(credentials_path, "");
467  HttpClient http;
468  Json::Value data;
469  data["deviceId"] = device_id;
470  data["ttl"] = 36000;
471 
472  std::cout << "Provisioning against server...\n";
473  http.setCerts(boot.getCa(), CryptoSource::kFile, boot.getCert(), CryptoSource::kFile, boot.getPkey(),
474  CryptoSource::kFile);
475  HttpResponse response = http.post(serverUrl + "/devices", data);
476  if (!response.isOk()) {
477  Json::Value resp_code = response.getJson()["code"];
478  if (resp_code.isString() && resp_code.asString() == "device_already_registered") {
479  std::cout << "Device ID" << device_id << "is occupied.\n";
480  return EXIT_FAILURE;
481  }
482  std::cout << "Provisioning failed, response: " << response.body << "\n";
483  return EXIT_FAILURE;
484  }
485  std::cout << "...success\n";
486 
487  StructGuard<BIO> device_p12(BIO_new_mem_buf(response.body.c_str(), static_cast<int>(response.body.size())),
488  BIO_vfree);
489  if (!Crypto::parseP12(device_p12.get(), "", &pkey, &cert, &ca)) {
490  std::cout << "Unable to parse p12 file received from server.\n";
491  return EXIT_FAILURE;
492  }
493  } else { // fleet CA set => generate and sign a new certificate
494  if (!generateAndSign(fleet_ca_path.native(), fleet_ca_key_path.native(), &pkey, &cert, commandline_map)) {
495  return EXIT_FAILURE;
496  }
497 
498  if (provide_ca) {
499  // Read server root CA from server_ca.pem in archive if found (to support
500  // community edition use case). Otherwise, default to the old version of
501  // expecting it to be in the p12.
502  ca = Bootstrap::readServerCa(credentials_path);
503  if (ca.empty()) {
504  Bootstrap boot(credentials_path, "");
505  ca = boot.getCa();
506  std::cout << "Server root CA read from autoprov_credentials.p12 in zipped archive.\n";
507  } else {
508  std::cout << "Server root CA read from server_ca.pem in zipped archive.\n";
509  }
510  }
511  }
512 
513  tmp_pkey_file.PutContents(pkey);
514  tmp_cert_file.PutContents(cert);
515  if (provide_ca) {
516  tmp_ca_file.PutContents(ca);
517  }
518  if (provide_url) {
519  tmp_url_file.PutContents(serverUrl);
520  }
521 
522  if (!local_dir.empty()) {
523  auto pkey_file_path = local_dir / pkey_file.get(directory);
524  std::cout << "Writing the generated client private key to " << pkey_file_path << " ...\n";
525  copyLocal(tmp_pkey_file.PathString(), pkey_file_path);
526 
527  auto cert_file_path = local_dir / cert_file.get(directory);
528  std::cout << "Writing the generated and signed client certificate to " << cert_file_path << " ...\n";
529  copyLocal(tmp_cert_file.PathString(), cert_file_path);
530 
531  if (provide_ca) {
532  auto root_ca_file = local_dir / ca_file.get(directory);
533  std::cout << "Writing the server root CA to " << root_ca_file << " ...\n";
534  copyLocal(tmp_ca_file.PathString(), root_ca_file);
535  }
536  if (provide_url) {
537  auto gtw_url_file = local_dir / url_file.get(directory);
538  std::cout << "Writing the gateway URL to " << gtw_url_file << " ...\n";
539  copyLocal(tmp_url_file.PathString(), gtw_url_file);
540  }
541  std::cout << "...success\n";
542  }
543 
544  if (!target.empty()) {
545  std::cout << "Copying client certificate and keys to " << target << ":" << directory;
546  if (port != 0) {
547  std::cout << " on port " << port;
548  }
549  std::cout << " ...\n";
550 
551  SSHRunner ssh{target, skip_checks, port};
552 
553  try {
554  ssh.transferFile(tmp_pkey_file.Path(), pkey_file.get(directory));
555 
556  ssh.transferFile(tmp_cert_file.Path(), cert_file.get(directory));
557 
558  if (provide_ca) {
559  ssh.transferFile(tmp_ca_file.Path(), ca_file.get(directory));
560  }
561  if (provide_url) {
562  ssh.transferFile(tmp_url_file.Path(), url_file.get(directory));
563  }
564 
565  std::cout << "...success\n";
566  } catch (const std::runtime_error& exc) {
567  std::cout << exc.what() << std::endl;
568  }
569  }
570 
571  exit_code = EXIT_SUCCESS;
572  } catch (const std::exception& exc) {
573  LOG_ERROR << "Error: " << exc.what();
574 
575  exit_code = EXIT_FAILURE;
576  }
577 
578  return exit_code;
579 }
General data structures.
Definition: types.h:215
Configuration object for an aktualizr instance running on a Primary ECU.
Definition: config.h:210
RAII Temporary file creation.
Definition: utils.h:68
The BasedPath class Can represent an absolute or relative path, only readable through the BasePath::g...
Definition: types.h:29