xref: /netbsd-src/usr.sbin/isibootd/isibootd.c (revision 626fac18a1370d3613b437c24b097937177fa959)
1 /*	$NetBSD: isibootd.c,v 1.5 2021/08/22 20:18:39 andvar Exp $	*/
2 /*	Id: isiboot.c,v 1.2 1999/12/26 14:33:33 nisimura Exp 	*/
3 
4 /*-
5  * Copyright (c) 2000, 2011 The NetBSD Foundation, Inc.
6  * All rights reserved.
7  *
8  * This code is derived from software contributed to The NetBSD Foundation
9  * by Tohru Nishimura.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
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  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 #include <sys/param.h>
34 #include <sys/types.h>
35 #include <sys/endian.h>
36 #include <sys/ioctl.h>
37 #include <sys/socket.h>
38 
39 #include <net/bpf.h>
40 #include <net/if.h>
41 #include <net/if_dl.h>
42 #include <net/if_ether.h>
43 
44 #include <err.h>
45 #include <fcntl.h>
46 #include <ifaddrs.h>
47 #include <netdb.h>
48 #include <paths.h>
49 #include <poll.h>
50 #include <stddef.h>
51 #include <stdio.h>
52 #include <stdlib.h>
53 #include <string.h>
54 #include <unistd.h>
55 #include <util.h>
56 
57 #define	TRACE(l, x) if ((l) <= dbg) printf x
58 
59 /*
60  * Integrated Solutions Inc. "ISIBOOT" boot enet protocol.
61  *
62  * Following data format depends on m68k order, and aligned harmful
63  * to RISC processors.
64  */
65 #define	ISIBOOT_FRAMETYPE	0x80df
66 #define	ISIBOOT_FRAMELEN	1468
67 struct frame {
68 	uint8_t dst[ETHER_ADDR_LEN];
69 	uint8_t src[ETHER_ADDR_LEN];
70 	uint16_t type;
71 	uint16_t pad_0;
72 	uint16_t seqno;
73 	uint8_t opcode;
74 	uint8_t pad_1;
75 	uint8_t pos[4];
76 	uint8_t siz[4];
77 	uint8_t data[ISIBOOT_FRAMELEN - 28];
78 } __packed;
79 
80 struct station {
81 	int 	fd;
82 	char	name[MAXHOSTNAMELEN];
83 	char	ifname[IFNAMSIZ];
84 	uint8_t addr[ETHER_ADDR_LEN];
85 } station;
86 
87 struct session {
88 	struct session *next;
89 	int state;
90 	FILE *file;
91 	uint8_t addr[ETHER_ADDR_LEN];
92 } *activelist, *freelist;
93 #define	NEWPOOL 10
94 
95 #define	WAITING	0	/* implicit state after receiving the first frame */
96 #define	OPENING	1	/* waiting for OPEN after CONNECT is received */
97 #define	TRANSFER 2	/* data transferring state after OPEN is well done */
98 static __unused const char *state[] = { "WAITING", "OPENING", "TRANSFER" };
99 
100 #define	CONNECT	0
101 #define	OPEN	1
102 #define	READ	2
103 #define	CLOSE	4
104 static __unused const char *op[] =
105     { "CONNECT", "OPEN", "READ", "WRITE", "CLOSE", "FIND" };
106 
107 static void createbpfport(char *, uint8_t **, size_t *, struct station *);
108 static struct session *search(uint8_t *);
109 static void closedown(struct session *);
110 static void makepool(void);
111 static char *etheraddr(uint8_t *);
112 static int pickif(char *, uint8_t *);
113 static __dead void usage(void);
114 
115 #define	ISIBOOT_FRAME(buf)	((buf) + ((struct bpf_hdr *)(buf))->bh_hdrlen)
116 
117 #define	PATH_DEFBOOTDIR	"/tftpboot"
118 
119 int
main(int argc,char * argv[])120 main(int argc, char *argv[])
121 {
122 	int cc, dbg, dflag;
123 	size_t iolen;
124 	uint32_t pos, siz;
125 	size_t nread;
126 	char *ifname, *p;
127 	const char *bootwd, *servername, *filename;
128 	uint8_t *iobuf;
129 	struct session *cp;
130 	struct frame *fp;
131 	struct pollfd pollfd;
132 	char clientname[MAXHOSTNAMELEN + 1];
133 	struct hostent *clientent;
134 
135 	ifname = NULL;
136 	bootwd = PATH_DEFBOOTDIR;
137 	dbg = 0;
138 	dflag = 0;
139 	while ((cc = getopt(argc, argv, "i:s:d:")) != -1) {
140 		switch (cc) {
141 		case 'i':
142 			ifname = optarg;
143 			break;
144 		case 's':
145 			bootwd = optarg;
146 			break;
147 		case 'd':
148 			dflag = 1;
149 			dbg = atoi(optarg);
150 			break;
151 		default:
152 			usage();
153 			/* NOTREACHED */
154 		}
155 	}
156 	argv += optind;
157 	argc -= optind;
158 
159 	if (geteuid() != 0)
160 		warnx("WARNING: run by non root privilege");
161 
162 	memset(station.name, 0, sizeof(station.name));
163 	gethostname(station.name, sizeof(station.name) - 1);
164 	if ((p = strchr(station.name, '.')) != NULL)
165 		*p = '\0';
166 
167 	createbpfport(ifname, &iobuf, &iolen, &station);
168 
169 	TRACE(1, ("Using interface: %s (%s)\n",
170 	    station.ifname, etheraddr(station.addr)));
171 
172 	if (!dflag) {
173 		if (daemon(0, 0))
174 			err(EXIT_FAILURE, "can not start daemon");
175 #ifdef __NetBSD__
176 		pidfile(NULL);
177 #endif
178 	}
179 
180 	if (chdir(bootwd) < 0)
181 		err(EXIT_FAILURE, "can not chdir to %s", bootwd);
182 
183 	pollfd.fd = station.fd;
184 	pollfd.events = POLLIN;
185 	for (;;) {
186 		poll(&pollfd, 1, INFTIM);
187 		read(pollfd.fd, iobuf, iolen);	/* returns 1468 */
188 		fp = (struct frame *)ISIBOOT_FRAME(iobuf);
189 
190 		/* ignore own TX packets */
191 		if (memcmp(fp->src, station.addr, ETHER_ADDR_LEN) == 0)
192 			continue;
193 
194 		/* check if the received Ethernet address is in ethers(5) */
195 		if (ether_ntohost(clientname, (struct ether_addr *)fp->src)) {
196 			TRACE(3, ("'%s' is not in ethers(5)\n",
197 			    etheraddr(fp->src)));
198 			continue;
199 		}
200 		/* check if the client has a valid hostname */
201 		clientname[sizeof(clientname) - 1] = '\0';
202 		clientent = gethostbyname(clientname);
203 		if (clientent == NULL || clientent->h_addrtype != AF_INET) {
204 			TRACE(3, ("'%s' is not a valid host\n", clientname));
205 			continue;
206 		}
207 
208 		cp = search(fp->src);
209 		TRACE(2, ("[%s] ", etheraddr(fp->src)));
210 		switch (cp->state) {
211 		case WAITING:
212 			if (fp->opcode != CONNECT) {
213 				TRACE(2, ("not connected\n"));
214 				continue;
215 			}
216 			/* check if specified servername is mine */
217 			fp->data[sizeof(fp->data) - 1] = '\0';
218 			servername = (char *)fp->data;
219 			if (strcmp(servername, station.name) != 0) {
220 				TRACE(3, ("'%s' not for me\n", servername));
221 				continue;
222 			}
223 			cp->state = OPENING;
224 			TRACE(2, ("new connection\n"));
225 			break;
226 		case OPENING:
227 			if (fp->opcode != OPEN)
228 				goto aborting;	/* out of phase */
229 
230 			/* don't allow files outside the specified dir */
231 			fp->data[sizeof(fp->data) - 1] = '\0';
232 			filename = strrchr((char *)fp->data, '/');
233 			if (filename != NULL)
234 				filename++;
235 			else
236 				filename = (char *)fp->data;
237 
238 			cp->file = fopen(filename, "r");
239 			if (cp->file == NULL) {
240 				TRACE(1, ("failed to open '%s'\n", filename));
241 				goto closedown;	/* no such file */
242 			}
243 			cp->state = TRANSFER;
244 			TRACE(2, ("open '%s'\n", filename));
245 			break;
246 		case TRANSFER:
247 			if (fp->opcode == CLOSE) {
248 				TRACE(2, ("connection closed\n"));
249 				goto closedown;	/* close request */
250 			}
251 			if (fp->opcode != READ)
252 				goto aborting;	/* out of phase */
253 			siz = be32dec(fp->siz);
254 			pos = be32dec(fp->pos);
255 			nread = siz;
256 			if (nread > sizeof(fp->data) ||
257 			    fseek(cp->file, pos, 0L) < 0 ||
258 			    fread(fp->data, 1, nread, cp->file) < nread) {
259 				be32enc(fp->siz, 0); /* corrupted file */
260 			}
261 			TRACE(3, ("%u@%u\n", siz, pos));
262 			break;
263  aborting:
264 			TRACE(1, ("out of phase\n"));
265  closedown:
266 			closedown(cp);
267 			fp->opcode = CLOSE;
268 			break;
269 		}
270 		memcpy(fp->dst, fp->src, ETHER_ADDR_LEN);
271 		memcpy(fp->src, station.addr, ETHER_ADDR_LEN);
272 		write(pollfd.fd, fp, ISIBOOT_FRAMELEN);
273 	}
274 	/* NOTREACHED */
275 }
276 
277 struct session *
search(uint8_t * client)278 search(uint8_t *client)
279 {
280 	struct session *cp;
281 
282 	for (cp = activelist; cp; cp = cp->next) {
283 		if (memcmp(client, cp->addr, ETHER_ADDR_LEN) == 0)
284 			return cp;
285 	}
286 	if (freelist == NULL)
287 		makepool();
288 	cp = freelist;
289 	freelist = cp->next;
290 	cp->next = activelist;
291 	activelist = cp;
292 
293 	cp->state = WAITING;
294 	cp->file = NULL;
295 	memcpy(cp->addr, client, ETHER_ADDR_LEN);
296 	return cp;
297 }
298 
299 void
closedown(struct session * cp)300 closedown(struct session *cp)
301 {
302 	struct session *cpp;
303 
304 	cpp = activelist;
305 	if (cpp == cp)
306 		activelist = cp->next;
307 	else {
308 		do {
309 			if (cpp->next == cp)
310 				break;
311 		} while (NULL != (cpp = cpp->next)); /* should never happen */
312 		cpp->next = cp->next;
313 	}
314 	cp->next = freelist;
315 	freelist = cp;
316 
317 	if (cp->file != NULL)
318 		fclose(cp->file);
319 	cp->file = NULL;
320 	memset(cp->addr, 0, ETHER_ADDR_LEN);
321 }
322 
323 void
makepool(void)324 makepool(void)
325 {
326 	struct session *cp;
327 	int n;
328 
329 	freelist = calloc(NEWPOOL, sizeof(struct session));
330 	if (freelist == NULL)
331 		err(EXIT_FAILURE, "Can't allocate pool");
332 	cp = freelist;
333 	for (n = 0; n < NEWPOOL - 1; n++) {
334 		cp->next = cp + 1;
335 		cp++;
336 	}
337 }
338 
339 char *
etheraddr(uint8_t * e)340 etheraddr(uint8_t *e)
341 {
342 	static char address[sizeof("xx:xx:xx:xx:xx:xx")];
343 
344 	snprintf(address, sizeof(address), "%02x:%02x:%02x:%02x:%02x:%02x",
345 	    e[0], e[1], e[2], e[3], e[4], e[5]);
346 	return address;
347 }
348 
349 static struct bpf_insn bpf_insn[] = {
350 	{ BPF_LD|BPF_H|BPF_ABS,  0, 0, offsetof(struct frame, type) },
351 	{ BPF_JMP|BPF_JEQ|BPF_K, 0, 1, ISIBOOT_FRAMETYPE },
352 	{ BPF_RET|BPF_K,         0, 0, ISIBOOT_FRAMELEN },
353 	{ BPF_RET|BPF_K,         0, 0, 0x0 }
354 };
355 static struct bpf_program bpf_pgm = {
356 	sizeof(bpf_insn) / sizeof(bpf_insn[0]),
357 	bpf_insn
358 };
359 
360 void
createbpfport(char * ifname,uint8_t ** iobufp,size_t * iolenp,struct station * st)361 createbpfport(char *ifname, uint8_t **iobufp, size_t *iolenp,
362     struct station *st)
363 {
364 	struct ifreq ifr;
365 	int fd;
366 	u_int type;
367 	u_int buflen;
368 	uint8_t dladdr[ETHER_ADDR_LEN], *buf;
369 #ifdef BIOCIMMEDIATE
370 	u_int flag;
371 #endif
372 #ifndef _PATH_BPF
373 	char devbpf[PATH_MAX];
374 	int n;
375 #endif
376 
377 #ifdef _PATH_BPF
378 	fd = open(_PATH_BPF, O_RDWR, 0);
379 #else
380 	n = 0;
381 	do {
382 		snprintf(devbpf, sizeof(devbpf), "/dev/bpf%d", n++);
383 		fd = open(devbpf, O_RDWR, 0);
384 	} while (fd == -1 && errno == EBUSY);
385 #endif
386 	if (fd == -1)
387 		err(EXIT_FAILURE, "No bpf device available");
388 	memset(&ifr, 0, sizeof(ifr));
389 	if (ifname != NULL)
390 		strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
391 	if (pickif(ifr.ifr_name, dladdr) < 0)
392 		errx(EXIT_FAILURE,
393 		    "No network interface available: %s\n", ifr.ifr_name);
394 
395 	ioctl(fd, BIOCSETIF, &ifr);
396 	ioctl(fd, BIOCGDLT, &type);	/* XXX - should check whether EN10MB */
397 #ifdef BIOCIMMEDIATE
398 	flag = 1;
399 	ioctl(fd, BIOCIMMEDIATE, &flag);
400 #endif
401 	ioctl(fd, BIOCGBLEN, &buflen);
402 	ioctl(fd, BIOCSETF, &bpf_pgm);
403 
404 	buf = malloc(buflen);
405 	if (buf == NULL)
406 		err(EXIT_FAILURE, "Can't allocate buffer");
407 	*iobufp = buf;
408 	*iolenp = buflen;
409 	st->fd = fd;
410 	strlcpy(st->ifname, ifr.ifr_name, sizeof(st->ifname));
411 	memcpy(st->addr, dladdr, ETHER_ADDR_LEN);
412 }
413 
414 int
pickif(char * xname,uint8_t * dladdr)415 pickif(char *xname, uint8_t *dladdr)
416 {
417 #define	MATCH(x, v) ((v) == ((v) & (x)))
418 #ifndef CLLADDR
419 #define CLLADDR(s) ((const char *)((s)->sdl_data + (s)->sdl_nlen))
420 #endif
421 	int s, error;
422 	struct ifaddrs *ifaddrs, *ifa;
423 	const struct sockaddr_dl *sdl;
424 
425 	error = -1;
426 	s = socket(AF_INET, SOCK_DGRAM, 0);
427 	if (s == -1)
428 		return error;
429 	if (getifaddrs(&ifaddrs) == -1)
430 		goto out;
431 
432 	for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
433 		if (ifa->ifa_addr->sa_family == AF_LINK) {
434 			if (MATCH(ifa->ifa_flags, IFF_UP | IFF_BROADCAST)) {
435 				sdl = (const struct sockaddr_dl *)ifa->ifa_addr;
436 				if (xname[0] == '\0') {
437 					strlcpy(xname, ifa->ifa_name,
438 					    IFNAMSIZ);
439 					memcpy(dladdr, CLLADDR(sdl),
440 					    ETHER_ADDR_LEN);
441 					error = 0;
442 					break;
443 				} else if (strcmp(xname, ifa->ifa_name) == 0) {
444 					memcpy(dladdr, CLLADDR(sdl),
445 					    ETHER_ADDR_LEN);
446 					error = 0;
447 					break;
448 				}
449 			}
450 		}
451 	}
452 	freeifaddrs(ifaddrs);
453  out:
454 	close(s);
455 	return error;
456 #undef MATCH
457 }
458 
459 void
usage(void)460 usage(void)
461 {
462 
463 	fprintf(stderr,
464 	    "usage: %s [-d tracelevel] [-i interface] [-s directory]\n",
465 	    getprogname());
466 	exit(0);
467 }
468