_ _
| |_ ___| |_ _
| | . | | | |
|_|_|___|_|_ |
|___|
git mirror - github.com/owenewans/holy - branch master
file backends/apt-bind.c
#define _XOPEN_SOURCE 700
#include "apt.h"
#include "../src/config.h"
#include "../src/source.h"
#include "../src/stage.h"
#include "../src/state.h"
#include <openssl/evp.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
static int digest(const char *value)
{
return value && strlen(value) == 64 && strspn(value, "0123456789abcdef") == 64;
}
static int segment(const char *value)
{
size_t i, count = value ? strlen(value) : 0;
if (!count || count > 128 || !strcmp(value, ".") || !strcmp(value, "..")) return 0;
for (i = 0; i < count; ++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 void quote(FILE *out, const char *value)
{
const unsigned char *p = (const unsigned char *)value;
fputc('"', out);
for (; *p; ++p) {
if (*p == '"' || *p == '\\') fprintf(out, "\\%c", *p);
else if (*p < 32 || *p >= 127) fprintf(out, "\\x%02x", *p);
else fputc(*p, out);
}
fputc('"', out);
}
static int binding_name(const char *id, const char *suite,
const char *component, const char *arch, char name[130])
{
char coordinates[512];
unsigned char bytes[32];
unsigned length;
size_t i;
int count;
if (!digest(id) || !segment(suite) || !segment(component) || !segment(arch)) return 0;
count = snprintf(coordinates, sizeof coordinates, "%s\n%s\n%s\n",
suite, component, arch);
if (count < 0 || count >= (int)sizeof coordinates ||
EVP_Digest(coordinates, (size_t)count, bytes, &length, EVP_sha256(), NULL) != 1 ||
length != 32) return 0;
memcpy(name, id, 64);
name[64] = '.';
for (i = 0; i < 32; ++i) snprintf(name + 65 + i * 2, 3, "%02x", bytes[i]);
return 1;
}
static int binding_directory(int database, int create)
{
struct stat st;
int dir;
if (create && mkdirat(database, "apt-catalogs", 0700) && errno != EEXIST) return -1;
if (create && fsync(database)) return -1;
dir = openat(database, "apt-catalogs", O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
if (dir < 0) return -1;
if (fstat(dir, &st) || !S_ISDIR(st.st_mode) ||
(st.st_uid != 0 && st.st_uid != geteuid()) || (st.st_mode & 0022)) {
close(dir); return -1;
}
return dir;
}
static int proof_coordinates(const char *catalog, const char *suite,
const char *component, const char *arch,
char release_hash[65], char key_hash[65])
{
char *path = NULL, saved_suite[129], index_path[1025], wanted[512];
char kind[128], signature_hash[65];
struct stat st;
FILE *file = NULL;
int fd = -1, ok = 0;
if (!segment(suite) || !segment(component) || !segment(arch)) return 0;
if (snprintf(wanted, sizeof wanted, "%s/binary-%s/Packages.gz",
component, arch) >= (int)sizeof wanted) return 0;
path = malloc(strlen(catalog) + sizeof "/release-proof");
if (!path) return 0;
sprintf(path, "%s/release-proof", catalog);
fd = open(path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
if (fd < 0 || fstat(fd, &st) || !S_ISREG(st.st_mode) || st.st_size > 2048) goto done;
file = fdopen(fd, "r");
if (!file) goto done;
fd = -1;
if (fscanf(file, "release-sha256 %64s\nkey-sha256 %64s\nsuite %128s\nindex-path %1024s\n",
release_hash, key_hash, saved_suite, index_path) == 4 &&
digest(release_hash) && digest(key_hash) && !strcmp(saved_suite, suite) &&
!strcmp(index_path, wanted)) {
if (fgets(kind, sizeof kind, file))
ok = !strcmp(kind, "signature-kind inrelease\n") &&
fscanf(file, "signature-sha256 %64s\n", signature_hash) == 1 &&
digest(signature_hash) && fgetc(file) == EOF;
else ok = !ferror(file);
}
done:
if (file) fclose(file);
if (fd >= 0) close(fd);
free(path);
return ok;
}
int holy_apt_bind(const char *root, const char *source, const char *suite,
const char *component, const char *index_arch,
const char *catalog)
{
char id[65], index[65], name[130], release_hash[65], key_hash[65];
char temporary[43] = {0}, *path = NULL, *root_path = NULL;
unsigned long long generation;
FILE *record = NULL;
int database = -1, dir = -1, fd = -1, result = 2;
if (!root || !source || !catalog || !segment(suite) ||
!segment(component) || !segment(index_arch)) return 2;
path = realpath(catalog, NULL);
root_path = realpath(root, NULL);
if (!path || !root_path) { result = 6; goto done; }
result = holy_apt_catalog_identity(path, root, source, id, index);
if (result) goto done;
if (!binding_name(id, suite, component, index_arch, name) ||
!proof_coordinates(path, suite, component, index_arch,
release_hash, key_hash)) { result = 4; goto done; }
database = holy_state_lock(root, 1, &generation, &result);
if (database < 0) goto done;
dir = binding_directory(database, 1);
if (dir < 0) { result = 1; goto done; }
fd = holy_temporary_at(dir, temporary);
if (fd < 0) { result = 1; goto done; }
record = fdopen(fd, "w");
if (!record) { result = 1; goto done; }
fd = -1;
fprintf(record, "format holy-apt-binding-1\nsource-id %s\nsuite %s\ncomponent %s\nindex-arch %s\nindex-sha256 %s\nrelease-sha256 %s\nkey-sha256 %s\n",
id, suite, component, index_arch, index, release_hash, key_hash);
if (strcmp(root_path, "/") && !strncmp(path, root_path, strlen(root_path)) &&
path[strlen(root_path)] == '/') {
fputs("root-path ", record); quote(record, path + strlen(root_path));
} else {
fputs("path ", record); quote(record, path);
}
fputc('\n', record);
{
int failed = ferror(record) || fflush(record);
if (!failed && fsync(fileno(record))) failed = 1;
if (fclose(record)) failed = 1;
record = NULL;
if (failed || renameat(dir, temporary, dir, name) || fsync(dir)) {
result = 1; goto done;
}
}
result = 0;
printf("apt-catalog-bound %s %s %s %s index %s\n",
id, suite, component, index_arch, index);
done:
if (result) fprintf(stderr, "holypkg: APT catalog binding failed (status %d)\n", result);
if (record) fclose(record);
if (fd >= 0) close(fd);
if (dir >= 0) {
if (temporary[0]) unlinkat(dir, temporary, 0);
close(dir);
}
if (database >= 0) close(database);
free(path); free(root_path);
return result;
}
static int read_binding(int dir, const char *name, char *values[9], int *root_relative)
{
static const char *keys[8] = {"format", "source-id", "suite", "component",
"index-arch", "index-sha256", "release-sha256",
"key-sha256"};
struct stat st;
FILE *stream = NULL;
char *line = NULL;
size_t capacity = 0, i;
int fd = openat(dir, name, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
int ok = 0;
if (fd < 0 || fstat(fd, &st) || !S_ISREG(st.st_mode) || st.st_size > 4096 ||
(st.st_uid != 0 && st.st_uid != geteuid()) || (st.st_mode & 0022)) goto done;
stream = fdopen(fd, "r");
if (!stream) goto done;
fd = -1;
for (i = 0; i < 9; ++i) {
char **v = NULL, *error = NULL;
size_t n = 0;
ssize_t got = getline(&line, &capacity, stream);
if (got <= 0 || got > 4096 ||
!holy_lex(line, (size_t)got, &v, &n, "APT binding", 0, &error) || n != 2 ||
(i < 8 ? strcmp(v[0], keys[i]) :
(strcmp(v[0], "path") && strcmp(v[0], "root-path")))) {
holy_tokens_free(v, n); free(error); goto done;
}
if (i == 8) *root_relative = !strcmp(v[0], "root-path");
values[i] = strdup(v[1]);
holy_tokens_free(v, n); free(error);
if (!values[i]) goto done;
}
if (fgetc(stream) != EOF || strcmp(values[0], "holy-apt-binding-1") ||
!digest(values[1]) || !digest(values[5]) || !digest(values[6]) ||
!digest(values[7]) || values[8][0] != '/') goto done;
ok = 1;
done:
if (stream) fclose(stream);
if (fd >= 0) close(fd);
free(line);
return ok;
}
int holy_apt_catalog_path(const char *root, const char *source, const char *suite,
const char *component, const char *index_arch,
char **catalog)
{
char id[65], key[65], checked_id[65], checked_index[65], name[130];
char release_hash[65], key_hash[65], *base = NULL, *trust = NULL;
char *values[9] = {0}, *root_path = NULL, *joined = NULL, *path = NULL;
unsigned long long generation;
size_t i;
int database = -1, dir = -1, result, root_relative = 0;
*catalog = NULL;
if (!root || !source || !segment(suite) || !segment(component) ||
!segment(index_arch)) return 2;
result = holy_source_apt(root, source, id, &base, &trust, key);
if (result) goto done;
if (!key[0] || !binding_name(id, suite, component, index_arch, name)) {
result = 6; goto done;
}
database = holy_state_lock(root, 0, &generation, &result);
if (database < 0) goto done;
dir = binding_directory(database, 0);
if (dir < 0 || !read_binding(dir, name, values, &root_relative)) {
result = 6; goto done;
}
if (strcmp(values[1], id) || strcmp(values[2], suite) ||
strcmp(values[3], component) || strcmp(values[4], index_arch) ||
strcmp(values[7], key)) { result = 6; goto done; }
close(dir); dir = -1;
close(database); database = -1;
if (root_relative) {
root_path = realpath(root, NULL);
if (!root_path) { result = 6; goto done; }
joined = malloc(strlen(root_path) + strlen(values[8]) + 1);
if (!joined) { result = 1; goto done; }
sprintf(joined, "%s%s", strcmp(root_path, "/") ? root_path : "", values[8]);
path = realpath(joined, NULL);
if (!path || (strcmp(root_path, "/") &&
(strncmp(path, root_path, strlen(root_path)) ||
path[strlen(root_path)] != '/'))) { result = 6; goto done; }
} else {
path = realpath(values[8], NULL);
if (!path) { result = 6; goto done; }
}
result = holy_apt_catalog_identity(path, root, source, checked_id, checked_index);
if (result || strcmp(checked_id, id) || strcmp(checked_index, values[5]) ||
!proof_coordinates(path, suite, component, index_arch,
release_hash, key_hash) ||
strcmp(release_hash, values[6]) || strcmp(key_hash, values[7])) {
if (!result) result = 6;
goto done;
}
*catalog = path; path = NULL; result = 0;
done:
if (result) fprintf(stderr, "holypkg: bound APT catalog unavailable (status %d)\n", result);
for (i = 0; i < 9; ++i) free(values[i]);
if (dir >= 0) close(dir);
if (database >= 0) close(database);
free(base); free(trust); free(root_path); free(joined); free(path);
return result;
}