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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 #include "apr_strings.h"
19 #include "ap_config.h"
20 #include "ap_provider.h"
22 #include "http_config.h"
23 #include "http_core.h"
25 #include "http_protocol.h"
26 #include "http_request.h"
30 #include "ap_socache.h"
31 #include "util_mutex.h"
32 #include "apr_optional.h"
34 module AP_MODULE_DECLARE_DATA authn_socache_module;
36 typedef struct authn_cache_dircfg {
37 apr_interval_time_t timeout;
38 apr_array_header_t *providers;
43 * I think the cache and mutex should be global
45 static apr_global_mutex_t *authn_cache_mutex = NULL;
46 static ap_socache_provider_t *socache_provider = NULL;
47 static ap_socache_instance_t *socache_instance = NULL;
48 static const char *const authn_cache_id = "authn-socache";
49 static int configured;
51 static apr_status_t remove_lock(void *data)
53 if (authn_cache_mutex) {
54 apr_global_mutex_destroy(authn_cache_mutex);
55 authn_cache_mutex = NULL;
59 static apr_status_t destroy_cache(void *data)
61 if (socache_instance) {
62 socache_provider->destroy(socache_instance, (server_rec*)data);
63 socache_instance = NULL;
69 static int authn_cache_precfg(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptmp)
71 apr_status_t rv = ap_mutex_register(pconf, authn_cache_id,
72 NULL, APR_LOCK_DEFAULT, 0);
73 if (rv != APR_SUCCESS) {
74 ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(01673)
75 "failed to register %s mutex", authn_cache_id);
76 return 500; /* An HTTP status would be a misnomer! */
78 socache_provider = ap_lookup_provider(AP_SOCACHE_PROVIDER_GROUP,
79 AP_SOCACHE_DEFAULT_PROVIDER,
80 AP_SOCACHE_PROVIDER_VERSION);
84 static int authn_cache_post_config(apr_pool_t *pconf, apr_pool_t *plog,
85 apr_pool_t *ptmp, server_rec *s)
88 static struct ap_socache_hints authn_cache_hints = {64, 32, 60000000};
92 return OK; /* don't waste the overhead of creating mutex & cache */
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! */
102 /* We have socache_provider, but do not have socache_instance. This should
103 * happen only when using "default" socache_provider, so create default
104 * socache_instance in this case. */
105 if (socache_instance == NULL) {
106 errmsg = socache_provider->create(&socache_instance, NULL,
109 ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(02612)
110 "failed to create mod_socache_shmcb socache "
111 "instance: %s", errmsg);
116 rv = ap_global_mutex_create(&authn_cache_mutex, NULL,
117 authn_cache_id, NULL, s, pconf, 0);
118 if (rv != APR_SUCCESS) {
119 ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(01675)
120 "failed to create %s mutex", authn_cache_id);
121 return 500; /* An HTTP status would be a misnomer! */
123 apr_pool_cleanup_register(pconf, NULL, remove_lock, apr_pool_cleanup_null);
125 rv = socache_provider->init(socache_instance, authn_cache_id,
126 &authn_cache_hints, s, pconf);
127 if (rv != APR_SUCCESS) {
128 ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(01677)
129 "failed to initialise %s cache", authn_cache_id);
130 return 500; /* An HTTP status would be a misnomer! */
132 apr_pool_cleanup_register(pconf, (void*)s, destroy_cache, apr_pool_cleanup_null);
135 static void authn_cache_child_init(apr_pool_t *p, server_rec *s)
140 return; /* don't waste the overhead of creating mutex & cache */
142 lock = apr_global_mutex_lockfile(authn_cache_mutex);
143 rv = apr_global_mutex_child_init(&authn_cache_mutex, lock, p);
144 if (rv != APR_SUCCESS) {
145 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(01678)
146 "failed to initialise mutex in child_init");
150 static const char *authn_cache_socache(cmd_parms *cmd, void *CFG,
153 const char *errmsg = ap_check_cmd_context(cmd, GLOBAL_ONLY);
154 const char *sep, *name;
159 /* Argument is of form 'name:args' or just 'name'. */
160 sep = ap_strchr_c(arg, ':');
162 name = apr_pstrmemdup(cmd->pool, arg, sep - arg);
169 socache_provider = ap_lookup_provider(AP_SOCACHE_PROVIDER_GROUP, name,
170 AP_SOCACHE_PROVIDER_VERSION);
171 if (socache_provider == NULL) {
172 errmsg = apr_psprintf(cmd->pool,
173 "Unknown socache provider '%s'. Maybe you need "
174 "to load the appropriate socache module "
175 "(mod_socache_%s?)", arg, arg);
178 errmsg = socache_provider->create(&socache_instance, sep,
179 cmd->temp_pool, cmd->pool);
183 errmsg = apr_psprintf(cmd->pool, "AuthnCacheSOCache: %s", errmsg);
188 static const char *authn_cache_enable(cmd_parms *cmd, void *CFG)
190 const char *errmsg = ap_check_cmd_context(cmd, GLOBAL_ONLY);
195 static const char *const directory = "directory";
196 static void* authn_cache_dircfg_create(apr_pool_t *pool, char *s)
198 authn_cache_dircfg *ret = apr_palloc(pool, sizeof(authn_cache_dircfg));
199 ret->timeout = apr_time_from_sec(300);
200 ret->providers = NULL;
201 ret->context = directory;
204 /* not sure we want this. Might be safer to document use-all-or-none */
205 static void* authn_cache_dircfg_merge(apr_pool_t *pool, void *BASE, void *ADD)
207 authn_cache_dircfg *base = BASE;
208 authn_cache_dircfg *add = ADD;
209 authn_cache_dircfg *ret = apr_pmemdup(pool, add, sizeof(authn_cache_dircfg));
210 /* preserve context and timeout if not defaults */
211 if (add->context == directory) {
212 ret->context = base->context;
214 if (add->timeout == apr_time_from_sec(300)) {
215 ret->timeout = base->timeout;
217 if (add->providers == NULL) {
218 ret->providers = base->providers;
223 static const char *authn_cache_setprovider(cmd_parms *cmd, void *CFG,
226 authn_cache_dircfg *cfg = CFG;
227 if (cfg->providers == NULL) {
228 cfg->providers = apr_array_make(cmd->pool, 4, sizeof(const char*));
230 APR_ARRAY_PUSH(cfg->providers, const char*) = arg;
235 static const char *authn_cache_timeout(cmd_parms *cmd, void *CFG,
238 authn_cache_dircfg *cfg = CFG;
239 int secs = atoi(arg);
240 cfg->timeout = apr_time_from_sec(secs);
244 static const command_rec authn_cache_cmds[] =
246 /* global stuff: cache and mutex */
247 AP_INIT_TAKE1("AuthnCacheSOCache", authn_cache_socache, NULL, RSRC_CONF,
248 "socache provider for authn cache"),
249 AP_INIT_NO_ARGS("AuthnCacheEnable", authn_cache_enable, NULL, RSRC_CONF,
250 "enable socache configuration in htaccess even if not enabled anywhere else"),
252 AP_INIT_ITERATE("AuthnCacheProvideFor", authn_cache_setprovider, NULL,
253 OR_AUTHCFG, "Determine what authn providers to cache for"),
254 AP_INIT_TAKE1("AuthnCacheTimeout", authn_cache_timeout, NULL,
255 OR_AUTHCFG, "Timeout (secs) for cached credentials"),
256 AP_INIT_TAKE1("AuthnCacheContext", ap_set_string_slot,
257 (void*)APR_OFFSETOF(authn_cache_dircfg, context),
258 ACCESS_CONF, "Context for authn cache"),
262 static const char *construct_key(request_rec *r, const char *context,
263 const char *user, const char *realm)
265 /* handle "special" context values */
266 if (!strcmp(context, "directory")) {
267 /* FIXME: are we at risk of this blowing up? */
269 char *slash = strrchr(r->uri, '/');
270 new_context = apr_palloc(r->pool, slash - r->uri +
271 strlen(r->server->server_hostname) + 1);
272 strcpy(new_context, r->server->server_hostname);
273 strncat(new_context, r->uri, slash - r->uri);
274 context = new_context;
276 else if (!strcmp(context, "server")) {
277 context = r->server->server_hostname;
279 /* any other context value is literal */
281 if (realm == NULL) { /* basic auth */
282 return apr_pstrcat(r->pool, context, ":", user, NULL);
284 else { /* digest auth */
285 return apr_pstrcat(r->pool, context, ":", user, ":", realm, NULL);
288 static void ap_authn_cache_store(request_rec *r, const char *module,
289 const char *user, const char *realm,
293 authn_cache_dircfg *dcfg;
299 /* first check whether we're cacheing for this module */
300 dcfg = ap_get_module_config(r->per_dir_config, &authn_socache_module);
301 if (!configured || !dcfg->providers) {
304 for (i = 0; i < dcfg->providers->nelts; ++i) {
305 if (!strcmp(module, APR_ARRAY_IDX(dcfg->providers, i, const char*))) {
314 /* OK, we're on. Grab mutex to do our business */
315 rv = apr_global_mutex_trylock(authn_cache_mutex);
316 if (APR_STATUS_IS_EBUSY(rv)) {
317 /* don't wait around; just abandon it */
318 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(01679)
319 "authn credentials for %s not cached (mutex busy)", user);
322 else if (rv != APR_SUCCESS) {
323 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01680)
324 "Failed to cache authn credentials for %s in %s",
325 module, dcfg->context);
329 /* We have the mutex, so go ahead */
330 /* first build our key and determine expiry time */
331 key = construct_key(r, dcfg->context, user, realm);
332 expiry = apr_time_now() + dcfg->timeout;
335 rv = socache_provider->store(socache_instance, r->server,
336 (unsigned char*)key, strlen(key), expiry,
337 (unsigned char*)data, strlen(data), r->pool);
338 if (rv == APR_SUCCESS) {
339 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01681)
340 "Cached authn credentials for %s in %s",
341 user, dcfg->context);
344 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01682)
345 "Failed to cache authn credentials for %s in %s",
346 module, dcfg->context);
349 /* We're done with the mutex */
350 rv = apr_global_mutex_unlock(authn_cache_mutex);
351 if (rv != APR_SUCCESS) {
352 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01683) "Failed to release mutex!");
357 #define MAX_VAL_LEN 100
358 static authn_status check_password(request_rec *r, const char *user,
359 const char *password)
364 * if found, test password
366 * mutexing here would be a big performance drag.
367 * It's definitely unnecessary with some backends (like ndbm or gdbm)
368 * Is there a risk in the general case? I guess the only risk we
369 * care about is a race condition that gets us a dangling pointer
370 * to no-longer-defined memory. Hmmm ...
374 authn_cache_dircfg *dcfg;
375 unsigned char val[MAX_VAL_LEN];
376 unsigned int vallen = MAX_VAL_LEN - 1;
377 dcfg = ap_get_module_config(r->per_dir_config, &authn_socache_module);
378 if (!configured || !dcfg->providers) {
379 return AUTH_USER_NOT_FOUND;
381 key = construct_key(r, dcfg->context, user, NULL);
382 rv = socache_provider->retrieve(socache_instance, r->server,
383 (unsigned char*)key, strlen(key),
384 val, &vallen, r->pool);
386 if (APR_STATUS_IS_NOTFOUND(rv)) {
387 /* not found - just return */
388 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01684)
389 "Authn cache: no credentials found for %s", user);
390 return AUTH_USER_NOT_FOUND;
392 else if (rv == APR_SUCCESS) {
393 /* OK, we got a value */
394 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01685)
395 "Authn cache: found credentials for %s", user);
399 /* error: give up and pass the buck */
400 /* FIXME: getting this for NOTFOUND - prolly a bug in mod_socache */
401 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01686)
402 "Error accessing authentication cache");
403 return AUTH_USER_NOT_FOUND;
406 rv = ap_password_validate(r, user, password, (char*) val);
407 if (rv != APR_SUCCESS) {
414 static authn_status get_realm_hash(request_rec *r, const char *user,
415 const char *realm, char **rethash)
419 authn_cache_dircfg *dcfg;
420 unsigned char val[MAX_VAL_LEN];
421 unsigned int vallen = MAX_VAL_LEN - 1;
422 dcfg = ap_get_module_config(r->per_dir_config, &authn_socache_module);
423 if (!configured || !dcfg->providers) {
424 return AUTH_USER_NOT_FOUND;
426 key = construct_key(r, dcfg->context, user, realm);
427 rv = socache_provider->retrieve(socache_instance, r->server,
428 (unsigned char*)key, strlen(key),
429 val, &vallen, r->pool);
431 if (APR_STATUS_IS_NOTFOUND(rv)) {
432 /* not found - just return */
433 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01687)
434 "Authn cache: no credentials found for %s", user);
435 return AUTH_USER_NOT_FOUND;
437 else if (rv == APR_SUCCESS) {
438 /* OK, we got a value */
439 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01688)
440 "Authn cache: found credentials for %s", user);
443 /* error: give up and pass the buck */
444 /* FIXME: getting this for NOTFOUND - prolly a bug in mod_socache */
445 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01689)
446 "Error accessing authentication cache");
447 return AUTH_USER_NOT_FOUND;
449 *rethash = apr_pstrmemdup(r->pool, (char *)val, vallen);
451 return AUTH_USER_FOUND;
454 static const authn_provider authn_cache_provider =
459 static void register_hooks(apr_pool_t *p)
461 ap_register_auth_provider(p, AUTHN_PROVIDER_GROUP, "socache",
462 AUTHN_PROVIDER_VERSION,
463 &authn_cache_provider, AP_AUTH_INTERNAL_PER_CONF);
464 APR_REGISTER_OPTIONAL_FN(ap_authn_cache_store);
465 ap_hook_pre_config(authn_cache_precfg, NULL, NULL, APR_HOOK_MIDDLE);
466 ap_hook_post_config(authn_cache_post_config, NULL, NULL, APR_HOOK_MIDDLE);
467 ap_hook_child_init(authn_cache_child_init, NULL, NULL, APR_HOOK_MIDDLE);
470 AP_DECLARE_MODULE(authn_socache) =
472 STANDARD20_MODULE_STUFF,
473 authn_cache_dircfg_create,
474 authn_cache_dircfg_merge,