#ifndef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200809L #endif #include #include #include #include #include #include #include #include #include typedef struct { char *data; size_t len; size_t cap; } amduatd_buf_t; static void amduatd_buf_free(amduatd_buf_t *b) { if (b == NULL) { return; } free(b->data); b->data = NULL; b->len = 0u; b->cap = 0u; } static bool amduatd_buf_reserve(amduatd_buf_t *b, size_t extra) { size_t need; size_t next; char *mem; if (b == NULL) { return false; } if (extra > SIZE_MAX - b->len) { return false; } need = b->len + extra; if (need <= b->cap) { return true; } next = b->cap == 0u ? 512u : b->cap; while (next < need) { if (next > SIZE_MAX / 2u) { next = need; break; } next *= 2u; } mem = (char *)realloc(b->data, next); if (mem == NULL) { return false; } b->data = mem; b->cap = next; return true; } static bool amduatd_buf_append(amduatd_buf_t *b, const char *s, size_t n) { if (b == NULL) { return false; } if (n == 0u) { return true; } if (s == NULL) { return false; } if (!amduatd_buf_reserve(b, n)) { return false; } memcpy(b->data + b->len, s, n); b->len += n; return true; } static bool amduatd_buf_append_cstr(amduatd_buf_t *b, const char *s) { return amduatd_buf_append(b, s, s != NULL ? strlen(s) : 0u); } static void amduatd_usage(const char *prog) { fprintf(stderr, "usage: %s --sock PATH --method METHOD --path PATH " "[--header \"K: V\"]... [--data STRING|--data-file PATH] " "[--allow-status]\n", prog); } static bool amduatd_read_file(const char *path, uint8_t **out_data, size_t *out_len) { FILE *fp; long size; uint8_t *buf; if (out_data == NULL || out_len == NULL || path == NULL) { return false; } *out_data = NULL; *out_len = 0u; fp = fopen(path, "rb"); if (fp == NULL) { return false; } if (fseek(fp, 0, SEEK_END) != 0) { fclose(fp); return false; } size = ftell(fp); if (size < 0) { fclose(fp); return false; } if (fseek(fp, 0, SEEK_SET) != 0) { fclose(fp); return false; } buf = (uint8_t *)malloc((size_t)size); if (buf == NULL) { fclose(fp); return false; } if (size != 0 && fread(buf, 1u, (size_t)size, fp) != (size_t)size) { free(buf); fclose(fp); return false; } fclose(fp); *out_data = buf; *out_len = (size_t)size; return true; } static int amduatd_parse_status(const char *line) { const char *p; char *end = NULL; long code; if (line == NULL) { return -1; } p = strchr(line, ' '); if (p == NULL) { return -1; } while (*p == ' ') { p++; } errno = 0; code = strtol(p, &end, 10); if (errno != 0 || end == p || code < 0 || code > 999) { return -1; } return (int)code; } int main(int argc, char **argv) { const char *sock_path = NULL; const char *method = NULL; const char *path = NULL; bool allow_status = false; char **headers = NULL; size_t header_count = 0u; uint8_t *body = NULL; size_t body_len = 0u; int fd = -1; struct sockaddr_un addr; amduatd_buf_t req; amduatd_buf_t resp; ssize_t nread; int status; char *header_end; size_t body_offset; size_t i; memset(&req, 0, sizeof(req)); memset(&resp, 0, sizeof(resp)); for (i = 1; i < (size_t)argc; ++i) { const char *arg = argv[i]; if (strcmp(arg, "--sock") == 0 && i + 1 < (size_t)argc) { sock_path = argv[++i]; } else if (strcmp(arg, "--method") == 0 && i + 1 < (size_t)argc) { method = argv[++i]; } else if (strcmp(arg, "--path") == 0 && i + 1 < (size_t)argc) { path = argv[++i]; } else if (strcmp(arg, "--header") == 0 && i + 1 < (size_t)argc) { char **next = (char **)realloc(headers, (header_count + 1u) * sizeof(*headers)); if (next == NULL) { fprintf(stderr, "oom\n"); goto fail; } headers = next; headers[header_count++] = argv[++i]; } else if (strcmp(arg, "--data") == 0 && i + 1 < (size_t)argc) { const char *data = argv[++i]; body_len = strlen(data); if (body_len == 0u) { body = NULL; } else { body = (uint8_t *)malloc(body_len); if (body == NULL) { fprintf(stderr, "oom\n"); goto fail; } memcpy(body, data, body_len); } } else if (strcmp(arg, "--data-file") == 0 && i + 1 < (size_t)argc) { if (!amduatd_read_file(argv[++i], &body, &body_len)) { fprintf(stderr, "failed to read data file\n"); goto fail; } } else if (strcmp(arg, "--allow-status") == 0) { allow_status = true; } else { amduatd_usage(argv[0]); goto fail; } } if (sock_path == NULL || method == NULL || path == NULL) { amduatd_usage(argv[0]); goto fail; } fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) { perror("socket"); goto fail; } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; if (strlen(sock_path) >= sizeof(addr.sun_path)) { fprintf(stderr, "socket path too long\n"); goto fail; } strncpy(addr.sun_path, sock_path, sizeof(addr.sun_path) - 1u); if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) { perror("connect"); goto fail; } if (!amduatd_buf_append_cstr(&req, method) || !amduatd_buf_append_cstr(&req, " ") || !amduatd_buf_append_cstr(&req, path) || !amduatd_buf_append_cstr(&req, " HTTP/1.1\r\n") || !amduatd_buf_append_cstr(&req, "Host: localhost\r\n") || !amduatd_buf_append_cstr(&req, "Connection: close\r\n")) { fprintf(stderr, "oom\n"); goto fail; } for (i = 0; i < header_count; ++i) { if (!amduatd_buf_append_cstr(&req, headers[i]) || !amduatd_buf_append_cstr(&req, "\r\n")) { fprintf(stderr, "oom\n"); goto fail; } } if (body != NULL) { char len_buf[64]; int len; len = snprintf(len_buf, sizeof(len_buf), "Content-Length: %zu\r\n", body_len); if (len < 0 || (size_t)len >= sizeof(len_buf) || !amduatd_buf_append(&req, len_buf, (size_t)len)) { fprintf(stderr, "oom\n"); goto fail; } } if (!amduatd_buf_append_cstr(&req, "\r\n")) { fprintf(stderr, "oom\n"); goto fail; } { size_t off = 0u; while (off < req.len) { ssize_t n = write(fd, req.data + off, req.len - off); if (n <= 0) { perror("write"); goto fail; } off += (size_t)n; } } if (body != NULL && body_len != 0u) { size_t off = 0u; while (off < body_len) { ssize_t n = write(fd, body + off, body_len - off); if (n <= 0) { perror("write"); goto fail; } off += (size_t)n; } } while (true) { char tmp[4096]; nread = read(fd, tmp, sizeof(tmp)); if (nread < 0) { perror("read"); goto fail; } if (nread == 0) { break; } if (!amduatd_buf_append(&resp, tmp, (size_t)nread)) { fprintf(stderr, "oom\n"); goto fail; } } if (resp.len == 0u) { fprintf(stderr, "empty response\n"); goto fail; } if (!amduatd_buf_append(&resp, "\0", 1u)) { fprintf(stderr, "oom\n"); goto fail; } { char *line_end = strstr(resp.data, "\r\n"); if (line_end == NULL) { fprintf(stderr, "invalid response\n"); goto fail; } *line_end = '\0'; status = amduatd_parse_status(resp.data); *line_end = '\r'; if (status < 0) { fprintf(stderr, "invalid status\n"); goto fail; } } header_end = strstr(resp.data, "\r\n\r\n"); if (header_end == NULL) { body_offset = resp.len; } else { body_offset = (size_t)(header_end - resp.data) + 4u; } if (body_offset < resp.len) { size_t out_len = resp.len - body_offset; if (resp.len > 0u && resp.data[resp.len - 1u] == '\0') { if (out_len > 0u) { out_len -= 1u; } } if (out_len > 0u) { fwrite(resp.data + body_offset, 1u, out_len, stdout); } } fflush(stdout); amduatd_buf_free(&req); amduatd_buf_free(&resp); free(body); free(headers); if (fd >= 0) { close(fd); } if (!allow_status && (status < 200 || status >= 300)) { return 1; } return 0; fail: amduatd_buf_free(&req); amduatd_buf_free(&resp); free(body); free(headers); if (fd >= 0) { close(fd); } return 1; }