source: MondoRescue/branches/2.2.5/mindi-busybox/networking/tftp.c@ 1765

Last change on this file since 1765 was 1765, checked in by Bruno Cornec, 16 years ago

Update to busybox 1.7.2

File size: 11.6 KB
Line 
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 "libbb.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#if ENABLE_FEATURE_TFTP_GET && !ENABLE_FEATURE_TFTP_PUT
40#define USE_GETPUT(...)
41#define CMD_GET(cmd) 1
42#define CMD_PUT(cmd) 0
43#elif !ENABLE_FEATURE_TFTP_GET && ENABLE_FEATURE_TFTP_PUT
44#define USE_GETPUT(...)
45#define CMD_GET(cmd) 0
46#define CMD_PUT(cmd) 1
47#else
48#define USE_GETPUT(...) __VA_ARGS__
49/* masks coming from getpot32 */
50#define CMD_GET(cmd) ((cmd) & 1)
51#define CMD_PUT(cmd) ((cmd) & 2)
52#endif
53/* NB: in the code below
54 * CMD_GET(cmd) and CMD_PUT(cmd) are mutually exclusive
55 */
56
57
58#if ENABLE_FEATURE_TFTP_BLOCKSIZE
59
60static int tftp_blocksize_check(int blocksize, int bufsize)
61{
62 /* Check if the blocksize is valid:
63 * RFC2348 says between 8 and 65464,
64 * but our implementation makes it impossible
65 * to use blocksizes smaller than 22 octets.
66 */
67
68 if ((bufsize && (blocksize > bufsize))
69 || (blocksize < 8) || (blocksize > 65564)
70 ) {
71 bb_error_msg("bad blocksize");
72 return 0;
73 }
74
75 return blocksize;
76}
77
78static char *tftp_option_get(char *buf, int len, const char *option)
79{
80 int opt_val = 0;
81 int opt_found = 0;
82 int k;
83
84 while (len > 0) {
85 /* Make sure the options are terminated correctly */
86 for (k = 0; k < len; k++) {
87 if (buf[k] == '\0') {
88 goto nul_found;
89 }
90 }
91 return NULL;
92 nul_found:
93 if (opt_val == 0) {
94 if (strcasecmp(buf, option) == 0) {
95 opt_found = 1;
96 }
97 } else if (opt_found) {
98 return buf;
99 }
100
101 k++;
102 buf += k;
103 len -= k;
104 opt_val ^= 1;
105 }
106
107 return NULL;
108}
109
110#endif
111
112static int tftp( USE_GETPUT(const int cmd,)
113 len_and_sockaddr *peer_lsa,
114 const char *remotefile, const int localfd,
115 unsigned port, int tftp_bufsize)
116{
117 struct timeval tv;
118 fd_set rfds;
119 int socketfd;
120 int len;
121 int send_len;
122 USE_FEATURE_TFTP_BLOCKSIZE(smallint want_option_ack = 0;)
123 smallint finished = 0;
124 uint16_t opcode;
125 uint16_t block_nr = 1;
126 uint16_t recv_blk;
127 int timeout = TFTP_NUM_RETRIES;
128 char *cp;
129
130 unsigned org_port;
131 len_and_sockaddr *const from = alloca(offsetof(len_and_sockaddr, sa) + peer_lsa->len);
132
133 /* Can't use RESERVE_CONFIG_BUFFER here since the allocation
134 * size varies meaning BUFFERS_GO_ON_STACK would fail */
135 /* We must keep the transmit and receive buffers seperate */
136 /* In case we rcv a garbage pkt and we need to rexmit the last pkt */
137 char *xbuf = xmalloc(tftp_bufsize += 4);
138 char *rbuf = xmalloc(tftp_bufsize);
139
140 port = org_port = htons(port);
141
142 socketfd = xsocket(peer_lsa->sa.sa_family, SOCK_DGRAM, 0);
143
144 /* build opcode */
145 opcode = TFTP_WRQ;
146 if (CMD_GET(cmd)) {
147 opcode = TFTP_RRQ;
148 }
149 cp = xbuf + 2;
150 /* add filename and mode */
151 /* fill in packet if the filename fits into xbuf */
152 len = strlen(remotefile) + 1;
153 if (2 + len + sizeof("octet") >= tftp_bufsize) {
154 bb_error_msg("remote filename is too long");
155 goto ret;
156 }
157 strcpy(cp, remotefile);
158 cp += len;
159 /* add "mode" part of the package */
160 strcpy(cp, "octet");
161 cp += sizeof("octet");
162
163#if ENABLE_FEATURE_TFTP_BLOCKSIZE
164 len = tftp_bufsize - 4; /* data block size */
165 if (len != TFTP_BLOCKSIZE_DEFAULT) {
166 /* rfc2348 says that 65464 is a max allowed value */
167 if ((&xbuf[tftp_bufsize - 1] - cp) < sizeof("blksize NNNNN")) {
168 bb_error_msg("remote filename is too long");
169 goto ret;
170 }
171 /* add "blksize", <nul>, blocksize */
172 strcpy(cp, "blksize");
173 cp += sizeof("blksize");
174 cp += snprintf(cp, 6, "%d", len) + 1;
175 want_option_ack = 1;
176 }
177#endif
178 /* First packet is built, so skip packet generation */
179 goto send_pkt;
180
181 /* Using mostly goto's - continue/break will be less clear
182 * in where we actually jump to */
183
184 while (1) {
185 /* Build ACK or DATA */
186 cp = xbuf + 2;
187 *((uint16_t*)cp) = htons(block_nr);
188 cp += 2;
189 block_nr++;
190 opcode = TFTP_ACK;
191 if (CMD_PUT(cmd)) {
192 opcode = TFTP_DATA;
193 len = full_read(localfd, cp, tftp_bufsize - 4);
194 if (len < 0) {
195 bb_perror_msg(bb_msg_read_error);
196 goto ret;
197 }
198 if (len != (tftp_bufsize - 4)) {
199 finished = 1;
200 }
201 cp += len;
202 }
203 send_pkt:
204 /* Send packet */
205 *((uint16_t*)xbuf) = htons(opcode); /* fill in opcode part */
206 send_len = cp - xbuf;
207 /* NB: send_len value is preserved in code below
208 * for potential resend */
209 send_again:
210#if ENABLE_DEBUG_TFTP
211 fprintf(stderr, "sending %u bytes\n", send_len);
212 for (cp = xbuf; cp < &xbuf[send_len]; cp++)
213 fprintf(stderr, "%02x ", (unsigned char) *cp);
214 fprintf(stderr, "\n");
215#endif
216 xsendto(socketfd, xbuf, send_len, &peer_lsa->sa, peer_lsa->len);
217 /* Was it final ACK? then exit */
218 if (finished && (opcode == TFTP_ACK))
219 goto ret;
220
221 timeout = TFTP_NUM_RETRIES; /* re-initialize */
222 recv_again:
223 /* Receive packet */
224 tv.tv_sec = TFTP_TIMEOUT;
225 tv.tv_usec = 0;
226 FD_ZERO(&rfds);
227 FD_SET(socketfd, &rfds);
228 switch (select(socketfd + 1, &rfds, NULL, NULL, &tv)) {
229 unsigned from_port;
230 case 1:
231 from->len = peer_lsa->len;
232 memset(&from->sa, 0, peer_lsa->len);
233 len = recvfrom(socketfd, rbuf, tftp_bufsize, 0,
234 &from->sa, &from->len);
235 if (len < 0) {
236 bb_perror_msg("recvfrom");
237 goto ret;
238 }
239 from_port = get_nport(&from->sa);
240 if (port == org_port) {
241 /* Our first query went to port 69
242 * but reply will come from different one.
243 * Remember and use this new port */
244 port = from_port;
245 set_nport(peer_lsa, from_port);
246 }
247 if (port != from_port)
248 goto recv_again;
249 goto process_pkt;
250 case 0:
251 timeout--;
252 if (timeout == 0) {
253 bb_error_msg("last timeout");
254 goto ret;
255 }
256 bb_error_msg("last timeout" + 5);
257 goto send_again; /* resend last sent pkt */
258 default:
259 bb_perror_msg("select");
260 goto ret;
261 }
262 process_pkt:
263 /* Process recv'ed packet */
264 opcode = ntohs( ((uint16_t*)rbuf)[0] );
265 recv_blk = ntohs( ((uint16_t*)rbuf)[1] );
266
267#if ENABLE_DEBUG_TFTP
268 fprintf(stderr, "received %d bytes: %04x %04x\n", len, opcode, recv_blk);
269#endif
270
271 if (opcode == TFTP_ERROR) {
272 static const char *const errcode_str[] = {
273 "",
274 "file not found",
275 "access violation",
276 "disk full",
277 "illegal TFTP operation",
278 "unknown transfer id",
279 "file already exists",
280 "no such user",
281 "bad option"
282 };
283
284 const char *msg = "";
285
286 if (rbuf[4] != '\0') {
287 msg = &rbuf[4];
288 rbuf[tftp_bufsize - 1] = '\0';
289 } else if (recv_blk < ARRAY_SIZE(errcode_str)) {
290 msg = errcode_str[recv_blk];
291 }
292 bb_error_msg("server error: (%u) %s", recv_blk, msg);
293 goto ret;
294 }
295
296#if ENABLE_FEATURE_TFTP_BLOCKSIZE
297 if (want_option_ack) {
298 want_option_ack = 0;
299
300 if (opcode == TFTP_OACK) {
301 /* server seems to support options */
302 char *res;
303
304 res = tftp_option_get(&rbuf[2], len - 2, "blksize");
305 if (res) {
306 int blksize = xatoi_u(res);
307 if (!tftp_blocksize_check(blksize, tftp_bufsize - 4)) {
308 /* send ERROR 8 to server... */
309 /* htons can be impossible to use in const initializer: */
310 /*static const uint16_t error_8[2] = { htons(TFTP_ERROR), htons(8) };*/
311 /* thus we open-code big-endian layout */
312 static const uint8_t error_8[4] = { 0,TFTP_ERROR, 0,8 };
313 xsendto(socketfd, error_8, 4, &peer_lsa->sa, peer_lsa->len);
314 bb_error_msg("server proposes bad blksize %d, exiting", blksize);
315 goto ret;
316 }
317#if ENABLE_DEBUG_TFTP
318 fprintf(stderr, "using blksize %u\n",
319 blksize);
320#endif
321 tftp_bufsize = blksize + 4;
322 /* Send ACK for OACK ("block" no: 0) */
323 block_nr = 0;
324 continue;
325 }
326 /* rfc2347:
327 * "An option not acknowledged by the server
328 * must be ignored by the client and server
329 * as if it were never requested." */
330 }
331
332 bb_error_msg("blksize is not supported by server"
333 " - reverting to 512");
334 tftp_bufsize = TFTP_BLOCKSIZE_DEFAULT + 4;
335 }
336#endif
337 /* block_nr is already advanced to next block# we expect
338 * to get / block# we are about to send next time */
339
340 if (CMD_GET(cmd) && (opcode == TFTP_DATA)) {
341 if (recv_blk == block_nr) {
342 len = full_write(localfd, &rbuf[4], len - 4);
343 if (len < 0) {
344 bb_perror_msg(bb_msg_write_error);
345 goto ret;
346 }
347 if (len != (tftp_bufsize - 4)) {
348 finished = 1;
349 }
350 continue; /* send ACK */
351 }
352 if (recv_blk == (block_nr - 1)) {
353 /* Server lost our TFTP_ACK. Resend it */
354 block_nr = recv_blk;
355 continue;
356 }
357 }
358
359 if (CMD_PUT(cmd) && (opcode == TFTP_ACK)) {
360 /* did server ACK our last DATA pkt? */
361 if (recv_blk == (uint16_t) (block_nr - 1)) {
362 if (finished)
363 goto ret;
364 continue; /* send next block */
365 }
366 }
367 /* Awww... recv'd packet is not recognized! */
368 goto recv_again;
369 /* why recv_again? - rfc1123 says:
370 * "The sender (i.e., the side originating the DATA packets)
371 * must never resend the current DATA packet on receipt
372 * of a duplicate ACK".
373 * DATA pkts are resent ONLY on timeout.
374 * Thus "goto send_again" will ba a bad mistake above.
375 * See:
376 * http://en.wikipedia.org/wiki/Sorcerer's_Apprentice_Syndrome
377 */
378 }
379 ret:
380 if (ENABLE_FEATURE_CLEAN_UP) {
381 close(socketfd);
382 free(xbuf);
383 free(rbuf);
384 }
385 return finished == 0; /* returns 1 on failure */
386}
387
388int tftp_main(int argc, char **argv);
389int tftp_main(int argc, char **argv)
390{
391 len_and_sockaddr *peer_lsa;
392 const char *localfile = NULL;
393 const char *remotefile = NULL;
394#if ENABLE_FEATURE_TFTP_BLOCKSIZE
395 const char *sblocksize = NULL;
396#endif
397 int port;
398 USE_GETPUT(int cmd;)
399 int fd = -1;
400 int flags = 0;
401 int result;
402 int blocksize = TFTP_BLOCKSIZE_DEFAULT;
403
404 /* -p or -g is mandatory, and they are mutually exclusive */
405 opt_complementary = "" USE_FEATURE_TFTP_GET("g:") USE_FEATURE_TFTP_PUT("p:")
406 USE_GETPUT("?g--p:p--g");
407
408 USE_GETPUT(cmd =) getopt32(argv,
409 USE_FEATURE_TFTP_GET("g") USE_FEATURE_TFTP_PUT("p")
410 "l:r:" USE_FEATURE_TFTP_BLOCKSIZE("b:"),
411 &localfile, &remotefile
412 USE_FEATURE_TFTP_BLOCKSIZE(, &sblocksize));
413 argv += optind;
414
415 flags = O_RDONLY;
416 if (CMD_GET(cmd))
417 flags = O_WRONLY | O_CREAT | O_TRUNC;
418
419#if ENABLE_FEATURE_TFTP_BLOCKSIZE
420 if (sblocksize) {
421 blocksize = xatoi_u(sblocksize);
422 if (!tftp_blocksize_check(blocksize, 0)) {
423 return EXIT_FAILURE;
424 }
425 }
426#endif
427
428 if (!localfile)
429 localfile = remotefile;
430 if (!remotefile)
431 remotefile = localfile;
432 /* Error if filename or host is not known */
433 if (!remotefile || !argv[0])
434 bb_show_usage();
435
436 fd = CMD_GET(cmd) ? STDOUT_FILENO : STDIN_FILENO;
437 if (!LONE_DASH(localfile)) {
438 fd = xopen(localfile, flags);
439 }
440
441 port = bb_lookup_port(argv[1], "udp", 69);
442 peer_lsa = xhost2sockaddr(argv[0], port);
443
444#if ENABLE_DEBUG_TFTP
445 fprintf(stderr, "using server '%s', remotefile '%s', localfile '%s'\n",
446 xmalloc_sockaddr2dotted(&peer_lsa->sa),
447 remotefile, localfile);
448#endif
449
450 result = tftp( USE_GETPUT(cmd,) peer_lsa, remotefile, fd, port, blocksize);
451
452 if (ENABLE_FEATURE_CLEAN_UP)
453 close(fd);
454 if (result != EXIT_SUCCESS && !LONE_DASH(localfile) && CMD_GET(cmd)) {
455 unlink(localfile);
456 }
457 return result;
458}
459
460#endif /* ENABLE_FEATURE_TFTP_GET || ENABLE_FEATURE_TFTP_PUT */
Note: See TracBrowser for help on using the repository browser.