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