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"
18 #include "apr_md5.h" /* for apr_password_validate */
20 #include "ap_config.h"
21 #include "ap_provider.h"
23 #include "http_config.h"
24 #include "http_core.h"
26 #include "http_protocol.h"
27 #include "http_request.h"
31 #include "ap_socache.h"
32 #include "util_mutex.h"
33 #include "apr_optional.h"
35 module AP_MODULE_DECLARE_DATA authn_socache_module;
37 typedef struct authn_cache_dircfg {
38 apr_interval_time_t timeout;
39 apr_array_header_t *providers;
43 /* FIXME: figure out usage of socache create vs init
44 * I think the cache and mutex should be global
46 static apr_global_mutex_t *authn_cache_mutex = NULL;
47 static ap_socache_provider_t *socache_provider = NULL;
48 static ap_socache_instance_t *socache_instance = NULL;
49 static const char *const authn_cache_id = "authn-socache";
50 static int configured;
52 static apr_status_t remove_lock(void *data)
54 if (authn_cache_mutex) {
55 apr_global_mutex_destroy(authn_cache_mutex);
56 authn_cache_mutex = NULL;
60 static apr_status_t destroy_cache(void *data)
62 if (socache_instance) {
63 socache_provider->destroy(socache_instance, (server_rec*)data);
64 socache_instance = NULL;
70 static int authn_cache_precfg(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptmp)
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,
76 "failed to register %s mutex", authn_cache_id);
77 return 500; /* An HTTP status would be a misnomer! */
79 socache_provider = ap_lookup_provider(AP_SOCACHE_PROVIDER_GROUP,
80 AP_SOCACHE_DEFAULT_PROVIDER,
81 AP_SOCACHE_PROVIDER_VERSION);
85 static int authn_cache_post_config(apr_pool_t *pconf, apr_pool_t *plog,
86 apr_pool_t *ptmp, server_rec *s)
90 static struct ap_socache_hints authn_cache_hints = {64, 32, 60000000};
93 return OK; /* don't waste the overhead of creating mutex & cache */
95 if (socache_provider == NULL) {
96 ap_log_perror(APLOG_MARK, APLOG_CRIT, 0, plog,
97 "Please select a socache provider with AuthnCacheSOCache "
98 "(no default found on this platform)");
99 return 500; /* An HTTP status would be a misnomer! */
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,
106 "failed to create %s mutex", authn_cache_id);
107 return 500; /* An HTTP status would be a misnomer! */
109 apr_pool_cleanup_register(pconf, NULL, remove_lock, apr_pool_cleanup_null);
111 errmsg = socache_provider->create(&socache_instance, NULL, ptmp, pconf);
113 ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, "%s", errmsg);
114 return 500; /* An HTTP status would be a misnomer! */
117 rv = socache_provider->init(socache_instance, authn_cache_id,
118 &authn_cache_hints, s, pconf);
119 if (rv != APR_SUCCESS) {
120 ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog,
121 "failed to initialise %s cache", authn_cache_id);
122 return 500; /* An HTTP status would be a misnomer! */
124 apr_pool_cleanup_register(pconf, (void*)s, destroy_cache, apr_pool_cleanup_null);
127 static void authn_cache_child_init(apr_pool_t *p, server_rec *s)
132 return; /* don't waste the overhead of creating mutex & cache */
134 lock = apr_global_mutex_lockfile(authn_cache_mutex);
135 rv = apr_global_mutex_child_init(&authn_cache_mutex, lock, p);
136 if (rv != APR_SUCCESS) {
137 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
138 "failed to initialise mutex in child_init");
142 static const char *authn_cache_socache(cmd_parms *cmd, void *CFG,
145 const char *errmsg = ap_check_cmd_context(cmd, GLOBAL_ONLY);
146 socache_provider = ap_lookup_provider(AP_SOCACHE_PROVIDER_GROUP, arg,
147 AP_SOCACHE_PROVIDER_VERSION);
148 if (socache_provider == NULL) {
149 errmsg = "Unknown socache provider";
154 static const char *const directory = "directory";
155 static void* authn_cache_dircfg_create(apr_pool_t *pool, char *s)
157 authn_cache_dircfg *ret = apr_palloc(pool, sizeof(authn_cache_dircfg));
158 ret->timeout = apr_time_from_sec(300);
159 ret->providers = NULL;
160 ret->context = directory;
163 /* not sure we want this. Might be safer to document use-all-or-none */
164 static void* authn_cache_dircfg_merge(apr_pool_t *pool, void *BASE, void *ADD)
166 authn_cache_dircfg *base = BASE;
167 authn_cache_dircfg *add = ADD;
168 authn_cache_dircfg *ret = apr_pmemdup(pool, add, sizeof(authn_cache_dircfg));
169 /* preserve context and timeout if not defaults */
170 if (add->context == directory) {
171 ret->context = base->context;
173 if (add->timeout == apr_time_from_sec(300)) {
174 ret->timeout = base->timeout;
176 if (add->providers == NULL) {
177 ret->providers = base->providers;
182 static const char *authn_cache_setprovider(cmd_parms *cmd, void *CFG,
185 authn_cache_dircfg *cfg = CFG;
186 if (cfg->providers == NULL) {
187 cfg->providers = apr_array_make(cmd->pool, 4, sizeof(const char*));
189 APR_ARRAY_PUSH(cfg->providers, const char*) = arg;
194 static const char *authn_cache_timeout(cmd_parms *cmd, void *CFG,
197 authn_cache_dircfg *cfg = CFG;
198 int secs = atoi(arg);
199 cfg->timeout = apr_time_from_sec(secs);
203 static const command_rec authn_cache_cmds[] =
205 /* global stuff: cache and mutex */
206 AP_INIT_TAKE1("AuthnCacheSOCache", authn_cache_socache, NULL, RSRC_CONF,
207 "socache provider for authn cache"),
209 AP_INIT_ITERATE("AuthnCacheProvideFor", authn_cache_setprovider, NULL,
210 OR_AUTHCFG, "Determine what authn providers to cache for"),
211 AP_INIT_TAKE1("AuthnCacheTimeout", authn_cache_timeout, NULL,
212 OR_AUTHCFG, "Timeout (secs) for cached credentials"),
213 AP_INIT_TAKE1("AuthnCacheContext", ap_set_string_slot,
214 (void*)APR_OFFSETOF(authn_cache_dircfg, context),
215 ACCESS_CONF, "Context for authn cache"),
219 static const char *construct_key(request_rec *r, const char *context,
220 const char *user, const char *realm)
222 /* handle "special" context values */
223 if (!strcmp(context, "directory")) {
224 /* FIXME: are we at risk of this blowing up? */
225 char *slash = strrchr(r->uri, '/');
226 context = apr_pstrndup(r->pool, r->uri, slash - r->uri + 1);
228 else if (!strcmp(context, "server")) {
229 context = r->server->server_hostname;
231 /* any other context value is literal */
233 if (realm == NULL) { /* basic auth */
234 return apr_pstrcat(r->pool, context, ":", user, NULL);
236 else { /* digest auth */
237 return apr_pstrcat(r->pool, context, ":", user, ":", realm, NULL);
240 static void ap_authn_cache_store(request_rec *r, const char *module,
241 const char *user, const char *realm,
245 authn_cache_dircfg *dcfg;
251 /* first check whether we're cacheing for this module */
252 dcfg = ap_get_module_config(r->per_dir_config, &authn_socache_module);
253 if (!dcfg->providers) {
256 for (i = 0; i < dcfg->providers->nelts; ++i) {
257 if (!strcmp(module, APR_ARRAY_IDX(dcfg->providers, i, const char*))) {
266 /* OK, we're on. Grab mutex to do our business */
267 rv = apr_global_mutex_trylock(authn_cache_mutex);
268 if (APR_STATUS_IS_EBUSY(rv)) {
269 /* don't wait around; just abandon it */
270 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
271 "authn credentials for %s not cached (mutex busy)", user);
274 else if (rv != APR_SUCCESS) {
275 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
276 "Failed to cache authn credentials for %s in %s",
277 module, dcfg->context);
281 /* We have the mutex, so go ahead */
282 /* first build our key and determine expiry time */
283 key = construct_key(r, dcfg->context, user, realm);
284 expiry = apr_time_now() + dcfg->timeout;
287 rv = socache_provider->store(socache_instance, r->server,
288 (unsigned char*)key, strlen(key), expiry,
289 (unsigned char*)data, strlen(data), r->pool);
290 if (rv == APR_SUCCESS) {
291 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
292 "Cached authn credentials for %s in %s",
293 user, dcfg->context);
296 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
297 "Failed to cache authn credentials for %s in %s",
298 module, dcfg->context);
301 /* We're done with the mutex */
302 rv = apr_global_mutex_unlock(authn_cache_mutex);
303 if (rv != APR_SUCCESS) {
304 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "Failed to release mutex!");
309 #define MAX_VAL_LEN 100
310 static authn_status check_password(request_rec *r, const char *user,
311 const char *password)
316 * if found, test password
318 * mutexing here would be a big performance drag.
319 * It's definitely unnecessary with some backends (like ndbm or gdbm)
320 * Is there a risk in the general case? I guess the only risk we
321 * care about is a race condition that gets us a dangling pointer
322 * to no-longer-defined memory. Hmmm ...
326 authn_cache_dircfg *dcfg;
327 unsigned char val[MAX_VAL_LEN];
328 unsigned int vallen = MAX_VAL_LEN - 1;
329 dcfg = ap_get_module_config(r->per_dir_config, &authn_socache_module);
330 if (!dcfg->providers) {
331 return AUTH_USER_NOT_FOUND;
333 key = construct_key(r, dcfg->context, user, NULL);
334 rv = socache_provider->retrieve(socache_instance, r->server,
335 (unsigned char*)key, strlen(key),
336 val, &vallen, r->pool);
338 if (APR_STATUS_IS_NOTFOUND(rv)) {
339 /* not found - just return */
340 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
341 "Authn cache: no credentials found for %s", user);
342 return AUTH_USER_NOT_FOUND;
344 else if (rv == APR_SUCCESS) {
345 /* OK, we got a value */
346 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
347 "Authn cache: found credentials for %s", user);
351 /* error: give up and pass the buck */
352 /* FIXME: getting this for NOTFOUND - prolly a bug in mod_socache */
353 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
354 "Error accessing authentication cache");
355 return AUTH_USER_NOT_FOUND;
358 rv = apr_password_validate(password, (char*) val);
359 if (rv != APR_SUCCESS) {
366 static authn_status get_realm_hash(request_rec *r, const char *user,
367 const char *realm, char **rethash)
371 authn_cache_dircfg *dcfg;
372 unsigned char val[MAX_VAL_LEN];
373 unsigned int vallen = MAX_VAL_LEN - 1;
374 dcfg = ap_get_module_config(r->per_dir_config, &authn_socache_module);
375 if (!dcfg->providers) {
376 return AUTH_USER_NOT_FOUND;
378 key = construct_key(r, dcfg->context, user, realm);
379 rv = socache_provider->retrieve(socache_instance, r->server,
380 (unsigned char*)key, strlen(key),
381 val, &vallen, r->pool);
383 if (APR_STATUS_IS_NOTFOUND(rv)) {
384 /* not found - just return */
385 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
386 "Authn cache: no credentials found for %s", user);
387 return AUTH_USER_NOT_FOUND;
389 else if (rv == APR_SUCCESS) {
390 /* OK, we got a value */
391 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
392 "Authn cache: found credentials for %s", user);
396 /* error: give up and pass the buck */
397 /* FIXME: getting this for NOTFOUND - prolly a bug in mod_socache */
398 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
399 "Error accessing authentication cache");
400 return AUTH_USER_NOT_FOUND;
402 *rethash = (char*)val;
404 return AUTH_USER_FOUND;
407 static const authn_provider authn_cache_provider =
412 static void register_hooks(apr_pool_t *p)
414 ap_register_auth_provider(p, AUTHN_PROVIDER_GROUP, "socache",
415 AUTHN_PROVIDER_VERSION,
416 &authn_cache_provider, AP_AUTH_INTERNAL_PER_CONF);
417 APR_REGISTER_OPTIONAL_FN(ap_authn_cache_store);
418 ap_hook_pre_config(authn_cache_precfg, NULL, NULL, APR_HOOK_MIDDLE);
419 ap_hook_post_config(authn_cache_post_config, NULL, NULL, APR_HOOK_MIDDLE);
420 ap_hook_child_init(authn_cache_child_init, NULL, NULL, APR_HOOK_MIDDLE);
423 AP_DECLARE_MODULE(authn_socache) =
425 STANDARD20_MODULE_STUFF,
426 authn_cache_dircfg_create,
427 authn_cache_dircfg_merge,