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