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"
|
|
12 default y
|
|
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 }
|