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