1 /**
2  *  Copyright 2005, Paul Querna
3  *
4  *  Licensed under the Apache License, Version 2.0 (the "License");
5  *  you may not use this file except in compliance with the License.
6  *  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 
18 #include "mod_auth_xradius.h"
19 
20 #include "apr_md5.h"
21 #include "util_md5.h"
22 
23 #if USING_2_1_RECENT
24 #include "ap_provider.h"
25 #include "mod_auth.h"
26 #endif
27 
28 /* All use of the RADIUS Library is contained to this file. */
29 #include "radlib.h"
30 
31 /* Macros used to simplify the setting of variables in the RADIUS Request */
32 #define _xrad_put_string(rvx, ctx, key, value)   \
33 rvx = xrad_put_string(ctx, key, value); \
34 if (rvx != 0) { \
35     ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, \
36                   "xradius: Failed to put "#key": (%d) %s", \
37                   rvx, xrad_strerror(rctx)); \
38                       goto run_cleanup; \
39 }
40 
41 #define _xrad_put_int(rvx, ctx, key, value)   \
42 rvx = xrad_put_int(ctx, key, value); \
43 if (rvx != 0) { \
44     ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, \
45                   "xradius: Failed to put "#key": (%d) %s", \
46                   rvx, xrad_strerror(rctx)); \
47                       goto run_cleanup; \
48 }
49 
50 apr_proc_mutex_t *gmutex;
51 static int use_mutex;
52 
53 /**
54  * This function does the actual validation of the submitted username and
55  * password.  The values in the username and password have already been vetted
56  * for bogus values / large values by the httpd core.
57  */
xrad_run_auth_check(request_rec * r,const char * user,const char * password)58 static int xrad_run_auth_check(request_rec* r, const char* user,
59                                const char* password)
60 {
61     int i;
62     int rc;
63     int can_cache = 0;
64     int ret = HTTP_UNAUTHORIZED;
65     struct xrad_handle* rctx = NULL;
66     xrad_server_info *sr;
67     apr_md5_ctx_t md5ctx;
68     char* digest = NULL;
69 
70     xrad_dirconf_rec *dc = ap_get_module_config(r->per_dir_config,
71                                                 &auth_xradius_module);
72 
73     xrad_serverconf_rec *sc = ap_get_module_config(r->server->module_config,
74                                                 &auth_xradius_module);
75 
76     /**
77      * If no RADIUS servers have been configured, we always deny access.
78      */
79     if (dc->servers == NULL || apr_is_empty_array(dc->servers)) {
80         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
81                       "xradius: no servers configured for authentication!");
82         return HTTP_UNAUTHORIZED;
83     }
84 
85     if (dc->reject_blank && strlen(password) == 0) {
86         return HTTP_UNAUTHORIZED;
87     }
88 
89     /*
90      */
91     if (use_mutex) {
92         apr_proc_mutex_unlock(gmutex);
93     }
94 
95     /**
96      * Step 1: Check the Positive and Negative Cache Backends.
97      *         Only one cache type can be active at a time.
98      */
99     if (sc->cache_type != xrad_cache_none) {
100         apr_md5_init(&md5ctx);
101         apr_md5_update(&md5ctx, password, strlen(password));
102         digest = ap_md5contextTo64(r->pool, &md5ctx);
103 
104         if (sc->cache_type == xrad_cache_dbm) {
105             rc = xrad_cache_dbm_check(r, sc, user, digest);
106             if (rc != DECLINED) {
107                 ret = rc;
108                 goto run_cleanup;
109             }
110         }
111 #if HAVE_APR_MEMCACHE
112         else if (sc->cache_type == xrad_cache_memcache) {
113             rc = xrad_cache_mc_check(r, sc, user, digest);
114             if (rc != DECLINED) {
115                 ret = rc;
116                 goto run_cleanup;
117             }
118         }
119 #endif
120     }
121     /**
122      * Step 2: The User/Password combination wasn't found in the database,
123      *          So we are going to use RADIUS for this request.
124      */
125     rctx = xrad_auth_open();
126 
127     /* Loop through the array of RADIUS Servers, adding them to the rctx object */
128     for (i = 0; i < dc->servers->nelts; ++i) {
129         sr = &(((xrad_server_info*)dc->servers->elts)[i]);
130         rc = xrad_add_server(rctx, sr->hostname, sr->port, sr->secret,
131                              dc->timeout, dc->maxtries);
132 
133         if (rc != 0) {
134             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
135                           "xradius: Failed to add server '%s:%d': (%d) %s",
136                           sr->hostname, sr->port, rc, xrad_strerror(rctx));
137             goto run_cleanup;
138         }
139     }
140 
141     /**
142      * Variables set for the RADIUS Authentication Request:
143      *      request type:   RAD_ACCESS_REQUEST;
144      *      service type:   RAD_SERVICE_TYPE     : RAD_AUTHENTICATE_ONLY
145      *      nas host:       RAD_NAS_IDENTIFIER   : r->hostname
146      *      nas Port:       RAD_NAS_PORT_TYPE    : RAD_VIRTUAL
147      *      username:       RAD_USER_NAME        : user
148      *      password:       RAD_PASSWORD         : password
149      */
150 
151     /* Step 2.1: Create the Access Request */
152     rc = xrad_create_request(rctx, RAD_ACCESS_REQUEST);
153     if (rc != 0) {
154         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
155                       "xradius: Failed to create request: (%d) %s",
156                       rc, xrad_strerror(rctx));
157         goto run_cleanup;
158     }
159 
160     /* Step 2.2: Put the Variables into the Request */
161     _xrad_put_int(rc, rctx, RAD_SERVICE_TYPE, RAD_AUTHENTICATE_ONLY);
162     _xrad_put_int(rc, rctx, RAD_NAS_PORT_TYPE, RAD_VIRTUAL);
163     _xrad_put_string(rc, rctx, RAD_USER_NAME, user);
164     _xrad_put_string(rc, rctx, RAD_NAS_IDENTIFIER, r->hostname);
165     _xrad_put_string(rc, rctx, RAD_USER_PASSWORD, password);
166 
167     /* Step 2.3: Send the Request to the server(s). This is a blocking Operation.*/
168     rc = xrad_send_request(rctx);
169 
170     /* Step 2.4: Check What the RADIUS Server said. */
171     if (rc == RAD_ACCESS_ACCEPT) {
172         /* An Accepted Client, make sure this result is cached. */
173 #if XRAD_DEBUG
174         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
175                       "xradius: '%s' -> RAD_ACCESS_ACCEPT",
176                       user);
177 #endif
178         can_cache = 1;
179         ret = OK;
180     }
181     else if (rc == RAD_ACCESS_REJECT) {
182         /* An Rejected Client. Commonly for the wrong password. */
183 #if XRAD_DEBUG
184         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
185                       "xradius: user '%s' was rejected by the server.",
186                       user);
187 #endif
188         ret = HTTP_UNAUTHORIZED;
189         can_cache = 1;
190         ap_note_basic_auth_failure(r);
191     }
192     else if (rc == RAD_ACCESS_CHALLENGE) {
193         /**
194          * libradius does not ever return 'RAD_ACCESS_CHALLENGE',
195          * But, it is documented as a possible return value.  We handle it here,
196          * in the case it is ever implemented.
197          */
198         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
199                       "xradius: user '%s' retutned CHALLENGE. Fatal Error.",
200                       user);
201         ret = HTTP_UNAUTHORIZED;
202         ap_note_basic_auth_failure(r);
203     }
204     else {
205         /**
206          * This is the catch all.  Most common cause is we could not contact
207          * the RADIUS server.  Default to DENY Access.
208          */
209         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
210                       "xradius: RADIUS Request for user '%s' failed: (%d) %s",
211                       user, rc, xrad_strerror(rctx));
212         ret = HTTP_UNAUTHORIZED;
213         ap_note_basic_auth_failure(r);
214     }
215 
216     /* Step 3: Store Result into the Cache. */
217     if (can_cache) {
218         if (sc->cache_type == xrad_cache_dbm) {
219             rc = xrad_cache_dbm_store(r, sc, user, digest, ret);
220         }
221 #if HAVE_APR_MEMCACHE
222         else if (sc->cache_type == xrad_cache_memcache) {
223             rc = xrad_cache_mc_store(r, sc, user, digest, ret);
224         }
225 #endif
226     }
227 
228 run_cleanup:
229     if (rctx) {
230         /* Cleanup the Resources used by libradius */
231         xrad_close(rctx);
232     }
233 
234     if (use_mutex) {
235         apr_proc_mutex_lock(gmutex);
236     }
237 
238     return ret;
239 }
240 
241 #if USING_2_1_RECENT
242 
xrad_check_pw(request_rec * r,const char * user,const char * password)243 static authn_status xrad_check_pw(request_rec * r, const char *user,
244                                  const char *password)
245 {
246     authn_status arv;
247     int rv;
248 
249     rv = xrad_run_auth_check(r, user, password);
250 
251     if (rv == OK) {
252         arv = AUTH_GRANTED;
253     }
254     else {
255         /* Default to deny */
256         arv = AUTH_DENIED;
257     }
258 
259     /* TODO: Support AUTH_GENERAL_ERROR and AUTH_USER_NOT_FOUND */
260     return arv;
261 }
262 
263 #else /* Using 2.0.xx */
264 /* The Entry Point from Apache, for validating the User ID/Passsword */
xrad_check_user_id(request_rec * r)265 static int xrad_check_user_id(request_rec *r)
266 {
267     const char *sent_pw;
268     int rv;
269 
270     /**
271      * Fetch the password from the HTTP Headers.  If it was not supplied, this
272      * will return !OK, and prompt the client for their user/password
273      */
274     rv = ap_get_basic_auth_pw(r, &sent_pw);
275     if (rv != OK) {
276         return rv;
277     }
278 
279     /* The User did Submit a Username and Password, do the Authentication Check now. */
280     return xrad_run_auth_check(r, r->user, sent_pw);
281 }
282 
283 #endif
284 
285 /* Adds a RADIUS Server to the Directory Configuration */
xrad_conf_add_server(cmd_parms * parms,void * dummy,const char * server_addr,const char * secret)286 static const char *xrad_conf_add_server(cmd_parms * parms, void *dummy,
287                                         const char *server_addr, const char* secret)
288 {
289     xrad_dirconf_rec *dc = dummy;
290     apr_status_t rv;
291     char* scope_id;
292     xrad_server_info *sr;
293 
294     /* To properly use the Pools, this array is allocated from the here, instead of
295         inside the directory configuration creation function. */
296     if (dc->servers == NULL) {
297         dc->servers = apr_array_make(parms->pool, 4, sizeof(xrad_server_info));
298     }
299 
300     sr = apr_array_push(dc->servers);
301 
302     /**
303      * format like "radius.example.com:1183". This also understands IP Addresses
304      * and IPv6 Addresses.
305      */
306     rv = apr_parse_addr_port(&sr->hostname, &scope_id, &sr->port, server_addr,
307                              parms->pool);
308 
309     if (rv != APR_SUCCESS) {
310         /* We didn't use this space in the array. pop it off */
311         apr_array_pop(dc->servers);
312         return "AuthXRadiusAddServer: Invalid 'server' string.";
313     }
314 
315     if (sr->hostname == NULL) {
316         apr_array_pop(dc->servers);
317         return "AuthXRadiusAddServer: Invalid server string. No hostname found";
318     }
319 
320     if (sr->port == 0) {
321         sr->port = RAD_DEFAULT_PORT;
322     }
323 
324     sr->secret = apr_pstrdup(parms->pool, secret);
325     /* no error in the parameters */
326     return NULL;
327 }
328 
329 /* Sets the global cache timeout. */
xrad_conf_cache_timeout(cmd_parms * parms,void * dummy,const char * time)330 static const char *xrad_conf_cache_timeout(cmd_parms * parms, void *dummy,
331                                            const char *time)
332 {
333     const char* err;
334 
335     xrad_serverconf_rec *sc = ap_get_module_config(parms->server->module_config,
336                                                    &auth_xradius_module);
337 
338     /* The Cache Configuration Must take place in the global context only. */
339     if ((err = ap_check_cmd_context(parms, GLOBAL_ONLY))) {
340         return err;
341     }
342 
343     sc->cache_timeout = atoi(time);
344     return NULL;
345 }
346 
347 /* Sets the global cache timeout. */
xrad_conf_cache_mutex(cmd_parms * parms,void * dummy,const char * arg)348 static const char *xrad_conf_cache_mutex(cmd_parms * parms, void *dummy,
349                                            const char *arg)
350 {
351     const char* err;
352 
353     /* The Cache Configuration Must take place in the global context only. */
354     if ((err = ap_check_cmd_context(parms, GLOBAL_ONLY))) {
355         return err;
356     }
357 
358     if (strcasecmp("on", arg) == 0) {
359         use_mutex = 1;
360     }
361     else if (strcasecmp("off", arg) == 0) {
362         use_mutex = 0;
363     }
364     else {
365         return "AuthXRadiusCacheMutex: Argument must be 'on' or 'off'.";
366     }
367 
368     return NULL;
369 }
370 
371 /* Sets the Cache type, and the Cache Args */
xrad_conf_cache_conifg(cmd_parms * parms,void * dummy,const char * type,const char * arg)372 static const char *xrad_conf_cache_conifg(cmd_parms * parms, void *dummy,
373                                           const char *type, const char* arg)
374 {
375     const char* err;
376     xrad_serverconf_rec *sc = ap_get_module_config(parms->server->module_config,
377                                                    &auth_xradius_module);
378 
379     /* The Cache Configuration Must take place in the global context only. */
380     if ((err = ap_check_cmd_context(parms, GLOBAL_ONLY))) {
381         return err;
382     }
383 
384     if (strcasecmp("none", type) == 0) {
385         sc->cache_type = xrad_cache_none;
386     }
387     else if (strcasecmp("dbm", type) == 0) {
388         sc->cache_type = xrad_cache_dbm;
389     }
390 #if HAVE_APR_MEMCACHE
391     else if (strcasecmp("memcache", type) == 0) {
392         sc->cache_type = xrad_cache_memcache;
393     }
394 #endif
395     else {
396         return "Invalid Type for AuthXRadiusCache!";
397     }
398 
399     if (sc->cache_type == xrad_cache_dbm) {
400         /* The DBM Cache uses a possibly Relative File Path */
401         sc->cache_config = ap_server_root_relative(parms->pool, arg);
402     }
403     else {
404         sc->cache_config = apr_pstrdup(parms->pool, arg);
405     }
406 
407     return NULL;
408 }
409 
410 /* Allocate the Directory Configuration, and set default values. */
xrad_create_dirconf(apr_pool_t * p,char * dir)411 void *xrad_create_dirconf(apr_pool_t *p, char *dir)
412 {
413     xrad_dirconf_rec *dc = apr_palloc(p, sizeof(*dc));
414 
415     dc->reject_blank = 1;
416     dc->timeout = RAD_DEFAULT_TIMEOUT;
417     dc->maxtries = RAD_DEFAULT_MAX_TRIES;
418     dc->servers = NULL;
419     return dc;
420 }
421 
422 /* Allocate the Server Configuration, and set default values. */
xrad_create_serverconf(apr_pool_t * p,server_rec * s)423 static void *xrad_create_serverconf(apr_pool_t * p, server_rec * s)
424 {
425     xrad_serverconf_rec *sc = apr_pcalloc(p, sizeof(*sc));
426 
427     sc->cache_type = xrad_cache_none;
428     sc->cache_config = NULL;
429     sc->cache_timeout = RAD_DEFAULT_CACHE_TIMEOUT;
430     return sc;
431 }
432 
433 /**
434  * Since the Global Auth Cache should only be set globally,
435  * this propogates the global settings down to all children servers
436  */
xrad_merge_serverconf(apr_pool_t * p,void * basev,void * addv)437 void *xrad_merge_serverconf(apr_pool_t *p, void *basev, void *addv)
438 {
439     xrad_serverconf_rec *base = (xrad_serverconf_rec *)basev;
440     xrad_serverconf_rec *mrg  = apr_pcalloc(p, sizeof(*mrg));
441 
442     mrg->cache_type = base->cache_type;
443     mrg->cache_config = base->cache_config ? apr_pstrdup(p, base->cache_config) : NULL;
444     mrg->cache_timeout = base->cache_timeout;
445 
446     return mrg;
447 }
448 
449 
450 /* This is called by Apache every tiem after the configuration is read. */
xrad_post_config(apr_pool_t * p,apr_pool_t * plog,apr_pool_t * ptemp,server_rec * s)451 static int xrad_post_config(apr_pool_t* p, apr_pool_t* plog,
452                             apr_pool_t* ptemp,
453                             server_rec* s)
454 {
455     apr_status_t rv;
456     void *data = NULL;
457     int first_run = 0;
458     const char* userdata_key = "mod_auth_xradius_init";
459     xrad_serverconf_rec* sc;
460 
461     /**
462      * The First run of the configuration is rather useless.
463      * After running, everything is cleared.  This is used for testing,
464      * and making sure everything can be parsed.
465      */
466     apr_pool_userdata_get(&data, userdata_key, s->process->pool);
467     if (data == NULL) {
468         first_run = 1;
469         apr_pool_userdata_set((const void *)1, userdata_key,
470                               apr_pool_cleanup_null,
471                               s->process->pool);
472     }
473 
474     if (!first_run) {
475         sc = (xrad_serverconf_rec *) ap_get_module_config(s->module_config,
476                                                           &auth_xradius_module);
477         if (use_mutex) {
478             rv = apr_proc_mutex_create(&gmutex, NULL,
479                                    APR_LOCK_DEFAULT, s->process->pool);
480             if (rv != APR_SUCCESS) {
481                 ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
482                          "xradius: Cannot create Cache Process Lock: (%d)",
483                          rv);
484                 return rv;
485             }
486         }
487         if (sc->cache_type == xrad_cache_dbm) {
488             /**
489              * The DBM Cache requires some extra steps before the children fork
490              * and drop priviledges.
491              */
492             return xrad_cache_dbm_post_config(ptemp, s, sc);
493         }
494     }
495 
496     return OK;
497 }
498 
499 
xrad_child_init(apr_pool_t * p,server_rec * s)500 static void xrad_child_init(apr_pool_t *p, server_rec *s)
501 {
502     apr_status_t rv;
503     xrad_serverconf_rec *sc = ap_get_module_config(s->module_config,
504                                                    &auth_xradius_module);
505     if (use_mutex) {
506         rv = apr_proc_mutex_child_init(&gmutex, NULL, s->process->pool);
507 
508         if (rv != APR_SUCCESS) {
509             ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
510                          "xradius: Cannot connect to Cache Process Lock in child: (%d)",
511                          rv);
512         }
513     }
514 
515     if (sc->cache_type == xrad_cache_dbm) {
516         /* noop */
517     }
518 #if HAVE_APR_MEMCACHE
519     else if (sc->cache_type == xrad_cache_memcache) {
520         /* This Creates the memcache object inside every child process. */
521         xrad_cache_mc_child_init(p, s, sc);
522     }
523 #endif
524 }
525 
526 #if USING_2_1_RECENT
527 static const authn_provider authn_xradius_provider = {
528     &xrad_check_pw,
529     NULL
530 };
531 #endif
532 
533 /* Tell Apache Which Parts we want to Control Ourselves. */
xrad_hooks(apr_pool_t * p)534 static void xrad_hooks(apr_pool_t * p)
535 {
536     use_mutex = 0;
537 
538     ap_hook_post_config(xrad_post_config, NULL, NULL, APR_HOOK_MIDDLE);
539     ap_hook_child_init(xrad_child_init, NULL, NULL, APR_HOOK_MIDDLE);
540 #if USING_2_1_RECENT
541     ap_register_provider(p, AUTHN_PROVIDER_GROUP, "xradius", "0",
542                          &authn_xradius_provider);
543 #else
544     ap_hook_check_user_id(xrad_check_user_id, NULL, NULL, APR_HOOK_MIDDLE);
545 #endif
546 }
547 
548 /* Our Configuration Commands */
549 static const command_rec xrad_cmds[] = {
550     AP_INIT_TAKE2("AuthXRadiusCache", xrad_conf_cache_conifg,
551                   NULL,
552                   RSRC_CONF,
553                   "Configure the Caching System"),
554     AP_INIT_TAKE1("AuthXRadiusCacheTimeout", xrad_conf_cache_timeout,
555                   NULL,
556                   RSRC_CONF,
557                   "Set the Timeout for the Cache"),
558     AP_INIT_TAKE1("AuthXRadiusCacheMutex", xrad_conf_cache_mutex,
559                   NULL,
560                   RSRC_CONF,
561                   "Set the Timeout for the Cache"),
562     AP_INIT_TAKE2("AuthXRadiusAddServer", xrad_conf_add_server,
563                   NULL,
564                   OR_AUTHCFG,
565                   "Add a RADIUS Server to try for Authentication"),
566     AP_INIT_TAKE1("AuthXRadiusTimeout", ap_set_int_slot,
567                   (void*)APR_OFFSETOF(xrad_dirconf_rec, timeout),
568                   OR_AUTHCFG,
569                   "Set the Timeout for Connecting to a RADIUS Server."),
570     AP_INIT_TAKE1("AuthXRadiusRetries", ap_set_int_slot,
571                   (void*)APR_OFFSETOF(xrad_dirconf_rec, maxtries),
572                   OR_AUTHCFG,
573                   "Set the Number of Retries for connecting to a RADIUS Server."),
574     AP_INIT_FLAG("AuthXRadiusRejectBlank", ap_set_flag_slot,
575                   (void*)APR_OFFSETOF(xrad_dirconf_rec, reject_blank),
576                   OR_AUTHCFG,
577                   "Reject all Blank Passwords from users."),
578     {NULL}
579 };
580 
581 /* The Module Definition */
582 module AP_MODULE_DECLARE_DATA auth_xradius_module = {
583     STANDARD20_MODULE_STUFF,
584     xrad_create_dirconf,
585     NULL,
586     xrad_create_serverconf,
587     xrad_merge_serverconf,
588     xrad_cmds,
589     xrad_hooks
590 };
591