/* * s_client command * * Copyright (C) 2019 Patrick McDermott * * This file is part of wolfssl-util. * * wolfssl-util is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * wolfssl-util is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with wolfssl-util. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include "commands.h" #define CA_CERTS "/etc/ssl/certs" #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) #define MAX(a, b) (((a) > (b)) ? (a) : (b)) #define MIN(a, b) (((a) < (b)) ? (a) : (b)) static _Bool parse_host_port(char *hostport, char **host, char **port) { *host = hostport; /* XXX: Port is required. Otherwise, this will mangle IPv6 addresses * without some more intelligent (and larger) code. */ *port = strrchr(*host, ':'); if (!*port) { fputs("Port is required\n", stderr); return false; } **port = '\0'; ++*port; return true; } static _Bool connect_socket(const char *host, const char *port) { struct addrinfo hints = {0}; struct addrinfo *result; int s; struct addrinfo *rp; int sfd; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; hints.ai_protocol = 0; s = getaddrinfo(host, port, &hints, &result); if (s != 0) { fprintf(stderr, "Failed to resolve host and port: %s\n", gai_strerror(s)); return -1; } for (rp = result; rp != NULL; rp = rp->ai_next) { sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sfd == -1) { continue; } if (connect(sfd, rp->ai_addr, rp->ai_addrlen) == -1) { close(sfd); continue; } break; } if (rp == NULL) { fputs("Failed to connect\n", stderr); sfd = -1; } freeaddrinfo(result); return sfd; } static _Bool write_all(int fd, const void *buf, size_t count) { ssize_t ret; while (count > 0) { while ((ret = write(fd, buf, count)) < 0 && errno == EINTR) { continue; } if (ret < 0) { return false; } buf = ((const char *) buf) + ret; count -= ret; } return true; } static _Bool poll_fds(int sfd, WOLFSSL *ssl) { struct pollfd fds[2] = { { .fd = -1, .events = POLLIN|POLLERR, .revents = 0 }, { .fd = -1, .events = POLLIN|POLLERR, .revents = 0 }, }; char buf[MAX(8192, WOLFSSL_MAX_ERROR_SZ)]; ssize_t len; int ret; fds[0].fd = STDIN_FILENO; fds[1].fd = sfd; for (;;) { while (poll(fds, ARRAY_SIZE(fds), -1) < 0 && (errno == EINTR || errno == EAGAIN)) { continue; } if (fds[0].revents > 0) { /* stdin */ len = read(STDIN_FILENO, buf, sizeof(buf)); if (len < 0) { fputs("Input read error\n", stderr); return false; } else if (len == 0) { fds[0].fd = -1; /* Stop polling. */ } else if ((ret = wolfSSL_write(ssl, buf, len)) <= 0) { wolfSSL_ERR_error_string(wolfSSL_get_error(ssl, ret), buf); fprintf(stderr, "Socket write error: %s\n", buf); return false; } } if (fds[1].revents > 0) { /* socket */ ret = wolfSSL_read(ssl, buf, MIN(sizeof(buf), 1024)); if (ret < 0) { wolfSSL_ERR_error_string(wolfSSL_get_error(ssl, ret), buf); fprintf(stderr, "Socket read error: %s\n", buf); return false; } else if (ret == 0) { fds[1].fd = -1; /* Stop polling. */ close(STDOUT_FILENO); /* Signal socket EOF. */ } else if (write_all(STDOUT_FILENO, buf, (size_t) ret) == false) { return false; } } if (fds[0].fd == fds[1].fd) { /* Both -1 (no longer polled) */ return true; } } /* Unreached */ } int s_client(int argc, char **argv) { char *host = NULL; char *port = NULL; const char *servername = NULL; int ret = EXIT_SUCCESS; WOLFSSL_METHOD *method; WOLFSSL_CTX *ctx; WOLFSSL *ssl; int sfd; for (; argc > 0; --argc, ++argv) { if (strcmp(*argv, "-quiet") == 0) { /* No-op */ } else if (strcmp(*argv, "-connect") == 0) { --argc, ++argv; if (parse_host_port(*argv, &host, &port) == false) { return EXIT_FAILURE; } } else if (strcmp(*argv, "-servername") == 0) { --argc, ++argv; servername = *argv; } else if (**argv == '-') { fprintf(stderr, "Unsupported option \"%s\"\n", *argv); } } wolfSSL_Init(); method = wolfSSLv23_client_method(); if (method == NULL) { fputs("Out of memory\n", stderr); ret = EXIT_FAILURE; goto cleanup; } ctx = wolfSSL_CTX_new(method); if (ctx == NULL) { fputs("Out of memory\n", stderr); ret = EXIT_FAILURE; goto cleanup; } if (wolfSSL_CTX_load_verify_locations_ex(ctx, NULL, CA_CERTS, WOLFSSL_LOAD_FLAG_IGNORE_ERR) != WOLFSSL_SUCCESS) { fputs("Failed to load CA certificates\n", stderr); ret = EXIT_FAILURE; goto ctx_free; } #ifdef HAVE_OCSP if (wolfSSL_CTX_EnableOCSP(ctx, WOLFSSL_OCSP_CHECKALL) != WOLFSSL_SUCCESS) { fputs("Failed to enable OCSP\n", stderr); goto ctx_free; } #endif #ifdef HAVE_SNI if (servername != NULL) { if (wolfSSL_CTX_UseSNI(ctx, WOLFSSL_SNI_HOST_NAME, servername, strlen(servername)) != WOLFSSL_SUCCESS){ fputs("Out of memory\n", stderr); ret = EXIT_FAILURE; goto ctx_free; } } #else (void) servername; #endif ssl = wolfSSL_new(ctx); if (ssl == NULL) { fputs("Out of memory\n", stderr); ret = EXIT_FAILURE; goto ctx_free; } sfd = connect_socket(host, port); if (sfd == -1) { ret = EXIT_FAILURE; goto ssl_free; } if (poll_fds(sfd, ssl) == false) { ret = EXIT_FAILURE; } close(sfd); ssl_free: wolfSSL_free(ssl); ctx_free: wolfSSL_CTX_free(ctx); cleanup: wolfSSL_Cleanup(); return ret; }