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