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