#include #include #include #include #include #include #include #include "pxe.h" #define GPXE 1 static uint16_t real_base_mem; /* Amount of DOS memory after freeing */ uint8_t MAC[MAC_MAX]; /* Actual MAC address */ uint8_t MAC_len; /* MAC address len */ uint8_t MAC_type; /* MAC address type */ char __bss16 BOOTIFStr[7+3*(MAC_MAX+1)]; #define MAC_str (BOOTIFStr+7) /* The actual hardware address */ char __bss16 SYSUUIDStr[8+32+5]; #define UUID_str (SYSUUIDStr+8) /* The actual UUID */ char boot_file[256]; /* From DHCP */ char path_prefix[256]; /* From DHCP */ char dot_quad_buf[16]; static bool has_gpxe; static uint32_t gpxe_funcs; bool have_uuid = false; /* Common receive buffer */ static __lowmem char packet_buf[PKTBUF_SIZE] __aligned(16); const uint8_t TimeoutTable[] = { 2, 2, 3, 3, 4, 5, 6, 7, 9, 10, 12, 15, 18, 21, 26, 31, 37, 44, 53, 64, 77, 92, 110, 132, 159, 191, 229, 255, 255, 255, 255, 0 }; struct tftp_options { const char *str_ptr; /* string pointer */ size_t offset; /* offset into socket structre */ }; #define IFIELD(x) offsetof(struct inode, x) #define PFIELD(x) (offsetof(struct inode, pvt) + \ offsetof(struct pxe_pvt_inode, x)) static const struct tftp_options tftp_options[] = { { "tsize", IFIELD(size) }, { "blksize", PFIELD(tftp_blksize) }, }; static const int tftp_nopts = sizeof tftp_options / sizeof tftp_options[0]; static void tftp_error(struct inode *file, uint16_t errnum, const char *errstr); /* * Allocate a local UDP port structure and assign it a local port number. * Return the inode pointer if success, or null if failure */ static struct inode *allocate_socket(struct fs_info *fs) { struct inode *inode = alloc_inode(fs, 0, sizeof(struct pxe_pvt_inode)); if (!inode) { malloc_error("socket structure"); } else { struct pxe_pvt_inode *socket = PVT(inode); socket->tftp_localport = get_port(); inode->mode = DT_REG; /* No other types relevant for PXE */ } return inode; } static void free_socket(struct inode *inode) { struct pxe_pvt_inode *socket = PVT(inode); free_port(socket->tftp_localport); free_inode(inode); } #if GPXE static void gpxe_close_file(struct inode *inode) { struct pxe_pvt_inode *socket = PVT(inode); static __lowmem struct s_PXENV_FILE_CLOSE file_close; file_close.FileHandle = socket->tftp_remoteport; pxe_call(PXENV_FILE_CLOSE, &file_close); } #endif static void pxe_close_file(struct file *file) { struct inode *inode = file->inode; struct pxe_pvt_inode *socket = PVT(inode); if (!socket->tftp_goteof) { #if GPXE if (socket->tftp_localport == 0xffff) { gpxe_close_file(inode); } else #endif if (socket->tftp_localport != 0) { tftp_error(inode, 0, "No error, file close"); } } free_socket(inode); } /** * Take a nubmer of bytes in memory and convert to lower-case hxeadecimal * * @param: dst, output buffer * @param: src, input buffer * @param: count, number of bytes * */ static void lchexbytes(char *dst, const void *src, int count) { uint8_t half; uint8_t c; const uint8_t *s = src; for(; count > 0; count--) { c = *s++; half = ((c >> 4) & 0x0f) + '0'; *dst++ = half > '9' ? (half + 'a' - '9' - 1) : half; half = (c & 0x0f) + '0'; *dst++ = half > '9' ? (half + 'a' - '9' - 1) : half; } } /* * just like the lchexbytes, except to upper-case * */ static void uchexbytes(char *dst, const void *src, int count) { uint8_t half; uint8_t c; const uint8_t *s = src; for(; count > 0; count--) { c = *s++; half = ((c >> 4) & 0x0f) + '0'; *dst++ = half > '9' ? (half + 'A' - '9' - 1) : half; half = (c & 0x0f) + '0'; *dst++ = half > '9' ? (half + 'A' - '9' - 1) : half; } } /* * Parse a single hexadecimal byte, which must be complete (two * digits). This is used in URL parsing. */ static int hexbyte(const char *p) { if (!is_hex(p[0]) || !is_hex(p[1])) return -1; else return (hexval(p[0]) << 4) + hexval(p[1]); } /* * Tests an IP address in _ip_ for validity; return with 0 for bad, 1 for good. * We used to refuse class E, but class E addresses are likely to become * assignable unicast addresses in the near future. * */ bool ip_ok(uint32_t ip) { uint8_t ip_hi = (uint8_t)ip; /* First octet of the ip address */ if (ip == 0xffffffff || /* Refuse the all-ones address */ ip_hi == 0 || /* Refuse network zero */ ip_hi == 127 || /* Refuse the loopback network */ (ip_hi & 240) == 224) /* Refuse class D */ return false; return true; } /* * Take an IP address (in network byte order) in _ip_ and * output a dotted quad string to _dst_, returns the length * of the dotted quad ip string. * */ static int gendotquad(char *dst, uint32_t ip) { int part; int i = 0, j; char temp[4]; char *p = dst; for (; i < 4; i++) { j = 0; part = ip & 0xff; do { temp[j++] = (part % 10) + '0'; }while(part /= 10); for (; j > 0; j--) *p++ = temp[j-1]; *p++ = '.'; ip >>= 8; } /* drop the last dot '.' and zero-terminate string*/ *(--p) = 0; return p - dst; } /* * parse the ip_str and return the ip address with *res. * return the the string address after the ip string * */ static const char *parse_dotquad(const char *ip_str, uint32_t *res) { const char *p = ip_str; uint8_t part = 0; uint32_t ip = 0; int i; for (i = 0; i < 4; i++) { while (is_digit(*p)) { part = part * 10 + *p - '0'; p++; } if (i != 3 && *p != '.') return NULL; ip = (ip << 8) | part; part = 0; p++; } p--; *res = htonl(ip); return p; } /* * the ASM pxenv function wrapper, return 1 if error, or 0 * */ int pxe_call(int opcode, void *data) { extern void pxenv(void); com32sys_t regs; #if 0 printf("pxe_call op %04x data %p\n", opcode, data); #endif memset(®s, 0, sizeof regs); regs.ebx.w[0] = opcode; regs.es = SEG(data); regs.edi.w[0] = OFFS(data); call16(pxenv, ®s, ®s); return regs.eflags.l & EFLAGS_CF; /* CF SET if fail */ } /** * Send an ERROR packet. This is used to terminate a connection. * * @inode: Inode structure * @errnum: Error number (network byte order) * @errstr: Error string (included in packet) */ static void tftp_error(struct inode *inode, uint16_t errnum, const char *errstr) { static __lowmem struct { uint16_t err_op; uint16_t err_num; char err_msg[64]; } __packed err_buf; static __lowmem struct s_PXENV_UDP_WRITE udp_write; int len = min(strlen(errstr), sizeof(err_buf.err_msg)-1); struct pxe_pvt_inode *socket = PVT(inode); err_buf.err_op = TFTP_ERROR; err_buf.err_num = errnum; memcpy(err_buf.err_msg, errstr, len); err_buf.err_msg[len] = '\0'; udp_write.src_port = socket->tftp_localport; udp_write.dst_port = socket->tftp_remoteport; udp_write.ip = socket->tftp_remoteip; udp_write.gw = gateway(udp_write.ip); udp_write.buffer = FAR_PTR(&err_buf); udp_write.buffer_size = 4 + len + 1; /* If something goes wrong, there is nothing we can do, anyway... */ pxe_call(PXENV_UDP_WRITE, &udp_write); } /** * Send ACK packet. This is a common operation and so is worth canning. * * @param: inode, Inode pointer * @param: ack_num, Packet # to ack (network byte order) * */ static void ack_packet(struct inode *inode, uint16_t ack_num) { int err; static __lowmem uint16_t ack_packet_buf[2]; static __lowmem struct s_PXENV_UDP_WRITE udp_write; struct pxe_pvt_inode *socket = PVT(inode); /* Packet number to ack */ ack_packet_buf[0] = TFTP_ACK; ack_packet_buf[1] = ack_num; udp_write.src_port = socket->tftp_localport; udp_write.dst_port = socket->tftp_remoteport; udp_write.ip = socket->tftp_remoteip; udp_write.gw = gateway(udp_write.ip); udp_write.buffer = FAR_PTR(ack_packet_buf); udp_write.buffer_size = 4; err = pxe_call(PXENV_UDP_WRITE, &udp_write); #if 0 printf("sent %s\n", err ? "FAILED" : "OK"); #endif } /** * Get a DHCP packet from the PXE stack into the trackbuf * * @param: type, packet type * @return: buffer size * */ static int pxe_get_cached_info(int type) { int err; static __lowmem struct s_PXENV_GET_CACHED_INFO get_cached_info; printf(" %02x", type); get_cached_info.Status = 0; get_cached_info.PacketType = type; get_cached_info.BufferSize = 8192; get_cached_info.Buffer = FAR_PTR(trackbuf); err = pxe_call(PXENV_GET_CACHED_INFO, &get_cached_info); if (err) { printf("PXE API call failed, error %04x\n", err); kaboom(); } return get_cached_info.BufferSize; } /* * Return the type of pathname passed. */ enum pxe_path_type { PXE_RELATIVE, /* No :: or URL */ PXE_HOMESERVER, /* Starting with :: */ PXE_TFTP, /* host:: */ PXE_URL_TFTP, /* tftp:// */ PXE_URL, /* Absolute URL syntax */ }; static enum pxe_path_type pxe_path_type(const char *str) { const char *p; p = str; while (1) { switch (*p) { case ':': if (p[1] == ':') { if (p == str) return PXE_HOMESERVER; else return PXE_TFTP; } else if (p > str && p[1] == '/' && p[2] == '/') { if (!strncasecmp(str, "tftp://", 7)) return PXE_URL_TFTP; else return PXE_URL; } /* else fall through */ case '/': case '!': case '@': case '#': case '%': case '^': case '&': case '*': case '(': case ')': case '[': case ']': case '{': case '}': case '\\': case '|': case '=': case '`': case '~': case '\'': case '\"': case ';': case '>': case '<': case '?': case '\0': /* Any of these characters terminate the colon search */ return PXE_RELATIVE; default: break; } p++; } } #if GPXE /** * Get a fresh packet from a gPXE socket * @param: inode -> Inode pointer * */ static void get_packet_gpxe(struct inode *inode) { struct pxe_pvt_inode *socket = PVT(inode); static __lowmem struct s_PXENV_FILE_READ file_read; int err; while (1) { file_read.FileHandle = socket->tftp_remoteport; file_read.Buffer = FAR_PTR(packet_buf); file_read.BufferSize = PKTBUF_SIZE; err = pxe_call(PXENV_FILE_READ, &file_read); if (!err) /* successed */ break; if (file_read.Status != PXENV_STATUS_TFTP_OPEN) kaboom(); } memcpy(socket->tftp_pktbuf, packet_buf, file_read.BufferSize); socket->tftp_dataptr = socket->tftp_pktbuf; socket->tftp_bytesleft = file_read.BufferSize; socket->tftp_filepos += file_read.BufferSize; if (socket->tftp_bytesleft == 0) inode->size = socket->tftp_filepos; /* if we're done here, close the file */ if (inode->size > socket->tftp_filepos) return; /* Got EOF, close it */ socket->tftp_goteof = 1; gpxe_close_file(inode); } #endif /* GPXE */ /* * mangle a filename pointed to by _src_ into a buffer pointed * to by _dst_; ends on encountering any whitespace. * */ static void pxe_mangle_name(char *dst, const char *src) { size_t len = FILENAME_MAX-1; while (len-- && not_whitespace(*src)) *dst++ = *src++; *dst = '\0'; } /* * Get a fresh packet if the buffer is drained, and we haven't hit * EOF yet. The buffer should be filled immediately after draining! */ static void fill_buffer(struct inode *inode) { int err; int last_pkt; const uint8_t *timeout_ptr; uint8_t timeout; uint16_t buffersize; uint32_t oldtime; void *data = NULL; static __lowmem struct s_PXENV_UDP_READ udp_read; struct pxe_pvt_inode *socket = PVT(inode); if (socket->tftp_bytesleft || socket->tftp_goteof) return; #if GPXE if (socket->tftp_localport == 0xffff) { get_packet_gpxe(inode); return; } #endif /* * Start by ACKing the previous packet; this should cause * the next packet to be sent. */ timeout_ptr = TimeoutTable; timeout = *timeout_ptr++; oldtime = jiffies(); ack_again: ack_packet(inode, socket->tftp_lastpkt); while (timeout) { udp_read.buffer = FAR_PTR(packet_buf); udp_read.buffer_size = PKTBUF_SIZE; udp_read.src_ip = socket->tftp_remoteip; udp_read.dest_ip = IPInfo.myip; udp_read.s_port = socket->tftp_remoteport; udp_read.d_port = socket->tftp_localport; err = pxe_call(PXENV_UDP_READ, &udp_read); if (err) { uint32_t now = jiffies(); if (now-oldtime >= timeout) { oldtime = now; timeout = *timeout_ptr++; if (!timeout) break; } continue; } if (udp_read.buffer_size < 4) /* Bad size for a DATA packet */ continue; data = packet_buf; if (*(uint16_t *)data != TFTP_DATA) /* Not a data packet */ continue; /* If goes here, recevie OK, break */ break; } /* time runs out */ if (timeout == 0) kaboom(); last_pkt = socket->tftp_lastpkt; last_pkt = ntohs(last_pkt); /* Host byte order */ last_pkt++; last_pkt = htons(last_pkt); /* Network byte order */ if (*(uint16_t *)(data + 2) != last_pkt) { /* * Wrong packet, ACK the packet and try again. * This is presumably because the ACK got lost, * so the server just resent the previous packet. */ #if 0 printf("Wrong packet, wanted %04x, got %04x\n", \ htons(last_pkt), htons(*(uint16_t *)(data+2))); #endif goto ack_again; } /* It's the packet we want. We're also EOF if the size < blocksize */ socket->tftp_lastpkt = last_pkt; /* Update last packet number */ buffersize = udp_read.buffer_size - 4; /* Skip TFTP header */ memcpy(socket->tftp_pktbuf, packet_buf + 4, buffersize); socket->tftp_dataptr = socket->tftp_pktbuf; socket->tftp_filepos += buffersize; socket->tftp_bytesleft = buffersize; if (buffersize < socket->tftp_blksize) { /* it's the last block, ACK packet immediately */ ack_packet(inode, *(uint16_t *)(data + 2)); /* Make sure we know we are at end of file */ inode->size = socket->tftp_filepos; socket->tftp_goteof = 1; } } /** * getfssec: Get multiple clusters from a file, given the starting cluster. * In this case, get multiple blocks from a specific TCP connection. * * @param: fs, the fs_info structure address, in pxe, we don't use this. * @param: buf, buffer to store the read data * @param: openfile, TFTP socket pointer * @param: blocks, 512-byte block count; 0FFFFh = until end of file * * @return: the bytes read * */ static uint32_t pxe_getfssec(struct file *file, char *buf, int blocks, bool *have_more) { struct inode *inode = file->inode; struct pxe_pvt_inode *socket = PVT(inode); int count = blocks; int chunk; int bytes_read = 0; count <<= TFTP_BLOCKSIZE_LG2; while (count) { fill_buffer(inode); /* If we have no 'fresh' buffer, get it */ if (!socket->tftp_bytesleft) break; chunk = count; if (chunk > socket->tftp_bytesleft) chunk = socket->tftp_bytesleft; socket->tftp_bytesleft -= chunk; memcpy(buf, socket->tftp_dataptr, chunk); socket->tftp_dataptr += chunk; buf += chunk; bytes_read += chunk; count -= chunk; } if (socket->tftp_bytesleft || (socket->tftp_filepos < inode->size)) { fill_buffer(inode); *have_more = 1; } else if (socket->tftp_goteof) { /* * The socket is closed and the buffer drained; the caller will * call close_file and therefore free the socket. */ *have_more = 0; } return bytes_read; } /** * Open a TFTP connection to the server * * @param:filename, the file we wanna open * * @out: open_file_t structure, stores in file->open_file * @out: the lenght of this file, stores in file->file_len * */ static void pxe_searchdir(const char *filename, struct file *file) { struct fs_info *fs = file->fs; struct inode *inode; struct pxe_pvt_inode *socket; char *buf; const char *np; char *p; char *options; char *data; static __lowmem struct s_PXENV_UDP_WRITE udp_write; static __lowmem struct s_PXENV_UDP_READ udp_read; static __lowmem struct s_PXENV_FILE_OPEN file_open; static const char rrq_tail[] = "octet\0""tsize\0""0\0""blksize\0""1408"; static __lowmem char rrq_packet_buf[2+2*FILENAME_MAX+sizeof rrq_tail]; const struct tftp_options *tftp_opt; int i = 0; int err; int buffersize; int rrq_len; const uint8_t *timeout_ptr; uint32_t timeout; uint32_t oldtime; uint16_t tid; uint16_t opcode; uint16_t blk_num; uint32_t ip = 0; uint32_t opdata, *opdata_ptr; enum pxe_path_type path_type; char fullpath[2*FILENAME_MAX]; uint16_t server_port = TFTP_PORT; /* TFTP server port */ inode = file->inode = NULL; buf = rrq_packet_buf; *(uint16_t *)buf = TFTP_RRQ; /* TFTP opcode */ buf += 2; path_type = pxe_path_type(filename); if (path_type == PXE_RELATIVE) { snprintf(fullpath, sizeof fullpath, "%s%s", fs->cwd_name, filename); path_type = pxe_path_type(filename = fullpath); } switch (path_type) { case PXE_RELATIVE: /* Really shouldn't happen... */ case PXE_URL: buf = stpcpy(buf, filename); ip = IPInfo.serverip; /* Default server */ break; case PXE_HOMESERVER: buf = stpcpy(buf, filename+2); ip = IPInfo.serverip; break; case PXE_TFTP: np = strchr(filename, ':'); buf = stpcpy(buf, np+2); if (parse_dotquad(filename, &ip) != np) ip = dns_resolv(filename); break; case PXE_URL_TFTP: np = filename + 7; while (*np && *np != '/' && *np != ':') np++; if (np > filename + 7) { if (parse_dotquad(filename + 7, &ip) != np) ip = dns_resolv(filename + 7); } if (*np == ':') { np++; server_port = 0; while (*np >= '0' && *np <= '9') server_port = server_port * 10 + *np++ - '0'; server_port = server_port ? htons(server_port) : TFTP_PORT; } if (*np == '/') np++; /* Do *NOT* eat more than one slash here... */ /* * The ; is because of a quirk in the TFTP URI spec (RFC * 3617); it is to be followed by TFTP modes, which we just ignore. */ while (*np && *np != ';') { int v; if (*np == '%' && (v = hexbyte(np+1)) > 0) { *buf++ = v; np += 3; } else { *buf++ = *np++; } } *buf = '\0'; break; } if (!ip) return; /* No server */ buf++; /* Point *past* the final NULL */ memcpy(buf, rrq_tail, sizeof rrq_tail); buf += sizeof rrq_tail; rrq_len = buf - rrq_packet_buf; inode = allocate_socket(fs); if (!inode) return; /* Allocation failure */ socket = PVT(inode); #if GPXE if (path_type == PXE_URL) { if (has_gpxe) { file_open.Status = PXENV_STATUS_BAD_FUNC; file_open.FileName = FAR_PTR(rrq_packet_buf + 2); err = pxe_call(PXENV_FILE_OPEN, &file_open); if (err) goto done; socket->tftp_localport = -1; socket->tftp_remoteport = file_open.FileHandle; inode->size = -1; goto done; } else { static bool already = false; if (!already) { printf("URL syntax, but gPXE extensions not detected, " "trying plain TFTP...\n"); already = true; } } } #endif /* GPXE */ timeout_ptr = TimeoutTable; /* Reset timeout */ sendreq: timeout = *timeout_ptr++; if (!timeout) return; /* No file available... */ oldtime = jiffies(); socket->tftp_remoteip = ip; tid = socket->tftp_localport; /* TID(local port No) */ udp_write.buffer = FAR_PTR(rrq_packet_buf); udp_write.ip = ip; udp_write.gw = gateway(udp_write.ip); udp_write.src_port = tid; udp_write.dst_port = server_port; udp_write.buffer_size = rrq_len; pxe_call(PXENV_UDP_WRITE, &udp_write); /* If the WRITE call fails, we let the timeout take care of it... */ wait_pkt: for (;;) { buf = packet_buf; udp_read.status = 0; udp_read.buffer = FAR_PTR(buf); udp_read.buffer_size = PKTBUF_SIZE; udp_read.dest_ip = IPInfo.myip; udp_read.d_port = tid; err = pxe_call(PXENV_UDP_READ, &udp_read); if (err || udp_read.status) { uint32_t now = jiffies(); if (now - oldtime >= timeout) goto sendreq; } else { /* Make sure the packet actually came from the server */ if (udp_read.src_ip == socket->tftp_remoteip) break; } } socket->tftp_remoteport = udp_read.s_port; /* filesize <- -1 == unknown */ inode->size = -1; /* Default blksize unless blksize option negotiated */ socket->tftp_blksize = TFTP_BLOCKSIZE; buffersize = udp_read.buffer_size - 2; /* bytes after opcode */ if (buffersize < 0) goto wait_pkt; /* Garbled reply */ /* * Get the opcode type, and parse it */ opcode = *(uint16_t *)packet_buf; switch (opcode) { case TFTP_ERROR: inode->size = 0; break; /* ERROR reply; don't try again */ case TFTP_DATA: /* * If the server doesn't support any options, we'll get a * DATA reply instead of OACK. Stash the data in the file * buffer and go with the default value for all options... * * We got a DATA packet, meaning no options are * suported. Save the data away and consider the * length undefined, *unless* this is the only * data packet... */ buffersize -= 2; if (buffersize < 0) goto wait_pkt; data = packet_buf + 2; blk_num = *(uint16_t *)data; data += 2; if (blk_num != htons(1)) goto wait_pkt; socket->tftp_lastpkt = blk_num; if (buffersize > TFTP_BLOCKSIZE) goto err_reply; /* Corrupt */ else if (buffersize < TFTP_BLOCKSIZE) { /* * This is the final EOF packet, already... * We know the filesize, but we also want to * ack the packet and set the EOF flag. */ inode->size = buffersize; socket->tftp_goteof = 1; ack_packet(inode, blk_num); } socket->tftp_bytesleft = buffersize; socket->tftp_dataptr = socket->tftp_pktbuf; memcpy(socket->tftp_pktbuf, data, buffersize); break; case TFTP_OACK: /* * Now we need to parse the OACK packet to get the transfer * and packet sizes. */ options = packet_buf + 2; p = options; while (buffersize) { const char *opt = p; /* * If we find an option which starts with a NUL byte, * (a null option), we're either seeing garbage that some * TFTP servers add to the end of the packet, or we have * no clue how to parse the rest of the packet (what is * an option name and what is a value?) In either case, * discard the rest. */ if (!*opt) goto done; while (buffersize) { if (!*p) break; /* Found a final null */ *p++ |= 0x20; buffersize--; } if (!buffersize) break; /* Unterminated option */ /* Consume the terminal null */ p++; buffersize--; if (!buffersize) break; /* No option data */ /* * Parse option pointed to by options; guaranteed to be * null-terminated */ tftp_opt = tftp_options; for (i = 0; i < tftp_nopts; i++) { if (!strcmp(opt, tftp_opt->str_ptr)) break; tftp_opt++; } if (i == tftp_nopts) goto err_reply; /* Non-negotitated option returned, no idea what it means ...*/ /* get the address of the filed that we want to write on */ opdata_ptr = (uint32_t *)((char *)inode + tftp_opt->offset); opdata = 0; /* do convert a number-string to decimal number, just like atoi */ while (buffersize--) { uint8_t d = *p++; if (d == '\0') break; /* found a final null */ d -= '0'; if (d > 9) goto err_reply; /* Not a decimal digit */ opdata = opdata*10 + d; } *opdata_ptr = opdata; } break; default: printf("TFTP unknown opcode %d\n", ntohs(opcode)); goto err_reply; } done: if (!inode->size) { free_socket(inode); return; } file->inode = inode; return; err_reply: /* Build the TFTP error packet */ tftp_error(inode, TFTP_EOPTNEG, "TFTP protocol error"); printf("TFTP server sent an incomprehesible reply\n"); kaboom(); } /* * Store standard filename prefix */ static void get_prefix(void) { int len; char *p; char c; if (!(DHCPMagic & 0x04)) { /* No path prefix option, derive from boot file */ strlcpy(path_prefix, boot_file, sizeof path_prefix); len = strlen(path_prefix); p = &path_prefix[len - 1]; while (len--) { c = *p--; c |= 0x20; c = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c == '.' || c == '-'); if (!c) break; }; if (len < 0) p --; *(p + 2) = 0; /* Zero-terminate after delimiter */ } printf("TFTP prefix: %s\n", path_prefix); chdir(path_prefix); } /* * realpath for PXE */ static size_t pxe_realpath(struct fs_info *fs, char *dst, const char *src, size_t bufsize) { enum pxe_path_type path_type = pxe_path_type(src); return snprintf(dst, bufsize, "%s%s", path_type == PXE_RELATIVE ? fs->cwd_name : "", src); } /* * chdir for PXE */ static int pxe_chdir(struct fs_info *fs, const char *src) { /* The cwd for PXE is just a text prefix */ enum pxe_path_type path_type = pxe_path_type(src); if (path_type == PXE_RELATIVE) strlcat(fs->cwd_name, src, sizeof fs->cwd_name); else strlcpy(fs->cwd_name, src, sizeof fs->cwd_name); dprintf("cwd = \"%s\"\n", fs->cwd_name); return 0; } /* * try to load a config file, if found, return 1, or return 0 * */ static int try_load(char *config_name) { com32sys_t regs; printf("Trying to load: %-50s ", config_name); pxe_mangle_name(KernelName, config_name); memset(®s, 0, sizeof regs); regs.edi.w[0] = OFFS_WRT(KernelName, 0); call16(core_open, ®s, ®s); if (regs.eflags.l & EFLAGS_ZF) { strcpy(ConfigName, KernelName); printf("\r"); return 0; } else { printf("ok\n"); return 1; } } /* Load the config file, return 1 if failed, or 0 */ static int pxe_load_config(void) { const char *cfgprefix = "pxelinux.cfg/"; const char *default_str = "default"; char *config_file; char *last; int tries = 8; get_prefix(); if (DHCPMagic & 0x02) { /* We got a DHCP option, try it first */ if (try_load(ConfigName)) return 0; } /* * Have to guess config file name ... */ config_file = stpcpy(ConfigName, cfgprefix); /* Try loading by UUID */ if (have_uuid) { strcpy(config_file, UUID_str); if (try_load(ConfigName)) return 0; } /* Try loading by MAC address */ strcpy(config_file, MAC_str); if (try_load(ConfigName)) return 0; /* Nope, try hexadecimal IP prefixes... */ uchexbytes(config_file, (uint8_t *)&IPInfo.myip, 4); last = &config_file[8]; while (tries) { *last = '\0'; /* Zero-terminate string */ if (try_load(ConfigName)) return 0; last--; /* Drop one character */ tries--; }; /* Final attempt: "default" string */ strcpy(config_file, default_str); if (try_load(ConfigName)) return 0; printf("%-68s\n", "Unable to locate configuration file"); kaboom(); } /* * Generate the bootif string. */ static void make_bootif_string(void) { const uint8_t *src; char *dst = BOOTIFStr; int i; dst += sprintf(dst, "BOOTIF=%02x", MAC_type); src = MAC; for (i = MAC_len; i; i--) dst += sprintf(dst, "-%02x", *src++); } /* * Generate the SYSUUID string, if we have one... */ static void make_sysuuid_string(void) { static const uint8_t uuid_dashes[] = {4, 2, 2, 2, 6, 0}; const uint8_t *src = uuid; const uint8_t *uuid_ptr = uuid_dashes; char *dst; SYSUUIDStr[0] = '\0'; /* If nothing there... */ /* Try loading by UUID */ if (have_uuid) { dst = stpcpy(SYSUUIDStr, "SYSUUID="); while (*uuid_ptr) { int len = *uuid_ptr; lchexbytes(dst, src, len); dst += len * 2; src += len; uuid_ptr++; *dst++ = '-'; } /* Remove last dash and zero-terminate */ *--dst = '\0'; } } /* * Generate an ip=::: * option into IPOption based on a DHCP packet in trackbuf. * */ char __bss16 IPOption[3+4*16]; static void genipopt(void) { char *p = IPOption; const uint32_t *v = &IPInfo.myip; int i; p = stpcpy(p, "ip="); for (i = 0; i < 4; i++) { p += gendotquad(p, *v++); *p++ = ':'; } *--p = '\0'; } /* Generate ip= option and print the ip adress */ static void ip_init(void) { uint32_t ip = IPInfo.myip; genipopt(); gendotquad(dot_quad_buf, ip); ip = ntohl(ip); printf("My IP address seems to be %08X %s\n", ip, dot_quad_buf); } /* * Print the IPAPPEND strings, in order */ extern const uint16_t IPAppends[]; extern const char numIPAppends[]; static void print_ipappend(void) { size_t i; for (i = 0; i < (size_t)numIPAppends; i++) { const char *p = (const char *)(size_t)IPAppends[i]; if (*p) printf("%s\n", p); } } /* * Validity check on possible !PXE structure in buf * return 1 for success, 0 for failure. * */ static int is_pxe(const void *buf) { const struct pxe_t *pxe = buf; const uint8_t *p = buf; int i = pxe->structlength; uint8_t sum = 0; if (i < sizeof(struct pxe_t) || memcmp(pxe->signature, "!PXE", 4)) return 0; while (i--) sum += *p++; return sum == 0; } /* * Just like is_pxe, it checks PXENV+ structure * */ static int is_pxenv(const void *buf) { const struct pxenv_t *pxenv = buf; const uint8_t *p = buf; int i = pxenv->length; uint8_t sum = 0; /* The pxeptr field isn't present in old versions */ if (i < offsetof(struct pxenv_t, pxeptr) || memcmp(pxenv->signature, "PXENV+", 6)) return 0; while (i--) sum += *p++; return sum == 0; } /* * memory_scan_for_pxe_struct: * memory_scan_for_pxenv_struct: * * If none of the standard methods find the !PXE/PXENV+ structure, * look for it by scanning memory. * * return the corresponding pxe structure if found, or NULL; */ static const void *memory_scan(uintptr_t start, int (*func)(const void *)) { const char *ptr; /* Scan each 16 bytes of conventional memory before the VGA region */ for (ptr = (const char *)start; ptr < (const char *)0xA0000; ptr += 16) { if (func(ptr)) return ptr; /* found it! */ ptr += 16; } return NULL; } static const struct pxe_t *memory_scan_for_pxe_struct(void) { extern uint16_t BIOS_fbm; /* Starting segment */ return memory_scan(BIOS_fbm << 10, is_pxe); } static const struct pxenv_t *memory_scan_for_pxenv_struct(void) { return memory_scan(0x10000, is_pxenv); } /* * Find the !PXE structure; we search for the following, in order: * * a. !PXE structure as SS:[SP + 4] * b. PXENV+ structure at [ES:BX] * c. INT 1Ah AX=0x5650 -> PXENV+ * d. Search memory for !PXE * e. Search memory for PXENV+ * * If we find a PXENV+ structure, we try to find a !PXE structure from * if if the API version is 2.1 or later * */ static int pxe_init(bool quiet) { extern void pxe_int1a(void); char plan = 'A'; uint16_t seg, off; uint16_t code_seg, code_len; uint16_t data_seg, data_len; const char *base = GET_PTR(InitStack); com32sys_t regs; const char *type; const struct pxenv_t *pxenv; const struct pxe_t *pxe; /* Assume API version 2.1 */ APIVer = 0x201; /* Plan A: !PXE structure as SS:[SP + 4] */ off = *(const uint16_t *)(base + 48); seg = *(const uint16_t *)(base + 50); pxe = MK_PTR(seg, off); if (is_pxe(pxe)) goto have_pxe; /* Plan B: PXENV+ structure at [ES:BX] */ plan++; off = *(const uint16_t *)(base + 24); /* Original BX */ seg = *(const uint16_t *)(base + 4); /* Original ES */ pxenv = MK_PTR(seg, off); if (is_pxenv(pxenv)) goto have_pxenv; /* Plan C: PXENV+ structure via INT 1Ah AX=5650h */ plan++; memset(®s, 0, sizeof regs); regs.eax.w[0] = 0x5650; call16(pxe_int1a, ®s, ®s); if (!(regs.eflags.l & EFLAGS_CF) && (regs.eax.w[0] == 0x564e)) { pxenv = MK_PTR(regs.es, regs.ebx.w[0]); if (is_pxenv(pxenv)) goto have_pxenv; } /* Plan D: !PXE memory scan */ plan++; if ((pxe = memory_scan_for_pxe_struct())) goto have_pxe; /* Plan E: PXENV+ memory scan */ plan++; if ((pxenv = memory_scan_for_pxenv_struct())) goto have_pxenv; /* Found nothing at all !! */ if (!quiet) printf("No !PXE or PXENV+ API found; we're dead...\n"); return -1; have_pxenv: APIVer = pxenv->version; if (!quiet) printf("Found PXENV+ structure\nPXE API version is %04x\n", APIVer); /* if the API version number is 0x0201 or higher, use the !PXE structure */ if (APIVer >= 0x201) { if (pxenv->length >= sizeof(struct pxenv_t)) { pxe = GET_PTR(pxenv->pxeptr); if (is_pxe(pxe)) goto have_pxe; /* * Nope, !PXE structure missing despite API 2.1+, or at least * the pointer is missing. Do a last-ditch attempt to find it */ if ((pxe = memory_scan_for_pxe_struct())) goto have_pxe; } APIVer = 0x200; /* PXENV+ only, assume version 2.00 */ } /* Otherwise, no dice, use PXENV+ structure */ data_len = pxenv->undidatasize; data_seg = pxenv->undidataseg; code_len = pxenv->undicodesize; code_seg = pxenv->undicodeseg; PXEEntry = pxenv->rmentry; type = "PXENV+"; goto have_entrypoint; have_pxe: data_len = pxe->seg[PXE_Seg_UNDIData].size; data_seg = pxe->seg[PXE_Seg_UNDIData].sel; code_len = pxe->seg[PXE_Seg_UNDICode].size; code_seg = pxe->seg[PXE_Seg_UNDICode].sel; PXEEntry = pxe->entrypointsp; type = "!PXE"; have_entrypoint: if (!quiet) { printf("%s entry point found (we hope) at %04X:%04X via plan %c\n", type, PXEEntry.seg, PXEEntry.offs, plan); printf("UNDI code segment at %04X len %04X\n", code_seg, code_len); printf("UNDI data segment at %04X len %04X\n", data_seg, data_len); } code_seg = code_seg + ((code_len + 15) >> 4); data_seg = data_seg + ((data_len + 15) >> 4); real_base_mem = max(code_seg, data_seg) >> 6; /* Convert to kilobytes */ return 0; } /* * See if we have gPXE */ static void gpxe_init(void) { int err; static __lowmem struct s_PXENV_FILE_API_CHECK api_check; if (APIVer >= 0x201) { api_check.Size = sizeof api_check; api_check.Magic = 0x91d447b2; err = pxe_call(PXENV_FILE_API_CHECK, &api_check); if (!err && api_check.Magic == 0xe9c17b20) gpxe_funcs = api_check.APIMask; } /* Necessary functions for us to use the gPXE file API */ has_gpxe = (~gpxe_funcs & 0x4b) == 0; } /* * Initialize UDP stack * */ static void udp_init(void) { int err; static __lowmem struct s_PXENV_UDP_OPEN udp_open; udp_open.src_ip = IPInfo.myip; err = pxe_call(PXENV_UDP_OPEN, &udp_open); if (err || udp_open.status) { printf("Failed to initialize UDP stack "); printf("%d\n", udp_open.status); kaboom(); } } /* * Network-specific initialization */ static void network_init(void) { struct bootp_t *bp = (struct bootp_t *)trackbuf; int pkt_len; *LocalDomain = 0; /* No LocalDomain received */ /* * Get the DHCP client identifiers (query info 1) */ printf("Getting cached packet "); pkt_len = pxe_get_cached_info(1); parse_dhcp(pkt_len); /* * We don't use flags from the request packet, so * this is a good time to initialize DHCPMagic... * Initialize it to 1 meaning we will accept options found; * in earlier versions of PXELINUX bit 0 was used to indicate * we have found option 208 with the appropriate magic number; * we no longer require that, but MAY want to re-introduce * it in the future for vendor encapsulated options. */ *(char *)&DHCPMagic = 1; /* * Get the BOOTP/DHCP packet that brought us file (and an IP * address). This lives in the DHCPACK packet (query info 2) */ pkt_len = pxe_get_cached_info(2); parse_dhcp(pkt_len); /* * Save away MAC address (assume this is in query info 2. If this * turns out to be problematic it might be better getting it from * the query info 1 packet */ MAC_len = bp->hardlen > 16 ? 0 : bp->hardlen; MAC_type = bp->hardware; memcpy(MAC, bp->macaddr, MAC_len); /* * Get the boot file and other info. This lives in the CACHED_REPLY * packet (query info 3) */ pkt_len = pxe_get_cached_info(3); parse_dhcp(pkt_len); printf("\n"); make_bootif_string(); make_sysuuid_string(); ip_init(); print_ipappend(); /* * Check to see if we got any PXELINUX-specific DHCP options; in particular, * if we didn't get the magic enable, do not recognize any other options. */ if ((DHCPMagic & 1) == 0) DHCPMagic = 0; udp_init(); } /* * Initialize pxe fs * */ static int pxe_fs_init(struct fs_info *fs) { (void)fs; /* drop the compile warning message */ /* This block size is actually arbitrary... */ fs->sector_shift = fs->block_shift = TFTP_BLOCKSIZE_LG2; fs->sector_size = fs->block_size = 1 << TFTP_BLOCKSIZE_LG2; /* This block size is actually arbitrary... */ fs->sector_shift = fs->block_shift = TFTP_BLOCKSIZE_LG2; fs->sector_size = fs->block_size = 1 << TFTP_BLOCKSIZE_LG2; /* Find the PXE stack */ if (pxe_init(false)) kaboom(); /* See if we also have a gPXE stack */ gpxe_init(); /* Network-specific initialization */ network_init(); /* Initialize network-card-specific idle handling */ pxe_idle_init(); /* Our name for the root */ strcpy(fs->cwd_name, "::"); return 0; } /* * Look to see if we are on an EFI CSM system. Some EFI * CSM systems put the BEV stack in low memory, which means * a return to the PXE stack will crash the system. However, * INT 18h works reliably, so in that case hack the stack and * point the "return address" to an INT 18h instruction. * * Hack the stack instead of the much simpler "just invoke INT 18h * if we want to reset", so that chainloading other NBPs will work. * * This manipulates the real-mode InitStack directly. It relies on this * *not* being a currently active stack, i.e. the former * USE_PXE_PROVIDED_STACK no longer works. */ extern far_ptr_t InitStack; struct efi_struct { uint32_t magic; uint8_t csum; uint8_t len; } __attribute__((packed)); #define EFI_MAGIC (('$' << 24)+('E' << 16)+('F' << 8)+'I') static inline bool is_efi(const struct efi_struct *efi) { /* * We don't verify the checksum, because it seems some CSMs leave * it at zero, sigh... */ return (efi->magic == EFI_MAGIC) && (efi->len >= 83); } static void install_efi_csm_hack(void) { static const uint8_t efi_csm_hack[] = { 0xcd, 0x18, /* int $0x18 */ 0xea, 0xf0, 0xff, 0x00, 0xf0, /* ljmpw $0xf000,$0xfff0 */ 0xf4 /* hlt */ }; uint16_t *retcode; retcode = GET_PTR(*(far_ptr_t *)((char *)GET_PTR(InitStack) + 44)); /* Don't do this if the return already points to int $0x18 */ if (*retcode != 0x18cd) { uint32_t efi_ptr; bool efi = false; for (efi_ptr = 0xe0000 ; efi_ptr < 0x100000 ; efi_ptr += 16) { if (is_efi((const struct efi_struct *)efi_ptr)) { efi = true; break; } } if (efi) { uint8_t *src = GET_PTR(InitStack); uint8_t *dst = src - sizeof efi_csm_hack; memmove(dst, src, 52); memcpy(dst+52, efi_csm_hack, sizeof efi_csm_hack); InitStack.offs -= sizeof efi_csm_hack; /* Clobber the return address */ *(uint16_t *)(dst+44) = OFFS_WRT(dst+52, InitStack.seg); *(uint16_t *)(dst+46) = InitStack.seg; } } } int reset_pxe(void) { static __lowmem struct s_PXENV_UDP_CLOSE udp_close; extern void gpxe_unload(void); int err = 0; pxe_idle_cleanup(); pxe_call(PXENV_UDP_CLOSE, &udp_close); if (gpxe_funcs & 0x80) { /* gPXE special unload implemented */ call16(gpxe_unload, &zero_regs, NULL); /* Locate the actual vendor stack... */ err = pxe_init(true); } install_efi_csm_hack(); return err; } /* * This function unloads the PXE and UNDI stacks and * unclaims the memory. */ void unload_pxe(void) { /* PXE unload sequences */ static const uint8_t new_api_unload[] = { PXENV_UNDI_SHUTDOWN, PXENV_UNLOAD_STACK, PXENV_STOP_UNDI, 0 }; static const uint8_t old_api_unload[] = { PXENV_UNDI_SHUTDOWN, PXENV_UNLOAD_STACK, PXENV_UNDI_CLEANUP, 0 }; unsigned int api; const uint8_t *api_ptr; int err; size_t int_addr; static __lowmem union { struct s_PXENV_UNDI_SHUTDOWN undi_shutdown; struct s_PXENV_UNLOAD_STACK unload_stack; struct s_PXENV_STOP_UNDI stop_undi; struct s_PXENV_UNDI_CLEANUP undi_cleanup; uint16_t Status; /* All calls have this as the first member */ } unload_call; dprintf("FBM before unload = %d\n", BIOS_fbm); err = reset_pxe(); dprintf("FBM after reset_pxe = %d, err = %d\n", BIOS_fbm, err); /* If we want to keep PXE around, we still need to reset it */ if (KeepPXE || err) return; dprintf("APIVer = %04x\n", APIVer); api_ptr = APIVer >= 0x0200 ? new_api_unload : old_api_unload; while((api = *api_ptr++)) { dprintf("PXE call %04x\n", api); memset(&unload_call, 0, sizeof unload_call); err = pxe_call(api, &unload_call); if (err || unload_call.Status != PXENV_STATUS_SUCCESS) { dprintf("PXE unload API call %04x failed\n", api); goto cant_free; } } api = 0xff00; if (real_base_mem <= BIOS_fbm) { /* Sanity check */ dprintf("FBM %d < real_base_mem %d\n", BIOS_fbm, real_base_mem); goto cant_free; } api++; /* Check that PXE actually unhooked the INT 0x1A chain */ int_addr = (size_t)GET_PTR(*(far_ptr_t *)(4 * 0x1a)); int_addr >>= 10; if (int_addr >= real_base_mem || int_addr < BIOS_fbm) { BIOS_fbm = real_base_mem; dprintf("FBM after unload_pxe = %d\n", BIOS_fbm); return; } dprintf("Can't free FBM, real_base_mem = %d, " "FBM = %d, INT 1A = %08x (%d)\n", real_base_mem, BIOS_fbm, *(uint32_t *)(4 * 0x1a), int_addr); cant_free: printf("Failed to free base memory error %04x-%08x (%d/%dK)\n", api, *(uint32_t *)(4 * 0x1a), BIOS_fbm, real_base_mem); return; } const struct fs_ops pxe_fs_ops = { .fs_name = "pxe", .fs_flags = FS_NODEV, .fs_init = pxe_fs_init, .searchdir = pxe_searchdir, .chdir = pxe_chdir, .realpath = pxe_realpath, .getfssec = pxe_getfssec, .close_file = pxe_close_file, .mangle_name = pxe_mangle_name, .load_config = pxe_load_config, };