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