| /* ----------------------------------------------------------------------- * |
| * |
| * 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> |
| |
| |
| #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) |
| { |
| const union syslinux_derivative_info *sdi; |
| const com32sys_t *pxe_regs; |
| |
| sdi = syslinux_derivative_info(); |
| pxe_regs = sdi->pxe.stack; /* Original register values */ |
| |
| /* Just to be sure... */ |
| memset(regs, 0, sizeof *regs); |
| |
| regs->ip = 0x7C00; |
| |
| /* Point to the original stack */ |
| regs->ss = sdi->pxe.stack_seg; |
| regs->esp.l = sdi->pxe.stack_offs + sizeof(com32sys_t); |
| |
| /* Point to the PXENV+ address */ |
| regs->es = pxe_regs->es; |
| regs->ebx.l = pxe_regs->ebx.l; |
| |
| dprintf("\nsp:%04x ss:%04x es:%04x bx:%04x\n", regs->esp.w[0], |
| regs->ss, regs->es, regs->ebx.w[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) |
| { |
| 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; |
| pxe_call(PXENV_GET_CACHED_INFO, ci); |
| |
| 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; |
| 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); |
| 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); |
| |
| pxe_call(PXENV_RESTART_TFTP, pxep); |
| |
| printf("PXENV_RESTART_TFTP returned %d\n", pxep->Status); |
| 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; |
| } |