Patch from Ben Low <ben@titr.uow.edu.au> to allow tftp to work
[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                         } else if (finished) {
283                                 break;
284                         }
285                 }
286
287
288                 /* send packet */
289
290
291                 do {
292
293                         len = cp - buf;
294
295 #ifdef CONFIG_FEATURE_TFTP_DEBUG
296                         printf("sending %u bytes\n", len);
297                         for (cp = buf; cp < &buf[len]; cp++)
298                                 printf("%02x ", *cp);
299                         printf("\n");
300 #endif
301                         if (sendto(socketfd, buf, len, 0,
302                                         (struct sockaddr *) &sa, sizeof(sa)) < 0) {
303                                 perror_msg("send");
304                                 len = -1;
305                                 break;
306                         }
307
308
309                         /* receive packet */
310
311
312                         memset(&from, 0, sizeof(from));
313                         fromlen = sizeof(from);
314
315                         tv.tv_sec = TFTP_TIMEOUT;
316                         tv.tv_usec = 0;
317
318                         FD_ZERO(&rfds);
319                         FD_SET(socketfd, &rfds);
320
321                         switch (select(FD_SETSIZE, &rfds, NULL, NULL, &tv)) {
322                         case 1:
323                                 len = recvfrom(socketfd, buf, tftp_bufsize, 0,
324                                                 (struct sockaddr *) &from, &fromlen);
325
326                                 if (len < 0) {
327                                         perror_msg("recvfrom");
328                                         break;
329                                 }
330
331                                 timeout = 0;
332
333                                 if (sa.sin_port == htons(port)) {
334                                         sa.sin_port = from.sin_port;
335                                 }
336                                 if (sa.sin_port == from.sin_port) {
337                                         break;
338                                 }
339
340                                 /* fall-through for bad packets! */
341                                 /* discard the packet - treat as timeout */
342                                 timeout = bb_tftp_num_retries;
343
344                         case 0:
345                                 error_msg("timeout");
346
347                                 if (timeout == 0) {
348                                         len = -1;
349                                         error_msg("last timeout");
350                                 } else {
351                                         timeout--;
352                                 }
353                                 break;
354
355                         default:
356                                 perror_msg("select");
357                                 len = -1;
358                         }
359
360                 } while (timeout && (len >= 0));
361
362                 if (len < 0) {
363                         break;
364                 }
365
366                 /* process received packet */
367
368
369                 opcode = ntohs(*((unsigned short *) buf));
370                 tmp = ntohs(*((unsigned short *) &buf[2]));
371
372 #ifdef CONFIG_FEATURE_TFTP_DEBUG
373                 printf("received %d bytes: %04x %04x\n", len, opcode, tmp);
374 #endif
375
376                 if (opcode == TFTP_ERROR) {
377                         char *msg = NULL;
378
379                         if (buf[4] != '\0') {
380                                 msg = &buf[4];
381                                 buf[tftp_bufsize - 1] = '\0';
382                         } else if (tmp < (sizeof(tftp_error_msg) 
383                                           / sizeof(char *))) {
384
385                                 msg = (char *) tftp_error_msg[tmp];
386                         }
387
388                         if (msg) {
389                                 error_msg("server says: %s", msg);
390                         }
391
392                         break;
393                 }
394
395 #ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
396                 if (want_option_ack) {
397
398                          want_option_ack = 0;
399
400                          if (opcode == TFTP_OACK) {
401
402                                  /* server seems to support options */
403
404                                  char *res;
405
406                                  res = tftp_option_get(&buf[2], len-2, 
407                                                        "blksize");
408
409                                  if (res) {
410                                          int foo = atoi(res);
411                              
412                                          if (tftp_blocksize_check(foo,
413                                                            tftp_bufsize - 4)) {
414
415                                                  if (cmd_put) {
416                                                          opcode = TFTP_DATA;
417                                                  }
418                                                  else {
419                                                          opcode = TFTP_ACK;
420                                                  }
421 #ifdef CONFIG_FEATURE_TFTP_DEBUG
422                                                  printf("using blksize %u\n");
423 #endif
424                                                  tftp_bufsize = foo + 4;
425                                                  block_nr = 0;
426                                                  continue;
427                                          }
428                                  }
429                                  /* FIXME:
430                                   * we should send ERROR 8 */
431                                  error_msg("bad server option");
432                                  break;
433                          }
434
435                          error_msg("warning: blksize not supported by server"
436                                    " - reverting to 512");
437
438                          tftp_bufsize = TFTP_BLOCKSIZE_DEFAULT + 4;
439                 }
440 #endif
441
442                 if (cmd_get && (opcode == TFTP_DATA)) {
443
444                         if (tmp == block_nr) {
445                             
446                                 len = write(localfd, &buf[4], len - 4);
447
448                                 if (len < 0) {
449                                         perror_msg("write");
450                                         break;
451                                 }
452
453                                 if (len != (tftp_bufsize - 4)) {
454                                         finished++;
455                                 }
456
457                                 opcode = TFTP_ACK;
458                                 continue;
459                         }
460                 }
461
462                 if (cmd_put && (opcode == TFTP_ACK)) {
463
464                         if (tmp == (block_nr - 1)) {
465                                 if (finished) {
466                                         break;
467                                 }
468
469                                 opcode = TFTP_DATA;
470                                 continue;
471                         }
472                 }
473         }
474
475 #ifdef CONFIG_FEATURE_CLEAN_UP
476         close(socketfd);
477
478         RELEASE_CONFIG_BUFFER(buf);
479 #endif
480
481         return finished ? EXIT_SUCCESS : EXIT_FAILURE;
482 }
483
484 int tftp_main(int argc, char **argv)
485 {
486         struct hostent *host = NULL;
487         char *localfile = NULL;
488         char *remotefile = NULL;
489         int port = 69;
490         int cmd = 0;
491         int fd = -1;
492         int flags = 0;
493         int opt;
494         int result;
495         int blocksize = TFTP_BLOCKSIZE_DEFAULT;
496
497         /* figure out what to pass to getopt */
498
499 #ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
500 #define BS "b:"
501 #else
502 #define BS
503 #endif
504
505 #ifdef CONFIG_FEATURE_TFTP_GET
506 #define GET "g"
507 #else
508 #define GET 
509 #endif
510
511 #ifdef CONFIG_FEATURE_TFTP_PUT
512 #define PUT "p"
513 #else
514 #define PUT 
515 #endif
516
517         while ((opt = getopt(argc, argv, BS GET PUT "l:r:")) != -1) {
518                 switch (opt) {
519 #ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
520                 case 'b':
521                         blocksize = atoi(optarg);
522                         if (!tftp_blocksize_check(blocksize, 0)) {
523                                 return EXIT_FAILURE;
524                         }
525                         break;
526 #endif
527 #ifdef CONFIG_FEATURE_TFTP_GET
528                 case 'g':
529                         cmd = tftp_cmd_get;
530                         flags = O_WRONLY | O_CREAT;
531                         break;
532 #endif
533 #ifdef CONFIG_FEATURE_TFTP_PUT
534                 case 'p':
535                         cmd = tftp_cmd_put;
536                         flags = O_RDONLY;
537                         break;
538 #endif
539                 case 'l': 
540                         localfile = xstrdup(optarg);
541                         break;
542                 case 'r':
543                         remotefile = xstrdup(optarg);
544                         break;
545                 }
546         }
547
548         if ((cmd == 0) || (optind == argc)) {
549                 show_usage();
550         }
551         if(localfile && strcmp(localfile, "-") == 0) {
552             fd = fileno((cmd==tftp_cmd_get)? stdin : stdout);
553         }
554         if(localfile == NULL)
555             localfile = remotefile;
556         if(remotefile == NULL)
557             remotefile = localfile;
558         if (fd==-1) {
559             fd = open(localfile, flags, 0644);
560         }
561         if (fd < 0) {
562                 perror_msg_and_die("local file");
563         }
564
565         host = xgethostbyname(argv[optind]);
566
567         if (optind + 2 == argc) {
568                 port = atoi(argv[optind + 1]);
569         }
570
571 #ifdef CONFIG_FEATURE_TFTP_DEBUG
572         printf("using server \"%s\", remotefile \"%s\", "
573                 "localfile \"%s\".\n",
574                 inet_ntoa(*((struct in_addr *) host->h_addr)),
575                 remotefile, localfile);
576 #endif
577
578         result = tftp(cmd, host, remotefile, fd, port, blocksize);
579
580 #ifdef CONFIG_FEATURE_CLEAN_UP
581         if (!(fd == fileno(stdout) || fd == fileno(stdin))) {
582             close(fd);
583         }
584 #endif
585         return(result);
586 }