Bugfix, wastn retrieving last block, patch by Jean-Christophe Dubois
[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_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                 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                 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                                 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                                         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                                         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                 do {
290
291                         len = cp - buf;
292
293 #ifdef CONFIG_FEATURE_TFTP_DEBUG
294                         printf("sending %u bytes\n", len);
295                         for (cp = buf; cp < &buf[len]; cp++)
296                                 printf("%02x ", *cp);
297                         printf("\n");
298 #endif
299                         if (sendto(socketfd, buf, len, 0,
300                                         (struct sockaddr *) &sa, sizeof(sa)) < 0) {
301                                 perror_msg("send");
302                                 len = -1;
303                                 break;
304                         }
305
306
307                         if (finished) {
308                                 break;
309                         }
310
311                         /* receive packet */
312
313                         memset(&from, 0, sizeof(from));
314                         fromlen = sizeof(from);
315
316                         tv.tv_sec = TFTP_TIMEOUT;
317                         tv.tv_usec = 0;
318
319                         FD_ZERO(&rfds);
320                         FD_SET(socketfd, &rfds);
321
322                         switch (select(FD_SETSIZE, &rfds, NULL, NULL, &tv)) {
323                         case 1:
324                                 len = recvfrom(socketfd, buf, tftp_bufsize, 0,
325                                                 (struct sockaddr *) &from, &fromlen);
326
327                                 if (len < 0) {
328                                         perror_msg("recvfrom");
329                                         break;
330                                 }
331
332                                 timeout = 0;
333
334                                 if (sa.sin_port == htons(port)) {
335                                         sa.sin_port = from.sin_port;
336                                 }
337                                 if (sa.sin_port == from.sin_port) {
338                                         break;
339                                 }
340
341                                 /* fall-through for bad packets! */
342                                 /* discard the packet - treat as timeout */
343                                 timeout = bb_tftp_num_retries;
344
345                         case 0:
346                                 error_msg("timeout");
347
348                                 if (timeout == 0) {
349                                         len = -1;
350                                         error_msg("last timeout");
351                                 } else {
352                                         timeout--;
353                                 }
354                                 break;
355
356                         default:
357                                 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                 printf("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_error_msg) 
384                                           / sizeof(char *))) {
385
386                                 msg = (char *) tftp_error_msg[tmp];
387                         }
388
389                         if (msg) {
390                                 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                                                  printf("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                                  error_msg("bad server option");
433                                  break;
434                          }
435
436                          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 = write(localfd, &buf[4], len - 4);
448
449                                 if (len < 0) {
450                                         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 == (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 = 69;
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;
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 = xstrdup(optarg);
542                         break;
543                 case 'r':
544                         remotefile = xstrdup(optarg);
545                         break;
546                 }
547         }
548
549         if ((cmd == 0) || (optind == argc)) {
550                 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                 perror_msg_and_die("local file");
564         }
565
566         host = xgethostbyname(argv[optind]);
567
568         if (optind + 2 == argc) {
569                 port = atoi(argv[optind + 1]);
570         }
571
572 #ifdef CONFIG_FEATURE_TFTP_DEBUG
573         printf("using server \"%s\", remotefile \"%s\", "
574                 "localfile \"%s\".\n",
575                 inet_ntoa(*((struct in_addr *) host->h_addr)),
576                 remotefile, localfile);
577 #endif
578
579         result = tftp(cmd, host, remotefile, fd, port, blocksize);
580
581 #ifdef CONFIG_FEATURE_CLEAN_UP
582         if (!(fd == fileno(stdout) || fd == fileno(stdin))) {
583             close(fd);
584         }
585 #endif
586         return(result);
587 }