#include "download_db.h" #include "util.h" #include #include #include #include #include #include // note: fixed location of download database for now const char download_db_path[] = "./var/snag/downloads"; // libcrpyto context EVP_MD_CTX *mdctx = NULL; const EVP_MD *md = NULL; // curl context CURL *curl = NULL; static size_t download_write_callback(char* ptr, size_t size, size_t nmemb, void* usrdata) { // write the curl data to the file in usr data size_t written = fwrite(ptr, size, nmemb, (FILE *)usrdata); // update the hash with the new data EVP_DigestUpdate(mdctx, ptr, written); return written; } bool package_get_download(package_info_t* pkg, package_download_t* download) { memset(download, 0, sizeof(*download)); // construct path of download char* download_path = vastrcat(download_db_path, "/", pkg->attrs.id, "-", pkg->attrs.version, ".tar.gz"); // return download if it already exists in database if (access(download_path, F_OK) == 0) { download->download_path = download_path; return true; } else { print("Downloading %s\n", pkg->attrs.source_url); } // initialize curl if it isn't already if (curl == NULL) { CURLcode result = curl_global_init(CURL_GLOBAL_ALL); if (result != CURLE_OK) die("failed to initialize curl: %s", curl_easy_strerror(result)); curl = curl_easy_init(); // note(jqj): for now we reuse one easy curl object if (curl == NULL) die("failed to initialize easy curl"); /* come back to this if we have init and deinit */ /* curl_easy_cleanup(curl); */ /* curl_global_cleanup(); */ } // initialize libcrypto context if it isn't already if (mdctx == NULL) { mdctx = EVP_MD_CTX_new(); md = EVP_sha256(); /* come back to this if we have init and deinit */ /* EVP_MD_CTX_free(mdctx); */ } // initialize up sha256 hash EVP_DigestInit_ex(mdctx, md, NULL); // open file for writing // note(jqj): extension is fixed to tar gz until the switch out download // attrs for the swenu api download command which will specify // more information FILE* download_file = fopen(download_path, "wb"); if (download_file == NULL) die("failed open download file for writing (%s): %s", download_path, strerror(errno)); // make curl request with url and output file // callback writes to the file and feeds the hash curl_easy_setopt(curl, CURLOPT_URL, pkg->attrs.source_url); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, download_write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, download_file); CURLcode result = curl_easy_perform(curl); if(result != CURLE_OK) { // todo(jqj): handle better, maybe try again print("failed to download package with curl: %s", curl_easy_strerror(result)); goto fail; } // get sha256 hash unsigned char hash[SHA256_DIGEST_LENGTH]; unsigned int hash_len; EVP_DigestFinal_ex(mdctx, hash, &hash_len); // make hexadecimal string of sha256 hash char hash_hex[SHA256_DIGEST_LENGTH * 2 + 1]; for(int i = 0; i < SHA256_DIGEST_LENGTH; i++) { sprintf(hash_hex + i*2,"%02x", hash[i]); } hash_hex[SHA256_DIGEST_LENGTH * 2] = '\0'; // todo(jqj): collapse codepath and delete download if it doesn't match checksum // confirm that hash matches checksum if (pkg->attrs.source_checksum != NULL) { if (strcmp(pkg->attrs.source_checksum, hash_hex) == 0) { print("checksum verified for download: %s\n", hash_hex); download->download_path = download_path; return true; } else { print("FAIL: bad checksum for download: %s\n", hash_hex); goto fail; } } else { // todo(jqj): not sure if we will allow packages without hashes yet print("WARNING: could not verify checksum for %s download because none was provided\n", pkg->attrs.id); goto fail; } fail: free(download_path); return false; }