use the pedantically correct compiler for preprocessing
[oweals/busybox.git] / tftp.c
1 /* ------------------------------------------------------------------------- */
2 /* tftp.c                                                                    */
3 /*                                                                           */
4 /* A simple tftp client for busybox.                                         */
5 /* Tries to follow RFC1350.                                                  */
6 /* Only "octet" mode and 512-byte data blocks are supported.                 */
7 /*                                                                           */
8 /* Copyright (C) 2001 Magnus Damm <damm@opensource.se>                       */
9 /*                                                                           */
10 /* Parts of the code based on:                                               */
11 /*                                                                           */
12 /* atftp:  Copyright (C) 2000 Jean-Pierre Lefebvre <helix@step.polymtl.ca>   */
13 /*                        and Remi Lefebvre <remi@debian.org>                */
14 /*                                                                           */
15 /* utftp:  Copyright (C) 1999 Uwe Ohse <uwe@ohse.de>                         */
16 /*                                                                           */
17 /* This program is free software; you can redistribute it and/or modify      */
18 /* it under the terms of the GNU General Public License as published by      */
19 /* the Free Software Foundation; either version 2 of the License, or         */
20 /* (at your option) any later version.                                       */
21 /*                                                                           */
22 /* This program is distributed in the hope that it will be useful,           */
23 /* but WITHOUT ANY WARRANTY; without even the implied warranty of            */
24 /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU          */
25 /* General Public License for more details.                                  */
26 /*                                                                           */
27 /* You should have received a copy of the GNU General Public License         */
28 /* along with this program; if not, write to the Free Software               */
29 /* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA   */
30 /*                                                                           */
31 /* ------------------------------------------------------------------------- */
32
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <sys/types.h>
37 #include <sys/socket.h>
38 #include <sys/time.h>
39 #include <sys/stat.h>
40 #include <netdb.h>
41 #include <netinet/in.h>
42 #include <arpa/inet.h>
43 #include <unistd.h>
44 #include <fcntl.h>
45
46 #include "busybox.h"
47
48 //#define BB_FEATURE_TFTP_DEBUG
49
50 static const char *tftp_error_msg[] = {
51         "Undefined error",
52         "File not found",
53         "Access violation",
54         "Disk full or allocation error",
55         "Illegal TFTP operation",
56         "Unknown transfer ID",
57         "File already exists",
58         "No such user"
59 };
60
61 const int tftp_cmd_get = 1;
62 const int tftp_cmd_put = 2;
63
64 static inline int tftp(const int cmd, const struct hostent *host,
65         const char *serverfile, int localfd, const int port, int tftp_bufsize)
66 {
67         const int cmd_get = cmd & tftp_cmd_get;
68         const int cmd_put = cmd & tftp_cmd_put;
69         const int bb_tftp_num_retries = 5;
70
71         struct sockaddr_in sa;
72         struct sockaddr_in from;
73         struct timeval tv;
74         socklen_t fromlen;
75         fd_set rfds;
76         char *cp;
77         unsigned short tmp;
78         int socketfd;
79         int len;
80         int opcode = 0;
81         int finished = 0;
82         int timeout = bb_tftp_num_retries;
83         int block_nr = 1;
84         RESERVE_BB_BUFFER(buf, tftp_bufsize + 4); // Why 4 ?
85
86         tftp_bufsize += 4;
87
88         if ((socketfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
89                 perror_msg("socket");
90                 return EXIT_FAILURE;
91         }
92
93         len = sizeof(sa);
94
95         memset(&sa, 0, len);
96         bind(socketfd, (struct sockaddr *)&sa, len);
97
98         sa.sin_family = host->h_addrtype;
99         sa.sin_port = htons(port);
100         memcpy(&sa.sin_addr, (struct in_addr *) host->h_addr,
101                    sizeof(sa.sin_addr));
102
103         /* build opcode */
104
105         if (cmd_get) {
106                 opcode = 1;     // read request = 1
107         }
108
109         if (cmd_put) {
110                 opcode = 2;     // write request = 2
111         }
112
113         while (1) {
114
115
116                 /* build packet of type "opcode" */
117
118
119                 cp = buf;
120
121                 *((unsigned short *) cp) = htons(opcode);
122
123                 cp += 2;
124
125                 /* add filename and mode */
126
127                 if ((cmd_get && (opcode == 1)) || // read request = 1
128                         (cmd_put && (opcode == 2))) { // write request = 2
129
130                         /* what is this trying to do ? */
131                         while (cp != &buf[tftp_bufsize - 1]) {
132                                 if ((*cp = *serverfile++) == '\0')
133                                         break;
134                                 cp++;
135                         }
136                         /* and this ? */
137                         if ((*cp != '\0') || (&buf[tftp_bufsize - 1] - cp) < 7) {
138                                 error_msg("too long server-filename");
139                                 break;
140                         }
141
142                         memcpy(cp + 1, "octet", 6);
143                         cp += 7;
144                 }
145
146                 /* add ack and data */
147
148                 if ((cmd_get && (opcode == 4)) || // acknowledgement = 4
149                         (cmd_put && (opcode == 3))) { // data packet == 3
150
151                         *((unsigned short *) cp) = htons(block_nr);
152
153                         cp += 2;
154
155                         block_nr++;
156
157                         if (cmd_put && (opcode == 3)) { // data packet == 3
158                                 len = read(localfd, cp, tftp_bufsize - 4);
159
160                                 if (len < 0) {
161                                         perror_msg("read");
162                                         break;
163                                 }
164
165                                 if (len != (tftp_bufsize - 4)) {
166                                         finished++;
167                                 }
168
169                                 cp += len;
170                         } else if (finished) {
171                                 break;
172                         }
173                 }
174
175
176                 /* send packet */
177
178
179                 do {
180
181                         len = cp - buf;
182
183 #ifdef BB_FEATURE_TFTP_DEBUG
184                         printf("sending %u bytes\n", len);
185                         for (cp = buf; cp < &buf[len]; cp++)
186                                 printf("%02x ", *cp);
187                         printf("\n");
188 #endif
189                         if (sendto(socketfd, buf, len, 0,
190                                         (struct sockaddr *) &sa, sizeof(sa)) < 0) {
191                                 perror_msg("send");
192                                 len = -1;
193                                 break;
194                         }
195
196
197                         /* receive packet */
198
199
200                         memset(&from, 0, sizeof(from));
201                         fromlen = sizeof(from);
202
203                         tv.tv_sec = 5; // BB_TFPT_TIMEOUT = 5
204                         tv.tv_usec = 0;
205
206                         FD_ZERO(&rfds);
207                         FD_SET(socketfd, &rfds);
208
209                         switch (select(FD_SETSIZE, &rfds, NULL, NULL, &tv)) {
210                         case 1:
211                                 len = recvfrom(socketfd, buf, tftp_bufsize, 0,
212                                                 (struct sockaddr *) &from, &fromlen);
213
214                                 if (len < 0) {
215                                         perror_msg("recvfrom");
216                                         break;
217                                 }
218
219                                 timeout = 0;
220
221                                 if (sa.sin_port == htons(port)) {
222                                         sa.sin_port = from.sin_port;
223                                 }
224                                 if (sa.sin_port == from.sin_port) {
225                                         break;
226                                 }
227
228                                 /* fall-through for bad packets! */
229                                 /* discard the packet - treat as timeout */
230                                 timeout = bb_tftp_num_retries;
231
232                         case 0:
233                                 error_msg("timeout");
234
235                                 if (timeout == 0) {
236                                         len = -1;
237                                         error_msg("last timeout");
238                                 } else {
239                                         timeout--;
240                                 }
241                                 break;
242
243                         default:
244                                 perror_msg("select");
245                                 len = -1;
246                         }
247
248                 } while (timeout && (len >= 0));
249
250                 if (len < 0) {
251                         break;
252                 }
253
254                 /* process received packet */
255
256
257                 opcode = ntohs(*((unsigned short *) buf));
258                 tmp = ntohs(*((unsigned short *) &buf[2]));
259
260 #ifdef BB_FEATURE_TFTP_DEBUG
261                 printf("received %d bytes: %04x %04x\n", len, opcode, tmp);
262 #endif
263
264                 if (cmd_get && (opcode == 3)) { // data packet == 3
265
266                         if (tmp == block_nr) {
267                                 len = write(localfd, &buf[4], len - 4);
268
269                                 if (len < 0) {
270                                         perror_msg("write");
271                                         break;
272                                 }
273
274                                 if (len != (tftp_bufsize - 4)) {
275                                         finished++;
276                                 }
277
278                                 opcode = 4; // acknowledgement = 4
279                                 continue;
280                         }
281                 }
282
283                 if (cmd_put && (opcode == 4)) { // acknowledgement = 4
284
285                         if (tmp == (block_nr - 1)) {
286                                 if (finished) {
287                                         break;
288                                 }
289
290                                 opcode = 3; // data packet == 3
291                                 continue;
292                         }
293                 }
294
295                 if (opcode == 5) { // error code == 5
296                         char *msg = NULL;
297
298                         if (buf[4] != '\0') {
299                                 msg = &buf[4];
300                                 buf[tftp_bufsize - 1] = '\0';
301                         } else if (tmp < (sizeof(tftp_error_msg) / sizeof(char *))) {
302                                 msg = (char *) tftp_error_msg[tmp];
303                         }
304
305                         if (msg) {
306                                 error_msg("server says: %s", msg);
307                         }
308
309                         break;
310                 }
311         }
312
313         close(socketfd);
314
315         return finished ? EXIT_SUCCESS : EXIT_FAILURE;
316 }
317
318 int tftp_main(int argc, char **argv)
319 {
320         struct hostent *host = NULL;
321         char *localfile = NULL;
322         char *remotefile = NULL;
323         int port = 69;
324         int cmd = 0;
325         int fd = -1;
326         int flags = 0;
327         int opt;
328         int result;
329         int blocksize = 512;
330
331         while ((opt = getopt(argc, argv, "b:gpl:r:")) != -1) {
332                 switch (opt) {
333                 case 'b':
334                         blocksize = atoi(optarg);
335                         break;
336 #ifdef BB_FEATURE_TFTP_GET
337                 case 'g':
338                         cmd = tftp_cmd_get;
339                         flags = O_WRONLY | O_CREAT;
340                         break;
341 #endif
342 #ifdef BB_FEATURE_TFTP_PUT
343                 case 'p':
344                         cmd = tftp_cmd_put;
345                         flags = O_RDONLY;
346                         break;
347 #endif
348                 case 'l': 
349                         localfile = xstrdup(optarg);
350                         break;
351                 case 'r':
352                         remotefile = xstrdup(optarg);
353                         break;
354                 }
355         }
356
357         if ((cmd == 0) || (optind == argc)) {
358                 show_usage();
359         }
360
361         fd = open(localfile, flags, 0644);
362         if (fd < 0) {
363                 perror_msg_and_die("local file");
364         }
365
366         host = xgethostbyname(argv[optind]);
367
368         if (optind + 2 == argc) {
369                 port = atoi(argv[optind + 1]);
370         }
371
372 #ifdef BB_FEATURE_TFTP_DEBUG
373         printf("using server \"%s\", serverfile \"%s\","
374                 "localfile \"%s\".\n",
375                 inet_ntoa(*((struct in_addr *) host->h_addr)),
376                 remotefile, localfile);
377 #endif
378
379         result = tftp(cmd, host, remotefile, fd, port, blocksize);
380         close(fd);
381
382         return(result);
383 }