/* ----------------------------------------------------------------------- * * * 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 #ifdef DEBUG # define PXECHN_DEBUG 1 #else # define PXECHN_DEBUG 0 #endif typedef union { uint64_t q; uint32_t l[2]; uint16_t w[4]; uint8_t b[8]; } reg64_t; #define dprintf0(f, ...) ((void)0) #ifndef dprintf # if (PXECHN_DEBUG > 0) # define dprintf printf # else # define dprintf(f, ...) ((void)0) # endif #endif #if (PXECHN_DEBUG > 0) # define dpressanykey pressanykey # 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 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 */ in_addr_t sip; /* siaddr: Next Server IP Address */ 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->sip = 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:St: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 'S': /* sip from sName */ pxe->sip = 1; 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; char *str; 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; if (pxe->sip > 0xFFFFFF) { /* a real IPv4 address */ bootp1->sip = pxe->sip; } else if ((pxe->sip == 1) && (opts[66].len > 0)){ /* unterminated? */ if (strnlen(opts[66].data, opts[66].len) == (size_t)opts[66].len) { str = malloc(opts[66].len + 1); if (str) { memcpy(str, opts[66].data, opts[66].len); str[opts[66].len] = 0; } } else { str = opts[66].data; } if (str) { bootp1->sip = pxe_dns(str); if (str != opts[66].data) free(str); } else { bootp1->sip = pxe->fip; } } else { 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; }