_ _
| |_ ___| |_ _
| | . | | | |
|_|_|___|_|_ |
|___|
git mirror - github.com/owenewans/holy - branch master
file backends/apt-release.c
#define _DEFAULT_SOURCE
#define _POSIX_C_SOURCE 200809L
#include "apt-release.h"
#include "apt.h"
#include "../src/fetch.h"
#include "../src/stage.h"
#include "../src/source.h"
#include <curl/curl.h>
#include <openssl/evp.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <zlib.h>
static int hex_hash(const char *value)
{
return value && strlen(value) == 64 && strspn(value, "0123456789abcdef") == 64;
}
static int segment(const char *value)
{
size_t i, size = value ? strlen(value) : 0;
if (!size || size > 128 || !strcmp(value, ".") || !strcmp(value, "..")) return 0;
for (i = 0; i < size; ++i)
if (!((value[i] >= 'a' && value[i] <= 'z') ||
(value[i] >= 'A' && value[i] <= 'Z') ||
(value[i] >= '0' && value[i] <= '9') ||
value[i] == '.' || value[i] == '_' || value[i] == '-')) return 0;
return 1;
}
static char *path_name(const char *directory, const char *name)
{
size_t a = strlen(directory), b = strlen(name);
char *path;
if (a > SIZE_MAX - b - 2 || !(path = malloc(a + b + 2))) return NULL;
memcpy(path, directory, a);
path[a] = '/';
memcpy(path + a + 1, name, b + 1);
return path;
}
static char *child_directory(const char *base, const char *name)
{
char *path = holy_fetch_child_url(base, name), *next;
size_t size;
if (!path) return NULL;
size = strlen(path);
next = realloc(path, size + 2);
if (!next) { free(path); return NULL; }
next[size] = '/'; next[size + 1] = 0;
return next;
}
static int hash_file(const char *path, char hash[65], off_t *size, off_t limit)
{
unsigned char bytes[65536], result[32];
unsigned int count;
EVP_MD_CTX *ctx = NULL;
struct stat st;
ssize_t got;
int fd = open(path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK);
int ok = 0;
size_t i;
if (fd < 0 || fstat(fd, &st) || !S_ISREG(st.st_mode) || st.st_size < 0 ||
st.st_size > limit || !(ctx = EVP_MD_CTX_new()) ||
EVP_DigestInit_ex(ctx, EVP_sha256(), NULL) != 1) goto done;
for (;;) {
got = read(fd, bytes, sizeof bytes);
if (got < 0 && errno == EINTR) continue;
if (got <= 0) break;
if (EVP_DigestUpdate(ctx, bytes, (size_t)got) != 1) goto done;
}
if (got < 0 || EVP_DigestFinal_ex(ctx, result, &count) != 1 || count != 32) goto done;
for (i = 0; i < 32; ++i) snprintf(hash + i * 2, 3, "%02x", result[i]);
if (size) *size = st.st_size;
ok = 1;
done:
EVP_MD_CTX_free(ctx);
if (fd >= 0) close(fd);
return ok;
}
int holy_apt_key_fingerprint(const char *keyring, char hash[65])
{
return keyring && hash && hash_file(keyring, hash, NULL, 1024 * 1024);
}
static int copy_file(const char *source, const char *target, off_t limit)
{
struct stat st;
char buffer[65536];
ssize_t got;
int input = open(source, O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK);
int output = -1, ok = 0;
if (input < 0 || fstat(input, &st) || !S_ISREG(st.st_mode) ||
st.st_size < 0 || st.st_size > limit) goto done;
output = open(target, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, 0600);
if (output < 0) goto done;
for (;;) {
size_t written = 0;
got = read(input, buffer, sizeof buffer);
if (got < 0 && errno == EINTR) continue;
if (got <= 0) break;
while (written < (size_t)got) {
ssize_t n = write(output, buffer + written, (size_t)got - written);
if (n < 0 && errno == EINTR) continue;
if (n <= 0) goto done;
written += (size_t)n;
}
}
ok = !got && !fsync(output);
done:
if (input >= 0) close(input);
if (output >= 0) close(output);
if (!ok) unlink(target);
return ok;
}
static int valid_contents(const char *path)
{
char bytes[65536];
unsigned char magic[2];
size_t total = 0;
int fd = open(path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
gzFile input;
int got, status, ok = 0;
if (fd < 0) return 0;
if (read(fd, magic, sizeof magic) != (ssize_t)sizeof magic ||
magic[0] != 0x1f || magic[1] != 0x8b || lseek(fd, 0, SEEK_SET) < 0) {
close(fd); return 0;
}
input = gzdopen(fd, "rb");
if (!input) { close(fd); return 0; }
for (;;) {
got = gzread(input, bytes, sizeof bytes);
if (got < 0 || total > 512u * 1024u * 1024u - (size_t)got) break;
if (!got) { ok = total > 0; break; }
total += (size_t)got;
}
gzerror(input, &status);
if (status != Z_OK && status != Z_STREAM_END) ok = 0;
if (gzclose(input) != Z_OK) ok = 0;
return ok;
}
static int verify_signature(const char *key, const char *signature, const char *release)
{
pid_t child = fork();
int status;
if (child < 0) return 1;
if (!child) {
execlp("gpgv", "gpgv", "--quiet", "--keyring", key,
signature, release, (char *)NULL);
_exit(127);
}
while (waitpid(child, &status, 0) < 0) {
if (errno != EINTR) return 1;
}
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) return 0;
if (WIFEXITED(status) && WEXITSTATUS(status) == 127) return 6;
return 4;
}
static int verify_inline(const char *key, const char *inrelease)
{
pid_t child = fork();
int status;
if (child < 0) return 1;
if (!child) {
execlp("gpgv", "gpgv", "--quiet", "--keyring", key,
inrelease, (char *)NULL);
_exit(127);
}
while (waitpid(child, &status, 0) < 0) {
if (errno != EINTR) return 1;
}
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) return 0;
if (WIFEXITED(status) && WEXITSTATUS(status) == 127) return 6;
return 4;
}
static int extract_inline(const char *input, const char *output)
{
int fd = open(input, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
int out = -1, ok = 0, stage = 0;
FILE *file = NULL;
char *line = NULL;
size_t capacity = 0, total = 0;
ssize_t length;
struct stat st;
if (fd < 0 || fstat(fd, &st) || !S_ISREG(st.st_mode) ||
st.st_size > 8 * 1024 * 1024) goto done;
file = fdopen(fd, "r");
if (!file) goto done;
fd = -1;
out = open(output, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, 0600);
if (out < 0) goto done;
while ((length = getline(&line, &capacity, file)) >= 0) {
size_t n = (size_t)length;
if (!n || n > 65536 || memchr(line, 0, n) || line[n - 1] != '\n' ||
(n >= 2 && line[n - 2] == '\r')) goto done;
if (stage == 0) {
if (strcmp(line, "-----BEGIN PGP SIGNED MESSAGE-----\n")) goto done;
stage = 1;
} else if (stage == 1) {
if (n == 1) stage = 2;
else if (strncmp(line, "Hash: ", 6) || n > 128) goto done;
} else if (stage == 2) {
const char *body = line;
size_t written = 0;
if (!strcmp(line, "-----BEGIN PGP SIGNATURE-----\n")) { stage = 3; continue; }
if (line[0] == '-' && (n < 2 || line[1] != ' ')) goto done;
if (n >= 2 && line[0] == '-' && line[1] == ' ') { body += 2; n -= 2; }
if (n >= 2 && (body[n - 2] == ' ' || body[n - 2] == '\t')) goto done;
if (total > 4 * 1024 * 1024 - n) goto done;
while (written < n) {
ssize_t count = write(out, body + written, n - written);
if (count < 0 && errno == EINTR) continue;
if (count <= 0) goto done;
written += (size_t)count;
}
total += n;
} else if (stage == 3) {
if (!strcmp(line, "-----END PGP SIGNATURE-----\n")) stage = 4;
else if (n > 128) goto done;
} else goto done;
}
ok = !ferror(file) && stage == 4 && !fsync(out);
done:
free(line);
if (file) fclose(file);
if (fd >= 0) close(fd);
if (out >= 0) close(out);
if (!ok) unlink(output);
return ok;
}
static int release_entry(const char *release, const char *path, const char *suite,
char hash[65], off_t *size)
{
int fd = open(release, O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK);
FILE *stream = NULL;
struct stat st;
char *line = NULL;
size_t capacity = 0, lines = 0;
ssize_t length;
int in_sha256 = 0, seen_section = 0, found = 0, suite_match = 0;
int valid_until_seen = 0, ok = 0;
time_t now = time(NULL);
if (fd < 0 || fstat(fd, &st) || !S_ISREG(st.st_mode) ||
st.st_size > 4 * 1024 * 1024 || now == (time_t)-1) goto done;
stream = fdopen(fd, "r");
if (!stream) goto done;
fd = -1;
while ((length = getline(&line, &capacity, stream)) >= 0) {
char digest[65], filename[1025], trailing;
unsigned long long amount;
if (++lines > 100000 || length > 65536 || memchr(line, 0, (size_t)length)) goto done;
if (!strncasecmp(line, "Valid-Until:", 12)) {
time_t deadline = curl_getdate(line + 12, NULL);
if (valid_until_seen++ || deadline == (time_t)-1 || deadline < now) goto done;
}
if ((!strncasecmp(line, "Suite:", 6) && line[6] == ' ') ||
(!strncasecmp(line, "Codename:", 9) && line[9] == ' ')) {
const char *value = line + (line[0] == 'S' || line[0] == 's' ? 6 : 9);
size_t length;
while (*value == ' ' || *value == '\t') ++value;
length = strcspn(value, "\r\n");
if (length == strlen(suite) && !memcmp(value, suite, length))
suite_match = 1;
}
if (!strncasecmp(line, "SHA256:", 7)) {
if (seen_section++) goto done;
in_sha256 = 1;
continue;
}
if (line[0] != ' ' && line[0] != '\t') in_sha256 = 0;
if (!in_sha256) continue;
if (sscanf(line, " %64s %llu %1024s %c", digest, &amount, filename, &trailing) != 3 ||
!hex_hash(digest)) goto done;
if (!strcmp(filename, path)) {
if (found++ || amount > 64ULL * 1024 * 1024) goto done;
strcpy(hash, digest);
*size = (off_t)amount;
}
}
ok = !ferror(stream) && seen_section == 1 && found == 1 && suite_match;
done:
if (stream) fclose(stream);
if (fd >= 0) close(fd);
free(line);
return ok;
}
int holy_apt_verify_release(const char *catalog, const char *index_hash)
{
char *proof = path_name(catalog, "release-proof");
char *release = NULL, *signature = NULL, *key = NULL, *original = NULL;
char *extracted = NULL;
char release_hash[65], key_hash[65], path[1025], suite[129], actual[65];
char stated_release[65], stated_key[65], stated_signature[65], extra[128];
char scratch[] = "/tmp/holy-apt-verify-XXXXXX";
struct stat st;
FILE *stream = NULL;
off_t expected_size, actual_size;
int result = -1, fd, inline_signature = 0, scratch_created = 0;
if (!proof) return -1;
fd = open(proof, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
if (fd < 0 && errno == ENOENT) { result = 0; goto done; }
if (fd < 0 || fstat(fd, &st) || !S_ISREG(st.st_mode) || st.st_size > 2048) {
if (fd >= 0) close(fd);
goto done;
}
stream = fdopen(fd, "r");
if (!stream) { close(fd); goto done; }
release = path_name(catalog, "release");
if (!release || fscanf(stream, "release-sha256 %64s\nkey-sha256 %64s\nsuite %128s\nindex-path %1024s\n",
stated_release, stated_key, suite, path) != 4 ||
!hex_hash(stated_release) || !hex_hash(stated_key) ||
!segment(suite) ||
!strchr(path, '/') || path[0] == '/' || strstr(path, "..")) goto done;
if (fgets(extra, sizeof extra, stream)) {
if (strcmp(extra, "signature-kind inrelease\n") ||
fscanf(stream, "signature-sha256 %64s\n", stated_signature) != 1 ||
!hex_hash(stated_signature) || fgetc(stream) != EOF) goto done;
inline_signature = 1;
} else if (ferror(stream)) goto done;
if (!release_entry(release, path, suite, actual, &expected_size) ||
strcmp(actual, index_hash)) goto done;
signature = path_name(catalog, inline_signature ? "inrelease" : "release.gpg");
key = path_name(catalog, "keyring");
original = path_name(catalog, "original");
if (!release || !signature || !key || !original ||
!hash_file(release, release_hash, NULL, 4 * 1024 * 1024) ||
strcmp(release_hash, stated_release) ||
!hash_file(signature, actual, NULL,
inline_signature ? 8 * 1024 * 1024 : 1024 * 1024) ||
(inline_signature && strcmp(actual, stated_signature)) ||
!hash_file(key, key_hash, NULL, 1024 * 1024) ||
strcmp(key_hash, stated_key) ||
!hash_file(original, actual, &actual_size, 64 * 1024 * 1024) ||
strcmp(actual, index_hash) || actual_size != expected_size) goto done;
if (inline_signature) {
if (!mkdtemp(scratch)) goto done;
scratch_created = 1;
extracted = path_name(scratch, "release");
if (!extracted) goto done;
{
int verified = verify_inline(key, signature);
if (verified) { if (verified == 6) result = -2; goto done; }
}
if (!extract_inline(signature, extracted)) goto done;
if (!hash_file(extracted, actual, NULL, 4 * 1024 * 1024) ||
strcmp(actual, stated_release)) goto done;
result = 2;
} else {
int verified = verify_signature(key, signature, release);
if (verified) { if (verified == 6) result = -2; goto done; }
result = 1;
}
done:
if (stream) fclose(stream);
if (extracted) unlink(extracted);
if (scratch_created) rmdir(scratch);
free(extracted);
free(proof); free(release); free(signature); free(key); free(original);
return result;
}
int holy_apt_verify_files(const char *catalog)
{
char *proof = path_name(catalog, "file-proof"), *release_proof = NULL;
char *release = NULL, *contents = NULL;
char suite[129], path[1025], stated_hash[65], expected_hash[65], actual[65];
char package_path[1025], release_suite[129], component[129], arch[129];
char release_hash[65], key_hash[65], wanted[512], trailing;
unsigned long long stated_size, retrieved_at;
struct stat st;
FILE *file = NULL, *bound = NULL;
off_t expected_size, actual_size;
int fd, bound_fd = -1, result = -1;
if (!proof) return -1;
fd = open(proof, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
if (fd < 0 && errno == ENOENT) { result = 0; goto done; }
if (fd < 0 || fstat(fd, &st) || !S_ISREG(st.st_mode) || st.st_size > 2048) {
if (fd >= 0) close(fd);
goto done;
}
file = fdopen(fd, "r");
if (!file) { close(fd); goto done; }
if (fscanf(file, "suite %128s\nfile-index-path %1024s\nfile-index-sha256 %64s\nfile-index-size %llu\nfile-index-coverage partial\nretrieved-at %llu\n",
suite, path, stated_hash, &stated_size, &retrieved_at) != 5 ||
fgetc(file) != EOF || !retrieved_at ||
!segment(suite) || !hex_hash(stated_hash) || path[0] == '/' ||
!strchr(path, '/') || strstr(path, "..") || stated_size > 64ULL * 1024 * 1024)
goto done;
release_proof = path_name(catalog, "release-proof");
if (!release_proof ||
(bound_fd = open(release_proof, O_RDONLY | O_NOFOLLOW | O_CLOEXEC)) < 0 ||
fstat(bound_fd, &st) || !S_ISREG(st.st_mode) || st.st_size > 2048 ||
!(bound = fdopen(bound_fd, "r")) ||
fscanf(bound, "release-sha256 %64s\nkey-sha256 %64s\nsuite %128s\nindex-path %1024s\n",
release_hash, key_hash, release_suite, package_path) != 4 ||
strcmp(suite, release_suite) ||
sscanf(package_path, "%128[^/]/binary-%128[^/]/Packages.gz%c",
component, arch, &trailing) != 2 ||
!segment(component) || !segment(arch) ||
snprintf(wanted, sizeof wanted, "%s/Contents-%s.gz", component, arch) >=
(int)sizeof wanted || strcmp(path, wanted)) goto done;
release = path_name(catalog, "release");
contents = path_name(catalog, "contents.gz");
if (!release || !contents ||
!release_entry(release, path, suite, expected_hash, &expected_size) ||
strcmp(stated_hash, expected_hash) || stated_size != (unsigned long long)expected_size ||
!hash_file(contents, actual, &actual_size, 64 * 1024 * 1024) ||
strcmp(actual, expected_hash) || actual_size != expected_size) goto done;
result = 1;
done:
if (file) fclose(file);
if (bound) fclose(bound);
else if (bound_fd >= 0) close(bound_fd);
free(proof); free(release_proof); free(release); free(contents);
return result;
}
static int sync_release(const char *base, const char *suite,
const char *component, const char *arch,
const char *source, const char *keyring,
const char *output, const char *ca_file,
const char *root, const char *source_id,
const char *source_key, const char *source_trust,
int inrelease, int files)
{
char *staging = NULL, *catalog = NULL, *key_copy = NULL;
char *dists = NULL, *suite_url = NULL, *component_url = NULL, *arch_url = NULL;
char *release_url = NULL, *signature_url = NULL, *index_url = NULL, *files_url = NULL;
char *release = NULL, *signature = NULL, *index = NULL, *contents = NULL, *target = NULL;
char release_hash[65] = {0}, signature_hash[65] = {0}, index_hash[65] = {0};
char key_hash[65], expected_hash[65], files_hash[65] = {0};
char relative[512], files_relative[512], subdir[160], files_name[160];
struct stat st;
off_t expected_size, actual_size, files_size = 0;
FILE *proof = NULL;
int dir = -1, parent_fd = -1, result = 1, published = 0;
char *parent = NULL;
if (!base || !segment(suite) || !segment(component) || !segment(arch) ||
!segment(source) || !strcmp(source, "local") || !keyring || !output ||
!*output || (lstat(output, &st) == 0 || errno != ENOENT)) return 2;
{
const char *slash = strrchr(output, '/');
parent = slash ? strndup(output, slash == output ? 1 : (size_t)(slash - output)) : strdup(".");
if (!parent) return 1;
parent_fd = open(parent, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
if (parent_fd < 0) { result = 6; goto done; }
}
dists = child_directory(base, "dists");
suite_url = dists ? child_directory(dists, suite) : NULL;
component_url = suite_url ? child_directory(suite_url, component) : NULL;
if (snprintf(subdir, sizeof subdir, "binary-%s", arch) >= (int)sizeof subdir ||
snprintf(relative, sizeof relative, "%s/%s/Packages.gz", component, subdir) >=
(int)sizeof relative ||
snprintf(files_name, sizeof files_name, "Contents-%s.gz", arch) >= (int)sizeof files_name ||
snprintf(files_relative, sizeof files_relative, "%s/%s", component, files_name) >=
(int)sizeof files_relative) { result = 2; goto done; }
arch_url = component_url ? child_directory(component_url, subdir) : NULL;
release_url = !inrelease && suite_url ? holy_fetch_child_url(suite_url, "Release") : NULL;
signature_url = suite_url ? holy_fetch_child_url(suite_url,
inrelease ? "InRelease" : "Release.gpg") : NULL;
index_url = arch_url ? holy_fetch_child_url(arch_url, "Packages.gz") : NULL;
files_url = files && component_url ? holy_fetch_child_url(component_url, files_name) : NULL;
if (!index_url || (!inrelease && !release_url) || !signature_url ||
(files && !files_url)) { result = 2; goto done; }
staging = malloc(strlen(output) + sizeof ".tmp-XXXXXX");
if (!staging) goto done;
sprintf(staging, "%s.tmp-XXXXXX", output);
if (!mkdtemp(staging)) goto done;
if (!inrelease) {
result = holy_fetch_https_foreign_limited(release_url, staging, ca_file,
release_hash, 4 * 1024 * 1024);
if (result) goto done;
release = path_name(staging, release_hash);
}
result = holy_fetch_https_foreign_limited(signature_url, staging, ca_file,
signature_hash,
inrelease ? 8 * 1024 * 1024 : 1024 * 1024);
if (result) goto done;
signature = path_name(staging, signature_hash);
key_copy = holy_stage_local(keyring, "holy-apt-key");
if (!key_copy || (!inrelease && !release) || !signature ||
!hash_file(key_copy, key_hash, NULL, 1024 * 1024)) { result = 6; goto done; }
if (source_key && strcmp(source_key, key_hash)) { result = 4; goto done; }
if (inrelease) release = path_name(staging, "release-clear");
if (!release) { result = 1; goto done; }
result = inrelease ? verify_inline(key_copy, signature) :
verify_signature(key_copy, signature, release);
if (result) goto done;
if (inrelease && !extract_inline(signature, release)) { result = 4; goto done; }
if (inrelease && !hash_file(release, release_hash, NULL, 4 * 1024 * 1024)) {
result = 4; goto done;
}
if (!release_entry(release, relative, suite, expected_hash, &expected_size)) {
result = 4; goto done;
}
if (files && !release_entry(release, files_relative, suite, files_hash, &files_size)) {
result = 4; goto done;
}
result = holy_fetch_https_foreign_limited(index_url, staging, ca_file,
index_hash, 64 * 1024 * 1024);
if (result) goto done;
index = path_name(staging, index_hash);
if (!index || !hash_file(index, index_hash, &actual_size, 64 * 1024 * 1024) ||
strcmp(index_hash, expected_hash) || actual_size != expected_size) {
result = 4; goto done;
}
if (files) {
char received[65];
result = holy_fetch_https_foreign_limited(files_url, staging, ca_file,
received, 64 * 1024 * 1024);
if (result) goto done;
contents = path_name(staging, received);
if (!contents || strcmp(received, files_hash) ||
!hash_file(contents, received, &actual_size, 64 * 1024 * 1024) ||
actual_size != files_size || !valid_contents(contents)) {
result = 4; goto done;
}
}
catalog = path_name(staging, "catalog");
if (!catalog) { result = 1; goto done; }
result = holy_apt_index_quiet(index, expected_hash, source, base, catalog);
if (result) goto done;
target = path_name(catalog, "release");
if (!target || !copy_file(release, target, 4 * 1024 * 1024)) { result = 1; goto done; }
free(target); target = path_name(catalog, inrelease ? "inrelease" : "release.gpg");
if (!target || !copy_file(signature, target,
inrelease ? 8 * 1024 * 1024 : 1024 * 1024)) { result = 1; goto done; }
free(target); target = path_name(catalog, "keyring");
if (!target || !copy_file(key_copy, target, 1024 * 1024)) { result = 1; goto done; }
free(target); target = path_name(catalog, "release-proof");
if (!target || !(proof = fopen(target, "wx"))) { result = 1; goto done; }
fprintf(proof, "release-sha256 %s\nkey-sha256 %s\nsuite %s\nindex-path %s\n",
release_hash, key_hash, suite, relative);
if (inrelease)
fprintf(proof, "signature-kind inrelease\nsignature-sha256 %s\n", signature_hash);
{
int failed = fflush(proof) || fsync(fileno(proof));
if (fclose(proof)) failed = 1;
proof = NULL;
if (failed) { result = 1; goto done; }
}
{
int verified = holy_apt_verify_release(catalog, expected_hash);
if (verified != (inrelease ? 2 : 1)) {
result = verified == -2 ? 6 : 4; goto done;
}
}
if (files) {
time_t retrieved_at = time(NULL);
if (retrieved_at == (time_t)-1) { result = 1; goto done; }
free(target); target = path_name(catalog, "contents.gz");
if (!target || !copy_file(contents, target, 64 * 1024 * 1024)) {
result = 1; goto done;
}
free(target); target = path_name(catalog, "file-proof");
if (!target || !(proof = fopen(target, "wx"))) { result = 1; goto done; }
fprintf(proof, "suite %s\nfile-index-path %s\nfile-index-sha256 %s\nfile-index-size %lld\nfile-index-coverage partial\nretrieved-at %lld\n",
suite, files_relative, files_hash, (long long)files_size,
(long long)retrieved_at);
{
int failed = ferror(proof) || fflush(proof) || fsync(fileno(proof));
if (fclose(proof)) failed = 1;
proof = NULL;
if (failed || holy_apt_verify_files(catalog) != 1) {
result = 4; goto done;
}
}
}
if (source_id) {
char current_id[65], current_key[65];
char *current_base = NULL, *current_trust = NULL;
result = holy_source_apt(root, source, current_id, ¤t_base,
¤t_trust, current_key);
if (!result && (strcmp(current_id, source_id) ||
strcmp(current_key, source_key) ||
strcmp(current_base, base) ||
strcmp(current_trust, source_trust))) result = 3;
free(current_base); free(current_trust);
if (result) goto done;
}
free(target); target = path_name(catalog, "conversion");
if (!target) { result = 1; goto done; }
{
int marker = open(target, O_WRONLY | O_APPEND | O_NOFOLLOW | O_CLOEXEC);
char line[128];
int length = source_id ? snprintf(line, sizeof line,
"release-required yes\nsource-id %s\n", source_id) :
snprintf(line, sizeof line, "release-required yes\n");
if (marker < 0 || length < 0 || length >= (int)sizeof line ||
write(marker, line, (size_t)length) != length ||
fsync(marker)) {
if (marker >= 0) close(marker);
result = 1; goto done;
}
close(marker);
}
dir = open(catalog, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
if (dir < 0 || fsync(dir)) { result = 1; goto done; }
#ifdef SYS_renameat2
if (syscall(SYS_renameat2, AT_FDCWD, catalog, AT_FDCWD, output, 1u)) {
result = errno == EEXIST ? 2 : 6; goto done;
}
#else
result = 6; goto done;
#endif
published = 1;
if (fsync(parent_fd)) { result = 1; goto done; }
printf("apt signed catalog %s release %s index %s\n", output, release_hash, index_hash);
result = 0;
done:
if (proof) fclose(proof);
if (dir >= 0) close(dir);
if (parent_fd >= 0) close(parent_fd);
if (!published && catalog) {
const char *files[] = {"original", "conversion", "release", "release.gpg",
"inrelease", "keyring", "release-proof", "contents.gz", "file-proof"};
size_t i;
for (i = 0; i < sizeof files / sizeof files[0]; ++i) {
char *name = path_name(catalog, files[i]);
if (name) { unlink(name); free(name); }
}
rmdir(catalog);
}
if (release) unlink(release);
if (signature) unlink(signature);
if (index) unlink(index);
if (contents) unlink(contents);
if (staging) rmdir(staging);
if (key_copy) { unlink(key_copy); free(key_copy); }
free(parent); free(staging); free(catalog); free(dists); free(suite_url); free(component_url);
free(arch_url); free(release_url); free(signature_url); free(index_url); free(files_url);
free(release); free(signature); free(index); free(contents); free(target);
if (result) fprintf(stderr, "holypkg: APT signed sync failed (status %d)\n", result);
return result;
}
int holy_apt_release_sync(const char *base, const char *suite,
const char *component, const char *arch,
const char *source, const char *keyring,
const char *output, const char *ca_file, int inrelease,
int files)
{
return sync_release(base, suite, component, arch, source, keyring,
output, ca_file, NULL, NULL, NULL, NULL, inrelease, files);
}
int holy_apt_release_sync_source(const char *root, const char *alias,
const char *suite, const char *component,
const char *arch, const char *keyring,
const char *output, const char *ca_file,
int inrelease, int files)
{
char id[65], key[65], actual[65];
char *base = NULL, *trust = NULL, *snapshot = NULL;
int result = holy_source_apt(root, alias, id, &base, &trust, key);
if (result) goto done;
snapshot = keyring ? holy_stage_local(keyring, "holy-apt-source-key") : NULL;
if (!key[0] || !snapshot || !holy_apt_key_fingerprint(snapshot, actual)) {
result = 6; goto done;
}
if (strcmp(key, actual)) { result = 4; goto done; }
result = sync_release(base, suite, component, arch, alias, snapshot,
output, ca_file, root, id, key, trust, inrelease, files);
done:
if (snapshot) { unlink(snapshot); free(snapshot); }
free(base); free(trust);
return result;
}