723a68704639688943169aa4d77f392605602ff5
[freeradius.git] / src / main / proxy.c
1 /*
2  * proxy.c      Proxy stuff.
3  *
4  * Version:     $Id$
5  *
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.
10  *
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.
15  *
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
19  *
20  * Copyright 2000  The FreeRADIUS server project
21  * Copyright 2000  Miquel van Smoorenburg <miquels@cistron.nl>
22  * Copyright 2000  Chris Parker <cparker@starnetusa.com>
23  */
24
25 static const char rcsid[] = "$Id$";
26
27 #include <freeradius-devel/autoconf.h>
28
29 #include <sys/socket.h>
30
31 #ifdef HAVE_NETINET_IN_H
32 #       include <netinet/in.h>
33 #endif
34
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <ctype.h>
38 #include <string.h>
39
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>
44
45 /*
46  *      We received a response from a remote radius server.
47  *      Call the post-proxy modules.
48  */
49 int proxy_receive(REQUEST *request)
50 {
51         int rcode;
52         int post_proxy_type = 0;
53         VALUE_PAIR *vp;
54
55         /*
56          *      Delete any reply we had accumulated until now.
57          */
58         pairfree(&request->reply->vps);
59
60         /*
61          *      Run the packet through the post-proxy stage,
62          *      BEFORE playing games with the attributes.
63          */
64         vp = pairfind(request->config_items, PW_POST_PROXY_TYPE);
65         if (vp) {
66                 DEBUG2("  Found Post-Proxy-Type %s", vp->vp_strvalue);
67                 post_proxy_type = vp->lvalue;
68         }
69         rcode = module_post_proxy(post_proxy_type, request);
70
71         /*
72          *      Delete the Proxy-State Attributes from the reply.
73          *      These include Proxy-State attributes from us and
74          *      remote server.
75          */
76         pairdelete(&request->proxy_reply->vps, PW_PROXY_STATE);
77
78         /*
79          *      Add the attributes left in the proxy reply to
80          *      the reply list.
81          */
82         pairadd(&request->reply->vps, request->proxy_reply->vps);
83         request->proxy_reply->vps = NULL;
84
85         /*
86          *      Free proxy request pairs.
87          */
88         pairfree(&request->proxy->vps);
89
90         /*
91          *      FIXME: If the packet is an Access-Challenge,
92          *      THEN add it to a cache, which does:
93          *
94          *      (src IP, State) -> (home server ip/port)
95          *
96          *      This allows the load-balancing code to
97          *      work for EAP...
98          *
99          *      Alternately, we can delete the State from the home
100          *      server, and use our own..  that might be better.
101          */
102
103         return rcode;
104 }
105
106 /*
107  *      Add a proxy-pair to the end of the request.
108  */
109 static void proxy_addinfo(REQUEST *request)
110 {
111         VALUE_PAIR *proxy_pair;
112
113         proxy_pair = paircreate(PW_PROXY_STATE, PW_TYPE_STRING);
114         if (proxy_pair == NULL) {
115                 radlog(L_ERR|L_CONS, "no memory");
116                 exit(1);
117         }
118         sprintf(proxy_pair->vp_strvalue, "%d", request->packet->id);
119         proxy_pair->length = strlen(proxy_pair->vp_strvalue);
120
121         pairadd(&request->proxy->vps, proxy_pair);
122 }
123
124
125 /*
126  *      Like realm find, but does load balancing, and we don't
127  *      wake up any sleeping realms.  Someone should already have
128  *      done that.
129  *
130  *      It also does NOT do fail-over to default if the realms are dead,
131  *      as that decision has already been made.
132  */
133 static REALM *proxy_realm_ldb(REQUEST *request, const char *realm_name,
134                               int accounting)
135 {
136         REALM           *cl, *lb;
137         uint32_t        count;
138
139         /*
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
144          *      that realm.
145          */
146
147         lb = NULL;
148         count = 0;
149         for (cl = mainconfig.realms; cl; cl = cl->next) {
150                 /*
151                  *      Wake up any sleeping realm.
152                  *
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.
156                  */
157                 if (cl->wakeup <= request->timestamp) {
158                         cl->active = TRUE;
159                 }
160                 if (cl->acct_wakeup <= request->timestamp) {
161                         cl->acct_active = TRUE;
162                 }
163
164                 /*
165                  *      Asked for auth/acct, and the auth/acct server
166                  *      is not active.  Skip it.
167                  */
168                 if ((!accounting && !cl->active) ||
169                     (accounting && !cl->acct_active)) {
170                         continue;
171                 }
172
173                 /*
174                  *      The realm name doesn't match, skip it.
175                  */
176                 if (strcasecmp(cl->realm, realm_name) != 0) {
177                         continue;
178                 }
179
180                 /*
181                  *      Fail-over, pick the first one that matches.
182                  */
183                 if ((count == 0) && /* if size > 0, we have round-robin */
184                     (cl->ldflag == 0)) {
185                         return cl;
186                 }
187
188                 /*
189                  *      We're doing load-balancing.  Pick a random
190                  *      number, which will be used to determine which
191                  *      home server is chosen.
192                  */
193                 if (!lb) {
194                         lb = cl;
195                         count = 1;
196                         continue;
197                 }
198
199                 /*
200                  *      Keep track of how many load balancing servers
201                  *      we've gone through.
202                  */
203                 count++;
204
205                 /*
206                  *      See the "camel book" for why this works.
207                  *
208                  *      If (rand(0..n) < 1), pick the current realm.
209                  *      We add a scale factor of 65536, to avoid
210                  *      floating point.
211                  */
212                 if ((count * (lrad_rand() & 0xffff)) < (uint32_t) 0x10000) {
213                         lb = cl;
214                 }
215         } /* loop over the realms */
216
217         /*
218          *      Return the load-balanced realm.
219          */
220         return lb;
221 }
222
223 /*
224  *      Relay the request to a remote server.
225  *      Returns:
226  *
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
230  */
231 int proxy_send(REQUEST *request)
232 {
233         int rcode;
234         int pre_proxy_type = 0;
235         VALUE_PAIR *realmpair;
236         VALUE_PAIR *strippedname;
237         VALUE_PAIR *vp;
238         REALM *realm;
239         char *realmname;
240
241         /*
242          *      Not authentication or accounting.  Stop it.
243          */
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;
249         }
250
251         /*
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. */
257
258         realmpair = pairfind(request->config_items, PW_PROXY_TO_REALM);
259         if (!realmpair) {
260                 /*
261                  *      Not proxying, so we can exit from the proxy
262                  *      code.
263                  */
264                 return RLM_MODULE_NOOP;
265         }
266
267         /*
268          *      If the server has already decided to reject the request,
269          *      then don't try to proxy it.
270          */
271         if (request->reply->code == PW_AUTHENTICATION_REJECT) {
272                 DEBUG2("Cancelling proxy as request was already rejected");
273                 return RLM_MODULE_REJECT;
274         }
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;
279         }
280         /*
281          *      Length == 0 means it exists, but there's no realm.
282          *      Don't proxy it.
283          */
284         if (realmpair->length == 0) {
285                 return RLM_MODULE_NOOP;
286         }
287
288         realmname = (char *)realmpair->vp_strvalue;
289
290         /*
291          *      Look for the realm, using the load balancing
292          *      version of realm find.
293          */
294         realm = proxy_realm_ldb(request, realmname,
295                                 (request->packet->code == PW_ACCOUNTING_REQUEST));
296         if (realm == NULL) {
297                 DEBUG2("  ERROR: Failed to find live home server for realm %s",
298                        realmname);
299                 return RLM_MODULE_FAIL;
300         }
301
302         /*
303          *      Remember that we sent the request to a Realm.
304          */
305         pairadd(&request->packet->vps,
306                 pairmake("Realm", realm->realm, T_OP_EQ));
307
308         /*
309          *      Access-Request: look for LOCAL realm.
310          *      Accounting-Request: look for LOCAL realm.
311          */
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.",
319                        realm->realm);
320                 return RLM_MODULE_NOOP;
321         }
322
323         /*
324          *      This is mainly for radrelay.  Don't proxy packets back
325          *      to servers which sent them to us.
326          */
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;
334         }
335
336         /*
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.
340          *
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.
347          *
348          *      The proxy then works like normal, except that the response
349          *      packet is "eaten" by the EAP module, and encapsulated into
350          *      an EAP packet.
351          */
352         if (!request->proxy) {
353                 /*
354                  *      Now build a new RADIUS_PACKET.
355                  *
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
359                  *      per remote server.
360                  *
361                  *      OTOH the remote radius server should be smart
362                  *      enough to compare _both_ ID and vector.
363                  *      Right?
364                  */
365                 if ((request->proxy = rad_alloc(TRUE)) == NULL) {
366                         radlog(L_ERR|L_CONS, "no memory");
367                         exit(1);
368                 }
369
370                 /*
371                  *      We now massage the attributes to be proxied...
372                  */
373
374                 /*
375                  *      Copy the request, then look up name and
376                  *      plain-text password in the copy.
377                  *
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.
382                  */
383                 request->proxy->vps =  paircopy(request->packet->vps);
384         }
385
386         /*
387          *      Strip the name, if told to.
388          *
389          *      Doing it here catches the case of proxied tunneled
390          *      requests.
391          */
392         if (realm->striprealm == TRUE &&
393            (strippedname = pairfind(request->proxy->vps, PW_STRIPPED_USER_NAME)) != NULL) {
394                 /*
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
398                  *      original name.
399                  *
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.
406                  */
407                 vp = pairfind(request->proxy->vps, PW_USER_NAME);
408                 if (!vp) {
409                         vp = paircreate(PW_USER_NAME, PW_TYPE_STRING);
410                         if (!vp) {
411                                 radlog(L_ERR|L_CONS, "no memory");
412                                 exit(1);
413                         }
414                         vp->next = request->proxy->vps;
415                         request->proxy->vps = vp;
416                 }
417                 memcpy(vp->vp_strvalue, strippedname->vp_strvalue,
418                        sizeof(vp->vp_strvalue));
419                 vp->length = strippedname->length;
420
421                 /*
422                  *      Do NOT delete Stripped-User-Name.
423                  */
424         }
425         
426         /*
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.
431          */
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);
435                 if (!vp) {
436                         radlog(L_ERR|L_CONS, "no memory");
437                         exit(1);
438                 }
439                 vp->length = AUTH_VECTOR_LEN;
440                 memcpy(vp->vp_strvalue, request->packet->vector, AUTH_VECTOR_LEN);
441                 pairadd(&(request->proxy->vps), vp);
442         }
443
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;
451         }
452
453         /*
454          *      Add PROXY_STATE attribute, before pre-proxy stage,
455          *      so the pre-proxy modules have access to it.
456          *
457          *      Note that, at this point, the proxied request HAS NOT
458          *      been assigned a RADIUS Id.
459          */
460         proxy_addinfo(request);
461
462         /*
463          *      Set up for sending the request.
464          */
465         memcpy(request->proxysecret, realm->secret, sizeof(request->proxysecret));
466         request->proxy_try_count = mainconfig.proxy_retry_count - 1;
467
468         vp = NULL;
469         if (request->packet->code == PW_ACCOUNTING_REQUEST) {
470                 vp = pairfind(request->proxy->vps, PW_ACCT_DELAY_TIME);
471         }
472         if (vp) {
473                 request->proxy->timestamp = request->timestamp - vp->lvalue;
474         } else {
475                 request->proxy->timestamp = request->timestamp;
476         }
477         request->proxy_start_time = request->timestamp;
478
479         /*
480          *      Do pre-proxying.
481          */
482         vp = pairfind(request->config_items, PW_PRE_PROXY_TYPE);
483         if (vp) {
484                 DEBUG2("  Found Pre-Proxy-Type %s", vp->vp_strvalue);
485                 pre_proxy_type = vp->lvalue;
486         }
487         rcode = module_pre_proxy(pre_proxy_type, request);
488         switch (rcode) {
489         /*
490          *      Only proxy the packet if the pre-proxy code succeeded.
491          */
492         case RLM_MODULE_NOOP:
493         case RLM_MODULE_OK:
494         case RLM_MODULE_UPDATED:
495                 /*
496                  *      Delay sending the proxy packet until after we've
497                  *      done the work above, playing with the request.
498                  *
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.
502                  */
503                 request->options |= RAD_REQUEST_OPTION_PROXIED;
504
505                 /*
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.
509                  */
510                 if ((request->options & RAD_REQUEST_OPTION_FAKE_REQUEST) == 0) {
511                         /*
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
517                          *      back very quickly.
518                          */
519                         if (!rl_add_proxy(request)) {
520                                 DEBUG("ERROR: Failed to proxy request %d",
521                                       request->number);
522                                 return RLM_MODULE_FAIL; /* caller doesn't reply */
523                         }
524
525                         request->proxy_listener->send(request->proxy_listener,
526                                                       request);
527                 }
528                 rcode = RLM_MODULE_HANDLED; /* caller doesn't reply */
529                 break;
530         /*
531          *      The module handled the request, don't reply.
532          */
533         case RLM_MODULE_HANDLED:
534                 break;
535         /*
536          *      Neither proxy, nor reply to invalid requests.
537          */
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:
543         default:
544                 rcode = RLM_MODULE_FAIL; /* caller doesn't reply */
545                 break;
546         }
547
548         /*
549          *      Do NOT free request->proxy->vps, the pairs are needed
550          *      for the retries!
551          */
552         return rcode;
553 }