138451Smsmith/*	$NetBSD: bootp.c,v 1.14 1998/02/16 11:10:54 drochner Exp $	*/
238451Smsmith
338451Smsmith/*
438451Smsmith * Copyright (c) 1992 Regents of the University of California.
538451Smsmith * All rights reserved.
638451Smsmith *
738451Smsmith * This software was developed by the Computer Systems Engineering group
838451Smsmith * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
938451Smsmith * contributed to Berkeley.
1038451Smsmith *
1138451Smsmith * Redistribution and use in source and binary forms, with or without
1238451Smsmith * modification, are permitted provided that the following conditions
1338451Smsmith * are met:
1438451Smsmith * 1. Redistributions of source code must retain the above copyright
1538451Smsmith *    notice, this list of conditions and the following disclaimer.
1638451Smsmith * 2. Redistributions in binary form must reproduce the above copyright
1738451Smsmith *    notice, this list of conditions and the following disclaimer in the
1838451Smsmith *    documentation and/or other materials provided with the distribution.
1938451Smsmith * 4. Neither the name of the University nor the names of its contributors
2038451Smsmith *    may be used to endorse or promote products derived from this software
2138451Smsmith *    without specific prior written permission.
2238451Smsmith *
2338451Smsmith * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2438451Smsmith * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2538451Smsmith * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2638451Smsmith * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2738451Smsmith * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2838451Smsmith * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2938451Smsmith * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
3038451Smsmith * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
3138451Smsmith * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3238451Smsmith * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3338451Smsmith * SUCH DAMAGE.
3438451Smsmith *
3538451Smsmith * @(#) Header: bootp.c,v 1.4 93/09/11 03:13:51 leres Exp  (LBL)
3638451Smsmith */
3738451Smsmith
3884221Sdillon#include <sys/cdefs.h>
3984221Sdillon__FBSDID("$FreeBSD: stable/11/stand/libsa/bootp.c 344408 2019-02-21 02:43:48Z kevans $");
4084221Sdillon
41329100Skevans#include <stddef.h>
4238451Smsmith#include <sys/types.h>
43329099Skevans#include <sys/limits.h>
44297150Sian#include <sys/endian.h>
4538451Smsmith#include <netinet/in.h>
4638451Smsmith#include <netinet/in_systm.h>
4738451Smsmith
4838451Smsmith#include <string.h>
4938451Smsmith
5038451Smsmith#define BOOTP_DEBUGxx
5138451Smsmith#define SUPPORT_DHCP
5238451Smsmith
53185643Sluigi#define	DHCP_ENV_NOVENDOR	1	/* do not parse vendor options */
54185643Sluigi#define	DHCP_ENV_PXE		10	/* assume pxe vendor options */
55193189Sed#define	DHCP_ENV_FREEBSD	11	/* assume freebsd vendor options */
56185643Sluigi/* set DHCP_ENV to one of the values above to export dhcp options to kenv */
57185643Sluigi#define DHCP_ENV		DHCP_ENV_NO_VENDOR
58185643Sluigi
5938451Smsmith#include "stand.h"
6038451Smsmith#include "net.h"
6138451Smsmith#include "netif.h"
6238451Smsmith#include "bootp.h"
6338451Smsmith
6438451Smsmith
6538451Smsmithstruct in_addr servip;
6638451Smsmith
6738451Smsmithstatic time_t	bot;
6838451Smsmith
6938451Smsmithstatic	char vm_rfc1048[4] = VM_RFC1048;
7038451Smsmith#ifdef BOOTP_VEND_CMU
7138451Smsmithstatic	char vm_cmu[4] = VM_CMU;
7238451Smsmith#endif
7338451Smsmith
7438451Smsmith/* Local forwards */
7538451Smsmithstatic	ssize_t bootpsend(struct iodesc *, void *, size_t);
76330898Skevansstatic	ssize_t bootprecv(struct iodesc *, void **, void **, time_t, void *);
7738451Smsmithstatic	int vend_rfc1048(u_char *, u_int);
7838451Smsmith#ifdef BOOTP_VEND_CMU
7938451Smsmithstatic	void vend_cmu(u_char *);
8038451Smsmith#endif
8138451Smsmith
82185643Sluigi#ifdef DHCP_ENV		/* export the dhcp response to kenv */
83185643Sluigistruct dhcp_opt;
84185643Sluigistatic void setenv_(u_char *cp,  u_char *ep, struct dhcp_opt *opts);
85185643Sluigi#else
86185643Sluigi#define setenv_(a, b, c)
87185643Sluigi#endif
88185643Sluigi
8938451Smsmith#ifdef SUPPORT_DHCP
9038451Smsmithstatic char expected_dhcpmsgtype = -1, dhcp_ok;
9138451Smsmithstruct in_addr dhcp_serverip;
9238451Smsmith#endif
93329100Skevansstruct bootp *bootp_response;
94329100Skevanssize_t bootp_response_size;
9538451Smsmith
96329100Skevansstatic void
97329100Skevansbootp_fill_request(unsigned char *bp_vend)
98329100Skevans{
99329100Skevans	/*
100329100Skevans	 * We are booting from PXE, we want to send the string
101329100Skevans	 * 'PXEClient' to the DHCP server so you have the option of
102329100Skevans	 * only responding to PXE aware dhcp requests.
103329100Skevans	 */
104329100Skevans	bp_vend[0] = TAG_CLASSID;
105329100Skevans	bp_vend[1] = 9;
106329100Skevans	bcopy("PXEClient", &bp_vend[2], 9);
107329100Skevans	bp_vend[11] = TAG_USER_CLASS;
108329100Skevans	/* len of each user class + number of user class */
109329100Skevans	bp_vend[12] = 8;
110329100Skevans	/* len of the first user class */
111329100Skevans	bp_vend[13] = 7;
112329100Skevans	bcopy("FreeBSD", &bp_vend[14], 7);
113329100Skevans	bp_vend[21] = TAG_PARAM_REQ;
114329100Skevans	bp_vend[22] = 7;
115329100Skevans	bp_vend[23] = TAG_ROOTPATH;
116329100Skevans	bp_vend[24] = TAG_HOSTNAME;
117329100Skevans	bp_vend[25] = TAG_SWAPSERVER;
118329100Skevans	bp_vend[26] = TAG_GATEWAY;
119329100Skevans	bp_vend[27] = TAG_SUBNET_MASK;
120329100Skevans	bp_vend[28] = TAG_INTF_MTU;
121329100Skevans	bp_vend[29] = TAG_SERVERID;
122329100Skevans	bp_vend[30] = TAG_END;
123329100Skevans}
124329100Skevans
12538451Smsmith/* Fetch required bootp infomation */
12638451Smsmithvoid
127329100Skevansbootp(int sock)
12838451Smsmith{
129329100Skevans	void *pkt;
13038451Smsmith	struct iodesc *d;
13192913Sobrien	struct bootp *bp;
13238451Smsmith	struct {
13338451Smsmith		u_char header[HEADER_SIZE];
13438451Smsmith		struct bootp wbootp;
13538451Smsmith	} wbuf;
136329100Skevans	struct bootp *rbootp;
13738451Smsmith
13838451Smsmith#ifdef BOOTP_DEBUG
13938451Smsmith 	if (debug)
14038451Smsmith		printf("bootp: socket=%d\n", sock);
14138451Smsmith#endif
14238451Smsmith	if (!bot)
14338451Smsmith		bot = getsecs();
14438451Smsmith
14538451Smsmith	if (!(d = socktodesc(sock))) {
14638451Smsmith		printf("bootp: bad socket. %d\n", sock);
14738451Smsmith		return;
14838451Smsmith	}
14938451Smsmith#ifdef BOOTP_DEBUG
15038451Smsmith 	if (debug)
15138451Smsmith		printf("bootp: d=%lx\n", (long)d);
15238451Smsmith#endif
15338451Smsmith
15438451Smsmith	bp = &wbuf.wbootp;
15538451Smsmith	bzero(bp, sizeof(*bp));
15638451Smsmith
15738451Smsmith	bp->bp_op = BOOTREQUEST;
15838451Smsmith	bp->bp_htype = 1;		/* 10Mb Ethernet (48 bits) */
15938451Smsmith	bp->bp_hlen = 6;
16038451Smsmith	bp->bp_xid = htonl(d->xid);
16138451Smsmith	MACPY(d->myea, bp->bp_chaddr);
16238451Smsmith	strncpy(bp->bp_file, bootfile, sizeof(bp->bp_file));
16338451Smsmith	bcopy(vm_rfc1048, bp->bp_vend, sizeof(vm_rfc1048));
16438451Smsmith#ifdef SUPPORT_DHCP
16538451Smsmith	bp->bp_vend[4] = TAG_DHCP_MSGTYPE;
16638451Smsmith	bp->bp_vend[5] = 1;
16738451Smsmith	bp->bp_vend[6] = DHCPDISCOVER;
168329100Skevans	bootp_fill_request(&bp->bp_vend[7]);
16964527Sps
17038451Smsmith#else
17138451Smsmith	bp->bp_vend[4] = TAG_END;
17238451Smsmith#endif
17338451Smsmith
17438451Smsmith	d->myip.s_addr = INADDR_ANY;
17538451Smsmith	d->myport = htons(IPPORT_BOOTPC);
17638451Smsmith	d->destip.s_addr = INADDR_BROADCAST;
17738451Smsmith	d->destport = htons(IPPORT_BOOTPS);
17838451Smsmith
17938451Smsmith#ifdef SUPPORT_DHCP
18038451Smsmith	expected_dhcpmsgtype = DHCPOFFER;
18138451Smsmith	dhcp_ok = 0;
18238451Smsmith#endif
18338451Smsmith
18438451Smsmith	if(sendrecv(d,
18538451Smsmith		    bootpsend, bp, sizeof(*bp),
186330898Skevans		    bootprecv, &pkt, (void **)&rbootp, NULL) == -1) {
18738451Smsmith	    printf("bootp: no reply\n");
18838451Smsmith	    return;
18938451Smsmith	}
19038451Smsmith
19138451Smsmith#ifdef SUPPORT_DHCP
19238451Smsmith	if(dhcp_ok) {
193332154Skevans		uint32_t leasetime;
19438451Smsmith		bp->bp_vend[6] = DHCPREQUEST;
19538451Smsmith		bp->bp_vend[7] = TAG_REQ_ADDR;
19638451Smsmith		bp->bp_vend[8] = 4;
197329100Skevans		bcopy(&rbootp->bp_yiaddr, &bp->bp_vend[9], 4);
19838451Smsmith		bp->bp_vend[13] = TAG_SERVERID;
19938451Smsmith		bp->bp_vend[14] = 4;
20038451Smsmith		bcopy(&dhcp_serverip.s_addr, &bp->bp_vend[15], 4);
20138451Smsmith		bp->bp_vend[19] = TAG_LEASETIME;
20238451Smsmith		bp->bp_vend[20] = 4;
20338451Smsmith		leasetime = htonl(300);
20438451Smsmith		bcopy(&leasetime, &bp->bp_vend[21], 4);
205329100Skevans		bootp_fill_request(&bp->bp_vend[25]);
20638451Smsmith
20738451Smsmith		expected_dhcpmsgtype = DHCPACK;
20838451Smsmith
209329100Skevans		free(pkt);
21038451Smsmith		if(sendrecv(d,
21138451Smsmith			    bootpsend, bp, sizeof(*bp),
212330898Skevans			    bootprecv, &pkt, (void **)&rbootp, NULL) == -1) {
21338451Smsmith			printf("DHCPREQUEST failed\n");
21438451Smsmith			return;
21538451Smsmith		}
21638451Smsmith	}
21738451Smsmith#endif
21838451Smsmith
219329100Skevans	myip = d->myip = rbootp->bp_yiaddr;
220329100Skevans	servip = rbootp->bp_siaddr;
221329100Skevans	if (rootip.s_addr == INADDR_ANY)
222329100Skevans		rootip = servip;
223329100Skevans	bcopy(rbootp->bp_file, bootfile, sizeof(bootfile));
22438451Smsmith	bootfile[sizeof(bootfile) - 1] = '\0';
22538451Smsmith
226312930Sbapt	if (!netmask) {
227312930Sbapt		if (IN_CLASSA(ntohl(myip.s_addr)))
228312930Sbapt			netmask = htonl(IN_CLASSA_NET);
229312930Sbapt		else if (IN_CLASSB(ntohl(myip.s_addr)))
230312930Sbapt			netmask = htonl(IN_CLASSB_NET);
231312930Sbapt		else
232312930Sbapt			netmask = htonl(IN_CLASSC_NET);
23338451Smsmith#ifdef BOOTP_DEBUG
23438451Smsmith		if (debug)
235312930Sbapt			printf("'native netmask' is %s\n", intoa(netmask));
23638451Smsmith#endif
23738451Smsmith	}
23838451Smsmith
23938451Smsmith#ifdef BOOTP_DEBUG
24038451Smsmith	if (debug)
24138451Smsmith		printf("mask: %s\n", intoa(netmask));
24238451Smsmith#endif
24338451Smsmith
24438451Smsmith	/* We need a gateway if root is on a different net */
24538451Smsmith	if (!SAMENET(myip, rootip, netmask)) {
24638451Smsmith#ifdef BOOTP_DEBUG
24738451Smsmith		if (debug)
24838451Smsmith			printf("need gateway for root ip\n");
24938451Smsmith#endif
25038451Smsmith	}
25138451Smsmith
25238451Smsmith	/* Toss gateway if on a different net */
25338451Smsmith	if (!SAMENET(myip, gateip, netmask)) {
25438451Smsmith#ifdef BOOTP_DEBUG
25538451Smsmith		if (debug)
25638451Smsmith			printf("gateway ip (%s) bad\n", inet_ntoa(gateip));
25738451Smsmith#endif
25838451Smsmith		gateip.s_addr = 0;
25938451Smsmith	}
26038451Smsmith
26138451Smsmith	/* Bump xid so next request will be unique. */
26238451Smsmith	++d->xid;
263329100Skevans	free(pkt);
26438451Smsmith}
26538451Smsmith
26638451Smsmith/* Transmit a bootp request */
26738451Smsmithstatic ssize_t
268329100Skevansbootpsend(struct iodesc *d, void *pkt, size_t len)
26938451Smsmith{
27092913Sobrien	struct bootp *bp;
27138451Smsmith
27238451Smsmith#ifdef BOOTP_DEBUG
27338451Smsmith	if (debug)
27438451Smsmith		printf("bootpsend: d=%lx called.\n", (long)d);
27538451Smsmith#endif
27638451Smsmith
27738451Smsmith	bp = pkt;
27838451Smsmith	bp->bp_secs = htons((u_short)(getsecs() - bot));
27938451Smsmith
28038451Smsmith#ifdef BOOTP_DEBUG
28138451Smsmith	if (debug)
28238451Smsmith		printf("bootpsend: calling sendudp\n");
28338451Smsmith#endif
28438451Smsmith
28538451Smsmith	return (sendudp(d, pkt, len));
28638451Smsmith}
28738451Smsmith
28838451Smsmithstatic ssize_t
289330898Skevansbootprecv(struct iodesc *d, void **pkt, void **payload, time_t tleft,
290330898Skevans    void *extra)
29138451Smsmith{
29292913Sobrien	ssize_t n;
29392913Sobrien	struct bootp *bp;
294329100Skevans	void *ptr;
29538451Smsmith
296329100Skevans#ifdef BOOTP_DEBUG
29738451Smsmith	if (debug)
29838451Smsmith		printf("bootp_recvoffer: called\n");
29938451Smsmith#endif
30038451Smsmith
301329100Skevans	ptr = NULL;
302329100Skevans	n = readudp(d, &ptr, (void **)&bp, tleft);
30338451Smsmith	if (n == -1 || n < sizeof(struct bootp) - BOOTP_VENDSIZE)
30438451Smsmith		goto bad;
30538451Smsmith
30638451Smsmith#ifdef BOOTP_DEBUG
30738451Smsmith	if (debug)
308329100Skevans		printf("bootprecv: checked.  bp = %p, n = %zd\n", bp, n);
30938451Smsmith#endif
31038451Smsmith	if (bp->bp_xid != htonl(d->xid)) {
31138451Smsmith#ifdef BOOTP_DEBUG
31238451Smsmith		if (debug) {
31338451Smsmith			printf("bootprecv: expected xid 0x%lx, got 0x%x\n",
31438451Smsmith			    d->xid, ntohl(bp->bp_xid));
31538451Smsmith		}
31638451Smsmith#endif
31738451Smsmith		goto bad;
31838451Smsmith	}
31938451Smsmith
32038451Smsmith#ifdef BOOTP_DEBUG
32138451Smsmith	if (debug)
32238451Smsmith		printf("bootprecv: got one!\n");
32338451Smsmith#endif
32438451Smsmith
32538451Smsmith	/* Suck out vendor info */
32638451Smsmith	if (bcmp(vm_rfc1048, bp->bp_vend, sizeof(vm_rfc1048)) == 0) {
327329100Skevans		int vsize = n - offsetof(struct bootp, bp_vend);
328329100Skevans		if (vend_rfc1048(bp->bp_vend, vsize) != 0)
32938451Smsmith		    goto bad;
330329100Skevans
331329100Skevans		/* Save copy of bootp reply or DHCP ACK message */
332329100Skevans		if (bp->bp_op == BOOTREPLY &&
333329100Skevans		    ((dhcp_ok == 1 && expected_dhcpmsgtype == DHCPACK) ||
334329100Skevans		    dhcp_ok == 0)) {
335329100Skevans			free(bootp_response);
336329100Skevans			bootp_response = malloc(n);
337329100Skevans			if (bootp_response != NULL) {
338329100Skevans				bootp_response_size = n;
339329100Skevans				bcopy(bp, bootp_response, bootp_response_size);
340329100Skevans			}
341329100Skevans		}
34238451Smsmith	}
34338451Smsmith#ifdef BOOTP_VEND_CMU
34438451Smsmith	else if (bcmp(vm_cmu, bp->bp_vend, sizeof(vm_cmu)) == 0)
34538451Smsmith		vend_cmu(bp->bp_vend);
34638451Smsmith#endif
34738451Smsmith	else
34838451Smsmith		printf("bootprecv: unknown vendor 0x%lx\n", (long)bp->bp_vend);
34938451Smsmith
350329100Skevans	*pkt = ptr;
351329100Skevans	*payload = bp;
352329100Skevans	return (n);
35338451Smsmithbad:
354329100Skevans	free(ptr);
35538451Smsmith	errno = 0;
35638451Smsmith	return (-1);
35738451Smsmith}
35838451Smsmith
35938451Smsmithstatic int
360329100Skevansvend_rfc1048(u_char *cp, u_int len)
36138451Smsmith{
36292913Sobrien	u_char *ep;
36392913Sobrien	int size;
36492913Sobrien	u_char tag;
365292583Sian	const char *val;
36638451Smsmith
36738451Smsmith#ifdef BOOTP_DEBUG
36838451Smsmith	if (debug)
36938451Smsmith		printf("vend_rfc1048 bootp info. len=%d\n", len);
37038451Smsmith#endif
37138451Smsmith	ep = cp + len;
37238451Smsmith
37338451Smsmith	/* Step over magic cookie */
37438451Smsmith	cp += sizeof(int);
37538451Smsmith
376185643Sluigi	setenv_(cp, ep, NULL);
377185643Sluigi
37838451Smsmith	while (cp < ep) {
37938451Smsmith		tag = *cp++;
38038451Smsmith		size = *cp++;
38138451Smsmith		if (tag == TAG_END)
38238451Smsmith			break;
38338451Smsmith
38438451Smsmith		if (tag == TAG_SUBNET_MASK) {
385312930Sbapt			bcopy(cp, &netmask, sizeof(netmask));
38638451Smsmith		}
38738451Smsmith		if (tag == TAG_GATEWAY) {
38838451Smsmith			bcopy(cp, &gateip.s_addr, sizeof(gateip.s_addr));
38938451Smsmith		}
39038451Smsmith		if (tag == TAG_SWAPSERVER) {
39138451Smsmith			/* let it override bp_siaddr */
392292583Sian			bcopy(cp, &rootip.s_addr, sizeof(rootip.s_addr));
39338451Smsmith		}
39438451Smsmith		if (tag == TAG_ROOTPATH) {
395292583Sian			if ((val = getenv("dhcp.root-path")) == NULL)
396292583Sian				val = (const char *)cp;
397292583Sian			strlcpy(rootpath, val, sizeof(rootpath));
39838451Smsmith		}
39938451Smsmith		if (tag == TAG_HOSTNAME) {
400292583Sian			if ((val = getenv("dhcp.host-name")) == NULL)
401292583Sian				val = (const char *)cp;
402292583Sian			strlcpy(hostname, val, sizeof(hostname));
40338451Smsmith		}
404297150Sian		if (tag == TAG_INTF_MTU) {
405329099Skevans			intf_mtu = 0;
406297150Sian			if ((val = getenv("dhcp.interface-mtu")) != NULL) {
407329099Skevans				unsigned long tmp;
408329099Skevans				char *end;
409329099Skevans
410329099Skevans				errno = 0;
411329099Skevans				/*
412329099Skevans				 * Do not allow MTU to exceed max IPv4 packet
413329099Skevans				 * size, max value of 16-bit word.
414329099Skevans				 */
415329099Skevans				tmp = strtoul(val, &end, 0);
416329099Skevans				if (errno != 0 ||
417329099Skevans				    *val == '\0' || *end != '\0' ||
418329099Skevans				    tmp > USHRT_MAX) {
419329099Skevans					printf("%s: bad value: \"%s\", "
420329099Skevans					    "ignoring\n",
421329099Skevans					    "dhcp.interface-mtu", val);
422329099Skevans				} else {
423329099Skevans					intf_mtu = (u_int)tmp;
424329099Skevans				}
425329099Skevans			}
426329099Skevans			if (intf_mtu <= 0)
427297150Sian				intf_mtu = be16dec(cp);
428297150Sian		}
42938451Smsmith#ifdef SUPPORT_DHCP
43038451Smsmith		if (tag == TAG_DHCP_MSGTYPE) {
43138451Smsmith			if(*cp != expected_dhcpmsgtype)
43238451Smsmith			    return(-1);
43338451Smsmith			dhcp_ok = 1;
43438451Smsmith		}
43538451Smsmith		if (tag == TAG_SERVERID) {
43638451Smsmith			bcopy(cp, &dhcp_serverip.s_addr,
43738451Smsmith			      sizeof(dhcp_serverip.s_addr));
43838451Smsmith		}
43938451Smsmith#endif
44038451Smsmith		cp += size;
44138451Smsmith	}
44238451Smsmith	return(0);
44338451Smsmith}
44438451Smsmith
44538451Smsmith#ifdef BOOTP_VEND_CMU
44638451Smsmithstatic void
447329100Skevansvend_cmu(u_char *cp)
44838451Smsmith{
44992913Sobrien	struct cmu_vend *vp;
45038451Smsmith
45138451Smsmith#ifdef BOOTP_DEBUG
45238451Smsmith	if (debug)
45338451Smsmith		printf("vend_cmu bootp info.\n");
45438451Smsmith#endif
45538451Smsmith	vp = (struct cmu_vend *)cp;
45638451Smsmith
45738451Smsmith	if (vp->v_smask.s_addr != 0) {
458312930Sbapt		netmask = vp->v_smask.s_addr;
45938451Smsmith	}
46038451Smsmith	if (vp->v_dgate.s_addr != 0) {
46138451Smsmith		gateip = vp->v_dgate;
46238451Smsmith	}
46338451Smsmith}
46438451Smsmith#endif
465185643Sluigi
466185643Sluigi#ifdef DHCP_ENV
467185643Sluigi/*
468185643Sluigi * Parse DHCP options and store them into kenv variables.
469185643Sluigi * Original code from Danny Braniss, modifications by Luigi Rizzo.
470185643Sluigi *
471185643Sluigi * The parser is driven by tables which specify the type and name of
472185643Sluigi * each dhcp option and how it appears in kenv.
473185643Sluigi * The first entry in the list contains the prefix used to set the kenv
474185643Sluigi * name (including the . if needed), the last entry must have a 0 tag.
475185643Sluigi * Entries do not need to be sorted though it helps for readability.
476185643Sluigi *
477185643Sluigi * Certain vendor-specific tables can be enabled according to DHCP_ENV.
478185643Sluigi * Set it to 0 if you don't want any.
479185643Sluigi */
480185643Sluigienum opt_fmt { __NONE = 0,
481185643Sluigi	__8 = 1, __16 = 2, __32 = 4,	/* Unsigned fields, value=size	*/
482185643Sluigi	__IP,				/* IPv4 address			*/
483185643Sluigi	__TXT,				/* C string			*/
484185643Sluigi	__BYTES,			/* byte sequence, printed %02x	*/
485185643Sluigi	__INDIR,			/* name=value			*/
486185643Sluigi	__ILIST,			/* name=value;name=value ... */
487185643Sluigi	__VE,				/* vendor specific, recurse	*/
488185643Sluigi};
489185643Sluigi
490185643Sluigistruct dhcp_opt {
491185643Sluigi	uint8_t	tag;
492185643Sluigi	uint8_t	fmt;
493185643Sluigi	const char	*desc;
494185643Sluigi};
495185643Sluigi
496185643Sluigistatic struct dhcp_opt vndr_opt[] = { /* Vendor Specific Options */
497185643Sluigi#if DHCP_ENV == DHCP_ENV_FREEBSD /* FreeBSD table in the original code */
498185643Sluigi	{0,	0,	"FreeBSD"},		/* prefix */
499185643Sluigi	{1,	__TXT,	"kernel"},
500185643Sluigi	{2,	__TXT,	"kernelname"},
501185643Sluigi	{3,	__TXT,	"kernel_options"},
502185643Sluigi	{4,	__IP,	"usr-ip"},
503185643Sluigi	{5,	__TXT,	"conf-path"},
504185643Sluigi	{6,	__TXT,	"rc.conf0"},
505185643Sluigi	{7,	__TXT,	"rc.conf1"},
506185643Sluigi	{8,	__TXT,	"rc.conf2"},
507185643Sluigi	{9,	__TXT,	"rc.conf3"},
508185643Sluigi	{10,	__TXT,	"rc.conf4"},
509185643Sluigi	{11,	__TXT,	"rc.conf5"},
510185643Sluigi	{12,	__TXT,	"rc.conf6"},
511185643Sluigi	{13,	__TXT,	"rc.conf7"},
512185643Sluigi	{14,	__TXT,	"rc.conf8"},
513185643Sluigi	{15,	__TXT,	"rc.conf9"},
514185643Sluigi
515185643Sluigi	{20,	__TXT,  "boot.nfsroot.options"},
516185643Sluigi
517185643Sluigi	{245,	__INDIR, ""},
518185643Sluigi	{246,	__INDIR, ""},
519185643Sluigi	{247,	__INDIR, ""},
520185643Sluigi	{248,	__INDIR, ""},
521185643Sluigi	{249,	__INDIR, ""},
522185643Sluigi	{250,	__INDIR, ""},
523185643Sluigi	{251,	__INDIR, ""},
524185643Sluigi	{252,	__INDIR, ""},
525185643Sluigi	{253,	__INDIR, ""},
526185643Sluigi	{254,	__INDIR, ""},
527185643Sluigi
528185643Sluigi#elif DHCP_ENV == DHCP_ENV_PXE		/* some pxe options, RFC4578 */
529185643Sluigi	{0,	0,	"pxe"},		/* prefix */
530185643Sluigi	{93,	__16,	"system-architecture"},
531185643Sluigi	{94,	__BYTES,	"network-interface"},
532185643Sluigi	{97,	__BYTES,	"machine-identifier"},
533185643Sluigi#else					/* default (empty) table */
534186799Sluigi	{0,	0,	"dhcp.vendor."},		/* prefix */
535185643Sluigi#endif
536185643Sluigi	{0,	__TXT,	"%soption-%d"}
537185643Sluigi};
538185643Sluigi
539185643Sluigistatic struct dhcp_opt dhcp_opt[] = {
540185643Sluigi	/* DHCP Option names, formats and codes, from RFC2132. */
541185643Sluigi	{0,	0,	"dhcp."},	// prefix
542185643Sluigi	{1,	__IP,	"subnet-mask"},
543185643Sluigi	{2,	__32,	"time-offset"}, /* this is signed */
544185643Sluigi	{3,	__IP,	"routers"},
545185643Sluigi	{4,	__IP,	"time-servers"},
546185643Sluigi	{5,	__IP,	"ien116-name-servers"},
547185643Sluigi	{6,	__IP,	"domain-name-servers"},
548185643Sluigi	{7,	__IP,	"log-servers"},
549185643Sluigi	{8,	__IP,	"cookie-servers"},
550185643Sluigi	{9,	__IP,	"lpr-servers"},
551185643Sluigi	{10,	__IP,	"impress-servers"},
552185643Sluigi	{11,	__IP,	"resource-location-servers"},
553185643Sluigi	{12,	__TXT,	"host-name"},
554185643Sluigi	{13,	__16,	"boot-size"},
555185643Sluigi	{14,	__TXT,	"merit-dump"},
556185643Sluigi	{15,	__TXT,	"domain-name"},
557185643Sluigi	{16,	__IP,	"swap-server"},
558185643Sluigi	{17,	__TXT,	"root-path"},
559185643Sluigi	{18,	__TXT,	"extensions-path"},
560185643Sluigi	{19,	__8,	"ip-forwarding"},
561185643Sluigi	{20,	__8,	"non-local-source-routing"},
562185643Sluigi	{21,	__IP,	"policy-filter"},
563185643Sluigi	{22,	__16,	"max-dgram-reassembly"},
564185643Sluigi	{23,	__8,	"default-ip-ttl"},
565185643Sluigi	{24,	__32,	"path-mtu-aging-timeout"},
566185643Sluigi	{25,	__16,	"path-mtu-plateau-table"},
567185643Sluigi	{26,	__16,	"interface-mtu"},
568185643Sluigi	{27,	__8,	"all-subnets-local"},
569185643Sluigi	{28,	__IP,	"broadcast-address"},
570185643Sluigi	{29,	__8,	"perform-mask-discovery"},
571185643Sluigi	{30,	__8,	"mask-supplier"},
572185643Sluigi	{31,	__8,	"perform-router-discovery"},
573185643Sluigi	{32,	__IP,	"router-solicitation-address"},
574185643Sluigi	{33,	__IP,	"static-routes"},
575185643Sluigi	{34,	__8,	"trailer-encapsulation"},
576185643Sluigi	{35,	__32,	"arp-cache-timeout"},
577185643Sluigi	{36,	__8,	"ieee802-3-encapsulation"},
578185643Sluigi	{37,	__8,	"default-tcp-ttl"},
579185643Sluigi	{38,	__32,	"tcp-keepalive-interval"},
580185643Sluigi	{39,	__8,	"tcp-keepalive-garbage"},
581185643Sluigi	{40,	__TXT,	"nis-domain"},
582185643Sluigi	{41,	__IP,	"nis-servers"},
583185643Sluigi	{42,	__IP,	"ntp-servers"},
584185643Sluigi	{43,	__VE,	"vendor-encapsulated-options"},
585185643Sluigi	{44,	__IP,	"netbios-name-servers"},
586185643Sluigi	{45,	__IP,	"netbios-dd-server"},
587185643Sluigi	{46,	__8,	"netbios-node-type"},
588185643Sluigi	{47,	__TXT,	"netbios-scope"},
589185643Sluigi	{48,	__IP,	"x-font-servers"},
590185643Sluigi	{49,	__IP,	"x-display-managers"},
591185643Sluigi	{50,	__IP,	"dhcp-requested-address"},
592185643Sluigi	{51,	__32,	"dhcp-lease-time"},
593185643Sluigi	{52,	__8,	"dhcp-option-overload"},
594185643Sluigi	{53,	__8,	"dhcp-message-type"},
595185643Sluigi	{54,	__IP,	"dhcp-server-identifier"},
596185643Sluigi	{55,	__8,	"dhcp-parameter-request-list"},
597185643Sluigi	{56,	__TXT,	"dhcp-message"},
598185643Sluigi	{57,	__16,	"dhcp-max-message-size"},
599185643Sluigi	{58,	__32,	"dhcp-renewal-time"},
600185643Sluigi	{59,	__32,	"dhcp-rebinding-time"},
601185643Sluigi	{60,	__TXT,	"vendor-class-identifier"},
602185643Sluigi	{61,	__TXT,	"dhcp-client-identifier"},
603185643Sluigi	{64,	__TXT,	"nisplus-domain"},
604185643Sluigi	{65,	__IP,	"nisplus-servers"},
605185643Sluigi	{66,	__TXT,	"tftp-server-name"},
606185643Sluigi	{67,	__TXT,	"bootfile-name"},
607185643Sluigi	{68,	__IP,	"mobile-ip-home-agent"},
608185643Sluigi	{69,	__IP,	"smtp-server"},
609185643Sluigi	{70,	__IP,	"pop-server"},
610185643Sluigi	{71,	__IP,	"nntp-server"},
611185643Sluigi	{72,	__IP,	"www-server"},
612185643Sluigi	{73,	__IP,	"finger-server"},
613185643Sluigi	{74,	__IP,	"irc-server"},
614185643Sluigi	{75,	__IP,	"streettalk-server"},
615185643Sluigi	{76,	__IP,	"streettalk-directory-assistance-server"},
616185643Sluigi	{77,	__TXT,	"user-class"},
617185643Sluigi	{85,	__IP,	"nds-servers"},
618185643Sluigi	{86,	__TXT,	"nds-tree-name"},
619185643Sluigi	{87,	__TXT,	"nds-context"},
620185643Sluigi	{210,	__TXT,	"authenticate"},
621185643Sluigi
622185643Sluigi	/* use the following entries for arbitrary variables */
623185643Sluigi	{246,	__ILIST, ""},
624185643Sluigi	{247,	__ILIST, ""},
625185643Sluigi	{248,	__ILIST, ""},
626185643Sluigi	{249,	__ILIST, ""},
627185643Sluigi	{250,	__INDIR, ""},
628185643Sluigi	{251,	__INDIR, ""},
629185643Sluigi	{252,	__INDIR, ""},
630185643Sluigi	{253,	__INDIR, ""},
631185643Sluigi	{254,	__INDIR, ""},
632185643Sluigi	{0,	__TXT,	"%soption-%d"}
633185643Sluigi};
634185643Sluigi
635185643Sluigi/*
636185643Sluigi * parse a dhcp response, set environment variables translating options
637185643Sluigi * names and values according to the tables above. Also set dhcp.tags
638185643Sluigi * to the list of selected tags.
639185643Sluigi */
640185643Sluigistatic void
641185643Sluigisetenv_(u_char *cp,  u_char *ep, struct dhcp_opt *opts)
642185643Sluigi{
643185643Sluigi    u_char	*ncp;
644185643Sluigi    u_char	tag;
645185643Sluigi    char	tags[512], *tp;	/* the list of tags */
646185643Sluigi
647185643Sluigi#define FLD_SEP	','	/* separator in list of elements */
648185643Sluigi    ncp = cp;
649185643Sluigi    tp = tags;
650185643Sluigi    if (opts == NULL)
651185643Sluigi	opts = dhcp_opt;
652185643Sluigi
653185643Sluigi    while (ncp < ep) {
654185643Sluigi	unsigned int	size;		/* option size */
655185643Sluigi	char *vp, *endv, buf[256];	/* the value buffer */
656185643Sluigi	struct dhcp_opt *op;
657185643Sluigi
658185643Sluigi	tag = *ncp++;			/* extract tag and size */
659185643Sluigi	size = *ncp++;
660185643Sluigi	cp = ncp;			/* current payload */
661185643Sluigi	ncp += size;			/* point to the next option */
662185643Sluigi
663185643Sluigi	if (tag == TAG_END)
664185643Sluigi	    break;
665185643Sluigi	if (tag == 0)
666185643Sluigi	    continue;
667185643Sluigi
668185643Sluigi	for (op = opts+1; op->tag && op->tag != tag; op++)
669185643Sluigi		;
670185643Sluigi	/* if not found we end up on the default entry */
671185643Sluigi
672185643Sluigi	/*
673185643Sluigi	 * Copy data into the buffer. libstand does not have snprintf so we
674185643Sluigi	 * need to be careful with sprintf(). With strings, the source is
675185643Sluigi	 * always <256 char so shorter than the buffer so we are safe; with
676185643Sluigi	 * other arguments, the longest string is inet_ntoa which is 16 bytes
677185643Sluigi	 * so we make sure to have always enough room in the string before
678185643Sluigi	 * trying an sprint.
679185643Sluigi	 */
680185643Sluigi	vp = buf;
681185643Sluigi	*vp = '\0';
682185643Sluigi	endv = buf + sizeof(buf) - 1 - 16;	/* last valid write position */
683185643Sluigi
684185643Sluigi	switch(op->fmt) {
685185643Sluigi	case __NONE:
686185643Sluigi	    break;	/* should not happen */
687185643Sluigi
688185643Sluigi	case __VE: /* recurse, vendor specific */
689185643Sluigi	    setenv_(cp, cp+size, vndr_opt);
690185643Sluigi	    break;
691185643Sluigi
692185643Sluigi	case __IP:	/* ip address */
693185643Sluigi	    for (; size > 0 && vp < endv; size -= 4, cp += 4) {
694185643Sluigi		struct	in_addr in_ip;		/* ip addresses */
695185643Sluigi		if (vp != buf)
696185643Sluigi		    *vp++ = FLD_SEP;
697185643Sluigi		bcopy(cp, &in_ip.s_addr, sizeof(in_ip.s_addr));
698185643Sluigi		sprintf(vp, "%s", inet_ntoa(in_ip));
699185643Sluigi		vp += strlen(vp);
700185643Sluigi	    }
701185643Sluigi	    break;
702185643Sluigi
703185643Sluigi	case __BYTES:	/* opaque byte string */
704185643Sluigi	    for (; size > 0 && vp < endv; size -= 1, cp += 1) {
705185643Sluigi		sprintf(vp, "%02x", *cp);
706185643Sluigi		vp += strlen(vp);
707185643Sluigi	    }
708185643Sluigi	    break;
709185643Sluigi
710185643Sluigi	case __TXT:
711185643Sluigi	    bcopy(cp, buf, size);	/* cannot overflow */
712185643Sluigi	    buf[size] = 0;
713185643Sluigi	    break;
714185643Sluigi
715185643Sluigi	case __32:
716185643Sluigi	case __16:
717185643Sluigi	case __8:	/* op->fmt is also the length of each field */
718185643Sluigi	    for (; size > 0 && vp < endv; size -= op->fmt, cp += op->fmt) {
719185643Sluigi		uint32_t v;
720185643Sluigi		if (op->fmt == __32)
721185643Sluigi			v = (cp[0]<<24) + (cp[1]<<16) + (cp[2]<<8) + cp[3];
722185643Sluigi		else if (op->fmt == __16)
723185643Sluigi			v = (cp[0]<<8) + cp[1];
724185643Sluigi		else
725185643Sluigi			v = cp[0];
726185643Sluigi		if (vp != buf)
727185643Sluigi		    *vp++ = FLD_SEP;
728185643Sluigi		sprintf(vp, "%u", v);
729185643Sluigi		vp += strlen(vp);
730185643Sluigi	    }
731185643Sluigi	    break;
732185643Sluigi
733185643Sluigi	case __INDIR:	/* name=value */
734185643Sluigi	case __ILIST:	/* name=value;name=value... */
735185643Sluigi	    bcopy(cp, buf, size);	/* cannot overflow */
736185643Sluigi	    buf[size] = '\0';
737185643Sluigi	    for (endv = buf; endv; endv = vp) {
738344408Skevans		char *s = NULL;	/* semicolon ? */
739185643Sluigi
740185643Sluigi		/* skip leading whitespace */
741229403Sed		while (*endv && strchr(" \t\n\r", *endv))
742185643Sluigi		    endv++;
743229403Sed		vp = strchr(endv, '=');	/* find name=value separator */
744185643Sluigi		if (!vp)
745185643Sluigi		    break;
746185643Sluigi		*vp++ = 0;
747229403Sed		if (op->fmt == __ILIST && (s = strchr(vp, ';')))
748185643Sluigi		    *s++ = '\0';
749185643Sluigi		setenv(endv, vp, 1);
750185643Sluigi		vp = s;	/* prepare for next round */
751185643Sluigi	    }
752185643Sluigi	    buf[0] = '\0';	/* option already done */
753185643Sluigi	}
754185643Sluigi
755185643Sluigi	if (tp - tags < sizeof(tags) - 5) {	/* add tag to the list */
756185643Sluigi	    if (tp != tags)
757185643Sluigi		*tp++ = FLD_SEP;
758185643Sluigi	    sprintf(tp, "%d", tag);
759185643Sluigi	    tp += strlen(tp);
760185643Sluigi	}
761185643Sluigi	if (buf[0]) {
762185643Sluigi	    char	env[128];	/* the string name */
763185643Sluigi
764185643Sluigi	    if (op->tag == 0)
765185643Sluigi		sprintf(env, op->desc, opts[0].desc, tag);
766185643Sluigi	    else
767185643Sluigi		sprintf(env, "%s%s", opts[0].desc, op->desc);
768292583Sian	    /*
769292583Sian	     * Do not replace existing values in the environment, so that
770292583Sian	     * locally-obtained values can override server-provided values.
771292583Sian	     */
772292583Sian	    setenv(env, buf, 0);
773185643Sluigi	}
774185643Sluigi    }
775185643Sluigi    if (tp != tags) {
776185643Sluigi	char	env[128];	/* the string name */
777185643Sluigi	sprintf(env, "%stags", opts[0].desc);
778185643Sluigi	setenv(env, tags, 1);
779185643Sluigi    }
780185643Sluigi}
781185643Sluigi#endif /* additional dhcp */
782