/* $OpenBSD: s_client.c,v 1.68 2026/02/01 08:45:31 martijn Exp $ */
/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
 * All rights reserved.
 *
 * This package is an SSL implementation written
 * by Eric Young (eay@cryptsoft.com).
 * The implementation was written so as to conform with Netscapes SSL.
 *
 * This library is free for commercial and non-commercial use as long as
 * the following conditions are aheared to.  The following conditions
 * apply to all code found in this distribution, be it the RC4, RSA,
 * lhash, DES, etc., code; not just the SSL code.  The SSL documentation
 * included with this distribution is covered by the same copyright terms
 * except that the holder is Tim Hudson (tjh@cryptsoft.com).
 *
 * Copyright remains Eric Young's, and as such any Copyright notices in
 * the code are not to be removed.
 * If this package is used in a product, Eric Young should be given attribution
 * as the author of the parts of the library used.
 * This can be in the form of a textual message at program startup or
 * in documentation (online or textual) provided with the package.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *    "This product includes cryptographic software written by
 *     Eric Young (eay@cryptsoft.com)"
 *    The word 'cryptographic' can be left out if the rouines from the library
 *    being used are not cryptographic related :-).
 * 4. If you include any Windows specific code (or a derivative thereof) from
 *    the apps directory (application code) you must include an acknowledgement:
 *    "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
 *
 * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * The licence and distribution terms for any publically available version or
 * derivative of this code cannot be changed.  i.e. this code cannot simply be
 * copied and put under another distribution licence
 * [including the GNU Public Licence.]
 */
/* ====================================================================
 * Copyright (c) 1998-2006 The OpenSSL Project.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by the OpenSSL Project
 *    for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
 *
 * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For written permission, please contact
 *    openssl-core@openssl.org.
 *
 * 5. Products derived from this software may not be called "OpenSSL"
 *    nor may "OpenSSL" appear in their names without prior written
 *    permission of the OpenSSL Project.
 *
 * 6. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by the OpenSSL Project
 *    for use in the OpenSSL Toolkit (http://www.openssl.org/)"
 *
 * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE OpenSSL PROJECT OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 *
 * This product includes cryptographic software written by Eric Young
 * (eay@cryptsoft.com).  This product includes software written by Tim
 * Hudson (tjh@cryptsoft.com).
 *
 */
/* ====================================================================
 * Copyright 2005 Nokia. All rights reserved.
 *
 * The portions of the attached software ("Contribution") is developed by
 * Nokia Corporation and is licensed pursuant to the OpenSSL open source
 * license.
 *
 * The Contribution, originally written by Mika Kousa and Pasi Eronen of
 * Nokia Corporation, consists of the "PSK" (Pre-Shared Key) ciphersuites
 * support (see RFC 4279) to OpenSSL.
 *
 * No patent licenses or other rights except those expressly stated in
 * the OpenSSL open source license shall be deemed granted or received
 * expressly, by implication, estoppel, or otherwise.
 *
 * No assurances are provided by Nokia that the Contribution does not
 * infringe the patent or other intellectual property rights of any third
 * party or that the license provides you with all the necessary rights
 * to make use of the Contribution.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. IN
 * ADDITION TO THE DISCLAIMERS INCLUDED IN THE LICENSE, NOKIA
 * SPECIFICALLY DISCLAIMS ANY LIABILITY FOR CLAIMS BROUGHT BY YOU OR ANY
 * OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR
 * OTHERWISE.
 */

#include <sys/types.h>
#include <sys/socket.h>

#include <netinet/in.h>

#include <assert.h>
#include <ctype.h>
#include <limits.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>

#include "apps.h"

#include <openssl/bn.h>
#include <openssl/err.h>
#include <openssl/ocsp.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>

#define SSL_HOST_NAME	"localhost"

#define BUFSIZZ 1024*8

static void sc_usage(void);
static void print_stuff(BIO *berr, SSL *con, int full);
static int ocsp_resp_cb(SSL *s, void *arg);
static int ssl_servername_cb(SSL *s, int *ad, void *arg);

enum {
	PROTO_OFF = 0,
	PROTO_SMTP,
	PROTO_LMTP,
	PROTO_POP3,
	PROTO_IMAP,
	PROTO_SIEVE,
	PROTO_FTP,
	PROTO_XMPP,
};

/* This is a context that we pass to callbacks */
typedef struct tlsextctx_st {
	BIO *biodebug;
	int ack;
} tlsextctx;

static struct {
	int af;
	char *alpn_in;
	int bugs;
	char *CAfile;
	char *CApath;
	char *cert_file;
	int cert_format;
	char *cipher;
	unsigned int clr;
	char *connect;
	int crlf;
	int debug;
	int enable_timeouts;
	const char *errstr;
	char *groups_in;
	char *host;
	int ign_eof;
	char *key_file;
	int key_format;
	char *keymatexportlabel;
	int keymatexportlen;
	uint16_t max_version;
	uint16_t min_version;
	const SSL_METHOD *meth;
	int msg;
	int nbio;
	int nbio_test;
	int no_servername;
	char *npn_in;
	unsigned int off;
	char *passarg;
	int peekaboo;
	char *port;
	int prexit;
	char *proxy;
	int quiet;
	int reconnect;
	char *servername;
	char *sess_in;
	char *sess_out;
	int showcerts;
	int socket_type;
	long socket_mtu;
#ifndef OPENSSL_NO_SRTP
	char *srtp_profiles;
#endif
	int starttls_proto;
	int state;
	int status_req;
	int tlsextdebug;
	int verify;
	X509_VERIFY_PARAM *vpm;
	char *xmpphost;
} cfg;

static int
s_client_opt_keymatexportlen(char *arg)
{
	cfg.keymatexportlen = strtonum(arg, 1, INT_MAX,
	    &cfg.errstr);
	if (cfg.errstr != NULL) {
		BIO_printf(bio_err, "invalid argument %s: %s\n",
		    arg, cfg.errstr);
		return (1);
	}
	return (0);
}

#ifndef OPENSSL_NO_DTLS
static int
s_client_opt_mtu(char *arg)
{
	cfg.socket_mtu = strtonum(arg, 0, LONG_MAX,
	    &cfg.errstr);
	if (cfg.errstr != NULL) {
		BIO_printf(bio_err, "invalid argument %s: %s\n",
		    arg, cfg.errstr);
		return (1);
	}
	return (0);
}
#endif

static int
s_client_opt_port(char *arg)
{
	if (*arg == '\0')
		return (1);

	cfg.port = arg;
	return (0);
}

#ifndef OPENSSL_NO_DTLS
static int
s_client_opt_protocol_version_dtls(void)
{
	cfg.meth = DTLS_client_method();
	cfg.socket_type = SOCK_DGRAM;
	return (0);
}
#endif

#ifndef OPENSSL_NO_DTLS1_2
static int
s_client_opt_protocol_version_dtls1_2(void)
{
	cfg.meth = DTLS_client_method();
	cfg.min_version = DTLS1_2_VERSION;
	cfg.max_version = DTLS1_2_VERSION;
	cfg.socket_type = SOCK_DGRAM;
	return (0);
}
#endif

static int
s_client_opt_protocol_version_tls1_2(void)
{
	cfg.min_version = TLS1_2_VERSION;
	cfg.max_version = TLS1_2_VERSION;
	return (0);
}

static int
s_client_opt_protocol_version_tls1_3(void)
{
	cfg.min_version = TLS1_3_VERSION;
	cfg.max_version = TLS1_3_VERSION;
	return (0);
}

static int
s_client_opt_quiet(void)
{
	cfg.quiet = 1;
	cfg.ign_eof = 1;
	return (0);
}

