oops
[oweals/busybox.git] / networking / tftp.c
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
63 static 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
74 const int tftp_cmd_get = 1;
75 const int tftp_cmd_put = 2;
76
77 #ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
78
79 static 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
96 static 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
140 static 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
485 int tftp_main(int argc, char **argv)
486 {
487         struct hostent *host = NULL;
488         char *localfile = NULL;
489         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 = bb_xstrdup(optarg);
542                         break;
543                 case 'r':
544                         remotefile = bb_xstrdup(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 }