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