6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * Copyright 2000,2006 The FreeRADIUS server project
21 * Copyright 2000 Miquel van Smoorenburg <miquels@cistron.nl>
22 * Copyright 2000 Chris Parker <cparker@starnetusa.com>
25 #include <freeradius-devel/ident.h>
28 #include <freeradius-devel/autoconf.h>
30 #include <sys/socket.h>
32 #ifdef HAVE_NETINET_IN_H
33 # include <netinet/in.h>
41 #include <freeradius-devel/radiusd.h>
42 #include <freeradius-devel/rad_assert.h>
43 #include <freeradius-devel/modules.h>
44 #include <freeradius-devel/request_list.h>
47 * We received a response from a remote radius server.
48 * Call the post-proxy modules.
50 int proxy_receive(REQUEST *request)
53 int post_proxy_type = 0;
57 * Delete any reply we had accumulated until now.
59 pairfree(&request->reply->vps);
62 * Run the packet through the post-proxy stage,
63 * BEFORE playing games with the attributes.
65 vp = pairfind(request->config_items, PW_POST_PROXY_TYPE);
67 DEBUG2(" Found Post-Proxy-Type %s", vp->vp_strvalue);
68 post_proxy_type = vp->lvalue;
70 rcode = module_post_proxy(post_proxy_type, request);
73 * Delete the Proxy-State Attributes from the reply.
74 * These include Proxy-State attributes from us and
77 pairdelete(&request->proxy_reply->vps, PW_PROXY_STATE);
80 * Add the attributes left in the proxy reply to
83 pairadd(&request->reply->vps, request->proxy_reply->vps);
84 request->proxy_reply->vps = NULL;
87 * Free proxy request pairs.
89 pairfree(&request->proxy->vps);
92 * FIXME: If the packet is an Access-Challenge,
93 * THEN add it to a cache, which does:
95 * (src IP, State) -> (home server ip/port)
97 * This allows the load-balancing code to
100 * Alternately, we can delete the State from the home
101 * server, and use our own.. that might be better.
108 * Add a proxy-pair to the end of the request.
110 static void proxy_addinfo(REQUEST *request)
112 VALUE_PAIR *proxy_pair;
114 proxy_pair = paircreate(PW_PROXY_STATE, PW_TYPE_STRING);
115 if (proxy_pair == NULL) {
116 radlog(L_ERR|L_CONS, "no memory");
119 sprintf(proxy_pair->vp_strvalue, "%d", request->packet->id);
120 proxy_pair->length = strlen(proxy_pair->vp_strvalue);
122 pairadd(&request->proxy->vps, proxy_pair);
127 * Like realm find, but does load balancing, and we don't
128 * wake up any sleeping realms. Someone should already have
131 * It also does NOT do fail-over to default if the realms are dead,
132 * as that decision has already been made.
134 static REALM *proxy_realm_ldb(REQUEST *request, const char *realm_name,
141 * FIXME: If the packet contains a State attribute,
142 * AND the realm is load-balance,
143 * AND there is a matching
144 * State attribute in the cached entry, THEN proxy it to
150 for (cl = mainconfig.realms; cl; cl = cl->next) {
152 * Wake up any sleeping realm.
154 * Note that the 'realm find' function will only
155 * wake up the FIRST realm which matches. We've
156 * got to wake up ALL of the matching realms.
158 if (cl->wakeup <= request->timestamp) {
161 if (cl->acct_wakeup <= request->timestamp) {
162 cl->acct_active = TRUE;
166 * Asked for auth/acct, and the auth/acct server
167 * is not active. Skip it.
169 if ((!accounting && !cl->active) ||
170 (accounting && !cl->acct_active)) {
175 * The realm name doesn't match, skip it.
177 if (strcasecmp(cl->realm, realm_name) != 0) {
182 * Fail-over, pick the first one that matches.
184 if ((count == 0) && /* if size > 0, we have round-robin */
190 * We're doing load-balancing. Pick a random
191 * number, which will be used to determine which
192 * home server is chosen.
201 * Keep track of how many load balancing servers
202 * we've gone through.
207 * See the "camel book" for why this works.
209 * If (rand(0..n) < 1), pick the current realm.
210 * We add a scale factor of 65536, to avoid
213 if ((count * (lrad_rand() & 0xffff)) < (uint32_t) 0x10000) {
216 } /* loop over the realms */
219 * Return the load-balanced realm.
225 * Relay the request to a remote server.
228 * RLM_MODULE_FAIL: we don't reply, caller returns without replying
229 * RLM_MODULE_NOOP: caller falls through to normal processing
230 * RLM_MODULE_HANDLED : we reply, caller returns without replying
232 int proxy_send(REQUEST *request)
235 int pre_proxy_type = 0;
236 VALUE_PAIR *realmpair;
237 VALUE_PAIR *strippedname;
243 * Not authentication or accounting. Stop it.
245 if ((request->packet->code != PW_AUTHENTICATION_REQUEST) &&
246 (request->packet->code != PW_ACCOUNTING_REQUEST)) {
247 DEBUG2(" ERROR: Cannot proxy packets of type %d",
248 request->packet->code);
249 return RLM_MODULE_FAIL;
253 * The timestamp is used below to figure the
254 * next_try. The request needs to "hang around" until
255 * either the other server sends a reply or the retry
256 * count has been exceeded. Until then, it should not
257 * be eligible for the time-based cleanup. --Pac. */
259 realmpair = pairfind(request->config_items, PW_PROXY_TO_REALM);
262 * Not proxying, so we can exit from the proxy
265 return RLM_MODULE_NOOP;
269 * If the server has already decided to reject the request,
270 * then don't try to proxy it.
272 if (request->reply->code == PW_AUTHENTICATION_REJECT) {
273 DEBUG2("Cancelling proxy as request was already rejected");
274 return RLM_MODULE_REJECT;
276 if (((vp = pairfind(request->config_items, PW_AUTH_TYPE)) != NULL) &&
277 (vp->lvalue == PW_AUTHTYPE_REJECT)) {
278 DEBUG2("Cancelling proxy as request was already rejected");
279 return RLM_MODULE_REJECT;
282 * Length == 0 means it exists, but there's no realm.
285 if (realmpair->length == 0) {
286 return RLM_MODULE_NOOP;
289 realmname = (char *)realmpair->vp_strvalue;
292 * Look for the realm, using the load balancing
293 * version of realm find.
295 realm = proxy_realm_ldb(request, realmname,
296 (request->packet->code == PW_ACCOUNTING_REQUEST));
298 DEBUG2(" ERROR: Failed to find live home server for realm %s",
300 return RLM_MODULE_FAIL;
304 * Remember that we sent the request to a Realm.
306 pairadd(&request->packet->vps,
307 pairmake("Realm", realm->realm, T_OP_EQ));
310 * Access-Request: look for LOCAL realm.
311 * Accounting-Request: look for LOCAL realm.
313 if (((request->packet->code == PW_AUTHENTICATION_REQUEST) &&
314 (realm->ipaddr.af == AF_INET) &&
315 (realm->ipaddr.ipaddr.ip4addr.s_addr == htonl(INADDR_NONE))) ||
316 ((request->packet->code == PW_ACCOUNTING_REQUEST) &&
317 (realm->acct_ipaddr.af == AF_INET) &&
318 (realm->acct_ipaddr.ipaddr.ip4addr.s_addr == htonl(INADDR_NONE)))) {
319 DEBUG2(" WARNING: Cancelling proxy to Realm %s, as the realm is local.",
321 return RLM_MODULE_NOOP;
325 * This is mainly for radrelay. Don't proxy packets back
326 * to servers which sent them to us.
328 if ((request->packet->code == PW_ACCOUNTING_REQUEST) &&
329 (request->listener->type == RAD_LISTEN_DETAIL) &&
330 (realm->acct_ipaddr.af == AF_INET) &&
331 (request->packet->src_ipaddr.af == AF_INET) &&
332 (realm->acct_ipaddr.ipaddr.ip4addr.s_addr == request->packet->src_ipaddr.ipaddr.ip4addr.s_addr)) {
333 DEBUG2(" rlm_realm: Packet came from realm %s, proxy cancelled", realm->realm);
334 return RLM_MODULE_NOOP;
338 * Allocate the proxy packet, only if it wasn't already
339 * allocated by a module. This check is mainly to support
340 * the proxying of EAP-TTLS and EAP-PEAP tunneled requests.
342 * In those cases, the EAP module creates a "fake"
343 * request, and recursively passes it through the
344 * authentication stage of the server. The module then
345 * checks if the request was supposed to be proxied, and
346 * if so, creates a proxy packet from the TUNNELED request,
347 * and not from the EAP request outside of the tunnel.
349 * The proxy then works like normal, except that the response
350 * packet is "eaten" by the EAP module, and encapsulated into
353 if (!request->proxy) {
355 * Now build a new RADIUS_PACKET.
357 * FIXME: it could be that the id wraps around
358 * too fast if we have a lot of requests, it
359 * might be better to keep a seperate ID value
362 * OTOH the remote radius server should be smart
363 * enough to compare _both_ ID and vector.
366 if ((request->proxy = rad_alloc(TRUE)) == NULL) {
367 radlog(L_ERR|L_CONS, "no memory");
372 * We now massage the attributes to be proxied...
376 * Copy the request, then look up name and
377 * plain-text password in the copy.
379 * Note that the User-Name attribute is the
380 * *original* as sent over by the client. The
381 * Stripped-User-Name attribute is the one hacked
382 * through the 'hints' file.
384 request->proxy->vps = paircopy(request->packet->vps);
388 * Strip the name, if told to.
390 * Doing it here catches the case of proxied tunneled
393 if (realm->striprealm == TRUE &&
394 (strippedname = pairfind(request->proxy->vps, PW_STRIPPED_USER_NAME)) != NULL) {
396 * If there's a Stripped-User-Name attribute in
397 * the request, then use THAT as the User-Name
398 * for the proxied request, instead of the
401 * This is done by making a copy of the
402 * Stripped-User-Name attribute, turning it into
403 * a User-Name attribute, deleting the
404 * Stripped-User-Name and User-Name attributes
405 * from the vps list, and making the new
406 * User-Name the head of the vps list.
408 vp = pairfind(request->proxy->vps, PW_USER_NAME);
410 vp = paircreate(PW_USER_NAME, PW_TYPE_STRING);
412 radlog(L_ERR|L_CONS, "no memory");
415 vp->next = request->proxy->vps;
416 request->proxy->vps = vp;
418 memcpy(vp->vp_strvalue, strippedname->vp_strvalue,
419 sizeof(vp->vp_strvalue));
420 vp->length = strippedname->length;
423 * Do NOT delete Stripped-User-Name.
428 * If there is no PW_CHAP_CHALLENGE attribute but
429 * there is a PW_CHAP_PASSWORD we need to add it
430 * since we can't use the request authenticator
431 * anymore - we changed it.
433 if (pairfind(request->proxy->vps, PW_CHAP_PASSWORD) &&
434 pairfind(request->proxy->vps, PW_CHAP_CHALLENGE) == NULL) {
435 vp = paircreate(PW_CHAP_CHALLENGE, PW_TYPE_STRING);
437 radlog(L_ERR|L_CONS, "no memory");
440 vp->length = AUTH_VECTOR_LEN;
441 memcpy(vp->vp_strvalue, request->packet->vector, AUTH_VECTOR_LEN);
442 pairadd(&(request->proxy->vps), vp);
445 request->proxy->code = request->packet->code;
446 if (request->packet->code == PW_AUTHENTICATION_REQUEST) {
447 request->proxy->dst_port = realm->auth_port;
448 request->proxy->dst_ipaddr = realm->ipaddr;
449 } else if (request->packet->code == PW_ACCOUNTING_REQUEST) {
450 request->proxy->dst_port = realm->acct_port;
451 request->proxy->dst_ipaddr = realm->acct_ipaddr;
455 * Add PROXY_STATE attribute, before pre-proxy stage,
456 * so the pre-proxy modules have access to it.
458 * Note that, at this point, the proxied request HAS NOT
459 * been assigned a RADIUS Id.
461 proxy_addinfo(request);
464 * Set up for sending the request.
466 memcpy(request->proxysecret, realm->secret, sizeof(request->proxysecret));
467 request->proxy_try_count = mainconfig.proxy_retry_count - 1;
470 if (request->packet->code == PW_ACCOUNTING_REQUEST) {
471 vp = pairfind(request->proxy->vps, PW_ACCT_DELAY_TIME);
474 request->proxy->timestamp = request->timestamp - vp->lvalue;
476 request->proxy->timestamp = request->timestamp;
478 request->proxy_start_time = request->timestamp;
483 vp = pairfind(request->config_items, PW_PRE_PROXY_TYPE);
485 DEBUG2(" Found Pre-Proxy-Type %s", vp->vp_strvalue);
486 pre_proxy_type = vp->lvalue;
488 rcode = module_pre_proxy(pre_proxy_type, request);
491 * Only proxy the packet if the pre-proxy code succeeded.
493 case RLM_MODULE_NOOP:
495 case RLM_MODULE_UPDATED:
497 * Delay sending the proxy packet until after we've
498 * done the work above, playing with the request.
500 * After this point, it becomes dangerous to play with
501 * the request data structure, as the reply MAY come in
502 * and get processed before we're done with it here.
504 request->options |= RAD_REQUEST_OPTION_PROXIED;
507 * If it's a fake request, don't send the proxy
508 * packet. The outer tunnel session will take
509 * care of doing that.
511 if ((request->options & RAD_REQUEST_OPTION_FAKE_REQUEST) == 0) {
513 * Add the proxied request to the
514 * list of outstanding proxied
515 * requests, BEFORE we send it, so
516 * we have fewer problems with race
517 * conditions when the responses come
520 if (!rl_add_proxy(request)) {
521 DEBUG("ERROR: Failed to proxy request %d",
523 return RLM_MODULE_FAIL; /* caller doesn't reply */
527 * We're still running, encode & sign the
528 * packet outside of the critical section.
530 if (request->child_pid != NO_SUCH_CHILD_PID) {
531 rad_encode(request->proxy, NULL,
532 (char *)request->proxysecret);
533 rad_sign(request->proxy, NULL,
534 (char *)request->proxysecret);
536 request->proxy_listener->send(request->proxy_listener,
540 rcode = RLM_MODULE_HANDLED; /* caller doesn't reply */
543 * The module handled the request, don't reply.
545 case RLM_MODULE_HANDLED:
548 * Neither proxy, nor reply to invalid requests.
550 case RLM_MODULE_FAIL:
551 case RLM_MODULE_INVALID:
552 case RLM_MODULE_NOTFOUND:
553 case RLM_MODULE_REJECT:
554 case RLM_MODULE_USERLOCK:
556 rcode = RLM_MODULE_FAIL; /* caller doesn't reply */
561 * Do NOT free request->proxy->vps, the pairs are needed