diff options
author | Timo Teräs <timo.teras@iki.fi> | 2020-09-30 14:11:37 +0300 |
---|---|---|
committer | Timo Teräs <timo.teras@iki.fi> | 2020-10-09 16:09:19 +0300 |
commit | efe0c4afecb9fd3da2ab4849d2b8edd5bea14d08 (patch) | |
tree | b04a9d2533148a1fcb4a28560ffd76c4bbc5e5ad | |
parent | 81782bfc150225df75b11efa4fa0ade428ae3676 (diff) | |
download | apk-tools-efe0c4afecb9fd3da2ab4849d2b8edd5bea14d08.tar.gz apk-tools-efe0c4afecb9fd3da2ab4849d2b8edd5bea14d08.tar.bz2 apk-tools-efe0c4afecb9fd3da2ab4849d2b8edd5bea14d08.tar.xz apk-tools-efe0c4afecb9fd3da2ab4849d2b8edd5bea14d08.zip |
adb: introduce apk-tools database format, and few applets
This is a flat buffers inspired format that allows fast
mmaped access to the data with low overhead, signature support
and relatively good forward support.
-rw-r--r-- | src/Makefile | 7 | ||||
-rw-r--r-- | src/adb.c | 802 | ||||
-rw-r--r-- | src/adb.h | 255 | ||||
-rw-r--r-- | src/adb_trust.c | 266 | ||||
-rw-r--r-- | src/apk.c | 2 | ||||
-rw-r--r-- | src/apk_adb.c | 472 | ||||
-rw-r--r-- | src/apk_adb.h | 91 | ||||
-rw-r--r-- | src/apk_applet.h | 2 | ||||
-rw-r--r-- | src/apk_blob.h | 2 | ||||
-rw-r--r-- | src/apk_database.h | 5 | ||||
-rw-r--r-- | src/apk_defines.h | 5 | ||||
-rw-r--r-- | src/apk_io.h | 5 | ||||
-rw-r--r-- | src/app_adbdump.c | 246 | ||||
-rw-r--r-- | src/app_adbsign.c | 97 | ||||
-rw-r--r-- | src/app_convdb.c | 238 | ||||
-rw-r--r-- | src/app_convndx.c | 105 | ||||
-rw-r--r-- | src/app_mkndx.c | 329 | ||||
-rw-r--r-- | src/blob.c | 7 | ||||
-rw-r--r-- | src/database.c | 8 | ||||
-rw-r--r-- | src/io.c | 9 | ||||
-rw-r--r-- | src/package.c | 6 | ||||
-rw-r--r-- | src/print.c | 4 | ||||
-rw-r--r-- | src/version.c | 2 |
23 files changed, 2955 insertions, 10 deletions
diff --git a/src/Makefile b/src/Makefile index 186823f..4360c48 100644 --- a/src/Makefile +++ b/src/Makefile @@ -19,6 +19,7 @@ ZLIB_LIBS := $(shell $(PKG_CONFIG) --libs zlib) libapk_soname := 3.12.0 libapk_so := $(obj)/libapk.so.$(libapk_soname) libapk.so.$(libapk_soname)-objs := \ + adb.o adb_trust.o \ common.o database.o package.o commit.o solver.o \ version.o atom.o blob.o hash.o print.o \ io.o io_url.o io_gunzip.o io_archive.o @@ -67,6 +68,12 @@ apk-objs := apk.o help.o \ app_index.o app_fetch.o app_verify.o app_dot.o \ app_audit.o +ifeq ($(ADB),y) +libapk.so.$(libapk_soname)-objs += apk_adb.o +apk-objs += app_adbdump.o app_adbsign.o app_mkndx.o \ + app_convdb.o app_convndx.o +endif + LIBS_apk := -lapk LIBS_apk-test := -lapk LIBS_apk.so := -L$(obj) -lapk diff --git a/src/adb.c b/src/adb.c new file mode 100644 index 0000000..2237a67 --- /dev/null +++ b/src/adb.c @@ -0,0 +1,802 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <malloc.h> +#include <assert.h> +#include <errno.h> +#include <sys/mman.h> +#include <sys/stat.h> + +#include "adb.h" +#include "apk_blob.h" + +/* Block enumeration */ +static inline struct adb_block *adb_block_validate(struct adb_block *blk, apk_blob_t b) +{ + size_t pos = (char *)blk - b.ptr; + if (pos == b.len) return NULL; + pos += sizeof(struct adb_block); + if (pos > b.len || ADB_BLOCK_SIZE(blk) > b.len - pos) return ERR_PTR(-EBADMSG); + return blk; +} + +struct adb_block *adb_block_first(apk_blob_t b) +{ + return adb_block_validate((struct adb_block*)b.ptr, b); +} + +struct adb_block *adb_block_next(struct adb_block *cur, apk_blob_t b) +{ + return adb_block_validate((struct adb_block*)((char*)cur + sizeof(struct adb_block) + ADB_BLOCK_SIZE(cur)), b); +} + +/* Init stuff */ +int adb_free(struct adb *db) +{ + if (db->mmap.ptr) { + munmap(db->mmap.ptr, db->mmap.len); + } else { + struct adb_w_bucket *bucket, *nxt; + int i; + + for (i = 0; i < db->num_buckets; i++) + list_for_each_entry_safe(bucket, nxt, &db->bucket[i], node) + free(bucket); + free(db->adb.ptr); + } + return 0; +} + +void adb_reset(struct adb *db) +{ + struct adb_w_bucket *bucket, *nxt; + int i; + + for (i = 0; i < db->num_buckets; i++) { + list_for_each_entry_safe(bucket, nxt, &db->bucket[i], node) + free(bucket); + list_init(&db->bucket[i]); + } + db->adb.len = 0; +} + +int adb_m_map(struct adb *db, int fd, uint32_t expected_schema, struct adb_trust *t) +{ + struct stat st; + struct adb_header *hdr; + struct adb_block *blk; + struct adb_verify_ctx vfy = {}; + int trusted = t ? 0 : 1; + + if (fstat(fd, &st) != 0) return -errno; + if (st.st_size < sizeof *hdr) return -EIO; + + memset(db, 0, sizeof *db); + db->mmap.ptr = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + db->mmap.len = st.st_size; + if (db->mmap.ptr == MAP_FAILED) return -errno; + + hdr = (struct adb_header *) db->mmap.ptr; + if (hdr->magic != htole32(ADB_FORMAT_MAGIC)) goto bad_msg; + if (expected_schema && expected_schema != le32toh(hdr->schema)) goto bad_msg; + + db->hdr = *hdr; + db->data = APK_BLOB_PTR_LEN(db->mmap.ptr + sizeof *hdr, db->mmap.len - sizeof *hdr); + adb_foreach_block(blk, db->data) { + apk_blob_t b = APK_BLOB_PTR_LEN((char*)(blk+1), ADB_BLOCK_SIZE(blk)); + switch (ADB_BLOCK_TYPE(blk)) { + case ADB_BLOCK_ADB: + if (!APK_BLOB_IS_NULL(db->adb)) goto bad_msg; + db->adb = b; + break; + case ADB_BLOCK_SIG: + if (APK_BLOB_IS_NULL(db->adb)) goto bad_msg; + if (!trusted && + adb_trust_verify_signature(t, db, &vfy, b) == 0) + trusted = 1; + break; + default: + if (APK_BLOB_IS_NULL(db->adb)) goto bad_msg; + break; + } + } + if (!trusted) blk = ERR_PTR(-ENOKEY); + if (IS_ERR(blk)) goto err; + return 0; + +bad_msg: + blk = ERR_PTR(-EBADMSG); +err: + adb_free(db); + return PTR_ERR(blk); +} + +int adb_w_init_dynamic(struct adb *db, uint32_t schema, void *buckets, size_t num_buckets) +{ + size_t i; + + *db = (struct adb) { + .hdr.magic = htole32(ADB_FORMAT_MAGIC), + .hdr.schema = htole32(schema), + .num_buckets = num_buckets, + .bucket = buckets, + }; + + if (num_buckets) { + for (i = 0; i < db->num_buckets; i++) + list_init(&db->bucket[i]); + } + + return 0; +} + +int adb_w_init_static(struct adb *db, void *buf, size_t bufsz) +{ + *db = (struct adb) { + .hdr.magic = htole32(ADB_FORMAT_MAGIC), + .adb.ptr = buf, + .mmap.len = bufsz, + }; + return 0; +} + +/* Read interface */ +static inline void *adb_r_deref(const struct adb *db, adb_val_t v, size_t offs, size_t s) +{ + offs += ADB_VAL_VALUE(v); + if (offs + s > db->adb.len) return NULL; + return db->adb.ptr + offs; +} + +adb_val_t adb_r_root(const struct adb *db) +{ + if (db->adb.len < sizeof(adb_val_t)) return ADB_NULL; + return *(adb_val_t *)(db->adb.ptr + db->adb.len - sizeof(adb_val_t)); +} + +uint32_t adb_r_int(const struct adb *db, adb_val_t v) +{ + uint32_t *int4; + + switch (ADB_VAL_TYPE(v)) { + case ADB_TYPE_INT: + return ADB_VAL_VALUE(v); + case ADB_TYPE_INT_32: + int4 = adb_r_deref(db, v, 0, sizeof int4); + if (!int4) return 0; + return le32toh(*int4); + default: + return 0; + } +} + +apk_blob_t adb_r_blob(const struct adb *db, adb_val_t v) +{ + void *blob; + size_t len; + + switch (ADB_VAL_TYPE(v)) { + case ADB_TYPE_BLOB_8: + blob = adb_r_deref(db, v, 0, 1); + len = *(uint8_t*) blob; + return APK_BLOB_PTR_LEN(adb_r_deref(db, v, 1, len), len); + case ADB_TYPE_BLOB_16: + blob = adb_r_deref(db, v, 0, 2); + len = le16toh(*(uint16_t*) blob); + return APK_BLOB_PTR_LEN(adb_r_deref(db, v, 2, len), len); + case ADB_TYPE_BLOB_32: + blob = adb_r_deref(db, v, 0, 4); + len = le32toh(*(uint32_t*) blob); + return APK_BLOB_PTR_LEN(adb_r_deref(db, v, 4, len), len); + default: + return APK_BLOB_NULL; + } +} + +struct adb_obj *adb_r_obj(struct adb *db, adb_val_t v, struct adb_obj *obj, const struct adb_object_schema *schema) +{ + adb_val_t *o; + uint32_t num; + + if (ADB_VAL_TYPE(v) != ADB_TYPE_ARRAY && + ADB_VAL_TYPE(v) != ADB_TYPE_OBJECT) + goto err; + + o = adb_r_deref(db, v, 0, sizeof(adb_val_t[ADBI_NUM_ENTRIES])); + if (!o) goto err; + + num = le32toh(o[ADBI_NUM_ENTRIES]); + o = adb_r_deref(db, v, 0, sizeof(adb_val_t[num])); + if (!o) goto err; + + *obj = (struct adb_obj) { + .schema = schema, + .db = db, + .num = num, + .obj = o, + }; + return obj; +err: + *obj = (struct adb_obj) { + .schema = schema, + .db = db, + .num = 1, + .obj = 0, + }; + return obj; +} + +struct adb_obj *adb_r_rootobj(struct adb *db, struct adb_obj *obj, const struct adb_object_schema *schema) +{ + return adb_r_obj(db, adb_r_root(db), obj, schema); +} + +adb_val_t adb_ro_val(const struct adb_obj *o, unsigned i) +{ + if (i >= o->num) return ADB_NULL; + return o->obj[i]; +} + +uint32_t adb_ro_int(const struct adb_obj *o, unsigned i) +{ + adb_val_t val = adb_ro_val(o, i); + if (val == ADB_NULL && o->schema && o->schema->get_default_int) + return o->schema->get_default_int(i); + return adb_r_int(o->db, val); +} + +apk_blob_t adb_ro_blob(const struct adb_obj *o, unsigned i) +{ + return adb_r_blob(o->db, adb_ro_val(o, i)); +} + +struct adb_obj *adb_ro_obj(const struct adb_obj *o, unsigned i, struct adb_obj *no) +{ + const struct adb_object_schema *schema = NULL; + + if (o->schema) { + if (o->schema->kind == ADB_KIND_ARRAY) + schema = container_of(o->schema->fields[0].kind, struct adb_object_schema, kind); + else if (i > 0 && i < o->schema->num_fields) + schema = container_of(o->schema->fields[i-1].kind, struct adb_object_schema, kind); + assert(schema->kind == ADB_KIND_OBJECT || schema->kind == ADB_KIND_ARRAY); + } + + return adb_r_obj(o->db, adb_ro_val(o, i), no, schema); +} + +static struct adb *__db1, *__db2; +static const struct adb_object_schema *__schema; + +static int wacmp(const void *p1, const void *p2) +{ + struct adb_obj o1, o2; + adb_r_obj(__db1, *(adb_val_t *)p1, &o1, __schema); + adb_r_obj(__db2, *(adb_val_t *)p2, &o2, __schema); + return o1.schema->compare(&o1, &o2); +} + +int adb_ra_find(struct adb_obj *arr, int cur, struct adb *db, adb_val_t val) +{ + adb_val_t *ndx; + + __db1 = db; + __db2 = arr->db; + __schema = arr->schema; + assert(__schema->kind == ADB_KIND_ARRAY); + __schema = container_of(__schema->fields[0].kind, struct adb_object_schema, kind); + + if (cur == 0) { + ndx = bsearch(&val, &arr->obj[ADBI_FIRST], adb_ra_num(arr), sizeof(arr->obj[0]), wacmp); + if (!ndx) return -1; + cur = ndx - arr->obj; + while (cur > 1 && wacmp(&val, &arr->obj[cur-1]) == 0) cur--; + } else { + cur++; + if (wacmp(&val, &arr->obj[cur]) != 0) + return -1; + } + return cur; + +} + +/* Write interface */ +static inline size_t iovec_len(struct iovec *vec, size_t nvec) +{ + size_t i, l = 0; + for (i = 0; i < nvec; i++) l += vec[i].iov_len; + return l; +} + +static unsigned iovec_hash(struct iovec *vec, size_t nvec, size_t *len) +{ + size_t i, l = 0; + unsigned hash = 5381; + + for (i = 0; i < nvec; i++) { + hash = apk_blob_hash_seed(APK_BLOB_PTR_LEN(vec[i].iov_base, vec[i].iov_len), hash); + l += vec[i].iov_len; + } + *len = l; + return hash; +} + +static unsigned iovec_memcmp(struct iovec *vec, size_t nvec, void *base) +{ + uint8_t *b = (uint8_t *) base; + size_t i; + + for (i = 0; i < nvec; i++) { + if (memcmp(b, vec[i].iov_base, vec[i].iov_len) != 0) + return 1; + b += vec[i].iov_len; + } + return 0; +} + +static adb_val_t adb_w_error(struct adb *db, int rc) +{ + assert(0); + db->hdr.magic = 0; + return ADB_ERROR(rc); +} + +static size_t adb_w_raw(struct adb *db, struct iovec *vec, size_t n, size_t len, size_t alignment) +{ + void *ptr; + size_t offs, i; + + if ((i = ROUND_UP(db->adb.len, alignment) - db->adb.len) != 0) { + memset(&db->adb.ptr[db->adb.len], 0, i); + db->adb.len += i; + } + + if (db->adb.len + len > db->mmap.len) { + assert(db->num_buckets); + if (!db->mmap.len) db->mmap.len = 8192; + while (db->adb.len + len > db->mmap.len) + db->mmap.len *= 2; + ptr = realloc(db->adb.ptr, db->mmap.len); + assert(ptr); + db->adb.ptr = ptr; + } + + offs = db->adb.len; + for (i = 0; i < n; i++) { + memcpy(&db->adb.ptr[db->adb.len], vec[i].iov_base, vec[i].iov_len); + db->adb.len += vec[i].iov_len; + } + + return offs; +} + +static size_t adb_w_data(struct adb *db, struct iovec *vec, size_t nvec, size_t alignment) +{ + size_t len, i; + unsigned hash, bucketno; + struct adb_w_bucket *bucket; + struct adb_w_bucket_entry *entry = 0; + + if (!db->num_buckets) return adb_w_raw(db, vec, nvec, iovec_len(vec, nvec), alignment); + + hash = iovec_hash(vec, nvec, &len); + bucketno = hash % db->num_buckets; + list_for_each_entry(bucket, &db->bucket[bucketno], node) { + for (i = 0, entry = bucket->entries; i < ARRAY_SIZE(bucket->entries); i++, entry++) { + if (entry->len == 0) goto add; + if (entry->hash != hash) continue; + if (entry->len == len && iovec_memcmp(vec, nvec, &((uint8_t*)db->adb.ptr)[entry->offs]) == 0) { + if ((entry->offs & alignment) != 0) goto add; + return entry->offs; + } + } + entry = 0; + } + + bucket = calloc(1, sizeof *bucket); + list_init(&bucket->node); + list_add_tail(&bucket->node, &db->bucket[bucketno]); + entry = &bucket->entries[0]; + +add: + entry->hash = hash; + entry->len = len; + entry->offs = adb_w_raw(db, vec, nvec, len, alignment); + return entry->offs; +} + +static size_t adb_w_data1(struct adb *db, void *ptr, size_t len, size_t alignment) +{ + struct iovec vec[] = { + { .iov_base = ptr, .iov_len = len }, + }; + if (!ptr) return ADB_NULL; + return adb_w_data(db, vec, ARRAY_SIZE(vec), alignment); +} + +void adb_w_root(struct adb *db, adb_val_t root_val) +{ + struct iovec vec = { + .iov_base = &root_val, .iov_len = sizeof(adb_val_t), + }; + adb_w_raw(db, &vec, 1, vec.iov_len, sizeof root_val); +} + +void adb_w_rootobj(struct adb_obj *obj) +{ + adb_w_root(obj->db, adb_w_obj(obj)); +} + +adb_val_t adb_w_blob(struct adb *db, apk_blob_t b) +{ + union { + uint32_t u32; + uint16_t u16; + uint8_t u8; + } val; + uint32_t n = b.len; + struct iovec vec[2] = { + { .iov_base = &val, .iov_len = sizeof val }, + { .iov_base = (void *) b.ptr, .iov_len = n }, + }; + adb_val_t o; + + if (n > 0xffff) { + val.u32 = htole32(n); + vec[0].iov_len = sizeof val.u32; + o = ADB_TYPE_BLOB_32; + } else if (n > 0xff) { + val.u16 = htole16(n); + vec[0].iov_len = sizeof val.u16; + o = ADB_TYPE_BLOB_16; + } else { + val.u8 = n; + vec[0].iov_len = sizeof val.u8; + o = ADB_TYPE_BLOB_8; + } + + return ADB_VAL(o, adb_w_data(db, vec, ARRAY_SIZE(vec), vec[0].iov_len)); +} + +adb_val_t adb_w_int(struct adb *db, uint32_t val) +{ + if (val >= 0x10000000) + return ADB_VAL(ADB_TYPE_INT_32, adb_w_data1(db, &val, sizeof val, sizeof val)); + return ADB_VAL(ADB_TYPE_INT, val); +} + +adb_val_t adb_w_copy(struct adb *db, struct adb *srcdb, adb_val_t v) +{ + void *ptr; + size_t sz, align = 1; + + if (db == srcdb) return v; + + switch (ADB_VAL_TYPE(v)) { + case ADB_TYPE_SPECIAL: + case ADB_TYPE_INT: + return v; + case ADB_TYPE_INT_32: + sz = align = sizeof(uint32_t); + goto copy; + case ADB_TYPE_BLOB_8: + ptr = adb_r_deref(srcdb, v, 0, 1); + sz = 1UL + *(uint8_t*) ptr; + goto copy; + case ADB_TYPE_BLOB_16: + ptr = adb_r_deref(srcdb, v, 0, 2); + sz = 1UL + *(uint16_t*) ptr; + goto copy; + case ADB_TYPE_OBJECT: + case ADB_TYPE_ARRAY: { + adb_val_t cpy[512]; + struct adb_obj obj; + adb_r_obj(srcdb, v, &obj, NULL); + sz = adb_ro_num(&obj); + if (sz > ARRAY_SIZE(cpy)) return adb_w_error(db, E2BIG); + cpy[ADBI_NUM_ENTRIES] = obj.obj[ADBI_NUM_ENTRIES]; + for (int i = ADBI_FIRST; i < sz; i++) cpy[i] = adb_w_copy(db, srcdb, adb_ro_val(&obj, i)); + return ADB_VAL(ADB_VAL_TYPE(v), adb_w_data1(db, cpy, sizeof(adb_val_t[sz]), sizeof(adb_val_t))); + } + case ADB_TYPE_INT_64: + case ADB_TYPE_BLOB_32: + default: + return adb_w_error(db, ENOSYS); + } +copy: + ptr = adb_r_deref(srcdb, v, 0, sz); + return ADB_VAL(ADB_VAL_TYPE(v), adb_w_data1(db, ptr, sz, align)); +} + +adb_val_t adb_w_adb(struct adb *db, struct adb *valdb) +{ + uint32_t bsz; + struct adb_block blk = { + .type_size = htole32((ADB_BLOCK_ADB << 30) + valdb->adb.len) + }; + struct iovec vec[] = { + { .iov_base = &bsz, .iov_len = sizeof bsz }, + { .iov_base = &blk, .iov_len = sizeof blk }, + { .iov_base = valdb->adb.ptr, .iov_len = valdb->adb.len }, + }; + if (valdb->adb.len <= 4) return ADB_NULL; + bsz = htole32(iovec_len(vec, ARRAY_SIZE(vec)) - sizeof bsz); + return ADB_VAL(ADB_TYPE_BLOB_32, adb_w_raw(db, vec, ARRAY_SIZE(vec), iovec_len(vec, ARRAY_SIZE(vec)), sizeof(uint32_t))); +} + +adb_val_t adb_w_fromstring(struct adb *db, const uint8_t *kind, apk_blob_t val) +{ + int r; + + switch (*kind) { + case ADB_KIND_BLOB: + case ADB_KIND_INT: + return container_of(kind, struct adb_scalar_schema, kind)->fromstring(db, val); + case ADB_KIND_OBJECT: + case ADB_KIND_ARRAY:; { + struct adb_obj obj; + struct adb_object_schema *schema = container_of(kind, struct adb_object_schema, kind); + adb_wo_alloca(&obj, schema, db); + r = schema->fromstring(&obj, val); + if (r) return ADB_ERROR(r); + return adb_w_obj(&obj); + } + default: + return ADB_ERROR(ENOSYS); + } +} + +struct adb_obj *adb_wo_init(struct adb_obj *o, adb_val_t *p, const struct adb_object_schema *schema, struct adb *db) +{ + memset(p, 0, sizeof(adb_val_t[schema->num_fields])); + /* Use the backing num entries index as the 'maximum' allocated space + * information while building the object/array. */ + p[ADBI_NUM_ENTRIES] = schema->num_fields; + + *o = (struct adb_obj) { + .schema = schema, + .db = db, + .obj = p, + .num = 1, + }; + return o; +} + +void adb_wo_reset(struct adb_obj *o) +{ + uint32_t max = o->obj[ADBI_NUM_ENTRIES]; + memset(o->obj, 0, sizeof(adb_val_t[o->num])); + o->obj[ADBI_NUM_ENTRIES] = max; + o->num = 1; +} + +void adb_wo_resetdb(struct adb_obj *o) +{ + adb_wo_reset(o); + adb_reset(o->db); +} + +static adb_val_t __adb_w_obj(struct adb_obj *o, uint32_t type) +{ + uint32_t n, max = o->obj[ADBI_NUM_ENTRIES]; + adb_val_t *obj = o->obj, val = ADB_NULL; + + if (o->schema && o->schema->pre_commit) o->schema->pre_commit(o); + + for (n = o->num; n > 1 && obj[n-1] == ADB_NULL; n--) + ; + if (n > 1) { + obj[ADBI_NUM_ENTRIES] = htole32(n); + val = ADB_VAL(type, adb_w_data1(o->db, obj, sizeof(adb_val_t[n]), sizeof(adb_val_t))); + } + adb_wo_reset(o); + o->obj[ADBI_NUM_ENTRIES] = max; + return val; +} + +adb_val_t adb_w_obj(struct adb_obj *o) +{ + return __adb_w_obj(o, ADB_TYPE_OBJECT); +} + +adb_val_t adb_w_arr(struct adb_obj *o) +{ + return __adb_w_obj(o, ADB_TYPE_ARRAY); +} + +adb_val_t adb_wo_fromstring(struct adb_obj *o, apk_blob_t val) +{ + adb_wo_reset(o); + return o->schema->fromstring(o, val); +} + +adb_val_t adb_wo_val(struct adb_obj *o, unsigned i, adb_val_t v) +{ + if (i >= o->obj[ADBI_NUM_ENTRIES]) return adb_w_error(o->db, E2BIG); + if (ADB_IS_ERROR(v)) return adb_w_error(o->db, ADB_VAL_VALUE(v)); + if (v != ADB_NULL && i >= o->num) o->num = i + 1; + return o->obj[i] = v; +} + +adb_val_t adb_wo_val_fromstring(struct adb_obj *o, unsigned i, apk_blob_t val) +{ + if (i >= o->obj[ADBI_NUM_ENTRIES]) return adb_w_error(o->db, E2BIG); + if (i >= o->num) o->num = i + 1; + return o->obj[i] = adb_w_fromstring(o->db, o->schema->fields[i-1].kind, val); +} + +adb_val_t adb_wo_int(struct adb_obj *o, unsigned i, uint32_t v) +{ + if (o->schema && o->schema->get_default_int && + v == o->schema->get_default_int(i)) + return ADB_NULL; + return adb_wo_val(o, i, adb_w_int(o->db, v)); +} + +adb_val_t adb_wo_blob(struct adb_obj *o, unsigned i, apk_blob_t b) +{ + return adb_wo_val(o, i, adb_w_blob(o->db, b)); +} + +adb_val_t adb_wo_obj(struct adb_obj *o, unsigned i, struct adb_obj *no) +{ + assert(o->db == no->db); + return adb_wo_val(o, i, adb_w_obj(no)); +} + +adb_val_t adb_wo_arr(struct adb_obj *o, unsigned i, struct adb_obj *no) +{ + assert(o->db == no->db); + return adb_wo_val(o, i, adb_w_arr(no)); +} + +adb_val_t adb_wa_append(struct adb_obj *o, adb_val_t v) +{ + if (o->num >= o->obj[ADBI_NUM_ENTRIES]) return adb_w_error(o->db, E2BIG); + if (ADB_IS_ERROR(v)) return adb_w_error(o->db, ADB_VAL_VALUE(v)); + o->obj[o->num++] = v; + return v; +} + +adb_val_t adb_wa_append_obj(struct adb_obj *o, struct adb_obj *no) +{ + assert(o->db == no->db); + return adb_wa_append(o, adb_w_obj(no)); +} + +adb_val_t adb_wa_append_fromstring(struct adb_obj *o, apk_blob_t b) +{ + return adb_wa_append(o, adb_w_fromstring(o->db, o->schema->fields[0].kind, b)); +} + +void adb_wa_sort(struct adb_obj *arr) +{ + __db1 = __db2 = arr->db; + __schema = container_of(arr->schema->fields[0].kind, struct adb_object_schema, kind); + qsort(&arr->obj[ADBI_FIRST], adb_ra_num(arr), sizeof(arr->obj[0]), wacmp); +} + +void adb_wa_sort_unique(struct adb_obj *arr) +{ + int i, j, num; + + adb_wa_sort(arr); + num = adb_ra_num(arr); + if (num >= 2) { + for (i = 2, j = 2; i <= num; i++) { + if (arr->obj[i] == arr->obj[i-1]) continue; + arr->obj[j++] = arr->obj[i]; + } + arr->num = j; + } +} + +/* Schema helpers */ +int adb_s_field_by_name(const struct adb_object_schema *schema, const char *name) +{ + for (int i = 0; i < schema->num_fields; i++) + if (strcmp(schema->fields[i].name, name) == 0) + return i + 1; + return 0; +} + +/* Container creation */ +int adb_c_header(struct apk_ostream *os, struct adb *db) +{ + return apk_ostream_write(os, &db->hdr, sizeof db->hdr); +} + +int adb_c_block(struct apk_ostream *os, uint32_t type, apk_blob_t val) +{ + struct adb_block blk = { + .type_size = htole32((type << 30) + val.len) + }; + int r; + + r = apk_ostream_write(os, &blk, sizeof blk); + if (r < 0) return r; + + r = apk_ostream_write(os, val.ptr, val.len); + if (r < 0) return r; + return 0; +} + +int adb_c_block_copy(struct apk_ostream *os, struct adb_block *b, struct apk_istream *is, struct adb_verify_ctx *vfy) +{ + EVP_MD_CTX *mdctx = NULL; + int r; + + r = apk_ostream_write(os, b, sizeof *b); + if (r < 0) return r; + + if (vfy) { + mdctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(mdctx, EVP_sha512(), 0); + } + r = apk_stream_copy(is, os, ADB_BLOCK_SIZE(b), 0, 0, mdctx); + if (vfy) { + EVP_DigestFinal_ex(mdctx, vfy->sha512, 0); + EVP_MD_CTX_free(mdctx); + vfy->calc |= (1 << ADB_HASH_SHA512); + } + return r; +} + +int adb_c_create(struct apk_ostream *os, struct adb *db, struct adb_trust *t) +{ + if (IS_ERR(os)) return PTR_ERR(os); + if (db->hdr.magic != htole32(ADB_FORMAT_MAGIC)) { + apk_ostream_cancel(os, -EAPKFORMAT); + goto ret; + } + adb_c_header(os, db); + adb_c_block(os, ADB_BLOCK_ADB, db->adb); + if (t) adb_trust_write_signatures(t, db, NULL, os); +ret: + return apk_ostream_close(os); +} + +/* Container transformation interface */ +int adb_c_xfrm(struct adb_xfrm *x, int (*cb)(struct adb_xfrm *, struct adb_block *, struct apk_istream *)) +{ + struct adb_block blk; + struct apk_segment_istream seg; + int r, block_no = 0; + size_t sz; + + r = apk_istream_read(x->is, &x->db.hdr, sizeof x->db.hdr); + if (r < 0) goto err; + + if (x->db.hdr.magic != htole32(ADB_FORMAT_MAGIC)) goto bad_msg; + r = apk_ostream_write(x->os, &x->db.hdr, sizeof x->db.hdr); + if (r < 0) goto err; + + do { + r = apk_istream_read(x->is, &blk, sizeof blk); + if (r <= 0) { + if (r == 0) r = cb(x, NULL, NULL); + goto err; + } + + if ((block_no++ == 0) != (ADB_BLOCK_TYPE(&blk) == ADB_BLOCK_ADB)) goto bad_msg; + + sz = ADB_BLOCK_SIZE(&blk); + r = cb(x, &blk, apk_istream_segment(&seg, x->is, sz, 0)); + if (r < 0) goto err; + + if (r == 0 && seg.bytes_left == sz) { + r = apk_ostream_write(x->os, &blk, sizeof blk); + if (r < 0) goto err; + r = apk_stream_copy(x->is, x->os, seg.bytes_left, 0, 0, 0); + if (r < 0) goto err; + } else if (seg.bytes_left > 0) { + r = apk_istream_read(x->is, NULL, sz - seg.bytes_left); + if (r < 0) goto err; + } + } while (1); +bad_msg: + r = -EBADMSG; +err: + apk_ostream_cancel(x->os, r); + return r; +} diff --git a/src/adb.h b/src/adb.h new file mode 100644 index 0000000..576f13f --- /dev/null +++ b/src/adb.h @@ -0,0 +1,255 @@ +#ifndef ADB_H +#define ADB_H + +#include <endian.h> +#include <stdint.h> +#include <sys/types.h> +#include "apk_io.h" + +struct adb; +struct adb_obj; +struct adb_trust; +struct adb_verify_ctx; + +typedef uint32_t adb_val_t; + +#define ADB_TYPE_SPECIAL 0x00000000 +#define ADB_TYPE_INT 0x10000000 +#define ADB_TYPE_INT_32 0x20000000 +#define ADB_TYPE_INT_64 0x30000000 +#define ADB_TYPE_BLOB_8 0x80000000 +#define ADB_TYPE_BLOB_16 0x90000000 +#define ADB_TYPE_BLOB_32 0xa0000000 +#define ADB_TYPE_ARRAY 0xd0000000 +#define ADB_TYPE_OBJECT 0xe0000000 +#define ADB_TYPE_ERROR 0xf0000000 +#define ADB_TYPE_MASK 0xf0000000 +#define ADB_VALUE_MASK 0x0fffffff +#define ADB_VAL_TYPE(x) ((le32toh(x))&ADB_TYPE_MASK) +#define ADB_VAL_VALUE(x) ((le32toh(x))&ADB_VALUE_MASK) +#define ADB_IS_ERROR(x) (ADB_VAL_TYPE(x) == ADB_TYPE_ERROR) +#define ADB_VAL(type, val) (htole32((type) | (val))) +#define ADB_ERROR(val) ADB_VAL(ADB_TYPE_ERROR, val) + +/* ADB_TYPE_SPECIAL */ +#define ADB_VAL_NULL 0x00000000 +#define ADB_VAL_TRUE 0x00000001 +#define ADB_VAL_FALSE 0x00000002 + +#define ADB_NULL ADB_VAL(ADB_TYPE_SPECIAL, ADB_VAL_NULL) + +/* Generic */ +#define ADBI_NUM_ENTRIES 0x00 +#define ADBI_FIRST 0x01 + +/* File Header */ +#define ADB_FORMAT_MAGIC 0x2e424441 // ADB. + +struct adb_header { + uint32_t magic; + uint32_t schema; +}; + +/* Blocks */ +#define ADB_BLOCK_END -1 +#define ADB_BLOCK_ADB 0 +#define ADB_BLOCK_SIG 2 + +#define ADB_BLOCK_TYPE(b) (le32toh((b)->type_size) >> 30) +#define ADB_BLOCK_SIZE(b) (le32toh((b)->type_size) & 0x3fffffff) + +struct adb_block { + uint32_t type_size; +}; + +struct adb_sign_hdr { + uint8_t sign_ver, hash_alg; +}; + +struct adb_sign_v0 { + struct adb_sign_hdr hdr; + uint8_t id[16]; + uint8_t sig[0]; +}; + +/* Hash algorithms */ +#define ADB_HASH_NONE 0x00 +#define ADB_HASH_SHA1 0x01 +#define ADB_HASH_SHA256 0x02 +#define ADB_HASH_SHA512 0x03 + +/* Block enumeration */ +struct adb_block *adb_block_first(apk_blob_t b); +struct adb_block *adb_block_next(struct adb_block *cur, apk_blob_t b); + +#define adb_foreach_block(__blk, __adb) \ + for (__blk = adb_block_first(__adb); !IS_ERR_OR_NULL(__blk); __blk = adb_block_next(__blk, __adb)) + +/* Schema */ +#define ADB_KIND_ADB 1 +#define ADB_KIND_OBJECT 2 +#define ADB_KIND_ARRAY 3 +#define ADB_KIND_BLOB 4 +#define ADB_KIND_INT 5 + +#define ADB_ARRAY_ITEM(_t) { { .kind = &(_t).kind } } +#define ADB_FIELD(_i, _n, _t) [(_i)-1] = { .name = _n, .kind = &(_t).kind } + +struct adb_object_schema { + uint8_t kind; + uint16_t num_fields; + + apk_blob_t (*tostring)(struct adb_obj *, char *, size_t); + int (*fromstring)(struct adb_obj *, apk_blob_t); + uint32_t (*get_default_int)(unsigned i); + int (*compare)(struct adb_obj *, struct adb_obj *); + void (*pre_commit)(struct adb_obj *); + + struct { + const char *name; + const uint8_t *kind; + } fields[]; +}; + +struct adb_scalar_schema { + uint8_t kind; + + apk_blob_t (*tostring)(struct adb*, adb_val_t, char *, size_t); + adb_val_t (*fromstring)(struct adb*, apk_blob_t); + int (*compare)(adb_val_t, adb_val_t); +}; + +struct adb_adb_schema { + uint8_t kind; + uint32_t schema_id; + struct adb_object_schema *obj; +}; + +/* Database read interface */ +struct adb_w_bucket { + struct list_head node; + struct adb_w_bucket_entry { + uint32_t hash; + uint32_t offs; + uint32_t len; + } entries[40]; +}; + +struct adb { + apk_blob_t mmap, data, adb; + struct adb_header hdr; + size_t num_buckets; + struct list_head *bucket; +}; + +struct adb_obj { + struct adb *db; + const struct adb_object_schema *schema; + uint32_t num; + adb_val_t *obj; +}; + +/* Container read interface */ +int adb_free(struct adb *); +void adb_reset(struct adb *); + +int adb_m_map(struct adb *, int fd, uint32_t expected_schema, struct adb_trust *); +#define adb_w_init_alloca(db, schema, num_buckets) adb_w_init_dynamic(db, schema, alloca(sizeof(struct list_head[num_buckets])), num_buckets) +#define adb_w_init_tmp(db, size) adb_w_init_static(db, alloca(size), size) +int adb_w_init_dynamic(struct adb *db, uint32_t schema, void *buckets, size_t num_buckets); +int adb_w_init_static(struct adb *db, void *buf, size_t bufsz); + +/* Primitive read */ +adb_val_t adb_r_root(const struct adb *); +struct adb_obj *adb_r_rootobj(struct adb *a, struct adb_obj *o, const struct adb_object_schema *); +uint32_t adb_r_int(const struct adb *, adb_val_t); +apk_blob_t adb_r_blob(const struct adb *, adb_val_t); +struct adb_obj *adb_r_obj(struct adb *, adb_val_t, struct adb_obj *o, const struct adb_object_schema *); + +/* Object read */ +static inline uint32_t adb_ro_num(const struct adb_obj *o) { return o->num; } +static inline uint32_t adb_ra_num(const struct adb_obj *o) { return (o->num ?: 1) - 1; } + +adb_val_t adb_ro_val(const struct adb_obj *o, unsigned i); +uint32_t adb_ro_int(const struct adb_obj *o, unsigned i); +apk_blob_t adb_ro_blob(const struct adb_obj *o, unsigned i); +struct adb_obj *adb_ro_obj(const struct adb_obj *o, unsigned i, struct adb_obj *); +int adb_ra_find(struct adb_obj *arr, int cur, struct adb *db, adb_val_t val); + +/* Primitive write */ +void adb_w_root(struct adb *, adb_val_t); +void adb_w_rootobj(struct adb_obj *); +adb_val_t adb_w_blob(struct adb *, apk_blob_t); +adb_val_t adb_w_int(struct adb *, uint32_t); +adb_val_t adb_w_copy(struct adb *, struct adb *, adb_val_t); +adb_val_t adb_w_adb(struct adb *, struct adb *); +adb_val_t adb_w_fromstring(struct adb *, const uint8_t *kind, apk_blob_t); + +/* Object write */ +#define adb_wo_alloca(o, schema, db) adb_wo_init(o, alloca(sizeof(adb_val_t[(schema)->num_fields])), schema, db) + +struct adb_obj *adb_wo_init(struct adb_obj *, adb_val_t *, const struct adb_object_schema *, struct adb *); +void adb_wo_reset(struct adb_obj *); +void adb_wo_resetdb(struct adb_obj *); +adb_val_t adb_w_obj(struct adb_obj *); +adb_val_t adb_w_arr(struct adb_obj *); +adb_val_t adb_wo_fromstring(struct adb_obj *o, apk_blob_t); +adb_val_t adb_wo_val(struct adb_obj *o, unsigned i, adb_val_t); +adb_val_t adb_wo_val_fromstring(struct adb_obj *o, unsigned i, apk_blob_t); +adb_val_t adb_wo_int(struct adb_obj *o, unsigned i, uint32_t); +adb_val_t adb_wo_blob(struct adb_obj *o, unsigned i, apk_blob_t); +adb_val_t adb_wo_obj(struct adb_obj *o, unsigned i, struct adb_obj *); +adb_val_t adb_wo_arr(struct adb_obj *o, unsigned i, struct adb_obj *); +adb_val_t adb_wa_append(struct adb_obj *o, adb_val_t); +adb_val_t adb_wa_append_obj(struct adb_obj *o, struct adb_obj *); +adb_val_t adb_wa_append_fromstring(struct adb_obj *o, apk_blob_t); +void adb_wa_sort(struct adb_obj *); +void adb_wa_sort_unique(struct adb_obj *); + +/* Schema helpers */ +int adb_s_field_by_name(const struct adb_object_schema *, const char *); + +/* Creation */ +int adb_c_header(struct apk_ostream *os, struct adb *db); +int adb_c_block(struct apk_ostream *os, uint32_t type, apk_blob_t); +int adb_c_block_copy(struct apk_ostream *os, struct adb_block *b, struct apk_istream *is, struct adb_verify_ctx *); +int adb_c_create(struct apk_ostream *os, struct adb *db, struct adb_trust *t); + +/* Trust */ +#include <openssl/evp.h> + +struct adb_pkey { + uint8_t id[16]; + EVP_PKEY *key; +}; + +int adb_pkey_init(struct adb_pkey *pkey, EVP_PKEY *key); +void adb_pkey_free(struct adb_pkey *pkey); +int adb_pkey_load(struct adb_pkey *pkey, int dirfd, const char *fn); + +struct adb_trust { + EVP_MD_CTX *mdctx; + struct list_head trusted_key_list; + struct list_head private_key_list; +}; + +struct adb_verify_ctx { + uint32_t calc; + uint8_t sha512[64]; +}; + +int adb_trust_init(struct adb_trust *trust, int keysfd, struct apk_string_array *); +void adb_trust_free(struct adb_trust *trust); +int adb_trust_write_signatures(struct adb_trust *trust, struct adb *db, struct adb_verify_ctx *vfy, struct apk_ostream *os); +int adb_trust_verify_signature(struct adb_trust *trust, struct adb *db, struct adb_verify_ctx *vfy, apk_blob_t sigb); + +/* Transform existing file */ +struct adb_xfrm { + struct apk_istream *is; + struct apk_ostream *os; + struct adb db; + struct adb_verify_ctx vfy; +}; +int adb_c_xfrm(struct adb_xfrm *, int (*cb)(struct adb_xfrm *, struct adb_block *, struct apk_istream *)); + +#endif diff --git a/src/adb_trust.c b/src/adb_trust.c new file mode 100644 index 0000000..41c904f --- /dev/null +++ b/src/adb_trust.c @@ -0,0 +1,266 @@ +#include <errno.h> +#include <stdio.h> +#include <openssl/bio.h> +#include <openssl/pem.h> +#include <openssl/err.h> +#include "apk_defines.h" +#include "adb.h" + +struct adb_trust_key { + struct list_head key_node; + struct adb_pkey key; + +}; + +/* Trust */ +int adb_pkey_init(struct adb_pkey *pkey, EVP_PKEY *key) +{ + unsigned char dig[EVP_MAX_MD_SIZE], *pub = NULL; + unsigned int dlen = sizeof dig; + int len; + + if ((len = i2d_PublicKey(key, &pub)) < 0) return -EIO; + EVP_Digest(pub, len, dig, &dlen, EVP_sha512(), NULL); + memcpy(pkey->id, dig, sizeof pkey->id); + OPENSSL_free(pub); + + pkey->key = key; + return 0; +} + +void adb_pkey_free(struct adb_pkey *pkey) +{ + EVP_PKEY_free(pkey->key); +} + +int adb_pkey_load(struct adb_pkey *pkey, int dirfd, const char *fn) +{ + EVP_PKEY *key; + BIO *bio; + int fd; + + fd = openat(dirfd, fn, O_RDONLY|O_CLOEXEC); + if (fd < 0) return -errno; + + bio = BIO_new_fp(fdopen(fd, "r"), BIO_CLOSE); + if (!bio) return -ENOMEM; + + key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL); + if (!key) { + BIO_reset(bio); + key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL); + } + ERR_clear_error(); + + BIO_free(bio); + if (!key) return -EBADMSG; + + adb_pkey_init(pkey, key); + return 0; +} + +static struct adb_trust_key *adb_trust_load_key(int dirfd, const char *filename) +{ + struct adb_trust_key *key; + int r; + + key = calloc(1, sizeof *key); + if (!key) return ERR_PTR(-ENOMEM); + + r = adb_pkey_load(&key->key, dirfd, filename); + if (r) { + free(key); + return ERR_PTR(-ENOKEY); + } + + list_init(&key->key_node); + return key; +} + +static int __adb_trust_load_pubkey(void *pctx, int dirfd, const char *filename) +{ + struct adb_trust *trust = pctx; + struct adb_trust_key *key = adb_trust_load_key(dirfd, filename); + + if (!IS_ERR(key)) + list_add_tail(&key->key_node, &trust->trusted_key_list); + + return 0; +} + +int adb_trust_init(struct adb_trust *trust, int dirfd, struct apk_string_array *pkey_files) +{ + char **fn; + + *trust = (struct adb_trust){ + .mdctx = EVP_MD_CTX_new(), + }; + if (!trust->mdctx) return -ENOMEM; + EVP_MD_CTX_set_flags(trust->mdctx, EVP_MD_CTX_FLAG_FINALISE); + list_init(&trust->trusted_key_list); + list_init(&trust->private_key_list); + apk_dir_foreach_file(dirfd, __adb_trust_load_pubkey, trust); + + foreach_array_item(fn, pkey_files) { + struct adb_trust_key *key = adb_trust_load_key(AT_FDCWD, *fn); + if (IS_ERR(key)) return PTR_ERR(key); + list_add_tail(&key->key_node, &trust->private_key_list); + } + + return 0; +} + +static void __adb_trust_free_keys(struct list_head *h) +{ + struct adb_trust_key *tkey, *n; + + list_for_each_entry_safe(tkey, n, h, key_node) { + list_del(&tkey->key_node); + adb_pkey_free(&tkey->key); + free(tkey); + } +} + +void adb_trust_free(struct adb_trust *trust) +{ + if (!trust->mdctx) return; + __adb_trust_free_keys(&trust->trusted_key_list); + __adb_trust_free_keys(&trust->private_key_list); + EVP_MD_CTX_free(trust->mdctx); +} + +static int adb_verify_ctx_calc(struct adb_verify_ctx *vfy, unsigned int hash_alg, apk_blob_t data, apk_blob_t *pmd) +{ + const EVP_MD *evp; + apk_blob_t md; + + switch (hash_alg) { + case ADB_HASH_SHA512: + evp = EVP_sha512(); + *pmd = md = APK_BLOB_BUF(vfy->sha512); + break; + default: + return -ENOTSUP; + } + + if (!(vfy->calc & (1 << hash_alg))) { + unsigned int sz = md.len; + if (APK_BLOB_IS_NULL(data)) return -ENOMSG; + if (EVP_Digest(data.ptr, data.len, (unsigned char*) md.ptr, &sz, evp, NULL) != 1 || + sz != md.len) + return -EIO; + vfy->calc |= (1 << hash_alg); + } + return 0; +} + +int adb_trust_write_signatures(struct adb_trust *trust, struct adb *db, struct adb_verify_ctx *vfy, struct apk_ostream *os) +{ + union { + struct adb_sign_hdr hdr; + struct adb_sign_v0 v0; + unsigned char buf[8192]; + } sig; + struct adb_trust_key *tkey; + apk_blob_t md; + size_t siglen; + int r; + + if (!vfy) { + vfy = alloca(sizeof *vfy); + memset(vfy, 0, sizeof *vfy); + } + + r = adb_verify_ctx_calc(vfy, ADB_HASH_SHA512, db->adb, &md); + if (r) return r; + + list_for_each_entry(tkey, &trust->private_key_list, key_node) { + sig.v0 = (struct adb_sign_v0) { + .hdr.sign_ver = 0, + .hdr.hash_alg = ADB_HASH_SHA512, + }; + memcpy(sig.v0.id, tkey->key.id, sizeof(sig.v0.id)); + + siglen = sizeof sig.buf - sizeof sig.v0; + EVP_MD_CTX_set_pkey_ctx(trust->mdctx, NULL); + if (EVP_DigestSignInit(trust->mdctx, NULL, EVP_sha512(), NULL, tkey->key.key) != 1 || + EVP_DigestUpdate(trust->mdctx, &db->hdr, sizeof db->hdr) != 1 || + EVP_DigestUpdate(trust->mdctx, &sig.hdr.sign_ver, sizeof sig.hdr.sign_ver) != 1 || + EVP_DigestUpdate(trust->mdctx, &sig.hdr.hash_alg, sizeof sig.hdr.hash_alg) != 1 || + EVP_DigestUpdate(trust->mdctx, md.ptr, md.len) != 1 || + EVP_DigestSignFinal(trust->mdctx, sig.v0.sig, &siglen) != 1) { + ERR_print_errors_fp(stdout); + goto err_io; + } + + r = adb_c_block(os, ADB_BLOCK_SIG, APK_BLOB_PTR_LEN((char*) &sig, sizeof(sig.v0) + siglen)); + if (r < 0) goto err; + } + return 0; +err_io: + r = -EIO; +err: + apk_ostream_cancel(os, r); + return r; +} + +int adb_trust_verify_signature(struct adb_trust *trust, struct adb *db, struct adb_verify_ctx *vfy, apk_blob_t sigb) +{ + struct adb_trust_key *tkey; + struct adb_sign_hdr *sig; + struct adb_sign_v0 *sig0; + apk_blob_t md; + + if (APK_BLOB_IS_NULL(db->adb)) return -ENOMSG; + if (sigb.len < sizeof(struct adb_sign_hdr)) return -EBADMSG; + + sig = (struct adb_sign_hdr *) sigb.ptr; + sig0 = (struct adb_sign_v0 *) sigb.ptr; + if (sig->sign_ver != 0) return -ENOSYS; + + list_for_each_entry(tkey, &trust->trusted_key_list, key_node) { + if (memcmp(sig0->id, tkey->key.id, sizeof sig0->id) != 0) continue; + if (adb_verify_ctx_calc(vfy, sig->hash_alg, db->adb, &md) != 0) continue; + + EVP_MD_CTX_set_pkey_ctx(trust->mdctx, NULL); + if (EVP_DigestVerifyInit(trust->mdctx, NULL, EVP_sha512(), NULL, tkey->key.key) != 1 || + EVP_DigestUpdate(trust->mdctx, &db->hdr, sizeof db->hdr) != 1 || + EVP_DigestUpdate(trust->mdctx, &sig->sign_ver, sizeof sig->sign_ver) != 1 || + EVP_DigestUpdate(trust->mdctx, &sig->hash_alg, sizeof sig->hash_alg) != 1 || + EVP_DigestUpdate(trust->mdctx, md.ptr, md.len) != 1 || + EVP_DigestVerifyFinal(trust->mdctx, sig0->sig, sigb.len - sizeof(*sig0)) != 1) { + ERR_clear_error(); + continue; + } + + return 0; + } + + return -EKEYREJECTED; +} + +/* Command group for signing */ + +#include "apk_applet.h" + +#define SIGNING_OPTIONS(OPT) \ + OPT(OPT_SIGN_sign_key, APK_OPT_ARG "sign-key") + +APK_OPT_GROUP(options_signing, "Signing", SIGNING_OPTIONS); + +static int option_parse_signing(void *ctx, struct apk_db_options *dbopts, int optch, const char *optarg) +{ + switch (optch) { + case OPT_SIGN_sign_key: + *apk_string_array_add(&dbopts->private_keys) = (char*) optarg; + break; + default: + return -ENOTSUP; + } + return 0; +} + +const struct apk_option_group optgroup_signing = { + .desc = options_signing, + .parse = option_parse_signing, +}; @@ -472,6 +472,7 @@ int main(int argc, char **argv) memset(&dbopts, 0, sizeof(dbopts)); list_init(&dbopts.repository_list); + apk_string_array_init(&dbopts.private_keys); umask(0); setup_terminal(); @@ -577,6 +578,7 @@ err: fetchConnectionCacheClose(); apk_string_array_free(&args); + apk_string_array_free(&dbopts.private_keys); free(apk_argv); if (r < 0) r = 250; diff --git a/src/apk_adb.c b/src/apk_adb.c new file mode 100644 index 0000000..4abc0f3 --- /dev/null +++ b/src/apk_adb.c @@ -0,0 +1,472 @@ +#include <errno.h> +#include "adb.h" +#include "apk_adb.h" +#include "apk_print.h" +#include "apk_version.h" + +#define APK_VERSION_CONFLICT 16 + +/* Few helpers to map old database to new one */ + +int apk_dep_split(apk_blob_t *b, apk_blob_t *bdep) +{ + extern const apk_spn_match_def apk_spn_dependency_separator; + + if (APK_BLOB_IS_NULL(*b)) return 0; + if (apk_blob_cspn(*b, apk_spn_dependency_separator, bdep, b)) { + /* found separator - update b to skip over after all separators */ + if (!apk_blob_spn(*b, apk_spn_dependency_separator, NULL, b)) + *b = APK_BLOB_NULL; + } else { + /* no separator - return this as the last dependency, signal quit */ + *bdep = *b; + *b = APK_BLOB_NULL; + } + return 1; +} + +adb_val_t adb_wo_pkginfo(struct adb_obj *obj, unsigned int f, apk_blob_t val) +{ + struct apk_checksum csum; + adb_val_t v = ADB_ERROR(EAPKFORMAT); + + /* FIXME: get rid of this function, and handle the conversion via schema? */ + switch (f) { + case ADBI_PI_UNIQUE_ID: + if (!val.ptr || val.len < 4) break; + apk_blob_pull_csum(&val, &csum); + v = adb_w_int(obj->db, get_unaligned32(csum.data) & ADB_VALUE_MASK); + break; + case ADBI_PI_REPO_COMMIT: + if (val.len < 40) break; + csum.type = 20; + apk_blob_pull_hexdump(&val, APK_BLOB_CSUM(csum)); + if (val.ptr) v = adb_w_blob(obj->db, APK_BLOB_CSUM(csum)); + break; + default: + return adb_wo_val_fromstring(obj, f, val); + } + if (v != ADB_NULL && !ADB_IS_ERROR(v)) + v = adb_wo_val(obj, f, v); + return v; +} + +unsigned int adb_pkg_field_index(char f) +{ +#define MAP(ch, ndx) [ch - 'A'] = ndx + static unsigned char map[] = { + MAP('C', ADBI_PI_UNIQUE_ID), + MAP('P', ADBI_PI_NAME), + MAP('V', ADBI_PI_VERSION), + MAP('T', ADBI_PI_DESCRIPTION), + MAP('U', ADBI_PI_URL), + MAP('I', ADBI_PI_INSTALLED_SIZE), + MAP('S', ADBI_PI_FILE_SIZE), + MAP('L', ADBI_PI_LICENSE), + MAP('A', ADBI_PI_ARCH), + MAP('D', ADBI_PI_DEPENDS), + MAP('i', ADBI_PI_INSTALL_IF), + MAP('p', ADBI_PI_PROVIDES), + MAP('o', ADBI_PI_ORIGIN), + MAP('m', ADBI_PI_MAINTAINER), + MAP('t', ADBI_PI_BUILD_TIME), + MAP('c', ADBI_PI_REPO_COMMIT), + MAP('r', ADBI_PI_REPLACES), + MAP('k', ADBI_PI_PRIORITY), + }; + if (f < 'A' || f-'A' >= ARRAY_SIZE(map)) return 0; + return map[(unsigned char)f - 'A']; +} + +/* Schema */ + +static apk_blob_t string_tostring(struct adb *db, adb_val_t val, char *buf, size_t bufsz) +{ + return adb_r_blob(db, val); +} + +static adb_val_t string_fromstring(struct adb *db, apk_blob_t val) +{ + return adb_w_blob(db, val); +} + +static struct adb_scalar_schema scalar_string = { + .kind = ADB_KIND_BLOB, + .tostring = string_tostring, + .fromstring = string_fromstring, +}; + +const struct adb_object_schema schema_string_array = { + .kind = ADB_KIND_ARRAY, + .num_fields = APK_MAX_PKG_TRIGGERS, + .fields = ADB_ARRAY_ITEM(scalar_string), +}; + +static apk_blob_t hexblob_tostring(struct adb *db, adb_val_t val, char *buf, size_t bufsz) +{ + apk_blob_t b = adb_r_blob(db, val), to = APK_BLOB_PTR_LEN(buf, bufsz); + + if (APK_BLOB_IS_NULL(b)) return b; + + apk_blob_push_hexdump(&to, b); + if (!APK_BLOB_IS_NULL(to)) + return APK_BLOB_PTR_PTR(buf, to.ptr-1); + + return APK_BLOB_PTR_LEN(buf, snprintf(buf, bufsz, "(%ld bytes)", b.len)); +} + +static struct adb_scalar_schema scalar_hexblob = { + .kind = ADB_KIND_BLOB, + .tostring = hexblob_tostring, +}; + +static apk_blob_t int_tostring(struct adb *db, adb_val_t val, char *buf, size_t bufsz) +{ + return APK_BLOB_PTR_LEN(buf, snprintf(buf, bufsz, "%u", adb_r_int(db, val))); +} + +static adb_val_t int_fromstring(struct adb *db, apk_blob_t val) +{ + uint32_t n = apk_blob_pull_uint(&val, 10); + if (val.len) return ADB_ERROR(EINVAL); + return adb_w_int(db, n); +} + +static struct adb_scalar_schema scalar_int = { + .kind = ADB_KIND_INT, + .tostring = int_tostring, + .fromstring = int_fromstring, +}; + +static apk_blob_t oct_tostring(struct adb *db, adb_val_t val, char *buf, size_t bufsz) +{ + return APK_BLOB_PTR_LEN(buf, snprintf(buf, bufsz, "%o", adb_r_int(db, val))); +} + +static struct adb_scalar_schema scalar_oct = { + .kind = ADB_KIND_INT, + .tostring = oct_tostring, +}; + +static apk_blob_t hsize_tostring(struct adb *db, adb_val_t val, char *buf, size_t bufsz) +{ + off_t v = adb_r_int(db, val); + const char *unit = apk_get_human_size(v, &v); + + return APK_BLOB_PTR_LEN(buf, snprintf(buf, bufsz, "%jd %s", (intmax_t)v, unit)); +} + +static struct adb_scalar_schema scalar_hsize = { + .kind = ADB_KIND_INT, + .tostring = hsize_tostring, + .fromstring = int_fromstring, +}; + +static apk_blob_t dependency_tostring(struct adb_obj *obj, char *buf, size_t bufsz) +{ + apk_blob_t name, ver; + unsigned int mask; + + name = adb_ro_blob(obj, ADBI_DEP_NAME); + ver = adb_ro_blob(obj, ADBI_DEP_VERSION); + + if (APK_BLOB_IS_NULL(name)) return APK_BLOB_NULL; + if (APK_BLOB_IS_NULL(ver)) return name; + + mask = adb_ro_int(obj, ADBI_DEP_MATCH) ?: APK_VERSION_EQUAL; + return APK_BLOB_PTR_LEN(buf, + snprintf(buf, bufsz, "%s"BLOB_FMT"%s"BLOB_FMT, + (mask & APK_VERSION_CONFLICT) ? "!" : "", + BLOB_PRINTF(name), + apk_version_op_string(mask & ~APK_VERSION_CONFLICT), + BLOB_PRINTF(ver))); +} + +static int dependency_fromstring(struct adb_obj *obj, apk_blob_t bdep) +{ + extern const apk_spn_match_def apk_spn_dependency_comparer; + extern const apk_spn_match_def apk_spn_repotag_separator; + apk_blob_t bname, bop, bver = APK_BLOB_NULL, btag; + int mask = APK_DEPMASK_ANY; + + /* [!]name[<,<=,<~,=,~,>~,>=,>,><]ver */ + + /* parse the version */ + if (bdep.ptr[0] == '!') { + bdep.ptr++; + bdep.len--; + mask |= APK_VERSION_CONFLICT; + } + + if (apk_blob_cspn(bdep, apk_spn_dependency_comparer, &bname, &bop)) { + int i; + + if (mask == 0) + goto fail; + if (!apk_blob_spn(bop, apk_spn_dependency_comparer, &bop, &bver)) + goto fail; + + mask = 0; + for (i = 0; i < bop.len; i++) { + switch (bop.ptr[i]) { + case '<': + mask |= APK_VERSION_LESS; + break; + case '>': + mask |= APK_VERSION_GREATER; + break; + case '~': + mask |= APK_VERSION_FUZZY|APK_VERSION_EQUAL; + break; + case '=': + mask |= APK_VERSION_EQUAL; + break; + } + } + if ((mask & APK_DEPMASK_CHECKSUM) != APK_DEPMASK_CHECKSUM && + !apk_version_validate(bver)) + goto fail; + } else { + bname = bdep; + bop = APK_BLOB_NULL; + bver = APK_BLOB_NULL; + } + + if (apk_blob_cspn(bname, apk_spn_repotag_separator, &bname, &btag)) + ; /* tag = repository tag */ + + adb_wo_blob(obj, ADBI_DEP_NAME, bname); + if (mask != APK_DEPMASK_ANY) { + adb_wo_blob(obj, ADBI_DEP_VERSION, bver); + if (mask != APK_VERSION_EQUAL) + adb_wo_int(obj, ADBI_DEP_MATCH, mask); + } + return 0; + +fail: + return -EAPKDEPFORMAT; +} + +static int dependency_cmp(struct adb_obj *o1, struct adb_obj *o2) +{ + return apk_blob_sort( + adb_ro_blob(o1, ADBI_DEP_NAME), + adb_ro_blob(o2, ADBI_DEP_NAME)); +} + +const struct adb_object_schema schema_dependency = { + .kind = ADB_KIND_OBJECT, + .num_fields = ADBI_DEP_MAX, + .tostring = dependency_tostring, + .fromstring = dependency_fromstring, + .compare = dependency_cmp, + .fields = { + ADB_FIELD(ADBI_DEP_NAME, "name", scalar_string), + ADB_FIELD(ADBI_DEP_VERSION, "version", scalar_string), + ADB_FIELD(ADBI_DEP_MATCH, "match", scalar_int), + }, +}; + +static int dependencies_fromstring(struct adb_obj *obj, apk_blob_t b) +{ + struct adb_obj dep; + apk_blob_t bdep; + + adb_wo_alloca(&dep, &schema_dependency, obj->db); + + while (apk_dep_split(&b, &bdep)) { + adb_wo_fromstring(&dep, bdep); + adb_wa_append_obj(obj, &dep); + } + + return 0; +} + +const struct adb_object_schema schema_dependency_array = { + .kind = ADB_KIND_ARRAY, + .fromstring = dependencies_fromstring, + .num_fields = APK_MAX_PKG_DEPENDENCIES, + .pre_commit = adb_wa_sort_unique, + .fields = ADB_ARRAY_ITEM(schema_dependency), +}; + +static int pkginfo_cmp(struct adb_obj *o1, struct adb_obj *o2) +{ + int r; + r = apk_blob_sort( + adb_ro_blob(o1, ADBI_PI_NAME), + adb_ro_blob(o2, ADBI_PI_NAME)); + if (r) return r; + + r = apk_version_compare_blob( + adb_ro_blob(o1, ADBI_PI_VERSION), + adb_ro_blob(o2, ADBI_PI_VERSION)); + switch (r) { + case APK_VERSION_LESS: return -1; + case APK_VERSION_GREATER: return 1; + } + return 0; +} + +const struct adb_object_schema schema_pkginfo = { + .kind = ADB_KIND_OBJECT, + .num_fields = ADBI_PI_MAX, + .compare = pkginfo_cmp, + .fields = { + ADB_FIELD(ADBI_PI_NAME, "name", scalar_string), + ADB_FIELD(ADBI_PI_VERSION, "version", scalar_string), + ADB_FIELD(ADBI_PI_UNIQUE_ID, "unique-id", scalar_int), + ADB_FIELD(ADBI_PI_DESCRIPTION, "description", scalar_string), + ADB_FIELD(ADBI_PI_ARCH, "arch", scalar_string), + ADB_FIELD(ADBI_PI_LICENSE, "license", scalar_string), + ADB_FIELD(ADBI_PI_ORIGIN, "origin", scalar_string), + ADB_FIELD(ADBI_PI_MAINTAINER, "maintainer", scalar_string), + ADB_FIELD(ADBI_PI_URL, "url", scalar_string), + ADB_FIELD(ADBI_PI_REPO_COMMIT, "repo-commit", scalar_hexblob), + ADB_FIELD(ADBI_PI_BUILD_TIME, "build-time", scalar_int), + ADB_FIELD(ADBI_PI_INSTALLED_SIZE,"installed-size",scalar_hsize), + ADB_FIELD(ADBI_PI_FILE_SIZE, "file-size", scalar_hsize), + ADB_FIELD(ADBI_PI_PRIORITY, "priority", scalar_int), + ADB_FIELD(ADBI_PI_DEPENDS, "depends", schema_dependency_array), + ADB_FIELD(ADBI_PI_PROVIDES, "provides", schema_dependency_array), + ADB_FIELD(ADBI_PI_REPLACES, "replaces", schema_dependency_array), + ADB_FIELD(ADBI_PI_INSTALL_IF, "install-if", schema_dependency_array), + ADB_FIELD(ADBI_PI_RECOMMENDS, "recommends", schema_dependency_array), + }, +}; + +const struct adb_object_schema schema_pkginfo_array = { + .kind = ADB_KIND_ARRAY, + .num_fields = APK_MAX_INDEX_PACKAGES, + .pre_commit = adb_wa_sort, + .fields = ADB_ARRAY_ITEM(schema_pkginfo), +}; + +const struct adb_object_schema schema_index = { + .kind = ADB_KIND_OBJECT, + .num_fields = ADBI_NDX_MAX, + .fields = { + ADB_FIELD(ADBI_NDX_DESCRIPTION, "description", scalar_string), + ADB_FIELD(ADBI_NDX_PACKAGES, "packages", schema_pkginfo_array), + }, +}; + +static uint32_t file_get_default_int(unsigned i) +{ + switch (i) { + case ADBI_FI_UID: + case ADBI_FI_GID: + return 0; + case ADBI_FI_MODE: + return 0644; + } + return -1; +} + +static int file_cmp(struct adb_obj *o1, struct adb_obj *o2) +{ + return apk_blob_sort( + adb_ro_blob(o1, ADBI_FI_NAME), + adb_ro_blob(o2, ADBI_FI_NAME)); +} + +const struct adb_object_schema schema_file = { + .kind = ADB_KIND_OBJECT, + .num_fields = ADBI_FI_MAX, + .get_default_int = file_get_default_int, + .compare = file_cmp, + .fields = { + ADB_FIELD(ADBI_FI_NAME, "name", scalar_string), + ADB_FIELD(ADBI_FI_HASHES, "hash", scalar_hexblob), + ADB_FIELD(ADBI_FI_UID, "uid", scalar_int), + ADB_FIELD(ADBI_FI_GID, "gid", scalar_int), + ADB_FIELD(ADBI_FI_MODE, "mode", scalar_oct), + ADB_FIELD(ADBI_FI_XATTRS, "xattr", scalar_hexblob), + }, +}; + +const struct adb_object_schema schema_file_array = { + .kind = ADB_KIND_ARRAY, + .pre_commit = adb_wa_sort, + .num_fields = APK_MAX_MANIFEST_FILES, + .fields = ADB_ARRAY_ITEM(schema_file), +}; + +static uint32_t path_get_default_int(unsigned i) +{ + switch (i) { + case ADBI_FI_UID: + case ADBI_FI_GID: + return 0; + case ADBI_FI_MODE: + return 0755; + } + return -1; +} + +const struct adb_object_schema schema_path = { + .kind = ADB_KIND_OBJECT, + .num_fields = ADBI_FI_MAX, + .get_default_int = path_get_default_int, + .compare = file_cmp, + .fields = { + ADB_FIELD(ADBI_FI_NAME, "name", scalar_string), + ADB_FIELD(ADBI_FI_FILES, "files", schema_file_array), + ADB_FIELD(ADBI_FI_UID, "uid", scalar_int), + ADB_FIELD(ADBI_FI_GID, "gid", scalar_int), + ADB_FIELD(ADBI_FI_MODE, "mode", scalar_oct), + ADB_FIELD(ADBI_FI_XATTRS, "xattr", scalar_hexblob), + }, +}; + +const struct adb_object_schema schema_path_array = { + .kind = ADB_KIND_ARRAY, + .pre_commit = adb_wa_sort, + .num_fields = APK_MAX_MANIFEST_PATHS, + .fields = ADB_ARRAY_ITEM(schema_path), +}; + +const struct adb_object_schema schema_scripts = { + .kind = ADB_KIND_OBJECT, + .num_fields = ADBI_SCRPT_MAX, + .fields = { + ADB_FIELD(ADBI_SCRPT_TRIGGER, "trigger", scalar_string), + ADB_FIELD(ADBI_SCRPT_PREINST, "pre-install", scalar_string), + ADB_FIELD(ADBI_SCRPT_POSTINST, "post-install", scalar_string), + ADB_FIELD(ADBI_SCRPT_PREDEINST, "pre-deinstall",scalar_string), + ADB_FIELD(ADBI_SCRPT_POSTDEINST,"post-deinstall",scalar_string), + ADB_FIELD(ADBI_SCRPT_PREUPGRADE,"pre-upgrade", scalar_string), + ADB_FIELD(ADBI_SCRPT_POSTUPGRADE,"post-upgrade",scalar_string), + }, +}; + +const struct adb_object_schema schema_package = { + .kind = ADB_KIND_OBJECT, + .num_fields = ADBI_PKG_MAX, + .fields = { + ADB_FIELD(ADBI_PKG_PKGINFO, "info", schema_pkginfo), + ADB_FIELD(ADBI_PKG_PATHS, "paths", schema_path_array), + ADB_FIELD(ADBI_PKG_SCRIPTS, "scripts", schema_scripts), + ADB_FIELD(ADBI_PKG_TRIGGERS, "triggers", schema_string_array), + //ADB_FIELD(ADBI_PKG_PASSWD, "passwd", schema_string_array), + }, +}; + +const struct adb_adb_schema schema_package_adb = { + .kind = ADB_KIND_ADB, + .schema_id = ADB_SCHEMA_PACKAGE, +}; + +const struct adb_object_schema schema_package_adb_array = { + .kind = ADB_KIND_ARRAY, + .num_fields = APK_MAX_INDEX_PACKAGES, + .fields = ADB_ARRAY_ITEM(schema_package_adb), +}; + +const struct adb_object_schema schema_idb = { + .kind = ADB_KIND_OBJECT, + .num_fields = ADBI_IDB_MAX, + .fields = { + ADB_FIELD(ADBI_IDB_PACKAGES, "packages", schema_package_adb_array), + }, +}; diff --git a/src/apk_adb.h b/src/apk_adb.h new file mode 100644 index 0000000..557bc6e --- /dev/null +++ b/src/apk_adb.h @@ -0,0 +1,91 @@ +#include "adb.h" + +/* Schemas */ +#define ADB_SCHEMA_INDEX 0x78646e69 // indx +#define ADB_SCHEMA_PACKAGE 0x676b6370 // pckg +#define ADB_SCHEMA_INSTALLED_DB 0x00626469 // idb + +/* Dependency */ +#define ADBI_DEP_NAME 0x01 +#define ADBI_DEP_VERSION 0x02 +#define ADBI_DEP_MATCH 0x03 +#define ADBI_DEP_MAX 0x04 + +/* Package Info */ +#define ADBI_PI_NAME 0x01 +#define ADBI_PI_VERSION 0x02 +#define ADBI_PI_UNIQUE_ID 0x03 +#define ADBI_PI_DESCRIPTION 0x04 +#define ADBI_PI_ARCH 0x05 +#define ADBI_PI_LICENSE 0x06 +#define ADBI_PI_ORIGIN 0x07 +#define ADBI_PI_MAINTAINER 0x08 +#define ADBI_PI_URL 0x09 +#define ADBI_PI_REPO_COMMIT 0x0a +#define ADBI_PI_BUILD_TIME 0x0b +#define ADBI_PI_INSTALLED_SIZE 0x0c +#define ADBI_PI_FILE_SIZE 0x0d +#define ADBI_PI_PRIORITY 0x0e +#define ADBI_PI_DEPENDS 0x0f +#define ADBI_PI_PROVIDES 0x10 +#define ADBI_PI_REPLACES 0x11 +#define ADBI_PI_INSTALL_IF 0x12 +#define ADBI_PI_RECOMMENDS 0x13 +#define ADBI_PI_MAX 0x14 + +/* File Info */ +#define ADBI_FI_NAME 0x01 +#define ADBI_FI_HASHES 0x02 +#define ADBI_FI_FILES 0x02 +#define ADBI_FI_MODE 0x03 +#define ADBI_FI_UID 0x04 +#define ADBI_FI_GID 0x05 +#define ADBI_FI_XATTRS 0x06 +#define ADBI_FI_MAX 0x07 + +/* Scripts */ +#define ADBI_SCRPT_TRIGGER 0x01 +#define ADBI_SCRPT_PREINST 0x02 +#define ADBI_SCRPT_POSTINST 0x03 +#define ADBI_SCRPT_PREDEINST 0x04 +#define ADBI_SCRPT_POSTDEINST 0x05 +#define ADBI_SCRPT_PREUPGRADE 0x06 +#define ADBI_SCRPT_POSTUPGRADE 0x07 +#define ADBI_SCRPT_MAX 0x08 + +/* Package */ +#define ADBI_PKG_PKGINFO 0x01 +#define ADBI_PKG_PATHS 0x02 +#define ADBI_PKG_SCRIPTS 0x03 +#define ADBI_PKG_TRIGGERS 0x04 +#define ADBI_PKG_PASSWD 0x05 +#define ADBI_PKG_MAX 0x06 + +/* Index */ +#define ADBI_NDX_DESCRIPTION 0x01 +#define ADBI_NDX_PACKAGES 0x02 +#define ADBI_NDX_MAX 0x03 + +/* Installed DB */ +#define ADBI_IDB_PACKAGES 0x01 +#define ADBI_IDB_MAX 0x02 + +/* */ +#define APK_MAX_PKG_DEPENDENCIES 512 +#define APK_MAX_PKG_REPLACES 32 +#define APK_MAX_PKG_TRIGGERS 32 +#define APK_MAX_INDEX_PACKAGES 20000 +#define APK_MAX_MANIFEST_FILES 8000 +#define APK_MAX_MANIFEST_PATHS 6000 + +extern const struct adb_object_schema + schema_dependency, schema_dependency_array, + schema_pkginfo, schema_pkginfo_array, + schema_file, schema_file_array, schema_path, schema_path_array, + schema_string_array, schema_scripts, schema_package, schema_package_adb_array, + schema_index, schema_idb; + +/* */ +int apk_dep_split(apk_blob_t *b, apk_blob_t *bdep); +adb_val_t adb_wo_pkginfo(struct adb_obj *obj, unsigned int f, apk_blob_t val); +unsigned int adb_pkg_field_index(char f); diff --git a/src/apk_applet.h b/src/apk_applet.h index 3bc2eb9..30fdc06 100644 --- a/src/apk_applet.h +++ b/src/apk_applet.h @@ -61,7 +61,7 @@ struct apk_applet { int (*main)(void *ctx, struct apk_database *db, struct apk_string_array *args); }; -extern const struct apk_option_group optgroup_global, optgroup_commit; +extern const struct apk_option_group optgroup_global, optgroup_commit, optgroup_signing; void apk_help(struct apk_applet *applet); void apk_applet_register(struct apk_applet *); diff --git a/src/apk_blob.h b/src/apk_blob.h index 008666e..9426721 100644 --- a/src/apk_blob.h +++ b/src/apk_blob.h @@ -68,6 +68,7 @@ static inline const EVP_MD *apk_checksum_default(void) #define APK_BLOB_BUF(buf) ((apk_blob_t){sizeof(buf), (char *)(buf)}) #define APK_BLOB_CSUM(csum) ((apk_blob_t){(csum).type, (char *)(csum).data}) #define APK_BLOB_STRUCT(s) ((apk_blob_t){sizeof(s), (char*)&(s)}) +#define APK_BLOB_STRLIT(s) ((apk_blob_t){sizeof(s)-1, (char *)(s)}) #define APK_BLOB_PTR_LEN(beg,len) ((apk_blob_t){(len), (beg)}) #define APK_BLOB_PTR_PTR(beg,end) APK_BLOB_PTR_LEN((beg),(end)-(beg)+1) @@ -95,6 +96,7 @@ apk_blob_t apk_blob_pushed(apk_blob_t buffer, apk_blob_t left); unsigned long apk_blob_hash_seed(apk_blob_t, unsigned long seed); unsigned long apk_blob_hash(apk_blob_t str); int apk_blob_compare(apk_blob_t a, apk_blob_t b); +int apk_blob_sort(apk_blob_t a, apk_blob_t b); int apk_blob_ends_with(apk_blob_t str, apk_blob_t suffix); int apk_blob_for_each_segment(apk_blob_t blob, const char *split, apk_blob_cb cb, void *ctx); diff --git a/src/apk_database.h b/src/apk_database.h index 3488031..1b0dbc6 100644 --- a/src/apk_database.h +++ b/src/apk_database.h @@ -20,6 +20,8 @@ #include "apk_provider_data.h" #include "apk_solver_data.h" +#include "adb.h" + struct apk_name; APK_ARRAY(apk_name_array, struct apk_name *); @@ -130,6 +132,7 @@ struct apk_db_options { const char *cache_dir; const char *repositories_file; struct list_head repository_list; + struct apk_string_array *private_keys; }; #define APK_REPOSITORY_CACHED 0 @@ -162,6 +165,8 @@ struct apk_database { int compat_newfeatures : 1; int compat_notinstallable : 1; + struct adb_trust trust; + struct apk_dependency_array *world; struct apk_protected_path_array *protected_paths; struct apk_repository repos[APK_MAX_REPOS]; diff --git a/src/apk_defines.h b/src/apk_defines.h index 505b212..dc38a0e 100644 --- a/src/apk_defines.h +++ b/src/apk_defines.h @@ -34,6 +34,8 @@ #define EAPKBADURL 1024 #define EAPKSTALEINDEX 1025 +#define EAPKFORMAT 1026 +#define EAPKDEPFORMAT 1027 static inline void *ERR_PTR(long error) { return (void*) error; } static inline void *ERR_CAST(const void *ptr) { return (void*) ptr; } @@ -63,6 +65,9 @@ static inline int IS_ERR_OR_NULL(const void *ptr) { return IS_ERR(ptr) || !ptr; (type *)( (char *)__mptr - offsetof(type,member) );}) #endif +#define ROUND_DOWN(x,a) ((x) & ~(a-1)) +#define ROUND_UP(x,a) (((x)+(a)-1) & ~((a)-1)) + extern int apk_verbosity; extern unsigned int apk_flags, apk_force; extern const char *apk_arch; diff --git a/src/apk_io.h b/src/apk_io.h index 18b8ecc..87b953e 100644 --- a/src/apk_io.h +++ b/src/apk_io.h @@ -141,6 +141,7 @@ struct apk_ostream_ops { struct apk_ostream { const struct apk_ostream_ops *ops; + int rc; }; struct apk_ostream *apk_ostream_gzip(struct apk_ostream *); @@ -149,13 +150,15 @@ struct apk_ostream *apk_ostream_to_fd(int fd); struct apk_ostream *apk_ostream_to_file(int atfd, const char *file, const char *tmpfile, mode_t mode); struct apk_ostream *apk_ostream_to_file_gz(int atfd, const char *file, const char *tmpfile, mode_t mode); size_t apk_ostream_write_string(struct apk_ostream *ostream, const char *string); +static inline void apk_ostream_cancel(struct apk_ostream *os, int rc) { if (!os->rc) os->rc = rc; } static inline ssize_t apk_ostream_write(struct apk_ostream *os, const void *buf, size_t size) { return os->ops->write(os, buf, size); } static inline int apk_ostream_close(struct apk_ostream *os) { - return os->ops->close(os); + int rc = os->rc; + return os->ops->close(os) ?: rc; } apk_blob_t apk_blob_from_istream(struct apk_istream *istream, size_t size); diff --git a/src/app_adbdump.c b/src/app_adbdump.c new file mode 100644 index 0000000..3914563 --- /dev/null +++ b/src/app_adbdump.c @@ -0,0 +1,246 @@ +#include <stdio.h> +#include <unistd.h> +#include <assert.h> +#include "apk_adb.h" +#include "apk_applet.h" +#include "apk_print.h" + +struct adb_dump_ctx { + struct adb *db; + struct adb_trust *trust; + char prefix[128], *pfx; +}; + +struct adb_db_schema { + unsigned long magic; + const struct adb_object_schema *root; +}; + +static void ctx_nest(struct adb_dump_ctx *ctx, unsigned depth) +{ + while (depth--) *ctx->pfx++ = ' '; + assert(ctx->pfx < &ctx->prefix[ARRAY_SIZE(ctx->prefix)]); + *ctx->pfx = 0; +} + +static void ctx_unnest(struct adb_dump_ctx *ctx, unsigned depth) +{ + ctx->pfx -= depth; + assert(ctx->pfx >= ctx->prefix); + *ctx->pfx = 0; +} + +static void ctx_itemstart(struct adb_dump_ctx *ctx) +{ + ctx->pfx[-2] = '-'; +} + +static void ctx_itemdone(struct adb_dump_ctx *ctx) +{ + memset(ctx->prefix, ' ', ctx->pfx - ctx->prefix); +} + +static void dump_object(struct adb_dump_ctx *ctx, const struct adb_object_schema *schema, adb_val_t v); +static void dump_adb(struct adb_dump_ctx *ctx); + +static void dump_item(struct adb_dump_ctx *ctx, const char *name, const uint8_t *kind, adb_val_t v) +{ + struct adb db, *origdb; + struct adb_obj o; + struct adb_object_schema *obj_schema; + char tmp[256]; + apk_blob_t b, nl = APK_BLOB_STR("\n"); + + switch (*kind) { + case ADB_KIND_ARRAY: + obj_schema = container_of(kind, struct adb_object_schema, kind); + adb_r_obj(ctx->db, v, &o, obj_schema); + if (!adb_ra_num(&o)) return; + + fprintf(stdout, "%s%s: # %u items\n", ctx->prefix, name, adb_ra_num(&o)); + ctx_nest(ctx, 4); + for (size_t i = ADBI_FIRST; i <= adb_ra_num(&o); i++) { + ctx_itemstart(ctx); + dump_item(ctx, NULL, obj_schema->fields[0].kind, adb_ro_val(&o, i)); + ctx_itemdone(ctx); + } + ctx_unnest(ctx, 4); + break; + case ADB_KIND_ADB: + db.hdr.schema = container_of(kind, struct adb_adb_schema, kind)->schema_id; + db.data = adb_r_blob(ctx->db, v); + origdb = ctx->db; + ctx->db = &db; + dump_adb(ctx); + ctx->db = origdb; + break; + case ADB_KIND_OBJECT: + if (name) { + fprintf(stdout, "%s%s:\n", ctx->prefix, name); + ctx_nest(ctx, 4); + } + dump_object(ctx, container_of(kind, struct adb_object_schema, kind), v); + if (name) ctx_unnest(ctx, 4); + break; + case ADB_KIND_BLOB: + case ADB_KIND_INT:; + struct adb_scalar_schema *scalar = container_of(kind, struct adb_scalar_schema, kind); + if (scalar->tostring) { + b = scalar->tostring(ctx->db, v, tmp, sizeof tmp); + } else { + b = APK_BLOB_STR("(unknown)"); + } + if (!APK_BLOB_IS_NULL(b)) { + fputs(ctx->prefix, stdout); + if (name) fprintf(stdout, "%s: ", name); + if (b.len >= 60 || apk_blob_chr(b, '\n')) { + /* long or multiline */ + apk_blob_t l; + fprintf(stdout, "|\n"); + ctx_itemdone(ctx); + ctx_nest(ctx, 4); + while (apk_blob_split(b, nl, &l, &b)) { + fprintf(stdout, "%s"BLOB_FMT"\n", + ctx->prefix, BLOB_PRINTF(l)); + } + if (b.len) { + fprintf(stdout, "%s"BLOB_FMT"\n", + ctx->prefix, BLOB_PRINTF(b)); + } + ctx_unnest(ctx, 4); + } else { + fwrite(b.ptr, 1, b.len, stdout); + fputc('\n', stdout); + } + } + break; + } +} + +static void dump_object(struct adb_dump_ctx *ctx, const struct adb_object_schema *schema, adb_val_t v) +{ + size_t schema_len = 0; + struct adb_obj o; + char tmp[256]; + apk_blob_t b; + + adb_r_obj(ctx->db, v, &o, schema); + if (schema) { + if (schema->tostring) { + b = schema->tostring(&o, tmp, sizeof tmp); + if (!APK_BLOB_IS_NULL(b)) + fprintf(stdout, "%s"BLOB_FMT"\n", ctx->prefix, BLOB_PRINTF(b)); + ctx_itemdone(ctx); + return; + } + schema_len = schema->num_fields; + } + + for (size_t i = ADBI_FIRST; i < adb_ro_num(&o); i++) { + adb_val_t val = adb_ro_val(&o, i); + if (val == ADB_NULL) continue; + if (i < schema_len && schema->fields[i-1].kind != 0) { + dump_item(ctx, schema->fields[i-1].name, schema->fields[i-1].kind, val); + ctx_itemdone(ctx); + } + } +} + +static const struct adb_db_schema dbschemas[] = { + { .magic = ADB_SCHEMA_INDEX, .root = &schema_index, }, + { .magic = ADB_SCHEMA_INSTALLED_DB, .root = &schema_idb, }, + { .magic = ADB_SCHEMA_PACKAGE, .root = &schema_package }, +}; + +static void dump_adb(struct adb_dump_ctx *ctx) +{ + struct adb_block *blk; + struct adb_sign_hdr *s; + struct adb_verify_ctx vfy = {}; + const struct adb_db_schema *ds; + unsigned char *id; + uint32_t schema_magic = ctx->db->hdr.schema; + int r; + + for (ds = dbschemas; ds < &dbschemas[ARRAY_SIZE(dbschemas)]; ds++) + if (ds->magic == schema_magic) break; + if (ds >= &dbschemas[ARRAY_SIZE(dbschemas)]) ds = NULL; + + adb_foreach_block(blk, ctx->db->data) { + apk_blob_t b = APK_BLOB_PTR_LEN((char*)(blk+1), ADB_BLOCK_SIZE(blk)); + switch (ADB_BLOCK_TYPE(blk)) { + case ADB_BLOCK_ADB: + fprintf(stdout, "%s# ADB block, size: %d\n", ctx->prefix, ADB_BLOCK_SIZE(blk)); + ctx_itemdone(ctx); + ctx->db->adb = b; + if (ds) + dump_object(ctx, ds->root, adb_r_root(ctx->db)); + else + fprintf(stdout, "%s# Unrecognized schema: 0x%08x\n", ctx->prefix, schema_magic); + break; + case ADB_BLOCK_SIG: + s = (struct adb_sign_hdr*) b.ptr; + fprintf(stdout, "%s# signature: v%d ", ctx->prefix, s->sign_ver); + ctx_itemdone(ctx); + r = adb_trust_verify_signature(ctx->trust, ctx->db, &vfy, b); + switch (s->sign_ver) { + case 0: + id = (unsigned char*)(s + 1); + for (size_t j = 0; j < 16; j++) + fprintf(stdout, "%02x", id[j]); + break; + default: + break; + } + fprintf(stdout, ": %s\n", r ? apk_error_str(r) : "OK"); + break; + default: + fprintf(stdout, "%s# unknown block %d, size: %d\n", + ctx->prefix, ADB_BLOCK_TYPE(blk), ADB_BLOCK_SIZE(blk)); + ctx_itemdone(ctx); + } + } + if (IS_ERR(blk)) fprintf(stdout, "%s# block enumeration error: corrupt data area\n", ctx->prefix); +} + +static int mmap_and_dump_adb(struct adb_trust *trust, int fd) +{ + struct adb db; + struct adb_dump_ctx ctx = { + .db = &db, + .pfx = ctx.prefix, + .trust = trust, + }; + int r; + + r = adb_m_map(&db, fd, 0, NULL); + if (r) return r; + + dump_adb(&ctx); + adb_free(&db); + return 0; +} + +static int adbdump_main(void *pctx, struct apk_database *db, struct apk_string_array *args) +{ + char **arg; + int r; + + foreach_array_item(arg, args) { + r = mmap_and_dump_adb(&db->trust, open(*arg, O_RDONLY)); + if (r) { + apk_error("%s: %s", *arg, apk_error_str(r)); + return r; + } + } + + return 0; +} + +static struct apk_applet apk_adbdump = { + .name = "adbdump", + .open_flags = APK_OPENF_READ | APK_OPENF_NO_STATE | APK_OPENF_NO_REPOS, + .main = adbdump_main, +}; +APK_DEFINE_APPLET(apk_adbdump); + diff --git a/src/app_adbsign.c b/src/app_adbsign.c new file mode 100644 index 0000000..6861776 --- /dev/null +++ b/src/app_adbsign.c @@ -0,0 +1,97 @@ +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <assert.h> + +#include "adb.h" +#include "apk_applet.h" +#include "apk_print.h" + +struct sign_ctx { + struct adb_xfrm xfrm; + struct apk_database *db; + int reset_signatures : 1; + int signatures_written : 1; +}; + +#define ADBSIGN_OPTIONS(OPT) \ + OPT(OPT_ADBSIGN_reset_signatures, "reset-signatures") + +APK_OPT_APPLET(option_desc, ADBSIGN_OPTIONS); + +static int option_parse_applet(void *pctx, struct apk_db_options *dbopts, int optch, const char *optarg) +{ + struct sign_ctx *ctx = (struct sign_ctx *) pctx; + + switch (optch) { + case OPT_ADBSIGN_reset_signatures: + ctx->reset_signatures = 1; + break; + default: + return -ENOTSUP; + } + return 0; +} + +static const struct apk_option_group optgroup_applet = { + .desc = option_desc, + .parse = option_parse_applet, +}; + +static int update_signatures(struct adb_xfrm *xfrm, struct adb_block *blk, struct apk_istream *is) +{ + struct sign_ctx *ctx = container_of(xfrm, struct sign_ctx, xfrm); + int r; + + switch (blk ? ADB_BLOCK_TYPE(blk) : -1) { + case ADB_BLOCK_ADB: + return adb_c_block_copy(xfrm->os, blk, is, &xfrm->vfy); + case ADB_BLOCK_SIG: + if (ctx->reset_signatures) + break; + return adb_c_block_copy(xfrm->os, blk, is, NULL); + default: + if (!ctx->signatures_written) { + ctx->signatures_written = 1; + r = adb_trust_write_signatures(&ctx->db->trust, &xfrm->db, &xfrm->vfy, xfrm->os); + if (r) return r; + } + if (!blk) break; + return adb_c_block_copy(xfrm->os, blk, is, NULL); + } + return 0; +} + +static int adbsign_main(void *pctx, struct apk_database *db, struct apk_string_array *args) +{ + char tmpname[PATH_MAX]; + struct sign_ctx *ctx = pctx; + char **arg; + int r; + + ctx->db = db; + foreach_array_item(arg, args) { + if (snprintf(tmpname, sizeof tmpname, "%s.tmp", *arg) >= sizeof tmpname) { + r = ENAMETOOLONG; + } else { + ctx->xfrm.is = apk_istream_from_file(AT_FDCWD, *arg); + ctx->xfrm.os = apk_ostream_to_file(AT_FDCWD, *arg, tmpname, 0644); + adb_c_xfrm(&ctx->xfrm, update_signatures); + apk_istream_close(ctx->xfrm.is); + r = apk_ostream_close(ctx->xfrm.os); + } + if (r) apk_error("%s: %s", *arg, apk_error_str(r)); + } + + return 0; +} + +static struct apk_applet apk_adbsign = { + .name = "adbsign", + .open_flags = APK_OPENF_READ | APK_OPENF_NO_STATE | APK_OPENF_NO_REPOS, + .context_size = sizeof(struct sign_ctx), + .optgroups = { &optgroup_global, &optgroup_signing, &optgroup_applet }, + .main = adbsign_main, +}; + +APK_DEFINE_APPLET(apk_adbsign); diff --git a/src/app_convdb.c b/src/app_convdb.c new file mode 100644 index 0000000..5b5581e --- /dev/null +++ b/src/app_convdb.c @@ -0,0 +1,238 @@ +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <assert.h> +#include <sys/stat.h> + +#include "apk_adb.h" +#include "apk_applet.h" + +struct conv_script { + struct list_head script_node; + char csum_len; + char csum[32]; + int type; + size_t size; + apk_blob_t *triggers; + char script[]; +}; + +struct conv_ctx { + struct apk_database *db; + struct adb_obj pkgs; + + struct list_head script_head; + struct adb dbi; + struct adb dbp; + int found; +}; + +static int read_script(void *pctx, const struct apk_file_info *ae, struct apk_istream *is) +{ + struct conv_ctx *ctx = pctx; + struct conv_script *s; + char *fncsum, *fnaction; + apk_blob_t blob; + int type; + + /* The scripts db expects regular file with name in format: + * pkgname-version.identity.action */ + if (!S_ISREG(ae->mode)) return 0; + fnaction = memrchr(ae->name, '.', strlen(ae->name)); + if (!fnaction || fnaction == ae->name) return 0; + fncsum = memrchr(ae->name, '.', fnaction - ae->name - 1); + if (!fncsum) return 0; + fnaction++; + fncsum++; + + /* Parse it */ + type = adb_s_field_by_name(&schema_scripts, fnaction); + if (!type) return 0; + + blob = APK_BLOB_PTR_PTR(fncsum, fnaction - 2); + if (blob.len+1 > sizeof s->csum) return 0; + + s = malloc(sizeof(struct conv_script) + ae->size); + if (!s) return 0; + memset(s, 0, sizeof *s); + list_init(&s->script_node); + s->csum_len = blob.len; + memcpy(s->csum, blob.ptr, blob.len); + s->type = type; + s->size = ae->size; + apk_istream_read(is, s->script, s->size); + if (s->script[s->size-1] == '\n') { + while (s->size > 1 && s->script[s->size-2] == '\n') + s->size--; + } + list_add_tail(&s->script_node, &ctx->script_head); + + return 0; +} + +static struct conv_script *find_pkg(struct conv_ctx *ctx, apk_blob_t identity, int type) +{ + struct conv_script *s; + list_for_each_entry(s, &ctx->script_head, script_node) + if (apk_blob_compare(APK_BLOB_PTR_LEN(s->csum, s->csum_len), identity) == 0) + return s; + return 0; +} + +static int read_triggers(struct conv_ctx *ctx, struct apk_istream *is) +{ + apk_blob_t l, r, nl = APK_BLOB_STR("\n"), spc = APK_BLOB_STR(" "); + struct conv_script *s; + + if (IS_ERR(is)) return PTR_ERR(is); + + while (!APK_BLOB_IS_NULL(l = apk_istream_get_delim(is, nl))) { + if (!apk_blob_split(l, spc, &l, &r)) continue; + s = find_pkg(ctx, l, ADBI_SCRPT_TRIGGER); + if (!s) continue; + + s->triggers = apk_atomize_dup(&ctx->db->atoms, r); + } + + apk_istream_close(is); + return 0; +} + +static void convert_idb(struct conv_ctx *ctx, struct apk_istream *is) +{ + struct apk_checksum csum; + struct adb_obj pkg, pkginfo, files, file, paths, path, scripts, triggers; + apk_blob_t l, val, spc = APK_BLOB_STR(" "), nl = APK_BLOB_STR("\n"); + struct conv_script *s; + int i; + + adb_wo_alloca(&scripts, &schema_scripts, &ctx->dbp); + adb_wo_alloca(&triggers, &schema_string_array, &ctx->dbp); + adb_wo_alloca(&pkginfo, &schema_pkginfo, &ctx->dbp); + adb_wo_alloca(&files, &schema_file_array, &ctx->dbp); + adb_wo_alloca(&file, &schema_file, &ctx->dbp); + adb_wo_alloca(&paths, &schema_path_array, &ctx->dbp); + adb_wo_alloca(&path, &schema_path, &ctx->dbp); + adb_wo_alloca(&pkg, &schema_package, &ctx->dbp); + + while (!APK_BLOB_IS_NULL(l = apk_istream_get_delim(is, nl))) { + if (l.len < 2) { + adb_wa_append_obj(&files, &file); + adb_wo_obj(&path, ADBI_FI_FILES, &files); + adb_wa_append_obj(&paths, &path); + + adb_wo_obj(&pkg, ADBI_PKG_PKGINFO, &pkginfo); + adb_wo_obj(&pkg, ADBI_PKG_PATHS, &paths); + adb_w_rootobj(&pkg); + + adb_wa_append(&ctx->pkgs, adb_w_adb(&ctx->dbi, &ctx->dbp)); + adb_reset(&ctx->dbp); + continue; + } + val = APK_BLOB_PTR_LEN(l.ptr+2, l.len-2); + i = adb_pkg_field_index(l.ptr[0]); + if (i > 0) adb_wo_pkginfo(&pkginfo, i, val); + + switch (l.ptr[0]) { + case 'C': // pkg checksum + list_for_each_entry(s, &ctx->script_head, script_node) { + if (apk_blob_compare(APK_BLOB_PTR_LEN(s->csum, s->csum_len), val) != 0) + continue; + + adb_wo_blob(&scripts, s->type, APK_BLOB_PTR_LEN(s->script, s->size)); + if (s->type == ADBI_SCRPT_TRIGGER && s->triggers) { + apk_blob_t r = *s->triggers, l = *s->triggers; + while (apk_blob_split(r, spc, &l, &r)) + adb_wa_append(&triggers, adb_w_blob(&ctx->dbp, l)); + adb_wa_append(&triggers, adb_w_blob(&ctx->dbp, r)); + adb_wo_obj(&pkg, ADBI_PKG_TRIGGERS, &triggers); + } + } + adb_wo_obj(&pkg, ADBI_PKG_SCRIPTS, &scripts); + break; + case 'F': // directory name + adb_wa_append_obj(&files, &file); + adb_wo_obj(&path, ADBI_FI_FILES, &files); + adb_wa_append_obj(&paths, &path); + + adb_wo_blob(&path, ADBI_FI_NAME, val); + break; + case 'M': // directory mode: uid:gid:mode:xattrcsum + adb_wo_int(&path, ADBI_FI_UID, apk_blob_pull_uint(&val, 10)); + apk_blob_pull_char(&val, ':'); + adb_wo_int(&path, ADBI_FI_GID, apk_blob_pull_uint(&val, 10)); + apk_blob_pull_char(&val, ':'); + adb_wo_int(&path, ADBI_FI_MODE, apk_blob_pull_uint(&val, 8)); + break; + case 'R': // file name + adb_wa_append_obj(&files, &file); + adb_wo_blob(&file, ADBI_FI_NAME, val); + break; + case 'a': // file mode: uid:gid:mode:xattrcsum + adb_wo_int(&file, ADBI_FI_UID, apk_blob_pull_uint(&val, 10)); + apk_blob_pull_char(&val, ':'); + adb_wo_int(&file, ADBI_FI_GID, apk_blob_pull_uint(&val, 10)); + apk_blob_pull_char(&val, ':'); + adb_wo_int(&file, ADBI_FI_MODE, apk_blob_pull_uint(&val, 8)); + break; + case 'Z': // file content hash + apk_blob_pull_csum(&val, &csum); + adb_wo_blob(&file, ADBI_FI_HASHES, APK_BLOB_CSUM(csum)); + break; + case 's': // repository_tag + case 'f': // fix required (flags: fsx) + /* FIXME */ + break; + default: + break; + } + } +} + +static int conv_main(void *pctx, struct apk_database *db, struct apk_string_array *args) +{ + struct conv_ctx *ctx = pctx; + struct adb_obj idb; + int r; + + ctx->db = db; + list_init(&ctx->script_head); + + adb_w_init_alloca(&ctx->dbi, ADB_SCHEMA_INSTALLED_DB, 10); + adb_w_init_alloca(&ctx->dbp, ADB_SCHEMA_PACKAGE, 100); + adb_wo_alloca(&idb, &schema_idb, &ctx->dbi); + adb_wo_alloca(&ctx->pkgs, &schema_package_adb_array, &ctx->dbi); + + apk_tar_parse( + apk_istream_from_file(db->root_fd, "lib/apk/db/scripts.tar"), + read_script, ctx, &db->id_cache); + + read_triggers(ctx, apk_istream_from_file(db->root_fd, "lib/apk/db/triggers")); + + convert_idb(ctx, apk_istream_from_fd_url(db->root_fd, "lib/apk/db/installed")); + + adb_wo_obj(&idb, ADBI_IDB_PACKAGES, &ctx->pkgs); + adb_w_rootobj(&idb); + + r = adb_c_create( + //apk_ostream_to_file(db->root_fd, "lib/apk/db/installed.adb", 0644), + apk_ostream_to_file(AT_FDCWD, "installed.adb", 0, 0644), + &ctx->dbi, &db->trust); + if (r == 0) { + // unlink old files + } + + adb_free(&ctx->dbi); + adb_free(&ctx->dbp); + + return r; +} + +static struct apk_applet apk_convdb = { + .name = "convdb", + .open_flags = APK_OPENF_READ | APK_OPENF_NO_STATE | APK_OPENF_NO_REPOS, + .context_size = sizeof(struct conv_ctx), + .optgroups = { &optgroup_global, &optgroup_signing }, + .main = conv_main, +}; +APK_DEFINE_APPLET(apk_convdb); diff --git a/src/app_convndx.c b/src/app_convndx.c new file mode 100644 index 0000000..a19b322 --- /dev/null +++ b/src/app_convndx.c @@ -0,0 +1,105 @@ +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <assert.h> + +#include "apk_adb.h" +#include "apk_applet.h" + +struct conv_ctx { + struct apk_database *db; + struct adb_obj pkgs; + struct adb dbi; + struct apk_sign_ctx sctx; + int found; +}; + +static void convert_index(struct conv_ctx *ctx, struct apk_istream *is) +{ + struct adb_obj pkginfo; + apk_blob_t token = APK_BLOB_STR("\n"), l; + int i; + + adb_wo_alloca(&pkginfo, &schema_pkginfo, &ctx->dbi); + + while (!APK_BLOB_IS_NULL(l = apk_istream_get_delim(is, token))) { + if (l.len < 2) { + adb_wa_append_obj(&ctx->pkgs, &pkginfo); + continue; + } + i = adb_pkg_field_index(l.ptr[0]); + if (i > 0) adb_wo_pkginfo(&pkginfo, i, APK_BLOB_PTR_LEN(l.ptr+2, l.len-2)); + } +} + +static int load_apkindex(void *sctx, const struct apk_file_info *fi, + struct apk_istream *is) +{ + struct conv_ctx *ctx = sctx; + int r; + + r = apk_sign_ctx_process_file(&ctx->sctx, fi, is); + if (r <= 0) return r; + + if (strcmp(fi->name, "APKINDEX") == 0) { + ctx->found = 1; + convert_index(ctx, is); + apk_istream_close(is); + } + + return 0; +} + +static int load_index(struct conv_ctx *ctx, struct apk_istream *is) +{ + int r = 0; + + if (IS_ERR_OR_NULL(is)) return is ? PTR_ERR(is) : -EINVAL; + + ctx->found = 0; + apk_sign_ctx_init(&ctx->sctx, APK_SIGN_VERIFY, NULL, ctx->db->keys_fd); + r = apk_tar_parse( + apk_istream_gunzip_mpart(is, apk_sign_ctx_mpart_cb, &ctx->sctx), + load_apkindex, ctx, &ctx->db->id_cache); + apk_sign_ctx_free(&ctx->sctx); + if (r >= 0 && ctx->found == 0) r = -ENOMSG; + + return r; +} + +static int conv_main(void *pctx, struct apk_database *db, struct apk_string_array *args) +{ + char **arg; + struct conv_ctx *ctx = pctx; + struct adb_obj ndx; + int r; + + ctx->db = db; + adb_w_init_alloca(&ctx->dbi, ADB_SCHEMA_INDEX, 1000); + adb_wo_alloca(&ndx, &schema_index, &ctx->dbi); + adb_wo_alloca(&ctx->pkgs, &schema_pkginfo_array, &ctx->dbi); + + foreach_array_item(arg, args) { + r = load_index(ctx, apk_istream_from_url(*arg)); + if (r) goto err; + fprintf(stderr, "%s: %u packages\n", *arg, adb_ra_num(&ctx->pkgs)); + } + + adb_wo_obj(&ndx, ADBI_NDX_PACKAGES, &ctx->pkgs); + adb_w_rootobj(&ndx); + + r = adb_c_create(apk_ostream_to_fd(STDOUT_FILENO), &ctx->dbi, &db->trust); +err: + adb_free(&ctx->dbi); + + return r; +} + +static struct apk_applet apk_convndx = { + .name = "convndx", + .open_flags = APK_OPENF_READ | APK_OPENF_NO_STATE | APK_OPENF_NO_REPOS, + .context_size = sizeof(struct conv_ctx), + .optgroups = { &optgroup_global, &optgroup_signing }, + .main = conv_main, +}; +APK_DEFINE_APPLET(apk_convndx); diff --git a/src/app_mkndx.c b/src/app_mkndx.c new file mode 100644 index 0000000..d06576d --- /dev/null +++ b/src/app_mkndx.c @@ -0,0 +1,329 @@ +/* app_mkndx.c - Alpine Package Keeper (APK) + * + * Copyright (C) 2005-2008 Natanael Copa <n@tanael.org> + * Copyright (C) 2008-2020 Timo Teräs <timo.teras@iki.fi> + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. See http://www.gnu.org/ for details. + */ + +#include <errno.h> +#include <stdio.h> +#include <fcntl.h> +#include <assert.h> +#include <unistd.h> +#include <sys/stat.h> + +#include "apk_adb.h" +#include "apk_applet.h" +#include "apk_database.h" +#include "apk_print.h" + +struct mkndx_ctx { + const char *index; + const char *output; + const char *description; + apk_blob_t rewrite_arch; + + apk_blob_t r; + struct adb db; + struct adb_obj pkgs; + time_t index_mtime; + + struct apk_sign_ctx sctx; + size_t file_size; +}; + +#define MKNDX_OPTIONS(OPT) \ + OPT(OPT_MKNDX_description, APK_OPT_ARG APK_OPT_SH("d") "description") \ + OPT(OPT_MKNDX_index, APK_OPT_ARG APK_OPT_SH("x") "index") \ + OPT(OPT_MKNDX_output, APK_OPT_ARG APK_OPT_SH("o") "output") \ + OPT(OPT_MKNDX_rewrite_arch, APK_OPT_ARG "rewrite-arch") + +APK_OPT_APPLET(option_desc, MKNDX_OPTIONS); + +static int option_parse_applet(void *ctx, struct apk_db_options *dbopts, int optch, const char *optarg) +{ + struct mkndx_ctx *ictx = ctx; + + switch (optch) { + case OPT_MKNDX_index: + ictx->index = optarg; + break; + case OPT_MKNDX_output: + ictx->output = optarg; + break; + case OPT_MKNDX_description: + ictx->description = optarg; + break; + case OPT_MKNDX_rewrite_arch: + ictx->rewrite_arch = APK_BLOB_STR(optarg); + break; + default: + return -ENOTSUP; + } + return 0; +} + +static const struct apk_option_group optgroup_applet = { + .desc = option_desc, + .parse = option_parse_applet, +}; + +struct field { + apk_blob_t str; + unsigned int ndx; +}; +#define FIELD(s, n) { .str = APK_BLOB_STRLIT(s), .ndx = n } + +static int cmpfield(const void *pa, const void *pb) +{ + const struct field *a = pa, *b = pb; + return apk_blob_sort(a->str, b->str); +} + +static adb_val_t mkndx_read_v2_pkginfo(struct adb *db, struct apk_istream *is, size_t file_size, struct apk_sign_ctx *sctx, apk_blob_t rewrite_arch) +{ + static struct field fields[] = { + FIELD("arch", ADBI_PI_ARCH), + FIELD("builddate", ADBI_PI_BUILD_TIME), + FIELD("commit", ADBI_PI_REPO_COMMIT), + FIELD("datahash", 0), + FIELD("depend", ADBI_PI_DEPENDS), + FIELD("install_if", ADBI_PI_INSTALL_IF), + FIELD("license", ADBI_PI_LICENSE), + FIELD("maintainer", ADBI_PI_MAINTAINER), + FIELD("origin", ADBI_PI_ORIGIN), + FIELD("packager", 0), + FIELD("pkgdesc", ADBI_PI_DESCRIPTION), + FIELD("pkgname", ADBI_PI_NAME), + FIELD("pkgver", ADBI_PI_VERSION), + FIELD("provider_priority", 0), + FIELD("provides", ADBI_PI_PROVIDES), + FIELD("replaces", ADBI_PI_REPLACES), + FIELD("replaces_priority", ADBI_PI_PRIORITY), + FIELD("size", ADBI_PI_INSTALLED_SIZE), + FIELD("triggers", 0), + FIELD("url", ADBI_PI_URL), + }; + struct field *f, key; + struct adb_obj pkginfo, deps[3]; + apk_blob_t line, l, r, token = APK_BLOB_STR("\n"), bdep; + int e = 0, i = 0; + + adb_wo_alloca(&pkginfo, &schema_pkginfo, db); + adb_wo_alloca(&deps[0], &schema_dependency_array, db); + adb_wo_alloca(&deps[1], &schema_dependency_array, db); + adb_wo_alloca(&deps[2], &schema_dependency_array, db); + + while (!APK_BLOB_IS_NULL(line = apk_istream_get_delim(is, token))) { + if (sctx) apk_sign_ctx_parse_pkginfo_line(sctx, line); + if (line.len < 1 || line.ptr[0] == '#') continue; + if (!apk_blob_split(line, APK_BLOB_STR(" = "), &l, &r)) continue; + + key.str = l; + f = bsearch(&key, fields, ARRAY_SIZE(fields), sizeof(fields[0]), cmpfield); + if (!f || f->ndx == 0) continue; + + if (adb_ro_val(&pkginfo, f->ndx) != ADB_NULL) { + /* Workaround abuild bug that emitted multiple license lines */ + if (f->ndx == ADBI_PI_LICENSE) continue; + e = ADB_ERROR(EAPKFORMAT); + continue; + } + + switch (f->ndx) { + case ADBI_PI_ARCH: + if (!APK_BLOB_IS_NULL(rewrite_arch)) r = rewrite_arch; + break; + case ADBI_PI_DEPENDS: + i = 0; + goto parse_deps; + case ADBI_PI_PROVIDES: + i = 1; + goto parse_deps; + case ADBI_PI_REPLACES: + i = 2; + parse_deps: + while (!ADB_IS_ERROR(e) && apk_dep_split(&r, &bdep)) + e = adb_wa_append_fromstring(&deps[i], bdep); + continue; + } + adb_wo_pkginfo(&pkginfo, f->ndx, r); + } + if (ADB_IS_ERROR(e)) return e; + + adb_wo_arr(&pkginfo, ADBI_PI_DEPENDS, &deps[0]); + adb_wo_arr(&pkginfo, ADBI_PI_PROVIDES, &deps[1]); + adb_wo_arr(&pkginfo, ADBI_PI_REPLACES, &deps[2]); + adb_wo_int(&pkginfo, ADBI_PI_FILE_SIZE, file_size); + + return adb_w_obj(&pkginfo); +} + +static int mkndx_parse_v2_tar(void *pctx, const struct apk_file_info *ae, struct apk_istream *is) +{ + struct mkndx_ctx *ctx = pctx; + adb_val_t o; + int r; + + r = apk_sign_ctx_process_file(&ctx->sctx, ae, is); + if (r <= 0) return r; + if (ctx->sctx.control_verified) return -ECANCELED; + if (!ctx->sctx.control_started || ctx->sctx.data_started) return 0; + + if (strcmp(ae->name, ".PKGINFO") == 0) { + o = adb_wa_append( + &ctx->pkgs, + mkndx_read_v2_pkginfo( + &ctx->db, is, ctx->file_size, &ctx->sctx, + ctx->rewrite_arch)); + if (ADB_IS_ERROR(o)) return -ADB_VAL_VALUE(o); + } + + return 0; +} + +static int mkndx_main(void *pctx, struct apk_database *db, struct apk_string_array *args) +{ + struct adb odb, tmpdb; + struct adb_obj oroot, opkgs, ndx, tmpl; + struct apk_file_info fi; + adb_val_t match; + int r, found, errors = 0, newpkgs = 0, numpkgs; + struct mkndx_ctx *ctx = pctx; + char **parg; + time_t index_mtime = 0; + + if (ctx->output == NULL) { + apk_error("Please specify --output FILE"); + return -1; + } + + adb_w_init_tmp(&tmpdb, 200); + adb_wo_alloca(&tmpl, &schema_pkginfo, &tmpdb); + + adb_w_init_alloca(&ctx->db, ADB_SCHEMA_INDEX, 1000); + adb_wo_alloca(&ndx, &schema_index, &ctx->db); + adb_wo_alloca(&ctx->pkgs, &schema_pkginfo_array, &ctx->db); + + if (ctx->index) { + apk_fileinfo_get(AT_FDCWD, ctx->index, APK_CHECKSUM_NONE, &fi, 0); + index_mtime = fi.mtime; + + r = adb_m_map(&odb, open(ctx->index, O_RDONLY), ADB_SCHEMA_INDEX, &db->trust); + if (r) { + apk_error("%s: %s", ctx->index, apk_error_str(r)); + return r; + } + adb_ro_obj(adb_r_rootobj(&odb, &oroot, &schema_index), ADBI_NDX_PACKAGES, &opkgs); + } + + foreach_array_item(parg, args) { + r = apk_fileinfo_get(AT_FDCWD, *parg, APK_CHECKSUM_NONE, &fi, 0); + if (r < 0) { + err_pkg: + apk_error("%s: %s", *parg, apk_error_str(r)); + errors++; + continue; + } + ctx->file_size = fi.size; + + found = FALSE; + if (index_mtime >= fi.mtime) { + char *fname, *fend; + apk_blob_t bname, bver; + + /* Check that it looks like a package name */ + fname = strrchr(*parg, '/'); + if (fname == NULL) + fname = *parg; + else + fname++; + fend = strstr(fname, ".apk"); + if (!fend) goto do_file; + if (fend-fname > 10 && fend[-9] == '.') fend -= 9; + if (apk_pkg_parse_name(APK_BLOB_PTR_PTR(fname, fend-1), + &bname, &bver) < 0) + goto do_file; + + adb_wo_resetdb(&tmpl); + adb_wo_blob(&tmpl, ADBI_PI_NAME, bname); + adb_wo_blob(&tmpl, ADBI_PI_VERSION, bver); + match = adb_w_obj(&tmpl); + + for (int i = 0; (i = adb_ra_find(&opkgs, i, &tmpdb, match)) > 0; ) { + struct adb_obj pkg; + adb_val_t val; + + adb_ro_obj(&opkgs, i, &pkg); + if (apk_blob_compare(bname, adb_ro_blob(&pkg, ADBI_PI_NAME))) continue; + if (apk_blob_compare(bver, adb_ro_blob(&pkg, ADBI_PI_VERSION))) continue; + if (fi.size != adb_ro_int(&pkg, ADBI_PI_FILE_SIZE)) continue; + + val = adb_wa_append(&ctx->pkgs, adb_w_copy(&ctx->db, &odb, adb_ro_val(&opkgs, i))); + if (ADB_IS_ERROR(val)) + errors++; + + found = TRUE; + break; + } + } + if (!found) { + do_file: + apk_sign_ctx_init(&ctx->sctx, APK_SIGN_VERIFY, NULL, db->keys_fd); + r = apk_tar_parse( + apk_istream_gunzip_mpart(apk_istream_from_file(AT_FDCWD, *parg), apk_sign_ctx_mpart_cb, &ctx->sctx), + mkndx_parse_v2_tar, ctx, &db->id_cache); + apk_sign_ctx_free(&ctx->sctx); + if (r < 0 && r != -ECANCELED) goto err_pkg; + newpkgs++; + } + } + if (errors) { + apk_error("%d errors, not creating index", errors); + return -1; + } + + numpkgs = adb_ra_num(&ctx->pkgs); + adb_wo_blob(&ndx, ADBI_NDX_DESCRIPTION, APK_BLOB_STR(ctx->description)); + adb_wo_obj(&ndx, ADBI_NDX_PACKAGES, &ctx->pkgs); + adb_w_rootobj(&ndx); + + r = adb_c_create( + apk_ostream_to_file(AT_FDCWD, ctx->output, NULL, 0644), + &ctx->db, &db->trust); + + adb_free(&ctx->db); + adb_free(&odb); + + if (r == 0) + apk_message("Index has %d packages (of which %d are new)", + numpkgs, newpkgs); + else + apk_error("Index creation failed: %s", apk_error_str(r)); + + +#if 0 + apk_hash_foreach(&db->available.names, warn_if_no_providers, &counts); + + if (counts.unsatisfied != 0) + apk_warning("Total of %d unsatisfiable package " + "names. Your repository may be broken.", + counts.unsatisfied); +#endif + + return r; +} + +static struct apk_applet apk_mkndx = { + .name = "mkndx", + .open_flags = APK_OPENF_READ | APK_OPENF_NO_STATE | APK_OPENF_NO_REPOS, + .context_size = sizeof(struct mkndx_ctx), + .optgroups = { &optgroup_global, &optgroup_signing, &optgroup_applet }, + .main = mkndx_main, +}; + +APK_DEFINE_APPLET(apk_mkndx); @@ -256,6 +256,13 @@ int apk_blob_compare(apk_blob_t a, apk_blob_t b) return 1; } +int apk_blob_sort(apk_blob_t a, apk_blob_t b) +{ + int s = memcmp(a.ptr, b.ptr, min(a.len, b.len)); + if (s != 0) return s; + return a.len - b.len; +} + int apk_blob_ends_with(apk_blob_t a, apk_blob_t b) { if (a.len < b.len) return 0; diff --git a/src/database.c b/src/database.c index bbf072e..8298f72 100644 --- a/src/database.c +++ b/src/database.c @@ -1676,6 +1676,12 @@ int apk_db_open(struct apk_database *db, struct apk_db_options *dbopts) dbopts->keys_dir ?: "etc/apk/keys", O_RDONLY | O_CLOEXEC); + r = adb_trust_init(&db->trust, dup(db->keys_fd), dbopts->private_keys); + if (r) { + msg = "Unable to read trusted keys"; + goto ret_r; + } + if (apk_flags & APK_OVERLAY_FROM_STDIN) { apk_flags &= ~APK_OVERLAY_FROM_STDIN; apk_db_read_overlay(db, apk_istream_from_fd(STDIN_FILENO)); @@ -1852,6 +1858,8 @@ void apk_db_close(struct apk_database *db) db->cache_remount_dir = NULL; } + adb_trust_free(&db->trust); + if (db->keys_fd) close(db->keys_fd); if (db->cache_fd) @@ -827,7 +827,7 @@ struct apk_istream *apk_istream_from_file_gz(int atfd, const char *file) struct apk_fd_ostream { struct apk_ostream os; - int fd, rc; + int fd; const char *file, *tmpfile; int atfd; @@ -860,7 +860,7 @@ static ssize_t fdo_flush(struct apk_fd_ostream *fos) return 0; if ((r = safe_write(fos->fd, fos->buffer, fos->bytes)) != fos->bytes) { - fos->rc = r < 0 ? r : -EIO; + apk_ostream_cancel(&fos->os, r < 0 ? r : -EIO); return r; } @@ -879,8 +879,7 @@ static ssize_t fdo_write(struct apk_ostream *os, const void *ptr, size_t size) return r; if (size >= sizeof(fos->buffer) / 2) { r = safe_write(fos->fd, ptr, size); - if (r != size) - fos->rc = r < 0 ? r : -EIO; + if (r != size) apk_ostream_cancel(&fos->os, r < 0 ? r : -EIO); return r; } } @@ -897,7 +896,7 @@ static int fdo_close(struct apk_ostream *os) int rc; fdo_flush(fos); - rc = fos->rc; + rc = fos->os.rc; if (fos->fd > STDERR_FILENO && close(fos->fd) < 0) diff --git a/src/package.c b/src/package.c index 6e7a062..4fca548 100644 --- a/src/package.c +++ b/src/package.c @@ -28,17 +28,17 @@ #include "apk_database.h" #include "apk_print.h" -static const apk_spn_match_def apk_spn_dependency_comparer = { +const apk_spn_match_def apk_spn_dependency_comparer = { [7] = (1<<4) /*<*/ | (1<<5) /*=*/ | (1<<6) /*<*/, [15] = (1<<6) /*~*/ }; -static const apk_spn_match_def apk_spn_dependency_separator = { +const apk_spn_match_def apk_spn_dependency_separator = { [1] = (1<<2) /*\n*/, [4] = (1<<0) /* */, }; -static const apk_spn_match_def apk_spn_repotag_separator = { +const apk_spn_match_def apk_spn_repotag_separator = { [8] = (1<<0) /*@*/ }; diff --git a/src/print.c b/src/print.c index 38dad1c..7be181e 100644 --- a/src/print.c +++ b/src/print.c @@ -179,6 +179,10 @@ const char *apk_error_str(int error) return "invalid URL (check your repositories file)"; case EAPKSTALEINDEX: return "package mentioned in index not found (try 'apk update')"; + case EAPKFORMAT: + return "package file format error"; + case EAPKDEPFORMAT: + return "package dependency format error"; default: return strerror(error); } diff --git a/src/version.c b/src/version.c index dc874a6..f6fc5af 100644 --- a/src/version.c +++ b/src/version.c @@ -154,6 +154,8 @@ const char *apk_version_op_string(int mask) return ">"; case APK_DEPMASK_CHECKSUM: return "><"; + case APK_DEPMASK_ANY: + return ""; default: return "?"; } |