tftp.c - sbase - suckless unix tools | |
git clone git://git.suckless.org/sbase | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
tftp.c (5791B) | |
--- | |
1 /* See LICENSE file for copyright and license details. */ | |
2 #include <sys/time.h> | |
3 #include <sys/types.h> | |
4 #include <sys/socket.h> | |
5 | |
6 #include <netdb.h> | |
7 #include <netinet/in.h> | |
8 | |
9 #include <errno.h> | |
10 #include <stdio.h> | |
11 #include <stdlib.h> | |
12 #include <string.h> | |
13 #include <unistd.h> | |
14 | |
15 #include "util.h" | |
16 | |
17 #define BLKSIZE 512 | |
18 #define HDRSIZE 4 | |
19 #define PKTSIZE (BLKSIZE + HDRSIZE) | |
20 | |
21 #define TIMEOUT_SEC 5 | |
22 /* transfer will time out after NRETRIES * TIMEOUT_SEC */ | |
23 #define NRETRIES 5 | |
24 | |
25 #define RRQ 1 | |
26 #define WWQ 2 | |
27 #define DATA 3 | |
28 #define ACK 4 | |
29 #define ERR 5 | |
30 | |
31 static char *errtext[] = { | |
32 "Undefined", | |
33 "File not found", | |
34 "Access violation", | |
35 "Disk full or allocation exceeded", | |
36 "Illegal TFTP operation", | |
37 "Unknown transfer ID", | |
38 "File already exists", | |
39 "No such user" | |
40 }; | |
41 | |
42 static struct sockaddr_storage to; | |
43 static socklen_t tolen; | |
44 static int timeout; | |
45 static int state; | |
46 static int s; | |
47 | |
48 static int | |
49 packreq(unsigned char *buf, int op, char *path, char *mode) | |
50 { | |
51 unsigned char *p = buf; | |
52 | |
53 *p++ = op >> 8; | |
54 *p++ = op & 0xff; | |
55 if (strlen(path) + 1 > 256) | |
56 eprintf("filename too long\n"); | |
57 memcpy(p, path, strlen(path) + 1); | |
58 p += strlen(path) + 1; | |
59 memcpy(p, mode, strlen(mode) + 1); | |
60 p += strlen(mode) + 1; | |
61 return p - buf; | |
62 } | |
63 | |
64 static int | |
65 packack(unsigned char *buf, int blkno) | |
66 { | |
67 buf[0] = ACK >> 8; | |
68 buf[1] = ACK & 0xff; | |
69 buf[2] = blkno >> 8; | |
70 buf[3] = blkno & 0xff; | |
71 return 4; | |
72 } | |
73 | |
74 static int | |
75 packdata(unsigned char *buf, int blkno) | |
76 { | |
77 buf[0] = DATA >> 8; | |
78 buf[1] = DATA & 0xff; | |
79 buf[2] = blkno >> 8; | |
80 buf[3] = blkno & 0xff; | |
81 return 4; | |
82 } | |
83 | |
84 static int | |
85 unpackop(unsigned char *buf) | |
86 { | |
87 return (buf[0] << 8) | (buf[1] & 0xff); | |
88 } | |
89 | |
90 static int | |
91 unpackblkno(unsigned char *buf) | |
92 { | |
93 return (buf[2] << 8) | (buf[3] & 0xff); | |
94 } | |
95 | |
96 static int | |
97 unpackerrc(unsigned char *buf) | |
98 { | |
99 int errc; | |
100 | |
101 errc = (buf[2] << 8) | (buf[3] & 0xff); | |
102 if (errc < 0 || errc >= LEN(errtext)) | |
103 eprintf("bad error code: %d\n", errc); | |
104 return errc; | |
105 } | |
106 | |
107 static int | |
108 writepkt(unsigned char *buf, int len) | |
109 { | |
110 int n; | |
111 | |
112 n = sendto(s, buf, len, 0, (struct sockaddr *)&to, | |
113 tolen); | |
114 if (n < 0) | |
115 if (errno != EINTR) | |
116 eprintf("sendto:"); | |
117 return n; | |
118 } | |
119 | |
120 static int | |
121 readpkt(unsigned char *buf, int len) | |
122 { | |
123 int n; | |
124 | |
125 n = recvfrom(s, buf, len, 0, (struct sockaddr *)&to, | |
126 &tolen); | |
127 if (n < 0) { | |
128 if (errno != EINTR && errno != EWOULDBLOCK) | |
129 eprintf("recvfrom:"); | |
130 timeout++; | |
131 if (timeout == NRETRIES) | |
132 eprintf("transfer timed out\n"); | |
133 } else { | |
134 timeout = 0; | |
135 } | |
136 return n; | |
137 } | |
138 | |
139 static void | |
140 getfile(char *file) | |
141 { | |
142 unsigned char buf[PKTSIZE]; | |
143 int n, op, blkno, nextblkno = 1, done = 0; | |
144 | |
145 state = RRQ; | |
146 for (;;) { | |
147 switch (state) { | |
148 case RRQ: | |
149 n = packreq(buf, RRQ, file, "octet"); | |
150 writepkt(buf, n); | |
151 n = readpkt(buf, sizeof(buf)); | |
152 if (n > 0) { | |
153 op = unpackop(buf); | |
154 if (op != DATA && op != ERR) | |
155 eprintf("bad opcode: %d\n", op); | |
156 state = op; | |
157 } | |
158 break; | |
159 case DATA: | |
160 n -= HDRSIZE; | |
161 if (n < 0) | |
162 eprintf("truncated packet\n"); | |
163 blkno = unpackblkno(buf); | |
164 if (blkno == nextblkno) { | |
165 nextblkno++; | |
166 write(1, &buf[HDRSIZE], n); | |
167 } | |
168 if (n < BLKSIZE) | |
169 done = 1; | |
170 state = ACK; | |
171 break; | |
172 case ACK: | |
173 n = packack(buf, blkno); | |
174 writepkt(buf, n); | |
175 if (done) | |
176 return; | |
177 n = readpkt(buf, sizeof(buf)); | |
178 if (n > 0) { | |
179 op = unpackop(buf); | |
180 if (op != DATA && op != ERR) | |
181 eprintf("bad opcode: %d\n", op); | |
182 state = op; | |
183 } | |
184 break; | |
185 case ERR: | |
186 eprintf("error: %s\n", errtext[unpackerrc(buf)]); | |
187 } | |
188 } | |
189 } | |
190 | |
191 static void | |
192 putfile(char *file) | |
193 { | |
194 unsigned char inbuf[PKTSIZE], outbuf[PKTSIZE]; | |
195 int inb, outb, op, blkno, nextblkno = 0, done = 0; | |
196 | |
197 state = WWQ; | |
198 for (;;) { | |
199 switch (state) { | |
200 case WWQ: | |
201 outb = packreq(outbuf, WWQ, file, "octet"); | |
202 writepkt(outbuf, outb); | |
203 inb = readpkt(inbuf, sizeof(inbuf)); | |
204 if (inb > 0) { | |
205 op = unpackop(inbuf); | |
206 if (op != ACK && op != ERR) | |
207 eprintf("bad opcode: %d\n", op); | |
208 state = op; | |
209 } | |
210 break; | |
211 case DATA: | |
212 if (blkno == nextblkno) { | |
213 nextblkno++; | |
214 packdata(outbuf, nextblkno); | |
215 outb = read(0, &outbuf[HDRSIZE], BLKSIZE… | |
216 if (outb < BLKSIZE) | |
217 done = 1; | |
218 } | |
219 writepkt(outbuf, outb + HDRSIZE); | |
220 inb = readpkt(inbuf, sizeof(inbuf)); | |
221 if (inb > 0) { | |
222 op = unpackop(inbuf); | |
223 if (op != ACK && op != ERR) | |
224 eprintf("bad opcode: %d\n", op); | |
225 state = op; | |
226 } | |
227 break; | |
228 case ACK: | |
229 if (inb < HDRSIZE) | |
230 eprintf("truncated packet\n"); | |
231 blkno = unpackblkno(inbuf); | |
232 if (blkno == nextblkno) | |
233 if (done) | |
234 return; | |
235 state = DATA; | |
236 break; | |
237 case ERR: | |
238 eprintf("error: %s\n", errtext[unpackerrc(inbuf)… | |
239 } | |
240 } | |
241 } | |
242 | |
243 static void | |
244 usage(void) | |
245 { | |
246 eprintf("usage: %s -h host [-p port] [-x | -c] file\n", argv0); | |
247 } | |
248 | |
249 int | |
250 main(int argc, char *argv[]) | |
251 { | |
252 struct addrinfo hints, *res, *r; | |
253 struct timeval tv; | |
254 char *host = NULL, *port = "tftp"; | |
255 void (*fn)(char *) = getfile; | |
256 int ret; | |
257 | |
258 ARGBEGIN { | |
259 case 'h': | |
260 host = EARGF(usage()); | |
261 break; | |
262 case 'p': | |
263 port = EARGF(usage()); | |
264 break; | |
265 case 'x': | |
266 fn = getfile; | |
267 break; | |
268 case 'c': | |
269 fn = putfile; | |
270 break; | |
271 default: | |
272 usage(); | |
273 } ARGEND | |
274 | |
275 if (!host || !argc) | |
276 usage(); | |
277 | |
278 memset(&hints, 0, sizeof(hints)); | |
279 hints.ai_family = AF_UNSPEC; | |
280 hints.ai_socktype = SOCK_DGRAM; | |
281 hints.ai_protocol = IPPROTO_UDP; | |
282 ret = getaddrinfo(host, port, &hints, &res); | |
283 if (ret) | |
284 eprintf("getaddrinfo: %s\n", gai_strerror(ret)); | |
285 | |
286 for (r = res; r; r = r->ai_next) { | |
287 if (r->ai_family != AF_INET && | |
288 r->ai_family != AF_INET6) | |
289 continue; | |
290 s = socket(r->ai_family, r->ai_socktype, | |
291 r->ai_protocol); | |
292 if (s < 0) | |
293 continue; | |
294 break; | |
295 } | |
296 if (!r) | |
297 eprintf("cannot create socket\n"); | |
298 memcpy(&to, r->ai_addr, r->ai_addrlen); | |
299 tolen = r->ai_addrlen; | |
300 freeaddrinfo(res); | |
301 | |
302 tv.tv_sec = TIMEOUT_SEC; | |
303 tv.tv_usec = 0; | |
304 if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) | |
305 eprintf("setsockopt:"); | |
306 | |
307 fn(argv[0]); | |
308 return 0; | |
309 } |