Remove some orphaned extern declarations.
[dragonfly.git] / lib / libstand / tftp.c
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) != 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         tftpfile = (struct tftp_handle *) malloc(sizeof(*tftpfile));
279         if (!tftpfile)
280                 return (ENOMEM);
281
282         tftpfile->iodesc = io = socktodesc(*(int *) (f->f_devdata));
283         if (io == NULL)
284                 return (EINVAL);
285
286         io->destip = servip;
287         tftpfile->off = 0;
288         tftpfile->path = strdup(path);
289         if (tftpfile->path == NULL) {
290             free(tftpfile);
291             return(ENOMEM);
292         }
293
294         res = tftp_makereq(tftpfile);
295
296         if (res) {
297                 free(tftpfile->path);
298                 free(tftpfile);
299                 return (res);
300         }
301         f->f_fsdata = (void *) tftpfile;
302         return (0);
303 }
304
305 /*
306  * Parameters:
307  *      resid:  out
308  */
309 static int 
310 tftp_read(struct open_file *f, void *addr, size_t size, size_t *resid)
311 {
312         struct tftp_handle *tftpfile;
313         static int      tc = 0;
314         tftpfile = (struct tftp_handle *) f->f_fsdata;
315
316         while (size > 0) {
317                 int needblock, count;
318
319                 if (!(tc++ % 16))
320                         twiddle();
321
322                 needblock = tftpfile->off / SEGSIZE + 1;
323
324                 if (tftpfile->currblock > needblock)    /* seek backwards */
325                         tftp_makereq(tftpfile); /* no error check, it worked
326                                                  * for open */
327
328                 while (tftpfile->currblock < needblock) {
329                         int res;
330
331                         res = tftp_getnextblock(tftpfile);
332                         if (res) {      /* no answer */
333 #ifdef DEBUG
334                                 printf("tftp: read error\n");
335 #endif
336                                 return (res);
337                         }
338                         if (tftpfile->islastblock)
339                                 break;
340                 }
341
342                 if (tftpfile->currblock == needblock) {
343                         int offinblock, inbuffer;
344
345                         offinblock = tftpfile->off % SEGSIZE;
346
347                         inbuffer = tftpfile->validsize - offinblock;
348                         if (inbuffer < 0) {
349 #ifdef DEBUG
350                                 printf("tftp: invalid offset %d\n",
351                                     tftpfile->off);
352 #endif
353                                 return (EINVAL);
354                         }
355                         count = (size < inbuffer ? size : inbuffer);
356                         bcopy(tftpfile->lastdata.t.th_data + offinblock,
357                               addr, count);
358
359                         addr = (char *)addr + count;
360                         tftpfile->off += count;
361                         size -= count;
362
363                         if ((tftpfile->islastblock) && (count == inbuffer))
364                                 break;  /* EOF */
365                 } else {
366 #ifdef DEBUG
367                         printf("tftp: block %d not found\n", needblock);
368 #endif
369                         return (EINVAL);
370                 }
371
372         }
373
374         if (resid)
375                 *resid = size;
376         return (0);
377 }
378
379 static int 
380 tftp_close(struct open_file *f)
381 {
382         struct tftp_handle *tftpfile;
383         tftpfile = (struct tftp_handle *) f->f_fsdata;
384
385         /* let it time out ... */
386         f->f_fsdata = NULL;
387         if (tftpfile) {
388                 free(tftpfile->path);
389                 free(tftpfile);
390                 f->f_fsdata = NULL;
391         }
392         return (0);
393 }
394
395 /*
396  * Parameters:
397  *      resid:  out
398  */
399 static int 
400 tftp_write(struct open_file *f, void *start, size_t size, size_t *resid)
401 {
402         return (EROFS);
403 }
404
405 static int 
406 tftp_stat(struct open_file *f, struct stat *sb)
407 {
408         sb->st_mode = 0444 | S_IFREG;
409         sb->st_nlink = 1;
410         sb->st_uid = 0;
411         sb->st_gid = 0;
412         sb->st_size = -1;
413         return (0);
414 }
415
416 static off_t 
417 tftp_seek(struct open_file *f, off_t offset, int where)
418 {
419         struct tftp_handle *tftpfile;
420         tftpfile = (struct tftp_handle *) f->f_fsdata;
421
422         switch (where) {
423         case SEEK_SET:
424                 tftpfile->off = offset;
425                 break;
426         case SEEK_CUR:
427                 tftpfile->off += offset;
428                 break;
429         default:
430                 errno = EOFFSET;
431                 return (-1);
432         }
433         return (tftpfile->off);
434 }