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