static int
s_client_opt_starttls(char *arg)
{
	if (strcmp(arg, "smtp") == 0)
		cfg.starttls_proto = PROTO_SMTP;
	else if (strcmp(arg, "lmtp") == 0)
		cfg.starttls_proto = PROTO_LMTP;
	else if (strcmp(arg, "pop3") == 0)
		cfg.starttls_proto = PROTO_POP3;
	else if (strcmp(arg, "imap") == 0)
		cfg.starttls_proto = PROTO_IMAP;
	else if (strcmp(arg, "sieve") == 0)
		cfg.starttls_proto = PROTO_SIEVE;
	else if (strcmp(arg, "ftp") == 0)
		cfg.starttls_proto = PROTO_FTP;
	else if (strcmp(arg, "xmpp") == 0)
		cfg.starttls_proto = PROTO_XMPP;
	else
		return (1);
	return (0);
}

static int
s_client_opt_verify(char *arg)
{
	cfg.verify = SSL_VERIFY_PEER;

	verify_depth = strtonum(arg, 0, INT_MAX, &cfg.errstr);
	if (cfg.errstr != NULL) {
		BIO_printf(bio_err, "invalid argument %s: %s\n",
		    arg, cfg.errstr);
		return (1);
	}
	BIO_printf(bio_err, "verify depth is %d\n", verify_depth);
	return (0);
}

static int
s_client_opt_verify_param(int argc, char **argv, int *argsused)
{
	char **pargs = argv;
	int pargc = argc;
	int badarg = 0;

	if (!args_verify(&pargs, &pargc, &badarg, bio_err,
	    &cfg.vpm)) {
		BIO_printf(bio_err, "unknown option %s\n", *argv);
		return (1);
	}
	if (badarg)
		return (1);

	*argsused = argc - pargc;
	return (0);
}

