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