xref: /netbsd-src/sys/lib/libsa/tftp.c (revision eec9572cb1dcafa40314440ae655ca84b8fd9dc5)
1 /*	$NetBSD: tftp.c,v 1.38 2022/08/07 05:51:55 rin 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[UDP_TOTAL_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
recvtftp(struct iodesc * d,void * pkt,size_t len,saseconds_t tleft)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 (ntohs(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
tftp_makereq(struct tftp_handle * h)153 tftp_makereq(struct tftp_handle *h)
154 {
155 	struct {
156 		u_char header[UDP_TOTAL_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
tftp_getnextblock(struct tftp_handle * h)196 tftp_getnextblock(struct tftp_handle *h)
197 {
198 	struct {
199 		u_char header[UDP_TOTAL_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
tftp_terminate(struct tftp_handle * h)229 tftp_terminate(struct tftp_handle *h)
230 {
231 	struct {
232 		u_char header[UDP_TOTAL_HEADER_SIZE];
233 		struct tftphdr t;
234 	} wbuf;
235 	char           *wtail;
236 
237 	wtail = (char *)&wbuf.t.th_data;
238 	if (h->islastblock) {
239 		wbuf.t.th_opcode = htons((u_short)ACK);
240 		wbuf.t.th_block = htons((u_short)h->currblock);
241 	} else {
242 		wbuf.t.th_opcode = htons((u_short)ERROR);
243 		wbuf.t.th_code = htons((u_short)ENOSPACE); /* ??? */
244 		*wtail++ = '\0'; /* empty error string */
245 	}
246 
247 	(void)sendudp(h->iodesc, &wbuf.t, wtail - (char *)&wbuf.t);
248 }
249 #endif
250 
251 __compactcall int
tftp_open(const char * path,struct open_file * f)252 tftp_open(const char *path, struct open_file *f)
253 {
254 	struct tftp_handle *tftpfile;
255 	struct iodesc  *io;
256 	int             res;
257 
258 	tftpfile = (struct tftp_handle *)alloc(sizeof(*tftpfile));
259 	if (!tftpfile)
260 		return ENOMEM;
261 
262 	tftpfile->iodesc = io = socktodesc(*(int *)(f->f_devdata));
263 	io->destip = servip;
264 	tftpfile->off = 0;
265 	tftpfile->path = path;	/* XXXXXXX we hope it's static */
266 
267 	res = tftp_makereq(tftpfile);
268 
269 	if (res) {
270 		dealloc(tftpfile, sizeof(*tftpfile));
271 		return res;
272 	}
273 	f->f_fsdata = (void *)tftpfile;
274 	fsmod = "nfs";
275 	return 0;
276 }
277 
278 __compactcall int
tftp_read(struct open_file * f,void * addr,size_t size,size_t * resid)279 tftp_read(struct open_file *f, void *addr, size_t size, size_t *resid)
280 {
281 	struct tftp_handle *tftpfile;
282 #if !defined(LIBSA_NO_TWIDDLE)
283 	static int      tc = 0;
284 #endif
285 	tftpfile = (struct tftp_handle *)f->f_fsdata;
286 
287 	while (size > 0) {
288 		int needblock;
289 		size_t count;
290 
291 		needblock = tftpfile->off / SEGSIZE + 1;
292 
293 		if (tftpfile->currblock > needblock) {	/* seek backwards */
294 #ifndef TFTP_NOTERMINATE
295 			tftp_terminate(tftpfile);
296 #endif
297 			tftp_makereq(tftpfile);	/* no error check, it worked
298 			                         * for open */
299 		}
300 
301 		while (tftpfile->currblock < needblock) {
302 			int res;
303 
304 #if !defined(LIBSA_NO_TWIDDLE)
305 			if (!(tc++ % 16))
306 				twiddle();
307 #endif
308 
309 			res = tftp_getnextblock(tftpfile);
310 			if (res) {	/* no answer */
311 #ifdef DEBUG
312 				printf("%s: read error (block %d->%d)\n",
313 				    __func__, tftpfile->currblock, needblock);
314 #endif
315 				return res;
316 			}
317 			if (tftpfile->islastblock)
318 				break;
319 		}
320 
321 		if (tftpfile->currblock == needblock) {
322 			size_t offinblock, inbuffer;
323 
324 			offinblock = tftpfile->off % SEGSIZE;
325 
326 			if ((int)offinblock > tftpfile->validsize) {
327 #ifdef DEBUG
328 				printf("%s: invalid offset %d\n", __func__,
329 				    tftpfile->off);
330 #endif
331 				return EINVAL;
332 			}
333 			inbuffer = tftpfile->validsize - offinblock;
334 			count = (size < inbuffer ? size : inbuffer);
335 			(void)memcpy(addr,
336 			    tftpfile->lastdata.t.th_data + offinblock,
337 			    count);
338 
339 			addr = (char *)addr + count;
340 			tftpfile->off += count;
341 			size -= count;
342 
343 			if ((tftpfile->islastblock) && (count == inbuffer))
344 				break;	/* EOF */
345 		} else {
346 #ifdef DEBUG
347 			printf("%s: block %d not found\n",
348 			    __func__, needblock);
349 #endif
350 			return EINVAL;
351 		}
352 
353 	}
354 
355 	if (resid)
356 		*resid = size;
357 	return 0;
358 }
359 
360 __compactcall int
tftp_close(struct open_file * f)361 tftp_close(struct open_file *f)
362 {
363 	struct tftp_handle *tftpfile;
364 	tftpfile = (struct tftp_handle *)f->f_fsdata;
365 
366 #ifdef TFTP_NOTERMINATE
367 	/* let it time out ... */
368 #else
369 	tftp_terminate(tftpfile);
370 #endif
371 
372 	dealloc(tftpfile, sizeof(*tftpfile));
373 	return 0;
374 }
375 
376 __compactcall int
tftp_write(struct open_file * f,void * start,size_t size,size_t * resid)377 tftp_write(struct open_file *f, void *start, size_t size, size_t *resid)
378 {
379 
380 	return EROFS;
381 }
382 
383 static ssize_t
tftp_size_of_file(struct tftp_handle * tftpfile)384 tftp_size_of_file(struct tftp_handle *tftpfile)
385 {
386 	ssize_t filesize;
387 
388 	if (tftpfile->currblock > 1) {	/* move to start of file */
389 #ifndef TFTP_NOTERMINATE
390 		tftp_terminate(tftpfile);
391 #endif
392 		tftp_makereq(tftpfile);	/* no error check, it worked
393 		      			 * for open */
394 	}
395 
396 	/* start with the size of block 1 */
397 	filesize = tftpfile->validsize;
398 
399 	/* and keep adding the sizes till we hit the last block */
400 	while (!tftpfile->islastblock) {
401 		int res;
402 
403 		res = tftp_getnextblock(tftpfile);
404 		if (res) {	/* no answer */
405 #ifdef DEBUG
406 			printf("%s: read error (block %d)\n",
407 			    __func__, tftpfile->currblock);
408 #endif
409 			return -1;
410 		}
411 		filesize += tftpfile->validsize;
412 	}
413 #ifdef DEBUG
414 	printf("%s: file is %zu bytes\n", __func__, filesize);
415 #endif
416 	return filesize;
417 }
418 
419 __compactcall int
tftp_stat(struct open_file * f,struct stat * sb)420 tftp_stat(struct open_file *f, struct stat *sb)
421 {
422 	struct tftp_handle *tftpfile;
423 	tftpfile = (struct tftp_handle *)f->f_fsdata;
424 
425 	sb->st_mode = 0444;
426 	sb->st_nlink = 1;
427 	sb->st_uid = 0;
428 	sb->st_gid = 0;
429 	sb->st_size = tftp_size_of_file(tftpfile);
430 	return 0;
431 }
432 
433 #if defined(LIBSA_ENABLE_LS_OP)
434 #include "ls.h"
435 __compactcall void
tftp_ls(struct open_file * f,const char * pattern)436 tftp_ls(struct open_file *f, const char *pattern)
437 {
438 	lsunsup("tftp");
439 }
440 #endif
441 
442 __compactcall off_t
tftp_seek(struct open_file * f,off_t offset,int where)443 tftp_seek(struct open_file *f, off_t offset, int where)
444 {
445 	struct tftp_handle *tftpfile;
446 	tftpfile = (struct tftp_handle *)f->f_fsdata;
447 
448 	switch (where) {
449 	case SEEK_SET:
450 		tftpfile->off = offset;
451 		break;
452 	case SEEK_CUR:
453 		tftpfile->off += offset;
454 		break;
455 	default:
456 		errno = EOFFSET;
457 		return -1;
458 	}
459 	return tftpfile->off;
460 }
461