xref: /netbsd-src/sys/lib/libsa/tftp.c (revision b8c616269f5ebf18ab2e35cb8099d683130a177c)
1 /*	$NetBSD: tftp.c,v 1.12 2003/01/12 18:59:15 christos 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 #include "netif.h"
66 
67 #include "tftp.h"
68 
69 extern struct in_addr servip;
70 
71 static int      tftpport = 2000;
72 
73 #define RSPACE 520		/* max data packet, rounded up */
74 
75 struct tftp_handle {
76 	struct iodesc  *iodesc;
77 	int             currblock;	/* contents of lastdata */
78 	int             islastblock;	/* flag */
79 	int             validsize;
80 	int             off;
81 	char           *path;	/* saved for re-requests */
82 	struct {
83 		u_char header[HEADER_SIZE];
84 		struct tftphdr t;
85 		u_char space[RSPACE];
86 	} lastdata;
87 };
88 
89 static int tftperrors[8] = {
90 	0,			/* ??? */
91 	ENOENT,
92 	EPERM,
93 	ENOSPC,
94 	EINVAL,			/* ??? */
95 	EINVAL,			/* ??? */
96 	EEXIST,
97 	EINVAL			/* ??? */
98 };
99 
100 static ssize_t recvtftp __P((struct iodesc *, void *, size_t, time_t));
101 static int tftp_makereq __P((struct tftp_handle *));
102 static int tftp_getnextblock __P((struct tftp_handle *));
103 #ifndef TFTP_NOTERMINATE
104 static void tftp_terminate __P((struct tftp_handle *));
105 #endif
106 
107 static ssize_t
108 recvtftp(d, pkt, len, tleft)
109 	struct iodesc *d;
110 	void  *pkt;
111 	size_t len;
112 	time_t          tleft;
113 {
114 	ssize_t n;
115 	struct tftphdr *t;
116 
117 	errno = 0;
118 
119 	n = readudp(d, pkt, len, tleft);
120 
121 	if (n < 4)
122 		return (-1);
123 
124 	t = (struct tftphdr *) pkt;
125 	switch (ntohs(t->th_opcode)) {
126 	case DATA:
127 		if (htons(t->th_block) != d->xid) {
128 			/*
129 			 * Expected block?
130 			 */
131 			return (-1);
132 		}
133 		if (d->xid == 1) {
134 			/*
135 			 * First data packet from new port.
136 			 */
137 			struct udphdr *uh;
138 			uh = (struct udphdr *) pkt - 1;
139 			d->destport = uh->uh_sport;
140 		} /* else check uh_sport has not changed??? */
141 		return (n - (t->th_data - (char *)t));
142 	case ERROR:
143 		if ((unsigned) ntohs(t->th_code) >= 8) {
144 			printf("illegal tftp error %d\n", ntohs(t->th_code));
145 			errno = EIO;
146 		} else {
147 #ifdef DEBUG
148 			printf("tftp-error %d\n", ntohs(t->th_code));
149 #endif
150 			errno = tftperrors[ntohs(t->th_code)];
151 		}
152 		return (-1);
153 	default:
154 #ifdef DEBUG
155 		printf("tftp type %d not handled\n", ntohs(t->th_opcode));
156 #endif
157 		return (-1);
158 	}
159 }
160 
161 /* send request, expect first block (or error) */
162 static int
163 tftp_makereq(h)
164 	struct tftp_handle *h;
165 {
166 	struct {
167 		u_char header[HEADER_SIZE];
168 		struct tftphdr  t;
169 		u_char space[FNAME_SIZE + 6];
170 	} wbuf;
171 	char           *wtail;
172 	int             l;
173 	ssize_t         res;
174 	struct tftphdr *t;
175 
176 	wbuf.t.th_opcode = htons((u_short) RRQ);
177 	wtail = wbuf.t.th_stuff;
178 	l = strlen(h->path);
179 	bcopy(h->path, wtail, l + 1);
180 	wtail += l + 1;
181 	bcopy("octet", wtail, 6);
182 	wtail += 6;
183 
184 	t = &h->lastdata.t;
185 
186 	/* h->iodesc->myport = htons(--tftpport); */
187 	h->iodesc->myport = htons(tftpport + (getsecs() & 0x3ff));
188 	h->iodesc->destport = htons(IPPORT_TFTP);
189 	h->iodesc->xid = 1;	/* expected block */
190 
191 	res = sendrecv(h->iodesc, sendudp, &wbuf.t, wtail - (char *) &wbuf.t,
192 		       recvtftp, t, sizeof(*t) + RSPACE);
193 
194 	if (res == -1)
195 		return (errno);
196 
197 	h->currblock = 1;
198 	h->validsize = res;
199 	h->islastblock = 0;
200 	if (res < SEGSIZE)
201 		h->islastblock = 1;	/* very short file */
202 	return (0);
203 }
204 
205 /* ack block, expect next */
206 static int
207 tftp_getnextblock(h)
208 	struct tftp_handle *h;
209 {
210 	struct {
211 		u_char header[HEADER_SIZE];
212 		struct tftphdr t;
213 	} wbuf;
214 	char           *wtail;
215 	int             res;
216 	struct tftphdr *t;
217 
218 	wbuf.t.th_opcode = htons((u_short) ACK);
219 	wbuf.t.th_block = htons((u_short) h->currblock);
220 	wtail = (char *) &wbuf.t.th_data;
221 
222 	t = &h->lastdata.t;
223 
224 	h->iodesc->xid = h->currblock + 1;	/* expected block */
225 
226 	res = sendrecv(h->iodesc, sendudp, &wbuf.t, wtail - (char *) &wbuf.t,
227 		       recvtftp, t, sizeof(*t) + RSPACE);
228 
229 	if (res == -1)		/* 0 is OK! */
230 		return (errno);
231 
232 	h->currblock++;
233 	h->validsize = res;
234 	if (res < SEGSIZE)
235 		h->islastblock = 1;	/* EOF */
236 	return (0);
237 }
238 
239 #ifndef TFTP_NOTERMINATE
240 static void
241 tftp_terminate(h)
242 	struct tftp_handle *h;
243 {
244 	struct {
245 		u_char header[HEADER_SIZE];
246 		struct tftphdr t;
247 	} wbuf;
248 	char           *wtail;
249 
250 	if (h->islastblock) {
251 		wbuf.t.th_opcode = htons((u_short) ACK);
252 		wbuf.t.th_block = htons((u_short) h->currblock);
253 	} else {
254 		wbuf.t.th_opcode = htons((u_short) ERROR);
255 		wbuf.t.th_code = htons((u_short) ENOSPACE); /* ??? */
256 	}
257 	wtail = (char *) &wbuf.t.th_data;
258 
259 	(void) sendudp(h->iodesc, &wbuf.t, wtail - (char *) &wbuf.t);
260 }
261 #endif
262 
263 int
264 tftp_open(path, f)
265 	char           *path;
266 	struct open_file *f;
267 {
268 	struct tftp_handle *tftpfile;
269 	struct iodesc  *io;
270 	int             res;
271 
272 	tftpfile = (struct tftp_handle *) alloc(sizeof(*tftpfile));
273 	if (!tftpfile)
274 		return (ENOMEM);
275 
276 	tftpfile->iodesc = io = socktodesc(*(int *) (f->f_devdata));
277 	io->destip = servip;
278 	tftpfile->off = 0;
279 	tftpfile->path = path;	/* XXXXXXX we hope it's static */
280 
281 	res = tftp_makereq(tftpfile);
282 
283 	if (res) {
284 		free(tftpfile, sizeof(*tftpfile));
285 		return (res);
286 	}
287 	f->f_fsdata = (void *) tftpfile;
288 	return (0);
289 }
290 
291 int
292 tftp_read(f, addr, size, resid)
293 	struct open_file *f;
294 	void           *addr;
295 	size_t          size;
296 	size_t         *resid;	/* out */
297 {
298 	struct tftp_handle *tftpfile;
299 #if !defined(LIBSA_NO_TWIDDLE)
300 	static int      tc = 0;
301 #endif
302 	tftpfile = (struct tftp_handle *) f->f_fsdata;
303 
304 	while (size > 0) {
305 		int needblock, count;
306 
307 #if !defined(LIBSA_NO_TWIDDLE)
308 		if (!(tc++ % 16))
309 			twiddle();
310 #endif
311 
312 		needblock = tftpfile->off / SEGSIZE + 1;
313 
314 		if (tftpfile->currblock > needblock) {	/* seek backwards */
315 #ifndef TFTP_NOTERMINATE
316 			tftp_terminate(tftpfile);
317 #endif
318 			tftp_makereq(tftpfile);	/* no error check, it worked
319 						 * for open */
320 		}
321 
322 		while (tftpfile->currblock < needblock) {
323 			int res;
324 
325 			res = tftp_getnextblock(tftpfile);
326 			if (res) {	/* no answer */
327 #ifdef DEBUG
328 				printf("tftp: read error (block %d->%d)\n",
329 				       tftpfile->currblock, needblock);
330 #endif
331 				return (res);
332 			}
333 			if (tftpfile->islastblock)
334 				break;
335 		}
336 
337 		if (tftpfile->currblock == needblock) {
338 			int offinblock, inbuffer;
339 
340 			offinblock = tftpfile->off % SEGSIZE;
341 
342 			inbuffer = tftpfile->validsize - offinblock;
343 			if (inbuffer < 0) {
344 #ifdef DEBUG
345 				printf("tftp: invalid offset %d\n",
346 				    tftpfile->off);
347 #endif
348 				return (EINVAL);
349 			}
350 			count = (size < inbuffer ? size : inbuffer);
351 			bcopy(tftpfile->lastdata.t.th_data + offinblock,
352 			    addr, count);
353 
354 			addr = (caddr_t)addr + count;
355 			tftpfile->off += count;
356 			size -= count;
357 
358 			if ((tftpfile->islastblock) && (count == inbuffer))
359 				break;	/* EOF */
360 		} else {
361 #ifdef DEBUG
362 			printf("tftp: block %d not found\n", needblock);
363 #endif
364 			return (EINVAL);
365 		}
366 
367 	}
368 
369 	if (resid)
370 		*resid = size;
371 	return (0);
372 }
373 
374 int
375 tftp_close(f)
376 	struct open_file *f;
377 {
378 	struct tftp_handle *tftpfile;
379 	tftpfile = (struct tftp_handle *) f->f_fsdata;
380 
381 #ifdef TFTP_NOTERMINATE
382 	/* let it time out ... */
383 #else
384 	tftp_terminate(tftpfile);
385 #endif
386 
387 	free(tftpfile, sizeof(*tftpfile));
388 	return (0);
389 }
390 
391 int
392 tftp_write(f, start, size, resid)
393 	struct open_file *f;
394 	void           *start;
395 	size_t          size;
396 	size_t         *resid;	/* out */
397 {
398 	return (EROFS);
399 }
400 
401 int
402 tftp_stat(f, sb)
403 	struct open_file *f;
404 	struct stat    *sb;
405 {
406 	struct tftp_handle *tftpfile;
407 	tftpfile = (struct tftp_handle *) f->f_fsdata;
408 
409 	sb->st_mode = 0444;
410 	sb->st_nlink = 1;
411 	sb->st_uid = 0;
412 	sb->st_gid = 0;
413 	sb->st_size = -1;
414 	return (0);
415 }
416 
417 off_t
418 tftp_seek(f, offset, where)
419 	struct open_file *f;
420 	off_t           offset;
421 	int             where;
422 {
423 	struct tftp_handle *tftpfile;
424 	tftpfile = (struct tftp_handle *) f->f_fsdata;
425 
426 	switch (where) {
427 	case SEEK_SET:
428 		tftpfile->off = offset;
429 		break;
430 	case SEEK_CUR:
431 		tftpfile->off += offset;
432 		break;
433 	default:
434 		errno = EOFFSET;
435 		return (-1);
436 	}
437 	return (tftpfile->off);
438 }
439