From 32d48e29a2306f6951ff54bc6938f0a33a348479 Mon Sep 17 00:00:00 2001 From: Gene Cumm Date: Sun, 27 May 2012 09:23:07 -0400 Subject: [PATCH] pxechn.c32: PXE NBP chainloader 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 --- com32/modules/Makefile | 3 +- com32/modules/pxechn.c | 1122 ++++++++++++++++++++++++++++++++++++++++++++++++ doc/pxechn.txt | 94 ++++ 3 files changed, 1218 insertions(+), 1 deletion(-) create mode 100644 com32/modules/pxechn.c create mode 100644 doc/pxechn.txt diff --git a/com32/modules/Makefile b/com32/modules/Makefile index 1b2854f..d8861c4 100644 --- a/com32/modules/Makefile +++ b/com32/modules/Makefile @@ -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 index 0000000..3f9ebd3 --- /dev/null +++ b/com32/modules/pxechn.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#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(®, 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, ®, ®); + + 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(®s); + /* 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, ®s); + } + /* 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(®, 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, ®, ®); + + 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 index 0000000..a09bbe2 --- /dev/null +++ b/doc/pxechn.txt @@ -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). -- 2.7.4