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