aboutsummaryrefslogtreecommitdiff
path: root/busybox/networking/tftp.c
diff options
context:
space:
mode:
Diffstat (limited to 'busybox/networking/tftp.c')
-rw-r--r--busybox/networking/tftp.c584
1 files changed, 584 insertions, 0 deletions
diff --git a/busybox/networking/tftp.c b/busybox/networking/tftp.c
new file mode 100644
index 000000000..3c947318b
--- /dev/null
+++ b/busybox/networking/tftp.c
@@ -0,0 +1,584 @@
1/* ------------------------------------------------------------------------- */
2/* tftp.c */
3/* */
4/* A simple tftp client for busybox. */
5/* Tries to follow RFC1350. */
6/* Only "octet" mode supported. */
7/* Optional blocksize negotiation (RFC2347 + RFC2348) */
8/* */
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 CONFIG_FEATURE_TFTP_DEBUG
50
51#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
63static const char *tftp_bb_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
74const int tftp_cmd_get = 1;
75const int tftp_cmd_put = 2;
76
77#ifdef CONFIG_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 bb_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
140static inline int tftp(const int cmd, const struct hostent *host,
141 const char *remotefile, int localfd, const unsigned short port, int tftp_bufsize)
142{
143 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
147 struct sockaddr_in sa;
148 struct sockaddr_in from;
149 struct timeval tv;
150 socklen_t fromlen;
151 fd_set rfds;
152 char *cp;
153 unsigned short tmp;
154 int socketfd;
155 int len;
156 int opcode = 0;
157 int finished = 0;
158 int timeout = bb_tftp_num_retries;
159 unsigned short block_nr = 1;
160
161#ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
162 int want_option_ack = 0;
163#endif
164
165 /* Can't use RESERVE_CONFIG_BUFFER here since the allocation
166 * size varies meaning BUFFERS_GO_ON_STACK would fail */
167 char *buf=xmalloc(tftp_bufsize + 4);
168
169 tftp_bufsize += 4;
170
171 if ((socketfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
172 bb_perror_msg("socket");
173 return EXIT_FAILURE;
174 }
175
176 len = sizeof(sa);
177
178 memset(&sa, 0, len);
179 bind(socketfd, (struct sockaddr *)&sa, len);
180
181 sa.sin_family = host->h_addrtype;
182 sa.sin_port = port;
183 memcpy(&sa.sin_addr, (struct in_addr *) host->h_addr,
184 sizeof(sa.sin_addr));
185
186 /* build opcode */
187
188 if (cmd_get) {
189 opcode = TFTP_RRQ;
190 }
191
192 if (cmd_put) {
193 opcode = TFTP_WRQ;
194 }
195
196 while (1) {
197
198 cp = buf;
199
200 /* first create the opcode part */
201
202 *((unsigned short *) cp) = htons(opcode);
203
204 cp += 2;
205
206 /* add filename and mode */
207
208 if ((cmd_get && (opcode == TFTP_RRQ)) ||
209 (cmd_put && (opcode == TFTP_WRQ))) {
210 int too_long = 0;
211
212 /* see if the filename fits into buf */
213 /* and fill in packet */
214
215 len = strlen(remotefile) + 1;
216
217 if ((cp + len) >= &buf[tftp_bufsize - 1]) {
218 too_long = 1;
219 }
220 else {
221 safe_strncpy(cp, remotefile, len);
222 cp += len;
223 }
224
225 if (too_long || ((&buf[tftp_bufsize - 1] - cp) < 6)) {
226 bb_error_msg("too long remote-filename");
227 break;
228 }
229
230 /* add "mode" part of the package */
231
232 memcpy(cp, "octet", 6);
233 cp += 6;
234
235#ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
236
237 len = tftp_bufsize - 4; /* data block size */
238
239 if (len != TFTP_BLOCKSIZE_DEFAULT) {
240
241 if ((&buf[tftp_bufsize - 1] - cp) < 15) {
242 bb_error_msg("too long remote-filename");
243 break;
244 }
245
246 /* add "blksize" + number of blocks */
247
248 memcpy(cp, "blksize", 8);
249 cp += 8;
250
251 cp += snprintf(cp, 6, "%d", len) + 1;
252
253 want_option_ack = 1;
254 }
255#endif
256 }
257
258 /* add ack and data */
259
260 if ((cmd_get && (opcode == TFTP_ACK)) ||
261 (cmd_put && (opcode == TFTP_DATA))) {
262
263 *((unsigned short *) cp) = htons(block_nr);
264
265 cp += 2;
266
267 block_nr++;
268
269 if (cmd_put && (opcode == TFTP_DATA)) {
270 len = bb_full_read(localfd, cp, tftp_bufsize - 4);
271
272 if (len < 0) {
273 bb_perror_msg("read");
274 break;
275 }
276
277 if (len != (tftp_bufsize - 4)) {
278 finished++;
279 }
280
281 cp += len;
282 }
283 }
284
285
286 /* send packet */
287
288
289 timeout = bb_tftp_num_retries; /* re-initialize */
290 do {
291
292 len = cp - buf;
293
294#ifdef CONFIG_FEATURE_TFTP_DEBUG
295 fprintf(stderr, "sending %u bytes\n", len);
296 for (cp = buf; cp < &buf[len]; cp++)
297 fprintf(stderr, "%02x ", (unsigned char)*cp);
298 fprintf(stderr, "\n");
299#endif
300 if (sendto(socketfd, buf, len, 0,
301 (struct sockaddr *) &sa, sizeof(sa)) < 0) {
302 bb_perror_msg("send");
303 len = -1;
304 break;
305 }
306
307
308 if (finished && (opcode == TFTP_ACK)) {
309 break;
310 }
311
312 /* receive packet */
313
314 memset(&from, 0, sizeof(from));
315 fromlen = sizeof(from);
316
317 tv.tv_sec = TFTP_TIMEOUT;
318 tv.tv_usec = 0;
319
320 FD_ZERO(&rfds);
321 FD_SET(socketfd, &rfds);
322
323 switch (select(FD_SETSIZE, &rfds, NULL, NULL, &tv)) {
324 case 1:
325 len = recvfrom(socketfd, buf, tftp_bufsize, 0,
326 (struct sockaddr *) &from, &fromlen);
327
328 if (len < 0) {
329 bb_perror_msg("recvfrom");
330 break;
331 }
332
333 timeout = 0;
334
335 if (sa.sin_port == port) {
336 sa.sin_port = from.sin_port;
337 }
338 if (sa.sin_port == from.sin_port) {
339 break;
340 }
341
342 /* fall-through for bad packets! */
343 /* discard the packet - treat as timeout */
344 timeout = bb_tftp_num_retries;
345
346 case 0:
347 bb_error_msg("timeout");
348
349 timeout--;
350 if (timeout == 0) {
351 len = -1;
352 bb_error_msg("last timeout");
353 }
354 break;
355
356 default:
357 bb_perror_msg("select");
358 len = -1;
359 }
360
361 } while (timeout && (len >= 0));
362
363 if ((finished) || (len < 0)) {
364 break;
365 }
366
367 /* process received packet */
368
369
370 opcode = ntohs(*((unsigned short *) buf));
371 tmp = ntohs(*((unsigned short *) &buf[2]));
372
373#ifdef CONFIG_FEATURE_TFTP_DEBUG
374 fprintf(stderr, "received %d bytes: %04x %04x\n", len, opcode, tmp);
375#endif
376
377 if (opcode == TFTP_ERROR) {
378 char *msg = NULL;
379
380 if (buf[4] != '\0') {
381 msg = &buf[4];
382 buf[tftp_bufsize - 1] = '\0';
383 } else if (tmp < (sizeof(tftp_bb_error_msg)
384 / sizeof(char *))) {
385
386 msg = (char *) tftp_bb_error_msg[tmp];
387 }
388
389 if (msg) {
390 bb_error_msg("server says: %s", msg);
391 }
392
393 break;
394 }
395
396#ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
397 if (want_option_ack) {
398
399 want_option_ack = 0;
400
401 if (opcode == TFTP_OACK) {
402
403 /* server seems to support options */
404
405 char *res;
406
407 res = tftp_option_get(&buf[2], len-2,
408 "blksize");
409
410 if (res) {
411 int blksize = atoi(res);
412
413 if (tftp_blocksize_check(blksize,
414 tftp_bufsize - 4)) {
415
416 if (cmd_put) {
417 opcode = TFTP_DATA;
418 }
419 else {
420 opcode = TFTP_ACK;
421 }
422#ifdef CONFIG_FEATURE_TFTP_DEBUG
423 fprintf(stderr, "using blksize %u\n", blksize);
424#endif
425 tftp_bufsize = blksize + 4;
426 block_nr = 0;
427 continue;
428 }
429 }
430 /* FIXME:
431 * we should send ERROR 8 */
432 bb_error_msg("bad server option");
433 break;
434 }
435
436 bb_error_msg("warning: blksize not supported by server"
437 " - reverting to 512");
438
439 tftp_bufsize = TFTP_BLOCKSIZE_DEFAULT + 4;
440 }
441#endif
442
443 if (cmd_get && (opcode == TFTP_DATA)) {
444
445 if (tmp == block_nr) {
446
447 len = bb_full_write(localfd, &buf[4], len - 4);
448
449 if (len < 0) {
450 bb_perror_msg("write");
451 break;
452 }
453
454 if (len != (tftp_bufsize - 4)) {
455 finished++;
456 }
457
458 opcode = TFTP_ACK;
459 continue;
460 }
461 }
462
463 if (cmd_put && (opcode == TFTP_ACK)) {
464
465 if (tmp == (unsigned short)(block_nr - 1)) {
466 if (finished) {
467 break;
468 }
469
470 opcode = TFTP_DATA;
471 continue;
472 }
473 }
474 }
475
476#ifdef CONFIG_FEATURE_CLEAN_UP
477 close(socketfd);
478
479 free(buf);
480#endif
481
482 return finished ? EXIT_SUCCESS : EXIT_FAILURE;
483}
484
485int tftp_main(int argc, char **argv)
486{
487 struct hostent *host = NULL;
488 const char *localfile = NULL;
489 const char *remotefile = NULL;
490 int port;
491 int cmd = 0;
492 int fd = -1;
493 int flags = 0;
494 int opt;
495 int result;
496 int blocksize = TFTP_BLOCKSIZE_DEFAULT;
497
498 /* figure out what to pass to getopt */
499
500#ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
501#define BS "b:"
502#else
503#define BS
504#endif
505
506#ifdef CONFIG_FEATURE_TFTP_GET
507#define GET "g"
508#else
509#define GET
510#endif
511
512#ifdef CONFIG_FEATURE_TFTP_PUT
513#define PUT "p"
514#else
515#define PUT
516#endif
517
518 while ((opt = getopt(argc, argv, BS GET PUT "l:r:")) != -1) {
519 switch (opt) {
520#ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
521 case 'b':
522 blocksize = atoi(optarg);
523 if (!tftp_blocksize_check(blocksize, 0)) {
524 return EXIT_FAILURE;
525 }
526 break;
527#endif
528#ifdef CONFIG_FEATURE_TFTP_GET
529 case 'g':
530 cmd = tftp_cmd_get;
531 flags = O_WRONLY | O_CREAT | O_TRUNC;
532 break;
533#endif
534#ifdef CONFIG_FEATURE_TFTP_PUT
535 case 'p':
536 cmd = tftp_cmd_put;
537 flags = O_RDONLY;
538 break;
539#endif
540 case 'l':
541 localfile = optarg;
542 break;
543 case 'r':
544 remotefile = optarg;
545 break;
546 }
547 }
548
549 if ((cmd == 0) || (optind == argc)) {
550 bb_show_usage();
551 }
552 if(localfile && strcmp(localfile, "-") == 0) {
553 fd = fileno((cmd==tftp_cmd_get)? stdout : stdin);
554 }
555 if(localfile == NULL)
556 localfile = remotefile;
557 if(remotefile == NULL)
558 remotefile = localfile;
559 if (fd==-1) {
560 fd = open(localfile, flags, 0644);
561 }
562 if (fd < 0) {
563 bb_perror_msg_and_die("local file");
564 }
565
566 host = xgethostbyname(argv[optind]);
567 port = bb_lookup_port(argv[optind + 1], "udp", 69);
568
569#ifdef CONFIG_FEATURE_TFTP_DEBUG
570 fprintf(stderr, "using server \"%s\", remotefile \"%s\", "
571 "localfile \"%s\".\n",
572 inet_ntoa(*((struct in_addr *) host->h_addr)),
573 remotefile, localfile);
574#endif
575
576 result = tftp(cmd, host, remotefile, fd, port, blocksize);
577
578#ifdef CONFIG_FEATURE_CLEAN_UP
579 if (!(fd == STDOUT_FILENO || fd == STDIN_FILENO)) {
580 close(fd);
581 }
582#endif
583 return(result);
584}