RSA BSAFE Micro Edition Suite

Streamlined security for mobile and embedded devices

Search  Print

cache_server.c

/* $Id: cache_server.c,v 1.34 2005/07/14 04:28:59 gsingh 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 "server_defaults.h"       /* The default values for server samples */
#include "builtin_cert_pkey.h"    /* The built-in certificate & private key */
#include "debug_cb.h"                /* The BIO and SSL state dump callback */
#include "arguments.h"                  /* The program arguments processing */
#include "app_cache.h"                        /* The external session cache */

/*
 * Macro constants
 */

#define SSL_CACHE_SERVER_QUIT_CMD                "exit"

/*
 * Global Variable Definitions
 */

BIO *bio_err;

/*
 * Local function prototype declarations
 */

static void cache_server_usage(BIO *bio);
static void print_connection_source(BIO *accept_bio, BIO *bio_out);


int main(int argc, char *argv[])
{
    int ret = R_ERROR_FAILED;                  /* The function return value */
    BIO *bio = NULL;
    BIO *bio_out = NULL;
    BIO *abio = NULL;
    int bytes_read;                 /* The number of Bytes read from client */
    int ssl_err;                                /* The ssl error identifier */
    char *port = SSL_SERVER_PORT_DEFAULT;     /* The port to accept clients */
    char *ciphers = NULL;             /*The cipher list. Null means default */
    static char buf[SSL_SERVER_DATA_BUFFER_LEN];  /* Application I/O buffer */
    int debug = 0;                     /* The switch for extra debug output */
    int state = 0;                               /* The print the SSL state */
    int extras = 0;                     /* The extra command line arguments */
    int connections = SSL_SERVER_UNLIMITED_CONNECTIONS; /* Connection count */
    SSL *ssl = NULL;                           /* The ssl connection object */
    SSL_METHOD *meth = NULL;            /* The pointer to server SSL method */
    SSL_CTX *ssl_ctx = NULL;                  /* The pointer to SSL context */
    R_LIB_CTX *lib_ctx = NULL;            /* The pointer to library context */
    SSLCERT *server_cert = NULL;       /* The pointer to server certificate */
    EVP_PKEY *pkey = NULL;             /* The pointer to server private key */
    SESS_CACHE *session_cache = NULL;              /* The SSL session cache */
    int arg = 0;                       /* The command line argument counter */
    int bad_arg = 0;                      /* The flag for improper argument */
    int no_flush = 0;                /* Flag to stop session cache flushing */
    int no_cache = 0;                   /* The flag to stop session caching */
    long session_timeout = 0;                 /* The valid session lifetime */
    int cache_mode;                               /* The session cache mode */
    int cmd_len;                     /* The length of expected command data */
    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 */

    /* 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 general server arguments */
    if (server_parse_arguments(argc, argv, bio_err, bio_out, &port,
        &connections, &debug, &state, &meth, &extras, &off, &ciphers,
        &mode) == 1)
    {
        /* An error in the general arguments so report usage and exit */
        server_usage(bio_err, argv[0]);
        cache_server_usage(bio_err);
        goto end;
    }

    /* Parse application-specific server arguments */
    if (extras > 0)
    {
        for (arg = 1; arg < argc; arg++)
        {
            /* Skip the NULL arguments */
            if (argv[arg] == NULL)
            {
                continue;
            }

            /* Process specific arguments */
            if (Strcmp(argv[arg], "-no_cache") == 0)
            {
                /* Stop flushing the session cache */
                no_cache = 1;
            }
            else if (Strcmp(argv[arg], "-no_flush") == 0)
            {
                /* Stop flushing the session cache */
                no_flush = 1;
            }
            else if (Strcmp(argv[arg], "-timeout") == 0)
            {
                /* Assign a default session timeout */
                if (++arg >= argc)
                {
                    bad_arg = 1;
                }
                else
                {
                    session_timeout = atol(argv[arg]);
                }
            }
            else
            {
                bad_arg = 1;
            }

            if (bad_arg == 1)
            {
                /* Report specific problem */
                BIO_printf(bio_err, "\nUnknown argument : %s\n", argv[arg]);

                /* Report program usage and exit */
                server_usage(bio_err, argv[0]);
                cache_server_usage(bio_err);
                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 server 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_server_method();
        BIO_printf(bio_out, "Doing TLSv1_server_method\n");
#elif !defined(NO_SSL2) && !defined(NO_SSL3)
            meth = SSLv23_server_method();
            BIO_printf(bio_out, "Doing SSLv23_server_method\n");
#elif !defined(NO_SSL3)
            meth = SSLv3_server_method();
            BIO_printf(bio_out, "Doing SSLv3_server_method\n");
#elif !defined(NO_SSL2)
            meth = SSLv2_server_method();
            BIO_printf(bio_out, "Doing SSLv2_server_method\n");
#else
            BIO_printf(bio_err, "Unable to set default server method.\n");
            goto end;
#endif
    }

    /* Create the 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);

    /* Load the built-in server certificate */
    BIO_printf(bio_out, "Server is using the built-in certificate\n");
    server_cert = sslcert_get_certificate();
    if (!SSL_CTX_use_certificate(ssl_ctx, server_cert))
    {
        BIO_printf(bio_err, "Unable to load server certificate\n");
        goto end;
    }

    /* Load the built-in server private key */
    BIO_printf(bio_out, "Server is using the built-in RSA key\n");
    pkey = get_rsa512_priv_server();
    if (!SSL_CTX_use_PrivateKey(ssl_ctx, pkey))
    {
        BIO_printf(bio_err, "Unable to load server private key\n");
        goto end;
    }

   /*
    * Check that the certificate and private key match. This is a common error
    * which should be avoided before starting the server, as without a
    * certificate and matching private key the SSL handshake cannot be
    * completed.
    */
    if (!SSL_CTX_check_private_key(ssl_ctx))
    {
        BIO_printf(bio_err, "Private key check failed\n");
        goto end;
    }

    /* Set the external session caching code */
    if (!no_cache)
    {
#ifdef SSLC_SMALL_CODE
        /*
         * For multithreaded programs the session cache must be protected
         * against simultaneous updates. For these programs you must add a call
         * to R_lock_set_cb() to provide a thread locking mechanism. Typically
         * the locking mechanism is platform-dependent. See the
         * multithreading sample code for calls to APP_locking_cb_add().
         */

        /* Initialize the local session cache */
        if (APP_session_cache_init(&session_cache) == 0)
        {
            BIO_printf(bio_err, "Session cache initialization failed\n");
            goto end;
        }

        /* Set the session cache callback */
        SSL_CTX_set_session_cache_cb(ssl_ctx, APP_session_cache_cb);

        /* Set the session cache structure as the callback argument */
        SSL_CTX_set_session_cache_cb_arg(ssl_ctx, session_cache);
#endif /* SSLC_SMALL_CODE */

        /* Set the caching flags on the SSL_CTX */
        cache_mode = SSL_SESS_CACHE_SERVER;
        if (no_flush)
        {
            cache_mode |= SSL_SESS_CACHE_NO_AUTO_CLEAR;
        }
        SSL_CTX_set_session_cache_mode(ssl_ctx, cache_mode);
    }

    /* Set the given session timeout on the SSL_CTX */
    SSL_CTX_set_timeout(ssl_ctx, session_timeout);


    /* Set up an accept BIO to simplify the process of setting up a server */
    if ((abio = BIO_new_accept(port)) == NULL)
    {
        BIO_printf(bio_err, "Unable to create accept BIO\n");
        goto end;
    }
    else
    {
        BIO_printf(bio_out, "Creating an accept BIO\n");
    }

    /* Allow for easy restart of the server accept socket */
    BIO_set_bind_mode(abio, SIO_BIND_REUSEADDR);

    /*
     * The first call to BIO_do_accept() sets up the socket for accepting
     * incoming connections by establishing a listener
     */
    if (BIO_do_accept(abio) <= 0)
    {
        BIO_printf(bio_err, "Unable to initialize server socket\n");
        goto end;
    }

    /*
     * In debug mode add a BIO callback to output all data passing through the
     * connection. This can be done on the accept socket because the callback
     * reference is passed on to the accepting socket.
     */
    if (debug)
    {
        BIO_set_cb(abio, bio_dump_cb);
        BIO_set_cb_arg(abio, (char *)bio_out);
    }

    /*
     * Create the SSL structure. Defaults are inherited from the SSL_CTX.
     * Options are usually set against the SSL_CTX with those requiring
     * modification set against the SSL.
     */
    if ((ssl = SSL_new(ssl_ctx)) == NULL)
    {
        BIO_printf(bio_err, "Unable to create SSL structure\n");
        goto end;
    }

    /*
     * Enter the web server loop which will allow a set number of
     * connections before exiting. The number of connections will be
     * unlimited if the number of connections is negative.
     */
    while (connections != 0)
    {
        BIO_flush(bio_out);
        BIO_flush(bio_err);
        /* Decrement the connection count */
        connections--;

        BIO_printf(bio_out, "\nWaiting for new connection ...\n");

        /* Accept a new client socket connection */
        if (BIO_do_accept(abio) < 0)
        {
            /* The accept of a new socket failed */
            BIO_printf(bio_err, "Accept of client connection failed\n");
            ERR_print_errors(bio_err);

            /* Attempt a new socket connection */
            continue;
        }

        /* Pop the newly generated BIO from the listener BIO */
        bio = BIO_pop(abio);

        /* Print feedback about the socket connection */
        print_connection_source(bio, bio_out);

        /*
         * The BIO is taken by SSL_set_bio(). It does not need to be freed
         * as this is performed during the SSL clean up.
         */
        SSL_set_bio(ssl, bio, bio);

        /*
         * Instruct the SSL layer to perform the server-side of the protocol
         * when SSL_do_handshake() is called. SSL_do_handshake() is implicitly
         * called by SSL_read()/SSL_write(). Note that SSL_connect() or
         * SSL_accept() explicitly do the client- or server-side handshake
         * functions.
         */
        SSL_set_accept_state(ssl);

        /*
         * If the cached session identifier is not cleared, the SSL uses the
         * protocol version of the last connection. Here the session
         * identifier is cleared and reverts to the protocol specified by the
         * SSL_CTX.
         */
        SSL_set_session(ssl, NULL);

        /*
         * Reset the SSL connection deals to be in an "initial" state
         * ready to begin another handshake.
         */
        SSL_clear(ssl);

        /*
         * Call SSL_read() to perform the full handshake. Read less than a full
         * buffer here to allow for processing later.
         */
        bytes_read = SSL_read(ssl, buf, SSL_SERVER_DATA_BUFFER_LEN - 1);

        /*
         * Categorize the return value from the SSL call to determine how to
         * deal with the error. This call also works when the SSL return code
         * does not indicate an error in which case SSL_ERROR_NONE is always
         * returned here.
         */
        ssl_err = SSL_get_error(ssl, bytes_read);

        switch (ssl_err)
        {
        case SSL_ERROR_NONE:
            /* Handshake finished, so continue parsing the data */
            break;
        case SSL_ERROR_SSL:
            /* Handshake error, report and exit */
            BIO_printf(bio_out, "Handshake failure\n");
            ERR_print_errors(bio_out);
            goto close_connection;
        case SSL_ERROR_ZERO_RETURN:
            break;
        case SSL_ERROR_WANT_READ:
        case SSL_ERROR_WANT_WRITE:
        case SSL_ERROR_WANT_CONNECT:
        case SSL_ERROR_SYSCALL:
        default:
            /*
             * System call error. This error is different from the
             * SSL_ERROR_SSL in that errno (under unix) has the numeric error
             * value, and is not converted into text. If calling SSL_read()
             * or SSL_write() there is no recorded error in the error logging
             * because the error could be able to be retried, about which the
             * library is unaware.
             */
            BIO_printf(bio_out,
                "System call error (ssl_err=%d,ret=%d,errno=%d)\n",
                ssl_err, bytes_read, R_os_get_last_sys_error());
            ERR_print_errors(bio_out);
            goto close_connection;
            break;
        }

        /* Process the data read from the client */

        /*
         * NULL-terminate the buffer of data read from the client. This sample
         * assumes that the server has just read text data.
         */
        buf[bytes_read] = '\0';

        /* Report feedback to the standard output */
        BIO_printf(bio_out, "Received from client '%s'\n", buf);

        /* Check for a quit signal from the client */
        cmd_len = Strlen(SSL_CACHE_SERVER_QUIT_CMD);
        if (Strncmp(SSL_CACHE_SERVER_QUIT_CMD, buf, cmd_len) == 0)
        {
            /* Exit immediately from the connection loop */
            BIO_printf(bio_out, "Received quit from client\n");
            connections = 0;
        }

        /* No reply to client is made here */

close_connection:

        /* Close the SSL connection */
        SSL_shutdown(ssl);

        /* Report the connection close to standard output */
        BIO_printf(bio_out, "Done\n");
        BIO_flush(bio_out);
    }

    /* Set program success */
    ret = R_ERROR_NONE;

end:

    /* On error output the error stack */
    if ((ret != R_ERROR_NONE) && (bio_err != NULL))
    {
        ERR_print_errors(bio_err);
    }

    /* Free memory for all structures */
    if (ssl != NULL)
    {
        SSL_free(ssl);
    }

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

    if (!no_cache)
    {
        /* In debug mode output the cache statistics */
        if (debug)
        {
            APP_session_cache_stats(session_cache, bio_out);
        }

        /* Finalize the application cache */
        if (APP_session_cache_final(session_cache))
        {
            session_cache = NULL;
        }

        /* The finalization routine frees all the lock names */
    }

    /*
     * The private key and certificate were generated as structures in memory
     * before being associated with the SSL. The application therefore still
     * has a reference to the private key and certificate which must be freed
     * now.
     */
    if (pkey != NULL)
    {
        SSLCERT_PKEY_free(pkey);
    }

    if (server_cert != NULL)
    {
        SSLCERT_free(server_cert);
    }

    if (ssl_ctx != NULL)
    {
        /*
         * Do not check for the return as no more memory freeing will be
         * performed
         */
        SSL_CTX_free(ssl_ctx);
    }

    /* Free the SSL library context */
    if (lib_ctx != NULL)
    {
        PRODUCT_LIBRARY_FREE(lib_ctx);
    }

    /* Free the BIOs for standard out and error */
    BIO_flush(bio_out);
    BIO_flush(bio_err);
    if (bio_out != NULL)
    {
        BIO_free(bio_out);
    }

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

    return(R_ERROR_EXIT_CODE(ret));
}

static void cache_server_usage(BIO *bio)
{
    BIO_printf(bio, "Where specific arguments are :\n");
    BIO_printf(bio, "-no_cache        - No session caching\n");
    BIO_printf(bio, "-no_flush        - No session cache flushing\n");
    BIO_printf(bio, "-timeout arg     - Reset the default session lifetime\n");

    return;
}

static void print_connection_source(BIO *accept_bio, BIO *bio_out)
{
    int sock;                                 /* The socket file descriptor */
    unsigned long len;               /* The length of socket data structure */
    struct sockaddr_in name;                   /* The socket data structure */
    static char hostbuf[128];        /* The buffer for the host name string */
    int hostlen = 0;                   /* The length of the hostname string */
    int i;

    /* Extract the file descriptor from the BIO */
    BIO_get_fd(accept_bio, &sock);

    /* Make a socket call to retrieve the socket address details */
    len = (unsigned long)sizeof(name);
    i = SIO_getsockname(sock, (struct sockaddr *) &name, &len);

    /* Retrieve the peer host name from the address */
    if (i == 0)
    {
        /* Initialize the hostname string to be empty */
        hostbuf[0] = '\0';
        hostlen = SIO_gethostbyaddr((unsigned char *) &name, len, hostbuf,
            sizeof(hostbuf));
    }
    else
    {
        BIO_printf(bio_out, "getsockname failed - %d\n",
            R_os_get_last_sys_error());
    }

    /* Report the host name if it was found */
    if (hostlen != 0)
    {
        BIO_printf(bio_out, "New connection from %s\n", hostbuf);
    }
    else
    {
        BIO_printf(bio_out, "New connection\n");
    }

    return;
}

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