/* * GRUB -- GRand Unified Bootloader * Copyright (C) 2000,2001,2002,2004 Free Software Foundation, Inc. * * 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; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* Based on "src/main.c" in etherboot-4.5.8. */ /************************************************************************** ETHERBOOT - BOOTP/TFTP Bootstrap Program Author: Martin Renters Date: Dec/93 **************************************************************************/ /* #define TFTP_DEBUG 1 */ #include #include #include "grub.h" #include "tftp.h" #include "nic.h" static int tftp_file_read_undi(const char *name, int (*fnc)(unsigned char *, unsigned int, unsigned int, int)); static int tftp_read_undi(char *addr, int size); static int tftp_dir_undi(char *dirname); static void tftp_close_undi(void); static int buf_fill_undi(int abort); extern int use_bios_pxe; static int retry; static unsigned short iport = 2000; static unsigned short oport = 0; static unsigned short block, prevblock; static int bcounter; static struct tftp_t tp, saved_tp; static int packetsize; static int buf_eof, buf_read; static int saved_filepos; static unsigned short len, saved_len; static char *buf, *saved_name; /** * tftp_read * * Read file with _name_, data handled by _fnc_. In fact, grub never * use it, we just use it to read dhcp config file. */ static int await_tftp(int ival, void *ptr __unused, unsigned short ptype __unused, struct iphdr *ip, struct udphdr *udp) { static int tftp_count = 0; if (!udp) { return 0; } if (arptable[ARP_CLIENT].ipaddr.s_addr != ip->dest.s_addr) return 0; if (ntohs(udp->dest) != ival) return 0; tftp_count++; /* show progress */ if ((tftp_count % 1000) == 0) printf("."); return 1; } int tftp_file_read(const char *name, int (*fnc)(unsigned char *, unsigned int, unsigned int, int)) { struct tftpreq_t tp; struct tftp_t *tr; int rc; if (use_bios_pxe) return (tftp_file_read_undi(name, fnc)); retry = 0; block = 0; prevblock = 0; bcounter = 0; rx_qdrain(); tp.opcode = htons(TFTP_RRQ); /* Warning: the following assumes the layout of bootp_t. But that's fixed by the IP, UDP and BOOTP specs. */ len = sizeof(tp.ip) + sizeof(tp.udp) + sizeof(tp.opcode) + sprintf((char *)tp.u.rrq, "%s%coctet%cblksize%c%d", name, 0, 0, 0, TFTP_MAX_PACKET) + 1; if (!udp_transmit(arptable[ARP_SERVER].ipaddr.s_addr, ++iport, TFTP_PORT, len, &tp)) return (0); for (;;) { long timeout; #ifdef CONGESTED timeout = rfc2131_sleep_interval(block?TFTP_REXMT: TIMEOUT, retry); #else timeout = rfc2131_sleep_interval(TIMEOUT, retry); #endif if (!await_reply(await_tftp, iport, NULL, timeout)) { if (!block && retry++ < MAX_TFTP_RETRIES) { /* maybe initial request was lost */ if (!udp_transmit(arptable[ARP_SERVER].ipaddr.s_addr, ++iport, TFTP_PORT, len, &tp)) return (0); continue; } #ifdef CONGESTED if (block && ((retry += TFTP_REXMT) < TFTP_TIMEOUT)) { /* we resend our last ack */ #ifdef MDEBUG printf("\n"); #endif udp_transmit(arptable[ARP_SERVER].ipaddr.s_addr, iport, oport, TFTP_MIN_PACKET, &tp); continue; } #endif break; /* timeout */ } tr = (struct tftp_t *)&nic.packet[ETH_HLEN]; if (tr->opcode == ntohs(TFTP_ERROR)) { printf("TFTP error %d (%s)\n", ntohs(tr->u.err.errcode), tr->u.err.errmsg); break; } if (tr->opcode == ntohs(TFTP_OACK)) { char *p = tr->u.oack.data, *e; if (prevblock) /* shouldn't happen */ continue; /* ignore it */ len = ntohs(tr->udp.len) - sizeof(struct udphdr) - 2; if (len > TFTP_MAX_PACKET) goto noak; e = p + len; while (*p != '\0' && p < e) { /* if (!strcasecmp("blksize", p)) { */ if (!grub_strcmp("blksize", p)) { p += 8; /* if ((packetsize = strtoul(p, &p, 10)) < */ if ((packetsize = getdec(&p)) < TFTP_DEFAULTSIZE_PACKET) goto noak; while (p < e && *p) p++; if (p < e) p++; } else { noak: tp.opcode = htons(TFTP_ERROR); tp.u.err.errcode = 8; /* * Warning: the following assumes the layout of bootp_t. * But that's fixed by the IP, UDP and BOOTP specs. */ len = sizeof(tp.ip) + sizeof(tp.udp) + sizeof(tp.opcode) + sizeof(tp.u.err.errcode) + /* * Normally bad form to omit the format string, but in this case * the string we are copying from is fixed. sprintf is just being * used as a strcpy and strlen. */ sprintf((char *)tp.u.err.errmsg, "RFC1782 error") + 1; udp_transmit(arptable[ARP_SERVER].ipaddr.s_addr, iport, ntohs(tr->udp.src), len, &tp); return (0); } } if (p > e) goto noak; block = tp.u.ack.block = 0; /* this ensures, that */ /* the packet does not get */ /* processed as data! */ } else if (tr->opcode == htons(TFTP_DATA)) { len = ntohs(tr->udp.len) - sizeof(struct udphdr) - 4; if (len > packetsize) /* shouldn't happen */ continue; /* ignore it */ block = ntohs(tp.u.ack.block = tr->u.data.block); } else {/* neither TFTP_OACK nor TFTP_DATA */ break; } if ((block || bcounter) && (block != (unsigned short)(prevblock+1))) { /* Block order should be continuous */ tp.u.ack.block = htons(block = prevblock); } tp.opcode = htons(TFTP_ACK); oport = ntohs(tr->udp.src); udp_transmit(arptable[ARP_SERVER].ipaddr.s_addr, iport, oport, TFTP_MIN_PACKET, &tp); /* ack */ if ((unsigned short)(block-prevblock) != 1) { /* Retransmission or OACK, don't process via callback * and don't change the value of prevblock. */ continue; } prevblock = block; retry = 0; /* It's the right place to zero the timer? */ if ((rc = fnc(tr->u.data.download, ++bcounter, len, len < packetsize)) <= 0) return(rc); if (len < packetsize) { /* End of data --- fnc should not have returned */ printf("tftp download complete, but\n"); return (1); } } return (0); } /* Fill the buffer by receiving the data via the TFTP protocol. */ static int buf_fill (int abort) { #ifdef TFTP_DEBUG grub_printf ("buf_fill (%d)\n", abort); #endif if (use_bios_pxe) return (buf_fill_undi(abort)); while (! buf_eof && (buf_read + packetsize <= FSYS_BUFLEN)) { struct tftp_t *tr; long timeout; #ifdef CONGESTED timeout = rfc2131_sleep_interval (block ? TFTP_REXMT : TIMEOUT, retry); #else timeout = rfc2131_sleep_interval (TIMEOUT, retry); #endif if (! await_reply (await_tftp, iport, NULL, timeout)) { if (user_abort) return 0; if (! block && retry++ < MAX_TFTP_RETRIES) { /* Maybe initial request was lost. */ #ifdef TFTP_DEBUG grub_printf ("Maybe initial request was lost.\n"); #endif if (! udp_transmit (arptable[ARP_SERVER].ipaddr.s_addr, ++iport, TFTP_PORT, len, &tp)) return 0; continue; } #ifdef CONGESTED if (block && ((retry += TFTP_REXMT) < TFTP_TIMEOUT)) { /* We resend our last ack. */ # ifdef TFTP_DEBUG grub_printf ("\n"); # endif udp_transmit (arptable[ARP_SERVER].ipaddr.s_addr, iport, oport, TFTP_MIN_PACKET, &tp); continue; } #endif /* Timeout. */ return 0; } tr = (struct tftp_t *) &nic.packet[ETH_HLEN]; if (tr->opcode == ntohs (TFTP_ERROR)) { grub_printf ("TFTP error %d (%s)\n", ntohs (tr->u.err.errcode), tr->u.err.errmsg); return 0; } if (tr->opcode == ntohs (TFTP_OACK)) { char *p = tr->u.oack.data, *e; #ifdef TFTP_DEBUG grub_printf ("OACK "); #endif /* Shouldn't happen. */ if (prevblock) { /* Ignore it. */ grub_printf ("%s:%d: warning: PREVBLOCK != 0 (0x%x)\n", __FILE__, __LINE__, prevblock); continue; } len = ntohs (tr->udp.len) - sizeof (struct udphdr) - 2; if (len > TFTP_MAX_PACKET) goto noak; e = p + len; while (*p != '\000' && p < e) { if (! grub_strcmp ("blksize", p)) { p += 8; if ((packetsize = getdec (&p)) < TFTP_DEFAULTSIZE_PACKET) goto noak; #ifdef TFTP_DEBUG grub_printf ("blksize = %d\n", packetsize); #endif } else if (! grub_strcmp ("tsize", p)) { p += 6; if ((filemax = getdec (&p)) < 0) { filemax = -1; goto noak; } #ifdef TFTP_DEBUG grub_printf ("tsize = %d\n", filemax); #endif } else { noak: #ifdef TFTP_DEBUG grub_printf ("NOAK\n"); #endif tp.opcode = htons (TFTP_ERROR); tp.u.err.errcode = 8; len = (grub_sprintf ((char *) tp.u.err.errmsg, "RFC1782 error") + sizeof (tp.ip) + sizeof (tp.udp) + sizeof (tp.opcode) + sizeof (tp.u.err.errcode) + 1); udp_transmit (arptable[ARP_SERVER].ipaddr.s_addr, iport, ntohs (tr->udp.src), len, &tp); return 0; } while (p < e && *p) p++; if (p < e) p++; } if (p > e) goto noak; /* This ensures that the packet does not get processed as data! */ block = tp.u.ack.block = 0; } else if (tr->opcode == ntohs (TFTP_DATA)) { #ifdef TFTP_DEBUG grub_printf ("DATA "); #endif len = ntohs (tr->udp.len) - sizeof (struct udphdr) - 4; /* Shouldn't happen. */ if (len > packetsize) { /* Ignore it. */ grub_printf ("%s:%d: warning: LEN > PACKETSIZE (0x%x > 0x%x)\n", __FILE__, __LINE__, len, packetsize); continue; } block = ntohs (tp.u.ack.block = tr->u.data.block); } else /* Neither TFTP_OACK nor TFTP_DATA. */ break; if ((block || bcounter) && (block != (unsigned short) (prevblock + 1))) /* Block order should be continuous */ tp.u.ack.block = htons (block = prevblock); /* Should be continuous. */ tp.opcode = abort ? htons (TFTP_ERROR) : htons (TFTP_ACK); oport = ntohs (tr->udp.src); #ifdef TFTP_DEBUG grub_printf ("ACK\n"); #endif /* Ack. */ udp_transmit (arptable[ARP_SERVER].ipaddr.s_addr, iport, oport, TFTP_MIN_PACKET, &tp); if (abort) { buf_eof = 1; break; } /* Retransmission or OACK. */ if ((unsigned short) (block - prevblock) != 1) /* Don't process. */ continue; prevblock = block; /* Is it the right place to zero the timer? */ retry = 0; /* In GRUB, this variable doesn't play any important role at all, but use it for consistency with Etherboot. */ bcounter++; /* Copy the downloaded data to the buffer. */ grub_memmove (buf + buf_read, tr->u.data.download, len); buf_read += len; /* End of data. */ if (len < packetsize) buf_eof = 1; } return 1; } /* Send the RRQ whose length is LEN. */ static int send_rrq (void) { /* Initialize some variables. */ retry = 0; block = 0; prevblock = 0; packetsize = TFTP_DEFAULTSIZE_PACKET; bcounter = 0; buf = (char *) FSYS_BUF; buf_eof = 0; buf_read = 0; saved_filepos = 0; rx_qdrain(); #ifdef TFTP_DEBUG grub_printf ("send_rrq ()\n"); { int i; char *p; for (i = 0, p = (char *) &tp; i < len; i++) if (p[i] >= ' ' && p[i] <= '~') grub_putchar (p[i]); else grub_printf ("\\%x", (unsigned) p[i]); grub_putchar ('\n'); } #endif /* Send the packet. */ return udp_transmit (arptable[ARP_SERVER].ipaddr.s_addr, ++iport, TFTP_PORT, len, &tp); } /* Mount the network drive. If the drive is ready, return one, otherwise return zero. */ int tftp_mount (void) { /* Check if the current drive is the network drive. */ if (current_drive != NETWORK_DRIVE) return 0; /* If the drive is not initialized yet, abort. */ if (! network_ready) return 0; return 1; } /* Read up to SIZE bytes, returned in ADDR. */ int tftp_read (char *addr, int size) { /* How many bytes is read? */ int ret = 0; #ifdef TFTP_DEBUG grub_printf ("tftp_read (0x%x, %d)\n", (int) addr, size); #endif if (use_bios_pxe) return (tftp_read_undi(addr, size)); if (filepos < saved_filepos) { /* Uggh.. FILEPOS has been moved backwards. So reopen the file. */ buf_read = 0; buf_fill (1); grub_memmove ((char *) &tp, (char *) &saved_tp, saved_len); len = saved_len; #ifdef TFTP_DEBUG { int i; grub_printf ("opcode = 0x%x, rrq = ", (unsigned long) tp.opcode); for (i = 0; i < TFTP_DEFAULTSIZE_PACKET; i++) { if (tp.u.rrq[i] >= ' ' && tp.u.rrq[i] <= '~') grub_putchar (tp.u.rrq[i]); else grub_putchar ('*'); } grub_putchar ('\n'); } #endif if (! send_rrq ()) { errnum = ERR_WRITE; return 0; } } while (size > 0) { int amt = buf_read + saved_filepos - filepos; /* If the length that can be copied from the buffer is over the requested size, cut it down. */ if (amt > size) amt = size; if (amt > 0) { /* Copy the buffer to the supplied memory space. */ grub_memmove (addr, buf + filepos - saved_filepos, amt); size -= amt; addr += amt; filepos += amt; ret += amt; /* If the size of the empty space becomes small, move the unused data forwards. */ if (filepos - saved_filepos > FSYS_BUFLEN / 2) { grub_memmove (buf, buf + FSYS_BUFLEN / 2, FSYS_BUFLEN / 2); buf_read -= FSYS_BUFLEN / 2; saved_filepos += FSYS_BUFLEN / 2; } } else { /* Skip the whole buffer. */ saved_filepos += buf_read; buf_read = 0; } /* Read the data. */ if (size > 0 && ! buf_fill (0)) { errnum = ERR_READ; return 0; } /* Sanity check. */ if (size > 0 && buf_read == 0) { errnum = ERR_READ; return 0; } } return ret; } /* Check if the file DIRNAME really exists. Get the size and save it in FILEMAX. */ int tftp_dir (char *dirname) { int ch; #ifdef TFTP_DEBUG grub_printf ("tftp_dir (%s)\n", dirname); #endif if (use_bios_pxe) return (tftp_dir_undi(dirname)); /* In TFTP, there is no way to know what files exist. */ if (print_possibilities) return 1; /* Don't know the size yet. */ filemax = -1; reopen: /* Construct the TFTP request packet. */ tp.opcode = htons (TFTP_RRQ); /* Terminate the filename. */ ch = nul_terminate (dirname); /* Make the request string (octet, blksize and tsize). */ len = (grub_sprintf ((char *) tp.u.rrq, "%s%coctet%cblksize%c%d%ctsize%c0", dirname, 0, 0, 0, TFTP_MAX_PACKET, 0, 0) + sizeof (tp.ip) + sizeof (tp.udp) + sizeof (tp.opcode) + 1); /* Restore the original DIRNAME. */ dirname[grub_strlen (dirname)] = ch; /* Save the TFTP packet so that we can reopen the file later. */ grub_memmove ((char *) &saved_tp, (char *) &tp, len); saved_len = len; if (! send_rrq ()) { errnum = ERR_WRITE; return 0; } /* Read the data. */ if (! buf_fill (0)) { errnum = ERR_FILE_NOT_FOUND; return 0; } if (filemax == -1) { /* The server doesn't support the "tsize" option, so we must read the file twice... */ /* Zero the size of the file. */ filemax = 0; do { /* Add the length of the downloaded data. */ filemax += buf_read; /* Reset the offset. Just discard the contents of the buffer. */ buf_read = 0; /* Read the data. */ if (! buf_fill (0)) { errnum = ERR_READ; return 0; } } while (! buf_eof); /* Maybe a few amounts of data remains. */ filemax += buf_read; /* Retry the open instruction. */ goto reopen; } return 1; } /* Close the file. */ void tftp_close (void) { #ifdef TFTP_DEBUG grub_printf ("tftp_close ()\n"); #endif if (use_bios_pxe) { tftp_close_undi(); return; } buf_read = 0; buf_fill (1); } /* tftp implementation using BIOS established PXE stack */ static int tftp_file_read_undi(const char *name, int (*fnc)(unsigned char *, unsigned int, unsigned int, int)) { int rc; uint16_t len; buf = (char *)&nic.packet; /* open tftp session */ if (eb_pxenv_tftp_open(name, arptable[ARP_SERVER].ipaddr, arptable[ARP_GATEWAY].ipaddr, &packetsize) == 0) return (0); /* read blocks and invoke fnc for each block */ for (;;) { rc = eb_pxenv_tftp_read(buf, &len); if (rc == 0) break; rc = fnc(buf, ++block, len, len < packetsize); if (rc <= 0 || len < packetsize) break; } (void) eb_pxenv_tftp_close(); return (rc > 0 ? 1 : 0); } /* Fill the buffer by reading the data via the TFTP protocol. */ static int buf_fill_undi(int abort) { int rc; uint8_t *tmpbuf; while (! buf_eof && (buf_read + packetsize <= FSYS_BUFLEN)) { poll_interruptions(); if (user_abort) return 0; if (abort) { buf_eof = 1; break; } if (eb_pxenv_tftp_read(buf + buf_read, &len) == 0) return (0); buf_read += len; /* End of data. */ if (len < packetsize) buf_eof = 1; } return 1; } static void tftp_reopen_undi(void) { tftp_close(); (void) eb_pxenv_tftp_open(saved_name, arptable[ARP_SERVER].ipaddr, arptable[ARP_GATEWAY].ipaddr, &packetsize); buf_eof = 0; buf_read = 0; saved_filepos = 0; } /* Read up to SIZE bytes, returned in ADDR. */ static int tftp_read_undi(char *addr, int size) { int ret = 0; if (filepos < saved_filepos) { /* Uggh.. FILEPOS has been moved backwards. reopen the file. */ tftp_reopen_undi(); } while (size > 0) { int amt = buf_read + saved_filepos - filepos; /* If the length that can be copied from the buffer is over the requested size, cut it down. */ if (amt > size) amt = size; if (amt > 0) { /* Copy the buffer to the supplied memory space. */ grub_memmove (addr, buf + filepos - saved_filepos, amt); size -= amt; addr += amt; filepos += amt; ret += amt; /* If the size of the empty space becomes small, * move the unused data forwards. */ if (filepos - saved_filepos > FSYS_BUFLEN / 2) { grub_memmove (buf, buf + FSYS_BUFLEN / 2, FSYS_BUFLEN / 2); buf_read -= FSYS_BUFLEN / 2; saved_filepos += FSYS_BUFLEN / 2; } } else { /* Skip the whole buffer. */ saved_filepos += buf_read; buf_read = 0; } /* Read the data. */ if (size > 0 && ! buf_fill (0)) { errnum = ERR_READ; return 0; } /* Sanity check. */ if (size > 0 && buf_read == 0) { errnum = ERR_READ; return 0; } } return ret; } static int tftp_dir_undi(char *dirname) { int rc, ch; uint16_t len; /* In TFTP, there is no way to know what files exist. */ if (print_possibilities) return 1; /* name may be space terminated */ ch = nul_terminate(dirname); saved_name = (char *)&saved_tp; sprintf(saved_name, "%s", dirname); /* Restore the original dirname */ dirname[grub_strlen (dirname)] = ch; /* get the file size; must call before tftp_open */ rc = eb_pxenv_tftp_get_fsize(saved_name, arptable[ARP_SERVER].ipaddr, arptable[ARP_GATEWAY].ipaddr, &filemax); /* open tftp session */ if (eb_pxenv_tftp_open(saved_name, arptable[ARP_SERVER].ipaddr, arptable[ARP_GATEWAY].ipaddr, &packetsize) == 0) return (0); buf = (char *) FSYS_BUF; buf_eof = 0; buf_read = 0; saved_filepos = 0; if (rc == 0) { /* Read the entire file to get filemax */ filemax = 0; do { /* Add the length of the downloaded data. */ filemax += buf_read; buf_read = 0; if (! buf_fill (0)) { errnum = ERR_READ; return 0; } } while (! buf_eof); /* Maybe a few amounts of data remains. */ filemax += buf_read; tftp_reopen_undi(); /* reopen file to read from beginning */ } return (1); } static void tftp_close_undi(void) { buf_read = 0; buf_fill (1); (void) eb_pxenv_tftp_close(); }