blob: a2683971a87e6c88f9f6ef5f179b0841347efb2f [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
Denis Vlasenkob6adbf12007-05-26 19:00:18 +000022#include "libbb.h"
Mark Whitley450736c2001-03-02 19:08:50 +000023
Denis Vlasenko31635552007-01-20 16:54:19 +000024#if ENABLE_FEATURE_TFTP_GET || ENABLE_FEATURE_TFTP_PUT
25
Denis Vlasenko87f3b262007-09-07 13:43:28 +000026#define TFTP_BLOCKSIZE_DEFAULT 512 /* according to RFC 1350, don't change */
27#define TFTP_TIMEOUT_MS 50
28#define TFTP_MAXTIMEOUT_MS 2000
29#define TFTP_NUM_RETRIES 12 /* number of backed-off retries */
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +000030
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
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +000039#if ENABLE_FEATURE_TFTP_GET && !ENABLE_FEATURE_TFTP_PUT
Denis Vlasenkobf678d52007-05-09 12:50:08 +000040#define USE_GETPUT(...)
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +000041#define CMD_GET(cmd) 1
42#define CMD_PUT(cmd) 0
43#elif !ENABLE_FEATURE_TFTP_GET && ENABLE_FEATURE_TFTP_PUT
Denis Vlasenkobf678d52007-05-09 12:50:08 +000044#define USE_GETPUT(...)
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +000045#define CMD_GET(cmd) 0
46#define CMD_PUT(cmd) 1
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +000047#else
Denis Vlasenkobf678d52007-05-09 12:50:08 +000048#define USE_GETPUT(...) __VA_ARGS__
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +000049/* masks coming from getpot32 */
Denis Vlasenko31635552007-01-20 16:54:19 +000050#define CMD_GET(cmd) ((cmd) & 1)
51#define CMD_PUT(cmd) ((cmd) & 2)
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +000052#endif
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +000053/* NB: in the code below
Denis Vlasenkoa04561f2007-05-08 23:12:21 +000054 * CMD_GET(cmd) and CMD_PUT(cmd) are mutually exclusive
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +000055 */
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +000056
Eric Andersen76fa8ea2001-08-20 17:47:49 +000057
Denis Vlasenko04291bc2006-11-21 10:15:25 +000058#if ENABLE_FEATURE_TFTP_BLOCKSIZE
Glenn L McGrathad117d82001-10-05 04:40:37 +000059
Eric Andersenc7bda1c2004-03-15 08:29:22 +000060static int tftp_blocksize_check(int blocksize, int bufsize)
Glenn L McGrathad117d82001-10-05 04:40:37 +000061{
Tim Rikerc1ef7bd2006-01-25 00:08:53 +000062 /* Check if the blocksize is valid:
Glenn L McGrathad117d82001-10-05 04:40:37 +000063 * RFC2348 says between 8 and 65464,
64 * but our implementation makes it impossible
65 * to use blocksizes smaller than 22 octets.
66 */
67
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +000068 if ((bufsize && (blocksize > bufsize))
69 || (blocksize < 8) || (blocksize > 65564)
70 ) {
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +000071 bb_error_msg("bad blocksize");
72 return 0;
Glenn L McGrathad117d82001-10-05 04:40:37 +000073 }
74
75 return blocksize;
76}
77
Denis Vlasenkoa04561f2007-05-08 23:12:21 +000078static char *tftp_option_get(char *buf, int len, const char *option)
Glenn L McGrathad117d82001-10-05 04:40:37 +000079{
Tim Rikerc1ef7bd2006-01-25 00:08:53 +000080 int opt_val = 0;
Glenn L McGrathad117d82001-10-05 04:40:37 +000081 int opt_found = 0;
82 int k;
Eric Andersenc7bda1c2004-03-15 08:29:22 +000083
Glenn L McGrathad117d82001-10-05 04:40:37 +000084 while (len > 0) {
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +000085 /* Make sure the options are terminated correctly */
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +000086 for (k = 0; k < len; k++) {
87 if (buf[k] == '\0') {
Denis Vlasenkoa04561f2007-05-08 23:12:21 +000088 goto nul_found;
Glenn L McGrathad117d82001-10-05 04:40:37 +000089 }
90 }
Denis Vlasenkoa04561f2007-05-08 23:12:21 +000091 return NULL;
92 nul_found:
Glenn L McGrathad117d82001-10-05 04:40:37 +000093 if (opt_val == 0) {
94 if (strcasecmp(buf, option) == 0) {
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +000095 opt_found = 1;
Glenn L McGrathad117d82001-10-05 04:40:37 +000096 }
Denis Vlasenkoa04561f2007-05-08 23:12:21 +000097 } else if (opt_found) {
98 return buf;
Glenn L McGrathad117d82001-10-05 04:40:37 +000099 }
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000100
Glenn L McGrathad117d82001-10-05 04:40:37 +0000101 k++;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000102 buf += k;
103 len -= k;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000104 opt_val ^= 1;
105 }
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000106
Glenn L McGrathad117d82001-10-05 04:40:37 +0000107 return NULL;
108}
109
110#endif
111
Denis Vlasenkobf678d52007-05-09 12:50:08 +0000112static int tftp( USE_GETPUT(const int cmd,)
Denis Vlasenko0850cda2007-02-07 23:20:32 +0000113 len_and_sockaddr *peer_lsa,
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000114 const char *remotefile, const int localfd,
Denis Vlasenko6536a9b2007-01-12 10:35:23 +0000115 unsigned port, int tftp_bufsize)
Mark Whitley450736c2001-03-02 19:08:50 +0000116{
Denis Vlasenko87f3b262007-09-07 13:43:28 +0000117 struct pollfd pfd[1];
118#define socketfd (pfd[0].fd)
Bernhard Reutner-Fischer62f98562006-06-10 14:32:56 +0000119 int len;
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000120 int send_len;
121 USE_FEATURE_TFTP_BLOCKSIZE(smallint want_option_ack = 0;)
122 smallint finished = 0;
123 uint16_t opcode;
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000124 uint16_t block_nr = 1;
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000125 uint16_t recv_blk;
Denis Vlasenko87f3b262007-09-07 13:43:28 +0000126 int retries, waittime_ms;
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000127 char *cp;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000128
Denis Vlasenko0850cda2007-02-07 23:20:32 +0000129 unsigned org_port;
130 len_and_sockaddr *const from = alloca(offsetof(len_and_sockaddr, sa) + peer_lsa->len);
131
Eric Andersen744a1942001-11-10 11:16:39 +0000132 /* Can't use RESERVE_CONFIG_BUFFER here since the allocation
133 * size varies meaning BUFFERS_GO_ON_STACK would fail */
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000134 /* We must keep the transmit and receive buffers seperate */
135 /* In case we rcv a garbage pkt and we need to rexmit the last pkt */
136 char *xbuf = xmalloc(tftp_bufsize += 4);
137 char *rbuf = xmalloc(tftp_bufsize);
Mark Whitley450736c2001-03-02 19:08:50 +0000138
Denis Vlasenko0850cda2007-02-07 23:20:32 +0000139 port = org_port = htons(port);
Denis Vlasenko6536a9b2007-01-12 10:35:23 +0000140
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000141 socketfd = xsocket(peer_lsa->sa.sa_family, SOCK_DGRAM, 0);
Mark Whitley450736c2001-03-02 19:08:50 +0000142
143 /* build opcode */
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000144 opcode = TFTP_WRQ;
145 if (CMD_GET(cmd)) {
Glenn L McGrathad117d82001-10-05 04:40:37 +0000146 opcode = TFTP_RRQ;
Mark Whitley450736c2001-03-02 19:08:50 +0000147 }
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000148 cp = xbuf + 2;
149 /* add filename and mode */
150 /* fill in packet if the filename fits into xbuf */
151 len = strlen(remotefile) + 1;
152 if (2 + len + sizeof("octet") >= tftp_bufsize) {
153 bb_error_msg("remote filename is too long");
154 goto ret;
155 }
156 strcpy(cp, remotefile);
157 cp += len;
158 /* add "mode" part of the package */
159 strcpy(cp, "octet");
160 cp += sizeof("octet");
Glenn L McGrathad117d82001-10-05 04:40:37 +0000161
Denis Vlasenko04291bc2006-11-21 10:15:25 +0000162#if ENABLE_FEATURE_TFTP_BLOCKSIZE
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000163 len = tftp_bufsize - 4; /* data block size */
164 if (len != TFTP_BLOCKSIZE_DEFAULT) {
165 /* rfc2348 says that 65464 is a max allowed value */
166 if ((&xbuf[tftp_bufsize - 1] - cp) < sizeof("blksize NNNNN")) {
167 bb_error_msg("remote filename is too long");
168 goto ret;
169 }
170 /* add "blksize", <nul>, blocksize */
171 strcpy(cp, "blksize");
172 cp += sizeof("blksize");
173 cp += snprintf(cp, 6, "%d", len) + 1;
174 want_option_ack = 1;
175 }
Glenn L McGrathad117d82001-10-05 04:40:37 +0000176#endif
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000177 /* First packet is built, so skip packet generation */
178 goto send_pkt;
Mark Whitley450736c2001-03-02 19:08:50 +0000179
Denis Vlasenkobf678d52007-05-09 12:50:08 +0000180 /* Using mostly goto's - continue/break will be less clear
181 * in where we actually jump to */
182
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000183 while (1) {
184 /* Build ACK or DATA */
185 cp = xbuf + 2;
186 *((uint16_t*)cp) = htons(block_nr);
187 cp += 2;
188 block_nr++;
189 opcode = TFTP_ACK;
190 if (CMD_PUT(cmd)) {
191 opcode = TFTP_DATA;
192 len = full_read(localfd, cp, tftp_bufsize - 4);
193 if (len < 0) {
194 bb_perror_msg(bb_msg_read_error);
195 goto ret;
Mark Whitley450736c2001-03-02 19:08:50 +0000196 }
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000197 if (len != (tftp_bufsize - 4)) {
198 finished = 1;
199 }
200 cp += len;
Mark Whitley450736c2001-03-02 19:08:50 +0000201 }
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000202 send_pkt:
203 /* Send packet */
204 *((uint16_t*)xbuf) = htons(opcode); /* fill in opcode part */
Denis Vlasenkobf678d52007-05-09 12:50:08 +0000205 send_len = cp - xbuf;
206 /* NB: send_len value is preserved in code below
207 * for potential resend */
Paul Fox40f0bcf2007-09-06 17:52:22 +0000208
209 retries = TFTP_NUM_RETRIES; /* re-initialize */
Denis Vlasenko87f3b262007-09-07 13:43:28 +0000210 waittime_ms = TFTP_TIMEOUT_MS;
Paul Fox40f0bcf2007-09-06 17:52:22 +0000211
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000212 send_again:
Denis Vlasenko04291bc2006-11-21 10:15:25 +0000213#if ENABLE_DEBUG_TFTP
Denis Vlasenkobf678d52007-05-09 12:50:08 +0000214 fprintf(stderr, "sending %u bytes\n", send_len);
215 for (cp = xbuf; cp < &xbuf[send_len]; cp++)
216 fprintf(stderr, "%02x ", (unsigned char) *cp);
217 fprintf(stderr, "\n");
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000218#endif
Denis Vlasenkobf678d52007-05-09 12:50:08 +0000219 xsendto(socketfd, xbuf, send_len, &peer_lsa->sa, peer_lsa->len);
220 /* Was it final ACK? then exit */
221 if (finished && (opcode == TFTP_ACK))
222 goto ret;
Mark Whitley450736c2001-03-02 19:08:50 +0000223
Denis Vlasenko2c916522007-01-12 14:57:37 +0000224 recv_again:
Denis Vlasenkobf678d52007-05-09 12:50:08 +0000225 /* Receive packet */
Denis Vlasenko87f3b262007-09-07 13:43:28 +0000226 /*pfd[0].fd = socketfd;*/
227 pfd[0].events = POLLIN;
Denis Vlasenko5d61e712007-09-27 10:09:59 +0000228 switch (safe_poll(pfd, 1, waittime_ms)) {
Denis Vlasenkobf678d52007-05-09 12:50:08 +0000229 unsigned from_port;
230 case 1:
231 from->len = peer_lsa->len;
232 memset(&from->sa, 0, peer_lsa->len);
233 len = recvfrom(socketfd, rbuf, tftp_bufsize, 0,
234 &from->sa, &from->len);
235 if (len < 0) {
236 bb_perror_msg("recvfrom");
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000237 goto ret;
Mark Whitley450736c2001-03-02 19:08:50 +0000238 }
Denis Vlasenkobf678d52007-05-09 12:50:08 +0000239 from_port = get_nport(&from->sa);
240 if (port == org_port) {
241 /* Our first query went to port 69
242 * but reply will come from different one.
243 * Remember and use this new port */
244 port = from_port;
245 set_nport(peer_lsa, from_port);
246 }
247 if (port != from_port)
248 goto recv_again;
249 goto process_pkt;
250 case 0:
Paul Fox40f0bcf2007-09-06 17:52:22 +0000251 retries--;
252 if (retries == 0) {
253 bb_error_msg("timeout");
Denis Vlasenkobf678d52007-05-09 12:50:08 +0000254 goto ret;
255 }
Paul Fox40f0bcf2007-09-06 17:52:22 +0000256
257 /* exponential backoff with limit */
Denis Vlasenko87f3b262007-09-07 13:43:28 +0000258 waittime_ms += waittime_ms/2;
259 if (waittime_ms > TFTP_MAXTIMEOUT_MS) {
260 waittime_ms = TFTP_MAXTIMEOUT_MS;
Paul Fox40f0bcf2007-09-06 17:52:22 +0000261 }
262
Denis Vlasenkobf678d52007-05-09 12:50:08 +0000263 goto send_again; /* resend last sent pkt */
264 default:
Denis Vlasenko5d61e712007-09-27 10:09:59 +0000265 /*bb_perror_msg("poll"); - done in safe_poll */
Denis Vlasenkobf678d52007-05-09 12:50:08 +0000266 goto ret;
267 }
268 process_pkt:
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000269 /* Process recv'ed packet */
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000270 opcode = ntohs( ((uint16_t*)rbuf)[0] );
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000271 recv_blk = ntohs( ((uint16_t*)rbuf)[1] );
Mark Whitley450736c2001-03-02 19:08:50 +0000272
Denis Vlasenko04291bc2006-11-21 10:15:25 +0000273#if ENABLE_DEBUG_TFTP
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000274 fprintf(stderr, "received %d bytes: %04x %04x\n", len, opcode, recv_blk);
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000275#endif
Mark Whitley450736c2001-03-02 19:08:50 +0000276
Glenn L McGrathad117d82001-10-05 04:40:37 +0000277 if (opcode == TFTP_ERROR) {
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000278 static const char *const errcode_str[] = {
279 "",
280 "file not found",
281 "access violation",
282 "disk full",
283 "illegal TFTP operation",
284 "unknown transfer id",
285 "file already exists",
286 "no such user",
Denis Vlasenko6ca409e2007-08-12 20:58:27 +0000287 "bad option"
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000288 };
Denis Vlasenko80b8b392007-06-25 10:55:35 +0000289
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000290 const char *msg = "";
Glenn L McGrathad117d82001-10-05 04:40:37 +0000291
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000292 if (rbuf[4] != '\0') {
293 msg = &rbuf[4];
294 rbuf[tftp_bufsize - 1] = '\0';
Denis Vlasenko80b8b392007-06-25 10:55:35 +0000295 } else if (recv_blk < ARRAY_SIZE(errcode_str)) {
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000296 msg = errcode_str[recv_blk];
Glenn L McGrathad117d82001-10-05 04:40:37 +0000297 }
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000298 bb_error_msg("server error: (%u) %s", recv_blk, msg);
299 goto ret;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000300 }
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000301
Denis Vlasenko04291bc2006-11-21 10:15:25 +0000302#if ENABLE_FEATURE_TFTP_BLOCKSIZE
Glenn L McGrathad117d82001-10-05 04:40:37 +0000303 if (want_option_ack) {
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000304 want_option_ack = 0;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000305
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000306 if (opcode == TFTP_OACK) {
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000307 /* server seems to support options */
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000308 char *res;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000309
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000310 res = tftp_option_get(&rbuf[2], len - 2, "blksize");
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000311 if (res) {
Denis Vlasenko13858992006-10-08 12:49:22 +0000312 int blksize = xatoi_u(res);
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000313 if (!tftp_blocksize_check(blksize, tftp_bufsize - 4)) {
Denis Vlasenkobf678d52007-05-09 12:50:08 +0000314 /* send ERROR 8 to server... */
315 /* htons can be impossible to use in const initializer: */
316 /*static const uint16_t error_8[2] = { htons(TFTP_ERROR), htons(8) };*/
317 /* thus we open-code big-endian layout */
Denis Vlasenko6ca409e2007-08-12 20:58:27 +0000318 static const uint8_t error_8[4] = { 0,TFTP_ERROR, 0,8 };
Denis Vlasenkobf678d52007-05-09 12:50:08 +0000319 xsendto(socketfd, error_8, 4, &peer_lsa->sa, peer_lsa->len);
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000320 bb_error_msg("server proposes bad blksize %d, exiting", blksize);
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000321 goto ret;
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000322 }
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000323#if ENABLE_DEBUG_TFTP
324 fprintf(stderr, "using blksize %u\n",
325 blksize);
326#endif
327 tftp_bufsize = blksize + 4;
Denis Vlasenkobf678d52007-05-09 12:50:08 +0000328 /* Send ACK for OACK ("block" no: 0) */
329 block_nr = 0;
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000330 continue;
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000331 }
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000332 /* rfc2347:
333 * "An option not acknowledged by the server
334 * must be ignored by the client and server
335 * as if it were never requested." */
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000336 }
Glenn L McGrathad117d82001-10-05 04:40:37 +0000337
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000338 bb_error_msg("blksize is not supported by server"
339 " - reverting to 512");
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000340 tftp_bufsize = TFTP_BLOCKSIZE_DEFAULT + 4;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000341 }
342#endif
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000343 /* block_nr is already advanced to next block# we expect
344 * to get / block# we are about to send next time */
Glenn L McGrathad117d82001-10-05 04:40:37 +0000345
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000346 if (CMD_GET(cmd) && (opcode == TFTP_DATA)) {
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000347 if (recv_blk == block_nr) {
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000348 len = full_write(localfd, &rbuf[4], len - 4);
Mark Whitley450736c2001-03-02 19:08:50 +0000349 if (len < 0) {
Bernhard Reutner-Fischer1b9d7c92006-06-03 22:45:37 +0000350 bb_perror_msg(bb_msg_write_error);
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000351 goto ret;
Mark Whitley450736c2001-03-02 19:08:50 +0000352 }
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000353 if (len != (tftp_bufsize - 4)) {
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000354 finished = 1;
Mark Whitley450736c2001-03-02 19:08:50 +0000355 }
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000356 continue; /* send ACK */
Mark Whitley450736c2001-03-02 19:08:50 +0000357 }
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000358 if (recv_blk == (block_nr - 1)) {
Paul Fox1d4c88c2005-07-20 19:49:15 +0000359 /* Server lost our TFTP_ACK. Resend it */
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000360 block_nr = recv_blk;
Paul Fox1d4c88c2005-07-20 19:49:15 +0000361 continue;
Denis Vlasenko4b924f32007-05-30 00:29:55 +0000362 }
Mark Whitley450736c2001-03-02 19:08:50 +0000363 }
364
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000365 if (CMD_PUT(cmd) && (opcode == TFTP_ACK)) {
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000366 /* did server ACK our last DATA pkt? */
367 if (recv_blk == (uint16_t) (block_nr - 1)) {
368 if (finished)
369 goto ret;
370 continue; /* send next block */
Mark Whitley450736c2001-03-02 19:08:50 +0000371 }
372 }
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000373 /* Awww... recv'd packet is not recognized! */
374 goto recv_again;
375 /* why recv_again? - rfc1123 says:
376 * "The sender (i.e., the side originating the DATA packets)
377 * must never resend the current DATA packet on receipt
378 * of a duplicate ACK".
379 * DATA pkts are resent ONLY on timeout.
380 * Thus "goto send_again" will ba a bad mistake above.
381 * See:
382 * http://en.wikipedia.org/wiki/Sorcerer's_Apprentice_Syndrome
383 */
Mark Whitley450736c2001-03-02 19:08:50 +0000384 }
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000385 ret:
Denis Vlasenko2c916522007-01-12 14:57:37 +0000386 if (ENABLE_FEATURE_CLEAN_UP) {
387 close(socketfd);
388 free(xbuf);
389 free(rbuf);
390 }
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000391 return finished == 0; /* returns 1 on failure */
Mark Whitley450736c2001-03-02 19:08:50 +0000392}
393
Denis Vlasenko9b49a5e2007-10-11 10:05:36 +0000394int tftp_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Mark Whitley450736c2001-03-02 19:08:50 +0000395int tftp_main(int argc, char **argv)
396{
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000397 len_and_sockaddr *peer_lsa;
Glenn L McGrathd4004ee2004-09-14 17:24:59 +0000398 const char *localfile = NULL;
399 const char *remotefile = NULL;
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000400#if ENABLE_FEATURE_TFTP_BLOCKSIZE
401 const char *sblocksize = NULL;
402#endif
Glenn L McGrath036dbaa2004-01-17 05:03:31 +0000403 int port;
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000404 USE_GETPUT(int cmd;)
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000405 int fd = -1;
406 int flags = 0;
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000407 int result;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000408 int blocksize = TFTP_BLOCKSIZE_DEFAULT;
Mark Whitley450736c2001-03-02 19:08:50 +0000409
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000410 /* -p or -g is mandatory, and they are mutually exclusive */
411 opt_complementary = "" USE_FEATURE_TFTP_GET("g:") USE_FEATURE_TFTP_PUT("p:")
412 USE_GETPUT("?g--p:p--g");
Glenn L McGrathad117d82001-10-05 04:40:37 +0000413
Denis Vlasenkofe7cd642007-08-18 15:32:12 +0000414 USE_GETPUT(cmd =) getopt32(argv,
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000415 USE_FEATURE_TFTP_GET("g") USE_FEATURE_TFTP_PUT("p")
416 "l:r:" USE_FEATURE_TFTP_BLOCKSIZE("b:"),
417 &localfile, &remotefile
418 USE_FEATURE_TFTP_BLOCKSIZE(, &sblocksize));
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000419 argv += optind;
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000420
Denis Vlasenko8e9ccba2007-01-11 16:50:23 +0000421 flags = O_RDONLY;
422 if (CMD_GET(cmd))
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000423 flags = O_WRONLY | O_CREAT | O_TRUNC;
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000424
Denis Vlasenko04291bc2006-11-21 10:15:25 +0000425#if ENABLE_FEATURE_TFTP_BLOCKSIZE
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000426 if (sblocksize) {
Denis Vlasenko13858992006-10-08 12:49:22 +0000427 blocksize = xatoi_u(sblocksize);
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000428 if (!tftp_blocksize_check(blocksize, 0)) {
429 return EXIT_FAILURE;
Mark Whitley450736c2001-03-02 19:08:50 +0000430 }
Mark Whitley450736c2001-03-02 19:08:50 +0000431 }
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000432#endif
Mark Whitley450736c2001-03-02 19:08:50 +0000433
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000434 if (!localfile)
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000435 localfile = remotefile;
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000436 if (!remotefile)
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000437 remotefile = localfile;
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000438 /* Error if filename or host is not known */
439 if (!remotefile || !argv[0])
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000440 bb_show_usage();
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000441
Denis Vlasenkobf678d52007-05-09 12:50:08 +0000442 fd = CMD_GET(cmd) ? STDOUT_FILENO : STDIN_FILENO;
443 if (!LONE_DASH(localfile)) {
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000444 fd = xopen(localfile, flags);
Mark Whitley450736c2001-03-02 19:08:50 +0000445 }
446
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000447 port = bb_lookup_port(argv[1], "udp", 69);
448 peer_lsa = xhost2sockaddr(argv[0], port);
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000449
Denis Vlasenko04291bc2006-11-21 10:15:25 +0000450#if ENABLE_DEBUG_TFTP
Denis Vlasenkobf678d52007-05-09 12:50:08 +0000451 fprintf(stderr, "using server '%s', remotefile '%s', localfile '%s'\n",
Denis Vlasenkoa27a11b2007-08-18 14:16:39 +0000452 xmalloc_sockaddr2dotted(&peer_lsa->sa),
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000453 remotefile, localfile);
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000454#endif
455
Denis Vlasenkobf678d52007-05-09 12:50:08 +0000456 result = tftp( USE_GETPUT(cmd,) peer_lsa, remotefile, fd, port, blocksize);
Mark Whitley450736c2001-03-02 19:08:50 +0000457
Denis Vlasenkoa04561f2007-05-08 23:12:21 +0000458 if (ENABLE_FEATURE_CLEAN_UP)
459 close(fd);
460 if (result != EXIT_SUCCESS && !LONE_DASH(localfile) && CMD_GET(cmd)) {
461 unlink(localfile);
Eric Andersena66a43e2002-04-13 09:30:25 +0000462 }
Denis Vlasenko000b9ba2006-10-05 23:12:49 +0000463 return result;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000464}
Denis Vlasenko31635552007-01-20 16:54:19 +0000465
466#endif /* ENABLE_FEATURE_TFTP_GET || ENABLE_FEATURE_TFTP_PUT */