Project

General

Profile

Feature #1936 » mod_lvhost.c

hazzadous, 2009-03-22 14:10

 
/*
*
* mod_lvhost.c
* Copyright (c) 2008, Stephane Camberlin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of the 'incremental' nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (
* INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*
*/

#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <assert.h>

#include "base.h"
#include "log.h"
#include "buffer.h"
#include "stat_cache.h"

#include "plugin.h"

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <ldap.h>
#include <lber.h>

#define DEBUG 1
#define CACHE_LIFETIME 3600

#define PATCH_OPTION(x) \
p->conf.x = s->x;

/**
* Common lvhost related data.
*
* Configuration shared by all handled requests within a config_context.
*/
typedef struct
{
LDAP *ldap_c; /** LDAP session handle used for all queries. */
buffer *ldap_uri; /** URI in form that openldap library expects it. */
unsigned short ldap_starttls;
buffer *sasl_user; /** SASL auth user ( = authz user). */
buffer *sasl_pass;
buffer *sasl_realm;
buffer *sasl_mech;
buffer *ldap_binddn; /** If set, use simple binds. */
buffer *ldap_password; /** Simple bind password. */
buffer *ldap_base; /** The search root. Scope is sub */

/** The filter used to search for document root,
* given an input of server name.
* Default: (&(objectClass=vhost)(serverName=?))
*/
buffer *ldap_filter;

/** The attribute representing docroot.
* Default: documentRoot
*/
buffer *ldap_attribute;

unsigned short ldap_persist; /** If use, use refreshAndPersist */
buffer *ldap_pre;
buffer *ldap_post;
int free_me;

} lvhost_plugin_config;

/* cache struct. Maybe use lightys arrays later */
typedef struct {

buffer* server_name;
buffer* docroot;
time_t birth;

} doc_cache_t;

typedef struct {

PLUGIN_DATA;
buffer *docroot;
buffer *tmp_buf;
doc_cache_t **doc_cache;
unsigned int cache_size;

lvhost_plugin_config **config_storage;
lvhost_plugin_config conf;

} plugin_data;

INIT_FUNC(mod_lvhost_init) {

plugin_data *p;

p = (plugin_data*)malloc( sizeof(*p) );

if( p )
{
p->tmp_buf = buffer_init();
p->docroot = buffer_init();
p->doc_cache = malloc( 0 );
p->cache_size = 0;
}

return p;
}

FREE_FUNC(mod_lvhost_cleanup) {

plugin_data *p = p_d;

UNUSED(srv);

if (!p) return HANDLER_GO_ON;

if (p->config_storage) {
size_t i, j;

for (i = 0; i < srv->config_context->used; i++)
{
lvhost_plugin_config *s = p->config_storage[i];
if (!s) continue;
if (s->free_me) ldap_unbind_ext(s->ldap_c, NULL, NULL);

/* Free all the buffers for this config_context */
buffer *bufs[] = { s->ldap_uri, s->sasl_user,
s->sasl_pass, s->sasl_mech,
s->sasl_realm, s->ldap_binddn,
s->ldap_password, s->ldap_base,
s->ldap_filter, s->ldap_attribute,
s->ldap_pre, s->ldap_post };

for(j = 0; j * sizeof(buffer*) < sizeof(bufs); j++)
buffer_free(bufs[j]);
free(s);
}
free(p->config_storage);
}

buffer_free(p->docroot);
free(p->doc_cache);

free(p);

return HANDLER_GO_ON;
}

