1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright (c) 1993, 2010, Oracle and/or its affiliates. All rights reserved.
23 */
24
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <stdarg.h>
29 #include <sys/types.h>
30 #include <errno.h>
31 #include <assert.h>
32 #include <string.h>
33 #include <syslog.h>
34 #include <sys/socket.h>
35 #include <net/if.h>
36 #include <netinet/in.h>
37 #include <netinet/if_ether.h>
38 #include <arpa/inet.h>
39 #include <nss_dbdefs.h>
40 #include <netinet/dhcp.h>
41 #include <netdb.h>
42 #include <dhcp_symbol.h>
43 #include "dhcpd.h"
44 #include "per_dnet.h"
45 #include "interfaces.h"
46 #include <locale.h>
47
48 /*
49 * This file contains the code which implements the BOOTP compatibility.
50 */
51
52 /*
53 * We are guaranteed that the packet received is a BOOTP request packet,
54 * e.g., *NOT* a DHCP packet.
55 */
56 void
57 bootp(dsvc_clnt_t *pcd, PKT_LIST *plp)
58 {
59 boolean_t result, existing_offer = B_FALSE;
60 int err, write_error = DSVC_SUCCESS, flags = 0;
61 int pkt_len;
62 uint_t crecords = 0, irecords = 0, srecords = 0, clen;
63 uint32_t query;
64 PKT *rep_pktp = NULL;
65 IF *ifp = pcd->ifp;
66 dsvc_dnet_t *pnd = pcd->pnd;
67 uchar_t *optp;
68 dn_rec_t dn, ndn, *dnp;
69 dn_rec_list_t *dncp = NULL, *dnip = NULL, *dnlp = NULL;
70 struct in_addr ciaddr;
71 struct in_addr no_ip; /* network order IP */
72 ENCODE *ecp, *hecp;
73 MACRO *mp, *nmp, *cmp;
74 time_t now = time(NULL);
75 DHCP_MSG_CATEGORIES log;
76 struct hostent h, *hp;
77 char ntoab[INET_ADDRSTRLEN], cipbuf[INET_ADDRSTRLEN];
78 char cidbuf[DHCP_MAX_OPT_SIZE];
79 char hbuf[NSS_BUFLEN_HOSTS];
80
81 ciaddr.s_addr = htonl(INADDR_ANY);
82
83 #ifdef DEBUG
84 dhcpmsg(LOG_DEBUG, "BOOTP request received on %s\n", ifp->nm);
85 #endif /* DEBUG */
86
87 if (pcd->off_ip.s_addr != htonl(INADDR_ANY) &&
88 PCD_OFFER_TIMEOUT(pcd, now))
89 purge_offer(pcd, B_TRUE, B_TRUE);
90
91 if (pcd->off_ip.s_addr != htonl(INADDR_ANY)) {
92 existing_offer = B_TRUE;
93 dnlp = pcd->dnlp;
94 assert(dnlp != NULL);
95 dnp = dnlp->dnl_rec;
96 no_ip.s_addr = htonl(dnp->dn_cip.s_addr);
97 crecords = 1;
98 } else {
99 /*
100 * Try to find a CID entry for the client. We don't care about
101 * lease info here, since a BOOTP client always has a permanent
102 * lease. We also don't care about the entry owner either,
103 * unless we end up allocating a new entry for the client.
104 */
105 DSVC_QINIT(query);
106
107 DSVC_QEQ(query, DN_QCID);
108 (void) memcpy(dn.dn_cid, pcd->cid, pcd->cid_len);
109 dn.dn_cid_len = pcd->cid_len;
110
111 DSVC_QEQ(query, DN_QFBOOTP_ONLY);
112 dn.dn_flags = DN_FBOOTP_ONLY;
113
114 /*
115 * If a client address (ciaddr) is given, we simply trust that
116 * the client knows what it's doing, and we use that IP address
117 * to locate the client's record. If we can't find the client's
118 * record, then we keep silent. If the client id of the record
119 * doesn't match this client, then either the client or our
120 * database is inconsistent, and we'll ignore it (notice
121 * message generated).
122 */
123 ciaddr.s_addr = plp->pkt->ciaddr.s_addr;
124 if (ciaddr.s_addr != htonl(INADDR_ANY)) {
125 DSVC_QEQ(query, DN_QCIP);
126 dn.dn_cip.s_addr = ntohl(ciaddr.s_addr);
127 }
128
129 dnlp = dhcp_lookup_dd_classify(pcd->pnd, B_FALSE, query,
130 -1, &dn, (void **)&dncp, S_CID);
131 if (dnlp != NULL) {
132 crecords = 1;
133 dnp = dnlp->dnl_rec;
134 if (dnp->dn_flags & DN_FUNUSABLE)
135 goto leave_bootp;
136 no_ip.s_addr = htonl(dnp->dn_cip.s_addr);
137 }
138 }
139
140 (void) inet_ntop(AF_INET, &no_ip, cipbuf, sizeof (cipbuf));
141
142 if (crecords == 0 && !be_automatic) {
143 if (verbose) {
144 dhcpmsg(LOG_INFO, "BOOTP client: %1$s is looking for "
145 "a configuration on net %2$s\n", pcd->cidbuf,
146 pnd->network);
147 }
148 goto leave_bootp;
149 }
150
151 /*
152 * If the client thinks it knows who it is (ciaddr), and this doesn't
153 * match our registered IP address, then display an error message and
154 * give up.
155 */
156 if (ciaddr.s_addr != htonl(INADDR_ANY) && crecords == 0) {
157 /*
158 * If the client specified an IP address, then let's check
159 * whether it is available, since we have no CID mapping
160 * registered for this client. If it is available and
161 * unassigned but owned by a different server, we ignore the
162 * client.
163 */
164 DSVC_QINIT(query);
165
166 DSVC_QEQ(query, DN_QCIP);
167 dn.dn_cip.s_addr = ntohl(ciaddr.s_addr);
168 (void) inet_ntop(AF_INET, &ciaddr, cipbuf, sizeof (cipbuf));
169
170 DSVC_QEQ(query, DN_QFBOOTP_ONLY);
171 dn.dn_flags = DN_FBOOTP_ONLY;
172
173 dnip = NULL;
174 dnlp = dhcp_lookup_dd_classify(pcd->pnd, B_FALSE, query,
175 -1, &dn, (void **)&dncp, S_CID);
176 if (dnlp == NULL) {
177 /*
178 * We have no record of this client's IP address, thus
179 * we really can't respond to this client, because it
180 * doesn't have a configuration.
181 */
182 if (verbose) {
183 dhcpmsg(LOG_INFO, "No configuration for BOOTP "
184 "client: %1$s. IP address: %2$s not "
185 "administered by this server.\n",
186 pcd->cidbuf, inet_ntop(AF_INET, &ciaddr,
187 ntoab, sizeof (ntoab)));
188 }
189 goto leave_bootp;
190 } else
191 irecords = 1;
192
193 dnp = dnlp->dnl_rec;
194 if (dnp->dn_flags & DN_FUNUSABLE)
195 goto leave_bootp;
196
197 if (dn.dn_cid_len != 0) {
198 if (dn.dn_cid_len != pcd->cid_len || memcmp(dn.dn_cid,
199 pcd->cid, pcd->cid_len) != 0) {
200 if (verbose) {
201 clen = sizeof (cidbuf);
202 (void) octet_to_hexascii(dn.dn_cid,
203 dn.dn_cid_len, cidbuf, &clen);
204 dhcpmsg(LOG_INFO, "BOOTP client: %1$s "
205 "thinks it owns %2$s, but that "
206 "address belongs to %3$s. Ignoring "
207 "client.\n", pcd->cidbuf, cipbuf,
208 cidbuf);
209 }
210 goto leave_bootp;
211 }
212 } else {
213 if (match_ownerip(htonl(dn.dn_sip.s_addr)) == NULL) {
214 if (verbose) {
215 no_ip.s_addr =
216 htonl(dnp->dn_sip.s_addr);
217 dhcpmsg(LOG_INFO, "BOOTP client: %1$s "
218 "believes it owns %2$s. That "
219 "address is free, but is owned by "
220 "DHCP server %3$s. Ignoring "
221 "client.\n", pcd->cidbuf, cipbuf,
222 inet_ntop(AF_INET, &no_ip, ntoab,
223 sizeof (ntoab)));
224 }
225 goto leave_bootp;
226 }
227 }
228 no_ip.s_addr = htonl(dnp->dn_cip.s_addr);
229 (void) inet_ntop(AF_INET, &no_ip, cipbuf, sizeof (cipbuf));
230 }
231
232 if (crecords == 0) {
233 /*
234 * The dhcp-network table did not have any matching entries.
235 * Try to allocate a new one if possible.
236 */
237 if (irecords == 0 && select_offer(pnd, plp, pcd, &dnlp)) {
238 dnp = dnlp->dnl_rec;
239 no_ip.s_addr = htonl(dnp->dn_cip.s_addr);
240 (void) inet_ntop(AF_INET, &no_ip, cipbuf,
241 sizeof (cipbuf));
242 srecords = 1;
243 }
244 }
245
246 if (crecords == 0 && irecords == 0 && srecords == 0) {
247 dhcpmsg(LOG_NOTICE,
248 "(%1$s) No more BOOTP IP addresses for %2$s network.\n",
249 pcd->cidbuf, pnd->network);
250 goto leave_bootp;
251 }
252
253 /* Check the address. But only if client doesn't know its address. */
254 ndn = *dnp; /* struct copy */
255 no_ip.s_addr = htonl(ndn.dn_cip.s_addr);
256 (void) inet_ntop(AF_INET, &no_ip, cipbuf, sizeof (cipbuf));
257 if (ciaddr.s_addr == htonl(INADDR_ANY)) {
258 if ((ifp->flags & IFF_NOARP) == 0)
259 (void) set_arp(ifp, &no_ip, NULL, 0, DHCP_ARP_DEL);
260 if (!noping) {
261 /*
262 * If icmp echo check fails,
263 * let the plp fall by the wayside.
264 */
265 errno = icmp_echo_check(&no_ip, &result);
266 if (errno != 0) {
267 dhcpmsg(LOG_ERR, "ICMP ECHO check cannot be "
268 "performed for: %s, ignoring\n", cipbuf);
269 goto leave_bootp;
270 }
271 if (result) {
272 dhcpmsg(LOG_ERR, "ICMP ECHO reply to BOOTP "
273 "OFFER candidate: %s, disabling.\n",
274 cipbuf);
275
276 ndn.dn_flags |= DN_FUNUSABLE;
277
278 if ((err = dhcp_modify_dd_entry(pnd->dh,
279 dnp, &ndn)) == DSVC_SUCCESS) {
280 /* Keep the cached entry current. */
281 *dnp = ndn; /* struct copy */
282 }
283
284 logtrans(P_BOOTP, L_ICMP_ECHO, 0, no_ip,
285 server_ip, plp);
286
287 goto leave_bootp;
288 }
289 }
290 }
291
292 /*
293 * It is possible that the client could specify a REQUEST list,
294 * but then it would be a DHCP client, wouldn't it? Only copy the
295 * std option list, since that potentially could be changed by
296 * load_options().
297 */
298 ecp = NULL;
299 if (!no_dhcptab) {
300 open_macros();
301 if ((nmp = get_macro(pnd->network)) != NULL)
302 ecp = dup_encode_list(nmp->head);
303 if ((mp = get_macro(dnp->dn_macro)) != NULL)
304 ecp = combine_encodes(ecp, mp->head, ENC_DONT_COPY);
305 if ((cmp = get_macro(pcd->cidbuf)) != NULL)
306 ecp = combine_encodes(ecp, cmp->head, ENC_DONT_COPY);
307
308 /* If dhcptab configured to return hostname, do so. */
309 if (find_encode(ecp, DSYM_INTERNAL, CD_BOOL_HOSTNAME) !=
310 NULL) {
311 hp = gethostbyaddr_r((char *)&ndn.dn_cip,
312 sizeof (struct in_addr), AF_INET, &h, hbuf,
313 sizeof (hbuf), &err);
314 if (hp != NULL) {
315 hecp = make_encode(DSYM_STANDARD,
316 CD_HOSTNAME, strlen(hp->h_name),
317 hp->h_name, ENC_COPY);
318 replace_encode(&ecp, hecp, ENC_DONT_COPY);
319 }
320 }
321 }
322
323 /* Produce a BOOTP reply. */
324 rep_pktp = gen_bootp_pkt(sizeof (PKT), plp->pkt);
325
326 rep_pktp->op = BOOTREPLY;
327 optp = rep_pktp->options;
328
329 /*
330 * Set the client's "your" IP address if client doesn't know it,
331 * otherwise echo the client's ciaddr back to him.
332 */
333 if (ciaddr.s_addr == htonl(INADDR_ANY))
334 rep_pktp->yiaddr.s_addr = htonl(ndn.dn_cip.s_addr);
335 else
336 rep_pktp->ciaddr.s_addr = ciaddr.s_addr;
337
338 /*
339 * Omit lease time options implicitly, e.g.
340 * ~(DHCP_DHCP_CLNT | DHCP_SEND_LEASE)
341 */
342
343 if (!plp->rfc1048)
344 flags |= DHCP_NON_RFC1048;
345
346 /* Now load in configured options. */
347 pkt_len = load_options(flags, plp, rep_pktp, sizeof (PKT), optp, ecp,
348 NULL);
349
350 free_encode_list(ecp);
351 if (!no_dhcptab)
352 close_macros();
353
354 if (pkt_len < sizeof (PKT))
355 pkt_len = sizeof (PKT);
356
357 /*
358 * Only perform a write if we have selected an entry not yet
359 * assigned to the client (a matching DN_FBOOTP_ONLY entry from
360 * ip address lookup, or an unassigned entry from select_offer()).
361 */
362 if (srecords > 0 || irecords > 0) {
363 (void) memcpy(&ndn.dn_cid, pcd->cid, pcd->cid_len);
364 ndn.dn_cid_len = pcd->cid_len;
365
366 write_error = dhcp_modify_dd_entry(pnd->dh, dnp, &ndn);
367
368 /* Keep state of the cached entry current. */
369 *dnp = ndn; /* struct copy */
370
371 log = L_ASSIGN;
372 } else {
373 if (verbose) {
374 dhcpmsg(LOG_INFO, "Database write unnecessary for "
375 "BOOTP client: %1$s, %2$s\n",
376 pcd->cidbuf, cipbuf);
377 }
378 log = L_REPLY;
379 }
380
381 if (write_error == DSVC_SUCCESS) {
382 if (send_reply(ifp, rep_pktp, pkt_len, &no_ip) != 0) {
383 dhcpmsg(LOG_ERR,
384 "Reply to BOOTP client %1$s with %2$s failed.\n",
385 pcd->cidbuf, cipbuf);
386 } else {
387 /* Note that the conversation has completed. */
388 pcd->state = ACK;
389
390 (void) update_offer(pcd, &dnlp, 0, &no_ip, B_TRUE);
391 existing_offer = B_TRUE;
392 }
393
394 logtrans(P_BOOTP, log, ndn.dn_lease, no_ip, server_ip, plp);
395 }
396
397 leave_bootp:
398 if (rep_pktp != NULL)
399 free(rep_pktp);
400 if (dncp != NULL)
401 dhcp_free_dd_list(pnd->dh, dncp);
402 if (dnip != NULL)
403 dhcp_free_dd_list(pnd->dh, dnip);
404 if (dnlp != NULL && !existing_offer)
405 dhcp_free_dd_list(pnd->dh, dnlp);
406 }