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