aboutsummaryrefslogtreecommitdiff
path: root/clients/client.c
diff options
context:
space:
mode:
Diffstat (limited to 'clients/client.c')
-rw-r--r--clients/client.c241
1 files changed, 241 insertions, 0 deletions
diff --git a/clients/client.c b/clients/client.c
new file mode 100644
index 0000000..ea5b906
--- /dev/null
+++ b/clients/client.c
@@ -0,0 +1,241 @@
+#include "client.h"
+
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#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;
+}