httpd-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From TOKI...@aol.com
Subject Re: Completely transform a request ( Followup: Example code mod_transform )
Date Wed, 22 Aug 2007 02:23:20 GMT
* HOW TO COMPLETELY TRANSFORM AN INBOUND APACHE REQUEST

>Arturo wrote...
>
>I want to Completely Transform a Request. 
>100% transformation.
>
>Based on a certain logic, If an incoming request matches one 
>of my action triggers, then I want to apply a transformation 
>to the 100% of the incoming request. I know I can do that when 
>I just want to modify brigade-by-brigade. But I need to read 
>the WHOLE request before doing so. Even the METHOD line. 
>Even the headers. Even the body. All of it. Then, completely 
>transform that into another request, and have Apache process it.
>
>With the current input filtering framework, at the connection 
>level, I should be able to do it. But I can't.
>
>If you NEED an example of what I'd like to transform, and into 
>WHAT i want to transform it, see this post:
>
>What I'd like to transform:
>http://www.mail-archive.com/dev@httpd.apache.org/msg37206.html
>
>Into WHAT I want to transform it: a completely different request 
>(i.e different method line, different headers and different body, 
>and I can't do that in stages, I have to read the whole request first).
>
>Sincerely,
>
>Arturo "Buanzo" Busleiman - 
>Consultor Independiente en Seguridad Informatica
>
> In case you want to take a look, you can check out the filter 
> function from...
>
> _http://www.buanzo.com.ar/files/openpgp.conn.filter.in.c_ 
(http://www.buanzo.com.ar/files/openpgp.conn.filter.in.c) 

I've looked at the code there and you definitely are on the
right track. There is nothing fundamentally wrong with your
code except, as you discovered, you have to be totally aware
of what is actually happening during the input phase or it's
easy to trip up and get an "Error reading headers" from inside
the ap_get_mime_headers_core() routine.

>> Kevin wrote...
>>
>> Just to satisfy my own curiosity I worked up
>> a module here that is, in fact, able to do what 
>> you want. There don't seem to be any fatal flaws
>> in the input filtering that would prevent it.
>
> Arturo responded...
>
> Great, Can I take a look? That'd be cool.

Sure. Entire filter source code is included below.

The following "mod_transform.c" filter does 
EXACTLY what you are trying to do.

It performs a complete, 100 percent transformation on
an inbound Apache request.

I tested it with your own sample non-encrypted 
transformation input test and it works perfectly fine here.

The code can certainly be consolidated but I used a 
STATE HANDLER to make it CLEAR what is happening when
and why your code was having problems.

I hope this is of some help and if anyone who knows more
than I do about filtering wants to follow up with more
pointers please go for it. We all have things to learn.

Later...
Kevin

COMPLETE SOURCE CODE FOR MOD_TRANSFORM.C

/* mod_transform.c - Completely transform an inbound request
*
* Author: Kevin Kiley - 08/12/07
*
* There are no configuration commands.
*
* To install and run just put mod_transform.so in your server's
* ../modules directory and then add the following to the
* HTTPD config file...
*
* LoadModule transform_module modules/mod_transform.so
*
* The filter will be loaded automatically at runtime.
*
* See the 'YOUR CODE GOES HERE' comment in the TRANSFORMATION
* section to add any custom transformation(s).
*
* This demo assumes that the POST data is NOT encrypted and
* is already a fully formed secondary request. It simply
* "Transforms" the initial request into the secondary one.
*/

#include "httpd.h"
#include "http_connection.h" /* Required for ap_hook_pre_xxxx() references */
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"

/* Globals */

/* The 'trigger string'
* If this string is part of the inbound request line
* then the transformation will take place.
* Case matters.
*/

char trigger_string[] = "HTTP_OPENPGP_DECRYPT";

/* Module related items... */

module AP_MODULE_DECLARE_DATA transform_module ;

#define MOD_TRANSFORM_DEFAULT_BUFFERSIZE 8096

/* The transformation STATES... */

#define MOD_TRANSFORM_STATE_IDLE                  0
#define MOD_TRANSFORM_STATE_GET_REQUEST_LINE      1
#define MOD_TRANSFORM_STATE_GET_REQUEST_HEADERS   2
#define MOD_TRANSFORM_STATE_GET_REQUEST_CONTENT   3
#define MOD_TRANSFORM_STATE_DO_THE_TRANSFORMATION 4
#define MOD_TRANSFORM_STATE_PUT_REQUEST_LINE      5
#define MOD_TRANSFORM_STATE_PUT_REQUEST_HEADERS   6
#define MOD_TRANSFORM_STATE_PUT_REQUEST_CONTENT   7

/* Our own filter CONTEXT data... */

typedef struct transform_ctx_t
{
int            state;
long           content_length;
int            content_length_is_known;

/* A flat buffer for transforming the request... */

unsigned char *content_data_buffer;
long           content_data_buffer_len;
unsigned char *content_data_buffer_ptr;
long           content_data_buffer_bytesleft;

} transform_ctx;

/* The input filter function itself... */

static int transform_input_filter(
ap_filter_t *f,
apr_bucket_brigade *bb,
ap_input_mode_t mode,
apr_read_type_e block,
apr_off_t readbytes)
{
transform_ctx *ctx = f->ctx;
apr_status_t status;
apr_status_t ret;
apr_bucket *b;
apr_bucket *b2;
apr_off_t bytes_to_read = 0;
unsigned char *p1;
unsigned char *p2;
const char *buf = 0;
int return_to_caller = 0;
int transform_this_request = 0;
long p2len;
long p2maxlen;
long bytes_read = 0;
long bytes_left = 0;
long bytes_to_copy = 0;

#define CONTENT_LENGTH_STRING_MAXLEN 90
char  content_length_string[ CONTENT_LENGTH_STRING_MAXLEN + 2 ];

if ( mode == AP_MODE_EATCRLF )
{
/* Not something we are concerned with... */

return ap_get_brigade( f->next, bb, mode, block, readbytes );
}

if ( !ctx )
{
ctx = f->ctx = apr_pcalloc( f->c->pool, sizeof( *ctx ) );

if ( ctx )
{
ctx->state = MOD_TRANSFORM_STATE_GET_REQUEST_LINE;

ctx->content_length = 0;
ctx->content_length_is_known = 0;
ctx->content_data_buffer = 0;
ctx->content_data_buffer_len = 0;
ctx->content_data_buffer_ptr = 0;
ctx->content_data_buffer_bytesleft = 0;
}
else /* Whoops! We didn't get the memory we needed */
{
/* TODO: Log an error message */

return( APR_EGENERAL ); /* Abort */
}

}/* End 'if( !ctx )' */

for ( ;; ) /* Stay in the state handler until it's OK to exit... */
{
bytes_read = 0;
buf = NULL;

/* Process the current state... */

switch( ctx->state )
{
case MOD_TRANSFORM_STATE_IDLE:

/* Do nothing... */
 
                return( ap_get_brigade( f->next, bb, mode, block, readbytes 
));

case MOD_TRANSFORM_STATE_GET_REQUEST_LINE:

ret = ap_get_brigade( f->next, bb, mode, block, readbytes );

if ( ret == APR_SUCCESS )
{
b = APR_BRIGADE_FIRST(bb);

/* Call apr_bucket_read() and get the new request line */

status = (apr_status_t)
apr_bucket_read(b, &buf, &bytes_read, APR_BLOCK_READ);

if ( status != APR_SUCCESS )
{
/* Something weird happened. */
/* Just return to caller... */

return_to_caller = 1;

break;
}

/* If we got some data... process it... */

if ( bytes_read > 0 )
{
/* We only check POST requests for now... */

if ( strnicmp( buf, "POST", 4 ) == 0 )
{
if ( strstr( buf, trigger_string ) )
{
transform_this_request = 1;
}

}/* End 'if( POST )' */

if ( transform_this_request )
{
/* Get all the request headers now... */

ctx->state = MOD_TRANSFORM_STATE_GET_REQUEST_HEADERS;

apr_bucket_delete( b ); /* Discard the bucket */
}
else /* Leave this one alone... */
{
ctx->state = MOD_TRANSFORM_STATE_IDLE;

return_to_caller = 1;
}

}/* End 'if( bytes_read > 0 )' */

}/* End 'if ( apr_get_brigade() call SUCCEEDED )' */

else /* ap_get_brigade() call FAILED... */
{
/* TODO: Log an error */

return_to_caller = 1;
}

break;

case MOD_TRANSFORM_STATE_GET_REQUEST_HEADERS:

/* Stay in AP_MODE_GETLINE until "End Of Header" is seen */

ret = ap_get_brigade( f->next, bb, mode, block, readbytes );

if ( ret == APR_SUCCESS )
{
b = APR_BRIGADE_FIRST(bb);

/* Call apr_bucket_read() and get some data... */

status = (apr_status_t)
apr_bucket_read(b, &buf, &bytes_read, APR_BLOCK_READ);

if ( status != APR_SUCCESS )
{
return_to_caller = 1;
break;
}

/* If we got some data... process it... */

if ( bytes_read > 0 )
{
bytes_left = bytes_read; /* Bytes left to process */

if ( strnicmp( buf, "Content-length:", 15 ) == 0 )
{
/* Get the Content-Length value... */

p1       = (char *) buf;
p1      += 15;
p2       = &content_length_string[0];
p2len    = 0;
p2maxlen = CONTENT_LENGTH_STRING_MAXLEN;

while( *p1!=0 && *p1<33) p1++;
while( *p1!=0 && *p1>32 && p2len<p2maxlen ) {
*p2++ = *p1++; p2len++; }

*p2 = 0; /* Terminate scratch buffer with NULL */

/* Get the actual byte count now... */

ctx->content_length = (long)
atol( content_length_string );

ctx->content_length_is_known = 1;
}
else if ( buf[0]==13 || buf[0]==10 )
{

/* Is there any content data coming? */

if ( !ctx->content_length_is_known )
{
/* TODO: Create a brigade and read
* into it until "Transfer-encoding"
* indicates "End of Data".
*/
}
else if ( ctx->content_length > 0 )
{
/* There is content data coming so record it */

ctx->state = MOD_TRANSFORM_STATE_GET_REQUEST_CONTENT;

/* Allocate a buffer to hold the content data */

ctx->content_data_buffer = malloc( (ctx->content_length+2) );

if ( ctx->content_data_buffer )
{
/* Reset the memory to all NULLS... */

memset( ctx->content_data_buffer, 0, (ctx->content_length+2) );

/* Reset the content data work pointer to */
/* the start of the allocated buffer... */

ctx->content_data_buffer_len       = (long) 0L;
ctx->content_data_buffer_bytesleft = (long) 0L;
ctx->content_data_buffer_ptr       = ctx->content_data_buffer;

}/* End 'if( ctx->content_data_buffer )' */

else /* Memory allocation FAILED... */
{
/* We are not going to be able to */
/* record the data. */

/* Drop into IDLE mode... */

ctx->state = MOD_TRANSFORM_STATE_IDLE;

/* TODO: Log an error */

return APR_EGENERAL;
}

}/* End 'if( ctx->content_length > 0 )' */

}/* End 'else if ( buf[0]==13 || buf[0]==10 )' */

}/* End 'if( bytes_read > 0 )' */

apr_bucket_delete( b ); /* Destroy the bucket */

}/* End 'if ( apr_get_brigade() call SUCCEEDED )' */

else /* ap_get_brigade() call FAILED... */
{
/* TODO: Log an error */

return_to_caller = 1;
}

break;

case MOD_TRANSFORM_STATE_GET_REQUEST_CONTENT:

bytes_to_read =
( ctx->content_length - ctx->content_data_buffer_len );

ret = ap_get_brigade( f->next, bb, AP_MODE_READBYTES,
APR_BLOCK_READ, bytes_to_read );

if ( ret == APR_SUCCESS )
{
b  = APR_BRIGADE_FIRST(bb);

/* Call apr_bucket_read() and get some data... */

status = (apr_status_t)
apr_bucket_read(b, &buf, &bytes_read, APR_BLOCK_READ);

if ( status != APR_SUCCESS )
{
/* Something weird happened. */
/* Just return to caller. */

return_to_caller = 1;
break;
}

/* If we got some data... process it... */

if ( bytes_read > 0 )
{
bytes_left = bytes_read; /* Bytes left to process */

p1 = (char *) buf;

/* Assume ALL of the bytes are needed... */

bytes_to_copy = bytes_left;

/* Check for overflow. Adjust, if necessary... */

if ( ( ctx->content_data_buffer_len + bytes_to_copy ) > ctx->content_length )
{
/* Only copy as many bytes as we need to */
/* reach the actual 'Content-Length:' value... */

bytes_to_copy = ( ctx->content_length - ctx->content_data_buffer_len );
}

/* Copy the data out of the bucket now... */

memcpy( ctx->content_data_buffer_ptr, buf, bytes_to_copy );

/* Advance counters and pointers... */

ctx->content_data_buffer_ptr       += bytes_to_copy;
ctx->content_data_buffer_len       += bytes_to_copy;
ctx->content_data_buffer_bytesleft += bytes_to_copy;

/* Subtract what was copied from the */
/* 'bytes_left' counter... */

bytes_left -= bytes_to_copy;

/* Are we done? */

if ( ctx->content_data_buffer_len >=
ctx->content_length )
{
ctx->state = MOD_TRANSFORM_STATE_DO_THE_TRANSFORMATION;

}/* End 'if ( All Content Data Received )' */

}/* End 'if( bytes_read > 0 )' */

apr_bucket_delete( b ); /* Destroy the bucket */

}/* End 'if ( apr_get_brigade() call SUCCEEDED )' */

else /* ap_get_brigade() call FAILED... */
{
/* TODO: Log an error */

return_to_caller = 1;
}

break;

case MOD_TRANSFORM_STATE_DO_THE_TRANSFORMATION:

/* YOUR CODE GOES HERE!
* Transform the flat data buffer as needed.
*
* This demo assumes that the payload data is
* NOT encoded and is already a fully formed
* secondary request so it just moves on.
*/

/* Start the output phase now... */

ctx->state = MOD_TRANSFORM_STATE_PUT_REQUEST_LINE;

break;

case MOD_TRANSFORM_STATE_PUT_REQUEST_LINE:

/* Send the NEW request line now... */

ctx->content_data_buffer_ptr =
ctx->content_data_buffer;

p2    = ctx->content_data_buffer_ptr;
p2len = 0;

/* Find the end of the new request line */
/* in the transformed request buffer... */

while( *p2!=0 && *p2!=10 ) { p2len++; p2++; }

if ( *p2 == 10 ) p2len++; /* Include LF */

/* Create a heap bucket to hold the new */
/* request line... */

b2 = apr_bucket_heap_create(ctx->content_data_buffer_ptr, p2len, NULL, 
f->c->bucket_alloc);

/* Add the new request line to the brigade... */

APR_BRIGADE_INSERT_TAIL( bb, b2 );

ctx->content_data_buffer_ptr       += p2len;
ctx->content_data_buffer_bytesleft -= p2len;

/* Start sending the headers next... */

ctx->state = MOD_TRANSFORM_STATE_PUT_REQUEST_HEADERS;

return_to_caller = 1;

break;


case MOD_TRANSFORM_STATE_PUT_REQUEST_HEADERS:

p2    = ctx->content_data_buffer_ptr;
p2len = 0;

/* Find the next CRLF in our new data buffer... */

while( *p2!=0 && *p2!=10 ) { p2len++; p2++; }

if ( *p2 == 10 )
{
p2len++; /* Include LF in the data copy */
}
else /* Search terminated on NULL instead of LF... */
{
/* We do NOT increment 'p2len' and we keep the */
/* character that ended the search. */
}

/* Create a new HEAP bucket to hold the header line... */

b2 = apr_bucket_heap_create(ctx->content_data_buffer_ptr, p2len, NULL, 
f->c->bucket_alloc);

if ( b2 )
{
/* If the bucket was created OK then add it to */
/* the end of the brigade... */

APR_BRIGADE_INSERT_TAIL( bb, b2 );
}
else /* apr_bucket_heap_create() FAILED... */
{
/* TODO: Log an error */
}

ctx->content_data_buffer_ptr       += p2len; /* Advance */
ctx->content_data_buffer_bytesleft -= p2len; /* Subtract */

/* Are we done adding headers? */

if ( p2len < 3 ) /* We just added the EOH bucket */
{
/* Advance to the next 'state'... */

ctx->state = MOD_TRANSFORM_STATE_PUT_REQUEST_CONTENT;
}

return APR_SUCCESS; /* Just return to caller now... */

case MOD_TRANSFORM_STATE_PUT_REQUEST_CONTENT:

p2    = ctx->content_data_buffer_ptr;
p2len = ctx->content_data_buffer_bytesleft;

/* CHECK 'readbytes' value against 'p2len' value...
*
* Make sure we don't 'give back' more than the specified
* number of 'readbytes'...
*/

if ( readbytes > 0 ) /* A positive value was passed */
{
if ( p2len > readbytes )
{
p2len = (long) readbytes; /* Equalize */
}
}

/* Create a new HEAP bucket to hold the content data... */

b2 = apr_bucket_heap_create(ctx->content_data_buffer_ptr, p2len, NULL, 
f->c->bucket_alloc);

if ( b2 )
{
/* If the bucket was created OK then add it to */
/* the end of the brigade... */

APR_BRIGADE_INSERT_TAIL( bb, b2 );
}
else /* apr_bucket_heap_create() FAILED... */
{
/* TODO: Log an error */
}

ctx->content_data_buffer_ptr       += p2len; /* Advance */
ctx->content_data_buffer_bytesleft -= p2len; /* Subtract */

if ( ctx->content_data_buffer_bytesleft < 1 )
{
if ( ctx->content_data_buffer )
{
free( ctx->content_data_buffer );       /* Free  memory */
ctx->content_data_buffer     = 0; /* Reset pointer */
ctx->content_data_buffer_ptr = 0; /* Reset pointer */
ctx->content_data_buffer_len = 0; /* Reset length */

}/* End 'if( ctx->content_data_buffer )' */

/* Reset the ctx->content_length value and flag(s)... */

ctx->content_length = 0;
ctx->content_length_is_known = 0;

/* Drop into the IDLE state when done... */

ctx->state = MOD_TRANSFORM_STATE_IDLE;

}/* End 'if ( ctx->content_data_buffer_bytesleft < 1 )' */

/* We can simply return to the caller at this point... */

return APR_SUCCESS;

default: /* Safety catch */

/* TODO: We should never hit here. Log an error. */

return_to_caller = 1; /* Just return to the caller */

break;

}/* End of switch( ctx->state ) */

if ( return_to_caller )
{
break; /* Break out of 'for(;;)' loop and return to caller */
}

}/* End 'for(;;)' loop that runs the state handler */

return APR_SUCCESS;
}

static int transform_pre_conn(conn_rec *c, void *csd)
{
ap_add_input_filter("TRANSFORM_INPUT_FILTER", NULL, NULL, c);

return OK;
}

static void transform_register_hooks(apr_pool_t *p)
{
/* NOTE: SSL is CONNECTION +5 so use +3 */

ap_register_input_filter("TRANSFORM_INPUT_FILTER", transform_input_filter,
NULL, AP_FTYPE_CONNECTION + 3) ;

ap_hook_pre_connection(transform_pre_conn, NULL, NULL, APR_HOOK_MIDDLE);
}

module AP_MODULE_DECLARE_DATA transform_module = {
STANDARD20_MODULE_STUFF,
NULL,
NULL,
NULL,
NULL,
NULL,
transform_register_hooks
};

/* END OF FILE */



************************************** Get a sneak peek of the all-new AOL at 
http://discover.aol.com/memed/aolcom30tour

Mime
View raw message