root/usr.sbin/isibootd/isibootd.c
/*      $NetBSD: isibootd.c,v 1.5 2021/08/22 20:18:39 andvar Exp $      */
/*      Id: isiboot.c,v 1.2 1999/12/26 14:33:33 nisimura Exp    */

/*-
 * Copyright (c) 2000, 2011 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.
 *
 * 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.
 */

#include <sys/param.h>
#include <sys/types.h>
#include <sys/endian.h>
#include <sys/ioctl.h>
#include <sys/socket.h>

#include <net/bpf.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_ether.h>

#include <err.h>
#include <fcntl.h>
#include <ifaddrs.h>
#include <netdb.h>
#include <paths.h>
#include <poll.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <util.h>

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

/*
 * Integrated Solutions Inc. "ISIBOOT" boot enet protocol.
 *
 * Following data format depends on m68k order, and aligned harmful
 * to RISC processors.
 */
#define ISIBOOT_FRAMETYPE       0x80df
#define ISIBOOT_FRAMELEN        1468
struct frame {
        uint8_t dst[ETHER_ADDR_LEN];
        uint8_t src[ETHER_ADDR_LEN];
        uint16_t type;
        uint16_t pad_0;
        uint16_t seqno;
        uint8_t opcode;
        uint8_t pad_1;
        uint8_t pos[4];
        uint8_t siz[4];
        uint8_t data[ISIBOOT_FRAMELEN - 28];
} __packed;

struct station {
        int     fd;
        char    name[MAXHOSTNAMELEN];
        char    ifname[IFNAMSIZ];
        uint8_t addr[ETHER_ADDR_LEN];
} station;

struct session {
        struct session *next;
        int state;
        FILE *file;
        uint8_t addr[ETHER_ADDR_LEN];
} *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 __unused const char *state[] = { "WAITING", "OPENING", "TRANSFER" };

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

static void createbpfport(char *, uint8_t **, size_t *, struct station *);
static struct session *search(uint8_t *);
static void closedown(struct session *);
static void makepool(void);
static char *etheraddr(uint8_t *);
static int pickif(char *, uint8_t *);
static __dead void usage(void);

#define ISIBOOT_FRAME(buf)      ((buf) + ((struct bpf_hdr *)(buf))->bh_hdrlen)

#define PATH_DEFBOOTDIR "/tftpboot"

