chibipub

experimental activitypub node in C
git clone git://jb55.com/chibipub
Log | Files | Refs | README | LICENSE

commit deca2e241bc1d924cc914e6c8e995f42f7e31bd7
parent 8044145b1a5e7bd1d4b18c167f8c1b8081d39c50
Author: William Casarin <jb55@jb55.com>
Date:   Sun, 14 Feb 2021 15:42:14 -0800

sigcheck: pubkey fetching actually working

Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
Msrc/io.c | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/io.h | 3+++
Msrc/sigcheck.c | 141+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Msrc/ubjson.c | 34+++++++++++++++++++++++++++++++---
4 files changed, 208 insertions(+), 23 deletions(-)

diff --git a/src/io.c b/src/io.c @@ -2,6 +2,7 @@ #include <sys/types.h> #include <sys/stat.h> #include <sys/mman.h> +#include <sys/wait.h> #include <unistd.h> #include <fcntl.h> @@ -22,6 +23,27 @@ int map_file(const char *filename, unsigned char **p, size_t *flen) return *p != MAP_FAILED; } +int write_file(const char *filename, unsigned char *out, size_t len) +{ + FILE *file = NULL; + size_t written; + + file = fopen(filename, "wb"); + if (file == NULL) + return 0; + + written = fwrite(out, 1, len, file); + + if (written != len) { + fprintf(stderr, "written != len, %ld != %ld\n", written, len); + fclose(file); + return 0; + } + + fclose(file); + return 1; +} + int read_fd(FILE *fd, unsigned char *buf, int buflen, int *written) { unsigned char *p = buf; @@ -49,3 +71,34 @@ int read_file(const char *filename, unsigned char *out, int out_len, int *writte return read_fd(file, out, out_len, written); } + +int dir_exists(const char *path) +{ + struct stat st; + + if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) { + return 1; + } + + return 0; +} + +// todo: make this less dumb +int mkdirp(const char *path) +{ + int status; + + switch (fork()) { + case -1: + return 0; + case 0: + /* child, run man command. */ + execlp("mkdir", "mkdir", "-p", path, (char *)NULL); + return 0; + default: + break; + } + + wait(&status); + return WIFEXITED(status) && WEXITSTATUS(status) == 0; +} diff --git a/src/io.h b/src/io.h @@ -9,5 +9,8 @@ int map_file(const char *filename, unsigned char **p, size_t *flen); int read_fd(FILE *fd, unsigned char *buf, int buflen, int *written); int read_file(const char *filename, unsigned char *out, int out_len, int *written); +int write_file(const char *filename, unsigned char *data, size_t len); +int dir_exists(const char *path); +int mkdirp(const char *path); #endif /* CHIBIPUB_IO */ diff --git a/src/sigcheck.c b/src/sigcheck.c @@ -37,6 +37,7 @@ struct key_writer { struct keyid_pubkey *pubkey; const char *keyid; struct cursor slice; + struct errors errs; int flags; }; @@ -172,7 +173,7 @@ static int gather_keyids(unsigned char *json, int json_len, return 1; } -static int hex_bytes(unsigned char *bytes, int n_bytes, unsigned char *buf, +static int hex_bytes(unsigned char *bytes, int n_bytes, char *buf, int buf_size) { static const char *hex = "0123456789abcdef"; @@ -190,25 +191,18 @@ static int hex_bytes(unsigned char *bytes, int n_bytes, unsigned char *buf, return 1; } -static int get_cached_pubkey(unsigned char *keyid, int size, - unsigned char *buf, int buflen, int *pubkey_size) +static int get_cached_pubkey(const char *keyid, unsigned char *buf, int buflen, int *pubkey_size) { unsigned char hash[32]; - unsigned char hash_str[64]; - char path[128] = {0}; - mode_t mode = 0777; + char hash_str[64]; + char path[2048] = {0}; - // if we sucessfully created a directory, then we definitely - // don't have any cached pubkeys - if (0 == mkdir(".chibipub", mode)) { + // no objects dir, definitely don't have any cached pubkeys + if (!dir_exists(".chibipub/objects")) { return 0; } - if (0 == mkdir(".chibipub/objects", mode)) { - return 0; - } - - blake3_hash(keyid, size, hash); + blake3_hash((unsigned char *)keyid, strlen(keyid), hash); if (!hex_bytes(hash, 32, hash_str, 64)) { assert(0); } @@ -276,12 +270,18 @@ static size_t write_cb(char *data, size_t n, size_t l, void *userp) static int add_signature_transfer(CURLM *cm, struct key_writer *writer) { + struct curl_slist *list = NULL; + CURL *eh = curl_easy_init(); writer->curl = eh; curl_easy_setopt(eh, CURLOPT_WRITEFUNCTION, write_cb); curl_easy_setopt(eh, CURLOPT_WRITEDATA, writer); curl_easy_setopt(eh, CURLOPT_URL, writer->keyid); curl_easy_setopt(eh, CURLOPT_PRIVATE, writer); + + list = curl_slist_append(list, "Accept: application/ld+json"); + curl_easy_setopt(eh, CURLOPT_HTTPHEADER, list); + curl_multi_add_handle(cm, eh); return 1; @@ -294,10 +294,104 @@ static void prepare_transfers(CURLM **cm) curl_multi_setopt(*cm, CURLMOPT_MAXCONNECTS, (long)10); } -static int handle_transfer_msg(CURLM *cm, CURLMsg *msg, int *transfers, +static int write_pubkey_cache(struct key_writer *writer, struct cursor arena) +{ + unsigned char *hash; + char *hash_str; + char *path; + + if (!(hash = cursor_alloc(&arena, 32))) { + return 0; + } + + blake3_hash((unsigned char*)writer->keyid, strlen(writer->keyid), hash); + + if (!(hash_str = cursor_alloc(&arena, 64))) { + note_error(&writer->errs, "alloc hash_str"); + return 0; + } + + if (!hex_bytes(hash, 32, hash_str, 64)) { + note_error(&writer->errs, "hex_bytes"); + return 0; + } + + path = (char*)arena.p; + + if (!(push_str(&arena, ".chibipub/objects/") && + push_sized_str(&arena, hash_str, 2) && + push_byte(&arena, 0) && + mkdirp(path))) + { + note_error(&writer->errs, "mkdir -p '%s'", path); + return 0; + } + + // continue path + arena.p--; + + if (!(push_byte(&arena,'/') && + push_sized_str(&arena, hash_str, 64) && + push_byte(&arena, 0))) + { + note_error(&writer->errs, "push path oob"); + return 0; + } + + if (!write_file(path, writer->pubkey->data, writer->pubkey->len)) { + note_error(&writer->errs, "write_file '%s' pubkey failed", path); + return 0; + } + + return 1; +} + +static int handle_pubkey_response(struct cursor arena, + struct key_writer *writer, unsigned char *json, int json_len) +{ + unsigned char *start; + struct ubjson ubjson; + struct json_handlers handler; + struct json_parser parser; + struct json val; + static const char *path[] = {"publicKey", "publicKeyPem"}; + + start = arena.p; + + make_ubjson_handlers(&handler, &arena); + init_json_parser(&parser, json, json_len, &handler); + if (!parse_json(&parser)) { + note_error(&writer->errs, "keyid '%s' activity json parse failed", + writer->keyid); + return 0; + } + + init_ubjson(&ubjson, start, arena.p - start); + ubjson.data_end = arena.p; + + if (!ubjson_lookup(&ubjson, path, ARRAY_SIZE(path), &val)) { + note_error(&writer->errs, + "keyid '%s' could not find 'publicKeyPem' field", + writer->keyid); + return 0; + } + + writer->pubkey->data = (unsigned char*)val.string; + writer->pubkey->len = val.len; + + if (!write_pubkey_cache(writer, arena)) { + note_error(&writer->errs, "failed to write pubkey"); + return 0; + } + + return 1; +} + +static int handle_transfer_msg(struct cursor *arena, CURLM *cm, CURLMsg *msg, int *transfers, struct key_writer *to_fetch, int n_to_fetch) { struct key_writer *writer; + int ok = 1; if (msg->msg == CURLMSG_DONE) { CURL *e = msg->easy_handle; @@ -309,6 +403,12 @@ static int handle_transfer_msg(CURLM *cm, CURLMsg *msg, int *transfers, curl_easy_strerror(msg->data.result), writer->keyid); + if (msg->data.result == CURLE_OK) { + ok = handle_pubkey_response(*arena, writer, + writer->pubkey->data, + writer->pubkey->len); + } + curl_multi_remove_handle(cm, e); curl_easy_cleanup(e); } else { @@ -320,10 +420,10 @@ static int handle_transfer_msg(CURLM *cm, CURLMsg *msg, int *transfers, *transfers = *transfers + 1; } - return 1; + return ok; } -static int perform_transfers(CURLM *cm, int transfers, +static int perform_transfers(struct cursor *arena, CURLM *cm, int transfers, struct key_writer *to_fetch, int n_to_fetch) { CURLMsg *msg; @@ -334,7 +434,7 @@ static int perform_transfers(CURLM *cm, int transfers, curl_multi_perform(cm, &still_alive); while ((msg = curl_multi_info_read(cm, &msgs_left))) { - handle_transfer_msg( + handle_transfer_msg(arena, cm, msg, &transfers, to_fetch, n_to_fetch); } @@ -403,7 +503,7 @@ static int fetch_signatures(unsigned char *json, int json_len, return 0; } - if (get_cached_pubkey(scan.p, size, arena->p, + if (get_cached_pubkey(keyid_str, arena->p, arena->end - arena->p, &pubkey->len)) { pubkey->data = arena->p; @@ -417,6 +517,7 @@ static int fetch_signatures(unsigned char *json, int json_len, pubkey->len = 0; kw = &to_fetch[n_to_fetch++]; + init_errors(&kw->errs); kw->keyid = keyid_str; kw->arena = arena; kw->pubkey = pubkey; @@ -427,7 +528,7 @@ static int fetch_signatures(unsigned char *json, int json_len, add_signature_transfer(cm, &to_fetch[transfers]); } - if (!perform_transfers(cm, transfers, to_fetch, n_to_fetch)) { + if (!perform_transfers(arena, cm, transfers, to_fetch, n_to_fetch)) { return 0; } diff --git a/src/ubjson.c b/src/ubjson.c @@ -161,16 +161,32 @@ static int count_skipped(unsigned char *text, int size) return skipped; } +static char escaped_char(char c) +{ + switch (c) { + case 'n': return '\n'; + case 't': return '\t'; + case 'r': return '\r'; + case 'b': return '\b'; + case 'f': return '\f'; + } + return c; +} + static int push_unescaped(struct cursor *cur, unsigned char *text, int size) { unsigned char *p; + unsigned char c; for (p = text; p < text+size; p++) { if (*p == '\\' && (p+1 < text+size) && is_escape_char(*(p+1))) { p++; + c = (unsigned char)escaped_char((char)*p); + } else { + c = *p; } - if (!push_byte(cur, *p)) { + if (!push_byte(cur, c)) { return 0; } } @@ -217,7 +233,7 @@ static inline void copy_ubjson(struct ubjson *src, struct ubjson *dst) { dst->data_end = src->data_end; } -static int parse_ubjson_object(struct ubjson *ubjson, struct json *val) +static int parse_ubjson_container(struct ubjson *ubjson, struct json *val, char token) { char c; c = 0; @@ -226,7 +242,7 @@ static int parse_ubjson_object(struct ubjson *ubjson, struct json *val) assert(ubjson != &val->container); copy_ubjson(ubjson, &val->container); - if (!parse_char(&ubjson->cur, &c, '{')) { + if (!parse_char(&ubjson->cur, &c, token)) { note_error(&ubjson->errs, "expected '{' tag, got '%c'", c); return 0; } @@ -250,6 +266,16 @@ static int parse_ubjson_object(struct ubjson *ubjson, struct json *val) return 1; } +static inline int parse_ubjson_object(struct ubjson *ubjson, struct json *val) +{ + return parse_ubjson_container(ubjson, val, '{'); +} + +static inline int parse_ubjson_array(struct ubjson *ubjson, struct json *val) +{ + return parse_ubjson_container(ubjson, val, '['); +} + static int parse_ubjson_number(struct ubjson *ubjson, struct json *val) { if (!parse_ubjson_sized_len(ubjson, &val->number_int, &val->len)) { @@ -297,6 +323,8 @@ int parse_ubjson_value(struct ubjson *ubjson, struct json *val) return parse_ubjson_string(ubjson, val); } else if (tag == '{') { return parse_ubjson_object(ubjson, val); + } else if (tag == '[') { + return parse_ubjson_array(ubjson, val); } else if (is_number_tag(tag)) { return parse_ubjson_number(ubjson, val); } else if (tag == 'F' || tag == 'T') {