]> granicus.if.org Git - apache/blob - modules/aaa/mod_authn_socache.c
Cleanup effort in prep for GA push:
[apache] / modules / aaa / mod_authn_socache.c
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
35 module AP_MODULE_DECLARE_DATA authn_socache_module;
36
37 typedef 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: figure out usage of socache create vs init
44  * I think the cache and mutex should be global
45  */
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;
51
52 static 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 }
60 static 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
70 static 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,
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 }
85 static 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     const char *errmsg;
90     static struct ap_socache_hints authn_cache_hints = {64, 32, 60000000};
91
92     if (!configured) {
93         return OK;    /* don't waste the overhead of creating mutex & cache */
94     }
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! */
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,
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     errmsg = socache_provider->create(&socache_instance, NULL, ptmp, pconf);
112     if (errmsg) {
113         ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, "%s", errmsg);
114         return 500; /* An HTTP status would be a misnomer! */
115     }
116
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! */
123     }
124     apr_pool_cleanup_register(pconf, (void*)s, destroy_cache, apr_pool_cleanup_null);
125     return OK;
126 }
127 static void authn_cache_child_init(apr_pool_t *p, server_rec *s)
128 {
129     const char *lock;
130     apr_status_t rv;
131     if (!configured) {
132         return;       /* don't waste the overhead of creating mutex & cache */
133     }
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");
139     }
140 }
141
142 static const char *authn_cache_socache(cmd_parms *cmd, void *CFG,
143                                        const char *arg)
144 {
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";
150     }
151     return errmsg;
152 }
153
154 static const char *const directory = "directory";
155 static void* authn_cache_dircfg_create(apr_pool_t *pool, char *s)
156 {
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;
161     return ret;
162 }
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)
165 {
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;
172     }
173     if (add->timeout == apr_time_from_sec(300)) {
174         ret->timeout = base->timeout;
175     }
176     if (add->providers == NULL) {
177         ret->providers = base->providers;
178     }
179     return ret;
180 }
181
182 static const char *authn_cache_setprovider(cmd_parms *cmd, void *CFG,
183                                            const char *arg)
184 {
185     authn_cache_dircfg *cfg = CFG;
186     if (cfg->providers == NULL) {
187         cfg->providers = apr_array_make(cmd->pool, 4, sizeof(const char*));
188     }
189     APR_ARRAY_PUSH(cfg->providers, const char*) = arg;
190     configured = 1;
191     return NULL;
192 }
193
194 static const char *authn_cache_timeout(cmd_parms *cmd, void *CFG,
195                                        const char *arg)
196 {
197     authn_cache_dircfg *cfg = CFG;
198     int secs = atoi(arg);
199     cfg->timeout = apr_time_from_sec(secs);
200     return NULL;
201 }
202
203 static const command_rec authn_cache_cmds[] =
204 {
205     /* global stuff: cache and mutex */
206     AP_INIT_TAKE1("AuthnCacheSOCache", authn_cache_socache, NULL, RSRC_CONF,
207                   "socache provider for authn cache"),
208     /* per-dir stuff */
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"),
216     {NULL}
217 };
218
219 static const char *construct_key(request_rec *r, const char *context,
220                                  const char *user, const char *realm)
221 {
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);
227     }
228     else if (!strcmp(context, "server")) {
229         context = r->server->server_hostname;
230     }
231     /* any other context value is literal */
232
233     if (realm == NULL) {                              /* basic auth */
234         return apr_pstrcat(r->pool, context, ":", user, NULL);
235     }
236     else {                                            /* digest auth */
237         return apr_pstrcat(r->pool, context, ":", user, ":", realm, NULL);
238     }
239 }
240 static void ap_authn_cache_store(request_rec *r, const char *module,
241                                  const char *user, const char *realm,
242                                  const char* data)
243 {
244     apr_status_t rv;
245     authn_cache_dircfg *dcfg;
246     const char *key;
247     apr_time_t expiry;
248     int i;
249     int use_cache = 0;
250
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) {
254         return;
255     }
256     for (i = 0; i < dcfg->providers->nelts; ++i) {
257         if (!strcmp(module, APR_ARRAY_IDX(dcfg->providers, i, const char*))) {
258             use_cache = 1;
259             break;
260         }
261     }
262     if (!use_cache) {
263         return;
264     }
265
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);
272         return;
273     }
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);
278         return;
279     }
280
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;
285
286     /* store it */
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);
294     }
295     else {
296         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
297                       "Failed to cache authn credentials for %s in %s",
298                       module, dcfg->context);
299     }
300
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!");
305     }
306     return;
307 }
308
309 #define MAX_VAL_LEN 100
310 static authn_status check_password(request_rec *r, const char *user,
311                                    const char *password)
312 {
313
314     /* construct key
315      * look it up
316      * if found, test password
317      *
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 ...
323      */
324     apr_status_t rv;
325     const char *key;
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;
332     }
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);
337
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;
343     }
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);
348         val[vallen] = 0;
349     }
350     else {
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;
356     }
357
358     rv = apr_password_validate(password, (char*) val);
359     if (rv != APR_SUCCESS) {
360         return AUTH_DENIED;
361     }
362
363     return AUTH_GRANTED;
364 }
365
366 static authn_status get_realm_hash(request_rec *r, const char *user,
367                                    const char *realm, char **rethash)
368 {
369     apr_status_t rv;
370     const char *key;
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;
377     }
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);
382
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;
388     }
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);
393         val[vallen] = 0;
394     }
395     else {
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;
401     }
402     *rethash = (char*)val;
403
404     return AUTH_USER_FOUND;
405 }
406
407 static const authn_provider authn_cache_provider =
408 {
409     &check_password,
410     &get_realm_hash,
411 };
412 static void register_hooks(apr_pool_t *p)
413 {
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);
421 }
422
423 AP_DECLARE_MODULE(authn_socache) =
424 {
425     STANDARD20_MODULE_STUFF,
426     authn_cache_dircfg_create,
427     authn_cache_dircfg_merge,
428     NULL,
429     NULL,
430     authn_cache_cmds,
431     register_hooks
432 };