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