Home | History | Annotate | Line # | Download | only in isibootd
      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
    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 *
    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
    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
    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 *
    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
    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
    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
    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