e82669a1fb6f0a2c62abf85ca0f340043e25cae5
[freeradius.git] / src / modules / rlm_eap / libeap / eap_chbind.c
1 /*
2  * eap_chbind.c
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  * Copyright 2014  Network RADIUS SARL
21  * Copyright 2014  The FreeRADIUS server project
22  */
23
24
25 RCSID("$Id$")
26
27 #include "eap_chbind.h"
28
29 static bool chbind_build_response(REQUEST *request, CHBIND_REQ *chbind)
30 {
31         int length;
32         size_t total;
33         uint8_t *ptr, *end;
34         VALUE_PAIR const *vp;
35         vp_cursor_t cursor;
36
37         total = 0;
38         for (vp = fr_cursor_init(&cursor, &request->reply->vps);
39              vp != NULL;
40              vp = fr_cursor_next(&cursor)) {
41                 /*
42                  *      Skip things which shouldn't be in channel bindings.
43                  */
44                 if (vp->da->flags.encrypt != FLAG_ENCRYPT_NONE) continue;
45                 if (!vp->da->vendor && (vp->da->attr == PW_MESSAGE_AUTHENTICATOR)) continue;
46
47                 total += 2 + vp->vp_length;
48         }
49
50         /*
51          *      No attributes: just send a 1-byte response code.
52          */
53         if (!total) {
54                 ptr = talloc_zero_array(chbind, uint8_t, 1);
55         } else {
56                 ptr = talloc_zero_array(chbind, uint8_t, total + 4);
57         }
58         if (!ptr) return false;
59         chbind->response = (chbind_packet_t *) ptr;
60
61         /*
62          *      Set the response code.  Default to "fail" if none was
63          *      specified.
64          */
65         vp = fr_pair_find_by_num(request->config, PW_CHBIND_RESPONSE_CODE, 0, TAG_ANY);
66         if (vp) {
67                 ptr[0] = vp->vp_integer;
68         } else {
69                 ptr[0] = CHBIND_CODE_FAILURE;
70         }
71
72         if (!total) return true; /* nothing to encode */
73
74         /* Write the length field into the header */
75         ptr[1] = (total >> 8) & 0xff;
76         ptr[2] = total & 0xff;
77         ptr[3] = CHBIND_NSID_RADIUS;
78
79         RDEBUG("Sending chbind response: code %i", (int )(ptr[0]));
80         rdebug_pair_list(L_DBG_LVL_1, request, request->reply->vps, NULL);
81
82         /* Encode the chbind attributes into the response */
83         ptr += 4;
84         end = ptr + total;
85         for (vp = fr_cursor_init(&cursor, &request->reply->vps);
86              vp != NULL;
87              vp = fr_cursor_next(&cursor)) {
88                 /*
89                  *      Skip things which shouldn't be in channel bindings.
90                  */
91                 if (vp->da->flags.encrypt != FLAG_ENCRYPT_NONE) continue;
92                 if (!vp->da->vendor && (vp->da->attr == PW_MESSAGE_AUTHENTICATOR)) continue;
93
94                 length = rad_vp2attr(NULL, NULL, NULL, &vp, ptr, end - ptr);
95                 if (length < 0) continue;
96                 ptr += length;
97         }
98
99         return true;
100 }
101
102
103 /*
104  *      Parse channel binding packet to obtain data for a specific
105  *      NSID.
106  *
107  *      See:
108  *      http://tools.ietf.org/html/draft-ietf-emu-chbind-13#section-5.3.2
109  */
110 static size_t chbind_get_data(chbind_packet_t const *packet,
111                               int desired_nsid,
112                               uint8_t const **data)
113 {
114         uint8_t const *ptr;
115         uint8_t const *end;
116
117         if (packet->code != CHBIND_CODE_REQUEST) {
118                 return 0;
119         }
120
121         ptr = (uint8_t const *) packet;
122         end = ptr + talloc_array_length(packet);
123
124         ptr++;                  /* skip the code at the start of the packet */
125         while (ptr < end) {
126                 uint8_t nsid;
127                 size_t length;
128
129                 /*
130                  *      Need room for length(2) + NSID + data.
131                  */
132                 if ((end - ptr) < 4) return 0;
133
134                 length = (ptr[0] << 8) | ptr[1];
135                 if (length == 0) return 0;
136
137                 if ((ptr + length + 3) > end) return 0;
138
139                 nsid = ptr[2];
140                 if (nsid == desired_nsid) {
141                         ptr += 3;
142                         *data = ptr;
143                         return length;
144                 }
145
146                 ptr += 3 + length;
147         }
148
149         return 0;
150 }
151
152
153 PW_CODE chbind_process(REQUEST *request, CHBIND_REQ *chbind)
154 {
155         PW_CODE rcode;
156         REQUEST *fake = NULL;
157         VALUE_PAIR *vp = NULL;
158         uint8_t const *attr_data;
159         size_t data_len = 0;
160
161         /* check input parameters */
162         rad_assert((request != NULL) &&
163                    (chbind != NULL) &&
164                    (chbind->request != NULL) &&
165                    (chbind->response == NULL));
166
167         /* Set-up the fake request */
168         fake = request_alloc_fake(request);
169         fr_pair_make(fake->packet, &fake->packet->vps, "Freeradius-Proxied-To", "127.0.0.1", T_OP_EQ);
170
171         /* Add the username to the fake request */
172         if (chbind->username) {
173                 vp = fr_pair_copy(fake->packet, chbind->username);
174                 fr_pair_add(&fake->packet->vps, vp);
175                 fake->username = vp;
176         }
177
178         /*
179          *      Maybe copy the State over, too?
180          */
181
182         /* Add the channel binding attributes to the fake packet */
183         data_len = chbind_get_data(chbind->request, CHBIND_NSID_RADIUS, &attr_data);
184         if (data_len) {
185                 rad_assert(data_len <= talloc_array_length(chbind->request));
186
187                 while (data_len > 0) {
188                         int attr_len = rad_attr2vp(fake->packet, NULL, NULL, NULL, attr_data, data_len, &vp);
189                         if (attr_len <= 0) {
190                                 /* If radaddr2vp fails, return NULL string for
191                                    channel binding response */
192                                 talloc_free(fake);
193                                 return PW_CODE_ACCESS_ACCEPT;
194                         }
195                         if (vp) {
196                                 fr_pair_add(&fake->packet->vps, vp);
197                         }
198                         attr_data += attr_len;
199                         data_len -= attr_len;
200                 }
201         }
202
203         /*
204          *      Set virtual server based on configuration for channel
205          *      bindings, this is hard-coded for now.
206          */
207         fake->server = "channel_bindings";
208         fake->packet->code = PW_CODE_ACCESS_REQUEST;
209
210         rcode = rad_virtual_server(fake);
211
212         switch (rcode) {
213                 /* If rad_authenticate succeeded, build a reply */
214         case RLM_MODULE_OK:
215         case RLM_MODULE_HANDLED:
216                 if (chbind_build_response(fake, chbind)) {
217                         rcode = PW_CODE_ACCESS_ACCEPT;
218                         break;
219                 }
220                 /* FALL-THROUGH */
221
222                 /* If we got any other response from rad_authenticate, it maps to a reject */
223         default:
224                 rcode = PW_CODE_ACCESS_REJECT;
225                 break;
226         }
227
228         talloc_free(fake);
229
230         return rcode;
231 }
232
233 /*
234  *      Handles multiple EAP-channel-binding Message attrs
235  *      ie concatenates all to get the complete EAP-channel-binding packet.
236  */
237 chbind_packet_t *eap_chbind_vp2packet(TALLOC_CTX *ctx, VALUE_PAIR *vps)
238 {
239         size_t length;
240         uint8_t *ptr;
241         VALUE_PAIR *first, *vp;
242         chbind_packet_t *packet;
243         vp_cursor_t cursor;
244
245         first = fr_pair_find_by_num(vps, PW_UKERNA_CHBIND, VENDORPEC_UKERNA, TAG_ANY);
246         if (!first) return NULL;
247
248         /*
249          *      Compute the total length of the channel binding data.
250          */
251         length = 0;
252         for (vp =fr_cursor_init(&cursor, &first);
253              vp != NULL;
254              vp = fr_cursor_next_by_num(&cursor, PW_UKERNA_CHBIND, VENDORPEC_UKERNA, TAG_ANY)) {
255                 length += vp->vp_length;
256         }
257
258         if (length < 4) {
259                 DEBUG("Invalid length %u for channel binding data", (unsigned int) length);
260                 return NULL;
261         }
262
263         /*
264          *      Now that we know the length, allocate memory for the packet.
265          */
266         ptr = talloc_zero_array(ctx, uint8_t, length);
267         if (!ptr) return NULL;
268
269         /*
270          *      Copy the data over to our packet.
271          */
272         packet = (chbind_packet_t *) ptr;
273         for (vp = fr_cursor_init(&cursor, &first);
274              vp != NULL;
275              vp = fr_cursor_next_by_num(&cursor, PW_UKERNA_CHBIND, VENDORPEC_UKERNA, TAG_ANY)) {
276                 memcpy(ptr, vp->vp_octets, vp->vp_length);
277                 ptr += vp->vp_length;
278         }
279
280         return packet;
281 }
282
283 VALUE_PAIR *eap_chbind_packet2vp(RADIUS_PACKET *packet, chbind_packet_t *chbind)
284 {
285         VALUE_PAIR      *vp;
286
287         if (!chbind) return NULL; /* don't produce garbage */
288
289         vp = fr_pair_afrom_num(packet, VENDORPEC_UKERNA, PW_UKERNA_CHBIND);
290         if (!vp) return NULL;
291         fr_pair_value_memcpy(vp, (uint8_t *) chbind, talloc_array_length((uint8_t *)chbind));
292
293         return vp;
294 }