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