SETDEFAULTS_FUNC(mod_lvhost_set_defaults) {

plugin_data *p = p_d;
size_t i, j, k;
int err;
struct berval cred;

int ldap_version = LDAP_VERSION3;

/*
* List of config values to be read from the lighttpd configfile.
* Format is { confstring, destination memory, type, scope }
* Please place T_CONFIG_STRINGs at top.
*/
config_values_t cv[] = {
{ "lvhost.uri", NULL,
T_CONFIG_STRING,
T_CONFIG_SCOPE_SERVER },

{ "lvhost.sasl_mech", NULL,
T_CONFIG_STRING,
T_CONFIG_SCOPE_SERVER },

{ "lvhost.sasl_user", NULL,
T_CONFIG_STRING,
T_CONFIG_SCOPE_SERVER },

{ "lvhost.sasl_realm", NULL,
T_CONFIG_STRING,
T_CONFIG_SCOPE_SERVER },

{ "lvhost.sasl_pass", NULL,
T_CONFIG_STRING,
T_CONFIG_SCOPE_SERVER },

{ "lvhost.binddn", NULL,
T_CONFIG_STRING,
T_CONFIG_SCOPE_SERVER },

{ "lvhost.password", NULL,
T_CONFIG_STRING,
T_CONFIG_SCOPE_SERVER },

{ "lvhost.base", NULL,
T_CONFIG_STRING,
T_CONFIG_SCOPE_SERVER },

{ "lvhost.filter", NULL,
T_CONFIG_STRING,
T_CONFIG_SCOPE_SERVER },

{ "lvhost.attribute", NULL,
T_CONFIG_STRING,
T_CONFIG_SCOPE_SERVER },

{ "lvhost.starttls", NULL,
T_CONFIG_BOOLEAN,
T_CONFIG_SCOPE_SERVER },

{ "lvhost.persistent", NULL,
T_CONFIG_BOOLEAN,
T_CONFIG_SCOPE_SERVER },

{ NULL, NULL,
T_CONFIG_UNSET,
T_CONFIG_SCOPE_UNSET }

};
if ( !p ) return HANDLER_ERROR;
/*
* Allocate memory for pointers to instances of lvhost_plugin_config
* that will be created one per config_context.
*/
p->config_storage =
(lvhost_plugin_config**)malloc( srv->config_context->used
* sizeof(lvhost_plugin_config*) );

if( !(p->config_storage) ) return HANDLER_ERROR;

/*
* If we managed to allocate space for the config points,
* continue with initiate these pointers.
*/
for (i = 0; i < srv->config_context->used; i++)
{
buffer *myfilter = buffer_init();
char *qmark = 0;
/* The lvhost plugin config for the i-th config_context */

lvhost_plugin_config *s;
s = (lvhost_plugin_config *)malloc( sizeof(*s) );
if( !s ) return HANDLER_ERROR;

/* Initialize all the buffers for this config_context */
buffer **bufs[] = { &s->ldap_uri, &s->sasl_mech, &s->sasl_user,
&s->sasl_realm, &s->sasl_pass, &s->ldap_binddn,
&s->ldap_password, &s->ldap_base, &s->ldap_filter,
&s->ldap_attribute };

/* Init these separately */
s->ldap_pre = buffer_init();
s->ldap_post = buffer_init();

for(j = 0; j * sizeof(buffer**) < sizeof(bufs); j++)
{
#if DEBUG==2
log_error_write( srv, __FILE__, __LINE__, "ss",
"Initializing buffer for", cv[j].key );
#endif
*bufs[j] = buffer_init();
}

/*
* Point the destinations of cv to our config storage to
* be filled by config_insert_values_global. dests must be the same
* order as they are in cv, so make sure bufs and bools are correct
*/
unsigned short *bools[] = { &s->ldap_starttls, &s->ldap_persist };
void** dests = (void**)malloc( sizeof(bufs) + sizeof(bools) );
if(!dests) return HANDLER_ERROR;

/* Append bools to the buffers */
for(j = 0; j * sizeof(buffer**) < sizeof(bufs); j++)
dests[j] = *bufs[j];
memcpy( dests + j, bools, sizeof(bools) );

/* nDests should be the same as the length of cv - 1 */
size_t nDests = sizeof(cv) / sizeof(config_values_t) - 1;
assert( (nDests * sizeof(void*)) == (sizeof(bools) + sizeof(bufs)) );

for(j = 0; cv[j].key; j++)
{
#if DEBUG==2
log_error_write( srv, __FILE__, __LINE__, "ss",
"Setting destination for", cv[j].key );
#endif
assert( dests[j] );
cv[j].destination = dests[j];
}

p->config_storage[i] = s;

if (config_insert_values_global(srv,
((data_config *)srv->config_context->data[i])->value, cv))
{
return HANDLER_ERROR;
}

#if DEBUG==2
/* Print what we got from lighttpd config. */
for(j = 0; cv[j].key; j++)
{
switch( cv[j].type )
{
case T_CONFIG_STRING:
log_error_write( srv, __FILE__, __LINE__, "ssb",
"lvhost config: ", cv[j].key,
(buffer*)dests[j] );
break;

case T_CONFIG_BOOLEAN:
log_error_write( srv, __FILE__, __LINE__, "ssd",
"lvhost config: ", cv[j].key,
*((unsigned short*)cv[j].destination) );
break;
default:
log_error_write( srv, __FILE__, __LINE__, "ssd",
"lvhost config: ", cv[j].key,
*((int*)cv[j].destination) );
};
}
#endif /* DEBUG */

/*
* Initiate default values for certain buffers
*/

/**
* Structure containing a buffer - stringi default pairing
*/
typedef struct { buffer* b; const char* d; } buffer_default_t;
buffer_default_t defaults[] =
{
{ s->ldap_filter, "(&(objectClass=vhost)(serverName=?))" },
{ s->ldap_attribute, "documentRoot" },
{ s->sasl_mech, "PLAIN" }
};

for(j = 0; j * sizeof(buffer_default_t) < sizeof(defaults); j++)
if( buffer_is_empty(defaults[j].b) )
buffer_copy_string( defaults[j].b, defaults[j].d );

/*
* Check that we have none empty buffers on
* the following variables.
*/
buffer* required_buffers[] = { s->ldap_uri, s->ldap_base,
s->ldap_filter, s->ldap_attribute };

for(j = 0; j * sizeof(buffer*) < sizeof(required_buffers); j++)
{
/* Get the index of required_buffers, as seen in cv */
for( k = 0; k < nDests; k++ )
if( required_buffers[j] == dests[k] ) break;

/* Should have gotten a match */
assert( k < nDests );

if( buffer_is_empty(required_buffers[j]) )
{
log_error_write( srv, __FILE__, __LINE__, "ss",
"Missing required option:", cv[k].key );
/* TODO: readd this line maybe
* I don't seem to understand config_contexts
* yet to leave this out
* return HANDLER_ERROR;
*/

return HANDLER_GO_ON;
}
}

/*
* Next we split the ldap_filter string in two
* at the contained question mark if it has one,
* and put the two halfs in ldap_pre and ldap_post maybe
*/
buffer_copy_string_buffer(myfilter, s->ldap_filter);

if( (qmark = index(myfilter->ptr, '?')) )
{
*qmark = '\0';
buffer_copy_string(s->ldap_pre, myfilter->ptr);
buffer_copy_string(s->ldap_post, qmark+1);
}
else
{
buffer_copy_string_buffer(s->ldap_pre, myfilter);
}
buffer_free(myfilter);
s->free_me = 0;

/*
* Now we setup the ldap session to be referenced by ldap_c
*/
if ((err = ldap_initialize( &s->ldap_c,
s->ldap_uri->ptr )) != LDAP_SUCCESS) {
log_error_write( srv, __FILE__, __LINE__, "ss",
"ldap_initialize() failed, exiting... :",
ldap_err2string(err) );
return HANDLER_ERROR;
}
s->free_me = 1;

/* Start TLS if requested */
if( s->ldap_starttls
&& ( err = ldap_start_tls_s( s->ldap_c, NULL, NULL ) ) )
{
log_error_write( srv, __FILE__, __LINE__, "ss",
"ldap_start_tls_s() failed, exiting... :",
ldap_err2string(err) );
return HANDLER_ERROR;
}

ldap_set_option( s->ldap_c, LDAP_OPT_PROTOCOL_VERSION, &ldap_version );

/*
* If binddn was given, assume we want a simple bind.
* For now let us perform all ldap operations syncronously.
*/
if( !buffer_is_empty(s->ldap_binddn) )
{
if ((err = ldap_simple_bind_s( s->ldap_c,
s->ldap_binddn->ptr,
s->ldap_password->ptr)) != LDAP_SUCCESS)
{
log_error_write( srv, __FILE__, __LINE__, "ss",
"ldap_simple_bind_s() failed, exiting... :",
ldap_err2string(err) );
return HANDLER_ERROR;
}

/* We were successful. No need to do anything else */
return HANDLER_GO_ON;
}

if( !buffer_is_empty(s->sasl_pass) )
{
cred.bv_val = strdup(s->sasl_pass->ptr);
cred.bv_len = strlen(s->sasl_pass->ptr);
} else {
cred.bv_val = NULL;
cred.bv_len = 0;
}
if ((err = ldap_sasl_bind_s( s->ldap_c,
s->sasl_user->ptr,
s->sasl_mech->ptr,
&cred, NULL, NULL, NULL )) != LDAP_SUCCESS)
{
log_error_write( srv, __FILE__, __LINE__, "ss",
"ldap_sasl_bind_s() failed, exiting... :",
ldap_err2string(err) );
return HANDLER_ERROR;
}
}

return HANDLER_GO_ON;
}

