blob: 2d28973dc6ed52419bc7bd997f8d36aff8ad6ef4 [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
Denis Vlasenko04291bc2006-11-21 10:15:25 +000063#if ENABLE_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 */
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000147 /* We must keep the transmit and receive buffers seperate */
148 /* In case we rcv a garbage pkt and we need to rexmit the last pkt */
149 char *xbuf = xmalloc(tftp_bufsize += 4);
150 char *rbuf = xmalloc(tftp_bufsize);
Mark Whitley450736c2001-03-02 19:08:50 +0000151
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000152 if ((socketfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
Rob Landleyd921b2e2006-08-03 15:41:12 +0000153 /* need to unlink the localfile, so don't use xsocket here. */
Manuel Novoa III cad53642003-03-19 09:13:01 +0000154 bb_perror_msg("socket");
Mark Whitley8bb7df42001-03-06 20:58:48 +0000155 return EXIT_FAILURE;
Mark Whitley450736c2001-03-02 19:08:50 +0000156 }
157
158 len = sizeof(sa);
159
160 memset(&sa, 0, len);
Rob Landleyd921b2e2006-08-03 15:41:12 +0000161 xbind(socketfd, (struct sockaddr *)&sa, len);
Mark Whitley450736c2001-03-02 19:08:50 +0000162
163 sa.sin_family = host->h_addrtype;
Glenn L McGrath036dbaa2004-01-17 05:03:31 +0000164 sa.sin_port = port;
Mark Whitley450736c2001-03-02 19:08:50 +0000165 memcpy(&sa.sin_addr, (struct in_addr *) host->h_addr,
166 sizeof(sa.sin_addr));
167
168 /* build opcode */
Bernhard Reutner-Fischer32bf1f92006-06-14 17:29:10 +0000169 if (cmd & tftp_cmd_get) {
Glenn L McGrathad117d82001-10-05 04:40:37 +0000170 opcode = TFTP_RRQ;
Mark Whitley450736c2001-03-02 19:08:50 +0000171 }
Bernhard Reutner-Fischer32bf1f92006-06-14 17:29:10 +0000172 if (cmd & tftp_cmd_put) {
Glenn L McGrathad117d82001-10-05 04:40:37 +0000173 opcode = TFTP_WRQ;
Mark Whitley450736c2001-03-02 19:08:50 +0000174 }
175
176 while (1) {
177
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000178 cp = xbuf;
Mark Whitley450736c2001-03-02 19:08:50 +0000179
Glenn L McGrathad117d82001-10-05 04:40:37 +0000180 /* first create the opcode part */
Mark Whitley450736c2001-03-02 19:08:50 +0000181 *((unsigned short *) cp) = htons(opcode);
Mark Whitley450736c2001-03-02 19:08:50 +0000182 cp += 2;
183
184 /* add filename and mode */
Bernhard Reutner-Fischer32bf1f92006-06-14 17:29:10 +0000185 if (((cmd & tftp_cmd_get) && (opcode == TFTP_RRQ)) ||
186 ((cmd & tftp_cmd_put) && (opcode == TFTP_WRQ)))
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000187 {
Tim Rikerc1ef7bd2006-01-25 00:08:53 +0000188 int too_long = 0;
Mark Whitley450736c2001-03-02 19:08:50 +0000189
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000190 /* see if the filename fits into xbuf
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000191 * and fill in packet. */
Glenn L McGrathad117d82001-10-05 04:40:37 +0000192 len = strlen(remotefile) + 1;
193
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000194 if ((cp + len) >= &xbuf[tftp_bufsize - 1]) {
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000195 too_long = 1;
196 } else {
197 safe_strncpy(cp, remotefile, len);
Glenn L McGrathad117d82001-10-05 04:40:37 +0000198 cp += len;
199 }
200
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000201 if (too_long || ((&xbuf[tftp_bufsize - 1] - cp) < MODE_OCTET_LEN)) {
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000202 bb_error_msg("remote filename too long");
Mark Whitley450736c2001-03-02 19:08:50 +0000203 break;
204 }
205
Glenn L McGrathad117d82001-10-05 04:40:37 +0000206 /* add "mode" part of the package */
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000207 memcpy(cp, MODE_OCTET, MODE_OCTET_LEN);
208 cp += MODE_OCTET_LEN;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000209
Denis Vlasenko04291bc2006-11-21 10:15:25 +0000210#if ENABLE_FEATURE_TFTP_BLOCKSIZE
Glenn L McGrathad117d82001-10-05 04:40:37 +0000211
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000212 len = tftp_bufsize - 4; /* data block size */
Glenn L McGrathad117d82001-10-05 04:40:37 +0000213
214 if (len != TFTP_BLOCKSIZE_DEFAULT) {
215
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000216 if ((&xbuf[tftp_bufsize - 1] - cp) < 15) {
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000217 bb_error_msg("remote filename too long");
Glenn L McGrathad117d82001-10-05 04:40:37 +0000218 break;
219 }
220
221 /* add "blksize" + number of blocks */
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000222 memcpy(cp, OPTION_BLOCKSIZE, OPTION_BLOCKSIZE_LEN);
223 cp += OPTION_BLOCKSIZE_LEN;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000224 cp += snprintf(cp, 6, "%d", len) + 1;
225
226 want_option_ack = 1;
227 }
228#endif
Mark Whitley450736c2001-03-02 19:08:50 +0000229 }
230
231 /* add ack and data */
232
Bernhard Reutner-Fischer32bf1f92006-06-14 17:29:10 +0000233 if (((cmd & tftp_cmd_get) && (opcode == TFTP_ACK)) ||
234 ((cmd & tftp_cmd_put) && (opcode == TFTP_DATA))) {
Mark Whitley450736c2001-03-02 19:08:50 +0000235
236 *((unsigned short *) cp) = htons(block_nr);
237
238 cp += 2;
239
240 block_nr++;
241
Bernhard Reutner-Fischer32bf1f92006-06-14 17:29:10 +0000242 if ((cmd & tftp_cmd_put) && (opcode == TFTP_DATA)) {
Rob Landley53437472006-07-16 08:14:35 +0000243 len = full_read(localfd, cp, tftp_bufsize - 4);
Mark Whitley450736c2001-03-02 19:08:50 +0000244
245 if (len < 0) {
Bernhard Reutner-Fischer1b9d7c92006-06-03 22:45:37 +0000246 bb_perror_msg(bb_msg_read_error);
Mark Whitley450736c2001-03-02 19:08:50 +0000247 break;
248 }
249
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000250 if (len != (tftp_bufsize - 4)) {
Mark Whitley450736c2001-03-02 19:08:50 +0000251 finished++;
252 }
253
254 cp += len;
Mark Whitley450736c2001-03-02 19:08:50 +0000255 }
256 }
257
258
259 /* send packet */
260
261
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000262 timeout = TFTP_NUM_RETRIES; /* re-initialize */
Mark Whitley450736c2001-03-02 19:08:50 +0000263 do {
264
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000265 len = cp - xbuf;
Mark Whitley450736c2001-03-02 19:08:50 +0000266
Denis Vlasenko04291bc2006-11-21 10:15:25 +0000267#if ENABLE_DEBUG_TFTP
Glenn L McGrathd33278d2004-02-22 07:20:25 +0000268 fprintf(stderr, "sending %u bytes\n", len);
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000269 for (cp = xbuf; cp < &xbuf[len]; cp++)
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000270 fprintf(stderr, "%02x ", (unsigned char) *cp);
Glenn L McGrathd33278d2004-02-22 07:20:25 +0000271 fprintf(stderr, "\n");
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000272#endif
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000273 if (sendto(socketfd, xbuf, len, 0,
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000274 (struct sockaddr *) &sa, sizeof(sa)) < 0) {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000275 bb_perror_msg("send");
Mark Whitley450736c2001-03-02 19:08:50 +0000276 len = -1;
277 break;
278 }
279
280
Eric Andersenb99aec02003-07-30 07:16:39 +0000281 if (finished && (opcode == TFTP_ACK)) {
Glenn L McGrath0f182712002-12-19 20:16:22 +0000282 break;
283 }
Mark Whitley450736c2001-03-02 19:08:50 +0000284
Glenn L McGrath0f182712002-12-19 20:16:22 +0000285 /* receive packet */
Mark Whitley450736c2001-03-02 19:08:50 +0000286
287 memset(&from, 0, sizeof(from));
288 fromlen = sizeof(from);
289
Glenn L McGrathad117d82001-10-05 04:40:37 +0000290 tv.tv_sec = TFTP_TIMEOUT;
Mark Whitley450736c2001-03-02 19:08:50 +0000291 tv.tv_usec = 0;
292
293 FD_ZERO(&rfds);
294 FD_SET(socketfd, &rfds);
295
Bernhard Reutner-Fischer62f98562006-06-10 14:32:56 +0000296 switch (select(socketfd + 1, &rfds, NULL, NULL, &tv)) {
297 case 1:
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000298 len = recvfrom(socketfd, rbuf, tftp_bufsize, 0,
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000299 (struct sockaddr *) &from, &fromlen);
Mark Whitley450736c2001-03-02 19:08:50 +0000300
301 if (len < 0) {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000302 bb_perror_msg("recvfrom");
Mark Whitley450736c2001-03-02 19:08:50 +0000303 break;
304 }
305
306 timeout = 0;
307
Glenn L McGrath036dbaa2004-01-17 05:03:31 +0000308 if (sa.sin_port == port) {
Mark Whitley450736c2001-03-02 19:08:50 +0000309 sa.sin_port = from.sin_port;
Mark Whitley450736c2001-03-02 19:08:50 +0000310 }
Mark Whitley450736c2001-03-02 19:08:50 +0000311 if (sa.sin_port == from.sin_port) {
312 break;
313 }
314
315 /* fall-through for bad packets! */
316 /* discard the packet - treat as timeout */
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000317 timeout = TFTP_NUM_RETRIES;
Bernhard Reutner-Fischer62f98562006-06-10 14:32:56 +0000318 case 0:
Manuel Novoa III cad53642003-03-19 09:13:01 +0000319 bb_error_msg("timeout");
Mark Whitley450736c2001-03-02 19:08:50 +0000320
Eric Andersenb99aec02003-07-30 07:16:39 +0000321 timeout--;
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000322 if (timeout == 0) {
Mark Whitley450736c2001-03-02 19:08:50 +0000323 len = -1;
Manuel Novoa III cad53642003-03-19 09:13:01 +0000324 bb_error_msg("last timeout");
Mark Whitley450736c2001-03-02 19:08:50 +0000325 }
326 break;
Bernhard Reutner-Fischer62f98562006-06-10 14:32:56 +0000327 default:
Manuel Novoa III cad53642003-03-19 09:13:01 +0000328 bb_perror_msg("select");
Mark Whitley450736c2001-03-02 19:08:50 +0000329 len = -1;
330 }
331
332 } while (timeout && (len >= 0));
333
Glenn L McGrath0f182712002-12-19 20:16:22 +0000334 if ((finished) || (len < 0)) {
Mark Whitley450736c2001-03-02 19:08:50 +0000335 break;
336 }
337
338 /* process received packet */
339
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000340 opcode = ntohs(*((unsigned short *) rbuf));
341 tmp = ntohs(*((unsigned short *) &rbuf[2]));
Mark Whitley450736c2001-03-02 19:08:50 +0000342
Denis Vlasenko04291bc2006-11-21 10:15:25 +0000343#if ENABLE_DEBUG_TFTP
Glenn L McGrathd33278d2004-02-22 07:20:25 +0000344 fprintf(stderr, "received %d bytes: %04x %04x\n", len, opcode, tmp);
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000345#endif
Mark Whitley450736c2001-03-02 19:08:50 +0000346
Glenn L McGrathad117d82001-10-05 04:40:37 +0000347 if (opcode == TFTP_ERROR) {
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000348 const char *msg = NULL;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000349
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000350 if (rbuf[4] != '\0') {
351 msg = &rbuf[4];
352 rbuf[tftp_bufsize - 1] = '\0';
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000353 } else if (tmp < (sizeof(tftp_bb_error_msg)
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000354 / sizeof(char *))) {
Glenn L McGrathad117d82001-10-05 04:40:37 +0000355
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000356 msg = tftp_bb_error_msg[tmp];
Glenn L McGrathad117d82001-10-05 04:40:37 +0000357 }
358
359 if (msg) {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000360 bb_error_msg("server says: %s", msg);
Glenn L McGrathad117d82001-10-05 04:40:37 +0000361 }
362
363 break;
364 }
Denis Vlasenko04291bc2006-11-21 10:15:25 +0000365#if ENABLE_FEATURE_TFTP_BLOCKSIZE
Glenn L McGrathad117d82001-10-05 04:40:37 +0000366 if (want_option_ack) {
367
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000368 want_option_ack = 0;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000369
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000370 if (opcode == TFTP_OACK) {
Glenn L McGrathad117d82001-10-05 04:40:37 +0000371
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000372 /* server seems to support options */
Glenn L McGrathad117d82001-10-05 04:40:37 +0000373
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000374 char *res;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000375
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000376 res = tftp_option_get(&rbuf[2], len - 2, OPTION_BLOCKSIZE);
Glenn L McGrathad117d82001-10-05 04:40:37 +0000377
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000378 if (res) {
Denis Vlasenko13858992006-10-08 12:49:22 +0000379 int blksize = xatoi_u(res);
Tim Rikerc1ef7bd2006-01-25 00:08:53 +0000380
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000381 if (tftp_blocksize_check(blksize, tftp_bufsize - 4)) {
Glenn L McGrathad117d82001-10-05 04:40:37 +0000382
Bernhard Reutner-Fischer32bf1f92006-06-14 17:29:10 +0000383 if (cmd & tftp_cmd_put) {
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000384 opcode = TFTP_DATA;
385 } else {
386 opcode = TFTP_ACK;
387 }
Denis Vlasenko04291bc2006-11-21 10:15:25 +0000388#if ENABLE_DEBUG_TFTP
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000389 fprintf(stderr, "using %s %u\n", OPTION_BLOCKSIZE,
390 blksize);
Glenn L McGrathad117d82001-10-05 04:40:37 +0000391#endif
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000392 tftp_bufsize = blksize + 4;
393 block_nr = 0;
394 continue;
395 }
396 }
397 /* FIXME:
398 * we should send ERROR 8 */
399 bb_error_msg("bad server option");
400 break;
401 }
Glenn L McGrathad117d82001-10-05 04:40:37 +0000402
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000403 bb_error_msg("warning: blksize not supported by server"
404 " - reverting to 512");
Glenn L McGrathad117d82001-10-05 04:40:37 +0000405
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000406 tftp_bufsize = TFTP_BLOCKSIZE_DEFAULT + 4;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000407 }
408#endif
409
Bernhard Reutner-Fischer32bf1f92006-06-14 17:29:10 +0000410 if ((cmd & tftp_cmd_get) && (opcode == TFTP_DATA)) {
Mark Whitley450736c2001-03-02 19:08:50 +0000411
412 if (tmp == block_nr) {
Tim Rikerc1ef7bd2006-01-25 00:08:53 +0000413
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000414 len = full_write(localfd, &rbuf[4], len - 4);
Mark Whitley450736c2001-03-02 19:08:50 +0000415
416 if (len < 0) {
Bernhard Reutner-Fischer1b9d7c92006-06-03 22:45:37 +0000417 bb_perror_msg(bb_msg_write_error);
Mark Whitley450736c2001-03-02 19:08:50 +0000418 break;
419 }
420
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000421 if (len != (tftp_bufsize - 4)) {
Mark Whitley450736c2001-03-02 19:08:50 +0000422 finished++;
423 }
424
Glenn L McGrathad117d82001-10-05 04:40:37 +0000425 opcode = TFTP_ACK;
Mark Whitley450736c2001-03-02 19:08:50 +0000426 continue;
427 }
Rob Landleyf3133c42005-06-07 02:40:39 +0000428 /* in case the last ack disappeared into the ether */
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000429 if (tmp == (block_nr - 1)) {
Rob Landleyf3133c42005-06-07 02:40:39 +0000430 --block_nr;
431 opcode = TFTP_ACK;
432 continue;
Paul Fox1d4c88c2005-07-20 19:49:15 +0000433 } else if (tmp + 1 == block_nr) {
434 /* Server lost our TFTP_ACK. Resend it */
435 block_nr = tmp;
436 opcode = TFTP_ACK;
437 continue;
Rob Landleyf3133c42005-06-07 02:40:39 +0000438 }
Mark Whitley450736c2001-03-02 19:08:50 +0000439 }
440
Bernhard Reutner-Fischer32bf1f92006-06-14 17:29:10 +0000441 if ((cmd & tftp_cmd_put) && (opcode == TFTP_ACK)) {
Mark Whitley450736c2001-03-02 19:08:50 +0000442
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000443 if (tmp == (unsigned short) (block_nr - 1)) {
Mark Whitley450736c2001-03-02 19:08:50 +0000444 if (finished) {
445 break;
446 }
447
Glenn L McGrathad117d82001-10-05 04:40:37 +0000448 opcode = TFTP_DATA;
Mark Whitley450736c2001-03-02 19:08:50 +0000449 continue;
450 }
451 }
Mark Whitley450736c2001-03-02 19:08:50 +0000452 }
453
Denis Vlasenko04291bc2006-11-21 10:15:25 +0000454#if ENABLE_FEATURE_CLEAN_UP
Mark Whitley450736c2001-03-02 19:08:50 +0000455 close(socketfd);
Denis Vlasenko10f7dd12006-12-17 01:14:08 +0000456 free(xbuf);
457 free(rbuf);
Glenn L McGrathad117d82001-10-05 04:40:37 +0000458#endif
459
Mark Whitley8bb7df42001-03-06 20:58:48 +0000460 return finished ? EXIT_SUCCESS : EXIT_FAILURE;
Mark Whitley450736c2001-03-02 19:08:50 +0000461}
462
463int tftp_main(int argc, char **argv)
464{
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000465 struct hostent *host = NULL;
Glenn L McGrathd4004ee2004-09-14 17:24:59 +0000466 const char *localfile = NULL;
467 const char *remotefile = NULL;
Glenn L McGrath036dbaa2004-01-17 05:03:31 +0000468 int port;
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000469 int cmd = 0;
470 int fd = -1;
471 int flags = 0;
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000472 int result;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000473 int blocksize = TFTP_BLOCKSIZE_DEFAULT;
Mark Whitley450736c2001-03-02 19:08:50 +0000474
Glenn L McGrathad117d82001-10-05 04:40:37 +0000475 /* figure out what to pass to getopt */
476
Denis Vlasenko04291bc2006-11-21 10:15:25 +0000477#if ENABLE_FEATURE_TFTP_BLOCKSIZE
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000478 char *sblocksize = NULL;
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000479
Glenn L McGrathad117d82001-10-05 04:40:37 +0000480#define BS "b:"
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000481#define BS_ARG , &sblocksize
Glenn L McGrathad117d82001-10-05 04:40:37 +0000482#else
483#define BS
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000484#define BS_ARG
Glenn L McGrathad117d82001-10-05 04:40:37 +0000485#endif
486
Denis Vlasenko04291bc2006-11-21 10:15:25 +0000487#if ENABLE_FEATURE_TFTP_GET
Glenn L McGrathad117d82001-10-05 04:40:37 +0000488#define GET "g"
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000489#define GET_COMPL ":g"
Glenn L McGrathad117d82001-10-05 04:40:37 +0000490#else
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000491#define GET
Bernhard Reutner-Fischer886f6af2006-04-05 16:47:02 +0000492#define GET_COMPL
Glenn L McGrathad117d82001-10-05 04:40:37 +0000493#endif
494
Denis Vlasenko04291bc2006-11-21 10:15:25 +0000495#if ENABLE_FEATURE_TFTP_PUT
Glenn L McGrathad117d82001-10-05 04:40:37 +0000496#define PUT "p"
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000497#define PUT_COMPL ":p"
Glenn L McGrathad117d82001-10-05 04:40:37 +0000498#else
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000499#define PUT
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000500#define PUT_COMPL
Glenn L McGrathad117d82001-10-05 04:40:37 +0000501#endif
502
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000503#if defined(CONFIG_FEATURE_TFTP_GET) && defined(CONFIG_FEATURE_TFTP_PUT)
Denis Vlasenko67b23e62006-10-03 21:00:06 +0000504 opt_complementary = GET_COMPL PUT_COMPL ":?g--p:p--g";
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000505#elif defined(CONFIG_FEATURE_TFTP_GET) || defined(CONFIG_FEATURE_TFTP_PUT)
Denis Vlasenko67b23e62006-10-03 21:00:06 +0000506 opt_complementary = GET_COMPL PUT_COMPL;
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000507#endif
508
Denis Vlasenko000b9ba2006-10-05 23:12:49 +0000509 cmd = getopt32(argc, argv, GET PUT "l:r:" BS, &localfile, &remotefile BS_ARG);
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000510
511 cmd &= (tftp_cmd_get | tftp_cmd_put);
Denis Vlasenko04291bc2006-11-21 10:15:25 +0000512#if ENABLE_FEATURE_TFTP_GET
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000513 if (cmd == tftp_cmd_get)
514 flags = O_WRONLY | O_CREAT | O_TRUNC;
515#endif
Denis Vlasenko04291bc2006-11-21 10:15:25 +0000516#if ENABLE_FEATURE_TFTP_PUT
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000517 if (cmd == tftp_cmd_put)
518 flags = O_RDONLY;
519#endif
520
Denis Vlasenko04291bc2006-11-21 10:15:25 +0000521#if ENABLE_FEATURE_TFTP_BLOCKSIZE
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000522 if (sblocksize) {
Denis Vlasenko13858992006-10-08 12:49:22 +0000523 blocksize = xatoi_u(sblocksize);
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000524 if (!tftp_blocksize_check(blocksize, 0)) {
525 return EXIT_FAILURE;
Mark Whitley450736c2001-03-02 19:08:50 +0000526 }
Mark Whitley450736c2001-03-02 19:08:50 +0000527 }
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000528#endif
Mark Whitley450736c2001-03-02 19:08:50 +0000529
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000530 if (localfile == NULL)
531 localfile = remotefile;
532 if (remotefile == NULL)
533 remotefile = localfile;
534 if ((localfile == NULL && remotefile == NULL) || (argv[optind] == NULL))
535 bb_show_usage();
"Vladimir N. Oleynik"86ac0722005-10-17 10:47:19 +0000536
Denis Vlasenko9f739442006-12-16 23:49:13 +0000537 if (localfile == NULL || LONE_DASH(localfile)) {
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000538 fd = (cmd == tftp_cmd_get) ? STDOUT_FILENO : STDIN_FILENO;
539 } else {
540 fd = open(localfile, flags, 0644); /* fail below */
Eric Andersena66a43e2002-04-13 09:30:25 +0000541 }
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000542 if (fd < 0) {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000543 bb_perror_msg_and_die("local file");
Mark Whitley450736c2001-03-02 19:08:50 +0000544 }
545
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000546 host = xgethostbyname(argv[optind]);
Glenn L McGrath036dbaa2004-01-17 05:03:31 +0000547 port = bb_lookup_port(argv[optind + 1], "udp", 69);
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000548
Denis Vlasenko04291bc2006-11-21 10:15:25 +0000549#if ENABLE_DEBUG_TFTP
Glenn L McGrathd33278d2004-02-22 07:20:25 +0000550 fprintf(stderr, "using server \"%s\", remotefile \"%s\", "
Bernhard Reutner-Fischerb25f98a2006-06-10 14:15:03 +0000551 "localfile \"%s\".\n",
552 inet_ntoa(*((struct in_addr *) host->h_addr)),
553 remotefile, localfile);
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000554#endif
555
556 result = tftp(cmd, host, remotefile, fd, port, blocksize);
Mark Whitley450736c2001-03-02 19:08:50 +0000557
Eric Andersen70060d22004-03-27 10:02:48 +0000558 if (!(fd == STDOUT_FILENO || fd == STDIN_FILENO)) {
Bernhard Reutner-Fischer32bf1f92006-06-14 17:29:10 +0000559 if (ENABLE_FEATURE_CLEAN_UP)
560 close(fd);
561 if (cmd == tftp_cmd_get && result != EXIT_SUCCESS)
562 unlink(localfile);
Eric Andersena66a43e2002-04-13 09:30:25 +0000563 }
Denis Vlasenko000b9ba2006-10-05 23:12:49 +0000564 return result;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000565}