modmellon/auth_mellon_cache.c

583 lines
17 KiB
C

/*
*
* auth_mellon_cache.c: an authentication apache module
* Copyright © 2003-2007 UNINETT (http://www.uninett.no/)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "auth_mellon.h"
/* This function locks the session table and locates a session entry.
* Unlocks the table and returns NULL if the entry wasn't found.
* If a entry was found, then you _must_ unlock it with am_cache_unlock
* after you are done with it.
*
* Parameters:
* server_rec *s The current server.
* const char *key The session key or user
* am_cache_key_t type AM_CACHE_SESSION or AM_CACHE_NAMEID
*
* Returns:
* The session entry on success or NULL on failure.
*/
am_cache_entry_t *am_cache_lock(server_rec *s,
am_cache_key_t type,
const char *key)
{
am_mod_cfg_rec *mod_cfg;
am_cache_entry_t *table;
int i;
int rv;
char buffer[512];
/* Check if we have a valid session key. We abort if we don't. */
if (key == NULL)
return NULL;
switch (type) {
case AM_CACHE_SESSION:
if (strlen(key) != AM_ID_LENGTH)
return NULL;
break;
case AM_CACHE_NAMEID:
if (strlen(key) > AM_CACHE_MAX_LASSO_IDENTITY_SIZE)
return NULL;
break;
default:
return NULL;
break;
}
mod_cfg = am_get_mod_cfg(s);
/* Lock the table. */
if((rv = apr_global_mutex_lock(mod_cfg->lock)) != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
"apr_global_mutex_lock() failed [%d]: %s",
rv, apr_strerror(rv, buffer, sizeof(buffer)));
return NULL;
}
table = apr_shm_baseaddr_get(mod_cfg->cache);
for(i = 0; i < mod_cfg->init_cache_size; i++) {
const char *tablekey;
switch (type) {
case AM_CACHE_SESSION:
tablekey = table[i].key;
break;
case AM_CACHE_NAMEID:
/* tablekey may be NULL */
tablekey = am_cache_env_fetch_first(&table[i], "NAME_ID");
break;
default:
tablekey = NULL;
break;
}
if (tablekey == NULL)
continue;
if(strcmp(tablekey, key) == 0) {
/* We found the entry. */
if(table[i].expires > apr_time_now()) {
/* And it hasn't expired. */
return &table[i];
}
}
}
/* We didn't find a entry matching the key. Unlock the table and
* return NULL;
*/
apr_global_mutex_unlock(mod_cfg->lock);
return NULL;
}
/* This function locks the session table and creates a new session entry.
* It will first attempt to locate a free session. If it doesn't find a
* free session, then it will take the least recentry used session.
*
* Remember to unlock the table with am_cache_unlock(...) afterwards.
*
* Parameters:
* server_rec *s The current server.
* const char *key The key of the session to allocate.
*
* Returns:
* The new session entry on success. NULL if key is a invalid session
* key.
*/
am_cache_entry_t *am_cache_new(server_rec *s, const char *key)
{
am_cache_entry_t *t;
am_mod_cfg_rec *mod_cfg;
am_cache_entry_t *table;
apr_time_t current_time;
int i;
apr_time_t age;
int rv;
char buffer[512];
/* Check if we have a valid session key. We abort if we don't. */
if(key == NULL || strlen(key) != AM_ID_LENGTH) {
return NULL;
}
mod_cfg = am_get_mod_cfg(s);
/* Lock the table. */
if((rv = apr_global_mutex_lock(mod_cfg->lock)) != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
"apr_global_mutex_lock() failed [%d]: %s",
rv, apr_strerror(rv, buffer, sizeof(buffer)));
return NULL;
}
table = apr_shm_baseaddr_get(mod_cfg->cache);
/* Get current time. If we find a entry with expires <= the current
* time, then we can use it.
*/
current_time = apr_time_now();
/* We will use 't' to remember the best/oldest entry. We
* initalize it to the first entry in the table to simplify the
* following code (saves test for t == NULL).
*/
t = &table[0];
/* Iterate over the session table. Update 't' to match the "best"
* entry (the least recently used). 't' will point a free entry
* if we find one. Otherwise, 't' will point to the least recently
* used entry.
*/
for(i = 0; i < mod_cfg->init_cache_size; i++) {
if(table[i].key[0] == '\0') {
/* This entry is free. Update 't' to this entry
* and exit loop.
*/
t = &table[i];
break;
}
if(table[i].expires <= current_time) {
/* This entry is expired, and is therefore free.
* Update 't' and exit loop.
*/
t = &table[i];
break;
}
if(table[i].access < t->access) {
/* This entry is older than 't' - update 't'. */
t = &table[i];
}
}
if(t->key[0] != '\0' && t->expires > current_time) {
/* We dropped a LRU entry. Calculate the age in seconds. */
age = (current_time - t->access) / 1000000;
if(age < 3600) {
ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s,
"Dropping LRU entry entry with age = %" APR_TIME_T_FMT
"s, which is less than one hour. It may be a good"
" idea to increase MellonCacheSize.",
age);
}
}
/* Now 't' points to the entry we are going to use. We initialize
* it and returns it.
*/
strcpy(t->key, key);
/* Far far into the future. */
t->expires = 0x7fffffffffffffffLL;
t->logged_in = 0;
t->size = 0;
t->user[0] = '\0';
t->lasso_identity[0] = '\0';
t->lasso_session[0] = '\0';
return t;
}
/* This function unlocks a session entry.
*
* Parameters:
* server_rec *s The current server.
* am_cache_entry_t *entry The session entry.
*
* Returns:
* Nothing.
*/
void am_cache_unlock(server_rec *s, am_cache_entry_t *entry)
{
am_mod_cfg_rec *mod_cfg;
/* Update access time. */
entry->access = apr_time_now();
mod_cfg = am_get_mod_cfg(s);
apr_global_mutex_unlock(mod_cfg->lock);
}
/* This function updates the expire-timestamp of a session, if the new
* timestamp is earlier than the previous.
*
* Parameters:
* am_cache_entry_t *t The current session.
* apr_time_t expires The new timestamp.
*
* Returns:
* Nothing.
*/
void am_cache_update_expires(am_cache_entry_t *t, apr_time_t expires)
{
/* Check if we should update the expires timestamp. */
if(t->expires == 0 || t->expires > expires) {
t->expires = expires;
}
}
/* This function appends a name-value pair to a session. It is possible to
* store several values with the same name. This is the method used to store
* multivalued fields.
*
* Parameters:
* am_cache_entry_t *t The current session.
* const char *var The name of the value to be stored.
* const char *val The value which should be stored in the session.
*
* Returns:
* OK on success or HTTP_INTERNAL_SERVER_ERROR on failure.
*/
int am_cache_env_append(am_cache_entry_t *t,
const char *var, const char *val)
{
/* Make sure that the name and value will fit inside the
* fixed size buffer.
*/
if(strlen(val) >= AM_CACHE_VALSIZE ||
strlen(var) >= AM_CACHE_VARSIZE) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
"Unable to store session data because it is to big. "
"Name = \"%s\"; Value = \"%s\".", var, val);
return HTTP_INTERNAL_SERVER_ERROR;
}
if(t->size >= AM_CACHE_ENVSIZE) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
"Unable to store attribute value because we have"
" reached the maximum number of name-value pairs for"
" this session.");
return HTTP_INTERNAL_SERVER_ERROR;
}
strcpy(t->env[t->size].varname, var);
strcpy(t->env[t->size].value, val);
t->size++;
return OK;
}
/* This function fetches a value from a session.
* If multiple values are available, the first one is returned.
*
* Parameters:
* am_cache_entry_t *t The current session.
* const char *var The name of the value to be stored.
*
* Returns:
* The first value, NULL if it does not exist.
*/
const char *am_cache_env_fetch_first(am_cache_entry_t *t,
const char *var)
{
int i;
for (i = 0; t->size; i++) {
if (strcmp(t->env[i].varname, var) == 0)
return t->env[i].value;
}
return NULL;
}
/* This function populates the subprocess environment with data received
* from the IdP.
*
* Parameters:
* request_rec *r The request we should add the data to.
* am_cache_entry_t *t The session data.
*
* Returns:
* Nothing.
*/
void am_cache_env_populate(request_rec *r, am_cache_entry_t *t)
{
am_dir_cfg_rec *d;
int i;
apr_hash_t *counters;
const char *varname;
const char *env_varname;
const char *value;
int *count;
d = am_get_dir_cfg(r);
/* Check if the user attribute has been set, and set it if it
* hasn't been set. */
if(t->user[0] == '\0') {
for(i = 0; i < t->size; ++i) {
if(strcmp(t->env[i].varname, d->userattr) == 0) {
strcpy(t->user, t->env[i].value);
}
}
}
/* Allocate a set of counters for duplicate variables in the list. */
counters = apr_hash_make(r->pool);
/* Populate the subprocess environment with the attributes we
* received from the IdP.
*/
for(i = 0; i < t->size; ++i) {
varname = t->env[i].varname;
/* Check if we should map this name into another name. */
env_varname = (const char*)apr_hash_get(
d->envattr, varname, APR_HASH_KEY_STRING);
if(env_varname != NULL) {
varname = env_varname;
}
value = t->env[i].value;
/*
* If we find a variable remapping to MellonUser, use it.
*/
if ((t->user[0] == '\0') && (strcmp(varname, d->userattr) == 0))
strcpy(t->user, value);
/* Find the number of times this variable has been set. */
count = apr_hash_get(counters, varname, APR_HASH_KEY_STRING);
if(count == NULL) {
/* This is the first time. Create a counter for this variable. */
count = apr_palloc(r->pool, sizeof(int));
*count = 0;
apr_hash_set(counters, varname, APR_HASH_KEY_STRING, count);
/* Add the variable without a suffix. */
apr_table_set(r->subprocess_env,
apr_pstrcat(r->pool, "MELLON_", varname, NULL),
value);
}
/* Add the variable with a suffix indicating how many times it has
* been added before.
*/
apr_table_set(r->subprocess_env,
apr_psprintf(r->pool, "MELLON_%s_%d", varname, *count),
value);
/* Increase the count. */
++(*count);
}
if(t->user[0] != '\0') {
/* We have a user-"name". Set r->user and r->ap_auth_type. */
r->user = apr_pstrdup(r->pool, t->user);
r->ap_auth_type = apr_pstrdup(r->pool, "Mellon");
} else {
/* We don't have a user-"name". Log error. */
ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r,
"Didn't find the attribute \"%s\" in the attributes"
" which were received from the IdP. Cannot set a user"
" for this request without a valid user attribute.",
d->userattr);
}
/* Populate with the session? */
if (d->dump_session) {
char *session;
int srclen, dstlen;
srclen = strlen(t->lasso_session);
dstlen = apr_base64_encode_len(srclen);
session = apr_palloc(r->pool, dstlen);
(void)apr_base64_encode(session, t->lasso_session, srclen);
apr_table_set(r->subprocess_env, "MELLON_SESSION", session);
}
if (d->dump_saml_response)
apr_table_set(r->subprocess_env,
"MELLON_SAML_RESPONSE",
t->lasso_saml_response);
}
/* This function deletes a given key from the session store.
*
* Parameters:
* server_rec *s The current server.
* am_cache_entry_t *cache The entry we are deleting.
*
* Returns:
* Nothing.
*/
void am_cache_delete(server_rec *s, am_cache_entry_t *cache)
{
/* We write a null-byte at the beginning of the key to
* mark this slot as unused.
*/
cache->key[0] = '\0';
/* Unlock the entry. */
am_cache_unlock(s, cache);
}
/* This function stores a lasso identity dump and a lasso session dump in
* the given session object.
*
* Parameters:
* am_cache_entry_t *session The session object.
* const char *lasso_identity The identity dump.
* const char *lasso_session The session dump.
*
* Returns:
* OK on success or HTTP_INTERNAL_SERVER_ERROR if the lasso state information
* is to big to fit in our session.
*/
int am_cache_set_lasso_state(am_cache_entry_t *session,
const char *lasso_identity,
const char *lasso_session,
const char *lasso_saml_response)
{
if(lasso_identity != NULL) {
if(strlen(lasso_identity) < AM_CACHE_MAX_LASSO_IDENTITY_SIZE) {
strcpy(session->lasso_identity, lasso_identity);
} else {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
"Lasso identity is to big for storage. Size of lasso"
" identity is %" APR_SIZE_T_FMT ", max size is %"
APR_SIZE_T_FMT ".",
(apr_size_t)strlen(lasso_identity),
(apr_size_t)AM_CACHE_MAX_LASSO_IDENTITY_SIZE - 1);
return HTTP_INTERNAL_SERVER_ERROR;
}
} else {
/* No identity dump to save. */
strcpy(session->lasso_identity, "");
}
if(lasso_session != NULL) {
if(strlen(lasso_session) < AM_CACHE_MAX_LASSO_SESSION_SIZE) {
strcpy(session->lasso_session, lasso_session);
} else {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
"Lasso session is to big for storage. Size of lasso"
" session is %" APR_SIZE_T_FMT ", max size is %"
APR_SIZE_T_FMT ".",
(apr_size_t)strlen(lasso_session),
(apr_size_t)AM_CACHE_MAX_LASSO_SESSION_SIZE - 1);
return HTTP_INTERNAL_SERVER_ERROR;
}
} else {
/* No session dump to save. */
strcpy(session->lasso_session, "");
}
if(lasso_saml_response != NULL) {
if(strlen(lasso_saml_response) < AM_CACHE_MAX_LASSO_SAML_RESPONSE_SIZE) {
strcpy(session->lasso_saml_response, lasso_saml_response);
} else {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
"Lasso SAML response is to big for storage. "
"Size of lasso session is %" APR_SIZE_T_FMT
", max size is %" APR_SIZE_T_FMT ".",
(apr_size_t)strlen(lasso_saml_response),
(apr_size_t)AM_CACHE_MAX_LASSO_SAML_RESPONSE_SIZE - 1);
return HTTP_INTERNAL_SERVER_ERROR;
}
} else {
/* No session dump to save. */
strcpy(session->lasso_saml_response, "");
}
return OK;
}
/* This function retrieves a lasso identity dump from the session object.
*
* Parameters:
* am_cache_entry_t *session The session object.
*
* Returns:
* The identity dump, or NULL if we don't have a session dump.
*/
const char *am_cache_get_lasso_identity(am_cache_entry_t *session)
{
if(strlen(session->lasso_identity) == 0) {
return NULL;
}
return session->lasso_identity;
}
/* This function retrieves a lasso session dump from the session object.
*
* Parameters:
* am_cache_entry_t *session The session object.
*
* Returns:
* The session dump, or NULL if we don't have a session dump.
*/
const char *am_cache_get_lasso_session(am_cache_entry_t *session)
{
if(strlen(session->lasso_session) == 0) {
return NULL;
}
return session->lasso_session;
}