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