#define _POSIX_C_SOURCE 200809L #include "amduat/asl/artifact_io.h" #include "amduat/asl/asl_store_fs.h" #include "amduat/asl/asl_store_fs_meta.h" #include "amduat/asl/ref_text.h" #include "amduat/asl/store.h" #include "amduat/enc/asl1_core_codec.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const char *const AMDUATD_DEFAULT_ROOT = ".amduat-asl"; static const char *const AMDUATD_DEFAULT_SOCK = "amduatd.sock"; static volatile sig_atomic_t amduatd_should_exit = 0; static void amduatd_on_signal(int signo) { (void)signo; amduatd_should_exit = 1; } static bool amduatd_write_all(int fd, const uint8_t *buf, size_t len) { size_t off = 0; while (off < len) { ssize_t n = write(fd, buf + off, len - off); if (n < 0) { if (errno == EINTR) { continue; } return false; } if (n == 0) { return false; } off += (size_t)n; } return true; } static bool amduatd_read_exact(int fd, uint8_t *buf, size_t len) { size_t off = 0; while (off < len) { ssize_t n = read(fd, buf + off, len - off); if (n < 0) { if (errno == EINTR) { continue; } return false; } if (n == 0) { return false; } off += (size_t)n; } return true; } static bool amduatd_read_line(int fd, char *buf, size_t cap, size_t *out_len) { size_t len = 0; while (len + 1 < cap) { char c = 0; ssize_t n = read(fd, &c, 1); if (n < 0) { if (errno == EINTR) { continue; } return false; } if (n == 0) { return false; } if (c == '\n') { break; } buf[len++] = c; } if (len == 0) { return false; } if (len > 0 && buf[len - 1] == '\r') { len--; } buf[len] = '\0'; if (out_len != NULL) { *out_len = len; } return true; } typedef struct { char method[8]; char path[1024]; char content_type[128]; char accept[128]; char x_type_tag[64]; size_t content_length; } amduatd_http_req_t; static void amduatd_http_req_init(amduatd_http_req_t *req) { memset(req, 0, sizeof(*req)); } static bool amduatd_http_parse_request(int fd, amduatd_http_req_t *out_req) { char line[2048]; size_t line_len = 0; char *sp1 = NULL; char *sp2 = NULL; if (out_req == NULL) { return false; } amduatd_http_req_init(out_req); if (!amduatd_read_line(fd, line, sizeof(line), &line_len)) { return false; } sp1 = strchr(line, ' '); if (sp1 == NULL) { return false; } *sp1++ = '\0'; sp2 = strchr(sp1, ' '); if (sp2 == NULL) { return false; } *sp2++ = '\0'; if (strlen(line) >= sizeof(out_req->method)) { return false; } strncpy(out_req->method, line, sizeof(out_req->method) - 1); if (strlen(sp1) >= sizeof(out_req->path)) { return false; } strncpy(out_req->path, sp1, sizeof(out_req->path) - 1); for (;;) { if (!amduatd_read_line(fd, line, sizeof(line), &line_len)) { return false; } if (line_len == 0) { break; } if (strncasecmp(line, "Content-Length:", 15) == 0) { const char *v = line + 15; while (*v == ' ' || *v == '\t') { v++; } out_req->content_length = (size_t)strtoull(v, NULL, 10); } else if (strncasecmp(line, "Content-Type:", 13) == 0) { const char *v = line + 13; while (*v == ' ' || *v == '\t') { v++; } strncpy(out_req->content_type, v, sizeof(out_req->content_type) - 1); } else if (strncasecmp(line, "Accept:", 7) == 0) { const char *v = line + 7; while (*v == ' ' || *v == '\t') { v++; } strncpy(out_req->accept, v, sizeof(out_req->accept) - 1); } else if (strncasecmp(line, "X-Amduat-Type-Tag:", 18) == 0) { const char *v = line + 18; while (*v == ' ' || *v == '\t') { v++; } strncpy(out_req->x_type_tag, v, sizeof(out_req->x_type_tag) - 1); } } return true; } static bool amduatd_http_send_status(int fd, int code, const char *reason, const char *content_type, const uint8_t *body, size_t body_len, bool head_only) { char hdr[512]; int n = snprintf(hdr, sizeof(hdr), "HTTP/1.1 %d %s\r\n" "Content-Length: %zu\r\n" "Content-Type: %s\r\n" "Connection: close\r\n" "\r\n", code, reason != NULL ? reason : "", body_len, content_type != NULL ? content_type : "text/plain"); if (n <= 0 || (size_t)n >= sizeof(hdr)) { return false; } if (!amduatd_write_all(fd, (const uint8_t *)hdr, (size_t)n)) { return false; } if (!head_only && body != NULL && body_len != 0) { return amduatd_write_all(fd, body, body_len); } return true; } static bool amduatd_http_send_text(int fd, int code, const char *reason, const char *text, bool head_only) { const uint8_t *body = (const uint8_t *)(text != NULL ? text : ""); size_t len = text != NULL ? strlen(text) : 0; return amduatd_http_send_status( fd, code, reason, "text/plain; charset=utf-8", body, len, head_only); } static bool amduatd_http_send_json(int fd, int code, const char *reason, const char *json, bool head_only) { const uint8_t *body = (const uint8_t *)(json != NULL ? json : "{}"); size_t len = json != NULL ? strlen(json) : 2; return amduatd_http_send_status( fd, code, reason, "application/json", body, len, head_only); } static const char *amduatd_query_param(const char *path, const char *key, char *out_value, size_t out_cap) { const char *q = strchr(path, '?'); size_t key_len = 0; if (out_value != NULL && out_cap != 0) { out_value[0] = '\0'; } if (q == NULL || key == NULL || out_value == NULL || out_cap == 0) { return NULL; } q++; key_len = strlen(key); while (*q != '\0') { const char *k = q; const char *eq = strchr(k, '='); const char *amp = strchr(k, '&'); size_t klen = 0; const char *v = NULL; size_t vlen = 0; if (amp == NULL) { amp = q + strlen(q); } if (eq == NULL || eq > amp) { q = (*amp == '&') ? (amp + 1) : amp; continue; } klen = (size_t)(eq - k); if (klen == key_len && strncmp(k, key, key_len) == 0) { v = eq + 1; vlen = (size_t)(amp - v); if (vlen >= out_cap) { vlen = out_cap - 1; } memcpy(out_value, v, vlen); out_value[vlen] = '\0'; return out_value; } q = (*amp == '&') ? (amp + 1) : amp; } return NULL; } static void amduatd_path_without_query(const char *path, char *out, size_t cap) { const char *q = NULL; size_t n = 0; if (out == NULL || cap == 0) { return; } out[0] = '\0'; if (path == NULL) { return; } q = strchr(path, '?'); n = q != NULL ? (size_t)(q - path) : strlen(path); if (n >= cap) { n = cap - 1; } memcpy(out, path, n); out[n] = '\0'; } static bool amduatd_parse_type_tag_hex(const char *text, bool *out_has_type_tag, amduat_type_tag_t *out_type_tag) { char *end = NULL; unsigned long v = 0; if (out_has_type_tag != NULL) { *out_has_type_tag = false; } if (text == NULL || text[0] == '\0' || out_type_tag == NULL || out_has_type_tag == NULL) { return true; } errno = 0; v = strtoul(text, &end, 0); if (errno != 0 || end == text || *end != '\0' || v > 0xffffffffUL) { return false; } *out_has_type_tag = true; out_type_tag->tag_id = (uint32_t)v; return true; } static bool amduatd_handle_meta(int fd, const amduat_asl_store_fs_config_t *cfg, bool head_only) { char json[512]; int n = snprintf(json, sizeof(json), "{" "\"store_id\":\"%s\"," "\"encoding_profile_id\":\"0x%04x\"," "\"hash_id\":\"0x%04x\"" "}\n", cfg != NULL ? cfg->store_id : "", cfg != NULL ? (unsigned int)cfg->config.encoding_profile_id : 0u, cfg != NULL ? (unsigned int)cfg->config.hash_id : 0u); if (n <= 0 || (size_t)n >= sizeof(json)) { return amduatd_http_send_text(fd, 500, "Internal Server Error", "error\n", head_only); } return amduatd_http_send_json(fd, 200, "OK", json, head_only); } static bool amduatd_handle_get_artifact(int fd, amduat_asl_store_t *store, const char *path, bool head_only) { char no_query[1024]; char format[32]; const char *ref_text = NULL; amduat_reference_t ref; amduat_artifact_t artifact; amduat_asl_store_error_t err; bool want_artifact = false; memset(&ref, 0, sizeof(ref)); memset(&artifact, 0, sizeof(artifact)); amduatd_path_without_query(path, no_query, sizeof(no_query)); if (strncmp(no_query, "/v1/artifacts/", 14) != 0) { return amduatd_http_send_text(fd, 404, "Not Found", "not found\n", head_only); } ref_text = no_query + 14; if (ref_text[0] == '\0') { return amduatd_http_send_text(fd, 400, "Bad Request", "missing ref\n", head_only); } if (!amduat_asl_ref_decode_hex(ref_text, &ref)) { return amduatd_http_send_text(fd, 400, "Bad Request", "invalid ref\n", head_only); } memset(format, 0, sizeof(format)); if (amduatd_query_param(path, "format", format, sizeof(format)) != NULL) { if (strcmp(format, "artifact") == 0) { want_artifact = true; } else if (strcmp(format, "raw") == 0 || format[0] == '\0') { want_artifact = false; } else { free((void *)ref.digest.data); return amduatd_http_send_text(fd, 400, "Bad Request", "invalid format\n", head_only); } } err = amduat_asl_store_get(store, ref, &artifact); free((void *)ref.digest.data); if (err == AMDUAT_ASL_STORE_ERR_NOT_FOUND) { return amduatd_http_send_text(fd, 404, "Not Found", "not found\n", head_only); } if (err != AMDUAT_ASL_STORE_OK) { amduat_asl_artifact_free(&artifact); return amduatd_http_send_text(fd, 500, "Internal Server Error", "store error\n", head_only); } if (!want_artifact) { bool ok = amduatd_http_send_status( fd, 200, "OK", "application/octet-stream", artifact.bytes.data, artifact.bytes.len, head_only); amduat_asl_artifact_free(&artifact); return ok; } { amduat_octets_t out = amduat_octets(NULL, 0); bool ok = amduat_enc_asl1_core_encode_artifact_v1(artifact, &out); amduat_asl_artifact_free(&artifact); if (!ok) { return amduatd_http_send_text(fd, 500, "Internal Server Error", "encode error\n", head_only); } ok = amduatd_http_send_status(fd, 200, "OK", "application/vnd.amduat.asl.artifact+v1", out.data, out.len, head_only); free((void *)out.data); return ok; } } static bool amduatd_handle_head_artifact(int fd, amduat_asl_store_t *store, const char *path) { return amduatd_handle_get_artifact(fd, store, path, true); } static bool amduatd_handle_post_artifacts(int fd, amduat_asl_store_t *store, const amduatd_http_req_t *req) { uint8_t *body = NULL; bool has_type_tag = false; amduat_type_tag_t type_tag; amduat_asl_io_format_t input_format = AMDUAT_ASL_IO_RAW; amduat_artifact_t artifact; amduat_reference_t ref; amduat_asl_store_error_t err; char *ref_hex = NULL; char json[2048]; memset(&artifact, 0, sizeof(artifact)); memset(&ref, 0, sizeof(ref)); memset(&type_tag, 0, sizeof(type_tag)); if (req == NULL) { return amduatd_http_send_text(fd, 400, "Bad Request", "bad request\n", false); } if (req->content_length > (64u * 1024u * 1024u)) { return amduatd_http_send_text(fd, 413, "Payload Too Large", "payload too large\n", false); } if (req->content_length != 0) { body = (uint8_t *)malloc(req->content_length); if (body == NULL) { return amduatd_http_send_text(fd, 500, "Internal Server Error", "oom\n", false); } if (!amduatd_read_exact(fd, body, req->content_length)) { free(body); return false; } } else { body = NULL; } if (req->content_type[0] != '\0' && strstr(req->content_type, "application/vnd.amduat.asl.artifact+v1") != NULL) { input_format = AMDUAT_ASL_IO_ARTIFACT; } if (input_format == AMDUAT_ASL_IO_RAW) { if (!amduatd_parse_type_tag_hex(req->x_type_tag, &has_type_tag, &type_tag)) { free(body); return amduatd_http_send_text(fd, 400, "Bad Request", "invalid X-Amduat-Type-Tag\n", false); } } if (!amduat_asl_artifact_from_bytes(amduat_octets(body, req->content_length), input_format, has_type_tag, type_tag, &artifact)) { free(body); return amduatd_http_send_text(fd, 400, "Bad Request", "invalid artifact\n", false); } err = amduat_asl_store_put(store, artifact, &ref); amduat_asl_artifact_free(&artifact); if (err != AMDUAT_ASL_STORE_OK) { free((void *)ref.digest.data); return amduatd_http_send_text(fd, 500, "Internal Server Error", "store error\n", false); } if (!amduat_asl_ref_encode_hex(ref, &ref_hex)) { free((void *)ref.digest.data); return amduatd_http_send_text(fd, 500, "Internal Server Error", "encode ref error\n", false); } free((void *)ref.digest.data); { int n = snprintf(json, sizeof(json), "{\"ref\":\"%s\"}\n", ref_hex); free(ref_hex); if (n <= 0 || (size_t)n >= sizeof(json)) { return amduatd_http_send_text(fd, 500, "Internal Server Error", "error\n", false); } return amduatd_http_send_json(fd, 200, "OK", json, false); } } static bool amduatd_handle_conn(int fd, amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg) { amduatd_http_req_t req; char no_query[1024]; if (!amduatd_http_parse_request(fd, &req)) { return false; } amduatd_path_without_query(req.path, no_query, sizeof(no_query)); if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/meta") == 0) { return amduatd_handle_meta(fd, cfg, false); } if (strcmp(req.method, "HEAD") == 0 && strcmp(no_query, "/v1/meta") == 0) { return amduatd_handle_meta(fd, cfg, true); } if (strcmp(req.method, "POST") == 0 && strcmp(no_query, "/v1/artifacts") == 0) { return amduatd_handle_post_artifacts(fd, store, &req); } if (strcmp(req.method, "GET") == 0 && strncmp(no_query, "/v1/artifacts/", 14) == 0) { return amduatd_handle_get_artifact(fd, store, req.path, false); } if (strcmp(req.method, "HEAD") == 0 && strncmp(no_query, "/v1/artifacts/", 14) == 0) { return amduatd_handle_head_artifact(fd, store, req.path); } return amduatd_http_send_text(fd, 404, "Not Found", "not found\n", false); } static void amduatd_print_usage(FILE *stream) { fprintf(stream, "usage:\n" " amduatd [--root PATH] [--sock PATH]\n" "\n" "defaults:\n" " --root %s\n" " --sock %s\n", AMDUATD_DEFAULT_ROOT, AMDUATD_DEFAULT_SOCK); } int main(int argc, char **argv) { const char *root = AMDUATD_DEFAULT_ROOT; const char *sock_path = AMDUATD_DEFAULT_SOCK; amduat_asl_store_fs_config_t cfg; amduat_asl_store_fs_t fs; amduat_asl_store_t store; int i; int sfd = -1; for (i = 1; i < argc; ++i) { if (strcmp(argv[i], "--root") == 0) { if (i + 1 >= argc) { fprintf(stderr, "error: --root requires a path\n"); return 2; } root = argv[++i]; } else if (strcmp(argv[i], "--sock") == 0) { if (i + 1 >= argc) { fprintf(stderr, "error: --sock requires a path\n"); return 2; } sock_path = argv[++i]; } else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { amduatd_print_usage(stdout); return 0; } else { fprintf(stderr, "error: unknown option: %s\n", argv[i]); return 2; } } memset(&cfg, 0, sizeof(cfg)); if (!amduat_asl_store_fs_load_config(root, &cfg)) { fprintf(stderr, "error: failed to load store config: %s\n", root); return 8; } memset(&fs, 0, sizeof(fs)); if (!amduat_asl_store_fs_init(&fs, cfg.config, root)) { fprintf(stderr, "error: failed to initialize store: %s\n", root); return 8; } amduat_asl_store_init(&store, cfg.config, amduat_asl_store_fs_ops(), &fs); signal(SIGINT, amduatd_on_signal); signal(SIGTERM, amduatd_on_signal); sfd = socket(AF_UNIX, SOCK_STREAM, 0); if (sfd < 0) { perror("socket"); return 8; } { struct sockaddr_un addr; memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; if (strlen(sock_path) >= sizeof(addr.sun_path)) { fprintf(stderr, "error: socket path too long\n"); close(sfd); return 2; } strncpy(addr.sun_path, sock_path, sizeof(addr.sun_path) - 1); (void)unlink(sock_path); if (bind(sfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("bind"); close(sfd); return 8; } (void)chmod(sock_path, 0660); if (listen(sfd, 16) < 0) { perror("listen"); (void)unlink(sock_path); close(sfd); return 8; } } fprintf(stderr, "amduatd: root=%s sock=%s\n", root, sock_path); while (!amduatd_should_exit) { int cfd = accept(sfd, NULL, NULL); if (cfd < 0) { if (errno == EINTR) { continue; } perror("accept"); break; } (void)amduatd_handle_conn(cfd, &store, &cfg); (void)close(cfd); } (void)unlink(sock_path); (void)close(sfd); return 0; }