pxechn.c32: PXE NBP chainloader
authorGene Cumm <gene.cumm@gmail.com>
Sun, 27 May 2012 13:23:07 +0000 (09:23 -0400)
committerGene Cumm <gene.cumm@gmail.com>
Sun, 27 May 2012 13:23:07 +0000 (09:23 -0400)
Designed as a more versatile COM32-based alternative to pxechain.com.
It can use the PXE RESTART or chain to the new NBP without the PXE
stack.  It also enables a user to boot Microsoft Windows Server 2008R2
Windows Deployment Services's wdsnbp.com from PXELINUX.

Signed-off-by: Gene Cumm <gene.cumm@gmail.com>
com32/modules/Makefile
com32/modules/pxechn.c [new file with mode: 0644]
doc/pxechn.txt [new file with mode: 0644]

index 1b2854f..d8861c4 100644 (file)
@@ -23,7 +23,8 @@ MODULES         = config.c32 ethersel.c32 dmitest.c32 cpuidtest.c32 \
            disk.c32 pcitest.c32 elf.c32 linux.c32 reboot.c32 pmload.c32 \
            meminfo.c32 sdi.c32 sanboot.c32 ifcpu64.c32 vesainfo.c32 \
            kbdmap.c32 cmd.c32 vpdtest.c32 host.c32 ls.c32 gpxecmd.c32 \
-           ifcpu.c32 cpuid.c32 cat.c32 pwd.c32 ifplop.c32 zzjson.c32 whichsys.c32
+           ifcpu.c32 cpuid.c32 cat.c32 pwd.c32 ifplop.c32 zzjson.c32 \
+           whichsys.c32 pxechn.c32
 
 TESTFILES =
 
