diff options
Diffstat (limited to 'clients/client.c')
| -rw-r--r-- | clients/client.c | 296 |
1 files changed, 240 insertions, 56 deletions
diff --git a/clients/client.c b/clients/client.c index ea5b906..8fb604b 100644 --- a/clients/client.c +++ b/clients/client.c @@ -1,7 +1,11 @@ #include "client.h" +#include <arpa/inet.h> #include <errno.h> #include <netdb.h> +#include <openssl/err.h> +#include <openssl/ssl.h> +#include <openssl/x509v3.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -12,6 +16,28 @@ #include "util.h" #include "yyjson.h" +static char g_last_error[256]; + +const char *client_last_error(void) +{ + return g_last_error[0] ? g_last_error : "connection error"; +} + +static void set_error(const char *msg) +{ + snprintf(g_last_error, sizeof g_last_error, "%s", msg); +} + +static void set_tls_error(const char *what) +{ + unsigned long e = ERR_get_error(); + char buf[200] = "unknown TLS error"; + if (e) + ERR_error_string_n(e, buf, sizeof buf); + snprintf(g_last_error, sizeof g_last_error, "%s: %s", what, buf); + ERR_clear_error(); +} + static ssize_t write_all(int fd, const char *buf, size_t len) { size_t off = 0; @@ -27,63 +53,34 @@ static ssize_t write_all(int fd, const char *buf, size_t len) return (ssize_t)off; } -int client_send_line(int fd, const char *line) +static void split_addrport(const char *addrport, char *host, size_t host_sz, + char *port, size_t port_sz, const char *def_port) { - 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"; + snprintf(host, host_sz, "127.0.0.1"); + snprintf(port, port_sz, "%s", def_port); const char *colon = strrchr(addrport, ':'); if (colon) { size_t hl = (size_t)(colon - addrport); - if (hl < sizeof host) { + if (hl < host_sz) { memcpy(host, addrport, hl); host[hl] = '\0'; } - snprintf(port, sizeof port, "%s", colon + 1); - } else { - snprintf(port, sizeof port, "%s", addrport); + snprintf(port, port_sz, "%s", colon + 1); + } else if (*addrport) { + snprintf(port, port_sz, "%s", addrport); } +} + +static int connect_tcp(const char *host, const char *port) +{ 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) + if (getaddrinfo(host, port, &hints, &res) != 0) { + set_error("cannot resolve host"); 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); @@ -94,30 +91,217 @@ static int tcp_connect_addr(const char *addrport) close(fd); fd = -1; } + if (fd < 0) + set_error(strerror(errno)); freeaddrinfo(res); return fd; } -int client_connect(const char *target) +static int set_verify_host(SSL *ssl, const char *host) +{ + X509_VERIFY_PARAM *param = SSL_get0_param(ssl); + struct in_addr in4; + struct in6_addr in6; + X509_VERIFY_PARAM_set_hostflags(param, + X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); + if (inet_pton(AF_INET, host, &in4) == 1 || + inet_pton(AF_INET6, host, &in6) == 1) + return X509_VERIFY_PARAM_set1_ip_asc(param, host); + return X509_VERIFY_PARAM_set1_host(param, host, 0); +} + +static int tls_connect_addr(const char *addrport, struct client_conn *out) { - if (strncmp(target, "tcp:", 4) == 0) - return tcp_connect_addr(target + 4); + char host[256], port[16]; + split_addrport(addrport, host, sizeof host, port, sizeof port, "8788"); + int fd = connect_tcp(host, port); + if (fd < 0) + return -1; + + SSL_CTX *ctx = SSL_CTX_new(TLS_client_method()); + if (!ctx) { + set_tls_error("TLS context"); + close(fd); + return -1; + } + SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); + SSL_CTX_set_default_verify_paths(ctx); + const char *ca = getenv("BOKFD_TLS_CA"); + if (ca && *ca && SSL_CTX_load_verify_locations(ctx, ca, NULL) != 1) { + set_tls_error("TLS CA file"); + SSL_CTX_free(ctx); + close(fd); + return -1; + } + SSL *ssl = SSL_new(ctx); + if (!ssl) { + set_tls_error("TLS connection"); + SSL_CTX_free(ctx); + close(fd); + return -1; + } + SSL_set_fd(ssl, fd); + SSL_set_tlsext_host_name(ssl, host); + if (set_verify_host(ssl, host) != 1) { + set_error("invalid TLS host name"); + SSL_free(ssl); + SSL_CTX_free(ctx); + close(fd); + return -1; + } + if (SSL_connect(ssl) != 1) { + long vr = SSL_get_verify_result(ssl); + if (vr != X509_V_OK) + snprintf(g_last_error, sizeof g_last_error, + "certificate verification failed: %s", + X509_verify_cert_error_string(vr)); + else + set_tls_error("TLS handshake"); + SSL_free(ssl); + SSL_CTX_free(ctx); + close(fd); + return -1; + } + out->fd = fd; + out->ssl = ssl; + out->ctx = ctx; + return 0; +} + +int client_connect(const char *target, struct client_conn *out) +{ + memset(out, 0, sizeof *out); + out->fd = -1; + g_last_error[0] = '\0'; + + if (strncmp(target, "tls:", 4) == 0) + return tls_connect_addr(target + 4, out); + if (strncmp(target, "tcp:", 4) == 0) { + char host[256], port[16]; + split_addrport(target + 4, host, sizeof host, port, sizeof port, + "8787"); + out->fd = connect_tcp(host, port); + return out->fd < 0 ? -1 : 0; + } + struct sockaddr_un sa; memset(&sa, 0, sizeof sa); sa.sun_family = AF_UNIX; if (strlen(target) >= sizeof sa.sun_path) { + set_error("socket path too long"); 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) + if (fd < 0) { + set_error(strerror(errno)); return -1; + } if (connect(fd, (struct sockaddr *)&sa, sizeof sa) != 0) { + set_error(strerror(errno)); close(fd); return -1; } - return fd; + out->fd = fd; + return 0; +} + +void client_close(struct client_conn *c) +{ + if (c->ssl) { + SSL_shutdown(c->ssl); + SSL_free(c->ssl); + c->ssl = NULL; + } + if (c->ctx) { + SSL_CTX_free(c->ctx); + c->ctx = NULL; + } + if (c->fd >= 0) { + close(c->fd); + c->fd = -1; + } +} + +int client_send_line(struct client_conn *c, const char *line) +{ + size_t len = strlen(line); + if (c->ssl) { + size_t off = 0; + while (off < len) { + int n = SSL_write(c->ssl, line + off, (int)(len - off)); + if (n <= 0) { + int e = SSL_get_error(c->ssl, n); + if (e == SSL_ERROR_WANT_READ || e == SSL_ERROR_WANT_WRITE) + set_error("TLS write would block"); + else + set_tls_error("TLS write"); + return -1; + } + off += (size_t)n; + } + int n = SSL_write(c->ssl, "\n", 1); + if (n <= 0) { + set_tls_error("TLS write"); + return -1; + } + return 0; + } + if (write_all(c->fd, line, len) < 0) + return -1; + return write_all(c->fd, "\n", 1) < 0 ? -1 : 0; +} + +char *client_read_line(struct client_conn *c) +{ + struct buf b; + buf_init(&b); + char chunk[4096]; + for (;;) { + ssize_t r; + if (c->ssl) { + int n = SSL_read(c->ssl, chunk, sizeof chunk); + if (n > 0) { + r = n; + } else { + int e = SSL_get_error(c->ssl, n); + if (e == SSL_ERROR_ZERO_RETURN) { + r = 0; + } else { + if (e == SSL_ERROR_WANT_READ || e == SSL_ERROR_WANT_WRITE) + set_error("TLS read would block"); + else + set_tls_error("TLS read"); + buf_free(&b); + return NULL; + } + } + } else { + r = read(c->fd, chunk, sizeof chunk); + if (r < 0) { + if (errno == EINTR) + continue; + set_error(strerror(errno)); + 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; } char *client_make_request(const char *cmd, const char *session, int64_t org, @@ -164,20 +348,20 @@ char *client_make_login_args(const char *user, const char *password) return s; } -char *client_rpc(int fd, const char *cmd, const char *session, int64_t org, - const char *args_json) +char *client_rpc(struct client_conn *c, 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); + int rc = client_send_line(c, req); free(req); if (rc != 0) return NULL; - return client_read_line(fd); + return client_read_line(c); } -int client_login(int fd, const char *user, const char *password, +int client_login(struct client_conn *c, const char *user, const char *password, char **session_out, char **err_out) { *session_out = NULL; @@ -187,10 +371,10 @@ int client_login(int fd, const char *user, const char *password, *err_out = xstrdup("could not build login request"); return -1; } - char *resp = client_rpc(fd, "session.open", NULL, 0, args); + char *resp = client_rpc(c, "session.open", NULL, 0, args); free(args); if (!resp) { - *err_out = xstrdup(strerror(errno)); + *err_out = xstrdup(client_last_error()); return -1; } if (!client_ok(resp)) { |
