/* See LICENSE file for copyright and license details. */ #include #include #include #include #include #include #include #include #include #include #include static int tcp_connect(const char *host, const char *port) { struct addrinfo hints; struct addrinfo *result, *rp; int s; int fd; int err; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_protocol = IPPROTO_TCP; s = getaddrinfo(host, port, &hints, &result); if (s < 0) errx(1, "unable to resolve host: %s", gai_strerror(s)); for (rp = result; rp; rp = rp->ai_next) { fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (fd < 0) continue; if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0) break; err = errno; close(fd); errno = err; } if (rp == NULL) fd = -1; freeaddrinfo(result); return fd; } static int write_all(int fd, const void *buf, size_t count) { ssize_t nbytes; while (count > 0) { nbytes = write(fd, buf, count); if (nbytes < 0 && errno == EINTR) continue; if (nbytes < 0) return -1; buf += nbytes; count -= nbytes; } return 0; } static void usage(int status) { fputs("usage: electrumc
[]\n", status ? stderr : stdout); exit(status); } static const char *ssl_strerror(int err) { switch (err) { case SSL_ERROR_NONE: return "no error"; case SSL_ERROR_ZERO_RETURN: return "connection closed"; case SSL_ERROR_WANT_READ: return "read did not complete"; case SSL_ERROR_WANT_WRITE: return "write did not complete"; case SSL_ERROR_WANT_CONNECT: return "connect did not complete"; case SSL_ERROR_WANT_ACCEPT: return "accept did not complete"; case SSL_ERROR_WANT_X509_LOOKUP: return "cert application callback set"; case SSL_ERROR_WANT_ASYNC: return "asynchronous engine is still processing data"; case SSL_ERROR_WANT_ASYNC_JOB: return "no async jobs available"; case SSL_ERROR_SYSCALL: return strerror(errno); case SSL_ERROR_SSL: ERR_print_errors_fp(stderr); return "protocol error"; //return ERR_error_string(ERR_get_error(), NULL); default: return "unknown error"; } } static void ssl_err(const char *prefix, const SSL *ssl, int rc) { rc = SSL_get_error(ssl, rc); errx(1, "%s: %s", prefix, ssl_strerror(rc)); } static int read_response(int s, SSL *ssl, bool use_ssl) { char buf[8192]; int len; if (use_ssl) { len = SSL_pending(ssl); if (len < 0) errx(1, "ssl pending"); if (len == 0) len = 1; else if (len > (int)sizeof(buf)) len = sizeof(buf); len = SSL_read(ssl, buf, len); if (len < 0) ssl_err("SSL_read", ssl, len); } else { len = read(s, buf, sizeof(buf)); if (len == 0) return -1; if (len < 0) err(1, "read"); } len = fwrite(buf, 1, len, stdout); if (len < 0) errx(1, "failed to print response"); if (buf[len-1] == '\n') return 1; return 0; } int main(int argc, char *argv[]) { int rc; SSL *ssl; SSL_CTX *ssl_ctx; char *host, *port, *type, *method, *params; int s; bool use_ssl; if (argc == 1) usage(0); if (argc != 3 && argc != 4) usage(1); host = argv[1]; method = argv[2]; params = argc == 3 ? NULL : argv[3]; type = strrchr(host, ':'); if (!type) errx(1, "address should be in format ::"); *type++ = '\0'; if (!strcmp(type, "t")) use_ssl = false; else if (!strcmp(type, "s")) use_ssl = true; else errx(1, "address type must be 't' or 's', but found: '%s'", type); port = strrchr(host, ':'); if (!port) errx(1, "address should be in format ::"); *port++ = '\0'; if (!*host) errx(1, "address hostname missing"); if (use_ssl) { SSL_load_error_strings(); ssl_ctx = SSL_CTX_new(TLS_client_method()); if (ssl_ctx == NULL) errx(1, "failed to create ssl context"); (void)SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); ssl = SSL_new(ssl_ctx); if (ssl_ctx == NULL) errx(1, "failed to create ssl object"); } s = tcp_connect(host, port); if (s < 0) err(1, "failed to connect"); if (use_ssl) { rc = SSL_set_fd(ssl, s); if (rc < 0) errx(1, "failed to set SSL fd"); SSL_set_hostflags(ssl, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); if (!SSL_set1_host(ssl, host)) errx(1, "failed to set SSL host"); SSL_set_verify(ssl, SSL_VERIFY_PEER, NULL); rc = SSL_connect(ssl); if (rc < 0) ssl_err("SSL_connect", ssl, rc); } char buf[8192]; int len = snprintf(buf, sizeof(buf)-1, "{\"id\":0,\"method\":\"%s\"%s%s}\n", method, params ? ",\"params\":" : "", params ? params : ""); if (len < 0 || len == (int)sizeof(buf)) errx(1, "request too long"); if (use_ssl) { rc = SSL_write(ssl, buf, len); if (rc < 0) ssl_err("SSL_write", ssl, rc); } else { len = write_all(s, buf, len); if (len < 0) err(1, "write"); } do rc = read_response(s, ssl, use_ssl); while (rc == 0); if (rc < 0 && errno == 0) errx(1, "incomplete response"); if (rc < 0) err(1, "read"); if (use_ssl) { rc = SSL_shutdown(ssl); if (rc == 0) rc = SSL_shutdown(ssl); if (rc < 0) ssl_err("SSL_shutdown", ssl, rc); } close(s); }