RSA BSAFE Micro Edition Suite

Streamlined security for mobile and embedded devices

Search  Print

nbio_client.c

/* $Id: nbio_client.c,v 1.45 2005/04/11 12:28:11 hfrancis Exp $ */

/*
 * Copyright (C) 1999-2003 RSA Security Inc. All rights reserved.
 *
 * This work contains proprietary information of RSA Security.
 * Distribution is limited to authorized licensees of RSA
 * Security. Any unauthorized reproduction, distribution or
 * modification of this work is strictly prohibited.
 */

/*
 * A source of pseudo random numbers is required for various aspects of the
 * security protocol and components included in this product. Failure to
 * appropriately seed the Pseudo Random Number Generator (PRNG) will
 * seriously impact the security provided. Your application should provide this
 * random seed.
 *
 * The exact requirements for this seeding process may depend upon your
 * application and the environment for which your application is designed.
 * See RFC 1750 - Randomness Recommendations for Security.
 */

#include "r_prod.h"
#include "client_defaults.h"          /* Default defines for client samples */
#include "debug_cb.h"                    /* BIO and SSL state dump callback */
#include "arguments.h"                      /* Program arguments processing */
#include "verify_cb.h"                                   /* Verify callback */
#include "non_blocking.h"                          /* Non-blocking function */

/* Global error output for program error reporting */
BIO *bio_err;

