
/******************************************************************************
**
**  This program is free software; you can redistribute it and/or
**  modify it, however, you cannot sell it.
**
**  This program 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.
**
**  You should have received a copy of the license attached to the
**  use of this software.  If not, visit www.shmoo.com/osiris for
**  details.
**
******************************************************************************/

/*****************************************************************************
**
**  File:    md_http.c
**  Date:    July 17, 2003
**
**  Author:  Brian Wotring
**  Purpose: http management routines.
**
******************************************************************************/

#include "libosiris.h"
#include "libosirism.h"
#include "libosirisdb.h"
#include "libosirisctl.h"
#include "libfileapi.h"

#include "md_hosts.h"
#include "md_compare.h"
#include "md_database.h"
#include "md_http.h"
#include "md_auth.h"

#include "common.h"
#include "logging.h"

#define HTTP_PROTOCOL         " HTTP/"   /* need the leading space. */ 

#define HTTP_HOST_FIELD       "host="
#define HTTP_BASE_DB_FIELD    "base_db=" 

#define HTTP_AUTH_HEADER                "Authorization: Basic "
#define MAX_HTTP_AUTH_PASSWORD_LENGTH   128 

#define RESPONSE_FORMAT "HTTP/1.1 200\r\nContent-Length: %d\r\n\
Content-Type: text/html\r\n\r\n%s"

#define RESPONSE_SUCCESS_FORMAT "<HTML><BODY><FONT FACE='arial' SIZE='2'>\
<STRONG>Success</STRONG><BR><BR>Host: %s<BR>Trusted database changed \
to: %s<BR><BR><I>Osiris IDS version: %s</I><BR></FONT></BODY></HTML>"

#define HTTP_AUTH_REQUEST "HTTP/1.1 401 Unauthorized\r\nConnection: close\r\nContent-Length: 71\r\nContent-Type: text/html\r\nWWW-Authenticate: Basic realm=\"osiris\"\r\nExpires: Sat, Jan 01 2000 01:01:01 GMT\r\n\r\n<HTML><HEAD><META HTTP-EQUIV=\"Pragma\" CONTENT=\"no-cache\"></HEAD></HTML>"

/* the HTTP server only really supports on operation currently.  If */
/* it is ever expanded, this function will need to be broken down.  */

void process_http_message( SSL *ssl, const char *http_message )
{
    int index;
    int log_level = LOG_MEDIUM;
    OSI_HOST_BRIEF *host_brief;

    char request[MAX_MESSAGE_DATA_SIZE];
    char response[MAX_MESSAGE_DATA_SIZE];

    char *protocol = NULL;
    char *host = NULL;
    char *base_db = NULL;

    if( ( ssl == NULL ) || ( http_message == NULL ) )
    {
        return;
    }

    /* if this request does not have an authorization field. */
    /* we ask for it.                                        */

    if( strstr( http_message, HTTP_AUTH_HEADER ) == NULL )
    {
        osi_ssl_write_bytes( ssl, HTTP_AUTH_REQUEST,
                             strlen( HTTP_AUTH_REQUEST ) );
        return;
    }

    /* we have an auth header; first, we must authorize this user. */

    if( http_request_is_authorized( http_message ) == FALSE )
    {
        osi_strlcpy( response, "authorization failure.",
                     sizeof( response ) );

        goto exit_error;    
    }

    /* make a copy. */

    osi_strlcpy( request, http_message, sizeof( request ) );

    /* some sanity; find the protocol section. if not there, we bail. */

    protocol = strstr( request, HTTP_PROTOCOL );

    if( protocol == NULL )
    {
        osi_strlcpy( response, "invalid http request stream.",
                     sizeof( response ) );

        goto exit_error;
    }

    /* locate the stuff we care about. */

    host = strstr( request, HTTP_HOST_FIELD );
    base_db = strstr( request, HTTP_BASE_DB_FIELD );

    /* not our request, ignore it.  However, we log this on high */

    if( ( host == NULL ) || ( base_db == NULL ) )
    {
        log_level = LOG_HIGH;
        osi_strlcpy( response, "invalid request.", sizeof( response ) );        

        goto exit_error;     
    }

    /* cap the string before the protocol section. */

    (*protocol) = '\0';

    /* replace all of the '&' chars with NULLs. */

    for( index = 0; request[index] != '\0'; index++ )
    {
        if( request[index] == '&' )
        {
            request[index] = '\0';
        }
    }

    /* make sure we have values. */

    host += strlen( HTTP_HOST_FIELD );
    base_db += strlen( HTTP_BASE_DB_FIELD );

    if( ( host == NULL ) || ( base_db == NULL ) )
    {
        osi_strlcpy( response, "the format of this request is not valid.",
                     sizeof( response ) );

        goto exit_error;
    }

    /* update the trusted database for this host, or try to. */

    host_brief = osi_read_host_brief( host );

    if( host_brief == NULL )
    {
        osi_strlcpy( response, 
                     "unable to set trusted database, host doesn't exist.",
                     sizeof( response ) );

        goto exit_error;
    }

    if( osi_host_database_exists( host, base_db ) == FALSE )
    {
        osi_snprintf( response, sizeof( response ),
               "unable to set trusted database, database (%s) doesn't exist.",
                base_db );

        goto exit_error;
    }

    /* copy in new value for base database, and save host. */

    osi_strlcpy( host_brief->base_db, base_db,
                 sizeof( host_brief->base_db ) );

    /* success !! */

    if( osi_write_host_brief( host_brief ) )
    {
        osi_snprintf( response, sizeof( response ), RESPONSE_SUCCESS_FORMAT,
                      host, base_db, OSIRIS_VERSION );
            
        log_info( LOG_MEDIUM, host, "trusted database updated to %s", base_db );
        send_http_response( ssl, response ); 

        return;
    }

    else
    {
        osi_snprintf( response, sizeof( response ),
                      "unable to set trusted database to: %s.",
                       base_db );
    }

exit_error:

    log_error( log_level, host, response );
    send_http_response( ssl, response );
}

