xref: /netbsd-src/sys/lib/libsa/tftp.c (revision b5677b36047b601b9addaaa494a58ceae82c2a6c)
1 /*	$NetBSD: tftp.c,v 1.29 2009/01/17 14:00:36 tsutsui 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  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  *
27  */
28 
29 /*
30  * Simple TFTP implementation for libsa.
31  * Assumes:
32  *  - socket descriptor (int) at open_file->f_devdata
33  *  - server host IP in global servip
34  * Restrictions:
35  *  - read only
36  *  - lseek only with SEEK_SET or SEEK_CUR
37  *  - no big time differences between transfers (<tftp timeout)
38  */
39 
40 /*
41  * XXX Does not currently implement:
42  * XXX
43  * XXX LIBSA_NO_FS_CLOSE
44  * XXX LIBSA_NO_FS_SEEK
45  * XXX LIBSA_NO_FS_WRITE
46  * XXX LIBSA_NO_FS_SYMLINK (does this even make sense?)
47  * XXX LIBSA_FS_SINGLECOMPONENT (does this even make sense?)
48  */
49 
50 #include <sys/types.h>
51 #include <sys/stat.h>
52 #include <netinet/in.h>
53 #include <netinet/udp.h>
54 #include <netinet/in_systm.h>
55 #include <lib/libkern/libkern.h>
56 
57 #include "stand.h"
58 #include "net.h"
59 
60 #include "tftp.h"
61 
62 extern struct in_addr servip;
63 
64 static int      tftpport = 2000;
65 
66 #define RSPACE 520		/* max data packet, rounded up */
67 
68 struct tftp_handle {
69 	struct iodesc  *iodesc;
70 	int             currblock;	/* contents of lastdata */
71 	int             islastblock;	/* flag */
72 	int             validsize;
73 	int             off;
74 	const char     *path;	/* saved for re-requests */
75 	struct {
76 		u_char header[HEADER_SIZE];
77 		struct tftphdr t;
78 		u_char space[RSPACE];
79 	} lastdata;
80 };
81 
82 static const int tftperrors[8] = {
83 	0,			/* ??? */
84 	ENOENT,
85 	EPERM,
86 	ENOSPC,
87 	EINVAL,			/* ??? */
88 	EINVAL,			/* ??? */
89 	EEXIST,
90 	EINVAL,			/* ??? */
91 };
92 
93 static ssize_t recvtftp(struct iodesc *, void *, size_t, saseconds_t);
94 static int tftp_makereq(struct tftp_handle *);
95 static int tftp_getnextblock(struct tftp_handle *);
96 #ifndef TFTP_NOTERMINATE
97 static void tftp_terminate(struct tftp_handle *);
98 #endif
99 static ssize_t tftp_size_of_file(struct tftp_handle *tftpfile);
100 
101 static ssize_t
102 recvtftp(struct iodesc *d, void *pkt, size_t len, saseconds_t tleft)
103 {
104 	ssize_t n;
105 	struct tftphdr *t;
106 
107 	errno = 0;
108 
109 	n = readudp(d, pkt, len, tleft);
110 
111 	if (n < 4)
112 		return -1;
113 
114 	t = (struct tftphdr *)pkt;
115 	switch (ntohs(t->th_opcode)) {
116 	case DATA:
117 		if (htons(t->th_block) != d->xid) {
118 			/*
119 			 * Expected block?
120 			 */
121 			return -1;
122 		}
123 		if (d->xid == 1) {
124 			/*
125 			 * First data packet from new port.
126 			 */
127 			struct udphdr *uh;
128 			uh = (struct udphdr *)pkt - 1;
129 			d->destport = uh->uh_sport;
130 		} /* else check uh_sport has not changed??? */
131 		return (n - (t->th_data - (char *)t));
132 	case ERROR:
133 		if ((unsigned int)ntohs(t->th_code) >= 8) {
134 			printf("illegal tftp error %d\n", ntohs(t->th_code));
135 			errno = EIO;
136 		} else {
137 #ifdef DEBUG
138 			printf("tftp-error %d\n", ntohs(t->th_code));
139 #endif
140 			errno = tftperrors[ntohs(t->th_code)];
141 		}
142 		return -1;
143 	default:
144 #ifdef DEBUG
145 		printf("tftp type %d not handled\n", ntohs(t->th_opcode));
146 #endif
147 		return -1;
148 	}
149 }
150 
151 /* send request, expect first block (or error) */
152 static int
153 tftp_makereq(struct tftp_handle *h)
154 {
155 	struct {
156 		u_char header[HEADER_SIZE];
157 		struct tftphdr t;
158 		u_char space[FNAME_SIZE + 6];
159 	} wbuf;
160 	char           *wtail;
161 	int             l;
162 	ssize_t         res;
163 	struct tftphdr *t;
164 
165 	wbuf.t.th_opcode = htons((u_short)RRQ);
166 	wtail = wbuf.t.th_stuff;
167 	l = strlen(h->path);
168 	(void)memcpy(wtail, h->path, l + 1);
169 	wtail += l + 1;
170 	(void)memcpy(wtail, "octet", 6);
171 	wtail += 6;
172 
173 	t = &h->lastdata.t;
174 
175 	/* h->iodesc->myport = htons(--tftpport); */
176 	h->iodesc->myport = htons(tftpport + (getsecs() & 0x3ff));
177 	h->iodesc->destport = htons(IPPORT_TFTP);
178 	h->iodesc->xid = 1;	/* expected block */
179 
180 	res = sendrecv(h->iodesc, sendudp, &wbuf.t, wtail - (char *)&wbuf.t,
181 		       recvtftp, t, sizeof(*t) + RSPACE);
182 
183 	if (res == -1)
184 		return errno;
185 
186 	h->currblock = 1;
187 	h->validsize = res;
188 	h->islastblock = 0;
189 	if (res < SEGSIZE)
190 		h->islastblock = 1;	/* very short file */
191 	return 0;
192 }
193 
194 /* ack block, expect next */
195 static int
196 tftp_getnextblock(struct tftp_handle *h)
197 {
198 	struct {
199 		u_char header[HEADER_SIZE];
200 		struct tftphdr t;
201 	} wbuf;
202 	char           *wtail;
203 	int             res;
204 	struct tftphdr *t;
205 
206 	wbuf.t.th_opcode = htons((u_short)ACK);
207 	wbuf.t.th_block = htons((u_short)h->currblock);
208 	wtail = (char *)&wbuf.t.th_data;
209 
210 	t = &h->lastdata.t;
211 
212 	h->iodesc->xid = h->currblock + 1;	/* expected block */
213 
214 	res = sendrecv(h->iodesc, sendudp, &wbuf.t, wtail - (char *)&wbuf.t,
215 		       recvtftp, t, sizeof(*t) + RSPACE);
216 
217 	if (res == -1)		/* 0 is OK! */
218 		return errno;
219 
220 	h->currblock++;
221 	h->validsize = res;
222 	if (res < SEGSIZE)
223 		h->islastblock = 1;	/* EOF */
224 	return 0;
225 }
226 
227 #ifndef TFTP_NOTERMINATE
228 static void
229 tftp_terminate(struct tftp_handle *h)
230 {
231 	struct {
232 		u_char header[HEADER_SIZE];
233 		struct tftphdr t;
234 	} wbuf;
235 	char           *wtail;
236 
237 	if (h->islastblock) {
238 		wbuf.t.th_opcode = htons((u_short)ACK);
239 		wbuf.t.th_block = htons((u_short)h->currblock);
240 	} else {
241 		wbuf.t.th_opcode = htons((u_short)ERROR);
242 		wbuf.t.th_code = htons((u_short)ENOSPACE); /* ??? */
243 	}
244 	wtail = (char *)&wbuf.t.th_data;
245 
246 	(void)sendudp(h->iodesc, &wbuf.t, wtail - (char *)&wbuf.t);
247 }
248 #endif
249 
250 int
251 tftp_open(const char *path, struct open_file *f)
252 {
253 	struct tftp_handle *tftpfile;
254 	struct iodesc  *io;
255 	int             res;
256 
257 	tftpfile = (struct tftp_handle *)alloc(sizeof(*tftpfile));
258 	if (!tftpfile)
259 		return ENOMEM;
260 
261 	tftpfile->iodesc = io = socktodesc(*(int *)(f->f_devdata));
262 	io->destip = servip;
263 	tftpfile->off = 0;
264 	tftpfile->path = path;	/* XXXXXXX we hope it's static */
265 
266 	res = tftp_makereq(tftpfile);
267 
268 	if (res) {
269 		dealloc(tftpfile, sizeof(*tftpfile));
270 		return res;
271 	}
272 	f->f_fsdata = (void *)tftpfile;
273 	fsmod = "nfs";
274 	return 0;
275 }
276 
277 int
278 tftp_read(struct open_file *f, void *addr, size_t size, size_t *resid)
279 {
280 	struct tftp_handle *tftpfile;
281 #if !defined(LIBSA_NO_TWIDDLE)
282 	static int      tc = 0;
283 #endif
284 	tftpfile = (struct tftp_handle *)f->f_fsdata;
285 
286 	while (size > 0) {
287 		int needblock;
288 		size_t count;
289 
290 #if !defined(LIBSA_NO_TWIDDLE)
291 		if (!(tc++ % 16))
292 			twiddle();
293 #endif
294 
295 		needblock = tftpfile->off / SEGSIZE + 1;
296 
297 		if (tftpfile->currblock > needblock) {	/* seek backwards */
298 #ifndef TFTP_NOTERMINATE
299 			tftp_terminate(tftpfile);
300 #endif
301 			tftp_makereq(tftpfile);	/* no error check, it worked
302 			                         * for open */
303 		}
304 
305 		while (tftpfile->currblock < needblock) {
306 			int res;
307 
308 			res = tftp_getnextblock(tftpfile);
309 			if (res) {	/* no answer */
310 #ifdef DEBUG
311 				printf("tftp: read error (block %d->%d)\n",
312 				       tftpfile->currblock, needblock);
313 #endif
314 				return res;
315 			}
316 			if (tftpfile->islastblock)
317 				break;
318 		}
319 
320 		if (tftpfile->currblock == needblock) {
321 			size_t offinblock, inbuffer;
322 
323 			offinblock = tftpfile->off % SEGSIZE;
324 
325 			if (offinblock > tftpfile->validsize) {
326 #ifdef DEBUG
327 				printf("tftp: invalid offset %d\n",
328 				    tftpfile->off);
329 #endif
330 				return EINVAL;
331 			}
332 			inbuffer = tftpfile->validsize - offinblock;
333 			count = (size < inbuffer ? size : inbuffer);
334 			(void)memcpy(addr,
335 			    tftpfile->lastdata.t.th_data + offinblock,
336 			    count);
337 
338 			addr = (char *)addr + count;
339 			tftpfile->off += count;
340 			size -= count;
341 
342 			if ((tftpfile->islastblock) && (count == inbuffer))
343 				break;	/* EOF */
344 		} else {
345 #ifdef DEBUG
346 			printf("tftp: block %d not found\n", needblock);
347 #endif
348 			return EINVAL;
349 		}
350 
351 	}
352 
353 	if (resid)
354 		*resid = size;
355 	return 0;
356 }
357 
358 int
359 tftp_close(struct open_file *f)
360 {
361 	struct tftp_handle *tftpfile;
362 	tftpfile = (struct tftp_handle *)f->f_fsdata;
363 
364 #ifdef TFTP_NOTERMINATE
365 	/* let it time out ... */
366 #else
367 	tftp_terminate(tftpfile);
368 #endif
369 
370 	dealloc(tftpfile, sizeof(*tftpfile));
371 	return 0;
372 }
373 
374 int
375 tftp_write(struct open_file *f, void *start, size_t size, size_t *resid)
376 {
377 
378 	return EROFS;
379 }
380 
381 static ssize_t
382 tftp_size_of_file(struct tftp_handle *tftpfile)
383 {
384 	ssize_t filesize;
385 
386 	if (tftpfile->currblock > 1) {	/* move to start of file */
387 #ifndef TFTP_NOTERMINATE
388 		tftp_terminate(tftpfile);
389 #endif
390 		tftp_makereq(tftpfile);	/* no error check, it worked
391 		      			 * for open */
392 	}
393 
394 	/* start with the size of block 1 */
395 	filesize = tftpfile->validsize;
396 
397 	/* and keep adding the sizes till we hit the last block */
398 	while (!tftpfile->islastblock) {
399 		int res;
400 
401 		res = tftp_getnextblock(tftpfile);
402 		if (res) {	/* no answer */
403 #ifdef DEBUG
404 			printf("tftp: read error (block %d)\n",
405 					tftpfile->currblock);
406 #endif
407 			return -1;
408 		}
409 		filesize += tftpfile->validsize;
410 	}
411 #ifdef DEBUG
412 	printf("tftp_size_of_file: file is %d bytes\n", filesize);
413 #endif
414 	return filesize;
415 }
416 
417 int
418 tftp_stat(struct open_file *f, struct stat *sb)
419 {
420 	struct tftp_handle *tftpfile;
421 	tftpfile = (struct tftp_handle *)f->f_fsdata;
422 
423 	sb->st_mode = 0444;
424 	sb->st_nlink = 1;
425 	sb->st_uid = 0;
426 	sb->st_gid = 0;
427 	sb->st_size = tftp_size_of_file(tftpfile);
428 	return 0;
429 }
430 
431 off_t
432 tftp_seek(struct open_file *f, off_t offset, int where)
433 {
434 	struct tftp_handle *tftpfile;
435 	tftpfile = (struct tftp_handle *)f->f_fsdata;
436 
437 	switch (where) {
438 	case SEEK_SET:
439 		tftpfile->off = offset;
440 		break;
441 	case SEEK_CUR:
442 		tftpfile->off += offset;
443 		break;
444 	default:
445 		errno = EOFFSET;
446 		return -1;
447 	}
448 	return tftpfile->off;
449 }
450