static const struct option s_client_options[] = {
	{
		.name = "4",
		.desc = "Use IPv4 only",
		.type = OPTION_VALUE,
		.opt.value = &cfg.af,
		.value = AF_INET,
	},
	{
		.name = "6",
		.desc = "Use IPv6 only",
		.type = OPTION_VALUE,
		.opt.value = &cfg.af,
		.value = AF_INET6,
	},
	{
		.name = "alpn",
		.argname = "protocols",
		.desc = "Set the advertised protocols for ALPN"
			" (comma-separated list)",
		.type = OPTION_ARG,
		.opt.arg = &cfg.alpn_in,
	},
	{
		.name = "bugs",
		.desc = "Enable various workarounds for buggy implementations",
		.type = OPTION_FLAG,
		.opt.flag = &cfg.bugs,
	},
	{
		.name = "CAfile",
		.argname = "file",
		.desc = "PEM format file of CA certificates",
		.type = OPTION_ARG,
		.opt.arg = &cfg.CAfile,
	},
	{
		.name = "CApath",
		.argname = "directory",
		.desc = "PEM format directory of CA certificates",
		.type = OPTION_ARG,
		.opt.arg = &cfg.CApath,
	},
	{
		.name = "cert",
		.argname = "file",
		.desc = "Certificate file to use, PEM format assumed",
		.type = OPTION_ARG,
		.opt.arg = &cfg.cert_file,
	},
	{
		.name = "certform",
		.argname = "fmt",
		.desc = "Certificate format (PEM or DER) PEM default",
		.type = OPTION_ARG_FORMAT,
		.opt.value = &cfg.cert_format,
	},
	{
		.name = "cipher",
		.argname = "cipherlist",
		.desc = "Preferred cipher to use (see 'openssl ciphers')",
		.type = OPTION_ARG,
		.opt.arg = &cfg.cipher,
	},
	{
		.name = "connect",
		.argname = "host:port",
		.desc = "Who to connect to (default is localhost:4433)",
		.type = OPTION_ARG,
		.opt.arg = &cfg.connect,
	},
	{
		.name = "crlf",
		.desc = "Convert LF from terminal into CRLF",
		.type = OPTION_FLAG,
		.opt.flag = &cfg.crlf,
	},
	{
		.name = "debug",
		.desc = "Print extensive debugging information",
		.type = OPTION_FLAG,
		.opt.flag = &cfg.debug,
	},
#ifndef OPENSSL_NO_DTLS
	{
		.name = "dtls",
		.desc = "Use any version of DTLS",
		.type = OPTION_FUNC,
		.opt.func = s_client_opt_protocol_version_dtls,
	},
#endif
#ifndef OPENSSL_NO_DTLS1_2
	{
		.name = "dtls1_2",
		.desc = "Just use DTLSv1.2",
		.type = OPTION_FUNC,
		.opt.func = s_client_opt_protocol_version_dtls1_2,
	},
#endif
	{
		.name = "groups",
		.argname = "list",
		.desc = "Specify EC groups (colon-separated list)",
		.type = OPTION_ARG,
		.opt.arg = &cfg.groups_in,
	},
	{
		.name = "host",
		.argname = "host",
		.desc = "Use -connect instead",
		.type = OPTION_ARG,
		.opt.arg = &cfg.host,
	},
	{
		.name = "ign_eof",
		.desc = "Ignore input EOF (default when -quiet)",
		.type = OPTION_VALUE,
		.opt.value = &cfg.ign_eof,
		.value = 1,
	},
	{
		.name = "key",
		.argname = "file",
		.desc = "Private key file to use, if not, -cert file is used",
		.type = OPTION_ARG,
		.opt.arg = &cfg.key_file,
	},
	{
		.name = "keyform",
		.argname = "fmt",
		.desc = "Key format (PEM or DER) PEM default",
		.type = OPTION_ARG_FORMAT,
		.opt.value = &cfg.key_format,
	},
	{
		.name = "keymatexport",
		.argname = "label",
		.desc = "Export keying material using label",
		.type = OPTION_ARG,
		.opt.arg = &cfg.keymatexportlabel,
	},
	{
		.name = "keymatexportlen",
		.argname = "len",
		.desc = "Export len bytes of keying material (default 20)",
		.type = OPTION_ARG_FUNC,
		.opt.argfunc = s_client_opt_keymatexportlen,
	},
	{
		.name = "legacy_renegotiation",
		.type = OPTION_DISCARD,
	},
	{
		.name = "legacy_server_connect",
		.desc = "Allow initial connection to servers that don't support RI",
		.type = OPTION_VALUE_OR,
		.opt.value = &cfg.off,
		.value = SSL_OP_LEGACY_SERVER_CONNECT,
	},
	{
		.name = "msg",
		.desc = "Show all protocol messages with hex dump",
		.type = OPTION_FLAG,
		.opt.flag = &cfg.msg,
	},
#ifndef OPENSSL_NO_DTLS
	{
		.name = "mtu",
		.argname = "mtu",
		.desc = "Set the link layer MTU on DTLS connections",
		.type = OPTION_ARG_FUNC,
		.opt.argfunc = s_client_opt_mtu,
	},
#endif
	{
		.name = "nbio",
		.desc = "Turn on non-blocking I/O",
		.type = OPTION_FLAG,
		.opt.flag = &cfg.nbio,
	},
	{
		.name = "nbio_test",
		.desc = "Test non-blocking I/O",
		.type = OPTION_FLAG,
		.opt.flag = &cfg.nbio_test,
	},
	{
		.name = "nextprotoneg",
		.argname = "protocols",
		.type = OPTION_ARG,
		.opt.arg = &cfg.npn_in, /* Ignored. */
	},
	{
		.name = "no_comp",
		.type = OPTION_VALUE_OR,
		.opt.value = &cfg.off,
		.value = SSL_OP_NO_COMPRESSION,
	},
	{
		.name = "no_ign_eof",
		.desc = "Don't ignore input EOF",
		.type = OPTION_VALUE,
		.opt.value = &cfg.ign_eof,
		.value = 0,
	},
	{
		.name = "no_legacy_server_connect",
		.desc = "Disallow initial connection to servers that don't support RI",
		.type = OPTION_VALUE_OR,
		.opt.value = &cfg.clr,
		.value = SSL_OP_LEGACY_SERVER_CONNECT,
	},
	{
		.name = "no_servername",
		.desc = "Do not send a Server Name Indication (SNI) extension",
		.type = OPTION_FLAG,
		.opt.value = &cfg.no_servername,
	},
	{
		.name = "no_ssl2",
		.type = OPTION_VALUE_OR,
		.opt.value = &cfg.off,
		.value = SSL_OP_NO_SSLv2,
	},
	{
		.name = "no_ssl3",
		.type = OPTION_VALUE_OR,
		.opt.value = &cfg.off,
		.value = SSL_OP_NO_SSLv3,
	},
	{
		.name = "no_ticket",
		.desc = "Disable use of RFC4507 session ticket support",
		.type = OPTION_VALUE_OR,
		.opt.value = &cfg.off,
		.value = SSL_OP_NO_TICKET,
	},
	{
		.name = "no_tls1",
		.type = OPTION_DISCARD,
	},
	{
		.name = "no_tls1_1",
		.type = OPTION_DISCARD,
	},
	{
		.name = "no_tls1_2",
		.desc = "Disable the use of TLSv1.2",
		.type = OPTION_VALUE_OR,
		.opt.value = &cfg.off,
		.value = SSL_OP_NO_TLSv1_2,
	},
	{
		.name = "no_tls1_3",
		.desc = "Disable the use of TLSv1.3",
		.type = OPTION_VALUE_OR,
		.opt.value = &cfg.off,
		.value = SSL_OP_NO_TLSv1_3,
	},
	{
		.name = "noservername",
		.type = OPTION_FLAG,
		.opt.value = &cfg.no_servername,
	},
	{
		.name = "pass",
		.argname = "arg",
		.desc = "Private key file pass phrase source",
		.type = OPTION_ARG,
		.opt.arg = &cfg.passarg,
	},
	{
		.name = "pause",
		.type = OPTION_DISCARD,
	},
	{
		.name = "peekaboo",
		.type = OPTION_FLAG,
		.opt.flag = &cfg.peekaboo,
	},
	{
		.name = "port",
		.argname = "port",
		.desc = "Use -connect instead",
		.type = OPTION_ARG_FUNC,
		.opt.argfunc = s_client_opt_port,
	},
	{
		.name = "prexit",
		.desc = "Print session information when the program exits",
		.type = OPTION_FLAG,
		.opt.flag = &cfg.prexit,
	},
	{
		.name = "proxy",
		.argname = "host:port",
		.desc = "Connect to http proxy",
		.type = OPTION_ARG,
		.opt.arg = &cfg.proxy,
	},
	{
		.name = "quiet",
		.desc = "Inhibit printing of session and certificate info",
		.type = OPTION_FUNC,
		.opt.func = s_client_opt_quiet,
	},
	{
		.name = "reconnect",
		.desc = "Drop and re-make the connection with the same Session-ID",
		.type = OPTION_VALUE,
		.opt.value = &cfg.reconnect,
		.value = 5,
	},
	{
		.name = "servername",
		.argname = "name",
		.desc = "Set TLS extension servername in ClientHello (SNI)",
		.type = OPTION_ARG,
		.opt.arg = &cfg.servername,
	},
	{
		.name = "serverpref",
		.desc = "Use server's cipher preferences",
		.type = OPTION_VALUE_OR,
		.opt.value = &cfg.off,
		.value = SSL_OP_CIPHER_SERVER_PREFERENCE,
	},
	{
		.name = "sess_in",
		.argname = "file",
		.desc = "File to read TLS session from",
		.type = OPTION_ARG,
		.opt.arg = &cfg.sess_in,
	},
	{
		.name = "sess_out",
		.argname = "file",
		.desc = "File to write TLS session to",
		.type = OPTION_ARG,
		.opt.arg = &cfg.sess_out,
	},
	{
		.name = "showcerts",
		.desc = "Show all server certificates in the chain",
		.type = OPTION_FLAG,
		.opt.flag = &cfg.showcerts,
	},
	{
		.name = "starttls",
		.argname = "protocol",
		.desc = "Use the STARTTLS command before starting TLS,\n"
		        "smtp, lmtp, pop3, imap, sieve, ftp and xmpp "
			"are supported.",
		.type = OPTION_ARG_FUNC,
		.opt.argfunc = s_client_opt_starttls,
	},
	{
		.name = "state",
		.desc = "Print the TLS session states",
		.type = OPTION_FLAG,
		.opt.flag = &cfg.state,
	},
	{
		.name = "status",
		.desc = "Send a certificate status request to the server (OCSP)",
		.type = OPTION_FLAG,
		.opt.flag = &cfg.status_req,
	},
#ifndef OPENSSL_NO_DTLS
	{
		.name = "timeout",
		.desc = "Enable send/receive timeout on DTLS connections",
		.type = OPTION_FLAG,
		.opt.flag = &cfg.enable_timeouts,
	},
#endif
	{
		.name = "tls1_2",
		.desc = "Just use TLSv1.2",
		.type = OPTION_FUNC,
		.opt.func = s_client_opt_protocol_version_tls1_2,
	},
	{
		.name = "tls1_3",
		.desc = "Just use TLSv1.3",
		.type = OPTION_FUNC,
		.opt.func = s_client_opt_protocol_version_tls1_3,
	},
	{
		.name = "tlsextdebug",
		.desc = "Hex dump of all TLS extensions received",
		.type = OPTION_FLAG,
		.opt.flag = &cfg.tlsextdebug,
	},
#ifndef OPENSSL_NO_SRTP
	{
		.name = "use_srtp",
		.argname = "profiles",
		.desc = "Offer SRTP key management with a colon-separated profiles",
		.type = OPTION_ARG,
		.opt.arg = &cfg.srtp_profiles,
	},
#endif
	{
		.name = "verify",
		.argname = "depth",
		.desc = "Turn on peer certificate verification, with a max of depth",
		.type = OPTION_ARG_FUNC,
		.opt.argfunc = s_client_opt_verify,
	},
	{
		.name = "verify_return_error",
		.desc = "Return verification error",
		.type = OPTION_FLAG,
		.opt.flag = &verify_return_error,
	},
	{
		.name = "xmpphost",
		.argname = "host",
		.desc = "Connect to this virtual host on the xmpp server",
		.type = OPTION_ARG,
		.opt.arg = &cfg.xmpphost,
	},
	{
		.name = NULL,
		.desc = "",
		.type = OPTION_ARGV_FUNC,
		.opt.argvfunc = s_client_opt_verify_param,
	},
	{ NULL },
};

