/*	$Id: isiboot.c,v 1.2 1999/12/26 14:33:33 nisimura Exp $	*/

/*-
 * Copyright (c) 2000 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Tohru Nishimura.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *        This product includes software developed by the NetBSD
 *        Foundation, Inc. and its contributors.
 * 4. Neither the name of The NetBSD Foundation nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#define	TRACE(l, x) if ((l) <= dbg) printf x

#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/bpf.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*
 * Following data format depends on m68k order, and aligned harmful
 * to RISC processors.
 */
struct frame {
	u_int8_t dst[6];
	u_int8_t src[6];
	u_int16_t type;
	u_int16_t fill_0;
	u_int16_t seqno;
	u_int8_t opcode;
	u_int8_t fill_1;
	u_int8_t pos[4];
	u_int8_t siz[4];
	unsigned char data[1];
};
#define	FRAMETYPE	0x80df
#define	FRAMELEN	1468

#define CPY32(x,y) {						\
	u_int8_t *p = (y);					\
	(x) = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];	\
    } while (0)

struct station {
	int 	fd;
	char	name[33];
	char	ifname[IFNAMSIZ];
	u_int8_t addr[6];
} station;

struct session {
	struct session *next;
	int state;
	FILE *file;
	u_int8_t addr[6];
} *activelist, *freelist;
#define	NEWPOOL 10

#define	WAITING 0	/* implicit state after receiving the first frame */
#define	OPENING 1	/* waiting for OPEN after CONNECT is received */
#define	TRANSFER 2	/* data transferring state after OPEN is well done */
static char *state[] = { "WAITING", "OPENING", "TRANSFER" };

#define	CONNECT 0
#define	OPEN 1
#define	READ 2
#define	CLOSE 4
static char *op[] = { "CONNECT", "OPEN", "READ", "WRITE", "CLOSE", "FIND" };

void createbpfport(char *, char **, int *, struct station *);
struct session *search(u_int8_t *);
void closedown(struct session *);
void makepool(void);
char *etheraddr(u_int8_t *);
int pickif(char *, u_int8_t *);

#define	NEQ(x,y) strcmp(x,y)
#define	FRAME(X) ((X)+((struct bpf_hdr *)X)->bh_hdrlen)

char usage[] = "usage: %s [-i interface] [-s directory] [-d tracelevel]\n";
char devbpf[] = "/dev/bpf?";

main(argc, argv)
	int argc;
	char *argv[];
{
	int cc, nread, iolen, pos, siz, dbg;
	char *ifname, *bootwd, *p, *iobuf;
	struct session *cp;
	struct frame *fp;
	struct pollfd pollfd;
	static u_int8_t om[6] = { 0, 0, 0xA, 0, 0, 0 }; /* XXX */

	if (geteuid() != 0)
		fprintf(stderr, "WARNING: run by non root priviledge\n");

	ifname = bootwd = NULL;
	dbg = 0;
	while ((cc = getopt(argc, argv, "i:s:d:")) != -1) {
		switch (cc) {
		case 'i':
			ifname = optarg;
			break;
		case 's':
			bootwd = optarg;
			break;
		case 'd':
			dbg = atoi(optarg);
			break;
		default:
			fprintf(stderr, usage, argv[0]);
			exit(1);
		}
	}
	argv += optind;
	argc -= optind;

	memset(station.name, 0, sizeof(station.name));
	gethostname(station.name, sizeof(station.name) - 1);
	if ((p = strchr(station.name, '.')) != NULL)
		*p = '\0';

	createbpfport(ifname, &iobuf, &iolen, &station);

	if (bootwd != NULL && chroot(bootwd) < 0) {
		perror(argv[0]);
		exit(1);
	}

	pollfd.fd = station.fd;
	pollfd.events = POLLIN;
	for (;;) {
		poll(&pollfd, 1, INFTIM);
		read(pollfd.fd, iobuf, iolen);	/* returns 1468 */
		fp = (struct frame *)FRAME(iobuf);
		if (memcmp(fp->dst, om, 3) == 0)
			continue;	
		cp = search(fp->src);
		TRACE(2, ("[%s] ", etheraddr(fp->src)));
		switch (cp->state) {
		case WAITING:
			if (fp->opcode != CONNECT)
				continue;
			else if (NEQ(fp->data, station.name)) {
				TRACE(3, ("'%s' not for me\n", fp->data));
				continue;
			}
			cp->state = OPENING;
			TRACE(2, ("new connection\n"));
			break;
		case OPENING:
			if (fp->opcode != OPEN)
				goto aborting;	/* out of phase */
			cp->file = fopen(fp->data, "r");
			if (cp->file == NULL) {
				TRACE(1, ("filed to open '%s'\n", fp->data));
				goto closedown;	/* no such file */
			}
			cp->state = TRANSFER;
			TRACE(2, ("opened '%s'\n", fp->data));
			break;
		case TRANSFER:
			if (fp->opcode == CLOSE) {
				TRACE(2, ("connection closed\n"));
				goto closedown;	/* close request */
			}
			if (fp->opcode != READ)
				goto aborting;	/* out of phase */
			CPY32(siz, fp->siz);
			CPY32(pos, fp->pos);
			nread = siz;
			if (fseek(cp->file, pos, 0L) < 0
			     || fread(fp->data, 1, nread, cp->file) < nread) {
				memset(fp->siz, 0, 4); /* corrupted file */
			}
			TRACE(3, ("%d@%d\n", siz, pos));
			break;
		aborting:
			TRACE(1, ("out of phase\n"));
		closedown:
			closedown(cp);
			fp->opcode = CLOSE;
			break;
		}
		memcpy(fp->dst, fp->src, 6);
		memcpy(fp->src, station.addr, 6);
		write(pollfd.fd, fp, FRAMELEN);
	}
	/* NOTREACHED */
}

