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