83f889156460ed030b34fbe388f00e76204c885c
[freeradius.git] / src / main / dhcpd.c
1 /*
2  * dhcp.c       DHCP processing.  Done poorly for now.
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 2008 The FreeRADIUS server project
21  * Copyright 2008 Alan DeKok <aland@deployingradius.com>
22  */
23
24 #ifdef WITH_DHCP
25
26 /*
27  *      Same layout, etc. as listen_socket_t.
28  */
29 typedef struct dhcp_socket_t {
30         /*
31          *      For normal sockets.
32          */
33         fr_ipaddr_t     ipaddr;
34         int             port;
35 #ifdef SO_BINDTODEVICE
36         const char      *interface;
37 #endif
38         int             suppress_responses;
39         RADCLIENT       dhcp_client;
40 } dhcp_socket_t;
41
42 static int dhcp_process(REQUEST *request)
43 {
44         int rcode;
45         VALUE_PAIR *vp;
46
47         vp = pairfind(request->packet->vps, DHCP2ATTR(53)); /* DHCP-Message-Type */
48         if (vp) {
49                 DICT_VALUE *dv = dict_valbyattr(DHCP2ATTR(53), vp->vp_integer);
50                 DEBUG("Trying sub-section dhcp %s {...}",
51                       dv->name ? dv->name : "<unknown>");
52                 rcode = module_post_auth(vp->vp_integer, request);
53         } else {
54                 DEBUG("DHCP: Failed to find DHCP-Message-Type in packet!");
55                 rcode = RLM_MODULE_FAIL;
56         }
57
58         /*
59          *      Look for Relay attribute, and forward it if necessary.
60          */
61         vp = pairfind(request->config_items, DHCP2ATTR(270));
62         if (vp) {
63                 VALUE_PAIR *giaddr;
64                 RADIUS_PACKET relayed;
65                 
66                 request->reply->code = 0; /* don't reply to the client */
67
68                 /*
69                  *      Find the original giaddr.
70                  *      FIXME: Maybe look in the original packet?
71                  *
72                  *      It's invalid to have giaddr=0 AND a relay option
73                  */
74                 giaddr = pairfind(request->packet->vps, 266);
75                 if (giaddr && (giaddr->vp_ipaddr == htonl(INADDR_ANY))) {
76                         if (pairfind(request->packet->vps, DHCP2ATTR(82))) {
77                                 RDEBUG("DHCP: Received packet with giaddr = 0 and containing relay option: Discarding packet");
78                                 return 1;
79                         }
80
81                         /*
82                          *      FIXME: Add cache by XID.
83                          */
84                         RDEBUG("DHCP: Cannot yet relay packets with giaddr = 0");
85                         return 1;
86                 }
87
88                 if (request->packet->data[3] > 10) {
89                         RDEBUG("DHCP: Number of hops is greater than 10: not relaying");
90                         return 1;
91                 }
92
93                 /*
94                  *      Forward it VERBATIM to the next server, rather
95                  *      than to the client.
96                  */
97                 memcpy(&relayed, packet, sizeof(relayed));
98
99                 relayed.dst_ipaddr.af = AF_INET;
100                 relayed.dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
101                 relayed.dst_port = request->packet->dst_port;
102
103                 relayed.src_ipaddr = request->packet->dst_ipaddr;
104                 relayed.src_port = request->packet->dst_port;
105
106                 relayed.data = rad_malloc(relayed.data_len);
107                 memcpy(relayed.data, request->packet->data, request->packet->data_len);
108                 relayed.vps = NULL;
109
110                 /*
111                  *      The only field that changes is the number of hops
112                  */
113                 relayed.data[3]++; /* number of hops */
114                 
115                 /*
116                  *      Forward the relayed packet VERBATIM, don't
117                  *      respond to the client, and forget completely
118                  *      about this request.
119                  */
120                 fr_dhcp_send(&relayed);
121                 free(relayed.data);
122                 return 1;
123         }
124
125         vp = pairfind(request->reply->vps, DHCP2ATTR(53)); /* DHCP-Message-Type */
126         if (vp) {
127                 request->reply->code = vp->vp_integer;
128                 if (request->reply->code < PW_DHCP_OFFSET) {
129                         request->reply->code += PW_DHCP_OFFSET;
130                 }
131         }
132         else switch (rcode) {
133         case RLM_MODULE_OK:
134         case RLM_MODULE_UPDATED:
135                 if (request->packet->code == PW_DHCP_DISCOVER) {
136                         request->reply->code = PW_DHCP_OFFER;
137                         break;
138
139                 } else if (request->packet->code == PW_DHCP_REQUEST) {
140                         request->reply->code = PW_DHCP_ACK;
141                         break;
142                 }
143
144                 /* FALL-THROUGH */
145
146         default:
147         case RLM_MODULE_REJECT:
148         case RLM_MODULE_FAIL:
149         case RLM_MODULE_INVALID:
150         case RLM_MODULE_NOOP:
151         case RLM_MODULE_NOTFOUND:
152                 request->reply->code = PW_DHCP_NAK;
153                 break;
154
155         case RLM_MODULE_HANDLED:
156                 break;
157         }
158
159         return 1;
160 }
161
162 static int dhcp_socket_parse(CONF_SECTION *cs, rad_listen_t *this)
163 {
164         int rcode;
165         int on = 1;
166         dhcp_socket_t *sock;
167         RADCLIENT *client;
168         CONF_PAIR *cp;
169
170         rcode = common_socket_parse(cs, this);
171         if (rcode != 0) return rcode;
172
173         sock = this->data;
174
175         /*
176          *      FIXME: Parse config file option for "do broadast = yes/no"
177          */
178         if (setsockopt(this->fd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) < 0) {
179                 radlog(L_ERR, "Can't set broadcast option: %s\n",
180                        strerror(errno));
181                 return -1;
182         }
183
184         if (setsockopt(this->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
185                 radlog(L_ERR, "Can't set re-use addres option: %s\n",
186                        strerror(errno));
187                 return -1;
188         }
189
190         /*
191          *      Undocumented extension for testing without
192          *      destroying your network!
193          */
194         sock->suppress_responses = FALSE;
195         cp = cf_pair_find(cs, "suppress_responses");
196         if (cp) {
197                 const char *value;
198
199                 value = cf_pair_value(cp);
200
201                 if (value && (strcmp(value, "yes") == 0)) {
202                         sock->suppress_responses = TRUE;
203                 }
204         }
205
206         /*
207          *      Initialize the fake client.
208          */
209         client = &sock->dhcp_client;
210         memset(client, 0, sizeof(*client));
211         client->ipaddr.af = AF_INET;
212         client->ipaddr.ipaddr.ip4addr.s_addr = INADDR_NONE;
213         client->prefix = 0;
214         client->longname = client->shortname = "dhcp";
215         client->secret = client->shortname;
216         client->nastype = strdup("none");
217
218         return 0;
219 }
220
221
222 /*
223  *      Check if an incoming request is "ok"
224  *
225  *      It takes packets, not requests.  It sees if the packet looks
226  *      OK.  If so, it does a number of sanity checks on it.
227  */
228 static int dhcp_socket_recv(rad_listen_t *listener,
229                             RAD_REQUEST_FUNP *pfun, REQUEST **prequest)
230 {
231         RADIUS_PACKET   *packet;
232         dhcp_socket_t   *sock;
233
234         packet = fr_dhcp_recv(listener->fd);
235         if (!packet) {
236                 radlog(L_ERR, "%s", fr_strerror());
237                 return 0;
238         }
239
240         sock = listener->data;
241         if (!received_request(listener, packet, prequest, &sock->dhcp_client)) {
242                 rad_free(&packet);
243                 return 0;
244         }
245
246         *pfun = dhcp_process;
247
248         return 1;
249 }
250
251
252 /*
253  *      Send an authentication response packet
254  */
255 static int dhcp_socket_send(rad_listen_t *listener, REQUEST *request)
256 {
257         dhcp_socket_t   *sock;
258
259         rad_assert(request->listener == listener);
260         rad_assert(listener->send == dhcp_socket_send);
261
262         if (request->reply->code == 0) return 0; /* don't reply */
263
264         if (fr_dhcp_encode(request->reply, request->packet) < 0) {
265                 return -1;
266         }
267
268         sock = listener->data;
269         if (sock->suppress_responses) return 0;
270
271         /*
272          *      Don't send anything
273          */
274         return fr_dhcp_send(request->reply);
275 }
276
277
278 static int dhcp_socket_encode(UNUSED rad_listen_t *listener, REQUEST *request)
279 {
280         DEBUG2("NO ENCODE!");
281         return 0;
282         return fr_dhcp_encode(request->reply, request->packet);
283 }
284
285
286 static int dhcp_socket_decode(UNUSED rad_listen_t *listener, REQUEST *request)
287 {
288         return fr_dhcp_decode(request->packet);
289 }
290 #endif /* WITH_DCHP */
291