/* * fetch.cc - Implementation of the HorizonScript Locator * Project Horizon * * Copyright (c) 2019 Adélie Linux and contributors. All rights reserved. * This code is licensed under the AGPL 3.0 license, as noted in the * LICENSE-code file in the root directory of this repository. * * SPDX-License-Identifier: AGPL-3.0-only */ #include /* transform */ #include /* EXIT_* */ #ifdef HAVE_LIBCURL # include /* strerror */ # include /* curl_* */ # include /* errno */ #endif /* HAVE_LIBCURL */ #include #include #include #include /* access */ #include "util/output.hh" static const char *IFILE_PATH = "/etc/horizon/installfile"; static const int SCRIPT_LINE_MAX = 512; bool pretty = true; /*! Process a local path and copy it to the proper location. * @param path The local path. * @returns An exit code. */ int process_local(const std::string &path) { /* if we can't read it, don't even bother */ if(access(path.c_str(), R_OK) != 0) { output_error("process_local", std::string(path) + " does not exist or is not readable"); return EXIT_FAILURE; } std::ofstream output(IFILE_PATH, std::ios_base::trunc); if(!output) { output_error("process_local", std::string(IFILE_PATH) + " could not be opened for writing"); return EXIT_FAILURE; } std::ifstream input(path, std::ios_base::in); if(!input) { output_error("process_local", path + " could not be opened for reading"); return EXIT_FAILURE; } char buffer[SCRIPT_LINE_MAX]; while(input.getline(buffer, SCRIPT_LINE_MAX)) { output << buffer << std::endl; } if(input.fail() && !input.eof()) { output_error("process_local", "line too long while reading " + path); return EXIT_FAILURE; } if(input.bad() && !input.eof()) { output_error("process_local", "I/O error reading " + path); return EXIT_FAILURE; } return EXIT_SUCCESS; } #ifdef HAVE_LIBCURL /*! Download an installfile using cURL. * @param path The remote path to download. */ int process_curl(const std::string &path) { CURL *curl = curl_easy_init(); CURLcode result; int return_code = EXIT_FAILURE; char errbuf[CURL_ERROR_SIZE]; FILE *fp; if(curl == nullptr) { output_error("internal", "trouble initialising cURL library"); return EXIT_FAILURE; } fp = fopen(IFILE_PATH, "w"); if(fp == nullptr) { output_error("internal", "couldn't open " + std::string(IFILE_PATH) + " for writing", strerror(errno)); curl_easy_cleanup(curl); return EXIT_FAILURE; } curl_easy_setopt(curl, CURLOPT_URL, path.c_str()); curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf); curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); result = curl_easy_perform(curl); if(result == CURLE_OK) { return_code = EXIT_SUCCESS; } else { output_error("curl", "couldn't download installfile", errbuf); } curl_easy_cleanup(curl); return return_code; } #endif /* HAVE_LIBCURL */ /*! The signature of a protocol dispatch function */ typedef int (*proto_dispatch_fn)(const std::string &); /*! Protocol handlers */ static const std::map protos = { #ifdef HAVE_LIBCURL {"http", process_curl}, {"https", process_curl}, {"tftp", process_curl}, #endif /* HAVE_LIBCURL */ }; /*! Dispatch handling of +path+ to a protocol handler, or return failure. * @param path The remote path to download to IFILE_PATH. * @returns EXIT_SUCCESS if the file was downloaded; EXIT_FAILURE otherwise. * @note Currently, it is ambiguous whether EXIT_FAILURE means invalid proto * or error downloading from the URL. */ int process_maybe_remote(const std::string &path) { std::string proto = path.substr(0, path.find_first_of(":")); std::transform(proto.begin(), proto.end(), proto.begin(), ::tolower); if(protos.find(proto) != protos.end()) { return (*protos.at(proto))(path); } std::string support = "This build of Horizon supports: "; for(auto &proto_pair : protos) { support += proto_pair.first + " "; } output_error("argv", proto + " is not a valid protocol", support); return EXIT_FAILURE; } /*! Figure out the TFTP path(s) to try, and use them */ int full_auto_tftp() { output_error("tftp", "stub!"); return EXIT_FAILURE; } /* * The goal of the Locator is to find the HorizonScript for this computer, * which is the target, and copy it to the well-known path /etc/horizon * as 'installfile'. */ int main(int argc, char *argv[]) { std::string installfile; if(argc < 1 || argc > 2) { std::cerr << "usage: " << std::endl; std::cerr << "\thscript-fetch [path|url]" << std::endl; return EXIT_FAILURE; } bold_if_pretty(std::cout); std::cout << "HorizonScript Locator version " << VERSTR; reset_if_pretty(std::cout); std::cout << std::endl << "Copyright (c) 2019-2020 Adélie Linux and contributors." << std::endl << "This software is licensed to you under the terms of the " << std::endl << "AGPL 3.0 license, unless otherwise noted." << std::endl << std::endl; if(argc == 1) { if(access(IFILE_PATH, F_OK) == 0) { /* That was easy™ */ std::cout << "HorizonScript already present at " << IFILE_PATH << ". Quitting." << std::endl; return EXIT_SUCCESS; } if(protos.find("tftp") == protos.end()) { output_error("internal", "A Fully Automatic installation requires " "Horizon to be built with TFTP support"); return EXIT_FAILURE; } else { return full_auto_tftp(); } } std::string path(argv[1]); if(path == "--version") { /* Banner printed above is our version statement. */ return EXIT_SUCCESS; } if(path[0] == '/') { return process_local(path); } if(path.find("://") != std::string::npos) { return process_maybe_remote(path); } output_error("argv", "Unrecognised protocol or invalid URL", "An absolute path or URL is required"); return EXIT_FAILURE; }