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