void send_http_response( SSL *ssl, const char *response )
{
    int content_length = 0;
    char data[MAX_MESSAGE_DATA_SIZE];

    if( ( ssl == NULL ) || ( response == NULL ) )
    {
        return;
    }

    content_length = strlen( response );

    osi_snprintf( data, sizeof( data ),
                  RESPONSE_FORMAT, content_length, response );

    osi_ssl_write_bytes( ssl, data, strlen( data ) );
}

osi_bool http_request_is_authorized( const char *http_message )
{
    char *auth = NULL;
    char *temp;

    char *user;
    char *pass;

    char password[MAX_HTTP_AUTH_PASSWORD_LENGTH];
    char decoded[MAX_HTTP_AUTH_PASSWORD_LENGTH];

    OSI_AUTH_CONTEXT auth_context;

    memset( &auth_context, 0, sizeof( auth_context ) );
    auth = strstr( http_message, HTTP_AUTH_HEADER ); 

    if( auth == NULL )
    {
        return FALSE;
    }

    auth += strlen( HTTP_AUTH_HEADER );

    if( auth == NULL )
    {
        return FALSE;
    }

    osi_strlcpy( password, auth, sizeof( password ) );

    /* now replace the first '\r' and '\n' characters with NULLs. */

    if( ( temp = strchr( password, '\r' ) ) != NULL )
    {
        (*temp) = '\0';
    }

    if( ( temp = strchr( password, '\n' ) ) != NULL )
    {
        (*temp) = '\0';
    }

    /* now we base64 decode this thing. It must end in a newline  */

    osi_strlcat( password, "\n", sizeof( password ) );
    decode_base64_string( password, decoded, sizeof( decoded ) );

    if( strlen( decoded ) == 0 )
    {
        return FALSE;
    }

    user = strtok( decoded, ":" );
    pass = strtok( NULL, ":" );

    if( ( user == NULL ) )
    {
        return FALSE;
    }

    /* now authenticate against our user database. */

    osi_strlcpy( auth_context.auth_user, user,
                 sizeof( auth_context.auth_user ) );    

    if( pass != NULL )
    {
        osi_strlcpy( auth_context.auth_pass, pass,
                     sizeof( auth_context.auth_pass ) );
    }

    return ( authenticate_auth_context( &auth_context ) );
}

void decode_base64_string( char *source, char *dest, int dest_size )
{
    BIO *bio_b64, *bio_s, *mem;

    bio_b64 = BIO_new((BIO_METHOD*) BIO_f_base64());
    bio_s = BIO_new((BIO_METHOD*)BIO_f_buffer());

    mem = BIO_new(BIO_s_mem());
    BIO_puts(mem, source);

    bio_b64 = BIO_push(bio_b64, mem);
    bio_s = BIO_push(bio_s, bio_b64);

    memset(dest,0,dest_size);
    BIO_read(bio_s,dest,dest_size);

    BIO_free_all( bio_s );
}

