123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485 |
- /*
- * GRUB -- GRand Unified Bootloader
- * Copyright (C) 2010,2011 Free Software Foundation, Inc.
- *
- * GRUB 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 3 of the License, or
- * (at your option) any later version.
- *
- * GRUB 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 GRUB. If not, see <http://www.gnu.org/licenses/>.
- */
- #include <grub/misc.h>
- #include <grub/net/udp.h>
- #include <grub/net/ip.h>
- #include <grub/net/ethernet.h>
- #include <grub/net/netbuff.h>
- #include <grub/net.h>
- #include <grub/mm.h>
- #include <grub/dl.h>
- #include <grub/file.h>
- #include <grub/i18n.h>
- GRUB_MOD_LICENSE ("GPLv3+");
- /* IP port for the MTFTP server used for Intel's PXE */
- enum
- {
- MTFTP_SERVER_PORT = 75,
- MTFTP_CLIENT_PORT = 76,
- /* IP port for the TFTP server */
- TFTP_SERVER_PORT = 69
- };
- enum
- {
- TFTP_DEFAULTSIZE_PACKET = 512,
- };
- enum
- {
- TFTP_CODE_EOF = 1,
- TFTP_CODE_MORE = 2,
- TFTP_CODE_ERROR = 3,
- TFTP_CODE_BOOT = 4,
- TFTP_CODE_CFG = 5
- };
- enum
- {
- TFTP_RRQ = 1,
- TFTP_WRQ = 2,
- TFTP_DATA = 3,
- TFTP_ACK = 4,
- TFTP_ERROR = 5,
- TFTP_OACK = 6
- };
- enum
- {
- TFTP_EUNDEF = 0, /* not defined */
- TFTP_ENOTFOUND = 1, /* file not found */
- TFTP_EACCESS = 2, /* access violation */
- TFTP_ENOSPACE = 3, /* disk full or allocation exceeded */
- TFTP_EBADOP = 4, /* illegal TFTP operation */
- TFTP_EBADID = 5, /* unknown transfer ID */
- TFTP_EEXISTS = 6, /* file already exists */
- TFTP_ENOUSER = 7 /* no such user */
- };
- struct tftphdr {
- grub_uint16_t opcode;
- union {
- grub_int8_t rrq[TFTP_DEFAULTSIZE_PACKET];
- struct {
- grub_uint16_t block;
- grub_int8_t download[0];
- } data;
- struct {
- grub_uint16_t block;
- } ack;
- struct {
- grub_uint16_t errcode;
- grub_int8_t errmsg[TFTP_DEFAULTSIZE_PACKET];
- } err;
- struct {
- grub_int8_t data[TFTP_DEFAULTSIZE_PACKET+2];
- } oack;
- } u;
- } GRUB_PACKED ;
- typedef struct tftp_data
- {
- grub_uint64_t file_size;
- grub_uint64_t block;
- grub_uint32_t block_size;
- grub_uint64_t ack_sent;
- int have_oack;
- struct grub_error_saved save_err;
- grub_net_udp_socket_t sock;
- } *tftp_data_t;
- static grub_err_t
- ack (tftp_data_t data, grub_uint64_t block)
- {
- struct tftphdr *tftph_ack;
- grub_uint8_t nbdata[512];
- struct grub_net_buff nb_ack;
- grub_err_t err;
- nb_ack.head = nbdata;
- nb_ack.end = nbdata + sizeof (nbdata);
- grub_netbuff_clear (&nb_ack);
- grub_netbuff_reserve (&nb_ack, 512);
- err = grub_netbuff_push (&nb_ack, sizeof (tftph_ack->opcode)
- + sizeof (tftph_ack->u.ack.block));
- if (err)
- return err;
- tftph_ack = (struct tftphdr *) nb_ack.data;
- tftph_ack->opcode = grub_cpu_to_be16_compile_time (TFTP_ACK);
- tftph_ack->u.ack.block = grub_cpu_to_be16 (block);
- err = grub_net_send_udp_packet (data->sock, &nb_ack);
- if (err)
- return err;
- data->ack_sent = block;
- return GRUB_ERR_NONE;
- }
- static grub_err_t
- tftp_receive (grub_net_udp_socket_t sock __attribute__ ((unused)),
- struct grub_net_buff *nb,
- void *f)
- {
- grub_file_t file = f;
- struct tftphdr *tftph = (void *) nb->data;
- tftp_data_t data = file->data;
- grub_err_t err;
- grub_uint8_t *ptr;
- if (nb->tail - nb->data < (grub_ssize_t) sizeof (tftph->opcode))
- {
- grub_dprintf ("tftp", "TFTP packet too small\n");
- return GRUB_ERR_NONE;
- }
- tftph = (struct tftphdr *) nb->data;
- switch (grub_be_to_cpu16 (tftph->opcode))
- {
- case TFTP_OACK:
- data->block_size = TFTP_DEFAULTSIZE_PACKET;
- data->have_oack = 1;
- for (ptr = nb->data + sizeof (tftph->opcode); ptr < nb->tail;)
- {
- if (grub_memcmp (ptr, "tsize\0", sizeof ("tsize\0") - 1) == 0)
- data->file_size = grub_strtoul ((char *) ptr + sizeof ("tsize\0")
- - 1, 0, 0);
- if (grub_memcmp (ptr, "blksize\0", sizeof ("blksize\0") - 1) == 0)
- data->block_size = grub_strtoul ((char *) ptr + sizeof ("blksize\0")
- - 1, 0, 0);
- while (ptr < nb->tail && *ptr)
- ptr++;
- ptr++;
- }
- data->block = 0;
- grub_netbuff_free (nb);
- err = ack (data, 0);
- grub_error_save (&data->save_err);
- return GRUB_ERR_NONE;
- case TFTP_DATA:
- if (nb->tail - nb->data < (grub_ssize_t) (sizeof (tftph->opcode)
- + sizeof (tftph->u.data.block)))
- {
- grub_dprintf ("tftp", "TFTP packet too small\n");
- return GRUB_ERR_NONE;
- }
- /*
- * Ack old/retransmitted block.
- *
- * The block number is a 16-bit counter, thus the maximum file size that
- * could be transfered is 65535 * block size. Most TFTP hosts support to
- * roll-over the block counter to allow unlimited transfer file size.
- *
- * This behavior is not defined in the RFC 1350 [0] but is implemented by
- * most TFTP clients and hosts.
- *
- * [0]: https://tools.ietf.org/html/rfc1350
- */
- if (grub_be_to_cpu16 (tftph->u.data.block) < ((grub_uint16_t) (data->block + 1)))
- ack (data, grub_be_to_cpu16 (tftph->u.data.block));
- /* Ignore unexpected block. */
- else if (grub_be_to_cpu16 (tftph->u.data.block) > ((grub_uint16_t) (data->block + 1)))
- grub_dprintf ("tftp", "TFTP unexpected block # %d\n", tftph->u.data.block);
- else
- {
- unsigned size;
- if (file->device->net->packs.count < 50)
- {
- err = ack (data, data->block + 1);
- if (err)
- return err;
- }
- else
- file->device->net->stall = 1;
- err = grub_netbuff_pull (nb, sizeof (tftph->opcode) +
- sizeof (tftph->u.data.block));
- if (err)
- return err;
- size = nb->tail - nb->data;
- data->block++;
- if (size < data->block_size)
- {
- if (data->ack_sent < data->block)
- ack (data, data->block);
- file->device->net->eof = 1;
- file->device->net->stall = 1;
- grub_net_udp_close (data->sock);
- data->sock = NULL;
- }
- /*
- * Prevent garbage in broken cards. Is it still necessary
- * given that IP implementation has been fixed?
- */
- if (size > data->block_size)
- {
- err = grub_netbuff_unput (nb, size - data->block_size);
- if (err)
- return err;
- }
- /* If there is data, puts packet in socket list. */
- if ((nb->tail - nb->data) > 0)
- {
- grub_net_put_packet (&file->device->net->packs, nb);
- /* Do not free nb. */
- return GRUB_ERR_NONE;
- }
- }
- grub_netbuff_free (nb);
- return GRUB_ERR_NONE;
- case TFTP_ERROR:
- data->have_oack = 1;
- grub_error (GRUB_ERR_IO, "%s", tftph->u.err.errmsg);
- grub_error_save (&data->save_err);
- grub_netbuff_free (nb);
- return GRUB_ERR_NONE;
- default:
- grub_netbuff_free (nb);
- return GRUB_ERR_NONE;
- }
- }
- /*
- * Create a normalized copy of the filename. Compress any string of consecutive
- * forward slashes to a single forward slash.
- */
- static void
- grub_normalize_filename (char *normalized, const char *filename)
- {
- char *dest = normalized;
- const char *src = filename;
- while (*src != '\0')
- {
- if (src[0] == '/' && src[1] == '/')
- src++;
- else
- *dest++ = *src++;
- }
- *dest = '\0';
- }
- static grub_err_t
- tftp_open (struct grub_file *file, const char *filename)
- {
- struct tftphdr *tftph;
- char *rrq;
- int i;
- int rrqlen;
- int hdrlen;
- grub_uint8_t open_data[1500];
- struct grub_net_buff nb;
- tftp_data_t data;
- grub_err_t err;
- grub_uint8_t *nbd;
- grub_net_network_level_address_t addr;
- int port = file->device->net->port;
- data = grub_zalloc (sizeof (*data));
- if (!data)
- return grub_errno;
- nb.head = open_data;
- nb.end = open_data + sizeof (open_data);
- grub_netbuff_clear (&nb);
- grub_netbuff_reserve (&nb, 1500);
- err = grub_netbuff_push (&nb, sizeof (*tftph));
- if (err)
- {
- grub_free (data);
- return err;
- }
- tftph = (struct tftphdr *) nb.data;
- rrq = (char *) tftph->u.rrq;
- rrqlen = 0;
- tftph->opcode = grub_cpu_to_be16_compile_time (TFTP_RRQ);
- /*
- * Copy and normalize the filename to work-around issues on some TFTP
- * servers when file names are being matched for remapping.
- */
- grub_normalize_filename (rrq, filename);
- rrqlen += grub_strlen (rrq) + 1;
- rrq += grub_strlen (rrq) + 1;
- grub_strcpy (rrq, "octet");
- rrqlen += grub_strlen ("octet") + 1;
- rrq += grub_strlen ("octet") + 1;
- grub_strcpy (rrq, "blksize");
- rrqlen += grub_strlen ("blksize") + 1;
- rrq += grub_strlen ("blksize") + 1;
- grub_strcpy (rrq, "1024");
- rrqlen += grub_strlen ("1024") + 1;
- rrq += grub_strlen ("1024") + 1;
- grub_strcpy (rrq, "tsize");
- rrqlen += grub_strlen ("tsize") + 1;
- rrq += grub_strlen ("tsize") + 1;
- grub_strcpy (rrq, "0");
- rrqlen += grub_strlen ("0") + 1;
- rrq += grub_strlen ("0") + 1;
- hdrlen = sizeof (tftph->opcode) + rrqlen;
- err = grub_netbuff_unput (&nb, nb.tail - (nb.data + hdrlen));
- if (err)
- {
- grub_free (data);
- return err;
- }
- file->not_easily_seekable = 1;
- file->data = data;
- err = grub_net_resolve_address (file->device->net->server, &addr);
- if (err)
- {
- grub_dprintf ("tftp", "Address resolution failed: %d\n", err);
- grub_dprintf ("tftp", "file_size is %" PRIuGRUB_UINT64_T ", block_size is %" PRIuGRUB_UINT32_T "\n",
- data->file_size, data->block_size);
- grub_free (data);
- return err;
- }
- data->sock = grub_net_udp_open (addr,
- port ? port : TFTP_SERVER_PORT, tftp_receive,
- file);
- if (!data->sock)
- {
- grub_free (data);
- return grub_errno;
- }
- /* Receive OACK packet. */
- nbd = nb.data;
- for (i = 0; i < GRUB_NET_TRIES; i++)
- {
- nb.data = nbd;
- err = grub_net_send_udp_packet (data->sock, &nb);
- if (err)
- {
- grub_net_udp_close (data->sock);
- grub_free (data);
- return err;
- }
- grub_net_poll_cards (GRUB_NET_INTERVAL + (i * GRUB_NET_INTERVAL_ADDITION),
- &data->have_oack);
- if (data->have_oack)
- break;
- }
- if (!data->have_oack)
- grub_error (GRUB_ERR_TIMEOUT, N_("time out opening `%s'"), filename);
- else
- grub_error_load (&data->save_err);
- if (grub_errno)
- {
- grub_net_udp_close (data->sock);
- grub_free (data);
- file->data = NULL;
- return grub_errno;
- }
- file->size = data->file_size;
- return GRUB_ERR_NONE;
- }
- static grub_err_t
- tftp_close (struct grub_file *file)
- {
- tftp_data_t data = file->data;
- if (data->sock)
- {
- grub_uint8_t nbdata[512];
- grub_err_t err;
- struct grub_net_buff nb_err;
- struct tftphdr *tftph;
- nb_err.head = nbdata;
- nb_err.end = nbdata + sizeof (nbdata);
- grub_netbuff_clear (&nb_err);
- grub_netbuff_reserve (&nb_err, 512);
- err = grub_netbuff_push (&nb_err, sizeof (tftph->opcode)
- + sizeof (tftph->u.err.errcode)
- + sizeof ("closed"));
- if (!err)
- {
- tftph = (struct tftphdr *) nb_err.data;
- tftph->opcode = grub_cpu_to_be16_compile_time (TFTP_ERROR);
- tftph->u.err.errcode = grub_cpu_to_be16_compile_time (TFTP_EUNDEF);
- grub_memcpy (tftph->u.err.errmsg, "closed", sizeof ("closed"));
- err = grub_net_send_udp_packet (data->sock, &nb_err);
- }
- if (err)
- grub_print_error ();
- grub_net_udp_close (data->sock);
- }
- grub_free (data);
- file->data = NULL;
- return GRUB_ERR_NONE;
- }
- static grub_err_t
- tftp_packets_pulled (struct grub_file *file)
- {
- tftp_data_t data = file->data;
- if (file->device->net->packs.count >= 50)
- return 0;
- if (!file->device->net->eof)
- file->device->net->stall = 0;
- if (data->ack_sent >= data->block)
- return 0;
- return ack (data, data->block);
- }
- static struct grub_net_app_protocol grub_tftp_protocol =
- {
- .name = "tftp",
- .open = tftp_open,
- .close = tftp_close,
- .packets_pulled = tftp_packets_pulled
- };
- GRUB_MOD_INIT (tftp)
- {
- grub_net_app_level_register (&grub_tftp_protocol);
- }
- GRUB_MOD_FINI (tftp)
- {
- grub_net_app_level_unregister (&grub_tftp_protocol);
- }
|