diff options
author | Jannis Pohlmann <jannis.pohlmann@codethink.co.uk> | 2012-12-22 11:51:38 +0000 |
---|---|---|
committer | Jannis Pohlmann <jannis.pohlmann@codethink.co.uk> | 2012-12-22 11:51:38 +0000 |
commit | 8821237240c5374d83298b2da5ad88fa1e3c1ef7 (patch) | |
tree | e1bc03fa83c61165f48ffe3f528ac5496332872a /com32/libupload/upload_tftp.c | |
parent | 38dcca25c8855c95649e3f0b5b09fae862ed5c7a (diff) | |
parent | 7307d60063ee4303da4de45f9d984fdc8df92146 (diff) | |
download | syslinux-8821237240c5374d83298b2da5ad88fa1e3c1ef7.tar.gz |
Merge remote-tracking branch 'remotes/upstream/master' into baserock/morph
Diffstat (limited to 'com32/libupload/upload_tftp.c')
-rw-r--r-- | com32/libupload/upload_tftp.c | 204 |
1 files changed, 204 insertions, 0 deletions
diff --git a/com32/libupload/upload_tftp.c b/com32/libupload/upload_tftp.c new file mode 100644 index 00000000..5e73c1c5 --- /dev/null +++ b/com32/libupload/upload_tftp.c @@ -0,0 +1,204 @@ +/* + * TFTP data output backend + */ + +#include <string.h> +#include <stdio.h> +#include <syslinux/pxe.h> +#include <syslinux/config.h> +#include <netinet/in.h> +#include <sys/times.h> +#include "upload_backend.h" + +enum tftp_opcode { + TFTP_RRQ = 1, + TFTP_WRQ = 2, + TFTP_DATA = 3, + TFTP_ACK = 4, + TFTP_ERROR = 5, +}; + +struct tftp_error { + uint16_t opcode; + uint16_t errcode; + char errmsg[0]; +} __attribute__ (( packed )); + +struct tftp_state { + uint32_t my_ip; + uint32_t srv_ip; + uint32_t srv_gw; + uint16_t my_port; + uint16_t srv_port; + uint16_t seq; +}; + +const char *tftp_string_error_message[]={ +"", +"File not found", +"Access Denied", +"Disk Full", +"Illegal Operation", +"Unknown Transfert ID", +"File already exists", +"Unknown User", +"Negociation failed", +"Unable to resolve hostname", // not in RFC +"Unable to connect", // not in RFC +"No Error", +}; + +#define RCV_BUF 2048 + +static int send_ack_packet(struct tftp_state *tftp, + const void *pkt, size_t len) +{ + com32sys_t ireg, oreg; + t_PXENV_UDP_WRITE *uw; + t_PXENV_UDP_READ *ur; + clock_t start; + static const clock_t timeouts[] = { + 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, 0 + }; + const clock_t *timeout; + int err = -1; + + uw = lmalloc(sizeof *uw + len); + ur = lmalloc(sizeof *ur + RCV_BUF); + + memset(&ireg, 0, sizeof ireg); + ireg.eax.w[0] = 0x0009; + + for (timeout = timeouts ; *timeout ; timeout++) { + memset(uw, 0, sizeof *uw); + memcpy(uw+1, pkt, len); + uw->ip = tftp->srv_ip; + uw->gw = tftp->srv_gw; + uw->src_port = tftp->my_port; + uw->dst_port = tftp->srv_port ? tftp->srv_port : htons(69); + uw->buffer_size = len; + uw->buffer = FAR_PTR(uw+1); + + ireg.ebx.w[0] = PXENV_UDP_WRITE; + ireg.es = SEG(uw); + ireg.edi.w[0] = OFFS(uw); + + __intcall(0x22, &ireg, &oreg); + + start = times(NULL); + + do { + memset(ur, 0, sizeof *ur); + ur->src_ip = tftp->srv_ip; + ur->dest_ip = tftp->my_ip; + ur->s_port = tftp->srv_port; + ur->d_port = tftp->my_port; + ur->buffer_size = RCV_BUF; + ur->buffer = FAR_PTR(ur+1); + + ireg.ebx.w[0] = PXENV_UDP_READ; + ireg.es = SEG(ur); + ireg.edi.w[0] = OFFS(ur); + __intcall(0x22, &ireg, &oreg); + + if (!(oreg.eflags.l & EFLAGS_CF) && + ur->status == PXENV_STATUS_SUCCESS && + tftp->srv_ip == ur->src_ip && + (tftp->srv_port == 0 || + tftp->srv_port == ur->s_port)) { + uint16_t *xb = (uint16_t *)(ur+1); + if (ntohs(xb[0]) == TFTP_ACK && + ntohs(xb[1]) == tftp->seq) { + tftp->srv_port = ur->s_port; + err = TFTP_OK; /* All good! */ + goto done; + } else if (ntohs(xb[0]) == TFTP_ERROR) { + struct tftp_error *te = (struct tftp_error *)(ur+1); + if (te->errcode == TFTP_ERR_UNKNOWN_ERROR) { + tftp_string_error_message[TFTP_ERR_UNKNOWN_ERROR]=strdup(te->errmsg); + } + err=-ntohs(te->errcode); // Return the associated error code + goto done; + } + } + } while ((clock_t)(times(NULL) - start) < *timeout); + } + +done: + lfree(ur); + lfree(uw); + + return err; +} + +static int upload_tftp_write(struct upload_backend *be) +{ + static uint16_t local_port = 0x4000; + struct tftp_state tftp; + char buffer[512+4+6]; + int nlen; + int err=TFTP_OK; + const union syslinux_derivative_info *sdi = + syslinux_derivative_info(); + const char *data = be->outbuf; + size_t len = be->zbytes; + size_t chunk; + + tftp.my_ip = sdi->pxe.myip; + tftp.my_port = htons(local_port++); + tftp.srv_gw = ((tftp.srv_ip ^ tftp.my_ip) & sdi->pxe.ipinfo->netmask) + ? sdi->pxe.ipinfo->gateway : 0; + tftp.srv_port = 0; + tftp.seq = 0; + + if (be->argv[1]) { + tftp.srv_ip = pxe_dns(be->argv[1]); + if (!tftp.srv_ip) { +// printf("\nUnable to resolve hostname: %s\n", be->argv[1]); + return -TFTP_ERR_UNABLE_TO_RESOLVE; + } + } else { + tftp.srv_ip = sdi->pxe.ipinfo->serverip; + if (!tftp.srv_ip) { +// printf("\nNo server IP address\n"); + return -TFTP_ERR_UNABLE_TO_CONNECT; + } + } + +/* printf("server %u.%u.%u.%u... ", + ((uint8_t *)&tftp.srv_ip)[0], + ((uint8_t *)&tftp.srv_ip)[1], + ((uint8_t *)&tftp.srv_ip)[2], + ((uint8_t *)&tftp.srv_ip)[3]);*/ + + buffer[0] = 0; + buffer[1] = TFTP_WRQ; + nlen = strlcpy(buffer+2, be->argv[0], 512); + memcpy(buffer+3+nlen, "octet", 6); + + if ((err=send_ack_packet(&tftp, buffer, 2+nlen+1+6))!=TFTP_OK) + return err; + + do { + chunk = len >= 512 ? 512 : len; + + buffer[1] = TFTP_DATA; + *((uint16_t *)(buffer+2)) = htons(++tftp.seq); + memcpy(buffer+4, data, chunk); + data += chunk; + len -= chunk; + + if ((err=send_ack_packet(&tftp, buffer, chunk+4))!=TFTP_OK) + return err; + } while (chunk == 512); + + return TFTP_OK; +} + +struct upload_backend upload_tftp = { + .name = "tftp", + .helpmsg = "filename [tftp_server]", + .minargs = 1, + .write = upload_tftp_write, +}; |