static int mod_lvhost_patch_connection( server *srv,
connection *con,
plugin_data *p )
{
size_t i;
lvhost_plugin_config *s = p->config_storage[0];

PATCH_OPTION(ldap_base);
PATCH_OPTION(ldap_attribute);
PATCH_OPTION(ldap_pre);
PATCH_OPTION(ldap_post);
PATCH_OPTION(ldap_c);

for (i = 1; i < srv->config_context->used; i++)
{
data_config *dc = (data_config *)srv->config_context->data[i];
s = p->config_storage[i];

if (!config_check_cond(srv, con, dc)) continue;

PATCH_OPTION(ldap_base);
PATCH_OPTION(ldap_attribute);
PATCH_OPTION(ldap_pre);
PATCH_OPTION(ldap_post);
PATCH_OPTION(ldap_c);
}

return 0;
}

CONNECTION_FUNC(mod_lvhost_handle_docroot)
{
plugin_data *p = p_d;

int n = 0;
size_t i;
LDAPMessage *msg, *entry;
struct berval **vals;
int ldap_scope = LDAP_SCOPE_SUBTREE;

/* Fold if no host specified */
if (!con->uri.authority->used)
{
log_error_write( srv, __FILE__, __LINE__,
"s", "Exiting: no host header." );
return HANDLER_GO_ON;
}

/* Find the requested server name in cache */
doc_cache_t *cache_entry = 0;

#if DEBUG==2
log_error_write( srv, __FILE__, __LINE__, "sd",
"Checking cache of size", p->cache_size );
#endif

for(i = 0; i < p->cache_size; i++)
{
if(buffer_is_equal(p->doc_cache[i]->server_name, con->uri.authority))
{
cache_entry = p->doc_cache[i]; n=1;
break;
}
}

if( cache_entry && ( (time(0) - cache_entry->birth ) < CACHE_LIFETIME ) )
{
log_error_write( srv, __FILE__, __LINE__, "sb",
"Using cache for", cache_entry->server_name );
buffer_copy_string_buffer(con->server_name, cache_entry->server_name);
buffer_copy_string_buffer(con->physical.doc_root, cache_entry->docroot);
return HANDLER_GO_ON;
}

mod_lvhost_patch_connection(srv, con, p);

/* Perhaps ldap_c has become invalid? Create a new session? */
if (!p->conf.ldap_c)
{
log_error_write( srv, __FILE__, __LINE__, "s",
"Exiting: no ldap handler..." );
return HANDLER_GO_ON;
}
buffer_copy_string_buffer(p->tmp_buf, p->conf.ldap_pre);
if (p->conf.ldap_post->used)
{
buffer_append_string_buffer(p->tmp_buf, con->uri.authority);
buffer_append_string_buffer(p->tmp_buf, p->conf.ldap_post);
}

if (ldap_search_ext_s( p->conf.ldap_c, p->conf.ldap_base->ptr,
ldap_scope, p->tmp_buf->ptr, NULL,
0, NULL, NULL, NULL, 0, &msg ) != LDAP_SUCCESS)
{
log_error_write( srv, __FILE__, __LINE__, "sb",
"Exiting: ldap_search_ext_s failed:", p->tmp_buf);
}
else
{
/* If search successful, update the cache. */
n = ldap_count_entries(p->conf.ldap_c, msg);
if( !n ) return HANDLER_GO_ON;
if( n>1 )
{
log_error_write( srv, __FILE__, __LINE__, "sbs",
"Duplicate entry for filter:", p->tmp_buf,
": Just using the first" );
}

entry = ldap_first_entry(p->conf.ldap_c, msg);

vals = ldap_get_values_len( p->conf.ldap_c, entry,
p->conf.ldap_attribute->ptr );

if( vals )
{
stat_cache_entry *sce;
buffer_copy_string(p->docroot, vals[0]->bv_val);

if (HANDLER_ERROR
== stat_cache_get_entry(srv, con, p->docroot, &sce))
{
log_error_write( srv, __FILE__, __LINE__, "sb",
strerror(errno), p->docroot );
return HANDLER_GO_ON;
}

if (!S_ISDIR(sce->st.st_mode))
{
log_error_write( srv, __FILE__, __LINE__, "sb",
"Not a directory", p->docroot );
return HANDLER_GO_ON;
}


if( !cache_entry )
{
cache_entry = malloc( sizeof(doc_cache_t) );
if(!cache_entry) return HANDLER_ERROR;

cache_entry->server_name = buffer_init();
cache_entry->docroot = buffer_init();

buffer_copy_string_buffer( cache_entry->server_name,
con->uri.authority );

p->doc_cache = realloc( p->doc_cache,
(p->cache_size + 1)
* sizeof(doc_cache_t*) );

if(!p->doc_cache) return HANDLER_ERROR;

p->doc_cache[p->cache_size] = cache_entry;
p->cache_size = p->cache_size + 1;

#if DEBUG==2
log_error_write( srv, __FILE__, __LINE__, "sd",
"Adding doc root to cache, new size:",
p->cache_size );
#endif

}

cache_entry->birth = time(0);
buffer_copy_string_buffer(cache_entry->docroot, p->docroot);
}
else
{
/* We should remove the cache perhaps? Leave it for now. */
}

ldap_value_free_len(vals);
ldap_msgfree(msg);
}

/* If we haven't gotten cache_entry by now, we are stumped. */
if( !cache_entry ) return HANDLER_GO_ON;

buffer_copy_string_buffer(con->server_name, cache_entry->server_name);
buffer_copy_string_buffer(con->physical.doc_root, cache_entry->docroot);

#ifdef DEBUG
log_error_write( srv, __FILE__, __LINE__, "sb",
"Server name:", con->server_name );
log_error_write( srv, __FILE__, __LINE__, "sb",
"Document root:", con->physical.doc_root );
#endif

return HANDLER_GO_ON;
}

int mod_lvhost_plugin_init(plugin *p) {

p->version = LIGHTTPD_VERSION_ID;
p->name = buffer_init_string("lvhost");
p->init = mod_lvhost_init;
p->cleanup = mod_lvhost_cleanup;
p->set_defaults = mod_lvhost_set_defaults;
p->handle_docroot = mod_lvhost_handle_docroot;

p->data = NULL;

return 0;

}

(2-2/2)