X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=tftp.c;h=1e0dd736801769e2716dee164161ea396f2d5dce;hb=b3ba1c016b8fb2c970da9aae3fcb48fb4c255c94;hp=9d6ae51e0d3b3456674349338dcf1adcbc0c2c96;hpb=39b00333253c80698843e81e3fe4a14ed21af709;p=oweals%2Fnmrpflash.git diff --git a/tftp.c b/tftp.c index 9d6ae51..1e0dd73 100644 --- a/tftp.c +++ b/tftp.c @@ -1,19 +1,19 @@ /** - * nmrp-flash - Netgear Unbrick Utility + * nmrpflash - Netgear Unbrick Utility * Copyright (C) 2016 Joseph Lehner * - * nmrp-flash is free software: you can redistribute it and/or modify + * nmrpflash 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. * - * nmrp-flash is distributed in the hope that it will be useful, + * nmrpflash 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 nmrp-flash. If not, see . + * along with nmrpflash. If not, see . * */ @@ -26,10 +26,14 @@ #include #include "nmrpd.h" -#define TFTP_PKT_SIZE 516 +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +#define TFTP_BLKSIZE 1456 static const char *opcode_names[] = { - "RRQ", "WRQ", "DATA", "ACK", "ERR" + "RRQ", "WRQ", "DATA", "ACK", "ERR", "OACK" }; enum tftp_opcode { @@ -37,31 +41,16 @@ enum tftp_opcode { WRQ = 2, DATA = 3, ACK = 4, - ERR = 5 + ERR = 5, + OACK = 6 }; -static const char *leafname(const char *path) -{ - const char *slash, *bslash; - - slash = strrchr(path, '/'); - bslash = strrchr(path, '\\'); - - if (slash && bslash) { - path = 1 + (slash > bslash ? slash : bslash); - } else if (slash) { - path = 1 + slash; - } else if (bslash) { - path = 1 + bslash; - } - - return path; -} - static bool is_netascii(const char *str) { - for (; *str; ++str) { - if (*str < 0x20 || *str > 0x7f) { + uint8_t *p = (uint8_t*)str; + + for (; *p; ++p) { + if (*p < 0x20 || *p > 0x7f) { return false; } } @@ -69,9 +58,10 @@ static bool is_netascii(const char *str) return true; } -static inline void pkt_mknum(char *pkt, uint16_t n) +static inline char *pkt_mknum(char *pkt, uint16_t n) { *(uint16_t*)pkt = htons(n); + return pkt + 2; } static inline uint16_t pkt_num(char *pkt) @@ -79,27 +69,93 @@ static inline uint16_t pkt_num(char *pkt) return ntohs(*(uint16_t*)pkt); } -static void pkt_mkwrq(char *pkt, const char *filename) +static char *pkt_mkopt(char *pkt, const char *opt, const char* val) +{ + strcpy(pkt, opt); + pkt += strlen(opt) + 1; + strcpy(pkt, val); + pkt += strlen(val) + 1; + return pkt; +} + +static bool pkt_nextstr(char **pkt, char **str, size_t *rem) +{ + size_t len; + + if (!isprint(**pkt) || !(len = strnlen(*pkt, *rem))) { + return false; + } else if (str) { + *str = *pkt; + } + + *pkt += len + 1; + + if (*rem > 1) { + *rem -= len + 1; + } else { + *rem = 0; + } + + return true; +} + +static bool pkt_nextopt(char **pkt, char **opt, char **val, size_t *rem) +{ + return pkt_nextstr(pkt, opt, rem) && pkt_nextstr(pkt, val, rem); +} + +static char *pkt_optval(char* pkt, const char* name) +{ + size_t rem = 512; + char *opt, *val; + pkt += 2; + + while (pkt_nextopt(&pkt, &opt, &val, &rem)) { + if (!strcasecmp(name, opt)) { + return val; + } + } + + return NULL; +} + +static size_t pkt_xrqlen(char *pkt) { - size_t len = 2; + size_t rem = 512; + + pkt += 2; + while (pkt_nextopt(&pkt, NULL, NULL, &rem)) { + ; + } + return 514 - rem; +} + +static void pkt_mkwrq(char *pkt, const char *filename, unsigned blksize) +{ filename = leafname(filename); - if (!is_netascii(filename) || strlen(filename) > 500) { - fprintf(stderr, "Overlong/illegal filename; using 'firmware.bin'."); - filename = "firmware.bin"; + if (!tftp_is_valid_filename(filename)) { + fprintf(stderr, "Overlong/illegal filename; using 'firmware'.\n"); + filename = "firmware"; + } else if (!strcmp(filename, "-")) { + filename = "firmware"; } - pkt_mknum(pkt, WRQ); + pkt = pkt_mknum(pkt, WRQ); + pkt = pkt_mkopt(pkt, filename, "octet"); - strcpy(pkt + len, filename); - len += strlen(filename) + 1; - strcpy(pkt + len, "octet"); + if (blksize && blksize != 512) { + pkt = pkt_mkopt(pkt, "blksize", lltostr(blksize, 10)); + } } static inline void pkt_print(char *pkt, FILE *fp) { uint16_t opcode = pkt_num(pkt); - if (!opcode || opcode > ERR) { + size_t rem; + char *opt, *val; + + if (!opcode || opcode > OACK) { fprintf(fp, "(%d)", opcode); } else { fprintf(fp, "%s", opcode_names[opcode - 1]); @@ -107,12 +163,20 @@ static inline void pkt_print(char *pkt, FILE *fp) fprintf(fp, "(%d)", pkt_num(pkt + 2)); } else if (opcode == WRQ || opcode == RRQ) { fprintf(fp, "(%s, %s)", pkt + 2, pkt + 2 + strlen(pkt + 2) + 1); + } else if (opcode == OACK) { + fprintf(fp, "("); + rem = 512; + pkt += 2; + while (pkt_nextopt(&pkt, &opt, &val, &rem)) { + fprintf(fp, " %s=%s ", opt, val); + } + fprintf(fp, ")"); } } } static ssize_t tftp_recvfrom(int sock, char *pkt, uint16_t* port, - unsigned timeout) + unsigned timeout, size_t pktlen) { ssize_t len; struct sockaddr_in src; @@ -130,7 +194,7 @@ static ssize_t tftp_recvfrom(int sock, char *pkt, uint16_t* port, } alen = sizeof(src); - len = recvfrom(sock, pkt, TFTP_PKT_SIZE, 0, (struct sockaddr*)&src, &alen); + len = recvfrom(sock, pkt, pktlen, 0, (struct sockaddr*)&src, &alen); if (len < 0) { sock_perror("recvfrom"); return -1; @@ -149,12 +213,18 @@ static ssize_t tftp_recvfrom(int sock, char *pkt, uint16_t* port, * at offset 0. The limit of 32 chars is arbitrary. */ fprintf(stderr, "Error: %.32s\n", pkt); - return -3; - } else if (!opcode || opcode > ERR) { + return -2; + } else if (!opcode || opcode > OACK) { fprintf(stderr, "Received invalid packet: "); pkt_print(pkt, stderr); fprintf(stderr, ".\n"); - return -2; + return -1; + } + + if (verbosity > 2) { + printf(">> "); + pkt_print(pkt, stdout); + printf("\n"); } return len; @@ -168,8 +238,8 @@ static ssize_t tftp_sendto(int sock, char *pkt, size_t len, switch (pkt_num(pkt)) { case RRQ: case WRQ: - len = 2 + strlen(pkt + 2) + 1; - len += strlen(pkt + len) + 1; + case OACK: + len = pkt_xrqlen(pkt); break; case DATA: len += 4; @@ -187,6 +257,12 @@ static ssize_t tftp_sendto(int sock, char *pkt, size_t len, return -1; } + if (verbosity > 2) { + printf("<< "); + pkt_print(pkt, stdout); + printf("\n"); + } + sent = sendto(sock, pkt, len, 0, (struct sockaddr*)dst, sizeof(*dst)); if (sent < 0) { sock_perror("sendto"); @@ -195,36 +271,75 @@ static ssize_t tftp_sendto(int sock, char *pkt, size_t len, return sent; } +const char *leafname(const char *path) +{ + if (!path) { + return NULL; + } + + const char *slash, *bslash; + + slash = strrchr(path, '/'); + bslash = strrchr(path, '\\'); + + if (slash && bslash) { + path = 1 + (slash > bslash ? slash : bslash); + } else if (slash) { + path = 1 + slash; + } else if (bslash) { + path = 1 + bslash; + } + + return path; +} + #ifdef NMRPFLASH_WINDOWS void sock_perror(const char *msg) { win_perror2(msg, WSAGetLastError()); } -#else -inline void sock_perror(const char *msg) +#endif + +inline bool tftp_is_valid_filename(const char *filename) { - perror(msg); + return strlen(filename) <= 255 && is_netascii(filename); } -#endif int tftp_put(struct nmrpd_args *args) { struct sockaddr_in addr; - uint16_t block, port; + uint16_t block, port, op, blksize; ssize_t len, last_len; - int fd, sock, ret, timeout; - char rx[TFTP_PKT_SIZE], tx[TFTP_PKT_SIZE]; + int fd, sock, ret, timeouts, errors, ackblock; + char rx[2048], tx[2048]; + const char *file_remote = args->file_remote; + char *val, *end; + bool freeze_block = false; sock = -1; ret = -1; + fd = -1; - fd = open(args->filename, O_RDONLY); - if (fd < 0) { - perror("open"); - ret = fd; + if (g_interrupted) { goto cleanup; } + if (!strcmp(args->file_local, "-")) { + fd = STDIN_FILENO; + if (!file_remote) { + file_remote = "firmware"; + } + } else { + fd = open(args->file_local, O_RDONLY | O_BINARY); + if (fd < 0) { + xperror("open"); + ret = fd; + goto cleanup; + } else if (!file_remote) { + file_remote = args->file_local; + } + } + sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sock < 0) { sock_perror("socket"); @@ -232,35 +347,73 @@ int tftp_put(struct nmrpd_args *args) goto cleanup; } + memset(&addr, 0, sizeof(addr)); + + addr.sin_family = AF_INET; + + if (args->ipaddr_intf) { + if ((addr.sin_addr.s_addr = inet_addr(args->ipaddr_intf)) == INADDR_NONE) { + xperror("inet_addr"); + goto cleanup; + } + + if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) != 0) { + sock_perror("bind"); + goto cleanup; + } + } + if ((addr.sin_addr.s_addr = inet_addr(args->ipaddr)) == INADDR_NONE) { - perror("inet_addr"); + xperror("inet_addr"); goto cleanup; } - - addr.sin_family = AF_INET; addr.sin_port = htons(args->port); + blksize = 512; block = 0; last_len = -1; len = 0; + errors = 0; /* Not really, but this way the loop sends our WRQ before receiving */ - timeout = 1; + timeouts = 1; + + pkt_mkwrq(tx, file_remote, TFTP_BLKSIZE); + + while (!g_interrupted) { + ackblock = -1; + op = pkt_num(rx); + + if (!timeouts) { + if (op == ACK) { + ackblock = pkt_num(rx + 2); + } else if (op == OACK) { + ackblock = 0; + if ((val = pkt_optval(rx, "blksize"))) { + blksize = strtol(val, &end, 10); + if (*end != '\0' || blksize < 8 || blksize > TFTP_BLKSIZE) { + fprintf(stderr, "Error: invalid blksize in OACK: %s\n", val); + ret = -1; + goto cleanup; + } + } + } + } - pkt_mkwrq(tx, args->filename); + if (timeouts || ackblock == block) { + if (!timeouts) { + if (!freeze_block) { + ++block; + } - do { - if (timeout || (pkt_num(rx) == ACK && pkt_num(rx + 2) == block)) { - if (!timeout) { - ++block; pkt_mknum(tx, DATA); pkt_mknum(tx + 2, block); - len = read(fd, tx + 4, 512); + len = read(fd, tx + 4, blksize); if (len < 0) { - perror("read"); + xperror("read"); ret = len; goto cleanup; } else if (!len) { - if (last_len != 512) { + if (last_len != blksize && last_len != -1) { break; } } @@ -272,23 +425,46 @@ int tftp_put(struct nmrpd_args *args) if (ret < 0) { goto cleanup; } - } else if (pkt_num(rx) != ACK) { - fprintf(stderr, "Expected ACK(%d), got ", block); - pkt_print(rx, stderr); - fprintf(stderr, "!\n"); + } else if ((op != OACK && op != ACK) || ackblock > block) { + if (verbosity) { + fprintf(stderr, "Expected ACK(%d), got ", block); + pkt_print(rx, stderr); + fprintf(stderr, ".\n"); + } + + if (ackblock != -1 && ++errors > 5) { + if (ackblock == UINT16_MAX && block == 0 && !freeze_block) { + /* work around the 32 MiB limit if block rollover is not + * supported, by transmitting all remaining packets as + * block #65535 - reported working on a Netgear D7000. + */ + block = UINT16_MAX; + freeze_block = true; + errors = 0; + printf("Transmitting rest of file as block %d.\n", block); + } else { + fprintf(stderr, "Protocol error; bailing out.\n"); + ret = -1; + goto cleanup; + } + } } - ret = tftp_recvfrom(sock, rx, &port, args->rx_timeout); + ret = tftp_recvfrom(sock, rx, &port, args->rx_timeout, blksize + 4); if (ret < 0) { goto cleanup; } else if (!ret) { - if (++timeout < 5) { + if (++timeouts < 5 || (!block && timeouts < 10)) { continue; + } else if (block) { + fprintf(stderr, "Timeout while waiting for ACK(%d).\n", block); + } else { + fprintf(stderr, "Timeout while waiting for ACK(0)/OACK.\n"); } - fprintf(stderr, "Timeout while waiting for ACK(%d).\n", block); + ret = -1; goto cleanup; } else { - timeout = 0; + timeouts = 0; ret = 0; if (!block && port != args->port) { @@ -298,9 +474,9 @@ int tftp_put(struct nmrpd_args *args) addr.sin_port = htons(port); } } - } while(1); + } - ret = 0; + ret = !g_interrupted ? 0 : -1; cleanup: if (fd >= 0) {