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