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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 * Copyright 2000 The FreeRADIUS server project
21 * Copyright 2000 Miquel van Smoorenburg <miquels@cistron.nl>
22 * Copyright 2000 Chris Parker <cparker@starnetusa.com>
25 static const char rcsid[] = "$Id$";
27 #include <freeradius-devel/autoconf.h>
29 #include <sys/socket.h>
31 #ifdef HAVE_NETINET_IN_H
32 # include <netinet/in.h>
40 #include <freeradius-devel/radiusd.h>
41 #include <freeradius-devel/rad_assert.h>
42 #include <freeradius-devel/modules.h>
43 #include <freeradius-devel/request_list.h>
46 * We received a response from a remote radius server.
47 * Call the post-proxy modules.
49 int proxy_receive(REQUEST *request)
52 int post_proxy_type = 0;
56 * Delete any reply we had accumulated until now.
58 pairfree(&request->reply->vps);
61 * Run the packet through the post-proxy stage,
62 * BEFORE playing games with the attributes.
64 vp = pairfind(request->config_items, PW_POST_PROXY_TYPE);
66 DEBUG2(" Found Post-Proxy-Type %s", vp->vp_strvalue);
67 post_proxy_type = vp->lvalue;
69 rcode = module_post_proxy(post_proxy_type, request);
72 * Delete the Proxy-State Attributes from the reply.
73 * These include Proxy-State attributes from us and
76 pairdelete(&request->proxy_reply->vps, PW_PROXY_STATE);
79 * Add the attributes left in the proxy reply to
82 pairadd(&request->reply->vps, request->proxy_reply->vps);
83 request->proxy_reply->vps = NULL;
86 * Free proxy request pairs.
88 pairfree(&request->proxy->vps);
91 * FIXME: If the packet is an Access-Challenge,
92 * THEN add it to a cache, which does:
94 * (src IP, State) -> (home server ip/port)
96 * This allows the load-balancing code to
99 * Alternately, we can delete the State from the home
100 * server, and use our own.. that might be better.
107 * Add a proxy-pair to the end of the request.
109 static void proxy_addinfo(REQUEST *request)
111 VALUE_PAIR *proxy_pair;
113 proxy_pair = paircreate(PW_PROXY_STATE, PW_TYPE_STRING);
114 if (proxy_pair == NULL) {
115 radlog(L_ERR|L_CONS, "no memory");
118 sprintf(proxy_pair->vp_strvalue, "%d", request->packet->id);
119 proxy_pair->length = strlen(proxy_pair->vp_strvalue);
121 pairadd(&request->proxy->vps, proxy_pair);
126 * Like realm find, but does load balancing, and we don't
127 * wake up any sleeping realms. Someone should already have
130 * It also does NOT do fail-over to default if the realms are dead,
131 * as that decision has already been made.
133 static REALM *proxy_realm_ldb(REQUEST *request, const char *realm_name,
140 * FIXME: If the packet contains a State attribute,
141 * AND the realm is load-balance,
142 * AND there is a matching
143 * State attribute in the cached entry, THEN proxy it to
149 for (cl = mainconfig.realms; cl; cl = cl->next) {
151 * Wake up any sleeping realm.
153 * Note that the 'realm find' function will only
154 * wake up the FIRST realm which matches. We've
155 * got to wake up ALL of the matching realms.
157 if (cl->wakeup <= request->timestamp) {
160 if (cl->acct_wakeup <= request->timestamp) {
161 cl->acct_active = TRUE;
165 * Asked for auth/acct, and the auth/acct server
166 * is not active. Skip it.
168 if ((!accounting && !cl->active) ||
169 (accounting && !cl->acct_active)) {
174 * The realm name doesn't match, skip it.
176 if (strcasecmp(cl->realm, realm_name) != 0) {
181 * Fail-over, pick the first one that matches.
183 if ((count == 0) && /* if size > 0, we have round-robin */
189 * We're doing load-balancing. Pick a random
190 * number, which will be used to determine which
191 * home server is chosen.
200 * Keep track of how many load balancing servers
201 * we've gone through.
206 * See the "camel book" for why this works.
208 * If (rand(0..n) < 1), pick the current realm.
209 * We add a scale factor of 65536, to avoid
212 if ((count * (lrad_rand() & 0xffff)) < (uint32_t) 0x10000) {
215 } /* loop over the realms */
218 * Return the load-balanced realm.
224 * Relay the request to a remote server.
227 * RLM_MODULE_FAIL: we don't reply, caller returns without replying
228 * RLM_MODULE_NOOP: caller falls through to normal processing
229 * RLM_MODULE_HANDLED : we reply, caller returns without replying
231 int proxy_send(REQUEST *request)
234 int pre_proxy_type = 0;
235 VALUE_PAIR *realmpair;
236 VALUE_PAIR *strippedname;
242 * Not authentication or accounting. Stop it.
244 if ((request->packet->code != PW_AUTHENTICATION_REQUEST) &&
245 (request->packet->code != PW_ACCOUNTING_REQUEST)) {
246 DEBUG2(" ERROR: Cannot proxy packets of type %d",
247 request->packet->code);
248 return RLM_MODULE_FAIL;
252 * The timestamp is used below to figure the
253 * next_try. The request needs to "hang around" until
254 * either the other server sends a reply or the retry
255 * count has been exceeded. Until then, it should not
256 * be eligible for the time-based cleanup. --Pac. */
258 realmpair = pairfind(request->config_items, PW_PROXY_TO_REALM);
261 * Not proxying, so we can exit from the proxy
264 return RLM_MODULE_NOOP;
268 * If the server has already decided to reject the request,
269 * then don't try to proxy it.
271 if (request->reply->code == PW_AUTHENTICATION_REJECT) {
272 DEBUG2("Cancelling proxy as request was already rejected");
273 return RLM_MODULE_REJECT;
275 if (((vp = pairfind(request->config_items, PW_AUTH_TYPE)) != NULL) &&
276 (vp->lvalue == PW_AUTHTYPE_REJECT)) {
277 DEBUG2("Cancelling proxy as request was already rejected");
278 return RLM_MODULE_REJECT;
281 * Length == 0 means it exists, but there's no realm.
284 if (realmpair->length == 0) {
285 return RLM_MODULE_NOOP;
288 realmname = (char *)realmpair->vp_strvalue;
291 * Look for the realm, using the load balancing
292 * version of realm find.
294 realm = proxy_realm_ldb(request, realmname,
295 (request->packet->code == PW_ACCOUNTING_REQUEST));
297 DEBUG2(" ERROR: Failed to find live home server for realm %s",
299 return RLM_MODULE_FAIL;
303 * Remember that we sent the request to a Realm.
305 pairadd(&request->packet->vps,
306 pairmake("Realm", realm->realm, T_OP_EQ));
309 * Access-Request: look for LOCAL realm.
310 * Accounting-Request: look for LOCAL realm.
312 if (((request->packet->code == PW_AUTHENTICATION_REQUEST) &&
313 (realm->ipaddr.af == AF_INET) &&
314 (realm->ipaddr.ipaddr.ip4addr.s_addr == htonl(INADDR_NONE))) ||
315 ((request->packet->code == PW_ACCOUNTING_REQUEST) &&
316 (realm->acct_ipaddr.af == AF_INET) &&
317 (realm->acct_ipaddr.ipaddr.ip4addr.s_addr == htonl(INADDR_NONE)))) {
318 DEBUG2(" WARNING: Cancelling proxy to Realm %s, as the realm is local.",
320 return RLM_MODULE_NOOP;
324 * This is mainly for radrelay. Don't proxy packets back
325 * to servers which sent them to us.
327 if ((request->packet->code == PW_ACCOUNTING_REQUEST) &&
328 (request->listener->type == RAD_LISTEN_DETAIL) &&
329 (realm->acct_ipaddr.af == AF_INET) &&
330 (request->packet->src_ipaddr.af == AF_INET) &&
331 (realm->acct_ipaddr.ipaddr.ip4addr.s_addr == request->packet->src_ipaddr.ipaddr.ip4addr.s_addr)) {
332 DEBUG2(" rlm_realm: Packet came from realm %s, proxy cancelled", realm->realm);
333 return RLM_MODULE_NOOP;
337 * Allocate the proxy packet, only if it wasn't already
338 * allocated by a module. This check is mainly to support
339 * the proxying of EAP-TTLS and EAP-PEAP tunneled requests.
341 * In those cases, the EAP module creates a "fake"
342 * request, and recursively passes it through the
343 * authentication stage of the server. The module then
344 * checks if the request was supposed to be proxied, and
345 * if so, creates a proxy packet from the TUNNELED request,
346 * and not from the EAP request outside of the tunnel.
348 * The proxy then works like normal, except that the response
349 * packet is "eaten" by the EAP module, and encapsulated into
352 if (!request->proxy) {
354 * Now build a new RADIUS_PACKET.
356 * FIXME: it could be that the id wraps around
357 * too fast if we have a lot of requests, it
358 * might be better to keep a seperate ID value
361 * OTOH the remote radius server should be smart
362 * enough to compare _both_ ID and vector.
365 if ((request->proxy = rad_alloc(TRUE)) == NULL) {
366 radlog(L_ERR|L_CONS, "no memory");
371 * We now massage the attributes to be proxied...
375 * Copy the request, then look up name and
376 * plain-text password in the copy.
378 * Note that the User-Name attribute is the
379 * *original* as sent over by the client. The
380 * Stripped-User-Name attribute is the one hacked
381 * through the 'hints' file.
383 request->proxy->vps = paircopy(request->packet->vps);
387 * Strip the name, if told to.
389 * Doing it here catches the case of proxied tunneled
392 if (realm->striprealm == TRUE &&
393 (strippedname = pairfind(request->proxy->vps, PW_STRIPPED_USER_NAME)) != NULL) {
395 * If there's a Stripped-User-Name attribute in
396 * the request, then use THAT as the User-Name
397 * for the proxied request, instead of the
400 * This is done by making a copy of the
401 * Stripped-User-Name attribute, turning it into
402 * a User-Name attribute, deleting the
403 * Stripped-User-Name and User-Name attributes
404 * from the vps list, and making the new
405 * User-Name the head of the vps list.
407 vp = pairfind(request->proxy->vps, PW_USER_NAME);
409 vp = paircreate(PW_USER_NAME, PW_TYPE_STRING);
411 radlog(L_ERR|L_CONS, "no memory");
414 vp->next = request->proxy->vps;
415 request->proxy->vps = vp;
417 memcpy(vp->vp_strvalue, strippedname->vp_strvalue,
418 sizeof(vp->vp_strvalue));
419 vp->length = strippedname->length;
422 * Do NOT delete Stripped-User-Name.
427 * If there is no PW_CHAP_CHALLENGE attribute but
428 * there is a PW_CHAP_PASSWORD we need to add it
429 * since we can't use the request authenticator
430 * anymore - we changed it.
432 if (pairfind(request->proxy->vps, PW_CHAP_PASSWORD) &&
433 pairfind(request->proxy->vps, PW_CHAP_CHALLENGE) == NULL) {
434 vp = paircreate(PW_CHAP_CHALLENGE, PW_TYPE_STRING);
436 radlog(L_ERR|L_CONS, "no memory");
439 vp->length = AUTH_VECTOR_LEN;
440 memcpy(vp->vp_strvalue, request->packet->vector, AUTH_VECTOR_LEN);
441 pairadd(&(request->proxy->vps), vp);
444 request->proxy->code = request->packet->code;
445 if (request->packet->code == PW_AUTHENTICATION_REQUEST) {
446 request->proxy->dst_port = realm->auth_port;
447 request->proxy->dst_ipaddr = realm->ipaddr;
448 } else if (request->packet->code == PW_ACCOUNTING_REQUEST) {
449 request->proxy->dst_port = realm->acct_port;
450 request->proxy->dst_ipaddr = realm->acct_ipaddr;
454 * Add PROXY_STATE attribute, before pre-proxy stage,
455 * so the pre-proxy modules have access to it.
457 * Note that, at this point, the proxied request HAS NOT
458 * been assigned a RADIUS Id.
460 proxy_addinfo(request);
463 * Set up for sending the request.
465 memcpy(request->proxysecret, realm->secret, sizeof(request->proxysecret));
466 request->proxy_try_count = mainconfig.proxy_retry_count - 1;
469 if (request->packet->code == PW_ACCOUNTING_REQUEST) {
470 vp = pairfind(request->proxy->vps, PW_ACCT_DELAY_TIME);
473 request->proxy->timestamp = request->timestamp - vp->lvalue;
475 request->proxy->timestamp = request->timestamp;
477 request->proxy_start_time = request->timestamp;
482 vp = pairfind(request->config_items, PW_PRE_PROXY_TYPE);
484 DEBUG2(" Found Pre-Proxy-Type %s", vp->vp_strvalue);
485 pre_proxy_type = vp->lvalue;
487 rcode = module_pre_proxy(pre_proxy_type, request);
490 * Only proxy the packet if the pre-proxy code succeeded.
492 case RLM_MODULE_NOOP:
494 case RLM_MODULE_UPDATED:
496 * Delay sending the proxy packet until after we've
497 * done the work above, playing with the request.
499 * After this point, it becomes dangerous to play with
500 * the request data structure, as the reply MAY come in
501 * and get processed before we're done with it here.
503 request->options |= RAD_REQUEST_OPTION_PROXIED;
506 * If it's a fake request, don't send the proxy
507 * packet. The outer tunnel session will take
508 * care of doing that.
510 if ((request->options & RAD_REQUEST_OPTION_FAKE_REQUEST) == 0) {
512 * Add the proxied request to the
513 * list of outstanding proxied
514 * requests, BEFORE we send it, so
515 * we have fewer problems with race
516 * conditions when the responses come
519 if (!rl_add_proxy(request)) {
520 DEBUG("ERROR: Failed to proxy request %d",
522 return RLM_MODULE_FAIL; /* caller doesn't reply */
525 request->proxy_listener->send(request->proxy_listener,
528 rcode = RLM_MODULE_HANDLED; /* caller doesn't reply */
531 * The module handled the request, don't reply.
533 case RLM_MODULE_HANDLED:
536 * Neither proxy, nor reply to invalid requests.
538 case RLM_MODULE_FAIL:
539 case RLM_MODULE_INVALID:
540 case RLM_MODULE_NOTFOUND:
541 case RLM_MODULE_REJECT:
542 case RLM_MODULE_USERLOCK:
544 rcode = RLM_MODULE_FAIL; /* caller doesn't reply */
549 * Do NOT free request->proxy->vps, the pairs are needed