Magellan Linux

Contents of /trunk/mkinitrd-magellan/busybox/networking/tftp.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 532 - (show annotations) (download)
Sat Sep 1 22:45:15 2007 UTC (16 years, 8 months ago) by niro
File MIME type: text/plain
File size: 11473 byte(s)
-import if magellan mkinitrd; it is a fork of redhats mkinitrd-5.0.8 with all magellan patches and features; deprecates magellan-src/mkinitrd

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 "busybox.h"
23
24
25 #if ENABLE_FEATURE_TFTP_GET || ENABLE_FEATURE_TFTP_PUT
26
27 #define TFTP_BLOCKSIZE_DEFAULT 512 /* according to RFC 1350, don't change */
28 #define TFTP_TIMEOUT 5 /* seconds */
29 #define TFTP_NUM_RETRIES 5 /* number of retries */
30
31 /* opcodes we support */
32 #define TFTP_RRQ 1
33 #define TFTP_WRQ 2
34 #define TFTP_DATA 3
35 #define TFTP_ACK 4
36 #define TFTP_ERROR 5
37 #define TFTP_OACK 6
38
39 static const char *const tftp_bb_error_msg[] = {
40 "Undefined error",
41 "File not found",
42 "Access violation",
43 "Disk full or allocation error",
44 "Illegal TFTP operation",
45 "Unknown transfer ID",
46 "File already exists",
47 "No such user"
48 };
49
50 #if ENABLE_FEATURE_TFTP_GET && !ENABLE_FEATURE_TFTP_PUT
51 #define USE_GETPUT(a)
52 #define CMD_GET(cmd) 1
53 #define CMD_PUT(cmd) 0
54 #elif !ENABLE_FEATURE_TFTP_GET && ENABLE_FEATURE_TFTP_PUT
55 #define USE_GETPUT(a)
56 #define CMD_GET(cmd) 0
57 #define CMD_PUT(cmd) 1
58 #else
59 #define USE_GETPUT(a) a
60 /* masks coming from getpot32 */
61 #define CMD_GET(cmd) ((cmd) & 1)
62 #define CMD_PUT(cmd) ((cmd) & 2)
63 #endif
64 /* NB: in the code below
65 * CMD_GET(cmd) and CMD_GET(cmd) are mutually exclusive
66 */
67
68
69 #if ENABLE_FEATURE_TFTP_BLOCKSIZE
70
71 static int tftp_blocksize_check(int blocksize, int bufsize)
72 {
73 /* Check if the blocksize is valid:
74 * RFC2348 says between 8 and 65464,
75 * but our implementation makes it impossible
76 * to use blocksizes smaller than 22 octets.
77 */
78
79 if ((bufsize && (blocksize > bufsize))
80 || (blocksize < 8) || (blocksize > 65564)
81 ) {
82 bb_error_msg("bad blocksize");
83 return 0;
84 }
85
86 return blocksize;
87 }
88
89 static char *tftp_option_get(char *buf, int len, const char * const option)
90 {
91 int opt_val = 0;
92 int opt_found = 0;
93 int k;
94
95 while (len > 0) {
96 /* Make sure the options are terminated correctly */
97
98 for (k = 0; k < len; k++) {
99 if (buf[k] == '\0') {
100 break;
101 }
102 }
103
104 if (k >= len) {
105 break;
106 }
107
108 if (opt_val == 0) {
109 if (strcasecmp(buf, option) == 0) {
110 opt_found = 1;
111 }
112 } else {
113 if (opt_found) {
114 return buf;
115 }
116 }
117
118 k++;
119
120 buf += k;
121 len -= k;
122
123 opt_val ^= 1;
124 }
125
126 return NULL;
127 }
128
129 #endif
130
131 static int tftp(
132 #if ENABLE_FEATURE_TFTP_GET && ENABLE_FEATURE_TFTP_PUT
133 const int cmd,
134 #endif
135 len_and_sockaddr *peer_lsa,
136 const char *remotefile, const int localfd,
137 unsigned port, int tftp_bufsize)
138 {
139 struct timeval tv;
140 fd_set rfds;
141 int socketfd;
142 int len;
143 int opcode = 0;
144 int finished = 0;
145 int timeout = TFTP_NUM_RETRIES;
146 uint16_t block_nr = 1;
147 uint16_t tmp;
148 char *cp;
149
150 USE_FEATURE_TFTP_BLOCKSIZE(int want_option_ack = 0;)
151
152 unsigned org_port;
153 len_and_sockaddr *const from = alloca(offsetof(len_and_sockaddr, sa) + peer_lsa->len);
154
155 /* Can't use RESERVE_CONFIG_BUFFER here since the allocation
156 * size varies meaning BUFFERS_GO_ON_STACK would fail */
157 /* We must keep the transmit and receive buffers seperate */
158 /* In case we rcv a garbage pkt and we need to rexmit the last pkt */
159 char *xbuf = xmalloc(tftp_bufsize += 4);
160 char *rbuf = xmalloc(tftp_bufsize);
161
162 port = org_port = htons(port);
163
164 socketfd = xsocket(peer_lsa->sa.sa_family, SOCK_DGRAM, 0);
165
166 /* build opcode */
167 opcode = TFTP_WRQ;
168 if (CMD_GET(cmd)) {
169 opcode = TFTP_RRQ;
170 }
171
172 while (1) {
173 cp = xbuf;
174
175 /* first create the opcode part */
176 /* (this 16bit store is aligned) */
177 *((uint16_t*)cp) = htons(opcode);
178 cp += 2;
179
180 /* add filename and mode */
181 if (CMD_GET(cmd) ? (opcode == TFTP_RRQ) : (opcode == TFTP_WRQ)) {
182 int too_long = 0;
183
184 /* see if the filename fits into xbuf
185 * and fill in packet. */
186 len = strlen(remotefile) + 1;
187
188 if ((cp + len) >= &xbuf[tftp_bufsize - 1]) {
189 too_long = 1;
190 } else {
191 safe_strncpy(cp, remotefile, len);
192 cp += len;
193 }
194
195 if (too_long || (&xbuf[tftp_bufsize - 1] - cp) < sizeof("octet")) {
196 bb_error_msg("remote filename too long");
197 break;
198 }
199
200 /* add "mode" part of the package */
201 memcpy(cp, "octet", sizeof("octet"));
202 cp += sizeof("octet");
203
204 #if ENABLE_FEATURE_TFTP_BLOCKSIZE
205
206 len = tftp_bufsize - 4; /* data block size */
207
208 if (len != TFTP_BLOCKSIZE_DEFAULT) {
209
210 if ((&xbuf[tftp_bufsize - 1] - cp) < 15) {
211 bb_error_msg("remote filename too long");
212 break;
213 }
214
215 /* add "blksize" + number of blocks */
216 memcpy(cp, "blksize", sizeof("blksize"));
217 cp += sizeof("blksize");
218 cp += snprintf(cp, 6, "%d", len) + 1;
219
220 want_option_ack = 1;
221 }
222 #endif
223 }
224
225 /* add ack and data */
226
227 if (CMD_GET(cmd) ? (opcode == TFTP_ACK) : (opcode == TFTP_DATA)) {
228 /* TODO: unaligned access! */
229 *((uint16_t*)cp) = htons(block_nr);
230 cp += 2;
231 block_nr++;
232
233 if (CMD_PUT(cmd) && (opcode == TFTP_DATA)) {
234 len = full_read(localfd, cp, tftp_bufsize - 4);
235
236 if (len < 0) {
237 bb_perror_msg(bb_msg_read_error);
238 break;
239 }
240
241 if (len != (tftp_bufsize - 4)) {
242 finished++;
243 }
244
245 cp += len;
246 }
247 }
248
249 /* send packet */
250
251 timeout = TFTP_NUM_RETRIES; /* re-initialize */
252 do {
253 len = cp - xbuf;
254 #if ENABLE_DEBUG_TFTP
255 fprintf(stderr, "sending %u bytes\n", len);
256 for (cp = xbuf; cp < &xbuf[len]; cp++)
257 fprintf(stderr, "%02x ", (unsigned char) *cp);
258 fprintf(stderr, "\n");
259 #endif
260 if (sendto(socketfd, xbuf, len, 0,
261 &peer_lsa->sa, peer_lsa->len) < 0) {
262 bb_perror_msg("send");
263 len = -1;
264 break;
265 }
266
267 if (finished && (opcode == TFTP_ACK)) {
268 break;
269 }
270
271 /* receive packet */
272 recv_again:
273 tv.tv_sec = TFTP_TIMEOUT;
274 tv.tv_usec = 0;
275
276 FD_ZERO(&rfds);
277 FD_SET(socketfd, &rfds);
278
279 switch (select(socketfd + 1, &rfds, NULL, NULL, &tv)) {
280 unsigned from_port;
281 case 1:
282 from->len = peer_lsa->len;
283 memset(&from->sa, 0, peer_lsa->len);
284 len = recvfrom(socketfd, rbuf, tftp_bufsize, 0,
285 &from->sa, &from->len);
286 if (len < 0) {
287 bb_perror_msg("recvfrom");
288 break;
289 }
290 from_port = get_nport(from);
291 if (port == org_port) {
292 /* Our first query went to port 69
293 * but reply will come from different one.
294 * Remember and use this new port */
295 port = from_port;
296 set_nport(peer_lsa, from_port);
297 }
298 if (port != from_port)
299 goto recv_again;
300 timeout = 0;
301 break;
302 case 0:
303 bb_error_msg("timeout");
304 timeout--;
305 if (timeout == 0) {
306 len = -1;
307 bb_error_msg("last timeout");
308 }
309 break;
310 default:
311 bb_perror_msg("select");
312 len = -1;
313 }
314
315 } while (timeout && (len >= 0));
316
317 if (finished || (len < 0)) {
318 break;
319 }
320
321 /* process received packet */
322 /* (both accesses seems to be aligned) */
323
324 opcode = ntohs( ((uint16_t*)rbuf)[0] );
325 tmp = ntohs( ((uint16_t*)rbuf)[1] );
326
327 #if ENABLE_DEBUG_TFTP
328 fprintf(stderr, "received %d bytes: %04x %04x\n", len, opcode, tmp);
329 #endif
330
331 if (opcode == TFTP_ERROR) {
332 const char *msg = NULL;
333
334 if (rbuf[4] != '\0') {
335 msg = &rbuf[4];
336 rbuf[tftp_bufsize - 1] = '\0';
337 } else if (tmp < (sizeof(tftp_bb_error_msg)
338 / sizeof(char *))) {
339 msg = tftp_bb_error_msg[tmp];
340 }
341
342 if (msg) {
343 bb_error_msg("server says: %s", msg);
344 }
345
346 break;
347 }
348 #if ENABLE_FEATURE_TFTP_BLOCKSIZE
349 if (want_option_ack) {
350
351 want_option_ack = 0;
352
353 if (opcode == TFTP_OACK) {
354 /* server seems to support options */
355 char *res;
356
357 res = tftp_option_get(&rbuf[2], len - 2, "blksize");
358
359 if (res) {
360 int blksize = xatoi_u(res);
361
362 if (tftp_blocksize_check(blksize, tftp_bufsize - 4)) {
363 if (CMD_PUT(cmd)) {
364 opcode = TFTP_DATA;
365 } else {
366 opcode = TFTP_ACK;
367 }
368 #if ENABLE_DEBUG_TFTP
369 fprintf(stderr, "using blksize %u\n",
370 blksize);
371 #endif
372 tftp_bufsize = blksize + 4;
373 block_nr = 0;
374 continue;
375 }
376 }
377 /* FIXME:
378 * we should send ERROR 8 */
379 bb_error_msg("bad server option");
380 break;
381 }
382
383 bb_error_msg("warning: blksize not supported by server"
384 " - reverting to 512");
385
386 tftp_bufsize = TFTP_BLOCKSIZE_DEFAULT + 4;
387 }
388 #endif
389
390 if (CMD_GET(cmd) && (opcode == TFTP_DATA)) {
391 if (tmp == block_nr) {
392 len = full_write(localfd, &rbuf[4], len - 4);
393
394 if (len < 0) {
395 bb_perror_msg(bb_msg_write_error);
396 break;
397 }
398
399 if (len != (tftp_bufsize - 4)) {
400 finished++;
401 }
402
403 opcode = TFTP_ACK;
404 continue;
405 }
406 /* in case the last ack disappeared into the ether */
407 if (tmp == (block_nr - 1)) {
408 --block_nr;
409 opcode = TFTP_ACK;
410 continue;
411 // tmp==(block_nr-1) and (tmp+1)==block_nr is always same, I think. wtf?
412 } else if (tmp + 1 == block_nr) {
413 /* Server lost our TFTP_ACK. Resend it */
414 block_nr = tmp;
415 opcode = TFTP_ACK;
416 continue;
417 }
418 }
419
420 if (CMD_PUT(cmd) && (opcode == TFTP_ACK)) {
421 if (tmp == (uint16_t) (block_nr - 1)) {
422 if (finished) {
423 break;
424 }
425
426 opcode = TFTP_DATA;
427 continue;
428 }
429 }
430 }
431
432 if (ENABLE_FEATURE_CLEAN_UP) {
433 close(socketfd);
434 free(xbuf);
435 free(rbuf);
436 }
437
438 return finished ? EXIT_SUCCESS : EXIT_FAILURE;
439 }
440
441 int tftp_main(int argc, char **argv)
442 {
443 len_and_sockaddr *peer_lsa;
444 const char *localfile = NULL;
445 const char *remotefile = NULL;
446 #if ENABLE_FEATURE_TFTP_BLOCKSIZE
447 const char *sblocksize = NULL;
448 #endif
449 int port;
450 USE_GETPUT(int cmd;)
451 int fd = -1;
452 int flags = 0;
453 int result;
454 int blocksize = TFTP_BLOCKSIZE_DEFAULT;
455
456 /* -p or -g is mandatory, and they are mutually exclusive */
457 opt_complementary = "" USE_FEATURE_TFTP_GET("g:") USE_FEATURE_TFTP_PUT("p:")
458 USE_GETPUT("?g--p:p--g");
459
460 USE_GETPUT(cmd =) getopt32(argc, argv,
461 USE_FEATURE_TFTP_GET("g") USE_FEATURE_TFTP_PUT("p")
462 "l:r:" USE_FEATURE_TFTP_BLOCKSIZE("b:"),
463 &localfile, &remotefile
464 USE_FEATURE_TFTP_BLOCKSIZE(, &sblocksize));
465
466 flags = O_RDONLY;
467 if (CMD_GET(cmd))
468 flags = O_WRONLY | O_CREAT | O_TRUNC;
469
470 #if ENABLE_FEATURE_TFTP_BLOCKSIZE
471 if (sblocksize) {
472 blocksize = xatoi_u(sblocksize);
473 if (!tftp_blocksize_check(blocksize, 0)) {
474 return EXIT_FAILURE;
475 }
476 }
477 #endif
478
479 if (localfile == NULL)
480 localfile = remotefile;
481 if (remotefile == NULL)
482 remotefile = localfile;
483 if ((localfile == NULL && remotefile == NULL) || (argv[optind] == NULL))
484 bb_show_usage();
485
486 if (localfile == NULL || LONE_DASH(localfile)) {
487 fd = CMD_GET(cmd) ? STDOUT_FILENO : STDIN_FILENO;
488 } else {
489 fd = xopen3(localfile, flags, 0644);
490 }
491
492 port = bb_lookup_port(argv[optind + 1], "udp", 69);
493 peer_lsa = host2sockaddr(argv[optind], port);
494
495 #if ENABLE_DEBUG_TFTP
496 fprintf(stderr, "using server \"%s\", "
497 "remotefile \"%s\", localfile \"%s\".\n",
498 xmalloc_sockaddr2dotted(&peer_lsa->sa, peer_lsa->len),
499 remotefile, localfile);
500 #endif
501
502 result = tftp(
503 #if ENABLE_FEATURE_TFTP_GET && ENABLE_FEATURE_TFTP_PUT
504 cmd,
505 #endif
506 peer_lsa, remotefile, fd, port, blocksize);
507
508 if (fd > 1) {
509 if (ENABLE_FEATURE_CLEAN_UP)
510 close(fd);
511 if (CMD_GET(cmd) && result != EXIT_SUCCESS)
512 unlink(localfile);
513 }
514 return result;
515 }
516
517 #endif /* ENABLE_FEATURE_TFTP_GET || ENABLE_FEATURE_TFTP_PUT */