1/* Licensed to the Apache Software Foundation (ASF) under one or more 2 * contributor license agreements. See the NOTICE file distributed with 3 * this work for additional information regarding copyright ownership. 4 * The ASF licenses this file to You under the Apache License, Version 2.0 5 * (the "License"); you may not use this file except in compliance with 6 * the License. You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#include "apr_strings.h" 18#include "apr_md5.h" /* for apr_password_validate */ 19 20#include "ap_config.h" 21#include "ap_provider.h" 22#include "httpd.h" 23#include "http_config.h" 24#include "http_core.h" 25#include "http_log.h" 26#include "http_protocol.h" 27#include "http_request.h" 28 29#include "mod_auth.h" 30 31#include "ap_socache.h" 32#include "util_mutex.h" 33#include "apr_optional.h" 34 35module AP_MODULE_DECLARE_DATA authn_socache_module; 36 37typedef struct authn_cache_dircfg { 38 apr_interval_time_t timeout; 39 apr_array_header_t *providers; 40 const char *context; 41} authn_cache_dircfg; 42 43/* FIXME: 44 * I think the cache and mutex should be global 45 */ 46static apr_global_mutex_t *authn_cache_mutex = NULL; 47static ap_socache_provider_t *socache_provider = NULL; 48static ap_socache_instance_t *socache_instance = NULL; 49static const char *const authn_cache_id = "authn-socache"; 50static int configured; 51 52static apr_status_t remove_lock(void *data) 53{ 54 if (authn_cache_mutex) { 55 apr_global_mutex_destroy(authn_cache_mutex); 56 authn_cache_mutex = NULL; 57 } 58 return APR_SUCCESS; 59} 60static apr_status_t destroy_cache(void *data) 61{ 62 if (socache_instance) { 63 socache_provider->destroy(socache_instance, (server_rec*)data); 64 socache_instance = NULL; 65 } 66 return APR_SUCCESS; 67} 68 69 70static int authn_cache_precfg(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptmp) 71{ 72 apr_status_t rv = ap_mutex_register(pconf, authn_cache_id, 73 NULL, APR_LOCK_DEFAULT, 0); 74 if (rv != APR_SUCCESS) { 75 ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(01673) 76 "failed to register %s mutex", authn_cache_id); 77 return 500; /* An HTTP status would be a misnomer! */ 78 } 79 socache_provider = ap_lookup_provider(AP_SOCACHE_PROVIDER_GROUP, 80 AP_SOCACHE_DEFAULT_PROVIDER, 81 AP_SOCACHE_PROVIDER_VERSION); 82 configured = 0; 83 return OK; 84} 85static int authn_cache_post_config(apr_pool_t *pconf, apr_pool_t *plog, 86 apr_pool_t *ptmp, server_rec *s) 87{ 88 apr_status_t rv; 89 static struct ap_socache_hints authn_cache_hints = {64, 32, 60000000}; 90 91 if (!configured) { 92 return OK; /* don't waste the overhead of creating mutex & cache */ 93 } 94 if (socache_provider == NULL) { 95 ap_log_perror(APLOG_MARK, APLOG_CRIT, 0, plog, APLOGNO(01674) 96 "Please select a socache provider with AuthnCacheSOCache " 97 "(no default found on this platform). Maybe you need to " 98 "load mod_socache_shmcb or another socache module first"); 99 return 500; /* An HTTP status would be a misnomer! */ 100 } 101 102 rv = ap_global_mutex_create(&authn_cache_mutex, NULL, 103 authn_cache_id, NULL, s, pconf, 0); 104 if (rv != APR_SUCCESS) { 105 ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(01675) 106 "failed to create %s mutex", authn_cache_id); 107 return 500; /* An HTTP status would be a misnomer! */ 108 } 109 apr_pool_cleanup_register(pconf, NULL, remove_lock, apr_pool_cleanup_null); 110 111 rv = socache_provider->init(socache_instance, authn_cache_id, 112 &authn_cache_hints, s, pconf); 113 if (rv != APR_SUCCESS) { 114 ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(01677) 115 "failed to initialise %s cache", authn_cache_id); 116 return 500; /* An HTTP status would be a misnomer! */ 117 } 118 apr_pool_cleanup_register(pconf, (void*)s, destroy_cache, apr_pool_cleanup_null); 119 return OK; 120} 121static void authn_cache_child_init(apr_pool_t *p, server_rec *s) 122{ 123 const char *lock; 124 apr_status_t rv; 125 if (!configured) { 126 return; /* don't waste the overhead of creating mutex & cache */ 127 } 128 lock = apr_global_mutex_lockfile(authn_cache_mutex); 129 rv = apr_global_mutex_child_init(&authn_cache_mutex, lock, p); 130 if (rv != APR_SUCCESS) { 131 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(01678) 132 "failed to initialise mutex in child_init"); 133 } 134} 135 136static const char *authn_cache_socache(cmd_parms *cmd, void *CFG, 137 const char *arg) 138{ 139 const char *errmsg = ap_check_cmd_context(cmd, GLOBAL_ONLY); 140 const char *sep, *name; 141 142 if (errmsg) 143 return errmsg; 144 145 /* Argument is of form 'name:args' or just 'name'. */ 146 sep = ap_strchr_c(arg, ':'); 147 if (sep) { 148 name = apr_pstrmemdup(cmd->pool, arg, sep - arg); 149 sep++; 150 } 151 else { 152 name = arg; 153 } 154 155 socache_provider = ap_lookup_provider(AP_SOCACHE_PROVIDER_GROUP, name, 156 AP_SOCACHE_PROVIDER_VERSION); 157 if (socache_provider == NULL) { 158 errmsg = apr_psprintf(cmd->pool, 159 "Unknown socache provider '%s'. Maybe you need " 160 "to load the appropriate socache module " 161 "(mod_socache_%s?)", arg, arg); 162 } 163 else { 164 errmsg = socache_provider->create(&socache_instance, sep, 165 cmd->temp_pool, cmd->pool); 166 } 167 168 if (errmsg) { 169 errmsg = apr_psprintf(cmd->pool, "AuthnCacheSOCache: %s", errmsg); 170 } 171 return errmsg; 172} 173 174static const char *authn_cache_enable(cmd_parms *cmd, void *CFG) 175{ 176 const char *errmsg = ap_check_cmd_context(cmd, GLOBAL_ONLY); 177 configured = 1; 178 return errmsg; 179} 180 181static const char *const directory = "directory"; 182static void* authn_cache_dircfg_create(apr_pool_t *pool, char *s) 183{ 184 authn_cache_dircfg *ret = apr_palloc(pool, sizeof(authn_cache_dircfg)); 185 ret->timeout = apr_time_from_sec(300); 186 ret->providers = NULL; 187 ret->context = directory; 188 return ret; 189} 190/* not sure we want this. Might be safer to document use-all-or-none */ 191static void* authn_cache_dircfg_merge(apr_pool_t *pool, void *BASE, void *ADD) 192{ 193 authn_cache_dircfg *base = BASE; 194 authn_cache_dircfg *add = ADD; 195 authn_cache_dircfg *ret = apr_pmemdup(pool, add, sizeof(authn_cache_dircfg)); 196 /* preserve context and timeout if not defaults */ 197 if (add->context == directory) { 198 ret->context = base->context; 199 } 200 if (add->timeout == apr_time_from_sec(300)) { 201 ret->timeout = base->timeout; 202 } 203 if (add->providers == NULL) { 204 ret->providers = base->providers; 205 } 206 return ret; 207} 208 209static const char *authn_cache_setprovider(cmd_parms *cmd, void *CFG, 210 const char *arg) 211{ 212 authn_cache_dircfg *cfg = CFG; 213 if (cfg->providers == NULL) { 214 cfg->providers = apr_array_make(cmd->pool, 4, sizeof(const char*)); 215 } 216 APR_ARRAY_PUSH(cfg->providers, const char*) = arg; 217 configured = 1; 218 return NULL; 219} 220 221static const char *authn_cache_timeout(cmd_parms *cmd, void *CFG, 222 const char *arg) 223{ 224 authn_cache_dircfg *cfg = CFG; 225 int secs = atoi(arg); 226 cfg->timeout = apr_time_from_sec(secs); 227 return NULL; 228} 229 230static const command_rec authn_cache_cmds[] = 231{ 232 /* global stuff: cache and mutex */ 233 AP_INIT_TAKE1("AuthnCacheSOCache", authn_cache_socache, NULL, RSRC_CONF, 234 "socache provider for authn cache"), 235 AP_INIT_NO_ARGS("AuthnCacheEnable", authn_cache_enable, NULL, RSRC_CONF, 236 "enable socache configuration in htaccess even if not enabled anywhere else"), 237 /* per-dir stuff */ 238 AP_INIT_ITERATE("AuthnCacheProvideFor", authn_cache_setprovider, NULL, 239 OR_AUTHCFG, "Determine what authn providers to cache for"), 240 AP_INIT_TAKE1("AuthnCacheTimeout", authn_cache_timeout, NULL, 241 OR_AUTHCFG, "Timeout (secs) for cached credentials"), 242 AP_INIT_TAKE1("AuthnCacheContext", ap_set_string_slot, 243 (void*)APR_OFFSETOF(authn_cache_dircfg, context), 244 ACCESS_CONF, "Context for authn cache"), 245 {NULL} 246}; 247 248static const char *construct_key(request_rec *r, const char *context, 249 const char *user, const char *realm) 250{ 251 /* handle "special" context values */ 252 if (!strcmp(context, "directory")) { 253 /* FIXME: are we at risk of this blowing up? */ 254 char *new_context; 255 char *slash = strrchr(r->uri, '/'); 256 new_context = apr_palloc(r->pool, slash - r->uri + 257 strlen(r->server->server_hostname) + 1); 258 strcpy(new_context, r->server->server_hostname); 259 strncat(new_context, r->uri, slash - r->uri); 260 context = new_context; 261 } 262 else if (!strcmp(context, "server")) { 263 context = r->server->server_hostname; 264 } 265 /* any other context value is literal */ 266 267 if (realm == NULL) { /* basic auth */ 268 return apr_pstrcat(r->pool, context, ":", user, NULL); 269 } 270 else { /* digest auth */ 271 return apr_pstrcat(r->pool, context, ":", user, ":", realm, NULL); 272 } 273} 274static void ap_authn_cache_store(request_rec *r, const char *module, 275 const char *user, const char *realm, 276 const char* data) 277{ 278 apr_status_t rv; 279 authn_cache_dircfg *dcfg; 280 const char *key; 281 apr_time_t expiry; 282 int i; 283 int use_cache = 0; 284 285 /* first check whether we're cacheing for this module */ 286 dcfg = ap_get_module_config(r->per_dir_config, &authn_socache_module); 287 if (!configured || !dcfg->providers) { 288 return; 289 } 290 for (i = 0; i < dcfg->providers->nelts; ++i) { 291 if (!strcmp(module, APR_ARRAY_IDX(dcfg->providers, i, const char*))) { 292 use_cache = 1; 293 break; 294 } 295 } 296 if (!use_cache) { 297 return; 298 } 299 300 /* OK, we're on. Grab mutex to do our business */ 301 rv = apr_global_mutex_trylock(authn_cache_mutex); 302 if (APR_STATUS_IS_EBUSY(rv)) { 303 /* don't wait around; just abandon it */ 304 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(01679) 305 "authn credentials for %s not cached (mutex busy)", user); 306 return; 307 } 308 else if (rv != APR_SUCCESS) { 309 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01680) 310 "Failed to cache authn credentials for %s in %s", 311 module, dcfg->context); 312 return; 313 } 314 315 /* We have the mutex, so go ahead */ 316 /* first build our key and determine expiry time */ 317 key = construct_key(r, dcfg->context, user, realm); 318 expiry = apr_time_now() + dcfg->timeout; 319 320 /* store it */ 321 rv = socache_provider->store(socache_instance, r->server, 322 (unsigned char*)key, strlen(key), expiry, 323 (unsigned char*)data, strlen(data), r->pool); 324 if (rv == APR_SUCCESS) { 325 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01681) 326 "Cached authn credentials for %s in %s", 327 user, dcfg->context); 328 } 329 else { 330 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01682) 331 "Failed to cache authn credentials for %s in %s", 332 module, dcfg->context); 333 } 334 335 /* We're done with the mutex */ 336 rv = apr_global_mutex_unlock(authn_cache_mutex); 337 if (rv != APR_SUCCESS) { 338 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01683) "Failed to release mutex!"); 339 } 340 return; 341} 342 343#define MAX_VAL_LEN 100 344static authn_status check_password(request_rec *r, const char *user, 345 const char *password) 346{ 347 348 /* construct key 349 * look it up 350 * if found, test password 351 * 352 * mutexing here would be a big performance drag. 353 * It's definitely unnecessary with some backends (like ndbm or gdbm) 354 * Is there a risk in the general case? I guess the only risk we 355 * care about is a race condition that gets us a dangling pointer 356 * to no-longer-defined memory. Hmmm ... 357 */ 358 apr_status_t rv; 359 const char *key; 360 authn_cache_dircfg *dcfg; 361 unsigned char val[MAX_VAL_LEN]; 362 unsigned int vallen = MAX_VAL_LEN - 1; 363 dcfg = ap_get_module_config(r->per_dir_config, &authn_socache_module); 364 if (!configured || !dcfg->providers) { 365 return AUTH_USER_NOT_FOUND; 366 } 367 key = construct_key(r, dcfg->context, user, NULL); 368 rv = socache_provider->retrieve(socache_instance, r->server, 369 (unsigned char*)key, strlen(key), 370 val, &vallen, r->pool); 371 372 if (APR_STATUS_IS_NOTFOUND(rv)) { 373 /* not found - just return */ 374 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01684) 375 "Authn cache: no credentials found for %s", user); 376 return AUTH_USER_NOT_FOUND; 377 } 378 else if (rv == APR_SUCCESS) { 379 /* OK, we got a value */ 380 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01685) 381 "Authn cache: found credentials for %s", user); 382 val[vallen] = 0; 383 } 384 else { 385 /* error: give up and pass the buck */ 386 /* FIXME: getting this for NOTFOUND - prolly a bug in mod_socache */ 387 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01686) 388 "Error accessing authentication cache"); 389 return AUTH_USER_NOT_FOUND; 390 } 391 392 rv = apr_password_validate(password, (char*) val); 393 if (rv != APR_SUCCESS) { 394 return AUTH_DENIED; 395 } 396 397 return AUTH_GRANTED; 398} 399 400static authn_status get_realm_hash(request_rec *r, const char *user, 401 const char *realm, char **rethash) 402{ 403 apr_status_t rv; 404 const char *key; 405 authn_cache_dircfg *dcfg; 406 unsigned char val[MAX_VAL_LEN]; 407 unsigned int vallen = MAX_VAL_LEN - 1; 408 dcfg = ap_get_module_config(r->per_dir_config, &authn_socache_module); 409 if (!configured || !dcfg->providers) { 410 return AUTH_USER_NOT_FOUND; 411 } 412 key = construct_key(r, dcfg->context, user, realm); 413 rv = socache_provider->retrieve(socache_instance, r->server, 414 (unsigned char*)key, strlen(key), 415 val, &vallen, r->pool); 416 417 if (APR_STATUS_IS_NOTFOUND(rv)) { 418 /* not found - just return */ 419 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01687) 420 "Authn cache: no credentials found for %s", user); 421 return AUTH_USER_NOT_FOUND; 422 } 423 else if (rv == APR_SUCCESS) { 424 /* OK, we got a value */ 425 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01688) 426 "Authn cache: found credentials for %s", user); 427 } 428 else { 429 /* error: give up and pass the buck */ 430 /* FIXME: getting this for NOTFOUND - prolly a bug in mod_socache */ 431 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01689) 432 "Error accessing authentication cache"); 433 return AUTH_USER_NOT_FOUND; 434 } 435 *rethash = apr_pstrmemdup(r->pool, (char *)val, vallen); 436 437 return AUTH_USER_FOUND; 438} 439 440static const authn_provider authn_cache_provider = 441{ 442 &check_password, 443 &get_realm_hash, 444}; 445static void register_hooks(apr_pool_t *p) 446{ 447 ap_register_auth_provider(p, AUTHN_PROVIDER_GROUP, "socache", 448 AUTHN_PROVIDER_VERSION, 449 &authn_cache_provider, AP_AUTH_INTERNAL_PER_CONF); 450 APR_REGISTER_OPTIONAL_FN(ap_authn_cache_store); 451 ap_hook_pre_config(authn_cache_precfg, NULL, NULL, APR_HOOK_MIDDLE); 452 ap_hook_post_config(authn_cache_post_config, NULL, NULL, APR_HOOK_MIDDLE); 453 ap_hook_child_init(authn_cache_child_init, NULL, NULL, APR_HOOK_MIDDLE); 454} 455 456AP_DECLARE_MODULE(authn_socache) = 457{ 458 STANDARD20_MODULE_STUFF, 459 authn_cache_dircfg_create, 460 authn_cache_dircfg_merge, 461 NULL, 462 NULL, 463 authn_cache_cmds, 464 register_hooks 465}; 466