Mercurial > hg > toybox
annotate toys/pending/tftpd.c @ 1112:f7bec236efd3 draft
Stuff in pending should default n until cleaned up.
author | Rob Landley <rob@landley.net> |
---|---|
date | Sun, 10 Nov 2013 15:49:21 -0600 |
parents | f665f065fe87 |
children | 2131db66a732 |
rev | line source |
---|---|
1111 | 1 /* tftpd.c - TFTP server. |
2 * | |
3 * Copyright 2013 Ranjan Kumar <ranjankumar.bth@gmail.com> | |
4 * Copyright 2013 Kyungwan Han <asura321@gmail.com> | |
5 * | |
6 * No Standard. | |
7 | |
8 USE_TFTPD(NEWTOY(tftpd, "rcu:", TOYFLAG_BIN)) | |
9 | |
10 config TFTPD | |
11 bool "tftpd" | |
1112
f7bec236efd3
Stuff in pending should default n until cleaned up.
Rob Landley <rob@landley.net>
parents:
1111
diff
changeset
|
12 default n |
1111 | 13 help |
14 usage: tftpd [-cr] [-u USER] [DIR] | |
15 | |
16 Transfer file from/to tftp server. | |
17 | |
18 -r Prohibit upload | |
19 -c Allow file creation via upload | |
20 -u Access files as USER | |
21 */ | |
22 #define FOR_tftpd | |
23 #include "toys.h" | |
24 | |
25 GLOBALS( | |
26 char *user; | |
27 long sfd; | |
28 struct passwd *pw; | |
29 ) | |
30 | |
31 #define TFTPD_BLKSIZE 512 // as per RFC 1350. | |
32 | |
33 // opcodes | |
34 #define TFTPD_OP_RRQ 1 // Read Request RFC 1350, RFC 2090 | |
35 #define TFTPD_OP_WRQ 2 // Write Request RFC 1350 | |
36 #define TFTPD_OP_DATA 3 // Data chunk RFC 1350 | |
37 #define TFTPD_OP_ACK 4 // Acknowledgement RFC 1350 | |
38 #define TFTPD_OP_ERR 5 // Error Message RFC 1350 | |
39 #define TFTPD_OP_OACK 6 // Option acknowledgment RFC 2347 | |
40 | |
41 // Error Codes: | |
42 #define TFTPD_ER_NOSUCHFILE 1 // File not found | |
43 #define TFTPD_ER_ACCESS 2 // Access violation | |
44 #define TFTPD_ER_FULL 3 // Disk full or allocation exceeded | |
45 #define TFTPD_ER_ILLEGALOP 4 // Illegal TFTP operation | |
46 #define TFTPD_ER_UNKID 5 // Unknown transfer ID | |
47 #define TFTPD_ER_EXISTS 6 // File already exists | |
48 #define TFTPD_ER_UNKUSER 7 // No such user | |
49 #define TFTPD_ER_NEGOTIATE 8 // Terminate transfer due to option negotiation | |
50 | |
51 /* TFTP Packet Formats | |
52 * Type Op # Format without header | |
53 * 2 bytes string 1 byte string 1 byte | |
54 * ----------------------------------------------- | |
55 * RRQ/ | 01/02 | Filename | 0 | Mode | 0 | | |
56 * WRQ ----------------------------------------------- | |
57 * 2 bytes 2 bytes n bytes | |
58 * --------------------------------- | |
59 * DATA | 03 | Block # | Data | | |
60 * --------------------------------- | |
61 * 2 bytes 2 bytes | |
62 * ------------------- | |
63 * ACK | 04 | Block # | | |
64 * -------------------- | |
65 * 2 bytes 2 bytes string 1 byte | |
66 * ---------------------------------------- | |
67 * ERROR | 05 | ErrorCode | ErrMsg | 0 | | |
68 * ---------------------------------------- | |
69 */ | |
70 | |
71 static char g_buff[TFTPD_BLKSIZE]; | |
72 static char g_errpkt[TFTPD_BLKSIZE]; | |
73 | |
74 static void bind_and_connect(struct sockaddr *srcaddr, | |
75 struct sockaddr *dstaddr, socklen_t socklen) | |
76 { | |
77 int set = 1; | |
78 | |
79 TT.sfd = xsocket(dstaddr->sa_family, SOCK_DGRAM, 0); | |
80 if (setsockopt(TT.sfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&set, | |
81 sizeof(set)) < 0) perror_exit("setsockopt failed"); | |
82 if (bind(TT.sfd, srcaddr, socklen)) perror_exit("bind"); | |
83 if (connect(TT.sfd, dstaddr, socklen) < 0) | |
84 perror_exit("can't connect to remote host"); | |
85 } | |
86 | |
87 // Create and send error packet. | |
88 static void send_errpkt(struct sockaddr *dstaddr, | |
89 socklen_t socklen, char *errmsg) | |
90 { | |
91 error_msg(errmsg); | |
92 g_errpkt[1] = TFTPD_OP_ERR; | |
93 strcpy(g_errpkt + 4, errmsg); | |
94 if (sendto(TT.sfd, g_errpkt, strlen(errmsg)+5, 0, dstaddr, socklen) < 0) | |
95 perror_exit("sendto failed"); | |
96 } | |
97 | |
98 // Used to send / receive packets. | |
99 static void do_action(struct sockaddr *srcaddr, struct sockaddr *dstaddr, | |
100 socklen_t socklen, char *file, int opcode, int tsize, int blksize) | |
101 { | |
102 int fd, done = 0, retry_count = 12, timeout = 100, len; | |
103 uint16_t blockno = 1, pktopcode, rblockno; | |
104 char *ptr, *spkt, *rpkt; | |
105 struct pollfd pollfds[1]; | |
106 | |
107 spkt = xzalloc(blksize + 4); | |
108 rpkt = xzalloc(blksize + 4); | |
109 ptr = spkt+2; //point after opcode. | |
110 | |
111 pollfds[0].fd = TT.sfd; | |
112 // initialize groups, setgid and setuid | |
113 if (TT.pw) { | |
114 if (change_identity(TT.pw)) perror_exit("Failed to change identity"); | |
115 endgrent(); | |
116 } | |
117 | |
118 if (opcode == TFTPD_OP_RRQ) fd = open(file, O_RDONLY, 0666); | |
119 else fd = open(file, ((toys.optflags & FLAG_c) ? | |
120 (O_WRONLY|O_TRUNC|O_CREAT) : (O_WRONLY|O_TRUNC)) , 0666); | |
121 if (fd < 0) { | |
122 g_errpkt[3] = TFTPD_ER_NOSUCHFILE; | |
123 send_errpkt(dstaddr, socklen, "can't open file"); | |
124 goto CLEAN_APP; | |
125 } | |
126 // For download -> blockno will be 1. | |
127 // 1st ACK will be from dst,which will have blockno-=1 | |
128 // Create and send ACK packet. | |
129 if (blksize != TFTPD_BLKSIZE || tsize) { | |
130 pktopcode = TFTPD_OP_OACK; | |
131 // add "blksize\000blksize_val\000" in send buffer. | |
132 if (blksize != TFTPD_BLKSIZE) { | |
133 strcpy(ptr, "blksize"); | |
134 ptr += strlen("blksize") + 1; | |
135 ptr += snprintf(ptr, 6, "%d", blksize) + 1; | |
136 } | |
137 if (tsize) {// add "tsize\000tsize_val\000" in send buffer. | |
138 struct stat sb; | |
139 | |
140 sb.st_size = 0; | |
141 fstat(fd, &sb); | |
142 strcpy(ptr, "tsize"); | |
143 ptr += strlen("tsize") + 1; | |
144 ptr += sprintf(ptr, "%lu", (unsigned long)sb.st_size)+1; | |
145 } | |
146 goto SEND_PKT; | |
147 } | |
148 // upload -> ACK 1st packet with filename, as it has blockno 0. | |
149 if (opcode == TFTPD_OP_WRQ) blockno = 0; | |
150 | |
151 // Prepare DATA and/or ACK pkt and send it. | |
152 for (;;) { | |
153 int poll_ret; | |
154 | |
155 retry_count = 12, timeout = 100, pktopcode = TFTPD_OP_ACK; | |
156 ptr = spkt+2; | |
157 *((uint16_t*)ptr) = htons(blockno); | |
158 blockno++; | |
159 ptr += 2; | |
160 if (opcode == TFTPD_OP_RRQ) { | |
161 pktopcode = TFTPD_OP_DATA; | |
162 len = readall(fd, ptr, blksize); | |
163 if (len < 0) { | |
164 send_errpkt(dstaddr, socklen, "read-error"); | |
165 break; | |
166 } | |
167 if (len != blksize) done = 1; //last pkt. | |
168 ptr += len; | |
169 } | |
170 SEND_PKT: | |
171 // 1st ACK will be from dst, which will have blockno-=1 | |
172 *((uint16_t*)spkt) = htons(pktopcode); //append send pkt's opcode. | |
173 RETRY_SEND: | |
174 if (sendto(TT.sfd, spkt, (ptr - spkt), 0, dstaddr, socklen) <0) | |
175 perror_exit("sendto failed"); | |
176 // if "block size < 512", send ACK and exit. | |
177 if ((pktopcode == TFTPD_OP_ACK) && done) break; | |
178 | |
179 POLL_IN: | |
180 pollfds[0].events = POLLIN; | |
181 pollfds[0].fd = TT.sfd; | |
182 poll_ret = poll(pollfds, 1, timeout); | |
183 if (poll_ret < 0 && (errno == EINTR || errno == ENOMEM)) goto POLL_IN; | |
184 if (!poll_ret) { | |
185 if (!--retry_count) { | |
186 error_msg("timeout"); | |
187 break; | |
188 } | |
189 timeout += 150; | |
190 goto RETRY_SEND; | |
191 } else if (poll_ret == 1) { | |
192 len = read(pollfds[0].fd, rpkt, blksize + 4); | |
193 if (len < 0) { | |
194 send_errpkt(dstaddr, socklen, "read-error"); | |
195 break; | |
196 } | |
197 if (len < 4) goto POLL_IN; | |
198 } else { | |
199 perror_msg("poll"); | |
200 break; | |
201 } | |
202 // Validate receive packet. | |
203 pktopcode = ntohs(((uint16_t*)rpkt)[0]); | |
204 rblockno = ntohs(((uint16_t*)rpkt)[1]); | |
205 if (pktopcode == TFTPD_OP_ERR) { | |
206 switch(rblockno) { | |
207 case TFTPD_ER_NOSUCHFILE: error_msg("File not found"); break; | |
208 case TFTPD_ER_ACCESS: error_msg("Access violation"); break; | |
209 case TFTPD_ER_FULL: error_msg("Disk full or allocation exceeded"); | |
210 break; | |
211 case TFTPD_ER_ILLEGALOP: error_msg("Illegal TFTP operation"); break; | |
212 case TFTPD_ER_UNKID: error_msg("Unknown transfer ID"); break; | |
213 case TFTPD_ER_EXISTS: error_msg("File already exists"); break; | |
214 case TFTPD_ER_UNKUSER: error_msg("No such user"); break; | |
215 case TFTPD_ER_NEGOTIATE: | |
216 error_msg("Terminate transfer due to option negotiation"); break; | |
217 default: error_msg("DATA Check failure."); break; | |
218 } | |
219 break; // Break the for loop. | |
220 } | |
221 | |
222 // if download requested by client, | |
223 // server will send data pkt and will receive ACK pkt from client. | |
224 if ((opcode == TFTPD_OP_RRQ) && (pktopcode == TFTPD_OP_ACK)) { | |
225 if (rblockno == (uint16_t) (blockno - 1)) { | |
226 if (!done) continue; // Send next chunk of data. | |
227 break; | |
228 } | |
229 } | |
230 | |
231 // server will receive DATA pkt and write the data. | |
232 if ((opcode == TFTPD_OP_WRQ) && (pktopcode == TFTPD_OP_DATA)) { | |
233 if (rblockno == blockno) { | |
234 int nw = writeall(fd, &rpkt[4], len-4); | |
235 if (nw != len-4) { | |
236 g_errpkt[3] = TFTPD_ER_FULL; | |
237 send_errpkt(dstaddr, socklen, "write error"); | |
238 break; | |
239 } | |
240 | |
241 if (nw != blksize) done = 1; | |
242 } | |
243 continue; | |
244 } | |
245 goto POLL_IN; | |
246 } // end of loop | |
247 | |
248 CLEAN_APP: | |
249 if (CFG_TOYBOX_FREE) { | |
250 free(spkt); | |
251 free(rpkt); | |
252 close(fd); | |
253 } | |
254 } | |
255 | |
256 void tftpd_main(void) | |
257 { | |
258 int recvmsg_len, rbuflen, opcode, blksize = TFTPD_BLKSIZE, tsize = 0; | |
259 struct sockaddr_storage srcaddr, dstaddr; | |
260 static socklen_t socklen = sizeof(struct sockaddr_storage); | |
261 char *buf = g_buff; | |
262 | |
263 TT.pw = NULL; | |
264 memset(&srcaddr, 0, sizeof(srcaddr)); | |
265 if (getsockname(STDIN_FILENO, (struct sockaddr*)&srcaddr, &socklen)) { | |
266 toys.exithelp = 1; | |
267 error_exit(NULL); | |
268 } | |
269 | |
270 if (toys.optflags & FLAG_u) { | |
271 struct passwd *pw = getpwnam(TT.user); | |
272 if (!pw) error_exit("unknown user %s", TT.user); | |
273 TT.pw = pw; | |
274 } | |
275 if (*toys.optargs) { | |
276 if (chroot(*toys.optargs)) | |
277 perror_exit("can't change root directory to '%s'", *toys.optargs); | |
278 if (chdir("/")) perror_exit("can't change directory to '/'"); | |
279 } | |
280 | |
281 recvmsg_len = recvfrom(STDIN_FILENO, g_buff, TFTPD_BLKSIZE, 0, | |
282 (struct sockaddr*)&dstaddr, &socklen); | |
283 bind_and_connect((struct sockaddr*)&srcaddr, (struct sockaddr*)&dstaddr, socklen); | |
284 // Error condition. | |
285 if (recvmsg_len < 4 || recvmsg_len > TFTPD_BLKSIZE | |
286 || g_buff[recvmsg_len-1] != '\0') { | |
287 send_errpkt((struct sockaddr*)&dstaddr, socklen, "packet format error"); | |
288 return; | |
289 } | |
290 | |
291 // request is either upload or Download. | |
292 opcode = ntohs(*(uint16_t*)buf); | |
293 if (((opcode != TFTPD_OP_RRQ) && (opcode != TFTPD_OP_WRQ)) | |
294 || ((opcode == TFTPD_OP_WRQ) && (toys.optflags & FLAG_r))) { | |
295 send_errpkt((struct sockaddr*)&dstaddr, socklen, | |
296 ((opcode == TFTPD_OP_WRQ) ? "write error" : "packet format error")); | |
297 return; | |
298 } | |
299 | |
300 buf += 2; | |
301 if (*buf == '.' || strstr(buf, "/.")) { | |
302 send_errpkt((struct sockaddr*)&dstaddr, socklen, "dot in filename"); | |
303 return; | |
304 } | |
305 | |
306 buf += strlen(buf) + 1; //1 '\0'. | |
307 // As per RFC 1350, mode is case in-sensitive. | |
308 if ((buf >= (g_buff + recvmsg_len)) || (strcasecmp(buf, "octet"))) { | |
309 send_errpkt((struct sockaddr*)&dstaddr, socklen, "packet format error"); | |
310 return; | |
311 } | |
312 | |
313 //RFC2348. e.g. of size type: "ttype1\0ttype1_val\0...ttypeN\0ttypeN_val\0" | |
314 buf += strlen(buf) + 1; | |
315 rbuflen = g_buff + recvmsg_len - buf; | |
316 if (rbuflen) { | |
317 int jump = 0, bflag = 0; | |
318 | |
319 for (; rbuflen; rbuflen -= jump, buf += jump) { | |
320 if (!bflag && !strcasecmp(buf, "blksize")) { //get blksize | |
321 errno = 0; | |
322 blksize = strtoul(buf, NULL, 10); | |
323 if (errno || blksize > 65564 || blksize < 8) blksize = TFTPD_BLKSIZE; | |
324 bflag ^= 1; | |
325 } else if (!tsize && !strcasecmp(buf, "tsize")) tsize ^= 1; | |
326 | |
327 jump += strlen(buf) + 1; | |
328 } | |
329 tsize &= (opcode == TFTPD_OP_RRQ); | |
330 } | |
331 | |
332 //do send / receive file. | |
333 do_action((struct sockaddr*)&srcaddr, (struct sockaddr*)&dstaddr, | |
334 socklen, g_buff + 2, opcode, tsize, blksize); | |
335 if (CFG_TOYBOX_FREE) close(STDIN_FILENO); | |
336 } |