int main(int argc, char *argv[])
{
    int ret = R_ERROR_FAILED;                      /* Function return value */
    BIO *bio_out = NULL;            /* Standard output for program feedback */
    BIO *bio_ssl = NULL;                 /* BIO for non-blocking connection */
                               /* "host:port" string of the server location */
    char *host = SSL_CLIENT_HOST_PORT_DEFAULT;
    char *ciphers = NULL;                /* Cipher list, NULL means default */
    char *server_req;     /* Request for the server - this is the data sent */
    int len;                                /* Length of the request string */
    int offset;                                 /* Offset for the data sent */
    static char buf[SSL_CLIENT_DATA_BUFFER_LEN];  /* Buffer for server data */
    int debug = 0;                              /* Print extra debug output */
    int state = 0;                                   /* Print the SSL state */
    int extras = 0;                         /* Extra command line arguments */
    int bytes_read;                        /* Number bytes read from server */
    int bytes_written;                    /* Number bytes written to server */
    SSL_METHOD *meth = NULL;                /* Pointer to server SSL method */
    SSL_CTX *ssl_ctx = NULL;                      /* Pointer to SSL context */
    SSL *ssl = NULL;            /* SSL reference for setting debug callback */
    R_LIB_CTX *lib_ctx = NULL;                /* Pointer to library context */
    int arg;                                            /* Argument counter */
    int off = 0;                                          /* Additional options */
    static unsigned char rand_seed[] = "A bad seed for software PRNG";
    int mode = R_LIB_CTX_FIPS140_MODE;            /* Library's default mode */
    int sock_fd = 0;     /* File descriptor for socket connection to server */
    int retVal = 0;                          /* Return value from API calls */
    int sockOptVal = 0;                 /* Buffer for setting socket option */
    unsigned long optLen = 0;                /* Length of the socket option */

    /* Create an output channel */
    if ((bio_err = BIO_new_fp(stderr, BIO_NOCLOSE)) == NULL)
    {
        goto end;
    }
    BIO_set_flags(bio_err, BIO_FLAGS_FLUSH_ON_WRITE);

    /* Create an output channel */
    if ((bio_out = BIO_new_fp(stdout, BIO_NOCLOSE)) == NULL)
    {
        goto end;
    }
    BIO_set_flags(bio_out, BIO_FLAGS_FLUSH_ON_WRITE);

    /* Parse the client application arguments */
    if (client_parse_arguments(argc, argv, bio_err, bio_out, &host,
        &debug, &state, &meth, &extras, &off, &ciphers, &mode) == 1)
    {
        client_usage(bio_err, argv[0]);
        goto end;
    }

    /* Extra command line arguments are unwanted */
    if (extras > 0)
    {
        /* Report the first command line problem */
        for (arg = 1; arg < argc; arg++)
        {
            if (argv[arg] != NULL)
            {
                BIO_printf(bio_err, "\nUnknown argument : %s\n", argv[arg]);
                break;
            }
        }

        /* Print the program usage messages */
        client_usage(bio_err, argv[0]);
        goto end;
    }

    /* Initialize the SSL library using the default resources */
    if (PRODUCT_LIBRARY_NEW(PRODUCT_DEFAULT_RESOURCE_LIST(), R_RES_FLAG_DEF,
        &lib_ctx) != R_ERROR_NONE)
    {
        BIO_printf(bio_err, "Unable to create library context\n");
        goto end;
    }

    /*
         * This demonstrates how to seed the software PRNG of the SSL library.
         * Seeding information gathered using software methods is not the best
         * source, so do not use the following example of application-specified
         * entropy in production. RNG hardware is considered the best source of
         * random information.
     */
    if (R_rand_seed(R_rand_get_default(), rand_seed, sizeof(rand_seed)) == 0)
    {
        BIO_printf(bio_err, "Unable to seed the PRNG\n");
        goto end;
    }

#ifndef SSLC_SMALL_CODE
    SSL_load_error_strings();
#endif /* !SSLC_SMALL_CODE */

    /* Set the client default method if it is not set */
    if (meth == NULL)
    {
        /*
         * Select the protocol version in the following order:
         * - Use pure TLSv1 if it is the only protocol version available
         * - Use SSLv3 support optionally in an SSLv2 handshake
         *   (for maximum compatibility) (if possible)
         * - Use pure SSLv3
         * - Use pure SSLv2
         */
#if defined(NO_SSL2) && defined(NO_SSL3) && !defined(NO_TLS1)
        meth = TLSv1_client_method();
        BIO_printf(bio_out, "Doing TLSv1_client_method\n");
#elif (!defined(NO_SSL2) || defined(NO_SSL2IMPL)) && !defined(NO_SSL3)
        meth = SSLv23_client_method();
        BIO_printf(bio_out, "Doing SSLv23_client_method\n");
#elif !defined(NO_SSL3)
        meth = SSLv3_client_method();
        BIO_printf(bio_out, "Doing SSLv3_client_method\n");
#elif !defined(NO_SSL2)
        meth = SSLv2_client_method();
        BIO_printf(bio_out, "Doing SSLv2_client_method\n");
#else
        BIO_printf(bio_err, "Unable to set default client method.\n");
        goto end;
#endif
    }

    /* Create an SSL context structure. */
    if ((ssl_ctx = SSL_CTX_new(meth)) == NULL)
    {
        BIO_printf(bio_err, "Unable to create SSL context\n");
        goto end;
    }

    /*
     * Set the mode of operation of the context.
     *
     * Note this is only applicable to libraries that support FIPS/non-FIPS
     * modes of operations.
     */
    (void)SSL_CTX_set_R_LIB_CTX(ssl_ctx, lib_ctx, mode);

    /* Set the cipher list if specified. Otherwise use the default. */
    if (ciphers != NULL)
    {
        SSL_CTX_set_cipher_list(ssl_ctx, ciphers);
    }

    /* Set the SSL information callback to print the SSL state */
    if (state)
    {
        SSL_CTX_set_info_cb(ssl_ctx, ssl_state_info_cb);
    }

    /* Enable all vendor bug compatibility options */
    SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL | off);

    /*
     * Set the verification callback used by the application. This replaces
     * the internal verification. Note that v_cb_dummy is a verification
     * callback which ignores any input.
     */
    SSL_CTX_set_app_verify_cb(ssl_ctx, v_cb_dummy, NULL);

    /*
     * Set the verification mode for the context. Verification is not required
     * here, so set it to none.
     */
    SSL_CTX_set_verify_mode(ssl_ctx, SSL_VERIFY_NONE);

    /*
     * Create a BIO that handles the connection and set up of transparent SSL
     * handling which encapsulates the socket setup.
     */
    if ((bio_ssl = BIO_new_ssl_connect(ssl_ctx)) == NULL)
    {
        BIO_printf(bio_err, "Unable to create an SSL object\n");
        goto end;
    }

    /* Set the host name value for the connection BIO bio_ssl */
    BIO_set_conn_hostname(bio_ssl, host);

    /*
     * Switch on the non-blocking I/O flag for the connection BIO bio_ssl
     */
    if (BIO_set_nbio(bio_ssl, 1) != 1)
    {
        BIO_printf(bio_err, "Unable to set non-blocking mode\n");
        goto end;
    }

    /*
     * Set debug on both the SSL and the BIO in order to obtain output both
     * during the handshake as well as after the handshake has finished
     */
    if (debug)
    {
        BIO_get_ssl(bio_ssl, &ssl);
        BIO_set_cb(bio_ssl, bio_dump_cb);
        BIO_set_cb_arg(bio_ssl, (char *)bio_out);
        BIO_set_cb(SSL_get_rbio(ssl), bio_dump_cb);
        BIO_set_cb_arg(SSL_get_rbio(ssl), (char *)bio_out);
    }

    /*
     * The command to send to the web server is a simple HTTP command to get
     * the top-level page
     */
    server_req = SSL_CLIENT_REQUEST_DEFAULT;
    len = Strlen(server_req);

    for (;;)
    {
        /*
         * Do a handshake with the server to create a connection, can subsequently
         * make changes to the type of socket connection as desired.
         */
        retVal = BIO_do_connect(bio_ssl);
        if (retVal == 1)
        {
            /* The handshake has completed successfully */
            break;
        }

        /*
         * -1 indicates that an error occurred, or a temporary error, such as
         * the server is busy, occurred and we need to retry later.
         */
        if (retVal <= 0)
        {
            /*
             * Indicates whether a temporary error occurred or a failure to
             * complete the operation occurred
             */
            if ((ret = BIO_should_retry(bio_ssl)))
            {
                if (debug)
                {
                    BIO_printf(bio_out, "write DELAY\n");
                }

                /* Wait until the write can be accomplished */
                select_wait(bio_ssl, debug, bio_out);
                continue;
            }
            else
            {
                /* If not a retry then it is an error */
                ret = R_ERROR_FAILED;
                goto end;
            }
        }
    }


    /*
     * Set the KEEPALIVE socket option, need to get socket fd first
     * Also check the option was correctly set
     */
    retVal = BIO_get_fd(bio_ssl, &sock_fd);
    sockOptVal = 1; /* non zero value to turn option on */
    optLen = sizeof(sockOptVal);
    retVal = SIO_setsockopt(sock_fd, SOL_SOCKET, SO_KEEPALIVE, &sockOptVal, optLen);
    if (retVal != 0)
    {
        /* If fail, report it but continue on */
        BIO_printf(bio_err, "Setting KEEPALIVE socket option failure\n");
    }
    retVal = SIO_getsockopt(sock_fd, SOL_SOCKET, SO_KEEPALIVE, &sockOptVal, &optLen);
    if (retVal != 0)
    {
        /* If fail, report it but continue on */
        BIO_printf(bio_err, "Getting KEEPALIVE socket option failure\n");
    }
    else
    {
        if (sockOptVal == 0)
        {
        /* If fail, report it but continue on */
        BIO_printf(bio_err, "KEEPALIVE socket option was not correctly set\n");
        }
    }

    /*
     * Send the request to the server. As this sample uses non-blocking
     * sockets, we send data in until all Bytes have been written to the
     * server, or an error occurs. Note that the select_wait function is used
     * in a simplistic way. Also note that the SSL BIO "hides" the handshake
     * messages that are sent to the server to establish the secure channel.
     */
    offset = 0;
    for (;;)
    {
        /* Send the request to the server */
        bytes_written = BIO_write(bio_ssl, &(server_req[offset]), len);

        /*
         * -1 indicates that an error occurred, or a temporary error, such as
         * the server is busy, occurred and we need to retry later.
         */
        if (bytes_written <= 0)
        {
            /*
             * Indicates whether a temporary error occurred or a failure to
             * complete the operation occurred
             */
            if ((ret = BIO_should_retry(bio_ssl)))
            {
                if (debug)
                {
                    BIO_printf(bio_out, "write DELAY\n");
                }

                /* Wait until the write can be accomplished */
                select_wait(bio_ssl, debug, bio_out);
                continue;
            }
            else
            {
                /* If not a retry then it is an error */
                ret = R_ERROR_FAILED;
                goto end;
            }
        }

        /* The write request succeeded in writing some Bytes */
        offset += bytes_written;
        len -= bytes_written;

        /*
         * If there is no more data to write, the request sending has been
         * completed
         */
        if (len <= 0)
        {
            BIO_printf(bio_out, "Finished writing request to server\n");
            break;
        }
    }

    /*
     * Read the reply from the server and display it on stdout. As this sample
     * uses non-blocking sockets, read data in until the server closes the
     * connection. A more complete implementation would have a protocol with
     * message types and Bytes sent.
     */
    for (;;)
    {
        bytes_read = BIO_read(bio_ssl, buf, sizeof(buf));

        /* A zero return indicates end-of-file (close-of-socket) */
        if (bytes_read == 0)
        {
            break;
        }

        if (bytes_read < 0)
        {
            /*
             * Check to see if the reason was something that should be retried
             * later.
             */
            if (BIO_should_retry(bio_ssl))
            {
                if (debug)
                {
                    BIO_printf(bio_out, "read DELAY\n");
                }

                /* Wait until the server has sent information */
                select_wait(bio_ssl, debug, bio_out);
                continue;
            }
            else
            {
                /*
                 * If not a retry then it is an error, or the server has
                 * closed the connection
                 */
                goto end;
            }
        }

        BIO_printf(bio_out, "RECEIVED: %s\n", buf);
    }

    /* Report that the request and reply were successful */
    BIO_printf(bio_out, "Connection completed successfully\n");

    ret = R_ERROR_NONE;

end:

    /* If there was an error display the error stack */
    if ((ret != R_ERROR_NONE) && (bio_err != NULL))
    {
        ERR_print_errors(bio_err);
    }

    /* Clean up time */
    if (bio_ssl != NULL)
    {
        BIO_free_all(bio_ssl);
    }

    /* Free the SSL_CTX */
    if (ssl_ctx != NULL)
    {
        SSL_CTX_free(ssl_ctx);
    }

    /* Clean up the library */
    PRODUCT_LIBRARY_FREE(lib_ctx);

    /* Free the standard output and error BIOs */
    if (bio_out != NULL)
    {
        BIO_free_all(bio_out);
    }

    if (bio_err != NULL)
    {
        BIO_free(bio_err);
        bio_err = NULL;
    }

    return(R_ERROR_EXIT_CODE(ret));
}

Copyright (c) 1999-2005 RSA Security Inc. All rights reserved. 072-001001-2100-001-000 - 2.1