int
main(int argc, char *argv[])
{
        int cc, dbg, dflag;
        size_t iolen;
        uint32_t pos, siz;
        size_t nread;
        char *ifname, *p;
        const char *bootwd, *servername, *filename;
        uint8_t *iobuf;
        struct session *cp;
        struct frame *fp;
        struct pollfd pollfd;
        char clientname[MAXHOSTNAMELEN + 1];
        struct hostent *clientent;

        ifname = NULL;
        bootwd = PATH_DEFBOOTDIR;
        dbg = 0;
        dflag = 0;
        while ((cc = getopt(argc, argv, "i:s:d:")) != -1) {
                switch (cc) {
                case 'i':
                        ifname = optarg;
                        break;
                case 's':
                        bootwd = optarg;
                        break;
                case 'd':
                        dflag = 1;
                        dbg = atoi(optarg);
                        break;
                default:
                        usage();
                        /* NOTREACHED */
                }
        }
        argv += optind;
        argc -= optind;

        if (geteuid() != 0)
                warnx("WARNING: run by non root privilege");

        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);

        TRACE(1, ("Using interface: %s (%s)\n",
            station.ifname, etheraddr(station.addr)));

        if (!dflag) {
                if (daemon(0, 0))
                        err(EXIT_FAILURE, "can not start daemon");
#ifdef __NetBSD__
                pidfile(NULL);
#endif
        }

        if (chdir(bootwd) < 0)
                err(EXIT_FAILURE, "can not chdir to %s", bootwd);

        pollfd.fd = station.fd;
        pollfd.events = POLLIN;
        for (;;) {
                poll(&pollfd, 1, INFTIM);
                read(pollfd.fd, iobuf, iolen);  /* returns 1468 */
                fp = (struct frame *)ISIBOOT_FRAME(iobuf);

                /* ignore own TX packets */
                if (memcmp(fp->src, station.addr, ETHER_ADDR_LEN) == 0)
                        continue;

                /* check if the received Ethernet address is in ethers(5) */
                if (ether_ntohost(clientname, (struct ether_addr *)fp->src)) {
                        TRACE(3, ("'%s' is not in ethers(5)\n",
                            etheraddr(fp->src)));
                        continue;       
                }
                /* check if the client has a valid hostname */
                clientname[sizeof(clientname) - 1] = '\0';
                clientent = gethostbyname(clientname);
                if (clientent == NULL || clientent->h_addrtype != AF_INET) {
                        TRACE(3, ("'%s' is not a valid host\n", clientname));
                        continue;       
                }

                cp = search(fp->src);
                TRACE(2, ("[%s] ", etheraddr(fp->src)));
                switch (cp->state) {
                case WAITING:
                        if (fp->opcode != CONNECT) {
                                TRACE(2, ("not connected\n"));
                                continue;
                        }
                        /* check if specified servername is mine */
                        fp->data[sizeof(fp->data) - 1] = '\0';
                        servername = (char *)fp->data;
                        if (strcmp(servername, station.name) != 0) {
                                TRACE(3, ("'%s' not for me\n", servername));
                                continue;
                        }
                        cp->state = OPENING;
                        TRACE(2, ("new connection\n"));
                        break;
                case OPENING:
                        if (fp->opcode != OPEN)
                                goto aborting;  /* out of phase */

                        /* don't allow files outside the specified dir */
                        fp->data[sizeof(fp->data) - 1] = '\0';
                        filename = strrchr((char *)fp->data, '/');
                        if (filename != NULL)
                                filename++;
                        else
                                filename = (char *)fp->data;

                        cp->file = fopen(filename, "r");
                        if (cp->file == NULL) {
                                TRACE(1, ("failed to open '%s'\n", filename));
                                goto closedown; /* no such file */
                        }
                        cp->state = TRANSFER;
                        TRACE(2, ("open '%s'\n", filename));
                        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 */
                        siz = be32dec(fp->siz);
                        pos = be32dec(fp->pos);
                        nread = siz;
                        if (nread > sizeof(fp->data) ||
                            fseek(cp->file, pos, 0L) < 0 ||
                            fread(fp->data, 1, nread, cp->file) < nread) {
                                be32enc(fp->siz, 0); /* corrupted file */
                        }
                        TRACE(3, ("%u@%u\n", siz, pos));
                        break;
 aborting:
                        TRACE(1, ("out of phase\n"));
 closedown:
                        closedown(cp);
                        fp->opcode = CLOSE;
                        break;
                }
                memcpy(fp->dst, fp->src, ETHER_ADDR_LEN);
                memcpy(fp->src, station.addr, ETHER_ADDR_LEN);
                write(pollfd.fd, fp, ISIBOOT_FRAMELEN);
        }
        /* NOTREACHED */
}

struct session *
search(uint8_t *client)
{
        struct session *cp;

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

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

void
closedown(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 != NULL)
                fclose(cp->file);
        cp->file = NULL;
        memset(cp->addr, 0, ETHER_ADDR_LEN);
}

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

        freelist = calloc(NEWPOOL, sizeof(struct session));
        if (freelist == NULL)
                err(EXIT_FAILURE, "Can't allocate pool");
        cp = freelist;
        for (n = 0; n < NEWPOOL - 1; n++) {
                cp->next = cp + 1;
                cp++;
        }
}

