summaryrefslogtreecommitdiffstats
path: root/src/cli.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cli.c')
-rw-r--r--src/cli.c2372
1 files changed, 2372 insertions, 0 deletions
diff --git a/src/cli.c b/src/cli.c
new file mode 100644
index 0000000..7c031f1
--- /dev/null
+++ b/src/cli.c
@@ -0,0 +1,2372 @@
+/*
+ * Copyright (C) 2000-2016 Free Software Foundation, Inc.
+ * Copyright (C) 2013-2016 Nikos Mavrogiannopoulos
+ * Copyright (C) 2015-2017 Red Hat, Inc.
+ *
+ * This file is part of GnuTLS.
+ *
+ * GnuTLS 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuTLS 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 this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#if HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#elif HAVE_WS2TCPIP_H
+#include <ws2tcpip.h>
+#endif
+#include <sys/select.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <ctype.h>
+#include <assert.h>
+
+/* Get TCP_FASTOPEN */
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#endif
+
+#include <gnutls/gnutls.h>
+#include <gnutls/abstract.h>
+#include <gnutls/dtls.h>
+#include <gnutls/x509.h>
+#include <gnutls/pkcs11.h>
+#include <gnutls/crypto.h>
+#include <gnutls/socket.h>
+
+/* Gnulib portability files. */
+#include <read-file.h>
+#include <getpass.h>
+#include <minmax.h>
+
+#include "sockets.h"
+#include "benchmark.h"
+#include "inline_cmds.h"
+
+#ifdef HAVE_DANE
+#include <gnutls/dane.h>
+#endif
+
+#include <common.h>
+#include <socket.h>
+
+#include "gnutls-cli-options.h"
+#include <ocsptool-common.h>
+
+#define MAX_BUF 4096
+
+#define HEADER_PATTERN "GET /%s HTTP/1.0\r\n" \
+ "Host: %s\r\n" \
+ "Accept: */*\r\n" \
+ "Connection: close\r\n\r\n"
+
+/* global stuff here */
+int resume, starttls, insecure, ranges, rehandshake, udp, mtu,
+ inline_commands, waitresumption;
+unsigned int global_vflags = 0;
+char *hostname = NULL;
+char service[32]="";
+int record_max_size;
+int crlf;
+int fastopen;
+unsigned int verbose = 0;
+int print_cert;
+
+const char *srp_passwd = NULL;
+const char *srp_username = NULL;
+const char *x509_keyfile = NULL;
+const char *x509_certfile = NULL;
+const char *x509_cafile = NULL;
+const char *x509_crlfile = NULL;
+static int x509ctype;
+const char *rawpk_keyfile = NULL;
+const char *rawpk_file = NULL;
+static int disable_extensions;
+static int disable_sni;
+static unsigned int init_flags = GNUTLS_CLIENT | GNUTLS_ENABLE_RAWPK;
+static const char *priorities = NULL;
+static const char *inline_commands_prefix;
+
+const char *psk_username = NULL;
+gnutls_datum_t psk_key = { NULL, 0 };
+
+static gnutls_srp_client_credentials_t srp_cred;
+static gnutls_psk_client_credentials_t psk_cred;
+static gnutls_anon_client_credentials_t anon_cred;
+static gnutls_certificate_credentials_t xcred;
+
+/* end of global stuff */
+
+/* prototypes */
+
+static void check_server_cmd(socket_st * socket, int ret);
+static void init_global_tls_stuff(void);
+static int cert_verify_ocsp(gnutls_session_t session);
+static const char *host_from_url(const char *url, unsigned int *port, const char **path);
+static size_t get_data(void *buf, size_t size, size_t nmemb, void *userp);
+static int getissuer_callback(const gnutls_x509_trust_list_t tlist,
+ const gnutls_x509_crt_t cert,
+ gnutls_x509_crt_t **issuers,
+ unsigned int *issuers_size);
+
+#define MAX_CRT 6
+static unsigned int x509_crt_size;
+static gnutls_pcert_st x509_crt[MAX_CRT];
+static gnutls_privkey_t x509_key = NULL;
+static gnutls_pcert_st rawpk;
+static gnutls_privkey_t rawpk_key = NULL;
+
+
+/* Load a PKCS #8, PKCS #12 private key or PKCS #11 URL
+ */
+static void load_priv_key(gnutls_privkey_t* privkey, const char* key_source)
+{
+ int ret;
+ gnutls_datum_t data = { NULL, 0 };
+
+ ret = gnutls_privkey_init(privkey);
+
+ if (ret < 0) {
+ fprintf(stderr, "*** Error initializing key: %s\n",
+ gnutls_strerror(ret));
+ exit(1);
+ }
+
+ gnutls_privkey_set_pin_function(*privkey, pin_callback,
+ NULL);
+
+ if (gnutls_url_is_supported(key_source) != 0) {
+ ret = gnutls_privkey_import_url(*privkey, key_source, 0);
+ if (ret < 0) {
+ fprintf(stderr,
+ "*** Error loading url: %s\n",
+ gnutls_strerror(ret));
+ exit(1);
+ }
+ } else {
+ ret = gnutls_load_file(key_source, &data);
+ if (ret < 0) {
+ fprintf(stderr,
+ "*** Error loading key file.\n");
+ exit(1);
+ }
+
+ ret = gnutls_privkey_import_x509_raw(*privkey, &data,
+ x509ctype, NULL, 0);
+ if (ret < 0) {
+ fprintf(stderr,
+ "*** Error importing key: %s\n",
+ gnutls_strerror(ret));
+ exit(1);
+ }
+
+ gnutls_free(data.data);
+ }
+}
+
+/* Load the X509 certificate and the private key.
+ */
+static void load_x509_keys(void)
+{
+ unsigned int crt_num;
+ int ret;
+ unsigned int i;
+ gnutls_datum_t data = { NULL, 0 };
+ gnutls_x509_crt_t crt_list[MAX_CRT];
+
+ if (x509_certfile != NULL && x509_keyfile != NULL) {
+#ifdef ENABLE_PKCS11
+ if (strncmp(x509_certfile, "pkcs11:", 7) == 0) {
+ crt_num = 1;
+ ret = gnutls_x509_crt_init(&crt_list[0]);
+ if (ret < 0) {
+ fprintf(stderr, "Memory error\n");
+ exit(1);
+ }
+ gnutls_x509_crt_set_pin_function(crt_list[0],
+ pin_callback,
+ NULL);
+
+ ret =
+ gnutls_x509_crt_import_pkcs11_url(crt_list[0],
+ x509_certfile,
+ 0);
+
+ if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
+ ret =
+ gnutls_x509_crt_import_pkcs11_url
+ (crt_list[0], x509_certfile,
+ GNUTLS_PKCS11_OBJ_FLAG_LOGIN);
+
+ if (ret < 0) {
+ fprintf(stderr,
+ "*** Error loading cert file.\n");
+ exit(1);
+ }
+ x509_crt_size = 1;
+ } else
+#endif /* ENABLE_PKCS11 */
+ {
+
+ ret = gnutls_load_file(x509_certfile, &data);
+ if (ret < 0) {
+ fprintf(stderr,
+ "*** Error loading cert file.\n");
+ exit(1);
+ }
+
+ crt_num = MAX_CRT;
+ ret =
+ gnutls_x509_crt_list_import(crt_list, &crt_num,
+ &data, x509ctype,
+ GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);
+ if (ret < 0) {
+ if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER) {
+ fprintf(stderr,
+ "*** Error loading cert file: Too many certs %d\n",
+ crt_num);
+
+ } else {
+ fprintf(stderr,
+ "*** Error loading cert file: %s\n",
+ gnutls_strerror(ret));
+ }
+ exit(1);
+ }
+ x509_crt_size = ret;
+ }
+
+ for (i = 0; i < x509_crt_size; i++) {
+ ret =
+ gnutls_pcert_import_x509(&x509_crt[i],
+ crt_list[i], 0);
+ if (ret < 0) {
+ fprintf(stderr,
+ "*** Error importing crt to pcert: %s\n",
+ gnutls_strerror(ret));
+ exit(1);
+ }
+ gnutls_x509_crt_deinit(crt_list[i]);
+ }
+
+ gnutls_free(data.data);
+
+ load_priv_key(&x509_key, x509_keyfile);
+
+ log_msg(stdout,
+ "Processed %d client X.509 certificates...\n",
+ x509_crt_size);
+ }
+}
+
+/* Load the raw public key and corresponding private key.
+ */
+static void load_rawpk_keys(void)
+{
+ int ret;
+ gnutls_datum_t data = { NULL, 0 };
+
+ if (rawpk_file != NULL && rawpk_keyfile != NULL) {
+ // First we load the raw public key
+ ret = gnutls_load_file(rawpk_file, &data);
+ if (ret < 0) {
+ fprintf(stderr,
+ "*** Error loading cert file.\n");
+ exit(1);
+ }
+
+ ret = gnutls_pcert_import_rawpk_raw(&rawpk, &data, x509ctype, 0, 0);
+ if (ret < 0) {
+ fprintf(stderr,
+ "*** Error importing rawpk to pcert: %s\n",
+ gnutls_strerror(ret));
+ exit(1);
+ }
+
+ gnutls_free(data.data);
+
+ // Secondly, we load the private key corresponding to the raw pk
+ load_priv_key(&rawpk_key, rawpk_keyfile);
+
+ log_msg(stdout,
+ "Processed %d client raw public key pair...\n", 1);
+ }
+}
+
+#define IS_NEWLINE(x) ((x[0] == '\n') || (x[0] == '\r'))
+static int read_yesno(const char *input_str)
+{
+ char input[128];
+
+ fputs(input_str, stderr);
+ if (fgets(input, sizeof(input), stdin) == NULL)
+ return 0;
+
+ if (IS_NEWLINE(input))
+ return 0;
+
+ if (input[0] == 'y' || input[0] == 'Y')
+ return 1;
+
+ return 0;
+}
+
+static void try_save_cert(gnutls_session_t session)
+{
+ const gnutls_datum_t *cert_list;
+ unsigned int cert_list_size = 0;
+ int ret;
+ unsigned i;
+ gnutls_datum_t t;
+ FILE *fp;
+
+ cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
+ if (cert_list_size == 0) {
+ fprintf(stderr, "no certificates sent by server!\n");
+ exit(1);
+ }
+
+ fp = fopen(OPT_ARG(SAVE_CERT), "w");
+ if (fp == NULL) {
+ fprintf(stderr, "could not open %s\n", OPT_ARG(SAVE_CERT));
+ exit(1);
+ }
+
+ for (i=0;i<cert_list_size;i++) {
+ ret = gnutls_pem_base64_encode_alloc("CERTIFICATE", &cert_list[i], &t);
+ if (ret < 0) {
+ fprintf(stderr, "error[%d]: %s\n", __LINE__,
+ gnutls_strerror(ret));
+ exit(1);
+ }
+
+ fwrite(t.data, t.size, 1, fp);
+ gnutls_free(t.data);
+ }
+ fclose(fp);
+
+ return;
+}
+
+static void try_save_ocsp_status(gnutls_session_t session)
+{
+ unsigned int cert_num = 0;
+ gnutls_certificate_get_peers(session, &cert_num);
+ if (cert_num == 0) {
+ fprintf(stderr, "no certificates sent by server, so can't get OCSP status!\n");
+ return;
+ }
+
+ const char *path;
+ gnutls_x509_crt_fmt_t type;
+ unsigned int max_out;
+
+ /* This function is called if exactly one of SAVE_OCSP and
+ * SAVE_OCSP_MULTI is set. */
+ if (HAVE_OPT(SAVE_OCSP))
+ {
+ path = OPT_ARG(SAVE_OCSP);
+ type = GNUTLS_X509_FMT_DER;
+ max_out = 1;
+ } else {
+ path = OPT_ARG(SAVE_OCSP_MULTI);
+ type = GNUTLS_X509_FMT_PEM;
+ max_out = cert_num;
+ }
+
+ FILE *fp = fopen(path, "w");
+ if (fp == NULL) {
+ fprintf(stderr, "could not open %s for writing\n", path);
+ exit(1);
+ }
+
+ for (unsigned int i = 0; i < max_out; i++) {
+ gnutls_datum_t oresp;
+ int ret = gnutls_ocsp_status_request_get2(session, i, &oresp);
+ if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
+ fprintf(stderr, "no OCSP response for certificate %u\n", i);
+ continue;
+ } else if (ret < 0) {
+ fprintf(stderr, "error getting OCSP response %u: %s\n",
+ i, gnutls_strerror(ret));
+ exit(1);
+ }
+
+ if (type == GNUTLS_X509_FMT_DER) {
+ /* on success the return value is equal to the
+ * number of items (third parameter) */
+ if (fwrite(oresp.data, oresp.size, 1, fp) != 1) {
+ fprintf(stderr, "writing to %s failed\n", path);
+ exit(1);
+ }
+ continue;
+ }
+
+ gnutls_datum_t t;
+ ret = gnutls_pem_base64_encode_alloc("OCSP RESPONSE",
+ &oresp, &t);
+ if (ret < 0) {
+ fprintf(stderr, "error allocating PEM OCSP response: %s\n",
+ gnutls_strerror(ret));
+ exit(1);
+ }
+
+ /* on success the return value is equal to the number
+ * of items (third parameter) */
+ if (fwrite(t.data, t.size, 1, fp) != 1) {
+ fprintf(stderr, "writing to %s failed\n", path);
+ exit(1);
+ }
+ gnutls_free(t.data);
+ }
+ if (fclose(fp) != 0) {
+ perror("failed to close OCSP save file");
+ }
+
+ return;
+}
+
+static int cert_verify_callback(gnutls_session_t session)
+{
+ int rc;
+ unsigned int status = 0;
+ int ssh = ENABLED_OPT(TOFU);
+ int strictssh = ENABLED_OPT(STRICT_TOFU);
+ int dane = ENABLED_OPT(DANE);
+ int ca_verify = ENABLED_OPT(CA_VERIFICATION);
+ const char *txt_service;
+ const char *host;
+
+ /* On an session with TOFU the PKI/DANE verification
+ * become advisory.
+ */
+
+ if (strictssh) {
+ ssh = strictssh;
+ }
+
+ if (HAVE_OPT(VERIFY_HOSTNAME)) {
+ host = OPT_ARG(VERIFY_HOSTNAME);
+ canonicalize_host((char *) host, NULL, 0);
+ } else
+ host = hostname;
+
+ /* Save certificate and OCSP response */
+ if (HAVE_OPT(SAVE_CERT)) {
+ try_save_cert(session);
+ }
+
+#ifndef ENABLE_OCSP
+ if (HAVE_OPT(SAVE_OCSP_MULTI) || HAVE_OPT(SAVE_OCSP) || HAVE_OPT(OCSP)) {
+ fprintf(stderr, "OCSP is not supported!\n");
+ }
+#else
+ if (HAVE_OPT(SAVE_OCSP_MULTI) || HAVE_OPT(SAVE_OCSP)) {
+ try_save_ocsp_status(session);
+ }
+#endif
+
+ print_cert_info(session, verbose, print_cert);
+
+ if (ca_verify) {
+ rc = cert_verify(session, host, GNUTLS_KP_TLS_WWW_SERVER);
+ if (rc == 0) {
+ log_msg
+ (stdout, "*** PKI verification of server certificate failed...\n");
+ if (!insecure && !ssh)
+ return -1;
+ }
+#ifdef ENABLE_OCSP
+ else if (ENABLED_OPT(OCSP) && gnutls_ocsp_status_request_is_checked(session, 0) == 0) { /* off-line verification succeeded. Try OCSP */
+ rc = cert_verify_ocsp(session);
+ if (rc == -1) {
+ log_msg
+ (stdout, "*** Verifying (with OCSP) server certificate chain failed...\n");
+ if (!insecure && !ssh)
+ return -1;
+ } else if (rc == 0)
+ log_msg(stdout, "*** OCSP: nothing to check.\n");
+ else
+ log_msg(stdout, "*** OCSP: verified %d certificate(s).\n", rc);
+ }
+#endif
+ }
+
+ if (dane) { /* try DANE auth */
+#ifdef HAVE_DANE
+ int port;
+ unsigned vflags = 0;
+ unsigned int sflags =
+ ENABLED_OPT(LOCAL_DNS) ? 0 :
+ DANE_F_IGNORE_LOCAL_RESOLVER;
+
+ /* if we didn't verify the chain it only makes sense
+ * to check the end certificate using dane. */
+ if (ca_verify == 0)
+ vflags |= DANE_VFLAG_ONLY_CHECK_EE_USAGE;
+
+ port = service_to_port(service, udp?"udp":"tcp");
+ rc = dane_verify_session_crt(NULL, session, host,
+ udp ? "udp" : "tcp", port,
+ sflags, vflags, &status);
+ if (rc < 0) {
+ fprintf(stderr,
+ "*** DANE verification error: %s\n",
+ dane_strerror(rc));
+ if (!insecure && !ssh)
+ return -1;
+ } else {
+ gnutls_datum_t out;
+
+ rc = dane_verification_status_print(status, &out,
+ 0);
+ if (rc < 0) {
+ fprintf(stderr, "*** DANE error: %s\n",
+ dane_strerror(rc));
+ } else {
+ fprintf(stderr, "- DANE: %s\n", out.data);
+ gnutls_free(out.data);
+ }
+
+ if (status != 0 && !insecure && !ssh)
+ return -1;
+ }
+#else
+ fprintf(stderr, "*** DANE error: GnuTLS is not compiled with DANE support.\n");
+ if (!insecure && !ssh)
+ return -1;
+#endif
+ }
+
+ if (ssh) { /* try ssh auth */
+ unsigned int list_size;
+ const gnutls_datum_t *cert;
+
+ cert = gnutls_certificate_get_peers(session, &list_size);
+ if (cert == NULL) {
+ fprintf(stderr,
+ "Cannot obtain peer's certificate!\n");
+ return -1;
+ }
+
+ txt_service = port_to_service(service, udp?"udp":"tcp");
+
+ rc = gnutls_verify_stored_pubkey(NULL, NULL, host,
+ txt_service,
+ GNUTLS_CRT_X509, cert, 0);
+ if (rc == GNUTLS_E_NO_CERTIFICATE_FOUND) {
+ fprintf(stderr,
+ "Host %s (%s) has never been contacted before.\n",
+ host, txt_service);
+ if (status == 0)
+ fprintf(stderr,
+ "Its certificate is valid for %s.\n",
+ host);
+
+ if (strictssh)
+ return -1;
+
+ rc = read_yesno
+ ("Are you sure you want to trust it? (y/N): ");
+ if (rc == 0)
+ return -1;
+ } else if (rc == GNUTLS_E_CERTIFICATE_KEY_MISMATCH) {
+ fprintf(stderr,
+ "Warning: host %s is known and it is associated with a different key.\n",
+ host);
+ fprintf(stderr,
+ "It might be that the server has multiple keys, or an attacker replaced the key to eavesdrop this connection .\n");
+ if (status == 0)
+ fprintf(stderr,
+ "Its certificate is valid for %s.\n",
+ host);
+
+ if (strictssh)
+ return -1;
+
+ rc = read_yesno
+ ("Do you trust the received key? (y/N): ");
+ if (rc == 0)
+ return -1;
+ } else if (rc < 0) {
+ fprintf(stderr,
+ "gnutls_verify_stored_pubkey: %s\n",
+ gnutls_strerror(rc));
+ return -1;
+ }
+
+ if (rc != 0) {
+ rc = gnutls_store_pubkey(NULL, NULL, host,
+ txt_service,
+ GNUTLS_CRT_X509, cert, 0,
+ 0);
+ if (rc < 0)
+ fprintf(stderr,
+ "Could not store key: %s\n",
+ gnutls_strerror(rc));
+ }
+ }
+ return 0;
+}
+
+/* This callback should be associated with a session by calling
+ * gnutls_certificate_client_set_retrieve_function( session, cert_callback),
+ * before a handshake.
+ */
+
+static int
+cert_callback(gnutls_session_t session,
+ const gnutls_datum_t * req_ca_rdn, int nreqs,
+ const gnutls_pk_algorithm_t * sign_algos,
+ int sign_algos_length, gnutls_pcert_st ** pcert,
+ unsigned int *pcert_length, gnutls_privkey_t * pkey)
+{
+ char issuer_dn[256];
+ int i, ret, cert_type;
+ size_t len;
+
+ if (verbose) {
+ /* Print the server's trusted CAs
+ */
+ if (nreqs > 0)
+ log_msg(stdout, "- Server's trusted authorities:\n");
+ else
+ log_msg
+ (stdout, "- Server did not send us any trusted authorities names.\n");
+
+ /* print the names (if any) */
+ for (i = 0; i < nreqs; i++) {
+ len = sizeof(issuer_dn);
+ ret =
+ gnutls_x509_rdn_get(&req_ca_rdn[i], issuer_dn,
+ &len);
+ if (ret >= 0) {
+ log_msg(stdout, " [%d]: ", i);
+ log_msg(stdout, "%s\n", issuer_dn);
+ }
+ }
+ }
+
+ /* Select a certificate and return it.
+ * The certificate must be of any of the "sign algorithms"
+ * supported by the server.
+ */
+
+ cert_type = gnutls_certificate_type_get2(session, GNUTLS_CTYPE_CLIENT);
+
+ *pcert_length = 0;
+
+ switch (cert_type) {
+ case GNUTLS_CRT_X509:
+ if (x509_crt_size > 0) {
+ if (x509_key != NULL) {
+ *pkey = x509_key;
+ } else {
+ log_msg
+ (stdout, "- Could not find a suitable key to send to server\n");
+ return -1;
+ }
+
+ *pcert_length = x509_crt_size;
+ *pcert = x509_crt;
+ }
+ break;
+ case GNUTLS_CRT_RAWPK:
+ if (rawpk_key == NULL || rawpk.type != GNUTLS_CRT_RAWPK) {
+ log_msg
+ (stdout, "- Could not find a suitable key to send to server\n");
+ return -1;
+ }
+
+ *pkey = rawpk_key;
+ *pcert = &rawpk;
+ *pcert_length = 1;
+ break;
+ default:
+ log_msg(stdout, "- Could not retrieve unsupported certificate type %s.\n",
+ gnutls_certificate_type_get_name(cert_type));
+ return -1;
+ }
+
+ log_msg(stdout, "- Successfully sent %u certificate(s) to server.\n",
+ *pcert_length);
+ return 0;
+
+}
+
+/* initializes a gnutls_session_t with some defaults.
+ */
+gnutls_session_t init_tls_session(const char *host)
+{
+ const char *err;
+ int ret;
+ unsigned i;
+ gnutls_session_t session;
+
+ if (udp) {
+ gnutls_init(&session, GNUTLS_DATAGRAM | init_flags);
+ if (mtu)
+ gnutls_dtls_set_mtu(session, mtu);
+ } else
+ gnutls_init(&session, init_flags);
+
+ if (priorities == NULL) {
+ ret = gnutls_set_default_priority(session);
+ if (ret < 0) {
+ fprintf(stderr, "Error in setting priorities: %s\n",
+ gnutls_strerror(ret));
+ exit(1);
+ }
+ } else {
+ ret = gnutls_priority_set_direct(session, priorities, &err);
+ if (ret < 0) {
+ if (ret == GNUTLS_E_INVALID_REQUEST)
+ fprintf(stderr, "Syntax error at: %s\n", err);
+ else
+ fprintf(stderr, "Error in priorities: %s\n",
+ gnutls_strerror(ret));
+ exit(1);
+ }
+ }
+
+ /* allow the use of private ciphersuites.
+ */
+ if (disable_extensions == 0 && disable_sni == 0) {
+ if (HAVE_OPT(SNI_HOSTNAME)) {
+ const char *sni_host = OPT_ARG(SNI_HOSTNAME);
+
+ canonicalize_host((char *) sni_host, NULL, 0);
+ gnutls_server_name_set(session, GNUTLS_NAME_DNS, sni_host, strlen(sni_host));
+ } else if (host != NULL && is_ip(host) == 0)
+ gnutls_server_name_set(session, GNUTLS_NAME_DNS,
+ host, strlen(host));
+ }
+
+ if (HAVE_OPT(DH_BITS)) {
+#if defined(ENABLE_DHE) || defined(ENABLE_ANON)
+ gnutls_dh_set_prime_bits(session, OPT_VALUE_DH_BITS);
+#else
+ fprintf(stderr, "Setting DH parameters is not supported\n");
+ exit(1);
+#endif
+ }
+
+
+ if (HAVE_OPT(ALPN)) {
+#ifndef ENABLE_ALPN
+ fprintf(stderr, "ALPN is not supported\n");
+ exit(1);
+#else
+ unsigned proto_n = STACKCT_OPT(ALPN);
+ char **protos = (void *) STACKLST_OPT(ALPN);
+
+ if (proto_n > 1024) {
+ fprintf(stderr, "Number of ALPN protocols too large (%d)\n",
+ proto_n);
+ exit(1);
+ }
+
+ gnutls_datum_t p[1024];
+ for (i = 0; i < proto_n; i++) {
+ p[i].data = (void *) protos[i];
+ p[i].size = strlen(protos[i]);
+ }
+ gnutls_alpn_set_protocols(session, p, proto_n, 0);
+#endif
+ }
+
+ gnutls_credentials_set(session, GNUTLS_CRD_ANON, anon_cred);
+ if (srp_cred)
+ gnutls_credentials_set(session, GNUTLS_CRD_SRP, srp_cred);
+ if (psk_cred)
+ gnutls_credentials_set(session, GNUTLS_CRD_PSK, psk_cred);
+ gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, xcred);
+
+ gnutls_certificate_set_retrieve_function2(xcred, cert_callback);
+ gnutls_certificate_set_verify_function(xcred,
+ cert_verify_callback);
+
+ /* use the max record size extension */
+ if (record_max_size > 0 && disable_extensions == 0) {
+ if (gnutls_record_set_max_size(session, record_max_size) <
+ 0) {
+ fprintf(stderr,
+ "Cannot set the maximum record size to %d.\n",
+ record_max_size);
+ fprintf(stderr,
+ "Possible values: 512, 1024, 2048, 4096.\n");
+ exit(1);
+ }
+ }
+
+ if (HAVE_OPT(COMPRESS_CERT) && disable_extensions == 0) {
+ ret = compress_cert_set_methods(session,
+ OPTS_ARRAY(COMPRESS_CERT),
+ OPTS_COUNT(COMPRESS_CERT));
+ if (ret < 0)
+ exit(1);
+ }
+
+ if (HAVE_OPT(HEARTBEAT))
+ gnutls_heartbeat_enable(session,
+ GNUTLS_HB_PEER_ALLOWED_TO_SEND);
+
+#ifdef ENABLE_DTLS_SRTP
+ if (HAVE_OPT(SRTP_PROFILES)) {
+ ret =
+ gnutls_srtp_set_profile_direct(session,
+ OPT_ARG(SRTP_PROFILES),
+ &err);
+ if (ret == GNUTLS_E_INVALID_REQUEST)
+ fprintf(stderr, "Syntax error at: %s\n", err);
+ else if (ret != 0)
+ fprintf(stderr, "Error in profiles: %s\n",
+ gnutls_strerror(ret));
+ else fprintf(stderr,"DTLS profile set to %s\n",
+ OPT_ARG(SRTP_PROFILES));
+
+ if (ret != 0) exit(1);
+ }
+#endif
+
+
+ return session;
+}
+
+static void cmd_parser(int argc, char **argv);
+
+/* Returns zero if the error code was successfully handled.
+ */
+static int handle_error(socket_st * hd, int err)
+{
+ int alert, ret;
+ const char *err_type, *str;
+
+ if (err >= 0 || err == GNUTLS_E_AGAIN
+ || err == GNUTLS_E_INTERRUPTED)
+ return 0;
+
+ if (gnutls_error_is_fatal(err) == 0) {
+ ret = 0;
+ err_type = "Non fatal";
+ } else {
+ ret = err;
+ err_type = "Fatal";
+ }
+
+ str = gnutls_strerror(err);
+ if (str == NULL)
+ str = str_unknown;
+ fprintf(stderr, "*** %s error: %s\n", err_type, str);
+
+ if (err == GNUTLS_E_WARNING_ALERT_RECEIVED
+ || err == GNUTLS_E_FATAL_ALERT_RECEIVED) {
+ alert = gnutls_alert_get(hd->session);
+ str = gnutls_alert_get_name(alert);
+ if (str == NULL)
+ str = str_unknown;
+ log_msg(stdout, "*** Received alert [%d]: %s\n", alert, str);
+ }
+
+ check_server_cmd(hd, err);
+
+ return ret;
+}
+
+int starttls_alarmed = 0;
+
+#ifndef _WIN32
+static void starttls_alarm(int signum)
+{
+ starttls_alarmed = 1;
+}
+#endif
+
+static void tls_log_func(int level, const char *str)
+{
+ fprintf(stderr, "|<%d>| %s", level, str);
+}
+
+#define IN_NONE 0
+#define IN_KEYBOARD 1
+#define IN_NET 2
+#define IN_TERM 3
+/* returns IN_KEYBOARD for keyboard input and IN_NET for network input
+ */
+static int check_net_or_keyboard_input(socket_st * hd, unsigned user_term)
+{
+ int maxfd;
+ fd_set rset;
+ int err;
+ struct timeval tv;
+
+ do {
+ FD_ZERO(&rset);
+ FD_SET(hd->fd, &rset);
+
+#ifndef _WIN32
+ if (!user_term) {
+ FD_SET(fileno(stdin), &rset);
+ maxfd = MAX(fileno(stdin), hd->fd);
+ } else {
+ maxfd = hd->fd;
+ }
+#else
+ maxfd = hd->fd;
+#endif
+
+ tv.tv_sec = 2;
+ tv.tv_usec = 0;
+
+ if (hd->secure == 1)
+ if (gnutls_record_check_pending(hd->session))
+ return IN_NET;
+
+ err = select(maxfd + 1, &rset, NULL, NULL, &tv);
+ if (err < 0)
+ continue;
+
+ if (FD_ISSET(hd->fd, &rset))
+ return IN_NET;
+
+#ifdef _WIN32
+ {
+ int state;
+ state =
+ WaitForSingleObject(GetStdHandle
+ (STD_INPUT_HANDLE), 200);
+
+ if (state == WAIT_OBJECT_0)
+ return IN_KEYBOARD;
+ }
+#else
+ if (!user_term && FD_ISSET(fileno(stdin), &rset))
+ return IN_KEYBOARD;
+#endif
+ if (err == 0 && user_term)
+ return IN_TERM;
+ }
+ while (err == 0);
+
+ return IN_NONE;
+}
+
+static int try_rehandshake(socket_st * hd)
+{
+ int ret;
+
+ ret = do_handshake(hd);
+ if (ret < 0) {
+ fprintf(stderr, "*** ReHandshake has failed\n");
+ gnutls_perror(ret);
+ return ret;
+ } else {
+ log_msg(stdout, "- ReHandshake was completed\n");
+ return 0;
+ }
+}
+
+static int try_rekey(socket_st * hd, unsigned peer)
+{
+ int ret;
+
+ do {
+ ret = gnutls_session_key_update(hd->session, peer?GNUTLS_KU_PEER:0);
+ } while(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED);
+
+ if (ret < 0) {
+ fprintf(stderr, "*** Rekey has failed: %s\n", gnutls_strerror(ret));
+ return ret;
+ } else {
+ log_msg(stdout, "- Rekey was completed\n");
+ return 0;
+ }
+}
+
+static int try_resume(socket_st * hd)
+{
+ int ret, socket_flags = SOCKET_FLAG_DONT_PRINT_ERRORS;
+ gnutls_datum_t rdata = {NULL, 0};
+ gnutls_datum_t edata = {NULL, 0};
+
+ if (gnutls_session_is_resumed(hd->session) == 0) {
+ do {
+ /* not resumed - obtain the session data */
+ ret = gnutls_session_get_data2(hd->session, &rdata);
+ if (ret < 0) {
+ rdata.data = NULL;
+ }
+
+ if ((gnutls_protocol_get_version(hd->session) != GNUTLS_TLS1_3) ||
+ ((gnutls_session_get_flags(hd->session) &
+ GNUTLS_SFLAGS_SESSION_TICKET))) {
+ break;
+ }
+ } while (waitresumption);
+ } else {
+ /* resumed - try to reuse the previous session data */
+ rdata.data = hd->rdata.data;
+ rdata.size = hd->rdata.size;
+ hd->rdata.data = NULL;
+ }
+
+ log_msg(stdout, "- Disconnecting\n");
+ socket_bye(hd, 1);
+
+ canonicalize_host(hostname, service, sizeof(service));
+
+ log_msg
+ (stdout, "\n\n- Connecting again- trying to resume previous session\n");
+ if (HAVE_OPT(STARTTLS_PROTO))
+ socket_flags |= SOCKET_FLAG_STARTTLS;
+ else if (fastopen)
+ socket_flags |= SOCKET_FLAG_FASTOPEN;
+
+ if (udp)
+ socket_flags |= SOCKET_FLAG_UDP;
+
+ if (HAVE_OPT(EARLYDATA)) {
+ FILE *fp;
+ size_t size;
+
+ fp = fopen(OPT_ARG(EARLYDATA), "r");
+ if (fp == NULL) {
+ fprintf(stderr, "could not open %s\n", OPT_ARG(EARLYDATA));
+ exit(1);
+ }
+ edata.data = (void *) fread_file(fp, 0, &size);
+ edata.size = size;
+ fclose(fp);
+ }
+
+ socket_open3(hd, hostname, service, OPT_ARG(STARTTLS_PROTO),
+ socket_flags, CONNECT_MSG, &rdata, &edata);
+
+ log_msg(stdout, "- Resume Handshake was completed\n");
+ if (gnutls_session_is_resumed(hd->session) != 0)
+ log_msg(stdout, "*** This is a resumed session\n");
+
+ return 0;
+}
+
+static
+bool parse_for_inline_commands_in_buffer(char *buffer, size_t bytes,
+ inline_cmds_st * inline_cmds)
+{
+ ssize_t local_bytes, match_bytes, prev_bytes_copied, ii;
+ unsigned jj;
+ char *local_buffer_ptr, *ptr;
+ char inline_command_string[MAX_INLINE_COMMAND_BYTES];
+ ssize_t l;
+
+ inline_cmds->bytes_to_flush = 0;
+ inline_cmds->cmd_found = INLINE_COMMAND_NONE;
+
+ if (inline_cmds->bytes_copied) {
+ local_buffer_ptr =
+ &inline_cmds->inline_cmd_buffer[inline_cmds->
+ bytes_copied];
+
+ local_bytes =
+ ((inline_cmds->bytes_copied + bytes) <=
+ MAX_INLINE_COMMAND_BYTES) ? (ssize_t) bytes
+ : (MAX_INLINE_COMMAND_BYTES -
+ inline_cmds->bytes_copied);
+
+ memcpy(local_buffer_ptr, buffer, local_bytes);
+ prev_bytes_copied = inline_cmds->bytes_copied;
+ inline_cmds->new_buffer_ptr = buffer + local_bytes;
+ inline_cmds->bytes_copied += local_bytes;
+ local_buffer_ptr = inline_cmds->inline_cmd_buffer;
+ local_bytes = inline_cmds->bytes_copied;
+ } else {
+ prev_bytes_copied = 0;
+ local_buffer_ptr = buffer;
+ local_bytes = bytes;
+ inline_cmds->new_buffer_ptr = buffer + bytes;
+ }
+
+ assert(local_buffer_ptr != NULL);
+
+ inline_cmds->current_ptr = local_buffer_ptr;
+
+ if (local_buffer_ptr[0] == inline_commands_prefix[0]
+ && inline_cmds->lf_found) {
+ for (jj = 0; jj < NUM_INLINE_COMMANDS; jj++) {
+ if (inline_commands_prefix[0] != '^') { /* refer inline_cmds.h for usage of ^ */
+ strcpy(inline_command_string,
+ inline_commands_def[jj].string);
+ inline_command_string[strlen
+ (inline_commands_def
+ [jj].string)] =
+ '\0';
+ inline_command_string[0] =
+ inline_commands_prefix[0];
+ /* Inline commands are delimited by the inline_commands_prefix[0] (default is ^).
+ The inline_commands_def[].string includes a trailing LF */
+ inline_command_string[strlen
+ (inline_commands_def
+ [jj].string) - 2] =
+ inline_commands_prefix[0];
+ ptr = inline_command_string;
+ } else
+ ptr = inline_commands_def[jj].string;
+
+ l = strlen(ptr);
+ match_bytes = (local_bytes <= l) ? local_bytes : l;
+ if (strncmp(ptr, local_buffer_ptr, match_bytes) ==
+ 0) {
+ if (match_bytes == (ssize_t) strlen(ptr)) {
+ inline_cmds->new_buffer_ptr =
+ buffer + match_bytes -
+ prev_bytes_copied;
+ inline_cmds->cmd_found =
+ inline_commands_def[jj].
+ command;
+ inline_cmds->bytes_copied = 0; /* reset it */
+ } else {
+ /* partial command */
+ memcpy(&inline_cmds->
+ inline_cmd_buffer
+ [inline_cmds->bytes_copied],
+ buffer, bytes);
+ inline_cmds->bytes_copied += bytes;
+ }
+ return true;
+ }
+ /* else - if not a match, do nothing here */
+ } /* for */
+ }
+
+ for (ii = prev_bytes_copied; ii < local_bytes; ii++) {
+ if (ii && local_buffer_ptr[ii] == inline_commands_prefix[0]
+ && inline_cmds->lf_found) {
+ /* possible inline command. First, let's flush bytes up to ^ */
+ inline_cmds->new_buffer_ptr =
+ buffer + ii - prev_bytes_copied;
+ inline_cmds->bytes_to_flush = ii;
+ inline_cmds->lf_found = true;
+
+ /* bytes to flush starts at inline_cmds->current_ptr */
+ return true;
+ } else if (local_buffer_ptr[ii] == '\n') {
+ inline_cmds->lf_found = true;
+ } else {
+ inline_cmds->lf_found = false;
+ }
+ } /* for */
+
+ inline_cmds->bytes_copied = 0; /* reset it */
+ return false; /* not an inline command */
+}
+
+static
+int run_inline_command(inline_cmds_st * cmd, socket_st * hd)
+{
+ switch (cmd->cmd_found) {
+ case INLINE_COMMAND_RESUME:
+ return try_resume(hd);
+ case INLINE_COMMAND_REKEY_LOCAL:
+ return try_rekey(hd, 0);
+ case INLINE_COMMAND_REKEY_BOTH:
+ return try_rekey(hd, 1);
+ case INLINE_COMMAND_RENEGOTIATE:
+ return try_rehandshake(hd);
+ default:
+ return -1;
+ }
+}
+
+static
+int do_inline_command_processing(char *buffer_ptr, size_t curr_bytes,
+ socket_st * hd,
+ inline_cmds_st * inline_cmds)
+{
+ int skip_bytes, bytes;
+ bool inline_cmd_start_found;
+
+ bytes = curr_bytes;
+
+ continue_inline_processing:
+ /* parse_for_inline_commands_in_buffer hunts for start of an inline command
+ * sequence. The function maintains state information in inline_cmds.
+ */
+ inline_cmd_start_found =
+ parse_for_inline_commands_in_buffer(buffer_ptr, bytes,
+ inline_cmds);
+ if (!inline_cmd_start_found)
+ return bytes;
+
+ /* inline_cmd_start_found is set */
+
+ if (inline_cmds->bytes_to_flush) {
+ /* start of an inline command sequence found, but is not
+ * at the beginning of buffer. So, we flush all preceding bytes.
+ */
+ return inline_cmds->bytes_to_flush;
+ } else if (inline_cmds->cmd_found == INLINE_COMMAND_NONE) {
+ /* partial command found */
+ return 0;
+ } else {
+ /* complete inline command found and is at the start */
+ if (run_inline_command(inline_cmds, hd))
+ return -1;
+
+ inline_cmds->cmd_found = INLINE_COMMAND_NONE;
+ skip_bytes = inline_cmds->new_buffer_ptr - buffer_ptr;
+
+ if (skip_bytes >= bytes)
+ return 0;
+ else {
+ buffer_ptr = inline_cmds->new_buffer_ptr;
+ bytes -= skip_bytes;
+ goto continue_inline_processing;
+ }
+ }
+}
+
+static void
+print_other_info(gnutls_session_t session)
+{
+#ifdef ENABLE_OCSP
+ int ret;
+ unsigned i;
+ unsigned int list_size;
+ gnutls_datum_t oresp;
+ const gnutls_datum_t * peers;
+
+ peers = gnutls_certificate_get_peers(session, &list_size);
+
+ if (!ENABLED_OPT(VERBOSE) || peers == NULL)
+ return;
+
+ for (i = 0; i < list_size; i++) {
+ ret = gnutls_ocsp_status_request_get2(session, i, &oresp);
+ if (ret < 0) {
+ oresp.data = NULL;
+ oresp.size = 0;
+ continue;
+ }
+
+ gnutls_ocsp_resp_t r;
+ gnutls_datum_t p;
+ unsigned flag;
+
+ ret = gnutls_ocsp_resp_init(&r);
+ if (ret < 0) {
+ fprintf(stderr, "ocsp_resp_init: %s\n",
+ gnutls_strerror(ret));
+ return;
+ }
+
+ ret = gnutls_ocsp_resp_import(r, &oresp);
+ if (ret < 0) {
+ fprintf(stderr, "importing response: %s\n",
+ gnutls_strerror(ret));
+ return;
+ }
+
+ if (print_cert != 0)
+ flag = GNUTLS_OCSP_PRINT_FULL;
+ else
+ flag = GNUTLS_OCSP_PRINT_COMPACT;
+ ret =
+ gnutls_ocsp_resp_print(r, flag, &p);
+ gnutls_ocsp_resp_deinit(r);
+ if (ret>=0) {
+ log_msg(stdout, "%s", (char*) p.data);
+ gnutls_free(p.data);
+ }
+ }
+#endif
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+ int ii, inp;
+ char buffer[MAX_BUF + 1];
+ int user_term = 0, retval = 0;
+ socket_st hd;
+ ssize_t bytes, keyboard_bytes;
+ char *keyboard_buffer_ptr;
+ inline_cmds_st inline_cmds;
+ int socket_flags = SOCKET_FLAG_DONT_PRINT_ERRORS;
+ FILE *server_fp = NULL;
+ FILE *client_fp = NULL;
+ FILE *logfile = NULL;
+#ifndef _WIN32
+ struct sigaction new_action;
+#endif
+
+ cmd_parser(argc, argv);
+
+ if (HAVE_OPT(LOGFILE)) {
+ logfile = fopen(OPT_ARG(LOGFILE), "w+");
+ if (!logfile) {
+ log_msg(stderr, "Unable to open '%s'!\n", OPT_ARG(LOGFILE));
+ exit(1);
+ }
+ log_set(logfile);
+ }
+
+ gnutls_global_set_log_function(tls_log_func);
+ gnutls_global_set_log_level(OPT_VALUE_DEBUG);
+
+ if ((ret = gnutls_global_init()) < 0) {
+ fprintf(stderr, "global_init: %s\n", gnutls_strerror(ret));
+ exit(1);
+ }
+
+ if (hostname == NULL) {
+ fprintf(stderr, "No hostname given\n");
+ exit(1);
+ }
+
+ sockets_init();
+
+ init_global_tls_stuff();
+
+ canonicalize_host(hostname, service, sizeof(service));
+
+ if (udp)
+ socket_flags |= SOCKET_FLAG_UDP;
+ if (fastopen)
+ socket_flags |= SOCKET_FLAG_FASTOPEN;
+ if (verbose)
+ socket_flags |= SOCKET_FLAG_VERBOSE;
+ if (starttls)
+ socket_flags |= SOCKET_FLAG_RAW;
+ else if (HAVE_OPT(STARTTLS_PROTO))
+ socket_flags |= SOCKET_FLAG_STARTTLS;
+
+ if (HAVE_OPT(SAVE_SERVER_TRACE)) {
+ server_fp = fopen(OPT_ARG(SAVE_SERVER_TRACE), "wb");
+ }
+ if (HAVE_OPT(SAVE_CLIENT_TRACE)) {
+ client_fp = fopen(OPT_ARG(SAVE_CLIENT_TRACE), "wb");
+ }
+
+ socket_open2(&hd, hostname, service, OPT_ARG(STARTTLS_PROTO),
+ socket_flags, CONNECT_MSG, NULL, NULL,
+ server_fp, client_fp);
+
+ hd.verbose = verbose;
+
+ if (hd.secure) {
+ log_msg(stdout, "- Handshake was completed\n");
+
+ if (resume != 0)
+ if (try_resume(&hd)) {
+ retval = 1;
+ goto cleanup;
+ }
+
+ print_other_info(hd.session);
+ }
+
+ /* Warning! Do not touch this text string, it is used by external
+ programs to search for when gnutls-cli has reached this point. */
+ log_msg(stdout, "\n- Simple Client Mode:\n\n");
+
+ if (rehandshake)
+ if (try_rehandshake(&hd)) {
+ retval = 1;
+ goto cleanup;
+ }
+
+#ifndef _WIN32
+ new_action.sa_handler = starttls_alarm;
+ sigemptyset(&new_action.sa_mask);
+ new_action.sa_flags = 0;
+
+ sigaction(SIGALRM, &new_action, NULL);
+#endif
+
+ fflush(stdout);
+ fflush(stderr);
+
+ /* do not buffer */
+#ifndef _WIN32
+ setbuf(stdin, NULL);
+#endif
+ setbuf(stdout, NULL);
+ setbuf(stderr, NULL);
+
+ memset(&inline_cmds, 0, sizeof(inline_cmds_st));
+ if (inline_commands) {
+ inline_cmds.lf_found = true; /* initially, at start of line */
+ }
+
+ for (;;) {
+ if (starttls_alarmed && !hd.secure) {
+ /* Warning! Do not touch this text string, it is used by
+ external programs to search for when gnutls-cli has
+ reached this point. */
+ fprintf(stderr, "*** Starting TLS handshake\n");
+ ret = do_handshake(&hd);
+ if (ret < 0) {
+ fprintf(stderr,
+ "*** Handshake has failed\n");
+ retval = 1;
+ break;
+ }
+ }
+
+ inp = check_net_or_keyboard_input(&hd, user_term);
+ if (inp == IN_TERM)
+ break;
+
+ if (inp == IN_NET) {
+ memset(buffer, 0, MAX_BUF + 1);
+ ret = socket_recv(&hd, buffer, MAX_BUF);
+
+ if (ret == 0 || (ret == GNUTLS_E_PREMATURE_TERMINATION && user_term)) {
+ log_msg
+ (stdout, "- Peer has closed the GnuTLS connection\n");
+ break;
+ } else if (handle_error(&hd, ret) < 0) {
+ fprintf(stderr,
+ "*** Server has terminated the connection abnormally.\n");
+ retval = 1;
+ break;
+ } else if (ret > 0) {
+ if (verbose != 0)
+ log_msg(stdout, "- Received[%d]: ", ret);
+ for (ii = 0; ii < ret; ii++) {
+ fputc(buffer[ii], stdout);
+ }
+ fflush(stdout);
+ }
+ }
+
+ if (inp == IN_KEYBOARD && user_term == 0) {
+ if ((bytes =
+ read(fileno(stdin), buffer,
+ MAX_BUF - 1)) <= 0) {
+ if (hd.secure == 0) {
+ /* Warning! Do not touch this text string, it is
+ used by external programs to search for when
+ gnutls-cli has reached this point. */
+ fprintf(stderr,
+ "*** Starting TLS handshake\n");
+ ret = do_handshake(&hd);
+ clearerr(stdin);
+ if (ret < 0) {
+ fprintf(stderr,
+ "*** Handshake has failed\n");
+ retval = 1;
+ break;
+ }
+ } else {
+ do {
+ ret = gnutls_bye(hd.session, GNUTLS_SHUT_WR);
+ } while (ret == GNUTLS_E_INTERRUPTED ||
+ ret == GNUTLS_E_AGAIN);
+
+ user_term = 1;
+ }
+ continue;
+ }
+ buffer[bytes] = 0;
+
+ if (crlf != 0) {
+ char *b = strchr(buffer, '\n');
+ if (b != NULL) {
+ strcpy(b, "\r\n");
+ bytes++;
+ }
+ }
+
+ keyboard_bytes = bytes;
+ keyboard_buffer_ptr = buffer;
+
+ inline_command_processing:
+
+ if (inline_commands) {
+ keyboard_bytes =
+ do_inline_command_processing
+ (keyboard_buffer_ptr, keyboard_bytes,
+ &hd, &inline_cmds);
+ if (keyboard_bytes == 0)
+ continue;
+ else if (keyboard_bytes < 0) { /* error processing an inline command */
+ retval = 1;
+ break;
+ } else {
+ /* current_ptr could point to either an inline_cmd_buffer
+ * or may point to start or an offset into buffer.
+ */
+ keyboard_buffer_ptr =
+ inline_cmds.current_ptr;
+ }
+ }
+
+ if (ranges
+ && gnutls_record_can_use_length_hiding(hd.
+ session))
+ {
+ gnutls_range_st range;
+ range.low = 0;
+ range.high = MAX_BUF;
+ ret =
+ socket_send_range(&hd,
+ keyboard_buffer_ptr,
+ keyboard_bytes,
+ &range);
+ } else {
+ ret =
+ socket_send(&hd, keyboard_buffer_ptr,
+ keyboard_bytes);
+ }
+
+ if (ret > 0) {
+ if (verbose != 0)
+ log_msg(stdout, "- Sent: %d bytes\n", ret);
+ } else
+ handle_error(&hd, ret);
+
+ if (inline_commands &&
+ inline_cmds.new_buffer_ptr < (buffer + bytes))
+ {
+ keyboard_buffer_ptr =
+ inline_cmds.new_buffer_ptr;
+ keyboard_bytes =
+ (buffer + bytes) - keyboard_buffer_ptr;
+ goto inline_command_processing;
+ }
+ }
+ }
+
+ cleanup:
+ socket_bye(&hd, 0);
+ if (logfile) {
+ fclose(logfile);
+ logfile = NULL;
+ }
+
+#ifdef ENABLE_SRP
+ if (srp_cred)
+ gnutls_srp_free_client_credentials(srp_cred);
+#endif
+#ifdef ENABLE_PSK
+ if (psk_cred)
+ gnutls_psk_free_client_credentials(psk_cred);
+#endif
+
+ gnutls_certificate_free_credentials(xcred);
+
+#ifdef ENABLE_ANON
+ gnutls_anon_free_client_credentials(anon_cred);
+#endif
+
+ gnutls_global_deinit();
+
+ return retval;
+}
+
+static
+void print_priority_list(void)
+{
+ unsigned int idx;
+ const char *str;
+ unsigned int lineb = 0;
+
+ log_msg(stdout, "Priority strings in GnuTLS %s:\n", gnutls_check_version(NULL));
+
+ fputs("\t", stdout);
+ for (idx=0;;idx++) {
+ str = gnutls_priority_string_list(idx, GNUTLS_PRIORITY_LIST_INIT_KEYWORDS);
+ if (str == NULL)
+ break;
+ lineb += log_msg(stdout, "%s ", str);
+ if (lineb > 64) {
+ lineb = 0;
+ log_msg(stdout, "\n\t");
+ }
+ }
+
+ log_msg(stdout, "\n\nSpecial strings:\n");
+ lineb = 0;
+ fputs("\t", stdout);
+ for (idx=0;;idx++) {
+ str = gnutls_priority_string_list(idx, GNUTLS_PRIORITY_LIST_SPECIAL);
+ if (str == NULL)
+ break;
+ if (str[0] == 0)
+ continue;
+ lineb += log_msg(stdout, "%%%s ", str);
+ if (lineb > 64) {
+ lineb = 0;
+ log_msg(stdout, "\n\t");
+ }
+ }
+ log_msg(stdout, "\n");
+
+ return;
+}
+
+static void cmd_parser(int argc, char **argv)
+{
+ char *rest = NULL;
+
+ int optct = optionProcess(&gnutls_cliOptions, argc, argv);
+ argc -= optct;
+ argv += optct;
+
+ if (rest == NULL && argc > 0)
+ rest = argv[0];
+
+
+ if (HAVE_OPT(FIPS140_MODE)) {
+ if (gnutls_fips140_mode_enabled() != 0) {
+ fprintf(stderr, "library is in FIPS140-2 mode\n");
+ exit(0);
+ }
+ fprintf(stderr, "library is NOT in FIPS140-2 mode\n");
+ exit(1);
+ }
+
+ if (HAVE_OPT(LIST_CONFIG)) {
+ const gnutls_library_config_st *p;
+
+ for (p = gnutls_get_library_config(); p->name; p++) {
+ log_msg(stdout, "%s: %s\n", p->name, p->value);
+ }
+ log_msg(stdout, "system-config: %s\n", gnutls_get_system_config_file());
+ exit(0);
+ }
+
+ if (HAVE_OPT(BENCHMARK_CIPHERS)) {
+ benchmark_cipher(OPT_VALUE_DEBUG);
+ exit(0);
+ }
+
+ if (HAVE_OPT(BENCHMARK_TLS_CIPHERS)) {
+ benchmark_tls(OPT_VALUE_DEBUG, 1);
+ exit(0);
+ }
+
+ if (HAVE_OPT(BENCHMARK_TLS_KX)) {
+ benchmark_tls(OPT_VALUE_DEBUG, 0);
+ exit(0);
+ }
+
+ if (HAVE_OPT(PRIORITY)) {
+ priorities = OPT_ARG(PRIORITY);
+ }
+ verbose = HAVE_OPT(VERBOSE);
+ if (verbose)
+ print_cert = 1;
+ else
+ print_cert = HAVE_OPT(PRINT_CERT);
+
+ if (HAVE_OPT(LIST)) {
+ print_list(priorities, verbose);
+ exit(0);
+ }
+
+ if (HAVE_OPT(PRIORITY_LIST)) {
+ print_priority_list();
+ exit(0);
+ }
+
+ disable_sni = HAVE_OPT(DISABLE_SNI);
+ disable_extensions = HAVE_OPT(DISABLE_EXTENSIONS);
+ if (disable_extensions)
+ init_flags |= GNUTLS_NO_EXTENSIONS;
+
+ if (HAVE_OPT(SINGLE_KEY_SHARE))
+ init_flags |= GNUTLS_KEY_SHARE_TOP;
+
+ if (HAVE_OPT(POST_HANDSHAKE_AUTH))
+ init_flags |= GNUTLS_POST_HANDSHAKE_AUTH;
+
+ inline_commands = HAVE_OPT(INLINE_COMMANDS);
+ if (HAVE_OPT(INLINE_COMMANDS_PREFIX)) {
+ if (strlen(OPT_ARG(INLINE_COMMANDS_PREFIX)) > 1) {
+ fprintf(stderr,
+ "inline-commands-prefix value is a single US-ASCII character (octets 0 - 127)\n");
+ exit(1);
+ }
+ inline_commands_prefix =
+ (char *) OPT_ARG(INLINE_COMMANDS_PREFIX);
+ if (!isascii(inline_commands_prefix[0])) {
+ fprintf(stderr,
+ "inline-commands-prefix value is a single US-ASCII character (octets 0 - 127)\n");
+ exit(1);
+ }
+ } else
+ inline_commands_prefix = "^";
+
+ starttls = HAVE_OPT(STARTTLS);
+ resume = HAVE_OPT(RESUME);
+ rehandshake = HAVE_OPT(REHANDSHAKE);
+ insecure = HAVE_OPT(INSECURE);
+ ranges = HAVE_OPT(RANGES);
+ waitresumption = HAVE_OPT(WAITRESUMPTION);
+
+ if (insecure || HAVE_OPT(VERIFY_ALLOW_BROKEN)) {
+ global_vflags |= GNUTLS_VERIFY_ALLOW_BROKEN;
+ }
+
+ udp = HAVE_OPT(UDP);
+ mtu = OPT_VALUE_MTU;
+
+ if (HAVE_OPT(PORT)) {
+ snprintf(service, sizeof(service), "%s", OPT_ARG(PORT));
+ } else {
+ if (HAVE_OPT(STARTTLS_PROTO))
+ snprintf(service, sizeof(service), "%s", starttls_proto_to_service(OPT_ARG(STARTTLS_PROTO)));
+ else
+ strcpy(service, "443");
+ }
+
+ record_max_size = OPT_VALUE_RECORDSIZE;
+
+ if (HAVE_OPT(X509FMTDER))
+ x509ctype = GNUTLS_X509_FMT_DER;
+ else
+ x509ctype = GNUTLS_X509_FMT_PEM;
+
+ if (HAVE_OPT(SRPUSERNAME))
+ srp_username = OPT_ARG(SRPUSERNAME);
+
+ if (HAVE_OPT(SRPPASSWD))
+ srp_passwd = OPT_ARG(SRPPASSWD);
+
+ if (HAVE_OPT(X509CAFILE))
+ x509_cafile = OPT_ARG(X509CAFILE);
+
+ if (HAVE_OPT(X509CRLFILE))
+ x509_crlfile = OPT_ARG(X509CRLFILE);
+
+ if (HAVE_OPT(X509KEYFILE))
+ x509_keyfile = OPT_ARG(X509KEYFILE);
+
+ if (HAVE_OPT(X509CERTFILE))
+ x509_certfile = OPT_ARG(X509CERTFILE);
+
+ if (HAVE_OPT(RAWPKKEYFILE))
+ rawpk_keyfile = OPT_ARG(RAWPKKEYFILE);
+
+ if (HAVE_OPT(RAWPKFILE))
+ rawpk_file = OPT_ARG(RAWPKFILE);
+
+ if (HAVE_OPT(PSKUSERNAME))
+ psk_username = OPT_ARG(PSKUSERNAME);
+
+ if (HAVE_OPT(PSKKEY)) {
+ psk_key.data = (unsigned char *) OPT_ARG(PSKKEY);
+ psk_key.size = strlen(OPT_ARG(PSKKEY));
+ } else
+ psk_key.size = 0;
+
+ crlf = HAVE_OPT(CRLF);
+
+#ifdef TCP_FASTOPEN
+ fastopen = HAVE_OPT(FASTOPEN);
+#else
+ if (HAVE_OPT(FASTOPEN)) {
+ fprintf(stderr, "Warning: TCP Fast Open not supported on this OS\n");
+ }
+#endif
+
+ if (rest != NULL)
+ hostname = rest;
+
+ if (hostname == NULL) {
+ fprintf(stderr, "No hostname specified\n");
+ exit(1);
+ }
+}
+
+static void check_server_cmd(socket_st * socket, int ret)
+{
+ if (socket->secure) {
+ if (ret == GNUTLS_E_REHANDSHAKE) {
+ /* There is a race condition here. If application
+ * data is sent after the rehandshake request,
+ * the server thinks we ignored his request.
+ * This is a bad design of this client.
+ */
+ log_msg(stdout, "*** Received rehandshake request\n");
+ /* gnutls_alert_send( session, GNUTLS_AL_WARNING, GNUTLS_A_NO_RENEGOTIATION); */
+
+ ret = do_handshake(socket);
+
+ if (ret == 0) {
+ log_msg(stdout, "*** Rehandshake was performed.\n");
+ } else {
+ log_msg(stdout, "*** Rehandshake Failed: %s\n", gnutls_strerror(ret));
+ }
+ } else if (ret == GNUTLS_E_REAUTH_REQUEST) {
+ do {
+ ret = gnutls_reauth(socket->session, 0);
+ } while (ret < 0 && gnutls_error_is_fatal(ret) == 0);
+
+ if (ret == 0) {
+ log_msg(stdout, "*** Re-auth was performed.\n");
+ } else {
+ log_msg(stdout, "*** Re-auth failed: %s\n", gnutls_strerror(ret));
+ }
+ }
+ }
+}
+
+
+int do_handshake(socket_st * socket)
+{
+ int ret;
+
+ if (fastopen && socket->connect_addrlen) {
+ gnutls_transport_set_fastopen(socket->session, socket->fd,
+ (struct sockaddr*)&socket->connect_addr,
+ socket->connect_addrlen, 0);
+ socket->connect_addrlen = 0;
+ } else {
+ set_read_funcs(socket->session);
+ }
+
+ do {
+ gnutls_handshake_set_timeout(socket->session,
+ GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
+ ret = gnutls_handshake(socket->session);
+
+ if (ret < 0) {
+ handle_error(socket, ret);
+ }
+ }
+ while (ret < 0 && gnutls_error_is_fatal(ret) == 0);
+
+ if (ret == 0) {
+ /* print some information */
+ print_info(socket->session, verbose, HAVE_OPT(X509CERTFILE)?P_WAIT_FOR_CERT:0);
+ if (HAVE_OPT(KEYMATEXPORT))
+ print_key_material(socket->session,
+ OPT_ARG(KEYMATEXPORT),
+ HAVE_OPT(KEYMATEXPORTSIZE) ?
+ OPT_VALUE_KEYMATEXPORTSIZE : 20);
+ socket->secure = 1;
+ } else {
+ gnutls_alert_send_appropriate(socket->session, ret);
+ shutdown(socket->fd, SHUT_RDWR);
+ }
+ return ret;
+}
+
+static int
+srp_username_callback(gnutls_session_t session,
+ char **username, char **password)
+{
+ if (srp_username == NULL || srp_passwd == NULL) {
+ return -1;
+ }
+
+ *username = gnutls_strdup(srp_username);
+ *password = gnutls_strdup(srp_passwd);
+
+ return 0;
+}
+
+static int
+psk_callback(gnutls_session_t session, char **username,
+ gnutls_datum_t * key)
+{
+ const char *hint = gnutls_psk_client_get_hint(session);
+ char *rawkey;
+ char *passwd;
+ int ret;
+ size_t res_size;
+ gnutls_datum_t tmp;
+
+ log_msg(stdout, "- PSK client callback. ");
+ if (hint)
+ log_msg(stdout, "PSK hint '%s'\n", hint);
+ else
+ log_msg(stdout, "No PSK hint\n");
+
+ if (HAVE_OPT(PSKUSERNAME))
+ *username = gnutls_strdup(OPT_ARG(PSKUSERNAME));
+ else {
+ char *p = NULL;
+ size_t n;
+
+ log_msg(stdout, "Enter PSK identity: ");
+ fflush(stdout);
+ ret = getline(&p, &n, stdin);
+
+ if (ret == -1 || p == NULL) {
+ fprintf(stderr,
+ "No username given, aborting...\n");
+ return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
+ }
+
+ if (p[strlen(p) - 1] == '\n')
+ p[strlen(p) - 1] = '\0';
+ if (p[strlen(p) - 1] == '\r')
+ p[strlen(p) - 1] = '\0';
+
+ *username = gnutls_strdup(p);
+ free(p);
+ }
+ if (!*username)
+ return GNUTLS_E_MEMORY_ERROR;
+
+ passwd = getpass("Enter key: ");
+ if (passwd == NULL) {
+ fprintf(stderr, "No key given, aborting...\n");
+ return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
+ }
+
+ tmp.data = (void *) passwd;
+ tmp.size = strlen(passwd);
+
+ res_size = tmp.size / 2 + 1;
+ rawkey = gnutls_malloc(res_size);
+ if (rawkey == NULL)
+ return GNUTLS_E_MEMORY_ERROR;
+
+ ret = gnutls_hex_decode(&tmp, rawkey, &res_size);
+ if (ret < 0) {
+ fprintf(stderr, "Error deriving password: %s\n",
+ gnutls_strerror(ret));
+ gnutls_free(rawkey);
+ gnutls_free(*username);
+ return ret;
+ }
+
+ key->data = (void *) rawkey;
+ key->size = res_size;
+
+ if (HAVE_OPT(DEBUG)) {
+ char hexkey[41];
+ res_size = sizeof(hexkey);
+ ret = gnutls_hex_encode(key, hexkey, &res_size);
+ if (ret < 0) {
+ fprintf(stderr, "Error in hex encoding: %s\n", gnutls_strerror(ret));
+ exit(1);
+ }
+ fprintf(stderr, "PSK username: %s\n", *username);
+ fprintf(stderr, "PSK hint: %s\n", hint);
+ fprintf(stderr, "PSK key: %s\n", hexkey);
+ }
+
+ return 0;
+}
+
+static void init_global_tls_stuff(void)
+{
+ gnutls_x509_trust_list_t tlist;
+ int ret;
+
+#ifdef ENABLE_PKCS11
+ if (HAVE_OPT(PROVIDER)) {
+ ret = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL);
+ if (ret < 0)
+ fprintf(stderr, "pkcs11_init: %s",
+ gnutls_strerror(ret));
+ else {
+ ret =
+ gnutls_pkcs11_add_provider(OPT_ARG(PROVIDER),
+ NULL);
+ if (ret < 0) {
+ fprintf(stderr, "pkcs11_add_provider: %s",
+ gnutls_strerror(ret));
+ exit(1);
+ }
+ }
+ }
+#endif
+
+ /* X509 stuff */
+ if (gnutls_certificate_allocate_credentials(&xcred) < 0) {
+ fprintf(stderr, "Certificate allocation memory error\n");
+ exit(1);
+ }
+ gnutls_certificate_set_pin_function(xcred, pin_callback, NULL);
+
+ gnutls_certificate_set_verify_flags(xcred, global_vflags);
+ gnutls_certificate_set_flags(xcred, GNUTLS_CERTIFICATE_VERIFY_CRLS);
+
+ if (gnutls_x509_trust_list_init(&tlist, 0) < 0) {
+ fprintf(stderr, "Trust list allocation memory error\n");
+ exit(1);
+ }
+ gnutls_certificate_set_trust_list(xcred, tlist, 0);
+
+ if (x509_cafile != NULL) {
+ ret = gnutls_x509_trust_list_add_trust_file(tlist,
+ x509_cafile,
+ NULL,
+ x509ctype,
+ GNUTLS_TL_USE_IN_TLS,
+ 0);
+ } else {
+ if (insecure == 0) {
+ ret = gnutls_x509_trust_list_add_system_trust(tlist,
+ GNUTLS_TL_USE_IN_TLS,
+ 0);
+ if (ret == GNUTLS_E_UNIMPLEMENTED_FEATURE) {
+ fprintf(stderr, "Warning: this system doesn't support a default trust store\n");
+ ret = 0;
+ }
+ } else {
+ ret = 0;
+ }
+ }
+ if (ret < 0) {
+ fprintf(stderr, "Error setting the x509 trust file: %s\n", gnutls_strerror(ret));
+ exit(1);
+ } else {
+ log_msg(stdout, "Processed %d CA certificate(s).\n", ret);
+ }
+
+ if (ENABLED_OPT(CA_AUTO_RETRIEVE))
+ gnutls_x509_trust_list_set_getissuer_function(tlist, getissuer_callback);
+
+ if (x509_crlfile != NULL) {
+ ret =
+ gnutls_certificate_set_x509_crl_file(xcred,
+ x509_crlfile,
+ x509ctype);
+ if (ret < 0) {
+ fprintf(stderr,
+ "Error setting the x509 CRL file: %s\n", gnutls_strerror(ret));
+ exit(1);
+ } else {
+ log_msg(stdout, "Processed %d CRL(s).\n", ret);
+ }
+ }
+
+ load_x509_keys();
+ load_rawpk_keys();
+
+#ifdef ENABLE_SRP
+ if (srp_username && srp_passwd) {
+ /* SRP stuff */
+ if (gnutls_srp_allocate_client_credentials(&srp_cred) < 0) {
+ fprintf(stderr, "SRP authentication error\n");
+ }
+
+ gnutls_srp_set_client_credentials_function(srp_cred,
+ srp_username_callback);
+ }
+#endif
+
+#ifdef ENABLE_PSK
+ /* PSK stuff */
+ if (gnutls_psk_allocate_client_credentials(&psk_cred) < 0) {
+ fprintf(stderr, "PSK authentication error\n");
+ }
+
+ if (psk_username && psk_key.data) {
+ ret = gnutls_psk_set_client_credentials(psk_cred,
+ psk_username,
+ &psk_key,
+ GNUTLS_PSK_KEY_HEX);
+ if (ret < 0) {
+ fprintf(stderr,
+ "Error setting the PSK credentials: %s\n",
+ gnutls_strerror(ret));
+ }
+ } else
+ gnutls_psk_set_client_credentials_function(psk_cred,
+ psk_callback);
+#endif
+
+#ifdef ENABLE_ANON
+ /* ANON stuff */
+ if (gnutls_anon_allocate_client_credentials(&anon_cred) < 0) {
+ fprintf(stderr, "Anonymous authentication error\n");
+ }
+#endif
+
+}
+
+/* OCSP check for the peer's certificate. Should be called
+ * only after the certificate list verification is complete.
+ * Returns:
+ * -1: certificate chain could not be checked fully
+ * >=0: number of certificates verified ok
+ */
+#ifdef ENABLE_OCSP
+static int cert_verify_ocsp(gnutls_session_t session)
+{
+ gnutls_x509_crt_t cert, issuer;
+ const gnutls_datum_t *cert_list;
+ unsigned int cert_list_size = 0, ok = 0;
+ unsigned failed = 0;
+ int deinit_issuer = 0, deinit_cert = 0;
+ gnutls_datum_t resp;
+ unsigned char noncebuf[23];
+ gnutls_datum_t nonce = { noncebuf, sizeof(noncebuf) };
+ int ret;
+ unsigned it;
+
+ cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
+ if (cert_list_size == 0) {
+ fprintf(stderr, "No certificates found!\n");
+ return 0;
+ }
+
+ for (it = 0; it < cert_list_size; it++) {
+ if (deinit_cert)
+ gnutls_x509_crt_deinit(cert);
+
+ ret = gnutls_x509_crt_init(&cert);
+ if (ret < 0) {
+ fprintf(stderr, "Memory error: %s\n", gnutls_strerror(ret));
+ goto cleanup;
+ }
+
+ deinit_cert = 1;
+ ret = gnutls_x509_crt_import(cert, &cert_list[it], GNUTLS_X509_FMT_DER);
+ if (ret < 0) {
+ fprintf(stderr, "Decoding error: %s\n", gnutls_strerror(ret));
+ goto cleanup;
+ }
+
+ if (deinit_issuer) {
+ gnutls_x509_crt_deinit(issuer);
+ deinit_issuer = 0;
+ }
+
+ ret = gnutls_certificate_get_issuer(xcred, cert, &issuer, 0);
+ if (ret < 0 && cert_list_size - it > 1) {
+ ret = gnutls_x509_crt_init(&issuer);
+ if (ret < 0) {
+ fprintf(stderr, "Memory error: %s\n", gnutls_strerror(ret));
+ goto cleanup;
+ }
+ deinit_issuer = 1;
+ ret = gnutls_x509_crt_import(issuer, &cert_list[it + 1], GNUTLS_X509_FMT_DER);
+ if (ret < 0) {
+ fprintf(stderr, "Decoding error: %s\n", gnutls_strerror(ret));
+ goto cleanup;
+ }
+ } else if (ret < 0) {
+ if (it == 0)
+ fprintf(stderr, "Cannot find issuer: %s\n", gnutls_strerror(ret));
+ goto cleanup;
+ }
+
+ ret = gnutls_rnd(GNUTLS_RND_NONCE, nonce.data, nonce.size);
+ if (ret < 0) {
+ fprintf(stderr, "gnutls_rnd: %s", gnutls_strerror(ret));
+ goto cleanup;
+ }
+
+ ret = send_ocsp_request(NULL, cert, issuer, &resp, &nonce);
+ if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
+ continue;
+ }
+ if (ret < 0) {
+ fprintf(stderr, "Cannot contact OCSP server\n");
+ goto cleanup;
+ }
+
+ /* verify and check the response for revoked cert */
+ ret = check_ocsp_response(cert, issuer, &resp, &nonce, verbose);
+ free(resp.data);
+ if (ret == 1)
+ ok++;
+ else if (ret == 0) {
+ failed++;
+ break;
+ }
+ }
+
+cleanup:
+ if (deinit_issuer)
+ gnutls_x509_crt_deinit(issuer);
+ if (deinit_cert)
+ gnutls_x509_crt_deinit(cert);
+
+ if (failed > 0)
+ return -1;
+ return ok >= 1 ? (int) ok : -1;
+}
+#endif
+
+/* returns the host part of a URL */
+static const char *host_from_url(const char *url, unsigned int *port, const char **path)
+{
+ static char buffer[512];
+ char *p;
+
+ *port = 0;
+ *path = "";
+
+ if ((p = strstr(url, "http://")) != NULL) {
+ snprintf(buffer, sizeof(buffer), "%s", p + 7);
+ p = strchr(buffer, '/');
+ if (p != NULL) {
+ *p = 0;
+ *path = p+1;
+ }
+
+ p = strchr(buffer, ':');
+ if (p != NULL) {
+ *p = 0;
+ *port = atoi(p + 1);
+ }
+
+ return buffer;
+ } else {
+ return url;
+ }
+}
+
+static size_t get_data(void *buf, size_t size, size_t nmemb, void *userp)
+{
+ gnutls_datum_t *ud = userp;
+
+ size *= nmemb;
+
+ ud->data = realloc(ud->data, size + ud->size);
+ if (ud->data == NULL) {
+ fprintf(stderr, "Not enough memory for the request\n");
+ exit(1);
+ }
+
+ memcpy(&ud->data[ud->size], buf, size);
+ ud->size += size;
+
+ return size;
+}
+
+/* Returns 0 on ok, and -1 on error */
+static int
+getissuer_callback(const gnutls_x509_trust_list_t tlist,
+ const gnutls_x509_crt_t cert,
+ gnutls_x509_crt_t **issuers,
+ unsigned int *issuers_size)
+{
+ gnutls_datum_t ud;
+ int ret;
+ gnutls_datum_t resp;
+ char *url = NULL;
+ char headers[1024];
+ char _service[16];
+ unsigned char *p;
+ const char *_hostname;
+ const char *path = "";
+ unsigned i;
+ unsigned int headers_size = 0, port;
+ socket_st hd;
+ gnutls_x509_crt_t issuer;
+ gnutls_datum_t data = { NULL, 0 };
+ static char buffer[MAX_BUF + 1];
+
+ sockets_init();
+
+ i = 0;
+ do {
+ ret = gnutls_x509_crt_get_authority_info_access(cert, i++,
+ GNUTLS_IA_CAISSUERS_URI,
+ &data,
+ NULL);
+ } while (ret == GNUTLS_E_UNKNOWN_ALGORITHM);
+
+ if (ret < 0) {
+ fprintf(stderr,
+ "*** Cannot find caIssuer URI in certificate: %s\n",
+ gnutls_strerror(ret));
+ return 0;
+ }
+
+ url = malloc(data.size + 1);
+ if (url == NULL) {
+ return -1;
+ }
+ memcpy(url, data.data, data.size);
+ url[data.size] = 0;
+
+ gnutls_free(data.data);
+
+ _hostname = host_from_url(url, &port, &path);
+ if (port != 0)
+ snprintf(_service, sizeof(_service), "%u", port);
+ else
+ strcpy(_service, "80");
+
+ fprintf(stderr, "Connecting to caIssuer server: %s...\n", _hostname);
+
+ memset(&ud, 0, sizeof(ud));
+
+ snprintf(headers, sizeof(headers), HEADER_PATTERN, path, _hostname);
+ headers_size = strlen(headers);
+
+ socket_open(&hd, _hostname, _service, NULL, SOCKET_FLAG_RAW|SOCKET_FLAG_SKIP_INIT, CONNECT_MSG, NULL);
+ socket_send(&hd, headers, headers_size);
+
+ do {
+ ret = socket_recv(&hd, buffer, sizeof(buffer));
+ if (ret > 0)
+ get_data(buffer, ret, 1, &ud);
+ } while (ret > 0);
+
+ if (ret < 0 || ud.size == 0) {
+ perror("recv");
+ ret = -1;
+ socket_bye(&hd, 0);
+ goto cleanup;
+ }
+
+ socket_bye(&hd, 0);
+
+ p = memmem(ud.data, ud.size, "\r\n\r\n", 4);
+ if (p == NULL) {
+ fprintf(stderr, "Cannot interpret HTTP response\n");
+ ret = -1;
+ goto cleanup;
+ }
+ p += 4;
+ resp.size = ud.size - (p - ud.data);
+ resp.data = p;
+
+ ret = gnutls_x509_crt_init(&issuer);
+ if (ret < 0) {
+ fprintf(stderr, "Memory error\n");
+ ret = -1;
+ goto cleanup;
+ }
+ ret = gnutls_x509_crt_list_import2(issuers, issuers_size, &resp,
+ GNUTLS_X509_FMT_DER, 0);
+ if (ret < 0) {
+ fprintf(stderr, "Decoding error: %s\n", gnutls_strerror(ret));
+ ret = -1;
+ goto cleanup;
+ }
+
+ ret = 0;
+
+cleanup:
+ gnutls_free(data.data);
+ free(ud.data);
+ free(url);
+
+ return ret;
+}