]> granicus.if.org Git - apache/blob - modules/aaa/mod_authn_socache.c
C99 is not a requirement, fix style violation niq refuses(?) to correct.
[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
51 static apr_status_t remove_lock(void *data)
52 {
53     if (authn_cache_mutex) {
54         apr_global_mutex_destroy(authn_cache_mutex);
55         authn_cache_mutex = NULL;
56     }
57     return APR_SUCCESS;
58 }
59 static apr_status_t destroy_cache(void *data)
60 {
61     if (socache_instance) {
62         socache_provider->destroy(socache_instance, (server_rec*)data);
63         socache_instance = NULL;
64     }
65     return APR_SUCCESS;
66 }
67
68
69 static int authn_cache_precfg(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptmp)
70 {
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,
75                       "failed to register %s mutex", authn_cache_id);
76         return 500; /* An HTTP status would be a misnomer! */
77     }
78     socache_provider = ap_lookup_provider(AP_SOCACHE_PROVIDER_GROUP,
79                                           AP_SOCACHE_DEFAULT_PROVIDER,
80                                           AP_SOCACHE_PROVIDER_VERSION);
81     return OK;
82 }
83 static int authn_cache_post_config(apr_pool_t *pconf, apr_pool_t *plog,
84                                    apr_pool_t *ptmp, server_rec *s)
85 {
86     apr_status_t rv;
87     const char *errmsg;
88     static struct ap_socache_hints authn_cache_hints = {64, 32, 60000000};
89
90     rv = ap_global_mutex_create(&authn_cache_mutex, NULL,
91                                 authn_cache_id, NULL, s, pconf, 0);
92     if (rv != APR_SUCCESS) {
93         ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog,
94                       "failed to create %s mutex", authn_cache_id);
95         return 500; /* An HTTP status would be a misnomer! */
96     }
97     apr_pool_cleanup_register(pconf, NULL, remove_lock, apr_pool_cleanup_null);
98
99     errmsg = socache_provider->create(&socache_instance, NULL, ptmp, pconf);
100     if (errmsg) {
101         ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, "%s", errmsg);
102         return 500; /* An HTTP status would be a misnomer! */
103     }
104
105     rv = socache_provider->init(socache_instance, authn_cache_id,
106                                 &authn_cache_hints, s, pconf);
107     if (rv != APR_SUCCESS) {
108         ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog,
109                       "failed to initialise %s cache", authn_cache_id);
110         return 500; /* An HTTP status would be a misnomer! */
111     }
112     apr_pool_cleanup_register(pconf, (void*)s, destroy_cache, apr_pool_cleanup_null);
113     return OK;
114 }
115 static void authn_cache_child_init(apr_pool_t *p, server_rec *s)
116 {
117     const char *lock = apr_global_mutex_lockfile(authn_cache_mutex);
118     apr_status_t rv = apr_global_mutex_child_init(&authn_cache_mutex, lock, p);
119     if (rv != APR_SUCCESS) {
120         ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
121                      "failed to initialise mutex in child_init");
122     }
123 }
124
125 static const char *authn_cache_socache(cmd_parms *cmd, void *CFG,
126                                        const char *arg)
127 {
128     const char *errmsg = ap_check_cmd_context(cmd, GLOBAL_ONLY);
129     socache_provider = ap_lookup_provider(AP_SOCACHE_PROVIDER_GROUP, arg,
130                                           AP_SOCACHE_PROVIDER_VERSION);
131     if (socache_provider == NULL) {
132         errmsg = "Unknown socache provider";
133     }
134     return errmsg;
135 }
136
137 static const char *const directory = "directory";
138 static void* authn_cache_dircfg_create(apr_pool_t *pool, char *s)
139 {
140     authn_cache_dircfg *ret = apr_palloc(pool, sizeof(authn_cache_dircfg));
141     ret->timeout = apr_time_from_sec(300);
142     ret->providers = NULL;
143     ret->context = directory;
144     return ret;
145 }
146 /* not sure we want this.  Might be safer to document use-all-or-none */
147 static void* authn_cache_dircfg_merge(apr_pool_t *pool, void *BASE, void *ADD)
148 {
149     authn_cache_dircfg *base = BASE;
150     authn_cache_dircfg *add = ADD;
151     authn_cache_dircfg *ret = apr_pmemdup(pool, add, sizeof(authn_cache_dircfg));
152     /* preserve context and timeout if not defaults */
153     if (add->context == directory) {
154         ret->context = base->context;
155     }
156     if (add->timeout == apr_time_from_sec(300)) {
157         ret->timeout = base->timeout;
158     }
159     if (add->providers == NULL) {
160         ret->providers = base->providers;
161     }
162     return ret;
163 }
164
165 static const char *authn_cache_setprovider(cmd_parms *cmd, void *CFG,
166                                            const char *arg)
167 {
168     authn_cache_dircfg *cfg = CFG;
169     if (cfg->providers == NULL) {
170         cfg->providers = apr_array_make(cmd->pool, 4, sizeof(const char*));
171     }
172     APR_ARRAY_PUSH(cfg->providers, const char*) = arg;
173     return NULL;
174 }
175
176 static const char *authn_cache_timeout(cmd_parms *cmd, void *CFG,
177                                        const char *arg)
178 {
179     authn_cache_dircfg *cfg = CFG;
180     int secs = atoi(arg);
181     cfg->timeout = apr_time_from_sec(secs);
182     return NULL;
183 }
184
185 static const command_rec authn_cache_cmds[] =
186 {
187     /* global stuff: cache and mutex */
188     AP_INIT_TAKE1("AuthnCacheSOCache", authn_cache_socache, NULL, RSRC_CONF,
189                   "socache provider for authn cache"),
190     /* per-dir stuff */
191     AP_INIT_ITERATE("AuthnCacheProvider", authn_cache_setprovider, NULL,
192                     OR_AUTHCFG, "Determine what authn providers to cache for"),
193     AP_INIT_TAKE1("AuthnCacheTimeout", authn_cache_timeout, NULL,
194                   OR_AUTHCFG, "Timeout (secs) for cached credentials"),
195     AP_INIT_TAKE1("AuthnCacheContext", ap_set_string_slot,
196                   (void*)APR_OFFSETOF(authn_cache_dircfg, context),
197                   ACCESS_CONF, "Context for authn cache"),
198     {NULL}
199 };
200
201 static const char *construct_key(request_rec *r, const char *context,
202                                  const char *user, const char *realm)
203 {
204     /* handle "special" context values */
205     if (!strcmp(context, "directory")) {
206         /* FIXME: are we at risk of this blowing up? */
207         char *slash = strrchr(r->uri, '/');
208         context = apr_pstrndup(r->pool, r->uri, slash - r->uri + 1);
209     }
210     else if (!strcmp(context, "server")) {
211         context = r->server->server_hostname;
212     }
213     /* any other context value is literal */
214
215     if (realm == NULL) {                              /* basic auth */
216         return apr_pstrcat(r->pool, context, ":", user, NULL);
217     }
218     else {                                            /* digest auth */
219         return apr_pstrcat(r->pool, context, ":", user, ":", realm, NULL);
220     }
221 }
222 static void ap_authn_cache_store(request_rec *r, const char *module,
223                                  const char *user, const char *realm,
224                                  const char* data)
225 {
226     apr_status_t rv;
227     authn_cache_dircfg *dcfg;
228     const char *key;
229     apr_time_t expiry;
230     int i;
231     int use_cache = 0;
232
233     /* first check whether we're cacheing for this module */
234     dcfg = ap_get_module_config(r->per_dir_config, &authn_socache_module);
235     for (i = 0; i < dcfg->providers->nelts; ++i) {
236         if (!strcmp(module, APR_ARRAY_IDX(dcfg->providers, i, const char*))) {
237             use_cache = 1;
238             break;
239         }
240     }
241     if (!use_cache) {
242         return;
243     }
244
245     /* OK, we're on.  Grab mutex to do our business */
246     rv = apr_global_mutex_trylock(authn_cache_mutex);
247     if (APR_STATUS_IS_EBUSY(rv)) {
248         /* don't wait around; just abandon it */
249         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
250                       "authn credentials for %s not cached (mutex busy)", user);
251         return;
252     }
253     else if (rv != APR_SUCCESS) {
254         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
255                       "Failed to cache authn credentials for %s in %s",
256                       module, dcfg->context);
257         return;
258     }
259
260     /* We have the mutex, so go ahead */
261     /* first build our key and determine expiry time */
262     key = construct_key(r, dcfg->context, user, realm);
263     expiry = apr_time_now() + dcfg->timeout;
264
265     /* store it */
266     rv = socache_provider->store(socache_instance, r->server,
267                                  (unsigned char*)key, strlen(key), expiry,
268                                  (unsigned char*)data, strlen(data), r->pool);
269     if (rv == APR_SUCCESS) {
270         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
271                       "Cached authn credentials for %s in %s",
272                       user, dcfg->context);
273     }
274     else {
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     }
279
280     /* We're done with the mutex */
281     rv = apr_global_mutex_unlock(authn_cache_mutex);
282     if (rv != APR_SUCCESS) {
283         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "Failed to release mutex!");
284     }
285     return;
286 }
287
288 #define MAX_VAL_LEN 100
289 static authn_status check_password(request_rec *r, const char *user,
290                                    const char *password)
291 {
292
293     /* construct key
294      * look it up
295      * if found, test password
296      * 
297      * mutexing here would be a big performance drag.
298      * It's definitely unnecessary with some backends (like ndbm or gdbm)
299      * Is there a risk in the general case?  I guess the only risk we
300      * care about is a race condition that gets us a dangling pointer
301      * to no-longer-defined memory.  Hmmm ...
302      */
303     apr_status_t rv;
304     const char *key;
305     unsigned char val[MAX_VAL_LEN];
306     unsigned int vallen = MAX_VAL_LEN - 1;
307     authn_cache_dircfg *dcfg = ap_get_module_config(r->per_dir_config,
308                                                     &authn_socache_module);
309     key = construct_key(r, dcfg->context, user, NULL);
310     rv = socache_provider->retrieve(socache_instance, r->server,
311                                     (unsigned char*)key, strlen(key),
312                                     val, &vallen, r->pool);
313
314     if (APR_STATUS_IS_NOTFOUND(rv)) {
315         /* not found - just return */
316         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
317                       "Authn cache: no credentials found for %s", user);
318         return AUTH_USER_NOT_FOUND;
319     }
320     else if (rv == APR_SUCCESS) {
321         /* OK, we got a value */
322         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
323                       "Authn cache: found credentials for %s", user);
324         val[vallen] = 0;
325     }
326     else {
327         /* error: give up and pass the buck */
328         /* FIXME: getting this for NOTFOUND - prolly a bug in mod_socache */
329         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
330                       "Error accessing authentication cache");
331         return AUTH_USER_NOT_FOUND;
332     }
333
334     rv = apr_password_validate(password, (char*) val);
335     if (rv != APR_SUCCESS) {
336         return AUTH_DENIED;
337     }
338
339     return AUTH_GRANTED;
340 }
341
342 static authn_status get_realm_hash(request_rec *r, const char *user,
343                                    const char *realm, char **rethash)
344 {
345     apr_status_t rv;
346     const char *key;
347     authn_cache_dircfg *dcfg;
348     unsigned char val[MAX_VAL_LEN];
349     unsigned int vallen = MAX_VAL_LEN - 1;
350     dcfg = ap_get_module_config(r->per_dir_config, &authn_socache_module);
351     key = construct_key(r, dcfg->context, user, realm);
352     rv = socache_provider->retrieve(socache_instance, r->server,
353                                     (unsigned char*)key, strlen(key),
354                                     val, &vallen, r->pool);
355
356     if (APR_STATUS_IS_NOTFOUND(rv)) {
357         /* not found - just return */
358         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
359                       "Authn cache: no credentials found for %s", user);
360         return AUTH_USER_NOT_FOUND;
361     }
362     else if (rv == APR_SUCCESS) {
363         /* OK, we got a value */
364         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
365                       "Authn cache: found credentials for %s", user);
366         val[vallen] = 0;
367     }
368     else {
369         /* error: give up and pass the buck */
370         /* FIXME: getting this for NOTFOUND - prolly a bug in mod_socache */
371         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
372                       "Error accessing authentication cache");
373         return AUTH_USER_NOT_FOUND;
374     }
375     *rethash = (char*)val;
376
377     return AUTH_USER_FOUND;
378 }
379
380 static const authn_provider authn_cache_provider =
381 {
382     &check_password,
383     &get_realm_hash,
384 };
385 static void register_hooks(apr_pool_t *p)
386 {
387     ap_register_auth_provider(p, AUTHN_PROVIDER_GROUP, "socache",
388                               AUTHN_PROVIDER_VERSION,
389                               &authn_cache_provider, AP_AUTH_INTERNAL_PER_CONF);
390     APR_REGISTER_OPTIONAL_FN(ap_authn_cache_store);
391     ap_hook_pre_config(authn_cache_precfg, NULL, NULL, APR_HOOK_MIDDLE);
392     ap_hook_post_config(authn_cache_post_config, NULL, NULL, APR_HOOK_MIDDLE);
393     ap_hook_child_init(authn_cache_child_init, NULL, NULL, APR_HOOK_MIDDLE);
394 }
395
396 AP_DECLARE_MODULE(authn_socache) =
397 {
398     STANDARD20_MODULE_STUFF,
399     authn_cache_dircfg_create,
400     authn_cache_dircfg_merge,
401     NULL,
402     NULL,
403     authn_cache_cmds,
404     register_hooks
405 };