/*
* s_client command
*
* Copyright (C) 2019 Libiquity LLC
*
* This file is part of wolfutil.
*
* wolfutil 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.
*
* wolfutil 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 wolfutil. If not, see .
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "commands.h"
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
#undef MAX
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#undef MIN
#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 == NULL) {
fputs("Port is required\n", stderr);
return false;
}
**port = '\0';
++*port;
return true;
}
static int
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)
{
_Bool quiet = false;
char *host = NULL;
char *port = NULL;
const char *servername = NULL;
int ret = EXIT_FAILURE;
WOLFSSL_METHOD *method;
WOLFSSL_CTX *ctx = NULL;
WOLFSSL *ssl = NULL;
int sfd = -1;
int err;
char buf[WOLFSSL_MAX_ERROR_SZ];
for (; argc > 0; --argc, ++argv) {
if (strcmp(*argv, "-quiet") == 0) {
quiet = true;
} 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();
if (quiet == false) {
wolfSSL_Debugging_ON();
}
if (
(method = wolfTLSv1_2_client_method()) == NULL ||
(ctx = wolfSSL_CTX_new(method)) == NULL ||
#ifdef HAVE_CERTIFICATE_STATUS_REQUEST_V2
wolfSSL_CTX_EnableOCSPStapling(ctx) != WOLFSSL_SUCCESS
|| wolfSSL_CTX_UseOCSPStaplingV2(ctx,
WOLFSSL_CSR2_OCSP_MULTI, 0) !=
WOLFSSL_SUCCESS ||
#endif
#ifdef HAVE_CERTIFICATE_STATUS_REQUEST
wolfSSL_CTX_EnableOCSPStapling(ctx) != WOLFSSL_SUCCESS
|| wolfSSL_CTX_UseOCSPStapling(ctx, WOLFSSL_CSR_OCSP, 0)
!= WOLFSSL_SUCCESS ||
#endif
#ifdef HAVE_OCSP
wolfSSL_CTX_EnableOCSP(ctx, WOLFSSL_OCSP_CHECKALL) !=
WOLFSSL_SUCCESS ||
#endif
#if defined(HAVE_CRL) && defined(HAVE_CRL_IO)
wolfSSL_CTX_EnableCRL(ctx, WOLFSSL_CRL_CHECKALL) !=
WOLFSSL_SUCCESS ||
#endif
#ifdef HAVE_SNI
(servername != NULL && wolfSSL_CTX_UseSNI(ctx,
WOLFSSL_SNI_HOST_NAME, servername,
strlen(servername)) != WOLFSSL_SUCCESS) ||
#endif
(ssl = wolfSSL_new(ctx)) == NULL ||
wolfSSL_check_domain_name(ssl, servername) !=
WOLFSSL_SUCCESS
) {
fputs("Out of memory\n", stderr);
goto error;
}
#if defined(HAVE_CA_CERTS) && HAVE_CA_CERTS
if (wolfSSL_CTX_load_verify_locations_ex(ctx,
CA_CERTS_FILE, CA_CERTS_DIR,
WOLFSSL_LOAD_FLAG_IGNORE_ERR) !=
WOLFSSL_SUCCESS) {
fputs("Failed to load CA certificates\n", stderr);
goto error;
}
#endif
if ((sfd = connect_socket(host, port)) == -1) {
goto error;
}
wolfSSL_set_fd(ssl, sfd);
if ((err = wolfSSL_connect(ssl)) != WOLFSSL_SUCCESS) {
wolfSSL_ERR_error_string(wolfSSL_get_error(ssl, err), buf);
fprintf(stderr, "Handshake error: %s\n", buf);
goto error;
}
if (poll_fds(sfd, ssl) == false) {
goto error;
}
ret = EXIT_SUCCESS;
error:
close(sfd);
wolfSSL_free(ssl);
wolfSSL_CTX_free(ctx);
wolfSSL_Cleanup();
return ret;
}