blob: eb192d8fc9b6ff8f238246915bdf3797bf9b2b6b [file] [log] [blame]
Bernhard Reutner-Fischerdac7ff12006-04-12 17:55:51 +00001/* vi: set sw=4 ts=4: */
2/* -------------------------------------------------------------------------
3 * tftp.c
4 *
5 * A simple tftp client for busybox.
6 * Tries to follow RFC1350.
7 * Only "octet" mode supported.
8 * Optional blocksize negotiation (RFC2347 + RFC2348)
9 *
10 * Copyright (C) 2001 Magnus Damm <damm@opensource.se>
11 *
12 * Parts of the code based on:
13 *
14 * atftp: Copyright (C) 2000 Jean-Pierre Lefebvre <helix@step.polymtl.ca>
15 * and Remi Lefebvre <remi@debian.org>
16 *
17 * utftp: Copyright (C) 1999 Uwe Ohse <uwe@ohse.de>
18 *
19 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
20 * ------------------------------------------------------------------------- */
Mark Whitley450736c2001-03-02 19:08:50 +000021
Mark Whitley450736c2001-03-02 19:08:50 +000022#include "busybox.h"
23
Mark Whitley450736c2001-03-02 19:08:50 +000024
Denis Vlasenko31635552007-01-20 16:54:19 +000025#if ENABLE_FEATURE_TFTP_GET || ENABLE_FEATURE_TFTP_PUT
26
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +000027#define TFTP_BLOCKSIZE_DEFAULT 512 /* according to RFC 1350, don't change */
28#define TFTP_TIMEOUT 5 /* seconds */
29#define TFTP_NUM_RETRIES 5 /* number of retries */
30
Glenn L McGrathad117d82001-10-05 04:40:37 +000031/* opcodes we support */
Glenn L McGrathad117d82001-10-05 04:40:37 +000032#define TFTP_RRQ 1
33#define TFTP_WRQ 2
34#define TFTP_DATA 3
35#define TFTP_ACK 4
36#define TFTP_ERROR 5
37#define TFTP_OACK 6
38
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +000039static const char *const tftp_bb_error_msg[] = {
Mark Whitley450736c2001-03-02 19:08:50 +000040 "Undefined error",
41 "File not found",
42 "Access violation",
43 "Disk full or allocation error",
44 "Illegal TFTP operation",
45 "Unknown transfer ID",
46 "File already exists",
47 "No such user"
48};
49
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +000050#if ENABLE_FEATURE_TFTP_GET && !ENABLE_FEATURE_TFTP_PUT
51#define USE_GETPUT(a)
52#define CMD_GET(cmd) 1
53#define CMD_PUT(cmd) 0
54#elif !ENABLE_FEATURE_TFTP_GET && ENABLE_FEATURE_TFTP_PUT
55#define USE_GETPUT(a)
56#define CMD_GET(cmd) 0
57#define CMD_PUT(cmd) 1
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +000058#else
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +000059#define USE_GETPUT(a) a
60/* masks coming from getpot32 */
Denis Vlasenko31635552007-01-20 16:54:19 +000061#define CMD_GET(cmd) ((cmd) & 1)
62#define CMD_PUT(cmd) ((cmd) & 2)
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +000063#endif
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +000064/* NB: in the code below
65 * CMD_GET(cmd) and CMD_GET(cmd) are mutually exclusive
66 */
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +000067
Eric Andersen76fa8ea2001-08-20 17:47:49 +000068
Denis Vlasenko04291bc2006-11-21 10:15:25 +000069#if ENABLE_FEATURE_TFTP_BLOCKSIZE
Glenn L McGrathad117d82001-10-05 04:40:37 +000070
Eric Andersenc7bda1c2004-03-15 08:29:22 +000071static int tftp_blocksize_check(int blocksize, int bufsize)
Glenn L McGrathad117d82001-10-05 04:40:37 +000072{
Tim Rikerc1ef7bd2006-01-25 00:08:53 +000073 /* Check if the blocksize is valid:
Glenn L McGrathad117d82001-10-05 04:40:37 +000074 * RFC2348 says between 8 and 65464,
75 * but our implementation makes it impossible
76 * to use blocksizes smaller than 22 octets.
77 */
78
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +000079 if ((bufsize && (blocksize > bufsize))
80 || (blocksize < 8) || (blocksize > 65564)
81 ) {
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +000082 bb_error_msg("bad blocksize");
83 return 0;
Glenn L McGrathad117d82001-10-05 04:40:37 +000084 }
85
86 return blocksize;
87}
88
Bernhard Reutner-Fischer32bf1f92006-06-14 17:29:10 +000089static char *tftp_option_get(char *buf, int len, const char * const option)
Glenn L McGrathad117d82001-10-05 04:40:37 +000090{
Tim Rikerc1ef7bd2006-01-25 00:08:53 +000091 int opt_val = 0;
Glenn L McGrathad117d82001-10-05 04:40:37 +000092 int opt_found = 0;
93 int k;
Eric Andersenc7bda1c2004-03-15 08:29:22 +000094
Glenn L McGrathad117d82001-10-05 04:40:37 +000095 while (len > 0) {
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +000096 /* Make sure the options are terminated correctly */
Glenn L McGrathad117d82001-10-05 04:40:37 +000097
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +000098 for (k = 0; k < len; k++) {
99 if (buf[k] == '\0') {
100 break;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000101 }
102 }
103
104 if (k >= len) {
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000105 break;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000106 }
107
108 if (opt_val == 0) {
109 if (strcasecmp(buf, option) == 0) {
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000110 opt_found = 1;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000111 }
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000112 } else {
113 if (opt_found) {
Glenn L McGrathad117d82001-10-05 04:40:37 +0000114 return buf;
115 }
116 }
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000117
Glenn L McGrathad117d82001-10-05 04:40:37 +0000118 k++;
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000119
Glenn L McGrathad117d82001-10-05 04:40:37 +0000120 buf += k;
121 len -= k;
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000122
Glenn L McGrathad117d82001-10-05 04:40:37 +0000123 opt_val ^= 1;
124 }
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000125
Glenn L McGrathad117d82001-10-05 04:40:37 +0000126 return NULL;
127}
128
129#endif
130
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000131static int tftp(
132#if ENABLE_FEATURE_TFTP_GET && ENABLE_FEATURE_TFTP_PUT
133 const int cmd,
134#endif
135 const len_and_sockaddr *peer_lsa,
136 const char *remotefile, const int localfd,
Denis Vlasenko6536a9b2007-01-12 10:35:23 +0000137 unsigned port, int tftp_bufsize)
Mark Whitley450736c2001-03-02 19:08:50 +0000138{
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000139 struct timeval tv;
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000140 fd_set rfds;
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000141 int socketfd;
Bernhard Reutner-Fischer62f98562006-06-10 14:32:56 +0000142 int len;
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000143 int opcode = 0;
144 int finished = 0;
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000145 int timeout = TFTP_NUM_RETRIES;
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000146 uint16_t block_nr = 1;
147 uint16_t tmp;
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000148 char *cp;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000149
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000150 USE_FEATURE_TFTP_BLOCKSIZE(int want_option_ack = 0;)
Glenn L McGrathad117d82001-10-05 04:40:37 +0000151
Eric Andersen744a1942001-11-10 11:16:39 +0000152 /* Can't use RESERVE_CONFIG_BUFFER here since the allocation
153 * size varies meaning BUFFERS_GO_ON_STACK would fail */
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000154 /* We must keep the transmit and receive buffers seperate */
155 /* In case we rcv a garbage pkt and we need to rexmit the last pkt */
156 char *xbuf = xmalloc(tftp_bufsize += 4);
157 char *rbuf = xmalloc(tftp_bufsize);
Mark Whitley450736c2001-03-02 19:08:50 +0000158
Denis Vlasenko6536a9b2007-01-12 10:35:23 +0000159 port = htons(port);
160
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000161 socketfd = xsocket(peer_lsa->sa.sa_family, SOCK_DGRAM, 0);
Mark Whitley450736c2001-03-02 19:08:50 +0000162
163 /* build opcode */
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000164 opcode = TFTP_WRQ;
165 if (CMD_GET(cmd)) {
Glenn L McGrathad117d82001-10-05 04:40:37 +0000166 opcode = TFTP_RRQ;
Mark Whitley450736c2001-03-02 19:08:50 +0000167 }
Mark Whitley450736c2001-03-02 19:08:50 +0000168
169 while (1) {
170
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000171 cp = xbuf;
Mark Whitley450736c2001-03-02 19:08:50 +0000172
Glenn L McGrathad117d82001-10-05 04:40:37 +0000173 /* first create the opcode part */
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000174 *((uint16_t*)cp) = htons(opcode);
Mark Whitley450736c2001-03-02 19:08:50 +0000175 cp += 2;
176
177 /* add filename and mode */
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000178 if (CMD_GET(cmd) ? (opcode == TFTP_RRQ) : (opcode == TFTP_WRQ)) {
Tim Rikerc1ef7bd2006-01-25 00:08:53 +0000179 int too_long = 0;
Mark Whitley450736c2001-03-02 19:08:50 +0000180
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000181 /* see if the filename fits into xbuf
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000182 * and fill in packet. */
Glenn L McGrathad117d82001-10-05 04:40:37 +0000183 len = strlen(remotefile) + 1;
184
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000185 if ((cp + len) >= &xbuf[tftp_bufsize - 1]) {
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000186 too_long = 1;
187 } else {
188 safe_strncpy(cp, remotefile, len);
Glenn L McGrathad117d82001-10-05 04:40:37 +0000189 cp += len;
190 }
191
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000192 if (too_long || (&xbuf[tftp_bufsize - 1] - cp) < sizeof("octet")) {
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000193 bb_error_msg("remote filename too long");
Mark Whitley450736c2001-03-02 19:08:50 +0000194 break;
195 }
196
Glenn L McGrathad117d82001-10-05 04:40:37 +0000197 /* add "mode" part of the package */
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000198 memcpy(cp, "octet", sizeof("octet"));
199 cp += sizeof("octet");
Glenn L McGrathad117d82001-10-05 04:40:37 +0000200
Denis Vlasenko04291bc2006-11-21 10:15:25 +0000201#if ENABLE_FEATURE_TFTP_BLOCKSIZE
Glenn L McGrathad117d82001-10-05 04:40:37 +0000202
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000203 len = tftp_bufsize - 4; /* data block size */
Glenn L McGrathad117d82001-10-05 04:40:37 +0000204
205 if (len != TFTP_BLOCKSIZE_DEFAULT) {
206
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000207 if ((&xbuf[tftp_bufsize - 1] - cp) < 15) {
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000208 bb_error_msg("remote filename too long");
Glenn L McGrathad117d82001-10-05 04:40:37 +0000209 break;
210 }
211
212 /* add "blksize" + number of blocks */
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000213 memcpy(cp, "blksize", sizeof("blksize"));
214 cp += sizeof("blksize");
Glenn L McGrathad117d82001-10-05 04:40:37 +0000215 cp += snprintf(cp, 6, "%d", len) + 1;
216
217 want_option_ack = 1;
218 }
219#endif
Mark Whitley450736c2001-03-02 19:08:50 +0000220 }
221
222 /* add ack and data */
223
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000224 if (CMD_GET(cmd) ? (opcode == TFTP_ACK) : (opcode == TFTP_DATA)) {
225 *((uint16_t*)cp) = htons(block_nr);
Mark Whitley450736c2001-03-02 19:08:50 +0000226 cp += 2;
Mark Whitley450736c2001-03-02 19:08:50 +0000227 block_nr++;
228
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000229 if (CMD_PUT(cmd) && (opcode == TFTP_DATA)) {
Rob Landley53437472006-07-16 08:14:35 +0000230 len = full_read(localfd, cp, tftp_bufsize - 4);
Mark Whitley450736c2001-03-02 19:08:50 +0000231
232 if (len < 0) {
Bernhard Reutner-Fischer1b9d7c92006-06-03 22:45:37 +0000233 bb_perror_msg(bb_msg_read_error);
Mark Whitley450736c2001-03-02 19:08:50 +0000234 break;
235 }
236
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000237 if (len != (tftp_bufsize - 4)) {
Mark Whitley450736c2001-03-02 19:08:50 +0000238 finished++;
239 }
240
241 cp += len;
Mark Whitley450736c2001-03-02 19:08:50 +0000242 }
243 }
244
Mark Whitley450736c2001-03-02 19:08:50 +0000245 /* send packet */
246
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000247 timeout = TFTP_NUM_RETRIES; /* re-initialize */
Mark Whitley450736c2001-03-02 19:08:50 +0000248 do {
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000249 len = cp - xbuf;
Denis Vlasenko04291bc2006-11-21 10:15:25 +0000250#if ENABLE_DEBUG_TFTP
Glenn L McGrathd33278d2004-02-22 07:20:25 +0000251 fprintf(stderr, "sending %u bytes\n", len);
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000252 for (cp = xbuf; cp < &xbuf[len]; cp++)
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000253 fprintf(stderr, "%02x ", (unsigned char) *cp);
Glenn L McGrathd33278d2004-02-22 07:20:25 +0000254 fprintf(stderr, "\n");
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000255#endif
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000256 if (sendto(socketfd, xbuf, len, 0,
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000257 &peer_lsa->sa, peer_lsa->len) < 0) {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000258 bb_perror_msg("send");
Mark Whitley450736c2001-03-02 19:08:50 +0000259 len = -1;
260 break;
261 }
262
Eric Andersenb99aec02003-07-30 07:16:39 +0000263 if (finished && (opcode == TFTP_ACK)) {
Glenn L McGrath0f182712002-12-19 20:16:22 +0000264 break;
265 }
Mark Whitley450736c2001-03-02 19:08:50 +0000266
Glenn L McGrath0f182712002-12-19 20:16:22 +0000267 /* receive packet */
Denis Vlasenko2c916522007-01-12 14:57:37 +0000268 recv_again:
Glenn L McGrathad117d82001-10-05 04:40:37 +0000269 tv.tv_sec = TFTP_TIMEOUT;
Mark Whitley450736c2001-03-02 19:08:50 +0000270 tv.tv_usec = 0;
271
272 FD_ZERO(&rfds);
273 FD_SET(socketfd, &rfds);
274
Bernhard Reutner-Fischer62f98562006-06-10 14:32:56 +0000275 switch (select(socketfd + 1, &rfds, NULL, NULL, &tv)) {
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000276 struct sockaddr *from;
277 socklen_t fromlen;
Mark Whitley450736c2001-03-02 19:08:50 +0000278
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000279 case 1:
280 fromlen = peer_lsa->len;
281 from = alloca(fromlen);
282 memset(from, 0, fromlen);
283
284 len = recvfrom(socketfd, rbuf, tftp_bufsize, 0,
285 from, &fromlen);
Mark Whitley450736c2001-03-02 19:08:50 +0000286 if (len < 0) {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000287 bb_perror_msg("recvfrom");
Mark Whitley450736c2001-03-02 19:08:50 +0000288 break;
289 }
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000290#if ENABLE_FEATURE_IPV6
Denis Vlasenko2c916522007-01-12 14:57:37 +0000291 if (from->sa_family == AF_INET6)
292 if (((struct sockaddr_in6*)from)->sin6_port != port)
293 goto recv_again;
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000294#endif
Denis Vlasenko2c916522007-01-12 14:57:37 +0000295 if (from->sa_family == AF_INET)
296 if (((struct sockaddr_in*)from)->sin_port != port)
297 goto recv_again;
298 timeout = 0;
299 break;
Bernhard Reutner-Fischer62f98562006-06-10 14:32:56 +0000300 case 0:
Manuel Novoa III cad53642003-03-19 09:13:01 +0000301 bb_error_msg("timeout");
Eric Andersenb99aec02003-07-30 07:16:39 +0000302 timeout--;
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000303 if (timeout == 0) {
Mark Whitley450736c2001-03-02 19:08:50 +0000304 len = -1;
Manuel Novoa III cad53642003-03-19 09:13:01 +0000305 bb_error_msg("last timeout");
Mark Whitley450736c2001-03-02 19:08:50 +0000306 }
307 break;
Bernhard Reutner-Fischer62f98562006-06-10 14:32:56 +0000308 default:
Manuel Novoa III cad53642003-03-19 09:13:01 +0000309 bb_perror_msg("select");
Mark Whitley450736c2001-03-02 19:08:50 +0000310 len = -1;
311 }
312
313 } while (timeout && (len >= 0));
314
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000315 if (finished || (len < 0)) {
Mark Whitley450736c2001-03-02 19:08:50 +0000316 break;
317 }
318
319 /* process received packet */
320
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000321 opcode = ntohs( ((uint16_t*)rbuf)[0] );
322 tmp = ntohs( ((uint16_t*)rbuf)[1] );
Mark Whitley450736c2001-03-02 19:08:50 +0000323
Denis Vlasenko04291bc2006-11-21 10:15:25 +0000324#if ENABLE_DEBUG_TFTP
Glenn L McGrathd33278d2004-02-22 07:20:25 +0000325 fprintf(stderr, "received %d bytes: %04x %04x\n", len, opcode, tmp);
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000326#endif
Mark Whitley450736c2001-03-02 19:08:50 +0000327
Glenn L McGrathad117d82001-10-05 04:40:37 +0000328 if (opcode == TFTP_ERROR) {
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000329 const char *msg = NULL;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000330
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000331 if (rbuf[4] != '\0') {
332 msg = &rbuf[4];
333 rbuf[tftp_bufsize - 1] = '\0';
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000334 } else if (tmp < (sizeof(tftp_bb_error_msg)
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000335 / sizeof(char *))) {
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000336 msg = tftp_bb_error_msg[tmp];
Glenn L McGrathad117d82001-10-05 04:40:37 +0000337 }
338
339 if (msg) {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000340 bb_error_msg("server says: %s", msg);
Glenn L McGrathad117d82001-10-05 04:40:37 +0000341 }
342
343 break;
344 }
Denis Vlasenko04291bc2006-11-21 10:15:25 +0000345#if ENABLE_FEATURE_TFTP_BLOCKSIZE
Glenn L McGrathad117d82001-10-05 04:40:37 +0000346 if (want_option_ack) {
347
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000348 want_option_ack = 0;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000349
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000350 if (opcode == TFTP_OACK) {
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000351 /* server seems to support options */
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000352 char *res;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000353
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000354 res = tftp_option_get(&rbuf[2], len - 2, "blksize");
Glenn L McGrathad117d82001-10-05 04:40:37 +0000355
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000356 if (res) {
Denis Vlasenko13858992006-10-08 12:49:22 +0000357 int blksize = xatoi_u(res);
Tim Rikerc1ef7bd2006-01-25 00:08:53 +0000358
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000359 if (tftp_blocksize_check(blksize, tftp_bufsize - 4)) {
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000360 if (CMD_PUT(cmd)) {
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000361 opcode = TFTP_DATA;
362 } else {
363 opcode = TFTP_ACK;
364 }
Denis Vlasenko04291bc2006-11-21 10:15:25 +0000365#if ENABLE_DEBUG_TFTP
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000366 fprintf(stderr, "using blksize %u\n",
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000367 blksize);
Glenn L McGrathad117d82001-10-05 04:40:37 +0000368#endif
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000369 tftp_bufsize = blksize + 4;
370 block_nr = 0;
371 continue;
372 }
373 }
374 /* FIXME:
375 * we should send ERROR 8 */
376 bb_error_msg("bad server option");
377 break;
378 }
Glenn L McGrathad117d82001-10-05 04:40:37 +0000379
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000380 bb_error_msg("warning: blksize not supported by server"
381 " - reverting to 512");
Glenn L McGrathad117d82001-10-05 04:40:37 +0000382
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000383 tftp_bufsize = TFTP_BLOCKSIZE_DEFAULT + 4;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000384 }
385#endif
386
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000387 if (CMD_GET(cmd) && (opcode == TFTP_DATA)) {
Mark Whitley450736c2001-03-02 19:08:50 +0000388 if (tmp == block_nr) {
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000389 len = full_write(localfd, &rbuf[4], len - 4);
Mark Whitley450736c2001-03-02 19:08:50 +0000390
391 if (len < 0) {
Bernhard Reutner-Fischer1b9d7c92006-06-03 22:45:37 +0000392 bb_perror_msg(bb_msg_write_error);
Mark Whitley450736c2001-03-02 19:08:50 +0000393 break;
394 }
395
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000396 if (len != (tftp_bufsize - 4)) {
Mark Whitley450736c2001-03-02 19:08:50 +0000397 finished++;
398 }
399
Glenn L McGrathad117d82001-10-05 04:40:37 +0000400 opcode = TFTP_ACK;
Mark Whitley450736c2001-03-02 19:08:50 +0000401 continue;
402 }
Rob Landleyf3133c42005-06-07 02:40:39 +0000403 /* in case the last ack disappeared into the ether */
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000404 if (tmp == (block_nr - 1)) {
Rob Landleyf3133c42005-06-07 02:40:39 +0000405 --block_nr;
406 opcode = TFTP_ACK;
407 continue;
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000408// tmp==(block_nr-1) and (tmp+1)==block_nr is always same, I think. wtf?
Paul Fox1d4c88c2005-07-20 19:49:15 +0000409 } else if (tmp + 1 == block_nr) {
410 /* Server lost our TFTP_ACK. Resend it */
411 block_nr = tmp;
412 opcode = TFTP_ACK;
413 continue;
Rob Landleyf3133c42005-06-07 02:40:39 +0000414 }
Mark Whitley450736c2001-03-02 19:08:50 +0000415 }
416
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000417 if (CMD_PUT(cmd) && (opcode == TFTP_ACK)) {
418 if (tmp == (uint16_t) (block_nr - 1)) {
Mark Whitley450736c2001-03-02 19:08:50 +0000419 if (finished) {
420 break;
421 }
422
Glenn L McGrathad117d82001-10-05 04:40:37 +0000423 opcode = TFTP_DATA;
Mark Whitley450736c2001-03-02 19:08:50 +0000424 continue;
425 }
426 }
Mark Whitley450736c2001-03-02 19:08:50 +0000427 }
428
Denis Vlasenko2c916522007-01-12 14:57:37 +0000429 if (ENABLE_FEATURE_CLEAN_UP) {
430 close(socketfd);
431 free(xbuf);
432 free(rbuf);
433 }
Glenn L McGrathad117d82001-10-05 04:40:37 +0000434
Mark Whitley8bb7df42001-03-06 20:58:48 +0000435 return finished ? EXIT_SUCCESS : EXIT_FAILURE;
Mark Whitley450736c2001-03-02 19:08:50 +0000436}
437
438int tftp_main(int argc, char **argv)
439{
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000440 len_and_sockaddr *peer_lsa;
Glenn L McGrathd4004ee2004-09-14 17:24:59 +0000441 const char *localfile = NULL;
442 const char *remotefile = NULL;
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000443#if ENABLE_FEATURE_TFTP_BLOCKSIZE
444 const char *sblocksize = NULL;
445#endif
Glenn L McGrath036dbaa2004-01-17 05:03:31 +0000446 int port;
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000447 USE_GETPUT(int cmd;)
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000448 int fd = -1;
449 int flags = 0;
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000450 int result;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000451 int blocksize = TFTP_BLOCKSIZE_DEFAULT;
Mark Whitley450736c2001-03-02 19:08:50 +0000452
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000453 /* -p or -g is mandatory, and they are mutually exclusive */
454 opt_complementary = "" USE_FEATURE_TFTP_GET("g:") USE_FEATURE_TFTP_PUT("p:")
455 USE_GETPUT("?g--p:p--g");
Glenn L McGrathad117d82001-10-05 04:40:37 +0000456
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000457 USE_GETPUT(cmd =) getopt32(argc, argv,
458 USE_FEATURE_TFTP_GET("g") USE_FEATURE_TFTP_PUT("p")
459 "l:r:" USE_FEATURE_TFTP_BLOCKSIZE("b:"),
460 &localfile, &remotefile
461 USE_FEATURE_TFTP_BLOCKSIZE(, &sblocksize));
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000462
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000463 flags = O_RDONLY;
464 if (CMD_GET(cmd))
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000465 flags = O_WRONLY | O_CREAT | O_TRUNC;
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000466
Denis Vlasenko04291bc2006-11-21 10:15:25 +0000467#if ENABLE_FEATURE_TFTP_BLOCKSIZE
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000468 if (sblocksize) {
Denis Vlasenko13858992006-10-08 12:49:22 +0000469 blocksize = xatoi_u(sblocksize);
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000470 if (!tftp_blocksize_check(blocksize, 0)) {
471 return EXIT_FAILURE;
Mark Whitley450736c2001-03-02 19:08:50 +0000472 }
Mark Whitley450736c2001-03-02 19:08:50 +0000473 }
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000474#endif
Mark Whitley450736c2001-03-02 19:08:50 +0000475
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000476 if (localfile == NULL)
477 localfile = remotefile;
478 if (remotefile == NULL)
479 remotefile = localfile;
480 if ((localfile == NULL && remotefile == NULL) || (argv[optind] == NULL))
481 bb_show_usage();
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000482
Denis Vlasenko9f739442006-12-16 23:49:13 +0000483 if (localfile == NULL || LONE_DASH(localfile)) {
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000484 fd = CMD_GET(cmd) ? STDOUT_FILENO : STDIN_FILENO;
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000485 } else {
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000486 fd = xopen3(localfile, flags, 0644);
Mark Whitley450736c2001-03-02 19:08:50 +0000487 }
488
Glenn L McGrath036dbaa2004-01-17 05:03:31 +0000489 port = bb_lookup_port(argv[optind + 1], "udp", 69);
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000490 peer_lsa = host2sockaddr(argv[optind], port);
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000491
Denis Vlasenko04291bc2006-11-21 10:15:25 +0000492#if ENABLE_DEBUG_TFTP
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000493 fprintf(stderr, "using server \"%s\", "
494 "remotefile \"%s\", localfile \"%s\".\n",
495 xmalloc_sockaddr2dotted(&peer_lsa->sa, peer_lsa->len),
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000496 remotefile, localfile);
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000497#endif
498
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000499 result = tftp(
500#if ENABLE_FEATURE_TFTP_GET && ENABLE_FEATURE_TFTP_PUT
501 cmd,
502#endif
503 peer_lsa, remotefile, fd, port, blocksize);
Mark Whitley450736c2001-03-02 19:08:50 +0000504
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000505 if (fd > 1) {
Bernhard Reutner-Fischer32bf1f92006-06-14 17:29:10 +0000506 if (ENABLE_FEATURE_CLEAN_UP)
507 close(fd);
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000508 if (CMD_GET(cmd) && result != EXIT_SUCCESS)
Bernhard Reutner-Fischer32bf1f92006-06-14 17:29:10 +0000509 unlink(localfile);
Eric Andersena66a43e2002-04-13 09:30:25 +0000510 }
Denis Vlasenko000b9ba2006-10-05 23:12:49 +0000511 return result;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000512}
Denis Vlasenko31635552007-01-20 16:54:19 +0000513
514#endif /* ENABLE_FEATURE_TFTP_GET || ENABLE_FEATURE_TFTP_PUT */