char *
etheraddr(uint8_t *e)
{
        static char address[sizeof("xx:xx:xx:xx:xx:xx")];

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

static struct bpf_insn bpf_insn[] = {
        { BPF_LD|BPF_H|BPF_ABS,  0, 0, offsetof(struct frame, type) },
        { BPF_JMP|BPF_JEQ|BPF_K, 0, 1, ISIBOOT_FRAMETYPE },
        { BPF_RET|BPF_K,         0, 0, ISIBOOT_FRAMELEN },
        { BPF_RET|BPF_K,         0, 0, 0x0 }
};
static struct bpf_program bpf_pgm = {
        sizeof(bpf_insn) / sizeof(bpf_insn[0]),
        bpf_insn
};

void
createbpfport(char *ifname, uint8_t **iobufp, size_t *iolenp,
    struct station *st)
{
        struct ifreq ifr;
        int fd;
        u_int type;
        u_int buflen;
        uint8_t dladdr[ETHER_ADDR_LEN], *buf;
#ifdef BIOCIMMEDIATE
        u_int flag;
#endif
#ifndef _PATH_BPF
        char devbpf[PATH_MAX];
        int n;
#endif

#ifdef _PATH_BPF
        fd = open(_PATH_BPF, O_RDWR, 0);
#else
        n = 0;
        do {
                snprintf(devbpf, sizeof(devbpf), "/dev/bpf%d", n++);
                fd = open(devbpf, O_RDWR, 0);
        } while (fd == -1 && errno == EBUSY);
#endif
        if (fd == -1)
                err(EXIT_FAILURE, "No bpf device available");
        memset(&ifr, 0, sizeof(ifr));
        if (ifname != NULL)
                strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
        if (pickif(ifr.ifr_name, dladdr) < 0)
                errx(EXIT_FAILURE,
                    "No network interface available: %s\n", ifr.ifr_name);

        ioctl(fd, BIOCSETIF, &ifr);
        ioctl(fd, BIOCGDLT, &type);     /* XXX - should check whether EN10MB */
#ifdef BIOCIMMEDIATE
        flag = 1;
        ioctl(fd, BIOCIMMEDIATE, &flag);
#endif
        ioctl(fd, BIOCGBLEN, &buflen);
        ioctl(fd, BIOCSETF, &bpf_pgm);

        buf = malloc(buflen);
        if (buf == NULL)
                err(EXIT_FAILURE, "Can't allocate buffer");
        *iobufp = buf;
        *iolenp = buflen;
        st->fd = fd;
        strlcpy(st->ifname, ifr.ifr_name, sizeof(st->ifname));
        memcpy(st->addr, dladdr, ETHER_ADDR_LEN);
}

int
pickif(char *xname, uint8_t *dladdr)
{
#define MATCH(x, v) ((v) == ((v) & (x)))
#ifndef CLLADDR
#define CLLADDR(s) ((const char *)((s)->sdl_data + (s)->sdl_nlen))
#endif
        int s, error;
        struct ifaddrs *ifaddrs, *ifa;
        const struct sockaddr_dl *sdl;

        error = -1;
        s = socket(AF_INET, SOCK_DGRAM, 0);
        if (s == -1)
                return error;
        if (getifaddrs(&ifaddrs) == -1)
                goto out;

        for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
                if (ifa->ifa_addr->sa_family == AF_LINK) {
                        if (MATCH(ifa->ifa_flags, IFF_UP | IFF_BROADCAST)) {
                                sdl = (const struct sockaddr_dl *)ifa->ifa_addr;
                                if (xname[0] == '\0') {
                                        strlcpy(xname, ifa->ifa_name,
                                            IFNAMSIZ);
                                        memcpy(dladdr, CLLADDR(sdl),
                                            ETHER_ADDR_LEN);
                                        error = 0;
                                        break;
                                } else if (strcmp(xname, ifa->ifa_name) == 0) {
                                        memcpy(dladdr, CLLADDR(sdl),
                                            ETHER_ADDR_LEN);
                                        error = 0;
                                        break;
                                }
                        }
                }
        }
        freeifaddrs(ifaddrs);
 out:
        close(s);
        return error;
#undef MATCH
}

void
usage(void)
{

        fprintf(stderr,
            "usage: %s [-d tracelevel] [-i interface] [-s directory]\n",
            getprogname());
        exit(0);
}