struct session *
search(client)
	u_int8_t client[];
{
	struct session *cp;

	for (cp = activelist; cp; cp = cp->next) {
		if (memcmp(client, cp->addr, 6) == 0)
			return cp;
	}
	if (freelist == NULL)
		makepool();
	cp = freelist;
	freelist = cp->next;	
	cp->next = activelist;
	activelist = cp;

	cp->state = WAITING;
	cp->file = NULL;
	bcopy(client, cp->addr, 6);
	return cp;
}

void
closedown(cp)
	struct session *cp;
{
	struct session *cpp;

	cpp = activelist;
	if (cpp == cp)
		activelist = cp->next;
	else {
		do {
			if (cpp->next == cp)
				break;
		} while (NULL != (cpp = cpp->next)); /* should never happen */
		cpp->next = cp->next;
	}
	cp->next = freelist;
	freelist = cp;

	if (cp->file)
		fclose(cp->file);
	cp->file = NULL;
	bzero(cp->addr, 6);
}

void
makepool()
{
	struct session *cp;
	int n;

	freelist = cp = malloc(sizeof(struct session) * NEWPOOL);
	for (n = 0; n < NEWPOOL; n++) {
		cp->next = cp + 1;
		memset(cp->addr, 0, 6);
		cp++;
	}
	cp[-1].next = NULL;
}

char *
etheraddr(e)
	u_int8_t e[];
{
	static char address[18];

	sprintf(address,
		"%x:%x:%x:%x:%x:%x", e[0], e[1], e[2], e[3], e[4], e[5]);
	return address;
}

static struct bpf_insn bpf_insn[] = {
#define FRAMEHEADER ((struct frame *)0)
    { BPF_LD|BPF_H|BPF_ABS,  0, 0, (long)&FRAMEHEADER->type },
    { BPF_JMP|BPF_JEQ|BPF_K, 0, 1, FRAMETYPE },
    { BPF_RET|BPF_K,         0, 0, FRAMELEN },
    { BPF_RET|BPF_K,         0, 0, 0x0 }
#undef FRAMEHEADER
};
static struct bpf_program bpf_pgm = {
    sizeof(bpf_insn)/sizeof(bpf_insn[0]), bpf_insn
};

void
createbpfport(ifname, iobufp, iolenp, st)
	char *ifname;
	char **iobufp;
	int *iolenp;
	struct station *st;
{
	int n, fd;
	char xname[IFNAMSIZ];
	u_int8_t dladdr[6];

	n = 0;
	do {
		devbpf[8] = n + '0';
		fd = open(devbpf, O_RDWR, 0);
		if (fd > 0)
			goto found;
	} while (errno != ENOENT && ++n < 10);
	fprintf(stderr, "No bpf device available\n");
	exit(1);
found:
	memset(xname, 0, sizeof(memset));
	if (ifname != NULL)
		strcpy(xname, ifname);
	if (pickif(xname, dladdr) < 0) {
		fprintf(stderr, "No network interface available\n");
		exit(1);
	}
	ioctl(fd, BIOCSETIF, xname);
	ioctl(fd, BIOCGDLT, &n);	/* XXX - should check whether EN10MB */
	n = 1; ioctl(fd, BIOCIMMEDIATE, &n);
	ioctl(fd, BIOCGBLEN, &n);
	ioctl(fd, BIOCSETF, &bpf_pgm);

	*iobufp = (char *)malloc(n);
	*iolenp = n;
	st->fd = fd;
	memcpy(st->ifname, xname, IFNAMSIZ);
	memcpy(st->addr, dladdr, 6);
}

int
pickif(xname, dladdr)
	char *xname;
	u_int8_t dladdr[6];
{
#define	MATCH(x,v) ((v)==((v)&(x)))
	int s, len, excess, error;
	struct ifconf ifconfs;
	struct ifreq *ifr, reqbuf[32];

	s = socket(AF_INET, SOCK_DGRAM, 0);
	ifconfs.ifc_len = sizeof(reqbuf);
	ifconfs.ifc_buf = (caddr_t)reqbuf;
	ioctl(s, SIOCGIFCONF, &ifconfs);
	ifr = ifconfs.ifc_req;
	len = ifconfs.ifc_len;
	error = -1;
	while (len > 0) {
		excess = ifr->ifr_addr.sa_len - sizeof(struct sockaddr); 
		if (ifr->ifr_addr.sa_family == AF_LINK) {
			struct sockaddr_dl *sdl = (void *)&ifr->ifr_addr;

			memcpy(dladdr, (caddr_t)LLADDR(sdl), sdl->sdl_alen);
			ioctl(s, SIOCGIFFLAGS, ifr);
			if (MATCH(ifr->ifr_flags, IFF_UP|IFF_BROADCAST)) {
				if (xname[0] == '\0') {
					strcpy(xname, ifr->ifr_name);
					error = 0;
					break;
				}
				else if (strcmp(xname, ifr->ifr_name) == 0) {
					error = 0;
					break;
				}
			}
		}
		ifr += 1;
		len -= sizeof(struct ifreq);
		if (excess > 0) {
			ifr = (struct ifreq *)((char *)ifr + excess);
			len -= excess;
		}
	}
	close(s);
	return error;
#undef MATCH
}
