blob: 42fd9d2cac73c114cbbd0b9286e32458e1c807bb [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
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +000025#define TFTP_BLOCKSIZE_DEFAULT 512 /* according to RFC 1350, don't change */
26#define TFTP_TIMEOUT 5 /* seconds */
27#define TFTP_NUM_RETRIES 5 /* number of retries */
28
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +000029static const char * const MODE_OCTET = "octet";
30#define MODE_OCTET_LEN 6 /* sizeof(MODE_OCTET)*/
31
32static const char * const OPTION_BLOCKSIZE = "blksize";
33#define OPTION_BLOCKSIZE_LEN 8 /* sizeof(OPTION_BLOCKSIZE) */
Glenn L McGrathad117d82001-10-05 04:40:37 +000034
35/* opcodes we support */
Glenn L McGrathad117d82001-10-05 04:40:37 +000036#define TFTP_RRQ 1
37#define TFTP_WRQ 2
38#define TFTP_DATA 3
39#define TFTP_ACK 4
40#define TFTP_ERROR 5
41#define TFTP_OACK 6
42
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +000043static const char *const tftp_bb_error_msg[] = {
Mark Whitley450736c2001-03-02 19:08:50 +000044 "Undefined error",
45 "File not found",
46 "Access violation",
47 "Disk full or allocation error",
48 "Illegal TFTP operation",
49 "Unknown transfer ID",
50 "File already exists",
51 "No such user"
52};
53
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +000054#define tftp_cmd_get ENABLE_FEATURE_TFTP_GET
55
56#if ENABLE_FEATURE_TFTP_PUT
57# define tftp_cmd_put (tftp_cmd_get+ENABLE_FEATURE_TFTP_PUT)
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +000058#else
59# define tftp_cmd_put 0
60#endif
61
Eric Andersen76fa8ea2001-08-20 17:47:49 +000062
Eric Andersenbdfd0d72001-10-24 05:00:29 +000063#ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
Glenn L McGrathad117d82001-10-05 04:40:37 +000064
Eric Andersenc7bda1c2004-03-15 08:29:22 +000065static int tftp_blocksize_check(int blocksize, int bufsize)
Glenn L McGrathad117d82001-10-05 04:40:37 +000066{
Tim Rikerc1ef7bd2006-01-25 00:08:53 +000067 /* Check if the blocksize is valid:
Glenn L McGrathad117d82001-10-05 04:40:37 +000068 * RFC2348 says between 8 and 65464,
69 * but our implementation makes it impossible
70 * to use blocksizes smaller than 22 octets.
71 */
72
Tim Rikerc1ef7bd2006-01-25 00:08:53 +000073 if ((bufsize && (blocksize > bufsize)) ||
Rob Landley14d70652006-06-18 15:23:13 +000074 (blocksize < 8) || (blocksize > 65564)) {
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +000075 bb_error_msg("bad blocksize");
76 return 0;
Glenn L McGrathad117d82001-10-05 04:40:37 +000077 }
78
79 return blocksize;
80}
81
Bernhard Reutner-Fischer32bf1f92006-06-14 17:29:10 +000082static char *tftp_option_get(char *buf, int len, const char * const option)
Glenn L McGrathad117d82001-10-05 04:40:37 +000083{
Tim Rikerc1ef7bd2006-01-25 00:08:53 +000084 int opt_val = 0;
Glenn L McGrathad117d82001-10-05 04:40:37 +000085 int opt_found = 0;
86 int k;
Eric Andersenc7bda1c2004-03-15 08:29:22 +000087
Glenn L McGrathad117d82001-10-05 04:40:37 +000088 while (len > 0) {
89
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +000090 /* Make sure the options are terminated correctly */
Glenn L McGrathad117d82001-10-05 04:40:37 +000091
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +000092 for (k = 0; k < len; k++) {
93 if (buf[k] == '\0') {
94 break;
Glenn L McGrathad117d82001-10-05 04:40:37 +000095 }
96 }
97
98 if (k >= len) {
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +000099 break;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000100 }
101
102 if (opt_val == 0) {
103 if (strcasecmp(buf, option) == 0) {
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000104 opt_found = 1;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000105 }
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000106 } else {
107 if (opt_found) {
Glenn L McGrathad117d82001-10-05 04:40:37 +0000108 return buf;
109 }
110 }
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000111
Glenn L McGrathad117d82001-10-05 04:40:37 +0000112 k++;
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000113
Glenn L McGrathad117d82001-10-05 04:40:37 +0000114 buf += k;
115 len -= k;
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000116
Glenn L McGrathad117d82001-10-05 04:40:37 +0000117 opt_val ^= 1;
118 }
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000119
Glenn L McGrathad117d82001-10-05 04:40:37 +0000120 return NULL;
121}
122
123#endif
124
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000125static int tftp(const int cmd, const struct hostent *host,
126 const char *remotefile, const int localfd,
127 const unsigned short port, int tftp_bufsize)
Mark Whitley450736c2001-03-02 19:08:50 +0000128{
129 struct sockaddr_in sa;
Mark Whitley450736c2001-03-02 19:08:50 +0000130 struct sockaddr_in from;
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000131 struct timeval tv;
Mark Whitley450736c2001-03-02 19:08:50 +0000132 socklen_t fromlen;
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000133 fd_set rfds;
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000134 int socketfd;
Bernhard Reutner-Fischer62f98562006-06-10 14:32:56 +0000135 int len;
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000136 int opcode = 0;
137 int finished = 0;
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000138 int timeout = TFTP_NUM_RETRIES;
Glenn L McGrathc6997782004-02-22 03:33:53 +0000139 unsigned short block_nr = 1;
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000140 unsigned short tmp;
141 char *cp;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000142
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000143 USE_FEATURE_TFTP_BLOCKSIZE(int want_option_ack = 0;)
Glenn L McGrathad117d82001-10-05 04:40:37 +0000144
Eric Andersen744a1942001-11-10 11:16:39 +0000145 /* Can't use RESERVE_CONFIG_BUFFER here since the allocation
146 * size varies meaning BUFFERS_GO_ON_STACK would fail */
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000147 char *buf=xmalloc(tftp_bufsize += 4);
Mark Whitley450736c2001-03-02 19:08:50 +0000148
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000149 if ((socketfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
Rob Landleyd921b2e2006-08-03 15:41:12 +0000150 /* need to unlink the localfile, so don't use xsocket here. */
Manuel Novoa III cad53642003-03-19 09:13:01 +0000151 bb_perror_msg("socket");
Mark Whitley8bb7df42001-03-06 20:58:48 +0000152 return EXIT_FAILURE;
Mark Whitley450736c2001-03-02 19:08:50 +0000153 }
154
155 len = sizeof(sa);
156
157 memset(&sa, 0, len);
Rob Landleyd921b2e2006-08-03 15:41:12 +0000158 xbind(socketfd, (struct sockaddr *)&sa, len);
Mark Whitley450736c2001-03-02 19:08:50 +0000159
160 sa.sin_family = host->h_addrtype;
Glenn L McGrath036dbaa2004-01-17 05:03:31 +0000161 sa.sin_port = port;
Mark Whitley450736c2001-03-02 19:08:50 +0000162 memcpy(&sa.sin_addr, (struct in_addr *) host->h_addr,
163 sizeof(sa.sin_addr));
164
165 /* build opcode */
Bernhard Reutner-Fischer32bf1f92006-06-14 17:29:10 +0000166 if (cmd & tftp_cmd_get) {
Glenn L McGrathad117d82001-10-05 04:40:37 +0000167 opcode = TFTP_RRQ;
Mark Whitley450736c2001-03-02 19:08:50 +0000168 }
Bernhard Reutner-Fischer32bf1f92006-06-14 17:29:10 +0000169 if (cmd & tftp_cmd_put) {
Glenn L McGrathad117d82001-10-05 04:40:37 +0000170 opcode = TFTP_WRQ;
Mark Whitley450736c2001-03-02 19:08:50 +0000171 }
172
173 while (1) {
174
Mark Whitley450736c2001-03-02 19:08:50 +0000175 cp = buf;
176
Glenn L McGrathad117d82001-10-05 04:40:37 +0000177 /* first create the opcode part */
Mark Whitley450736c2001-03-02 19:08:50 +0000178 *((unsigned short *) cp) = htons(opcode);
Mark Whitley450736c2001-03-02 19:08:50 +0000179 cp += 2;
180
181 /* add filename and mode */
Bernhard Reutner-Fischer32bf1f92006-06-14 17:29:10 +0000182 if (((cmd & tftp_cmd_get) && (opcode == TFTP_RRQ)) ||
183 ((cmd & tftp_cmd_put) && (opcode == TFTP_WRQ)))
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000184 {
Tim Rikerc1ef7bd2006-01-25 00:08:53 +0000185 int too_long = 0;
Mark Whitley450736c2001-03-02 19:08:50 +0000186
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000187 /* see if the filename fits into buf
188 * and fill in packet. */
Glenn L McGrathad117d82001-10-05 04:40:37 +0000189 len = strlen(remotefile) + 1;
190
191 if ((cp + len) >= &buf[tftp_bufsize - 1]) {
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000192 too_long = 1;
193 } else {
194 safe_strncpy(cp, remotefile, len);
Glenn L McGrathad117d82001-10-05 04:40:37 +0000195 cp += len;
196 }
197
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000198 if (too_long || ((&buf[tftp_bufsize - 1] - cp) < MODE_OCTET_LEN)) {
199 bb_error_msg("remote filename too long");
Mark Whitley450736c2001-03-02 19:08:50 +0000200 break;
201 }
202
Glenn L McGrathad117d82001-10-05 04:40:37 +0000203 /* add "mode" part of the package */
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000204 memcpy(cp, MODE_OCTET, MODE_OCTET_LEN);
205 cp += MODE_OCTET_LEN;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000206
Eric Andersenbdfd0d72001-10-24 05:00:29 +0000207#ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
Glenn L McGrathad117d82001-10-05 04:40:37 +0000208
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000209 len = tftp_bufsize - 4; /* data block size */
Glenn L McGrathad117d82001-10-05 04:40:37 +0000210
211 if (len != TFTP_BLOCKSIZE_DEFAULT) {
212
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000213 if ((&buf[tftp_bufsize - 1] - cp) < 15) {
214 bb_error_msg("remote filename too long");
Glenn L McGrathad117d82001-10-05 04:40:37 +0000215 break;
216 }
217
218 /* add "blksize" + number of blocks */
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000219 memcpy(cp, OPTION_BLOCKSIZE, OPTION_BLOCKSIZE_LEN);
220 cp += OPTION_BLOCKSIZE_LEN;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000221 cp += snprintf(cp, 6, "%d", len) + 1;
222
223 want_option_ack = 1;
224 }
225#endif
Mark Whitley450736c2001-03-02 19:08:50 +0000226 }
227
228 /* add ack and data */
229
Bernhard Reutner-Fischer32bf1f92006-06-14 17:29:10 +0000230 if (((cmd & tftp_cmd_get) && (opcode == TFTP_ACK)) ||
231 ((cmd & tftp_cmd_put) && (opcode == TFTP_DATA))) {
Mark Whitley450736c2001-03-02 19:08:50 +0000232
233 *((unsigned short *) cp) = htons(block_nr);
234
235 cp += 2;
236
237 block_nr++;
238
Bernhard Reutner-Fischer32bf1f92006-06-14 17:29:10 +0000239 if ((cmd & tftp_cmd_put) && (opcode == TFTP_DATA)) {
Rob Landley53437472006-07-16 08:14:35 +0000240 len = full_read(localfd, cp, tftp_bufsize - 4);
Mark Whitley450736c2001-03-02 19:08:50 +0000241
242 if (len < 0) {
Bernhard Reutner-Fischer1b9d7c92006-06-03 22:45:37 +0000243 bb_perror_msg(bb_msg_read_error);
Mark Whitley450736c2001-03-02 19:08:50 +0000244 break;
245 }
246
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000247 if (len != (tftp_bufsize - 4)) {
Mark Whitley450736c2001-03-02 19:08:50 +0000248 finished++;
249 }
250
251 cp += len;
Mark Whitley450736c2001-03-02 19:08:50 +0000252 }
253 }
254
255
256 /* send packet */
257
258
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000259 timeout = TFTP_NUM_RETRIES; /* re-initialize */
Mark Whitley450736c2001-03-02 19:08:50 +0000260 do {
261
262 len = cp - buf;
263
Bernhard Reutner-Fischer32bf1f92006-06-14 17:29:10 +0000264#ifdef CONFIG_DEBUG_TFTP
Glenn L McGrathd33278d2004-02-22 07:20:25 +0000265 fprintf(stderr, "sending %u bytes\n", len);
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000266 for (cp = buf; cp < &buf[len]; cp++)
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000267 fprintf(stderr, "%02x ", (unsigned char) *cp);
Glenn L McGrathd33278d2004-02-22 07:20:25 +0000268 fprintf(stderr, "\n");
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000269#endif
Mark Whitley450736c2001-03-02 19:08:50 +0000270 if (sendto(socketfd, buf, len, 0,
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000271 (struct sockaddr *) &sa, sizeof(sa)) < 0) {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000272 bb_perror_msg("send");
Mark Whitley450736c2001-03-02 19:08:50 +0000273 len = -1;
274 break;
275 }
276
277
Eric Andersenb99aec02003-07-30 07:16:39 +0000278 if (finished && (opcode == TFTP_ACK)) {
Glenn L McGrath0f182712002-12-19 20:16:22 +0000279 break;
280 }
Mark Whitley450736c2001-03-02 19:08:50 +0000281
Glenn L McGrath0f182712002-12-19 20:16:22 +0000282 /* receive packet */
Mark Whitley450736c2001-03-02 19:08:50 +0000283
284 memset(&from, 0, sizeof(from));
285 fromlen = sizeof(from);
286
Glenn L McGrathad117d82001-10-05 04:40:37 +0000287 tv.tv_sec = TFTP_TIMEOUT;
Mark Whitley450736c2001-03-02 19:08:50 +0000288 tv.tv_usec = 0;
289
290 FD_ZERO(&rfds);
291 FD_SET(socketfd, &rfds);
292
Bernhard Reutner-Fischer62f98562006-06-10 14:32:56 +0000293 switch (select(socketfd + 1, &rfds, NULL, NULL, &tv)) {
294 case 1:
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000295 len = recvfrom(socketfd, buf, tftp_bufsize, 0,
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000296 (struct sockaddr *) &from, &fromlen);
Mark Whitley450736c2001-03-02 19:08:50 +0000297
298 if (len < 0) {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000299 bb_perror_msg("recvfrom");
Mark Whitley450736c2001-03-02 19:08:50 +0000300 break;
301 }
302
303 timeout = 0;
304
Glenn L McGrath036dbaa2004-01-17 05:03:31 +0000305 if (sa.sin_port == port) {
Mark Whitley450736c2001-03-02 19:08:50 +0000306 sa.sin_port = from.sin_port;
Mark Whitley450736c2001-03-02 19:08:50 +0000307 }
Mark Whitley450736c2001-03-02 19:08:50 +0000308 if (sa.sin_port == from.sin_port) {
309 break;
310 }
311
312 /* fall-through for bad packets! */
313 /* discard the packet - treat as timeout */
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000314 timeout = TFTP_NUM_RETRIES;
Bernhard Reutner-Fischer62f98562006-06-10 14:32:56 +0000315 case 0:
Manuel Novoa III cad53642003-03-19 09:13:01 +0000316 bb_error_msg("timeout");
Mark Whitley450736c2001-03-02 19:08:50 +0000317
Eric Andersenb99aec02003-07-30 07:16:39 +0000318 timeout--;
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000319 if (timeout == 0) {
Mark Whitley450736c2001-03-02 19:08:50 +0000320 len = -1;
Manuel Novoa III cad53642003-03-19 09:13:01 +0000321 bb_error_msg("last timeout");
Mark Whitley450736c2001-03-02 19:08:50 +0000322 }
323 break;
Bernhard Reutner-Fischer62f98562006-06-10 14:32:56 +0000324 default:
Manuel Novoa III cad53642003-03-19 09:13:01 +0000325 bb_perror_msg("select");
Mark Whitley450736c2001-03-02 19:08:50 +0000326 len = -1;
327 }
328
329 } while (timeout && (len >= 0));
330
Glenn L McGrath0f182712002-12-19 20:16:22 +0000331 if ((finished) || (len < 0)) {
Mark Whitley450736c2001-03-02 19:08:50 +0000332 break;
333 }
334
335 /* process received packet */
336
Mark Whitley450736c2001-03-02 19:08:50 +0000337 opcode = ntohs(*((unsigned short *) buf));
338 tmp = ntohs(*((unsigned short *) &buf[2]));
339
Bernhard Reutner-Fischer32bf1f92006-06-14 17:29:10 +0000340#ifdef CONFIG_DEBUG_TFTP
Glenn L McGrathd33278d2004-02-22 07:20:25 +0000341 fprintf(stderr, "received %d bytes: %04x %04x\n", len, opcode, tmp);
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000342#endif
Mark Whitley450736c2001-03-02 19:08:50 +0000343
Glenn L McGrathad117d82001-10-05 04:40:37 +0000344 if (opcode == TFTP_ERROR) {
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000345 const char *msg = NULL;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000346
347 if (buf[4] != '\0') {
348 msg = &buf[4];
349 buf[tftp_bufsize - 1] = '\0';
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000350 } else if (tmp < (sizeof(tftp_bb_error_msg)
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000351 / sizeof(char *))) {
Glenn L McGrathad117d82001-10-05 04:40:37 +0000352
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000353 msg = tftp_bb_error_msg[tmp];
Glenn L McGrathad117d82001-10-05 04:40:37 +0000354 }
355
356 if (msg) {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000357 bb_error_msg("server says: %s", msg);
Glenn L McGrathad117d82001-10-05 04:40:37 +0000358 }
359
360 break;
361 }
Eric Andersenbdfd0d72001-10-24 05:00:29 +0000362#ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
Glenn L McGrathad117d82001-10-05 04:40:37 +0000363 if (want_option_ack) {
364
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000365 want_option_ack = 0;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000366
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000367 if (opcode == TFTP_OACK) {
Glenn L McGrathad117d82001-10-05 04:40:37 +0000368
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000369 /* server seems to support options */
Glenn L McGrathad117d82001-10-05 04:40:37 +0000370
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000371 char *res;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000372
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000373 res = tftp_option_get(&buf[2], len - 2, OPTION_BLOCKSIZE);
Glenn L McGrathad117d82001-10-05 04:40:37 +0000374
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000375 if (res) {
376 int blksize = atoi(res);
Tim Rikerc1ef7bd2006-01-25 00:08:53 +0000377
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000378 if (tftp_blocksize_check(blksize, tftp_bufsize - 4)) {
Glenn L McGrathad117d82001-10-05 04:40:37 +0000379
Bernhard Reutner-Fischer32bf1f92006-06-14 17:29:10 +0000380 if (cmd & tftp_cmd_put) {
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000381 opcode = TFTP_DATA;
382 } else {
383 opcode = TFTP_ACK;
384 }
Bernhard Reutner-Fischer32bf1f92006-06-14 17:29:10 +0000385#ifdef CONFIG_DEBUG_TFTP
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000386 fprintf(stderr, "using %s %u\n", OPTION_BLOCKSIZE,
387 blksize);
Glenn L McGrathad117d82001-10-05 04:40:37 +0000388#endif
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000389 tftp_bufsize = blksize + 4;
390 block_nr = 0;
391 continue;
392 }
393 }
394 /* FIXME:
395 * we should send ERROR 8 */
396 bb_error_msg("bad server option");
397 break;
398 }
Glenn L McGrathad117d82001-10-05 04:40:37 +0000399
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000400 bb_error_msg("warning: blksize not supported by server"
401 " - reverting to 512");
Glenn L McGrathad117d82001-10-05 04:40:37 +0000402
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000403 tftp_bufsize = TFTP_BLOCKSIZE_DEFAULT + 4;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000404 }
405#endif
406
Bernhard Reutner-Fischer32bf1f92006-06-14 17:29:10 +0000407 if ((cmd & tftp_cmd_get) && (opcode == TFTP_DATA)) {
Mark Whitley450736c2001-03-02 19:08:50 +0000408
409 if (tmp == block_nr) {
Tim Rikerc1ef7bd2006-01-25 00:08:53 +0000410
Rob Landley53437472006-07-16 08:14:35 +0000411 len = full_write(localfd, &buf[4], len - 4);
Mark Whitley450736c2001-03-02 19:08:50 +0000412
413 if (len < 0) {
Bernhard Reutner-Fischer1b9d7c92006-06-03 22:45:37 +0000414 bb_perror_msg(bb_msg_write_error);
Mark Whitley450736c2001-03-02 19:08:50 +0000415 break;
416 }
417
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000418 if (len != (tftp_bufsize - 4)) {
Mark Whitley450736c2001-03-02 19:08:50 +0000419 finished++;
420 }
421
Glenn L McGrathad117d82001-10-05 04:40:37 +0000422 opcode = TFTP_ACK;
Mark Whitley450736c2001-03-02 19:08:50 +0000423 continue;
424 }
Rob Landleyf3133c42005-06-07 02:40:39 +0000425 /* in case the last ack disappeared into the ether */
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000426 if (tmp == (block_nr - 1)) {
Rob Landleyf3133c42005-06-07 02:40:39 +0000427 --block_nr;
428 opcode = TFTP_ACK;
429 continue;
Paul Fox1d4c88c2005-07-20 19:49:15 +0000430 } else if (tmp + 1 == block_nr) {
431 /* Server lost our TFTP_ACK. Resend it */
432 block_nr = tmp;
433 opcode = TFTP_ACK;
434 continue;
Rob Landleyf3133c42005-06-07 02:40:39 +0000435 }
Mark Whitley450736c2001-03-02 19:08:50 +0000436 }
437
Bernhard Reutner-Fischer32bf1f92006-06-14 17:29:10 +0000438 if ((cmd & tftp_cmd_put) && (opcode == TFTP_ACK)) {
Mark Whitley450736c2001-03-02 19:08:50 +0000439
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000440 if (tmp == (unsigned short) (block_nr - 1)) {
Mark Whitley450736c2001-03-02 19:08:50 +0000441 if (finished) {
442 break;
443 }
444
Glenn L McGrathad117d82001-10-05 04:40:37 +0000445 opcode = TFTP_DATA;
Mark Whitley450736c2001-03-02 19:08:50 +0000446 continue;
447 }
448 }
Mark Whitley450736c2001-03-02 19:08:50 +0000449 }
450
Eric Andersenbdfd0d72001-10-24 05:00:29 +0000451#ifdef CONFIG_FEATURE_CLEAN_UP
Mark Whitley450736c2001-03-02 19:08:50 +0000452 close(socketfd);
Tim Rikerc1ef7bd2006-01-25 00:08:53 +0000453 free(buf);
Glenn L McGrathad117d82001-10-05 04:40:37 +0000454#endif
455
Mark Whitley8bb7df42001-03-06 20:58:48 +0000456 return finished ? EXIT_SUCCESS : EXIT_FAILURE;
Mark Whitley450736c2001-03-02 19:08:50 +0000457}
458
459int tftp_main(int argc, char **argv)
460{
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000461 struct hostent *host = NULL;
Glenn L McGrathd4004ee2004-09-14 17:24:59 +0000462 const char *localfile = NULL;
463 const char *remotefile = NULL;
Glenn L McGrath036dbaa2004-01-17 05:03:31 +0000464 int port;
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000465 int cmd = 0;
466 int fd = -1;
467 int flags = 0;
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000468 int result;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000469 int blocksize = TFTP_BLOCKSIZE_DEFAULT;
Mark Whitley450736c2001-03-02 19:08:50 +0000470
Glenn L McGrathad117d82001-10-05 04:40:37 +0000471 /* figure out what to pass to getopt */
472
Eric Andersenbdfd0d72001-10-24 05:00:29 +0000473#ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000474 char *sblocksize = NULL;
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000475
Glenn L McGrathad117d82001-10-05 04:40:37 +0000476#define BS "b:"
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000477#define BS_ARG , &sblocksize
Glenn L McGrathad117d82001-10-05 04:40:37 +0000478#else
479#define BS
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000480#define BS_ARG
Glenn L McGrathad117d82001-10-05 04:40:37 +0000481#endif
482
Eric Andersenbdfd0d72001-10-24 05:00:29 +0000483#ifdef CONFIG_FEATURE_TFTP_GET
Glenn L McGrathad117d82001-10-05 04:40:37 +0000484#define GET "g"
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000485#define GET_COMPL ":g"
Glenn L McGrathad117d82001-10-05 04:40:37 +0000486#else
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000487#define GET
Bernhard Reutner-Fischer886f6af2006-04-05 16:47:02 +0000488#define GET_COMPL
Glenn L McGrathad117d82001-10-05 04:40:37 +0000489#endif
490
Eric Andersenbdfd0d72001-10-24 05:00:29 +0000491#ifdef CONFIG_FEATURE_TFTP_PUT
Glenn L McGrathad117d82001-10-05 04:40:37 +0000492#define PUT "p"
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000493#define PUT_COMPL ":p"
Glenn L McGrathad117d82001-10-05 04:40:37 +0000494#else
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000495#define PUT
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000496#define PUT_COMPL
Glenn L McGrathad117d82001-10-05 04:40:37 +0000497#endif
498
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000499#if defined(CONFIG_FEATURE_TFTP_GET) && defined(CONFIG_FEATURE_TFTP_PUT)
500 bb_opt_complementally = GET_COMPL PUT_COMPL ":?g--p:p--g";
501#elif defined(CONFIG_FEATURE_TFTP_GET) || defined(CONFIG_FEATURE_TFTP_PUT)
502 bb_opt_complementally = GET_COMPL PUT_COMPL;
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000503#endif
504
Tim Rikerc1ef7bd2006-01-25 00:08:53 +0000505
506 cmd = bb_getopt_ulflags(argc, argv, GET PUT "l:r:" BS,
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000507 &localfile, &remotefile BS_ARG);
508
509 cmd &= (tftp_cmd_get | tftp_cmd_put);
510#ifdef CONFIG_FEATURE_TFTP_GET
511 if (cmd == tftp_cmd_get)
512 flags = O_WRONLY | O_CREAT | O_TRUNC;
513#endif
514#ifdef CONFIG_FEATURE_TFTP_PUT
515 if (cmd == tftp_cmd_put)
516 flags = O_RDONLY;
517#endif
518
Eric Andersenbdfd0d72001-10-24 05:00:29 +0000519#ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000520 if (sblocksize) {
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000521 blocksize = atoi(sblocksize);
522 if (!tftp_blocksize_check(blocksize, 0)) {
523 return EXIT_FAILURE;
Mark Whitley450736c2001-03-02 19:08:50 +0000524 }
Mark Whitley450736c2001-03-02 19:08:50 +0000525 }
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000526#endif
Mark Whitley450736c2001-03-02 19:08:50 +0000527
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000528 if (localfile == NULL)
529 localfile = remotefile;
530 if (remotefile == NULL)
531 remotefile = localfile;
532 if ((localfile == NULL && remotefile == NULL) || (argv[optind] == NULL))
533 bb_show_usage();
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000534
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000535 if (localfile == NULL || strcmp(localfile, "-") == 0) {
536 fd = (cmd == tftp_cmd_get) ? STDOUT_FILENO : STDIN_FILENO;
537 } else {
538 fd = open(localfile, flags, 0644); /* fail below */
Eric Andersena66a43e2002-04-13 09:30:25 +0000539 }
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000540 if (fd < 0) {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000541 bb_perror_msg_and_die("local file");
Mark Whitley450736c2001-03-02 19:08:50 +0000542 }
543
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000544 host = xgethostbyname(argv[optind]);
Glenn L McGrath036dbaa2004-01-17 05:03:31 +0000545 port = bb_lookup_port(argv[optind + 1], "udp", 69);
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000546
Bernhard Reutner-Fischer32bf1f92006-06-14 17:29:10 +0000547#ifdef CONFIG_DEBUG_TFTP
Glenn L McGrathd33278d2004-02-22 07:20:25 +0000548 fprintf(stderr, "using server \"%s\", remotefile \"%s\", "
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000549 "localfile \"%s\".\n",
550 inet_ntoa(*((struct in_addr *) host->h_addr)),
551 remotefile, localfile);
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000552#endif
553
554 result = tftp(cmd, host, remotefile, fd, port, blocksize);
Mark Whitley450736c2001-03-02 19:08:50 +0000555
Eric Andersen70060d22004-03-27 10:02:48 +0000556 if (!(fd == STDOUT_FILENO || fd == STDIN_FILENO)) {
Bernhard Reutner-Fischer32bf1f92006-06-14 17:29:10 +0000557 if (ENABLE_FEATURE_CLEAN_UP)
558 close(fd);
559 if (cmd == tftp_cmd_get && result != EXIT_SUCCESS)
560 unlink(localfile);
Eric Andersena66a43e2002-04-13 09:30:25 +0000561 }
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000562 return (result);
Glenn L McGrathad117d82001-10-05 04:40:37 +0000563}