tftp.c revision 1.14
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
68extern struct in_addr servip;
69
70static int      tftpport = 2000;
71
72#define RSPACE 520		/* max data packet, rounded up */
73
74struct 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
88static const int tftperrors[8] = {
89	0,			/* ??? */
90	ENOENT,
91	EPERM,
92	ENOSPC,
93	EINVAL,			/* ??? */
94	EINVAL,			/* ??? */
95	EEXIST,
96	EINVAL			/* ??? */
97};
98
99static ssize_t recvtftp __P((struct iodesc *, void *, size_t, time_t));
100static int tftp_makereq __P((struct tftp_handle *));
101static int tftp_getnextblock __P((struct tftp_handle *));
102#ifndef TFTP_NOTERMINATE
103static void tftp_terminate __P((struct tftp_handle *));
104#endif
105
106static ssize_t
107recvtftp(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) */
161static int
162tftp_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 */
205static int
206tftp_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
239static void
240tftp_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
262int
263tftp_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
290int
291tftp_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
373int
374tftp_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
390int
391tftp_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
400int
401tftp_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
416off_t
417tftp_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