Initial code for EAP Channel Binding support.
[freeradius.git] / src / modules / rlm_eap / libeap / eap_chbind.c
1 /*
2  * eap_chbind.c
3  *
4  * Version:     $Id$
5  *
6  * Copyright (c) 2012, JANET(UK)
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  *
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  *
20  * 3. Neither the name of JANET(UK) nor the names of its contributors
21  *    may be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 
25  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
26  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
27  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
28  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
29  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
30  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
31  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
32  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
33  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
34  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35  */
36
37 #include <freeradius-devel/ident.h>
38 RCSID("$Id$")
39
40 #include "eap_chbind.h"
41
42 #define MAX_PACKET_LEN          1024
43
44 /*
45  * Process any channel bindings included in the request.
46  */
47 CHBIND_REQ *chbind_allocate()
48 {
49   CHBIND_REQ *ret;
50   ret = malloc(sizeof CHBIND_REQUQEST);
51   if (0 != ret)
52     memset(ret, 0, sizeof CHBIND_REQUEST);
53   return ret;
54 }
55
56 void *chbind_free(CHBIND_REQ *chbind)
57 {
58   /* free the chbind response, if allocated by chbind_process */
59   if (chbind->chbind_resp)
60     free(chbind_resp);
61
62   free(chbind);
63 }
64
65 int chbind_process(REQUEST *req, CHBIND_REQ *chbind_req)
66 {
67   int rcode = PW_AUTHENTICATION_REJECT;
68   REQUEST *fake = NULL;
69   VALUE_PAIR *user_vp = NULL;
70   uint8_t *attr_data;
71   uint8_t attr_len = 0;
72   int i = 0;
73
74   /* check input parameters */
75   rad_assert((request != NULL) && 
76              (chbind_req != NULL) &&
77              (chbind_req->username != NULL) &&
78              (chbind_req->chbind_req_buf != NULL));
79   if (chbind_req->len < 4)
80     return PW_AUTHENTICATION_REJECT;  /* Is this the right response? */
81
82   /* Set-up NULL response for cases where channel bindings can't be processed */
83   chbind_req->chbind_resp = NULL;
84   chbind_req->chbind_resp_len = 0;
85
86   /* Set-up the fake request */
87   fake = request_alloc_fake(request);
88   rad_assert(fake->packet->vps == NULL);
89   vp = pairmake("Freeradius-Proxied-To", "127.0.0.1", T_OP_EQ);
90   if (vp) {
91     pairadd(&fake->packet->vps, vp);
92   }
93   
94   /* Add the username to the fake request */
95   vp = paircreate(PW_USER_NAME, 0, PW_TYPE_STRING);
96   rad_assert(vp);
97   memcpy(vp->vp_octets, chbind_req->username, chbind_req->username_len);
98   vp->length = chbind_req->username_len;
99
100   pairadd(&fake->packet-vps, vp);
101   fake->username = pairfind(fake->packet->vps, PW_USER_NAME, 0);
102
103   /* Copy the request state into the fake request */
104   vp = paircopy(req->state);
105   if (vp)
106     pairadd(&fake->packet->vps, vp);
107
108   /* Add the channel binding attributes to the fake packet */
109   if (0 != (datalen = chbind_get_data((chbind_packet_t *)chbind_req, chbind_req_len, 
110                                       CHBIND_NSID_RADIUS, &attr_data))) {
111     if (0 < radattr2vp(NULL, NULL, NULL, attr_data, datalen, &vp)) {
112       /* If radaddr2vp fails, return NULL string for channel binding response */
113       request_free(fake);
114       return PW_AUTHENTICATION_OK;
115     }
116     if (vp)
117       pairadd(&fake->packet->vps, vp);
118   }
119
120   /* Set virtual server based on configuration for channel bindings,
121      this is hard-coded to "chbind" for now */
122   fake->server = pairmake("Virtual-Server", "chbind", T_OP_EQ);
123
124   /* Call rad_authenticate */
125   rad_authenticate(fake);
126
127   switch(fake->reply->code) {
128     /* If rad_authenticate succeeded, build a reply */
129   case RLM_MODULE_OK:
130   case RLM_MODULE_HANDLED:
131     if (chbind_req->chbind_resp = chbind_build_response(fake, &chbind_req->chbind_resp_len))
132       rcode = PW_AUTHENTICATION_ACK;
133     else
134       rcode = PW_AUTHENTICATION_REJECT;
135     break;
136   
137   /* If we got any other response from rad_authenticate, it maps to a reject */
138   default:
139     rcode = PW_AUTHENTICATION_REJECT;
140     break;
141   }
142
143   request_free(fake);
144
145   return rcode;
146 }
147
148 /*
149  * Parse channel binding packet to obtain data for a specific NSID.
150  * See http://tools.ietf.org/html/draft-ietf-emu-chbind-13#section-5.3.2:
151  */ 
152
153 size_t chbind_get_data(chbind_packet_t *chbind_packet,
154                            size_t chbind_packet_len,
155                            int desired_nsid,
156                            uint8_t **radbuf_data)
157 {
158   size_t chbind_data_len = chbind_packet_len-1;
159   size_t pos=0;
160   if (chbind_packet->code != CHBIND_CODE_REQUEST)
161     return 0;
162   while (pos + 3 < chbind_data_len) {
163     size_t len = (chbind_packet->data[pos] << 8) + 
164       chbind_packet->data[pos + 1];
165     uint8_t nsid = chbind_packet->data[pos + 2];
166     if (pos + 3 > chbind_data_len + len) {
167       /* malformed packet; warn here */
168       return 0;
169     }
170     if (nsid == desired_nsid) {
171       *radbuf_data = &chbind_packet->data[pos+3];
172       return len;
173     }
174     pos += 3 + len;
175   }
176   /* didn't find any data matching nsid */
177   if (pos != chbind_data_len) {
178     /* warn about malformed packet */
179   }
180
181   return 0;
182 }
183
184 uint8_t *chbind_build_response(REQUEST *req, int *resp_len)
185 {
186   uint8_t *resp, *rp = NULL;
187   uint16_t rlen, len = 0;
188
189   *resp_len = 0;
190   resp = malloc(MAX_PACKET_LEN + 4);
191   rad_assert(resp);
192
193   /* Set-up the chbind header fields (except length, computed later) */
194   vp = pairfind(fake->config_items, PW_CHBIND_RESPONSE_CODE, 0);
195   resp[0] = vp->vp_integer;
196
197   resp[3] = CHBIND_NSID_RADIUS;
198
199   /* Encode the chbind attributes into the response */
200   for (vp = fake->reply->vps, rlen = 4; 
201        (vp != NULL) && (rlen < MAX_PACKET_LEN + 4); 
202        rlen += len) {
203     len = rad_vp2attr(NULL, NULL, NULL, *vp, resp[rlen], (MAX_PACKET_LEN + 4) - rlen);
204   }
205
206   /* Write the length field into the header */
207   resp[1] = (uint8_t)(rlen >> 8);
208   resp[2] = (uint8_t)(rlen & 0x00FF);
209     size_t len = (chbind_packet->data[pos] << 8) + 
210       chbind_packet->data[pos + 1];
211   
212   /* Output the length of the entire response (attrs + header) */
213   *resp_len = rlen + 4;
214
215   return resp;
216 }