source: trunk/mindi-busybox/networking/tftp.c @ 904

Last change on this file since 904 was 821, checked in by bruno, 13 years ago

Addition of busybox 1.2.1 as a mindi-busybox new package
This should avoid delivering binary files in mindi not built there (Fedora and Debian are quite serious about that)

File size: 11.8 KB
Line 
1/* 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 * ------------------------------------------------------------------------- */
21
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25#include <sys/types.h>
26#include <sys/socket.h>
27#include <sys/stat.h>
28#include <netdb.h>
29#include <netinet/in.h>
30#include <arpa/inet.h>
31#include <unistd.h>
32#include <fcntl.h>
33
34#include "busybox.h"
35
36
37#define TFTP_BLOCKSIZE_DEFAULT 512  /* according to RFC 1350, don't change */
38#define TFTP_TIMEOUT 5  /* seconds */
39#define TFTP_NUM_RETRIES 5 /* number of retries */
40
41static const char * const MODE_OCTET = "octet";
42#define MODE_OCTET_LEN 6 /* sizeof(MODE_OCTET)*/
43
44static const char * const OPTION_BLOCKSIZE = "blksize";
45#define OPTION_BLOCKSIZE_LEN 8 /* sizeof(OPTION_BLOCKSIZE) */
46
47/* opcodes we support */
48#define TFTP_RRQ   1
49#define TFTP_WRQ   2
50#define TFTP_DATA  3
51#define TFTP_ACK   4
52#define TFTP_ERROR 5
53#define TFTP_OACK  6
54
55static const char *const tftp_bb_error_msg[] = {
56    "Undefined error",
57    "File not found",
58    "Access violation",
59    "Disk full or allocation error",
60    "Illegal TFTP operation",
61    "Unknown transfer ID",
62    "File already exists",
63    "No such user"
64};
65
66#define tftp_cmd_get ENABLE_FEATURE_TFTP_GET
67
68#if ENABLE_FEATURE_TFTP_PUT
69# define tftp_cmd_put (tftp_cmd_get+ENABLE_FEATURE_TFTP_PUT)
70#else
71# define tftp_cmd_put 0
72#endif
73
74
75#ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
76
77static int tftp_blocksize_check(int blocksize, int bufsize)
78{
79    /* Check if the blocksize is valid:
80     * RFC2348 says between 8 and 65464,
81     * but our implementation makes it impossible
82     * to use blocksizes smaller than 22 octets.
83     */
84
85    if ((bufsize && (blocksize > bufsize)) ||
86        (blocksize < 8) || (blocksize > 65564)) {
87        bb_error_msg("bad blocksize");
88        return 0;
89    }
90
91    return blocksize;
92}
93
94static char *tftp_option_get(char *buf, int len, const char * const option)
95{
96    int opt_val = 0;
97    int opt_found = 0;
98    int k;
99
100    while (len > 0) {
101
102        /* Make sure the options are terminated correctly */
103
104        for (k = 0; k < len; k++) {
105            if (buf[k] == '\0') {
106                break;
107            }
108        }
109
110        if (k >= len) {
111            break;
112        }
113
114        if (opt_val == 0) {
115            if (strcasecmp(buf, option) == 0) {
116                opt_found = 1;
117            }
118        } else {
119            if (opt_found) {
120                return buf;
121            }
122        }
123
124        k++;
125
126        buf += k;
127        len -= k;
128
129        opt_val ^= 1;
130    }
131
132    return NULL;
133}
134
135#endif
136
137static int tftp(const int cmd, const struct hostent *host,
138           const char *remotefile, const int localfd,
139           const unsigned short port, int tftp_bufsize)
140{
141    struct sockaddr_in sa;
142    struct sockaddr_in from;
143    struct timeval tv;
144    socklen_t fromlen;
145    fd_set rfds;
146    int socketfd;
147    int len;
148    int opcode = 0;
149    int finished = 0;
150    int timeout = TFTP_NUM_RETRIES;
151    unsigned short block_nr = 1;
152    unsigned short tmp;
153    char *cp;
154
155    USE_FEATURE_TFTP_BLOCKSIZE(int want_option_ack = 0;)
156
157    /* Can't use RESERVE_CONFIG_BUFFER here since the allocation
158     * size varies meaning BUFFERS_GO_ON_STACK would fail */
159    char *buf=xmalloc(tftp_bufsize += 4);
160
161    if ((socketfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
162        /* need to unlink the localfile, so don't use bb_xsocket here. */
163        bb_perror_msg("socket");
164        return EXIT_FAILURE;
165    }
166
167    len = sizeof(sa);
168
169    memset(&sa, 0, len);
170    bb_xbind(socketfd, (struct sockaddr *)&sa, len);
171
172    sa.sin_family = host->h_addrtype;
173    sa.sin_port = port;
174    memcpy(&sa.sin_addr, (struct in_addr *) host->h_addr,
175           sizeof(sa.sin_addr));
176
177    /* build opcode */
178    if (cmd & tftp_cmd_get) {
179        opcode = TFTP_RRQ;
180    }
181    if (cmd & tftp_cmd_put) {
182        opcode = TFTP_WRQ;
183    }
184
185    while (1) {
186
187        cp = buf;
188
189        /* first create the opcode part */
190        *((unsigned short *) cp) = htons(opcode);
191        cp += 2;
192
193        /* add filename and mode */
194        if (((cmd & tftp_cmd_get) && (opcode == TFTP_RRQ)) ||
195            ((cmd & tftp_cmd_put) && (opcode == TFTP_WRQ)))
196        {
197            int too_long = 0;
198
199            /* see if the filename fits into buf
200             * and fill in packet.  */
201            len = strlen(remotefile) + 1;
202
203            if ((cp + len) >= &buf[tftp_bufsize - 1]) {
204                too_long = 1;
205            } else {
206                safe_strncpy(cp, remotefile, len);
207                cp += len;
208            }
209
210            if (too_long || ((&buf[tftp_bufsize - 1] - cp) < MODE_OCTET_LEN)) {
211                bb_error_msg("remote filename too long");
212                break;
213            }
214
215            /* add "mode" part of the package */
216            memcpy(cp, MODE_OCTET, MODE_OCTET_LEN);
217            cp += MODE_OCTET_LEN;
218
219#ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
220
221            len = tftp_bufsize - 4; /* data block size */
222
223            if (len != TFTP_BLOCKSIZE_DEFAULT) {
224
225                if ((&buf[tftp_bufsize - 1] - cp) < 15) {
226                    bb_error_msg("remote filename too long");
227                    break;
228                }
229
230                /* add "blksize" + number of blocks  */
231                memcpy(cp, OPTION_BLOCKSIZE, OPTION_BLOCKSIZE_LEN);
232                cp += OPTION_BLOCKSIZE_LEN;
233                cp += snprintf(cp, 6, "%d", len) + 1;
234
235                want_option_ack = 1;
236            }
237#endif
238        }
239
240        /* add ack and data */
241
242        if (((cmd & tftp_cmd_get) && (opcode == TFTP_ACK)) ||
243            ((cmd & tftp_cmd_put) && (opcode == TFTP_DATA))) {
244
245            *((unsigned short *) cp) = htons(block_nr);
246
247            cp += 2;
248
249            block_nr++;
250
251            if ((cmd & tftp_cmd_put) && (opcode == TFTP_DATA)) {
252                len = bb_full_read(localfd, cp, tftp_bufsize - 4);
253
254                if (len < 0) {
255                    bb_perror_msg(bb_msg_read_error);
256                    break;
257                }
258
259                if (len != (tftp_bufsize - 4)) {
260                    finished++;
261                }
262
263                cp += len;
264            }
265        }
266
267
268        /* send packet */
269
270
271        timeout = TFTP_NUM_RETRIES; /* re-initialize */
272        do {
273
274            len = cp - buf;
275
276#ifdef CONFIG_DEBUG_TFTP
277            fprintf(stderr, "sending %u bytes\n", len);
278            for (cp = buf; cp < &buf[len]; cp++)
279                fprintf(stderr, "%02x ", (unsigned char) *cp);
280            fprintf(stderr, "\n");
281#endif
282            if (sendto(socketfd, buf, len, 0,
283                       (struct sockaddr *) &sa, sizeof(sa)) < 0) {
284                bb_perror_msg("send");
285                len = -1;
286                break;
287            }
288
289
290            if (finished && (opcode == TFTP_ACK)) {
291                break;
292            }
293
294            /* receive packet */
295
296            memset(&from, 0, sizeof(from));
297            fromlen = sizeof(from);
298
299            tv.tv_sec = TFTP_TIMEOUT;
300            tv.tv_usec = 0;
301
302            FD_ZERO(&rfds);
303            FD_SET(socketfd, &rfds);
304
305            switch (select(socketfd + 1, &rfds, NULL, NULL, &tv)) {
306            case 1:
307                len = recvfrom(socketfd, buf, tftp_bufsize, 0,
308                               (struct sockaddr *) &from, &fromlen);
309
310                if (len < 0) {
311                    bb_perror_msg("recvfrom");
312                    break;
313                }
314
315                timeout = 0;
316
317                if (sa.sin_port == port) {
318                    sa.sin_port = from.sin_port;
319                }
320                if (sa.sin_port == from.sin_port) {
321                    break;
322                }
323
324                /* fall-through for bad packets! */
325                /* discard the packet - treat as timeout */
326                timeout = TFTP_NUM_RETRIES;
327            case 0:
328                bb_error_msg("timeout");
329
330                timeout--;
331                if (timeout == 0) {
332                    len = -1;
333                    bb_error_msg("last timeout");
334                }
335                break;
336            default:
337                bb_perror_msg("select");
338                len = -1;
339            }
340
341        } while (timeout && (len >= 0));
342
343        if ((finished) || (len < 0)) {
344            break;
345        }
346
347        /* process received packet */
348
349        opcode = ntohs(*((unsigned short *) buf));
350        tmp = ntohs(*((unsigned short *) &buf[2]));
351
352#ifdef CONFIG_DEBUG_TFTP
353        fprintf(stderr, "received %d bytes: %04x %04x\n", len, opcode, tmp);
354#endif
355
356        if (opcode == TFTP_ERROR) {
357            const char *msg = NULL;
358
359            if (buf[4] != '\0') {
360                msg = &buf[4];
361                buf[tftp_bufsize - 1] = '\0';
362            } else if (tmp < (sizeof(tftp_bb_error_msg)
363                              / sizeof(char *))) {
364
365                msg = tftp_bb_error_msg[tmp];
366            }
367
368            if (msg) {
369                bb_error_msg("server says: %s", msg);
370            }
371
372            break;
373        }
374#ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
375        if (want_option_ack) {
376
377            want_option_ack = 0;
378
379            if (opcode == TFTP_OACK) {
380
381                /* server seems to support options */
382
383                char *res;
384
385                res = tftp_option_get(&buf[2], len - 2, OPTION_BLOCKSIZE);
386
387                if (res) {
388                    int blksize = atoi(res);
389
390                    if (tftp_blocksize_check(blksize, tftp_bufsize - 4)) {
391
392                        if (cmd & tftp_cmd_put) {
393                            opcode = TFTP_DATA;
394                        } else {
395                            opcode = TFTP_ACK;
396                        }
397#ifdef CONFIG_DEBUG_TFTP
398                        fprintf(stderr, "using %s %u\n", OPTION_BLOCKSIZE,
399                                blksize);
400#endif
401                        tftp_bufsize = blksize + 4;
402                        block_nr = 0;
403                        continue;
404                    }
405                }
406                /* FIXME:
407                 * we should send ERROR 8 */
408                bb_error_msg("bad server option");
409                break;
410            }
411
412            bb_error_msg("warning: blksize not supported by server"
413                         " - reverting to 512");
414
415            tftp_bufsize = TFTP_BLOCKSIZE_DEFAULT + 4;
416        }
417#endif
418
419        if ((cmd & tftp_cmd_get) && (opcode == TFTP_DATA)) {
420
421            if (tmp == block_nr) {
422
423                len = bb_full_write(localfd, &buf[4], len - 4);
424
425                if (len < 0) {
426                    bb_perror_msg(bb_msg_write_error);
427                    break;
428                }
429
430                if (len != (tftp_bufsize - 4)) {
431                    finished++;
432                }
433
434                opcode = TFTP_ACK;
435                continue;
436            }
437            /* in case the last ack disappeared into the ether */
438            if (tmp == (block_nr - 1)) {
439                --block_nr;
440                opcode = TFTP_ACK;
441                continue;
442            } else if (tmp + 1 == block_nr) {
443                /* Server lost our TFTP_ACK.  Resend it */
444                block_nr = tmp;
445                opcode = TFTP_ACK;
446                continue;
447            }
448        }
449
450        if ((cmd & tftp_cmd_put) && (opcode == TFTP_ACK)) {
451
452            if (tmp == (unsigned short) (block_nr - 1)) {
453                if (finished) {
454                    break;
455                }
456
457                opcode = TFTP_DATA;
458                continue;
459            }
460        }
461    }
462
463#ifdef CONFIG_FEATURE_CLEAN_UP
464    close(socketfd);
465    free(buf);
466#endif
467
468    return finished ? EXIT_SUCCESS : EXIT_FAILURE;
469}
470
471int tftp_main(int argc, char **argv)
472{
473    struct hostent *host = NULL;
474    const char *localfile = NULL;
475    const char *remotefile = NULL;
476    int port;
477    int cmd = 0;
478    int fd = -1;
479    int flags = 0;
480    int result;
481    int blocksize = TFTP_BLOCKSIZE_DEFAULT;
482
483    /* figure out what to pass to getopt */
484
485#ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
486    char *sblocksize = NULL;
487
488#define BS "b:"
489#define BS_ARG , &sblocksize
490#else
491#define BS
492#define BS_ARG
493#endif
494
495#ifdef CONFIG_FEATURE_TFTP_GET
496#define GET "g"
497#define GET_COMPL ":g"
498#else
499#define GET
500#define GET_COMPL
501#endif
502
503#ifdef CONFIG_FEATURE_TFTP_PUT
504#define PUT "p"
505#define PUT_COMPL ":p"
506#else
507#define PUT
508#define PUT_COMPL
509#endif
510
511#if defined(CONFIG_FEATURE_TFTP_GET) && defined(CONFIG_FEATURE_TFTP_PUT)
512    bb_opt_complementally = GET_COMPL PUT_COMPL ":?g--p:p--g";
513#elif defined(CONFIG_FEATURE_TFTP_GET) || defined(CONFIG_FEATURE_TFTP_PUT)
514    bb_opt_complementally = GET_COMPL PUT_COMPL;
515#endif
516
517
518    cmd = bb_getopt_ulflags(argc, argv, GET PUT "l:r:" BS,
519                            &localfile, &remotefile BS_ARG);
520
521    cmd &= (tftp_cmd_get | tftp_cmd_put);
522#ifdef CONFIG_FEATURE_TFTP_GET
523    if (cmd == tftp_cmd_get)
524        flags = O_WRONLY | O_CREAT | O_TRUNC;
525#endif
526#ifdef CONFIG_FEATURE_TFTP_PUT
527    if (cmd == tftp_cmd_put)
528        flags = O_RDONLY;
529#endif
530
531#ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
532    if (sblocksize) {
533        blocksize = atoi(sblocksize);
534        if (!tftp_blocksize_check(blocksize, 0)) {
535            return EXIT_FAILURE;
536        }
537    }
538#endif
539
540    if (localfile == NULL)
541        localfile = remotefile;
542    if (remotefile == NULL)
543        remotefile = localfile;
544    if ((localfile == NULL && remotefile == NULL) || (argv[optind] == NULL))
545        bb_show_usage();
546
547    if (localfile == NULL || strcmp(localfile, "-") == 0) {
548        fd = (cmd == tftp_cmd_get) ? STDOUT_FILENO : STDIN_FILENO;
549    } else {
550        fd = open(localfile, flags, 0644); /* fail below */
551    }
552    if (fd < 0) {
553        bb_perror_msg_and_die("local file");
554    }
555
556    host = xgethostbyname(argv[optind]);
557    port = bb_lookup_port(argv[optind + 1], "udp", 69);
558
559#ifdef CONFIG_DEBUG_TFTP
560    fprintf(stderr, "using server \"%s\", remotefile \"%s\", "
561            "localfile \"%s\".\n",
562            inet_ntoa(*((struct in_addr *) host->h_addr)),
563            remotefile, localfile);
564#endif
565
566    result = tftp(cmd, host, remotefile, fd, port, blocksize);
567
568    if (!(fd == STDOUT_FILENO || fd == STDIN_FILENO)) {
569        if (ENABLE_FEATURE_CLEAN_UP)
570            close(fd);
571        if (cmd == tftp_cmd_get && result != EXIT_SUCCESS)
572            unlink(localfile);
573    }
574    return (result);
575}
Note: See TracBrowser for help on using the repository browser.