electrumc.cView |
---|
| 1 … | + |
| 2 … | +#include <err.h> |
| 3 … | +#include <errno.h> |
| 4 … | +#include <netdb.h> |
| 5 … | +#include <netinet/in.h> |
| 6 … | +#include <stdbool.h> |
| 7 … | +#include <string.h> |
| 8 … | +#include <sys/socket.h> |
| 9 … | +#include <unistd.h> |
| 10 … | + |
| 11 … | +#include <openssl/ssl.h> |
| 12 … | + |
| 13 … | +static int tcp_connect(const char *host, const char *port) { |
| 14 … | + struct addrinfo hints; |
| 15 … | + struct addrinfo *result, *rp; |
| 16 … | + int s; |
| 17 … | + int fd; |
| 18 … | + int err; |
| 19 … | + |
| 20 … | + memset(&hints, 0, sizeof(hints)); |
| 21 … | + hints.ai_family = AF_UNSPEC; |
| 22 … | + hints.ai_protocol = IPPROTO_TCP; |
| 23 … | + |
| 24 … | + s = getaddrinfo(host, port, &hints, &result); |
| 25 … | + if (s < 0) errx(1, "unable to resolve host: %s", gai_strerror(s)); |
| 26 … | + |
| 27 … | + for (rp = result; rp; rp = rp->ai_next) { |
| 28 … | + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); |
| 29 … | + if (fd < 0) continue; |
| 30 … | + if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0) break; |
| 31 … | + err = errno; |
| 32 … | + close(fd); |
| 33 … | + errno = err; |
| 34 … | + } |
| 35 … | + if (rp == NULL) fd = -1; |
| 36 … | + |
| 37 … | + freeaddrinfo(result); |
| 38 … | + return fd; |
| 39 … | +} |
| 40 … | + |
| 41 … | +static int write_all(int fd, const void *buf, size_t count) { |
| 42 … | + ssize_t nbytes; |
| 43 … | + while (count > 0) { |
| 44 … | + nbytes = write(fd, buf, count); |
| 45 … | + if (nbytes < 0 && errno == EINTR) continue; |
| 46 … | + if (nbytes < 0) return -1; |
| 47 … | + buf += nbytes; |
| 48 … | + count -= nbytes; |
| 49 … | + } |
| 50 … | + return 0; |
| 51 … | +} |
| 52 … | + |
| 53 … | +static void usage() { |
| 54 … | + fputs("usage: electrumc <host> <port> <method> [<params>]\n", stderr); |
| 55 … | + exit(EXIT_FAILURE); |
| 56 … | +} |
| 57 … | + |
| 58 … | +static int read_response(int s, SSL *ssl, bool use_ssl) { |
| 59 … | + char buf[8192]; |
| 60 … | + int len; |
| 61 … | + |
| 62 … | + if (use_ssl) { |
| 63 … | + len = SSL_pending(ssl); |
| 64 … | + if (len < 0) errx(1, "ssl pending"); |
| 65 … | + if (len == 0) len = 1; |
| 66 … | + else if (len > (int)sizeof(buf)) len = sizeof(buf); |
| 67 … | + len = SSL_read(ssl, buf, len); |
| 68 … | + if (len < 0) errx(1, "ssl read failed"); |
| 69 … | + } else { |
| 70 … | + len = read(s, buf, sizeof(buf)); |
| 71 … | + if (len == 0) return -1; |
| 72 … | + if (len < 0) err(1, "read"); |
| 73 … | + } |
| 74 … | + |
| 75 … | + len = fwrite(buf, 1, len, stdout); |
| 76 … | + if (len < 0) errx(1, "failed to print response"); |
| 77 … | + |
| 78 … | + if (buf[len-1] == '\n') return 1; |
| 79 … | + return 0; |
| 80 … | +} |
| 81 … | + |
| 82 … | +int main(int argc, char *argv[]) { |
| 83 … | + int rc; |
| 84 … | + SSL *ssl; |
| 85 … | + SSL_CTX *ssl_ctx; |
| 86 … | + const char *host, *port, *method, *params; |
| 87 … | + int s; |
| 88 … | + bool use_ssl; |
| 89 … | + |
| 90 … | + if (argc != 4 && argc != 5) usage(); |
| 91 … | + host = argv[1]; |
| 92 … | + port = argv[2]; |
| 93 … | + method = argv[3]; |
| 94 … | + params = argc == 4 ? NULL : argv[4]; |
| 95 … | + |
| 96 … | + switch (port[0]) { |
| 97 … | + case 't': use_ssl = false; break; |
| 98 … | + case 's': use_ssl = true; break; |
| 99 … | + default: errx(1, "port should begin with 't' for TCP or 's' for TCP+SSL"); |
| 100 … | + } |
| 101 … | + port++; |
| 102 … | + |
| 103 … | + if (use_ssl) { |
| 104 … | + ssl_ctx = SSL_CTX_new(TLS_client_method()); |
| 105 … | + if (ssl_ctx == NULL) errx(1, "failed to create ssl context"); |
| 106 … | + (void)SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); |
| 107 … | + |
| 108 … | + ssl = SSL_new(ssl_ctx); |
| 109 … | + if (ssl_ctx == NULL) errx(1, "failed to create ssl object"); |
| 110 … | + } |
| 111 … | + |
| 112 … | + s = tcp_connect(host, port); |
| 113 … | + if (s < 0) err(1, "failed to connect"); |
| 114 … | + |
| 115 … | + if (use_ssl) { |
| 116 … | + rc = SSL_set_fd(ssl, s); |
| 117 … | + if (rc < 0) errx(1, "failed to set ssl s"); |
| 118 … | + |
| 119 … | + rc = SSL_connect(ssl); |
| 120 … | + if (rc < 0) errx(1, "ssl_connect failed"); |
| 121 … | + } |
| 122 … | + |
| 123 … | + char buf[8192]; |
| 124 … | + int len = snprintf(buf, sizeof(buf)-1, "{\"id\":0,\"method\":\"%s\"%s%s}\n", |
| 125 … | + method, |
| 126 … | + params ? ",\"params\":" : "", |
| 127 … | + params ? params : ""); |
| 128 … | + if (len < 0 || len == (int)sizeof(buf)) errx(1, "request too long"); |
| 129 … | + |
| 130 … | + if (use_ssl) { |
| 131 … | + rc = SSL_write(ssl, buf, len); |
| 132 … | + if (rc < 0) errx(1, "ssl write failed"); |
| 133 … | + } else { |
| 134 … | + len = write_all(s, buf, len); |
| 135 … | + if (len < 0) err(1, "write"); |
| 136 … | + } |
| 137 … | + |
| 138 … | + do rc = read_response(s, ssl, use_ssl); |
| 139 … | + while (rc == 0); |
| 140 … | + if (rc < 0 && errno == 0) errx(1, "incomplete response"); |
| 141 … | + if (rc < 0) err(1, "read"); |
| 142 … | + |
| 143 … | + if (use_ssl) { |
| 144 … | + rc = SSL_shutdown(ssl); |
| 145 … | + if (rc < 0) errx(1, "ssl shutdown failed"); |
| 146 … | + } |
| 147 … | + close(s); |
| 148 … | +} |