1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
#include "source_db.h"
#include "util.h"
#include <curl/curl.h>
#include <openssl/evp.h>
#include <openssl/sha.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
// libcrpyto context
EVP_MD_CTX *mdctx = NULL;
const EVP_MD *md = NULL;
void ensure_libcrypto();
// curl context
CURL *curl = NULL;
void ensure_libcurl();
// note: fixed location of source database for now
const char source_db_path[] = "./var/snag/sources";
char* get_source_path(source_info_t* source) { return join_path(source_db_path, source->name); }
bool verify_source(source_info_t* source) {
// verify source exists
char* source_path = get_source_path(source);
bool verified = access(source_path, F_OK) == 0;
// todo(jqj): actually verify checksum
free(source_path);
return verified;
}
static size_t download_write_callback(char* ptr, size_t size, size_t nmemb, void* usrdata);
bool download_source(source_info_t* source) {
char* source_path = get_source_path(source);
print("Downloading %s\n", source->url);
// initialize libcurl and libcrypto if they aren't already
ensure_libcurl();
ensure_libcrypto();
// initialize sha256 hash
EVP_DigestInit_ex(mdctx, md, NULL);
// open file for writing
FILE* download_file = fopen(source_path, "wb");
if (download_file == NULL) die("failed open download file for writing (%s): %s", source_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_WRITEDATA, download_file);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, download_write_callback);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_URL, source->url);
CURLcode result = curl_easy_perform(curl);
fclose(download_file);
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';
// confirm that checksum matches
assert(source->checksum != NULL);
if (strcmp(source->checksum, hash_hex) == 0) {
print("checksum verified for download: %s\n", hash_hex);
return true;
} else {
print("FAIL: bad checksum for download: %s\n", hash_hex);
goto fail;
}
fail:
if (access(source_path, F_OK) == 0) remove(source_path);
free(source_path);
return false;
}
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;
}
void ensure_libcrypto() {
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); */
}
}
void ensure_libcurl() {
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(); */
}
}
|