diff --git a/com32/modules/pxechn.c b/com32/modules/pxechn.c
new file mode 100644 (file)
index 0000000..3f9ebd3
--- /dev/null
@@ -0,0 +1,1122 @@
+/* ----------------------------------------------------------------------- *
+ *
+ *   Copyright 2010-2012 Gene Cumm - All Rights Reserved
+ *
+ *   Portions from chain.c:
+ *   Copyright 2003-2009 H. Peter Anvin - All Rights Reserved
+ *   Copyright 2009-2010 Intel Corporation; author: H. Peter Anvin
+ *   Significant portions copyright (C) 2010 Shao Miller
+ *                                     [partition iteration, GPT, "fs"]
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, Inc., 53 Temple Place Ste 330,
+ *   Boston MA 02111-1307, USA; either version 2 of the License, or
+ *   (at your option) any later version; incorporated herein by reference.
+ *
+ * ----------------------------------------------------------------------- */
+
+/*
+ * pxechn.c
+ *
+ * PXE Chain Loader; Chain load to another PXE network boot program
+ * that may be on another host.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <consoles.h>
+#include <console.h>
+#include <errno.h>
+#include <string.h>
+#include <syslinux/config.h>
+#include <syslinux/loadfile.h>
+#include <syslinux/bootrm.h>
+#include <syslinux/video.h>
+#include <com32.h>
+#include <stdint.h>
+#include <syslinux/pxe.h>
+#include <sys/gpxe.h>
+#include <unistd.h>
+#include <getkey.h>
+#include <dhcp.h>
+#include <limits.h>
+
+
+#define PXECHN_DEBUG 1
+
+typedef union {
+    uint64_t q;
+    uint32_t l[2];
+    uint16_t w[4];
+    uint8_t b[8];
+} reg64_t;
+
+#define dprintf0(f, ...)               ((void)0)
+
+#if (PXECHN_DEBUG > 0)
+#  define dpressanykey                 pressanykey
+#  define dprintf                      printf
+#  define dprint_pxe_bootp_t           print_pxe_bootp_t
+#  define dprint_pxe_vendor_blk                print_pxe_vendor_blk
+#  define dprint_pxe_vendor_raw                print_pxe_vendor_raw
+#else
+#  define dpressanykey(tm)             ((void)0)
+#  define dprintf(f, ...)              ((void)0)
+#  define dprint_pxe_bootp_t(p, l)     ((void)0)
+#  define dprint_pxe_vendor_blk(p, l)  ((void)0)
+#  define dprint_pxe_vendor_raw(p, l)  ((void)0)
+#endif
+
+#define dprintf_opt_cp         dprintf0
+#define dprintf_opt_inj                dprintf0
+#define dprintf_pc_pa          dprintf
+#define dprintf_pc_so_s                dprintf0
+
+#define t_PXENV_RESTART_TFTP   t_PXENV_TFTP_READ_FILE
+
+#define STACK_SPLIT    11
+
+/* same as pxelinux.asm REBOOT_TIME */
+#define REBOOT_TIME    300
+
+#define NUM_DHCP_OPTS          256
+#define DHCP_OPT_LEN_MAX       256
+#define PXE_VENDOR_RAW_PRN_MAX 0x7F
+#define PXECHN_HOST_LEN                256     /* 63 bytes per label; 255 max total */
+
+#define PXECHN_NUM_PKT_TYPE    3
+#define PXECHN_NUM_PKT_AVAIL   2*PXECHN_NUM_PKT_TYPE
+#define PXECHN_PKT_TYPE_START  PXENV_PACKET_TYPE_DHCP_DISCOVER
+
+#define PXECHN_FORCE_PKT1      0x80000000
+#define PXECHN_FORCE_PKT2      0x40000000
+#define PXECHN_FORCE_ALL       (PXECHN_FORCE_PKT1 | PXECHN_FORCE_PKT2)
+#define PXECHN_FORCE_ALL_1     0
+#define STRASINT_str           ('s' + (('t' + ('r' << 8)) << 8))
+
+#define min(a,b) (((a) < (b)) ? (a) : (b))
+
+const char app_name_str[] = "pxechn.c32";
+
+struct pxelinux_opt {
+    char *fn;  /* Filename as passed to us */
+    in_addr_t fip;     /* fn's IP component */
+    char *fp;  /* fn's path component */
+    in_addr_t gip;     /* giaddr; Gateway/DHCP relay */
+    uint32_t force;
+    uint32_t wait;     /* Additional decision to wait before boot */
+    int32_t wds;       /* WDS option/level */
+    struct dhcp_option p[PXECHN_NUM_PKT_AVAIL];
+       /* original _DHCP_DISCOVER, _DHCP_ACK, _CACHED_REPLY then modified packets */
+    char host[PXECHN_HOST_LEN];
+    struct dhcp_option opts[PXECHN_NUM_PKT_TYPE][NUM_DHCP_OPTS];
+    char p_unpacked[PXECHN_NUM_PKT_TYPE];
+};
+
+
+/* from chain.c */
+struct data_area {
+    void *data;
+    addr_t base;
+    addr_t size;
+};
+
+/* From chain.c */
+static inline void error(const char *msg)
+{
+    fputs(msg, stderr);
+}
+
+/* From chain.c */
+static void do_boot(struct data_area *data, int ndata,
+                   struct syslinux_rm_regs *regs)
+{
+    uint16_t *const bios_fbm = (uint16_t *) 0x413;
+    addr_t dosmem = *bios_fbm << 10;   /* Technically a low bound */
+    struct syslinux_memmap *mmap;
+    struct syslinux_movelist *mlist = NULL;
+    addr_t endimage;
+    int i;
+
+    mmap = syslinux_memory_map();
+
+    if (!mmap) {
+       error("Cannot read system memory map\n");
+       return;
+    }
+
+    endimage = 0;
+    for (i = 0; i < ndata; i++) {
+       if (data[i].base + data[i].size > endimage)
+           endimage = data[i].base + data[i].size;
+    }
+    if (endimage > dosmem)
+       goto too_big;
+
+    for (i = 0; i < ndata; i++) {
+       if (syslinux_add_movelist(&mlist, data[i].base,
+                                 (addr_t) data[i].data, data[i].size))
+           goto enomem;
+    }
+
+
+    /* Tell the shuffler not to muck with this area... */
+    syslinux_add_memmap(&mmap, endimage, 0xa0000 - endimage, SMT_RESERVED);
+
+    /* Force text mode */
+    syslinux_force_text_mode();
+
+    fputs("Booting...\n", stdout);
+    syslinux_shuffle_boot_rm(mlist, mmap, 3, regs);
+    error("Chainboot failed!\n");
+    return;
+
+too_big:
+    error("Loader file too large\n");
+    return;
+
+enomem:
+    error("Out of memory\n");
+    return;
+}
+
+void usage(void)
+{
+    printf("USAGE:\n"
+        "    %s [OPTIONS]... _new-nbp_\n"
+       "    %s -r _new-nbp_    (calls PXE stack PXENV_RESTART_TFTP)\n"
+       "OPTIONS:\n"
+       "    [-c config] [-g gateway] [-p prefix] [-t reboot] [-u] [-w] [-W]"
+       " [-o opt.ty=val]\n\n",
+       app_name_str, app_name_str);
+}
+
+void pxe_error(int ierr, const char *evt, const char *msg)
+{
+    if (msg)
+       printf("%s", msg);
+    else if (evt)
+       printf("Error while %s: ", evt);
+    printf("%d:%s\n", ierr, strerror(ierr));
+}
+
+int pressanykey(clock_t tm) {
+    int inc;
+
+    printf("Press any key to continue. ");
+    inc = get_key(stdin, tm);
+    puts("");
+    return inc;
+}
+
+int dhcp_find_opt(pxe_bootp_t *p, size_t len, uint8_t opt)
+{
+    int rv = -1;
+    int i, vlen, oplen;
+    uint8_t *d;
+    uint32_t magic;
+
+    if (!p) {
+       dprintf("  packet pointer is null\n");
+       return rv;
+    }
+    vlen = len - ((void *)&(p->vendor) - (void *)p);
+    d = p->vendor.d;
+    magic = ntohl(*((uint32_t *)d));
+    if (magic != VM_RFC1048)   /* Invalid DHCP packet */
+       vlen = 0;
+    for (i = 4; i < vlen; i++) {
+       if (d[i] == opt) {
+           dprintf("\n    @%03X-%2d\n", i, d[i]);
+           rv = i;
+           break;
+       }
+       if (d[i] == ((NUM_DHCP_OPTS) - 1))      /* End of list */
+           break;
+       if (d[i]) {             /* Skip padding */
+           oplen = d[++i];
+           i += oplen;
+       }
+    }
+    return rv;
+}
+
+void print_pxe_vendor_raw(pxe_bootp_t *p, size_t len)
+{
+    int i, vlen;
+
+    if (!p) {
+       printf("  packet pointer is null\n");
+       return;
+    }
+    vlen = len - ((void *)&(p->vendor) - (void *)p);
+    if (vlen > PXE_VENDOR_RAW_PRN_MAX)
+       vlen = PXE_VENDOR_RAW_PRN_MAX;
+    dprintf("  rawLen = %d", vlen);
+    for (i = 0; i < vlen; i++) {
+       if ((i & 0xf) == 0)
+           printf("\n  %04X:", i);
+       printf(" %02X", p->vendor.d[i]);
+    }
+    printf("\n");
+}
+
+void print_pxe_vendor_blk(pxe_bootp_t *p, size_t len)
+{
+    int i, vlen, oplen, j;
+    uint8_t *d;
+    uint32_t magic;
+    if (!p) {
+       printf("  packet pointer is null\n");
+       return;
+    }
+    vlen = len - ((void *)&(p->vendor) - (void *)p);
+    printf("  Vendor Data:    Len=%d", vlen);
+    d = p->vendor.d;
+    magic = ntohl(*((uint32_t *)d));
+    printf("    Magic: %08X", ntohl(*((uint32_t *)d)));
+    if (magic != VM_RFC1048)   /* Invalid DHCP packet */
+       vlen = 0;
+    for (i = 4; i < vlen; i++) {
+       if (d[i])       /* Skip the padding */
+           printf("\n    @%03X-%3d", i, d[i]);
+       if (d[i] == ((NUM_DHCP_OPTS) - 1))      /* End of list */
+           break;
+       if (d[i]) {
+           oplen = d[++i];
+           printf(" l=%3d:", oplen);
+           for (j = (++i + oplen); i < vlen && i < j; i++) {
+               printf(" %02X", d[i]);
+           }
+           i--;
+       }
+    }
+    printf("\n");
+}
+
+void print_pxe_bootp_t(pxe_bootp_t *p, size_t len)
+{
+    if (!p || len <= 0) {
+       printf("  packet pointer is null\n");
+       return;
+    }
+    printf("  op:%02X  hw:%02X  hl:%02X  gh:%02X  id:%08X se:%04X f:%04X"
+       "  cip:%08X\n", p->opcode, p->Hardware, p->Hardlen, p->Gatehops,
+       ntohl(p->ident), ntohs(p->seconds), ntohs(p->Flags), ntohl(p->cip));
+    printf("  yip:%08X  sip:%08X  gip:%08X",
+       ntohl(p->yip), ntohl(p->sip), ntohl(p->gip));
+    printf("  caddr-%02X:%02X:%02X:%02X:%02X:%02X\n", p->CAddr[0],
+       p->CAddr[1], p->CAddr[2], p->CAddr[3], p->CAddr[4], p->CAddr[5]);
+    printf("  sName: '%s'\n", p->Sname);
+    printf("  bootfile: '%s'\n", p->bootfile);
+    dprint_pxe_vendor_blk(p, len);
+}
+
+void pxe_set_regs(struct syslinux_rm_regs *regs)
+{
+    com32sys_t tregs;
+
+    regs->ip = 0x7C00;
+    /* Plan A uses SS:[SP + 4] */
+    /* sdi->pxe.stack is a usable pointer, not something that can be nicely
+       and reliably split to SS:SP without causing issues */
+    tregs.eax.l = 0x000A;
+    __intcall(0x22, &tregs, &tregs);
+    regs->ss = tregs.fs;
+    regs->esp.l = tregs.esi.w[0] + sizeof(tregs);
+    /* Plan B uses [ES:BX] */
+    regs->es = tregs.es;
+    regs->ebx = tregs.ebx;
+    dprintf("\nsp:%04x    ss:%04x    es:%04x    bx:%04x\n", regs->esp.w[0],
+       regs->ss, regs->es, regs->ebx.w[0]);
+    /* Zero out everything else just to be sure */
+    regs->cs = regs->ds = regs->fs = regs->gs = 0;
+    regs->eax.l = regs->ecx.l = regs->edx.l = 0;
+}
+
+int hostlen_limit(int len)
+{
+    return min(len, ((PXECHN_HOST_LEN) - 1));
+}
+
+//FIXME: To a library
+/* Parse a filename into an IPv4 address and filename pointer
+ *     returns Based on the interpretation of fn
+ *             0 regular file name
+ *             1 in format IP::FN
+ *             2 TFTP URL
+ *             3 HTTP URL
+ *             4 FTP URL
+ *             3 + 2^30 HTTPS URL
+ *             -1 if fn is another URL type
+ */
+int pxechn_parse_fn(char fn[], in_addr_t *fip, char *host, char *fp[])
+{
+    in_addr_t tip = 0;
+    char *csep, *ssep, *hsep;  /* Colon, Slash separator positions */
+    int hlen, plen;    /* Hostname, protocol length */
+    int rv = 0;
+
+    csep = strchr(fn, ':');
+    if (csep) {
+       if (csep[1] == ':') {   /* assume IP::FN */
+           *fp = &csep[2];
+           rv = 1;
+           if (fn[0] != ':') {
+               hlen = hostlen_limit(csep - fn);
+               memcpy(host, fn, hlen);
+               host[hlen] = 0;
+           }
+       } else if ((csep[1] == '/') && (csep[2] == '/')) {
+               /* URL: proto://host:port/path/file */
+               /* proto://[user[:passwd]@]host[:port]/path/file */
+           ssep = strchr(csep + 3, '/');
+           if (ssep) {
+               hlen = hostlen_limit(ssep - (csep + 3));
+               *fp = ssep + 1;
+           } else {
+               hlen = hostlen_limit(strlen(csep + 3));
+           }
+           memcpy(host, (csep + 3), hlen);
+           host[hlen] = 0;
+           plen = csep - fn;
+           if (strncmp(fn, "tftp", plen) == 0)
+               rv = 2;
+           else if (strncmp(fn, "http", plen) == 0)
+               rv = 3;
+           else if (strncmp(fn, "ftp", plen) == 0)
+               rv = 4;
+           else if (strncmp(fn, "https", plen) == 0)
+               rv = 3 + ( 1 << 30 );
+           else
+               rv = -1;
+       } else {
+           csep = NULL;
+       }
+    }
+    if (!csep) {
+       *fp = fn;
+    }
+    if (host[0]) {
+       hsep = strchr(host, '@');
+       if (!hsep)
+           hsep = host;
+       tip = pxe_dns(hsep);
+    }
+    if (tip != 0)
+       *fip = tip;
+    dprintf0("  host '%s'\n  fp   '%s'\n  fip  %08x\n", host, *fp, ntohl(*fip));
+    return rv;
+}
+
+void pxechn_opt_free(struct dhcp_option *opt)
+{
+    free(opt->data);
+    opt->len = -1;
+}
+
+void pxechn_fill_pkt(struct pxelinux_opt *pxe, int ptype)
+{
+    int rv = -1;
+    int p1, p2;
+    if ((ptype < 0) || (ptype > PXECHN_NUM_PKT_TYPE))
+       rv = -2;
+    p1 = ptype - PXECHN_PKT_TYPE_START;
+    p2 = p1 + PXECHN_NUM_PKT_TYPE;
+    if ((rv >= -1) && (!pxe_get_cached_info(ptype,
+           (void **)&(pxe->p[p1].data), (size_t *)&(pxe->p[p1].len)))) {
+       pxe->p[p2].data = malloc(2048);
+       if (pxe->p[p2].data) {
+           memcpy(pxe->p[p2].data, pxe->p[p1].data, pxe->p[p1].len);
+           pxe->p[p2].len = pxe->p[p1].len;
+           rv = 0;
+           dprint_pxe_bootp_t((pxe_bootp_t *)(pxe->p[p1].data), pxe->p[p1].len);
+           dpressanykey(INT_MAX);
+       } else {
+           printf("%s: ERROR: Unable to malloc() for second packet\n", app_name_str);
+       }
+    } else {
+       printf("%s: ERROR: Unable to retrieve first packet\n", app_name_str);
+    }
+    if (rv <= -1) {
+       pxechn_opt_free(&pxe->p[p1]);
+    }
+}
+
+void pxechn_init(struct pxelinux_opt *pxe)
+{
+    /* Init for paranoia */
+    pxe->fn = NULL;
+    pxe->fp = NULL;
+    pxe->force = 0;
+    pxe->wait = 0;
+    pxe->gip = 0;
+    pxe->wds = 0;
+    pxe->host[0] = 0;
+    pxe->host[((NUM_DHCP_OPTS) - 1)] = 0;
+    for (int j = 0; j < PXECHN_NUM_PKT_TYPE; j++){
+       for (int i = 0; i < NUM_DHCP_OPTS; i++) {
+           pxe->opts[j][i].data = NULL;
+           pxe->opts[j][i].len = -1;
+       }
+       pxe->p_unpacked[j] = 0;
+       pxe->p[j].data = NULL;
+       pxe->p[j+PXECHN_NUM_PKT_TYPE].data = NULL;
+       pxe->p[j].len = 0;
+       pxe->p[j+PXECHN_NUM_PKT_TYPE].len = 0;
+    }
+    pxechn_fill_pkt(pxe, PXENV_PACKET_TYPE_CACHED_REPLY);
+}
+
+int pxechn_to_hex(char i)
+{
+    if (i >= '0' && i <= '9')
+       return (i - '0');
+    if (i >= 'A' && i <= 'F')
+       return (i - 'A' + 10);
+    if (i >= 'a' && i <= 'f')
+       return (i - 'a' + 10);
+    if (i == 0)
+       return -1;
+    return -2;
+}
+
+int pxechn_parse_2bhex(char ins[])
+{
+    int ret = -2;
+    int n0 = -3, n1 = -3;
+    /* NULL pointer */
+    if (!ins) {
+       ret = -1;
+    /* pxechn_to_hex can handle the NULL character by returning -1 and
+       breaking the execution of the statement chain */
+    } else if (((n0 = pxechn_to_hex(ins[0])) >= 0)
+           && ((n1 = pxechn_to_hex(ins[1])) >= 0)) {
+       ret = (n0 * 16) + n1;
+    } else if (n0 == -1) {     /* Leading NULL char */
+       ret = -1;
+    }
+    return ret;
+}
+
+int pxechn_optnum_ok(int optnum)
+{
+    if ((optnum > 0) && (optnum < ((NUM_DHCP_OPTS) - 1)))
+       return 1;
+    return 0;
+}
+
+int pxechn_optnum_ok_notres(int optnum)
+{
+    if ((optnum <= 0) && (optnum >= ((NUM_DHCP_OPTS) - 1)))
+       return 0;
+    switch(optnum){
+    case 66: case 67:
+       return 0;
+       break;
+    default:   return 1;
+    }
+}
+
+int pxechn_optlen_ok(int optlen)
+{
+    if ((optlen >= 0) && (optlen < ((DHCP_OPT_LEN_MAX) - 1)))
+       return 1;
+    return 0;
+}
+
+int pxechn_setopt(struct dhcp_option *opt, void *data, int len)
+{
+    void *p;
+    if (!opt || !data)
+       return -1;
+    if (len < 0) {
+       return -3;
+    }
+    p = realloc(opt->data, len);
+    if (!p && len) {   /* Allow for len=0 */
+       pxechn_opt_free(opt);
+       return -2;
+    }
+    opt->data = p;
+    memcpy(opt->data, data, len);
+    opt->len = len;
+    return len;
+}
+
+int pxechn_setopt_str(struct dhcp_option *opt, void *data)
+{
+    return pxechn_setopt(opt, data, strnlen(data, DHCP_OPT_LEN_MAX));
+}
+
+int pxechn_parse_int(char *data, char istr[], int tlen)
+{
+    int terr = errno;
+
+    if ((tlen == 1) || (tlen == 2) || (tlen == 4)) {
+       errno = 0;
+       uint32_t optval = strtoul(istr, NULL, 0);
+       if (errno)
+           return -3;
+       errno = terr;
+       switch(tlen){
+       case  1:
+           if (optval & 0xFFFFFF00)
+               return -4;
+           break;
+       case  2:
+           if (optval & 0xFFFF0000)
+               return -4;
+           optval = htons(optval);
+           break;
+       case  4:
+           optval = htonl(optval);
+           break;
+       }
+       memcpy(data, &optval, tlen);
+    } else if (tlen == 8) {
+       errno = 0;
+       uint64_t optval = strtoull(istr, NULL, 0);
+       if (errno)
+           return -3;
+       errno = terr;
+       optval = htonq(optval);
+       memcpy(data, &optval, tlen);
+    } else {
+       return -2;
+    }
+    return tlen;
+}
+
+int pxechn_parse_hex_sep(char *data, char istr[], char sep)
+{
+    int len = 0;
+    int ipos = 0, ichar;
+    
+    if (!data || !istr)
+       return -1;
+    while ((istr[ipos]) && (len < DHCP_OPT_LEN_MAX)) {
+       dprintf(" %02X%02X", *((int *)(istr + ipos)) & 0xFF, *((int *)(istr + ipos +1)) & 0xFF);
+       ichar = pxechn_parse_2bhex(istr + ipos);
+       if (ichar >=0) {
+           data[len++] = ichar;
+       } else {
+           return -EINVAL;
+       }
+       if (!istr[ipos+2]){
+           ipos += 2;
+       } else if (istr[ipos+2] != sep) {
+           return -(EINVAL + 1);
+       } else {
+           ipos += 3;
+       }
+    }
+    return len;
+}
+
+int pxechn_parse_opttype(char istr[], int optnum)
+{
+    char *pos;
+    int tlen, type, tmask;
+
+    if (!istr)
+       return -1;
+    pos = strchr(istr, '=');
+    if (!pos)
+       return -2;
+    if (istr[0] != '.') {
+       if (!pxechn_optnum_ok(optnum))
+           return -3;
+       return -3;      /* do lookup here */
+    } else {
+       tlen = pos - istr - 1;
+       if ((tlen < 1) || (tlen > 4))
+           return -4;
+       tmask = 0xFFFFFFFF >> (8 * (4 - tlen));
+       type = (*(int*)(istr + 1)) & tmask;
+    }
+    return type;
+}
+
+int pxechn_parse_setopt(struct dhcp_option opts[], struct dhcp_option *iopt,
+                       char istr[])
+{
+    int rv = 0, optnum, opttype;
+    char *cpos = NULL, *pos;
+
+    if (!opts || !iopt || !(iopt->data))
+       return -1;
+    if (!istr || !istr[0])
+       return -2;
+    // -EINVAL;
+    optnum = strtoul(istr, &cpos, 0);
+    if (!pxechn_optnum_ok(optnum))
+       return -3;
+    pos = strchr(cpos, '=');
+    if (!pos)
+       return -4;
+    opttype = pxechn_parse_opttype(cpos, optnum);
+    pos++;
+    switch(opttype) {
+    case 'b':
+       iopt->len = pxechn_parse_int(iopt->data, pos, 1);
+       break;
+    case 'l':
+       iopt->len = pxechn_parse_int(iopt->data, pos, 4);
+       break;
+    case 'q':
+       iopt->len = pxechn_parse_int(iopt->data, pos, 8);
+       break;
+    case 's':
+    case STRASINT_str:
+       iopt->len = strlen(pos);
+       if (iopt->len > DHCP_OPT_LEN_MAX)
+           iopt->len = DHCP_OPT_LEN_MAX;
+       memcpy(iopt->data, pos, iopt->len);
+       dprintf_pc_so_s("s.len=%d\trv=%d\n", iopt->len, rv);
+       break;
+    case 'w':
+       iopt->len = pxechn_parse_int(iopt->data, pos, 2);
+       break;
+    case 'x':
+       iopt->len = pxechn_parse_hex_sep(iopt->data, pos, ':');
+       break;
+    default:
+       return -6;
+       break;
+    }
+    if (pxechn_optlen_ok(iopt->len)) {
+       rv = pxechn_setopt(&(opts[optnum]), (void *)(iopt->data), iopt->len);
+    }
+    if((opttype == 's') || (opttype == STRASINT_str))
+       dprintf_pc_so_s("rv=%d\n", rv);
+    return rv;
+}
+
+int pxechn_parse_force(const char istr[])
+{
+    uint32_t rv = 0;
+    char *pos;
+    int terr = errno;
+
+    errno = 0;
+    rv = strtoul(istr, &pos, 0);
+    if ((istr == pos ) || ((rv == ULONG_MAX) && (errno)))
+       rv = 0;
+    errno = terr;
+    return rv;
+}
+
+int pxechn_uuid_set(struct pxelinux_opt *pxe)
+{
+    int ret = 0;
+
+    if (!pxe->p_unpacked[0])
+       ret = dhcp_unpack_packet((pxe_bootp_t *)(pxe->p[0].data),
+                                pxe->p[0].len, pxe->opts[0]);
+    if (ret) {
+       error("Could not unpack packet\n");
+       return -ret;    /* dhcp_unpack_packet always returns positive errors */
+    }
+
+    if (pxe->opts[0][97].len >= 0 )
+       pxechn_setopt(&(pxe->opts[2][97]), pxe->opts[0][97].data, pxe->opts[0][97].len);
+       return 1;
+    return 0;
+}
+
+int pxechn_parse_args(int argc, char *argv[], struct pxelinux_opt *pxe,
+                        struct dhcp_option opts[])
+{
+    int arg, optnum, rv = 0;
+    char *p = NULL;
+    const char optstr[] = "c:f:g:o:p:t:uwW";
+    struct dhcp_option iopt;
+
+    if (pxe->p[5].data)
+       pxe->fip = ( (pxe_bootp_t *)(pxe->p[5].data) )->sip;
+    else
+       pxe->fip = 0;
+    /* Fill */
+    pxe->fn = argv[0];
+    pxechn_parse_fn(pxe->fn, &(pxe->fip), pxe->host, &(pxe->fp));
+    pxechn_setopt_str(&(opts[67]), pxe->fp);
+    pxechn_setopt_str(&(opts[66]), pxe->host);
+    iopt.data = malloc(DHCP_OPT_LEN_MAX);
+    iopt.len = 0;
+    while ((rv >= 0) && (arg = getopt(argc, argv, optstr)) >= 0) {
+       dprintf_pc_pa("  Got arg '%c'/'%c' addr %08X val %s\n", arg == '?' ? optopt : arg, arg, (unsigned int)optarg, optarg ? optarg : "");
+       switch(arg) {
+       case 'c':       /* config */
+           pxechn_setopt_str(&(opts[209]), optarg);
+           break;
+       case 'f':       /* force */
+           pxe->force = pxechn_parse_force(optarg);
+           break;
+       case 'g':       /* gateway/DHCP relay */
+           pxe->gip = pxe_dns(optarg);
+           break;
+       case 'n':       /* native */
+           break;
+       case 'o':       /* option */
+           rv = pxechn_parse_setopt(opts, &iopt, optarg);
+           break;
+       case 'p':       /* prefix */
+           pxechn_setopt_str(&(opts[210]), optarg);
+           break;
+       case 't':       /* timeout */
+           optnum = strtoul(optarg, &p, 0);
+           if (p != optarg) {
+               optnum = htonl(optnum);
+               pxechn_setopt(&(opts[211]), (void *)(&optnum), 4);
+           } else {
+               rv = -3;
+           }
+           break;
+       case 'u':       /* UUID: copy option 97 from packet 1 if present */
+           pxechn_uuid_set(pxe);
+           break;
+       case 'w':       /* wait */
+           pxe->wait = 1;
+           break;
+       case 'W':       /* WDS */
+           pxe->wds = 1;
+           break;
+       case '?':
+           rv = -'?';
+       default:
+           break;
+       }
+       if (rv >= 0)    /* Clear it since getopt() doesn't guarentee it */
+           optarg = NULL;
+    }
+    if (iopt.data)
+       pxechn_opt_free(&iopt);
+/* FIXME: consider reordering the application of parsed command line options
+       such that the new nbp may be at the end */
+    if (rv >= 0) {
+       rv = 0;
+    } else if (arg != '?') {
+       printf("Invalid argument for -%c: %s\n", arg, optarg);
+    }
+    dprintf("pxechn_parse_args rv=%d\n", rv);
+    return rv;
+}
+
+int pxechn_args(int argc, char *argv[], struct pxelinux_opt *pxe)
+{
+    pxe_bootp_t *bootp0, *bootp1;
+    int ret = 0;
+    struct dhcp_option *opts;
+
+    opts = pxe->opts[2];
+    /* Start filling packet #1 */
+    bootp0 = (pxe_bootp_t *)(pxe->p[2].data);
+    bootp1 = (pxe_bootp_t *)(pxe->p[5].data);
+
+    ret = dhcp_unpack_packet(bootp0, pxe->p[2].len, opts);
+    if (ret) {
+       error("Could not unpack packet\n");
+       return -ret;
+    }
+    pxe->p_unpacked[2] = 1;
+    pxe->gip = bootp1->gip;
+
+    ret = pxechn_parse_args(argc, argv, pxe, opts);
+    if (ret)
+       return ret;
+    bootp1->sip = pxe->fip;
+    bootp1->gip = pxe->gip;
+
+    ret = dhcp_pack_packet(bootp1, (size_t *)&(pxe->p[5].len), opts);
+    if (ret) {
+       error("Could not pack packet\n");
+       return -ret;    /* dhcp_pack_packet always returns positive errors */
+    }
+    return ret;
+}
+
+/* dhcp_pkt2pxe: Copy packet to PXE's BC data for a ptype packet
+ *     Input:
+ *     p       Packet data to copy
+ *     len     length of data to copy
+ *     ptype   Packet type to overwrite
+ */
+int dhcp_pkt2pxe(pxe_bootp_t *p, size_t len, int ptype)
+{
+    com32sys_t reg;
+    t_PXENV_GET_CACHED_INFO *ci;
+    void *cp;
+    int rv = -1;
+
+    if (!(ci = lzalloc(sizeof(t_PXENV_GET_CACHED_INFO)))){
+       dprintf("Unable to lzalloc() for PXE call structure\n");
+       rv = 1;
+       goto ret;
+    }
+    ci->Status = PXENV_STATUS_FAILURE;
+    ci->PacketType = ptype;
+    memset(&reg, 0, sizeof(reg));
+    reg.eax.w[0] = 0x0009;
+    reg.ebx.w[0] = PXENV_GET_CACHED_INFO;
+    reg.edi.w[0] = OFFS(ci);
+    reg.es = SEG(ci);
+    __intcall(0x22, &reg, &reg);
+
+    if (ci->Status != PXENV_STATUS_SUCCESS) {
+       dprintf("PXE Get Cached Info failed: %d\n", ci->Status);
+       rv = 2;
+       goto ret;
+    }
+
+    cp = MK_PTR(ci->Buffer.seg, ci->Buffer.offs);
+    if (!(memcpy(cp, p, len))) {
+       dprintf("Failed to copy packet\n");
+       rv = 3;
+       goto ret;
+    }
+ret:
+    lfree(ci);
+   return rv;
+}
+
+int pxechn_mergeopt(struct pxelinux_opt *pxe, int d, int s)
+{
+    int ret = 0, i;
+
+    if ((d >= PXECHN_NUM_PKT_TYPE) || (s >= PXECHN_NUM_PKT_TYPE) 
+           || (d < 0) || (s < 0)) {
+       return -2;
+    }
+    if (!pxe->p_unpacked[s])
+       ret = dhcp_unpack_packet(pxe->p[s].data, pxe->p[s].len, pxe->opts[s]);
+    if (ret) {
+       error("Could not unpack packet for merge\n");
+       printf("Error %d (%d)\n", ret, EINVAL);
+       if (ret == EINVAL) {
+           if (pxe->p[s].len < 240)
+               printf("Packet %d is too short: %d (240)\n", s, pxe->p[s].len);
+           else if (((const struct dhcp_packet *)(pxe->p[s].data))->magic != htonl(DHCP_VENDOR_MAGIC))
+               printf("Packet %d has no magic\n", s);
+           else
+               error("Unknown EINVAL error\n");
+       } else {
+           error("Unknown error\n");
+       }
+       return -ret;
+    }
+    for (i = 0; i < NUM_DHCP_OPTS; i++) {
+       if (pxe->opts[d][i].len <= -1) {
+           if (pxe->opts[s][i].len >= 0)
+               pxechn_setopt(&(pxe->opts[d][i]), pxe->opts[s][i].data, pxe->opts[s][i].len);
+       }
+    }
+    return 0;
+}
+
+/* pxechn: Chainload to new PXE file ourselves
+ *     Input:
+ *     argc    Count of arguments passed
+ *     argv    Values of arguments passed
+ *     Returns 0 on success (which should never happen)
+ *             1 on loadfile() error
+ *             2 if DHCP Option 52 (Option Overload) used file field
+ *             -1 on usage error
+ */
+int pxechn(int argc, char *argv[])
+{
+    struct pxelinux_opt pxe;
+    pxe_bootp_t* p[(2 * PXECHN_NUM_PKT_TYPE)];
+    int rv = 0;
+    int i;
+    struct data_area file;
+    struct syslinux_rm_regs regs;
+
+    pxechn_init(&pxe);
+    for (i = 0; i < (2 * PXECHN_NUM_PKT_TYPE); i++) {
+       p[i] = (pxe_bootp_t *)(pxe.p[i].data);
+    }
+
+    /* Parse arguments and patch packet 1 */
+    rv = pxechn_args(argc, argv, &pxe);
+    dpressanykey(INT_MAX);
+    if (rv)
+       goto ret;
+    pxe_set_regs(&regs);
+    /* Load the file late; it's the most time-expensive operation */
+    printf("%s: Attempting to load '%s': ", app_name_str, pxe.fn);
+    if (loadfile(pxe.fn, &file.data, &file.size)) {
+       pxe_error(errno, NULL, NULL);
+       rv = -2;
+       goto ret;
+    }
+    puts("loaded.");
+    /* we'll be shuffling to the standard location of 7C00h */
+    file.base = 0x7C00;
+    if ((pxe.wds) || 
+           ((pxe.force) && ((pxe.force & (~PXECHN_FORCE_ALL)) == 0))) {
+       printf("Forcing behavior %08X\n", pxe.force);
+       // P2 is the same as P3 if no PXE server present.
+       if ((pxe.wds) ||
+               (pxe.force & PXECHN_FORCE_PKT2)) {
+           pxechn_fill_pkt(&pxe, PXENV_PACKET_TYPE_DHCP_ACK);
+           rv = pxechn_mergeopt(&pxe, 2, 1);
+           if (rv) {
+               dprintf("Merge Option returned %d\n", rv);
+           }
+           rv = dhcp_pack_packet(p[5], (size_t *)&(pxe.p[5].len), pxe.opts[2]);
+           rv = dhcp_pkt2pxe(p[5], pxe.p[5].len, PXENV_PACKET_TYPE_DHCP_ACK);
+       }
+       if (pxe.force & PXECHN_FORCE_PKT1) {
+           puts("Unimplemented force option utilized");
+       }
+    }
+    rv = dhcp_pkt2pxe(p[5], pxe.p[5].len, PXENV_PACKET_TYPE_CACHED_REPLY);
+    dprint_pxe_bootp_t(p[5], pxe.p[5].len);
+    if ((pxe.wds) ||
+           ((pxe.force) && ((pxe.force & (~PXECHN_FORCE_ALL)) == 0))) {
+       // printf("Forcing behavior %08X\n", pxe.force);
+       // P2 is the same as P3 if no PXE server present.
+       if ((pxe.wds) ||
+               (pxe.force & PXECHN_FORCE_PKT2)) {
+           rv = dhcp_pkt2pxe(p[5], pxe.p[5].len, PXENV_PACKET_TYPE_DHCP_ACK);
+       }
+    } else if (pxe.force) {
+       printf("FORCE: bad argument %08X\n", pxe.force);
+    }
+    printf("\n...Ready to boot:\n");
+    if (pxe.wait) {
+       pressanykey(INT_MAX);
+    } else {
+       dpressanykey(INT_MAX);
+    }
+    if (true) {
+       puts("  Attempting to boot...");
+       do_boot(&file, 1, &regs);
+    }
+    /* If failed, copy backup back in and abort */
+    dhcp_pkt2pxe(p[2], pxe.p[2].len, PXENV_PACKET_TYPE_CACHED_REPLY);
+    if (pxe.force && ((pxe.force & (~PXECHN_FORCE_ALL)) == 0)) {
+       if (pxe.force & PXECHN_FORCE_PKT2) {
+           rv = dhcp_pkt2pxe(p[1], pxe.p[1].len, PXENV_PACKET_TYPE_DHCP_ACK);
+       }
+    }
+ret:
+    return rv;
+}
+
+/* pxe_restart: Restart the PXE environment with a new PXE file
+ *     Input:
+ *     ifn     Name of file to chainload to in a format PXELINUX understands
+ *             This must strictly be TFTP or relative file
+ */
+int pxe_restart(char *ifn)
+{
+    int rv = 0;
+    struct pxelinux_opt pxe;
+    com32sys_t reg;
+    t_PXENV_RESTART_TFTP *pxep;        /* PXENV callback Parameter */
+
+    pxe.fn = ifn;
+    pxechn_fill_pkt(&pxe, PXENV_PACKET_TYPE_CACHED_REPLY);
+    if (pxe.p[5].data)
+       pxe.fip = ( (pxe_bootp_t *)(pxe.p[5].data) )->sip;
+    else
+       pxe.fip = 0;
+    rv = pxechn_parse_fn(pxe.fn, &(pxe.fip), pxe.host, &(pxe.fp));
+    if ((rv > 2) || (rv < 0)) {
+       printf("%s: ERROR: Unparsable filename argument: '%s'\n\n", app_name_str, pxe.fn);
+       goto ret;
+    }
+    printf("  Attempting to boot '%s'...\n\n", pxe.fn);
+    memset(&reg, 0, sizeof reg);
+    if (sizeof(t_PXENV_TFTP_READ_FILE) <= __com32.cs_bounce_size) {
+       pxep = __com32.cs_bounce;
+       memset(pxep, 0, sizeof(t_PXENV_RESTART_TFTP));
+    } else if (!(pxep = lzalloc(sizeof(t_PXENV_RESTART_TFTP)))){
+       dprintf("Unable to lzalloc() for PXE call structure\n");
+       goto ret;
+    }
+    pxep->Status = PXENV_STATUS_SUCCESS;       /* PXENV_STATUS_FAILURE */
+    strcpy((char *)pxep->FileName, ifn);
+    pxep->BufferSize = 0x8000;
+    pxep->Buffer = (void *)0x7c00;
+    pxep->ServerIPAddress = pxe.fip;
+    dprintf("FN='%s'  %08X %08X %08X %08X\n\n", (char *)pxep->FileName,
+       pxep->ServerIPAddress, (unsigned int)pxep,
+       pxep->BufferSize, (unsigned int)pxep->Buffer);
+    dprintf("PXENV_RESTART_TFTP status %d\n", pxep->Status);
+    reg.eax.w[0] = 0x0009;
+    reg.ebx.w[0] = PXENV_RESTART_TFTP;
+    reg.edi.w[0] = OFFS(pxep);
+    reg.es = SEG(pxep);
+
+    __intcall(0x22, &reg, &reg);
+
+    printf("PXENV_RESTART_TFTP returned %d\n", pxep->Status);
+    if (pxep != __com32.cs_bounce)
+       lfree(pxep);
+
+ret:
+    return rv;
+}
+
+/* pxechn_gpxe: Use gPXE to chainload a new NBP
+ *     Input:
+ *     argc    Count of arguments passed
+ *     argv    Values of arguments passed
+ *     Returns 0 on success (which should never happen)
+ *             1 on loadfile() error
+ *             -1 on usage error
+ */
+//FIXME:Implement
+int pxechn_gpxe(int argc, char *argv[])
+{
+    int rv = 0;
+    struct pxelinux_opt pxe;
+
+    if (argc) {
+       printf("%s\n", argv[0]);
+       pxechn_args(argc, argv, &pxe);
+    }
+    return rv;
+}
+
+int main(int argc, char *argv[])
+{
+    int rv= -1;
+    int err;
+    const struct syslinux_version *sv;
+
+    /* Initialization */
+    err = errno;
+    console_ansi_raw();        /* sets errno = 9 (EBADF) */
+    /* printf("%d %d\n", err, errno); */
+    errno = err;
+    sv = syslinux_version();
+    if (sv->filesystem != SYSLINUX_FS_PXELINUX) {
+       printf("%s: May only run in PXELINUX\n", app_name_str);
+       argc = 1;       /* prevents further processing to boot */
+    }
+    if (argc == 2) {
+       if ((strcasecmp(argv[1], "-h") == 0) || ((strcmp(argv[1], "-?") == 0))
+               || (strcasecmp(argv[1], "--help") == 0)) {
+           argc = 1;
+       } else {
+           rv = pxechn(argc - 1, &argv[1]);
+       }
+    } else if (argc >= 3) {
+       if ((strcmp(argv[1], "-r") == 0)) {
+           if (argc == 3)
+               rv = pxe_restart(argv[2]);
+       } else {
+           rv = pxechn(argc - 1, &argv[1]);
+       }
+    }
+    if (rv <= -1 ) {
+       usage();
+       rv = 1;
+    }
+    return rv;
+}
diff --git a/doc/pxechn.txt b/doc/pxechn.txt
new file mode 100644 (file)
index 0000000..a09bbe2
--- /dev/null
@@ -0,0 +1,94 @@
+= pxechn.c32 =
+:doctype: manpage
+:author: Gene Cumm
+:email: gene.cumm@gmail.com
+:revdate: 2012-05-27
+
+
+== NAME ==
+pxechn.c32 - Chainboot to new NBP
+
+
+== SYNOPSIS ==
+*pxechn.c32* [-h | --help | -?]
+*pxechn.c32* -r 'FILE'
+*pxechn.c32* 'FILE' ['OPTIONS']
+
+
+== DESCRIPTION ==
+Chainboot to a new NBP (Network Boot Program) 'FILE' with options to adjust PXE packet #3 (PXENV_PACKET_TYPE_CACHED_REPLY) to alter end behavior.  'FILE' may be a filename, an IP::FN ( 192.168.1.1::path/to/file.0 ), or URL.  'FILE' is parsed to adjust the DHCP 'sname' field/option 66 and 'file' field/option 67.
+// but these may be override-able in the future.
+
+
+== OPTIONS ==
+*-c* 'CONFIG'::
+    PXELINUX config file (DHCP Option 209).
+
+// *-f* 'MOD'::
+//     Force behavior specified by 'MOD'
+//
+// *-g* 'HOST'::
+//     Set DHCP gateway/relay.  Parsed by pxe_dns().
+//
+*-h*, *--help*, *-?*::
+    Print usage information; invalid options will also cause this.
+
+// *-n*::
+//     Use native methods, ignoring underlying gPXE/iPXE.
+// 
+// *-N*::
+//     Use non-native methods to utilize gPXE/iPXE (if available).
+//
+*-o* 'OPT.TYPE=VALUE'::
+    Specify a generic option.  'OPT' is in 'DECIMAL INPUT' format (below).  'TYPE' specifies the output type and input syntax.  'b'yte, 'w'ord(2B), 'l'ong(4B), 'q'uad(8B), character 's'tring and colon-separated he'x' string (case insensitive; bytes must have 2 digits and each byte must be separated).  byte, word, long and quad input values must meet criteria for 'DECIMAL INPUT'
+
+*-p* 'PATH'::
+    PXELINUX path (DHCP Option 210).
+
+*-r*::
+    Call the PXE stack with PXENV_RESTART_TFTP.  _Must_ be the only option and before 'FILE'.
+
+*-t* 'SECONDS'::
+    PXELINUX timeout (DHCP Option 211).
+
+// *-u*::
+//     Copy UUID (Option 97) if found in packet #1
+
+*-w*::
+    wait after loading before booting for user input.
+
+*-W*::
+    Enable WDS (Windows Deployment Services) - specific options.
+
+
+== DECIMAL INPUT ==
+All parameters that are defaulted to decimal format are processed by *strtoul*(3) with a base of 0 which allows alternate formats and finds a suitable non-space separating character.
+
+
+== EXAMPLES ==
+pxechn.c32 http://myhost.dom.loc/path/nbp.0 -c myconfig
+       Load nbp.0 and set PXELINUX config (option 209).
+
+pxechn.c32 gpxelinux.0 -p http://172.16.23.1/tftp/ -w -c myconfig -o 15.s=domain.loc -o 6.x=0A:01:01:02:ac:17:4D:Ec -
+       Load gpxelinux.0 from the current directory, set prefix, wait to execute, set first config, set the domain name and 2 domain name servers (case mixed to show insensitivity; 10.1.1.2 and 172.23.77.236).
+
+pxechn.c32 gpxelinux.0 -p http://172.16.23.1/tftp/ -w -X A012345678 -x 197:00d0de00
+pxechn.c32 gpxelinux.0 -p http://172.16.23.1/tftp/ -w -X A012:3456:78 -x 197:00-d0-de-00
+       Both of these are equivalent.  Load gpxelinux.0 (relative to the current directory and not altering sname/option 66), set the PXELINUX path prefix, wait after loading, set option 160 to 0x12 0x34 0x56 0x78, and option 197 to 0x00 0xD0 0xDE 0x00.
+
+
+== NOTES ==
+Please note that some NBPs may ignore packet #3 by either not examining it at all or by issuing its own DHCP DISCOVER/REQUEST, negating all DHCP field/option modifications by pxechn.c32.
+
+URL specifications in 'FILE' that include user/password before the host will currently cause the siaddr field to not be set properly.
+
+The non-space constraint is due to how Syslinux variants parse the command line as of 2012-01-12.
+
+
+== AUTHOR ==
+{author} <{email}>
+
+
+== COPYRIGHT ==
+Copyright \(C) 2012 {author}. Free use of this software is granted under
+the terms of the GNU General Public License (GPL).