blob: 530b3d134660e3c5b8dbbf2e86d89857b859667e [file] [log] [blame]
Mark Whitley450736c2001-03-02 19:08:50 +00001/* ------------------------------------------------------------------------- */
2/* tftp.c */
3/* */
4/* A simple tftp client for busybox. */
5/* Tries to follow RFC1350. */
Glenn L McGrathad117d82001-10-05 04:40:37 +00006/* Only "octet" mode supported. */
7/* Optional blocksize negotiation (RFC2347 + RFC2348) */
Mark Whitley450736c2001-03-02 19:08:50 +00008/* */
9/* Copyright (C) 2001 Magnus Damm <damm@opensource.se> */
10/* */
11/* Parts of the code based on: */
12/* */
13/* atftp: Copyright (C) 2000 Jean-Pierre Lefebvre <helix@step.polymtl.ca> */
14/* and Remi Lefebvre <remi@debian.org> */
15/* */
16/* utftp: Copyright (C) 1999 Uwe Ohse <uwe@ohse.de> */
17/* */
18/* This program is free software; you can redistribute it and/or modify */
19/* it under the terms of the GNU General Public License as published by */
20/* the Free Software Foundation; either version 2 of the License, or */
21/* (at your option) any later version. */
22/* */
23/* This program is distributed in the hope that it will be useful, */
24/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
25/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU */
26/* General Public License for more details. */
27/* */
28/* You should have received a copy of the GNU General Public License */
29/* along with this program; if not, write to the Free Software */
30/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
31/* */
32/* ------------------------------------------------------------------------- */
33
34#include <stdio.h>
35#include <stdlib.h>
36#include <string.h>
37#include <sys/types.h>
38#include <sys/socket.h>
39#include <sys/time.h>
40#include <sys/stat.h>
41#include <netdb.h>
42#include <netinet/in.h>
43#include <arpa/inet.h>
44#include <unistd.h>
45#include <fcntl.h>
46
47#include "busybox.h"
48
49//#define BB_FEATURE_TFTP_DEBUG
50
Glenn L McGrathad117d82001-10-05 04:40:37 +000051#define TFTP_BLOCKSIZE_DEFAULT 512 /* according to RFC 1350, don't change */
52#define TFTP_TIMEOUT 5 /* seconds */
53
54/* opcodes we support */
55
56#define TFTP_RRQ 1
57#define TFTP_WRQ 2
58#define TFTP_DATA 3
59#define TFTP_ACK 4
60#define TFTP_ERROR 5
61#define TFTP_OACK 6
62
Mark Whitley450736c2001-03-02 19:08:50 +000063static const char *tftp_error_msg[] = {
64 "Undefined error",
65 "File not found",
66 "Access violation",
67 "Disk full or allocation error",
68 "Illegal TFTP operation",
69 "Unknown transfer ID",
70 "File already exists",
71 "No such user"
72};
73
Eric Andersen76fa8ea2001-08-20 17:47:49 +000074const int tftp_cmd_get = 1;
75const int tftp_cmd_put = 2;
76
Glenn L McGrathad117d82001-10-05 04:40:37 +000077#ifdef BB_FEATURE_TFTP_BLOCKSIZE
78
79static int tftp_blocksize_check(int blocksize, int bufsize)
80{
81 /* Check if the blocksize is valid:
82 * RFC2348 says between 8 and 65464,
83 * but our implementation makes it impossible
84 * to use blocksizes smaller than 22 octets.
85 */
86
87 if ((bufsize && (blocksize > bufsize)) ||
88 (blocksize < 8) || (blocksize > 65464)) {
89 error_msg("bad blocksize");
90 return 0;
91 }
92
93 return blocksize;
94}
95
96static char *tftp_option_get(char *buf, int len, char *option)
97{
98 int opt_val = 0;
99 int opt_found = 0;
100 int k;
101
102 while (len > 0) {
103
104 /* Make sure the options are terminated correctly */
105
106 for (k = 0; k < len; k++) {
107 if (buf[k] == '\0') {
108 break;
109 }
110 }
111
112 if (k >= len) {
113 break;
114 }
115
116 if (opt_val == 0) {
117 if (strcasecmp(buf, option) == 0) {
118 opt_found = 1;
119 }
120 }
121 else {
122 if (opt_found) {
123 return buf;
124 }
125 }
126
127 k++;
128
129 buf += k;
130 len -= k;
131
132 opt_val ^= 1;
133 }
134
135 return NULL;
136}
137
138#endif
139
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000140static inline int tftp(const int cmd, const struct hostent *host,
Glenn L McGrathad117d82001-10-05 04:40:37 +0000141 const char *remotefile, int localfd, const int port, int tftp_bufsize)
Mark Whitley450736c2001-03-02 19:08:50 +0000142{
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000143 const int cmd_get = cmd & tftp_cmd_get;
144 const int cmd_put = cmd & tftp_cmd_put;
145 const int bb_tftp_num_retries = 5;
146
Mark Whitley450736c2001-03-02 19:08:50 +0000147 struct sockaddr_in sa;
Mark Whitley450736c2001-03-02 19:08:50 +0000148 struct sockaddr_in from;
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000149 struct timeval tv;
Mark Whitley450736c2001-03-02 19:08:50 +0000150 socklen_t fromlen;
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000151 fd_set rfds;
Mark Whitley450736c2001-03-02 19:08:50 +0000152 char *cp;
153 unsigned short tmp;
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000154 int socketfd;
155 int len;
156 int opcode = 0;
157 int finished = 0;
158 int timeout = bb_tftp_num_retries;
159 int block_nr = 1;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000160
161#ifdef BB_FEATURE_TFTP_BLOCKSIZE
162 int want_option_ack = 0;
163#endif
164
165 RESERVE_BB_BUFFER(buf, tftp_bufsize + 4); /* Opcode + Block # + Data */
Mark Whitley450736c2001-03-02 19:08:50 +0000166
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000167 tftp_bufsize += 4;
Mark Whitley450736c2001-03-02 19:08:50 +0000168
169 if ((socketfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
170 perror_msg("socket");
Mark Whitley8bb7df42001-03-06 20:58:48 +0000171 return EXIT_FAILURE;
Mark Whitley450736c2001-03-02 19:08:50 +0000172 }
173
174 len = sizeof(sa);
175
176 memset(&sa, 0, len);
Eric Andersene76c3b02001-04-05 03:14:39 +0000177 bind(socketfd, (struct sockaddr *)&sa, len);
Mark Whitley450736c2001-03-02 19:08:50 +0000178
179 sa.sin_family = host->h_addrtype;
180 sa.sin_port = htons(port);
181 memcpy(&sa.sin_addr, (struct in_addr *) host->h_addr,
182 sizeof(sa.sin_addr));
183
184 /* build opcode */
185
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000186 if (cmd_get) {
Glenn L McGrathad117d82001-10-05 04:40:37 +0000187 opcode = TFTP_RRQ;
Mark Whitley450736c2001-03-02 19:08:50 +0000188 }
189
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000190 if (cmd_put) {
Glenn L McGrathad117d82001-10-05 04:40:37 +0000191 opcode = TFTP_WRQ;
Mark Whitley450736c2001-03-02 19:08:50 +0000192 }
193
194 while (1) {
195
Mark Whitley450736c2001-03-02 19:08:50 +0000196 cp = buf;
197
Glenn L McGrathad117d82001-10-05 04:40:37 +0000198 /* first create the opcode part */
199
Mark Whitley450736c2001-03-02 19:08:50 +0000200 *((unsigned short *) cp) = htons(opcode);
201
202 cp += 2;
203
204 /* add filename and mode */
205
Glenn L McGrathad117d82001-10-05 04:40:37 +0000206 if ((cmd_get && (opcode == TFTP_RRQ)) ||
207 (cmd_put && (opcode == TFTP_WRQ))) {
208 int too_long = 0;
Mark Whitley450736c2001-03-02 19:08:50 +0000209
Glenn L McGrathad117d82001-10-05 04:40:37 +0000210 /* see if the filename fits into buf */
211 /* and fill in packet */
212
213 len = strlen(remotefile) + 1;
214
215 if ((cp + len) >= &buf[tftp_bufsize - 1]) {
216 too_long = 1;
Mark Whitley450736c2001-03-02 19:08:50 +0000217 }
Glenn L McGrathad117d82001-10-05 04:40:37 +0000218 else {
219 safe_strncpy(cp, remotefile, len);
220 cp += len;
221 }
222
223 if (too_long || ((&buf[tftp_bufsize - 1] - cp) < 6)) {
224 error_msg("too long remote-filename");
Mark Whitley450736c2001-03-02 19:08:50 +0000225 break;
226 }
227
Glenn L McGrathad117d82001-10-05 04:40:37 +0000228 /* add "mode" part of the package */
229
230 memcpy(cp, "octet", 6);
231 cp += 6;
232
233#ifdef BB_FEATURE_TFTP_BLOCKSIZE
234
235 len = tftp_bufsize - 4; /* data block size */
236
237 if (len != TFTP_BLOCKSIZE_DEFAULT) {
238
239 if ((&buf[tftp_bufsize - 1] - cp) < 15) {
240 error_msg("too long remote-filename");
241 break;
242 }
243
244 /* add "blksize" + number of blocks */
245
246 memcpy(cp, "blksize", 8);
247 cp += 8;
248
249 cp += snprintf(cp, 6, "%d", len) + 1;
250
251 want_option_ack = 1;
252 }
253#endif
Mark Whitley450736c2001-03-02 19:08:50 +0000254 }
255
256 /* add ack and data */
257
Glenn L McGrathad117d82001-10-05 04:40:37 +0000258 if ((cmd_get && (opcode == TFTP_ACK)) ||
259 (cmd_put && (opcode == TFTP_DATA))) {
Mark Whitley450736c2001-03-02 19:08:50 +0000260
261 *((unsigned short *) cp) = htons(block_nr);
262
263 cp += 2;
264
265 block_nr++;
266
Glenn L McGrathad117d82001-10-05 04:40:37 +0000267 if (cmd_put && (opcode == TFTP_DATA)) {
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000268 len = read(localfd, cp, tftp_bufsize - 4);
Mark Whitley450736c2001-03-02 19:08:50 +0000269
270 if (len < 0) {
271 perror_msg("read");
272 break;
273 }
274
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000275 if (len != (tftp_bufsize - 4)) {
Mark Whitley450736c2001-03-02 19:08:50 +0000276 finished++;
277 }
278
279 cp += len;
280 } else if (finished) {
281 break;
282 }
283 }
284
285
286 /* send packet */
287
288
289 do {
290
291 len = cp - buf;
292
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000293#ifdef BB_FEATURE_TFTP_DEBUG
294 printf("sending %u bytes\n", len);
295 for (cp = buf; cp < &buf[len]; cp++)
296 printf("%02x ", *cp);
297 printf("\n");
298#endif
Mark Whitley450736c2001-03-02 19:08:50 +0000299 if (sendto(socketfd, buf, len, 0,
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000300 (struct sockaddr *) &sa, sizeof(sa)) < 0) {
Mark Whitley8bb7df42001-03-06 20:58:48 +0000301 perror_msg("send");
Mark Whitley450736c2001-03-02 19:08:50 +0000302 len = -1;
303 break;
304 }
305
306
307 /* receive packet */
308
309
310 memset(&from, 0, sizeof(from));
311 fromlen = sizeof(from);
312
Glenn L McGrathad117d82001-10-05 04:40:37 +0000313 tv.tv_sec = TFTP_TIMEOUT;
Mark Whitley450736c2001-03-02 19:08:50 +0000314 tv.tv_usec = 0;
315
316 FD_ZERO(&rfds);
317 FD_SET(socketfd, &rfds);
318
319 switch (select(FD_SETSIZE, &rfds, NULL, NULL, &tv)) {
320 case 1:
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000321 len = recvfrom(socketfd, buf, tftp_bufsize, 0,
322 (struct sockaddr *) &from, &fromlen);
Mark Whitley450736c2001-03-02 19:08:50 +0000323
324 if (len < 0) {
325 perror_msg("recvfrom");
326 break;
327 }
328
329 timeout = 0;
330
331 if (sa.sin_port == htons(port)) {
332 sa.sin_port = from.sin_port;
Mark Whitley450736c2001-03-02 19:08:50 +0000333 }
Mark Whitley450736c2001-03-02 19:08:50 +0000334 if (sa.sin_port == from.sin_port) {
335 break;
336 }
337
338 /* fall-through for bad packets! */
339 /* discard the packet - treat as timeout */
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000340 timeout = bb_tftp_num_retries;
Mark Whitley450736c2001-03-02 19:08:50 +0000341
342 case 0:
Mark Whitley8bb7df42001-03-06 20:58:48 +0000343 error_msg("timeout");
Mark Whitley450736c2001-03-02 19:08:50 +0000344
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000345 if (timeout == 0) {
Mark Whitley450736c2001-03-02 19:08:50 +0000346 len = -1;
Mark Whitley8bb7df42001-03-06 20:58:48 +0000347 error_msg("last timeout");
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000348 } else {
349 timeout--;
Mark Whitley450736c2001-03-02 19:08:50 +0000350 }
351 break;
352
353 default:
354 perror_msg("select");
355 len = -1;
356 }
357
358 } while (timeout && (len >= 0));
359
360 if (len < 0) {
361 break;
362 }
363
364 /* process received packet */
365
366
367 opcode = ntohs(*((unsigned short *) buf));
368 tmp = ntohs(*((unsigned short *) &buf[2]));
369
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000370#ifdef BB_FEATURE_TFTP_DEBUG
371 printf("received %d bytes: %04x %04x\n", len, opcode, tmp);
372#endif
Mark Whitley450736c2001-03-02 19:08:50 +0000373
Glenn L McGrathad117d82001-10-05 04:40:37 +0000374 if (opcode == TFTP_ERROR) {
375 char *msg = NULL;
376
377 if (buf[4] != '\0') {
378 msg = &buf[4];
379 buf[tftp_bufsize - 1] = '\0';
380 } else if (tmp < (sizeof(tftp_error_msg)
381 / sizeof(char *))) {
382
383 msg = (char *) tftp_error_msg[tmp];
384 }
385
386 if (msg) {
387 error_msg("server says: %s", msg);
388 }
389
390 break;
391 }
392
393#ifdef BB_FEATURE_TFTP_BLOCKSIZE
394 if (want_option_ack) {
395
396 want_option_ack = 0;
397
398 if (opcode == TFTP_OACK) {
399
400 /* server seems to support options */
401
402 char *res;
403
404 res = tftp_option_get(&buf[2], len-2,
405 "blksize");
406
407 if (res) {
408 int foo = atoi(res);
409
410 if (tftp_blocksize_check(foo,
411 tftp_bufsize - 4)) {
412
413 if (cmd_put) {
414 opcode = TFTP_DATA;
415 }
416 else {
417 opcode = TFTP_ACK;
418 }
419#ifdef BB_FEATURE_TFTP_DEBUG
420 printf("using blksize %u\n");
421#endif
422 tftp_bufsize = foo + 4;
423 block_nr = 0;
424 continue;
425 }
426 }
427 /* FIXME:
428 * we should send ERROR 8 */
429 error_msg("bad server option");
430 break;
431 }
432
433 error_msg("warning: blksize not supported by server"
434 " - reverting to 512");
435
436 tftp_bufsize = TFTP_BLOCKSIZE_DEFAULT + 4;
437 }
438#endif
439
440 if (cmd_get && (opcode == TFTP_DATA)) {
Mark Whitley450736c2001-03-02 19:08:50 +0000441
442 if (tmp == block_nr) {
Glenn L McGrathad117d82001-10-05 04:40:37 +0000443
Mark Whitley450736c2001-03-02 19:08:50 +0000444 len = write(localfd, &buf[4], len - 4);
445
446 if (len < 0) {
447 perror_msg("write");
448 break;
449 }
450
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000451 if (len != (tftp_bufsize - 4)) {
Mark Whitley450736c2001-03-02 19:08:50 +0000452 finished++;
453 }
454
Glenn L McGrathad117d82001-10-05 04:40:37 +0000455 opcode = TFTP_ACK;
Mark Whitley450736c2001-03-02 19:08:50 +0000456 continue;
457 }
458 }
459
Glenn L McGrathad117d82001-10-05 04:40:37 +0000460 if (cmd_put && (opcode == TFTP_ACK)) {
Mark Whitley450736c2001-03-02 19:08:50 +0000461
462 if (tmp == (block_nr - 1)) {
463 if (finished) {
464 break;
465 }
466
Glenn L McGrathad117d82001-10-05 04:40:37 +0000467 opcode = TFTP_DATA;
Mark Whitley450736c2001-03-02 19:08:50 +0000468 continue;
469 }
470 }
Mark Whitley450736c2001-03-02 19:08:50 +0000471 }
472
Glenn L McGrathad117d82001-10-05 04:40:37 +0000473#ifdef BB_FEATURE_CLEAN_UP
Mark Whitley450736c2001-03-02 19:08:50 +0000474 close(socketfd);
475
Glenn L McGrathad117d82001-10-05 04:40:37 +0000476 RELEASE_BB_BUFFER(buf);
477#endif
478
Mark Whitley8bb7df42001-03-06 20:58:48 +0000479 return finished ? EXIT_SUCCESS : EXIT_FAILURE;
Mark Whitley450736c2001-03-02 19:08:50 +0000480}
481
482int tftp_main(int argc, char **argv)
483{
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000484 struct hostent *host = NULL;
485 char *localfile = NULL;
486 char *remotefile = NULL;
487 int port = 69;
488 int cmd = 0;
489 int fd = -1;
490 int flags = 0;
491 int opt;
492 int result;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000493 int blocksize = TFTP_BLOCKSIZE_DEFAULT;
Mark Whitley450736c2001-03-02 19:08:50 +0000494
Glenn L McGrathad117d82001-10-05 04:40:37 +0000495 /* figure out what to pass to getopt */
496
497#ifdef BB_FEATURE_TFTP_BLOCKSIZE
498#define BS "b:"
499#else
500#define BS
501#endif
502
503#ifdef BB_FEATURE_TFTP_GET
504#define GET "g"
505#else
506#define GET
507#endif
508
509#ifdef BB_FEATURE_TFTP_PUT
510#define PUT "p"
511#else
512#define PUT
513#endif
514
515 while ((opt = getopt(argc, argv, BS GET PUT "l:r:")) != -1) {
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000516 switch (opt) {
Glenn L McGrathad117d82001-10-05 04:40:37 +0000517#ifdef BB_FEATURE_TFTP_BLOCKSIZE
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000518 case 'b':
519 blocksize = atoi(optarg);
Glenn L McGrathad117d82001-10-05 04:40:37 +0000520 if (!tftp_blocksize_check(blocksize, 0)) {
521 return EXIT_FAILURE;
522 }
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000523 break;
Glenn L McGrathad117d82001-10-05 04:40:37 +0000524#endif
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000525#ifdef BB_FEATURE_TFTP_GET
526 case 'g':
527 cmd = tftp_cmd_get;
Mark Whitley450736c2001-03-02 19:08:50 +0000528 flags = O_WRONLY | O_CREAT;
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000529 break;
530#endif
531#ifdef BB_FEATURE_TFTP_PUT
532 case 'p':
533 cmd = tftp_cmd_put;
Mark Whitley450736c2001-03-02 19:08:50 +0000534 flags = O_RDONLY;
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000535 break;
536#endif
537 case 'l':
538 localfile = xstrdup(optarg);
539 break;
540 case 'r':
541 remotefile = xstrdup(optarg);
542 break;
Mark Whitley450736c2001-03-02 19:08:50 +0000543 }
Mark Whitley450736c2001-03-02 19:08:50 +0000544 }
545
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000546 if ((cmd == 0) || (optind == argc)) {
Mark Whitley450736c2001-03-02 19:08:50 +0000547 show_usage();
548 }
549
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000550 fd = open(localfile, flags, 0644);
551 if (fd < 0) {
Mark Whitley450736c2001-03-02 19:08:50 +0000552 perror_msg_and_die("local file");
553 }
554
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000555 host = xgethostbyname(argv[optind]);
Mark Whitley450736c2001-03-02 19:08:50 +0000556
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000557 if (optind + 2 == argc) {
558 port = atoi(argv[optind + 1]);
559 }
560
561#ifdef BB_FEATURE_TFTP_DEBUG
Glenn L McGrathad117d82001-10-05 04:40:37 +0000562 printf("using server \"%s\", remotefile \"%s\", "
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000563 "localfile \"%s\".\n",
564 inet_ntoa(*((struct in_addr *) host->h_addr)),
565 remotefile, localfile);
566#endif
567
568 result = tftp(cmd, host, remotefile, fd, port, blocksize);
Mark Whitley450736c2001-03-02 19:08:50 +0000569
Glenn L McGrathad117d82001-10-05 04:40:37 +0000570#ifdef BB_FEATURE_CLEAN_UP
571 close(fd);
572#endif
Eric Andersen76fa8ea2001-08-20 17:47:49 +0000573 return(result);
Glenn L McGrathad117d82001-10-05 04:40:37 +0000574}