Patches from "Alan Curry" <pacman-radius@cqc.com>
[freeradius.git] / src / main / proxy.c
1 /*
2  * proxy.c      Proxy stuff.
3  *
4  * Version:     $Id$
5  */
6
7 static const char rcsid[] = "$Id$";
8
9 #include        "autoconf.h"
10
11 #include        <sys/types.h>
12 #include        <sys/socket.h>
13 #include        <netinet/in.h>
14
15 #include        <stdio.h>
16 #include        <stdlib.h>
17 #include        <unistd.h>
18 #include        <ctype.h>
19 #include        <string.h>
20
21 #if HAVE_MALLOC_H
22 #  include      <malloc.h>
23 #endif
24
25 #include        <assert.h>
26
27 #include        "radiusd.h"
28
29
30 static uint32_t proxy_id = 1;
31
32 static const int allowed[] = {
33         PW_SERVICE_TYPE,
34         PW_FRAMED_PROTOCOL,
35         PW_FILTER_ID,
36         PW_FRAMED_MTU,
37         PW_FRAMED_COMPRESSION,
38         PW_LOGIN_SERVICE,
39         PW_REPLY_MESSAGE,
40         PW_SESSION_TIMEOUT,
41         PW_IDLE_TIMEOUT,
42         PW_PORT_LIMIT,
43         0,
44 };
45
46 static const int trusted_allowed[] = {
47         PW_SERVICE_TYPE,
48         PW_FRAMED_PROTOCOL,
49         PW_FILTER_ID,
50         PW_FRAMED_MTU,
51         PW_FRAMED_COMPRESSION,
52         PW_FRAMED_IP_ADDRESS,
53         PW_FRAMED_IP_NETMASK,
54         PW_FRAMED_ROUTING,
55         PW_FRAMED_ROUTE,
56         PW_LOGIN_SERVICE,
57         PW_REPLY_MESSAGE,
58         PW_SESSION_TIMEOUT,
59         PW_IDLE_TIMEOUT,
60         PW_PORT_LIMIT,
61         0,
62 };
63
64 /*
65  *      We received a response from a remote radius server.
66  *      Find the original request, then return.
67  *      Returns:   1 replication don't reply
68  *                 0 proxy found
69  *                -1 error don't reply
70  */
71 int proxy_receive(REQUEST *request)
72 {
73         VALUE_PAIR      *allowed_pairs;
74         int             i;
75         VALUE_PAIR      *proxypair;
76         VALUE_PAIR      *replicatepair;
77         VALUE_PAIR      *realmpair;
78         int             replicating;
79         REALM           *realm;
80         char            *realmname;
81
82         /*
83          *      FIXME: calculate md5 checksum!
84          */
85
86         proxypair = pairfind(request->config_items, PW_PROXY_TO_REALM);
87         replicatepair = pairfind(request->config_items, PW_REPLICATE_TO_REALM);
88         if(proxypair) {
89                 realmpair=proxypair;
90                 replicating=0;
91         } else if(replicatepair) {
92                 realmpair=replicatepair;
93                 replicating=1;
94         } else {
95                 log(L_PROXY, "Proxy reply to packet with no Realm");
96                 return -1;
97         }
98
99         realmname=(char *)realmpair->strvalue;
100         realm = realm_find(realmname);
101         allowed_pairs = NULL;
102
103         /* FIXME - do we want to use the trusted/allowed filters on replicate
104          * replies, which are not going to be used for anything except maybe
105          * a log file? */
106         if (realm->trusted) {
107                 /*
108                  *      Only allow some attributes to be propagated from
109                  *      the remote server back to the NAS, for security.
110                  */
111                 allowed_pairs = NULL;
112                 for(i = 0; trusted_allowed[i]; i++)
113                         pairmove2(&allowed_pairs, &(request->proxy_reply->vps), trusted_allowed[i]);
114         } else {
115                 /*
116                  *      Only allow some attributes to be propagated from
117                  *      the remote server back to the NAS, for security.
118                  */
119                 allowed_pairs = NULL;
120                 for(i = 0; allowed[i]; i++)
121                         pairmove2(&allowed_pairs, &(request->proxy_reply->vps), allowed[i]);
122         }
123         
124         /*
125          *      Delete the left-over attributes, and move the
126          *      allowed ones back.
127          */
128         pairfree(request->proxy_reply->vps);
129         request->proxy_reply->vps = allowed_pairs;
130
131         return replicating?1:0;
132 }
133
134 /*
135  *      Add a proxy-pair to the end of the request.
136  */
137 static void proxy_addinfo(REQUEST *request)
138 {
139         VALUE_PAIR              *proxy_pair;
140
141         proxy_pair = paircreate(PW_PROXY_STATE, PW_TYPE_STRING);
142         if  (proxy_pair == NULL) {
143                 log(L_ERR|L_CONS, "no memory");
144                 exit(1);
145         }
146         sprintf((char *)proxy_pair->strvalue, "%d", request->packet->id);
147         proxy_pair->length = strlen((char *)proxy_pair->strvalue);
148
149         pairadd(&request->proxy->vps, proxy_pair);
150 }
151
152 /*
153  *      Relay the request to a remote server.
154  *      Returns:  2 success (we replicate, caller replies normally)
155  *                1 success (we reply, caller returns without replying)
156  *                0 fail (caller falls through to normal processing)
157  *               -1 fail (we don't reply, caller returns without replying)
158  */
159 int proxy_send(REQUEST *request)
160 {
161         VALUE_PAIR              *proxypair;
162         VALUE_PAIR              *replicatepair;
163         VALUE_PAIR              *realmpair;
164         VALUE_PAIR              *namepair;
165         VALUE_PAIR              *strippednamepair;
166         VALUE_PAIR              *delaypair;
167         VALUE_PAIR              *vp, *vps;
168         REALM                   *realm;
169         char                    *realmname;
170         int                     replicating;
171
172 #if 0   /* This looks bad to me... the timestamp is used below to figure the
173          * next_try. The request needs to "hang around" until either the
174          * other server sends a reply or the retry count has been exceeded.
175          * Until then, it should not be eligible for the time-based cleanup.
176          * --Pac. */
177         /*
178          *      Ensure that the request hangs around for a little
179          *      while longer.
180          *
181          *      FIXME: This is a hack... it should be more intelligent.
182          */
183         request->timestamp += 5;
184 #endif
185
186         /* Look for proxy/replicate signs */
187         /* FIXME - What to do if multiple Proxy-To/Replicate-To attrs are
188          * set...  Log an error? Actually replicate to multiple places? That
189          * would be cool. For now though, I'll just take the first one and
190          * ignore the rest. */
191         proxypair = pairfind(request->config_items, PW_PROXY_TO_REALM);
192         replicatepair = pairfind(request->config_items, PW_REPLICATE_TO_REALM);
193         if (proxypair) {
194                 realmpair = proxypair;
195                 replicating = 0;
196         } else if (replicatepair) {
197                 realmpair = replicatepair;
198                 replicating = 1;
199         } else {
200                 /*
201                  *      Neither proxy or replicate attributes are set,
202                  *      so we can exit from the proxy code.
203                  */
204                 return 0;
205         }
206
207         realmname = (char *)realmpair->strvalue;
208
209         /*
210          *      Look for the realm, letting realm_find take care
211          *      of the "NULL" realm.
212          *
213          *      If there is no such realm, then exit.
214          *      Maybe we should log an error?
215          */
216         realm = realm_find(realmname);
217         if (realm == NULL) {
218                 return 0;
219         }
220
221         /*
222          *      Copy the request, then look up
223          *      name and plain-text password in the copy.
224          *
225          *      Note that the User-Name attribute is the *original*
226          *      as sent over by the client.  The Stripped-User-Name
227          *      attribute is the one hacked through the 'hints' file.
228          */
229         vps = paircopy(request->packet->vps);
230         namepair = pairfind(vps, PW_USER_NAME);
231         strippednamepair = pairfind(vps, PW_STRIPPED_USER_NAME);
232
233         /*
234          *      If there's a Stripped-User-Name attribute in the
235          *      request, then use THAT as the User-Name for the
236          *      proxied request, instead of the original name.
237          *
238          *      This is done by making a copy of the Stripped-User-Name
239          *      attribute, turning it into a User-Name attribute,
240          *      deleting the Stripped-User-Name and User-Name attributes
241          *      from the vps list, and making the new User-Name
242          *      the head of the vps list.
243          */
244         if (strippednamepair) {
245                 vp = paircreate(PW_USER_NAME, PW_TYPE_STRING);
246                 if (!vp) {
247                         log(L_ERR|L_CONS, "no memory");
248                         exit(1);
249                 }
250                 memcpy(vp->strvalue, strippednamepair->strvalue,
251                        sizeof(vp->strvalue));
252                 vp->length = strippednamepair->length;
253                 pairdelete(&vps, PW_USER_NAME);
254                 pairdelete(&vps, PW_STRIPPED_USER_NAME);
255                 vp->next = vps;
256                 namepair = vp;
257                 vps = vp;
258         }
259
260         /*
261          *      Remember that we sent the request to a Realm.
262          */
263         pairadd(&request->packet->vps,
264                 pairmake("Realm", realm->realm, T_OP_EQ));
265
266         /*
267          *      Now build a new RADIUS_PACKET and send it.
268          *
269          *      FIXME: it could be that the id wraps around too fast if
270          *      we have a lot of requests, it might be better to keep
271          *      a seperate ID value per remote server.
272          *
273          *      OTOH the remote radius server should be smart enough to
274          *      compare _both_ ID and vector. Right ?
275          */
276         if ((request->proxy = rad_alloc(TRUE)) == NULL) {
277                 log(L_ERR|L_CONS, "no memory");
278                 exit(1);
279         }
280
281         /*
282          *      Proxied requests get sent out the proxy FD ONLY.
283          */
284         request->proxy->sockfd = proxyfd;
285
286         request->proxy->code = request->packet->code;
287         request->proxy->dst_ipaddr = realm->ipaddr;
288         if (request->packet->code == PW_AUTHENTICATION_REQUEST)
289                 request->proxy->dst_port = realm->auth_port;
290         else
291                 request->proxy->dst_port = realm->acct_port;
292         assert(request->proxy->vps == NULL);
293         request->proxy->vps = vps;
294
295         /*
296          *      Add the request to the list of outstanding requests.
297          *      Note that request->proxy->id is a 16 bits value,
298          *      while rad_send sends only the 8 least significant
299          *      bits of that same value.
300          */
301         request->proxy->id = (proxy_id++) & 0xff;
302         proxy_id &= 0xffff;
303
304         /*
305          *      Add PROXY_STATE attribute.
306          */
307         proxy_addinfo(request);
308
309         /*
310          *      If there is no PW_CHAP_CHALLENGE attribute but there
311          *      is a PW_CHAP_PASSWORD we need to add it since we can't
312          *      use the request authenticator anymore - we changed it.
313          */
314         if (pairfind(vps, PW_CHAP_PASSWORD) &&
315             pairfind(vps, PW_CHAP_CHALLENGE) == NULL) {
316                 vp = paircreate(PW_CHAP_CHALLENGE, PW_TYPE_STRING);
317                 if (!vp) {
318                         log(L_ERR|L_CONS, "no memory");
319                         exit(1);
320                 }
321                 vp->length = AUTH_VECTOR_LEN;
322                 memcpy(vp->strvalue, request->packet->vector, AUTH_VECTOR_LEN);
323                 pairadd(&vps, vp);
324         }
325
326         /*
327          *      Send the request.
328          */
329         rad_send(request->proxy, (char *)realm->secret);
330         memcpy(request->proxysecret, realm->secret, sizeof(request->proxysecret));
331         request->proxy_is_replicate = replicating;
332         request->proxy_try_count = proxy_retry_count - 1;
333         request->proxy_next_try = request->timestamp + proxy_retry_delay;
334         delaypair = pairfind(vps, PW_ACCT_DELAY_TIME);
335         request->proxy->timestamp = request->timestamp - (delaypair ? delaypair->lvalue : 0);
336
337 #if 0   /* You can't do this - the pairs are needed for the retries! --Pac. */
338         /*
339          *      We can free proxy->vps now, not needed anymore.
340          */
341         pairfree(request->proxy->vps);
342         request->proxy->vps = NULL;
343 #endif
344
345         return replicating?2:1;
346 }