static void
sc_usage(void)
{
	fprintf(stderr, "usage: s_client "
	    "[-4 | -6] [-alpn protocols] [-bugs] [-CAfile file]\n"
	    "    [-CApath directory] [-cert file] [-certform der | pem] [-check_ss_sig]\n"
	    "    [-cipher cipherlist] [-connect host[:port]] [-crl_check]\n"
	    "    [-crl_check_all] [-crlf] [-debug] [-dtls] [-dtls1_2] [-extended_crl]\n"
	    "    [-groups list] [-host host] [-ign_eof] [-ignore_critical]\n"
	    "    [-issuer_checks] [-key keyfile] [-keyform der | pem]\n"
	    "    [-keymatexport label] [-keymatexportlen len] [-legacy_server_connect]\n"
	    "    [-msg] [-mtu mtu] [-nbio] [-nbio_test] [-no_comp] [-no_ign_eof]\n"
	    "    [-no_legacy_server_connect] [-no_ticket] \n"
	    "    [-no_tls1_2] [-no_tls1_3] [-pass arg] [-policy_check]\n"
	    "    [-port port] [-prexit] [-proxy host:port] [-quiet] [-reconnect]\n"
	    "    [-servername name] [-serverpref] [-sess_in file] [-sess_out file]\n"
	    "    [-showcerts] [-starttls protocol] [-state] [-status] [-timeout]\n"
	    "    [-tls1_2] [-tls1_3] [-tlsextdebug]\n"
	    "    [-use_srtp profiles] [-verify depth] [-verify_return_error]\n"
	    "    [-x509_strict] [-xmpphost host]\n");
	fprintf(stderr, "\n");
	options_usage(s_client_options);
	fprintf(stderr, "\n");
}

static int
s_client_sieve_response_ok(const char *resp, int resplen)
{
	/* All lines need to be CRLF terminated */
	if (resplen <= 2) {
		BIO_printf(bio_err, "Failed to get full server line\n");
		return 0;
	}
	if (resplen >= 4 && strncasecmp(resp, "OK", 2) == 0 &&
	    (resp[2] == ' ' || resp[2] == '\r'))
		return 1;
	if (resplen >= 4 && strncasecmp(resp, "NO", 2) == 0 &&
	    (resp[2] == ' ' || resp[2] == '\r'))
		BIO_printf(bio_err, "Server rejected our connection\n");
	else if (resplen >= 5 && strncasecmp(resp, "BYE", 3) == 0 &&
	    (resp[3] == ' ' || resp[3] == '\r'))
		BIO_printf(bio_err, "Server disconnected our connection\n");
	else
		BIO_printf(bio_err, "Server sent invalid response\n");
	return 0;
}

