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