static int keymatexportlen = 20;
static BIO *bio_c_out = NULL;
static int c_quiet = 0;
+static char *sess_out = NULL;
static void print_stuff(BIO *berr, SSL *con, int full);
#ifndef OPENSSL_NO_OCSP
OPT_SERVERINFO, OPT_STARTTLS, OPT_SERVERNAME,
OPT_USE_SRTP, OPT_KEYMATEXPORT, OPT_KEYMATEXPORTLEN, OPT_SMTPHOST,
OPT_ASYNC, OPT_SPLIT_SEND_FRAG, OPT_MAX_PIPELINES, OPT_READ_BUF,
+ OPT_KEYLOG_FILE,
OPT_V_ENUM,
OPT_X_ENUM,
OPT_S_ENUM,
{"noct", OPT_NOCT, '-', "Do not request or parse SCTs (default)"},
{"ctlogfile", OPT_CTLOG_FILE, '<', "CT log list CONF file"},
#endif
+ {"keylogfile", OPT_KEYLOG_FILE, '>', "Write TLS secrets to file"},
{NULL, OPT_EOF, 0x00, NULL}
};
PROTO_CONNECT,
PROTO_IRC,
PROTO_POSTGRES,
- PROTO_LMTP
+ PROTO_LMTP,
+ PROTO_NNTP,
+ PROTO_SIEVE
} PROTOCOL_CHOICE;
static const OPT_PAIR services[] = {
{"irc", PROTO_IRC},
{"postgres", PROTO_POSTGRES},
{"lmtp", PROTO_LMTP},
+ {"nntp", PROTO_NNTP},
+ {"sieve", PROTO_SIEVE},
{NULL, 0}
};
*dest = OPENSSL_strdup(source);
}
+static int new_session_cb(SSL *S, SSL_SESSION *sess)
+{
+ BIO *stmp = BIO_new_file(sess_out, "w");
+
+ if (stmp == NULL) {
+ BIO_printf(bio_err, "Error writing session file %s\n", sess_out);
+ } else {
+ PEM_write_bio_SSL_SESSION(stmp, sess);
+ BIO_free(stmp);
+ }
+
+ /*
+ * We always return a "fail" response so that the session gets freed again
+ * because we haven't used the reference.
+ */
+ return 0;
+}
+
int s_client_main(int argc, char **argv)
{
BIO *sbio;
char *port = OPENSSL_strdup(PORT);
char *inrand = NULL;
char *passarg = NULL, *pass = NULL, *vfyCApath = NULL, *vfyCAfile = NULL;
- char *sess_in = NULL, *sess_out = NULL, *crl_file = NULL, *p;
+ char *sess_in = NULL, *crl_file = NULL, *p;
char *xmpphost = NULL;
const char *ehlo = "mail.example.com";
struct timeval timeout, *timeoutp;
int c_status_req = 0;
#endif
BIO *bio_c_msg = NULL;
+ const char *keylog_file = NULL;
FD_ZERO(&readfds);
FD_ZERO(&writefds);
case OPT_READ_BUF:
read_buf_len = atoi(opt_arg());
break;
+ case OPT_KEYLOG_FILE:
+ keylog_file = opt_arg();
+ break;
}
}
if (count4or6 >= 2) {
}
}
+ /*
+ * In TLSv1.3 NewSessionTicket messages arrive after the handshake and can
+ * come at any time. Therefore we use a callback to write out the session
+ * when we know about it. This approach works for < TLSv1.3 as well.
+ */
+ if (sess_out) {
+ SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_CLIENT
+ | SSL_SESS_CACHE_NO_INTERNAL_STORE);
+ SSL_CTX_sess_set_new_cb(ctx, new_session_cb);
+ }
+
+ if (set_keylog_file(ctx, keylog_file))
+ goto end;
+
con = SSL_new(ctx);
if (sess_in) {
SSL_SESSION *sess;
*/
int foundit = 0;
BIO *fbio = BIO_new(BIO_f_buffer());
+
BIO_push(fbio, sbio);
/* Wait for multi-line response to end from LMTP or SMTP */
do {
mbuf_len = BIO_gets(fbio, mbuf, BUFSIZZ);
- }
- while (mbuf_len > 3 && mbuf[3] == '-');
- switch ((PROTOCOL_CHOICE) starttls_proto) {
- case PROTO_LMTP:
- BIO_printf(fbio, "LHLO %s\r\n", ehlo);
- break;
- case PROTO_SMTP:
- BIO_printf(fbio, "EHLO %s\r\n", ehlo);
- break;
- }
+ } while (mbuf_len > 3 && mbuf[3] == '-');
+ if (starttls_proto == (int)PROTO_LMTP)
+ BIO_printf(fbio, "LHLO %s\r\n", ehlo);
+ else
+ BIO_printf(fbio, "EHLO %s\r\n", ehlo);
(void)BIO_flush(fbio);
/*
* Wait for multi-line response to end LHLO LMTP or EHLO SMTP
mbuf_len = BIO_gets(fbio, mbuf, BUFSIZZ);
if (strstr(mbuf, "STARTTLS"))
foundit = 1;
- }
- while (mbuf_len > 3 && mbuf[3] == '-');
+ } 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,"
+ "Didn't find STARTTLS in server response,"
" trying anyway...\n");
BIO_printf(sbio, "STARTTLS\r\n");
BIO_read(sbio, sbuf, BUFSIZZ);
{
int foundit = 0;
BIO *fbio = BIO_new(BIO_f_buffer());
+
BIO_push(fbio, sbio);
BIO_gets(fbio, mbuf, BUFSIZZ);
/* STARTTLS command requires CAPABILITY... */
BIO_free(fbio);
if (!foundit)
BIO_printf(bio_err,
- "didn't find STARTTLS in server response,"
+ "Didn't find STARTTLS in server response,"
" trying anyway...\n");
BIO_printf(sbio, ". STARTTLS\r\n");
BIO_read(sbio, sbuf, BUFSIZZ);
case PROTO_FTP:
{
BIO *fbio = BIO_new(BIO_f_buffer());
+
BIO_push(fbio, sbio);
/* wait for multi-line response to end from FTP */
do {
starttls_proto == PROTO_XMPP ? "client" : "server",
xmpphost ? xmpphost : host);
seen = BIO_read(sbio, mbuf, BUFSIZZ);
- mbuf[seen] = 0;
+ if (seen < 0) {
+ BIO_printf(bio_err, "BIO_read failed\n");
+ goto end;
+ }
+ mbuf[seen] = '\0';
while (!strstr
(mbuf, "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'")
&& !strstr(mbuf,
if (seen <= 0)
goto shut;
- mbuf[seen] = 0;
+ 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 (seen < 0) {
+ BIO_printf(bio_err, "BIO_read failed\n");
+ goto shut;
+ }
+ sbuf[seen] = '\0';
if (!strstr(sbuf, "<proceed"))
goto shut;
- mbuf[0] = 0;
+ mbuf[0] = '\0';
}
break;
case PROTO_TELNET:
break;
case PROTO_CONNECT:
{
- int foundit = 0;
+ enum {
+ error_proto, /* Wrong protocol, not even HTTP */
+ error_connect, /* CONNECT failed */
+ success
+ } foundit = error_connect;
BIO *fbio = BIO_new(BIO_f_buffer());
BIO_push(fbio, sbio);
BIO_printf(fbio, "CONNECT %s HTTP/1.0\r\n\r\n", connectstr);
(void)BIO_flush(fbio);
- /* wait for multi-line response to end CONNECT response */
- do {
- mbuf_len = BIO_gets(fbio, mbuf, BUFSIZZ);
- if (strstr(mbuf, "200") != NULL
- && strstr(mbuf, "established") != NULL)
- foundit++;
- } while (mbuf_len > 3 && foundit == 0);
+ /*
+ * The first line is the HTTP response. According to RFC 7230,
+ * it's formated exactly like this:
+ *
+ * HTTP/d.d ddd Reason text\r\n
+ */
+ mbuf_len = BIO_gets(fbio, mbuf, BUFSIZZ);
+ if (mbuf[8] != ' ') {
+ BIO_printf(bio_err,
+ "%s: HTTP CONNECT failed, incorrect response "
+ "from proxy\n", prog);
+ foundit = error_proto;
+ } else if (mbuf[9] != '2') {
+ BIO_printf(bio_err, "%s: HTTP CONNECT failed: %s ", prog,
+ &mbuf[9]);
+ } else {
+ foundit = success;
+ }
+ if (foundit != error_proto) {
+ /* Read past all following headers */
+ do {
+ mbuf_len = BIO_gets(fbio, mbuf, BUFSIZZ);
+ } while (mbuf_len > 2);
+ }
(void)BIO_flush(fbio);
BIO_pop(fbio);
BIO_free(fbio);
- if (!foundit) {
- BIO_printf(bio_err, "%s: HTTP CONNECT failed\n", prog);
+ if (foundit != success) {
goto shut;
}
}
goto shut;
}
break;
+ case PROTO_NNTP:
+ {
+ int foundit = 0;
+ BIO *fbio = BIO_new(BIO_f_buffer());
+
+ BIO_push(fbio, sbio);
+ BIO_gets(fbio, mbuf, BUFSIZZ);
+ /* STARTTLS command requires CAPABILITIES... */
+ BIO_printf(fbio, "CAPABILITIES\r\n");
+ (void)BIO_flush(fbio);
+ /* wait for multi-line CAPABILITIES response */
+ do {
+ mbuf_len = BIO_gets(fbio, mbuf, BUFSIZZ);
+ if (strstr(mbuf, "STARTTLS"))
+ foundit = 1;
+ } while (mbuf_len > 1 && 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,"
+ " trying anyway...\n");
+ BIO_printf(sbio, "STARTTLS\r\n");
+ mbuf_len = BIO_read(sbio, mbuf, BUFSIZZ);
+ if (mbuf_len < 0) {
+ BIO_printf(bio_err, "BIO_read failed\n");
+ goto end;
+ }
+ mbuf[mbuf_len] = '\0';
+ if (strstr(mbuf, "382") == NULL) {
+ BIO_printf(bio_err, "STARTTLS failed: %s", mbuf);
+ goto shut;
+ }
+ }
+ break;
+ case PROTO_SIEVE:
+ {
+ int foundit = 0;
+ BIO *fbio = BIO_new(BIO_f_buffer());
+
+ BIO_push(fbio, sbio);
+ /* wait for multi-line response to end from Sieve */
+ do {
+ mbuf_len = BIO_gets(fbio, mbuf, BUFSIZZ);
+ /*
+ * According to RFC 5804 § 1.7, capability
+ * is case-insensitive, make it uppercase
+ */
+ if (mbuf_len > 1 && mbuf[0] == '"') {
+ make_uppercase(mbuf);
+ if (strncmp(mbuf, "\"STARTTLS\"", 10) == 0)
+ foundit = 1;
+ }
+ } while (mbuf_len > 1 && 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,"
+ " trying anyway...\n");
+ BIO_printf(sbio, "STARTTLS\r\n");
+ mbuf_len = BIO_read(sbio, mbuf, BUFSIZZ);
+ if (mbuf_len < 0) {
+ BIO_printf(bio_err, "BIO_read failed\n");
+ goto end;
+ }
+ mbuf[mbuf_len] = '\0';
+ if (mbuf_len < 2) {
+ BIO_printf(bio_err, "STARTTLS failed: %s", mbuf);
+ goto shut;
+ }
+ /*
+ * According to RFC 5804 § 2.2, response codes are case-
+ * insensitive, make it uppercase but preserve the response.
+ */
+ strncpy(sbuf, mbuf, 2);
+ make_uppercase(sbuf);
+ if (strncmp(sbuf, "OK", 2) != 0) {
+ BIO_printf(bio_err, "STARTTLS not supported: %s", mbuf);
+ goto shut;
+ }
+ }
+ break;
}
for (;;) {
tlsextcbp.ack ? "" : "not ");
}
- if (sess_out) {
- BIO *stmp = BIO_new_file(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",
- sess_out);
- }
if (c_brief) {
BIO_puts(bio_err, "CONNECTION ESTABLISHED\n");
print_ssl_summary(con);
/* goto end; */
}
- sbuf_len -= i;;
+ sbuf_len -= i;
sbuf_off += i;
if (sbuf_len <= 0) {
read_ssl = 1;
OPENSSL_free(next_proto.data);
#endif
SSL_CTX_free(ctx);
+ set_keylog_file(NULL, NULL);
X509_free(cert);
sk_X509_CRL_pop_free(crls, X509_CRL_free);
EVP_PKEY_free(key);