last_patch95 from vodz:
[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 int 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         int 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 = htons(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 = 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                         printf("sending %u bytes\n", len);
296                         for (cp = buf; cp < &buf[len]; cp++)
297                                 printf("%02x ", *cp);
298                         printf("\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) {
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 == htons(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                                 if (timeout == 0) {
350                                         len = -1;
351                                         bb_error_msg("last timeout");
352                                 } else {
353                                         timeout--;
354                                 }
355                                 break;
356
357                         default:
358                                 bb_perror_msg("select");
359                                 len = -1;
360                         }
361
362                 } while (timeout && (len >= 0));
363
364                 if ((finished) || (len < 0)) {
365                         break;
366                 }
367
368                 /* process received packet */
369
370
371                 opcode = ntohs(*((unsigned short *) buf));
372                 tmp = ntohs(*((unsigned short *) &buf[2]));
373
374 #ifdef CONFIG_FEATURE_TFTP_DEBUG
375                 printf("received %d bytes: %04x %04x\n", len, opcode, tmp);
376 #endif
377
378                 if (opcode == TFTP_ERROR) {
379                         char *msg = NULL;
380
381                         if (buf[4] != '\0') {
382                                 msg = &buf[4];
383                                 buf[tftp_bufsize - 1] = '\0';
384                         } else if (tmp < (sizeof(tftp_bb_error_msg) 
385                                           / sizeof(char *))) {
386
387                                 msg = (char *) tftp_bb_error_msg[tmp];
388                         }
389
390                         if (msg) {
391                                 bb_error_msg("server says: %s", msg);
392                         }
393
394                         break;
395                 }
396
397 #ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
398                 if (want_option_ack) {
399
400                          want_option_ack = 0;
401
402                          if (opcode == TFTP_OACK) {
403
404                                  /* server seems to support options */
405
406                                  char *res;
407
408                                  res = tftp_option_get(&buf[2], len-2, 
409                                                        "blksize");
410
411                                  if (res) {
412                                          int blksize = atoi(res);
413                              
414                                          if (tftp_blocksize_check(blksize,
415                                                            tftp_bufsize - 4)) {
416
417                                                  if (cmd_put) {
418                                                          opcode = TFTP_DATA;
419                                                  }
420                                                  else {
421                                                          opcode = TFTP_ACK;
422                                                  }
423 #ifdef CONFIG_FEATURE_TFTP_DEBUG
424                                                  printf("using blksize %u\n", blksize);
425 #endif
426                                                  tftp_bufsize = blksize + 4;
427                                                  block_nr = 0;
428                                                  continue;
429                                          }
430                                  }
431                                  /* FIXME:
432                                   * we should send ERROR 8 */
433                                  bb_error_msg("bad server option");
434                                  break;
435                          }
436
437                          bb_error_msg("warning: blksize not supported by server"
438                                    " - reverting to 512");
439
440                          tftp_bufsize = TFTP_BLOCKSIZE_DEFAULT + 4;
441                 }
442 #endif
443
444                 if (cmd_get && (opcode == TFTP_DATA)) {
445
446                         if (tmp == block_nr) {
447                             
448                                 len = write(localfd, &buf[4], len - 4);
449
450                                 if (len < 0) {
451                                         bb_perror_msg("write");
452                                         break;
453                                 }
454
455                                 if (len != (tftp_bufsize - 4)) {
456                                         finished++;
457                                 }
458
459                                 opcode = TFTP_ACK;
460                                 continue;
461                         }
462                 }
463
464                 if (cmd_put && (opcode == TFTP_ACK)) {
465
466                         if (tmp == (block_nr - 1)) {
467                                 if (finished) {
468                                         break;
469                                 }
470
471                                 opcode = TFTP_DATA;
472                                 continue;
473                         }
474                 }
475         }
476
477 #ifdef CONFIG_FEATURE_CLEAN_UP
478         close(socketfd);
479
480         free(buf);
481 #endif
482
483         return finished ? EXIT_SUCCESS : EXIT_FAILURE;
484 }
485
486 int tftp_main(int argc, char **argv)
487 {
488         struct hostent *host = NULL;
489         char *localfile = NULL;
490         char *remotefile = NULL;
491         int port = 69;
492         int cmd = 0;
493         int fd = -1;
494         int flags = 0;
495         int opt;
496         int result;
497         int blocksize = TFTP_BLOCKSIZE_DEFAULT;
498
499         /* figure out what to pass to getopt */
500
501 #ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
502 #define BS "b:"
503 #else
504 #define BS
505 #endif
506
507 #ifdef CONFIG_FEATURE_TFTP_GET
508 #define GET "g"
509 #else
510 #define GET 
511 #endif
512
513 #ifdef CONFIG_FEATURE_TFTP_PUT
514 #define PUT "p"
515 #else
516 #define PUT 
517 #endif
518
519         while ((opt = getopt(argc, argv, BS GET PUT "l:r:")) != -1) {
520                 switch (opt) {
521 #ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
522                 case 'b':
523                         blocksize = atoi(optarg);
524                         if (!tftp_blocksize_check(blocksize, 0)) {
525                                 return EXIT_FAILURE;
526                         }
527                         break;
528 #endif
529 #ifdef CONFIG_FEATURE_TFTP_GET
530                 case 'g':
531                         cmd = tftp_cmd_get;
532                         flags = O_WRONLY | O_CREAT;
533                         break;
534 #endif
535 #ifdef CONFIG_FEATURE_TFTP_PUT
536                 case 'p':
537                         cmd = tftp_cmd_put;
538                         flags = O_RDONLY;
539                         break;
540 #endif
541                 case 'l': 
542                         localfile = bb_xstrdup(optarg);
543                         break;
544                 case 'r':
545                         remotefile = bb_xstrdup(optarg);
546                         break;
547                 }
548         }
549
550         if ((cmd == 0) || (optind == argc)) {
551                 bb_show_usage();
552         }
553         if(localfile && strcmp(localfile, "-") == 0) {
554             fd = fileno((cmd==tftp_cmd_get)? stdout : stdin);
555         }
556         if(localfile == NULL)
557             localfile = remotefile;
558         if(remotefile == NULL)
559             remotefile = localfile;
560         if (fd==-1) {
561             fd = open(localfile, flags, 0644);
562         }
563         if (fd < 0) {
564                 bb_perror_msg_and_die("local file");
565         }
566
567         host = xgethostbyname(argv[optind]);
568
569         if (optind + 2 == argc) {
570                 port = atoi(argv[optind + 1]);
571         }
572
573 #ifdef CONFIG_FEATURE_TFTP_DEBUG
574         printf("using server \"%s\", remotefile \"%s\", "
575                 "localfile \"%s\".\n",
576                 inet_ntoa(*((struct in_addr *) host->h_addr)),
577                 remotefile, localfile);
578 #endif
579
580         result = tftp(cmd, host, remotefile, fd, port, blocksize);
581
582 #ifdef CONFIG_FEATURE_CLEAN_UP
583         if (!(fd == fileno(stdout) || fd == fileno(stdin))) {
584             close(fd);
585         }
586 #endif
587         return(result);
588 }