| /* |
| * 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) |
| { |
| 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); |
| |
| 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); |
| |
| pxe_call(PXENV_UDP_WRITE, uw); |
| |
| 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); |
| |
| err = pxe_call(PXENV_UDP_READ, ur); |
| |
| if (!err && 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, |
| }; |