#include "client.h" #include #include #include #include #include #include #include #include #include "util.h" #include "yyjson.h" static ssize_t write_all(int fd, const char *buf, size_t len) { size_t off = 0; while (off < len) { ssize_t w = write(fd, buf + off, len - off); if (w < 0) { if (errno == EINTR) continue; return -1; } off += (size_t)w; } return (ssize_t)off; } int client_send_line(int fd, const char *line) { if (write_all(fd, line, strlen(line)) < 0) return -1; return write_all(fd, "\n", 1) < 0 ? -1 : 0; } char *client_read_line(int fd) { struct buf b; buf_init(&b); char chunk[4096]; for (;;) { ssize_t r = read(fd, chunk, sizeof chunk); if (r < 0) { if (errno == EINTR) continue; buf_free(&b); return NULL; } if (r == 0) break; unsigned char *nl = memchr(chunk, '\n', (size_t)r); if (nl) { buf_append(&b, chunk, (size_t)(nl - (unsigned char *)chunk)); break; } buf_append(&b, chunk, (size_t)r); } char *out = xmalloc(b.len + 1); memcpy(out, b.p ? (char *)b.p : "", b.len); out[b.len] = '\0'; buf_free(&b); return out; } static int tcp_connect_addr(const char *addrport) { char host[256] = "127.0.0.1"; char port[16] = "8787"; const char *colon = strrchr(addrport, ':'); if (colon) { size_t hl = (size_t)(colon - addrport); if (hl < sizeof host) { memcpy(host, addrport, hl); host[hl] = '\0'; } snprintf(port, sizeof port, "%s", colon + 1); } else { snprintf(port, sizeof port, "%s", addrport); } struct addrinfo hints, *res = NULL; memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; if (getaddrinfo(host, port, &hints, &res) != 0) return -1; int fd = -1; for (struct addrinfo *ai = res; ai; ai = ai->ai_next) { fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (fd < 0) continue; if (connect(fd, ai->ai_addr, ai->ai_addrlen) == 0) break; close(fd); fd = -1; } freeaddrinfo(res); return fd; } int client_connect(const char *target) { if (strncmp(target, "tcp:", 4) == 0) return tcp_connect_addr(target + 4); struct sockaddr_un sa; memset(&sa, 0, sizeof sa); sa.sun_family = AF_UNIX; if (strlen(target) >= sizeof sa.sun_path) { errno = ENAMETOOLONG; return -1; } snprintf(sa.sun_path, sizeof sa.sun_path, "%s", target); int fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) return -1; if (connect(fd, (struct sockaddr *)&sa, sizeof sa) != 0) { close(fd); return -1; } return fd; } char *client_make_request(const char *cmd, const char *session, int64_t org, const char *args_json, const char *id) { yyjson_doc *adoc = NULL; if (args_json) { adoc = yyjson_read(args_json, strlen(args_json), 0); if (!adoc || !yyjson_is_obj(yyjson_doc_get_root(adoc))) { yyjson_doc_free(adoc); return NULL; } } yyjson_mut_doc *d = yyjson_mut_doc_new(NULL); yyjson_mut_val *o = yyjson_mut_obj(d); yyjson_mut_doc_set_root(d, o); yyjson_mut_obj_add_int(d, o, "v", 1); yyjson_mut_obj_add_strcpy(d, o, "id", id ? id : "cli"); yyjson_mut_obj_add_strcpy(d, o, "cmd", cmd); if (session) yyjson_mut_obj_add_strcpy(d, o, "session", session); if (org > 0) yyjson_mut_obj_add_int(d, o, "org", org); if (adoc) { yyjson_mut_val *args = yyjson_val_mut_copy(d, yyjson_doc_get_root(adoc)); yyjson_mut_obj_add_val(d, o, "args", args); yyjson_doc_free(adoc); } char *s = yyjson_mut_write(d, 0, NULL); yyjson_mut_doc_free(d); return s; } char *client_make_login_args(const char *user, const char *password) { yyjson_mut_doc *d = yyjson_mut_doc_new(NULL); yyjson_mut_val *o = yyjson_mut_obj(d); yyjson_mut_doc_set_root(d, o); yyjson_mut_obj_add_strcpy(d, o, "method", "password"); yyjson_mut_obj_add_strcpy(d, o, "username", user); yyjson_mut_obj_add_strcpy(d, o, "password", password); char *s = yyjson_mut_write(d, 0, NULL); yyjson_mut_doc_free(d); return s; } char *client_rpc(int fd, const char *cmd, const char *session, int64_t org, const char *args_json) { char *req = client_make_request(cmd, session, org, args_json, "rpc"); if (!req) return NULL; int rc = client_send_line(fd, req); free(req); if (rc != 0) return NULL; return client_read_line(fd); } int client_login(int fd, const char *user, const char *password, char **session_out, char **err_out) { *session_out = NULL; *err_out = NULL; char *args = client_make_login_args(user, password); if (!args) { *err_out = xstrdup("could not build login request"); return -1; } char *resp = client_rpc(fd, "session.open", NULL, 0, args); free(args); if (!resp) { *err_out = xstrdup(strerror(errno)); return -1; } if (!client_ok(resp)) { *err_out = resp; return -1; } yyjson_doc *d = yyjson_read(resp, strlen(resp), 0); yyjson_val *root = d ? yyjson_doc_get_root(d) : NULL; yyjson_val *res = root ? yyjson_obj_get(root, "result") : NULL; yyjson_val *s = res ? yyjson_obj_get(res, "session") : NULL; if (!s || !yyjson_is_str(s)) { *err_out = xstrdup("login response had no session"); yyjson_doc_free(d); free(resp); return -1; } *session_out = xstrdup(yyjson_get_str(s)); yyjson_doc_free(d); return 0; } int client_ok(const char *response) { if (!response) return 0; yyjson_doc *d = yyjson_read(response, strlen(response), 0); yyjson_val *ok = d ? yyjson_obj_get(yyjson_doc_get_root(d), "ok") : NULL; int result = ok && yyjson_is_bool(ok) && yyjson_get_bool(ok); yyjson_doc_free(d); return result; } int client_session_from(const char *response, char *buf, unsigned long cap) { if (!response) return -1; yyjson_doc *d = yyjson_read(response, strlen(response), 0); yyjson_val *root = d ? yyjson_doc_get_root(d) : NULL; yyjson_val *res = root ? yyjson_obj_get(root, "result") : NULL; yyjson_val *s = res ? yyjson_obj_get(res, "session") : NULL; if (!s || !yyjson_is_str(s)) { yyjson_doc_free(d); return -1; } snprintf(buf, cap, "%s", yyjson_get_str(s)); yyjson_doc_free(d); return 0; }