int
s_client_main(int argc, char **argv)
{
	SSL *con = NULL;
	int s, k, p = 0, pending = 0;
	char *cbuf = NULL, *sbuf = NULL, *mbuf = NULL, *pbuf = NULL;
	int cbuf_len, cbuf_off;
	int sbuf_len, sbuf_off;
	int full_log = 1;
	const char *servername;
	char *pass = NULL;
	X509 *cert = NULL;
	EVP_PKEY *key = NULL;
	int badop = 0;
	int write_tty, read_tty, write_ssl, read_ssl, tty_on, ssl_pending;
	SSL_CTX *ctx = NULL;
	int ret = 1, in_init = 1, i;
	BIO *bio_c_out = NULL;
	BIO *sbio;
	int mbuf_len = 0;
	struct timeval timeout;
	tlsextctx tlsextcbp = {NULL, 0};
	struct sockaddr_storage peer;
	int peerlen = sizeof(peer);

	if (pledge("stdio cpath wpath rpath inet dns tty", NULL) == -1) {
		perror("pledge");
		exit(1);
	}

	memset(&cfg, 0, sizeof(cfg));
	cfg.af = AF_UNSPEC;
	cfg.cert_format = FORMAT_PEM;
	cfg.host = SSL_HOST_NAME;
	cfg.key_format = FORMAT_PEM;
	cfg.keymatexportlen = 20;
	cfg.meth = TLS_client_method();
	cfg.port = PORT_STR;
	cfg.socket_type = SOCK_STREAM;
	cfg.starttls_proto = PROTO_OFF;
	cfg.verify = SSL_VERIFY_NONE;

	if (((cbuf = malloc(BUFSIZZ)) == NULL) ||
	    ((sbuf = malloc(BUFSIZZ)) == NULL) ||
	    ((pbuf = malloc(BUFSIZZ)) == NULL) ||
	    ((mbuf = malloc(BUFSIZZ + 1)) == NULL)) {	/* NUL byte */
		BIO_printf(bio_err, "out of memory\n");
		goto end;
	}
	verify_depth = 0;

	if (options_parse(argc, argv, s_client_options, NULL, NULL) != 0) {
		badop = 1;
		goto bad;
	}
	if (cfg.proxy != NULL) {
		if (!extract_host_port(cfg.proxy,
		    &cfg.host, NULL, &cfg.port))
			goto bad;
		if (cfg.connect == NULL)
			cfg.connect = SSL_HOST_NAME;
	} else if (cfg.connect != NULL) {
		if (!extract_host_port(cfg.connect,
		    &cfg.host, NULL, &cfg.port))
			goto bad;
	}
	if (badop) {
 bad:
		if (cfg.errstr == NULL)
			sc_usage();
		goto end;
	}

	if (!app_passwd(bio_err, cfg.passarg, NULL, &pass, NULL)) {
		BIO_printf(bio_err, "Error getting password\n");
		goto end;
	}
	if (cfg.key_file == NULL)
		cfg.key_file = cfg.cert_file;


	if (cfg.key_file) {

		key = load_key(bio_err, cfg.key_file,
		    cfg.key_format, 0, pass,
		    "client certificate private key file");
		if (!key) {
			ERR_print_errors(bio_err);
			goto end;
		}
	}
	if (cfg.cert_file) {
		cert = load_cert(bio_err, cfg.cert_file,
		    cfg.cert_format,
		    NULL, "client certificate file");

		if (!cert) {
			ERR_print_errors(bio_err);
			goto end;
		}
	}
	if (cfg.quiet && !cfg.debug &&
	    !cfg.msg) {
		if ((bio_c_out = BIO_new(BIO_s_null())) == NULL)
			goto end;
	} else {
		if ((bio_c_out = BIO_new_fp(stdout, BIO_NOCLOSE)) == NULL)
			goto end;
	}

	ctx = SSL_CTX_new(cfg.meth);
	if (ctx == NULL) {
		ERR_print_errors(bio_err);
		goto end;
	}

	SSL_CTX_clear_mode(ctx, SSL_MODE_AUTO_RETRY);

	if (cfg.vpm)
		SSL_CTX_set1_param(ctx, cfg.vpm);

	if (!SSL_CTX_set_min_proto_version(ctx, cfg.min_version))
		goto end;
	if (!SSL_CTX_set_max_proto_version(ctx, cfg.max_version))
		goto end;

#ifndef OPENSSL_NO_SRTP
	if (cfg.srtp_profiles != NULL)
		SSL_CTX_set_tlsext_use_srtp(ctx, cfg.srtp_profiles);
#endif
	if (cfg.bugs)
		SSL_CTX_set_options(ctx, SSL_OP_ALL | cfg.off);
	else
		SSL_CTX_set_options(ctx, cfg.off);

	if (cfg.clr)
		SSL_CTX_clear_options(ctx, cfg.clr);

	if (cfg.alpn_in) {
		unsigned short alpn_len;
		unsigned char *alpn;

		alpn = next_protos_parse(&alpn_len, cfg.alpn_in);
		if (alpn == NULL) {
			BIO_printf(bio_err, "Error parsing -alpn argument\n");
			goto end;
		}
		SSL_CTX_set_alpn_protos(ctx, alpn, alpn_len);
		free(alpn);
	}
	if (cfg.groups_in != NULL) {
		if (SSL_CTX_set1_groups_list(ctx, cfg.groups_in) != 1) {
			BIO_printf(bio_err, "Failed to set groups '%s'\n",
			    cfg.groups_in);
			goto end;
		}
	}

	if (cfg.state)
		SSL_CTX_set_info_callback(ctx, apps_ssl_info_callback);
	if (cfg.cipher != NULL)
		if (!SSL_CTX_set_cipher_list(ctx, cfg.cipher)) {
			BIO_printf(bio_err, "error setting cipher list\n");
			ERR_print_errors(bio_err);
			goto end;
		}

	SSL_CTX_set_verify(ctx, cfg.verify, verify_callback);
	if (!set_cert_key_stuff(ctx, cert, key))
		goto end;

	if ((cfg.CAfile || cfg.CApath)
	    && !SSL_CTX_load_verify_locations(ctx, cfg.CAfile,
	    cfg.CApath))
		ERR_print_errors(bio_err);

	if (!SSL_CTX_set_default_verify_paths(ctx))
		ERR_print_errors(bio_err);

	con = SSL_new(ctx);
	if (cfg.sess_in) {
		SSL_SESSION *sess;
		BIO *stmp = BIO_new_file(cfg.sess_in, "r");
		if (!stmp) {
			BIO_printf(bio_err, "Can't open session file %s\n",
			    cfg.sess_in);
			ERR_print_errors(bio_err);
			goto end;
		}
		sess = PEM_read_bio_SSL_SESSION(stmp, NULL, 0, NULL);
		BIO_free(stmp);
		if (!sess) {
			BIO_printf(bio_err, "Can't open session file %s\n",
			    cfg.sess_in);
			ERR_print_errors(bio_err);
			goto end;
		}
		SSL_set_session(con, sess);
		SSL_SESSION_free(sess);
	}

	/* Attempt to opportunistically use the host name for SNI. */
	servername = cfg.servername;
	if (servername == NULL)
		servername = cfg.host;

	if (!cfg.no_servername && servername != NULL &&
	    !SSL_set_tlsext_host_name(con, servername)) {
		long ssl_err = ERR_peek_error();

		if (cfg.servername != NULL ||
		    ERR_GET_LIB(ssl_err) != ERR_LIB_SSL ||
		    ERR_GET_REASON(ssl_err) != SSL_R_SSL3_EXT_INVALID_SERVERNAME) {
			BIO_printf(bio_err,
			    "Unable to set TLS servername extension.\n");
			ERR_print_errors(bio_err);
			goto end;
		}
		servername = NULL;
		ERR_clear_error();
	}
	if (!cfg.no_servername && servername != NULL) {
		tlsextcbp.biodebug = bio_err;
		SSL_CTX_set_tlsext_servername_callback(ctx, ssl_servername_cb);
		SSL_CTX_set_tlsext_servername_arg(ctx, &tlsextcbp);
	}

 re_start:

	if (init_client(&s, cfg.host, cfg.port,
	    cfg.socket_type, cfg.af) == 0) {
		BIO_printf(bio_err, "connect:errno=%d\n", errno);
		goto end;
	}
	BIO_printf(bio_c_out, "CONNECTED(%08X)\n", s);

	if (cfg.nbio) {
		if (!cfg.quiet)
			BIO_printf(bio_c_out, "turning on non blocking io\n");
		if (!BIO_socket_nbio(s, 1)) {
			ERR_print_errors(bio_err);
			goto end;
		}
	}

	if (SSL_is_dtls(con)) {
		sbio = BIO_new_dgram(s, BIO_NOCLOSE);
		if (getsockname(s, (struct sockaddr *)&peer,
		    (void *)&peerlen) == -1) {
			BIO_printf(bio_err, "getsockname:errno=%d\n",
			    errno);
			shutdown(s, SHUT_RD);
			close(s);
			goto end;
		}
		(void) BIO_ctrl_set_connected(sbio, 1, &peer);

		if (cfg.enable_timeouts) {
			timeout.tv_sec = 0;
			timeout.tv_usec = DGRAM_RCV_TIMEOUT;
			BIO_ctrl(sbio, BIO_CTRL_DGRAM_SET_RECV_TIMEOUT, 0,
			    &timeout);

			timeout.tv_sec = 0;
			timeout.tv_usec = DGRAM_SND_TIMEOUT;
			BIO_ctrl(sbio, BIO_CTRL_DGRAM_SET_SEND_TIMEOUT, 0,
			    &timeout);
		}
		if (cfg.socket_mtu > 28) {
			SSL_set_options(con, SSL_OP_NO_QUERY_MTU);
			SSL_set_mtu(con, cfg.socket_mtu - 28);
		} else
			/* want to do MTU discovery */
			BIO_ctrl(sbio, BIO_CTRL_DGRAM_MTU_DISCOVER, 0, NULL);
	} else
		sbio = BIO_new_socket(s, BIO_NOCLOSE);

	if (cfg.nbio_test) {
		BIO *test;

		test = BIO_new(BIO_f_nbio_test());
		sbio = BIO_push(test, sbio);
	}
	if (cfg.debug) {
		BIO_set_callback(sbio, bio_dump_callback);
		BIO_set_callback_arg(sbio, (char *) bio_c_out);
	}
	if (cfg.msg) {
		SSL_set_msg_callback(con, msg_cb);
		SSL_set_msg_callback_arg(con, bio_c_out);
	}
	if (cfg.tlsextdebug) {
		SSL_set_tlsext_debug_callback(con, tlsext_cb);
		SSL_set_tlsext_debug_arg(con, bio_c_out);
	}
	if (cfg.status_req) {
		SSL_set_tlsext_status_type(con, TLSEXT_STATUSTYPE_ocsp);
		SSL_CTX_set_tlsext_status_cb(ctx, ocsp_resp_cb);
		SSL_CTX_set_tlsext_status_arg(ctx, bio_c_out);
	}

	SSL_set_bio(con, sbio, sbio);
	SSL_set_connect_state(con);

	/* ok, lets connect */
	read_tty = 1;
	write_tty = 0;
	tty_on = 0;
	read_ssl = 1;
	write_ssl = 1;

	cbuf_len = 0;
	cbuf_off = 0;
	sbuf_len = 0;
	sbuf_off = 0;

	/* This is an ugly hack that does a lot of assumptions */
	/*
	 * We do have to handle multi-line responses which may come in a
	 * single packet or not. We therefore have to use BIO_gets() which
	 * does need a buffering BIO. So during the initial chitchat we do
	 * push a buffering BIO into the chain that is removed again later on
	 * to not disturb the rest of the s_client operation.
	 */
	if (cfg.starttls_proto == PROTO_SMTP ||
	    cfg.starttls_proto == PROTO_LMTP) {
		int foundit = 0;
		BIO *fbio = BIO_new(BIO_f_buffer());
		BIO_push(fbio, sbio);
		/* wait for multi-line response to end from SMTP */
		do {
			mbuf_len = BIO_gets(fbio, mbuf, BUFSIZZ);
		}
		while (mbuf_len > 3 && mbuf[3] == '-');
		/* STARTTLS command requires EHLO... */
		BIO_printf(fbio, "%cHLO openssl.client.net\r\n",
		    cfg.starttls_proto == PROTO_SMTP ? 'E' : 'L');
		(void) BIO_flush(fbio);
		/* wait for multi-line response to end EHLO SMTP response */
		do {
			mbuf_len = BIO_gets(fbio, mbuf, BUFSIZZ);
			if (strstr(mbuf, "STARTTLS"))
				foundit = 1;
		}
		while (mbuf_len > 3 && mbuf[3] == '-');
		(void) BIO_flush(fbio);
		BIO_pop(fbio);
		BIO_free(fbio);
		if (!foundit)
			BIO_printf(bio_err,
			    "didn't find starttls in server response,"
			    " try anyway...\n");
		BIO_printf(sbio, "STARTTLS\r\n");
		BIO_read(sbio, sbuf, BUFSIZZ);
	} else if (cfg.starttls_proto == PROTO_POP3) {
		mbuf_len = BIO_read(sbio, mbuf, BUFSIZZ);
		if (mbuf_len == -1) {
			BIO_printf(bio_err, "BIO_read failed\n");
			goto end;
		}
		BIO_printf(sbio, "STLS\r\n");
		BIO_read(sbio, sbuf, BUFSIZZ);
	} else if (cfg.starttls_proto == PROTO_IMAP) {
		int foundit = 0;
		BIO *fbio = BIO_new(BIO_f_buffer());
		BIO_push(fbio, sbio);
		BIO_gets(fbio, mbuf, BUFSIZZ);
		/* STARTTLS command requires CAPABILITY... */
		BIO_printf(fbio, ". CAPABILITY\r\n");
		(void) BIO_flush(fbio);
		/* wait for multi-line CAPABILITY response */
		do {
			mbuf_len = BIO_gets(fbio, mbuf, BUFSIZZ);
			if (strstr(mbuf, "STARTTLS"))
				foundit = 1;
		}
		while (mbuf_len > 3 && mbuf[0] != '.');
		(void) BIO_flush(fbio);
		BIO_pop(fbio);
		BIO_free(fbio);
		if (!foundit)
			BIO_printf(bio_err,
			    "didn't find STARTTLS in server response,"
			    " try anyway...\n");
		BIO_printf(sbio, ". STARTTLS\r\n");
		BIO_read(sbio, sbuf, BUFSIZZ);
	} else if (cfg.starttls_proto == PROTO_SIEVE) {
		int foundit = 0;
		BIO *fbio;

		if ((fbio = BIO_new(BIO_f_buffer()))== NULL) {
			BIO_printf(bio_err, "Failed to create BIO\n");
			goto end;
		}
		BIO_push(fbio, sbio);
		/* wait for multi-line CAPABILITY response */
		while (1) {
			mbuf_len = BIO_gets(fbio, mbuf, BUFSIZZ);
			if (mbuf_len > 2 && mbuf[0] == '"') {
				if (strcasecmp(mbuf, "\"STARTTLS\"\r\n") == 0)
					foundit = 1;
			} else if (s_client_sieve_response_ok(mbuf, mbuf_len))
				break;
			else
				goto end;
		}
		if (!foundit)
			BIO_printf(bio_err,
			    "didn't find STARTTLS in server response,"
			    " try anyway...\n");
		BIO_printf(sbio, "STARTTLS\r\n");
		mbuf_len = BIO_gets(fbio, mbuf, BUFSIZZ);
		(void) BIO_flush(fbio);
		BIO_pop(fbio);
		BIO_free(fbio);
		if (!s_client_sieve_response_ok(mbuf, mbuf_len))
			goto end;
	} else if (cfg.starttls_proto == PROTO_FTP) {
		BIO *fbio = BIO_new(BIO_f_buffer());
		BIO_push(fbio, sbio);
		/* wait for multi-line response to end from FTP */
		do {
			mbuf_len = BIO_gets(fbio, mbuf, BUFSIZZ);
		}
		while (mbuf_len > 3 && mbuf[3] == '-');
		(void) BIO_flush(fbio);
		BIO_pop(fbio);
		BIO_free(fbio);
		BIO_printf(sbio, "AUTH TLS\r\n");
		BIO_read(sbio, sbuf, BUFSIZZ);
	} else if (cfg.starttls_proto == PROTO_XMPP) {
		int seen = 0;
		BIO_printf(sbio, "<stream:stream "
		    "xmlns:stream='http://etherx.jabber.org/streams' "
		    "xmlns='jabber:client' to='%s' version='1.0'>",
		    cfg.xmpphost ?
		    cfg.xmpphost : cfg.host);
		seen = BIO_read(sbio, mbuf, BUFSIZZ);

		if (seen <= 0)
			goto shut;

		mbuf[seen] = 0;
		while (!strstr(mbuf, "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'") &&
		       !strstr(mbuf, "<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"")) {
			seen = BIO_read(sbio, mbuf, BUFSIZZ);

			if (seen <= 0)
				goto shut;

			mbuf[seen] = 0;
		}
		BIO_printf(sbio,
		    "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
		seen = BIO_read(sbio, sbuf, BUFSIZZ);
		sbuf[seen] = 0;
		if (!strstr(sbuf, "<proceed"))
			goto shut;
		mbuf[0] = 0;
	} else if (cfg.proxy != NULL) {
		BIO_printf(sbio, "CONNECT %s HTTP/1.0\r\n\r\n",
		    cfg.connect);
		mbuf_len = BIO_read(sbio, mbuf, BUFSIZZ);
		if (mbuf_len == -1) {
			BIO_printf(bio_err, "BIO_read failed\n");
			goto end;
		}
	}
	for (;;) {
		struct pollfd pfd[3];	/* stdin, stdout, socket */
		int ptimeout = -1;

		if (SSL_is_dtls(con) && DTLSv1_get_timeout(con, &timeout))
			ptimeout = timeout.tv_sec * 1000 +
			    timeout.tv_usec / 1000;

		if (SSL_in_init(con) && !SSL_total_renegotiations(con)) {
			in_init = 1;
			tty_on = 0;
		} else {
			tty_on = 1;
			if (in_init) {
				in_init = 0;
				if (cfg.sess_out) {
					BIO *stmp = BIO_new_file(
					    cfg.sess_out, "w");
					if (stmp) {
						PEM_write_bio_SSL_SESSION(stmp,
						    SSL_get_session(con));
						BIO_free(stmp);
					} else
						BIO_printf(bio_err,
						    "Error writing session file %s\n",
						    cfg.sess_out);
				}
				print_stuff(bio_c_out, con, full_log);
				if (full_log > 0)
					full_log--;

				if (cfg.starttls_proto) {
					BIO_write(bio_err, mbuf, mbuf_len);
					/* We don't need to know any more */
					cfg.starttls_proto = PROTO_OFF;
				}
				if (cfg.reconnect) {
					cfg.reconnect--;
					BIO_printf(bio_c_out,
					    "drop connection and then reconnect\n");
					SSL_shutdown(con);
					SSL_set_connect_state(con);
					shutdown(SSL_get_fd(con), SHUT_RD);
					close(SSL_get_fd(con));
					goto re_start;
				}
			}
		}

		ssl_pending = read_ssl && SSL_pending(con);

		pfd[0].fd = -1;
		pfd[1].fd = -1;
		if (!ssl_pending) {
			if (tty_on) {
				if (read_tty) {
					pfd[0].fd = fileno(stdin);
					pfd[0].events = POLLIN;
				}
				if (write_tty) {
					pfd[1].fd = fileno(stdout);
					pfd[1].events = POLLOUT;
				}
			}

			pfd[2].fd = SSL_get_fd(con);
			pfd[2].events = 0;
			if (read_ssl)
				pfd[2].events |= POLLIN;
			if (write_ssl)
				pfd[2].events |= POLLOUT;

/*			printf("mode tty(%d %d%d) ssl(%d%d)\n",
				tty_on,read_tty,write_tty,read_ssl,write_ssl);*/

			i = poll(pfd, 3, ptimeout);
			if (i == -1) {
				BIO_printf(bio_err, "bad select %d\n",
				    errno);
				goto shut;
				/* goto end; */
			}
		}
		if (SSL_is_dtls(con) &&
		    DTLSv1_handle_timeout(con) > 0)
			BIO_printf(bio_err, "TIMEOUT occured\n");
		if (!ssl_pending &&
		    (pfd[2].revents & (POLLOUT|POLLERR|POLLNVAL))) {
			if (pfd[2].revents & (POLLERR|POLLNVAL)) {
				BIO_printf(bio_err, "poll error");
				goto shut;
			}
			k = SSL_write(con, &(cbuf[cbuf_off]),
			    (unsigned int) cbuf_len);
			switch (SSL_get_error(con, k)) {
			case SSL_ERROR_NONE:
				cbuf_off += k;
				cbuf_len -= k;
				if (k <= 0)
					goto end;
				/* we have done a  write(con,NULL,0); */
				if (cbuf_len <= 0) {
					read_tty = 1;
					write_ssl = 0;
				} else {	/* if (cbuf_len > 0) */
					read_tty = 0;
					write_ssl = 1;
				}
				break;
			case SSL_ERROR_WANT_WRITE:
				BIO_printf(bio_c_out, "write W BLOCK\n");
				write_ssl = 1;
				read_tty = 0;
				break;
			case SSL_ERROR_WANT_READ:
				BIO_printf(bio_c_out, "write R BLOCK\n");
				write_tty = 0;
				read_ssl = 1;
				write_ssl = 0;
				break;
			case SSL_ERROR_WANT_X509_LOOKUP:
				BIO_printf(bio_c_out, "write X BLOCK\n");
				break;
			case SSL_ERROR_ZERO_RETURN:
				if (cbuf_len != 0) {
					BIO_printf(bio_c_out, "shutdown\n");
					ret = 0;
					goto shut;
				} else {
					read_tty = 1;
					write_ssl = 0;
					break;
				}

			case SSL_ERROR_SYSCALL:
				if ((k != 0) || (cbuf_len != 0)) {
					BIO_printf(bio_err, "write:errno=%d\n",
					    errno);
					goto shut;
				} else {
					read_tty = 1;
					write_ssl = 0;
				}
				break;
			case SSL_ERROR_SSL:
				ERR_print_errors(bio_err);
				goto shut;
			}
		} else if (!ssl_pending &&
		    (pfd[1].revents & (POLLOUT|POLLERR|POLLNVAL))) {
			if (pfd[1].revents & (POLLERR|POLLNVAL)) {
				BIO_printf(bio_err, "poll error");
				goto shut;
			}
			i = write(fileno(stdout), &(sbuf[sbuf_off]), sbuf_len);

			if (i <= 0) {
				BIO_printf(bio_c_out, "DONE\n");
				ret = 0;
				goto shut;
				/* goto end; */
			}
			sbuf_len -= i;
			sbuf_off += i;
			if (sbuf_len <= 0) {
				read_ssl = 1;
				write_tty = 0;
			}
		} else if (ssl_pending || (pfd[2].revents & (POLLIN|POLLHUP))) {
#ifdef RENEG
			{
				static int iiii;
				if (++iiii == 52) {
					SSL_renegotiate(con);
					iiii = 0;
				}
			}
#endif
			if (cfg.peekaboo) {
				k = p = SSL_peek(con, pbuf, 1024 /* BUFSIZZ */ );
				pending = SSL_pending(con);
				if (SSL_get_error(con, p) == SSL_ERROR_NONE) {
					if (p <= 0)
						goto end;

					k = SSL_read(con, sbuf, p);
				}
			} else {
				k = SSL_read(con, sbuf, 1024 /* BUFSIZZ */ );
			}

			switch (SSL_get_error(con, k)) {
			case SSL_ERROR_NONE:
				if (k <= 0)
					goto end;
				sbuf_off = 0;
				sbuf_len = k;
				if (cfg.peekaboo) {
					if (p != pending) {
						ret = -1;
						BIO_printf(bio_err,
						    "peeked %d but pending %d!\n",
						    p, pending);
						goto shut;
					}
					if (k < p) {
						ret = -1;
						BIO_printf(bio_err,
						    "read less than peek!\n");
						goto shut;
					}
					if (p > 0 &&
					    (memcmp(sbuf, pbuf, p) != 0)) {
						ret = -1;
						BIO_printf(bio_err,
						    "peek of %d different from read of %d!\n",
						    p, k);
						goto shut;
					}
				}
				read_ssl = 0;
				write_tty = 1;
				break;
			case SSL_ERROR_WANT_WRITE:
				BIO_printf(bio_c_out, "read W BLOCK\n");
				write_ssl = 1;
				read_tty = 0;
				break;
			case SSL_ERROR_WANT_READ:
				BIO_printf(bio_c_out, "read R BLOCK\n");
				write_tty = 0;
				read_ssl = 1;
				if ((read_tty == 0) && (write_ssl == 0))
					write_ssl = 1;
				break;
			case SSL_ERROR_WANT_X509_LOOKUP:
				BIO_printf(bio_c_out, "read X BLOCK\n");
				break;
			case SSL_ERROR_SYSCALL:
				ret = errno;
				BIO_printf(bio_err, "read:errno=%d\n", ret);
				goto shut;
			case SSL_ERROR_ZERO_RETURN:
				BIO_printf(bio_c_out, "closed\n");
				ret = 0;
				goto shut;
			case SSL_ERROR_SSL:
				ERR_print_errors(bio_err);
				goto shut;
				/* break; */
			}
		} else if (pfd[0].revents) {
			if (pfd[0].revents & (POLLERR|POLLNVAL)) {
				BIO_printf(bio_err, "poll error");
				goto shut;
			}
			if (cfg.crlf) {
				int j, lf_num;

				i = read(fileno(stdin), cbuf, BUFSIZZ / 2);
				lf_num = 0;
				/* both loops are skipped when i <= 0 */
				for (j = 0; j < i; j++)
					if (cbuf[j] == '\n')
						lf_num++;
				for (j = i - 1; j >= 0; j--) {
					cbuf[j + lf_num] = cbuf[j];
					if (cbuf[j] == '\n') {
						lf_num--;
						i++;
						cbuf[j + lf_num] = '\r';
					}
				}
				assert(lf_num == 0);
			} else
				i = read(fileno(stdin), cbuf, BUFSIZZ);

			if ((!cfg.ign_eof) &&
			    ((i <= 0) || (cbuf[0] == 'Q'))) {
				BIO_printf(bio_err, "DONE\n");
				ret = 0;
				goto shut;
			}
			if ((!cfg.ign_eof) && (cbuf[0] == 'R')) {
				BIO_printf(bio_err, "RENEGOTIATING\n");
				SSL_renegotiate(con);
				cbuf_len = 0;
			} else {
				cbuf_len = i;
				cbuf_off = 0;
			}

			write_ssl = 1;
			read_tty = 0;
		}
	}

	ret = 0;
 shut:
	if (in_init)
		print_stuff(bio_c_out, con, full_log);
	SSL_shutdown(con);
	shutdown(SSL_get_fd(con), SHUT_RD);
	close(SSL_get_fd(con));
 end:
	if (con != NULL) {
		if (cfg.prexit != 0)
			print_stuff(bio_c_out, con, 1);
		SSL_free(con);
	}
	SSL_CTX_free(ctx);
	X509_free(cert);
	EVP_PKEY_free(key);
	free(pass);
	X509_VERIFY_PARAM_free(cfg.vpm);
	freezero(cbuf, BUFSIZZ);
	freezero(sbuf, BUFSIZZ);
	freezero(pbuf, BUFSIZZ);
	freezero(mbuf, BUFSIZZ);
	BIO_free(bio_c_out);

	return (ret);
}

static void
print_stuff(BIO *bio, SSL *s, int full)
{
	X509 *peer = NULL;
	char *p;
	static const char *space = "                ";
	char buf[BUFSIZ];
	STACK_OF(X509) *sk;
	STACK_OF(X509_NAME) *sk2;
	const SSL_CIPHER *c;
	X509_NAME *xn;
	int j, i;
	unsigned char *exportedkeymat;

	if (full) {
		int got_a_chain = 0;

		sk = SSL_get_peer_cert_chain(s);
		if (sk != NULL) {
			got_a_chain = 1;	/* we don't have it for SSL2
						 * (yet) */

			BIO_printf(bio, "---\nCertificate chain\n");
			for (i = 0; i < sk_X509_num(sk); i++) {
				X509_NAME_oneline(X509_get_subject_name(
					sk_X509_value(sk, i)), buf, sizeof buf);
				BIO_printf(bio, "%2d s:%s\n", i, buf);
				X509_NAME_oneline(X509_get_issuer_name(
					sk_X509_value(sk, i)), buf, sizeof buf);
				BIO_printf(bio, "   i:%s\n", buf);
				if (cfg.showcerts)
					PEM_write_bio_X509(bio,
					    sk_X509_value(sk, i));
			}
		}
		BIO_printf(bio, "---\n");
		peer = SSL_get_peer_certificate(s);
		if (peer != NULL) {
			BIO_printf(bio, "Server certificate\n");
			if (!(cfg.showcerts && got_a_chain)) {
				/* Redundant if we showed the whole chain */
				PEM_write_bio_X509(bio, peer);
			}
			X509_NAME_oneline(X509_get_subject_name(peer),
			    buf, sizeof buf);
			BIO_printf(bio, "subject=%s\n", buf);
			X509_NAME_oneline(X509_get_issuer_name(peer),
			    buf, sizeof buf);
			BIO_printf(bio, "issuer=%s\n", buf);
		} else
			BIO_printf(bio, "no peer certificate available\n");

		sk2 = SSL_get_client_CA_list(s);
		if ((sk2 != NULL) && (sk_X509_NAME_num(sk2) > 0)) {
			BIO_printf(bio,
			    "---\nAcceptable client certificate CA names\n");
			for (i = 0; i < sk_X509_NAME_num(sk2); i++) {
				xn = sk_X509_NAME_value(sk2, i);
				X509_NAME_oneline(xn, buf, sizeof(buf));
				BIO_write(bio, buf, strlen(buf));
				BIO_write(bio, "\n", 1);
			}
		} else {
			BIO_printf(bio,
			    "---\nNo client certificate CA names sent\n");
		}
		p = SSL_get_shared_ciphers(s, buf, sizeof buf);
		if (p != NULL) {
			/*
			 * This works only for SSL 2.  In later protocol
			 * versions, the client does not know what other
			 * ciphers (in addition to the one to be used in the
			 * current connection) the server supports.
			 */

			BIO_printf(bio,
			    "---\nCiphers common between both SSL endpoints:\n");
			j = i = 0;
			while (*p) {
				if (*p == ':') {
					BIO_write(bio, space, 15 - j % 25);
					i++;
					j = 0;
					BIO_write(bio,
					    ((i % 3) ? " " : "\n"), 1);
				} else {
					BIO_write(bio, p, 1);
					j++;
				}
				p++;
			}
			BIO_write(bio, "\n", 1);
		}

		ssl_print_tmp_key(bio, s);

		BIO_printf(bio,
		    "---\nSSL handshake has read %ld bytes and written %ld bytes\n",
		    BIO_number_read(SSL_get_rbio(s)),
		    BIO_number_written(SSL_get_wbio(s)));
	}
	BIO_printf(bio, (SSL_cache_hit(s) ? "---\nReused, " : "---\nNew, "));
	c = SSL_get_current_cipher(s);
	BIO_printf(bio, "%s, Cipher is %s\n",
	    SSL_CIPHER_get_version(c),
	    SSL_CIPHER_get_name(c));
	if (peer != NULL) {
		EVP_PKEY *pktmp;

		pktmp = X509_get0_pubkey(peer);
		BIO_printf(bio, "Server public key is %d bit\n",
		    EVP_PKEY_bits(pktmp));
	}
	BIO_printf(bio, "Secure Renegotiation IS%s supported\n",
	    SSL_get_secure_renegotiation_support(s) ? "" : " NOT");

	/* Compression is not supported and will always be none. */
	BIO_printf(bio, "Compression: NONE\n");
	BIO_printf(bio, "Expansion: NONE\n");

#ifdef SSL_DEBUG
	{
		/* Print out local port of connection: useful for debugging */
		int sock;
		struct sockaddr_in ladd;
		socklen_t ladd_size = sizeof(ladd);
		sock = SSL_get_fd(s);
		getsockname(sock, (struct sockaddr *) & ladd, &ladd_size);
		BIO_printf(bio, "LOCAL PORT is %u\n",
		    ntohs(ladd.sin_port));
	}
#endif

	{
		const unsigned char *proto;
		unsigned int proto_len;
		SSL_get0_alpn_selected(s, &proto, &proto_len);
		if (proto_len > 0) {
			BIO_printf(bio, "ALPN protocol: ");
			BIO_write(bio, proto, proto_len);
			BIO_write(bio, "\n", 1);
		} else
			BIO_printf(bio, "No ALPN negotiated\n");
	}

#ifndef OPENSSL_NO_SRTP
	{
		SRTP_PROTECTION_PROFILE *srtp_profile;

		srtp_profile = SSL_get_selected_srtp_profile(s);
		if (srtp_profile)
			BIO_printf(bio,
			    "SRTP Extension negotiated, profile=%s\n",
			    srtp_profile->name);
	}
#endif

	SSL_SESSION_print(bio, SSL_get_session(s));
	if (cfg.keymatexportlabel != NULL) {
		BIO_printf(bio, "Keying material exporter:\n");
		BIO_printf(bio, "    Label: '%s'\n",
		    cfg.keymatexportlabel);
		BIO_printf(bio, "    Length: %i bytes\n",
		    cfg.keymatexportlen);
		exportedkeymat = malloc(cfg.keymatexportlen);
		if (exportedkeymat != NULL) {
			if (!SSL_export_keying_material(s, exportedkeymat,
				cfg.keymatexportlen,
				cfg.keymatexportlabel,
				strlen(cfg.keymatexportlabel),
				NULL, 0, 0)) {
				BIO_printf(bio, "    Error\n");
			} else {
				BIO_printf(bio, "    Keying material: ");
				for (i = 0; i < cfg.keymatexportlen; i++)
					BIO_printf(bio, "%02X",
					    exportedkeymat[i]);
				BIO_printf(bio, "\n");
			}
			free(exportedkeymat);
		}
	}
	BIO_printf(bio, "---\n");
	X509_free(peer);
	/* flush, or debugging output gets mixed with http response */
	(void) BIO_flush(bio);
}

static int
ocsp_resp_cb(SSL *s, void *arg)
{
	const unsigned char *p;
	int len;
	OCSP_RESPONSE *rsp;
	len = SSL_get_tlsext_status_ocsp_resp(s, &p);
	BIO_puts(arg, "OCSP response: ");
	if (!p) {
		BIO_puts(arg, "no response sent\n");
		return 1;
	}
	rsp = d2i_OCSP_RESPONSE(NULL, &p, len);
	if (!rsp) {
		BIO_puts(arg, "response parse error\n");
		BIO_dump_indent(arg, (char *) p, len, 4);
		return 0;
	}
	BIO_puts(arg, "\n======================================\n");
	OCSP_RESPONSE_print(arg, rsp, 0);
	BIO_puts(arg, "======================================\n");
	OCSP_RESPONSE_free(rsp);
	return 1;
}

static int
ssl_servername_cb(SSL *s, int *ad, void *arg)
{
	tlsextctx *p = (tlsextctx *) arg;
	const char *hn = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
	if (SSL_get_servername_type(s) != -1)
		p->ack = !SSL_session_reused(s) && hn != NULL;
	else
		BIO_printf(bio_err, "Can't use SSL_get_servername\n");

	return SSL_TLSEXT_ERR_OK;
}

