xref: /netbsd-src/sys/lib/libsa/tftp.c (revision 8b0f9554ff8762542c4defc4f70e1eb76fb508fa)
1 /*	$NetBSD: tftp.c,v 1.24 2007/12/02 04:59:26 tsutsui Exp $	 */
2 
3 /*
4  * Copyright (c) 1996
5  *	Matthias Drochner.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  *
27  */
28 
29 /*
30  * Simple TFTP implementation for libsa.
31  * Assumes:
32  *  - socket descriptor (int) at open_file->f_devdata
33  *  - server host IP in global servip
34  * Restrictions:
35  *  - read only
36  *  - lseek only with SEEK_SET or SEEK_CUR
37  *  - no big time differences between transfers (<tftp timeout)
38  */
39 
40 /*
41  * XXX Does not currently implement:
42  * XXX
43  * XXX LIBSA_NO_FS_CLOSE
44  * XXX LIBSA_NO_FS_SEEK
45  * XXX LIBSA_NO_FS_WRITE
46  * XXX LIBSA_NO_FS_SYMLINK (does this even make sense?)
47  * XXX LIBSA_FS_SINGLECOMPONENT (does this even make sense?)
48  */
49 
50 #include <sys/types.h>
51 #include <sys/stat.h>
52 #include <netinet/in.h>
53 #include <netinet/udp.h>
54 #include <netinet/in_systm.h>
55 #include <lib/libkern/libkern.h>
56 
57 #include "stand.h"
58 #include "net.h"
59 
60 #include "tftp.h"
61 
62 extern struct in_addr servip;
63 
64 static int      tftpport = 2000;
65 
66 #define RSPACE 520		/* max data packet, rounded up */
67 
68 struct tftp_handle {
69 	struct iodesc  *iodesc;
70 	int             currblock;	/* contents of lastdata */
71 	int             islastblock;	/* flag */
72 	int             validsize;
73 	int             off;
74 	const char     *path;	/* saved for re-requests */
75 	struct {
76 		u_char header[HEADER_SIZE];
77 		struct tftphdr t;
78 		u_char space[RSPACE];
79 	} lastdata;
80 };
81 
82 static const int tftperrors[8] = {
83 	0,			/* ??? */
84 	ENOENT,
85 	EPERM,
86 	ENOSPC,
87 	EINVAL,			/* ??? */
88 	EINVAL,			/* ??? */
89 	EEXIST,
90 	EINVAL,			/* ??? */
91 };
92 
93 static ssize_t recvtftp __P((struct iodesc *, void *, size_t, time_t));
94 static int tftp_makereq __P((struct tftp_handle *));
95 static int tftp_getnextblock __P((struct tftp_handle *));
96 #ifndef TFTP_NOTERMINATE
97 static void tftp_terminate __P((struct tftp_handle *));
98 #endif
99 
100 static ssize_t
101 recvtftp(struct iodesc *d, void *pkt, size_t len, time_t tleft)
102 {
103 	ssize_t n;
104 	struct tftphdr *t;
105 
106 	errno = 0;
107 
108 	n = readudp(d, pkt, len, tleft);
109 
110 	if (n < 4)
111 		return -1;
112 
113 	t = (struct tftphdr *)pkt;
114 	switch (ntohs(t->th_opcode)) {
115 	case DATA:
116 		if (htons(t->th_block) != d->xid) {
117 			/*
118 			 * Expected block?
119 			 */
120 			return -1;
121 		}
122 		if (d->xid == 1) {
123 			/*
124 			 * First data packet from new port.
125 			 */
126 			struct udphdr *uh;
127 			uh = (struct udphdr *)pkt - 1;
128 			d->destport = uh->uh_sport;
129 		} /* else check uh_sport has not changed??? */
130 		return (n - (t->th_data - (char *)t));
131 	case ERROR:
132 		if ((unsigned int)ntohs(t->th_code) >= 8) {
133 			printf("illegal tftp error %d\n", ntohs(t->th_code));
134 			errno = EIO;
135 		} else {
136 #ifdef DEBUG
137 			printf("tftp-error %d\n", ntohs(t->th_code));
138 #endif
139 			errno = tftperrors[ntohs(t->th_code)];
140 		}
141 		return -1;
142 	default:
143 #ifdef DEBUG
144 		printf("tftp type %d not handled\n", ntohs(t->th_opcode));
145 #endif
146 		return -1;
147 	}
148 }
149 
150 /* send request, expect first block (or error) */
151 static int
152 tftp_makereq(struct tftp_handle *h)
153 {
154 	struct {
155 		u_char header[HEADER_SIZE];
156 		struct tftphdr t;
157 		u_char space[FNAME_SIZE + 6];
158 	} wbuf;
159 	char           *wtail;
160 	int             l;
161 	ssize_t         res;
162 	struct tftphdr *t;
163 
164 	wbuf.t.th_opcode = htons((u_short)RRQ);
165 	wtail = wbuf.t.th_stuff;
166 	l = strlen(h->path);
167 	bcopy(h->path, wtail, l + 1);
168 	wtail += l + 1;
169 	bcopy("octet", wtail, 6);
170 	wtail += 6;
171 
172 	t = &h->lastdata.t;
173 
174 	/* h->iodesc->myport = htons(--tftpport); */
175 	h->iodesc->myport = htons(tftpport + (getsecs() & 0x3ff));
176 	h->iodesc->destport = htons(IPPORT_TFTP);
177 	h->iodesc->xid = 1;	/* expected block */
178 
179 	res = sendrecv(h->iodesc, sendudp, &wbuf.t, wtail - (char *)&wbuf.t,
180 		       recvtftp, t, sizeof(*t) + RSPACE);
181 
182 	if (res == -1)
183 		return errno;
184 
185 	h->currblock = 1;
186 	h->validsize = res;
187 	h->islastblock = 0;
188 	if (res < SEGSIZE)
189 		h->islastblock = 1;	/* very short file */
190 	return 0;
191 }
192 
193 /* ack block, expect next */
194 static int
195 tftp_getnextblock(struct tftp_handle *h)
196 {
197 	struct {
198 		u_char header[HEADER_SIZE];
199 		struct tftphdr t;
200 	} wbuf;
201 	char           *wtail;
202 	int             res;
203 	struct tftphdr *t;
204 
205 	wbuf.t.th_opcode = htons((u_short)ACK);
206 	wbuf.t.th_block = htons((u_short)h->currblock);
207 	wtail = (char *)&wbuf.t.th_data;
208 
209 	t = &h->lastdata.t;
210 
211 	h->iodesc->xid = h->currblock + 1;	/* expected block */
212 
213 	res = sendrecv(h->iodesc, sendudp, &wbuf.t, wtail - (char *)&wbuf.t,
214 		       recvtftp, t, sizeof(*t) + RSPACE);
215 
216 	if (res == -1)		/* 0 is OK! */
217 		return errno;
218 
219 	h->currblock++;
220 	h->validsize = res;
221 	if (res < SEGSIZE)
222 		h->islastblock = 1;	/* EOF */
223 	return 0;
224 }
225 
226 #ifndef TFTP_NOTERMINATE
227 static void
228 tftp_terminate(struct tftp_handle *h)
229 {
230 	struct {
231 		u_char header[HEADER_SIZE];
232 		struct tftphdr t;
233 	} wbuf;
234 	char           *wtail;
235 
236 	if (h->islastblock) {
237 		wbuf.t.th_opcode = htons((u_short)ACK);
238 		wbuf.t.th_block = htons((u_short)h->currblock);
239 	} else {
240 		wbuf.t.th_opcode = htons((u_short)ERROR);
241 		wbuf.t.th_code = htons((u_short)ENOSPACE); /* ??? */
242 	}
243 	wtail = (char *)&wbuf.t.th_data;
244 
245 	(void)sendudp(h->iodesc, &wbuf.t, wtail - (char *)&wbuf.t);
246 }
247 #endif
248 
249 int
250 tftp_open(const char *path, struct open_file *f)
251 {
252 	struct tftp_handle *tftpfile;
253 	struct iodesc  *io;
254 	int             res;
255 
256 	tftpfile = (struct tftp_handle *)alloc(sizeof(*tftpfile));
257 	if (!tftpfile)
258 		return ENOMEM;
259 
260 	tftpfile->iodesc = io = socktodesc(*(int *)(f->f_devdata));
261 	io->destip = servip;
262 	tftpfile->off = 0;
263 	tftpfile->path = path;	/* XXXXXXX we hope it's static */
264 
265 	res = tftp_makereq(tftpfile);
266 
267 	if (res) {
268 		dealloc(tftpfile, sizeof(*tftpfile));
269 		return res;
270 	}
271 	f->f_fsdata = (void *)tftpfile;
272 	return 0;
273 }
274 
275 int
276 tftp_read(struct open_file *f, void *addr, size_t size, size_t *resid)
277 {
278 	struct tftp_handle *tftpfile;
279 #if !defined(LIBSA_NO_TWIDDLE)
280 	static int      tc = 0;
281 #endif
282 	tftpfile = (struct tftp_handle *)f->f_fsdata;
283 
284 	while (size > 0) {
285 		int needblock;
286 		size_t count;
287 
288 #if !defined(LIBSA_NO_TWIDDLE)
289 		if (!(tc++ % 16))
290 			twiddle();
291 #endif
292 
293 		needblock = tftpfile->off / SEGSIZE + 1;
294 
295 		if (tftpfile->currblock > needblock) {	/* seek backwards */
296 #ifndef TFTP_NOTERMINATE
297 			tftp_terminate(tftpfile);
298 #endif
299 			tftp_makereq(tftpfile);	/* no error check, it worked
300 			                         * for open */
301 		}
302 
303 		while (tftpfile->currblock < needblock) {
304 			int res;
305 
306 			res = tftp_getnextblock(tftpfile);
307 			if (res) {	/* no answer */
308 #ifdef DEBUG
309 				printf("tftp: read error (block %d->%d)\n",
310 				       tftpfile->currblock, needblock);
311 #endif
312 				return res;
313 			}
314 			if (tftpfile->islastblock)
315 				break;
316 		}
317 
318 		if (tftpfile->currblock == needblock) {
319 			size_t offinblock, inbuffer;
320 
321 			offinblock = tftpfile->off % SEGSIZE;
322 
323 			inbuffer = tftpfile->validsize - offinblock;
324 			if (inbuffer < 0) {
325 #ifdef DEBUG
326 				printf("tftp: invalid offset %d\n",
327 				    tftpfile->off);
328 #endif
329 				return EINVAL;
330 			}
331 			count = (size < inbuffer ? size : inbuffer);
332 			bcopy(tftpfile->lastdata.t.th_data + offinblock,
333 			    addr, count);
334 
335 			addr = (char *)addr + count;
336 			tftpfile->off += count;
337 			size -= count;
338 
339 			if ((tftpfile->islastblock) && (count == inbuffer))
340 				break;	/* EOF */
341 		} else {
342 #ifdef DEBUG
343 			printf("tftp: block %d not found\n", needblock);
344 #endif
345 			return EINVAL;
346 		}
347 
348 	}
349 
350 	if (resid)
351 		*resid = size;
352 	return 0;
353 }
354 
355 int
356 tftp_close(struct open_file *f)
357 {
358 	struct tftp_handle *tftpfile;
359 	tftpfile = (struct tftp_handle *)f->f_fsdata;
360 
361 #ifdef TFTP_NOTERMINATE
362 	/* let it time out ... */
363 #else
364 	tftp_terminate(tftpfile);
365 #endif
366 
367 	dealloc(tftpfile, sizeof(*tftpfile));
368 	return 0;
369 }
370 
371 int
372 tftp_write(struct open_file *f, void *start, size_t size, size_t *resid)
373 {
374 
375 	return EROFS;
376 }
377 
378 int
379 tftp_stat(struct open_file *f, struct stat *sb)
380 {
381 	struct tftp_handle *tftpfile;
382 	tftpfile = (struct tftp_handle *)f->f_fsdata;
383 
384 	sb->st_mode = 0444;
385 	sb->st_nlink = 1;
386 	sb->st_uid = 0;
387 	sb->st_gid = 0;
388 	sb->st_size = -1;
389 	return 0;
390 }
391 
392 off_t
393 tftp_seek(struct open_file *f, off_t offset, int where)
394 {
395 	struct tftp_handle *tftpfile;
396 	tftpfile = (struct tftp_handle *)f->f_fsdata;
397 
398 	switch (where) {
399 	case SEEK_SET:
400 		tftpfile->off = offset;
401 		break;
402 	case SEEK_CUR:
403 		tftpfile->off += offset;
404 		break;
405 	default:
406 		errno = EOFFSET;
407 		return -1;
408 	}
409 	return tftpfile->off;
410 }
411