/* Very simple TLS client, to test TLS 1.3 connections */

#define MAXHOSTNAME 256
#define MAXPORTNAME 128
#define MAXMSG 1024
/* Default port */
#define PORT "https"

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <errno.h>

#include <gnutls/gnutls.h>

/* https://gnutls.org/manual/gnutls.html */

int
main(argc, argv)
    int             argc;
    char          **argv;
{
    gnutls_session_t *session = malloc(sizeof(gnutls_session_t));
    int             tls_result;
    int             last_alert;
    int             cert_status;
    gnutls_datum_t *cert_out;
    int             cert_type;
    int             result;
    char           *hostname = malloc(MAXHOSTNAME);
    char           *portname = malloc(MAXPORTNAME);
    int             sockfd;
    struct addrinfo hints, *res;
    gnutls_certificate_credentials_t xcred;

    if (argc <= 1 || argc >= 4) {
        fprintf(stderr, "Usage: %s server-name [server-port]\n", argv[0]);
        exit(1);
    }
    strcpy(hostname, argv[1]);
    if (argc == 3) {
        strcpy(portname, argv[2]);
    } else {
        strcpy(portname, PORT);
    }

    tls_result = gnutls_certificate_allocate_credentials(&xcred);
    if (tls_result != GNUTLS_E_SUCCESS) {
        fprintf(stderr, "Cannot allocate credentials: %s\n",
                gnutls_strerror(tls_result));
        exit(1);
    }
    tls_result = gnutls_certificate_set_x509_system_trust(xcred);
    if (tls_result < 0) {
        fprintf(stderr, "Cannot load credentials: %s\n",
                gnutls_strerror(tls_result));
        exit(1);
    }
    tls_result = gnutls_init(session, GNUTLS_CLIENT);
    if (tls_result != GNUTLS_E_SUCCESS) {
        fprintf(stderr, "Cannot create a new TLS session: %s\n",
                gnutls_strerror(tls_result));
        exit(1);
    }
    tls_result = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE, xcred);       /* Mandatory 
                                                                                         * or 
                                                                                         * "no 
                                                                                         * cipher 
                                                                                         * suites 
                                                                                         * found" 
                                                                                         */
    if (tls_result != GNUTLS_E_SUCCESS) {
        fprintf(stderr, "Cannot set credentials: %s\n", gnutls_strerror(tls_result));
        exit(1);
    }
    tls_result =
        gnutls_priority_set_direct(*session,
                                   "NORMAL:-VERS-ALL:+VERS-TLS1.3:+VERS-TLS1.2:+VERS-TLS1.1:+VERS-TLS1.0",
                                   NULL);
    if (tls_result != GNUTLS_E_SUCCESS) {
        fprintf(stderr, "Cannot set priorities: %s\n", gnutls_strerror(tls_result));
        exit(1);
    }
    gnutls_session_set_verify_cert(*session, hostname, 0);
    tls_result = gnutls_server_name_set(*session, GNUTLS_NAME_DNS, hostname,
                                        strlen(hostname));
    if (tls_result != GNUTLS_E_SUCCESS) {
        fprintf(stderr, "Cannot set Server Name Indication: %s\n",
                gnutls_strerror(tls_result));
        exit(1);
    }
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = PF_UNSPEC;        /* v4 or v6, I don't care */
    hints.ai_socktype = SOCK_STREAM;
    result = getaddrinfo(hostname, portname, &hints, &res);
    if (result != 0) {
        fprintf(stderr,
                "Cannot resolve name '%s : %s' (wrong name or wrong port): %d\n",
                hostname, portname, result);
        exit(1);
    }
    sockfd = socket(res->ai_family, res->ai_socktype, 0);
    if (sockfd == -1) {
        fprintf(stderr, "Cannot create socket\n");
        exit(1);
    }
    result = connect(sockfd, res->ai_addr, res->ai_addrlen);
    if (result != 0) {
        fprintf(stderr, "Cannot connect to %s: %d\n", hostname, errno);
        exit(1);
    }
    gnutls_transport_set_int(*session, sockfd);
    tls_result = gnutls_handshake(*session);
    if (tls_result != GNUTLS_E_SUCCESS) {
        fprintf(stderr, "Cannot start the TLS session to %s: ", hostname);
        if (tls_result == GNUTLS_E_FATAL_ALERT_RECEIVED) {
            last_alert = gnutls_alert_get(*session);
            fprintf(stderr, "Received alert '%d': %s.\n", last_alert,
                    gnutls_alert_get_name(last_alert));
        } else if (tls_result == GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR) {
            cert_status = gnutls_session_get_verify_cert_status(*session);
            cert_out = malloc(sizeof(gnutls_datum_t));
            cert_out->size = 0;
            cert_out->data = malloc(MAXMSG);
            cert_out->data[0] = '\0';
            cert_type = gnutls_certificate_type_get(*session);
            tls_result =
                gnutls_certificate_verification_status_print(cert_status, cert_type,
                                                             cert_out, 0);
            if (tls_result != GNUTLS_E_SUCCESS) {
                fprintf(stderr, "Error in printing the certificate check status\n");
                exit(1);
            }
            fprintf(stderr, "%s\n", cert_out->data);
        } else {
            fprintf(stderr, "%s\n", gnutls_strerror(tls_result));
        }
        exit(1);
    }
    fprintf
        (stderr, "TLS connection using \"%s %s\"\n",
         gnutls_protocol_get_name(gnutls_protocol_get_version(*session)),
         gnutls_cipher_get_name(gnutls_cipher_get(*session)));

}
