xref: /netbsd-src/sys/lib/libsa/tftp.c (revision cac8e449158efc7261bebc8657cbb0125a2cfdde)
1 /*	$NetBSD: tftp.c,v 1.26 2008/05/11 11:29:12 chris 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 __P((struct iodesc *, void *, size_t, time_t));
94 static int tftp_makereq __P((struct tftp_handle *));
95 static int tftp_getnextblock __P((struct tftp_handle *));
96 #ifndef TFTP_NOTERMINATE
97 static void tftp_terminate __P((struct tftp_handle *));
98 #endif
99 static ssize_t tftp_size_of_file __P((struct tftp_handle *tftpfile));
100 
101 static ssize_t
102 recvtftp(struct iodesc *d, void *pkt, size_t len, time_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 	return 0;
274 }
275 
276 int
277 tftp_read(struct open_file *f, void *addr, size_t size, size_t *resid)
278 {
279 	struct tftp_handle *tftpfile;
280 #if !defined(LIBSA_NO_TWIDDLE)
281 	static int      tc = 0;
282 #endif
283 	tftpfile = (struct tftp_handle *)f->f_fsdata;
284 
285 	while (size > 0) {
286 		int needblock;
287 		size_t count;
288 
289 #if !defined(LIBSA_NO_TWIDDLE)
290 		if (!(tc++ % 16))
291 			twiddle();
292 #endif
293 
294 		needblock = tftpfile->off / SEGSIZE + 1;
295 
296 		if (tftpfile->currblock > needblock) {	/* seek backwards */
297 #ifndef TFTP_NOTERMINATE
298 			tftp_terminate(tftpfile);
299 #endif
300 			tftp_makereq(tftpfile);	/* no error check, it worked
301 			                         * for open */
302 		}
303 
304 		while (tftpfile->currblock < needblock) {
305 			int res;
306 
307 			res = tftp_getnextblock(tftpfile);
308 			if (res) {	/* no answer */
309 #ifdef DEBUG
310 				printf("tftp: read error (block %d->%d)\n",
311 				       tftpfile->currblock, needblock);
312 #endif
313 				return res;
314 			}
315 			if (tftpfile->islastblock)
316 				break;
317 		}
318 
319 		if (tftpfile->currblock == needblock) {
320 			size_t offinblock, inbuffer;
321 
322 			offinblock = tftpfile->off % SEGSIZE;
323 
324 			inbuffer = tftpfile->validsize - offinblock;
325 			if (inbuffer < 0) {
326 #ifdef DEBUG
327 				printf("tftp: invalid offset %d\n",
328 				    tftpfile->off);
329 #endif
330 				return EINVAL;
331 			}
332 			count = (size < inbuffer ? size : inbuffer);
333 			(void)memcpy(addr,
334 			    tftpfile->lastdata.t.th_data + offinblock,
335 			    count);
336 
337 			addr = (char *)addr + count;
338 			tftpfile->off += count;
339 			size -= count;
340 
341 			if ((tftpfile->islastblock) && (count == inbuffer))
342 				break;	/* EOF */
343 		} else {
344 #ifdef DEBUG
345 			printf("tftp: block %d not found\n", needblock);
346 #endif
347 			return EINVAL;
348 		}
349 
350 	}
351 
352 	if (resid)
353 		*resid = size;
354 	return 0;
355 }
356 
357 int
358 tftp_close(struct open_file *f)
359 {
360 	struct tftp_handle *tftpfile;
361 	tftpfile = (struct tftp_handle *)f->f_fsdata;
362 
363 #ifdef TFTP_NOTERMINATE
364 	/* let it time out ... */
365 #else
366 	tftp_terminate(tftpfile);
367 #endif
368 
369 	dealloc(tftpfile, sizeof(*tftpfile));
370 	return 0;
371 }
372 
373 int
374 tftp_write(struct open_file *f, void *start, size_t size, size_t *resid)
375 {
376 
377 	return EROFS;
378 }
379 
380 static ssize_t
381 tftp_size_of_file(struct tftp_handle *tftpfile)
382 {
383 	ssize_t filesize;
384 
385 	if (tftpfile->currblock > 1) {	/* move to start of file */
386 #ifndef TFTP_NOTERMINATE
387 		tftp_terminate(tftpfile);
388 #endif
389 		tftp_makereq(tftpfile);	/* no error check, it worked
390 		      			 * for open */
391 	}
392 
393 	/* start with the size of block 1 */
394 	filesize = tftpfile->validsize;
395 
396 	/* and keep adding the sizes till we hit the last block */
397 	while (!tftpfile->islastblock) {
398 		int res;
399 
400 		res = tftp_getnextblock(tftpfile);
401 		if (res) {	/* no answer */
402 #ifdef DEBUG
403 			printf("tftp: read error (block %d)\n",
404 					tftpfile->currblock);
405 #endif
406 			return -1;
407 		}
408 		filesize += tftpfile->validsize;
409 	}
410 #ifdef DEBUG
411 	printf("tftp_size_of_file: file is %d bytes\n", filesize);
412 #endif
413 	return filesize;
414 }
415 
416 int
417 tftp_stat(struct open_file *f, struct stat *sb)
418 {
419 	struct tftp_handle *tftpfile;
420 	tftpfile = (struct tftp_handle *)f->f_fsdata;
421 
422 	sb->st_mode = 0444;
423 	sb->st_nlink = 1;
424 	sb->st_uid = 0;
425 	sb->st_gid = 0;
426 	sb->st_size = tftp_size_of_file(tftpfile);
427 	return 0;
428 }
429 
430 off_t
431 tftp_seek(struct open_file *f, off_t offset, int where)
432 {
433 	struct tftp_handle *tftpfile;
434 	tftpfile = (struct tftp_handle *)f->f_fsdata;
435 
436 	switch (where) {
437 	case SEEK_SET:
438 		tftpfile->off = offset;
439 		break;
440 	case SEEK_CUR:
441 		tftpfile->off += offset;
442 		break;
443 	default:
444 		errno = EOFFSET;
445 		return -1;
446 	}
447 	return tftpfile->off;
448 }
449