#include <assert.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include "pkg.h"
#include "private/pkg.h"
#include "private/event.h"
#include "sha256.h"
#include "blake2.h"
#define PKG_CKSUM_SEPARATOR '$'
#define PKG_CHECKSUM_SHA256_LEN (SHA256_BLOCK_SIZE * 2 + 1)
#define PKG_CHECKSUM_BLAKE2_LEN (BLAKE2B_OUTBYTES * 8 / 5 + sizeof("100") * 2 + 2)
#define PKG_CHECKSUM_BLAKE2S_LEN (BLAKE2S_OUTBYTES * 8 / 5 + sizeof("100") * 2 + 2)
#define PKG_CHECKSUM_CUR_VERSION 2
struct kv {
const char *key;
const char *value;
struct kv *next;
};
struct kv *
kv_new(const char *k, const char *v)
{
struct kv *kv = xcalloc(1, sizeof(*kv));
kv->key = k;
kv->value = v;
return (kv);
}
typedef void (*pkg_checksum_hash_func)(struct kv *entries,
unsigned char **out, size_t *outlen);
typedef void (*pkg_checksum_hash_bulk_func)(const unsigned char *in, size_t inlen,
unsigned char **out, size_t *outlen);
typedef void (*pkg_checksum_encode_func)(unsigned char *in, size_t inlen,
char *out, size_t outlen);
typedef void (*pkg_checksum_hash_file_func)(int fd, unsigned char **out,
size_t *outlen);
static void pkg_checksum_hash_sha256(struct kv *entries,
unsigned char **out, size_t *outlen);
static void pkg_checksum_hash_sha256_bulk(const unsigned char *in, size_t inlen,
unsigned char **out, size_t *outlen);
static void pkg_checksum_hash_sha256_file(int fd, unsigned char **out,
size_t *outlen);
static void pkg_checksum_hash_blake2(struct kv *entries,
unsigned char **out, size_t *outlen);
static void pkg_checksum_hash_blake2_bulk(const unsigned char *in, size_t inlen,
unsigned char **out, size_t *outlen);
static void pkg_checksum_hash_blake2_file(int fd, unsigned char **out,
size_t *outlen);
static void pkg_checksum_hash_blake2s(struct kv *entries,
unsigned char **out, size_t *outlen);
static void pkg_checksum_hash_blake2s_bulk(const unsigned char *in, size_t inlen,
unsigned char **out, size_t *outlen);
static void pkg_checksum_hash_blake2s_file(int fd, unsigned char **out,
size_t *outlen);
static void pkg_checksum_encode_base32(unsigned char *in, size_t inlen,
char *out, size_t outlen);
static void pkg_checksum_encode_hex(unsigned char *in, size_t inlen,
char *out, size_t outlen);
static const struct _pkg_cksum_type {
const char *name;
size_t hlen;
pkg_checksum_hash_func hfunc;
pkg_checksum_hash_bulk_func hbulkfunc;
pkg_checksum_hash_file_func hfilefunc;
pkg_checksum_encode_func encfunc;
} checksum_types[] = {
[PKG_HASH_TYPE_SHA256_BASE32] = {
"sha256_base32",
PKG_CHECKSUM_SHA256_LEN,
pkg_checksum_hash_sha256,
pkg_checksum_hash_sha256_bulk,
pkg_checksum_hash_sha256_file,
pkg_checksum_encode_base32
},
[PKG_HASH_TYPE_SHA256_HEX] = {
"sha256_hex",
PKG_CHECKSUM_SHA256_LEN,
pkg_checksum_hash_sha256,
pkg_checksum_hash_sha256_bulk,
pkg_checksum_hash_sha256_file,
pkg_checksum_encode_hex
},
[PKG_HASH_TYPE_BLAKE2_BASE32] = {
"blake2_base32",
PKG_CHECKSUM_BLAKE2_LEN,
pkg_checksum_hash_blake2,
pkg_checksum_hash_blake2_bulk,
pkg_checksum_hash_blake2_file,
pkg_checksum_encode_base32
},
[PKG_HASH_TYPE_SHA256_RAW] = {
"sha256_raw",
SHA256_BLOCK_SIZE,
pkg_checksum_hash_sha256,
pkg_checksum_hash_sha256_bulk,
pkg_checksum_hash_sha256_file,
NULL
},
[PKG_HASH_TYPE_BLAKE2_RAW] = {
"blake2_raw",
BLAKE2B_OUTBYTES,
pkg_checksum_hash_blake2,
pkg_checksum_hash_blake2_bulk,
pkg_checksum_hash_blake2_file,
NULL
},
[PKG_HASH_TYPE_BLAKE2S_BASE32] = {
"blake2s_base32",
PKG_CHECKSUM_BLAKE2S_LEN,
pkg_checksum_hash_blake2s,
pkg_checksum_hash_blake2s_bulk,
pkg_checksum_hash_blake2s_file,
pkg_checksum_encode_base32
},
[PKG_HASH_TYPE_BLAKE2S_RAW] = {
"blake2s_raw",
BLAKE2S_OUTBYTES,
pkg_checksum_hash_blake2s,
pkg_checksum_hash_blake2s_bulk,
pkg_checksum_hash_blake2s_file,
NULL
},
[PKG_HASH_TYPE_UNKNOWN] = {
NULL,
-1,
NULL,
NULL,
NULL
}
};
static int
pkg_checksum_entry_cmp(struct kv *e1,
struct kv *e2)
{
int r;
r = strcmp(e1->key, e2->key);
if (r != 0)
return r;
return (strcmp(e1->value, e2->value));
}
int
pkg_checksum_generate(struct pkg *pkg, char *dest, size_t destlen,
pkg_checksum_type_t type, bool inc_scripts, bool inc_version, bool inc_files __unused)
{
unsigned char *bdigest;
char *olduid;
size_t blen;
struct kv *entries = NULL;
charv_t tofree = vec_init();
struct pkg_option *option = NULL;
struct pkg_dep *dep = NULL;
struct pkg_file *f = NULL;
bool is_group = false;
if (pkg == NULL || type >= PKG_HASH_TYPE_UNKNOWN ||
destlen < checksum_types[type].hlen)
return (EPKG_FATAL);
if (pkg_type(pkg) == PKG_GROUP_REMOTE || pkg_type(pkg) == PKG_GROUP_INSTALLED)
is_group = true;
LL_APPEND(entries, kv_new("name", pkg->name));
if (!is_group)
LL_APPEND(entries, kv_new("origin", pkg->origin));
if (inc_version && !is_group)
LL_APPEND(entries, kv_new("version", pkg->version));
if (!is_group)
LL_APPEND(entries, kv_new("arch", pkg->altabi));
while (pkg_options(pkg, &option) == EPKG_OK) {
LL_APPEND(entries, kv_new(option->key, option->value));
}
vec_foreach(pkg->shlibs_required, i) {
LL_APPEND(entries, kv_new("required_shlib", pkg->shlibs_required.d[i]));
}
vec_foreach(pkg->shlibs_provided, i) {
LL_APPEND(entries, kv_new("provided_shlib", pkg->shlibs_provided.d[i]));
}
vec_foreach(pkg->users, i) {
LL_APPEND(entries, kv_new("user", pkg->users.d[i]));
}
vec_foreach(pkg->groups, i) {
LL_APPEND(entries, kv_new("group", pkg->groups.d[i]));
}
while (pkg_deps(pkg, &dep) == EPKG_OK) {
if (is_group) {
LL_APPEND(entries, kv_new("depend", dep->name));
} else {
xasprintf(&olduid, "%s~%s", dep->name, dep->origin);
LL_APPEND(entries, kv_new("depend", olduid));
vec_push(&tofree, olduid);
}
}
vec_foreach(pkg->provides, i) {
LL_APPEND(entries, kv_new("provide", pkg->provides.d[i]));
}
vec_foreach(pkg->requires, i) {
LL_APPEND(entries, kv_new("require", pkg->requires.d[i]));
}
if (inc_scripts) {
for (int i = 0; i < PKG_NUM_SCRIPTS; i++) {
if (pkg->scripts[i] != NULL) {
fflush(pkg->scripts[i]->fp);
LL_APPEND(entries, kv_new("script", pkg->scripts[i]->buf));
}
}
for (int i = 0; i < PKG_NUM_LUA_SCRIPTS; i++) {
vec_foreach(pkg->lua_scripts[i], j)
LL_APPEND(entries, kv_new("lua_script", pkg->lua_scripts[i].d[j]));
}
}
while (pkg_files(pkg, &f) == EPKG_OK) {
LL_APPEND(entries, kv_new(f->path, f->sum));
}
LL_SORT(entries, pkg_checksum_entry_cmp);
checksum_types[type].hfunc(entries, &bdigest, &blen);
if (blen == 0 || bdigest == NULL) {
LL_FREE(entries, free);
vec_free_and_free(&tofree, free);
return (EPKG_FATAL);
}
if (checksum_types[type].encfunc) {
size_t i = snprintf(dest, destlen, "%d%c%d%c", PKG_CHECKSUM_CUR_VERSION,
PKG_CKSUM_SEPARATOR, type, PKG_CKSUM_SEPARATOR);
assert(i < destlen);
checksum_types[type].encfunc(bdigest, blen, dest + i, destlen - i);
}
else {
assert(destlen >= blen);
memcpy(dest, bdigest, blen);
}
free(bdigest);
LL_FREE(entries, free);
vec_free_and_free(&tofree, free);
return (EPKG_OK);
}
bool
pkg_checksum_is_valid(const char *cksum, size_t clen)
{
const char *sep;
unsigned int value;
if (clen < 4)
return (false);
sep = strchr(cksum, PKG_CKSUM_SEPARATOR);
if (sep == NULL || *sep == '\0')
return (false);
value = strtoul(cksum, NULL, 10);
if (value != PKG_CHECKSUM_CUR_VERSION)
return (false);
cksum = sep + 1;
sep = strchr(cksum, PKG_CKSUM_SEPARATOR);
if (sep == NULL || *sep == '\0')
return (false);
value = strtoul(cksum, NULL, 10);
if (value >= PKG_HASH_TYPE_UNKNOWN)
return (false);
return (true);
}
pkg_checksum_type_t
pkg_checksum_file_get_type(const char *cksum, size_t clen __unused)
{
unsigned int value;
if (strchr(cksum, PKG_CKSUM_SEPARATOR) == NULL)
return (PKG_HASH_TYPE_UNKNOWN);
value = strtoul(cksum, NULL, 10);
if (value < PKG_HASH_TYPE_UNKNOWN)
return (value);
return (PKG_HASH_TYPE_UNKNOWN);
}
pkg_checksum_type_t
pkg_checksum_get_type(const char *cksum, size_t clen __unused)
{
const char *sep;
unsigned int value;
sep = strchr(cksum, PKG_CKSUM_SEPARATOR);
if (sep != NULL && *sep != '\0') {
value = strtoul(sep + 1, NULL, 10);
if (value < PKG_HASH_TYPE_UNKNOWN)
return (value);
}
return (PKG_HASH_TYPE_UNKNOWN);
}
static void
pkg_checksum_hash_sha256(struct kv *entries,
unsigned char **out, size_t *outlen)
{
SHA256_CTX sign_ctx;
struct kv *e;
sha256_init(&sign_ctx);
LL_FOREACH(entries, e) {
sha256_update(&sign_ctx, e->key, strlen(e->key));
sha256_update(&sign_ctx, e->value, strlen(e->value));
}
*out = xmalloc(SHA256_BLOCK_SIZE);
sha256_final(&sign_ctx, *out);
*outlen = SHA256_BLOCK_SIZE;
}
static void
pkg_checksum_hash_sha256_bulk(const unsigned char *in, size_t inlen,
unsigned char **out, size_t *outlen)
{
SHA256_CTX sign_ctx;
*out = xmalloc(SHA256_BLOCK_SIZE);
sha256_init(&sign_ctx);
sha256_update(&sign_ctx, in, inlen);
sha256_final(&sign_ctx, *out);
*outlen = SHA256_BLOCK_SIZE;
}
static void
pkg_checksum_hash_sha256_file(int fd, unsigned char **out, size_t *outlen)
{
char buffer[8192];
ssize_t r;
SHA256_CTX sign_ctx;
*out = xmalloc(SHA256_BLOCK_SIZE);
sha256_init(&sign_ctx);
while ((r = read(fd, buffer, sizeof(buffer))) > 0)
sha256_update(&sign_ctx, buffer, r);
if (r < 0) {
pkg_emit_errno(__func__, "read failed");
free(*out);
*out = NULL;
return;
}
sha256_final(&sign_ctx, *out);
*outlen = SHA256_BLOCK_SIZE;
}
static void
pkg_checksum_hash_blake2(struct kv *entries,
unsigned char **out, size_t *outlen)
{
blake2b_state st;
struct kv *e;
blake2b_init (&st, BLAKE2B_OUTBYTES);
LL_FOREACH(entries, e) {
blake2b_update (&st, e->key, strlen(e->key));
blake2b_update (&st, e->value, strlen(e->value));
}
*out = xmalloc(BLAKE2B_OUTBYTES);
blake2b_final (&st, *out, BLAKE2B_OUTBYTES);
*outlen = BLAKE2B_OUTBYTES;
}
static void
pkg_checksum_hash_blake2_bulk(const unsigned char *in, size_t inlen,
unsigned char **out, size_t *outlen)
{
*out = xmalloc(BLAKE2B_OUTBYTES);
blake2b(*out, BLAKE2B_OUTBYTES, in, inlen, NULL, 0);
*outlen = BLAKE2B_OUTBYTES;
}
static void
pkg_checksum_hash_blake2_file(int fd, unsigned char **out, size_t *outlen)
{
char buffer[8192];
ssize_t r;
blake2b_state st;
blake2b_init(&st, BLAKE2B_OUTBYTES);
while ((r = read(fd, buffer, sizeof(buffer))) > 0)
blake2b_update(&st, buffer, r);
if (r < 0) {
pkg_emit_errno(__func__, "read failed");
free(*out);
*out = NULL;
return;
}
*out = xmalloc(BLAKE2B_OUTBYTES);
blake2b_final(&st, *out, BLAKE2B_OUTBYTES);
*outlen = BLAKE2B_OUTBYTES;
}
static void
pkg_checksum_hash_blake2s(struct kv *entries,
unsigned char **out, size_t *outlen)
{
blake2s_state st;
struct kv *e;
blake2s_init (&st, BLAKE2S_OUTBYTES);
LL_FOREACH(entries, e) {
blake2s_update (&st, e->key, strlen(e->key));
blake2s_update (&st, e->value, strlen(e->value));
}
*out = xmalloc(BLAKE2S_OUTBYTES);
blake2s_final (&st, *out, BLAKE2S_OUTBYTES);
*outlen = BLAKE2S_OUTBYTES;
}
static void
pkg_checksum_hash_blake2s_bulk(const unsigned char *in, size_t inlen,
unsigned char **out, size_t *outlen)
{
*out = xmalloc(BLAKE2S_OUTBYTES);
blake2s(*out, BLAKE2S_OUTBYTES, in, inlen, NULL, 0);
*outlen = BLAKE2S_OUTBYTES;
}
static void
pkg_checksum_hash_blake2s_file(int fd, unsigned char **out, size_t *outlen)
{
char buffer[8192];
ssize_t r;
blake2s_state st;
blake2s_init(&st, BLAKE2S_OUTBYTES);
while ((r = read(fd, buffer, sizeof(buffer))) > 0)
blake2s_update(&st, buffer, r);
if (r < 0) {
pkg_emit_errno(__func__, "read failed");
free(*out);
*out = NULL;
return;
}
*out = xmalloc(BLAKE2S_OUTBYTES);
blake2s_final(&st, *out, BLAKE2S_OUTBYTES);
*outlen = BLAKE2S_OUTBYTES;
}
static const char b32[]="ybndrfg8ejkmcpqxot1uwisza345h769";
static void
pkg_checksum_encode_base32(unsigned char *in, size_t inlen,
char *out, size_t outlen)
{
int i, remain = -1, r, x;
if (outlen < inlen * 8 / 5) {
pkg_emit_error("cannot encode base32 as outlen is not sufficient");
return;
}
for (i = 0, r = 0; i < inlen; i++) {
switch (i % 5) {
case 0:
x = in[i];
remain = in[i] >> 5;
out[r++] = b32[x & 0x1F];
break;
case 1:
x = remain | in[i] << 3;
out[r++] = b32[x & 0x1F];
out[r++] = b32[x >> 5 & 0x1F];
remain = x >> 10;
break;
case 2:
x = remain | in[i] << 1;
out[r++] = b32[x & 0x1F];
remain = x >> 5;
break;
case 3:
x = remain | in[i] << 4;
out[r++] = b32[x & 0x1F];
out[r++] = b32[x >> 5 & 0x1F];
remain = x >> 10 & 0x3;
break;
case 4:
x = remain | in[i] << 2;
out[r++] = b32[x & 0x1F];
out[r++] = b32[x >> 5 & 0x1F];
remain = -1;
break;
default:
break;
}
}
if (remain >= 0)
out[r++] = b32[remain];
out[r] = 0;
}
static void
pkg_checksum_encode_hex(unsigned char *in, size_t inlen,
char *out, size_t outlen)
{
int i;
if (outlen < inlen * 2) {
pkg_emit_error("cannot encode hex as outlen is not sufficient");
return;
}
for (i = 0; i < inlen; i++)
sprintf(out + (i * 2), "%02x", in[i]);
out[inlen * 2] = '\0';
}
pkg_checksum_type_t
pkg_checksum_type_from_string(const char *name)
{
int i;
for (i = 0; i < PKG_HASH_TYPE_UNKNOWN; i ++) {
if (STRIEQ(name, checksum_types[i].name))
return (i);
}
return (PKG_HASH_TYPE_UNKNOWN);
}
const char*
pkg_checksum_type_to_string(pkg_checksum_type_t type)
{
return (checksum_types[type].name);
}
size_t
pkg_checksum_type_size(pkg_checksum_type_t type)
{
return (checksum_types[type].hlen);
}
int
pkg_checksum_calculate(struct pkg *pkg, struct pkgdb *db, bool inc_scripts,
bool inc_version, bool inc_files)
{
char *new_digest;
struct pkg_repo *repo;
int rc = EPKG_OK;
pkg_checksum_type_t type;
if (sizeof(void *) == 8)
type = PKG_HASH_TYPE_BLAKE2_BASE32;
else
type = PKG_HASH_TYPE_BLAKE2S_BASE32;
if (pkg->reponame != NULL) {
repo = pkg_repo_find(pkg->reponame);
if (repo != NULL)
type = repo->meta->digest_format;
}
new_digest = xmalloc(pkg_checksum_type_size(type));
if (pkg_checksum_generate(pkg, new_digest, pkg_checksum_type_size(type),
type, inc_scripts, inc_version, inc_files)
!= EPKG_OK) {
free(new_digest);
return (EPKG_FATAL);
}
free(pkg->digest);
pkg->digest = new_digest;
if (db != NULL)
pkgdb_set_pkg_digest(db, pkg);
return (rc);
}
unsigned char *
pkg_checksum_data(const unsigned char *in, size_t inlen,
pkg_checksum_type_t type)
{
const struct _pkg_cksum_type *cksum;
unsigned char *out, *res = NULL;
size_t outlen;
if (type >= PKG_HASH_TYPE_UNKNOWN || in == NULL)
return (NULL);
if (inlen == 0) {
inlen = strlen(in);
}
cksum = &checksum_types[type];
cksum->hbulkfunc(in, inlen, &out, &outlen);
if (out != NULL) {
if (cksum->encfunc != NULL) {
res = xmalloc(cksum->hlen);
cksum->encfunc(out, outlen, res, cksum->hlen);
free(out);
}
else {
res = out;
}
}
return (res);
}
unsigned char *
pkg_checksum_fileat(int rootfd, const char *path, pkg_checksum_type_t type)
{
int fd;
unsigned char *ret;
if ((fd = openat(rootfd, path, O_RDONLY)) == -1) {
pkg_emit_errno("open", path);
return (NULL);
}
ret = pkg_checksum_fd(fd, type);
close(fd);
return (ret);
}
unsigned char *
pkg_checksum_file(const char *path, pkg_checksum_type_t type)
{
return pkg_checksum_fileat(AT_FDCWD, path, type);
}
unsigned char *
pkg_checksum_fd(int fd, pkg_checksum_type_t type)
{
const struct _pkg_cksum_type *cksum;
unsigned char *out, *res = NULL;
size_t outlen;
if (type >= PKG_HASH_TYPE_UNKNOWN || fd < 0)
return (NULL);
cksum = &checksum_types[type];
cksum->hfilefunc(fd, &out, &outlen);
if (out != NULL) {
if (cksum->encfunc != NULL) {
res = xmalloc(cksum->hlen);
cksum->encfunc(out, outlen, res, cksum->hlen);
free(out);
} else {
res = out;
}
}
return (res);
}
static unsigned char *
pkg_checksum_symlink_readlink(const char *linkbuf, int linklen,
pkg_checksum_type_t type)
{
const char *lnk;
lnk = linkbuf;
return (pkg_checksum_data(RELATIVE_PATH(lnk), linklen, type));
}
unsigned char *
pkg_checksum_symlink(const char *path, pkg_checksum_type_t type)
{
return pkg_checksum_symlinkat(AT_FDCWD, path, type);
}
unsigned char *
pkg_checksum_symlinkat(int fd, const char *path, pkg_checksum_type_t type)
{
char linkbuf[MAXPATHLEN];
int linklen;
if ((linklen = readlinkat(fd, path, linkbuf, sizeof(linkbuf) - 1)) == -1) {
pkg_emit_errno("pkg_checksum_symlinkat", "readlink failed");
return (NULL);
}
linkbuf[linklen] = '\0';
return (pkg_checksum_symlink_readlink(linkbuf, linklen, type));
}
int
pkg_checksum_validate_file(const char *path, const char *sum)
{
return pkg_checksum_validate_fileat(AT_FDCWD, path, sum);
}
int
pkg_checksum_validate_fileat(int rootfd, const char *path, const char *sum)
{
struct stat st;
char *newsum;
pkg_checksum_type_t type;
type = pkg_checksum_file_get_type(sum, strlen(sum));
if (type == PKG_HASH_TYPE_UNKNOWN) {
type = PKG_HASH_TYPE_SHA256_HEX;
} else {
sum = strchr(sum, PKG_CKSUM_SEPARATOR);
if (sum != NULL)
sum++;
}
if (fstatat(rootfd, path, &st, AT_SYMLINK_NOFOLLOW) == -1) {
return (errno);
}
if (S_ISLNK(st.st_mode))
newsum = pkg_checksum_symlinkat(rootfd, path, type);
else
newsum = pkg_checksum_fileat(rootfd, path, type);
if (newsum == NULL)
return (-1);
if (!STREQ(sum, newsum)) {
free(newsum);
return (-1);
}
free(newsum);
return (0);
}
char *
pkg_checksum_generate_file(const char *path, pkg_checksum_type_t type)
{
return pkg_checksum_generate_fileat(AT_FDCWD, path, type);
}
char *
pkg_checksum_generate_fileat(int rootfd, const char *path,
pkg_checksum_type_t type)
{
struct stat st;
unsigned char *sum;
char *cksum;
if (fstatat(rootfd, path, &st, AT_SYMLINK_NOFOLLOW) == -1) {
pkg_emit_errno("pkg_checksum_generate_file", "lstat");
return (NULL);
}
if (S_ISLNK(st.st_mode))
sum = pkg_checksum_symlinkat(rootfd, path, type);
else
sum = pkg_checksum_fileat(rootfd, path, type);
if (sum == NULL)
return (NULL);
xasprintf(&cksum, "%d%c%s", type, PKG_CKSUM_SEPARATOR, sum);
free(sum);
return (cksum);
}