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